Files
aiops/backend/internal/api/handlers/prometheus_datasource.go
T

353 lines
10 KiB
Go

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,
})
}