refactor(i18n): U34 System & User 模块 i18n 清理 — ProfilePage + ResetPasswordDialog + SystemRolesTab + LdapManagementSection 4 文件 UI 硬编码中文清零

- ProfilePage.tsx: 从零接入 useTranslation,基本信息/安全设置/密码修改/用户名邮箱手机/账号来源 15 处中文→t()
- ResetPasswordDialog.tsx: 从零接入 useTranslation,重置密码标题/描述/LDAP提示/新密码确认密码/确认重置 11 处中文→t()
- SystemRolesTab.tsx: 权限分组标签(云资源/自动化/审批/审计)+toast 校验+confirm description+角色计数+空状态+权限列表计数+编码标签+尾部文案 12 处中文→t()/i18n.t()
- LdapManagementSection.tsx: 验证按钮+根OU标签+创建按钮×2+导入按钮+编辑用户描述 8 处中文→t()
- 新增 user.profile.* 14键 + user.resetPwd.* 11键 + system.* 16键 共 41 翻译键
- zh/en 7524→7565 +41 对齐
- tsc -b=0, npm build✓ (16.05s), ESLint errors=0/any=0/unused=0, go build=0
- 17/17 集成回归测试通过
This commit is contained in:
attacker committed 2026-07-12 21:29:58 +08:00
1 parent ad9acea1e5
commit 0dbb85f5b8
7 files changed
+141 -46

No files matched your search

