- 支持 Windows Agent 构建、安装脚本和统一上报协议 - 增强 RDP 剪贴板、虚拟盘和文件传输能力 - 新增 AIOps 上下文工具、告警 RCA trace 落库和回放接口 - 重构 docs 模块文档并补充终端、堡垒机、监控、AI Agent 设计
378 lines
9.4 KiB
Go
378 lines
9.4 KiB
Go
package controller
|
||
|
||
import (
|
||
"net/http"
|
||
"strconv"
|
||
"time"
|
||
|
||
"ezops/middleware"
|
||
"ezops/model"
|
||
"ezops/pkg/scriptrunner"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// GetScriptRuntime 获取本机脚本运行时能力(Shell/Python/PowerShell) // Logan.Li
|
||
func (h *AutomationHandler) GetScriptRuntime(c *gin.Context) {
|
||
rt := scriptrunner.DetectRuntime()
|
||
c.JSON(http.StatusOK, model.Response{
|
||
Code: 200,
|
||
Message: "success",
|
||
Data: rt,
|
||
})
|
||
}
|
||
|
||
// ListScripts 获取脚本列表
|
||
// @Summary 获取脚本列表
|
||
// @Description 获取所有脚本列表
|
||
// @Tags 自动化运维
|
||
// @Produce json
|
||
// @Security Bearer
|
||
// @Param page query int false "页码"
|
||
// @Param page_size query int false "每页数量"
|
||
// @Success 200 {object} model.Response{data=model.ListResponse}
|
||
// @Router /scripts [get]
|
||
func (h *AutomationHandler) ListScripts(c *gin.Context) {
|
||
var query model.PageQuery
|
||
c.ShouldBindQuery(&query)
|
||
|
||
var total int64
|
||
h.db.Model(&model.Script{}).Count(&total)
|
||
|
||
var scripts []model.Script
|
||
h.db.Offset(query.GetOffset()).Limit(query.GetLimit()).Find(&scripts)
|
||
|
||
c.JSON(http.StatusOK, model.Response{
|
||
Code: 200,
|
||
Message: "success",
|
||
Data: model.ListResponse{
|
||
List: scripts,
|
||
Total: total,
|
||
},
|
||
})
|
||
}
|
||
|
||
// GetScript 获取脚本详情
|
||
// @Summary 获取脚本详情
|
||
// @Description 获取指定脚本的详细信息
|
||
// @Tags 自动化运维
|
||
// @Produce json
|
||
// @Security Bearer
|
||
// @Param id path int true "脚本ID"
|
||
// @Success 200 {object} model.Response{data=model.Script}
|
||
// @Router /scripts/{id} [get]
|
||
func (h *AutomationHandler) GetScript(c *gin.Context) {
|
||
id, _ := strconv.Atoi(c.Param("id"))
|
||
|
||
var script model.Script
|
||
if err := h.db.First(&script, id).Error; err != nil {
|
||
c.JSON(http.StatusNotFound, model.Response{
|
||
Code: 404,
|
||
Message: "脚本不存在",
|
||
})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, model.Response{
|
||
Code: 200,
|
||
Message: "success",
|
||
Data: script,
|
||
})
|
||
}
|
||
|
||
// CreateScript 创建脚本
|
||
// @Summary 创建脚本
|
||
// @Description 创建新脚本
|
||
// @Tags 自动化运维
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security Bearer
|
||
// @Param request body model.CreateScriptRequest true "创建请求"
|
||
// @Success 200 {object} model.Response{data=model.Script}
|
||
// @Router /scripts [post]
|
||
func (h *AutomationHandler) CreateScript(c *gin.Context) {
|
||
var req model.CreateScriptRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, model.Response{
|
||
Code: 400,
|
||
Message: "请求参数错误: " + err.Error(),
|
||
})
|
||
return
|
||
}
|
||
|
||
script := model.Script{
|
||
Name: req.Name,
|
||
Code: req.Code,
|
||
Description: req.Description,
|
||
Type: req.Type,
|
||
Content: req.Content,
|
||
Params: req.Params,
|
||
Timeout: req.Timeout,
|
||
}
|
||
|
||
if script.Type == "" {
|
||
script.Type = "shell"
|
||
}
|
||
if script.Timeout == 0 {
|
||
script.Timeout = 300
|
||
}
|
||
if script.Code == "" {
|
||
script.Code = randomCode("sc")
|
||
}
|
||
script.CreatedBy = middleware.GetUserID(c)
|
||
|
||
if err := h.db.Create(&script).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, model.Response{
|
||
Code: 500,
|
||
Message: "创建失败",
|
||
})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, model.Response{
|
||
Code: 200,
|
||
Message: "创建成功",
|
||
Data: script,
|
||
})
|
||
}
|
||
|
||
// UpdateScript 更新脚本
|
||
// @Summary 更新脚本
|
||
// @Description 更新指定脚本
|
||
// @Tags 自动化运维
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security Bearer
|
||
// @Param id path int true "脚本ID"
|
||
// @Param request body model.CreateScriptRequest true "更新请求"
|
||
// @Success 200 {object} model.Response{data=model.Script}
|
||
// @Router /scripts/{id} [put]
|
||
func (h *AutomationHandler) UpdateScript(c *gin.Context) {
|
||
id, _ := strconv.Atoi(c.Param("id"))
|
||
|
||
var req model.CreateScriptRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, model.Response{
|
||
Code: 400,
|
||
Message: "请求参数错误: " + err.Error(),
|
||
})
|
||
return
|
||
}
|
||
|
||
var script model.Script
|
||
if err := h.db.First(&script, id).Error; err != nil {
|
||
c.JSON(http.StatusNotFound, model.Response{
|
||
Code: 404,
|
||
Message: "脚本不存在",
|
||
})
|
||
return
|
||
}
|
||
|
||
updates := map[string]interface{}{
|
||
"name": req.Name,
|
||
"description": req.Description,
|
||
"type": req.Type,
|
||
"content": req.Content,
|
||
"params": req.Params,
|
||
"timeout": req.Timeout,
|
||
"version": script.Version + 1,
|
||
}
|
||
|
||
if err := h.db.Model(&script).Updates(updates).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, model.Response{
|
||
Code: 500,
|
||
Message: "更新失败",
|
||
})
|
||
return
|
||
}
|
||
|
||
h.db.First(&script, id)
|
||
|
||
c.JSON(http.StatusOK, model.Response{
|
||
Code: 200,
|
||
Message: "更新成功",
|
||
Data: script,
|
||
})
|
||
}
|
||
|
||
// DeleteScript 删除脚本
|
||
// @Summary 删除脚本
|
||
// @Description 删除指定脚本
|
||
// @Tags 自动化运维
|
||
// @Produce json
|
||
// @Security Bearer
|
||
// @Param id path int true "脚本ID"
|
||
// @Success 200 {object} model.Response
|
||
// @Router /scripts/{id} [delete]
|
||
func (h *AutomationHandler) DeleteScript(c *gin.Context) {
|
||
id, _ := strconv.Atoi(c.Param("id"))
|
||
|
||
if err := h.db.Delete(&model.Script{}, id).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, model.Response{
|
||
Code: 500,
|
||
Message: "删除失败",
|
||
})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, model.Response{
|
||
Code: 200,
|
||
Message: "删除成功",
|
||
})
|
||
}
|
||
|
||
// BatchDeleteScripts 批量删除脚本
|
||
// @Summary 批量删除脚本
|
||
// @Description 批量删除脚本
|
||
// @Tags 自动化运维
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security Bearer
|
||
// @Param request body model.IDsRequest true "删除请求"
|
||
// @Success 200 {object} model.Response
|
||
// @Router /scripts/batch-delete [post]
|
||
func (h *AutomationHandler) BatchDeleteScripts(c *gin.Context) {
|
||
var req model.IDsRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, model.Response{
|
||
Code: 400,
|
||
Message: "请求参数错误",
|
||
})
|
||
return
|
||
}
|
||
|
||
if err := h.db.Delete(&model.Script{}, req.IDs).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, model.Response{
|
||
Code: 500,
|
||
Message: "删除失败",
|
||
})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, model.Response{
|
||
Code: 200,
|
||
Message: "删除成功",
|
||
})
|
||
}
|
||
|
||
// ExecuteScript 执行脚本
|
||
// @Summary 执行脚本
|
||
// @Description 在目标服务器上执行脚本
|
||
// @Tags 自动化运维
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security Bearer
|
||
// @Param id path int true "脚本ID"
|
||
// @Param request body model.ExecuteScriptRequest true "执行请求"
|
||
// @Success 200 {object} model.Response{data=model.TaskExecution}
|
||
// @Router /scripts/{id}/execute [post]
|
||
func (h *AutomationHandler) ExecuteScript(c *gin.Context) {
|
||
id, _ := strconv.Atoi(c.Param("id"))
|
||
|
||
var script model.Script
|
||
if err := h.db.First(&script, id).Error; err != nil {
|
||
c.JSON(http.StatusNotFound, model.Response{
|
||
Code: 404,
|
||
Message: "脚本不存在",
|
||
})
|
||
return
|
||
}
|
||
|
||
// The request body is optional — when omitted we execute locally.
|
||
var req model.ExecuteScriptRequest
|
||
_ = c.ShouldBindJSON(&req)
|
||
|
||
now := time.Now()
|
||
execution := model.TaskExecution{
|
||
TaskType: "script",
|
||
TaskID: uint(id),
|
||
TaskName: script.Name,
|
||
Status: "running",
|
||
TargetType: req.TargetType,
|
||
StartedAt: &now,
|
||
}
|
||
if err := h.db.Create(&execution).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, model.Response{
|
||
Code: 500,
|
||
Message: "创建执行记录失败",
|
||
})
|
||
return
|
||
}
|
||
|
||
go h.finishScriptExecution(script, execution.ID)
|
||
|
||
c.JSON(http.StatusOK, model.Response{
|
||
Code: 200,
|
||
Message: "已提交后台执行",
|
||
Data: execution,
|
||
})
|
||
}
|
||
|
||
// finishScriptExecution 在后台完成脚本执行,HTTP 请求只负责提交任务 // Logan.Li
|
||
func (h *AutomationHandler) finishScriptExecution(script model.Script, executionID uint) {
|
||
rt := scriptrunner.DetectRuntime()
|
||
status := "success"
|
||
output := ""
|
||
exitCode := 0
|
||
duration := 0
|
||
if ok, reason := scriptrunner.CanRun(rt, script.Type); !ok {
|
||
status = "failed"
|
||
exitCode = -1
|
||
output = reason + "\n"
|
||
} else {
|
||
output, exitCode, duration = runLocal(script.Type, script.Content, script.Timeout)
|
||
if exitCode != 0 {
|
||
status = "failed"
|
||
}
|
||
}
|
||
finishedAt := time.Now()
|
||
h.db.Model(&model.TaskExecution{}).Where("id = ?", executionID).Updates(map[string]interface{}{
|
||
"status": status,
|
||
"output": output,
|
||
"duration": duration,
|
||
"finished_at": finishedAt,
|
||
})
|
||
h.db.Model(&model.Script{}).Where("id = ?", script.ID).UpdateColumn("run_count", script.RunCount+1)
|
||
_ = exitCode
|
||
}
|
||
|
||
// ListExecutions 获取任务执行记录列表
|
||
// @Summary 获取任务执行记录
|
||
// @Description 获取脚本/定时任务的执行历史记录(支持 task_type / task_id 筛选)
|
||
// @Tags 自动化运维
|
||
// @Produce json
|
||
// @Security Bearer
|
||
// @Param page query int false "页码"
|
||
// @Param page_size query int false "每页数量"
|
||
// @Param task_type query string false "任务类型 (cron/script)"
|
||
// @Param task_id query string false "任务ID"
|
||
// @Success 200 {object} model.Response{data=model.ListResponse}
|
||
// @Router /task-executions [get]
|
||
func (h *AutomationHandler) ListExecutions(c *gin.Context) {
|
||
var query model.PageQuery
|
||
c.ShouldBindQuery(&query)
|
||
|
||
db := h.db.Model(&model.TaskExecution{})
|
||
if taskType := c.Query("task_type"); taskType != "" {
|
||
db = db.Where("task_type = ?", taskType)
|
||
}
|
||
if taskID := c.Query("task_id"); taskID != "" {
|
||
db = db.Where("task_id = ?", taskID)
|
||
}
|
||
|
||
var total int64
|
||
db.Count(&total)
|
||
|
||
var execs []model.TaskExecution
|
||
db.Order("created_at DESC").Offset(query.GetOffset()).Limit(query.GetLimit()).Find(&execs)
|
||
|
||
c.JSON(http.StatusOK, model.Response{
|
||
Code: 200,
|
||
Message: "success",
|
||
Data: model.ListResponse{
|
||
List: execs,
|
||
Total: total,
|
||
},
|
||
})
|
||
}
|