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:
@ -104,13 +104,41 @@ func (r *ClusterRepositoryMock) Delete(ctx context.Context, id string) error {
|
||||
func (r *ClusterRepositoryMock) List(ctx context.Context) ([]*entity.Cluster, error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
|
||||
clusters := make([]*entity.Cluster, 0, len(r.clusters))
|
||||
for _, cluster := range r.clusters {
|
||||
// 解密敏感数据后返回
|
||||
clusters = append(clusters, r.decryptCluster(cluster))
|
||||
}
|
||||
|
||||
|
||||
return clusters, nil
|
||||
}
|
||||
|
||||
func (r *ClusterRepositoryMock) GetByWorkspace(ctx context.Context, workspaceID string) ([]*entity.Cluster, error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
clusters := make([]*entity.Cluster, 0)
|
||||
for _, cluster := range r.clusters {
|
||||
if cluster.WorkspaceID == workspaceID {
|
||||
clusters = append(clusters, r.decryptCluster(cluster))
|
||||
}
|
||||
}
|
||||
|
||||
return clusters, nil
|
||||
}
|
||||
|
||||
func (r *ClusterRepositoryMock) GetShared(ctx context.Context) ([]*entity.Cluster, error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
clusters := make([]*entity.Cluster, 0)
|
||||
for _, cluster := range r.clusters {
|
||||
if cluster.IsShared {
|
||||
clusters = append(clusters, r.decryptCluster(cluster))
|
||||
}
|
||||
}
|
||||
|
||||
return clusters, nil
|
||||
}
|
||||
|
||||
|
||||
@ -102,12 +102,26 @@ func (r *InstanceRepositoryMock) ListByCluster(ctx context.Context, clusterID st
|
||||
func (r *InstanceRepositoryMock) List(ctx context.Context) ([]*entity.Instance, error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
|
||||
instances := make([]*entity.Instance, 0, len(r.instances))
|
||||
for _, instance := range r.instances {
|
||||
instances = append(instances, instance)
|
||||
}
|
||||
|
||||
|
||||
return instances, nil
|
||||
}
|
||||
|
||||
func (r *InstanceRepositoryMock) GetByWorkspace(ctx context.Context, workspaceID string) ([]*entity.Instance, error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
instances := make([]*entity.Instance, 0)
|
||||
for _, instance := range r.instances {
|
||||
if instance.WorkspaceID == workspaceID {
|
||||
instances = append(instances, instance)
|
||||
}
|
||||
}
|
||||
|
||||
return instances, nil
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,96 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ocdp/cluster-service/internal/domain/entity"
|
||||
)
|
||||
|
||||
// StorageRepositoryMock Storage 仓储 Mock
|
||||
type StorageRepositoryMock struct {
|
||||
storages map[string]*entity.StorageBackend
|
||||
}
|
||||
|
||||
// NewStorageRepositoryMock 创建 Mock
|
||||
func NewStorageRepositoryMock() *StorageRepositoryMock {
|
||||
return &StorageRepositoryMock{
|
||||
storages: make(map[string]*entity.StorageBackend),
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建存储
|
||||
func (r *StorageRepositoryMock) Create(ctx context.Context, storage *entity.StorageBackend) error {
|
||||
r.storages[storage.ID] = storage
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByID 获取存储
|
||||
func (r *StorageRepositoryMock) GetByID(ctx context.Context, id string) (*entity.StorageBackend, error) {
|
||||
if s, ok := r.storages[id]; ok {
|
||||
return s, nil
|
||||
}
|
||||
return nil, entity.ErrStorageNotFound
|
||||
}
|
||||
|
||||
// GetByWorkspace 获取工作空间的存储
|
||||
func (r *StorageRepositoryMock) GetByWorkspace(ctx context.Context, workspaceID string) ([]*entity.StorageBackend, error) {
|
||||
var result []*entity.StorageBackend
|
||||
for _, s := range r.storages {
|
||||
if s.WorkspaceID == workspaceID {
|
||||
result = append(result, s)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetByName 按名称获取
|
||||
func (r *StorageRepositoryMock) GetByName(ctx context.Context, workspaceID, name string) (*entity.StorageBackend, error) {
|
||||
for _, s := range r.storages {
|
||||
if s.WorkspaceID == workspaceID && s.Name == name {
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
return nil, entity.ErrStorageNotFound
|
||||
}
|
||||
|
||||
// Update 更新存储
|
||||
func (r *StorageRepositoryMock) Update(ctx context.Context, storage *entity.StorageBackend) error {
|
||||
r.storages[storage.ID] = storage
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除存储
|
||||
func (r *StorageRepositoryMock) Delete(ctx context.Context, id string) error {
|
||||
delete(r.storages, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetShared 获取所有共享存储后端
|
||||
func (r *StorageRepositoryMock) GetShared(ctx context.Context) ([]*entity.StorageBackend, error) {
|
||||
var result []*entity.StorageBackend
|
||||
for _, s := range r.storages {
|
||||
if s.IsShared {
|
||||
result = append(result, s)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetDefault 获取 workspace 的默认存储后端
|
||||
func (r *StorageRepositoryMock) GetDefault(ctx context.Context, workspaceID string) (*entity.StorageBackend, error) {
|
||||
for _, s := range r.storages {
|
||||
if s.WorkspaceID == workspaceID && s.IsDefault {
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
return nil, entity.ErrStorageNotFound
|
||||
}
|
||||
|
||||
// List 列出所有存储(管理员用)
|
||||
func (r *StorageRepositoryMock) List(ctx context.Context) ([]*entity.StorageBackend, error) {
|
||||
var result []*entity.StorageBackend
|
||||
for _, s := range r.storages {
|
||||
result = append(result, s)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@ -88,12 +88,40 @@ func (r *UserRepositoryMock) Delete(ctx context.Context, id string) error {
|
||||
func (r *UserRepositoryMock) List(ctx context.Context) ([]*entity.User, error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
|
||||
users := make([]*entity.User, 0, len(r.users))
|
||||
for _, user := range r.users {
|
||||
users = append(users, user)
|
||||
}
|
||||
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (r *UserRepositoryMock) ListByWorkspace(ctx context.Context, workspaceID string) ([]*entity.User, error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
users := make([]*entity.User, 0)
|
||||
for _, user := range r.users {
|
||||
if user.WorkspaceID == workspaceID {
|
||||
users = append(users, user)
|
||||
}
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (r *UserRepositoryMock) ListActive(ctx context.Context) ([]*entity.User, error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
users := make([]*entity.User, 0)
|
||||
for _, user := range r.users {
|
||||
if user.IsActive {
|
||||
users = append(users, user)
|
||||
}
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user