refactor: full-stack restructure with multi-tenancy, workspace management, and K8s diagnostics

- 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
This commit is contained in:
Ivan087
2026-05-12 16:15:14 +08:00
parent c5e51ed069
commit 7f238a3168
172 changed files with 15703 additions and 3162 deletions

View File

@ -6,9 +6,9 @@ type RepositoryListResponse struct {
RegistryURL string `json:"registryUrl"`
Repositories []string `json:"repositories"`
Total int `json:"total"`
CatalogSupported bool `json:"catalogSupported"` // Whether _catalog API is supported
Source string `json:"source"` // Data source: "catalog" | "preconfigured" | "unavailable"
Message string `json:"message,omitempty"` // User-friendly message
CatalogSupported bool `json:"catalogSupported"` // Whether _catalog API is supported
Source string `json:"source"` // Data source: "catalog" | "preconfigured" | "unavailable"
Message string `json:"message,omitempty"` // User-friendly message
}
// ArtifactResponse Artifact 响应(简化版本,只包含核心字段)
@ -23,11 +23,11 @@ type ArtifactResponse struct {
// TagResponse Tag 响应(前端期望的扁平化结构)
type TagResponse struct {
RepositoryName string `json:"repositoryName"` // Repository name
Tag string `json:"tag"` // Tag name (e.g. "1.0.0", "latest")
Type string `json:"type"` // Artifact type: chart, image, other
RepositoryName string `json:"repositoryName"` // Repository name
Tag string `json:"tag"` // Tag name (e.g. "1.0.0", "latest")
Type string `json:"type"` // Artifact type: chart, image, other
MediaType string `json:"mediaType,omitempty"`
Size int64 `json:"size"` // Artifact size (bytes)
Size int64 `json:"size"` // Artifact size (bytes)
}
// ArtifactListResponse Artifact 列表响应(包装格式,用于详细接口)
@ -42,3 +42,7 @@ type ValuesSchemaResponse struct {
Schema string `json:"schema"`
}
// ValuesYAMLResponse Helm Chart 默认 values.yaml 响应
type ValuesYAMLResponse struct {
ValuesYAML string `json:"valuesYaml"`
}

View File

@ -2,8 +2,18 @@ package dto
// RegisterRequest 用户注册请求
type RegisterRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required,min=6"`
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required,min=6"`
Role string `json:"role,omitempty"`
WorkspaceID string `json:"workspaceId,omitempty"`
Namespace string `json:"namespace,omitempty"`
DefaultClusterID string `json:"defaultClusterId,omitempty"`
QuotaCPU string `json:"quotaCpu,omitempty"`
QuotaMemory string `json:"quotaMemory,omitempty"`
QuotaGPU string `json:"quotaGpu,omitempty"`
QuotaGPUMem string `json:"quotaGpuMemory,omitempty"`
IsActive *bool `json:"isActive,omitempty"`
MustChangePassword *bool `json:"mustChangePassword,omitempty"`
}
// LoginRequest 用户登录请求
@ -19,17 +29,53 @@ type RefreshTokenRequest struct {
// AuthResponse 认证响应
type AuthResponse struct {
AccessToken string `json:"accessToken"`
RefreshToken string `json:"refreshToken"`
UserID string `json:"userId"`
Username string `json:"username"`
AccessToken string `json:"accessToken"`
RefreshToken string `json:"refreshToken"`
UserID string `json:"userId"`
Username string `json:"username"`
Role string `json:"role"`
WorkspaceID string `json:"workspaceId"`
WorkspaceName string `json:"workspaceName,omitempty"`
Namespace string `json:"namespace,omitempty"`
DefaultClusterID string `json:"defaultClusterId,omitempty"`
QuotaCPU string `json:"quotaCpu,omitempty"`
QuotaMemory string `json:"quotaMemory,omitempty"`
QuotaGPU string `json:"quotaGpu,omitempty"`
QuotaGPUMem string `json:"quotaGpuMemory,omitempty"`
Permissions []string `json:"permissions,omitempty"`
PermissionVersion int `json:"permissionVersion"`
}
// UserResponse 用户信息响应
type UserResponse struct {
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Role string `json:"role"`
WorkspaceID string `json:"workspaceId"`
WorkspaceName string `json:"workspaceName,omitempty"`
Namespace string `json:"namespace,omitempty"`
DefaultClusterID string `json:"defaultClusterId,omitempty"`
QuotaCPU string `json:"quotaCpu,omitempty"`
QuotaMemory string `json:"quotaMemory,omitempty"`
QuotaGPU string `json:"quotaGpu,omitempty"`
QuotaGPUMem string `json:"quotaGpuMemory,omitempty"`
IsActive bool `json:"isActive"`
MustChangePassword bool `json:"mustChangePassword"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
// UpdateUserRequest 管理员更新用户状态/角色请求
type UpdateUserRequest struct {
Role string `json:"role,omitempty"`
WorkspaceID string `json:"workspaceId,omitempty"`
Namespace string `json:"namespace,omitempty"`
DefaultClusterID string `json:"defaultClusterId,omitempty"`
QuotaCPU string `json:"quotaCpu,omitempty"`
QuotaMemory string `json:"quotaMemory,omitempty"`
QuotaGPU string `json:"quotaGpu,omitempty"`
QuotaGPUMem string `json:"quotaGpuMemory,omitempty"`
IsActive *bool `json:"isActive,omitempty"`
MustChangePassword *bool `json:"mustChangePassword,omitempty"`
}

View File

@ -2,30 +2,38 @@ package dto
// CreateClusterRequest 创建集群请求
type CreateClusterRequest struct {
Name string `json:"name" binding:"required"`
Host string `json:"host" binding:"required"`
CAData string `json:"caData"`
CADataAlt string `json:"ca_data"`
CertData string `json:"certData"`
CertDataAlt string `json:"cert_data"`
KeyData string `json:"keyData"`
KeyDataAlt string `json:"key_data"`
Token string `json:"token"`
Description string `json:"description"`
Name string `json:"name" binding:"required"`
Host string `json:"host" binding:"required"`
CAData string `json:"caData"`
CADataAlt string `json:"ca_data"`
CertData string `json:"certData"`
CertDataAlt string `json:"cert_data"`
KeyData string `json:"keyData"`
KeyDataAlt string `json:"key_data"`
Token string `json:"token"`
Description string `json:"description"`
Visibility string `json:"visibility"`
GlobalShared bool `json:"globalShared"`
GlobalSharedAlt bool `json:"global_shared"`
DefaultNamespace string `json:"defaultNamespace"`
}
// UpdateClusterRequest 更新集群请求
type UpdateClusterRequest struct {
Name string `json:"name"`
Host string `json:"host"`
CAData string `json:"caData"`
CADataAlt string `json:"ca_data"`
CertData string `json:"certData"`
CertDataAlt string `json:"cert_data"`
KeyData string `json:"keyData"`
KeyDataAlt string `json:"key_data"`
Token string `json:"token"`
Description string `json:"description"`
Name string `json:"name"`
Host string `json:"host"`
CAData string `json:"caData"`
CADataAlt string `json:"ca_data"`
CertData string `json:"certData"`
CertDataAlt string `json:"cert_data"`
KeyData string `json:"keyData"`
KeyDataAlt string `json:"key_data"`
Token string `json:"token"`
Description string `json:"description"`
Visibility string `json:"visibility"`
GlobalShared bool `json:"globalShared"`
GlobalSharedAlt bool `json:"global_shared"`
DefaultNamespace string `json:"defaultNamespace"`
}
// Normalize 将多种命名风格的字段合并到统一字段
@ -56,10 +64,15 @@ func (r *UpdateClusterRequest) Normalize() {
// ClusterResponse 集群响应(敏感数据已脱敏)
type ClusterResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Host string `json:"host"`
Description string `json:"description"`
ID string `json:"id"`
Name string `json:"name"`
Host string `json:"host"`
Description string `json:"description"`
WorkspaceID string `json:"workspaceId"`
OwnerID string `json:"ownerId"`
Visibility string `json:"visibility"`
DefaultNamespace string `json:"defaultNamespace,omitempty"`
AllowedActions []string `json:"allowedActions,omitempty"`
// 认证配置状态(不返回实际证书数据,仅返回是否已配置)
HasCAData bool `json:"hasCaData"`
HasCertData bool `json:"hasCertData"`

View File

@ -9,6 +9,9 @@ import (
func ToRegistryResponse(registry *entity.Registry) *RegistryResponse {
response := &RegistryResponse{
ID: registry.ID,
WorkspaceID: registry.WorkspaceID,
OwnerID: registry.OwnerID,
Visibility: registry.Visibility,
Name: registry.Name,
URL: registry.URL,
Description: registry.Description,
@ -17,33 +20,37 @@ func ToRegistryResponse(registry *entity.Registry) *RegistryResponse {
CreatedAt: registry.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
UpdatedAt: registry.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
}
// 脱敏处理密码
if registry.Password != "" {
response.HasPassword = true
response.Password = crypto.MaskSensitiveData(registry.Password)
}
return response
}
// ToClusterResponse 转换 Cluster 实体为响应 DTO脱敏
func ToClusterResponse(cluster *entity.Cluster) *ClusterResponse {
response := &ClusterResponse{
ID: cluster.ID,
Name: cluster.Name,
Host: cluster.Host,
Description: cluster.Description,
CreatedAt: cluster.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
UpdatedAt: cluster.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
ID: cluster.ID,
WorkspaceID: cluster.WorkspaceID,
OwnerID: cluster.OwnerID,
Visibility: cluster.Visibility,
Name: cluster.Name,
Host: cluster.Host,
Description: cluster.Description,
DefaultNamespace: cluster.DefaultNamespace,
CreatedAt: cluster.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
UpdatedAt: cluster.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
}
// 设置认证配置状态标志
response.HasCAData = cluster.CAData != ""
response.HasCertData = cluster.CertData != ""
response.HasKeyData = cluster.KeyData != ""
response.HasToken = cluster.Token != ""
// 脱敏处理敏感数据(仅显示掩码)
if cluster.CAData != "" {
response.CAData = crypto.MaskSensitiveData(cluster.CAData)
@ -57,7 +64,6 @@ func ToClusterResponse(cluster *entity.Cluster) *ClusterResponse {
if cluster.Token != "" {
response.Token = crypto.MaskSensitiveData(cluster.Token)
}
return response
}

View File

@ -12,4 +12,3 @@ type SuccessResponse struct {
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}

View File

@ -43,23 +43,26 @@ type DeleteInstanceRequest struct {
// InstanceResponse 实例响应
type InstanceResponse struct {
ID string `json:"id"`
ClusterID string `json:"clusterId"`
Name string `json:"name"`
Namespace string `json:"namespace"`
RegistryID string `json:"registryId"`
Repository string `json:"repository"`
Chart string `json:"chart"`
Version string `json:"version"`
Description string `json:"description"`
Status string `json:"status"`
StatusReason string `json:"statusReason,omitempty"`
LastOperation string `json:"lastOperation,omitempty"`
LastError string `json:"lastError,omitempty"`
Revision int `json:"revision"`
Values map[string]interface{} `json:"values,omitempty"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
ID string `json:"id"`
ClusterID string `json:"clusterId"`
Name string `json:"name"`
Namespace string `json:"namespace"`
RegistryID string `json:"registryId"`
Repository string `json:"repository"`
Chart string `json:"chart"`
Version string `json:"version"`
Description string `json:"description"`
Status string `json:"status"`
WorkspaceID string `json:"workspaceId"`
OwnerID string `json:"ownerId"`
AllowedActions []string `json:"allowedActions,omitempty"`
StatusReason string `json:"statusReason,omitempty"`
LastOperation string `json:"lastOperation,omitempty"`
LastError string `json:"lastError,omitempty"`
Revision int `json:"revision"`
Values map[string]interface{} `json:"values,omitempty"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
// InstanceStatusResponse 实例状态响应
@ -131,3 +134,70 @@ type InstanceEntryResponse struct {
Hosts []InstanceEntryHostResponse `json:"hosts,omitempty"`
TLS []InstanceEntryTLSResponse `json:"tls,omitempty"`
}
type InstanceDiagnosticsResponse struct {
InstanceName string `json:"instanceName"`
Namespace string `json:"namespace"`
Pods []InstancePodDiagnostics `json:"pods"`
Services []InstanceServiceDiagnostics `json:"services"`
Events []InstanceEventDiagnostics `json:"events"`
Logs []InstancePodLogResponse `json:"logs"`
CollectedAt string `json:"collectedAt"`
}
type InstancePodDiagnostics struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Phase string `json:"phase"`
NodeName string `json:"nodeName,omitempty"`
PodIP string `json:"podIp,omitempty"`
HostIP string `json:"hostIp,omitempty"`
RestartCount int32 `json:"restartCount"`
Containers []InstanceContainerDiagnostics `json:"containers"`
Conditions []InstanceConditionDiagnostics `json:"conditions"`
CreationTimestamp string `json:"creationTimestamp,omitempty"`
}
type InstanceContainerDiagnostics struct {
Name string `json:"name"`
Image string `json:"image"`
Ready bool `json:"ready"`
RestartCount int32 `json:"restartCount"`
State string `json:"state"`
Reason string `json:"reason,omitempty"`
Message string `json:"message,omitempty"`
}
type InstanceConditionDiagnostics struct {
Type string `json:"type"`
Status string `json:"status"`
Reason string `json:"reason,omitempty"`
Message string `json:"message,omitempty"`
}
type InstanceServiceDiagnostics struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Type string `json:"type"`
ClusterIP string `json:"clusterIP,omitempty"`
Ports []InstanceEntryPortResponse `json:"ports,omitempty"`
}
type InstanceEventDiagnostics struct {
Type string `json:"type"`
Reason string `json:"reason"`
Message string `json:"message"`
InvolvedKind string `json:"involvedKind"`
InvolvedName string `json:"involvedName"`
Count int32 `json:"count"`
FirstTimestamp string `json:"firstTimestamp,omitempty"`
LastTimestamp string `json:"lastTimestamp,omitempty"`
}
type InstancePodLogResponse struct {
Pod string `json:"pod"`
Container string `json:"container"`
TailLines int64 `json:"tailLines"`
Log string `json:"log,omitempty"`
Error string `json:"error,omitempty"`
}

View File

@ -8,29 +8,29 @@ import (
// ClusterMetricsResponse 集群监控响应
type ClusterMetricsResponse struct {
ClusterID string `json:"clusterId"`
ClusterName string `json:"clusterName"`
Status string `json:"status"`
Uptime string `json:"uptime"`
NodeCount int `json:"nodeCount"`
PodCount int `json:"podCount"`
LastCheck time.Time `json:"lastCheck"`
TotalCPU string `json:"totalCpu"`
TotalMemory string `json:"totalMemory"`
TotalGPU int `json:"totalGpu"`
UsedCPU string `json:"usedCpu"`
UsedMemory string `json:"usedMemory"`
UsedGPU int `json:"usedGpu"`
CPUUsage float64 `json:"cpuUsage"`
MemoryUsage float64 `json:"memoryUsage"`
GPUUsage float64 `json:"gpuUsage"`
MaxNodeCPU string `json:"maxNodeCpu"`
MaxNodeMemory string `json:"maxNodeMemory"`
MaxNodeGPU int `json:"maxNodeGpu"`
MaxNodeCPUUsage float64 `json:"maxNodeCpuUsage"`
MaxNodeMemUsage float64 `json:"maxNodeMemUsage"`
MaxNodeGPUUsage float64 `json:"maxNodeGpuUsage"`
Nodes []NodeMetricsResponse `json:"nodes,omitempty"`
ClusterID string `json:"clusterId"`
ClusterName string `json:"clusterName"`
Status string `json:"status"`
Uptime string `json:"uptime"`
NodeCount int `json:"nodeCount"`
PodCount int `json:"podCount"`
LastCheck time.Time `json:"lastCheck"`
TotalCPU string `json:"totalCpu"`
TotalMemory string `json:"totalMemory"`
TotalGPU int `json:"totalGpu"`
UsedCPU string `json:"usedCpu"`
UsedMemory string `json:"usedMemory"`
UsedGPU int `json:"usedGpu"`
CPUUsage float64 `json:"cpuUsage"`
MemoryUsage float64 `json:"memoryUsage"`
GPUUsage float64 `json:"gpuUsage"`
MaxNodeCPU string `json:"maxNodeCpu"`
MaxNodeMemory string `json:"maxNodeMemory"`
MaxNodeGPU int `json:"maxNodeGpu"`
MaxNodeCPUUsage float64 `json:"maxNodeCpuUsage"`
MaxNodeMemUsage float64 `json:"maxNodeMemUsage"`
MaxNodeGPUUsage float64 `json:"maxNodeGpuUsage"`
Nodes []NodeMetricsResponse `json:"nodes,omitempty"`
}
// NodeMetricsResponse 节点监控响应
@ -72,28 +72,28 @@ type MonitoringSummaryResponse struct {
// ToClusterMetricsResponse 转换为响应
func ToClusterMetricsResponse(m *entity.ClusterMetrics) *ClusterMetricsResponse {
resp := &ClusterMetricsResponse{
ClusterID: m.ClusterID,
ClusterName: m.ClusterName,
Status: m.Status,
Uptime: m.Uptime,
NodeCount: m.NodeCount,
PodCount: m.PodCount,
LastCheck: m.LastCheck,
TotalCPU: m.TotalCPU,
TotalMemory: m.TotalMemory,
TotalGPU: m.TotalGPU,
UsedCPU: m.UsedCPU,
UsedMemory: m.UsedMemory,
UsedGPU: m.UsedGPU,
CPUUsage: m.CPUUsage,
MemoryUsage: m.MemoryUsage,
GPUUsage: m.GPUUsage,
MaxNodeCPU: m.MaxNodeCPU,
MaxNodeMemory: m.MaxNodeMemory,
MaxNodeGPU: m.MaxNodeGPU,
MaxNodeCPUUsage: m.MaxNodeCPUUsage,
MaxNodeMemUsage: m.MaxNodeMemUsage,
MaxNodeGPUUsage: m.MaxNodeGPUUsage,
ClusterID: m.ClusterID,
ClusterName: m.ClusterName,
Status: m.Status,
Uptime: m.Uptime,
NodeCount: m.NodeCount,
PodCount: m.PodCount,
LastCheck: m.LastCheck,
TotalCPU: m.TotalCPU,
TotalMemory: m.TotalMemory,
TotalGPU: m.TotalGPU,
UsedCPU: m.UsedCPU,
UsedMemory: m.UsedMemory,
UsedGPU: m.UsedGPU,
CPUUsage: m.CPUUsage,
MemoryUsage: m.MemoryUsage,
GPUUsage: m.GPUUsage,
MaxNodeCPU: m.MaxNodeCPU,
MaxNodeMemory: m.MaxNodeMemory,
MaxNodeGPU: m.MaxNodeGPU,
MaxNodeCPUUsage: m.MaxNodeCPUUsage,
MaxNodeMemUsage: m.MaxNodeMemUsage,
MaxNodeGPUUsage: m.MaxNodeGPUUsage,
}
if len(m.Nodes) > 0 {
@ -140,4 +140,3 @@ func ToMonitoringSummaryResponse(s *entity.MonitoringSummary) *MonitoringSummary
LastUpdate: s.LastUpdate,
}
}

View File

@ -2,36 +2,46 @@ package dto
// CreateRegistryRequest 创建 Registry 请求
type CreateRegistryRequest struct {
Name string `json:"name" binding:"required"`
URL string `json:"url" binding:"required"`
Username string `json:"username"`
Password string `json:"password"`
Description string `json:"description"`
Insecure bool `json:"insecure"`
Name string `json:"name" binding:"required"`
URL string `json:"url" binding:"required"`
Username string `json:"username"`
Password string `json:"password"`
Description string `json:"description"`
Insecure bool `json:"insecure"`
Visibility string `json:"visibility"`
GlobalShared bool `json:"globalShared"`
GlobalSharedAlt bool `json:"global_shared"`
}
// UpdateRegistryRequest 更新 Registry 请求
type UpdateRegistryRequest struct {
Name string `json:"name"`
URL string `json:"url"`
Username string `json:"username"`
Password string `json:"password"`
Description string `json:"description"`
Insecure bool `json:"insecure"`
Name string `json:"name"`
URL string `json:"url"`
Username string `json:"username"`
Password string `json:"password"`
Description string `json:"description"`
Insecure bool `json:"insecure"`
Visibility string `json:"visibility"`
GlobalShared bool `json:"globalShared"`
GlobalSharedAlt bool `json:"global_shared"`
}
// RegistryResponse Registry 响应(敏感数据已脱敏)
type RegistryResponse struct {
ID string `json:"id"`
Name string `json:"name"`
URL string `json:"url"`
Description string `json:"description"`
Username string `json:"username,omitempty"` // 明文返回用户名(不敏感)
Password string `json:"password,omitempty"` // 脱敏显示(••••••••)
HasPassword bool `json:"hasPassword"` // 是否已设置密码
Insecure bool `json:"insecure"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
ID string `json:"id"`
Name string `json:"name"`
URL string `json:"url"`
Description string `json:"description"`
WorkspaceID string `json:"workspaceId"`
OwnerID string `json:"ownerId"`
Visibility string `json:"visibility"`
AllowedActions []string `json:"allowedActions,omitempty"`
Username string `json:"username,omitempty"` // 明文返回用户名(不敏感)
Password string `json:"password,omitempty"` // 脱敏显示(••••••••)
HasPassword bool `json:"hasPassword"` // 是否已设置密码
Insecure bool `json:"insecure"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
// RegistryHealthResponse Registry 健康状态响应
@ -39,4 +49,3 @@ type RegistryHealthResponse struct {
Healthy bool `json:"healthy"`
Message string `json:"message,omitempty"`
}