{item.description}
{item.message}
import React, { useEffect, useMemo, useState } from "react"; import { apiFetch } from "../api"; import { useAuth } from "../state"; const initialForm = { name: "", description: "", target_id: "", sql_text: "SELECT count(*)::float FROM pg_stat_activity WHERE state = 'active'", comparison: "gte", warning_threshold: "", alert_threshold: "", enabled: true, }; function formatAlertValue(value) { if (value === null || value === undefined) return "-"; if (Number.isInteger(value)) return String(value); return Number(value).toFixed(2); } export function AlertsPage() { const { tokens, refresh, me } = useAuth(); const [targets, setTargets] = useState([]); const [status, setStatus] = useState({ warnings: [], alerts: [], warning_count: 0, alert_count: 0 }); const [definitions, setDefinitions] = useState([]); const [form, setForm] = useState(initialForm); const [error, setError] = useState(""); const [loading, setLoading] = useState(true); const [testing, setTesting] = useState(false); const [testResult, setTestResult] = useState(""); const [saving, setSaving] = useState(false); const canManageAlerts = me?.role === "admin" || me?.role === "operator"; const loadAll = async () => { try { setError(""); const [targetRows, statusPayload] = await Promise.all([ apiFetch("/targets", {}, tokens, refresh), apiFetch("/alerts/status", {}, tokens, refresh), ]); setTargets(targetRows); setStatus(statusPayload); if (canManageAlerts) { const defs = await apiFetch("/alerts/definitions", {}, tokens, refresh); setDefinitions(defs); } } catch (e) { setError(String(e.message || e)); } finally { setLoading(false); } }; useEffect(() => { loadAll(); }, [canManageAlerts]); useEffect(() => { const timer = setInterval(() => { apiFetch("/alerts/status", {}, tokens, refresh) .then(setStatus) .catch(() => {}); }, 20000); return () => clearInterval(timer); }, [tokens, refresh]); const targetOptions = useMemo( () => [{ id: "", name: "All targets" }, ...targets.map((t) => ({ id: String(t.id), name: `${t.name} (${t.host}:${t.port})` }))], [targets] ); const createDefinition = async (e) => { e.preventDefault(); setSaving(true); setTestResult(""); try { await apiFetch( "/alerts/definitions", { method: "POST", body: JSON.stringify({ name: form.name, description: form.description || null, target_id: form.target_id ? Number(form.target_id) : null, sql_text: form.sql_text, comparison: form.comparison, warning_threshold: form.warning_threshold === "" ? null : Number(form.warning_threshold), alert_threshold: Number(form.alert_threshold), enabled: !!form.enabled, }), }, tokens, refresh ); setForm(initialForm); await loadAll(); } catch (e) { setError(String(e.message || e)); } finally { setSaving(false); } }; const testDefinition = async () => { if (!form.target_id) { setTestResult("Select a specific target to test this SQL query."); return; } setTesting(true); setTestResult(""); try { const res = await apiFetch( "/alerts/definitions/test", { method: "POST", body: JSON.stringify({ target_id: Number(form.target_id), sql_text: form.sql_text, }), }, tokens, refresh ); if (res.ok) { setTestResult(`Query test succeeded. Returned value: ${formatAlertValue(res.value)}`); } else { setTestResult(`Query test failed: ${res.error}`); } } catch (e) { setTestResult(String(e.message || e)); } finally { setTesting(false); } }; const removeDefinition = async (definitionId) => { if (!confirm("Delete this custom alert definition?")) return; try { await apiFetch(`/alerts/definitions/${definitionId}`, { method: "DELETE" }, tokens, refresh); await loadAll(); } catch (e) { setError(String(e.message || e)); } }; const toggleDefinition = async (definition) => { try { await apiFetch( `/alerts/definitions/${definition.id}`, { method: "PUT", body: JSON.stringify({ enabled: !definition.enabled }) }, tokens, refresh ); await loadAll(); } catch (e) { setError(String(e.message || e)); } }; if (loading) return
Warnings are early signals. Alerts are critical thresholds reached or exceeded.
{error &&{item.description}
{item.message}
No warning-level alerts right now.
)}{item.description}
{item.message}
No critical alerts right now.
)}Admins and operators can add SQL-based checks with warning and alert thresholds.
| Name | Scope | Comparison | Warn | Alert | Status | Actions |
|---|---|---|---|---|---|---|
| {d.name} | {d.target_id ? targets.find((t) => t.id === d.target_id)?.name || `Target #${d.target_id}` : "All targets"} | {d.comparison} | {d.warning_threshold ?? "-"} | {d.alert_threshold} | {d.enabled ? "Enabled" : "Disabled"} | {" "} |
No custom alerts created yet.
)}