dashboard-v2 frontend: node column + clickable pills on storage tabs
Build with clawstor cache / Cargo build (clawstor-cached) (pull_request) Failing after 3s

All storage rows now carry a 'node' field from the aggregator.
Adds a small green-pill component that renders the source node
name; click drills into that node's detail page.

Also fixes the TS interfaces to match the new backend shape
(node field added to Blob/Tag/Ref/Snapshot/RefTracking types).
This commit is contained in:
Omar Sobh
2026-07-14 16:44:06 -07:00
parent 1211f0d891
commit b672037e42
5 changed files with 48 additions and 10 deletions
+13 -4
View File
@@ -10,7 +10,8 @@ function BlobsList() {
useEffect(() => {
api.blobs().then(setRows).catch((e) => setErr(String(e)));
}, []);
return (_jsxs(_Fragment, { children: [err && _jsx(ErrorBox, { msg: err }), _jsx(Table, { headers: ['blob-id', 'size', 'chunks'], rows: rows.map((r) => [
return (_jsxs(_Fragment, { children: [err && _jsx(ErrorBox, { msg: err }), _jsx(Table, { headers: ['node', 'blob-id', 'size', 'chunks'], rows: rows.map((r) => [
_jsx(NodePill, { node: r.node }, "n"),
_jsxs("span", { className: "font-mono text-xs", children: [r.blob_id_hex.slice(0, 24), "\u2026"] }, "hex"),
fmtBytes(r.size_bytes),
r.chunk_count.toString(),
@@ -23,7 +24,8 @@ function TagsList() {
useEffect(() => {
api.tags(prefix).then(setRows).catch((e) => setErr(String(e)));
}, [prefix]);
return (_jsxs(_Fragment, { children: [_jsx("input", { value: prefix, onChange: (e) => setPrefix(e.target.value), placeholder: "filter prefix \u2014 e.g. clawverse:", className: "w-full sm:w-96 rounded bg-slate-900 border border-slate-800 px-3 py-2 text-sm font-mono" }), err && _jsx(ErrorBox, { msg: err }), _jsx(Table, { headers: ['key', 'blob-id'], rows: rows.map((r) => [
return (_jsxs(_Fragment, { children: [_jsx("input", { value: prefix, onChange: (e) => setPrefix(e.target.value), placeholder: "filter prefix \u2014 e.g. clawverse:", className: "w-full sm:w-96 rounded bg-slate-900 border border-slate-800 px-3 py-2 text-sm font-mono" }), err && _jsx(ErrorBox, { msg: err }), _jsx(Table, { headers: ['node', 'key', 'blob-id'], rows: rows.map((r) => [
_jsx(NodePill, { node: r.node }, "n"),
_jsx("span", { className: "font-mono text-sm", children: r.key }, "k"),
_jsxs("span", { className: "font-mono text-xs text-slate-400", children: [r.value_hex.slice(0, 24), "\u2026"] }, "v"),
]) }), _jsx(Footer, { count: rows.length })] }));
@@ -34,7 +36,8 @@ function RefsList() {
useEffect(() => {
api.refs().then(setRows).catch((e) => setErr(String(e)));
}, []);
return (_jsxs(_Fragment, { children: [err && _jsx(ErrorBox, { msg: err }), _jsx(Table, { headers: ['fingerprint', 'blob-id'], rows: rows.map((r) => [
return (_jsxs(_Fragment, { children: [err && _jsx(ErrorBox, { msg: err }), _jsx(Table, { headers: ['node', 'fingerprint', 'blob-id'], rows: rows.map((r) => [
_jsx(NodePill, { node: r.node }, "n"),
_jsxs("span", { className: "font-mono text-xs", children: [r.fingerprint_hex.slice(0, 24), "\u2026"] }, "fp"),
_jsxs("span", { className: "font-mono text-xs text-slate-400", children: [r.blob_id_hex.slice(0, 24), "\u2026"] }, "b"),
]) }), _jsx(Footer, { count: rows.length })] }));
@@ -45,7 +48,8 @@ function SnapshotsList() {
useEffect(() => {
api.snapshots().then(setRows).catch((e) => setErr(String(e)));
}, []);
return (_jsxs(_Fragment, { children: [err && _jsx(ErrorBox, { msg: err }), _jsx(Table, { headers: ['name', 'created', 'blobs', 'json size'], rows: rows.map((r) => [
return (_jsxs(_Fragment, { children: [err && _jsx(ErrorBox, { msg: err }), _jsx(Table, { headers: ['node', 'name', 'created', 'blobs', 'json size'], rows: rows.map((r) => [
_jsx(NodePill, { node: r.node }, "node"),
_jsx("span", { className: "font-mono text-sm", children: r.name }, "n"),
fmtAge(r.created_at_unix),
r.blob_count.toString(),
@@ -61,3 +65,8 @@ function Footer({ count }) {
function ErrorBox({ msg }) {
return (_jsx("div", { className: "rounded border border-red-800 bg-red-950/40 p-3 text-red-300 text-sm mb-2", children: msg }));
}
/// Small pill badge for the originating node — clickable to
/// drill into that node's detail page.
function NodePill({ node }) {
return (_jsx("a", { href: `#/nodes/${node}`, className: "inline-block rounded bg-slate-800 text-emerald-300 text-xs font-mono px-2 py-0.5 hover:bg-slate-700", children: node }));
}