diff --git a/backend/internal/api/handlers/approval.go b/backend/internal/api/handlers/approval.go index f020d01..147bd11 100644 --- a/backend/internal/api/handlers/approval.go +++ b/backend/internal/api/handlers/approval.go @@ -206,230 +206,4 @@ func (h *ApprovalHandler) DeleteTemplate(c *gin.Context) { }) } -// 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, - }) -} +// Process 相关 handler 见 approval_process.go diff --git a/backend/internal/api/handlers/approval_process.go b/backend/internal/api/handlers/approval_process.go new file mode 100644 index 0000000..d8df630 --- /dev/null +++ b/backend/internal/api/handlers/approval_process.go @@ -0,0 +1,187 @@ +package handlers + +import ( + "net/http" + "strconv" + + "ezops/internal/models" + + "github.com/gin-gonic/gin" +) + +// ListProcesses 获取审批流程列表 // Logan.Li +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 获取审批流程详情 // Logan.Li +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 创建审批流程 // Logan.Li +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 审批通过 // Logan.Li +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" + + 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 审批拒绝 // Logan.Li +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 + + 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, + }) +} diff --git a/backend/internal/api/handlers/audit.go b/backend/internal/api/handlers/audit.go index f2cdf9b..8a32b88 100644 --- a/backend/internal/api/handlers/audit.go +++ b/backend/internal/api/handlers/audit.go @@ -144,7 +144,7 @@ func (h *AuditHandler) GetAuditModules(c *gin.Context) { {"value": "domain", "label": "域名管理"}, } - c.JSON(http.StatusOK, gin.H{"data": modules}) + c.JSON(http.StatusOK, models.Response{Code: 200, Data: modules}) // Logan.Li } // GetAuditActions 获取审计操作类型列表 @@ -170,7 +170,7 @@ func (h *AuditHandler) GetAuditActions(c *gin.Context) { {"value": "export", "label": "导出"}, } - c.JSON(http.StatusOK, gin.H{"data": actions}) + c.JSON(http.StatusOK, models.Response{Code: 200, Data: actions}) // Logan.Li } // ExportAuditLogs 导出审计日志 @@ -222,11 +222,10 @@ func (h *AuditHandler) ExportAuditLogs(c *gin.Context) { // TODO: 实现CSV/Excel导出功能 // 这里返回JSON格式的数据,实际项目中应该生成CSV或Excel文件 - c.JSON(http.StatusOK, gin.H{ - "message": "导出成功", - "count": len(logs), - "data": logs, - }) + c.JSON(http.StatusOK, models.Response{Code: 200, Message: "导出成功", Data: gin.H{ + "count": len(logs), + "logs": logs, + }}) // Logan.Li } // GetAuditStatistics 获取审计统计 @@ -291,13 +290,13 @@ func (h *AuditHandler) GetAuditStatistics(c *gin.Context) { 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, - }) + c.JSON(http.StatusOK, models.Response{Code: 200, Data: gin.H{ + "total_count": totalCount, + "today_count": todayCount, + "module_stats": moduleStats, + "action_stats": actionStats, + "daily_stats": dailyStats, + }}) // Logan.Li } // ClearAuditLogs 清理审计日志 @@ -341,10 +340,9 @@ func (h *AuditHandler) ClearAuditLogs(c *gin.Context) { "deleted": result.RowsAffected, }) - c.JSON(http.StatusOK, gin.H{ - "message": "清理成功", - "deleted": result.RowsAffected, - }) + c.JSON(http.StatusOK, models.Response{Code: 200, Message: "清理成功", Data: gin.H{ + "deleted": result.RowsAffected, + }}) // Logan.Li } // RecordAuditLog 记录审计日志 diff --git a/backend/internal/api/handlers/navigation.go b/backend/internal/api/handlers/navigation.go index 304f30b..d99c465 100644 --- a/backend/internal/api/handlers/navigation.go +++ b/backend/internal/api/handlers/navigation.go @@ -1,7 +1,6 @@ package handlers import ( - "encoding/json" "net/http" "strconv" @@ -183,246 +182,5 @@ func (h *NavigationHandler) DeleteGroup(c *gin.Context) { }) } -// 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, - }) -} +// 导航链接(Link)相关的 CRUD 及收藏功能处理器方法已拆分到 navigation_link.go +// 涉及方法: ListLinks, CreateLink, UpdateLink, DeleteLink, ToggleFavorite diff --git a/backend/internal/api/handlers/navigation_link.go b/backend/internal/api/handlers/navigation_link.go new file mode 100644 index 0000000..ac136fa --- /dev/null +++ b/backend/internal/api/handlers/navigation_link.go @@ -0,0 +1,259 @@ +// Logan.Li +// 导航链接相关处理器方法 +// 从 navigation.go 拆分而来,包含导航链接的 CRUD 及收藏功能 + +package handlers + +import ( + "encoding/json" + "net/http" + "strconv" + + "ezops/internal/models" + + "github.com/gin-gonic/gin" +) + +// 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, + }) +} diff --git a/frontend/src/components/agent/AgentMonitor.tsx b/frontend/src/components/agent/AgentMonitor.tsx index 6345550..bb3919b 100644 --- a/frontend/src/components/agent/AgentMonitor.tsx +++ b/frontend/src/components/agent/AgentMonitor.tsx @@ -553,6 +553,7 @@ function HostDetailDialog({ 离线 )} + 查看主机详情和指标趋势
diff --git a/frontend/src/components/audit/SQLAuditManager.tsx b/frontend/src/components/audit/SQLAuditManager.tsx index 20741c2..2876857 100644 --- a/frontend/src/components/audit/SQLAuditManager.tsx +++ b/frontend/src/components/audit/SQLAuditManager.tsx @@ -535,6 +535,7 @@ const SQLAuditManager: React.FC = () => { SQL审计详情 + 查看SQL审计记录详情 {selectedRecord && (
diff --git a/frontend/src/components/cloud/PlatformManager.tsx b/frontend/src/components/cloud/PlatformManager.tsx index d85b826..08b9c09 100644 --- a/frontend/src/components/cloud/PlatformManager.tsx +++ b/frontend/src/components/cloud/PlatformManager.tsx @@ -6,6 +6,7 @@ import { Badge } from '@/components/ui/badge'; import { Dialog, DialogContent, + DialogDescription, DialogFooter, DialogHeader, DialogTitle, @@ -221,6 +222,7 @@ export function PlatformManager() { {editingItem ? '编辑云平台' : '添加云平台'} + 描述
@@ -305,6 +307,7 @@ export function PlatformManager() { 确认删除 + 描述

确定删除此云平台?此操作不可撤销。

diff --git a/frontend/src/components/cloud/RegionManager.tsx b/frontend/src/components/cloud/RegionManager.tsx index fa599a7..0f589d1 100644 --- a/frontend/src/components/cloud/RegionManager.tsx +++ b/frontend/src/components/cloud/RegionManager.tsx @@ -15,6 +15,7 @@ import { import { Dialog, DialogContent, + DialogDescription, DialogFooter, DialogHeader, DialogTitle, @@ -318,6 +319,7 @@ export function RegionManager() { {editingItem ? '编辑区域' : '添加区域'} + 描述
diff --git a/frontend/src/components/deploy/AppRelease.tsx b/frontend/src/components/deploy/AppRelease.tsx index e5c5573..2975756 100644 --- a/frontend/src/components/deploy/AppRelease.tsx +++ b/frontend/src/components/deploy/AppRelease.tsx @@ -53,7 +53,6 @@ import { Loader2, ArrowLeft, ArrowRight, - Terminal, } from 'lucide-react'; import { releaseApi } from '@/services/api'; import { toast } from 'sonner'; @@ -416,7 +415,20 @@ export function AppReleaseManager() { 详情 {release.status === 'failed' && ( - @@ -521,14 +533,26 @@ export function AppReleaseManager() {
- {selectedRelease?.status === 'failed' && ( - )} diff --git a/frontend/src/components/monitor/alert-center/AlertCenterDetail.tsx b/frontend/src/components/monitor/alert-center/AlertCenterDetail.tsx index 6141245..0026c0b 100644 --- a/frontend/src/components/monitor/alert-center/AlertCenterDetail.tsx +++ b/frontend/src/components/monitor/alert-center/AlertCenterDetail.tsx @@ -26,6 +26,7 @@ export function AlertDetailDialog({ {alert?.title} {alert && getSeverityBadge(alert.severity)} + 查看告警详情 {alert && (
diff --git a/frontend/src/components/monitor/trace/TraceManager.tsx b/frontend/src/components/monitor/trace/TraceManager.tsx index 43b9d8c..c2e2ebe 100644 --- a/frontend/src/components/monitor/trace/TraceManager.tsx +++ b/frontend/src/components/monitor/trace/TraceManager.tsx @@ -502,6 +502,7 @@ const TraceManager: React.FC = () => { Trace详情 {selectedTrace?.traceId} + 描述 {selectedTrace && (
@@ -550,6 +551,7 @@ const TraceManager: React.FC = () => { Span详情 + 描述 {selectedSpan && (
diff --git a/frontend/src/components/server/ServerManager.tsx b/frontend/src/components/server/ServerManager.tsx index 411b2cc..7148ec9 100644 --- a/frontend/src/components/server/ServerManager.tsx +++ b/frontend/src/components/server/ServerManager.tsx @@ -33,19 +33,17 @@ import { Plus, MoreHorizontal, Terminal, - Power, - RotateCw, Trash2, Edit, Download, Upload, - Server, - Activity, + Server as ServerIcon, Cloud, FileSpreadsheet, RefreshCw, } from 'lucide-react'; import { serverApi } from '@/services/api'; +import { ServerStatsCards } from './ServerStatsCards'; import { useConfirm } from '@/components/ui/confirm-dialog'; import { toast } from 'sonner'; import type { Server as ServerType } from '@/types'; @@ -247,6 +245,32 @@ export function ServerManager() { } }; + // 导出 CSV — 将当前服务器列表导出为 CSV 文件 // Logan.Li + const handleExport = () => { + if (filteredServers.length === 0) { + toast.error('没有可导出的数据'); + return; + } + const headers = ['主机名', 'IP地址', '公网IP', '端口', '操作系统', 'CPU(核)', '内存(GB)', '磁盘(GB)', '状态', '标签']; + const rows = filteredServers.map((s) => { + const memGB = s.memory ? Math.round(s.memory / 1024) : 0; + const tags = getTags(s).join(';'); + return [s.hostname || '', s.ip || '', s.public_ip || '', s.port || 22, s.os || '', + s.cpu || 0, memGB, s.disk || 0, s.status || '', tags]; + }); + const csv = [headers, ...rows] + .map((row) => row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(',')) + .join('\n'); + const blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `servers_${new Date().toISOString().slice(0, 10)}.csv`; + a.click(); + URL.revokeObjectURL(url); + toast.success(`已导出 ${filteredServers.length} 台服务器`); + }; + const getStatusBadge = (status: ServerType['status']) => { switch (status) { case 'running': @@ -299,7 +323,11 @@ export function ServerManager() { 刷新 - @@ -342,66 +370,7 @@ export function ServerManager() {
{/* Stats Cards */} -
- - -
-
-

服务器总数

-

{servers.length}

-
-
- -
-
-
-
- - -
-
-

运行中

-

- {servers.filter((s) => s.status === 'running').length} -

-
-
- -
-
-
-
- - -
-
-

维护中

-

- {servers.filter((s) => s.status === 'maintenance').length} -

-
-
- -
-
-
-
- - -
-
-

已停止

-

- {servers.filter((s) => s.status === 'stopped').length} -

-
-
- -
-
-
-
-
+ {/* Filters & Table */} @@ -434,14 +403,6 @@ export function ServerManager() { {selectedServers.length > 0 && (
已选择 {selectedServers.length} 项 - -