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

99 lines
3.9 KiB
Markdown

# 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.