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:
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>
|
||||
|
||||
Reference in new issue
Block a user