feat(i18n): deploy 模块(Pipeline/Credential)国际化增量迁移 — 翻译键 deploy 命名空间(455键) + CredentialManager 补全 3 处 UI 文案
- PipelineManager.tsx: 流水线全量翻译(标题/搜索/CRUD/阶段/任务/运行/横幅) - CredentialManager.tsx: 操作按钮 title 属性补全(编辑/禁用启用/删除) - zh-CN.json + en-US.json: deploy 命名空间 455 键完全对齐 - 总翻译键: 1019(zh) = 1019(en) - tsc -b 零错误, npm build 成功(17.42s)
This commit is contained in:
1 parent
ed7a5c07a7
commit
01967bc52b
4 files changed
+1053
-67
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Plus, Pencil, Trash2, RefreshCw, Key, Shield, Search,
|
||||
Clock, CheckCircle2, XCircle, Zap,
|
||||
@@ -17,6 +18,7 @@ import { CredFormDialog } from './credential-manager/CredFormDialog';
|
||||
|
||||
// 凭据管理页编排器 // Logan.Li
|
||||
export default function CredentialManager() {
|
||||
const { t } = useTranslation();
|
||||
const confirm = useConfirm();
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [editing, setEditing] = useState<Credential | null>(null);
|
||||
@@ -38,28 +40,28 @@ export default function CredentialManager() {
|
||||
|
||||
const handleDelete = async (cred: Credential) => {
|
||||
const ok = await confirm({
|
||||
title: '删除凭据',
|
||||
description: `确定删除凭据「${cred.name}」?此操作不可撤销。`,
|
||||
confirmText: '删除',
|
||||
title: t('deploy.cred.deleteCred'),
|
||||
description: t('deploy.pipeline.deleteConfirm', { name: cred.name }),
|
||||
confirmText: t('common.delete'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
if (!ok) return;
|
||||
try {
|
||||
await deleteMutation.mutateAsync(cred.id);
|
||||
toast.success('已删除');
|
||||
} catch { toast.error('删除失败'); }
|
||||
toast.success(t('deploy.toast.deleted'));
|
||||
} catch { toast.error(t('deploy.toast.deleteFailed')); }
|
||||
};
|
||||
|
||||
const handleToggleActive = async (cred: Credential) => {
|
||||
try {
|
||||
await toggleActiveMutation.mutateAsync({ id: cred.id, isActive: !cred.is_active });
|
||||
toast.success(cred.is_active ? '已禁用' : '已启用');
|
||||
} catch { toast.error('操作失败'); }
|
||||
toast.success(cred.is_active ? t('deploy.toast.disabled') : t('deploy.toast.enabled'));
|
||||
} catch { toast.error(t('deploy.toast.operationFailed')); }
|
||||
};
|
||||
|
||||
const handleCopyPrefix = (prefix: string) => {
|
||||
navigator.clipboard.writeText(prefix);
|
||||
toast.success('引用 ID 已复制');
|
||||
toast.success(t('deploy.cred.refIdCopied'));
|
||||
};
|
||||
|
||||
const typeOptions = Object.entries(TYPE_CONFIG) as [SecretType, typeof TYPE_CONFIG[SecretType]][];
|
||||
@@ -68,17 +70,17 @@ export default function CredentialManager() {
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-[var(--dg-text-primary)]">凭据管理</h2>
|
||||
<h2 className="text-lg font-semibold text-[var(--dg-text-primary)]">{t('deploy.cred.title')}</h2>
|
||||
<p className="text-sm text-[var(--dg-text-muted)] mt-0.5">
|
||||
统一管理 SSH 密钥、Docker 密码、AKSK、数据库等敏感凭据(AES 加密存储)
|
||||
{t('deploy.cred.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="sm" onClick={() => refetch()}>
|
||||
<RefreshCw className="w-4 h-4 mr-1" />刷新
|
||||
<RefreshCw className="w-4 h-4 mr-1" />{t('common.refresh')}
|
||||
</Button>
|
||||
<Button size="sm" onClick={() => { setEditing(null); setDialogOpen(true); }}>
|
||||
<Plus className="w-4 h-4 mr-1" />新增凭据
|
||||
<Plus className="w-4 h-4 mr-1" />{t('deploy.cred.addCredential')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -87,10 +89,10 @@ export default function CredentialManager() {
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<Select value={filterType} onValueChange={setFilterType}>
|
||||
<SelectTrigger className="w-[140px] bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] h-9 text-sm">
|
||||
<SelectValue placeholder="全部类型" />
|
||||
<SelectValue placeholder={t('deploy.label.allType')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部类型</SelectItem>
|
||||
<SelectItem value="all">{t('deploy.label.allType')}</SelectItem>
|
||||
{typeOptions.map(([k, v]) => (
|
||||
<SelectItem key={k} value={k}>
|
||||
<span className="flex items-center gap-2"><v.icon className="h-3.5 w-3.5" />{v.label}</span>
|
||||
@@ -101,23 +103,23 @@ export default function CredentialManager() {
|
||||
<div className="relative flex-1 max-w-xs">
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--dg-text-muted)]" />
|
||||
<Input value={filterSearch} onChange={e => setFilterSearch(e.target.value)}
|
||||
placeholder="搜索名称、描述..."
|
||||
placeholder={t('deploy.label.searchPlaceholder')}
|
||||
className="pl-8 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] h-9 text-sm" />
|
||||
</div>
|
||||
<span className="text-xs text-[var(--dg-text-muted)]">{credentials.length} 个凭据</span>
|
||||
<span className="text-xs text-[var(--dg-text-muted)]">{t('deploy.cred.credCount', { count: credentials.length })}</span>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="text-center py-16 text-[var(--dg-text-secondary)]">
|
||||
<Zap className="w-6 h-6 animate-spin mx-auto mb-2 text-[var(--dg-blue)]" />加载中...
|
||||
<Zap className="w-6 h-6 animate-spin mx-auto mb-2 text-[var(--dg-blue)]" />{t('deploy.label.loading')}
|
||||
</div>
|
||||
) : credentials.length === 0 ? (
|
||||
<div className="text-center py-16 border border-dashed border-[var(--dg-border)] rounded-lg">
|
||||
<Shield className="w-12 h-12 mx-auto mb-3 text-[var(--dg-text-muted)]" />
|
||||
<p className="text-[var(--dg-text-secondary)] mb-2">暂无凭据</p>
|
||||
<p className="text-xs text-[var(--dg-text-muted)] mb-4">创建 SSH、Docker、AKSK 等类型的凭据,供流水线和服务器使用</p>
|
||||
<p className="text-[var(--dg-text-secondary)] mb-2">{t('deploy.cred.noCredentials')}</p>
|
||||
<p className="text-xs text-[var(--dg-text-muted)] mb-4">{t('deploy.cred.subtitle')}</p>
|
||||
<Button onClick={() => { setEditing(null); setDialogOpen(true); }}>
|
||||
<Plus className="w-4 h-4 mr-1" />创建第一个凭据
|
||||
<Plus className="w-4 h-4 mr-1" />{t('deploy.cred.createFirst')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
@@ -125,13 +127,13 @@ export default function CredentialManager() {
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-[var(--dg-bg-tertiary)] border-b border-[var(--dg-border)]">
|
||||
<tr>
|
||||
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)]">名称</th>
|
||||
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)]">类型</th>
|
||||
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)] hidden md:table-cell">摘要</th>
|
||||
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)] hidden lg:table-cell">标签</th>
|
||||
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)] hidden xl:table-cell">引用ID</th>
|
||||
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)]">状态</th>
|
||||
<th className="px-3 py-2.5 text-right font-medium text-[var(--dg-text-secondary)]">操作</th>
|
||||
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)]">{t('deploy.table.name')}</th>
|
||||
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)]">{t('deploy.table.type')}</th>
|
||||
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)] hidden md:table-cell">{t('deploy.table.repoUrl')}</th>
|
||||
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)] hidden lg:table-cell">{t('deploy.table.tagLabel')}</th>
|
||||
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)] hidden xl:table-cell">{t('deploy.cred.refId')}</th>
|
||||
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)]">{t('deploy.table.status')}</th>
|
||||
<th className="px-3 py-2.5 text-right font-medium text-[var(--dg-text-secondary)]">{t('deploy.table.actions')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -181,28 +183,28 @@ export default function CredentialManager() {
|
||||
<td className="px-3 py-2.5">
|
||||
<div className="flex items-center gap-1">
|
||||
{cred.is_active ? (
|
||||
<Badge className="bg-green-500/20 text-green-400 text-xs border-0">启用</Badge>
|
||||
<Badge className="bg-green-500/20 text-green-400 text-xs border-0">{t('common.enable')}</Badge>
|
||||
) : (
|
||||
<Badge className="bg-red-500/20 text-red-400 text-xs border-0">禁用</Badge>
|
||||
<Badge className="bg-red-500/20 text-red-400 text-xs border-0">{t('common.disable')}</Badge>
|
||||
)}
|
||||
{isExpired && (
|
||||
<Badge className="bg-yellow-500/20 text-yellow-400 text-xs border-0"><Clock className="h-2.5 w-2.5 mr-0.5" />过期</Badge>
|
||||
<Badge className="bg-yellow-500/20 text-yellow-400 text-xs border-0"><Clock className="h-2.5 w-2.5 mr-0.5" />{t('deploy.status.expired')}</Badge>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 py-2.5 text-right" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="flex justify-end gap-1">
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7"
|
||||
onClick={() => { setEditing(cred as Credential); setDialogOpen(true); }} title="编辑">
|
||||
onClick={() => { setEditing(cred as Credential); setDialogOpen(true); }} title={t('common.edit')}>
|
||||
<Pencil className="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7"
|
||||
onClick={() => handleToggleActive(cred as Credential)}
|
||||
title={cred.is_active ? '禁用' : '启用'}>
|
||||
title={cred.is_active ? t('common.disable') : t('common.enable')}>
|
||||
{cred.is_active ? <XCircle className="w-3.5 h-3.5 text-yellow-500" /> : <CheckCircle2 className="w-3.5 h-3.5 text-green-500" />}
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7 text-red-500"
|
||||
onClick={() => handleDelete(cred as Credential)} title="删除">
|
||||
onClick={() => handleDelete(cred as Credential)} title={t('common.delete')}>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user