feat: complete E2E deployment flow with storage layered config and values template versioning

- Instance deployment: charts browser, deploy modal, instances list
- Values Template version management (create/history/rollback)
- Storage layered config (cluster > workspace > shared priority)
- Cluster credential decryptIfNeeded for mixed encrypted/plaintext kubeconfig
- YAML syntax validation (client-side + server-side warning)
- Frontend: charts, instances, storage, templates, admin pages
- Backend: storage service, instance service, cluster service, helm client
- Multi-Tenant Kubeconfig.md: added by user
This commit is contained in:
Ivan087
2026-04-30 16:31:00 +08:00
parent 985369d40f
commit 47849042a7
42 changed files with 2029 additions and 255 deletions

View File

@ -1,9 +1,12 @@
package entity
import (
"log"
"strings"
"time"
"unicode"
"gopkg.in/yaml.v3"
)
// InstanceStatus 实例状态
@ -103,9 +106,31 @@ func (i *Instance) SetValues(values map[string]interface{}) {
i.UpdatedAt = time.Now()
}
// SetValuesYAML 设置 YAML 格式的 Values
func (i *Instance) SetValuesYAML(yaml string) {
i.ValuesYAML = yaml
// SetValuesYAML 设置 YAML 格式的 Values 并解析到 Values map
func (i *Instance) SetValuesYAML(yamlStr string) {
i.ValuesYAML = yamlStr
if yamlStr == "" {
return
}
// 解析 YAML 到 map确保 Helm 客户端能正确使用
var parsed map[string]interface{}
if err := yaml.Unmarshal([]byte(yamlStr), &parsed); err != nil {
log.Printf("[SetValuesYAML] WARNING: failed to parse YAML for instance %s: %s, yaml=%q", i.Name, err, yamlStr)
return
}
if parsed == nil {
return
}
// Merge into existing Values (user-provided takes precedence)
if i.Values == nil {
i.Values = make(map[string]interface{})
}
for k, v := range parsed {
// Only set if not already present (Values map takes precedence over YAML fallback)
if _, exists := i.Values[k]; !exists {
i.Values[k] = v
}
}
i.UpdatedAt = time.Now()
}

View File

@ -43,6 +43,7 @@ type HostPathConfig struct {
type StorageBackend struct {
ID string
WorkspaceID string
ClusterID string // 关联的 clusterNULL 表示 workspace/shared 级别
OwnerID string
Name string
Type StorageType
@ -70,6 +71,13 @@ func NewStorageBackend(workspaceID, ownerID, name string, storageType StorageTyp
}
}
// NewClusterStorageBackend 创建 cluster 级别的存储后端
func NewClusterStorageBackend(workspaceID, clusterID, ownerID, name string, storageType StorageType, config StorageConfig) *StorageBackend {
storage := NewStorageBackend(workspaceID, ownerID, name, storageType, config)
storage.ClusterID = clusterID
return storage
}
// Validate 验证存储后端数据
func (s *StorageBackend) Validate() error {
if s.Name == "" {

View File

@ -9,17 +9,19 @@ type Workspace struct {
ID string
Name string
Description string
CreatedBy string // 创建者用户 ID
ClusterIDs []string // 关联的集群 ID 列表
CreatedBy string // 创建者用户 ID
CreatedAt time.Time
UpdatedAt time.Time
}
// NewWorkspace 创建新工作空间
func NewWorkspace(name, description, createdBy string) *Workspace {
func NewWorkspace(name, description, createdBy string, clusterIDs []string) *Workspace {
now := time.Now()
return &Workspace{
Name: name,
Description: description,
ClusterIDs: clusterIDs,
CreatedBy: createdBy,
CreatedAt: now,
UpdatedAt: now,