Replace the free-text target-version field with a select populated from versions known across the fleet (each host's running + targeted version, newest first), plus a 'custom...' escape to a text input for a version not yet seen. Avoids typos on the pin. (Authoritative serving-side source is a follow-up.)
fleet-manager UI — the fleet command deck
The admin SPA for fleet-manager. Svelte 5 + Tailwind 4 + Vite, themed
LYSANDRA / restricted-violet (a high-security evolution of the kiosk-app's
LYSANDRA flight-deck: amber deck, violet reserved for privileged/destructive +
auth surfaces, teal telemetry, red alert, clearance chrome). A sibling of
serving/kiosk-app, kept in-repo for now (may graduate to its own submodule).
Three pillars over the manager's JSON API, plus the passkey gate:
- Login / Enrol — WebAuthn ceremonies (
navigator.credentials). Enrol is reached via the one-time link fromfleet-manager register-admin. - Monitor — fleet roster (online / version-drift / health) + per-host detail
with bespoke SVG telemetry charts (from
/api/hosts/{serial}/metrics). - Authorise — pending-enrolment queue: cross-check the fingerprint, approve (violet) + bind, or deny.
- Lifecycle — per-host desired-state (version pin, kiosk URL, reboot, drain); destructive changes are confirm-gated.
Build / embed
The built SPA is embedded into the fleet-manager binary via go:embed and
served same-origin over the admin plane.
npm ci
npm run build # -> dist/ (gitignored)
cd .. && go build -tags embedspa # embeds dist/ into the binary
The default go build (no tag) ships a stub instead, so Go builds/tests don't
require dist/. The Dockerfile does the two-stage build (npm → dist → go build
-tags embedspa).
Dev
npm run dev # Vite on :5173, proxies /api + /auth to :9500
Point it at a locally-running manager. Against an insecure+seeded manager
(FLEET_MGR_ADMIN_INSECURE=true FLEET_MGR_SEED=true), /api/whoami returns
dev-insecure, so the SPA renders the pillars without a passkey — handy for
theme/layout work. Passkey login/enrol need the manager in secure mode with a
valid RP origin + a WebAuthn authenticator.