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.
62 lines
4.3 KiB
JavaScript
62 lines
4.3 KiB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
import { useState } from 'react';
|
|
import { Route, Switch, Link, Router, useLocation } from 'wouter';
|
|
import { CommandCenter } from './pages/CommandCenter';
|
|
import { NodeDetail } from './pages/NodeDetail';
|
|
import { StorageBrowser } from './pages/StorageBrowser';
|
|
import { RefTrackingPage } from './pages/RefTrackingPage';
|
|
// Base path — matches the deploy mount. Detected from
|
|
// window.location so a single SPA build serves both local
|
|
// (:7700/v2/) and Tailscale (/clawstor).
|
|
const BASE = (() => {
|
|
if (typeof window === 'undefined')
|
|
return '';
|
|
const p = window.location.pathname;
|
|
if (p === '/clawstor' || p.startsWith('/clawstor/'))
|
|
return '/clawstor';
|
|
if (p === '/v2' || p.startsWith('/v2/'))
|
|
return '/v2';
|
|
return '';
|
|
})();
|
|
export default function App() {
|
|
return (_jsx(Router, { base: BASE, children: _jsx(Shell, {}) }));
|
|
}
|
|
function Shell() {
|
|
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(NavBar, {}), _jsx("main", { className: "max-w-7xl mx-auto p-4", children: _jsxs(Switch, { children: [_jsx(Route, { path: "/", component: CommandCenter }), _jsx(Route, { path: "/nodes/:name", children: (params) => _jsx(NodeDetail, { name: params.name }) }), _jsx(Route, { path: "/advanced", children: _jsx(AdvancedRedirect, {}) }), _jsx(Route, { path: "/advanced/:tab", children: (params) => _jsx(StorageBrowser, { tab: params.tab }) }), _jsx(Route, { path: "/advanced/refs/tracking", component: RefTrackingPage }), _jsx(Route, { children: _jsx("div", { className: "text-slate-400 py-12 text-center", children: "404" }) })] }) })] }));
|
|
}
|
|
function NavBar() {
|
|
const [loc] = useLocation();
|
|
const [advOpen, setAdvOpen] = useState(false);
|
|
const advActive = loc.startsWith('/advanced');
|
|
const primary = [{ path: '/', label: 'Fleet health' }];
|
|
const advanced = [
|
|
{ path: '/advanced/blobs', label: 'Blobs' },
|
|
{ path: '/advanced/tags', label: 'Tags' },
|
|
{ path: '/advanced/refs', label: 'Refs' },
|
|
{ path: '/advanced/snapshots', label: 'Snapshots' },
|
|
{ path: '/advanced/refs/tracking', label: 'Ref-tracking' },
|
|
];
|
|
return (_jsx("header", { className: "border-b border-slate-800 bg-slate-950/80 backdrop-blur sticky top-0 z-10", children: _jsxs("div", { className: "max-w-7xl mx-auto px-4 flex items-center gap-6", children: [_jsx(Link, { href: "/", children: _jsx("a", { className: "font-mono text-lg text-emerald-300 py-3", children: "clawstor \u00B7 command center" }) }), _jsxs("nav", { className: "flex items-center", children: [primary.map((t) => (_jsx(Tab, { path: t.path, label: t.label, active: loc === t.path }, t.path))), _jsxs("div", { className: "relative", onMouseEnter: () => setAdvOpen(true), onMouseLeave: () => setAdvOpen(false), children: [_jsx("span", { className: [
|
|
'px-3 py-2 text-sm border-b-2 cursor-pointer transition-colors select-none',
|
|
advActive
|
|
? 'border-emerald-400 text-emerald-300'
|
|
: 'border-transparent text-slate-400 hover:text-slate-100',
|
|
].join(' '), children: "View Advanced \u25BE" }), advOpen && (_jsx("div", { className: "absolute left-0 top-full mt-0 bg-slate-900 border border-slate-800 rounded shadow-lg min-w-[12rem] z-20", children: advanced.map((t) => (_jsx(Link, { href: t.path, children: _jsx("a", { className: [
|
|
'block px-3 py-2 text-sm hover:bg-slate-800',
|
|
loc === t.path ? 'text-emerald-300' : 'text-slate-300',
|
|
].join(' '), children: t.label }) }, t.path))) }))] })] })] }) }));
|
|
}
|
|
function Tab({ path, label, active, }) {
|
|
return (_jsx(Link, { href: path, children: _jsx("a", { className: [
|
|
'px-3 py-2 text-sm border-b-2 transition-colors',
|
|
active
|
|
? 'border-emerald-400 text-emerald-300'
|
|
: 'border-transparent text-slate-400 hover:text-slate-100',
|
|
].join(' '), children: label }) }));
|
|
}
|
|
function AdvancedRedirect() {
|
|
const [, nav] = useLocation();
|
|
nav('/advanced/blobs', { replace: true });
|
|
return null;
|
|
}
|