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:
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>
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
Reference in new issue
Block a user