fix(i18n): 回退到稳定的 32% 覆盖率状态 (tsc + Vite 构建通过)

批量转换脚本引入的语法错误已回退,保留稳定的 i18n 转换:
- 翻译词典: 22 namespaces, 3600+ keys
- 已接入: 145+ 文件 (Dashboard/Login/K8s 主页面/Monitor/System/Deploy)
- 语言切换器在 Header 中可用 (🇨🇳/🇺🇸)
- tsc 零错误, Vite 构建成功
This commit is contained in:
attacker committed 2026-07-03 02:02:29 +08:00
1 parent ecec73989b
commit 72bfb0e12d
191 files changed
+5057 -7592

No files matched your search

@@ -5,7 +5,6 @@ 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;
@@ -71,7 +70,6 @@ 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 }[]>([]);
@@ -84,7 +82,7 @@ export function GrafanaPanelContent({ panel, datasourceUrl }: { panel: Panel; da
try {
const url = await getDefaultPromURL(datasourceUrl);
if (!url) {
setError(t('monitor.dashboard.prometheus.emptyTitle'));
setError('未配置 Prometheus 数据源');
setLoading(false);
return;
}
@@ -102,16 +100,16 @@ export function GrafanaPanelContent({ panel, datasourceUrl }: { panel: Panel; da
});
const parsed = parseRangeResult(res);
setChartData(parsed.length > 0 ? parsed : []);
if (parsed.length === 0) setError(t('kubernetes.configmap.noData'));
if (parsed.length === 0) setError('无数据');
} else {
// stat / gauge / table 用即时查询
const res = await prometheusProxyApi.query(url, panel.query);
const parsed = parseInstantResult(res);
setInstantData(parsed);
if (parsed.length === 0) setError(t('kubernetes.configmap.noData'));
if (parsed.length === 0) setError('无数据');
}
} catch (e: any) {
setError(e?.message ? String(e.message).slice(0, 80) : t('monitor.grafanapanelcontent.s0d66ed'));
setError(e?.message ? String(e.message).slice(0, 80) : '查询失败');
} finally {
setLoading(false);
}
@@ -155,7 +153,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>{t('common.realtime')}</span>
<span>实时</span>
</div>
</div>
);
@@ -189,7 +187,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)]">{t('kubernetes.configmap.noData')}</div>;
if (instantData.length === 0) return <div className="text-center py-4 text-xs text-[var(--dg-text-muted)]">无数据</div>;
return (
<div className="overflow-auto max-h-[200px]">
<table className="w-full text-[11px]">
@@ -218,7 +216,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)]">{t('kubernetes.configmap.noData')}</div>;
return <div className="text-center py-4 text-xs text-[var(--dg-text-muted)]">无数据</div>;
}
return (
<div className="h-full min-h-[120px]">
@@ -2,6 +2,7 @@
// 参照: Grafana node-dashboard.json (表格+时序) / kubernetes-dashboard.json (bargauge+阈值)
import { useState, useEffect, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import {
Server,
@@ -30,7 +31,6 @@ 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';
@@ -49,7 +49,7 @@ function getUsageBar(val: number) {
}
export default function HostMonitorDashboard() {
const { t } = useTranslation();
const { t } = useTranslation(['monitor', 'common']);
const navigate = useNavigate();
const [hosts, setHosts] = useState<JsonData[]>([]);
const [total, setTotal] = useState(0);
@@ -143,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]">{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>;
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>;
};
@@ -158,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)]">{t('monitor.hostmonitordashboard.sd94cd2')}</div>
<div className="text-[10px] text-[var(--dg-text-muted)]">{t('monitor:hostDashboard.hostTotal')}</div>
</div>
</CardContent>
</Card>
@@ -167,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)]">{t('monitor.hostmonitordashboard.s42860c')}</div>
<div className="text-[10px] text-[var(--dg-text-muted)]">{t('monitor:hostDashboard.agentOnline')}</div>
</div>
</CardContent>
</Card>
@@ -176,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)]">{t('common.offline')}</div>
<div className="text-[10px] text-[var(--dg-text-muted)]">{t('common:offline')}</div>
</div>
</CardContent>
</Card>
@@ -192,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)]">{t('monitor.hostmonitordashboard.s16dd85')}</div>
<div className="text-[10px] text-[var(--dg-text-muted)]">{t('monitor:hostDashboard.avgCpu')}</div>
</div>
</CardContent>
</Card>
@@ -201,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)]">{t('monitor.hostmonitordashboard.sa09099')}</div>
<div className="text-[10px] text-[var(--dg-text-muted)]">{t('monitor:hostDashboard.avgMemory')}</div>
</div>
</CardContent>
</Card>
@@ -217,8 +217,8 @@ export default function HostMonitorDashboard() {
<CardHeader className="pb-2">
<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">{t('monitor.hostmonitordashboard.sec309b')}</Badge>
{t('monitor:hostDashboard.cpuTop5')}
<Badge variant="outline" className="text-[10px] bg-red-500/10 dg-text-red border-red-500/30">{t('monitor:hostDashboard.threshold')}</Badge>
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
@@ -258,8 +258,8 @@ export default function HostMonitorDashboard() {
<CardHeader className="pb-2">
<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">{t('monitor.hostmonitordashboard.sec309b')}</Badge>
{t('monitor:hostDashboard.memTop5')}
<Badge variant="outline" className="text-[10px] bg-amber-500/10 dg-text-yellow border-amber-500/30">{t('monitor:hostDashboard.threshold')}</Badge>
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
@@ -302,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)]">{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>
<CardTitle className="text-base text-[var(--dg-text-primary)]">{t('monitor:hostDashboard.listTitle')}</CardTitle>
<Badge variant="outline" className="text-xs bg-emerald-500/10 dg-text-green border-emerald-500/30">{t('monitor:hostDashboard.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=t('monitor.hostmonitordashboard.s3d0fc0')
placeholder={t('monitor:hostDashboard.searchPlaceholder')}
value={search}
onChange={(e) => setSearch(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
@@ -318,7 +318,7 @@ export default function HostMonitorDashboard() {
</div>
<Button variant="outline" size="sm" onClick={() => loadData(true)} disabled={refreshing}>
<RefreshCw className={`h-4 w-4 mr-1 ${refreshing ? 'animate-spin' : ''}`} />
刷新
{t('common:refresh')}
</Button>
</div>
</div>
@@ -327,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">{t('common.loading')}</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">{t('monitor.hostmonitordashboard.s2678f4')}</p>
<p className="text-xs mt-1">{t('monitor.hostmonitordashboard.s350255')}</p>
<p className="text-sm">{t('monitor:hostDashboard.noData')}</p>
<p className="text-xs mt-1">{t('monitor:hostDashboard.noDataDesc')}</p>
</div>
) : (
<>
@@ -342,14 +342,14 @@ export default function HostMonitorDashboard() {
<thead>
<tr className="border-b border-[var(--dg-border)]">
{[
{ 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: 'hostname', label: t('monitor:hostDashboard.colHostId'), w: '' },
{ key: 'agent_version', label: t('monitor:hostDashboard.colAgentVersion'), w: 'w-[100px]' },
{ key: 'agent_status', label: t('monitor:hostDashboard.colAgentStatus'), w: 'w-[90px]' },
{ key: 'cpu_usage', label: 'CPU', w: 'w-[130px]' },
{ 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]' },
{ key: 'memory_usage', label: t('monitor:hostMetric.memory'), w: 'w-[130px]' },
{ key: 'disk_usage', label: t('monitor:hostMetric.disk'), w: 'w-[130px]' },
{ key: 'region', label: t('monitor:hostDashboard.colRegion'), w: 'w-[120px]' },
{ key: 'ip', label: t('monitor:hostDashboard.colIp'), w: 'w-[140px]' },
].map(col => (
<th
key={col.key}
@@ -362,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)]">{t('common.actions')}</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>
@@ -425,20 +425,20 @@ 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=t('monitor.hostmonitordashboard.s2ee528')
title={t('monitor:hostDashboard.viewMonitorChart')}
>
<Monitor className="h-3 w-3 mr-1" />
监控
{t('monitor:hostDashboard.monitor')}
</Button>
<Button
variant="ghost"
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=t('monitor.hostmonitordashboard.s356aa8')
title={t('monitor:hostDashboard.viewAlertRules')}
>
<Bell className="h-3 w-3 mr-1" />
告警
{t('monitor:hostDashboard.alert')}
</Button>
</div>
</td>
@@ -451,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>{t('common.perPage')}</span>
<span>{t('monitor:hostDashboard.perPage')}</span>
<Select value={String(pageSize)} onValueChange={(v) => { setPageSize(Number(v)); setPage(1); }}>
<SelectTrigger className="h-7 w-[60px] text-xs">
<SelectValue />
@@ -462,7 +462,7 @@ export default function HostMonitorDashboard() {
<SelectItem value="30">30</SelectItem>
</SelectContent>
</Select>
<span>条,共 {total} 条</span>
<span>{t('monitor:hostDashboard.totalCount', { count: total })}</span>
</div>
<div className="flex items-center gap-1">
<Button
@@ -8,12 +8,10 @@ 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]">
@@ -48,7 +46,6 @@ 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">
@@ -61,7 +58,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" />{i18n.t('monitor.prometheusdashboard.se7fb2b')}</div>
<div className="text-xs font-medium mb-2 flex items-center gap-1"><Cpu className="w-3 h-3 dg-text-purple" />节点 CPU / 内存利用率</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>;
@@ -73,7 +70,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">{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>
<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>
<tbody>
{(d.nodes || []).map((n: JsonData, i: number) => {
const inst = n.instance || n.exported_instance || '';
@@ -107,12 +104,11 @@ 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">{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 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>
);
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 (
@@ -127,7 +123,6 @@ 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);
@@ -148,7 +143,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||t('aiops.llmTestFailed')); }
} catch (e) { setConnected(false); setError((e as JsonData).message||'连接失败'); }
finally { setLoading(false); }
};
@@ -174,13 +169,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">{t('monitor.prometheusdashboard.s006a5f')}</CardTitle>
<CardTitle className="text-sm font-medium">Prometheus 监控</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" />{t('common.refresh')}</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" />刷新</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)]">{t('monitor.prometheusdashboard.s5e8eb2')}</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)]">连接中…</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,7 +18,6 @@ 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;
@@ -32,7 +31,6 @@ 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[]>([]);
@@ -52,7 +50,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 || t('aiops.llmTestFailed')); }
} catch (e) { setConnected(false); setError((e as JsonData).message || '连接失败'); }
finally { setLoading(false); }
};
@@ -96,24 +94,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">{t('monitor.zabbixdashboard.s6f2695')}</CardTitle>
<CardTitle className="text-sm font-medium">Zabbix 主机监控</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" />{t('common.refresh')}</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" />刷新</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)]">{t('monitor.zabbixdashboard.s5e8eb2')}</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)]">连接中…</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">{t('monitor.zabbixdashboard.sdb131a')}</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">未找到被监控的 Zabbix 主机</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: 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') },
{ 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: '运行' },
].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>