diff --git a/frontend/src/components/monitor/dashboard/GrafanaDashboard.tsx b/frontend/src/components/monitor/dashboard/GrafanaDashboard.tsx index 9de4d93..e206ee5 100644 --- a/frontend/src/components/monitor/dashboard/GrafanaDashboard.tsx +++ b/frontend/src/components/monitor/dashboard/GrafanaDashboard.tsx @@ -1,16 +1,16 @@ // 监控大屏 — 标签页切换 Agent / Zabbix / Prometheus + 自定义仪表盘 // Logan.Li -import React, { useState, useEffect } from 'react'; +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, Trash2, Rocket, Zap + ExternalLink, Download, Rocket, PackageOpen, Loader2, Info } 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 { - Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter + Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, } from '@/components/ui/dialog'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Label } from '@/components/ui/label'; @@ -24,7 +24,6 @@ import { http } from '@/services/http'; import { asList } from '@/components/agent/agentUtils'; import { AgentWizard } from '@/components/agent/AgentWizard'; import { Checkbox } from '@/components/ui/checkbox'; -import { useConfirm } from '@/components/ui/confirm-dialog'; import { useNavigate } from 'react-router-dom'; // ═══════════ Tab 配置 ═══════════ @@ -41,7 +40,6 @@ interface AgentHost { server_id: number; hostname: string; ip: string; cpu_usage type SortField = 'hostname' | 'agent_version' | 'agent_status' | 'cpu_usage' | 'mem_usage' | 'disk_usage' | 'region' | 'ip'; const AgentTab: React.FC = () => { - const _confirm = useConfirm(); const navigate = useNavigate(); const [hosts, setHosts] = useState([]); const [loading, setLoading] = useState(true); @@ -52,6 +50,13 @@ const AgentTab: React.FC = () => { const [selected, setSelected] = useState>(new Set()); const [wizardOpen, setWizardOpen] = useState(false); const [wizardServer, setWizardServer] = useState(null); + // 批量部署对话框 + const [batchOpen, setBatchOpen] = useState(false); + const [batchMode, setBatchMode] = useState<'install' | 'upgrade'>('install'); + const [batchAck, setBatchAck] = useState(false); + const [batchSubmitting, setBatchSubmitting] = useState(false); + // 正在执行操作的主机 ID 集合(版本列 loading) + const [operating, setOperating] = useState>(new Set()); const totalPages = Math.max(1, Math.ceil(hosts.length / pageSize)); const fetch = async () => { try { setHosts(asList(await http.get('/agent/latest'))); } catch {} finally { setLoading(false); } }; useEffect(() => { fetch(); const t = setInterval(fetch, 30000); return () => clearInterval(t); }, []); @@ -72,53 +77,52 @@ const AgentTab: React.FC = () => { const selAny = selected.size > 0; const selIds = [...selected].map(Number).filter(n => !isNaN(n)); - // 批量安装 Agent // Logan.Li - const [batchInstalling, setBatchInstalling] = useState(false); - const handleBatchInstall = async () => { + // 选中主机按安装/在线分组 + const selectedHosts = useMemo(() => { + return hosts.filter(h => selected.has(String(h.server_id))); + }, [hosts, selected]); + const needInstall = useMemo(() => selectedHosts.filter(h => h.agent_status !== 'online'), [selectedHosts]); + const needUpgrade = useMemo(() => selectedHosts.filter(h => h.agent_status === 'online'), [selectedHosts]); + + // 打开批量部署对话框 + const openBatchDialog = () => { + setBatchAck(false); + // 自动推断模式: 全无Agent → install,全在线 → upgrade,混合 → install + if (needInstall.length > 0 && needUpgrade.length === 0) setBatchMode('install'); + else if (needInstall.length === 0 && needUpgrade.length > 0) setBatchMode('upgrade'); + else setBatchMode('install'); + setBatchOpen(true); + }; + + // 执行批量部署 + const handleBatchSubmit = async () => { + if (!batchAck) { toast.error('请先确认并勾选「我已知悉」'); return; } + setBatchSubmitting(true); const token = useUserStore.getState().token; - if (!token) { toast.error('未登录'); return; } - const ok = await _confirm({ title: '批量安装 Agent', description: `确定对选中的 ${selIds.length} 台主机远程安装 Agent?安装将在后台异步执行。`, confirmText: '安装', variant: 'destructive' }); - if (!ok) return; - setBatchInstalling(true); + const ids = selectedHosts.map(h => h.server_id); + setOperating(new Set(ids)); try { - const res = await serverApi.batchInstallAgent(selIds, token); - toast.success(`已提交:批量安装 ${res?.total ?? selIds.length} 台主机的 Agent,后台执行中,30秒后自动刷新`); - setSelected(new Set()); - setTimeout(() => fetch(), 30000); - } catch { toast.error('提交失败'); } - finally { setBatchInstalling(false); } - }; - - // 批量卸载 Agent // Logan.Li - const [batchUninstalling, setBatchUninstalling] = useState(false); - const handleBatchUninstall = async () => { - const ok = await _confirm({ title: '批量卸载 Agent', description: `确定卸载选中的 ${selIds.length} 台主机的 AIOps Agent?卸载将在后台异步执行。`, confirmText: '卸载', variant: 'destructive' }); - if (!ok) return; - setBatchUninstalling(true); - try { - await agentApi.batchUninstall(selIds); - toast.success(`已提交:批量卸载 ${selIds.length} 台主机的 Agent,后台执行中,30秒后自动刷新`); - setSelected(new Set()); - setTimeout(() => fetch(), 30000); - } catch { toast.error('提交失败'); } - finally { setBatchUninstalling(false); } - }; - - // 批量升级 Agent 到最新版本 - const [batchUpgrading, setBatchUpgrading] = useState(false); - const handleBatchUpgrade = async () => { - const online = hosts.filter((h: any) => selIds.includes(h.server_id) && h.agent_status === 'online'); - if (online.length === 0) { toast.error('所选主机中没有在线 Agent'); return; } - const ids = online.map((h: any) => h.server_id); - const ok = await _confirm({ title: '批量升级 Agent', description: `确定升级选中的 ${ids.length} 台在线主机的 Agent 到最新版本?升级将在后台异步执行。`, confirmText: '升级', variant: 'default' }); - if (!ok) return; - setBatchUpgrading(true); - try { - const res = await agentApi.batchUpgrade(ids); - toast.success(res?.message || `已提交:批量升级 ${ids.length} 台主机的 Agent,后台执行中,30秒后自动刷新`); - setTimeout(() => fetch(), 30000); - } catch { toast.error('提交失败'); } - finally { setBatchUpgrading(false); } + if (batchMode === 'install') { + if (!token) { toast.error('未登录'); return; } + // 安装:只安装无 Agent 的 + const installIds = needInstall.map(h => h.server_id); + if (installIds.length === 0) { toast.warning('所选主机均已安装 Agent,无需安装'); return; } + const res = await serverApi.batchInstallAgent(installIds, token); + toast.success(`已提交:批量安装 ${res?.total ?? installIds.length} 台主机的 Agent,后台执行中,30秒后自动刷新`); + setSelected(new Set()); + setTimeout(() => { fetch(); setOperating(new Set()); }, 30000); + } else { + // 升级:只升级在线的 + const upgradeIds = needUpgrade.map(h => h.server_id); + if (upgradeIds.length === 0) { toast.warning('所选主机中没有在线 Agent,无法升级'); return; } + const res = await agentApi.batchUpgrade(upgradeIds); + toast.success(res?.message || `已提交:批量升级 ${upgradeIds.length} 台主机的 Agent,后台执行中,30秒后自动刷新`); + setSelected(new Set()); + setTimeout(() => { fetch(); setOperating(new Set()); }, 30000); + } + setBatchOpen(false); + } catch { toast.error('提交失败'); setOperating(new Set()); } + finally { setBatchSubmitting(false); } }; if (loading) return

