refactor(i18n): U28 多模块 phantom key 清零 — theme/middleware-monitor/site-monitor/analytics/cost-predict/predict-types/agentAlertHelper/export-audit/AgentCLIMessages/copy-button 10 文件 74 phantom key → 语义化键, 新增 4 命名空间 (theme/agent.alerts.metric*/audit.*) + 扩展 3 命名空间 (monitor.lbl.mw*/site*/aiops.predict*), zh/en 7294→7358 +64 键对齐, phantom key 103→29 (残留全为 JSX 模板表达式/ANSI 转义码误匹配), tsc -b=0, npm build✓ (17.40s), ESLint=0/53/0, go build=0, 17/17 测试通过

This commit is contained in:
attacker committed 2026-07-12 05:18:52 +08:00
1 parent b812b3b20c
commit b2715ca3bd
12 files changed
+206 -74

No files matched your search

@@ -13,12 +13,12 @@ export const METRIC_OPTIONS = [
{ value: 'cpu_iowait', label: i18n.t('agent.alertHelper.netInBps'), 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('agent.alertHelper.memAvailable'), 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('agent.alerts.metricSwap'), icon: MemoryStick },
{ value: 'load1', label: i18n.t('1分钟负载'), icon: Gauge }, { value: 'load1', label: i18n.t('agent.alerts.metricLoad1'), 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('agent.alerts.metricInode'), icon: HardDrive },
{ value: 'disk_io_util', label: i18n.t('agent.alertHelper.diskIOUtil'), 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('agent.alerts.metricTcpEst'), 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 },
@@ -253,7 +253,7 @@ export function MessageView({ item, inRoundGroup = false }: { item: ChatItem; in
return ( return (
<div className="flex gap-2 rounded-lg bg-red-500/10 border border-red-500/30 p-3"> <div className="flex gap-2 rounded-lg bg-red-500/10 border border-red-500/30 p-3">
<AlertTriangle className="w-5 h-5 dg-text-red shrink-0 mt-0.5" /> <AlertTriangle className="w-5 h-5 dg-text-red shrink-0 mt-0.5" />
<div className="text-sm text-[var(--dg-text-primary)] whitespace-pre-wrap">{item.content || i18n.t('发生错误')}</div> <div className="text-sm text-[var(--dg-text-primary)] whitespace-pre-wrap">{item.content || i18n.t('aiops.agentCliErrorOccurred')}</div>
</div> </div>
); );
} }
@@ -309,10 +309,10 @@ function ToolBlock({ item, inRoundGroup }: { item: ChatItem; inRoundGroup?: bool
{item.duration && <div className="text-[10px] dg-text-muted">{i18n.t('aiops.durationLabel', { duration: item.duration })}</div>} {item.duration && <div className="text-[10px] dg-text-muted">{i18n.t('aiops.durationLabel', { duration: item.duration })}</div>}
{(item.toolName === 'analyze_output' || looksLikeMarkdown(item.output)) ? ( {(item.toolName === 'analyze_output' || looksLikeMarkdown(item.output)) ? (
<div className="bg-[var(--dg-bg-primary)]/40 rounded px-2 py-1.5 max-h-80 overflow-y-auto"> <div className="bg-[var(--dg-bg-primary)]/40 rounded px-2 py-1.5 max-h-80 overflow-y-auto">
<AgentMarkdown content={item.output || i18n.t('(无输出)')} /> <AgentMarkdown content={item.output || i18n.t('aiops.agentCliNoOutput')} />
</div> </div>
) : ( ) : (
<pre className="text-xs text-[var(--dg-text-secondary)] bg-[var(--dg-bg-primary)]/40 rounded px-2 py-1.5 overflow-x-auto max-h-80 whitespace-pre-wrap">{item.output || i18n.t('(无输出)')}</pre> <pre className="text-xs text-[var(--dg-text-secondary)] bg-[var(--dg-bg-primary)]/40 rounded px-2 py-1.5 overflow-x-auto max-h-80 whitespace-pre-wrap">{item.output || i18n.t('aiops.agentCliNoOutput')}</pre>
)} )}
</> </>
)} )}
@@ -83,15 +83,15 @@ function costTrendFromSlope(slope: number): 'up' | 'down' | 'stable' {
/** 成本优化建议 */ /** 成本优化建议 */
export function costRecommendation(trend: 'up' | 'down' | 'stable', ratio: number): string { export function costRecommendation(trend: 'up' | 'down' | 'stable', ratio: number): string {
if (trend === 'up' && ratio >= 0.85) { if (trend === 'up' && ratio >= 0.85) {
return i18n.t('排查内存泄漏,优化进程内存配置,考虑增加节点'); return i18n.t('aiops.predictMemLeak');
} }
if (trend === 'up') { if (trend === 'up') {
return i18n.t('排查内存泄漏,优化进程内存配置,考虑增加节点'); return i18n.t('aiops.predictMemLeak');
} }
if (trend === 'down') { if (trend === 'down') {
return i18n.t('排查内存泄漏,优化进程内存配置,考虑增加节点'); return i18n.t('aiops.predictMemLeak');
} }
return i18n.t('你是 AIOps 容量规划专家。基于以下服务器资源容量预测数据,给出综合风险评估与可执行的运维建议:'); return i18n.t('aiops.predictutilsts.sa3b5c7d9');
} }
/** 预算风险建议 */ /** 预算风险建议 */
@@ -256,7 +256,7 @@ export function computeCostPredictions(input: ComputeCostPredictionsInput): Comp
const allRatio = const allRatio =
summary.predictedMonthEnd > 0 ? summary.monthCost / summary.predictedMonthEnd : 0; summary.predictedMonthEnd > 0 ? summary.monthCost / summary.predictedMonthEnd : 0;
dimensionForecasts.push({ dimensionForecasts.push({
dimension: i18n.t('清理历史数据和日志,实施数据归档策略,扩容磁盘'), dimension: i18n.t('aiops.predictDiskFull'),
currentMonth: summary.monthCost, currentMonth: summary.monthCost,
predictedMonthEnd: summary.predictedMonthEnd, predictedMonthEnd: summary.predictedMonthEnd,
dailyAvg: summary.dailyAvg, dailyAvg: summary.dailyAvg,
@@ -302,7 +302,7 @@ export function computeCostPredictions(input: ComputeCostPredictionsInput): Comp
const metric = (rule.metric === 'daily' ? 'daily' : 'monthly') as 'daily' | 'monthly'; const metric = (rule.metric === 'daily' ? 'daily' : 'monthly') as 'daily' | 'monthly';
const ruleProvider = String(rule.provider || ''); const ruleProvider = String(rule.provider || '');
const providerLabel = ruleProvider ? PROVIDER_LABELS[ruleProvider] || ruleProvider : i18n.t('24小时后'); const providerLabel = ruleProvider ? PROVIDER_LABELS[ruleProvider] || ruleProvider : i18n.t('aiops.predictIn24h');
let current = 0; let current = 0;
let predicted = 0; let predicted = 0;
@@ -339,7 +339,7 @@ export function computeCostPredictions(input: ComputeCostPredictionsInput): Comp
if (current >= threshold) probability += 25; if (current >= threshold) probability += 25;
probability = Math.max(5, Math.min(92, probability)); probability = Math.max(5, Math.min(92, probability));
let timeframe = metric === 'daily' ? i18n.t('未来24小时') : i18n.t('aiops.predictutilsts.sd4e5f6g7'); let timeframe = metric === 'daily' ? i18n.t('aiops.predictNext24h') : i18n.t('aiops.predictutilsts.sd4e5f6g7');
if (slope > 0.01 && threshold > current) { if (slope > 0.01 && threshold > current) {
const days = (threshold - current) / slope; const days = (threshold - current) / slope;
if (days < 1) timeframe = metric === 'daily' ? i18n.t('aiops.predictutilsts.se5f6g7h8') : i18n.t('aiops.predictutilsts.sf6g7h8i9'); if (days < 1) timeframe = metric === 'daily' ? i18n.t('aiops.predictutilsts.se5f6g7h8') : i18n.t('aiops.predictutilsts.sf6g7h8i9');
@@ -120,16 +120,16 @@ 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('aiops.predictCpuUsage'), exprKey: 'cpu_usage', threshold: 85 },
{ key: 'memory', label: i18n.t('aiops.predictTypes.memUsage'), exprKey: 'memory_usage', threshold: 85 }, { key: 'memory', label: i18n.t('aiops.predictTypes.memUsage'), exprKey: 'memory_usage', threshold: 85 },
{ key: 'disk', label: i18n.t('aiops.predictTypes.diskUsage'), 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> = {
'1h': i18n.t('1小时后'), '1h': i18n.t('aiops.predictIn1h'),
'24h': i18n.t('24小时后'), '24h': i18n.t('aiops.predictIn24h'),
'7d': i18n.t('7天后'), '7d': i18n.t('aiops.predictIn7d'),
'30d': i18n.t('30天后'), '30d': i18n.t('aiops.predictIn30d'),
}; };
export const TIME_RANGE_HOURS: Record<string, number> = { export const TIME_RANGE_HOURS: Record<string, number> = {
@@ -23,7 +23,7 @@ function escapeCsv(s: string): string {
// exportAuditReportCsv 导出会话审计报告 CSV // Logan.Li // exportAuditReportCsv 导出会话审计报告 CSV // Logan.Li
export function exportAuditReportCsv(meta: ReplaySessionMeta, commands: CommandBookmark[]) { export function exportAuditReportCsv(meta: ReplaySessionMeta, commands: CommandBookmark[]) {
const lines: string[] = [ const lines: string[] = [
i18n.t('# 终端会话审计报告'), i18n.t('audit.reportTitle'),
`会话ID,${meta.id}`, `会话ID,${meta.id}`,
`用户,${escapeCsv(meta.username || '')}`, `用户,${escapeCsv(meta.username || '')}`,
`目标主机,${escapeCsv(meta.server_name || '')}`, `目标主机,${escapeCsv(meta.server_name || '')}`,
@@ -33,7 +33,7 @@ export function exportAuditReportCsv(meta: ReplaySessionMeta, commands: CommandB
`时长(秒),${meta.duration || 0}`, `时长(秒),${meta.duration || 0}`,
`录制大小,${meta.record_size || 0}`, `录制大小,${meta.record_size || 0}`,
'', '',
i18n.t('# 终端会话审计报告'), i18n.t('audit.reportTitle'),
]; ];
for (const c of commands) { for (const c of commands) {
lines.push(`${c.label},${escapeCsv(c.command)},${c.highRisk ? '是' : '否'}`); lines.push(`${c.label},${escapeCsv(c.command)},${c.highRisk ? '是' : '否'}`);
@@ -55,7 +55,7 @@ export interface CommandExportRow {
// exportCommandsListCsv 导出命令列表 CSV // Logan.Li // exportCommandsListCsv 导出命令列表 CSV // Logan.Li
export function exportCommandsListCsv(commands: CommandExportRow[]) { export function exportCommandsListCsv(commands: CommandExportRow[]) {
const lines: string[] = [i18n.t('时间,用户,命令,输出大小,会话ID,高危')]; const lines: string[] = [i18n.t('audit.csvHeader')];
for (const c of commands) { for (const c of commands) {
const ts = c.timestamp || c.created_at || ''; const ts = c.timestamp || c.created_at || '';
const highRisk = isHighRiskCommand(c.command || ''); const highRisk = isHighRiskCommand(c.command || '');
+1 -1
View File
@@ -42,7 +42,7 @@ export function CopyButton({
} else { } else {
toast({ toast({
title: i18n.t('monitor.logPanel.copyFailed'), title: i18n.t('monitor.logPanel.copyFailed'),
description: i18n.t('请手动选中内容后复制'), description: i18n.t('common.copyManually'),
variant: 'destructive', variant: 'destructive',
}); });
} }
+71 -5
View File
@@ -513,7 +513,8 @@
"critical": "Critical", "critical": "Critical",
"request.tokenExpired": "Session expired, please log in again", "request.tokenExpired": "Session expired, please log in again",
"days": "days", "days": "days",
"seconds": "sec" "seconds": "sec",
"copyManually": "Please manually select and copy"
}, },
"menu": { "menu": {
"dashboard": "Dashboard", "dashboard": "Dashboard",
@@ -1505,7 +1506,9 @@
"failed": "Failed", "failed": "Failed",
"closed": "Closed" "closed": "Closed"
} }
} },
"reportTitle": "Terminal Session Audit Report",
"csvHeader": "Time,User,Command,Output Size,Session ID,Critical"
}, },
"deploy": { "deploy": {
"pipeline": { "pipeline": {
@@ -4865,7 +4868,44 @@
"selectMetricsHint": "Please check metrics from the left panel", "selectMetricsHint": "Please check metrics from the left panel",
"probeName": "Probe Name", "probeName": "Probe Name",
"execCommand": "Execute Command", "execCommand": "Execute Command",
"refreshing": " · Refreshing..." "refreshing": " · Refreshing...",
"mwMemUsage": "Memory Usage",
"mwSlowQueryRate": "Slow Query Rate",
"mwBlockedClients": "Blocked Clients",
"mwActiveThreads": "Active Threads",
"mwBufferHitRate": "Buffer Pool Hit Rate",
"mwPerCycle": "/cycle",
"mwCluster": "Cluster",
"mwInstanceOffline": "Instance Offline",
"mwHitRateBelow80": "Hit Rate < 80%",
"mwHitRateBelow90": "Hit Rate < 90%",
"mwMemAbove90": "Memory > 90%",
"mwMemAbove80": "Memory > 80%",
"mwKeyEviction": "Key Eviction",
"mwBufferHitBelow90": "Buffer Pool Hit < 90%",
"mwBufferHitBelow95": "Buffer Pool Hit < 95%",
"mwConnAbove90": "Connections > 90% Limit",
"mwConnAbove80": "Connections > 80% Limit",
"mwTooManyThreads": "Too Many Active Threads",
"siteRecentHour": "Last 1 hour",
"siteRecent24h": "Last 24 hours",
"siteRecent7d": "Last 7 days",
"siteTargetUrl": "Target URL",
"siteHttpHint": "Web request (supports http/https), detects status code and response time",
"siteSslAddr": "SSL Address",
"siteSslPlaceholder": "example.com:443 or https://example.com",
"siteSslHint": "TLS handshake certificate check (supports host:port / https:// domain, no HTTP request)",
"siteHostPort": "Host:Port",
"siteTcpPlaceholder": "192.168.1.1:8080 or example.com:443",
"siteTcpHint": "TCP Port Connectivity",
"siteHostAddr": "Host Address",
"sitePingPlaceholder": "192.168.1.1 or example.com",
"sitePingHint": "ICMP connectivity and latency (default 2 min/interval)",
"siteServerError": "Server Error",
"siteClientError": "Client/Auth Error",
"siteRequestFailed": "Request Failed",
"siteRequestTimeout": "Request Timeout",
"siteConnFailed": "Connection Failed"
}, },
"alertRuleDialog2": { "alertRuleDialog2": {
"editRule": "Edit Alert Rule", "editRule": "Edit Alert Rule",
@@ -6806,7 +6846,11 @@
"deleteRule": "Delete Rule", "deleteRule": "Delete Rule",
"thresholdTriggered": "Alert threshold triggered", "thresholdTriggered": "Alert threshold triggered",
"noResolved": "No resolved alerts", "noResolved": "No resolved alerts",
"noActive": "No active alerts, all clear" "noActive": "No active alerts, all clear",
"metricSwap": "Swap Usage",
"metricLoad1": "1-min Load Average",
"metricInode": "Inode Usage",
"metricTcpEst": "TCP Established"
}, },
"monitor": { "monitor": {
"loadHostFailed": "Failed to load host list", "loadHostFailed": "Failed to load host list",
@@ -7809,7 +7853,17 @@
"predictTypes": { "predictTypes": {
"memUsage": "Memory Usage", "memUsage": "Memory Usage",
"diskUsage": "Disk Usage" "diskUsage": "Disk Usage"
} },
"predictMemLeak": "Investigate memory leaks, optimize process memory config, consider adding nodes",
"predictDiskFull": "Clean up historical data and logs, implement data archiving, expand disk",
"predictIn24h": "In 24 hours",
"predictNext24h": "Next 24 hours",
"predictCpuUsage": "CPU Usage",
"predictIn1h": "In 1 hour",
"predictIn7d": "In 7 days",
"predictIn30d": "In 30 days",
"agentCliErrorOccurred": "Error occurred",
"agentCliNoOutput": "(no output)"
}, },
"breadcrumb": { "breadcrumb": {
"dashboard": "Dashboard", "dashboard": "Dashboard",
@@ -7936,5 +7990,17 @@
"quickQuery": "Quick Query", "quickQuery": "Quick Query",
"quickQueryHint": "{{engine}} common query templates, click to fill", "quickQueryHint": "{{engine}} common query templates, click to fill",
"entries": "entries" "entries": "entries"
},
"theme": {
"system": "Follow System",
"deepBlue": "Deep Ocean Blue",
"brightWhite": "Bright White",
"brightWhiteDesc": "Enterprise console clean light theme",
"oneDarkDesc": "Atom warm gray IDE, purple-green syntax",
"draculaDesc": "Classic purple-pink dark, high contrast",
"tokyoNightDesc": "Tokyo neon deep blue-purple, VS Code popular",
"catppuccinDesc": "Macaron soft dark, eye-friendly",
"mainstream": "Mainstream",
"developer": "Developer"
} }
} }
+71 -5
View File
@@ -513,7 +513,8 @@
"critical": "严重", "critical": "严重",
"request.tokenExpired": "登录已过期,请重新登录", "request.tokenExpired": "登录已过期,请重新登录",
"days": "天", "days": "天",
"seconds": "秒" "seconds": "秒",
"copyManually": "请手动选中内容后复制"
}, },
"menu": { "menu": {
"dashboard": "仪表盘", "dashboard": "仪表盘",
@@ -1505,7 +1506,9 @@
"failed": "失败", "failed": "失败",
"closed": "已关闭" "closed": "已关闭"
} }
} },
"reportTitle": "终端会话审计报告",
"csvHeader": "时间,用户,命令,输出大小,会话ID,高危"
}, },
"deploy": { "deploy": {
"pipeline": { "pipeline": {
@@ -4865,7 +4868,44 @@
"selectMetricsHint": "请从左侧勾选指标", "selectMetricsHint": "请从左侧勾选指标",
"probeName": "探针名称", "probeName": "探针名称",
"execCommand": "执行命令", "execCommand": "执行命令",
"refreshing": " · 刷新中..." "refreshing": " · 刷新中...",
"mwMemUsage": "内存占用",
"mwSlowQueryRate": "慢查询速率",
"mwBlockedClients": "阻塞客户端",
"mwActiveThreads": "活跃线程",
"mwBufferHitRate": "缓冲池命中率",
"mwPerCycle": "次/周期",
"mwCluster": "集群",
"mwInstanceOffline": "实例离线",
"mwHitRateBelow80": "命中率 < 80%",
"mwHitRateBelow90": "命中率 < 90%",
"mwMemAbove90": "内存 > 90%",
"mwMemAbove80": "内存 > 80%",
"mwKeyEviction": "Key 逐出",
"mwBufferHitBelow90": "缓冲池命中 < 90%",
"mwBufferHitBelow95": "缓冲池命中 < 95%",
"mwConnAbove90": "连接 > 90% 上限",
"mwConnAbove80": "连接 > 80% 上限",
"mwTooManyThreads": "活跃线程偏多",
"siteRecentHour": "近 1 小时",
"siteRecent24h": "近 24 小时",
"siteRecent7d": "近 7 天",
"siteTargetUrl": "目标 URL",
"siteHttpHint": "Web 请求(支持 http/https),检测状态码与响应时间",
"siteSslAddr": "SSL 地址",
"siteSslPlaceholder": "example.com:443 或 https://example.com",
"siteSslHint": "TLS 握手检测证书(支持 host:port / https:// 域名,不发起 HTTP 请求)",
"siteHostPort": "主机:端口",
"siteTcpPlaceholder": "192.168.1.1:8080 或 example.com:443",
"siteTcpHint": "TCP 端口连通性",
"siteHostAddr": "主机地址",
"sitePingPlaceholder": "192.168.1.1 或 example.com",
"sitePingHint": "ICMP 连通性与延迟(默认 2 分钟/次)",
"siteServerError": "服务端错误",
"siteClientError": "客户端/鉴权错误",
"siteRequestFailed": "请求失败",
"siteRequestTimeout": "请求超时",
"siteConnFailed": "连接失败"
}, },
"alertRuleDialog2": { "alertRuleDialog2": {
"editRule": "编辑告警规则", "editRule": "编辑告警规则",
@@ -6806,7 +6846,11 @@
"deleteRule": "删除规则", "deleteRule": "删除规则",
"thresholdTriggered": "触发告警阈值", "thresholdTriggered": "触发告警阈值",
"noResolved": "暂无已恢复的告警", "noResolved": "暂无已恢复的告警",
"noActive": "当前无活动告警,一切正常" "noActive": "当前无活动告警,一切正常",
"metricSwap": "Swap 使用率",
"metricLoad1": "1分钟负载",
"metricInode": "Inode 使用率",
"metricTcpEst": "TCP 已建立"
}, },
"monitor": { "monitor": {
"loadHostFailed": "加载主机列表失败", "loadHostFailed": "加载主机列表失败",
@@ -7809,7 +7853,17 @@
"predictTypes": { "predictTypes": {
"memUsage": "内存使用率", "memUsage": "内存使用率",
"diskUsage": "磁盘使用率" "diskUsage": "磁盘使用率"
} },
"predictMemLeak": "排查内存泄漏,优化进程内存配置,考虑增加节点",
"predictDiskFull": "清理历史数据和日志,实施数据归档策略,扩容磁盘",
"predictIn24h": "24小时后",
"predictNext24h": "未来24小时",
"predictCpuUsage": "CPU使用率",
"predictIn1h": "1小时后",
"predictIn7d": "7天后",
"predictIn30d": "30天后",
"agentCliErrorOccurred": "发生错误",
"agentCliNoOutput": "(无输出)"
}, },
"breadcrumb": { "breadcrumb": {
"dashboard": "仪表盘", "dashboard": "仪表盘",
@@ -7936,5 +7990,17 @@
"quickQuery": "快捷查询", "quickQuery": "快捷查询",
"quickQueryHint": "{{engine}} 常用查询模板,点击快速填充", "quickQueryHint": "{{engine}} 常用查询模板,点击快速填充",
"entries": "条" "entries": "条"
},
"theme": {
"system": "跟随系统",
"deepBlue": "深海蓝",
"brightWhite": "明亮白",
"brightWhiteDesc": "企业控制台清爽浅色",
"oneDarkDesc": "Atom 暖灰 IDE,紫绿语法渐变",
"draculaDesc": "经典紫粉暗色,极高辨识度",
"tokyoNightDesc": "东京霓虹深蓝紫,VS Code 热门",
"catppuccinDesc": "马卡龙柔和暗色,护眼",
"mainstream": "主流",
"developer": "开发者"
} }
} }
@@ -59,20 +59,20 @@ export const MYSQL_PRIMARY_KEYS = ['mysql_qps', 'mysql_connections', 'mysql_buff
export const REDIS_CHARTS: MwChartDef[] = [ export const REDIS_CHARTS: MwChartDef[] = [
{ key: 'redis_qps', label: 'QPS', color: 'var(--dg-blue)', unit: 'ops/s' }, { key: 'redis_qps', label: 'QPS', color: 'var(--dg-blue)', unit: 'ops/s' },
{ key: 'redis_used_memory', label: i18n.t('内存占用'), color: 'var(--dg-red)', formatter: fmtMemory }, { key: 'redis_used_memory', label: i18n.t('monitor.lbl.mwMemUsage'), color: 'var(--dg-red)', formatter: fmtMemory },
{ key: 'redis_hit_rate', label: i18n.t('monitor.lbl.hitRate'), color: 'var(--dg-green)', percent: true }, { key: 'redis_hit_rate', label: i18n.t('monitor.lbl.hitRate'), color: 'var(--dg-green)', percent: true },
{ key: 'redis_connected_clients', label: i18n.t('kubernetes.topology.connections'), color: 'var(--dg-cyan)' }, { key: 'redis_connected_clients', label: i18n.t('kubernetes.topology.connections'), color: 'var(--dg-cyan)' },
{ key: 'redis_db_size', label: i18n.t('慢查询速率'), color: 'var(--dg-purple)' }, { key: 'redis_db_size', label: i18n.t('monitor.lbl.mwSlowQueryRate'), color: 'var(--dg-purple)' },
{ key: 'redis_blocked_clients', label: i18n.t('阻塞客户端'), color: 'var(--dg-orange)' }, { key: 'redis_blocked_clients', label: i18n.t('monitor.lbl.mwBlockedClients'), color: 'var(--dg-orange)' },
]; ];
export const MYSQL_CHARTS: MwChartDef[] = [ export const MYSQL_CHARTS: MwChartDef[] = [
{ key: 'mysql_qps', label: 'QPS', color: 'var(--dg-blue)', unit: 'q/s' }, { key: 'mysql_qps', label: 'QPS', color: 'var(--dg-blue)', unit: 'q/s' },
{ key: 'mysql_connections', label: i18n.t('kubernetes.topology.connections'), color: 'var(--dg-cyan)' }, { key: 'mysql_connections', label: i18n.t('kubernetes.topology.connections'), color: 'var(--dg-cyan)' },
{ key: 'mysql_threads_running', label: i18n.t('活跃线程'), color: 'var(--dg-purple)' }, { key: 'mysql_threads_running', label: i18n.t('monitor.lbl.mwActiveThreads'), color: 'var(--dg-purple)' },
{ key: 'mysql_buffer_hit_rate', label: i18n.t('缓冲池命中率'), color: 'var(--dg-green)', percent: true }, { key: 'mysql_buffer_hit_rate', label: i18n.t('monitor.lbl.mwBufferHitRate'), color: 'var(--dg-green)', percent: true },
{ key: 'mysql_slow_queries', label: i18n.t('慢查询速率'), color: 'var(--dg-orange)', unit: i18n.t('次/周期'), counterDelta: true }, { key: 'mysql_slow_queries', label: i18n.t('monitor.lbl.mwSlowQueryRate'), color: 'var(--dg-orange)', unit: i18n.t('monitor.lbl.mwPerCycle'), counterDelta: true },
{ key: 'mysql_bytes_sent', label: i18n.t('缓冲池命中率'), color: 'var(--dg-amber)', formatter: fmtMemory }, { key: 'mysql_bytes_sent', label: i18n.t('monitor.lbl.mwBufferHitRate'), color: 'var(--dg-amber)', formatter: fmtMemory },
]; ];
export function fmtMemory(bytes: number): string { export function fmtMemory(bytes: number): string {
@@ -139,7 +139,7 @@ export function statusBg(status: string): string {
} }
export function redisModeLabel(mode?: string): string { export function redisModeLabel(mode?: string): string {
return mode === 'cluster' ? i18n.t('k8s.topology.typeCluster') : i18n.t('集群'); return mode === 'cluster' ? i18n.t('k8s.topology.typeCluster') : i18n.t('monitor.lbl.mwCluster');
} }
export function redisMemoryPct(metrics: Record<string, number>): number | null { export function redisMemoryPct(metrics: Record<string, number>): number | null {
@@ -209,27 +209,27 @@ export function evalMwHealth(target: MwTarget): MwHealthIssue[] {
const m = target.latest_metrics || {}; const m = target.latest_metrics || {};
const issues: MwHealthIssue[] = []; const issues: MwHealthIssue[] = [];
if (target.status !== 'online') { if (target.status !== 'online') {
issues.push({ level: 'crit', message: i18n.t('实例离线') }); issues.push({ level: 'crit', message: i18n.t('monitor.lbl.mwInstanceOffline') });
return issues; return issues;
} }
if (target.description === 'redis') { if (target.description === 'redis') {
if (m.redis_hit_rate > 0 && m.redis_hit_rate < 0.8) issues.push({ level: 'crit', message: i18n.t('命中率 < 80%') }); if (m.redis_hit_rate > 0 && m.redis_hit_rate < 0.8) issues.push({ level: 'crit', message: i18n.t('monitor.lbl.mwHitRateBelow80') });
else if (m.redis_hit_rate > 0 && m.redis_hit_rate < 0.9) issues.push({ level: 'warn', message: i18n.t('命中率 < 90%') }); else if (m.redis_hit_rate > 0 && m.redis_hit_rate < 0.9) issues.push({ level: 'warn', message: i18n.t('monitor.lbl.mwHitRateBelow90') });
const memPct = redisMemoryPct(m); const memPct = redisMemoryPct(m);
if (memPct != null && memPct > 90) issues.push({ level: 'crit', message: i18n.t('内存 > 90%') }); if (memPct != null && memPct > 90) issues.push({ level: 'crit', message: i18n.t('monitor.lbl.mwMemAbove90') });
else if (memPct != null && memPct > 80) issues.push({ level: 'warn', message: i18n.t('内存 > 80%') }); else if (memPct != null && memPct > 80) issues.push({ level: 'warn', message: i18n.t('monitor.lbl.mwMemAbove80') });
if (m.redis_blocked_clients > 0) issues.push({ level: 'warn', message: i18n.t('阻塞客户端') }); if (m.redis_blocked_clients > 0) issues.push({ level: 'warn', message: i18n.t('monitor.lbl.mwBlockedClients') });
if (m.redis_evicted_keys > 0) issues.push({ level: 'warn', message: i18n.t('Key 逐出') }); if (m.redis_evicted_keys > 0) issues.push({ level: 'warn', message: i18n.t('monitor.lbl.mwKeyEviction') });
} else if (target.description === 'mysql') { } else if (target.description === 'mysql') {
if (m.mysql_buffer_hit_rate > 0 && m.mysql_buffer_hit_rate < 0.9) { if (m.mysql_buffer_hit_rate > 0 && m.mysql_buffer_hit_rate < 0.9) {
issues.push({ level: 'crit', message: i18n.t('缓冲池命中 < 90%') }); issues.push({ level: 'crit', message: i18n.t('monitor.lbl.mwBufferHitBelow90') });
} else if (m.mysql_buffer_hit_rate > 0 && m.mysql_buffer_hit_rate < 0.95) { } else if (m.mysql_buffer_hit_rate > 0 && m.mysql_buffer_hit_rate < 0.95) {
issues.push({ level: 'warn', message: i18n.t('缓冲池命中 < 95%') }); issues.push({ level: 'warn', message: i18n.t('monitor.lbl.mwBufferHitBelow95') });
} }
const connPct = mysqlConnPct(m); const connPct = mysqlConnPct(m);
if (connPct != null && connPct > 90) issues.push({ level: 'crit', message: i18n.t('连接 > 90% 上限') }); if (connPct != null && connPct > 90) issues.push({ level: 'crit', message: i18n.t('monitor.lbl.mwConnAbove90') });
else if (connPct != null && connPct > 80) issues.push({ level: 'warn', message: i18n.t('连接 > 80% 上限') }); else if (connPct != null && connPct > 80) issues.push({ level: 'warn', message: i18n.t('monitor.lbl.mwConnAbove80') });
if (m.mysql_threads_running > 20) issues.push({ level: 'warn', message: i18n.t('活跃线程偏多') }); if (m.mysql_threads_running > 20) issues.push({ level: 'warn', message: i18n.t('monitor.lbl.mwTooManyThreads') });
} }
return issues; return issues;
} }
@@ -57,10 +57,10 @@ export const SITE_TIME_RANGE_HOURS: Record<SiteTimeRange, number> = {
}; };
export const SITE_TIME_RANGE_LABELS: Record<SiteTimeRange, string> = { export const SITE_TIME_RANGE_LABELS: Record<SiteTimeRange, string> = {
'1h': i18n.t('近 1 小时'), '1h': i18n.t('monitor.lbl.siteRecentHour'),
'6h': i18n.t('monitor.siteProtocol.rangeDefault'), '6h': i18n.t('monitor.siteProtocol.rangeDefault'),
'24h': i18n.t('近 24 小时'), '24h': i18n.t('monitor.lbl.siteRecent24h'),
'7d': i18n.t('近 7 天'), '7d': i18n.t('monitor.lbl.siteRecent7d'),
}; };
/** 各协议推荐默认拨测间隔(秒)// Logan.Li */ /** 各协议推荐默认拨测间隔(秒)// Logan.Li */
@@ -86,11 +86,11 @@ export function defaultProbeFrequency(type: ProbeType): number {
} }
export const probeTypeMeta: Record<ProbeType, { label: string; placeholder: string; hint: string }> = { export const probeTypeMeta: Record<ProbeType, { label: string; placeholder: string; hint: string }> = {
HTTP: { label: i18n.t('目标 URL'), placeholder: 'https://example.com/health', hint: i18n.t('Web 请求(支持 http/https),检测状态码与响应时间') }, HTTP: { label: i18n.t('monitor.lbl.siteTargetUrl'), placeholder: 'https://example.com/health', hint: i18n.t('monitor.lbl.siteHttpHint') },
SSL: { label: i18n.t('SSL 地址'), placeholder: i18n.t('example.com:443 或 https://example.com'), hint: i18n.t('TLS 握手检测证书(支持 host:port / https:// 域名,不发起 HTTP 请求)') }, SSL: { label: i18n.t('monitor.lbl.siteSslAddr'), placeholder: i18n.t('monitor.lbl.siteSslPlaceholder'), hint: i18n.t('monitor.lbl.siteSslHint') },
TCP: { label: i18n.t('主机:端口'), placeholder: i18n.t('192.168.1.1:8080 或 example.com:443'), hint: i18n.t('TCP 端口连通性') }, TCP: { label: i18n.t('monitor.lbl.siteHostPort'), placeholder: i18n.t('monitor.lbl.siteTcpPlaceholder'), hint: i18n.t('monitor.lbl.siteTcpHint') },
DNS: { label: i18n.t('cloud.domainName'), placeholder: 'example.com', hint: i18n.t('monitor.helpersts.sd3fd8f1d') }, DNS: { label: i18n.t('cloud.domainName'), placeholder: 'example.com', hint: i18n.t('monitor.helpersts.sd3fd8f1d') },
PING: { label: i18n.t('主机地址'), placeholder: i18n.t('192.168.1.1 或 example.com'), hint: i18n.t('ICMP 连通性与延迟(默认 2 分钟/次)') }, PING: { label: i18n.t('monitor.lbl.siteHostAddr'), placeholder: i18n.t('monitor.lbl.sitePingPlaceholder'), hint: i18n.t('monitor.lbl.sitePingHint') },
}; };
export function probeFrequencyHint(type: ProbeType): string { export function probeFrequencyHint(type: ProbeType): string {
@@ -93,16 +93,16 @@ export function analyzeHttpResults(results: SiteResult[], task: SiteTask): HttpO
} }
if (code >= 500) { if (code >= 500) {
err5xx++; err5xx++;
pushEvent(events, r, `HTTP ${code}`, 'error', r.error_message || i18n.t('服务端错误')); pushEvent(events, r, `HTTP ${code}`, 'error', r.error_message || i18n.t('monitor.lbl.siteServerError'));
} else if (code >= 400) { } else if (code >= 400) {
err4xx++; err4xx++;
pushEvent(events, r, `HTTP ${code}`, 'warn', r.error_message || i18n.t('客户端/鉴权错误')); pushEvent(events, r, `HTTP ${code}`, 'warn', r.error_message || i18n.t('monitor.lbl.siteClientError'));
} else if (r.status === 'timeout') { } else if (r.status === 'timeout') {
otherErr++; otherErr++;
pushEvent(events, r, i18n.t('common.timeoutError'), 'error', r.error_message); pushEvent(events, r, i18n.t('common.timeoutError'), 'error', r.error_message);
} else { } else {
otherErr++; otherErr++;
pushEvent(events, r, code > 0 ? `HTTP ${code}` : i18n.t('请求失败'), 'error', r.error_message); pushEvent(events, r, code > 0 ? `HTTP ${code}` : i18n.t('monitor.lbl.siteRequestFailed'), 'error', r.error_message);
} }
} }
const latest = results[0]; const latest = results[0];
@@ -128,7 +128,7 @@ export function analyzeSslResults(results: SiteResult[], task: SiteTask): SslObs
const events: ProbeEvent[] = []; const events: ProbeEvent[] = [];
for (const r of results) { for (const r of results) {
if (r.status !== 'success') { if (r.status !== 'success') {
pushEvent(events, r, i18n.t('请求超时'), 'error', r.error_message); pushEvent(events, r, i18n.t('monitor.lbl.siteRequestTimeout'), 'error', r.error_message);
} else if (r.ssl_days_remaining != null && r.ssl_days_remaining < 30) { } else if (r.ssl_days_remaining != null && r.ssl_days_remaining < 30) {
pushEvent(events, r, `证书剩余 ${r.ssl_days_remaining} 天`, r.ssl_days_remaining <= 7 ? 'error' : 'warn', expiry); pushEvent(events, r, `证书剩余 ${r.ssl_days_remaining} 天`, r.ssl_days_remaining <= 7 ? 'error' : 'warn', expiry);
} }
@@ -186,7 +186,7 @@ export function analyzeTcpResults(results: SiteResult[]): ConnectivityObsSummary
const failed = r.status !== 'success'; const failed = r.status !== 'success';
if (failed) { if (failed) {
lastFailAt = r.checked_at; lastFailAt = r.checked_at;
pushEvent(events, r, i18n.t('连接失败'), 'error', r.error_message || i18n.t('aiops.llmTestFailed')); pushEvent(events, r, i18n.t('monitor.lbl.siteConnFailed'), 'error', r.error_message || i18n.t('aiops.llmTestFailed'));
} }
return { time: fmtTime(r.checked_at).slice(-5), up: failed ? 0 : 100, failed }; return { time: fmtTime(r.checked_at).slice(-5), up: failed ? 0 : 100, failed };
}); });
+9 -9
View File
@@ -32,22 +32,22 @@ export interface ThemeGroup {
/** 主流主题 — 默认推荐 */ /** 主流主题 — 默认推荐 */
const MAINSTREAM_THEMES: ThemeOption[] = [ const MAINSTREAM_THEMES: ThemeOption[] = [
{ id: 'system', name: i18n.t('common.lightDark'), description: i18n.t('跟随系统'), isDark: false, primaryColor: 'linear-gradient(135deg, #2563eb 50%, #0d1117 50%)' }, { id: 'system', name: i18n.t('common.lightDark'), description: i18n.t('theme.system'), isDark: false, primaryColor: 'linear-gradient(135deg, #2563eb 50%, #0d1117 50%)' },
{ id: 'ocean-deep', name: i18n.t('深海蓝'), description: i18n.t('深海蓝'), isDark: true, primaryColor: '#0d1117' }, { id: 'ocean-deep', name: i18n.t('theme.deepBlue'), description: i18n.t('theme.deepBlue'), isDark: true, primaryColor: '#0d1117' },
{ id: 'light-professional', name: i18n.t('明亮白'), description: i18n.t('企业控制台清爽浅色'), isDark: false, primaryColor: '#2563eb' }, { id: 'light-professional', name: i18n.t('theme.brightWhite'), description: i18n.t('theme.brightWhiteDesc'), isDark: false, primaryColor: '#2563eb' },
]; ];
/** 开发者主题 — 高辨识度扩展 */ /** 开发者主题 — 高辨识度扩展 */
const DEVELOPER_THEMES: ThemeOption[] = [ const DEVELOPER_THEMES: ThemeOption[] = [
{ id: 'one-dark', name: 'One Dark', description: i18n.t('Atom 暖灰 IDE,紫绿语法渐变'), isDark: true, primaryColor: '#c678dd' }, { id: 'one-dark', name: 'One Dark', description: i18n.t('theme.oneDarkDesc'), isDark: true, primaryColor: '#c678dd' },
{ id: 'dracula', name: 'Dracula', description: i18n.t('经典紫粉暗色,极高辨识度'), isDark: true, primaryColor: '#bd93f9' }, { id: 'dracula', name: 'Dracula', description: i18n.t('theme.draculaDesc'), isDark: true, primaryColor: '#bd93f9' },
{ id: 'tokyo-night', name: 'Tokyo Night', description: i18n.t('东京霓虹深蓝紫,VS Code 热门'), isDark: true, primaryColor: '#7aa2f7' }, { id: 'tokyo-night', name: 'Tokyo Night', description: i18n.t('theme.tokyoNightDesc'), isDark: true, primaryColor: '#7aa2f7' },
{ id: 'catppuccin', name: 'Catppuccin', description: i18n.t('马卡龙柔和暗色,护眼'), isDark: true, primaryColor: '#89b4fa' }, { id: 'catppuccin', name: 'Catppuccin', description: i18n.t('theme.catppuccinDesc'), isDark: true, primaryColor: '#89b4fa' },
]; ];
export const THEME_GROUPS: ThemeGroup[] = [ export const THEME_GROUPS: ThemeGroup[] = [
{ label: i18n.t('主流'), options: MAINSTREAM_THEMES }, { label: i18n.t('theme.mainstream'), options: MAINSTREAM_THEMES },
{ label: i18n.t('开发者'), options: DEVELOPER_THEMES }, { label: i18n.t('theme.developer'), options: DEVELOPER_THEMES },
]; ];
export const THEME_OPTIONS: ThemeOption[] = [ export const THEME_OPTIONS: ThemeOption[] = [