diff --git a/admin/client/admin_client.py b/admin/client/admin_client.py index 891a395f1..100783505 100644 --- a/admin/client/admin_client.py +++ b/admin/client/admin_client.py @@ -55,6 +55,9 @@ sql_command: list_services | show_version | grant_admin | revoke_admin + | set_variable + | show_variable + | list_variables // meta command definition meta_command: "\\" meta_command_name [meta_args] @@ -98,6 +101,8 @@ RESOURCES: "RESOURCES"i ON: "ON"i SET: "SET"i VERSION: "VERSION"i +VAR: "VAR"i +VARS: "VARS"i list_services: LIST SERVICES ";" show_service: SHOW SERVICE NUMBER ";" @@ -129,6 +134,10 @@ show_user_permission: SHOW USER PERMISSION quoted_string ";" grant_admin: GRANT ADMIN quoted_string ";" revoke_admin: REVOKE ADMIN quoted_string ";" +set_variable: SET VAR identifier identifier ";" +show_variable: SHOW VAR identifier ";" +list_variables: LIST VARS ";" + show_version: SHOW VERSION ";" action_list: identifier ("," identifier)* @@ -263,6 +272,18 @@ class AdminTransformer(Transformer): user_name = items[2] return {"type": "revoke_admin", "user_name": user_name} + def set_variable(self, items): + var_name = items[2] + var_value = items[3] + return {"type": "set_variable", "var_name": var_name, "var_value": var_value} + + def show_variable(self, items): + var_name = items[2] + return {"type": "show_variable", "var_name": var_name} + + def list_variables(self, items): + return {"type": "list_variables"} + def action_list(self, items): return items @@ -621,6 +642,12 @@ class AdminCLI(Cmd): self._grant_admin(command_dict) case "revoke_admin": self._revoke_admin(command_dict) + case "set_variable": + self._set_variable(command_dict) + case "show_variable": + self._show_variable(command_dict) + case "list_variables": + self._list_variables(command_dict) case "meta": self._handle_meta_command(command_dict) case _: @@ -780,6 +807,39 @@ class AdminCLI(Cmd): else: print(f"Fail to revoke {user_name} admin authorization, code: {res_json['code']}, message: {res_json['message']}") + def _set_variable(self, command): + var_name_tree: Tree = command["var_name"] + var_name = var_name_tree.children[0].strip("'\"") + var_value_tree: Tree = command["var_value"] + var_value = var_value_tree.children[0].strip("'\"") + url = f"http://{self.host}:{self.port}/api/v1/admin/variables" + response = self.session.put(url, json={"var_name": var_name, "var_value": var_value}) + res_json = response.json() + if response.status_code == 200: + print(res_json["message"]) + else: + print(f"Fail to set variable {var_name} to {var_value}, code: {res_json['code']}, message: {res_json['message']}") + + def _show_variable(self, command): + var_name_tree: Tree = command["var_name"] + var_name = var_name_tree.children[0].strip("'\"") + url = f"http://{self.host}:{self.port}/api/v1/admin/variables" + response = self.session.get(url, json={"var_name": var_name}) + res_json = response.json() + if response.status_code == 200: + self._print_table_simple(res_json["data"]) + else: + print(f"Fail to get variable {var_name}, code: {res_json['code']}, message: {res_json['message']}") + + def _list_variables(self, command): + url = f"http://{self.host}:{self.port}/api/v1/admin/variables" + response = self.session.get(url) + res_json = response.json() + if response.status_code == 200: + self._print_table_simple(res_json["data"]) + else: + print(f"Fail to list variables, code: {res_json['code']}, message: {res_json['message']}") + def _handle_list_datasets(self, command): username_tree: Tree = command["user_name"] user_name: str = username_tree.children[0].strip("'\"") diff --git a/admin/server/routes.py b/admin/server/routes.py index 2d0e771ce..fcf75e485 100644 --- a/admin/server/routes.py +++ b/admin/server/routes.py @@ -21,7 +21,7 @@ from flask_login import current_user, login_required, logout_user from auth import login_verify, login_admin, check_admin_auth from responses import success_response, error_response -from services import UserMgr, ServiceMgr, UserServiceMgr +from services import UserMgr, ServiceMgr, UserServiceMgr, SettingsMgr from roles import RoleMgr from api.common.exceptions import AdminException from common.versions import get_ragflow_version @@ -406,6 +406,49 @@ def get_user_permission(user_name: str): except Exception as e: return error_response(str(e), 500) +@admin_bp.route('/variables', methods=['PUT']) +@login_required +@check_admin_auth +def set_variable(): + try: + data = request.get_json() + if not data and 'var_name' not in data: + return error_response("Var name is required", 400) + + if 'var_value' not in data: + return error_response("Var value is required", 400) + var_name: str = data['var_name'] + var_value: str = data['var_value'] + + SettingsMgr.update_by_name(var_name, var_value) + return success_response(None, "Set variable successfully") + except AdminException as e: + return error_response(str(e), 400) + except Exception as e: + return error_response(str(e), 500) + +@admin_bp.route('/variables', methods=['GET']) +@login_required +@check_admin_auth +def get_variable(): + try: + if request.content_length is None or request.content_length == 0: + # list variables + res = list(SettingsMgr.get_all()) + return success_response(res) + + # get var + data = request.get_json() + if not data and 'var_name' not in data: + return error_response("Var name is required", 400) + var_name: str = data['var_name'] + res = SettingsMgr.get_by_name(var_name) + return success_response(res) + except AdminException as e: + return error_response(str(e), 400) + except Exception as e: + return error_response(str(e), 500) + @admin_bp.route('/version', methods=['GET']) @login_required @check_admin_auth diff --git a/admin/server/services.py b/admin/server/services.py index 3cb908ebc..a1b4d5f1e 100644 --- a/admin/server/services.py +++ b/admin/server/services.py @@ -24,6 +24,7 @@ from api.db.joint_services.user_account_service import create_new_user, delete_u from api.db.services.canvas_service import UserCanvasService from api.db.services.user_service import TenantService from api.db.services.knowledgebase_service import KnowledgebaseService +from api.db.services.system_settings_service import SystemSettingsService from api.utils.crypt import decrypt from api.utils import health_utils @@ -263,3 +264,47 @@ class ServiceMgr: @staticmethod def restart_service(service_id: int): raise AdminException("restart_service: not implemented") + +class SettingsMgr: + @staticmethod + def get_all(): + + settings = SystemSettingsService.get_all() + result = [] + for setting in settings: + result.append({ + 'name': setting.name, + 'setting_type': setting.setting_type, + 'data_type': setting.data_type, + 'value': setting.value, + }) + return result + + @staticmethod + def get_by_name(name: str): + settings = SystemSettingsService.get_by_name(name) + if len(settings) == 0: + raise AdminException(f"Can't get setting: {name}") + result = [] + for setting in settings: + result.append({ + 'name': setting.name, + 'setting_type': setting.setting_type, + 'data_type': setting.data_type, + 'value': setting.value, + }) + return result + + @staticmethod + def update_by_name(name: str, value: str): + settings = SystemSettingsService.get_by_name(name) + if len(settings) == 1: + setting = settings[0] + setting.value = value + setting_dict = setting.to_dict() + SystemSettingsService.update_by_name(name, setting_dict) + elif len(settings) > 1: + raise AdminException(f"Can't update more than 1 setting: {name}") + else: + raise AdminException(f"No sett" + f"ing: {name}") \ No newline at end of file diff --git a/api/db/db_models.py b/api/db/db_models.py index 738e26a06..dff2f2f70 100644 --- a/api/db/db_models.py +++ b/api/db/db_models.py @@ -1197,6 +1197,13 @@ class Memory(DataBaseModel): class Meta: db_table = "memory" +class SystemSettings(DataBaseModel): + name = CharField(max_length=128, primary_key=True) + setting_type = CharField(max_length=32, null=False, index=False) + data_type = CharField(max_length=32, null=False, index=False) + value = CharField(max_length=1024, null=False, index=False) + class Meta: + db_table = "system_settings" def migrate_db(): logging.disable(logging.ERROR) diff --git a/api/db/init_data.py b/api/db/init_data.py index 77f676f09..cb2feb748 100644 --- a/api/db/init_data.py +++ b/api/db/init_data.py @@ -30,6 +30,7 @@ from api.db.services.knowledgebase_service import KnowledgebaseService from api.db.services.tenant_llm_service import LLMFactoriesService, TenantLLMService from api.db.services.llm_service import LLMService, LLMBundle, get_init_tenant_llm from api.db.services.user_service import TenantService, UserTenantService +from api.db.services.system_settings_service import SystemSettingsService from api.db.joint_services.memory_message_service import init_message_id_sequence, init_memory_size_cache from common.constants import LLMType from common.file_utils import get_project_base_directory @@ -158,13 +159,15 @@ def add_graph_templates(): CanvasTemplateService.save(**cnvs) except Exception: CanvasTemplateService.update_by_id(cnvs["id"], cnvs) - except Exception: - logging.exception("Add agent templates error: ") + except Exception as e: + logging.exception(f"Add agent templates error: {e}") def init_web_data(): start_time = time.time() + init_table() + init_llm_factory() # if not UserService.get_all().count(): # init_superuser() @@ -174,6 +177,31 @@ def init_web_data(): init_memory_size_cache() logging.info("init web data success:{}".format(time.time() - start_time)) +def init_table(): + # init system_settings + with open(os.path.join(get_project_base_directory(), "conf", "system_settings.json"), "r") as f: + records_from_file = json.load(f)["system_settings"] + + record_index = {} + records_from_db = SystemSettingsService.get_all() + for index, record in enumerate(records_from_db): + record_index[record.name] = index + + to_save = [] + for record in records_from_file: + setting_name = record["name"] + if setting_name not in record_index: + to_save.append(record) + + len_to_save = len(to_save) + if len_to_save > 0: + # not initialized + try: + SystemSettingsService.insert_many(to_save, len_to_save) + except Exception as e: + logging.exception("System settings init error: {}".format(e)) + raise e + if __name__ == '__main__': init_web_db() diff --git a/api/db/services/common_service.py b/api/db/services/common_service.py index 60db241cc..df95debb5 100644 --- a/api/db/services/common_service.py +++ b/api/db/services/common_service.py @@ -190,10 +190,15 @@ class CommonService: data_list (list): List of dictionaries containing record data to insert. batch_size (int, optional): Number of records to insert in each batch. Defaults to 100. """ + current_ts = current_timestamp() + current_datetime = datetime_format(datetime.now()) with DB.atomic(): for d in data_list: - d["create_time"] = current_timestamp() - d["create_date"] = datetime_format(datetime.now()) + d["create_time"] = current_ts + d["create_date"] = current_datetime + d["update_time"] = current_ts + d["update_date"] = current_datetime + for i in range(0, len(data_list), batch_size): cls.model.insert_many(data_list[i : i + batch_size]).execute() diff --git a/api/db/services/system_settings_service.py b/api/db/services/system_settings_service.py new file mode 100644 index 000000000..1ffabf698 --- /dev/null +++ b/api/db/services/system_settings_service.py @@ -0,0 +1,44 @@ +# +# 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. +# +from datetime import datetime +from common.time_utils import current_timestamp, datetime_format +from api.db.db_models import DB +from api.db.db_models import SystemSettings +from api.db.services.common_service import CommonService + + +class SystemSettingsService(CommonService): + model = SystemSettings + + @classmethod + @DB.connection_context() + def get_by_name(cls, name): + objs = cls.model.select().where(cls.model.name.startswith(name)) + return objs + + @classmethod + @DB.connection_context() + def update_by_name(cls, name, obj): + obj["update_time"] = current_timestamp() + obj["update_date"] = datetime_format(datetime.now()) + cls.model.update(obj).where(cls.model.name.startswith(name)).execute() + return SystemSettings(**obj) + + @classmethod + @DB.connection_context() + def get_record_count(cls): + count = cls.model.select().count() + return count diff --git a/conf/system_settings.json b/conf/system_settings.json new file mode 100644 index 000000000..5ac6b4ceb --- /dev/null +++ b/conf/system_settings.json @@ -0,0 +1,64 @@ +{ + "system_settings": [ + { + "name": "enable_whitelist", + "setting_type": "config", + "data_type": "bool", + "value": "true" + }, + { + "name": "default_role", + "setting_type": "config", + "data_type": "string", + "value": "" + }, + { + "name": "mail.server", + "setting_type": "config", + "data_type": "string", + "value": "" + }, + { + "name": "mail.port", + "setting_type": "config", + "data_type": "integer", + "value": "" + }, + { + "name": "mail.use_ssl", + "setting_type": "config", + "data_type": "bool", + "value": "false" + }, + { + "name": "mail.use_tls", + "setting_type": "config", + "data_type": "bool", + "value": "false" + }, + { + "name": "mail.username", + "setting_type": "config", + "data_type": "string", + "value": "" + }, + { + "name": "mail.password", + "setting_type": "config", + "data_type": "string", + "value": "" + }, + { + "name": "mail.timeout", + "setting_type": "config", + "data_type": "integer", + "value": "10" + }, + { + "name": "mail.default_sender", + "setting_type": "config", + "data_type": "string", + "value": "" + } + ] +} \ No newline at end of file