Files
aiops/backend/controller/automation_script.go
T
LoganandClaude 471353e3d7 feat: 监控告警体系统一重构 + 自动化任务调度引擎 + 监控大屏拆分
告警中心
- 新增统一告警(alert_unified)、监控告警规则(monitor_alert_rule)、指标目录(alert_metric_catalog)
- 通知策略与消息分发(alert_notify_policy/dispatch/message)、静默规则
- 告警引擎按阈值/SNMP 拆分(alert_engine_monitor/snmp)

监控采集
- SNMP 常量/模板与历史指标(snmp templates/constants、snmp_metrics_history、snmp_collect)
- 中间件监控 Redis 采集与种子(middleware_monitor_redis/seed)
- 站点探测 pkg(siteprobe) + 站点视图(site_view)

自动化任务
- cron 调度器(cronscheduler)、脚本运行器(scriptrunner)、系统任务(systask)及运行记录(cron_job_run、system_task)

审计与凭证
- 审计设置(audit_setting) + 历史清理(auditcleaner)

前端
- 监控页面(host/middleware/site/snmp)拆分为子模块并大屏化
- 告警中心多 Tab(规则/指标目录/通知策略/静默)、任务管理组件重构、Agent CLI Markdown 渲染
- 移除旧脚本卡片/详情/日志组件,统一为 ScriptTable/ScriptCodeEditor/TerminalOutput

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-01 23:44:45 +08:00

382 lines
9.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}
rt := scriptrunner.DetectRuntime()
if ok, reason := scriptrunner.CanRun(rt, script.Type); !ok {
c.JSON(http.StatusBadRequest, model.Response{
Code: 400,
Message: reason,
})
return
}
// 在 AIOps 本机执行(embed 单二进制 / Docker 容器内)
output, exitCode, duration := runLocal(script.Type, script.Content, script.Timeout)
finishedAt := time.Now()
status := "success"
if exitCode != 0 {
status = "failed"
}
h.db.Model(&execution).Updates(map[string]interface{}{
"status": status,
"output": output,
"duration": duration,
"finished_at": finishedAt,
})
h.db.Model(&script).UpdateColumn("run_count", script.RunCount+1)
execution.Output = output
execution.Status = status
execution.Duration = duration
execution.FinishedAt = &finishedAt
msg := "执行成功"
if status == "failed" {
msg = "执行失败"
}
c.JSON(http.StatusOK, model.Response{
Code: 200,
Message: msg,
Data: execution,
})
}
// 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,
},
})
}