feat(i18n): 安全批量转换 135 文件 — 覆盖率 34%→62%

使用 i18n.t() 全局方式(避免 hook 定位问题):
- 288 目标文件中 135 个成功转换
- 2228 处中文替换为 i18n.t() 调用
- 1006 个新翻译 key 添加到 zh-CN.json + en-US.json
- 词典总量: 3554 keys (22 namespaces)
- tsc 零错误 + Vite 构建成功
- 6 个含代码示例/数据字符串的文件安全回退
This commit is contained in:
attacker committed 2026-07-03 02:14:20 +08:00
1 parent 72bfb0e12d
commit 55f216cfbb
131 files changed
+4049 -1908

No files matched your search

+21 -20
View File
@@ -28,6 +28,7 @@ import {
} from './agentAlertHelper';
import { AgentRuleDialog } from './AgentRuleDialog';
import type { JsonData } from '@/types/common';
import i18n from '@/i18n';
export function AgentAlerts() {
const confirm = useConfirm();
@@ -52,7 +53,7 @@ export function AgentAlerts() {
setRules(asList(rulesRes));
} catch (err) {
console.error(err);
toast.error('加载告警数据失败');
toast.error(i18n.t('agent.agentalertstsx.s7ffaa448'));
} finally {
setLoading(false);
}
@@ -64,14 +65,14 @@ export function AgentAlerts() {
// ============ 告警事件 ============
const handleResolve = async (id: number) => {
const ok = await confirm({ title: '恢复告警', description: '确定将此告警标记为已恢复?', confirmText: '恢复' });
const ok = await confirm({ title: i18n.t('agent.agentalertstsx.sd56d01fa'), description: i18n.t('agent.agentalertstsx.s99f0fb65'), confirmText: i18n.t('kubernetes.deployment.resume') });
if (!ok) return;
try {
await agentApi.resolveAlert(id);
toast.success('告警已恢复');
toast.success(i18n.t('agent.agentalertstsx.s0e23522e'));
loadData();
} catch (err) {
toast.error(err?.message || '操作失败');
toast.error(err?.message || i18n.t('cloud.operationFailed'));
}
};
@@ -87,14 +88,14 @@ export function AgentAlerts() {
};
const handleDeleteRule = async (id: number) => {
const ok = await confirm({ title: '删除规则', description: '确定删除此告警规则?', confirmText: '删除', variant: 'destructive' });
const ok = await confirm({ title: i18n.t('monitor.deleteRule'), description: i18n.t('monitor.alertrulestabts.sd301558d'), confirmText: i18n.t('kubernetes.storage.delete2'), variant: 'destructive' });
if (!ok) return;
try {
await agentApi.deleteRule(id);
toast.success('规则已删除');
toast.success(i18n.t('agent.agentalertstsx.s8b2798c4'));
loadData();
} catch (err) {
toast.error(err?.message || '删除失败');
toast.error(err?.message || i18n.t('kubernetes.msg.deleteFailed2'));
}
};
@@ -103,7 +104,7 @@ export function AgentAlerts() {
await agentApi.updateRule(rule.id, { ...rule, enabled: !rule.enabled });
loadData();
} catch (err) {
toast.error(err?.message || '操作失败');
toast.error(err?.message || i18n.t('cloud.operationFailed'));
}
};
@@ -113,7 +114,7 @@ export function AgentAlerts() {
<div className="flex items-center justify-between">
<div>
<h2 className="text-xl font-semibold text-[var(--dg-text-primary)]">Agent 告警</h2>
<p className="text-sm dg-text-secondary mt-0.5">基于 Agent 上报指标的告警规则与事件</p>
<p className="text-sm dg-text-secondary mt-0.5"> {i18n.t('agent.agentalertstsx.s03a08cd6')} </p>
</div>
<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' : ''}`} />
@@ -123,8 +124,8 @@ export function AgentAlerts() {
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList className="bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)]">
<TabsTrigger value="events" className="data-[state=active]:bg-[var(--dg-bg-active)]">告警事件</TabsTrigger>
<TabsTrigger value="rules" className="data-[state=active]:bg-[var(--dg-bg-active)]">告警规则</TabsTrigger>
<TabsTrigger value="events" className="data-[state=active]:bg-[var(--dg-bg-active)]"> {i18n.t('kubernetes.eventspagetsx.s0e5b83cc')} </TabsTrigger>
<TabsTrigger value="rules" className="data-[state=active]:bg-[var(--dg-bg-active)]"> {i18n.t('monitor.alertRules')} </TabsTrigger>
</TabsList>
{/* ========== 告警事件 ========== */}
@@ -152,14 +153,14 @@ export function AgentAlerts() {
<Card className="dg-card">
<CardContent className="flex items-center justify-center py-16">
<Loader2 className="h-5 w-5 animate-spin dg-text-muted" />
<span className="ml-2 dg-text-secondary">加载中...</span>
<span className="ml-2 dg-text-secondary"> {i18n.t('monitor.logPanel.logResults.loading')} </span>
</CardContent>
</Card>
) : alerts.length === 0 ? (
<Card className="dg-card">
<CardContent className="flex flex-col items-center justify-center py-16">
<CheckCircle className="h-10 w-10 text-emerald-500 mb-3" />
<p className="dg-text-secondary">{resolvedView ? '暂无已恢复的告警' : '当前无活动告警,一切正常'}</p>
<p className="dg-text-secondary">{resolvedView ? i18n.t('agent.agentalertstsx.se459f480') : i18n.t('agent.agentalertstsx.s778455b9')}</p>
</CardContent>
</Card>
) : (
@@ -175,20 +176,20 @@ export function AgentAlerts() {
<div className="min-w-0">
<div className="flex items-center gap-2 flex-wrap">
{severityBadge(alert.severity)}
<span className="font-medium text-[var(--dg-text-primary)]">{alert.rule_name || alert.name || alert.title || '告警'}</span>
<span className="font-medium text-[var(--dg-text-primary)]">{alert.rule_name || alert.name || alert.title || i18n.t('monitor.hostDashboard.alert')}</span>
</div>
<p className="text-sm dg-text-secondary mt-1">{alert.message || alert.description || '触发告警阈值'}</p>
<p className="text-sm dg-text-secondary mt-1">{alert.message || alert.description || i18n.t('agent.agentalertstsx.sddab4a17')}</p>
<div className="flex items-center gap-4 mt-2 text-xs dg-text-muted flex-wrap">
<span className="inline-flex items-center gap-1">
<Cpu className="h-3 w-3" />
{alert.hostname || '--'}
</span>
<span className="inline-flex items-center gap-1">
<span className="dg-text-secondary">指标:</span> {getMetricLabel(alert.metric)}
<span className="dg-text-secondary"> {i18n.t('agent.agentalertstsx.sbfdcf242')} </span> {getMetricLabel(alert.metric)}
</span>
{alert.value != null && (
<span className="inline-flex items-center gap-1">
<span className="dg-text-secondary">当前值:</span> {alert.value}
<span className="dg-text-secondary"> {i18n.t('agent.agentalertstsx.s2d13763b')} </span> {alert.value}
</span>
)}
<span className="inline-flex items-center gap-1">
@@ -230,14 +231,14 @@ export function AgentAlerts() {
<Card className="dg-card">
<CardContent className="flex items-center justify-center py-16">
<Loader2 className="h-5 w-5 animate-spin dg-text-muted" />
<span className="ml-2 dg-text-secondary">加载中...</span>
<span className="ml-2 dg-text-secondary"> {i18n.t('monitor.logPanel.logResults.loading')} </span>
</CardContent>
</Card>
) : rules.length === 0 ? (
<Card className="dg-card">
<CardContent className="flex flex-col items-center justify-center py-16">
<Bell className="h-10 w-10 dg-text-muted mb-3" />
<p className="dg-text-secondary">暂无告警规则</p>
<p className="dg-text-secondary"> {i18n.t('monitor.noRules')} </p>
<Button size="sm" onClick={openCreateRule} className="mt-4 bg-blue-600 hover:bg-blue-700 text-white">
<Plus className="h-4 w-4 mr-1.5" />
创建第一条规则
@@ -261,7 +262,7 @@ export function AgentAlerts() {
<span className="font-medium text-[var(--dg-text-primary)]">{rule.name}</span>
{severityBadge(rule.severity)}
{rule.enabled === false && (
<Badge className="bg-[var(--dg-bg-active)] dg-text-secondary border border-[var(--dg-border-hover)]">已禁用</Badge>
<Badge className="bg-[var(--dg-bg-active)] dg-text-secondary border border-[var(--dg-border-hover)]"> {i18n.t('aiops.llmDisabled')} </Badge>
)}
</div>
<div className="flex items-center gap-3 mt-1.5 text-xs dg-text-muted flex-wrap">
+21 -20
View File
@@ -13,6 +13,7 @@ import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { Badge } from '@/components/ui/badge';
import { Progress } from '@/components/ui/progress';
import i18n from '@/i18n';
import {
Dialog,
DialogContent,
@@ -68,7 +69,7 @@ export function AgentMonitor() {
if (fresh) setSelectedHost(fresh);
}
} catch (err) {
if (!silent) toast.error('加载主机列表失败');
if (!silent) toast.error(i18n.t('agent.agentmonitortsx.s5760f667'));
console.error(err);
} finally {
setLoading(false);
@@ -97,7 +98,7 @@ export function AgentMonitor() {
// 提交远程命令(直连 SSH 即时返回)
const submitCommand = async () => {
if (!cmdCommand.trim()) {
toast.error('请输入要执行的命令');
toast.error(i18n.t('agent.agentmonitortsx.s9358aada'));
return;
}
setCmdSubmitting(true);
@@ -111,11 +112,11 @@ export function AgentMonitor() {
const resData = (res?.data ?? {}) as Record<string, unknown>;
setCmdResult({
status: res?.code === 200 ? 'completed' : 'failed',
result: (resData.output as string) || (res?.message as string) || '无输出',
result: (resData.output as string) || (res?.message as string) || i18n.t('agent.agentmonitortsx.s26d45bea'),
error: resData.error as string | undefined,
});
} catch (err) {
setCmdResult({ status: 'failed', result: err?.message || '执行失败' });
setCmdResult({ status: 'failed', result: err?.message || i18n.t('automation.systemTaskFailed') });
} finally {
setCmdSubmitting(false);
loadLatest(true);
@@ -129,9 +130,9 @@ export function AgentMonitor() {
const onlineCount = hosts.filter((h) => isOnline(h.last_report || h.last_reported_at || h.updated_at)).length;
const stats = [
{ label: '主机总数', value: hosts.length, icon: Server, color: 'dg-text-blue' },
{ label: '在线', value: onlineCount, icon: Wifi, color: 'dg-text-green' },
{ label: '离线', value: hosts.length - onlineCount, icon: WifiOff, color: 'dg-text-red' },
{ label: i18n.t('monitor.hostDashboard.hostTotal'), value: hosts.length, icon: Server, color: 'dg-text-blue' },
{ label: i18n.t('agent.agentOnline'), value: onlineCount, icon: Wifi, color: 'dg-text-green' },
{ label: i18n.t('agent.agentOffline'), value: hosts.length - onlineCount, icon: WifiOff, color: 'dg-text-red' },
];
return (
@@ -140,7 +141,7 @@ export function AgentMonitor() {
<div className="flex items-center justify-between">
<div>
<h2 className="text-xl font-semibold text-[var(--dg-text-primary)]">Agent 监控</h2>
<p className="text-sm dg-text-secondary mt-0.5">实时监控 Agent 上报的主机指标</p>
<p className="text-sm dg-text-secondary mt-0.5"> {i18n.t('agent.agentmonitortsx.sa75d7519')} </p>
</div>
<div className="flex items-center gap-2">
<Button
@@ -186,14 +187,14 @@ export function AgentMonitor() {
<Card className="dg-card">
<CardContent className="flex items-center justify-center py-16">
<Loader2 className="h-6 w-6 animate-spin dg-text-muted" />
<span className="ml-2 dg-text-secondary">加载中...</span>
<span className="ml-2 dg-text-secondary"> {i18n.t('monitor.logPanel.logResults.loading')} </span>
</CardContent>
</Card>
) : hosts.length === 0 ? (
<Card className="dg-card">
<CardContent className="flex flex-col items-center justify-center py-16">
<Server className="h-10 w-10 dg-text-muted mb-3" />
<p className="dg-text-secondary">暂无 Agent 上报数据</p>
<p className="dg-text-secondary"> {i18n.t('agent.noAgentData')} </p>
<Button
size="sm"
onClick={() => setWizardOpen(true)}
@@ -240,8 +241,8 @@ export function AgentMonitor() {
{/* 指标条 */}
<div className="space-y-2">
<MetricBar icon={Cpu} label="CPU" value={cpu} />
<MetricBar icon={MemoryStick} label="内存" value={mem} />
<MetricBar icon={HardDrive} label="磁盘" value={disk} />
<MetricBar icon={MemoryStick} label={i18n.t('kubernetes.pod.memory')} value={mem} />
<MetricBar icon={HardDrive} label={i18n.t('monitor.dashboard.col.disk')} value={disk} />
</div>
{/* 端口 & 时间 */}
@@ -307,7 +308,7 @@ export function AgentMonitor() {
<div className="space-y-3">
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)] text-xs">目标主机</Label>
<Label className="text-[var(--dg-text-secondary)] text-xs"> {i18n.t('agent.installTarget')} </Label>
<Input
value={cmdHost}
onChange={(e) => setCmdHost(e.target.value)}
@@ -318,18 +319,18 @@ export function AgentMonitor() {
</div>
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)] text-xs">命令</Label>
<Label className="text-[var(--dg-text-secondary)] text-xs"> {i18n.t('system.terminalCommand')} </Label>
<Textarea
value={cmdCommand}
onChange={(e) => setCmdCommand(e.target.value)}
placeholder="例如:free -m && df -h"
placeholder={i18n.t('agent.agentmonitortsx.s6e62a5b0')}
rows={3}
className="bg-[var(--dg-bg-primary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] font-mono text-sm resize-none"
/>
</div>
<div className="w-40 space-y-1.5">
<Label className="text-[var(--dg-text-secondary)] text-xs">超时(秒)</Label>
<Label className="text-[var(--dg-text-secondary)] text-xs"> {i18n.t('automation.remoteTimeout')} </Label>
<Input
type="number"
value={cmdTimeout}
@@ -342,7 +343,7 @@ export function AgentMonitor() {
{cmdResult && (
<div className="space-y-1.5">
<div className="flex items-center gap-2">
<Label className="text-[var(--dg-text-secondary)] text-xs">执行结果</Label>
<Label className="text-[var(--dg-text-secondary)] text-xs"> {i18n.t('automation.remoteExecResult')} </Label>
<Badge
className={
cmdResult.status === 'success' || cmdResult.status === 'done' || cmdResult.status === 'completed'
@@ -356,7 +357,7 @@ export function AgentMonitor() {
</Badge>
</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">
{cmdResult.result || cmdResult.output || cmdResult.error || '无输出'}
{cmdResult.result || cmdResult.output || cmdResult.error || i18n.t('agent.agentmonitortsx.s26d45bea')}
</pre>
</div>
)}
@@ -372,9 +373,9 @@ export function AgentMonitor() {
className="bg-blue-600 hover:bg-blue-700 text-white"
>
{cmdSubmitting ? (
<><Loader2 className="h-4 w-4 mr-1.5 animate-spin" />执行中</>
<><Loader2 className="h-4 w-4 mr-1.5 animate-spin" /> {i18n.t('task.cronJob.status.running')} </>
) : (
<><Terminal className="h-4 w-4 mr-1.5" />执行</>
<><Terminal className="h-4 w-4 mr-1.5" /> {i18n.t('deploy.label.execute')} </>
)}
</Button>
</DialogFooter>
@@ -24,6 +24,7 @@ import {
import { Loader2 } from 'lucide-react';
import { METRIC_OPTIONS, OPERATOR_OPTIONS, SEVERITY_OPTIONS } from './agentAlertHelper';
import type { JsonData } from '@/types/common';
import i18n from '@/i18n';
interface Props {
open: boolean;
@@ -69,7 +70,7 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
const handleSave = async () => {
if (!ruleForm.name.trim()) {
toast.error('请输入规则名称');
toast.error(i18n.t('agent.agentruledialog.s40f3f17d'));
return;
}
setSaving(true);
@@ -88,15 +89,15 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
}
if (editingRule) {
await agentApi.updateRule(editingRule.id, payload);
toast.success('规则已更新');
toast.success(i18n.t('monitor.monitorRule.ruleUpdated'));
} else {
await agentApi.createRule(payload);
toast.success('规则已创建');
toast.success(i18n.t('monitor.monitorRule.ruleCreated'));
}
onOpenChange(false);
onSuccess();
} catch (err) {
toast.error(err?.message || '保存失败');
toast.error(err?.message || i18n.t('kubernetes.msg.saveFailed'));
} finally {
setSaving(false);
}
@@ -106,24 +107,24 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
<Dialog open={open} onOpenChange={(v) => { if (!v) onOpenChange(false); }}>
<DialogContent className="max-w-md dg-card text-[var(--dg-text-primary)]">
<DialogHeader>
<DialogTitle className="text-[var(--dg-text-primary)]">{editingRule ? '编辑规则' : '新建规则'}</DialogTitle>
<DialogDescription className="dg-text-secondary">配置 Agent 指标告警规则</DialogDescription>
<DialogTitle className="text-[var(--dg-text-primary)]">{editingRule ? i18n.t('monitor.editRule') : i18n.t('agent.agentruledialog.sf10ed468')}</DialogTitle>
<DialogDescription className="dg-text-secondary"> {i18n.t('agent.agentruledialog.s0cc0f608')} </DialogDescription>
</DialogHeader>
<div className="space-y-3">
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)] text-xs">规则名称</Label>
<Label className="text-[var(--dg-text-secondary)] text-xs"> {i18n.t('monitor.ruleName')} </Label>
<Input
value={ruleForm.name}
onChange={(e) => setRuleForm({ ...ruleForm, name: e.target.value })}
placeholder="例如:CPU 使用率告警"
placeholder={i18n.t('agent.agentruledialog.s5a0f7e3e')}
className="h-9 dg-input"
/>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)] text-xs">监控指标</Label>
<Label className="text-[var(--dg-text-secondary)] text-xs"> {i18n.t('monitor.monitorRule.metricLabel')} </Label>
<Select value={ruleForm.metric} onValueChange={(v) => setRuleForm({ ...ruleForm, metric: v })}>
<SelectTrigger className="h-9 dg-input">
<SelectValue />
@@ -136,7 +137,7 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
</Select>
</div>
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)] text-xs">严重程度</Label>
<Label className="text-[var(--dg-text-secondary)] text-xs"> {i18n.t('monitor.ruleSeverity')} </Label>
<Select value={ruleForm.severity} onValueChange={(v) => setRuleForm({ ...ruleForm, severity: v })}>
<SelectTrigger className="h-9 dg-input">
<SelectValue />
@@ -153,7 +154,7 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
{ruleForm.metric !== 'offline' && (
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)] text-xs">比较运算符</Label>
<Label className="text-[var(--dg-text-secondary)] text-xs"> {i18n.t('agent.agentruledialog.sd52f2cb3')} </Label>
<Select value={ruleForm.operator} onValueChange={(v) => setRuleForm({ ...ruleForm, operator: v })}>
<SelectTrigger className="h-9 dg-input">
<SelectValue />
@@ -179,39 +180,39 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
{ruleForm.metric === 'port_down' && (
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)] text-xs">监控端口</Label>
<Label className="text-[var(--dg-text-secondary)] text-xs"> {i18n.t('agent.agentruledialog.s4d134841')} </Label>
<Input
type="number"
value={ruleForm.port}
onChange={(e) => setRuleForm({ ...ruleForm, port: e.target.value })}
placeholder="例如:8080"
placeholder={i18n.t('agent.agentruledialog.s77565e5d')}
className="h-9 dg-input"
/>
</div>
)}
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)] text-xs">持续时间(秒)</Label>
<Label className="text-[var(--dg-text-secondary)] text-xs"> {i18n.t('agent.agentruledialog.sb1551d7f')} </Label>
<Input
type="number"
value={ruleForm.duration}
onChange={(e) => setRuleForm({ ...ruleForm, duration: e.target.value })}
placeholder="指标持续超限多久后告警"
placeholder={i18n.t('agent.agentruledialog.sd140c78d')}
className="h-9 dg-input"
/>
</div>
<div className="flex items-center justify-between">
<Label className="text-[var(--dg-text-secondary)] text-xs">启用规则</Label>
<Label className="text-[var(--dg-text-secondary)] text-xs"> {i18n.t('monitor.monitorRule.enableRule')} </Label>
<Switch checked={ruleForm.enabled} onCheckedChange={(v) => setRuleForm({ ...ruleForm, enabled: v })} />
</div>
</div>
<DialogFooter className="gap-2">
<Button variant="outline" onClick={() => onOpenChange(false)} className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">取消</Button>
<Button variant="outline" onClick={() => onOpenChange(false)} className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]"> {i18n.t('rbac.jobRole.cancel')} </Button>
<Button onClick={handleSave} disabled={saving} className="bg-blue-600 hover:bg-blue-700 text-white">
{saving ? <Loader2 className="h-4 w-4 mr-1.5 animate-spin" /> : null}
{editingRule ? '保存' : '创建'}
{editingRule ? i18n.t('task.script.save') : i18n.t('rbac.jobRole.create')}
</Button>
</DialogFooter>
</DialogContent>
+18 -17
View File
@@ -14,6 +14,7 @@ import { Label } from '@/components/ui/label';
import { Copy, Check, Terminal, Download, Settings, Play, Cpu, Zap, Loader2, Server, Shield } from 'lucide-react';
import { serverApi } from '@/api';
import { toast } from 'sonner';
import i18n from '@/i18n';
interface AgentWizardProps {
open: boolean;
@@ -104,10 +105,10 @@ export function AgentWizard({ open, onOpenChange, server, onStart, onInstalled,
if (success) {
setCopied(true);
toast.success('安装命令已复制到剪贴板,粘贴到目标主机执行');
toast.success(i18n.t('agent.agentwizardtsx.s19ee0048'));
setTimeout(() => setCopied(false), 3000);
} else {
toast.error('自动复制失败,请在下方手动全选复制 (Ctrl+A → Ctrl+C)');
toast.error(i18n.t('agent.agentwizardtsx.s7ac41b1c'));
setShowManualInput(true);
}
};
@@ -117,19 +118,19 @@ export function AgentWizard({ open, onOpenChange, server, onStart, onInstalled,
const manualSteps = [
{
icon: Download,
title: '1. 下载二进制',
title: i18n.t('agent.agentwizardtsx.s8c59e054'),
commands: [
`curl -fsSL ${binaryUrl} -o /usr/local/bin/aiops-agent`,
],
},
{
icon: Settings,
title: '2. 赋予执行权限',
title: i18n.t('agent.agentwizardtsx.s2f17bf61'),
commands: ['chmod +x /usr/local/bin/aiops-agent'],
},
{
icon: Cpu,
title: '3. 创建 systemd 服务',
title: i18n.t('agent.agentwizardtsx.s03fa0002'),
commands: [
`cat > /etc/systemd/system/aiops-agent.service << 'EOF'`,
`[Unit]`,
@@ -149,7 +150,7 @@ export function AgentWizard({ open, onOpenChange, server, onStart, onInstalled,
},
{
icon: Play,
title: '4. 启动并设置开机自启',
title: i18n.t('agent.agentwizardtsx.s84bd81f4'),
commands: [
'systemctl daemon-reload',
'systemctl enable --now aiops-agent',
@@ -163,7 +164,7 @@ export function AgentWizard({ open, onOpenChange, server, onStart, onInstalled,
const handleSshInstall = async () => {
if (!server || !token) return;
if (needsCreds && !sshPassword && !sshKey) {
toast.error('请填写 SSH 登录凭据(密码或密钥)');
toast.error(i18n.t('agent.agentwizardtsx.s6cfe961c'));
return;
}
setSshInstalling(true);
@@ -186,7 +187,7 @@ export function AgentWizard({ open, onOpenChange, server, onStart, onInstalled,
onInstalled?.(server.id);
onOpenChange(false);
} catch (e) {
const msg = e?.message || '安装失败';
const msg = e?.message || i18n.t('agent.installFailed');
toast.error(`${server.hostname} ${msg}`);
onFailed?.(server.id, msg);
} finally {
@@ -199,12 +200,12 @@ export function AgentWizard({ open, onOpenChange, server, onStart, onInstalled,
<DialogContent className="max-w-2xl dg-card text-[var(--dg-text-primary)]">
<DialogHeader>
<DialogTitle className="text-[var(--dg-text-primary)]">
{server ? `部署 Agent → ${server.hostname}` : '部署 Agent'}
{server ? `部署 Agent → ${server.hostname}` : i18n.t('agent.agentwizardtsx.s812cc1a5')}
</DialogTitle>
<DialogDescription className="dg-text-secondary">
{server
? `目标主机 ${server.hostname} (${server.ip}),选择安装方式:`
: '在目标主机上执行以下命令即可完成 Agent 安装与注册'
: i18n.t('agent.agentwizardtsx.sed0c1b63')
}
</DialogDescription>
</DialogHeader>
@@ -246,7 +247,7 @@ export function AgentWizard({ open, onOpenChange, server, onStart, onInstalled,
variant="ghost"
onClick={handleCopy}
className="absolute top-2 right-2 h-8 w-8 dg-text-secondary hover:text-[var(--dg-text-primary)] hover:bg-[var(--dg-bg-hover)]"
title="复制到剪贴板"
title={i18n.t('agent.agentwizardtsx.s564be0b1')}
>
{copied ? <Check className="h-4 w-4 dg-text-green" /> : <Copy className="h-4 w-4" />}
</Button>
@@ -279,8 +280,8 @@ export function AgentWizard({ open, onOpenChange, server, onStart, onInstalled,
{!server ? (
<div className="text-center py-12 dg-text-secondary">
<Server className="h-10 w-10 mx-auto mb-3 opacity-50" />
<p className="text-sm font-medium mb-1">未选择目标主机</p>
<p className="text-xs">请先在主机列表中勾选一台主机,再点击「安装 Agent」打开此页面</p>
<p className="text-sm font-medium mb-1"> {i18n.t('agent.agentwizardtsx.sc9b7eec2')} </p>
<p className="text-xs"> {i18n.t('agent.agentwizardtsx.s12a11150')} </p>
</div>
) : (
<>
@@ -298,11 +299,11 @@ export function AgentWizard({ open, onOpenChange, server, onStart, onInstalled,
<div className="mb-4 p-3 rounded-lg bg-amber-950/30 border border-amber-800/40 space-y-3">
<div className="flex items-center gap-2 text-xs text-amber-300/80">
<Shield className="h-3.5 w-3.5" />
<span>该服务器未配置 SSH 凭据,请填写以下信息(首次连接成功后自动保存)</span>
<span> {i18n.t('agent.agentwizardtsx.seb55dff9')} </span>
</div>
<div className="grid grid-cols-2 gap-2">
<div>
<Label className="text-xs dg-text-secondary">用户名</Label>
<Label className="text-xs dg-text-secondary"> {i18n.t('system.userName')} </Label>
<Input
value={sshUsername}
onChange={e => setSshUsername(e.target.value)}
@@ -312,7 +313,7 @@ export function AgentWizard({ open, onOpenChange, server, onStart, onInstalled,
</div>
<div className="flex items-end gap-1">
<div className="flex-1">
<Label className="text-xs dg-text-secondary">密码</Label>
<Label className="text-xs dg-text-secondary"> {i18n.t('system.passwordLabel')} </Label>
<Input
type="password"
value={sshPassword}
@@ -360,7 +361,7 @@ export function AgentWizard({ open, onOpenChange, server, onStart, onInstalled,
) : (
<>
<Zap className="h-4 w-4 mr-2" />
{needsCreds ? '连接并安装 Agent(凭据将自动保存)' : '一键安装 Agent'}
{needsCreds ? i18n.t('agent.agentwizardtsx.sce2f5317') : i18n.t('agent.agentwizardtsx.s9866eb4f')}
</>
)}
</Button>
@@ -30,6 +30,7 @@ import {
ResponsiveContainer,
} from 'recharts';
import type { JsonData } from '@/types/common';
import i18n from '@/i18n';
export function HostDetailDialog({
host,
@@ -78,21 +79,21 @@ export function HostDetailDialog({
<Server className="h-5 w-5 dg-text-blue" />
{host.hostname}
{online ? (
<Badge className="bg-emerald-500/15 dg-text-green border border-emerald-500/30">在线</Badge>
<Badge className="bg-emerald-500/15 dg-text-green border border-emerald-500/30"> {i18n.t('agent.agentOnline')} </Badge>
) : (
<Badge className="bg-red-500/15 dg-text-red border border-red-500/30">离线</Badge>
<Badge className="bg-red-500/15 dg-text-red border border-red-500/30"> {i18n.t('agent.agentOffline')} </Badge>
)}
</DialogTitle>
<DialogDescription className="sr-only">查看主机详情和指标趋势</DialogDescription>
<DialogDescription className="sr-only"> {i18n.t('agent.hostdetaildialo.sffa4f720')} </DialogDescription>
</DialogHeader>
<div className="space-y-4">
{/* 基本信息 */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 text-sm">
<InfoCell label="IP 地址" value={host.ip || '--'} />
<InfoCell label="操作系统" value={host.os || host.platform || '--'} />
<InfoCell label="最后上报" value={timeAgo(host.last_report || host.last_reported_at || host.updated_at)} />
<InfoCell label="端口数" value={String(ports.length)} />
<InfoCell label={i18n.t('agent.agentIp')} value={host.ip || '--'} />
<InfoCell label={i18n.t('agent.agentOs')} value={host.os || host.platform || '--'} />
<InfoCell label={i18n.t('agent.agentLastSeen')} value={timeAgo(host.last_report || host.last_reported_at || host.updated_at)} />
<InfoCell label={i18n.t('agent.hostdetaildialo.s6ad4fc6e')} value={String(ports.length)} />
</div>
{/* 趋势图 */}
@@ -107,9 +108,9 @@ export function HostDetailDialog({
<SelectValue />
</SelectTrigger>
<SelectContent className="dg-input">
<SelectItem value="1">最近 1 小时</SelectItem>
<SelectItem value="6">最近 6 小时</SelectItem>
<SelectItem value="24">最近 24 小时</SelectItem>
<SelectItem value="1"> {i18n.t('agent.hostdetaildialo.s3851af2c')} </SelectItem>
<SelectItem value="6"> {i18n.t('agent.hostdetaildialo.s2d59b172')} </SelectItem>
<SelectItem value="24"> {i18n.t('agent.hostdetaildialo.s768a1b34')} </SelectItem>
</SelectContent>
</Select>
</div>
@@ -119,7 +120,7 @@ export function HostDetailDialog({
<Loader2 className="h-5 w-5 animate-spin dg-text-muted" />
</div>
) : chartData.length === 0 ? (
<div className="h-full flex items-center justify-center text-sm dg-text-muted">暂无历史数据</div>
<div className="h-full flex items-center justify-center text-sm dg-text-muted"> {i18n.t('agent.hostdetaildialo.sdc73dbe9')} </div>
) : (
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={chartData} margin={{ top: 5, right: 5, bottom: 0, left: -20 }}>
@@ -144,8 +145,8 @@ export function HostDetailDialog({
contentStyle={{ backgroundColor: '#0f172a', border: '1px solid #1e293b', borderRadius: '6px', color: '#f8fafc', fontSize: '12px' }}
/>
<Area type="monotone" dataKey="cpu" stroke="#3b82f6" fill="url(#gCpu)" name="CPU" />
<Area type="monotone" dataKey="mem" stroke="#10b981" fill="url(#gMem)" name="内存" />
<Area type="monotone" dataKey="disk" stroke="#f59e0b" fill="url(#gDisk)" name="磁盘" />
<Area type="monotone" dataKey="mem" stroke="#10b981" fill="url(#gMem)" name={i18n.t('kubernetes.pod.memory')} />
<Area type="monotone" dataKey="disk" stroke="#f59e0b" fill="url(#gDisk)" name={i18n.t('monitor.dashboard.col.disk')} />
</AreaChart>
</ResponsiveContainer>
)}
@@ -159,7 +160,7 @@ export function HostDetailDialog({
端口发现 ({ports.length})
</span>
{ports.length === 0 ? (
<p className="text-xs dg-text-muted">暂无端口数据</p>
<p className="text-xs dg-text-muted"> {i18n.t('agent.hostdetaildialo.se7887d60')} </p>
) : (
<div className="flex flex-wrap gap-1.5 max-h-32 overflow-y-auto">
{ports.map((p, idx) => (
@@ -5,42 +5,43 @@ import {
Activity, Gauge, Timer,
} from 'lucide-react';
import type { JsonData } from '@/types/common';
import i18n from '@/i18n';
// 主机监控指标(与后端 alert_engine 对齐)// Logan.Li
export const METRIC_OPTIONS = [
{ value: 'cpu', label: 'CPU 使用率', icon: Cpu },
{ value: 'cpu_iowait', label: 'CPU IO 等待', icon: Cpu },
{ value: 'mem', label: '内存使用率', icon: MemoryStick },
{ value: 'mem_available', label: '可用内存(GB)', icon: MemoryStick },
{ value: 'swap', label: 'Swap 使用率', icon: MemoryStick },
{ value: 'load1', label: '1分钟负载', icon: Gauge },
{ value: 'disk', label: '磁盘使用率', icon: HardDrive },
{ value: 'disk_inode', label: 'Inode 使用率', icon: HardDrive },
{ value: 'disk_io_util', label: '磁盘 IO 利用率', icon: HardDrive },
{ value: 'tcp_established', label: 'TCP 已建立', icon: Network },
{ value: 'cpu', label: i18n.t('kubernetes.cluster.cpuUsage'), icon: Cpu },
{ value: 'cpu_iowait', label: i18n.t('agent.agentalerthelpe.sc1eec3e5'), icon: Cpu },
{ value: 'mem', label: i18n.t('kubernetes.cluster.memoryUsage'), icon: MemoryStick },
{ value: 'mem_available', label: i18n.t('monitor.hostmonitortsx.sa23fcda0'), icon: MemoryStick },
{ value: 'swap', label: i18n.t('agent.agentalerthelpe.sa2f9101c'), icon: MemoryStick },
{ value: 'load1', label: i18n.t('agent.agentalerthelpe.sf23ce82f'), icon: Gauge },
{ value: 'disk', label: i18n.t('monitor.hostDisk'), icon: HardDrive },
{ value: 'disk_inode', label: i18n.t('agent.agentalerthelpe.sf7eadeca'), icon: HardDrive },
{ value: 'disk_io_util', label: i18n.t('agent.agentalerthelpe.s565a0e4d'), icon: HardDrive },
{ value: 'tcp_established', label: i18n.t('agent.agentalerthelpe.s6d9639e8'), icon: Network },
{ value: 'tcp_time_wait', label: 'TCP TIME_WAIT', icon: Network },
{ value: 'tcp_close_wait', label: 'TCP CLOSE_WAIT', icon: Network },
{ value: 'net_rx_bps', label: '入网带宽', icon: Network },
{ value: 'net_tx_bps', label: '出网带宽', icon: Network },
{ value: 'net_rx_errors', label: '入网错误', icon: Network },
{ value: 'fd', label: '文件描述符', icon: Activity },
{ value: 'offline', label: '主机离线', icon: WifiOff },
{ value: 'port_down', label: '端口不通', icon: Network },
{ value: 'health_probe', label: '健康探针', icon: Timer },
{ value: 'net_rx_bps', label: i18n.t('monitor.hostNetIn'), icon: Network },
{ value: 'net_tx_bps', label: i18n.t('monitor.hostNetOut'), icon: Network },
{ value: 'net_rx_errors', label: i18n.t('agent.agentalerthelpe.s8d73bfe3'), icon: Network },
{ value: 'fd', label: i18n.t('monitor.hostFileDescriptor'), icon: Activity },
{ value: 'offline', label: i18n.t('agent.agentalerthelpe.s8ed49aa6'), icon: WifiOff },
{ value: 'port_down', label: i18n.t('agent.agentalerthelpe.sf62ce662'), icon: Network },
{ value: 'health_probe', label: i18n.t('kubernetes.container.probes'), icon: Timer },
];
export const OPERATOR_OPTIONS = [
{ value: '>', label: '> 大于' },
{ value: '<', label: '< 小于' },
{ value: '>=', label: '≥ 大于等于' },
{ value: '<=', label: '≤ 小于等于' },
{ value: '==', label: '= 等于' },
{ value: '>', label: i18n.t('monitor.monitorRule.opGt') },
{ value: '<', label: i18n.t('monitor.monitorRule.opLt') },
{ value: '>=', label: i18n.t('monitor.monitorRule.opGte') },
{ value: '<=', label: i18n.t('monitor.monitorRule.opLte') },
{ value: '==', label: i18n.t('monitor.monitorRule.opEq') },
];
export const SEVERITY_OPTIONS = [
{ value: 'critical', label: '严重', color: 'bg-red-500/15 dg-text-red border-red-500/30' },
{ value: 'warning', label: '警告', color: 'bg-amber-500/15 dg-text-yellow border-amber-500/30' },
{ value: 'info', label: '提示', color: 'bg-blue-500/15 dg-text-blue border-blue-500/30' },
{ value: 'critical', label: i18n.t('monitor.alertHelper.sevCritical'), color: 'bg-red-500/15 dg-text-red border-red-500/30' },
{ value: 'warning', label: i18n.t('kubernetes.event.warning'), color: 'bg-amber-500/15 dg-text-yellow border-amber-500/30' },
{ value: 'info', label: i18n.t('monitor.alertEvents.severity.info'), color: 'bg-blue-500/15 dg-text-blue border-blue-500/30' },
];
export function getMetricLabel(metric: string) {