import { createFileRoute } from "@tanstack/react-router"; import { invoke } from "@tauri-apps/api/core"; import { open } from "@tauri-apps/plugin-dialog"; import { readTextFile } from "@tauri-apps/plugin-fs"; import { FileUp, Filter, Search } from "lucide-react"; import * as React from "react"; import { AlertList } from "@/components/monitor/alert-list"; import { IncidentManager } from "@/components/monitor/incident-manager"; import { KpiDashboard } from "@/components/monitor/kpi-dashboard"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Checkbox } from "@/components/ui/checkbox"; import { DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; import type { Incident, KpiMetrics, ProcessedAlert } from "@/lib/types/alerts"; type StatusFilter = "all" | "resolved" | "unresolved"; export const Route = createFileRoute("/monitor")({ component: MonitorPage, }); function MonitorPage() { const [alerts, setAlerts] = React.useState([]); const [incidents, setIncidents] = React.useState([]); const [kpis, setKpis] = React.useState(null); const [selectedAlert, setSelectedAlert] = React.useState(null); const [isLoading, setIsLoading] = React.useState(false); const [error, setError] = React.useState(null); const [searchQuery, setSearchQuery] = React.useState(""); const [statusFilter, setStatusFilter] = React.useState("all"); const filteredAlerts = React.useMemo(() => { return alerts.filter((alert) => { if (statusFilter === "resolved" && !alert.isResolved) return false; if (statusFilter === "unresolved" && alert.isResolved) return false; if (!searchQuery.trim()) return true; const query = searchQuery.toLowerCase(); if (alert.alertName?.toLowerCase().includes(query)) return true; for (const value of Object.values(alert.labels)) { if (value.toLowerCase().includes(query)) return true; } return false; }); }, [alerts, searchQuery, statusFilter]); const selectAllState = React.useMemo(() => { if (filteredAlerts.length === 0) return "none" as const; const invalidCount = filteredAlerts.filter((a) => a.isInvalid).length; if (invalidCount === 0) return "none" as const; if (invalidCount === filteredAlerts.length) return "all" as const; return "some" as const; }, [filteredAlerts]); const recalculateKpis = React.useCallback( async (currentAlerts: ProcessedAlert[], currentIncidents: Incident[]) => { try { const newKpis = await invoke("calculate_kpis_command", { alerts: currentAlerts, incidents: currentIncidents, }); setKpis(newKpis); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } }, [], ); const handleLoadFile = async () => { try { setIsLoading(true); setError(null); const filePath = await open({ multiple: false, filters: [{ name: "JSON", extensions: ["json"] }], }); if (!filePath) { setIsLoading(false); return; } const content = await readTextFile(filePath); const processedAlerts = await invoke( "process_alerts_json", { jsonContent: content }, ); setAlerts(processedAlerts); setSelectedAlert(null); await recalculateKpis(processedAlerts, incidents); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setIsLoading(false); } }; const handleToggleInvalid = (alertId: number, isInvalid: boolean) => { setAlerts((prev) => { const updated = prev.map((a) => a.id === alertId ? { ...a, isInvalid } : a, ); recalculateKpis(updated, incidents); return updated; }); if (selectedAlert?.id === alertId) { setSelectedAlert((prev) => (prev ? { ...prev, isInvalid } : null)); } }; const handleToggleAllInvalid = (isInvalid: boolean) => { const filteredIds = new Set(filteredAlerts.map((a) => a.id)); setAlerts((prev) => { const updated = prev.map((a) => filteredIds.has(a.id) ? { ...a, isInvalid } : a, ); recalculateKpis(updated, incidents); return updated; }); if (selectedAlert && filteredIds.has(selectedAlert.id)) { setSelectedAlert((prev) => (prev ? { ...prev, isInvalid } : null)); } }; const handleSelectAlert = (alert: ProcessedAlert) => { setSelectedAlert(alert); }; const handleAddIncident = (incident: Omit) => { const newIncident: Incident = { ...incident, attachedAlertIds: [], }; const newIncidents = [...incidents, newIncident]; setIncidents(newIncidents); recalculateKpis(alerts, newIncidents); }; const handleAttachAlert = (alertId: number, incidentId: string) => { const updatedAlerts = alerts.map((a) => a.id === alertId ? { ...a, attachedIncidentId: incidentId } : a, ); const updatedIncidents = incidents.map((i) => i.id === incidentId && !i.attachedAlertIds.includes(alertId) ? { ...i, attachedAlertIds: [...i.attachedAlertIds, alertId] } : i, ); setAlerts(updatedAlerts); setIncidents(updatedIncidents); recalculateKpis(updatedAlerts, updatedIncidents); if (selectedAlert?.id === alertId) { setSelectedAlert((prev) => prev ? { ...prev, attachedIncidentId: incidentId } : null, ); } }; const handleDetachAlert = (alertId: number) => { const alert = alerts.find((a) => a.id === alertId); const incidentId = alert?.attachedIncidentId; const updatedAlerts = alerts.map((a) => a.id === alertId ? { ...a, attachedIncidentId: null } : a, ); const updatedIncidents = incidentId ? incidents.map((i) => i.id === incidentId ? { ...i, attachedAlertIds: i.attachedAlertIds.filter( (aid) => aid !== alertId, ), } : i, ) : incidents; setAlerts(updatedAlerts); setIncidents(updatedIncidents); recalculateKpis(updatedAlerts, updatedIncidents); if (selectedAlert?.id === alertId) { setSelectedAlert((prev) => prev ? { ...prev, attachedIncidentId: null } : null, ); } }; return (

Alert Monitor

{error && (
{error}
)}
handleToggleAllInvalid(checked === true) } disabled={filteredAlerts.length === 0} aria-label="Mark all filtered as invalid" /> {filteredAlerts.length} {(searchQuery || statusFilter !== "all") && ` / ${alerts.length}`} {statusFilter === "all" ? "All" : statusFilter === "resolved" ? "Resolved" : "Unresolved"} } /> setStatusFilter(v as StatusFilter)} > All Resolved Unresolved
setSearchQuery(e.target.value)} className="h-7 pl-7 text-xs" />
{alerts.length === 0 ? (
No Alerts Loaded Click "Load Alerts JSON" to import alert data from a Grafana Alerts API export.
) : ( )}
); }