From 95abe727290ed8dbb5e371f32a92472e1775feab Mon Sep 17 00:00:00 2001 From: Cam Proudlock Date: Sun, 6 Sep 2026 19:25:02 -0400 Subject: [PATCH] Endpoint telemetry, PC Detail rework, a checked backup/restore path, and setup-wizard fixes --- frontend/src/views/AppLayout.vue | 17 +++++++++++---- frontend/src/views/SetupWizard.vue | 34 ++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/frontend/src/views/AppLayout.vue b/frontend/src/views/AppLayout.vue index 04a81235..cbd5bedd 100644 --- a/frontend/src/views/AppLayout.vue +++ b/frontend/src/views/AppLayout.vue @@ -40,9 +40,9 @@ Shopfloor Dashboard TV Slideshow - Parts Kiosk - USB Kiosk Settings @@ -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 ) diff --git a/frontend/src/views/SetupWizard.vue b/frontend/src/views/SetupWizard.vue index a4898dd6..ae76bdb6 100644 --- a/frontend/src/views/SetupWizard.vue +++ b/frontend/src/views/SetupWizard.vue @@ -28,7 +28,8 @@
- site logo + + site logo Skip to keep the shipped GE branding. You can change this later under Settings > Branding.
@@ -92,15 +93,17 @@

Floor map

-

Upload your facility blueprint (light and dark), and set its native pixel size.

+

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.

+ light blueprint
+ dark blueprint
@@ -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() {