// Agent 告警主组件:告警事件 Tab + 告警规则 Tab + 规则编辑弹窗 // Logan.Li import { useState, useEffect, useCallback } from 'react'; import { toast } from 'sonner'; import { useConfirm } from '@/components/ui/confirm-dialog'; import { agentApi } from '@/api'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Switch } from '@/components/ui/switch'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { AlertTriangle, CheckCircle, Plus, Pencil, Trash2, Loader2, RefreshCw, Bell, Cpu, Clock, } from 'lucide-react'; import { METRIC_OPTIONS, getMetricLabel, asList, severityBadge, } from './agentAlertHelper'; import { AgentRuleDialog } from './AgentRuleDialog'; export function AgentAlerts() { const confirm = useConfirm(); const [activeTab, setActiveTab] = useState('events'); const [alerts, setAlerts] = useState([]); const [rules, setRules] = useState([]); const [loading, setLoading] = useState(true); const [resolvedView, setResolvedView] = useState(false); // 规则编辑弹窗 const [ruleOpen, setRuleOpen] = useState(false); const [editingRule, setEditingRule] = useState(null); const loadData = useCallback(async () => { setLoading(true); try { const [alertsRes, rulesRes] = await Promise.all([ agentApi.listAlerts({ resolved: String(resolvedView) }), agentApi.listRules(), ]); setAlerts(asList(alertsRes)); setRules(asList(rulesRes)); } catch (err) { console.error(err); toast.error('加载告警数据失败'); } finally { setLoading(false); } }, [resolvedView]); useEffect(() => { loadData(); }, [loadData]); // ============ 告警事件 ============ const handleResolve = async (id: number) => { const ok = await confirm({ title: '恢复告警', description: '确定将此告警标记为已恢复?', confirmText: '恢复' }); if (!ok) return; try { await agentApi.resolveAlert(id); toast.success('告警已恢复'); loadData(); } catch (err: any) { toast.error(err?.message || '操作失败'); } }; // ============ 规则 CRUD ============ const openCreateRule = () => { setEditingRule(null); setRuleOpen(true); }; const openEditRule = ( rule: any) => { setEditingRule(rule); setRuleOpen(true); }; const handleDeleteRule = async (id: number) => { const ok = await confirm({ title: '删除规则', description: '确定删除此告警规则?', confirmText: '删除', variant: 'destructive' }); if (!ok) return; try { await agentApi.deleteRule(id); toast.success('规则已删除'); loadData(); } catch (err: any) { toast.error(err?.message || '删除失败'); } }; const handleToggleRule = async ( rule: any) => { try { await agentApi.updateRule(rule.id, { ...rule, enabled: !rule.enabled }); loadData(); } catch (err: any) { toast.error(err?.message || '操作失败'); } }; return (
{/* 页头 */}

Agent 告警

基于 Agent 上报指标的告警规则与事件

告警事件 告警规则 {/* ========== 告警事件 ========== */}
{loading ? ( 加载中... ) : alerts.length === 0 ? (

{resolvedView ? '暂无已恢复的告警' : '当前无活动告警,一切正常'}

) : (
{alerts.map((alert) => (
{severityBadge(alert.severity)} {alert.rule_name || alert.name || alert.title || '告警'}

{alert.message || alert.description || '触发告警阈值'}

{alert.hostname || '--'} 指标: {getMetricLabel(alert.metric)} {alert.value != null && ( 当前值: {alert.value} )} {alert.created_at || alert.triggered_at ? new Date(alert.created_at || alert.triggered_at).toLocaleString() : '--'}
{!resolvedView && ( )}
))}
)}
{/* ========== 告警规则 ========== */}
{loading ? ( 加载中... ) : rules.length === 0 ? (

暂无告警规则

) : (
{rules.map((rule) => { const MetricIcon = METRIC_OPTIONS.find((m) => m.value === rule.metric)?.icon || Cpu; return (
{rule.name} {severityBadge(rule.severity)} {rule.enabled === false && ( 已禁用 )}
{getMetricLabel(rule.metric)} {rule.metric !== 'offline' && ( {rule.operator} {rule.threshold}{rule.metric === 'port_down' ? '' : '%'} )} {rule.metric === 'port_down' && rule.port ? ( 端口 {rule.port} ) : null} {rule.duration ? 持续 {rule.duration}s : null}
handleToggleRule(rule)} />
); })}
)}
{/* ========== 规则编辑弹窗 ========== */}
); } export default AgentAlerts;