f13a63dc2f
- 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
58 lines
1.6 KiB
TypeScript
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();
|
|
}
|