Monorepo for Aesthetic.Computer aesthetic.computer
core toolchain fleet DASHBOARD.md
3.9 kB

Fleet dashboard — design note #

A read-mostly web view of the fleet, sibling to the auth/status dashboards on lith.aesthetic.computer. It answers, at a glance and for humans, the same question fleet-mcp answers for agents: what machines exist, what can they do, which are alive right now.

What it shows #

  • Machine tiles, grouped by designation (agent-endpoint · compute-node · control · build · service · display · legacy). Each tile:
    • emoji + label + canonical name
    • live status dot: 🟢 online · ⚪ offline · ❔ unknown, with last-seen relative time for offline nodes (from tailscale status)
    • designation badge + capability chips (gpu, mlx, chromium-pool, …)
    • hardware line (chip / cores / memory) and OS
    • a ⚠ review flag for entries normalize-machines.mjs hasn't confirmed
  • Filter bar: by designation, by capability chip, online-only toggle. Same vocabulary as fleet_find.
  • Fleet header counts: N online / M total, and per-designation tallies.
  • Optional agent/identity column once hermes lands: which agent-endpoint owns which identity, last heartbeat.

Deliberately not shown in the public/tailnet page: raw IPs, ssh key paths, passwords, droplet IDs. Those stay server-side (vault) — the page renders labels, designations, capabilities, and liveness only. (PII-out rule.)

Where it lives — recommendation #

Recommended: a tailnet-gated page, not a public lith route.

Two viable homes:

  1. lith Express route — add system/netlify/functions/fleet.mjs (lith adapts function files as routes; the path is historical). It would read the vault registry server-side + shell tailscale status --json on whatever host serves it. Problem: lith is public-internet facing and does not run on the tailnet, so (a) it can't see tailscale status for @jeffrey's tailnet, and (b) a fleet map is exactly the private inventory the "slab is public / keep PII out" rule wants off the public web. Workable only as a JSON API behind real auth, fed by a pushed status blob — more plumbing than it's worth for v1.

  2. A small page served on the tailnet from jasellite (the always-on services appliance already exposing authed tailnet APIs on ports like :7765). It has first-hand tailscale status, already holds the vault-style private config, and is only reachable by devices on the tailnet — so tailnet membership is the auth boundary. This is the recommendation for v1.

    Shape: a /api/fleet JSON endpoint that runs the exact fleet-mcp.mjs merge logic (factor the registry+tailscale merge into a shared fleet.mjs module that both the MCP and the endpoint import), plus a static single-file HTML dashboard that polls it every ~10s. Zero new datastore — the vault file + live tailscale status are the whole backend.

Auth considerations #

  • v1: tailnet membership is the gate (page bound to the tailnet interface on jasellite; not exposed publicly). No extra login needed — if you can reach it, you're on the tailnet.
  • If it must go on lith/public later: put it behind the same session/handle auth the other lith dashboards use, and serve a redacted projection only (labels + designation + capabilities + online dot — never IPs/keys). Feed it a status blob that jasellite pushes up on a timer, rather than giving the public box tailnet visibility.
  • Ties into AC shared session (~/.ac-token) if per-viewer identity is ever wanted, but that's overkill for a personal tailnet inventory.

Build order (when picked up) #

  1. Extract the merge (loadRegistry + tailscaleStatus + liveFor) from fleet-mcp.mjs into a shared fleet.mjs; have the MCP import it.
  2. Add GET /api/fleet on jasellite returning the redacted JSON projection.
  3. Static HTML dashboard (tiles + filter + poll). No framework needed.