Build with clawstor cache / Cargo build (clawstor-cached) (pull_request) Successful in 17s
Kicks off the single-pane-of-glass command-center rewrite. Legacy /api/* handlers untouched — v2 is additive so cutover is safe. Doc: docs/dashboard-v2.md — design goals, endpoint spec, cutover plan. Backend: new serve_v2 module wired into serve.rs. Endpoints: GET /api/v2/node/local/status GET /api/v2/node/:name/status (fan-out: not-yet-impl) GET /api/v2/storage/blobs?limit&offset GET /api/v2/storage/tags?prefix GET /api/v2/storage/refs?limit&offset GET /api/v2/storage/snapshots GET /api/v2/storage/ref-tracking?repo V2State opens BlobStore / TagStore / RefStore / SnapshotStore / RefTracking under the daemon's blob_store_root + the conventional subdirs (tags-db, refs-db). All handlers are single-node reads; cross-node fan-out lands in PR 3. +6 tests: status w/ seeded blob+snapshot, cross-node returns 501, blobs pagination, tags prefix filter, snapshots list, ref-tracking repo filter. 395 tests pass. serve.rs merges v2 routes onto the axum router with shared CORS. Single-shot deploy on tank + architect will expose /api/v2/* alongside the existing /api/*. PR 2 = frontend rewrite consuming these endpoints. PR 3 = fleet fan-out (cross-node aggregation via QUIC RPC). PR 4 = action endpoints (POST scrub/gc/snapshot/pin). PR 5 = cutover (deprecate legacy /api/*).
3.9 KiB
3.9 KiB
dashboard-v2 — single-pane-of-glass command center
Redesign of the legacy claw-store serve dashboard for the current
distributed architecture.
Design goals
- One URL for the whole fleet. Hit any node's
:7700; that node fans out to every peer via existing QUIC RPC and serves an aggregated view. No "3 browser tabs" pattern. - Command-center landing page. At-a-glance health strip + key metrics + recent-events feed. Operator answers "is anything on fire?" in <2 s.
- Node-detail drill-down. Click any node in the strip → detail page with per-node metrics, timers, storage counts, journal tail.
- Cross-cutting content browsers. Blobs / Tags / Refs / Snapshots aggregated across the fleet, searchable, click-through to detail.
- Trigger actions from the UI. Scrub, GC, snapshot-create, pin/unpin — anything currently a CLI invocation.
Non-goals (v2 scope)
- Real-time streaming metrics beyond SSE snapshots. Prometheus stays the source of truth for graphs; this dashboard is for state + actions, not observability.
- Auth beyond a shared bearer token (fleet is trust-perimeter — CA auth for the UI is future work).
- Editing configs. Read + trigger, never write config.
Backend shape
Additive /api/v2/* alongside the legacy /api/* handlers so the
cutover is safe:
/api/v2/fleet aggregated snapshot (all peers)
/api/v2/node/<name>/status this-peer or remote via gossip lookup
/api/v2/node/<name>/timers systemd timer state for the 4 timers
/api/v2/node/<name>/journal?unit=… last N lines of journalctl
/api/v2/storage/blobs?limit&offset BlobStore::list_blob_ids + summaries
/api/v2/storage/tags?prefix TagStore::list (both layers)
/api/v2/storage/refs?limit&offset RefStore::list unioned
/api/v2/storage/snapshots SnapshotStore::list
/api/v2/storage/ref-tracking?repo RefTracking::list_all
/api/v2/cache/metrics router.metrics().snapshot() + gossiped
/api/v2/peers gossip snapshot + last-probe route
/api/v2/events SSE — fleet event stream
POST /api/v2/actions/scrub
POST /api/v2/actions/gc { evict_to_gb? }
POST /api/v2/actions/snapshot { name }
POST /api/v2/actions/pin { key, blob_id_hex }
POST /api/v2/actions/unpin { key }
Aggregation model
Server-side fan-out. When the dashboard requests /api/v2/fleet,
the serving node walks its gossip peer list and issues a
PeerStatus RPC to each. Results collated into one JSON.
Trade-offs:
- Simpler frontend (no per-peer TLS material in browser).
- Backend caches results per-endpoint (5 s TTL) so 10 dashboard tabs don't cause 30 peer RPCs.
- Any node can serve the dashboard — no "coordinator" single point of failure.
Frontend shape (implementation PR follows)
Routes:
/ → CommandCenter
/nodes/<name> → NodeDetail
/storage/blobs → StorageBrowser (blobs tab)
/storage/tags → StorageBrowser (tags tab)
/storage/refs → StorageBrowser (refs tab)
/storage/snapshots → StorageBrowser (snapshots tab)
/refs/tracking → RefTracking
/ops → OpsPanel (timers + actions + journal)
React + Vite + Tailwind, single SPA served from /usr/share/claw-store/static-v2/.
Cutover plan
- Ship v2 backend endpoints — legacy
/api/*untouched. - Ship v2 frontend at
dashboard-v2/, built tostatic-v2/. claw-store serve --v2-static-dir <path>— new flag serves v2 assets at/v2while/still serves legacy for a burn-in period.- After burn-in: swap defaults; legacy accessible at
/legacy. - Remove legacy after 30 days.
Auth
Config-driven: [dashboard] api_token = "...". Bearer required for
all POST endpoints; GET endpoints open on trusted-fleet networks
(tailscale + LAN). If token absent → POSTs disabled entirely
(read-only dashboard). Same shape as the legacy dashboard.