swarm-ui: rename OverviewPage/'overview' to HivesPage/'hives'
Closes #3460. Renamed for accuracy — the page/nav item is the hive roster, not a general dashboard overview. Nav label, page title, component name, and file all renamed together so the internal name doesn't drift from what's displayed (a component still called OverviewPage under a 'hives' nav label would be exactly the kind of stale prior-art that makes the next contributor search harder, not less).
This commit is contained in:
parent
02aa866e5b
commit
537d6f9dac
4 changed files with 7 additions and 7 deletions
|
|
@ -5,7 +5,7 @@ import { Shell } from './shell/Shell.js';
|
||||||
import { ComponentsPage } from './pages/ComponentsPage.js';
|
import { ComponentsPage } from './pages/ComponentsPage.js';
|
||||||
import { CreateAgentPage } from './pages/CreateAgentPage.js';
|
import { CreateAgentPage } from './pages/CreateAgentPage.js';
|
||||||
import { JobsPage } from './pages/JobsPage.js';
|
import { JobsPage } from './pages/JobsPage.js';
|
||||||
import { OverviewPage } from './pages/OverviewPage.js';
|
import { HivesPage } from './pages/HivesPage.js';
|
||||||
import { Panel } from './ui/panel/Panel.js';
|
import { Panel } from './ui/panel/Panel.js';
|
||||||
|
|
||||||
function NotFound() {
|
function NotFound() {
|
||||||
|
|
@ -20,7 +20,7 @@ export function App() {
|
||||||
return (
|
return (
|
||||||
<Shell>
|
<Shell>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/" component={OverviewPage} />
|
<Route path="/" component={HivesPage} />
|
||||||
<Route path="/create-agent" component={CreateAgentPage} />
|
<Route path="/create-agent" component={CreateAgentPage} />
|
||||||
<Route path="/jobs" component={JobsPage} />
|
<Route path="/jobs" component={JobsPage} />
|
||||||
<Route path="/components" component={ComponentsPage} />
|
<Route path="/components" component={ComponentsPage} />
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ import { SelectField, type SelectOption } from '../ui/select-field/SelectField.j
|
||||||
import { Button } from '../ui/button/Button.js';
|
import { Button } from '../ui/button/Button.js';
|
||||||
import './CreateAgentPage.css';
|
import './CreateAgentPage.css';
|
||||||
|
|
||||||
// Mirrors swarm-controller's `HiveEntry` — same shape `OverviewPage`
|
// Mirrors swarm-controller's `HiveEntry` — same shape `HivesPage`
|
||||||
// consumes off `/api/hives/status`, but this page hits the plain
|
// consumes off `/api/hives/status`, but this page hits the plain
|
||||||
// `/api/hives` roster (no status/freshness needed, just "what hives
|
// `/api/hives` roster (no status/freshness needed, just "what hives
|
||||||
// exist to spawn into").
|
// exist to spawn into").
|
||||||
|
|
|
||||||
85
frontend/packages/swarm-ui/src/pages/HivesPage.tsx
Normal file
85
frontend/packages/swarm-ui/src/pages/HivesPage.tsx
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
// <HivesPage> — the hive roster, `/`'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 { ApiErrorPanel } from '@hive/shared/api-error-panel.js';
|
||||||
|
import { readApiError, type ProblemDetails } from '@hive/shared/api-error.js';
|
||||||
|
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<Freshness, { tone: ChipTone; label: string }> = {
|
||||||
|
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<HiveStatus>[] = [
|
||||||
|
{ key: 'name', header: 'name', render: (h) => h.name },
|
||||||
|
{
|
||||||
|
key: 'domain',
|
||||||
|
header: 'domain',
|
||||||
|
render: (h) =>
|
||||||
|
h.domain ? (
|
||||||
|
<a href={`https://${h.domain}/`} target="_blank" rel="noreferrer">
|
||||||
|
{h.domain}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
'—'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'status',
|
||||||
|
header: 'status',
|
||||||
|
render: (h) => {
|
||||||
|
const { tone, label } = FRESHNESS[h.freshness];
|
||||||
|
const age = h.age_seconds !== null ? ` (${fmtAgo(h.age_seconds)})` : '';
|
||||||
|
return <StatusChip tone={tone} label={label + age} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function HivesPage() {
|
||||||
|
const [hives, setHives] = useState<HiveStatus[] | null>(null);
|
||||||
|
const [error, setError] = useState<ProblemDetails | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
const r = await fetch('/api/hives/status');
|
||||||
|
if (!r.ok) {
|
||||||
|
setError(await readApiError(r));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setHives((await r.json()) as HiveStatus[]);
|
||||||
|
})().catch((e: unknown) => setError({ detail: String(e) }));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Panel title="hives">
|
||||||
|
{error ? <ApiErrorPanel context="failed to load the hive roster" problem={error} /> : null}
|
||||||
|
{!error && hives === null ? <p>loading…</p> : null}
|
||||||
|
{hives ? <Table columns={COLUMNS} rows={hives} rowKey={(h) => h.name} /> : null}
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -19,7 +19,7 @@ import { LinksMenu } from './LinksMenu.js';
|
||||||
import './Shell.css';
|
import './Shell.css';
|
||||||
|
|
||||||
const NAV_ITEMS: { href: string; label: string }[] = [
|
const NAV_ITEMS: { href: string; label: string }[] = [
|
||||||
{ href: '/', label: 'overview' },
|
{ href: '/', label: 'hives' },
|
||||||
{ href: '/create-agent', label: 'new agent' },
|
{ href: '/create-agent', label: 'new agent' },
|
||||||
{ href: '/jobs', label: 'jobs' },
|
{ href: '/jobs', label: 'jobs' },
|
||||||
{ href: '/components', label: 'components' },
|
{ href: '/components', label: 'components' },
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue