Files
headquarter/apps/web/src/components/toast-rules.ts
T
alex f13a63dc2f feat: container monitoring frontend UI (PR-2)
- Custom ToastContext + ToastProvider + ToastContainer (~170 lines, no deps)
- useEvents() SSE hook with exponential backoff reconnect
- EventProvider context for app-wide SSE stream sharing
- Event-to-toast bridge with severity mapping and deduplication
- Real-time status badge updates replacing 30s polling
- EventSource auth probe (401/429 detection via fetch)
- 14 frontend tests (useEvents + toast-rules)

Quality gates: vitest 14 passed, tsc clean, eslint clean
2026-05-29 10:25:00 +02:00

58 lines
1.6 KiB
TypeScript

import { toast } from "../state/toast";
import type { InstanceEventPayload } from "../types/events";
const DEDUP_WINDOW_MS = 1000;
const lastToastTime = new Map<string, number>();
function makeDedupKey(instanceId: string, eventType: string): string {
return `${instanceId}:${eventType}`;
}
function shouldShowToast(instanceId: string, eventType: string): boolean {
const key = makeDedupKey(instanceId, eventType);
const now = Date.now();
const last = lastToastTime.get(key);
if (last && now - last < DEDUP_WINDOW_MS) {
return false;
}
lastToastTime.set(key, now);
return true;
}
export function handleEventToast(event: InstanceEventPayload): void {
const { event: eventType, instance_id, status, message, metadata } = event;
switch (eventType) {
case "instance.started":
if (shouldShowToast(instance_id, eventType)) {
toast.info("Container starting...", { duration: 3000 });
}
break;
case "instance.health_changed":
if (status === "running" && shouldShowToast(instance_id, eventType)) {
toast.success("Container running", { duration: 3000 });
} else if (
status === "unhealthy" &&
shouldShowToast(instance_id, eventType)
) {
toast.warning("Container unhealthy", { duration: 5000 });
}
break;
case "instance.error":
if (shouldShowToast(instance_id, eventType)) {
const msg = message ?? "Container error";
const exitCode = metadata?.exit_code;
const fullMsg =
exitCode !== undefined ? `${msg} (exit code: ${exitCode})` : msg;
toast.error(fullMsg, { duration: 10000 });
}
break;
default:
break;
}
}
export function clearToastDedup(): void {
lastToastTime.clear();
}