Commit Graph
7 Commits
Author SHA1 Message Date
Audiobuzz 1c68c37824 fleet-manager: chrony tracking metrics relative to the NTP source
The agent only reported chrony as synced|unsynced — nothing about how well the
clock tracks. Parse `chronyc tracking` for the source-relative metrics.

- proto: HealthSnapshot.chrony_offset_seconds (16, signed system-time offset,
  fast=+), chrony_source (17, reference name/IP), chrony_stratum (18); regenerated.
- agent: replace chronyState() with chronyTracking()/parseChrony() — one exec,
  parses Leap status + Reference ID + Stratum + System time offset (+ test).
- manager: Health.{ChronyOffset,ChronySource,ChronyStratum}; snapshotToHealth maps
  them; influx writes chrony_offset_seconds/_stratum/_source and whitelists the
  offset + stratum as graphable fields.
- UI: chronySummary() + offsetMs() — the chrony row now shows
  "synced · 192.168.50.10 · s3 · +0.01ms" instead of just "synced".
2026-07-10 06:47:27 +00:00
Audiobuzz df7dad81b7 fleet-manager: host uptime metric + reboot-pending status
The manager lets an admin schedule a reboot (desired.reboot_at) but never showed
whether it had happened. Add host uptime to the health snapshot and derive a
reboot status from it.

- proto: HealthSnapshot.uptime_seconds (15); regenerated pb.go (manager + agent copy).
- agent: probe /proc/uptime -> uptime_seconds (with parser + test).
- manager: Health.UptimeSeconds cached in inventory; snapshotToHealth maps it;
  influx writes + whitelists uptime_seconds as a queryable field.
- UI: fmt.js dur() + rebootStatus() — boot_time = last_seen - uptime (manager clock
  frame, no host/manager sync needed), compared to reboot_at. Monitor host detail
  shows an "uptime" row and a "reboot" row: pending in X / overdue by X / rebooted.

Observability-only — works before the reboot ACTUATOR lands (deferred). Once a host
boots after the scheduled time (BMC/manual now, agent later), the badge flips to
"rebooted" automatically.
2026-07-10 06:41:09 +00:00
Audiobuzz e6ae8c4b23 fleet-manager UI: auto-refresh the selected host's metric graphs
The Monitor tab polled /api/hosts every 10s (roster + host-detail refresh) but
only fetched a host's metric series on click, so the tmpfs/cpu/temp graphs went
stale until you re-selected. Extract loadMetrics() and call it from the same 10s
tick when a host is selected, so the graphs stay live.
2026-07-10 06:22:33 +00:00
Audiobuzz 12c4296f9d fleet-manager ui: label target-version pin as recorded intent (not yet enforced)
Per the option-A decision (deploy current design without per-host versioning):
add an honest note on the Lifecycle panel that kiosk-url/reboot/drain reconcile
via the agent but per-host boot-version assignment (dnsmasq cohort steering) is
not yet wired, so a version pin is stored + pushed but does not change what the
host boots. Avoids implying the pin takes effect.
2026-07-04 06:19:21 +00:00
Audiobuzz f71a55eab5 fleet-manager ui: target version as a dropdown (fleet-known + custom)
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.)
2026-07-04 06:08:15 +00:00
Audiobuzz 20b29d9990 fleet-manager ui: readability + reboot-now
- Contrast: lighten the muted/faint text tokens (AA on the hull panels) and make
  Monitor host-card data values bright ink against dim faint labels, so version/
  tmpfs/seen details read clearly on the blue-grey panels (was grey-on-grey).
- Lifecycle: reboot selector now offers none | now | at... — 'now' sets
  reboot_at to the current time (agent reboots on reconcile); still confirm-gated
  as a high-consequence action.
2026-07-04 06:05:29 +00:00
Audiobuzz fae3624c58 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.
2026-07-04 05:54:41 +00:00