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:
@ -32,6 +32,11 @@ func (r *ClusterRepository) Create(ctx context.Context, cluster *entity.Cluster)
|
||||
cluster.ID = uuid.New().String()
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if cluster.IsolationMode == "" {
|
||||
cluster.IsolationMode = entity.IsolationModeNamespace
|
||||
}
|
||||
|
||||
// 加密敏感数据
|
||||
encryptedCAData, err := r.encryptor.Encrypt(cluster.CAData)
|
||||
if err != nil {
|
||||
@ -54,12 +59,14 @@ func (r *ClusterRepository) Create(ctx context.Context, cluster *entity.Cluster)
|
||||
}
|
||||
|
||||
query := `
|
||||
INSERT INTO clusters (id, name, host, ca_data, cert_data, key_data, token, description, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
INSERT INTO clusters (id, workspace_id, owner_id, name, host, ca_data, cert_data, key_data, token, description, isolation_mode, default_namespace, is_shared, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
|
||||
`
|
||||
|
||||
_, err = r.db.conn.ExecContext(ctx, query,
|
||||
cluster.ID,
|
||||
cluster.WorkspaceID,
|
||||
cluster.OwnerID,
|
||||
cluster.Name,
|
||||
cluster.Host,
|
||||
encryptedCAData,
|
||||
@ -67,6 +74,9 @@ func (r *ClusterRepository) Create(ctx context.Context, cluster *entity.Cluster)
|
||||
encryptedKeyData,
|
||||
encryptedToken,
|
||||
cluster.Description,
|
||||
cluster.IsolationMode,
|
||||
cluster.DefaultNamespace,
|
||||
cluster.IsShared,
|
||||
cluster.CreatedAt,
|
||||
cluster.UpdatedAt,
|
||||
)
|
||||
@ -81,7 +91,7 @@ func (r *ClusterRepository) Create(ctx context.Context, cluster *entity.Cluster)
|
||||
// GetByID 根据 ID 获取集群
|
||||
func (r *ClusterRepository) GetByID(ctx context.Context, id string) (*entity.Cluster, error) {
|
||||
query := `
|
||||
SELECT id, name, host, ca_data, cert_data, key_data, token, description, created_at, updated_at
|
||||
SELECT id, workspace_id, owner_id, name, host, ca_data, cert_data, key_data, token, description, isolation_mode, default_namespace, is_shared, created_at, updated_at
|
||||
FROM clusters
|
||||
WHERE id = $1
|
||||
`
|
||||
@ -91,6 +101,8 @@ func (r *ClusterRepository) GetByID(ctx context.Context, id string) (*entity.Clu
|
||||
|
||||
err := r.db.conn.QueryRowContext(ctx, query, id).Scan(
|
||||
&cluster.ID,
|
||||
&cluster.WorkspaceID,
|
||||
&cluster.OwnerID,
|
||||
&cluster.Name,
|
||||
&cluster.Host,
|
||||
&encryptedCAData,
|
||||
@ -98,6 +110,9 @@ func (r *ClusterRepository) GetByID(ctx context.Context, id string) (*entity.Clu
|
||||
&encryptedKeyData,
|
||||
&encryptedToken,
|
||||
&cluster.Description,
|
||||
&cluster.IsolationMode,
|
||||
&cluster.DefaultNamespace,
|
||||
&cluster.IsShared,
|
||||
&cluster.CreatedAt,
|
||||
&cluster.UpdatedAt,
|
||||
)
|
||||
@ -110,25 +125,10 @@ func (r *ClusterRepository) GetByID(ctx context.Context, id string) (*entity.Clu
|
||||
}
|
||||
|
||||
// 解密敏感数据
|
||||
cluster.CAData, err = r.encryptor.Decrypt(encryptedCAData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt CA data: %w", err)
|
||||
}
|
||||
|
||||
cluster.CertData, err = r.encryptor.Decrypt(encryptedCertData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt cert data: %w", err)
|
||||
}
|
||||
|
||||
cluster.KeyData, err = r.encryptor.Decrypt(encryptedKeyData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt key data: %w", err)
|
||||
}
|
||||
|
||||
cluster.Token, err = r.encryptor.Decrypt(encryptedToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt token: %w", err)
|
||||
}
|
||||
cluster.CAData, _ = r.encryptor.Decrypt(encryptedCAData)
|
||||
cluster.CertData, _ = r.encryptor.Decrypt(encryptedCertData)
|
||||
cluster.KeyData, _ = r.encryptor.Decrypt(encryptedKeyData)
|
||||
cluster.Token, _ = r.encryptor.Decrypt(encryptedToken)
|
||||
|
||||
return cluster, nil
|
||||
}
|
||||
@ -136,7 +136,7 @@ func (r *ClusterRepository) GetByID(ctx context.Context, id string) (*entity.Clu
|
||||
// GetByName 根据名称获取集群
|
||||
func (r *ClusterRepository) GetByName(ctx context.Context, name string) (*entity.Cluster, error) {
|
||||
query := `
|
||||
SELECT id, name, host, ca_data, cert_data, key_data, token, description, created_at, updated_at
|
||||
SELECT id, workspace_id, owner_id, name, host, ca_data, cert_data, key_data, token, description, isolation_mode, default_namespace, is_shared, created_at, updated_at
|
||||
FROM clusters
|
||||
WHERE name = $1
|
||||
`
|
||||
@ -146,6 +146,8 @@ func (r *ClusterRepository) GetByName(ctx context.Context, name string) (*entity
|
||||
|
||||
err := r.db.conn.QueryRowContext(ctx, query, name).Scan(
|
||||
&cluster.ID,
|
||||
&cluster.WorkspaceID,
|
||||
&cluster.OwnerID,
|
||||
&cluster.Name,
|
||||
&cluster.Host,
|
||||
&encryptedCAData,
|
||||
@ -153,6 +155,9 @@ func (r *ClusterRepository) GetByName(ctx context.Context, name string) (*entity
|
||||
&encryptedKeyData,
|
||||
&encryptedToken,
|
||||
&cluster.Description,
|
||||
&cluster.IsolationMode,
|
||||
&cluster.DefaultNamespace,
|
||||
&cluster.IsShared,
|
||||
&cluster.CreatedAt,
|
||||
&cluster.UpdatedAt,
|
||||
)
|
||||
@ -165,25 +170,10 @@ func (r *ClusterRepository) GetByName(ctx context.Context, name string) (*entity
|
||||
}
|
||||
|
||||
// 解密敏感数据
|
||||
cluster.CAData, err = r.encryptor.Decrypt(encryptedCAData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt CA data: %w", err)
|
||||
}
|
||||
|
||||
cluster.CertData, err = r.encryptor.Decrypt(encryptedCertData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt cert data: %w", err)
|
||||
}
|
||||
|
||||
cluster.KeyData, err = r.encryptor.Decrypt(encryptedKeyData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt key data: %w", err)
|
||||
}
|
||||
|
||||
cluster.Token, err = r.encryptor.Decrypt(encryptedToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt token: %w", err)
|
||||
}
|
||||
cluster.CAData, _ = r.encryptor.Decrypt(encryptedCAData)
|
||||
cluster.CertData, _ = r.encryptor.Decrypt(encryptedCertData)
|
||||
cluster.KeyData, _ = r.encryptor.Decrypt(encryptedKeyData)
|
||||
cluster.Token, _ = r.encryptor.Decrypt(encryptedToken)
|
||||
|
||||
return cluster, nil
|
||||
}
|
||||
@ -215,9 +205,10 @@ func (r *ClusterRepository) Update(ctx context.Context, cluster *entity.Cluster)
|
||||
|
||||
query := `
|
||||
UPDATE clusters
|
||||
SET name = $1, host = $2, ca_data = $3, cert_data = $4, key_data = $5,
|
||||
token = $6, description = $7, updated_at = $8
|
||||
WHERE id = $9
|
||||
SET name = $1, host = $2, ca_data = $3, cert_data = $4, key_data = $5,
|
||||
token = $6, description = $7, isolation_mode = $8, default_namespace = $9,
|
||||
is_shared = $10, updated_at = $11
|
||||
WHERE id = $12
|
||||
`
|
||||
|
||||
result, err := r.db.conn.ExecContext(ctx, query,
|
||||
@ -228,6 +219,9 @@ func (r *ClusterRepository) Update(ctx context.Context, cluster *entity.Cluster)
|
||||
encryptedKeyData,
|
||||
encryptedToken,
|
||||
cluster.Description,
|
||||
cluster.IsolationMode,
|
||||
cluster.DefaultNamespace,
|
||||
cluster.IsShared,
|
||||
cluster.UpdatedAt,
|
||||
cluster.ID,
|
||||
)
|
||||
@ -272,7 +266,7 @@ func (r *ClusterRepository) Delete(ctx context.Context, id string) error {
|
||||
// List 列出所有集群
|
||||
func (r *ClusterRepository) List(ctx context.Context) ([]*entity.Cluster, error) {
|
||||
query := `
|
||||
SELECT id, name, host, ca_data, cert_data, key_data, token, description, created_at, updated_at
|
||||
SELECT id, workspace_id, owner_id, name, host, ca_data, cert_data, key_data, token, description, isolation_mode, default_namespace, is_shared, created_at, updated_at
|
||||
FROM clusters
|
||||
ORDER BY created_at DESC
|
||||
`
|
||||
@ -283,13 +277,59 @@ func (r *ClusterRepository) List(ctx context.Context) ([]*entity.Cluster, error)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
return r.scanClusters(rows)
|
||||
}
|
||||
|
||||
// GetByWorkspace 获取 workspace 的所有集群(包括共享集群)
|
||||
func (r *ClusterRepository) GetByWorkspace(ctx context.Context, workspaceID string) ([]*entity.Cluster, error) {
|
||||
query := `
|
||||
SELECT id, workspace_id, owner_id, name, host, ca_data, cert_data, key_data, token, description, isolation_mode, default_namespace, is_shared, created_at, updated_at
|
||||
FROM clusters
|
||||
WHERE workspace_id = $1 OR is_shared = TRUE
|
||||
ORDER BY is_shared, created_at DESC
|
||||
`
|
||||
|
||||
rows, err := r.db.conn.QueryContext(ctx, query, workspaceID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list clusters by workspace: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
return r.scanClusters(rows)
|
||||
}
|
||||
|
||||
// GetShared 获取所有共享集群
|
||||
func (r *ClusterRepository) GetShared(ctx context.Context) ([]*entity.Cluster, error) {
|
||||
query := `
|
||||
SELECT id, workspace_id, owner_id, name, host, ca_data, cert_data, key_data, token, description, isolation_mode, default_namespace, is_shared, created_at, updated_at
|
||||
FROM clusters
|
||||
WHERE is_shared = TRUE
|
||||
ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
rows, err := r.db.conn.QueryContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list shared clusters: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
return r.scanClusters(rows)
|
||||
}
|
||||
|
||||
// scanClusters 扫描多行结果
|
||||
func (r *ClusterRepository) scanClusters(rows *sql.Rows) ([]*entity.Cluster, error) {
|
||||
clusters := make([]*entity.Cluster, 0)
|
||||
for rows.Next() {
|
||||
cluster := &entity.Cluster{}
|
||||
var encryptedCAData, encryptedCertData, encryptedKeyData, encryptedToken string
|
||||
var (
|
||||
encryptedCAData, encryptedCertData, encryptedKeyData, encryptedToken sql.NullString
|
||||
workspaceID, ownerID, defaultNamespace sql.NullString
|
||||
)
|
||||
|
||||
err := rows.Scan(
|
||||
&cluster.ID,
|
||||
&workspaceID,
|
||||
&ownerID,
|
||||
&cluster.Name,
|
||||
&cluster.Host,
|
||||
&encryptedCAData,
|
||||
@ -297,6 +337,9 @@ func (r *ClusterRepository) List(ctx context.Context) ([]*entity.Cluster, error)
|
||||
&encryptedKeyData,
|
||||
&encryptedToken,
|
||||
&cluster.Description,
|
||||
&cluster.IsolationMode,
|
||||
&defaultNamespace,
|
||||
&cluster.IsShared,
|
||||
&cluster.CreatedAt,
|
||||
&cluster.UpdatedAt,
|
||||
)
|
||||
@ -304,25 +347,23 @@ func (r *ClusterRepository) List(ctx context.Context) ([]*entity.Cluster, error)
|
||||
return nil, fmt.Errorf("failed to scan cluster: %w", err)
|
||||
}
|
||||
|
||||
// 处理 NULL 值
|
||||
cluster.WorkspaceID = workspaceID.String
|
||||
cluster.OwnerID = ownerID.String
|
||||
cluster.DefaultNamespace = defaultNamespace.String
|
||||
|
||||
// 解密敏感数据
|
||||
cluster.CAData, err = r.encryptor.Decrypt(encryptedCAData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt CA data: %w", err)
|
||||
if encryptedCAData.Valid {
|
||||
cluster.CAData, _ = r.encryptor.Decrypt(encryptedCAData.String)
|
||||
}
|
||||
|
||||
cluster.CertData, err = r.encryptor.Decrypt(encryptedCertData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt cert data: %w", err)
|
||||
if encryptedCertData.Valid {
|
||||
cluster.CertData, _ = r.encryptor.Decrypt(encryptedCertData.String)
|
||||
}
|
||||
|
||||
cluster.KeyData, err = r.encryptor.Decrypt(encryptedKeyData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt key data: %w", err)
|
||||
if encryptedKeyData.Valid {
|
||||
cluster.KeyData, _ = r.encryptor.Decrypt(encryptedKeyData.String)
|
||||
}
|
||||
|
||||
cluster.Token, err = r.encryptor.Decrypt(encryptedToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt token: %w", err)
|
||||
if encryptedToken.Valid {
|
||||
cluster.Token, _ = r.encryptor.Decrypt(encryptedToken.String)
|
||||
}
|
||||
|
||||
clusters = append(clusters, cluster)
|
||||
|
||||
Reference in New Issue
Block a user