// — the notification router's flows. Backed by // swarm-controller's `GET/PUT /api/notify/flows`; the controller is the only // writer of the stored document and the router reads it every few seconds. // // Read-only except for one switch per built-in flow. A flow draws as an // indented tree, the shape the API validates it to. Custom flows are not // editable here: they arrive through the API. import { useState } from "preact/hooks"; import { ApiErrorPanel } from "@hive/shared/api-error-panel.js"; import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js"; import { Card } from "../../ui/card/Card.js"; import { CheckboxField } from "../../ui/checkbox-field/CheckboxField.js"; import { Panel } from "../../ui/panel/Panel.js"; import { RefreshIntervalPicker, type RefreshIntervalMs, } from "../../ui/refresh-interval/RefreshInterval.js"; import { fetchJson, usePolled } from "../../ui/polled/usePolled.js"; import { describeNode, treeRows, withBuiltin, type Flow, type FlowsView, } from "./FlowTypes.js"; import "./NotificationsPage.css"; const DEFAULT_INTERVAL_MS: RefreshIntervalMs = 30_000; function FlowTree({ flow }: { flow: Flow }) { return ( ); } export function NotificationsPage() { const [intervalMs, setIntervalMs] = useState(DEFAULT_INTERVAL_MS); const { data: view, error, refresh, } = usePolled(intervalMs, (signal) => fetchJson("/api/notify/flows", signal), ); const [pending, setPending] = useState(false); const [actionError, setActionError] = useState(null); // Switch one built-in on or off: the whole document goes back with the // revision this page read, so a change made elsewhere since is refused // rather than overwritten. async function setBuiltin(id: string, enabled: boolean) { if (!view) return; setPending(true); setActionError(null); try { const r = await fetch("/api/notify/flows", { method: "PUT", headers: { "content-type": "application/json" }, body: JSON.stringify({ revision: view.revision, disabled_builtins: withBuiltin(view.disabled_builtins, id, enabled), flows: view.flows, }), }); if (!r.ok) setActionError(await readApiError(r)); } catch (err) { setActionError({ detail: String(err) }); } finally { setPending(false); refresh(); } } return ( } > {error ? ( ) : null} {actionError ? ( ) : null} {!error && view === null ?

loading…

: null} {view ? (

built-in flows

{view.builtins.map((flow) => { const on = !view.disabled_builtins.includes(flow.id); return (
{flow.name} setBuiltin(flow.id, checked)} />
); })}

custom flows

{view.flows.length === 0 ?

no custom flows

: null} {view.flows.map((flow) => (
{flow.name} {flow.enabled ? "on" : "off"}
))}
) : null}
); }