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.
319 lines
9.5 KiB
Go
319 lines
9.5 KiB
Go
package output
|
||
|
||
import (
|
||
"fmt"
|
||
|
||
helmMock "github.com/ocdp/cluster-service/internal/adapter/output/helm/mock"
|
||
helmReal "github.com/ocdp/cluster-service/internal/adapter/output/helm/real"
|
||
"github.com/ocdp/cluster-service/internal/adapter/output/k8s"
|
||
ociMock "github.com/ocdp/cluster-service/internal/adapter/output/oci/mock"
|
||
ociReal "github.com/ocdp/cluster-service/internal/adapter/output/oci/real"
|
||
"github.com/ocdp/cluster-service/internal/adapter/output/persistence/mock"
|
||
"github.com/ocdp/cluster-service/internal/adapter/output/persistence/postgres"
|
||
"github.com/ocdp/cluster-service/internal/domain/repository"
|
||
"github.com/ocdp/cluster-service/internal/pkg/crypto"
|
||
)
|
||
|
||
// AdapterMode 适配器模式
|
||
type AdapterMode string
|
||
|
||
const (
|
||
ModeMock AdapterMode = "mock" // Mock 模式(内存存储,用于开发调试)
|
||
// 默认模式:连接真实 PostgreSQL 和服务(任何非 "mock" 的值都是默认模式)
|
||
)
|
||
|
||
// AdapterFactory 适配器工厂
|
||
// 用于创建所有 Output Adapters,支持 Mock 和真实实现切换
|
||
type AdapterFactory struct {
|
||
mode AdapterMode
|
||
encryptor crypto.Encryptor // 加密器(用于敏感数据加密)
|
||
|
||
// 数据库连接字符串(非 Mock 模式需要)
|
||
dbConnString string
|
||
|
||
// 数据库连接(非 Mock 模式)
|
||
db *postgres.DB
|
||
}
|
||
|
||
// NewAdapterFactory 创建适配器工厂
|
||
func NewAdapterFactory(mode AdapterMode, encryptor crypto.Encryptor, dbConnString string) *AdapterFactory {
|
||
return &AdapterFactory{
|
||
mode: mode,
|
||
encryptor: encryptor,
|
||
dbConnString: dbConnString,
|
||
}
|
||
}
|
||
|
||
// CreateUserRepository 创建用户仓储
|
||
func (f *AdapterFactory) CreateUserRepository() (repository.UserRepository, error) {
|
||
if f.mode == ModeMock {
|
||
return mock.NewUserRepositoryMock(), nil
|
||
}
|
||
|
||
// 默认:真实实现(PostgreSQL)
|
||
if err := f.ensureDBConnection(); err != nil {
|
||
return nil, err
|
||
}
|
||
return postgres.NewUserRepository(f.db), nil
|
||
}
|
||
|
||
// CreateClusterRepository 创建集群仓储
|
||
func (f *AdapterFactory) CreateClusterRepository() (repository.ClusterRepository, error) {
|
||
if f.mode == ModeMock {
|
||
return mock.NewClusterRepositoryMock(f.encryptor), nil
|
||
}
|
||
|
||
// 默认:真实实现(PostgreSQL)
|
||
if err := f.ensureDBConnection(); err != nil {
|
||
return nil, err
|
||
}
|
||
return postgres.NewClusterRepository(f.db, f.encryptor), nil
|
||
}
|
||
|
||
// CreateRegistryRepository 创建 Registry 仓储
|
||
func (f *AdapterFactory) CreateRegistryRepository() (repository.RegistryRepository, error) {
|
||
if f.mode == ModeMock {
|
||
return mock.NewRegistryRepositoryMock(f.encryptor), nil
|
||
}
|
||
|
||
// 默认:真实实现(PostgreSQL)
|
||
if err := f.ensureDBConnection(); err != nil {
|
||
return nil, err
|
||
}
|
||
return postgres.NewRegistryRepository(f.db, f.encryptor), nil
|
||
}
|
||
|
||
// CreateInstanceRepository 创建实例仓储
|
||
func (f *AdapterFactory) CreateInstanceRepository() (repository.InstanceRepository, error) {
|
||
if f.mode == ModeMock {
|
||
return mock.NewInstanceRepositoryMock(), nil
|
||
}
|
||
|
||
// 默认:真实实现(PostgreSQL)
|
||
if err := f.ensureDBConnection(); err != nil {
|
||
return nil, err
|
||
}
|
||
return postgres.NewInstanceRepository(f.db), nil
|
||
}
|
||
|
||
// CreateOCIClient 创建 OCI 客户端
|
||
func (f *AdapterFactory) CreateOCIClient() (repository.OCIClient, error) {
|
||
if f.mode == ModeMock {
|
||
return ociMock.NewOCIClientMock(), nil
|
||
}
|
||
|
||
// 默认:真实实现(ORAS SDK)
|
||
return ociReal.NewOCIClient(), nil
|
||
}
|
||
|
||
// CreateHelmClient 创建 Helm 客户端
|
||
func (f *AdapterFactory) CreateHelmClient() (repository.HelmClient, error) {
|
||
if f.mode == ModeMock {
|
||
return helmMock.NewHelmClientMock(), nil
|
||
}
|
||
|
||
// 默认:真实实现(Helm SDK)
|
||
return helmReal.NewHelmClient(), nil
|
||
}
|
||
|
||
// CreateMetricsClient 创建 Metrics 客户端
|
||
func (f *AdapterFactory) CreateMetricsClient(clusterRepo repository.ClusterRepository) repository.MetricsClient {
|
||
// Metrics client 总是使用真实的 Kubernetes API
|
||
return k8s.NewMetricsClient(clusterRepo)
|
||
}
|
||
|
||
// CreateEntryClient 创建实例入口查询客户端
|
||
func (f *AdapterFactory) CreateEntryClient() repository.InstanceEntryClient {
|
||
return k8s.NewEntryClient()
|
||
}
|
||
|
||
// CreateWorkspaceRepository 创建 Workspace 仓储
|
||
func (f *AdapterFactory) CreateWorkspaceRepository() (repository.WorkspaceRepository, error) {
|
||
if f.mode == ModeMock {
|
||
return nil, fmt.Errorf("workspace repository mock not implemented")
|
||
}
|
||
|
||
// 默认:真实实现(PostgreSQL)
|
||
if err := f.ensureDBConnection(); err != nil {
|
||
return nil, err
|
||
}
|
||
return postgres.NewWorkspaceRepository(f.db), nil
|
||
}
|
||
|
||
// CreateQuotaRepository 创建 Quota 仓储
|
||
// CreateStorageRepository 创建存储后端仓储
|
||
func (f *AdapterFactory) CreateStorageRepository() (repository.StorageRepository, error) {
|
||
if f.mode == ModeMock {
|
||
return mock.NewStorageRepositoryMock(), nil
|
||
}
|
||
|
||
if err := f.ensureDBConnection(); err != nil {
|
||
return nil, err
|
||
}
|
||
return postgres.NewStorageRepository(f.db), nil
|
||
}
|
||
|
||
// CreateQuotaRepository 创建配额仓储
|
||
func (f *AdapterFactory) CreateQuotaRepository() (repository.QuotaRepository, error) {
|
||
if f.mode == ModeMock {
|
||
return nil, fmt.Errorf("quota repository mock not implemented")
|
||
}
|
||
|
||
// 默认:真实实现(PostgreSQL)
|
||
if err := f.ensureDBConnection(); err != nil {
|
||
return nil, err
|
||
}
|
||
return postgres.NewQuotaRepository(f.db), nil
|
||
}
|
||
|
||
// CreateChartReferenceRepository 创建 Chart 引用仓储
|
||
func (f *AdapterFactory) CreateChartReferenceRepository() (repository.ChartReferenceRepository, error) {
|
||
if f.mode == ModeMock {
|
||
return nil, fmt.Errorf("chart reference repository mock not implemented")
|
||
}
|
||
|
||
if err := f.ensureDBConnection(); err != nil {
|
||
return nil, err
|
||
}
|
||
return postgres.NewChartReferenceRepository(f.db), nil
|
||
}
|
||
|
||
// CreateValuesTemplateRepository 创建 Values 模板仓储
|
||
func (f *AdapterFactory) CreateValuesTemplateRepository() (repository.ValuesTemplateRepository, error) {
|
||
if f.mode == ModeMock {
|
||
return nil, fmt.Errorf("values template repository mock not implemented")
|
||
}
|
||
|
||
if err := f.ensureDBConnection(); err != nil {
|
||
return nil, err
|
||
}
|
||
return postgres.NewValuesTemplateRepository(f.db), nil
|
||
}
|
||
|
||
// CreateAllRepositories 一次性创建所有 Repositories
|
||
func (f *AdapterFactory) CreateAllRepositories() (*Repositories, error) {
|
||
userRepo, err := f.CreateUserRepository()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create user repository: %w", err)
|
||
}
|
||
|
||
clusterRepo, err := f.CreateClusterRepository()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create cluster repository: %w", err)
|
||
}
|
||
|
||
registryRepo, err := f.CreateRegistryRepository()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create registry repository: %w", err)
|
||
}
|
||
|
||
instanceRepo, err := f.CreateInstanceRepository()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create instance repository: %w", err)
|
||
}
|
||
|
||
workspaceRepo, err := f.CreateWorkspaceRepository()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create workspace repository: %w", err)
|
||
}
|
||
|
||
storageRepo, err := f.CreateStorageRepository()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create storage repository: %w", err)
|
||
}
|
||
|
||
quotaRepo, err := f.CreateQuotaRepository()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create quota repository: %w", err)
|
||
}
|
||
|
||
ociClient, err := f.CreateOCIClient()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create OCI client: %w", err)
|
||
}
|
||
|
||
helmClient, err := f.CreateHelmClient()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create Helm client: %w", err)
|
||
}
|
||
|
||
// 创建 Metrics client(依赖 clusterRepo)
|
||
metricsClient := f.CreateMetricsClient(clusterRepo)
|
||
entryClient := f.CreateEntryClient()
|
||
|
||
chartRefRepo, err := f.CreateChartReferenceRepository()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create chart reference repository: %w", err)
|
||
}
|
||
|
||
valuesTemplateRepo, err := f.CreateValuesTemplateRepository()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create values template repository: %w", err)
|
||
}
|
||
|
||
return &Repositories{
|
||
UserRepo: userRepo,
|
||
ClusterRepo: clusterRepo,
|
||
RegistryRepo: registryRepo,
|
||
InstanceRepo: instanceRepo,
|
||
WorkspaceRepo: workspaceRepo,
|
||
StorageRepo: storageRepo,
|
||
ChartRefRepo: chartRefRepo,
|
||
ValuesTemplateRepo: valuesTemplateRepo,
|
||
QuotaRepo: quotaRepo,
|
||
OCIClient: ociClient,
|
||
HelmClient: helmClient,
|
||
MetricsClient: metricsClient,
|
||
EntryClient: entryClient,
|
||
}, nil
|
||
}
|
||
|
||
// Repositories 所有仓储的集合
|
||
type Repositories struct {
|
||
UserRepo repository.UserRepository
|
||
ClusterRepo repository.ClusterRepository
|
||
RegistryRepo repository.RegistryRepository
|
||
InstanceRepo repository.InstanceRepository
|
||
WorkspaceRepo repository.WorkspaceRepository
|
||
StorageRepo repository.StorageRepository
|
||
ChartRefRepo repository.ChartReferenceRepository
|
||
ValuesTemplateRepo repository.ValuesTemplateRepository
|
||
QuotaRepo repository.QuotaRepository
|
||
OCIClient repository.OCIClient
|
||
HelmClient repository.HelmClient
|
||
MetricsClient repository.MetricsClient
|
||
EntryClient repository.InstanceEntryClient
|
||
}
|
||
|
||
// ensureDBConnection 确保数据库连接已建立
|
||
func (f *AdapterFactory) ensureDBConnection() error {
|
||
if f.db != nil {
|
||
return nil
|
||
}
|
||
|
||
if f.dbConnString == "" {
|
||
return fmt.Errorf("database connection string is required (set DATABASE_URL environment variable)")
|
||
}
|
||
|
||
db, err := postgres.NewDB(f.dbConnString)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to connect to database: %w", err)
|
||
}
|
||
|
||
// 初始化数据库 schema
|
||
if err := db.InitSchema(); err != nil {
|
||
return fmt.Errorf("failed to initialize database schema: %w", err)
|
||
}
|
||
|
||
f.db = db
|
||
return nil
|
||
}
|
||
|
||
// Close 关闭工厂资源
|
||
func (f *AdapterFactory) Close() error {
|
||
if f.db != nil {
|
||
return f.db.Close()
|
||
}
|
||
return nil
|
||
}
|