feat(i18n): 全量中英文国际化改造
- 翻译 en-US.json 中 1006 条未翻译的中文条目为英文 - 提取 30+ 组件文件的硬编码中文到 locale JSON - 新增 900+ 翻译键覆盖所有业务模块: - Layout (Sidebar, Header, Breadcrumb) - Server (ServerForm, CloudServerImport, WebShell, RdpTools) - Deploy (ReleaseManager, PipelineCreate, AppRelease) - Kubernetes (DeploymentEdit, PodExec, PodLogs, ResourceEditor) - Monitor (UnifiedQueryPanel, AlertCenter, Prometheus/Grafana Dashboard) - AIOps (predictUtils, PredictiveCostTab, AIPredictive) - System (useLdapManagement, AgentMonitor, AgentWizard) - tsc --noEmit 类型检查通过
This commit is contained in:
1 parent
76ec9387f0
commit
a9513c2056
44 files changed
+4342
-1792
No files matched your search
@@ -13,6 +13,7 @@ import { kubernetesApi } from '@/api';
|
||||
import { PageShell } from '@/components/layout/PageShell';
|
||||
import { ContentPanel } from '@/components/layout/ContentPanel';
|
||||
import type { JsonData } from '@/types/common';
|
||||
import i18n from '@/i18n';
|
||||
|
||||
export default function CapacityPage() {
|
||||
const { clusterCode } = useParams<{ clusterCode: string }>();
|
||||
@@ -76,8 +77,8 @@ export default function CapacityPage() {
|
||||
return (
|
||||
<PageShell
|
||||
variant="list"
|
||||
title="资源容量分析"
|
||||
subtitle={clusterName ? `${clusterName} · Deployment 资源请求与限制` : 'Deployment 资源请求与限制'}
|
||||
title=i18n.t('k8s.capacity.resources')
|
||||
subtitle={clusterName ? `${clusterName} · Deployment 资源请求与限制` : i18n.t('k8s.capacity.resources1')}
|
||||
icon={<BarChart3 className="h-5 w-5" />}
|
||||
actions={(
|
||||
<>
|
||||
@@ -88,7 +89,7 @@ export default function CapacityPage() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select value={selectedNs} onValueChange={setSelectedNs}>
|
||||
<SelectTrigger className="w-32 h-9 dg-input"><SelectValue placeholder="所有命名空间" /></SelectTrigger>
|
||||
<SelectTrigger className="w-32 h-9 dg-input"><SelectValue placeholder=i18n.t('k8s.capacity.namespace') /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">所有命名空间</SelectItem>
|
||||
{namespaces.map(n => <SelectItem key={n} value={n}>{n}</SelectItem>)}
|
||||
@@ -119,7 +120,7 @@ export default function CapacityPage() {
|
||||
</TableRow></TableHeader>
|
||||
<TableBody>
|
||||
{rows.length === 0 ? (
|
||||
<TableRow><TableCell colSpan={8} className="text-center dg-text-muted py-8">暂无数据{loading ? <span className="animate-spin inline-block ml-2">⏳</span> : ''}</TableCell></TableRow>
|
||||
<TableRow><TableCell colSpan={8} className="text-center dg-text-muted py-8i18n.t('k8s.capacity.s934752c9')animate-spin inline-block ml-2">⏳</span> : ''}</TableCell></TableRow>
|
||||
) : rows.map((r, i) => (
|
||||
<TableRow key={`${r.deployment}-${r.container}-${i}`} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-hover)]">
|
||||
<TableCell className="font-medium text-[var(--dg-text-primary)] text-sm">{r.deployment}</TableCell>
|
||||
|
||||
@@ -334,7 +334,7 @@ export function DeploymentEditAdvanced({ clusterCode, namespace, deploymentName,
|
||||
return (
|
||||
<Card className="dg-card">
|
||||
<CardContent className="p-8 text-center dg-text-muted">
|
||||
未获取到 manifest 数据,请使用 YAML 编辑模式直接修改。
|
||||
{i18n.t('k8s.editAdvanced.noManifest')}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
@@ -346,7 +346,7 @@ export function DeploymentEditAdvanced({ clusterCode, namespace, deploymentName,
|
||||
<Card className="dg-card">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<RefreshCw className="h-4 w-4 dg-text-blue" />更新策略 (Strategy)
|
||||
<RefreshCw className="h-4 w-4 dg-text-blue" />{i18n.t('k8s.editAdvanced.updateStrategy')}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 space-y-3">
|
||||
@@ -432,7 +432,7 @@ export function DeploymentEditAdvanced({ clusterCode, namespace, deploymentName,
|
||||
<option value="PreferNoSchedule">PreferNoSchedule</option>
|
||||
<option value="NoExecute">NoExecute</option>
|
||||
</select>
|
||||
<Input value={t.tolerationSeconds} placeholder="秒" disabled={t.effect !== 'NoExecute'}
|
||||
<Input value={t.tolerationSeconds} placeholder=i18n.t('k8s.editAdvanced.s0c1fec65') disabled={t.effect !== 'NoExecute'}
|
||||
onChange={e => setTolerations(prev => prev.map((x, idx) => idx === i ? { ...x, tolerationSeconds: e.target.value } : x))}
|
||||
className="h-7 dg-input text-xs font-mono" />
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7 dg-text-red" onClick={() => setTolerations(prev => prev.filter((_, idx) => idx !== i))}>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Plus, Trash2, Save, Loader2, Tags, FileText, Hash, Lock } from 'lucide-
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { saveDeploymentManifest } from './deployment-manifest-save';
|
||||
import type { JsonData } from '@/types/common';
|
||||
import i18n from '@/i18n';
|
||||
|
||||
interface DeploymentEditBasicProps {
|
||||
clusterCode: string;
|
||||
@@ -94,7 +95,7 @@ export function DeploymentEditBasic({ clusterCode, namespace, deploymentName, ma
|
||||
const merged = { ...kvToObj(prev), ...sel };
|
||||
return Object.entries(merged).map(([key, value]) => ({ key, value }));
|
||||
});
|
||||
toast({ title: '已同步', description: 'Selector 标签已合并到 Pod 模板标签' });
|
||||
toast({ title: i18n.t('k8s.editBasic.synced'), description: i18n.t('k8s.editBasic.labels') });
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
@@ -107,7 +108,7 @@ export function DeploymentEditBasic({ clusterCode, namespace, deploymentName, ma
|
||||
base.metadata = base.metadata || {};
|
||||
if (isCreate) {
|
||||
const trimmed = resName.trim();
|
||||
if (!trimmed) throw new Error('名称不能为空');
|
||||
if (!trimmed) throw new Error(i18n.t('k8s.editBasic.name'));
|
||||
base.metadata.name = trimmed;
|
||||
base.metadata.namespace = namespace;
|
||||
}
|
||||
@@ -128,10 +129,10 @@ export function DeploymentEditBasic({ clusterCode, namespace, deploymentName, ma
|
||||
delete base.spec.template.metadata.labels;
|
||||
}
|
||||
const savedName = await saveDeploymentManifest(clusterCode, namespace, deploymentName, base);
|
||||
toast({ title: isCreate ? '创建成功' : '基本信息已保存', description: isCreate ? `${savedName} 已创建` : `${savedName} 更新已提交` });
|
||||
toast({ title: isCreate ? i18n.t('k8s.editBasic.createSuccess') : i18n.t('k8s.editBasic.save'), description: isCreate ? `${savedName} ${i18n.t('k8s.editBasic.created')}` : `${savedName} ${i18n.t('k8s.editBasic.updated')}` });
|
||||
onSaved(base);
|
||||
} catch (e) {
|
||||
toast({ title: '保存失败', description: e?.message || String(e), variant: 'destructive' });
|
||||
toast({ title: i18n.t('k8s.editBasic.saveFailed'), description: e?.message || String(e), variant: 'destructive' });
|
||||
} finally { setSaving(false); }
|
||||
};
|
||||
|
||||
@@ -140,7 +141,7 @@ export function DeploymentEditBasic({ clusterCode, namespace, deploymentName, ma
|
||||
<Card className="dg-card">
|
||||
<CardContent className="p-12 text-center">
|
||||
<Loader2 className="h-6 w-6 animate-spin mx-auto dg-text-muted" />
|
||||
<p className="dg-text-muted mt-3 text-sm">加载中...</p>
|
||||
<p className="dg-text-muted mt-3 text-sm">{i18n.t('k8s.editBasic.loading')}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
@@ -150,7 +151,7 @@ export function DeploymentEditBasic({ clusterCode, namespace, deploymentName, ma
|
||||
return (
|
||||
<Card className="dg-card">
|
||||
<CardContent className="p-8 text-center dg-text-muted">
|
||||
未获取到 manifest 数据,请使用 YAML 编辑模式直接修改。
|
||||
{i18n.t('k8s.editBasic.noManifest')}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
@@ -163,7 +164,7 @@ export function DeploymentEditBasic({ clusterCode, namespace, deploymentName, ma
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Hash className="h-4 w-4 dg-text-blue" />
|
||||
副本数 (Replicas)
|
||||
{i18n.t('k8s.editBasic.replicas')}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
@@ -175,7 +176,7 @@ export function DeploymentEditBasic({ clusterCode, namespace, deploymentName, ma
|
||||
onChange={e => setReplicas(Math.max(0, Number(e.target.value) || 0))}
|
||||
className="w-32 dg-input"
|
||||
/>
|
||||
<span className="text-xs dg-text-muted">期望运行的 Pod 实例数量</span>
|
||||
<span className="text-xs dg-text-muted">{i18n.t('k8s.editBasic.replicasHint')}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -185,12 +186,12 @@ export function DeploymentEditBasic({ clusterCode, namespace, deploymentName, ma
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Lock className="h-4 w-4 dg-text-muted" />
|
||||
{isCreate ? '基本标识' : '基本标识(只读)'}
|
||||
{isCreate ? i18n.t('k8s.editBasic.basicIdentity') : i18n.t('k8s.editBasic.readOnly')}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label className="dg-text-secondary text-xs">名称 (name)</Label>
|
||||
<Label className="dg-text-secondary text-xs">{i18n.t('k8s.editBasic.nameLabel')}</Label>
|
||||
<Input
|
||||
value={isCreate ? resName : deploymentName}
|
||||
readOnly={!isCreate}
|
||||
@@ -199,7 +200,7 @@ export function DeploymentEditBasic({ clusterCode, namespace, deploymentName, ma
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="dg-text-secondary text-xs">命名空间 (namespace)</Label>
|
||||
<Label className="dg-text-secondary text-xs">{i18n.t('k8s.editBasic.namespaceLabel')}</Label>
|
||||
<Input value={namespace} readOnly
|
||||
className="mt-1 bg-[var(--dg-bg-primary)] border-[var(--dg-border)] dg-text-muted font-mono text-sm cursor-not-allowed" />
|
||||
</div>
|
||||
@@ -212,16 +213,16 @@ export function DeploymentEditBasic({ clusterCode, namespace, deploymentName, ma
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Tags className="h-4 w-4 dg-text-green" />
|
||||
标签 (Labels)
|
||||
{i18n.t('k8s.editBasic.labels')}
|
||||
<Badge variant="outline" className="border-[var(--dg-border)] dg-text-muted text-[10px]">{labels.length}</Badge>
|
||||
</CardTitle>
|
||||
<Button size="sm" variant="outline" className="h-7 border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-xs" onClick={addLabel}>
|
||||
<Plus className="h-3 w-3 mr-1" />新增
|
||||
<Plus className="h-3 w-3 mr-1" />{i18n.t('k8s.editBasic.addNew')}
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 space-y-1.5">
|
||||
{labels.length === 0 && <p className="text-[11px] dg-text-muted">无标签</p>}
|
||||
{labels.length === 0 && <p className="text-[11px] dg-text-muted">{i18n.t('k8s.editBasic.noLabels')}</p>}
|
||||
{labels.map((l, i) => (
|
||||
<div key={i} className="grid grid-cols-[1fr_1.4fr_auto] gap-2">
|
||||
<Input value={l.key} placeholder="app"
|
||||
@@ -243,17 +244,17 @@ export function DeploymentEditBasic({ clusterCode, namespace, deploymentName, ma
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Tags className="h-4 w-4 dg-text-yellow" />
|
||||
选择器 (Selector)
|
||||
<Badge variant="outline" className="border-amber-700/50 text-amber-500 text-[10px]">⚠️ 需与模板标签一致</Badge>
|
||||
{i18n.t('k8s.editBasic.selector')}
|
||||
<Badge variant="outline" className="border-amber-700/50 text-amber-500 text-[10px]">⚠️ {i18n.t('k8s.editBasic.selectorWarning')}</Badge>
|
||||
</CardTitle>
|
||||
<Button size="sm" variant="outline" className="h-7 border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-xs" onClick={addSelector}>
|
||||
<Plus className="h-3 w-3 mr-1" />新增
|
||||
<Plus className="h-3 w-3 mr-1" />{i18n.t('k8s.editBasic.addNew')}
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 space-y-1.5">
|
||||
<p className="text-[11px] text-amber-500/70 mb-1">Selector 决定哪些 Pod 归属此 Deployment。修改时必须确保与 Pod Template Labels 匹配,否则 Deployment 将无法管理 Pod。</p>
|
||||
{selectorLabels.length === 0 && <p className="text-[11px] dg-text-muted">无选择器</p>}
|
||||
<p className="text-[11px] text-amber-500/70 mb-1">{i18n.t('k8s.editBasic.selectorHint')}</p>
|
||||
{selectorLabels.length === 0 && <p className="text-[11px] dg-text-muted">{i18n.t('k8s.editBasic.noSelector')}</p>}
|
||||
{selectorLabels.map((s, i) => (
|
||||
<div key={i} className="grid grid-cols-[1fr_1.4fr_auto] gap-2">
|
||||
<Input value={s.key} placeholder="app"
|
||||
@@ -275,21 +276,21 @@ export function DeploymentEditBasic({ clusterCode, namespace, deploymentName, ma
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Tags className="h-4 w-4 dg-text-cyan" />
|
||||
Pod 模板标签 (template.metadata.labels)
|
||||
{i18n.t('k8s.editBasic.templateLabels')}
|
||||
</CardTitle>
|
||||
<div className="flex gap-1">
|
||||
<Button size="sm" variant="outline" className="h-7 border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-xs" onClick={syncSelectorToTemplate}>
|
||||
从 Selector 同步
|
||||
{i18n.t('k8s.editBasic.syncFromSelector')}
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" className="h-7 border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-xs" onClick={addTemplateLabel}>
|
||||
<Plus className="h-3 w-3 mr-1" />新增
|
||||
<Plus className="h-3 w-3 mr-1" />{i18n.t('k8s.editBasic.addNew')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 space-y-1.5">
|
||||
<p className="text-[11px] dg-text-muted mb-1">Pod 模板标签必须包含 Selector 全部键值,否则 Deployment 无法管理 Pod</p>
|
||||
{templateLabels.length === 0 && <p className="text-[11px] dg-text-muted">无模板标签</p>}
|
||||
<p className="text-[11px] dg-text-muted mb-1">{i18n.t('k8s.editBasic.templateLabelsHint')}</p>
|
||||
{templateLabels.length === 0 && <p className="text-[11px] dg-text-muted">{i18n.t('k8s.editBasic.noTemplateLabels')}</p>}
|
||||
{templateLabels.map((l, i) => (
|
||||
<div key={i} className="grid grid-cols-[1fr_1.4fr_auto] gap-2">
|
||||
<Input value={l.key} placeholder="app"
|
||||
@@ -311,16 +312,16 @@ export function DeploymentEditBasic({ clusterCode, namespace, deploymentName, ma
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<FileText className="h-4 w-4 dg-text-purple" />
|
||||
注解 (Annotations)
|
||||
{i18n.t('k8s.editBasic.annotations')}
|
||||
<Badge variant="outline" className="border-[var(--dg-border)] dg-text-muted text-[10px]">{annotations.length}</Badge>
|
||||
</CardTitle>
|
||||
<Button size="sm" variant="outline" className="h-7 border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-xs" onClick={addAnnotation}>
|
||||
<Plus className="h-3 w-3 mr-1" />新增
|
||||
<Plus className="h-3 w-3 mr-1" />{i18n.t('k8s.editBasic.addNew')}
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 space-y-1.5">
|
||||
{annotations.length === 0 && <p className="text-[11px] dg-text-muted">无注解</p>}
|
||||
{annotations.length === 0 && <p className="text-[11px] dg-text-muted">{i18n.t('k8s.editBasic.noAnnotations')}</p>}
|
||||
{annotations.map((a, i) => (
|
||||
<div key={i} className="grid grid-cols-[1fr_1.4fr_auto] gap-2">
|
||||
<Input value={a.key} placeholder="deployment.kubernetes.io/revision"
|
||||
@@ -339,13 +340,13 @@ export function DeploymentEditBasic({ clusterCode, namespace, deploymentName, ma
|
||||
{/* 保存栏 */}
|
||||
<div className="sticky bottom-0 bg-[var(--dg-bg-primary)]/80 backdrop-blur border-t border-[var(--dg-border)] p-3 flex items-center justify-between">
|
||||
<p className="text-[11px] dg-text-muted">
|
||||
保存将通过 <code className="dg-text-secondary">PUT application/yaml</code> 提交(自动剥离 status/managedFields 等只读字段)
|
||||
{i18n.t('k8s.editBasic.saveHint')}
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" className="border-[var(--dg-border)] dg-text-secondary" onClick={onCancel}>取消</Button>
|
||||
<Button variant="outline" className="border-[var(--dg-border)] dg-text-secondary" onClick={onCancel}>{i18n.t('common.cancel')}</Button>
|
||||
<Button className="bg-blue-600 hover:bg-blue-700" disabled={saving} onClick={save}>
|
||||
{saving ? <Loader2 className="h-4 w-4 animate-spin mr-1" /> : <Save className="h-4 w-4 mr-1" />}
|
||||
保存
|
||||
{i18n.t('common.save')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { cn } from '@/lib/utils';
|
||||
import { attachTerminalResize, mountXterm } from '@/lib/xterm-utils';
|
||||
import { useUserStore } from '@/store';
|
||||
import { TerminalLoadingOverlay } from '@/components/common/TerminalLoadingOverlay';
|
||||
import i18n from '@/i18n';
|
||||
|
||||
interface KubectlTerminalProps {
|
||||
open: boolean;
|
||||
@@ -102,13 +103,13 @@ export function KubectlTerminal({ open, onOpenChange, clusterId, clusterName }:
|
||||
ws.onclose = () => {
|
||||
setConnected(false);
|
||||
setHasOutput(true);
|
||||
term.write('\r\n\x1b[31m[连接已断开]\x1b[0m\r\n');
|
||||
term.write(i18n.t('k8s.kubectl.disconnected'));
|
||||
};
|
||||
|
||||
ws.onerror = () => {
|
||||
setError('WebSocket 连接失败');
|
||||
setError(i18n.t('k8s.kubectl.failed'));
|
||||
setHasOutput(true);
|
||||
term.write('\r\n\x1b[31m[连接发生错误]\x1b[0m\r\n');
|
||||
term.write(i18n.t('k8s.kubectl.sc8107726'));
|
||||
};
|
||||
|
||||
return () => {
|
||||
@@ -189,7 +190,7 @@ export function KubectlTerminal({ open, onOpenChange, clusterId, clusterName }:
|
||||
{/* 终端容器 */}
|
||||
<div className="relative bg-[#0f172a]" style={{ height: fullscreen ? 'calc(100vh - 49px)' : '480px' }}>
|
||||
<div ref={containerRef} className="xterm-terminal-host h-full" />
|
||||
<TerminalLoadingOverlay show={!hasOutput && !error} label="正在连接集群..." />
|
||||
<TerminalLoadingOverlay show={!hasOutput && !error} label=i18n.t('k8s.kubectl.cluster') />
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -37,6 +37,7 @@ import { KubernetesClusterDialog } from './KubernetesClusterDialog';
|
||||
import { KubectlTerminal } from './KubectlTerminal';
|
||||
import { PageShell } from '@/components/layout/PageShell';
|
||||
import type { JsonData } from '@/types/common';
|
||||
import i18n from '@/i18n';
|
||||
|
||||
export function KubernetesClusters() {
|
||||
const { t } = useTranslation();
|
||||
@@ -63,7 +64,7 @@ export function KubernetesClusters() {
|
||||
const res: JsonData = await kubernetesApi.getClusters();
|
||||
setClusters(res?.list || []);
|
||||
} catch (e) {
|
||||
console.error('加载集群列表失败:', e);
|
||||
console.error(i18n.t('k8s.misc.cluster1'), e);
|
||||
setClusters([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -97,7 +98,7 @@ export function KubernetesClusters() {
|
||||
await kubernetesApi.deleteCluster(id);
|
||||
loadData();
|
||||
} catch (e) {
|
||||
console.error('删除集群失败:', e);
|
||||
console.error(i18n.t('k8s.misc.delete'), e);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -450,7 +450,7 @@ export default function NamespaceResourcePage() {
|
||||
const handleDeletePod = async (p: any) => {
|
||||
const ok = await confirm({
|
||||
title: i18n.t('kubernetes.pod.deletePod'),
|
||||
description: '确认删除 Pod "' + (p.name || '') + '"?操作不可恢复。',
|
||||
description: i18n.t('k8s.namespaceResource.delete1') + (p.name || '') + i18n.t('k8s.namespaceResource.actions'),
|
||||
confirmText: i18n.t('kubernetes.storage.delete2'),
|
||||
});
|
||||
if (!ok) return;
|
||||
|
||||
@@ -24,6 +24,7 @@ import { attachTerminalResize, mountXterm } from '@/lib/xterm-utils';
|
||||
import { TerminalLoadingOverlay } from '@/components/common/TerminalLoadingOverlay';
|
||||
import { kubernetesApi } from '@/api';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import i18n from '@/i18n';
|
||||
|
||||
export interface PodExecTerminalProps {
|
||||
open: boolean;
|
||||
@@ -96,7 +97,7 @@ export function PodExecTerminal({
|
||||
setConnState('connecting');
|
||||
setHasOutput(false);
|
||||
term.clear();
|
||||
term.writeln(`\x1b[36m[正在连接 ${podName}${container ? '/' + container : ''} shell=${shell} ...]\x1b[0m`);
|
||||
term.writeln(`\x1b[36m[${i18n.t('k8s.podExec.connecting')} ${podName}${container ? '/' + container : ''} shell=${shell} ...]\x1b[0m`);
|
||||
|
||||
const wsUrl = kubernetesApi.buildPodExecWsUrl(clusterId, namespace, podName, container || undefined, shell);
|
||||
|
||||
@@ -122,9 +123,9 @@ export function PodExecTerminal({
|
||||
term.write(msg.data);
|
||||
} else if (msg.status === 2 || msg.error) {
|
||||
setHasOutput(true);
|
||||
const errMsg = msg.data || msg.message || msg.error || '执行错误';
|
||||
const errMsg = msg.data || msg.message || msg.error || i18n.t('k8s.podExec.execError');
|
||||
if (errMsg.includes('not found') && shell === 'bash') {
|
||||
term.writeln('\r\n\x1b[33m⚠ bash 不可用,请切换 /bin/sh 后重连。\x1b[0m');
|
||||
term.writeln(i18n.t('k8s.podExec.reconnect'));
|
||||
errorShownRef.current = true;
|
||||
return;
|
||||
}
|
||||
@@ -142,7 +143,7 @@ export function PodExecTerminal({
|
||||
setConnState((s) => (s === 'error' ? s : 'closed'));
|
||||
setHasOutput(true);
|
||||
if (!errorShownRef.current) {
|
||||
term.write('\r\n\x1b[33m[连接已关闭]\x1b[0m\r\n');
|
||||
term.write(i18n.t('k8s.podExec.close'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -150,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(i18n.t('k8s.podExec.sc8107726'));
|
||||
};
|
||||
}, [clusterId, namespace, podName, container, shell]);
|
||||
|
||||
@@ -268,10 +269,10 @@ export function PodExecTerminal({
|
||||
const statusColor = connState === 'connected' ? 'bg-emerald-400'
|
||||
: connState === 'error' ? 'bg-red-400'
|
||||
: connState === 'closed' ? 'bg-[var(--dg-bg-active)]' : 'bg-amber-400';
|
||||
const statusText = connState === 'connected' ? '已连接'
|
||||
: connState === 'connecting' ? '连接中'
|
||||
: connState === 'error' ? '连接错误'
|
||||
: connState === 'closed' ? '已断开' : '空闲';
|
||||
const statusText = connState === 'connected' ? i18n.t('k8s.podExec.connected')
|
||||
: connState === 'connecting' ? i18n.t('k8s.podExec.connecting')
|
||||
: connState === 'error' ? i18n.t('k8s.podExec.connectionError')
|
||||
: connState === 'closed' ? i18n.t('k8s.podExec.disconnected1') : i18n.t('k8s.podExec.idle');
|
||||
|
||||
const handleTerminalClick = useCallback(() => {
|
||||
try { termRef.current?.focus(); } catch { /* noop */ }
|
||||
@@ -291,7 +292,7 @@ export function PodExecTerminal({
|
||||
style={fullscreen ? { maxWidth: '100vw', width: '100vw', height: '100vh' } : { maxWidth: '960px', width: '92vw' }}
|
||||
>
|
||||
<DialogTitle className="sr-only">exec — {podName}</DialogTitle>
|
||||
<DialogDescription className="sr-only">进入 Pod 容器执行命令</DialogDescription>
|
||||
<DialogDescription className="sr-only">{i18n.t('k8s.podExec.enterPod')}</DialogDescription>
|
||||
|
||||
{/* 工具栏 */}
|
||||
<div className="flex items-center justify-between gap-2 px-4 py-2 border-b border-[var(--dg-border)] flex-wrap">
|
||||
@@ -307,7 +308,7 @@ export function PodExecTerminal({
|
||||
<Select value={container} onValueChange={setContainer}>
|
||||
<SelectTrigger className="h-8 w-[150px] text-xs bg-[var(--dg-bg-hover)] border-[var(--dg-border)]">
|
||||
<Box className="h-3 w-3 mr-1 text-[var(--dg-text-muted)]" />
|
||||
<SelectValue placeholder="容器" />
|
||||
<SelectValue placeholder=i18n.t('k8s.podExec.container') />
|
||||
</SelectTrigger>
|
||||
<SelectContent>{containers.map((c) => <SelectItem key={c} value={c}>{c}</SelectItem>)}</SelectContent>
|
||||
</Select>
|
||||
@@ -319,13 +320,13 @@ export function PodExecTerminal({
|
||||
<SelectContent>{SHELLS.map(s => <SelectItem key={s.value} value={s.value}>{s.label}</SelectItem>)}</SelectContent>
|
||||
</Select>
|
||||
<Button variant="ghost" size="sm" className="h-8 px-2 text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-primary)]"
|
||||
onClick={() => { connect(); toast({ title: '正在重连…' }); }} title="重连">
|
||||
onClick={() => { connect(); toast({ title: i18n.t('k8s.podExec.reconnect1') }); }} title=i18n.t('k8s.podExec.reconnect2')>
|
||||
<RotateCw className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<button onClick={() => setFullscreen(!fullscreen)} className="p-1.5 rounded hover:bg-[var(--dg-bg-hover)] text-[var(--dg-text-secondary)]" title={fullscreen ? '退出全屏' : '全屏'}>
|
||||
<button onClick={() => setFullscreen(!fullscreen)} className="p-1.5 rounded hover:bg-[var(--dg-bg-hover)] text-[var(--dg-text-secondary)]" title={fullscreen ? i18n.t('k8s.podExec.exitFullscreen') : i18n.t('k8s.podExec.fullscreen')}>
|
||||
{fullscreen ? <Minimize2 className="h-4 w-4" /> : <Maximize2 className="h-4 w-4" />}
|
||||
</button>
|
||||
<button onClick={() => onOpenChange(false)} className="p-1.5 rounded hover:bg-[var(--dg-bg-hover)] text-[var(--dg-text-secondary)]" title="关闭">
|
||||
<button onClick={() => onOpenChange(false)} className="p-1.5 rounded hover:bg-[var(--dg-bg-hover)] text-[var(--dg-text-secondary)]" title=i18n.t('k8s.podExec.close1')>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -338,7 +339,7 @@ export function PodExecTerminal({
|
||||
className="xterm-terminal-host h-full cursor-text"
|
||||
onClick={handleTerminalClick}
|
||||
/>
|
||||
<TerminalLoadingOverlay show={connState === 'connecting' && !hasOutput} label="正在进入容器..." />
|
||||
<TerminalLoadingOverlay show={connState === 'connecting' && !hasOutput} label=i18n.t('k8s.podExec.container1') />
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { PageShell } from '@/components/layout/PageShell';
|
||||
import { ContentPanel } from '@/components/layout/ContentPanel';
|
||||
import { StatStrip } from '@/components/ui/stat-strip';
|
||||
import type { JsonData } from '@/types/common';
|
||||
import i18n from '@/i18n';
|
||||
|
||||
export default function PodMetricsPage() {
|
||||
const { clusterCode } = useParams<{ clusterCode: string }>();
|
||||
@@ -85,15 +86,15 @@ export default function PodMetricsPage() {
|
||||
return (
|
||||
<PageShell
|
||||
variant="list"
|
||||
title="Pod 资源用量"
|
||||
subtitle={clusterName ? `${clusterName} · 容器 CPU/内存实时指标` : '容器 CPU/内存实时指标'}
|
||||
title=i18n.t('k8s.podMetrics.resources2')
|
||||
subtitle={clusterName ? `${clusterName} · 容器 CPU/内存实时指标` : i18n.t('k8s.podMetrics.container2')}
|
||||
icon={<Activity className="h-5 w-5" />}
|
||||
metrics={!loading ? (
|
||||
<StatStrip
|
||||
items={[
|
||||
{ label: 'Pod 数', value: metrics.length, color: 'muted' },
|
||||
{ label: '总 CPU', value: `${totalCPU}m`, color: 'blue' },
|
||||
{ label: '总内存', value: `${totalMem.toFixed(0)}Mi`, color: 'green' },
|
||||
{ label: i18n.t('k8s.podMetrics.sa2e8040e'), 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' },
|
||||
]}
|
||||
/>
|
||||
) : undefined}
|
||||
@@ -106,7 +107,7 @@ export default function PodMetricsPage() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select value={selectedNs} onValueChange={setSelectedNs}>
|
||||
<SelectTrigger className="w-36 h-9 dg-input"><SelectValue placeholder="所有命名空间" /></SelectTrigger>
|
||||
<SelectTrigger className="w-36 h-9 dg-input"><SelectValue placeholder=i18n.t('k8s.podMetrics.namespace') /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">所有命名空间</SelectItem>
|
||||
{namespaces.map(n => <SelectItem key={n} value={n}>{n}</SelectItem>)}
|
||||
@@ -130,7 +131,7 @@ export default function PodMetricsPage() {
|
||||
</TableRow></TableHeader>
|
||||
<TableBody>
|
||||
{rows.length === 0 ? (
|
||||
<TableRow><TableCell colSpan={5} className="text-center dg-text-muted py-8">暂无数据{loading ? <span className="animate-spin inline-block ml-2">⏳</span> : ''}</TableCell></TableRow>
|
||||
<TableRow><TableCell colSpan={5} className="text-center dg-text-muted py-8i18n.t('k8s.podMetrics.s934752c9')animate-spin inline-block ml-2">⏳</span> : ''}</TableCell></TableRow>
|
||||
) : rows.map((r, i) => (
|
||||
<TableRow key={`${r.pod}-${r.container}-${i}`} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-hover)]">
|
||||
<TableCell className="font-medium text-[var(--dg-text-primary)] text-sm">{r.pod}</TableCell>
|
||||
|
||||
@@ -335,7 +335,7 @@ function ConfigMapEditor({ manifest, onSave, saving, isCreate }: { manifest: any
|
||||
<Textarea
|
||||
value={entry.value}
|
||||
onChange={e => update(i, 'value', e.target.value)}
|
||||
placeholder="值"
|
||||
placeholder=i18n.t('k8s.resourceEditor.sfe7509e0')
|
||||
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"
|
||||
/>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Label } from '@/components/ui/label';
|
||||
import { kubernetesApi } from '@/api';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { getErrorMessage } from '@/lib/utils';
|
||||
import i18n from '@/i18n';
|
||||
|
||||
export interface CreateHPADialogProps {
|
||||
clusterCode: string;
|
||||
@@ -29,7 +30,7 @@ export function CreateHPADialog({ clusterCode, namespace, deploymentName, onCrea
|
||||
|
||||
const handleCreate = async () => {
|
||||
if (!form.name.trim()) {
|
||||
toast({ title: '名称不能为空', variant: 'destructive' });
|
||||
toast({ title: i18n.t('k8s.misc.name'), variant: 'destructive' });
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
@@ -44,12 +45,12 @@ export function CreateHPADialog({ clusterCode, namespace, deploymentName, onCrea
|
||||
cpuPercent: form.cpuPercent,
|
||||
memPercent: form.memPercent,
|
||||
});
|
||||
toast({ title: 'HPA 创建成功' });
|
||||
toast({ title: i18n.t('k8s.misc.createSuccess1') });
|
||||
setOpen(false);
|
||||
onCreated();
|
||||
setForm({ name: '', minReplicas: 1, maxReplicas: 10, cpuPercent: 80, memPercent: 0 });
|
||||
} catch (e: unknown) {
|
||||
toast({ title: '创建失败', description: getErrorMessage(e), variant: 'destructive' });
|
||||
toast({ title: i18n.t('k8s.misc.failed1'), description: getErrorMessage(e), variant: 'destructive' });
|
||||
} finally { setLoading(false); }
|
||||
};
|
||||
|
||||
@@ -97,7 +98,7 @@ export function CreateHPADialog({ clusterCode, namespace, deploymentName, onCrea
|
||||
className="dg-input mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<Label className="dg-text-secondary text-sm">内存目标 (%) <span className="dg-text-muted">可选</span></Label>
|
||||
<Label className="dg-text-secondary text-smi18n.t('k8s.misc.memory1')dg-text-muted">可选</span></Label>
|
||||
<Input type="number" value={form.memPercent} min={0} max={100}
|
||||
onChange={(e) => setForm({ ...form, memPercent: Number(e.target.value) })}
|
||||
className="dg-input mt-1" />
|
||||
@@ -107,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" /> : '创建'}
|
||||
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : i18n.t('k8s.misc.sd9ac9228')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { kubernetesApi } from '@/api';
|
||||
import { prepareManifestForSave } from './k8s-manifest-utils';
|
||||
import { isCreateDeployment } from './deployment-constants';
|
||||
import type { JsonData } from '@/types/common';
|
||||
import i18n from '@/i18n';
|
||||
|
||||
export async function saveDeploymentManifest(
|
||||
clusterCode: string,
|
||||
@@ -11,7 +12,7 @@ export async function saveDeploymentManifest(
|
||||
manifest: JsonData,
|
||||
): Promise<string> {
|
||||
const resName = String(manifest?.metadata?.name || '').trim();
|
||||
if (!resName) throw new Error('Deployment 名称不能为空');
|
||||
if (!resName) throw new Error(i18n.t('k8s.misc.name1'));
|
||||
const yaml = await prepareManifestForSave(manifest);
|
||||
if (isCreateDeployment(deploymentName)) {
|
||||
await kubernetesApi.applyYAML(clusterCode, yaml);
|
||||
|
||||
Reference in new issue
Block a user