diff --git a/internal/admin/handler.go b/internal/admin/handler.go index c311310da6..4263ec4437 100644 --- a/internal/admin/handler.go +++ b/internal/admin/handler.go @@ -957,7 +957,7 @@ func (h *Handler) HandleNoRoute(c *gin.Context) { // GetLogLevel returns the current log level func (h *Handler) GetLogLevel(c *gin.Context) { level := common.GetLevel() - success(c, gin.H{"level": level}, "") + success(c, gin.H{"level": level}, "SUCCESS") } // SetLogLevelRequest set log level request @@ -978,7 +978,7 @@ func (h *Handler) SetLogLevel(c *gin.Context) { return } - success(c, gin.H{"level": req.Level}, "Log level updated successfully") + success(c, gin.H{"level": req.Level}, "SUCCESS") } func (h *Handler) ListMessagesFromQueue(c *gin.Context) { diff --git a/internal/admin/router.go b/internal/admin/router.go index d803d505cc..e728397b6e 100644 --- a/internal/admin/router.go +++ b/internal/admin/router.go @@ -83,6 +83,9 @@ func (r *Router) Setup(engine *gin.Engine) { // Configs protected.GET("/configs", r.handler.ListConfigs) + // Log level + protected.GET("/config/log", r.handler.GetLogLevel) + protected.PUT("/config/log", r.handler.SetLogLevel) // Environments protected.GET("/environments", r.handler.ListEnvironments) @@ -90,10 +93,6 @@ func (r *Router) Setup(engine *gin.Engine) { // Version protected.GET("/version", r.handler.GetVersion) - // Log level - protected.GET("/log_level", r.handler.GetLogLevel) - protected.PUT("/log_level", r.handler.SetLogLevel) - queue := protected.Group("/queue") { queue.GET("/", r.handler.ShowMessageQueue) diff --git a/internal/admin/service.go b/internal/admin/service.go index 7c88c46c8a..3991f376f2 100644 --- a/internal/admin/service.go +++ b/internal/admin/service.go @@ -21,7 +21,6 @@ import ( "crypto/tls" "encoding/base64" "encoding/hex" - "encoding/json" "errors" "fmt" "net/http" @@ -36,7 +35,6 @@ import ( "ragflow/internal/utility" "regexp" "strconv" - "strings" "time" "go.uber.org/zap" @@ -1456,56 +1454,6 @@ func NewAdminException(message string) *AdminException { } } -func formatSystemSetting(setting entity.SystemSettings) map[string]interface{} { - return map[string]interface{}{ - "data_type": setting.DataType, - "name": setting.Name, - "setting_type": "config", - "value": setting.Value, - } -} - -func formatSystemSettings(settings []entity.SystemSettings) []map[string]interface{} { - result := make([]map[string]interface{}, 0, len(settings)) - for _, setting := range settings { - result = append(result, formatSystemSetting(setting)) - } - return result -} - -func validateSystemSettingValue(setting entity.SystemSettings, value string) error { - dataType := strings.ToLower(setting.DataType) - switch dataType { - case "string": - return nil - case "integer", "int": - if _, err := strconv.Atoi(value); err != nil { - return NewAdminException(fmt.Sprintf("Invalid integer value for %s: %s", setting.Name, value)) - } - case "bool", "boolean": - if value != "true" && value != "false" { - return NewAdminException(fmt.Sprintf("Invalid bool value for %s: expected true or false", setting.Name)) - } - case "json": - if !json.Valid([]byte(value)) { - return NewAdminException(fmt.Sprintf("Invalid JSON value for %s", setting.Name)) - } - default: - return NewAdminException(fmt.Sprintf("Unsupported data type for %s: %s", setting.Name, setting.DataType)) - } - return nil -} - -func inferSystemSettingDataType(name string) string { - if strings.HasPrefix(name, "sandbox.") { - return "json" - } - if strings.HasSuffix(name, ".enabled") { - return "bool" - } - return "string" -} - // GetVariable get variable by name // Returns the exact system setting with the given name, or settings matching the // given name prefix when an exact setting does not exist. @@ -1524,7 +1472,7 @@ func (s *Service) GetVariable(varName string) ([]map[string]interface{}, error) return nil, NewAdminException("Can't get setting: " + varName) } } - return formatSystemSettings(settings), nil + return common.FormatSystemSettings(settings), nil } // ListAllVariables list all variables @@ -1535,7 +1483,7 @@ func (s *Service) ListAllVariables() ([]map[string]interface{}, error) { return nil, err } - return formatSystemSettings(settings), nil + return common.FormatSystemSettings(settings), nil } // SetVariable set variable @@ -1549,7 +1497,7 @@ func (s *Service) SetVariable(varName, varValue string) error { if len(settings) == 1 { setting := &settings[0] - if err := validateSystemSettingValue(*setting, varValue); err != nil { + if err = common.ValidateSystemSettingValue(*setting, varValue); err != nil { return err } setting.Value = varValue @@ -1558,14 +1506,14 @@ func (s *Service) SetVariable(varName, varValue string) error { return NewAdminException("Can't update more than 1 setting: " + varName) } - dataType := inferSystemSettingDataType(varName) + dataType := common.InferSystemSettingDataType(varName) newSetting := &entity.SystemSettings{ Name: varName, Value: varValue, Source: "admin", DataType: dataType, } - if err := validateSystemSettingValue(*newSetting, varValue); err != nil { + if err = common.ValidateSystemSettingValue(*newSetting, varValue); err != nil { return err } return s.systemSettingsDAO.Create(newSetting) diff --git a/internal/admin/service_variables_test.go b/internal/admin/service_variables_test.go index 2b94a09088..fd135de13b 100644 --- a/internal/admin/service_variables_test.go +++ b/internal/admin/service_variables_test.go @@ -17,6 +17,7 @@ package admin import ( + "ragflow/internal/common" "ragflow/internal/entity" "testing" ) @@ -42,7 +43,7 @@ func TestValidateSystemSettingValue(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { setting := entity.SystemSettings{Name: "test.setting", DataType: tt.dataType} - err := validateSystemSettingValue(setting, tt.value) + err := common.ValidateSystemSettingValue(setting, tt.value) if (err != nil) != tt.wantError { t.Fatalf("validateSystemSettingValue() error = %v, wantError %v", err, tt.wantError) } @@ -58,7 +59,7 @@ func TestInferSystemSettingDataType(t *testing.T) { } for name, want := range tests { - if got := inferSystemSettingDataType(name); got != want { + if got := common.InferSystemSettingDataType(name); got != want { t.Fatalf("inferSystemSettingDataType(%q) = %q, want %q", name, got, want) } } diff --git a/internal/cli/admin_command.go b/internal/cli/admin_command.go index 29cdf69a15..1858e9ba3e 100644 --- a/internal/cli/admin_command.go +++ b/internal/cli/admin_command.go @@ -1073,8 +1073,8 @@ func (c *CLI) AdminSetLicenseConfigCommand(cmd *Command) (ResponseIf, error) { return &result, nil } -// SetVariable updates a system variable (admin mode only). -func (c *CLI) SetVariable(cmd *Command) (ResponseIf, error) { +// AdminSetVariableCommand updates a system variable (admin mode only). +func (c *CLI) AdminSetVariableCommand(cmd *Command) (ResponseIf, error) { if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } @@ -1171,6 +1171,39 @@ func (c *CLI) AdminSetRoleDefaultModelsCommand(cmd *Command) (ResponseIf, error) return &result, nil } +// AdminSetLogLevelCommand set log level (admin mode only). +func (c *CLI) AdminSetLogLevelCommand(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") + } + + logLevel, ok := cmd.Params["level"].(string) + if !ok { + return nil, fmt.Errorf("no log level") + } + + payload := map[string]interface{}{ + "level": logLevel, + } + + resp, err := c.AdminServerClient.Request("PUT", "/admin/config/log", "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 +} + // AdminResetRoleDefaultModelsCommand reset role default models (admin mode only). func (c *CLI) AdminResetRoleDefaultModelsCommand(cmd *Command) (ResponseIf, error) { if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { @@ -3504,3 +3537,31 @@ func (c *CLI) AdminDeleteModelsCommand(cmd *Command) (ResponseIf, error) { result.Duration = resp.Duration return &result, nil } + +func (c *CLI) AdminShowLogLevelCommand(cmd *Command) (ResponseIf, error) { + + if c.Config.CLIMode != AdminMode { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") + } + + resp, err := c.AdminServerClient.Request("GET", "/admin/config/log", "web", nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to get log level config: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to get log level config: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + + var result CommonDataResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("get log level config failed: invalid JSON (%w)", err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + + result.Duration = resp.Duration + return &result, nil +} diff --git a/internal/cli/admin_parser.go b/internal/cli/admin_parser.go index 6a1a2bb735..eced62cc91 100644 --- a/internal/cli/admin_parser.go +++ b/internal/cli/admin_parser.go @@ -396,6 +396,8 @@ func (p *Parser) parseAdminShowCommands() (*Command, error) { return p.parseAdminShowQuota() case TokenTasks: return p.parseAdminShowTasks() + case TokenLog: + return p.parseAdminShowLogCommands() default: return nil, fmt.Errorf("unknown SHOW target: %s", p.curToken.Value) } @@ -1058,6 +1060,7 @@ func (p *Parser) parseAdminCreateCommand() (*Command, error) { } } +// CREATE USER 'user@example.com' 'password'; func (p *Parser) parseAdminCreateUser() (*Command, error) { p.nextToken() // consume USER userName, err := p.parseQuotedString() @@ -1649,6 +1652,8 @@ func (p *Parser) parseAdminSetCommand() (*Command, error) { return p.parseAdminSetVariable() case TokenRole: return p.parseAdminSetRoleDefaultModel() + case TokenLog: + return p.parseAdminSetLog() default: return nil, fmt.Errorf("unknown SET target: %s", p.curToken.Value) } @@ -1660,7 +1665,7 @@ func (p *Parser) parseAdminSetLicense() (*Command, error) { if p.curToken.Type == TokenConfig { p.nextToken() // consume CONFIG // SET LICENSE CONFIG - cmd := NewCommand("admin_set_license_config_command") + cmd := NewCommand("admin_set_license_config") number1, err := p.parseNumber() if err != nil { return nil, err @@ -1682,7 +1687,7 @@ func (p *Parser) parseAdminSetLicense() (*Command, error) { } p.nextToken() - cmd := NewCommand("admin_set_license_command") + cmd := NewCommand("admin_set_license") cmd.Params["license"] = license // Semicolon is optional @@ -1706,7 +1711,7 @@ func (p *Parser) parseAdminSetVariable() (*Command, error) { return nil, err } - cmd := NewCommand("set_variable") + cmd := NewCommand("admin_set_variable") cmd.Params["var_name"] = varName cmd.Params["var_value"] = varValue @@ -1778,6 +1783,45 @@ func (p *Parser) parseAdminSetRoleDefaultModel() (*Command, error) { return cmd, nil } +func (p *Parser) parseAdminSetLog() (*Command, error) { + p.nextToken() // consume LOG + + switch p.curToken.Type { + case TokenLevel: + return p.parseAdminSetLogLevel() + default: + return nil, fmt.Errorf("unknown log target: %s", p.curToken.Value) + } +} + +func (p *Parser) parseAdminSetLogLevel() (*Command, error) { + p.nextToken() // consume LEVEL + + cmd := NewCommand("admin_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 level: %s", p.curToken.Value) + } + p.nextToken() + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + func (p *Parser) parseAdminResetCommand() (*Command, error) { p.nextToken() // consume RESET @@ -2695,6 +2739,30 @@ func (p *Parser) parseAdminShowTasks() (*Command, error) { return cmd, nil } +func (p *Parser) parseAdminShowLogCommands() (*Command, error) { + p.nextToken() // consume LOG + + switch p.curToken.Type { + case TokenLevel: + return p.parseAdminShowLogLevel() + default: + return nil, fmt.Errorf("expected LEVEL after LOG") + } +} + +func (p *Parser) parseAdminShowLogLevel() (*Command, error) { + p.nextToken() // consume LEVEL + + cmd := NewCommand("admin_show_log_level") + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return cmd, nil +} + // PURGE PREVIEW ORPHAN // PURGE ORPHAN diff --git a/internal/cli/cli_http.go b/internal/cli/cli_http.go index 0d9eb84bd8..d9111f5424 100644 --- a/internal/cli/cli_http.go +++ b/internal/cli/cli_http.go @@ -97,14 +97,16 @@ func (c *CLI) ExecuteAdminCommand(cmd *Command) (ResponseIf, error) { return c.AdminListEnvironmentsCommand(cmd) case "admin_show_variable": return c.AdminShowVariable(cmd) - case "admin_set_license_command": + case "admin_set_license": return c.AdminSetLicenseCommand(cmd) - case "admin_set_license_config_command": + case "admin_set_license_config": return c.AdminSetLicenseConfigCommand(cmd) - case "set_variable": - return c.SetVariable(cmd) + case "admin_set_variable": + return c.AdminSetVariableCommand(cmd) case "admin_set_role_default_model": return c.AdminSetRoleDefaultModelsCommand(cmd) + case "admin_set_log_level": + return c.AdminSetLogLevelCommand(cmd) case "admin_reset_role_default_model": return c.AdminResetRoleDefaultModelsCommand(cmd) case "list_user_datasets": @@ -260,6 +262,8 @@ func (c *CLI) ExecuteAdminCommand(cmd *Command) (ResponseIf, error) { return c.ShowAdminServer(cmd) case "show_api_server": return c.ShowAPIServer(cmd) + case "admin_show_log_level": + return c.AdminShowLogLevelCommand(cmd) case "admin_list_api_servers": return c.CommonListAPIServers(cmd) case "add_api_server": @@ -293,8 +297,8 @@ func (c *CLI) ExecuteUserCommand(cmd *Command) (ResponseIf, error) { // Configuration commands case "api_list_configs": return c.ListConfigs(cmd) - case "set_log_level": - return c.SetLogLevel(cmd) + case "api_set_log_level": + return c.APISetLogLevelCommand(cmd) case "benchmark": return c.RunBenchmark(cmd) case "api_list_datasets": @@ -323,14 +327,16 @@ func (c *CLI) ExecuteUserCommand(cmd *Command) (ResponseIf, error) { case "api_delete_api_key": return c.APIDeleteAPIKeyCommand(cmd) case "api_set_api_key": - return c.APISetAPIKey(cmd) + return c.APISetAPIKeyCommand(cmd) + case "api_set_variable": + return c.APISetVariableCommand(cmd) case "api_unset_api_key": return c.APIUnsetAPIKeyCommand(cmd) case "api_show_version": return c.APIShowVersionCommand(cmd) case "api_show_api_key": return c.APIShowAPIKeyCommand(cmd) - case "show_current": + case "api_show_current": return c.CommonShowCurrentCommand(cmd) case "api_list_available_providers": return c.CommonAvailableProvidersCommand(cmd) @@ -411,7 +417,7 @@ func (c *CLI) ExecuteUserCommand(cmd *Command) (ResponseIf, error) { return c.CommonUseAdminServerCommand(cmd) case "set_default_model": return c.SetDefaultModel(cmd) - case "reset_default_model": + case "api_reset_default_model": return c.ResetDefaultModel(cmd) case "api_list_default_models": return c.ListDefaultModels(cmd) @@ -460,8 +466,14 @@ func (c *CLI) ExecuteUserCommand(cmd *Command) (ResponseIf, error) { return c.ShowAdminServer(cmd) case "show_api_server": return c.ShowAPIServer(cmd) + case "api_show_log_level": + return c.APIShowLogLevelCommand(cmd) case "api_list_api_servers": return c.CommonListAPIServers(cmd) + case "api_list_environments": + return c.APIListEnvironmentsCommand(cmd) + case "api_list_variables": + return c.APIListVariablesCommand(cmd) case "add_api_server": return c.AddAPIServer(cmd) case "delete_api_server": diff --git a/internal/cli/parser.go b/internal/cli/parser.go index 43614e4f66..1061a7a252 100644 --- a/internal/cli/parser.go +++ b/internal/cli/parser.go @@ -164,10 +164,8 @@ func (p *Parser) parseUserCommand() (*Command, error) { return p.parseAlterCommand() case TokenSet: return p.parseAPISetCommands() - case TokenUnset: - return p.parseUnsetCommand() case TokenReset: - return p.parseResetCommand() + return p.parseAPIResetCommands() case TokenImport: return p.parseImportCommand() case TokenInsert: diff --git a/internal/cli/user_command.go b/internal/cli/user_command.go index 88c599fabe..72867467db 100644 --- a/internal/cli/user_command.go +++ b/internal/cli/user_command.go @@ -227,36 +227,37 @@ func GetHost(config *map[string]interface{}, serverType, address, port string) s return result } -func (c *CLI) SetLogLevel(cmd *Command) (ResponseIf, error) { +func (c *CLI) APISetLogLevelCommand(cmd *Command) (ResponseIf, error) { if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } - if logLevel, ok := cmd.Params["level"].(string); ok { - payload := map[string]interface{}{ - "level": logLevel, - } - - httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] - resp, err := httpClient.Request("PUT", "/system/log", "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 + logLevel, ok := cmd.Params["level"].(string) + if !ok { + return nil, fmt.Errorf("no log level") } - return nil, fmt.Errorf("no log level") + payload := map[string]interface{}{ + "level": logLevel, + } + + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + resp, err := httpClient.Request("PUT", "/system/config/log", "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 } func (c *CLI) RegisterUser(cmd *Command) (ResponseIf, error) { @@ -1041,8 +1042,8 @@ func (c *CLI) APIDeleteAPIKeyCommand(cmd *Command) (ResponseIf, error) { return &result, nil } -// APISetAPIKey sets the API key after validating it -func (c *CLI) APISetAPIKey(cmd *Command) (ResponseIf, error) { +// APISetAPIKeyCommand sets the API key after validating it +func (c *CLI) APISetAPIKeyCommand(cmd *Command) (ResponseIf, error) { if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -1099,6 +1100,52 @@ func (c *CLI) APISetAPIKey(cmd *Command) (ResponseIf, error) { return &successResult, nil } +// APISetVariableCommand sets variable value +func (c *CLI) APISetVariableCommand(cmd *Command) (ResponseIf, error) { + + if c.Config.CLIMode != APIMode { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIKey == nil && c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { + return nil, fmt.Errorf("API key not set. Please login first") + } + + varName, ok := cmd.Params["var_name"].(string) + if !ok { + return nil, fmt.Errorf("var_name not provided") + } + varValue, ok := cmd.Params["var_value"].(string) + if !ok { + return nil, fmt.Errorf("var_value not provided") + } + + payload := map[string]interface{}{ + "var_name": varName, + "var_value": varValue, + } + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("PUT", "/system/variables", "web", nil, payload) + if err != nil { + return nil, fmt.Errorf("failed to set variable: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to set variable: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + + var result MessageResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("set variable failed: invalid JSON (%w)", err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + + result.Duration = resp.Duration + return &result, nil +} + // APIShowAPIKeyCommand displays the current API key func (c *CLI) APIShowAPIKeyCommand(cmd *Command) (ResponseIf, error) { if c.Config.CLIMode != APIMode { @@ -3360,6 +3407,99 @@ func (c *CLI) ListUserIngestionTasks(cmd *Command) (ResponseIf, error) { return &result, nil } +// APIShowLogLevelCommand sets the log level for the system. +func (c *CLI) APIShowLogLevelCommand(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", "/system/config/log", "web", nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to get log level config: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to get log level config: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + + var result CommonDataResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("get log level config failed: invalid JSON (%w)", err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + + result.Duration = resp.Duration + return &result, nil + +} + +// APIListEnvironmentsCommand lists all system environments (api mode only). +func (c *CLI) APIListEnvironmentsCommand(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIKey == nil && c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { + return nil, fmt.Errorf("API key not set. Please login first") + } + + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", "/system/environments", "web", nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to list environments: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to list environments: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + + var result CommonResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("list environments failed: invalid JSON (%w)", err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + + result.Duration = resp.Duration + return &result, nil +} + +// APIListVariablesCommand lists all system variables (api mode only). +func (c *CLI) APIListVariablesCommand(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIKey == nil && c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { + return nil, fmt.Errorf("API key not set. Please login first") + } + + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", "/system/variables", "web", nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to list variables: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to list variables: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + + var result CommonResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("list environments failed: invalid JSON (%w)", err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + + result.Duration = resp.Duration + return &result, nil +} + func (c *CLI) UserStartIngestionCommand(cmd *Command) (ResponseIf, error) { if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIKey == nil && c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { return nil, fmt.Errorf("API key not set. Please login first") diff --git a/internal/cli/user_parser.go b/internal/cli/user_parser.go index a6f77faec8..d77653246c 100644 --- a/internal/cli/user_parser.go +++ b/internal/cli/user_parser.go @@ -20,7 +20,7 @@ func tokenTypeDescription(t int, tok Token) string { func (p *Parser) parseAPILogout() (*Command, error) { cmd := NewCommand("api_logout") p.nextToken() - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -55,7 +55,7 @@ func (p *Parser) parseAPILoginUser() (*Command, error) { p.nextToken() } - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -66,7 +66,7 @@ func (p *Parser) parseAPILoginUser() (*Command, error) { func (p *Parser) parseAPIPingServer() (*Command, error) { cmd := NewCommand("api_ping_server") p.nextToken() - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -112,7 +112,7 @@ func (p *Parser) parseAPIRegisterCommand() (*Command, error) { cmd.Params["password"] = password p.nextToken() // consume 'password' - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -154,6 +154,10 @@ func (p *Parser) parseAPIListCommands() (*Command, error) { return p.parseAPIListAvailableProviders() case TokenAPI: return p.parseAPIListAPIServers() + case TokenEnvs: + return p.parseAPIListEnvironments() + case TokenVars: + return p.parseAPIListVariables() default: return nil, fmt.Errorf("unknown LIST target: %s", p.curToken.Value) } @@ -174,7 +178,7 @@ func (p *Parser) parseAPIListDatasets() (*Command, error) { cmd := NewCommand("api_list_datasets") p.nextToken() // consume DATASETS - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -459,7 +463,7 @@ func (p *Parser) parseAPIListDefaultModels() (*Command, error) { return nil, fmt.Errorf("expected MODELS") } p.nextToken() - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -516,13 +520,7 @@ func (p *Parser) parseAPIShowCommands() (*Command, error) { case TokenKey: return p.parseAPIShowKey() case TokenCurrent: - p.nextToken() - - // Semicolon is optional - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("show_current"), nil + return p.parseAPIShowCurrent() case TokenVar: return p.parseShowVariable() case TokenProvider: @@ -541,6 +539,8 @@ func (p *Parser) parseAPIShowCommands() (*Command, error) { return p.parseUserShowAdmin() case TokenAPI: return p.parseUserShowAPI() + case TokenLog: + return p.parseAPIShowLogCommands() default: return nil, fmt.Errorf("unknown SHOW target: %s", p.curToken.Value) } @@ -566,6 +566,16 @@ func (p *Parser) parseAPIShowKey() (*Command, error) { return NewCommand("api_show_api_key"), nil } +func (p *Parser) parseAPIShowCurrent() (*Command, error) { + p.nextToken() + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("api_show_current"), nil +} + func (p *Parser) parseShowVariable() (*Command, error) { p.nextToken() // consume VAR varName, err := p.parseIdentifier() @@ -577,7 +587,7 @@ func (p *Parser) parseShowVariable() (*Command, error) { cmd.Params["var_name"] = varName p.nextToken() - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -792,7 +802,7 @@ func (p *Parser) parseCreateRole() (*Command, error) { p.nextToken() } - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -822,7 +832,7 @@ func (p *Parser) parseCreateModelProvider() (*Command, error) { cmd.Params["provider_key"] = providerKey p.nextToken() - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -1271,7 +1281,7 @@ func (p *Parser) parseCreateDataset() (*Command, error) { return nil, fmt.Errorf("expected PARSER or PIPELINE") } - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -1289,7 +1299,7 @@ func (p *Parser) parseCreateChat() (*Command, error) { cmd.Params["chat_name"] = chatName p.nextToken() - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -1436,7 +1446,7 @@ func (p *Parser) parseDropUser() (*Command, error) { cmd.Params["user_name"] = userName p.nextToken() - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -1454,7 +1464,7 @@ func (p *Parser) parseDropRole() (*Command, error) { cmd.Params["role_name"] = roleName p.nextToken() - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -1492,7 +1502,7 @@ func (p *Parser) parseDropDataset() (*Command, error) { cmd.Params["dataset_name"] = datasetName p.nextToken() - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -1510,7 +1520,7 @@ func (p *Parser) parseDropChat() (*Command, error) { cmd.Params["chat_name"] = chatName p.nextToken() - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -1907,7 +1917,7 @@ func (p *Parser) parseAPISetCommands() (*Command, error) { p.nextToken() // consume SET if p.curToken.Type == TokenVar { - return p.parseSetVariable() + return p.parseAPISetVariable() } if p.curToken.Type == TokenDefault { return p.parseSetDefault() @@ -1919,31 +1929,32 @@ func (p *Parser) parseAPISetCommands() (*Command, error) { return p.parseSetMeta() } if p.curToken.Type == TokenLog { - return p.parseSetLog() + return p.parseAPISetLog() } return nil, fmt.Errorf("unknown SET target: %s", p.curToken.Value) } -func (p *Parser) parseSetVariable() (*Command, error) { +func (p *Parser) parseAPISetVariable() (*Command, error) { p.nextToken() // consume VAR - varName, err := p.parseIdentifier() + + varName, err := p.parseQuotedString() if err != nil { return nil, err } - p.nextToken() + varValue, err := p.parseVariableValue() if err != nil { return nil, err } + p.nextToken() - cmd := NewCommand("set_variable") + cmd := NewCommand("api_set_variable") cmd.Params["var_name"] = varName cmd.Params["var_value"] = varValue - p.nextToken() - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -2030,21 +2041,21 @@ func (p *Parser) parseAPISetAPIKey() (*Command, error) { return cmd, nil } -func (p *Parser) parseSetLog() (*Command, error) { +func (p *Parser) parseAPISetLog() (*Command, error) { p.nextToken() // consume LOG switch p.curToken.Type { case TokenLevel: - return p.parseSetLogLevel() + return p.parseAPISetLogLevel() default: return nil, fmt.Errorf("unknown log target: %s", p.curToken.Value) } } -func (p *Parser) parseSetLogLevel() (*Command, error) { +func (p *Parser) parseAPISetLogLevel() (*Command, error) { p.nextToken() // consume LEVEL - cmd := NewCommand("set_log_level") + cmd := NewCommand("api_set_log_level") switch p.curToken.Type { case TokenDebug: cmd.Params["level"] = "debug" @@ -2059,23 +2070,31 @@ func (p *Parser) parseSetLogLevel() (*Command, error) { case TokenPanic: cmd.Params["level"] = "panic" default: - return nil, fmt.Errorf("unknown log target: %s", p.curToken.Value) + return nil, fmt.Errorf("unknown log level: %s", p.curToken.Value) } p.nextToken() - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return cmd, nil } -func (p *Parser) parseResetCommand() (*Command, error) { +func (p *Parser) parseAPIResetCommands() (*Command, error) { p.nextToken() // consume RESET - if p.curToken.Type != TokenDefault { - return nil, fmt.Errorf("expected DEFAULT") + switch p.curToken.Type { + case TokenDefault: + return p.parseAPIResetDefaultModel() + case TokenKey: + return p.parseAPIResetKey() + default: + return nil, fmt.Errorf("unknown RESET target: %s", p.curToken.Value) } - p.nextToken() +} + +func (p *Parser) parseAPIResetDefaultModel() (*Command, error) { + p.nextToken() // consume DEFAULT var modelType string switch p.curToken.Type { @@ -2106,13 +2125,23 @@ func (p *Parser) parseResetCommand() (*Command, error) { } p.nextToken() // pass MODEL - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return cmd, nil } +func (p *Parser) parseAPIResetKey() (*Command, error) { + p.nextToken() + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("api_unset_api_key"), nil +} + func (p *Parser) parseImportCommand() (*Command, error) { p.nextToken() // consume IMPORT documentPaths, err := p.parseQuotedString() @@ -2140,7 +2169,7 @@ func (p *Parser) parseImportCommand() (*Command, error) { cmd.Params["dataset_name"] = datasetName p.nextToken() - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -2519,7 +2548,7 @@ func (p *Parser) parseEnableCommand() (*Command, error) { } p.nextToken() - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -2562,7 +2591,7 @@ func (p *Parser) parseDisableCommand() (*Command, error) { } p.nextToken() - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -3006,7 +3035,7 @@ func (p *Parser) parseASRCommand() (*Command, error) { } } - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -3421,7 +3450,7 @@ func (p *Parser) parseParseDataset() (*Command, error) { cmd.Params["method"] = method p.nextToken() - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -3455,7 +3484,7 @@ func (p *Parser) parseParseDocs() (*Command, error) { cmd.Params["documents"] = documents cmd.Params["dataset_id"] = datasetID - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -3584,10 +3613,6 @@ func (p *Parser) parseUserStatement() (*Command, error) { return p.parseCreateCommand() case TokenDrop: return p.parseDropCommand() - case TokenUnset: - return p.parseUnsetCommand() - case TokenReset: - return p.parseResetCommand() case TokenList: return p.parseAPIListCommands() case TokenParse: @@ -3609,21 +3634,6 @@ func (p *Parser) parseUserStatement() (*Command, error) { } } -func (p *Parser) parseUnsetCommand() (*Command, error) { - p.nextToken() // consume UNSET - - if p.curToken.Type != TokenKey { - return nil, fmt.Errorf("expected TOKEN after UNSET") - } - p.nextToken() - - // Semicolon is optional - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("api_unset_api_key"), nil -} - // parseGetCommand parses: GET CHUNK or GET METADATA func (p *Parser) parseGetCommand() (*Command, error) { p.nextToken() // consume GET @@ -4061,7 +4071,7 @@ func (p *Parser) parseUserStartIngestion() (*Command, error) { cmd.Params["dataset_id"] = datasetID p.nextToken() - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -4112,7 +4122,7 @@ func (p *Parser) parseUserStopIngestion() (*Command, error) { cmd.Params["tasks"] = tasks p.nextToken() - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -4138,7 +4148,7 @@ func (p *Parser) parseAPIListIngestionTasks() (*Command, error) { cmd.Params["dataset_id"] = datasetID } - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -4164,7 +4174,7 @@ func (p *Parser) parseUserRemoveTask() (*Command, error) { cmd.Params["tasks"] = tasks - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -4201,6 +4211,30 @@ func (p *Parser) parseUserShowAPI() (*Command, error) { return cmd, nil } +func (p *Parser) parseAPIShowLogCommands() (*Command, error) { + p.nextToken() // consume LOG + + switch p.curToken.Type { + case TokenLevel: + return p.parseShowLogLevel() + default: + return nil, fmt.Errorf("expected LEVEL after LOG") + } +} + +func (p *Parser) parseShowLogLevel() (*Command, error) { + p.nextToken() // consume LEVEL + + cmd := NewCommand("api_show_log_level") + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return cmd, nil +} + func (p *Parser) parseAPIListAPIServers() (*Command, error) { p.nextToken() // consume API @@ -4221,6 +4255,32 @@ func (p *Parser) parseAPIListAPIServers() (*Command, error) { return cmd, nil } +func (p *Parser) parseAPIListEnvironments() (*Command, error) { + p.nextToken() // consume Envs + + cmd := NewCommand("api_list_environments") + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return cmd, nil +} + +func (p *Parser) parseAPIListVariables() (*Command, error) { + p.nextToken() // consume Variables + + cmd := NewCommand("api_list_variables") + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return cmd, nil +} + func (p *Parser) parseExplainCommand() (*Command, error) { p.nextToken() // consume EXPLAIN diff --git a/internal/common/status_message.go b/internal/common/status_message.go index 8be706274e..ad68edb42b 100644 --- a/internal/common/status_message.go +++ b/internal/common/status_message.go @@ -1,3 +1,19 @@ +// +// Copyright 2026 The InfiniFlow Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + package common import ( diff --git a/internal/common/system.go b/internal/common/system.go new file mode 100644 index 0000000000..d7ef609304 --- /dev/null +++ b/internal/common/system.go @@ -0,0 +1,75 @@ +// +// Copyright 2026 The InfiniFlow Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package common + +import ( + "encoding/json" + "fmt" + "ragflow/internal/entity" + "strconv" + "strings" +) + +func FormatSystemSetting(setting entity.SystemSettings) map[string]interface{} { + return map[string]interface{}{ + "data_type": setting.DataType, + "name": setting.Name, + "setting_type": "config", + "value": setting.Value, + } +} + +func FormatSystemSettings(settings []entity.SystemSettings) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(settings)) + for _, setting := range settings { + result = append(result, FormatSystemSetting(setting)) + } + return result +} + +func ValidateSystemSettingValue(setting entity.SystemSettings, value string) error { + dataType := strings.ToLower(setting.DataType) + switch dataType { + case "string": + return nil + case "integer", "int": + if _, err := strconv.Atoi(value); err != nil { + return fmt.Errorf("invalid integer value for %s: %s", setting.Name, value) + } + case "bool", "boolean": + if value != "true" && value != "false" { + return fmt.Errorf("invalid bool value for %s: expected true or false", setting.Name) + } + case "json": + if !json.Valid([]byte(value)) { + return fmt.Errorf("invalid JSON value for %s", setting.Name) + } + default: + return fmt.Errorf("unsupported data type for %s: %s", setting.Name, setting.DataType) + } + return nil +} + +func InferSystemSettingDataType(name string) string { + if strings.HasPrefix(name, "sandbox.") { + return "json" + } + if strings.HasSuffix(name, ".enabled") { + return "bool" + } + return "string" +} diff --git a/internal/handler/system.go b/internal/handler/system.go index 7c26074d40..8aa9d70371 100644 --- a/internal/handler/system.go +++ b/internal/handler/system.go @@ -222,3 +222,87 @@ func (h *SystemHandler) SetLogLevel(c *gin.Context) { "data": gin.H{"level": req.Level}, }) } + +// ListVariables handle list variables +func (h *SystemHandler) ListVariables(c *gin.Context) { + variables, err := h.systemService.ListAllVariables() + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "code": 500, + "message": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "message": "success", + "data": 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 +// Python logic: update or create a system setting with the given name and value +func (h *SystemHandler) SetVariable(c *gin.Context) { + var req SetVariableHTTPRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusOK, gin.H{ + "code": 400, + "message": "Var name is required", + }) + return + } + + if req.VarName == "" { + c.JSON(http.StatusOK, gin.H{ + "code": 400, + "message": "Var name is required", + }) + return + } + + if req.VarValue == "" { + c.JSON(http.StatusOK, gin.H{ + "code": 400, + "message": "Var value is required", + }) + return + } + + if err := h.systemService.SetVariable(req.VarName, req.VarValue); err != nil { + c.JSON(http.StatusOK, gin.H{ + "code": 500, + "message": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "message": "SUCCESS", + }) +} + +// ListEnvironments handle list environments +func (h *SystemHandler) ListEnvironments(c *gin.Context) { + environments, err := h.systemService.ListEnvironments() + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "code": 500, + "message": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "message": "success", + "data": environments, + }) +} diff --git a/internal/router/router.go b/internal/router/router.go index 124fc1d3db..91428a7842 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -561,6 +561,13 @@ func (r *Router) Setup(engine *gin.Engine) { config.PUT("/log", r.systemHandler.SetLogLevel) } + // Variables/Settings + system.GET("/variables", r.systemHandler.ListVariables) + system.PUT("/variables", r.systemHandler.SetVariable) + + // Environments + system.GET("/environments", r.systemHandler.ListEnvironments) + //log := system.Group("/log") //{ // // /api/v1/system/log GET diff --git a/internal/service/system.go b/internal/service/system.go index 417ee4fd5f..cf82f7eb15 100644 --- a/internal/service/system.go +++ b/internal/service/system.go @@ -20,7 +20,10 @@ import ( "context" "encoding/json" "fmt" + "os" + "ragflow/internal/common" "ragflow/internal/engine/redis" + "ragflow/internal/entity" "strings" "time" @@ -32,11 +35,15 @@ import ( ) // SystemService system service -type SystemService struct{} +type SystemService struct { + systemSettingsDAO *dao.SystemSettingsDAO +} // NewSystemService create system service func NewSystemService() *SystemService { - return &SystemService{} + return &SystemService{ + systemSettingsDAO: dao.NewSystemSettingsDAO(), + } } // ConfigResponse system configuration response @@ -370,3 +377,115 @@ func (s *SystemService) Healthz(ctx context.Context) (*HealthzResponse, bool) { } return result, allOK } + +// ListAllVariables list all variables +// Returns all system settings from database +func (s *SystemService) ListAllVariables() ([]map[string]interface{}, error) { + settings, err := s.systemSettingsDAO.GetAll() + if err != nil { + return nil, err + } + + return common.FormatSystemSettings(settings), nil +} + +// SetVariable set variable +// Creates or updates a system setting +// If the setting exists, updates it; otherwise creates a new one +func (s *SystemService) SetVariable(varName, varValue string) error { + settings, err := s.systemSettingsDAO.GetByName(varName) + if err != nil { + return err + } + + if len(settings) == 1 { + setting := &settings[0] + if err = common.ValidateSystemSettingValue(*setting, varValue); err != nil { + return err + } + setting.Value = varValue + return s.systemSettingsDAO.UpdateByName(varName, setting) + } else if len(settings) > 1 { + return fmt.Errorf("can't update more than 1 setting: %s", varName) + } + + dataType := common.InferSystemSettingDataType(varName) + newSetting := &entity.SystemSettings{ + Name: varName, + Value: varValue, + Source: "admin", + DataType: dataType, + } + if err = common.ValidateSystemSettingValue(*newSetting, varValue); err != nil { + return err + } + return s.systemSettingsDAO.Create(newSetting) +} + +// Config methods + +// ListAllConfigs list all configs +// Returns all service configurations from the config file +func (s *SystemService) ListAllConfigs() ([]map[string]interface{}, error) { + result := server.GetAllConfigs() + return result, nil +} + +// Environment methods + +// ListEnvironments list all environments +func (s *SystemService) ListEnvironments() ([]map[string]interface{}, error) { + result := make([]map[string]interface{}, 0) + + // DOC_ENGINE + docEngine := os.Getenv("DOC_ENGINE") + if docEngine == "" { + docEngine = "elasticsearch" + } + result = append(result, map[string]interface{}{ + "env": "DOC_ENGINE", + "value": docEngine, + }) + + // DEFAULT_SUPERUSER_EMAIL + defaultSuperuserEmail := os.Getenv("DEFAULT_SUPERUSER_EMAIL") + if defaultSuperuserEmail == "" { + defaultSuperuserEmail = "admin@ragflow.io" + } + result = append(result, map[string]interface{}{ + "env": "DEFAULT_SUPERUSER_EMAIL", + "value": defaultSuperuserEmail, + }) + + // DB_TYPE + dbType := os.Getenv("DB_TYPE") + if dbType == "" { + dbType = "mysql" + } + result = append(result, map[string]interface{}{ + "env": "DB_TYPE", + "value": dbType, + }) + + // DEVICE + device := os.Getenv("DEVICE") + if device == "" { + device = "cpu" + } + result = append(result, map[string]interface{}{ + "env": "DEVICE", + "value": device, + }) + + // STORAGE_IMPL + storageImpl := os.Getenv("STORAGE_IMPL") + if storageImpl == "" { + storageImpl = "MINIO" + } + result = append(result, map[string]interface{}{ + "env": "STORAGE_IMPL", + "value": storageImpl, + }) + + return result, nil +}