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: "删除成功",
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user