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

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

No files matched your search

@@ -1,4 +1,5 @@
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Key, Eye, EyeOff } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@@ -19,6 +20,7 @@ export interface CredFormDialogProps {
// 凭据新增/编辑弹窗 // Logan.Li
export function CredFormDialog({ open, onClose, onSaved, editData }: CredFormDialogProps) {
const { t } = useTranslation();
const isEdit = !!editData;
const [name, setName] = useState('');
const [varName, setVarName] = useState('');
@@ -95,7 +97,7 @@ export function CredFormDialog({ open, onClose, onSaved, editData }: CredFormDia
const toggleShow = (key: string) => setShowMap(prev => ({ ...prev, [key]: !prev[key] }));
const handleSubmit = async () => {
if (!name.trim()) { toast.error('请填写凭据名称'); return; }
if (!name.trim()) { toast.error(t('deploy.cred.form.nameRequired')); return; }
setSubmitting(true);
try {
const payload: Record<string, unknown> = {
@@ -141,16 +143,16 @@ export function CredFormDialog({ open, onClose, onSaved, editData }: CredFormDia
if (isEdit) {
await credentialApi.update(editData!.id, payload);
toast.success('已更新');
toast.success(t('deploy.cred.form.updated'));
} else {
await credentialApi.create(payload);
toast.success('已创建');
toast.success(t('deploy.cred.form.created'));
}
onSaved();
onClose();
} catch (e: unknown) {
const err = e as { response?: { data?: { message?: string } } };
toast.error(err?.response?.data?.message || '操作失败');
toast.error(err?.response?.data?.message || t('deploy.cred.form.operationFailed'));
} finally { setSubmitting(false); }
};
@@ -190,29 +192,29 @@ export function CredFormDialog({ open, onClose, onSaved, editData }: CredFormDia
<DialogHeader>
<DialogTitle className="text-[var(--dg-text-primary)] flex items-center gap-2">
<TIicon className={`h-5 w-5 ${TI?.color || ''}`} />
{isEdit ? '编辑凭据' : '新增凭据'}
{isEdit ? t('deploy.cred.form.editTitle') : t('deploy.cred.form.newTitle')}
</DialogTitle>
<DialogDescription className="text-[var(--dg-text-muted)] text-xs">
{TI?.desc || '管理敏感凭据信息'}
{TI?.desc || t('deploy.cred.descDefault')}
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-2">
{/* 基本信息 // Logan.Li */}
<div className="border-b border-[var(--dg-border)] pb-3">
<div className="text-xs font-medium text-[var(--dg-text-secondary)] mb-2">基本信息</div>
<div className="text-xs font-medium text-[var(--dg-text-secondary)] mb-2">{t('deploy.cred.form.basicInfo')}</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<Label className="text-[var(--dg-text-secondary)] text-xs">凭据名称 <span className="dg-text-red">*</span></Label>
<Input value={name} onChange={e => setName(e.target.value)} placeholder="如:生产环境 SSH"
<Label className="text-[var(--dg-text-secondary)] text-xs">{t('deploy.cred.credName')} <span className="dg-text-red">*</span></Label>
<Input value={name} onChange={e => setName(e.target.value)} placeholder={t('deploy.cred.form.credNamePlaceholder')}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
</div>
<div className="space-y-1">
<Label className="text-[var(--dg-text-secondary)] text-xs">变量别名</Label>
<Input value={varName} onChange={e => setVarName(e.target.value)} placeholder="如:MYSQL_PROD"
<Label className="text-[var(--dg-text-secondary)] text-xs">{t('deploy.cred.form.varAlias')}</Label>
<Input value={varName} onChange={e => setVarName(e.target.value)} placeholder={t('deploy.cred.form.varAliasPlaceholder')}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] font-mono text-sm" />
</div>
<div className="space-y-1">
<Label className="text-[var(--dg-text-secondary)] text-xs">类型{!isEdit && <span className="dg-text-red"> *</span>}</Label>
<Label className="text-[var(--dg-text-secondary)] text-xs">{t('common.type')}{!isEdit && <span className="dg-text-red"> *</span>}</Label>
<Select value={secretType} onValueChange={(v) => setSecretType(v as SecretType)} disabled={isEdit}>
<SelectTrigger className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]">
<SelectValue />
@@ -227,14 +229,14 @@ export function CredFormDialog({ open, onClose, onSaved, editData }: CredFormDia
</Select>
</div>
<div className="space-y-1">
<Label className="text-[var(--dg-text-secondary)] text-xs">标签</Label>
<Input value={tags} onChange={e => setTags(e.target.value)} placeholder="生产环境, 阿里云"
<Label className="text-[var(--dg-text-secondary)] text-xs">{t('deploy.cred.form.tagsLabel')}</Label>
<Input value={tags} onChange={e => setTags(e.target.value)} placeholder={t('deploy.cred.form.tagsPlaceholder')}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
</div>
</div>
<div className="mt-2 space-y-1">
<Label className="text-[var(--dg-text-secondary)] text-xs">描述</Label>
<Input value={description} onChange={e => setDescription(e.target.value)} placeholder="可选备注信息"
<Label className="text-[var(--dg-text-secondary)] text-xs">{t('common.description')}</Label>
<Input value={description} onChange={e => setDescription(e.target.value)} placeholder={t('deploy.cred.form.descPlaceholder')}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
</div>
</div>
@@ -242,33 +244,33 @@ export function CredFormDialog({ open, onClose, onSaved, editData }: CredFormDia
{/* 类型专属字段 // Logan.Li */}
<div>
<div className="text-xs font-medium text-[var(--dg-text-secondary)] mb-2 flex items-center gap-2">
<TIicon className="h-3.5 w-3.5" />{TI?.label || secretType} 配置
{isEdit && <span className="text-[var(--dg-text-muted)]">(仅填写需要修改的字段)</span>}
<TIicon className="h-3.5 w-3.5" />{TI?.label || secretType} {t('deploy.cred.form.typeConfig')}
{isEdit && <span className="text-[var(--dg-text-muted)]">{t('deploy.cred.form.editHint')}</span>}
</div>
{secretType === 'ssh' && (
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<Label className="text-[var(--dg-text-secondary)] text-xs">SSH 用户</Label>
<Label className="text-[var(--dg-text-secondary)] text-xs">{t('deploy.cred.form.sshUser')}</Label>
<Input value={sshUser} onChange={e => setSshUser(e.target.value)} placeholder="root"
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
</div>
<div className="space-y-1">
<Label className="text-[var(--dg-text-secondary)] text-xs">SSH 端口</Label>
<Label className="text-[var(--dg-text-secondary)] text-xs">{t('deploy.cred.form.sshPort')}</Label>
<Input value={sshPort} onChange={e => setSshPort(e.target.value)} placeholder="22" type="number"
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
</div>
<div className="col-span-2">
<FieldWithToggle label="SSH 密码" value={sshPassword} onChange={setSshPassword}
placeholder="密码(或留空用密钥)" fieldKey="ssh_pwd" />
<FieldWithToggle label={t('deploy.cred.form.sshPassword')} value={sshPassword} onChange={setSshPassword}
placeholder={t('deploy.cred.form.sshPwdPlaceholder')} fieldKey="ssh_pwd" />
</div>
<div className="col-span-2">
<FieldWithToggle label="私钥内容" value={sshPrivateKey} onChange={setSshPrivateKey}
placeholder="粘贴 SSH 私钥..." fieldKey="ssh_pk" isLong />
<FieldWithToggle label={t('deploy.cred.form.privateKey')} value={sshPrivateKey} onChange={setSshPrivateKey}
placeholder={t('deploy.cred.form.privateKeyPlaceholder')} fieldKey="ssh_pk" isLong />
</div>
<div className="col-span-2">
<FieldWithToggle label="公钥内容" value={sshPublicKey} onChange={setSshPublicKey}
placeholder="粘贴 SSH 公钥..." fieldKey="ssh_pub" isLong />
<FieldWithToggle label={t('deploy.cred.form.publicKey')} value={sshPublicKey} onChange={setSshPublicKey}
placeholder={t('deploy.cred.form.publicKeyPlaceholder')} fieldKey="ssh_pub" isLong />
</div>
</div>
)}
@@ -276,28 +278,28 @@ export function CredFormDialog({ open, onClose, onSaved, editData }: CredFormDia
{secretType === 'docker' && (
<div className="grid grid-cols-2 gap-3">
<div className="col-span-2 space-y-1">
<Label className="text-[var(--dg-text-secondary)] text-xs">Registry 地址 <span className="dg-text-red">*</span></Label>
<Label className="text-[var(--dg-text-secondary)] text-xs">{t('deploy.cred.form.registryAddr')} <span className="dg-text-red">*</span></Label>
<Input value={dockerServer} onChange={e => setDockerServer(e.target.value)} placeholder="registry.example.com"
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] font-mono" />
</div>
<div className="space-y-1">
<Label className="text-[var(--dg-text-secondary)] text-xs">用户名</Label>
<Label className="text-[var(--dg-text-secondary)] text-xs">{t('common.username')}</Label>
<Input value={dockerUsername} onChange={e => setDockerUsername(e.target.value)} placeholder="admin"
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
</div>
<div><FieldWithToggle label="密码" value={dockerPassword} onChange={setDockerPassword}
placeholder="Registry 密码" fieldKey="docker_pwd" required /></div>
<div><FieldWithToggle label={t('common.password')} value={dockerPassword} onChange={setDockerPassword}
placeholder={t('deploy.cred.form.registryPwdPlaceholder')} fieldKey="docker_pwd" required /></div>
</div>
)}
{secretType === 'tls' && (
<div className="space-y-3">
<FieldWithToggle label="证书 (PEM)" value={tlsCert} onChange={setTlsCert}
<FieldWithToggle label={t('deploy.cred.form.certPem')} value={tlsCert} onChange={setTlsCert}
placeholder="-----BEGIN CERTIFICATE-----" fieldKey="tls_cert" isLong required />
<FieldWithToggle label="私钥 (PEM)" value={tlsKey} onChange={setTlsKey}
<FieldWithToggle label={t('deploy.cred.form.keyPem')} value={tlsKey} onChange={setTlsKey}
placeholder="-----BEGIN PRIVATE KEY-----" fieldKey="tls_key" isLong required />
<FieldWithToggle label="CA 证书" value={tlsCa} onChange={setTlsCa}
placeholder="CA 证书内容..." fieldKey="tls_ca" isLong />
<FieldWithToggle label={t('deploy.cred.form.caCert')} value={tlsCa} onChange={setTlsCa}
placeholder={t('deploy.cred.form.caCertPlaceholder')} fieldKey="tls_ca" isLong />
</div>
)}
@@ -310,7 +312,7 @@ export function CredFormDialog({ open, onClose, onSaved, editData }: CredFormDia
</div>
<div className="col-span-2">
<FieldWithToggle label="Secret Access Key" value={akskSecretKey} onChange={setAkskSecretKey}
placeholder="输入 Secret Key" fieldKey="aksk_sk" required />
placeholder={t('deploy.cred.form.sshPwdPlaceholder')} fieldKey="aksk_sk" required />
</div>
</div>
)}
@@ -318,7 +320,7 @@ export function CredFormDialog({ open, onClose, onSaved, editData }: CredFormDia
{secretType === 'database' && (
<div className="grid grid-cols-3 gap-3">
<div className="space-y-1">
<Label className="text-[var(--dg-text-secondary)] text-xs">数据库类型</Label>
<Label className="text-[var(--dg-text-secondary)] text-xs">{t('deploy.cred.form.dbType')}</Label>
<Select value={dbType} onValueChange={setDbType}>
<SelectTrigger className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]">
<SelectValue />
@@ -334,38 +336,38 @@ export function CredFormDialog({ open, onClose, onSaved, editData }: CredFormDia
</Select>
</div>
<div className="space-y-1">
<Label className="text-[var(--dg-text-secondary)] text-xs">主机 <span className="dg-text-red">*</span></Label>
<Label className="text-[var(--dg-text-secondary)] text-xs">{t('deploy.cred.form.host')} <span className="dg-text-red">*</span></Label>
<Input value={dbHost} onChange={e => setDbHost(e.target.value)} placeholder="10.0.0.1"
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] font-mono" />
</div>
<div className="space-y-1">
<Label className="text-[var(--dg-text-secondary)] text-xs">端口</Label>
<Label className="text-[var(--dg-text-secondary)] text-xs">{t('common.port')}</Label>
<Input value={dbPort} onChange={e => setDbPort(e.target.value)} placeholder="3306" type="number"
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
</div>
<div className="space-y-1">
<Label className="text-[var(--dg-text-secondary)] text-xs">数据库名</Label>
<Label className="text-[var(--dg-text-secondary)] text-xs">{t('deploy.cred.form.dbName')}</Label>
<Input value={dbName} onChange={e => setDbName(e.target.value)} placeholder="aiops"
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
</div>
<div className="space-y-1">
<Label className="text-[var(--dg-text-secondary)] text-xs">用户名</Label>
<Label className="text-[var(--dg-text-secondary)] text-xs">{t('common.username')}</Label>
<Input value={dbUsername} onChange={e => setDbUsername(e.target.value)} placeholder="root"
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
</div>
<div><FieldWithToggle label="密码" value={dbPassword} onChange={setDbPassword}
placeholder="数据库密码" fieldKey="db_pwd" /></div>
<div><FieldWithToggle label={t('common.password')} value={dbPassword} onChange={setDbPassword}
placeholder={t('deploy.cred.form.dbPwdPlaceholder')} fieldKey="db_pwd" /></div>
</div>
)}
{secretType === 'api' && (
<div className="space-y-3">
<div className="space-y-1">
<Label className="text-[var(--dg-text-secondary)] text-xs">API 端点</Label>
<Label className="text-[var(--dg-text-secondary)] text-xs">{t('deploy.cred.form.apiEndpoint')}</Label>
<Input value={apiEndpoint} onChange={e => setApiEndpoint(e.target.value)} placeholder="https://api.example.com"
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] font-mono" />
</div>
<FieldWithToggle label="API Key / Token" value={apiKey} onChange={setApiKey}
<FieldWithToggle label={t('deploy.cred.form.apiKeyToken')} value={apiKey} onChange={setApiKey}
placeholder="sk-xxxx..." fieldKey="api_key" required />
</div>
)}
@@ -378,15 +380,15 @@ export function CredFormDialog({ open, onClose, onSaved, editData }: CredFormDia
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] font-mono" />
</div>
<FieldWithToggle label="Value" value={genericValue} onChange={setGenericValue}
placeholder="密钥值" fieldKey="generic_val" required />
placeholder={t('deploy.cred.form.secretValuePlaceholder')} fieldKey="generic_val" required />
</div>
)}
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={onClose}>取消</Button>
<Button variant="outline" onClick={onClose}>{t('common.cancel')}</Button>
<Button onClick={handleSubmit} disabled={submitting} className="bg-[var(--dg-blue)] hover:bg-[var(--dg-blue)]/80">
{submitting ? '保存中...' : (isEdit ? '更新' : '创建')}
{submitting ? t('deploy.cred.form.saving') : (isEdit ? t('common.update') : t('common.create'))}
</Button>
</DialogFooter>
</DialogContent>