Support subpath IIS deployment as a second install method
Some checks failed
CI / backend (push) Successful in 1m41s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s

The app can run as an IIS Application under an existing site
(e.g. https://host/ops/) instead of its own site + port:

- frontend: vite base via VITE_BASE_PATH; router history, axios
  baseURL, and root-absolute asset/route paths resolve through
  utils/basePath.js withBase()
- backend: MOUNT_PATH (env or .env) wraps the app in a WSGI
  middleware that shifts the prefix into SCRIPT_NAME, so one knob
  serves API + SPA under the mount
- docs: INSTALL-WINDOWS-IIS.md section 7b runbook + troubleshooting
  rows; DEPLOY-WINDOWS-IIS.md pointer; commented examples in
  deploy/windows/web.config and .env.example

Root deployment unchanged (MOUNT_PATH unset, base '/'). Also folds
two stray root-absolute callers into the shared plumbing
(MachineForm relationship-types fetch, reports CSV window.open).
This commit is contained in:
cproudlock
2026-07-13 16:11:12 -04:00
parent 69dd6d0abe
commit 6010f01de1
19 changed files with 143 additions and 25 deletions

View File

@@ -0,0 +1,13 @@
// Single source for the app's mount path. Vite injects import.meta.env.BASE_URL
// from the build-time `base` (default '/', or e.g. '/ops/' for a subpath IIS
// mount). Every root-absolute URL to a Flask-served asset or route must go
// through withBase() so it resolves under the mount instead of the server root.
export const BASE_URL = import.meta.env.BASE_URL
// Prefix a root-absolute path (e.g. '/ge-aerospace-logo.svg', '/api', '/tv')
// with the mount base. Leaves full URLs (http, data:) untouched.
export function withBase(path) {
if (!path) return path
if (/^([a-z]+:)?\/\//i.test(path) || path.startsWith('data:')) return path
return BASE_URL + String(path).replace(/^\//, '')
}

View File

@@ -2,6 +2,7 @@
// The settings GET is public (jwt optional), so the kiosk dashboard and the
// print views can read these without auth. Fetched once and cached per page.
import { settingsApi } from '@/api'
import { withBase } from '@/utils/basePath'
let settingsCache = null
@@ -38,7 +39,7 @@ export async function getFacilityName() {
// Main site logo (sidebar, login, dashboard header). Fallback = shipped GE mark.
export async function getSiteLogo() {
return getSetting('site_logo', '/ge-aerospace-logo.svg')
return withBase(await getSetting('site_logo', '/ge-aerospace-logo.svg'))
}
// Logo composited into the center of printer QR codes. Empty = no overlay.
@@ -47,17 +48,18 @@ export async function getSiteLogo() {
export async function getQrLogo() {
const settings = await loadSettings()
const value = settings['qr_logo']
return (value === undefined || value === null) ? '/ge-monogram.svg' : value
return withBase((value === undefined || value === null) ? '/ge-monogram.svg' : value)
}
// Logo printed on machine inspection badges.
export async function getBadgeLogo() {
return getSetting('badge_logo', '/ge-aerospace-logo.svg')
return withBase(await getSetting('badge_logo', '/ge-aerospace-logo.svg'))
}
// Browser-tab favicon. Empty = keep the shipped /favicon.svg.
export async function getFavicon() {
return getSetting('site_favicon', '')
const value = await getSetting('site_favicon', '')
return value ? withBase(value) : value
}
// Brand primary color override. Empty = built-in palette from style.css.