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,229 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// ChartReferenceHandler Chart Reference Handler
|
||||
type ChartReferenceHandler struct {
|
||||
chartRefService *service.ChartReferenceService
|
||||
}
|
||||
|
||||
// NewChartReferenceHandler 创建 Chart Reference Handler
|
||||
func NewChartReferenceHandler(chartRefService *service.ChartReferenceService) *ChartReferenceHandler {
|
||||
return &ChartReferenceHandler{
|
||||
chartRefService: chartRefService,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateChartReference 创建 Chart 引用
|
||||
// @Summary 创建 Chart 引用
|
||||
// @Description 新增 Chart 引用配置
|
||||
// @Tags Chart References
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param request body dto.CreateChartReferenceRequest true "Chart 引用信息"
|
||||
// @Success 201 {object} dto.ChartReferenceResponse
|
||||
// @Failure 400 {object} dto.ErrorResponse
|
||||
// @Router /chart-references [post]
|
||||
func (h *ChartReferenceHandler) CreateChartReference(w http.ResponseWriter, r *http.Request) {
|
||||
var req dto.CreateChartReferenceRequest
|
||||
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")
|
||||
|
||||
chartRef, err := h.chartRefService.Create(
|
||||
r.Context(),
|
||||
workspaceID,
|
||||
req.RegistryID,
|
||||
req.Repository,
|
||||
req.ChartName,
|
||||
req.Description,
|
||||
)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusBadRequest, "Failed to create chart reference", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response := toChartReferenceResponse(chartRef)
|
||||
respondJSON(w, http.StatusCreated, response)
|
||||
}
|
||||
|
||||
// GetChartReference 获取 Chart 引用详情
|
||||
// @Summary 获取 Chart 引用
|
||||
// @Tags Chart References
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param chart_reference_id path string true "Chart Reference ID"
|
||||
// @Success 200 {object} dto.ChartReferenceResponse
|
||||
// @Failure 404 {object} dto.ErrorResponse
|
||||
// @Router /chart-references/{chart_reference_id} [get]
|
||||
func (h *ChartReferenceHandler) GetChartReference(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
chartRefID := vars["chart_reference_id"]
|
||||
|
||||
chartRef, err := h.chartRefService.GetByID(r.Context(), chartRefID)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusNotFound, "Chart reference not found", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response := toChartReferenceResponse(chartRef)
|
||||
respondJSON(w, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// GetAllChartReferences 获取所有 Chart 引用
|
||||
// @Summary 列出所有 Chart 引用
|
||||
// @Tags Chart References
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {array} dto.ChartReferenceResponse
|
||||
// @Failure 500 {object} dto.ErrorResponse
|
||||
// @Router /chart-references [get]
|
||||
func (h *ChartReferenceHandler) GetAllChartReferences(w http.ResponseWriter, r *http.Request) {
|
||||
workspaceID := r.Header.Get("X-Workspace-ID")
|
||||
role := r.Header.Get("X-User-Role")
|
||||
|
||||
var chartRefs []*dto.ChartReferenceResponse
|
||||
|
||||
// Admin 可以看到所有,其他用户只看自己 workspace
|
||||
if role == "admin" {
|
||||
allChartRefs, err := h.chartRefService.List(r.Context())
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "Failed to list chart references", err.Error())
|
||||
return
|
||||
}
|
||||
for _, cr := range allChartRefs {
|
||||
chartRefs = append(chartRefs, toChartReferenceResponse(cr))
|
||||
}
|
||||
} else if workspaceID != "" {
|
||||
workspaceChartRefs, err := h.chartRefService.GetByWorkspace(r.Context(), workspaceID)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "Failed to list chart references", err.Error())
|
||||
return
|
||||
}
|
||||
for _, cr := range workspaceChartRefs {
|
||||
chartRefs = append(chartRefs, toChartReferenceResponse(cr))
|
||||
}
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, chartRefs)
|
||||
}
|
||||
|
||||
// UpdateChartReference 更新 Chart 引用
|
||||
// @Summary 更新 Chart 引用
|
||||
// @Tags Chart References
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param chart_reference_id path string true "Chart Reference ID"
|
||||
// @Param request body dto.UpdateChartReferenceRequest true "更新内容"
|
||||
// @Success 200 {object} dto.ChartReferenceResponse
|
||||
// @Failure 404 {object} dto.ErrorResponse
|
||||
// @Router /chart-references/{chart_reference_id} [put]
|
||||
func (h *ChartReferenceHandler) UpdateChartReference(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
chartRefID := vars["chart_reference_id"]
|
||||
|
||||
var req dto.UpdateChartReferenceRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
respondError(w, http.StatusBadRequest, "Invalid request body", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
isEnabled := true
|
||||
if req.IsEnabled != nil {
|
||||
isEnabled = *req.IsEnabled
|
||||
}
|
||||
|
||||
chartRef, err := h.chartRefService.Update(
|
||||
r.Context(),
|
||||
chartRefID,
|
||||
req.RegistryID,
|
||||
req.Repository,
|
||||
req.ChartName,
|
||||
req.Description,
|
||||
isEnabled,
|
||||
)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusBadRequest, "Failed to update chart reference", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response := toChartReferenceResponse(chartRef)
|
||||
respondJSON(w, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// DeleteChartReference 删除 Chart 引用
|
||||
// @Summary 删除 Chart 引用
|
||||
// @Tags Chart References
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param chart_reference_id path string true "Chart Reference ID"
|
||||
// @Success 204 {string} string "No Content"
|
||||
// @Failure 404 {object} dto.ErrorResponse
|
||||
// @Router /chart-references/{chart_reference_id} [delete]
|
||||
func (h *ChartReferenceHandler) DeleteChartReference(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
chartRefID := vars["chart_reference_id"]
|
||||
|
||||
if err := h.chartRefService.Delete(r.Context(), chartRefID); err != nil {
|
||||
respondError(w, http.StatusNotFound, "Failed to delete chart reference", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// GetChartReferencesByRegistry 获取 Registry 的所有 Chart 引用
|
||||
// @Summary 获取 Registry 的 Chart 引用
|
||||
// @Tags Chart References
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param registry_id path string true "Registry ID"
|
||||
// @Success 200 {array} dto.ChartReferenceResponse
|
||||
// @Failure 500 {object} dto.ErrorResponse
|
||||
// @Router /registries/{registry_id}/chart-references [get]
|
||||
func (h *ChartReferenceHandler) GetChartReferencesByRegistry(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
registryID := vars["registry_id"]
|
||||
|
||||
chartRefs, err := h.chartRefService.GetByRegistry(r.Context(), registryID)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "Failed to list chart references", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
responses := make([]*dto.ChartReferenceResponse, 0, len(chartRefs))
|
||||
for _, cr := range chartRefs {
|
||||
responses = append(responses, toChartReferenceResponse(cr))
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, responses)
|
||||
}
|
||||
|
||||
// toChartReferenceResponse 转换为响应 DTO
|
||||
func toChartReferenceResponse(chartRef *entity.ChartReference) *dto.ChartReferenceResponse {
|
||||
return &dto.ChartReferenceResponse{
|
||||
ID: chartRef.ID,
|
||||
WorkspaceID: chartRef.WorkspaceID,
|
||||
RegistryID: chartRef.RegistryID,
|
||||
Repository: chartRef.Repository,
|
||||
ChartName: chartRef.ChartName,
|
||||
Description: chartRef.Description,
|
||||
IsEnabled: chartRef.IsEnabled,
|
||||
CreatedAt: chartRef.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
UpdatedAt: chartRef.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user