feat(audit): F1+F2 告警静默规则标记完成 + 审计日志CSV导出实现
F1 告警静默规则 [done]: - 后端 alert_silence.go: CRUD + Toggle (onetime/daily/weekly 三模式) - 告警引擎 isSilenced 集成 (alert_engine.go) - 前端 SilenceRuleTab + SilenceDialog - 路由 /alerts/silences 已注册 F2 审计日志导出 [done]: - 重写 ExportAuditLogs: 真正的 CSV 导出 (encoding/csv + UTF-8 BOM) - 支持 module/action/username/startTime/endTime 筛选 - 导出限制 10000 条防溢出,自动记录审计日志 - JSON 格式向后兼容 (format=json) - 前端 exportLogs 传递查询参数 同时包含累积的前期工作: - 目录重构 internal/ -> controller/model/router (v0.55-v0.57) - 后端分层架构 dto/repository/service/utils - 审计模块全功能 (终端审计/操作审计/登录日志) - asciicast v2 录制格式 - SFTP 文件浏览器 - StatStrip 紧凑统计条 - 在线会话自动刷新 - LiveMonitor WebSocket 诊断修复 验证: go build ✅ | npm run build ✅ | Docker 部署 ✅ | 10 回归端点 ✅ | CSV 导出 ✅ | 静默 CRUD ✅
This commit is contained in:
1 parent
e57718998a
commit
77c234b060
314 files changed
+10050
-4065
No files matched your search
@@ -0,0 +1,514 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"ezops/model"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AgentInstallHandler Agent 远程安装处理器 // Logan.Li
|
||||
type AgentInstallHandler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewAgentInstallHandler(db *gorm.DB) *AgentInstallHandler {
|
||||
return &AgentInstallHandler{db: db}
|
||||
}
|
||||
|
||||
// sshDial 复用 webshell.go 的 SSH 连接逻辑(TOFU HostKey 验证)
|
||||
func sshDial(server model.Server) (*ssh.Client, error) {
|
||||
config := &ssh.ClientConfig{
|
||||
User: server.Username,
|
||||
Timeout: 10 * time.Second,
|
||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
// TOFU: 记录并信任首次连接的 HostKey
|
||||
fp := ssh.FingerprintSHA256(key)
|
||||
log.Printf("[AgentInstall] SSH TOFU: trusting %s (%s): %s", server.Hostname, hostname, fp)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
switch server.AuthType {
|
||||
case "key":
|
||||
signer, err := ssh.ParsePrivateKey([]byte(server.SSHKey))
|
||||
if err != nil {
|
||||
config.Auth = []ssh.AuthMethod{ssh.Password(server.Password)}
|
||||
} else {
|
||||
config.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)}
|
||||
}
|
||||
default:
|
||||
config.Auth = []ssh.AuthMethod{ssh.Password(server.Password)}
|
||||
}
|
||||
|
||||
addr := server.IP + ":" + strconv.Itoa(server.Port)
|
||||
return ssh.Dial("tcp", addr, config)
|
||||
}
|
||||
|
||||
// runSSHCommand 在远程主机执行命令并返回输出
|
||||
func runSSHCommand(client *ssh.Client, cmd string) (string, error) {
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
output, err := session.CombinedOutput(cmd)
|
||||
return string(output), err
|
||||
}
|
||||
|
||||
// InstallAgent 远程安装 Agent 到指定服务器
|
||||
// POST /servers/:id/install-agent
|
||||
// 支持传入临时凭证:首次连接成功后自动更新服务器认证凭据
|
||||
func (h *AgentInstallHandler) InstallAgent(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var req struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
SSHKey string `json:"ssh_key"`
|
||||
AuthType string `json:"auth_type"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
// 兼容旧调用方式:token 在 query,新方式在 body
|
||||
c.ShouldBindJSON(&req)
|
||||
if req.Token == "" {
|
||||
req.Token = c.Query("token")
|
||||
}
|
||||
if req.AuthType == "" {
|
||||
req.AuthType = "password"
|
||||
}
|
||||
|
||||
var server model.Server
|
||||
if err := h.db.First(&server, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "服务器不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
if server.Status != "running" {
|
||||
// 允许安装,不强制 running 状态
|
||||
}
|
||||
|
||||
// 如果请求中提供了凭据,使用请求凭据覆盖服务器存储的凭据进行 SSH
|
||||
effectiveServer := server
|
||||
if req.Username != "" {
|
||||
effectiveServer.Username = req.Username
|
||||
}
|
||||
if req.Password != "" {
|
||||
effectiveServer.Password = req.Password
|
||||
}
|
||||
if req.SSHKey != "" {
|
||||
effectiveServer.SSHKey = req.SSHKey
|
||||
effectiveServer.AuthType = "key"
|
||||
}
|
||||
if req.AuthType != "" && req.AuthType != effectiveServer.AuthType {
|
||||
effectiveServer.AuthType = req.AuthType
|
||||
}
|
||||
|
||||
// 1. SSH 连接目标主机
|
||||
client, err := sshDial(effectiveServer)
|
||||
if err != nil {
|
||||
log.Printf("[AgentInstall] SSH连接失败 %s: %v", server.IP, err)
|
||||
c.JSON(http.StatusOK, model.Response{
|
||||
Code: 500,
|
||||
Message: "SSH 连接失败: " + formatSSHError(err),
|
||||
})
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// 首次连接成功 → 如果服务器之前未配置凭据,自动保存使用的凭据
|
||||
if (server.Password == "" && server.SSHKey == "") || server.Username == "" {
|
||||
updates := map[string]interface{}{
|
||||
"username": effectiveServer.Username,
|
||||
"auth_type": effectiveServer.AuthType,
|
||||
}
|
||||
if effectiveServer.Password != "" {
|
||||
updates["password"] = effectiveServer.Password
|
||||
}
|
||||
if effectiveServer.SSHKey != "" {
|
||||
updates["ssh_key"] = effectiveServer.SSHKey
|
||||
}
|
||||
if server.Username == "" {
|
||||
updates["username"] = effectiveServer.Username
|
||||
}
|
||||
h.db.Model(&server).Updates(updates)
|
||||
log.Printf("[AgentInstall] %s 首次连接成功,已自动关联凭证", server.Hostname)
|
||||
}
|
||||
|
||||
// 2. 检测系统架构
|
||||
archOut, _ := runSSHCommand(client, "uname -m")
|
||||
arch := "amd64"
|
||||
if len(archOut) > 0 && (contains(archOut, "aarch64") || contains(archOut, "arm64")) {
|
||||
arch = "arm64"
|
||||
}
|
||||
|
||||
// 3. 构造安装命令(使用 Agent API 服务地址)
|
||||
// 从请求头获取后端可访问地址
|
||||
serverURL := fmt.Sprintf("http://%s", c.Request.Host)
|
||||
token := c.Query("token")
|
||||
|
||||
installCmd := fmt.Sprintf(
|
||||
`curl -fsSL %s/agent/install.sh | sudo bash -s -- --server %s --token %s`,
|
||||
serverURL, serverURL, token,
|
||||
)
|
||||
|
||||
// 4. 执行安装
|
||||
installOut, err := runSSHCommand(client, installCmd)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, model.Response{
|
||||
Code: 500,
|
||||
Message: "安装命令执行失败(可能需要 sudo 权限或 curl 未安装)",
|
||||
Data: installOut,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 5. 验证安装是否成功
|
||||
verifyOut, _ := runSSHCommand(client, "systemctl is-active aiops-agent 2>/dev/null || echo 'not_found'")
|
||||
installed := contains(verifyOut, "active") || contains(verifyOut, "running")
|
||||
|
||||
if installed {
|
||||
// 更新服务器 Agent 状态(版本号:安装成功后由 Agent 下一次上报自动更新)
|
||||
h.db.Model(&server).Updates(map[string]interface{}{
|
||||
"agent_status": "online",
|
||||
"agent_version": "v0.1.0",
|
||||
"last_check_at": time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, model.Response{
|
||||
Code: 200,
|
||||
Message: func() string {
|
||||
if installed {
|
||||
return "Agent 安装成功,已自动启动并设置开机自启"
|
||||
}
|
||||
return "安装命令已执行,但 Agent 服务未检测到运行(请查看日志排查)"
|
||||
}(),
|
||||
Data: gin.H{
|
||||
"installed": installed,
|
||||
"arch": arch,
|
||||
"output": installOut,
|
||||
"verify": verifyOut,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// installOne 安装单个服务器的 Agent(内部方法)
|
||||
func (h *AgentInstallHandler) installOne(server model.Server, serverURL, token string) (bool, string, string, error) {
|
||||
client, err := sshDial(server)
|
||||
if err != nil {
|
||||
return false, "", "", fmt.Errorf("SSH连接失败: %w", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
if server.Status != "running" {
|
||||
return false, "", "", fmt.Errorf("服务器未运行")
|
||||
}
|
||||
|
||||
installCmd := fmt.Sprintf(
|
||||
`curl -fsSL %s/agent/install.sh | sudo bash -s -- --server %s --token %s`,
|
||||
serverURL, serverURL, token,
|
||||
)
|
||||
|
||||
installOut, err := runSSHCommand(client, installCmd)
|
||||
if err != nil {
|
||||
return false, installOut, "", err
|
||||
}
|
||||
|
||||
verifyOut, _ := runSSHCommand(client, "systemctl is-active aiops-agent 2>/dev/null || echo 'not_found'")
|
||||
installed := contains(verifyOut, "active") || contains(verifyOut, "running")
|
||||
|
||||
if installed {
|
||||
h.db.Model(&server).Updates(map[string]interface{}{
|
||||
"agent_status": "online",
|
||||
"agent_version": "v0.1.0",
|
||||
"last_check_at": time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
return installed, installOut, verifyOut, nil
|
||||
}
|
||||
|
||||
// BatchInstallAgent 批量安装 Agent(异步执行,立即返回)// Logan.Li
|
||||
// POST /servers/install-agent
|
||||
func (h *AgentInstallHandler) BatchInstallAgent(c *gin.Context) {
|
||||
var req struct {
|
||||
ServerIDs []uint `json:"server_ids"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil || len(req.ServerIDs) == 0 {
|
||||
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "请提供要安装的服务器 ID 列表"})
|
||||
return
|
||||
}
|
||||
|
||||
serverURL := fmt.Sprintf("http://%s", c.Request.Host)
|
||||
token := c.Query("token")
|
||||
|
||||
// 异步执行:立即返回,后台 goroutine 逐台安装
|
||||
go func() {
|
||||
successCount := 0
|
||||
for _, sid := range req.ServerIDs {
|
||||
var server model.Server
|
||||
if err := h.db.First(&server, sid).Error; err != nil {
|
||||
log.Printf("[BatchInstall] 服务器 %d 不存在,跳过", sid)
|
||||
continue
|
||||
}
|
||||
installed, _, _, err := h.installOne(server, serverURL, token)
|
||||
if err != nil {
|
||||
log.Printf("[BatchInstall] %s (%s) 安装失败: %v", server.Hostname, server.IP, err)
|
||||
} else {
|
||||
successCount++
|
||||
if installed {
|
||||
log.Printf("[BatchInstall] %s Agent 安装成功", server.Hostname)
|
||||
} else {
|
||||
log.Printf("[BatchInstall] %s Agent 安装命令已执行但服务未检测到运行", server.Hostname)
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Printf("[BatchInstall] 批量安装完成: %d/%d 成功", successCount, len(req.ServerIDs))
|
||||
}()
|
||||
|
||||
c.JSON(http.StatusOK, model.Response{
|
||||
Code: 200,
|
||||
Message: fmt.Sprintf("已提交异步安装任务,共 %d 台主机,正在后台执行…", len(req.ServerIDs)),
|
||||
Data: gin.H{"total": len(req.ServerIDs), "async": true},
|
||||
})
|
||||
}
|
||||
|
||||
// UninstallAgent 远程卸载 Agent
|
||||
// POST /api/v1/agent/uninstall
|
||||
func (h *AgentInstallHandler) UninstallAgent(c *gin.Context) {
|
||||
var req struct {
|
||||
Hostname string `json:"hostname"`
|
||||
ServerID *uint `json:"server_id"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "参数错误"})
|
||||
return
|
||||
}
|
||||
|
||||
var server model.Server
|
||||
if req.ServerID != nil {
|
||||
if err := h.db.First(&server, *req.ServerID).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "服务器不存在"})
|
||||
return
|
||||
}
|
||||
} else if req.Hostname != "" {
|
||||
if err := h.db.Where("hostname = ? OR ip = ?", req.Hostname, req.Hostname).First(&server).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "未找到服务器: " + req.Hostname})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "请提供 hostname 或 server_id"})
|
||||
return
|
||||
}
|
||||
|
||||
// 无 SSH 凭据的主机(自动注册的)直接改本地状态
|
||||
if server.Username == "" || server.Password == "" {
|
||||
h.db.Model(&server).Updates(map[string]interface{}{
|
||||
"agent_status": "offline", "agent_version": "",
|
||||
})
|
||||
c.JSON(http.StatusOK, model.Response{Code: 200, Message: "已标记为离线(该主机无SSH凭据,无法远程卸载,请手动执行 systemctl stop aiops-agent)"})
|
||||
return
|
||||
}
|
||||
|
||||
client, err := sshDial(server)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, model.Response{Code: 500, Message: "SSH连接失败: " + formatSSHError(err)})
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
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")
|
||||
|
||||
h.db.Model(&server).Updates(map[string]interface{}{
|
||||
"agent_status": "offline", "agent_version": "",
|
||||
})
|
||||
|
||||
c.JSON(http.StatusOK, model.Response{Code: 200, Message: fmt.Sprintf("已卸载 %s (%s) 的 Agent", server.Hostname, server.IP)})
|
||||
}
|
||||
|
||||
// BatchUninstallAgent 批量卸载 Agent(异步执行,立即返回)// Logan.Li
|
||||
// POST /api/v1/agent/batch-uninstall
|
||||
func (h *AgentInstallHandler) BatchUninstallAgent(c *gin.Context) {
|
||||
var req struct {
|
||||
ServerIDs []uint `json:"server_ids"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil || len(req.ServerIDs) == 0 {
|
||||
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "请提供服务器 ID 列表"})
|
||||
return
|
||||
}
|
||||
|
||||
// 异步执行:立即返回,后台逐台卸载
|
||||
go func() {
|
||||
successCount := 0
|
||||
for _, id := range req.ServerIDs {
|
||||
var server model.Server
|
||||
if err := h.db.First(&server, id).Error; err != nil {
|
||||
log.Printf("[BatchUninstall] 服务器 %d 不存在,跳过", id)
|
||||
continue
|
||||
}
|
||||
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)
|
||||
successCount++
|
||||
continue
|
||||
}
|
||||
client, err := sshDial(server)
|
||||
if err != nil {
|
||||
log.Printf("[BatchUninstall] %s SSH失败: %v", server.Hostname, 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)
|
||||
successCount++
|
||||
}
|
||||
log.Printf("[BatchUninstall] 批量卸载完成: %d/%d 成功", successCount, len(req.ServerIDs))
|
||||
}()
|
||||
|
||||
c.JSON(http.StatusOK, model.Response{
|
||||
Code: 200,
|
||||
Message: fmt.Sprintf("已提交异步卸载任务,共 %d 台主机,正在后台执行…", len(req.ServerIDs)),
|
||||
Data: gin.H{"total": len(req.ServerIDs), "async": true},
|
||||
})
|
||||
}
|
||||
|
||||
// BatchUpgradeAgent 批量升级 Agent 到最新版本(SSH下载新二进制 + 重启服务)
|
||||
// POST /api/v1/agent/batch-upgrade
|
||||
func (h *AgentInstallHandler) BatchUpgradeAgent(c *gin.Context) {
|
||||
var req struct {
|
||||
ServerIDs []uint `json:"server_ids"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil || len(req.ServerIDs) == 0 {
|
||||
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "请提供要升级的服务器 ID 列表"})
|
||||
return
|
||||
}
|
||||
|
||||
serverURL := fmt.Sprintf("http://%s", c.Request.Host)
|
||||
|
||||
// 异步执行:立即返回,后台逐台升级
|
||||
go func() {
|
||||
successCount := 0
|
||||
for _, sid := range req.ServerIDs {
|
||||
var server model.Server
|
||||
if err := h.db.First(&server, sid).Error; err != nil {
|
||||
log.Printf("[BatchUpgrade] 服务器 %d 不存在,跳过", sid)
|
||||
continue
|
||||
}
|
||||
if server.Username == "" || (server.Password == "" && server.SSHKey == "") {
|
||||
log.Printf("[BatchUpgrade] %s 无SSH凭据,跳过", server.Hostname)
|
||||
continue
|
||||
}
|
||||
|
||||
// 检测架构
|
||||
client, err := sshDial(server)
|
||||
if err != nil {
|
||||
log.Printf("[BatchUpgrade] %s SSH连接失败: %v", server.Hostname, err)
|
||||
continue
|
||||
}
|
||||
|
||||
archOut, _ := runSSHCommand(client, "uname -m")
|
||||
binaryFile := "aiops-agent-linux-amd64"
|
||||
if len(archOut) > 0 && (contains(archOut, "aarch64") || contains(archOut, "arm64")) {
|
||||
binaryFile = "aiops-agent-linux-arm64"
|
||||
}
|
||||
|
||||
// 下载新二进制 + 获取版本号 + 重启服务
|
||||
upgradeScript := fmt.Sprintf(
|
||||
`curl -fsSL -o /tmp/aiops-agent %s/api/agent/bin/%s && chmod +x /tmp/aiops-agent && systemctl stop aiops-agent 2>/dev/null; mv /tmp/aiops-agent /usr/local/bin/aiops-agent && systemctl start aiops-agent; ver=$(/usr/local/bin/aiops-agent output 2>/dev/null | grep 'agent.version' | awk -F'[|[|]' '{print $2}' | tr -d ' '); echo "UPGRADE_OK ver=$ver"`,
|
||||
serverURL, binaryFile,
|
||||
)
|
||||
|
||||
out, err := runSSHCommand(client, upgradeScript)
|
||||
client.Close()
|
||||
|
||||
if err != nil || !contains(out, "UPGRADE_OK") {
|
||||
log.Printf("[BatchUpgrade] %s 升级失败: output=%s err=%v", server.Hostname, out, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 从输出提取版本号
|
||||
agentVersion := "unknown"
|
||||
parts := strings.Split(out, "ver=")
|
||||
if len(parts) > 1 {
|
||||
agentVersion = strings.TrimSpace(parts[1])
|
||||
}
|
||||
|
||||
h.db.Model(&server).Updates(map[string]interface{}{
|
||||
"agent_version": agentVersion,
|
||||
"last_check_at": time.Now(),
|
||||
})
|
||||
log.Printf("[BatchUpgrade] %s Agent 升级成功", server.Hostname)
|
||||
successCount++
|
||||
}
|
||||
log.Printf("[BatchUpgrade] 批量升级完成: %d/%d 成功", successCount, len(req.ServerIDs))
|
||||
}()
|
||||
|
||||
c.JSON(http.StatusOK, model.Response{
|
||||
Code: 200,
|
||||
Message: fmt.Sprintf("已提交异步升级任务,共 %d 台主机,正在后台执行…", len(req.ServerIDs)),
|
||||
Data: gin.H{"total": len(req.ServerIDs), "async": true},
|
||||
})
|
||||
}
|
||||
|
||||
// RemoteExec 直连 SSH 执行命令(即时返回,无轮询)
|
||||
// POST /api/v1/agent/exec
|
||||
func (h *AgentInstallHandler) RemoteExec(c *gin.Context) {
|
||||
var req struct {
|
||||
Hostname string `json:"hostname"`
|
||||
Command string `json:"command"`
|
||||
Timeout int `json:"timeout"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil || req.Hostname == "" || req.Command == "" {
|
||||
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "请提供 hostname 和 command"})
|
||||
return
|
||||
}
|
||||
if req.Timeout <= 0 {
|
||||
req.Timeout = 30
|
||||
}
|
||||
|
||||
var server model.Server
|
||||
if err := h.db.Where("hostname = ? OR ip = ?", req.Hostname, req.Hostname).First(&server).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "未找到服务器: " + req.Hostname})
|
||||
return
|
||||
}
|
||||
|
||||
client, err := sshDial(server)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, model.Response{Code: 500, Message: "SSH连接失败: " + err.Error()})
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
type result struct {
|
||||
out string
|
||||
err error
|
||||
}
|
||||
done := make(chan result, 1)
|
||||
go func() {
|
||||
out, err := runSSHCommand(client, req.Command)
|
||||
done <- result{out, err}
|
||||
}()
|
||||
|
||||
select {
|
||||
case r := <-done:
|
||||
if r.err != nil {
|
||||
c.JSON(http.StatusOK, model.Response{Code: 200, Message: "执行完成", Data: gin.H{"output": r.out, "error": r.err.Error()}})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, model.Response{Code: 200, Message: "执行成功", Data: gin.H{"output": r.out}})
|
||||
case <-time.After(time.Duration(req.Timeout) * time.Second):
|
||||
c.JSON(http.StatusOK, model.Response{Code: 408, Message: fmt.Sprintf("命令执行超时(%ds)", req.Timeout)})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user