🚧 Add broker status page

This commit is contained in:
Jonas Bengtsson
2026-09-20 07:55:18 +02:00
parent 4322446fcf
commit 1e491642f1
9 changed files with 461 additions and 6 deletions
+160
View File
@@ -0,0 +1,160 @@
import { useQuery } from "@tanstack/react-query";
import { api } from "../api";
export interface BrokerStatus {
available: boolean;
last_update: string | null;
version: string | null;
uptime: string | null;
clients_connected: number | null;
clients_disconnected: number | null;
clients_total: number | null;
messages_received: number | null;
messages_sent: number | null;
messages_stored: number | null;
messages_dropped: number | null;
publish_received: number | null;
publish_sent: number | null;
bytes_received: number | null;
bytes_sent: number | null;
subscriptions_count: number | null;
retained_count: number | null;
load_messages_received_1min: number | null;
load_messages_sent_1min: number | null;
load_publish_received_1min: number | null;
load_publish_sent_1min: number | null;
load_bytes_received_1min: number | null;
load_bytes_sent_1min: number | null;
}
function fmt(n: number | null | undefined): string {
return n === null || n === undefined ? "—" : n.toLocaleString();
}
function Metric({
label,
value,
hint,
}: {
label: string;
value: string;
hint?: string;
}) {
return (
<div
style={{
background: "#fff",
border: "1px solid #e4e7eb",
borderRadius: "8px",
padding: "0.8rem 1rem",
minWidth: "150px",
flex: "1 1 150px",
}}
title={hint}
>
<div style={{ fontSize: "0.75rem", color: "#52606d" }}>{label}</div>
<div className="mono" style={{ fontSize: "1.15rem" }}>
{value}
</div>
</div>
);
}
export function StatusPage() {
const status = useQuery({
queryKey: ["broker-status"],
queryFn: () => api.get<BrokerStatus>("/broker/status"),
// Match the broker's sys_interval (10 s in the bootstrap config).
refetchInterval: 10000,
});
const s = status.data;
return (
<div>
<h2>Broker status</h2>
<p>
Live telemetry from the broker's <code>$SYS</code> topics. The broker
publishes every 10 seconds; this page refreshes to match.
</p>
{status.error && (
<div className="banner error">{(status.error as Error).message}</div>
)}
{!s?.available && !status.error && (
<div className="banner warn">
No $SYS telemetry received yet. This happens for a few seconds after
backend startup, or if the management identity lacks $SYS read access
(the sys-observe grant is applied at backend startup).
</div>
)}
{s?.available && (
<>
<div style={{ display: "flex", gap: "1rem", flexWrap: "wrap" }}>
<Metric label="Version" value={s.version ?? "—"} />
<Metric label="Uptime" value={s.uptime ?? "—"} />
<Metric
label="Connected clients"
value={fmt(s.clients_connected)}
hint="devices currently connected"
/>
<Metric
label="Stored sessions"
value={fmt(s.clients_disconnected)}
hint="persistent clients currently offline"
/>
<Metric label="Subscriptions" value={fmt(s.subscriptions_count)} />
<Metric label="Retained messages" value={fmt(s.retained_count)} />
</div>
<h3 style={{ marginTop: "1.5rem" }}>Messages</h3>
<div style={{ display: "flex", gap: "1rem", flexWrap: "wrap" }}>
<Metric label="Received (total)" value={fmt(s.messages_received)} />
<Metric label="Sent (total)" value={fmt(s.messages_sent)} />
<Metric label="Stored" value={fmt(s.messages_stored)} />
<Metric label="Dropped" value={fmt(s.messages_dropped)} />
<Metric label="Publish received" value={fmt(s.publish_received)} />
<Metric label="Publish sent" value={fmt(s.publish_sent)} />
</div>
<h3 style={{ marginTop: "1.5rem" }}>Throughput (1 min avg / s)</h3>
<div style={{ display: "flex", gap: "1rem", flexWrap: "wrap" }}>
<Metric
label="Messages in"
value={fmt(s.load_messages_received_1min)}
/>
<Metric
label="Messages out"
value={fmt(s.load_messages_sent_1min)}
/>
<Metric
label="Publishes in"
value={fmt(s.load_publish_received_1min)}
/>
<Metric
label="Publishes out"
value={fmt(s.load_publish_sent_1min)}
/>
<Metric label="Bytes in" value={fmt(s.load_bytes_received_1min)} />
<Metric label="Bytes out" value={fmt(s.load_bytes_sent_1min)} />
</div>
<h3 style={{ marginTop: "1.5rem" }}>Bytes (total)</h3>
<div style={{ display: "flex", gap: "1rem", flexWrap: "wrap" }}>
<Metric label="Received" value={fmt(s.bytes_received)} />
<Metric label="Sent" value={fmt(s.bytes_sent)} />
</div>
<p style={{ color: "#52606d", fontSize: "0.8rem", marginTop: "1rem" }}>
Last $SYS update:{" "}
{s.last_update
? new Date(s.last_update).toLocaleTimeString()
: "—"}
</p>
</>
)}
</div>
);
}