FleetHealth PR 2 (frontend): human-oriented landing + advanced menu
Build with clawstor cache / Cargo build (clawstor-cached) (pull_request) Successful in 18s
Build with clawstor cache / Cargo build (clawstor-cached) (pull_request) Successful in 18s
Reworks the SPA around the new DashboardStatus fields. The landing is now a fleet-health dashboard aimed at a layperson — disk gauges, mount ✓/✗, cache hit rate, next scheduled job. No hex, no primitives. Nav restructure: * Primary: 'Fleet health' (just the landing). * 'View Advanced ▾' dropdown reveals: Blobs / Tags / Refs / Snapshots / Ref-tracking. Routes moved under /advanced/*. New components: * StorageBar — horizontal used/total bar with pinned/evictable split, health-color threshold at 60/85%. * NodeCard — traffic-light dot + disk + hot tier bars + mount state + cache hit rate + next-timer countdown. Whole card is a link into node detail. New CommandCenter: * Fleet-wide storage roll-up card (sum of every node's disk). * Grid of NodeCards. * 10s poll cadence retained from previous version. Existing StorageBrowser + RefTrackingPage moved behind /advanced/* routes; internal component code untouched.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { fmtBytes } from '../lib/api';
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
used: number;
|
||||
total: number;
|
||||
// Optional split: a portion of `used` that's "pinned" (won't be
|
||||
// evicted). Rendered green; the rest of used is amber.
|
||||
pinned?: number | null;
|
||||
}
|
||||
|
||||
/// Big horizontal storage bar. Read-only. Renders green (pinned,
|
||||
/// safe) + amber (used, evictable) + slate (free). Health color
|
||||
/// on the label based on fill %.
|
||||
export function StorageBar({ label, used, total, pinned }: Props) {
|
||||
const safeTotal = Math.max(total, 1);
|
||||
const pct = Math.min(100, Math.round((used / safeTotal) * 100));
|
||||
const pinnedPct = pinned
|
||||
? Math.min(100, Math.round((pinned / safeTotal) * 100))
|
||||
: 0;
|
||||
const evictablePct = Math.max(0, pct - pinnedPct);
|
||||
const bar = pct < 60 ? 'ok' : pct < 85 ? 'warn' : 'err';
|
||||
const barText = {
|
||||
ok: 'text-emerald-300',
|
||||
warn: 'text-amber-300',
|
||||
err: 'text-red-300',
|
||||
}[bar];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-baseline justify-between text-xs mb-1">
|
||||
<span className="text-slate-500 uppercase tracking-wider">{label}</span>
|
||||
<span className={`font-mono ${barText}`}>
|
||||
{fmtBytes(used)} / {fmtBytes(total)} · {pct}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2.5 w-full rounded-full bg-slate-800 overflow-hidden flex">
|
||||
{pinnedPct > 0 && (
|
||||
<div
|
||||
className="bg-emerald-500 h-full"
|
||||
style={{ width: `${pinnedPct}%` }}
|
||||
title={`Pinned: ${fmtBytes(pinned!)}`}
|
||||
/>
|
||||
)}
|
||||
{evictablePct > 0 && (
|
||||
<div
|
||||
className={`h-full ${
|
||||
bar === 'err' ? 'bg-red-500' : bar === 'warn' ? 'bg-amber-500' : 'bg-emerald-600'
|
||||
}`}
|
||||
style={{ width: `${evictablePct}%` }}
|
||||
title={`Used: ${fmtBytes(used - (pinned ?? 0))}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user