mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-03-16 12:27:49 +08:00
### What problem does this PR solve? Feat: Implement user creation, deletion, and permission management functionality. - Added the `ListByEmail` method to `user.go` to query users by email address. - Updated the user activation status handling logic in `handler.go`, adding input validation. - Added RSA password decryption functionality to `password.go`. - Implemented complete user management functionality in `service.go`, including user creation, deletion, password modification, activation status, and permission management. - Added input validation and error handling logic. ### Type of change - [x] New Feature (non-breaking change which adds functionality)
1121 lines
29 KiB
Go
1121 lines
29 KiB
Go
//
|
|
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
package admin
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/tls"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"ragflow/internal/cache"
|
|
"ragflow/internal/common"
|
|
"ragflow/internal/dao"
|
|
"ragflow/internal/engine/elasticsearch"
|
|
"ragflow/internal/model"
|
|
"ragflow/internal/server"
|
|
"ragflow/internal/utility"
|
|
"regexp"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
// Service errors
|
|
var (
|
|
ErrInvalidToken = errors.New("invalid token")
|
|
ErrNotAdmin = errors.New("user is not admin")
|
|
ErrUserInactive = errors.New("user is inactive")
|
|
)
|
|
|
|
// Service admin service layer
|
|
type Service struct {
|
|
userDAO *dao.UserDAO
|
|
licenseDAO *dao.LicenseDAO
|
|
}
|
|
|
|
// NewService create admin service
|
|
func NewService() *Service {
|
|
return &Service{
|
|
userDAO: dao.NewUserDAO(),
|
|
licenseDAO: dao.NewLicenseDAO(),
|
|
}
|
|
}
|
|
|
|
// Logout user logout
|
|
func (s *Service) Logout(user interface{}) error {
|
|
// Invalidate token by setting it to INVALID_ prefix
|
|
if u, ok := user.(*model.User); ok {
|
|
invalidToken := "INVALID_" + generateRandomHex(16)
|
|
return s.userDAO.UpdateAccessToken(u, invalidToken)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetUserByToken get user by access token
|
|
func (s *Service) GetUserByToken(token string) (*model.User, error) {
|
|
user, err := s.userDAO.GetByAccessToken(token)
|
|
if err != nil {
|
|
return nil, ErrInvalidToken
|
|
}
|
|
|
|
if user.IsSuperuser == nil || !*user.IsSuperuser {
|
|
return nil, ErrNotAdmin
|
|
}
|
|
|
|
if user.IsActive != "1" {
|
|
return nil, fmt.Errorf("user inactive")
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
// generateRandomHex generate random hex string
|
|
func generateRandomHex(n int) string {
|
|
bytes := make([]byte, n)
|
|
rand.Read(bytes)
|
|
return hex.EncodeToString(bytes)
|
|
}
|
|
|
|
// ListUsers list all users
|
|
func (s *Service) ListUsers() ([]map[string]interface{}, error) {
|
|
users, _, err := s.userDAO.List(0, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := make([]map[string]interface{}, 0, len(users))
|
|
for _, user := range users {
|
|
result = append(result, map[string]interface{}{
|
|
"email": user.Email,
|
|
"nickname": user.Nickname,
|
|
"create_date": user.CreateTime,
|
|
"is_active": user.IsActive,
|
|
"is_superuser": user.IsSuperuser,
|
|
})
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// CreateUser create a new user
|
|
// Parameters:
|
|
// - username: email address of the user
|
|
// - password: encrypted password (base64 encoded RSA encrypted)
|
|
// - role: user role ("user" or "admin")
|
|
//
|
|
// Returns:
|
|
// - map[string]interface{}: user information without password
|
|
// - error: error message
|
|
func (s *Service) CreateUser(username, password, role string) (map[string]interface{}, error) {
|
|
emailRegex := regexp.MustCompile(`^[\w\._-]+@([\w_-]+\.)+[\w-]{2,}$`)
|
|
if !emailRegex.MatchString(username) {
|
|
return nil, fmt.Errorf("Invalid email address: %s!", username)
|
|
}
|
|
|
|
existUser, _ := s.userDAO.GetByEmail(username)
|
|
if existUser != nil {
|
|
return nil, fmt.Errorf("User '%s' already exists", username)
|
|
}
|
|
|
|
decryptedPassword, err := DecryptPassword(password)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decrypt password: %w", err)
|
|
}
|
|
|
|
hashedPassword, err := GenerateWerkzeugPasswordHash(decryptedPassword, 150000)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to hash password: %w", err)
|
|
}
|
|
|
|
userID := utility.GenerateToken()
|
|
accessToken := utility.GenerateToken()
|
|
status := "1"
|
|
loginChannel := "password"
|
|
isSuperuser := role == "admin"
|
|
|
|
now := time.Now().Unix()
|
|
nowDate := time.Now()
|
|
|
|
user := &model.User{
|
|
ID: userID,
|
|
AccessToken: &accessToken,
|
|
Email: username,
|
|
Nickname: "",
|
|
Password: &hashedPassword,
|
|
Status: &status,
|
|
IsActive: "1",
|
|
IsAuthenticated: "1",
|
|
IsAnonymous: "0",
|
|
LoginChannel: &loginChannel,
|
|
IsSuperuser: &isSuperuser,
|
|
BaseModel: model.BaseModel{
|
|
CreateTime: &now,
|
|
CreateDate: &nowDate,
|
|
UpdateTime: &now,
|
|
UpdateDate: &nowDate,
|
|
},
|
|
}
|
|
|
|
if err := s.userDAO.Create(user); err != nil {
|
|
return nil, fmt.Errorf("failed to create user: %w", err)
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"id": user.ID,
|
|
"email": user.Email,
|
|
"nickname": user.Nickname,
|
|
"is_active": user.IsActive,
|
|
"is_superuser": isSuperuser,
|
|
"create_date": user.CreateDate,
|
|
}, nil
|
|
}
|
|
|
|
// GetUserDetails get user details
|
|
func (s *Service) GetUserDetails(username string) (map[string]interface{}, error) {
|
|
// Query user by email/username
|
|
var user model.User
|
|
err := dao.DB.Where("email = ?", username).First(&user).Error
|
|
if err != nil {
|
|
return nil, ErrUserNotFound
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"id": user.ID,
|
|
"email": user.Email,
|
|
"nickname": user.Nickname,
|
|
"is_active": user.IsActive,
|
|
"create_time": user.CreateTime,
|
|
"update_time": user.UpdateTime,
|
|
}, nil
|
|
}
|
|
|
|
// DeleteUser delete user
|
|
// Parameters:
|
|
// - username: email address of the user to delete
|
|
//
|
|
// Returns:
|
|
// - error: error message
|
|
func (s *Service) DeleteUser(username string) error {
|
|
userList, err := s.userDAO.ListByEmail(username)
|
|
if err != nil || len(userList) == 0 {
|
|
return fmt.Errorf("User '%s' not found", username)
|
|
}
|
|
|
|
if len(userList) > 1 {
|
|
return fmt.Errorf("Exist more than 1 user: %s!", username)
|
|
}
|
|
|
|
user := userList[0]
|
|
|
|
// Check if user is active - cannot delete active users
|
|
if user.IsActive == "1" {
|
|
return fmt.Errorf("User '%s' is active and can't be deleted. Please deactivate the user first", username)
|
|
}
|
|
|
|
// Check if user is superuser - cannot delete admin accounts
|
|
if user.IsSuperuser != nil && *user.IsSuperuser {
|
|
return fmt.Errorf("Cannot delete admin account")
|
|
}
|
|
|
|
if err := s.userDAO.DeleteByID(user.ID); err != nil {
|
|
return fmt.Errorf("failed to delete user: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ChangePassword change user password
|
|
// Parameters:
|
|
// - username: email address of the user
|
|
// - newPassword: new encrypted password (base64 encoded RSA encrypted)
|
|
//
|
|
// Returns:
|
|
// - error: error message
|
|
func (s *Service) ChangePassword(username, newPassword string) error {
|
|
userList, err := s.userDAO.ListByEmail(username)
|
|
if err != nil || len(userList) == 0 {
|
|
return fmt.Errorf("User '%s' not found", username)
|
|
}
|
|
|
|
if len(userList) > 1 {
|
|
return fmt.Errorf("Exist more than 1 user: %s!", username)
|
|
}
|
|
|
|
user := userList[0]
|
|
|
|
decryptedPassword, err := DecryptPassword(newPassword)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decrypt password: %w", err)
|
|
}
|
|
|
|
if user.Password != nil && CheckWerkzeugPassword(decryptedPassword, *user.Password) {
|
|
return nil
|
|
}
|
|
|
|
hashedPassword, err := GenerateWerkzeugPasswordHash(decryptedPassword, 150000)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to hash password: %w", err)
|
|
}
|
|
|
|
user.Password = &hashedPassword
|
|
now := time.Now().Unix()
|
|
user.UpdateTime = &now
|
|
|
|
if err := s.userDAO.Update(user); err != nil {
|
|
return fmt.Errorf("failed to update user: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateUserActivateStatus update user activate status
|
|
// Parameters:
|
|
// - username: email address of the user
|
|
// - isActive: true to activate, false to deactivate
|
|
//
|
|
// Returns:
|
|
// - error: error message
|
|
func (s *Service) UpdateUserActivateStatus(username string, isActive bool) error {
|
|
userList, err := s.userDAO.ListByEmail(username)
|
|
if err != nil || len(userList) == 0 {
|
|
return fmt.Errorf("User '%s' not found", username)
|
|
}
|
|
|
|
if len(userList) > 1 {
|
|
return fmt.Errorf("Exist more than 1 user: %s!", username)
|
|
}
|
|
|
|
user := userList[0]
|
|
|
|
targetStatus := "0"
|
|
if isActive {
|
|
targetStatus = "1"
|
|
}
|
|
|
|
if user.IsActive == targetStatus {
|
|
return nil
|
|
}
|
|
|
|
user.IsActive = targetStatus
|
|
now := time.Now().Unix()
|
|
user.UpdateTime = &now
|
|
|
|
if err := s.userDAO.Update(user); err != nil {
|
|
return fmt.Errorf("failed to update user: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GrantAdmin grant admin privileges
|
|
// Parameters:
|
|
// - username: email address of the user
|
|
//
|
|
// Returns:
|
|
// - error: error message
|
|
func (s *Service) GrantAdmin(username string) error {
|
|
userList, err := s.userDAO.ListByEmail(username)
|
|
if err != nil || len(userList) == 0 {
|
|
return fmt.Errorf("User '%s' not found", username)
|
|
}
|
|
|
|
if len(userList) > 1 {
|
|
return fmt.Errorf("Exist more than 1 user: %s!", username)
|
|
}
|
|
|
|
user := userList[0]
|
|
|
|
if user.IsSuperuser != nil && *user.IsSuperuser {
|
|
return nil
|
|
}
|
|
|
|
isSuperuser := true
|
|
user.IsSuperuser = &isSuperuser
|
|
now := time.Now().Unix()
|
|
user.UpdateTime = &now
|
|
|
|
if err := s.userDAO.Update(user); err != nil {
|
|
return fmt.Errorf("failed to update user: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RevokeAdmin revoke admin privileges
|
|
// Parameters:
|
|
// - username: email address of the user
|
|
//
|
|
// Returns:
|
|
// - error: error message
|
|
func (s *Service) RevokeAdmin(username string) error {
|
|
userList, err := s.userDAO.ListByEmail(username)
|
|
if err != nil || len(userList) == 0 {
|
|
return fmt.Errorf("User '%s' not found", username)
|
|
}
|
|
|
|
if len(userList) > 1 {
|
|
return fmt.Errorf("Exist more than 1 user: %s!", username)
|
|
}
|
|
|
|
user := userList[0]
|
|
|
|
if user.IsSuperuser == nil || !*user.IsSuperuser {
|
|
return nil
|
|
}
|
|
|
|
isSuperuser := false
|
|
user.IsSuperuser = &isSuperuser
|
|
now := time.Now().Unix()
|
|
user.UpdateTime = &now
|
|
|
|
if err := s.userDAO.Update(user); err != nil {
|
|
return fmt.Errorf("failed to update user: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetUserDatasets get user datasets
|
|
func (s *Service) GetUserDatasets(username string) ([]map[string]interface{}, error) {
|
|
// TODO: Implement get user datasets
|
|
return []map[string]interface{}{}, nil
|
|
}
|
|
|
|
// GetUserAgents get user agents
|
|
func (s *Service) GetUserAgents(username string) ([]map[string]interface{}, error) {
|
|
// TODO: Implement get user agents
|
|
return []map[string]interface{}{}, nil
|
|
}
|
|
|
|
// API Key methods
|
|
|
|
// GetUserAPIKeys get user API keys
|
|
func (s *Service) GetUserAPIKeys(username string) ([]map[string]interface{}, error) {
|
|
// TODO: Implement get API keys
|
|
return []map[string]interface{}{}, nil
|
|
}
|
|
|
|
// GenerateUserAPIKey generate API key for user
|
|
func (s *Service) GenerateUserAPIKey(username string) (map[string]interface{}, error) {
|
|
// TODO: Implement generate API key
|
|
return map[string]interface{}{}, nil
|
|
}
|
|
|
|
// DeleteUserAPIKey delete user API key
|
|
func (s *Service) DeleteUserAPIKey(username, key string) error {
|
|
// TODO: Implement delete API key
|
|
return nil
|
|
}
|
|
|
|
// Role management methods
|
|
|
|
// ListRoles list all roles
|
|
func (s *Service) ListRoles() ([]map[string]interface{}, error) {
|
|
// TODO: Implement list roles
|
|
return []map[string]interface{}{}, nil
|
|
}
|
|
|
|
// CreateRole create a new role
|
|
func (s *Service) CreateRole(roleName, description string) (map[string]interface{}, error) {
|
|
// TODO: Implement create role
|
|
return map[string]interface{}{}, nil
|
|
}
|
|
|
|
// GetRole get role details
|
|
func (s *Service) GetRole(roleName string) (map[string]interface{}, error) {
|
|
// TODO: Implement get role
|
|
return map[string]interface{}{}, nil
|
|
}
|
|
|
|
// UpdateRole update role
|
|
func (s *Service) UpdateRole(roleName, description string) (map[string]interface{}, error) {
|
|
// TODO: Implement update role
|
|
return map[string]interface{}{}, nil
|
|
}
|
|
|
|
// DeleteRole delete role
|
|
func (s *Service) DeleteRole(roleName string) error {
|
|
// TODO: Implement delete role
|
|
return nil
|
|
}
|
|
|
|
// GetRolePermission get role permissions
|
|
func (s *Service) GetRolePermission(roleName string) ([]map[string]interface{}, error) {
|
|
// TODO: Implement get role permissions
|
|
return []map[string]interface{}{}, nil
|
|
}
|
|
|
|
// GrantRolePermission grant permission to role
|
|
func (s *Service) GrantRolePermission(roleName string, actions []string, resource string) (map[string]interface{}, error) {
|
|
// TODO: Implement grant role permission
|
|
return map[string]interface{}{}, nil
|
|
}
|
|
|
|
// RevokeRolePermission revoke permission from role
|
|
func (s *Service) RevokeRolePermission(roleName string, actions []string, resource string) (map[string]interface{}, error) {
|
|
// TODO: Implement revoke role permission
|
|
return map[string]interface{}{}, nil
|
|
}
|
|
|
|
// UpdateUserRole update user role
|
|
func (s *Service) UpdateUserRole(username, roleName string) ([]map[string]interface{}, error) {
|
|
// TODO: Implement update user role
|
|
return []map[string]interface{}{}, nil
|
|
}
|
|
|
|
// GetUserPermission get user permissions
|
|
func (s *Service) GetUserPermission(username string) ([]map[string]interface{}, error) {
|
|
// TODO: Implement get user permissions
|
|
return []map[string]interface{}{}, nil
|
|
}
|
|
|
|
// ListServices get all services
|
|
func (s *Service) ListServices() ([]map[string]interface{}, error) {
|
|
allConfigs := server.GetAllConfigs()
|
|
|
|
var result []map[string]interface{}
|
|
for _, configDict := range allConfigs {
|
|
serviceType := configDict["service_type"]
|
|
if serviceType != "ragflow_server" {
|
|
// Get service details to check status
|
|
serviceDetail, err := s.GetServiceDetails(configDict)
|
|
if err == nil {
|
|
if status, ok := serviceDetail["status"]; ok {
|
|
configDict["status"] = status
|
|
} else {
|
|
configDict["status"] = "timeout"
|
|
}
|
|
} else {
|
|
configDict["status"] = "timeout"
|
|
}
|
|
result = append(result, configDict)
|
|
}
|
|
|
|
}
|
|
|
|
id := len(result)
|
|
serverList := GlobalServerStatusStore.GetAllStatuses()
|
|
for _, serverStatus := range serverList {
|
|
serverItem := make(map[string]interface{})
|
|
serverItem["name"] = serverStatus.ServerName
|
|
serverItem["service_type"] = serverStatus.ServerType
|
|
serverItem["id"] = id
|
|
id++
|
|
serverItem["host"] = serverStatus.Host
|
|
serverItem["port"] = serverStatus.Port
|
|
serverItem["status"] = "alive"
|
|
result = append(result, serverItem)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// GetServicesByType get services by type
|
|
func (s *Service) GetServicesByType(serviceType string) ([]map[string]interface{}, error) {
|
|
return nil, errors.New("get_services_by_type: not implemented")
|
|
}
|
|
|
|
// GetServiceDetails get service details
|
|
func (s *Service) GetServiceDetails(configDict map[string]interface{}) (map[string]interface{}, error) {
|
|
serviceType, _ := configDict["service_type"].(string)
|
|
name, _ := configDict["name"].(string)
|
|
|
|
// Call detail function based on service type
|
|
switch serviceType {
|
|
case "meta_data":
|
|
return s.getMySQLStatus(name)
|
|
case "message_queue":
|
|
return s.getRedisInfo(name)
|
|
case "retrieval":
|
|
// Check the extra.retrieval_type to determine which retrieval service
|
|
if extra, ok := configDict["extra"].(map[string]interface{}); ok {
|
|
if retrievalType, ok := extra["retrieval_type"].(string); ok {
|
|
if retrievalType == "infinity" {
|
|
return s.getInfinityStatus(name)
|
|
}
|
|
}
|
|
}
|
|
return s.getESClusterStats(name)
|
|
case "ragflow_server":
|
|
return s.checkRAGFlowServerAlive(name)
|
|
case "file_store":
|
|
return s.checkMinioAlive(name)
|
|
case "task_executor":
|
|
return s.checkTaskExecutorAlive(name)
|
|
default:
|
|
return map[string]interface{}{
|
|
"service_name": name,
|
|
"status": "unknown",
|
|
"message": "Service type not supported",
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
// getMySQLStatus gets MySQL service status
|
|
func (s *Service) getMySQLStatus(name string) (map[string]interface{}, error) {
|
|
startTime := time.Now()
|
|
|
|
// Check basic connectivity with SELECT 1
|
|
sqlDB, err := dao.DB.DB()
|
|
if err != nil {
|
|
return map[string]interface{}{
|
|
"service_name": name,
|
|
"status": "timeout",
|
|
"elapsed": fmt.Sprintf("%.1f", time.Since(startTime).Milliseconds()),
|
|
"message": err.Error(),
|
|
}, nil
|
|
}
|
|
|
|
// Execute SELECT 1 to check connectivity
|
|
_, err = sqlDB.Exec("SELECT 1")
|
|
if err != nil {
|
|
return map[string]interface{}{
|
|
"service_name": name,
|
|
"status": "timeout",
|
|
"elapsed": fmt.Sprintf("%.1f", time.Since(startTime).Milliseconds()),
|
|
"message": err.Error(),
|
|
}, nil
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"service_name": name,
|
|
"status": "alive",
|
|
"elapsed": fmt.Sprintf("%.1f", time.Since(startTime).Milliseconds()),
|
|
"message": "MySQL connection successful",
|
|
}, nil
|
|
}
|
|
|
|
// getRedisInfo gets Redis service info
|
|
func (s *Service) getRedisInfo(name string) (map[string]interface{}, error) {
|
|
startTime := time.Now()
|
|
|
|
redisClient := cache.Get()
|
|
if redisClient == nil {
|
|
return map[string]interface{}{
|
|
"service_name": name,
|
|
"status": "timeout",
|
|
"elapsed": fmt.Sprintf("%.1f", time.Since(startTime).Milliseconds()),
|
|
"error": "Redis client not initialized",
|
|
}, nil
|
|
}
|
|
|
|
// Check health
|
|
if !redisClient.Health() {
|
|
return map[string]interface{}{
|
|
"service_name": name,
|
|
"status": "timeout",
|
|
"elapsed": fmt.Sprintf("%.1f", time.Since(startTime).Milliseconds()),
|
|
"error": "Redis health check failed",
|
|
}, nil
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"service_name": name,
|
|
"status": "alive",
|
|
"elapsed": fmt.Sprintf("%.1f", time.Since(startTime).Milliseconds()),
|
|
"message": "Redis connection successful",
|
|
}, nil
|
|
}
|
|
|
|
// getESClusterStats gets Elasticsearch cluster stats
|
|
func (s *Service) getESClusterStats(name string) (map[string]interface{}, error) {
|
|
// Check if Elasticsearch is the doc engine
|
|
docEngine := os.Getenv("DOC_ENGINE")
|
|
if docEngine == "" {
|
|
docEngine = "elasticsearch"
|
|
}
|
|
if docEngine != "elasticsearch" {
|
|
return map[string]interface{}{
|
|
"service_name": name,
|
|
"status": "timeout",
|
|
"message": "error: Elasticsearch is not in use.",
|
|
}, nil
|
|
}
|
|
|
|
// Get ES config from server config
|
|
cfg := server.GetConfig()
|
|
if cfg == nil || cfg.DocEngine.ES == nil {
|
|
return map[string]interface{}{
|
|
"service_name": name,
|
|
"status": "timeout",
|
|
"message": "error: Elasticsearch configuration not found",
|
|
}, nil
|
|
}
|
|
|
|
// Create ES engine and get cluster stats
|
|
esEngine, err := elasticsearch.NewEngine(cfg.DocEngine.ES)
|
|
if err != nil {
|
|
return map[string]interface{}{
|
|
"service_name": name,
|
|
"status": "timeout",
|
|
"message": fmt.Sprintf("error: %s", err.Error()),
|
|
}, nil
|
|
}
|
|
defer esEngine.Close()
|
|
|
|
clusterStats, err := esEngine.GetClusterStats()
|
|
if err != nil {
|
|
return map[string]interface{}{
|
|
"service_name": name,
|
|
"status": "timeout",
|
|
"message": fmt.Sprintf("error: %s", err.Error()),
|
|
}, nil
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"service_name": name,
|
|
"status": "alive",
|
|
"message": clusterStats,
|
|
}, nil
|
|
}
|
|
|
|
// getInfinityStatus gets Infinity service status
|
|
func (s *Service) getInfinityStatus(name string) (map[string]interface{}, error) {
|
|
// TODO: Implement actual Infinity health check
|
|
return map[string]interface{}{
|
|
"service_name": name,
|
|
"status": "unknown",
|
|
"message": "Infinity health check not implemented",
|
|
}, nil
|
|
}
|
|
|
|
// checkRAGFlowServerAlive checks if RAGFlow server is alive
|
|
func (s *Service) checkRAGFlowServerAlive(name string) (map[string]interface{}, error) {
|
|
startTime := time.Now()
|
|
|
|
// Get ragflow config from allConfigs
|
|
var host string
|
|
var port int
|
|
allConfigs := server.GetAllConfigs()
|
|
for _, config := range allConfigs {
|
|
if serviceType, ok := config["service_type"].(string); ok && serviceType == "ragflow_server" {
|
|
if h, ok := config["host"].(string); ok {
|
|
host = h
|
|
}
|
|
if p, ok := config["port"].(int); ok {
|
|
port = p
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
// Default values
|
|
if host == "" {
|
|
host = "127.0.0.1"
|
|
}
|
|
if port == 0 {
|
|
port = 9380
|
|
}
|
|
|
|
// Replace 0.0.0.0 with 127.0.0.1 for local check
|
|
if host == "0.0.0.0" {
|
|
host = "127.0.0.1"
|
|
}
|
|
|
|
url := fmt.Sprintf("http://%s:%d/v1/system/ping", host, port)
|
|
|
|
// Create HTTP client with timeout
|
|
client := &http.Client{
|
|
Timeout: 10 * time.Second,
|
|
}
|
|
|
|
resp, err := client.Get(url)
|
|
if err != nil {
|
|
return map[string]interface{}{
|
|
"service_name": name,
|
|
"status": "timeout",
|
|
"message": fmt.Sprintf("error: %s", err.Error()),
|
|
}, nil
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
elapsed := time.Since(startTime).Milliseconds()
|
|
if resp.StatusCode == 200 {
|
|
return map[string]interface{}{
|
|
"service_name": name,
|
|
"status": "alive",
|
|
"message": fmt.Sprintf("Confirm elapsed: %.1f ms.", float64(elapsed)),
|
|
}, nil
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"service_name": name,
|
|
"status": "timeout",
|
|
"message": fmt.Sprintf("Confirm elapsed: %.1f ms.", float64(elapsed)),
|
|
}, nil
|
|
}
|
|
|
|
// checkMinioAlive checks if MinIO is alive
|
|
func (s *Service) checkMinioAlive(name string) (map[string]interface{}, error) {
|
|
startTime := time.Now()
|
|
|
|
// Get minio config from allConfigs
|
|
var host string
|
|
var port int
|
|
var secure bool
|
|
var verify bool = true
|
|
|
|
allConfigs := server.GetAllConfigs()
|
|
for _, config := range allConfigs {
|
|
if serviceType, ok := config["service_type"].(string); ok && serviceType == "file_store" {
|
|
// Get host from config
|
|
if h, ok := config["host"].(string); ok {
|
|
host = h
|
|
}
|
|
|
|
if p, ok := config["port"].(int); ok {
|
|
port = p
|
|
} else if p, ok := config["port"].(float64); ok {
|
|
port = int(p)
|
|
} else if p, ok := config["port"].(string); ok {
|
|
if parsedPort, err := strconv.Atoi(p); err == nil {
|
|
port = parsedPort
|
|
}
|
|
}
|
|
// Get secure from extra config
|
|
if extra, ok := config["extra"].(map[string]interface{}); ok {
|
|
if s, ok := extra["secure"].(bool); ok {
|
|
secure = s
|
|
} else if s, ok := extra["secure"].(string); ok {
|
|
secure = s == "true" || s == "1" || s == "yes"
|
|
}
|
|
if v, ok := extra["verify"].(bool); ok {
|
|
verify = v
|
|
} else if v, ok := extra["verify"].(string); ok {
|
|
verify = !(v == "false" || v == "0" || v == "no")
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
// Default host
|
|
if host == "" {
|
|
host = "localhost"
|
|
}
|
|
if port == 0 {
|
|
port = 9000
|
|
}
|
|
|
|
// Determine scheme
|
|
scheme := "http"
|
|
if secure {
|
|
scheme = "https"
|
|
}
|
|
|
|
url := fmt.Sprintf("%s://%s:%d/minio/health/live", scheme, host, port)
|
|
|
|
// Create HTTP client with timeout
|
|
client := &http.Client{
|
|
Timeout: 10 * time.Second,
|
|
}
|
|
|
|
// If verify is false, we need to skip SSL verification
|
|
if !verify && scheme == "https" {
|
|
client.Transport = &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
}
|
|
}
|
|
|
|
resp, err := client.Get(url)
|
|
if err != nil {
|
|
return map[string]interface{}{
|
|
"service_name": name,
|
|
"status": "timeout",
|
|
"message": fmt.Sprintf("error: %s", err.Error()),
|
|
}, nil
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
elapsed := time.Since(startTime).Milliseconds()
|
|
if resp.StatusCode == 200 {
|
|
return map[string]interface{}{
|
|
"service_name": name,
|
|
"status": "alive",
|
|
"message": fmt.Sprintf("Confirm elapsed: %.1f ms.", float64(elapsed)),
|
|
}, nil
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"service_name": name,
|
|
"status": "timeout",
|
|
"message": fmt.Sprintf("Confirm elapsed: %.1f ms.", float64(elapsed)),
|
|
}, nil
|
|
}
|
|
|
|
// checkTaskExecutorAlive checks if task executor is alive
|
|
func (s *Service) checkTaskExecutorAlive(name string) (map[string]interface{}, error) {
|
|
// TODO: Implement actual task executor health check
|
|
return map[string]interface{}{
|
|
"service_name": name,
|
|
"status": "unknown",
|
|
"message": "Task executor health check not implemented",
|
|
}, nil
|
|
}
|
|
|
|
// ShutdownService shutdown service
|
|
func (s *Service) ShutdownService(serviceID string) (map[string]interface{}, error) {
|
|
// TODO: Implement with proper service manager
|
|
return map[string]interface{}{
|
|
"service_id": serviceID,
|
|
"status": "shutdown",
|
|
}, nil
|
|
}
|
|
|
|
// RestartService restart service
|
|
func (s *Service) RestartService(serviceID string) (map[string]interface{}, error) {
|
|
// TODO: Implement with proper service manager
|
|
return map[string]interface{}{
|
|
"service_id": serviceID,
|
|
"status": "restarted",
|
|
}, nil
|
|
}
|
|
|
|
// Variable/Settings methods
|
|
|
|
// GetVariable get variable
|
|
func (s *Service) GetVariable(varName string) (map[string]interface{}, error) {
|
|
// TODO: Implement with settings manager
|
|
return map[string]interface{}{
|
|
"var_name": varName,
|
|
"var_value": "",
|
|
}, nil
|
|
}
|
|
|
|
// GetAllVariables get all variables
|
|
func (s *Service) GetAllVariables() ([]map[string]interface{}, error) {
|
|
// TODO: Implement with settings manager
|
|
return []map[string]interface{}{}, nil
|
|
}
|
|
|
|
// SetVariable set variable
|
|
func (s *Service) SetVariable(varName, varValue string) error {
|
|
// TODO: Implement with settings manager
|
|
_ = varName
|
|
_ = varValue
|
|
return nil
|
|
}
|
|
|
|
// Config methods
|
|
|
|
// GetAllConfigs get all configs
|
|
func (s *Service) GetAllConfigs() ([]map[string]interface{}, error) {
|
|
// TODO: Implement with config manager
|
|
return []map[string]interface{}{}, nil
|
|
}
|
|
|
|
// Environment methods
|
|
|
|
// GetAllEnvironments get all environments
|
|
func (s *Service) GetAllEnvironments() ([]map[string]interface{}, error) {
|
|
// TODO: Implement with environment manager
|
|
return []map[string]interface{}{}, nil
|
|
}
|
|
|
|
// Version methods
|
|
|
|
// GetVersion get RAGFlow version
|
|
func (s *Service) GetVersion() string {
|
|
return utility.GetRAGFlowVersion()
|
|
}
|
|
|
|
// Sandbox methods
|
|
|
|
// ListSandboxProviders list sandbox providers
|
|
func (s *Service) ListSandboxProviders() ([]map[string]interface{}, error) {
|
|
// TODO: Implement with sandbox manager
|
|
return []map[string]interface{}{}, nil
|
|
}
|
|
|
|
// GetSandboxProviderSchema get sandbox provider schema
|
|
func (s *Service) GetSandboxProviderSchema(providerID string) (map[string]interface{}, error) {
|
|
// TODO: Implement with sandbox manager
|
|
return map[string]interface{}{}, nil
|
|
}
|
|
|
|
// GetSandboxConfig get sandbox config
|
|
func (s *Service) GetSandboxConfig() (map[string]interface{}, error) {
|
|
// TODO: Implement with sandbox manager
|
|
return map[string]interface{}{}, nil
|
|
}
|
|
|
|
// SetSandboxConfig set sandbox config
|
|
func (s *Service) SetSandboxConfig(providerType string, config map[string]interface{}, setActive bool) (map[string]interface{}, error) {
|
|
// TODO: Implement with sandbox manager
|
|
return map[string]interface{}{
|
|
"provider_type": providerType,
|
|
"config": config,
|
|
"set_active": setActive,
|
|
}, nil
|
|
}
|
|
|
|
// TestSandboxConnection test sandbox connection
|
|
func (s *Service) TestSandboxConnection(providerType string, config map[string]interface{}) (map[string]interface{}, error) {
|
|
// TODO: Implement with sandbox manager
|
|
return map[string]interface{}{
|
|
"provider_type": providerType,
|
|
"config": config,
|
|
"connected": true,
|
|
}, nil
|
|
}
|
|
|
|
// HandleHeartbeat handle heartbeat
|
|
func (s *Service) HandleHeartbeat(msg *common.BaseMessage) error {
|
|
status := &common.BaseMessage{
|
|
ServerName: msg.ServerName,
|
|
ServerType: msg.ServerType,
|
|
Host: msg.Host,
|
|
Port: msg.Port,
|
|
Version: msg.Version,
|
|
Timestamp: msg.Timestamp,
|
|
Ext: msg.Ext,
|
|
}
|
|
GlobalServerStatusStore.UpdateStatus(msg.ServerName, status)
|
|
return nil
|
|
}
|
|
|
|
// InitDefaultAdmin initialize default admin user
|
|
// This matches Python's init_default_admin behavior
|
|
func (s *Service) InitDefaultAdmin() error {
|
|
// Default superuser settings (matching Python's DEFAULT_SUPERUSER_* defaults)
|
|
defaultNickname := "admin"
|
|
defaultEmail := "admin@ragflow.io"
|
|
defaultPassword := "admin"
|
|
|
|
// Query superusers
|
|
var users []*model.User
|
|
err := dao.DB.Where("is_superuser = ? AND status = ?", true, "1").Find(&users).Error
|
|
if err != nil {
|
|
return fmt.Errorf("failed to query superusers: %w", err)
|
|
}
|
|
|
|
if len(users) == 0 {
|
|
now := time.Now().Unix()
|
|
nowDate := time.Now()
|
|
userID := utility.GenerateToken()
|
|
accessToken := utility.GenerateToken()
|
|
status := "1"
|
|
loginChannel := "password"
|
|
isSuperuser := true
|
|
|
|
// Python: password = encode_to_base64(password) = base64.b64encode(password)
|
|
// Then: generate_password_hash(base64_password) creates werkzeug hash
|
|
password := base64.StdEncoding.EncodeToString([]byte(defaultPassword))
|
|
hashedPassword, err := GenerateWerkzeugPasswordHash(password, 150000)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to hash password: %w", err)
|
|
}
|
|
|
|
user := &model.User{
|
|
ID: userID,
|
|
Email: defaultEmail,
|
|
Nickname: defaultNickname,
|
|
Password: &hashedPassword,
|
|
AccessToken: &accessToken,
|
|
Status: &status,
|
|
IsActive: "1",
|
|
IsAuthenticated: "1",
|
|
IsAnonymous: "0",
|
|
LoginChannel: &loginChannel,
|
|
IsSuperuser: &isSuperuser,
|
|
BaseModel: model.BaseModel{
|
|
CreateTime: &now,
|
|
CreateDate: &nowDate,
|
|
UpdateTime: &now,
|
|
UpdateDate: &nowDate,
|
|
},
|
|
}
|
|
|
|
if err := dao.DB.Create(user).Error; err != nil {
|
|
return fmt.Errorf("can't init admin: %w", err)
|
|
}
|
|
|
|
if err := s.addTenantForAdmin(userID, defaultNickname); err != nil {
|
|
return fmt.Errorf("failed to add tenant for admin: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
for _, user := range users {
|
|
if user.IsActive != "1" {
|
|
return fmt.Errorf("no active admin. Please update 'is_active' in db manually")
|
|
}
|
|
}
|
|
|
|
for _, user := range users {
|
|
if user.Email == defaultEmail {
|
|
// Check if tenant exists
|
|
var count int64
|
|
dao.DB.Model(&model.UserTenant{}).Where("user_id = ? AND status = ?", user.ID, "1").Count(&count)
|
|
if count == 0 {
|
|
nickname := defaultNickname
|
|
if user.Nickname != "" {
|
|
nickname = user.Nickname
|
|
}
|
|
if err := s.addTenantForAdmin(user.ID, nickname); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// addTenantForAdmin add tenant for admin user
|
|
func (s *Service) addTenantForAdmin(userID, nickname string) error {
|
|
now := time.Now().Unix()
|
|
nowDate := time.Now()
|
|
status := "1"
|
|
role := "owner"
|
|
tenantName := nickname + "'s Kingdom"
|
|
|
|
tenant := &model.Tenant{
|
|
ID: userID,
|
|
Name: &tenantName,
|
|
BaseModel: model.BaseModel{
|
|
CreateTime: &now,
|
|
CreateDate: &nowDate,
|
|
UpdateTime: &now,
|
|
UpdateDate: &nowDate,
|
|
},
|
|
}
|
|
|
|
if err := dao.DB.Create(tenant).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
userTenant := &model.UserTenant{
|
|
TenantID: userID,
|
|
UserID: userID,
|
|
InvitedBy: userID,
|
|
Role: role,
|
|
Status: &status,
|
|
BaseModel: model.BaseModel{
|
|
CreateTime: &now,
|
|
CreateDate: &nowDate,
|
|
UpdateTime: &now,
|
|
UpdateDate: &nowDate,
|
|
},
|
|
}
|
|
|
|
return dao.DB.Create(userTenant).Error
|
|
}
|