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:
@ -2,9 +2,16 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/ocdp/cluster-service/internal/domain/entity"
|
||||
"github.com/ocdp/cluster-service/internal/domain/repository"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
// ClusterService 集群管理领域服务
|
||||
@ -75,3 +82,105 @@ func (s *ClusterService) ListClusters(ctx context.Context) ([]*entity.Cluster, e
|
||||
return s.clusterRepo.List(ctx)
|
||||
}
|
||||
|
||||
// ListByWorkspace 列出指定 workspace 的集群(包括共享集群)
|
||||
func (s *ClusterService) ListByWorkspace(ctx context.Context, workspaceID string) ([]*entity.Cluster, error) {
|
||||
return s.clusterRepo.GetByWorkspace(ctx, workspaceID)
|
||||
}
|
||||
|
||||
// GetSharedClusters 获取所有共享集群
|
||||
func (s *ClusterService) GetSharedClusters(ctx context.Context) ([]*entity.Cluster, error) {
|
||||
return s.clusterRepo.GetShared(ctx)
|
||||
}
|
||||
|
||||
// TestConnection 测试集群连接是否可用
|
||||
func (s *ClusterService) TestConnection(ctx context.Context, cluster *entity.Cluster) error {
|
||||
// Mock 模式直接返回成功
|
||||
if os.Getenv("ADAPTER_MODE") == "mock" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 尝试创建 k8s client
|
||||
config, err := s.createRestConfig(cluster)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create k8s config: %w", err)
|
||||
}
|
||||
|
||||
// 设置超时
|
||||
config.Timeout = 30 * 1000000000 // 30秒 (nanoseconds)
|
||||
|
||||
clientset, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create k8s client: %w", err)
|
||||
}
|
||||
|
||||
// 测试连接 - 获取 version 信息
|
||||
version, err := clientset.ServerVersion()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to cluster: %w", err)
|
||||
}
|
||||
|
||||
if version == nil {
|
||||
return fmt.Errorf("cluster returned nil version")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createRestConfig 从 cluster 实体创建 k8s REST 配置
|
||||
func (s *ClusterService) createRestConfig(cluster *entity.Cluster) (*rest.Config, error) {
|
||||
// 优先使用 kubeconfig 格式(如果 CAData 包含完整的 kubeconfig 内容)
|
||||
if len(cluster.CAData) > 100 && (cluster.CAData[:11] == "apiVersion:" || cluster.CAData[:5] == "kind:") {
|
||||
return clientcmd.RESTConfigFromKubeConfig([]byte(cluster.CAData))
|
||||
}
|
||||
|
||||
// 使用证书或 token 认证
|
||||
config := &rest.Config{
|
||||
Host: cluster.Host,
|
||||
}
|
||||
|
||||
if cluster.CertData != "" && cluster.KeyData != "" {
|
||||
// 尝试解码 base64 编码的证书,如果失败则尝试原始 PEM
|
||||
var caData, certData, keyData []byte
|
||||
var decodeErr error
|
||||
|
||||
// 先尝试 base64 解码
|
||||
caData, decodeErr = base64.StdEncoding.DecodeString(cluster.CAData)
|
||||
if decodeErr != nil {
|
||||
// base64 解码失败,可能是原始 PEM
|
||||
caData = []byte(cluster.CAData)
|
||||
}
|
||||
|
||||
certData, decodeErr = base64.StdEncoding.DecodeString(cluster.CertData)
|
||||
if decodeErr != nil {
|
||||
certData = []byte(cluster.CertData)
|
||||
}
|
||||
|
||||
keyData, decodeErr = base64.StdEncoding.DecodeString(cluster.KeyData)
|
||||
if decodeErr != nil {
|
||||
keyData = []byte(cluster.KeyData)
|
||||
}
|
||||
|
||||
config.TLSClientConfig = rest.TLSClientConfig{
|
||||
CAData: caData,
|
||||
CertData: certData,
|
||||
KeyData: keyData,
|
||||
Insecure: false,
|
||||
}
|
||||
} else if cluster.Token != "" {
|
||||
config.BearerToken = cluster.Token
|
||||
} else {
|
||||
// 尝试使用本地 kubeconfig
|
||||
kubeconfig := os.Getenv("KUBECONFIG")
|
||||
if kubeconfig == "" {
|
||||
kubeconfig = ".kube/config"
|
||||
}
|
||||
// 尝试从文件加载 kubeconfig
|
||||
if _, err := os.Stat(kubeconfig); err == nil {
|
||||
return clientcmd.BuildConfigFromFlags("", kubeconfig)
|
||||
}
|
||||
return clientcmd.BuildConfigFromFlags("", kubeconfig)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user