dfc6b8ff76
REQ-014: surface relay agent health (green/yellow/red), target hostname, last 100 log lines in admin dashboard. Per-tenant view under RLS. ---ci--- phase: 5 milestone: v0.1 status: execute ---/ci---
61 lines
2.3 KiB
TypeScript
61 lines
2.3 KiB
TypeScript
/**
|
|
* Health computation for Relay Agent targets (REQ-014, Wave E).
|
|
*
|
|
* Status is computed from `targets.last_seen_at`:
|
|
* green — last_seen within the last 60s (agent is heartbeating)
|
|
* yellow — last_seen within the last 5min (degraded — maybe a slow network
|
|
* or a restart in progress; agent may still recover)
|
|
* red — last_seen more than 5min ago, or NULL (never seen / down)
|
|
*
|
|
* The thresholds match the Wave E spec + the in-memory `getConnectedAgents()`
|
|
* snapshot from the WS server (real-time online check). The dashboard SSE
|
|
* fan-out (api/relay/status) and the targets list/detail routes both use this
|
|
* helper so the badge color is consistent everywhere.
|
|
*/
|
|
|
|
export type HealthStatus = "green" | "yellow" | "red";
|
|
|
|
/** green threshold (seconds since last_seen). */
|
|
export const GREEN_THRESHOLD_S = 60;
|
|
/** yellow threshold (seconds since last_seen). */
|
|
export const YELLOW_THRESHOLD_S = 5 * 60;
|
|
|
|
export interface HealthInput {
|
|
/** ISO timestamptz string, or null if the agent never heartbeated. */
|
|
lastSeenAt: string | null;
|
|
/** Optional: the WS server's in-memory connected-agent snapshot, keyed by
|
|
* targetId. If a target is currently connected, we treat it as green even
|
|
* if last_seen is slightly stale (heartbeat updates are batched). */
|
|
connectedTargetIds?: ReadonlySet<string>;
|
|
/** Target id (for the connected lookup). */
|
|
targetId?: string;
|
|
}
|
|
|
|
/**
|
|
* Compute health from last_seen_at (and optionally the connected-agents set).
|
|
* `now` defaults to Date.now() but can be injected for deterministic tests.
|
|
*/
|
|
export function computeHealth(input: HealthInput, now: number = Date.now()): HealthStatus {
|
|
if (input.targetId && input.connectedTargetIds?.has(input.targetId)) {
|
|
return "green";
|
|
}
|
|
if (!input.lastSeenAt) return "red";
|
|
const last = Date.parse(input.lastSeenAt);
|
|
if (Number.isNaN(last)) return "red";
|
|
const ageS = (now - last) / 1000;
|
|
if (ageS <= GREEN_THRESHOLD_S) return "green";
|
|
if (ageS <= YELLOW_THRESHOLD_S) return "yellow";
|
|
return "red";
|
|
}
|
|
|
|
/** Badge color for a health status (used by the dashboard pages). */
|
|
export function healthColor(status: HealthStatus): string {
|
|
switch (status) {
|
|
case "green":
|
|
return "#22c55e";
|
|
case "yellow":
|
|
return "#eab308";
|
|
case "red":
|
|
return "#ef4444";
|
|
}
|
|
} |