diff --git a/frontend/src/components/monitor/dashboard/ZabbixDashboard.tsx b/frontend/src/components/monitor/dashboard/ZabbixDashboard.tsx index 371dd8a..a2d696a 100644 --- a/frontend/src/components/monitor/dashboard/ZabbixDashboard.tsx +++ b/frontend/src/components/monitor/dashboard/ZabbixDashboard.tsx @@ -1,7 +1,7 @@ // Zabbix 数据源监控面板 — 接受外部配置 // Logan.Li import React, { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { - Server, Loader2, AlertTriangle, @@ -17,7 +17,6 @@ import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { zabbixProxyApi } from '@/api'; import type { JsonData } from '@/types/common'; -import i18n from '@/i18n'; interface ZabbixHost { hostid: string; hostname: string; name: string; ip: string; available: string; @@ -31,6 +30,7 @@ type SortField = 'hostname' | 'cpu_usage' | 'memory_usage' | 'disk_usage' | 'loa interface Props { dsUrl?: string; dsUsername?: string; dsPassword?: string; } export const ZabbixDashboard: React.FC = ({ dsUrl, dsUsername, dsPassword }) => { + const { t } = useTranslation(); const [connected, setConnected] = useState(false); const [loading, setLoading] = useState(false); const [hosts, setHosts] = useState([]); @@ -43,6 +43,7 @@ export const ZabbixDashboard: React.FC = ({ dsUrl, dsUsername, dsPassword useEffect(() => { if (dsUrl && dsUsername && dsPassword) doConnect(dsUrl, dsUsername, dsPassword); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [dsUrl, dsUsername, dsPassword]); const doConnect = async (u: string, un: string, pw: string) => { @@ -52,7 +53,7 @@ export const ZabbixDashboard: React.FC = ({ dsUrl, dsUsername, dsPassword setHosts(data.hosts || []); setVersion(data.version || ''); setConnected(true); - } catch (e) { setConnected(false); setError((e as JsonData).message || i18n.t('aiops.llmTestFailed')); } + } catch (e) { setConnected(false); setError((e as JsonData).message || t('aiops.llmTestFailed')); } finally { setLoading(false); } }; @@ -99,24 +100,24 @@ export const ZabbixDashboard: React.FC = ({ dsUrl, dsUsername, dsPassword
- Zabbix 主机监控 + {t('monitor.zabbixDs.hostMonitor')} {version && v{version}} - {connected && {hosts.length} 台} - {connected &&
} + {connected && {hosts.length} {t('monitor.zabbixDs.unit')}} + {connected &&
}
- {loading ? (

{i18n.t('monitor.prometheusdashb.s5e8eb212')}

) + {loading ? (

{t('monitor.zabbixDs.loading')}

) : error ? (
{error}
) - : hosts.length === 0 ? (

{i18n.t('monitor.zabbixdashboard.sdb131ab4')}

) + : hosts.length === 0 ? (

{t('monitor.zabbixDs.noHosts')}

) : (
{[ - { f: 'hostname' as SortField, l: i18n.t('kubernetes.workload.hostname') },{ f: 'cpu_usage' as SortField, l: 'CPU' },{ f: 'memory_usage' as SortField, l: i18n.t('kubernetes.pod.memory') }, - { f: 'disk_usage' as SortField, l: i18n.t('monitor.dashboard.col.disk') },{ f: 'load5' as SortField, l: 'Load5' },{ f: 'net_in_bps' as SortField, l: i18n.t('磁盘') }, - { f: 'net_out_bps' as SortField, l: i18n.t('流出') },{ f: 'problem_count' as SortField, l: i18n.t('问题') },{ f: 'uptime_days' as SortField, l: i18n.t('automation.scriptRun') }, + { f: 'hostname' as SortField, l: t('kubernetes.workload.hostname') },{ f: 'cpu_usage' as SortField, l: 'CPU' },{ f: 'memory_usage' as SortField, l: t('kubernetes.pod.memory') }, + { f: 'disk_usage' as SortField, l: t('monitor.dashboard.col.disk') },{ f: 'load5' as SortField, l: 'Load5' },{ f: 'net_in_bps' as SortField, l: t('monitor.zabbixDs.disk') }, + { f: 'net_out_bps' as SortField, l: t('monitor.zabbixDs.flowOut') },{ f: 'problem_count' as SortField, l: t('monitor.zabbixDs.problems') },{ f: 'uptime_days' as SortField, l: t('automation.scriptRun') }, ].map(({ f, l }) => ( @@ -140,15 +141,15 @@ export const ZabbixDashboard: React.FC = ({ dsUrl, dsUsername, dsPassword {sorted.length > pageSize && (
- 共 {sorted.length} 台主机 + {t('monitor.zabbixDs.totalHosts', { count: sorted.length })}
- + {safePage} / {totalPages} - +
)} diff --git a/frontend/src/components/monitor/datasource/ZabbixDatasourceManager.tsx b/frontend/src/components/monitor/datasource/ZabbixDatasourceManager.tsx index 976159c..57f43c2 100644 --- a/frontend/src/components/monitor/datasource/ZabbixDatasourceManager.tsx +++ b/frontend/src/components/monitor/datasource/ZabbixDatasourceManager.tsx @@ -1,7 +1,6 @@ // Zabbix 数据源管理 — 参照 Grafana Zabbix 数据源配置界面 // Logan.Li import { useState, useEffect, useCallback } from 'react'; import { - Database, Plus, Trash2, @@ -18,6 +17,7 @@ import { CheckCircle2, XCircle, } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -32,7 +32,6 @@ import { toast } from 'sonner'; import { useConfirm } from '@/components/ui/confirm-dialog'; import { zabbixDatasourceApi } from '@/api'; import type { JsonData } from '@/types/common'; -import i18n from '@/i18n'; interface ZabbixDS { id: number; @@ -60,6 +59,7 @@ const defaultForm = { }; export default function ZabbixDatasourceManager() { + const { t } = useTranslation(); const _confirm = useConfirm(); const [sources, setSources] = useState([]); const [loading, setLoading] = useState(true); @@ -79,11 +79,11 @@ export default function ZabbixDatasourceManager() { const res: JsonData = await zabbixDatasourceApi.list(); setSources(res?.data || res || []); } catch { - toast.error(i18n.t('获取数据源列表失败')); + toast.error(t('monitor.zabbixDs.fetchListError')); } finally { setLoading(false); } - }, []); + }, [t]); useEffect(() => { fetchList(); }, [fetchList]); @@ -116,51 +116,51 @@ export default function ZabbixDatasourceManager() { }; const handleSave = async () => { - if (!form.name.trim()) { toast.error(i18n.t('monitor.promDs.nameRequired')); return; } - if (!form.url.trim()) { toast.error(i18n.t('请输入 Zabbix API URL')); return; } - if (!form.username.trim()) { toast.error(i18n.t('pipeline.usernameRequired')); return; } - if (!form.password.trim()) { toast.error(i18n.t('login.passwordRequired')); return; } + if (!form.name.trim()) { toast.error(t('monitor.promDs.nameRequired')); return; } + if (!form.url.trim()) { toast.error(t('monitor.zabbixDs.urlRequired')); return; } + if (!form.username.trim()) { toast.error(t('pipeline.usernameRequired')); return; } + if (!form.password.trim()) { toast.error(t('login.passwordRequired')); return; } try { if (editingId) { await zabbixDatasourceApi.update(editingId, form); - toast.success(i18n.t('数据源已更新')); + toast.success(t('monitor.zabbixDs.datasourceSaved')); } else { await zabbixDatasourceApi.create(form); - toast.success(i18n.t('数据源已更新')); + toast.success(t('monitor.zabbixDs.datasourceSaved')); } setDialogOpen(false); fetchList(); } catch (err) { - toast.error(err?.message || i18n.t('cloud.operationFailed')); + toast.error((err as Error)?.message || t('cloud.operationFailed')); } }; const handleDelete = async (ds: ZabbixDS) => { const ok = await _confirm({ - title: i18n.t('monitor.deleteDatasource'), - description: `确定删除「${ds.name}」?`, + title: t('monitor.deleteDatasource'), + description: t('monitor.zabbixDs.deleteConfirm', { name: ds.name }), variant: 'destructive', }); if (!ok) return; try { await zabbixDatasourceApi.delete(ds.id); - toast.success(i18n.t('monitor.silenceRules.deleted')); + toast.success(t('monitor.silenceRules.deleted')); fetchList(); - } catch { toast.error(i18n.t('kubernetes.msg.deleteFailed2')); } + } catch { toast.error(t('kubernetes.msg.deleteFailed2')); } }; const handleSetDefault = async (ds: ZabbixDS) => { try { await zabbixDatasourceApi.setDefault(ds.id); - toast.success(`已将「${ds.name}」设为默认`); + toast.success(t('monitor.zabbixDs.setDefaultSuccess', { name: ds.name })); fetchList(); - } catch { toast.error(i18n.t('删除失败')); } + } catch { toast.error(t('monitor.zabbixDs.deleteFailed')); } }; const handleTest = async () => { if (!form.url.trim() || !form.username.trim()) { - toast.error(i18n.t('获取数据源列表失败')); + toast.error(t('monitor.zabbixDs.fetchListError')); return; } setTesting(true); @@ -174,7 +174,7 @@ export default function ZabbixDatasourceManager() { const data = res?.data || res; setTestResult(data); } catch (err) { - setTestResult({ success: false, message: err?.message || i18n.t('monitor.alertChannels.testFailed') }); + setTestResult({ success: false, message: (err as Error)?.message || t('monitor.alertChannels.testFailed') }); } finally { setTesting(false); } @@ -198,12 +198,12 @@ export default function ZabbixDatasourceManager() { className="w-5 h-5 rounded" onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }} /> - Zabbix 数据源 + {t('monitor.zabbixDs.title')} -

{i18n.t('monitor.zabbixdatasourc.s79e929d8')}

+

{t('monitor.zabbixDs.subtitle')}

@@ -211,9 +211,9 @@ export default function ZabbixDatasourceManager() { -

{i18n.t('monitor.zabbixdatasourc.sc9c9bd18')}

+

{t('monitor.zabbixDs.emptyState')}

@@ -227,25 +227,25 @@ export default function ZabbixDatasourceManager() {
{ds.name} - {ds.is_default && {i18n.t('k8s.storage.default')} } + {ds.is_default && {t('k8s.storage.default')} } {ds.status === 1 - ? {i18n.t('system.userActive')} - : {i18n.t('system.userInactive')} } + ? {t('system.userActive')} + : {t('system.userInactive')} }
{ds.url} - 用户: {ds.username} - {ds.direct_db && {i18n.t('monitor.zabbixdatasourc.s85260abd')} } + {t('monitor.zabbixDs.user')}: {ds.username} + {ds.direct_db && {t('monitor.zabbixDs.directDbBadge')} }
{!ds.is_default && ( )} {showAuth && (
- updateForm('username', e.target.value)} /> - updateForm('password', e.target.value)} />
)} @@ -326,7 +326,7 @@ export default function ZabbixDatasourceManager() { onClick={() => setShowTrends(!showTrends)}>
- Trends 设置 + {t('monitor.zabbixDs.trendsSettings')}
{showTrends ? : } @@ -334,24 +334,24 @@ export default function ZabbixDatasourceManager() {
- + updateForm('trends_after', parseInt(e.target.value) || 0)} />
- + updateForm('trends_range', parseInt(e.target.value) || 0)} />
- + updateForm('trends_cache_ttl', parseInt(e.target.value) || 3600)} />
- + updateForm('trends_timeout', parseInt(e.target.value) || 30)} />
@@ -366,7 +366,7 @@ export default function ZabbixDatasourceManager() { onClick={() => setShowOther(!showOther)}>
- {i18n.t('编辑 Zabbix 数据源')} + {t('monitor.zabbixDs.otherSettings')}
{showOther ? : } @@ -377,11 +377,11 @@ export default function ZabbixDatasourceManager() { updateForm('direct_db', v)} />
- + updateForm('disable_acknowledges', v)} />
- + updateForm('disable_data_alignment', v)} />
@@ -402,11 +402,11 @@ export default function ZabbixDatasourceManager() {
- - + + diff --git a/frontend/src/locales/en-US.json b/frontend/src/locales/en-US.json index 8eacd47..44648ab 100644 --- a/frontend/src/locales/en-US.json +++ b/frontend/src/locales/en-US.json @@ -511,7 +511,9 @@ "created": "Created", "severity": "Severity", "critical": "Critical", - "request.tokenExpired": "Session expired, please log in again" + "request.tokenExpired": "Session expired, please log in again", + "days": "days", + "seconds": "sec" }, "menu": { "dashboard": "Dashboard", @@ -4393,6 +4395,41 @@ "wechat": "WeChat Work", "email": "Email", "sms": "SMS" + }, + "zabbixDs": { + "title": "Zabbix Datasource", + "subtitle": "Configure Zabbix API connection to get host monitoring data", + "addDatasource": "Add Datasource", + "emptyState": "No Zabbix data sources", + "fetchListError": "Failed to fetch datasource list", + "urlRequired": "Please enter Zabbix API URL", + "datasourceSaved": "Datasource saved", + "deleteConfirm": "Are you sure to delete \"{{name}}\"?", + "setDefaultSuccess": "\"{{name}}\" set as default", + "deleteFailed": "Delete failed", + "editTitle": "Edit Zabbix Datasource", + "createTitle": "Create Zabbix Datasource", + "dialogDesc": "Configure Zabbix API connection for host monitoring and alert data", + "namePlaceholder": "e.g. Production Zabbix", + "apiAuth": "Zabbix API Authentication", + "trendsSettings": "Trends Settings", + "otherSettings": "Other Settings", + "directDbBadge": "Direct DB", + "disableAck": "Disable Acknowledges (Read-only User)", + "disableDataAlignment": "Disable Data Alignment", + "testConnection": "Test Connection", + "user": "User", + "loading": "Loading...", + "noHosts": "No monitored Zabbix hosts found", + "hostMonitor": "Zabbix Host Monitor", + "unit": "hosts", + "disk": "Disk", + "flowOut": "Out", + "problems": "Problems", + "totalHosts": "{{count}} hosts total", + "perPage": "/page", + "prevPage": "Prev", + "nextPage": "Next" } }, "task": { diff --git a/frontend/src/locales/zh-CN.json b/frontend/src/locales/zh-CN.json index 46114dc..1f4ce87 100644 --- a/frontend/src/locales/zh-CN.json +++ b/frontend/src/locales/zh-CN.json @@ -511,7 +511,9 @@ "created": "已创建", "severity": "级别", "critical": "严重", - "request.tokenExpired": "登录已过期,请重新登录" + "request.tokenExpired": "登录已过期,请重新登录", + "days": "天", + "seconds": "秒" }, "menu": { "dashboard": "仪表盘", @@ -4393,6 +4395,41 @@ "wechat": "企业微信", "email": "邮件", "sms": "短信" + }, + "zabbixDs": { + "title": "Zabbix 数据源", + "subtitle": "配置 Zabbix API 连接,获取主机监控数据", + "addDatasource": "添加数据源", + "emptyState": "暂无 Zabbix 数据源", + "fetchListError": "获取数据源列表失败", + "urlRequired": "请输入 Zabbix API URL", + "datasourceSaved": "数据源已保存", + "deleteConfirm": "确定删除「{{name}}」?", + "setDefaultSuccess": "已将「{{name}}」设为默认", + "deleteFailed": "删除失败", + "editTitle": "编辑 Zabbix 数据源", + "createTitle": "新建 Zabbix 数据源", + "dialogDesc": "配置 Zabbix API 连接,用于主机监控与告警数据获取", + "namePlaceholder": "如:生产 Zabbix", + "apiAuth": "Zabbix API 认证", + "trendsSettings": "Trends 设置", + "otherSettings": "其他设置", + "directDbBadge": "直连DB", + "disableAck": "只读用户禁用确认", + "disableDataAlignment": "禁用数据对齐", + "testConnection": "测试连接", + "user": "用户", + "loading": "加载中...", + "noHosts": "未找到被监控的 Zabbix 主机", + "hostMonitor": "Zabbix 主机监控", + "unit": "台", + "disk": "磁盘", + "flowOut": "流出", + "problems": "问题", + "totalHosts": "共 {{count}} 台主机", + "perPage": "/页", + "prevPage": "上一页", + "nextPage": "下一页" } }, "task": {
toggleSort(f)}>
{l}