init: EZOps AIOps 智能运维平台
- 前端: React 18 + TypeScript + Vite + Tailwind CSS + shadcn/ui - 后端: Go + Gin + GORM + JWT (golang-jwt/jwt/v5) - 功能: 服务器管理、K8s集群、监控告警、CI/CD、自动化运维、审批流程、审计日志 - 文档: 设计文档(ROADMAP/PDD/架构/模块)
This commit is contained in:
commit
7d6674bddd
156 files changed
+45704
No files matched your search
@@ -0,0 +1,350 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"ezops/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AlertHandler 告警处理器
|
||||
type AlertHandler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewAlertHandler 创建告警处理器
|
||||
func NewAlertHandler(db *gorm.DB) *AlertHandler {
|
||||
return &AlertHandler{db: db}
|
||||
}
|
||||
|
||||
// ListAlerts 获取告警事件列表
|
||||
// @Summary 获取告警事件列表
|
||||
// @Description 获取所有告警事件列表
|
||||
// @Tags 告警
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页数量"
|
||||
// @Param status query string false "状态过滤"
|
||||
// @Param severity query string false "级别过滤"
|
||||
// @Success 200 {object} models.Response{data=models.ListResponse}
|
||||
// @Router /alerts [get]
|
||||
func (h *AlertHandler) ListAlerts(c *gin.Context) {
|
||||
var query models.PageQuery
|
||||
c.ShouldBindQuery(&query)
|
||||
|
||||
db := h.db.Model(&models.AlertEvent{}).Preload("Rule")
|
||||
|
||||
// 状态过滤
|
||||
if status := c.Query("status"); status != "" {
|
||||
db = db.Where("status = ?", status)
|
||||
}
|
||||
|
||||
// 级别过滤
|
||||
if severity := c.Query("severity"); severity != "" {
|
||||
db = db.Where("severity = ?", severity)
|
||||
}
|
||||
|
||||
var total int64
|
||||
db.Count(&total)
|
||||
|
||||
var alerts []models.AlertEvent
|
||||
db.Order("created_at DESC").
|
||||
Offset(query.GetOffset()).
|
||||
Limit(query.GetLimit()).
|
||||
Find(&alerts)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: models.ListResponse{
|
||||
List: alerts,
|
||||
Total: total,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ListRules 获取告警规则列表
|
||||
// @Summary 获取告警规则列表
|
||||
// @Description 获取所有告警规则列表
|
||||
// @Tags 告警
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页数量"
|
||||
// @Success 200 {object} models.Response{data=models.ListResponse}
|
||||
// @Router /alerts/rules [get]
|
||||
func (h *AlertHandler) ListRules(c *gin.Context) {
|
||||
var query models.PageQuery
|
||||
c.ShouldBindQuery(&query)
|
||||
|
||||
var total int64
|
||||
h.db.Model(&models.AlertRule{}).Count(&total)
|
||||
|
||||
var rules []models.AlertRule
|
||||
h.db.Offset(query.GetOffset()).Limit(query.GetLimit()).Find(&rules)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: models.ListResponse{
|
||||
List: rules,
|
||||
Total: total,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// CreateRule 创建告警规则
|
||||
// @Summary 创建告警规则
|
||||
// @Description 创建新告警规则
|
||||
// @Tags 告警
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body models.CreateAlertRuleRequest true "创建请求"
|
||||
// @Success 200 {object} models.Response{data=models.AlertRule}
|
||||
// @Router /alerts/rules [post]
|
||||
func (h *AlertHandler) CreateRule(c *gin.Context) {
|
||||
var req models.CreateAlertRuleRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
rule := models.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, models.Response{
|
||||
Code: 500,
|
||||
Message: "创建失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "创建成功",
|
||||
Data: rule,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateRule 更新告警规则
|
||||
// @Summary 更新告警规则
|
||||
// @Description 更新指定告警规则
|
||||
// @Tags 告警
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "规则ID"
|
||||
// @Param request body models.UpdateAlertRuleRequest true "更新请求"
|
||||
// @Success 200 {object} models.Response{data=models.AlertRule}
|
||||
// @Router /alerts/rules/{id} [put]
|
||||
func (h *AlertHandler) UpdateRule(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var req models.UpdateAlertRuleRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var rule models.AlertRule
|
||||
if err := h.db.First(&rule, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.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"] = 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, models.Response{
|
||||
Code: 500,
|
||||
Message: "更新失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
h.db.First(&rule, id)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "更新成功",
|
||||
Data: rule,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteRule 删除告警规则
|
||||
// @Summary 删除告警规则
|
||||
// @Description 删除指定告警规则
|
||||
// @Tags 告警
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "规则ID"
|
||||
// @Success 200 {object} models.Response
|
||||
// @Router /alerts/rules/{id} [delete]
|
||||
func (h *AlertHandler) DeleteRule(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
if err := h.db.Delete(&models.AlertRule{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "删除失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
// ToggleRule 切换告警规则状态
|
||||
// @Summary 切换告警规则状态
|
||||
// @Description 启用或禁用告警规则
|
||||
// @Tags 告警
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "规则ID"
|
||||
// @Success 200 {object} models.Response{data=models.AlertRule}
|
||||
// @Router /alerts/rules/{id}/toggle [post]
|
||||
func (h *AlertHandler) ToggleRule(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var rule models.AlertRule
|
||||
if err := h.db.First(&rule, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "规则不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
rule.Enabled = !rule.Enabled
|
||||
if err := h.db.Save(&rule).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "操作失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "操作成功",
|
||||
Data: rule,
|
||||
})
|
||||
}
|
||||
|
||||
// AcknowledgeAlert 确认告警
|
||||
// @Summary 确认告警
|
||||
// @Description 确认告警事件
|
||||
// @Tags 告警
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "告警ID"
|
||||
// @Param request body models.AcknowledgeAlertRequest true "确认请求"
|
||||
// @Success 200 {object} models.Response{data=models.AlertEvent}
|
||||
// @Router /alerts/{id}/ack [post]
|
||||
func (h *AlertHandler) AcknowledgeAlert(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var req models.AcknowledgeAlertRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var alert models.AlertEvent
|
||||
if err := h.db.First(&alert, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "告警不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
alert.Acknowledged = true
|
||||
// alert.AcknowledgedBy = middleware.GetUserID(c)
|
||||
// now := time.Now()
|
||||
// alert.AcknowledgedAt = &now
|
||||
|
||||
if err := h.db.Save(&alert).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "确认失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "确认成功",
|
||||
Data: alert,
|
||||
})
|
||||
}
|
||||
|
||||
// toJSON serializes a value to JSON string. Returns empty string on error.
|
||||
func toJSON(v interface{}) string {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"ezops/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ApprovalHandler 审批处理器
|
||||
type ApprovalHandler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewApprovalHandler 创建审批处理器
|
||||
func NewApprovalHandler(db *gorm.DB) *ApprovalHandler {
|
||||
return &ApprovalHandler{db: db}
|
||||
}
|
||||
|
||||
// ListTemplates 获取审批模板列表
|
||||
// @Summary 获取审批模板列表
|
||||
// @Description 获取所有审批模板列表
|
||||
// @Tags 审批流程
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页数量"
|
||||
// @Success 200 {object} models.Response{data=models.ListResponse}
|
||||
// @Router /approvals/templates [get]
|
||||
func (h *ApprovalHandler) ListTemplates(c *gin.Context) {
|
||||
var query models.PageQuery
|
||||
c.ShouldBindQuery(&query)
|
||||
|
||||
var total int64
|
||||
h.db.Model(&models.ApprovalTemplate{}).Count(&total)
|
||||
|
||||
var templates []models.ApprovalTemplate
|
||||
h.db.Offset(query.GetOffset()).Limit(query.GetLimit()).Find(&templates)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: models.ListResponse{
|
||||
List: templates,
|
||||
Total: total,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetTemplate 获取审批模板详情
|
||||
// @Summary 获取审批模板详情
|
||||
// @Description 获取指定审批模板的详细信息
|
||||
// @Tags 审批流程
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "模板ID"
|
||||
// @Success 200 {object} models.Response{data=models.ApprovalTemplate}
|
||||
// @Router /approvals/templates/{id} [get]
|
||||
func (h *ApprovalHandler) GetTemplate(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var template models.ApprovalTemplate
|
||||
if err := h.db.First(&template, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "模板不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: template,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateTemplate 创建审批模板
|
||||
// @Summary 创建审批模板
|
||||
// @Description 创建新审批模板
|
||||
// @Tags 审批流程
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body models.CreateApprovalTemplateRequest true "创建请求"
|
||||
// @Success 200 {object} models.Response{data=models.ApprovalTemplate}
|
||||
// @Router /approvals/templates [post]
|
||||
func (h *ApprovalHandler) CreateTemplate(c *gin.Context) {
|
||||
var req models.CreateApprovalTemplateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
template := models.ApprovalTemplate{
|
||||
Name: req.Name,
|
||||
Code: req.Code,
|
||||
Description: req.Description,
|
||||
Type: req.Type,
|
||||
FormSchema: req.FormSchema,
|
||||
FlowConfig: req.FlowConfig,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
if err := h.db.Create(&template).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "创建失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "创建成功",
|
||||
Data: template,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateTemplate 更新审批模板
|
||||
// @Summary 更新审批模板
|
||||
// @Description 更新指定审批模板
|
||||
// @Tags 审批流程
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "模板ID"
|
||||
// @Param request body models.CreateApprovalTemplateRequest true "更新请求"
|
||||
// @Success 200 {object} models.Response{data=models.ApprovalTemplate}
|
||||
// @Router /approvals/templates/{id} [put]
|
||||
func (h *ApprovalHandler) UpdateTemplate(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var req models.CreateApprovalTemplateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var template models.ApprovalTemplate
|
||||
if err := h.db.First(&template, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "模板不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
updates := map[string]interface{}{
|
||||
"name": req.Name,
|
||||
"description": req.Description,
|
||||
"type": req.Type,
|
||||
"form_schema": req.FormSchema,
|
||||
"flow_config": req.FlowConfig,
|
||||
}
|
||||
|
||||
if err := h.db.Model(&template).Updates(updates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "更新失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
h.db.First(&template, id)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "更新成功",
|
||||
Data: template,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteTemplate 删除审批模板
|
||||
// @Summary 删除审批模板
|
||||
// @Description 删除指定审批模板
|
||||
// @Tags 审批流程
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "模板ID"
|
||||
// @Success 200 {object} models.Response
|
||||
// @Router /approvals/templates/{id} [delete]
|
||||
func (h *ApprovalHandler) DeleteTemplate(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
if err := h.db.Delete(&models.ApprovalTemplate{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "删除失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
// ListProcesses 获取审批流程列表
|
||||
// @Summary 获取审批流程列表
|
||||
// @Description 获取所有审批流程列表
|
||||
// @Tags 审批流程
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页数量"
|
||||
// @Param status query string false "状态过滤"
|
||||
// @Success 200 {object} models.Response{data=models.ListResponse}
|
||||
// @Router /approvals/processes [get]
|
||||
func (h *ApprovalHandler) ListProcesses(c *gin.Context) {
|
||||
var query models.PageQuery
|
||||
c.ShouldBindQuery(&query)
|
||||
|
||||
db := h.db.Model(&models.ApprovalProcess{}).Preload("Template").Preload("ApplicantUser")
|
||||
|
||||
// 状态过滤
|
||||
if status := c.Query("status"); status != "" {
|
||||
db = db.Where("status = ?", status)
|
||||
}
|
||||
|
||||
var total int64
|
||||
db.Count(&total)
|
||||
|
||||
var processes []models.ApprovalProcess
|
||||
db.Order("created_at DESC").
|
||||
Offset(query.GetOffset()).
|
||||
Limit(query.GetLimit()).
|
||||
Find(&processes)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: models.ListResponse{
|
||||
List: processes,
|
||||
Total: total,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetProcess 获取审批流程详情
|
||||
// @Summary 获取审批流程详情
|
||||
// @Description 获取指定审批流程的详细信息
|
||||
// @Tags 审批流程
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "流程ID"
|
||||
// @Success 200 {object} models.Response{data=models.ApprovalProcess}
|
||||
// @Router /approvals/processes/{id} [get]
|
||||
func (h *ApprovalHandler) GetProcess(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var process models.ApprovalProcess
|
||||
if err := h.db.Preload("Template").Preload("ApplicantUser").Preload("Steps.ApproverUser").First(&process, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "流程不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: process,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateProcess 创建审批流程
|
||||
// @Summary 创建审批流程
|
||||
// @Description 创建新审批流程
|
||||
// @Tags 审批流程
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body models.CreateApprovalProcessRequest true "创建请求"
|
||||
// @Success 200 {object} models.Response{data=models.ApprovalProcess}
|
||||
// @Router /approvals/processes [post]
|
||||
func (h *ApprovalHandler) CreateProcess(c *gin.Context) {
|
||||
var req models.CreateApprovalProcessRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取模板
|
||||
var template models.ApprovalTemplate
|
||||
if err := h.db.First(&template, req.TemplateID).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "模板不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
process := models.ApprovalProcess{
|
||||
TemplateID: req.TemplateID,
|
||||
Title: req.Title,
|
||||
FormData: req.FormData,
|
||||
Status: "pending",
|
||||
CurrentStep: 0,
|
||||
TotalSteps: 1, // 简化处理
|
||||
}
|
||||
|
||||
if err := h.db.Create(&process).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "创建失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
h.db.Preload("Template").First(&process, process.ID)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "创建成功",
|
||||
Data: process,
|
||||
})
|
||||
}
|
||||
|
||||
// ApproveProcess 审批通过
|
||||
// @Summary 审批通过
|
||||
// @Description 审批通过指定流程
|
||||
// @Tags 审批流程
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "流程ID"
|
||||
// @Param request body models.ApproveRequest true "审批请求"
|
||||
// @Success 200 {object} models.Response{data=models.ApprovalProcess}
|
||||
// @Router /approvals/processes/{id}/approve [post]
|
||||
func (h *ApprovalHandler) ApproveProcess(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var req models.ApproveRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var process models.ApprovalProcess
|
||||
if err := h.db.First(&process, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "流程不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
process.Status = "approved"
|
||||
// now := time.Now()
|
||||
// process.ApprovedAt = &now
|
||||
|
||||
if err := h.db.Save(&process).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "审批失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "审批通过",
|
||||
Data: process,
|
||||
})
|
||||
}
|
||||
|
||||
// RejectProcess 审批拒绝
|
||||
// @Summary 审批拒绝
|
||||
// @Description 审批拒绝指定流程
|
||||
// @Tags 审批流程
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "流程ID"
|
||||
// @Param request body models.ApproveRequest true "审批请求"
|
||||
// @Success 200 {object} models.Response{data=models.ApprovalProcess}
|
||||
// @Router /approvals/processes/{id}/reject [post]
|
||||
func (h *ApprovalHandler) RejectProcess(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var req models.ApproveRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var process models.ApprovalProcess
|
||||
if err := h.db.First(&process, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "流程不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
process.Status = "rejected"
|
||||
process.RejectReason = req.Comment
|
||||
// now := time.Now()
|
||||
// process.RejectedAt = &now
|
||||
|
||||
if err := h.db.Save(&process).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "拒绝失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "已拒绝",
|
||||
Data: process,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"ezops/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AuditHandler 审计日志处理器
|
||||
type AuditHandler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewAuditHandler 创建审计日志处理器
|
||||
func NewAuditHandler(db *gorm.DB) *AuditHandler {
|
||||
return &AuditHandler{db: db}
|
||||
}
|
||||
|
||||
// ListAuditLogs 获取审计日志列表
|
||||
// @Summary 获取审计日志列表
|
||||
// @Description 获取审计日志列表,支持分页和筛选
|
||||
// @Tags 审计日志
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param pageSize query int false "每页数量" default(20)
|
||||
// @Param module query string false "模块"
|
||||
// @Param action query string false "操作类型"
|
||||
// @Param username query string false "用户名"
|
||||
// @Param startTime query string false "开始时间"
|
||||
// @Param endTime query string false "结束时间"
|
||||
// @Success 200 {object} models.AuditLogListResponse
|
||||
// @Router /api/v1/audit/logs [get]
|
||||
func (h *AuditHandler) ListAuditLogs(c *gin.Context) {
|
||||
var req models.AuditLogQuery
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if req.Page <= 0 {
|
||||
req.Page = 1
|
||||
}
|
||||
if req.PageSize <= 0 {
|
||||
req.PageSize = 20
|
||||
}
|
||||
|
||||
// 构建查询
|
||||
query := h.db.Model(&models.AuditLog{})
|
||||
|
||||
if req.Module != "" {
|
||||
query = query.Where("module = ?", req.Module)
|
||||
}
|
||||
if req.Action != "" {
|
||||
query = query.Where("action = ?", req.Action)
|
||||
}
|
||||
if req.Username != "" {
|
||||
query = query.Where("username LIKE ?", "%"+req.Username+"%")
|
||||
}
|
||||
if req.StartTime != "" {
|
||||
query = query.Where("created_at >= ?", req.StartTime)
|
||||
}
|
||||
if req.EndTime != "" {
|
||||
query = query.Where("created_at <= ?", req.EndTime)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "查询失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取列表
|
||||
var logs []models.AuditLog
|
||||
if err := query.Order("created_at DESC").
|
||||
Offset((req.Page - 1) * req.PageSize).
|
||||
Limit(req.PageSize).
|
||||
Find(&logs).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "查询失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.AuditLogListResponse{
|
||||
List: logs,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
// GetAuditLogDetail 获取审计日志详情
|
||||
// @Summary 获取审计日志详情
|
||||
// @Description 获取指定ID的审计日志详情
|
||||
// @Tags 审计日志
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "日志ID"
|
||||
// @Success 200 {object} models.AuditLog
|
||||
// @Router /api/v1/audit/logs/{id} [get]
|
||||
func (h *AuditHandler) GetAuditLogDetail(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "无效的日志ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var log models.AuditLog
|
||||
if err := h.db.First(&log, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "日志不存在"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "查询失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, log)
|
||||
}
|
||||
|
||||
// GetAuditModules 获取审计模块列表
|
||||
// @Summary 获取审计模块列表
|
||||
// @Description 获取所有审计模块类型
|
||||
// @Tags 审计日志
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/audit/modules [get]
|
||||
func (h *AuditHandler) GetAuditModules(c *gin.Context) {
|
||||
modules := []map[string]string{
|
||||
{"value": "user", "label": "用户管理"},
|
||||
{"value": "server", "label": "服务器管理"},
|
||||
{"value": "k8s", "label": "Kubernetes"},
|
||||
{"value": "alert", "label": "监控告警"},
|
||||
{"value": "pipeline", "label": "应用发布"},
|
||||
{"value": "automation", "label": "自动化运维"},
|
||||
{"value": "approval", "label": "审批流程"},
|
||||
{"value": "navigation", "label": "站内导航"},
|
||||
{"value": "cloud_account", "label": "云账号管理"},
|
||||
{"value": "domain", "label": "域名管理"},
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": modules})
|
||||
}
|
||||
|
||||
// GetAuditActions 获取审计操作类型列表
|
||||
// @Summary 获取审计操作类型列表
|
||||
// @Description 获取所有审计操作类型
|
||||
// @Tags 审计日志
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/audit/actions [get]
|
||||
func (h *AuditHandler) GetAuditActions(c *gin.Context) {
|
||||
actions := []map[string]string{
|
||||
{"value": "create", "label": "创建"},
|
||||
{"value": "update", "label": "更新"},
|
||||
{"value": "delete", "label": "删除"},
|
||||
{"value": "view", "label": "查看"},
|
||||
{"value": "login", "label": "登录"},
|
||||
{"value": "logout", "label": "登出"},
|
||||
{"value": "execute", "label": "执行"},
|
||||
{"value": "approve", "label": "审批"},
|
||||
{"value": "reject", "label": "驳回"},
|
||||
{"value": "import", "label": "导入"},
|
||||
{"value": "export", "label": "导出"},
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": actions})
|
||||
}
|
||||
|
||||
// ExportAuditLogs 导出审计日志
|
||||
// @Summary 导出审计日志
|
||||
// @Description 导出审计日志为Excel或CSV
|
||||
// @Tags 审计日志
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param module query string false "模块"
|
||||
// @Param action query string false "操作类型"
|
||||
// @Param username query string false "用户名"
|
||||
// @Param startTime query string false "开始时间"
|
||||
// @Param endTime query string false "结束时间"
|
||||
// @Param format query string false "导出格式" default("csv") Enums(csv, excel)
|
||||
// @Success 200 {file} binary
|
||||
// @Router /api/v1/audit/logs/export [get]
|
||||
func (h *AuditHandler) ExportAuditLogs(c *gin.Context) {
|
||||
var req models.AuditLogQuery
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 构建查询
|
||||
query := h.db.Model(&models.AuditLog{})
|
||||
|
||||
if req.Module != "" {
|
||||
query = query.Where("module = ?", req.Module)
|
||||
}
|
||||
if req.Action != "" {
|
||||
query = query.Where("action = ?", req.Action)
|
||||
}
|
||||
if req.Username != "" {
|
||||
query = query.Where("username LIKE ?", "%"+req.Username+"%")
|
||||
}
|
||||
if req.StartTime != "" {
|
||||
query = query.Where("created_at >= ?", req.StartTime)
|
||||
}
|
||||
if req.EndTime != "" {
|
||||
query = query.Where("created_at <= ?", req.EndTime)
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
var logs []models.AuditLog
|
||||
if err := query.Order("created_at DESC").Find(&logs).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "导出失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: 实现CSV/Excel导出功能
|
||||
// 这里返回JSON格式的数据,实际项目中应该生成CSV或Excel文件
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "导出成功",
|
||||
"count": len(logs),
|
||||
"data": logs,
|
||||
})
|
||||
}
|
||||
|
||||
// GetAuditStatistics 获取审计统计
|
||||
// @Summary 获取审计统计
|
||||
// @Description 获取审计日志统计信息
|
||||
// @Tags 审计日志
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param days query int false "统计天数" default(7)
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/audit/statistics [get]
|
||||
func (h *AuditHandler) GetAuditStatistics(c *gin.Context) {
|
||||
days, _ := strconv.Atoi(c.DefaultQuery("days", "7"))
|
||||
if days <= 0 {
|
||||
days = 7
|
||||
}
|
||||
|
||||
startTime := time.Now().AddDate(0, 0, -days)
|
||||
|
||||
// 统计各模块的操作数量
|
||||
var moduleStats []struct {
|
||||
Module string `json:"module"`
|
||||
Count int64 `json:"count"`
|
||||
}
|
||||
h.db.Model(&models.AuditLog{}).
|
||||
Select("module, COUNT(*) as count").
|
||||
Where("created_at >= ?", startTime).
|
||||
Group("module").
|
||||
Scan(&moduleStats)
|
||||
|
||||
// 统计各操作类型的数量
|
||||
var actionStats []struct {
|
||||
Action string `json:"action"`
|
||||
Count int64 `json:"count"`
|
||||
}
|
||||
h.db.Model(&models.AuditLog{}).
|
||||
Select("action, COUNT(*) as count").
|
||||
Where("created_at >= ?", startTime).
|
||||
Group("action").
|
||||
Scan(&actionStats)
|
||||
|
||||
// 统计每日操作数量
|
||||
var dailyStats []struct {
|
||||
Date string `json:"date"`
|
||||
Count int64 `json:"count"`
|
||||
}
|
||||
h.db.Model(&models.AuditLog{}).
|
||||
Select("DATE(created_at) as date, COUNT(*) as count").
|
||||
Where("created_at >= ?", startTime).
|
||||
Group("DATE(created_at)").
|
||||
Order("date").
|
||||
Scan(&dailyStats)
|
||||
|
||||
// 总记录数
|
||||
var totalCount int64
|
||||
h.db.Model(&models.AuditLog{}).Count(&totalCount)
|
||||
|
||||
// 今日记录数
|
||||
today := time.Now().Format("2006-01-02")
|
||||
var todayCount int64
|
||||
h.db.Model(&models.AuditLog{}).
|
||||
Where("DATE(created_at) = ?", today).
|
||||
Count(&todayCount)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"total_count": totalCount,
|
||||
"today_count": todayCount,
|
||||
"module_stats": moduleStats,
|
||||
"action_stats": actionStats,
|
||||
"daily_stats": dailyStats,
|
||||
})
|
||||
}
|
||||
|
||||
// ClearAuditLogs 清理审计日志
|
||||
// @Summary 清理审计日志
|
||||
// @Description 清理指定日期之前的审计日志
|
||||
// @Tags 审计日志
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body map[string]string true "清理请求"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/audit/logs/clear [post]
|
||||
func (h *AuditHandler) ClearAuditLogs(c *gin.Context) {
|
||||
var req struct {
|
||||
BeforeDate string `json:"before_date" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "无效的请求参数"})
|
||||
return
|
||||
}
|
||||
|
||||
// 解析日期
|
||||
beforeDate, err := time.Parse("2006-01-02", req.BeforeDate)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "无效的日期格式"})
|
||||
return
|
||||
}
|
||||
|
||||
// 删除指定日期之前的日志
|
||||
result := h.db.Where("created_at < ?", beforeDate).Delete(&models.AuditLog{})
|
||||
if result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "清理失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 记录审计日志
|
||||
userID, _ := c.Get("userID")
|
||||
username, _ := c.Get("username")
|
||||
h.RecordAuditLog(userID.(uint), username.(string), "audit", "clear", "清理审计日志", gin.H{
|
||||
"before_date": req.BeforeDate,
|
||||
"deleted": result.RowsAffected,
|
||||
})
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "清理成功",
|
||||
"deleted": result.RowsAffected,
|
||||
})
|
||||
}
|
||||
|
||||
// RecordAuditLog 记录审计日志
|
||||
func (h *AuditHandler) RecordAuditLog(userID uint, username, module, action, description string, details interface{}) {
|
||||
uid := userID
|
||||
log := models.AuditLog{
|
||||
UserID: &uid,
|
||||
Username: username,
|
||||
Module: module,
|
||||
Action: action,
|
||||
Resource: description,
|
||||
}
|
||||
|
||||
h.db.Create(&log)
|
||||
}
|
||||
@@ -0,0 +1,553 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"ezops/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AutomationHandler 自动化运维处理器
|
||||
type AutomationHandler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewAutomationHandler 创建自动化运维处理器
|
||||
func NewAutomationHandler(db *gorm.DB) *AutomationHandler {
|
||||
return &AutomationHandler{db: db}
|
||||
}
|
||||
|
||||
// ListCronJobs 获取定时任务列表
|
||||
// @Summary 获取定时任务列表
|
||||
// @Description 获取所有定时任务列表
|
||||
// @Tags 自动化运维
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页数量"
|
||||
// @Success 200 {object} models.Response{data=models.ListResponse}
|
||||
// @Router /cron-jobs [get]
|
||||
func (h *AutomationHandler) ListCronJobs(c *gin.Context) {
|
||||
var query models.PageQuery
|
||||
c.ShouldBindQuery(&query)
|
||||
|
||||
var total int64
|
||||
h.db.Model(&models.CronJob{}).Count(&total)
|
||||
|
||||
var jobs []models.CronJob
|
||||
h.db.Preload("Script").Offset(query.GetOffset()).Limit(query.GetLimit()).Find(&jobs)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: models.ListResponse{
|
||||
List: jobs,
|
||||
Total: total,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetCronJob 获取定时任务详情
|
||||
// @Summary 获取定时任务详情
|
||||
// @Description 获取指定定时任务的详细信息
|
||||
// @Tags 自动化运维
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "任务ID"
|
||||
// @Success 200 {object} models.Response{data=models.CronJob}
|
||||
// @Router /cron-jobs/{id} [get]
|
||||
func (h *AutomationHandler) GetCronJob(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var job models.CronJob
|
||||
if err := h.db.Preload("Script").First(&job, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "定时任务不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: job,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateCronJob 创建定时任务
|
||||
// @Summary 创建定时任务
|
||||
// @Description 创建新定时任务
|
||||
// @Tags 自动化运维
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body models.CreateCronJobRequest true "创建请求"
|
||||
// @Success 200 {object} models.Response{data=models.CronJob}
|
||||
// @Router /cron-jobs [post]
|
||||
func (h *AutomationHandler) CreateCronJob(c *gin.Context) {
|
||||
var req models.CreateCronJobRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
job := models.CronJob{
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
CronExpr: req.CronExpr,
|
||||
Type: req.Type,
|
||||
Content: req.Content,
|
||||
ScriptID: req.ScriptID,
|
||||
TargetType: req.TargetType,
|
||||
Timeout: req.Timeout,
|
||||
NotifyOn: req.NotifyOn,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
if job.Timeout == 0 {
|
||||
job.Timeout = 300
|
||||
}
|
||||
if job.NotifyOn == "" {
|
||||
job.NotifyOn = "failure"
|
||||
}
|
||||
|
||||
if err := h.db.Create(&job).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "创建失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "创建成功",
|
||||
Data: job,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateCronJob 更新定时任务
|
||||
// @Summary 更新定时任务
|
||||
// @Description 更新指定定时任务
|
||||
// @Tags 自动化运维
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "任务ID"
|
||||
// @Param request body models.CreateCronJobRequest true "更新请求"
|
||||
// @Success 200 {object} models.Response{data=models.CronJob}
|
||||
// @Router /cron-jobs/{id} [put]
|
||||
func (h *AutomationHandler) UpdateCronJob(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var req models.CreateCronJobRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var job models.CronJob
|
||||
if err := h.db.First(&job, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "定时任务不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
updates := map[string]interface{}{
|
||||
"name": req.Name,
|
||||
"description": req.Description,
|
||||
"cron_expr": req.CronExpr,
|
||||
"type": req.Type,
|
||||
"content": req.Content,
|
||||
"script_id": req.ScriptID,
|
||||
"target_type": req.TargetType,
|
||||
"timeout": req.Timeout,
|
||||
"notify_on": req.NotifyOn,
|
||||
}
|
||||
|
||||
if err := h.db.Model(&job).Updates(updates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "更新失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
h.db.Preload("Script").First(&job, id)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "更新成功",
|
||||
Data: job,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteCronJob 删除定时任务
|
||||
// @Summary 删除定时任务
|
||||
// @Description 删除指定定时任务
|
||||
// @Tags 自动化运维
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "任务ID"
|
||||
// @Success 200 {object} models.Response
|
||||
// @Router /cron-jobs/{id} [delete]
|
||||
func (h *AutomationHandler) DeleteCronJob(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
if err := h.db.Delete(&models.CronJob{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "删除失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
// ToggleCronJob 切换定时任务状态
|
||||
// @Summary 切换定时任务状态
|
||||
// @Description 启用或禁用定时任务
|
||||
// @Tags 自动化运维
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "任务ID"
|
||||
// @Success 200 {object} models.Response{data=models.CronJob}
|
||||
// @Router /cron-jobs/{id}/toggle [post]
|
||||
func (h *AutomationHandler) ToggleCronJob(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var job models.CronJob
|
||||
if err := h.db.First(&job, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "定时任务不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
job.Enabled = !job.Enabled
|
||||
if err := h.db.Save(&job).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "操作失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "操作成功",
|
||||
Data: job,
|
||||
})
|
||||
}
|
||||
|
||||
// ExecuteCronJob 执行定时任务
|
||||
// @Summary 执行定时任务
|
||||
// @Description 手动执行定时任务
|
||||
// @Tags 自动化运维
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "任务ID"
|
||||
// @Success 200 {object} models.Response{data=models.TaskExecution}
|
||||
// @Router /cron-jobs/{id}/execute [post]
|
||||
func (h *AutomationHandler) ExecuteCronJob(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var job models.CronJob
|
||||
if err := h.db.First(&job, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "定时任务不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
execution := models.TaskExecution{
|
||||
TaskType: "cron",
|
||||
TaskID: uint(id),
|
||||
TaskName: job.Name,
|
||||
Status: "pending",
|
||||
TargetType: job.TargetType,
|
||||
Targets: job.Targets,
|
||||
}
|
||||
|
||||
if err := h.db.Create(&execution).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "创建执行记录失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "执行中",
|
||||
Data: execution,
|
||||
})
|
||||
}
|
||||
|
||||
// ListScripts 获取脚本列表
|
||||
// @Summary 获取脚本列表
|
||||
// @Description 获取所有脚本列表
|
||||
// @Tags 自动化运维
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页数量"
|
||||
// @Success 200 {object} models.Response{data=models.ListResponse}
|
||||
// @Router /scripts [get]
|
||||
func (h *AutomationHandler) ListScripts(c *gin.Context) {
|
||||
var query models.PageQuery
|
||||
c.ShouldBindQuery(&query)
|
||||
|
||||
var total int64
|
||||
h.db.Model(&models.Script{}).Count(&total)
|
||||
|
||||
var scripts []models.Script
|
||||
h.db.Offset(query.GetOffset()).Limit(query.GetLimit()).Find(&scripts)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: models.ListResponse{
|
||||
List: scripts,
|
||||
Total: total,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetScript 获取脚本详情
|
||||
// @Summary 获取脚本详情
|
||||
// @Description 获取指定脚本的详细信息
|
||||
// @Tags 自动化运维
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "脚本ID"
|
||||
// @Success 200 {object} models.Response{data=models.Script}
|
||||
// @Router /scripts/{id} [get]
|
||||
func (h *AutomationHandler) GetScript(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var script models.Script
|
||||
if err := h.db.First(&script, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "脚本不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: script,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateScript 创建脚本
|
||||
// @Summary 创建脚本
|
||||
// @Description 创建新脚本
|
||||
// @Tags 自动化运维
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body models.CreateScriptRequest true "创建请求"
|
||||
// @Success 200 {object} models.Response{data=models.Script}
|
||||
// @Router /scripts [post]
|
||||
func (h *AutomationHandler) CreateScript(c *gin.Context) {
|
||||
var req models.CreateScriptRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
script := models.Script{
|
||||
Name: req.Name,
|
||||
Code: req.Code,
|
||||
Description: req.Description,
|
||||
Type: req.Type,
|
||||
Content: req.Content,
|
||||
Params: req.Params,
|
||||
Timeout: req.Timeout,
|
||||
}
|
||||
|
||||
if script.Type == "" {
|
||||
script.Type = "shell"
|
||||
}
|
||||
if script.Timeout == 0 {
|
||||
script.Timeout = 300
|
||||
}
|
||||
|
||||
if err := h.db.Create(&script).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "创建失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "创建成功",
|
||||
Data: script,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateScript 更新脚本
|
||||
// @Summary 更新脚本
|
||||
// @Description 更新指定脚本
|
||||
// @Tags 自动化运维
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "脚本ID"
|
||||
// @Param request body models.CreateScriptRequest true "更新请求"
|
||||
// @Success 200 {object} models.Response{data=models.Script}
|
||||
// @Router /scripts/{id} [put]
|
||||
func (h *AutomationHandler) UpdateScript(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var req models.CreateScriptRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var script models.Script
|
||||
if err := h.db.First(&script, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "脚本不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
updates := map[string]interface{}{
|
||||
"name": req.Name,
|
||||
"description": req.Description,
|
||||
"type": req.Type,
|
||||
"content": req.Content,
|
||||
"params": req.Params,
|
||||
"timeout": req.Timeout,
|
||||
"version": script.Version + 1,
|
||||
}
|
||||
|
||||
if err := h.db.Model(&script).Updates(updates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "更新失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
h.db.First(&script, id)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "更新成功",
|
||||
Data: script,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteScript 删除脚本
|
||||
// @Summary 删除脚本
|
||||
// @Description 删除指定脚本
|
||||
// @Tags 自动化运维
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "脚本ID"
|
||||
// @Success 200 {object} models.Response
|
||||
// @Router /scripts/{id} [delete]
|
||||
func (h *AutomationHandler) DeleteScript(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
if err := h.db.Delete(&models.Script{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "删除失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
// ExecuteScript 执行脚本
|
||||
// @Summary 执行脚本
|
||||
// @Description 在目标服务器上执行脚本
|
||||
// @Tags 自动化运维
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "脚本ID"
|
||||
// @Param request body models.ExecuteScriptRequest true "执行请求"
|
||||
// @Success 200 {object} models.Response{data=models.TaskExecution}
|
||||
// @Router /scripts/{id}/execute [post]
|
||||
func (h *AutomationHandler) ExecuteScript(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var script models.Script
|
||||
if err := h.db.First(&script, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "脚本不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var req models.ExecuteScriptRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
execution := models.TaskExecution{
|
||||
TaskType: "script",
|
||||
TaskID: uint(id),
|
||||
TaskName: script.Name,
|
||||
Status: "pending",
|
||||
TargetType: req.TargetType,
|
||||
}
|
||||
|
||||
if err := h.db.Create(&execution).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "创建执行记录失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "执行中",
|
||||
Data: execution,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"ezops/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// DashboardHandler 仪表盘处理器
|
||||
type DashboardHandler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewDashboardHandler 创建仪表盘处理器
|
||||
func NewDashboardHandler(db *gorm.DB) *DashboardHandler {
|
||||
return &DashboardHandler{db: db}
|
||||
}
|
||||
|
||||
// GetStats 获取仪表盘统计
|
||||
// @Summary 获取仪表盘统计
|
||||
// @Description 获取仪表盘所有统计数据
|
||||
// @Tags 仪表盘
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} models.Response{data=models.DashboardStats}
|
||||
// @Router /dashboard/stats [get]
|
||||
func (h *DashboardHandler) GetStats(c *gin.Context) {
|
||||
var stats models.DashboardStats
|
||||
|
||||
// 服务器统计
|
||||
var serverStats models.ServerStats
|
||||
h.db.Model(&models.Server{}).Count(&serverStats.Total)
|
||||
h.db.Model(&models.Server{}).Where("status = ?", "running").Count(&serverStats.Running)
|
||||
h.db.Model(&models.Server{}).Where("status = ?", "stopped").Count(&serverStats.Stopped)
|
||||
h.db.Model(&models.Server{}).Where("status = ?", "maintenance").Count(&serverStats.Maintenance)
|
||||
h.db.Model(&models.Server{}).Where("status = ?", "error").Count(&serverStats.Error)
|
||||
|
||||
var avgCPU, avgMemory, avgDisk float64
|
||||
h.db.Model(&models.Server{}).Select("AVG(cpu_usage)").Scan(&avgCPU)
|
||||
h.db.Model(&models.Server{}).Select("AVG(memory_usage)").Scan(&avgMemory)
|
||||
h.db.Model(&models.Server{}).Select("AVG(disk_usage)").Scan(&avgDisk)
|
||||
serverStats.AvgCPU = math.Round(avgCPU*100) / 100
|
||||
serverStats.AvgMemory = math.Round(avgMemory*100) / 100
|
||||
serverStats.AvgDisk = math.Round(avgDisk*100) / 100
|
||||
|
||||
stats.ServerStats = serverStats
|
||||
|
||||
// K8s集群统计
|
||||
var k8sStats models.K8sClusterStats
|
||||
h.db.Model(&models.K8sCluster{}).Count(&k8sStats.Total)
|
||||
h.db.Model(&models.K8sCluster{}).Where("status = ?", "healthy").Count(&k8sStats.Healthy)
|
||||
h.db.Model(&models.K8sCluster{}).Where("status = ?", "warning").Count(&k8sStats.Warning)
|
||||
h.db.Model(&models.K8sCluster{}).Where("status = ?", "error").Count(&k8sStats.Error)
|
||||
|
||||
var totalNodes, totalPods, totalServices int64
|
||||
h.db.Model(&models.K8sNode{}).Count(&totalNodes)
|
||||
h.db.Model(&models.K8sPod{}).Count(&totalPods)
|
||||
h.db.Model(&models.K8sService{}).Count(&totalServices)
|
||||
k8sStats.TotalNodes = totalNodes
|
||||
k8sStats.TotalPods = totalPods
|
||||
k8sStats.TotalServices = totalServices
|
||||
|
||||
stats.K8sStats = k8sStats
|
||||
|
||||
// 告警统计
|
||||
var alertStats models.AlertStats
|
||||
h.db.Model(&models.AlertEvent{}).Count(&alertStats.Total)
|
||||
h.db.Model(&models.AlertEvent{}).Where("status = ?", "firing").Count(&alertStats.Firing)
|
||||
h.db.Model(&models.AlertEvent{}).Where("status = ?", "resolved").Count(&alertStats.Resolved)
|
||||
h.db.Model(&models.AlertEvent{}).Where("severity = ?", "critical").Count(&alertStats.Critical)
|
||||
h.db.Model(&models.AlertEvent{}).Where("severity = ?", "warning").Count(&alertStats.Warning)
|
||||
h.db.Model(&models.AlertEvent{}).Where("severity = ?", "info").Count(&alertStats.Info)
|
||||
|
||||
// 今日告警
|
||||
today := time.Now().Format("2006-01-02")
|
||||
h.db.Model(&models.AlertEvent{}).Where("DATE(created_at) = ?", today).Count(&alertStats.TodayCount)
|
||||
|
||||
// 本周告警
|
||||
weekAgo := time.Now().AddDate(0, 0, -7).Format("2006-01-02")
|
||||
h.db.Model(&models.AlertEvent{}).Where("created_at >= ?", weekAgo).Count(&alertStats.WeekCount)
|
||||
|
||||
stats.AlertStats = alertStats
|
||||
|
||||
// 流水线统计
|
||||
var pipelineStats models.PipelineStats
|
||||
h.db.Model(&models.PipelineRun{}).Count(&pipelineStats.Total)
|
||||
h.db.Model(&models.PipelineRun{}).Where("status = ?", "success").Count(&pipelineStats.Success)
|
||||
h.db.Model(&models.PipelineRun{}).Where("status = ?", "failed").Count(&pipelineStats.Failed)
|
||||
h.db.Model(&models.PipelineRun{}).Where("status = ?", "running").Count(&pipelineStats.Running)
|
||||
h.db.Model(&models.PipelineRun{}).Where("DATE(created_at) = ?", today).Count(&pipelineStats.TodayCount)
|
||||
|
||||
if pipelineStats.Total > 0 {
|
||||
pipelineStats.SuccessRate = math.Round(float64(pipelineStats.Success)/float64(pipelineStats.Total)*10000) / 100
|
||||
}
|
||||
|
||||
stats.PipelineStats = pipelineStats
|
||||
|
||||
// 成本统计
|
||||
costStats := models.CostStats{
|
||||
TotalCost: 450000,
|
||||
MonthCost: 107700,
|
||||
CloudCosts: []models.CloudCost{
|
||||
{Cloud: "阿里云", Cost: 28500},
|
||||
{Cloud: "AWS", Cost: 42000},
|
||||
{Cloud: "腾讯云", Cost: 15800},
|
||||
{Cloud: "华为云", Cost: 8900},
|
||||
{Cloud: "Azure", Cost: 12500},
|
||||
},
|
||||
MonthTrend: []models.MonthTrend{
|
||||
{Month: "1月", Cost: 85000},
|
||||
{Month: "2月", Cost: 92000},
|
||||
{Month: "3月", Cost: 88000},
|
||||
{Month: "4月", Cost: 95000},
|
||||
{Month: "5月", Cost: 107700},
|
||||
},
|
||||
}
|
||||
stats.CostStats = costStats
|
||||
|
||||
// Top资源使用
|
||||
var topCPU, topMemory, topDisk []models.Server
|
||||
h.db.Order("cpu_usage DESC").Limit(5).Find(&topCPU)
|
||||
h.db.Order("memory_usage DESC").Limit(5).Find(&topMemory)
|
||||
h.db.Order("disk_usage DESC").Limit(5).Find(&topDisk)
|
||||
|
||||
var topResourceUsage models.TopResourceUsage
|
||||
for _, s := range topCPU {
|
||||
topResourceUsage.CPU = append(topResourceUsage.CPU, models.ServerResource{
|
||||
ID: s.ID,
|
||||
Hostname: s.Hostname,
|
||||
IP: s.IP,
|
||||
Usage: s.CPUUsage,
|
||||
})
|
||||
}
|
||||
for _, s := range topMemory {
|
||||
topResourceUsage.Memory = append(topResourceUsage.Memory, models.ServerResource{
|
||||
ID: s.ID,
|
||||
Hostname: s.Hostname,
|
||||
IP: s.IP,
|
||||
Usage: s.MemoryUsage,
|
||||
})
|
||||
}
|
||||
for _, s := range topDisk {
|
||||
topResourceUsage.Disk = append(topResourceUsage.Disk, models.ServerResource{
|
||||
ID: s.ID,
|
||||
Hostname: s.Hostname,
|
||||
IP: s.IP,
|
||||
Usage: s.DiskUsage,
|
||||
})
|
||||
}
|
||||
stats.TopResourceUsage = topResourceUsage
|
||||
|
||||
// 即将过期的证书
|
||||
var domains []models.Domain
|
||||
h.db.Where("ssl_enabled = ?", true).Find(&domains)
|
||||
|
||||
var expiringCerts []models.DomainCert
|
||||
now := time.Now()
|
||||
for _, d := range domains {
|
||||
if d.SSLExpireDate != nil {
|
||||
days := int(d.SSLExpireDate.Sub(now).Hours() / 24)
|
||||
if days < 90 {
|
||||
expiringCerts = append(expiringCerts, models.DomainCert{
|
||||
ID: d.ID,
|
||||
Name: d.Name,
|
||||
ExpireDate: d.SSLExpireDate.Format("2006-01-02"),
|
||||
DaysUntilExpiry: days,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
stats.ExpiringCerts = expiringCerts
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: stats,
|
||||
})
|
||||
}
|
||||
|
||||
// GetRecentAlerts 获取最近告警
|
||||
// @Summary 获取最近告警
|
||||
// @Description 获取最近触发的告警事件
|
||||
// @Tags 仪表盘
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param limit query int false "数量限制"
|
||||
// @Success 200 {object} models.Response{data=[]models.AlertEvent}
|
||||
// @Router /dashboard/recent-alerts [get]
|
||||
func (h *DashboardHandler) GetRecentAlerts(c *gin.Context) {
|
||||
limit := 10
|
||||
if l := c.Query("limit"); l != "" {
|
||||
if parsed, err := time.Parse("2006-01-02", l); err == nil {
|
||||
_ = parsed
|
||||
}
|
||||
}
|
||||
|
||||
var alerts []models.AlertEvent
|
||||
h.db.Preload("Rule").Where("status = ?", "firing").
|
||||
Order("created_at DESC").
|
||||
Limit(limit).
|
||||
Find(&alerts)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: alerts,
|
||||
})
|
||||
}
|
||||
|
||||
// GetRecentReleases 获取最近发布
|
||||
// @Summary 获取最近发布
|
||||
// @Description 获取最近的应用发布记录
|
||||
// @Tags 仪表盘
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param limit query int false "数量限制"
|
||||
// @Success 200 {object} models.Response{data=[]models.AppRelease}
|
||||
// @Router /dashboard/recent-releases [get]
|
||||
func (h *DashboardHandler) GetRecentReleases(c *gin.Context) {
|
||||
limit := 5
|
||||
|
||||
var releases []models.AppRelease
|
||||
h.db.Order("created_at DESC").
|
||||
Limit(limit).
|
||||
Find(&releases)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: releases,
|
||||
})
|
||||
}
|
||||
|
||||
// GetMetrics 获取监控指标
|
||||
// @Summary 获取监控指标
|
||||
// @Description 获取系统监控指标数据
|
||||
// @Tags 仪表盘
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param hours query int false "小时数"
|
||||
// @Success 200 {object} models.Response{data=[]models.MetricData}
|
||||
// @Router /dashboard/metrics [get]
|
||||
func (h *DashboardHandler) GetMetrics(c *gin.Context) {
|
||||
hours := 24
|
||||
|
||||
// 生成模拟数据
|
||||
var metrics []models.MetricData
|
||||
now := time.Now()
|
||||
|
||||
for i := 0; i < hours; i++ {
|
||||
timestamp := now.Add(-time.Duration(hours-i) * time.Hour)
|
||||
metrics = append(metrics, models.MetricData{
|
||||
Name: "cpu",
|
||||
Value: 40 + float64(i%30),
|
||||
Timestamp: timestamp,
|
||||
})
|
||||
metrics = append(metrics, models.MetricData{
|
||||
Name: "memory",
|
||||
Value: 50 + float64(i%20),
|
||||
Timestamp: timestamp,
|
||||
})
|
||||
metrics = append(metrics, models.MetricData{
|
||||
Name: "disk",
|
||||
Value: 45 + float64(i%10),
|
||||
Timestamp: timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: metrics,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"ezops/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// K8sHandler K8s处理器
|
||||
type K8sHandler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewK8sHandler 创建K8s处理器
|
||||
func NewK8sHandler(db *gorm.DB) *K8sHandler {
|
||||
return &K8sHandler{db: db}
|
||||
}
|
||||
|
||||
// ListClusters 获取集群列表
|
||||
// @Summary 获取集群列表
|
||||
// @Description 获取所有K8s集群列表
|
||||
// @Tags K8s集群
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页数量"
|
||||
// @Success 200 {object} models.Response{data=models.ListResponse}
|
||||
// @Router /k8s-clusters [get]
|
||||
func (h *K8sHandler) ListClusters(c *gin.Context) {
|
||||
var query models.PageQuery
|
||||
c.ShouldBindQuery(&query)
|
||||
|
||||
var total int64
|
||||
h.db.Model(&models.K8sCluster{}).Count(&total)
|
||||
|
||||
var clusters []models.K8sCluster
|
||||
h.db.Offset(query.GetOffset()).Limit(query.GetLimit()).Find(&clusters)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: models.ListResponse{
|
||||
List: clusters,
|
||||
Total: total,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetCluster 获取集群详情
|
||||
// @Summary 获取集群详情
|
||||
// @Description 获取指定K8s集群的详细信息
|
||||
// @Tags K8s集群
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "集群ID"
|
||||
// @Success 200 {object} models.Response{data=models.K8sCluster}
|
||||
// @Router /k8s-clusters/{id} [get]
|
||||
func (h *K8sHandler) GetCluster(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var cluster models.K8sCluster
|
||||
if err := h.db.First(&cluster, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "集群不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: cluster,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateCluster 创建集群
|
||||
// @Summary 创建集群
|
||||
// @Description 创建新K8s集群
|
||||
// @Tags K8s集群
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body models.CreateK8sClusterRequest true "创建请求"
|
||||
// @Success 200 {object} models.Response{data=models.K8sCluster}
|
||||
// @Router /k8s-clusters [post]
|
||||
func (h *K8sHandler) CreateCluster(c *gin.Context) {
|
||||
var req models.CreateK8sClusterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
cluster := models.K8sCluster{
|
||||
Name: req.Name,
|
||||
Code: req.Code,
|
||||
Version: req.Version,
|
||||
APIEndpoint: req.APIEndpoint,
|
||||
KubeConfig: req.KubeConfig,
|
||||
Description: req.Description,
|
||||
Status: "healthy",
|
||||
}
|
||||
|
||||
if err := h.db.Create(&cluster).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "创建失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "创建成功",
|
||||
Data: cluster,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateCluster 更新集群
|
||||
// @Summary 更新集群
|
||||
// @Description 更新指定K8s集群信息
|
||||
// @Tags K8s集群
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "集群ID"
|
||||
// @Param request body models.CreateK8sClusterRequest true "更新请求"
|
||||
// @Success 200 {object} models.Response{data=models.K8sCluster}
|
||||
// @Router /k8s-clusters/{id} [put]
|
||||
func (h *K8sHandler) UpdateCluster(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var req models.CreateK8sClusterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var cluster models.K8sCluster
|
||||
if err := h.db.First(&cluster, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "集群不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
updates := map[string]interface{}{
|
||||
"name": req.Name,
|
||||
"version": req.Version,
|
||||
"api_endpoint": req.APIEndpoint,
|
||||
"kube_config": req.KubeConfig,
|
||||
"description": req.Description,
|
||||
}
|
||||
|
||||
if err := h.db.Model(&cluster).Updates(updates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "更新失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
h.db.First(&cluster, id)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "更新成功",
|
||||
Data: cluster,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteCluster 删除集群
|
||||
// @Summary 删除集群
|
||||
// @Description 删除指定K8s集群
|
||||
// @Tags K8s集群
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "集群ID"
|
||||
// @Success 200 {object} models.Response
|
||||
// @Router /k8s-clusters/{id} [delete]
|
||||
func (h *K8sHandler) DeleteCluster(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
if err := h.db.Delete(&models.K8sCluster{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "删除失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
// ListNodes 获取节点列表
|
||||
// @Summary 获取节点列表
|
||||
// @Description 获取指定集群的节点列表
|
||||
// @Tags K8s集群
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "集群ID"
|
||||
// @Success 200 {object} models.Response{data=[]models.K8sNode}
|
||||
// @Router /k8s-clusters/{id}/nodes [get]
|
||||
func (h *K8sHandler) ListNodes(c *gin.Context) {
|
||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var nodes []models.K8sNode
|
||||
h.db.Where("cluster_id = ?", clusterID).Find(&nodes)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: nodes,
|
||||
})
|
||||
}
|
||||
|
||||
// ListPods 获取Pod列表
|
||||
// @Summary 获取Pod列表
|
||||
// @Description 获取指定集群的Pod列表
|
||||
// @Tags K8s集群
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "集群ID"
|
||||
// @Param namespace query string false "命名空间"
|
||||
// @Success 200 {object} models.Response{data=[]models.K8sPod}
|
||||
// @Router /k8s-clusters/{id}/pods [get]
|
||||
func (h *K8sHandler) ListPods(c *gin.Context) {
|
||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||
namespace := c.Query("namespace")
|
||||
|
||||
db := h.db.Where("cluster_id = ?", clusterID)
|
||||
if namespace != "" {
|
||||
db = db.Where("namespace = ?", namespace)
|
||||
}
|
||||
|
||||
var pods []models.K8sPod
|
||||
db.Find(&pods)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: pods,
|
||||
})
|
||||
}
|
||||
|
||||
// ListDeployments 获取Deployment列表
|
||||
// @Summary 获取Deployment列表
|
||||
// @Description 获取指定集群的Deployment列表
|
||||
// @Tags K8s集群
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "集群ID"
|
||||
// @Param namespace query string false "命名空间"
|
||||
// @Success 200 {object} models.Response{data=[]models.K8sDeployment}
|
||||
// @Router /k8s-clusters/{id}/deployments [get]
|
||||
func (h *K8sHandler) ListDeployments(c *gin.Context) {
|
||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||
namespace := c.Query("namespace")
|
||||
|
||||
db := h.db.Where("cluster_id = ?", clusterID)
|
||||
if namespace != "" {
|
||||
db = db.Where("namespace = ?", namespace)
|
||||
}
|
||||
|
||||
var deployments []models.K8sDeployment
|
||||
db.Find(&deployments)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: deployments,
|
||||
})
|
||||
}
|
||||
|
||||
// ListServices 获取Service列表
|
||||
// @Summary 获取Service列表
|
||||
// @Description 获取指定集群的Service列表
|
||||
// @Tags K8s集群
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "集群ID"
|
||||
// @Param namespace query string false "命名空间"
|
||||
// @Success 200 {object} models.Response{data=[]models.K8sService}
|
||||
// @Router /k8s-clusters/{id}/services [get]
|
||||
func (h *K8sHandler) ListServices(c *gin.Context) {
|
||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||
namespace := c.Query("namespace")
|
||||
|
||||
db := h.db.Where("cluster_id = ?", clusterID)
|
||||
if namespace != "" {
|
||||
db = db.Where("namespace = ?", namespace)
|
||||
}
|
||||
|
||||
var services []models.K8sService
|
||||
db.Find(&services)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: services,
|
||||
})
|
||||
}
|
||||
|
||||
// ListIngresses 获取Ingress列表
|
||||
// @Summary 获取Ingress列表
|
||||
// @Description 获取指定集群的Ingress列表
|
||||
// @Tags K8s集群
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "集群ID"
|
||||
// @Param namespace query string false "命名空间"
|
||||
// @Success 200 {object} models.Response{data=[]models.K8sIngress}
|
||||
// @Router /k8s-clusters/{id}/ingresses [get]
|
||||
func (h *K8sHandler) ListIngresses(c *gin.Context) {
|
||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||
namespace := c.Query("namespace")
|
||||
|
||||
db := h.db.Where("cluster_id = ?", clusterID)
|
||||
if namespace != "" {
|
||||
db = db.Where("namespace = ?", namespace)
|
||||
}
|
||||
|
||||
var ingresses []models.K8sIngress
|
||||
db.Find(&ingresses)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: ingresses,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"ezops/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// NavigationHandler 导航处理器
|
||||
type NavigationHandler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewNavigationHandler 创建导航处理器
|
||||
func NewNavigationHandler(db *gorm.DB) *NavigationHandler {
|
||||
return &NavigationHandler{db: db}
|
||||
}
|
||||
|
||||
// ListGroups 获取导航分组列表
|
||||
// @Summary 获取导航分组列表
|
||||
// @Description 获取所有导航分组列表
|
||||
// @Tags 站内导航
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} models.Response{data=[]models.NavigationGroup}
|
||||
// @Router /navigations/groups [get]
|
||||
func (h *NavigationHandler) ListGroups(c *gin.Context) {
|
||||
var groups []models.NavigationGroup
|
||||
h.db.Order("sort ASC, created_at ASC").Find(&groups)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: groups,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateGroup 创建导航分组
|
||||
// @Summary 创建导航分组
|
||||
// @Description 创建新导航分组
|
||||
// @Tags 站内导航
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body models.CreateNavigationGroupRequest true "创建请求"
|
||||
// @Success 200 {object} models.Response{data=models.NavigationGroup}
|
||||
// @Router /navigations/groups [post]
|
||||
func (h *NavigationHandler) CreateGroup(c *gin.Context) {
|
||||
var req models.CreateNavigationGroupRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
group := models.NavigationGroup{
|
||||
Name: req.Name,
|
||||
Code: req.Code,
|
||||
Description: req.Description,
|
||||
Icon: req.Icon,
|
||||
Sort: req.Sort,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
if err := h.db.Create(&group).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "创建失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "创建成功",
|
||||
Data: group,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateGroup 更新导航分组
|
||||
// @Summary 更新导航分组
|
||||
// @Description 更新指定导航分组
|
||||
// @Tags 站内导航
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "分组ID"
|
||||
// @Param request body models.UpdateNavigationGroupRequest true "更新请求"
|
||||
// @Success 200 {object} models.Response{data=models.NavigationGroup}
|
||||
// @Router /navigations/groups/{id} [put]
|
||||
func (h *NavigationHandler) UpdateGroup(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var req models.UpdateNavigationGroupRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var group models.NavigationGroup
|
||||
if err := h.db.First(&group, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.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.Icon != "" {
|
||||
updates["icon"] = req.Icon
|
||||
}
|
||||
updates["sort"] = req.Sort
|
||||
if req.Enabled != nil {
|
||||
updates["enabled"] = *req.Enabled
|
||||
}
|
||||
|
||||
if err := h.db.Model(&group).Updates(updates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "更新失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
h.db.First(&group, id)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "更新成功",
|
||||
Data: group,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteGroup 删除导航分组
|
||||
// @Summary 删除导航分组
|
||||
// @Description 删除指定导航分组
|
||||
// @Tags 站内导航
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "分组ID"
|
||||
// @Success 200 {object} models.Response
|
||||
// @Router /navigations/groups/{id} [delete]
|
||||
func (h *NavigationHandler) DeleteGroup(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
// 删除分组下的所有链接
|
||||
if err := h.db.Where("group_id = ?", id).Delete(&models.NavigationLink{}).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "删除链接失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&models.NavigationGroup{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "删除失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
// ListLinks 获取导航链接列表
|
||||
// @Summary 获取导航链接列表
|
||||
// @Description 获取所有导航链接列表
|
||||
// @Tags 站内导航
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param group_id query int false "分组ID"
|
||||
// @Param keyword query string false "搜索关键词"
|
||||
// @Success 200 {object} models.Response{data=[]models.NavigationLink}
|
||||
// @Router /navigations/links [get]
|
||||
func (h *NavigationHandler) ListLinks(c *gin.Context) {
|
||||
db := h.db.Model(&models.NavigationLink{}).Preload("Group")
|
||||
|
||||
// 分组过滤
|
||||
if groupID := c.Query("group_id"); groupID != "" {
|
||||
db = db.Where("group_id = ?", groupID)
|
||||
}
|
||||
|
||||
// 关键词搜索
|
||||
if keyword := c.Query("keyword"); keyword != "" {
|
||||
db = db.Where("name LIKE ? OR description LIKE ?", "%"+keyword+"%", "%"+keyword+"%")
|
||||
}
|
||||
|
||||
var links []models.NavigationLink
|
||||
db.Order("sort ASC, created_at ASC").Find(&links)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: links,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateLink 创建导航链接
|
||||
// @Summary 创建导航链接
|
||||
// @Description 创建新导航链接
|
||||
// @Tags 站内导航
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body models.CreateNavigationLinkRequest true "创建请求"
|
||||
// @Success 200 {object} models.Response{data=models.NavigationLink}
|
||||
// @Router /navigations/links [post]
|
||||
func (h *NavigationHandler) CreateLink(c *gin.Context) {
|
||||
var req models.CreateNavigationLinkRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
tags, _ := json.Marshal(req.Tags)
|
||||
|
||||
link := models.NavigationLink{
|
||||
GroupID: req.GroupID,
|
||||
Name: req.Name,
|
||||
URL: req.URL,
|
||||
Description: req.Description,
|
||||
Icon: req.Icon,
|
||||
Tags: string(tags),
|
||||
Sort: req.Sort,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
if err := h.db.Create(&link).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "创建失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新分组链接数
|
||||
h.db.Model(&models.NavigationGroup{}).Where("id = ?", req.GroupID).
|
||||
UpdateColumn("link_count", h.db.Model(&models.NavigationLink{}).Where("group_id = ?", req.GroupID).Select("COUNT(*)"))
|
||||
|
||||
h.db.Preload("Group").First(&link, link.ID)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "创建成功",
|
||||
Data: link,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateLink 更新导航链接
|
||||
// @Summary 更新导航链接
|
||||
// @Description 更新指定导航链接
|
||||
// @Tags 站内导航
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "链接ID"
|
||||
// @Param request body models.UpdateNavigationLinkRequest true "更新请求"
|
||||
// @Success 200 {object} models.Response{data=models.NavigationLink}
|
||||
// @Router /navigations/links/{id} [put]
|
||||
func (h *NavigationHandler) UpdateLink(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var req models.UpdateNavigationLinkRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var link models.NavigationLink
|
||||
if err := h.db.First(&link, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "链接不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
updates := map[string]interface{}{}
|
||||
if req.GroupID > 0 {
|
||||
updates["group_id"] = req.GroupID
|
||||
}
|
||||
if req.Name != "" {
|
||||
updates["name"] = req.Name
|
||||
}
|
||||
if req.URL != "" {
|
||||
updates["url"] = req.URL
|
||||
}
|
||||
if req.Description != "" {
|
||||
updates["description"] = req.Description
|
||||
}
|
||||
if req.Icon != "" {
|
||||
updates["icon"] = req.Icon
|
||||
}
|
||||
if len(req.Tags) > 0 {
|
||||
tags, _ := json.Marshal(req.Tags)
|
||||
updates["tags"] = string(tags)
|
||||
}
|
||||
updates["sort"] = req.Sort
|
||||
if req.Enabled != nil {
|
||||
updates["enabled"] = *req.Enabled
|
||||
}
|
||||
|
||||
if err := h.db.Model(&link).Updates(updates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "更新失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
h.db.Preload("Group").First(&link, id)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "更新成功",
|
||||
Data: link,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteLink 删除导航链接
|
||||
// @Summary 删除导航链接
|
||||
// @Description 删除指定导航链接
|
||||
// @Tags 站内导航
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "链接ID"
|
||||
// @Success 200 {object} models.Response
|
||||
// @Router /navigations/links/{id} [delete]
|
||||
func (h *NavigationHandler) DeleteLink(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var link models.NavigationLink
|
||||
h.db.First(&link, id)
|
||||
|
||||
if err := h.db.Delete(&models.NavigationLink{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "删除失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新分组链接数
|
||||
if link.GroupID > 0 {
|
||||
h.db.Model(&models.NavigationGroup{}).Where("id = ?", link.GroupID).
|
||||
UpdateColumn("link_count", h.db.Model(&models.NavigationLink{}).Where("group_id = ?", link.GroupID).Select("COUNT(*)"))
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
// ToggleFavorite 切换收藏状态
|
||||
// @Summary 切换收藏状态
|
||||
// @Description 切换导航链接的收藏状态
|
||||
// @Tags 站内导航
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "链接ID"
|
||||
// @Param request body models.ToggleFavoriteRequest true "收藏请求"
|
||||
// @Success 200 {object} models.Response{data=models.NavigationLink}
|
||||
// @Router /navigations/links/{id}/favorite [post]
|
||||
func (h *NavigationHandler) ToggleFavorite(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var req models.ToggleFavoriteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var link models.NavigationLink
|
||||
if err := h.db.First(&link, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "链接不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
link.IsFavorite = req.IsFavorite
|
||||
if err := h.db.Save(&link).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "操作失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "操作成功",
|
||||
Data: link,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"ezops/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// PipelineHandler 流水线处理器
|
||||
type PipelineHandler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewPipelineHandler 创建流水线处理器
|
||||
func NewPipelineHandler(db *gorm.DB) *PipelineHandler {
|
||||
return &PipelineHandler{db: db}
|
||||
}
|
||||
|
||||
// ListPipelines 获取流水线列表
|
||||
// @Summary 获取流水线列表
|
||||
// @Description 获取所有流水线列表
|
||||
// @Tags 流水线
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页数量"
|
||||
// @Success 200 {object} models.Response{data=models.ListResponse}
|
||||
// @Router /pipelines [get]
|
||||
func (h *PipelineHandler) ListPipelines(c *gin.Context) {
|
||||
var query models.PageQuery
|
||||
c.ShouldBindQuery(&query)
|
||||
|
||||
var total int64
|
||||
h.db.Model(&models.Pipeline{}).Count(&total)
|
||||
|
||||
var pipelines []models.Pipeline
|
||||
h.db.Offset(query.GetOffset()).Limit(query.GetLimit()).Find(&pipelines)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: models.ListResponse{
|
||||
List: pipelines,
|
||||
Total: total,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetPipeline 获取流水线详情
|
||||
// @Summary 获取流水线详情
|
||||
// @Description 获取指定流水线的详细信息
|
||||
// @Tags 流水线
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "流水线ID"
|
||||
// @Success 200 {object} models.Response{data=models.Pipeline}
|
||||
// @Router /pipelines/{id} [get]
|
||||
func (h *PipelineHandler) GetPipeline(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var pipeline models.Pipeline
|
||||
if err := h.db.First(&pipeline, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "流水线不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: pipeline,
|
||||
})
|
||||
}
|
||||
|
||||
// CreatePipeline 创建流水线
|
||||
// @Summary 创建流水线
|
||||
// @Description 创建新流水线
|
||||
// @Tags 流水线
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body models.CreatePipelineRequest true "创建请求"
|
||||
// @Success 200 {object} models.Response{data=models.Pipeline}
|
||||
// @Router /pipelines [post]
|
||||
func (h *PipelineHandler) CreatePipeline(c *gin.Context) {
|
||||
var req models.CreatePipelineRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
pipeline := models.Pipeline{
|
||||
Name: req.Name,
|
||||
Code: req.Code,
|
||||
Description: req.Description,
|
||||
Type: req.Type,
|
||||
Source: req.Source,
|
||||
Branch: req.Branch,
|
||||
Dockerfile: req.Dockerfile,
|
||||
Stages: req.Stages,
|
||||
Variables: req.Variables,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
if err := h.db.Create(&pipeline).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "创建失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "创建成功",
|
||||
Data: pipeline,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdatePipeline 更新流水线
|
||||
// @Summary 更新流水线
|
||||
// @Description 更新指定流水线
|
||||
// @Tags 流水线
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "流水线ID"
|
||||
// @Param request body models.CreatePipelineRequest true "更新请求"
|
||||
// @Success 200 {object} models.Response{data=models.Pipeline}
|
||||
// @Router /pipelines/{id} [put]
|
||||
func (h *PipelineHandler) UpdatePipeline(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var req models.CreatePipelineRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var pipeline models.Pipeline
|
||||
if err := h.db.First(&pipeline, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "流水线不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
updates := map[string]interface{}{
|
||||
"name": req.Name,
|
||||
"description": req.Description,
|
||||
"type": req.Type,
|
||||
"source": req.Source,
|
||||
"branch": req.Branch,
|
||||
"dockerfile": req.Dockerfile,
|
||||
"stages": req.Stages,
|
||||
"variables": req.Variables,
|
||||
}
|
||||
|
||||
if err := h.db.Model(&pipeline).Updates(updates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "更新失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
h.db.First(&pipeline, id)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "更新成功",
|
||||
Data: pipeline,
|
||||
})
|
||||
}
|
||||
|
||||
// DeletePipeline 删除流水线
|
||||
// @Summary 删除流水线
|
||||
// @Description 删除指定流水线
|
||||
// @Tags 流水线
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "流水线ID"
|
||||
// @Success 200 {object} models.Response
|
||||
// @Router /pipelines/{id} [delete]
|
||||
func (h *PipelineHandler) DeletePipeline(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
if err := h.db.Delete(&models.Pipeline{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "删除失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
// TriggerPipeline 触发流水线
|
||||
// @Summary 触发流水线
|
||||
// @Description 手动触发流水线执行
|
||||
// @Tags 流水线
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "流水线ID"
|
||||
// @Param request body models.TriggerPipelineRequest true "触发请求"
|
||||
// @Success 200 {object} models.Response{data=models.PipelineRun}
|
||||
// @Router /pipelines/{id}/trigger [post]
|
||||
func (h *PipelineHandler) TriggerPipeline(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var pipeline models.Pipeline
|
||||
if err := h.db.First(&pipeline, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "流水线不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var req models.TriggerPipelineRequest
|
||||
c.ShouldBindJSON(&req)
|
||||
|
||||
// 获取最新构建号
|
||||
var maxBuildNumber int
|
||||
h.db.Model(&models.PipelineRun{}).Where("pipeline_id = ?", id).Select("COALESCE(MAX(build_number), 0)").Scan(&maxBuildNumber)
|
||||
|
||||
run := models.PipelineRun{
|
||||
PipelineID: uint(id),
|
||||
BuildNumber: maxBuildNumber + 1,
|
||||
Status: "pending",
|
||||
Branch: req.Branch,
|
||||
TriggeredBy: "manual",
|
||||
}
|
||||
|
||||
if req.Branch == "" {
|
||||
run.Branch = pipeline.Branch
|
||||
}
|
||||
|
||||
if err := h.db.Create(&run).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "触发失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "触发成功",
|
||||
Data: run,
|
||||
})
|
||||
}
|
||||
|
||||
// ListRuns 获取运行记录列表
|
||||
// @Summary 获取运行记录列表
|
||||
// @Description 获取指定流水线的运行记录
|
||||
// @Tags 流水线
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "流水线ID"
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页数量"
|
||||
// @Success 200 {object} models.Response{data=models.ListResponse}
|
||||
// @Router /pipelines/{id}/runs [get]
|
||||
func (h *PipelineHandler) ListRuns(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var query models.PageQuery
|
||||
c.ShouldBindQuery(&query)
|
||||
|
||||
db := h.db.Model(&models.PipelineRun{}).Where("pipeline_id = ?", id)
|
||||
|
||||
var total int64
|
||||
db.Count(&total)
|
||||
|
||||
var runs []models.PipelineRun
|
||||
db.Order("created_at DESC").
|
||||
Offset(query.GetOffset()).
|
||||
Limit(query.GetLimit()).
|
||||
Find(&runs)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: models.ListResponse{
|
||||
List: runs,
|
||||
Total: total,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ListReleases 获取发布列表
|
||||
// @Summary 获取发布列表
|
||||
// @Description 获取所有应用发布列表
|
||||
// @Tags 应用发布
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页数量"
|
||||
// @Success 200 {object} models.Response{data=models.ListResponse}
|
||||
// @Router /releases [get]
|
||||
func (h *PipelineHandler) ListReleases(c *gin.Context) {
|
||||
var query models.PageQuery
|
||||
c.ShouldBindQuery(&query)
|
||||
|
||||
var total int64
|
||||
h.db.Model(&models.AppRelease{}).Count(&total)
|
||||
|
||||
var releases []models.AppRelease
|
||||
h.db.Order("created_at DESC").
|
||||
Offset(query.GetOffset()).
|
||||
Limit(query.GetLimit()).
|
||||
Find(&releases)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: models.ListResponse{
|
||||
List: releases,
|
||||
Total: total,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// CreateRelease 创建发布
|
||||
// @Summary 创建发布
|
||||
// @Description 创建新应用发布
|
||||
// @Tags 应用发布
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body models.CreateAppReleaseRequest true "创建请求"
|
||||
// @Success 200 {object} models.Response{data=models.AppRelease}
|
||||
// @Router /releases [post]
|
||||
func (h *PipelineHandler) CreateRelease(c *gin.Context) {
|
||||
var req models.CreateAppReleaseRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
release := models.AppRelease{
|
||||
AppName: req.AppName,
|
||||
Version: req.Version,
|
||||
Environment: req.Environment,
|
||||
Status: "pending",
|
||||
CommitID: req.CommitID,
|
||||
CommitMsg: req.CommitMsg,
|
||||
Servers: "[]",
|
||||
Description: req.Description,
|
||||
}
|
||||
|
||||
if err := h.db.Create(&release).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "创建失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "创建成功",
|
||||
Data: release,
|
||||
})
|
||||
}
|
||||
|
||||
// ApproveRelease 审批发布
|
||||
// @Summary 审批发布
|
||||
// @Description 审批应用发布
|
||||
// @Tags 应用发布
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "发布ID"
|
||||
// @Success 200 {object} models.Response{data=models.AppRelease}
|
||||
// @Router /releases/{id}/approve [post]
|
||||
func (h *PipelineHandler) ApproveRelease(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var release models.AppRelease
|
||||
if err := h.db.First(&release, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "发布不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
release.Status = "approved"
|
||||
// release.Approver = middleware.GetUsername(c)
|
||||
// now := time.Now()
|
||||
// release.ApprovedAt = &now
|
||||
|
||||
if err := h.db.Save(&release).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "审批失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "审批成功",
|
||||
Data: release,
|
||||
})
|
||||
}
|
||||
|
||||
// DeployRelease 部署发布
|
||||
// @Summary 部署发布
|
||||
// @Description 部署应用发布
|
||||
// @Tags 应用发布
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "发布ID"
|
||||
// @Success 200 {object} models.Response{data=models.AppRelease}
|
||||
// @Router /releases/{id}/deploy [post]
|
||||
func (h *PipelineHandler) DeployRelease(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var release models.AppRelease
|
||||
if err := h.db.First(&release, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "发布不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
release.Status = "deploying"
|
||||
if err := h.db.Save(&release).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "部署失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "部署中",
|
||||
Data: release,
|
||||
})
|
||||
}
|
||||
|
||||
// RollbackRelease 回滚发布
|
||||
// @Summary 回滚发布
|
||||
// @Description 回滚应用发布
|
||||
// @Tags 应用发布
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "发布ID"
|
||||
// @Success 200 {object} models.Response{data=models.AppRelease}
|
||||
// @Router /releases/{id}/rollback [post]
|
||||
func (h *PipelineHandler) RollbackRelease(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var release models.AppRelease
|
||||
if err := h.db.First(&release, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "发布不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
release.Status = "rolled_back"
|
||||
// now := time.Now()
|
||||
// release.RollbackAt = &now
|
||||
|
||||
if err := h.db.Save(&release).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "回滚失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "回滚成功",
|
||||
Data: release,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,566 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"ezops/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ServerHandler 服务器处理器
|
||||
type ServerHandler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewServerHandler 创建服务器处理器
|
||||
func NewServerHandler(db *gorm.DB) *ServerHandler {
|
||||
return &ServerHandler{db: db}
|
||||
}
|
||||
|
||||
// ListServers 获取服务器列表
|
||||
// @Summary 获取服务器列表
|
||||
// @Description 获取所有服务器列表
|
||||
// @Tags 服务器
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页数量"
|
||||
// @Param keyword query string false "搜索关键词"
|
||||
// @Param status query string false "状态过滤"
|
||||
// @Param region_id query int false "区域ID"
|
||||
// @Success 200 {object} models.Response{data=models.ListResponse}
|
||||
// @Router /servers [get]
|
||||
func (h *ServerHandler) ListServers(c *gin.Context) {
|
||||
var query models.PageQuery
|
||||
if err := c.ShouldBindQuery(&query); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
db := h.db.Model(&models.Server{})
|
||||
|
||||
// 关键词搜索
|
||||
if query.Keyword != "" {
|
||||
db = db.Where("hostname LIKE ? OR ip LIKE ? OR public_ip LIKE ?",
|
||||
"%"+query.Keyword+"%", "%"+query.Keyword+"%", "%"+query.Keyword+"%")
|
||||
}
|
||||
|
||||
// 状态过滤
|
||||
if status := c.Query("status"); status != "" {
|
||||
db = db.Where("status = ?", status)
|
||||
}
|
||||
|
||||
// 区域过滤
|
||||
if regionID := c.Query("region_id"); regionID != "" {
|
||||
db = db.Where("region_id = ?", regionID)
|
||||
}
|
||||
|
||||
var total int64
|
||||
db.Count(&total)
|
||||
|
||||
var servers []models.Server
|
||||
db.Preload("Region").Preload("Cloud").
|
||||
Offset(query.GetOffset()).Limit(query.GetLimit()).
|
||||
Order("created_at DESC").
|
||||
Find(&servers)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: models.ListResponse{
|
||||
List: servers,
|
||||
Total: total,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetServer 获取服务器详情
|
||||
// @Summary 获取服务器详情
|
||||
// @Description 获取指定服务器的详细信息
|
||||
// @Tags 服务器
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "服务器ID"
|
||||
// @Success 200 {object} models.Response{data=models.Server}
|
||||
// @Router /servers/{id} [get]
|
||||
func (h *ServerHandler) GetServer(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var server models.Server
|
||||
if err := h.db.Preload("Region").Preload("Cloud").First(&server, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "服务器不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: server,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateServer 创建服务器
|
||||
// @Summary 创建服务器
|
||||
// @Description 创建新服务器
|
||||
// @Tags 服务器
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body models.CreateServerRequest true "创建请求"
|
||||
// @Success 200 {object} models.Response{data=models.Server}
|
||||
// @Router /servers [post]
|
||||
func (h *ServerHandler) CreateServer(c *gin.Context) {
|
||||
var req models.CreateServerRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
server := models.Server{
|
||||
Hostname: req.Hostname,
|
||||
IP: req.IP,
|
||||
PublicIP: req.PublicIP,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
SSHKey: req.SSHKey,
|
||||
AuthType: req.AuthType,
|
||||
OS: req.OS,
|
||||
CPU: req.CPU,
|
||||
Memory: req.Memory,
|
||||
Disk: req.Disk,
|
||||
RegionID: req.RegionID,
|
||||
CloudID: req.CloudID,
|
||||
Tags: req.Tags,
|
||||
Description: req.Description,
|
||||
Status: "stopped",
|
||||
}
|
||||
|
||||
if server.Port == 0 {
|
||||
server.Port = 22
|
||||
}
|
||||
if server.Username == "" {
|
||||
server.Username = "root"
|
||||
}
|
||||
if server.AuthType == "" {
|
||||
server.AuthType = "password"
|
||||
}
|
||||
|
||||
if err := h.db.Create(&server).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "创建服务器失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "创建成功",
|
||||
Data: server,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateServer 更新服务器
|
||||
// @Summary 更新服务器
|
||||
// @Description 更新指定服务器信息
|
||||
// @Tags 服务器
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "服务器ID"
|
||||
// @Param request body models.UpdateServerRequest true "更新请求"
|
||||
// @Success 200 {object} models.Response{data=models.Server}
|
||||
// @Router /servers/{id} [put]
|
||||
func (h *ServerHandler) UpdateServer(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var req models.UpdateServerRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var server models.Server
|
||||
if err := h.db.First(&server, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "服务器不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
updates := map[string]interface{}{}
|
||||
if req.Hostname != "" {
|
||||
updates["hostname"] = req.Hostname
|
||||
}
|
||||
if req.PublicIP != "" {
|
||||
updates["public_ip"] = req.PublicIP
|
||||
}
|
||||
if req.Port > 0 {
|
||||
updates["port"] = req.Port
|
||||
}
|
||||
if req.Username != "" {
|
||||
updates["username"] = req.Username
|
||||
}
|
||||
if req.Password != "" {
|
||||
updates["password"] = req.Password
|
||||
}
|
||||
if req.SSHKey != "" {
|
||||
updates["ssh_key"] = req.SSHKey
|
||||
}
|
||||
if req.AuthType != "" {
|
||||
updates["auth_type"] = req.AuthType
|
||||
}
|
||||
if req.OS != "" {
|
||||
updates["os"] = req.OS
|
||||
}
|
||||
if req.CPU > 0 {
|
||||
updates["cpu"] = req.CPU
|
||||
}
|
||||
if req.Memory > 0 {
|
||||
updates["memory"] = req.Memory
|
||||
}
|
||||
if req.Disk > 0 {
|
||||
updates["disk"] = req.Disk
|
||||
}
|
||||
if req.Status != "" {
|
||||
updates["status"] = req.Status
|
||||
}
|
||||
if req.RegionID != nil {
|
||||
updates["region_id"] = *req.RegionID
|
||||
}
|
||||
if req.CloudID != nil {
|
||||
updates["cloud_id"] = *req.CloudID
|
||||
}
|
||||
if req.Tags != "" {
|
||||
updates["tags"] = req.Tags
|
||||
}
|
||||
if req.Description != "" {
|
||||
updates["description"] = req.Description
|
||||
}
|
||||
|
||||
if err := h.db.Model(&server).Updates(updates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "更新失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
h.db.Preload("Region").Preload("Cloud").First(&server, id)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "更新成功",
|
||||
Data: server,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteServer 删除服务器
|
||||
// @Summary 删除服务器
|
||||
// @Description 删除指定服务器
|
||||
// @Tags 服务器
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "服务器ID"
|
||||
// @Success 200 {object} models.Response
|
||||
// @Router /servers/{id} [delete]
|
||||
func (h *ServerHandler) DeleteServer(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
if err := h.db.Delete(&models.Server{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "删除失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
// BatchDeleteServers 批量删除服务器
|
||||
// @Summary 批量删除服务器
|
||||
// @Description 批量删除服务器
|
||||
// @Tags 服务器
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body models.IDsRequest true "删除请求"
|
||||
// @Success 200 {object} models.Response
|
||||
// @Router /servers/batch-delete [post]
|
||||
func (h *ServerHandler) BatchDeleteServers(c *gin.Context) {
|
||||
var req models.IDsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&models.Server{}, req.IDs).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "删除失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
// GetServerStats 获取服务器统计
|
||||
// @Summary 获取服务器统计
|
||||
// @Description 获取服务器统计数据
|
||||
// @Tags 服务器
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} models.Response{data=models.ServerStats}
|
||||
// @Router /servers/stats [get]
|
||||
func (h *ServerHandler) GetServerStats(c *gin.Context) {
|
||||
var stats models.ServerStats
|
||||
|
||||
h.db.Model(&models.Server{}).Count(&stats.Total)
|
||||
h.db.Model(&models.Server{}).Where("status = ?", "running").Count(&stats.Running)
|
||||
h.db.Model(&models.Server{}).Where("status = ?", "stopped").Count(&stats.Stopped)
|
||||
h.db.Model(&models.Server{}).Where("status = ?", "maintenance").Count(&stats.Maintenance)
|
||||
h.db.Model(&models.Server{}).Where("status = ?", "error").Count(&stats.Error)
|
||||
|
||||
var avgCPU, avgMemory, avgDisk float64
|
||||
h.db.Model(&models.Server{}).Select("AVG(cpu_usage)").Scan(&avgCPU)
|
||||
h.db.Model(&models.Server{}).Select("AVG(memory_usage)").Scan(&avgMemory)
|
||||
h.db.Model(&models.Server{}).Select("AVG(disk_usage)").Scan(&avgDisk)
|
||||
|
||||
stats.AvgCPU = avgCPU
|
||||
stats.AvgMemory = avgMemory
|
||||
stats.AvgDisk = avgDisk
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: stats,
|
||||
})
|
||||
}
|
||||
|
||||
// CloudAccountHandler 云账号处理器
|
||||
type CloudAccountHandler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewCloudAccountHandler 创建云账号处理器
|
||||
func NewCloudAccountHandler(db *gorm.DB) *CloudAccountHandler {
|
||||
return &CloudAccountHandler{db: db}
|
||||
}
|
||||
|
||||
// ListCloudAccounts 获取云账号列表
|
||||
// @Summary 获取云账号列表
|
||||
// @Description 获取所有云账号列表
|
||||
// @Tags 云账号
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} models.Response{data=models.ListResponse}
|
||||
// @Router /cloud-accounts [get]
|
||||
func (h *CloudAccountHandler) ListCloudAccounts(c *gin.Context) {
|
||||
var accounts []models.CloudAccount
|
||||
h.db.Find(&accounts)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: models.ListResponse{
|
||||
List: accounts,
|
||||
Total: int64(len(accounts)),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// CreateCloudAccount 创建云账号
|
||||
// @Summary 创建云账号
|
||||
// @Description 创建新云账号
|
||||
// @Tags 云账号
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body models.CloudAccount true "创建请求"
|
||||
// @Success 200 {object} models.Response{data=models.CloudAccount}
|
||||
// @Router /cloud-accounts [post]
|
||||
func (h *CloudAccountHandler) CreateCloudAccount(c *gin.Context) {
|
||||
var account models.CloudAccount
|
||||
if err := c.ShouldBindJSON(&account); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.Create(&account).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "创建失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "创建成功",
|
||||
Data: account,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteCloudAccount 删除云账号
|
||||
// @Summary 删除云账号
|
||||
// @Description 删除指定云账号
|
||||
// @Tags 云账号
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "云账号ID"
|
||||
// @Success 200 {object} models.Response
|
||||
// @Router /cloud-accounts/{id} [delete]
|
||||
func (h *CloudAccountHandler) DeleteCloudAccount(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
if err := h.db.Delete(&models.CloudAccount{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "删除失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
// DomainHandler 域名处理器
|
||||
type DomainHandler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewDomainHandler 创建域名处理器
|
||||
func NewDomainHandler(db *gorm.DB) *DomainHandler {
|
||||
return &DomainHandler{db: db}
|
||||
}
|
||||
|
||||
// ListDomains 获取域名列表
|
||||
// @Summary 获取域名列表
|
||||
// @Description 获取所有域名列表
|
||||
// @Tags 域名
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页数量"
|
||||
// @Param keyword query string false "搜索关键词"
|
||||
// @Success 200 {object} models.Response{data=models.ListResponse}
|
||||
// @Router /domains [get]
|
||||
func (h *DomainHandler) ListDomains(c *gin.Context) {
|
||||
var query models.PageQuery
|
||||
c.ShouldBindQuery(&query)
|
||||
|
||||
db := h.db.Model(&models.Domain{})
|
||||
if query.Keyword != "" {
|
||||
db = db.Where("name LIKE ?", "%"+query.Keyword+"%")
|
||||
}
|
||||
|
||||
var total int64
|
||||
db.Count(&total)
|
||||
|
||||
var domains []models.Domain
|
||||
db.Offset(query.GetOffset()).Limit(query.GetLimit()).Find(&domains)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: models.ListResponse{
|
||||
List: domains,
|
||||
Total: total,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// CreateDomain 创建域名
|
||||
// @Summary 创建域名
|
||||
// @Description 创建新域名
|
||||
// @Tags 域名
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body models.Domain true "创建请求"
|
||||
// @Success 200 {object} models.Response{data=models.Domain}
|
||||
// @Router /domains [post]
|
||||
func (h *DomainHandler) CreateDomain(c *gin.Context) {
|
||||
var domain models.Domain
|
||||
if err := c.ShouldBindJSON(&domain); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.Create(&domain).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "创建失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "创建成功",
|
||||
Data: domain,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteDomain 删除域名
|
||||
// @Summary 删除域名
|
||||
// @Description 删除指定域名
|
||||
// @Tags 域名
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "域名ID"
|
||||
// @Success 200 {object} models.Response
|
||||
// @Router /domains/{id} [delete]
|
||||
func (h *DomainHandler) DeleteDomain(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
if err := h.db.Delete(&models.Domain{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "删除失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "删除成功",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,538 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"ezops/internal/api/middleware"
|
||||
"ezops/internal/config"
|
||||
"ezops/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UserHandler 用户处理器
|
||||
type UserHandler struct {
|
||||
db *gorm.DB
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
// NewUserHandler 创建用户处理器
|
||||
func NewUserHandler(db *gorm.DB, cfg *config.Config) *UserHandler {
|
||||
return &UserHandler{db: db, cfg: cfg}
|
||||
}
|
||||
|
||||
// Login 用户登录
|
||||
// @Summary 用户登录
|
||||
// @Description 用户登录获取JWT令牌
|
||||
// @Tags 用户
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body models.LoginRequest true "登录请求"
|
||||
// @Success 200 {object} models.Response{data=models.LoginResponse}
|
||||
// @Failure 400 {object} models.Response
|
||||
// @Failure 401 {object} models.Response
|
||||
// @Router /auth/login [post]
|
||||
func (h *UserHandler) Login(c *gin.Context) {
|
||||
var req models.LoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := h.db.Where("username = ?", req.Username).First(&user).Error; err != nil {
|
||||
c.JSON(http.StatusUnauthorized, models.Response{
|
||||
Code: 401,
|
||||
Message: "用户名或密码错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if user.Status != 1 {
|
||||
c.JSON(http.StatusUnauthorized, models.Response{
|
||||
Code: 401,
|
||||
Message: "账号已被禁用",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
|
||||
c.JSON(http.StatusUnauthorized, models.Response{
|
||||
Code: 401,
|
||||
Message: "用户名或密码错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 生成JWT令牌
|
||||
token, err := middleware.GenerateToken(&user, &h.cfg.JWT)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "生成令牌失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新最后登录时间
|
||||
now := h.db.NowFunc()
|
||||
h.db.Model(&user).Update("last_login", &now)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "登录成功",
|
||||
Data: models.LoginResponse{
|
||||
Token: token,
|
||||
ExpiresIn: h.cfg.JWT.ExpireHour * 3600,
|
||||
User: &user,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Register 用户注册
|
||||
// @Summary 用户注册
|
||||
// @Description 注册新用户
|
||||
// @Tags 用户
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body models.CreateUserRequest true "注册请求"
|
||||
// @Success 200 {object} models.Response{data=models.User}
|
||||
// @Failure 400 {object} models.Response
|
||||
// @Router /auth/register [post]
|
||||
func (h *UserHandler) Register(c *gin.Context) {
|
||||
var req models.CreateUserRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查用户名是否已存在
|
||||
var existingUser models.User
|
||||
if err := h.db.Where("username = ?", req.Username).First(&existingUser).Error; err == nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "用户名已存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 加密密码
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "密码加密失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
user := models.User{
|
||||
Username: req.Username,
|
||||
Password: string(hashedPassword),
|
||||
Email: req.Email,
|
||||
Phone: req.Phone,
|
||||
RealName: req.RealName,
|
||||
Status: 1,
|
||||
}
|
||||
|
||||
if err := h.db.Create(&user).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "创建用户失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "注册成功",
|
||||
Data: user,
|
||||
})
|
||||
}
|
||||
|
||||
// GetProfile 获取当前用户信息
|
||||
// @Summary 获取当前用户信息
|
||||
// @Description 获取当前登录用户的信息
|
||||
// @Tags 用户
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} models.Response{data=models.User}
|
||||
// @Router /user/profile [get]
|
||||
func (h *UserHandler) GetProfile(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
|
||||
var user models.User
|
||||
if err := h.db.Preload("Roles").First(&user, userID).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "用户不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: user,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateProfile 更新当前用户信息
|
||||
// @Summary 更新当前用户信息
|
||||
// @Description 更新当前登录用户的信息
|
||||
// @Tags 用户
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body models.UpdateUserRequest true "更新请求"
|
||||
// @Success 200 {object} models.Response{data=models.User}
|
||||
// @Router /user/profile [put]
|
||||
func (h *UserHandler) UpdateProfile(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
|
||||
var req models.UpdateUserRequest
|
||||
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.Email != "" {
|
||||
updates["email"] = req.Email
|
||||
}
|
||||
if req.Phone != "" {
|
||||
updates["phone"] = req.Phone
|
||||
}
|
||||
if req.RealName != "" {
|
||||
updates["real_name"] = req.RealName
|
||||
}
|
||||
|
||||
if err := h.db.Model(&models.User{}).Where("id = ?", userID).Updates(updates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "更新失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var user models.User
|
||||
h.db.First(&user, userID)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "更新成功",
|
||||
Data: user,
|
||||
})
|
||||
}
|
||||
|
||||
// ChangePassword 修改密码
|
||||
// @Summary 修改密码
|
||||
// @Description 修改当前用户密码
|
||||
// @Tags 用户
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body models.ChangePasswordRequest true "修改密码请求"
|
||||
// @Success 200 {object} models.Response
|
||||
// @Router /user/password [put]
|
||||
func (h *UserHandler) ChangePassword(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
|
||||
var req models.ChangePasswordRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := h.db.First(&user, userID).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "用户不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.OldPassword)); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "原密码错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "密码加密失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.Model(&user).Update("password", string(hashedPassword)).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "修改密码失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "密码修改成功",
|
||||
})
|
||||
}
|
||||
|
||||
// ListUsers 获取用户列表
|
||||
// @Summary 获取用户列表
|
||||
// @Description 获取所有用户列表(管理员)
|
||||
// @Tags 用户
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页数量"
|
||||
// @Param keyword query string false "搜索关键词"
|
||||
// @Success 200 {object} models.Response{data=models.ListResponse}
|
||||
// @Router /users [get]
|
||||
func (h *UserHandler) ListUsers(c *gin.Context) {
|
||||
var query models.PageQuery
|
||||
if err := c.ShouldBindQuery(&query); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
db := h.db.Model(&models.User{})
|
||||
if query.Keyword != "" {
|
||||
db = db.Where("username LIKE ? OR email LIKE ? OR real_name LIKE ?",
|
||||
"%"+query.Keyword+"%", "%"+query.Keyword+"%", "%"+query.Keyword+"%")
|
||||
}
|
||||
|
||||
var total int64
|
||||
db.Count(&total)
|
||||
|
||||
var users []models.User
|
||||
db.Preload("Roles").Offset(query.GetOffset()).Limit(query.GetLimit()).Find(&users)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: models.ListResponse{
|
||||
List: users,
|
||||
Total: total,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetUser 获取用户详情
|
||||
// @Summary 获取用户详情
|
||||
// @Description 获取指定用户的详细信息
|
||||
// @Tags 用户
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "用户ID"
|
||||
// @Success 200 {object} models.Response{data=models.User}
|
||||
// @Router /users/{id} [get]
|
||||
func (h *UserHandler) GetUser(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var user models.User
|
||||
if err := h.db.Preload("Roles").First(&user, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "用户不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: user,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateUser 创建用户
|
||||
// @Summary 创建用户
|
||||
// @Description 创建新用户(管理员)
|
||||
// @Tags 用户
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body models.CreateUserRequest true "创建请求"
|
||||
// @Success 200 {object} models.Response{data=models.User}
|
||||
// @Router /users [post]
|
||||
func (h *UserHandler) CreateUser(c *gin.Context) {
|
||||
var req models.CreateUserRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查用户名是否已存在
|
||||
var existingUser models.User
|
||||
if err := h.db.Where("username = ?", req.Username).First(&existingUser).Error; err == nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "用户名已存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 加密密码
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "密码加密失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
user := models.User{
|
||||
Username: req.Username,
|
||||
Password: string(hashedPassword),
|
||||
Email: req.Email,
|
||||
Phone: req.Phone,
|
||||
RealName: req.RealName,
|
||||
Status: 1,
|
||||
}
|
||||
|
||||
if err := h.db.Create(&user).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "创建用户失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 关联角色
|
||||
if len(req.RoleIDs) > 0 {
|
||||
var roles []models.Role
|
||||
h.db.Find(&roles, req.RoleIDs)
|
||||
h.db.Model(&user).Association("Roles").Append(roles)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "创建成功",
|
||||
Data: user,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateUser 更新用户
|
||||
// @Summary 更新用户
|
||||
// @Description 更新指定用户信息(管理员)
|
||||
// @Tags 用户
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "用户ID"
|
||||
// @Param request body models.UpdateUserRequest true "更新请求"
|
||||
// @Success 200 {object} models.Response{data=models.User}
|
||||
// @Router /users/{id} [put]
|
||||
func (h *UserHandler) UpdateUser(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var req models.UpdateUserRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := h.db.First(&user, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "用户不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
updates := map[string]interface{}{}
|
||||
if req.Email != "" {
|
||||
updates["email"] = req.Email
|
||||
}
|
||||
if req.Phone != "" {
|
||||
updates["phone"] = req.Phone
|
||||
}
|
||||
if req.RealName != "" {
|
||||
updates["real_name"] = req.RealName
|
||||
}
|
||||
if req.Status != nil {
|
||||
updates["status"] = *req.Status
|
||||
}
|
||||
|
||||
if err := h.db.Model(&user).Updates(updates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "更新失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新角色
|
||||
if len(req.RoleIDs) > 0 {
|
||||
var roles []models.Role
|
||||
h.db.Find(&roles, req.RoleIDs)
|
||||
h.db.Model(&user).Association("Roles").Replace(roles)
|
||||
}
|
||||
|
||||
h.db.Preload("Roles").First(&user, id)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "更新成功",
|
||||
Data: user,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteUser 删除用户
|
||||
// @Summary 删除用户
|
||||
// @Description 删除指定用户(管理员)
|
||||
// @Tags 用户
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "用户ID"
|
||||
// @Success 200 {object} models.Response
|
||||
// @Router /users/{id} [delete]
|
||||
func (h *UserHandler) DeleteUser(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
if err := h.db.Delete(&models.User{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "删除失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "删除成功",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"ezops/internal/config"
|
||||
"ezops/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
// JWTClaims JWT声明
|
||||
type JWTClaims struct {
|
||||
UserID uint `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// GenerateToken 生成JWT令牌
|
||||
func GenerateToken(user *models.User, cfg *config.JWTConfig) (string, error) {
|
||||
claims := JWTClaims{
|
||||
UserID: user.ID,
|
||||
Username: user.Username,
|
||||
IsAdmin: user.IsAdmin,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(cfg.ExpireHour) * time.Hour)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
Issuer: "ezops",
|
||||
},
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString([]byte(cfg.Secret))
|
||||
}
|
||||
|
||||
// JWTAuth JWT认证中间件
|
||||
func JWTAuth(cfg *config.JWTConfig) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" {
|
||||
c.JSON(http.StatusUnauthorized, models.Response{
|
||||
Code: 401,
|
||||
Message: "未提供认证令牌",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.SplitN(authHeader, " ", 2)
|
||||
if !(len(parts) == 2 && parts[0] == "Bearer") {
|
||||
c.JSON(http.StatusUnauthorized, models.Response{
|
||||
Code: 401,
|
||||
Message: "认证令牌格式错误",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
tokenString := parts[1]
|
||||
token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(cfg.Secret), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, models.Response{
|
||||
Code: 401,
|
||||
Message: "无效的认证令牌: " + err.Error(),
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(*JWTClaims); ok && token.Valid {
|
||||
c.Set("userID", claims.UserID)
|
||||
c.Set("username", claims.Username)
|
||||
c.Set("isAdmin", claims.IsAdmin)
|
||||
c.Next()
|
||||
} else {
|
||||
c.JSON(http.StatusUnauthorized, models.Response{
|
||||
Code: 401,
|
||||
Message: "无效的认证令牌",
|
||||
})
|
||||
c.Abort()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AdminAuth 管理员认证中间件
|
||||
func AdminAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
isAdmin, exists := c.Get("isAdmin")
|
||||
if !exists || !isAdmin.(bool) {
|
||||
c.JSON(http.StatusForbidden, models.Response{
|
||||
Code: 403,
|
||||
Message: "需要管理员权限",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserID 获取当前用户ID
|
||||
func GetUserID(c *gin.Context) uint {
|
||||
userID, exists := c.Get("userID")
|
||||
if !exists {
|
||||
return 0
|
||||
}
|
||||
return userID.(uint)
|
||||
}
|
||||
|
||||
// GetUsername 获取当前用户名
|
||||
func GetUsername(c *gin.Context) string {
|
||||
username, exists := c.Get("username")
|
||||
if !exists {
|
||||
return ""
|
||||
}
|
||||
return username.(string)
|
||||
}
|
||||
|
||||
// GetIsAdmin 获取是否为管理员
|
||||
func GetIsAdmin(c *gin.Context) bool {
|
||||
isAdmin, exists := c.Get("isAdmin")
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
return isAdmin.(bool)
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"ezops/internal/api/handlers"
|
||||
"ezops/internal/api/middleware"
|
||||
"ezops/internal/config"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Register 注册所有路由
|
||||
func Register(r *gin.Engine, db *gorm.DB, log *zap.Logger, cfg *config.Config) {
|
||||
// API版本前缀
|
||||
api := r.Group("/api/v1")
|
||||
|
||||
// 健康检查
|
||||
r.GET("/health", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"status": "ok"})
|
||||
})
|
||||
|
||||
// 公开路由
|
||||
authHandler := handlers.NewUserHandler(db, cfg)
|
||||
api.POST("/auth/login", authHandler.Login)
|
||||
api.POST("/auth/register", authHandler.Register)
|
||||
|
||||
// 需要认证的路由
|
||||
authorized := api.Group("")
|
||||
authorized.Use(middleware.JWTAuth(&cfg.JWT))
|
||||
|
||||
// 用户路由
|
||||
user := authorized.Group("/user")
|
||||
{
|
||||
user.GET("/profile", authHandler.GetProfile)
|
||||
user.PUT("/profile", authHandler.UpdateProfile)
|
||||
user.PUT("/password", authHandler.ChangePassword)
|
||||
}
|
||||
|
||||
// 管理员路由
|
||||
admin := authorized.Group("/admin")
|
||||
admin.Use(middleware.AdminAuth())
|
||||
{
|
||||
admin.GET("/users", authHandler.ListUsers)
|
||||
admin.GET("/users/:id", authHandler.GetUser)
|
||||
admin.POST("/users", authHandler.CreateUser)
|
||||
admin.PUT("/users/:id", authHandler.UpdateUser)
|
||||
admin.DELETE("/users/:id", authHandler.DeleteUser)
|
||||
}
|
||||
|
||||
// 仪表盘路由
|
||||
dashboardHandler := handlers.NewDashboardHandler(db)
|
||||
dashboard := authorized.Group("/dashboard")
|
||||
{
|
||||
dashboard.GET("/stats", dashboardHandler.GetStats)
|
||||
dashboard.GET("/recent-alerts", dashboardHandler.GetRecentAlerts)
|
||||
dashboard.GET("/recent-releases", dashboardHandler.GetRecentReleases)
|
||||
dashboard.GET("/metrics", dashboardHandler.GetMetrics)
|
||||
}
|
||||
|
||||
// 服务器路由
|
||||
serverHandler := handlers.NewServerHandler(db)
|
||||
servers := authorized.Group("/servers")
|
||||
{
|
||||
servers.GET("", serverHandler.ListServers)
|
||||
servers.GET("/stats", serverHandler.GetServerStats)
|
||||
servers.GET("/:id", serverHandler.GetServer)
|
||||
servers.POST("", serverHandler.CreateServer)
|
||||
servers.PUT("/:id", serverHandler.UpdateServer)
|
||||
servers.DELETE("/:id", serverHandler.DeleteServer)
|
||||
servers.POST("/batch-delete", serverHandler.BatchDeleteServers)
|
||||
}
|
||||
|
||||
// 云账号路由
|
||||
cloudAccountHandler := handlers.NewCloudAccountHandler(db)
|
||||
cloudAccounts := authorized.Group("/cloud-accounts")
|
||||
{
|
||||
cloudAccounts.GET("", cloudAccountHandler.ListCloudAccounts)
|
||||
cloudAccounts.POST("", cloudAccountHandler.CreateCloudAccount)
|
||||
cloudAccounts.DELETE("/:id", cloudAccountHandler.DeleteCloudAccount)
|
||||
}
|
||||
|
||||
// 域名路由
|
||||
domainHandler := handlers.NewDomainHandler(db)
|
||||
domains := authorized.Group("/domains")
|
||||
{
|
||||
domains.GET("", domainHandler.ListDomains)
|
||||
domains.POST("", domainHandler.CreateDomain)
|
||||
domains.DELETE("/:id", domainHandler.DeleteDomain)
|
||||
}
|
||||
|
||||
// K8s集群路由
|
||||
k8sHandler := handlers.NewK8sHandler(db)
|
||||
clusters := authorized.Group("/k8s-clusters")
|
||||
{
|
||||
clusters.GET("", k8sHandler.ListClusters)
|
||||
clusters.GET("/:id", k8sHandler.GetCluster)
|
||||
clusters.POST("", k8sHandler.CreateCluster)
|
||||
clusters.PUT("/:id", k8sHandler.UpdateCluster)
|
||||
clusters.DELETE("/:id", k8sHandler.DeleteCluster)
|
||||
clusters.GET("/:id/nodes", k8sHandler.ListNodes)
|
||||
clusters.GET("/:id/pods", k8sHandler.ListPods)
|
||||
clusters.GET("/:id/deployments", k8sHandler.ListDeployments)
|
||||
clusters.GET("/:id/services", k8sHandler.ListServices)
|
||||
clusters.GET("/:id/ingresses", k8sHandler.ListIngresses)
|
||||
}
|
||||
|
||||
// 告警路由
|
||||
alertHandler := handlers.NewAlertHandler(db)
|
||||
alerts := authorized.Group("/alerts")
|
||||
{
|
||||
alerts.GET("", alertHandler.ListAlerts)
|
||||
alerts.GET("/rules", alertHandler.ListRules)
|
||||
alerts.POST("/rules", alertHandler.CreateRule)
|
||||
alerts.PUT("/rules/:id", alertHandler.UpdateRule)
|
||||
alerts.DELETE("/rules/:id", alertHandler.DeleteRule)
|
||||
alerts.POST("/rules/:id/toggle", alertHandler.ToggleRule)
|
||||
alerts.POST("/:id/ack", alertHandler.AcknowledgeAlert)
|
||||
}
|
||||
|
||||
// 流水线路由
|
||||
pipelineHandler := handlers.NewPipelineHandler(db)
|
||||
pipelines := authorized.Group("/pipelines")
|
||||
{
|
||||
pipelines.GET("", pipelineHandler.ListPipelines)
|
||||
pipelines.POST("", pipelineHandler.CreatePipeline)
|
||||
pipelines.GET("/:id", pipelineHandler.GetPipeline)
|
||||
pipelines.PUT("/:id", pipelineHandler.UpdatePipeline)
|
||||
pipelines.DELETE("/:id", pipelineHandler.DeletePipeline)
|
||||
pipelines.POST("/:id/trigger", pipelineHandler.TriggerPipeline)
|
||||
pipelines.GET("/:id/runs", pipelineHandler.ListRuns)
|
||||
}
|
||||
|
||||
// 应用发布路由
|
||||
releases := authorized.Group("/releases")
|
||||
{
|
||||
releases.GET("", pipelineHandler.ListReleases)
|
||||
releases.POST("", pipelineHandler.CreateRelease)
|
||||
releases.POST("/:id/approve", pipelineHandler.ApproveRelease)
|
||||
releases.POST("/:id/deploy", pipelineHandler.DeployRelease)
|
||||
releases.POST("/:id/rollback", pipelineHandler.RollbackRelease)
|
||||
}
|
||||
|
||||
// 自动化运维路由
|
||||
automationHandler := handlers.NewAutomationHandler(db)
|
||||
cronJobs := authorized.Group("/cron-jobs")
|
||||
{
|
||||
cronJobs.GET("", automationHandler.ListCronJobs)
|
||||
cronJobs.POST("", automationHandler.CreateCronJob)
|
||||
cronJobs.GET("/:id", automationHandler.GetCronJob)
|
||||
cronJobs.PUT("/:id", automationHandler.UpdateCronJob)
|
||||
cronJobs.DELETE("/:id", automationHandler.DeleteCronJob)
|
||||
cronJobs.POST("/:id/toggle", automationHandler.ToggleCronJob)
|
||||
cronJobs.POST("/:id/execute", automationHandler.ExecuteCronJob)
|
||||
}
|
||||
|
||||
scripts := authorized.Group("/scripts")
|
||||
{
|
||||
scripts.GET("", automationHandler.ListScripts)
|
||||
scripts.POST("", automationHandler.CreateScript)
|
||||
scripts.GET("/:id", automationHandler.GetScript)
|
||||
scripts.PUT("/:id", automationHandler.UpdateScript)
|
||||
scripts.DELETE("/:id", automationHandler.DeleteScript)
|
||||
scripts.POST("/:id/execute", automationHandler.ExecuteScript)
|
||||
}
|
||||
|
||||
// 审批流程路由
|
||||
approvalHandler := handlers.NewApprovalHandler(db)
|
||||
approvals := authorized.Group("/approvals")
|
||||
{
|
||||
approvals.GET("/templates", approvalHandler.ListTemplates)
|
||||
approvals.POST("/templates", approvalHandler.CreateTemplate)
|
||||
approvals.GET("/templates/:id", approvalHandler.GetTemplate)
|
||||
approvals.PUT("/templates/:id", approvalHandler.UpdateTemplate)
|
||||
approvals.DELETE("/templates/:id", approvalHandler.DeleteTemplate)
|
||||
|
||||
approvals.GET("/processes", approvalHandler.ListProcesses)
|
||||
approvals.POST("/processes", approvalHandler.CreateProcess)
|
||||
approvals.GET("/processes/:id", approvalHandler.GetProcess)
|
||||
approvals.POST("/processes/:id/approve", approvalHandler.ApproveProcess)
|
||||
approvals.POST("/processes/:id/reject", approvalHandler.RejectProcess)
|
||||
}
|
||||
|
||||
// 导航路由
|
||||
navHandler := handlers.NewNavigationHandler(db)
|
||||
navigations := authorized.Group("/navigations")
|
||||
{
|
||||
navigations.GET("/groups", navHandler.ListGroups)
|
||||
navigations.POST("/groups", navHandler.CreateGroup)
|
||||
navigations.PUT("/groups/:id", navHandler.UpdateGroup)
|
||||
navigations.DELETE("/groups/:id", navHandler.DeleteGroup)
|
||||
|
||||
navigations.GET("/links", navHandler.ListLinks)
|
||||
navigations.POST("/links", navHandler.CreateLink)
|
||||
navigations.PUT("/links/:id", navHandler.UpdateLink)
|
||||
navigations.DELETE("/links/:id", navHandler.DeleteLink)
|
||||
navigations.POST("/links/:id/favorite", navHandler.ToggleFavorite)
|
||||
}
|
||||
|
||||
// 审计日志路由
|
||||
auditHandler := handlers.NewAuditHandler(db)
|
||||
audit := authorized.Group("/audit")
|
||||
{
|
||||
audit.GET("/logs", auditHandler.ListAuditLogs)
|
||||
audit.GET("/logs/:id", auditHandler.GetAuditLogDetail)
|
||||
audit.GET("/modules", auditHandler.GetAuditModules)
|
||||
audit.GET("/actions", auditHandler.GetAuditActions)
|
||||
audit.GET("/logs/export", auditHandler.ExportAuditLogs)
|
||||
audit.GET("/statistics", auditHandler.GetAuditStatistics)
|
||||
audit.POST("/logs/clear", auditHandler.ClearAuditLogs)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Config 应用配置
|
||||
type Config struct {
|
||||
Mode string `mapstructure:"mode"`
|
||||
Port string `mapstructure:"port"`
|
||||
LogLevel string `mapstructure:"log_level"`
|
||||
Database DatabaseConfig `mapstructure:"database"`
|
||||
JWT JWTConfig `mapstructure:"jwt"`
|
||||
Redis RedisConfig `mapstructure:"redis"`
|
||||
Websocket WebsocketConfig `mapstructure:"websocket"`
|
||||
}
|
||||
|
||||
// DatabaseConfig 数据库配置
|
||||
type DatabaseConfig struct {
|
||||
Driver string `mapstructure:"driver"`
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
User string `mapstructure:"user"`
|
||||
Password string `mapstructure:"password"`
|
||||
DBName string `mapstructure:"dbname"`
|
||||
Charset string `mapstructure:"charset"`
|
||||
MaxConn int `mapstructure:"max_conn"`
|
||||
MaxIdle int `mapstructure:"max_idle"`
|
||||
}
|
||||
|
||||
// JWTConfig JWT配置
|
||||
type JWTConfig struct {
|
||||
Secret string `mapstructure:"secret"`
|
||||
ExpireHour int `mapstructure:"expire_hour"`
|
||||
}
|
||||
|
||||
// RedisConfig Redis配置
|
||||
type RedisConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Password string `mapstructure:"password"`
|
||||
DB int `mapstructure:"db"`
|
||||
}
|
||||
|
||||
// WebsocketConfig WebSocket配置
|
||||
type WebsocketConfig struct {
|
||||
ReadBufferSize int `mapstructure:"read_buffer_size"`
|
||||
WriteBufferSize int `mapstructure:"write_buffer_size"`
|
||||
}
|
||||
|
||||
// Load 加载配置
|
||||
func Load() *Config {
|
||||
viper.SetConfigName("config")
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AddConfigPath("./configs")
|
||||
viper.AddConfigPath(".")
|
||||
|
||||
// 设置默认值
|
||||
viper.SetDefault("mode", "development")
|
||||
viper.SetDefault("port", "8080")
|
||||
viper.SetDefault("log_level", "info")
|
||||
viper.SetDefault("database.driver", "mysql")
|
||||
viper.SetDefault("database.host", "localhost")
|
||||
viper.SetDefault("database.port", 3306)
|
||||
viper.SetDefault("database.charset", "utf8mb4")
|
||||
viper.SetDefault("database.max_conn", 100)
|
||||
viper.SetDefault("database.max_idle", 10)
|
||||
viper.SetDefault("jwt.expire_hour", 24)
|
||||
viper.SetDefault("redis.host", "localhost")
|
||||
viper.SetDefault("redis.port", 6379)
|
||||
viper.SetDefault("websocket.read_buffer_size", 1024)
|
||||
viper.SetDefault("websocket.write_buffer_size", 1024)
|
||||
|
||||
// 读取环境变量
|
||||
viper.AutomaticEnv()
|
||||
|
||||
var cfg Config
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
zap.L().Warn("config file not found, using defaults", zap.Error(err))
|
||||
}
|
||||
|
||||
if err := viper.Unmarshal(&cfg); err != nil {
|
||||
zap.L().Fatal("failed to unmarshal config", zap.Error(err))
|
||||
}
|
||||
|
||||
return &cfg
|
||||
}
|
||||
|
||||
// DSN 返回数据库连接字符串
|
||||
func (d *DatabaseConfig) DSN() string {
|
||||
if d.Driver == "mysql" {
|
||||
return d.User + ":" + d.Password + "@tcp(" + d.Host + ":" + string(rune(d.Port)) + ")/" + d.DBName + "?charset=" + d.Charset + "&parseTime=True&loc=Local"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AlertRule 告警规则模型
|
||||
type AlertRule struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
Name string `gorm:"size:128;not null" json:"name"`
|
||||
Description string `gorm:"size:500" json:"description"`
|
||||
Type string `gorm:"size:32;not null" json:"type"` // metric, log, event
|
||||
Source string `gorm:"size:64;not null" json:"source"` // prometheus, zabbix, custom
|
||||
Expression string `gorm:"type:text;not null" json:"expression"`
|
||||
Severity string `gorm:"size:32;default:warning" json:"severity"` // info, warning, critical
|
||||
Duration int `json:"duration"` // 持续时间(秒)
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
Labels string `gorm:"type:text" json:"labels"` // JSON
|
||||
Annotations string `gorm:"type:text" json:"annotations"` // JSON
|
||||
NotifyChannels string `gorm:"type:text" json:"notify_channels"` // JSON
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (AlertRule) TableName() string {
|
||||
return "alert_rules"
|
||||
}
|
||||
|
||||
// AlertEvent 告警事件模型
|
||||
type AlertEvent struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
RuleID uint `json:"rule_id"`
|
||||
RuleName string `gorm:"size:128" json:"rule_name"`
|
||||
Severity string `gorm:"size:32" json:"severity"`
|
||||
Status string `gorm:"size:32;default:firing" json:"status"` // firing, resolved
|
||||
Target string `gorm:"size:255" json:"target"`
|
||||
Message string `gorm:"type:text" json:"message"`
|
||||
Value float64 `json:"value"`
|
||||
Labels string `gorm:"type:text" json:"labels"`
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
ResolvedAt *time.Time `json:"resolved_at"`
|
||||
Acknowledged bool `json:"acknowledged"`
|
||||
AcknowledgedBy *uint `json:"acknowledged_by"`
|
||||
AcknowledgedAt *time.Time `json:"acknowledged_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// 关联
|
||||
Rule *AlertRule `json:"rule,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (AlertEvent) TableName() string {
|
||||
return "alert_events"
|
||||
}
|
||||
|
||||
// NotifyChannel 通知渠道模型
|
||||
type NotifyChannel struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
Name string `gorm:"size:64;not null" json:"name"`
|
||||
Type string `gorm:"size:32;not null" json:"type"` // email, sms, webhook, dingtalk, wechat
|
||||
Config string `gorm:"type:text" json:"config"` // JSON
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
TestStatus string `gorm:"size:32" json:"test_status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (NotifyChannel) TableName() string {
|
||||
return "notify_channels"
|
||||
}
|
||||
|
||||
// MetricData 监控指标数据
|
||||
type MetricData struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
ServerID *uint `json:"server_id"`
|
||||
ClusterID *uint `json:"cluster_id"`
|
||||
Name string `gorm:"size:64" json:"name"`
|
||||
Value float64 `json:"value"`
|
||||
Labels string `gorm:"type:text" json:"labels"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (MetricData) TableName() string {
|
||||
return "metric_data"
|
||||
}
|
||||
|
||||
// CreateAlertRuleRequest 创建告警规则请求
|
||||
type CreateAlertRuleRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type" binding:"required,oneof=metric log event"`
|
||||
Source string `json:"source" binding:"required"`
|
||||
Expression string `json:"expression" binding:"required"`
|
||||
Severity string `json:"severity" binding:"omitempty,oneof=info warning critical"`
|
||||
Duration int `json:"duration"`
|
||||
Labels string `json:"labels"`
|
||||
Annotations string `json:"annotations"`
|
||||
NotifyChannels []string `json:"notify_channels"`
|
||||
}
|
||||
|
||||
// UpdateAlertRuleRequest 更新告警规则请求
|
||||
type UpdateAlertRuleRequest struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Expression string `json:"expression"`
|
||||
Severity string `json:"severity" binding:"omitempty,oneof=info warning critical"`
|
||||
Duration int `json:"duration"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
Labels string `json:"labels"`
|
||||
Annotations string `json:"annotations"`
|
||||
NotifyChannels []string `json:"notify_channels"`
|
||||
}
|
||||
|
||||
// AlertStats 告警统计
|
||||
type AlertStats struct {
|
||||
Total int64 `json:"total"`
|
||||
Firing int64 `json:"firing"`
|
||||
Resolved int64 `json:"resolved"`
|
||||
Critical int64 `json:"critical"`
|
||||
Warning int64 `json:"warning"`
|
||||
Info int64 `json:"info"`
|
||||
TodayCount int64 `json:"today_count"`
|
||||
WeekCount int64 `json:"week_count"`
|
||||
}
|
||||
|
||||
// AcknowledgeAlertRequest 确认告警请求
|
||||
type AcknowledgeAlertRequest struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ApprovalTemplate 审批模板模型
|
||||
type ApprovalTemplate struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
Name string `gorm:"size:128;not null" json:"name"`
|
||||
Code string `gorm:"size:64;uniqueIndex;not null" json:"code"`
|
||||
Description string `gorm:"size:500" json:"description"`
|
||||
Type string `gorm:"size:32;not null" json:"type"` // release, server, permission
|
||||
FormSchema string `gorm:"type:text" json:"form_schema"` // JSON
|
||||
FlowConfig string `gorm:"type:text" json:"flow_config"` // JSON
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (ApprovalTemplate) TableName() string {
|
||||
return "approval_templates"
|
||||
}
|
||||
|
||||
// ApprovalProcess 审批流程模型
|
||||
type ApprovalProcess struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
TemplateID uint `json:"template_id"`
|
||||
Title string `gorm:"size:255;not null" json:"title"`
|
||||
Applicant uint `json:"applicant"`
|
||||
Status string `gorm:"size:32;default:pending" json:"status"` // pending, approved, rejected
|
||||
FormData string `gorm:"type:text" json:"form_data"` // JSON
|
||||
CurrentStep int `gorm:"default:0" json:"current_step"`
|
||||
TotalSteps int `json:"total_steps"`
|
||||
ApprovedAt *time.Time `json:"approved_at"`
|
||||
RejectedAt *time.Time `json:"rejected_at"`
|
||||
RejectReason string `gorm:"size:500" json:"reject_reason"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
// 关联
|
||||
Template *ApprovalTemplate `json:"template,omitempty"`
|
||||
ApplicantUser *User `json:"applicant_user,omitempty"`
|
||||
Steps []ApprovalStep `json:"steps,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (ApprovalProcess) TableName() string {
|
||||
return "approval_processes"
|
||||
}
|
||||
|
||||
// ApprovalStep 审批步骤模型
|
||||
type ApprovalStep struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
ProcessID uint `json:"process_id"`
|
||||
Step int `json:"step"`
|
||||
Approver uint `json:"approver"`
|
||||
Status string `gorm:"size:32;default:pending" json:"status"` // pending, approved, rejected
|
||||
Comment string `gorm:"size:500" json:"comment"`
|
||||
ApprovedAt *time.Time `json:"approved_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// 关联
|
||||
ApproverUser *User `json:"approver_user,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (ApprovalStep) TableName() string {
|
||||
return "approval_steps"
|
||||
}
|
||||
|
||||
// CreateApprovalTemplateRequest 创建审批模板请求
|
||||
type CreateApprovalTemplateRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type" binding:"required,oneof=release server permission"`
|
||||
FormSchema string `json:"form_schema"`
|
||||
FlowConfig string `json:"flow_config"`
|
||||
}
|
||||
|
||||
// CreateApprovalProcessRequest 创建审批流程请求
|
||||
type CreateApprovalProcessRequest struct {
|
||||
TemplateID uint `json:"template_id" binding:"required"`
|
||||
Title string `json:"title" binding:"required"`
|
||||
FormData string `json:"form_data"`
|
||||
}
|
||||
|
||||
// ApproveRequest 审批请求
|
||||
type ApproveRequest struct {
|
||||
Status string `json:"status" binding:"required,oneof=approved rejected"`
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
|
||||
// ApprovalStats 审批统计
|
||||
type ApprovalStats struct {
|
||||
Pending int64 `json:"pending"`
|
||||
Approved int64 `json:"approved"`
|
||||
Rejected int64 `json:"rejected"`
|
||||
Total int64 `json:"total"`
|
||||
MyPending int64 `json:"my_pending"`
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// AuditLog 审计日志模型
|
||||
type AuditLog struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
UserID *uint `json:"user_id"`
|
||||
Username string `gorm:"size:64" json:"username"`
|
||||
IP string `gorm:"size:64" json:"ip"`
|
||||
UserAgent string `gorm:"size:500" json:"user_agent"`
|
||||
Module string `gorm:"size:64" json:"module"`
|
||||
Action string `gorm:"size:64" json:"action"`
|
||||
Resource string `gorm:"size:128" json:"resource"`
|
||||
ResourceID string `gorm:"size:64" json:"resource_id"`
|
||||
Request string `gorm:"type:text" json:"request"`
|
||||
Response string `gorm:"type:text" json:"response"`
|
||||
Status int `json:"status"`
|
||||
Duration int `json:"duration"` // ms
|
||||
Error string `gorm:"type:text" json:"error"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (AuditLog) TableName() string {
|
||||
return "audit_logs"
|
||||
}
|
||||
|
||||
// AuditLogQuery 审计日志查询
|
||||
type AuditLogQuery struct {
|
||||
UserID *uint `form:"user_id"`
|
||||
Username string `form:"username"`
|
||||
Module string `form:"module"`
|
||||
Action string `form:"action"`
|
||||
StartTime string `form:"start_time"`
|
||||
EndTime string `form:"end_time"`
|
||||
Page int `form:"page,default=1"`
|
||||
PageSize int `form:"page_size,default=20"`
|
||||
}
|
||||
|
||||
// AuditLogListResponse 审计日志列表响应
|
||||
type AuditLogListResponse struct {
|
||||
List []AuditLog `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
// AuditStats 审计统计
|
||||
type AuditStats struct {
|
||||
TodayCount int64 `json:"today_count"`
|
||||
WeekCount int64 `json:"week_count"`
|
||||
MonthCount int64 `json:"month_count"`
|
||||
TopModules []ModuleStat `json:"top_modules"`
|
||||
TopUsers []UserStat `json:"top_users"`
|
||||
}
|
||||
|
||||
// ModuleStat 模块统计
|
||||
type ModuleStat struct {
|
||||
Module string `json:"module"`
|
||||
Count int64 `json:"count"`
|
||||
}
|
||||
|
||||
// UserStat 用户统计
|
||||
type UserStat struct {
|
||||
Username string `json:"username"`
|
||||
Count int64 `json:"count"`
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CronJob 定时任务模型
|
||||
type CronJob struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
Name string `gorm:"size:128;not null" json:"name"`
|
||||
Description string `gorm:"size:500" json:"description"`
|
||||
CronExpr string `gorm:"size:64;not null" json:"cron_expr"`
|
||||
Type string `gorm:"size:32;not null" json:"type"` // script, command, playbook
|
||||
Content string `gorm:"type:text" json:"content"`
|
||||
ScriptID *uint `json:"script_id"`
|
||||
TargetType string `gorm:"size:32;default:server" json:"target_type"` // server, group, tag
|
||||
Targets string `gorm:"type:text" json:"targets"` // JSON
|
||||
Timeout int `gorm:"default:300" json:"timeout"`
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
LastRunAt *time.Time `json:"last_run_at"`
|
||||
LastStatus string `gorm:"size:32" json:"last_status"`
|
||||
RunCount int `gorm:"default:0" json:"run_count"`
|
||||
SuccessCount int `gorm:"default:0" json:"success_count"`
|
||||
FailCount int `gorm:"default:0" json:"fail_count"`
|
||||
NotifyOn string `gorm:"size:32;default:failure" json:"notify_on"` // always, failure, success
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
// 关联
|
||||
Script *Script `json:"script,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (CronJob) TableName() string {
|
||||
return "cron_jobs"
|
||||
}
|
||||
|
||||
// Script 脚本库模型
|
||||
type Script struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
Name string `gorm:"size:128;not null" json:"name"`
|
||||
Code string `gorm:"size:64;uniqueIndex;not null" json:"code"`
|
||||
Description string `gorm:"size:500" json:"description"`
|
||||
Type string `gorm:"size:32;default:shell" json:"type"` // shell, python, powershell, yaml
|
||||
Content string `gorm:"type:text;not null" json:"content"`
|
||||
Params string `gorm:"type:text" json:"params"` // JSON
|
||||
Timeout int `gorm:"default:300" json:"timeout"`
|
||||
Version int `gorm:"default:1" json:"version"`
|
||||
RunCount int `gorm:"default:0" json:"run_count"`
|
||||
CreatedBy uint `json:"created_by"`
|
||||
UpdatedBy uint `json:"updated_by"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (Script) TableName() string {
|
||||
return "scripts"
|
||||
}
|
||||
|
||||
// TaskExecution 任务执行记录
|
||||
type TaskExecution struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
TaskType string `gorm:"size:32;not null" json:"task_type"` // cron, script, playbook
|
||||
TaskID uint `json:"task_id"`
|
||||
TaskName string `gorm:"size:128" json:"task_name"`
|
||||
Status string `gorm:"size:32;default:pending" json:"status"` // pending, running, success, failed, cancelled
|
||||
TargetType string `gorm:"size:32" json:"target_type"`
|
||||
Targets string `gorm:"type:text" json:"targets"`
|
||||
Output string `gorm:"type:longtext" json:"output"`
|
||||
Error string `gorm:"type:text" json:"error"`
|
||||
Duration int `json:"duration"`
|
||||
StartedAt *time.Time `json:"started_at"`
|
||||
FinishedAt *time.Time `json:"finished_at"`
|
||||
CreatedBy uint `json:"created_by"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (TaskExecution) TableName() string {
|
||||
return "task_executions"
|
||||
}
|
||||
|
||||
// Playbook Ansible Playbook模型
|
||||
type Playbook struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
Name string `gorm:"size:128;not null" json:"name"`
|
||||
Code string `gorm:"size:64;uniqueIndex;not null" json:"code"`
|
||||
Description string `gorm:"size:500" json:"description"`
|
||||
Content string `gorm:"type:text;not null" json:"content"`
|
||||
Variables string `gorm:"type:text" json:"variables"`
|
||||
Inventory string `gorm:"type:text" json:"inventory"`
|
||||
Timeout int `gorm:"default:600" json:"timeout"`
|
||||
RunCount int `gorm:"default:0" json:"run_count"`
|
||||
CreatedBy uint `json:"created_by"`
|
||||
UpdatedBy uint `json:"updated_by"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (Playbook) TableName() string {
|
||||
return "playbooks"
|
||||
}
|
||||
|
||||
// CreateCronJobRequest 创建定时任务请求
|
||||
type CreateCronJobRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
CronExpr string `json:"cron_expr" binding:"required"`
|
||||
Type string `json:"type" binding:"required,oneof=script command playbook"`
|
||||
Content string `json:"content"`
|
||||
ScriptID *uint `json:"script_id"`
|
||||
TargetType string `json:"target_type" binding:"omitempty,oneof=server group tag"`
|
||||
Targets []uint `json:"targets"`
|
||||
Timeout int `json:"timeout"`
|
||||
NotifyOn string `json:"notify_on" binding:"omitempty,oneof=always failure success"`
|
||||
}
|
||||
|
||||
// CreateScriptRequest 创建脚本请求
|
||||
type CreateScriptRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type" binding:"omitempty,oneof=shell python powershell yaml"`
|
||||
Content string `json:"content" binding:"required"`
|
||||
Params string `json:"params"`
|
||||
Timeout int `json:"timeout"`
|
||||
}
|
||||
|
||||
// ExecuteScriptRequest 执行脚本请求
|
||||
type ExecuteScriptRequest struct {
|
||||
TargetType string `json:"target_type" binding:"required,oneof=server group tag"`
|
||||
Targets []uint `json:"targets" binding:"required"`
|
||||
Params map[string]string `json:"params"`
|
||||
}
|
||||
|
||||
// AutomationStats 自动化统计
|
||||
type AutomationStats struct {
|
||||
CronJobCount int64 `json:"cron_job_count"`
|
||||
ScriptCount int64 `json:"script_count"`
|
||||
PlaybookCount int64 `json:"playbook_count"`
|
||||
TodayExecutions int64 `json:"today_executions"`
|
||||
SuccessRate float64 `json:"success_rate"`
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Pipeline CI/CD流水线模型
|
||||
type Pipeline struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
Name string `gorm:"size:128;not null" json:"name"`
|
||||
Code string `gorm:"size:64;uniqueIndex;not null" json:"code"`
|
||||
Description string `gorm:"size:500" json:"description"`
|
||||
Type string `gorm:"size:32;default:deploy" json:"type"` // build, deploy, test
|
||||
Source string `gorm:"size:255" json:"source"` // Git仓库地址
|
||||
Branch string `gorm:"size:64;default:main" json:"branch"`
|
||||
Dockerfile string `gorm:"size:255" json:"dockerfile"`
|
||||
Stages string `gorm:"type:text" json:"stages"` // JSON
|
||||
Variables string `gorm:"type:text" json:"variables"` // JSON
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
LastRunAt *time.Time `json:"last_run_at"`
|
||||
LastStatus string `gorm:"size:32" json:"last_status"`
|
||||
RunCount int `gorm:"default:0" json:"run_count"`
|
||||
SuccessCount int `gorm:"default:0" json:"success_count"`
|
||||
FailCount int `gorm:"default:0" json:"fail_count"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (Pipeline) TableName() string {
|
||||
return "pipelines"
|
||||
}
|
||||
|
||||
// PipelineRun 流水线运行记录
|
||||
type PipelineRun struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
PipelineID uint `gorm:"not null" json:"pipeline_id"`
|
||||
BuildNumber int `json:"build_number"`
|
||||
Status string `gorm:"size:32;default:pending" json:"status"` // pending, running, success, failed, cancelled
|
||||
Branch string `gorm:"size:64" json:"branch"`
|
||||
CommitID string `gorm:"size:64" json:"commit_id"`
|
||||
CommitMsg string `gorm:"size:500" json:"commit_msg"`
|
||||
Author string `gorm:"size:64" json:"author"`
|
||||
Duration int `json:"duration"` // 秒
|
||||
Stages string `gorm:"type:text" json:"stages"` // JSON
|
||||
Logs string `gorm:"type:longtext" json:"logs"`
|
||||
TriggeredBy string `gorm:"size:64" json:"triggered_by"`
|
||||
StartedAt *time.Time `json:"started_at"`
|
||||
FinishedAt *time.Time `json:"finished_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// 关联
|
||||
Pipeline *Pipeline `json:"pipeline,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (PipelineRun) TableName() string {
|
||||
return "pipeline_runs"
|
||||
}
|
||||
|
||||
// AppRelease 应用发布模型
|
||||
type AppRelease struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
AppName string `gorm:"size:128;not null" json:"app_name"`
|
||||
Version string `gorm:"size:64;not null" json:"version"`
|
||||
Environment string `gorm:"size:32;not null" json:"environment"` // dev, test, staging, prod
|
||||
Status string `gorm:"size:32;default:pending" json:"status"` // pending, building, deploying, success, failed, rolled_back
|
||||
CommitID string `gorm:"size:64" json:"commit_id"`
|
||||
CommitMsg string `gorm:"size:500" json:"commit_msg"`
|
||||
Author string `gorm:"size:64" json:"author"`
|
||||
Approver string `gorm:"size:64" json:"approver"`
|
||||
ApprovedAt *time.Time `json:"approved_at"`
|
||||
DeployedAt *time.Time `json:"deployed_at"`
|
||||
RollbackAt *time.Time `json:"rollback_at"`
|
||||
Duration int `json:"duration"`
|
||||
Servers string `gorm:"type:text" json:"servers"` // JSON
|
||||
Description string `gorm:"size:500" json:"description"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (AppRelease) TableName() string {
|
||||
return "app_releases"
|
||||
}
|
||||
|
||||
// CreatePipelineRequest 创建流水线请求
|
||||
type CreatePipelineRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type" binding:"omitempty,oneof=build deploy test"`
|
||||
Source string `json:"source"`
|
||||
Branch string `json:"branch"`
|
||||
Dockerfile string `json:"dockerfile"`
|
||||
Stages string `json:"stages"`
|
||||
Variables string `json:"variables"`
|
||||
}
|
||||
|
||||
// TriggerPipelineRequest 触发流水线请求
|
||||
type TriggerPipelineRequest struct {
|
||||
Branch string `json:"branch"`
|
||||
Variables map[string]string `json:"variables"`
|
||||
}
|
||||
|
||||
// CreateAppReleaseRequest 创建应用发布请求
|
||||
type CreateAppReleaseRequest struct {
|
||||
AppName string `json:"app_name" binding:"required"`
|
||||
Version string `json:"version" binding:"required"`
|
||||
Environment string `json:"environment" binding:"required,oneof=dev test staging prod"`
|
||||
CommitID string `json:"commit_id"`
|
||||
CommitMsg string `json:"commit_msg"`
|
||||
Servers []uint `json:"servers"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// PipelineStats 流水线统计
|
||||
type PipelineStats struct {
|
||||
Total int64 `json:"total"`
|
||||
Success int64 `json:"success"`
|
||||
Failed int64 `json:"failed"`
|
||||
Running int64 `json:"running"`
|
||||
TodayCount int64 `json:"today_count"`
|
||||
SuccessRate float64 `json:"success_rate"`
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package models
|
||||
|
||||
// Response 通用响应
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// ListResponse 列表响应
|
||||
type ListResponse struct {
|
||||
List interface{} `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
// PageQuery 分页查询
|
||||
type PageQuery struct {
|
||||
Page int `form:"page,default=1"`
|
||||
PageSize int `form:"page_size,default=20"`
|
||||
Keyword string `form:"keyword"`
|
||||
Sort string `form:"sort"`
|
||||
Order string `form:"order,default=desc"`
|
||||
}
|
||||
|
||||
// GetOffset 获取偏移量
|
||||
func (p *PageQuery) GetOffset() int {
|
||||
if p.Page < 1 {
|
||||
p.Page = 1
|
||||
}
|
||||
return (p.Page - 1) * p.GetLimit()
|
||||
}
|
||||
|
||||
// GetLimit 获取每页数量
|
||||
func (p *PageQuery) GetLimit() int {
|
||||
if p.PageSize < 1 {
|
||||
p.PageSize = 20
|
||||
}
|
||||
if p.PageSize > 100 {
|
||||
p.PageSize = 100
|
||||
}
|
||||
return p.PageSize
|
||||
}
|
||||
|
||||
// IDRequest ID请求
|
||||
type IDRequest struct {
|
||||
ID uint `uri:"id" binding:"required"`
|
||||
}
|
||||
|
||||
// IDsRequest IDs请求
|
||||
type IDsRequest struct {
|
||||
IDs []uint `json:"ids" binding:"required"`
|
||||
}
|
||||
|
||||
// StatusRequest 状态请求
|
||||
type StatusRequest struct {
|
||||
Status int `json:"status" binding:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
// DashboardStats 仪表盘统计
|
||||
type DashboardStats struct {
|
||||
ServerStats ServerStats `json:"server_stats"`
|
||||
K8sStats K8sClusterStats `json:"k8s_stats"`
|
||||
AlertStats AlertStats `json:"alert_stats"`
|
||||
PipelineStats PipelineStats `json:"pipeline_stats"`
|
||||
CostStats CostStats `json:"cost_stats"`
|
||||
TopResourceUsage TopResourceUsage `json:"top_resource_usage"`
|
||||
ExpiringCerts []DomainCert `json:"expiring_certs"`
|
||||
}
|
||||
|
||||
// CostStats 成本统计
|
||||
type CostStats struct {
|
||||
TotalCost float64 `json:"total_cost"`
|
||||
MonthCost float64 `json:"month_cost"`
|
||||
CloudCosts []CloudCost `json:"cloud_costs"`
|
||||
MonthTrend []MonthTrend `json:"month_trend"`
|
||||
}
|
||||
|
||||
// CloudCost 云平台成本
|
||||
type CloudCost struct {
|
||||
Cloud string `json:"cloud"`
|
||||
Cost float64 `json:"cost"`
|
||||
}
|
||||
|
||||
// MonthTrend 月度趋势
|
||||
type MonthTrend struct {
|
||||
Month string `json:"month"`
|
||||
Cost float64 `json:"cost"`
|
||||
}
|
||||
|
||||
// TopResourceUsage Top资源使用
|
||||
type TopResourceUsage struct {
|
||||
CPU []ServerResource `json:"cpu"`
|
||||
Memory []ServerResource `json:"memory"`
|
||||
Disk []ServerResource `json:"disk"`
|
||||
}
|
||||
|
||||
// ServerResource 服务器资源
|
||||
type ServerResource struct {
|
||||
ID uint `json:"id"`
|
||||
Hostname string `json:"hostname"`
|
||||
IP string `json:"ip"`
|
||||
Usage float64 `json:"usage"`
|
||||
}
|
||||
|
||||
// DomainCert 域名证书
|
||||
type DomainCert struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ExpireDate string `json:"expire_date"`
|
||||
DaysUntilExpiry int `json:"days_until_expiry"`
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// K8sCluster K8s集群模型
|
||||
type K8sCluster struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
Name string `gorm:"size:128;not null" json:"name"`
|
||||
Code string `gorm:"size:64;uniqueIndex;not null" json:"code"`
|
||||
Version string `gorm:"size:32" json:"version"`
|
||||
Status string `gorm:"size:32;default:healthy" json:"status"` // healthy, warning, error
|
||||
APIEndpoint string `gorm:"size:255" json:"api_endpoint"`
|
||||
KubeConfig string `gorm:"type:text" json:"-"`
|
||||
NodeCount int `gorm:"default:0" json:"node_count"`
|
||||
PodCount int `gorm:"default:0" json:"pod_count"`
|
||||
ServiceCount int `gorm:"default:0" json:"service_count"`
|
||||
CPUUsage float64 `json:"cpu_usage"`
|
||||
MemoryUsage float64 `json:"memory_usage"`
|
||||
Description string `gorm:"size:500" json:"description"`
|
||||
Labels string `gorm:"type:text" json:"labels"` // JSON
|
||||
LastSyncAt *time.Time `json:"last_sync_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (K8sCluster) TableName() string {
|
||||
return "k8s_clusters"
|
||||
}
|
||||
|
||||
// K8sNode K8s节点模型
|
||||
type K8sNode struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
ClusterID uint `gorm:"not null" json:"cluster_id"`
|
||||
Name string `gorm:"size:128;not null" json:"name"`
|
||||
IP string `gorm:"size:64" json:"ip"`
|
||||
Role string `gorm:"size:32" json:"role"` // master, worker
|
||||
Status string `gorm:"size:32" json:"status"`
|
||||
OS string `gorm:"size:64" json:"os"`
|
||||
KubeletVer string `gorm:"size:32" json:"kubelet_version"`
|
||||
CPU int `json:"cpu"`
|
||||
Memory int64 `json:"memory"`
|
||||
Disk int64 `json:"disk"`
|
||||
CPUUsage float64 `json:"cpu_usage"`
|
||||
MemoryUsage float64 `json:"memory_usage"`
|
||||
DiskUsage float64 `json:"disk_usage"`
|
||||
PodCount int `json:"pod_count"`
|
||||
MaxPods int `json:"max_pods"`
|
||||
Labels string `gorm:"type:text" json:"labels"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
// 关联
|
||||
Cluster *K8sCluster `json:"cluster,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (K8sNode) TableName() string {
|
||||
return "k8s_nodes"
|
||||
}
|
||||
|
||||
// K8sPod K8s Pod模型
|
||||
type K8sPod struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
ClusterID uint `gorm:"not null" json:"cluster_id"`
|
||||
Namespace string `gorm:"size:64;not null" json:"namespace"`
|
||||
Name string `gorm:"size:128;not null" json:"name"`
|
||||
Status string `gorm:"size:32" json:"status"` // Running, Pending, Succeeded, Failed
|
||||
NodeName string `gorm:"size:128" json:"node_name"`
|
||||
IP string `gorm:"size:64" json:"ip"`
|
||||
RestartCount int `json:"restart_count"`
|
||||
Image string `gorm:"size:255" json:"image"`
|
||||
CPUUsage float64 `json:"cpu_usage"`
|
||||
MemoryUsage float64 `json:"memory_usage"`
|
||||
Labels string `gorm:"type:text" json:"labels"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
// 关联
|
||||
Cluster *K8sCluster `json:"cluster,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (K8sPod) TableName() string {
|
||||
return "k8s_pods"
|
||||
}
|
||||
|
||||
// K8sDeployment K8s Deployment模型
|
||||
type K8sDeployment struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
ClusterID uint `gorm:"not null" json:"cluster_id"`
|
||||
Namespace string `gorm:"size:64;not null" json:"namespace"`
|
||||
Name string `gorm:"size:128;not null" json:"name"`
|
||||
Replicas int `json:"replicas"`
|
||||
AvailableReplicas int `json:"available_replicas"`
|
||||
ReadyReplicas int `json:"ready_replicas"`
|
||||
Image string `gorm:"size:255" json:"image"`
|
||||
Strategy string `gorm:"size:32" json:"strategy"`
|
||||
Labels string `gorm:"type:text" json:"labels"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
// 关联
|
||||
Cluster *K8sCluster `json:"cluster,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (K8sDeployment) TableName() string {
|
||||
return "k8s_deployments"
|
||||
}
|
||||
|
||||
// K8sService K8s Service模型
|
||||
type K8sService struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
ClusterID uint `gorm:"not null" json:"cluster_id"`
|
||||
Namespace string `gorm:"size:64;not null" json:"namespace"`
|
||||
Name string `gorm:"size:128;not null" json:"name"`
|
||||
Type string `gorm:"size:32" json:"type"` // ClusterIP, NodePort, LoadBalancer
|
||||
ClusterIP string `gorm:"size:64" json:"cluster_ip"`
|
||||
ExternalIP string `gorm:"size:255" json:"external_ip"`
|
||||
Ports string `gorm:"type:text" json:"ports"` // JSON
|
||||
Selector string `gorm:"type:text" json:"selector"`
|
||||
Labels string `gorm:"type:text" json:"labels"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
// 关联
|
||||
Cluster *K8sCluster `json:"cluster,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (K8sService) TableName() string {
|
||||
return "k8s_services"
|
||||
}
|
||||
|
||||
// K8sIngress K8s Ingress模型
|
||||
type K8sIngress struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
ClusterID uint `gorm:"not null" json:"cluster_id"`
|
||||
Namespace string `gorm:"size:64;not null" json:"namespace"`
|
||||
Name string `gorm:"size:128;not null" json:"name"`
|
||||
Hosts string `gorm:"type:text" json:"hosts"` // JSON
|
||||
Backend string `gorm:"size:255" json:"backend"`
|
||||
TLS bool `json:"tls"`
|
||||
TLSHosts string `gorm:"type:text" json:"tls_hosts"`
|
||||
Labels string `gorm:"type:text" json:"labels"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
// 关联
|
||||
Cluster *K8sCluster `json:"cluster,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (K8sIngress) TableName() string {
|
||||
return "k8s_ingresses"
|
||||
}
|
||||
|
||||
// CreateK8sClusterRequest 创建K8s集群请求
|
||||
type CreateK8sClusterRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
Version string `json:"version"`
|
||||
APIEndpoint string `json:"api_endpoint"`
|
||||
KubeConfig string `json:"kube_config"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// K8sClusterStats K8s集群统计
|
||||
type K8sClusterStats struct {
|
||||
Total int64 `json:"total"`
|
||||
Healthy int64 `json:"healthy"`
|
||||
Warning int64 `json:"warning"`
|
||||
Error int64 `json:"error"`
|
||||
TotalNodes int64 `json:"total_nodes"`
|
||||
TotalPods int64 `json:"total_pods"`
|
||||
TotalServices int64 `json:"total_services"`
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// NavigationGroup 导航分组模型
|
||||
type NavigationGroup struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
Name string `gorm:"size:64;not null" json:"name"`
|
||||
Code string `gorm:"size:64;uniqueIndex;not null" json:"code"`
|
||||
Description string `gorm:"size:255" json:"description"`
|
||||
Icon string `gorm:"size:64" json:"icon"`
|
||||
Sort int `gorm:"default:0" json:"sort"`
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
LinkCount int `gorm:"default:0" json:"link_count"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (NavigationGroup) TableName() string {
|
||||
return "navigation_groups"
|
||||
}
|
||||
|
||||
// NavigationLink 导航链接模型
|
||||
type NavigationLink struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
GroupID uint `json:"group_id"`
|
||||
Name string `gorm:"size:128;not null" json:"name"`
|
||||
URL string `gorm:"size:500;not null" json:"url"`
|
||||
Description string `gorm:"size:255" json:"description"`
|
||||
Icon string `gorm:"size:64" json:"icon"`
|
||||
Tags string `gorm:"type:text" json:"tags"` // JSON
|
||||
IsFavorite bool `gorm:"default:false" json:"is_favorite"`
|
||||
Sort int `gorm:"default:0" json:"sort"`
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
// 关联
|
||||
Group *NavigationGroup `json:"group,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (NavigationLink) TableName() string {
|
||||
return "navigation_links"
|
||||
}
|
||||
|
||||
// CreateNavigationGroupRequest 创建导航分组请求
|
||||
type CreateNavigationGroupRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Icon string `json:"icon"`
|
||||
Sort int `json:"sort"`
|
||||
}
|
||||
|
||||
// UpdateNavigationGroupRequest 更新导航分组请求
|
||||
type UpdateNavigationGroupRequest struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Icon string `json:"icon"`
|
||||
Sort int `json:"sort"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// CreateNavigationLinkRequest 创建导航链接请求
|
||||
type CreateNavigationLinkRequest struct {
|
||||
GroupID uint `json:"group_id" binding:"required"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
URL string `json:"url" binding:"required,url"`
|
||||
Description string `json:"description"`
|
||||
Icon string `json:"icon"`
|
||||
Tags []string `json:"tags"`
|
||||
Sort int `json:"sort"`
|
||||
}
|
||||
|
||||
// UpdateNavigationLinkRequest 更新导航链接请求
|
||||
type UpdateNavigationLinkRequest struct {
|
||||
GroupID uint `json:"group_id"`
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url" binding:"omitempty,url"`
|
||||
Description string `json:"description"`
|
||||
Icon string `json:"icon"`
|
||||
Tags []string `json:"tags"`
|
||||
Sort int `json:"sort"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// ToggleFavoriteRequest 切换收藏请求
|
||||
type ToggleFavoriteRequest struct {
|
||||
IsFavorite bool `json:"is_favorite"`
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Server 服务器模型
|
||||
type Server struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
Hostname string `gorm:"size:128;not null" json:"hostname"`
|
||||
IP string `gorm:"size:64;not null" json:"ip"`
|
||||
PublicIP string `gorm:"size:64" json:"public_ip"`
|
||||
Port int `gorm:"default:22" json:"port"`
|
||||
Username string `gorm:"size:64;default:root" json:"username"`
|
||||
Password string `gorm:"size:255" json:"-"`
|
||||
SSHKey string `gorm:"type:text" json:"-"`
|
||||
AuthType string `gorm:"size:32;default:password" json:"auth_type"` // password, key
|
||||
OS string `gorm:"size:64" json:"os"`
|
||||
OSVersion string `gorm:"size:64" json:"os_version"`
|
||||
Arch string `gorm:"size:32" json:"arch"`
|
||||
CPU int `json:"cpu"`
|
||||
Memory int64 `json:"memory"` // MB
|
||||
Disk int64 `json:"disk"` // GB
|
||||
Status string `gorm:"size:32;default:stopped" json:"status"` // running, stopped, maintenance, error
|
||||
CPUUsage float64 `json:"cpu_usage"`
|
||||
MemoryUsage float64 `json:"memory_usage"`
|
||||
DiskUsage float64 `json:"disk_usage"`
|
||||
Uptime int64 `json:"uptime"` // seconds
|
||||
AgentVersion string `gorm:"size:32" json:"agent_version"`
|
||||
AgentStatus string `gorm:"size:32;default:offline" json:"agent_status"` // online, offline
|
||||
RegionID *uint `json:"region_id"`
|
||||
CloudID *uint `json:"cloud_id"`
|
||||
Tags string `gorm:"type:text" json:"tags"` // JSON
|
||||
Description string `gorm:"size:500" json:"description"`
|
||||
LastCheckAt *time.Time `json:"last_check_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
// 关联
|
||||
Region *Region `json:"region,omitempty"`
|
||||
Cloud *Cloud `json:"cloud,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (Server) TableName() string {
|
||||
return "servers"
|
||||
}
|
||||
|
||||
// Cloud 云平台模型
|
||||
type Cloud struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
Name string `gorm:"size:64;not null" json:"name"`
|
||||
Code string `gorm:"size:64;uniqueIndex;not null" json:"code"`
|
||||
Type string `gorm:"size:32;default:public" json:"type"` // public, private, hybrid
|
||||
Status string `gorm:"size:32;default:active" json:"status"`
|
||||
Website string `gorm:"size:255" json:"website"`
|
||||
Description string `gorm:"size:500" json:"description"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (Cloud) TableName() string {
|
||||
return "clouds"
|
||||
}
|
||||
|
||||
// CloudAccount 云账号模型
|
||||
type CloudAccount struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
Name string `gorm:"size:128;not null" json:"name"`
|
||||
Provider string `gorm:"size:64;not null" json:"provider"` // aws, aliyun, tencent, huawei, azure
|
||||
AccessKey string `gorm:"size:255;not null" json:"-"`
|
||||
SecretKey string `gorm:"size:255;not null" json:"-"`
|
||||
Status string `gorm:"size:32;default:active" json:"status"` // active, inactive, error
|
||||
RegionCount int `gorm:"default:0" json:"region_count"`
|
||||
ServerCount int `gorm:"default:0" json:"server_count"`
|
||||
LastSyncAt *time.Time `json:"last_sync_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (CloudAccount) TableName() string {
|
||||
return "cloud_accounts"
|
||||
}
|
||||
|
||||
// Region 区域模型
|
||||
type Region struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
Name string `gorm:"size:64;not null" json:"name"`
|
||||
Code string `gorm:"size:64;uniqueIndex;not null" json:"code"`
|
||||
Provider string `gorm:"size:64;not null" json:"provider"`
|
||||
Location string `gorm:"size:128" json:"location"`
|
||||
Status string `gorm:"size:32;default:active" json:"status"`
|
||||
Endpoint string `gorm:"size:255" json:"endpoint"`
|
||||
VPCCount int `gorm:"default:0" json:"vpc_count"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (Region) TableName() string {
|
||||
return "regions"
|
||||
}
|
||||
|
||||
// Domain 域名模型
|
||||
type Domain struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
Name string `gorm:"size:255;uniqueIndex;not null" json:"name"`
|
||||
Registrar string `gorm:"size:128" json:"registrar"`
|
||||
Status string `gorm:"size:32;default:active" json:"status"` // active, expired, pending
|
||||
ExpireDate *time.Time `json:"expire_date"`
|
||||
DNSProvider string `gorm:"size:128" json:"dns_provider"`
|
||||
RecordCount int `gorm:"default:0" json:"record_count"`
|
||||
SSLEnabled bool `gorm:"default:false" json:"ssl_enabled"`
|
||||
SSLExpireDate *time.Time `json:"ssl_expire_date"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (Domain) TableName() string {
|
||||
return "domains"
|
||||
}
|
||||
|
||||
// CreateServerRequest 创建服务器请求
|
||||
type CreateServerRequest struct {
|
||||
Hostname string `json:"hostname" binding:"required"`
|
||||
IP string `json:"ip" binding:"required,ip"`
|
||||
PublicIP string `json:"public_ip" binding:"omitempty,ip"`
|
||||
Port int `json:"port" binding:"omitempty,min=1,max=65535"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
SSHKey string `json:"ssh_key"`
|
||||
AuthType string `json:"auth_type" binding:"omitempty,oneof=password key"`
|
||||
OS string `json:"os"`
|
||||
CPU int `json:"cpu"`
|
||||
Memory int64 `json:"memory"`
|
||||
Disk int64 `json:"disk"`
|
||||
RegionID *uint `json:"region_id"`
|
||||
CloudID *uint `json:"cloud_id"`
|
||||
Tags string `json:"tags"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// UpdateServerRequest 更新服务器请求
|
||||
type UpdateServerRequest struct {
|
||||
Hostname string `json:"hostname"`
|
||||
PublicIP string `json:"public_ip" binding:"omitempty,ip"`
|
||||
Port int `json:"port" binding:"omitempty,min=1,max=65535"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
SSHKey string `json:"ssh_key"`
|
||||
AuthType string `json:"auth_type" binding:"omitempty,oneof=password key"`
|
||||
OS string `json:"os"`
|
||||
CPU int `json:"cpu"`
|
||||
Memory int64 `json:"memory"`
|
||||
Disk int64 `json:"disk"`
|
||||
Status string `json:"status" binding:"omitempty,oneof=running stopped maintenance error"`
|
||||
RegionID *uint `json:"region_id"`
|
||||
CloudID *uint `json:"cloud_id"`
|
||||
Tags string `json:"tags"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// ServerListResponse 服务器列表响应
|
||||
type ServerListResponse struct {
|
||||
List []Server `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
// ServerStats 服务器统计
|
||||
type ServerStats struct {
|
||||
Total int64 `json:"total"`
|
||||
Running int64 `json:"running"`
|
||||
Stopped int64 `json:"stopped"`
|
||||
Maintenance int64 `json:"maintenance"`
|
||||
Error int64 `json:"error"`
|
||||
AvgCPU float64 `json:"avg_cpu"`
|
||||
AvgMemory float64 `json:"avg_memory"`
|
||||
AvgDisk float64 `json:"avg_disk"`
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// User 用户模型
|
||||
type User struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
Username string `gorm:"size:64;uniqueIndex;not null" json:"username"`
|
||||
Password string `gorm:"size:255;not null" json:"-"`
|
||||
Email string `gorm:"size:128;uniqueIndex" json:"email"`
|
||||
Phone string `gorm:"size:32" json:"phone"`
|
||||
RealName string `gorm:"size:64" json:"real_name"`
|
||||
Avatar string `gorm:"size:255" json:"avatar"`
|
||||
Status int `gorm:"default:1" json:"status"` // 0:禁用 1:启用
|
||||
IsAdmin bool `gorm:"default:false" json:"is_admin"`
|
||||
LastLogin *time.Time `json:"last_login"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
// 关联
|
||||
Roles []Role `gorm:"many2many:user_roles;" json:"roles,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (User) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
// Role 角色模型
|
||||
type Role struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
Name string `gorm:"size:64;uniqueIndex;not null" json:"name"`
|
||||
Code string `gorm:"size:64;uniqueIndex;not null" json:"code"`
|
||||
Description string `gorm:"size:255" json:"description"`
|
||||
Status int `gorm:"default:1" json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
// 关联
|
||||
Permissions []Permission `gorm:"many2many:role_permissions;" json:"permissions,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (Role) TableName() string {
|
||||
return "roles"
|
||||
}
|
||||
|
||||
// Permission 权限模型
|
||||
type Permission struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
Name string `gorm:"size:64;not null" json:"name"`
|
||||
Code string `gorm:"size:128;uniqueIndex;not null" json:"code"`
|
||||
Type string `gorm:"size:32" json:"type"` // menu, button, api
|
||||
Path string `gorm:"size:255" json:"path"`
|
||||
Method string `gorm:"size:32" json:"method"`
|
||||
ParentID *uint `json:"parent_id"`
|
||||
Sort int `gorm:"default:0" json:"sort"`
|
||||
Status int `gorm:"default:1" json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (Permission) TableName() string {
|
||||
return "permissions"
|
||||
}
|
||||
|
||||
// LoginRequest 登录请求
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
// LoginResponse 登录响应
|
||||
type LoginResponse struct {
|
||||
Token string `json:"token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
User *User `json:"user"`
|
||||
}
|
||||
|
||||
// CreateUserRequest 创建用户请求
|
||||
type CreateUserRequest struct {
|
||||
Username string `json:"username" binding:"required,min=3,max=64"`
|
||||
Password string `json:"password" binding:"required,min=6,max=32"`
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Phone string `json:"phone"`
|
||||
RealName string `json:"real_name"`
|
||||
RoleIDs []uint `json:"role_ids"`
|
||||
}
|
||||
|
||||
// UpdateUserRequest 更新用户请求
|
||||
type UpdateUserRequest struct {
|
||||
Email string `json:"email" binding:"omitempty,email"`
|
||||
Phone string `json:"phone"`
|
||||
RealName string `json:"real_name"`
|
||||
Status *int `json:"status"`
|
||||
RoleIDs []uint `json:"role_ids"`
|
||||
}
|
||||
|
||||
// ChangePasswordRequest 修改密码请求
|
||||
type ChangePasswordRequest struct {
|
||||
OldPassword string `json:"old_password" binding:"required"`
|
||||
NewPassword string `json:"new_password" binding:"required,min=6,max=32"`
|
||||
}
|
||||
Reference in new issue
Block a user