refactor(i18n): U41 残余硬编码中文清零 — 工具函数+审计RDP标签+AIOps分析+Monitor残余+审批选项

18 文件 ~35 处 UI 硬编码中文→t()/i18n.t() 调用:
- agent-utils.ts timeAgo() 秒前/分钟前/小时前
- utils.ts formatDistanceToNow() 分钟前/小时前/天前
- request.ts 响应解析失败错误消息
- ActiveSessionsPanel/HistorySessionsPanel RDP 在线/历史会话标签
- MetricsExplorer 清除/已选/最近1小时
- HostMonitor 当前主机 subtitle
- AIAnalysisResult 证据链 (新增 useTranslation hook)
- capacity-predict.ts 容量预测摘要模板
- ApprovalResourcePage 阿里云/腾讯云/华为云
- ApprovalPermissionPage 堡垒机
- site-monitor/helpers.ts 频率/时间/证书天数格式化
- site-monitor/analytics.ts 证书剩余/检查失败/延迟波动事件
- middleware-monitor MwTargetChartsPanel/MwTrendChart/helpers.ts
- snmp-monitor SnmpMetricTrendChart/SnmpDeviceChartsPanel
- host-monitor/utils.ts 图表点数标签 + PortPolicyDialog 系统进程说明

新增 34 翻译键 (common.duration.secAgo/hourAgo/dayAgo + common.selected/
responseParseError/rdpActiveSessions/rdpHistorySessions/rdpSessionCount/
evidenceChain/last1Hour/currentHost + monitor.lbl 10键 + monitor.siteMonitor
8键 + approval.provider 3键 + approval.permType 1键 +
monitor.portPolicyDialog.systemProcessDesc + aiops.capacity.summary)

zh/en 7813→7847 对齐; tsc -b=0, npm build✓ (17.31s), ESLint=0/43/any=0,
go build=0, 17/17 测试通过
This commit is contained in:
attacker committed 2026-07-13 22:21:47 +08:00
1 parent 4e2ba77aa8
commit a7838aaf65
23 files changed
+140 -57

No files matched your search

