fleet-manager: checkpoint 6 — the admin SPA (fleet command deck)

The themed single-page admin UI (serving/fleet-manager/ui/), embedded into the
binary and served same-origin over the admin plane.

- Svelte 5 + Tailwind 4 + Vite; a sibling of serving/kiosk-app. Theme = LYSANDRA
  'restricted-violet' (high-security evolution of the kiosk's flight-deck DNA):
  amber deck, VIOLET reserved for privileged/destructive + auth surfaces, teal
  telemetry, red alert, clearance chrome (corner-reticle panels, scanlines, mono
  clearance labels, secure-link status bar).
- Passkey gate: Login (usernameless discoverable WebAuthn) + Enrol (from the
  register-admin one-time link) via navigator.credentials, with base64url<->buffer
  helpers and the go-webauthn response shapes.
- Three pillars over the JSON API: Monitor (fleet roster + version-drift + health
  + per-host detail with bespoke SVG telemetry charts), Authorise (pending queue:
  fingerprint cross-check, approve+bind in violet, deny), Lifecycle (desired-state
  form; drain/reboot confirm-gated). Session-aware fetch client (cookie + CSRF).
- go:embed behind the  build tag (spa_embed.go / spa_stub.go) so the
  default go build/test need no dist/; mounted at / (api/auth/healthz win).

Verified: npm build clean (71KB JS / 33KB CSS + self-hosted fonts); default +
-tags embedspa go builds; smoke test serves index + assets + API through the SPA
origin. Against an insecure+seeded backend the deck renders passkey-free for
theme work. dist/ + node_modules gitignored.
This commit is contained in:
Audiobuzz
2026-07-04 05:54:41 +00:00
parent c40dd5ac93
commit fae3624c58
30 changed files with 3203 additions and 1 deletions
@@ -0,0 +1,42 @@
<script>
import { router } from '../lib/router.svelte.js'
import { passkeyRegister, passkeySupported } from '../lib/webauthn.js'
let busy = $state(false)
let done = $state(false)
let who = $state('')
let error = $state('')
async function enrol() {
busy = true
error = ''
try {
const r = await passkeyRegister(router.token)
who = r.admin
done = true
} catch (e) {
error = e?.message || 'enrolment failed'
} finally {
busy = false
}
}
</script>
<div class="relative z-10 flex min-h-full items-center justify-center p-6">
<div class="panel panel-restricted w-full max-w-md p-8 text-center">
<div class="tag mb-2">passkey enrolment</div>
<h1 class="mb-4 font-display text-xl text-amber">Register a hardware passkey</h1>
{#if done}
<p class="mb-4"><span class="pill pill-ok">enrolled · {who}</span></p>
<a class="btn" href="#/monitor">continue → sign in</a>
{:else if !router.token}
<p class="pill pill-alert">missing enrolment token — use the link from <code>register-admin</code></p>
{:else if !passkeySupported()}
<p class="pill pill-alert">this browser has no WebAuthn support</p>
{:else}
<p class="mb-6 text-sm text-muted">Insert your YubiKey and tap to bind it to this fleet console.</p>
<button class="btn btn-restricted w-full py-3" disabled={busy} onclick={enrol}>
{busy ? 'waiting for passkey…' : '⚿ register passkey'}
</button>
{/if}
{#if error}<p class="mt-4"><span class="pill pill-alert">{error}</span></p>{/if}
</div>
</div>