+1
View File
@@ -522,6 +522,7 @@ services/api.ts (1324L)
| U31 | AIOps Agent CLI 组件 i18n 修复(AgentCLI + AgentCLIToolbar + AgentCLIHistory + AgentCLIEmptyState + ChatInputArea + agent-cli-constants.ts 6 文件 — Q7/Q35 拆分后丢失的 i18n 集成全部修复,新增 `aiops.cli.*` 命名空间 100 翻译键(8 子分类: toolbar/safety/tips/error/info/viewMode/placeholder/history/empty/input),SAFETY_MODES+TIPS_HOST+TIPS_KUBERNETES 常量数组改为 `getSafetyModes(t)`/`getTipsHost(t)`/`getTipsKubernetes(t)` 接收 TFunction 参数的工厂函数,全部 UI 硬编码中文清零,zh/en 7381→7481 +100 键对齐,tsc -b=0,npm build✓ (18.21s),ESLint=0/53/0,go build=0,17/17 测试通过) | P3 | **done** | | U31 | AIOps Agent CLI 组件 i18n 修复(AgentCLI + AgentCLIToolbar + AgentCLIHistory + AgentCLIEmptyState + ChatInputArea + agent-cli-constants.ts 6 文件 — Q7/Q35 拆分后丢失的 i18n 集成全部修复,新增 `aiops.cli.*` 命名空间 100 翻译键(8 子分类: toolbar/safety/tips/error/info/viewMode/placeholder/history/empty/input),SAFETY_MODES+TIPS_HOST+TIPS_KUBERNETES 常量数组改为 `getSafetyModes(t)`/`getTipsHost(t)`/`getTipsKubernetes(t)` 接收 TFunction 参数的工厂函数,全部 UI 硬编码中文清零,zh/en 7381→7481 +100 键对齐,tsc -b=0,npm build✓ (18.21s),ESLint=0/53/0,go build=0,17/17 测试通过) | P3 | **done** |
| U32 | 全量 hash key 清理 + React Hooks 警告修复(151 个 hash-style flat key (s+hex 后缀) 全部重命名为语义化 nested key — monitor 58/kubernetes 42/aiops 17/agent 12/cloud 13/pipeline 6/system 3;58 个源文件 147 处 t()/i18n.t() 调用更新;3 个命名空间冲突修复(monitor.hostMonitor/agent.agentMonitor/aiops.aiAnalysisResult 原 string → .title 子键);React Hooks 警告 53→43 (-10): exhaustive-deps 4→0 + preserve-manual-memoization 5→0 + 变量遮蔽修复(filter(t)→filter(tg));zh/en 7480=7480 完全对齐 0 hash key 残留;tsc -b=0,npm build✓ (17.59s),ESLint=0/43/any=0,go build=0,17/17 测试通过) | P3 | **done** | | U32 | 全量 hash key 清理 + React Hooks 警告修复(151 个 hash-style flat key (s+hex 后缀) 全部重命名为语义化 nested key — monitor 58/kubernetes 42/aiops 17/agent 12/cloud 13/pipeline 6/system 3;58 个源文件 147 处 t()/i18n.t() 调用更新;3 个命名空间冲突修复(monitor.hostMonitor/agent.agentMonitor/aiops.aiAnalysisResult 原 string → .title 子键);React Hooks 警告 53→43 (-10): exhaustive-deps 4→0 + preserve-manual-memoization 5→0 + 变量遮蔽修复(filter(t)→filter(tg));zh/en 7480=7480 完全对齐 0 hash key 残留;tsc -b=0,npm build✓ (17.59s),ESLint=0/43/any=0,go build=0,17/17 测试通过) | P3 | **done** |
| U33 | K8s 资源编辑器 + Pod 日志/Metrics i18n 补全(resource-visual-editors/ 5 文件: ConfigMap/Secret/Service/Ingress/PvcEditor 按钮文案+帮助文本+访问模式+IngressClass 提示全部 i18n;pod-logs-viewer/ 3 文件: ControlBar 导出/刷新/实时/停止+LogContentArea 加载中+LogsHeader 标题;PodLogsViewer 6 处错误消息+DialogTitle/Description;PodMetricsPage subtitle+表头+所有命名空间;17 处 toast description 模板字面量中文→i18n.t() 插值 (DeploymentEditAdvanced/Containers/Storage/View/Detail/Service/ResourceVisualEditor/NamespaceResourcePage/ClusterOverviewPage);5 处 confirm description (DeploymentView/EditService/Detail/WorkloadViewPage/MiddlewareMonitor/SiteMonitor/HealthProbeSection);5 处 subtitle 模板字面量 (PodMetricsPage/CapacityPage/EventsPage/KubernetesNodes/ClusterOverviewPage);新增 k8s.rve 14键+k8s.toast 18键+k8s.podMetrics 4键+k8s.podLogs 13键 共 49 翻译键;zh/en 7481→7524 +43 对齐;tsc -b=0,npm build✓ (17.76s),ESLint=0/43/any=0,go build=0,17/17 测试通过) | P3 | **done** | | U33 | K8s 资源编辑器 + Pod 日志/Metrics i18n 补全(resource-visual-editors/ 5 文件: ConfigMap/Secret/Service/Ingress/PvcEditor 按钮文案+帮助文本+访问模式+IngressClass 提示全部 i18n;pod-logs-viewer/ 3 文件: ControlBar 导出/刷新/实时/停止+LogContentArea 加载中+LogsHeader 标题;PodLogsViewer 6 处错误消息+DialogTitle/Description;PodMetricsPage subtitle+表头+所有命名空间;17 处 toast description 模板字面量中文→i18n.t() 插值 (DeploymentEditAdvanced/Containers/Storage/View/Detail/Service/ResourceVisualEditor/NamespaceResourcePage/ClusterOverviewPage);5 处 confirm description (DeploymentView/EditService/Detail/WorkloadViewPage/MiddlewareMonitor/SiteMonitor/HealthProbeSection);5 处 subtitle 模板字面量 (PodMetricsPage/CapacityPage/EventsPage/KubernetesNodes/ClusterOverviewPage);新增 k8s.rve 14键+k8s.toast 18键+k8s.podMetrics 4键+k8s.podLogs 13键 共 49 翻译键;zh/en 7481→7524 +43 对齐;tsc -b=0,npm build✓ (17.76s),ESLint=0/43/any=0,go build=0,17/17 测试通过) | P3 | **done** |
| U34 | System & User 模块 i18n 清理(ProfilePage + ResetPasswordDialog + SystemRolesTab + LdapManagementSection 4 文件 — ProfilePage 从零接入 useTranslation + ResetPasswordDialog 从零接入 useTranslation + SystemRolesTab 权限分组标签(云资源/自动化/审批/审计)+toast+confirm description+角色计数+空状态+权限列表计数+编码标签+尾部文案全部 i18n + LdapManagementSection 验证按钮+根OU标签+创建按钮+导入按钮+编辑用户描述全部 i18n;新增 user.profile.* 14键+user.resetPwd.* 11键+system.* 16键 共 41 翻译键;UI 硬编码中文 ProfilePage 15→0 + ResetPasswordDialog 11→0 + SystemRolesTab 12→0 + LdapManagementSection 8→0;zh/en 7524→7565 +41 对齐;tsc -b=0,npm build✓ (16.05s),ESLint=0/43/any=0,go build=0,17/17 测试通过) | P3 | **done** |
--- ---
@@ -204,7 +204,7 @@ export function LdapManagementSection(props: LdapManagementSectionProps) {
disabled={ldapUserTesting || !ldapTestUser} disabled={ldapUserTesting || !ldapTestUser}
> >
{ldapUserTesting ? <Loader2 className="h-4 w-4 mr-1.5 animate-spin" /> : null} {ldapUserTesting ? <Loader2 className="h-4 w-4 mr-1.5 animate-spin" /> : null}
验证 {t('system.testConnection')}
</Button> </Button>
</div> </div>
</DialogContent> </DialogContent>
@@ -221,7 +221,7 @@ export function LdapManagementSection(props: LdapManagementSectionProps) {
<SelectValue placeholder={t('system.selectOu')} /> <SelectValue placeholder={t('system.selectOu')} />
</SelectTrigger> </SelectTrigger>
<SelectContent className="dg-input"> <SelectContent className="dg-input">
<SelectItem value={ldapConfig.base_dn}>根 ({ldapConfig.base_dn})</SelectItem> <SelectItem value={ldapConfig.base_dn}>{t('system.rootOu', { dn: ldapConfig.base_dn })}</SelectItem>
{ldapOUs.map((ou: JsonData) => ( {ldapOUs.map((ou: JsonData) => (
<SelectItem key={ou.dn} value={ou.dn}> <SelectItem key={ou.dn} value={ou.dn}>
{ou.description ? `${ou.name} (${ou.description})` : ou.name} {ou.description ? `${ou.name} (${ou.description})` : ou.name}
@@ -258,7 +258,7 @@ export function LdapManagementSection(props: LdapManagementSectionProps) {
</div> </div>
<Button className="w-full bg-blue-600 hover:bg-blue-700 text-white" onClick={handleCreateOU} disabled={!ouForm.ou_name || ldapSaving}> <Button className="w-full bg-blue-600 hover:bg-blue-700 text-white" onClick={handleCreateOU} disabled={!ouForm.ou_name || ldapSaving}>
{ldapSaving ? <Loader2 className="h-4 w-4 mr-1.5 animate-spin" /> : null} {ldapSaving ? <Loader2 className="h-4 w-4 mr-1.5 animate-spin" /> : null}
创建 {t('common.create')}
</Button> </Button>
</div> </div>
</DialogContent> </DialogContent>
@@ -300,7 +300,7 @@ export function LdapManagementSection(props: LdapManagementSectionProps) {
</div> </div>
<Button className="w-full bg-blue-600 hover:bg-blue-700 text-white" onClick={handleCreateLDAPUser} disabled={!newUser.username || !newUser.password || ldapSaving}> <Button className="w-full bg-blue-600 hover:bg-blue-700 text-white" onClick={handleCreateLDAPUser} disabled={!newUser.username || !newUser.password || ldapSaving}>
{ldapSaving ? <Loader2 className="h-4 w-4 mr-1.5 animate-spin" /> : null} {ldapSaving ? <Loader2 className="h-4 w-4 mr-1.5 animate-spin" /> : null}
创建 {t('common.create')}
</Button> </Button>
</div> </div>
</DialogContent> </DialogContent>
@@ -315,7 +315,7 @@ export function LdapManagementSection(props: LdapManagementSectionProps) {
disabled={ldapUsers.length === 0 || importing} disabled={ldapUsers.length === 0 || importing}
> >
{importing ? <Loader2 className="h-3.5 w-3.5 mr-1 animate-spin" /> : <Download className="h-3.5 w-3.5 mr-1" />} {importing ? <Loader2 className="h-3.5 w-3.5 mr-1 animate-spin" /> : <Download className="h-3.5 w-3.5 mr-1" />}
{selectedLDAPUsers.length > 0 ? `导入 (${selectedLDAPUsers.length})` : `导入全部 (${ldapUsers.length})`} {selectedLDAPUsers.length > 0 ? t('system.importSelected', { count: selectedLDAPUsers.length }) : t('system.importAll', { count: ldapUsers.length })}
</Button> </Button>
</div> </div>
@@ -427,7 +427,7 @@ export function LdapManagementSection(props: LdapManagementSectionProps) {
{t('system.editLdapUser')} {t('system.editLdapUser')}
</DialogTitle> </DialogTitle>
<DialogDescription className="dg-text-muted"> <DialogDescription className="dg-text-muted">
修改 {editLdapUser?.uid} 的属性 {t('system.editLdapUserDesc', { uid: editLdapUser?.uid })}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
{editLdapUser && ( {editLdapUser && (
@@ -1,5 +1,6 @@
// 管理员重置用户密码弹窗 // Logan.Li // 管理员重置用户密码弹窗 // Logan.Li
import { useState } from 'react'; import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
@@ -17,6 +18,7 @@ interface ResetPasswordDialogProps {
} }
export function ResetPasswordDialog({ user, onClose }: ResetPasswordDialogProps) { export function ResetPasswordDialog({ user, onClose }: ResetPasswordDialogProps) {
const { t } = useTranslation();
const { toast } = useToast(); const { toast } = useToast();
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [confirm, setConfirm] = useState(''); const [confirm, setConfirm] = useState('');
@@ -30,12 +32,12 @@ export function ResetPasswordDialog({ user, onClose }: ResetPasswordDialogProps)
setSaving(true); setSaving(true);
try { try {
await userApi.resetUserPassword(user.id, password); await userApi.resetUserPassword(user.id, password);
toast({ title: '密码已重置', description: `用户 ${user.nickname || user.username} 的新密码已生效` }); toast({ title: t('user.resetPwd.success'), description: t('user.resetPwd.successDesc', { name: user.nickname || user.username }) });
setPassword(''); setPassword('');
setConfirm(''); setConfirm('');
onClose(); onClose();
} catch (e: unknown) { } catch (e: unknown) {
toast({ title: '重置失败', description: getErrorMessage(e), variant: 'destructive' }); toast({ title: t('user.resetPwd.failed'), description: getErrorMessage(e), variant: 'destructive' });
} finally { } finally {
setSaving(false); setSaving(false);
} }
@@ -47,34 +49,34 @@ export function ResetPasswordDialog({ user, onClose }: ResetPasswordDialogProps)
<DialogHeader> <DialogHeader>
<DialogTitle className="text-[var(--dg-text-primary)] flex items-center gap-2"> <DialogTitle className="text-[var(--dg-text-primary)] flex items-center gap-2">
<Key className="h-5 w-5 text-[var(--dg-blue)]" /> <Key className="h-5 w-5 text-[var(--dg-blue)]" />
重置密码 {t('user.resetPwd.title')}
</DialogTitle> </DialogTitle>
<DialogDescription className="text-[var(--dg-text-muted)]"> <DialogDescription className="text-[var(--dg-text-muted)]">
{user ? `为「${user.nickname || user.username}」设置新密码` : ''} {user ? t('user.resetPwd.desc', { name: user.nickname || user.username }) : ''}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
{isLdap ? ( {isLdap ? (
<p className="text-sm dg-text-yellow/90 py-2">LDAP 用户请在 LDAP 目录中管理密码,不支持本地重置。</p> <p className="text-sm dg-text-yellow/90 py-2">{t('user.resetPwd.ldapHint')}</p>
) : ( ) : (
<div className="space-y-3 py-1"> <div className="space-y-3 py-1">
<div> <div>
<Label className="text-xs text-[var(--dg-text-muted)]">新密码</Label> <Label className="text-xs text-[var(--dg-text-muted)]">{t('user.resetPwd.newPwd')}</Label>
<Input <Input
type="password" type="password"
value={password} value={password}
onChange={e => setPassword(e.target.value)} onChange={e => setPassword(e.target.value)}
placeholder="至少 6 位" placeholder={t('user.resetPwd.newPwdPlaceholder')}
className="mt-1 h-9 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" className="mt-1 h-9 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]"
/> />
</div> </div>
<div> <div>
<Label className="text-xs text-[var(--dg-text-muted)]">确认密码</Label> <Label className="text-xs text-[var(--dg-text-muted)]">{t('user.resetPwd.confirmPwd')}</Label>
<Input <Input
type="password" type="password"
value={confirm} value={confirm}
onChange={e => setConfirm(e.target.value)} onChange={e => setConfirm(e.target.value)}
placeholder="再次输入新密码" placeholder={t('user.resetPwd.confirmPlaceholder')}
className="mt-1 h-9 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" className="mt-1 h-9 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]"
/> />
</div> </div>
@@ -82,11 +84,11 @@ export function ResetPasswordDialog({ user, onClose }: ResetPasswordDialogProps)
)} )}
<DialogFooter> <DialogFooter>
<Button variant="outline" className="border-[var(--dg-border)]" onClick={onClose}>取消</Button> <Button variant="outline" className="border-[var(--dg-border)]" onClick={onClose}>{t('common.cancel')}</Button>
{!isLdap && ( {!isLdap && (
<Button onClick={handleReset} disabled={!canSubmit || saving} className="bg-[var(--dg-blue)] hover:opacity-90"> <Button onClick={handleReset} disabled={!canSubmit || saving} className="bg-[var(--dg-blue)] hover:opacity-90">
{saving ? <Loader2 className="h-4 w-4 animate-spin mr-1" /> : null} {saving ? <Loader2 className="h-4 w-4 animate-spin mr-1" /> : null}
确认重置 {t('user.resetPwd.confirm')}
</Button> </Button>
)} )}
</DialogFooter> </DialogFooter>
@@ -35,14 +35,14 @@ const ROLE_ICONS: Record<string, string> = {
const PERM_MODULE_GROUPS: { label: string; prefixes: string[] }[] = [ const PERM_MODULE_GROUPS: { label: string; prefixes: string[] }[] = [
{ label: i18n.t('menu.dashboard'), prefixes: ['dashboard:'] }, { label: i18n.t('menu.dashboard'), prefixes: ['dashboard:'] },
{ label: i18n.t('menu.assets'), prefixes: ['server:'] }, { label: i18n.t('menu.assets'), prefixes: ['server:'] },
{ label: '云资源', prefixes: ['cloud:'] }, { label: i18n.t('system.permCloudResource'), prefixes: ['cloud:'] },
{ label: 'K8s', prefixes: ['k8s:'] }, { label: 'K8s', prefixes: ['k8s:'] },
{ label: i18n.t('common.publish'), prefixes: ['deploy:'] }, { label: i18n.t('common.publish'), prefixes: ['deploy:'] },
{ label: i18n.t('monitor.hostDashboard.monitor'), prefixes: ['monitor:', 'agent:'] }, { label: i18n.t('monitor.hostDashboard.monitor'), prefixes: ['monitor:', 'agent:'] },
{ label: '自动化', prefixes: ['auto:'] }, { label: i18n.t('system.permAutomation'), prefixes: ['auto:'] },
{ label: 'AI', prefixes: ['aiops:'] }, { label: 'AI', prefixes: ['aiops:'] },
{ label: '审批', prefixes: ['approval:'] }, { label: i18n.t('system.permApproval'), prefixes: ['approval:'] },
{ label: '审计', prefixes: ['audit:'] }, { label: i18n.t('system.permAudit'), prefixes: ['audit:'] },
{ label: i18n.t('common.system3'), prefixes: ['system:'] }, { label: i18n.t('common.system3'), prefixes: ['system:'] },
]; ];
@@ -138,8 +138,8 @@ export function SystemRolesTab({ selectedRole, setSelectedRole }: SystemRolesTab
}; };
const handleSubmit = async () => { const handleSubmit = async () => {
if (!form.name.trim()) { toast.error('请输入角色名称'); return; } if (!form.name.trim()) { toast.error(t('system.roleNameRequired')); return; }
if (!form.code.trim()) { toast.error('请输入角色编码'); return; } if (!form.code.trim()) { toast.error(t('system.roleCodeRequired')); return; }
setSaving(true); setSaving(true);
try { try {
const payload = { const payload = {
@@ -171,7 +171,7 @@ export function SystemRolesTab({ selectedRole, setSelectedRole }: SystemRolesTab
} }
const ok = await confirm({ const ok = await confirm({
title: t('system.deleteRole'), title: t('system.deleteRole'),
description: `确认删除角色「${role.name}」吗?该操作不可恢复。`, description: t('system.confirmDeleteRoleDesc', { name: role.name }),
confirmText: t('common.delete'), confirmText: t('common.delete'),
variant: 'destructive', variant: 'destructive',
}); });
@@ -192,12 +192,12 @@ export function SystemRolesTab({ selectedRole, setSelectedRole }: SystemRolesTab
<ShieldCheck className="h-5 w-5 dg-text-blue shrink-0 mt-0.5" /> <ShieldCheck className="h-5 w-5 dg-text-blue shrink-0 mt-0.5" />
<div className="text-sm text-[var(--dg-text-secondary)] space-y-1"> <div className="text-sm text-[var(--dg-text-secondary)] space-y-1">
<p className="font-medium text-[var(--dg-text-primary)]">{t('system.permHowEffect')}</p> <p className="font-medium text-[var(--dg-text-primary)]">{t('system.permHowEffect')}</p>
<p>{t('system.permEffectDesc')}<strong className="text-[var(--dg-text-primary)]">{t('system.permBackendAuth')}</strong>{t('monitor.logCenterArch.connector')}<strong className="text-[var(--dg-text-primary)]">{t('system.permEffectFrontend')}</strong>,修改后用户需重新登录生效。</p> <p>{t('system.permEffectDesc')}<strong className="text-[var(--dg-text-primary)]">{t('system.permBackendAuth')}</strong>{t('monitor.logCenterArch.connector')}<strong className="text-[var(--dg-text-primary)]">{t('system.permEffectFrontend')}</strong>{t('system.permEffectTrailing')}</p>
</div> </div>
</div> </div>
<div className="flex items-center justify-end gap-2"> <div className="flex items-center justify-end gap-2">
<span className="text-xs text-[var(--dg-text-muted)]">{roles.length} 个角色</span> <span className="text-xs text-[var(--dg-text-muted)]">{t('system.roleCount', { count: roles.length })}</span>
<Button className="bg-blue-600 hover:bg-blue-700 text-white" onClick={openCreate}> <Button className="bg-blue-600 hover:bg-blue-700 text-white" onClick={openCreate}>
<Plus className="h-4 w-4 mr-2" />{t('pipeline.newRole')} <Plus className="h-4 w-4 mr-2" />{t('pipeline.newRole')}
</Button> </Button>
@@ -210,7 +210,7 @@ export function SystemRolesTab({ selectedRole, setSelectedRole }: SystemRolesTab
) : roles.length === 0 ? ( ) : roles.length === 0 ? (
<div className="flex flex-col items-center justify-center py-16 text-[var(--dg-text-muted)]"> <div className="flex flex-col items-center justify-center py-16 text-[var(--dg-text-muted)]">
<Shield className="h-10 w-10 mb-3 opacity-40" /> <Shield className="h-10 w-10 mb-3 opacity-40" />
<p className="text-sm">暂无角色数据</p> <p className="text-sm">{t('system.noRoleData')}</p>
</div> </div>
) : ( ) : (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
@@ -321,7 +321,7 @@ function RoleDetailDialog({
</Badge> </Badge>
)} )}
</DialogTitle> </DialogTitle>
<DialogDescription className="text-[var(--dg-text-muted)]">编码:{role?.code}</DialogDescription> <DialogDescription className="text-[var(--dg-text-muted)]">{t('system.roleCodeLabel', { code: role?.code })}</DialogDescription>
</DialogHeader> </DialogHeader>
<div className="space-y-4"> <div className="space-y-4">
<div className="rounded-lg bg-[var(--dg-bg-tertiary)] p-4"> <div className="rounded-lg bg-[var(--dg-bg-tertiary)] p-4">
@@ -330,7 +330,7 @@ function RoleDetailDialog({
</div> </div>
<div> <div>
<h4 className="text-sm font-medium text-[var(--dg-text-primary)] mb-2"> <h4 className="text-sm font-medium text-[var(--dg-text-primary)] mb-2">
权限列表 ({role?.permissions?.length || 0} 项) {t('system.permListCount', { count: role?.permissions?.length || 0 })}
</h4> </h4>
{role && (role.permissions as string[]).length > 0 ? ( {role && (role.permissions as string[]).length > 0 ? (
<div className="flex flex-wrap gap-2 max-h-60 overflow-y-auto"> <div className="flex flex-wrap gap-2 max-h-60 overflow-y-auto">
+17 -15
View File
@@ -1,5 +1,6 @@
// ProfilePage — 个人中心(基本信息 / 安全 / API 访问密钥)// Logan.Li // ProfilePage — 个人中心(基本信息 / 安全 / API 访问密钥)// Logan.Li
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
@@ -29,6 +30,7 @@ type Profile = {
} }
export default function ProfilePage() { export default function ProfilePage() {
const { t } = useTranslation();
const { toast } = useToast(); const { toast } = useToast();
const storeUser = useUserStore(s => s.user); const storeUser = useUserStore(s => s.user);
@@ -51,7 +53,7 @@ export default function ProfilePage() {
useEffect(() => { load(); }, [load]); useEffect(() => { load(); }, [load]);
const displayName = profile?.real_name || profile?.nickname || storeUser?.nickname || storeUser?.username || '用户'; const displayName = profile?.real_name || profile?.nickname || storeUser?.nickname || storeUser?.username || t('user.profile.defaultUser');
const username = profile?.username || storeUser?.username || ''; const username = profile?.username || storeUser?.username || '';
const handleChangePassword = async () => { const handleChangePassword = async () => {
@@ -59,11 +61,11 @@ export default function ProfilePage() {
setPwdSaving(true); setPwdSaving(true);
try { try {
await authApi.changePassword({ old_password: oldPwd, new_password: newPwd }); await authApi.changePassword({ old_password: oldPwd, new_password: newPwd });
toast({ title: '密码已修改' }); toast({ title: t('user.profile.pwdChanged') });
setOldPwd(''); setOldPwd('');
setNewPwd(''); setNewPwd('');
} catch (e: unknown) { } catch (e: unknown) {
toast({ title: '修改失败', description: getErrorMessage(e), variant: 'destructive' }); toast({ title: t('user.profile.pwdChangeFailed'), description: getErrorMessage(e), variant: 'destructive' });
} finally { } finally {
setPwdSaving(false); setPwdSaving(false);
} }
@@ -77,7 +79,7 @@ export default function ProfilePage() {
subtitle={`@${username} · ${profile?.email || '—'}`} subtitle={`@${username} · ${profile?.email || '—'}`}
icon={<User className="h-5 w-5" />} icon={<User className="h-5 w-5" />}
loading={loading} loading={loading}
loadingLabel="加载中..." loadingLabel={t('common.loading')}
actions={( actions={(
<div className="flex gap-2 flex-wrap"> <div className="flex gap-2 flex-wrap">
{(profile?.roles || []).map((r) => ( {(profile?.roles || []).map((r) => (
@@ -92,15 +94,15 @@ export default function ProfilePage() {
<Card className="dg-card"> <Card className="dg-card">
<CardHeader className="pb-3"> <CardHeader className="pb-3">
<CardTitle className="text-base flex items-center gap-2 text-[var(--dg-text-primary)]"> <CardTitle className="text-base flex items-center gap-2 text-[var(--dg-text-primary)]">
<User className="h-4 w-4 text-[var(--dg-blue)]" />基本信息 <User className="h-4 w-4 text-[var(--dg-blue)]" />{t('user.profile.basicInfo')}
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-3 text-sm"> <CardContent className="space-y-3 text-sm">
<div className="flex justify-between"><span className="text-[var(--dg-text-muted)]">用户名</span><span className="font-mono">{username}</span></div> <div className="flex justify-between"><span className="text-[var(--dg-text-muted)]">{t('user.profile.username')}</span><span className="font-mono">{username}</span></div>
<div className="flex justify-between"><span className="text-[var(--dg-text-muted)]">邮箱</span><span>{profile?.email || '—'}</span></div> <div className="flex justify-between"><span className="text-[var(--dg-text-muted)]">{t('user.profile.email')}</span><span>{profile?.email || '—'}</span></div>
<div className="flex justify-between"><span className="text-[var(--dg-text-muted)]">手机</span><span>{profile?.phone || '—'}</span></div> <div className="flex justify-between"><span className="text-[var(--dg-text-muted)]">{t('user.profile.phone')}</span><span>{profile?.phone || '—'}</span></div>
<div className="flex justify-between"><span className="text-[var(--dg-text-muted)]">账号来源</span> <div className="flex justify-between"><span className="text-[var(--dg-text-muted)]">{t('user.profile.authSource')}</span>
<Badge variant="outline" className="text-xs">{profile?.auth_source === 'ldap' ? 'LDAP' : '本地'}</Badge> <Badge variant="outline" className="text-xs">{profile?.auth_source === 'ldap' ? 'LDAP' : t('user.profile.local')}</Badge>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
@@ -108,22 +110,22 @@ export default function ProfilePage() {
<Card className="dg-card"> <Card className="dg-card">
<CardHeader className="pb-3"> <CardHeader className="pb-3">
<CardTitle className="text-base flex items-center gap-2 text-[var(--dg-text-primary)]"> <CardTitle className="text-base flex items-center gap-2 text-[var(--dg-text-primary)]">
<Shield className="h-4 w-4 dg-text-green" />安全设置 <Shield className="h-4 w-4 dg-text-green" />{t('user.profile.security')}
</CardTitle> </CardTitle>
<CardDescription className="text-xs">修改登录密码</CardDescription> <CardDescription className="text-xs">{t('user.profile.changePwd')}</CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-3"> <CardContent className="space-y-3">
<div> <div>
<Label className="text-xs text-[var(--dg-text-muted)]">当前密码</Label> <Label className="text-xs text-[var(--dg-text-muted)]">{t('user.profile.currentPwd')}</Label>
<Input type="password" value={oldPwd} onChange={e => setOldPwd(e.target.value)} className="mt-1 h-9 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" /> <Input type="password" value={oldPwd} onChange={e => setOldPwd(e.target.value)} className="mt-1 h-9 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" />
</div> </div>
<div> <div>
<Label className="text-xs text-[var(--dg-text-muted)]">新密码</Label> <Label className="text-xs text-[var(--dg-text-muted)]">{t('user.profile.newPwd')}</Label>
<Input type="password" value={newPwd} onChange={e => setNewPwd(e.target.value)} className="mt-1 h-9 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" /> <Input type="password" value={newPwd} onChange={e => setNewPwd(e.target.value)} className="mt-1 h-9 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" />
</div> </div>
<Button size="sm" onClick={handleChangePassword} disabled={pwdSaving || !oldPwd || newPwd.length < 6}> <Button size="sm" onClick={handleChangePassword} disabled={pwdSaving || !oldPwd || newPwd.length < 6}>
{pwdSaving ? <Loader2 className="h-4 w-4 animate-spin mr-1" /> : null} {pwdSaving ? <Loader2 className="h-4 w-4 animate-spin mr-1" /> : null}
保存密码 {t('user.profile.savePwd')}
</Button> </Button>
</CardContent> </CardContent>
</Card> </Card>
+46 -1
View File
@@ -7455,7 +7455,23 @@
"loadFailed": "Failed to load audit settings", "loadFailed": "Failed to load audit settings",
"invalidRetentionDays": "Please select valid retention days", "invalidRetentionDays": "Please select valid retention days",
"noExpiredRecords": ", no expired records" "noExpiredRecords": ", no expired records"
} },
"permCloudResource": "Cloud Resources",
"permAutomation": "Automation",
"permApproval": "Approval",
"permAudit": "Audit",
"roleCodeRequired": "Please enter role code",
"confirmDeleteRoleDesc": "Are you sure to delete role \"{{name}}\"? This action cannot be undone.",
"roleCount": "{{count}} roles",
"noRoleData": "No role data",
"permListCount": "Permissions ({{count}})",
"roleCodeLabel": "Code: {{code}}",
"testConnection": "Verify",
"rootOu": "Root ({{dn}})",
"importSelected": "Import ({{count}})",
"importAll": "Import All ({{count}})",
"editLdapUserDesc": "Modify attributes of {{uid}}",
"permEffectTrailing": ", changes take effect after user re-login."
}, },
"aiops": { "aiops": {
"title": "AIOps", "title": "AIOps",
@@ -8273,6 +8289,35 @@
"credentialCopied": "Credential copied", "credentialCopied": "Credential copied",
"saved": "I've saved it", "saved": "I've saved it",
"placeholderToken": "Your access credential" "placeholderToken": "Your access credential"
},
"profile": {
"basicInfo": "Basic Info",
"security": "Security",
"changePwd": "Change Login Password",
"currentPwd": "Current Password",
"newPwd": "New Password",
"savePwd": "Save Password",
"username": "Username",
"email": "Email",
"phone": "Phone",
"authSource": "Account Source",
"local": "Local",
"pwdChanged": "Password Changed",
"pwdChangeFailed": "Change Failed",
"defaultUser": "User"
},
"resetPwd": {
"title": "Reset Password",
"desc": "Set a new password for \"{{name}}\"",
"ldapHint": "LDAP users must manage passwords in the LDAP directory. Local reset is not supported.",
"newPwd": "New Password",
"newPwdPlaceholder": "At least 6 characters",
"confirmPwd": "Confirm Password",
"confirmPlaceholder": "Enter the new password again",
"confirm": "Confirm Reset",
"success": "Password Reset",
"successDesc": "New password for user {{name}} is now active",
"failed": "Reset Failed"
} }
}, },
"logCenter": { "logCenter": {
+46 -1
View File
@@ -7455,7 +7455,23 @@
"loadFailed": "加载审计设置失败", "loadFailed": "加载审计设置失败",
"invalidRetentionDays": "请选择有效的保留天数", "invalidRetentionDays": "请选择有效的保留天数",
"noExpiredRecords": ",无超期记录" "noExpiredRecords": ",无超期记录"
} },
"permCloudResource": "云资源",
"permAutomation": "自动化",
"permApproval": "审批",
"permAudit": "审计",
"roleCodeRequired": "请输入角色编码",
"confirmDeleteRoleDesc": "确认删除角色「{{name}}」吗?该操作不可恢复。",
"roleCount": "{{count}} 个角色",
"noRoleData": "暂无角色数据",
"permListCount": "权限列表 ({{count}} 项)",
"roleCodeLabel": "编码:{{code}}",
"testConnection": "验证",
"rootOu": "根 ({{dn}})",
"importSelected": "导入 ({{count}})",
"importAll": "导入全部 ({{count}})",
"editLdapUserDesc": "修改 {{uid}} 的属性",
"permEffectTrailing": ",修改后用户需重新登录生效。"
}, },
"aiops": { "aiops": {
"title": "AIOps 智能", "title": "AIOps 智能",
@@ -8273,6 +8289,35 @@
"credentialCopied": "凭证已复制", "credentialCopied": "凭证已复制",
"saved": "我已保存", "saved": "我已保存",
"placeholderToken": "你的访问凭证" "placeholderToken": "你的访问凭证"
},
"profile": {
"basicInfo": "基本信息",
"security": "安全设置",
"changePwd": "修改登录密码",
"currentPwd": "当前密码",
"newPwd": "新密码",
"savePwd": "保存密码",
"username": "用户名",
"email": "邮箱",
"phone": "手机",
"authSource": "账号来源",
"local": "本地",
"pwdChanged": "密码已修改",
"pwdChangeFailed": "修改失败",
"defaultUser": "用户"
},
"resetPwd": {
"title": "重置密码",
"desc": "为「{{name}}」设置新密码",
"ldapHint": "LDAP 用户请在 LDAP 目录中管理密码,不支持本地重置。",
"newPwd": "新密码",
"newPwdPlaceholder": "至少 6 位",
"confirmPwd": "确认密码",
"confirmPlaceholder": "再次输入新密码",
"confirm": "确认重置",
"success": "密码已重置",
"successDesc": "用户 {{name}} 的新密码已生效",
"failed": "重置失败"
} }
}, },
"logCenter": { "logCenter": {