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:
attacker committed 2026-06-13 23:09:47 +08:00
commit 7d6674bddd
156 files changed
+45704

No files matched your search

+350
View File
@@ -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)
}
+435
View File
@@ -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,
})
}
+362
View File
@@ -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)
}
+553
View File
@@ -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,
})
}
+279
View File
@@ -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,
})
}
+345
View File
@@ -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,
})
}
+428
View File
@@ -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,
})
}
+504
View File
@@ -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,
})
}
+566
View File
@@ -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: "删除成功",
})
}
+538
View File
@@ -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: "删除成功",
})
}
+133
View File
@@ -0,0 +1,133 @@
package middleware
import (
"net/http"
"strings"
"time"
"ezops/internal/config"
"ezops/internal/models"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
// JWTClaims JWT声明
type JWTClaims struct {
UserID uint `json:"user_id"`
Username string `json:"username"`
IsAdmin bool `json:"is_admin"`
jwt.RegisteredClaims
}
// GenerateToken 生成JWT令牌
func GenerateToken(user *models.User, cfg *config.JWTConfig) (string, error) {
claims := JWTClaims{
UserID: user.ID,
Username: user.Username,
IsAdmin: user.IsAdmin,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(cfg.ExpireHour) * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: "ezops",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(cfg.Secret))
}
// JWTAuth JWT认证中间件
func JWTAuth(cfg *config.JWTConfig) gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.JSON(http.StatusUnauthorized, models.Response{
Code: 401,
Message: "未提供认证令牌",
})
c.Abort()
return
}
parts := strings.SplitN(authHeader, " ", 2)
if !(len(parts) == 2 && parts[0] == "Bearer") {
c.JSON(http.StatusUnauthorized, models.Response{
Code: 401,
Message: "认证令牌格式错误",
})
c.Abort()
return
}
tokenString := parts[1]
token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(cfg.Secret), nil
})
if err != nil {
c.JSON(http.StatusUnauthorized, models.Response{
Code: 401,
Message: "无效的认证令牌: " + err.Error(),
})
c.Abort()
return
}
if claims, ok := token.Claims.(*JWTClaims); ok && token.Valid {
c.Set("userID", claims.UserID)
c.Set("username", claims.Username)
c.Set("isAdmin", claims.IsAdmin)
c.Next()
} else {
c.JSON(http.StatusUnauthorized, models.Response{
Code: 401,
Message: "无效的认证令牌",
})
c.Abort()
}
}
}
// AdminAuth 管理员认证中间件
func AdminAuth() gin.HandlerFunc {
return func(c *gin.Context) {
isAdmin, exists := c.Get("isAdmin")
if !exists || !isAdmin.(bool) {
c.JSON(http.StatusForbidden, models.Response{
Code: 403,
Message: "需要管理员权限",
})
c.Abort()
return
}
c.Next()
}
}
// GetUserID 获取当前用户ID
func GetUserID(c *gin.Context) uint {
userID, exists := c.Get("userID")
if !exists {
return 0
}
return userID.(uint)
}
// GetUsername 获取当前用户名
func GetUsername(c *gin.Context) string {
username, exists := c.Get("username")
if !exists {
return ""
}
return username.(string)
}
// GetIsAdmin 获取是否为管理员
func GetIsAdmin(c *gin.Context) bool {
isAdmin, exists := c.Get("isAdmin")
if !exists {
return false
}
return isAdmin.(bool)
}
+212
View File
@@ -0,0 +1,212 @@
package routes
import (
"ezops/internal/api/handlers"
"ezops/internal/api/middleware"
"ezops/internal/config"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"gorm.io/gorm"
)
// Register 注册所有路由
func Register(r *gin.Engine, db *gorm.DB, log *zap.Logger, cfg *config.Config) {
// API版本前缀
api := r.Group("/api/v1")
// 健康检查
r.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok"})
})
// 公开路由
authHandler := handlers.NewUserHandler(db, cfg)
api.POST("/auth/login", authHandler.Login)
api.POST("/auth/register", authHandler.Register)
// 需要认证的路由
authorized := api.Group("")
authorized.Use(middleware.JWTAuth(&cfg.JWT))
// 用户路由
user := authorized.Group("/user")
{
user.GET("/profile", authHandler.GetProfile)
user.PUT("/profile", authHandler.UpdateProfile)
user.PUT("/password", authHandler.ChangePassword)
}
// 管理员路由
admin := authorized.Group("/admin")
admin.Use(middleware.AdminAuth())
{
admin.GET("/users", authHandler.ListUsers)
admin.GET("/users/:id", authHandler.GetUser)
admin.POST("/users", authHandler.CreateUser)
admin.PUT("/users/:id", authHandler.UpdateUser)
admin.DELETE("/users/:id", authHandler.DeleteUser)
}
// 仪表盘路由
dashboardHandler := handlers.NewDashboardHandler(db)
dashboard := authorized.Group("/dashboard")
{
dashboard.GET("/stats", dashboardHandler.GetStats)
dashboard.GET("/recent-alerts", dashboardHandler.GetRecentAlerts)
dashboard.GET("/recent-releases", dashboardHandler.GetRecentReleases)
dashboard.GET("/metrics", dashboardHandler.GetMetrics)
}
// 服务器路由
serverHandler := handlers.NewServerHandler(db)
servers := authorized.Group("/servers")
{
servers.GET("", serverHandler.ListServers)
servers.GET("/stats", serverHandler.GetServerStats)
servers.GET("/:id", serverHandler.GetServer)
servers.POST("", serverHandler.CreateServer)
servers.PUT("/:id", serverHandler.UpdateServer)
servers.DELETE("/:id", serverHandler.DeleteServer)
servers.POST("/batch-delete", serverHandler.BatchDeleteServers)
}
// 云账号路由
cloudAccountHandler := handlers.NewCloudAccountHandler(db)
cloudAccounts := authorized.Group("/cloud-accounts")
{
cloudAccounts.GET("", cloudAccountHandler.ListCloudAccounts)
cloudAccounts.POST("", cloudAccountHandler.CreateCloudAccount)
cloudAccounts.DELETE("/:id", cloudAccountHandler.DeleteCloudAccount)
}
// 域名路由
domainHandler := handlers.NewDomainHandler(db)
domains := authorized.Group("/domains")
{
domains.GET("", domainHandler.ListDomains)
domains.POST("", domainHandler.CreateDomain)
domains.DELETE("/:id", domainHandler.DeleteDomain)
}
// K8s集群路由
k8sHandler := handlers.NewK8sHandler(db)
clusters := authorized.Group("/k8s-clusters")
{
clusters.GET("", k8sHandler.ListClusters)
clusters.GET("/:id", k8sHandler.GetCluster)
clusters.POST("", k8sHandler.CreateCluster)
clusters.PUT("/:id", k8sHandler.UpdateCluster)
clusters.DELETE("/:id", k8sHandler.DeleteCluster)
clusters.GET("/:id/nodes", k8sHandler.ListNodes)
clusters.GET("/:id/pods", k8sHandler.ListPods)
clusters.GET("/:id/deployments", k8sHandler.ListDeployments)
clusters.GET("/:id/services", k8sHandler.ListServices)
clusters.GET("/:id/ingresses", k8sHandler.ListIngresses)
}
// 告警路由
alertHandler := handlers.NewAlertHandler(db)
alerts := authorized.Group("/alerts")
{
alerts.GET("", alertHandler.ListAlerts)
alerts.GET("/rules", alertHandler.ListRules)
alerts.POST("/rules", alertHandler.CreateRule)
alerts.PUT("/rules/:id", alertHandler.UpdateRule)
alerts.DELETE("/rules/:id", alertHandler.DeleteRule)
alerts.POST("/rules/:id/toggle", alertHandler.ToggleRule)
alerts.POST("/:id/ack", alertHandler.AcknowledgeAlert)
}
// 流水线路由
pipelineHandler := handlers.NewPipelineHandler(db)
pipelines := authorized.Group("/pipelines")
{
pipelines.GET("", pipelineHandler.ListPipelines)
pipelines.POST("", pipelineHandler.CreatePipeline)
pipelines.GET("/:id", pipelineHandler.GetPipeline)
pipelines.PUT("/:id", pipelineHandler.UpdatePipeline)
pipelines.DELETE("/:id", pipelineHandler.DeletePipeline)
pipelines.POST("/:id/trigger", pipelineHandler.TriggerPipeline)
pipelines.GET("/:id/runs", pipelineHandler.ListRuns)
}
// 应用发布路由
releases := authorized.Group("/releases")
{
releases.GET("", pipelineHandler.ListReleases)
releases.POST("", pipelineHandler.CreateRelease)
releases.POST("/:id/approve", pipelineHandler.ApproveRelease)
releases.POST("/:id/deploy", pipelineHandler.DeployRelease)
releases.POST("/:id/rollback", pipelineHandler.RollbackRelease)
}
// 自动化运维路由
automationHandler := handlers.NewAutomationHandler(db)
cronJobs := authorized.Group("/cron-jobs")
{
cronJobs.GET("", automationHandler.ListCronJobs)
cronJobs.POST("", automationHandler.CreateCronJob)
cronJobs.GET("/:id", automationHandler.GetCronJob)
cronJobs.PUT("/:id", automationHandler.UpdateCronJob)
cronJobs.DELETE("/:id", automationHandler.DeleteCronJob)
cronJobs.POST("/:id/toggle", automationHandler.ToggleCronJob)
cronJobs.POST("/:id/execute", automationHandler.ExecuteCronJob)
}
scripts := authorized.Group("/scripts")
{
scripts.GET("", automationHandler.ListScripts)
scripts.POST("", automationHandler.CreateScript)
scripts.GET("/:id", automationHandler.GetScript)
scripts.PUT("/:id", automationHandler.UpdateScript)
scripts.DELETE("/:id", automationHandler.DeleteScript)
scripts.POST("/:id/execute", automationHandler.ExecuteScript)
}
// 审批流程路由
approvalHandler := handlers.NewApprovalHandler(db)
approvals := authorized.Group("/approvals")
{
approvals.GET("/templates", approvalHandler.ListTemplates)
approvals.POST("/templates", approvalHandler.CreateTemplate)
approvals.GET("/templates/:id", approvalHandler.GetTemplate)
approvals.PUT("/templates/:id", approvalHandler.UpdateTemplate)
approvals.DELETE("/templates/:id", approvalHandler.DeleteTemplate)
approvals.GET("/processes", approvalHandler.ListProcesses)
approvals.POST("/processes", approvalHandler.CreateProcess)
approvals.GET("/processes/:id", approvalHandler.GetProcess)
approvals.POST("/processes/:id/approve", approvalHandler.ApproveProcess)
approvals.POST("/processes/:id/reject", approvalHandler.RejectProcess)
}
// 导航路由
navHandler := handlers.NewNavigationHandler(db)
navigations := authorized.Group("/navigations")
{
navigations.GET("/groups", navHandler.ListGroups)
navigations.POST("/groups", navHandler.CreateGroup)
navigations.PUT("/groups/:id", navHandler.UpdateGroup)
navigations.DELETE("/groups/:id", navHandler.DeleteGroup)
navigations.GET("/links", navHandler.ListLinks)
navigations.POST("/links", navHandler.CreateLink)
navigations.PUT("/links/:id", navHandler.UpdateLink)
navigations.DELETE("/links/:id", navHandler.DeleteLink)
navigations.POST("/links/:id/favorite", navHandler.ToggleFavorite)
}
// 审计日志路由
auditHandler := handlers.NewAuditHandler(db)
audit := authorized.Group("/audit")
{
audit.GET("/logs", auditHandler.ListAuditLogs)
audit.GET("/logs/:id", auditHandler.GetAuditLogDetail)
audit.GET("/modules", auditHandler.GetAuditModules)
audit.GET("/actions", auditHandler.GetAuditActions)
audit.GET("/logs/export", auditHandler.ExportAuditLogs)
audit.GET("/statistics", auditHandler.GetAuditStatistics)
audit.POST("/logs/clear", auditHandler.ClearAuditLogs)
}
}
+96
View File
@@ -0,0 +1,96 @@
package config
import (
"github.com/spf13/viper"
"go.uber.org/zap"
)
// Config 应用配置
type Config struct {
Mode string `mapstructure:"mode"`
Port string `mapstructure:"port"`
LogLevel string `mapstructure:"log_level"`
Database DatabaseConfig `mapstructure:"database"`
JWT JWTConfig `mapstructure:"jwt"`
Redis RedisConfig `mapstructure:"redis"`
Websocket WebsocketConfig `mapstructure:"websocket"`
}
// DatabaseConfig 数据库配置
type DatabaseConfig struct {
Driver string `mapstructure:"driver"`
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
DBName string `mapstructure:"dbname"`
Charset string `mapstructure:"charset"`
MaxConn int `mapstructure:"max_conn"`
MaxIdle int `mapstructure:"max_idle"`
}
// JWTConfig JWT配置
type JWTConfig struct {
Secret string `mapstructure:"secret"`
ExpireHour int `mapstructure:"expire_hour"`
}
// RedisConfig Redis配置
type RedisConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Password string `mapstructure:"password"`
DB int `mapstructure:"db"`
}
// WebsocketConfig WebSocket配置
type WebsocketConfig struct {
ReadBufferSize int `mapstructure:"read_buffer_size"`
WriteBufferSize int `mapstructure:"write_buffer_size"`
}
// Load 加载配置
func Load() *Config {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath("./configs")
viper.AddConfigPath(".")
// 设置默认值
viper.SetDefault("mode", "development")
viper.SetDefault("port", "8080")
viper.SetDefault("log_level", "info")
viper.SetDefault("database.driver", "mysql")
viper.SetDefault("database.host", "localhost")
viper.SetDefault("database.port", 3306)
viper.SetDefault("database.charset", "utf8mb4")
viper.SetDefault("database.max_conn", 100)
viper.SetDefault("database.max_idle", 10)
viper.SetDefault("jwt.expire_hour", 24)
viper.SetDefault("redis.host", "localhost")
viper.SetDefault("redis.port", 6379)
viper.SetDefault("websocket.read_buffer_size", 1024)
viper.SetDefault("websocket.write_buffer_size", 1024)
// 读取环境变量
viper.AutomaticEnv()
var cfg Config
if err := viper.ReadInConfig(); err != nil {
zap.L().Warn("config file not found, using defaults", zap.Error(err))
}
if err := viper.Unmarshal(&cfg); err != nil {
zap.L().Fatal("failed to unmarshal config", zap.Error(err))
}
return &cfg
}
// DSN 返回数据库连接字符串
func (d *DatabaseConfig) DSN() string {
if d.Driver == "mysql" {
return d.User + ":" + d.Password + "@tcp(" + d.Host + ":" + string(rune(d.Port)) + ")/" + d.DBName + "?charset=" + d.Charset + "&parseTime=True&loc=Local"
}
return ""
}
+137
View File
@@ -0,0 +1,137 @@
package models
import (
"time"
"gorm.io/gorm"
)
// AlertRule 告警规则模型
type AlertRule struct {
ID uint `gorm:"primarykey" json:"id"`
Name string `gorm:"size:128;not null" json:"name"`
Description string `gorm:"size:500" json:"description"`
Type string `gorm:"size:32;not null" json:"type"` // metric, log, event
Source string `gorm:"size:64;not null" json:"source"` // prometheus, zabbix, custom
Expression string `gorm:"type:text;not null" json:"expression"`
Severity string `gorm:"size:32;default:warning" json:"severity"` // info, warning, critical
Duration int `json:"duration"` // 持续时间(秒)
Enabled bool `gorm:"default:true" json:"enabled"`
Labels string `gorm:"type:text" json:"labels"` // JSON
Annotations string `gorm:"type:text" json:"annotations"` // JSON
NotifyChannels string `gorm:"type:text" json:"notify_channels"` // JSON
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// TableName 表名
func (AlertRule) TableName() string {
return "alert_rules"
}
// AlertEvent 告警事件模型
type AlertEvent struct {
ID uint `gorm:"primarykey" json:"id"`
RuleID uint `json:"rule_id"`
RuleName string `gorm:"size:128" json:"rule_name"`
Severity string `gorm:"size:32" json:"severity"`
Status string `gorm:"size:32;default:firing" json:"status"` // firing, resolved
Target string `gorm:"size:255" json:"target"`
Message string `gorm:"type:text" json:"message"`
Value float64 `json:"value"`
Labels string `gorm:"type:text" json:"labels"`
StartedAt time.Time `json:"started_at"`
ResolvedAt *time.Time `json:"resolved_at"`
Acknowledged bool `json:"acknowledged"`
AcknowledgedBy *uint `json:"acknowledged_by"`
AcknowledgedAt *time.Time `json:"acknowledged_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// 关联
Rule *AlertRule `json:"rule,omitempty"`
}
// TableName 表名
func (AlertEvent) TableName() string {
return "alert_events"
}
// NotifyChannel 通知渠道模型
type NotifyChannel struct {
ID uint `gorm:"primarykey" json:"id"`
Name string `gorm:"size:64;not null" json:"name"`
Type string `gorm:"size:32;not null" json:"type"` // email, sms, webhook, dingtalk, wechat
Config string `gorm:"type:text" json:"config"` // JSON
Enabled bool `gorm:"default:true" json:"enabled"`
TestStatus string `gorm:"size:32" json:"test_status"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// TableName 表名
func (NotifyChannel) TableName() string {
return "notify_channels"
}
// MetricData 监控指标数据
type MetricData struct {
ID uint `gorm:"primarykey" json:"id"`
ServerID *uint `json:"server_id"`
ClusterID *uint `json:"cluster_id"`
Name string `gorm:"size:64" json:"name"`
Value float64 `json:"value"`
Labels string `gorm:"type:text" json:"labels"`
Timestamp time.Time `json:"timestamp"`
}
// TableName 表名
func (MetricData) TableName() string {
return "metric_data"
}
// CreateAlertRuleRequest 创建告警规则请求
type CreateAlertRuleRequest struct {
Name string `json:"name" binding:"required"`
Description string `json:"description"`
Type string `json:"type" binding:"required,oneof=metric log event"`
Source string `json:"source" binding:"required"`
Expression string `json:"expression" binding:"required"`
Severity string `json:"severity" binding:"omitempty,oneof=info warning critical"`
Duration int `json:"duration"`
Labels string `json:"labels"`
Annotations string `json:"annotations"`
NotifyChannels []string `json:"notify_channels"`
}
// UpdateAlertRuleRequest 更新告警规则请求
type UpdateAlertRuleRequest struct {
Name string `json:"name"`
Description string `json:"description"`
Expression string `json:"expression"`
Severity string `json:"severity" binding:"omitempty,oneof=info warning critical"`
Duration int `json:"duration"`
Enabled *bool `json:"enabled"`
Labels string `json:"labels"`
Annotations string `json:"annotations"`
NotifyChannels []string `json:"notify_channels"`
}
// AlertStats 告警统计
type AlertStats struct {
Total int64 `json:"total"`
Firing int64 `json:"firing"`
Resolved int64 `json:"resolved"`
Critical int64 `json:"critical"`
Warning int64 `json:"warning"`
Info int64 `json:"info"`
TodayCount int64 `json:"today_count"`
WeekCount int64 `json:"week_count"`
}
// AcknowledgeAlertRequest 确认告警请求
type AcknowledgeAlertRequest struct {
Message string `json:"message"`
}
+108
View File
@@ -0,0 +1,108 @@
package models
import (
"time"
"gorm.io/gorm"
)
// ApprovalTemplate 审批模板模型
type ApprovalTemplate struct {
ID uint `gorm:"primarykey" json:"id"`
Name string `gorm:"size:128;not null" json:"name"`
Code string `gorm:"size:64;uniqueIndex;not null" json:"code"`
Description string `gorm:"size:500" json:"description"`
Type string `gorm:"size:32;not null" json:"type"` // release, server, permission
FormSchema string `gorm:"type:text" json:"form_schema"` // JSON
FlowConfig string `gorm:"type:text" json:"flow_config"` // JSON
Enabled bool `gorm:"default:true" json:"enabled"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// TableName 表名
func (ApprovalTemplate) TableName() string {
return "approval_templates"
}
// ApprovalProcess 审批流程模型
type ApprovalProcess struct {
ID uint `gorm:"primarykey" json:"id"`
TemplateID uint `json:"template_id"`
Title string `gorm:"size:255;not null" json:"title"`
Applicant uint `json:"applicant"`
Status string `gorm:"size:32;default:pending" json:"status"` // pending, approved, rejected
FormData string `gorm:"type:text" json:"form_data"` // JSON
CurrentStep int `gorm:"default:0" json:"current_step"`
TotalSteps int `json:"total_steps"`
ApprovedAt *time.Time `json:"approved_at"`
RejectedAt *time.Time `json:"rejected_at"`
RejectReason string `gorm:"size:500" json:"reject_reason"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
// 关联
Template *ApprovalTemplate `json:"template,omitempty"`
ApplicantUser *User `json:"applicant_user,omitempty"`
Steps []ApprovalStep `json:"steps,omitempty"`
}
// TableName 表名
func (ApprovalProcess) TableName() string {
return "approval_processes"
}
// ApprovalStep 审批步骤模型
type ApprovalStep struct {
ID uint `gorm:"primarykey" json:"id"`
ProcessID uint `json:"process_id"`
Step int `json:"step"`
Approver uint `json:"approver"`
Status string `gorm:"size:32;default:pending" json:"status"` // pending, approved, rejected
Comment string `gorm:"size:500" json:"comment"`
ApprovedAt *time.Time `json:"approved_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// 关联
ApproverUser *User `json:"approver_user,omitempty"`
}
// TableName 表名
func (ApprovalStep) TableName() string {
return "approval_steps"
}
// CreateApprovalTemplateRequest 创建审批模板请求
type CreateApprovalTemplateRequest struct {
Name string `json:"name" binding:"required"`
Code string `json:"code" binding:"required"`
Description string `json:"description"`
Type string `json:"type" binding:"required,oneof=release server permission"`
FormSchema string `json:"form_schema"`
FlowConfig string `json:"flow_config"`
}
// CreateApprovalProcessRequest 创建审批流程请求
type CreateApprovalProcessRequest struct {
TemplateID uint `json:"template_id" binding:"required"`
Title string `json:"title" binding:"required"`
FormData string `json:"form_data"`
}
// ApproveRequest 审批请求
type ApproveRequest struct {
Status string `json:"status" binding:"required,oneof=approved rejected"`
Comment string `json:"comment"`
}
// ApprovalStats 审批统计
type ApprovalStats struct {
Pending int64 `json:"pending"`
Approved int64 `json:"approved"`
Rejected int64 `json:"rejected"`
Total int64 `json:"total"`
MyPending int64 `json:"my_pending"`
}
+68
View File
@@ -0,0 +1,68 @@
package models
import (
"time"
)
// AuditLog 审计日志模型
type AuditLog struct {
ID uint `gorm:"primarykey" json:"id"`
UserID *uint `json:"user_id"`
Username string `gorm:"size:64" json:"username"`
IP string `gorm:"size:64" json:"ip"`
UserAgent string `gorm:"size:500" json:"user_agent"`
Module string `gorm:"size:64" json:"module"`
Action string `gorm:"size:64" json:"action"`
Resource string `gorm:"size:128" json:"resource"`
ResourceID string `gorm:"size:64" json:"resource_id"`
Request string `gorm:"type:text" json:"request"`
Response string `gorm:"type:text" json:"response"`
Status int `json:"status"`
Duration int `json:"duration"` // ms
Error string `gorm:"type:text" json:"error"`
CreatedAt time.Time `json:"created_at"`
}
// TableName 表名
func (AuditLog) TableName() string {
return "audit_logs"
}
// AuditLogQuery 审计日志查询
type AuditLogQuery struct {
UserID *uint `form:"user_id"`
Username string `form:"username"`
Module string `form:"module"`
Action string `form:"action"`
StartTime string `form:"start_time"`
EndTime string `form:"end_time"`
Page int `form:"page,default=1"`
PageSize int `form:"page_size,default=20"`
}
// AuditLogListResponse 审计日志列表响应
type AuditLogListResponse struct {
List []AuditLog `json:"list"`
Total int64 `json:"total"`
}
// AuditStats 审计统计
type AuditStats struct {
TodayCount int64 `json:"today_count"`
WeekCount int64 `json:"week_count"`
MonthCount int64 `json:"month_count"`
TopModules []ModuleStat `json:"top_modules"`
TopUsers []UserStat `json:"top_users"`
}
// ModuleStat 模块统计
type ModuleStat struct {
Module string `json:"module"`
Count int64 `json:"count"`
}
// UserStat 用户统计
type UserStat struct {
Username string `json:"username"`
Count int64 `json:"count"`
}
+151
View File
@@ -0,0 +1,151 @@
package models
import (
"time"
"gorm.io/gorm"
)
// CronJob 定时任务模型
type CronJob struct {
ID uint `gorm:"primarykey" json:"id"`
Name string `gorm:"size:128;not null" json:"name"`
Description string `gorm:"size:500" json:"description"`
CronExpr string `gorm:"size:64;not null" json:"cron_expr"`
Type string `gorm:"size:32;not null" json:"type"` // script, command, playbook
Content string `gorm:"type:text" json:"content"`
ScriptID *uint `json:"script_id"`
TargetType string `gorm:"size:32;default:server" json:"target_type"` // server, group, tag
Targets string `gorm:"type:text" json:"targets"` // JSON
Timeout int `gorm:"default:300" json:"timeout"`
Enabled bool `gorm:"default:true" json:"enabled"`
LastRunAt *time.Time `json:"last_run_at"`
LastStatus string `gorm:"size:32" json:"last_status"`
RunCount int `gorm:"default:0" json:"run_count"`
SuccessCount int `gorm:"default:0" json:"success_count"`
FailCount int `gorm:"default:0" json:"fail_count"`
NotifyOn string `gorm:"size:32;default:failure" json:"notify_on"` // always, failure, success
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
// 关联
Script *Script `json:"script,omitempty"`
}
// TableName 表名
func (CronJob) TableName() string {
return "cron_jobs"
}
// Script 脚本库模型
type Script struct {
ID uint `gorm:"primarykey" json:"id"`
Name string `gorm:"size:128;not null" json:"name"`
Code string `gorm:"size:64;uniqueIndex;not null" json:"code"`
Description string `gorm:"size:500" json:"description"`
Type string `gorm:"size:32;default:shell" json:"type"` // shell, python, powershell, yaml
Content string `gorm:"type:text;not null" json:"content"`
Params string `gorm:"type:text" json:"params"` // JSON
Timeout int `gorm:"default:300" json:"timeout"`
Version int `gorm:"default:1" json:"version"`
RunCount int `gorm:"default:0" json:"run_count"`
CreatedBy uint `json:"created_by"`
UpdatedBy uint `json:"updated_by"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// TableName 表名
func (Script) TableName() string {
return "scripts"
}
// TaskExecution 任务执行记录
type TaskExecution struct {
ID uint `gorm:"primarykey" json:"id"`
TaskType string `gorm:"size:32;not null" json:"task_type"` // cron, script, playbook
TaskID uint `json:"task_id"`
TaskName string `gorm:"size:128" json:"task_name"`
Status string `gorm:"size:32;default:pending" json:"status"` // pending, running, success, failed, cancelled
TargetType string `gorm:"size:32" json:"target_type"`
Targets string `gorm:"type:text" json:"targets"`
Output string `gorm:"type:longtext" json:"output"`
Error string `gorm:"type:text" json:"error"`
Duration int `json:"duration"`
StartedAt *time.Time `json:"started_at"`
FinishedAt *time.Time `json:"finished_at"`
CreatedBy uint `json:"created_by"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// TableName 表名
func (TaskExecution) TableName() string {
return "task_executions"
}
// Playbook Ansible Playbook模型
type Playbook struct {
ID uint `gorm:"primarykey" json:"id"`
Name string `gorm:"size:128;not null" json:"name"`
Code string `gorm:"size:64;uniqueIndex;not null" json:"code"`
Description string `gorm:"size:500" json:"description"`
Content string `gorm:"type:text;not null" json:"content"`
Variables string `gorm:"type:text" json:"variables"`
Inventory string `gorm:"type:text" json:"inventory"`
Timeout int `gorm:"default:600" json:"timeout"`
RunCount int `gorm:"default:0" json:"run_count"`
CreatedBy uint `json:"created_by"`
UpdatedBy uint `json:"updated_by"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// TableName 表名
func (Playbook) TableName() string {
return "playbooks"
}
// CreateCronJobRequest 创建定时任务请求
type CreateCronJobRequest struct {
Name string `json:"name" binding:"required"`
Description string `json:"description"`
CronExpr string `json:"cron_expr" binding:"required"`
Type string `json:"type" binding:"required,oneof=script command playbook"`
Content string `json:"content"`
ScriptID *uint `json:"script_id"`
TargetType string `json:"target_type" binding:"omitempty,oneof=server group tag"`
Targets []uint `json:"targets"`
Timeout int `json:"timeout"`
NotifyOn string `json:"notify_on" binding:"omitempty,oneof=always failure success"`
}
// CreateScriptRequest 创建脚本请求
type CreateScriptRequest struct {
Name string `json:"name" binding:"required"`
Code string `json:"code" binding:"required"`
Description string `json:"description"`
Type string `json:"type" binding:"omitempty,oneof=shell python powershell yaml"`
Content string `json:"content" binding:"required"`
Params string `json:"params"`
Timeout int `json:"timeout"`
}
// ExecuteScriptRequest 执行脚本请求
type ExecuteScriptRequest struct {
TargetType string `json:"target_type" binding:"required,oneof=server group tag"`
Targets []uint `json:"targets" binding:"required"`
Params map[string]string `json:"params"`
}
// AutomationStats 自动化统计
type AutomationStats struct {
CronJobCount int64 `json:"cron_job_count"`
ScriptCount int64 `json:"script_count"`
PlaybookCount int64 `json:"playbook_count"`
TodayExecutions int64 `json:"today_executions"`
SuccessRate float64 `json:"success_rate"`
}
+130
View File
@@ -0,0 +1,130 @@
package models
import (
"time"
"gorm.io/gorm"
)
// Pipeline CI/CD流水线模型
type Pipeline struct {
ID uint `gorm:"primarykey" json:"id"`
Name string `gorm:"size:128;not null" json:"name"`
Code string `gorm:"size:64;uniqueIndex;not null" json:"code"`
Description string `gorm:"size:500" json:"description"`
Type string `gorm:"size:32;default:deploy" json:"type"` // build, deploy, test
Source string `gorm:"size:255" json:"source"` // Git仓库地址
Branch string `gorm:"size:64;default:main" json:"branch"`
Dockerfile string `gorm:"size:255" json:"dockerfile"`
Stages string `gorm:"type:text" json:"stages"` // JSON
Variables string `gorm:"type:text" json:"variables"` // JSON
Enabled bool `gorm:"default:true" json:"enabled"`
LastRunAt *time.Time `json:"last_run_at"`
LastStatus string `gorm:"size:32" json:"last_status"`
RunCount int `gorm:"default:0" json:"run_count"`
SuccessCount int `gorm:"default:0" json:"success_count"`
FailCount int `gorm:"default:0" json:"fail_count"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// TableName 表名
func (Pipeline) TableName() string {
return "pipelines"
}
// PipelineRun 流水线运行记录
type PipelineRun struct {
ID uint `gorm:"primarykey" json:"id"`
PipelineID uint `gorm:"not null" json:"pipeline_id"`
BuildNumber int `json:"build_number"`
Status string `gorm:"size:32;default:pending" json:"status"` // pending, running, success, failed, cancelled
Branch string `gorm:"size:64" json:"branch"`
CommitID string `gorm:"size:64" json:"commit_id"`
CommitMsg string `gorm:"size:500" json:"commit_msg"`
Author string `gorm:"size:64" json:"author"`
Duration int `json:"duration"` // 秒
Stages string `gorm:"type:text" json:"stages"` // JSON
Logs string `gorm:"type:longtext" json:"logs"`
TriggeredBy string `gorm:"size:64" json:"triggered_by"`
StartedAt *time.Time `json:"started_at"`
FinishedAt *time.Time `json:"finished_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// 关联
Pipeline *Pipeline `json:"pipeline,omitempty"`
}
// TableName 表名
func (PipelineRun) TableName() string {
return "pipeline_runs"
}
// AppRelease 应用发布模型
type AppRelease struct {
ID uint `gorm:"primarykey" json:"id"`
AppName string `gorm:"size:128;not null" json:"app_name"`
Version string `gorm:"size:64;not null" json:"version"`
Environment string `gorm:"size:32;not null" json:"environment"` // dev, test, staging, prod
Status string `gorm:"size:32;default:pending" json:"status"` // pending, building, deploying, success, failed, rolled_back
CommitID string `gorm:"size:64" json:"commit_id"`
CommitMsg string `gorm:"size:500" json:"commit_msg"`
Author string `gorm:"size:64" json:"author"`
Approver string `gorm:"size:64" json:"approver"`
ApprovedAt *time.Time `json:"approved_at"`
DeployedAt *time.Time `json:"deployed_at"`
RollbackAt *time.Time `json:"rollback_at"`
Duration int `json:"duration"`
Servers string `gorm:"type:text" json:"servers"` // JSON
Description string `gorm:"size:500" json:"description"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// TableName 表名
func (AppRelease) TableName() string {
return "app_releases"
}
// CreatePipelineRequest 创建流水线请求
type CreatePipelineRequest struct {
Name string `json:"name" binding:"required"`
Code string `json:"code" binding:"required"`
Description string `json:"description"`
Type string `json:"type" binding:"omitempty,oneof=build deploy test"`
Source string `json:"source"`
Branch string `json:"branch"`
Dockerfile string `json:"dockerfile"`
Stages string `json:"stages"`
Variables string `json:"variables"`
}
// TriggerPipelineRequest 触发流水线请求
type TriggerPipelineRequest struct {
Branch string `json:"branch"`
Variables map[string]string `json:"variables"`
}
// CreateAppReleaseRequest 创建应用发布请求
type CreateAppReleaseRequest struct {
AppName string `json:"app_name" binding:"required"`
Version string `json:"version" binding:"required"`
Environment string `json:"environment" binding:"required,oneof=dev test staging prod"`
CommitID string `json:"commit_id"`
CommitMsg string `json:"commit_msg"`
Servers []uint `json:"servers"`
Description string `json:"description"`
}
// PipelineStats 流水线统计
type PipelineStats struct {
Total int64 `json:"total"`
Success int64 `json:"success"`
Failed int64 `json:"failed"`
Running int64 `json:"running"`
TodayCount int64 `json:"today_count"`
SuccessRate float64 `json:"success_rate"`
}
+111
View File
@@ -0,0 +1,111 @@
package models
// Response 通用响应
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
// ListResponse 列表响应
type ListResponse struct {
List interface{} `json:"list"`
Total int64 `json:"total"`
}
// PageQuery 分页查询
type PageQuery struct {
Page int `form:"page,default=1"`
PageSize int `form:"page_size,default=20"`
Keyword string `form:"keyword"`
Sort string `form:"sort"`
Order string `form:"order,default=desc"`
}
// GetOffset 获取偏移量
func (p *PageQuery) GetOffset() int {
if p.Page < 1 {
p.Page = 1
}
return (p.Page - 1) * p.GetLimit()
}
// GetLimit 获取每页数量
func (p *PageQuery) GetLimit() int {
if p.PageSize < 1 {
p.PageSize = 20
}
if p.PageSize > 100 {
p.PageSize = 100
}
return p.PageSize
}
// IDRequest ID请求
type IDRequest struct {
ID uint `uri:"id" binding:"required"`
}
// IDsRequest IDs请求
type IDsRequest struct {
IDs []uint `json:"ids" binding:"required"`
}
// StatusRequest 状态请求
type StatusRequest struct {
Status int `json:"status" binding:"required,oneof=0 1"`
}
// DashboardStats 仪表盘统计
type DashboardStats struct {
ServerStats ServerStats `json:"server_stats"`
K8sStats K8sClusterStats `json:"k8s_stats"`
AlertStats AlertStats `json:"alert_stats"`
PipelineStats PipelineStats `json:"pipeline_stats"`
CostStats CostStats `json:"cost_stats"`
TopResourceUsage TopResourceUsage `json:"top_resource_usage"`
ExpiringCerts []DomainCert `json:"expiring_certs"`
}
// CostStats 成本统计
type CostStats struct {
TotalCost float64 `json:"total_cost"`
MonthCost float64 `json:"month_cost"`
CloudCosts []CloudCost `json:"cloud_costs"`
MonthTrend []MonthTrend `json:"month_trend"`
}
// CloudCost 云平台成本
type CloudCost struct {
Cloud string `json:"cloud"`
Cost float64 `json:"cost"`
}
// MonthTrend 月度趋势
type MonthTrend struct {
Month string `json:"month"`
Cost float64 `json:"cost"`
}
// TopResourceUsage Top资源使用
type TopResourceUsage struct {
CPU []ServerResource `json:"cpu"`
Memory []ServerResource `json:"memory"`
Disk []ServerResource `json:"disk"`
}
// ServerResource 服务器资源
type ServerResource struct {
ID uint `json:"id"`
Hostname string `json:"hostname"`
IP string `json:"ip"`
Usage float64 `json:"usage"`
}
// DomainCert 域名证书
type DomainCert struct {
ID uint `json:"id"`
Name string `json:"name"`
ExpireDate string `json:"expire_date"`
DaysUntilExpiry int `json:"days_until_expiry"`
}
+188
View File
@@ -0,0 +1,188 @@
package models
import (
"time"
"gorm.io/gorm"
)
// K8sCluster K8s集群模型
type K8sCluster struct {
ID uint `gorm:"primarykey" json:"id"`
Name string `gorm:"size:128;not null" json:"name"`
Code string `gorm:"size:64;uniqueIndex;not null" json:"code"`
Version string `gorm:"size:32" json:"version"`
Status string `gorm:"size:32;default:healthy" json:"status"` // healthy, warning, error
APIEndpoint string `gorm:"size:255" json:"api_endpoint"`
KubeConfig string `gorm:"type:text" json:"-"`
NodeCount int `gorm:"default:0" json:"node_count"`
PodCount int `gorm:"default:0" json:"pod_count"`
ServiceCount int `gorm:"default:0" json:"service_count"`
CPUUsage float64 `json:"cpu_usage"`
MemoryUsage float64 `json:"memory_usage"`
Description string `gorm:"size:500" json:"description"`
Labels string `gorm:"type:text" json:"labels"` // JSON
LastSyncAt *time.Time `json:"last_sync_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// TableName 表名
func (K8sCluster) TableName() string {
return "k8s_clusters"
}
// K8sNode K8s节点模型
type K8sNode struct {
ID uint `gorm:"primarykey" json:"id"`
ClusterID uint `gorm:"not null" json:"cluster_id"`
Name string `gorm:"size:128;not null" json:"name"`
IP string `gorm:"size:64" json:"ip"`
Role string `gorm:"size:32" json:"role"` // master, worker
Status string `gorm:"size:32" json:"status"`
OS string `gorm:"size:64" json:"os"`
KubeletVer string `gorm:"size:32" json:"kubelet_version"`
CPU int `json:"cpu"`
Memory int64 `json:"memory"`
Disk int64 `json:"disk"`
CPUUsage float64 `json:"cpu_usage"`
MemoryUsage float64 `json:"memory_usage"`
DiskUsage float64 `json:"disk_usage"`
PodCount int `json:"pod_count"`
MaxPods int `json:"max_pods"`
Labels string `gorm:"type:text" json:"labels"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
// 关联
Cluster *K8sCluster `json:"cluster,omitempty"`
}
// TableName 表名
func (K8sNode) TableName() string {
return "k8s_nodes"
}
// K8sPod K8s Pod模型
type K8sPod struct {
ID uint `gorm:"primarykey" json:"id"`
ClusterID uint `gorm:"not null" json:"cluster_id"`
Namespace string `gorm:"size:64;not null" json:"namespace"`
Name string `gorm:"size:128;not null" json:"name"`
Status string `gorm:"size:32" json:"status"` // Running, Pending, Succeeded, Failed
NodeName string `gorm:"size:128" json:"node_name"`
IP string `gorm:"size:64" json:"ip"`
RestartCount int `json:"restart_count"`
Image string `gorm:"size:255" json:"image"`
CPUUsage float64 `json:"cpu_usage"`
MemoryUsage float64 `json:"memory_usage"`
Labels string `gorm:"type:text" json:"labels"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
// 关联
Cluster *K8sCluster `json:"cluster,omitempty"`
}
// TableName 表名
func (K8sPod) TableName() string {
return "k8s_pods"
}
// K8sDeployment K8s Deployment模型
type K8sDeployment struct {
ID uint `gorm:"primarykey" json:"id"`
ClusterID uint `gorm:"not null" json:"cluster_id"`
Namespace string `gorm:"size:64;not null" json:"namespace"`
Name string `gorm:"size:128;not null" json:"name"`
Replicas int `json:"replicas"`
AvailableReplicas int `json:"available_replicas"`
ReadyReplicas int `json:"ready_replicas"`
Image string `gorm:"size:255" json:"image"`
Strategy string `gorm:"size:32" json:"strategy"`
Labels string `gorm:"type:text" json:"labels"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
// 关联
Cluster *K8sCluster `json:"cluster,omitempty"`
}
// TableName 表名
func (K8sDeployment) TableName() string {
return "k8s_deployments"
}
// K8sService K8s Service模型
type K8sService struct {
ID uint `gorm:"primarykey" json:"id"`
ClusterID uint `gorm:"not null" json:"cluster_id"`
Namespace string `gorm:"size:64;not null" json:"namespace"`
Name string `gorm:"size:128;not null" json:"name"`
Type string `gorm:"size:32" json:"type"` // ClusterIP, NodePort, LoadBalancer
ClusterIP string `gorm:"size:64" json:"cluster_ip"`
ExternalIP string `gorm:"size:255" json:"external_ip"`
Ports string `gorm:"type:text" json:"ports"` // JSON
Selector string `gorm:"type:text" json:"selector"`
Labels string `gorm:"type:text" json:"labels"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
// 关联
Cluster *K8sCluster `json:"cluster,omitempty"`
}
// TableName 表名
func (K8sService) TableName() string {
return "k8s_services"
}
// K8sIngress K8s Ingress模型
type K8sIngress struct {
ID uint `gorm:"primarykey" json:"id"`
ClusterID uint `gorm:"not null" json:"cluster_id"`
Namespace string `gorm:"size:64;not null" json:"namespace"`
Name string `gorm:"size:128;not null" json:"name"`
Hosts string `gorm:"type:text" json:"hosts"` // JSON
Backend string `gorm:"size:255" json:"backend"`
TLS bool `json:"tls"`
TLSHosts string `gorm:"type:text" json:"tls_hosts"`
Labels string `gorm:"type:text" json:"labels"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
// 关联
Cluster *K8sCluster `json:"cluster,omitempty"`
}
// TableName 表名
func (K8sIngress) TableName() string {
return "k8s_ingresses"
}
// CreateK8sClusterRequest 创建K8s集群请求
type CreateK8sClusterRequest struct {
Name string `json:"name" binding:"required"`
Code string `json:"code" binding:"required"`
Version string `json:"version"`
APIEndpoint string `json:"api_endpoint"`
KubeConfig string `json:"kube_config"`
Description string `json:"description"`
}
// K8sClusterStats K8s集群统计
type K8sClusterStats struct {
Total int64 `json:"total"`
Healthy int64 `json:"healthy"`
Warning int64 `json:"warning"`
Error int64 `json:"error"`
TotalNodes int64 `json:"total_nodes"`
TotalPods int64 `json:"total_pods"`
TotalServices int64 `json:"total_services"`
}
+98
View File
@@ -0,0 +1,98 @@
package models
import (
"time"
"gorm.io/gorm"
)
// NavigationGroup 导航分组模型
type NavigationGroup struct {
ID uint `gorm:"primarykey" json:"id"`
Name string `gorm:"size:64;not null" json:"name"`
Code string `gorm:"size:64;uniqueIndex;not null" json:"code"`
Description string `gorm:"size:255" json:"description"`
Icon string `gorm:"size:64" json:"icon"`
Sort int `gorm:"default:0" json:"sort"`
Enabled bool `gorm:"default:true" json:"enabled"`
LinkCount int `gorm:"default:0" json:"link_count"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// TableName 表名
func (NavigationGroup) TableName() string {
return "navigation_groups"
}
// NavigationLink 导航链接模型
type NavigationLink struct {
ID uint `gorm:"primarykey" json:"id"`
GroupID uint `json:"group_id"`
Name string `gorm:"size:128;not null" json:"name"`
URL string `gorm:"size:500;not null" json:"url"`
Description string `gorm:"size:255" json:"description"`
Icon string `gorm:"size:64" json:"icon"`
Tags string `gorm:"type:text" json:"tags"` // JSON
IsFavorite bool `gorm:"default:false" json:"is_favorite"`
Sort int `gorm:"default:0" json:"sort"`
Enabled bool `gorm:"default:true" json:"enabled"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
// 关联
Group *NavigationGroup `json:"group,omitempty"`
}
// TableName 表名
func (NavigationLink) TableName() string {
return "navigation_links"
}
// CreateNavigationGroupRequest 创建导航分组请求
type CreateNavigationGroupRequest struct {
Name string `json:"name" binding:"required"`
Code string `json:"code" binding:"required"`
Description string `json:"description"`
Icon string `json:"icon"`
Sort int `json:"sort"`
}
// UpdateNavigationGroupRequest 更新导航分组请求
type UpdateNavigationGroupRequest struct {
Name string `json:"name"`
Description string `json:"description"`
Icon string `json:"icon"`
Sort int `json:"sort"`
Enabled *bool `json:"enabled"`
}
// CreateNavigationLinkRequest 创建导航链接请求
type CreateNavigationLinkRequest struct {
GroupID uint `json:"group_id" binding:"required"`
Name string `json:"name" binding:"required"`
URL string `json:"url" binding:"required,url"`
Description string `json:"description"`
Icon string `json:"icon"`
Tags []string `json:"tags"`
Sort int `json:"sort"`
}
// UpdateNavigationLinkRequest 更新导航链接请求
type UpdateNavigationLinkRequest struct {
GroupID uint `json:"group_id"`
Name string `json:"name"`
URL string `json:"url" binding:"omitempty,url"`
Description string `json:"description"`
Icon string `json:"icon"`
Tags []string `json:"tags"`
Sort int `json:"sort"`
Enabled *bool `json:"enabled"`
}
// ToggleFavoriteRequest 切换收藏请求
type ToggleFavoriteRequest struct {
IsFavorite bool `json:"is_favorite"`
}
+189
View File
@@ -0,0 +1,189 @@
package models
import (
"time"
"gorm.io/gorm"
)
// Server 服务器模型
type Server struct {
ID uint `gorm:"primarykey" json:"id"`
Hostname string `gorm:"size:128;not null" json:"hostname"`
IP string `gorm:"size:64;not null" json:"ip"`
PublicIP string `gorm:"size:64" json:"public_ip"`
Port int `gorm:"default:22" json:"port"`
Username string `gorm:"size:64;default:root" json:"username"`
Password string `gorm:"size:255" json:"-"`
SSHKey string `gorm:"type:text" json:"-"`
AuthType string `gorm:"size:32;default:password" json:"auth_type"` // password, key
OS string `gorm:"size:64" json:"os"`
OSVersion string `gorm:"size:64" json:"os_version"`
Arch string `gorm:"size:32" json:"arch"`
CPU int `json:"cpu"`
Memory int64 `json:"memory"` // MB
Disk int64 `json:"disk"` // GB
Status string `gorm:"size:32;default:stopped" json:"status"` // running, stopped, maintenance, error
CPUUsage float64 `json:"cpu_usage"`
MemoryUsage float64 `json:"memory_usage"`
DiskUsage float64 `json:"disk_usage"`
Uptime int64 `json:"uptime"` // seconds
AgentVersion string `gorm:"size:32" json:"agent_version"`
AgentStatus string `gorm:"size:32;default:offline" json:"agent_status"` // online, offline
RegionID *uint `json:"region_id"`
CloudID *uint `json:"cloud_id"`
Tags string `gorm:"type:text" json:"tags"` // JSON
Description string `gorm:"size:500" json:"description"`
LastCheckAt *time.Time `json:"last_check_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
// 关联
Region *Region `json:"region,omitempty"`
Cloud *Cloud `json:"cloud,omitempty"`
}
// TableName 表名
func (Server) TableName() string {
return "servers"
}
// Cloud 云平台模型
type Cloud struct {
ID uint `gorm:"primarykey" json:"id"`
Name string `gorm:"size:64;not null" json:"name"`
Code string `gorm:"size:64;uniqueIndex;not null" json:"code"`
Type string `gorm:"size:32;default:public" json:"type"` // public, private, hybrid
Status string `gorm:"size:32;default:active" json:"status"`
Website string `gorm:"size:255" json:"website"`
Description string `gorm:"size:500" json:"description"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// TableName 表名
func (Cloud) TableName() string {
return "clouds"
}
// CloudAccount 云账号模型
type CloudAccount struct {
ID uint `gorm:"primarykey" json:"id"`
Name string `gorm:"size:128;not null" json:"name"`
Provider string `gorm:"size:64;not null" json:"provider"` // aws, aliyun, tencent, huawei, azure
AccessKey string `gorm:"size:255;not null" json:"-"`
SecretKey string `gorm:"size:255;not null" json:"-"`
Status string `gorm:"size:32;default:active" json:"status"` // active, inactive, error
RegionCount int `gorm:"default:0" json:"region_count"`
ServerCount int `gorm:"default:0" json:"server_count"`
LastSyncAt *time.Time `json:"last_sync_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// TableName 表名
func (CloudAccount) TableName() string {
return "cloud_accounts"
}
// Region 区域模型
type Region struct {
ID uint `gorm:"primarykey" json:"id"`
Name string `gorm:"size:64;not null" json:"name"`
Code string `gorm:"size:64;uniqueIndex;not null" json:"code"`
Provider string `gorm:"size:64;not null" json:"provider"`
Location string `gorm:"size:128" json:"location"`
Status string `gorm:"size:32;default:active" json:"status"`
Endpoint string `gorm:"size:255" json:"endpoint"`
VPCCount int `gorm:"default:0" json:"vpc_count"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// TableName 表名
func (Region) TableName() string {
return "regions"
}
// Domain 域名模型
type Domain struct {
ID uint `gorm:"primarykey" json:"id"`
Name string `gorm:"size:255;uniqueIndex;not null" json:"name"`
Registrar string `gorm:"size:128" json:"registrar"`
Status string `gorm:"size:32;default:active" json:"status"` // active, expired, pending
ExpireDate *time.Time `json:"expire_date"`
DNSProvider string `gorm:"size:128" json:"dns_provider"`
RecordCount int `gorm:"default:0" json:"record_count"`
SSLEnabled bool `gorm:"default:false" json:"ssl_enabled"`
SSLExpireDate *time.Time `json:"ssl_expire_date"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// TableName 表名
func (Domain) TableName() string {
return "domains"
}
// CreateServerRequest 创建服务器请求
type CreateServerRequest struct {
Hostname string `json:"hostname" binding:"required"`
IP string `json:"ip" binding:"required,ip"`
PublicIP string `json:"public_ip" binding:"omitempty,ip"`
Port int `json:"port" binding:"omitempty,min=1,max=65535"`
Username string `json:"username"`
Password string `json:"password"`
SSHKey string `json:"ssh_key"`
AuthType string `json:"auth_type" binding:"omitempty,oneof=password key"`
OS string `json:"os"`
CPU int `json:"cpu"`
Memory int64 `json:"memory"`
Disk int64 `json:"disk"`
RegionID *uint `json:"region_id"`
CloudID *uint `json:"cloud_id"`
Tags string `json:"tags"`
Description string `json:"description"`
}
// UpdateServerRequest 更新服务器请求
type UpdateServerRequest struct {
Hostname string `json:"hostname"`
PublicIP string `json:"public_ip" binding:"omitempty,ip"`
Port int `json:"port" binding:"omitempty,min=1,max=65535"`
Username string `json:"username"`
Password string `json:"password"`
SSHKey string `json:"ssh_key"`
AuthType string `json:"auth_type" binding:"omitempty,oneof=password key"`
OS string `json:"os"`
CPU int `json:"cpu"`
Memory int64 `json:"memory"`
Disk int64 `json:"disk"`
Status string `json:"status" binding:"omitempty,oneof=running stopped maintenance error"`
RegionID *uint `json:"region_id"`
CloudID *uint `json:"cloud_id"`
Tags string `json:"tags"`
Description string `json:"description"`
}
// ServerListResponse 服务器列表响应
type ServerListResponse struct {
List []Server `json:"list"`
Total int64 `json:"total"`
}
// ServerStats 服务器统计
type ServerStats struct {
Total int64 `json:"total"`
Running int64 `json:"running"`
Stopped int64 `json:"stopped"`
Maintenance int64 `json:"maintenance"`
Error int64 `json:"error"`
AvgCPU float64 `json:"avg_cpu"`
AvgMemory float64 `json:"avg_memory"`
AvgDisk float64 `json:"avg_disk"`
}
+111
View File
@@ -0,0 +1,111 @@
package models
import (
"time"
"gorm.io/gorm"
)
// User 用户模型
type User struct {
ID uint `gorm:"primarykey" json:"id"`
Username string `gorm:"size:64;uniqueIndex;not null" json:"username"`
Password string `gorm:"size:255;not null" json:"-"`
Email string `gorm:"size:128;uniqueIndex" json:"email"`
Phone string `gorm:"size:32" json:"phone"`
RealName string `gorm:"size:64" json:"real_name"`
Avatar string `gorm:"size:255" json:"avatar"`
Status int `gorm:"default:1" json:"status"` // 0:禁用 1:启用
IsAdmin bool `gorm:"default:false" json:"is_admin"`
LastLogin *time.Time `json:"last_login"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
// 关联
Roles []Role `gorm:"many2many:user_roles;" json:"roles,omitempty"`
}
// TableName 表名
func (User) TableName() string {
return "users"
}
// Role 角色模型
type Role struct {
ID uint `gorm:"primarykey" json:"id"`
Name string `gorm:"size:64;uniqueIndex;not null" json:"name"`
Code string `gorm:"size:64;uniqueIndex;not null" json:"code"`
Description string `gorm:"size:255" json:"description"`
Status int `gorm:"default:1" json:"status"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
// 关联
Permissions []Permission `gorm:"many2many:role_permissions;" json:"permissions,omitempty"`
}
// TableName 表名
func (Role) TableName() string {
return "roles"
}
// Permission 权限模型
type Permission struct {
ID uint `gorm:"primarykey" json:"id"`
Name string `gorm:"size:64;not null" json:"name"`
Code string `gorm:"size:128;uniqueIndex;not null" json:"code"`
Type string `gorm:"size:32" json:"type"` // menu, button, api
Path string `gorm:"size:255" json:"path"`
Method string `gorm:"size:32" json:"method"`
ParentID *uint `json:"parent_id"`
Sort int `gorm:"default:0" json:"sort"`
Status int `gorm:"default:1" json:"status"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// TableName 表名
func (Permission) TableName() string {
return "permissions"
}
// LoginRequest 登录请求
type LoginRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
// LoginResponse 登录响应
type LoginResponse struct {
Token string `json:"token"`
ExpiresIn int `json:"expires_in"`
User *User `json:"user"`
}
// CreateUserRequest 创建用户请求
type CreateUserRequest struct {
Username string `json:"username" binding:"required,min=3,max=64"`
Password string `json:"password" binding:"required,min=6,max=32"`
Email string `json:"email" binding:"required,email"`
Phone string `json:"phone"`
RealName string `json:"real_name"`
RoleIDs []uint `json:"role_ids"`
}
// UpdateUserRequest 更新用户请求
type UpdateUserRequest struct {
Email string `json:"email" binding:"omitempty,email"`
Phone string `json:"phone"`
RealName string `json:"real_name"`
Status *int `json:"status"`
RoleIDs []uint `json:"role_ids"`
}
// ChangePasswordRequest 修改密码请求
type ChangePasswordRequest struct {
OldPassword string `json:"old_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required,min=6,max=32"`
}