feat: 工具配置增强、站点拨测与日志中心多项改进

- 工具配置:新增卸载 API/UI、Go 工具链、流水线构建自动安装依赖
- 修复 ListTools VerifyTool 递归栈溢出导致获取工具配置失败
- Node.js Alpine 安装改用 unofficial-builds musl 预编译包与 index.json 解析 LTS
- 站点拨测:详情页路由、告警自动恢复、并发拨测与默认可观测频率
- 日志中心:RBAC、查询构建器、ES/Loki 增强与审计 request/response 展示
- 发布配置:构建节点、流水线远程执行、Deploy RBAC 导航拆分
This commit is contained in:
Logan committed 2026-07-02 07:47:33 +08:00
1 parent cde7bbe540
commit 7e1c8f583c
84 files changed
+7729 -1507

No files matched your search

+37 -23
View File
@@ -58,9 +58,9 @@ func truncateAuditText(s string, n int) string {
return s
}
// recordAgentAudit 记录 Agent 安装/卸载/升级操作审计 // Logan.Li
// status: 1=成功, 0=失败;errMsg 记录失败原因(SSH 错误、脚本输出等),用于追溯"安装过程遇到什么问题"
func (h *AgentInstallHandler) recordAgentAudit(ac auditCtx, action, resource, resourceID string, status int, errMsg string) {
// recordAgentAudit 记录 Agent 安装/卸载/升级操作审计(含命令输出与验证结果)// Logan.Li
// status: 1=成功, 0=失败
func (h *AgentInstallHandler) recordAgentAudit(ac auditCtx, action, resource, resourceID string, status int, reqDetail, respDetail, errMsg string) {
uid := ac.userID
h.db.Create(&model.AuditLog{
UserID: &uid,
@@ -71,8 +71,10 @@ func (h *AgentInstallHandler) recordAgentAudit(ac auditCtx, action, resource, re
Action: action,
Resource: resource,
ResourceID: resourceID,
Request: truncateAuditText(reqDetail, 2048),
Response: truncateAuditText(respDetail, 8192),
Status: status,
Error: errMsg,
Error: truncateAuditText(errMsg, 512),
})
}
@@ -235,7 +237,7 @@ func (h *AgentInstallHandler) InstallAgent(c *gin.Context) {
client, err := sshDial(effectiveServer)
if err != nil {
log.Printf("[AgentInstall] SSH连接失败 %s: %v", server.IP, err)
h.recordAgentAudit(ac, "install", resource, resourceID, 0, "SSH连接失败: "+formatSSHError(err))
h.recordAgentAudit(ac, "install", resource, resourceID, 0, resource, "", "SSH连接失败: "+formatSSHError(err))
c.JSON(http.StatusOK, model.Response{
Code: 500,
Message: "SSH 连接失败: " + formatSSHError(err),
@@ -296,7 +298,8 @@ func (h *AgentInstallHandler) InstallAgent(c *gin.Context) {
} else if strings.Contains(lowerOut, "下载失败") || strings.Contains(lowerOut, "failed to connect") || strings.Contains(lowerOut, "connection refused") {
msg = "目标主机无法下载安装脚本,请检查后端地址 " + serverURL + " 是否可达"
}
h.recordAgentAudit(ac, "install", resource, resourceID, 0, msg+": "+truncateAuditText(installOut, 300))
h.recordAgentAudit(ac, "install", resource, resourceID, 0,
fmt.Sprintf("server=%s arch=%s", serverURL, arch), installOut, msg)
c.JSON(http.StatusOK, model.Response{
Code: 500,
Message: msg,
@@ -309,23 +312,30 @@ func (h *AgentInstallHandler) InstallAgent(c *gin.Context) {
verifyOut, _ := runSSHCommand(client, "systemctl is-active aiops-agent 2>/dev/null || echo 'not_found'")
installed := contains(verifyOut, "active") || contains(verifyOut, "running")
var agentVer string
if installed {
// 只更新在线状态;版本号仅在能读到真实版本时才写入,未上报时不写假版本(等 Agent 首次上报真实数据)// Logan.Li
updates := map[string]interface{}{
"agent_status": "online",
"last_check_at": time.Now(),
}
if agentVersion := detectAgentVersion(client); agentVersion != "" {
updates["agent_version"] = agentVersion
agentVer = detectAgentVersion(client)
if agentVer != "" {
updates["agent_version"] = agentVer
}
h.db.Model(&server).Updates(updates)
}
// 审计:成功或"命令已执行但服务未运行"均记录,便于追溯 // Logan.Li
reqDetail := fmt.Sprintf("server=%s arch=%s", serverURL, arch)
respDetail := fmt.Sprintf("=== 安装输出 ===\n%s\n=== 服务状态 ===\n%s", installOut, verifyOut)
if agentVer != "" {
respDetail += "\n=== Agent版本 ===\n" + agentVer
}
if installed {
h.recordAgentAudit(ac, "install", resource, resourceID, 1, "")
h.recordAgentAudit(ac, "install", resource, resourceID, 1, reqDetail, respDetail, "")
} else {
h.recordAgentAudit(ac, "install", resource, resourceID, 0, "安装命令已执行但服务未检测到运行: "+truncateAuditText(verifyOut, 300))
h.recordAgentAudit(ac, "install", resource, resourceID, 0, reqDetail, respDetail, "安装命令已执行但服务未检测到运行")
}
c.JSON(http.StatusOK, model.Response{
@@ -434,20 +444,22 @@ func (h *AgentInstallHandler) BatchInstallAgent(c *gin.Context) {
}
}
installed, _, verifyOut, err := h.installOne(effectiveServer, serverURL, token)
installed, installOut, verifyOut, err := h.installOne(effectiveServer, serverURL, token)
res := server.Hostname + " (" + server.IP + ")"
rid := strconv.Itoa(int(server.ID))
reqDetail := fmt.Sprintf("server=%s host=%s", serverURL, res)
if err != nil {
log.Printf("[BatchInstall] %s (%s) 安装失败: %v", server.Hostname, server.IP, err)
h.recordAgentAudit(ac, "install", res, rid, 0, truncateAuditText(err.Error(), 500))
h.recordAgentAudit(ac, "install", res, rid, 0, reqDetail, installOut, truncateAuditText(err.Error(), 500))
} else {
successCount++
respDetail := fmt.Sprintf("=== 安装输出 ===\n%s\n=== 服务状态 ===\n%s", installOut, verifyOut)
if installed {
log.Printf("[BatchInstall] %s Agent 安装成功", server.Hostname)
h.recordAgentAudit(ac, "install", res, rid, 1, "")
h.recordAgentAudit(ac, "install", res, rid, 1, reqDetail, respDetail, "")
} else {
log.Printf("[BatchInstall] %s Agent 安装命令已执行但服务未检测到运行", server.Hostname)
h.recordAgentAudit(ac, "install", res, rid, 0, "服务未检测到运行: "+truncateAuditText(verifyOut, 300))
h.recordAgentAudit(ac, "install", res, rid, 0, reqDetail, respDetail, "服务未检测到运行")
}
}
}
@@ -498,14 +510,14 @@ func (h *AgentInstallHandler) UninstallAgent(c *gin.Context) {
h.db.Model(&server).Updates(map[string]interface{}{
"agent_status": "offline", "agent_version": "",
})
h.recordAgentAudit(ac, "uninstall", resource, resourceID, 1, "无SSH凭据,已标记离线")
h.recordAgentAudit(ac, "uninstall", resource, resourceID, 1, resource, "无SSH凭据,已标记离线", "")
c.JSON(http.StatusOK, model.Response{Code: 200, Message: "已标记为离线(该主机无SSH凭据,无法远程卸载,请手动执行 systemctl stop aiops-agent)"})
return
}
client, err := sshDial(server)
if err != nil {
h.recordAgentAudit(ac, "uninstall", resource, resourceID, 0, "SSH连接失败: "+formatSSHError(err))
h.recordAgentAudit(ac, "uninstall", resource, resourceID, 0, resource, "", "SSH连接失败: "+formatSSHError(err))
c.JSON(http.StatusOK, model.Response{Code: 500, Message: "SSH连接失败: " + formatSSHError(err)})
return
}
@@ -516,7 +528,7 @@ func (h *AgentInstallHandler) UninstallAgent(c *gin.Context) {
h.db.Model(&server).Updates(map[string]interface{}{
"agent_status": "offline", "agent_version": "",
})
h.recordAgentAudit(ac, "uninstall", resource, resourceID, 1, "")
h.recordAgentAudit(ac, "uninstall", resource, resourceID, 1, resource, "systemctl stop + rm binary", "")
c.JSON(http.StatusOK, model.Response{Code: 200, Message: fmt.Sprintf("已卸载 %s (%s) 的 Agent", server.Hostname, server.IP)})
}
@@ -548,21 +560,21 @@ func (h *AgentInstallHandler) BatchUninstallAgent(c *gin.Context) {
if server.Username == "" || (server.Password == "" && server.SSHKey == "") {
h.db.Model(&server).Updates(map[string]interface{}{"agent_status": "offline", "agent_version": ""})
log.Printf("[BatchUninstall] %s 无SSH凭据,已标记离线", server.Hostname)
h.recordAgentAudit(ac, "uninstall", res, rid, 1, "无SSH凭据,已标记离线")
h.recordAgentAudit(ac, "uninstall", res, rid, 1, res, "无SSH凭据,已标记离线", "")
successCount++
continue
}
client, err := sshDial(server)
if err != nil {
log.Printf("[BatchUninstall] %s SSH失败: %v", server.Hostname, err)
h.recordAgentAudit(ac, "uninstall", res, rid, 0, "SSH连接失败: "+formatSSHError(err))
h.recordAgentAudit(ac, "uninstall", res, rid, 0, res, "", "SSH连接失败: "+formatSSHError(err))
continue
}
runSSHCommand(client, "systemctl stop aiops-agent 2>/dev/null; systemctl disable aiops-agent 2>/dev/null; rm -f /usr/local/bin/aiops-agent /etc/systemd/system/aiops-agent.service; systemctl daemon-reload 2>/dev/null")
client.Close()
h.db.Model(&server).Updates(map[string]interface{}{"agent_status": "offline", "agent_version": ""})
log.Printf("[BatchUninstall] %s Agent 已卸载", server.Hostname)
h.recordAgentAudit(ac, "uninstall", res, rid, 1, "")
h.recordAgentAudit(ac, "uninstall", res, rid, 1, res, "systemctl stop + rm binary", "")
successCount++
}
log.Printf("[BatchUninstall] 批量卸载完成: %d/%d 成功", successCount, len(req.ServerIDs))
@@ -610,7 +622,7 @@ func (h *AgentInstallHandler) BatchUpgradeAgent(c *gin.Context) {
client, err := sshDial(server)
if err != nil {
log.Printf("[BatchUpgrade] %s SSH连接失败: %v", server.Hostname, err)
h.recordAgentAudit(ac, "upgrade", res, rid, 0, "SSH连接失败: "+formatSSHError(err))
h.recordAgentAudit(ac, "upgrade", res, rid, 0, res, "", "SSH连接失败: "+formatSSHError(err))
continue
}
@@ -631,7 +643,8 @@ func (h *AgentInstallHandler) BatchUpgradeAgent(c *gin.Context) {
if err != nil || !contains(out, "UPGRADE_OK") {
log.Printf("[BatchUpgrade] %s 升级失败: output=%s err=%v", server.Hostname, out, err)
h.recordAgentAudit(ac, "upgrade", res, rid, 0, "升级失败: "+truncateAuditText(out, 300))
h.recordAgentAudit(ac, "upgrade", res, rid, 0,
fmt.Sprintf("server=%s binary=%s", serverURL, binaryFile), out, "升级失败")
continue
}
@@ -647,7 +660,8 @@ func (h *AgentInstallHandler) BatchUpgradeAgent(c *gin.Context) {
"last_check_at": time.Now(),
})
log.Printf("[BatchUpgrade] %s Agent 升级成功", server.Hostname)
h.recordAgentAudit(ac, "upgrade", res, rid, 1, "")
h.recordAgentAudit(ac, "upgrade", res, rid, 1,
fmt.Sprintf("server=%s binary=%s", serverURL, binaryFile), out, "")
successCount++
}
log.Printf("[BatchUpgrade] 批量升级完成: %d/%d 成功", successCount, len(req.ServerIDs))