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.
298 lines
7.5 KiB
Go
298 lines
7.5 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"fmt"
|
||
"time"
|
||
|
||
"github.com/google/uuid"
|
||
"github.com/ocdp/cluster-service/internal/domain/entity"
|
||
"github.com/ocdp/cluster-service/internal/domain/repository"
|
||
)
|
||
|
||
// UserManagementService 用户管理领域服务(仅 Admin 可用)
|
||
type UserManagementService struct {
|
||
userRepo repository.UserRepository
|
||
workspaceRepo repository.WorkspaceRepository
|
||
passwordHasher PasswordHasher
|
||
}
|
||
|
||
// NewUserManagementService 创建用户管理服务
|
||
func NewUserManagementService(
|
||
userRepo repository.UserRepository,
|
||
workspaceRepo repository.WorkspaceRepository,
|
||
passwordHasher PasswordHasher,
|
||
) *UserManagementService {
|
||
return &UserManagementService{
|
||
userRepo: userRepo,
|
||
workspaceRepo: workspaceRepo,
|
||
passwordHasher: passwordHasher,
|
||
}
|
||
}
|
||
|
||
// CreateUser 创建用户(Admin 操作)
|
||
func (s *UserManagementService) CreateUser(ctx context.Context, username, password, email, role string, workspaceID string) (*entity.User, error) {
|
||
// 检查用户是否已存在
|
||
existing, _ := s.userRepo.GetByUsername(ctx, username)
|
||
if existing != nil {
|
||
return nil, entity.ErrUserExists
|
||
}
|
||
|
||
// 验证角色
|
||
if role != string(entity.RoleAdmin) && role != string(entity.RoleUser) {
|
||
return nil, fmt.Errorf("invalid role: %s", role)
|
||
}
|
||
|
||
// 如果指定了 workspace,验证 workspace 存在
|
||
if workspaceID != "" {
|
||
_, err := s.workspaceRepo.GetByID(ctx, workspaceID)
|
||
if err != nil {
|
||
if err == entity.ErrWorkspaceNotFound {
|
||
return nil, entity.ErrWorkspaceNotFound
|
||
}
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
// Admin 不能分配到 workspace
|
||
if role == string(entity.RoleAdmin) && workspaceID != "" {
|
||
workspaceID = ""
|
||
}
|
||
|
||
// 哈希密码
|
||
passwordHash, err := s.passwordHasher.Hash(password)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 生成占位邮箱
|
||
if email == "" {
|
||
email = username + "@local.ocdp"
|
||
}
|
||
|
||
// 创建用户
|
||
user := entity.NewUser(username, passwordHash, email)
|
||
user.ID = uuid.New().String()
|
||
user.Role = entity.UserRole(role)
|
||
user.WorkspaceID = workspaceID
|
||
user.IsActive = true
|
||
user.MustChangePassword = true // 首次登录必须修改密码
|
||
|
||
if err := user.Validate(); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if err := s.userRepo.Create(ctx, user); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return user, nil
|
||
}
|
||
|
||
// GetUser 获取用户
|
||
func (s *UserManagementService) GetUser(ctx context.Context, id string) (*entity.User, error) {
|
||
return s.userRepo.GetByID(ctx, id)
|
||
}
|
||
|
||
// ListUsers 列出用户(可筛选 workspace)
|
||
func (s *UserManagementService) ListUsers(ctx context.Context, workspaceID string) ([]*entity.User, error) {
|
||
if workspaceID != "" {
|
||
return s.userRepo.ListByWorkspace(ctx, workspaceID)
|
||
}
|
||
return s.userRepo.List(ctx)
|
||
}
|
||
|
||
// UpdateUser 更新用户信息
|
||
func (s *UserManagementService) UpdateUser(ctx context.Context, user *entity.User) error {
|
||
return s.userRepo.Update(ctx, user)
|
||
}
|
||
|
||
// SetUserActive 启用/禁用用户
|
||
func (s *UserManagementService) SetUserActive(ctx context.Context, userID string, isActive bool) error {
|
||
user, err := s.userRepo.GetByID(ctx, userID)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
user.IsActive = isActive
|
||
return s.userRepo.Update(ctx, user)
|
||
}
|
||
|
||
// ChangeUserWorkspace 分配用户到 workspace
|
||
func (s *UserManagementService) ChangeUserWorkspace(ctx context.Context, userID, workspaceID string) error {
|
||
user, err := s.userRepo.GetByID(ctx, userID)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// Admin 不能分配到 workspace
|
||
if user.Role == entity.RoleAdmin {
|
||
return fmt.Errorf("admin user cannot be assigned to workspace")
|
||
}
|
||
|
||
// 验证 workspace 存在
|
||
if workspaceID != "" {
|
||
_, err := s.workspaceRepo.GetByID(ctx, workspaceID)
|
||
if err != nil {
|
||
if err == sql.ErrNoRows || err == entity.ErrWorkspaceNotFound {
|
||
return entity.ErrWorkspaceNotFound
|
||
}
|
||
return err
|
||
}
|
||
}
|
||
|
||
user.WorkspaceID = workspaceID
|
||
return s.userRepo.Update(ctx, user)
|
||
}
|
||
|
||
// ResetPassword 重置用户密码(Admin 操作)
|
||
func (s *UserManagementService) ResetPassword(ctx context.Context, userID, newPassword string) error {
|
||
user, err := s.userRepo.GetByID(ctx, userID)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 哈希新密码
|
||
passwordHash, err := s.passwordHasher.Hash(newPassword)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 更新密码并设置必须修改密码标志
|
||
user.PasswordHash = passwordHash
|
||
user.MustChangePassword = true
|
||
user.RevokeAllTokens() // 强制登出所有会话
|
||
|
||
return s.userRepo.Update(ctx, user)
|
||
}
|
||
|
||
// DeleteUser 删除用户
|
||
func (s *UserManagementService) DeleteUser(ctx context.Context, id string) error {
|
||
return s.userRepo.Delete(ctx, id)
|
||
}
|
||
|
||
// GetUserWithWorkspace 获取用户及其 workspace 信息
|
||
type UserWithWorkspace struct {
|
||
User *entity.User
|
||
Workspace *entity.Workspace
|
||
}
|
||
|
||
// GetUserWithWorkspace 获取用户及其 workspace 信息
|
||
func (s *UserManagementService) GetUserWithWorkspace(ctx context.Context, userID string) (*UserWithWorkspace, error) {
|
||
user, err := s.userRepo.GetByID(ctx, userID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
result := &UserWithWorkspace{
|
||
User: user,
|
||
}
|
||
|
||
if user.WorkspaceID != "" {
|
||
workspace, _ := s.workspaceRepo.GetByID(ctx, user.WorkspaceID)
|
||
result.Workspace = workspace
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// ListUsersWithWorkspace 列出用户及其 workspace 信息
|
||
func (s *UserManagementService) ListUsersWithWorkspace(ctx context.Context) ([]*UserWithWorkspace, error) {
|
||
users, err := s.userRepo.List(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 预加载所有 workspace
|
||
workspaces, err := s.workspaceRepo.List(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
workspaceMap := make(map[string]*entity.Workspace)
|
||
for _, w := range workspaces {
|
||
workspaceMap[w.ID] = w
|
||
}
|
||
|
||
result := make([]*UserWithWorkspace, len(users))
|
||
for i, user := range users {
|
||
result[i] = &UserWithWorkspace{
|
||
User: user,
|
||
}
|
||
if user.WorkspaceID != "" {
|
||
result[i].Workspace = workspaceMap[user.WorkspaceID]
|
||
}
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// EnsureAdminExists 确保存在一个 Admin 用户
|
||
func (s *UserManagementService) EnsureAdminExists(ctx context.Context, defaultPassword string) error {
|
||
users, err := s.userRepo.List(ctx)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 检查是否已有 admin
|
||
for _, u := range users {
|
||
if u.Role == entity.RoleAdmin {
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 创建默认 admin 用户
|
||
_, err = s.CreateUser(ctx, "admin", defaultPassword, "", string(entity.RoleAdmin), "")
|
||
return err
|
||
}
|
||
|
||
// CreateInitialUser 创建初始用户(首次启动时调用)
|
||
func (s *UserManagementService) CreateInitialUser(ctx context.Context, username, password, role string) (*entity.User, error) {
|
||
// 检查是否已有用户
|
||
users, err := s.userRepo.List(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if len(users) > 0 {
|
||
return nil, fmt.Errorf("initial user already exists")
|
||
}
|
||
|
||
// 验证角色
|
||
if role != string(entity.RoleAdmin) && role != string(entity.RoleUser) {
|
||
return nil, fmt.Errorf("invalid role: %s", role)
|
||
}
|
||
|
||
// 哈希密码
|
||
passwordHash, err := s.passwordHasher.Hash(password)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 生成占位邮箱
|
||
email := username + "@local.ocdp"
|
||
|
||
// 创建用户
|
||
user := entity.NewUser(username, passwordHash, email)
|
||
user.ID = uuid.New().String()
|
||
user.Role = entity.UserRole(role)
|
||
// workspace_id 为 NULL(admin)或空(首个普通用户)
|
||
user.IsActive = true
|
||
user.MustChangePassword = false // 初始用户不需要强制修改密码
|
||
|
||
if err := user.Validate(); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 设置创建时间和更新时间
|
||
now := time.Now()
|
||
user.CreatedAt = now
|
||
user.UpdatedAt = now
|
||
|
||
if err := s.userRepo.Create(ctx, user); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return user, nil
|
||
} |