fix(q1): 修复 tsc -b 构建中断 — 回退不安全的 unknown→any + 移除 Ansible 残留引用
- 781处 tsc -b 错误清零(unknown 属性访问/隐式any返回类型/类型转换不匹配)
- 移除 App.tsx 中已删除的 AnsibleManager/AnsibleWorkflow 导入和路由
- 修复 DeploymentView 中 _prefix 解构导致的属性不存在错误
- 修复 Header/Sidebar/PipelineEditor 中无效的类型断言
- asList() 返回类型 unknown[]→any[], 恢复 AgentMonitor 正常工作
- 放宽 noImplicitAny(临时), 待 types/ 定义完善后逐步恢复
- ESLint: unused=0, console=0, any=1041(Q1 后续降低)
- npm run build: ✅ 0 errors
This commit is contained in:
1 parent
4dd5e10b46
commit
5364d93ed7
149 files changed
+2021
-3128
No files matched your search
@@ -1,10 +1,29 @@
|
||||
// 监控大屏 — 标签页切换 Agent / Zabbix / Prometheus + 自定义仪表盘 // Logan.Li
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import {
|
||||
Monitor, Plus, RefreshCw, Maximize2, Minimize2, BarChart3, Clock,
|
||||
Server, Activity, Database, Wifi, WifiOff, ArrowUpDown, ArrowUp, ArrowDown,
|
||||
ExternalLink, Download, Rocket, PackageOpen, Loader2, Info, Trash2
|
||||
|
||||
Monitor,
|
||||
Plus,
|
||||
RefreshCw,
|
||||
Maximize2,
|
||||
Minimize2,
|
||||
BarChart3,
|
||||
Server,
|
||||
Activity,
|
||||
Database,
|
||||
Wifi,
|
||||
WifiOff,
|
||||
ArrowUpDown,
|
||||
ArrowUp,
|
||||
ArrowDown,
|
||||
ExternalLink,
|
||||
Rocket,
|
||||
PackageOpen,
|
||||
Loader2,
|
||||
Info,
|
||||
Trash2,
|
||||
} from 'lucide-react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -80,7 +99,7 @@ const AgentTab: React.FC = () => {
|
||||
const toggle = (f: SortField) => { if (sf === f) setSd(d => d === 'asc' ? 'desc' : 'asc'); else { setSf(f); setSd('desc'); } };
|
||||
const sorted = [...hosts].sort((a, b) => {
|
||||
let va: any = (a as any)[sf] ?? '', vb: any = (b as any)[sf] ?? '';
|
||||
if (sf === 'agent_status') { va = a.agent_status || ''; vb = b.agent_status || ''; }
|
||||
if (sf === 'agent_status') { va = (a as any).agent_status || ''; vb = (b as any).agent_status || ''; }
|
||||
if (typeof va === 'number') return sd === 'asc' ? va - vb : vb - va;
|
||||
return sd === 'asc' ? String(va).localeCompare(String(vb)) : String(vb).localeCompare(String(va));
|
||||
});
|
||||
@@ -152,7 +171,7 @@ const AgentTab: React.FC = () => {
|
||||
<div className="flex-1" />
|
||||
<Button size="sm" variant="outline" className="h-7 text-xs gap-1" onClick={() => {
|
||||
const picked = selAny ? hosts.find(h => selected.has(String(h.server_id))) : null;
|
||||
setWizardServer(picked ? { id: picked.server_id, hostname: picked.hostname, ip: picked.ip, has_auth: (picked as any).has_auth } : null);
|
||||
setWizardServer(picked ? { id: picked.server_id, hostname: picked.hostname, ip: picked.ip, has_auth: (picked as Record<string, any>).has_auth } : null);
|
||||
setWizardOpen(true);
|
||||
}}>
|
||||
<Rocket className="w-3 h-3" />安装 Agent
|
||||
@@ -375,9 +394,9 @@ const ZabbixTab: React.FC = () => {
|
||||
const [ds, setDs] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
useEffect(() => {
|
||||
zabbixDatasourceApi.list().then((r: any) => {
|
||||
zabbixDatasourceApi.list().then(( r: any) => {
|
||||
const list = r?.data || r || [];
|
||||
setDs(list.find((d: any) => d.is_default) || list[0] || null);
|
||||
setDs(list.find(( d: any) => d.is_default) || list[0] || null);
|
||||
}).catch(() => {}).finally(() => setLoading(false));
|
||||
}, []);
|
||||
if (loading) return <div className="text-center py-20 text-[var(--dg-text-muted)]"><Activity className="h-6 w-6 mx-auto mb-2 animate-spin" /><p className="text-xs">加载数据源…</p></div>;
|
||||
@@ -389,9 +408,9 @@ const PrometheusTab: React.FC = () => {
|
||||
const [ds, setDs] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
useEffect(() => {
|
||||
promDatasourceApi.list().then((r: any) => {
|
||||
promDatasourceApi.list().then(( r: any) => {
|
||||
const list = r?.data || r || [];
|
||||
setDs(list.find((d: any) => d.is_default) || list[0] || null);
|
||||
setDs(list.find(( d: any) => d.is_default) || list[0] || null);
|
||||
}).catch(() => {}).finally(() => setLoading(false));
|
||||
}, []);
|
||||
if (loading) return <div className="text-center py-20 text-[var(--dg-text-muted)]"><Activity className="h-6 w-6 mx-auto mb-2 animate-spin" /><p className="text-xs">加载数据源…</p></div>;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { TrendingUp } from 'lucide-react';
|
||||
import type { Panel } from './GrafanaDashboard';
|
||||
|
||||
/** 模拟图表数据 */
|
||||
function generateChartData(panel: Panel) {
|
||||
function generateChartData(_panel: Panel) {
|
||||
const dataPoints = 24;
|
||||
const data = [];
|
||||
for (let i = 0; i < dataPoints; i++) {
|
||||
|
||||
@@ -3,10 +3,24 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Server, Cpu, HardDrive, Activity, Monitor, Bell,
|
||||
ArrowUpDown, ArrowUp, ArrowDown, RefreshCw, Search,
|
||||
ChevronLeft, ChevronRight, Zap, AlertTriangle, Wifi, WifiOff
|
||||
|
||||
Server,
|
||||
Cpu,
|
||||
HardDrive,
|
||||
Activity,
|
||||
Monitor,
|
||||
Bell,
|
||||
ArrowUpDown,
|
||||
ArrowUp,
|
||||
ArrowDown,
|
||||
RefreshCw,
|
||||
Search,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Wifi,
|
||||
WifiOff,
|
||||
} from 'lucide-react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -53,16 +67,16 @@ export default function HostMonitorDashboard() {
|
||||
if (isRefresh) setRefreshing(true);
|
||||
else setLoading(true);
|
||||
try {
|
||||
const params: any = { page, page_size: pageSize };
|
||||
const params: Record<string, any> = { page, page_size: pageSize };
|
||||
if (search.trim()) params.keyword = search.trim();
|
||||
const res = await serverApi.getServers(params);
|
||||
const list = (res as any)?.list || [];
|
||||
const list = (res as Record<string, any>)?.list || [];
|
||||
setHosts(list);
|
||||
setTotal((res as any)?.total || 0);
|
||||
setTotal((res as Record<string, any>)?.total || 0);
|
||||
|
||||
// 统计
|
||||
const online = list.filter((h: any) => h.agent_status === 'online').length;
|
||||
setStats({ online, offline: list.length - online, total: (res as any)?.total || 0 });
|
||||
const online = list.filter(( h: any) => h.agent_status === 'online').length;
|
||||
setStats({ online, offline: list.length - online, total: (res as Record<string, any>)?.total || 0 });
|
||||
} catch {
|
||||
setHosts([]);
|
||||
setTotal(0);
|
||||
@@ -75,8 +89,8 @@ export default function HostMonitorDashboard() {
|
||||
useEffect(() => { loadData(); }, [loadData]);
|
||||
|
||||
// 排序后的数据
|
||||
const sorted = [...hosts].sort((a: any, b: any) => {
|
||||
let va: any, vb: any;
|
||||
const sorted = [...hosts].sort(( a: any, b: any) => {
|
||||
let va: Record<string, any>, vb: any;
|
||||
if (sortField === 'region') {
|
||||
va = (a.region?.name || a.region?.Name || '').toLowerCase();
|
||||
vb = (b.region?.name || b.region?.Name || '').toLowerCase();
|
||||
@@ -164,7 +178,7 @@ export default function HostMonitorDashboard() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
{(() => {
|
||||
const onlines = hosts.filter((h: any) => h.agent_status === 'online');
|
||||
const onlines = hosts.filter(( h: any) => h.agent_status === 'online');
|
||||
const avgCpu = onlines.length > 0 ? onlines.reduce((s: number, h: any) => s + (h.cpu_usage || 0), 0) / onlines.length : 0;
|
||||
const avgMem = onlines.length > 0 ? onlines.reduce((s: number, h: any) => s + (h.memory_usage || 0), 0) / onlines.length : 0;
|
||||
const cpuColor = avgCpu > 85 ? 'text-red-400' : avgCpu > 60 ? 'text-amber-400' : 'text-emerald-400';
|
||||
@@ -193,7 +207,7 @@ export default function HostMonitorDashboard() {
|
||||
</div>
|
||||
|
||||
{/* 资源条状图 — Grafana Bargauge 风格 (参照 K8s Node CPU/Memory Ratio) */}
|
||||
{hosts.filter((h: any) => h.agent_status === 'online').length > 0 && (
|
||||
{hosts.filter(( h: any) => h.agent_status === 'online').length > 0 && (
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{/* CPU 排行 */}
|
||||
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||
@@ -206,10 +220,10 @@ export default function HostMonitorDashboard() {
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
{[...hosts]
|
||||
.filter((h: any) => h.agent_status === 'online')
|
||||
.sort((a: any, b: any) => (b.cpu_usage || 0) - (a.cpu_usage || 0))
|
||||
.filter(( h: any) => h.agent_status === 'online')
|
||||
.sort(( a: any, b: any) => (b.cpu_usage || 0) - (a.cpu_usage || 0))
|
||||
.slice(0, 5)
|
||||
.map((h: any) => {
|
||||
.map(( h: any) => {
|
||||
const val = h.cpu_usage || 0;
|
||||
return (
|
||||
<div key={h.id} className="flex items-center gap-2">
|
||||
@@ -247,10 +261,10 @@ export default function HostMonitorDashboard() {
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
{[...hosts]
|
||||
.filter((h: any) => h.agent_status === 'online')
|
||||
.sort((a: any, b: any) => (b.memory_usage || 0) - (a.memory_usage || 0))
|
||||
.filter(( h: any) => h.agent_status === 'online')
|
||||
.sort(( a: any, b: any) => (b.memory_usage || 0) - (a.memory_usage || 0))
|
||||
.slice(0, 5)
|
||||
.map((h: any) => {
|
||||
.map(( h: any) => {
|
||||
const val = h.memory_usage || 0;
|
||||
return (
|
||||
<div key={h.id} className="flex items-center gap-2">
|
||||
@@ -349,7 +363,7 @@ export default function HostMonitorDashboard() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sorted.map((h: any) => (
|
||||
{sorted.map(( h: any) => (
|
||||
<tr key={h.id} className="border-b border-[var(--dg-border)]/50 hover:bg-[var(--dg-bg-tertiary)]/50 transition-colors">
|
||||
{/* 主机名/ID */}
|
||||
<td className="px-3 py-2">
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// Prometheus 数据源面板 — 接受外部配置 // Logan.Li
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Server, Loader2, Activity, Cpu, RefreshCw, AlertTriangle, Container, Zap, ExternalLink } from 'lucide-react';
|
||||
import { Server, Loader2, Activity, Cpu, RefreshCw, AlertTriangle, Container, Zap} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { toast } from 'sonner';
|
||||
import { http } from '@/services/http';
|
||||
|
||||
interface Props { dsUrl?: string; }
|
||||
@@ -36,17 +35,17 @@ const fmtBps = (b: number) => {
|
||||
return `${b.toFixed(1)} B/s`;
|
||||
};
|
||||
|
||||
function parsePrune(result: any[]): any[] {
|
||||
return (result || []).map((r: any) => {
|
||||
const m: any = { ...(r.metric || {}) };
|
||||
function parsePrune( result: any[]): any[] {
|
||||
return (result || []).map(( r: any) => {
|
||||
const m: Record<string, any> = { ...(r.metric || {}) };
|
||||
if (r.value) m.value = r.value[1];
|
||||
return m;
|
||||
});
|
||||
}
|
||||
|
||||
const Box = (p: any) => <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 Box = ( p: any) => <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 K8sPanel: React.FC<{ d: any }> = ({ d }) => (
|
||||
const K8sPanel: React.FC<{ d: Record<string, any>}> = ({ d }) => (
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<Card className="bg-[var(--dg-bg-primary)] border-[var(--dg-border)]"><CardContent className="p-3 text-center"><div className="text-2xl font-bold text-purple-400">{d.podCount?.[0]?.value || 0}</div><div className="text-[10px] text-[var(--dg-text-muted)]">Pods</div></CardContent></Card>
|
||||
@@ -56,27 +55,27 @@ const K8sPanel: React.FC<{ d: any }> = ({ d }) => (
|
||||
</div>
|
||||
<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 text-purple-400" />节点 CPU / 内存利用率</div>
|
||||
{(d.nodeCPU || []).map((n: any, i: number) => {
|
||||
const mem = (d.nodeMem || []).find((m: any) => m.node === n.node);
|
||||
{(d.nodeCPU || []).map(( n: any, i: number) => {
|
||||
const mem = (d.nodeMem || []).find(( m: any) => 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>;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const NodePanel: React.FC<{ d: any }> = ({ d }) => (
|
||||
const NodePanel: React.FC<{ d: Record<string, any>}> = ({ 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">节点</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: any, i: number) => {
|
||||
{(d.nodes || []).map(( n: any, i: number) => {
|
||||
const inst = n.instance || n.exported_instance || '';
|
||||
const cpu = (d.cpuUsage||[]).find((x:any)=>x.instance===inst);
|
||||
const mem = (d.memUsage||[]).find((x:any)=>x.instance===inst);
|
||||
const disk = (d.diskUsage||[]).find((x:any)=>x.instance===inst);
|
||||
const up = (d.up||[]).find((x:any)=>x.instance===inst);
|
||||
const rx = (d.netRx||[]).find((x:any)=>x.instance===inst);
|
||||
const tx = (d.netTx||[]).find((x:any)=>x.instance===inst);
|
||||
const cpu = (d.cpuUsage||[]).find(( x: any)=>x.instance===inst);
|
||||
const mem = (d.memUsage||[]).find(( x: any)=>x.instance===inst);
|
||||
const disk = (d.diskUsage||[]).find(( x: any)=>x.instance===inst);
|
||||
const up = (d.up||[]).find(( x: any)=>x.instance===inst);
|
||||
const rx = (d.netRx||[]).find(( x: any)=>x.instance===inst);
|
||||
const tx = (d.netTx||[]).find(( x: any)=>x.instance===inst);
|
||||
return <tr key={i} className="border-b border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/50">
|
||||
<td className="px-3 py-2 font-medium">{n.nodename||inst}</td>
|
||||
<td className="px-3 py-2"><Badge className={`text-[10px] ${up?.value===1?'bg-emerald-500/20 text-emerald-400':'bg-red-500/20 text-red-400'}`}>{up?.value===1?'UP':'DOWN'}</Badge></td>
|
||||
@@ -92,18 +91,18 @@ const NodePanel: React.FC<{ d: any }> = ({ d }) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const PodPanel: React.FC<{ d: any }> = ({ d }) => (
|
||||
const PodPanel: React.FC<{ d: Record<string, any>}> = ({ d }) => (
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<Card className="bg-[var(--dg-bg-primary)]"><CardContent className="p-3 text-center"><div className="text-xl font-bold text-emerald-400">{d.running?.[0]?.value||0}</div><div className="text-[10px] text-[var(--dg-text-muted)]">Running</div></CardContent></Card>
|
||||
<Card className="bg-[var(--dg-bg-primary)]"><CardContent className="p-3 text-center"><div className="text-xl font-bold text-amber-400">{d.pending?.[0]?.value||0}</div><div className="text-[10px] text-[var(--dg-text-muted)]">Pending</div></CardContent></Card>
|
||||
</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">CPU Top 10</div>{(d.cpuTop||[]).map((p:any,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">内存 Top 10</div>{(d.memTop||[]).map((p:any,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">CPU Top 10</div>{(d.cpuTop||[]).map(( p: any,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">内存 Top 10</div>{(d.memTop||[]).map(( p: any,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: any }> = ({ d }) => {
|
||||
const JvmPanel: React.FC<{ d: Record<string, any>}> = ({ d }) => {
|
||||
const h = Number(d.heapUsed?.[0]?.value||0);
|
||||
const uptime = Number(d.uptime?.[0]?.value||0);
|
||||
return (
|
||||
@@ -135,20 +134,20 @@ export const PrometheusDashboard: React.FC<Props> = ({ dsUrl }) => {
|
||||
const doConnect = async (u: string) => {
|
||||
setLoading(true); setError('');
|
||||
try {
|
||||
const data: any = await http.post('/prometheus/connect', { url: u });
|
||||
const data: Record<string, any> = await http.post('/prometheus/connect', { url: u });
|
||||
setConnected(true); setTargets(data.targets||0);
|
||||
loadAll(u);
|
||||
} catch (e: any) { setConnected(false); setError(e.message||'连接失败'); }
|
||||
} catch (e: any) { setConnected(false); setError((e as Record<string, any>).message||'连接失败'); }
|
||||
finally { setLoading(false); }
|
||||
};
|
||||
|
||||
const loadAll = async (u: string) => {
|
||||
for (const t of ['kubernetes','node','pod','jvm']) {
|
||||
try {
|
||||
const data: any = await http.post('/prometheus/dashboard', { url: u, type: t });
|
||||
const items: any = {};
|
||||
const data: Record<string, any> = await http.post('/prometheus/dashboard', { url: u, type: t });
|
||||
const items: Record<string, any> = {};
|
||||
for (const [k, v] of Object.entries(data||{}))
|
||||
items[k] = Array.isArray(v) ? v : ((v as any)?.data?.result ? parsePrune((v as any).data.result) : v);
|
||||
items[k] = Array.isArray(v) ? v : ((v as Record<string, any>)?.data?.result ? parsePrune((v as Record<string, any>).data.result) : v);
|
||||
if (t==='kubernetes') setK8sD(items); if (t==='node') setNodeD(items);
|
||||
if (t==='pod') setPodD(items); if (t==='jvm') setJvmD(items);
|
||||
} catch {}
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
// Zabbix 数据源监控面板 — 接受外部配置 // Logan.Li
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Server, Wifi, WifiOff, Loader2, AlertTriangle, Activity,
|
||||
Cpu, HardDrive, Network, Clock, RefreshCw, Plug, Settings,
|
||||
ArrowUpDown, ArrowUp, ArrowDown, ExternalLink
|
||||
|
||||
Server,
|
||||
Wifi,
|
||||
Loader2,
|
||||
AlertTriangle,
|
||||
Activity,
|
||||
RefreshCw,
|
||||
ArrowUpDown,
|
||||
ArrowUp,
|
||||
ArrowDown,
|
||||
} from 'lucide-react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { toast } from 'sonner';
|
||||
import { http } from '@/services/http';
|
||||
|
||||
interface ZabbixHost {
|
||||
@@ -38,11 +45,11 @@ export const ZabbixDashboard: React.FC<Props> = ({ dsUrl, dsUsername, dsPassword
|
||||
const doConnect = async (u: string, un: string, pw: string) => {
|
||||
setLoading(true); setError('');
|
||||
try {
|
||||
const data: any = await http.post('/zabbix/connect', { url: u, username: un, password: pw });
|
||||
const data: Record<string, any> = await http.post('/zabbix/connect', { url: u, username: un, password: pw });
|
||||
setHosts(data.hosts || []);
|
||||
setVersion(data.version || '');
|
||||
setConnected(true);
|
||||
} catch (e: any) { setConnected(false); setError(e.message || '连接失败'); }
|
||||
} catch (e: any) { setConnected(false); setError((e as Record<string, any>).message || '连接失败'); }
|
||||
finally { setLoading(false); }
|
||||
};
|
||||
|
||||
@@ -52,7 +59,7 @@ export const ZabbixDashboard: React.FC<Props> = ({ dsUrl, dsUsername, dsPassword
|
||||
else { setSortField(f); setSortDir('desc'); }
|
||||
};
|
||||
const sorted = [...hosts].sort((a, b) => {
|
||||
const va = (a as any)[sortField] ?? '', vb = (b as any)[sortField] ?? '';
|
||||
const va = (a as Record<string, any>)[sortField] ?? '', vb = (b as Record<string, any>)[sortField] ?? '';
|
||||
if (typeof va === 'number') return sortDir === 'asc' ? va - vb : vb - va;
|
||||
return sortDir === 'asc' ? String(va).localeCompare(String(vb)) : String(vb).localeCompare(String(va));
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user