Files
ocdp-go/backend/internal/adapter/input/http/rest/workspace_handler.go
Ivan087 29d0310f03 feat(frontend): add Helm chart browser, monitoring, chart-references and values templates pages
Add new frontend pages for the multi-tenant OCDP platform:

- Charts page (/charts): Browse Harbor OCI registries to list Helm chart repositories
  and versions, with deploy modal to launch charts on selected clusters
- Monitoring page (/monitoring): Display cluster metrics (CPU/Memory/GPU usage)
  and per-node details with resource utilization bars
- Chart References page (/chart-references): CRUD for chart metadata references
- Values Templates page (/templates): CRUD for Helm values templates with version
  history and rollback support
- Sidebar: Add Charts navigation, update Storage and Templates links
- api.ts: Add all API client functions (clusterApi, registryApi, instanceApi,
  monitoringApi, storageApi, chartReferenceApi, valuesTemplateApi,
  workspaceApi, userApi) with full TypeScript types

Note: deploy flow and values template rollback not yet end-to-end tested.
2026-04-15 16:59:31 +08:00

306 lines
8.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package rest
import (
"encoding/json"
"net/http"
"github.com/gorilla/mux"
"github.com/ocdp/cluster-service/internal/adapter/input/http/dto"
"github.com/ocdp/cluster-service/internal/domain/entity"
"github.com/ocdp/cluster-service/internal/domain/service"
)
// WorkspaceHandler 工作空间 HTTP 处理程序
type WorkspaceHandler struct {
workspaceService *service.WorkspaceService
authService *service.AuthService
}
// NewWorkspaceHandler 创建工作空间处理程序
func NewWorkspaceHandler(workspaceService *service.WorkspaceService, authService *service.AuthService) *WorkspaceHandler {
return &WorkspaceHandler{
workspaceService: workspaceService,
authService: authService,
}
}
// CreateWorkspace 创建工作空间
// @Summary 创建工作空间
// @Description 创建新的工作空间Admin 专用)
// @Tags workspace
// @Accept json
// @Produce json
// @Param request body dto.CreateWorkspaceRequest true "创建工作空间请求"
// @Success 200 {object} dto.WorkspaceDTO
// @Router /workspaces [post]
func (h *WorkspaceHandler) CreateWorkspace(w http.ResponseWriter, r *http.Request) {
// 检查权限Admin
if !h.requireAdmin(w, r) {
return
}
var req dto.CreateWorkspaceRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondError(w, http.StatusBadRequest, "Invalid request body", "")
return
}
// 获取创建者 ID
userID := GetUserIDFromRequest(r)
workspace, err := h.workspaceService.Create(r.Context(), req.Name, req.Description, userID)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error(), "")
return
}
respondSuccess(w, "", dto.WorkspaceDTOFromEntity(workspace))
}
// GetWorkspace 获取工作空间
// @Summary 获取工作空间
// @Description 获取指定工作空间的详细信息和配额
// @Tags workspace
// @Accept json
// @Produce json
// @Param workspace_id path string true "工作空间 ID"
// @Success 200 {object} dto.WorkspaceResponse
// @Router /workspaces/{workspace_id} [get]
func (h *WorkspaceHandler) GetWorkspace(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
workspaceID := vars["workspace_id"]
workspace, err := h.workspaceService.GetByID(r.Context(), workspaceID)
if err != nil {
respondError(w, http.StatusNotFound, "Workspace not found", "")
return
}
// 检查访问权限
if !h.canAccessWorkspace(w, r, workspace.ID) {
return
}
// 获取配额
quotas, _ := h.workspaceService.GetQuotas(r.Context(), workspace.ID)
response := dto.WorkspaceResponse{
Workspace: dto.WorkspaceDTOFromEntity(workspace),
Quotas: dto.QuotaDTOsFromEntities(quotas),
}
respondSuccess(w, "", response)
}
// UpdateWorkspace 更新工作空间
// @Summary 更新工作空间
// @Description 更新工作空间信息Admin 专用)
// @Tags workspace
// @Accept json
// @Produce json
// @Param workspace_id path string true "工作空间 ID"
// @Param request body dto.UpdateWorkspaceRequest true "更新工作空间请求"
// @Success 200 {object} dto.WorkspaceDTO
// @Router /workspaces/{workspace_id} [put]
func (h *WorkspaceHandler) UpdateWorkspace(w http.ResponseWriter, r *http.Request) {
// 检查权限Admin
if !h.requireAdmin(w, r) {
return
}
vars := mux.Vars(r)
workspaceID := vars["workspace_id"]
workspace, err := h.workspaceService.GetByID(r.Context(), workspaceID)
if err != nil {
respondError(w, http.StatusNotFound, "Workspace not found", "")
return
}
var req dto.UpdateWorkspaceRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondError(w, http.StatusBadRequest, "Invalid request body", "")
return
}
if req.Name != "" {
workspace.Name = req.Name
}
if req.Description != "" {
workspace.Description = req.Description
}
if err := h.workspaceService.Update(r.Context(), workspace); err != nil {
respondError(w, http.StatusBadRequest, err.Error(), "")
return
}
respondSuccess(w, "", dto.WorkspaceDTOFromEntity(workspace))
}
// DeleteWorkspace 删除工作空间
// @Summary 删除工作空间
// @Description 删除指定工作空间Admin 专用)
// @Tags workspace
// @Accept json
// @Produce json
// @Param workspace_id path string true "工作空间 ID"
// @Success 200
// @Router /workspaces/{workspace_id} [delete]
func (h *WorkspaceHandler) DeleteWorkspace(w http.ResponseWriter, r *http.Request) {
// 检查权限Admin
if !h.requireAdmin(w, r) {
return
}
vars := mux.Vars(r)
workspaceID := vars["workspace_id"]
if err := h.workspaceService.Delete(r.Context(), workspaceID); err != nil {
respondError(w, http.StatusBadRequest, err.Error(), "")
return
}
respondSuccess(w, "", nil)
}
// ListWorkspaces 列出所有工作空间
// @Summary 列出所有工作空间
// @Description 获取所有工作空间列表Admin 专用)
// @Tags workspace
// @Accept json
// @Produce json
// @Success 200 {object} dto.WorkspaceListResponse
// @Router /workspaces [get]
func (h *WorkspaceHandler) ListWorkspaces(w http.ResponseWriter, r *http.Request) {
// 检查权限Admin
if !h.requireAdmin(w, r) {
return
}
workspaces, err := h.workspaceService.List(r.Context())
if err != nil {
respondError(w, http.StatusInternalServerError, err.Error(), "")
return
}
respondSuccess(w, "", dto.WorkspaceListResponse{
Workspaces: dto.WorkspaceDTOsFromEntities(workspaces),
Total: len(workspaces),
})
}
// GetWorkspaceQuotas 获取工作空间配额
// @Summary 获取工作空间配额
// @Description 获取指定工作空间的资源配额
// @Tags workspace
// @Accept json
// @Produce json
// @Param workspace_id path string true "工作空间 ID"
// @Success 200 {array} dto.QuotaDTO
// @Router /workspaces/{workspace_id}/quotas [get]
func (h *WorkspaceHandler) GetWorkspaceQuotas(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
workspaceID := vars["workspace_id"]
// 检查访问权限
if !h.canAccessWorkspace(w, r, workspaceID) {
return
}
quotas, err := h.workspaceService.GetQuotas(r.Context(), workspaceID)
if err != nil {
respondError(w, http.StatusInternalServerError, err.Error(), "")
return
}
respondSuccess(w, "", dto.QuotaDTOsFromEntities(quotas))
}
// SetWorkspaceQuotas 设置工作空间配额
// @Summary 设置工作空间配额
// @Description 设置指定工作空间的 CPU/GPU/GPU Memory 配额Admin 专用)
// @Tags workspace
// @Accept json
// @Produce json
// @Param workspace_id path string true "工作空间 ID"
// @Param request body dto.SetQuotasRequest true "配额设置请求"
// @Success 200 {array} dto.QuotaDTO
// @Router /workspaces/{workspace_id}/quotas [put]
func (h *WorkspaceHandler) SetWorkspaceQuotas(w http.ResponseWriter, r *http.Request) {
// 检查权限Admin
if !h.requireAdmin(w, r) {
return
}
vars := mux.Vars(r)
workspaceID := vars["workspace_id"]
var req dto.SetQuotasRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondError(w, http.StatusBadRequest, "Invalid request body", "")
return
}
quotas := make(map[entity.ResourceType]struct {
HardLimit float64
SoftLimit float64
})
if req.CPU != nil {
quotas[entity.ResourceCPU] = struct {
HardLimit float64
SoftLimit float64
}{req.CPU.HardLimit, req.CPU.SoftLimit}
}
if req.GPU != nil {
quotas[entity.ResourceGPU] = struct {
HardLimit float64
SoftLimit float64
}{req.GPU.HardLimit, req.GPU.SoftLimit}
}
if req.GPUMemory != nil {
quotas[entity.ResourceGPUMemory] = struct {
HardLimit float64
SoftLimit float64
}{req.GPUMemory.HardLimit, req.GPUMemory.SoftLimit}
}
if err := h.workspaceService.SetQuotas(r.Context(), workspaceID, quotas); err != nil {
respondError(w, http.StatusBadRequest, err.Error(), "")
return
}
// 返回更新后的配额
updatedQuotas, _ := h.workspaceService.GetQuotas(r.Context(), workspaceID)
respondSuccess(w, "", dto.QuotaDTOsFromEntities(updatedQuotas))
}
// requireAdmin 检查是否为 Admin
func (h *WorkspaceHandler) requireAdmin(w http.ResponseWriter, r *http.Request) bool {
userRole := r.Header.Get("X-User-Role")
if userRole != string(entity.RoleAdmin) {
respondError(w, http.StatusForbidden, "Admin access required", "")
return false
}
return true
}
// canAccessWorkspace 检查是否可以访问工作空间
func (h *WorkspaceHandler) canAccessWorkspace(w http.ResponseWriter, r *http.Request, workspaceID string) bool {
userRole := r.Header.Get("X-User-Role")
userWorkspaceID := r.Header.Get("X-Workspace-ID")
// Admin 可以访问所有
if userRole == string(entity.RoleAdmin) {
return true
}
// 普通用户只能访问自己的 workspace
if userWorkspaceID != workspaceID {
respondError(w, http.StatusForbidden, "Access denied", "")
return false
}
return true
}