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:
attacker committed 2026-06-25 07:17:47 +08:00
1 parent 4dd5e10b46
commit 5364d93ed7
149 files changed
+2021 -3128

No files matched your search

@@ -45,7 +45,7 @@ export function AlertChannelDialog({
}
}, [open]);
const openEditChannel = (ch: any) => {
const openEditChannel = ( ch: any) => {
setEditingChannel(ch);
setChannelForm({
name: ch.name || '',
@@ -83,7 +83,7 @@ export function AlertChannelDialog({
}
};
const handleDeleteChannel = async (id: any) => {
const handleDeleteChannel = async ( id: any) => {
{ const ok = await _confirm({ title: '删除通知渠道', description: '确定删除此通知渠道?', confirmText: '删除', variant: 'destructive' }); if (!ok) return; }
try {
await alertApi.deleteNotifyChannel(id);
@@ -94,7 +94,7 @@ export function AlertChannelDialog({
}
};
const handleToggleChannel = async (ch: any) => {
const handleToggleChannel = async ( ch: any) => {
try {
await alertApi.updateNotifyChannel(ch.id, { enabled: !ch.enabled });
onChannelsChanged();
@@ -103,7 +103,7 @@ export function AlertChannelDialog({
}
};
const handleTestChannel = async (id: any) => {
const handleTestChannel = async ( id: any) => {
setChannelTesting(id);
try {
await alertApi.testNotifyChannel(id);
@@ -38,7 +38,7 @@ export function AlertEventsTab({
return matchesSearch && matchesSeverity && matchesStatus;
});
const getSeverityBadge = (severity: any) => {
const getSeverityBadge = ( severity: any) => {
switch (severity) {
case 'critical':
return (
@@ -66,7 +66,7 @@ export function AlertEventsTab({
}
};
const getStatusBadge = (status: any) => {
const getStatusBadge = ( status: any) => {
switch (status) {
case 'firing':
return (
@@ -85,7 +85,7 @@ export function AlertEventsTab({
}
};
const handleAcknowledge = async (id: any) => {
const handleAcknowledge = async ( id: any) => {
try {
await alertApi.acknowledge(id);
onReload();
@@ -36,9 +36,9 @@ export function AlertManager() {
alertApi.getRules(),
alertApi.getNotifyChannels(),
]);
setAlerts((alertsData as any)?.list || []);
setRules((rulesData as any)?.list || (Array.isArray(rulesData) ? rulesData : []));
setNotifyChannels((channelsData as any)?.list || []);
setAlerts((alertsData as Record<string, any>)?.list || []);
setRules((rulesData as Record<string, any>)?.list || (Array.isArray(rulesData) ? rulesData : []));
setNotifyChannels((channelsData as Record<string, any>)?.list || []);
} catch (error) {
console.error('Failed to load alert data:', error);
} finally {
@@ -55,7 +55,7 @@ export function AlertManager() {
setRuleDialogOpen(true);
};
const openEditRule = (rule: any) => {
const openEditRule = ( rule: any) => {
setEditingRule(rule);
setRuleDialogOpen(true);
};
@@ -29,7 +29,7 @@ export function AlertRulesTab({
rules: any[];
notifyChannels: any[];
onReload: () => void;
onEditRule: (rule: any) => void;
onEditRule: ( rule: any) => void;
}) {
const confirm = useConfirm();
const [selectedIds, setSelectedIds] = useState<number[]>([]);
@@ -50,7 +50,7 @@ export function AlertRulesTab({
);
};
const handleDeleteRule = async (id: any) => {
const handleDeleteRule = async ( id: any) => {
const ok = await confirm({
title: '删除告警规则',
description: '确定删除此告警规则?',
@@ -89,7 +89,7 @@ export function AlertRulesTab({
}
};
const handleToggleRule = async (id: any) => {
const handleToggleRule = async ( id: any) => {
try {
await alertApi.toggleRule(id);
onReload();
@@ -72,7 +72,7 @@ const AlertCenter: React.FC = () => {
const fetchAlerts = async () => {
setLoading(true);
try {
const res: any = await alertCenterApi.getAlerts({
const res: Record<string, any> = await alertCenterApi.getAlerts({
page,
page_size: pageSize,
severity: severityFilter !== 'all' ? severityFilter : undefined,
@@ -117,14 +117,14 @@ const AlertCenter: React.FC = () => {
const fetchContacts = async () => {
try {
const res: any = await alertApi.getContacts();
const res: Record<string, any> = await alertApi.getContacts();
setContacts((res?.data?.list || res?.list || []) as Contact[]);
} catch { /* silent */ }
};
const fetchContactGroups = async () => {
try {
const res: any = await alertApi.getContactGroups();
const res: Record<string, any> = await alertApi.getContactGroups();
setContactGroups((res?.data?.list || res?.list || []) as ContactGroup[]);
} catch { /* silent */ }
};
@@ -24,7 +24,7 @@ export const AlertChannelsTab: React.FC<{ refreshKey: number }> = ({ refreshKey
setLoading(true);
try {
const res = await alertApi.getNotifyChannels();
const list = (res as any)?.list || [];
const list = (res as Record<string, any>)?.list || [];
setChannels(list);
} catch {
toast.error('获取通知渠道失败');
@@ -60,7 +60,7 @@ export const AlertChannelsTab: React.FC<{ refreshKey: number }> = ({ refreshKey
const handleTest = async (id: number) => {
try {
const res = await alertApi.testNotifyChannel(id);
const status = (res as any)?.status;
const status = (res as Record<string, any>)?.status;
if (status === 'success') {
toast.success('测试通知已发送');
} else {
@@ -76,7 +76,7 @@ export interface ContactGroup {
}
// Logan.Li — 后端字段映射到前端 Alert 接口
export function mapAlert(a: any): Alert {
export function mapAlert( a: any): Alert {
return {
id: String(a.id),
title: a.rule_name || a.title || `告警 #${a.id}`,
@@ -1,7 +1,7 @@
import { Mail, Phone, MessageSquare, Bell } from 'lucide-react';
// 将 notify_channels (可能是数组/JSON字符串/逗号分隔) 解析为数组
export function parseChannels(notifyChannels: any): string[] {
export function parseChannels( notifyChannels: any): string[] {
if (!notifyChannels) return [];
if (Array.isArray(notifyChannels)) return notifyChannels;
if (typeof notifyChannels === 'string') {
@@ -71,7 +71,7 @@ export function getChannelIcon(type: string) {
}
// 渲染规则的通知渠道引用列表(兼容新格式=渠道ID 和 旧格式=类型字符串)
export function renderRuleChannels(rule: any, notifyChannels: any[]) {
export function renderRuleChannels( rule: any, notifyChannels: any[]) {
const refs = parseChannels(rule.notify_channels);
if (refs.length === 0) return <span className="text-xs text-slate-600">--</span>;
return (
@@ -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));
});
@@ -1,17 +1,34 @@
// Prometheus 数据源管理 — 参考 Grafana 界面设计 // Logan.Li
import { useState, useEffect, useCallback } from 'react';
import {
Database, Plus, Edit, Trash2, Star, RefreshCw, CheckCircle, XCircle,
Shield, Globe, Clock, Wrench, Settings, Key, Link, X, ChevronDown, ChevronUp,
Plug, AlertTriangle
Database,
Plus,
Edit,
Trash2,
Star,
RefreshCw,
CheckCircle,
XCircle,
Shield,
Globe,
Clock,
Wrench,
Settings,
Key,
X,
ChevronDown,
ChevronUp,
Plug,
AlertTriangle,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import { Separator } from '@/components/ui/separator';
import {
Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter
} from '@/components/ui/dialog';
@@ -89,7 +106,7 @@ export default function PrometheusDatasourceManager() {
const [headers, setHeaders] = useState<HeaderEntry[]>([]);
// 折叠面板
const [showAuth, setShowAuth] = useState(false);
const [showTLS, setShowTLS] = useState(false);
const [, setShowTLS] = useState(false);
const [showAlerting, setShowAlerting] = useState(false);
const [showMisc, setShowMisc] = useState(false);
@@ -99,7 +116,7 @@ export default function PrometheusDatasourceManager() {
const fetchList = useCallback(async () => {
try {
const res: any = await promDatasourceApi.list();
const res: Record<string, any> = await promDatasourceApi.list();
setSources(res?.data || res || []);
} catch {
toast.error('获取数据源列表失败');
@@ -187,7 +204,7 @@ export default function PrometheusDatasourceManager() {
if (!form.name.trim()) { toast.error('请输入数据源名称'); return; }
if (!form.url.trim()) { toast.error('请输入 Prometheus URL'); return; }
const payload: any = {
const payload: Record<string, any> = {
name: form.name.trim(),
url: form.url.trim(),
access: form.access,
@@ -258,7 +275,7 @@ export default function PrometheusDatasourceManager() {
setTesting(true);
setTestResult(null);
try {
const res: any = await promDatasourceApi.testConnection({
const res: Record<string, any> = await promDatasourceApi.testConnection({
url: form.url.trim(),
basic_auth: form.basic_auth,
basic_auth_user: form.basic_auth_user,
@@ -810,7 +827,7 @@ export default function PrometheusDatasourceManager() {
// 快速测试连接(列表中的测试按钮)
async function handleTestQuick(ds: PromDS) {
try {
const res: any = await promDatasourceApi.testConnection({
const res: Record<string, any> = await promDatasourceApi.testConnection({
url: ds.url,
basic_auth: ds.basic_auth,
basic_auth_user: ds.basic_auth_user,
@@ -1,20 +1,33 @@
// Zabbix 数据源管理 — 参照 Grafana Zabbix 数据源配置界面 // Logan.Li
import { useState, useEffect, useCallback } from 'react';
import {
Database, Plus, Trash2, Star, RefreshCw, Plug,
Shield, Globe, Settings, Key, ChevronDown, ChevronUp,
Edit, Clock, CheckCircle2, XCircle
Database,
Plus,
Trash2,
Star,
RefreshCw,
Plug,
Shield,
Globe,
Settings,
ChevronDown,
ChevronUp,
Edit,
Clock,
CheckCircle2,
XCircle,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Card, CardContent} from '@/components/ui/card';
import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import {
Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter
} from '@/components/ui/dialog';
import { Separator } from '@/components/ui/separator';
import { toast } from 'sonner';
import { useConfirm } from '@/components/ui/confirm-dialog';
import { zabbixDatasourceApi } from '@/services/api';
@@ -61,7 +74,7 @@ export default function ZabbixDatasourceManager() {
const fetchList = useCallback(async () => {
try {
const res: any = await zabbixDatasourceApi.list();
const res: Record<string, any> = await zabbixDatasourceApi.list();
setSources(res?.data || res || []);
} catch {
toast.error('获取数据源列表失败');
@@ -151,7 +164,7 @@ export default function ZabbixDatasourceManager() {
setTesting(true);
setTestResult(null);
try {
const res: any = await zabbixDatasourceApi.testConnection({
const res: Record<string, any> = await zabbixDatasourceApi.testConnection({
url: form.url.trim(),
username: form.username.trim(),
password: form.password || '',
@@ -1,14 +1,31 @@
import React, { useState, useEffect } from 'react';
import {
Search, Filter, Clock, Server, ArrowRight, ChevronRight, ChevronDown, Activity,
Zap, AlertCircle, CheckCircle, XCircle, BarChart3, PieChart, TrendingUp,
Download, RefreshCw, Eye, Maximize2, Minimize2, Target, Layers, GitBranch,
Cpu, Database, Globe, Code, Box, Container, Settings, Info
Search,
Clock,
Server,
ArrowRight,
ChevronRight,
ChevronDown,
Activity,
Zap,
AlertCircle,
CheckCircle,
XCircle,
BarChart3,
TrendingUp,
RefreshCw,
Eye,
Layers,
GitBranch,
Code,
Box,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card';
import {
Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription
} from '@/components/ui/dialog';
@@ -91,7 +108,7 @@ const TraceManager: React.FC = () => {
try {
const res = await traceApi.getTraces({ timeRange, service: serviceFilter, status: statusFilter });
setTraces(res.data || []);
} catch (error) {
} catch {
toast.error('获取链路数据失败');
} finally {
setLoading(false);
@@ -102,7 +119,7 @@ const TraceManager: React.FC = () => {
try {
const res = await traceApi.getServices(timeRange);
setServices(res.data || []);
} catch (error) {
} catch {
toast.error('获取服务数据失败');
}
};
@@ -111,7 +128,7 @@ const TraceManager: React.FC = () => {
try {
const res = await traceApi.getTraceDetail(traceId);
return res.data;
} catch (error) {
} catch {
toast.error('获取链路详情失败');
return null;
}
@@ -192,7 +209,7 @@ const TraceManager: React.FC = () => {
const SpanTreeNode: React.FC<{ span: Span; level: number; totalDuration: number }> = ({ span, level, totalDuration }) => {
const [expanded, setExpanded] = useState(true);
const widthPercent = totalDuration > 0 ? (span.duration / totalDuration) * 100 : 0;
const offsetPercent = 0; // Simplified
const _offsetPercent = 0; // Simplified
return (
<div className="select-none">
@@ -536,7 +553,7 @@ const TraceManager: React.FC = () => {
<span className="w-20 text-right">耗时</span>
</div>
{/* Span树渲染 */}
{(selectedTrace as any).spans && buildSpanTree((selectedTrace as any).spans).map(span => (
{(selectedTrace as Record<string, any>).spans && buildSpanTree((selectedTrace as Record<string, any>).spans).map(span => (
<SpanTreeNode key={span.spanId} span={span} level={0} totalDuration={selectedTrace.duration} />
))}
</div>