push terminal session changes and alert evaluator health
This commit is contained in:
11 files changed
+663
-101
No files matched your search
+58
-11
@@ -1,4 +1,4 @@
|
||||
import { lazy, Suspense, useEffect, useState } from "react";
|
||||
import { lazy, Suspense, useEffect, useRef, useState } from "react";
|
||||
import { Navigate, Route, Routes } from "react-router-dom";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -40,22 +40,48 @@ export function App() {
|
||||
const [history, setHistory] = useState<AlertTransition[]>([]);
|
||||
const [audit, setAudit] = useState<AuditEvent[]>([]);
|
||||
const [selectedAgentId, setSelectedAgentId] = useState("");
|
||||
const [alertsHealth, setAlertsHealth] = useState<{
|
||||
available: boolean;
|
||||
reason?: string;
|
||||
} | null>(null);
|
||||
const [alertsStreamConnected, setAlertsStreamConnected] = useState(false);
|
||||
const streamHealthReceived = useRef(false);
|
||||
const alertsRevision = useRef(0);
|
||||
|
||||
const [state, setState] = useState<LoadState>("idle");
|
||||
|
||||
async function loadAlertsWithoutBlocking() {
|
||||
try {
|
||||
const nextAlerts = await fetchAlerts();
|
||||
if (!streamHealthReceived.current) {
|
||||
setAlertsHealth({ available: true });
|
||||
}
|
||||
return nextAlerts;
|
||||
} catch (err) {
|
||||
if (!streamHealthReceived.current) {
|
||||
setAlertsHealth({ available: false, reason: String(err) });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAll() {
|
||||
const revision = alertsRevision.current;
|
||||
setState("loading");
|
||||
try {
|
||||
const [nextAgents, nextAlerts, nextHistory, nextAudit] =
|
||||
await Promise.all([
|
||||
fetchAgents(),
|
||||
fetchAlerts(),
|
||||
loadAlertsWithoutBlocking(),
|
||||
fetchAlertHistory(20),
|
||||
fetchAudit(30),
|
||||
]);
|
||||
setAgents(nextAgents);
|
||||
setAlerts(nextAlerts);
|
||||
setHistory(nextHistory);
|
||||
// Streamed state wins over HTTP requests started before its arrival.
|
||||
if (revision === alertsRevision.current) {
|
||||
if (nextAlerts) setAlerts(nextAlerts);
|
||||
setHistory(nextHistory);
|
||||
}
|
||||
setAudit(nextAudit);
|
||||
const firstConnectedAgentId =
|
||||
nextAgents.find((agent) => agent.connected)?.agent_id ?? "";
|
||||
@@ -100,12 +126,15 @@ export function App() {
|
||||
}
|
||||
|
||||
async function refreshAlertsAndHistory() {
|
||||
const revision = alertsRevision.current;
|
||||
const [nextAlerts, nextHistory] = await Promise.all([
|
||||
fetchAlerts(),
|
||||
loadAlertsWithoutBlocking(),
|
||||
fetchAlertHistory(20),
|
||||
]);
|
||||
setAlerts(nextAlerts);
|
||||
setHistory(nextHistory);
|
||||
if (revision === alertsRevision.current) {
|
||||
if (nextAlerts) setAlerts(nextAlerts);
|
||||
setHistory(nextHistory);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -117,15 +146,14 @@ export function App() {
|
||||
let disposed = false;
|
||||
let attempt = 0;
|
||||
let openedAt = 0;
|
||||
let wsGeneration = 0;
|
||||
let reconnectTimer: number | undefined;
|
||||
|
||||
const resync = () => {
|
||||
const generation = wsGeneration;
|
||||
const generation = alertsRevision.current;
|
||||
void Promise.all([fetchAlerts(), fetchAlertHistory(20)])
|
||||
.then(([nextAlerts, nextHistory]) => {
|
||||
// A WS snapshot delivered while this request was in flight is newer.
|
||||
if (generation !== wsGeneration) return;
|
||||
if (disposed || generation !== alertsRevision.current) return;
|
||||
setAlerts(nextAlerts);
|
||||
setHistory(nextHistory);
|
||||
})
|
||||
@@ -138,18 +166,32 @@ export function App() {
|
||||
socket = new WebSocket(wsUrl);
|
||||
|
||||
socket.onopen = () => {
|
||||
if (disposed) return;
|
||||
openedAt = Date.now();
|
||||
setAlertsStreamConnected(true);
|
||||
// Pull anything missed while the stream was down.
|
||||
resync();
|
||||
};
|
||||
|
||||
socket.onmessage = (evt) => {
|
||||
wsGeneration += 1;
|
||||
if (disposed) return;
|
||||
alertsRevision.current += 1;
|
||||
try {
|
||||
const payload = JSON.parse(String(evt.data)) as {
|
||||
type?: string;
|
||||
available?: boolean;
|
||||
reason?: string;
|
||||
alerts?: Alert[];
|
||||
recent_transitions?: AlertTransition[];
|
||||
};
|
||||
if (payload.type === "alerts_health") {
|
||||
streamHealthReceived.current = true;
|
||||
setAlertsHealth({
|
||||
available: payload.available === true,
|
||||
reason: payload.reason,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (payload.alerts) setAlerts(payload.alerts);
|
||||
if (payload.recent_transitions)
|
||||
setHistory(payload.recent_transitions);
|
||||
@@ -161,6 +203,7 @@ export function App() {
|
||||
socket.onclose = () => {
|
||||
socket = null;
|
||||
if (disposed) return;
|
||||
setAlertsStreamConnected(false);
|
||||
// Only a connection that stayed up proves the stream is healthy; an
|
||||
// accept-then-close server must not pin us to the first backoff step.
|
||||
if (openedAt && Date.now() - openedAt > 10_000) attempt = 0;
|
||||
@@ -217,6 +260,8 @@ export function App() {
|
||||
transitions={history}
|
||||
audit={audit}
|
||||
loading={state === "loading"}
|
||||
alertsHealth={alertsHealth}
|
||||
alertsStreamConnected={alertsStreamConnected}
|
||||
onRefresh={loadAll}
|
||||
/>
|
||||
}
|
||||
@@ -259,6 +304,8 @@ export function App() {
|
||||
<AlertsPage
|
||||
alerts={alerts}
|
||||
transitions={history}
|
||||
alertsHealth={alertsHealth}
|
||||
alertsStreamConnected={alertsStreamConnected}
|
||||
onRefresh={refreshAlertsAndHistory}
|
||||
/>
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user