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

@@ -25,6 +25,8 @@ import { useToast } from '@/hooks/use-toast';
import { useConfirm } from '@/components/ui/confirm-dialog';
import { PageShell } from '@/components/layout/PageShell';
import { ContentPanel } from '@/components/layout/ContentPanel';
import { useTranslation } from 'react-i18next';
import i18n from '@/i18n';
type ResourceType =
| 'deployments' | 'pods' | 'services' | 'ingresses'
@@ -43,8 +45,8 @@ const BULK_EXPORT_TYPES: ResourceType[] = ['services', 'ingresses', 'configmaps'
const VisualActions = ({ name, opts }: { name: string; opts: { onYaml?: (n: string) => void; onEdit?: (n: string) => void } }) => (
<div className="flex items-center gap-0.5">
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-[var(--dg-green)] hover:text-[var(--dg-green)]" title="可视化编辑"
onClick={() => opts.onEdit?.(name)}><Pencil className="h-3.5 w-3.5 mr-1" />编辑</Button>
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-[var(--dg-green)] hover:text-[var(--dg-green)]" title=i18n.t('k8s.resource.visualEditor')
onClick={() => opts.onEdit?.(name)}><Pencil className="h-3.5 w-3.5 mr-1" />{i18n.t('common.edit')}</Button>
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-secondary)]" title="YAML"
onClick={() => opts.onYaml?.(name)}><FileCode className="h-3.5 w-3.5 mr-1" />YAML</Button>
</div>
@@ -58,8 +60,8 @@ const RESOURCE_CONFIG: Record<ResourceType, {
renderRow: ( item: any, opts: { clusterCode: string; namespace: string; onDetail?: ( item: any) => void; onYaml?: (name: string) => void; onEdit?: (name: string) => void; onTerminal?: ( item: any) => void; onFileBrowser?: ( item: any) => void; onDeletePod?: ( item: any) => void; onPodDetail?: ( item: any) => void }) => React.ReactNode;
}> = {
deployments: {
title: 'Deployments', subtitle: 'Deployment 工作负载运行状态', icon: Boxes,
columns: ['名称', '命名空间', '副本', '镜像'],
title: 'Deployments', subtitle: i18n.t('kubernetes.namespaceresourcepage.s35c744'), icon: Boxes,
columns: [i18n.t('common.name'), i18n.t('k8s.column.namespace'), i18n.t('kubernetes.namespaceresourcepage.s0428b3'), i18n.t('k8s.column.image')],
renderRow: (d, opts) => (
<TableRow key={d.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30 cursor-pointer" onClick={() => opts.onDetail?.(d)}>
<TableCell className="font-medium text-[var(--dg-text-primary)]">{d.name}</TableCell>
@@ -83,8 +85,8 @@ const RESOURCE_CONFIG: Record<ResourceType, {
),
},
pods: {
title: 'Pods', subtitle: 'Pod 实例运行状态', icon: Container,
columns: ['名称', '命名空间', '状态', '节点', 'CPU', '内存', '重启', '镜像', '操作'],
title: 'Pods', subtitle: i18n.t('kubernetes.namespaceresourcepage.s780b7d'), icon: Container,
columns: [i18n.t('common.name'), i18n.t('k8s.column.namespace'), i18n.t('common.status'), i18n.t('k8s.tab.nodes'), 'CPU', i18n.t('k8s.pods.memory'), i18n.t('common.reboot'), i18n.t('k8s.column.image'), i18n.t('common.actions')],
renderRow: (p, opts) => {
const statusMap: Record<string, string> = { Running: 'bg-[var(--dg-green)]/20 text-[var(--dg-green)] border-[var(--dg-green)]/30', Pending: 'bg-[var(--dg-orange)]/20 text-[var(--dg-orange)] border-[var(--dg-orange)]/30', Failed: 'bg-[var(--dg-red)]/20 text-[var(--dg-red)] border-[var(--dg-red)]/30' };
return (
@@ -104,13 +106,13 @@ const RESOURCE_CONFIG: Record<ResourceType, {
<TableCell className="text-xs text-[var(--dg-text-muted)] max-w-[200px] truncate">{p.image || '-'}</TableCell>
<TableCell>
<div className="flex items-center gap-0.5">
<Button variant="ghost" size="sm" className="h-7 w-7 p-0 shrink-0 text-[var(--dg-green)]" title="终端"
<Button variant="ghost" size="sm" className="h-7 w-7 p-0 shrink-0 text-[var(--dg-green)]" title=i18n.t('kubernetes.pod.terminal')
onClick={() => (opts as Record<string, any>).onTerminal?.(p)}><Terminal className="h-3.5 w-3.5" /></Button>
<Button variant="ghost" size="sm" className="h-7 w-7 p-0 shrink-0 text-[var(--dg-orange)]" title="文件"
<Button variant="ghost" size="sm" className="h-7 w-7 p-0 shrink-0 text-[var(--dg-orange)]" title=i18n.t('k8s.pods.action.file')
onClick={() => (opts as Record<string, any>).onFileBrowser?.(p)}><HardDrive className="h-3.5 w-3.5" /></Button>
<Button variant="ghost" size="sm" className="h-7 w-7 p-0 shrink-0 text-[var(--dg-blue)]" title="YAML"
onClick={() => opts.onYaml?.(p.name)}><FileCode className="h-3.5 w-3.5" /></Button>
<Button variant="ghost" size="sm" className="h-7 w-7 p-0 shrink-0 text-[var(--dg-red)] hover:text-[var(--dg-red)]" title="删除"
<Button variant="ghost" size="sm" className="h-7 w-7 p-0 shrink-0 text-[var(--dg-red)] hover:text-[var(--dg-red)]" title=i18n.t('common.delete')
onClick={() => opts.onDeletePod?.(p)}><Trash2 className="h-3.5 w-3.5" /></Button>
</div>
</TableCell>
@@ -119,8 +121,8 @@ const RESOURCE_CONFIG: Record<ResourceType, {
},
},
services: {
title: 'Services', subtitle: 'Service 网络服务', icon: Network,
columns: ['名称', '命名空间', '类型', 'Cluster IP', '端口', '操作'],
title: 'Services', subtitle: i18n.t('kubernetes.namespaceresourcepage.s5e76b6'), icon: Network,
columns: [i18n.t('common.name'), i18n.t('k8s.column.namespace'), i18n.t('common.type'), 'Cluster IP', i18n.t('server.csv.port'), i18n.t('common.actions')],
renderRow: (s, opts) => (
<TableRow key={s.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
<TableCell className="font-medium text-[var(--dg-text-primary)]">{s.name}</TableCell>
@@ -133,8 +135,8 @@ const RESOURCE_CONFIG: Record<ResourceType, {
),
},
ingresses: {
title: 'Ingresses', subtitle: 'Ingress 路由规则', icon: Network,
columns: ['名称', '命名空间', 'Hosts', 'TLS', '操作'],
title: 'Ingresses', subtitle: i18n.t('kubernetes.namespaceresourcepage.s8d5014'), icon: Network,
columns: [i18n.t('common.name'), i18n.t('k8s.column.namespace'), 'Hosts', 'TLS', i18n.t('common.actions')],
renderRow: (i, opts) => {
const hosts = i.hosts || [];
return (
@@ -149,8 +151,8 @@ const RESOURCE_CONFIG: Record<ResourceType, {
},
},
configmaps: {
title: 'ConfigMaps', subtitle: '配置字典', icon: FileText,
columns: ['名称', '命名空间', '键数量', '操作'],
title: 'ConfigMaps', subtitle: i18n.t('kubernetes.namespaceresourcepage.s0986a6'), icon: FileText,
columns: [i18n.t('common.name'), i18n.t('k8s.column.namespace'), i18n.t('kubernetes.namespaceresourcepage.sbd1fa0'), i18n.t('common.actions')],
renderRow: (c, opts) => (
<TableRow key={c.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
<TableCell className="font-medium text-[var(--dg-text-primary)]">{c.name}</TableCell>
@@ -161,8 +163,8 @@ const RESOURCE_CONFIG: Record<ResourceType, {
),
},
secrets: {
title: 'Secrets', subtitle: '密钥管理(仅名称与类型)', icon: Shield,
columns: ['名称', '命名空间', '类型', '操作'],
title: 'Secrets', subtitle: i18n.t('kubernetes.namespaceresourcepage.sd1424a'), icon: Shield,
columns: [i18n.t('common.name'), i18n.t('k8s.column.namespace'), i18n.t('common.type'), i18n.t('common.actions')],
renderRow: (s, opts) => (
<TableRow key={s.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
<TableCell className="font-medium text-[var(--dg-text-primary)]">{s.name}</TableCell>
@@ -173,8 +175,8 @@ const RESOURCE_CONFIG: Record<ResourceType, {
),
},
hpas: {
title: 'HPAs', subtitle: '水平自动伸缩', icon: Activity,
columns: ['名称', '命名空间', '目标', '最小/最大', 'CPU%', 'Mem%'],
title: 'HPAs', subtitle: i18n.t('kubernetes.namespaceresourcepage.s9abc8d'), icon: Activity,
columns: [i18n.t('common.name'), i18n.t('k8s.column.namespace'), i18n.t('common.target'), i18n.t('k8s.hpaCronHpa.col.minMax'), 'CPU%', 'Mem%'],
renderRow: (h, _opts) => (
<TableRow key={h.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
<TableCell className="font-medium text-[var(--dg-text-primary)]">{h.name}</TableCell>
@@ -187,21 +189,21 @@ const RESOURCE_CONFIG: Record<ResourceType, {
),
},
cronjobs: {
title: 'CronJobs', subtitle: '定时任务', icon: Clock,
columns: ['名称', '命名空间', '调度', '状态', '上次执行'],
title: 'CronJobs', subtitle: i18n.t('menu.cronJobs'), icon: Clock,
columns: [i18n.t('common.name'), i18n.t('k8s.column.namespace'), i18n.t('k8s.workload.scheduling'), i18n.t('common.status'), i18n.t('common.lastRun')],
renderRow: (cj) => (
<TableRow key={cj.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
<TableCell className="font-medium text-[var(--dg-text-primary)]">{cj.name}</TableCell>
<TableCell><Badge variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">{cj.namespace}</Badge></TableCell>
<TableCell className="text-sm font-mono text-[var(--dg-text-secondary)]">{cj.schedule || '-'}</TableCell>
<TableCell><Badge className={cj.suspend ? 'bg-[var(--dg-orange)]/20 text-[var(--dg-orange)]' : 'bg-[var(--dg-green)]/20 text-[var(--dg-green)]'}>{cj.suspend ? '已暂停' : '运行中'}</Badge></TableCell>
<TableCell><Badge className={cj.suspend ? 'bg-[var(--dg-orange)]/20 text-[var(--dg-orange)]' : 'bg-[var(--dg-green)]/20 text-[var(--dg-green)]'}>{cj.suspend ? i18n.t('kubernetes.deployment.paused') : i18n.t('common.running')}</Badge></TableCell>
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{cj.last_schedule || '-'}</TableCell>
</TableRow>
),
},
pvcs: {
title: '存储卷声明', subtitle: 'PersistentVolumeClaim', icon: Boxes,
columns: ['名称', '命名空间', '状态', '大小', 'StorageClass', '操作'],
title: i18n.t('kubernetes.namespaceresourcepage.s18c900'), subtitle: 'PersistentVolumeClaim', icon: Boxes,
columns: [i18n.t('common.name'), i18n.t('k8s.column.namespace'), i18n.t('common.status'), i18n.t('deploy.table.size'), 'StorageClass', i18n.t('common.actions')],
renderRow: (p, opts) => (
<TableRow key={p.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
<TableCell className="font-medium text-[var(--dg-text-primary)]">{p.name}</TableCell>
@@ -214,8 +216,8 @@ const RESOURCE_CONFIG: Record<ResourceType, {
),
},
statefulsets: {
title: 'StatefulSets', subtitle: '有状态应用', icon: Boxes,
columns: ['名称', '命名空间', '副本', '就绪', '镜像'],
title: 'StatefulSets', subtitle: i18n.t('kubernetes.namespaceresourcepage.sbfeb44'), icon: Boxes,
columns: [i18n.t('common.name'), i18n.t('k8s.column.namespace'), i18n.t('kubernetes.namespaceresourcepage.s0428b3'), i18n.t('common.ready'), i18n.t('k8s.column.image')],
renderRow: (s, opts) => (
<TableRow key={s.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30 cursor-pointer" onClick={() => opts.onDetail?.(s)}>
<TableCell className="font-medium text-[var(--dg-text-primary)]">{s.name}</TableCell>
@@ -227,8 +229,8 @@ const RESOURCE_CONFIG: Record<ResourceType, {
),
},
daemonsets: {
title: 'DaemonSets', subtitle: '守护进程集', icon: Boxes,
columns: ['名称', '命名空间', '期望', '当前', '就绪', '镜像'],
title: 'DaemonSets', subtitle: i18n.t('kubernetes.namespaceresourcepage.se1c074'), icon: Boxes,
columns: [i18n.t('common.name'), i18n.t('k8s.column.namespace'), i18n.t('monitor.siteProtocol.http.expectedPrefix'), i18n.t('common.current'), i18n.t('common.ready'), i18n.t('k8s.column.image')],
renderRow: (d, opts) => (
<TableRow key={d.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30 cursor-pointer" onClick={() => opts.onDetail?.(d)}>
<TableCell className="font-medium text-[var(--dg-text-primary)]">{d.name}</TableCell>
@@ -241,8 +243,8 @@ const RESOURCE_CONFIG: Record<ResourceType, {
),
},
serviceaccounts: {
title: 'ServiceAccounts', subtitle: '服务账号(RBAC 身份标识)', icon: Shield,
columns: ['名称', '命名空间', 'Secret数', '镜像拉取凭证', '创建时间', '操作'],
title: 'ServiceAccounts', subtitle: i18n.t('kubernetes.namespaceresourcepage.s645c9a'), icon: Shield,
columns: [i18n.t('common.name'), i18n.t('k8s.column.namespace'), i18n.t('kubernetes.namespaceresourcepage.sbaf7ed'), i18n.t('kubernetes.namespaceresourcepage.s260c11'), i18n.t('common.createdAt'), i18n.t('common.actions')],
renderRow: (sa, opts) => (
<TableRow key={sa.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
<TableCell className="font-medium text-[var(--dg-text-primary)]">{sa.name}</TableCell>
@@ -253,15 +255,15 @@ const RESOURCE_CONFIG: Record<ResourceType, {
</TableCell>
<TableCell className="text-xs text-[var(--dg-text-muted)]">{sa.age || '-'}</TableCell>
<TableCell onClick={(e) => e.stopPropagation()}>
<Button variant="ghost" size="icon" className="h-7 w-7 text-[var(--dg-text-secondary)] hover:text-[var(--dg-blue)]" title="查看YAML"
<Button variant="ghost" size="icon" className="h-7 w-7 text-[var(--dg-text-secondary)] hover:text-[var(--dg-blue)]" title=i18n.t('kubernetes.namespaceresourcepage.s4bde08')
onClick={() => opts.onYaml?.(sa.name)}><FileCode className="h-3.5 w-3.5" /></Button>
</TableCell>
</TableRow>
),
},
roles: {
title: 'Roles', subtitle: '命名空间级角色权限(RBAC)', icon: Shield,
columns: ['名称', '命名空间', '规则数', '权限范围', '创建时间', '操作'],
title: 'Roles', subtitle: i18n.t('kubernetes.namespaceresourcepage.s8905a6'), icon: Shield,
columns: [i18n.t('common.name'), i18n.t('k8s.column.namespace'), i18n.t('kubernetes.namespaceresourcepage.secde03'), i18n.t('system.apiKeyScope'), i18n.t('common.createdAt'), i18n.t('common.actions')],
renderRow: (r, opts) => {
const verbs = r.verbs || [];
const resources = r.resources || [];
@@ -281,9 +283,9 @@ const RESOURCE_CONFIG: Record<ResourceType, {
<TableCell className="text-xs text-[var(--dg-text-muted)]">{r.age || '-'}</TableCell>
<TableCell onClick={(e) => e.stopPropagation()}>
<div className="flex items-center gap-0.5">
<Button variant="ghost" size="icon" className="h-7 w-7 text-[var(--dg-text-secondary)] hover:text-[var(--dg-blue)]" title="查看YAML"
<Button variant="ghost" size="icon" className="h-7 w-7 text-[var(--dg-text-secondary)] hover:text-[var(--dg-blue)]" title=i18n.t('kubernetes.namespaceresourcepage.s4bde08')
onClick={() => opts.onYaml?.(r.name)}><FileCode className="h-3.5 w-3.5" /></Button>
<Button variant="ghost" size="icon" className="h-7 w-7 text-[var(--dg-green)] hover:text-[var(--dg-green)]" title="编辑"
<Button variant="ghost" size="icon" className="h-7 w-7 text-[var(--dg-green)] hover:text-[var(--dg-green)]" title=i18n.t('common.edit')
onClick={() => opts.onEdit?.(r.name)}><Pencil className="h-3.5 w-3.5" /></Button>
</div>
</TableCell>
@@ -292,8 +294,8 @@ const RESOURCE_CONFIG: Record<ResourceType, {
},
},
rolebindings: {
title: 'RoleBindings', subtitle: '角色绑定关系(RBAC 授权)', icon: Shield,
columns: ['名称', '命名空间', '角色引用', '绑定主体', '创建时间', '操作'],
title: 'RoleBindings', subtitle: i18n.t('kubernetes.namespaceresourcepage.sca10fe'), icon: Shield,
columns: [i18n.t('common.name'), i18n.t('k8s.column.namespace'), i18n.t('kubernetes.namespaceresourcepage.s227e5e'), i18n.t('kubernetes.namespaceresourcepage.s2667c7'), i18n.t('common.createdAt'), i18n.t('common.actions')],
renderRow: (rb, opts) => {
const subjects = rb.subjects || [];
const subjStr = subjects.length > 3 ? subjects.slice(0, 3).join(', ') + ` +${subjects.length - 3}` : subjects.join(', ');
@@ -312,9 +314,9 @@ const RESOURCE_CONFIG: Record<ResourceType, {
<TableCell className="text-xs text-[var(--dg-text-muted)]">{rb.age || '-'}</TableCell>
<TableCell onClick={(e) => e.stopPropagation()}>
<div className="flex items-center gap-0.5">
<Button variant="ghost" size="icon" className="h-7 w-7 text-[var(--dg-text-secondary)] hover:text-[var(--dg-blue)]" title="查看YAML"
<Button variant="ghost" size="icon" className="h-7 w-7 text-[var(--dg-text-secondary)] hover:text-[var(--dg-blue)]" title=i18n.t('kubernetes.namespaceresourcepage.s4bde08')
onClick={() => opts.onYaml?.(rb.name)}><FileCode className="h-3.5 w-3.5" /></Button>
<Button variant="ghost" size="icon" className="h-7 w-7 text-[var(--dg-green)] hover:text-[var(--dg-green)]" title="编辑"
<Button variant="ghost" size="icon" className="h-7 w-7 text-[var(--dg-green)] hover:text-[var(--dg-green)]" title=i18n.t('common.edit')
onClick={() => opts.onEdit?.(rb.name)}><Pencil className="h-3.5 w-3.5" /></Button>
</div>
</TableCell>
@@ -325,6 +327,7 @@ const RESOURCE_CONFIG: Record<ResourceType, {
};
export default function NamespaceResourcePage() {
const { t } = useTranslation();
const { clusterCode, namespace } = useParams<{ clusterCode: string; namespace: string }>();
const location = useLocation();
const navigate = useNavigate();
@@ -439,26 +442,26 @@ export default function NamespaceResourcePage() {
if (!clusterCode || !namespace) return;
try {
await kubernetesApi.exportNamespaceResources(clusterCode, namespace);
toast({ title: '导出完成', description: `${namespace} 资源 YAML 已开始下载` });
toast({ title: t('common.exportComplete'), description: t('kubernetes.namespaceresourcepage.sadafd4', { namespace: namespace }) });
} catch (e) {
toast({ title: '导出失败', description: (e as Error)?.message || String(e), variant: 'destructive' });
toast({ title: t('common.exportFailed'), description: (e as Error)?.message || String(e), variant: 'destructive' });
}
};
// Pod 删除 // Logan.Li
const handleDeletePod = async (p: any) => {
const ok = await confirm({
title: '删除 Pod',
description: '确认删除 Pod "' + (p.name || '') + '"?操作不可恢复。',
confirmText: '删除',
title: t('kubernetes.pod.deletePod'),
description: t('kubernetes.namespaceresourcepage.sc5bb25') + (p.name || '') + t('kubernetes.namespaceresourcepage.s3ec1f4'),
confirmText: t('common.delete'),
});
if (!ok) return;
try {
await kubernetesApi.deletePod(clusterCode || '', namespace || '', p.name);
toast({ title: 'Pod 已删除' });
toast({ title: t('kubernetes.pod.deleted') });
loadData();
} catch (e) {
toast({ title: '删除失败', description: (e as any)?.message || String(e), variant: 'destructive' });
toast({ title: t('common.deleteFailed'), description: (e as any)?.message || String(e), variant: 'destructive' });
}
};
@@ -467,7 +470,7 @@ export default function NamespaceResourcePage() {
const searchToolbar = (
<div className="relative w-56 flex-1 min-w-[200px] max-w-xs">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 dg-text-muted" />
<Input placeholder="搜索..." value={search} onChange={(e) => setSearch(e.target.value)} className="pl-9 h-9 dg-input" />
<Input placeholder=t('kubernetes.namespaceresourcepage.s64e327') value={search} onChange={(e) => setSearch(e.target.value)} className="pl-9 h-9 dg-input" />
</div>
);
@@ -482,11 +485,11 @@ export default function NamespaceResourcePage() {
actions={(
<>
<Select value={clusterCode || ''} onValueChange={(v) => navigate(`/kubernetes/${v}/namespace/${namespace || 'default'}/${type}`)}>
<SelectTrigger className="w-48 h-9 dg-input"><SelectValue placeholder="选择集群" /></SelectTrigger>
<SelectTrigger className="w-48 h-9 dg-input"><SelectValue placeholder=t('k8s.topology.selectCluster') /></SelectTrigger>
<SelectContent>{clusters.map((c) => <SelectItem key={c.id} value={c.code || String(c.id)}>{c.name}</SelectItem>)}</SelectContent>
</Select>
<Select value={namespace || ''} onValueChange={(v) => navigate(`/kubernetes/${clusterCode}/namespace/${v}/${type}`)}>
<SelectTrigger className="w-40 h-9 dg-input"><SelectValue placeholder="命名空间" /></SelectTrigger>
<SelectTrigger className="w-40 h-9 dg-input"><SelectValue placeholder=t('k8s.column.namespace') /></SelectTrigger>
<SelectContent className="max-h-[300px]">{namespaces.map((ns) => <SelectItem key={ns} value={ns}>{ns}</SelectItem>)}</SelectContent>
</Select>
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={loadData} disabled={loading}>
@@ -495,16 +498,16 @@ export default function NamespaceResourcePage() {
</Button>
{BULK_EXPORT_TYPES.includes(type) && (
<>
<Button variant="outline" size="sm" className="dg-btn-secondary text-xs" onClick={handleExportCSV} title="导出CSV">
<Button variant="outline" size="sm" className="dg-btn-secondary text-xs" onClick={handleExportCSV} title=t('kubernetes.namespaceresourcepage.sef28d5')>
<Download className="h-3 w-3 mr-1" />CSV
</Button>
<Button variant="outline" size="sm" className="dg-btn-secondary text-xs" onClick={handleExportJSON} title="导出JSON">
<Button variant="outline" size="sm" className="dg-btn-secondary text-xs" onClick={handleExportJSON} title=t('kubernetes.namespaceresourcepage.se11b48')>
<Download className="h-3 w-3 mr-1" />JSON
</Button>
</>
)}
{EXPORT_YAML_TYPES.includes(type) && (
<Button variant="outline" size="sm" className="dg-btn-secondary text-xs" onClick={handleExportYaml} title="导出命名空间 YAML(tar.gz)">
<Button variant="outline" size="sm" className="dg-btn-secondary text-xs" onClick={handleExportYaml} title=t('kubernetes.namespaceresourcepage.s2f8992')>
<Download className="h-3 w-3 mr-1" />导出YAML
</Button>
)}
@@ -556,7 +559,7 @@ export default function NamespaceResourcePage() {
<TableHeader><TableRow className="border-[var(--dg-border)] hover:bg-transparent">{config.columns.map((col) => <TableHead key={col} className="text-[var(--dg-text-secondary)]">{col}</TableHead>)}</TableRow></TableHeader>
<TableBody>
{loading ? (
<TableRow><TableCell colSpan={config.columns.length} className="text-center text-[var(--dg-text-muted)] py-12"><Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" />加载中...</TableCell></TableRow>
<TableRow><TableCell colSpan={config.columns.length} className="text-center text-[var(--dg-text-muted)] py-12"><Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" />{t('common.loading')}</TableCell></TableRow>
) : filtered.length === 0 ? (
<TableRow><TableCell colSpan={config.columns.length} className="text-center text-[var(--dg-text-muted)] py-12"><config.icon className="h-8 w-8 mx-auto mb-2 opacity-50" />暂无 {config.title}</TableCell></TableRow>
) : (
@@ -598,7 +601,7 @@ export default function NamespaceResourcePage() {
<div className="p-5 space-y-5">
{/* 基本信息 */}
<div>
<h4 className="text-sm font-medium text-[var(--dg-text-primary)] mb-3">基本信息</h4>
<h4 className="text-sm font-medium text-[var(--dg-text-primary)] mb-3">{t('deploy.label.basicInfo')}</h4>
{(() => {
const p = podDetail;
const labels = p.labels || p.metadata?.labels || {};
@@ -609,23 +612,23 @@ export default function NamespaceResourcePage() {
return (
<div className="grid grid-cols-2 gap-x-6 gap-y-2.5">
<div>
<span className="text-xs text-[var(--dg-text-muted)]">名称</span>
<span className="text-xs text-[var(--dg-text-muted)]">{t('common.name')}</span>
<p className="text-sm text-[var(--dg-text-primary)] font-mono">{p.name || '-'}</p>
</div>
<div>
<span className="text-xs text-[var(--dg-text-muted)]">命名空间</span>
<span className="text-xs text-[var(--dg-text-muted)]">{t('k8s.column.namespace')}</span>
<p className="text-sm text-[var(--dg-text-primary)]">{p.namespace || '-'}</p>
</div>
<div>
<span className="text-xs text-[var(--dg-text-muted)]">状态</span>
<span className="text-xs text-[var(--dg-text-muted)]">{t('common.status')}</span>
<div><Badge className={statusMap[st] || 'bg-[var(--dg-text-muted)]/20 text-[var(--dg-text-secondary)]'}>{st}</Badge></div>
</div>
<div>
<span className="text-xs text-[var(--dg-text-muted)]">创建时间</span>
<span className="text-xs text-[var(--dg-text-muted)]">{t('common.createdAt')}</span>
<p className="text-sm text-[var(--dg-text-primary)]">{created ? new Date(created).toLocaleString('zh-CN') : '-'}</p>
</div>
<div>
<span className="text-xs text-[var(--dg-text-muted)]">节点</span>
<span className="text-xs text-[var(--dg-text-muted)]">{t('k8s.tab.nodes')}</span>
<p className="text-sm text-[var(--dg-text-primary)]">{p.node_name || p.node || '-'}</p>
</div>
<div>
@@ -633,7 +636,7 @@ export default function NamespaceResourcePage() {
<p className="text-sm text-[var(--dg-text-primary)] font-mono">{p.ip || p.pod_ip || '-'}</p>
</div>
<div className="col-span-2">
<span className="text-xs text-[var(--dg-text-muted)]">标签</span>
<span className="text-xs text-[var(--dg-text-muted)]">{t('common.tag')}</span>
<div className="flex flex-wrap gap-1 mt-0.5">
{Object.keys(labels).length === 0 ? <span className="text-xs text-[var(--dg-text-muted)]">-</span>
: Object.entries(labels).map(([k, v]) => (
@@ -643,7 +646,7 @@ export default function NamespaceResourcePage() {
</div>
</div>
<div className="col-span-2">
<span className="text-xs text-[var(--dg-text-muted)]">注解</span>
<span className="text-xs text-[var(--dg-text-muted)]">{t('common.annotation')}</span>
<div className="flex flex-wrap gap-1 mt-0.5">
{Object.keys(annotations).length === 0 ? <span className="text-xs text-[var(--dg-text-muted)]">-</span>
: Object.entries(annotations).slice(0, 6).map(([k, v]) => (
@@ -659,19 +662,19 @@ export default function NamespaceResourcePage() {
{/* 现状详情(Conditions) */}
<div>
<h4 className="text-sm font-medium text-[var(--dg-text-primary)] mb-3">现状详情</h4>
<h4 className="text-sm font-medium text-[var(--dg-text-primary)] mb-3">{t('kubernetes.namespaceresourcepage.s1d8d6e')}</h4>
{(() => {
const cond: any[] = podDetail.conditions || podDetail.pod_conditions || [];
if (cond.length === 0) return <p className="text-xs text-[var(--dg-text-muted)] py-2">暂无数据</p>;
if (cond.length === 0) return <p className="text-xs text-[var(--dg-text-muted)] py-2">{t('common.noData')}</p>;
return (
<Table>
<TableHeader>
<TableRow className="border-[var(--dg-border)] hover:bg-transparent">
<TableHead className="text-[var(--dg-text-muted)] text-xs">类型</TableHead>
<TableHead className="text-[var(--dg-text-muted)] text-xs">状态</TableHead>
<TableHead className="text-[var(--dg-text-muted)] text-xs">更新时间</TableHead>
<TableHead className="text-[var(--dg-text-muted)] text-xs">原因</TableHead>
<TableHead className="text-[var(--dg-text-muted)] text-xs">消息</TableHead>
<TableHead className="text-[var(--dg-text-muted)] text-xs">{t('common.type')}</TableHead>
<TableHead className="text-[var(--dg-text-muted)] text-xs">{t('common.status')}</TableHead>
<TableHead className="text-[var(--dg-text-muted)] text-xs">{t('common.updatedAt')}</TableHead>
<TableHead className="text-[var(--dg-text-muted)] text-xs">{t('kubernetes.event.eventReason')}</TableHead>
<TableHead className="text-[var(--dg-text-muted)] text-xs">{t('kubernetes.event.eventMessage')}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -702,7 +705,7 @@ export default function NamespaceResourcePage() {
</div>
</div>
<div className="flex justify-end px-5 py-3 border-t border-[var(--dg-border)]">
<Button variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]" onClick={() => setPodDetail(null)}>关闭</Button>
<Button variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]" onClick={() => setPodDetail(null)}>{t('common.close')}</Button>
</div>
</div>
</div>