353 lines
10 KiB
Go
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,
|
|
})
|
|
}
|