// 告警中心主组件 // Logan.Li import React, { useState, useEffect } from 'react'; import { AlertTriangle, AlertCircle, CheckCircle, Zap, Search, RefreshCw, Eye, Check, VolumeX, Plus, Rocket, } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent } from '@/components/ui/card'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Switch } from '@/components/ui/switch'; import { toast } from 'sonner'; import { useConfirm } from '@/components/ui/confirm-dialog'; import { alertCenterApi, agentApi } from '@/services/api'; import { type Alert, type AlertRule, mapAlert, getSeverityBadge, getStatusBadge, } from './alert-center-helpers'; import { AlertDetailDialog, AlertSilenceDialog } from './AlertCenterDetail'; import { AlertChannelsTab } from './AlertChannelsTab'; import { AlertRuleDialog } from '@/components/monitor/AlertRuleDialog'; const AlertCenter: React.FC = () => { const _confirm = useConfirm(); const [alerts, setAlerts] = useState([]); const [rules, setRules] = useState([]); const [loading, setLoading] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [severityFilter, setSeverityFilter] = useState('all'); const [statusFilter, setStatusFilter] = useState('all'); const [activeTab, setActiveTab] = useState('alerts'); const [selectedAlert, setSelectedAlert] = useState(null); const [detailOpen, setDetailOpen] = useState(false); const [silenceOpen, setSilenceOpen] = useState(false); const [stats, setStats] = useState({ firing: 0, critical: 0, warning: 0, resolved: 0 }); // 规则弹框 const [ruleDialogOpen, setRuleDialogOpen] = useState(false); const [editingRule, setEditingRule] = useState(null); const [quickSetupLoading, setQuickSetupLoading] = useState(false); useEffect(() => { fetchData(); const interval = setInterval(fetchData, 30000); return () => clearInterval(interval); }, []); const fetchData = async () => { await Promise.all([fetchAlerts(), fetchRules()]); }; // Logan.Li — 获取告警列表 const fetchAlerts = async () => { setLoading(true); try { const res = await alertCenterApi.getAlerts(); const raw = (res.list || []) as any[]; const data: Alert[] = raw.map(mapAlert); setAlerts(data); setStats({ firing: data.filter(a => a.status === 'firing').length, critical: data.filter(a => a.severity === 'critical' && a.status === 'firing').length, warning: data.filter(a => a.severity === 'warning' && a.status === 'firing').length, resolved: data.filter(a => a.status === 'resolved').length, }); } catch { toast.error('获取告警失败'); } finally { setLoading(false); } }; const fetchRules = async () => { try { const res = await agentApi.listRules(); // agentApi.listRules returns {code, data: AgentRule[]} — map to AlertRule for UI const raw = (Array.isArray(res) ? res : (res.list || [])) as any[]; setRules(raw.map(r => ({ id: r.id, name: r.name, description: `${r.metric} ${r.operator} ${r.threshold}%`, enabled: r.enabled, severity: 'warning', type: r.metric, hostname: r.hostname || '全部主机', threshold: r.threshold, })) as AlertRule[]); } catch { toast.error('获取告警规则失败'); } }; const handleAcknowledge = async (id: string) => { try { await alertCenterApi.acknowledge(id); toast.success('告警已确认'); fetchAlerts(); } catch { toast.error('操作失败'); } }; const handleResolve = async (id: string) => { try { await alertCenterApi.resolve(id); toast.success('告警已解决'); fetchAlerts(); } catch { toast.error('操作失败'); } }; const handleSilence = async (id: string, duration: string) => { try { await alertCenterApi.silence(id, duration); toast.success('告警已静默'); setSilenceOpen(false); fetchAlerts(); } catch { toast.error('操作失败'); } }; const toggleRule = async (ruleId: string, enabled: boolean) => { try { await agentApi.updateRule(Number(ruleId), { enabled }); setRules(rules.map(r => String(r.id) === ruleId ? { ...r, enabled } : r)); toast.success('规则状态已更新'); } catch { toast.error('更新失败'); } }; const handleOpenAddRule = () => { setEditingRule(null); setRuleDialogOpen(true); }; const handleEditRule = (rule: AlertRule) => { setEditingRule(rule); setRuleDialogOpen(true); }; const handleRuleDialogClose = () => { setRuleDialogOpen(false); setEditingRule(null); }; const handleQuickSetup = async () => { const ok = await _confirm({ title: '一键报警', description: '将为 Agent 在线的全部主机创建 CPU使用率、内存使用率、磁盘使用率、网络带宽使用率 四条默认告警规则(阈值 80%,连续 5 次触发)。', confirmText: '确认创建', }); if (!ok) return; setQuickSetupLoading(true); try { const res = await agentApi.quickSetup('ecs'); toast.success(typeof res === 'string' ? res : (res?.message || '默认告警规则创建成功')); fetchRules(); } catch { toast.error('创建失败,请检查 Agent 主机是否在线'); } finally { setQuickSetupLoading(false); } }; const handleDeleteRule = async (rule: AlertRule) => { const ok = await _confirm({ title: '删除规则', description: `确定删除规则「${rule.name}」?`, confirmText: '删除', variant: 'destructive' }); if (!ok) return; try { await agentApi.deleteRule(Number(rule.id)); toast.success('已删除'); fetchRules(); } catch { toast.error('删除失败'); } }; const filteredAlerts = alerts.filter(alert => { const matchSearch = alert.title.toLowerCase().includes(searchQuery.toLowerCase()) || alert.description.toLowerCase().includes(searchQuery.toLowerCase()) || alert.source.toLowerCase().includes(searchQuery.toLowerCase()); const matchSeverity = severityFilter === 'all' || alert.severity === severityFilter; const matchStatus = statusFilter === 'all' || alert.status === statusFilter; return matchSearch && matchSeverity && matchStatus; }); return (
{/* 统计卡片 */}
0 ? 'border-red-500 border-2' : ''}>

