🚧 Add broker status page
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user