import { useState, useEffect, useRef, useCallback } from 'react'; import { toast } from 'sonner'; import { agentApi } from '@/services/api'; import { AgentWizard } from './AgentWizard'; import { HostDetailDialog } from './HostDetailDialog'; import { isOnline, timeAgo, asList } from './agentUtils'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Badge } from '@/components/ui/badge'; import { Progress } from '@/components/ui/progress'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Cpu, MemoryStick, HardDrive, Wifi, WifiOff, RefreshCw, Terminal, Rocket, Activity, Network, Loader2, Clock, Server, } from 'lucide-react'; // ============ 组件 ============ export function AgentMonitor() { const [hosts, setHosts] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [selectedHost, setSelectedHost] = useState(null); const [wizardOpen, setWizardOpen] = useState(false); const timerRef = useRef | null>(null); // 远程命令 const [cmdOpen, setCmdOpen] = useState(false); const [cmdHost, setCmdHost] = useState(''); const [cmdType, setCmdType] = useState('command'); const [cmdCommand, setCmdCommand] = useState(''); const [cmdTimeout, setCmdTimeout] = useState('30'); const [cmdSubmitting, setCmdSubmitting] = useState(false); const [cmdResult, setCmdResult] = useState(null); const [cmdPolling, setCmdPolling] = useState(false); const pollTimerRef = useRef | null>(null); const loadLatest = useCallback(async (silent = false) => { if (!silent) setRefreshing(true); try { const data = await agentApi.getLatest(); const list = asList(data); setHosts(list); // 同步选中主机的最新数据 if (selectedHost) { const fresh = list.find((h) => h.hostname === selectedHost.hostname); if (fresh) setSelectedHost(fresh); } } catch (err) { if (!silent) toast.error('加载主机列表失败'); console.error(err); } finally { setLoading(false); setRefreshing(false); } }, [selectedHost]); useEffect(() => { loadLatest(); timerRef.current = setInterval(() => loadLatest(true), 30000); return () => { if (timerRef.current) clearInterval(timerRef.current); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // 打开远程命令弹窗 const openCommandDialog = (hostname: string) => { setCmdHost(hostname); setCmdType('command'); setCmdCommand(''); setCmdTimeout('30'); setCmdResult(null); setCmdOpen(true); }; // 提交远程命令并轮询结果 const submitCommand = async () => { if (!cmdCommand.trim()) { toast.error('请输入要执行的命令'); return; } setCmdSubmitting(true); setCmdResult(null); try { const res: any = await agentApi.createTask({ hostname: cmdHost, type: cmdType, command: cmdCommand.trim(), timeout: Number(cmdTimeout) || 30, }); const taskId = res?.id ?? res?.task_id; toast.success('任务已下发'); setCmdSubmitting(false); // 轮询任务结果 if (taskId != null) { setCmdPolling(true); let attempts = 0; pollTimerRef.current = setInterval(async () => { attempts++; try { const tasks: any = await agentApi.listTasks({ hostname: cmdHost }); const list = asList(tasks); const t = list.find((x: any) => String(x.id) === String(taskId)); if (t && t.status && t.status !== 'pending' && t.status !== 'running') { setCmdResult(t); setCmdPolling(false); if (pollTimerRef.current) { clearInterval(pollTimerRef.current); pollTimerRef.current = null; } loadLatest(true); } } catch { /* ignore */ } if (attempts > 15) { // 30s 超时 setCmdPolling(false); setCmdResult({ status: 'timeout', result: '等待结果超时,请稍后在任务列表查看' }); if (pollTimerRef.current) { clearInterval(pollTimerRef.current); pollTimerRef.current = null; } } }, 2000); } } catch (err: any) { toast.error(err?.message || '任务下发失败'); setCmdSubmitting(false); } }; const closeCommandDialog = () => { setCmdOpen(false); if (pollTimerRef.current) { clearInterval(pollTimerRef.current); pollTimerRef.current = null; } setCmdPolling(false); }; const onlineCount = hosts.filter((h) => isOnline(h.last_report || h.last_reported_at || h.updated_at)).length; const stats = [ { label: '主机总数', value: hosts.length, icon: Server, color: 'text-blue-400' }, { label: '在线', value: onlineCount, icon: Wifi, color: 'text-emerald-400' }, { label: '离线', value: hosts.length - onlineCount, icon: WifiOff, color: 'text-red-400' }, ]; return (
{/* 页头 */}

Agent 监控

实时监控 Agent 上报的主机指标

{/* 统计卡片 */}
{stats.map((s) => (
{s.value}
{s.label}
))}
{/* 主机列表 */} {loading ? ( 加载中... ) : hosts.length === 0 ? (

暂无 Agent 上报数据

) : (
{hosts.map((host) => { const online = isOnline(host.last_report || host.last_reported_at || host.updated_at); const cpu = Number(host.cpu_percent ?? host.cpu ?? 0); const mem = Number(host.mem_percent ?? host.mem ?? 0); const disk = Number(host.disk_percent ?? host.disk ?? 0); const ports: any[] = Array.isArray(host.ports) ? host.ports : []; return ( setSelectedHost(host)} >
{host.hostname}
{online ? ( 在线 ) : ( 离线 )}
{/* 指标条 */}
{/* 端口 & 时间 */}
{ports.length} 个端口 {timeAgo(host.last_report || host.last_reported_at || host.updated_at)}
{/* 操作 */}
e.stopPropagation()}>
); })}
)} {/* 详情抽屉 */} setSelectedHost(null)} onCommand={(hn) => openCommandDialog(hn)} /> {/* 远程命令弹窗 */} { if (!v) closeCommandDialog(); }}> 远程命令 在 {cmdHost} 上执行命令
setCmdHost(e.target.value)} readOnly className="h-9 bg-slate-800 border-slate-700 text-slate-200" />