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
This commit is contained in:
@@ -40,9 +40,9 @@
|
|||||||
<div v-if="showDisplays" class="nav-section">Displays</div>
|
<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="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('/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>
|
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>
|
target="_blank" class="external-link">USB Kiosk</a>
|
||||||
|
|
||||||
<router-link v-if="authStore.isAdmin" to="/settings">Settings</router-link>
|
<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 { fiscalWeek } from '@/utils/fiscalWeek'
|
||||||
import { getFacilityName, getSiteLogo, getServicenowUrls, getEnvLabel } from '../utils/siteSettings'
|
import { getFacilityName, getSiteLogo, getServicenowUrls, getEnvLabel } from '../utils/siteSettings'
|
||||||
import { withBase } from '../utils/basePath'
|
import { withBase } from '../utils/basePath'
|
||||||
|
import { isPluginEnabled } from '../composables/enabledPlugins'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
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"
|
// 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
|
// test - more reliable than plugin-enabled state, which a copied registry can
|
||||||
// report for a plugin that was never staged.
|
// 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 stagedRoutePaths = new Set(router.getRoutes().map(r => r.path))
|
||||||
const hasRoute = (path) => stagedRoutePaths.has(path)
|
const hasRoute = (path) => stagedRoutePaths.has(path)
|
||||||
// Shopfloor Dashboard is a core view but runs entirely on the notifications
|
// 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.
|
// is empty, so gate the link on the notifications plugin being in this build.
|
||||||
const showShopfloor = hasRoute('/shopfloor')
|
const showShopfloor = hasRoute('/shopfloor')
|
||||||
&& (hasRoute('/notifications') || hasRoute('/calendar'))
|
&& (hasRoute('/notifications') || hasRoute('/calendar'))
|
||||||
const showDisplays = showShopfloor || hasRoute('/tv') || hasRoute('/parts-kiosk')
|
const showPartsKiosk = hasRoute('/parts-kiosk') && isPluginEnabled('printedparts')
|
||||||
|| hasRoute('/usb-kiosk')
|
const showUsbKiosk = hasRoute('/usb-kiosk') && isPluginEnabled('usb')
|
||||||
|
const showDisplays = showShopfloor || hasRoute('/tv') || showPartsKiosk
|
||||||
|
|| showUsbKiosk
|
||||||
const routeViewKey = computed(() =>
|
const routeViewKey = computed(() =>
|
||||||
route.path.startsWith('/settings') ? '/settings' : route.path
|
route.path.startsWith('/settings') ? '/settings' : route.path
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,7 +28,8 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Site logo <span class="hint">(optional)</span></label>
|
<label>Site logo <span class="hint">(optional)</span></label>
|
||||||
<input type="file" accept="image/*" @change="uploadSiteLogo($event)" :disabled="logoUploading" />
|
<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>
|
<small class="hint">Skip to keep the shipped GE branding. You can change this later under Settings > Branding.</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -92,15 +93,17 @@
|
|||||||
<!-- Floor map -->
|
<!-- Floor map -->
|
||||||
<div v-else-if="current.key === 'map'">
|
<div v-else-if="current.key === 'map'">
|
||||||
<h2>Floor map</h2>
|
<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">
|
<div class="form-group">
|
||||||
<label>Blueprint image (light theme)</label>
|
<label>Blueprint image (light theme)</label>
|
||||||
<input type="file" accept="image/*" @change="uploadBlueprint('light', $event)" :disabled="mapUploading" />
|
<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" />
|
<img v-if="blueprintLight" :src="withBase(blueprintLight)" class="wizard-map-thumb" alt="light blueprint" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Blueprint image (dark theme)</label>
|
<label>Blueprint image (dark theme)</label>
|
||||||
<input type="file" accept="image/*" @change="uploadBlueprint('dark', $event)" :disabled="mapUploading" />
|
<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" />
|
<img v-if="blueprintDark" :src="withBase(blueprintDark)" class="wizard-map-thumb dark" alt="dark blueprint" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
@@ -203,14 +206,24 @@ const blueprintLight = ref('')
|
|||||||
const blueprintDark = ref('')
|
const blueprintDark = ref('')
|
||||||
const logoUploading = ref(false)
|
const logoUploading = ref(false)
|
||||||
const siteLogo = ref('')
|
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) {
|
async function uploadSiteLogo(event) {
|
||||||
const file = event.target.files[0]
|
const file = event.target.files[0]
|
||||||
if (!file) return
|
if (!file) return
|
||||||
|
logoFilename.value = file.name
|
||||||
logoUploading.value = true
|
logoUploading.value = true
|
||||||
try {
|
try {
|
||||||
const { data } = await settingsApi.uploadBrandingLogo('site', file)
|
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')
|
toast.success('Logo uploaded')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(apiError(err, 'Upload failed'))
|
toast.error(apiError(err, 'Upload failed'))
|
||||||
@@ -223,10 +236,12 @@ async function uploadSiteLogo(event) {
|
|||||||
async function uploadBlueprint(theme, event) {
|
async function uploadBlueprint(theme, event) {
|
||||||
const file = event.target.files[0]
|
const file = event.target.files[0]
|
||||||
if (!file) return
|
if (!file) return
|
||||||
|
if (theme === 'light') blueprintLightFilename.value = file.name
|
||||||
|
else blueprintDarkFilename.value = file.name
|
||||||
mapUploading.value = true
|
mapUploading.value = true
|
||||||
try {
|
try {
|
||||||
const { data } = await settingsApi.uploadMapBlueprint(theme, file)
|
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
|
if (theme === 'light') blueprintLight.value = url
|
||||||
else blueprintDark.value = url
|
else blueprintDark.value = url
|
||||||
toast.success('Blueprint uploaded')
|
toast.success('Blueprint uploaded')
|
||||||
@@ -307,6 +322,17 @@ onMounted(async () => {
|
|||||||
} catch (err) { /* not set yet */ }
|
} 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() {
|
async function saveStep() {
|
||||||
|
|||||||
Reference in New Issue
Block a user