refactor(i18n): U42 残余硬编码中文彻底清零 — 33文件54处→0, zh/en 7847→7898对齐

全量 JSX children + toast/confirm + sr-only 描述硬编码中文清零:
- Kubernetes模块: UpdateStrategyCard maxSurge/maxUnavailable说明 + IngressCreateForm host域名/Service端口 + ServiceCreateForm 端口配置/selector + DeploymentEditService 无selector/个关联/暂无Service/Ingress + ClusterOverviewPage 核 + KubernetesClusterDialog 已配置 + KubectlTerminal/ResourceVisualEditor/CreateHPADialog/DeploymentEditContainers/Storage + PodInstancesTable/UpdateStrategySelectorCards
- AI模块: PredictiveCapacityTab AI深度解读/紧急/警告/7天预测/预计N天 + PredictiveCostTab AI深度解读 + event-handler.ts 高危操作确认toast
- Deploy模块: DeployCredentials 启用/禁用 + DeployRBAC 删除角色confirm + WorkspaceTermDialog + CredFormDialog Windows用户
- Monitor模块: AlertRulesTab 删除confirm/分钟 + PrometheusDatasourceManager/LogDatasourcePage 删除confirm + QueryHistoryPanel 条
- System模块: ServerFileBrowser/TerminalDialog/RdpAuditViewer sr-only + SystemManager/CredentialManager/TerminalSessionsTab 删除confirm + SecuritySettingsTab 30分钟/2小时
- Task模块: CronJobViewDialog 次

新增 61 翻译键 (k8s.editAdvanced 6 + k8s.editService 11 + k8s.cluster 2 + k8s.misc 9 + aiops.capacity 9 + aiops.cost 1 + aiops.cli 2 + deploy 4 + monitor 5 + system 10 + task 1 + common 2)
tsc -b=0, npm build✓ (17.71s), ESLint=0/43/any=0, go build=0, 17/17 测试通过
This commit is contained in:
attacker committed 2026-07-14 00:51:36 +08:00
1 parent a7838aaf65
commit c6e0d6ca57
35 files changed
+217 -104

No files matched your search

