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

233 lines
7.9 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 (
"errors"
"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"
)
// ArtifactHandler Artifact Handler
type ArtifactHandler struct {
artifactService *service.ArtifactService
}
// NewArtifactHandler 创建 Artifact Handler
func NewArtifactHandler(artifactService *service.ArtifactService) *ArtifactHandler {
return &ArtifactHandler{
artifactService: artifactService,
}
}
// ListRepositories 列出 Registry 中的所有 repositories
// @Summary 列出 Registry 中的所有 Repositories
// @Description 列出指定 Registry 中的所有 Repository
// @Tags Artifacts
// @Accept json
// @Produce json
// @Param registry_id path string true "Registry ID"
// @Success 200 {object} dto.RepositoryListResponse
// @Failure 500 {object} dto.ErrorResponse
// @Router /registries/{registry_id}/repositories [get]
func (h *ArtifactHandler) ListRepositories(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
registryID := vars["registry_id"]
repositories, err := h.artifactService.ListRepositories(r.Context(), registryID)
if err != nil {
respondError(w, http.StatusInternalServerError, "Failed to list repositories", err.Error())
return
}
// Get registry info for URL
registry, err := h.artifactService.GetRegistry(r.Context(), registryID)
registryURL := ""
if err == nil && registry != nil {
registryURL = registry.URL
}
// Determine source and message based on repository count
source := "catalog"
catalogSupported := true
message := ""
if len(repositories) == 0 {
source = "unavailable"
message = "No repositories found in this registry"
}
response := &dto.RepositoryListResponse{
RegistryID: registryID,
RegistryURL: registryURL,
Repositories: repositories,
Total: len(repositories),
CatalogSupported: catalogSupported,
Source: source,
Message: message,
}
respondJSON(w, http.StatusOK, response)
}
// ListArtifacts 列出 repository 中的所有 artifacts返回扁平化的 Tag 数组)
// @Summary 列出 Repository 中的所有 Artifacts
// @Description 列出指定 Repository 中的所有 Artifact支持按类型过滤
// @Tags Artifacts
// @Accept json
// @Produce json
// @Param registry_id path string true "Registry ID"
// @Param repository_name path string true "Repository Name (URL encoded, e.g. charts%2Fnginx)"
// @Param media_type query string false "过滤 Artifact 类型 (all, chart, image, other)" default(all)
// @Success 200 {array} dto.TagResponse
// @Failure 500 {object} dto.ErrorResponse
// @Router /registries/{registry_id}/repositories/{repository_name}/artifacts [get]
func (h *ArtifactHandler) ListArtifacts(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
registryID := vars["registry_id"]
repositoryName := vars["repository_name"]
// 获取 mediaType 过滤参数(默认为 "all"
mediaTypeFilter := r.URL.Query().Get("media_type")
if mediaTypeFilter == "" {
mediaTypeFilter = "all"
}
artifacts, err := h.artifactService.ListArtifacts(r.Context(), registryID, repositoryName, mediaTypeFilter)
if err != nil {
respondError(w, http.StatusInternalServerError, "Failed to list artifacts", err.Error())
return
}
// 转换为前端期望的扁平化 Tag 数组
tagResponses := make([]*dto.TagResponse, 0, len(artifacts))
for _, artifact := range artifacts {
tagResponses = append(tagResponses, &dto.TagResponse{
RepositoryName: artifact.Repository,
Tag: artifact.Tag,
Type: string(artifact.Type),
MediaType: artifact.MediaType,
Size: artifact.Size,
})
}
// 直接返回数组,不包装
respondJSON(w, http.StatusOK, tagResponses)
}
// GetArtifact 获取 artifact 详情
// @Summary 获取 Artifact 详情
// @Description 获取指定 Artifact 的详细信息
// @Tags Artifacts
// @Accept json
// @Produce json
// @Param registry_id path string true "Registry ID"
// @Param repository_name path string true "Repository Name (URL encoded)"
// @Param reference path string true "Artifact Reference (tag or digest)"
// @Success 200 {object} dto.ArtifactResponse
// @Failure 404 {object} dto.ErrorResponse
// @Router /registries/{registry_id}/repositories/{repository_name}/artifacts/{reference} [get]
func (h *ArtifactHandler) GetArtifact(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
registryID := vars["registry_id"]
repositoryName := vars["repository_name"]
reference := vars["reference"]
artifact, err := h.artifactService.GetArtifact(r.Context(), registryID, repositoryName, reference)
if err != nil {
respondError(w, http.StatusNotFound, "Artifact not found", err.Error())
return
}
response := &dto.ArtifactResponse{
RepositoryName: artifact.Repository,
Tag: artifact.Tag,
Digest: artifact.Digest,
Type: string(artifact.Type),
Size: artifact.Size,
CreatedAt: artifact.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
}
respondJSON(w, http.StatusOK, response)
}
// GetArtifactValuesSchema 获取 Helm Chart 的 values schema
// @Summary 获取 Helm Chart Values Schema
// @Description 获取 Helm Chart 的 values.schema.json (仅支持 Chart 类型)
// @Tags Artifacts
// @Accept json
// @Produce json
// @Param registry_id path string true "Registry ID"
// @Param repository_name path string true "Repository Name (URL encoded)"
// @Param reference path string true "Artifact Reference (tag or digest)"
// @Success 200 {object} dto.ValuesSchemaResponse
// @Failure 500 {object} dto.ErrorResponse
// @Router /registries/{registry_id}/repositories/{repository_name}/artifacts/{reference}/values-schema [get]
func (h *ArtifactHandler) GetArtifactValuesSchema(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
registryID := vars["registry_id"]
repositoryName := vars["repository_name"]
reference := vars["reference"]
schema, err := h.artifactService.GetValuesSchema(r.Context(), registryID, repositoryName, reference)
if err != nil {
switch {
case errors.Is(err, entity.ErrRegistryNotFound),
errors.Is(err, entity.ErrRepositoryNotFound),
errors.Is(err, entity.ErrArtifactNotFound),
errors.Is(err, entity.ErrValuesSchemaNotFound):
respondError(w, http.StatusNotFound, "Values schema not found", err.Error())
default:
respondError(w, http.StatusInternalServerError, "Failed to get values schema", err.Error())
}
return
}
response := &dto.ValuesSchemaResponse{
Schema: schema,
}
respondJSON(w, http.StatusOK, response)
}
// GetArtifactValues 获取 Helm Chart 的 values.yaml
// @Summary 获取 Helm Chart Values
// @Description 获取 Helm Chart 的 values.yaml 文件内容 (仅支持 Chart 类型)
// @Tags Artifacts
// @Accept json
// @Produce json
// @Param registry_id path string true "Registry ID"
// @Param repository_name path string true "Repository Name (URL encoded)"
// @Param reference path string true "Artifact Reference (tag or digest)"
// @Success 200 {object} dto.ValuesResponse
// @Failure 500 {object} dto.ErrorResponse
// @Router /registries/{registry_id}/repositories/{repository_name}/artifacts/{reference}/values [get]
func (h *ArtifactHandler) GetArtifactValues(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
registryID := vars["registry_id"]
repositoryName := vars["repository_name"]
reference := vars["reference"]
values, err := h.artifactService.GetValues(r.Context(), registryID, repositoryName, reference)
if err != nil {
switch {
case errors.Is(err, entity.ErrRegistryNotFound),
errors.Is(err, entity.ErrRepositoryNotFound),
errors.Is(err, entity.ErrArtifactNotFound),
errors.Is(err, entity.ErrValuesNotFound):
respondError(w, http.StatusNotFound, "Values not found", err.Error())
default:
respondError(w, http.StatusInternalServerError, "Failed to get values", err.Error())
}
return
}
response := &dto.ValuesResponse{
Values: values,
}
respondJSON(w, http.StatusOK, response)
}