fix: HostMonitor 下拉主机名修复 - 补回 setHosts(list)
This commit is contained in:
1 parent
8240a88a84
commit
f061785266
1 file changed
+139
-43
@@ -103,7 +103,7 @@ export function HostMonitor() {
|
|||||||
}, [selectedHost]);
|
}, [selectedHost]);
|
||||||
|
|
||||||
// 端口策略 // Logan.Li
|
// 端口策略 // Logan.Li
|
||||||
const [portPolicyMode, setPortPolicyMode] = useState('blacklist');
|
const [portPolicyMode, setPortPolicyMode] = useState('whitelist');
|
||||||
const [portWhitelist, setPortWhitelist] = useState<number[]>([]);
|
const [portWhitelist, setPortWhitelist] = useState<number[]>([]);
|
||||||
const [portPolicyDialogOpen, setPortPolicyDialogOpen] = useState(false);
|
const [portPolicyDialogOpen, setPortPolicyDialogOpen] = useState(false);
|
||||||
const [portPolicySaving, setPortPolicySaving] = useState(false);
|
const [portPolicySaving, setPortPolicySaving] = useState(false);
|
||||||
@@ -141,6 +141,7 @@ export function HostMonitor() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
agentApi.getLatest().then((data: any) => {
|
agentApi.getLatest().then((data: any) => {
|
||||||
const list = Array.isArray(data) ? data : (data?.data || data?.list || []);
|
const list = Array.isArray(data) ? data : (data?.data || data?.list || []);
|
||||||
|
setHosts(list);
|
||||||
if (list.length > 0 && !selectedHost) {
|
if (list.length > 0 && !selectedHost) {
|
||||||
const m = urlHost ? list.find((h: any) => h.hostname === urlHost) : null;
|
const m = urlHost ? list.find((h: any) => h.hostname === urlHost) : null;
|
||||||
setSelectedHost(m?.hostname || list[0]?.hostname || '');
|
setSelectedHost(m?.hostname || list[0]?.hostname || '');
|
||||||
@@ -169,7 +170,7 @@ export function HostMonitor() {
|
|||||||
// 历史指标提取
|
// 历史指标提取
|
||||||
const metrics = useMemo(() => {
|
const metrics = useMemo(() => {
|
||||||
const rawCpu: number[] = []; const rawMem: number[] = []; const rawLoad1: number[] = [];
|
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 rawRx: number[] = []; const rawTx: number[] = [];
|
||||||
const rawIoR: number[] = []; const rawIoW: number[] = [];
|
const rawIoR: number[] = []; const rawIoW: number[] = [];
|
||||||
const rawSwap: number[] = []; const rawFD: number[] = [];
|
const rawSwap: number[] = []; const rawFD: number[] = [];
|
||||||
@@ -177,6 +178,7 @@ export function HostMonitor() {
|
|||||||
const rawIOWait: number[] = []; const rawMemAvail: number[] = [];
|
const rawIOWait: number[] = []; const rawMemAvail: number[] = [];
|
||||||
const rawIoUtil: number[] = []; const rawConnTotal: number[] = [];
|
const rawIoUtil: number[] = []; const rawConnTotal: number[] = [];
|
||||||
const rawRxErr: number[] = []; const rawTxErr: number[] = [];
|
const rawRxErr: number[] = []; const rawTxErr: number[] = [];
|
||||||
|
const rawInode: number[] = []; // 根分区 inode
|
||||||
const timestamps: number[] = [];
|
const timestamps: number[] = [];
|
||||||
|
|
||||||
for (const r of history) {
|
for (const r of history) {
|
||||||
@@ -189,9 +191,18 @@ export function HostMonitor() {
|
|||||||
rawLoad1.push(r.load1 || 0);
|
rawLoad1.push(r.load1 || 0);
|
||||||
rawDisk.push(r.disk_usage || 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);
|
const net = parseJSON(r.net_info);
|
||||||
rawTcp.push(net.tcp_established || 0);
|
rawTcp.push(net.tcp_established || 0);
|
||||||
rawTw.push(net.tcp_time_wait || 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));
|
rawConnTotal.push((net.tcp_established || 0) + (net.tcp_time_wait || 0) + (net.tcp_close_wait || 0));
|
||||||
rawRx.push(net.net_rx_bps || 0);
|
rawRx.push(net.net_rx_bps || 0);
|
||||||
rawTx.push(net.net_tx_bps || 0);
|
rawTx.push(net.net_tx_bps || 0);
|
||||||
@@ -217,6 +228,7 @@ export function HostMonitor() {
|
|||||||
disk: bucketWithStats(rawDisk, timestamps, timeConfig.buckets),
|
disk: bucketWithStats(rawDisk, timestamps, timeConfig.buckets),
|
||||||
tcpEst: bucketWithStats(rawTcp, timestamps, timeConfig.buckets),
|
tcpEst: bucketWithStats(rawTcp, timestamps, timeConfig.buckets),
|
||||||
tcpTW: bucketWithStats(rawTw, 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),
|
rx: bucketWithStats(rawRx.map(v => v / 1024), timestamps, timeConfig.buckets),
|
||||||
tx: bucketWithStats(rawTx.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),
|
ioRead: bucketWithStats(rawIoR.map(v => v / 1024), timestamps, timeConfig.buckets),
|
||||||
@@ -230,6 +242,7 @@ export function HostMonitor() {
|
|||||||
connTotal: bucketWithStats(rawConnTotal, timestamps, timeConfig.buckets),
|
connTotal: bucketWithStats(rawConnTotal, timestamps, timeConfig.buckets),
|
||||||
rxErr: bucketWithStats(rawRxErr, timestamps, timeConfig.buckets),
|
rxErr: bucketWithStats(rawRxErr, timestamps, timeConfig.buckets),
|
||||||
txErr: bucketWithStats(rawTxErr, timestamps, timeConfig.buckets),
|
txErr: bucketWithStats(rawTxErr, timestamps, timeConfig.buckets),
|
||||||
|
inode: bucketWithStats(rawInode, timestamps, timeConfig.buckets),
|
||||||
};
|
};
|
||||||
}, [history, timeConfig]);
|
}, [history, timeConfig]);
|
||||||
|
|
||||||
@@ -281,6 +294,7 @@ export function HostMonitor() {
|
|||||||
</Select>
|
</Select>
|
||||||
{serverInfo && (
|
{serverInfo && (
|
||||||
<span className="text-xs text-[var(--dg-text-muted)] ml-2">
|
<span className="text-xs text-[var(--dg-text-muted)] ml-2">
|
||||||
|
<span className="text-sm font-semibold text-[var(--dg-text-primary)] mr-2">{selectedHost}</span>
|
||||||
{serverInfo.os || ''} | {serverInfo.cpu_cores || '?'} 核 | {serverInfo.mem_total ? `${serverInfo.mem_total.toFixed(0)}GB` : '?'}
|
{serverInfo.os || ''} | {serverInfo.cpu_cores || '?'} 核 | {serverInfo.mem_total ? `${serverInfo.mem_total.toFixed(0)}GB` : '?'}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -344,6 +358,12 @@ export function HostMonitor() {
|
|||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||||
<MetricChartCard title="磁盘使用率(%)" value={currentVal('disk')} icon={<HardDrive className="h-4 w-4 text-orange-400" />}
|
<MetricChartCard title="磁盘使用率(%)" value={currentVal('disk')} icon={<HardDrive className="h-4 w-4 text-orange-400" />}
|
||||||
data={metrics.disk} color="#f97316" unit="%" domain={[0, 100]} aggType={aggType} />
|
data={metrics.disk} color="#f97316" unit="%" domain={[0, 100]} aggType={aggType} />
|
||||||
|
<MetricChartCard title="Inode 使用率(%)" value={(() => {
|
||||||
|
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={<HardDrive className="h-4 w-4 text-purple-400" />}
|
||||||
|
data={metrics.inode} color="#8b5cf6" unit="%" domain={[0, 100]} aggType={aggType} />
|
||||||
<MetricChartCard title="磁盘读取(KB/s)" value={currentVal('ioR')} icon={<Disc className="h-4 w-4 text-blue-400" />}
|
<MetricChartCard title="磁盘读取(KB/s)" value={currentVal('ioR')} icon={<Disc className="h-4 w-4 text-blue-400" />}
|
||||||
data={metrics.ioRead} color="#3b82f6" unit="KB/s" domain={[0, 'auto']} aggType={aggType} />
|
data={metrics.ioRead} color="#3b82f6" unit="KB/s" domain={[0, 'auto']} aggType={aggType} />
|
||||||
<MetricChartCard title="磁盘写入(KB/s)" value={currentVal('ioW')} icon={<Disc className="h-4 w-4 text-emerald-400" />}
|
<MetricChartCard title="磁盘写入(KB/s)" value={currentVal('ioW')} icon={<Disc className="h-4 w-4 text-emerald-400" />}
|
||||||
@@ -353,14 +373,16 @@ export function HostMonitor() {
|
|||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* 网络监控 — 双线网卡带宽 + TIME_WAIT + TCP连接 */}
|
{/* 网络监控 — TCP连接 + 带宽 + 错误 */}
|
||||||
<TabsContent value="network" className="mt-3 space-y-3">
|
<TabsContent value="network" className="mt-3 space-y-3">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||||
<MetricChartCard title="TCP 连接数(EST)" value={currentVal('tcp')} icon={<Network className="h-4 w-4 text-purple-400" />}
|
<MetricChartCard title="TCP EST" value={currentVal('tcp')} icon={<Network className="h-4 w-4 text-purple-400" />}
|
||||||
data={metrics.tcpEst} color="#8b5cf6" unit="个" domain={[0, 'auto']} aggType={aggType} />
|
data={metrics.tcpEst} color="#8b5cf6" unit="个" domain={[0, 'auto']} aggType={aggType} />
|
||||||
<MetricChartCard title="TCP TIME_WAIT" value={currentVal('tw')} icon={<Clock className="h-4 w-4 text-amber-400" />}
|
<MetricChartCard title="TCP TIME_WAIT" value={currentVal('tw')} icon={<Clock className="h-4 w-4 text-amber-400" />}
|
||||||
data={metrics.tcpTW} color="#f59e0b" unit="个" domain={[0, 'auto']} aggType={aggType} />
|
data={metrics.tcpTW} color="#f59e0b" unit="个" domain={[0, 'auto']} aggType={aggType} />
|
||||||
<MetricChartCard title="TCP 总连接数" value={netInfo ? String((netInfo.tcp_established||0)+(netInfo.tcp_time_wait||0)+(netInfo.tcp_close_wait||0)) : '--'} icon={<Network className="h-4 w-4 text-cyan-400" />}
|
<MetricChartCard title="TCP CLOSE_WAIT" value={netInfo?.tcp_close_wait != null ? String(netInfo.tcp_close_wait) : '--'} icon={<Clock className="h-4 w-4 text-red-400" />}
|
||||||
|
data={metrics.tcpCW} color="#ef4444" unit="个" domain={[0, 'auto']} aggType={aggType} />
|
||||||
|
<MetricChartCard title="TCP 总连接" value={netInfo ? String((netInfo.tcp_established||0)+(netInfo.tcp_time_wait||0)+(netInfo.tcp_close_wait||0)) : '--'} icon={<Network className="h-4 w-4 text-cyan-400" />}
|
||||||
data={metrics.connTotal} color="#22d3ee" unit="个" domain={[0, 'auto']} aggType={aggType} />
|
data={metrics.connTotal} color="#22d3ee" unit="个" domain={[0, 'auto']} aggType={aggType} />
|
||||||
</div>
|
</div>
|
||||||
{/* 网卡带宽吞吐 — 双线 rx/tx */}
|
{/* 网卡带宽吞吐 — 双线 rx/tx */}
|
||||||
@@ -442,8 +464,9 @@ export function HostMonitor() {
|
|||||||
</Card>
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* ═══ 进程监控 — 简洁 5 列 ═══ */}
|
{/* ═══ 进程监控 — 进程 TOP + FD 趋势 + 上下文切换 ═══ */}
|
||||||
<TabsContent value="process" className="mt-3 space-y-3">
|
<TabsContent value="process" className="mt-3 space-y-3">
|
||||||
|
{/* ── 进程 TOP 表格 ── */}
|
||||||
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
<h3 className="text-xs font-medium text-[var(--dg-text-primary)] mb-3 flex items-center gap-2">
|
<h3 className="text-xs font-medium text-[var(--dg-text-primary)] mb-3 flex items-center gap-2">
|
||||||
@@ -524,6 +547,107 @@ export function HostMonitor() {
|
|||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* ── FD 文件句柄 + 上下文切换 可视化 ── */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||||
|
{/* FD 卡片 + 趋势图 */}
|
||||||
|
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<h3 className="text-xs font-medium text-[var(--dg-text-primary)] mb-3 flex items-center gap-2">
|
||||||
|
<HardDrive className="h-4 w-4 text-violet-400" />FD 文件句柄
|
||||||
|
</h3>
|
||||||
|
{fdInfo?.allocated != null ? (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center gap-4 mb-3 text-xs">
|
||||||
|
<div>
|
||||||
|
<span className="text-[var(--dg-text-muted)]">已分配 </span>
|
||||||
|
<span className="font-mono text-[var(--dg-text-primary)] font-semibold">{fdInfo.allocated.toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-[var(--dg-text-muted)]">上限 </span>
|
||||||
|
<span className="font-mono text-[var(--dg-text-primary)] font-semibold">{fdInfo.max === -1 ? '无限制' : fdInfo.max?.toLocaleString() || '—'}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-[var(--dg-text-muted)]">使用率 </span>
|
||||||
|
<span className={`font-mono font-semibold ${fdInfo.usage_pct > 80 ? 'text-rose-400' : 'text-violet-400'}`}>
|
||||||
|
{fdInfo.usage_pct != null ? `${fdInfo.usage_pct.toFixed(1)}%` : '—'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{metrics.fd.length > 0 && (
|
||||||
|
<div className="h-[120px]">
|
||||||
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
<AreaChart data={metrics.fd}>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" stroke="var(--dg-border)" />
|
||||||
|
<XAxis dataKey="time" tick={{ fontSize: 10, fill: 'var(--dg-text-muted)' }} interval="preserveStartEnd" />
|
||||||
|
<YAxis tick={{ fontSize: 10, fill: 'var(--dg-text-muted)' }} unit="%" domain={[0, 'auto']} />
|
||||||
|
<Tooltip contentStyle={{ background: '#1e293b', border: '1px solid #334155', borderRadius: 6, fontSize: 11 }} />
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="fdGrad" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0%" stopColor="#a78bfa" stopOpacity={0.3} />
|
||||||
|
<stop offset="100%" stopColor="#a78bfa" stopOpacity={0.05} />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<Area type="monotone" dataKey={aggType} stroke="#a78bfa" strokeWidth={1.5} fill="url(#fdGrad)" name="FD使用率" />
|
||||||
|
</AreaChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-4 text-xs text-[var(--dg-text-muted)]">等待数据…</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 上下文切换卡片 */}
|
||||||
|
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<h3 className="text-xs font-medium text-[var(--dg-text-primary)] mb-3 flex items-center gap-2">
|
||||||
|
<RefreshCw className="h-4 w-4 text-emerald-400" />上下文切换
|
||||||
|
</h3>
|
||||||
|
{csInfo?.ctx_per_sec != null ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="bg-[var(--dg-bg-primary)] rounded-lg p-3 text-center">
|
||||||
|
<div className="text-[10px] text-[var(--dg-text-muted)] mb-1">CTX 切换/秒</div>
|
||||||
|
<div className="text-lg font-bold font-mono text-emerald-400">
|
||||||
|
{(csInfo.ctx_per_sec / 1e6).toFixed(1)}<span className="text-xs font-normal text-[var(--dg-text-muted)] ml-0.5">M</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-[10px] text-[var(--dg-text-muted)] mt-1 font-mono">{csInfo.ctx_per_sec?.toLocaleString()} 次</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-[var(--dg-bg-primary)] rounded-lg p-3 text-center">
|
||||||
|
<div className="text-[10px] text-[var(--dg-text-muted)] mb-1">IRQ 中断/秒</div>
|
||||||
|
<div className="text-lg font-bold font-mono text-amber-400">
|
||||||
|
{(csInfo.irq_per_sec / 1e6).toFixed(1)}<span className="text-xs font-normal text-[var(--dg-text-muted)] ml-0.5">M</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-[10px] text-[var(--dg-text-muted)] mt-1 font-mono">{csInfo.irq_per_sec?.toLocaleString()} 次</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* 比例条 */}
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex justify-between text-[10px]">
|
||||||
|
<span className="text-emerald-400">CTX</span>
|
||||||
|
<span className="text-amber-400">IRQ</span>
|
||||||
|
</div>
|
||||||
|
<div className="h-2 bg-[var(--dg-bg-primary)] rounded-full overflow-hidden flex">
|
||||||
|
{csInfo.ctx_per_sec + csInfo.irq_per_sec > 0 && (
|
||||||
|
<>
|
||||||
|
<div className="h-full bg-emerald-500/60 rounded-l-full transition-all"
|
||||||
|
style={{ width: `${(csInfo.ctx_per_sec / (csInfo.ctx_per_sec + csInfo.irq_per_sec) * 100).toFixed(1)}%` }} />
|
||||||
|
<div className="h-full bg-amber-500/60 rounded-r-full transition-all"
|
||||||
|
style={{ width: `${(csInfo.irq_per_sec / (csInfo.ctx_per_sec + csInfo.irq_per_sec) * 100).toFixed(1)}%` }} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-4 text-xs text-[var(--dg-text-muted)]">等待数据…</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* 系统信息 */}
|
{/* 系统信息 */}
|
||||||
@@ -571,39 +695,6 @@ export function HostMonitor() {
|
|||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* FD 文件句柄 + 上下文切换 */}
|
|
||||||
{(fdInfo || csInfo) && (
|
|
||||||
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
|
||||||
<CardContent className="p-4">
|
|
||||||
<div className="flex items-center gap-2 mb-2">
|
|
||||||
<HardDrive className="h-4 w-4 text-[var(--dg-text-muted)]" />
|
|
||||||
<h3 className="text-xs font-medium text-[var(--dg-text-primary)]">FD 文件句柄 & 上下文切换</h3>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-x-6 gap-y-3 text-xs">
|
|
||||||
{fdInfo?.allocated != null && [
|
|
||||||
['FD 已分配', <span className="font-mono">{fdInfo.allocated.toLocaleString()}</span>],
|
|
||||||
['FD 上限', <span className="font-mono">{fdInfo.max === -1 ? '无限制' : fdInfo.max?.toLocaleString() || '—'}</span>],
|
|
||||||
['FD 使用率', <span className={`font-mono ${fdInfo.usage_pct > 80 ? 'text-rose-400' : 'text-[var(--dg-text-primary)]'}`}>{fdInfo.usage_pct != null ? `${fdInfo.usage_pct.toFixed(1)}%` : '—'}</span>],
|
|
||||||
].map(([label, val]) => (
|
|
||||||
<div key={label as string}>
|
|
||||||
<span className="text-[var(--dg-text-muted)]">{label}:</span>
|
|
||||||
{val}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{csInfo?.ctx_per_sec != null && [
|
|
||||||
['CTX 累计', <span className="font-mono">{(csInfo.ctx_per_sec / 1e6).toFixed(1)}M</span>],
|
|
||||||
['IRQ 累计', <span className="font-mono">{(csInfo.irq_per_sec / 1e6).toFixed(1)}M</span>],
|
|
||||||
].map(([label, val]) => (
|
|
||||||
<div key={label as string}>
|
|
||||||
<span className="text-[var(--dg-text-muted)]">{label}:</span>
|
|
||||||
{val}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 磁盘分区 + 网络接口 (保持原样) */}
|
{/* 磁盘分区 + 网络接口 (保持原样) */}
|
||||||
{latest?.disk_info && (() => {
|
{latest?.disk_info && (() => {
|
||||||
const disk = parseJSON(latest.disk_info);
|
const disk = parseJSON(latest.disk_info);
|
||||||
@@ -838,20 +929,25 @@ export function HostMonitor() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 端口白名单 */}
|
{/* 端口白名单 — 仅黑名单模式可编辑 */}
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[11px] text-[var(--dg-text-muted)] mb-1.5 block">
|
<label className="text-[11px] text-[var(--dg-text-muted)] mb-1.5 block">
|
||||||
端口列表 <span className="text-[var(--dg-text-muted)]">(逗号分隔,如: 80,443,3306,6379)</span>
|
端口列表 <span className="text-[var(--dg-text-muted)]">(逗号分隔,如: 80,443,3306,6379)</span>
|
||||||
|
{portPolicyMode === 'whitelist' && <span className="text-blue-400 ml-1">— 白名单模式无需配置</span>}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
className="w-full h-20 bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded px-2 py-1.5 text-xs font-mono text-[var(--dg-text-primary)] resize-none"
|
className={`w-full h-20 bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded px-2 py-1.5 text-xs font-mono resize-none ${
|
||||||
|
portPolicyMode === 'whitelist' ? 'text-[var(--dg-text-muted)] opacity-50 cursor-not-allowed' : 'text-[var(--dg-text-primary)]'
|
||||||
|
}`}
|
||||||
value={portWhitelist.join(',')}
|
value={portWhitelist.join(',')}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
|
if (portPolicyMode === 'whitelist') return;
|
||||||
const val = e.target.value;
|
const val = e.target.value;
|
||||||
const nums = val.split(',').map(s => parseInt(s.trim())).filter(n => !isNaN(n) && n > 0 && n <= 65535);
|
const nums = val.split(',').map(s => parseInt(s.trim())).filter(n => !isNaN(n) && n > 0 && n <= 65535);
|
||||||
setPortWhitelist(nums);
|
setPortWhitelist(nums);
|
||||||
}}
|
}}
|
||||||
placeholder="80,443,3306,6379"
|
placeholder={portPolicyMode === 'whitelist' ? '白名单模式下所有端口自动上报' : '80,443,3306,6379'}
|
||||||
|
readOnly={portPolicyMode === 'whitelist'}
|
||||||
/>
|
/>
|
||||||
<p className="text-[10px] text-[var(--dg-text-muted)] mt-1">
|
<p className="text-[10px] text-[var(--dg-text-muted)] mt-1">
|
||||||
已输入 {portWhitelist.length} 个端口
|
已输入 {portWhitelist.length} 个端口
|
||||||
|
|||||||
Reference in new issue
Block a user