refactor(i18n): U26 Kubernetes 模块 phantom key 大规模清理 — 32 文件 171 phantom key → 8 残留(均为误匹配), 163 个替换为 k8s.* 语义化键, 新增 19 子命名空间 + 扩展 8 个已有, zh/en 6990→7185 +195 键对齐, tsc -b=0, npm build✓ (17.59s), ESLint=0/53/0, go build=0, 17/17 测试通过

This commit is contained in:
attacker committed 2026-07-12 00:43:20 +08:00
1 parent c6a50216dd
commit 7f5295cfc2
31 files changed
+657 -190

No files matched your search

@@ -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('集群未找到'), description: `找不到集群 ${clusterCode}`, variant: 'destructive' });
toast({ title: i18n.t('k8s.clusterOverview.notFound'), description: `找不到集群 ${clusterCode}`, variant: 'destructive' });
}
} catch { setClusters([]); }
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -85,8 +85,8 @@ export default function ClusterOverviewPage() {
return (
<PageShell
variant="default"
title={clusterCode || i18n.t('集群概览')}
subtitle={clusterInfo?.version ? `集群概览 · Kubernetes ${clusterInfo.version}` : i18n.t('集群概览')}
title={clusterCode || i18n.t('k8s.clusterOverview.title')}
subtitle={clusterInfo?.version ? `集群概览 · Kubernetes ${clusterInfo.version}` : i18n.t('k8s.clusterOverview.title')}
icon={<Server className="h-5 w-5" />}
metrics={!loading ? (
<StatStrip
@@ -61,7 +61,7 @@ export function DeploymentDetail({ clusterCode, namespace, deploymentName, onBac
setPods(allPods.filter((p: JsonData) => p.namespace === namespace && p.name.startsWith(deploymentName)));
setHpas(allHpas.filter((h: JsonData) => h.target_name === deploymentName && h.namespace === namespace));
} catch (e) {
console.error(i18n.t('加载详情失败:'), e);
console.error(i18n.t('k8s.deploymentWorkload.loadFailed'), e);
} finally {
setLoading(false);
}
@@ -86,7 +86,7 @@ export function DeploymentDetail({ clusterCode, namespace, deploymentName, onBac
setActionLoading('restart');
try {
await kubernetesApi.restartDeployment(clusterCode, namespace, deploymentName);
toast({ title: i18n.t('重启指令已下发'), description: i18n.t('Deployment 正在滚动重启') });
toast({ title: i18n.t('k8s.workloadView.restartSent'), description: i18n.t('k8s.deploymentWorkload.restarting') });
} catch (e) {
toast({ title: i18n.t('kubernetes.deployment.restartFailed'), description: e?.message || String(e), variant: 'destructive' });
} finally { setActionLoading(''); }
@@ -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('镜像更新成功'), description: `正在滚动更新为 ${newImage}` });
toast({ title: i18n.t('k8s.deploymentDetail.imageUpdated'), description: `正在滚动更新为 ${newImage}` });
setShowImageUpdate(false);
loadData();
} catch (e) {
@@ -115,7 +115,7 @@ export function DeploymentDetail({ clusterCode, namespace, deploymentName, onBac
if (!ok) return;
try {
await kubernetesApi.deleteHPA(clusterCode, namespace, hpa.name);
toast({ title: i18n.t('HPA 已删除') });
toast({ title: i18n.t('k8s.deploymentDetail.hpaDeleted') });
loadData();
} catch (e) {
toast({ title: i18n.t('kubernetes.msg.deleteFailed2'), description: e?.message || String(e), variant: 'destructive' });
@@ -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('容器配置已保存'), description: `${deploymentName} 更新已提交` });
toast({ title: i18n.t('k8s.editContainers.saved'), description: `${deploymentName} 更新已提交` });
onSaved(base);
} catch (e) {
toast({ title: i18n.t('kubernetes.msg.saveFailed'), description: e?.message || String(e), variant: 'destructive' });
@@ -152,7 +152,7 @@ export function DeploymentEditContainers({ clusterCode, namespace, deploymentNam
{imagePullSecrets.length === 0 && <span className="text-[11px] dg-text-muted">无</span>}
</div>
<div className="flex gap-2">
<Input placeholder={i18n.t('输入 Secret 名称后回车')} className="dg-input text-xs h-8"
<Input placeholder={i18n.t('k8s.editContainers.secretPlaceholder')} className="dg-input text-xs h-8"
onKeyDown={(e) => {
if (e.key === 'Enter') {
const val = (e.target as HTMLInputElement).value.trim();
@@ -170,7 +170,7 @@ export function DeploymentEditContainers({ clusterCode, namespace, deploymentNam
{/* 初始化容器 */}
<ContainerGroupSection
title={i18n.t('kubernetes.container.initContainers')}
description={i18n.t('在主容器启动前按顺序执行的容器,用于初始化环境')}
description={i18n.t('k8s.editContainers.initDesc')}
icon={<Play className="h-4 w-4 dg-text-purple" />}
containers={initContainers}
accentColor="purple"
@@ -181,8 +181,8 @@ export function DeploymentEditContainers({ clusterCode, namespace, deploymentNam
{/* 工作容器 */}
<ContainerGroupSection
title={i18n.t('初始化容器')}
description={i18n.t('在主容器启动前按顺序执行的容器,用于初始化环境')}
title={i18n.t('k8s.editContainers.initContainers')}
description={i18n.t('k8s.editContainers.initDesc')}
icon={<Container className="h-4 w-4 dg-text-blue" />}
containers={containers}
accentColor="blue"
@@ -172,7 +172,7 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName,
try {
const yaml = await dumpManifest(svcManifest);
await kubernetesApi.applyYAML(clusterCode, yaml);
toast({ title: i18n.t('Service 已创建'), description: name });
toast({ title: i18n.t('k8s.editService.serviceCreated'), description: name });
setShowCreate(null);
load();
} catch (e) {
@@ -209,7 +209,7 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName,
try {
const yaml = await dumpManifest(ingManifest);
await kubernetesApi.applyYAML(clusterCode, yaml);
toast({ title: i18n.t('Service 已创建'), description: name });
toast({ title: i18n.t('k8s.editService.serviceCreated'), description: name });
setShowCreate(null);
load();
} catch (e) {
@@ -221,7 +221,7 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName,
// 删除资源
const deleteResource = async (kind: 'service' | 'ingress', name: string) => {
const ok = await confirm({
title: i18n.t('删除资源'),
title: i18n.t('common.delete'),
description: `确认删除 ${kind === 'service' ? 'Service' : 'Ingress'} 「${name}」?此操作不可撤销。`,
confirmText: i18n.t('kubernetes.storage.delete2'),
variant: 'destructive',
@@ -40,12 +40,12 @@ interface Props {
}
const VOLUME_TYPES: { key: VolumeForm['type']; label: string; icon: typeof HardDrive; color: string }[] = [
{ key: 'persistentVolumeClaim', label: i18n.t('存储卷声明'), icon: Database, color: 'dg-text-green' },
{ key: 'emptyDir', label: i18n.t('临时目录(EmptyDir)'), icon: Disc, color: 'dg-text-secondary' },
{ key: 'persistentVolumeClaim', label: i18n.t('k8s.nsResource.pvcDesc'), icon: Database, color: 'dg-text-green' },
{ key: 'emptyDir', label: i18n.t('k8s.editStorage.emptyDir'), icon: Disc, color: 'dg-text-secondary' },
{ key: 'hostPath', label: 'HostPath', icon: HardDrive, color: 'dg-text-yellow' },
{ key: 'nfs', label: 'NFS', icon: Server, color: 'dg-text-purple' },
{ key: 'configMap', label: i18n.t('配置字典'), icon: File, color: 'dg-text-blue' },
{ key: 'secret', label: i18n.t('密文'), icon: Lock, color: 'text-rose-400' },
{ key: 'configMap', label: i18n.t('k8s.nsResource.configMapDesc'), icon: File, color: 'dg-text-blue' },
{ key: 'secret', label: i18n.t('k8s.secret.data'), icon: Lock, color: 'text-rose-400' },
];
const SELECTABLE_TYPES = new Set(VOLUME_TYPES.map((t) => t.key));
@@ -129,20 +129,20 @@ export function DeploymentEditStorage({ clusterCode, namespace, deploymentName,
const save = async () => {
for (const entry of entries) {
if (!entry.volume.name.trim()) {
toast({ title: i18n.t('请填写数据卷名称'), variant: 'destructive' });
toast({ title: i18n.t('k8s.editStorage.volumeNameRequired'), variant: 'destructive' });
return;
}
if (!formToVolume(entry.volume)) {
const hint = entry.volume.type === 'configMap' ? i18n.t('请选择 ConfigMap')
: entry.volume.type === 'secret' ? i18n.t('请选择 Secret')
: entry.volume.type === 'persistentVolumeClaim' ? i18n.t('请选择 PVC')
: i18n.t('的类型必填项');
toast({ title: i18n.t('数据卷配置不完整'), description: `请为「${entry.volume.name}」${hint}`, variant: 'destructive' });
const hint = entry.volume.type === 'configMap' ? i18n.t('k8s.editStorage.selectConfigMap')
: entry.volume.type === 'secret' ? i18n.t('k8s.editStorage.selectSecret')
: entry.volume.type === 'persistentVolumeClaim' ? i18n.t('k8s.editStorage.selectPVC')
: i18n.t('k8s.editStorage.typeRequiredHint');
toast({ title: i18n.t('k8s.editStorage.incompleteConfig'), description: `请为「${entry.volume.name}」${hint}`, variant: 'destructive' });
return;
}
const validMounts = entry.mounts.filter((m) => m.mountPath.trim());
if (validMounts.length === 0) {
toast({ title: i18n.t('请填写挂载路径'), description: `数据卷「${entry.volume.name}」需至少挂载到一个容器`, variant: 'destructive' });
toast({ title: i18n.t('k8s.editStorage.mountPathRequired'), description: `数据卷「${entry.volume.name}」需至少挂载到一个容器`, variant: 'destructive' });
return;
}
}
@@ -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('存储配置已保存'), description: `${deploymentName} 更新已提交` });
toast({ title: i18n.t('k8s.editStorage.saved'), description: `${deploymentName} 更新已提交` });
onSaved(base);
} catch (e) {
toast({ title: i18n.t('kubernetes.msg.saveFailed'), description: (e as Error)?.message || String(e), variant: 'destructive' });
@@ -234,7 +234,7 @@ export function DeploymentEditStorage({ clusterCode, namespace, deploymentName,
if (vol.type === 'persistentVolumeClaim' && !vol.pvcName) patch.pvcName = name;
updateVolume(i, patch);
}}
placeholder={i18n.t('如 app-logs / fluent-bit-config')}
placeholder={i18n.t('k8s.editStorage.namePlaceholder')}
className="h-8 w-44 dg-input text-sm font-mono"
/>
{/* 原生 select,避免 Radix Select 在 overflow 容器内无法下拉 // Logan.Li */}
@@ -312,7 +312,7 @@ export function DeploymentEditStorage({ clusterCode, namespace, deploymentName,
<Input
value={mount.mountPath}
onChange={(e) => updateMount(i, mi, { mountPath: e.target.value })}
placeholder={i18n.t('挂载路径 /bitcloud/app/.../logs')}
placeholder={i18n.t('k8s.editStorage.mountPathPlaceholder')}
className="h-8 flex-1 min-w-[200px] dg-input text-sm font-mono"
/>
{entry.mounts.length > 1 && (
@@ -326,7 +326,7 @@ export function DeploymentEditStorage({ clusterCode, namespace, deploymentName,
<Input
value={mount.subPath}
onChange={(e) => updateMount(i, mi, { subPath: e.target.value })}
placeholder={i18n.t('挂载路径 /bitcloud/app/.../logs')}
placeholder={i18n.t('k8s.editStorage.mountPathPlaceholder')}
className="h-7 flex-1 max-w-md dg-input text-[var(--dg-text-secondary)] text-xs font-mono"
/>
</div>
@@ -139,7 +139,7 @@ export function DeploymentView(props: DeploymentViewProps) {
const handleRollback = async (rev: JsonData) => {
const revision = rev.revision ?? rev.revision_number ?? rev.number;
const ok = await confirm({
title: i18n.t('回滚版本'),
title: i18n.t('k8s.deploymentView.rollbackVersion'),
description: `确认回滚到修订版本 #${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('回滚已下发'), description: `正在回滚到 #${revision}` });
toast({ title: i18n.t('k8s.deploymentView.rollbackSent'), description: `正在回滚到 #${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('伸缩已提交'), description: `副本数 → ${scaleValue}` });
toast({ title: i18n.t('k8s.deploymentView.scaleSent'), description: `副本数 → ${scaleValue}` });
onRefresh();
} catch (e) {
toast({ title: i18n.t('kubernetes.msg.scaleFailed'), description: e?.message || String(e), variant: 'destructive' });
@@ -168,10 +168,10 @@ export function DeploymentView(props: DeploymentViewProps) {
};
const TABS = [
{ key: 'pods', label: i18n.t('容器组'), icon: Container, count: pods.length },
{ key: 'services', label: i18n.t('访问方式'), icon: Network, count: services.length },
{ key: 'pods', label: i18n.t('k8s.deploymentView.pods'), icon: Container, count: pods.length },
{ key: 'services', label: i18n.t('k8s.deploymentView.access'), icon: Network, count: services.length },
{ key: 'events', label: i18n.t('kubernetes.event.title'), icon: Activity, count: events.length },
{ key: 'scale', label: i18n.t('容器伸缩'), icon: Plus },
{ key: 'scale', label: i18n.t('k8s.deploymentView.scale'), icon: Plus },
{ key: 'history', label: i18n.t('kubernetes.deployment.revisions'), icon: History, count: revisions.length },
];
@@ -186,8 +186,8 @@ export function DeploymentView(props: DeploymentViewProps) {
{isHealthy ? 'Running' : 'Updating'}
</Badge>
} />
<InfoItem label={i18n.t('状态')} value={<span className="font-mono">{ready}/{total}</span>} />
<InfoItem label={i18n.t('状态')} value={deployment?.strategy || 'RollingUpdate'} />
<InfoItem label={i18n.t('common.status')} value={<span className="font-mono">{ready}/{total}</span>} />
<InfoItem label={i18n.t('common.status')} value={deployment?.strategy || 'RollingUpdate'} />
{pods[0] && (
<>
<InfoItem label={i18n.t('kubernetes.pod.nodeName')} value={
@@ -118,7 +118,7 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
setRevisions(toArray(revRes));
} catch (e) {
console.error(i18n.t('加载详情失败:'), e);
console.error(i18n.t('k8s.deploymentWorkload.loadFailed'), e);
} finally {
setLoading(false);
}
@@ -133,7 +133,7 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
const { manifest: parsed } = await parseEditableManifest(yamlText);
setManifest(parsed || deployment);
} catch (e) {
console.warn(i18n.t('加载 manifest 失败(编辑功能降级):'), e);
console.warn(i18n.t('k8s.deploymentWorkload.manifestLoadFailed'), e);
setManifest(deployment);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -191,7 +191,7 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
setActionLoading('restart');
try {
await kubernetesApi.restartDeployment(clusterCode, namespace, deploymentName);
toast({ title: i18n.t('重启指令已下发'), description: i18n.t('Deployment 正在滚动重启') });
toast({ title: i18n.t('k8s.workloadView.restartSent'), description: i18n.t('k8s.deploymentWorkload.restarting') });
setTimeout(loadData, 1500);
} catch (e) {
toast({ title: i18n.t('kubernetes.deployment.restartFailed'), description: e?.message || String(e), variant: 'destructive' });
@@ -234,7 +234,7 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
await kubernetesApi.updateDeploymentImage(
clusterCode, namespace, deploymentName, newImage, cicdContainerName || undefined,
);
toast({ title: i18n.t('扩缩容成功'), description: i18n.t('k8s.deploymentDetail.rollingUpdateImage', { image: newImage }) });
toast({ title: i18n.t('k8s.deploymentWorkload.scaleSuccess'), description: i18n.t('k8s.deploymentDetail.rollingUpdateImage', { image: newImage }) });
setModal(null);
loadData();
} catch (e) {
@@ -288,7 +288,7 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
// ============ 渲染 // Logan.Li ============
if (loading) {
return (
<PageShell variant="workspace" title={deploymentName} subtitle={`${clusterCode} / ${namespace}`} loading loadingLabel={i18n.t('加载 Deployment...')} actions={<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={onBack}><ArrowLeft className="h-4 w-4 mr-1" /> {i18n.t('common.back')} </Button>}>
<PageShell variant="workspace" title={deploymentName} subtitle={`${clusterCode} / ${namespace}`} loading loadingLabel={i18n.t('k8s.deploymentWorkload.loading')} actions={<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={onBack}><ArrowLeft className="h-4 w-4 mr-1" /> {i18n.t('common.back')} </Button>}>
{null}
</PageShell>
);
@@ -308,7 +308,7 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
if (name) navigate(`/kubernetes/${clusterCode}/namespace/${namespace}/workload/view/Deployment/${name}`);
};
return (
<PageShell variant="workspace" title={i18n.t('新建 Deployment')} subtitle={`${clusterCode} / ${namespace}`} actions={<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={onBack}><ArrowLeft className="h-4 w-4 mr-1" /> {i18n.t('common.back')} </Button>}>
<PageShell variant="workspace" title={i18n.t('k8s.deploymentWorkload.create')} subtitle={`${clusterCode} / ${namespace}`} actions={<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={onBack}><ArrowLeft className="h-4 w-4 mr-1" /> {i18n.t('common.back')} </Button>}>
<EditModeContainer clusterCode={clusterCode} namespace={namespace} deploymentName={CREATE_DEPLOYMENT_SLUG} manifest={manifest} isCreate onSaved={handleCreated} onCancel={onBack} />
</PageShell>
);
@@ -379,7 +379,7 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
resource="deployments"
name={deploymentName}
inline
resourceHint={i18n.t('含关联 Service')}
resourceHint={i18n.t('k8s.deploymentWorkload.withService')}
loadYamlFn={loadBundleYaml}
saveYamlFn={saveBundleYaml}
onSaved={loadData}
@@ -404,7 +404,7 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
namespace={namespace}
resource="deployments"
name={deploymentName}
resourceHint={i18n.t('含关联 Service')}
resourceHint={i18n.t('k8s.deploymentWorkload.withService')}
loadYamlFn={loadBundleYaml}
saveYamlFn={saveBundleYaml}
onClose={() => setModal(null)}
@@ -179,16 +179,16 @@ export default function EventsPage() {
return (
<PageShell
variant="list"
title={i18n.t('事件中心')}
subtitle={clusterName ? `${clusterName} · 集群事件与告警` : i18n.t('集群事件与告警')}
title={i18n.t('k8s.event.center')}
subtitle={clusterName ? `${clusterName} · 集群事件与告警` : i18n.t('k8s.event.clusterAlerts')}
icon={<Bell className="h-5 w-5" />}
metrics={!loading ? (
<StatStrip
items={[
{ label: i18n.t('正常事件'), value: summary?.total ?? events.length, color: 'muted' },
{ label: i18n.t('告警事件'), value: summary?.warning_count ?? warningCount, color: 'amber' },
{ label: i18n.t('正常事件'), value: summary?.normal_count ?? normalCount, color: 'green' },
{ label: i18n.t('告警类型'), value: reasonData.length, color: 'red' },
{ label: i18n.t('k8s.event.normal'), value: summary?.total ?? events.length, color: 'muted' },
{ label: i18n.t('k8s.event.warning'), value: summary?.warning_count ?? warningCount, color: 'amber' },
{ label: i18n.t('k8s.event.normal'), value: summary?.normal_count ?? normalCount, color: 'green' },
{ label: i18n.t('k8s.event.warningType'), value: reasonData.length, color: 'red' },
]}
/>
) : undefined}
@@ -229,10 +229,10 @@ export default function EventsPage() {
>
{reasonData.length > 0 && (
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
<RankPanel title={i18n.t('资源类型')} items={reasonData} selected={selectedReason} onSelect={setSelectedReason} />
<RankPanel title={i18n.t('k8s.event.resourceType')} items={reasonData} selected={selectedReason} onSelect={setSelectedReason} />
{nsData.length > 0 && <RankPanel title={i18n.t('kubernetes.namespace.title')} items={nsData} />}
{kindData.length > 0 && (
<RankPanel title={i18n.t('告警原因')} items={kindData} selected={selectedKind} onSelect={setSelectedKind} />
<RankPanel title={i18n.t('k8s.event.warningReason')} items={kindData} selected={selectedKind} onSelect={setSelectedKind} />
)}
</div>
)}
@@ -89,7 +89,7 @@ export function KubernetesClusterDialog({
if (!value.trim()) {
setNameError('');
} else if (!NAME_RE.test(value)) {
setNameError(i18n.t('只允许英文、数字和连字符(-),首尾不能是连字符'));
setNameError(i18n.t('k8s.clusterDialog.nameRule'));
} else {
setNameError('');
}
@@ -111,7 +111,7 @@ export function KubernetesClusterDialog({
const handleSubmit = async () => {
if (!formData.name.trim() || !NAME_RE.test(formData.name.trim())) {
toast.error(i18n.t('请输入有效的集群名称(英文/数字/连字符)')); return;
toast.error(i18n.t('k8s.clusterDialog.nameInvalid')); return;
}
try {
setSubmitting(true);
@@ -133,7 +133,7 @@ export function KubernetesClusterDialog({
onOpenChange(false);
onSaved();
} catch (e) {
console.error(i18n.t('保存集群失败:'), e);
console.error(i18n.t('k8s.clusterDialog.saveFailed'), e);
} finally {
setSubmitting(false);
}
@@ -149,7 +149,7 @@ export function KubernetesClusterDialog({
{editingCluster ? i18n.t('kubernetes.cluster.editCluster') : i18n.t('kubernetes.cluster.addCluster')}
</DialogTitle>
<DialogDescription className="text-[var(--dg-text-secondary)]">
{editingCluster ? i18n.t('编辑集群') : i18n.t('添加集群')}
{editingCluster ? i18n.t('k8s.clusterDialog.editTitle') : i18n.t('k8s.clusterDialog.addTitle')}
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-2 max-h-[60vh] overflow-y-auto pr-1">
@@ -159,7 +159,7 @@ export function KubernetesClusterDialog({
<Input
value={formData.name}
onChange={(e) => handleNameChange(e.target.value)}
placeholder={i18n.t('如: prod-cluster')}
placeholder={i18n.t('k8s.clusterDialog.namePlaceholder')}
className={`bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)] ${nameError ? 'border-red-500' : ''}`}
/>
{nameError && <p className="text-xs dg-text-red">{nameError}</p>}
@@ -172,7 +172,7 @@ export function KubernetesClusterDialog({
<Input
value={formData.version}
onChange={(e) => setFormData({ ...formData, version: e.target.value })}
placeholder={i18n.t('如: prod-cluster')}
placeholder={i18n.t('k8s.clusterDialog.namePlaceholder')}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)]"
/>
</div>
@@ -183,7 +183,7 @@ export function KubernetesClusterDialog({
<Input
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
placeholder={i18n.t('集群用途描述')}
placeholder={i18n.t('k8s.clusterDialog.descLabel')}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)]"
/>
</div>
@@ -224,7 +224,7 @@ export function KubernetesClusterDialog({
<Textarea
value={formData.token}
onChange={(e) => setFormData({ ...formData, token: e.target.value })}
placeholder={hasToken ? i18n.t('已配置,输入新值覆盖...') : i18n.t('粘贴 Service Account Token...')}
placeholder={hasToken ? i18n.t('k8s.clusterDialog.tokenConfigured') : i18n.t('k8s.clusterDialog.tokenPlaceholder')}
rows={4}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)] font-mono text-xs"
/>
@@ -237,7 +237,7 @@ export function KubernetesClusterDialog({
<Textarea
value={formData.kube_config}
onChange={(e) => handleKubeConfigChange(e.target.value)}
placeholder={hasKubeConfig ? i18n.t('已配置,输入新值覆盖...') : 'apiVersion: v1\nkind: Config\nclusters:\n- cluster:\n server: https://10.0.0.1:6443\n ...'}
placeholder={hasKubeConfig ? i18n.t('k8s.clusterDialog.tokenConfigured') : 'apiVersion: v1\nkind: Config\nclusters:\n- cluster:\n server: https://10.0.0.1:6443\n ...'}
rows={10}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)] font-mono text-xs"
/>
@@ -72,7 +72,7 @@ export function KubernetesNodes() {
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"> {i18n.t('选择集群')} </SelectItem>
<SelectItem value="all"> {i18n.t('k8s.nodes.selectCluster')} </SelectItem>
<SelectItem value="master">Master</SelectItem>
<SelectItem value="worker">Worker</SelectItem>
</SelectContent>
@@ -86,7 +86,7 @@ export function KubernetesNodes() {
<PageShell
variant="list"
title={i18n.t('menu.nodeManagement')}
subtitle={clusterLabel ? `${clusterLabel} · 工作节点状态与资源` : i18n.t('Kubernetes 集群工作节点状态与资源')}
subtitle={clusterLabel ? `${clusterLabel} · 工作节点状态与资源` : i18n.t('k8s.nodes.subtitle')}
icon={<Server className="h-5 w-5" />}
actions={(
<>
@@ -125,7 +125,7 @@ export function KubernetesNodes() {
{loading ? (
<TableRow><TableCell colSpan={8} className="text-center text-[var(--dg-text-muted)] py-12"><Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" /> {i18n.t('monitor.logPanel.logResults.loading')} </TableCell></TableRow>
) : filtered.length === 0 ? (
<TableRow><TableCell colSpan={8} className="text-center text-[var(--dg-text-muted)] py-12"><Server className="h-8 w-8 mx-auto mb-2 opacity-50" />{selectedCluster ? i18n.t('k8s.empty.nodes') : i18n.t('暂无节点数据')}</TableCell></TableRow>
<TableRow><TableCell colSpan={8} className="text-center text-[var(--dg-text-muted)] py-12"><Server className="h-8 w-8 mx-auto mb-2 opacity-50" />{selectedCluster ? i18n.t('k8s.empty.nodes') : i18n.t('k8s.nodes.noData')}</TableCell></TableRow>
) : (
filtered.map((n) => (
<TableRow key={n.id} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
@@ -112,7 +112,7 @@ export default function NamespaceManagementPage() {
<Input
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder={i18n.t('搜索命名空间...')}
placeholder={i18n.t('k8s.nsMgmt.searchPlaceholder')}
className="max-w-xs dg-input"
/>
);
@@ -120,7 +120,7 @@ export default function NamespaceManagementPage() {
return (
<PageShell
variant="list"
title={i18n.t('命名空间管理')}
title={i18n.t('k8s.nsMgmt.title')}
subtitle={`${clusterCode} · 资源配额与生命周期管理`}
icon={<Boxes className="h-5 w-5" />}
metrics={!loading ? (
@@ -130,7 +130,7 @@ export default function NamespaceManagementPage() {
{ label: 'Deployments', value: totalDeploys, color: 'blue' },
{ label: 'Pods', value: totalPods, color: 'purple' },
{ label: 'Services', value: totalSvcs, color: 'cyan' },
{ label: i18n.t('配额限制'), value: quotaCount, color: 'amber' },
{ label: i18n.t('k8s.nsMgmt.quotaLimit'), value: quotaCount, color: 'amber' },
]}
/>
) : undefined}
@@ -148,7 +148,7 @@ export default function NamespaceManagementPage() {
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle> {i18n.t('例如: my-app')} </DialogTitle>
<DialogTitle> {i18n.t('k8s.nsMgmt.nameExample')} </DialogTitle>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
@@ -156,7 +156,7 @@ export default function NamespaceManagementPage() {
<Input
value={newName}
onChange={(e) => setNewName(e.target.value)}
placeholder={i18n.t('例如: my-app')}
placeholder={i18n.t('k8s.nsMgmt.nameExample')}
onKeyDown={(e) => e.key === 'Enter' && handleCreate()}
/>
<p className="text-xs text-[var(--dg-text-muted)]">
@@ -19,14 +19,14 @@ import type { JsonData } from '@/types/common';
import i18n from '@/i18n';
const RESOURCE_LINKS = [
{ type: 'deployments', label: 'Deployments', icon: Boxes, desc: i18n.t('工作负载副本管理') },
{ type: 'pods', label: 'Pods', icon: Container, desc: i18n.t('容器实例运行状态') },
{ type: 'cronjobs', label: 'CronJobs', icon: Clock, desc: i18n.t('定时任务管理') },
{ type: 'services', label: 'Services', icon: Network, desc: i18n.t('服务发现与负载均衡') },
{ type: 'ingresses', label: 'Ingresses', icon: Network, desc: i18n.t('外部流量路由规则') },
{ type: 'configmaps', label: 'ConfigMaps', icon: FileText, desc: i18n.t('配置字典') },
{ type: 'secrets', label: 'Secrets', icon: Shield, desc: i18n.t('密钥与凭证') },
{ type: 'hpas', label: 'HPAs', icon: Activity, desc: i18n.t('水平自动伸缩策略') },
{ type: 'deployments', label: 'Deployments', icon: Boxes, desc: i18n.t('k8s.nsOverview.workloadReplicas') },
{ type: 'pods', label: 'Pods', icon: Container, desc: i18n.t('k8s.nsOverview.podStatus') },
{ type: 'cronjobs', label: 'CronJobs', icon: Clock, desc: i18n.t('k8s.nsOverview.cronjobManage') },
{ type: 'services', label: 'Services', icon: Network, desc: i18n.t('k8s.nsOverview.serviceDiscovery') },
{ type: 'ingresses', label: 'Ingresses', icon: Network, desc: i18n.t('k8s.nsOverview.externalRoute') },
{ type: 'configmaps', label: 'ConfigMaps', icon: FileText, desc: i18n.t('k8s.nsResource.configMapDesc') },
{ type: 'secrets', label: 'Secrets', icon: Shield, desc: i18n.t('k8s.nsOverview.secrets') },
{ type: 'hpas', label: 'HPAs', icon: Activity, desc: i18n.t('k8s.nsOverview.hpaStrategy') },
];
export default function NamespaceOverviewPage() {
@@ -71,7 +71,7 @@ export default function NamespaceOverviewPage() {
subtitle={`${clusterCode} · 命名空间概览`}
icon={<Cloud className="h-5 w-5" />}
loading={loading}
loadingLabel={i18n.t('加载资源统计...')}
loadingLabel={i18n.t('k8s.nsOverview.loadingStats')}
>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{RESOURCE_LINKS.map((r) => (
@@ -204,16 +204,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={i18n.t('导出CSV')}>
<Button variant="outline" size="sm" className="dg-btn-secondary text-xs" onClick={handleExportCSV} title={i18n.t('k8s.nsResourcePage.exportCsv')}>
<Download className="h-3 w-3 mr-1" />CSV
</Button>
<Button variant="outline" size="sm" className="dg-btn-secondary text-xs" onClick={handleExportJSON} title={i18n.t('导出JSON')}>
<Button variant="outline" size="sm" className="dg-btn-secondary text-xs" onClick={handleExportJSON} title={i18n.t('k8s.nsResourcePage.exportJson')}>
<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={i18n.t('导出命名空间 YAML(tar.gz)')}>
<Button variant="outline" size="sm" className="dg-btn-secondary text-xs" onClick={handleExportYaml} title={i18n.t('k8s.nsResourcePage.exportYaml')}>
<Download className="h-3 w-3 mr-1" />导出YAML
</Button>
)}
@@ -68,7 +68,7 @@ export function PodFileBrowser({
setEntries(list);
setCurrentPath(path);
} catch (e) {
setError((e as JsonData).message || i18n.t('加载文件列表失败'));
setError((e as JsonData).message || i18n.t('k8s.podFile.loadFailed'));
setEntries([]);
} finally {
setLoading(false);
@@ -93,7 +93,7 @@ export default function PodMetricsPage() {
metrics={!loading ? (
<StatStrip
items={[
{ label: i18n.t('Pod 数'), value: metrics.length, color: 'muted' },
{ label: i18n.t('k8s.clusterOverview.podCount'), value: metrics.length, color: 'muted' },
{ label: i18n.t('k8s.podMetrics.cpu'), value: `${totalCPU}m`, color: 'blue' },
{ label: i18n.t('k8s.podMetrics.memory'), value: `${totalMem.toFixed(0)}Mi`, color: 'green' },
]}
@@ -68,7 +68,7 @@ export function ResourceVisualEditor({ clusterCode, namespace, resource, name, o
setManifest(parsed);
if (!parsed) {
setMode('yaml');
toast({ title: i18n.t('可视化编辑不可用'), description: i18n.t('无法解析资源结构,已切换到 YAML 编辑模式') });
toast({ title: i18n.t('k8s.rve.visualUnavailable'), description: i18n.t('k8s.rve.fallbackToYaml') });
}
} catch (e) {
toast({ title: i18n.t('kubernetes.msg.loadFailed'), description: e instanceof Error ? e.message : String(e), variant: 'destructive' });
@@ -107,7 +107,7 @@ export function ResourceVisualEditor({ clusterCode, namespace, resource, name, o
const yamlStr = await prepareYamlForSave(editYaml);
if (isCreate) {
await kubernetesApi.applyYAML(clusterCode, yamlStr);
toast({ title: i18n.t('monitor.dashboard.custom.toastCreateSuccess'), description: i18n.t('保存失败') });
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} 已更新` });
@@ -127,7 +127,7 @@ export function ResourceVisualEditor({ clusterCode, namespace, resource, name, o
const switchToVisual = async () => {
const parsed = await parseManifest(editYaml);
if (!parsed) {
toast({ title: i18n.t('无法解析 YAML'), description: i18n.t('请检查语法后继续用 YAML 模式'), variant: 'destructive' });
toast({ title: i18n.t('k8s.rve.parseYamlFailed'), description: i18n.t('k8s.rve.checkSyntax'), variant: 'destructive' });
return;
}
setManifest(parsed);
@@ -156,7 +156,7 @@ export function ResourceVisualEditor({ clusterCode, namespace, resource, name, o
const downloadYamlContent = () => {
const fname = yamlDownloadFilename(resource, namespace, name || manifest?.metadata?.name || 'resource');
downloadYamlFile(fname, currentYamlText());
toast({ title: i18n.t('已下载'), description: fname });
toast({ title: i18n.t('k8s.rve.downloaded'), description: fname });
};
const resCfg = getYamlResourceConfig(resource);
@@ -262,7 +262,7 @@ export function ResourceVisualEditor({ clusterCode, namespace, resource, name, o
{!['configmaps', 'secrets', 'services', 'ingresses', 'pvcs'].includes(resource) && (
<div className="flex-1 flex items-center justify-center text-center text-[var(--dg-text-muted)]">
<div>
<p> {i18n.t('域名 (如 api.example.com)')} </p>
<p> {i18n.t('k8s.rve.hostPlaceholder')} </p>
<Button variant="outline" size="sm" className="mt-4" onClick={() => setMode('yaml')}> {i18n.t('kubernetes.resourcevisuale.s54c78d4f')} </Button>
</div>
</div>
@@ -270,7 +270,7 @@ export function ResourceVisualEditor({ clusterCode, namespace, resource, name, o
</div>
) : (
<div className="text-center py-12 text-[var(--dg-text-muted)]">
<p> {i18n.t('键名 (如 LOG_LEVEL)')} </p>
<p> {i18n.t('k8s.rve.keyPlaceholder')} </p>
<Button variant="outline" size="sm" className="mt-4" onClick={() => setMode('yaml')}> {i18n.t('kubernetes.resourcevisuale.s54c78d4f')} </Button>
</div>
)}
@@ -107,7 +107,7 @@ export default function WorkloadViewPage() {
} else {
await kubernetesApi.restartDaemonSet(clusterCode, namespace, name);
}
toast({ title: i18n.t('重启指令已下发'), description: `${kind} 滚动重启已触发` });
toast({ title: i18n.t('k8s.workloadView.restartSent'), description: `${kind} 滚动重启已触发` });
setTimeout(loadData, 1000);
} catch (e) {
toast({ title: i18n.t('kubernetes.deployment.restartFailed'), description: e instanceof Error ? e.message : String(e), variant: 'destructive' });
@@ -122,7 +122,7 @@ export default function WorkloadViewPage() {
setScaleModal(false);
setTimeout(loadData, 1000);
} catch (e) {
toast({ title: i18n.t('重启失败'), description: e instanceof Error ? e.message : String(e), variant: 'destructive' });
toast({ title: i18n.t('k8s.workloadView.restartFailed'), description: e instanceof Error ? e.message : String(e), variant: 'destructive' });
} finally { setActionLoading(false); }
};
@@ -149,7 +149,7 @@ export default function WorkloadViewPage() {
subtitle={`${clusterCode} / ${namespace} · ${kind}`}
icon={<Boxes className="h-5 w-5" />}
loading={loading}
loadingLabel={i18n.t('加载工作负载...')}
loadingLabel={i18n.t('k8s.workloadView.loading')}
actions={(
<>
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={() => navigate(-1)}>
@@ -162,7 +162,7 @@ export default function WorkloadViewPage() {
</Button>
)}
<Button variant="outline" size="sm" className="dg-btn-secondary" disabled={actionLoading} onClick={handleRestart}>
<RotateCcw className="h-3.5 w-3.5 mr-1" />{actionLoading ? i18n.t('重启中...') : i18n.t('agent.agentServiceRestart')}
<RotateCcw className="h-3.5 w-3.5 mr-1" />{actionLoading ? i18n.t('k8s.workloadView.restarting') : i18n.t('agent.agentServiceRestart')}
</Button>
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={() => setYamlModal(true)}>
<FileCode className="h-3.5 w-3.5 mr-1" />YAML
@@ -201,14 +201,14 @@ export default function WorkloadViewPage() {
<StatCard label={i18n.t('kubernetes.workloadviewpag.sea639697')} value={`${readyReplicas}/${replicas}`} color={isHealthy ? STATUS_COLORS.green : STATUS_COLORS.orange} sub={isHealthy ? i18n.t('kubernetes.workloadviewpag.s23c944c1') : i18n.t('kubernetes.workloadviewpag.sac100213')} />
<StatCard label={i18n.t('kubernetes.hpa.currentReplicas')} value={currentReplicas} color={STATUS_COLORS.blue} />
<StatCard label={i18n.t('kubernetes.deployment.updatedReplicas')} value={updatedReplicas} color={STATUS_COLORS.green} />
<StatCard label={i18n.t('当前副本')} value={stats.spec.serviceName || '-'} color="var(--dg-text-secondary)" />
<StatCard label={i18n.t('当前副本')} value={pods.length} color="var(--dg-text-secondary)" />
<StatCard label={i18n.t('k8s.workloadView.currentReplicas')} value={stats.spec.serviceName || '-'} color="var(--dg-text-secondary)" />
<StatCard label={i18n.t('k8s.workloadView.currentReplicas')} value={pods.length} color="var(--dg-text-secondary)" />
<StatCard label={i18n.t('system.userCreatedAt')} value={<span className="text-xs">{formatK8sTime(manifest?.metadata?.creationTimestamp)}</span>} color="var(--dg-text-secondary)" />
</>
) : (
<>
<StatCard label={i18n.t('期望调度')} value={desiredScheduled} color={STATUS_COLORS.blue} />
<StatCard label={i18n.t('当前调度')} value={currentScheduled} color="var(--dg-text-secondary)" />
<StatCard label={i18n.t('k8s.workloadView.desiredSchedule')} value={desiredScheduled} color={STATUS_COLORS.blue} />
<StatCard label={i18n.t('k8s.workloadView.actualSchedule')} value={currentScheduled} color="var(--dg-text-secondary)" />
<StatCard label={i18n.t('kubernetes.daemonset.numberReady')} value={numberReady} color={isHealthy ? STATUS_COLORS.green : STATUS_COLORS.orange} />
<StatCard label={i18n.t('kubernetes.daemonset.numberAvailable')} value={numberAvailable} color={STATUS_COLORS.green} />
<StatCard label={i18n.t('deploy.toast.updated')} value={numberUpdated} color="var(--dg-text-secondary)" />
@@ -74,7 +74,7 @@ export function YamlViewer({
setRawYaml(text);
await applyStripped(text);
} catch (e) {
toast({ title: i18n.t('获取YAML失败'), description: (e as JsonData).message, variant: 'destructive' });
toast({ title: i18n.t('k8s.yamlViewer.fetchFailed'), description: (e as JsonData).message, variant: 'destructive' });
const err = `# Error: ${(e as Error).message}`;
setRawYaml(err);
setDisplayYaml(err);
@@ -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('保存失败') : `${resource}/${name} 已更新` });
toast({ title: i18n.t('kubernetes.msg.saveSuccess'), description: saveYamlFn ? i18n.t('k8s.rve.saveFailed') : `${resource}/${name} 已更新` });
setEditing(false);
setRawYaml(yamlStr);
setDisplayYaml(yamlStr);
@@ -125,7 +125,7 @@ export function YamlViewer({
const downloadYaml = () => {
downloadYamlFile(yamlDownloadFilename(resource, namespace, name), currentYaml());
toast({ title: i18n.t('已下载'), description: yamlDownloadFilename(resource, namespace, name) });
toast({ title: i18n.t('k8s.rve.downloaded'), description: yamlDownloadFilename(resource, namespace, name) });
};
const display = editing ? editYaml : (showRaw ? rawYaml : displayYaml);
@@ -86,7 +86,7 @@ export async function saveDeploymentBundleYaml(
for (const manifest of manifests) {
const kind = String(manifest.kind || '').toLowerCase();
const name = String(manifest.metadata?.name || '').trim();
if (!kind || !name) throw new Error(i18n.t('每个 YAML 文档必须包含 kind 和 metadata.name'));
if (!kind || !name) throw new Error(i18n.t('k8s.misc.yamlDocError'));
const docYaml = await prepareManifestForSave(manifest);
if (kind === 'deployment') {
@@ -108,7 +108,7 @@ export function CreateHPADialog({ clusterCode, namespace, deploymentName, onCrea
<div className="flex justify-end gap-2 px-6 py-4 border-t border-[var(--dg-border)] shrink-0">
<Button variant="outline" className="border-[var(--dg-border)] dg-text-secondary" onClick={() => setOpen(false)}>取消</Button>
<Button className="bg-blue-600 hover:bg-blue-700" disabled={loading} onClick={handleCreate}>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : i18n.t('创建')}
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : i18n.t('common.create')}
</Button>
</div>
</div>
@@ -52,8 +52,8 @@ export const RESOURCE_CONFIG: Record<ResourceType, {
renderRow: (item: any, opts: ResourceRowOpts) => React.ReactNode;
}> = {
deployments: {
title: 'Deployments', subtitle: i18n.t('Deployment 工作负载运行状态'), icon: Boxes,
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('名称'), i18n.t('kubernetes.container.image')],
title: 'Deployments', subtitle: i18n.t('k8s.nsResource.deployDesc'), icon: Boxes,
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('common.name'), i18n.t('kubernetes.container.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>
@@ -77,7 +77,7 @@ export const RESOURCE_CONFIG: Record<ResourceType, {
),
},
pods: {
title: 'Pods', subtitle: i18n.t('Pod 实例运行状态'), icon: Container,
title: 'Pods', subtitle: i18n.t('k8s.nsResource.podDesc'), icon: Container,
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('system.terminalStatus'), i18n.t('kubernetes.node.title'), 'CPU', i18n.t('kubernetes.pod.memory'), i18n.t('agent.agentServiceRestart'), i18n.t('kubernetes.container.image'), i18n.t('system.auditAction')],
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' };
@@ -113,7 +113,7 @@ export const RESOURCE_CONFIG: Record<ResourceType, {
},
},
services: {
title: 'Services', subtitle: i18n.t('Service 网络服务'), icon: Network,
title: 'Services', subtitle: i18n.t('k8s.nsResource.serviceDesc'), icon: Network,
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('system.credentialType'), 'Cluster IP', i18n.t('system.ldapPort'), i18n.t('system.auditAction')],
renderRow: (s, opts) => (
<TableRow key={s.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
@@ -127,7 +127,7 @@ export const RESOURCE_CONFIG: Record<ResourceType, {
),
},
ingresses: {
title: 'Ingresses', subtitle: i18n.t('Ingress 路由规则'), icon: Network,
title: 'Ingresses', subtitle: i18n.t('k8s.nsResource.ingressDesc'), icon: Network,
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), 'Hosts', 'TLS', i18n.t('system.auditAction')],
renderRow: (i, opts) => {
const hosts = i.hosts || [];
@@ -143,8 +143,8 @@ export const RESOURCE_CONFIG: Record<ResourceType, {
},
},
configmaps: {
title: 'ConfigMaps', subtitle: i18n.t('配置字典'), icon: FileText,
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('名称'), i18n.t('system.auditAction')],
title: 'ConfigMaps', subtitle: i18n.t('k8s.nsResource.configMapDesc'), icon: FileText,
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('common.name'), i18n.t('system.auditAction')],
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>
@@ -155,7 +155,7 @@ export const RESOURCE_CONFIG: Record<ResourceType, {
),
},
secrets: {
title: 'Secrets', subtitle: i18n.t('密钥管理(仅名称与类型)'), icon: Shield,
title: 'Secrets', subtitle: i18n.t('k8s.nsResource.secretDesc'), icon: Shield,
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('system.credentialType'), i18n.t('system.auditAction')],
renderRow: (s, opts) => (
<TableRow key={s.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
@@ -167,7 +167,7 @@ export const RESOURCE_CONFIG: Record<ResourceType, {
),
},
hpas: {
title: 'HPAs', subtitle: i18n.t('水平自动伸缩'), icon: Activity,
title: 'HPAs', subtitle: i18n.t('k8s.nsResource.hpaDesc'), icon: Activity,
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('monitor.alertEvents.col.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">
@@ -194,7 +194,7 @@ export const RESOURCE_CONFIG: Record<ResourceType, {
),
},
pvcs: {
title: i18n.t('存储卷声明'), subtitle: 'PersistentVolumeClaim', icon: Boxes,
title: i18n.t('k8s.nsResource.pvcDesc'), subtitle: 'PersistentVolumeClaim', icon: Boxes,
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('system.terminalStatus'), i18n.t('kubernetes.storage.pvcSize'), 'StorageClass', i18n.t('system.auditAction')],
renderRow: (p, opts) => (
<TableRow key={p.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
@@ -208,8 +208,8 @@ export const RESOURCE_CONFIG: Record<ResourceType, {
),
},
statefulsets: {
title: 'StatefulSets', subtitle: i18n.t('有状态应用'), icon: Boxes,
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('名称'), i18n.t('kubernetes.pod.ready'), i18n.t('kubernetes.container.image')],
title: 'StatefulSets', subtitle: i18n.t('k8s.nsResource.stsDesc'), icon: Boxes,
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('common.name'), i18n.t('kubernetes.pod.ready'), i18n.t('kubernetes.container.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>
@@ -221,7 +221,7 @@ export const RESOURCE_CONFIG: Record<ResourceType, {
),
},
daemonsets: {
title: 'DaemonSets', subtitle: i18n.t('守护进程集'), icon: Boxes,
title: 'DaemonSets', subtitle: i18n.t('k8s.nsResource.dsDesc'), icon: Boxes,
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('monitor.siteProtocol.http.expectedPrefix'), i18n.t('cloud.current'), i18n.t('kubernetes.pod.ready'), i18n.t('kubernetes.container.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)}>
@@ -235,8 +235,8 @@ export const RESOURCE_CONFIG: Record<ResourceType, {
),
},
serviceaccounts: {
title: 'ServiceAccounts', subtitle: i18n.t('服务账号(RBAC 身份标识)'), icon: Shield,
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('名称'), i18n.t('命名空间'), i18n.t('system.userCreatedAt'), i18n.t('system.auditAction')],
title: 'ServiceAccounts', subtitle: i18n.t('k8s.nsResource.saDesc'), icon: Shield,
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('common.name'), i18n.t('common.namespace'), i18n.t('system.userCreatedAt'), i18n.t('system.auditAction')],
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>
@@ -247,15 +247,15 @@ export 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={i18n.t('查看YAML')}
<Button variant="ghost" size="icon" className="h-7 w-7 text-[var(--dg-text-secondary)] hover:text-[var(--dg-blue)]" title={i18n.t('k8s.nsResource.viewYaml')}
onClick={() => opts.onYaml?.(sa.name)}><FileCode className="h-3.5 w-3.5" /></Button>
</TableCell>
</TableRow>
),
},
roles: {
title: 'Roles', subtitle: i18n.t('命名空间级角色权限(RBAC)'), icon: Shield,
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('名称'), i18n.t('system.apiKeyScope'), i18n.t('system.userCreatedAt'), i18n.t('system.auditAction')],
title: 'Roles', subtitle: i18n.t('k8s.nsResource.roleDesc'), icon: Shield,
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('common.name'), i18n.t('system.apiKeyScope'), i18n.t('system.userCreatedAt'), i18n.t('system.auditAction')],
renderRow: (r, opts) => {
const verbs = r.verbs || [];
const resources = r.resources || [];
@@ -275,7 +275,7 @@ export 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={i18n.t('查看YAML')}
<Button variant="ghost" size="icon" className="h-7 w-7 text-[var(--dg-text-secondary)] hover:text-[var(--dg-blue)]" title={i18n.t('k8s.nsResource.viewYaml')}
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={i18n.t('common.edit')}
onClick={() => opts.onEdit?.(r.name)}><Pencil className="h-3.5 w-3.5" /></Button>
@@ -286,8 +286,8 @@ export const RESOURCE_CONFIG: Record<ResourceType, {
},
},
rolebindings: {
title: 'RoleBindings', subtitle: i18n.t('角色绑定关系(RBAC 授权)'), icon: Shield,
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('名称'), i18n.t('命名空间'), i18n.t('system.userCreatedAt'), i18n.t('system.auditAction')],
title: 'RoleBindings', subtitle: i18n.t('k88s.nsResource.rbDesc'), icon: Shield,
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('common.name'), i18n.t('common.namespace'), i18n.t('system.userCreatedAt'), i18n.t('system.auditAction')],
renderRow: (rb, opts) => {
const subjects = rb.subjects || [];
const subjStr = subjects.length > 3 ? subjects.slice(0, 3).join(', ') + ` +${subjects.length - 3}` : subjects.join(', ');
@@ -306,7 +306,7 @@ export 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={i18n.t('查看YAML')}
<Button variant="ghost" size="icon" className="h-7 w-7 text-[var(--dg-text-secondary)] hover:text-[var(--dg-blue)]" title={i18n.t('k8s.nsResource.viewYaml')}
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={i18n.t('common.edit')}
onClick={() => opts.onEdit?.(rb.name)}><Pencil className="h-3.5 w-3.5" /></Button>
@@ -85,7 +85,7 @@ export function ControlBar(props: ControlBarProps) {
size="sm"
className={cn('h-8 text-xs', previous ? 'bg-amber-600 hover:bg-amber-700' : 'border-[var(--dg-border)] text-[var(--dg-text-secondary)]')}
onClick={onPreviousToggle}
title={i18n.t('查看上一次(崩溃)容器的日志')}
title={i18n.t('k8s.podLogs.previousLogs')}
>
<History className="h-3.5 w-3.5 mr-1" />
previous
@@ -102,7 +102,7 @@ export function ControlBar(props: ControlBarProps) {
onSinceTimeChange(v ? new Date(v).toISOString() : '');
}}
className="h-8 w-[170px] text-xs bg-[var(--dg-bg-hover)] border-[var(--dg-border)] text-[var(--dg-text-primary)]"
title={i18n.t('查看上一次(崩溃)容器的日志')}
title={i18n.t('k8s.podLogs.previousLogs')}
/>
</div>
@@ -114,7 +114,7 @@ export function ControlBar(props: ControlBarProps) {
<Input
value={keyword}
onChange={(e) => onKeywordChange(e.target.value)}
placeholder={i18n.t('关键字过滤…')}
placeholder={i18n.t('k8s.podLogs.keywordFilter')}
className="h-8 w-[160px] pl-7 text-xs bg-[var(--dg-bg-hover)] border-[var(--dg-border)] text-[var(--dg-text-primary)]"
/>
</div>
@@ -124,7 +124,7 @@ export function ControlBar(props: ControlBarProps) {
variant="outline" size="sm"
className="h-8 text-xs border-[var(--dg-border)] text-[var(--dg-text-secondary)]"
onClick={onDownload}
title={i18n.t('查看上一次(崩溃)容器的日志')}
title={i18n.t('k8s.podLogs.previousLogs')}
disabled={filteredLineCount === 0}
>
<Download className="h-3.5 w-3.5 mr-1" />导出
@@ -136,7 +136,7 @@ export function ControlBar(props: ControlBarProps) {
className="h-8 text-xs border-[var(--dg-border)] text-[var(--dg-text-secondary)]"
onClick={onRefresh}
disabled={loading}
title={i18n.t('查看上一次(崩溃)容器的日志')}
title={i18n.t('k8s.podLogs.previousLogs')}
>
<RefreshCw className={cn('h-3.5 w-3.5 mr-1', loading && 'animate-spin')} />刷新
</Button>
@@ -148,7 +148,7 @@ export function ControlBar(props: ControlBarProps) {
className="h-8 text-xs bg-emerald-600 hover:bg-emerald-700"
onClick={onStartStream}
disabled={loading}
title={i18n.t('查看上一次(崩溃)容器的日志')}
title={i18n.t('k8s.podLogs.previousLogs')}
>
<Play className="h-3.5 w-3.5 mr-1" />实时
</Button>
@@ -49,10 +49,10 @@ export function ConfigMapEditor({ manifest, onSave, saving, isCreate }: ConfigMa
<div className="flex-1 min-h-0 flex flex-col mt-3">
<div className="flex items-center justify-between mb-2 shrink-0">
<Label className="text-[var(--dg-text-secondary)] text-sm"> {i18n.t('加载中...')} </Label>
<Label className="text-[var(--dg-text-secondary)] text-sm"> {i18n.t('common.loading')} </Label>
<div className="flex gap-1">
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={() => setExpandAll(!expandAll)}>
{expandAll ? i18n.t('折叠全部') : i18n.t('system.expandAll')}
{expandAll ? i18n.t('k8s.configMap.collapseAll') : i18n.t('system.expandAll')}
</Button>
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={addRow}><Plus className="h-3 w-3 mr-1" /> {i18n.t('monitor.dashboard.custom.addBtn')} </Button>
</div>
@@ -63,13 +63,13 @@ export function ConfigMapEditor({ manifest, onSave, saving, isCreate }: ConfigMa
<Input
value={entry.key}
onChange={e => update(i, 'key', e.target.value)}
placeholder={i18n.t('键名 (如 LOG_LEVEL)')}
placeholder={i18n.t('k8s.rve.keyPlaceholder')}
className="flex-1 font-mono text-xs h-8 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]"
/>
<Textarea
value={entry.value}
onChange={e => update(i, 'value', e.target.value)}
placeholder={i18n.t('键名 (如 LOG_LEVEL)')}
placeholder={i18n.t('k8s.rve.keyPlaceholder')}
rows={expandAll || entry.value.length > 300 ? 10 : (entry.value.length > 100 ? 6 : 3)}
className="flex-[2] font-mono text-xs min-h-[70px] max-h-[420px] resize-y bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] leading-relaxed"
/>
@@ -79,7 +79,7 @@ export function ConfigMapEditor({ manifest, onSave, saving, isCreate }: ConfigMa
</div>
))}
{entries.length === 0 && (
<div className="text-center py-8 text-[var(--dg-text-muted)] text-xs"> {i18n.t('名称')} </div>
<div className="text-center py-8 text-[var(--dg-text-muted)] text-xs"> {i18n.t('common.name')} </div>
)}
</div>
</div>
@@ -121,7 +121,7 @@ export function IngressEditor({ manifest, clusterCode, onSave, saving, isCreate
}}
>
<SelectTrigger className="h-8 text-xs bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]">
<SelectValue placeholder={loadingClasses ? i18n.t('monitor.logPanel.logResults.loading') : i18n.t('加载中...')} />
<SelectValue placeholder={loadingClasses ? i18n.t('monitor.logPanel.logResults.loading') : i18n.t('common.loading')} />
</SelectTrigger>
<SelectContent>
{ingressClasses.length > 0 ? (
@@ -131,7 +131,7 @@ export function IngressEditor({ manifest, clusterCode, onSave, saving, isCreate
</SelectItem>
))
) : (
!loadingClasses && <div className="px-2 py-1.5 text-xs text-[var(--dg-text-muted)]"> {i18n.t('加载中...')} </div>
!loadingClasses && <div className="px-2 py-1.5 text-xs text-[var(--dg-text-muted)]"> {i18n.t('common.loading')} </div>
)}
{/* 当前自定义值作为选项,确保选中态 */}
{ingressClass && !ingressClasses.some((ic) => ic.name === ingressClass) && (
@@ -169,7 +169,7 @@ export function IngressEditor({ manifest, clusterCode, onSave, saving, isCreate
<div className="flex-1 min-h-0 flex flex-col mt-3">
<div className="flex items-center justify-between mb-2 shrink-0">
<Label className="text-[var(--dg-text-secondary)] text-sm"> {i18n.t('kubernetes.ingress.rules')} </Label>
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={addRule}><Plus className="h-3 w-3 mr-1" /> {i18n.t('端口')} </Button>
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={addRule}><Plus className="h-3 w-3 mr-1" /> {i18n.t('k8s.service.portLabel')} </Button>
</div>
<div className="flex-1 min-h-0 overflow-y-auto pr-1 space-y-3 border border-[var(--dg-border)] rounded-lg p-2 bg-[var(--dg-bg-secondary)]">
{rules.map((rule, ri) => (
@@ -178,7 +178,7 @@ export function IngressEditor({ manifest, clusterCode, onSave, saving, isCreate
<Input
value={rule.host}
onChange={e => updateRule(ri, e.target.value)}
placeholder={i18n.t('域名 (如 api.example.com)')}
placeholder={i18n.t('k8s.rve.hostPlaceholder')}
className="flex-1 min-w-0 font-mono text-xs h-7 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]"
title={rule.host}
/>
@@ -187,7 +187,7 @@ export function IngressEditor({ manifest, clusterCode, onSave, saving, isCreate
{rule.paths.map((p, pi) => (
<div key={pi} className="overflow-x-auto pl-4">
<div className="grid grid-cols-[minmax(80px,1fr)_100px_minmax(80px,1fr)_60px_28px] gap-1.5 items-center min-w-[420px]">
<Input value={p.path} onChange={e => updatePath(ri, pi, 'path', e.target.value)} placeholder={i18n.t('路径 /')} className="font-mono text-xs h-7 min-w-0 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
<Input value={p.path} onChange={e => updatePath(ri, pi, 'path', e.target.value)} placeholder={i18n.t('k8s.ingress.pathPlaceholder')} className="font-mono text-xs h-7 min-w-0 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
<Select value={p.pathType} onValueChange={v => updatePath(ri, pi, 'pathType', v)}>
<SelectTrigger className="h-7 text-xs bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]"><SelectValue /></SelectTrigger>
<SelectContent><SelectItem value="Prefix">Prefix</SelectItem><SelectItem value="Exact">Exact</SelectItem></SelectContent>
@@ -201,7 +201,7 @@ export function IngressEditor({ manifest, clusterCode, onSave, saving, isCreate
<Button variant="ghost" size="sm" className="h-6 text-xs text-[var(--dg-text-muted)]" onClick={() => addPath(ri)}><Plus className="h-3 w-3 mr-1" /> {i18n.t('kubernetes.ingress.addPath')} </Button>
</div>
))}
{rules.length === 0 && <div className="text-center py-4 text-[var(--dg-text-muted)] text-xs"> {i18n.t('名称')} </div>}
{rules.length === 0 && <div className="text-center py-4 text-[var(--dg-text-muted)] text-xs"> {i18n.t('common.name')} </div>}
</div>
</div>
<div className="shrink-0 flex justify-end gap-2 pt-3 mt-2 border-t border-[var(--dg-border)]">
@@ -44,14 +44,14 @@ export function PvcEditor({ manifest, onSave, saving, isCreate }: PvcEditorProps
</div>
<div className="grid grid-cols-2 gap-4 mt-3 shrink-0">
<div>
<Label className="text-[var(--dg-text-secondary)] text-xs"> {i18n.t('加载中...')} </Label>
<Label className="text-[var(--dg-text-secondary)] text-xs"> {i18n.t('common.loading')} </Label>
<Input
value={size}
onChange={e => setSize(e.target.value)}
placeholder={i18n.t('如 10Gi')}
placeholder={i18n.t('k8s.pvc.sizePlaceholder')}
className="mt-1 font-mono text-xs h-8 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]"
/>
<p className="text-[10px] text-[var(--dg-text-muted)] mt-1"> {i18n.t('名称')} </p>
<p className="text-[10px] text-[var(--dg-text-muted)] mt-1"> {i18n.t('common.name')} </p>
</div>
<div>
<Label className="text-[var(--dg-text-secondary)] text-xs"> {i18n.t('kubernetes.storage.accessModes')} </Label>
@@ -72,7 +72,7 @@ export function PvcEditor({ manifest, onSave, saving, isCreate }: PvcEditorProps
<Input
value={storageClass}
onChange={e => setStorageClass(e.target.value)}
placeholder={i18n.t('留空使用默认 StorageClass')}
placeholder={i18n.t('k8s.pvc.scPlaceholder')}
className="mt-1 font-mono text-xs h-8 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]"
/>
</div>
@@ -68,10 +68,10 @@ export function SecretEditor({ manifest, onSave, saving, isCreate }: SecretEdito
<div className="flex-1 min-h-0 flex flex-col mt-3">
<div className="flex items-center justify-between mb-2 shrink-0">
<Label className="text-[var(--dg-text-secondary)] text-sm"> {i18n.t('加载中...')} </Label>
<Label className="text-[var(--dg-text-secondary)] text-sm"> {i18n.t('common.loading')} </Label>
<div className="flex gap-1">
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={() => setExpandAll(!expandAll)}>
{expandAll ? i18n.t('折叠全部') : i18n.t('system.expandAll')}
{expandAll ? i18n.t('k8s.configMap.collapseAll') : i18n.t('system.expandAll')}
</Button>
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={addRow}><Plus className="h-3 w-3 mr-1" /> {i18n.t('monitor.dashboard.custom.addBtn')} </Button>
</div>
@@ -82,13 +82,13 @@ export function SecretEditor({ manifest, onSave, saving, isCreate }: SecretEdito
<Input
value={entry.key}
onChange={e => update(i, 'key', e.target.value)}
placeholder={i18n.t('键名 (如 LOG_LEVEL)')}
placeholder={i18n.t('k8s.rve.keyPlaceholder')}
className="flex-1 font-mono text-xs h-8 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]"
/>
<Textarea
value={entry.value}
onChange={e => update(i, 'value', e.target.value)}
placeholder={i18n.t('键名 (如 LOG_LEVEL)')}
placeholder={i18n.t('k8s.rve.keyPlaceholder')}
rows={expandAll || entry.value.length > 300 ? 10 : (entry.value.length > 100 ? 6 : 3)}
className="flex-[2] font-mono text-xs min-h-[70px] max-h-[420px] resize-y bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] leading-relaxed"
/>
@@ -98,7 +98,7 @@ export function SecretEditor({ manifest, onSave, saving, isCreate }: SecretEdito
</div>
))}
{entries.length === 0 && (
<div className="text-center py-8 text-[var(--dg-text-muted)] text-xs"> {i18n.t('名称')} </div>
<div className="text-center py-8 text-[var(--dg-text-muted)] text-xs"> {i18n.t('common.name')} </div>
)}
</div>
</div>
@@ -99,7 +99,7 @@ export function ServiceEditor({ manifest, onSave, saving, isCreate }: ServiceEdi
<div className="flex-1 min-h-0 overflow-y-auto mt-3 pr-1 space-y-3">
<div>
<div className="flex items-center justify-between mb-2">
<Label className="text-[var(--dg-text-secondary)] text-sm"> {i18n.t('加载中...')} </Label>
<Label className="text-[var(--dg-text-secondary)] text-sm"> {i18n.t('common.loading')} </Label>
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={addPort}><Plus className="h-3 w-3 mr-1" /> {i18n.t('agent.addPort')} </Button>
</div>
<div className="space-y-2 border border-[var(--dg-border)] rounded-lg p-2 bg-[var(--dg-bg-secondary)]">
@@ -120,22 +120,22 @@ export function ServiceEditor({ manifest, onSave, saving, isCreate }: ServiceEdi
</div>
</div>
))}
{ports.length === 0 && <div className="text-center py-4 text-[var(--dg-text-muted)] text-xs"> {i18n.t('端口')} </div>}
{ports.length === 0 && <div className="text-center py-4 text-[var(--dg-text-muted)] text-xs"> {i18n.t('k8s.service.portLabel')} </div>}
</div>
</div>
{/* Selector */}
<div>
<div className="flex items-center justify-between mb-2">
<Label className="text-[var(--dg-text-secondary)] text-sm"> {i18n.t('加载中...')} </Label>
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={addSelector}><Plus className="h-3 w-3 mr-1" /> {i18n.t('加载中...')} </Button>
<Label className="text-[var(--dg-text-secondary)] text-sm"> {i18n.t('common.loading')} </Label>
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={addSelector}><Plus className="h-3 w-3 mr-1" /> {i18n.t('common.loading')} </Button>
</div>
<div className="space-y-2">
{selector.map((s, i) => (
<div key={i} className="flex items-center gap-2 min-w-0">
<Input value={s.key} onChange={e => updateSelector(i, 'key', e.target.value)} placeholder={i18n.t('键 (如 app)')} className="flex-1 min-w-0 font-mono text-xs h-7 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
<Input value={s.key} onChange={e => updateSelector(i, 'key', e.target.value)} placeholder={i18n.t('k8s.service.selectorKey')} className="flex-1 min-w-0 font-mono text-xs h-7 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
<span className="text-[var(--dg-text-muted)] text-xs shrink-0">=</span>
<Input value={s.value} onChange={e => updateSelector(i, 'value', e.target.value)} placeholder={i18n.t('值 (如 nginx)')} className="flex-1 min-w-0 font-mono text-xs h-7 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
<Input value={s.value} onChange={e => updateSelector(i, 'value', e.target.value)} placeholder={i18n.t('k8s.service.selectorValue')} className="flex-1 min-w-0 font-mono text-xs h-7 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
<Button variant="ghost" size="icon" className="h-7 w-7 shrink-0 dg-text-red hover:dg-text-red" onClick={() => removeSelector(i)}><Trash2 className="h-3 w-3" /></Button>
</div>
))}
@@ -20,13 +20,13 @@ export function UpdateStrategySelectorCards({ stats }: { stats: WorkloadStats })
</CardHeader>
<CardContent className="pt-0 space-y-2 text-xs">
<div className="flex justify-between">
<span className="text-[var(--dg-text-muted)]"> {i18n.t('服务名')} </span>
<span className="text-[var(--dg-text-muted)]"> {i18n.t('k8s.workloadView.serviceName')} </span>
<Badge variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">{strategyType}</Badge>
</div>
{strategyType === 'RollingUpdate' && isSts && updateStrategy.rollingUpdate && (
<>
<div className="flex justify-between">
<span className="text-[var(--dg-text-muted)]"> {i18n.t('Pod 数量')} </span>
<span className="text-[var(--dg-text-muted)]"> {i18n.t('k8s.workloadView.podCount')} </span>
<span className="text-[var(--dg-text-secondary)] font-mono">{updateStrategy.rollingUpdate.partition ?? 0}</span>
</div>
{updateStrategy.rollingUpdate.maxUnavailable && (
@@ -47,7 +47,7 @@ export function UpdateStrategySelectorCards({ stats }: { stats: WorkloadStats })
)}
{updateStrategy.rollingUpdate.maxSurge && (
<div className="flex justify-between">
<span className="text-[var(--dg-text-muted)]"> {i18n.t('Pod 数量')} </span>
<span className="text-[var(--dg-text-muted)]"> {i18n.t('k8s.workloadView.podCount')} </span>
<span className="text-[var(--dg-text-secondary)] font-mono">{typeof updateStrategy.rollingUpdate.maxSurge === 'object' ? JSON.stringify(updateStrategy.rollingUpdate.maxSurge) : updateStrategy.rollingUpdate.maxSurge}</span>
</div>
)}
@@ -60,7 +60,7 @@ export function UpdateStrategySelectorCards({ stats }: { stats: WorkloadStats })
</div>
)}
<div className="flex justify-between">
<span className="text-[var(--dg-text-muted)]"> {i18n.t('服务名')} </span>
<span className="text-[var(--dg-text-muted)]"> {i18n.t('k8s.workloadView.serviceName')} </span>
<span className="text-[var(--dg-text-secondary)] font-mono">{spec.revisionHistoryLimit ?? 10}</span>
</div>
</CardContent>
+247 -14
View File
@@ -2343,7 +2343,10 @@
"resourceTrend": "Resource Usage Trend (simulated)",
"refreshMetrics": "Refresh node metrics",
"resourceDistribution": "Resource Distribution",
"now": "Now"
"now": "Now",
"allNamespaces": "All Namespaces",
"provider": "Provider",
"region": "Region"
},
"tab": {
"overview": "Overview",
@@ -2352,7 +2355,15 @@
"deployments": "Deployments",
"services": "Services",
"ingresses": "Ingresses",
"events": "Events"
"events": "Events",
"statistics": "Statistics",
"cronJobs": "CronJobs",
"configMap": "ConfigMaps",
"secret": "Secrets",
"role": "Roles",
"roleBinding": "RoleBindings",
"serviceAccount": "Service Accounts",
"pvc": "PVCs"
},
"column": {
"name": "Name",
@@ -2703,7 +2714,13 @@
"deploymentDeleted": "{{name}} deleted",
"confirmDeletePod": "Confirm delete Pod \"{{name}}\"? This action cannot be undone.",
"deploymentNotFound": "Deployment not found: {{name}}",
"imageLabel": "Image"
"imageLabel": "Image",
"resourceQuota": "Resource Quota",
"limitRange": "Limit Range",
"pdb": "Pod Disruption Budget",
"editYaml": "Edit YAML",
"imageUpdated": "Image updated successfully",
"hpaDeleted": "HPA deleted"
},
"podLogs": {
"noLogs": "No logs",
@@ -2726,7 +2743,11 @@
"loading": "Loading…",
"fetchFailed": "Failed to fetch logs",
"streamEnded": "Stream ended",
"streamStopped": "Stream stopped"
"streamStopped": "Stream stopped",
"download": "Download Logs",
"searchPlaceholder": "Search logs...",
"previousLogs": "View previous (crashed) container logs",
"keywordFilter": "Keyword filter…"
},
"editMode": {
"basic": "Basic Info",
@@ -2791,7 +2812,8 @@
"minReplicas": "Min Replicas",
"maxReplicas": "Max Replicas",
"cpuTarget": "CPU Target (%)",
"memTarget": "Memory Target (%)"
"memTarget": "Memory Target (%)",
"targetCPU": "Target CPU Utilization"
},
"redirect": {
"redirecting": "Redirecting..."
@@ -2889,7 +2911,14 @@
"colMessage": "Message",
"nameLabel": "Event name:",
"totalCount": "Total {{count}} events",
"virtualScroll": "virtual scroll rendering"
"virtualScroll": "virtual scroll rendering",
"center": "Event Center",
"clusterAlerts": "Cluster Events & Alerts",
"normal": "Normal Events",
"warning": "Warning Events",
"warningType": "Warning Type",
"resourceType": "Resource Type",
"warningReason": "Warning Reason"
},
"yamlImport": {
"empty": "Please enter YAML",
@@ -3005,7 +3034,10 @@
"shareProcessDesc": "Share process namespace",
"save": "Save",
"saved": "update submitted",
"secondsPlaceholder": "seconds"
"secondsPlaceholder": "seconds",
"maxSurge": "Max Surge",
"maxUnavailable": "Max Unavailable",
"strategyLabel": "Update Strategy"
},
"editBasic": {
"synced": "Synced",
@@ -3062,7 +3094,12 @@
"file": "File",
"upload": "Upload",
"readFailed": "Read failed",
"uploadFailed": "Upload failed"
"uploadFailed": "Upload failed",
"confirmDelete": "Confirm Delete",
"scaleTo": "Scale to",
"restartConfirm": "Are you sure you want to restart this workload?",
"deleteWarning": "This action cannot be undone",
"yamlDocError": "Each YAML document must contain kind and metadata.name"
},
"namespaceResource": {
"delete1": "确认删除 Pod \"",
@@ -3085,14 +3122,17 @@
"close1": "Close",
"container1": "正在进入容器...",
"enterPod": "Enter Pod container to execute commands",
"connectErrorAnsi": "\\r\\n\\x1b[31m[连接发生错误]\\x1b[0m\\r\\n"
"connectErrorAnsi": "\\r\\n\\x1b[31m[连接发生错误]\\x1b[0m\\r\\n",
"shell": "Shell"
},
"podMetrics": {
"resources2": "Pod 资源用量",
"container2": "容器 CPU/内存实时指标",
"cpu": "总 CPU",
"memory": "总内存",
"namespace": "所有命名空间"
"namespace": "所有命名空间",
"cpuUsage": "CPU Usage",
"memUsage": "Memory Usage"
},
"resourceEditor": {
"create": "Create",
@@ -3133,7 +3173,11 @@
"servicePort": "Service Port",
"noSelector": "(No selector)",
"confirmDelete": "Confirm Delete",
"deleteDesc": "This operation cannot be undone"
"deleteDesc": "This operation cannot be undone",
"portName": "Port Name",
"targetPort": "Target Port",
"protocol": "Protocol",
"serviceCreated": "Service created"
},
"editContainers": {
"noManifest": "Manifest data not loaded. Please use YAML editor to modify directly.",
@@ -3143,7 +3187,14 @@
"addNew": "Add New",
"initContainers": "Init Containers",
"workContainers": "Work Containers",
"save": "Save"
"save": "Save",
"imageRequired": " image is required",
"nameRequired": " name is required",
"portNameRequired": "Port name is required",
"saved": "Container configuration saved",
"containerCount": "containers",
"secretPlaceholder": "Type Secret name and press Enter",
"initDesc": "Containers executed sequentially before main container starts, for environment initialization"
},
"editStorage": {
"volumes": "Volumes",
@@ -3154,13 +3205,34 @@
"noContainers": "(No containers)",
"readOnly": "Read Only",
"readWrite": "Read/Write",
"save": "Save"
"save": "Save",
"emptyDir": "EmptyDir (temporary)",
"volumeNameRequired": "Volume name is required",
"selectConfigMap": "Please select a ConfigMap",
"selectSecret": "Please select a Secret",
"selectPVC": "Please select a PVC",
"typeRequired": " type field is required",
"incompleteConfig": "Volume configuration incomplete",
"mountPathRequired": "Mount path is required",
"saved": "Storage configuration saved",
"namePlaceholder": "e.g. app-logs / fluent-bit-config",
"mountPathPlaceholder": "Mount path /bitcloud/app/.../logs",
"typeRequiredHint": " type required field"
},
"clusterDialog": {
"nameHint": "Only English letters, numbers and hyphens (-) allowed. Cannot start or end with hyphen. Name will be the cluster unique identifier.",
"configured": "Configured (leave empty to keep)",
"kubeconfigHint": "API Server address will be auto-parsed after pasting kubeconfig content",
"cancel": "Cancel"
"cancel": "Cancel",
"nameRule": "Only letters, digits, and hyphens (-) allowed; cannot start or end with a hyphen",
"nameInvalid": "Please enter a valid cluster name (letters/digits/hyphens)",
"saveFailed": "Failed to save cluster:",
"namePlaceholder": "e.g. prod-cluster",
"descLabel": "Cluster description",
"tokenPlaceholder": "Paste Service Account Token...",
"tokenConfigured": "Configured, enter new value to override...",
"addTitle": "Add Cluster",
"editTitle": "Edit Cluster"
},
"nodeDetail": {
"nodeTitle": "Node Details",
@@ -3184,6 +3256,167 @@
"podsOnNode": "Pods on Node",
"noPodsOnNode": "No pods on this node",
"cpuRequest": "CPU Request"
},
"nsResource": {
"deployDesc": "Deployment workload status",
"podDesc": "Pod instance status",
"serviceDesc": "Service networking",
"ingressDesc": "Ingress routing rules",
"configMapDesc": "Configuration maps",
"secretDesc": "Secret management (name and type only)",
"hpaDesc": "Horizontal Pod Autoscaler",
"pvcDesc": "Persistent Volume Claims",
"stsDesc": "StatefulSets",
"dsDesc": "DaemonSets",
"saDesc": "Service Accounts (RBAC identity)",
"roleDesc": "Namespace-level Roles (RBAC)",
"rbDesc": "RoleBindings (RBAC authorization)",
"viewYaml": "View YAML",
"resourceName": "Name",
"namespace": "Namespace"
},
"nsOverview": {
"workloadReplicas": "Workload replica management",
"podStatus": "Container instance status",
"cronjobManage": "CronJob management",
"serviceDiscovery": "Service discovery & load balancing",
"externalRoute": "External traffic routing rules",
"configMaps": "Configuration maps",
"secrets": "Secrets & credentials",
"hpaStrategy": "Horizontal autoscaling strategy",
"loadingStats": "Loading resource statistics..."
},
"deploymentWorkload": {
"loadFailed": "Failed to load details:",
"manifestFailed": "Failed to load manifest (edit degraded):",
"restartSent": "Restart command sent",
"restarting": "Deployment rolling restart in progress",
"scaleSuccess": "Scale successful",
"loading": "Loading Deployment...",
"create": "New Deployment",
"withService": "Includes associated Service",
"manifestLoadFailed": "Failed to load manifest (edit degraded):"
},
"deploymentView": {
"rollbackVersion": "Rollback Version",
"rollbackSent": "Rollback initiated",
"scaleSent": "Scaling submitted",
"pods": "Pods",
"access": "Access",
"scale": "Scale"
},
"rve": {
"visualUnavailable": "Visual editor unavailable",
"fallbackYaml": "Unable to parse resource structure, switched to YAML edit mode",
"saveFailed": "Save failed",
"parseFailed": "Unable to parse YAML",
"parseFailedHint": "Please check syntax and continue in YAML mode",
"downloaded": "Downloaded",
"hostPlaceholder": "Domain (e.g. api.example.com)",
"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"
},
"workloadView": {
"restartSent": "Restart command sent",
"restartFailed": "Restart failed",
"loading": "Loading workload...",
"restarting": "Restarting...",
"currentReplicas": "Current Replicas",
"desiredSchedule": "Desired Schedule",
"serviceName": "Service Name",
"podCount": "Pod Count",
"actualSchedule": "Actual Schedule"
},
"pvc": {
"sizeLabel": "Size",
"accessMode": "Access Mode",
"storageClass": "Storage Class",
"sizePlaceholder": "e.g. 10Gi",
"scPlaceholder": "Leave empty for default StorageClass"
},
"service": {
"portName": "Port Name",
"portNum": "Port",
"targetPort": "Target Port",
"protocol": "Protocol",
"nodePort": "Node Port",
"sessionAffinity": "Session Affinity",
"externalTrafficPolicy": "External Traffic Policy",
"portLabel": "Port",
"selectorKey": "Key (e.g. app)",
"selectorValue": "Value (e.g. nginx)"
},
"ingress": {
"host": "Host",
"path": "Path",
"pathType": "Path Type",
"backend": "Backend Service",
"backendPort": "Backend Port",
"tlsSecret": "TLS Certificate",
"pathPlaceholder": "Path /"
},
"configMap": {
"key": "Key",
"value": "Value",
"addKey": "Add Key-Value",
"collapseAll": "Collapse All"
},
"secret": {
"type": "Type",
"data": "Data",
"addKey": "Add Key",
"collapseAll": "Collapse All"
},
"nodes": {
"ready": "Ready",
"notReady": "Not Ready",
"selectCluster": "Select Cluster",
"subtitle": "Kubernetes cluster node status and resources",
"noData": "No node data"
},
"capacityPage": {
"title": "Capacity Management"
},
"nsMgmt": {
"status": "Status",
"age": "Age",
"quota": "Quota",
"labels": "Labels",
"searchPlaceholder": "Search namespaces...",
"title": "Namespace Management",
"quotaLimit": "Quota Limits",
"nameExample": "e.g. my-app"
},
"clusterOverview": {
"nodeCount": "Nodes",
"podCount": "Pods",
"serviceCount": "Services",
"notFound": "Cluster not found",
"title": "Cluster Overview"
},
"controlBar": {
"allNs": "All Namespaces",
"searchPlaceholder": "Search resources...",
"refresh": "Refresh",
"create": "Create"
},
"nsResourcePage": {
"loading": "Loading...",
"noData": "No data",
"exportCsv": "Export CSV",
"exportJson": "Export JSON",
"exportYaml": "Export namespace YAML (tar.gz)"
},
"yamlViewer": {
"copy": "Copy",
"download": "Download",
"copied": "Copied",
"fetchFailed": "Failed to fetch YAML"
},
"podFile": {
"loadFailed": "Failed to load file list"
}
},
"monitor": {
+247 -14
View File
@@ -2343,7 +2343,10 @@
"resourceTrend": "资源使用趋势(模拟最近变化)",
"refreshMetrics": "刷新节点指标",
"resourceDistribution": "资源分布",
"now": "现在"
"now": "现在",
"allNamespaces": "全部命名空间",
"provider": "提供商",
"region": "区域"
},
"tab": {
"overview": "概览",
@@ -2352,7 +2355,15 @@
"deployments": "Deployment",
"services": "Service",
"ingresses": "Ingress",
"events": "事件"
"events": "事件",
"statistics": "统计",
"cronJobs": "定时任务",
"configMap": "配置字典",
"secret": "密钥",
"role": "角色",
"roleBinding": "角色绑定",
"serviceAccount": "服务账号",
"pvc": "存储卷"
},
"column": {
"name": "名称",
@@ -2703,7 +2714,13 @@
"deploymentDeleted": "{{name}} 已删除",
"confirmDeletePod": "确认删除 Pod \"{{name}}\"?操作不可恢复。",
"deploymentNotFound": "未找到 Deployment: {{name}}",
"imageLabel": "镜像"
"imageLabel": "镜像",
"resourceQuota": "资源配额",
"limitRange": "限制范围",
"pdb": "Pod 干扰预算",
"editYaml": "编辑 YAML",
"imageUpdated": "镜像更新成功",
"hpaDeleted": "HPA 已删除"
},
"podLogs": {
"noLogs": "无日志",
@@ -2726,7 +2743,11 @@
"loading": "加载中…",
"fetchFailed": "获取日志失败",
"streamEnded": "流式传输结束",
"streamStopped": "流式传输已停止"
"streamStopped": "流式传输已停止",
"download": "下载日志",
"searchPlaceholder": "搜索日志...",
"previousLogs": "查看上一次(崩溃)容器的日志",
"keywordFilter": "关键字过滤…"
},
"editMode": {
"basic": "基本信息",
@@ -2791,7 +2812,8 @@
"minReplicas": "最小副本数",
"maxReplicas": "最大副本数",
"cpuTarget": "CPU 目标 (%)",
"memTarget": "内存目标 (%)"
"memTarget": "内存目标 (%)",
"targetCPU": "目标 CPU 使用率"
},
"redirect": {
"redirecting": "正在跳转..."
@@ -2889,7 +2911,14 @@
"colMessage": "消息",
"nameLabel": "事件名:",
"totalCount": "共 {{count}} 条事件",
"virtualScroll": "虚拟滚动渲染"
"virtualScroll": "虚拟滚动渲染",
"center": "事件中心",
"clusterAlerts": "集群事件与告警",
"normal": "正常事件",
"warning": "告警事件",
"warningType": "告警类型",
"resourceType": "资源类型",
"warningReason": "告警原因"
},
"yamlImport": {
"empty": "请输入 YAML",
@@ -3005,7 +3034,10 @@
"shareProcessDesc": "共享进程命名空间",
"save": "保存",
"saved": "更新已提交",
"secondsPlaceholder": "秒"
"secondsPlaceholder": "秒",
"maxSurge": "最大超出",
"maxUnavailable": "最大不可用",
"strategyLabel": "更新策略"
},
"editBasic": {
"synced": "已同步",
@@ -3062,7 +3094,12 @@
"file": "文件",
"upload": "上传",
"readFailed": "读取失败",
"uploadFailed": "上传失败"
"uploadFailed": "上传失败",
"confirmDelete": "确认删除",
"scaleTo": "伸缩至",
"restartConfirm": "确定要重启此工作负载吗?",
"deleteWarning": "此操作不可恢复",
"yamlDocError": "每个 YAML 文档必须包含 kind 和 metadata.name"
},
"namespaceResource": {
"delete1": "确认删除 Pod \"",
@@ -3085,14 +3122,17 @@
"close1": "关闭",
"container1": "正在进入容器...",
"enterPod": "进入 Pod 容器执行命令",
"connectErrorAnsi": "\\r\\n\\x1b[31m[连接发生错误]\\x1b[0m\\r\\n"
"connectErrorAnsi": "\\r\\n\\x1b[31m[连接发生错误]\\x1b[0m\\r\\n",
"shell": "Shell"
},
"podMetrics": {
"resources2": "Pod 资源用量",
"container2": "容器 CPU/内存实时指标",
"cpu": "总 CPU",
"memory": "总内存",
"namespace": "所有命名空间"
"namespace": "所有命名空间",
"cpuUsage": "CPU 使用",
"memUsage": "内存使用"
},
"resourceEditor": {
"create": "新建",
@@ -3133,7 +3173,11 @@
"servicePort": "Service 端口",
"noSelector": "(无 selector)",
"confirmDelete": "确认删除",
"deleteDesc": "此操作不可撤销"
"deleteDesc": "此操作不可撤销",
"portName": "端口名称",
"targetPort": "目标端口",
"protocol": "协议",
"serviceCreated": "Service 已创建"
},
"editContainers": {
"noManifest": "未获取到 manifest 数据,请使用 YAML 编辑模式直接修改。",
@@ -3143,7 +3187,14 @@
"addNew": "新增",
"initContainers": "初始化容器",
"workContainers": "工作容器",
"save": "保存"
"save": "保存",
"imageRequired": "的镜像不能为空",
"nameRequired": "的名称不能为空",
"portNameRequired": "端口名称不能为空",
"saved": "容器配置已保存",
"containerCount": "个容器",
"secretPlaceholder": "输入 Secret 名称后回车",
"initDesc": "在主容器启动前按顺序执行的容器,用于初始化环境"
},
"editStorage": {
"volumes": "数据卷",
@@ -3154,13 +3205,34 @@
"noContainers": "(无容器)",
"readOnly": "只读",
"readWrite": "读写",
"save": "保存"
"save": "保存",
"emptyDir": "临时目录(EmptyDir)",
"volumeNameRequired": "请填写数据卷名称",
"selectConfigMap": "请选择 ConfigMap",
"selectSecret": "请选择 Secret",
"selectPVC": "请选择 PVC",
"typeRequired": "的类型必填项",
"incompleteConfig": "数据卷配置不完整",
"mountPathRequired": "请填写挂载路径",
"saved": "存储配置已保存",
"namePlaceholder": "如 app-logs / fluent-bit-config",
"mountPathPlaceholder": "挂载路径 /bitcloud/app/.../logs",
"typeRequiredHint": "的类型必填项"
},
"clusterDialog": {
"nameHint": "只允许英文、数字和连字符(-),首尾不能是连字符。名称将作为集群唯一标识。",
"configured": "已配置(留空不修改)",
"kubeconfigHint": "粘贴 kubeconfig 内容后将自动解析 API Server 地址",
"cancel": "取消"
"cancel": "取消",
"nameRule": "只允许英文、数字和连字符(-),首尾不能是连字符",
"nameInvalid": "请输入有效的集群名称(英文/数字/连字符)",
"saveFailed": "保存集群失败:",
"namePlaceholder": "如: prod-cluster",
"descLabel": "集群用途描述",
"tokenPlaceholder": "粘贴 Service Account Token...",
"tokenConfigured": "已配置,输入新值覆盖...",
"addTitle": "添加集群",
"editTitle": "编辑集群"
},
"nodeDetail": {
"nodeTitle": "节点详情",
@@ -3184,6 +3256,167 @@
"podsOnNode": "节点上的 Pod",
"noPodsOnNode": "该节点上暂无 Pod",
"cpuRequest": "CPU 请求"
},
"nsResource": {
"deployDesc": "Deployment 工作负载运行状态",
"podDesc": "Pod 实例运行状态",
"serviceDesc": "Service 网络服务",
"ingressDesc": "Ingress 路由规则",
"configMapDesc": "配置字典",
"secretDesc": "密钥管理(仅名称与类型)",
"hpaDesc": "水平自动伸缩",
"pvcDesc": "存储卷声明",
"stsDesc": "有状态应用",
"dsDesc": "守护进程集",
"saDesc": "服务账号(RBAC 身份标识)",
"roleDesc": "命名空间级角色权限(RBAC)",
"rbDesc": "角色绑定关系(RBAC 授权)",
"viewYaml": "查看YAML",
"resourceName": "名称",
"namespace": "命名空间"
},
"nsOverview": {
"workloadReplicas": "工作负载副本管理",
"podStatus": "容器实例运行状态",
"cronjobManage": "定时任务管理",
"serviceDiscovery": "服务发现与负载均衡",
"externalRoute": "外部流量路由规则",
"configMaps": "配置字典",
"secrets": "密钥与凭证",
"hpaStrategy": "水平自动伸缩策略",
"loadingStats": "加载资源统计..."
},
"deploymentWorkload": {
"loadFailed": "加载详情失败:",
"manifestFailed": "加载 manifest 失败(编辑功能降级):",
"restartSent": "重启指令已下发",
"restarting": "Deployment 正在滚动重启",
"scaleSuccess": "扩缩容成功",
"loading": "加载 Deployment...",
"create": "新建 Deployment",
"withService": "含关联 Service",
"manifestLoadFailed": "加载 manifest 失败(编辑功能降级):"
},
"deploymentView": {
"rollbackVersion": "回滚版本",
"rollbackSent": "回滚已下发",
"scaleSent": "伸缩已提交",
"pods": "容器组",
"access": "访问方式",
"scale": "容器伸缩"
},
"rve": {
"visualUnavailable": "可视化编辑不可用",
"fallbackYaml": "无法解析资源结构,已切换到 YAML 编辑模式",
"saveFailed": "保存失败",
"parseFailed": "无法解析 YAML",
"parseFailedHint": "请检查语法后继续用 YAML 模式",
"downloaded": "已下载",
"hostPlaceholder": "域名 (如 api.example.com)",
"keyPlaceholder": "键名 (如 LOG_LEVEL)",
"parseYamlFailed": "无法解析 YAML",
"checkSyntax": "请检查语法后继续用 YAML 模式",
"fallbackToYaml": "无法解析资源结构,已切换到 YAML 编辑模式"
},
"workloadView": {
"restartSent": "重启指令已下发",
"restartFailed": "重启失败",
"loading": "加载工作负载...",
"restarting": "重启中...",
"currentReplicas": "当前副本",
"desiredSchedule": "期望调度",
"serviceName": "服务名",
"podCount": "Pod 数量",
"actualSchedule": "当前调度"
},
"pvc": {
"sizeLabel": "容量",
"accessMode": "访问模式",
"storageClass": "存储类",
"sizePlaceholder": "如 10Gi",
"scPlaceholder": "留空使用默认 StorageClass"
},
"service": {
"portName": "端口名称",
"portNum": "端口号",
"targetPort": "目标端口",
"protocol": "协议",
"nodePort": "Node Port",
"sessionAffinity": "会话亲和性",
"externalTrafficPolicy": "外部流量策略",
"portLabel": "端口",
"selectorKey": "键 (如 app)",
"selectorValue": "值 (如 nginx)"
},
"ingress": {
"host": "域名",
"path": "路径",
"pathType": "路径类型",
"backend": "后端服务",
"backendPort": "后端端口",
"tlsSecret": "TLS 证书",
"pathPlaceholder": "路径 /"
},
"configMap": {
"key": "键",
"value": "值",
"addKey": "添加键值对",
"collapseAll": "折叠全部"
},
"secret": {
"type": "类型",
"data": "数据",
"addKey": "添加密钥",
"collapseAll": "折叠全部"
},
"nodes": {
"ready": "就绪",
"notReady": "未就绪",
"selectCluster": "选择集群",
"subtitle": "Kubernetes 集群工作节点状态与资源",
"noData": "暂无节点数据"
},
"capacityPage": {
"title": "容量管理"
},
"nsMgmt": {
"status": "状态",
"age": "创建时间",
"quota": "配额",
"labels": "标签",
"searchPlaceholder": "搜索命名空间...",
"title": "命名空间管理",
"quotaLimit": "配额限制",
"nameExample": "例如: my-app"
},
"clusterOverview": {
"nodeCount": "节点数",
"podCount": "Pod 数",
"serviceCount": "Service 数",
"notFound": "集群未找到",
"title": "集群概览"
},
"controlBar": {
"allNs": "全部命名空间",
"searchPlaceholder": "搜索资源...",
"refresh": "刷新",
"create": "创建"
},
"nsResourcePage": {
"loading": "加载中...",
"noData": "暂无数据",
"exportCsv": "导出CSV",
"exportJson": "导出JSON",
"exportYaml": "导出命名空间 YAML(tar.gz)"
},
"yamlViewer": {
"copy": "复制",
"download": "下载",
"copied": "已复制",
"fetchFailed": "获取YAML失败"
},
"podFile": {
"loadFailed": "加载文件列表失败"
}
},
"monitor": {