package handlers import ( "crypto/tls" "encoding/base64" "encoding/json" "net/http" "time" "ezops/internal/models" "github.com/gin-gonic/gin" "gorm.io/gorm" ) // PromDSHandler Prometheus 数据源管理 handler // Logan.Li type PromDSHandler struct { db *gorm.DB } // NewPromDSHandler 创建 Prometheus 数据源 handler func NewPromDSHandler(db *gorm.DB) *PromDSHandler { return &PromDSHandler{db: db} } // ============================================ // CRUD // ============================================ // List 数据源列表 // GET /api/v1/prometheus-datasources func (h *PromDSHandler) List(c *gin.Context) { var sources []models.PrometheusDataSource if err := h.db.Order("is_default DESC, id ASC").Find(&sources).Error; err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "查询失败: " + err.Error()}) return } if sources == nil { sources = []models.PrometheusDataSource{} } c.JSON(http.StatusOK, models.Response{Code: 200, Data: sources}) } // Get 获取单个数据源 // GET /api/v1/prometheus-datasources/:id func (h *PromDSHandler) Get(c *gin.Context) { id := c.Param("id") var ds models.PrometheusDataSource if err := h.db.First(&ds, id).Error; err != nil { c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "数据源不存在"}) return } c.JSON(http.StatusOK, models.Response{Code: 200, Data: ds}) } // Create 创建数据源 // POST /api/v1/prometheus-datasources func (h *PromDSHandler) Create(c *gin.Context) { var req models.CreatePromDSReq if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "参数错误: " + err.Error()}) return } // 如果设为默认,先清除其他默认 if req.IsDefault { h.db.Model(&models.PrometheusDataSource{}).Where("is_default = ?", true).Update("is_default", false) } // 默认值 if req.Access == "" { req.Access = "server" } if req.HTTPMethod == "" { req.HTTPMethod = "POST" } if req.ScrapeInterval <= 0 { req.ScrapeInterval = 15 } if req.QueryTimeout <= 0 { req.QueryTimeout = 60 } enableAlerting := true if req.EnableAlerting != nil { enableAlerting = *req.EnableAlerting } ds := models.PrometheusDataSource{ Name: req.Name, URL: req.URL, Access: req.Access, BasicAuth: req.BasicAuth, BasicAuthUser: req.BasicAuthUser, BasicAuthPass: req.BasicAuthPass, WithCredentials: req.WithCredentials, TLSClientAuth: req.TLSClientAuth, WithCACert: req.WithCACert, CACert: req.CACert, SkipTLSVerify: req.SkipTLSVerify, CustomHeaders: req.CustomHeaders, EnableAlerting: enableAlerting, AlertmanagerDS: req.AlertmanagerDS, ScrapeInterval: req.ScrapeInterval, QueryTimeout: req.QueryTimeout, HTTPMethod: req.HTTPMethod, DisableMetricsLookup: req.DisableMetricsLookup, CustomQueryParams: req.CustomQueryParams, IsDefault: req.IsDefault, Status: 1, } if err := h.db.Create(&ds).Error; err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "创建失败: " + err.Error()}) return } h.recordAudit(c, "create", "prometheus_datasource", ds.Name) c.JSON(http.StatusOK, models.Response{Code: 200, Message: "创建成功", Data: ds}) } // Update 更新数据源 // PUT /api/v1/prometheus-datasources/:id func (h *PromDSHandler) Update(c *gin.Context) { id := c.Param("id") var ds models.PrometheusDataSource if err := h.db.First(&ds, id).Error; err != nil { c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "数据源不存在"}) return } var req models.UpdatePromDSReq if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.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.Access != nil { updates["access"] = *req.Access } if req.BasicAuth != nil { updates["basic_auth"] = *req.BasicAuth } if req.BasicAuthUser != nil { updates["basic_auth_user"] = *req.BasicAuthUser } if req.BasicAuthPass != nil { updates["basic_auth_pass"] = *req.BasicAuthPass } if req.WithCredentials != nil { updates["with_credentials"] = *req.WithCredentials } if req.TLSClientAuth != nil { updates["tls_client_auth"] = *req.TLSClientAuth } if req.WithCACert != nil { updates["with_ca_cert"] = *req.WithCACert } if req.CACert != nil { updates["ca_cert"] = *req.CACert } if req.SkipTLSVerify != nil { updates["skip_tls_verify"] = *req.SkipTLSVerify } if req.CustomHeaders != nil { updates["custom_headers"] = *req.CustomHeaders } if req.EnableAlerting != nil { updates["enable_alerting"] = *req.EnableAlerting } if req.AlertmanagerDS != nil { updates["alertmanager_ds"] = *req.AlertmanagerDS } if req.ScrapeInterval != nil { updates["scrape_interval"] = *req.ScrapeInterval } if req.QueryTimeout != nil { updates["query_timeout"] = *req.QueryTimeout } if req.HTTPMethod != nil { updates["http_method"] = *req.HTTPMethod } if req.DisableMetricsLookup != nil { updates["disable_metrics_lookup"] = *req.DisableMetricsLookup } if req.CustomQueryParams != nil { updates["custom_query_params"] = *req.CustomQueryParams } if req.IsDefault != nil { // 如果设为默认,先清除其他默认 if *req.IsDefault { h.db.Model(&models.PrometheusDataSource{}).Where("is_default = ?", true).Update("is_default", false) } updates["is_default"] = *req.IsDefault } if req.Status != nil { updates["status"] = *req.Status } if len(updates) == 0 { c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "无更新字段"}) return } if err := h.db.Model(&ds).Updates(updates).Error; err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "更新失败: " + err.Error()}) return } // 重新查询返回 h.db.First(&ds, id) h.recordAudit(c, "update", "prometheus_datasource", ds.Name) c.JSON(http.StatusOK, models.Response{Code: 200, Message: "更新成功", Data: ds}) } // Delete 删除数据源 // DELETE /api/v1/prometheus-datasources/:id func (h *PromDSHandler) Delete(c *gin.Context) { id := c.Param("id") var ds models.PrometheusDataSource if err := h.db.First(&ds, id).Error; err != nil { c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "数据源不存在"}) return } if err := h.db.Delete(&ds).Error; err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "删除失败: " + err.Error()}) return } h.recordAudit(c, "delete", "prometheus_datasource", ds.Name) c.JSON(http.StatusOK, models.Response{Code: 200, Message: "删除成功"}) } // SetDefault 设为默认数据源 // POST /api/v1/prometheus-datasources/:id/default func (h *PromDSHandler) SetDefault(c *gin.Context) { id := c.Param("id") var ds models.PrometheusDataSource if err := h.db.First(&ds, id).Error; err != nil { c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "数据源不存在"}) return } // 清除所有默认 h.db.Model(&models.PrometheusDataSource{}).Where("is_default = ?", true).Update("is_default", false) // 设置当前为默认 h.db.Model(&ds).Update("is_default", true) c.JSON(http.StatusOK, models.Response{Code: 200, Message: "已设为默认"}) } // ============================================ // Test Connection // ============================================ // TestConnection 测试数据源连接 // POST /api/v1/prometheus-datasources/test func (h *PromDSHandler) TestConnection(c *gin.Context) { var req models.TestPromDSReq if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "参数错误: " + err.Error()}) return } client := &http.Client{Timeout: 15 * time.Second} // TLS 配置 if req.SkipTLSVerify { client.Transport = &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } } // 构造请求 apiURL := req.URL + "/api/v1/query?query=up&time=" + time.Now().UTC().Format(time.RFC3339) httpReq, err := http.NewRequest("GET", apiURL, nil) if err != nil { c.JSON(http.StatusOK, models.Response{Code: 400, Data: gin.H{ "success": false, "message": "URL 格式错误: " + err.Error(), }}) return } // Basic Auth if req.BasicAuth { auth := base64.StdEncoding.EncodeToString([]byte(req.BasicAuthUser + ":" + req.BasicAuthPass)) httpReq.Header.Set("Authorization", "Basic "+auth) } resp, err := client.Do(httpReq) if err != nil { c.JSON(http.StatusOK, models.Response{Code: 200, Data: gin.H{ "success": false, "message": "连接失败: " + err.Error(), }}) return } defer resp.Body.Close() if resp.StatusCode >= 200 && resp.StatusCode < 300 { // 尝试解析响应获取 target 数量 var result struct { Data struct { Result []interface{} `json:"result"` } `json:"data"` } json.NewDecoder(resp.Body).Decode(&result) c.JSON(http.StatusOK, models.Response{Code: 200, Data: gin.H{ "success": true, "message": "连接成功", "status_code": resp.StatusCode, "target_count": len(result.Data.Result), }}) } else { c.JSON(http.StatusOK, models.Response{Code: 200, Data: gin.H{ "success": false, "message": "连接返回异常状态码", "status_code": resp.StatusCode, }}) } } // ============================================ // 审计 // ============================================ func (h *PromDSHandler) recordAudit(c *gin.Context, action, resource, name string) { userID, _ := c.Get("userID") username, _ := c.Get("username") var uid uint var uname string if v, ok := userID.(uint); ok { uid = v } if v, ok := username.(string); ok { uname = v } h.db.Create(&models.AuditLog{ UserID: &uid, Username: uname, IP: c.ClientIP(), UserAgent: c.Request.UserAgent(), Module: "prometheus_datasource", Action: action, Resource: resource, ResourceID: name, Status: 200, }) }