refactor(i18n): U21 Zabbix 组件 i18n 治理 — ZabbixDatasourceManager + ZabbixDashboard 从 i18n.t() 全局改为 useTranslation() hook

- ZabbixDatasourceManager.tsx (415L): 30 处 i18n.t()/硬编码中文清零, 删除 5 个 monitor.zabbixdatasourc.* hash 键, 新增 monitor.zabbixDs.* 语义化命名空间 (23 键)
- ZabbixDashboard.tsx (162L): 12 处 i18n.t()/硬编码中文清零, 删除 monitor.zabbixdashboard.* + monitor.prometheusdashb.s5e8eb212 hash 键, 修复 3 个中文幻影键 (i18n.t('磁盘') → t('monitor.zabbixDs.disk') 等)
- 新增 common.days/common.seconds 通用单位键
- 删除 monitor.zabbixdatasourc (5 hash 键) + monitor.zabbixdashboard (1 hash 键) + monitor.prometheusdashb.s5e8eb212 hash 键
- zh/en 6767=6767 对齐, tsc -b=0, npm build✓ (17.41s), ESLint=0/43/0/0, go build=0, 17/17 测试通过
This commit is contained in:
attacker committed 2026-07-11 12:57:46 +08:00
1 parent f953ba3248
commit fba5fc46b6
4 files changed
+142 -67

No files matched your search

