// SNMP 监控 — 设备管理 + 指标可视化(主区域优先) // Logan.Li import { useState, useEffect, useCallback, type MouseEvent } from 'react'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from '@/components/ui/dialog'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Router, RefreshCw, Plus, Trash2, Zap, Eye, Loader2 } from 'lucide-react'; import { toast } from 'sonner'; import { snmpApi } from '@/api'; import { SnmpOverviewCharts } from './snmp-monitor/SnmpOverviewCharts'; import { SnmpDeviceChartsPanel } from './snmp-monitor/SnmpDeviceChartsPanel'; import { SnmpTemplatesCollapsible, type SnmpTemplate } from './snmp-monitor/SnmpTemplatesCollapsible'; import type { SnmpDevice, SnmpMetric, SnmpOverviewItem } from './snmp-monitor/helpers'; interface SnmpStats { total: number; online: number; abnormal: number } const emptyForm = { name: '', ip: '', port: 161, snmp_version: 'v2c' as 'v2c' | 'v3', community: 'public', template_id: '', }; const keyMetrics = [ 'h3c_cpu_usage', 'hw_cpu_usage', 'cisco_cpu_5min', 'ruijie_cpu_usage', 'inspur_cpu_usage', 'dell_cpu_status', 'h3c_mem_usage', 'hw_mem_usage', 'h3c_temperature', 'hw_temperature', 'if_in_octets', 'if_out_octets', ]; export function SNMPMonitor() { const [stats, setStats] = useState({ total: 0, online: 0, abnormal: 0 }); const [devices, setDevices] = useState([]); const [overview, setOverview] = useState([]); const [templates, setTemplates] = useState([]); const [loading, setLoading] = useState(true); const [dialogOpen, setDialogOpen] = useState(false); const [form, setForm] = useState({ ...emptyForm }); const [submitting, setSubmitting] = useState(false); const [collectingId, setCollectingId] = useState(null); const [selectedDevice, setSelectedDevice] = useState(null); const [metricsOpen, setMetricsOpen] = useState(false); const [metricsDevice, setMetricsDevice] = useState(null); const [metrics, setMetrics] = useState([]); const [metricsLoading, setMetricsLoading] = useState(false); const loadTemplates = useCallback(async () => { try { const res = await snmpApi.getTemplates() as { list?: SnmpTemplate[] }; setTemplates(res?.list || []); } catch { setTemplates([]); } }, []); const loadStats = useCallback(async () => { try { setStats(await snmpApi.getStats() as SnmpStats); } catch { /* ignore */ } }, []); const loadOverview = useCallback(async () => { try { const data = await snmpApi.getOverviewMetrics() as { list?: SnmpOverviewItem[] }; setOverview(data?.list || []); } catch { setOverview([]); } }, []); const loadDevices = useCallback(async () => { try { const data = await snmpApi.getDevices() as { list?: SnmpDevice[] }; const list = data.list ?? []; setDevices(list); setSelectedDevice((prev) => { if (prev && list.some((d) => d.id === prev.id)) return list.find((d) => d.id === prev.id) || null; return list.find((d) => d.status === 'online') || list[0] || null; }); } catch { setDevices([]); setSelectedDevice(null); } finally { setLoading(false); } }, []); const refresh = useCallback(() => { loadStats(); loadDevices(); loadTemplates(); loadOverview(); }, [loadStats, loadDevices, loadTemplates, loadOverview]); useEffect(() => { refresh(); }, [refresh]); // 30s 静默刷新,与采集周期对齐 // Logan.Li useEffect(() => { if (loading && devices.length === 0) return; const timer = setInterval(refresh, 30_000); return () => clearInterval(timer); }, [refresh, loading, devices.length]); const templateName = (id: number) => templates.find((t) => t.id === id)?.name || `#${id}`; const handleAdd = async () => { if (!form.name.trim() || !form.ip.trim() || !form.template_id) { toast.error('请填写设备名称、IP 并选择 OID 模板'); return; } setSubmitting(true); try { const tpl = templates.find((t) => String(t.id) === form.template_id); await snmpApi.createDevice({ name: form.name.trim(), ip: form.ip.trim(), port: Number(form.port), snmp_version: form.snmp_version, community: form.community.trim(), template_id: Number(form.template_id), vendor: tpl?.vendor || '', device_type: tpl?.device_type || 'switch', }); toast.success('设备已添加'); setDialogOpen(false); setForm({ ...emptyForm }); refresh(); } catch (e) { toast.error((e as { message?: string }).message || '添加失败'); } finally { setSubmitting(false); } }; const handleCollect = async (d: SnmpDevice, e?: MouseEvent) => { e?.stopPropagation(); setCollectingId(d.id); try { await snmpApi.collectDevice(d.id); toast.success(`${d.name} 采集成功`); refresh(); } catch (err) { toast.error((err as { message?: string }).message || '采集失败'); refresh(); } finally { setCollectingId(null); } }; const openMetrics = async (d: SnmpDevice, e: MouseEvent) => { e.stopPropagation(); setMetricsDevice(d); setMetricsOpen(true); setMetricsLoading(true); try { const res = await snmpApi.getDeviceMetrics(d.id) as { list?: SnmpMetric[] }; setMetrics(res?.list || []); } catch { setMetrics([]); } finally { setMetricsLoading(false); } }; const handleDelete = async (id: number, e: MouseEvent) => { e.stopPropagation(); try { await snmpApi.deleteDevice(id); toast.success('已删除'); if (selectedDevice?.id === id) setSelectedDevice(null); refresh(); } catch { toast.error('删除失败'); } }; const statusBadge = (status?: string) => { if (status === 'online') return 在线; if (status === 'error') return 异常; return 离线; }; return (
{/* 工具栏 */}

