Watch
0
0
Fork
You've already forked hyperhive
0

notify router: deliveries log, dry-run hit counts, api and page

This commit is contained in:
atlas 2026-10-24 22:13:07 +02:00
commit ed1fd053e7
24 changed files with 1848 additions and 40 deletions

View file

@ -19,6 +19,7 @@ import {
type RefreshIntervalMs,
} from "../../ui/refresh-interval/RefreshInterval.js";
import { fetchJson, usePolled } from "../../ui/polled/usePolled.js";
import { Deliveries } from "./Deliveries.js";
import {
duplicateFlow,
newFlow,
@ -32,11 +33,21 @@ import {
type Flow,
type FlowsView,
} from "./FlowTypes.js";
import {
DELIVERIES_URL,
DRY_RUN_URL,
hitsOf,
unavailableText,
type Deliveries as DeliveriesView,
type DryRun,
} from "./RouterLive.js";
import "./NotificationsPage.css";
const DEFAULT_INTERVAL_MS: RefreshIntervalMs = 30_000;
function FlowTree({ flow }: { flow: Flow }) {
// `hits`, when the dry run covers this flow, puts the number of the newest events
// that reached each node beside it.
function FlowTree({ flow, hits }: { flow: Flow; hits?: Map<string, number> }) {
return (
<ul class="notify-flow-tree">
{treeRows(flow).map(({ node, depth }) => (
@ -47,6 +58,11 @@ function FlowTree({ flow }: { flow: Flow }) {
>
<span class="notify-node-kind">{node.kind}</span>
<span class="notify-node-text">{describeNode(node)}</span>
{hits ? (
<span class="notify-node-hits" title="events of the last dry run">
{hits.get(node.id) ?? 0}
</span>
) : null}
</li>
))}
</ul>
@ -63,6 +79,18 @@ export function NotificationsPage() {
} = usePolled(intervalMs, (signal) =>
fetchJson<FlowsView>("/api/notify/flows", signal),
);
// The router's own answers. Either is 503 while the running router predates
// it, which the page says in place of the section.
const { data: dry, error: dryError } = usePolled(intervalMs, (signal) =>
fetchJson<DryRun>(DRY_RUN_URL, signal),
);
const { data: deliveries, error: deliveriesError } = usePolled(
intervalMs,
(signal) => fetchJson<DeliveriesView>(DELIVERIES_URL, signal),
);
// A failed poll keeps the last answer; the counts are shown only while the
// latest poll worked.
const shown = dryError ? null : dry;
const [pending, setPending] = useState(false);
const [actionError, setActionError] = useState<ProblemDetails | null>(null);
const [newName, setNewName] = useState("");
@ -149,6 +177,15 @@ export function NotificationsPage() {
{!error && view === null ? <p>loading…</p> : null}
{view ? (
<div class="notify-flows">
{dryError ? (
<p class="notify-unavailable">
{unavailableText("hit counts", dryError)}
</p>
) : dry ? (
<p class="notify-dry-run">
counts: events that reached each node, of the newest {dry.events}
</p>
) : null}
<h3>built-in flows</h3>
{view.builtins.map((flow) => {
const on = !view.disabled_builtins.includes(flow.id);
@ -164,7 +201,7 @@ export function NotificationsPage() {
onChange={(checked) => setBuiltin(flow.id, checked)}
/>
</div>
<FlowTree flow={flow} />
<FlowTree flow={flow} hits={hitsOf(shown, flow.id)} />
<Button disabled={pending} onClick={() => void duplicate(flow)}>
duplicate to edit
</Button>
@ -202,9 +239,19 @@ export function NotificationsPage() {
duplicate
</Button>
</div>
<FlowTree flow={flow} />
<FlowTree flow={flow} hits={hitsOf(shown, flow.id)} />
</Card>
))}
<h3>recent deliveries</h3>
{deliveriesError ? (
<p class="notify-unavailable">
{unavailableText("the deliveries log", deliveriesError)}
</p>
) : deliveries ? (
<Deliveries rows={deliveries.rows} />
) : (
<p>loading…</p>
)}
</div>
) : null}
</Panel>