From 9428875e257fdb1bcc3924af57ad1a6b7fb5120e Mon Sep 17 00:00:00 2001 From: fit2cloud-chenyw Date: Wed, 24 Dec 2025 13:22:08 +0800 Subject: [PATCH 1/8] perf: API permission verification caused the assistant's query error --- backend/apps/chat/api/chat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/apps/chat/api/chat.py b/backend/apps/chat/api/chat.py index 211b9534..a3ba2783 100644 --- a/backend/apps/chat/api/chat.py +++ b/backend/apps/chat/api/chat.py @@ -158,7 +158,7 @@ def _err(_e: Exception): @router.get("/recent_questions/{datasource_id}", response_model=List[str], summary=f"{PLACEHOLDER_PREFIX}get_recommend_questions") -@require_permissions(permission=SqlbotPermission(type='ds', keyExpression="datasource_id")) +#@require_permissions(permission=SqlbotPermission(type='ds', keyExpression="datasource_id")) async def recommend_questions(session: SessionDep, current_user: CurrentUser, datasource_id: int = Path(..., description=f"{PLACEHOLDER_PREFIX}ds_id")): return list_recent_questions(session=session, current_user=current_user, datasource_id=datasource_id) From d04395813a1392cfe40ff14ac0f91a92de5300cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=98=89=E8=B1=AA?= <42510293+ziyujiahao@users.noreply.github.com> Date: Wed, 24 Dec 2025 14:16:40 +0800 Subject: [PATCH 2/8] feat: Support log management (#668) --- backend/apps/api.py | 1 + backend/apps/chat/api/chat.py | 7 +- backend/apps/dashboard/api/dashboard_api.py | 7 +- backend/apps/datasource/api/datasource.py | 8 +- backend/locales/en.json | 43 ++ backend/locales/ko-KR.json | 43 ++ backend/locales/zh-CN.json | 45 +- backend/pyproject.toml | 2 +- frontend/src/api/audit.ts | 12 + frontend/src/api/chat.ts | 4 +- frontend/src/api/dashboard.ts | 2 +- frontend/src/api/datasource.ts | 2 +- frontend/src/assets/svg/icon_error.svg | 11 + frontend/src/assets/svg/icon_issue.svg | 15 + frontend/src/assets/svg/icon_success.svg | 11 + frontend/src/components/layout/Menu.vue | 1 + frontend/src/i18n/en.json | 34 + frontend/src/i18n/ko-KR.json | 34 + frontend/src/i18n/zh-CN.json | 34 + frontend/src/router/index.ts | 7 + frontend/src/views/chat/ChatList.vue | 2 +- .../views/dashboard/common/ResourceTree.vue | 2 +- frontend/src/views/ds/Datasource.vue | 2 +- frontend/src/views/system/audit/index.vue | 627 ++++++++++++++++++ 24 files changed, 938 insertions(+), 18 deletions(-) create mode 100644 frontend/src/api/audit.ts create mode 100644 frontend/src/assets/svg/icon_error.svg create mode 100644 frontend/src/assets/svg/icon_issue.svg create mode 100644 frontend/src/assets/svg/icon_success.svg create mode 100644 frontend/src/views/system/audit/index.vue diff --git a/backend/apps/api.py b/backend/apps/api.py index 06dd11b5..23ca0175 100644 --- a/backend/apps/api.py +++ b/backend/apps/api.py @@ -9,6 +9,7 @@ from apps.terminology.api import terminology from apps.settings.api import base + api_router = APIRouter() api_router.include_router(login.router) api_router.include_router(user.router) diff --git a/backend/apps/chat/api/chat.py b/backend/apps/chat/api/chat.py index a3ba2783..6dfe495f 100644 --- a/backend/apps/chat/api/chat.py +++ b/backend/apps/chat/api/chat.py @@ -81,14 +81,15 @@ async def rename(session: SessionDep, chat: RenameChat): ) -@router.delete("/{chart_id}", response_model=str, summary=f"{PLACEHOLDER_PREFIX}delete_chat") +@router.delete("/{chart_id}/{brief}", response_model=str, summary=f"{PLACEHOLDER_PREFIX}delete_chat") @system_log(LogConfig( operation_type=OperationType.DELETE_QA, operation_detail=OperationDetails.DELETE_QA_DETAILS, module=OperationModules.QA, - resource_id_expr="chart_id" + resource_id_expr="chart_id", + remark_expr="brief" )) -async def delete(session: SessionDep, chart_id: int): +async def delete(session: SessionDep, chart_id: int, brief: str): try: return delete_chat(session=session, chart_id=chart_id) except Exception as e: diff --git a/backend/apps/dashboard/api/dashboard_api.py b/backend/apps/dashboard/api/dashboard_api.py index 7af75d76..f93d1af7 100644 --- a/backend/apps/dashboard/api/dashboard_api.py +++ b/backend/apps/dashboard/api/dashboard_api.py @@ -30,14 +30,15 @@ async def update_resource_api(session: SessionDep, user: CurrentUser, dashboard: return update_resource(session=session, user=user, dashboard=dashboard) -@router.delete("/delete_resource/{resource_id}") +@router.delete("/delete_resource/{resource_id}/{name}") @system_log(LogConfig( operation_type=OperationType.DELETE_DASHBOARD, operation_detail=OperationDetails.DELETE_DASHBOARD_DETAILS, module=OperationModules.DASHBOARD, - resource_id_expr="resource_id" + resource_id_expr="resource_id", + remark_expr="name" )) -async def delete_resource_api(session: SessionDep, resource_id: str): +async def delete_resource_api(session: SessionDep, resource_id: str, name: str): return delete_resource(session, resource_id) diff --git a/backend/apps/datasource/api/datasource.py b/backend/apps/datasource/api/datasource.py index 096abb3f..a4d7caec 100644 --- a/backend/apps/datasource/api/datasource.py +++ b/backend/apps/datasource/api/datasource.py @@ -30,6 +30,7 @@ TableSchemaResponse, ColumnSchemaResponse, PreviewResponse from sqlbot_xpack.audit.models.log_model import OperationType, OperationDetails, OperationModules from sqlbot_xpack.audit.schemas.logger_decorator import system_log, LogConfig + router = APIRouter(tags=["Datasource"], prefix="/datasource") path = settings.EXCEL_PATH @@ -107,15 +108,16 @@ def inner(): return await asyncio.to_thread(inner) -@router.post("/delete/{id}", response_model=None, summary=f"{PLACEHOLDER_PREFIX}ds_delete") +@router.post("/delete/{id}/{name}", response_model=None, summary=f"{PLACEHOLDER_PREFIX}ds_delete") @require_permissions(permission=SqlbotPermission(type='ds', keyExpression="id")) @system_log(LogConfig( operation_type=OperationType.DELETE_DATASOURCE, operation_detail=OperationDetails.DELETE_DATASOURCE_DETAILS, module=OperationModules.DATASOURCE, - resource_id_expr="id" + resource_id_expr="id", + remark_expr="name" )) -async def delete(session: SessionDep, id: int = Path(..., description=f"{PLACEHOLDER_PREFIX}ds_id")): +async def delete(session: SessionDep, id: int = Path(..., description=f"{PLACEHOLDER_PREFIX}ds_id"), name: str = None): return delete_ds(session, id) diff --git a/backend/locales/en.json b/backend/locales/en.json index b7054866..3c9aed5d 100644 --- a/backend/locales/en.json +++ b/backend/locales/en.json @@ -121,5 +121,48 @@ }, "i18n_authentication": { "record_not_exist": "{msg} record does not exist. Please save it first!" + }, + "i18n_audit": { + "success": "Success", + "failed": "Failed", + "create_qa": "Create Q&A", + "delete_qa": "Delete Q&A", + "create_datasource": "Create Datasource", + "update_datasource": "Edit Datasource", + "delete_datasource": "Delete Datasource", + "create_dashboard": "Create Dashboard", + "update_dashboard": "Edit Dashboard", + "delete_dashboard": "Delete Dashboard", + "setting_create_user": "Add Member", + "setting_delete_user": "Remove Member", + "setting_create_rule": "Add Rule Group", + "setting_delete_rule": "Remove Rule Group", + "setting_update_rule": "Configure Rules", + "setting_update_rule_user": "Configure Users", + "setting_create_terminology": "Create Terminology", + "create_qa_details": "Create Q&A【{resource_name}】", + "delete_qa_details": "Delete Q&A【{resource_name}】", + "create_datasource_details": "Create Datasource【{resource_name}】", + "update_datasource_details": "Edit Datasource【{resource_name}】", + "delete_datasource_details": "Delete Datasource【{resource_name}】", + "create_dashboard_details": "Create Dashboard【{resource_name}】", + "update_dashboard_details": "Edit Dashboard【{resource_name}】", + "delete_dashboard_details": "Delete Dashboard【{resource_name}】", + "setting_create_user_details": "Add Member【{resource_name}】", + "setting_delete_user_details": "Remove Member【{resource_name}】", + "setting_create_rule_details": "Add Rule Group【{resource_name}】", + "setting_delete_rule_details": "Remove Rule Group【{resource_name}】", + "setting_update_rule_details": "Configure Rules【{resource_name}】", + "setting_update_rule_user_details": "Configure Users【{resource_name}】", + "setting_create_terminology_details": "Create Terminology【{resource_name}】", + "system_log": "System Log", + "operation_type_name": "Operation Type", + "operation_detail_info": "Operation Details", + "user_name": "Operating User", + "oid_name": "Workspace", + "operation_status_name": "Operation Status", + "error_message": "Error Message", + "ip_address": "IP Address", + "create_time": "Operation Time" } } \ No newline at end of file diff --git a/backend/locales/ko-KR.json b/backend/locales/ko-KR.json index 786cc549..2b725828 100644 --- a/backend/locales/ko-KR.json +++ b/backend/locales/ko-KR.json @@ -121,5 +121,48 @@ }, "i18n_authentication": { "record_not_exist": "{msg} 기록이 존재하지 않습니다. 먼저 저장해 주세요!" + }, + "i18n_audit": { + "success": "성공", + "failed": "실패", + "create_qa": "Q&A 생성", + "delete_qa": "Q&A 삭제", + "create_datasource": "데이터 소스 생성", + "update_datasource": "데이터 소스 편집", + "delete_datasource": "데이터 소스 삭제", + "create_dashboard": "대시보드 생성", + "update_dashboard": "대시보드 편집", + "delete_dashboard": "대시보드 삭제", + "setting_create_user": "멤버 추가", + "setting_delete_user": "멤버 제거", + "setting_create_rule": "규칙 그룹 추가", + "setting_delete_rule": "규칙 그룹 제거", + "setting_update_rule": "규칙 설정", + "setting_update_rule_user": "사용자 설정", + "setting_create_terminology": "용어 생성", + "create_qa_details": "Q&A 생성【{resource_name}】", + "delete_qa_details": "Q&A 삭제【{resource_name}】", + "create_datasource_details": "데이터 소스 생성【{resource_name}】", + "update_datasource_details": "데이터 소스 편집【{resource_name}】", + "delete_datasource_details": "데이터 소스 삭제【{resource_name}】", + "create_dashboard_details": "대시보드 생성【{resource_name}】", + "update_dashboard_details": "대시보드 편집【{resource_name}】", + "delete_dashboard_details": "대시보드 삭제【{resource_name}】", + "setting_create_user_details": "멤버 추가【{resource_name}】", + "setting_delete_user_details": "멤버 제거【{resource_name}】", + "setting_create_rule_details": "규칙 그룹 추가【{resource_name}】", + "setting_delete_rule_details": "규칙 그룹 제거【{resource_name}】", + "setting_update_rule_details": "규칙 설정【{resource_name}】", + "setting_update_rule_user_details": "사용자 설정【{resource_name}】", + "setting_create_terminology_details": "용어 생성【{resource_name}】", + "system_log": "시스템 로그", + "operation_type_name": "작업 유형", + "operation_detail_info": "작업 상세 정보", + "user_name": "작업 사용자", + "oid_name": "작업 공간", + "operation_status_name": "작업 상태", + "error_message": "오류 메시지", + "ip_address": "IP 주소", + "create_time": "작업 시간" } } \ No newline at end of file diff --git a/backend/locales/zh-CN.json b/backend/locales/zh-CN.json index 9154c424..84236f34 100644 --- a/backend/locales/zh-CN.json +++ b/backend/locales/zh-CN.json @@ -121,5 +121,48 @@ }, "i18n_authentication": { "record_not_exist": "{msg} 记录不存在,请先保存!" + }, + "i18n_audit": { + "success": "成功", + "failed": "失败", + "create_qa": "新建问数", + "delete_qa": "删除问数", + "create_datasource": "新建数据源", + "update_datasource": "编辑数据源", + "delete_datasource": "删除数据源", + "create_dashboard": "新建仪表板", + "update_dashboard": "编辑仪表板", + "delete_dashboard": "删除仪表板", + "setting_create_user": "添加成员", + "setting_delete_user": "移除成员", + "setting_create_rule": "添加规则组", + "setting_delete_rule": "移除规则组", + "setting_update_rule": "设置规则", + "setting_update_rule_user": "设置用户", + "setting_create_terminology": "新建术语", + "create_qa_details": "新建问数【{resource_name}】", + "delete_qa_details": "删除问数【{resource_name}】", + "create_datasource_details": "新建数据源【{resource_name}】", + "update_datasource_details": "编辑数据源【{resource_name}】", + "delete_datasource_details": "删除数据源【{resource_name}】", + "create_dashboard_details": "新建仪表板【{resource_name}】", + "update_dashboard_details": "编辑仪表板【{resource_name}】", + "delete_dashboard_details": "删除仪表板【{resource_name}】", + "setting_create_user_details": "添加成员【{resource_name}】", + "setting_delete_user_details": "移除成员【{resource_name}】", + "setting_create_rule_details": "添加规则组【{resource_name}】", + "setting_delete_rule_details": "移除规则组【{resource_name}】", + "setting_update_rule_details": "设置规则【{resource_name}】", + "setting_update_rule_user_details": "设置用户【{resource_name}】", + "setting_create_terminology_details": "新建术语【{resource_name}】", + "system_log": "操作日志", + "operation_type_name": "操作类型", + "operation_detail_info": "操作详情", + "user_name": "操作用户", + "oid_name": "工作空间", + "operation_status_name": "操作状态", + "error_message": "错误信息", + "ip_address": "IP 地址", + "create_time": "操作时间" } -} \ No newline at end of file +} diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 83681ccd..4795d518 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -83,11 +83,11 @@ url = "https://test.pypi.org/simple" explicit = true [tool.uv.sources] -sqlbot-xpack = { index = "testpypi" } torch = [ { index = "pytorch-cpu", extra = "cpu" }, { index = "pytorch-cu128", extra = "cu128" }, ] +sqlbot-xpack = { path = "../../sqlbot-xpack" } [tool.uv] conflicts = [ diff --git a/frontend/src/api/audit.ts b/frontend/src/api/audit.ts new file mode 100644 index 00000000..7c19e90a --- /dev/null +++ b/frontend/src/api/audit.ts @@ -0,0 +1,12 @@ +import { request } from '@/utils/request' + +export const audit = { + getList: (pageNum: any, pageSize: any, params: any) => + request.get(`/system/audit/page/${pageNum}/${pageSize}${params}`), + export2Excel: (params: any) => + request.get(`/system/audit/export`, { + params, + responseType: 'blob', + requestOptions: { customError: true }, + }), +} diff --git a/frontend/src/api/chat.ts b/frontend/src/api/chat.ts index 42a7199c..918e58e3 100644 --- a/frontend/src/api/chat.ts +++ b/frontend/src/api/chat.ts @@ -324,8 +324,8 @@ export const chatApi = { renameChat: (chat_id: number | undefined, brief: string): Promise => { return request.post('/chat/rename', { id: chat_id, brief: brief }) }, - deleteChat: (id: number | undefined): Promise => { - return request.delete(`/chat/${id}`) + deleteChat: (id: number | undefined, brief: string): Promise => { + return request.delete(`/chat/${id}/${brief}`) }, analysis: (record_id: number | undefined, controller?: AbortController) => { return request.fetchStream(`/chat/record/${record_id}/analysis`, {}, controller) diff --git a/frontend/src/api/dashboard.ts b/frontend/src/api/dashboard.ts index f19ae1b8..7c5dc27e 100644 --- a/frontend/src/api/dashboard.ts +++ b/frontend/src/api/dashboard.ts @@ -9,6 +9,6 @@ export const dashboardApi = { update_canvas: (params: any) => request.post('/dashboard/update_canvas', params), check_name: (params: any) => request.post('/dashboard/check_name', params), delete_resource: (params: any) => - request.delete(`/dashboard/delete_resource/${params.id}`, params), + request.delete(`/dashboard/delete_resource/${params.id}/${params.name}`, params), move_resource: (params: any) => request.delete(`/dashboard/move_resource/${params.id}`, params), } diff --git a/frontend/src/api/datasource.ts b/frontend/src/api/datasource.ts index c349c85e..c14b13cd 100644 --- a/frontend/src/api/datasource.ts +++ b/frontend/src/api/datasource.ts @@ -8,7 +8,7 @@ export const datasourceApi = { add: (data: any) => request.post('/datasource/add', data), list: () => request.get('/datasource/list'), update: (data: any) => request.post('/datasource/update', data), - delete: (id: number) => request.post(`/datasource/delete/${id}`), + delete: (id: number, name: str) => request.post(`/datasource/delete/${id}/${name}`), getTables: (id: number) => request.post(`/datasource/getTables/${id}`), getTablesByConf: (data: any) => request.post('/datasource/getTablesByConf', data), getFields: (id: number, table_name: string) => diff --git a/frontend/src/assets/svg/icon_error.svg b/frontend/src/assets/svg/icon_error.svg new file mode 100644 index 00000000..5777bd1b --- /dev/null +++ b/frontend/src/assets/svg/icon_error.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/frontend/src/assets/svg/icon_issue.svg b/frontend/src/assets/svg/icon_issue.svg new file mode 100644 index 00000000..663e919b --- /dev/null +++ b/frontend/src/assets/svg/icon_issue.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/svg/icon_success.svg b/frontend/src/assets/svg/icon_success.svg new file mode 100644 index 00000000..2303d6f9 --- /dev/null +++ b/frontend/src/assets/svg/icon_success.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/frontend/src/components/layout/Menu.vue b/frontend/src/components/layout/Menu.vue index dd1fbcd2..a7514096 100644 --- a/frontend/src/components/layout/Menu.vue +++ b/frontend/src/components/layout/Menu.vue @@ -55,6 +55,7 @@ const routerList = computed(() => { !route.path.includes('prompt') && !route.path.includes('permission') && !route.path.includes('preview') && + !route.path.includes('audit') && route.path !== '/login' && route.path !== '/admin-login' && !route.path.includes('/system') && diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index 7d062246..74267635 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -822,6 +822,40 @@ "modelType": { "llm": "Large Language Model" }, + "audit": { + "system_log": "System Log", + "search_log": "Search Log", + "operation_type": "Operation Type", + "operation_details": "Operation Details", + "operation_user_name": "Operating User", + "operation_status": "Operation Status", + "user_name": "User Name", + "oid_name": "Workspace", + "ip_address": "IP Address", + "create_time": "Creation Time", + "no_log": "No Logs Available", + "all_236_terms": "Export all {msg} log entries?", + "export_hint": "Export all logs?", + "export": "Export", + "success": "Success", + "failed": "Failed", + "failed_info": "Operation Failure Details", + "create_qa": "Create Q&A", + "delete_qa": "Delete Q&A", + "create_datasource": "Create Datasource", + "update_datasource": "Edit Datasource", + "delete_datasource": "Delete Datasource", + "create_dashboard": "Create Dashboard", + "update_dashboard": "Edit Dashboard", + "delete_dashboard": "Delete Dashboard", + "setting_create_user": "Add Member", + "setting_delete_user": "Remove Member", + "setting_create_rule": "Add Rule Group", + "setting_delete_rule": "Remove Rule Group", + "setting_update_rule": "Configure Rules", + "setting_update_rule_user": "Configure Users", + "setting_create_terminology": "Create Terminology" + } "api_key": { "info_tips": "The API Key is your credential for accessing the SQLBot API, with full permissions for your account. Please keep it secure! Do not disclose the API Key in any public channels to avoid security risks from unauthorized use.", "create": "Create", diff --git a/frontend/src/i18n/ko-KR.json b/frontend/src/i18n/ko-KR.json index ca764db4..3805f8bf 100644 --- a/frontend/src/i18n/ko-KR.json +++ b/frontend/src/i18n/ko-KR.json @@ -822,6 +822,40 @@ "modelType": { "llm": "대형 언어 모델" }, + "audit": { + "system_log": "시스템 로그", + "search_log": "검색 로그", + "operation_type": "작업 유형", + "operation_details": "작업 상세", + "operation_user_name": "작업 사용자", + "operation_status": "작업 상태", + "user_name": "사용자 이름", + "oid_name": "작업 공간", + "ip_address": "IP 주소", + "create_time": "생성 시간", + "no_log": "로그 없음", + "all_236_terms": "전체 {msg}개의 로그를 내보내시겠습니까?", + "export_hint": "모든 로그를 내보내시겠습니까?", + "export": "내보내기", + "success": "성공", + "failed": "실패", + "failed_info": "작업 실패 상세", + "create_qa": "Q&A 생성", + "delete_qa": "Q&A 삭제", + "create_datasource": "데이터 소스 생성", + "update_datasource": "데이터 소스 편집", + "delete_datasource": "데이터 소스 삭제", + "create_dashboard": "대시보드 생성", + "update_dashboard": "대시보드 편집", + "delete_dashboard": "대시보드 삭제", + "setting_create_user": "멤버 추가", + "setting_delete_user": "멤버 제거", + "setting_create_rule": "규칙 그룹 추가", + "setting_delete_rule": "규칙 그룹 제거", + "setting_update_rule": "규칙 설정", + "setting_update_rule_user": "사용자 설정", + "setting_create_terminology": "용어 생성" + } "api_key": { "info_tips": "API 키는 SQLBot API에 액세스하는 비밀키로 계정의 모든 권한을 갖고 있습니다. 꼭 안전하게 보관하세요! 외부 채널에 어떠한 방식으로도 API 키를 공개하지 마시고, 타인이 악용하여 보안 위협이 발생하는 것을 방지하세요.", "create": "생성", diff --git a/frontend/src/i18n/zh-CN.json b/frontend/src/i18n/zh-CN.json index 91464287..fdb0b01a 100644 --- a/frontend/src/i18n/zh-CN.json +++ b/frontend/src/i18n/zh-CN.json @@ -823,6 +823,40 @@ "modelType": { "llm": "大语言模型" }, + "audit": { + "system_log": "操作日志", + "search_log": "搜索日志", + "operation_type": "操作类型", + "operation_details": "操作详情", + "operation_user_name": "操作用户", + "operation_status": "操作状态", + "user_name": "操作日志", + "oid_name": "工作空间", + "ip_address": "IP 地址", + "create_time": "创建时间", + "no_log": "暂无日志", + "all_236_terms": "是否导出全部 {msg} 条日志?", + "export_hint": "是否导出全部日志?", + "export": "导出", + "success": "成功", + "failed": "失败", + "failed_info": "操作失败详情", + "create_qa": "新建问数", + "delete_qa": "删除问数", + "create_datasource": "新建数据源", + "update_datasource": "编辑数据源", + "delete_datasource": "删除数据源", + "create_dashboard": "新建仪表板", + "update_dashboard": "编辑仪表板", + "delete_dashboard": "删除仪表板", + "setting_create_user": "添加成员", + "setting_delete_user": "移除成员", + "setting_create_rule": "添加规则组", + "setting_delete_rule": "移除规则组", + "setting_update_rule": "设置规则", + "setting_update_rule_user": "设置用户", + "setting_create_terminology": "新建术语" + } "api_key": { "info_tips": "API Key 是您访问 SQLBot API 的密钥,具有账户的完全权限,请您务必妥善保管!不要以任何方式公开 API Key 到外部渠道,避免被他人利用造成安全威胁。", "create": "创建", diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 51f8aba4..ca3f1d09 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -18,6 +18,7 @@ import Member from '@/views/system/member/index.vue' import Professional from '@/views/system/professional/index.vue' import Training from '@/views/system/training/index.vue' import Prompt from '@/views/system/prompt/index.vue' +import Audit from '@/views/system/audit/index.vue' import Appearance from '@/views/system/appearance/index.vue' import Parameter from '@/views/system/parameter/index.vue' import Authentication from '@/views/system/authentication/index.vue' @@ -130,6 +131,12 @@ export const routes = [ component: Prompt, meta: { title: t('prompt.customize_prompt_words') }, }, + { + path: '/set/audit', + name: 'audit', + component: Audit, + meta: { title: t('audit.system_log') }, + }, ], }, { diff --git a/frontend/src/views/chat/ChatList.vue b/frontend/src/views/chat/ChatList.vue index 36cc985c..c2a98aa3 100644 --- a/frontend/src/views/chat/ChatList.vue +++ b/frontend/src/views/chat/ChatList.vue @@ -122,7 +122,7 @@ function handleCommand(command: string | number | object, chat: Chat) { }).then(() => { _loading.value = true chatApi - .deleteChat(chat.id) + .deleteChat(chat.id, chat.brief) .then(() => { ElMessage({ type: 'success', diff --git a/frontend/src/views/dashboard/common/ResourceTree.vue b/frontend/src/views/dashboard/common/ResourceTree.vue index bce90f43..a6496ee4 100644 --- a/frontend/src/views/dashboard/common/ResourceTree.vue +++ b/frontend/src/views/dashboard/common/ResourceTree.vue @@ -230,7 +230,7 @@ const operation = (opt: string, data: SQTreeNode) => { autofocus: false, showClose: false, }).then(() => { - dashboardApi.delete_resource({ id: data.id }).then(() => { + dashboardApi.delete_resource({ id: data.id, name: data.name }).then(() => { ElMessage.success(t('dashboard.delete_success')) getTree() dashboardStore.canvasDataInit() diff --git a/frontend/src/views/ds/Datasource.vue b/frontend/src/views/ds/Datasource.vue index 3a9fcb28..6fb08ec1 100644 --- a/frontend/src/views/ds/Datasource.vue +++ b/frontend/src/views/ds/Datasource.vue @@ -159,7 +159,7 @@ const deleteHandler = (item: any) => { '' ), }).then(() => { - datasourceApi.delete(item.id).then(() => { + datasourceApi.delete(item.id, item.name).then(() => { ElMessage({ type: 'success', message: t('dashboard.delete_success'), diff --git a/frontend/src/views/system/audit/index.vue b/frontend/src/views/system/audit/index.vue new file mode 100644 index 00000000..65434e72 --- /dev/null +++ b/frontend/src/views/system/audit/index.vue @@ -0,0 +1,627 @@ + + + + + + From 9d21214ac957cf233836d069216c392fcf4a6945 Mon Sep 17 00:00:00 2001 From: fit2cloud-chenyw Date: Wed, 24 Dec 2025 14:23:48 +0800 Subject: [PATCH 3/8] perf: Optimize the model form information query method --- backend/apps/system/api/aimodel.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/backend/apps/system/api/aimodel.py b/backend/apps/system/api/aimodel.py index 8f01a419..1b49c00d 100644 --- a/backend/apps/system/api/aimodel.py +++ b/backend/apps/system/api/aimodel.py @@ -107,10 +107,13 @@ async def get_model_by_id( config_list = [AiModelConfigItem(**item) for item in raw] except Exception: pass - if db_model.api_key: - db_model.api_key = await sqlbot_decrypt(db_model.api_key) - if db_model.api_domain: - db_model.api_domain = await sqlbot_decrypt(db_model.api_domain) + try: + if db_model.api_key: + db_model.api_key = await sqlbot_decrypt(db_model.api_key) + if db_model.api_domain: + db_model.api_domain = await sqlbot_decrypt(db_model.api_domain) + except Exception: + pass data = AiModelDetail.model_validate(db_model).model_dump(exclude_unset=True) data.pop("config", None) data["config_list"] = config_list From 49973cedba5a1cc0a745dde0e63a93aec3c0a848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=98=89=E8=B1=AA?= <42510293+ziyujiahao@users.noreply.github.com> Date: Wed, 24 Dec 2025 14:30:41 +0800 Subject: [PATCH 4/8] refactor: code merge (#670) --- frontend/src/i18n/en.json | 2 +- frontend/src/i18n/ko-KR.json | 4 ++-- frontend/src/i18n/zh-CN.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index 74267635..62058a30 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -855,7 +855,7 @@ "setting_update_rule": "Configure Rules", "setting_update_rule_user": "Configure Users", "setting_create_terminology": "Create Terminology" - } + }, "api_key": { "info_tips": "The API Key is your credential for accessing the SQLBot API, with full permissions for your account. Please keep it secure! Do not disclose the API Key in any public channels to avoid security risks from unauthorized use.", "create": "Create", diff --git a/frontend/src/i18n/ko-KR.json b/frontend/src/i18n/ko-KR.json index 3805f8bf..f5d978fb 100644 --- a/frontend/src/i18n/ko-KR.json +++ b/frontend/src/i18n/ko-KR.json @@ -855,11 +855,11 @@ "setting_update_rule": "규칙 설정", "setting_update_rule_user": "사용자 설정", "setting_create_terminology": "용어 생성" - } + }, "api_key": { "info_tips": "API 키는 SQLBot API에 액세스하는 비밀키로 계정의 모든 권한을 갖고 있습니다. 꼭 안전하게 보관하세요! 외부 채널에 어떠한 방식으로도 API 키를 공개하지 마시고, 타인이 악용하여 보안 위협이 발생하는 것을 방지하세요.", "create": "생성", "to_doc": "API 보기", "trigger_limit": "최대 {0}개의 API 키 생성 지원" } -} \ No newline at end of file +} diff --git a/frontend/src/i18n/zh-CN.json b/frontend/src/i18n/zh-CN.json index fdb0b01a..a0f36808 100644 --- a/frontend/src/i18n/zh-CN.json +++ b/frontend/src/i18n/zh-CN.json @@ -856,11 +856,11 @@ "setting_update_rule": "设置规则", "setting_update_rule_user": "设置用户", "setting_create_terminology": "新建术语" - } + }, "api_key": { "info_tips": "API Key 是您访问 SQLBot API 的密钥,具有账户的完全权限,请您务必妥善保管!不要以任何方式公开 API Key 到外部渠道,避免被他人利用造成安全威胁。", "create": "创建", "to_doc": "查看 API", "trigger_limit": "最多支持创建 {0} 个 API Key" } -} \ No newline at end of file +} From a528a3ae537b57a962d755ea6805c114b063d0cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=98=89=E8=B1=AA?= <42510293+ziyujiahao@users.noreply.github.com> Date: Wed, 24 Dec 2025 14:36:52 +0800 Subject: [PATCH 5/8] refactor: update pyproject (#671) --- backend/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 4795d518..83681ccd 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -83,11 +83,11 @@ url = "https://test.pypi.org/simple" explicit = true [tool.uv.sources] +sqlbot-xpack = { index = "testpypi" } torch = [ { index = "pytorch-cpu", extra = "cpu" }, { index = "pytorch-cu128", extra = "cu128" }, ] -sqlbot-xpack = { path = "../../sqlbot-xpack" } [tool.uv] conflicts = [ From 5732690a3b0473d65c5b500f9ccd797435be0421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=98=89=E8=B1=AA?= <42510293+ziyujiahao@users.noreply.github.com> Date: Wed, 24 Dec 2025 15:05:17 +0800 Subject: [PATCH 6/8] style: update style (#672) --- frontend/src/api/chat.ts | 2 +- frontend/src/api/datasource.ts | 2 +- frontend/src/views/ds/index.vue | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/api/chat.ts b/frontend/src/api/chat.ts index 918e58e3..d64fee6d 100644 --- a/frontend/src/api/chat.ts +++ b/frontend/src/api/chat.ts @@ -324,7 +324,7 @@ export const chatApi = { renameChat: (chat_id: number | undefined, brief: string): Promise => { return request.post('/chat/rename', { id: chat_id, brief: brief }) }, - deleteChat: (id: number | undefined, brief: string): Promise => { + deleteChat: (id: number | undefined, brief: any): Promise => { return request.delete(`/chat/${id}/${brief}`) }, analysis: (record_id: number | undefined, controller?: AbortController) => { diff --git a/frontend/src/api/datasource.ts b/frontend/src/api/datasource.ts index c14b13cd..7f2fb782 100644 --- a/frontend/src/api/datasource.ts +++ b/frontend/src/api/datasource.ts @@ -8,7 +8,7 @@ export const datasourceApi = { add: (data: any) => request.post('/datasource/add', data), list: () => request.get('/datasource/list'), update: (data: any) => request.post('/datasource/update', data), - delete: (id: number, name: str) => request.post(`/datasource/delete/${id}/${name}`), + delete: (id: number, name: string) => request.post(`/datasource/delete/${id}/${name}`), getTables: (id: number) => request.post(`/datasource/getTables/${id}`), getTablesByConf: (data: any) => request.post('/datasource/getTablesByConf', data), getFields: (id: number, table_name: string) => diff --git a/frontend/src/views/ds/index.vue b/frontend/src/views/ds/index.vue index 41563894..7acacfee 100644 --- a/frontend/src/views/ds/index.vue +++ b/frontend/src/views/ds/index.vue @@ -109,7 +109,7 @@ const deleteDs = (item: any) => { type: 'warning', }) .then(() => { - datasourceApi.delete(item.id).then(() => { + datasourceApi.delete(item.id, item.name).then(() => { refresh() }) }) From a6dcec08ab521ca3135f4967dc8fea901dc38ec6 Mon Sep 17 00:00:00 2001 From: fit2cloud-chenyw Date: Wed, 24 Dec 2025 15:26:35 +0800 Subject: [PATCH 7/8] perf: Optimize API permission validation --- backend/apps/system/api/aimodel.py | 2 ++ backend/apps/system/middleware/auth.py | 13 +++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/backend/apps/system/api/aimodel.py b/backend/apps/system/api/aimodel.py index 1b49c00d..defa3b20 100644 --- a/backend/apps/system/api/aimodel.py +++ b/backend/apps/system/api/aimodel.py @@ -18,6 +18,7 @@ router = APIRouter(tags=["system_model"], prefix="/system/aimodel") @router.post("/status", include_in_schema=False) +@require_permissions(permission=SqlbotPermission(role=['admin'])) async def check_llm(info: AiModelCreator, trans: Trans): async def generate(): try: @@ -92,6 +93,7 @@ async def query( return items @router.get("/{id}", response_model=AiModelEditor, summary=f"{PLACEHOLDER_PREFIX}system_model_query", description=f"{PLACEHOLDER_PREFIX}system_model_query") +@require_permissions(permission=SqlbotPermission(role=['admin'])) async def get_model_by_id( session: SessionDep, id: int = Path(description="ID") diff --git a/backend/apps/system/middleware/auth.py b/backend/apps/system/middleware/auth.py index bdf88711..e7674152 100644 --- a/backend/apps/system/middleware/auth.py +++ b/backend/apps/system/middleware/auth.py @@ -205,6 +205,12 @@ async def validateEmbedded(self, param: str, trans: I18n) -> tuple[any]: return False, f"Miss account payload error!" account = payload['account'] with Session(engine) as session: + assistant_info = await get_assistant_info(session=session, assistant_id=embeddedId) + assistant_info = AssistantModel.model_validate(assistant_info) + payload = jwt.decode( + param, assistant_info.app_secret, algorithms=[security.ALGORITHM] + ) + assistant_info = AssistantHeader.model_validate(assistant_info.model_dump(exclude_unset=True)) """ session_user = await get_user_info(session = session, user_id = token_data.id) session_user = UserInfoDTO.model_validate(session_user) """ session_user = get_user_by_account(session = session, account=account) @@ -220,12 +226,7 @@ async def validateEmbedded(self, param: str, trans: I18n) -> tuple[any]: if not session_user.oid or session_user.oid == 0: message = trans('i18n_login.no_associated_ws', msg = trans('i18n_concat_admin')) raise Exception(message) - assistant_info = await get_assistant_info(session=session, assistant_id=embeddedId) - assistant_info = AssistantModel.model_validate(assistant_info) - payload = jwt.decode( - param, assistant_info.app_secret, algorithms=[security.ALGORITHM] - ) - assistant_info = AssistantHeader.model_validate(assistant_info.model_dump(exclude_unset=True)) + return True, session_user, assistant_info except Exception as e: SQLBotLogUtil.exception(f"Embedded validation error: {str(e)}") From c1393f3a3271e4428baf8ba0f75506c133187cbb Mon Sep 17 00:00:00 2001 From: junjun Date: Wed, 24 Dec 2025 18:07:33 +0800 Subject: [PATCH 8/8] feat: Support for batch uploading data table notes #557 --- backend/apps/datasource/api/datasource.py | 32 ++++++++++++++++------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/backend/apps/datasource/api/datasource.py b/backend/apps/datasource/api/datasource.py index a4d7caec..bbfd996b 100644 --- a/backend/apps/datasource/api/datasource.py +++ b/backend/apps/datasource/api/datasource.py @@ -387,6 +387,7 @@ def insert_pg(df, tableName, engine): t_sheet = "数据表列表" +t_s_col = "Sheet名称" t_n_col = "表名" t_c_col = "表备注" f_n_col = "字段名" @@ -406,7 +407,8 @@ def inner(): if id == 0: # download template file_name = '批量上传备注' df_list = [ - {'sheet': t_sheet, 'c1_h': t_n_col, 'c2_h': t_c_col, 'c1': ["user", "score"], + {'sheet': t_sheet, 'c0_h': t_s_col, 'c1_h': t_n_col, 'c2_h': t_c_col, 'c0': ["数据表1", "数据表2"], + 'c1': ["user", "score"], 'c2': ["用来存放用户信息的数据表", "用来存放用户课程信息的数据表"]}, {'sheet': '数据表1', 'c1_h': f_n_col, 'c2_h': f_c_col, 'c1': ["id", "name"], 'c2': ["用户id", "用户姓名"]}, @@ -421,14 +423,15 @@ def inner(): raise HTTPException(400, "No tables") df_list = [] - df1 = {'sheet': t_sheet, 'c1_h': t_n_col, 'c2_h': t_c_col, 'c1': [], 'c2': []} + df1 = {'sheet': t_sheet, 'c0_h': t_s_col,'c1_h': t_n_col, 'c2_h': t_c_col,'c0': [], 'c1': [], 'c2': []} df_list.append(df1) - for table in tables: + for index, table in enumerate(tables): + df1['c0'].append(f"Sheet{index}") df1['c1'].append(table.table_name) df1['c2'].append(table.custom_comment) fields = session.query(CoreField).filter(CoreField.table_id == table.id).all() - df_fields = {'sheet': table.table_name, 'c1_h': f_n_col, 'c2_h': f_c_col, 'c1': [], 'c2': []} + df_fields = {'sheet': f"Sheet{index}", 'c1_h': f_n_col, 'c2_h': f_c_col, 'c1': [], 'c2': []} for field in fields: df_fields['c1'].append(field.field_name) df_fields['c2'].append(field.custom_comment) @@ -437,10 +440,14 @@ def inner(): # build dataframe and export output = io.BytesIO() - with pd.ExcelWriter(output, engine='xlsxwriter') as writer: - for df in df_list: - pd.DataFrame({df['c1_h']: df['c1'], df['c2_h']: df['c2']}).to_excel(writer, sheet_name=df['sheet'], - index=False) + with (pd.ExcelWriter(output, engine='xlsxwriter') as writer): + for index, df in enumerate(df_list): + if index == 0: + pd.DataFrame({df['c0_h']: df['c0'], df['c1_h']: df['c1'], df['c2_h']: df['c2']} + ).to_excel(writer, sheet_name=df['sheet'], index=False) + else: + pd.DataFrame({df['c1_h']: df['c1'], df['c2_h']: df['c2']}).to_excel(writer, sheet_name=df['sheet'], + index=False) output.seek(0) @@ -486,10 +493,14 @@ async def upload_ds_schema(session: SessionDep, id: int = Path(..., description= # print(field_sheets) + # sheet table mapping + sheet_table_map = {} + # get data and update # update table comment if table_sheet and len(table_sheet) > 0: for table in table_sheet: + sheet_table_map[table[t_s_col]] = table[t_n_col] session.query(CoreTable).filter( and_(CoreTable.ds_id == id, CoreTable.table_name == table[t_n_col])).update( {'custom_comment': table[t_c_col]}) @@ -499,8 +510,9 @@ async def upload_ds_schema(session: SessionDep, id: int = Path(..., description= for fields in field_sheets: if len(fields['data']) > 0: # get table id + table_name = sheet_table_map[fields['sheet_name']] table = session.query(CoreTable).filter( - and_(CoreTable.ds_id == id, CoreTable.table_name == fields['sheet_name'])).first() + and_(CoreTable.ds_id == id, CoreTable.table_name == table_name)).first() if table: for field in fields['data']: session.query(CoreField).filter( @@ -512,4 +524,4 @@ async def upload_ds_schema(session: SessionDep, id: int = Path(..., description= return True except Exception as e: - raise HTTPException(status_code=500, detail=f"解析 Excel 失败: {str(e)}") + raise HTTPException(status_code=500, detail=f"Parse Excel Failed: {str(e)}")