SNMP 监控

5 分钟采集 · 指标保留 7 天

添加 SNMP 设备 选择厂商 OID 模板,配置管理 IP 与 Community
setForm({ ...form, name: e.target.value })} placeholder="核心交换机-A" />
setForm({ ...form, ip: e.target.value })} placeholder="192.168.1.1" />
setForm({ ...form, port: Number(e.target.value) || 161 })} />
setForm({ ...form, community: e.target.value })} />
{/* 紧凑统计 */}
{[ { label: '设备总数', value: stats.total, color: '' }, { label: '在线', value: stats.online, color: 'dg-text-green' }, { label: '异常', value: stats.abnormal, color: 'dg-text-red' }, { label: 'OID 模板', value: templates.length, color: 'dg-text-cyan' }, ].map((item) => (

{item.label}

{item.value}

))}
{loading ? (
加载中...
) : devices.length === 0 ? ( 暂无 SNMP 设备,点击「添加设备」开始监控 ) : ( <> {/* 主监控区:设备列表 + 趋势图 */}

设备列表 ({devices.length})

名称 IP 状态 {devices.map((d) => ( setSelectedDevice(d)} > {d.name} {d.ip} {statusBadge(d.status)}
e.stopPropagation()}>
))}
)} {/* OID 模板 — 默认折叠 */} {/* 指标详情弹窗 */} {metricsDevice?.name} — 采集指标 {metricsDevice?.ip} · {templateName(metricsDevice?.template_id || 0)} {metricsLoading ? (
加载中...
) : metrics.length === 0 ? (
暂无指标,请先采集 {metricsDevice?.last_error &&

{metricsDevice.last_error}

}
) : (
{metrics.filter((m) => keyMetrics.includes(m.name)).length > 0 && (
{metrics.filter((m) => keyMetrics.includes(m.name)).map((m) => (

{m.name}

{m.raw_value || m.value}{m.unit ? ` ${m.unit}` : ''}

))}
)} 指标索引值OID {metrics.map((m) => ( {m.name} {m.index || '-'} {m.raw_value || m.value}{m.unit ? ` ${m.unit}` : ''} {m.oid} ))}
)}
); }