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.
294 lines
9.7 KiB
Go
294 lines
9.7 KiB
Go
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"
|
|
)
|
|
|
|
// ValuesTemplateHandler Values Template Handler
|
|
type ValuesTemplateHandler struct {
|
|
valuesTemplateService *service.ValuesTemplateService
|
|
}
|
|
|
|
// NewValuesTemplateHandler 创建 Values Template Handler
|
|
func NewValuesTemplateHandler(valuesTemplateService *service.ValuesTemplateService) *ValuesTemplateHandler {
|
|
return &ValuesTemplateHandler{
|
|
valuesTemplateService: valuesTemplateService,
|
|
}
|
|
}
|
|
|
|
// CreateValuesTemplate 创建 Values 模板
|
|
// @Summary 创建 Values 模板
|
|
// @Description 新增 Values 模板配置(带版本管理)
|
|
// @Tags Values Templates
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param request body dto.CreateValuesTemplateRequest true "Values 模板信息"
|
|
// @Success 201 {object} dto.ValuesTemplateResponse
|
|
// @Failure 400 {object} dto.ErrorResponse
|
|
// @Router /values-templates [post]
|
|
func (h *ValuesTemplateHandler) CreateValuesTemplate(w http.ResponseWriter, r *http.Request) {
|
|
var req dto.CreateValuesTemplateRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
respondError(w, http.StatusBadRequest, "Invalid request body", err.Error())
|
|
return
|
|
}
|
|
|
|
// 获取用户信息
|
|
workspaceID := r.Header.Get("X-Workspace-ID")
|
|
ownerID := r.Header.Get("X-User-ID")
|
|
|
|
template, err := h.valuesTemplateService.Create(
|
|
r.Context(),
|
|
workspaceID,
|
|
ownerID,
|
|
req.ChartReferenceID,
|
|
req.Name,
|
|
req.Description,
|
|
req.ValuesYAML,
|
|
req.IsDefault,
|
|
)
|
|
if err != nil {
|
|
respondError(w, http.StatusBadRequest, "Failed to create values template", err.Error())
|
|
return
|
|
}
|
|
|
|
response := toValuesTemplateResponse(template)
|
|
respondJSON(w, http.StatusCreated, response)
|
|
}
|
|
|
|
// GetValuesTemplate 获取 Values 模板详情
|
|
// @Summary 获取 Values 模板
|
|
// @Tags Values Templates
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param template_id path string true "Template ID"
|
|
// @Success 200 {object} dto.ValuesTemplateResponse
|
|
// @Failure 404 {object} dto.ErrorResponse
|
|
// @Router /values-templates/{template_id} [get]
|
|
func (h *ValuesTemplateHandler) GetValuesTemplate(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
templateID := vars["template_id"]
|
|
|
|
template, err := h.valuesTemplateService.GetByID(r.Context(), templateID)
|
|
if err != nil {
|
|
respondError(w, http.StatusNotFound, "Values template not found", err.Error())
|
|
return
|
|
}
|
|
|
|
response := toValuesTemplateResponse(template)
|
|
respondJSON(w, http.StatusOK, response)
|
|
}
|
|
|
|
// GetAllValuesTemplates 获取所有 Values 模板
|
|
// @Summary 列出所有 Values 模板
|
|
// @Tags Values Templates
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Success 200 {array} dto.ValuesTemplateResponse
|
|
// @Failure 500 {object} dto.ErrorResponse
|
|
// @Router /values-templates [get]
|
|
func (h *ValuesTemplateHandler) GetAllValuesTemplates(w http.ResponseWriter, r *http.Request) {
|
|
workspaceID := r.Header.Get("X-Workspace-ID")
|
|
role := r.Header.Get("X-User-Role")
|
|
|
|
var templates []*dto.ValuesTemplateResponse
|
|
|
|
// Admin 可以看到所有,其他用户只看自己 workspace
|
|
if role == "admin" {
|
|
allTemplates, err := h.valuesTemplateService.List(r.Context())
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, "Failed to list values templates", err.Error())
|
|
return
|
|
}
|
|
for _, t := range allTemplates {
|
|
templates = append(templates, toValuesTemplateResponse(t))
|
|
}
|
|
} else if workspaceID != "" {
|
|
workspaceTemplates, err := h.valuesTemplateService.GetByWorkspace(r.Context(), workspaceID)
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, "Failed to list values templates", err.Error())
|
|
return
|
|
}
|
|
for _, t := range workspaceTemplates {
|
|
templates = append(templates, toValuesTemplateResponse(t))
|
|
}
|
|
}
|
|
|
|
respondJSON(w, http.StatusOK, templates)
|
|
}
|
|
|
|
// GetValuesTemplatesByChartReference 获取 Chart Reference 的所有 Values 模板
|
|
// @Summary 获取 Chart Reference 的 Values 模板
|
|
// @Tags Values Templates
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param chart_reference_id path string true "Chart Reference ID"
|
|
// @Success 200 {array} dto.ValuesTemplateResponse
|
|
// @Failure 500 {object} dto.ErrorResponse
|
|
// @Router /chart-references/{chart_reference_id}/values-templates [get]
|
|
func (h *ValuesTemplateHandler) GetValuesTemplatesByChartReference(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
chartRefID := vars["chart_reference_id"]
|
|
|
|
templates, err := h.valuesTemplateService.GetByChartReference(r.Context(), chartRefID)
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, "Failed to list values templates", err.Error())
|
|
return
|
|
}
|
|
|
|
responses := make([]*dto.ValuesTemplateResponse, 0, len(templates))
|
|
for _, t := range templates {
|
|
responses = append(responses, toValuesTemplateResponse(t))
|
|
}
|
|
|
|
respondJSON(w, http.StatusOK, responses)
|
|
}
|
|
|
|
// GetValuesTemplateHistory 获取模板的版本历史
|
|
// @Summary 获取 Values 模板版本历史
|
|
// @Tags Values Templates
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param chart_reference_id path string true "Chart Reference ID"
|
|
// @Param name query string true "Template Name"
|
|
// @Success 200 {array} dto.ValuesTemplateResponse
|
|
// @Failure 500 {object} dto.ErrorResponse
|
|
// @Router /chart-references/{chart_reference_id}/values-templates/history [get]
|
|
func (h *ValuesTemplateHandler) GetValuesTemplateHistory(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
chartRefID := vars["chart_reference_id"]
|
|
name := r.URL.Query().Get("name")
|
|
|
|
if name == "" {
|
|
respondError(w, http.StatusBadRequest, "Template name is required", "")
|
|
return
|
|
}
|
|
|
|
templates, err := h.valuesTemplateService.GetHistory(r.Context(), chartRefID, name)
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, "Failed to get values template history", err.Error())
|
|
return
|
|
}
|
|
|
|
responses := make([]*dto.ValuesTemplateResponse, 0, len(templates))
|
|
for _, t := range templates {
|
|
responses = append(responses, toValuesTemplateResponse(t))
|
|
}
|
|
|
|
respondJSON(w, http.StatusOK, responses)
|
|
}
|
|
|
|
// UpdateValuesTemplate 更新 Values 模板
|
|
// @Summary 更新 Values 模板
|
|
// @Tags Values Templates
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param template_id path string true "Template ID"
|
|
// @Param request body dto.UpdateValuesTemplateRequest true "更新内容"
|
|
// @Success 200 {object} dto.ValuesTemplateResponse
|
|
// @Failure 404 {object} dto.ErrorResponse
|
|
// @Router /values-templates/{template_id} [put]
|
|
func (h *ValuesTemplateHandler) UpdateValuesTemplate(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
templateID := vars["template_id"]
|
|
|
|
var req dto.UpdateValuesTemplateRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
respondError(w, http.StatusBadRequest, "Invalid request body", err.Error())
|
|
return
|
|
}
|
|
|
|
isDefault := false
|
|
if req.IsDefault != nil {
|
|
isDefault = *req.IsDefault
|
|
}
|
|
|
|
template, err := h.valuesTemplateService.Update(
|
|
r.Context(),
|
|
templateID,
|
|
req.Description,
|
|
req.ValuesYAML,
|
|
isDefault,
|
|
)
|
|
if err != nil {
|
|
respondError(w, http.StatusBadRequest, "Failed to update values template", err.Error())
|
|
return
|
|
}
|
|
|
|
response := toValuesTemplateResponse(template)
|
|
respondJSON(w, http.StatusOK, response)
|
|
}
|
|
|
|
// DeleteValuesTemplate 删除 Values 模板
|
|
// @Summary 删除 Values 模板
|
|
// @Tags Values Templates
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param template_id path string true "Template ID"
|
|
// @Success 204 {string} string "No Content"
|
|
// @Failure 404 {object} dto.ErrorResponse
|
|
// @Router /values-templates/{template_id} [delete]
|
|
func (h *ValuesTemplateHandler) DeleteValuesTemplate(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
templateID := vars["template_id"]
|
|
|
|
if err := h.valuesTemplateService.Delete(r.Context(), templateID); err != nil {
|
|
respondError(w, http.StatusNotFound, "Failed to delete values template", err.Error())
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
// RollbackValuesTemplate 回滚到指定版本
|
|
// @Summary 回滚 Values 模板
|
|
// @Tags Values Templates
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param chart_reference_id path string true "Chart Reference ID"
|
|
// @Param request body dto.RollbackValuesTemplateRequest true "回滚信息"
|
|
// @Success 200 {object} dto.ValuesTemplateResponse
|
|
// @Failure 404 {object} dto.ErrorResponse
|
|
// @Router /chart-references/{chart_reference_id}/values-templates/rollback [post]
|
|
func (h *ValuesTemplateHandler) RollbackValuesTemplate(w http.ResponseWriter, r *http.Request) {
|
|
var req dto.RollbackValuesTemplateRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
respondError(w, http.StatusBadRequest, "Invalid request body", err.Error())
|
|
return
|
|
}
|
|
|
|
template, err := h.valuesTemplateService.Rollback(r.Context(), req.TemplateID)
|
|
if err != nil {
|
|
respondError(w, http.StatusBadRequest, "Failed to rollback values template", err.Error())
|
|
return
|
|
}
|
|
|
|
response := toValuesTemplateResponse(template)
|
|
respondJSON(w, http.StatusOK, response)
|
|
}
|
|
|
|
// toValuesTemplateResponse 转换为响应 DTO
|
|
func toValuesTemplateResponse(template *entity.ValuesTemplate) *dto.ValuesTemplateResponse {
|
|
return &dto.ValuesTemplateResponse{
|
|
ID: template.ID,
|
|
WorkspaceID: template.WorkspaceID,
|
|
OwnerID: template.OwnerID,
|
|
ChartReferenceID: template.ChartReferenceID,
|
|
Name: template.Name,
|
|
Description: template.Description,
|
|
ValuesYAML: template.ValuesYAML,
|
|
Version: template.Version,
|
|
IsDefault: template.IsDefault,
|
|
CreatedAt: template.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
|
UpdatedAt: template.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
|
}
|
|
} |