fix(i18n): 回退到稳定的 32% 覆盖率状态 (tsc + Vite 构建通过)
批量转换脚本引入的语法错误已回退,保留稳定的 i18n 转换: - 翻译词典: 22 namespaces, 3600+ keys - 已接入: 145+ 文件 (Dashboard/Login/K8s 主页面/Monitor/System/Deploy) - 语言切换器在 Header 中可用 (🇨🇳/🇺🇸) - tsc 零错误, Vite 构建成功
This commit is contained in:
1 parent
ecec73989b
commit
72bfb0e12d
191 files changed
+5057
-7592
No files matched your search
@@ -5,7 +5,6 @@ import { TrendingUp, Loader2, AlertCircle } from 'lucide-react';
|
||||
import { LineChart, Line, ResponsiveContainer, XAxis, YAxis, Tooltip, CartesianGrid } from 'recharts';
|
||||
import type { Panel } from './GrafanaDashboard';
|
||||
import { prometheusProxyApi, promDatasourceApi } from '@/api';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
// 默认 Prometheus URL 缓存
|
||||
let _defaultPromURL: string | null = null;
|
||||
@@ -71,7 +70,6 @@ function parseInstantResult(promResult: any): { labels: Record<string, string>;
|
||||
|
||||
/** 渲染面板内容 */
|
||||
export function GrafanaPanelContent({ panel, datasourceUrl }: { panel: Panel; datasourceUrl?: string }) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [chartData, setChartData] = useState<{ time: string; value: number }[]>([]);
|
||||
@@ -84,7 +82,7 @@ export function GrafanaPanelContent({ panel, datasourceUrl }: { panel: Panel; da
|
||||
try {
|
||||
const url = await getDefaultPromURL(datasourceUrl);
|
||||
if (!url) {
|
||||
setError(t('monitor.dashboard.prometheus.emptyTitle'));
|
||||
setError('未配置 Prometheus 数据源');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
@@ -102,16 +100,16 @@ export function GrafanaPanelContent({ panel, datasourceUrl }: { panel: Panel; da
|
||||
});
|
||||
const parsed = parseRangeResult(res);
|
||||
setChartData(parsed.length > 0 ? parsed : []);
|
||||
if (parsed.length === 0) setError(t('kubernetes.configmap.noData'));
|
||||
if (parsed.length === 0) setError('无数据');
|
||||
} else {
|
||||
// stat / gauge / table 用即时查询
|
||||
const res = await prometheusProxyApi.query(url, panel.query);
|
||||
const parsed = parseInstantResult(res);
|
||||
setInstantData(parsed);
|
||||
if (parsed.length === 0) setError(t('kubernetes.configmap.noData'));
|
||||
if (parsed.length === 0) setError('无数据');
|
||||
}
|
||||
} catch (e: any) {
|
||||
setError(e?.message ? String(e.message).slice(0, 80) : t('monitor.grafanapanelcontent.s0d66ed'));
|
||||
setError(e?.message ? String(e.message).slice(0, 80) : '查询失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -155,7 +153,7 @@ export function GrafanaPanelContent({ panel, datasourceUrl }: { panel: Panel; da
|
||||
{unit && <span className="text-sm text-[var(--dg-text-muted)] mt-2">{unit}</span>}
|
||||
<div className="flex items-center gap-1 mt-2 dg-text-green text-sm">
|
||||
<TrendingUp className="w-4 h-4" />
|
||||
<span>{t('common.realtime')}</span>
|
||||
<span>实时</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -189,7 +187,7 @@ export function GrafanaPanelContent({ panel, datasourceUrl }: { panel: Panel; da
|
||||
);
|
||||
}
|
||||
case 'table': {
|
||||
if (instantData.length === 0) return <div className="text-center py-4 text-xs text-[var(--dg-text-muted)]">{t('kubernetes.configmap.noData')}</div>;
|
||||
if (instantData.length === 0) return <div className="text-center py-4 text-xs text-[var(--dg-text-muted)]">无数据</div>;
|
||||
return (
|
||||
<div className="overflow-auto max-h-[200px]">
|
||||
<table className="w-full text-[11px]">
|
||||
@@ -218,7 +216,7 @@ export function GrafanaPanelContent({ panel, datasourceUrl }: { panel: Panel; da
|
||||
case 'graph':
|
||||
default: {
|
||||
if (chartData.length === 0) {
|
||||
return <div className="text-center py-4 text-xs text-[var(--dg-text-muted)]">{t('kubernetes.configmap.noData')}</div>;
|
||||
return <div className="text-center py-4 text-xs text-[var(--dg-text-muted)]">无数据</div>;
|
||||
}
|
||||
return (
|
||||
<div className="h-full min-h-[120px]">
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// 参照: Grafana node-dashboard.json (表格+时序) / kubernetes-dashboard.json (bargauge+阈值)
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
|
||||
Server,
|
||||
@@ -30,7 +31,6 @@ import {
|
||||
} from '@/components/ui/select';
|
||||
import { serverApi } from '@/api';
|
||||
import type { JsonData } from '@/types/common';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type SortField = 'hostname' | 'ip' | 'agent_version' | 'agent_status' | 'cpu_usage' | 'memory_usage' | 'disk_usage' | 'region';
|
||||
type SortDir = 'asc' | 'desc';
|
||||
@@ -49,7 +49,7 @@ function getUsageBar(val: number) {
|
||||
}
|
||||
|
||||
export default function HostMonitorDashboard() {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useTranslation(['monitor', 'common']);
|
||||
const navigate = useNavigate();
|
||||
const [hosts, setHosts] = useState<JsonData[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
@@ -143,9 +143,9 @@ export default function HostMonitorDashboard() {
|
||||
|
||||
// 状态标签颜色
|
||||
const statusBadge = (status: string) => {
|
||||
if (status === 'online') return <Badge className="bg-emerald-500/20 dg-text-green border-emerald-500/30 text-[11px]">{t('common.online')}</Badge>;
|
||||
if (status === 'running') return <Badge className="bg-blue-500/20 dg-text-blue border-blue-500/30 text-[11px]">{t('common.running')}</Badge>;
|
||||
if (status === 'stopped') return <Badge className="bg-[var(--dg-bg-active)]/20 dg-text-secondary border-[var(--dg-border)] text-[11px]">{t('common.stopped')}</Badge>;
|
||||
if (status === 'online') return <Badge className="bg-emerald-500/20 dg-text-green border-emerald-500/30 text-[11px]">{t('common:online')}</Badge>;
|
||||
if (status === 'running') return <Badge className="bg-blue-500/20 dg-text-blue border-blue-500/30 text-[11px]">{t('common:running')}</Badge>;
|
||||
if (status === 'stopped') return <Badge className="bg-[var(--dg-bg-active)]/20 dg-text-secondary border-[var(--dg-border)] text-[11px]">{t('common:stopped')}</Badge>;
|
||||
return <Badge variant="outline" className="text-[11px] dg-text-muted">{status || '--'}</Badge>;
|
||||
};
|
||||
|
||||
@@ -158,7 +158,7 @@ export default function HostMonitorDashboard() {
|
||||
<div className="p-1.5 rounded-lg bg-blue-500/10"><Server className="h-4 w-4 dg-text-blue" /></div>
|
||||
<div>
|
||||
<div className="text-xl font-bold text-[var(--dg-text-primary)]">{stats.total}</div>
|
||||
<div className="text-[10px] text-[var(--dg-text-muted)]">{t('monitor.hostmonitordashboard.sd94cd2')}</div>
|
||||
<div className="text-[10px] text-[var(--dg-text-muted)]">{t('monitor:hostDashboard.hostTotal')}</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -167,7 +167,7 @@ export default function HostMonitorDashboard() {
|
||||
<div className="p-1.5 rounded-lg bg-emerald-500/10"><Wifi className="h-4 w-4 dg-text-green" /></div>
|
||||
<div>
|
||||
<div className="text-xl font-bold dg-text-green">{stats.online}</div>
|
||||
<div className="text-[10px] text-[var(--dg-text-muted)]">{t('monitor.hostmonitordashboard.s42860c')}</div>
|
||||
<div className="text-[10px] text-[var(--dg-text-muted)]">{t('monitor:hostDashboard.agentOnline')}</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -176,7 +176,7 @@ export default function HostMonitorDashboard() {
|
||||
<div className="p-1.5 rounded-lg bg-[var(--dg-bg-active)]/10"><WifiOff className="h-4 w-4 dg-text-secondary" /></div>
|
||||
<div>
|
||||
<div className="text-xl font-bold dg-text-secondary">{stats.offline}</div>
|
||||
<div className="text-[10px] text-[var(--dg-text-muted)]">{t('common.offline')}</div>
|
||||
<div className="text-[10px] text-[var(--dg-text-muted)]">{t('common:offline')}</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -192,7 +192,7 @@ export default function HostMonitorDashboard() {
|
||||
<div className="p-1.5 rounded-lg bg-red-500/10"><Cpu className="h-4 w-4 dg-text-red" /></div>
|
||||
<div>
|
||||
<div className={`text-xl font-bold ${cpuColor}`}>{avgCpu.toFixed(1)}<span className="text-sm">%</span></div>
|
||||
<div className="text-[10px] text-[var(--dg-text-muted)]">{t('monitor.hostmonitordashboard.s16dd85')}</div>
|
||||
<div className="text-[10px] text-[var(--dg-text-muted)]">{t('monitor:hostDashboard.avgCpu')}</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -201,7 +201,7 @@ export default function HostMonitorDashboard() {
|
||||
<div className="p-1.5 rounded-lg bg-amber-500/10"><Server className="h-4 w-4 dg-text-yellow" /></div>
|
||||
<div>
|
||||
<div className={`text-xl font-bold ${memColor}`}>{avgMem.toFixed(1)}<span className="text-sm">%</span></div>
|
||||
<div className="text-[10px] text-[var(--dg-text-muted)]">{t('monitor.hostmonitordashboard.sa09099')}</div>
|
||||
<div className="text-[10px] text-[var(--dg-text-muted)]">{t('monitor:hostDashboard.avgMemory')}</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -217,8 +217,8 @@ export default function HostMonitorDashboard() {
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm flex items-center gap-2 text-[var(--dg-text-primary)]">
|
||||
<Cpu className="h-4 w-4 dg-text-red" />
|
||||
CPU 使用率 Top 5
|
||||
<Badge variant="outline" className="text-[10px] bg-red-500/10 dg-text-red border-red-500/30">{t('monitor.hostmonitordashboard.sec309b')}</Badge>
|
||||
{t('monitor:hostDashboard.cpuTop5')}
|
||||
<Badge variant="outline" className="text-[10px] bg-red-500/10 dg-text-red border-red-500/30">{t('monitor:hostDashboard.threshold')}</Badge>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
@@ -258,8 +258,8 @@ export default function HostMonitorDashboard() {
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm flex items-center gap-2 text-[var(--dg-text-primary)]">
|
||||
<Server className="h-4 w-4 dg-text-yellow" />
|
||||
内存使用率 Top 5
|
||||
<Badge variant="outline" className="text-[10px] bg-amber-500/10 dg-text-yellow border-amber-500/30">{t('monitor.hostmonitordashboard.sec309b')}</Badge>
|
||||
{t('monitor:hostDashboard.memTop5')}
|
||||
<Badge variant="outline" className="text-[10px] bg-amber-500/10 dg-text-yellow border-amber-500/30">{t('monitor:hostDashboard.threshold')}</Badge>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
@@ -302,15 +302,15 @@ export default function HostMonitorDashboard() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Activity className="h-5 w-5 dg-text-green" />
|
||||
<CardTitle className="text-base text-[var(--dg-text-primary)]">{t('monitor.hostmonitordashboard.s6aa711')}</CardTitle>
|
||||
<Badge variant="outline" className="text-xs bg-emerald-500/10 dg-text-green border-emerald-500/30">{t('common.realtime')}</Badge>
|
||||
<CardTitle className="text-base text-[var(--dg-text-primary)]">{t('monitor:hostDashboard.listTitle')}</CardTitle>
|
||||
<Badge variant="outline" className="text-xs bg-emerald-500/10 dg-text-green border-emerald-500/30">{t('monitor:hostDashboard.realtime')}</Badge>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 dg-text-muted" />
|
||||
<Input
|
||||
className="w-[200px] pl-8 h-8 text-sm bg-[var(--dg-bg-primary)]"
|
||||
placeholder=t('monitor.hostmonitordashboard.s3d0fc0')
|
||||
placeholder={t('monitor:hostDashboard.searchPlaceholder')}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
||||
@@ -318,7 +318,7 @@ export default function HostMonitorDashboard() {
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={() => loadData(true)} disabled={refreshing}>
|
||||
<RefreshCw className={`h-4 w-4 mr-1 ${refreshing ? 'animate-spin' : ''}`} />
|
||||
刷新
|
||||
{t('common:refresh')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -327,13 +327,13 @@ export default function HostMonitorDashboard() {
|
||||
{loading ? (
|
||||
<div className="py-16 text-center text-[var(--dg-text-muted)]">
|
||||
<Activity className="h-8 w-8 mx-auto mb-2 animate-spin dg-text-green" />
|
||||
<p className="text-sm">{t('common.loading')}</p>
|
||||
<p className="text-sm">{t('common:loading')}</p>
|
||||
</div>
|
||||
) : sorted.length === 0 ? (
|
||||
<div className="py-16 text-center text-[var(--dg-text-muted)]">
|
||||
<Server className="h-10 w-10 mx-auto mb-2 dg-text-muted" />
|
||||
<p className="text-sm">{t('monitor.hostmonitordashboard.s2678f4')}</p>
|
||||
<p className="text-xs mt-1">{t('monitor.hostmonitordashboard.s350255')}</p>
|
||||
<p className="text-sm">{t('monitor:hostDashboard.noData')}</p>
|
||||
<p className="text-xs mt-1">{t('monitor:hostDashboard.noDataDesc')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
@@ -342,14 +342,14 @@ export default function HostMonitorDashboard() {
|
||||
<thead>
|
||||
<tr className="border-b border-[var(--dg-border)]">
|
||||
{[
|
||||
{ key: 'hostname', label: t('monitor.hostmonitordashboard.s7ba2a1'), w: '' },
|
||||
{ key: 'agent_version', label: t('monitor.systemInfo.agentVersion'), w: 'w-[100px]' },
|
||||
{ key: 'agent_status', label: t('monitor.hostmonitordashboard.secfbd1'), w: 'w-[90px]' },
|
||||
{ key: 'hostname', label: t('monitor:hostDashboard.colHostId'), w: '' },
|
||||
{ key: 'agent_version', label: t('monitor:hostDashboard.colAgentVersion'), w: 'w-[100px]' },
|
||||
{ key: 'agent_status', label: t('monitor:hostDashboard.colAgentStatus'), w: 'w-[90px]' },
|
||||
{ key: 'cpu_usage', label: 'CPU', w: 'w-[130px]' },
|
||||
{ key: 'memory_usage', label: t('k8s.pods.memory'), w: 'w-[130px]' },
|
||||
{ key: 'disk_usage', label: t('dashboard.topUsage.disk'), w: 'w-[130px]' },
|
||||
{ key: 'region', label: t('monitor.hostmonitordashboard.sc09adb'), w: 'w-[120px]' },
|
||||
{ key: 'ip', label: t('agent.agentIp'), w: 'w-[140px]' },
|
||||
{ key: 'memory_usage', label: t('monitor:hostMetric.memory'), w: 'w-[130px]' },
|
||||
{ key: 'disk_usage', label: t('monitor:hostMetric.disk'), w: 'w-[130px]' },
|
||||
{ key: 'region', label: t('monitor:hostDashboard.colRegion'), w: 'w-[120px]' },
|
||||
{ key: 'ip', label: t('monitor:hostDashboard.colIp'), w: 'w-[140px]' },
|
||||
].map(col => (
|
||||
<th
|
||||
key={col.key}
|
||||
@@ -362,7 +362,7 @@ export default function HostMonitorDashboard() {
|
||||
</div>
|
||||
</th>
|
||||
))}
|
||||
<th className="text-left px-3 py-2.5 text-xs font-medium text-[var(--dg-text-muted)]">{t('common.actions')}</th>
|
||||
<th className="text-left px-3 py-2.5 text-xs font-medium text-[var(--dg-text-muted)]">{t('common:actions')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -425,20 +425,20 @@ export default function HostMonitorDashboard() {
|
||||
size="sm"
|
||||
className="h-7 text-xs dg-text-blue hover:text-blue-300 hover:bg-blue-500/10"
|
||||
onClick={() => navigate(`/monitor-center?host=${encodeURIComponent(h.hostname)}`)}
|
||||
title=t('monitor.hostmonitordashboard.s2ee528')
|
||||
title={t('monitor:hostDashboard.viewMonitorChart')}
|
||||
>
|
||||
<Monitor className="h-3 w-3 mr-1" />
|
||||
监控
|
||||
{t('monitor:hostDashboard.monitor')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 text-xs dg-text-yellow hover:text-amber-300 hover:bg-amber-500/10"
|
||||
onClick={() => navigate(`/alerts?hostname=${encodeURIComponent(h.hostname)}`)}
|
||||
title=t('monitor.hostmonitordashboard.s356aa8')
|
||||
title={t('monitor:hostDashboard.viewAlertRules')}
|
||||
>
|
||||
<Bell className="h-3 w-3 mr-1" />
|
||||
告警
|
||||
{t('monitor:hostDashboard.alert')}
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
@@ -451,7 +451,7 @@ export default function HostMonitorDashboard() {
|
||||
{/* 分页 */}
|
||||
<div className="flex items-center justify-between px-4 py-3 border-t border-[var(--dg-border)]">
|
||||
<div className="flex items-center gap-2 text-xs text-[var(--dg-text-muted)]">
|
||||
<span>{t('common.perPage')}</span>
|
||||
<span>{t('monitor:hostDashboard.perPage')}</span>
|
||||
<Select value={String(pageSize)} onValueChange={(v) => { setPageSize(Number(v)); setPage(1); }}>
|
||||
<SelectTrigger className="h-7 w-[60px] text-xs">
|
||||
<SelectValue />
|
||||
@@ -462,7 +462,7 @@ export default function HostMonitorDashboard() {
|
||||
<SelectItem value="30">30</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<span>条,共 {total} 条</span>
|
||||
<span>{t('monitor:hostDashboard.totalCount', { count: total })}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
|
||||
@@ -8,12 +8,10 @@ import { StatStrip } from '@/components/ui/stat-strip';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { prometheusProxyApi } from '@/api';
|
||||
import type { JsonData } from '@/types/common';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface Props { dsUrl?: string; }
|
||||
|
||||
const MiniBar = ({ val, label, color }: { val: number; label: string; color?: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const pct = Math.min(val, 100);
|
||||
return (
|
||||
<div className="flex items-center gap-1.5 min-w-[80px]">
|
||||
@@ -48,7 +46,6 @@ function parsePrune( result: JsonData[]): JsonData[] {
|
||||
}
|
||||
|
||||
const Box = (p: JsonData) => <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg>;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const K8sPanel: React.FC<{ d: JsonData}> = ({ d }) => (
|
||||
<div className="space-y-4">
|
||||
@@ -61,7 +58,7 @@ const K8sPanel: React.FC<{ d: JsonData}> = ({ d }) => (
|
||||
]}
|
||||
/>
|
||||
<div className="rounded-lg bg-[var(--dg-bg-primary)] border border-[var(--dg-border)] p-3 max-h-60 overflow-y-auto">
|
||||
<div className="text-xs font-medium mb-2 flex items-center gap-1"><Cpu className="w-3 h-3 dg-text-purple" />{i18n.t('monitor.prometheusdashboard.se7fb2b')}</div>
|
||||
<div className="text-xs font-medium mb-2 flex items-center gap-1"><Cpu className="w-3 h-3 dg-text-purple" />节点 CPU / 内存利用率</div>
|
||||
{(d.nodeCPU || []).map((n: JsonData, i: number) => {
|
||||
const mem = (d.nodeMem || []).find((m: JsonData) => m.node === n.node);
|
||||
return <div key={i} className="flex items-center gap-3 text-[11px] mb-1.5"><span className="w-28 truncate" title={n.node}>{n.node}</span><MiniBar val={Number(n.value||0)} label={`${Number(n.value||0).toFixed(1)}%`} /><MiniBar val={Number(mem?.value||0)} label={`${Number(mem?.value||0).toFixed(1)}%`} color="bg-blue-500" /></div>;
|
||||
@@ -73,7 +70,7 @@ const K8sPanel: React.FC<{ d: JsonData}> = ({ d }) => (
|
||||
const NodePanel: React.FC<{ d: JsonData}> = ({ d }) => (
|
||||
<div className="overflow-x-auto rounded-lg border border-[var(--dg-border)]">
|
||||
<table className="w-full text-xs">
|
||||
<thead className="bg-[var(--dg-bg-tertiary)]"><tr><th className="px-3 py-2 text-left">{i18n.t('k8s.tab.nodes')}</th><th className="px-3 py-2 text-left">{i18n.t('common.status')}</th><th className="px-3 py-2 text-left">CPU</th><th className="px-3 py-2 text-left">{i18n.t('k8s.pods.memory')}</th><th className="px-3 py-2 text-left">{i18n.t('dashboard.topUsage.disk')}</th><th className="px-3 py-2 text-left">{i18n.t('monitor.prometheusdashboard.s361ffa')}</th><th className="px-3 py-2 text-left">{i18n.t('monitor.prometheusdashboard.s37cc0a')}</th></tr></thead>
|
||||
<thead className="bg-[var(--dg-bg-tertiary)]"><tr><th className="px-3 py-2 text-left">节点</th><th className="px-3 py-2 text-left">状态</th><th className="px-3 py-2 text-left">CPU</th><th className="px-3 py-2 text-left">内存</th><th className="px-3 py-2 text-left">磁盘</th><th className="px-3 py-2 text-left">流入</th><th className="px-3 py-2 text-left">流出</th></tr></thead>
|
||||
<tbody>
|
||||
{(d.nodes || []).map((n: JsonData, i: number) => {
|
||||
const inst = n.instance || n.exported_instance || '';
|
||||
@@ -107,12 +104,11 @@ const PodPanel: React.FC<{ d: JsonData}> = ({ d }) => (
|
||||
]}
|
||||
/>
|
||||
<div className="rounded-lg bg-[var(--dg-bg-primary)] p-3 max-h-40 overflow-y-auto"><div className="text-xs font-medium mb-2">CPU Top 10</div>{(d.cpuTop||[]).map((p: JsonData, i:number)=><div key={i} className="flex items-center gap-2 text-[11px] mb-1"><span className="w-10 text-right text-[var(--dg-text-muted)]">{i+1}</span><span className="w-32 truncate" title={p.pod}>{p.pod}</span><MiniBar val={Number(p.value||0)*100} label={`${Number(p.value||0).toFixed(2)}c`} color="bg-purple-500" /></div>)}</div>
|
||||
<div className="rounded-lg bg-[var(--dg-bg-primary)] p-3 max-h-40 overflow-y-auto"><div className="text-xs font-medium mb-2">{i18n.t('monitor.prometheusdashboard.sb0f653')}</div>{(d.memTop||[]).map((p: JsonData, i:number)=><div key={i} className="flex items-center gap-2 text-[11px] mb-1"><span className="w-10 text-right text-[var(--dg-text-muted)]">{i+1}</span><span className="w-32 truncate" title={p.pod}>{p.pod}</span><span className="ml-auto">{fmtBytes(Number(p.value||0))}</span></div>)}</div>
|
||||
<div className="rounded-lg bg-[var(--dg-bg-primary)] p-3 max-h-40 overflow-y-auto"><div className="text-xs font-medium mb-2">内存 Top 10</div>{(d.memTop||[]).map((p: JsonData, i:number)=><div key={i} className="flex items-center gap-2 text-[11px] mb-1"><span className="w-10 text-right text-[var(--dg-text-muted)]">{i+1}</span><span className="w-32 truncate" title={p.pod}>{p.pod}</span><span className="ml-auto">{fmtBytes(Number(p.value||0))}</span></div>)}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const JvmPanel: React.FC<{ d: JsonData}> = ({ d }) => {
|
||||
const { t } = useTranslation();
|
||||
const h = Number(d.heapUsed?.[0]?.value||0);
|
||||
const uptime = Number(d.uptime?.[0]?.value||0);
|
||||
return (
|
||||
@@ -127,7 +123,6 @@ const JvmPanel: React.FC<{ d: JsonData}> = ({ d }) => {
|
||||
};
|
||||
|
||||
export const PrometheusDashboard: React.FC<Props> = ({ dsUrl }) => {
|
||||
const { t } = useTranslation();
|
||||
const [connected, setConnected] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [targets, setTargets] = useState(0);
|
||||
@@ -148,7 +143,7 @@ export const PrometheusDashboard: React.FC<Props> = ({ dsUrl }) => {
|
||||
const data: JsonData = await prometheusProxyApi.connect(u);
|
||||
setConnected(true); setTargets(data.targets||0);
|
||||
loadAll(u);
|
||||
} catch (e) { setConnected(false); setError((e as JsonData).message||t('aiops.llmTestFailed')); }
|
||||
} catch (e) { setConnected(false); setError((e as JsonData).message||'连接失败'); }
|
||||
finally { setLoading(false); }
|
||||
};
|
||||
|
||||
@@ -174,13 +169,13 @@ export const PrometheusDashboard: React.FC<Props> = ({ dsUrl }) => {
|
||||
<CardHeader className="py-3 px-4 pb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Activity className="h-4 w-4 dg-text-purple" />
|
||||
<CardTitle className="text-sm font-medium">{t('monitor.prometheusdashboard.s006a5f')}</CardTitle>
|
||||
<CardTitle className="text-sm font-medium">Prometheus 监控</CardTitle>
|
||||
{connected && <Badge className="text-[10px] bg-purple-500/10 dg-text-purple">{targets} targets</Badge>}
|
||||
{connected && <div className="ml-auto"><Button variant="ghost" size="sm" className="h-6 px-2 text-[10px]" onClick={refresh}><RefreshCw className="w-3 h-3 mr-1" />{t('common.refresh')}</Button></div>}
|
||||
{connected && <div className="ml-auto"><Button variant="ghost" size="sm" className="h-6 px-2 text-[10px]" onClick={refresh}><RefreshCw className="w-3 h-3 mr-1" />刷新</Button></div>}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<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-purple" /><p className="text-xs text-[var(--dg-text-muted)]">{t('monitor.prometheusdashboard.s5e8eb2')}</p></div>)
|
||||
{loading ? (<div className="text-center py-12"><Loader2 className="h-6 w-6 mx-auto mb-2 animate-spin dg-text-purple" /><p className="text-xs text-[var(--dg-text-muted)]">连接中…</p></div>)
|
||||
: error ? (<div className="p-3 rounded-lg bg-red-500/10 border border-red-500/20 text-xs dg-text-red"><AlertTriangle className="h-4 w-4 inline mr-1" />{error}</div>)
|
||||
: (
|
||||
<Tabs value={tab} onValueChange={setTab}>
|
||||
|
||||
@@ -18,7 +18,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 { useTranslation } from 'react-i18next';
|
||||
|
||||
interface ZabbixHost {
|
||||
hostid: string; hostname: string; name: string; ip: string; available: string;
|
||||
@@ -32,7 +31,6 @@ type SortField = 'hostname' | 'cpu_usage' | 'memory_usage' | 'disk_usage' | 'loa
|
||||
interface Props { dsUrl?: string; dsUsername?: string; dsPassword?: string; }
|
||||
|
||||
export const ZabbixDashboard: React.FC<Props> = ({ dsUrl, dsUsername, dsPassword }) => {
|
||||
const { t } = useTranslation();
|
||||
const [connected, setConnected] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [hosts, setHosts] = useState<ZabbixHost[]>([]);
|
||||
@@ -52,7 +50,7 @@ export const ZabbixDashboard: React.FC<Props> = ({ dsUrl, dsUsername, dsPassword
|
||||
setHosts(data.hosts || []);
|
||||
setVersion(data.version || '');
|
||||
setConnected(true);
|
||||
} catch (e) { setConnected(false); setError((e as JsonData).message || t('aiops.llmTestFailed')); }
|
||||
} catch (e) { setConnected(false); setError((e as JsonData).message || '连接失败'); }
|
||||
finally { setLoading(false); }
|
||||
};
|
||||
|
||||
@@ -96,24 +94,24 @@ export const ZabbixDashboard: React.FC<Props> = ({ dsUrl, dsUsername, dsPassword
|
||||
<CardHeader className="py-3 px-4 pb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Activity className="h-4 w-4 dg-text-blue" />
|
||||
<CardTitle className="text-sm font-medium">{t('monitor.zabbixdashboard.s6f2695')}</CardTitle>
|
||||
<CardTitle className="text-sm font-medium">Zabbix 主机监控</CardTitle>
|
||||
{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 && <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('common.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" />刷新</Button></div>}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<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)]">{t('monitor.zabbixdashboard.s5e8eb2')}</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)]">连接中…</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>)
|
||||
: 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.zabbixdashboard.sdb131a')}</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">未找到被监控的 Zabbix 主机</p></div>)
|
||||
: (
|
||||
<div className="overflow-x-auto rounded-lg border border-[var(--dg-border)]">
|
||||
<table className="w-full text-xs">
|
||||
<thead className="bg-[var(--dg-bg-tertiary)] border-b border-[var(--dg-border)]"><tr>
|
||||
{[
|
||||
{ f: 'hostname' as SortField, l: t('server.hostname') },{ f: 'cpu_usage' as SortField, l: 'CPU' },{ f: 'memory_usage' as SortField, l: t('k8s.pods.memory') },
|
||||
{ f: 'disk_usage' as SortField, l: t('dashboard.topUsage.disk') },{ f: 'load5' as SortField, l: 'Load5' },{ f: 'net_in_bps' as SortField, l: t('monitor.zabbixdashboard.s361ffa') },
|
||||
{ f: 'net_out_bps' as SortField, l: t('monitor.zabbixdashboard.s37cc0a') },{ f: 'problem_count' as SortField, l: t('monitor.zabbixdashboard.s5dc99f') },{ f: 'uptime_days' as SortField, l: t('common.run') },
|
||||
{ f: 'hostname' as SortField, l: '主机名' },{ f: 'cpu_usage' as SortField, l: 'CPU' },{ f: 'memory_usage' as SortField, l: '内存' },
|
||||
{ f: 'disk_usage' as SortField, l: '磁盘' },{ f: 'load5' as SortField, l: 'Load5' },{ f: 'net_in_bps' as SortField, l: '流入' },
|
||||
{ f: 'net_out_bps' as SortField, l: '流出' },{ f: 'problem_count' as SortField, l: '问题' },{ f: 'uptime_days' as SortField, 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)}>
|
||||
<div className="flex items-center gap-1">{l}<SI f={f} /></div></th>
|
||||
|
||||
Reference in new issue
Block a user