// Alerts.jsx — live alerts screen backed by /api/v1/alerts function Alerts() { const [alerts, setAlerts] = React.useState([]); const [statusRows, setStatusRows] = React.useState([]); const [summaryCounts, setSummaryCounts] = React.useState({ active: 0, closed: 0 }); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(""); const [severityTab, setSeverityTab] = React.useState("all"); const [statusTab, setStatusTab] = React.useState("active"); const [pendingAck, setPendingAck] = React.useState({}); const [rowErrors, setRowErrors] = React.useState({}); const loadAlerts = React.useCallback(async () => { // [TASK_START:T007] const statusFilter = statusTab === "active" ? "open" : "closed"; // [TASK_COMPLETE:T007] // [TASK_START:T008] const severityFilter = severityTab === "all" ? undefined : severityTab; // [TASK_COMPLETE:T008] setLoading(true); setError(""); try { // [TASK_START:T006] const [statusScopedRows, filteredRows, openRows, closedRows] = await Promise.all([ Keeper.api.alerts({ status: statusFilter }), Keeper.api.alerts({ status: statusFilter, severity: severityFilter }), Keeper.api.alerts({ status: "open" }), Keeper.api.alerts({ status: "closed" }), ]); // [TASK_COMPLETE:T006] setStatusRows(Array.isArray(statusScopedRows) ? statusScopedRows : []); setAlerts(Array.isArray(filteredRows) ? filteredRows : []); setSummaryCounts({ active: Array.isArray(openRows) ? openRows.length : 0, closed: Array.isArray(closedRows) ? closedRows.length : 0, }); } catch (e) { // [TASK_START:T011] setStatusRows([]); setAlerts([]); setSummaryCounts({ active: 0, closed: 0 }); const fallbackMessage = "Не удалось загрузить уведомления. Повторите попытку."; const formatted = typeof Keeper.formatApiFailure === "function" ? Keeper.formatApiFailure(e, { fallbackMessage }) : fallbackMessage; setError(formatted); // [TASK_COMPLETE:T011] } finally { setLoading(false); } }, [severityTab, statusTab]); React.useEffect(() => { let cancelled = false; (async () => { await loadAlerts(); if (cancelled) return; })(); return () => { cancelled = true; }; }, [loadAlerts]); function isActive(alert) { return alert.status === "open" || alert.status === "active"; } const counts = { all: statusRows.length, crit: statusRows.filter((a) => (a.severity || a.kind) === "crit").length, warn: statusRows.filter((a) => (a.severity || a.kind) === "warn").length, info: statusRows.filter((a) => (a.severity || a.kind) === "info").length, active: summaryCounts.active, closed: summaryCounts.closed, }; // [TASK_START:T009] async function acknowledgeAlert(id) { const current = alerts.find((a) => a.id === id); if (!current || !isActive(current)) return; setPendingAck((m) => ({ ...m, [id]: true })); setRowErrors((m) => ({ ...m, [id]: "" })); const prevStatus = current.status; setAlerts((rows) => rows.map((a) => (a.id === id ? { ...a, status: "acknowledged" } : a))); try { const updated = await Keeper.api.ackAlert(id); const nextStatus = (updated && updated.status) || "acknowledged"; setAlerts((rows) => rows.map((a) => (a.id === id ? { ...a, status: nextStatus } : a))); await loadAlerts(); } catch (e) { setAlerts((rows) => rows.map((a) => (a.id === id ? { ...a, status: prevStatus } : a))); const fallbackMessage = "Не удалось подтвердить уведомление"; const formatted = typeof Keeper.formatApiFailure === "function" ? Keeper.formatApiFailure(e, { fallbackMessage }) : fallbackMessage; setRowErrors((m) => ({ ...m, [id]: formatted })); } finally { setPendingAck((m) => ({ ...m, [id]: false })); } } // [TASK_COMPLETE:T009] return (
Уведомления
{counts.active} активные · {counts.closed} закрытые
{loading && (
Загрузка уведомлений...
)} {!loading && error && (
{error}
)} {/* [TASK_START:T010] */} {!loading && !error && alerts.length === 0 && (
Нет уведомлений
)} {/* [TASK_COMPLETE:T010] */} {!loading && !error && alerts.map((a) => { const rowKind = a.severity || a.kind; const canAck = isActive(a); const ackBusy = !!pendingAck[a.id]; return (
{a.title}
{a.sub}
{rowErrors[a.id] && (
{rowErrors[a.id]}
)}
{a.zone || "—"} {a.who || "—"} {a.time} {canAck ? ( ) : ( {a.status === "acknowledged" ? "Подтверждено" : "Закрыто"} )}
); })}
); } window.Alerts = Alerts;