19242b4152
- EventToastBridge checks notification_toast_level and notification_mute_categories - toast-rules.ts: event-to-category/severity mapping functions - Settings page: notification preferences section (toast level dropdown, mute checkboxes) - Settings API types extended with notification preference fields - 17 frontend tests (toast-rules + bridge) - Preference hierarchy: mute categories → toast level → show/hide Quality gates: vitest 17 passed, tsc clean, eslint clean
84 lines
2.3 KiB
TypeScript
84 lines
2.3 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 mapEventToCategory(event: InstanceEventPayload): string {
|
|
if (event.event.startsWith("instance.")) return "instance";
|
|
if (event.event.startsWith("health.")) return "health";
|
|
return "system";
|
|
}
|
|
|
|
export function mapEventToSeverity(
|
|
event: InstanceEventPayload,
|
|
): "info" | "warning" | "error" | "success" {
|
|
switch (event.event) {
|
|
case "instance.error":
|
|
case "health.error":
|
|
return "error";
|
|
case "instance.health_changed":
|
|
return event.status === "unhealthy" ? "warning" : "success";
|
|
case "instance.created":
|
|
case "instance.started":
|
|
case "instance.stopped":
|
|
case "instance.restarted":
|
|
case "instance.deleted":
|
|
return "info";
|
|
default:
|
|
return "info";
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|