feat: 全站 PageShell 设计系统与专业 UI 统一

- 新增 PageShell/PageTabs/ContentPanel/PageLoading/PageEmpty 页面骨架
- index.css 设计令牌文档化,布局令牌与纯色主按钮(去除渐变粉彩)
- 全站路由页接入 PageShell,云账单三 Tab 为标杆
- 主题体系:跟随系统 + 主流/开发者分组,新增 Dracula/Tokyo Night,One Dark 暖灰差异化
- 登录页/侧栏/顶栏去渐变光球,企业克制风
- 修复 AIAlertAnalyzer JSX 与构建依赖导入问题
This commit is contained in:
Logan committed 2026-07-01 12:45:38 +08:00
1 parent 1acb0c49af
commit e363ded445
213 files changed
+7148 -6818

No files matched your search

+133 -107
View File
@@ -8,11 +8,13 @@ import { useHostList, useHostHistory, usePortPolicy } from '@/hooks/api/useHostM
import { queryKeys } from '@/hooks/api/queryKeys';
import { Card, CardContent } from '@/components/ui/card';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { PageShell } from '@/components/layout/PageShell';
import { PageLoading } from '@/components/layout/PageLoading';
import {
Cpu, HardDrive, Network, Activity, Server, Loader2,
Cpu, HardDrive, Network, Activity, Server,
Info, Monitor, Disc, Clock, List, HeartPulse, RefreshCw, XCircle,
} from 'lucide-react';
import {
@@ -27,7 +29,7 @@ import { PortPolicyDialog } from './host-monitor/PortPolicyDialog';
import { SystemInfoSection } from './host-monitor/SystemInfoSection';
import type { JsonData } from '@/types/common';
export function HostMonitor() {
export function HostMonitor({ embedded = false }: { embedded?: boolean }) {
const qc = useQueryClient();
const [searchParams] = useSearchParams();
const urlHost = searchParams.get('host') || '';
@@ -95,111 +97,108 @@ export function HostMonitor() {
const currentVal = (key: string, fallback = '--') =>
getCurrentVal(latest, netInfo, diskIOInfo, swapInfo, fdInfo, key, fallback);
if (loading && !history.length) {
return <div className="flex items-center justify-center h-64"><Loader2 className="h-8 w-8 animate-spin text-slate-500" /></div>;
}
return (
<div className="space-y-3">
<div className="flex items-center justify-between flex-wrap gap-2">
<div className="flex items-center gap-2">
<Server className="h-4 w-4 text-blue-400" />
<Select value={selectedHost} onValueChange={setSelectedHost}>
<SelectTrigger className="w-52 h-8 text-xs bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<SelectValue placeholder="选择主机..." />
</SelectTrigger>
<SelectContent className="bg-slate-800 border-slate-700 text-slate-200">
{hosts.map((h: JsonData) => (
<SelectItem key={h.hostname} value={h.hostname}>{h.hostname}</SelectItem>
))}
</SelectContent>
</Select>
{serverInfo && (
<span className="text-xs text-[var(--dg-text-muted)] ml-2">
{String(serverInfo.os || '')}{serverInfo.cpu_cores ? ` | ${serverInfo.cpu_cores as number | string} 核` : ''}{serverInfo.mem_total ? ` | ${(serverInfo.mem_total as number).toFixed(0)}GB` : ''}
</span>
)}
</div>
<div className="flex items-center gap-2">
<Select value={aggType} onValueChange={(v) => setAggType(v as AggType)}>
<SelectTrigger className="w-24 h-7 text-xs bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-slate-800 border-slate-700 text-slate-200">
<SelectItem value="avg">平均值</SelectItem>
<SelectItem value="max">最大值</SelectItem>
<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" onClick={() => setRangeHours(tr.hours)}>{tr.label}</Button>
const toolbar = (
<div className="flex items-center justify-between flex-wrap gap-2 w-full">
<div className="flex items-center gap-2">
<Select value={selectedHost} onValueChange={setSelectedHost}>
<SelectTrigger className="w-52 h-8 text-xs dg-input">
<SelectValue placeholder="选择主机..." />
</SelectTrigger>
<SelectContent className="dg-input">
{hosts.map((h: JsonData) => (
<SelectItem key={h.hostname} value={h.hostname}>{h.hostname}</SelectItem>
))}
</div>
</SelectContent>
</Select>
{serverInfo && (
<span className="text-xs dg-text-muted">
{String(serverInfo.os || '')}{serverInfo.cpu_cores ? ` | ${serverInfo.cpu_cores as number | string} 核` : ''}{serverInfo.mem_total ? ` | ${(serverInfo.mem_total as number).toFixed(0)}GB` : ''}
</span>
)}
</div>
<div className="flex items-center gap-2">
<Select value={aggType} onValueChange={(v) => setAggType(v as AggType)}>
<SelectTrigger className="w-24 h-7 text-xs dg-input">
<SelectValue />
</SelectTrigger>
<SelectContent className="dg-input">
<SelectItem value="avg">平均值</SelectItem>
<SelectItem value="max">最大值</SelectItem>
<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>
</div>
</div>
);
<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" /> },
].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>
))}
</TabsList>
const monitorBody = (
<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" /> },
].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>
))}
</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 text-blue-400" />}
<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 text-orange-400" />}
<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 text-emerald-400" />}
<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 text-emerald-400" />}
<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" />}
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 text-red-400" />}
<MetricChartCard title="Swap 使用率(%)" value={currentVal('swap')} icon={<HardDrive className="h-4 w-4 dg-text-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 text-purple-400" />}
<MetricChartCard title="系统负载(1m)" value={currentVal('load1')} icon={<Activity className="h-4 w-4 dg-text-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 text-orange-400" />}
<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="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 text-purple-400" />}
})()} 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 text-blue-400" />}
<MetricChartCard title="磁盘读取(KB/s)" value={currentVal('ioR')} icon={<Disc className="h-4 w-4 dg-text-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 text-emerald-400" />}
<MetricChartCard title="磁盘写入(KB/s)" value={currentVal('ioW')} icon={<Disc className="h-4 w-4 dg-text-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 text-red-400" />}
<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} />
</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 text-purple-400" />}
<MetricChartCard title="TCP EST" value={currentVal('tcp')} icon={<Network className="h-4 w-4 dg-text-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 text-amber-400" />}
<MetricChartCard title="TCP TIME_WAIT" value={currentVal('tw')} icon={<Clock className="h-4 w-4 dg-text-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 text-red-400" />}
<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" />}
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" />}
<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" />}
data={metrics.connTotal} color="#22d3ee" unit="个" domain={[0, 'auto']} aggType={aggType} />
</div>
<DualLineChart
@@ -210,9 +209,9 @@ export function HostMonitor() {
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 text-red-400" />}
<MetricChartCard title="网卡接收错误" value={netInfo?.net_rx_errors != null ? String(netInfo.net_rx_errors) : '--'} icon={<XCircle className="h-4 w-4 dg-text-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 text-orange-400" />}
<MetricChartCard title="网卡发送错误" value={netInfo?.net_tx_errors != null ? String(netInfo.net_tx_errors) : '--'} icon={<XCircle className="h-4 w-4 dg-text-orange" />}
data={metrics.txErr} color="#f97316" unit="个" domain={[0, 'auto']} aggType={aggType} />
</div>
</TabsContent>
@@ -221,15 +220,15 @@ export function HostMonitor() {
<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">
<Server className="h-4 w-4 text-emerald-400" />监听端口 {portInfo.length}
<Server className="h-4 w-4 dg-text-green" />监听端口 {portInfo.length}
<span className="ml-auto flex items-center gap-2">
{portPolicyMode === 'blacklist' && portWhitelist.length > 0 && (
<Badge variant="outline" className="text-[10px] text-amber-400 border-amber-500/30">
<Badge variant="outline" className="text-[10px] dg-text-yellow border-amber-500/30">
黑名单: {portWhitelist.join(',')}
</Badge>
)}
{portPolicyMode === 'whitelist' && (
<Badge variant="outline" className="text-[10px] text-blue-400 border-blue-500/30">
<Badge variant="outline" className="text-[10px] dg-text-blue border-blue-500/30">
白名单模式
</Badge>
)}
@@ -256,7 +255,7 @@ export function HostMonitor() {
</thead>
<tbody>
{portInfo.map((p: JsonData, i: number) => (
<tr key={i} className="border-b border-[var(--dg-border)]/30 hover:bg-slate-800/30 transition-colors">
<tr key={i} className="border-b border-[var(--dg-border)]/30 hover:bg-[var(--dg-bg-hover)] transition-colors">
<td className="py-1.5 font-mono text-[var(--dg-text-primary)]">{p.port}</td>
<td className="py-1.5">
<Badge variant="outline" className="text-[10px] font-mono">
@@ -265,7 +264,7 @@ export function HostMonitor() {
</td>
<td className="py-1.5 font-mono text-[var(--dg-text-muted)]">{p.process || '—'}</td>
<td className="py-1.5 text-right">
<Badge variant="default" className="text-[10px] bg-emerald-500/20 text-emerald-400 border-emerald-500/30">
<Badge variant="default" className="text-[10px] bg-emerald-500/20 dg-text-green border-emerald-500/30">
LISTEN
</Badge>
</td>
@@ -283,7 +282,7 @@ export function HostMonitor() {
<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">
<List className="h-4 w-4 text-cyan-400" />进程 TOP {processTop.length}
<List className="h-4 w-4 dg-text-cyan" />进程 TOP {processTop.length}
</h3>
{processTop.length === 0 ? (
<div className="text-center py-6 text-xs text-[var(--dg-text-muted)] border border-dashed border-[var(--dg-border)] rounded-lg">
@@ -307,14 +306,14 @@ export function HostMonitor() {
const cpup = p.cpu_pct != null ? Number(p.cpu_pct) : null;
const memp = p.mem_pct != null ? Number(p.mem_pct) : null;
return (
<tr key={i} className="border-b border-[var(--dg-border)]/30 hover:bg-slate-800/30 transition-colors">
<tr key={i} className="border-b border-[var(--dg-border)]/30 hover:bg-[var(--dg-bg-hover)] transition-colors">
<td className="py-1.5 pr-2">
<div className="flex items-center gap-1 group/cmd">
<span className="font-mono text-[var(--dg-text-primary)] truncate max-w-[320px] inline-block align-middle" title={cmd}>
{cmd}
</span>
<button
className="opacity-0 group-hover/cmd:opacity-100 transition-opacity p-0.5 hover:text-cyan-400 shrink-0"
className="opacity-0 group-hover/cmd:opacity-100 transition-opacity p-0.5 hover:dg-text-cyan shrink-0"
title="复制命令行"
onClick={async (e) => {
e.stopPropagation();
@@ -337,7 +336,7 @@ export function HostMonitor() {
</td>
<td className="py-1.5 text-right font-mono text-[var(--dg-text-muted)]">{p.pid}</td>
<td className="py-1.5 text-right font-mono">
<span className={memp != null && memp > 50 ? 'text-rose-400' : 'text-cyan-400'}>
<span className={memp != null && memp > 50 ? 'text-rose-400' : 'dg-text-cyan'}>
{memp != null ? memp.toFixed(2) : '--'}
</span>
</td>
@@ -361,7 +360,7 @@ export function HostMonitor() {
<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 文件句柄
<HardDrive className="h-4 w-4 dg-text-purple" />FD 文件句柄
</h3>
{fdInfo?.allocated != null ? (
<>
@@ -376,7 +375,7 @@ export function HostMonitor() {
</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'}`}>
<span className={`font-mono font-semibold ${fdInfo.usage_pct > 80 ? 'text-rose-400' : 'dg-text-purple'}`}>
{fdInfo.usage_pct != null ? `${fdInfo.usage_pct.toFixed(1)}%` : '—'}
</span>
</div>
@@ -410,21 +409,21 @@ export function HostMonitor() {
<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" />上下文切换
<RefreshCw className="h-4 w-4 dg-text-green" />上下文切换
</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">
<div className="text-lg font-bold font-mono dg-text-green">
{(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">
<div className="text-lg font-bold font-mono dg-text-yellow">
{(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>
@@ -432,8 +431,8 @@ export function HostMonitor() {
</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>
<span className="dg-text-green">CTX</span>
<span className="dg-text-yellow">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 && (
@@ -463,19 +462,46 @@ export function HostMonitor() {
portInfoCount={portInfo.length}
/>
<HealthProbeSection selectedHost={selectedHost} />
</Tabs>
<HealthProbeSection selectedHost={selectedHost} />
</Tabs>
);
<PortPolicyDialog
open={portPolicyDialogOpen}
onOpenChange={setPortPolicyDialogOpen}
portPolicyMode={portPolicyMode}
onModeChange={setPortPolicyMode}
portWhitelist={portWhitelist}
onWhitelistChange={setPortWhitelist}
onSave={handleSavePortPolicy}
saving={portPolicySaving}
/>
</div>
const dialogs = (
<PortPolicyDialog
open={portPolicyDialogOpen}
onOpenChange={setPortPolicyDialogOpen}
portPolicyMode={portPolicyMode}
onModeChange={setPortPolicyMode}
portWhitelist={portWhitelist}
onWhitelistChange={setPortWhitelist}
onSave={handleSavePortPolicy}
saving={portPolicySaving}
/>
);
if (embedded) {
return (
<div className="space-y-3">
{toolbar}
{loading && !history.length ? <PageLoading /> : monitorBody}
{dialogs}
</div>
);
}
return (
<PageShell
variant="workspace"
title="主机监控"
subtitle={selectedHost ? `当前主机: ${selectedHost}` : '选择主机查看监控指标'}
icon={<Server className="h-5 w-5" />}
toolbar={toolbar}
loading={loading && !history.length}
loadingLabel="加载监控数据..."
contentClassName="space-y-3"
>
{monitorBody}
{dialogs}
</PageShell>
);
}