refactor(i18n): U44 残余硬编码中文清零 — 终端审计状态+凭证类型+流水线阶段+时间格式+CSV导出

20 文件 ~70 处 UI 硬编码中文清零:
- credential-manager/constants.ts: 8 凭证类型 label/desc → labelKey/descKey + i18n.t()
- pipeline-run-detail/types.ts: 6 阶段 label → i18n.t('deploy.stage.*') + labelKey
- pipeline-manager/constants.ts + pipeline-editor/types.ts: 分秒 duration → i18n.t('common.duration.minSec')
- LiveMonitorDialog: 6 终端状态消息 → t()
- RdpAuditViewer: 6 RDP审计状态 → i18n.t('monitor.rdp.*')
- WorkspaceTermDialog: 2 终端连接状态 → t()
- TerminalSessionsTab: 4 时长格式+状态标签 → i18n.t()/t()
- k8s-event-utils + QueryHistoryPanel: 6 相对时间 → i18n.t('common.duration.*Ago')
- export-audit.ts: 9 CSV导出表头 → i18n.t('system.auditExport.*')
- useAuditSettings: 4 审计保留策略消息 → i18n.t('system.auditSettings.*')
- event-handler.ts: 4 AgentCLI事件消息 → i18n.t('aiops.cli.*')
- 其他 9 文件: NotifyPolicyTab/RoleFormDialog/CronJobViewDialog/TerminalOutput/shared-components/prometheus-ds/helpers/CloudServerImport/AdvancedSettingsPanel/releaseManagerHelpers

新增 75 翻译键, zh/en 7987→8059 对齐
tsc -b=0, npm build✓ (17.13s), ESLint=0/43/any=0, go build=0, 17/17 测试通过
This commit is contained in:
attacker committed 2026-07-14 23:13:32 +08:00
1 parent bd1ccefa7f
commit 05ee051879
26 files changed
+307 -93

No files matched your search

