refactor(i18n): U40 Monitor + Agent 子组件残余硬编码中文 i18n 补全 — 14 文件 ~50 处→0

- ContextSwitchCard: CTX/IRQ/次 → t()
- ProcessTable: agentWaitData/cpuUsagePercent → t()
- FDUsageCard: fdHandles → t()
- PortMonitorTab: 黑名单/白名单/策略配置/暂无端口数据 → t()
- PortPolicyDialog: 白名单全量上报/黑名单仅上报/端口列表/逗号分隔/白名单无需配置/已输入端口 → t()
- HostTimeRangePicker: 时间范围/自定义/取消/应用 → t()
- DualLineChart: 接收/发送 → t(), i18n.t→useTranslation hook
- ZoomableAreaChart: 暂无数据 → t()
- SiteResultsPanel: 选择任务/次/可用率/立即拨测/SSL证书预警/到期/剩余/阈值/天 → t()
- MiddlewareMonitor: Redis单机集群/上次更新/添加平台默认/暂无实例 → t()
- SiteMonitor: subtitle → t()
- SNMPMonitor: 标题/采集间隔/IP地址/OID模板/SNMP版本/确认/暂无设备/采集指标/暂无指标 → t()
- HealthProbeSection: TCP目标地址/TCP端口 → t()
- AgentAlerts: Agent告警/刷新/未恢复/已恢复/恢复/新建规则/创建第一条规则 → i18n.t()

新增 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 测试通过
This commit is contained in:
attacker committed 2026-07-13 19:45:45 +08:00
1 parent 70a7c2907c
commit 4e2ba77aa8
17 files changed
+165 -61

No files matched your search

