From 64e932ce5c9675d2f788fae82fff5aaec41bf529 Mon Sep 17 00:00:00 2001 From: cproudlock Date: Sun, 6 Sep 2026 19:24:50 -0400 Subject: [PATCH] Rescue the work-PC wizard and kiosk-link edits Two tracked files had been edited directly in the work-PC relay clone, which only fetches bundles and pushes them to GitHub. Nothing there reaches gitea, and the next bundle pull would have overwritten both silently. Brought across here, with three changes to what was written. Kiosk links now need the route AND the plugin. The edit swapped hasRoute for isPluginEnabled, which trades one wrong answer for another: a full build stages every route, so a site that turns printedparts or usb OFF still saw a link to a kiosk its backend refuses - the symptom being fixed - but isPluginEnabled fails OPEN when it cannot reach the plugin list, and a copied registry can report a plugin that was never staged into this build. That is what the comment above those lines was already warning about. Both tests together answer both states. The wizard shows the filename it is uploading. The browser puts it in chrome the full-screen wizard hides, so an operator picking a file got no confirmation it was the one being sent. Image previews get a cache-buster. The stored path does not change between uploads, so the preview showed the PREVIOUS logo or blueprint and the upload read as having silently failed. Display only - saveStep persists form values, never these, so no cache-busted URL is ever written to a setting. The wizard also loads the current site logo on mount, so re-running it does not present an empty slot as though branding had never been set. The shipped-asset fallback stays a root-absolute path: the template applies withBase, and prefixing it here as well produced /shopdb/shopdb/ge-aerospace-logo.svg on a subpath mount. Frontend 391 passing. Scope: product --- 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() {