@@ -39,7 +39,7 @@ export function createWsEventHandler(deps: WsEventHandlerDeps) {
variant: 'destructive',
});
wsRef.current?.send(JSON.stringify({ type: ok ? 'confirm' : 'deny', confirm_id: ev.confirm_id }));
if (!ok) push({ kind: 'info', content: `已拒绝执行:${ev.command || ev.tool_name}` });
if (!ok) push({ kind: 'info', content: i18n.t('aiops.cli.eventRejected', { command: ev.command || ev.tool_name }) });
}
// 处理后端事件
@@ -49,7 +49,7 @@ export function createWsEventHandler(deps: WsEventHandlerDeps) {
case 'session':
push({ kind: 'info', content: ev.message || '' });
// 仅「恢复历史会话」时进入可追问(新任务开始的 session 事件文案不同)
if (ev.message?.includes('已恢复历史会话')) {
if (ev.message?.includes(i18n.t('aiops.cli.eventRecovered'))) {
setViewMode(false);
setCanContinue(true);
}
@@ -127,7 +127,7 @@ export function createWsEventHandler(deps: WsEventHandlerDeps) {
return [...next, {
id: nextId(),
kind: 'done',
content: text || '任务已完成,请查看上方工具执行结果。',
content: text || i18n.t('aiops.cli.eventCompleted'),
round: ev.round,
}];
});
@@ -138,7 +138,7 @@ export function createWsEventHandler(deps: WsEventHandlerDeps) {
}
case 'error':
case 'aborted':
push({ kind: type === 'aborted' ? 'info' : 'error', content: ev.message || (type === 'aborted' ? '任务已中断' : '发生错误') });
push({ kind: type === 'aborted' ? 'info' : 'error', content: ev.message || (type === 'aborted' ? i18n.t('aiops.cli.eventAborted') : i18n.t('aiops.cli.eventFailed')) });
setRunning(false);
setCanContinue(type !== 'aborted');
setHistoryRefresh((k) => k + 1);
@@ -106,12 +106,12 @@ export function WorkspaceTermDialog({
ws.onclose = () => {
setConnected(false);
setHasOutput(true);
term.write('\r\n\x1b[31m[连接已关闭]\x1b[0m\r\n');
term.write(`\r\n\x1b[31m${t('deploy.terminal.connClosed')}\x1b[0m\r\n`);
};
ws.onerror = () => {
setHasOutput(true);
term.write('\r\n\x1b[31m[连接发生错误]\x1b[0m\r\n');
term.write(`\r\n\x1b[31m${t('deploy.terminal.connError')}\x1b[0m\r\n`);
};
return () => {
@@ -120,6 +120,7 @@ export function WorkspaceTermDialog({
wsRef.current = null;
setConnected(false);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open, runId]);
// 容器尺寸变化时 fit 并同步远端 PTY,避免缩放后黑边或铺不满。 // Logan.Li
@@ -3,17 +3,18 @@
import {
Key, Server, Container, Shield, Cloud, Database, FileText, Globe, Monitor,
} from 'lucide-react';
import i18n from '@/i18n';
import type { SecretType } from './types';
export const TYPE_CONFIG: Record<SecretType, { label: string; icon: typeof Key; color: string; desc: string }> = {
ssh: { label: 'SSH 密钥', icon: Server, color: 'dg-text-yellow', desc: 'SSH 私钥/公钥/密码' },
rdp: { label: 'Windows RDP 密码', icon: Monitor, color: 'dg-text-blue', desc: 'Windows 远程桌面用户名/密码' },
docker: { label: 'Docker 凭证', icon: Container, color: 'dg-text-cyan', desc: 'Docker Registry 登录凭据' },
tls: { label: 'TLS 证书', icon: Shield, color: 'dg-text-green', desc: 'TLS/SSL 证书与私钥' },
aksk: { label: 'AKSK 密钥', icon: Cloud, color: 'dg-text-purple', desc: '云服务 AccessKey/SecretKey' },
database: { label: '数据库凭证', icon: Database, color: 'dg-text-blue', desc: '数据库连接信息' },
api: { label: 'API 密钥', icon: Globe, color: 'dg-text-orange', desc: 'API Token/Key + 端点' },
generic: { label: '通用凭证', icon: FileText, color: 'dg-text-secondary', desc: '自定义 Key/Value 键值对' },
export const TYPE_CONFIG: Record<SecretType, { label: string; labelKey: string; icon: typeof Key; color: string; desc: string; descKey: string }> = {
ssh: { label: i18n.t('deploy.credType.ssh'), labelKey: 'deploy.credType.ssh', icon: Server, color: 'dg-text-yellow', desc: i18n.t('deploy.credTypeDesc.ssh'), descKey: 'deploy.credTypeDesc.ssh' },
rdp: { label: i18n.t('deploy.credType.rdp'), labelKey: 'deploy.credType.rdp', icon: Monitor, color: 'dg-text-blue', desc: i18n.t('deploy.credTypeDesc.rdp'), descKey: 'deploy.credTypeDesc.rdp' },
docker: { label: i18n.t('deploy.credType.docker'), labelKey: 'deploy.credType.docker', icon: Container, color: 'dg-text-cyan', desc: i18n.t('deploy.credTypeDesc.docker'), descKey: 'deploy.credTypeDesc.docker' },
tls: { label: i18n.t('deploy.credType.tls'), labelKey: 'deploy.credType.tls', icon: Shield, color: 'dg-text-green', desc: i18n.t('deploy.credTypeDesc.tls'), descKey: 'deploy.credTypeDesc.tls' },
aksk: { label: i18n.t('deploy.credType.aksk'), labelKey: 'deploy.credType.aksk', icon: Cloud, color: 'dg-text-purple', desc: i18n.t('deploy.credTypeDesc.aksk'), descKey: 'deploy.credTypeDesc.aksk' },
database: { label: i18n.t('deploy.credType.database'), labelKey: 'deploy.credType.database', icon: Database, color: 'dg-text-blue', desc: i18n.t('deploy.credTypeDesc.database'), descKey: 'deploy.credTypeDesc.database' },
api: { label: i18n.t('deploy.credType.api'), labelKey: 'deploy.credType.api', icon: Globe, color: 'dg-text-orange', desc: i18n.t('deploy.credTypeDesc.api'), descKey: 'deploy.credTypeDesc.api' },
generic: { label: i18n.t('deploy.credType.generic'), labelKey: 'deploy.credType.generic', icon: FileText, color: 'dg-text-secondary', desc: i18n.t('deploy.credTypeDesc.generic'), descKey: 'deploy.credTypeDesc.generic' },
};
export const TYPE_BADGE_CLASS: Record<SecretType, string> = {
@@ -1,3 +1,4 @@
import i18n from '@/i18n';
import {
GitBranch,
Wrench,
@@ -62,5 +63,5 @@ export function fmtDuration(s: number | undefined): string {
if (s < 60) return `${s}s`;
const m = Math.floor(s / 60);
const sec = s % 60;
return sec > 0 ? `${m}分${sec}秒` : `${m}分`;
return sec > 0 ? i18n.t('common.duration.minSec', { m, s: sec }) : i18n.t('common.duration.min', { m });
}
@@ -32,7 +32,7 @@ export function fmtDuration(s: number | undefined): string {
if (s < 60) return `${s}s`;
const m = Math.floor(s / 60);
const sec = s % 60;
return sec > 0 ? `${m}分${sec}秒` : `${m}分`;
return sec > 0 ? i18n.t('common.duration.minSec', { m, s: sec }) : i18n.t('common.duration.min', { m });
}
// 解析 stages 字段 // Logan.Li
@@ -80,7 +80,7 @@ export function AdvancedSettingsPanel({
</div>
<div className="space-y-1.5">
<Label className="text-xs">
Job 名称 <span className="text-[var(--dg-text-muted)]">(Webhook)</span>
{t('deploy.jobName')} <span className="text-[var(--dg-text-muted)]">(Webhook)</span>
</Label>
<Input
value={jobName}
@@ -1,3 +1,4 @@
import i18n from '@/i18n';
import {
CheckCircle2, Loader2, XCircle, Clock, AlertCircle,
GitBranch, Box, Container, Rocket, ShieldCheck,
@@ -9,12 +10,12 @@ export type { JsonData };
// ═══════════ 阶段定义 (与 PipelineManager 共用) ═══════════
export const STAGE_DEFS = [
{ key: 'checkout', label: '代码检出', icon: GitBranch, color: '#6366F1', order: 1 },
{ key: 'build', label: '构建', icon: Box, color: '#F59E0B', order: 2 },
{ key: 'test', label: '测试', icon: CheckCircle2, color: '#10B981', order: 3 },
{ key: 'image', label: '镜像打包', icon: Container, color: '#06B6D4', order: 4 },
{ key: 'deploy', label: '部署', icon: Rocket, color: '#EF4444', order: 5 },
{ key: 'verify', label: '验证', icon: ShieldCheck, color: '#3B82F6', order: 6 },
{ key: 'checkout', label: i18n.t('deploy.stage.checkout'), labelKey: 'deploy.stage.checkout', icon: GitBranch, color: '#6366F1', order: 1 },
{ key: 'build', label: i18n.t('deploy.stage.build'), labelKey: 'deploy.stage.build', icon: Box, color: '#F59E0B', order: 2 },
{ key: 'test', label: i18n.t('deploy.stage.test'), labelKey: 'deploy.stage.test', icon: CheckCircle2, color: '#10B981', order: 3 },
{ key: 'image', label: i18n.t('deploy.stage.image'), labelKey: 'deploy.stage.image', icon: Container, color: '#06B6D4', order: 4 },
{ key: 'deploy', label: i18n.t('deploy.stage.deploy'), labelKey: 'deploy.stage.deploy', icon: Rocket, color: '#EF4444', order: 5 },
{ key: 'verify', label: i18n.t('deploy.stage.verify'), labelKey: 'deploy.stage.verify', icon: ShieldCheck, color: '#3B82F6', order: 6 },
].sort((a, b) => a.order - b.order);
export interface PipelineRun {
@@ -14,10 +14,10 @@ import {
// 环境配置(值与后端 oneof=dev test staging prod 一致) // Logan.Li
export const ENVIRONMENTS: Record<string, { label: string; color: string }> = {
dev: { label: '开发环境', color: 'border-blue-500/30 dg-text-blue bg-blue-500/10' },
test: { label: '测试环境', color: 'border-cyan-500/30 dg-text-cyan bg-cyan-500/10' },
staging: { label: '预发环境', color: 'border-amber-500/30 dg-text-yellow bg-amber-500/10' },
prod: { label: '生产环境', color: 'border-red-500/30 dg-text-red bg-red-500/10' },
dev: { label: 'dev', color: 'border-blue-500/30 dg-text-blue bg-blue-500/10' },
test: { label: 'test', color: 'border-cyan-500/30 dg-text-cyan bg-cyan-500/10' },
staging: { label: 'staging', color: 'border-amber-500/30 dg-text-yellow bg-amber-500/10' },
prod: { label: 'prod', color: 'border-red-500/30 dg-text-red bg-red-500/10' },
};
const ENV_LABEL_KEY: Record<string, string> = {
@@ -61,10 +61,10 @@ export function formatEventRelative(ts: string): string {
if (diff < 0) return '';
const mins = Math.floor(diff / 60000);
if (mins < 1) return i18n.t('task.cronJob.time.justNow');
if (mins < 60) return `${mins}分钟前`;
if (mins < 60) return i18n.t('common.duration.minAgo', { count: mins });
const hrs = Math.floor(mins / 60);
if (hrs < 24) return `${hrs}小时前`;
return `${Math.floor(hrs / 24)}天前`;
if (hrs < 24) return i18n.t('common.duration.hourAgo', { count: hrs });
return i18n.t('common.duration.dayAgo', { count: Math.floor(hrs / 24) });
}
/** 重复跨度:首次 → 最后 // Logan.Li */
@@ -54,7 +54,7 @@ export function ContainerInfo({ c }: { c: JsonData }) {
CPU: <span className="text-[var(--dg-text-primary)] font-mono">{c.resources?.requests?.cpu || '-'}/{c.resources?.limits?.cpu || '-'}</span>
</div>
<div className="text-[var(--dg-text-secondary)]">
内存: <span className="text-[var(--dg-text-primary)] font-mono">{c.resources?.requests?.memory || '-'}/{c.resources?.limits?.memory || '-'}</span>
{i18n.t('k8s.workloadView.memoryLabel')}: <span className="text-[var(--dg-text-primary)] font-mono">{c.resources?.requests?.memory || '-'}/{c.resources?.limits?.memory || '-'}</span>
</div>
<div className="text-[var(--dg-text-secondary)]">
{i18n.t('k8s.workload.portsLabel')}: <span className="text-[var(--dg-text-primary)] font-mono">
@@ -1,6 +1,7 @@
// 告警集合通知策略 — 四类监控绑定联系组 + 消息模版 // Logan.Li
import { useState, useEffect, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import i18n from '@/i18n';
import { Server, Database, Globe, Network, Bell, Zap, Loader2, Save, FileText } from 'lucide-react';
import { SilenceRulesPanel } from './SilenceRulesPanel';
import { toast } from 'sonner';
@@ -41,7 +42,7 @@ const CATEGORIES = [
const DEFAULT_TITLE = '[{{severity_label}}] {{rule_name}} — {{hostname}} ({{metric}})';
const DEFAULT_FOOTER = '{{time}} | {{platform}}';
const DEFAULT_PLATFORM = 'AIOps 智能运维平台';
const DEFAULT_PLATFORM = i18n.t('monitor.notify.defaultPlatform');
const TEMPLATE_VARS = [
'{{time}}', '{{platform}}', '{{rule_name}}', '{{hostname}}',
@@ -105,7 +105,7 @@ export async function handleTestQuick(ds: PromDS): Promise<void> {
skip_tls_verify: ds.skip_tls_verify,
});
if (data?.success) {
toast.success(`连接成功 · ${data.target_count || 0} targets`);
toast.success(i18n.t('monitor.prometheus.connSuccess', { count: data.target_count || 0 }));
} else {
toast.error(data?.message || i18n.t('aiops.llmTestFailed'));
}
@@ -22,9 +22,9 @@ const ENGINE_ICONS: Record<string, { Icon: typeof Flame; color: string }> = {
function relativeTime(ts: number): string {
const diff = Date.now() - ts;
if (diff < 60000) return i18n.t('task.cronJob.time.justNow');
if (diff < 3600000) return `${Math.floor(diff / 60000)}分钟前`;
if (diff < 86400000) return `${Math.floor(diff / 3600000)}小时前`;
return `${Math.floor(diff / 86400000)}天前`;
if (diff < 3600000) return i18n.t('common.duration.minAgo', { count: Math.floor(diff / 60000) });
if (diff < 86400000) return i18n.t('common.duration.hourAgo', { count: Math.floor(diff / 3600000) });
return i18n.t('common.duration.dayAgo', { count: Math.floor(diff / 86400000) });
}
export function QueryHistoryPanel({ visible, onClose, onSelect, onCreateAlert }: Props) {
@@ -170,10 +170,11 @@ export function CloudServerImport({ open, onClose, initialAccountId }: CloudServ
setDiscoverErrors((res?.errors as string[]) || []);
} catch (e) {
console.error('发现主机失败:', e);
setDiscoverErrors([(e as Error)?.message || '发现主机失败']);
setDiscoverErrors([(e as Error)?.message || t('server.import.discoverFailed')]);
} finally {
setIsDiscovering(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedAccount, selectedRegions]);
// ==================== 导入逻辑 ====================
@@ -325,7 +325,7 @@ export function CredentialManager() {
{/* 内容 */}
<div>
<label className="block text-sm font-medium text-[var(--dg-text-secondary)] mb-1">
{formData.type === 'ssh_key' ? 'SSH 私钥' : formData.type === 'api_token' ? 'Token' : formData.type === 'access_key' ? 'AK/SK (每行一对,用空格或逗号分隔)' : '密码'}
{formData.type === 'ssh_key' ? t('system.credTypeLabel.sshKey') : formData.type === 'api_token' ? t('system.credTypeLabel.apiToken') : formData.type === 'access_key' ? 'AK/SK (每行一对,用空格或逗号分隔)' : t('system.credTypeLabel.password')}
{!editingId && <span className="dg-text-red"> *</span>}
{editingId && <span className="text-xs text-[var(--dg-text-muted)] ml-1">{t('system.leaveBlankNoUpdate')}</span>}
</label>
@@ -102,7 +102,7 @@ export function LiveMonitorDialog({ open, onOpenChange, sessionId, sessionInfo }
setConnected(true);
setConnectError('');
mountXterm(term, fitAddon, () => containerRef.current);
term.writeln('\x1b[36m[审计监控] 已连接到会话...\x1b[0m');
term.writeln(`\x1b[36m${t('monitor.terminal.auditConnected')}\x1b[0m`);
};
ws.onmessage = (event) => {
@@ -112,7 +112,7 @@ export function LiveMonitorDialog({ open, onOpenChange, sessionId, sessionInfo }
case 'session_info':
setMeta(msg.payload);
term.writeln(
`\x1b[36m用户: ${msg.payload.username} 服务器: ${msg.payload.server_name} (${msg.payload.server_ip})\x1b[0m`,
`\x1b[36m${t('monitor.terminal.userServer', { username: msg.payload.username, serverName: msg.payload.server_name, serverIp: msg.payload.server_ip })}\x1b[0m`,
);
break;
case 'history':
@@ -127,14 +127,14 @@ export function LiveMonitorDialog({ open, onOpenChange, sessionId, sessionInfo }
}
};
ws.onerror = () => setConnectError('WebSocket 连接失败');
ws.onerror = () => setConnectError(t('monitor.terminal.wsConnFailed'));
ws.onclose = (e) => {
if (!didOpen) {
setConnectError(`连接失败 (code=${e.code}): ${e.reason || '认证失败或网络问题'}`);
setConnectError(t('monitor.terminal.connFailedCode', { code: e.code, reason: e.reason || t('monitor.terminal.authOrNetwork') }));
}
setConnected(false);
term.writeln('\r\n\x1b[33m[审计监控] 连接已断开\x1b[0m');
term.writeln(`\r\n\x1b[33m${t('monitor.terminal.auditDisconnected')}\x1b[0m`);
};
const timeout = setTimeout(() => {
@@ -153,6 +153,7 @@ export function LiveMonitorDialog({ open, onOpenChange, sessionId, sessionInfo }
wsRef.current = null;
setConnected(false);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open, sessionId]);
// 容器尺寸变化时持续 fit // Logan.Li
@@ -126,7 +126,7 @@ export function RdpAuditViewer({ open, onOpenChange, sessionId, mode }: RdpAudit
useEffect(() => {
if (!open || !sessionId) return;
setStatus(mode === 'live' ? '正在连接 RDP 实时审计...' : '正在加载 RDP 录屏...');
setStatus(mode === 'live' ? i18n.t('monitor.rdp.connectingLive') : i18n.t('monitor.rdp.loadingRecording'));
setLoading(true);
setFrames([]);
setIndex(0);
@@ -161,8 +161,8 @@ export function RdpAuditViewer({ open, onOpenChange, sessionId, mode }: RdpAudit
/* ignore */
}
};
ws.onerror = () => { setLoading(false); setStatus('RDP 实时审计连接失败'); };
ws.onclose = () => setStatus((prev) => prev || 'RDP 实时审计已断开');
ws.onerror = () => { setLoading(false); setStatus(i18n.t('monitor.rdp.liveConnFailed')); };
ws.onclose = () => setStatus((prev) => prev || i18n.t('monitor.rdp.liveDisconnected'));
return () => {
ws.close();
wsRef.current = null;
@@ -182,13 +182,13 @@ export function RdpAuditViewer({ open, onOpenChange, sessionId, mode }: RdpAudit
setCanvasSize({ width: sizeFrame.width, height: sizeFrame.height });
}
setFrames(list);
setStatus(list.length ? '' : '该 RDP 会话没有可播放画面帧');
setStatus(list.length ? '' : i18n.t('monitor.rdp.noFrames'));
setPlaying(list.length > 0);
setLoading(false);
})
.catch(() => {
if (!cancelled) {
setStatus('RDP 录屏加载失败');
setStatus(i18n.t('monitor.rdp.loadFailed'));
setLoading(false);
}
});
@@ -45,7 +45,7 @@ const MODULE_GROUPS: { key: string; label: string; prefixes: string[] }[] = [
{ key: 'deploy', label: i18n.t('common.menuApp'), prefixes: ['deploy:'] },
{ key: 'monitor', label: i18n.t('common.menuMonitoring'), prefixes: ['monitor:', 'agent:'] },
{ key: 'auto', label: i18n.t('common.menuAutomation'), prefixes: ['auto:'] },
{ key: 'aiops', label: '🤖 AIOps智能', prefixes: ['aiops:'] },
{ key: 'aiops', label: i18n.t('system.role.aiopsGroup'), prefixes: ['aiops:'] },
{ key: 'approval', label: i18n.t('common.menuApproval'), prefixes: ['approval:'] },
{ key: 'audit', label: i18n.t('common.menuAudit'), prefixes: ['audit:'] },
{ key: 'system', label: i18n.t('common.menuSystem'), prefixes: ['system:'] },
@@ -17,16 +17,17 @@ import { useTerminalSessions, useTerminalSessionMutations } from '@/hooks/api/us
import { useConfirm } from '@/components/ui/confirm-dialog';
import { ReplayDialog } from './ReplayDialog';
import { useTranslation } from 'react-i18next';
import i18n from '@/i18n';
function formatDuration(sec: number): string {
if (!sec || sec < 0) return '--';
if (sec < 60) return `${sec}秒`;
if (sec < 60) return i18n.t('common.duration.seconds', { count: sec });
const m = Math.floor(sec / 60);
const s = sec % 60;
if (m < 60) return `${m}分${s > 0 ? `${s}秒` : ''}`;
if (m < 60) return s > 0 ? i18n.t('common.duration.minSec', { m, s }) : i18n.t('common.duration.min', { m });
const h = Math.floor(m / 60);
const rm = m % 60;
return `${h}时${rm > 0 ? `${rm}分` : ''}`;
return rm > 0 ? i18n.t('common.duration.hoursMin', { h, m: rm }) : i18n.t('common.duration.hour', { count: h });
}
function formatSize(bytes: number): string {
@@ -170,7 +171,7 @@ export function TerminalSessionsTab() {
}
onClick={() => setStatusFilter(st)}
>
{st === '' ? '全部' : st === 'active' ? '活动中' : st === 'closed' ? '已关闭' : '失败'}
{st === '' ? t('system.terminalSession.all') : st === 'active' ? t('system.terminalSession.active') : st === 'closed' ? t('system.terminalSession.closed') : t('system.terminalSession.failed')}
</Button>
))}
</div>
@@ -24,14 +24,14 @@ function escapeCsv(s: string): string {
export function exportAuditReportCsv(meta: ReplaySessionMeta, commands: CommandBookmark[]) {
const lines: string[] = [
i18n.t('audit.reportTitle'),
`会话ID,${meta.id}`,
`用户,${escapeCsv(meta.username || '')}`,
`目标主机,${escapeCsv(meta.server_name || '')}`,
`${i18n.t('system.auditExport.sessionId')},${meta.id}`,
`${i18n.t('system.auditExport.user')},${escapeCsv(meta.username || '')}`,
`${i18n.t('system.auditExport.targetHost')},${escapeCsv(meta.server_name || '')}`,
`IP,${meta.server_ip}:${meta.server_port}`,
`客户端IP,${meta.client_ip || ''}`,
`开始时间,${meta.start_time || ''}`,
`时长(秒),${meta.duration || 0}`,
`录制大小,${meta.record_size || 0}`,
`${i18n.t('system.auditExport.clientIp')},${meta.client_ip || ''}`,
`${i18n.t('system.auditExport.startTime')},${meta.start_time || ''}`,
`${i18n.t('system.auditExport.duration')},${meta.duration || 0}`,
`${i18n.t('system.auditExport.status')},${meta.record_size || 0}`,
'',
i18n.t('audit.reportTitle'),
];
@@ -107,8 +107,8 @@ export function useAuditSettings() {
const total = res.cleanup?.total ?? 0;
toast.success(
total > 0
? `已应用 ${days} 天保留策略,立即清理 ${total} 条超期记录`
: `已应用 ${days} 天保留策略,暂无超期记录需清理`,
? i18n.t('system.auditSettings.appliedWithCleanup', { days, total })
: i18n.t('system.auditSettings.appliedNoCleanup', { days }),
);
} catch (e: unknown) {
toast.error(
@@ -131,16 +131,13 @@ export function useAuditSettings() {
total?: number;
};
if (typeof s.total === "number") {
detail =
s.total > 0
? `,清理 ${s.total} 条`
: i18n.t("system.auditSetting.noExpiredRecords");
detail = s.total > 0 ? i18n.t('system.auditSettings.cleanupCount', { count: s.total }) : i18n.t("system.auditSetting.noExpiredRecords");
}
} catch {
// ignore
}
}
return `上次清理:${t}${detail}`;
return i18n.t('system.auditSettings.lastCleanup', { time: t, detail });
})();
return {
@@ -87,7 +87,7 @@ export function CronJobViewDialog({ open, onOpenChange, job }: Props) {
{job.script?.name && (
<div className="text-xs dg-text-secondary">
关联脚本: <span className="text-[var(--dg-text-primary)]">{job.script.name}</span>
{t('task.cronJob.associatedScript')}: <span className="text-[var(--dg-text-primary)]">{job.script.name}</span>
</div>
)}
@@ -101,7 +101,7 @@ export function CronJobViewDialog({ open, onOpenChange, job }: Props) {
/>
) : (
<pre className="rounded-lg border border-[var(--dg-border)] bg-[var(--dg-bg-tertiary)] p-4 text-xs font-mono text-[var(--dg-text-primary)] whitespace-pre-wrap break-all max-h-96 overflow-auto">
{content || '(无命令内容)'}
{content || t('task.cronJob.noCommand')}
</pre>
)}
</div>
@@ -33,7 +33,7 @@ export function TerminalOutput({
status,
durationMs,
startedAt,
emptyText = '(无输出)',
emptyText = i18n.t('task.terminal.noOutput'),
className,
maxHeight = 'min(28rem, 55vh)',
variant = 'plain',
+115 -11
View File
@@ -184,7 +184,9 @@
"minAgo": "{{count}} min ago",
"secAgo": "{{count}}s ago",
"hourAgo": "{{count}}h ago",
"dayAgo": "{{count}}d ago"
"dayAgo": "{{count}}d ago",
"seconds": "{{count}}s",
"hoursMin": "{{h}}h {{m}}m"
},
"ebCaughtError": "[ErrorBoundary] Caught error:",
"ebCopyError": "Copy Error",
@@ -1290,7 +1292,10 @@
"loggingIn": "Logging in..."
},
"webshellConnect": "Connect",
"webshellLoggingIn": "Logging in..."
"webshellLoggingIn": "Logging in...",
"import": {
"discoverFailed": "Failed to discover hosts"
}
},
"alert": {
"acknowledged": "Alert acknowledged",
@@ -2538,7 +2543,8 @@
"notInstalled": "Not installed",
"installing": "Installing",
"test": "Test",
"install": "Install"
"install": "Install",
"recommended": "Recommended"
},
"trigger": {
"manual": "Manual",
@@ -2583,7 +2589,35 @@
"envVar": "Environment Variables",
"envVarOptional": "Environment Variables (optional)"
},
"workspaceTerminalTitle": "Workspace Terminal #{{num}}"
"workspaceTerminalTitle": "Workspace Terminal #{{num}}",
"credType": {
"ssh": "SSH Key",
"rdp": "Windows RDP Password",
"docker": "Docker Credential",
"tls": "TLS Certificate",
"aksk": "AKSK Key",
"database": "Database Credential",
"api": "API Key",
"generic": "Generic Credential"
},
"credTypeDesc": {
"ssh": "SSH private/public key/password",
"rdp": "Windows remote desktop username/password",
"docker": "Docker Registry login credentials",
"tls": "TLS/SSL certificate and private key",
"aksk": "Cloud AccessKey/SecretKey",
"database": "Database connection info",
"api": "API Token/Key + endpoint",
"generic": "Custom Key/Value pairs"
},
"fmtDuration": {
"minSec": "{{m}}m {{s}}s"
},
"terminal": {
"connClosed": "[Connection Closed]",
"connError": "[Connection Error]"
},
"jobName": "Job Name"
},
"k8s": {
"actionModal": {
@@ -3708,7 +3742,8 @@
"serviceName": "Service Name",
"podCount": "Pod Count",
"actualSchedule": "Actual Schedule",
"restartSentDesc": "{{kind}} rolling restart triggered"
"restartSentDesc": "{{kind}} rolling restart triggered",
"memoryLabel": "Memory"
},
"workloads": {
"scaleSuccess": "Scaling succeeded",
@@ -4976,7 +5011,9 @@
"noData": "No Prometheus datasources",
"addHint": "Add datasources for metrics monitoring and dashboards"
},
"prometheus": "Prometheus",
"prometheus": {
"connSuccess": "Connection successful · {{count}} targets"
},
"prometheusDashb": {
"connecting": "Connecting…"
},
@@ -5397,6 +5434,25 @@
"durationMinutes": "{{count}} min",
"queryHistory": {
"count": "{{count}} entries"
},
"terminal": {
"auditConnected": "[Audit Monitor] Connected to session...",
"userServer": "User: {{username}} Server: {{serverName}} ({{serverIp}})",
"wsConnFailed": "WebSocket connection failed",
"connFailedCode": "Connection failed (code={{code}}): {{reason}}",
"auditDisconnected": "[Audit Monitor] Disconnected",
"authOrNetwork": "Authentication failed or network issue"
},
"rdp": {
"connectingLive": "Connecting to RDP live audit...",
"loadingRecording": "Loading RDP recording...",
"liveConnFailed": "RDP live audit connection failed",
"liveDisconnected": "RDP live audit disconnected",
"noFrames": "No playable frames for this RDP session",
"loadFailed": "RDP recording load failed"
},
"notify": {
"defaultPlatform": "AIOps Platform"
}
},
"task": {
@@ -5476,7 +5532,9 @@
"confirmDelete": "Delete \"{{name}}\"? This cannot be undone.",
"execDuration": "Duration: {{ms}}ms"
},
"runCount": "{{count}} times"
"runCount": "{{count}} times",
"associatedScript": "Associated Script",
"noCommand": "(No command content)"
},
"cronJobCol": {
"task": "Task",
@@ -5601,6 +5659,9 @@
"noShell": "No shell interpreter detected. Scripts cannot run on this host. For Docker deployments, ensure bash is installed in the image.",
"noPython": "Python is not installed. Python scripts will not be executable.",
"partialUnavailable": "Some interpreters are unavailable; scripts of those types cannot run after creation."
},
"terminal": {
"noOutput": "(No output)"
}
},
"rbac": {
@@ -7466,7 +7527,12 @@
"invalidRetentionDays": "Please select valid retention days",
"noExpiredRecords": ", no expired records"
},
"auditSettings": "Audit Settings",
"auditSettings": {
"appliedWithCleanup": "Applied {{days}}-day retention policy, cleaned {{total}} expired records",
"appliedNoCleanup": "Applied {{days}}-day retention policy, no expired records to clean",
"cleanupCount": ", cleaned {{count}} records",
"lastCleanup": "Last cleanup: {{time}}{{detail}}"
},
"auditSettingsDesc": "Operation logs and audit policy",
"auditStats": "Audit Statistics",
"auditSuccess": "Success",
@@ -7818,7 +7884,12 @@
"terminalRecordSize": "Recording Size",
"terminalReplay": "Replay",
"terminalServer": "Server",
"terminalSession": "Session",
"terminalSession": {
"all": "All",
"active": "Active",
"closed": "Closed",
"failed": "Failed"
},
"terminalStartTime": "Start Time",
"terminalStatus": "Status",
"terminalUser": "User",
@@ -7897,7 +7968,35 @@
"credentialAkSk": "AK/SK (one pair per line, space or comma separated)",
"credentialEncryptedHint": "Credentials are encrypted with AES-256-GCM, decrypted only on server side",
"permissionAssign": "Permission Assignment",
"adminPassword": "Admin Password"
"adminPassword": "Admin Password",
"csvHeader": {
"username": "Username",
"nickname": "Nickname",
"displayName": "Display Name",
"email": "Email",
"phone": "Phone",
"role": "Role",
"department": "Department"
},
"auditExport": {
"sessionId": "Session ID",
"user": "User",
"targetHost": "Target Host",
"clientIp": "Client IP",
"startTime": "Start Time",
"endTime": "End Time",
"duration": "Duration",
"commandCount": "Command Count",
"status": "Status"
},
"credTypeLabel": {
"sshKey": "SSH Private Key",
"apiToken": "Token",
"password": "Password"
},
"role": {
"aiopsGroup": "AIOps Intelligence"
}
},
"aiops": {
"activeCount": "{{count}} active",
@@ -8255,7 +8354,12 @@
"execute": "Run"
},
"confirmHighRiskTitle": "⚠️ Confirm High-Risk Operation",
"confirmHighRiskDesc": "Tool: {{tool}}\nRisk: {{risk}}\nCommand: {{command}}"
"confirmHighRiskDesc": "Tool: {{tool}}\nRisk: {{risk}}\nCommand: {{command}}",
"eventRejected": "Execution rejected: {{command}}",
"eventRecovered": "Session recovered",
"eventCompleted": "Task completed. Please check the tool execution results above.",
"eventAborted": "Task aborted",
"eventFailed": "Task execution failed"
},
"cloudService": "Cloud Services",
"codingPlan": "Coding Plan",
+115 -11
View File
@@ -184,7 +184,9 @@
"minAgo": "{{count}}分钟前",
"secAgo": "{{count}}秒前",
"hourAgo": "{{count}}小时前",
"dayAgo": "{{count}}天前"
"dayAgo": "{{count}}天前",
"seconds": "{{count}}秒",
"hoursMin": "{{h}}时{{m}}分"
},
"ebCaughtError": "[ErrorBoundary] 捕获到错误:",
"ebCopyError": "复制错误",
@@ -1290,7 +1292,10 @@
"loggingIn": "正在登录..."
},
"webshellConnect": "连接",
"webshellLoggingIn": "正在登录..."
"webshellLoggingIn": "正在登录...",
"import": {
"discoverFailed": "发现主机失败"
}
},
"alert": {
"acknowledged": "告警已确认",
@@ -2538,7 +2543,8 @@
"notInstalled": "未安装",
"installing": "安装中",
"test": "测试",
"install": "安装"
"install": "安装",
"recommended": "推荐"
},
"trigger": {
"manual": "手动",
@@ -2583,7 +2589,35 @@
"envVar": "环境变量",
"envVarOptional": "环境变量 (可选)"
},
"workspaceTerminalTitle": "Workspace 终端 #{{num}}"
"workspaceTerminalTitle": "Workspace 终端 #{{num}}",
"credType": {
"ssh": "SSH 密钥",
"rdp": "Windows RDP 密码",
"docker": "Docker 凭证",
"tls": "TLS 证书",
"aksk": "AKSK 密钥",
"database": "数据库凭证",
"api": "API 密钥",
"generic": "通用凭证"
},
"credTypeDesc": {
"ssh": "SSH 私钥/公钥/密码",
"rdp": "Windows 远程桌面用户名/密码",
"docker": "Docker Registry 登录凭据",
"tls": "TLS/SSL 证书与私钥",
"aksk": "云服务 AccessKey/SecretKey",
"database": "数据库连接信息",
"api": "API Token/Key + 端点",
"generic": "自定义 Key/Value 键值对"
},
"fmtDuration": {
"minSec": "{{m}}分{{s}}秒"
},
"terminal": {
"connClosed": "[连接已关闭]",
"connError": "[连接发生错误]"
},
"jobName": "Job 名称"
},
"k8s": {
"actionModal": {
@@ -3708,7 +3742,8 @@
"serviceName": "服务名",
"podCount": "Pod 数量",
"actualSchedule": "当前调度",
"restartSentDesc": "{{kind}} 滚动重启已触发"
"restartSentDesc": "{{kind}} 滚动重启已触发",
"memoryLabel": "内存"
},
"workloads": {
"scaleSuccess": "扩缩容成功",
@@ -4976,7 +5011,9 @@
"noData": "暂无 Prometheus 数据源",
"addHint": "添加数据源后可用于指标监控与监控大屏"
},
"prometheus": "Prometheus",
"prometheus": {
"connSuccess": "连接成功 · {{count}} targets"
},
"prometheusDashb": {
"connecting": "连接中…"
},
@@ -5397,6 +5434,25 @@
"durationMinutes": "{{count}}分钟",
"queryHistory": {
"count": "{{count}} 条"
},
"terminal": {
"auditConnected": "[审计监控] 已连接到会话...",
"userServer": "用户: {{username}} 服务器: {{serverName}} ({{serverIp}})",
"wsConnFailed": "WebSocket 连接失败",
"connFailedCode": "连接失败 (code={{code}}): {{reason}}",
"auditDisconnected": "[审计监控] 连接已断开",
"authOrNetwork": "认证失败或网络问题"
},
"rdp": {
"connectingLive": "正在连接 RDP 实时审计...",
"loadingRecording": "正在加载 RDP 录屏...",
"liveConnFailed": "RDP 实时审计连接失败",
"liveDisconnected": "RDP 实时审计已断开",
"noFrames": "该 RDP 会话没有可播放画面帧",
"loadFailed": "RDP 录屏加载失败"
},
"notify": {
"defaultPlatform": "AIOps 智能运维平台"
}
},
"task": {
@@ -5476,7 +5532,9 @@
"confirmDelete": "确定删除「{{name}}」吗?此操作不可恢复。",
"execDuration": "耗时 {{ms}}ms"
},
"runCount": "{{count}} 次"
"runCount": "{{count}} 次",
"associatedScript": "关联脚本",
"noCommand": "(无命令内容)"
},
"cronJobCol": {
"task": "任务",
@@ -5601,6 +5659,9 @@
"noShell": "未检测到 Shell 解释器,脚本无法在本机执行。Docker 部署请确认镜像已安装 bash。",
"noPython": "Python 未安装,Python 类型脚本将无法执行。",
"partialUnavailable": "部分解释器不可用,对应类型的脚本创建后无法执行。"
},
"terminal": {
"noOutput": "(无输出)"
}
},
"rbac": {
@@ -7466,7 +7527,12 @@
"invalidRetentionDays": "请选择有效的保留天数",
"noExpiredRecords": ",无超期记录"
},
"auditSettings": "审计设置",
"auditSettings": {
"appliedWithCleanup": "已应用 {{days}} 天保留策略,立即清理 {{total}} 条超期记录",
"appliedNoCleanup": "已应用 {{days}} 天保留策略,暂无超期记录需清理",
"cleanupCount": ",清理 {{count}} 条",
"lastCleanup": "上次清理:{{time}}{{detail}}"
},
"auditSettingsDesc": "操作日志与审计策略",
"auditStats": "审计统计",
"auditSuccess": "成功",
@@ -7818,7 +7884,12 @@
"terminalRecordSize": "录制大小",
"terminalReplay": "回放",
"terminalServer": "服务器",
"terminalSession": "会话",
"terminalSession": {
"all": "全部",
"active": "活动中",
"closed": "已关闭",
"failed": "失败"
},
"terminalStartTime": "开始时间",
"terminalStatus": "状态",
"terminalUser": "用户",
@@ -7897,7 +7968,35 @@
"credentialAkSk": "AK/SK (每行一对,用空格或逗号分隔)",
"credentialEncryptedHint": "凭据内容使用 AES-256-GCM 加密存储,仅在服务器端解密使用",
"permissionAssign": "权限分配",
"adminPassword": "管理员密码"
"adminPassword": "管理员密码",
"csvHeader": {
"username": "用户名",
"nickname": "昵称",
"displayName": "显示名",
"email": "邮箱",
"phone": "手机",
"role": "角色",
"department": "部门"
},
"auditExport": {
"sessionId": "会话ID",
"user": "用户",
"targetHost": "目标主机",
"clientIp": "客户端IP",
"startTime": "开始时间",
"endTime": "结束时间",
"duration": "时长",
"commandCount": "命令数",
"status": "状态"
},
"credTypeLabel": {
"sshKey": "SSH 私钥",
"apiToken": "Token",
"password": "密码"
},
"role": {
"aiopsGroup": "AIOps智能"
}
},
"aiops": {
"activeCount": "{{count}} 个活跃",
@@ -8255,7 +8354,12 @@
"execute": "执行"
},
"confirmHighRiskTitle": "⚠️ 确认执行高危操作",
"confirmHighRiskDesc": "工具:{{tool}}\n风险:{{risk}}\n命令:{{command}}"
"confirmHighRiskDesc": "工具:{{tool}}\n风险:{{risk}}\n命令:{{command}}",
"eventRejected": "已拒绝执行:{{command}}",
"eventRecovered": "已恢复历史会话",
"eventCompleted": "任务已完成,请查看上方工具执行结果。",
"eventAborted": "任务已取消",
"eventFailed": "任务执行失败"
},
"cloudService": "云服务",
"codingPlan": "编码套餐 Coding Plan",