feature: add system setting service (#12408)

### What problem does this PR solve?

#12409 

### Type of change

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

---------

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
This commit is contained in:
Jin Hai
2026-01-04 14:21:39 +08:00
committed by GitHub
parent 11779697de
commit ac9113b0ef
8 changed files with 301 additions and 5 deletions

View File

@ -55,6 +55,9 @@ sql_command: list_services
| show_version | show_version
| grant_admin | grant_admin
| revoke_admin | revoke_admin
| set_variable
| show_variable
| list_variables
// meta command definition // meta command definition
meta_command: "\\" meta_command_name [meta_args] meta_command: "\\" meta_command_name [meta_args]
@ -98,6 +101,8 @@ RESOURCES: "RESOURCES"i
ON: "ON"i ON: "ON"i
SET: "SET"i SET: "SET"i
VERSION: "VERSION"i VERSION: "VERSION"i
VAR: "VAR"i
VARS: "VARS"i
list_services: LIST SERVICES ";" list_services: LIST SERVICES ";"
show_service: SHOW SERVICE NUMBER ";" show_service: SHOW SERVICE NUMBER ";"
@ -129,6 +134,10 @@ show_user_permission: SHOW USER PERMISSION quoted_string ";"
grant_admin: GRANT ADMIN quoted_string ";" grant_admin: GRANT ADMIN quoted_string ";"
revoke_admin: REVOKE 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 ";" show_version: SHOW VERSION ";"
action_list: identifier ("," identifier)* action_list: identifier ("," identifier)*
@ -263,6 +272,18 @@ class AdminTransformer(Transformer):
user_name = items[2] user_name = items[2]
return {"type": "revoke_admin", "user_name": user_name} 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): def action_list(self, items):
return items return items
@ -621,6 +642,12 @@ class AdminCLI(Cmd):
self._grant_admin(command_dict) self._grant_admin(command_dict)
case "revoke_admin": case "revoke_admin":
self._revoke_admin(command_dict) 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": case "meta":
self._handle_meta_command(command_dict) self._handle_meta_command(command_dict)
case _: case _:
@ -780,6 +807,39 @@ class AdminCLI(Cmd):
else: else:
print(f"Fail to revoke {user_name} admin authorization, code: {res_json['code']}, message: {res_json['message']}") 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): def _handle_list_datasets(self, command):
username_tree: Tree = command["user_name"] username_tree: Tree = command["user_name"]
user_name: str = username_tree.children[0].strip("'\"") user_name: str = username_tree.children[0].strip("'\"")

View File

@ -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 auth import login_verify, login_admin, check_admin_auth
from responses import success_response, error_response 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 roles import RoleMgr
from api.common.exceptions import AdminException from api.common.exceptions import AdminException
from common.versions import get_ragflow_version from common.versions import get_ragflow_version
@ -406,6 +406,49 @@ def get_user_permission(user_name: str):
except Exception as e: except Exception as e:
return error_response(str(e), 500) 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']) @admin_bp.route('/version', methods=['GET'])
@login_required @login_required
@check_admin_auth @check_admin_auth

View File

@ -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.canvas_service import UserCanvasService
from api.db.services.user_service import TenantService from api.db.services.user_service import TenantService
from api.db.services.knowledgebase_service import KnowledgebaseService 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.crypt import decrypt
from api.utils import health_utils from api.utils import health_utils
@ -263,3 +264,47 @@ class ServiceMgr:
@staticmethod @staticmethod
def restart_service(service_id: int): def restart_service(service_id: int):
raise AdminException("restart_service: not implemented") 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}")

View File

@ -1197,6 +1197,13 @@ class Memory(DataBaseModel):
class Meta: class Meta:
db_table = "memory" 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(): def migrate_db():
logging.disable(logging.ERROR) logging.disable(logging.ERROR)

View File

@ -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.tenant_llm_service import LLMFactoriesService, TenantLLMService
from api.db.services.llm_service import LLMService, LLMBundle, get_init_tenant_llm 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.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 api.db.joint_services.memory_message_service import init_message_id_sequence, init_memory_size_cache
from common.constants import LLMType from common.constants import LLMType
from common.file_utils import get_project_base_directory from common.file_utils import get_project_base_directory
@ -158,13 +159,15 @@ def add_graph_templates():
CanvasTemplateService.save(**cnvs) CanvasTemplateService.save(**cnvs)
except Exception: except Exception:
CanvasTemplateService.update_by_id(cnvs["id"], cnvs) CanvasTemplateService.update_by_id(cnvs["id"], cnvs)
except Exception: except Exception as e:
logging.exception("Add agent templates error: ") logging.exception(f"Add agent templates error: {e}")
def init_web_data(): def init_web_data():
start_time = time.time() start_time = time.time()
init_table()
init_llm_factory() init_llm_factory()
# if not UserService.get_all().count(): # if not UserService.get_all().count():
# init_superuser() # init_superuser()
@ -174,6 +177,31 @@ def init_web_data():
init_memory_size_cache() init_memory_size_cache()
logging.info("init web data success:{}".format(time.time() - start_time)) 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__': if __name__ == '__main__':
init_web_db() init_web_db()

View File

@ -190,10 +190,15 @@ class CommonService:
data_list (list): List of dictionaries containing record data to insert. 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. 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(): with DB.atomic():
for d in data_list: for d in data_list:
d["create_time"] = current_timestamp() d["create_time"] = current_ts
d["create_date"] = datetime_format(datetime.now()) 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): for i in range(0, len(data_list), batch_size):
cls.model.insert_many(data_list[i : i + batch_size]).execute() cls.model.insert_many(data_list[i : i + batch_size]).execute()

View File

@ -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

64
conf/system_settings.json Normal file
View File

@ -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": ""
}
]
}