Files
ocdp-go/backend/internal/adapter/input/http/rest/values_template_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

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"),
}
}