package controller import ( "encoding/json" "fmt" "net/http" "strconv" "time" "ezops/model" "github.com/gin-gonic/gin" ) // ==================== 环境管理 ==================== // ListEnvironments 环境列表 func (h *PipelineHandler) ListEnvironments(c *gin.Context) { var items []model.DeployEnvironment h.db.Order("sort_order ASC, id ASC").Find(&items) c.JSON(http.StatusOK, model.Response{Code: 200, Data: model.ListResponse{List: items, Total: int64(len(items))}}) } // CreateEnvironment 创建环境 func (h *PipelineHandler) CreateEnvironment(c *gin.Context) { var req struct { Name string `json:"name" binding:"required"` DisplayName string `json:"display_name" binding:"required"` Type string `json:"type" binding:"required,oneof=production staging testing development"` Color string `json:"color"` SortOrder int `json:"sort_order"` Description string `json:"description"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: err.Error()}) return } if req.Color == "" { req.Color = typeDefaultColor(req.Type) } item := model.DeployEnvironment{ Name: req.Name, DisplayName: req.DisplayName, Type: model.EnvironmentType(req.Type), Color: req.Color, SortOrder: req.SortOrder, Description: req.Description, } if err := h.db.Create(&item).Error; err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "创建失败: " + err.Error()}) return } c.JSON(http.StatusOK, model.Response{Code: 200, Data: item}) } // UpdateEnvironment 更新环境 func (h *PipelineHandler) UpdateEnvironment(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) var item model.DeployEnvironment if err := h.db.First(&item, id).Error; err != nil { c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "不存在"}) return } var req struct { Name *string `json:"name"` DisplayName *string `json:"display_name"` Type *string `json:"type"` Color *string `json:"color"` SortOrder *int `json:"sort_order"` Description *string `json:"description"` Enabled *bool `json:"enabled"` } c.ShouldBindJSON(&req) updates := map[string]interface{}{} if req.Name != nil { updates["name"] = *req.Name } if req.DisplayName != nil { updates["display_name"] = *req.DisplayName } if req.Type != nil { updates["type"] = *req.Type } if req.Color != nil { updates["color"] = *req.Color } if req.SortOrder != nil { updates["sort_order"] = *req.SortOrder } if req.Description != nil { updates["description"] = *req.Description } if req.Enabled != nil { updates["enabled"] = *req.Enabled } h.db.Model(&item).Updates(updates) h.db.First(&item, id) c.JSON(http.StatusOK, model.Response{Code: 200, Data: item}) } // DeleteEnvironment 删除环境 func (h *PipelineHandler) DeleteEnvironment(c *gin.Context) { h.db.Delete(&model.DeployEnvironment{}, c.Param("id")) c.JSON(http.StatusOK, model.Response{Code: 200, Message: "已删除"}) } func typeDefaultColor(t string) string { switch t { case "production": return "#EF4444" case "staging": return "#F59E0B" case "testing": return "#10B981" default: return "#3B82F6" // development } } // ==================== 流水线成员管理 ==================== // ListPipelineMembers 成员列表 func (h *PipelineHandler) ListPipelineMembers(c *gin.Context) { pipelineID := getPipelineID(c) var members []model.DeployPipelineMember h.db.Where("pipeline_id = ?", pipelineID).Order("created_at ASC").Find(&members) c.JSON(http.StatusOK, model.Response{Code: 200, Data: members}) } // AddPipelineMember 添加成员 func (h *PipelineHandler) AddPipelineMember(c *gin.Context) { pipelineID := getPipelineID(c) var req struct { Username string `json:"username" binding:"required"` Role string `json:"role" binding:"required,oneof=owner editor viewer"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: err.Error()}) return } var pipeline model.Pipeline if err := h.db.First(&pipeline, pipelineID).Error; err != nil { c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "流水线不存在"}) return } member := model.DeployPipelineMember{ PipelineID: uint(pipelineID), Username: &req.Username, Role: model.MemberRole(req.Role), } if err := h.db.Create(&member).Error; err != nil { c.JSON(http.StatusConflict, model.Response{Code: 409, Message: "该用户已是成员"}) return } c.JSON(http.StatusOK, model.Response{Code: 200, Data: member}) } // RemovePipelineMember 移除成员 func (h *PipelineHandler) RemovePipelineMember(c *gin.Context) { pipelineID := getPipelineID(c) memberID, _ := strconv.Atoi(c.Param("memberId")) result := h.db.Where("id = ? AND pipeline_id = ?", memberID, pipelineID).Delete(&model.DeployPipelineMember{}) if result.RowsAffected == 0 { c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "成员不存在"}) return } c.JSON(http.StatusOK, model.Response{Code: 200, Message: "已移除"}) } // ==================== 流水线运行控制 ==================== // StopPipelineRun 停止运行中的流水线 func (h *PipelineHandler) StopPipelineRun(c *gin.Context) { pipelineID := getPipelineID(c) runID, _ := strconv.Atoi(c.Param("runId")) var run model.PipelineRun if err := h.db.Where("id = ? AND pipeline_id = ?", runID, pipelineID).First(&run).Error; err != nil { c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "运行记录不存在"}) return } if run.Status != "running" && run.Status != "pending" { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "仅运行中或等待中的任务可停止, 当前状态: " + run.Status}) return } now := time.Now() updates := map[string]interface{}{ "status": "cancelled", "finished_at": &now, } if run.StartedAt != nil { dur := int(now.Sub(*run.StartedAt).Seconds()) updates["duration"] = dur } h.db.Model(&run).Updates(updates) // 追加日志 logEntry := map[string]string{ "time": now.Format("15:04:05"), "level": "warn", "message": ">> 任务已被手动停止", "stage": "--", } existingLogs := run.Logs if existingLogs == "" { existingLogs = "[]" } var logEntries []map[string]string json.Unmarshal([]byte(existingLogs), &logEntries) logEntries = append(logEntries, logEntry) newLogs, _ := json.Marshal(logEntries) h.db.Model(&run).Update("logs", string(newLogs)) h.db.First(&run, runID) c.JSON(http.StatusOK, model.Response{Code: 200, Data: run, Message: "已停止"}) } // GetRunLog 获取运行日志 func (h *PipelineHandler) GetRunLog(c *gin.Context) { pipelineID := getPipelineID(c) runID, _ := strconv.Atoi(c.Param("runId")) var run model.PipelineRun if err := h.db.Where("id = ? AND pipeline_id = ?", runID, pipelineID).First(&run).Error; err != nil { c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "运行记录不存在"}) return } var logs []map[string]string if run.Logs != "" { json.Unmarshal([]byte(run.Logs), &logs) } c.JSON(http.StatusOK, model.Response{Code: 200, Data: gin.H{ "run": run, "logs": logs, }}) } // BatchDeleteRuns 批量删除运行记录 func (h *PipelineHandler) BatchDeleteRuns(c *gin.Context) { pipelineID := getPipelineID(c) var req struct { IDs []uint `json:"ids" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil || len(req.IDs) == 0 { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "请选择要删除的记录"}) return } result := h.db.Where("pipeline_id = ? AND id IN ?", pipelineID, req.IDs).Delete(&model.PipelineRun{}) c.JSON(http.StatusOK, model.Response{Code: 200, Message: fmt.Sprintf("已删除 %d 条记录", result.RowsAffected)}) }