加载中…

; @@ -137,17 +141,9 @@ const AgentTab: React.FC = () => { 安装 Agent {selAny && ( - <> - - - - + )} @@ -156,6 +152,131 @@ const AgentTab: React.FC = () => { onInstalled={() => { toast.success('Agent 安装成功'); setWizardOpen(false); fetch(); }} onFailed={() => toast.error('安装失败')} /> + {/* ────────── 批量部署 Agent 对话框 ────────── */} + { if (!v) { setBatchOpen(false); setBatchAck(false); } }}> + + + + + 批量部署 Agent + + + 选中 {selectedHosts.length} 台主机(需安装: {needInstall.length},待升级: {needUpgrade.length}) + + + +
+ {/* 模式选择 — 类似阿里云安装/升级切换 */} +
+ {(['install', 'upgrade'] as const).map(m => ( + + ))} +
+ + {/* 主机列表 */} +
+ + + + + + + + + + + + {(batchMode === 'install' ? needInstall : needUpgrade).map(h => ( + + + + + + + + ))} + +
主机名IP状态当前版本操作
{h.hostname}{h.ip} + {h.agent_status === 'online' + ? 在线 + : + {h.agent_status === 'not_installed' ? '未安装' : h.agent_status || '离线'} + } + {h.agent_version || '—'} + + {batchMode === 'install' ? '即将安装' : '即将升级'} + +
+
+ + {/* ⚠️ 警告文案 — 参考阿里云批量安装/升级风格 */} +
+
+ +
+

+ {batchMode === 'install' ? '本次安装' : '本次升级'},主机监控探针将部署 AIOps Agent。AIOps Agent 是 AIOps 平台推出的新一代数据采集器,提供主机监控指标采集和可观测数据采集能力。 +

+

+ {batchMode === 'upgrade' && '升级前请注意以下事项:'} + {batchMode === 'install' && '安装前请注意以下事项:'} +

+
    + {batchMode === 'upgrade' && ( + <> +
  • 如果您的机器已经安装旧版 Agent,升级操作将停止旧版并替换为最新版本。
  • +
  • 如果您的机器之前安装过 Agent 但已卸载,遗留有相关采集配置,本次升级可能导致配置重新生效。升级前请确保存量废弃配置已经移除。
  • +
  • 如果您的机器采用自定义安装的 Agent(非 AIOps 提供的一键安装脚本),请不要直接使用批量升级。
  • +
  • 批量升级前,请务必先单机灰度升级,确保升级成功且不影响业务后再操作。
  • + + )} + {batchMode === 'install' && ( + <> +
  • 安装将自动检测目标主机架构(amd64/arm64),下载对应版本的 Agent 二进制并启动服务。
  • +
  • 请确保目标主机的 SSH 端口(默认 22)可访问,且已关联有效的 SSH 凭据。
  • +
  • 安装过程约 30-60 秒,期间目标主机 CPU 负载会短暂升高。
  • +
  • 若安装失败,请检查目标主机的防火墙规则和 SSH 服务状态后重试。
  • + + )} +
  • 操作不可回滚,请在操作前确认已备份关键配置。
  • +
  • 操作将在后台异步执行,您可以在「任务执行记录」中查看详细进度。
  • +
+
+
+
+ + {/* 我已知悉 勾选 */} +
+ setBatchAck(!!c)} /> + +
+
+ + + + + +
+
+
@@ -179,7 +300,16 @@ const AgentTab: React.FC = () => { }} /> - +
{h.hostname}{h.agent_version || '—'} + {operating.has(h.server_id) ? ( + + + 部署中 + + ) : ( + h.agent_version || '—' + )} + {h.agent_status==='online'?在线:h.agent_status==='not_installed'?未安装:离线} {bar(Number(h.cpu_usage||0), `${Number(h.cpu_usage||0).toFixed(1)}%`)} {bar(Number(h.mem_usage||0), `${Number(h.mem_usage||0).toFixed(1)}%`)}