Endpoint telemetry, PC Detail rework, a checked backup/restore path, and setup-wizard fixes
This commit is contained in:
@@ -40,9 +40,9 @@
|
||||
<div v-if="showDisplays" class="nav-section">Displays</div>
|
||||
<a v-if="showShopfloor" :href="withBase('/shopfloor')" target="_blank" class="external-link">Shopfloor Dashboard</a>
|
||||
<a v-if="hasRoute('/tv')" :href="withBase('/tv')" target="_blank" class="external-link">TV Slideshow</a>
|
||||
<a v-if="hasRoute('/parts-kiosk')" :href="withBase('/parts-kiosk')"
|
||||
<a v-if="showPartsKiosk" :href="withBase('/parts-kiosk')"
|
||||
target="_blank" class="external-link">Parts Kiosk</a>
|
||||
<a v-if="hasRoute('/usb-kiosk')" :href="withBase('/usb-kiosk')"
|
||||
<a v-if="showUsbKiosk" :href="withBase('/usb-kiosk')"
|
||||
target="_blank" class="external-link">USB Kiosk</a>
|
||||
|
||||
<router-link v-if="authStore.isAdmin" to="/settings">Settings</router-link>
|
||||
@@ -127,6 +127,7 @@ import { dashboardApi, notificationsApi } from '../api'
|
||||
import { fiscalWeek } from '@/utils/fiscalWeek'
|
||||
import { getFacilityName, getSiteLogo, getServicenowUrls, getEnvLabel } from '../utils/siteSettings'
|
||||
import { withBase } from '../utils/basePath'
|
||||
import { isPluginEnabled } from '../composables/enabledPlugins'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
@@ -138,6 +139,12 @@ const authStore = useAuthStore()
|
||||
// dead-ends on a blank page. Route existence is the true "is it in this build"
|
||||
// test - more reliable than plugin-enabled state, which a copied registry can
|
||||
// report for a plugin that was never staged.
|
||||
//
|
||||
// The kiosks need BOTH tests. Staged-but-disabled is a real state: a full build
|
||||
// stages every route, so a site that turns the plugin OFF still had the link,
|
||||
// pointing at a kiosk its backend refuses. Enabled-but-unstaged is the state the
|
||||
// paragraph above is about, and isPluginEnabled fails OPEN when it cannot reach
|
||||
// the plugin list, so it cannot carry the test on its own.
|
||||
const stagedRoutePaths = new Set(router.getRoutes().map(r => r.path))
|
||||
const hasRoute = (path) => stagedRoutePaths.has(path)
|
||||
// Shopfloor Dashboard is a core view but runs entirely on the notifications
|
||||
@@ -145,8 +152,10 @@ const hasRoute = (path) => stagedRoutePaths.has(path)
|
||||
// is empty, so gate the link on the notifications plugin being in this build.
|
||||
const showShopfloor = hasRoute('/shopfloor')
|
||||
&& (hasRoute('/notifications') || hasRoute('/calendar'))
|
||||
const showDisplays = showShopfloor || hasRoute('/tv') || hasRoute('/parts-kiosk')
|
||||
|| hasRoute('/usb-kiosk')
|
||||
const showPartsKiosk = hasRoute('/parts-kiosk') && isPluginEnabled('printedparts')
|
||||
const showUsbKiosk = hasRoute('/usb-kiosk') && isPluginEnabled('usb')
|
||||
const showDisplays = showShopfloor || hasRoute('/tv') || showPartsKiosk
|
||||
|| showUsbKiosk
|
||||
const routeViewKey = computed(() =>
|
||||
route.path.startsWith('/settings') ? '/settings' : route.path
|
||||
)
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
<div class="form-group">
|
||||
<label>Site logo <span class="hint">(optional)</span></label>
|
||||
<input type="file" accept="image/*" @change="uploadSiteLogo($event)" :disabled="logoUploading" />
|
||||
<img v-if="siteLogo" :src="siteLogo" class="wizard-map-thumb" alt="site logo" />
|
||||
<input v-model="logoFilename" type="text" class="form-control" placeholder="No file selected" readonly />
|
||||
<img v-if="siteLogo" :src="withBase(siteLogo)" class="wizard-map-thumb" alt="site logo" />
|
||||
<small class="hint">Skip to keep the shipped GE branding. You can change this later under Settings > Branding.</small>
|
||||
</div>
|
||||
</div>
|
||||
@@ -92,15 +93,17 @@
|
||||
<!-- Floor map -->
|
||||
<div v-else-if="current.key === 'map'">
|
||||
<h2>Floor map</h2>
|
||||
<p class="hint">Upload your facility blueprint (light and dark), and set its native pixel size.</p>
|
||||
<p class="hint">Upload your facility blueprint (light and dark) for the default level, and set its native pixel size. Additional levels can be added later in Settings.</p>
|
||||
<div class="form-group">
|
||||
<label>Blueprint image (light theme)</label>
|
||||
<input type="file" accept="image/*" @change="uploadBlueprint('light', $event)" :disabled="mapUploading" />
|
||||
<input v-model="blueprintLightFilename" type="text" class="form-control" placeholder="No file selected" readonly />
|
||||
<img v-if="blueprintLight" :src="withBase(blueprintLight)" class="wizard-map-thumb" alt="light blueprint" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Blueprint image (dark theme)</label>
|
||||
<input type="file" accept="image/*" @change="uploadBlueprint('dark', $event)" :disabled="mapUploading" />
|
||||
<input v-model="blueprintDarkFilename" type="text" class="form-control" placeholder="No file selected" readonly />
|
||||
<img v-if="blueprintDark" :src="withBase(blueprintDark)" class="wizard-map-thumb dark" alt="dark blueprint" />
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@@ -203,14 +206,24 @@ const blueprintLight = ref('')
|
||||
const blueprintDark = ref('')
|
||||
const logoUploading = ref(false)
|
||||
const siteLogo = ref('')
|
||||
// A file input shows the chosen name in the browser chrome, which the kiosk-
|
||||
// style full-screen wizard hides, so the operator had no confirmation that the
|
||||
// file they picked was the one being uploaded.
|
||||
const logoFilename = ref('')
|
||||
const blueprintLightFilename = ref('')
|
||||
const blueprintDarkFilename = ref('')
|
||||
|
||||
async function uploadSiteLogo(event) {
|
||||
const file = event.target.files[0]
|
||||
if (!file) return
|
||||
logoFilename.value = file.name
|
||||
logoUploading.value = true
|
||||
try {
|
||||
const { data } = await settingsApi.uploadBrandingLogo('site', file)
|
||||
siteLogo.value = data?.data?.value || ''
|
||||
// Same path every time, so without a cache-buster the preview shows the
|
||||
// PREVIOUS logo and the upload reads as having silently failed. Display
|
||||
// only - nothing persists this value.
|
||||
siteLogo.value = data?.data?.value ? `${data.data.value}?t=${Date.now()}` : ''
|
||||
toast.success('Logo uploaded')
|
||||
} catch (err) {
|
||||
toast.error(apiError(err, 'Upload failed'))
|
||||
@@ -223,10 +236,12 @@ async function uploadSiteLogo(event) {
|
||||
async function uploadBlueprint(theme, event) {
|
||||
const file = event.target.files[0]
|
||||
if (!file) return
|
||||
if (theme === 'light') blueprintLightFilename.value = file.name
|
||||
else blueprintDarkFilename.value = file.name
|
||||
mapUploading.value = true
|
||||
try {
|
||||
const { data } = await settingsApi.uploadMapBlueprint(theme, file)
|
||||
const url = data?.data?.value
|
||||
const url = data?.data?.value ? `${data.data.value}?t=${Date.now()}` : ''
|
||||
if (theme === 'light') blueprintLight.value = url
|
||||
else blueprintDark.value = url
|
||||
toast.success('Blueprint uploaded')
|
||||
@@ -307,6 +322,17 @@ onMounted(async () => {
|
||||
} catch (err) { /* not set yet */ }
|
||||
}
|
||||
}
|
||||
// Show whatever logo the site is already on, so re-running the wizard does
|
||||
// not present an empty slot as if branding had never been set. Stored as a
|
||||
// root-absolute path; the template applies withBase, so do NOT prefix here or
|
||||
// a subpath mount gets it twice (/shopdb/shopdb/...).
|
||||
const SHIPPED_LOGO = '/ge-aerospace-logo.svg'
|
||||
try {
|
||||
const response = await settingsApi.get('site_logo')
|
||||
siteLogo.value = response.data?.data?.value || SHIPPED_LOGO
|
||||
} catch (err) {
|
||||
siteLogo.value = SHIPPED_LOGO
|
||||
}
|
||||
})
|
||||
|
||||
async function saveStep() {
|
||||
|
||||
Reference in New Issue
Block a user