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:
1 parent
4e2ba77aa8
commit
a7838aaf65
23 files changed
+140
-57
No files matched your search
@@ -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** |
|
| 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** |
|
| 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** |
|
| 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 { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { Sparkles, Brain, Loader2 } from 'lucide-react';
|
import { Sparkles, Brain, Loader2 } from 'lucide-react';
|
||||||
import { AlertCircle, TrendingUp, CheckCircle, FileText, Terminal, Play } 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 type { AlertItem, AIAnalysis } from './analyzerHelpers';
|
||||||
import i18n from '@/i18n';
|
import i18n from '@/i18n';
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ interface AIAnalysisResultProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function AIAnalysisResult({ selectedAlert, isAnalyzing, analysis, onAutoFix }: AIAnalysisResultProps) {
|
export function AIAnalysisResult({ selectedAlert, isAnalyzing, analysis, onAutoFix }: AIAnalysisResultProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Card className="dg-card flex flex-col">
|
<Card className="dg-card flex flex-col">
|
||||||
<CardHeader className="border-b border-[var(--dg-border)] pb-3">
|
<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">
|
<div className="space-y-2">
|
||||||
<h4 className="text-sm font-medium text-[var(--dg-text-secondary)] flex items-center gap-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" />
|
<FileText className="h-4 w-4 dg-text-blue" />
|
||||||
证据链
|
{t('common.evidenceChain')}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{analysis.evidence.slice(0, 5).map((item, idx) => (
|
{analysis.evidence.slice(0, 5).map((item, idx) => (
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ export function buildCapacityInsightPrompt(forecasts: CapacityForecast[]): strin
|
|||||||
if (forecasts.length === 0) return '';
|
if (forecasts.length === 0) return '';
|
||||||
const lines = forecasts.map((f) => {
|
const lines = forecasts.map((f) => {
|
||||||
const risk = f.daysUntilThreshold >= 999 ? i18n.t('aiops.capacity.noRisk') : i18n.t('aiops.predictutilsts.daysUntilThreshold', { days: f.daysUntilThreshold });
|
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 [
|
return [
|
||||||
i18n.t('aiops.capacity.expertPrompt'),
|
i18n.t('aiops.capacity.expertPrompt'),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { JsonData } from "@/types/common";
|
import type { JsonData } from "@/types/common";
|
||||||
|
import i18n from "@/i18n";
|
||||||
// Agent 监控相关工具函数(供组件与 API 层共享) // Logan.Li
|
// Agent 监控相关工具函数(供组件与 API 层共享) // Logan.Li
|
||||||
|
|
||||||
/** 在线判定:3 分钟内有上报视为在线 */
|
/** 在线判定:3 分钟内有上报视为在线 */
|
||||||
@@ -15,9 +16,9 @@ export function timeAgo(ts: unknown): string {
|
|||||||
const t = new Date(ts as string | number | Date).getTime();
|
const t = new Date(ts as string | number | Date).getTime();
|
||||||
if (Number.isNaN(t)) return '--';
|
if (Number.isNaN(t)) return '--';
|
||||||
const diff = Math.floor((Date.now() - t) / 1000);
|
const diff = Math.floor((Date.now() - t) / 1000);
|
||||||
if (diff < 60) return `${diff}秒前`;
|
if (diff < 60) return i18n.t('common.duration.secAgo', { count: diff });
|
||||||
if (diff < 3600) return `${Math.floor(diff / 60)}分钟前`;
|
if (diff < 3600) return i18n.t('common.duration.minAgo', { count: Math.floor(diff / 60) });
|
||||||
if (diff < 86400) return `${Math.floor(diff / 3600)}小时前`;
|
if (diff < 86400) return i18n.t('common.duration.hourAgo', { count: Math.floor(diff / 3600) });
|
||||||
return new Date(ts as string | number | Date).toLocaleString();
|
return new Date(ts as string | number | Date).toLocaleString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ export function formatDistanceToNow(timestamp: number): string {
|
|||||||
if (seconds < 60) {
|
if (seconds < 60) {
|
||||||
return i18n.t('task.cronJob.time.justNow');
|
return i18n.t('task.cronJob.time.justNow');
|
||||||
} else if (minutes < 60) {
|
} else if (minutes < 60) {
|
||||||
return `${minutes}分钟前`;
|
return i18n.t('common.duration.minAgo', { count: minutes });
|
||||||
} else if (hours < 24) {
|
} else if (hours < 24) {
|
||||||
return `${hours}小时前`;
|
return i18n.t('common.duration.hourAgo', { count: hours });
|
||||||
} else if (days < 30) {
|
} else if (days < 30) {
|
||||||
return `${days}天前`;
|
return i18n.t('common.duration.dayAgo', { count: days });
|
||||||
} else {
|
} else {
|
||||||
return new Date(timestamp).toLocaleDateString();
|
return new Date(timestamp).toLocaleDateString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -181,7 +181,10 @@
|
|||||||
"minSec": "{{m}}m {{s}}s",
|
"minSec": "{{m}}m {{s}}s",
|
||||||
"hour": "{{count}}h",
|
"hour": "{{count}}h",
|
||||||
"hourMin": "{{h}}h {{m}}m",
|
"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:",
|
"ebCaughtError": "[ErrorBoundary] Caught error:",
|
||||||
"ebCopyError": "Copy Error",
|
"ebCopyError": "Copy Error",
|
||||||
@@ -623,7 +626,15 @@
|
|||||||
"fillIfMultiContainer": "(required for multi-container)",
|
"fillIfMultiContainer": "(required for multi-container)",
|
||||||
"confirmDeleteType": "Delete {{type}} \"{{name}}\"?",
|
"confirmDeleteType": "Delete {{type}} \"{{name}}\"?",
|
||||||
"unitCount": "",
|
"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": {
|
"menu": {
|
||||||
"aiAgentCli": "Agent CLI",
|
"aiAgentCli": "Agent CLI",
|
||||||
@@ -1478,7 +1489,15 @@
|
|||||||
"general": "General Approval"
|
"general": "General Approval"
|
||||||
},
|
},
|
||||||
"workflowSubtitle": "Configure multi-level approval workflows for resource, application, and permission requests",
|
"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": {
|
"audit": {
|
||||||
"csvHeader": "Time,User,Command,Output Size,Session ID,Critical",
|
"csvHeader": "Time,User,Command,Output Size,Session ID,Critical",
|
||||||
@@ -4423,7 +4442,16 @@
|
|||||||
"snmpVersion": "SNMP Version",
|
"snmpVersion": "SNMP Version",
|
||||||
"snmpNoDevice": "No SNMP devices. Click \"Add Device\" to start monitoring.",
|
"snmpNoDevice": "No SNMP devices. Click \"Add Device\" to start monitoring.",
|
||||||
"snmpCollectMetrics": "Collected Metrics",
|
"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": {
|
"logAlertBell": {
|
||||||
"linked": "Alert Policy Linked",
|
"linked": "Alert Policy Linked",
|
||||||
@@ -4813,7 +4841,8 @@
|
|||||||
"neverReport": "Never report",
|
"neverReport": "Never report",
|
||||||
"filterMode": "Filter Mode",
|
"filterMode": "Filter Mode",
|
||||||
"title": "Port Monitoring Policy",
|
"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",
|
"postgresql": "PostgreSQL",
|
||||||
"prevPage": "Previous",
|
"prevPage": "Previous",
|
||||||
@@ -5004,7 +5033,15 @@
|
|||||||
"probeDesc": "Configure probe tasks by protocol type, the system will automatically probe at intervals",
|
"probeDesc": "Configure probe tasks by protocol type, the system will automatically probe at intervals",
|
||||||
"taskNotFound": "Probe task does not exist or has been deleted",
|
"taskNotFound": "Probe task does not exist or has been deleted",
|
||||||
"clickToObserve": "Click to view details",
|
"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",
|
"siteProbe": "Site Probe",
|
||||||
"siteProbeTitle": "Site Probe Tasks",
|
"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:",
|
"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",
|
"resCpu": "Server CPU Compute",
|
||||||
"resMemory": "Server Memory",
|
"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)",
|
"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",
|
"capacityPlanning": "Capacity Planning",
|
||||||
|
|||||||
@@ -181,7 +181,10 @@
|
|||||||
"minSec": "{{m}}分{{s}}秒",
|
"minSec": "{{m}}分{{s}}秒",
|
||||||
"hour": "{{count}}时",
|
"hour": "{{count}}时",
|
||||||
"hourMin": "{{h}}时{{m}}分",
|
"hourMin": "{{h}}时{{m}}分",
|
||||||
"minAgo": "{{count}}分钟前"
|
"minAgo": "{{count}}分钟前",
|
||||||
|
"secAgo": "{{count}}秒前",
|
||||||
|
"hourAgo": "{{count}}小时前",
|
||||||
|
"dayAgo": "{{count}}天前"
|
||||||
},
|
},
|
||||||
"ebCaughtError": "[ErrorBoundary] 捕获到错误:",
|
"ebCaughtError": "[ErrorBoundary] 捕获到错误:",
|
||||||
"ebCopyError": "复制错误",
|
"ebCopyError": "复制错误",
|
||||||
@@ -623,7 +626,15 @@
|
|||||||
"fillIfMultiContainer": "(多容器时填写)",
|
"fillIfMultiContainer": "(多容器时填写)",
|
||||||
"confirmDeleteType": "确认删除 {{type}} \"{{name}}\"?",
|
"confirmDeleteType": "确认删除 {{type}} \"{{name}}\"?",
|
||||||
"unitCount": "个",
|
"unitCount": "个",
|
||||||
"fieldValues": "{{count}} 个值"
|
"fieldValues": "{{count}} 个值",
|
||||||
|
"selected": "{{count}} 已选",
|
||||||
|
"responseParseError": "响应解析失败",
|
||||||
|
"rdpActiveSessions": "RDP 在线会话",
|
||||||
|
"rdpHistorySessions": "RDP 历史会话",
|
||||||
|
"rdpSessionCount": "RDP 会话共 {{count}} 条",
|
||||||
|
"evidenceChain": "证据链",
|
||||||
|
"last1Hour": "最近 1 小时",
|
||||||
|
"currentHost": "当前主机: {{host}}"
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"aiAgentCli": "Agent CLI",
|
"aiAgentCli": "Agent CLI",
|
||||||
@@ -1478,7 +1489,15 @@
|
|||||||
"general": "通用审批"
|
"general": "通用审批"
|
||||||
},
|
},
|
||||||
"workflowSubtitle": "为资源申请、应用申请、权限审批配置多级审批流程",
|
"workflowSubtitle": "为资源申请、应用申请、权限审批配置多级审批流程",
|
||||||
"workflowTitle": "审批流程配置"
|
"workflowTitle": "审批流程配置",
|
||||||
|
"provider": {
|
||||||
|
"aliyun": "阿里云",
|
||||||
|
"tencent": "腾讯云",
|
||||||
|
"huawei": "华为云"
|
||||||
|
},
|
||||||
|
"permType": {
|
||||||
|
"bastion": "堡垒机"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"audit": {
|
"audit": {
|
||||||
"csvHeader": "时间,用户,命令,输出大小,会话ID,高危",
|
"csvHeader": "时间,用户,命令,输出大小,会话ID,高危",
|
||||||
@@ -4423,7 +4442,16 @@
|
|||||||
"snmpVersion": "SNMP 版本",
|
"snmpVersion": "SNMP 版本",
|
||||||
"snmpNoDevice": "暂无 SNMP 设备,点击「添加设备」开始监控",
|
"snmpNoDevice": "暂无 SNMP 设备,点击「添加设备」开始监控",
|
||||||
"snmpCollectMetrics": "采集指标",
|
"snmpCollectMetrics": "采集指标",
|
||||||
"snmpNoMetrics": "暂无指标,请先采集"
|
"snmpNoMetrics": "暂无指标,请先采集",
|
||||||
|
"noTrendData": "暂无趋势数据",
|
||||||
|
"noTrendDataWaiting": "暂无趋势数据,等待后台采集",
|
||||||
|
"selectInstanceHint": "选择左侧实例查看趋势与健康度",
|
||||||
|
"clickDeviceHint": "点击设备行查看 CPU / 内存 / 温度 / 风扇趋势图",
|
||||||
|
"noMetricsHint": "暂无可视化指标,请先完成至少一次采集",
|
||||||
|
"usedHint": "已用 {{value}}",
|
||||||
|
"clusterNodesHint": "集群 {{value}} 节点",
|
||||||
|
"slowQueriesHint": "慢查询 {{value}}",
|
||||||
|
"pointCount": "{{min}} — {{max}} · {{count}} 点"
|
||||||
},
|
},
|
||||||
"logAlertBell": {
|
"logAlertBell": {
|
||||||
"linked": "已关联告警策略",
|
"linked": "已关联告警策略",
|
||||||
@@ -4813,7 +4841,8 @@
|
|||||||
"neverReport": "永远不上报",
|
"neverReport": "永远不上报",
|
||||||
"filterMode": "过滤模式",
|
"filterMode": "过滤模式",
|
||||||
"title": "端口监控策略",
|
"title": "端口监控策略",
|
||||||
"desc": "配置 Agent 端口上报过滤规则"
|
"desc": "配置 Agent 端口上报过滤规则",
|
||||||
|
"systemProcessDesc": "⚠️ 无论选择哪种模式,以下系统进程端口 {{neverReport}}:sshd、rpcbind、master、systemd、dbus-daemon、rsyslogd 等。Agent 采集时自动跳过。"
|
||||||
},
|
},
|
||||||
"postgresql": "PostgreSQL",
|
"postgresql": "PostgreSQL",
|
||||||
"prevPage": "上一页",
|
"prevPage": "上一页",
|
||||||
@@ -5004,7 +5033,15 @@
|
|||||||
"probeDesc": "按协议类型配置探活任务,系统将按间隔自动拨测",
|
"probeDesc": "按协议类型配置探活任务,系统将按间隔自动拨测",
|
||||||
"taskNotFound": "拨测任务不存在或已删除",
|
"taskNotFound": "拨测任务不存在或已删除",
|
||||||
"clickToObserve": "点击可观测进入详情页",
|
"clickToObserve": "点击可观测进入详情页",
|
||||||
"alerting": "告警中"
|
"alerting": "告警中",
|
||||||
|
"recommendFreq": "推荐 {{freq}},最小 {{minFreq}}",
|
||||||
|
"seconds": "{{count}}秒",
|
||||||
|
"minutes": "{{count}}分钟",
|
||||||
|
"hours": "{{count}}小时",
|
||||||
|
"certDays": "{{count}}天",
|
||||||
|
"certRemaining": "证书剩余 {{days}} 天",
|
||||||
|
"checkFailed": "{{label}}失败",
|
||||||
|
"latencyFluctuation": "{{label}}延迟波动"
|
||||||
},
|
},
|
||||||
"siteProbe": "站点拨测",
|
"siteProbe": "站点拨测",
|
||||||
"siteProbeTitle": "站点拨测任务",
|
"siteProbeTitle": "站点拨测任务",
|
||||||
@@ -7933,7 +7970,8 @@
|
|||||||
"expertPrompt": "你是 AIOps 容量规划专家。基于以下服务器资源容量预测数据,给出综合风险评估与可执行的运维建议:",
|
"expertPrompt": "你是 AIOps 容量规划专家。基于以下服务器资源容量预测数据,给出综合风险评估与可执行的运维建议:",
|
||||||
"resCpu": "服务器CPU算力",
|
"resCpu": "服务器CPU算力",
|
||||||
"resMemory": "服务器内存",
|
"resMemory": "服务器内存",
|
||||||
"resDisk": "存储磁盘空间"
|
"resDisk": "存储磁盘空间",
|
||||||
|
"summary": "- {{resource}}:当前 {{current}}% → 7天后预测 {{predicted}}%(阈值 {{threshold}}%),{{risk}}"
|
||||||
},
|
},
|
||||||
"capacityPlanPrompt": "根据以下主机资源使用数据,提供成本优化和容量规划建议(识别闲置/过载主机,给出缩容/扩容建议)",
|
"capacityPlanPrompt": "根据以下主机资源使用数据,提供成本优化和容量规划建议(识别闲置/过载主机,给出缩容/扩容建议)",
|
||||||
"capacityPlanning": "容量规划",
|
"capacityPlanning": "容量规划",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ function ApprovalPermissionPage() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const PERMISSION_FIELDS: FormField[] = [
|
const PERMISSION_FIELDS: FormField[] = [
|
||||||
{ key: 'perm_type', label: t('approval.type.permission'), type: 'select', options: [
|
{ 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 },
|
], required: true },
|
||||||
{ key: 'account', label: 'Account', type: 'text', placeholder: 'logan.li', required: true },
|
{ key: 'account', label: 'Account', type: 'text', placeholder: 'logan.li', required: true },
|
||||||
{ key: 'reason', label: 'Reason', type: 'textarea', placeholder: '', required: true },
|
{ key: 'reason', label: 'Reason', type: 'textarea', placeholder: '', required: true },
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { ApprovalCategoryPage, type FormField } from './ApprovalCategoryPage';
|
|||||||
function ApprovalResourcePage() {
|
function ApprovalResourcePage() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const RESOURCE_FIELDS: FormField[] = [
|
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: '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: 'spec', label: 'Spec', type: 'select', options: ['2C4G', '4C8G', '8C16G', '16C32G', '32C64G'], required: true },
|
||||||
{ key: 'disk', label: 'Disk(GB)', type: 'number', defaultValue: 40, 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">
|
<Card className="dg-card">
|
||||||
<CardContent className="p-0">
|
<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 ? (
|
{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>
|
<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 ? (
|
) : rdpSessions.length === 0 ? (
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ export function HistorySessionsPanel() {
|
|||||||
|
|
||||||
<Card className="dg-card">
|
<Card className="dg-card">
|
||||||
<CardContent className="p-0">
|
<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 ? (
|
{rdpQuery.isLoading ? (
|
||||||
<div className="flex items-center justify-center py-12">
|
<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" />
|
<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>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
<div className="flex items-center justify-between px-4 py-3 border-t border-[var(--dg-border)]">
|
<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">
|
<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)}>
|
<Button variant="ghost" size="icon" className="h-8 w-8" disabled={rdpPage <= 1} onClick={() => setRdpPage((p) => p - 1)}>
|
||||||
<ChevronLeft className="h-4 w-4" />
|
<ChevronLeft className="h-4 w-4" />
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ export function HostMonitor({ embedded = false }: { embedded?: boolean }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PageShell variant="workspace" title={t('monitor.alertRulesTab.catHost')}
|
<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}
|
icon={<Server className="h-5 w-5" />} toolbar={toolbar}
|
||||||
loading={loading && !history.length} loadingLabel={t('monitor.hostMonitor.loadingData')}
|
loading={loading && !history.length} loadingLabel={t('monitor.hostMonitor.loadingData')}
|
||||||
contentClassName="space-y-3">
|
contentClassName="space-y-3">
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export function MetricsExplorer() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={() => { setSelectedMetrics([]); setQueryResults([]); }}>
|
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={() => { setSelectedMetrics([]); setQueryResults([]); }}>
|
||||||
清除
|
{t('common.clear')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -163,7 +163,7 @@ export function MetricsExplorer() {
|
|||||||
<div className="flex items-center gap-2 mb-3">
|
<div className="flex items-center gap-2 mb-3">
|
||||||
<BarChart3 className="h-4 w-4 dg-text-muted" />
|
<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>
|
<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>
|
||||||
<div className="space-y-4 max-h-[60vh] overflow-y-auto">
|
<div className="space-y-4 max-h-[60vh] overflow-y-auto">
|
||||||
{Object.entries(grouped).map(([cat, names]) => (
|
{Object.entries(grouped).map(([cat, names]) => (
|
||||||
@@ -203,7 +203,7 @@ export function MetricsExplorer() {
|
|||||||
<div className="flex items-center gap-2 mb-3">
|
<div className="flex items-center gap-2 mb-3">
|
||||||
<Activity className="h-4 w-4 dg-text-muted" />
|
<Activity className="h-4 w-4 dg-text-muted" />
|
||||||
<h3 className="text-sm font-medium text-[var(--dg-text-primary)]">
|
<h3 className="text-sm font-medium text-[var(--dg-text-primary)]">
|
||||||
{target?.name || ''} — 最近 1 小时
|
{target?.name || ''} — {t('common.last1Hour')}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
{selectedMetrics.length === 0 ? (
|
{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">
|
<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">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// 主机监控工具函数 // Logan.Li
|
// 主机监控工具函数 // Logan.Li
|
||||||
|
import i18n from '@/i18n';
|
||||||
import type { BucketPoint, HostMonitorTimeRange } from './types';
|
import type { BucketPoint, HostMonitorTimeRange } from './types';
|
||||||
import type { JsonData } from '@/types/common';
|
import type { JsonData } from '@/types/common';
|
||||||
/** 按时间跨度格式化 X 轴标签(避免 7 天范围只显示时分) // Logan.Li */
|
/** 按时间跨度格式化 X 轴标签(避免 7 天范围只显示时分) // Logan.Li */
|
||||||
@@ -175,7 +176,7 @@ export function getActualDataSpan(history: JsonData[], _range: HostMonitorTimeRa
|
|||||||
const min = Math.min(...ts);
|
const min = Math.min(...ts);
|
||||||
const max = Math.max(...ts);
|
const max = Math.max(...ts);
|
||||||
const spanMin = Math.max(1, Math.round((max - min) / 60));
|
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,
|
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 (
|
return (
|
||||||
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] h-full">
|
<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)]">
|
<CardContent className="p-8 text-center text-sm text-[var(--dg-text-muted)]">
|
||||||
选择左侧实例查看趋势与健康度
|
{t('monitor.lbl.selectInstanceHint')}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
@@ -127,14 +127,14 @@ export function MwTargetChartsPanel({ target, chartDefs, accent }: Props) {
|
|||||||
value={memPct ?? (m.redis_used_memory > 0 ? 50 : 0)}
|
value={memPct ?? (m.redis_used_memory > 0 ? 50 : 0)}
|
||||||
display={fmtMemory(m.redis_used_memory)}
|
display={fmtMemory(m.redis_used_memory)}
|
||||||
level={memPct != null ? gaugeTone(memPct, 80, 90) : 'ok'}
|
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
|
<MwGaugeBar
|
||||||
label={t('kubernetes.topology.connections')}
|
label={t('kubernetes.topology.connections')}
|
||||||
value={Math.min(100, (m.redis_connected_clients || 0) / 2)}
|
value={Math.min(100, (m.redis_connected_clients || 0) / 2)}
|
||||||
display={fmtVal(m.redis_connected_clients)}
|
display={fmtVal(m.redis_connected_clients)}
|
||||||
level={(m.redis_blocked_clients || 0) > 0 ? 'warn' : 'ok'}
|
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)}
|
value={Math.min(100, (m.mysql_threads_running || 0) * 5)}
|
||||||
display={fmtVal(m.mysql_threads_running)}
|
display={fmtVal(m.mysql_threads_running)}
|
||||||
level={(m.mysql_threads_running || 0) > 20 ? 'warn' : 'ok'}
|
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
|
// 中间件指标趋势图 — 光标拖拽选区 // Logan.Li
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import i18n from '@/i18n';
|
||||||
import {
|
import {
|
||||||
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
|
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
|
||||||
ReferenceArea,
|
ReferenceArea,
|
||||||
@@ -41,7 +42,7 @@ export function MwTrendChart({
|
|||||||
if (chartData.length === 0) {
|
if (chartData.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center text-xs text-[var(--dg-text-muted)]" style={{ height }}>
|
<div className="flex items-center justify-center text-xs text-[var(--dg-text-muted)]" style={{ height }}>
|
||||||
暂无趋势数据
|
{i18n.t('monitor.lbl.noTrendData')}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,8 +245,8 @@ export function fmtLastSeen(iso?: string): string {
|
|||||||
const d = new Date(iso);
|
const d = new Date(iso);
|
||||||
if (Number.isNaN(d.getTime())) return '--';
|
if (Number.isNaN(d.getTime())) return '--';
|
||||||
const diff = Math.floor((Date.now() - d.getTime()) / 1000);
|
const diff = Math.floor((Date.now() - d.getTime()) / 1000);
|
||||||
if (diff < 60) return `${diff}s 前`;
|
if (diff < 60) return i18n.t('common.duration.secAgo', { count: diff });
|
||||||
if (diff < 3600) return `${Math.floor(diff / 60)}m 前`;
|
if (diff < 3600) return i18n.t('common.duration.minAgo', { count: Math.floor(diff / 60) });
|
||||||
if (diff < 86400) return `${Math.floor(diff / 3600)}h 前`;
|
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')}`;
|
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 {
|
export function probeFrequencyHint(type: ProbeType): string {
|
||||||
const sec = defaultProbeFrequency(type);
|
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 */
|
/** 拨测频率展示 // Logan.Li */
|
||||||
export function fmtFrequency(sec?: number): string {
|
export function fmtFrequency(sec?: number): string {
|
||||||
if (!sec) return '--';
|
if (!sec) return '--';
|
||||||
if (sec < 60) return `${sec}秒`;
|
if (sec < 60) return i18n.t('monitor.siteMonitor.seconds', { count: sec });
|
||||||
if (sec < 3600) return `${Math.round(sec / 60)}分钟`;
|
if (sec < 3600) return i18n.t('monitor.siteMonitor.minutes', { count: Math.round(sec / 60) });
|
||||||
return `${(sec / 3600).toFixed(1)}小时`;
|
return i18n.t('monitor.siteMonitor.hours', { count: (sec / 3600).toFixed(1) });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 可用率 / 响应时间组合展示 // Logan.Li */
|
/** 可用率 / 响应时间组合展示 // 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 } {
|
export function fmtSslDays(days?: number): { text: string; tone: string } {
|
||||||
if (days == null) return { text: '--', tone: 'text-[var(--dg-text-muted)]' };
|
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 < 0) return { text: i18n.t('system.expired2'), tone: 'dg-text-red' };
|
||||||
if (days <= 7) return { text: `${days}天`, tone: 'dg-text-red' };
|
if (days <= 7) return { text: i18n.t('monitor.siteMonitor.certDays', { count: days }), tone: 'dg-text-red' };
|
||||||
if (days <= 30) return { text: `${days}天`, tone: 'dg-text-yellow' };
|
if (days <= 30) return { text: i18n.t('monitor.siteMonitor.certDays', { count: days }), tone: 'dg-text-yellow' };
|
||||||
return { text: `${days}天`, tone: 'dg-text-green' };
|
return { text: i18n.t('monitor.siteMonitor.certDays', { count: days }), tone: 'dg-text-green' };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 归一化拨测协议类型 // Logan.Li */
|
/** 归一化拨测协议类型 // Logan.Li */
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ export function analyzeSslResults(results: SiteResult[], task: SiteTask): SslObs
|
|||||||
if (r.status !== 'success') {
|
if (r.status !== 'success') {
|
||||||
pushEvent(events, r, i18n.t('monitor.lbl.siteRequestTimeout'), '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, i18n.t('monitor.siteMonitor.certRemaining', { days: r.ssl_days_remaining }), r.ssl_days_remaining <= 7 ? 'error' : 'warn', expiry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const chart = tooltipOrder(results)
|
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 latency = r.response_time ?? r.total_time ?? 0;
|
||||||
const failed = r.status !== 'success';
|
const failed = r.status !== 'success';
|
||||||
const spike = !failed && latency > spikeThreshold;
|
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) {
|
else if (spike) {
|
||||||
spikeCount++;
|
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 };
|
return { time: fmtTime(r.checked_at).slice(-5), latency, failed, spike };
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export function SnmpDeviceChartsPanel({ device, templateName }: Props) {
|
|||||||
return (
|
return (
|
||||||
<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)]">
|
||||||
点击设备行查看 CPU / 内存 / 温度 / 风扇趋势图
|
{t('monitor.lbl.clickDeviceHint')}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
@@ -104,7 +104,7 @@ export function SnmpDeviceChartsPanel({ device, templateName }: Props) {
|
|||||||
<div className="flex items-center gap-2 min-w-0">
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
<Activity className="h-4 w-4 text-[var(--dg-text-muted)] shrink-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">
|
<h3 className="text-sm font-medium text-[var(--dg-text-primary)] truncate">
|
||||||
可观测 · {device.name}
|
{t('monitor.lbl.observability')} · {device.name}
|
||||||
</h3>
|
</h3>
|
||||||
<Badge variant="outline" className="text-[10px] border-[var(--dg-border)] shrink-0">
|
<Badge variant="outline" className="text-[10px] border-[var(--dg-border)] shrink-0">
|
||||||
{templateName}
|
{templateName}
|
||||||
@@ -123,7 +123,7 @@ export function SnmpDeviceChartsPanel({ device, templateName }: Props) {
|
|||||||
</Select>
|
</Select>
|
||||||
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={loadCharts} disabled={loading}>
|
<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' : ''}`} />
|
<RefreshCw className={`h-3 w-3 mr-1 ${loading ? 'animate-spin' : ''}`} />
|
||||||
刷新
|
{t('common.refresh')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -147,7 +147,7 @@ export function SnmpDeviceChartsPanel({ device, templateName }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
) : charts.length === 0 ? (
|
) : charts.length === 0 ? (
|
||||||
<div className="py-12 text-center text-sm text-[var(--dg-text-muted)]">
|
<div className="py-12 text-center text-sm text-[var(--dg-text-muted)]">
|
||||||
暂无可视化指标,请先完成至少一次采集
|
{t('monitor.lbl.noMetricsHint')}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// SNMP 指标趋势折线图 — 光标拖拽选区 // Logan.Li
|
// SNMP 指标趋势折线图 — 光标拖拽选区 // Logan.Li
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import i18n from '@/i18n';
|
||||||
import {
|
import {
|
||||||
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
|
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
|
||||||
ReferenceArea,
|
ReferenceArea,
|
||||||
@@ -30,7 +31,7 @@ export function SnmpMetricTrendChart({ data, color, unit, height = 160 }: Props)
|
|||||||
if (chartData.length === 0) {
|
if (chartData.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center text-xs text-[var(--dg-text-muted)]" style={{ height }}>
|
<div className="flex items-center justify-center text-xs text-[var(--dg-text-muted)]" style={{ height }}>
|
||||||
暂无趋势数据,等待后台采集
|
{i18n.t('monitor.lbl.noTrendDataWaiting')}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ async function request<T = any>(
|
|||||||
try {
|
try {
|
||||||
json = await response.json();
|
json = await response.json();
|
||||||
} catch {
|
} 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)) {
|
if (!response.ok || (json.code && json.code !== 200)) {
|
||||||
|
|||||||
Reference in new issue
Block a user