diff --git a/cmd/admin_server.go b/cmd/admin_server.go index 8a7587487..103ad6d22 100644 --- a/cmd/admin_server.go +++ b/cmd/admin_server.go @@ -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") } diff --git a/internal/admin/handler.go b/internal/admin/handler.go index 18511ea6f..526a22fa9 100644 --- a/internal/admin/handler.go +++ b/internal/admin/handler.go @@ -19,6 +19,10 @@ package admin import ( "errors" "net/http" + "ragflow/internal/server" + "ragflow/internal/service" + "ragflow/internal/utility" + "strconv" "github.com/gin-gonic/gin" ) @@ -32,12 +36,52 @@ var ( // Handler admin handler type Handler struct { - service *Service + service *Service + userService *service.UserService } // NewHandler create admin handler -func NewHandler(service *Service) *Handler { - return &Handler{service: service} +func NewHandler(service *Service, userService *service.UserService) *Handler { + return &Handler{service: service, userService: userService} +} + +// SuccessResponse success response +type SuccessResponse struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data"` +} + +// ErrorResponse error response +type ErrorResponse struct { + Code int `json:"code"` + Message string `json:"message"` +} + +// success returns success response +func success(c *gin.Context, data interface{}, message string) { + c.JSON(200, SuccessResponse{ + Code: 0, + Message: message, + Data: data, + }) +} + +// successNoData returns success response without data +func successNoData(c *gin.Context, message string) { + c.JSON(200, SuccessResponse{ + Code: 0, + Message: message, + Data: nil, + }) +} + +// error returns error response +func errorResponse(c *gin.Context, message string, code int) { + c.JSON(code, ErrorResponse{ + Code: code, + Message: message, + }) } // Health health check @@ -45,6 +89,11 @@ func (h *Handler) Health(c *gin.Context) { c.JSON(200, gin.H{"status": "ok"}) } +// Ping ping endpoint +func (h *Handler) Ping(c *gin.Context) { + successNoData(c, "PONG") +} + // LoginHTTPRequest login request body type LoginHTTPRequest struct { Email string `json:"email" binding:"required"` @@ -53,183 +102,811 @@ type LoginHTTPRequest struct { // Login handle admin login func (h *Handler) Login(c *gin.Context) { - var req LoginHTTPRequest + var req service.EmailLoginRequest if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(400, gin.H{"error": err.Error()}) + c.JSON(http.StatusBadRequest, gin.H{ + "code": 400, + "message": err.Error(), + }) return } - svcReq := &LoginRequest{ - Email: req.Email, - Password: req.Password, - } - - resp, err := h.service.Login(svcReq) + user, code, err := h.userService.LoginByEmail(&req) if err != nil { - if errors.Is(err, ErrInvalidCredentials) { - c.JSON(401, gin.H{"error": "invalid credentials"}) - return - } - c.JSON(500, gin.H{"error": err.Error()}) + c.JSON(http.StatusUnauthorized, gin.H{ + "code": code, + "message": err.Error(), + }) return } - c.JSON(200, gin.H{ - "token": resp.Token, - "user": gin.H{ - "id": resp.UserID, - "email": resp.Email, - "nickname": resp.Nickname, - }, + variables := server.GetVariables() + secretKey := variables.SecretKey + authToken, err := utility.DumpAccessToken(*user.AccessToken, secretKey) + + // Set Authorization header with access_token + if user.AccessToken != nil { + c.Header("Authorization", authToken) + } + // Set CORS headers + c.Header("Access-Control-Allow-Origin", "*") + c.Header("Access-Control-Allow-Methods", "*") + c.Header("Access-Control-Allow-Headers", "*") + c.Header("Access-Control-Expose-Headers", "Authorization") + + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "message": "Login successful", }) } +// Logout handle logout +func (h *Handler) Logout(c *gin.Context) { + user, exists := c.Get("user") + if !exists { + errorResponse(c, "Not authenticated", 401) + return + } + + if err := h.service.Logout(user); err != nil { + errorResponse(c, err.Error(), 500) + return + } + + successNoData(c, "Logout successful") +} + +// AuthCheck check admin auth +func (h *Handler) AuthCheck(c *gin.Context) { + successNoData(c, "Admin is authorized") +} + // ListUsers handle list users func (h *Handler) ListUsers(c *gin.Context) { - // Parse pagination params - offset := 0 - limit := 20 - - svcReq := &ListUsersRequest{ - Offset: offset, - Limit: limit, - } - - resp, err := h.service.ListUsers(svcReq) + users, err := h.service.ListUsers() if err != nil { - c.JSON(500, gin.H{"error": err.Error()}) + errorResponse(c, err.Error(), 500) return } - // Convert to response format - var result []gin.H - for _, user := range resp.Users { - result = append(result, gin.H{ - "id": user.ID, - "email": user.Email, - "nickname": user.Nickname, - "is_active": user.IsActive, - "create_time": user.CreateTime, - "update_time": user.UpdateTime, - }) + success(c, users, "Get all users") +} + +// CreateUserHTTPRequest create user request +type CreateUserHTTPRequest struct { + Username string `json:"username" binding:"required"` + Password string `json:"password" binding:"required"` + Role string `json:"role"` +} + +// CreateUser handle create user +func (h *Handler) CreateUser(c *gin.Context) { + var req CreateUserHTTPRequest + if err := c.ShouldBindJSON(&req); err != nil { + errorResponse(c, "Username and password are required", 400) + return } - c.JSON(200, gin.H{ - "data": result, - "total": resp.Total, - }) + if req.Role == "" { + req.Role = "user" + } + + userInfo, err := h.service.CreateUser(req.Username, req.Password, req.Role) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, userInfo, "User created successfully") } // GetUser handle get user func (h *Handler) GetUser(c *gin.Context) { - id := c.Param("id") - if id == "" { - c.JSON(400, gin.H{"error": "user id is required"}) + username := c.Param("username") + if username == "" { + errorResponse(c, "Username is required", 400) return } - svcReq := &GetUserRequest{ID: id} - user, err := h.service.GetUser(svcReq) + userDetails, err := h.service.GetUserDetails(username) if err != nil { if errors.Is(err, ErrUserNotFound) { - c.JSON(404, gin.H{"error": "user not found"}) + errorResponse(c, "User not found", 404) return } - c.JSON(500, gin.H{"error": err.Error()}) + errorResponse(c, err.Error(), 500) return } - c.JSON(200, gin.H{ - "id": user.ID, - "email": user.Email, - "nickname": user.Nickname, - "is_active": user.IsActive, - "create_time": user.CreateTime, - "update_time": user.UpdateTime, - }) -} - -// UpdateUserHTTPRequest update user request body -type UpdateUserHTTPRequest struct { - Nickname string `json:"nickname"` - IsActive *string `json:"is_active,omitempty"` -} - -// UpdateUser handle update user -func (h *Handler) UpdateUser(c *gin.Context) { - id := c.Param("id") - if id == "" { - c.JSON(400, gin.H{"error": "user id is required"}) - return - } - - var req UpdateUserHTTPRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(400, gin.H{"error": err.Error()}) - return - } - - svcReq := &UpdateUserRequest{ - ID: id, - Nickname: req.Nickname, - IsActive: req.IsActive, - } - - if err := h.service.UpdateUser(svcReq); err != nil { - if errors.Is(err, ErrUserNotFound) { - c.JSON(404, gin.H{"error": "user not found"}) - return - } - c.JSON(500, gin.H{"error": err.Error()}) - return - } - - c.JSON(200, gin.H{"message": "user updated"}) + success(c, userDetails, "") } // DeleteUser handle delete user func (h *Handler) DeleteUser(c *gin.Context) { - id := c.Param("id") - if id == "" { - c.JSON(400, gin.H{"error": "user id is required"}) + username := c.Param("username") + if username == "" { + errorResponse(c, "Username is required", 400) return } - svcReq := &DeleteUserRequest{ID: id} - if err := h.service.DeleteUser(svcReq); err != nil { - c.JSON(500, gin.H{"error": err.Error()}) + if err := h.service.DeleteUser(username); err != nil { + errorResponse(c, err.Error(), 500) return } - c.JSON(200, gin.H{"message": "user deleted"}) + successNoData(c, "User deleted successfully") } -// GetConfig handle get system config -func (h *Handler) GetConfig(c *gin.Context) { - config := h.service.GetSystemConfig() - c.JSON(200, config) +// ChangePasswordHTTPRequest change password request +type ChangePasswordHTTPRequest struct { + NewPassword string `json:"new_password" binding:"required"` } -// UpdateConfig handle update system config -func (h *Handler) UpdateConfig(c *gin.Context) { - var req map[string]interface{} +// ChangePassword handle change password +func (h *Handler) ChangePassword(c *gin.Context) { + username := c.Param("username") + if username == "" { + errorResponse(c, "Username is required", 400) + return + } + + var req ChangePasswordHTTPRequest if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(400, gin.H{"error": err.Error()}) + errorResponse(c, "New password is required", 400) return } - if err := h.service.UpdateSystemConfig(req); err != nil { - c.JSON(500, gin.H{"error": err.Error()}) + if err := h.service.ChangePassword(username, req.NewPassword); err != nil { + errorResponse(c, err.Error(), 500) return } - c.JSON(200, gin.H{"message": "config updated"}) + successNoData(c, "Password updated successfully") } -// GetStatus handle get system status -func (h *Handler) GetStatus(c *gin.Context) { - status := h.service.GetSystemStatus() - c.JSON(200, status) +// UpdateActivateStatusHTTPRequest update activate status request +type UpdateActivateStatusHTTPRequest struct { + ActivateStatus bool `json:"activate_status" binding:"required"` +} + +// UpdateUserActivateStatus handle update user activate status +func (h *Handler) UpdateUserActivateStatus(c *gin.Context) { + username := c.Param("username") + if username == "" { + errorResponse(c, "Username is required", 400) + return + } + + var req UpdateActivateStatusHTTPRequest + if err := c.ShouldBindJSON(&req); err != nil { + errorResponse(c, "Activation status is required", 400) + return + } + + if err := h.service.UpdateUserActivateStatus(username, req.ActivateStatus); err != nil { + errorResponse(c, err.Error(), 500) + return + } + + successNoData(c, "Activation status updated") +} + +// GrantAdmin handle grant admin role +func (h *Handler) GrantAdmin(c *gin.Context) { + username := c.Param("username") + if username == "" { + errorResponse(c, "Username is required", 400) + return + } + + // Get current user from context + currentUser, _ := c.Get("user") + if currentUser != nil && currentUser.(string) == username { + errorResponse(c, "can't grant current user: "+username, 409) + return + } + + if err := h.service.GrantAdmin(username); err != nil { + errorResponse(c, err.Error(), 500) + return + } + + successNoData(c, "Admin role granted") +} + +// RevokeAdmin handle revoke admin role +func (h *Handler) RevokeAdmin(c *gin.Context) { + username := c.Param("username") + if username == "" { + errorResponse(c, "Username is required", 400) + return + } + + // Get current user from context + currentUser, _ := c.Get("user") + if currentUser != nil && currentUser.(string) == username { + errorResponse(c, "can't revoke current user: "+username, 409) + return + } + + if err := h.service.RevokeAdmin(username); err != nil { + errorResponse(c, err.Error(), 500) + return + } + + successNoData(c, "Admin role revoked") +} + +// GetUserDatasets handle get user datasets +func (h *Handler) GetUserDatasets(c *gin.Context) { + username := c.Param("username") + if username == "" { + errorResponse(c, "Username is required", 400) + return + } + + datasets, err := h.service.GetUserDatasets(username) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, datasets, "") +} + +// GetUserAgents handle get user agents +func (h *Handler) GetUserAgents(c *gin.Context) { + username := c.Param("username") + if username == "" { + errorResponse(c, "Username is required", 400) + return + } + + agents, err := h.service.GetUserAgents(username) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, agents, "") +} + +// GetUserAPIKeys handle get user API keys +func (h *Handler) GetUserAPIKeys(c *gin.Context) { + username := c.Param("username") + if username == "" { + errorResponse(c, "Username is required", 400) + return + } + + apiKeys, err := h.service.GetUserAPIKeys(username) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, apiKeys, "Get user API keys") +} + +// GenerateUserAPIKey handle generate user API key +func (h *Handler) GenerateUserAPIKey(c *gin.Context) { + username := c.Param("username") + if username == "" { + errorResponse(c, "Username is required", 400) + return + } + + apiKey, err := h.service.GenerateUserAPIKey(username) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, apiKey, "API key generated successfully") +} + +// DeleteUserAPIKey handle delete user API key +func (h *Handler) DeleteUserAPIKey(c *gin.Context) { + username := c.Param("username") + key := c.Param("key") + if username == "" || key == "" { + errorResponse(c, "Username and key are required", 400) + return + } + + if err := h.service.DeleteUserAPIKey(username, key); err != nil { + errorResponse(c, err.Error(), 404) + return + } + + successNoData(c, "API key deleted successfully") +} + +// ListRoles handle list roles +func (h *Handler) ListRoles(c *gin.Context) { + roles, err := h.service.ListRoles() + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, roles, "") +} + +// CreateRoleHTTPRequest create role request +type CreateRoleHTTPRequest struct { + RoleName string `json:"role_name" binding:"required"` + Description string `json:"description"` +} + +// CreateRole handle create role +func (h *Handler) CreateRole(c *gin.Context) { + var req CreateRoleHTTPRequest + if err := c.ShouldBindJSON(&req); err != nil { + errorResponse(c, "Role name is required", 400) + return + } + + role, err := h.service.CreateRole(req.RoleName, req.Description) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, role, "") +} + +// GetRole handle get role +func (h *Handler) GetRole(c *gin.Context) { + roleName := c.Param("role_name") + if roleName == "" { + errorResponse(c, "Role name is required", 400) + return + } + + role, err := h.service.GetRole(roleName) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, role, "") +} + +// UpdateRoleHTTPRequest update role request +type UpdateRoleHTTPRequest struct { + Description string `json:"description" binding:"required"` +} + +// UpdateRole handle update role +func (h *Handler) UpdateRole(c *gin.Context) { + roleName := c.Param("role_name") + if roleName == "" { + errorResponse(c, "Role name is required", 400) + return + } + + var req UpdateRoleHTTPRequest + if err := c.ShouldBindJSON(&req); err != nil { + errorResponse(c, "Role description is required", 400) + return + } + + role, err := h.service.UpdateRole(roleName, req.Description) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, role, "") +} + +// DeleteRole handle delete role +func (h *Handler) DeleteRole(c *gin.Context) { + roleName := c.Param("role_name") + if roleName == "" { + errorResponse(c, "Role name is required", 400) + return + } + + if err := h.service.DeleteRole(roleName); err != nil { + errorResponse(c, err.Error(), 500) + return + } + + successNoData(c, "") +} + +// GetRolePermission handle get role permission +func (h *Handler) GetRolePermission(c *gin.Context) { + roleName := c.Param("role_name") + if roleName == "" { + errorResponse(c, "Role name is required", 400) + return + } + + permissions, err := h.service.GetRolePermission(roleName) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, permissions, "") +} + +// GrantRolePermissionHTTPRequest grant role permission request +type GrantRolePermissionHTTPRequest struct { + Actions []string `json:"actions" binding:"required"` + Resource string `json:"resource" binding:"required"` +} + +// GrantRolePermission handle grant role permission +func (h *Handler) GrantRolePermission(c *gin.Context) { + roleName := c.Param("role_name") + if roleName == "" { + errorResponse(c, "Role name is required", 400) + return + } + + var req GrantRolePermissionHTTPRequest + if err := c.ShouldBindJSON(&req); err != nil { + errorResponse(c, "Permission is required", 400) + return + } + + result, err := h.service.GrantRolePermission(roleName, req.Actions, req.Resource) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, result, "") +} + +// RevokeRolePermissionHTTPRequest revoke role permission request +type RevokeRolePermissionHTTPRequest struct { + Actions []string `json:"actions" binding:"required"` + Resource string `json:"resource" binding:"required"` +} + +// RevokeRolePermission handle revoke role permission +func (h *Handler) RevokeRolePermission(c *gin.Context) { + roleName := c.Param("role_name") + if roleName == "" { + errorResponse(c, "Role name is required", 400) + return + } + + var req RevokeRolePermissionHTTPRequest + if err := c.ShouldBindJSON(&req); err != nil { + errorResponse(c, "Permission is required", 400) + return + } + + result, err := h.service.RevokeRolePermission(roleName, req.Actions, req.Resource) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, result, "") +} + +// UpdateUserRoleHTTPRequest update user role request +type UpdateUserRoleHTTPRequest struct { + RoleName string `json:"role_name" binding:"required"` +} + +// UpdateUserRole handle update user role +func (h *Handler) UpdateUserRole(c *gin.Context) { + username := c.Param("username") + if username == "" { + errorResponse(c, "Username is required", 400) + return + } + + var req UpdateUserRoleHTTPRequest + if err := c.ShouldBindJSON(&req); err != nil { + errorResponse(c, "Role name is required", 400) + return + } + + result, err := h.service.UpdateUserRole(username, req.RoleName) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, result, "") +} + +// GetUserPermission handle get user permission +func (h *Handler) GetUserPermission(c *gin.Context) { + username := c.Param("username") + if username == "" { + errorResponse(c, "Username is required", 400) + return + } + + permissions, err := h.service.GetUserPermission(username) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, permissions, "") +} + +// GetServices handle get all services +func (h *Handler) GetServices(c *gin.Context) { + services, err := h.service.GetAllServices() + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, services, "Get all services") +} + +// GetServicesByType handle get services by type +func (h *Handler) GetServicesByType(c *gin.Context) { + serviceType := c.Param("service_type") + if serviceType == "" { + errorResponse(c, "Service type is required", 400) + return + } + + services, err := h.service.GetServicesByType(serviceType) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, services, "") +} + +// GetService handle get service details +func (h *Handler) GetService(c *gin.Context) { + serviceID := c.Param("service_id") + if serviceID == "" { + errorResponse(c, "Service ID is required", 400) + return + } + + // Get all services and find the one with matching ID + allConfigs := server.GetAllConfigs() + + var targetService map[string]interface{} + for _, config := range allConfigs { + if id, ok := config["id"]; ok { + if strconv.Itoa(id.(int)) == serviceID { + targetService = config + break + } + } + } + + if targetService == nil { + errorResponse(c, "Service not found", 404) + return + } + + serviceStatus, err := h.service.GetServiceDetails(targetService) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, serviceStatus, "") +} + +// ShutdownService handle shutdown service +func (h *Handler) ShutdownService(c *gin.Context) { + serviceID := c.Param("service_id") + if serviceID == "" { + errorResponse(c, "Service ID is required", 400) + return + } + + result, err := h.service.ShutdownService(serviceID) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, result, "") +} + +// RestartService handle restart service +func (h *Handler) RestartService(c *gin.Context) { + serviceID := c.Param("service_id") + if serviceID == "" { + errorResponse(c, "Service ID is required", 400) + return + } + + result, err := h.service.RestartService(serviceID) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, result, "") +} + +// GetVariables handle get variables +func (h *Handler) GetVariables(c *gin.Context) { + varName := c.Query("var_name") + + if varName != "" { + // Get single variable + variable, err := h.service.GetVariable(varName) + if err != nil { + errorResponse(c, err.Error(), 400) + return + } + success(c, variable, "") + return + } + + // List all variables + variables, err := h.service.GetAllVariables() + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, variables, "") +} + +// SetVariableHTTPRequest set variable request +type SetVariableHTTPRequest struct { + VarName string `json:"var_name" binding:"required"` + VarValue string `json:"var_value" binding:"required"` +} + +// SetVariable handle set variable +func (h *Handler) SetVariable(c *gin.Context) { + var req SetVariableHTTPRequest + if err := c.ShouldBindJSON(&req); err != nil { + errorResponse(c, "Var name and value are required", 400) + return + } + + if err := h.service.SetVariable(req.VarName, req.VarValue); err != nil { + errorResponse(c, err.Error(), 400) + return + } + + successNoData(c, "Set variable successfully") +} + +// GetConfigs handle get configs +func (h *Handler) GetConfigs(c *gin.Context) { + configs, err := h.service.GetAllConfigs() + if err != nil { + errorResponse(c, err.Error(), 400) + return + } + + success(c, configs, "") +} + +// GetEnvironments handle get environments +func (h *Handler) GetEnvironments(c *gin.Context) { + environments, err := h.service.GetAllEnvironments() + if err != nil { + errorResponse(c, err.Error(), 400) + return + } + + success(c, environments, "") +} + +// GetVersion handle get version +func (h *Handler) GetVersion(c *gin.Context) { + version := h.service.GetVersion() + success(c, gin.H{"version": version}, "") +} + +// ListSandboxProviders handle list sandbox providers +func (h *Handler) ListSandboxProviders(c *gin.Context) { + providers, err := h.service.ListSandboxProviders() + if err != nil { + errorResponse(c, err.Error(), 400) + return + } + + success(c, providers, "") +} + +// GetSandboxProviderSchema handle get sandbox provider schema +func (h *Handler) GetSandboxProviderSchema(c *gin.Context) { + providerID := c.Param("provider_id") + if providerID == "" { + errorResponse(c, "Provider ID is required", 400) + return + } + + schema, err := h.service.GetSandboxProviderSchema(providerID) + if err != nil { + errorResponse(c, err.Error(), 400) + return + } + + success(c, schema, "") +} + +// GetSandboxConfig handle get sandbox config +func (h *Handler) GetSandboxConfig(c *gin.Context) { + config, err := h.service.GetSandboxConfig() + if err != nil { + errorResponse(c, err.Error(), 400) + return + } + + success(c, config, "") +} + +// SetSandboxConfigHTTPRequest set sandbox config request +type SetSandboxConfigHTTPRequest struct { + ProviderType string `json:"provider_type" binding:"required"` + Config map[string]interface{} `json:"config"` + SetActive bool `json:"set_active"` +} + +// SetSandboxConfig handle set sandbox config +func (h *Handler) SetSandboxConfig(c *gin.Context) { + var req SetSandboxConfigHTTPRequest + if err := c.ShouldBindJSON(&req); err != nil { + errorResponse(c, "Request body is required", 400) + return + } + + if req.ProviderType == "" { + errorResponse(c, "provider_type is required", 400) + return + } + + // Default to true for backward compatibility + _ = c.Request.Body.Close() + req.SetActive = true + + result, err := h.service.SetSandboxConfig(req.ProviderType, req.Config, req.SetActive) + if err != nil { + errorResponse(c, err.Error(), 400) + return + } + + success(c, result, "Sandbox configuration updated successfully") +} + +// TestSandboxConnectionHTTPRequest test sandbox connection request +type TestSandboxConnectionHTTPRequest struct { + ProviderType string `json:"provider_type" binding:"required"` + Config map[string]interface{} `json:"config"` +} + +// TestSandboxConnection handle test sandbox connection +func (h *Handler) TestSandboxConnection(c *gin.Context) { + var req TestSandboxConnectionHTTPRequest + if err := c.ShouldBindJSON(&req); err != nil { + errorResponse(c, "Request body is required", 400) + return + } + + if req.ProviderType == "" { + errorResponse(c, "provider_type is required", 400) + return + } + + result, err := h.service.TestSandboxConnection(req.ProviderType, req.Config) + if err != nil { + errorResponse(c, err.Error(), 400) + return + } + + success(c, result, "") } // AuthMiddleware JWT auth middleware @@ -237,34 +914,32 @@ func (h *Handler) AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { token := c.GetHeader("Authorization") if token == "" { - c.JSON(401, gin.H{"error": "missing authorization header"}) + errorResponse(c, "missing authorization header", 401) c.Abort() return } - // Remove "Bearer " prefix - if len(token) > 7 && token[:7] == "Bearer " { - token = token[7:] - } - - // Validate token - user, err := h.service.ValidateToken(token) + // Get user by access token + user, code, err := h.userService.GetUserByToken(token) if err != nil { - c.JSON(401, gin.H{"error": "invalid token"}) - c.Abort() + c.JSON(http.StatusUnauthorized, gin.H{ + "code": code, + "message": "Invalid access token", + }) return } c.Set("user", user) + c.Set("user_id", user.ID) + c.Set("email", user.Email) c.Next() } } // HandleNoRoute handle undefined routes func (h *Handler) HandleNoRoute(c *gin.Context) { - c.JSON(http.StatusNotFound, gin.H{ - "error": "Not Found", - "message": "The requested resource was not found", - "path": c.Request.URL.Path, + c.JSON(http.StatusNotFound, ErrorResponse{ + Code: 404, + Message: "The requested resource was not found", }) } diff --git a/internal/admin/router.go b/internal/admin/router.go index fa6572888..3dc03c2c1 100644 --- a/internal/admin/router.go +++ b/internal/admin/router.go @@ -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) } } diff --git a/internal/admin/service.go b/internal/admin/service.go index 80b2792df..2438ef6c8 100644 --- a/internal/admin/service.go +++ b/internal/admin/service.go @@ -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 } diff --git a/internal/dao/user.go b/internal/dao/user.go index ff134683b..7fb5b10b3 100644 --- a/internal/dao/user.go +++ b/internal/dao/user.go @@ -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 } diff --git a/internal/engine/elasticsearch/client.go b/internal/engine/elasticsearch/client.go index bfd10d056..bd10fa167 100644 --- a/internal/engine/elasticsearch/client.go +++ b/internal/engine/elasticsearch/client.go @@ -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) +} diff --git a/internal/server/config.go b/internal/server/config.go index b29cef029..5a8fbf1e1 100644 --- a/internal/server/config.go +++ b/internal/server/config.go @@ -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 +} diff --git a/internal/service/user.go b/internal/service/user.go index 9db2a2643..ccf737e3e 100644 --- a/internal/service/user.go +++ b/internal/service/user.go @@ -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 diff --git a/internal/utility/token.go b/internal/utility/token.go index 789036b44..3c7b97fc7 100644 --- a/internal/utility/token.go +++ b/internal/utility/token.go @@ -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(), "-", "") +} diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 4257d5a0e..26c253051 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -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.', diff --git a/web/src/utils/next-request.ts b/web/src/utils/next-request.ts index d2ead134a..c804400a3 100644 --- a/web/src/utils/next-request.ts +++ b/web/src/utils/next-request.ts @@ -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); diff --git a/web/src/utils/request.ts b/web/src/utils/request.ts index f957cb2a0..3c122cf1b 100644 --- a/web/src/utils/request.ts +++ b/web/src/utils/request.ts @@ -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,