fix: agent_version 为空 & 恢复批量卸载

- 后端上报时若 agent_version 为空则不覆盖已有版本(防止旧Agent清空版本号)
- 安装/升级后写入版本号 v0.1.0(而非 installed/latest)
- DB 存量 online 主机 agent_version 补填 v0.1.0
- 前端恢复批量卸载 Agent 按钮
This commit is contained in:
attacker committed 2026-06-19 01:51:16 +00:00
1 parent 502674d73a
commit 4dd3e3238d
3 files changed
+43 -11

No files matched your search

+14 -3
View File
@@ -96,19 +96,30 @@ func (h *AgentHandler) ReceiveReport(c *gin.Context) {
}
// 直接 SQL 更新 asset_servers 监控字段
// 若 AgentVersion 非空则同步写入,否则保留数据库中已有版本号(防止旧版Agent空字段覆盖已设置的版本)
updateSQL := `UPDATE asset_servers SET
cpu_usage = ?, memory_usage = ?, disk_usage = ?,
cpu = ?, memory = ?, disk = ?,
status = ?, agent_status = ?, agent_version = ?,
status = ?, agent_status = ?` + func() string {
if req.AgentVersion != "" {
return `, agent_version = ?`
}
return ``
}() + `,
hostname = CASE WHEN hostname != ? AND ip = ? THEN ? ELSE hostname END,
updated_at = NOW()`
args := []interface{}{
req.CPU.UsagePercent, req.Memory.UsagePercent, diskAvg,
req.CPU.CoreCount, memMB, diskGB,
"running", "online", req.AgentVersion,
"running", "online",
}
if req.AgentVersion != "" {
args = append(args, req.AgentVersion)
}
args = append(args,
// CASE WHEN hostname != ? AND ip = ? THEN ? ELSE hostname END
req.Hostname, matchIP, req.Hostname,
}
)
if req.System != nil {
if req.System.OSVersion != "" {
updateSQL += ", os = ?"
@@ -171,10 +171,10 @@ func (h *AgentInstallHandler) InstallAgent(c *gin.Context) {
installed := contains(verifyOut, "active") || contains(verifyOut, "running")
if installed {
// 更新服务器 Agent 状态
// 更新服务器 Agent 状态(版本号:安装成功后由 Agent 下一次上报自动更新)
h.db.Model(&server).Updates(map[string]interface{}{
"agent_status": "online",
"agent_version": "installed",
"agent_version": "v0.1.0",
"last_check_at": time.Now(),
})
}
@@ -224,7 +224,7 @@ func (h *AgentInstallHandler) installOne(server models.Server, serverURL, token
if installed {
h.db.Model(&server).Updates(map[string]interface{}{
"agent_status": "online",
"agent_version": "installed",
"agent_version": "v0.1.0",
"last_check_at": time.Now(),
})
}
@@ -433,7 +433,7 @@ func (h *AgentInstallHandler) BatchUpgradeAgent(c *gin.Context) {
// 更新版本号
h.db.Model(&server).Updates(map[string]interface{}{
"agent_version": "latest",
"agent_version": "v0.1.0",
"last_check_at": time.Now(),
})
log.Printf("[BatchUpgrade] %s Agent 升级成功", server.Hostname)
@@ -3,7 +3,7 @@ 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
ExternalLink, Download, Rocket, PackageOpen, Loader2, Info, Trash2
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@@ -24,6 +24,7 @@ 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 配置 ═══════════
@@ -40,6 +41,7 @@ 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<AgentHost[]>([]);
const [loading, setLoading] = useState(true);
@@ -57,6 +59,20 @@ const AgentTab: React.FC = () => {
const [batchSubmitting, setBatchSubmitting] = useState(false);
// 正在执行操作的主机 ID 集合(版本列 loading)
const [operating, setOperating] = useState<Set<number>>(new Set());
// 批量卸载
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); }
};
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); }, []);
@@ -141,9 +157,14 @@ const AgentTab: React.FC = () => {
<Rocket className="w-3 h-3" />安装 Agent
</Button>
{selAny && (
<Button size="sm" variant="outline" className="h-7 text-xs gap-1 border-blue-500/30 text-blue-400 hover:bg-blue-500/10" onClick={openBatchDialog}>
<PackageOpen className="w-3 h-3" />批量部署 ({selected.size})
</Button>
<>
<Button size="sm" variant="outline" className="h-7 text-xs gap-1 border-blue-500/30 text-blue-400 hover:bg-blue-500/10" onClick={openBatchDialog}>
<PackageOpen className="w-3 h-3" />批量部署 ({selected.size})
</Button>
<Button size="sm" variant="outline" className="h-7 text-xs gap-1 border-red-500/30 text-red-400 hover:bg-red-500/10" onClick={handleBatchUninstall} disabled={batchUninstalling}>
<Trash2 className="w-3 h-3" />批量卸载 ({selected.size})
</Button>
</>
)}
<Button size="sm" variant="ghost" className="h-7 text-xs gap-1" onClick={fetch}><RefreshCw className="w-3 h-3" />刷新</Button>
</div>