Files
clawstor/docs/dashboard-v2.md
T
Omar Sobh b431475af7
Build with clawstor cache / Cargo build (clawstor-cached) (pull_request) Successful in 17s
dashboard-v2 (PR 1): design doc + backend read-only endpoints
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/*).
2026-07-14 15:52:47 -07:00

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

  1. 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.
  2. Command-center landing page. At-a-glance health strip + key metrics + recent-events feed. Operator answers "is anything on fire?" in <2 s.
  3. Node-detail drill-down. Click any node in the strip → detail page with per-node metrics, timers, storage counts, journal tail.
  4. Cross-cutting content browsers. Blobs / Tags / Refs / Snapshots aggregated across the fleet, searchable, click-through to detail.
  5. 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

  1. Ship v2 backend endpoints — legacy /api/* untouched.
  2. Ship v2 frontend at dashboard-v2/, built to static-v2/.
  3. claw-store serve --v2-static-dir <path> — new flag serves v2 assets at /v2 while / still serves legacy for a burn-in period.
  4. After burn-in: swap defaults; legacy accessible at /legacy.
  5. 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.