Add more API of admin server of go (#13403)

### What problem does this PR solve?

Add APIs to admin server.

### Type of change

- [x] New Feature (non-breaking change which adds functionality)

---------

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
This commit is contained in:
Jin Hai
2026-03-09 10:44:53 +08:00
committed by GitHub
parent ab6ca75245
commit 610c1b507d
12 changed files with 1987 additions and 341 deletions

View File

@@ -17,56 +17,37 @@
package main
import (
"context"
"flag"
"fmt"
"net/http"
"os"
"os/signal"
"ragflow/internal/cache"
"ragflow/internal/engine"
"syscall"
"time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"ragflow/internal/admin"
"ragflow/internal/dao"
"ragflow/internal/handler"
"ragflow/internal/logger"
"ragflow/internal/server"
"ragflow/internal/service"
"ragflow/internal/utility"
)
// AdminServer admin server
type AdminServer struct {
router *admin.Router
handler *admin.Handler
service *admin.Service
engine *gin.Engine
port string
}
// NewAdminServer create admin server
func NewAdminServer(port string) *AdminServer {
return &AdminServer{
port: port,
}
}
// Init initialize admin server
func (s *AdminServer) Init() error {
gin.SetMode(gin.ReleaseMode)
s.engine = gin.New()
s.engine.Use(gin.Recovery())
// Initialize layers
s.service = admin.NewService()
s.handler = admin.NewHandler(s.service)
s.router = admin.NewRouter(s.handler)
// Setup routes
s.router.Setup(s.engine)
return nil
}
// Run start admin server
func (s *AdminServer) Run() error {
return s.engine.Run(":" + s.port)
router *admin.Router
handler *admin.Handler
service *admin.Service
userHandler *handler.UserHandler
engine *gin.Engine
port string
}
func main() {
@@ -85,14 +66,29 @@ func main() {
os.Exit(1)
}
cfg := server.GetConfig()
// Reinitialize logger with configured level if different
if cfg.Log.Level != "" && cfg.Log.Level != "info" {
if err := logger.Init(cfg.Log.Level); err != nil {
logger.Error("Failed to reinitialize logger with configured level", err)
}
}
// Set logger for server package
server.SetLogger(logger.Logger)
cfg := server.GetConfig()
logger.Info("Configuration loaded",
zap.String("database_host", cfg.Database.Host),
zap.Int("database_port", cfg.Database.Port),
)
logger.Info("Server mode", zap.String("mode", cfg.Server.Mode))
// Print all configuration settings
server.PrintAll()
// Set Gin mode
if cfg.Server.Mode == "release" {
gin.SetMode(gin.ReleaseMode)
} else {
gin.SetMode(gin.DebugMode)
}
// Initialize database
if err := dao.InitDB(); err != nil {
@@ -100,12 +96,57 @@ func main() {
os.Exit(1)
}
// Create and start admin server (port 9381)
adminServer := NewAdminServer("9381")
if err := adminServer.Init(); err != nil {
logger.Error("Failed to initialize admin server", err)
os.Exit(1)
// Initialize doc engine
if err := engine.Init(&cfg.DocEngine); err != nil {
logger.Fatal("Failed to initialize doc engine", zap.Error(err))
}
defer engine.Close()
// Initialize Redis cache
if err := cache.Init(&cfg.Redis); err != nil {
logger.Fatal("Failed to initialize Redis", zap.Error(err))
}
defer cache.Close()
// Initialize server variables (runtime variables that can change during operation)
// This must be done after Cache is initialized
if err := server.InitVariables(cache.Get()); err != nil {
logger.Warn("Failed to initialize server variables from Redis, using defaults", zap.String("error", err.Error()))
}
adminService := admin.NewService()
userService := service.NewUserService()
adminHandler := admin.NewHandler(adminService, userService)
// Initialize router
r := admin.NewRouter(adminHandler)
// Create Gin engine
ginEngine := gin.New()
// Middleware
if cfg.Server.Mode == "debug" {
ginEngine.Use(gin.Logger())
}
ginEngine.Use(gin.Recovery())
// Log request URL for every request
ginEngine.Use(func(c *gin.Context) {
logger.Info("HTTP Request", zap.String("url", c.Request.URL.String()), zap.String("method", c.Request.Method))
c.Next()
})
// Setup routes
r.Setup(ginEngine)
// Create HTTP server
addr := fmt.Sprintf(":9381")
srv := &http.Server{
Addr: addr,
Handler: ginEngine,
}
// Print RAGFlow version
logger.Info("RAGFlow version", zap.String("version", utility.GetRAGFlowVersion()))
// Print all configuration settings
server.PrintAll()
@@ -118,11 +159,31 @@ func main() {
" / _, _/ ___ / /_/ / __/ / / /_/ / |/ |/ / / ___ / /_/ / / / / / / / / / /\n" +
" /_/ |_/_/ |_\\____/_/ /_/\\____/|__/|__/ /_/ |_\\__,_/_/ /_/ /_/_/_/ /_/ \n")
// Print RAGFlow version
logger.Info(fmt.Sprintf("Version: %s", utility.GetRAGFlowVersion()))
logger.Info(fmt.Sprintf("Starting RAGFlow admin server on port: 9381"))
if err := adminServer.Run(); err != nil {
logger.Error("Admin server error", err)
os.Exit(1)
// Start server in a goroutine
go func() {
logger.Info(fmt.Sprintf("Version: %s", utility.GetRAGFlowVersion()))
logger.Info(fmt.Sprintf("Starting RAGFlow admin server on port: 9381"))
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Fatal("Failed to start server", zap.Error(err))
}
}()
// Wait for interrupt signal to gracefully shutdown
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGUSR2)
sig := <-quit
logger.Info("Received signal", zap.String("signal", sig.String()))
logger.Info("Shutting down server...")
// Create context with timeout for graceful shutdown
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Shutdown server
if err := srv.Shutdown(ctx); err != nil {
logger.Fatal("Server forced to shutdown", zap.Error(err))
}
logger.Info("Server exited")
}

File diff suppressed because it is too large Load Diff

View File

@@ -18,16 +18,21 @@ package admin
import (
"github.com/gin-gonic/gin"
"ragflow/internal/handler"
)
// Router admin router
type Router struct {
handler *Handler
handler *Handler
userHandler *handler.UserHandler
}
// NewRouter create admin router
func NewRouter(handler *Handler) *Router {
return &Router{handler: handler}
return &Router{
handler: handler,
}
}
// Setup setup routes
@@ -35,28 +40,78 @@ func (r *Router) Setup(engine *gin.Engine) {
// Health check
engine.GET("/health", r.handler.Health)
// Admin API routes
admin := engine.Group("/admin")
// Admin API routes with prefix /api/v1/admin
admin := engine.Group("/api/v1/admin")
{
// Auth
// Public routes
admin.GET("/ping", r.handler.Ping)
admin.POST("/login", r.handler.Login)
// Protected routes
protected := admin.Group("")
protected.Use(r.handler.AuthMiddleware())
{
// Auth
protected.GET("/auth", r.handler.AuthCheck)
protected.GET("/logout", r.handler.Logout)
// User management
protected.GET("/users", r.handler.ListUsers)
protected.GET("/users/:id", r.handler.GetUser)
protected.PUT("/users/:id", r.handler.UpdateUser)
protected.DELETE("/users/:id", r.handler.DeleteUser)
protected.POST("/users", r.handler.CreateUser)
protected.GET("/users/:username", r.handler.GetUser)
protected.DELETE("/users/:username", r.handler.DeleteUser)
protected.PUT("/users/:username/password", r.handler.ChangePassword)
protected.PUT("/users/:username/activate", r.handler.UpdateUserActivateStatus)
protected.PUT("/users/:username/admin", r.handler.GrantAdmin)
protected.DELETE("/users/:username/admin", r.handler.RevokeAdmin)
protected.GET("/users/:username/datasets", r.handler.GetUserDatasets)
protected.GET("/users/:username/agents", r.handler.GetUserAgents)
// System config
protected.GET("/config", r.handler.GetConfig)
protected.PUT("/config", r.handler.UpdateConfig)
// API Keys
protected.GET("/users/:username/keys", r.handler.GetUserAPIKeys)
protected.POST("/users/:username/keys", r.handler.GenerateUserAPIKey)
protected.DELETE("/users/:username/keys/:key", r.handler.DeleteUserAPIKey)
// System status
protected.GET("/status", r.handler.GetStatus)
// Role management
protected.GET("/roles", r.handler.ListRoles)
protected.POST("/roles", r.handler.CreateRole)
protected.GET("/roles/:role_name", r.handler.GetRole)
protected.PUT("/roles/:role_name", r.handler.UpdateRole)
protected.DELETE("/roles/:role_name", r.handler.DeleteRole)
protected.GET("/roles/:role_name/permission", r.handler.GetRolePermission)
protected.POST("/roles/:role_name/permission", r.handler.GrantRolePermission)
protected.DELETE("/roles/:role_name/permission", r.handler.RevokeRolePermission)
// User roles and permissions
protected.PUT("/users/:username/role", r.handler.UpdateUserRole)
protected.GET("/users/:username/permission", r.handler.GetUserPermission)
// Service management
protected.GET("/services", r.handler.GetServices)
protected.GET("/service_types/:service_type", r.handler.GetServicesByType)
protected.GET("/services/:service_id", r.handler.GetService)
protected.DELETE("/services/:service_id", r.handler.ShutdownService)
protected.PUT("/services/:service_id", r.handler.RestartService)
// Variables/Settings
protected.GET("/variables", r.handler.GetVariables)
protected.PUT("/variables", r.handler.SetVariable)
// Configs
protected.GET("/configs", r.handler.GetConfigs)
// Environments
protected.GET("/environments", r.handler.GetEnvironments)
// Version
protected.GET("/version", r.handler.GetVersion)
// Sandbox
protected.GET("/sandbox/providers", r.handler.ListSandboxProviders)
protected.GET("/sandbox/providers/:provider_id/schema", r.handler.GetSandboxProviderSchema)
protected.GET("/sandbox/config", r.handler.GetSandboxConfig)
protected.POST("/sandbox/config", r.handler.SetSandboxConfig)
protected.POST("/sandbox/test", r.handler.TestSandboxConnection)
}
}

View File

@@ -17,10 +17,20 @@
package admin
import (
"time"
"crypto/rand"
"crypto/tls"
"encoding/hex"
"errors"
"fmt"
"net/http"
"os"
"ragflow/internal/cache"
"ragflow/internal/dao"
"ragflow/internal/engine/elasticsearch"
"ragflow/internal/model"
"ragflow/internal/server"
"ragflow/internal/utility"
"time"
)
// Service admin service layer
@@ -57,13 +67,13 @@ func (s *Service) Login(req *LoginRequest) (*LoginResponse, error) {
return nil, ErrInvalidCredentials
}
// Verify password
if user.Password == nil || *user.Password != req.Password {
return nil, ErrInvalidCredentials
// Check if user is active
if user.IsActive != "1" {
return nil, errors.New("user is not active")
}
// Generate access token
token := generateToken()
token := utility.GenerateToken()
if err := s.userDAO.UpdateAccessToken(user, token); err != nil {
return nil, err
}
@@ -76,155 +86,649 @@ func (s *Service) Login(req *LoginRequest) (*LoginResponse, error) {
}, nil
}
// ListUsersRequest list users request
type ListUsersRequest struct {
Offset int
Limit int
// 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
}
// ListUsersResponse list users response
type ListUsersResponse struct {
Users []*UserInfo
Total int64
}
// UserInfo user info
type UserInfo struct {
ID string
Email string
Nickname string
IsActive string
CreateTime *int64
UpdateTime *int64
// 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(req *ListUsersRequest) (*ListUsersResponse, error) {
users, total, err := s.userDAO.List(req.Offset, req.Limit)
func (s *Service) ListUsers() ([]map[string]interface{}, error) {
users, _, err := s.userDAO.List(0, 0)
if err != nil {
return nil, err
}
var result []*UserInfo
result := make([]map[string]interface{}, 0, len(users))
for _, user := range users {
result = append(result, &UserInfo{
ID: user.ID,
Email: user.Email,
Nickname: user.Nickname,
IsActive: user.IsActive,
CreateTime: user.CreateTime,
UpdateTime: user.UpdateTime,
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
}
return &ListUsersResponse{
Users: result,
Total: total,
// CreateUser create a new user
func (s *Service) CreateUser(username, password, role string) (map[string]interface{}, error) {
// TODO: Implement user creation with proper password hashing
return map[string]interface{}{
"username": username,
"role": role,
}, nil
}
// GetUserRequest get user request
type GetUserRequest struct {
ID string
}
// GetUser get user by ID
func (s *Service) GetUser(req *GetUserRequest) (*UserInfo, error) {
// 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("id = ?", req.ID).First(&user).Error
err := dao.DB.Where("email = ?", username).First(&user).Error
if err != nil {
return nil, ErrUserNotFound
}
return &UserInfo{
ID: user.ID,
Email: user.Email,
Nickname: user.Nickname,
IsActive: user.IsActive,
CreateTime: user.CreateTime,
UpdateTime: user.UpdateTime,
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
}
// UpdateUserRequest update user request
type UpdateUserRequest struct {
ID string
Nickname string
IsActive *string
}
// UpdateUser update user
func (s *Service) UpdateUser(req *UpdateUserRequest) error {
var user model.User
if err := dao.DB.Where("id = ?", req.ID).First(&user).Error; err != nil {
return ErrUserNotFound
}
if req.Nickname != "" {
user.Nickname = req.Nickname
}
if req.IsActive != nil {
user.IsActive = *req.IsActive
}
return dao.DB.Save(&user).Error
}
// DeleteUserRequest delete user request
type DeleteUserRequest struct {
ID string
}
// DeleteUser delete user
func (s *Service) DeleteUser(req *DeleteUserRequest) error {
return dao.DB.Where("id = ?", req.ID).Delete(&model.User{}).Error
}
// GetSystemConfig get system config
func (s *Service) GetSystemConfig() map[string]interface{} {
// TODO: Load from database or config file
return map[string]interface{}{
"system_name": "RAGFlow Admin",
"version": "1.0.0",
}
}
// UpdateSystemConfig update system config
func (s *Service) UpdateSystemConfig(config map[string]interface{}) error {
// TODO: Save to database or config file
func (s *Service) DeleteUser(username string) error {
// TODO: Implement user deletion
return nil
}
// GetSystemStatus get system status
func (s *Service) GetSystemStatus() map[string]interface{} {
// TODO: Get real status from services
return map[string]interface{}{
"status": "running",
"uptime": time.Since(time.Now()).String(),
"db_status": "connected",
// ChangePassword change user password
func (s *Service) ChangePassword(username, newPassword string) error {
// TODO: Implement password change
return nil
}
// UpdateUserActivateStatus update user activate status
func (s *Service) UpdateUserActivateStatus(username string, isActive bool) error {
// TODO: Implement activate status update
return nil
}
// GrantAdmin grant admin privileges
func (s *Service) GrantAdmin(username string) error {
// TODO: Implement grant admin
return nil
}
// RevokeAdmin revoke admin privileges
func (s *Service) RevokeAdmin(username string) error {
// TODO: Implement revoke admin
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
}
// GetAllServices get all services
func (s *Service) GetAllServices() ([]map[string]interface{}, error) {
allConfigs := server.GetAllConfigs()
var result []map[string]interface{}
for _, configDict := range allConfigs {
// 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)
}
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
}
}
// ValidateToken validate access token
func (s *Service) ValidateToken(token string) (*model.User, error) {
user, err := s.userDAO.GetByAccessToken(token)
// 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 nil, ErrInvalidToken
return map[string]interface{}{
"service_name": name,
"status": "timeout",
"elapsed": fmt.Sprintf("%.1f", time.Since(startTime).Milliseconds()),
"message": err.Error(),
}, nil
}
return user, 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
}
// generateToken generate a simple token
func generateToken() string {
return time.Now().Format("20060102150405") + randomString(16)
// 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
}
// randomString generate random string
func randomString(n int) string {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, n)
for i := range b {
b[i] = letters[time.Now().UnixNano()%int64(len(letters))]
// 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"
}
return string(b)
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 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
}
// 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:9000"
}
// Determine scheme
scheme := "http"
if secure {
scheme = "https"
}
url := fmt.Sprintf("%s://%s/minio/health/live", scheme, host)
// 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
}

View File

@@ -93,7 +93,14 @@ func (dao *UserDAO) List(offset, limit int) ([]*model.User, int64, error) {
return nil, 0, err
}
err := DB.Offset(offset).Limit(limit).Find(&users).Error
query := DB.Model(&model.User{})
if offset > 0 {
query = query.Offset(offset)
}
if limit > 0 {
query = query.Limit(limit)
}
err := query.Find(&users).Error
return users, total, err
}

View File

@@ -18,6 +18,7 @@ package elasticsearch
import (
"context"
"encoding/json"
"fmt"
"net/http"
"ragflow/internal/server"
@@ -101,3 +102,144 @@ func (e *elasticsearchEngine) Close() error {
// Go-elasticsearch client doesn't have a Close method, connection is managed by the transport
return nil
}
// GetClusterStats gets Elasticsearch cluster statistics
// Reference: curl -XGET "http://{es_host}/_cluster/stats" -H "kbn-xsrf: reporting"
func (e *elasticsearchEngine) GetClusterStats() (map[string]interface{}, error) {
req := esapi.ClusterStatsRequest{}
res, err := req.Do(context.Background(), e.client)
if err != nil {
return nil, fmt.Errorf("failed to get cluster stats: %w", err)
}
defer res.Body.Close()
if res.IsError() {
return nil, fmt.Errorf("elasticsearch cluster stats returned error: %s", res.Status())
}
var rawStats map[string]interface{}
if err := json.NewDecoder(res.Body).Decode(&rawStats); err != nil {
return nil, fmt.Errorf("failed to decode cluster stats: %w", err)
}
result := make(map[string]interface{})
// Basic cluster info
if clusterName, ok := rawStats["cluster_name"].(string); ok {
result["cluster_name"] = clusterName
}
if status, ok := rawStats["status"].(string); ok {
result["status"] = status
}
// Indices info
if indices, ok := rawStats["indices"].(map[string]interface{}); ok {
if count, ok := indices["count"].(float64); ok {
result["indices"] = int(count)
}
if shards, ok := indices["shards"].(map[string]interface{}); ok {
if total, ok := shards["total"].(float64); ok {
result["indices_shards"] = int(total)
}
}
if docs, ok := indices["docs"].(map[string]interface{}); ok {
if docCount, ok := docs["count"].(float64); ok {
result["docs"] = int64(docCount)
}
if deleted, ok := docs["deleted"].(float64); ok {
result["docs_deleted"] = int64(deleted)
}
}
if store, ok := indices["store"].(map[string]interface{}); ok {
if sizeInBytes, ok := store["size_in_bytes"].(float64); ok {
result["store_size"] = convertBytes(int64(sizeInBytes))
}
if totalDataSetSize, ok := store["total_data_set_size_in_bytes"].(float64); ok {
result["total_dataset_size"] = convertBytes(int64(totalDataSetSize))
}
}
if mappings, ok := indices["mappings"].(map[string]interface{}); ok {
if fieldCount, ok := mappings["total_field_count"].(float64); ok {
result["mappings_fields"] = int(fieldCount)
}
if dedupFieldCount, ok := mappings["total_deduplicated_field_count"].(float64); ok {
result["mappings_deduplicated_fields"] = int(dedupFieldCount)
}
if dedupSize, ok := mappings["total_deduplicated_mapping_size_in_bytes"].(float64); ok {
result["mappings_deduplicated_size"] = convertBytes(int64(dedupSize))
}
}
}
// Nodes info
if nodes, ok := rawStats["nodes"].(map[string]interface{}); ok {
if count, ok := nodes["count"].(map[string]interface{}); ok {
if total, ok := count["total"].(float64); ok {
result["nodes"] = int(total)
}
}
if versions, ok := nodes["versions"].([]interface{}); ok {
result["nodes_version"] = versions
}
if os, ok := nodes["os"].(map[string]interface{}); ok {
if mem, ok := os["mem"].(map[string]interface{}); ok {
if totalInBytes, ok := mem["total_in_bytes"].(float64); ok {
result["os_mem"] = convertBytes(int64(totalInBytes))
}
if usedInBytes, ok := mem["used_in_bytes"].(float64); ok {
result["os_mem_used"] = convertBytes(int64(usedInBytes))
}
if usedPercent, ok := mem["used_percent"].(float64); ok {
result["os_mem_used_percent"] = usedPercent
}
}
}
if jvm, ok := nodes["jvm"].(map[string]interface{}); ok {
if versions, ok := jvm["versions"].([]interface{}); ok && len(versions) > 0 {
if version0, ok := versions[0].(map[string]interface{}); ok {
if vmVersion, ok := version0["vm_version"].(string); ok {
result["jvm_versions"] = vmVersion
}
}
}
if mem, ok := jvm["mem"].(map[string]interface{}); ok {
if heapUsed, ok := mem["heap_used_in_bytes"].(float64); ok {
result["jvm_heap_used"] = convertBytes(int64(heapUsed))
}
if heapMax, ok := mem["heap_max_in_bytes"].(float64); ok {
result["jvm_heap_max"] = convertBytes(int64(heapMax))
}
}
}
}
return result, nil
}
// convertBytes converts bytes to human readable format
func convertBytes(bytes int64) string {
const (
KB = 1024
MB = 1024 * KB
GB = 1024 * MB
TB = 1024 * GB
PB = 1024 * TB
)
if bytes >= PB {
return fmt.Sprintf("%.2f pb", float64(bytes)/float64(PB))
}
if bytes >= TB {
return fmt.Sprintf("%.2f tb", float64(bytes)/float64(TB))
}
if bytes >= GB {
return fmt.Sprintf("%.2f gb", float64(bytes)/float64(GB))
}
if bytes >= MB {
return fmt.Sprintf("%.2f mb", float64(bytes)/float64(MB))
}
if bytes >= KB {
return fmt.Sprintf("%.2f kb", float64(bytes)/float64(KB))
}
return fmt.Sprintf("%d b", bytes)
}

View File

@@ -18,6 +18,7 @@ package server
import (
"fmt"
"net/url"
"os"
"strconv"
"strings"
@@ -111,6 +112,7 @@ var (
globalConfig *Config
globalViper *viper.Viper
zapLogger *zap.Logger
allConfigs []map[string]interface{}
)
// Init initialize configuration
@@ -147,6 +149,153 @@ func Init(configPath string) error {
// Save viper instance
globalViper = v
docEngine := os.Getenv("DOC_ENGINE")
if docEngine == "" {
docEngine = "elasticsearch"
}
id := 0
for k, v := range globalViper.AllSettings() {
configDict, ok := v.(map[string]interface{})
if !ok {
continue
}
switch k {
case "ragflow":
configDict["id"] = id
configDict["name"] = fmt.Sprintf("ragflow_%d", id)
configDict["service_type"] = "ragflow_server"
configDict["extra"] = map[string]interface{}{}
configDict["port"] = configDict["http_port"]
delete(configDict, "http_port")
case "es":
// Skip if retrieval_type doesn't match doc_engine
if docEngine != "elasticsearch" {
continue
}
hosts := getString(configDict, "hosts")
host, port := parseHostPort(hosts)
username := getString(configDict, "username")
password := getString(configDict, "password")
configDict["id"] = id
configDict["name"] = "elasticsearch"
configDict["host"] = host
configDict["port"] = port
configDict["service_type"] = "retrieval"
configDict["extra"] = map[string]interface{}{
"retrieval_type": "elasticsearch",
"username": username,
"password": password,
}
delete(configDict, "hosts")
delete(configDict, "username")
delete(configDict, "password")
case "infinity":
// Skip if retrieval_type doesn't match doc_engine
if docEngine != "infinity" {
continue
}
uri := getString(configDict, "uri")
host, port := parseHostPort(uri)
dbName := getString(configDict, "db_name")
if dbName == "" {
dbName = "default_db"
}
configDict["id"] = id
configDict["name"] = "infinity"
configDict["host"] = host
configDict["port"] = port
configDict["service_type"] = "retrieval"
configDict["extra"] = map[string]interface{}{
"retrieval_type": "infinity",
"db_name": dbName,
}
case "minio":
hostPort := getString(configDict, "host")
host, port := parseHostPort(hostPort)
user := getString(configDict, "user")
password := getString(configDict, "password")
configDict["id"] = id
configDict["name"] = "minio"
configDict["host"] = host
configDict["port"] = port
configDict["service_type"] = "file_store"
configDict["extra"] = map[string]interface{}{
"store_type": "minio",
"user": user,
"password": password,
}
delete(configDict, "bucket")
delete(configDict, "user")
delete(configDict, "password")
case "redis":
hostPort := getString(configDict, "host")
host, port := parseHostPort(hostPort)
password := getString(configDict, "password")
db := getInt(configDict, "db")
configDict["id"] = id
configDict["name"] = "redis"
configDict["host"] = host
configDict["port"] = port
configDict["service_type"] = "message_queue"
configDict["extra"] = map[string]interface{}{
"mq_type": "redis",
"database": db,
"password": password,
}
delete(configDict, "password")
delete(configDict, "db")
case "mysql":
host := getString(configDict, "host")
port := getInt(configDict, "port")
user := getString(configDict, "user")
password := getString(configDict, "password")
configDict["id"] = id
configDict["name"] = "mysql"
configDict["host"] = host
configDict["port"] = port
configDict["service_type"] = "meta_data"
configDict["extra"] = map[string]interface{}{
"meta_type": "mysql",
"username": user,
"password": password,
}
delete(configDict, "stale_timeout")
delete(configDict, "max_connections")
delete(configDict, "max_allowed_packet")
delete(configDict, "user")
delete(configDict, "password")
case "task_executor":
mqType := getString(configDict, "message_queue_type")
configDict["id"] = id
configDict["name"] = "task_executor"
configDict["service_type"] = "task_executor"
configDict["extra"] = map[string]interface{}{
"message_queue_type": mqType,
}
delete(configDict, "message_queue_type")
case "admin":
// Skip admin section
continue
default:
// Skip unknown sections
continue
}
// Set default values for empty host/port
if configDict["host"] == "" {
configDict["host"] = "-"
}
if configDict["port"] == 0 {
configDict["port"] = "-"
}
delete(configDict, "prefix_path")
delete(configDict, "username")
allConfigs = append(allConfigs, configDict)
id++
}
// Unmarshal configuration to globalConfig
// Note: This will only unmarshal fields that match the Config struct
if err := v.Unmarshal(&globalConfig); err != nil {
@@ -278,6 +427,14 @@ func SetLogger(l *zap.Logger) {
zapLogger = l
}
func GetGlobalViperConfig() *viper.Viper {
return globalViper
}
func GetAllConfigs() []map[string]interface{} {
return allConfigs
}
// PrintAll prints all configuration settings
func PrintAll() {
if globalViper == nil {
@@ -292,3 +449,46 @@ func PrintAll() {
}
zapLogger.Info("=== End Configuration ===")
}
// parseHostPort parses host:port string and returns host and port
func parseHostPort(hostPort string) (string, int) {
if hostPort == "" {
return "", 0
}
// Handle URL format like http://host:port
if strings.Contains(hostPort, "://") {
u, err := url.Parse(hostPort)
if err == nil {
hostPort = u.Host
}
}
// Split host:port
parts := strings.Split(hostPort, ":")
host := parts[0]
port := 0
if len(parts) > 1 {
port, _ = strconv.Atoi(parts[1])
}
return host, port
}
// getString gets string value from map
func getString(m map[string]interface{}, key string) string {
if v, ok := m[key].(string); ok {
return v
}
return ""
}
// getInt gets int value from map
func getInt(m map[string]interface{}, key string) int {
if v, ok := m[key].(int); ok {
return v
}
if v, ok := m[key].(float64); ok {
return int(v)
}
return 0
}

View File

@@ -32,7 +32,6 @@ import (
"strings"
"time"
"github.com/google/uuid"
"golang.org/x/crypto/scrypt"
"ragflow/internal/dao"
@@ -123,8 +122,8 @@ func (s *UserService) Register(req *RegisterRequest) (*model.User, common.ErrorC
return nil, common.CodeServerError, fmt.Errorf("failed to hash password: %w", err)
}
userID := s.GenerateToken()
accessToken := s.GenerateToken()
userID := utility.GenerateToken()
accessToken := utility.GenerateToken()
status := "1"
loginChannel := "password"
isSuperuser := false
@@ -167,7 +166,7 @@ func (s *UserService) Register(req *RegisterRequest) (*model.User, common.ErrorC
tenant.CreateDate = &now_date
tenant.UpdateDate = &now_date
userTenantID := s.GenerateToken()
userTenantID := utility.GenerateToken()
userTenant := &model.UserTenant{
ID: userTenantID,
UserID: userID,
@@ -181,7 +180,7 @@ func (s *UserService) Register(req *RegisterRequest) (*model.User, common.ErrorC
userTenant.CreateDate = &now_date
userTenant.UpdateDate = &now_date
fileID := s.GenerateToken()
fileID := utility.GenerateToken()
rootFile := &model.File{
ID: fileID,
ParentID: fileID,
@@ -267,7 +266,7 @@ func (s *UserService) Login(req *LoginRequest) (*model.User, common.ErrorCode, e
}
// Generate new access token
token := s.GenerateToken()
token := utility.GenerateToken()
if err := s.UpdateUserAccessToken(user, token); err != nil {
return nil, common.CodeServerError, fmt.Errorf("failed to update access token: %w", err)
}
@@ -310,7 +309,8 @@ func (s *UserService) LoginByEmail(req *EmailLoginRequest) (*model.User, common.
return nil, common.CodeForbidden, fmt.Errorf("This account has been disabled, please contact the administrator!")
}
token := s.GenerateToken()
// Generate new access token
token := utility.GenerateToken()
user.AccessToken = &token
now := time.Now().Unix()
@@ -515,11 +515,6 @@ func (s *UserService) decryptPassword(encryptedPassword string) (string, error)
return string(plaintext), nil
}
// GenerateToken generates a new access token
func (s *UserService) GenerateToken() string {
return strings.ReplaceAll(uuid.New().String(), "-", "")
}
// GetUserByToken gets user by authorization header
// The token parameter is the authorization header value, which needs to be decrypted
// using itsdangerous URLSafeTimedSerializer to get the actual access_token
@@ -558,7 +553,7 @@ func (s *UserService) UpdateUserAccessToken(user *model.User, token string) erro
func (s *UserService) Logout(user *model.User) (common.ErrorCode, error) {
// Invalidate token by setting it to an invalid value
// Similar to Python implementation: "INVALID_" + secrets.token_hex(16)
invalidToken := "INVALID_" + s.GenerateToken()
invalidToken := "INVALID_" + utility.GenerateToken()
err := s.UpdateUserAccessToken(user, invalidToken)
if err != nil {
return common.CodeServerError, err

View File

@@ -25,6 +25,7 @@ import (
"fmt"
"strings"
"github.com/google/uuid"
"github.com/iromli/go-itsdangerous"
)
@@ -133,3 +134,7 @@ func GenerateSecretKey() (string, error) {
}
return hex.EncodeToString(bytes), nil
}
func GenerateToken() string {
return strings.ReplaceAll(uuid.New().String(), "-", "")
}

View File

@@ -518,7 +518,7 @@ Example: A 1 KB message with 1024-dim embedding uses ~9 KB. The 5 MB default lim
manualSetup: 'Pipeline',
builtIn: 'Built-in',
titleDescription:
'Update your memory configuration here, particularly the LLM and prompts.',
'Update your dataset configuration here, particularly the LLM and prompts.',
name: 'Dataset name',
photo: 'Dataset photo',
photoTip: 'You can upload an image up to 4 MB.',

View File

@@ -149,7 +149,7 @@ request.interceptors.response.use(
console.log('🚀 ~ error:', error);
// Handle HTTP 401 (token expired / invalid)
const status = error?.response?.status;
const status = error?.response?.status;
if (status === 401) {
if (!isRedirecting) {
isRedirecting = true;
@@ -164,8 +164,8 @@ request.interceptors.response.use(
redirectToLogin();
}
return Promise.reject(error);
}
return Promise.reject(error);
}
errorHandler(error);
return Promise.reject(error);

View File

@@ -117,10 +117,12 @@ request.interceptors.response.use(async (response: Response, options) => {
if (!isRedirecting) {
isRedirecting = true;
const data = await response.clone().json().catch(() => ({}));
const data = await response
.clone()
.json()
.catch(() => ({}));
const messageText =
data?.message || RetcodeMessage[401];
const messageText = data?.message || RetcodeMessage[401];
notification.error({
message: messageText,
description: messageText,