"use client"; import { FormEvent, useMemo, useState } from "react"; import { Radio, Search, ShieldAlert, Siren } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Dialog } from "@/components/ui/dialog"; import { Input, Textarea } from "@/components/ui/input"; import { Select } from "@/components/ui/select"; import { Table } from "@/components/ui/table"; import type { IncidentSeverity } from "@/modules/core/types"; import type { ApiResult } from "@/modules/core/server/api"; import { INCIDENT_SEVERITY_LABELS } from "@/modules/emergency/types"; import type { AlertCenterData, AlertRule, AlertRuleInput, AlertRuleStatus, AlertRuleType, AlertTrigger, AlertTriggerInput, AlertTriggerStatus } from "@/modules/alerts/types"; import { ALERT_RULE_STATUS_LABELS, ALERT_RULE_STATUS_VALUES, ALERT_RULE_TYPE_LABELS, ALERT_RULE_TYPE_VALUES, ALERT_TRIGGER_STATUS_LABELS, ALERT_TRIGGER_STATUS_VALUES } from "@/modules/alerts/types"; type Props = { canManage: boolean; initialData: AlertCenterData }; type DialogState = | { kind: "rule"; mode: "create"; value: AlertRuleInput } | { kind: "rule"; mode: "edit"; id: string; value: AlertRuleInput } | { kind: "trigger"; mode: "create"; value: AlertTriggerInput } | { kind: "trigger"; mode: "edit"; id: string; value: AlertTriggerInput }; const severityValues = ["info", "warning", "critical"] as const satisfies readonly IncidentSeverity[]; const emptyRule: AlertRuleInput = { name: "", ruleType: "health", severity: "warning", status: "enabled", conditionSummary: "", suggestion: "" }; const emptyTrigger: AlertTriggerInput = { ruleId: undefined, elderId: undefined, title: "", description: "", status: "open", source: "", handlingNotes: "" }; function formatDateTime(value: string | undefined): string { return value ? new Date(value).toLocaleString("zh-CN") : "-"; } function ruleToInput(rule: AlertRule): AlertRuleInput { return { name: rule.name, ruleType: rule.ruleType, severity: rule.severity, status: rule.status, conditionSummary: rule.conditionSummary, suggestion: rule.suggestion }; } function triggerToInput(trigger: AlertTrigger): AlertTriggerInput { return { ruleId: trigger.ruleId, elderId: trigger.elderId, title: trigger.title, description: trigger.description, status: trigger.status, source: trigger.source, handlingNotes: trigger.handlingNotes }; } function badgeVariant(value: string): "danger" | "secondary" | "success" | "warning" { if (value === "critical") return "danger"; if (value === "enabled" || value === "resolved" || value === "closed") return "success"; if (value === "open" || value === "acknowledged" || value === "warning") return "warning"; return "secondary"; } export function AlertsWorkspaceClient({ canManage, initialData }: Props): React.ReactElement { const [data, setData] = useState(initialData); const [query, setQuery] = useState(""); const [triggerStatus, setTriggerStatus] = useState<"all" | AlertTriggerStatus>("all"); const [dialog, setDialog] = useState(); const [message, setMessage] = useState(""); const [isPending, setIsPending] = useState(false); const normalizedQuery = query.trim().toLowerCase(); const filteredRules = useMemo(() => data.rules.filter((rule) => !normalizedQuery || [rule.name, rule.conditionSummary, rule.suggestion].join(" ").toLowerCase().includes(normalizedQuery)), [data.rules, normalizedQuery]); const filteredTriggers = useMemo( () => data.triggers.filter((trigger) => { const matchesQuery = !normalizedQuery || [trigger.title, trigger.description, trigger.ruleName, trigger.elderName, trigger.source].join(" ").toLowerCase().includes(normalizedQuery); return matchesQuery && (triggerStatus === "all" || trigger.status === triggerStatus); }), [data.triggers, normalizedQuery, triggerStatus], ); async function refreshData(): Promise { const response = await fetch("/api/alerts/rules"); const result = (await response.json()) as ApiResult<{ data: AlertCenterData }>; if (result.success) setData(result.data); } async function submitDialog(event: FormEvent): Promise { event.preventDefault(); if (!dialog || !canManage) { setMessage("当前角色无权维护规则预警"); return; } const isCreate = dialog.mode === "create"; const path = dialog.kind === "rule" ? (isCreate ? "/api/alerts/rules" : `/api/alerts/rules/${dialog.id}`) : isCreate ? "/api/alerts/triggers" : `/api/alerts/triggers/${dialog.id}`; setIsPending(true); const response = await fetch(path, { method: isCreate ? "POST" : "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify(dialog.value) }); const result = (await response.json()) as ApiResult>; setIsPending(false); setMessage(result.reason); if (result.success) { setDialog(undefined); await refreshData(); } } async function remove(kind: "rule" | "trigger", id: string): Promise { if (!canManage) { setMessage("当前角色无权删除规则预警记录"); return; } setIsPending(true); const response = await fetch(kind === "rule" ? `/api/alerts/rules/${id}` : `/api/alerts/triggers/${id}`, { method: "DELETE" }); const result = (await response.json()) as ApiResult>; setIsPending(false); setMessage(result.reason); if (result.success) await refreshData(); } return (

规则预警

维护规则并处理预警触发记录。

update({ name: event.target.value })} required value={value.name} /> update({ severity: severity as IncidentSeverity })} options={severityValues.map((item) => ({ value: item, label: INCIDENT_SEVERITY_LABELS[item] }))} value={value.severity} />