feat: 重构监控中心导航,新增资源监控4大模块

- 主菜单「监控告警」→「监控中心」
- 「告警中心」→「告警服务」移至链路追踪下方
- 新增资源监控 Tab 页(主机/中间件/SNMP/站点拨测)
- Agent 新增网络采集器(network)和磁盘IO采集器(diskio)
- 后端扩展 Agent 上报模型,支持 NetworkStat/DiskIOStat
- 前端 ServerTableRow 展示网络和磁盘IO实时指标
- WebSocket MetricsUpdate 推送扩展
This commit is contained in:
attacker committed 2026-06-18 09:30:45 +00:00
1 parent 020b7a9063
commit f3a9e5e8d0
21 files changed
+1007 -32

No files matched your search

+4 -5
View File
@@ -59,7 +59,6 @@ const menuItems: MenuItem[] = [
{ id: 'regions', name: '区域管理', icon: MapPin, path: '/regions' },
{ id: 'platforms', name: '云平台', icon: CloudCog, path: '/platforms' },
{ id: 'cloud-billing', name: '云账单', icon: BarChart3, path: '/cloud-billing' },
{ id: 'agent-monitor', name: 'Agent监控', icon: Activity, path: '/agent-monitor' },
],
},
{
@@ -84,14 +83,14 @@ const menuItems: MenuItem[] = [
},
{
id: 'monitor',
name: '监控告警',
name: '监控中心',
icon: Bell,
children: [
{ id: 'monitor-center', name: '资源监控', icon: Activity, path: '/monitor-center' },
{ id: 'metrics', name: '指标监控', icon: Cpu, path: '/metrics' },
{ id: 'alert-center', name: '告警中心', icon: Bell, path: '/alert-center' },
{ id: 'trace', name: '链路追踪', icon: GitBranch, path: '/trace' },
{ id: 'grafana', name: '监控大屏', icon: Monitor, path: '/grafana' },
{ id: 'agent-alerts', name: 'Agent告警', icon: AlertTriangle, path: '/agent-alerts' },
{ id: 'trace', name: '链路追踪', icon: GitBranch, path: '/trace' },
{ id: 'alert-center', name: '告警服务', icon: Bell, path: '/alert-center' },
],
},
{
@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useRef } from 'react';
import { cn } from '@/lib/utils';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
@@ -168,6 +168,82 @@ export function ServerManager() {
loadData();
}, []);
// WebSocket 实时接收 Agent 指标推送 // Logan.Li
const wsRef = useRef<WebSocket | null>(null);
const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => {
const token = useUserStore.getState().token;
if (!token) return;
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws/metrics?token=${token}`;
const connect = () => {
if (wsRef.current?.readyState === WebSocket.OPEN) return;
const ws = new WebSocket(wsUrl);
wsRef.current = ws;
ws.onopen = () => {
console.log('[MetricsWS] 已连接,实时接收指标推送');
};
ws.onmessage = (event) => {
try {
const msg = JSON.parse(event.data);
if (msg.type === 'metrics_update') {
setServers((prev) =>
prev.map((s) => {
// 通过 hostname 匹配(Agent 上报时使用 hostname)
if (s.hostname === msg.hostname) {
return {
...s,
cpu_usage: msg.cpu_usage,
memory_usage: msg.memory_usage,
disk_usage: msg.disk_usage,
status: msg.status,
tcp_established: msg.tcp_established,
tcp_time_wait: msg.tcp_time_wait,
net_rx_bps: msg.net_rx_bps,
net_tx_bps: msg.net_tx_bps,
disk_read_bps: msg.disk_read_bps,
disk_write_bps: msg.disk_write_bps,
};
}
return s;
})
);
}
} catch (e) {
// 忽略非 JSON 消息
}
};
ws.onclose = () => {
console.log('[MetricsWS] 连接断开,3 秒后重连');
wsRef.current = null;
reconnectTimerRef.current = setTimeout(connect, 3000);
};
ws.onerror = (err) => {
console.error('[MetricsWS] 连接错误:', err);
ws.close();
};
};
connect();
return () => {
if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current);
if (wsRef.current) {
wsRef.current.onclose = null; // 阻止自动重连
wsRef.current.close();
wsRef.current = null;
}
};
}, []);
const filteredServers = servers.filter((server) => {
const matchesSearch =
(server.hostname || '').toLowerCase().includes(searchQuery.toLowerCase()) ||
@@ -12,7 +12,7 @@ import {
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { TableCell, TableRow } from '@/components/ui/table';
import { Cpu, Edit, Loader2, MoreHorizontal, Server as ServerIcon, Terminal, Trash2, CheckCircle, XCircle } from "lucide-react";
import { Cpu, Edit, Loader2, MoreHorizontal, Server as ServerIcon, Terminal, Trash2, CheckCircle, XCircle, Network, HardDrive } from 'lucide-react';
import { getStatusBadge, getTags, getUsageColor } from './serverHelpers';
interface ServerTableRowProps {
@@ -42,6 +42,19 @@ export function ServerTableRow({
const memoryUsage = Number(server.memory_usage || 0);
const tags = getTags(server);
const memGB = server.memory ? Math.round(server.memory / 1024) : 0;
const tcpEst = server.tcp_established || 0;
const tcpTw = server.tcp_time_wait || 0;
const rxBps = server.net_rx_bps || 0;
const txBps = server.net_tx_bps || 0;
const readBps = server.disk_read_bps || 0;
const writeBps = server.disk_write_bps || 0;
// Format bytes/s to human readable
const fmtBps = (bps: number) => {
if (bps >= 1_000_000) return (bps / 1_000_000).toFixed(1) + 'MB/s';
if (bps >= 1_000) return (bps / 1_000).toFixed(1) + 'KB/s';
return bps.toFixed(0) + 'B/s';
};
return (
<TableRow className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/50">
@@ -107,6 +120,15 @@ export function ServerTableRow({
</div>
<span className="text-xs text-[var(--dg-text-secondary)]">{memoryUsage.toFixed(1)}%</span>
</div>
<div className="flex items-center gap-1 text-xs text-[var(--dg-text-muted)] pt-0.5">
<Network className="h-3 w-3" />
<span>{tcpEst}连接</span>
{tcpTw > 0 && <span className="text-[var(--dg-orange)]">({tcpTw}TW)</span>}
</div>
<div className="flex items-center gap-1 text-xs text-[var(--dg-text-muted)]">
<HardDrive className="h-3 w-3" />
<span>读{fmtBps(readBps)}/写{fmtBps(writeBps)}</span>
</div>
</div>
</TableCell>
<TableCell>