+1
View File
@@ -528,6 +528,7 @@ services/api.ts (1324L)
| U38 | i18n phantom CTK 清理 — 模板字面量 + JSX 属性残余中文清零(13 文件 16 处→0: LogsHeader "行"suffix + analyzerHelpers "当前值" + DeploymentEditStorage 2 toast description + WorkloadViewPage 2 toast description + ServerFileBrowser confirm + LogDatasourcePage toast + LogAlertsPanel confirm + DeployTargetFields JSX hint + UserGroupsManager 3 处 confirm/desc + BuildNodesManager JSX "(可选)" + useLdapManagement confirm — 新增 14 语义化翻译键 (common.lineSuffix/currentValue/memberCount/fillIfMultiContainer/confirmDeleteType + k8s.editStorage.*Desc + k8s.workloadView.restartSentDesc + k8s.workloads.scaleSuccessDesc + aiops.reachable + monitor.deleteRuleConfirmDesc + deploy.rbac.deleteGroupDesc + pipeline.removeMemberDesc + system.ldapMgmt.deleteEntryDesc),zh/en 7737→7751 +14 对齐;tsc -b=0, npm build✓ (16.86s), ESLint=0/43/any=0, go build=0, 17/17 测试通过) | P3 | **done** |
| U39 | 残余 JSX 属性/模板字面量中文清零(10 文件 14 处→0: DualLineChart subtitle 接收/发送 + NetworkTab 5处 unit="个" + FDUsageCard subtitle 当前 + NamespaceOverviewPage/NamespaceManagementPage subtitle + CreateHPADialog title+text 命名空间 + LogFieldSidebar title 个值 + LiveMonitorDialog title 全屏/退出全屏 + ExecutionHistoryDialog title 成功/失败 + LdapSettingsTab className 成功检查→ldapTestSuccess boolean 状态替代字符串匹配 — 新增 11 翻译键, zh/en 7751→7762 对齐;tsc -b=0, npm build✓ (16.94s), ESLint=0/0/any=0, go build=0, 17/17 测试通过) | P3 | **done** |
| U40 | Monitor + Agent 子组件残余硬编码中文 i18n 补全(14 文件 ~50 处→0: ContextSwitchCard CTX/IRQ/次 + ProcessTable agentWaitData/cpuUsagePercent + FDUsageCard fdHandles + PortMonitorTab 黑名单/白名单/策略配置/暂无端口数据 + PortPolicyDialog 白名单全量上报/黑名单仅上报选中/端口列表/逗号分隔/白名单无需配置/已输入端口 + HostTimeRangePicker 时间范围/自定义/取消/应用 + DualLineChart 接收/发送 (i18n.t→useTranslation hook) + ZoomableAreaChart 暂无数据 + SiteResultsPanel 选择任务/次/可用率/立即拨测/SSL证书预警/到期/剩余/阈值/天 + MiddlewareMonitor Redis单机集群/上次更新/添加平台默认/暂无实例 + SiteMonitor subtitle + SNMPMonitor 标题/采集间隔/IP地址/OID模板/SNMP版本/确认/暂无设备/采集指标/暂无指标 + HealthProbeSection TCP目标地址/TCP端口 + AgentAlerts Agent告警/刷新/未恢复/已恢复/恢复/新建规则/创建第一条规则 — 新增 monitor.lbl 46 语义化翻译键 + agent.alerts 6 键, zh/en 7762→7814 对齐;tsc -b=0, npm build✓ (16.88s), ESLint=0/43/any=0, go build=0, 17/17 测试通过) | P3 | **done** |
| U41 | i18n 残余硬编码中文清零 — 工具函数 + 审计RDP标签 + AIOps分析 + Monitor残余 + 审批选项(18 文件 ~35 处→0: agent-utils.ts timeAgo 秒前/分钟前/小时前 + utils.ts formatDistanceToNow 分钟前/小时前/天前 + request.ts 响应解析失败 + ActiveSessionsPanel RDP在线会话 + HistorySessionsPanel RDP历史会话/RDP会话共N条 + MetricsExplorer 清除/已选/最近1小时 + HostMonitor 当前主机 + AIAnalysisResult 证据链(useTranslation hook) + capacity-predict.ts 容量预测摘要模板 + ApprovalResourcePage 阿里云/腾讯云/华为云 + ApprovalPermissionPage 堡垒机 + site-monitor/helpers.ts 推荐频率/秒/分钟/小时/天 + site-monitor/analytics.ts 证书剩余/失败/延迟波动 + middleware-monitor/MwTargetChartsPanel 选择实例/已用/集群节点/慢查询 + MwTrendChart 暂无趋势数据 + middleware-monitor/helpers.ts 相对时间 + snmp-monitor/SnmpMetricTrendChart 暂无趋势数据等待采集 + SnmpDeviceChartsPanel 点击设备/可观测/刷新/暂无指标 + host-monitor/utils.ts 图表点数 + PortPolicyDialog 系统进程端口说明 — 新增 common.duration.secAgo/hourAgo/dayAgo + common.selected/responseParseError/rdpActiveSessions/rdpHistorySessions/rdpSessionCount/evidenceChain/last1Hour/currentHost + monitor.lbl 10键 + monitor.siteMonitor 8键 + approval.provider 3键 + approval.permType 1键 + monitor.portPolicyDialog.systemProcessDesc + aiops.capacity.summary 共 34 翻译键, zh/en 7813→7847 对齐;tsc -b=0, npm build✓ (17.31s), ESLint=0/43/any=0, go build=0, 17/17 测试通过) | P3 | **done** |
| U37 | 全量 JSX 文本节点硬编码中文清零(65 文件 151 处→0: MiddlewareMonitor/SiteMonitor/SNMPMonitor/HealthProbeSection/ContextSwitchCard/ProcessTable/PortMonitorTab/MwTargetChartsPanel/MiddlewareDialog/ChartRangeHint/SnmpDeviceChartsPanel/AlertRulesTab/LogClusterPanel/LogDatasourcePage/LogQueryToolbar/LogAlertsPanel/PrometheusDatasourceManager/PodMetricsPage/CapacityPage/NamespaceManagementPage/NamespaceResourcePage/DeploymentEditService/EventsPage/DeploymentDetail/ClusterOverviewPage/DeploymentEditContainers/DeploymentEditStorage/WorkloadViewPage/BottomStatusBar/UpdateStrategySelectorCards/PodInstancesTable/shared-components/UpdateStrategyCard/TolerationsCard/HostSettingsCard/IngressListItem/ServiceListItem/ServiceCreateForm/IngressCreateForm/CreateHPADialog/DeployCredentials/DeployRBAC/UserGroupsManager/BuildNodesManager/DeployTargetFields/PipelineCreateDialog/CredFormDialog/WorkspaceTermDialog/AgentAlerts/AgentRuleDialog/LiveMonitorDialog/RoleFormDialog/CronJobViewDialog/TerminalOutput/AIAnalysisResult/AIPredictiveAnalytics/PredictiveCostTab/PredictiveCapacityTab/ActiveSessionsPanel/HistorySessionsPanel/TerminalDialog/ServerFileBrowser/PodFileBrowser/SiteResultsPanel/SiteProtocolPieChart — 151 JSX 文本节点中文全部替换为 t()/i18n.t() 调用;新增 common.* 98 翻译键 (selectedCount/itemsCount/newValue/namespaceLabel/containerLabel/replicasLabel/minReplicas/maxReplicas/createNamespace/noNamespace/unknownResource/exportYaml/loadTrend/loadCapacity/predicted/threshold/hostSettings/addVolume/addMount/createService/createIngress/editAction/updateStrategy/selectAndLabel/historyVersion/fileBrowser/scale/back/createHpa/allNamespaces/certLabel/observable/deviceList/addDevice/addMonitor/collectNow/addTask/addProbe/restoreFull/contextSwitch/processTop/listenPorts/terminalRealtime/auditWebssh/buildTerminal/fileBrowse/newDir/loadDeployment/notLinked/selectedPerms/none/hasLabel/deleteNamespace/deleteNamespaceWarn/allResources/notFoundDeploy/nodeList/fileLabel/addCustom/newInstanceService/newInstanceIngress/totalContainers/workContainers/loadingEllipsis/volumeClaimTemplates/containerTemplate/passwordOptional/updated/page/perPage/clusterMode/totalLogs/countLabel/percentage/queryTime/addDatasource/newPermission/newEnv/envLabel/displayName/newUserGroup/auditTrace/predictValue/confidence/predictMonthEnd/noCapacityData/securityMode/standardRdp/domainLabel/noRdpActive/noRdpHistory/buffer/matched/podLogs/createHpaTitle/password/events/total);37 文件已有 import i18n→i18n.t() 模式, 2 文件新增 import i18n;zh/en 7640→3526 namespace-flat +98 键对齐;tsc -b=0, npm build✓ (17.96s), ESLint=0/43/any=0, go build=0, 17/17 测试通过) | P3 | **done** |
---
@@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Sparkles, Brain, Loader2 } from 'lucide-react';
import { AlertCircle, TrendingUp, CheckCircle, FileText, Terminal, Play } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import type { AlertItem, AIAnalysis } from './analyzerHelpers';
import i18n from '@/i18n';
@@ -15,6 +16,7 @@ interface AIAnalysisResultProps {
}
export function AIAnalysisResult({ selectedAlert, isAnalyzing, analysis, onAutoFix }: AIAnalysisResultProps) {
const { t } = useTranslation();
return (
<Card className="dg-card flex flex-col">
<CardHeader className="border-b border-[var(--dg-border)] pb-3">
@@ -91,7 +93,7 @@ export function AIAnalysisResult({ selectedAlert, isAnalyzing, analysis, onAutoF
<div className="space-y-2">
<h4 className="text-sm font-medium text-[var(--dg-text-secondary)] flex items-center gap-2">
<FileText className="h-4 w-4 dg-text-blue" />
证据链
{t('common.evidenceChain')}
</h4>
<div className="space-y-2">
{analysis.evidence.slice(0, 5).map((item, idx) => (
@@ -178,7 +178,7 @@ export function buildCapacityInsightPrompt(forecasts: CapacityForecast[]): strin
if (forecasts.length === 0) return '';
const lines = forecasts.map((f) => {
const risk = f.daysUntilThreshold >= 999 ? i18n.t('aiops.capacity.noRisk') : i18n.t('aiops.predictutilsts.daysUntilThreshold', { days: f.daysUntilThreshold });
return `- ${f.resource}:当前 ${f.currentUsage}% → 7天后预测 ${f.predictedUsage}%(阈值 ${f.threshold}%),${risk}`;
return i18n.t('aiops.capacity.summary', { resource: f.resource, current: f.currentUsage, predicted: f.predictedUsage, threshold: f.threshold, risk });
});
return [
i18n.t('aiops.capacity.expertPrompt'),
+4 -3
View File
@@ -1,4 +1,5 @@
import type { JsonData } from "@/types/common";
import i18n from "@/i18n";
// Agent 监控相关工具函数(供组件与 API 层共享) // Logan.Li
/** 在线判定:3 分钟内有上报视为在线 */
@@ -15,9 +16,9 @@ export function timeAgo(ts: unknown): string {
const t = new Date(ts as string | number | Date).getTime();
if (Number.isNaN(t)) return '--';
const diff = Math.floor((Date.now() - t) / 1000);
if (diff < 60) return `${diff}秒前`;
if (diff < 3600) return `${Math.floor(diff / 60)}分钟前`;
if (diff < 86400) return `${Math.floor(diff / 3600)}小时前`;
if (diff < 60) return i18n.t('common.duration.secAgo', { count: diff });
if (diff < 3600) return i18n.t('common.duration.minAgo', { count: Math.floor(diff / 60) });
if (diff < 86400) return i18n.t('common.duration.hourAgo', { count: Math.floor(diff / 3600) });
return new Date(ts as string | number | Date).toLocaleString();
}
+3 -3
View File
@@ -18,11 +18,11 @@ export function formatDistanceToNow(timestamp: number): string {
if (seconds < 60) {
return i18n.t('task.cronJob.time.justNow');
} else if (minutes < 60) {
return `${minutes}分钟前`;
return i18n.t('common.duration.minAgo', { count: minutes });
} else if (hours < 24) {
return `${hours}小时前`;
return i18n.t('common.duration.hourAgo', { count: hours });
} else if (days < 30) {
return `${days}天前`;
return i18n.t('common.duration.dayAgo', { count: days });
} else {
return new Date(timestamp).toLocaleDateString();
}
+45 -7
View File
@@ -181,7 +181,10 @@
"minSec": "{{m}}m {{s}}s",
"hour": "{{count}}h",
"hourMin": "{{h}}h {{m}}m",
"minAgo": "{{count}} min ago"
"minAgo": "{{count}} min ago",
"secAgo": "{{count}}s ago",
"hourAgo": "{{count}}h ago",
"dayAgo": "{{count}}d ago"
},
"ebCaughtError": "[ErrorBoundary] Caught error:",
"ebCopyError": "Copy Error",
@@ -623,7 +626,15 @@
"fillIfMultiContainer": "(required for multi-container)",
"confirmDeleteType": "Delete {{type}} \"{{name}}\"?",
"unitCount": "",
"fieldValues": "{{count}} values"
"fieldValues": "{{count}} values",
"selected": "{{count}} selected",
"responseParseError": "Response parse failed",
"rdpActiveSessions": "RDP Active Sessions",
"rdpHistorySessions": "RDP History Sessions",
"rdpSessionCount": "RDP sessions: {{count}}",
"evidenceChain": "Evidence Chain",
"last1Hour": "Last 1 hour",
"currentHost": "Current host: {{host}}"
},
"menu": {
"aiAgentCli": "Agent CLI",
@@ -1478,7 +1489,15 @@
"general": "General Approval"
},
"workflowSubtitle": "Configure multi-level approval workflows for resource, application, and permission requests",
"workflowTitle": "Approval Workflow Config"
"workflowTitle": "Approval Workflow Config",
"provider": {
"aliyun": "Alibaba Cloud",
"tencent": "Tencent Cloud",
"huawei": "Huawei Cloud"
},
"permType": {
"bastion": "Bastion"
}
},
"audit": {
"csvHeader": "Time,User,Command,Output Size,Session ID,Critical",
@@ -4423,7 +4442,16 @@
"snmpVersion": "SNMP Version",
"snmpNoDevice": "No SNMP devices. Click \"Add Device\" to start monitoring.",
"snmpCollectMetrics": "Collected Metrics",
"snmpNoMetrics": "No metrics yet. Please collect first."
"snmpNoMetrics": "No metrics yet. Please collect first.",
"noTrendData": "No trend data",
"noTrendDataWaiting": "No trend data, waiting for collection",
"selectInstanceHint": "Select an instance to view trends and health",
"clickDeviceHint": "Click a device row to view CPU / Memory / Temperature / Fan trends",
"noMetricsHint": "No visualizable metrics, please complete at least one collection",
"usedHint": "Used {{value}}",
"clusterNodesHint": "Cluster {{value}} nodes",
"slowQueriesHint": "Slow queries {{value}}",
"pointCount": "{{min}} — {{max}} · {{count}} pts"
},
"logAlertBell": {
"linked": "Alert Policy Linked",
@@ -4813,7 +4841,8 @@
"neverReport": "Never report",
"filterMode": "Filter Mode",
"title": "Port Monitoring Policy",
"desc": "Configure Agent port reporting filter rules"
"desc": "Configure Agent port reporting filter rules",
"systemProcessDesc": "⚠️ Regardless of mode, the following system process ports {{neverReport}}: sshd, rpcbind, master, systemd, dbus-daemon, rsyslogd, etc. Automatically skipped during collection."
},
"postgresql": "PostgreSQL",
"prevPage": "Previous",
@@ -5004,7 +5033,15 @@
"probeDesc": "Configure probe tasks by protocol type, the system will automatically probe at intervals",
"taskNotFound": "Probe task does not exist or has been deleted",
"clickToObserve": "Click to view details",
"alerting": "Alerting"
"alerting": "Alerting",
"recommendFreq": "Recommended {{freq}}, min {{minFreq}}",
"seconds": "{{count}}s",
"minutes": "{{count}}min",
"hours": "{{count}}h",
"certDays": "{{count}}d",
"certRemaining": "Certificate remaining {{days}} days",
"checkFailed": "{{label}} failed",
"latencyFluctuation": "{{label}} latency fluctuation"
},
"siteProbe": "Site Probe",
"siteProbeTitle": "Site Probe Tasks",
@@ -7933,7 +7970,8 @@
"expertPrompt": "You are an AIOps capacity planning expert. Based on the following server resource capacity forecast data, provide a comprehensive risk assessment and actionable operations recommendations:",
"resCpu": "Server CPU Compute",
"resMemory": "Server Memory",
"resDisk": "Storage Disk Space"
"resDisk": "Storage Disk Space",
"summary": "- {{resource}}: Current {{current}}% → 7-day forecast {{predicted}}% (threshold {{threshold}}%), {{risk}}"
},
"capacityPlanPrompt": "Based on the following host resource usage, provide cost optimization and capacity planning advice (identify idle/overloaded hosts; suggest scale-down/scale-up)",
"capacityPlanning": "Capacity Planning",
+45 -7
View File
@@ -181,7 +181,10 @@
"minSec": "{{m}}分{{s}}秒",
"hour": "{{count}}时",
"hourMin": "{{h}}时{{m}}分",
"minAgo": "{{count}}分钟前"
"minAgo": "{{count}}分钟前",
"secAgo": "{{count}}秒前",
"hourAgo": "{{count}}小时前",
"dayAgo": "{{count}}天前"
},
"ebCaughtError": "[ErrorBoundary] 捕获到错误:",
"ebCopyError": "复制错误",
@@ -623,7 +626,15 @@
"fillIfMultiContainer": "(多容器时填写)",
"confirmDeleteType": "确认删除 {{type}} \"{{name}}\"?",
"unitCount": "个",
"fieldValues": "{{count}} 个值"
"fieldValues": "{{count}} 个值",
"selected": "{{count}} 已选",
"responseParseError": "响应解析失败",
"rdpActiveSessions": "RDP 在线会话",
"rdpHistorySessions": "RDP 历史会话",
"rdpSessionCount": "RDP 会话共 {{count}} 条",
"evidenceChain": "证据链",
"last1Hour": "最近 1 小时",
"currentHost": "当前主机: {{host}}"
},
"menu": {
"aiAgentCli": "Agent CLI",
@@ -1478,7 +1489,15 @@
"general": "通用审批"
},
"workflowSubtitle": "为资源申请、应用申请、权限审批配置多级审批流程",
"workflowTitle": "审批流程配置"
"workflowTitle": "审批流程配置",
"provider": {
"aliyun": "阿里云",
"tencent": "腾讯云",
"huawei": "华为云"
},
"permType": {
"bastion": "堡垒机"
}
},
"audit": {
"csvHeader": "时间,用户,命令,输出大小,会话ID,高危",
@@ -4423,7 +4442,16 @@
"snmpVersion": "SNMP 版本",
"snmpNoDevice": "暂无 SNMP 设备,点击「添加设备」开始监控",
"snmpCollectMetrics": "采集指标",
"snmpNoMetrics": "暂无指标,请先采集"
"snmpNoMetrics": "暂无指标,请先采集",
"noTrendData": "暂无趋势数据",
"noTrendDataWaiting": "暂无趋势数据,等待后台采集",
"selectInstanceHint": "选择左侧实例查看趋势与健康度",
"clickDeviceHint": "点击设备行查看 CPU / 内存 / 温度 / 风扇趋势图",
"noMetricsHint": "暂无可视化指标,请先完成至少一次采集",
"usedHint": "已用 {{value}}",
"clusterNodesHint": "集群 {{value}} 节点",
"slowQueriesHint": "慢查询 {{value}}",
"pointCount": "{{min}} — {{max}} · {{count}} 点"
},
"logAlertBell": {
"linked": "已关联告警策略",
@@ -4813,7 +4841,8 @@
"neverReport": "永远不上报",
"filterMode": "过滤模式",
"title": "端口监控策略",
"desc": "配置 Agent 端口上报过滤规则"
"desc": "配置 Agent 端口上报过滤规则",
"systemProcessDesc": "⚠️ 无论选择哪种模式,以下系统进程端口 {{neverReport}}:sshd、rpcbind、master、systemd、dbus-daemon、rsyslogd 等。Agent 采集时自动跳过。"
},
"postgresql": "PostgreSQL",
"prevPage": "上一页",
@@ -5004,7 +5033,15 @@
"probeDesc": "按协议类型配置探活任务,系统将按间隔自动拨测",
"taskNotFound": "拨测任务不存在或已删除",
"clickToObserve": "点击可观测进入详情页",
"alerting": "告警中"
"alerting": "告警中",
"recommendFreq": "推荐 {{freq}},最小 {{minFreq}}",
"seconds": "{{count}}秒",
"minutes": "{{count}}分钟",
"hours": "{{count}}小时",
"certDays": "{{count}}天",
"certRemaining": "证书剩余 {{days}} 天",
"checkFailed": "{{label}}失败",
"latencyFluctuation": "{{label}}延迟波动"
},
"siteProbe": "站点拨测",
"siteProbeTitle": "站点拨测任务",
@@ -7933,7 +7970,8 @@
"expertPrompt": "你是 AIOps 容量规划专家。基于以下服务器资源容量预测数据,给出综合风险评估与可执行的运维建议:",
"resCpu": "服务器CPU算力",
"resMemory": "服务器内存",
"resDisk": "存储磁盘空间"
"resDisk": "存储磁盘空间",
"summary": "- {{resource}}:当前 {{current}}% → 7天后预测 {{predicted}}%(阈值 {{threshold}}%),{{risk}}"
},
"capacityPlanPrompt": "根据以下主机资源使用数据,提供成本优化和容量规划建议(识别闲置/过载主机,给出缩容/扩容建议)",
"capacityPlanning": "容量规划",
@@ -6,7 +6,7 @@ function ApprovalPermissionPage() {
const { t } = useTranslation();
const PERMISSION_FIELDS: FormField[] = [
{ key: 'perm_type', label: t('approval.type.permission'), type: 'select', options: [
'堡垒机', 'VPN', 'Log', 'DB', 'SSH', 'K8s',
t('approval.permType.bastion'), 'VPN', 'Log', 'DB', 'SSH', 'K8s',
], required: true },
{ key: 'account', label: 'Account', type: 'text', placeholder: 'logan.li', required: true },
{ key: 'reason', label: 'Reason', type: 'textarea', placeholder: '', required: true },
@@ -5,7 +5,7 @@ import { ApprovalCategoryPage, type FormField } from './ApprovalCategoryPage';
function ApprovalResourcePage() {
const { t } = useTranslation();
const RESOURCE_FIELDS: FormField[] = [
{ key: 'provider', label: t('approval.resourceTitle'), type: 'select', options: ['阿里云', '腾讯云', '华为云', 'AWS'], required: true },
{ key: 'provider', label: t('approval.resourceTitle'), type: 'select', options: [t('approval.provider.aliyun'), t('approval.provider.tencent'), t('approval.provider.huawei'), 'AWS'], required: true },
{ key: 'region', label: 'Region', type: 'text', placeholder: 'cn-hangzhou', required: true },
{ key: 'spec', label: 'Spec', type: 'select', options: ['2C4G', '4C8G', '8C16G', '16C32G', '32C64G'], required: true },
{ key: 'disk', label: 'Disk(GB)', type: 'number', defaultValue: 40, required: true },
@@ -319,7 +319,7 @@ export function ActiveSessionsPanel({ userToggledOff, onUserToggledOff }: Active
<Card className="dg-card">
<CardContent className="p-0">
<div className="px-4 py-2 border-b border-[var(--dg-border)] text-xs font-semibold dg-text-secondary">RDP 在线会话</div>
<div className="px-4 py-2 border-b border-[var(--dg-border)] text-xs font-semibold dg-text-secondary">{t('common.rdpActiveSessions')}</div>
{rdpQuery.isLoading ? (
<div className="flex items-center justify-center py-10"><div className="w-7 h-7 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" /></div>
) : rdpSessions.length === 0 ? (
@@ -248,7 +248,7 @@ export function HistorySessionsPanel() {
<Card className="dg-card">
<CardContent className="p-0">
<div className="px-4 py-2 border-b border-[var(--dg-border)] text-xs font-semibold dg-text-secondary">RDP 历史会话</div>
<div className="px-4 py-2 border-b border-[var(--dg-border)] text-xs font-semibold dg-text-secondary">{t('common.rdpHistorySessions')}</div>
{rdpQuery.isLoading ? (
<div className="flex items-center justify-center py-12">
<div className="w-7 h-7 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
@@ -303,7 +303,7 @@ export function HistorySessionsPanel() {
</TableBody>
</Table>
<div className="flex items-center justify-between px-4 py-3 border-t border-[var(--dg-border)]">
<span className="text-xs dg-text-muted">RDP 会话共 {rdpTotal} 条</span>
<span className="text-xs dg-text-muted">{t('common.rdpSessionCount', { count: rdpTotal })}</span>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon" className="h-8 w-8" disabled={rdpPage <= 1} onClick={() => setRdpPage((p) => p - 1)}>
<ChevronLeft className="h-4 w-4" />
+1 -1
View File
@@ -215,7 +215,7 @@ export function HostMonitor({ embedded = false }: { embedded?: boolean }) {
return (
<PageShell variant="workspace" title={t('monitor.alertRulesTab.catHost')}
subtitle={selectedHost ? `当前主机: ${selectedHost}` : t('monitor.lbl.selectHostHint')}
subtitle={selectedHost ? t('common.currentHost', { host: selectedHost }) : t('monitor.lbl.selectHostHint')}
icon={<Server className="h-5 w-5" />} toolbar={toolbar}
loading={loading && !history.length} loadingLabel={t('monitor.hostMonitor.loadingData')}
contentClassName="space-y-3">
@@ -134,7 +134,7 @@ export function MetricsExplorer() {
/>
</div>
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={() => { setSelectedMetrics([]); setQueryResults([]); }}>
清除
{t('common.clear')}
</Button>
</div>
);
@@ -163,7 +163,7 @@ export function MetricsExplorer() {
<div className="flex items-center gap-2 mb-3">
<BarChart3 className="h-4 w-4 dg-text-muted" />
<h3 className="text-sm font-medium text-[var(--dg-text-primary)]"> {t('monitor.metricsExplorer.metricList')} </h3>
<span className="text-xs dg-text-muted ml-auto">{selectedMetrics.length} 已选</span>
<span className="text-xs dg-text-muted ml-auto">{t('common.selected', { count: selectedMetrics.length })}</span>
</div>
<div className="space-y-4 max-h-[60vh] overflow-y-auto">
{Object.entries(grouped).map(([cat, names]) => (
@@ -203,7 +203,7 @@ export function MetricsExplorer() {
<div className="flex items-center gap-2 mb-3">
<Activity className="h-4 w-4 dg-text-muted" />
<h3 className="text-sm font-medium text-[var(--dg-text-primary)]">
{target?.name || ''} — 最近 1 小时
{target?.name || ''} — {t('common.last1Hour')}
</h3>
</div>
{selectedMetrics.length === 0 ? (
@@ -84,7 +84,7 @@ export function PortPolicyDialog({
<div className="bg-amber-500/5 border border-amber-500/20 rounded p-2.5">
<p className="text-[10px] text-amber-300/80 leading-relaxed">
⚠️ 无论选择哪种模式,以下系统进程端口<b> {t('monitor.portPolicyDialog.neverReport')} </b>:sshd、rpcbind、master、systemd、dbus-daemon、rsyslogd 等。Agent 采集时自动跳过。
{t('monitor.portPolicyDialog.systemProcessDesc', { neverReport: t('monitor.portPolicyDialog.neverReport') })}
</p>
</div>
</div>
@@ -1,4 +1,5 @@
// 主机监控工具函数 // Logan.Li
import i18n from '@/i18n';
import type { BucketPoint, HostMonitorTimeRange } from './types';
import type { JsonData } from '@/types/common';
/** 按时间跨度格式化 X 轴标签(避免 7 天范围只显示时分) // Logan.Li */
@@ -175,7 +176,7 @@ export function getActualDataSpan(history: JsonData[], _range: HostMonitorTimeRa
const min = Math.min(...ts);
const max = Math.max(...ts);
const spanMin = Math.max(1, Math.round((max - min) / 60));
return `${formatAxisTime(min, spanMin)} — ${formatAxisTime(max, spanMin)} · ${history.length} 点`;
return i18n.t('monitor.lbl.pointCount', { min: formatAxisTime(min, spanMin), max: formatAxisTime(max, spanMin), count: history.length });
}
export function getCurrentVal(latest: JsonData, netInfo: JsonData, diskIOInfo: JsonData, swapInfo: JsonData, fdInfo: JsonData,
@@ -67,7 +67,7 @@ export function MwTargetChartsPanel({ target, chartDefs, accent }: Props) {
return (
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] h-full">
<CardContent className="p-8 text-center text-sm text-[var(--dg-text-muted)]">
选择左侧实例查看趋势与健康度
{t('monitor.lbl.selectInstanceHint')}
</CardContent>
</Card>
);
@@ -127,14 +127,14 @@ export function MwTargetChartsPanel({ target, chartDefs, accent }: Props) {
value={memPct ?? (m.redis_used_memory > 0 ? 50 : 0)}
display={fmtMemory(m.redis_used_memory)}
level={memPct != null ? gaugeTone(memPct, 80, 90) : 'ok'}
hint={memPct != null ? `已用 ${memPct.toFixed(0)}%` : t('monitor.lbl.noMaxMemory')}
hint={memPct != null ? t('monitor.lbl.usedHint', { value: `${memPct.toFixed(0)}%` }) : t('monitor.lbl.noMaxMemory')}
/>
<MwGaugeBar
label={t('kubernetes.topology.connections')}
value={Math.min(100, (m.redis_connected_clients || 0) / 2)}
display={fmtVal(m.redis_connected_clients)}
level={(m.redis_blocked_clients || 0) > 0 ? 'warn' : 'ok'}
hint={m.redis_cluster_known_nodes ? `集群 ${fmtVal(m.redis_cluster_known_nodes)} 节点` : undefined}
hint={m.redis_cluster_known_nodes ? t('monitor.lbl.clusterNodesHint', { value: fmtVal(m.redis_cluster_known_nodes) }) : undefined}
/>
</>
)}
@@ -157,7 +157,7 @@ export function MwTargetChartsPanel({ target, chartDefs, accent }: Props) {
value={Math.min(100, (m.mysql_threads_running || 0) * 5)}
display={fmtVal(m.mysql_threads_running)}
level={(m.mysql_threads_running || 0) > 20 ? 'warn' : 'ok'}
hint={`慢查询 ${fmtVal(m.mysql_slow_queries)}`}
hint={t('monitor.lbl.slowQueriesHint', { value: fmtVal(m.mysql_slow_queries) })}
/>
</>
)}
@@ -1,5 +1,6 @@
// 中间件指标趋势图 — 光标拖拽选区 // Logan.Li
import { useMemo } from 'react';
import i18n from '@/i18n';
import {
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
ReferenceArea,
@@ -41,7 +42,7 @@ export function MwTrendChart({
if (chartData.length === 0) {
return (
<div className="flex items-center justify-center text-xs text-[var(--dg-text-muted)]" style={{ height }}>
暂无趋势数据
{i18n.t('monitor.lbl.noTrendData')}
</div>
);
}
@@ -245,8 +245,8 @@ export function fmtLastSeen(iso?: string): string {
const d = new Date(iso);
if (Number.isNaN(d.getTime())) return '--';
const diff = Math.floor((Date.now() - d.getTime()) / 1000);
if (diff < 60) return `${diff}s 前`;
if (diff < 3600) return `${Math.floor(diff / 60)}m 前`;
if (diff < 86400) return `${Math.floor(diff / 3600)}h 前`;
if (diff < 60) return i18n.t('common.duration.secAgo', { count: diff });
if (diff < 3600) return i18n.t('common.duration.minAgo', { count: Math.floor(diff / 60) });
if (diff < 86400) return i18n.t('common.duration.hourAgo', { count: Math.floor(diff / 3600) });
return `${d.getMonth() + 1}/${d.getDate()} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
}
@@ -95,15 +95,15 @@ export const probeTypeMeta: Record<ProbeType, { label: string; placeholder: stri
export function probeFrequencyHint(type: ProbeType): string {
const sec = defaultProbeFrequency(type);
return `推荐 ${fmtFrequency(sec)},最小 ${fmtFrequency(MIN_PROBE_FREQUENCY[type])}`;
return i18n.t('monitor.siteMonitor.recommendFreq', { freq: fmtFrequency(sec), minFreq: fmtFrequency(MIN_PROBE_FREQUENCY[type]) });
}
/** 拨测频率展示 // Logan.Li */
export function fmtFrequency(sec?: number): string {
if (!sec) return '--';
if (sec < 60) return `${sec}秒`;
if (sec < 3600) return `${Math.round(sec / 60)}分钟`;
return `${(sec / 3600).toFixed(1)}小时`;
if (sec < 60) return i18n.t('monitor.siteMonitor.seconds', { count: sec });
if (sec < 3600) return i18n.t('monitor.siteMonitor.minutes', { count: Math.round(sec / 60) });
return i18n.t('monitor.siteMonitor.hours', { count: (sec / 3600).toFixed(1) });
}
/** 可用率 / 响应时间组合展示 // Logan.Li */
@@ -147,9 +147,9 @@ export function isSslExpiringSoon(days?: number, threshold = SSL_EXPIRY_WARN_DAY
export function fmtSslDays(days?: number): { text: string; tone: string } {
if (days == null) return { text: '--', tone: 'text-[var(--dg-text-muted)]' };
if (days < 0) return { text: i18n.t('system.expired2'), tone: 'dg-text-red' };
if (days <= 7) return { text: `${days}天`, tone: 'dg-text-red' };
if (days <= 30) return { text: `${days}天`, tone: 'dg-text-yellow' };
return { text: `${days}天`, tone: 'dg-text-green' };
if (days <= 7) return { text: i18n.t('monitor.siteMonitor.certDays', { count: days }), tone: 'dg-text-red' };
if (days <= 30) return { text: i18n.t('monitor.siteMonitor.certDays', { count: days }), tone: 'dg-text-yellow' };
return { text: i18n.t('monitor.siteMonitor.certDays', { count: days }), tone: 'dg-text-green' };
}
/** 归一化拨测协议类型 // Logan.Li */
@@ -130,7 +130,7 @@ export function analyzeSslResults(results: SiteResult[], task: SiteTask): SslObs
if (r.status !== 'success') {
pushEvent(events, r, i18n.t('monitor.lbl.siteRequestTimeout'), 'error', r.error_message);
} 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, i18n.t('monitor.siteMonitor.certRemaining', { days: r.ssl_days_remaining }), r.ssl_days_remaining <= 7 ? 'error' : 'warn', expiry);
}
}
const chart = tooltipOrder(results)
@@ -158,10 +158,10 @@ function analyzeLatencyResults(results: SiteResult[], label: string): LatencyObs
const latency = r.response_time ?? r.total_time ?? 0;
const failed = r.status !== 'success';
const spike = !failed && latency > spikeThreshold;
if (failed) pushEvent(events, r, `${label}失败`, 'error', r.error_message || r.remote_ip);
if (failed) pushEvent(events, r, i18n.t('monitor.siteMonitor.checkFailed', { label }), 'error', r.error_message || r.remote_ip);
else if (spike) {
spikeCount++;
pushEvent(events, r, `${label}延迟波动`, 'warn', fmtMs(latency));
pushEvent(events, r, i18n.t('monitor.siteMonitor.latencyFluctuation', { label }), 'warn', fmtMs(latency));
}
return { time: fmtTime(r.checked_at).slice(-5), latency, failed, spike };
});
@@ -88,7 +88,7 @@ export function SnmpDeviceChartsPanel({ device, templateName }: Props) {
return (
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="p-8 text-center text-sm text-[var(--dg-text-muted)]">
点击设备行查看 CPU / 内存 / 温度 / 风扇趋势图
{t('monitor.lbl.clickDeviceHint')}
</CardContent>
</Card>
);
@@ -104,7 +104,7 @@ export function SnmpDeviceChartsPanel({ device, templateName }: Props) {
<div className="flex items-center gap-2 min-w-0">
<Activity className="h-4 w-4 text-[var(--dg-text-muted)] shrink-0" />
<h3 className="text-sm font-medium text-[var(--dg-text-primary)] truncate">
可观测 · {device.name}
{t('monitor.lbl.observability')} · {device.name}
</h3>
<Badge variant="outline" className="text-[10px] border-[var(--dg-border)] shrink-0">
{templateName}
@@ -123,7 +123,7 @@ export function SnmpDeviceChartsPanel({ device, templateName }: Props) {
</Select>
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={loadCharts} disabled={loading}>
<RefreshCw className={`h-3 w-3 mr-1 ${loading ? 'animate-spin' : ''}`} />
刷新
{t('common.refresh')}
</Button>
</div>
</div>
@@ -147,7 +147,7 @@ export function SnmpDeviceChartsPanel({ device, templateName }: Props) {
</div>
) : charts.length === 0 ? (
<div className="py-12 text-center text-sm text-[var(--dg-text-muted)]">
暂无可视化指标,请先完成至少一次采集
{t('monitor.lbl.noMetricsHint')}
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
@@ -1,5 +1,6 @@
// SNMP 指标趋势折线图 — 光标拖拽选区 // Logan.Li
import { useMemo } from 'react';
import i18n from '@/i18n';
import {
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
ReferenceArea,
@@ -30,7 +31,7 @@ export function SnmpMetricTrendChart({ data, color, unit, height = 160 }: Props)
if (chartData.length === 0) {
return (
<div className="flex items-center justify-center text-xs text-[var(--dg-text-muted)]" style={{ height }}>
暂无趋势数据,等待后台采集
{i18n.t('monitor.lbl.noTrendDataWaiting')}
</div>
);
}
+1 -1
View File
@@ -108,7 +108,7 @@ async function request<T = any>(
try {
json = await response.json();
} catch {
throw new Error(`HTTP ${response.status}: 响应解析失败`);
throw new Error(`HTTP ${response.status}: ${i18n.t('common.responseParseError')}`);
}
if (!response.ok || (json.code && json.code !== 200)) {