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.
This commit is contained in:
@ -0,0 +1,294 @@
|
||||
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"),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user