Files
helix-engage/src/hooks/use-performance-alerts.ts
saridsa2 180613a2f3 feat(notifications): poll real PerformanceAlert rows from sidecar
usePerformanceAlerts now fetches /api/supervisor/performance-alerts
every 60s instead of computing client-side. Dismiss + dismiss-all hit
the sidecar so state survives reload. Toast fires when new alerts arrive.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 09:02:20 +05:30

102 lines
3.4 KiB
TypeScript

import { useEffect, useRef, useState, useCallback } from 'react';
import { useAuth } from '@/providers/auth-provider';
import { notify } from '@/lib/toast';
export type PerformanceAlert = {
id: string;
agent: string;
agentId: string | null;
type: string;
value: string;
severity: 'error' | 'warning' | 'info';
message?: string | null;
firedAt?: string;
dismissed: boolean;
};
const API_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:4100';
const POLL_INTERVAL_MS = 60_000;
const sevToFront = (s: string): 'error' | 'warning' | 'info' => {
const v = (s ?? '').toLowerCase();
if (v === 'critical') return 'error';
if (v === 'warning') return 'warning';
return 'info';
};
export const usePerformanceAlerts = () => {
const { isAdmin } = useAuth();
const [alerts, setAlerts] = useState<PerformanceAlert[]>([]);
const lastSeenIdsRef = useRef<Set<string>>(new Set());
const load = useCallback(async () => {
if (!isAdmin) return;
const token = localStorage.getItem('helix_access_token') ?? '';
try {
const res = await fetch(`${API_URL}/api/supervisor/performance-alerts`, {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) return;
const json = await res.json();
const list: PerformanceAlert[] = (json?.alerts ?? []).map((a: any) => ({
id: a.id,
agent: a.agent,
agentId: a.agentId ?? null,
type: a.type,
value: a.value ?? '',
severity: sevToFront(a.severity),
message: a.message,
firedAt: a.firedAt,
dismissed: false,
}));
setAlerts(list);
// Toast for newly arrived alerts
const fresh = list.filter((a) => !lastSeenIdsRef.current.has(a.id));
if (fresh.length > 0 && lastSeenIdsRef.current.size > 0) {
notify.error('Performance Alerts', `${fresh.length} new alert(s)`);
}
lastSeenIdsRef.current = new Set(list.map((a) => a.id));
} catch {
// Silent — sidecar may be temporarily down
}
}, [isAdmin]);
useEffect(() => {
if (!isAdmin) return;
load();
const id = setInterval(load, POLL_INTERVAL_MS);
return () => clearInterval(id);
}, [isAdmin, load]);
const dismiss = useCallback(async (id: string) => {
// Optimistic
setAlerts((prev) => prev.filter((a) => a.id !== id));
const token = localStorage.getItem('helix_access_token') ?? '';
try {
await fetch(`${API_URL}/api/supervisor/performance-alerts/${id}/dismiss`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
});
} catch {
// Reload on failure to restore truth
load();
}
}, [load]);
const dismissAll = useCallback(async () => {
setAlerts([]);
const token = localStorage.getItem('helix_access_token') ?? '';
try {
await fetch(`${API_URL}/api/supervisor/performance-alerts/dismiss-all`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
});
} catch {
load();
}
}, [load]);
return { alerts, allAlerts: alerts, dismiss, dismissAll };
};