feat(i18n): CredFormDialog + ClusterComparePage 全量国际化 — 75 新增翻译键

- CredFormDialog.tsx: 凭据表单弹框全量迁移 (deploy.cred.form.* 37 键)
  SSH/Docker/TLS/AKSK/Database/API/Generic 7 种类型表单字段全覆盖
- ClusterComparePage.tsx: 多集群对比看板全量迁移 (k8s.compare.* 35 键)
  图表标签/表头/状态/空状态/标题全部国际化
- 新增 common.username/password/port 3 个通用键
- 翻译键总数 6182 → 6257 (zh-CN/en-US 完全对齐)
- tsc -b=0, npm build=✅ (20.76s), ESLint=0, 17/17 集成测试通过
This commit is contained in:
attacker committed 2026-07-03 21:32:56 +08:00
1 parent 88122a72a2
commit 777d9e4e71
4 files changed
+257 -95

No files matched your search

@@ -1,4 +1,5 @@
import { useState, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate, Link } from 'react-router-dom';
import { Card, CardContent } from '@/components/ui/card';
import { StatStrip } from '@/components/ui/stat-strip';
@@ -30,6 +31,7 @@ import type { JsonData } from '@/types/common';
const CHART_COLORS = ['#3B82F6', '#207060', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899', '#14B8A6', '#F97316'];
export default function ClusterComparePage() {
const { t } = useTranslation();
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const [data, setData] = useState<JsonData>(null);
@@ -41,13 +43,13 @@ export default function ClusterComparePage() {
try {
const res: JsonData = await kubernetesApi.getMultiClusterComparison();
setData(res || null);
} catch (e) {
setError(e?.message || '加载失败');
} catch {
setError(t('k8s.compare.loadFailed'));
setData(null);
} finally {
setLoading(false);
}
}, []);
}, [t]);
useEffect(() => { loadData(); }, [loadData]);
@@ -57,7 +59,7 @@ export default function ClusterComparePage() {
// 图表数据准备
const resourceBarData = clusters.map((c: JsonData) => ({
name: c.code || c.name,
节点: c.node_count || 0,
[t('k8s.compare.chartNodes')]: c.node_count || 0,
Pod: c.pod_count || 0,
Deployment: c.deployment_count || 0,
Service: c.service_count || 0,
@@ -65,17 +67,17 @@ export default function ClusterComparePage() {
const usageBarData = clusters.map((c: JsonData) => ({
name: c.code || c.name,
'CPU %': Number((c.cpu_usage || 0).toFixed(1)),
'内存 %': Number((c.memory_usage || 0).toFixed(1)),
[t('k8s.compare.cpuPercent')]: Number((c.cpu_usage || 0).toFixed(1)),
[t('k8s.compare.memPercent')]: Number((c.memory_usage || 0).toFixed(1)),
}));
const radarData = clusters.length > 0 ? [
{ metric: '节点数', ...Object.fromEntries(clusters.map((c: JsonData) => [c.code || c.name, c.node_count || 0])) },
{ metric: 'Pod数', ...Object.fromEntries(clusters.map((c: JsonData) => [c.code || c.name, Math.min(c.pod_count || 0, 100)])) },
{ metric: '部署数', ...Object.fromEntries(clusters.map((c: JsonData) => [c.code || c.name, c.deployment_count || 0])) },
{ metric: 'CPU使用', ...Object.fromEntries(clusters.map((c: JsonData) => [c.code || c.name, Number((c.cpu_usage || 0).toFixed(1))])) },
{ metric: '内存使用', ...Object.fromEntries(clusters.map((c: JsonData) => [c.code || c.name, Number((c.memory_usage || 0).toFixed(1))])) },
{ metric: 'CPU核数', ...Object.fromEntries(clusters.map((c: JsonData) => [c.code || c.name, c.total_cpu_cores || 0])) },
{ metric: t('k8s.compare.chartNodes'), ...Object.fromEntries(clusters.map((c: JsonData) => [c.code || c.name, c.node_count || 0])) },
{ metric: t('k8s.compare.chartPods'), ...Object.fromEntries(clusters.map((c: JsonData) => [c.code || c.name, Math.min(c.pod_count || 0, 100)])) },
{ metric: t('k8s.compare.chartDeploy'), ...Object.fromEntries(clusters.map((c: JsonData) => [c.code || c.name, c.deployment_count || 0])) },
{ metric: t('k8s.compare.chartCpuUsage'), ...Object.fromEntries(clusters.map((c: JsonData) => [c.code || c.name, Number((c.cpu_usage || 0).toFixed(1))])) },
{ metric: t('k8s.compare.chartMemUsage'), ...Object.fromEntries(clusters.map((c: JsonData) => [c.code || c.name, Number((c.memory_usage || 0).toFixed(1))])) },
{ metric: t('k8s.compare.chartCpuCores'), ...Object.fromEntries(clusters.map((c: JsonData) => [c.code || c.name, c.total_cpu_cores || 0])) },
] : [];
const nodeRolePie = clusters.flatMap((c: JsonData) => [
@@ -96,12 +98,12 @@ export default function ClusterComparePage() {
};
// 格式化同步时间
const fmtTime = (t: string | null | undefined) => {
if (!t) return '从未同步';
const fmtTime = (ts: string | null | undefined) => {
if (!ts) return t('k8s.compare.neverSynced');
try {
const d = new Date(t);
const d = new Date(ts);
return d.toLocaleString('zh-CN', { hour12: false });
} catch { return t; }
} catch { return ts; }
};
return (
@@ -111,19 +113,19 @@ export default function ClusterComparePage() {
<div className="flex items-center gap-3">
<Layers className="h-5 w-5 dg-text-blue" />
<div>
<h1 className="text-lg font-semibold">多集群资源对比看板</h1>
<p className="text-xs dg-text-muted">跨集群资源用量、节点分布、健康状态对比</p>
<h1 className="text-lg font-semibold">{t('k8s.compare.title')}</h1>
<p className="text-xs dg-text-muted">{t('k8s.compare.subtitle')}</p>
</div>
</div>
<div className="flex items-center gap-2">
<Button variant="outline" size="sm" onClick={loadData} disabled={loading}
className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-hover)]">
<RefreshCw className={`h-3.5 w-3.5 mr-1.5 ${loading ? 'animate-spin' : ''}`} />
刷新
{t('common.refresh')}
</Button>
<Link to="/kubernetes">
<Button variant="outline" size="sm" className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-hover)]">
<Cloud className="h-3.5 w-3.5 mr-1.5" />集群列表
<Cloud className="h-3.5 w-3.5 mr-1.5" />{t('k8s.compare.clusterList')}
</Button>
</Link>
</div>
@@ -139,8 +141,8 @@ export default function ClusterComparePage() {
{/* 概览卡片 */}
<StatStrip
items={[
{ label: '集群', value: totals.cluster_count || 0, color: 'blue' },
{ label: '节点', value: totals.node_count || 0, color: 'emerald' },
{ label: t('k8s.compare.statCluster'), value: totals.cluster_count || 0, color: 'blue' },
{ label: t('k8s.compare.statNodes'), value: totals.node_count || 0, color: 'emerald' },
{ label: 'Pod', value: totals.pod_count || 0, color: 'purple' },
{ label: 'Deploy', value: totals.deployment_count || 0, color: 'amber' },
{ label: 'Service', value: totals.service_count || 0, color: 'cyan' },
@@ -151,10 +153,10 @@ export default function ClusterComparePage() {
<Card className="bg-[var(--dg-bg-secondary)]/60 border-[var(--dg-border)]">
<CardContent className="py-12 text-center">
<Cloud className="h-12 w-12 dg-text-muted mx-auto mb-3" />
<p className="dg-text-muted">暂无集群数据,请先添加 Kubernetes 集群并同步</p>
<p className="dg-text-muted">{t('k8s.compare.noData')}</p>
<Link to="/kubernetes" className="inline-block mt-3">
<Button variant="outline" size="sm" className="border-[var(--dg-border)]">
去添加集群 <ChevronRight className="h-3.5 w-3.5 ml-1" />
{t('k8s.compare.goAddCluster')} <ChevronRight className="h-3.5 w-3.5 ml-1" />
</Button>
</Link>
</CardContent>
@@ -169,7 +171,7 @@ export default function ClusterComparePage() {
<CardContent className="p-5">
<div className="flex items-center gap-2 mb-4">
<Layers className="h-4 w-4 dg-text-blue" />
<h3 className="text-sm font-semibold">资源数量对比</h3>
<h3 className="text-sm font-semibold">{t('k8s.compare.resourceCompare')}</h3>
</div>
<ResponsiveContainer width="100%" height={280}>
<BarChart data={resourceBarData} margin={{ top: 5, right: 10, left: -15, bottom: 5 }}>
@@ -178,7 +180,7 @@ export default function ClusterComparePage() {
<YAxis tick={{ fill: '#94a3b8', fontSize: 11 }} />
<Tooltip contentStyle={{ background: '#0f172a', border: '1px solid #334155', borderRadius: 8, fontSize: 12 }} />
<Legend wrapperStyle={{ fontSize: 11 }} />
<Bar dataKey="节点" fill={CHART_COLORS[1]} radius={[3, 3, 0, 0]} />
<Bar dataKey={t('k8s.compare.chartNodes')} fill={CHART_COLORS[1]} radius={[3, 3, 0, 0]} />
<Bar dataKey="Pod" fill={CHART_COLORS[0]} radius={[3, 3, 0, 0]} />
<Bar dataKey="Deployment" fill={CHART_COLORS[2]} radius={[3, 3, 0, 0]} />
<Bar dataKey="Service" fill={CHART_COLORS[5]} radius={[3, 3, 0, 0]} />
@@ -191,7 +193,7 @@ export default function ClusterComparePage() {
<CardContent className="p-5">
<div className="flex items-center gap-2 mb-4">
<Activity className="h-4 w-4 dg-text-green" />
<h3 className="text-sm font-semibold">CPU / 内存使用率对比</h3>
<h3 className="text-sm font-semibold">{t('k8s.compare.usageCompare')}</h3>
</div>
<ResponsiveContainer width="100%" height={280}>
<BarChart data={usageBarData} margin={{ top: 5, right: 10, left: -15, bottom: 5 }}>
@@ -200,8 +202,8 @@ export default function ClusterComparePage() {
<YAxis tick={{ fill: '#94a3b8', fontSize: 11 }} unit="%" />
<Tooltip contentStyle={{ background: '#0f172a', border: '1px solid #334155', borderRadius: 8, fontSize: 12 }} />
<Legend wrapperStyle={{ fontSize: 11 }} />
<Bar dataKey="CPU %" fill={CHART_COLORS[0]} radius={[3, 3, 0, 0]} />
<Bar dataKey="内存 %" fill={CHART_COLORS[1]} radius={[3, 3, 0, 0]} />
<Bar dataKey={t('k8s.compare.cpuPercent')} fill={CHART_COLORS[0]} radius={[3, 3, 0, 0]} />
<Bar dataKey={t('k8s.compare.memPercent')} fill={CHART_COLORS[1]} radius={[3, 3, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</CardContent>
@@ -215,7 +217,7 @@ export default function ClusterComparePage() {
<CardContent className="p-5">
<div className="flex items-center gap-2 mb-4">
<TrendingUp className="h-4 w-4 dg-text-purple" />
<h3 className="text-sm font-semibold">资源雷达对比</h3>
<h3 className="text-sm font-semibold">{t('k8s.compare.radarCompare')}</h3>
</div>
<ResponsiveContainer width="100%" height={320}>
<RadarChart data={radarData}>
@@ -242,7 +244,7 @@ export default function ClusterComparePage() {
<CardContent className="p-5">
<div className="flex items-center gap-2 mb-4">
<Server className="h-4 w-4 dg-text-yellow" />
<h3 className="text-sm font-semibold">节点角色分布</h3>
<h3 className="text-sm font-semibold">{t('k8s.compare.nodeRoleDist')}</h3>
</div>
{nodeRolePie.length > 0 ? (
<ResponsiveContainer width="100%" height={320}>
@@ -260,7 +262,7 @@ export default function ClusterComparePage() {
</PieChart>
</ResponsiveContainer>
) : (
<div className="h-[320px] flex items-center justify-center dg-text-muted text-sm">无节点角色数据</div>
<div className="h-[320px] flex items-center justify-center dg-text-muted text-sm">{t('k8s.compare.noNodeRoleData')}</div>
)}
</CardContent>
</Card>
@@ -272,25 +274,25 @@ export default function ClusterComparePage() {
<CardContent className="p-5">
<div className="flex items-center gap-2 mb-4">
<HardDrive className="h-4 w-4 dg-text-cyan" />
<h3 className="text-sm font-semibold">详细对比</h3>
<span className="text-xs dg-text-muted ml-2">点击行可进入集群概览</span>
<h3 className="text-sm font-semibold">{t('k8s.compare.detailCompare')}</h3>
<span className="text-xs dg-text-muted ml-2">{t('k8s.compare.clickRowHint')}</span>
</div>
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow className="border-[var(--dg-border)] hover:bg-transparent">
<TableHead className="dg-text-secondary">集群</TableHead>
<TableHead className="dg-text-secondary">状态</TableHead>
<TableHead className="dg-text-secondary text-right">节点</TableHead>
<TableHead className="dg-text-secondary">{t('k8s.compare.colCluster')}</TableHead>
<TableHead className="dg-text-secondary">{t('common.status')}</TableHead>
<TableHead className="dg-text-secondary text-right">{t('k8s.compare.colNodes')}</TableHead>
<TableHead className="dg-text-secondary text-right">Pod</TableHead>
<TableHead className="dg-text-secondary text-right">Deployment</TableHead>
<TableHead className="dg-text-secondary text-right">Service</TableHead>
<TableHead className="dg-text-secondary text-right">CPU 核数</TableHead>
<TableHead className="dg-text-secondary text-right">内存</TableHead>
<TableHead className="dg-text-secondary text-right">CPU 使用率</TableHead>
<TableHead className="dg-text-secondary text-right">内存使用率</TableHead>
<TableHead className="dg-text-secondary">角色</TableHead>
<TableHead className="dg-text-secondary">同步时间</TableHead>
<TableHead className="dg-text-secondary text-right">{t('k8s.compare.colCpuCores')}</TableHead>
<TableHead className="dg-text-secondary text-right">{t('k8s.compare.colMemory')}</TableHead>
<TableHead className="dg-text-secondary text-right">{t('k8s.compare.colCpuUsage')}</TableHead>
<TableHead className="dg-text-secondary text-right">{t('k8s.compare.colMemUsage')}</TableHead>
<TableHead className="dg-text-secondary">{t('k8s.compare.colRole')}</TableHead>
<TableHead className="dg-text-secondary">{t('k8s.compare.colSyncTime')}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -305,7 +307,7 @@ export default function ClusterComparePage() {
</TableCell>
<TableCell>
<Badge variant="outline" className={`text-xs ${statusColorMap[c.status] || statusColorMap.error}`}>
{c.status === 'healthy' ? '✓ 健康' : c.status === 'warning' ? '⚠ 警告' : '✗ 异常'}
{c.status === 'healthy' ? t('k8s.compare.statusHealthy') : c.status === 'warning' ? t('k8s.compare.statusWarning') : t('k8s.compare.statusError')}
</Badge>
</TableCell>
<TableCell className="text-right font-mono text-[var(--dg-text-secondary)]">{c.node_count}</TableCell>