触发中

0 ? 'text-red-600' : ''}`}>{stats.firing}

0 ? 'bg-red-100' : 'bg-gray-100'}`}> 0 ? 'text-red-600' : 'text-gray-600'}`} />

严重告警

{stats.critical}

警告告警

{stats.warning}

今日已解决

{stats.resolved}

告警列表 告警规则 通知渠道 {/* 告警列表 Tab */}
setSearchQuery(e.target.value)} className="pl-10" />
{/* 告警列表 */}
{loading ? ( ) : filteredAlerts.length === 0 ? ( ) : ( filteredAlerts.map((alert) => ( )) )}
告警信息 级别 状态 来源 持续时间 操作
加载中...
暂无告警
{alert.title}
{alert.description}
{Object.entries(alert.labels).slice(0, 3).map(([k, v]) => ( {k}={v} ))}
{getSeverityBadge(alert.severity)} {getStatusBadge(alert.status)}
{alert.source}
{alert.service}
{alert.status === 'resolved' && alert.resolvedAt ? `${Math.floor((new Date(alert.resolvedAt).getTime() - new Date(alert.startedAt).getTime()) / 60000)}分钟` : `${Math.floor((Date.now() - new Date(alert.startedAt).getTime()) / 60000)}分钟`}
{alert.status === 'firing' && ( )} {(alert.status === 'firing' || alert.status === 'acknowledged') && ( )}
{/* 告警规则 Tab */}
{rules.length === 0 ? (

暂无告警规则

点击「添加规则」或「一键报警」快速创建

) : (
{rules.map((rule) => (
{rule.name} {getSeverityBadge(rule.severity)} {rule.enabled ? '已启用' : '已禁用'}

{rule.description}

{rule.expression && ( {rule.expression} )} {rule.duration ? 持续 {rule.duration}秒 : null}
{(() => { let chs: string[] = []; try { const raw = rule.notify_channels; if (raw) chs = Array.isArray(raw) ? raw : JSON.parse(raw); } catch { /* ignore */ } if (!chs.length) return null; return (
{chs.map(ch => ( {ch} ))}
); })()}
toggleRule(String(rule.id), checked)} />
))}
)}
{/* 通知渠道 Tab — 自包含子组件 */}
{ handleRuleDialogClose(); fetchRules(); }} />
); }; export default AlertCenter;