Files
aiops/backend/controller/alert_rules.go
T
attacker 77c234b060 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 ✅
2026-06-25 20:16:30 +08:00

337 lines
8.4 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 (
"fmt"
"net/http"
"strconv"
"ezops/model"
"github.com/gin-gonic/gin"
)
// 告警规则 CRUD(AlertHandler 方法接收者,与 alert.go 同包共享) // Logan.Li
// ListRules 获取告警规则列表 // Logan.Li
// @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 /alerts/rules [get]
func (h *AlertHandler) ListRules(c *gin.Context) {
var query model.PageQuery
c.ShouldBindQuery(&query)
var total int64
h.db.Model(&model.AlertRule{}).Count(&total)
var rules []model.AlertRule
h.db.Offset(query.GetOffset()).Limit(query.GetLimit()).Find(&rules)
c.JSON(http.StatusOK, model.Response{
Code: 200,
Message: "success",
Data: model.ListResponse{
List: rules,
Total: total,
},
})
}
// CreateRule 创建告警规则 // Logan.Li
// @Summary 创建告警规则
// @Description 创建新告警规则
// @Tags 告警
// @Accept json
// @Produce json
// @Security Bearer
// @Param request body model.CreateAlertRuleRequest true "创建请求"
// @Success 200 {object} model.Response{data=model.AlertRule}
// @Router /alerts/rules [post]
func (h *AlertHandler) CreateRule(c *gin.Context) {
var req model.CreateAlertRuleRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.Response{
Code: 400,
Message: "请求参数错误: " + err.Error(),
})
return
}
rule := model.AlertRule{
Name: req.Name,
Description: req.Description,
Type: req.Type,
Source: req.Source,
Expression: req.Expression,
Severity: req.Severity,
Duration: req.Duration,
Labels: req.Labels,
Annotations: req.Annotations,
NotifyChannels: toJSON(req.NotifyChannels),
Enabled: true,
}
if err := h.db.Create(&rule).Error; err != nil {
c.JSON(http.StatusInternalServerError, model.Response{
Code: 500,
Message: "创建失败",
})
return
}
c.JSON(http.StatusOK, model.Response{
Code: 200,
Message: "创建成功",
Data: rule,
})
}
// UpdateRule 更新告警规则 // Logan.Li
// @Summary 更新告警规则
// @Description 更新指定告警规则
// @Tags 告警
// @Accept json
// @Produce json
// @Security Bearer
// @Param id path int true "规则ID"
// @Param request body model.UpdateAlertRuleRequest true "更新请求"
// @Success 200 {object} model.Response{data=model.AlertRule}
// @Router /alerts/rules/{id} [put]
func (h *AlertHandler) UpdateRule(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var req model.UpdateAlertRuleRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.Response{
Code: 400,
Message: "请求参数错误: " + err.Error(),
})
return
}
var rule model.AlertRule
if err := h.db.First(&rule, id).Error; err != nil {
c.JSON(http.StatusNotFound, model.Response{
Code: 404,
Message: "规则不存在",
})
return
}
updates := map[string]interface{}{}
if req.Name != "" {
updates["name"] = req.Name
}
if req.Description != "" {
updates["description"] = req.Description
}
if req.Expression != "" {
updates["expression"] = req.Expression
}
if req.Severity != "" {
updates["severity"] = req.Severity
}
if req.Duration > 0 {
updates["duration"] = req.Duration
}
if req.Labels != "" {
updates["labels"] = req.Labels
}
if req.Annotations != "" {
updates["annotations"] = req.Annotations
}
if len(req.NotifyChannels) > 0 {
updates["notify_channels"] = toJSON(req.NotifyChannels)
}
if req.Enabled != nil {
updates["enabled"] = *req.Enabled
}
if err := h.db.Model(&rule).Updates(updates).Error; err != nil {
c.JSON(http.StatusInternalServerError, model.Response{
Code: 500,
Message: "更新失败",
})
return
}
h.db.First(&rule, id)
c.JSON(http.StatusOK, model.Response{
Code: 200,
Message: "更新成功",
Data: rule,
})
}
// DeleteRule 删除告警规则 // Logan.Li
// @Summary 删除告警规则
// @Description 删除指定告警规则
// @Tags 告警
// @Produce json
// @Security Bearer
// @Param id path int true "规则ID"
// @Success 200 {object} model.Response
// @Router /alerts/rules/{id} [delete]
func (h *AlertHandler) DeleteRule(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
if err := h.db.Delete(&model.AlertRule{}, id).Error; err != nil {
c.JSON(http.StatusInternalServerError, model.Response{
Code: 500,
Message: "删除失败",
})
return
}
c.JSON(http.StatusOK, model.Response{
Code: 200,
Message: "删除成功",
})
}
// BatchDeleteRules 批量删除告警规则 // Logan.Li
// @Summary 批量删除告警规则
// @Description 批量删除指定告警规则
// @Tags 告警
// @Accept json
// @Produce json
// @Security Bearer
// @Param request body model.IDsRequest true "删除请求"
// @Success 200 {object} model.Response
// @Router /alerts/rules/batch-delete [delete]
func (h *AlertHandler) BatchDeleteRules(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 len(req.IDs) == 0 {
c.JSON(http.StatusBadRequest, model.Response{
Code: 400,
Message: "请选择要删除的规则",
})
return
}
if err := h.db.Delete(&model.AlertRule{}, req.IDs).Error; err != nil {
c.JSON(http.StatusInternalServerError, model.Response{
Code: 500,
Message: "删除失败",
})
return
}
c.JSON(http.StatusOK, model.Response{
Code: 200,
Message: "删除成功",
})
}
// ToggleRule 切换告警规则状态 // Logan.Li
// @Summary 切换告警规则状态
// @Description 启用或禁用告警规则
// @Tags 告警
// @Produce json
// @Security Bearer
// @Param id path int true "规则ID"
// @Success 200 {object} model.Response{data=model.AlertRule}
// @Router /alerts/rules/{id}/toggle [post]
func (h *AlertHandler) ToggleRule(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var rule model.AlertRule
if err := h.db.First(&rule, id).Error; err != nil {
c.JSON(http.StatusNotFound, model.Response{
Code: 404,
Message: "规则不存在",
})
return
}
rule.Enabled = !rule.Enabled
if err := h.db.Save(&rule).Error; err != nil {
c.JSON(http.StatusInternalServerError, model.Response{
Code: 500,
Message: "操作失败",
})
return
}
c.JSON(http.StatusOK, model.Response{
Code: 200,
Message: "操作成功",
Data: rule,
})
}
// QuickSetupRules 一键创建测试告警规则(飞书Webhook测试用) // Logan.Li
// @Summary 一键创建测试告警规则
// @Tags 告警
// @Router /alerts/quick-setup [post]
func (h *AlertHandler) QuickSetupRules(c *gin.Context) {
type ruleDef struct {
Name string `json:"name"`
Metric string `json:"metric"`
Operator string `json:"operator"`
Threshold float64 `json:"threshold"`
Port int `json:"port,omitempty"`
}
var req struct {
Rules []ruleDef `json:"rules"`
}
c.ShouldBindJSON(&req)
// 默认规则:CPU 80%, 内存 80%, 磁盘 90%, TIME_WAIT 15000, CLOSE_WAIT 200, 端口Down, 进程检测
defaultRules := []ruleDef{
{Name: "CPU使用率", Metric: "cpu", Operator: ">", Threshold: 80},
{Name: "内存使用率", Metric: "mem", Operator: ">", Threshold: 80},
{Name: "磁盘使用率", Metric: "disk", Operator: ">", Threshold: 90},
{Name: "TIME_WAIT连接数", Metric: "tcp_time_wait", Operator: ">", Threshold: 15000},
{Name: "CLOSE_WAIT连接数", Metric: "tcp_close_wait", Operator: ">", Threshold: 200},
{Name: "进程异常退出", Metric: "service_process", Operator: "==", Threshold: 0},
}
rules := req.Rules
if len(rules) == 0 {
rules = defaultRules
}
created := 0
for _, def := range rules {
var existing model.AgentRule
h.db.Where("name = ?", def.Name).First(&existing)
if existing.ID > 0 {
continue
}
rule := model.AgentRule{
Name: def.Name,
Metric: def.Metric,
Operator: def.Operator,
Threshold: def.Threshold,
Port: def.Port,
Enabled: true,
}
if err := h.db.Create(&rule).Error; err != nil {
continue
}
created++
}
c.JSON(http.StatusOK, model.Response{
Code: 200,
Message: fmt.Sprintf("已创建 %d 条告警规则", created),
Data: gin.H{"created": created},
})
}