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 ✅
206 lines
6.5 KiB
Go
206 lines
6.5 KiB
Go
package controller
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"time"
|
|
|
|
"ezops/model"
|
|
"ezops/pkg/zabbix"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// ZabbixDSHandler Zabbix 数据源管理 handler // Logan.Li
|
|
type ZabbixDSHandler struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewZabbixDSHandler 创建 Zabbix 数据源 handler
|
|
func NewZabbixDSHandler(db *gorm.DB) *ZabbixDSHandler {
|
|
return &ZabbixDSHandler{db: db}
|
|
}
|
|
|
|
// ============================================
|
|
// CRUD
|
|
// ============================================
|
|
|
|
// List 数据源列表
|
|
// GET /api/v1/zabbix-datasources
|
|
func (h *ZabbixDSHandler) List(c *gin.Context) {
|
|
var sources []model.ZabbixDataSource
|
|
if err := h.db.Order("is_default DESC, id ASC").Find(&sources).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "查询失败: " + err.Error()})
|
|
return
|
|
}
|
|
if sources == nil {
|
|
sources = []model.ZabbixDataSource{}
|
|
}
|
|
c.JSON(http.StatusOK, model.Response{Code: 200, Data: sources})
|
|
}
|
|
|
|
// Get 获取单个数据源
|
|
// GET /api/v1/zabbix-datasources/:id
|
|
func (h *ZabbixDSHandler) Get(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var ds model.ZabbixDataSource
|
|
if err := h.db.First(&ds, id).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "数据源不存在"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, model.Response{Code: 200, Data: ds})
|
|
}
|
|
|
|
// Create 创建数据源
|
|
// POST /api/v1/zabbix-datasources
|
|
func (h *ZabbixDSHandler) Create(c *gin.Context) {
|
|
var req model.CreateZabbixDSReq
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "参数错误: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
if req.IsDefault {
|
|
h.db.Model(&model.ZabbixDataSource{}).Where("is_default = ?", true).Update("is_default", false)
|
|
}
|
|
|
|
ds := model.ZabbixDataSource{
|
|
Name: req.Name,
|
|
URL: req.URL,
|
|
Username: req.Username,
|
|
Password: req.Password,
|
|
TrendsAfter: req.TrendsAfter,
|
|
TrendsRange: req.TrendsRange,
|
|
TrendsCacheTTL: req.TrendsCacheTTL,
|
|
TrendsTimeout: req.TrendsTimeout,
|
|
DirectDB: req.DirectDB,
|
|
DisableAcknowledges: req.DisableAcknowledges,
|
|
DisableDataAlignment: req.DisableDataAlignment,
|
|
IsDefault: req.IsDefault,
|
|
Status: 1,
|
|
}
|
|
|
|
if ds.TrendsCacheTTL <= 0 {
|
|
ds.TrendsCacheTTL = 3600
|
|
}
|
|
if ds.TrendsTimeout <= 0 {
|
|
ds.TrendsTimeout = 30
|
|
}
|
|
|
|
if err := h.db.Create(&ds).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "创建失败: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, model.Response{Code: 200, Data: ds, Message: "创建成功"})
|
|
}
|
|
|
|
// Update 更新数据源
|
|
// PUT /api/v1/zabbix-datasources/:id
|
|
func (h *ZabbixDSHandler) Update(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var ds model.ZabbixDataSource
|
|
if err := h.db.First(&ds, id).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "数据源不存在"})
|
|
return
|
|
}
|
|
|
|
var req model.UpdateZabbixDSReq
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "参数错误: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
updates := map[string]interface{}{}
|
|
if req.Name != nil { updates["name"] = *req.Name }
|
|
if req.URL != nil { updates["url"] = *req.URL }
|
|
if req.Username != nil { updates["username"] = *req.Username }
|
|
if req.Password != nil && *req.Password != "" { updates["password"] = *req.Password }
|
|
if req.TrendsAfter != nil { updates["trends_after"] = *req.TrendsAfter }
|
|
if req.TrendsRange != nil { updates["trends_range"] = *req.TrendsRange }
|
|
if req.TrendsCacheTTL != nil { updates["trends_cache_ttl"] = *req.TrendsCacheTTL }
|
|
if req.TrendsTimeout != nil { updates["trends_timeout"] = *req.TrendsTimeout }
|
|
if req.DirectDB != nil { updates["direct_db"] = *req.DirectDB }
|
|
if req.DisableAcknowledges != nil { updates["disable_acknowledges"] = *req.DisableAcknowledges }
|
|
if req.DisableDataAlignment != nil { updates["disable_data_alignment"] = *req.DisableDataAlignment }
|
|
if req.IsDefault != nil {
|
|
updates["is_default"] = *req.IsDefault
|
|
if *req.IsDefault {
|
|
h.db.Model(&model.ZabbixDataSource{}).Where("is_default = ? AND id != ?", true, id).Update("is_default", false)
|
|
}
|
|
}
|
|
if req.Status != nil { updates["status"] = *req.Status }
|
|
|
|
if len(updates) > 0 {
|
|
if err := h.db.Model(&ds).Updates(updates).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "更新失败: " + err.Error()})
|
|
return
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, model.Response{Code: 200, Message: "更新成功"})
|
|
}
|
|
|
|
// Delete 删除数据源
|
|
// DELETE /api/v1/zabbix-datasources/:id
|
|
func (h *ZabbixDSHandler) Delete(c *gin.Context) {
|
|
id := c.Param("id")
|
|
if err := h.db.Delete(&model.ZabbixDataSource{}, id).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "删除失败: " + err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, model.Response{Code: 200, Message: "已删除"})
|
|
}
|
|
|
|
// SetDefault 设为默认
|
|
// POST /api/v1/zabbix-datasources/:id/default
|
|
func (h *ZabbixDSHandler) SetDefault(c *gin.Context) {
|
|
id := c.Param("id")
|
|
h.db.Model(&model.ZabbixDataSource{}).Where("is_default = ?", true).Update("is_default", false)
|
|
if err := h.db.Model(&model.ZabbixDataSource{}).Where("id = ?", id).Update("is_default", true).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "设置失败: " + err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, model.Response{Code: 200, Message: "已设为默认"})
|
|
}
|
|
|
|
// TestConnection 测试 Zabbix 连接
|
|
// POST /api/v1/zabbix-datasources/test
|
|
func (h *ZabbixDSHandler) TestConnection(c *gin.Context) {
|
|
var req model.TestZabbixDSReq
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "参数错误: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
client := zabbix.NewClient(req.URL, req.Username, req.Password,
|
|
zabbix.WithHTTPTimeout(10*time.Second),
|
|
)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
|
|
defer cancel()
|
|
|
|
if err := client.Auth(ctx); err != nil {
|
|
c.JSON(http.StatusOK, model.Response{
|
|
Code: 200,
|
|
Data: map[string]interface{}{
|
|
"success": false,
|
|
"message": "认证失败: " + err.Error(),
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
token := client.GetAuthToken()
|
|
|
|
c.JSON(http.StatusOK, model.Response{
|
|
Code: 200,
|
|
Data: map[string]interface{}{
|
|
"success": true,
|
|
"message": "连接成功,Zabbix 认证通过",
|
|
"session_id": token,
|
|
},
|
|
})
|
|
}
|