diff --git a/cmd/server_main.go b/cmd/server_main.go index d5897febbb..d1db4ad762 100644 --- a/cmd/server_main.go +++ b/cmd/server_main.go @@ -89,6 +89,9 @@ func main() { } } server.SetLogger(logger.Logger) + if config.Log.Level == "" { + config.Log.Level = logger.GetLevel() + } logger.Info("Server mode", zap.String("mode", config.Server.Mode)) diff --git a/internal/cli/admin_command.go b/internal/cli/admin_command.go index c40014555b..d092fe35b2 100644 --- a/internal/cli/admin_command.go +++ b/internal/cli/admin_command.go @@ -706,9 +706,9 @@ func (c *RAGFlowClient) ShowUser(cmd *Command) (ResponseIf, error) { return &result, nil } -// ListDatasets lists datasets for a specific user (admin mode) +// ListUserDatasets lists datasets for a specific user (admin mode) // Returns (result_map, error) - result_map is non-nil for benchmark mode -func (c *RAGFlowClient) ListDatasets(cmd *Command) (ResponseIf, error) { +func (c *RAGFlowClient) ListUserDatasets(cmd *Command) (ResponseIf, error) { if c.ServerType != "admin" { return nil, fmt.Errorf("this command is only allowed in ADMIN mode") } diff --git a/internal/cli/client.go b/internal/cli/client.go index 607e5f64f3..c368fc5383 100644 --- a/internal/cli/client.go +++ b/internal/cli/client.go @@ -126,8 +126,6 @@ func (c *RAGFlowClient) ExecuteAdminCommand(cmd *Command) (ResponseIf, error) { return c.PingAdmin(cmd) case "benchmark": return c.RunBenchmark(cmd) - case "list_user_datasets": - return c.ListUserDatasets(cmd) case "list_users": return c.ListUsers(cmd) case "list_services": @@ -150,8 +148,8 @@ func (c *RAGFlowClient) ExecuteAdminCommand(cmd *Command) (ResponseIf, error) { return c.ShowAdminVersion(cmd) case "show_user": return c.ShowUser(cmd) - case "list_datasets": - return c.ListDatasets(cmd) + case "list_user_datasets": + return c.ListUserDatasets(cmd) case "list_agents": return c.ListAgents(cmd) case "generate_token": @@ -185,10 +183,15 @@ func (c *RAGFlowClient) ExecuteUserCommand(cmd *Command) (ResponseIf, error) { return c.Logout() case "ping": return c.PingServer(cmd) + // Configuration commands + case "list_configs": + return c.ListConfigs(cmd) + case "set_log_level": + return c.SetLogLevel(cmd) case "benchmark": return c.RunBenchmark(cmd) - case "list_user_datasets": - return c.ListUserDatasets(cmd) + case "list_datasets": + return c.ListDatasets(cmd) case "search_on_datasets": return c.SearchOnDatasets(cmd) case "create_token": diff --git a/internal/cli/common_command.go b/internal/cli/common_command.go index d72d639156..52aacfea08 100644 --- a/internal/cli/common_command.go +++ b/internal/cli/common_command.go @@ -400,3 +400,27 @@ func ReadPasswordFallback() (string, error) { } return strings.TrimSpace(password), nil } + +// FlattenMap recursively flattens a nested map into dot-notation keys +func FlattenMap(data map[string]interface{}, prefix string, result *[]map[string]interface{}) { + for key, value := range data { + // Build the current key path + currentKey := key + if prefix != "" { + currentKey = prefix + "." + key + } + + // Check if the value is another nested map + if nestedMap, ok := value.(map[string]interface{}); ok { + // Recursively process the nested map + FlattenMap(nestedMap, currentKey, result) + } else { + // Leaf node: append to result slice + resultItem := map[string]interface{}{ + "key": currentKey, + "value": value, + } + *result = append(*result, resultItem) + } + } +} diff --git a/internal/cli/lexer.go b/internal/cli/lexer.go index b7777463a2..19ae0ce1a5 100644 --- a/internal/cli/lexer.go +++ b/internal/cli/lexer.go @@ -359,6 +359,22 @@ func (l *Lexer) lookupIdent(ident string) Token { return Token{Type: TokenDocument, Value: ident} case "TAGS": return Token{Type: TokenTag, Value: ident} + case "LOG": + return Token{Type: TokenLog, Value: ident} + case "LEVEL": + return Token{Type: TokenLevel, Value: ident} + case "DEBUG": + return Token{Type: TokenDebug, Value: ident} + case "INFO": + return Token{Type: TokenInfo, Value: ident} + case "WARN": + return Token{Type: TokenWarn, Value: ident} + case "ERROR": + return Token{Type: TokenError, Value: ident} + case "FATAL": + return Token{Type: TokenFatal, Value: ident} + case "PANIC": + return Token{Type: TokenPanic, Value: ident} default: return Token{Type: TokenIdentifier, Value: ident} } diff --git a/internal/cli/types.go b/internal/cli/types.go index 31e55e5c4c..5132455d4e 100644 --- a/internal/cli/types.go +++ b/internal/cli/types.go @@ -122,7 +122,14 @@ const ( TokenChunk TokenDocument TokenTag - + TokenLog + TokenLevel + TokenDebug + TokenInfo + TokenWarn + TokenError + TokenFatal + TokenPanic // Literals TokenIdentifier TokenQuotedString diff --git a/internal/cli/user_command.go b/internal/cli/user_command.go index 6b9a292e97..8f36c9ff65 100644 --- a/internal/cli/user_command.go +++ b/internal/cli/user_command.go @@ -92,6 +92,189 @@ func (c *RAGFlowClient) ShowServerVersion(cmd *Command) (ResponseIf, error) { return &result, nil } +func (c *RAGFlowClient) ListConfigs(cmd *Command) (ResponseIf, error) { + if c.ServerType != "user" { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode") + } + // Get iterations from command params (for benchmark) + iterations := 1 + if val, ok := cmd.Params["iterations"].(int); ok && val > 1 { + iterations = val + } + + if iterations > 1 { + // Benchmark mode: multiple iterations + return c.HTTPClient.RequestWithIterations("GET", "/system/configs", true, "web", nil, nil, iterations) + } + + // Single mode + resp, err := c.HTTPClient.Request("GET", "/system/configs", true, "web", nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to list configs: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to list configs: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + + var response CommonDataResponse + if err = json.Unmarshal(resp.Body, &response); err != nil { + return nil, fmt.Errorf("list configs failed: invalid JSON (%w)", err) + } + + var result CommonResponse + result.Code = 0 + result.Data, err = GetConfigs(&response.Data) + if err != nil { + return nil, fmt.Errorf("failed to list configs: %w", err) + } + result.Duration = resp.Duration + return &result, nil +} + +func GetConfigs(config *map[string]interface{}) ([]map[string]interface{}, error) { + if config == nil { + return nil, fmt.Errorf("config is nil") + } + result := []map[string]interface{}{} + { + redisHost := GetHost(config, "Redis", "Host", "Port") + result = append(result, map[string]interface{}{ + "key": "redis_host", + "value": redisHost}) + } + { + if docEngine, ok := (*config)["DocEngine"].(map[string]interface{}); ok { + engineType, _ := docEngine["Type"].(string) + result = append(result, map[string]interface{}{ + "key": "doc_engine", + "value": engineType}) + if engineType == "elasticsearch" { + esCfg, _ := docEngine["ES"].(map[string]interface{}) + esHost, _ := esCfg["Hosts"].(string) + result = append(result, map[string]interface{}{ + "key": "elasticsearch_host", + "value": esHost}) + } else if engineType == "Infinity" { + infinityCfg, _ := docEngine["Infinity"].(map[string]interface{}) + infinityHost, _ := infinityCfg["URI"] + result = append(result, map[string]interface{}{ + "key": "infinity_host", + "value": infinityHost}) + } else { + return nil, fmt.Errorf("unknown doc engine: %s", engineType) + } + } + } + { + if logConfig, ok := (*config)["Log"].(map[string]interface{}); ok { + level, _ := logConfig["Level"].(string) + result = append(result, map[string]interface{}{ + "key": "log_level", + "value": level}) + } + } + { + if databaseConfig, ok := (*config)["Database"].(map[string]interface{}); ok { + driver, _ := databaseConfig["Driver"].(string) + result = append(result, map[string]interface{}{ + "key": "database", + "value": driver}) + driverAddr, _ := databaseConfig["Host"].(string) + driverPort, _ := databaseConfig["Port"].(float64) + driverHost := fmt.Sprintf("%s:%0.f", driverAddr, driverPort) + result = append(result, map[string]interface{}{ + "key": "database_host", + "value": driverHost}) + } + } + { + if language, ok := (*config)["Language"].(map[string]interface{}); ok { + result = append(result, map[string]interface{}{ + "key": "language", + "value": language}) + } + } + { + if adminConfig, ok := (*config)["Admin"].(map[string]interface{}); ok { + adminAddr, _ := adminConfig["Host"].(string) + adminPort, _ := adminConfig["Port"].(float64) + adminHost := fmt.Sprintf("%s:%0.f", adminAddr, adminPort) + result = append(result, map[string]interface{}{ + "key": "admin", + "value": adminHost}) + } + } + { + if storageEngineConfig, ok := (*config)["StorageEngine"].(map[string]interface{}); ok { + engineType, _ := storageEngineConfig["Type"].(string) + result = append(result, map[string]interface{}{ + "key": "storage_engine", + "value": engineType}) + if engineType == "minio" { + minioCfg, _ := storageEngineConfig["Minio"].(map[string]interface{}) + miniHost, _ := minioCfg["Host"].(string) + result = append(result, map[string]interface{}{ + "key": "minio_host", + "value": miniHost}) + } else { + return nil, fmt.Errorf("unknown storage engine: %s", engineType) + } + } + } + return result, nil +} + +func GetHost(config *map[string]interface{}, serverType, address, port string) string { + if config == nil { + return "" + } + + result := "" + + if redis, ok := (*config)[serverType].(map[string]interface{}); ok { + serverAddr, hostOk := redis[address].(string) + serverPort, portOk := redis[port].(float64) + + if hostOk && portOk { + result = fmt.Sprintf("%s:%.0f", serverAddr, serverPort) + } + } + + return result +} + +func (c *RAGFlowClient) SetLogLevel(cmd *Command) (ResponseIf, error) { + if c.ServerType != "user" { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode") + } + + if logLevel, ok := cmd.Params["level"].(string); ok { + payload := map[string]interface{}{ + "level": logLevel, + } + + resp, err := c.HTTPClient.Request("PUT", "/system/log", true, "admin", nil, payload) + if err != nil { + return nil, fmt.Errorf("failed to change log level: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to register user: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + + var result SimpleResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("change log level failed: invalid JSON (%w)", err) + } + result.Code = 0 + result.Duration = resp.Duration + return &result, nil + } + + return nil, fmt.Errorf("no log level") +} + func (c *RAGFlowClient) RegisterUser(cmd *Command) (ResponseIf, error) { if c.ServerType != "user" { return nil, fmt.Errorf("this command is only allowed in ADMIN mode") @@ -150,9 +333,9 @@ func (c *RAGFlowClient) RegisterUser(cmd *Command) (ResponseIf, error) { return &result, nil } -// ListUserDatasets lists datasets for current user (user mode) +// ListDatasets lists datasets for current user (user mode) // Returns (result_map, error) - result_map is non-nil for benchmark mode -func (c *RAGFlowClient) ListUserDatasets(cmd *Command) (ResponseIf, error) { +func (c *RAGFlowClient) ListDatasets(cmd *Command) (ResponseIf, error) { if c.ServerType != "user" { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -164,11 +347,19 @@ func (c *RAGFlowClient) ListUserDatasets(cmd *Command) (ResponseIf, error) { } // Determine auth kind based on whether API token is being used + if c.HTTPClient.LoginToken == "" && !c.HTTPClient.useAPIToken { + return nil, fmt.Errorf("no authorization") + } + authKind := "web" if c.HTTPClient.useAPIToken { authKind = "api" } + if c.HTTPClient.LoginToken != "" { + authKind = "web" + } + if iterations > 1 { // Benchmark mode - return raw result for benchmark stats return c.HTTPClient.RequestWithIterations("GET", "/datasets", true, authKind, nil, nil, iterations) @@ -382,7 +573,7 @@ func (c *RAGFlowClient) CreateToken(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("this command is only allowed in USER mode") } - resp, err := c.HTTPClient.Request("POST", "/tokens", true, "web", nil, nil) + resp, err := c.HTTPClient.Request("POST", "/system/tokens", true, "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to create token: %w", err) } @@ -413,7 +604,7 @@ func (c *RAGFlowClient) ListTokens(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("this command is only allowed in USER mode") } - resp, err := c.HTTPClient.Request("GET", "/tokens", true, "web", nil, nil) + resp, err := c.HTTPClient.Request("GET", "/system/tokens", true, "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list tokens: %w", err) } @@ -445,7 +636,7 @@ func (c *RAGFlowClient) DropToken(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("token not provided") } - resp, err := c.HTTPClient.Request("DELETE", fmt.Sprintf("/tokens/%s", token), true, "web", nil, nil) + resp, err := c.HTTPClient.Request("DELETE", fmt.Sprintf("/system/tokens/%s", token), true, "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to drop token: %w", err) } diff --git a/internal/cli/user_parser.go b/internal/cli/user_parser.go index 61dfed6b0c..7f7552c5e3 100644 --- a/internal/cli/user_parser.go +++ b/internal/cli/user_parser.go @@ -188,24 +188,9 @@ func (p *Parser) parseListCommand() (*Command, error) { } func (p *Parser) parseListDatasets() (*Command, error) { - cmd := NewCommand("list_user_datasets") + cmd := NewCommand("list_datasets") p.nextToken() // consume DATASETS - if p.curToken.Type == TokenSemicolon { - return cmd, nil - } - - if p.curToken.Type == TokenOf { - p.nextToken() - userName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - cmd = NewCommand("list_datasets") - cmd.Params["user_name"] = userName - p.nextToken() - } - // Semicolon is optional for UNSET TOKEN if p.curToken.Type == TokenSemicolon { p.nextToken() @@ -243,20 +228,7 @@ func (p *Parser) parseListAgents() (*Command, error) { func (p *Parser) parseListTokens() (*Command, error) { p.nextToken() // consume TOKENS - if p.curToken.Type != TokenOf { - return nil, fmt.Errorf("expected OF") - } - p.nextToken() - - userName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - cmd := NewCommand("list_tokens") - cmd.Params["user_name"] = userName - - p.nextToken() // Semicolon is optional for UNSET TOKEN if p.curToken.Type == TokenSemicolon { p.nextToken() @@ -1584,6 +1556,9 @@ func (p *Parser) parseSetCommand() (*Command, error) { if p.curToken.Type == TokenMetadata { return p.parseSetMeta() } + if p.curToken.Type == TokenLog { + return p.parseSetLog() + } return nil, fmt.Errorf("unknown SET target: %s", p.curToken.Value) } @@ -1673,6 +1648,45 @@ func (p *Parser) parseSetToken() (*Command, error) { return cmd, nil } +func (p *Parser) parseSetLog() (*Command, error) { + p.nextToken() // consume LOG + + switch p.curToken.Type { + case TokenLevel: + return p.parseSetLogLevel() + default: + return nil, fmt.Errorf("unknown log target: %s", p.curToken.Value) + } +} + +func (p *Parser) parseSetLogLevel() (*Command, error) { + p.nextToken() // consume LEVEL + + cmd := NewCommand("set_log_level") + switch p.curToken.Type { + case TokenDebug: + cmd.Params["level"] = "debug" + case TokenInfo: + cmd.Params["level"] = "info" + case TokenWarn: + cmd.Params["level"] = "warn" + case TokenError: + cmd.Params["level"] = "error" + case TokenFatal: + cmd.Params["level"] = "fatal" + case TokenPanic: + cmd.Params["level"] = "panic" + default: + return nil, fmt.Errorf("unknown log target: %s", p.curToken.Value) + } + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + func (p *Parser) parseResetCommand() (*Command, error) { p.nextToken() // consume RESET diff --git a/internal/handler/system.go b/internal/handler/system.go index 6bd14e5067..cb645b9c03 100644 --- a/internal/handler/system.go +++ b/internal/handler/system.go @@ -165,6 +165,9 @@ func (h *SystemHandler) SetLogLevel(c *gin.Context) { return } + config := server.GetConfig() + config.Log.Level = req.Level + c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "Log level updated successfully", diff --git a/internal/router/router.go b/internal/router/router.go index acca8e653c..684aa3068f 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -90,8 +90,6 @@ func (r *Router) Setup(engine *gin.Engine) { engine.GET("/v1/system/config", r.systemHandler.GetConfig) engine.GET("/v1/system/configs", r.systemHandler.GetConfigs) engine.GET("/v1/system/version", r.systemHandler.GetVersion) - engine.GET("/v1/system/log_level", r.systemHandler.GetLogLevel) - engine.PUT("/v1/system/log_level", r.systemHandler.SetLogLevel) engine.POST("/v1/user/register", r.userHandler.Register) // User login channels endpoint engine.GET("/v1/user/login/channels", r.userHandler.GetLoginChannels) @@ -136,12 +134,12 @@ func (r *Router) Setup(engine *gin.Engine) { // users.GET("/:id", r.userHandler.GetUserByID) //} - apiTokens := v1.Group("/tokens") - { - apiTokens.POST("", r.systemHandler.CreateToken) - apiTokens.GET("", r.systemHandler.ListTokens) - apiTokens.DELETE("/:token", r.systemHandler.DeleteToken) - } + //apiTokens := v1.Group("/tokens") + //{ + // apiTokens.POST("", r.systemHandler.CreateToken) + // apiTokens.GET("", r.systemHandler.ListTokens) + // apiTokens.DELETE("/:token", r.systemHandler.DeleteToken) + //} // Document routes documents := v1.Group("/documents") @@ -233,6 +231,24 @@ func (r *Router) Setup(engine *gin.Engine) { system := v1.Group("/system") { system.GET("/version", r.systemHandler.GetVersion) + system.GET("/configs", r.systemHandler.GetConfigs) + log := system.Group("/log") + { + // /api/v1/system/log GET + log.GET("", r.systemHandler.GetLogLevel) + // /api/v1/system/log PUT + log.PUT("", r.systemHandler.SetLogLevel) + } + + tokens := system.Group("/tokens") + { + // list tokens /api/v1/system/tokens GET + tokens.GET("", r.systemHandler.ListTokens) + // create token /api/v1/system/tokens POST + tokens.POST("", r.systemHandler.CreateToken) + // delete token /api/v1/system/tokens/:token DELETE + tokens.DELETE("/:token", r.systemHandler.DeleteToken) + } } }