refactor(i18n): U27 多模块 phantom key 批量清理 — Cloud/Deploy/Agent/Monitor/System 5 模块 25 文件, 180 个 phantom key → 51 残留, 129 个替换为语义化键, 新增 12 子命名空间, zh/en 7185→7294 +109 键对齐, tsc -b=0, npm build✓ (16.31s), ESLint=0/0/0, go build=0, 17/17 测试通过

This commit is contained in:
attacker committed 2026-07-12 03:00:20 +08:00
1 parent 7f5295cfc2
commit b812b3b20c
27 files changed
+459 -148

No files matched your search

+1
View File
@@ -517,6 +517,7 @@ services/api.ts (1324L)
| U23 | Task 模块 i18n 治理(CronJobManager + CronJobFormDialog + ScriptManager + RemoteExecPage 4 文件 — `import i18n` 全局调用 → `useTranslation()` hook;42 处 phantom key (`i18n.t('Chinese text')` → 语义化 `task.cronJob.*`/`task.scriptList.*`/`task.remoteExec.*` 键);28 个孤儿 hash 键清理 (cronjobformdial.s*/remoteexecpaget.s*/systemtaskspanel.s*);50+ 处硬编码 JSX 中文 → t() 调用;zh/en 6825→6886 +61 键对齐,tsc -b=0,npm build✓ (17.46s),ESLint=0/45/0,go build=0,17/17 测试通过) | P3 | **done** | | U23 | Task 模块 i18n 治理(CronJobManager + CronJobFormDialog + ScriptManager + RemoteExecPage 4 文件 — `import i18n` 全局调用 → `useTranslation()` hook;42 处 phantom key (`i18n.t('Chinese text')` → 语义化 `task.cronJob.*`/`task.scriptList.*`/`task.remoteExec.*` 键);28 个孤儿 hash 键清理 (cronjobformdial.s*/remoteexecpaget.s*/systemtaskspanel.s*);50+ 处硬编码 JSX 中文 → t() 调用;zh/en 6825→6886 +61 键对齐,tsc -b=0,npm build✓ (17.46s),ESLint=0/45/0,go build=0,17/17 测试通过) | P3 | **done** |
| U24 | Monitor 模块 i18n hook 迁移(26 个 .tsx 文件从 `import i18n` 全局 → `useTranslation()` hook + 89 个 phantom 中文文本键 → `monitor.lbl.*` 语义化键;299 处 `i18n.t()` 调用 → 74 处保留(模块级/.ts 工具文件) + 225 处迁移到 `t()` hook;1 个 .ts 工具文件 helpers.ts 中文文本键替换为语义化键;zh/en 6886→6975 +89 键对齐,tsc -b=0,npm build✓ (17.16s),ESLint=0/53/0,go build=0,Docker healthy) | P3 | **done** | | U24 | Monitor 模块 i18n hook 迁移(26 个 .tsx 文件从 `import i18n` 全局 → `useTranslation()` hook + 89 个 phantom 中文文本键 → `monitor.lbl.*` 语义化键;299 处 `i18n.t()` 调用 → 74 处保留(模块级/.ts 工具文件) + 225 处迁移到 `t()` hook;1 个 .ts 工具文件 helpers.ts 中文文本键替换为语义化键;zh/en 6886→6975 +89 键对齐,tsc -b=0,npm build✓ (17.16s),ESLint=0/53/0,go build=0,Docker healthy) | P3 | **done** |
| U26 | Kubernetes 模块 phantom key 大规模清理(32 个 .tsx/.ts 文件,171 个中文 phantom key → 8 个残留(均为 ANSI 转义码/JSX 表达式误匹配),163 个成功替换为 k8s.* 语义化键;新增 19 个子命名空间(nsResource/nsOverview/deploymentWorkload/deploymentView/workloadView/controlBar/clusterOverview/nodes/podLogs/podExec/pvc/configMap/secret/service/ingress/nsMgmt/editContainers/capacityPage/podFile)+ 扩展 8 个已有子命名空间;zh/en 6990→7185 +195 键对齐,k8s 命名空间 65 子命名空间/940 键,tsc -b=0,npm build✓ (17.59s),ESLint=0/53/0,go build=0,17/17 测试通过) | P3 | **done** | | U26 | Kubernetes 模块 phantom key 大规模清理(32 个 .tsx/.ts 文件,171 个中文 phantom key → 8 个残留(均为 ANSI 转义码/JSX 表达式误匹配),163 个成功替换为 k8s.* 语义化键;新增 19 个子命名空间(nsResource/nsOverview/deploymentWorkload/deploymentView/workloadView/controlBar/clusterOverview/nodes/podLogs/podExec/pvc/configMap/secret/service/ingress/nsMgmt/editContainers/capacityPage/podFile)+ 扩展 8 个已有子命名空间;zh/en 6990→7185 +195 键对齐,k8s 命名空间 65 子命名空间/940 键,tsc -b=0,npm build✓ (17.59s),ESLint=0/53/0,go build=0,17/17 测试通过) | P3 | **done** |
| U27 | 多模块 phantom key 批量清理(Cloud/Deploy/Agent/Monitor/System 5 模块 25 个文件,180 个中文 phantom key → 51 个残留,129 个成功替换为语义化键;新增 12 个子命名空间(cloud.platform/deploy.buildNodes/deploy.rbac.policy/deploy.stage/deploy.target/deploy.userGroup/agent.alerts/agent.monitor/agent.alertHelper/agent.ruleDialog/monitor.alertRulesTab/system.ldapMgmt 等);硬编码 JSX 中文(刷新/添加/启用/禁用/Master内置/执行器/标签等) → i18n.t() 调用;zh/en 7185→7294 +109 键对齐,tsc -b=0,npm build✓ (16.31s),ESLint=0/0/0,go build=0,17/17 测试通过) | P3 | **done** |
--- ---
@@ -53,7 +53,7 @@ export function AgentAlerts() {
setRules(asList(rulesRes)); setRules(asList(rulesRes));
} catch (err) { } catch (err) {
console.error(err); console.error(err);
toast.error(i18n.t('加载告警数据失败')); toast.error(i18n.t('agent.alerts.loadFailed'));
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -65,11 +65,11 @@ export function AgentAlerts() {
// ============ 告警事件 ============ // ============ 告警事件 ============
const handleResolve = async (id: number) => { const handleResolve = async (id: number) => {
const ok = await confirm({ title: i18n.t('恢复告警'), description: i18n.t('确定将此告警标记为已恢复?'), confirmText: i18n.t('kubernetes.deployment.resume') }); const ok = await confirm({ title: i18n.t('agent.alerts.resolveAlert'), description: i18n.t('agent.alerts.resolveConfirm'), confirmText: i18n.t('kubernetes.deployment.resume') });
if (!ok) return; if (!ok) return;
try { try {
await agentApi.resolveAlert(id); await agentApi.resolveAlert(id);
toast.success(i18n.t('告警已恢复')); toast.success(i18n.t('agent.alerts.resolved'));
loadData(); loadData();
} catch (err) { } catch (err) {
toast.error(err?.message || i18n.t('cloud.operationFailed')); toast.error(err?.message || i18n.t('cloud.operationFailed'));
@@ -88,11 +88,11 @@ export function AgentAlerts() {
}; };
const handleDeleteRule = async (id: number) => { const handleDeleteRule = async (id: number) => {
const ok = await confirm({ title: i18n.t('monitor.deleteRule'), description: i18n.t('删除规则'), confirmText: i18n.t('kubernetes.storage.delete2'), variant: 'destructive' }); const ok = await confirm({ title: i18n.t('monitor.deleteRule'), description: i18n.t('agent.alerts.deleteRule'), confirmText: i18n.t('kubernetes.storage.delete2'), variant: 'destructive' });
if (!ok) return; if (!ok) return;
try { try {
await agentApi.deleteRule(id); await agentApi.deleteRule(id);
toast.success(i18n.t('告警已恢复')); toast.success(i18n.t('agent.alerts.resolved'));
loadData(); loadData();
} catch (err) { } catch (err) {
toast.error(err?.message || i18n.t('kubernetes.msg.deleteFailed2')); toast.error(err?.message || i18n.t('kubernetes.msg.deleteFailed2'));
@@ -114,7 +114,7 @@ export function AgentAlerts() {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<h2 className="text-xl font-semibold text-[var(--dg-text-primary)]">Agent 告警</h2> <h2 className="text-xl font-semibold text-[var(--dg-text-primary)]">Agent 告警</h2>
<p className="text-sm dg-text-secondary mt-0.5"> {i18n.t('触发告警阈值')} </p> <p className="text-sm dg-text-secondary mt-0.5"> {i18n.t('agent.alerts.thresholdTriggered')} </p>
</div> </div>
<Button variant="outline" size="sm" onClick={loadData} disabled={loading} className="dg-input text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)]"> <Button variant="outline" size="sm" onClick={loadData} disabled={loading} className="dg-input text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)]">
<RefreshCw className={`h-4 w-4 mr-1.5 ${loading ? 'animate-spin' : ''}`} /> <RefreshCw className={`h-4 w-4 mr-1.5 ${loading ? 'animate-spin' : ''}`} />
@@ -160,7 +160,7 @@ export function AgentAlerts() {
<Card className="dg-card"> <Card className="dg-card">
<CardContent className="flex flex-col items-center justify-center py-16"> <CardContent className="flex flex-col items-center justify-center py-16">
<CheckCircle className="h-10 w-10 text-emerald-500 mb-3" /> <CheckCircle className="h-10 w-10 text-emerald-500 mb-3" />
<p className="dg-text-secondary">{resolvedView ? i18n.t('暂无已恢复的告警') : i18n.t('当前无活动告警,一切正常')}</p> <p className="dg-text-secondary">{resolvedView ? i18n.t('agent.alerts.noResolved') : i18n.t('agent.alerts.noActive')}</p>
</CardContent> </CardContent>
</Card> </Card>
) : ( ) : (
@@ -178,7 +178,7 @@ export function AgentAlerts() {
{severityBadge(alert.severity)} {severityBadge(alert.severity)}
<span className="font-medium text-[var(--dg-text-primary)]">{alert.rule_name || alert.name || alert.title || i18n.t('monitor.hostDashboard.alert')}</span> <span className="font-medium text-[var(--dg-text-primary)]">{alert.rule_name || alert.name || alert.title || i18n.t('monitor.hostDashboard.alert')}</span>
</div> </div>
<p className="text-sm dg-text-secondary mt-1">{alert.message || alert.description || i18n.t('触发告警阈值')}</p> <p className="text-sm dg-text-secondary mt-1">{alert.message || alert.description || i18n.t('agent.alerts.thresholdTriggered')}</p>
<div className="flex items-center gap-4 mt-2 text-xs dg-text-muted flex-wrap"> <div className="flex items-center gap-4 mt-2 text-xs dg-text-muted flex-wrap">
<span className="inline-flex items-center gap-1"> <span className="inline-flex items-center gap-1">
<Cpu className="h-3 w-3" /> <Cpu className="h-3 w-3" />
@@ -71,7 +71,7 @@ export function AgentMonitor() {
if (fresh) setSelectedHost(fresh); if (fresh) setSelectedHost(fresh);
} }
} catch (err) { } catch (err) {
if (!silent) toast.error(i18n.t('加载主机列表失败')); if (!silent) toast.error(i18n.t('agent.monitor.loadHostFailed'));
console.error(err); console.error(err);
} finally { } finally {
setLoading(false); setLoading(false);
@@ -100,7 +100,7 @@ export function AgentMonitor() {
// 提交远程命令(直连 SSH 即时返回) // 提交远程命令(直连 SSH 即时返回)
const submitCommand = async () => { const submitCommand = async () => {
if (!cmdCommand.trim()) { if (!cmdCommand.trim()) {
toast.error(i18n.t('请输入要执行的命令')); toast.error(i18n.t('agent.monitor.enterCommand'));
return; return;
} }
setCmdSubmitting(true); setCmdSubmitting(true);
@@ -114,7 +114,7 @@ export function AgentMonitor() {
const resData = (res?.data ?? {}) as Record<string, unknown>; const resData = (res?.data ?? {}) as Record<string, unknown>;
setCmdResult({ setCmdResult({
status: res?.code === 200 ? 'completed' : 'failed', status: res?.code === 200 ? 'completed' : 'failed',
result: (resData.output as string) || (res?.message as string) || i18n.t('无输出'), result: (resData.output as string) || (res?.message as string) || i18n.t('agent.monitor.noOutput'),
error: resData.error as string | undefined, error: resData.error as string | undefined,
}); });
} catch (err) { } catch (err) {
@@ -154,7 +154,7 @@ export function AgentMonitor() {
className="dg-input text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)]" className="dg-input text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)]"
> >
<RefreshCw className={`h-4 w-4 mr-1.5 ${refreshing ? 'animate-spin' : ''}`} /> <RefreshCw className={`h-4 w-4 mr-1.5 ${refreshing ? 'animate-spin' : ''}`} />
{t('例如:free -m && df -h')} {t('agent.monitor.commandPlaceholder')}
</Button> </Button>
<Button <Button
size="sm" size="sm"
@@ -162,7 +162,7 @@ export function AgentMonitor() {
className="bg-blue-600 hover:bg-blue-700 text-white" className="bg-blue-600 hover:bg-blue-700 text-white"
> >
<Rocket className="h-4 w-4 mr-1.5" /> <Rocket className="h-4 w-4 mr-1.5" />
{t('例如:free -m && df -h')} {t('agent.monitor.commandPlaceholder')}
</Button> </Button>
</div> </div>
</div> </div>
@@ -325,7 +325,7 @@ export function AgentMonitor() {
<Textarea <Textarea
value={cmdCommand} value={cmdCommand}
onChange={(e) => setCmdCommand(e.target.value)} onChange={(e) => setCmdCommand(e.target.value)}
placeholder={i18n.t('例如:free -m && df -h')} placeholder={i18n.t('agent.monitor.commandPlaceholder')}
rows={3} rows={3}
className="bg-[var(--dg-bg-primary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] font-mono text-sm resize-none" className="bg-[var(--dg-bg-primary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] font-mono text-sm resize-none"
/> />
@@ -359,7 +359,7 @@ export function AgentMonitor() {
</Badge> </Badge>
</div> </div>
<pre className="text-xs text-[var(--dg-text-secondary)] font-mono whitespace-pre-wrap break-all bg-[var(--dg-bg-primary)] border border-[var(--dg-border)] rounded-lg p-3 max-h-48 overflow-auto"> <pre className="text-xs text-[var(--dg-text-secondary)] font-mono whitespace-pre-wrap break-all bg-[var(--dg-bg-primary)] border border-[var(--dg-border)] rounded-lg p-3 max-h-48 overflow-auto">
{cmdResult.result || cmdResult.output || cmdResult.error || i18n.t('无输出')} {cmdResult.result || cmdResult.output || cmdResult.error || i18n.t('agent.monitor.noOutput')}
</pre> </pre>
</div> </div>
)} )}
@@ -70,7 +70,7 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
const handleSave = async () => { const handleSave = async () => {
if (!ruleForm.name.trim()) { if (!ruleForm.name.trim()) {
toast.error(i18n.t('请输入规则名称')); toast.error(i18n.t('agent.ruleDialog.enterName'));
return; return;
} }
setSaving(true); setSaving(true);
@@ -107,7 +107,7 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
<Dialog open={open} onOpenChange={(v) => { if (!v) onOpenChange(false); }}> <Dialog open={open} onOpenChange={(v) => { if (!v) onOpenChange(false); }}>
<DialogContent className="max-w-md dg-card text-[var(--dg-text-primary)]"> <DialogContent className="max-w-md dg-card text-[var(--dg-text-primary)]">
<DialogHeader> <DialogHeader>
<DialogTitle className="text-[var(--dg-text-primary)]">{editingRule ? i18n.t('monitor.editRule') : i18n.t('编辑规则')}</DialogTitle> <DialogTitle className="text-[var(--dg-text-primary)]">{editingRule ? i18n.t('monitor.editRule') : i18n.t('agent.ruleDialog.editRule')}</DialogTitle>
<DialogDescription className="dg-text-secondary"> {i18n.t('>阈值 {ruleForm.metric !== ')} </DialogDescription> <DialogDescription className="dg-text-secondary"> {i18n.t('>阈值 {ruleForm.metric !== ')} </DialogDescription>
</DialogHeader> </DialogHeader>
@@ -117,7 +117,7 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
<Input <Input
value={ruleForm.name} value={ruleForm.name}
onChange={(e) => setRuleForm({ ...ruleForm, name: e.target.value })} onChange={(e) => setRuleForm({ ...ruleForm, name: e.target.value })}
placeholder={i18n.t('例如:CPU 使用率告警')} placeholder={i18n.t('agent.ruleDialog.namePlaceholder')}
className="h-9 dg-input" className="h-9 dg-input"
/> />
</div> </div>
@@ -185,7 +185,7 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
type="number" type="number"
value={ruleForm.port} value={ruleForm.port}
onChange={(e) => setRuleForm({ ...ruleForm, port: e.target.value })} onChange={(e) => setRuleForm({ ...ruleForm, port: e.target.value })}
placeholder={i18n.t('例如:8080')} placeholder={i18n.t('agent.ruleDialog.portPlaceholder')}
className="h-9 dg-input" className="h-9 dg-input"
/> />
</div> </div>
@@ -197,7 +197,7 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
type="number" type="number"
value={ruleForm.duration} value={ruleForm.duration}
onChange={(e) => setRuleForm({ ...ruleForm, duration: e.target.value })} onChange={(e) => setRuleForm({ ...ruleForm, duration: e.target.value })}
placeholder={i18n.t('例如:CPU 使用率告警')} placeholder={i18n.t('agent.ruleDialog.namePlaceholder')}
className="h-9 dg-input" className="h-9 dg-input"
/> />
</div> </div>
@@ -10,23 +10,23 @@ import i18n from '@/i18n';
// 主机监控指标(与后端 alert_engine 对齐)// Logan.Li // 主机监控指标(与后端 alert_engine 对齐)// Logan.Li
export const METRIC_OPTIONS = [ export const METRIC_OPTIONS = [
{ value: 'cpu', label: i18n.t('kubernetes.cluster.cpuUsage'), icon: Cpu }, { value: 'cpu', label: i18n.t('kubernetes.cluster.cpuUsage'), icon: Cpu },
{ value: 'cpu_iowait', label: i18n.t('入网带宽'), icon: Cpu }, { value: 'cpu_iowait', label: i18n.t('agent.alertHelper.netInBps'), icon: Cpu },
{ value: 'mem', label: i18n.t('kubernetes.cluster.memoryUsage'), icon: MemoryStick }, { value: 'mem', label: i18n.t('kubernetes.cluster.memoryUsage'), icon: MemoryStick },
{ value: 'mem_available', label: i18n.t('可用内存(GB)'), icon: MemoryStick }, { value: 'mem_available', label: i18n.t('agent.alertHelper.memAvailable'), icon: MemoryStick },
{ value: 'swap', label: i18n.t('Swap 使用率'), icon: MemoryStick }, { value: 'swap', label: i18n.t('Swap 使用率'), icon: MemoryStick },
{ value: 'load1', label: i18n.t('1分钟负载'), icon: Gauge }, { value: 'load1', label: i18n.t('1分钟负载'), icon: Gauge },
{ value: 'disk', label: i18n.t('monitor.hostDisk'), icon: HardDrive }, { value: 'disk', label: i18n.t('monitor.hostDisk'), icon: HardDrive },
{ value: 'disk_inode', label: i18n.t('Inode 使用率'), icon: HardDrive }, { value: 'disk_inode', label: i18n.t('Inode 使用率'), icon: HardDrive },
{ value: 'disk_io_util', label: i18n.t('磁盘 IO 利用率'), icon: HardDrive }, { value: 'disk_io_util', label: i18n.t('agent.alertHelper.diskIOUtil'), icon: HardDrive },
{ value: 'tcp_established', label: i18n.t('TCP 已建立'), icon: Network }, { value: 'tcp_established', label: i18n.t('TCP 已建立'), icon: Network },
{ value: 'tcp_time_wait', label: 'TCP TIME_WAIT', icon: Network }, { value: 'tcp_time_wait', label: 'TCP TIME_WAIT', icon: Network },
{ value: 'tcp_close_wait', label: 'TCP CLOSE_WAIT', icon: Network }, { value: 'tcp_close_wait', label: 'TCP CLOSE_WAIT', icon: Network },
{ value: 'net_rx_bps', label: i18n.t('monitor.hostNetIn'), icon: Network }, { value: 'net_rx_bps', label: i18n.t('monitor.hostNetIn'), icon: Network },
{ value: 'net_tx_bps', label: i18n.t('monitor.hostNetOut'), icon: Network }, { value: 'net_tx_bps', label: i18n.t('monitor.hostNetOut'), icon: Network },
{ value: 'net_rx_errors', label: i18n.t('入网错误'), icon: Network }, { value: 'net_rx_errors', label: i18n.t('agent.alertHelper.netInErrors'), icon: Network },
{ value: 'fd', label: i18n.t('monitor.hostFileDescriptor'), icon: Activity }, { value: 'fd', label: i18n.t('monitor.hostFileDescriptor'), icon: Activity },
{ value: 'offline', label: i18n.t('主机离线'), icon: WifiOff }, { value: 'offline', label: i18n.t('agent.alertHelper.hostOffline'), icon: WifiOff },
{ value: 'port_down', label: i18n.t('端口不通'), icon: Network }, { value: 'port_down', label: i18n.t('agent.alertHelper.portDown'), icon: Network },
{ value: 'health_probe', label: i18n.t('kubernetes.container.probes'), icon: Timer }, { value: 'health_probe', label: i18n.t('kubernetes.container.probes'), icon: Timer },
]; ];
@@ -121,8 +121,8 @@ export interface ComputeCostPredictionsInput {
/** 指标定义:后端 metric name → 展示标签 / 告警表达式前缀 / 阈值 */ /** 指标定义:后端 metric name → 展示标签 / 告警表达式前缀 / 阈值 */
export const METRIC_DEFS = [ export const METRIC_DEFS = [
{ key: 'cpu', label: i18n.t('CPU使用率'), exprKey: 'cpu_usage', threshold: 85 }, { key: 'cpu', label: i18n.t('CPU使用率'), exprKey: 'cpu_usage', threshold: 85 },
{ key: 'memory', label: i18n.t('内存使用率'), exprKey: 'memory_usage', threshold: 85 }, { key: 'memory', label: i18n.t('aiops.predictTypes.memUsage'), exprKey: 'memory_usage', threshold: 85 },
{ key: 'disk', label: i18n.t('磁盘使用率'), exprKey: 'disk_usage', threshold: 90 }, { key: 'disk', label: i18n.t('aiops.predictTypes.diskUsage'), exprKey: 'disk_usage', threshold: 90 },
] as const; ] as const;
export const TIME_RANGE_LABELS: Record<string, string> = { export const TIME_RANGE_LABELS: Record<string, string> = {
@@ -66,7 +66,7 @@ export function BillingRecordDialog({ open, onOpenChange, editingRecord, onSucce
const handleSave = async () => { const handleSave = async () => {
if (!form.provider || !form.billing_date || !form.cost) { if (!form.provider || !form.billing_date || !form.cost) {
toast.error(i18n.t('请填写必填字段')); toast.error(i18n.t('cloud.billingRecordDialog.fillFields'));
return; return;
} }
setSubmitting(true); setSubmitting(true);
@@ -92,7 +92,7 @@ export function BillingRecordDialog({ open, onOpenChange, editingRecord, onSucce
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-md"> <DialogContent className="max-w-md">
<DialogHeader> <DialogHeader>
<DialogTitle>{editingRecord ? i18n.t('编辑成本记录') : i18n.t('cloud.recordCost')}</DialogTitle> <DialogTitle>{editingRecord ? i18n.t('cloud.billingRecordDialog.editRecord') : i18n.t('cloud.recordCost')}</DialogTitle>
<DialogDescription> {i18n.t('cloud.billingrecorddi.s09e541fd')} </DialogDescription> <DialogDescription> {i18n.t('cloud.billingrecorddi.s09e541fd')} </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="space-y-3"> <div className="space-y-3">
@@ -130,7 +130,7 @@ export function BillingRecordDialog({ open, onOpenChange, editingRecord, onSucce
</div> </div>
<div> <div>
<Label className="text-sm text-[var(--dg-text-secondary)]"> {i18n.t('cloud.accountName')} </Label> <Label className="text-sm text-[var(--dg-text-secondary)]"> {i18n.t('cloud.accountName')} </Label>
<Input className="mt-1" placeholder={i18n.t('主账号')} value={form.account_name} onChange={(e) => setForm({ ...form, account_name: e.target.value })} /> <Input className="mt-1" placeholder={i18n.t('cloud.billingRecordDialog.mainAccount')} value={form.account_name} onChange={(e) => setForm({ ...form, account_name: e.target.value })} />
</div> </div>
</div> </div>
<div> <div>
@@ -58,7 +58,7 @@ export function BillingRuleDialog({ open, onOpenChange, editingRule, onSuccess }
const handleSave = async () => { const handleSave = async () => {
if (!form.name || !form.threshold) { if (!form.name || !form.threshold) {
toast.error(i18n.t('请填写规则名称和阈值')); toast.error(i18n.t('cloud.billingRuleDialog.fillFields'));
return; return;
} }
setSubmitting(true); setSubmitting(true);
@@ -83,13 +83,13 @@ export function BillingRuleDialog({ open, onOpenChange, editingRule, onSuccess }
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-md"> <DialogContent className="max-w-md">
<DialogHeader> <DialogHeader>
<DialogTitle>{editingRule ? i18n.t('编辑预警规则') : i18n.t('添加预警规则')}</DialogTitle> <DialogTitle>{editingRule ? i18n.t('cloud.billingRuleDialog.editRule') : i18n.t('cloud.billingRuleDialog.addRule')}</DialogTitle>
<DialogDescription> {i18n.t('cloud.billingruledial.s75227f20')} </DialogDescription> <DialogDescription> {i18n.t('cloud.billingruledial.s75227f20')} </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="space-y-3"> <div className="space-y-3">
<div> <div>
<Label className="text-sm text-[var(--dg-text-secondary)]"> {i18n.t('monitor.silenceDialog.ruleNameRequired')} </Label> <Label className="text-sm text-[var(--dg-text-secondary)]"> {i18n.t('monitor.silenceDialog.ruleNameRequired')} </Label>
<Input className="mt-1" placeholder={i18n.t('如:日成本超 500 元预警')} value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} /> <Input className="mt-1" placeholder={i18n.t('cloud.billingRuleDialog.namePlaceholder')} value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} />
</div> </div>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div> <div>
@@ -39,7 +39,7 @@ import type { JsonData } from '@/types/common';
import i18n from '@/i18n'; import i18n from '@/i18n';
const typeLabels: Record<string, string> = { const typeLabels: Record<string, string> = {
public: i18n.t('公有云'), public: i18n.t('cloud.platform.publicCloud'),
private: i18n.t('cloud.privateCloud'), private: i18n.t('cloud.privateCloud'),
hybrid: i18n.t('cloud.hybridCloud'), hybrid: i18n.t('cloud.hybridCloud'),
}; };
@@ -138,7 +138,7 @@ export function PlatformManager() {
<div className="relative w-72"> <div className="relative w-72">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 dg-text-muted" /> <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 dg-text-muted" />
<Input <Input
placeholder={i18n.t('搜索平台名称或编码...')} placeholder={i18n.t('cloud.platform.searchPlaceholder')}
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9 h-9 dg-input" className="pl-9 h-9 dg-input"
@@ -150,7 +150,7 @@ export function PlatformManager() {
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="all"> {i18n.t('k8s.topology.allTypes')} </SelectItem> <SelectItem value="all"> {i18n.t('k8s.topology.allTypes')} </SelectItem>
<SelectItem value="public"> {i18n.t('公有云')} </SelectItem> <SelectItem value="public"> {i18n.t('cloud.platform.publicCloud')} </SelectItem>
<SelectItem value="private"> {i18n.t('cloud.privateCloud')} </SelectItem> <SelectItem value="private"> {i18n.t('cloud.privateCloud')} </SelectItem>
<SelectItem value="hybrid"> {i18n.t('cloud.hybridCloud')} </SelectItem> <SelectItem value="hybrid"> {i18n.t('cloud.hybridCloud')} </SelectItem>
</SelectContent> </SelectContent>
@@ -162,7 +162,7 @@ export function PlatformManager() {
<PageShell <PageShell
variant="list" variant="list"
title={i18n.t('cloud.title')} title={i18n.t('cloud.title')}
subtitle={i18n.t('管理公有云、私有云与混合云平台信息')} subtitle={i18n.t('cloud.platform.subtitle')}
icon={<CloudCog className="h-5 w-5" />} icon={<CloudCog className="h-5 w-5" />}
metrics={ metrics={
!loading ? ( !loading ? (
@@ -170,7 +170,7 @@ export function PlatformManager() {
items={[ items={[
{ label: i18n.t('system.auditTotal'), value: stats.total, color: 'blue' }, { label: i18n.t('system.auditTotal'), value: stats.total, color: 'blue' },
{ label: i18n.t('system.userActive'), value: stats.active, color: 'green' }, { label: i18n.t('system.userActive'), value: stats.active, color: 'green' },
{ label: i18n.t('公有云'), value: stats.public, color: 'cyan' }, { label: i18n.t('cloud.platform.publicCloud'), value: stats.public, color: 'cyan' },
]} ]}
/> />
) : undefined ) : undefined
@@ -180,11 +180,11 @@ export function PlatformManager() {
<> <>
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={loadData}> <Button variant="outline" size="sm" className="dg-btn-secondary" onClick={loadData}>
<RefreshCw className={`h-4 w-4 mr-1 ${loading ? 'animate-spin' : ''}`} /> <RefreshCw className={`h-4 w-4 mr-1 ${loading ? 'animate-spin' : ''}`} />
刷新 {i18n.t('common.refresh')}
</Button> </Button>
<Button size="sm" className="dg-btn-primary" onClick={openCreate}> <Button size="sm" className="dg-btn-primary" onClick={openCreate}>
<Plus className="h-4 w-4 mr-1" /> <Plus className="h-4 w-4 mr-1" />
添加平台 {i18n.t('cloud.platform.addPlatform')}
</Button> </Button>
</> </>
)} )}
@@ -194,11 +194,11 @@ export function PlatformManager() {
) : filtered.length === 0 ? ( ) : filtered.length === 0 ? (
<PageEmpty <PageEmpty
icon={<CloudCog className="h-12 w-12" />} icon={<CloudCog className="h-12 w-12" />}
title={i18n.t('暂无云平台数据')} title={i18n.t('cloud.platform.noData')}
action={( action={(
<Button size="sm" className="dg-btn-primary" onClick={openCreate}> <Button size="sm" className="dg-btn-primary" onClick={openCreate}>
<Plus className="h-4 w-4 mr-1" /> <Plus className="h-4 w-4 mr-1" />
添加平台 {i18n.t('cloud.platform.addPlatform')}
</Button> </Button>
)} )}
/> />
@@ -244,11 +244,11 @@ export function PlatformManager() {
</Badge> </Badge>
{p.status === 'active' ? ( {p.status === 'active' ? (
<Badge className="dg-status-running gap-1 border-0"> <Badge className="dg-status-running gap-1 border-0">
<CheckCircle2 className="h-3 w-3" /> 启用 <CheckCircle2 className="h-3 w-3" /> {i18n.t('cloud.platform.enabled')}
</Badge> </Badge>
) : ( ) : (
<Badge className="dg-status-stopped gap-1 border-0"> <Badge className="dg-status-stopped gap-1 border-0">
<XCircle className="h-3 w-3" /> 禁用 <XCircle className="h-3 w-3" /> {i18n.t('cloud.platform.disabled')}
</Badge> </Badge>
)} )}
</div> </div>
@@ -261,26 +261,26 @@ export function PlatformManager() {
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}> <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>{editingItem ? i18n.t('编辑云平台') : i18n.t('添加云平台')}</DialogTitle> <DialogTitle>{editingItem ? i18n.t('cloud.platform.editPlatform') : i18n.t('cloud.platform.addPlatform')}</DialogTitle>
<DialogDescription className="sr-only"> {i18n.t('aiops.llmModelDesc')} </DialogDescription> <DialogDescription className="sr-only"> {i18n.t('aiops.llmModelDesc')} </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="space-y-4 py-2"> <div className="space-y-4 py-2">
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div className="space-y-1.5"> <div className="space-y-1.5">
<Label> {i18n.t('如:阿里云')} </Label> <Label> {i18n.t('common.name')} </Label>
<Input <Input
value={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e) => setFormData({ ...formData, name: e.target.value })}
placeholder={i18n.t('如:阿里云')} placeholder={i18n.t('cloud.platform.namePlaceholder')}
className="dg-input" className="dg-input"
/> />
</div> </div>
<div className="space-y-1.5"> <div className="space-y-1.5">
<Label> {i18n.t('如:阿里云')} </Label> <Label> {i18n.t('common.code')} </Label>
<Input <Input
value={formData.code} value={formData.code}
onChange={(e) => setFormData({ ...formData, code: e.target.value })} onChange={(e) => setFormData({ ...formData, code: e.target.value })}
placeholder={i18n.t('如:阿里云')} placeholder={i18n.t('cloud.platform.namePlaceholder')}
className="dg-input" className="dg-input"
disabled={!!editingItem} disabled={!!editingItem}
/> />
@@ -294,7 +294,7 @@ export function PlatformManager() {
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="public"> {i18n.t('公有云')} </SelectItem> <SelectItem value="public"> {i18n.t('cloud.platform.publicCloud')} </SelectItem>
<SelectItem value="private"> {i18n.t('cloud.privateCloud')} </SelectItem> <SelectItem value="private"> {i18n.t('cloud.privateCloud')} </SelectItem>
<SelectItem value="hybrid"> {i18n.t('cloud.hybridCloud')} </SelectItem> <SelectItem value="hybrid"> {i18n.t('cloud.hybridCloud')} </SelectItem>
</SelectContent> </SelectContent>
@@ -314,7 +314,7 @@ export function PlatformManager() {
</div> </div>
</div> </div>
<div className="space-y-1.5"> <div className="space-y-1.5">
<Label> {i18n.t('如:阿里云')} </Label> <Label> {i18n.t('cloud.platform.namePlaceholder')} </Label>
<Input <Input
value={formData.website} value={formData.website}
onChange={(e) => setFormData({ ...formData, website: e.target.value })} onChange={(e) => setFormData({ ...formData, website: e.target.value })}
@@ -327,7 +327,7 @@ export function PlatformManager() {
<Input <Input
value={formData.description} value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })} onChange={(e) => setFormData({ ...formData, description: e.target.value })}
placeholder={i18n.t('平台描述...')} placeholder={i18n.t('cloud.platform.descPlaceholder')}
className="dg-input" className="dg-input"
/> />
</div> </div>
@@ -59,7 +59,7 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
const srvList = (srvRes as { list?: ServerItem[] })?.list || []; const srvList = (srvRes as { list?: ServerItem[] })?.list || [];
setServers(srvList); setServers(srvList);
} catch { } catch {
toast.error(i18n.t('加载构建节点失败')); toast.error(i18n.t('deploy.buildNodes.loadFailed'));
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -91,7 +91,7 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
const handleSubmit = async () => { const handleSubmit = async () => {
if (!name.trim() || !serverId) { if (!name.trim() || !serverId) {
toast.error(i18n.t('加载构建节点失败')); toast.error(i18n.t('deploy.buildNodes.loadFailed'));
return; return;
} }
setSubmitting(true); setSubmitting(true);
@@ -121,14 +121,14 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
}; };
const handleDelete = async (node: BuildNode) => { const handleDelete = async (node: BuildNode) => {
const ok = await confirm({ title: i18n.t('kubernetes.topology.deleteNode'), description: `确定删除构建节点「${node.name}」?` }); const ok = await confirm({ title: i18n.t('kubernetes.topology.deleteNode'), description: i18n.t('deploy.buildNodes.deleteConfirm', { name: node.name }) });
if (!ok) return; if (!ok) return;
try { try {
await buildNodeApi.delete(node.id); await buildNodeApi.delete(node.id);
toast.success(i18n.t('monitor.silenceRules.deleted')); toast.success(i18n.t('monitor.silenceRules.deleted'));
load(); load();
} catch { } catch {
toast.error(i18n.t('加载构建节点失败')); toast.error(i18n.t('deploy.buildNodes.loadFailed'));
} }
}; };
@@ -137,7 +137,7 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
try { try {
const res = await buildNodeApi.test(node.id) as { success?: boolean; message?: string }; const res = await buildNodeApi.test(node.id) as { success?: boolean; message?: string };
if (res?.success) { if (res?.success) {
toast.success(`连通成功: ${res.message || ''}`); toast.success(i18n.t('deploy.buildNodes.testSuccess') + (res.message ? `: ${res.message}` : ''));
} else { } else {
toast.error(res?.message || i18n.t('aiops.llmTestFailed')); toast.error(res?.message || i18n.t('aiops.llmTestFailed'));
} }
@@ -160,7 +160,7 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
const handleSaveMaster = async () => { const handleSaveMaster = async () => {
const n = Number(masterMax); const n = Number(masterMax);
if (!n || n < 1) { if (!n || n < 1) {
toast.error(i18n.t('加载构建节点失败')); toast.error(i18n.t('deploy.buildNodes.loadFailed'));
return; return;
} }
setSavingMaster(true); setSavingMaster(true);
@@ -172,7 +172,7 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
max_executors: m?.max_executors ?? n, max_executors: m?.max_executors ?? n,
running_count: m?.running_count ?? prev.running_count, running_count: m?.running_count ?? prev.running_count,
})); }));
toast.success(i18n.t('已删除')); toast.success(i18n.t('deploy.buildNodes.deleted'));
} catch { } catch {
toast.error(i18n.t('kubernetes.deployment.imageUpdateFailed')); toast.error(i18n.t('kubernetes.deployment.imageUpdateFailed'));
} finally { } finally {
@@ -201,10 +201,10 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
<div className="flex flex-wrap items-center justify-between gap-3"> <div className="flex flex-wrap items-center justify-between gap-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Cpu className="h-4 w-4 text-[var(--dg-text-muted)]" /> <Cpu className="h-4 w-4 text-[var(--dg-text-muted)]" />
<span className="text-sm font-medium text-[var(--dg-text-primary)]">Master(内置)</span> <span className="text-sm font-medium text-[var(--dg-text-primary)]">{i18n.t('deploy.buildNodes.masterBuiltin')}</span>
<Badge variant="outline" className="text-[10px]"> {i18n.t('pipeline.buildnodesmanag.sa419fe03')} </Badge> <Badge variant="outline" className="text-[10px]"> {i18n.t('pipeline.buildnodesmanag.sa419fe03')} </Badge>
<span className="text-xs text-[var(--dg-text-muted)]"> <span className="text-xs text-[var(--dg-text-muted)]">
占用 {master.running_count}/{master.max_executors} {i18n.t('deploy.buildNodes.executorUsage', { running: master.running_count, max: master.max_executors })}
</span> </span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -218,19 +218,19 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
className="h-7 w-16 text-xs dg-input" className="h-7 w-16 text-xs dg-input"
/> />
<Button size="sm" className="h-7 text-xs" onClick={handleSaveMaster} disabled={savingMaster}> <Button size="sm" className="h-7 text-xs" onClick={handleSaveMaster} disabled={savingMaster}>
{savingMaster ? i18n.t('保存中') : i18n.t('task.script.save')} {savingMaster ? i18n.t('deploy.buildNodes.saving') : i18n.t('task.script.save')}
</Button> </Button>
</div> </div>
</div> </div>
<p className="text-[11px] text-[var(--dg-text-muted)] mt-2"> <p className="text-[11px] text-[var(--dg-text-muted)] mt-2">
未指定节点时优先在 Master 执行;并发设为 1 时按触发顺序串行排队;满载后自动调度到空闲远程节点 {i18n.t('deploy.buildNodes.schedulingHint')}
</p> </p>
</div> </div>
{nodes.length === 0 ? ( {nodes.length === 0 ? (
<PageEmpty <PageEmpty
title={i18n.t('暂无远程构建节点')} title={i18n.t('deploy.buildNodes.noRemoteNodes')}
description={i18n.t('关联服务器资产添加 SSH 节点,将重型构建任务调度到高性能机器')} description={i18n.t('deploy.buildNodes.noRemoteDesc')}
action={<Button size="sm" onClick={openCreate}><Plus className="h-3.5 w-3.5 mr-1" /> {i18n.t('kubernetes.topology.addNode')} </Button>} action={<Button size="sm" onClick={openCreate}><Plus className="h-3.5 w-3.5 mr-1" /> {i18n.t('kubernetes.topology.addNode')} </Button>}
/> />
) : ( ) : (
@@ -251,14 +251,14 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
</span> </span>
</div> </div>
<div className="flex items-center gap-3 mt-1 text-[11px] text-[var(--dg-text-muted)]"> <div className="flex items-center gap-3 mt-1 text-[11px] text-[var(--dg-text-muted)]">
<span>执行器 {node.running_count || 0}/{node.max_executors}</span> <span>{i18n.t('deploy.buildNodes.executorLabel', { running: node.running_count || 0, max: node.max_executors })}</span>
{node.labels && <span>标签 {node.labels}</span>} {node.labels && <span>{i18n.t('deploy.buildNodes.labelsLabel', { labels: node.labels })}</span>}
<span className="truncate">{node.workspace_path}</span> <span className="truncate">{node.workspace_path}</span>
</div> </div>
</div> </div>
<div className="flex items-center gap-1 shrink-0"> <div className="flex items-center gap-1 shrink-0">
<Button variant="ghost" size="sm" className="h-7 text-xs" disabled={testingId === node.id} onClick={() => handleTest(node)}> <Button variant="ghost" size="sm" className="h-7 text-xs" disabled={testingId === node.id} onClick={() => handleTest(node)}>
{testingId === node.id ? i18n.t('测试中') : i18n.t('pipeline.testTool')} {testingId === node.id ? i18n.t('deploy.buildNodes.testing') : i18n.t('pipeline.testTool')}
</Button> </Button>
<Button variant="ghost" size="sm" className="h-7 text-xs" onClick={() => handleToggle(node)}> <Button variant="ghost" size="sm" className="h-7 text-xs" onClick={() => handleToggle(node)}>
{node.enabled ? i18n.t('system.userInactive') : i18n.t('system.userActive')} {node.enabled ? i18n.t('system.userInactive') : i18n.t('system.userActive')}
@@ -295,7 +295,7 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
<div className="space-y-1"> <div className="space-y-1">
<Label className="text-xs"> {i18n.t('>标签 <span className=')} </Label> <Label className="text-xs"> {i18n.t('>标签 <span className=')} </Label>
<Select value={serverId || '__none'} onValueChange={(v) => setServerId(v === '__none' ? '' : v)}> <Select value={serverId || '__none'} onValueChange={(v) => setServerId(v === '__none' ? '' : v)}>
<SelectTrigger className="h-8 text-xs dg-input"><SelectValue placeholder={i18n.t('选择服务器')} /></SelectTrigger> <SelectTrigger className="h-8 text-xs dg-input"><SelectValue placeholder={i18n.t('deploy.buildNodes.selectServer')} /></SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="__none"> {i18n.t('k8s.storage.pleaseSelect')} </SelectItem> <SelectItem value="__none"> {i18n.t('k8s.storage.pleaseSelect')} </SelectItem>
{servers.map((s) => ( {servers.map((s) => (
@@ -326,7 +326,7 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
</div> </div>
<div className="flex justify-end gap-2 pt-2"> <div className="flex justify-end gap-2 pt-2">
<Button variant="outline" onClick={() => setDialogOpen(false)}> {i18n.t('rbac.jobRole.cancel')} </Button> <Button variant="outline" onClick={() => setDialogOpen(false)}> {i18n.t('rbac.jobRole.cancel')} </Button>
<Button onClick={handleSubmit} disabled={submitting}>{submitting ? i18n.t('保存中') : i18n.t('task.script.save')}</Button> <Button onClick={handleSubmit} disabled={submitting}>{submitting ? i18n.t('deploy.buildNodes.saving') : i18n.t('task.script.save')}</Button>
</div> </div>
</div> </div>
</DialogContent> </DialogContent>
@@ -343,7 +343,7 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
} }
return ( return (
<PageShell title={i18n.t('pipeline.buildNodes')} subtitle={i18n.t('构建节点')}> <PageShell title={i18n.t('pipeline.buildNodes')} subtitle={i18n.t('deploy.pipeline.buildNodes')}>
{panel} {panel}
{dialog} {dialog}
</PageShell> </PageShell>
@@ -112,7 +112,7 @@ export default function DeployRBAC() {
const handleCreate = async () => { const handleCreate = async () => {
if (!newName.trim() || !newCode.trim() || !newPattern.trim()) { if (!newName.trim() || !newCode.trim() || !newPattern.trim()) {
toast.error(i18n.t('请填写名称、标识与正则')); return; toast.error(i18n.t('deploy.rbac.policy.fillFields')); return;
} }
setSaving(true); setSaving(true);
try { try {
@@ -147,7 +147,7 @@ export default function DeployRBAC() {
const handleDelete = async (role: ItemRole) => { const handleDelete = async (role: ItemRole) => {
const ok = await _confirm({ const ok = await _confirm({
title: i18n.t('删除权限'), title: i18n.t('deploy.rbac.policy.deletePolicy'),
description: `确定删除「${role.name}」?将影响 ${matchCount[role.id]?.length || 0} 个发布任务`, description: `确定删除「${role.name}」?将影响 ${matchCount[role.id]?.length || 0} 个发布任务`,
variant: 'destructive', variant: 'destructive',
}); });
@@ -165,7 +165,7 @@ export default function DeployRBAC() {
}; };
const handleAssign = async () => { const handleAssign = async () => {
if (!selectedUserId || !assignRoleId) { toast.error(i18n.t('请选择用户和权限')); return; } if (!selectedUserId || !assignRoleId) { toast.error(i18n.t('deploy.rbac.policy.selectUserPermission')); return; }
setAssignLoading(true); setAssignLoading(true);
try { try {
await deployApi.assignItemRole(Number(selectedUserId), { await deployApi.assignItemRole(Number(selectedUserId), {
@@ -190,8 +190,8 @@ export default function DeployRBAC() {
const selectedUser = users.find((u) => String(u.id) === selectedUserId); const selectedUser = users.find((u) => String(u.id) === selectedUserId);
const pageTabs = [ const pageTabs = [
{ id: 'roles', label: i18n.t('权限策略'), badge: roles.length || undefined }, { id: 'roles', label: i18n.t('deploy.rbac.policy.policyStrategy'), badge: roles.length || undefined },
{ id: 'users', label: i18n.t('用户授权') }, { id: 'users', label: i18n.t('deploy.rbac.policy.userAuth') },
{ id: 'groups', label: i18n.t('pipeline.groupLabel') }, { id: 'groups', label: i18n.t('pipeline.groupLabel') },
{ id: 'preview', label: i18n.t('pipeline.matchPreview') }, { id: 'preview', label: i18n.t('pipeline.matchPreview') },
]; ];
@@ -200,7 +200,7 @@ export default function DeployRBAC() {
<PageShell <PageShell
variant="list" variant="list"
title={i18n.t('deploy.deployNav.jobPermission')} title={i18n.t('deploy.deployNav.jobPermission')}
subtitle={i18n.t('按流水线 code / Job 名称正则分配发布权限')} subtitle={i18n.t('deploy.rbac.policy.subtitle')}
icon={<Shield className="h-5 w-5" />} icon={<Shield className="h-5 w-5" />}
loading={loading} loading={loading}
tabs={pageTabs} tabs={pageTabs}
@@ -217,8 +217,8 @@ export default function DeployRBAC() {
roles.length === 0 ? ( roles.length === 0 ? (
<PageEmpty <PageEmpty
icon={<Shield className="h-10 w-10" />} icon={<Shield className="h-10 w-10" />}
title={i18n.t('暂无 Job 权限策略')} title={i18n.t('deploy.rbac.policy.noPolicy')}
description={i18n.t('创建策略后,用户将按正则自动获得匹配流水线的访问权限')} description={i18n.t('deploy.rbac.policy.createHint')}
action={<Button onClick={() => setCreateOpen(true)}><Plus className="h-4 w-4 mr-1" /> {i18n.t('monitor.logrbactsx.s6058845c')} </Button>} action={<Button onClick={() => setCreateOpen(true)}><Plus className="h-4 w-4 mr-1" /> {i18n.t('monitor.logrbactsx.s6058845c')} </Button>}
/> />
) : ( ) : (
@@ -296,7 +296,7 @@ export default function DeployRBAC() {
</Select> </Select>
</div> </div>
<div className="space-y-1 w-48"> <div className="space-y-1 w-48">
<Label className="text-xs"> {i18n.t('权限策略')} </Label> <Label className="text-xs"> {i18n.t('deploy.rbac.policy.policyStrategy')} </Label>
<Select value={assignRoleId} onValueChange={setAssignRoleId}> <Select value={assignRoleId} onValueChange={setAssignRoleId}>
<SelectTrigger className="h-8 text-sm dg-input"><SelectValue placeholder={i18n.t('kubernetes.hpa.selectPolicy')} /></SelectTrigger> <SelectTrigger className="h-8 text-sm dg-input"><SelectValue placeholder={i18n.t('kubernetes.hpa.selectPolicy')} /></SelectTrigger>
<SelectContent> <SelectContent>
@@ -99,8 +99,8 @@ export default function UserGroupsManager({ embedded = false }: { embedded?: boo
const listBody = !loading && groups.length === 0 ? ( const listBody = !loading && groups.length === 0 ? (
<PageEmpty <PageEmpty
icon={<Users className="h-10 w-10" />} icon={<Users className="h-10 w-10" />}
title={i18n.t('暂无用户组')} title={i18n.t('deploy.userGroup.noGroups')}
description={i18n.t('点击「新建用户组」创建第一个用户组')} description={i18n.t('deploy.userGroup.createFirstHint')}
action={( action={(
<Button size="sm" onClick={() => { setEditId(null); setFormName(''); setFormDesc(''); setFormOpen(true); }}> <Button size="sm" onClick={() => { setEditId(null); setFormName(''); setFormDesc(''); setFormOpen(true); }}>
<Plus className="w-4 h-4 mr-1" />新建用户组 <Plus className="w-4 h-4 mr-1" />新建用户组
@@ -165,7 +165,7 @@ export default function UserGroupsManager({ embedded = false }: { embedded?: boo
<DialogContent> <DialogContent>
<DialogHeader><DialogTitle>{editId ? i18n.t('deploy.rbac.groupEditTitle') : i18n.t('pipeline.newGroup')}</DialogTitle></DialogHeader> <DialogHeader><DialogTitle>{editId ? i18n.t('deploy.rbac.groupEditTitle') : i18n.t('pipeline.newGroup')}</DialogTitle></DialogHeader>
<div className="space-y-3"> <div className="space-y-3">
<div className="space-y-1"><Label> {i18n.t('monitor.groupName')} </Label><Input value={formName} onChange={e => setFormName(e.target.value)} placeholder={i18n.t('例如: 后端开发组')} /></div> <div className="space-y-1"><Label> {i18n.t('monitor.groupName')} </Label><Input value={formName} onChange={e => setFormName(e.target.value)} placeholder={i18n.t('deploy.userGroup.namePlaceholder')} /></div>
<div className="space-y-1"><Label> {i18n.t('aiops.llmModelDesc')} </Label><Input value={formDesc} onChange={e => setFormDesc(e.target.value)} placeholder={i18n.t('rbac.jobRole.placeholder.description')} /></div> <div className="space-y-1"><Label> {i18n.t('aiops.llmModelDesc')} </Label><Input value={formDesc} onChange={e => setFormDesc(e.target.value)} placeholder={i18n.t('rbac.jobRole.placeholder.description')} /></div>
</div> </div>
<div className="flex justify-end gap-2 mt-4 pt-4 border-t"> <div className="flex justify-end gap-2 mt-4 pt-4 border-t">
@@ -190,14 +190,14 @@ export default function UserGroupsManager({ embedded = false }: { embedded?: boo
<PageShell <PageShell
variant="list" variant="list"
title={i18n.t('pipeline.releaseTitle')} title={i18n.t('pipeline.releaseTitle')}
subtitle={i18n.t('用户组 — 批量授权流水线访问权限')} subtitle={i18n.t('deploy.userGroup.subtitle')}
icon={<Users className="h-5 w-5" />} icon={<Users className="h-5 w-5" />}
loading={loading} loading={loading}
metrics={( metrics={(
<StatStrip <StatStrip
items={[ items={[
{ label: i18n.t('pipeline.groupLabel'), value: groups.length, color: 'blue' }, { label: i18n.t('pipeline.groupLabel'), value: groups.length, color: 'blue' },
{ label: i18n.t('成员总数'), value: memberCount, color: 'purple' }, { label: i18n.t('deploy.userGroup.memberCount'), value: memberCount, color: 'purple' },
]} ]}
/> />
)} )}
@@ -149,8 +149,8 @@ export function DeployTargetFields({ value, onChange, requireTarget = true }: De
<div className="space-y-3"> <div className="space-y-3">
<div className="flex gap-1 p-1 rounded-lg bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)]"> <div className="flex gap-1 p-1 rounded-lg bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)]">
{([ {([
{ key: 'ssh' as const, label: i18n.t('主机发布'), icon: Server }, { key: 'ssh' as const, label: i18n.t('deploy.target.hostDeploy'), icon: Server },
{ key: 'k8s' as const, label: i18n.t('主机发布'), icon: Network }, { key: 'k8s' as const, label: i18n.t('deploy.target.hostDeploy'), icon: Network },
]).map((item) => { ]).map((item) => {
const active = value.deployTarget === item.key; const active = value.deployTarget === item.key;
const Icon = item.icon; const Icon = item.icon;
@@ -200,7 +200,7 @@ export function DeployTargetFields({ value, onChange, requireTarget = true }: De
<Input <Input
value={serverSearch} value={serverSearch}
onChange={(e) => setServerSearch(e.target.value)} onChange={(e) => setServerSearch(e.target.value)}
placeholder={i18n.t('搜索主机名或 IP')} placeholder={i18n.t('deploy.target.searchHost')}
className="pl-8 h-8 text-xs dg-input" className="pl-8 h-8 text-xs dg-input"
/> />
</div> </div>
@@ -296,7 +296,7 @@ export function DeployTargetFields({ value, onChange, requireTarget = true }: De
<Input <Input
readOnly readOnly
value={value.k8sDeployment ? `${value.k8sNamespace}/${value.k8sDeployment}` : ''} value={value.k8sDeployment ? `${value.k8sNamespace}/${value.k8sDeployment}` : ''}
placeholder={i18n.t('从下方列表选择')} placeholder={i18n.t('deploy.target.selectFromList')}
className="h-8 text-xs dg-input font-mono" className="h-8 text-xs dg-input font-mono"
/> />
</div> </div>
@@ -308,7 +308,7 @@ export function DeployTargetFields({ value, onChange, requireTarget = true }: De
<Input <Input
value={depSearch} value={depSearch}
onChange={(e) => setDepSearch(e.target.value)} onChange={(e) => setDepSearch(e.target.value)}
placeholder={i18n.t('搜索 Deployment 或命名空间')} placeholder={i18n.t('deploy.target.searchDeployment')}
className="pl-8 h-8 text-xs dg-input" className="pl-8 h-8 text-xs dg-input"
/> />
</div> </div>
@@ -353,7 +353,7 @@ export function DeployTargetFields({ value, onChange, requireTarget = true }: De
<Input <Input
value={value.k8sContainerName} value={value.k8sContainerName}
onChange={(e) => onChange({ k8sContainerName: e.target.value })} onChange={(e) => onChange({ k8sContainerName: e.target.value })}
placeholder={i18n.t('搜索 Deployment 或命名空间')} placeholder={i18n.t('deploy.target.searchDeployment')}
className="h-8 text-xs font-mono dg-input" className="h-8 text-xs font-mono dg-input"
/> />
</div> </div>
@@ -10,12 +10,12 @@ import {
// 流水线阶段定义(固定顺序) // Logan.Li // 流水线阶段定义(固定顺序) // Logan.Li
export const STAGE_DEFS = [ export const STAGE_DEFS = [
{ key: 'checkout', label: i18n.t('pipeline.checkout'), icon: GitBranch, color: '#6366F1', order: 1, needs: ['repo'], desc: i18n.t('代码检出') }, { key: 'checkout', label: i18n.t('pipeline.checkout'), icon: GitBranch, color: '#6366F1', order: 1, needs: ['repo'], desc: i18n.t('deploy.stage.checkout') },
{ key: 'build', label: i18n.t('pipeline.build'), icon: Box, color: '#F59E0B', order: 2, needs: [], desc: i18n.t('构建') }, { key: 'build', label: i18n.t('pipeline.build'), icon: Box, color: '#F59E0B', order: 2, needs: [], desc: i18n.t('deploy.stage.build') },
{ key: 'test', label: i18n.t('pipeline.testTool'), icon: CheckCircle, color: '#10B981', order: 3, needs: [], desc: i18n.t('测试') }, { key: 'test', label: i18n.t('pipeline.testTool'), icon: CheckCircle, color: '#10B981', order: 3, needs: [], desc: i18n.t('deploy.stage.test') },
{ key: 'image', label: i18n.t('pipeline.imageBuild'), icon: Container, color: '#06B6D4', order: 4, needs: ['registry'], desc: i18n.t('镜像打包') }, { key: 'image', label: i18n.t('pipeline.imageBuild'), icon: Container, color: '#06B6D4', order: 4, needs: ['registry'], desc: i18n.t('deploy.stage.imageBuild') },
{ key: 'deploy', label: i18n.t('pipeline.deploy2'), icon: Rocket, color: '#EF4444', order: 5, needs: [], desc: i18n.t('部署') }, { key: 'deploy', label: i18n.t('pipeline.deploy2'), icon: Rocket, color: '#EF4444', order: 5, needs: [], desc: i18n.t('deploy.stage.deploy') },
{ key: 'verify', label: i18n.t('system.credentialVerify'), icon: ShieldCheck, color: '#3B82F6', order: 6, needs: [], desc: i18n.t('验证') }, { key: 'verify', label: i18n.t('system.credentialVerify'), icon: ShieldCheck, color: '#3B82F6', order: 6, needs: [], desc: i18n.t('deploy.stage.verify') },
].sort((a, b) => a.order - b.order); ].sort((a, b) => a.order - b.order);
// 运行日志条目 // Logan.Li // 运行日志条目 // Logan.Li
@@ -207,7 +207,7 @@ export function AlertChannelDialog({
{/* 渠道表单 */} {/* 渠道表单 */}
<div className="space-y-4"> <div className="space-y-4">
<Label className="text-[var(--dg-text-secondary)] text-sm"> <Label className="text-[var(--dg-text-secondary)] text-sm">
{editingChannel ? i18n.t('编辑渠道') : i18n.t('新建渠道')} {editingChannel ? i18n.t('monitor.alertChannelDialog2.editChannel') : i18n.t('monitor.alertChannelDialog2.newChannel')}
</Label> </Label>
<div className="space-y-1.5"> <div className="space-y-1.5">
<Label className="dg-text-secondary text-xs"> {i18n.t('>渠道名称 <span className=')} <span className="dg-text-red">*</span></Label> <Label className="dg-text-secondary text-xs"> {i18n.t('>渠道名称 <span className=')} <span className="dg-text-red">*</span></Label>
@@ -76,15 +76,15 @@ export function AlertManager() {
]; ];
const pageTabs = [ const pageTabs = [
{ id: 'events', label: i18n.t('告警事件') }, { id: 'events', label: i18n.t('monitor.alertMgr2.alertEvents') },
{ id: 'rules', label: i18n.t('monitor.alertRules') }, { id: 'rules', label: i18n.t('monitor.alertRules') },
]; ];
return ( return (
<PageShell <PageShell
variant="list" variant="list"
title={i18n.t('监控告警')} title={i18n.t('monitor.alertMgr2.monitorAlert')}
subtitle={i18n.t('告警规则管理与告警事件处理')} subtitle={i18n.t('monitor.alertMgr2.subtitle')}
icon={<Bell className="h-5 w-5" />} icon={<Bell className="h-5 w-5" />}
tabs={pageTabs} tabs={pageTabs}
activeTab={activeTab} activeTab={activeTab}
@@ -94,7 +94,7 @@ export function AlertManager() {
items={[ items={[
{ label: i18n.t('monitor.alertRules'), value: rules.length, color: 'blue' }, { label: i18n.t('monitor.alertRules'), value: rules.length, color: 'blue' },
{ label: i18n.t('monitor.alertHelper.statusFiring'), value: alerts.filter((e) => e.status === 'firing').length, color: 'red' }, { label: i18n.t('monitor.alertHelper.statusFiring'), value: alerts.filter((e) => e.status === 'firing').length, color: 'red' },
{ label: i18n.t('今日告警'), value: alerts.length, color: 'amber' }, { label: i18n.t('monitor.alertMgr2.todayAlerts'), value: alerts.length, color: 'amber' },
{ label: i18n.t('kubernetes.msg.uncordonSuccess'), value: alerts.filter((e) => e.status === 'resolved').length, color: 'emerald' }, { label: i18n.t('kubernetes.msg.uncordonSuccess'), value: alerts.filter((e) => e.status === 'resolved').length, color: 'emerald' },
]} ]}
/> />
@@ -128,10 +128,10 @@ export function AlertRuleDialog({
<DialogContent className="dg-input max-w-lg"> <DialogContent className="dg-input max-w-lg">
<DialogHeader> <DialogHeader>
<DialogTitle className="text-[var(--dg-text-primary)]"> <DialogTitle className="text-[var(--dg-text-primary)]">
{editingRule ? i18n.t('编辑告警规则') : i18n.t('新建告警规则')} {editingRule ? i18n.t('monitor.alertRuleDialog2.editRule') : i18n.t('monitor.alertRuleDialog2.newRule')}
</DialogTitle> </DialogTitle>
<DialogDescription className="dg-text-muted"> <DialogDescription className="dg-text-muted">
{editingRule ? i18n.t('编辑告警规则') : i18n.t('新建告警规则')} {editingRule ? i18n.t('monitor.alertRuleDialog2.editRule') : i18n.t('monitor.alertRuleDialog2.newRule')}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
@@ -141,7 +141,7 @@ export function AlertRuleDialog({
<Input <Input
value={ruleForm.name} value={ruleForm.name}
onChange={(e) => setRuleForm({ ...ruleForm, name: e.target.value })} onChange={(e) => setRuleForm({ ...ruleForm, name: e.target.value })}
placeholder={i18n.t('如:CPU 使用率告警')} placeholder={i18n.t('monitor.alertRuleDialog2.namePlaceholder')}
className="dg-input" className="dg-input"
/> />
</div> </div>
@@ -151,7 +151,7 @@ export function AlertRuleDialog({
<Textarea <Textarea
value={ruleForm.description} value={ruleForm.description}
onChange={(e) => setRuleForm({ ...ruleForm, description: e.target.value })} onChange={(e) => setRuleForm({ ...ruleForm, description: e.target.value })}
placeholder={i18n.t('如:CPU 使用率告警')} placeholder={i18n.t('monitor.alertRuleDialog2.namePlaceholder')}
rows={2} rows={2}
className="dg-input resize-none" className="dg-input resize-none"
/> />
@@ -303,7 +303,7 @@ export function AlertRuleDialog({
onClick={handleSaveRule} onClick={handleSaveRule}
> >
{ruleSaving && <Loader2 className="h-4 w-4 mr-2 animate-spin" />} {ruleSaving && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
{editingRule ? i18n.t('cloud.saveChanges') : i18n.t('编辑告警规则')} {editingRule ? i18n.t('cloud.saveChanges') : i18n.t('monitor.alertRuleDialog2.editRule')}
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
@@ -54,15 +54,15 @@ export function AlertRulesTab({
const handleDeleteRule = async ( id: number) => { const handleDeleteRule = async ( id: number) => {
const ok = await confirm({ const ok = await confirm({
title: i18n.t('删除告警规则'), title: i18n.t('monitor.alertRulesTab.deleteRule'),
description: i18n.t('确定删除此告警规则?'), description: i18n.t('monitor.alertRulesTab.deleteConfirm'),
confirmText: i18n.t('kubernetes.storage.delete2'), confirmText: i18n.t('kubernetes.storage.delete2'),
variant: 'destructive', variant: 'destructive',
}); });
if (!ok) return; if (!ok) return;
try { try {
await alertApi.deleteRule(id); await alertApi.deleteRule(id);
toast.success(i18n.t('告警规则已删除')); toast.success(i18n.t('monitor.alertRulesTab.deleted'));
onReload(); onReload();
} catch (error) { } catch (error) {
console.error('Failed to delete rule:', error); console.error('Failed to delete rule:', error);
@@ -74,7 +74,7 @@ export function AlertRulesTab({
const handleBatchDelete = async () => { const handleBatchDelete = async () => {
if (selectedIds.length === 0) return; if (selectedIds.length === 0) return;
const ok = await confirm({ const ok = await confirm({
title: i18n.t('删除告警规则'), title: i18n.t('monitor.alertRulesTab.deleteRule'),
description: `确定删除选中的 ${selectedIds.length} 条告警规则吗?此操作不可撤销。`, description: `确定删除选中的 ${selectedIds.length} 条告警规则吗?此操作不可撤销。`,
confirmText: i18n.t('system.terminalBatchDelete'), confirmText: i18n.t('system.terminalBatchDelete'),
variant: 'destructive', variant: 'destructive',
@@ -34,7 +34,7 @@ export default function PrometheusDatasourceManager() {
const [editingDs, setEditingDs] = useState<PromDS | null>(null); const [editingDs, setEditingDs] = useState<PromDS | null>(null);
useEffect(() => { useEffect(() => {
if (isError) toast.error(i18n.t('获取数据源列表失败')); if (isError) toast.error(i18n.t('monitor.promDs2.listFailed'));
}, [isError]); }, [isError]);
const openCreate = () => { const openCreate = () => {
@@ -67,7 +67,7 @@ export default function PrometheusDatasourceManager() {
await setDefaultMutation.mutateAsync(ds.id); await setDefaultMutation.mutateAsync(ds.id);
toast.success(`已将「${ds.name}」设为默认数据源`); toast.success(`已将「${ds.name}」设为默认数据源`);
} catch { } catch {
toast.error(i18n.t('删除失败')); toast.error(i18n.t('monitor.promDs2.deleteFailed'));
} }
}; };
@@ -95,8 +95,8 @@ export default function PrometheusDatasourceManager() {
{sources.length === 0 ? ( {sources.length === 0 ? (
<PageEmpty <PageEmpty
icon={<Database className="h-12 w-12" />} icon={<Database className="h-12 w-12" />}
title={i18n.t('暂无 Prometheus 数据源')} title={i18n.t('monitor.promDs2.noData')}
description={i18n.t('添加数据源后可用于指标监控与监控大屏')} description={i18n.t('monitor.promDs2.addHint')}
action={( action={(
<Button onClick={openCreate} size="sm" className="dg-btn-primary"> <Button onClick={openCreate} size="sm" className="dg-btn-primary">
<Plus className="w-4 h-4 mr-1" />添加数据源 <Plus className="w-4 h-4 mr-1" />添加数据源
@@ -147,10 +147,10 @@ export function LogDatasourcePage() {
rowKey={(ds) => ds.id} rowKey={(ds) => ds.id}
loading={loading} loading={loading}
searchable searchable
searchPlaceholder={i18n.t('搜索数据源名称或地址...')} searchPlaceholder={i18n.t('monitor.logDsPage.searchPlaceholder')}
searchKeys={['name', 'url']} searchKeys={['name', 'url']}
sortable sortable
emptyText={i18n.t('暂无数据源,点击上方按钮添加')} emptyText={i18n.t('monitor.logDsPage.noData')}
toolbarExtra={ toolbarExtra={
<> <>
<Button variant="outline" size="sm" className="h-8 border-[var(--dg-border)] text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)]" onClick={load}> <Button variant="outline" size="sm" className="h-8 border-[var(--dg-border)] text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)]" onClick={load}>
@@ -24,8 +24,8 @@ interface Props {
} }
const STATUS_LABEL: Record<string, { text: string; color: string }> = { const STATUS_LABEL: Record<string, { text: string; color: string }> = {
precise: { text: i18n.t('结果精确'), color: '#22c55e' }, precise: { text: i18n.t('monitor.logQueryToolbar.exactMatch'), color: '#22c55e' },
partial: { text: i18n.t('部分命中'), color: '#f59e0b' }, partial: { text: i18n.t('monitor.logQueryToolbar.partialMatch'), color: '#f59e0b' },
failed: { text: i18n.t('monitor.logPanel.queryFailed'), color: '#ef4444' }, failed: { text: i18n.t('monitor.logPanel.queryFailed'), color: '#ef4444' },
}; };
@@ -78,7 +78,7 @@ export function ServerFileBrowser({ open, onOpenChange, serverId, serverName }:
setBreadcrumbs(data.breadcrumbs || []); setBreadcrumbs(data.breadcrumbs || []);
setCurrentPath(data.path || path); setCurrentPath(data.path || path);
} catch (err) { } catch (err) {
setError(err?.message || i18n.t('加载目录失败')); setError(err?.message || i18n.t('server.fileBrowser2.loadDirFailed'));
setItems([]); setItems([]);
} finally { } finally {
setLoading(false); setLoading(false);
@@ -97,7 +97,7 @@ export function ServerFileBrowser({ open, onOpenChange, serverId, serverName }:
try { try {
await serverApi.downloadFile(serverId, filePath); await serverApi.downloadFile(serverId, filePath);
} catch (err) { } catch (err) {
setError(i18n.t('下载失败: ') + ((err as JsonData)?.message || '')); setError(i18n.t('server.fileBrowser2.downloadFailed') + ((err as JsonData)?.message || ''));
} }
}; };
@@ -117,7 +117,7 @@ export function ServerFileBrowser({ open, onOpenChange, serverId, serverName }:
}; };
const handleDelete = async (filePath: string, isDir: boolean) => { const handleDelete = async (filePath: string, isDir: boolean) => {
if (!serverId || !confirm(`确认删除 ${isDir ? i18n.t('目录') : i18n.t('k8s.pods.action.file')} "${filePath}"?`)) return; if (!serverId || !confirm(`确认删除 ${isDir ? i18n.t('server.fileBrowser2.directory') : i18n.t('k8s.pods.action.file')} "${filePath}"?`)) return;
try { try {
await serverApi.deleteFile(serverId, filePath); await serverApi.deleteFile(serverId, filePath);
await fetchDir(currentPath); await fetchDir(currentPath);
@@ -148,7 +148,7 @@ export function useLdapManagement(onUsersChanged: () => Promise<void> | void) {
if (msg.includes('Already Exists') || msg.includes('68')) { if (msg.includes('Already Exists') || msg.includes('68')) {
toast.error(i18n.t('system.useldapmanageme.ouExists', { name: ouForm.ou_name })); toast.error(i18n.t('system.useldapmanageme.ouExists', { name: ouForm.ou_name }));
} else { } else {
toast.error(i18n.t('创建失败: ') + msg); toast.error(i18n.t('system.ldapMgmt.createFailed') + msg);
} }
} finally { setLdapSaving(false); } } finally { setLdapSaving(false); }
}; };
@@ -168,7 +168,7 @@ export function useLdapManagement(onUsersChanged: () => Promise<void> | void) {
/** 删除 LDAP 条目 // Logan.Li */ /** 删除 LDAP 条目 // Logan.Li */
const handleDeleteLDAPEntry = async (dn: string, silent = false) => { const handleDeleteLDAPEntry = async (dn: string, silent = false) => {
if (!silent) { if (!silent) {
const ok = await confirm({ title: i18n.t('删除 LDAP 条目'), description: `确定删除 ${dn}?`, confirmText: i18n.t('kubernetes.storage.delete2'), variant: 'destructive' }); const ok = await confirm({ title: i18n.t('system.ldapMgmt.deleteEntry'), description: `确定删除 ${dn}?`, confirmText: i18n.t('kubernetes.storage.delete2'), variant: 'destructive' });
if (!ok) return; if (!ok) return;
} }
try { try {
@@ -190,7 +190,7 @@ export function useLdapManagement(onUsersChanged: () => Promise<void> | void) {
await ldapApi.updateUser({ dn: editLdapUser.dn, ...editLdapForm }); await ldapApi.updateUser({ dn: editLdapUser.dn, ...editLdapForm });
setEditLdapUser(null); setEditLdapUser(null);
await loadLdapOUUsers(selectedOUDN); await loadLdapOUUsers(selectedOUDN);
} catch (e) { toast.error(e?.message || i18n.t('修改失败')); } } catch (e) { toast.error(e?.message || i18n.t('system.ldapMgmt.updateFailed')); }
}; };
/** LDAP 账号验证测试 // Logan.Li */ /** LDAP 账号验证测试 // Logan.Li */
@@ -201,7 +201,7 @@ export function useLdapManagement(onUsersChanged: () => Promise<void> | void) {
const res = await ldapApi.testConnection({ test_user: ldapTestUser, test_password: ldapTestPass }); const res = await ldapApi.testConnection({ test_user: ldapTestUser, test_password: ldapTestPass });
setLdapUserTestResult(res); setLdapUserTestResult(res);
} catch (e) { } catch (e) {
setLdapUserTestResult({ success: false, message: e?.message || i18n.t('验证失败') }); setLdapUserTestResult({ success: false, message: e?.message || i18n.t('system.ldapMgmt.verifyFailed') });
} finally { setLdapUserTesting(false); } } finally { setLdapUserTesting(false); }
}; };
@@ -234,11 +234,11 @@ export function useLdapManagement(onUsersChanged: () => Promise<void> | void) {
} else if (opts?.importAll) { } else if (opts?.importAll) {
selected = ldapUsers; selected = ldapUsers;
} else { } else {
toast.error(i18n.t('请至少选择一个用户')); toast.error(i18n.t('system.ldapMgmt.selectUsers'));
return; return;
} }
if (selected.length === 0) { if (selected.length === 0) {
toast.error(i18n.t('请至少选择一个用户')); toast.error(i18n.t('system.ldapMgmt.selectUsers'));
return; return;
} }
setImporting(true); setImporting(true);
@@ -265,7 +265,7 @@ export function useLdapManagement(onUsersChanged: () => Promise<void> | void) {
await onUsersChanged(); await onUsersChanged();
} catch (error: unknown) { } catch (error: unknown) {
const errMsg = error instanceof Error ? error.message : i18n.t('monitor.dashboard.agent.toast.unknownError'); const errMsg = error instanceof Error ? error.message : i18n.t('monitor.dashboard.agent.toast.unknownError');
const result = { code: 500, message: i18n.t('导入失败: ') + errMsg }; const result = { code: 500, message: i18n.t('system.ldapMgmt.importFailed') + errMsg };
if (opts?.setResult) { if (opts?.setResult) {
opts.setResult(result); opts.setResult(result);
} else { } else {
+162 -7
View File
@@ -1134,6 +1134,11 @@
"clipboardDisabled": "Clipboard is disabled by session policy", "clipboardDisabled": "Clipboard is disabled by session policy",
"fileUploadDisabled": "File upload is disabled by session policy", "fileUploadDisabled": "File upload is disabled by session policy",
"fileDownloadDisabled": "File download is disabled by session policy" "fileDownloadDisabled": "File download is disabled by session policy"
},
"fileBrowser2": {
"loadDirFailed": "Failed to load directory",
"downloadFailed": "Download failed: ",
"directory": "Directory"
} }
}, },
"alert": { "alert": {
@@ -1581,7 +1586,9 @@
"checkoutNeedGit": "Checkout stage requires Git URL", "checkoutNeedGit": "Checkout stage requires Git URL",
"imageNeedDocker": "Image build stage requires Docker credential", "imageNeedDocker": "Image build stage requires Docker credential",
"suggestDocker": "Recommended credential", "suggestDocker": "Recommended credential",
"needDocker": "Docker credential required" "needDocker": "Docker credential required",
"imageBuild": "Image Build",
"verify": "Verify"
}, },
"env": { "env": {
"all": "All", "all": "All",
@@ -1852,7 +1859,17 @@
"deleteGroup": "Delete User Group", "deleteGroup": "Delete User Group",
"deleteRole": "Delete Role", "deleteRole": "Delete Role",
"userCol": "User", "userCol": "User",
"noUserRole": "This user has not been assigned any Item Role" "noUserRole": "This user has not been assigned any Item Role",
"policy": {
"fillFields": "Please fill in name, code, and regex",
"deletePolicy": "Delete Policy",
"selectUserPermission": "Please select user and permission",
"policyStrategy": "Permission Policy",
"userAuth": "User Authorization",
"subtitle": "Assign release permissions by pipeline code / job name regex",
"noPolicy": "No job permission policies",
"createHint": "After creating a policy, users will automatically get access to matching pipelines via regex"
}
}, },
"banner": { "banner": {
"build": "Build", "build": "Build",
@@ -2331,7 +2348,37 @@
"filterSuccess": "Success", "filterSuccess": "Success",
"filterFailed": "Failed", "filterFailed": "Failed",
"filterDeploying": "Deploying", "filterDeploying": "Deploying",
"filterRolledBack": "Rolled Back" "filterRolledBack": "Rolled Back",
"buildNodes": {
"loadFailed": "Failed to load build nodes",
"deleteConfirm": "Delete build node \"{{name}}\"?",
"deleted": "Deleted",
"saving": "Saving...",
"testing": "Testing...",
"noRemoteNodes": "No remote build nodes",
"noRemoteDesc": "Associate server assets to add SSH nodes and schedule heavy build tasks to high-performance machines",
"selectServer": "Select server",
"masterBuiltin": "Master (Built-in)",
"executorUsage": "Busy {{running}}/{{max}}",
"remoteHint": "Register build agent based on server SSH credentials, pipelines can bind or auto-schedule",
"schedulingHint": "When no node is specified, tasks run on Master first; concurrency=1 queues tasks by trigger order; full load auto-schedules to idle remote nodes",
"testSuccess": "Connection successful",
"executorLabel": "Executors {{running}}/{{max}}",
"labelsLabel": "Labels {{labels}}"
},
"target": {
"hostDeploy": "SSH Host Deploy",
"searchHost": "Search hostname or IP",
"selectFromList": "Select from the list below",
"searchDeployment": "Search Deployment or namespace"
},
"userGroup": {
"noGroups": "No user groups",
"createFirstHint": "Click \"New User Group\" to create your first group",
"namePlaceholder": "e.g., Backend Team",
"subtitle": "User groups - batch authorize pipeline access",
"memberCount": "Total Members"
}
}, },
"k8s": { "k8s": {
"cluster": { "cluster": {
@@ -4194,7 +4241,10 @@
"disabled": "Disabled", "disabled": "Disabled",
"emptyDesc": "Click \"Add Rule\" or \"Default Rules\" to create", "emptyDesc": "Click \"Add Rule\" or \"Default Rules\" to create",
"emptyTitle": "No {{cat}} rules", "emptyTitle": "No {{cat}} rules",
"loading": "Loading rules..." "loading": "Loading rules...",
"deleteRule": "Delete Alert Rule",
"deleteConfirm": "Delete this alert rule?",
"deleted": "Alert rule deleted"
}, },
"contactDialog": { "contactDialog": {
"addContactBtn": "Add Contact", "addContactBtn": "Add Contact",
@@ -4816,6 +4866,41 @@
"probeName": "Probe Name", "probeName": "Probe Name",
"execCommand": "Execute Command", "execCommand": "Execute Command",
"refreshing": " · Refreshing..." "refreshing": " · Refreshing..."
},
"alertRuleDialog2": {
"editRule": "Edit Alert Rule",
"newRule": "New Alert Rule",
"namePlaceholder": "e.g., CPU Usage Alert"
},
"alertMgr2": {
"alertEvents": "Alert Events",
"monitorAlert": "Monitor Alerts",
"subtitle": "Alert rule management and alert event processing",
"todayAlerts": "Today's Alerts"
},
"promDs2": {
"listFailed": "Failed to get datasource list",
"deleteFailed": "Delete failed",
"noData": "No Prometheus datasources",
"addHint": "Add datasources for metrics monitoring and dashboards"
},
"alertChannelDialog2": {
"editChannel": "Edit Channel",
"newChannel": "New Channel"
},
"logDsPage": {
"searchPlaceholder": "Search datasource name or URL...",
"noData": "No datasources, click the button above to add"
},
"logQueryToolbar": {
"exactMatch": "Exact match",
"partialMatch": "Partial match"
},
"healthProbe": {
"namePlaceholder": "e.g., cex-agent health check"
},
"hostMonitorTypes": {
"systemInfo": "System Info"
} }
}, },
"task": { "task": {
@@ -6480,7 +6565,32 @@
"billingruledial.s8e1e26d1": "Greater Than or Equal &ge;", "billingruledial.s8e1e26d1": "Greater Than or Equal &ge;",
"billingruledial.s0196c18c": "Less Than &lt;", "billingruledial.s0196c18c": "Less Than &lt;",
"billingruledial.s45c301a8": "Less Than or Equal &le;", "billingruledial.s45c301a8": "Less Than or Equal &le;",
"billingruledial.s11f9fb4c": "Threshold (CNY) *" "billingruledial.s11f9fb4c": "Threshold (CNY) *",
"platform": {
"publicCloud": "Public Cloud",
"searchPlaceholder": "Search platform name or code...",
"subtitle": "Manage public, private, and hybrid cloud platforms",
"noData": "No cloud platform data",
"editPlatform": "Edit Platform",
"addPlatform": "Add Platform",
"namePlaceholder": "e.g., Alibaba Cloud",
"codePlaceholder": "e.g., aliyun",
"descPlaceholder": "Platform description...",
"websiteLabel": "Website",
"enabled": "Enabled",
"disabled": "Disabled"
},
"billingRuleDialog": {
"fillFields": "Please fill in rule name and threshold",
"editRule": "Edit Alert Rule",
"addRule": "Add Alert Rule",
"namePlaceholder": "e.g., Daily cost over ¥500 alert"
},
"billingRecordDialog": {
"fillFields": "Please fill in required fields",
"editRecord": "Edit Cost Record",
"mainAccount": "Main Account"
}
}, },
"agent": { "agent": {
"title": "Agent Management", "title": "Agent Management",
@@ -6687,7 +6797,40 @@
"wizard.wizard.stepService": "3. Create systemd service", "wizard.wizard.stepService": "3. Create systemd service",
"wizard.wizard.stepRegisterWindows": "Register Windows Service", "wizard.wizard.stepRegisterWindows": "Register Windows Service",
"wizard.wizard.stepStart": "4. Start and enable on boot", "wizard.wizard.stepStart": "4. Start and enable on boot",
"wizard.wizard.stepStartCheck": "Start and Check" "wizard.wizard.stepStartCheck": "Start and Check",
"alerts": {
"loadFailed": "Failed to load alert data",
"resolveAlert": "Resolve Alert",
"resolveConfirm": "Mark this alert as resolved?",
"resolved": "Alert resolved",
"deleteRule": "Delete Rule",
"thresholdTriggered": "Alert threshold triggered",
"noResolved": "No resolved alerts",
"noActive": "No active alerts, all clear"
},
"monitor": {
"loadHostFailed": "Failed to load host list",
"enterCommand": "Enter command to execute",
"noOutput": "No output",
"commandPlaceholder": "e.g., free -m && df -h"
},
"alertHelper": {
"netInBps": "Network In (BPS)",
"memAvailable": "Available Memory (GB)",
"diskIOUtil": "Disk IO Utilization",
"netInErrors": "Network In Errors",
"hostOffline": "Host Offline",
"portDown": "Port Down"
},
"ruleDialog": {
"enterName": "Please enter rule name",
"editRule": "Edit Rule",
"namePlaceholder": "e.g., CPU Usage Alert",
"portPlaceholder": "e.g., 8080"
},
"monitor2": {
"commandPlaceholder2": "e.g., free -m && df -h"
}
}, },
"system": { "system": {
"title": "System", "title": "System",
@@ -7102,7 +7245,15 @@
"useauditsetting.s51680af0": "Please select valid retention days", "useauditsetting.s51680af0": "Please select valid retention days",
"useauditsetting.s7d55bd57": ", no expired records", "useauditsetting.s7d55bd57": ", no expired records",
"useldapmanageme.ouExists": "OU \"{{name}}\" already exists, please use a different name", "useldapmanageme.ouExists": "OU \"{{name}}\" already exists, please use a different name",
"useldapmanageme.createOuFailed": "Failed to create OU: " "useldapmanageme.createOuFailed": "Failed to create OU: ",
"ldapMgmt": {
"createFailed": "Create failed: ",
"deleteEntry": "Delete LDAP Entry",
"updateFailed": "Update failed",
"verifyFailed": "Verification failed",
"selectUsers": "Please select at least one user",
"importFailed": "Import failed: "
}
}, },
"aiops": { "aiops": {
"title": "AIOps", "title": "AIOps",
@@ -7654,6 +7805,10 @@
"resCpu": "Server CPU Compute", "resCpu": "Server CPU Compute",
"resMemory": "Server Memory", "resMemory": "Server Memory",
"resDisk": "Storage Disk Space" "resDisk": "Storage Disk Space"
},
"predictTypes": {
"memUsage": "Memory Usage",
"diskUsage": "Disk Usage"
} }
}, },
"breadcrumb": { "breadcrumb": {
+162 -7
View File
@@ -1134,6 +1134,11 @@
"clipboardDisabled": "会话策略已禁用剪贴板", "clipboardDisabled": "会话策略已禁用剪贴板",
"fileUploadDisabled": "会话策略已禁用文件上传", "fileUploadDisabled": "会话策略已禁用文件上传",
"fileDownloadDisabled": "会话策略已禁用文件下载" "fileDownloadDisabled": "会话策略已禁用文件下载"
},
"fileBrowser2": {
"loadDirFailed": "加载目录失败",
"downloadFailed": "下载失败: ",
"directory": "目录"
} }
}, },
"alert": { "alert": {
@@ -1581,7 +1586,9 @@
"checkoutNeedGit": "代码检出阶段需要填写 Git URL", "checkoutNeedGit": "代码检出阶段需要填写 Git URL",
"imageNeedDocker": "镜像打包阶段需要关联 Docker 凭证", "imageNeedDocker": "镜像打包阶段需要关联 Docker 凭证",
"suggestDocker": "建议关联凭证", "suggestDocker": "建议关联凭证",
"needDocker": "需要关联 Docker 凭证" "needDocker": "需要关联 Docker 凭证",
"imageBuild": "镜像打包",
"verify": "验证"
}, },
"env": { "env": {
"all": "全部", "all": "全部",
@@ -1852,7 +1859,17 @@
"deleteGroup": "删除用户组", "deleteGroup": "删除用户组",
"deleteRole": "删除角色", "deleteRole": "删除角色",
"userCol": "用户", "userCol": "用户",
"noUserRole": "该用户尚未分配任何 Item Role" "noUserRole": "该用户尚未分配任何 Item Role",
"policy": {
"fillFields": "请填写名称、标识与正则",
"deletePolicy": "删除权限",
"selectUserPermission": "请选择用户和权限",
"policyStrategy": "权限策略",
"userAuth": "用户授权",
"subtitle": "按流水线 code / Job 名称正则分配发布权限",
"noPolicy": "暂无 Job 权限策略",
"createHint": "创建策略后,用户将按正则自动获得匹配流水线的访问权限"
}
}, },
"banner": { "banner": {
"build": "构建", "build": "构建",
@@ -2331,7 +2348,37 @@
"filterSuccess": "成功", "filterSuccess": "成功",
"filterFailed": "失败", "filterFailed": "失败",
"filterDeploying": "发布中", "filterDeploying": "发布中",
"filterRolledBack": "已回滚" "filterRolledBack": "已回滚",
"buildNodes": {
"loadFailed": "加载构建节点失败",
"deleteConfirm": "确定删除构建节点「{{name}}」?",
"deleted": "已删除",
"saving": "保存中",
"testing": "测试中",
"noRemoteNodes": "暂无远程构建节点",
"noRemoteDesc": "关联服务器资产添加 SSH 节点,将重型构建任务调度到高性能机器",
"selectServer": "选择服务器",
"masterBuiltin": "Master(内置)",
"executorUsage": "占用 {{running}}/{{max}}",
"remoteHint": "基于服务器 SSH 凭据注册构建 Agent,流水线可绑定或自动调度",
"schedulingHint": "未指定节点时优先在 Master 执行;并发设为 1 时按触发顺序串行排队;满载后自动调度到空闲远程节点",
"testSuccess": "连通成功",
"executorLabel": "执行器 {{running}}/{{max}}",
"labelsLabel": "标签 {{labels}}"
},
"target": {
"hostDeploy": "主机发布",
"searchHost": "搜索主机名或 IP",
"selectFromList": "从下方列表选择",
"searchDeployment": "搜索 Deployment 或命名空间"
},
"userGroup": {
"noGroups": "暂无用户组",
"createFirstHint": "点击「新建用户组」创建第一个用户组",
"namePlaceholder": "例如: 后端开发组",
"subtitle": "用户组 — 批量授权流水线访问权限",
"memberCount": "成员总数"
}
}, },
"k8s": { "k8s": {
"cluster": { "cluster": {
@@ -4457,7 +4504,10 @@
"defaultRules": "默认规则", "defaultRules": "默认规则",
"disabled": "停用", "disabled": "停用",
"emptyTitle": "暂无{{cat}}规则", "emptyTitle": "暂无{{cat}}规则",
"emptyDesc": "点击「添加规则」或「默认规则」创建" "emptyDesc": "点击「添加规则」或「默认规则」创建",
"deleteRule": "删除告警规则",
"deleteConfirm": "确定删除此告警规则?",
"deleted": "告警规则已删除"
}, },
"middlewaremonit.sbbe62a10": "暂无中间件监控目标", "middlewaremonit.sbbe62a10": "暂无中间件监控目标",
"middlewaremonit.se5caf12b": "添加平台默认", "middlewaremonit.se5caf12b": "添加平台默认",
@@ -4816,6 +4866,41 @@
"probeName": "探针名称", "probeName": "探针名称",
"execCommand": "执行命令", "execCommand": "执行命令",
"refreshing": " · 刷新中..." "refreshing": " · 刷新中..."
},
"alertRuleDialog2": {
"editRule": "编辑告警规则",
"newRule": "新建告警规则",
"namePlaceholder": "如:CPU 使用率告警"
},
"alertMgr2": {
"alertEvents": "告警事件",
"monitorAlert": "监控告警",
"subtitle": "告警规则管理与告警事件处理",
"todayAlerts": "今日告警"
},
"promDs2": {
"listFailed": "获取数据源列表失败",
"deleteFailed": "删除失败",
"noData": "暂无 Prometheus 数据源",
"addHint": "添加数据源后可用于指标监控与监控大屏"
},
"alertChannelDialog2": {
"editChannel": "编辑渠道",
"newChannel": "新建渠道"
},
"logDsPage": {
"searchPlaceholder": "搜索数据源名称或地址...",
"noData": "暂无数据源,点击上方按钮添加"
},
"logQueryToolbar": {
"exactMatch": "结果精确",
"partialMatch": "部分命中"
},
"healthProbe": {
"namePlaceholder": "如:cex-agent 健康检查"
},
"hostMonitorTypes": {
"systemInfo": "系统信息"
} }
}, },
"task": { "task": {
@@ -6480,7 +6565,32 @@
"billingruledial.s8e1e26d1": "大于等于 &ge;", "billingruledial.s8e1e26d1": "大于等于 &ge;",
"billingruledial.s0196c18c": "小于 &lt;", "billingruledial.s0196c18c": "小于 &lt;",
"billingruledial.s45c301a8": "小于等于 &le;", "billingruledial.s45c301a8": "小于等于 &le;",
"billingruledial.s11f9fb4c": "阈值(元) *" "billingruledial.s11f9fb4c": "阈值(元) *",
"platform": {
"publicCloud": "公有云",
"searchPlaceholder": "搜索平台名称或编码...",
"subtitle": "管理公有云、私有云与混合云平台信息",
"noData": "暂无云平台数据",
"editPlatform": "编辑云平台",
"addPlatform": "添加平台",
"namePlaceholder": "如:阿里云",
"codePlaceholder": "如:aliyun",
"descPlaceholder": "平台描述...",
"websiteLabel": "官网地址",
"enabled": "启用",
"disabled": "禁用"
},
"billingRuleDialog": {
"fillFields": "请填写规则名称和阈值",
"editRule": "编辑预警规则",
"addRule": "添加预警规则",
"namePlaceholder": "如:日成本超 500 元预警"
},
"billingRecordDialog": {
"fillFields": "请填写必填字段",
"editRecord": "编辑成本记录",
"mainAccount": "主账号"
}
}, },
"agent": { "agent": {
"title": "Agent 管理", "title": "Agent 管理",
@@ -6687,7 +6797,40 @@
"wizard.wizard.stepService": "3. 创建 systemd 服务", "wizard.wizard.stepService": "3. 创建 systemd 服务",
"wizard.wizard.stepRegisterWindows": "注册 Windows 服务", "wizard.wizard.stepRegisterWindows": "注册 Windows 服务",
"wizard.wizard.stepStart": "4. 启动并设置开机自启", "wizard.wizard.stepStart": "4. 启动并设置开机自启",
"wizard.wizard.stepStartCheck": "启动并检查" "wizard.wizard.stepStartCheck": "启动并检查",
"alerts": {
"loadFailed": "加载告警数据失败",
"resolveAlert": "恢复告警",
"resolveConfirm": "确定将此告警标记为已恢复?",
"resolved": "告警已恢复",
"deleteRule": "删除规则",
"thresholdTriggered": "触发告警阈值",
"noResolved": "暂无已恢复的告警",
"noActive": "当前无活动告警,一切正常"
},
"monitor": {
"loadHostFailed": "加载主机列表失败",
"enterCommand": "请输入要执行的命令",
"noOutput": "无输出",
"commandPlaceholder": "例如:free -m && df -h"
},
"alertHelper": {
"netInBps": "入网带宽",
"memAvailable": "可用内存(GB)",
"diskIOUtil": "磁盘 IO 利用率",
"netInErrors": "入网错误",
"hostOffline": "主机离线",
"portDown": "端口不通"
},
"ruleDialog": {
"enterName": "请输入规则名称",
"editRule": "编辑规则",
"namePlaceholder": "例如:CPU 使用率告警",
"portPlaceholder": "例如:8080"
},
"monitor2": {
"commandPlaceholder2": "例如:free -m && df -h"
}
}, },
"system": { "system": {
"title": "系统管理", "title": "系统管理",
@@ -7102,7 +7245,15 @@
"useauditsetting.s51680af0": "请选择有效的保留天数", "useauditsetting.s51680af0": "请选择有效的保留天数",
"useauditsetting.s7d55bd57": ",无超期记录", "useauditsetting.s7d55bd57": ",无超期记录",
"useldapmanageme.ouExists": "OU \"{{name}}\" 已存在,请使用其他名称", "useldapmanageme.ouExists": "OU \"{{name}}\" 已存在,请使用其他名称",
"useldapmanageme.createOuFailed": "创建 OU 失败:" "useldapmanageme.createOuFailed": "创建 OU 失败:",
"ldapMgmt": {
"createFailed": "创建失败: ",
"deleteEntry": "删除 LDAP 条目",
"updateFailed": "修改失败",
"verifyFailed": "验证失败",
"selectUsers": "请至少选择一个用户",
"importFailed": "导入失败: "
}
}, },
"aiops": { "aiops": {
"title": "AIOps 智能", "title": "AIOps 智能",
@@ -7654,6 +7805,10 @@
"resCpu": "服务器CPU算力", "resCpu": "服务器CPU算力",
"resMemory": "服务器内存", "resMemory": "服务器内存",
"resDisk": "存储磁盘空间" "resDisk": "存储磁盘空间"
},
"predictTypes": {
"memUsage": "内存使用率",
"diskUsage": "磁盘使用率"
} }
}, },
"breadcrumb": { "breadcrumb": {
@@ -169,7 +169,7 @@ export function HealthProbeSection({ selectedHost }: HealthProbeSectionProps) {
<div className="space-y-3 max-h-[60vh] overflow-y-auto pr-1"> <div className="space-y-3 max-h-[60vh] overflow-y-auto pr-1">
<div> <div>
<label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block"> {t('>探针名称 <span className=')} <span className="dg-text-red">*</span></label> <label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block"> {t('>探针名称 <span className=')} <span className="dg-text-red">*</span></label>
<input className="w-full h-8 bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded px-2 text-xs text-[var(--dg-text-primary)]" value={probeForm.name} onChange={e => setProbeForm({ ...probeForm, name: e.target.value })} placeholder={t('如:cex-agent 健康检查')} /> <input className="w-full h-8 bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded px-2 text-xs text-[var(--dg-text-primary)]" value={probeForm.name} onChange={e => setProbeForm({ ...probeForm, name: e.target.value })} placeholder={t('monitor.healthProbe.namePlaceholder')} />
</div> </div>
<div> <div>
<label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block"> {t('agent.installTarget')} </label> <label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block"> {t('agent.installTarget')} </label>
@@ -4,11 +4,11 @@ import i18n from '@/i18n';
/** 预设时间范围(分钟粒度,支持 15/30 分钟) // Logan.Li */ /** 预设时间范围(分钟粒度,支持 15/30 分钟) // Logan.Li */
export const TIME_RANGE_PRESETS = [ export const TIME_RANGE_PRESETS = [
{ id: '15m', label: i18n.t('monitor.traceMgr.min15'), minutes: 15, buckets: 30 }, { id: '15m', label: i18n.t('monitor.traceMgr.min15'), minutes: 15, buckets: 30 },
{ id: '30m', label: i18n.t('系统信息'), minutes: 30, buckets: 36 }, { id: '30m', label: i18n.t('monitor.hostMonitorTypes.systemInfo'), minutes: 30, buckets: 36 },
{ id: '1h', label: i18n.t('monitor.traceMgr.hour1'), minutes: 60, buckets: 60 }, { id: '1h', label: i18n.t('monitor.traceMgr.hour1'), minutes: 60, buckets: 60 },
{ id: '6h', label: i18n.t('monitor.traceMgr.hour6'), minutes: 360, buckets: 72 }, { id: '6h', label: i18n.t('monitor.traceMgr.hour6'), minutes: 360, buckets: 72 },
{ id: '12h', label: i18n.t('系统信息'), minutes: 720, buckets: 72 }, { id: '12h', label: i18n.t('monitor.hostMonitorTypes.systemInfo'), minutes: 720, buckets: 72 },
{ id: '1d', label: i18n.t('系统信息'), minutes: 1440, buckets: 96 }, { id: '1d', label: i18n.t('monitor.hostMonitorTypes.systemInfo'), minutes: 1440, buckets: 96 },
{ id: '7d', label: i18n.t('monitor.traceMgr.day7'), minutes: 10080, buckets: 84 }, { id: '7d', label: i18n.t('monitor.traceMgr.day7'), minutes: 10080, buckets: 84 },
] as const; ] as const;