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.
240 lines
6.3 KiB
Go
240 lines
6.3 KiB
Go
package entity
|
||
|
||
import (
|
||
"strings"
|
||
"time"
|
||
"unicode"
|
||
)
|
||
|
||
// InstanceStatus 实例状态
|
||
type InstanceStatus string
|
||
|
||
const (
|
||
StatusDeployed InstanceStatus = "deployed"
|
||
StatusUninstalled InstanceStatus = "uninstalled"
|
||
StatusSuperseded InstanceStatus = "superseded"
|
||
StatusFailed InstanceStatus = "failed"
|
||
StatusPending InstanceStatus = "pending-install"
|
||
StatusUpgrading InstanceStatus = "pending-upgrade"
|
||
StatusRollingBack InstanceStatus = "pending-rollback"
|
||
StatusTerminating InstanceStatus = "pending-delete"
|
||
StatusUnknown InstanceStatus = "unknown"
|
||
)
|
||
|
||
// InstanceOperation 实例操作类型
|
||
type InstanceOperation string
|
||
|
||
const (
|
||
OperationNone InstanceOperation = ""
|
||
OperationInstall InstanceOperation = "install"
|
||
OperationUpgrade InstanceOperation = "upgrade"
|
||
OperationRollback InstanceOperation = "rollback"
|
||
OperationDelete InstanceOperation = "delete"
|
||
OperationSync InstanceOperation = "sync"
|
||
)
|
||
|
||
// Instance Helm 应用实例领域实体
|
||
type Instance struct {
|
||
ID string
|
||
WorkspaceID string // 所属 workspace
|
||
OwnerID string // 创建者用户 ID
|
||
ClusterID string
|
||
RegistryID string
|
||
ChartReferenceID string // 引用的 Chart 引用
|
||
ValuesTemplateID string // 使用的 Values 模板
|
||
|
||
Name string // Helm Release Name
|
||
Namespace string
|
||
Repository string // OCI Repository (e.g., charts/app)
|
||
Chart string // Chart Name
|
||
Version string // Chart Version
|
||
Description string
|
||
Values map[string]interface{} // Helm Values (JSON)
|
||
ValuesYAML string // Helm Values (YAML format)
|
||
UserOverrideYAML string // 用户额外覆盖的配置
|
||
|
||
Status InstanceStatus
|
||
StatusReason string
|
||
LastOperation InstanceOperation
|
||
LastError string
|
||
Revision int // Helm Release Revision
|
||
|
||
// 资源使用统计(Helm 安装时从集群获取并更新)
|
||
CPURequested float64 // CPU 请求量 (cores)
|
||
MemoryRequested string // 内存请求量 (e.g., "2Gi")
|
||
GPURequested float64 // GPU 请求量 (cards)
|
||
GPUMemoryRequested string // GPU 内存请求量 (e.g., "16Gi")
|
||
|
||
CreatedAt time.Time
|
||
UpdatedAt time.Time
|
||
}
|
||
|
||
// NewInstance 创建新实例
|
||
func NewInstance(workspaceID, ownerID, clusterID, registryID, chartReferenceID, valuesTemplateID, name, namespace, repository, chart, version string) *Instance {
|
||
now := time.Now()
|
||
return &Instance{
|
||
WorkspaceID: workspaceID,
|
||
OwnerID: ownerID,
|
||
ClusterID: clusterID,
|
||
RegistryID: registryID,
|
||
ChartReferenceID: chartReferenceID,
|
||
ValuesTemplateID: valuesTemplateID,
|
||
Name: name,
|
||
Namespace: namespace,
|
||
Repository: repository,
|
||
Chart: chart,
|
||
Version: version,
|
||
Status: StatusPending,
|
||
StatusReason: "Pending install",
|
||
LastOperation: OperationInstall,
|
||
Revision: 1,
|
||
CPURequested: 0,
|
||
MemoryRequested: "0Mi",
|
||
GPURequested: 0,
|
||
GPUMemoryRequested: "0Mi",
|
||
CreatedAt: now,
|
||
UpdatedAt: now,
|
||
}
|
||
}
|
||
|
||
// SetValues 设置 Helm Values
|
||
func (i *Instance) SetValues(values map[string]interface{}) {
|
||
i.Values = values
|
||
i.UpdatedAt = time.Now()
|
||
}
|
||
|
||
// SetValuesYAML 设置 YAML 格式的 Values
|
||
func (i *Instance) SetValuesYAML(yaml string) {
|
||
i.ValuesYAML = yaml
|
||
i.UpdatedAt = time.Now()
|
||
}
|
||
|
||
// UpdateStatus 更新实例状态
|
||
func (i *Instance) UpdateStatus(status InstanceStatus, revision int) {
|
||
i.Status = status
|
||
i.Revision = revision
|
||
i.UpdatedAt = time.Now()
|
||
}
|
||
|
||
// BeginOperation 标记开始执行某个操作
|
||
func (i *Instance) BeginOperation(op InstanceOperation, reason string) {
|
||
i.LastOperation = op
|
||
if pendingStatus := pendingStatusForOperation(op); pendingStatus != "" {
|
||
i.Status = pendingStatus
|
||
}
|
||
if reason != "" {
|
||
i.StatusReason = reason
|
||
}
|
||
i.LastError = ""
|
||
i.UpdatedAt = time.Now()
|
||
}
|
||
|
||
// MarkSuccess 标记操作成功
|
||
func (i *Instance) MarkSuccess(status InstanceStatus, revision int, reason string) {
|
||
if status != "" {
|
||
i.Status = status
|
||
}
|
||
if revision > 0 {
|
||
i.Revision = revision
|
||
}
|
||
i.StatusReason = reason
|
||
i.LastError = ""
|
||
i.UpdatedAt = time.Now()
|
||
}
|
||
|
||
// MarkFailure 标记操作失败
|
||
func (i *Instance) MarkFailure(reason string, err error) {
|
||
i.Status = StatusFailed
|
||
if reason != "" {
|
||
i.StatusReason = reason
|
||
}
|
||
if err != nil {
|
||
i.LastError = err.Error()
|
||
}
|
||
i.UpdatedAt = time.Now()
|
||
}
|
||
|
||
func pendingStatusForOperation(op InstanceOperation) InstanceStatus {
|
||
switch op {
|
||
case OperationInstall:
|
||
return StatusPending
|
||
case OperationUpgrade:
|
||
return StatusUpgrading
|
||
case OperationRollback:
|
||
return StatusRollingBack
|
||
case OperationDelete:
|
||
return StatusTerminating
|
||
default:
|
||
return ""
|
||
}
|
||
}
|
||
|
||
// Upgrade 升级实例
|
||
func (i *Instance) Upgrade(version string, values map[string]interface{}) {
|
||
i.Version = version
|
||
if values != nil {
|
||
i.Values = values
|
||
}
|
||
i.BeginOperation(OperationUpgrade, "Pending upgrade")
|
||
}
|
||
|
||
// ValidateReleaseName 验证 Helm Release 名称是否符合 RFC 1123 DNS 子域名规范
|
||
// Helm release 名称必须:
|
||
// - 只能包含小写字母(a-z)、数字(0-9)和连字符(-)
|
||
// - 不能以连字符开头或结尾
|
||
// - 长度不超过 53 个字符
|
||
func ValidateReleaseName(name string) error {
|
||
if name == "" {
|
||
return ErrInvalidInstanceName
|
||
}
|
||
|
||
// 检查长度(RFC 1123 DNS 子域名最大长度为 63,但 Helm 限制为 53)
|
||
if len(name) > 53 {
|
||
return ErrInvalidInstanceName
|
||
}
|
||
|
||
// 不能以连字符开头或结尾
|
||
if strings.HasPrefix(name, "-") || strings.HasSuffix(name, "-") {
|
||
return ErrInvalidInstanceName
|
||
}
|
||
|
||
// 只能包含小写字母、数字和连字符
|
||
for _, r := range name {
|
||
if !(unicode.IsLower(r) || unicode.IsDigit(r) || r == '-') {
|
||
return ErrInvalidInstanceName
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// Validate 验证实例配置
|
||
func (i *Instance) Validate() error {
|
||
if i.ClusterID == "" {
|
||
return ErrInvalidClusterID
|
||
}
|
||
if err := ValidateReleaseName(i.Name); err != nil {
|
||
return err
|
||
}
|
||
if i.Namespace == "" {
|
||
return ErrInvalidNamespace
|
||
}
|
||
if i.Chart == "" {
|
||
return ErrInvalidChart
|
||
}
|
||
if i.Version == "" {
|
||
return ErrInvalidVersion
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ReleaseHistory Helm Release 历史记录
|
||
type ReleaseHistory struct {
|
||
Revision int
|
||
Updated time.Time
|
||
Status InstanceStatus
|
||
Chart string
|
||
AppVersion string
|
||
Description string
|
||
}
|