@@ -1,7 +1,7 @@
// Zabbix 数据源监控面板 — 接受外部配置 // Logan.Li // Zabbix 数据源监控面板 — 接受外部配置 // Logan.Li
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { import {
Server, Server,
Loader2, Loader2,
AlertTriangle, AlertTriangle,
@@ -17,7 +17,6 @@ import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { zabbixProxyApi } from '@/api'; import { zabbixProxyApi } from '@/api';
import type { JsonData } from '@/types/common'; import type { JsonData } from '@/types/common';
import i18n from '@/i18n';
interface ZabbixHost { interface ZabbixHost {
hostid: string; hostname: string; name: string; ip: string; available: string; 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; } interface Props { dsUrl?: string; dsUsername?: string; dsPassword?: string; }
export const ZabbixDashboard: React.FC<Props> = ({ dsUrl, dsUsername, dsPassword }) => { export const ZabbixDashboard: React.FC<Props> = ({ dsUrl, dsUsername, dsPassword }) => {
const { t } = useTranslation();
const [connected, setConnected] = useState(false); const [connected, setConnected] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [hosts, setHosts] = useState<ZabbixHost[]>([]); const [hosts, setHosts] = useState<ZabbixHost[]>([]);
@@ -43,6 +43,7 @@ export const ZabbixDashboard: React.FC<Props> = ({ dsUrl, dsUsername, dsPassword
useEffect(() => { useEffect(() => {
if (dsUrl && dsUsername && dsPassword) doConnect(dsUrl, dsUsername, dsPassword); if (dsUrl && dsUsername && dsPassword) doConnect(dsUrl, dsUsername, dsPassword);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dsUrl, dsUsername, dsPassword]); }, [dsUrl, dsUsername, dsPassword]);
const doConnect = async (u: string, un: string, pw: string) => { const doConnect = async (u: string, un: string, pw: string) => {
@@ -52,7 +53,7 @@ export const ZabbixDashboard: React.FC<Props> = ({ dsUrl, dsUsername, dsPassword
setHosts(data.hosts || []); setHosts(data.hosts || []);
setVersion(data.version || ''); setVersion(data.version || '');
setConnected(true); 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); } finally { setLoading(false); }
}; };
@@ -99,24 +100,24 @@ export const ZabbixDashboard: React.FC<Props> = ({ dsUrl, dsUsername, dsPassword
<CardHeader className="py-3 px-4 pb-2"> <CardHeader className="py-3 px-4 pb-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Activity className="h-4 w-4 dg-text-blue" /> <Activity className="h-4 w-4 dg-text-blue" />
<CardTitle className="text-sm font-medium">Zabbix 主机监控</CardTitle> <CardTitle className="text-sm font-medium">{t('monitor.zabbixDs.hostMonitor')}</CardTitle>
{version && <Badge className="text-[10px] bg-blue-500/10 dg-text-blue">v{version}</Badge>} {version && <Badge className="text-[10px] bg-blue-500/10 dg-text-blue">v{version}</Badge>}
{connected && <span className="text-[10px] text-[var(--dg-text-muted)] ml-2">{hosts.length} 台</span>} {connected && <span className="text-[10px] text-[var(--dg-text-muted)] ml-2">{hosts.length} {t('monitor.zabbixDs.unit')}</span>}
{connected && <div className="ml-auto flex gap-1"><Button variant="ghost" size="sm" className="h-6 px-2 text-[10px]" onClick={refresh}><RefreshCw className="w-3 h-3 mr-1" /> {i18n.t('monitor.dashboard.refresh')} </Button></div>} {connected && <div className="ml-auto flex gap-1"><Button variant="ghost" size="sm" className="h-6 px-2 text-[10px]" onClick={refresh}><RefreshCw className="w-3 h-3 mr-1" /> {t('monitor.dashboard.refresh')} </Button></div>}
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="px-4 pb-3 pt-0"> <CardContent className="px-4 pb-3 pt-0">
{loading ? (<div className="text-center py-12"><Loader2 className="h-6 w-6 mx-auto mb-2 animate-spin dg-text-blue" /><p className="text-xs text-[var(--dg-text-muted)]"> {i18n.t('monitor.prometheusdashb.s5e8eb212')} </p></div>) {loading ? (<div className="text-center py-12"><Loader2 className="h-6 w-6 mx-auto mb-2 animate-spin dg-text-blue" /><p className="text-xs text-[var(--dg-text-muted)]"> {t('monitor.zabbixDs.loading')} </p></div>)
: error ? (<div className="p-3 rounded-lg bg-red-500/10 border border-red-500/20 text-xs dg-text-red flex items-start gap-2"><AlertTriangle className="h-4 w-4 mt-0.5" /><span>{error}</span></div>) : error ? (<div className="p-3 rounded-lg bg-red-500/10 border border-red-500/20 text-xs dg-text-red flex items-start gap-2"><AlertTriangle className="h-4 w-4 mt-0.5" /><span>{error}</span></div>)
: hosts.length === 0 ? (<div className="text-center py-8 text-[var(--dg-text-muted)]"><Server className="h-8 w-8 mx-auto mb-2 dg-text-muted" /><p className="text-sm"> {i18n.t('monitor.zabbixdashboard.sdb131ab4')} </p></div>) : hosts.length === 0 ? (<div className="text-center py-8 text-[var(--dg-text-muted)]"><Server className="h-8 w-8 mx-auto mb-2 dg-text-muted" /><p className="text-sm"> {t('monitor.zabbixDs.noHosts')} </p></div>)
: ( : (
<div className="overflow-x-auto rounded-lg border border-[var(--dg-border)]"> <div className="overflow-x-auto rounded-lg border border-[var(--dg-border)]">
<table className="w-full text-xs"> <table className="w-full text-xs">
<thead className="bg-[var(--dg-bg-tertiary)] border-b border-[var(--dg-border)]"><tr> <thead className="bg-[var(--dg-bg-tertiary)] border-b border-[var(--dg-border)]"><tr>
{[ {[
{ 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: '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: i18n.t('monitor.dashboard.col.disk') },{ f: 'load5' as SortField, l: 'Load5' },{ f: 'net_in_bps' as SortField, l: i18n.t('磁盘') }, { 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: i18n.t('流出') },{ f: 'problem_count' as SortField, l: i18n.t('问题') },{ f: 'uptime_days' as SortField, l: i18n.t('automation.scriptRun') }, { 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 }) => ( ].map(({ f, l }) => (
<th key={f} className="px-3 py-2 text-left font-medium text-[var(--dg-text-secondary)] cursor-pointer hover:text-[var(--dg-text-primary)] select-none whitespace-nowrap" onClick={() => toggleSort(f)}> <th key={f} className="px-3 py-2 text-left font-medium text-[var(--dg-text-secondary)] cursor-pointer hover:text-[var(--dg-text-primary)] select-none whitespace-nowrap" onClick={() => toggleSort(f)}>
<div className="flex items-center gap-1">{l}<SI f={f} /></div></th> <div className="flex items-center gap-1">{l}<SI f={f} /></div></th>
@@ -140,15 +141,15 @@ export const ZabbixDashboard: React.FC<Props> = ({ dsUrl, dsUsername, dsPassword
{sorted.length > pageSize && ( {sorted.length > pageSize && (
<div className="flex items-center justify-between px-3 py-2 bg-[var(--dg-bg-tertiary)] border-t border-[var(--dg-border)] text-xs"> <div className="flex items-center justify-between px-3 py-2 bg-[var(--dg-bg-tertiary)] border-t border-[var(--dg-border)] text-xs">
<div className="flex items-center gap-2 text-[var(--dg-text-muted)]"> <div className="flex items-center gap-2 text-[var(--dg-text-muted)]">
<span>共 {sorted.length} 台主机</span> <span>{t('monitor.zabbixDs.totalHosts', { count: sorted.length })}</span>
<select className="bg-[var(--dg-bg-primary)] border border-[var(--dg-border)] rounded px-2 py-0.5 text-xs" value={pageSize} onChange={e => { setPageSize(Number(e.target.value)); setPage(1); }}> <select className="bg-[var(--dg-bg-primary)] border border-[var(--dg-border)] rounded px-2 py-0.5 text-xs" value={pageSize} onChange={e => { setPageSize(Number(e.target.value)); setPage(1); }}>
<option value={10}>10/页</option><option value={20}>20/页</option><option value={50}>50/页</option> <option value={10}>10{t('monitor.zabbixDs.perPage')}</option><option value={20}>20{t('monitor.zabbixDs.perPage')}</option><option value={50}>50{t('monitor.zabbixDs.perPage')}</option>
</select> </select>
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<button className="px-2 py-0.5 rounded hover:bg-[var(--dg-bg-primary)] disabled:opacity-30" disabled={safePage <= 1} onClick={() => setPage(Math.max(1, safePage - 1))}>上一页</button> <button className="px-2 py-0.5 rounded hover:bg-[var(--dg-bg-primary)] disabled:opacity-30" disabled={safePage <= 1} onClick={() => setPage(Math.max(1, safePage - 1))}>{t('monitor.zabbixDs.prevPage')}</button>
<span className="px-2 text-[var(--dg-text-primary)]">{safePage} / {totalPages}</span> <span className="px-2 text-[var(--dg-text-primary)]">{safePage} / {totalPages}</span>
<button className="px-2 py-0.5 rounded hover:bg-[var(--dg-bg-primary)] disabled:opacity-30" disabled={safePage >= totalPages} onClick={() => setPage(Math.min(totalPages, safePage + 1))}>下一页</button> <button className="px-2 py-0.5 rounded hover:bg-[var(--dg-bg-primary)] disabled:opacity-30" disabled={safePage >= totalPages} onClick={() => setPage(Math.min(totalPages, safePage + 1))}>{t('monitor.zabbixDs.nextPage')}</button>
</div> </div>
</div> </div>
)} )}
@@ -1,7 +1,6 @@
// Zabbix 数据源管理 — 参照 Grafana Zabbix 数据源配置界面 // Logan.Li // Zabbix 数据源管理 — 参照 Grafana Zabbix 数据源配置界面 // Logan.Li
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { import {
Database, Database,
Plus, Plus,
Trash2, Trash2,
@@ -18,6 +17,7 @@ import {
CheckCircle2, CheckCircle2,
XCircle, XCircle,
} from 'lucide-react'; } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
@@ -32,7 +32,6 @@ import { toast } from 'sonner';
import { useConfirm } from '@/components/ui/confirm-dialog'; import { useConfirm } from '@/components/ui/confirm-dialog';
import { zabbixDatasourceApi } from '@/api'; import { zabbixDatasourceApi } from '@/api';
import type { JsonData } from '@/types/common'; import type { JsonData } from '@/types/common';
import i18n from '@/i18n';
interface ZabbixDS { interface ZabbixDS {
id: number; id: number;
@@ -60,6 +59,7 @@ const defaultForm = {
}; };
export default function ZabbixDatasourceManager() { export default function ZabbixDatasourceManager() {
const { t } = useTranslation();
const _confirm = useConfirm(); const _confirm = useConfirm();
const [sources, setSources] = useState<ZabbixDS[]>([]); const [sources, setSources] = useState<ZabbixDS[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -79,11 +79,11 @@ export default function ZabbixDatasourceManager() {
const res: JsonData = await zabbixDatasourceApi.list(); const res: JsonData = await zabbixDatasourceApi.list();
setSources(res?.data || res || []); setSources(res?.data || res || []);
} catch { } catch {
toast.error(i18n.t('获取数据源列表失败')); toast.error(t('monitor.zabbixDs.fetchListError'));
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, []); }, [t]);
useEffect(() => { fetchList(); }, [fetchList]); useEffect(() => { fetchList(); }, [fetchList]);
@@ -116,51 +116,51 @@ export default function ZabbixDatasourceManager() {
}; };
const handleSave = async () => { const handleSave = async () => {
if (!form.name.trim()) { toast.error(i18n.t('monitor.promDs.nameRequired')); return; } if (!form.name.trim()) { toast.error(t('monitor.promDs.nameRequired')); return; }
if (!form.url.trim()) { toast.error(i18n.t('请输入 Zabbix API URL')); return; } if (!form.url.trim()) { toast.error(t('monitor.zabbixDs.urlRequired')); return; }
if (!form.username.trim()) { toast.error(i18n.t('pipeline.usernameRequired')); return; } if (!form.username.trim()) { toast.error(t('pipeline.usernameRequired')); return; }
if (!form.password.trim()) { toast.error(i18n.t('login.passwordRequired')); return; } if (!form.password.trim()) { toast.error(t('login.passwordRequired')); return; }
try { try {
if (editingId) { if (editingId) {
await zabbixDatasourceApi.update(editingId, form); await zabbixDatasourceApi.update(editingId, form);
toast.success(i18n.t('数据源已更新')); toast.success(t('monitor.zabbixDs.datasourceSaved'));
} else { } else {
await zabbixDatasourceApi.create(form); await zabbixDatasourceApi.create(form);
toast.success(i18n.t('数据源已更新')); toast.success(t('monitor.zabbixDs.datasourceSaved'));
} }
setDialogOpen(false); setDialogOpen(false);
fetchList(); fetchList();
} catch (err) { } 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 handleDelete = async (ds: ZabbixDS) => {
const ok = await _confirm({ const ok = await _confirm({
title: i18n.t('monitor.deleteDatasource'), title: t('monitor.deleteDatasource'),
description: `确定删除「${ds.name}」?`, description: t('monitor.zabbixDs.deleteConfirm', { name: ds.name }),
variant: 'destructive', variant: 'destructive',
}); });
if (!ok) return; if (!ok) return;
try { try {
await zabbixDatasourceApi.delete(ds.id); await zabbixDatasourceApi.delete(ds.id);
toast.success(i18n.t('monitor.silenceRules.deleted')); toast.success(t('monitor.silenceRules.deleted'));
fetchList(); fetchList();
} catch { toast.error(i18n.t('kubernetes.msg.deleteFailed2')); } } catch { toast.error(t('kubernetes.msg.deleteFailed2')); }
}; };
const handleSetDefault = async (ds: ZabbixDS) => { const handleSetDefault = async (ds: ZabbixDS) => {
try { try {
await zabbixDatasourceApi.setDefault(ds.id); await zabbixDatasourceApi.setDefault(ds.id);
toast.success(`已将「${ds.name}」设为默认`); toast.success(t('monitor.zabbixDs.setDefaultSuccess', { name: ds.name }));
fetchList(); fetchList();
} catch { toast.error(i18n.t('删除失败')); } } catch { toast.error(t('monitor.zabbixDs.deleteFailed')); }
}; };
const handleTest = async () => { const handleTest = async () => {
if (!form.url.trim() || !form.username.trim()) { if (!form.url.trim() || !form.username.trim()) {
toast.error(i18n.t('获取数据源列表失败')); toast.error(t('monitor.zabbixDs.fetchListError'));
return; return;
} }
setTesting(true); setTesting(true);
@@ -174,7 +174,7 @@ export default function ZabbixDatasourceManager() {
const data = res?.data || res; const data = res?.data || res;
setTestResult(data); setTestResult(data);
} catch (err) { } 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 { } finally {
setTesting(false); setTesting(false);
} }
@@ -198,12 +198,12 @@ export default function ZabbixDatasourceManager() {
className="w-5 h-5 rounded" className="w-5 h-5 rounded"
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }} onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
/> />
Zabbix 数据源 {t('monitor.zabbixDs.title')}
</h2> </h2>
<p className="text-xs text-[var(--dg-text-muted)]"> {i18n.t('monitor.zabbixdatasourc.s79e929d8')} </p> <p className="text-xs text-[var(--dg-text-muted)]"> {t('monitor.zabbixDs.subtitle')} </p>
</div> </div>
<Button onClick={openCreate} size="sm"> <Button onClick={openCreate} size="sm">
<Plus className="w-4 h-4 mr-1" />添加 <Plus className="w-4 h-4 mr-1" />{t('common.add')}
</Button> </Button>
</div> </div>
@@ -211,9 +211,9 @@ export default function ZabbixDatasourceManager() {
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]"> <Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="py-12 text-center text-[var(--dg-text-muted)]"> <CardContent className="py-12 text-center text-[var(--dg-text-muted)]">
<Database className="w-12 h-12 mx-auto mb-3 opacity-30" /> <Database className="w-12 h-12 mx-auto mb-3 opacity-30" />
<p className="text-sm"> {i18n.t('monitor.zabbixdatasourc.sc9c9bd18')} </p> <p className="text-sm"> {t('monitor.zabbixDs.emptyState')} </p>
<Button size="sm" className="mt-4" onClick={openCreate}> <Button size="sm" className="mt-4" onClick={openCreate}>
<Plus className="w-4 h-4 mr-1" />添加数据源 <Plus className="w-4 h-4 mr-1" />{t('monitor.zabbixDs.addDatasource')}
</Button> </Button>
</CardContent> </CardContent>
</Card> </Card>
@@ -227,25 +227,25 @@ export default function ZabbixDatasourceManager() {
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<div className={`w-2 h-2 rounded-full ${ds.status === 1 ? 'bg-emerald-400' : 'bg-[var(--dg-bg-active)]'}`} /> <div className={`w-2 h-2 rounded-full ${ds.status === 1 ? 'bg-emerald-400' : 'bg-[var(--dg-bg-active)]'}`} />
<span className="font-medium text-[var(--dg-text-primary)]">{ds.name}</span> <span className="font-medium text-[var(--dg-text-primary)]">{ds.name}</span>
{ds.is_default && <Badge className="bg-amber-500/20 dg-text-yellow text-[10px]"><Star className="w-3 h-3 mr-0.5" /> {i18n.t('k8s.storage.default')} </Badge>} {ds.is_default && <Badge className="bg-amber-500/20 dg-text-yellow text-[10px]"><Star className="w-3 h-3 mr-0.5" /> {t('k8s.storage.default')} </Badge>}
{ds.status === 1 {ds.status === 1
? <Badge className="bg-emerald-500/20 dg-text-green text-[10px]"> {i18n.t('system.userActive')} </Badge> ? <Badge className="bg-emerald-500/20 dg-text-green text-[10px]"> {t('system.userActive')} </Badge>
: <Badge className="bg-[var(--dg-bg-active)]/20 dg-text-secondary text-[10px]"> {i18n.t('system.userInactive')} </Badge>} : <Badge className="bg-[var(--dg-bg-active)]/20 dg-text-secondary text-[10px]"> {t('system.userInactive')} </Badge>}
</div> </div>
<div className="text-xs text-[var(--dg-text-muted)] space-x-3"> <div className="text-xs text-[var(--dg-text-muted)] space-x-3">
<code className="text-orange-300">{ds.url}</code> <code className="text-orange-300">{ds.url}</code>
<span>用户: {ds.username}</span> <span>{t('monitor.zabbixDs.user')}: {ds.username}</span>
{ds.direct_db && <Badge variant="outline" className="text-[10px]"> {i18n.t('monitor.zabbixdatasourc.s85260abd')} </Badge>} {ds.direct_db && <Badge variant="outline" className="text-[10px]"> {t('monitor.zabbixDs.directDbBadge')} </Badge>}
</div> </div>
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
{!ds.is_default && ( {!ds.is_default && (
<Button variant="ghost" size="sm" className="h-7 text-xs dg-text-yellow" onClick={() => handleSetDefault(ds)}> <Button variant="ghost" size="sm" className="h-7 text-xs dg-text-yellow" onClick={() => handleSetDefault(ds)}>
<Star className="w-3 h-3 mr-0.5" />默认 <Star className="w-3 h-3 mr-0.5" />{t('k8s.storage.default')}
</Button> </Button>
)} )}
<Button variant="ghost" size="sm" className="h-7 text-xs" onClick={() => openEdit(ds)}> <Button variant="ghost" size="sm" className="h-7 text-xs" onClick={() => openEdit(ds)}>
<Edit className="w-3 h-3 mr-0.5" />编辑 <Edit className="w-3 h-3 mr-0.5" />{t('common.edit')}
</Button> </Button>
<Button variant="ghost" size="sm" className="h-7 text-xs dg-text-red" onClick={() => handleDelete(ds)}> <Button variant="ghost" size="sm" className="h-7 text-xs dg-text-red" onClick={() => handleDelete(ds)}>
<Trash2 className="w-3 h-3" /> <Trash2 className="w-3 h-3" />
@@ -264,10 +264,10 @@ export default function ZabbixDatasourceManager() {
<DialogHeader> <DialogHeader>
<DialogTitle className="text-[var(--dg-text-primary)] flex items-center gap-2"> <DialogTitle className="text-[var(--dg-text-primary)] flex items-center gap-2">
<Database className="h-5 w-5 dg-text-orange" /> <Database className="h-5 w-5 dg-text-orange" />
{editingId ? i18n.t('编辑 Zabbix 数据源') : i18n.t('新建 Zabbix 数据源')} {editingId ? t('monitor.zabbixDs.editTitle') : t('monitor.zabbixDs.createTitle')}
</DialogTitle> </DialogTitle>
<DialogDescription> <DialogDescription>
配置 Zabbix API 连接,用于主机监控与告警数据获取 {t('monitor.zabbixDs.dialogDesc')}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
@@ -275,14 +275,14 @@ export default function ZabbixDatasourceManager() {
{/* Name + Default */} {/* Name + Default */}
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div> <div>
<Label className="text-xs text-[var(--dg-text-secondary)]"> {i18n.t('deploy.form.nameStar')} </Label> <Label className="text-xs text-[var(--dg-text-secondary)]"> {t('deploy.form.nameStar')} </Label>
<Input className="mt-1 h-9 text-sm bg-[var(--dg-bg-primary)]" placeholder={i18n.t('如:生产 Zabbix')} <Input className="mt-1 h-9 text-sm bg-[var(--dg-bg-primary)]" placeholder={t('monitor.zabbixDs.namePlaceholder')}
value={form.name} onChange={e => updateForm('name', e.target.value)} /> value={form.name} onChange={e => updateForm('name', e.target.value)} />
</div> </div>
<div className="flex items-end pb-1"> <div className="flex items-end pb-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Switch checked={form.is_default} onCheckedChange={v => updateForm('is_default', v)} /> <Switch checked={form.is_default} onCheckedChange={v => updateForm('is_default', v)} />
<Label className="text-xs cursor-pointer"> {i18n.t('aiops.llmSetDefault')} </Label> <Label className="text-xs cursor-pointer"> {t('aiops.llmSetDefault')} </Label>
</div> </div>
</div> </div>
</div> </div>
@@ -306,15 +306,15 @@ export default function ZabbixDatasourceManager() {
onClick={() => setShowAuth(!showAuth)}> onClick={() => setShowAuth(!showAuth)}>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Shield className="w-4 h-4" /> <Shield className="w-4 h-4" />
<span>Zabbix API 认证</span> <span>{t('monitor.zabbixDs.apiAuth')}</span>
</div> </div>
{showAuth ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />} {showAuth ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
</button> </button>
{showAuth && ( {showAuth && (
<div className="px-3 pb-3 space-y-3"> <div className="px-3 pb-3 space-y-3">
<Input className="h-9 text-sm bg-[var(--dg-bg-primary)]" placeholder={i18n.t('system.userNameRequired')} <Input className="h-9 text-sm bg-[var(--dg-bg-primary)]" placeholder={t('system.userNameRequired')}
value={form.username} onChange={e => updateForm('username', e.target.value)} /> value={form.username} onChange={e => updateForm('username', e.target.value)} />
<Input className="h-9 text-sm bg-[var(--dg-bg-primary)]" type="password" placeholder={i18n.t('system.passwordRequired')} <Input className="h-9 text-sm bg-[var(--dg-bg-primary)]" type="password" placeholder={t('system.passwordRequired')}
value={form.password} onChange={e => updateForm('password', e.target.value)} /> value={form.password} onChange={e => updateForm('password', e.target.value)} />
</div> </div>
)} )}
@@ -326,7 +326,7 @@ export default function ZabbixDatasourceManager() {
onClick={() => setShowTrends(!showTrends)}> onClick={() => setShowTrends(!showTrends)}>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Clock className="w-4 h-4" /> <Clock className="w-4 h-4" />
<span>Trends 设置</span> <span>{t('monitor.zabbixDs.trendsSettings')}</span>
</div> </div>
{showTrends ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />} {showTrends ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
</button> </button>
@@ -334,24 +334,24 @@ export default function ZabbixDatasourceManager() {
<div className="px-3 pb-3 space-y-3"> <div className="px-3 pb-3 space-y-3">
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div> <div>
<Label className="text-xs text-[var(--dg-text-secondary)]">After (天)</Label> <Label className="text-xs text-[var(--dg-text-secondary)]">After ({t('common.days')})</Label>
<Input className="mt-1 h-9 text-sm bg-[var(--dg-bg-primary)]" type="number" min={0} <Input className="mt-1 h-9 text-sm bg-[var(--dg-bg-primary)]" type="number" min={0}
value={form.trends_after} onChange={e => updateForm('trends_after', parseInt(e.target.value) || 0)} /> value={form.trends_after} onChange={e => updateForm('trends_after', parseInt(e.target.value) || 0)} />
</div> </div>
<div> <div>
<Label className="text-xs text-[var(--dg-text-secondary)]">Range (秒)</Label> <Label className="text-xs text-[var(--dg-text-secondary)]">Range ({t('common.seconds')})</Label>
<Input className="mt-1 h-9 text-sm bg-[var(--dg-bg-primary)]" type="number" min={0} <Input className="mt-1 h-9 text-sm bg-[var(--dg-bg-primary)]" type="number" min={0}
value={form.trends_range} onChange={e => updateForm('trends_range', parseInt(e.target.value) || 0)} /> value={form.trends_range} onChange={e => updateForm('trends_range', parseInt(e.target.value) || 0)} />
</div> </div>
</div> </div>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div> <div>
<Label className="text-xs text-[var(--dg-text-secondary)]">Cache TTL (秒)</Label> <Label className="text-xs text-[var(--dg-text-secondary)]">Cache TTL ({t('common.seconds')})</Label>
<Input className="mt-1 h-9 text-sm bg-[var(--dg-bg-primary)]" type="number" min={0} <Input className="mt-1 h-9 text-sm bg-[var(--dg-bg-primary)]" type="number" min={0}
value={form.trends_cache_ttl} onChange={e => updateForm('trends_cache_ttl', parseInt(e.target.value) || 3600)} /> value={form.trends_cache_ttl} onChange={e => updateForm('trends_cache_ttl', parseInt(e.target.value) || 3600)} />
</div> </div>
<div> <div>
<Label className="text-xs text-[var(--dg-text-secondary)]">Timeout (秒)</Label> <Label className="text-xs text-[var(--dg-text-secondary)]">Timeout ({t('common.seconds')})</Label>
<Input className="mt-1 h-9 text-sm bg-[var(--dg-bg-primary)]" type="number" min={1} <Input className="mt-1 h-9 text-sm bg-[var(--dg-bg-primary)]" type="number" min={1}
value={form.trends_timeout} onChange={e => updateForm('trends_timeout', parseInt(e.target.value) || 30)} /> value={form.trends_timeout} onChange={e => updateForm('trends_timeout', parseInt(e.target.value) || 30)} />
</div> </div>
@@ -366,7 +366,7 @@ export default function ZabbixDatasourceManager() {
onClick={() => setShowOther(!showOther)}> onClick={() => setShowOther(!showOther)}>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Settings className="w-4 h-4" /> <Settings className="w-4 h-4" />
<span> {i18n.t('编辑 Zabbix 数据源')} </span> <span> {t('monitor.zabbixDs.otherSettings')} </span>
</div> </div>
{showOther ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />} {showOther ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
</button> </button>
@@ -377,11 +377,11 @@ export default function ZabbixDatasourceManager() {
<Switch checked={form.direct_db} onCheckedChange={v => updateForm('direct_db', v)} /> <Switch checked={form.direct_db} onCheckedChange={v => updateForm('direct_db', v)} />
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Label className="text-xs"> {i18n.t('monitor.zabbixdatasourc.s413ec771')} </Label> <Label className="text-xs"> {t('monitor.zabbixDs.disableAck')} </Label>
<Switch checked={form.disable_acknowledges} onCheckedChange={v => updateForm('disable_acknowledges', v)} /> <Switch checked={form.disable_acknowledges} onCheckedChange={v => updateForm('disable_acknowledges', v)} />
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Label className="text-xs"> {i18n.t('monitor.zabbixdatasourc.s6d4d6f81')} </Label> <Label className="text-xs"> {t('monitor.zabbixDs.disableDataAlignment')} </Label>
<Switch checked={form.disable_data_alignment} onCheckedChange={v => updateForm('disable_data_alignment', v)} /> <Switch checked={form.disable_data_alignment} onCheckedChange={v => updateForm('disable_data_alignment', v)} />
</div> </div>
</div> </div>
@@ -402,11 +402,11 @@ export default function ZabbixDatasourceManager() {
<DialogFooter className="gap-2"> <DialogFooter className="gap-2">
<Button variant="outline" size="sm" onClick={handleTest} disabled={testing}> <Button variant="outline" size="sm" onClick={handleTest} disabled={testing}>
<Plug className={`w-4 h-4 mr-1 ${testing ? 'animate-pulse' : ''}`} /> <Plug className={`w-4 h-4 mr-1 ${testing ? 'animate-pulse' : ''}`} />
测试连接 {t('monitor.zabbixDs.testConnection')}
</Button> </Button>
<div className="flex-1" /> <div className="flex-1" />
<Button variant="ghost" size="sm" onClick={() => setDialogOpen(false)}> {i18n.t('rbac.jobRole.cancel')} </Button> <Button variant="ghost" size="sm" onClick={() => setDialogOpen(false)}> {t('common.cancel')} </Button>
<Button size="sm" onClick={handleSave}> {i18n.t('task.script.save')} </Button> <Button size="sm" onClick={handleSave}> {t('common.save')} </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
+38 -1
View File
@@ -511,7 +511,9 @@
"created": "Created", "created": "Created",
"severity": "Severity", "severity": "Severity",
"critical": "Critical", "critical": "Critical",
"request.tokenExpired": "Session expired, please log in again" "request.tokenExpired": "Session expired, please log in again",
"days": "days",
"seconds": "sec"
}, },
"menu": { "menu": {
"dashboard": "Dashboard", "dashboard": "Dashboard",
@@ -4393,6 +4395,41 @@
"wechat": "WeChat Work", "wechat": "WeChat Work",
"email": "Email", "email": "Email",
"sms": "SMS" "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": { "task": {
+38 -1
View File
@@ -511,7 +511,9 @@
"created": "已创建", "created": "已创建",
"severity": "级别", "severity": "级别",
"critical": "严重", "critical": "严重",
"request.tokenExpired": "登录已过期,请重新登录" "request.tokenExpired": "登录已过期,请重新登录",
"days": "天",
"seconds": "秒"
}, },
"menu": { "menu": {
"dashboard": "仪表盘", "dashboard": "仪表盘",
@@ -4393,6 +4395,41 @@
"wechat": "企业微信", "wechat": "企业微信",
"email": "邮件", "email": "邮件",
"sms": "短信" "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": { "task": {