diff --git a/ROADMAP.md b/ROADMAP.md index c0c6654..65cfc3a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -515,6 +515,7 @@ services/api.ts (1324L) | U19 | i18n hash 键清理(删除 866 个死 hash 键 + 重命名 21 个高频 live hash 键为语义化键名,zh/en 7597→6731 keys 对齐,文件 328KB→276KB (-16%),tsc -b=0,npm build✓,ESLint=0/66/0,17/17 测试通过) | P3 | **done** | | U20 | ESLint static-components 清零 + CredDetailDialog 内联组件修复 + i18n 批量迁移损坏修复(CredDetailDialog.tsx `Field` 内联组件提取为模块级 `CredField` — 23 处 react-hooks/static-components 警告清零;修复 8 处被批量正则损坏的 `i18n.t()` 调用(`i18n.t('>名称:')` 等 → 语义化键);6 处硬编码中文清零;新增 `deploy.cred.sensitiveWarn` 键;ESLint warnings 66→43 (-35%),tsc -b=0,npm build✓ (17.29s),17/17 测试通过) | P3 | **done** | | U23 | Task 模块 i18n 治理(CronJobManager + CronJobFormDialog + ScriptManager + RemoteExecPage 4 文件 — `import i18n` 全局调用 → `useTranslation()` hook;42 处 phantom key (`i18n.t('Chinese text')` → 语义化 `task.cronJob.*`/`task.scriptList.*`/`task.remoteExec.*` 键);28 个孤儿 hash 键清理 (cronjobformdial.s*/remoteexecpaget.s*/systemtaskspanel.s*);50+ 处硬编码 JSX 中文 → t() 调用;zh/en 6825→6886 +61 键对齐,tsc -b=0,npm build✓ (17.46s),ESLint=0/45/0,go build=0,17/17 测试通过) | P3 | **done** | +| U24 | Monitor 模块 i18n hook 迁移(26 个 .tsx 文件从 `import i18n` 全局 → `useTranslation()` hook + 89 个 phantom 中文文本键 → `monitor.lbl.*` 语义化键;299 处 `i18n.t()` 调用 → 74 处保留(模块级/.ts 工具文件) + 225 处迁移到 `t()` hook;1 个 .ts 工具文件 helpers.ts 中文文本键替换为语义化键;zh/en 6886→6975 +89 键对齐,tsc -b=0,npm build✓ (17.16s),ESLint=0/53/0,go build=0,Docker healthy) | P3 | **done** | --- @@ -1501,3 +1502,26 @@ pending → in_progress → done / blocked - [x] 17/17 集成回归测试通过(零回归) - [x] Docker 部署 healthy,前端页面正常加载(HTTP 200) +--- + +## U24: Monitor 模块 i18n hook 迁移 [done] + +**问题**: `src/pages/monitor/` 目录下 26 个 .tsx 文件仍使用全局 `import i18n from '@/i18n'` + `i18n.t('Chinese text')` 模式(phantom key 问题 — 中文文本作为 i18n key,在 zh-CN locale 下「恰好工作」(返回 key 本身),但切换 en-US 时仍显示中文文本而非英文翻译)。这是 U17-U23 i18n 治理系列中最后一个大规模 phantom key 模块,涉及 299 处 `i18n.t()` 调用和 89 个唯一 phantom 中文文本键。 + +**已完成 (2026-07-11)**: + +1. **新增 `monitor.lbl` 翻译子命名空间** — 89 个语义化翻译键覆盖 5 个子模块(HostMonitor/MiddlewareMonitor/SiteMonitor/SNMPMonitor/Shared) +2. **.tsx 文件 hook 迁移** — 26 个文件从 `import i18n` → `useTranslation()` hook + phantom 中文文本键 → `monitor.lbl.*` 语义化键 +3. **模块级代码保留 `i18n.t()`** — TABS 常量/METRIC_CATEGORIES 等模块级代码保留 `i18n.t()` + 语义化键 +4. **.ts 工具文件** — helpers.ts 中文文本键替换为语义化键(保留 `i18n.t()` 因 .ts 无法用 hook) + +**验收标准**: +- [x] `monitor.lbl` 命名空间 89 个语义化键,zh/en 完全对齐(6975 = 6975) +- [x] 26 个 .tsx 文件迁移到 `useTranslation()` hook +- [x] 299 处 `i18n.t()` → 74 保留 + 225 迁移到 `t()` hook +- [x] `tsc -b` 零错误 +- [x] `npm run build` 零错误(✓ built in 17.16s) +- [x] ESLint errors=0,any=0,unused=0 +- [x] `go build` 零错误 +- [x] Docker 部署 healthy + diff --git a/frontend/src/locales/en-US.json b/frontend/src/locales/en-US.json index ea14c32..684a59c 100644 --- a/frontend/src/locales/en-US.json +++ b/frontend/src/locales/en-US.json @@ -4492,6 +4492,97 @@ "userPolicyHint": "Users without any policy are not blocked (backward compatible); once assigned, only queries matching policies are allowed.", "assign": "Assign", "all": "All" + }, + "lbl": { + "unlimited": "Unlimited", + "hitRate": "Hit Rate", + "totalDevices": "Total Devices", + "instances": "Instances", + "instanceStatus": "Instance Status", + "connections": "Connections", + "online": "Online", + "fragmentationRate": "Fragmentation Rate", + "trafficSent": "Traffic Sent", + "evictedKeys": "Evicted Keys", + "noMaxMemory": "No maxmemory configured", + "standalone": "Standalone", + "middleware": "Middleware", + "hostMonitor": "Host Monitor", + "basicMonitor": "Basic Monitor", + "diskMonitor": "Disk Monitor", + "networkMonitor": "Network Monitor", + "processMonitor": "Process Monitor", + "selectHost": "Select host...", + "selectHostHint": "Select a host to view metrics", + "coreMetrics": "Core Metrics", + "allMetrics": "All Metrics", + "cpuUsage": "CPU Usage (%)", + "cpuIowait": "CPU IO Wait (%)", + "memUsage": "Memory Usage (%)", + "memAvailable": "Available Memory (GB)", + "swapUsage": "Swap Usage (%)", + "load1": "System Load (1m)", + "diskUsage": "Disk Usage (%)", + "diskRead": "Disk Read (KB/s)", + "diskWrite": "Disk Write (KB/s)", + "diskIoBusy": "Disk IO Busy (%)", + "fdUsage": "FD Usage Rate", + "fdRate": "FD Usage", + "nicRxErrors": "NIC RX Errors", + "nicTxErrors": "NIC TX Errors", + "nicBandwidth": "NIC Bandwidth", + "saving": "Saving...", + "allPortsReport": "All discovered ports are reported, any port change triggers alert + recovery.", + "selectedOnlyReport": "Only selected port changes are reported, others are silently ignored. System process ports are not alerted by default.", + "whitelistAutoReport": "In whitelist mode, all ports are automatically reported", + "nameIpRequired": "Name and IP are required", + "updateSuccess": "Updated successfully", + "editMw": "Edit Middleware", + "addMw": "Add Middleware", + "siteProbe": "Site Probe", + "availability24h": "Availability (24h)", + "totalProbes": "Total Probes", + "editTask": "Edit Probe Task", + "addProbe": "Add Probe", + "editProbe": "Edit Probe", + "deleteProbe": "Delete Probe", + "selectTemplate": "Select Template", + "deleteMonitor": "Delete Monitor", + "partialFail": "Partial Failure", + "defaultAdded": "Default monitoring added", + "defaultExists": "Default target already exists", + "collectDone": "Collection completed", + "deleteFail": "Delete failed", + "nameAddrRequired": "Name and address are required", + "taskUpdated": "Task updated", + "taskCreated": "Probe task created", + "taskDeleted": "Task deleted", + "totalTasks": "Total Tasks", + "noTasks": "No probe tasks", + "noFilteredTasks": "No tasks match current filter", + "urlPlaceholder": "e.g., Homepage", + "probeDone": "Probe completed", + "probeFail": "Probe failed", + "metricDetail": "Metric Details", + "notDetected": "Not Detected", + "snmpTitle": "Network Device / BMC Collection & Trends", + "fillRequired": "Please fill device name, IP and select OID template", + "deviceAdded": "Device added", + "collectFail": "Collection failed", + "demoSwitch": "Core-Switch-A", + "oidTemplate": "OID Template", + "device": "Device", + "centerSubtitle": "Comprehensive infrastructure & application monitoring", + "explorerTitle": "Metrics Explorer", + "searchMetrics": "Search metrics...", + "explorerSubtitle": "Browse all metrics by category/host, supports overlay comparison", + "loadingTargets": "Loading targets...", + "selectTarget": "Please select a target", + "selectHint": "After selecting a target host, check metrics from the left panel", + "selectMetricsHint": "Please check metrics from the left panel", + "probeName": "Probe Name", + "execCommand": "Execute Command", + "refreshing": " · Refreshing..." } }, "task": { diff --git a/frontend/src/locales/zh-CN.json b/frontend/src/locales/zh-CN.json index 932f318..204542d 100644 --- a/frontend/src/locales/zh-CN.json +++ b/frontend/src/locales/zh-CN.json @@ -4492,6 +4492,97 @@ "userPolicyHint": "用户未分配任何策略时不拦截(向后兼容);分配后仅允许命中策略的查询。", "assign": "分配", "all": "全部" + }, + "lbl": { + "unlimited": "无限制", + "hitRate": "命中率", + "totalDevices": "设备总数", + "instances": "监控实例", + "instanceStatus": "实例状态", + "connections": "连接", + "online": "在线", + "fragmentationRate": "碎片率", + "trafficSent": "发送流量", + "evictedKeys": "逐出 Key", + "noMaxMemory": "未配置 maxmemory", + "standalone": "单机", + "middleware": "中间件", + "hostMonitor": "主机监控", + "basicMonitor": "基础监控", + "diskMonitor": "磁盘监控", + "networkMonitor": "网络监控", + "processMonitor": "进程监控", + "selectHost": "选择主机...", + "selectHostHint": "选择主机查看监控指标", + "coreMetrics": "核心指标", + "allMetrics": "全部指标", + "cpuUsage": "CPU 使用率(%)", + "cpuIowait": "CPU IO等待(%)", + "memUsage": "内存使用率(%)", + "memAvailable": "可用内存(GB)", + "swapUsage": "Swap 使用率(%)", + "load1": "系统负载(1m)", + "diskUsage": "磁盘使用率(%)", + "diskRead": "磁盘读取(KB/s)", + "diskWrite": "磁盘写入(KB/s)", + "diskIoBusy": "磁盘IO繁忙度(%)", + "fdUsage": "FD 文件句柄使用率", + "fdRate": "FD 使用率", + "nicRxErrors": "网卡接收错误", + "nicTxErrors": "网卡发送错误", + "nicBandwidth": "网卡带宽吞吐", + "saving": "保存中…", + "allPortsReport": "所有发现的端口都上报,任意端口变化都会触发告警 + 恢复通知。", + "selectedOnlyReport": "仅上报选中端口的变化,其余端口变化静默忽略。系统进程端口默认不告警。", + "whitelistAutoReport": "白名单模式下所有端口自动上报", + "nameIpRequired": "请填写名称和 IP", + "updateSuccess": "更新成功", + "editMw": "编辑中间件", + "addMw": "添加中间件", + "siteProbe": "站点拨测", + "availability24h": "可用率(24h)", + "totalProbes": "探测次数", + "editTask": "编辑拨测任务", + "addProbe": "添加探针", + "editProbe": "编辑探针", + "deleteProbe": "删除探针", + "selectTemplate": "选择模板", + "deleteMonitor": "删除监控", + "partialFail": "部分失败", + "defaultAdded": "已添加默认监控", + "defaultExists": "默认目标已存在", + "collectDone": "采集完成", + "deleteFail": "删除失败", + "nameAddrRequired": "名称和地址不能为空", + "taskUpdated": "任务已更新", + "taskCreated": "拨测任务已创建", + "taskDeleted": "任务已删除", + "totalTasks": "总任务", + "noTasks": "暂无拨测任务", + "noFilteredTasks": "当前筛选无任务", + "urlPlaceholder": "例如:官网首页", + "probeDone": "拨测完成", + "probeFail": "拨测失败", + "metricDetail": "指标详情", + "notDetected": "未检测", + "snmpTitle": "网络设备 / BMC 采集与趋势", + "fillRequired": "请填写设备名称、IP 并选择 OID 模板", + "deviceAdded": "设备已添加", + "collectFail": "采集失败", + "demoSwitch": "核心交换机-A", + "oidTemplate": "OID 模板", + "device": "设备", + "centerSubtitle": "全面的基础设施与应用监控", + "explorerTitle": "指标浏览器", + "searchMetrics": "搜索指标...", + "explorerSubtitle": "按分类/主机浏览所有指标,支持叠加对比", + "loadingTargets": "加载监控目标...", + "selectTarget": "请选择监控目标", + "selectHint": "选择目标主机后,从左侧勾选要查看的指标", + "selectMetricsHint": "请从左侧勾选指标", + "probeName": "探针名称", + "execCommand": "执行命令", + "refreshing": " · 刷新中..." } }, "task": { diff --git a/frontend/src/pages/monitor/HostMonitor.tsx b/frontend/src/pages/monitor/HostMonitor.tsx index 2b60f11..66c6e2b 100644 --- a/frontend/src/pages/monitor/HostMonitor.tsx +++ b/frontend/src/pages/monitor/HostMonitor.tsx @@ -26,12 +26,13 @@ import { NetworkTab } from './host-monitor/tabs/NetworkTab'; import { PortMonitorTab } from './host-monitor/tabs/PortMonitorTab'; import { ProcessTab } from './host-monitor/tabs/ProcessTab'; import type { JsonData } from '@/types/common'; +import { useTranslation } from 'react-i18next'; import i18n from '@/i18n'; const TABS = [ - { id: 'basic', label: i18n.t('基础监控'), icon: }, - { id: 'disk', label: i18n.t('磁盘监控'), icon: }, - { id: 'network', label: i18n.t('网络监控'), icon: }, + { id: 'basic', label: i18n.t('monitor.lbl.basicMonitor'), icon: }, + { id: 'disk', label: i18n.t('monitor.lbl.diskMonitor'), icon: }, + { id: 'network', label: i18n.t('monitor.lbl.networkMonitor'), icon: }, { id: 'ports', label: i18n.t('agent.portMonitor'), icon: }, { id: 'process', label: i18n.t('monitor.hostProcess'), icon: }, { id: 'system', label: i18n.t('system.systemInfo'), icon: }, @@ -39,6 +40,7 @@ const TABS = [ ]; export function HostMonitor({ embedded = false }: { embedded?: boolean }) { + const { t } = useTranslation(); const qc = useQueryClient(); const [searchParams] = useSearchParams(); const urlHost = searchParams.get('host') || ''; @@ -118,7 +120,7 @@ export function HostMonitor({ embedded = false }: { embedded?: boolean }) {
{serverInfo && ( - {String(serverInfo.os || '')}{serverInfo.cpu_cores ? ` | ${serverInfo.cpu_cores as number | string} ${i18n.t('monitor.hostMonitorPanel.cores')}` : ''}{serverInfo.mem_total ? ` | ${(serverInfo.mem_total as number).toFixed(0)}GB` : ''} + {String(serverInfo.os || '')}{serverInfo.cpu_cores ? ` | ${serverInfo.cpu_cores as number | string} ${t('monitor.hostMonitorPanel.cores')}` : ''}{serverInfo.mem_total ? ` | ${(serverInfo.mem_total as number).toFixed(0)}GB` : ''} )}
@@ -136,15 +138,15 @@ export function HostMonitor({ embedded = false }: { embedded?: boolean }) {
{actualDataSpan && ( - {i18n.t('monitor.hostMonitorPanel.dataSpan', { span: actualDataSpan })} + {t('monitor.hostMonitorPanel.dataSpan', { span: actualDataSpan })} )}
@@ -212,10 +214,10 @@ export function HostMonitor({ embedded = false }: { embedded?: boolean }) { } return ( - } toolbar={toolbar} - loading={loading && !history.length} loadingLabel={i18n.t('monitor.hostmonitortsx.sc23e802e')} + loading={loading && !history.length} loadingLabel={t('monitor.hostmonitortsx.sc23e802e')} contentClassName="space-y-3"> {monitorBody} {dialogs} diff --git a/frontend/src/pages/monitor/MetricsExplorer.tsx b/frontend/src/pages/monitor/MetricsExplorer.tsx index a298080..80b3ae1 100644 --- a/frontend/src/pages/monitor/MetricsExplorer.tsx +++ b/frontend/src/pages/monitor/MetricsExplorer.tsx @@ -9,6 +9,7 @@ import { metricsApi } from '@/api'; import { PageShell } from '@/components/layout/PageShell'; import { PageEmpty } from '@/components/layout/PageEmpty'; import type { JsonData } from '@/types/common'; +import { useTranslation } from 'react-i18next'; import i18n from '@/i18n'; interface TargetInfo { @@ -34,10 +35,11 @@ const METRIC_CATEGORIES: Record }, network: { label: i18n.t('monitor.hostMetric.network'), icon: }, middleware: { label: i18n.t('monitor.alertRulesTab.catMiddleware'), icon: }, - device: { label: i18n.t('设备'), icon: }, + device: { label: i18n.t('monitor.lbl.device'), icon: }, }; export function MetricsExplorer() { + const { t } = useTranslation(); const [targets, setTargets] = useState([]); const [metricNames, setMetricNames] = useState([]); const [selectedTarget, setSelectedTarget] = useState(''); @@ -109,7 +111,7 @@ export function MetricsExplorer() {
setSearchKw(e.target.value)} /> @@ -140,19 +142,19 @@ export function MetricsExplorer() { return ( } toolbar={toolbar} loading={loading} - loadingLabel={i18n.t('加载监控目标...')} + loadingLabel={i18n.t('monitor.lbl.loadingTargets')} contentClassName="space-y-6" > {!selectedTarget ? ( } - title={i18n.t('请选择监控目标')} - description={i18n.t('选择目标主机后,从左侧勾选要查看的指标')} + title={i18n.t('monitor.lbl.selectTarget')} + description={i18n.t('monitor.lbl.selectHint')} /> ) : (
@@ -160,7 +162,7 @@ export function MetricsExplorer() {
-

{i18n.t('monitor.metricsexplorer.s3ec50388')}

+

{t('monitor.metricsexplorer.s3ec50388')}

{selectedMetrics.length} 已选
@@ -205,9 +207,9 @@ export function MetricsExplorer() {
{selectedMetrics.length === 0 ? ( - } title={i18n.t('请从左侧勾选指标')} className="py-16" /> + } title={i18n.t('monitor.lbl.selectMetricsHint')} className="py-16" /> ) : sortedTimes.length === 0 ? ( - + ) : (
diff --git a/frontend/src/pages/monitor/MiddlewareMonitor.tsx b/frontend/src/pages/monitor/MiddlewareMonitor.tsx index 4bfa99f..9fb7281 100644 --- a/frontend/src/pages/monitor/MiddlewareMonitor.tsx +++ b/frontend/src/pages/monitor/MiddlewareMonitor.tsx @@ -12,7 +12,7 @@ import { InstanceCard } from './middleware-monitor/InstanceCard'; import { MiddlewareDialog } from './middleware-monitor/MiddlewareDialog'; import type { MwTarget } from './middleware-monitor/helpers'; import { REDIS_CHARTS, MYSQL_CHARTS, evalMwHealth, worstHealthLevel } from './middleware-monitor/helpers'; -import i18n from '@/i18n'; +import { useTranslation } from 'react-i18next'; type TabKey = 'redis' | 'mysql'; const REFRESH_INTERVAL_MS = 30_000; @@ -20,6 +20,7 @@ const REFRESH_INTERVAL_MS = 30_000; interface MwListResponse { list: MwTarget[]; total: number } export function MiddlewareMonitor() { + const { t } = useTranslation(); const confirm = useConfirm(); const [targets, setTargets] = useState([]); const [loading, setLoading] = useState(true); @@ -62,7 +63,7 @@ export function MiddlewareMonitor() { hasLoaded.current = true; } catch (err) { if (!silent) { - setError((err as { message?: string }).message || i18n.t('kubernetes.msg.loadFailed')); + setError((err as { message?: string }).message || t('kubernetes.msg.loadFailed')); setTargets([]); } } finally { @@ -86,14 +87,14 @@ export function MiddlewareMonitor() { const warnCount = targets.filter((t) => worstHealthLevel(evalMwHealth(t)) !== 'ok').length; const handleDelete = async (target: MwTarget) => { - const ok = await confirm({ title: i18n.t('删除监控'), description: `确认删除 "${target.name}"?`, confirmText: i18n.t('kubernetes.storage.delete2') }); + const ok = await confirm({ title: t('monitor.lbl.deleteMonitor'), description: `确认删除 "${target.name}"?`, confirmText: t('kubernetes.storage.delete2') }); if (!ok) return; try { await middlewareMonitorApi.delete(target.id); - toast.success(i18n.t('monitor.silenceRules.deleted')); + toast.success(t('monitor.silenceRules.deleted')); loadTargets(); } catch (err) { - toast.error(i18n.t('kubernetes.msg.deleteFailed2'), { description: (err as { message?: string }).message }); + toast.error(t('kubernetes.msg.deleteFailed2'), { description: (err as { message?: string }).message }); } }; @@ -101,10 +102,10 @@ export function MiddlewareMonitor() { setTesting(target.id); try { const res = await middlewareMonitorApi.test(target.id) as { success: boolean; message: string }; - if (res.success) { toast.success(i18n.t('aiops.llmTestSuccess'), { description: res.message }); loadTargets(true); } - else toast.error(i18n.t('aiops.llmTestFailed'), { description: res.message }); + if (res.success) { toast.success(t('aiops.llmTestSuccess'), { description: res.message }); loadTargets(true); } + else toast.error(t('aiops.llmTestFailed'), { description: res.message }); } catch (err) { - toast.error(i18n.t('monitor.alertChannels.testFailed'), { description: (err as { message?: string }).message }); + toast.error(t('monitor.alertChannels.testFailed'), { description: (err as { message?: string }).message }); } finally { setTesting(null); } @@ -115,11 +116,11 @@ export function MiddlewareMonitor() { try { const res = await middlewareMonitorApi.seedDefaults() as { created: number; message: string; failed?: string[] }; await loadTargets(); - if (res.failed?.length) toast.error(i18n.t('部分失败'), { description: res.message }); - else if (res.created > 0) toast.success(i18n.t('已添加默认监控'), { description: res.message }); - else toast.success(i18n.t('默认目标已存在')); + if (res.failed?.length) toast.error(t('monitor.lbl.partialFail'), { description: res.message }); + else if (res.created > 0) toast.success(t('monitor.lbl.defaultAdded'), { description: res.message }); + else toast.success(t('monitor.lbl.defaultExists')); } catch (err) { - toast.error(i18n.t('monitor.dashboard.custom.toastPanelFailed'), { description: (err as { message?: string }).message }); + toast.error(t('monitor.dashboard.custom.toastPanelFailed'), { description: (err as { message?: string }).message }); } finally { setSeeding(false); } @@ -128,10 +129,10 @@ export function MiddlewareMonitor() { const handleCollectNow = async () => { try { const res = await middlewareMonitorApi.collectNow() as { message: string }; - toast.success(i18n.t('采集完成'), { description: res.message }); + toast.success(t('monitor.lbl.collectDone'), { description: res.message }); loadTargets(true); } catch (err) { - toast.error(i18n.t('删除失败'), { description: (err as { message?: string }).message }); + toast.error(t('monitor.lbl.deleteFail'), { description: (err as { message?: string }).message }); } }; @@ -149,13 +150,13 @@ export function MiddlewareMonitor() {
-

{i18n.t('monitor.alertHelper.srcMiddleware')}

+

{t('monitor.alertHelper.srcMiddleware')}

Redis 单机/集群 · MySQL · 30s 自动刷新 {lastRefreshAt && ( 上次更新 {lastRefreshAt.toLocaleTimeString()} - {refreshing && i18n.t(' · 刷新中...')} + {refreshing && t('monitor.lbl.refreshing')} )}

@@ -193,10 +194,10 @@ export function MiddlewareMonitor() { -

{i18n.t('monitor.middlewaremonit.sbbe62a10')}

+

{t('monitor.middlewaremonit.sbbe62a10')}

- - + +
@@ -206,9 +207,9 @@ export function MiddlewareMonitor() { <>
{[ - { label: i18n.t('监控实例'), value: targets.length, icon: Activity }, - { label: i18n.t('agent.agentOnline'), value: onlineCount, icon: Database, color: 'dg-text-green' }, - { label: i18n.t('monitor.hostDashboard.alert'), value: warnCount, icon: Activity, color: warnCount > 0 ? 'dg-text-yellow' : 'dg-text-green' }, + { label: t('monitor.lbl.instances'), value: targets.length, icon: Activity }, + { label: t('agent.agentOnline'), value: onlineCount, icon: Database, color: 'dg-text-green' }, + { label: t('monitor.hostDashboard.alert'), value: warnCount, icon: Activity, color: warnCount > 0 ? 'dg-text-yellow' : 'dg-text-green' }, { label: 'Redis', value: redisTargets.length, icon: Database, color: 'dg-text-red' }, { label: 'MySQL', value: mysqlTargets.length, icon: Server, color: 'dg-text-blue' }, ].map((item) => ( @@ -251,7 +252,7 @@ export function MiddlewareMonitor() { 暂无 {tab === 'redis' ? 'Redis' : 'MySQL'} 实例, - + ) : ( diff --git a/frontend/src/pages/monitor/MonitorCenter.tsx b/frontend/src/pages/monitor/MonitorCenter.tsx index b59a4f5..d76bd2f 100644 --- a/frontend/src/pages/monitor/MonitorCenter.tsx +++ b/frontend/src/pages/monitor/MonitorCenter.tsx @@ -7,15 +7,16 @@ import { HostMonitor } from './HostMonitor'; import { MiddlewareMonitor } from './MiddlewareMonitor'; import { SNMPMonitor } from './SNMPMonitor'; import { SiteMonitor } from './SiteMonitor'; +import { useTranslation } from 'react-i18next'; import i18n from '@/i18n'; type TabId = 'host' | 'middleware' | 'snmp' | 'site'; const tabs: { id: TabId; name: string; description: string }[] = [ - { id: 'host', name: i18n.t('monitor.alertRulesTab.catHost'), description: i18n.t('主机监控') }, - { id: 'middleware', name: i18n.t('monitor.alertRulesTab.catMiddleware'), description: i18n.t('中间件') }, - { id: 'snmp', name: 'SNMP', description: i18n.t('网络设备 / BMC 采集与趋势') }, - { id: 'site', name: i18n.t('monitor.alertRulesTab.catSite'), description: i18n.t('站点拨测') }, + { id: 'host', name: i18n.t('monitor.alertRulesTab.catHost'), description: i18n.t('monitor.lbl.hostMonitor') }, + { id: 'middleware', name: i18n.t('monitor.alertRulesTab.catMiddleware'), description: i18n.t('monitor.lbl.middleware') }, + { id: 'snmp', name: 'SNMP', description: i18n.t('monitor.lbl.snmpTitle') }, + { id: 'site', name: i18n.t('monitor.alertRulesTab.catSite'), description: i18n.t('monitor.lbl.siteProbe') }, ]; const TAB_IDS: TabId[] = ['host', 'middleware', 'snmp', 'site']; @@ -25,6 +26,7 @@ function parseTab(raw: string | null): TabId { } export function MonitorCenter() { + const { t } = useTranslation(); const [searchParams, setSearchParams] = useSearchParams(); const [activeTab, setActiveTab] = useState(() => parseTab(searchParams.get('tab'))); @@ -43,8 +45,8 @@ export function MonitorCenter() { return ( } tabs={tabs.map((t) => ({ id: t.id, label: t.name }))} activeTab={activeTab} diff --git a/frontend/src/pages/monitor/SNMPMonitor.tsx b/frontend/src/pages/monitor/SNMPMonitor.tsx index 9a30b8e..1b992ca 100644 --- a/frontend/src/pages/monitor/SNMPMonitor.tsx +++ b/frontend/src/pages/monitor/SNMPMonitor.tsx @@ -21,7 +21,7 @@ import { SnmpOverviewCharts } from './snmp-monitor/SnmpOverviewCharts'; import { SnmpDeviceChartsPanel } from './snmp-monitor/SnmpDeviceChartsPanel'; import { SnmpTemplatesCollapsible, type SnmpTemplate } from './snmp-monitor/SnmpTemplatesCollapsible'; import type { SnmpDevice, SnmpMetric, SnmpOverviewItem } from './snmp-monitor/helpers'; -import i18n from '@/i18n'; +import { useTranslation } from 'react-i18next'; interface SnmpStats { total: number; online: number; abnormal: number } @@ -38,6 +38,7 @@ const keyMetrics = [ ]; export function SNMPMonitor() { + const { t } = useTranslation(); const [stats, setStats] = useState({ total: 0, online: 0, abnormal: 0 }); const [devices, setDevices] = useState([]); const [overview, setOverview] = useState([]); @@ -105,7 +106,7 @@ export function SNMPMonitor() { const handleAdd = async () => { if (!form.name.trim() || !form.ip.trim() || !form.template_id) { - toast.error(i18n.t('请填写设备名称、IP 并选择 OID 模板')); + toast.error(t('monitor.lbl.fillRequired')); return; } setSubmitting(true); @@ -118,12 +119,12 @@ export function SNMPMonitor() { template_id: Number(form.template_id), vendor: tpl?.vendor || '', device_type: tpl?.device_type || 'switch', }); - toast.success(i18n.t('设备已添加')); + toast.success(t('monitor.lbl.deviceAdded')); setDialogOpen(false); setForm({ ...emptyForm }); refresh(); } catch (e) { - toast.error((e as { message?: string }).message || i18n.t('monitor.dashboard.custom.toastPanelFailed')); + toast.error((e as { message?: string }).message || t('monitor.dashboard.custom.toastPanelFailed')); } finally { setSubmitting(false); } @@ -137,7 +138,7 @@ export function SNMPMonitor() { toast.success(`${d.name} 采集成功`); refresh(); } catch (err) { - toast.error((err as { message?: string }).message || i18n.t('采集失败')); + toast.error((err as { message?: string }).message || t('monitor.lbl.collectFail')); refresh(); } finally { setCollectingId(null); @@ -163,18 +164,18 @@ export function SNMPMonitor() { e.stopPropagation(); try { await snmpApi.deleteDevice(id); - toast.success(i18n.t('monitor.silenceRules.deleted')); + toast.success(t('monitor.silenceRules.deleted')); if (selectedDevice?.id === id) setSelectedDevice(null); refresh(); } catch { - toast.error(i18n.t('kubernetes.msg.deleteFailed2')); + toast.error(t('kubernetes.msg.deleteFailed2')); } }; const statusBadge = (status?: string) => { - if (status === 'online') return {i18n.t('agent.agentOnline')} ; - if (status === 'error') return {i18n.t('agent.heartbeatAbnormal')} ; - return {i18n.t('agent.agentOffline')} ; + if (status === 'online') return {t('agent.agentOnline')} ; + if (status === 'error') return {t('agent.heartbeatAbnormal')} ; + return {t('agent.agentOffline')} ; }; return ( @@ -197,13 +198,13 @@ export function SNMPMonitor() { - {i18n.t('monitor.snmpmonitortsx.s29b90990')} - {i18n.t('monitor.snmpmonitortsx.s11ea7d0c')} + {t('monitor.snmpmonitortsx.s29b90990')} + {t('monitor.snmpmonitortsx.s11ea7d0c')}
- - setForm({ ...form, name: e.target.value })} placeholder={i18n.t('核心交换机-A')} /> + + setForm({ ...form, name: e.target.value })} placeholder={t('monitor.lbl.demoSwitch')} />
@@ -211,14 +212,14 @@ export function SNMPMonitor() { setForm({ ...form, ip: e.target.value })} placeholder="192.168.1.1" />
- + setForm({ ...form, port: Number(e.target.value) || 161 })} />
setForm({ ...form, name: e.target.value })} /> + + setForm({ ...form, name: e.target.value })} />
- + setForm({ ...form, frequency_sec: Number(e.target.value) || defaultProbeFrequency(form.type) })} />

{probeFrequencyHint(form.type)}

- + setForm({ ...form, timeout_sec: Number(e.target.value) || 10 })} />
{form.type === 'HTTP' && (
- + setForm({ ...form, expected_status_code: Number(e.target.value) || 200 })} />
)}
- + diff --git a/frontend/src/pages/monitor/host-monitor/HealthProbeSection.tsx b/frontend/src/pages/monitor/host-monitor/HealthProbeSection.tsx index 5a4c591..013f55c 100644 --- a/frontend/src/pages/monitor/host-monitor/HealthProbeSection.tsx +++ b/frontend/src/pages/monitor/host-monitor/HealthProbeSection.tsx @@ -18,13 +18,14 @@ import { import { DEFAULT_PROBE_FORM, type ProbeForm } from './types'; import { useConfirm } from '@/components/ui/confirm-dialog'; import { toast } from 'sonner'; -import i18n from '@/i18n'; +import { useTranslation } from 'react-i18next'; interface HealthProbeSectionProps { selectedHost: string; } export function HealthProbeSection({ selectedHost }: HealthProbeSectionProps) { + const { t } = useTranslation(); const confirm = useConfirm(); const qc = useQueryClient(); const { data: probes = [], isFetching, refetch } = useHealthProbes(selectedHost); @@ -69,15 +70,15 @@ export function HealthProbeSection({ selectedHost }: HealthProbeSectionProps) { setProbeDialogOpen(false); qc.invalidateQueries({ queryKey: queryKeys.hostMonitor.probes(selectedHost) }); } catch { - toast.error(i18n.t('kubernetes.msg.saveFailed')); + toast.error(t('kubernetes.msg.saveFailed')); } }; const handleDeleteProbe = async (p: Record) => { const ok = await confirm({ - title: i18n.t('删除探针'), + title: t('monitor.lbl.deleteProbe'), description: `确定删除探针「${p.name}」?`, - confirmText: i18n.t('kubernetes.storage.delete2'), + confirmText: t('kubernetes.storage.delete2'), variant: 'destructive', }); if (!ok) return; @@ -89,7 +90,7 @@ export function HealthProbeSection({ selectedHost }: HealthProbeSectionProps) { <>
-

{i18n.t('无限制')}

+

{t('monitor.lbl.unlimited')}

+ diff --git a/frontend/src/pages/monitor/host-monitor/HostTimeRangePicker.tsx b/frontend/src/pages/monitor/host-monitor/HostTimeRangePicker.tsx index dd23284..aeac2dd 100644 --- a/frontend/src/pages/monitor/host-monitor/HostTimeRangePicker.tsx +++ b/frontend/src/pages/monitor/host-monitor/HostTimeRangePicker.tsx @@ -9,7 +9,7 @@ import { type TimeRangePresetId, } from './types'; import { presetToRange, toDatetimeLocalValue, formatRangeSummary } from './timeRange'; -import i18n from '@/i18n'; +import { useTranslation } from 'react-i18next'; interface Props { value: HostMonitorTimeRange; @@ -17,6 +17,7 @@ interface Props { } export function HostTimeRangePicker({ value, onChange }: Props) { + const { t } = useTranslation(); const [customOpen, setCustomOpen] = useState(false); const now = new Date(); const [customStart, setCustomStart] = useState(toDatetimeLocalValue(new Date(now.getTime() - 3600_000))); @@ -45,7 +46,7 @@ export function HostTimeRangePicker({ value, onChange }: Props) { const e = start.getTime() <= end.getTime() ? end : start; onChange({ preset: 'custom', - label: i18n.t('aiops.providerCustom'), + label: t('aiops.providerCustom'), start: s.toISOString(), end: e.toISOString(), buckets: Math.min(120, Math.max(36, Math.ceil((e.getTime() - s.getTime()) / 60_000 / 2))), @@ -86,10 +87,10 @@ export function HostTimeRangePicker({ value, onChange }: Props) { {customOpen && (
-

{i18n.t('无限制')}

+

{t('monitor.lbl.unlimited')}

- +
- + - {i18n.t('monitor.portpolicydialo.s868e3dd0')} - {i18n.t('monitor.portpolicydialo.scd27ad47')} + {t('monitor.portpolicydialo.s868e3dd0')} + {t('monitor.portpolicydialo.scd27ad47')}
- +
@@ -73,7 +74,7 @@ export function PortPolicyDialog({ const val = e.target.value; onWhitelistChange(parsePortList(val)); }} - placeholder={portPolicyMode === 'whitelist' ? i18n.t('白名单模式下所有端口自动上报') : '80,443,3306,6379'} + placeholder={portPolicyMode === 'whitelist' ? t('monitor.lbl.whitelistAutoReport') : '80,443,3306,6379'} readOnly={portPolicyMode === 'whitelist'} />

@@ -83,14 +84,14 @@ export function PortPolicyDialog({

- ⚠️ 无论选择哪种模式,以下系统进程端口 {i18n.t('monitor.portpolicydialo.s2ff12d51')} :sshd、rpcbind、master、systemd、dbus-daemon、rsyslogd 等。Agent 采集时自动跳过。 + ⚠️ 无论选择哪种模式,以下系统进程端口 {t('monitor.portpolicydialo.s2ff12d51')} :sshd、rpcbind、master、systemd、dbus-daemon、rsyslogd 等。Agent 采集时自动跳过。

- + diff --git a/frontend/src/pages/monitor/host-monitor/ZoomableAreaChart.tsx b/frontend/src/pages/monitor/host-monitor/ZoomableAreaChart.tsx index ec948a8..67d296d 100644 --- a/frontend/src/pages/monitor/host-monitor/ZoomableAreaChart.tsx +++ b/frontend/src/pages/monitor/host-monitor/ZoomableAreaChart.tsx @@ -13,7 +13,7 @@ import { import { ChartToolbar } from './ChartToolbar'; import { MonitorChartTooltip } from '../shared/monitorChartTheme'; import { ChartRangeHint } from '../shared/ChartRangeHint'; -import i18n from '@/i18n'; +import { useTranslation } from 'react-i18next'; export interface ChartSeries { dataKey: string; @@ -192,6 +192,7 @@ export const ZoomableAreaChart = forwardRef(function onZoomedChange, referenceLines, }, ref) { + const { t } = useTranslation(); const uid = useId().replace(/:/g, ''); const gradPrefix = `grad-${chartId}-${uid}`; @@ -316,11 +317,11 @@ export const ZoomableAreaChart = forwardRef(function
- {title || i18n.t('指标详情')} + {title || t('monitor.lbl.metricDetail')} {subtitle && (

{subtitle}

)} - {i18n.t('选择主机...')} + {t('monitor.lbl.selectHost')}
- } iconTone="blue" + } iconTone="blue" data={metrics.cpu} color="#3b82f6" unit="%" domain={[0, 100]} aggType={aggType} referenceLines={pctRefLines} /> - } iconTone="orange" + } iconTone="orange" data={metrics.iowait} color="#f97316" unit="%" domain={[0, 100]} aggType={aggType} referenceLines={pctRefLines} /> - } iconTone="green" + } iconTone="green" data={metrics.mem} color="#10b981" unit="%" domain={[0, 100]} aggType={aggType} referenceLines={pctRefLines} /> - } iconTone="green" + } iconTone="green" data={metrics.memAvail} color="#34d399" unit="GB" domain={[0, 'auto']} aggType={aggType} /> - } iconTone="red" + } iconTone="red" data={metrics.swap} color="#ef4444" unit="%" domain={[0, 100]} aggType={aggType} /> - } iconTone="purple" + } iconTone="purple" data={metrics.load1} color="#8b5cf6" unit="" domain={[0, 'auto']} aggType={aggType} />
); diff --git a/frontend/src/pages/monitor/host-monitor/tabs/ContextSwitchCard.tsx b/frontend/src/pages/monitor/host-monitor/tabs/ContextSwitchCard.tsx index b8d3cca..d759ab5 100644 --- a/frontend/src/pages/monitor/host-monitor/tabs/ContextSwitchCard.tsx +++ b/frontend/src/pages/monitor/host-monitor/tabs/ContextSwitchCard.tsx @@ -2,9 +2,10 @@ import type { JsonData } from '@/types/common'; import { Card, CardContent } from '@/components/ui/card'; import { RefreshCw } from 'lucide-react'; -import i18n from '@/i18n'; +import { useTranslation } from 'react-i18next'; export function ContextSwitchCard({ csInfo }: { csInfo: JsonData }) { + const { t } = useTranslation(); return ( @@ -47,7 +48,7 @@ export function ContextSwitchCard({ csInfo }: { csInfo: JsonData }) {
) : ( -
{i18n.t('无限制')}
+
{t('monitor.lbl.unlimited')}
)} diff --git a/frontend/src/pages/monitor/host-monitor/tabs/DiskTab.tsx b/frontend/src/pages/monitor/host-monitor/tabs/DiskTab.tsx index 4830c98..7c34266 100644 --- a/frontend/src/pages/monitor/host-monitor/tabs/DiskTab.tsx +++ b/frontend/src/pages/monitor/host-monitor/tabs/DiskTab.tsx @@ -4,7 +4,7 @@ import type { AggType, BucketPoint, RefLine } from '../types'; import type { JsonData } from '@/types/common'; import { MetricChartCard } from '../MetricChartCard'; import { parseJSON } from '../utils'; -import i18n from '@/i18n'; +import { useTranslation } from 'react-i18next'; export interface DiskTabProps { metrics: { @@ -19,6 +19,7 @@ export interface DiskTabProps { } export function DiskTab({ metrics, latest, currentVal, aggType, pctRefLines, diskIOInfo }: DiskTabProps) { + const { t } = useTranslation(); const inodeVal = (() => { const dArr = Array.isArray(parseJSON(latest?.disk_info)) ? parseJSON(latest?.disk_info) : []; const root = dArr.find((d: JsonData) => d.mount === '/') || dArr[0]; @@ -27,15 +28,15 @@ export function DiskTab({ metrics, latest, currentVal, aggType, pctRefLines, dis return (
- } iconTone="orange" + } iconTone="orange" data={metrics.disk} color="#f97316" unit="%" domain={[0, 100]} aggType={aggType} referenceLines={pctRefLines} /> - } iconTone="purple" + } iconTone="purple" data={metrics.inode} color="#8b5cf6" unit="%" domain={[0, 100]} aggType={aggType} referenceLines={pctRefLines} /> - } iconTone="blue" + } iconTone="blue" data={metrics.ioRead} color="#3b82f6" unit="KB/s" domain={[0, 'auto']} aggType={aggType} /> - } iconTone="green" + } iconTone="green" data={metrics.ioWrite} color="#10b981" unit="KB/s" domain={[0, 'auto']} aggType={aggType} /> - } iconTone="red" + } iconTone="red" data={metrics.ioUtil} color="#ef4444" unit="%" domain={[0, 100]} aggType={aggType} referenceLines={pctRefLines} />
); diff --git a/frontend/src/pages/monitor/host-monitor/tabs/FDUsageCard.tsx b/frontend/src/pages/monitor/host-monitor/tabs/FDUsageCard.tsx index 5c86548..ae1efb4 100644 --- a/frontend/src/pages/monitor/host-monitor/tabs/FDUsageCard.tsx +++ b/frontend/src/pages/monitor/host-monitor/tabs/FDUsageCard.tsx @@ -7,7 +7,7 @@ import { Card, CardContent } from '@/components/ui/card'; import { MetricIconBox } from '../MetricIconBox'; import { ChartToolbar } from '../ChartToolbar'; import { ZoomableAreaChart, type ZoomableChartHandle } from '../ZoomableAreaChart'; -import i18n from '@/i18n'; +import { useTranslation } from 'react-i18next'; export interface FDUsageCardProps { fdInfo: JsonData; @@ -19,6 +19,7 @@ export interface FDUsageCardProps { } export function FDUsageCard({ fdInfo, fdMetrics, aggType, fdChartRef, fdChartZoomed, setFdChartZoomed }: FDUsageCardProps) { + const { t } = useTranslation(); return ( @@ -39,15 +40,15 @@ export function FDUsageCard({ fdInfo, fdMetrics, aggType, fdChartRef, fdChartZoo <>
- {i18n.t('deploy.toast.assigned')} + {t('deploy.toast.assigned')} {fdInfo.allocated.toLocaleString()}
- {i18n.t('monitor.metricCatalog.colMax')} - {fdInfo.max === -1 ? i18n.t('无限制') : fdInfo.max?.toLocaleString() || '—'} + {t('monitor.metricCatalog.colMax')} + {fdInfo.max === -1 ? t('monitor.lbl.unlimited') : fdInfo.max?.toLocaleString() || '—'}
- {i18n.t('server.usage')} + {t('server.usage')} 80 ? 'text-rose-400' : 'dg-text-purple'}`}> {fdInfo.usage_pct != null ? `${fdInfo.usage_pct.toFixed(1)}%` : '—'} @@ -57,11 +58,11 @@ export function FDUsageCard({ fdInfo, fdMetrics, aggType, fdChartRef, fdChartZoo ({ ...d, value: d[aggType] }))} - series={[{ dataKey: 'value', color: '#a78bfa', label: i18n.t('FD 使用率') }]} + series={[{ dataKey: 'value', color: '#a78bfa', label: t('monitor.lbl.fdRate') }]} yDomain={[0, 'auto']} unit="%" chartId="fd-usage" - title={i18n.t('网卡带宽吞吐')} + title={t('monitor.lbl.nicBandwidth')} subtitle={fdInfo?.usage_pct != null ? `当前 ${fdInfo.usage_pct.toFixed(1)}%` : undefined} height={160} toolbarPlacement="header" @@ -71,7 +72,7 @@ export function FDUsageCard({ fdInfo, fdMetrics, aggType, fdChartRef, fdChartZoo )} ) : ( -
{i18n.t('无限制')}
+
{t('monitor.lbl.unlimited')}
)} diff --git a/frontend/src/pages/monitor/host-monitor/tabs/NetworkTab.tsx b/frontend/src/pages/monitor/host-monitor/tabs/NetworkTab.tsx index cb36839..60c3d79 100644 --- a/frontend/src/pages/monitor/host-monitor/tabs/NetworkTab.tsx +++ b/frontend/src/pages/monitor/host-monitor/tabs/NetworkTab.tsx @@ -5,7 +5,7 @@ import type { JsonData } from '@/types/common'; import { MetricChartCard } from '../MetricChartCard'; import { DualLineChart } from '../DualLineChart'; import { formatBPS } from '../utils'; -import i18n from '@/i18n'; +import { useTranslation } from 'react-i18next'; export interface NetworkTabProps { metrics: { @@ -19,31 +19,32 @@ export interface NetworkTabProps { } export function NetworkTab({ metrics, netInfo, currentVal, aggType }: NetworkTabProps) { + const { t } = useTranslation(); const connTotal = netInfo ? String((netInfo.tcp_established || 0) + (netInfo.tcp_time_wait || 0) + (netInfo.tcp_close_wait || 0)) : '--'; return (
} iconTone="purple" - data={metrics.tcpEst} color="#8b5cf6" unit={i18n.t('monitor.hostMonitorPanel.portUnit')} domain={[0, 'auto']} aggType={aggType} /> + data={metrics.tcpEst} color="#8b5cf6" unit={t('monitor.hostMonitorPanel.portUnit')} domain={[0, 'auto']} aggType={aggType} /> } iconTone="yellow" data={metrics.tcpTW} color="#f59e0b" unit="个" domain={[0, 'auto']} aggType={aggType} /> } iconTone="red" data={metrics.tcpCW} color="#ef4444" unit="个" domain={[0, 'auto']} aggType={aggType} /> - } iconTone="cyan" + } iconTone="cyan" data={metrics.connTotal} color="#22d3ee" unit="个" domain={[0, 'auto']} aggType={aggType} />
- } iconTone="red" + } iconTone="red" data={metrics.rxErr} color="#ef4444" unit="个" domain={[0, 'auto']} aggType={aggType} /> - } iconTone="orange" + } iconTone="orange" data={metrics.txErr} color="#f97316" unit="个" domain={[0, 'auto']} aggType={aggType} />
diff --git a/frontend/src/pages/monitor/host-monitor/tabs/PortMonitorTab.tsx b/frontend/src/pages/monitor/host-monitor/tabs/PortMonitorTab.tsx index dd9370e..5ba6f90 100644 --- a/frontend/src/pages/monitor/host-monitor/tabs/PortMonitorTab.tsx +++ b/frontend/src/pages/monitor/host-monitor/tabs/PortMonitorTab.tsx @@ -4,7 +4,7 @@ import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Server } from 'lucide-react'; -import i18n from '@/i18n'; +import { useTranslation } from 'react-i18next'; export interface PortMonitorTabProps { portInfo: JsonData[]; @@ -14,6 +14,7 @@ export interface PortMonitorTabProps { } export function PortMonitorTab({ portInfo, portPolicyMode, portWhitelist, onOpenPortPolicy }: PortMonitorTabProps) { + const { t } = useTranslation(); return ( @@ -44,10 +45,10 @@ export function PortMonitorTab({ portInfo, portPolicyMode, portWhitelist, onOpen - - - - + + + + diff --git a/frontend/src/pages/monitor/host-monitor/tabs/ProcessTable.tsx b/frontend/src/pages/monitor/host-monitor/tabs/ProcessTable.tsx index a270c9c..62f472a 100644 --- a/frontend/src/pages/monitor/host-monitor/tabs/ProcessTable.tsx +++ b/frontend/src/pages/monitor/host-monitor/tabs/ProcessTable.tsx @@ -2,9 +2,10 @@ import type { JsonData } from '@/types/common'; import { Card, CardContent } from '@/components/ui/card'; import { List } from 'lucide-react'; -import i18n from '@/i18n'; +import { useTranslation } from 'react-i18next'; export function ProcessTable({ processTop }: { processTop: JsonData[] }) { + const { t } = useTranslation(); return ( @@ -20,11 +21,11 @@ export function ProcessTable({ processTop }: { processTop: JsonData[] }) {
{i18n.t('system.ldapPort')} {i18n.t('kubernetes.service.protocol')} {i18n.t('进程监控')} {i18n.t('system.terminalStatus')} {t('system.ldapPort')} {t('kubernetes.service.protocol')} {t('monitor.lbl.processMonitor')} {t('system.terminalStatus')}
- + - + - + @@ -41,7 +42,7 @@ export function ProcessTable({ processTop }: { processTop: JsonData[] }) { + diff --git a/frontend/src/pages/monitor/middleware-monitor/MwOverviewCharts.tsx b/frontend/src/pages/monitor/middleware-monitor/MwOverviewCharts.tsx index fcb9195..25b91ea 100644 --- a/frontend/src/pages/monitor/middleware-monitor/MwOverviewCharts.tsx +++ b/frontend/src/pages/monitor/middleware-monitor/MwOverviewCharts.tsx @@ -10,7 +10,7 @@ import type { MwTarget } from './helpers'; import { fmtHitRate, fmtVal } from './helpers'; import { ChartExpandShell } from '../shared/ChartExpandShell'; import { MonitorChartTooltip, MONITOR_PIE_STROKE, MonitorPieTooltip } from '../shared/monitorChartTheme'; -import i18n from '@/i18n'; +import { useTranslation } from 'react-i18next'; interface Props { targets: MwTarget[]; @@ -21,12 +21,13 @@ function shortName(name: string) { } export function MwOverviewCharts({ targets }: Props) { + const { t } = useTranslation(); const statusData = useMemo(() => { const online = targets.filter((t) => t.status === 'online').length; const offline = targets.length - online; return [ - { name: i18n.t('agent.agentOnline'), value: online, fill: 'var(--dg-green)' }, - { name: i18n.t('agent.agentOffline'), value: offline, fill: 'var(--dg-red)' }, + { name: t('agent.agentOnline'), value: online, fill: 'var(--dg-green)' }, + { name: t('agent.agentOffline'), value: offline, fill: 'var(--dg-red)' }, ].filter((d) => d.value > 0); }, [targets]); @@ -53,7 +54,7 @@ export function MwOverviewCharts({ targets }: Props) { return (
- } empty={statusData.length === 0}> + } empty={statusData.length === 0}> {(h) => ( @@ -67,13 +68,13 @@ export function MwOverviewCharts({ targets }: Props) { )} - } empty={redisHitData.length === 0}> + } empty={redisHitData.length === 0}> {(h) => ( `${v}%`} /> - fmtHitRate(v / 100)} nameFormatter={() => i18n.t('命中率')} />} /> + fmtHitRate(v / 100)} nameFormatter={() => t('monitor.lbl.hitRate')} />} /> {redisHitData.map((e) => )} @@ -97,13 +98,13 @@ export function MwOverviewCharts({ targets }: Props) { )} - } empty={mysqlConnData.length === 0}> + } empty={mysqlConnData.length === 0}> {(h) => ( - fmtVal(v)} nameFormatter={() => i18n.t('连接')} />} /> + fmtVal(v)} nameFormatter={() => t('monitor.lbl.connections')} />} /> {mysqlConnData.map((e) => )} diff --git a/frontend/src/pages/monitor/middleware-monitor/MwTargetChartsPanel.tsx b/frontend/src/pages/monitor/middleware-monitor/MwTargetChartsPanel.tsx index 01e048a..b03ab98 100644 --- a/frontend/src/pages/monitor/middleware-monitor/MwTargetChartsPanel.tsx +++ b/frontend/src/pages/monitor/middleware-monitor/MwTargetChartsPanel.tsx @@ -18,7 +18,7 @@ import { MwTrendChart } from './MwTrendChart'; import { MwGaugeBar } from './MwGaugeBar'; import { MwHealthStrip } from './MwHealthStrip'; import { ChartExpandShell } from '../shared/ChartExpandShell'; -import i18n from '@/i18n'; +import { useTranslation } from 'react-i18next'; interface Props { target: MwTarget | null; @@ -27,6 +27,7 @@ interface Props { } export function MwTargetChartsPanel({ target, chartDefs, accent }: Props) { + const { t } = useTranslation(); const [timeRange, setTimeRange] = useState('6h'); const [historyMap, setHistoryMap] = useState>>({}); const [loading, setLoading] = useState(false); @@ -92,15 +93,15 @@ export function MwTargetChartsPanel({ target, chartDefs, accent }: Props) {
diff --git a/frontend/src/pages/monitor/middleware-monitor/helpers.ts b/frontend/src/pages/monitor/middleware-monitor/helpers.ts index d629a44..0d14238 100644 --- a/frontend/src/pages/monitor/middleware-monitor/helpers.ts +++ b/frontend/src/pages/monitor/middleware-monitor/helpers.ts @@ -60,7 +60,7 @@ export const MYSQL_PRIMARY_KEYS = ['mysql_qps', 'mysql_connections', 'mysql_buff export const REDIS_CHARTS: MwChartDef[] = [ { key: 'redis_qps', label: 'QPS', color: 'var(--dg-blue)', unit: 'ops/s' }, { key: 'redis_used_memory', label: i18n.t('内存占用'), color: 'var(--dg-red)', formatter: fmtMemory }, - { key: 'redis_hit_rate', label: i18n.t('命中率'), color: 'var(--dg-green)', percent: true }, + { key: 'redis_hit_rate', label: i18n.t('monitor.lbl.hitRate'), color: 'var(--dg-green)', percent: true }, { key: 'redis_connected_clients', label: i18n.t('kubernetes.topology.connections'), color: 'var(--dg-cyan)' }, { key: 'redis_db_size', label: i18n.t('慢查询速率'), color: 'var(--dg-purple)' }, { key: 'redis_blocked_clients', label: i18n.t('阻塞客户端'), color: 'var(--dg-orange)' }, diff --git a/frontend/src/pages/monitor/shared/ChartRangeHint.tsx b/frontend/src/pages/monitor/shared/ChartRangeHint.tsx index 3a08dbb..62c9694 100644 --- a/frontend/src/pages/monitor/shared/ChartRangeHint.tsx +++ b/frontend/src/pages/monitor/shared/ChartRangeHint.tsx @@ -1,7 +1,7 @@ // 图表选区提示 — 仅缩放后显示恢复入口 // Logan.Li import { ZoomOut } from 'lucide-react'; import { Button } from '@/components/ui/button'; -import i18n from '@/i18n'; +import { useTranslation } from 'react-i18next'; interface Props { label: string | null; @@ -10,10 +10,11 @@ interface Props { } export function ChartRangeHint({ label, zoomed, onReset }: Props) { + const { t } = useTranslation(); if (!zoomed) return null; return (
- {i18n.t('monitor.chartrangehintt.s39ce9bad')} {label} + {t('monitor.chartrangehintt.s39ce9bad')} {label} diff --git a/frontend/src/pages/monitor/site-monitor/SiteProtocolPieChart.tsx b/frontend/src/pages/monitor/site-monitor/SiteProtocolPieChart.tsx index 583de87..efe454e 100644 --- a/frontend/src/pages/monitor/site-monitor/SiteProtocolPieChart.tsx +++ b/frontend/src/pages/monitor/site-monitor/SiteProtocolPieChart.tsx @@ -6,7 +6,7 @@ import { Card, CardContent } from '@/components/ui/card'; import { PieChart as PieChartIcon } from 'lucide-react'; import { PROBE_TYPE_OPTIONS, normalizeProbeType } from './helpers'; import type { SiteTask } from './helpers'; -import i18n from '@/i18n'; +import { useTranslation } from 'react-i18next'; interface Props { tasks: SiteTask[]; @@ -21,6 +21,7 @@ const PROTOCOL_COLORS: Record = { }; export function SiteProtocolPieChart({ tasks }: Props) { + const { t } = useTranslation(); const protocolData = useMemo(() => { const counts: Record = {}; for (const t of PROBE_TYPE_OPTIONS) counts[t] = 0; @@ -36,7 +37,7 @@ export function SiteProtocolPieChart({ tasks }: Props) { if (tasks.length === 0) { return ( - {i18n.t('可用率(24h)')} + {t('monitor.lbl.availability24h')} ); } @@ -46,7 +47,7 @@ export function SiteProtocolPieChart({ tasks }: Props) {
-

{i18n.t('可用率(24h)')}

+

{t('monitor.lbl.availability24h')}

共 {tasks.length} 个任务
diff --git a/frontend/src/pages/monitor/site-monitor/SiteResultsPanel.tsx b/frontend/src/pages/monitor/site-monitor/SiteResultsPanel.tsx index e91a71a..e8e32b8 100644 --- a/frontend/src/pages/monitor/site-monitor/SiteResultsPanel.tsx +++ b/frontend/src/pages/monitor/site-monitor/SiteResultsPanel.tsx @@ -19,6 +19,7 @@ import { SITE_TIME_RANGE_HOURS, SITE_TIME_RANGE_LABELS, } from './helpers'; import { SiteProtocolObs } from './observability/SiteProtocolObs'; +import { useTranslation } from 'react-i18next'; import i18n from '@/i18n'; interface Props { @@ -48,6 +49,7 @@ function httpCodeBadge(code?: number, expected = 200) { } export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) { + const { t } = useTranslation(); const [results, setResults] = useState([]); const [loading, setLoading] = useState(false); const [probing, setProbing] = useState(false); @@ -77,11 +79,11 @@ export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) { setProbing(true); try { await siteApi.probeNow(task.id); - toast.success(i18n.t('拨测完成')); + toast.success(t('monitor.lbl.probeDone')); loadResults(); onProbed(); } catch (e) { - toast.error((e as { message?: string }).message || i18n.t('拨测失败')); + toast.error((e as { message?: string }).message || t('monitor.lbl.probeFail')); } finally { setProbing(false); } @@ -117,41 +119,41 @@ export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) { case 'HTTP': return ( - {i18n.t('system.auditTime')} - {i18n.t('system.terminalStatus')} - {i18n.t('monitor.siteProtocol.http.statusCodeName')} - {i18n.t('monitor.siteProtocol.latency.latencyName')} - {i18n.t('cloud.colDescription')} + {t('system.auditTime')} + {t('system.terminalStatus')} + {t('monitor.siteProtocol.http.statusCodeName')} + {t('monitor.siteProtocol.latency.latencyName')} + {t('cloud.colDescription')} ); case 'SSL': return ( - {i18n.t('system.auditTime')} - {i18n.t('编辑拨测任务')} - {i18n.t('编辑拨测任务')} - {i18n.t('monitor.siteProtocol.ssl.daysName')} - {i18n.t('monitor.siteProtocol.ssl.issuer')} + {t('system.auditTime')} + {t('monitor.lbl.editTask')} + {t('monitor.lbl.editTask')} + {t('monitor.siteProtocol.ssl.daysName')} + {t('monitor.siteProtocol.ssl.issuer')} ); case 'TCP': return ( - {i18n.t('system.auditTime')} - {i18n.t('monitor.siteProtocol.tcp.connected')} - {i18n.t('monitor.siteProtocol.latency.latencyName')} - {i18n.t('编辑拨测任务')} - {i18n.t('cloud.colDescription')} + {t('system.auditTime')} + {t('monitor.siteProtocol.tcp.connected')} + {t('monitor.siteProtocol.latency.latencyName')} + {t('monitor.lbl.editTask')} + {t('cloud.colDescription')} ); default: return ( - {i18n.t('system.auditTime')} - {i18n.t('system.terminalStatus')} - {i18n.t('monitor.siteProtocol.latency.latencyName')} - {i18n.t('编辑拨测任务')} - {i18n.t('cloud.colDescription')} + {t('system.auditTime')} + {t('system.terminalStatus')} + {t('monitor.siteProtocol.latency.latencyName')} + {t('monitor.lbl.editTask')} + {t('cloud.colDescription')} ); } @@ -188,7 +190,7 @@ export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) { {statusBadge(r.status)} {fmtMs(r.response_time || r.total_time)} {r.remote_ip || '--'} - {r.error_message || (r.status === 'success' ? i18n.t('monitor.siteProtocol.tcp.connected') : '--')} + {r.error_message || (r.status === 'success' ? t('monitor.siteProtocol.tcp.connected') : '--')} ); default: @@ -223,10 +225,10 @@ export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) {
{i18n.t('进程监控')} {t('monitor.lbl.processMonitor')} PID {i18n.t('进程监控')} {t('monitor.lbl.processMonitor')} CPU使用率(%) {i18n.t('进程监控')} {t('monitor.lbl.processMonitor')}
{renderTableHead()} diff --git a/frontend/src/pages/monitor/snmp-monitor/SnmpDeviceChartsPanel.tsx b/frontend/src/pages/monitor/snmp-monitor/SnmpDeviceChartsPanel.tsx index c709ada..67db7aa 100644 --- a/frontend/src/pages/monitor/snmp-monitor/SnmpDeviceChartsPanel.tsx +++ b/frontend/src/pages/monitor/snmp-monitor/SnmpDeviceChartsPanel.tsx @@ -15,7 +15,7 @@ import { SNMP_TIME_RANGE_HOURS, fmtMetricValue, isSnmpCounterMetric, metricColor, pickSeriesByCategory, } from './helpers'; import { SnmpMetricTrendChart } from './SnmpMetricTrendChart'; -import i18n from '@/i18n'; +import { useTranslation } from 'react-i18next'; interface Props { device: SnmpDevice | null; @@ -30,6 +30,7 @@ interface ChartBlock { } export function SnmpDeviceChartsPanel({ device, templateName }: Props) { + const { t } = useTranslation(); const [timeRange, setTimeRange] = useState('24h'); const [series, setSeries] = useState([]); const [charts, setCharts] = useState([]); @@ -115,9 +116,9 @@ export function SnmpDeviceChartsPanel({ device, templateName }: Props) { - {i18n.t('monitor.siteProtocol.rangeDefault')} - {i18n.t('选择模板')} - {i18n.t('选择模板')} + {t('monitor.siteProtocol.rangeDefault')} + {t('monitor.lbl.selectTemplate')} + {t('monitor.lbl.selectTemplate')}