package handlers import ( "net/http" "strconv" "github.com/gin-gonic/gin" "gorm.io/gorm" "everypublish/server/internal/api/response" "everypublish/server/internal/models" "everypublish/server/internal/ws" ) // AdminHandler 平台管理处理器(AdminRequired 守卫) type AdminHandler struct { DB *gorm.DB Hub *ws.Hub } type tenantRow struct { models.Workspace OwnerEmail string `json:"ownerEmail"` MemberCount int64 `json:"memberCount"` DeviceCount int64 `json:"deviceCount"` } // Tenants 租户列表(含 owner 邮箱与成员/设备数) func (h *AdminHandler) Tenants(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) size, _ := strconv.Atoi(c.DefaultQuery("size", "20")) if page < 1 { page = 1 } if size < 1 || size > 100 { size = 20 } q := h.DB.Model(&models.Workspace{}) if s := c.Query("status"); s != "" { q = q.Where("status = ?", s) } var total int64 q.Count(&total) var wsList []models.Workspace q.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&wsList) rows := make([]tenantRow, 0, len(wsList)) for _, ws := range wsList { row := tenantRow{Workspace: ws} var owner models.User if err := h.DB.First(&owner, ws.OwnerID).Error; err == nil { row.OwnerEmail = owner.Email } h.DB.Model(&models.Member{}).Where("workspace_id = ?", ws.ID).Count(&row.MemberCount) h.DB.Model(&models.AgentDevice{}).Where("workspace_id = ?", ws.ID).Count(&row.DeviceCount) rows = append(rows, row) } response.OK(c, gin.H{"list": rows, "total": total, "page": page, "size": size}) } // TenantStatus 启用/停用租户 func (h *AdminHandler) TenantStatus(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { response.Fail(c, http.StatusBadRequest, 1001, "参数不合法") return } var req struct { Status string `json:"status" binding:"required"` } if err = c.ShouldBindJSON(&req); err != nil { response.Fail(c, http.StatusBadRequest, 1001, "参数不合法") return } if req.Status != "active" && req.Status != "suspended" { response.Fail(c, http.StatusBadRequest, 1001, "状态不合法") return } var ws models.Workspace if err = h.DB.First(&ws, id).Error; err != nil { response.Fail(c, http.StatusNotFound, 1004, "租户不存在") return } if err = h.DB.Model(&ws).Update("status", req.Status).Error; err != nil { response.Fail(c, http.StatusInternalServerError, 5000, "更新失败") return } response.Audit(c, "admin.tenant.status", "workspace:"+itoa(id), gin.H{"status": req.Status}) response.OK(c, nil) } type agentRow struct { models.AgentDevice WorkspaceName string `json:"workspaceName"` } // Agents 全局 Agent 总览 func (h *AdminHandler) Agents(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) size, _ := strconv.Atoi(c.DefaultQuery("size", "20")) if page < 1 { page = 1 } if size < 1 || size > 100 { size = 20 } var total int64 h.DB.Model(&models.AgentDevice{}).Count(&total) var devices []models.AgentDevice h.DB.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&devices) rows := make([]agentRow, 0, len(devices)) for _, d := range devices { row := agentRow{AgentDevice: d} var ws models.Workspace if err := h.DB.First(&ws, d.WorkspaceID).Error; err == nil { row.WorkspaceName = ws.Name } rows = append(rows, row) } response.OK(c, gin.H{"list": rows, "total": total, "page": page, "size": size}) } // AgentRevoke 全局强制吊销 func (h *AdminHandler) AgentRevoke(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { response.Fail(c, http.StatusBadRequest, 1001, "参数不合法") return } var device models.AgentDevice if err = h.DB.First(&device, id).Error; err != nil { response.Fail(c, http.StatusNotFound, 1004, "设备不存在") return } if err = h.DB.Model(&device).Updates(map[string]interface{}{"revoked": true, "status": "offline"}).Error; err != nil { response.Fail(c, http.StatusInternalServerError, 5000, "吊销失败") return } if h.Hub != nil { h.Hub.KickAgent(device.ID) } response.Audit(c, "admin.agent.revoke", "device:"+itoa(id), nil) response.OK(c, nil) }