+1
View File
@@ -527,6 +527,7 @@ services/api.ts (1324L)
| U36 | 全量 JSX 属性 + toast 硬编码中文清零(20 文件 30 处→0: TerminalLoadingOverlay/copy-button/AppRelease/DeployCredentials/PipelineManager/WorkspaceTermDialog/CredFormDialog/JobRoleCreateDialog/PipelineCreateDialog/AdvancedSettingsPanel/ServerForm/LdapManagementSection/LiveMonitorDialog/SystemManager/ActiveSessionsPanel/HistorySessionsPanel/AlertRulesTab/PrometheusDatasourceManager/SNMPMonitor/PredictiveCapacityTab — 23 JSX 属性 + 7 toast 调用全量清零, copy-button 从 import i18n→useTranslation hook, 4 组件新增 useTranslation hook;新增 25 翻译键;zh/en 7616→7640 +24 对齐;tsc -b=0, npm build✓ (17.25s), ESLint=0/43/any=0, go build=0, 17/17 测试通过) | P3 | **done** | | U36 | 全量 JSX 属性 + toast 硬编码中文清零(20 文件 30 处→0: TerminalLoadingOverlay/copy-button/AppRelease/DeployCredentials/PipelineManager/WorkspaceTermDialog/CredFormDialog/JobRoleCreateDialog/PipelineCreateDialog/AdvancedSettingsPanel/ServerForm/LdapManagementSection/LiveMonitorDialog/SystemManager/ActiveSessionsPanel/HistorySessionsPanel/AlertRulesTab/PrometheusDatasourceManager/SNMPMonitor/PredictiveCapacityTab — 23 JSX 属性 + 7 toast 调用全量清零, copy-button 从 import i18n→useTranslation hook, 4 组件新增 useTranslation hook;新增 25 翻译键;zh/en 7616→7640 +24 对齐;tsc -b=0, npm build✓ (17.25s), ESLint=0/43/any=0, go build=0, 17/17 测试通过) | P3 | **done** |
| 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** | | 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** | | 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** |
| 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** | | 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** |
--- ---
@@ -113,12 +113,12 @@ export function AgentAlerts() {
{/* 页头 */} {/* 页头 */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<h2 className="text-xl font-semibold text-[var(--dg-text-primary)]">Agent 告警</h2> <h2 className="text-xl font-semibold text-[var(--dg-text-primary)]">{i18n.t('agent.alerts.title')}</h2>
<p className="text-sm dg-text-secondary mt-0.5"> {i18n.t('agent.alerts.thresholdTriggered')} </p> <p className="text-sm dg-text-secondary mt-0.5"> {i18n.t('agent.alerts.thresholdTriggered')} </p>
</div> </div>
<Button variant="outline" size="sm" onClick={loadData} disabled={loading} className="dg-input text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)]"> <Button variant="outline" size="sm" onClick={loadData} disabled={loading} className="dg-input text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)]">
<RefreshCw className={`h-4 w-4 mr-1.5 ${loading ? 'animate-spin' : ''}`} /> <RefreshCw className={`h-4 w-4 mr-1.5 ${loading ? 'animate-spin' : ''}`} />
刷新 {i18n.t('common.refresh')}
</Button> </Button>
</div> </div>
@@ -137,7 +137,7 @@ export function AgentAlerts() {
onClick={() => setResolvedView(false)} onClick={() => setResolvedView(false)}
className={!resolvedView ? 'bg-blue-600 hover:bg-blue-700 text-white' : 'dg-input text-[var(--dg-text-secondary)]'} className={!resolvedView ? 'bg-blue-600 hover:bg-blue-700 text-white' : 'dg-input text-[var(--dg-text-secondary)]'}
> >
未恢复 {i18n.t('agent.alerts.unresolved')}
</Button> </Button>
<Button <Button
size="sm" size="sm"
@@ -145,7 +145,7 @@ export function AgentAlerts() {
onClick={() => setResolvedView(true)} onClick={() => setResolvedView(true)}
className={resolvedView ? 'bg-blue-600 hover:bg-blue-700 text-white' : 'dg-input text-[var(--dg-text-secondary)]'} className={resolvedView ? 'bg-blue-600 hover:bg-blue-700 text-white' : 'dg-input text-[var(--dg-text-secondary)]'}
> >
已恢复 {i18n.t('agent.alerts.resolved')}
</Button> </Button>
</div> </div>
@@ -207,7 +207,7 @@ export function AgentAlerts() {
className="shrink-0 h-8 dg-input dg-text-green hover:bg-emerald-500/10 hover:dg-text-green" className="shrink-0 h-8 dg-input dg-text-green hover:bg-emerald-500/10 hover:dg-text-green"
> >
<CheckCircle className="h-3.5 w-3.5 mr-1" /> <CheckCircle className="h-3.5 w-3.5 mr-1" />
恢复 {i18n.t('agent.alerts.resolve')}
</Button> </Button>
)} )}
</div> </div>
@@ -223,7 +223,7 @@ export function AgentAlerts() {
<div className="flex justify-end"> <div className="flex justify-end">
<Button size="sm" onClick={openCreateRule} className="bg-blue-600 hover:bg-blue-700 text-white"> <Button size="sm" onClick={openCreateRule} className="bg-blue-600 hover:bg-blue-700 text-white">
<Plus className="h-4 w-4 mr-1.5" /> <Plus className="h-4 w-4 mr-1.5" />
新建规则 {i18n.t('agent.alerts.newRule')}
</Button> </Button>
</div> </div>
@@ -241,7 +241,7 @@ export function AgentAlerts() {
<p className="dg-text-secondary"> {i18n.t('monitor.noRules')} </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"> <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" /> <Plus className="h-4 w-4 mr-1.5" />
创建第一条规则 {i18n.t('agent.alerts.createFirstRule')}
</Button> </Button>
</CardContent> </CardContent>
</Card> </Card>
+54 -3
View File
@@ -4377,7 +4377,53 @@
"noProbeHint": "No health probes. Click \"{{action}}\" to create one.", "noProbeHint": "No health probes. Click \"{{action}}\" to create one.",
"searchFields": "Search fields", "searchFields": "Search fields",
"receiveSend": "RX {{rx}} · TX {{tx}}", "receiveSend": "RX {{rx}} · TX {{tx}}",
"currentFDUsage": "Current {{pct}}%" "currentFDUsage": "Current {{pct}}%",
"ctxPerSec": "CTX switches/sec",
"irqPerSec": "IRQ interrupts/sec",
"times": "times",
"agentWaitData": "Agent v2.0+ required, waiting for data…",
"cpuUsagePercent": "CPU Usage(%)",
"fdHandles": "FD File Handles",
"blacklistLabel": "Blacklist",
"whitelistModeLabel": "Whitelist mode",
"policyConfig": "Policy Config",
"noPortData": "No port data",
"whitelistFullReport": "Whitelist — Report all",
"blacklistSelectedOnly": "Blacklist — Report selected only",
"portListLabel": "Port list",
"portListHint": "(comma-separated, e.g.: 80,443,3306,6379)",
"whitelistNoConfig": "— No config needed in whitelist mode",
"portsEntered": "{{count}} ports entered",
"timeRange": "Time range",
"custom": "Custom",
"apply": "Apply",
"rx": "RX",
"tx": "TX",
"noData": "No data",
"selectTaskHint": "Select a task to view protocol-specific metrics",
"probeNow": "Probe now",
"sslCertWarning": "SSL Certificate Warning",
"sslExpiring": "Expires",
"sslRemaining": "remaining",
"sslThreshold": "threshold",
"sslDays": "days",
"availabilityRate": "Availability",
"redisMysqlAutoRefresh": "Redis standalone/cluster · MySQL · 30s auto refresh",
"lastUpdate": "Last update",
"addPlatformDefault": "Add platform default",
"noInstancePrefix": "No",
"instanceSuffix": "instances",
"siteMonitorSubtitle": "HTTP Web probe · SSL cert detection · TCP / DNS / PING",
"snmpMonitorTitle": "SNMP Monitor",
"snmpCollectInterval": "5 min collection · 7 day retention",
"tcpTargetAddr": "TCP target address",
"tcpPort": "TCP port",
"ipAddress": "IP address",
"snmpOidTemplate": "OID Template",
"snmpVersion": "SNMP Version",
"snmpNoDevice": "No SNMP devices. Click \"Add Device\" to start monitoring.",
"snmpCollectMetrics": "Collected Metrics",
"snmpNoMetrics": "No metrics yet. Please collect first."
}, },
"logAlertBell": { "logAlertBell": {
"linked": "Alert Policy Linked", "linked": "Alert Policy Linked",
@@ -7043,7 +7089,7 @@
"loadFailed": "Failed to load alert data", "loadFailed": "Failed to load alert data",
"resolveAlert": "Resolve Alert", "resolveAlert": "Resolve Alert",
"resolveConfirm": "Mark this alert as resolved?", "resolveConfirm": "Mark this alert as resolved?",
"resolved": "Alert resolved", "resolved": "Resolved",
"deleteRule": "Delete Rule", "deleteRule": "Delete Rule",
"thresholdTriggered": "Alert threshold triggered", "thresholdTriggered": "Alert threshold triggered",
"noResolved": "No resolved alerts", "noResolved": "No resolved alerts",
@@ -7051,7 +7097,12 @@
"metricSwap": "Swap Usage", "metricSwap": "Swap Usage",
"metricLoad1": "1-min Load Average", "metricLoad1": "1-min Load Average",
"metricInode": "Inode Usage", "metricInode": "Inode Usage",
"metricTcpEst": "TCP Established" "metricTcpEst": "TCP Established",
"title": "Agent Alerts",
"unresolved": "Unresolved",
"resolve": "Resolve",
"newRule": "New Rule",
"createFirstRule": "Create your first rule"
}, },
"allPortsHint": "All ports reported automatically", "allPortsHint": "All ports reported automatically",
"allPortsMode": "All Ports", "allPortsMode": "All Ports",
+54 -3
View File
@@ -4377,7 +4377,53 @@
"noProbeHint": "暂无健康探针,点击「{{action}}」创建", "noProbeHint": "暂无健康探针,点击「{{action}}」创建",
"searchFields": "搜索字段", "searchFields": "搜索字段",
"receiveSend": "接收 {{rx}} · 发送 {{tx}}", "receiveSend": "接收 {{rx}} · 发送 {{tx}}",
"currentFDUsage": "当前 {{pct}}%" "currentFDUsage": "当前 {{pct}}%",
"ctxPerSec": "CTX 切换/秒",
"irqPerSec": "IRQ 中断/秒",
"times": "次",
"agentWaitData": "Agent v2.0+ 支持,等待数据上报…",
"cpuUsagePercent": "CPU使用率(%)",
"fdHandles": "FD 文件句柄",
"blacklistLabel": "黑名单",
"whitelistModeLabel": "白名单模式",
"policyConfig": "策略配置",
"noPortData": "暂无端口数据",
"whitelistFullReport": "白名单 ─ 全量上报",
"blacklistSelectedOnly": "黑名单 ─ 仅上报选中",
"portListLabel": "端口列表",
"portListHint": "(逗号分隔,如: 80,443,3306,6379)",
"whitelistNoConfig": "— 白名单模式无需配置",
"portsEntered": "已输入 {{count}} 个端口",
"timeRange": "时间范围",
"custom": "自定义",
"apply": "应用",
"rx": "接收",
"tx": "发送",
"noData": "暂无数据",
"selectTaskHint": "选择任务查看协议专属可观测指标",
"probeNow": "立即拨测",
"sslCertWarning": "SSL 证书预警",
"sslExpiring": "到期",
"sslRemaining": "剩余",
"sslThreshold": "阈值",
"sslDays": "天",
"availabilityRate": "可用率",
"redisMysqlAutoRefresh": "Redis 单机/集群 · MySQL · 30s 自动刷新",
"lastUpdate": "上次更新",
"addPlatformDefault": "添加平台默认",
"noInstancePrefix": "暂无",
"instanceSuffix": "实例",
"siteMonitorSubtitle": "HTTP Web 拨测 · SSL 证书检测 · TCP / DNS / PING",
"snmpMonitorTitle": "SNMP 监控",
"snmpCollectInterval": "5 分钟采集 · 指标保留 7 天",
"tcpTargetAddr": "TCP 目标地址",
"tcpPort": "TCP 端口",
"ipAddress": "IP 地址",
"snmpOidTemplate": "OID 模板",
"snmpVersion": "SNMP 版本",
"snmpNoDevice": "暂无 SNMP 设备,点击「添加设备」开始监控",
"snmpCollectMetrics": "采集指标",
"snmpNoMetrics": "暂无指标,请先采集"
}, },
"logAlertBell": { "logAlertBell": {
"linked": "已关联告警策略", "linked": "已关联告警策略",
@@ -7043,7 +7089,7 @@
"loadFailed": "加载告警数据失败", "loadFailed": "加载告警数据失败",
"resolveAlert": "恢复告警", "resolveAlert": "恢复告警",
"resolveConfirm": "确定将此告警标记为已恢复?", "resolveConfirm": "确定将此告警标记为已恢复?",
"resolved": "告警已恢复", "resolved": "已恢复",
"deleteRule": "删除规则", "deleteRule": "删除规则",
"thresholdTriggered": "触发告警阈值", "thresholdTriggered": "触发告警阈值",
"noResolved": "暂无已恢复的告警", "noResolved": "暂无已恢复的告警",
@@ -7051,7 +7097,12 @@
"metricSwap": "Swap 使用率", "metricSwap": "Swap 使用率",
"metricLoad1": "1分钟负载", "metricLoad1": "1分钟负载",
"metricInode": "Inode 使用率", "metricInode": "Inode 使用率",
"metricTcpEst": "TCP 已建立" "metricTcpEst": "TCP 已建立",
"title": "Agent 告警",
"unresolved": "未恢复",
"resolve": "恢复",
"newRule": "新建规则",
"createFirstRule": "创建第一条规则"
}, },
"allPortsHint": "全部端口自动上报", "allPortsHint": "全部端口自动上报",
"allPortsMode": "全量上报", "allPortsMode": "全量上报",
@@ -153,10 +153,10 @@ export function MiddlewareMonitor() {
<div> <div>
<h2 className="text-lg font-semibold"> {t('monitor.alertHelper.srcMiddleware')} </h2> <h2 className="text-lg font-semibold"> {t('monitor.alertHelper.srcMiddleware')} </h2>
<p className="text-xs text-[var(--dg-text-muted)] mt-0.5"> <p className="text-xs text-[var(--dg-text-muted)] mt-0.5">
Redis 单机/集群 · MySQL · 30s 自动刷新 {t('monitor.lbl.redisMysqlAutoRefresh')}
{lastRefreshAt && ( {lastRefreshAt && (
<span className="ml-2"> <span className="ml-2">
上次更新 {lastRefreshAt.toLocaleTimeString()} {t('monitor.lbl.lastUpdate')} {lastRefreshAt.toLocaleTimeString()}
{refreshing && t('monitor.lbl.refreshing')} {refreshing && t('monitor.lbl.refreshing')}
</span> </span>
)} )}
@@ -172,7 +172,7 @@ export function MiddlewareMonitor() {
{targets.length === 0 && ( {targets.length === 0 && (
<Button variant="outline" size="sm" onClick={handleSeedDefaults} disabled={seeding} className="gap-1.5"> <Button variant="outline" size="sm" onClick={handleSeedDefaults} disabled={seeding} className="gap-1.5">
{seeding ? <Loader2 className="h-4 w-4 animate-spin" /> : <Database className="h-4 w-4" />} {seeding ? <Loader2 className="h-4 w-4 animate-spin" /> : <Database className="h-4 w-4" />}
添加平台默认 {t('monitor.lbl.addPlatformDefault')}
</Button> </Button>
)} )}
<Button size="sm" onClick={() => openCreate()} className="gap-1.5"> <Button size="sm" onClick={() => openCreate()} className="gap-1.5">
@@ -252,7 +252,7 @@ export function MiddlewareMonitor() {
{currentTargets.length === 0 ? ( {currentTargets.length === 0 ? (
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]"> <Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="p-8 text-center text-sm text-[var(--dg-text-muted)]"> <CardContent className="p-8 text-center text-sm text-[var(--dg-text-muted)]">
暂无 {tab === 'redis' ? 'Redis' : 'MySQL'} 实例, {t('monitor.lbl.noInstancePrefix')} {tab === 'redis' ? 'Redis' : 'MySQL'} {t('monitor.lbl.instanceSuffix')},
<button type="button" className="dg-text-cyan ml-1 underline" onClick={() => openCreate(tab)}> {t('monitor.middlewareMonit.addNow')} </button> <button type="button" className="dg-text-cyan ml-1 underline" onClick={() => openCreate(tab)}> {t('monitor.middlewareMonit.addNow')} </button>
</CardContent> </CardContent>
</Card> </Card>
+9 -9
View File
@@ -183,8 +183,8 @@ export function SNMPMonitor() {
{/* 工具栏 */} {/* 工具栏 */}
<div className="flex items-center justify-between flex-wrap gap-2"> <div className="flex items-center justify-between flex-wrap gap-2">
<div> <div>
<h2 className="text-lg font-semibold">SNMP 监控</h2> <h2 className="text-lg font-semibold">{t('monitor.lbl.snmpMonitorTitle')}</h2>
<p className="text-xs text-[var(--dg-text-muted)]">5 分钟采集 · 指标保留 7 天</p> <p className="text-xs text-[var(--dg-text-muted)]">{t('monitor.lbl.snmpCollectInterval')}</p>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<Button size="sm" variant="outline" onClick={refresh} className="h-8"> <Button size="sm" variant="outline" onClick={refresh} className="h-8">
@@ -208,7 +208,7 @@ export function SNMPMonitor() {
</div> </div>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div className="grid gap-1.5"> <div className="grid gap-1.5">
<Label>IP 地址</Label> <Label>{t('monitor.lbl.ipAddress')}</Label>
<Input value={form.ip} onChange={(e) => setForm({ ...form, ip: e.target.value })} placeholder="192.168.1.1" /> <Input value={form.ip} onChange={(e) => setForm({ ...form, ip: e.target.value })} placeholder="192.168.1.1" />
</div> </div>
<div className="grid gap-1.5"> <div className="grid gap-1.5">
@@ -217,7 +217,7 @@ export function SNMPMonitor() {
</div> </div>
</div> </div>
<div className="grid gap-1.5"> <div className="grid gap-1.5">
<Label>OID 模板</Label> <Label>{t('monitor.lbl.snmpOidTemplate')}</Label>
<Select value={form.template_id} onValueChange={(v) => setForm({ ...form, template_id: v })}> <Select value={form.template_id} onValueChange={(v) => setForm({ ...form, template_id: v })}>
<SelectTrigger><SelectValue placeholder={t('pipeline.selectTemplate')} /></SelectTrigger> <SelectTrigger><SelectValue placeholder={t('pipeline.selectTemplate')} /></SelectTrigger>
<SelectContent> <SelectContent>
@@ -229,7 +229,7 @@ export function SNMPMonitor() {
</div> </div>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div className="grid gap-1.5"> <div className="grid gap-1.5">
<Label>SNMP 版本</Label> <Label>{t('monitor.lbl.snmpVersion')}</Label>
<Select value={form.snmp_version} onValueChange={(v) => setForm({ ...form, snmp_version: v as 'v2c' | 'v3' })}> <Select value={form.snmp_version} onValueChange={(v) => setForm({ ...form, snmp_version: v as 'v2c' | 'v3' })}>
<SelectTrigger><SelectValue /></SelectTrigger> <SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent> <SelectContent>
@@ -247,7 +247,7 @@ export function SNMPMonitor() {
<DialogFooter> <DialogFooter>
<Button variant="outline" onClick={() => setDialogOpen(false)}> {t('rbac.jobRole.cancel')} </Button> <Button variant="outline" onClick={() => setDialogOpen(false)}> {t('rbac.jobRole.cancel')} </Button>
<Button onClick={handleAdd} disabled={submitting}> <Button onClick={handleAdd} disabled={submitting}>
{submitting && <Loader2 className="h-4 w-4 animate-spin mr-1" />}确认 {submitting && <Loader2 className="h-4 w-4 animate-spin mr-1" />}{t('common.confirm')}
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
@@ -278,7 +278,7 @@ export function SNMPMonitor() {
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]"> <Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="py-12 text-center text-sm text-[var(--dg-text-muted)]"> <CardContent className="py-12 text-center text-sm text-[var(--dg-text-muted)]">
<Router className="h-10 w-10 mx-auto mb-3 opacity-40" /> <Router className="h-10 w-10 mx-auto mb-3 opacity-40" />
暂无 SNMP 设备,点击「添加设备」开始监控 {t('monitor.lbl.snmpNoDevice')}
</CardContent> </CardContent>
</Card> </Card>
) : ( ) : (
@@ -348,14 +348,14 @@ export function SNMPMonitor() {
<Dialog open={metricsOpen} onOpenChange={setMetricsOpen}> <Dialog open={metricsOpen} onOpenChange={setMetricsOpen}>
<DialogContent className="sm:max-w-2xl max-h-[80vh] overflow-y-auto bg-[var(--dg-bg-secondary)]"> <DialogContent className="sm:max-w-2xl max-h-[80vh] overflow-y-auto bg-[var(--dg-bg-secondary)]">
<DialogHeader> <DialogHeader>
<DialogTitle>{metricsDevice?.name} — 采集指标</DialogTitle> <DialogTitle>{metricsDevice?.name} — {t('monitor.lbl.snmpCollectMetrics')}</DialogTitle>
<DialogDescription>{metricsDevice?.ip} · {templateName(metricsDevice?.template_id || 0)}</DialogDescription> <DialogDescription>{metricsDevice?.ip} · {templateName(metricsDevice?.template_id || 0)}</DialogDescription>
</DialogHeader> </DialogHeader>
{metricsLoading ? ( {metricsLoading ? (
<div className="py-8 text-center text-sm text-muted-foreground"> {t('monitor.logPanel.logResults.loading')} </div> <div className="py-8 text-center text-sm text-muted-foreground"> {t('monitor.logPanel.logResults.loading')} </div>
) : metrics.length === 0 ? ( ) : metrics.length === 0 ? (
<div className="py-8 text-center text-sm text-muted-foreground"> <div className="py-8 text-center text-sm text-muted-foreground">
暂无指标,请先采集 {t('monitor.lbl.snmpNoMetrics')}
{metricsDevice?.last_error && <p className="mt-2 text-xs dg-text-red">{metricsDevice.last_error}</p>} {metricsDevice?.last_error && <p className="mt-2 text-xs dg-text-red">{metricsDevice.last_error}</p>}
</div> </div>
) : ( ) : (
+1 -1
View File
@@ -213,7 +213,7 @@ export function SiteMonitor() {
<div className="flex items-center justify-between flex-wrap gap-2"> <div className="flex items-center justify-between flex-wrap gap-2">
<div> <div>
<h2 className="text-lg font-semibold"> {t('monitor.alertRulesTab.catSite')} </h2> <h2 className="text-lg font-semibold"> {t('monitor.alertRulesTab.catSite')} </h2>
<p className="text-xs text-[var(--dg-text-muted)]">HTTP Web 拨测 · SSL 证书检测 · TCP / DNS / PING</p> <p className="text-xs text-[var(--dg-text-muted)]">{t('monitor.lbl.siteMonitorSubtitle')}</p>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<Button size="sm" variant="outline" className="h-8" onClick={() => { loadStats(); loadTasks(); }} disabled={refreshing}> <Button size="sm" variant="outline" className="h-8" onClick={() => { loadStats(); loadTasks(); }} disabled={refreshing}>
@@ -6,7 +6,7 @@ import { ZoomableAreaChart, type ZoomableChartHandle } from './ZoomableAreaChart
import { ChartToolbar } from './ChartToolbar'; import { ChartToolbar } from './ChartToolbar';
import { MetricIconBox } from './MetricIconBox'; import { MetricIconBox } from './MetricIconBox';
import type { AggType, BucketPoint } from './types'; import type { AggType, BucketPoint } from './types';
import i18n from '@/i18n'; import { useTranslation } from 'react-i18next';
interface DualLineChartProps { interface DualLineChartProps {
title: string; title: string;
@@ -21,6 +21,7 @@ interface DualLineChartProps {
export function DualLineChart({ export function DualLineChart({
title, currentRx, currentTx, rxData, txData, aggType, unit, title, currentRx, currentTx, rxData, txData, aggType, unit,
}: DualLineChartProps) { }: DualLineChartProps) {
const { t } = useTranslation();
const chartRef = useRef<ZoomableChartHandle>(null); const chartRef = useRef<ZoomableChartHandle>(null);
const [zoomed, setZoomed] = useState(false); const [zoomed, setZoomed] = useState(false);
@@ -43,11 +44,11 @@ export function DualLineChart({
<div className="flex items-center gap-3 text-xs"> <div className="flex items-center gap-3 text-xs">
<span> <span>
<span className="inline-block w-2.5 h-2.5 rounded-sm bg-blue-400 mr-1 align-middle" /> <span className="inline-block w-2.5 h-2.5 rounded-sm bg-blue-400 mr-1 align-middle" />
接收 <b className="text-blue-300">{currentRx}</b> {t('monitor.lbl.rx')} <b className="text-blue-300">{currentRx}</b>
</span> </span>
<span> <span>
<span className="inline-block w-2.5 h-2.5 rounded-sm bg-emerald-400 mr-1 align-middle" /> <span className="inline-block w-2.5 h-2.5 rounded-sm bg-emerald-400 mr-1 align-middle" />
发送 <b className="dg-text-green">{currentTx}</b> {t('monitor.lbl.tx')} <b className="dg-text-green">{currentTx}</b>
</span> </span>
</div> </div>
<ChartToolbar <ChartToolbar
@@ -61,14 +62,14 @@ export function DualLineChart({
ref={chartRef} ref={chartRef}
data={chartData} data={chartData}
series={[ series={[
{ dataKey: 'rx', color: '#3b82f6', label: i18n.t('common.receive') }, { dataKey: 'rx', color: '#3b82f6', label: t('common.receive') },
{ dataKey: 'tx', color: '#10b981', label: i18n.t('aiops.sendMessage') }, { dataKey: 'tx', color: '#10b981', label: t('aiops.sendMessage') },
]} ]}
yDomain={[0, 'auto']} yDomain={[0, 'auto']}
unit={unit} unit={unit}
chartId={`dual-${title.replace(/\s/g, '')}`} chartId={`dual-${title.replace(/\s/g, '')}`}
title={title} title={title}
subtitle={i18n.t('monitor.lbl.receiveSend', { rx: currentRx, tx: currentTx })} subtitle={t('monitor.lbl.receiveSend', { rx: currentRx, tx: currentTx })}
height={208} height={208}
toolbarPlacement="header" toolbarPlacement="header"
onZoomedChange={setZoomed} onZoomedChange={setZoomed}
@@ -200,9 +200,9 @@ export function HealthProbeSection({ selectedHost }: HealthProbeSectionProps) {
{probeForm.probe_type === 'tcp_socket' && ( {probeForm.probe_type === 'tcp_socket' && (
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-2 gap-2">
<div><label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block">TCP 目标地址</label> <div><label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block">{t('monitor.lbl.tcpTargetAddr')}</label>
<input className="w-full h-8 bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded px-2 text-xs text-[var(--dg-text-primary)]" value={probeForm.tcp_host} onChange={e => setProbeForm({ ...probeForm, tcp_host: e.target.value })} placeholder="localhost" /></div> <input className="w-full h-8 bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded px-2 text-xs text-[var(--dg-text-primary)]" value={probeForm.tcp_host} onChange={e => setProbeForm({ ...probeForm, tcp_host: e.target.value })} placeholder="localhost" /></div>
<div><label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block">TCP 端口</label> <div><label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block">{t('monitor.lbl.tcpPort')}</label>
<input type="number" className="w-full h-8 bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded px-2 text-xs text-[var(--dg-text-primary)]" value={probeForm.tcp_port} onChange={e => setProbeForm({ ...probeForm, tcp_port: Number(e.target.value) })} /></div> <input type="number" className="w-full h-8 bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded px-2 text-xs text-[var(--dg-text-primary)]" value={probeForm.tcp_port} onChange={e => setProbeForm({ ...probeForm, tcp_port: Number(e.target.value) })} /></div>
</div> </div>
)} )}
@@ -58,7 +58,7 @@ export function HostTimeRangePicker({ value, onChange }: Props) {
<div className="flex items-center gap-2 flex-wrap"> <div className="flex items-center gap-2 flex-wrap">
<span className="text-[11px] dg-text-muted hidden sm:inline-flex items-center gap-1"> <span className="text-[11px] dg-text-muted hidden sm:inline-flex items-center gap-1">
<Timer className="h-3 w-3" /> <Timer className="h-3 w-3" />
时间范围 {t('monitor.lbl.timeRange')}
</span> </span>
<div className="flex items-center gap-1 rounded-md border border-[var(--dg-border)] p-0.5 bg-[var(--dg-bg-secondary)] flex-wrap"> <div className="flex items-center gap-1 rounded-md border border-[var(--dg-border)] p-0.5 bg-[var(--dg-bg-secondary)] flex-wrap">
{TIME_RANGE_PRESETS.map((p) => ( {TIME_RANGE_PRESETS.map((p) => (
@@ -82,7 +82,7 @@ export function HostTimeRangePicker({ value, onChange }: Props) {
onClick={() => setCustomOpen((v) => !v)} onClick={() => setCustomOpen((v) => !v)}
> >
<Calendar className="h-3 w-3" /> <Calendar className="h-3 w-3" />
自定义 {t('monitor.lbl.custom')}
<ChevronDown className={`h-3 w-3 transition-transform ${customOpen ? 'rotate-180' : ''}`} /> <ChevronDown className={`h-3 w-3 transition-transform ${customOpen ? 'rotate-180' : ''}`} />
</Button> </Button>
{customOpen && ( {customOpen && (
@@ -110,10 +110,10 @@ export function HostTimeRangePicker({ value, onChange }: Props) {
</div> </div>
<div className="flex justify-end gap-2 mt-3"> <div className="flex justify-end gap-2 mt-3">
<Button type="button" variant="ghost" size="sm" className="h-7 text-xs" onClick={() => setCustomOpen(false)}> <Button type="button" variant="ghost" size="sm" className="h-7 text-xs" onClick={() => setCustomOpen(false)}>
取消 {t('common.cancel')}
</Button> </Button>
<Button type="button" size="sm" className="h-7 text-xs dg-btn-primary" onClick={applyCustom}> <Button type="button" size="sm" className="h-7 text-xs dg-btn-primary" onClick={applyCustom}>
应用 {t('monitor.lbl.apply')}
</Button> </Button>
</div> </div>
</div> </div>
@@ -43,13 +43,13 @@ export function PortPolicyDialog({
className={`flex-1 h-9 rounded text-xs font-medium border transition-colors ${portPolicyMode === 'whitelist' ? 'bg-blue-500/20 border-blue-500 dg-text-blue' : 'bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] text-[var(--dg-text-muted)]'}`} className={`flex-1 h-9 rounded text-xs font-medium border transition-colors ${portPolicyMode === 'whitelist' ? 'bg-blue-500/20 border-blue-500 dg-text-blue' : 'bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] text-[var(--dg-text-muted)]'}`}
onClick={() => onModeChange('whitelist')} onClick={() => onModeChange('whitelist')}
> >
白名单 ─ 全量上报 {t('monitor.lbl.whitelistFullReport')}
</button> </button>
<button <button
className={`flex-1 h-9 rounded text-xs font-medium border transition-colors ${portPolicyMode === 'blacklist' ? 'bg-amber-500/20 border-amber-500 dg-text-yellow' : 'bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] text-[var(--dg-text-muted)]'}`} className={`flex-1 h-9 rounded text-xs font-medium border transition-colors ${portPolicyMode === 'blacklist' ? 'bg-amber-500/20 border-amber-500 dg-text-yellow' : 'bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] text-[var(--dg-text-muted)]'}`}
onClick={() => onModeChange('blacklist')} onClick={() => onModeChange('blacklist')}
> >
黑名单 ─ 仅上报选中 {t('monitor.lbl.blacklistSelectedOnly')}
</button> </button>
</div> </div>
<p className="text-[10px] text-[var(--dg-text-muted)] mt-1.5 leading-relaxed"> <p className="text-[10px] text-[var(--dg-text-muted)] mt-1.5 leading-relaxed">
@@ -61,8 +61,8 @@ export function PortPolicyDialog({
<div> <div>
<label className="text-[11px] text-[var(--dg-text-muted)] mb-1.5 block"> <label className="text-[11px] text-[var(--dg-text-muted)] mb-1.5 block">
端口列表 <span className="text-[var(--dg-text-muted)]">(逗号分隔,如: 80,443,3306,6379)</span> {t('monitor.lbl.portListLabel')} <span className="text-[var(--dg-text-muted)]">{t('monitor.lbl.portListHint')}</span>
{portPolicyMode === 'whitelist' && <span className="dg-text-blue ml-1">— 白名单模式无需配置</span>} {portPolicyMode === 'whitelist' && <span className="dg-text-blue ml-1">{t('monitor.lbl.whitelistNoConfig')}</span>}
</label> </label>
<textarea <textarea
className={`w-full h-20 bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded px-2 py-1.5 text-xs font-mono resize-none ${ className={`w-full h-20 bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded px-2 py-1.5 text-xs font-mono resize-none ${
@@ -78,7 +78,7 @@ export function PortPolicyDialog({
readOnly={portPolicyMode === 'whitelist'} readOnly={portPolicyMode === 'whitelist'}
/> />
<p className="text-[10px] text-[var(--dg-text-muted)] mt-1"> <p className="text-[10px] text-[var(--dg-text-muted)] mt-1">
已输入 {portWhitelist.length} 个端口 {t('monitor.lbl.portsEntered', { count: portWhitelist.length })}
</p> </p>
</div> </div>
@@ -286,7 +286,7 @@ export const ZoomableAreaChart = forwardRef<ZoomableChartHandle, Props>(function
className="flex items-center justify-center text-xs dg-text-muted border border-dashed border-[var(--dg-border)] rounded-md w-full" className="flex items-center justify-center text-xs dg-text-muted border border-dashed border-[var(--dg-border)] rounded-md w-full"
style={{ height }} style={{ height }}
> >
暂无数据 {t('monitor.lbl.noData')}
</div> </div>
); );
} }
@@ -16,18 +16,18 @@ export function ContextSwitchCard({ csInfo }: { csInfo: JsonData }) {
<div className="space-y-4"> <div className="space-y-4">
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div className="bg-[var(--dg-bg-primary)] rounded-lg p-3 text-center"> <div className="bg-[var(--dg-bg-primary)] rounded-lg p-3 text-center">
<div className="text-[10px] text-[var(--dg-text-muted)] mb-1">CTX 切换/秒</div> <div className="text-[10px] text-[var(--dg-text-muted)] mb-1">{t('monitor.lbl.ctxPerSec')}</div>
<div className="text-lg font-bold font-mono dg-text-green"> <div className="text-lg font-bold font-mono dg-text-green">
{(csInfo.ctx_per_sec / 1e6).toFixed(1)}<span className="text-xs font-normal text-[var(--dg-text-muted)] ml-0.5">M</span> {(csInfo.ctx_per_sec / 1e6).toFixed(1)}<span className="text-xs font-normal text-[var(--dg-text-muted)] ml-0.5">M</span>
</div> </div>
<div className="text-[10px] text-[var(--dg-text-muted)] mt-1 font-mono">{csInfo.ctx_per_sec?.toLocaleString()} 次</div> <div className="text-[10px] text-[var(--dg-text-muted)] mt-1 font-mono">{csInfo.ctx_per_sec?.toLocaleString()} {t('monitor.lbl.times')}</div>
</div> </div>
<div className="bg-[var(--dg-bg-primary)] rounded-lg p-3 text-center"> <div className="bg-[var(--dg-bg-primary)] rounded-lg p-3 text-center">
<div className="text-[10px] text-[var(--dg-text-muted)] mb-1">IRQ 中断/秒</div> <div className="text-[10px] text-[var(--dg-text-muted)] mb-1">{t('monitor.lbl.irqPerSec')}</div>
<div className="text-lg font-bold font-mono dg-text-yellow"> <div className="text-lg font-bold font-mono dg-text-yellow">
{(csInfo.irq_per_sec / 1e6).toFixed(1)}<span className="text-xs font-normal text-[var(--dg-text-muted)] ml-0.5">M</span> {(csInfo.irq_per_sec / 1e6).toFixed(1)}<span className="text-xs font-normal text-[var(--dg-text-muted)] ml-0.5">M</span>
</div> </div>
<div className="text-[10px] text-[var(--dg-text-muted)] mt-1 font-mono">{csInfo.irq_per_sec?.toLocaleString()} 次</div> <div className="text-[10px] text-[var(--dg-text-muted)] mt-1 font-mono">{csInfo.irq_per_sec?.toLocaleString()} {t('monitor.lbl.times')}</div>
</div> </div>
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
@@ -26,7 +26,7 @@ export function FDUsageCard({ fdInfo, fdMetrics, aggType, fdChartRef, fdChartZoo
<div className="flex items-start justify-between gap-2 mb-3"> <div className="flex items-start justify-between gap-2 mb-3">
<h3 className="text-xs font-medium text-[var(--dg-text-primary)] flex items-center gap-2 leading-8"> <h3 className="text-xs font-medium text-[var(--dg-text-primary)] flex items-center gap-2 leading-8">
<MetricIconBox icon={<HardDrive />} tone="purple" /> <MetricIconBox icon={<HardDrive />} tone="purple" />
FD 文件句柄 {t('monitor.lbl.fdHandles')}
</h3> </h3>
{fdMetrics.length > 0 && ( {fdMetrics.length > 0 && (
<ChartToolbar <ChartToolbar
@@ -23,22 +23,22 @@ export function PortMonitorTab({ portInfo, portPolicyMode, portWhitelist, onOpen
<span className="ml-auto flex items-center gap-2"> <span className="ml-auto flex items-center gap-2">
{portPolicyMode === 'blacklist' && portWhitelist.length > 0 && ( {portPolicyMode === 'blacklist' && portWhitelist.length > 0 && (
<Badge variant="outline" className="text-[10px] dg-text-yellow border-amber-500/30"> <Badge variant="outline" className="text-[10px] dg-text-yellow border-amber-500/30">
黑名单: {portWhitelist.join(',')} {t('monitor.lbl.blacklistLabel')}: {portWhitelist.join(',')}
</Badge> </Badge>
)} )}
{portPolicyMode === 'whitelist' && ( {portPolicyMode === 'whitelist' && (
<Badge variant="outline" className="text-[10px] dg-text-blue border-blue-500/30"> <Badge variant="outline" className="text-[10px] dg-text-blue border-blue-500/30">
白名单模式 {t('monitor.lbl.whitelistModeLabel')}
</Badge> </Badge>
)} )}
<Button variant="outline" size="sm" className="h-6 text-[10px] px-2" onClick={onOpenPortPolicy}> <Button variant="outline" size="sm" className="h-6 text-[10px] px-2" onClick={onOpenPortPolicy}>
策略配置 {t('monitor.lbl.policyConfig')}
</Button> </Button>
</span> </span>
</h3> </h3>
{portInfo.length === 0 ? ( {portInfo.length === 0 ? (
<div className="text-center py-6 text-xs text-[var(--dg-text-muted)] border border-dashed border-[var(--dg-border)] rounded-lg"> <div className="text-center py-6 text-xs text-[var(--dg-text-muted)] border border-dashed border-[var(--dg-border)] rounded-lg">
暂无端口数据 {t('monitor.lbl.noPortData')}
</div> </div>
) : ( ) : (
<div className="overflow-x-auto"> <div className="overflow-x-auto">
@@ -14,7 +14,7 @@ export function ProcessTable({ processTop }: { processTop: JsonData[] }) {
</h3> </h3>
{processTop.length === 0 ? ( {processTop.length === 0 ? (
<div className="text-center py-6 text-xs text-[var(--dg-text-muted)] border border-dashed border-[var(--dg-border)] rounded-lg"> <div className="text-center py-6 text-xs text-[var(--dg-text-muted)] border border-dashed border-[var(--dg-border)] rounded-lg">
Agent v2.0+ 支持,等待数据上报… {t('monitor.lbl.agentWaitData')}
</div> </div>
) : ( ) : (
<div className="overflow-x-auto"> <div className="overflow-x-auto">
@@ -24,7 +24,7 @@ export function ProcessTable({ processTop }: { processTop: JsonData[] }) {
<th className="py-1.5 text-left font-medium w-[40%]"> {t('monitor.lbl.processMonitor')} </th> <th className="py-1.5 text-left font-medium w-[40%]"> {t('monitor.lbl.processMonitor')} </th>
<th className="py-1.5 text-right font-medium w-[8%]">PID</th> <th className="py-1.5 text-right font-medium w-[8%]">PID</th>
<th className="py-1.5 text-right font-medium w-[16%]"> {t('monitor.lbl.processMonitor')} </th> <th className="py-1.5 text-right font-medium w-[16%]"> {t('monitor.lbl.processMonitor')} </th>
<th className="py-1.5 text-right font-medium w-[16%]">CPU使用率(%)</th> <th className="py-1.5 text-right font-medium w-[16%]">{t('monitor.lbl.cpuUsagePercent')}</th>
<th className="py-1.5 text-right font-medium w-[20%]"> {t('monitor.lbl.processMonitor')} </th> <th className="py-1.5 text-right font-medium w-[20%]"> {t('monitor.lbl.processMonitor')} </th>
</tr> </tr>
</thead> </thead>
@@ -98,7 +98,7 @@ export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) {
if (!task) { if (!task) {
const empty = ( const empty = (
<div className="p-8 text-center text-sm text-[var(--dg-text-muted)]"> <div className="p-8 text-center text-sm text-[var(--dg-text-muted)]">
选择任务查看协议专属可观测指标 {t('monitor.lbl.selectTaskHint')}
</div> </div>
); );
return ( return (
@@ -218,7 +218,7 @@ export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) {
</> </>
)} )}
<span className="text-[10px] text-[var(--dg-text-muted)] shrink-0"> <span className="text-[10px] text-[var(--dg-text-muted)] shrink-0">
{rangeLabel} · {results.length} 次 · 可用率 {availSummary} {rangeLabel} · {results.length} {t('monitor.lbl.times')} · {t('monitor.lbl.availabilityRate')} {availSummary}
</span> </span>
</div> </div>
<div className="flex items-center gap-1.5 shrink-0"> <div className="flex items-center gap-1.5 shrink-0">
@@ -235,7 +235,7 @@ export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) {
<RefreshCw className={`h-3 w-3 mr-1 ${loading ? 'animate-spin' : ''}`} />{t("common.refresh")} <RefreshCw className={`h-3 w-3 mr-1 ${loading ? 'animate-spin' : ''}`} />{t("common.refresh")}
</Button> </Button>
<Button size="sm" className="h-7 text-xs" onClick={handleProbeNow} disabled={probing}> <Button size="sm" className="h-7 text-xs" onClick={handleProbeNow} disabled={probing}>
{probing ? <Loader2 className="h-3 w-3 mr-1 animate-spin" /> : <Zap className="h-3 w-3 mr-1" />}立即拨测 {probing ? <Loader2 className="h-3 w-3 mr-1 animate-spin" /> : <Zap className="h-3 w-3 mr-1" />}{t('monitor.lbl.probeNow')}
</Button> </Button>
</div> </div>
</div> </div>
@@ -244,9 +244,9 @@ export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) {
<div className="mb-4 flex items-start gap-2 rounded-lg border border-amber-500/30 bg-amber-500/10 px-3 py-2.5 text-xs"> <div className="mb-4 flex items-start gap-2 rounded-lg border border-amber-500/30 bg-amber-500/10 px-3 py-2.5 text-xs">
<ShieldAlert className="h-4 w-4 shrink-0 mt-0.5 dg-text-yellow" /> <ShieldAlert className="h-4 w-4 shrink-0 mt-0.5 dg-text-yellow" />
<div> <div>
<p className="font-medium text-[var(--dg-text-primary)]">SSL 证书预警</p> <p className="font-medium text-[var(--dg-text-primary)]">{t('monitor.lbl.sslCertWarning')}</p>
<p className="mt-0.5 text-[var(--dg-text-muted)]"> <p className="mt-0.5 text-[var(--dg-text-muted)]">
到期 <span className="font-mono">{task.ssl_cert_expiry?.slice(0, 10) || '--'}</span>,剩余 <span className={sslInfo.tone}>{sslInfo.text}</span>(阈值 {SSL_EXPIRY_WARN_DAYS} 天) {t('monitor.lbl.sslExpiring')} <span className="font-mono">{task.ssl_cert_expiry?.slice(0, 10) || '--'}</span>,{t('monitor.lbl.sslRemaining')} <span className={sslInfo.tone}>{sslInfo.text}</span>({t('monitor.lbl.sslThreshold')} {SSL_EXPIRY_WARN_DAYS} {t('monitor.lbl.sslDays')})
</p> </p>
</div> </div>
</div> </div>