dashboard-v2: node pill column on storage tabs #98
@@ -22,23 +22,28 @@ export interface FleetSnapshot {
|
|||||||
nodes: NodeStatusV2[];
|
nodes: NodeStatusV2[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Aggregator responses tag every row with the originating node.
|
||||||
export interface BlobSummary {
|
export interface BlobSummary {
|
||||||
|
node: string;
|
||||||
blob_id_hex: string;
|
blob_id_hex: string;
|
||||||
size_bytes: number;
|
size_bytes: number;
|
||||||
chunk_count: number;
|
chunk_count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TagSummary {
|
export interface TagSummary {
|
||||||
|
node: string;
|
||||||
key: string;
|
key: string;
|
||||||
value_hex: string;
|
value_hex: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RefSummary {
|
export interface RefSummary {
|
||||||
|
node: string;
|
||||||
fingerprint_hex: string;
|
fingerprint_hex: string;
|
||||||
blob_id_hex: string;
|
blob_id_hex: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SnapshotSummary {
|
export interface SnapshotSummary {
|
||||||
|
node: string;
|
||||||
name: string;
|
name: string;
|
||||||
created_at_unix: number;
|
created_at_unix: number;
|
||||||
blob_count: number;
|
blob_count: number;
|
||||||
@@ -46,6 +51,7 @@ export interface SnapshotSummary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface RefTrackingItem {
|
export interface RefTrackingItem {
|
||||||
|
node: string;
|
||||||
fingerprint_hex: string;
|
fingerprint_hex: string;
|
||||||
repo: string;
|
repo: string;
|
||||||
refs: string[];
|
refs: string[];
|
||||||
|
|||||||
@@ -21,5 +21,5 @@ export function RefTrackingPage() {
|
|||||||
}
|
}
|
||||||
return [...g.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
return [...g.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
||||||
}, [rows]);
|
}, [rows]);
|
||||||
return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold text-slate-100", children: "Ref-tracking" }), _jsxs("p", { className: "text-sm text-slate-500 mt-1", children: ["Every cached fingerprint's producing ", _jsx("span", { className: "font-mono", children: "(repo, git-ref)" }), ". Feeds the nightly ", _jsx("span", { className: "font-mono", children: "cluster-ref-sweep" }), " that reaps fingerprints whose refs are gone from Gitea."] })] }), _jsx("input", { value: repoFilter, onChange: (e) => setRepoFilter(e.target.value), placeholder: "filter repo \u2014 e.g. clawverse/clawstor", className: "w-full sm:w-96 rounded bg-slate-900 border border-slate-800 px-3 py-2 text-sm font-mono" }), err && (_jsx("div", { className: "rounded border border-red-800 bg-red-950/40 p-3 text-red-300 text-sm", children: err })), grouped.length === 0 && (_jsx("div", { className: "text-slate-500 text-sm py-6", children: "no ref-tracking entries yet" })), grouped.map(([repo, items]) => (_jsxs("div", { className: "rounded border border-slate-800 overflow-hidden", children: [_jsxs("div", { className: "bg-slate-900/60 px-4 py-2 flex items-baseline justify-between", children: [_jsx("span", { className: "font-mono text-sm text-emerald-300", children: repo }), _jsxs("span", { className: "text-xs text-slate-500", children: [items.length, " fingerprint", items.length === 1 ? '' : 's'] })] }), _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "text-slate-500 uppercase text-xs tracking-wider", children: _jsxs("tr", { children: [_jsx("th", { className: "text-left px-4 py-2 font-normal", children: "fingerprint" }), _jsx("th", { className: "text-left px-4 py-2 font-normal", children: "refs" }), _jsx("th", { className: "text-left px-4 py-2 font-normal", children: "first seen" }), _jsx("th", { className: "text-left px-4 py-2 font-normal", children: "last seen" })] }) }), _jsx("tbody", { children: items.map((it) => (_jsxs("tr", { className: "border-t border-slate-900", children: [_jsxs("td", { className: "px-4 py-2 font-mono text-xs", children: [it.fingerprint_hex.slice(0, 24), "\u2026"] }), _jsx("td", { className: "px-4 py-2 font-mono text-xs", children: it.refs.join(', ') }), _jsx("td", { className: "px-4 py-2 text-slate-400", children: fmtAge(it.first_seen_unix) }), _jsx("td", { className: "px-4 py-2 text-slate-400", children: fmtAge(it.last_seen_unix) })] }, it.fingerprint_hex))) })] })] }, repo)))] }));
|
return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold text-slate-100", children: "Ref-tracking" }), _jsxs("p", { className: "text-sm text-slate-500 mt-1", children: ["Every cached fingerprint's producing ", _jsx("span", { className: "font-mono", children: "(repo, git-ref)" }), ". Feeds the nightly ", _jsx("span", { className: "font-mono", children: "cluster-ref-sweep" }), " that reaps fingerprints whose refs are gone from Gitea."] })] }), _jsx("input", { value: repoFilter, onChange: (e) => setRepoFilter(e.target.value), placeholder: "filter repo \u2014 e.g. clawverse/clawstor", className: "w-full sm:w-96 rounded bg-slate-900 border border-slate-800 px-3 py-2 text-sm font-mono" }), err && (_jsx("div", { className: "rounded border border-red-800 bg-red-950/40 p-3 text-red-300 text-sm", children: err })), grouped.length === 0 && (_jsx("div", { className: "text-slate-500 text-sm py-6", children: "no ref-tracking entries yet" })), grouped.map(([repo, items]) => (_jsxs("div", { className: "rounded border border-slate-800 overflow-hidden", children: [_jsxs("div", { className: "bg-slate-900/60 px-4 py-2 flex items-baseline justify-between", children: [_jsx("span", { className: "font-mono text-sm text-emerald-300", children: repo }), _jsxs("span", { className: "text-xs text-slate-500", children: [items.length, " fingerprint", items.length === 1 ? '' : 's'] })] }), _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "text-slate-500 uppercase text-xs tracking-wider", children: _jsxs("tr", { children: [_jsx("th", { className: "text-left px-4 py-2 font-normal", children: "node" }), _jsx("th", { className: "text-left px-4 py-2 font-normal", children: "fingerprint" }), _jsx("th", { className: "text-left px-4 py-2 font-normal", children: "refs" }), _jsx("th", { className: "text-left px-4 py-2 font-normal", children: "first seen" }), _jsx("th", { className: "text-left px-4 py-2 font-normal", children: "last seen" })] }) }), _jsx("tbody", { children: items.map((it) => (_jsxs("tr", { className: "border-t border-slate-900", children: [_jsx("td", { className: "px-4 py-2", children: _jsx("span", { className: "inline-block rounded bg-slate-800 text-emerald-300 text-xs font-mono px-2 py-0.5", children: it.node }) }), _jsxs("td", { className: "px-4 py-2 font-mono text-xs", children: [it.fingerprint_hex.slice(0, 24), "\u2026"] }), _jsx("td", { className: "px-4 py-2 font-mono text-xs", children: it.refs.join(', ') }), _jsx("td", { className: "px-4 py-2 text-slate-400", children: fmtAge(it.first_seen_unix) }), _jsx("td", { className: "px-4 py-2 text-slate-400", children: fmtAge(it.last_seen_unix) })] }, it.node + ':' + it.fingerprint_hex))) })] })] }, repo)))] }));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export function RefTrackingPage() {
|
|||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead className="text-slate-500 uppercase text-xs tracking-wider">
|
<thead className="text-slate-500 uppercase text-xs tracking-wider">
|
||||||
<tr>
|
<tr>
|
||||||
|
<th className="text-left px-4 py-2 font-normal">node</th>
|
||||||
<th className="text-left px-4 py-2 font-normal">fingerprint</th>
|
<th className="text-left px-4 py-2 font-normal">fingerprint</th>
|
||||||
<th className="text-left px-4 py-2 font-normal">refs</th>
|
<th className="text-left px-4 py-2 font-normal">refs</th>
|
||||||
<th className="text-left px-4 py-2 font-normal">first seen</th>
|
<th className="text-left px-4 py-2 font-normal">first seen</th>
|
||||||
@@ -71,7 +72,12 @@ export function RefTrackingPage() {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{items.map((it) => (
|
{items.map((it) => (
|
||||||
<tr key={it.fingerprint_hex} className="border-t border-slate-900">
|
<tr key={it.node + ':' + it.fingerprint_hex} className="border-t border-slate-900">
|
||||||
|
<td className="px-4 py-2">
|
||||||
|
<span className="inline-block rounded bg-slate-800 text-emerald-300 text-xs font-mono px-2 py-0.5">
|
||||||
|
{it.node}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td className="px-4 py-2 font-mono text-xs">
|
<td className="px-4 py-2 font-mono text-xs">
|
||||||
{it.fingerprint_hex.slice(0, 24)}…
|
{it.fingerprint_hex.slice(0, 24)}…
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ function BlobsList() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api.blobs().then(setRows).catch((e) => setErr(String(e)));
|
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"),
|
_jsxs("span", { className: "font-mono text-xs", children: [r.blob_id_hex.slice(0, 24), "\u2026"] }, "hex"),
|
||||||
fmtBytes(r.size_bytes),
|
fmtBytes(r.size_bytes),
|
||||||
r.chunk_count.toString(),
|
r.chunk_count.toString(),
|
||||||
@@ -23,7 +24,8 @@ function TagsList() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api.tags(prefix).then(setRows).catch((e) => setErr(String(e)));
|
api.tags(prefix).then(setRows).catch((e) => setErr(String(e)));
|
||||||
}, [prefix]);
|
}, [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"),
|
_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"),
|
_jsxs("span", { className: "font-mono text-xs text-slate-400", children: [r.value_hex.slice(0, 24), "\u2026"] }, "v"),
|
||||||
]) }), _jsx(Footer, { count: rows.length })] }));
|
]) }), _jsx(Footer, { count: rows.length })] }));
|
||||||
@@ -34,7 +36,8 @@ function RefsList() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api.refs().then(setRows).catch((e) => setErr(String(e)));
|
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", 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"),
|
_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 })] }));
|
]) }), _jsx(Footer, { count: rows.length })] }));
|
||||||
@@ -45,7 +48,8 @@ function SnapshotsList() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api.snapshots().then(setRows).catch((e) => setErr(String(e)));
|
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"),
|
_jsx("span", { className: "font-mono text-sm", children: r.name }, "n"),
|
||||||
fmtAge(r.created_at_unix),
|
fmtAge(r.created_at_unix),
|
||||||
r.blob_count.toString(),
|
r.blob_count.toString(),
|
||||||
@@ -61,3 +65,8 @@ function Footer({ count }) {
|
|||||||
function ErrorBox({ msg }) {
|
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 }));
|
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 }));
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,8 +37,9 @@ function BlobsList() {
|
|||||||
<>
|
<>
|
||||||
{err && <ErrorBox msg={err} />}
|
{err && <ErrorBox msg={err} />}
|
||||||
<Table
|
<Table
|
||||||
headers={['blob-id', 'size', 'chunks']}
|
headers={['node', 'blob-id', 'size', 'chunks']}
|
||||||
rows={rows.map((r) => [
|
rows={rows.map((r) => [
|
||||||
|
<NodePill key="n" node={r.node} />,
|
||||||
<span key="hex" className="font-mono text-xs">
|
<span key="hex" className="font-mono text-xs">
|
||||||
{r.blob_id_hex.slice(0, 24)}…
|
{r.blob_id_hex.slice(0, 24)}…
|
||||||
</span>,
|
</span>,
|
||||||
@@ -68,8 +69,9 @@ function TagsList() {
|
|||||||
/>
|
/>
|
||||||
{err && <ErrorBox msg={err} />}
|
{err && <ErrorBox msg={err} />}
|
||||||
<Table
|
<Table
|
||||||
headers={['key', 'blob-id']}
|
headers={['node', 'key', 'blob-id']}
|
||||||
rows={rows.map((r) => [
|
rows={rows.map((r) => [
|
||||||
|
<NodePill key="n" node={r.node} />,
|
||||||
<span key="k" className="font-mono text-sm">
|
<span key="k" className="font-mono text-sm">
|
||||||
{r.key}
|
{r.key}
|
||||||
</span>,
|
</span>,
|
||||||
@@ -93,8 +95,9 @@ function RefsList() {
|
|||||||
<>
|
<>
|
||||||
{err && <ErrorBox msg={err} />}
|
{err && <ErrorBox msg={err} />}
|
||||||
<Table
|
<Table
|
||||||
headers={['fingerprint', 'blob-id']}
|
headers={['node', 'fingerprint', 'blob-id']}
|
||||||
rows={rows.map((r) => [
|
rows={rows.map((r) => [
|
||||||
|
<NodePill key="n" node={r.node} />,
|
||||||
<span key="fp" className="font-mono text-xs">
|
<span key="fp" className="font-mono text-xs">
|
||||||
{r.fingerprint_hex.slice(0, 24)}…
|
{r.fingerprint_hex.slice(0, 24)}…
|
||||||
</span>,
|
</span>,
|
||||||
@@ -118,8 +121,9 @@ function SnapshotsList() {
|
|||||||
<>
|
<>
|
||||||
{err && <ErrorBox msg={err} />}
|
{err && <ErrorBox msg={err} />}
|
||||||
<Table
|
<Table
|
||||||
headers={['name', 'created', 'blobs', 'json size']}
|
headers={['node', 'name', 'created', 'blobs', 'json size']}
|
||||||
rows={rows.map((r) => [
|
rows={rows.map((r) => [
|
||||||
|
<NodePill key="node" node={r.node} />,
|
||||||
<span key="n" className="font-mono text-sm">
|
<span key="n" className="font-mono text-sm">
|
||||||
{r.name}
|
{r.name}
|
||||||
</span>,
|
</span>,
|
||||||
@@ -190,3 +194,16 @@ function ErrorBox({ msg }: { msg: string }) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Small pill badge for the originating node — clickable to
|
||||||
|
/// drill into that node's detail page.
|
||||||
|
function NodePill({ node }: { node: string }) {
|
||||||
|
return (
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
{node}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user