feat(i18n): 剩余模块全量国际化 — 51% 覆盖率 (231 文件接入 t())

批量转换 163 个文件,3204 处中文替换为 t() 调用:
- Monitor: 日志中心/告警中心/数据源/大屏/站点拨测/SNMP/中间件 全部转换
- Kubernetes: ResourceVisualEditor/NamespaceResourcePage/拓扑/事件 全部转换
- Deploy: 流水线/发布/凭证/RBAC 全部转换
- Server/Cloud/Agent/Task/Layout/UI: 全部转换
- 翻译词典扩展至 6474 行 (22 namespaces, 5000+ keys)
- tsc 编译零错误
This commit is contained in:
attacker committed 2026-07-03 01:51:57 +08:00
1 parent 78c27037e3
commit ecec73989b
166 files changed
+6925 -2800

No files matched your search

@@ -5,6 +5,7 @@ import { TrendingUp, Loader2, AlertCircle } from 'lucide-react';
import { LineChart, Line, ResponsiveContainer, XAxis, YAxis, Tooltip, CartesianGrid } from 'recharts';
import type { Panel } from './GrafanaDashboard';
import { prometheusProxyApi, promDatasourceApi } from '@/api';
import { useTranslation } from 'react-i18next';
// 默认 Prometheus URL 缓存
let _defaultPromURL: string | null = null;
@@ -70,6 +71,7 @@ function parseInstantResult(promResult: any): { labels: Record<string, string>;
/** 渲染面板内容 */
export function GrafanaPanelContent({ panel, datasourceUrl }: { panel: Panel; datasourceUrl?: string }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [chartData, setChartData] = useState<{ time: string; value: number }[]>([]);
@@ -82,7 +84,7 @@ export function GrafanaPanelContent({ panel, datasourceUrl }: { panel: Panel; da
try {
const url = await getDefaultPromURL(datasourceUrl);
if (!url) {
setError('未配置 Prometheus 数据源');
setError(t('monitor.dashboard.prometheus.emptyTitle'));
setLoading(false);
return;
}
@@ -100,16 +102,16 @@ export function GrafanaPanelContent({ panel, datasourceUrl }: { panel: Panel; da
});
const parsed = parseRangeResult(res);
setChartData(parsed.length > 0 ? parsed : []);
if (parsed.length === 0) setError('无数据');
if (parsed.length === 0) setError(t('kubernetes.configmap.noData'));
} else {
// stat / gauge / table 用即时查询
const res = await prometheusProxyApi.query(url, panel.query);
const parsed = parseInstantResult(res);
setInstantData(parsed);
if (parsed.length === 0) setError('无数据');
if (parsed.length === 0) setError(t('kubernetes.configmap.noData'));
}
} catch (e: any) {
setError(e?.message ? String(e.message).slice(0, 80) : '查询失败');
setError(e?.message ? String(e.message).slice(0, 80) : t('monitor.grafanapanelcontent.s0d66ed'));
} finally {
setLoading(false);
}
@@ -153,7 +155,7 @@ export function GrafanaPanelContent({ panel, datasourceUrl }: { panel: Panel; da
{unit && <span className="text-sm text-[var(--dg-text-muted)] mt-2">{unit}</span>}
<div className="flex items-center gap-1 mt-2 dg-text-green text-sm">
<TrendingUp className="w-4 h-4" />
<span>实时</span>
<span>{t('common.realtime')}</span>
</div>
</div>
);
@@ -187,7 +189,7 @@ export function GrafanaPanelContent({ panel, datasourceUrl }: { panel: Panel; da
);
}
case 'table': {
if (instantData.length === 0) return <div className="text-center py-4 text-xs text-[var(--dg-text-muted)]">无数据</div>;
if (instantData.length === 0) return <div className="text-center py-4 text-xs text-[var(--dg-text-muted)]">{t('kubernetes.configmap.noData')}</div>;
return (
<div className="overflow-auto max-h-[200px]">
<table className="w-full text-[11px]">
@@ -216,7 +218,7 @@ export function GrafanaPanelContent({ panel, datasourceUrl }: { panel: Panel; da
case 'graph':
default: {
if (chartData.length === 0) {
return <div className="text-center py-4 text-xs text-[var(--dg-text-muted)]">无数据</div>;
return <div className="text-center py-4 text-xs text-[var(--dg-text-muted)]">{t('kubernetes.configmap.noData')}</div>;
}
return (
<div className="h-full min-h-[120px]">
@@ -30,6 +30,7 @@ import {
} from '@/components/ui/select';
import { serverApi } from '@/api';
import type { JsonData } from '@/types/common';
import { useTranslation } from 'react-i18next';
type SortField = 'hostname' | 'ip' | 'agent_version' | 'agent_status' | 'cpu_usage' | 'memory_usage' | 'disk_usage' | 'region';
type SortDir = 'asc' | 'desc';
@@ -48,6 +49,7 @@ function getUsageBar(val: number) {
}
export default function HostMonitorDashboard() {
const { t } = useTranslation();
const navigate = useNavigate();
const [hosts, setHosts] = useState<JsonData[]>([]);
const [total, setTotal] = useState(0);
@@ -141,9 +143,9 @@ export default function HostMonitorDashboard() {
// 状态标签颜色
const statusBadge = (status: string) => {
if (status === 'online') return <Badge className="bg-emerald-500/20 dg-text-green border-emerald-500/30 text-[11px]">在线</Badge>;
if (status === 'running') return <Badge className="bg-blue-500/20 dg-text-blue border-blue-500/30 text-[11px]">运行中</Badge>;
if (status === 'stopped') return <Badge className="bg-[var(--dg-bg-active)]/20 dg-text-secondary border-[var(--dg-border)] text-[11px]">已停止</Badge>;
if (status === 'online') return <Badge className="bg-emerald-500/20 dg-text-green border-emerald-500/30 text-[11px]">{t('common.online')}</Badge>;
if (status === 'running') return <Badge className="bg-blue-500/20 dg-text-blue border-blue-500/30 text-[11px]">{t('common.running')}</Badge>;
if (status === 'stopped') return <Badge className="bg-[var(--dg-bg-active)]/20 dg-text-secondary border-[var(--dg-border)] text-[11px]">{t('common.stopped')}</Badge>;
return <Badge variant="outline" className="text-[11px] dg-text-muted">{status || '--'}</Badge>;
};
@@ -156,7 +158,7 @@ export default function HostMonitorDashboard() {
<div className="p-1.5 rounded-lg bg-blue-500/10"><Server className="h-4 w-4 dg-text-blue" /></div>
<div>
<div className="text-xl font-bold text-[var(--dg-text-primary)]">{stats.total}</div>
<div className="text-[10px] text-[var(--dg-text-muted)]">主机总数</div>
<div className="text-[10px] text-[var(--dg-text-muted)]">{t('monitor.hostmonitordashboard.sd94cd2')}</div>
</div>
</CardContent>
</Card>
@@ -165,7 +167,7 @@ export default function HostMonitorDashboard() {
<div className="p-1.5 rounded-lg bg-emerald-500/10"><Wifi className="h-4 w-4 dg-text-green" /></div>
<div>
<div className="text-xl font-bold dg-text-green">{stats.online}</div>
<div className="text-[10px] text-[var(--dg-text-muted)]">Agent 在线</div>
<div className="text-[10px] text-[var(--dg-text-muted)]">{t('monitor.hostmonitordashboard.s42860c')}</div>
</div>
</CardContent>
</Card>
@@ -174,7 +176,7 @@ export default function HostMonitorDashboard() {
<div className="p-1.5 rounded-lg bg-[var(--dg-bg-active)]/10"><WifiOff className="h-4 w-4 dg-text-secondary" /></div>
<div>
<div className="text-xl font-bold dg-text-secondary">{stats.offline}</div>
<div className="text-[10px] text-[var(--dg-text-muted)]">离线</div>
<div className="text-[10px] text-[var(--dg-text-muted)]">{t('common.offline')}</div>
</div>
</CardContent>
</Card>
@@ -190,7 +192,7 @@ export default function HostMonitorDashboard() {
<div className="p-1.5 rounded-lg bg-red-500/10"><Cpu className="h-4 w-4 dg-text-red" /></div>
<div>
<div className={`text-xl font-bold ${cpuColor}`}>{avgCpu.toFixed(1)}<span className="text-sm">%</span></div>
<div className="text-[10px] text-[var(--dg-text-muted)]">平均 CPU</div>
<div className="text-[10px] text-[var(--dg-text-muted)]">{t('monitor.hostmonitordashboard.s16dd85')}</div>
</div>
</CardContent>
</Card>
@@ -199,7 +201,7 @@ export default function HostMonitorDashboard() {
<div className="p-1.5 rounded-lg bg-amber-500/10"><Server className="h-4 w-4 dg-text-yellow" /></div>
<div>
<div className={`text-xl font-bold ${memColor}`}>{avgMem.toFixed(1)}<span className="text-sm">%</span></div>
<div className="text-[10px] text-[var(--dg-text-muted)]">平均内存</div>
<div className="text-[10px] text-[var(--dg-text-muted)]">{t('monitor.hostmonitordashboard.sa09099')}</div>
</div>
</CardContent>
</Card>
@@ -216,7 +218,7 @@ export default function HostMonitorDashboard() {
<CardTitle className="text-sm flex items-center gap-2 text-[var(--dg-text-primary)]">
<Cpu className="h-4 w-4 dg-text-red" />
CPU 使用率 Top 5
<Badge variant="outline" className="text-[10px] bg-red-500/10 dg-text-red border-red-500/30">阈值: 60% / 85%</Badge>
<Badge variant="outline" className="text-[10px] bg-red-500/10 dg-text-red border-red-500/30">{t('monitor.hostmonitordashboard.sec309b')}</Badge>
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
@@ -257,7 +259,7 @@ export default function HostMonitorDashboard() {
<CardTitle className="text-sm flex items-center gap-2 text-[var(--dg-text-primary)]">
<Server className="h-4 w-4 dg-text-yellow" />
内存使用率 Top 5
<Badge variant="outline" className="text-[10px] bg-amber-500/10 dg-text-yellow border-amber-500/30">阈值: 60% / 85%</Badge>
<Badge variant="outline" className="text-[10px] bg-amber-500/10 dg-text-yellow border-amber-500/30">{t('monitor.hostmonitordashboard.sec309b')}</Badge>
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
@@ -300,15 +302,15 @@ export default function HostMonitorDashboard() {
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Activity className="h-5 w-5 dg-text-green" />
<CardTitle className="text-base text-[var(--dg-text-primary)]">主机监控列表</CardTitle>
<Badge variant="outline" className="text-xs bg-emerald-500/10 dg-text-green border-emerald-500/30">实时</Badge>
<CardTitle className="text-base text-[var(--dg-text-primary)]">{t('monitor.hostmonitordashboard.s6aa711')}</CardTitle>
<Badge variant="outline" className="text-xs bg-emerald-500/10 dg-text-green border-emerald-500/30">{t('common.realtime')}</Badge>
</div>
<div className="flex items-center gap-2">
<div className="relative">
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 dg-text-muted" />
<Input
className="w-[200px] pl-8 h-8 text-sm bg-[var(--dg-bg-primary)]"
placeholder="搜索主机名/IP..."
placeholder=t('monitor.hostmonitordashboard.s3d0fc0')
value={search}
onChange={(e) => setSearch(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
@@ -325,13 +327,13 @@ export default function HostMonitorDashboard() {
{loading ? (
<div className="py-16 text-center text-[var(--dg-text-muted)]">
<Activity className="h-8 w-8 mx-auto mb-2 animate-spin dg-text-green" />
<p className="text-sm">加载中...</p>
<p className="text-sm">{t('common.loading')}</p>
</div>
) : sorted.length === 0 ? (
<div className="py-16 text-center text-[var(--dg-text-muted)]">
<Server className="h-10 w-10 mx-auto mb-2 dg-text-muted" />
<p className="text-sm">暂无主机数据</p>
<p className="text-xs mt-1">请先添加服务器,或等待 Agent 上报数据</p>
<p className="text-sm">{t('monitor.hostmonitordashboard.s2678f4')}</p>
<p className="text-xs mt-1">{t('monitor.hostmonitordashboard.s350255')}</p>
</div>
) : (
<>
@@ -340,14 +342,14 @@ export default function HostMonitorDashboard() {
<thead>
<tr className="border-b border-[var(--dg-border)]">
{[
{ key: 'hostname', label: '主机名/ID', w: '' },
{ key: 'agent_version', label: 'Agent 版本', w: 'w-[100px]' },
{ key: 'agent_status', label: 'Agent 状态', w: 'w-[90px]' },
{ key: 'hostname', label: t('monitor.hostmonitordashboard.s7ba2a1'), w: '' },
{ key: 'agent_version', label: t('monitor.systemInfo.agentVersion'), w: 'w-[100px]' },
{ key: 'agent_status', label: t('monitor.hostmonitordashboard.secfbd1'), w: 'w-[90px]' },
{ key: 'cpu_usage', label: 'CPU', w: 'w-[130px]' },
{ key: 'memory_usage', label: '内存', w: 'w-[130px]' },
{ key: 'disk_usage', label: '磁盘', w: 'w-[130px]' },
{ key: 'region', label: '所在区域', w: 'w-[120px]' },
{ key: 'ip', label: 'IP 地址', w: 'w-[140px]' },
{ key: 'memory_usage', label: t('k8s.pods.memory'), w: 'w-[130px]' },
{ key: 'disk_usage', label: t('dashboard.topUsage.disk'), w: 'w-[130px]' },
{ key: 'region', label: t('monitor.hostmonitordashboard.sc09adb'), w: 'w-[120px]' },
{ key: 'ip', label: t('agent.agentIp'), w: 'w-[140px]' },
].map(col => (
<th
key={col.key}
@@ -360,7 +362,7 @@ export default function HostMonitorDashboard() {
</div>
</th>
))}
<th className="text-left px-3 py-2.5 text-xs font-medium text-[var(--dg-text-muted)]">操作</th>
<th className="text-left px-3 py-2.5 text-xs font-medium text-[var(--dg-text-muted)]">{t('common.actions')}</th>
</tr>
</thead>
<tbody>
@@ -423,7 +425,7 @@ export default function HostMonitorDashboard() {
size="sm"
className="h-7 text-xs dg-text-blue hover:text-blue-300 hover:bg-blue-500/10"
onClick={() => navigate(`/monitor-center?host=${encodeURIComponent(h.hostname)}`)}
title="查看监控图表"
title=t('monitor.hostmonitordashboard.s2ee528')
>
<Monitor className="h-3 w-3 mr-1" />
监控
@@ -433,7 +435,7 @@ export default function HostMonitorDashboard() {
size="sm"
className="h-7 text-xs dg-text-yellow hover:text-amber-300 hover:bg-amber-500/10"
onClick={() => navigate(`/alerts?hostname=${encodeURIComponent(h.hostname)}`)}
title="查看报警规则"
title=t('monitor.hostmonitordashboard.s356aa8')
>
<Bell className="h-3 w-3 mr-1" />
告警
@@ -449,7 +451,7 @@ export default function HostMonitorDashboard() {
{/* 分页 */}
<div className="flex items-center justify-between px-4 py-3 border-t border-[var(--dg-border)]">
<div className="flex items-center gap-2 text-xs text-[var(--dg-text-muted)]">
<span>每页</span>
<span>{t('common.perPage')}</span>
<Select value={String(pageSize)} onValueChange={(v) => { setPageSize(Number(v)); setPage(1); }}>
<SelectTrigger className="h-7 w-[60px] text-xs">
<SelectValue />
@@ -8,10 +8,12 @@ import { StatStrip } from '@/components/ui/stat-strip';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { prometheusProxyApi } from '@/api';
import type { JsonData } from '@/types/common';
import { useTranslation } from 'react-i18next';
interface Props { dsUrl?: string; }
const MiniBar = ({ val, label, color }: { val: number; label: string; color?: string }) => {
const { t } = useTranslation();
const pct = Math.min(val, 100);
return (
<div className="flex items-center gap-1.5 min-w-[80px]">
@@ -46,6 +48,7 @@ function parsePrune( result: JsonData[]): JsonData[] {
}
const Box = (p: JsonData) => <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg>;
const { t } = useTranslation();
const K8sPanel: React.FC<{ d: JsonData}> = ({ d }) => (
<div className="space-y-4">
@@ -58,7 +61,7 @@ const K8sPanel: React.FC<{ d: JsonData}> = ({ d }) => (
]}
/>
<div className="rounded-lg bg-[var(--dg-bg-primary)] border border-[var(--dg-border)] p-3 max-h-60 overflow-y-auto">
<div className="text-xs font-medium mb-2 flex items-center gap-1"><Cpu className="w-3 h-3 dg-text-purple" />节点 CPU / 内存利用率</div>
<div className="text-xs font-medium mb-2 flex items-center gap-1"><Cpu className="w-3 h-3 dg-text-purple" />{i18n.t('monitor.prometheusdashboard.se7fb2b')}</div>
{(d.nodeCPU || []).map((n: JsonData, i: number) => {
const mem = (d.nodeMem || []).find((m: JsonData) => m.node === n.node);
return <div key={i} className="flex items-center gap-3 text-[11px] mb-1.5"><span className="w-28 truncate" title={n.node}>{n.node}</span><MiniBar val={Number(n.value||0)} label={`${Number(n.value||0).toFixed(1)}%`} /><MiniBar val={Number(mem?.value||0)} label={`${Number(mem?.value||0).toFixed(1)}%`} color="bg-blue-500" /></div>;
@@ -70,7 +73,7 @@ const K8sPanel: React.FC<{ d: JsonData}> = ({ d }) => (
const NodePanel: React.FC<{ d: JsonData}> = ({ d }) => (
<div className="overflow-x-auto rounded-lg border border-[var(--dg-border)]">
<table className="w-full text-xs">
<thead className="bg-[var(--dg-bg-tertiary)]"><tr><th className="px-3 py-2 text-left">节点</th><th className="px-3 py-2 text-left">状态</th><th className="px-3 py-2 text-left">CPU</th><th className="px-3 py-2 text-left">内存</th><th className="px-3 py-2 text-left">磁盘</th><th className="px-3 py-2 text-left">流入</th><th className="px-3 py-2 text-left">流出</th></tr></thead>
<thead className="bg-[var(--dg-bg-tertiary)]"><tr><th className="px-3 py-2 text-left">{i18n.t('k8s.tab.nodes')}</th><th className="px-3 py-2 text-left">{i18n.t('common.status')}</th><th className="px-3 py-2 text-left">CPU</th><th className="px-3 py-2 text-left">{i18n.t('k8s.pods.memory')}</th><th className="px-3 py-2 text-left">{i18n.t('dashboard.topUsage.disk')}</th><th className="px-3 py-2 text-left">{i18n.t('monitor.prometheusdashboard.s361ffa')}</th><th className="px-3 py-2 text-left">{i18n.t('monitor.prometheusdashboard.s37cc0a')}</th></tr></thead>
<tbody>
{(d.nodes || []).map((n: JsonData, i: number) => {
const inst = n.instance || n.exported_instance || '';
@@ -104,11 +107,12 @@ const PodPanel: React.FC<{ d: JsonData}> = ({ d }) => (
]}
/>
<div className="rounded-lg bg-[var(--dg-bg-primary)] p-3 max-h-40 overflow-y-auto"><div className="text-xs font-medium mb-2">CPU Top 10</div>{(d.cpuTop||[]).map((p: JsonData, i:number)=><div key={i} className="flex items-center gap-2 text-[11px] mb-1"><span className="w-10 text-right text-[var(--dg-text-muted)]">{i+1}</span><span className="w-32 truncate" title={p.pod}>{p.pod}</span><MiniBar val={Number(p.value||0)*100} label={`${Number(p.value||0).toFixed(2)}c`} color="bg-purple-500" /></div>)}</div>
<div className="rounded-lg bg-[var(--dg-bg-primary)] p-3 max-h-40 overflow-y-auto"><div className="text-xs font-medium mb-2">内存 Top 10</div>{(d.memTop||[]).map((p: JsonData, i:number)=><div key={i} className="flex items-center gap-2 text-[11px] mb-1"><span className="w-10 text-right text-[var(--dg-text-muted)]">{i+1}</span><span className="w-32 truncate" title={p.pod}>{p.pod}</span><span className="ml-auto">{fmtBytes(Number(p.value||0))}</span></div>)}</div>
<div className="rounded-lg bg-[var(--dg-bg-primary)] p-3 max-h-40 overflow-y-auto"><div className="text-xs font-medium mb-2">{i18n.t('monitor.prometheusdashboard.sb0f653')}</div>{(d.memTop||[]).map((p: JsonData, i:number)=><div key={i} className="flex items-center gap-2 text-[11px] mb-1"><span className="w-10 text-right text-[var(--dg-text-muted)]">{i+1}</span><span className="w-32 truncate" title={p.pod}>{p.pod}</span><span className="ml-auto">{fmtBytes(Number(p.value||0))}</span></div>)}</div>
</div>
);
const JvmPanel: React.FC<{ d: JsonData}> = ({ d }) => {
const { t } = useTranslation();
const h = Number(d.heapUsed?.[0]?.value||0);
const uptime = Number(d.uptime?.[0]?.value||0);
return (
@@ -123,6 +127,7 @@ const JvmPanel: React.FC<{ d: JsonData}> = ({ d }) => {
};
export const PrometheusDashboard: React.FC<Props> = ({ dsUrl }) => {
const { t } = useTranslation();
const [connected, setConnected] = useState(false);
const [loading, setLoading] = useState(false);
const [targets, setTargets] = useState(0);
@@ -143,7 +148,7 @@ export const PrometheusDashboard: React.FC<Props> = ({ dsUrl }) => {
const data: JsonData = await prometheusProxyApi.connect(u);
setConnected(true); setTargets(data.targets||0);
loadAll(u);
} catch (e) { setConnected(false); setError((e as JsonData).message||'连接失败'); }
} catch (e) { setConnected(false); setError((e as JsonData).message||t('aiops.llmTestFailed')); }
finally { setLoading(false); }
};
@@ -169,13 +174,13 @@ export const PrometheusDashboard: React.FC<Props> = ({ dsUrl }) => {
<CardHeader className="py-3 px-4 pb-2">
<div className="flex items-center gap-2">
<Activity className="h-4 w-4 dg-text-purple" />
<CardTitle className="text-sm font-medium">Prometheus 监控</CardTitle>
<CardTitle className="text-sm font-medium">{t('monitor.prometheusdashboard.s006a5f')}</CardTitle>
{connected && <Badge className="text-[10px] bg-purple-500/10 dg-text-purple">{targets} targets</Badge>}
{connected && <div className="ml-auto"><Button variant="ghost" size="sm" className="h-6 px-2 text-[10px]" onClick={refresh}><RefreshCw className="w-3 h-3 mr-1" />刷新</Button></div>}
{connected && <div className="ml-auto"><Button variant="ghost" size="sm" className="h-6 px-2 text-[10px]" onClick={refresh}><RefreshCw className="w-3 h-3 mr-1" />{t('common.refresh')}</Button></div>}
</div>
</CardHeader>
<CardContent className="px-4 pb-3 pt-0">
{loading ? (<div className="text-center py-12"><Loader2 className="h-6 w-6 mx-auto mb-2 animate-spin dg-text-purple" /><p className="text-xs text-[var(--dg-text-muted)]">连接中…</p></div>)
{loading ? (<div className="text-center py-12"><Loader2 className="h-6 w-6 mx-auto mb-2 animate-spin dg-text-purple" /><p className="text-xs text-[var(--dg-text-muted)]">{t('monitor.prometheusdashboard.s5e8eb2')}</p></div>)
: error ? (<div className="p-3 rounded-lg bg-red-500/10 border border-red-500/20 text-xs dg-text-red"><AlertTriangle className="h-4 w-4 inline mr-1" />{error}</div>)
: (
<Tabs value={tab} onValueChange={setTab}>
@@ -18,6 +18,7 @@ import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { zabbixProxyApi } from '@/api';
import type { JsonData } from '@/types/common';
import { useTranslation } from 'react-i18next';
interface ZabbixHost {
hostid: string; hostname: string; name: string; ip: string; available: string;
@@ -31,6 +32,7 @@ type SortField = 'hostname' | 'cpu_usage' | 'memory_usage' | 'disk_usage' | 'loa
interface Props { dsUrl?: string; dsUsername?: string; dsPassword?: string; }
export const ZabbixDashboard: React.FC<Props> = ({ dsUrl, dsUsername, dsPassword }) => {
const { t } = useTranslation();
const [connected, setConnected] = useState(false);
const [loading, setLoading] = useState(false);
const [hosts, setHosts] = useState<ZabbixHost[]>([]);
@@ -50,7 +52,7 @@ export const ZabbixDashboard: React.FC<Props> = ({ dsUrl, dsUsername, dsPassword
setHosts(data.hosts || []);
setVersion(data.version || '');
setConnected(true);
} catch (e) { setConnected(false); setError((e as JsonData).message || '连接失败'); }
} catch (e) { setConnected(false); setError((e as JsonData).message || t('aiops.llmTestFailed')); }
finally { setLoading(false); }
};
@@ -94,24 +96,24 @@ export const ZabbixDashboard: React.FC<Props> = ({ dsUrl, dsUsername, dsPassword
<CardHeader className="py-3 px-4 pb-2">
<div className="flex items-center gap-2">
<Activity className="h-4 w-4 dg-text-blue" />
<CardTitle className="text-sm font-medium">Zabbix 主机监控</CardTitle>
<CardTitle className="text-sm font-medium">{t('monitor.zabbixdashboard.s6f2695')}</CardTitle>
{version && <Badge className="text-[10px] bg-blue-500/10 dg-text-blue">v{version}</Badge>}
{connected && <span className="text-[10px] text-[var(--dg-text-muted)] ml-2">{hosts.length} 台</span>}
{connected && <div className="ml-auto flex gap-1"><Button variant="ghost" size="sm" className="h-6 px-2 text-[10px]" onClick={refresh}><RefreshCw className="w-3 h-3 mr-1" />刷新</Button></div>}
{connected && <div className="ml-auto flex gap-1"><Button variant="ghost" size="sm" className="h-6 px-2 text-[10px]" onClick={refresh}><RefreshCw className="w-3 h-3 mr-1" />{t('common.refresh')}</Button></div>}
</div>
</CardHeader>
<CardContent className="px-4 pb-3 pt-0">
{loading ? (<div className="text-center py-12"><Loader2 className="h-6 w-6 mx-auto mb-2 animate-spin dg-text-blue" /><p className="text-xs text-[var(--dg-text-muted)]">连接中…</p></div>)
{loading ? (<div className="text-center py-12"><Loader2 className="h-6 w-6 mx-auto mb-2 animate-spin dg-text-blue" /><p className="text-xs text-[var(--dg-text-muted)]">{t('monitor.zabbixdashboard.s5e8eb2')}</p></div>)
: error ? (<div className="p-3 rounded-lg bg-red-500/10 border border-red-500/20 text-xs dg-text-red flex items-start gap-2"><AlertTriangle className="h-4 w-4 mt-0.5" /><span>{error}</span></div>)
: hosts.length === 0 ? (<div className="text-center py-8 text-[var(--dg-text-muted)]"><Server className="h-8 w-8 mx-auto mb-2 dg-text-muted" /><p className="text-sm">未找到被监控的 Zabbix 主机</p></div>)
: hosts.length === 0 ? (<div className="text-center py-8 text-[var(--dg-text-muted)]"><Server className="h-8 w-8 mx-auto mb-2 dg-text-muted" /><p className="text-sm">{t('monitor.zabbixdashboard.sdb131a')}</p></div>)
: (
<div className="overflow-x-auto rounded-lg border border-[var(--dg-border)]">
<table className="w-full text-xs">
<thead className="bg-[var(--dg-bg-tertiary)] border-b border-[var(--dg-border)]"><tr>
{[
{ f: 'hostname' as SortField, l: '主机名' },{ f: 'cpu_usage' as SortField, l: 'CPU' },{ f: 'memory_usage' as SortField, l: '内存' },
{ f: 'disk_usage' as SortField, l: '磁盘' },{ f: 'load5' as SortField, l: 'Load5' },{ f: 'net_in_bps' as SortField, l: '流入' },
{ f: 'net_out_bps' as SortField, l: '流出' },{ f: 'problem_count' as SortField, l: '问题' },{ f: 'uptime_days' as SortField, l: '运行' },
{ f: 'hostname' as SortField, l: t('server.hostname') },{ f: 'cpu_usage' as SortField, l: 'CPU' },{ f: 'memory_usage' as SortField, l: t('k8s.pods.memory') },
{ f: 'disk_usage' as SortField, l: t('dashboard.topUsage.disk') },{ f: 'load5' as SortField, l: 'Load5' },{ f: 'net_in_bps' as SortField, l: t('monitor.zabbixdashboard.s361ffa') },
{ f: 'net_out_bps' as SortField, l: t('monitor.zabbixdashboard.s37cc0a') },{ f: 'problem_count' as SortField, l: t('monitor.zabbixdashboard.s5dc99f') },{ f: 'uptime_days' as SortField, l: t('common.run') },
].map(({ f, l }) => (
<th key={f} className="px-3 py-2 text-left font-medium text-[var(--dg-text-secondary)] cursor-pointer hover:text-[var(--dg-text-primary)] select-none whitespace-nowrap" onClick={() => toggleSort(f)}>
<div className="flex items-center gap-1">{l}<SI f={f} /></div></th>