diff --git a/frontend/src/pages/HostMonitor.tsx b/frontend/src/pages/HostMonitor.tsx index 20158a2..5ed5316 100644 --- a/frontend/src/pages/HostMonitor.tsx +++ b/frontend/src/pages/HostMonitor.tsx @@ -103,7 +103,7 @@ export function HostMonitor() { }, [selectedHost]); // 端口策略 // Logan.Li - const [portPolicyMode, setPortPolicyMode] = useState('blacklist'); + const [portPolicyMode, setPortPolicyMode] = useState('whitelist'); const [portWhitelist, setPortWhitelist] = useState([]); const [portPolicyDialogOpen, setPortPolicyDialogOpen] = useState(false); const [portPolicySaving, setPortPolicySaving] = useState(false); @@ -141,6 +141,7 @@ export function HostMonitor() { useEffect(() => { agentApi.getLatest().then((data: any) => { const list = Array.isArray(data) ? data : (data?.data || data?.list || []); + setHosts(list); if (list.length > 0 && !selectedHost) { const m = urlHost ? list.find((h: any) => h.hostname === urlHost) : null; setSelectedHost(m?.hostname || list[0]?.hostname || ''); @@ -169,7 +170,7 @@ export function HostMonitor() { // 历史指标提取 const metrics = useMemo(() => { const rawCpu: number[] = []; const rawMem: number[] = []; const rawLoad1: number[] = []; - const rawDisk: number[] = []; const rawTcp: number[] = []; const rawTw: number[] = []; + const rawDisk: number[] = []; const rawTcp: number[] = []; const rawTw: number[] = []; const rawCw: number[] = []; const rawRx: number[] = []; const rawTx: number[] = []; const rawIoR: number[] = []; const rawIoW: number[] = []; const rawSwap: number[] = []; const rawFD: number[] = []; @@ -177,6 +178,7 @@ export function HostMonitor() { const rawIOWait: number[] = []; const rawMemAvail: number[] = []; const rawIoUtil: number[] = []; const rawConnTotal: number[] = []; const rawRxErr: number[] = []; const rawTxErr: number[] = []; + const rawInode: number[] = []; // 根分区 inode const timestamps: number[] = []; for (const r of history) { @@ -189,9 +191,18 @@ export function HostMonitor() { rawLoad1.push(r.load1 || 0); rawDisk.push(r.disk_usage || 0); + const diskInfoArr = parseJSON(r.disk_info); + if (Array.isArray(diskInfoArr) && diskInfoArr.length > 0) { + const rootDisk = diskInfoArr.find((d: any) => d.mount === '/') || diskInfoArr[0]; + rawInode.push(rootDisk?.inode_usage_pct || 0); + } else { + rawInode.push(0); + } + const net = parseJSON(r.net_info); rawTcp.push(net.tcp_established || 0); rawTw.push(net.tcp_time_wait || 0); + rawCw.push(net.tcp_close_wait || 0); rawConnTotal.push((net.tcp_established || 0) + (net.tcp_time_wait || 0) + (net.tcp_close_wait || 0)); rawRx.push(net.net_rx_bps || 0); rawTx.push(net.net_tx_bps || 0); @@ -217,6 +228,7 @@ export function HostMonitor() { disk: bucketWithStats(rawDisk, timestamps, timeConfig.buckets), tcpEst: bucketWithStats(rawTcp, timestamps, timeConfig.buckets), tcpTW: bucketWithStats(rawTw, timestamps, timeConfig.buckets), + tcpCW: bucketWithStats(rawCw, timestamps, timeConfig.buckets), rx: bucketWithStats(rawRx.map(v => v / 1024), timestamps, timeConfig.buckets), tx: bucketWithStats(rawTx.map(v => v / 1024), timestamps, timeConfig.buckets), ioRead: bucketWithStats(rawIoR.map(v => v / 1024), timestamps, timeConfig.buckets), @@ -230,6 +242,7 @@ export function HostMonitor() { connTotal: bucketWithStats(rawConnTotal, timestamps, timeConfig.buckets), rxErr: bucketWithStats(rawRxErr, timestamps, timeConfig.buckets), txErr: bucketWithStats(rawTxErr, timestamps, timeConfig.buckets), + inode: bucketWithStats(rawInode, timestamps, timeConfig.buckets), }; }, [history, timeConfig]); @@ -281,6 +294,7 @@ export function HostMonitor() { {serverInfo && ( + {selectedHost} {serverInfo.os || ''} | {serverInfo.cpu_cores || '?'} 核 | {serverInfo.mem_total ? `${serverInfo.mem_total.toFixed(0)}GB` : '?'} )} @@ -344,6 +358,12 @@ export function HostMonitor() {
} data={metrics.disk} color="#f97316" unit="%" domain={[0, 100]} aggType={aggType} /> + { + const dArr = Array.isArray(parseJSON(latest?.disk_info)) ? parseJSON(latest?.disk_info) : []; + const root = dArr.find((d: any) => d.mount === '/') || dArr[0]; + return root?.inode_usage_pct != null ? `${root.inode_usage_pct.toFixed(1)}%` : '--'; + })()} icon={} + data={metrics.inode} color="#8b5cf6" unit="%" domain={[0, 100]} aggType={aggType} /> } data={metrics.ioRead} color="#3b82f6" unit="KB/s" domain={[0, 'auto']} aggType={aggType} /> } @@ -353,14 +373,16 @@ export function HostMonitor() {
- {/* 网络监控 — 双线网卡带宽 + TIME_WAIT + TCP连接 */} + {/* 网络监控 — TCP连接 + 带宽 + 错误 */} -
- } +
+ } data={metrics.tcpEst} color="#8b5cf6" unit="个" domain={[0, 'auto']} aggType={aggType} /> } data={metrics.tcpTW} color="#f59e0b" unit="个" domain={[0, 'auto']} aggType={aggType} /> - } + } + data={metrics.tcpCW} color="#ef4444" unit="个" domain={[0, 'auto']} aggType={aggType} /> + } data={metrics.connTotal} color="#22d3ee" unit="个" domain={[0, 'auto']} aggType={aggType} />
{/* 网卡带宽吞吐 — 双线 rx/tx */} @@ -442,8 +464,9 @@ export function HostMonitor() { - {/* ═══ 进程监控 — 简洁 5 列 ═══ */} + {/* ═══ 进程监控 — 进程 TOP + FD 趋势 + 上下文切换 ═══ */} + {/* ── 进程 TOP 表格 ── */}

@@ -524,6 +547,107 @@ export function HostMonitor() { )} + + {/* ── FD 文件句柄 + 上下文切换 可视化 ── */} +
+ {/* FD 卡片 + 趋势图 */} + + +