@@ -76,7 +76,7 @@ export function PredictiveCapacityTab({
<div className="p-4 rounded-lg border border-purple-500/30 bg-purple-500/5">
<div className="flex items-center gap-2 mb-2">
<Sparkles className="h-4 w-4 dg-text-purple" />
<span className="text-sm font-medium text-purple-300">AI 深度解读</span>
<span className="text-sm font-medium text-purple-300">{t('aiops.capacity.aiInsight')}</span>
</div>
{looksLikeMarkdown(insight) ? (
<AgentMarkdown content={insight} />
@@ -103,24 +103,24 @@ export function PredictiveCapacityTab({
}
>
{forecast.daysUntilThreshold <= 7
? '紧急'
? t('aiops.capacity.urgency')
: forecast.daysUntilThreshold <= 14
? '警告'
: '正常'}
? t('aiops.capacity.warning')
: t('common.normal')}
</Badge>
</div>
<div className="mb-4">
<div className="flex items-center justify-between text-sm mb-1">
<span className="dg-text-secondary">7天使用率预测</span>
<span className="dg-text-secondary">{t('aiops.capacity.sevenDayForecast')}</span>
<span className="dg-text-secondary">
{forecast.daysUntilThreshold >= 999 ? (
'增长平缓,暂无触顶风险'
t('aiops.capacity.noRisk')
) : (
<>
预计{' '}
<span className="dg-text-yellow font-medium">{forecast.daysUntilThreshold}天</span>{' '}
后达到阈值
{t('aiops.capacity.expectedToReach')}{' '}
<span className="dg-text-yellow font-medium">{forecast.daysUntilThreshold}{t('aiops.capacity.days')}</span>{' '}
{t('aiops.capacity.afterReachThreshold')}
</>
)}
</span>
@@ -147,7 +147,7 @@ export function PredictiveCapacityTab({
<div className="flex items-start gap-2">
<Zap className="h-4 w-4 dg-text-green mt-0.5" />
<div>
<span className="text-sm dg-text-secondary">AI建议:</span>
<span className="text-sm dg-text-secondary">{t('aiops.capacity.aiSuggestion')}:</span>
<p className="text-sm text-[var(--dg-text-secondary)] mt-1">{forecast.recommendation}</p>
</div>
</div>
@@ -108,7 +108,7 @@ export function PredictiveCostTab({
<div className="p-4 rounded-lg border border-purple-500/30 bg-purple-500/5">
<div className="flex items-center gap-2 mb-2">
<Sparkles className="h-4 w-4 dg-text-purple" />
<span className="text-sm font-medium text-purple-300">AI 深度解读</span>
<span className="text-sm font-medium text-purple-300">{t('aiops.cost.aiInsight')}</span>
</div>
{looksLikeMarkdown(insight) ? (
<AgentMarkdown content={insight} />
@@ -5,6 +5,7 @@ import type { Dispatch, SetStateAction, RefObject } from 'react';
import type { ChatItem } from '../AgentCLIMessages';
import type { WsEvent } from '../agent-cli-constants';
import type { ConfirmOptions } from '@/components/ui/confirm-dialog';
import i18n from '@/i18n';
export interface WsEventHandlerDeps {
push: (it: Omit<ChatItem, 'id'>) => void;
@@ -31,10 +32,10 @@ export function createWsEventHandler(deps: WsEventHandlerDeps) {
// 高危命令确认(后端驱动 → 弹框 → 回传)
async function handleConfirm(ev: WsEvent) {
const ok = await confirm({
title: '⚠️ 确认执行高危操作',
description: `工具:${ev.tool_name}\n风险:${ev.risk}\n命令:${ev.command || '(无)'}`,
confirmText: '确认执行',
cancelText: '拒绝',
title: i18n.t('aiops.cli.confirmHighRiskTitle'),
description: i18n.t('aiops.cli.confirmHighRiskDesc', { tool: ev.tool_name, risk: ev.risk, command: ev.command || '(无)' }),
confirmText: i18n.t('common.confirm'),
cancelText: i18n.t('common.cancel'),
variant: 'destructive',
});
wsRef.current?.send(JSON.stringify({ type: ok ? 'confirm' : 'deny', confirm_id: ev.confirm_id }));
@@ -312,7 +312,7 @@ export default function DeployCredentials() {
<td className="px-3 py-2"><div className="w-6 h-6 rounded border" style={{ backgroundColor: env.color }} /></td>
<td className="px-3 py-2 text-[var(--dg-text-secondary)]">{env.sort_order}</td>
<td className="px-3 py-2">
<Badge className={env.enabled ? 'bg-emerald-500/20 dg-text-green border-emerald-500/30' : 'bg-[var(--dg-bg-active)]/20 dg-text-secondary border-[var(--dg-border)]'}>{env.enabled ? '启用' : '禁用'}</Badge>
<Badge className={env.enabled ? 'bg-emerald-500/20 dg-text-green border-emerald-500/30' : 'bg-[var(--dg-bg-active)]/20 dg-text-secondary border-[var(--dg-border)]'}>{env.enabled ? t('common.enable') : t('common.disable')}</Badge>
</td>
<td className="px-3 py-2 text-right">
<div className="flex justify-end gap-1">
@@ -148,7 +148,7 @@ export default function DeployRBAC() {
const handleDelete = async (role: ItemRole) => {
const ok = await _confirm({
title: i18n.t('deploy.rbac.policy.deletePolicy'),
description: `确定删除「${role.name}」?将影响 ${matchCount[role.id]?.length || 0} 个发布任务`,
description: i18n.t("deploy.rbac.deleteRoleDesc", { name: role.name, count: matchCount[role.id]?.length || 0 }),
variant: 'destructive',
});
if (!ok) return;
@@ -156,7 +156,7 @@ export function WorkspaceTermDialog({
)}
style={fullscreen ? { maxWidth: '100vw', width: '100vw', height: '100vh' } : { maxWidth: '900px', width: '90vw' }}
>
<DialogTitle className="sr-only">Workspace 终端 #{buildNumber}</DialogTitle>
<DialogTitle className="sr-only">{t("deploy.workspaceTerminalTitle", { num: buildNumber })}</DialogTitle>
<DialogDescription className="sr-only">{t("common.buildTerminal")}</DialogDescription>
<div className="flex items-center justify-between px-4 py-2 border-b border-[var(--dg-border)]">
<div className="flex items-center gap-2 text-sm min-w-0">
@@ -292,7 +292,7 @@ export function CredFormDialog({ open, onClose, onSaved, editData }: CredFormDia
{secretType === 'rdp' && (
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<Label className="text-[var(--dg-text-secondary)] text-xs">Windows 用户 <span className="dg-text-red">*</span></Label>
<Label className="text-[var(--dg-text-secondary)] text-xs">{t("deploy.cred.windowsUser")} <span className="dg-text-red">*</span></Label>
<Input value={rdpUsername} onChange={e => setRdpUsername(e.target.value)} placeholder="Administrator"
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
</div>
@@ -157,7 +157,7 @@ export default function ClusterOverviewPage() {
<span className="text-[var(--dg-text-secondary)]">{cpuPct.toFixed(0)}%</span>
</div>
) : (
<span className="text-[var(--dg-text-muted)]">{cpuCores} 核</span>
<span className="text-[var(--dg-text-muted)]">{cpuCores} {i18n.t("k8s.cluster.cpuCores", { count: cpuCores })}</span>
)}
</TableCell>
<TableCell className="text-xs">
@@ -137,7 +137,7 @@ export function DeploymentEditContainers({ clusterCode, namespace, deploymentNam
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
<KeyRound className="h-4 w-4 dg-text-yellow" />
镜像拉取凭证 (imagePullSecrets)
<Badge variant="outline" className="border-[var(--dg-border)] dg-text-muted text-[10px]">Pod 级别</Badge>
<Badge variant="outline" className="border-[var(--dg-border)] dg-text-muted text-[10px]">{i18n.t('k8s.podLevel')}</Badge>
</CardTitle>
</CardHeader>
<CardContent className="pt-0 space-y-2">
@@ -262,7 +262,7 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName,
<code key={k} className="bg-[var(--dg-bg-tertiary)] px-1.5 py-0.5 rounded text-[var(--dg-text-secondary)] text-[11px]">{k}={v}</code>
))}
{Object.keys(selectorLabels).length === 0 && (
<span className="dg-text-muted italic">(无 selector)</span>
<span className="dg-text-muted italic">{i18n.t('k8s.editService.noSelector')}</span>
)}
</div>
<Button size="sm" variant="ghost" className="h-6 ml-auto text-xs dg-text-secondary" onClick={load}>
@@ -279,7 +279,7 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName,
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
<Network className="h-4 w-4 dg-text-blue" />
Services
<Badge variant="outline" className="border-[var(--dg-border)] dg-text-secondary">{matchedSvcs.length} 个关联</Badge>
<Badge variant="outline" className="border-[var(--dg-border)] dg-text-secondary">{i18n.t('k8s.editService.svcLinked', { count: matchedSvcs.length })}</Badge>
</CardTitle>
<Button size="sm" variant="outline" className="h-7 border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-xs"
onClick={() => {
@@ -295,7 +295,7 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName,
{matchedSvcs.length === 0 && showCreate !== 'service' ? (
<div className="text-center py-6 dg-text-muted text-sm border border-dashed border-[var(--dg-border)] rounded-lg">
<Network className="h-8 w-8 mx-auto mb-2 dg-text-muted" />
当前命名空间暂无关联 Service
{i18n.t('k8s.editService.noSvcInNs')}
</div>
) : (
<div className="space-y-2">
@@ -330,7 +330,7 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName,
<div className="flex items-center justify-between">
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
<GitBranch className="h-4 w-4 dg-text-purple" />
Ingresses (应用路由)
{i18n.t('k8s.editService.ingressesLabel')}
<Badge variant="outline" className="border-[var(--dg-border)] dg-text-secondary">{matchedIngs.length}/{ingresses.length}</Badge>
</CardTitle>
<Button size="sm" variant="outline" className="h-7 border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-xs"
@@ -341,13 +341,13 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName,
<Plus className="h-3 w-3 mr-1" />{i18n.t("common.createIngress")}
</Button>
</div>
<p className="text-xs dg-text-muted mt-1">backend 指向关联 Service 的 Ingress 自动高亮</p>
<p className="text-xs dg-text-muted mt-1">{i18n.t('k8s.editService.ingressAutoHighlight')}</p>
</CardHeader>
<CardContent className="pt-0">
{ingresses.length === 0 ? (
<div className="text-center py-6 dg-text-muted text-sm border border-dashed border-[var(--dg-border)] rounded-lg">
<GitBranch className="h-8 w-8 mx-auto mb-2 dg-text-muted" />
当前命名空间暂无 Ingress
{i18n.t('k8s.editService.noIngInNs')}
</div>
) : (
<div className="space-y-2">
@@ -296,7 +296,7 @@ export function DeploymentEditStorage({ clusterCode, namespace, deploymentName,
onChange={(e) => updateMount(i, mi, { containerName: e.target.value })}
className="h-8 min-w-[140px] max-w-[280px] bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-md text-sm text-[var(--dg-text-primary)] px-2 font-medium truncate"
>
{containers.length === 0 && <option value="">(无容器)</option>}
{containers.length === 0 && <option value="">{i18n.t('k8s.cluster.noContainer')}</option>}
{containers.map((cn) => <option key={cn} value={cn}>{cn}</option>)}
</select>
<div className="flex items-center gap-1 bg-[var(--dg-bg-tertiary)] rounded-lg p-0.5 border border-[var(--dg-border)]">
@@ -161,7 +161,7 @@ export function KubectlTerminal({ open, onOpenChange, clusterId, clusterName }:
style={fullscreen ? { maxWidth: '100vw', width: '100vw', height: '100vh' } : { maxWidth: '900px', width: '90vw' }}
>
<DialogTitle className="sr-only">kubectl — {clusterName}</DialogTitle>
<DialogDescription className="sr-only">Kubernetes 集群命令行终端</DialogDescription>
<DialogDescription className="sr-only">{i18n.t('k8s.kubectlTerminalDesc')}</DialogDescription>
{/* 头部 */}
<div className="flex items-center justify-between px-4 py-2 border-b border-[var(--dg-border)]">
@@ -219,7 +219,7 @@ export function KubernetesClusterDialog({
{formData.config_type === 0 ? (
<div className="space-y-2">
<Label className="text-[var(--dg-text-secondary)]">
Token {hasToken && <span className="dg-text-green text-xs ml-1">✓ 已配置(留空不修改)</span>}
Token {hasToken && <span className="dg-text-green text-xs ml-1">{i18n.t('k8s.clusterDialog.configured')}</span>}
</Label>
<Textarea
value={formData.token}
@@ -232,7 +232,7 @@ export function KubernetesClusterDialog({
) : (
<div className="space-y-2">
<Label className="text-[var(--dg-text-secondary)]">
Kubeconfig {hasKubeConfig && <span className="dg-text-green text-xs ml-1">✓ 已配置(留空不修改)</span>}
Kubeconfig {hasKubeConfig && <span className="dg-text-green text-xs ml-1">{i18n.t('k8s.clusterDialog.configured')}</span>}
</Label>
<Textarea
value={formData.kube_config}
@@ -203,7 +203,7 @@ export function ResourceVisualEditor({ clusterCode, namespace, resource, name, o
<button
className={`px-3 py-1.5 text-xs font-medium transition-colors ${mode === 'yaml' ? 'bg-blue-600 text-white' : 'bg-transparent text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-tertiary)]'}`}
onClick={() => { if (mode === 'visual') switchToYaml(); else setMode('yaml'); }}
>YAML 编辑</button>
>YAML {i18n.t('k8s.rve.yamlEditBtn')}</button>
</div>
<Button variant="ghost" size="icon" className="h-8 w-8 text-[var(--dg-text-secondary)]" onClick={onClose}><X className="h-4 w-4" /></Button>
</div>
@@ -92,7 +92,7 @@ export function CreateHPADialog({ clusterCode, namespace, deploymentName, onCrea
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<Label className="dg-text-secondary text-sm">CPU 目标 (%)</Label>
<Label className="dg-text-secondary text-sm">{i18n.t('k8s.hpa.cpuTarget')}</Label>
<Input type="number" value={form.cpuPercent} min={0} max={100}
onChange={(e) => setForm({ ...form, cpuPercent: Number(e.target.value) })}
className="dg-input mt-1" />
@@ -40,23 +40,23 @@ export function UpdateStrategyCard({
<label className="flex items-center gap-1.5 text-xs text-[var(--dg-text-secondary)] cursor-pointer">
<input type="radio" checked={strategyType === 'RollingUpdate'} onChange={() => onStrategyTypeChange('RollingUpdate')}
className="accent-blue-500" />
RollingUpdate(滚动更新)
{i18n.t('k8s.editAdvanced.rollingUpdateDesc')}
</label>
<label className="flex items-center gap-1.5 text-xs text-[var(--dg-text-secondary)] cursor-pointer">
<input type="radio" checked={strategyType === 'Recreate'} onChange={() => onStrategyTypeChange('Recreate')}
className="accent-blue-500" />
Recreate(重建)
{i18n.t('k8s.editAdvanced.recreateDesc')}
</label>
</div>
{strategyType === 'RollingUpdate' && (
<div className="grid grid-cols-2 gap-3">
<div>
<Label className="dg-text-muted text-xs">maxSurge(整数或百分比,超出期望副本数)</Label>
<Label className="dg-text-muted text-xs">{i18n.t('k8s.editAdvanced.maxSurgeHint')}</Label>
<Input value={maxSurge} onChange={e => onMaxSurgeChange(e.target.value)}
placeholder="25%" className="h-8 mt-1 dg-input text-sm font-mono" />
</div>
<div>
<Label className="dg-text-muted text-xs">maxUnavailable(整数或百分比,不可用上限)</Label>
<Label className="dg-text-muted text-xs">{i18n.t('k8s.editAdvanced.maxUnavailableHint')}</Label>
<Input value={maxUnavailable} onChange={e => onMaxUnavailableChange(e.target.value)}
placeholder="25%" className="h-8 mt-1 dg-input text-sm font-mono" />
</div>
@@ -74,12 +74,12 @@ export function UpdateStrategyCard({
</CardHeader>
<CardContent className="pt-0 grid grid-cols-2 gap-3">
<div>
<Label className="dg-text-muted text-xs">revisionHistoryLimit(保留 RS 历史)</Label>
<Label className="dg-text-muted text-xs">{i18n.t('k8s.editAdvanced.revisionHistoryLimitHint')}</Label>
<Input type="number" value={revisionHistoryLimit} onChange={e => onRevisionHistoryLimitChange(e.target.value)}
className="h-8 mt-1 dg-input text-sm font-mono" />
</div>
<div>
<Label className="dg-text-muted text-xs">progressDeadlineSeconds(升级超时秒数)</Label>
<Label className="dg-text-muted text-xs">{i18n.t('k8s.editAdvanced.progressDeadlineSecondsHint')}</Label>
<Input type="number" value={progressDeadlineSeconds} onChange={e => onProgressDeadlineSecondsChange(e.target.value)}
className="h-8 mt-1 dg-input text-sm font-mono" />
</div>
@@ -35,7 +35,7 @@ export function IngressCreateForm({ ingForm, setIngForm, deploymentName, onCance
className="h-7 mt-0.5 dg-input text-[var(--dg-text-secondary)] text-xs font-mono" />
</div>
<div>
<Label className="dg-text-muted text-[11px]">host (域名)</Label>
<Label className="dg-text-muted text-[11px]">{i18n.t('k8s.editService.hostDomain')}</Label>
<Input value={ingForm.host} onChange={e => setIngForm(s => ({ ...s, host: e.target.value }))}
placeholder="app.example.com"
className="h-7 mt-0.5 dg-input text-[var(--dg-text-secondary)] text-xs font-mono" />
@@ -47,7 +47,7 @@ export function IngressCreateForm({ ingForm, setIngForm, deploymentName, onCance
className="h-7 mt-0.5 dg-input text-[var(--dg-text-secondary)] text-xs font-mono" />
</div>
<div>
<Label className="dg-text-muted text-[11px]">Service 端口</Label>
<Label className="dg-text-muted text-[11px]">{i18n.t('k8s.editService.servicePortLabel')}</Label>
<Input type="number" value={ingForm.svcPort} onChange={e => setIngForm(s => ({ ...s, svcPort: e.target.value }))}
className="h-7 mt-0.5 dg-input text-[var(--dg-text-secondary)] text-xs font-mono" />
</div>
@@ -84,7 +84,7 @@ export function ServiceCreateForm({ svcForm, setSvcForm, selectorLabels, deploym
{/* 端口列表 */}
<div className="space-y-2">
<Label className="dg-text-muted text-[11px]">
端口配置 <span className="dg-text-muted">({svcForm.ports.length} 个端口)</span>
{i18n.t('k8s.editService.portConfig')} <span className="dg-text-muted">({i18n.t('k8s.editService.portCount', { count: svcForm.ports.length })})</span>
</Label>
{svcForm.ports.map((p, i) => (
<div key={i} className="flex items-center gap-1.5 p-2 rounded bg-[var(--dg-bg-tertiary)]/50 border border-[var(--dg-border)]/50">
@@ -128,7 +128,7 @@ export function ServiceCreateForm({ svcForm, setSvcForm, selectorLabels, deploym
{/* Selector 预览 */}
<div className="text-[10px] dg-text-muted border-t border-[var(--dg-border)]/50 pt-2">
<span className="dg-text-muted">selector (自动检测): </span>
<span className="dg-text-muted">{i18n.t('k8s.editService.selectorAutoDetect')} </span>
{Object.keys(selectorLabels).length > 0 ? (
Object.entries(selectorLabels).map(([k, v]) => (
<code key={k} className="bg-[var(--dg-bg-tertiary)] px-1 py-0.5 rounded dg-text-secondary ml-1">{k}={v}</code>
@@ -7,8 +7,8 @@ import {
} from '@/components/ui/table';
import { Activity, Terminal, HardDrive } from 'lucide-react';
import { statusColor } from '../k8s-yaml-utils';
import type { JsonData } from '@/types/common';
import i18n from '@/i18n';
import type { JsonData } from '@/types/common';
export function PodInstancesTable({
pods,
@@ -30,7 +30,7 @@ export function PodInstancesTable({
<Table>
<TableHeader>
<TableRow className="border-[var(--dg-border)] hover:bg-transparent">
<TableHead className="text-[var(--dg-text-secondary)]">Pod 名称</TableHead>
<TableHead className="text-[var(--dg-text-secondary)]">{i18n.t('k8s.podNameLabel')}</TableHead>
<TableHead className="text-[var(--dg-text-secondary)]"> {i18n.t('system.terminalStatus')} </TableHead>
<TableHead className="text-[var(--dg-text-secondary)]"> {i18n.t('kubernetes.pod.nodeName')} </TableHead>
<TableHead className="text-[var(--dg-text-secondary)]">Pod IP</TableHead>
@@ -55,7 +55,7 @@ export function UpdateStrategySelectorCards({ stats }: { stats: WorkloadStats })
)}
{isSts && (
<div className="flex justify-between">
<span className="text-[var(--dg-text-muted)]">Pod 管理策略</span>
<span className="text-[var(--dg-text-muted)]">{i18n.t('k8s.podManagePolicy')}</span>
<span className="text-[var(--dg-text-secondary)] font-mono">{spec.podManagementPolicy || 'OrderedReady'}</span>
</div>
)}
@@ -75,7 +75,7 @@ export function AlertRulesTab({
if (selectedIds.length === 0) return;
const ok = await confirm({
title: i18n.t('monitor.alertRulesTab.deleteRule'),
description: `确定删除选中的 ${selectedIds.length} 条告警规则吗?此操作不可撤销。`,
description: i18n.t("monitor.deleteRulesConfirmDesc", { count: selectedIds.length }),
confirmText: i18n.t('system.terminalBatchDelete'),
variant: 'destructive',
});
@@ -194,7 +194,7 @@ export function AlertRulesTab({
)}
</TableCell>
<TableCell>
<span className="text-sm text-[var(--dg-text-secondary)]">{(rule.duration || 0) / 60}分钟</span>
<span className="text-sm text-[var(--dg-text-secondary)]">{i18n.t("monitor.durationMinutes", { count: (rule.duration || 0) / 60 })}</span>
</TableCell>
<TableCell>
{renderRuleChannels(rule, notifyChannels)}
@@ -50,7 +50,7 @@ export default function PrometheusDatasourceManager() {
const handleDelete = async (ds: PromDS) => {
const ok = await _confirm({
title: i18n.t('monitor.deleteDatasource'),
description: `确定删除数据源「${ds.name}」吗?此操作不可撤销。`,
description: i18n.t("monitor.deleteDatasourceConfirmDesc", { name: ds.name }),
variant: 'destructive',
});
if (!ok) return;
@@ -48,7 +48,7 @@ export function LogDatasourcePage() {
const handleDelete = async (ds: LogDataSource) => {
const ok = await confirm({
title: i18n.t('monitor.deleteDatasource'),
description: `确认删除「${ds.name}」?`,
description: i18n.t("monitor.deleteLogDsConfirmDesc", { name: ds.name }),
variant: 'destructive',
confirmText: i18n.t('kubernetes.storage.delete2'),
});
@@ -131,7 +131,7 @@ export function QueryHistoryPanel({ visible, onClose, onSelect, onCreateAlert }:
<div className="flex items-center gap-2">
<span className="text-[10px]" style={{ color: 'var(--dg-text-muted)' }}>{item.timeRange}</span>
{item.resultCount !== undefined && (
<span className="text-[10px]" style={{ color: 'var(--dg-text-muted)' }}>{item.resultCount} 条</span>
<span className="text-[10px]" style={{ color: 'var(--dg-text-muted)' }}>{i18n.t("monitor.queryHistory.count", { count: item.resultCount })}</span>
)}
</div>
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
@@ -164,7 +164,7 @@ export function ServerFileBrowser({ open, onOpenChange, serverId, serverName }:
}
>
<DialogTitle className="sr-only">{i18n.t("common.fileBrowse")} — {serverName}</DialogTitle>
<DialogDescription className="sr-only">SFTP 文件管理 · 上传 · 下载 · 删除</DialogDescription>
<DialogDescription className="sr-only">{i18n.t("system.sftpFileDesc")}</DialogDescription>
{/* Header */}
<div className="flex items-center justify-between px-4 py-2.5 border-b border-[var(--dg-border)] shrink-0">
@@ -173,7 +173,7 @@ export function ServerFileBrowser({ open, onOpenChange, serverId, serverName }:
<span className="text-[var(--dg-text-primary)] font-medium text-sm truncate">
{serverName}
</span>
<span className="text-[var(--dg-text-muted)] text-xs">· SFTP 文件管理</span>
<span className="text-[var(--dg-text-muted)] text-xs">{i18n.t("system.sftpFileLabel")}</span>
</div>
<div className="flex items-center gap-1">
<button
@@ -222,7 +222,7 @@ export function TerminalDialog({
}
>
<DialogTitle className="sr-only">{t("common.podLogs")} - {serverName}</DialogTitle>
<DialogDescription className="sr-only">SSH 终端连接</DialogDescription>
<DialogDescription className="sr-only">{t("system.sshTerminalDesc")}</DialogDescription>
{/* 终端头部 */}
<div className="flex items-center justify-between px-4 py-2 border-b border-[var(--dg-border)]">
<div className="flex items-center gap-2 text-sm">
@@ -124,7 +124,7 @@ export function CredentialManager() {
const item = credentials.find(c => c.id === id);
const ok = await confirm({
title: t('pipeline.deleteCred'),
description: `确定删除「${item?.name || id}」吗?删除后无法恢复,关联的服务器将无法使用此凭据连接。`,
description: i18n.t("system.deleteCredConfirmDesc", { name: item?.name || id }),
confirmText: t('common.delete'),
variant: 'destructive',
});
@@ -12,6 +12,7 @@ import { cn } from '@/lib/utils';
import { getAuthToken, getWsBase } from '@/utils/request';
import { rdpSessionApi } from '@/api';
import { Loader2, Maximize2, Minimize2, Pause, Play, RotateCcw, X } from 'lucide-react';
import i18n from '@/i18n';
interface RdpAuditViewerProps {
open: boolean;
@@ -237,8 +238,8 @@ export function RdpAuditViewer({ open, onOpenChange, sessionId, mode }: RdpAudit
)}
style={fullscreen ? { maxWidth: '100vw', width: '100vw', height: '100vh' } : { maxWidth: '1120px', width: '94vw' }}
>
<DialogTitle className="sr-only">{mode === 'live' ? 'RDP 实时审计' : 'RDP 历史回放'}</DialogTitle>
<DialogDescription className="sr-only">Windows RDP 图形会话审计查看</DialogDescription>
<DialogTitle className="sr-only">{mode === 'live' ? i18n.t('system.rdpLiveAudit') : i18n.t('system.rdpHistoryReplay')}</DialogTitle>
<DialogDescription className="sr-only">{i18n.t("system.rdpAuditDesc")}</DialogDescription>
<div className="flex items-center justify-between px-4 py-2 border-b border-[var(--dg-border)]">
<div className="text-sm font-medium text-[var(--dg-text-primary)]">
{mode === 'live' ? 'RDP 实时审计' : `RDP 历史回放 #${sessionId || ''}`}
@@ -207,7 +207,7 @@ export function SystemManager() {
const handleDelete = async (user: any) => {
const ok = await confirm({
title: t("system.deleteUser"),
description: `确认删除用户「${user.nickname || user.username}」吗?`,
description: t('system.deleteUserConfirmDesc', { name: user.nickname || user.username }),
confirmText: t("common.delete"),
variant: "destructive",
});
@@ -228,7 +228,7 @@ export function SystemManager() {
if (selectedUserIds.length === 0) return;
const ok = await confirm({
title: t("system.batchDeleteUsers"),
description: `确定删除选中的 ${selectedUserIds.length} 个用户吗?此操作不可恢复。`,
description: t("system.batchDeleteUsersConfirmDesc", { count: selectedUserIds.length }),
confirmText: t("system.terminalBatchDelete"),
variant: "destructive",
});
@@ -129,7 +129,7 @@ export function TerminalSessionsTab() {
if (selected.size === 0) return;
const ok = await confirm({
title: t('system.terminalBatchDelete'),
description: `确认删除选中的 ${selected.size} 条终端会话记录?`,
description: t("system.deleteTermSessionsConfirmDesc", { count: selected.size }),
confirmText: t('common.delete'),
variant: 'destructive',
});
@@ -266,11 +266,11 @@ export function SecuritySettingsTab(props: SecuritySettingsTabProps) {
<SelectItem value="15">
{t("monitor.traceMgr.min15")}
</SelectItem>
<SelectItem value="30">30分钟</SelectItem>
<SelectItem value="30">{t("common.minutes30")}</SelectItem>
<SelectItem value="60">
{t("monitor.traceMgr.hour1")}
</SelectItem>
<SelectItem value="120">2小时</SelectItem>
<SelectItem value="120">{t("common.hours2")}</SelectItem>
</SelectContent>
</Select>
</div>
@@ -65,7 +65,7 @@ export function CronJobViewDialog({ open, onOpenChange, job }: Props) {
</div>
<div>
<div className="dg-text-muted mb-0.5">{t("common.totalRun")}</div>
<div className="text-[var(--dg-text-primary)] tabular-nums">{job.run_count ?? 0} 次</div>
<div className="text-[var(--dg-text-primary)] tabular-nums">{t("task.cronJob.runCount", { count: job.run_count ?? 0 })}</div>
</div>
<div>
<div className="dg-text-muted mb-0.5">{t("common.successFail")}</div>