feat(i18n): 剩余模块全量国际化 — 51% 覆盖率 (231 文件接入 t())
批量转换 163 个文件,3204 处中文替换为 t() 调用: - Monitor: 日志中心/告警中心/数据源/大屏/站点拨测/SNMP/中间件 全部转换 - Kubernetes: ResourceVisualEditor/NamespaceResourcePage/拓扑/事件 全部转换 - Deploy: 流水线/发布/凭证/RBAC 全部转换 - Server/Cloud/Agent/Task/Layout/UI: 全部转换 - 翻译词典扩展至 6474 行 (22 namespaces, 5000+ keys) - tsc 编译零错误
This commit is contained in:
1 parent
78c27037e3
commit
ecec73989b
166 files changed
+6925
-2800
No files matched your search
@@ -17,6 +17,8 @@ import UserGroupsManager from './UserGroupsManager';
|
||||
import { JobRoleCreateDialog } from './deploy-rbac/JobRoleCreateDialog';
|
||||
import { matchPipelines, pipelineDisplayLabel, type PipelineMatchItem } from './deploy-rbac/matchPipeline';
|
||||
import type { JsonData } from '@/types/common';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import i18n from '@/i18n';
|
||||
|
||||
interface ItemRole {
|
||||
id: number;
|
||||
@@ -37,15 +39,16 @@ interface AssignedUser {
|
||||
}
|
||||
|
||||
const ROLE_LABEL: Record<string, string> = {
|
||||
owner: '拥有者',
|
||||
editor: '编辑者',
|
||||
viewer: '查看者',
|
||||
owner: i18n.t('common.owner'),
|
||||
editor: i18n.t('pipeline.editor'),
|
||||
viewer: i18n.t('pipeline.viewer'),
|
||||
};
|
||||
|
||||
const RBAC_TABS = ['roles', 'users', 'preview', 'groups'] as const;
|
||||
|
||||
// 发布 Job 权限 — 正则匹配 code / job_name,Jenkins Role Strategy 风格 // Logan.Li
|
||||
export default function DeployRBAC() {
|
||||
const { t } = useTranslation();
|
||||
const [searchParams] = useSearchParams();
|
||||
const tabFromUrl = searchParams.get('tab');
|
||||
const [roles, setRoles] = useState<ItemRole[]>([]);
|
||||
@@ -110,7 +113,7 @@ export default function DeployRBAC() {
|
||||
|
||||
const handleCreate = async () => {
|
||||
if (!newName.trim() || !newCode.trim() || !newPattern.trim()) {
|
||||
toast.error('请填写名称、标识与正则'); return;
|
||||
toast.error(t('deploy.deployrbac.sf6e80b')); return;
|
||||
}
|
||||
setSaving(true);
|
||||
try {
|
||||
@@ -121,84 +124,84 @@ export default function DeployRBAC() {
|
||||
role: newRole,
|
||||
description: newDesc,
|
||||
});
|
||||
toast.success('已创建');
|
||||
toast.success(t('deploy.toast.created'));
|
||||
setCreateOpen(false);
|
||||
setNewName(''); setNewCode(''); setNewPattern(''); setNewRole('editor'); setNewDesc('');
|
||||
loadData();
|
||||
} catch (e: unknown) {
|
||||
toast.error((e as Error)?.message || '创建失败');
|
||||
toast.error((e as Error)?.message || t('monitor.quickSetupFailed'));
|
||||
} finally { setSaving(false); }
|
||||
};
|
||||
|
||||
const handleUpdate = async (id: number) => {
|
||||
if (!editPattern.trim()) { toast.error('正则不能为空'); return; }
|
||||
if (!editPattern.trim()) { toast.error(t('pipeline.regexEmpty')); return; }
|
||||
setSaving(true);
|
||||
try {
|
||||
await deployApi.updateItemRole(id, { pattern: editPattern, role: editRole });
|
||||
toast.success('已更新');
|
||||
toast.success(t('deploy.toast.updated'));
|
||||
setEditing(null);
|
||||
loadData();
|
||||
} catch (e: unknown) {
|
||||
toast.error((e as Error)?.message || '更新失败');
|
||||
toast.error((e as Error)?.message || t('alert.updateFailed'));
|
||||
} finally { setSaving(false); }
|
||||
};
|
||||
|
||||
const handleDelete = async (role: ItemRole) => {
|
||||
const ok = await _confirm({
|
||||
title: '删除权限',
|
||||
description: `确定删除「${role.name}」?将影响 ${matchCount[role.id]?.length || 0} 个发布任务`,
|
||||
title: t('deploy.deployrbac.s5d79e4'),
|
||||
description: t('deploy.deployrbac.s2c1ef5', { role.name: role.name, matchCount[role.id]?.length || 0: matchCount[role.id]?.length || 0 }),
|
||||
variant: 'destructive',
|
||||
});
|
||||
if (!ok) return;
|
||||
try { await deployApi.deleteItemRole(role.id); toast.success('已删除'); loadData(); }
|
||||
catch { toast.error('删除失败'); }
|
||||
try { await deployApi.deleteItemRole(role.id); toast.success(t('alert.deleted')); loadData(); }
|
||||
catch { toast.error(t('common.deleteFailed')); }
|
||||
};
|
||||
|
||||
const handleToggleStatus = async (role: ItemRole) => {
|
||||
try {
|
||||
await deployApi.updateItemRole(role.id, { status: role.status ? 0 : 1 });
|
||||
toast.success(role.status ? '已禁用' : '已启用');
|
||||
toast.success(role.status ? t('alert.disabled') : t('alert.enabled'));
|
||||
loadData();
|
||||
} catch { toast.error('操作失败'); }
|
||||
} catch { toast.error(t('common.operationFailed')); }
|
||||
};
|
||||
|
||||
const handleAssign = async () => {
|
||||
if (!selectedUserId || !assignRoleId) { toast.error('请选择用户和权限'); return; }
|
||||
if (!selectedUserId || !assignRoleId) { toast.error(t('deploy.deployrbac.s0fc539')); return; }
|
||||
setAssignLoading(true);
|
||||
try {
|
||||
await deployApi.assignItemRole(Number(selectedUserId), {
|
||||
user_id: Number(selectedUserId),
|
||||
item_role_id: Number(assignRoleId),
|
||||
});
|
||||
toast.success('已分配');
|
||||
toast.success(t('common.assigned'));
|
||||
setAssignRoleId('');
|
||||
loadAssignments();
|
||||
} catch (e: unknown) {
|
||||
toast.error((e as Error)?.message || '分配失败');
|
||||
toast.error((e as Error)?.message || t('deploy.rbac.assignFail'));
|
||||
} finally { setAssignLoading(false); }
|
||||
};
|
||||
|
||||
const handleRevoke = async (id: number) => {
|
||||
try {
|
||||
await deployApi.removeItemRole(Number(selectedUserId), id);
|
||||
toast.success('已撤销');
|
||||
toast.success(t('deploy.toast.revoked'));
|
||||
loadAssignments();
|
||||
} catch { toast.error('撤销失败'); }
|
||||
} catch { toast.error(t('deploy.toast.revokeFailed')); }
|
||||
};
|
||||
|
||||
const selectedUser = users.find((u) => String(u.id) === selectedUserId);
|
||||
const pageTabs = [
|
||||
{ id: 'roles', label: '权限策略', badge: roles.length || undefined },
|
||||
{ id: 'users', label: '用户授权' },
|
||||
{ id: 'groups', label: '用户组' },
|
||||
{ id: 'preview', label: '命中预览' },
|
||||
{ id: 'roles', label: t('deploy.deployrbac.s93ef07'), badge: roles.length || undefined },
|
||||
{ id: 'users', label: t('deploy.deployrbac.s2de895') },
|
||||
{ id: 'groups', label: t('pipeline.userGroup') },
|
||||
{ id: 'preview', label: t('pipeline.matchPreview') },
|
||||
];
|
||||
|
||||
return (
|
||||
<PageShell
|
||||
variant="list"
|
||||
title="Job 权限"
|
||||
subtitle="按流水线 code / Job 名称正则分配发布权限"
|
||||
title=t('deploy.deployNav.jobPermission')
|
||||
subtitle=t('deploy.deployrbac.sa596d6')
|
||||
icon={<Shield className="h-5 w-5" />}
|
||||
loading={loading}
|
||||
tabs={pageTabs}
|
||||
@@ -215,9 +218,9 @@ export default function DeployRBAC() {
|
||||
roles.length === 0 ? (
|
||||
<PageEmpty
|
||||
icon={<Shield className="h-10 w-10" />}
|
||||
title="暂无 Job 权限策略"
|
||||
description="创建策略后,用户将按正则自动获得匹配流水线的访问权限"
|
||||
action={<Button onClick={() => setCreateOpen(true)}><Plus className="h-4 w-4 mr-1" />新建权限</Button>}
|
||||
title=t('deploy.deployrbac.sca578c')
|
||||
description=t('deploy.deployrbac.s5ac382')
|
||||
action={<Button onClick={() => setCreateOpen(true)}><Plus className="h-4 w-4 mr-1" />{t('deploy.deployrbac.s605884')}</Button>}
|
||||
/>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
@@ -231,7 +234,7 @@ export default function DeployRBAC() {
|
||||
<Badge variant="outline" className="text-[10px] border-[var(--dg-border)]">
|
||||
{ROLE_LABEL[role.role] || role.role}
|
||||
</Badge>
|
||||
{role.status === 0 && <Badge variant="outline" className="text-[10px]">已禁用</Badge>}
|
||||
{role.status === 0 && <Badge variant="outline" className="text-[10px]">{t('alert.disabled')}</Badge>}
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="text-[10px] cursor-pointer border-[var(--dg-border)]"
|
||||
@@ -252,9 +255,9 @@ export default function DeployRBAC() {
|
||||
<Select value={editRole} onValueChange={setEditRole}>
|
||||
<SelectTrigger className="h-7 w-20 text-xs"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="owner">拥有者</SelectItem>
|
||||
<SelectItem value="editor">编辑者</SelectItem>
|
||||
<SelectItem value="viewer">查看者</SelectItem>
|
||||
<SelectItem value="owner">{t('common.owner')}</SelectItem>
|
||||
<SelectItem value="editor">{t('pipeline.editor')}</SelectItem>
|
||||
<SelectItem value="viewer">{t('pipeline.viewer')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button size="icon" variant="ghost" className="h-7 w-7" onClick={() => handleUpdate(role.id)}><Check className="w-3.5 h-3.5" /></Button>
|
||||
@@ -262,8 +265,8 @@ export default function DeployRBAC() {
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button variant="ghost" size="sm" className="h-7 text-xs" onClick={() => { setEditing(role.id); setEditPattern(role.pattern); setEditRole(role.role); }}>编辑</Button>
|
||||
<Button variant="ghost" size="sm" className="h-7 text-xs" onClick={() => handleToggleStatus(role)}>{role.status ? '禁用' : '启用'}</Button>
|
||||
<Button variant="ghost" size="sm" className="h-7 text-xs" onClick={() => { setEditing(role.id); setEditPattern(role.pattern); setEditRole(role.role); }}>{t('common.edit')}</Button>
|
||||
<Button variant="ghost" size="sm" className="h-7 text-xs" onClick={() => handleToggleStatus(role)}>{role.status ? t('common.disable') : t('common.enable')}</Button>
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7 text-[var(--dg-red)]" onClick={() => handleDelete(role)}><Trash2 className="w-3.5 h-3.5" /></Button>
|
||||
</>
|
||||
)}
|
||||
@@ -281,9 +284,9 @@ export default function DeployRBAC() {
|
||||
<CardContent className="p-4 space-y-4">
|
||||
<div className="flex flex-wrap gap-3 items-end">
|
||||
<div className="space-y-1 flex-1 min-w-[200px]">
|
||||
<Label className="text-xs">用户</Label>
|
||||
<Label className="text-xs">{t('system.roleUsers')}</Label>
|
||||
<Select value={selectedUserId} onValueChange={setSelectedUserId}>
|
||||
<SelectTrigger className="h-8 text-sm dg-input"><SelectValue placeholder="选择用户" /></SelectTrigger>
|
||||
<SelectTrigger className="h-8 text-sm dg-input"><SelectValue placeholder=t('deploy.rbac.selectUser') /></SelectTrigger>
|
||||
<SelectContent className="max-h-60">
|
||||
{users.map((u) => (
|
||||
<SelectItem key={u.id as number} value={String(u.id)}>
|
||||
@@ -294,9 +297,9 @@ export default function DeployRBAC() {
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-1 w-48">
|
||||
<Label className="text-xs">权限策略</Label>
|
||||
<Label className="text-xs">{t('deploy.deployrbac.s93ef07')}</Label>
|
||||
<Select value={assignRoleId} onValueChange={setAssignRoleId}>
|
||||
<SelectTrigger className="h-8 text-sm dg-input"><SelectValue placeholder="选择策略" /></SelectTrigger>
|
||||
<SelectTrigger className="h-8 text-sm dg-input"><SelectValue placeholder=t('kubernetes.hpa.selectPolicy') /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{roles.filter((r) => r.status === 1).map((r) => (
|
||||
<SelectItem key={r.id} value={String(r.id)}>{r.name} ({ROLE_LABEL[r.role]})</SelectItem>
|
||||
@@ -311,7 +314,7 @@ export default function DeployRBAC() {
|
||||
</div>
|
||||
{selectedUser ? (
|
||||
assignments.length === 0 ? (
|
||||
<p className="text-xs text-[var(--dg-text-muted)]">该用户尚未分配 Job 权限</p>
|
||||
<p className="text-xs text-[var(--dg-text-muted)]">{t('deploy.deployrbac.sef9548')}</p>
|
||||
) : (
|
||||
<div className="space-y-1.5">
|
||||
{assignments.map((a) => {
|
||||
@@ -332,7 +335,7 @@ export default function DeployRBAC() {
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<p className="text-xs text-[var(--dg-text-muted)] flex items-center gap-1"><Users className="w-3.5 h-3.5" />选择用户后查看已分配权限</p>
|
||||
<p className="text-xs text-[var(--dg-text-muted)] flex items-center gap-1"><Users className="w-3.5 h-3.5" />{t('deploy.deployrbac.s9be00c')}</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -367,7 +370,7 @@ export default function DeployRBAC() {
|
||||
</Badge>
|
||||
))}
|
||||
{previewRoleId !== null && previewMatches.length === 0 && (
|
||||
<span className="text-xs text-[var(--dg-text-muted)]">无匹配 — 请调整正则</span>
|
||||
<span className="text-xs text-[var(--dg-text-muted)]">{t('deploy.deployrbac.s64096f')}</span>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
Reference in new issue
Block a user