+ FD 文件句柄 +

+ {fdInfo?.allocated != null ? ( + <> +
+
+ 已分配 + {fdInfo.allocated.toLocaleString()} +
+
+ 上限 + {fdInfo.max === -1 ? '无限制' : fdInfo.max?.toLocaleString() || '—'} +
+
+ 使用率 + 80 ? 'text-rose-400' : 'text-violet-400'}`}> + {fdInfo.usage_pct != null ? `${fdInfo.usage_pct.toFixed(1)}%` : '—'} + +
+
+ {metrics.fd.length > 0 && ( +
+ + + + + + + + + + + + + + + +
+ )} + + ) : ( +
等待数据…
+ )} +
+
+ + {/* 上下文切换卡片 */} + + +

+ 上下文切换 +

+ {csInfo?.ctx_per_sec != null ? ( +
+
+
+
CTX 切换/秒
+
+ {(csInfo.ctx_per_sec / 1e6).toFixed(1)}M +
+
{csInfo.ctx_per_sec?.toLocaleString()} 次
+
+
+
IRQ 中断/秒
+
+ {(csInfo.irq_per_sec / 1e6).toFixed(1)}M +
+
{csInfo.irq_per_sec?.toLocaleString()} 次
+
+
+ {/* 比例条 */} +
+
+ CTX + IRQ +
+
+ {csInfo.ctx_per_sec + csInfo.irq_per_sec > 0 && ( + <> +
+
+ + )} +
+
+
+ ) : ( +
等待数据…
+ )} + + +
{/* 系统信息 */} @@ -571,39 +695,6 @@ export function HostMonitor() { )} - {/* FD 文件句柄 + 上下文切换 */} - {(fdInfo || csInfo) && ( - - -
- -

FD 文件句柄 & 上下文切换

-
-
- {fdInfo?.allocated != null && [ - ['FD 已分配', {fdInfo.allocated.toLocaleString()}], - ['FD 上限', {fdInfo.max === -1 ? '无限制' : fdInfo.max?.toLocaleString() || '—'}], - ['FD 使用率', 80 ? 'text-rose-400' : 'text-[var(--dg-text-primary)]'}`}>{fdInfo.usage_pct != null ? `${fdInfo.usage_pct.toFixed(1)}%` : '—'}], - ].map(([label, val]) => ( -
- {label}: - {val} -
- ))} - {csInfo?.ctx_per_sec != null && [ - ['CTX 累计', {(csInfo.ctx_per_sec / 1e6).toFixed(1)}M], - ['IRQ 累计', {(csInfo.irq_per_sec / 1e6).toFixed(1)}M], - ].map(([label, val]) => ( -
- {label}: - {val} -
- ))} -
-
-
- )} - {/* 磁盘分区 + 网络接口 (保持原样) */} {latest?.disk_info && (() => { const disk = parseJSON(latest.disk_info); @@ -838,20 +929,25 @@ export function HostMonitor() {

- {/* 端口白名单 */} + {/* 端口白名单 — 仅黑名单模式可编辑 */}