160 lines
5.2 KiB
TypeScript
160 lines
5.2 KiB
TypeScript
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>
|
|
);
|
|
} |