refactor(i18n): U33 K8s 资源编辑器 + Pod 日志/Metrics 模板字面量中文清零

resource-visual-editors/ 5 文件 (ConfigMap/Secret/Service/Ingress/PvcEditor):
- 保存按钮文案 (保存配置/密钥/Service/Ingress/PVC)
- IngressEditor: (可选)/手工输入/帮助文本/常见示例/手动输入/继续手动输入
- PvcEditor: 访问模式描述 (单节点读写/多节点只读/多节点读写)

pod-logs-viewer/ 3 文件:
- ControlBar: 导出/刷新/实时/停止按钮
- LogContentArea: 加载中… 占位
- LogsHeader: 日志标题模板

PodLogsViewer: 6 处错误消息 + DialogTitle/Description
PodMetricsPage: subtitle 模板字面量 + 所有命名空间
PodExecTerminal: 连接错误消息

17 处 toast description 模板字面量中文→i18n.t() 插值:
- DeploymentEditAdvanced/Containers/Storage: 更新已提交
- DeploymentView: 回滚/伸缩
- DeploymentDetail: 伸缩/镜像更新
- DeploymentEditService: 资源删除
- ResourceVisualEditor/NamespaceResourcePage: 资源更新/YAML下载
- ClusterOverviewPage: 集群未找到
- DeploymentEditContainers: 容器名称/镜像为空

5 处 confirm description 中文→i18n.t() 插值:
- DeploymentView/EditService/Detail/WorkloadViewPage/MiddlewareMonitor/SiteMonitor/HealthProbeSection

5 处 subtitle 模板字面量中文→i18n.t():
- PodMetricsPage/CapacityPage/EventsPage/KubernetesNodes/ClusterOverviewPage

新增 k8s.rve 14键 + k8s.toast 18键 + k8s.podMetrics 4键 + k8s.podLogs 13键 = 49 翻译键
zh/en 7481→7524 完全对齐
tsc -b=0, npm build✓ (17.76s), ESLint errors=0/warnings=43/any=0, go build=0, 17/17 测试通过
This commit is contained in:
attacker committed 2026-07-12 16:51:57 +08:00
1 parent 5fbb5f824d
commit ad9acea1e5
31 files changed
+167 -72

No files matched your search

