feat: 监控告警体系统一重构 + 自动化任务调度引擎 + 监控大屏拆分
告警中心 - 新增统一告警(alert_unified)、监控告警规则(monitor_alert_rule)、指标目录(alert_metric_catalog) - 通知策略与消息分发(alert_notify_policy/dispatch/message)、静默规则 - 告警引擎按阈值/SNMP 拆分(alert_engine_monitor/snmp) 监控采集 - SNMP 常量/模板与历史指标(snmp templates/constants、snmp_metrics_history、snmp_collect) - 中间件监控 Redis 采集与种子(middleware_monitor_redis/seed) - 站点探测 pkg(siteprobe) + 站点视图(site_view) 自动化任务 - cron 调度器(cronscheduler)、脚本运行器(scriptrunner)、系统任务(systask)及运行记录(cron_job_run、system_task) 审计与凭证 - 审计设置(audit_setting) + 历史清理(auditcleaner) 前端 - 监控页面(host/middleware/site/snmp)拆分为子模块并大屏化 - 告警中心多 Tab(规则/指标目录/通知策略/静默)、任务管理组件重构、Agent CLI Markdown 渲染 - 移除旧脚本卡片/详情/日志组件,统一为 ScriptTable/ScriptCodeEditor/TerminalOutput Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
092d0e4b9e
commit
471353e3d7
190 files changed
+16640
-3484
No files matched your search
@@ -1,6 +1,6 @@
|
||||
// 主机监控详情 — 阿里云云监控风格 + v2.0 新增指标
|
||||
// 进程TOP / Swap / FD / 上下文切换 // Logan.Li
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useState, useEffect, useMemo, useRef } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { agentApi } from '@/api';
|
||||
@@ -17,16 +17,19 @@ import {
|
||||
Cpu, HardDrive, Network, Activity, Server,
|
||||
Info, Monitor, Disc, Clock, List, HeartPulse, RefreshCw, XCircle,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
|
||||
} from 'recharts';
|
||||
import { TIME_RANGES, type AggType, type HostRecord } from './host-monitor/types';
|
||||
import { parseJSON, formatBPS, buildMetricsFromHistory, getCurrentVal } from './host-monitor/utils';
|
||||
import { ZoomableAreaChart, type ZoomableChartHandle } from './host-monitor/ZoomableAreaChart';
|
||||
import { ChartToolbar } from './host-monitor/ChartToolbar';
|
||||
import { MetricIconBox } from './host-monitor/MetricIconBox';
|
||||
import { DEFAULT_TIME_RANGE, type AggType, type HostRecord, type HostMonitorTimeRange } from './host-monitor/types';
|
||||
import { HostTimeRangePicker } from './host-monitor/HostTimeRangePicker';
|
||||
import { getRangeSpanMinutes } from './host-monitor/timeRange';
|
||||
import { parseJSON, formatBPS, buildMetricsFromHistory, getCurrentVal, filterHistoryByRange, getActualDataSpan } from './host-monitor/utils';
|
||||
import { MetricChartCard } from './host-monitor/MetricChartCard';
|
||||
import { DualLineChart } from './host-monitor/DualLineChart';
|
||||
import { HealthProbeSection } from './host-monitor/HealthProbeSection';
|
||||
import { PortPolicyDialog } from './host-monitor/PortPolicyDialog';
|
||||
import { SystemInfoSection } from './host-monitor/SystemInfoSection';
|
||||
import { HostCoreMetricsStrip } from './host-monitor/HostCoreMetricsStrip';
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
export function HostMonitor({ embedded = false }: { embedded?: boolean }) {
|
||||
@@ -35,16 +38,18 @@ export function HostMonitor({ embedded = false }: { embedded?: boolean }) {
|
||||
const urlHost = searchParams.get('host') || '';
|
||||
const [selectedHost, setSelectedHost] = useState(urlHost);
|
||||
const [activeTab, setActiveTab] = useState('basic');
|
||||
const [rangeHours, setRangeHours] = useState(1);
|
||||
const [timeRange, setTimeRange] = useState<HostMonitorTimeRange>(DEFAULT_TIME_RANGE);
|
||||
const [aggType, setAggType] = useState<AggType>('avg');
|
||||
|
||||
const [portPolicyMode, setPortPolicyMode] = useState('whitelist');
|
||||
const [portWhitelist, setPortWhitelist] = useState<number[]>([]);
|
||||
const [portPolicyDialogOpen, setPortPolicyDialogOpen] = useState(false);
|
||||
const [portPolicySaving, setPortPolicySaving] = useState(false);
|
||||
const fdChartRef = useRef<ZoomableChartHandle>(null);
|
||||
const [fdChartZoomed, setFdChartZoomed] = useState(false);
|
||||
|
||||
const { data: hosts = [], isLoading: hostsLoading } = useHostList();
|
||||
const { data: history = [], isLoading: historyLoading } = useHostHistory(selectedHost, rangeHours);
|
||||
const { data: rawHistory = [], isLoading: historyLoading } = useHostHistory(selectedHost, timeRange);
|
||||
const { data: portPolicy, refetch: refetchPortPolicy } = usePortPolicy(selectedHost);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -74,7 +79,19 @@ export function HostMonitor({ embedded = false }: { embedded?: boolean }) {
|
||||
} catch { } finally { setPortPolicySaving(false); }
|
||||
};
|
||||
|
||||
const timeConfig = TIME_RANGES.find(tr => tr.hours === rangeHours) || TIME_RANGES[0];
|
||||
const history = useMemo(
|
||||
() => filterHistoryByRange(rawHistory, timeRange),
|
||||
[rawHistory, timeRange],
|
||||
);
|
||||
const spanMinutes = getRangeSpanMinutes(timeRange);
|
||||
const timeConfig = useMemo(
|
||||
() => ({ buckets: timeRange.buckets, spanMinutes }),
|
||||
[timeRange.buckets, spanMinutes],
|
||||
);
|
||||
const actualDataSpan = useMemo(
|
||||
() => getActualDataSpan(history, timeRange),
|
||||
[history, timeRange],
|
||||
);
|
||||
const loading = hostsLoading || historyLoading;
|
||||
const serverInfo = (hosts.find((h) => h.hostname === selectedHost) || null) as HostRecord | null;
|
||||
const latest = useMemo((): HostRecord => {
|
||||
@@ -97,6 +114,11 @@ export function HostMonitor({ embedded = false }: { embedded?: boolean }) {
|
||||
const currentVal = (key: string, fallback = '--') =>
|
||||
getCurrentVal(latest, netInfo, diskIOInfo, swapInfo, fdInfo, key, fallback);
|
||||
|
||||
const pctRefLines = [
|
||||
{ value: 80, label: '80%', color: 'var(--dg-yellow)' },
|
||||
{ value: 90, label: '90%', color: 'var(--dg-red)' },
|
||||
];
|
||||
|
||||
const toolbar = (
|
||||
<div className="flex items-center justify-between flex-wrap gap-2 w-full">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -127,12 +149,11 @@ export function HostMonitor({ embedded = false }: { embedded?: boolean }) {
|
||||
<SelectItem value="min">最小值</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="flex items-center gap-1">
|
||||
{TIME_RANGES.map(tr => (
|
||||
<Button key={tr.hours} variant={rangeHours === tr.hours ? 'default' : 'outline'} size="sm"
|
||||
className={`h-7 text-xs px-2.5 ${rangeHours === tr.hours ? 'dg-btn-primary' : 'dg-btn-secondary'}`}
|
||||
onClick={() => setRangeHours(tr.hours)}>{tr.label}</Button>
|
||||
))}
|
||||
<div className="flex flex-col items-end gap-0.5">
|
||||
<HostTimeRangePicker value={timeRange} onChange={setTimeRange} />
|
||||
{actualDataSpan && (
|
||||
<span className="text-[10px] dg-text-muted tabular-nums">数据 {actualDataSpan}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -142,63 +163,64 @@ export function HostMonitor({ embedded = false }: { embedded?: boolean }) {
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList className="h-8 bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] gap-0 overflow-x-auto">
|
||||
{[
|
||||
{ id: 'basic', label: '基础监控', icon: <Monitor className="w-3 h-3" /> },
|
||||
{ id: 'disk', label: '磁盘监控', icon: <HardDrive className="w-3 h-3" /> },
|
||||
{ id: 'network', label: '网络监控', icon: <Network className="w-3 h-3" /> },
|
||||
{ id: 'ports', label: '端口监控', icon: <Server className="w-3 h-3" /> },
|
||||
{ id: 'process', label: '进程监控', icon: <List className="w-3 h-3" /> },
|
||||
{ id: 'system', label: '系统信息', icon: <Info className="w-3 h-3" /> },
|
||||
{ id: 'health_probes', label: '健康探针', icon: <HeartPulse className="w-3 h-3" /> },
|
||||
{ id: 'basic', label: '基础监控', icon: <Monitor className="w-3.5 h-3.5" /> },
|
||||
{ id: 'disk', label: '磁盘监控', icon: <HardDrive className="w-3.5 h-3.5" /> },
|
||||
{ id: 'network', label: '网络监控', icon: <Network className="w-3.5 h-3.5" /> },
|
||||
{ id: 'ports', label: '端口监控', icon: <Server className="w-3.5 h-3.5" /> },
|
||||
{ id: 'process', label: '进程监控', icon: <List className="w-3.5 h-3.5" /> },
|
||||
{ id: 'system', label: '系统信息', icon: <Info className="w-3.5 h-3.5" /> },
|
||||
{ id: 'health_probes', label: '健康探针', icon: <HeartPulse className="w-3.5 h-3.5" /> },
|
||||
].map(tab => (
|
||||
<TabsTrigger key={tab.id} value={tab.id} className="text-[11px] h-7 gap-1 data-[state=active]:bg-[var(--dg-bg-primary)]">
|
||||
{tab.icon}{tab.label}
|
||||
<TabsTrigger key={tab.id} value={tab.id} className="text-[11px] h-7 gap-1.5 px-3 data-[state=active]:bg-[var(--dg-bg-primary)]">
|
||||
<span className="shrink-0 opacity-80">{tab.icon}</span>
|
||||
{tab.label}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="basic" className="mt-3 grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
<MetricChartCard title="CPU 使用率(%)" value={currentVal('cpu')} icon={<Cpu className="h-4 w-4 dg-text-blue" />}
|
||||
data={metrics.cpu} color="#3b82f6" unit="%" domain={[0, 100]} aggType={aggType} />
|
||||
<MetricChartCard title="CPU IO等待(%)" value={latest?.cpu_iowait != null ? `${(latest.cpu_iowait as number).toFixed(1)}%` : '--'} icon={<Clock className="h-4 w-4 dg-text-orange" />}
|
||||
data={metrics.iowait} color="#f97316" unit="%" domain={[0, 100]} aggType={aggType} />
|
||||
<MetricChartCard title="内存使用率(%)" value={currentVal('mem')} icon={<Server className="h-4 w-4 dg-text-green" />}
|
||||
data={metrics.mem} color="#10b981" unit="%" domain={[0, 100]} aggType={aggType} />
|
||||
<MetricChartCard title="可用内存(GB)" value={latest?.mem_available != null ? `${(latest.mem_available as number).toFixed(1)}GB` : '--'} icon={<Server className="h-4 w-4 dg-text-green" />}
|
||||
<MetricChartCard title="CPU 使用率(%)" value={currentVal('cpu')} icon={<Cpu />} iconTone="blue"
|
||||
data={metrics.cpu} color="#3b82f6" unit="%" domain={[0, 100]} aggType={aggType} referenceLines={pctRefLines} />
|
||||
<MetricChartCard title="CPU IO等待(%)" value={latest?.cpu_iowait != null ? `${(latest.cpu_iowait as number).toFixed(1)}%` : '--'} icon={<Clock />} iconTone="orange"
|
||||
data={metrics.iowait} color="#f97316" unit="%" domain={[0, 100]} aggType={aggType} referenceLines={pctRefLines} />
|
||||
<MetricChartCard title="内存使用率(%)" value={currentVal('mem')} icon={<Server />} iconTone="green"
|
||||
data={metrics.mem} color="#10b981" unit="%" domain={[0, 100]} aggType={aggType} referenceLines={pctRefLines} />
|
||||
<MetricChartCard title="可用内存(GB)" value={latest?.mem_available != null ? `${(latest.mem_available as number).toFixed(1)}GB` : '--'} icon={<Server />} iconTone="green"
|
||||
data={metrics.memAvail} color="#34d399" unit="GB" domain={[0, 'auto']} aggType={aggType} />
|
||||
<MetricChartCard title="Swap 使用率(%)" value={currentVal('swap')} icon={<HardDrive className="h-4 w-4 dg-text-red" />}
|
||||
<MetricChartCard title="Swap 使用率(%)" value={currentVal('swap')} icon={<HardDrive />} iconTone="red"
|
||||
data={metrics.swap} color="#ef4444" unit="%" domain={[0, 100]} aggType={aggType} />
|
||||
<MetricChartCard title="系统负载(1m)" value={currentVal('load1')} icon={<Activity className="h-4 w-4 dg-text-purple" />}
|
||||
<MetricChartCard title="系统负载(1m)" value={currentVal('load1')} icon={<Activity />} iconTone="purple"
|
||||
data={metrics.load1} color="#8b5cf6" unit="" domain={[0, 'auto']} aggType={aggType} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="disk" className="mt-3 space-y-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 dg-text-orange" />}
|
||||
data={metrics.disk} color="#f97316" unit="%" domain={[0, 100]} aggType={aggType} />
|
||||
<MetricChartCard title="磁盘使用率(%)" value={currentVal('disk')} icon={<HardDrive />} iconTone="orange"
|
||||
data={metrics.disk} color="#f97316" unit="%" domain={[0, 100]} aggType={aggType} referenceLines={pctRefLines} />
|
||||
<MetricChartCard title="Inode 使用率(%)" value={(() => {
|
||||
const dArr = Array.isArray(parseJSON(latest?.disk_info)) ? parseJSON(latest?.disk_info) : [];
|
||||
const root = dArr.find((d: JsonData) => d.mount === '/') || dArr[0];
|
||||
return root?.inode_usage_pct != null ? `${root.inode_usage_pct.toFixed(1)}%` : '--';
|
||||
})()} icon={<HardDrive className="h-4 w-4 dg-text-purple" />}
|
||||
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 dg-text-blue" />}
|
||||
})()} icon={<HardDrive />} iconTone="purple"
|
||||
data={metrics.inode} color="#8b5cf6" unit="%" domain={[0, 100]} aggType={aggType} referenceLines={pctRefLines} />
|
||||
<MetricChartCard title="磁盘读取(KB/s)" value={currentVal('ioR')} icon={<Disc />} iconTone="blue"
|
||||
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 dg-text-green" />}
|
||||
<MetricChartCard title="磁盘写入(KB/s)" value={currentVal('ioW')} icon={<Disc />} iconTone="green"
|
||||
data={metrics.ioWrite} color="#10b981" unit="KB/s" domain={[0, 'auto']} aggType={aggType} />
|
||||
<MetricChartCard title="磁盘IO繁忙度(%)" value={diskIOInfo?.util != null ? `${diskIOInfo.util.toFixed(1)}%` : '--'} icon={<Disc className="h-4 w-4 dg-text-red" />}
|
||||
data={metrics.ioUtil} color="#ef4444" unit="%" domain={[0, 100]} aggType={aggType} />
|
||||
<MetricChartCard title="磁盘IO繁忙度(%)" value={diskIOInfo?.util != null ? `${diskIOInfo.util.toFixed(1)}%` : '--'} icon={<Disc />} iconTone="red"
|
||||
data={metrics.ioUtil} color="#ef4444" unit="%" domain={[0, 100]} aggType={aggType} referenceLines={pctRefLines} />
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="network" className="mt-3 space-y-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 dg-text-purple" />}
|
||||
<MetricChartCard title="TCP EST" value={currentVal('tcp')} icon={<Network />} iconTone="purple"
|
||||
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 dg-text-yellow" />}
|
||||
<MetricChartCard title="TCP TIME_WAIT" value={currentVal('tw')} icon={<Clock />} iconTone="yellow"
|
||||
data={metrics.tcpTW} color="#f59e0b" unit="个" domain={[0, 'auto']} aggType={aggType} />
|
||||
<MetricChartCard title="TCP CLOSE_WAIT" value={netInfo?.tcp_close_wait != null ? String(netInfo.tcp_close_wait) : '--'} icon={<Clock className="h-4 w-4 dg-text-red" />}
|
||||
<MetricChartCard title="TCP CLOSE_WAIT" value={netInfo?.tcp_close_wait != null ? String(netInfo.tcp_close_wait) : '--'} icon={<Clock />} iconTone="red"
|
||||
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 dg-text-cyan" />}
|
||||
<MetricChartCard title="TCP 总连接" value={netInfo ? String((netInfo.tcp_established||0)+(netInfo.tcp_time_wait||0)+(netInfo.tcp_close_wait||0)) : '--'} icon={<Network />} iconTone="cyan"
|
||||
data={metrics.connTotal} color="#22d3ee" unit="个" domain={[0, 'auto']} aggType={aggType} />
|
||||
</div>
|
||||
<DualLineChart
|
||||
@@ -209,9 +231,9 @@ export function HostMonitor({ embedded = false }: { embedded?: boolean }) {
|
||||
aggType={aggType} unit="KB/s"
|
||||
/>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 mt-3">
|
||||
<MetricChartCard title="网卡接收错误" value={netInfo?.net_rx_errors != null ? String(netInfo.net_rx_errors) : '--'} icon={<XCircle className="h-4 w-4 dg-text-red" />}
|
||||
<MetricChartCard title="网卡接收错误" value={netInfo?.net_rx_errors != null ? String(netInfo.net_rx_errors) : '--'} icon={<XCircle />} iconTone="red"
|
||||
data={metrics.rxErr} color="#ef4444" unit="个" domain={[0, 'auto']} aggType={aggType} />
|
||||
<MetricChartCard title="网卡发送错误" value={netInfo?.net_tx_errors != null ? String(netInfo.net_tx_errors) : '--'} icon={<XCircle className="h-4 w-4 dg-text-orange" />}
|
||||
<MetricChartCard title="网卡发送错误" value={netInfo?.net_tx_errors != null ? String(netInfo.net_tx_errors) : '--'} icon={<XCircle />} iconTone="orange"
|
||||
data={metrics.txErr} color="#f97316" unit="个" domain={[0, 'auto']} aggType={aggType} />
|
||||
</div>
|
||||
</TabsContent>
|
||||
@@ -359,9 +381,19 @@ export function HostMonitor({ embedded = false }: { embedded?: boolean }) {
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<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 dg-text-purple" />FD 文件句柄
|
||||
</h3>
|
||||
<div className="flex items-start justify-between gap-2 mb-3">
|
||||
<h3 className="text-xs font-medium text-[var(--dg-text-primary)] flex items-center gap-2 leading-8">
|
||||
<MetricIconBox icon={<HardDrive />} tone="purple" />
|
||||
FD 文件句柄
|
||||
</h3>
|
||||
{metrics.fd.length > 0 && (
|
||||
<ChartToolbar
|
||||
zoomed={fdChartZoomed}
|
||||
onExpand={() => fdChartRef.current?.expand()}
|
||||
onResetZoom={() => fdChartRef.current?.resetZoom()}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{fdInfo?.allocated != null ? (
|
||||
<>
|
||||
<div className="flex items-center gap-4 mb-3 text-xs">
|
||||
@@ -381,23 +413,20 @@ export function HostMonitor({ embedded = false }: { embedded?: boolean }) {
|
||||
</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>
|
||||
<ZoomableAreaChart
|
||||
ref={fdChartRef}
|
||||
data={metrics.fd.map((d) => ({ ...d, value: d[aggType] }))}
|
||||
series={[{ dataKey: 'value', color: '#a78bfa', label: 'FD 使用率' }]}
|
||||
yDomain={[0, 'auto']}
|
||||
unit="%"
|
||||
chartId="fd-usage"
|
||||
title="FD 文件句柄使用率"
|
||||
subtitle={fdInfo?.usage_pct != null ? `当前 ${fdInfo.usage_pct.toFixed(1)}%` : undefined}
|
||||
height={160}
|
||||
toolbarPlacement="header"
|
||||
onZoomedChange={setFdChartZoomed}
|
||||
showHint={false}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
@@ -483,6 +512,14 @@ export function HostMonitor({ embedded = false }: { embedded?: boolean }) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{toolbar}
|
||||
{!loading && history.length > 0 && (
|
||||
<HostCoreMetricsStrip
|
||||
latest={latest}
|
||||
netInfo={netInfo}
|
||||
diskIOInfo={diskIOInfo}
|
||||
onJump={setActiveTab}
|
||||
/>
|
||||
)}
|
||||
{loading && !history.length ? <PageLoading /> : monitorBody}
|
||||
{dialogs}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user