// — the hive-roster overview, `/`'s content. Fetches // swarm-controller's `GET /api/hives/status`, the swarm-wide status // aggregate: one row per roster hive, freshness derived at read time // from the status bucket. Supersedes the earlier `GET /api/hives` + // static "configured" chip placeholder now that the real rollup // exists — same component, richer data, no rebuild, exactly the plan // that placeholder's comment laid out. Its own component/file rather // than living in `App.tsx`, matching `JobsPage`'s shape: `App.tsx` is // routing, a page owns its own fetch + render. import { useEffect, useState } from 'preact/hooks'; import { Panel } from '../ui/panel/Panel.js'; import { StatusChip, type ChipTone } from '../ui/status-chip/StatusChip.js'; import { Table, type TableColumn } from '../ui/table/Table.js'; import { fmtAgo } from '../util.js'; type Freshness = 'fresh' | 'stale' | 'never_reported' | 'unknown'; interface HiveStatus { name: string; domain: string | null; freshness: Freshness; last_seen_unix: number | null; age_seconds: number | null; } // One tone + label per freshness value. `unknown` (reporting but absent // from the roster) reads `negative` — it's the one case this endpoint // cannot vouch for at all, per `status.rs`'s doc comment. const FRESHNESS: Record = { fresh: { tone: 'positive', label: 'fresh' }, stale: { tone: 'warning', label: 'stale' }, never_reported: { tone: 'neutral', label: 'never reported' }, unknown: { tone: 'negative', label: 'unknown' }, }; const COLUMNS: TableColumn[] = [ { key: 'name', header: 'name', render: (h) => h.name }, { key: 'domain', header: 'domain', render: (h) => h.domain ? ( {h.domain} ) : ( '—' ), }, { key: 'status', header: 'status', render: (h) => { const { tone, label } = FRESHNESS[h.freshness]; const age = h.age_seconds !== null ? ` (${fmtAgo(h.age_seconds)})` : ''; return ; }, }, ]; export function OverviewPage() { const [hives, setHives] = useState(null); const [error, setError] = useState(null); useEffect(() => { fetch('/api/hives/status') .then((r) => { if (!r.ok) throw new Error(`http ${r.status}`); return r.json() as Promise; }) .then(setHives) .catch((e: unknown) => setError(String(e))); }, []); return ( {error ?

failed to load the hive roster: {error}

: null} {!error && hives === null ?

loading…

: null} {hives ? h.name} /> : null} ); }