+1
View File
@@ -521,6 +521,7 @@ services/api.ts (1324L)
| U30 | i18n 批量转换损坏调用修复(8 处损坏的 i18n.t() 调用修复 — DeploymentEditContainers.tsx:85 toast description 损坏 / DeploymentEditStorage.tsx:175,202 !manifest 空状态+数据卷副标题损坏 / AgentRuleDialog.tsx:111,157,183,195 DialogDescription+操作符/端口/持续时间 Label 损坏 / LogFieldSidebar.tsx:68 搜索 placeholder 损坏。损坏原因: 早期批量正则 i18n 转换将模板表达式/JSX 碎片错误包裹进 i18n.t() 调用。新增 6 个语义化键(agent.ruleDialog.description/operator/port/duration + kubernetes.storage.volumeDesc + monitor.lbl.searchFields),zh/en 7375→7381 +6 键对齐,tsc -b=0,npm build✓ (17.99s),ESLint=0/53/0,go build=0,17/17 测试通过) | P3 | **done** |
| U31 | AIOps Agent CLI 组件 i18n 修复(AgentCLI + AgentCLIToolbar + AgentCLIHistory + AgentCLIEmptyState + ChatInputArea + agent-cli-constants.ts 6 文件 — Q7/Q35 拆分后丢失的 i18n 集成全部修复,新增 `aiops.cli.*` 命名空间 100 翻译键(8 子分类: toolbar/safety/tips/error/info/viewMode/placeholder/history/empty/input),SAFETY_MODES+TIPS_HOST+TIPS_KUBERNETES 常量数组改为 `getSafetyModes(t)`/`getTipsHost(t)`/`getTipsKubernetes(t)` 接收 TFunction 参数的工厂函数,全部 UI 硬编码中文清零,zh/en 7381→7481 +100 键对齐,tsc -b=0,npm build✓ (18.21s),ESLint=0/53/0,go build=0,17/17 测试通过) | P3 | **done** |
| U32 | 全量 hash key 清理 + React Hooks 警告修复(151 个 hash-style flat key (s+hex 后缀) 全部重命名为语义化 nested key — monitor 58/kubernetes 42/aiops 17/agent 12/cloud 13/pipeline 6/system 3;58 个源文件 147 处 t()/i18n.t() 调用更新;3 个命名空间冲突修复(monitor.hostMonitor/agent.agentMonitor/aiops.aiAnalysisResult 原 string → .title 子键);React Hooks 警告 53→43 (-10): exhaustive-deps 4→0 + preserve-manual-memoization 5→0 + 变量遮蔽修复(filter(t)→filter(tg));zh/en 7480=7480 完全对齐 0 hash key 残留;tsc -b=0,npm build✓ (17.59s),ESLint=0/43/any=0,go build=0,17/17 测试通过) | P3 | **done** |
| U33 | K8s 资源编辑器 + Pod 日志/Metrics i18n 补全(resource-visual-editors/ 5 文件: ConfigMap/Secret/Service/Ingress/PvcEditor 按钮文案+帮助文本+访问模式+IngressClass 提示全部 i18n;pod-logs-viewer/ 3 文件: ControlBar 导出/刷新/实时/停止+LogContentArea 加载中+LogsHeader 标题;PodLogsViewer 6 处错误消息+DialogTitle/Description;PodMetricsPage subtitle+表头+所有命名空间;17 处 toast description 模板字面量中文→i18n.t() 插值 (DeploymentEditAdvanced/Containers/Storage/View/Detail/Service/ResourceVisualEditor/NamespaceResourcePage/ClusterOverviewPage);5 处 confirm description (DeploymentView/EditService/Detail/WorkloadViewPage/MiddlewareMonitor/SiteMonitor/HealthProbeSection);5 处 subtitle 模板字面量 (PodMetricsPage/CapacityPage/EventsPage/KubernetesNodes/ClusterOverviewPage);新增 k8s.rve 14键+k8s.toast 18键+k8s.podMetrics 4键+k8s.podLogs 13键 共 49 翻译键;zh/en 7481→7524 +43 对齐;tsc -b=0,npm build✓ (17.76s),ESLint=0/43/any=0,go build=0,17/17 测试通过) | P3 | **done** |
---
@@ -79,7 +79,7 @@ export default function CapacityPage() {
<PageShell
variant="list"
title={i18n.t('k8s.capacity.resources')}
subtitle={clusterName ? `${clusterName} · Deployment 资源请求与限制` : i18n.t('k8s.capacity.resources1')}
subtitle={clusterName ? `${clusterName} · ${i18n.t('k8s.capacity.resources1')}` : i18n.t('k8s.capacity.resources1')}
icon={<BarChart3 className="h-5 w-5" />}
actions={(
<>
@@ -54,7 +54,7 @@ export default function ClusterOverviewPage() {
const current = list.find((c: JsonData) => (c.code || String(c.id)) === clusterCode);
setClusterInfo(current || null);
if (!current && list.length > 0 && clusterCode) {
toast({ title: i18n.t('k8s.clusterOverview.notFound'), description: `找不到集群 ${clusterCode}`, variant: 'destructive' });
toast({ title: i18n.t('k8s.clusterOverview.notFound'), description: i18n.t('k8s.toast.clusterNotFound', { code: clusterCode }), variant: 'destructive' });
}
} catch { setClusters([]); }
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -86,7 +86,7 @@ export default function ClusterOverviewPage() {
<PageShell
variant="default"
title={clusterCode || i18n.t('k8s.clusterOverview.title')}
subtitle={clusterInfo?.version ? `集群概览 · Kubernetes ${clusterInfo.version}` : i18n.t('k8s.clusterOverview.title')}
subtitle={clusterInfo?.version ? `${i18n.t('k8s.clusterOverview.title')} · Kubernetes ${clusterInfo.version}` : i18n.t('k8s.clusterOverview.title')}
icon={<Server className="h-5 w-5" />}
metrics={!loading ? (
<StatStrip
@@ -75,7 +75,7 @@ export function DeploymentDetail({ clusterCode, namespace, deploymentName, onBac
setActionLoading('scale');
try {
await kubernetesApi.scaleDeployment(clusterCode, namespace, deploymentName, newReplicas);
toast({ title: i18n.t('k8s.workloads.scaleSuccess'), description: `副本数已设置为 ${newReplicas}` });
toast({ title: i18n.t('k8s.workloads.scaleSuccess'), description: i18n.t('k8s.toast.scaleSet', { count: newReplicas }) });
loadData();
} catch (e) {
toast({ title: i18n.t('cloud.operationFailed'), description: e?.message || String(e), variant: 'destructive' });
@@ -97,7 +97,7 @@ export function DeploymentDetail({ clusterCode, namespace, deploymentName, onBac
setActionLoading('image');
try {
await kubernetesApi.updateDeploymentImage(clusterCode, namespace, deploymentName, newImage);
toast({ title: i18n.t('k8s.deploymentDetail.imageUpdated'), description: `正在滚动更新为 ${newImage}` });
toast({ title: i18n.t('k8s.deploymentDetail.imageUpdated'), description: i18n.t('k8s.toast.imageUpdating', { image: newImage }) });
setShowImageUpdate(false);
loadData();
} catch (e) {
@@ -108,7 +108,7 @@ export function DeploymentDetail({ clusterCode, namespace, deploymentName, onBac
const handleHpaToggle = async (hpa: JsonData) => {
const ok = await confirm({
title: i18n.t('kubernetes.hpa.deleteHpa'),
description: `确定删除 HPA "${hpa.name}"?`,
description: i18n.t('k8s.toast.confirmDeleteHpa', { name: hpa.name }),
confirmText: i18n.t('kubernetes.storage.delete2'),
variant: 'destructive',
});
@@ -170,7 +170,7 @@ export function DeploymentEditAdvanced({ clusterCode, namespace, deploymentName,
};
const base = buildAdvancedManifest(rawManifest, state);
await saveDeploymentManifest(clusterCode, namespace, deploymentName, base);
toast({ title: i18n.t('kubernetes.deploymentEditAdvanced.saved'), description: `${deploymentName} 更新已提交` });
toast({ title: i18n.t('kubernetes.deploymentEditAdvanced.saved'), description: i18n.t('k8s.toast.updatedSubmitted', { name: deploymentName }) });
onSaved(base);
} catch (e) {
toast({ title: i18n.t('kubernetes.msg.saveFailed'), description: e instanceof Error ? e.message : String(e), variant: 'destructive' });
@@ -82,11 +82,11 @@ export function DeploymentEditContainers({ clusterCode, namespace, deploymentNam
// validate
for (const c of [...initContainers, ...containers]) {
if (!c.name.trim()) {
toast({ title: i18n.t('kubernetes.msg.saveFailed'), description: '容器名称不能为空', variant: 'destructive' });
toast({ title: i18n.t('kubernetes.msg.saveFailed'), description: i18n.t('k8s.toast.containerNameEmpty'), variant: 'destructive' });
return;
}
if (!c.image.trim()) {
toast({ title: i18n.t('kubernetes.msg.saveFailed'), description: `容器 ${c.name} 的镜像不能为空`, variant: 'destructive' });
toast({ title: i18n.t('kubernetes.msg.saveFailed'), description: i18n.t('k8s.toast.containerImageEmpty', { name: c.name }), variant: 'destructive' });
return;
}
}
@@ -102,7 +102,7 @@ export function DeploymentEditContainers({ clusterCode, namespace, deploymentNam
? imagePullSecrets.filter(s => s.trim()).map((s: string) => ({ name: s }))
: undefined;
await saveDeploymentManifest(clusterCode, namespace, deploymentName, base);
toast({ title: i18n.t('k8s.editContainers.saved'), description: `${deploymentName} 更新已提交` });
toast({ title: i18n.t('k8s.editContainers.saved'), description: i18n.t('k8s.toast.updatedSubmitted', { name: deploymentName }) });
onSaved(base);
} catch (e) {
toast({ title: i18n.t('kubernetes.msg.saveFailed'), description: e?.message || String(e), variant: 'destructive' });
@@ -222,7 +222,7 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName,
const deleteResource = async (kind: 'service' | 'ingress', name: string) => {
const ok = await confirm({
title: i18n.t('common.delete'),
description: `确认删除 ${kind === 'service' ? 'Service' : 'Ingress'} 「${name}」?此操作不可撤销。`,
description: i18n.t('k8s.toast.confirmDeleteResource', { kind: kind === 'service' ? 'Service' : 'Ingress', name }),
confirmText: i18n.t('kubernetes.storage.delete2'),
variant: 'destructive',
});
@@ -230,7 +230,7 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName,
try {
const resource = kind === 'service' ? 'services' : 'ingresses';
await kubernetesApi.deleteResourceYaml(clusterCode, namespace, resource, name);
toast({ title: i18n.t('monitor.silenceRules.deleted'), description: `${kind === 'service' ? 'Service' : 'Ingress'} ${name} 已删除` });
toast({ title: i18n.t('monitor.silenceRules.deleted'), description: i18n.t('k8s.toast.resourceDeleted', { kind: kind === 'service' ? 'Service' : 'Ingress', name }) });
load();
} catch (e) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -151,7 +151,7 @@ export function DeploymentEditStorage({ clusterCode, namespace, deploymentName,
const { volumes, mounts } = volumeEntriesToStorage(entries);
const base = applyStorageToManifest(rawManifest, volumes, mounts, passthroughVolumes);
await saveDeploymentManifest(clusterCode, namespace, deploymentName, base);
toast({ title: i18n.t('k8s.editStorage.saved'), description: `${deploymentName} 更新已提交` });
toast({ title: i18n.t('k8s.editStorage.saved'), description: i18n.t('k8s.toast.updatedSubmitted', { name: deploymentName }) });
onSaved(base);
} catch (e) {
toast({ title: i18n.t('kubernetes.msg.saveFailed'), description: (e as Error)?.message || String(e), variant: 'destructive' });
@@ -140,7 +140,7 @@ export function DeploymentView(props: DeploymentViewProps) {
const revision = rev.revision ?? rev.revision_number ?? rev.number;
const ok = await confirm({
title: i18n.t('k8s.deploymentView.rollbackVersion'),
description: `确认回滚到修订版本 #${revision}?`,
description: i18n.t('k8s.toast.confirmRollback', { revision }),
confirmText: i18n.t('pipeline.rollback'),
variant: 'destructive',
});
@@ -148,7 +148,7 @@ export function DeploymentView(props: DeploymentViewProps) {
setRollbackLoading(revision);
try {
await kubernetesApi.rollbackDeployment(clusterCode, namespace, deployment.name, Number(revision));
toast({ title: i18n.t('k8s.deploymentView.rollbackSent'), description: `正在回滚到 #${revision}` });
toast({ title: i18n.t('k8s.deploymentView.rollbackSent'), description: i18n.t('k8s.toast.rollbackToRevision', { revision }) });
onRefresh();
} catch (e) {
toast({ title: i18n.t('pipeline.rollbackFailed'), description: e?.message || String(e), variant: 'destructive' });
@@ -160,7 +160,7 @@ export function DeploymentView(props: DeploymentViewProps) {
setScaleLoading(true);
try {
await kubernetesApi.scaleDeployment(clusterCode, namespace, deployment.name, scaleValue);
toast({ title: i18n.t('k8s.deploymentView.scaleSent'), description: `副本数 → ${scaleValue}` });
toast({ title: i18n.t('k8s.deploymentView.scaleSent'), description: i18n.t('k8s.toast.scaleTo', { count: scaleValue }) });
onRefresh();
} catch (e) {
toast({ title: i18n.t('kubernetes.msg.scaleFailed'), description: e?.message || String(e), variant: 'destructive' });
@@ -180,7 +180,7 @@ export default function EventsPage() {
<PageShell
variant="list"
title={i18n.t('k8s.event.center')}
subtitle={clusterName ? `${clusterName} · 集群事件与告警` : i18n.t('k8s.event.clusterAlerts')}
subtitle={clusterName ? `${clusterName} · ${i18n.t('k8s.event.clusterAlerts')}` : i18n.t('k8s.event.clusterAlerts')}
icon={<Bell className="h-5 w-5" />}
metrics={!loading ? (
<StatStrip
@@ -86,7 +86,7 @@ export function KubernetesNodes() {
<PageShell
variant="list"
title={i18n.t('menu.nodeManagement')}
subtitle={clusterLabel ? `${clusterLabel} · 工作节点状态与资源` : i18n.t('k8s.nodes.subtitle')}
subtitle={clusterLabel ? `${clusterLabel} · ${i18n.t('k8s.nodes.subtitle')}` : i18n.t('k8s.nodes.subtitle')}
icon={<Server className="h-5 w-5" />}
actions={(
<>
@@ -144,7 +144,7 @@ export default function NamespaceResourcePage() {
if (!clusterCode || !namespace) return;
try {
await kubernetesApi.exportNamespaceResources(clusterCode, namespace);
toast({ title: i18n.t('common.exportComplete'), description: `${namespace} 资源 YAML 已开始下载` });
toast({ title: i18n.t('common.exportComplete'), description: i18n.t('k8s.toast.yamlDownloadStarted', { namespace }) });
} catch (e) {
toast({ title: i18n.t('kubernetes.msg.exportFailed'), description: (e as Error)?.message || String(e), variant: 'destructive' });
}
@@ -151,7 +151,7 @@ export function PodExecTerminal({
console.error('[PodExec:connect] WS error event fired, readyState=', ws.readyState, 'type=', ev.type);
setConnState('error');
setHasOutput(true);
term.write('\r\n\x1b[31m[连接发生错误]\x1b[0m\r\n');
term.write(`\r\n\x1b[31m${i18n.t('k8s.podLogs.connError')}\x1b[0m\r\n`);
};
}, [clusterId, namespace, podName, container, shell]);
@@ -125,7 +125,7 @@ export function PodLogsViewer({
appendLines(arr);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
appendLines([`\x1b[31m[获取日志失败: ${msg}]\x1b[0m`]);
appendLines([i18n.t('k8s.podLogs.fetchFailed', { msg })]);
toast({ title: i18n.t('k8s.podLogs.loadFailed'), description: msg, variant: 'destructive' });
} finally {
setLoading(false);
@@ -183,14 +183,14 @@ export function PodLogsViewer({
}
}
flush();
appendLines(['\x1b[33m— 流式结束 —\x1b[0m']);
appendLines([`\x1b[33m${i18n.t('k8s.podLogs.streamEnded')}\x1b[0m`]);
} catch (e) {
const errName = (e as { name?: string })?.name;
if (errName === 'AbortError') {
appendLines(['\x1b[33m— 已停止 —\x1b[0m']);
appendLines([`\x1b[33m${i18n.t('k8s.podLogs.streamStopped')}\x1b[0m`]);
} else {
const msg = e instanceof Error ? e.message : String(e);
appendLines([`\x1b[31m[流式连接中断: ${msg}]\x1b[0m`]);
appendLines([`\x1b[31m${i18n.t('k8s.podLogs.streamInterrupted', { msg })}\x1b[0m`]);
}
} finally {
setStreaming(false);
@@ -261,8 +261,8 @@ export function PodLogsViewer({
)}
style={fullscreen ? { maxWidth: '100vw', width: '100vw', height: '100vh' } : { maxWidth: '1100px', width: '94vw' }}
>
<DialogTitle className="sr-only">日志 — {podName}</DialogTitle>
<DialogDescription className="sr-only">Pod 容器日志查看</DialogDescription>
<DialogTitle className="sr-only">{i18n.t('k8s.podLogs.logTitle', { name: podName })}</DialogTitle>
<DialogDescription className="sr-only">{i18n.t('k8s.podLogs.logDesc')}</DialogDescription>
<LogsHeader
podName={podName}
@@ -88,7 +88,7 @@ export default function PodMetricsPage() {
<PageShell
variant="list"
title={i18n.t('k8s.podMetrics.resources2')}
subtitle={clusterName ? `${clusterName} · 容器 CPU/内存实时指标` : i18n.t('k8s.podMetrics.container2')}
subtitle={clusterName ? `${clusterName} · ${i18n.t('k8s.podMetrics.realtimeMetrics')}` : i18n.t('k8s.podMetrics.container2')}
icon={<Activity className="h-5 w-5" />}
metrics={!loading ? (
<StatStrip
@@ -110,7 +110,7 @@ export default function PodMetricsPage() {
<Select value={selectedNs} onValueChange={setSelectedNs}>
<SelectTrigger className="w-36 h-9 dg-input"><SelectValue placeholder={i18n.t('k8s.podMetrics.namespace')} /></SelectTrigger>
<SelectContent>
<SelectItem value="all">所有命名空间</SelectItem>
<SelectItem value="all">{i18n.t('k8s.podMetrics.allNamespaces')}</SelectItem>
{namespaces.map(n => <SelectItem key={n} value={n}>{n}</SelectItem>)}
</SelectContent>
</Select>
@@ -92,7 +92,7 @@ export function ResourceVisualEditor({ clusterCode, namespace, resource, name, o
toast({ title: i18n.t('monitor.dashboard.custom.toastCreateSuccess'), description: `${resource}/${resName}` });
} else {
await kubernetesApi.updateResourceYaml(clusterCode, namespace, resource, name, yamlStr);
toast({ title: i18n.t('kubernetes.msg.saveSuccess'), description: `${resource}/${name} 已更新` });
toast({ title: i18n.t('kubernetes.msg.saveSuccess'), description: i18n.t('k8s.toast.resourceUpdated', { resource, name }) });
}
onSaved?.();
onClose();
@@ -110,7 +110,7 @@ export function ResourceVisualEditor({ clusterCode, namespace, resource, name, o
toast({ title: i18n.t('monitor.dashboard.custom.toastCreateSuccess'), description: i18n.t('k8s.rve.saveFailed') });
} else {
await kubernetesApi.updateResourceYaml(clusterCode, namespace, resource, name, yamlStr);
toast({ title: i18n.t('kubernetes.msg.saveSuccess'), description: `${resource}/${name} 已更新` });
toast({ title: i18n.t('kubernetes.msg.saveSuccess'), description: i18n.t('k8s.toast.resourceUpdated', { resource, name }) });
}
setYamlEditing(false);
setDisplayYaml(yamlStr);
@@ -96,7 +96,7 @@ export default function WorkloadViewPage() {
const handleRestart = async () => {
const ok = await confirm({
title: i18n.t('kubernetes.deployment.restart'),
description: `确认重启 ${kind} ${name}?将触发滚动重启。`,
description: i18n.t('k8s.toast.confirmRestart', { kind, name }),
confirmText: i18n.t('agent.agentServiceRestart'),
});
if (!ok) return;
@@ -104,7 +104,7 @@ export function YamlViewer({
} else {
await kubernetesApi.updateResourceYaml(clusterCode, namespace, resource, name, yamlStr);
}
toast({ title: i18n.t('kubernetes.msg.saveSuccess'), description: saveYamlFn ? i18n.t('k8s.rve.saveFailed') : `${resource}/${name} 已更新` });
toast({ title: i18n.t('kubernetes.msg.saveSuccess'), description: i18n.t('k8s.toast.resourceUpdated', { resource, name }),});
setEditing(false);
setRawYaml(yamlStr);
setDisplayYaml(yamlStr);
@@ -127,7 +127,7 @@ export function ControlBar(props: ControlBarProps) {
title={i18n.t('k8s.podLogs.previousLogs')}
disabled={filteredLineCount === 0}
>
<Download className="h-3.5 w-3.5 mr-1" />导出
<Download className="h-3.5 w-3.5 mr-1" />{i18n.t('k8s.podLogs.exportBtn')}
</Button>
{/* 刷新(一次性) */}
@@ -138,7 +138,7 @@ export function ControlBar(props: ControlBarProps) {
disabled={loading}
title={i18n.t('k8s.podLogs.previousLogs')}
>
<RefreshCw className={cn('h-3.5 w-3.5 mr-1', loading && 'animate-spin')} />刷新
<RefreshCw className={cn('h-3.5 w-3.5 mr-1', loading && 'animate-spin')} />{i18n.t('k8s.podLogs.refreshBtn')}
</Button>
{/* 实时跟踪 / 暂停 / 继续 */}
@@ -150,7 +150,7 @@ export function ControlBar(props: ControlBarProps) {
disabled={loading}
title={i18n.t('k8s.podLogs.previousLogs')}
>
<Play className="h-3.5 w-3.5 mr-1" />实时
<Play className="h-3.5 w-3.5 mr-1" />{i18n.t('k8s.podLogs.liveBtn')}
</Button>
) : (
<>
@@ -169,7 +169,7 @@ export function ControlBar(props: ControlBarProps) {
onClick={onStopStream}
title={i18n.t('agent.agentServiceStop')}
>
<Pause className="h-3.5 w-3.5 mr-1" />停止
<Pause className="h-3.5 w-3.5 mr-1" />{i18n.t('k8s.podLogs.stopBtn')}
</Button>
</>
)}
@@ -51,7 +51,7 @@ export function LogContentArea({
)}
{loading && lineCount === 0 && (
<div className="flex items-center justify-center h-full text-[var(--dg-text-muted)]">
<Loader2 className="h-4 w-4 animate-spin mr-2" />加载中…
<Loader2 className="h-4 w-4 animate-spin mr-2" />{i18n.t('k8s.podLogs.loadingDots')}
</div>
)}
{filteredLines.map((line) => (
@@ -37,7 +37,7 @@ export function LogsHeader({
streaming ? (paused ? 'bg-amber-400' : 'bg-emerald-400 animate-pulse') : 'bg-[var(--dg-bg-active)]'
)} />
<ScrollText className="h-4 w-4 dg-text-blue shrink-0" />
<span className="text-[var(--dg-text-primary)] font-medium truncate">日志 — {podName}</span>
<span className="text-[var(--dg-text-primary)] font-medium truncate">{i18n.t('k8s.podLogs.logTitle', { name: podName })}</span>
{loading && <Loader2 className="h-3 w-3 animate-spin text-[var(--dg-text-muted)]" />}
<span className="text-xs text-[var(--dg-text-muted)]">
{streaming ? (paused ? i18n.t('kubernetes.deployment.paused') : i18n.t('kubernetes.podLogsViewer.liveTail')) : i18n.t('agent.agentServiceStopped')} · {matchedCount}{keyword ? `/${lineCount}` : ''} 行
@@ -87,7 +87,7 @@ export function ConfigMapEditor({ manifest, onSave, saving, isCreate }: ConfigMa
<div className="shrink-0 flex justify-end gap-2 pt-3 mt-2 border-t border-[var(--dg-border)]">
<Button size="sm" className="bg-blue-600 hover:bg-blue-700" onClick={handleSave} disabled={saving}>
{saving ? <Loader2 className="h-3.5 w-3.5 animate-spin mr-1" /> : <Save className="h-3.5 w-3.5 mr-1" />}
保存配置
{i18n.t('k8s.rve.saveConfigMap')}
</Button>
</div>
</div>
@@ -108,7 +108,7 @@ export function IngressEditor({ manifest, clusterCode, onSave, saving, isCreate
<div className="min-w-0">
<Label className="text-[var(--dg-text-secondary)] text-xs flex items-center gap-1">
Ingress Class
<span className="text-[10px] text-[var(--dg-text-muted)]">(可选)</span>
<span className="text-[10px] text-[var(--dg-text-muted)]">{i18n.t('k8s.rve.optional')}</span>
</Label>
{/* IngressClass 下拉 + 手工输入支持 // Logan.Li */}
<div className="space-y-1 mt-1">
@@ -135,15 +135,15 @@ export function IngressEditor({ manifest, clusterCode, onSave, saving, isCreate
)}
{/* 当前自定义值作为选项,确保选中态 */}
{ingressClass && !ingressClasses.some((ic) => ic.name === ingressClass) && (
<SelectItem value={ingressClass}>{ingressClass} (手动输入)</SelectItem>
<SelectItem value={ingressClass}>{ingressClass} {i18n.t('k8s.rve.manualEntry')}</SelectItem>
)}
<SelectItem value="__manual__">—— 继续手动输入 ——</SelectItem>
<SelectItem value="__manual__">{i18n.t('k8s.rve.continueManual')}</SelectItem>
</SelectContent>
</Select>
{/* 总是显示输入框支持手工输入或修改 */}
<div className="text-[10px] text-[var(--dg-text-muted)] flex items-center gap-1">
手工输入:
{i18n.t('k8s.rve.manualInput')}
<Input
value={ingressClass}
onChange={(e) => setIngressClass(e.target.value)}
@@ -154,13 +154,13 @@ export function IngressEditor({ manifest, clusterCode, onSave, saving, isCreate
{/* 帮助提示 */}
<div className="text-[10px] text-[var(--dg-text-muted)] leading-tight">
您的集群中可能有多个 IngressController,您可在此指定当前 Ingress 对应哪个 IngressController。<br />
从 IngressClass 列表选择,或手工输入名称。
{i18n.t('k8s.rve.ingressHelp1')}<br />
{i18n.t('k8s.rve.ingressHelp2')}
</div>
{/* 常见示例 */}
{(ingressClasses.length === 0 || ingressClass) && (
<div className="text-[10px] text-[var(--dg-text-muted)]">
常见:<span className="font-mono">alb</span> → ingress.k8s.alibabacloud/alb &nbsp;&nbsp; <span className="font-mono">nginx</span> → k8s.io/ingress-nginx
{i18n.t('k8s.rve.commonExamples')}<span className="font-mono">alb</span> → ingress.k8s.alibabacloud/alb &nbsp;&nbsp; <span className="font-mono">nginx</span> → k8s.io/ingress-nginx
</div>
)}
</div>
@@ -207,7 +207,7 @@ export function IngressEditor({ manifest, clusterCode, onSave, saving, isCreate
<div className="shrink-0 flex justify-end gap-2 pt-3 mt-2 border-t border-[var(--dg-border)]">
<Button size="sm" className="bg-blue-600 hover:bg-blue-700" onClick={handleSave} disabled={saving}>
{saving ? <Loader2 className="h-3.5 w-3.5 animate-spin mr-1" /> : <Save className="h-3.5 w-3.5 mr-1" />}
保存 Ingress
{i18n.t('k8s.rve.saveIngress')}
</Button>
</div>
</div>
@@ -60,9 +60,9 @@ export function PvcEditor({ manifest, onSave, saving, isCreate }: PvcEditorProps
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="ReadWriteOnce">ReadWriteOnce (单节点读写)</SelectItem>
<SelectItem value="ReadOnlyMany">ReadOnlyMany (多节点只读)</SelectItem>
<SelectItem value="ReadWriteMany">ReadWriteMany (多节点读写)</SelectItem>
<SelectItem value="ReadWriteOnce">{i18n.t('k8s.rve.accessModeRWO')}</SelectItem>
<SelectItem value="ReadOnlyMany">{i18n.t('k8s.rve.accessModeROX')}</SelectItem>
<SelectItem value="ReadWriteMany">{i18n.t('k8s.rve.accessModeRWX')}</SelectItem>
</SelectContent>
</Select>
</div>
@@ -82,7 +82,7 @@ export function PvcEditor({ manifest, onSave, saving, isCreate }: PvcEditorProps
<div className="shrink-0 flex justify-end gap-2 pt-3 mt-2 border-t border-[var(--dg-border)]">
<Button size="sm" className="bg-blue-600 hover:bg-blue-700" onClick={handleSave} disabled={saving}>
{saving ? <Loader2 className="h-3.5 w-3.5 animate-spin mr-1" /> : <Save className="h-3.5 w-3.5 mr-1" />}
保存 PVC
{i18n.t('k8s.rve.savePvc')}
</Button>
</div>
</div>
@@ -106,7 +106,7 @@ export function SecretEditor({ manifest, onSave, saving, isCreate }: SecretEdito
<div className="shrink-0 flex justify-end gap-2 pt-3 mt-2 border-t border-[var(--dg-border)]">
<Button size="sm" className="bg-blue-600 hover:bg-blue-700" onClick={handleSave} disabled={saving}>
{saving ? <Loader2 className="h-3.5 w-3.5 animate-spin mr-1" /> : <Save className="h-3.5 w-3.5 mr-1" />}
保存密钥
{i18n.t('k8s.rve.saveSecret')}
</Button>
</div>
</div>
@@ -147,7 +147,7 @@ export function ServiceEditor({ manifest, onSave, saving, isCreate }: ServiceEdi
<div className="shrink-0 flex justify-end gap-2 pt-3 mt-2 border-t border-[var(--dg-border)]">
<Button size="sm" className="bg-blue-600 hover:bg-blue-700" onClick={handleSave} disabled={saving}>
{saving ? <Loader2 className="h-3.5 w-3.5 animate-spin mr-1" /> : <Save className="h-3.5 w-3.5 mr-1" />}
保存 Service
{i18n.t('k8s.rve.saveService')}
</Button>
</div>
</div>
+54 -8
View File
@@ -2775,11 +2775,11 @@
"podLogs": {
"noLogs": "No logs",
"loadFailed": "Failed to load logs",
"streamInterrupted": "Stream connection interrupted",
"streamInterrupted": "[Stream connection interrupted: {{msg}}]",
"streaming": "Streaming...",
"liveStream": "Live Stream",
"noLogContent": "No log content",
"logTitle": "Logs",
"logTitle": "Logs — {{name}}",
"logDesc": "Pod container log viewer",
"export": "Export",
"refreshLogs": "Refresh",
@@ -2791,13 +2791,20 @@
"bufferFull": "Buffer full",
"matched": "Matched",
"loading": "Loading…",
"fetchFailed": "Failed to fetch logs",
"streamEnded": "Stream ended",
"streamStopped": "Stream stopped",
"fetchFailed": "[Failed to fetch logs: {{msg}}]",
"streamEnded": "— Stream ended —",
"streamStopped": "— Stopped —",
"download": "Download Logs",
"searchPlaceholder": "Search logs...",
"previousLogs": "View previous (crashed) container logs",
"keywordFilter": "Keyword filter…"
"keywordFilter": "Keyword filter…",
"liveTail": "Live Tail",
"exportBtn": "Export",
"refreshBtn": "Refresh",
"liveBtn": "Live",
"stopBtn": "Stop",
"loadingDots": "Loading…",
"connError": "[Connection error]"
},
"editMode": {
"basic": "Basic Info",
@@ -3182,7 +3189,11 @@
"memory": "总内存",
"namespace": "所有命名空间",
"cpuUsage": "CPU Usage",
"memUsage": "Memory Usage"
"memUsage": "Memory Usage",
"realtimeMetrics": "Container CPU/Memory real-time metrics",
"allNamespaces": "All Namespaces",
"colNamespace": "Namespace",
"colContainer": "Container"
},
"resourceEditor": {
"create": "Create",
@@ -3366,7 +3377,22 @@
"keyPlaceholder": "Key name (e.g. LOG_LEVEL)",
"parseYamlFailed": "Unable to parse YAML",
"checkSyntax": "Please check syntax and continue in YAML mode",
"fallbackToYaml": "Unable to parse resource structure, switched to YAML edit mode"
"fallbackToYaml": "Unable to parse resource structure, switched to YAML edit mode",
"saveConfigMap": "Save ConfigMap",
"saveSecret": "Save Secret",
"saveService": "Save Service",
"saveIngress": "Save Ingress",
"savePvc": "Save PVC",
"optional": "(Optional)",
"manualInput": "Manual input:",
"manualEntry": "(Manual)",
"continueManual": "—— Continue manual input ——",
"ingressHelp1": "Your cluster may have multiple IngressControllers. You can specify which IngressController this Ingress corresponds to.",
"ingressHelp2": "Select from the IngressClass list, or enter a name manually.",
"commonExamples": "Common:",
"accessModeRWO": "ReadWriteOnce (Single node read-write)",
"accessModeROX": "ReadOnlyMany (Multi-node read-only)",
"accessModeRWX": "ReadWriteMany (Multi-node read-write)"
},
"workloadView": {
"restartSent": "Restart command sent",
@@ -3467,6 +3493,26 @@
},
"podFile": {
"loadFailed": "Failed to load file list"
},
"toast": {
"updatedSubmitted": "{{name}} update submitted",
"yamlDownloadStarted": "{{namespace}} resource YAML download started",
"rollbackToRevision": "Rolling back to #{{revision}}",
"scaleTo": "Replicas → {{count}}",
"scaleSet": "Replicas set to {{count}}",
"imageUpdating": "Rolling update to {{image}}",
"resourceUpdated": "{{resource}}/{{name}} updated",
"resourceDeleted": "{{kind}} {{name}} deleted",
"containerNameEmpty": "Container name cannot be empty",
"containerImageEmpty": "Container {{name}} image cannot be empty",
"clusterNotFound": "Cluster {{code}} not found",
"confirmRollback": "Confirm rollback to revision #{{revision}}?",
"confirmDeleteResource": "Confirm delete {{kind}} \"{{name}}\"? This cannot be undone.",
"confirmDeleteHpa": "Delete HPA \"{{name}}\"?",
"confirmRestart": "Confirm restart {{kind}} {{name}}? This will trigger a rolling restart.",
"confirmDeleteProbe": "Delete probe \"{{name}}\"?",
"confirmDeleteMonitor": "Confirm delete \"{{name}}\"?",
"confirmDeleteTask": "Delete \"{{name}}\"?"
}
},
"monitor": {
+54 -8
View File
@@ -2775,11 +2775,11 @@
"podLogs": {
"noLogs": "无日志",
"loadFailed": "获取日志失败",
"streamInterrupted": "流式连接中断",
"streamInterrupted": "[流式连接中断: {{msg}}]",
"streaming": "流中...",
"liveStream": "实时流",
"noLogContent": "无日志内容",
"logTitle": "日志",
"logTitle": "日志 — {{name}}",
"logDesc": "Pod 容器日志查看",
"export": "导出",
"refreshLogs": "刷新",
@@ -2791,13 +2791,20 @@
"bufferFull": "缓冲已满",
"matched": "匹配",
"loading": "加载中…",
"fetchFailed": "获取日志失败",
"streamEnded": "流式传输结束",
"streamStopped": "流式传输已停止",
"fetchFailed": "[获取日志失败: {{msg}}]",
"streamEnded": "— 流式结束 —",
"streamStopped": "— 已停止 —",
"download": "下载日志",
"searchPlaceholder": "搜索日志...",
"previousLogs": "查看上一次(崩溃)容器的日志",
"keywordFilter": "关键字过滤…"
"keywordFilter": "关键字过滤…",
"liveTail": "实时跟踪",
"exportBtn": "导出",
"refreshBtn": "刷新",
"liveBtn": "实时",
"stopBtn": "停止",
"loadingDots": "加载中…",
"connError": "[连接发生错误]"
},
"editMode": {
"basic": "基本信息",
@@ -3182,7 +3189,11 @@
"memory": "总内存",
"namespace": "所有命名空间",
"cpuUsage": "CPU 使用",
"memUsage": "内存使用"
"memUsage": "内存使用",
"realtimeMetrics": "容器 CPU/内存实时指标",
"allNamespaces": "所有命名空间",
"colNamespace": "命名空间",
"colContainer": "容器"
},
"resourceEditor": {
"create": "新建",
@@ -3366,7 +3377,22 @@
"keyPlaceholder": "键名 (如 LOG_LEVEL)",
"parseYamlFailed": "无法解析 YAML",
"checkSyntax": "请检查语法后继续用 YAML 模式",
"fallbackToYaml": "无法解析资源结构,已切换到 YAML 编辑模式"
"fallbackToYaml": "无法解析资源结构,已切换到 YAML 编辑模式",
"saveConfigMap": "保存配置",
"saveSecret": "保存密钥",
"saveService": "保存 Service",
"saveIngress": "保存 Ingress",
"savePvc": "保存 PVC",
"optional": "(可选)",
"manualInput": "手工输入:",
"manualEntry": "(手动输入)",
"continueManual": "—— 继续手动输入 ——",
"ingressHelp1": "您的集群中可能有多个 IngressController,您可在此指定当前 Ingress 对应哪个 IngressController。",
"ingressHelp2": "从 IngressClass 列表选择,或手工输入名称。",
"commonExamples": "常见:",
"accessModeRWO": "ReadWriteOnce (单节点读写)",
"accessModeROX": "ReadOnlyMany (多节点只读)",
"accessModeRWX": "ReadWriteMany (多节点读写)"
},
"workloadView": {
"restartSent": "重启指令已下发",
@@ -3467,6 +3493,26 @@
},
"podFile": {
"loadFailed": "加载文件列表失败"
},
"toast": {
"updatedSubmitted": "{{name}} 更新已提交",
"yamlDownloadStarted": "{{namespace}} 资源 YAML 已开始下载",
"rollbackToRevision": "正在回滚到 #{{revision}}",
"scaleTo": "副本数 → {{count}}",
"scaleSet": "副本数已设置为 {{count}}",
"imageUpdating": "正在滚动更新为 {{image}}",
"resourceUpdated": "{{resource}}/{{name}} 已更新",
"resourceDeleted": "{{kind}} {{name}} 已删除",
"containerNameEmpty": "容器名称不能为空",
"containerImageEmpty": "容器 {{name}} 的镜像不能为空",
"clusterNotFound": "找不到集群 {{code}}",
"confirmRollback": "确认回滚到修订版本 #{{revision}}?",
"confirmDeleteResource": "确认删除 {{kind}} 「{{name}}」?此操作不可撤销。",
"confirmDeleteHpa": "确定删除 HPA \"{{name}}\"?",
"confirmRestart": "确认重启 {{kind}} {{name}}?将触发滚动重启。",
"confirmDeleteProbe": "确定删除探针「{{name}}」?",
"confirmDeleteMonitor": "确认删除 \"{{name}}\"?",
"confirmDeleteTask": "确定删除「{{name}}」吗?"
}
},
"monitor": {
@@ -13,6 +13,7 @@ import { MiddlewareDialog } from './middleware-monitor/MiddlewareDialog';
import type { MwTarget } from './middleware-monitor/helpers';
import { REDIS_CHARTS, MYSQL_CHARTS, evalMwHealth, worstHealthLevel } from './middleware-monitor/helpers';
import { useTranslation } from 'react-i18next';
import i18n from '@/i18n';
type TabKey = 'redis' | 'mysql';
const REFRESH_INTERVAL_MS = 30_000;
@@ -87,7 +88,7 @@ export function MiddlewareMonitor() {
const warnCount = targets.filter((t) => worstHealthLevel(evalMwHealth(t)) !== 'ok').length;
const handleDelete = async (target: MwTarget) => {
const ok = await confirm({ title: t('monitor.lbl.deleteMonitor'), description: `确认删除 "${target.name}"?`, confirmText: t('kubernetes.storage.delete2') });
const ok = await confirm({ title: t('monitor.lbl.deleteMonitor'), description: i18n.t('k8s.toast.confirmDeleteMonitor', { name: target.name }), confirmText: t('kubernetes.storage.delete2') });
if (!ok) return;
try {
await middlewareMonitorApi.delete(target.id);
+1 -1
View File
@@ -158,7 +158,7 @@ export function SiteMonitor() {
};
const handleDelete = async (task: SiteTask) => {
const ok = await _confirm({ title: t('monitor.deleteProbe'), description: `确定删除「${task.name}」吗?`, variant: 'destructive' });
const ok = await _confirm({ title: t('monitor.deleteProbe'), description: i18n.t('k8s.toast.confirmDeleteTask', { name: task.name }), variant: 'destructive' });
if (!ok) return;
try {
await siteApi.deleteTask(task.id);
@@ -19,6 +19,7 @@ import { DEFAULT_PROBE_FORM, type ProbeForm } from './types';
import { useConfirm } from '@/components/ui/confirm-dialog';
import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';
import i18n from '@/i18n';
interface HealthProbeSectionProps {
selectedHost: string;
@@ -77,7 +78,7 @@ export function HealthProbeSection({ selectedHost }: HealthProbeSectionProps) {
const handleDeleteProbe = async (p: Record<string, unknown>) => {
const ok = await confirm({
title: t('monitor.lbl.deleteProbe'),
description: `确定删除探针「${p.name}」?`,
description: i18n.t('k8s.toast.confirmDeleteProbe', { name: p.name }),
confirmText: t('kubernetes.storage.delete2'),
variant: 'destructive',
});