- Add Workspace domain (entity, repository, service, handler, DTO) - Add multi-tenant K8s client with tenant binding and quota management - Add K8s diagnostics client (instance diagnostics) - Add authorization middleware (authz package) - Restructure frontend to feature-based architecture (features/) - Add User Management page in configuration - Add AccessDenied page and route guards - Refactor shared components (form inputs, layout, UI) - Update Tailwind config for new design system - Add comprehensive documentation (docs/, tasks/, plans) - Improve cluster service with better kubeconfig handling - Add tests for crypto, config, helm client, tenant binding
151 lines
3.1 KiB
Go
151 lines
3.1 KiB
Go
package entity
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
DefaultWorkspaceID = "00000000-0000-0000-0000-000000000010"
|
|
DefaultWorkspaceName = "default"
|
|
)
|
|
|
|
type WorkspaceStatus string
|
|
|
|
const (
|
|
WorkspaceActive WorkspaceStatus = "active"
|
|
WorkspaceSuspended WorkspaceStatus = "suspended"
|
|
)
|
|
|
|
type Workspace struct {
|
|
ID string
|
|
Name string
|
|
Status WorkspaceStatus
|
|
K8sNamespace string
|
|
K8sSAName string
|
|
DefaultClusterID string
|
|
QuotaCPU string
|
|
QuotaMemory string
|
|
QuotaGPU string
|
|
QuotaGPUMem string
|
|
CreatedBy string
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
func NewWorkspace(name, createdBy string) *Workspace {
|
|
now := time.Now()
|
|
return &Workspace{
|
|
Name: name,
|
|
Status: WorkspaceActive,
|
|
K8sNamespace: NamespaceForWorkspace(name),
|
|
K8sSAName: ServiceAccountForWorkspace(name),
|
|
CreatedBy: createdBy,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
}
|
|
|
|
func NamespaceForWorkspace(name string) string {
|
|
if name == "" {
|
|
name = DefaultWorkspaceName
|
|
}
|
|
return prefixedDNSLabel("ocdp-ws-", name)
|
|
}
|
|
|
|
func NamespaceForUser(username string) string {
|
|
if username == "" {
|
|
username = "user"
|
|
}
|
|
return prefixedDNSLabel("ocdp-u-", username)
|
|
}
|
|
|
|
func ServiceAccountForWorkspace(name string) string {
|
|
if name == "" {
|
|
name = DefaultWorkspaceName
|
|
}
|
|
return prefixedDNSLabel("ocdp-ws-", name)
|
|
}
|
|
|
|
func ServiceAccountForNamespace(namespace string) string {
|
|
if namespace == "" {
|
|
namespace = DefaultWorkspaceName
|
|
}
|
|
return prefixedDNSLabel("ocdp-sa-", namespace)
|
|
}
|
|
|
|
func prefixedDNSLabel(prefix, value string) string {
|
|
label := normalizeDNSLabel(value)
|
|
maxLabelLen := 63 - len(prefix)
|
|
if maxLabelLen < 1 {
|
|
maxLabelLen = 1
|
|
}
|
|
if len(label) > maxLabelLen {
|
|
label = strings.Trim(label[:maxLabelLen], "-")
|
|
}
|
|
if label == "" {
|
|
label = DefaultWorkspaceName
|
|
if len(label) > maxLabelLen {
|
|
label = label[:maxLabelLen]
|
|
}
|
|
}
|
|
return prefix + label
|
|
}
|
|
|
|
func normalizeDNSLabel(value string) string {
|
|
out := make([]rune, 0, len(value))
|
|
lastDash := false
|
|
for _, r := range value {
|
|
valid := (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9')
|
|
if r >= 'A' && r <= 'Z' {
|
|
r = r + ('a' - 'A')
|
|
valid = true
|
|
}
|
|
if valid {
|
|
out = append(out, r)
|
|
lastDash = false
|
|
continue
|
|
}
|
|
if !lastDash && len(out) > 0 {
|
|
out = append(out, '-')
|
|
lastDash = true
|
|
}
|
|
}
|
|
for len(out) > 0 && out[len(out)-1] == '-' {
|
|
out = out[:len(out)-1]
|
|
}
|
|
if len(out) == 0 {
|
|
return DefaultWorkspaceName
|
|
}
|
|
return string(out)
|
|
}
|
|
|
|
type WorkspaceClusterBinding struct {
|
|
ID string
|
|
WorkspaceID string
|
|
ClusterID string
|
|
Namespace string
|
|
ServiceAccount string
|
|
QuotaCPU string
|
|
QuotaMemory string
|
|
QuotaGPU string
|
|
QuotaGPUMem string
|
|
Status string
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
type AuditLog struct {
|
|
ID string
|
|
WorkspaceID string
|
|
UserID string
|
|
Action string
|
|
ResourceType string
|
|
ResourceID string
|
|
ResourceName string
|
|
Details map[string]interface{}
|
|
IPAddress string
|
|
UserAgent string
|
|
CreatedAt time.Time
|
|
}
|