feat: 定时任务编辑入口全栈打通 + 修复ApprovalFlow编译错误 + 完成大文件拆分重构

- CronJobManager: 编辑按钮接通 onClick → openEdit(job),弹出编辑对话框预填数据,
  复用新建/编辑共用 Dialog(editingId 区分模式),标题/按钮文案动态切换,
  保存时根据 editingId 调用 createJob 或 updateJob — 消除占位按钮
- ApprovalFlow: 修复 API 方法名不匹配导致 tsc -b 编译失败
  (getInstances→getProcesses, getMyApprovals→getProcesses({status:'pending'}),
   approveInstance→approveProcess, rejectInstance→rejectProcess)
  + 添加 ApprovalFlowManager 导出别名兼容 App.tsx 导入
- 合并已完成的大文件拆分重构: automation.go→automation_cron/script/playbook,
  ldap.go→ldap_ou/ldap_users, server.go→cloud_accounts/domain/platform/region,
  user.go→user_admin; 前端 Dashboard/K8s/Server/Approval/Monitor 子组件拆分
  // Logan.Li
This commit is contained in:
attacker committed 2026-06-15 01:09:49 +08:00
1 parent 8be63ac168
commit a7e2351ad2
37 files changed
+7374 -4891

No files matched your search

-641
View File
@@ -3,8 +3,6 @@ package handlers
import (
"net/http"
"strconv"
"strings"
"time"
"ezops/internal/models"
@@ -370,642 +368,3 @@ func (h *ServerHandler) GetServerStats(c *gin.Context) {
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)),
},
})
}
// createCloudAccountRequest 创建云账号请求(独立 request struct,避免 CloudAccount.AccessKey/SecretKey 的 json:"-" 导致无法反序列化)
type createCloudAccountRequest struct {
Name string `json:"name"`
Provider string `json:"provider"`
AccessKey string `json:"access_key"`
SecretKey string `json:"secret_key"`
MonthlyCost float64 `json:"monthly_cost"`
Status string `json:"status"`
}
// CreateCloudAccount 创建云账号
// @Summary 创建云账号
// @Description 创建新云账号
// @Tags 云账号
// @Accept json
// @Produce json
// @Security Bearer
// @Param request body createCloudAccountRequest true "创建请求"
// @Success 200 {object} models.Response{data=models.CloudAccount}
// @Router /cloud-accounts [post]
func (h *CloudAccountHandler) CreateCloudAccount(c *gin.Context) {
var req createCloudAccountRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, models.Response{
Code: 400,
Message: "请求参数错误: " + err.Error(),
})
return
}
if strings.TrimSpace(req.Name) == "" || strings.TrimSpace(req.Provider) == "" {
c.JSON(http.StatusBadRequest, models.Response{
Code: 400,
Message: "账号名称和云平台不能为空",
})
return
}
status := strings.TrimSpace(req.Status)
if status == "" {
status = "active"
}
account := models.CloudAccount{
Name: strings.TrimSpace(req.Name),
Provider: strings.TrimSpace(req.Provider),
AccessKey: req.AccessKey,
SecretKey: req.SecretKey,
MonthlyCost: req.MonthlyCost,
Status: status,
}
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,
})
}
// UpdateCloudAccount 更新云账号
// @Summary 更新云账号
// @Description 更新指定云账号信息
// @Tags 云账号
// @Accept json
// @Produce json
// @Security Bearer
// @Param id path int true "云账号ID"
// @Param request body createCloudAccountRequest true "更新请求"
// @Success 200 {object} models.Response{data=models.CloudAccount}
// @Router /cloud-accounts/{id} [put]
func (h *CloudAccountHandler) UpdateCloudAccount(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var req createCloudAccountRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, models.Response{
Code: 400,
Message: "请求参数错误: " + err.Error(),
})
return
}
var account models.CloudAccount
if err := h.db.First(&account, id).Error; err != nil {
c.JSON(http.StatusNotFound, models.Response{
Code: 404,
Message: "云账号不存在",
})
return
}
// 只在提供非空值时更新凭证字段
if strings.TrimSpace(req.Name) != "" {
account.Name = strings.TrimSpace(req.Name)
}
if strings.TrimSpace(req.Provider) != "" {
account.Provider = strings.TrimSpace(req.Provider)
}
if strings.TrimSpace(req.AccessKey) != "" {
account.AccessKey = req.AccessKey
}
if strings.TrimSpace(req.SecretKey) != "" {
account.SecretKey = req.SecretKey
}
if strings.TrimSpace(req.Status) != "" {
account.Status = strings.TrimSpace(req.Status)
}
account.MonthlyCost = req.MonthlyCost
if err := h.db.Save(&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 req updateDomainRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, models.Response{
Code: 400,
Message: "请求参数错误: " + err.Error(),
})
return
}
if strings.TrimSpace(req.Name) == "" {
c.JSON(http.StatusBadRequest, models.Response{
Code: 400,
Message: "域名不能为空",
})
return
}
status := req.Status
if status == "" {
status = "active"
}
domain := models.Domain{
Name: strings.TrimSpace(req.Name),
Registrar: req.Registrar,
Status: status,
ExpireDate: parseDateStr(req.ExpireDate),
DNSProvider: req.DNSProvider,
RecordCount: req.RecordCount,
SSLEnabled: req.SSLEnabled,
SSLExpireDate: parseDateStr(req.SSLExpireDate),
}
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,
})
}
// updateDomainRequest 域名创建/更新请求(日期字段用字符串接收,避免 RFC3339 解析失败)
type updateDomainRequest struct {
Name string `json:"name"`
Registrar string `json:"registrar"`
Status string `json:"status"`
ExpireDate string `json:"expire_date"`
DNSProvider string `json:"dns_provider"`
RecordCount int `json:"record_count"`
SSLEnabled bool `json:"ssl_enabled"`
SSLExpireDate string `json:"ssl_expire_date"`
}
// parseDateStr 将 YYYY-MM-DD 格式字符串解析为 *time.Time,空串返回 nil
func parseDateStr(s string) *time.Time {
s = strings.TrimSpace(s)
if s == "" {
return nil
}
t, err := time.Parse("2006-01-02", s)
if err != nil {
return nil
}
return &t
}
// 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: "删除成功",
})
}
// UpdateDomain 更新域名
// @Summary 更新域名
// @Description 更新指定域名信息
// @Tags 域名
// @Accept json
// @Produce json
// @Security Bearer
// @Param id path int true "域名ID"
// @Param request body updateDomainRequest true "更新请求"
// @Success 200 {object} models.Response{data=models.Domain}
// @Router /domains/{id} [put]
func (h *DomainHandler) UpdateDomain(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var req updateDomainRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, models.Response{
Code: 400,
Message: "请求参数错误: " + err.Error(),
})
return
}
var domain models.Domain
if err := h.db.First(&domain, id).Error; err != nil {
c.JSON(http.StatusNotFound, models.Response{
Code: 404,
Message: "域名不存在",
})
return
}
if strings.TrimSpace(req.Name) != "" {
domain.Name = strings.TrimSpace(req.Name)
}
domain.Registrar = req.Registrar
domain.Status = req.Status
if domain.Status == "" {
domain.Status = "active"
}
domain.ExpireDate = parseDateStr(req.ExpireDate)
domain.DNSProvider = req.DNSProvider
domain.RecordCount = req.RecordCount
domain.SSLEnabled = req.SSLEnabled
domain.SSLExpireDate = parseDateStr(req.SSLExpireDate)
if err := h.db.Save(&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,
})
}
// ==================== 云平台(Platform / Cloud)====================
// PlatformHandler 云平台处理器
type PlatformHandler struct {
db *gorm.DB
}
func NewPlatformHandler(db *gorm.DB) *PlatformHandler {
return &PlatformHandler{db: db}
}
// platformRequest 云平台创建/更新请求
type platformRequest struct {
Name string `json:"name"`
Code string `json:"code"`
Type string `json:"type"`
Status string `json:"status"`
Website string `json:"website"`
Description string `json:"description"`
}
// ListPlatforms 获取云平台列表
func (h *PlatformHandler) ListPlatforms(c *gin.Context) {
var platforms []models.Cloud
h.db.Find(&platforms)
c.JSON(http.StatusOK, models.Response{
Code: 200,
Message: "success",
Data: models.ListResponse{
List: platforms,
Total: int64(len(platforms)),
},
})
}
// CreatePlatform 创建云平台
func (h *PlatformHandler) CreatePlatform(c *gin.Context) {
var req platformRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "请求参数错误: " + err.Error()})
return
}
if strings.TrimSpace(req.Name) == "" || strings.TrimSpace(req.Code) == "" {
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "平台名称和编码不能为空"})
return
}
cloudType := strings.TrimSpace(req.Type)
if cloudType == "" {
cloudType = "public"
}
status := strings.TrimSpace(req.Status)
if status == "" {
status = "active"
}
platform := models.Cloud{
Name: strings.TrimSpace(req.Name),
Code: strings.TrimSpace(req.Code),
Type: cloudType,
Status: status,
Website: req.Website,
Description: req.Description,
}
if err := h.db.Create(&platform).Error; err != nil {
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "创建失败,编码可能已存在"})
return
}
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "创建成功", Data: platform})
}
// UpdatePlatform 更新云平台
func (h *PlatformHandler) UpdatePlatform(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var req platformRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "请求参数错误: " + err.Error()})
return
}
var platform models.Cloud
if err := h.db.First(&platform, id).Error; err != nil {
c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "云平台不存在"})
return
}
if strings.TrimSpace(req.Name) != "" {
platform.Name = strings.TrimSpace(req.Name)
}
if strings.TrimSpace(req.Code) != "" {
platform.Code = strings.TrimSpace(req.Code)
}
if strings.TrimSpace(req.Type) != "" {
platform.Type = strings.TrimSpace(req.Type)
}
if strings.TrimSpace(req.Status) != "" {
platform.Status = strings.TrimSpace(req.Status)
}
platform.Website = req.Website
platform.Description = req.Description
if err := h.db.Save(&platform).Error; err != nil {
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "更新失败"})
return
}
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "更新成功", Data: platform})
}
// DeletePlatform 删除云平台
func (h *PlatformHandler) DeletePlatform(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
if err := h.db.Delete(&models.Cloud{}, id).Error; err != nil {
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "删除失败"})
return
}
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "删除成功"})
}
// ==================== 区域管理(Region)====================
// RegionHandler 区域处理器
type RegionHandler struct {
db *gorm.DB
}
func NewRegionHandler(db *gorm.DB) *RegionHandler {
return &RegionHandler{db: db}
}
// regionRequest 区域创建/更新请求
type regionRequest struct {
Name string `json:"name"`
Code string `json:"code"`
Provider string `json:"provider"`
Location string `json:"location"`
Status string `json:"status"`
Endpoint string `json:"endpoint"`
}
// ListRegions 获取区域列表
func (h *RegionHandler) ListRegions(c *gin.Context) {
var regions []models.Region
h.db.Find(&regions)
c.JSON(http.StatusOK, models.Response{
Code: 200,
Message: "success",
Data: models.ListResponse{
List: regions,
Total: int64(len(regions)),
},
})
}
// CreateRegion 创建区域
func (h *RegionHandler) CreateRegion(c *gin.Context) {
var req regionRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "请求参数错误: " + err.Error()})
return
}
if strings.TrimSpace(req.Name) == "" || strings.TrimSpace(req.Code) == "" {
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "区域名称和编码不能为空"})
return
}
status := strings.TrimSpace(req.Status)
if status == "" {
status = "active"
}
region := models.Region{
Name: strings.TrimSpace(req.Name),
Code: strings.TrimSpace(req.Code),
Provider: strings.TrimSpace(req.Provider),
Location: req.Location,
Status: status,
Endpoint: req.Endpoint,
}
if err := h.db.Create(&region).Error; err != nil {
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "创建失败,编码可能已存在"})
return
}
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "创建成功", Data: region})
}
// UpdateRegion 更新区域
func (h *RegionHandler) UpdateRegion(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var req regionRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "请求参数错误: " + err.Error()})
return
}
var region models.Region
if err := h.db.First(&region, id).Error; err != nil {
c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "区域不存在"})
return
}
if strings.TrimSpace(req.Name) != "" {
region.Name = strings.TrimSpace(req.Name)
}
if strings.TrimSpace(req.Code) != "" {
region.Code = strings.TrimSpace(req.Code)
}
if strings.TrimSpace(req.Provider) != "" {
region.Provider = strings.TrimSpace(req.Provider)
}
region.Location = req.Location
if strings.TrimSpace(req.Status) != "" {
region.Status = strings.TrimSpace(req.Status)
}
region.Endpoint = req.Endpoint
if err := h.db.Save(&region).Error; err != nil {
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "更新失败"})
return
}
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "更新成功", Data: region})
}
// DeleteRegion 删除区域
func (h *RegionHandler) DeleteRegion(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
if err := h.db.Delete(&models.Region{}, id).Error; err != nil {
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "删除失败"})
return
}
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "删除成功"})
}