diff --git a/CHANGELOG.md b/CHANGELOG.md index c1c6bb57d5..126b19e028 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.8.12] - 2026-03-26 + +### Added + +- 🌐 **Translation updates.** Translations for Simplified Chinese, Catalan, Portuguese (Brazil), Finnish, and Lithuanian were enhanced and expanded. + +### Fixed + +- 🔒 **Terminal server connection security.** Terminal server verification and policy saving now proxy through the backend, preventing API key exposure and CORS errors when connecting to in-cluster services. [Commit](https://github.com/open-webui/open-webui/commit/a6413257079a52fa4487eda36543f3955d0fbd53), [Commit](https://github.com/open-webui/open-webui/commit/4567cdc0d9cb7b42b6eba7b676c0ced3f4850d31) +- 🛠️ **Terminal tools exception handling.** Exceptions in middleware.py due to invalid return values from get_terminal_tools() have been resolved. [Commit](https://github.com/open-webui/open-webui/commit/52a06bd48aff34fb2211aac2879f0cd028129267) +- 📦 **Missing beautifulsoup4 dependency.** Users can now start Open WebUI using uvx without encountering the "bs4 module missing" error. [Commit](https://github.com/open-webui/open-webui/commit/1994d65306bbcc7406584e1bfef82f5d353fc91c) +- 🔌 **API files list error.** The /api/v1/files/ endpoint no longer returns a 500 error, fixing a regression that prevented file listing via the API. [Commit](https://github.com/open-webui/open-webui/commit/11f52921dc21c2dc61c03f12bcdf6f19140a350c) +- 📜 **License data loading.** License data now loads correctly, displaying the expected color and logo in the interface. [Commit](https://github.com/open-webui/open-webui/commit/16335f866ea4cedf00c4971963622fcc1fe02d82) +- 👑 **Admin model visibility.** Administrators can now see models even when no access control is configured yet, allowing them to manage all available models. [Commit](https://github.com/open-webui/open-webui/commit/f3f8f9874f55282603c2650b91801640cb3f69cb) +- 📊 **Tool call embed visibility.** Rich UI embeds from tool calls (like visualizations) are now rendered outside collapsed groups and remain visible without requiring manual expansion. [Commit](https://github.com/open-webui/open-webui/commit/4c872a8d128757d4a6f311fb86bc382af2ba5d0d), [Commit](https://github.com/open-webui/open-webui/commit/308fa924a5b2b7e08cd1e8f15b9c8c96e1de8f02) + ## [0.8.11] - 2026-03-25 ### Added diff --git a/CHANGELOG_EXTRA.md b/CHANGELOG_EXTRA.md index adeb272bfc..42ba5d80fd 100644 --- a/CHANGELOG_EXTRA.md +++ b/CHANGELOG_EXTRA.md @@ -5,11 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.8.12.1] - 2026.03.27 + +### Changed + +- 合并官方 [0.8.12](https://github.com/open-webui/open-webui/releases/tag/v0.8.12) 改动 + ## [0.8.11.1] - 2026.03.26 ### Changed -- 合并官方 0.8.11 改动 +- 合并官方 [0.8.11](https://github.com/open-webui/open-webui/releases/tag/v0.8.11) 改动 ## [0.8.10.2] - 2026.03.18 @@ -21,43 +27,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- 合并官方 0.8.10 改动 +- 合并官方 [0.8.10](https://github.com/open-webui/open-webui/releases/tag/v0.8.10) 改动 ## [0.8.9.1] - 2026.03.08 ### Changed -- 合并官方 0.8.9 改动 +- 合并官方 [0.8.9](https://github.com/open-webui/open-webui/releases/tag/v0.8.9) 改动 ## [0.8.8.1] - 2026.03.03 ### Changed -- 合并官方 0.8.8 改动 +- 合并官方 [0.8.8](https://github.com/open-webui/open-webui/releases/tag/v0.8.8) 改动 ## [0.8.7.1] - 2026.03.02 ### Changed -- 合并官方 0.8.7 改动 +- 合并官方 [0.8.7](https://github.com/open-webui/open-webui/releases/tag/v0.8.7) 改动 ## [0.8.5.1] - 2026.02.24 ### Changed -- 合并官方 0.8.5 改动 +- 合并官方 [0.8.5](https://github.com/open-webui/open-webui/releases/tag/v0.8.5) 改动 ## [0.8.3.1] - 2026.02.19 ### Changed -- 合并官方 0.8.3 改动 +- 合并官方 [0.8.3](https://github.com/open-webui/open-webui/releases/tag/v0.8.3) 改动 ## [0.8.1.1] - 2026.02.14 ### Changed -- 合并官方 0.8.1 改动 +- 合并官方 [0.8.1](https://github.com/open-webui/open-webui/releases/tag/v0.8.1) 改动 - 移除 Responses 接口 ## [0.8.0.2] - 2026.02.13 @@ -70,7 +76,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- 合并官方 0.8.0 改动 +- 合并官方 [0.8.0](https://github.com/open-webui/open-webui/releases/tag/v0.8.0) 改动 ## [0.7.2.5] - 2026.01.28 @@ -82,13 +88,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- 合并官方 0.7.2 改动 +- 合并官方 [0.7.2](https://github.com/open-webui/open-webui/releases/tag/v0.7.2) 改动 ## [0.7.1.1] - 2026.01.10 ### Changed -- 合并官方 0.7.1 改动 +- 合并官方 [0.7.1](https://github.com/open-webui/open-webui/releases/tag/v0.7.1) 改动 ## [0.6.43.2] - 2025.12.22 @@ -100,7 +106,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- 合并官方 0.6.43 改动 +- 合并官方 [0.6.43](https://github.com/open-webui/open-webui/releases/tag/v0.6.43) 改动 ## [0.6.42.1] - 2025.12.21 @@ -114,7 +120,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 对部分配置增加额外的校验和提示 - 优化积分统计面板、积分日志、兑换码管理的加载性能 -- 合并官方 0.6.42 改动 +- 合并官方 [0.6.42](https://github.com/open-webui/open-webui/releases/tag/v0.6.42) 改动 ### Fixed @@ -125,7 +131,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- 合并官方 0.6.41 改动 +- 合并官方 [0.6.41](https://github.com/open-webui/open-webui/releases/tag/v0.6.41) 改动 ## [0.6.40.2] - 2025.11.27 @@ -141,13 +147,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- 合并官方 0.6.40 改动 +- 合并官方 [0.6.40](https://github.com/open-webui/open-webui/releases/tag/v0.6.40) 改动 ## [0.6.38.1] - 2025.11.24 ### Changed -- 合并官方 0.6.38 改动 +- 合并官方 [0.6.38](https://github.com/open-webui/open-webui/releases/tag/v0.6.38) 改动 ## [0.6.37.1] - 2025.11.24 @@ -157,7 +163,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- 合并官方 0.6.37 改动 +- 合并官方 [0.6.37](https://github.com/open-webui/open-webui/releases/tag/v0.6.37) 改动 ### Fixed @@ -167,19 +173,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- 合并官方 0.6.36 改动 +- 合并官方 [0.6.36](https://github.com/open-webui/open-webui/releases/tag/v0.6.36) 改动 ## [0.6.34.1] - 2025.10.17 ### Changed -- 合并官方 0.6.34 改动 +- 合并官方 [0.6.34](https://github.com/open-webui/open-webui/releases/tag/v0.6.34) 改动 ## [0.6.33.1] - 2025.10.08 ### Changed -- 合并官方 0.6.33 改动 +- 合并官方 [0.6.33](https://github.com/open-webui/open-webui/releases/tag/v0.6.33) 改动 ## [0.6.32.1] - 2025.09.29 @@ -189,7 +195,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- 合并官方 0.6.32 改动 +- 合并官方 [0.6.32](https://github.com/open-webui/open-webui/releases/tag/v0.6.32) 改动 ## [0.6.31.1] - 2025.09.26 @@ -197,7 +203,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 移除 Markdown 编辑器 - 移除自定义代码块样式 -- 合并官方 0.6.31 改动 +- 合并官方 [0.6.31](https://github.com/open-webui/open-webui/releases/tag/v0.6.31) 改动 ### Fixed @@ -207,37 +213,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- 合并官方 0.6.30 改动 +- 合并官方 [0.6.30](https://github.com/open-webui/open-webui/releases/tag/v0.6.30) 改动 ## [0.6.28.1] - 2025.09.11 ### Changed -- 合并官方 0.6.28 改动 +- 合并官方 [0.6.28](https://github.com/open-webui/open-webui/releases/tag/v0.6.28) 改动 ## [0.6.27.1] - 2025.09.10 ### Changed -- 合并官方 0.6.27 改动 +- 合并官方 [0.6.27](https://github.com/open-webui/open-webui/releases/tag/v0.6.27) 改动 ## [0.6.26.1] - 2025.08.28 ### Changed -- 合并官方 0.6.26 改动 +- 合并官方 [0.6.26](https://github.com/open-webui/open-webui/releases/tag/v0.6.26) 改动 ## [0.6.25.1] - 2025.08.22 ### Changed -- 合并官方 0.6.25 改动 +- 合并官方 [0.6.25](https://github.com/open-webui/open-webui/releases/tag/v0.6.25) 改动 ## [0.6.24.1] - 2025.08.22 ### Changed -- 合并官方 0.6.24 改动 +- 合并官方 [0.6.24](https://github.com/open-webui/open-webui/releases/tag/v0.6.24) 改动 ## [0.6.23.1] - 2025.08.22 @@ -247,7 +253,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 支持禁用自定义编辑器和代码块 - 优化 DB 初始化流程 - 移除不必要的对话日志加载逻辑 -- 合并官方 0.6.23 改动 +- 合并官方 [0.6.23](https://github.com/open-webui/open-webui/releases/tag/v0.6.23) 改动 ### Fixed @@ -263,13 +269,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- 合并官方 0.6.22 改动 +- 合并官方 [0.6.22](https://github.com/open-webui/open-webui/releases/tag/v0.6.22) 改动 ## [0.6.21.1] - 2025.08.10 ### Changed -- 合并官方 0.6.21 改动 +- 合并官方 [0.6.21](https://github.com/open-webui/open-webui/releases/tag/v0.6.21) 改动 ## [0.6.20.1] - 2025.08.10 @@ -282,7 +288,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - 移除积分统计面板的数字展示 -- 合并官方 0.6.20 改动 +- 合并官方 [0.6.20](https://github.com/open-webui/open-webui/releases/tag/v0.6.20) 改动 ### Fixed @@ -292,7 +298,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- 合并官方 0.6.18 改动 +- 合并官方 [0.6.18](https://github.com/open-webui/open-webui/releases/tag/v0.6.18) 改动 ## [0.6.16.1] - 2025.07.15 @@ -303,13 +309,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - 移除英文/中文以外的语言支持 -- 合并官方 0.6.16 改动 +- 合并官方 [0.6.16](https://github.com/open-webui/open-webui/releases/tag/v0.6.16) 改动 ## [0.6.15.1] - 2025.06.18 ### Changed -- 合并官方 0.6.15 改动 +- 合并官方 [0.6.15](https://github.com/open-webui/open-webui/releases/tag/v0.6.15) 改动 ## [0.6.14.1] - 2025.06.11 @@ -323,7 +329,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - 修改积分统计的最大范围为最近 180 天 -- 合并官方 0.6.14 改动 +- 合并官方 [0.6.14](https://github.com/open-webui/open-webui/releases/tag/v0.6.14) 改动 ## [0.6.13.1] - 2025.05.30 @@ -333,7 +339,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- 合并官方 0.6.13 改动 +- 合并官方 [0.6.13](https://github.com/open-webui/open-webui/releases/tag/v0.6.13) 改动 ## [0.6.12.1] - 2025.05.29 @@ -343,14 +349,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- 合并官方 0.6.12 改动 +- 合并官方 [0.6.12](https://github.com/open-webui/open-webui/releases/tag/v0.6.12) 改动 ## [0.6.11.1] - 2025.05.27 ### Changed - 优化用量信息的交互 -- 合并官方 0.6.11 改动 +- 合并官方 [0.6.11](https://github.com/open-webui/open-webui/releases/tag/v0.6.11) 改动 ### Fixed @@ -368,7 +374,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 积分日志搜索修改为搜索用户名 - 移除自定义滚动条样式 - 修改积分日志单页数量为 30 -- 合并官方 0.6.10 改动 +- 合并官方 [0.6.10](https://github.com/open-webui/open-webui/releases/tag/v0.6.10) 改动 ## [0.6.9.1] - 2025.05.11 @@ -382,7 +388,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- 合并官方 0.6.9 改动 +- 合并官方 [0.6.9](https://github.com/open-webui/open-webui/releases/tag/v0.6.9) 改动 ### Fixed @@ -398,7 +404,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- 合并官方 0.6.7 改动 +- 合并官方 [0.6.7](https://github.com/open-webui/open-webui/releases/tag/v0.6.7) 改动 ## [0.6.6.1] - 2025-05-05 diff --git a/backend/open_webui/models/files.py b/backend/open_webui/models/files.py index 9a5b8fa400..7a9f77a3b0 100644 --- a/backend/open_webui/models/files.py +++ b/backend/open_webui/models/files.py @@ -87,7 +87,7 @@ class FileModelResponse(BaseModel): filename: str data: Optional[dict] = None - meta: FileMeta + meta: Optional[FileMeta] = None created_at: int # timestamp in epoch updated_at: Optional[int] = None # timestamp in epoch, optional for legacy files @@ -246,7 +246,7 @@ def get_file_list( total = query.count() items = [ - FileModel.model_validate(file) + FileModelResponse.model_validate(file, from_attributes=True) for file in query.order_by(File.updated_at.desc(), File.id.desc()).offset(skip).limit(limit).all() ] diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index b6c46c1dad..7aa68bc602 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -29,6 +29,7 @@ from open_webui.models.chats import Chats from open_webui.models.notes import Notes from open_webui.models.access_grants import AccessGrants +from open_webui.utils.access_control.files import has_access_to_file from open_webui.retrieval.vector.main import GetResult from open_webui.utils.headers import include_user_info_headers @@ -996,7 +997,11 @@ async def get_sources_from_items( } elif item.get('id'): file_object = Files.get_file_by_id(item.get('id')) - if file_object: + if file_object and ( + user.role == 'admin' + or file_object.user_id == user.id + or has_access_to_file(item.get('id'), 'read', user) + ): query_result = { 'documents': [[file_object.data.get('content', '')]], 'metadatas': [ diff --git a/backend/open_webui/routers/configs.py b/backend/open_webui/routers/configs.py index 6d09602c5c..82adf9da10 100644 --- a/backend/open_webui/routers/configs.py +++ b/backend/open_webui/routers/configs.py @@ -269,6 +269,92 @@ async def set_terminal_servers_config( } +@router.post('/terminal_servers/verify') +async def verify_terminal_server_connection( + request: Request, form_data: TerminalServerConnection, user=Depends(get_admin_user) +): + """ + Verify the connection to a terminal server by detecting its type. + + Tries GET {url}/api/v1/policies (orchestrator) then GET {url}/api/config + (plain terminal). Returns ``{status: true, type: "orchestrator"|"terminal"}``. + """ + base_url = (form_data.url or '').rstrip('/') + if not base_url: + raise HTTPException(status_code=400, detail='Terminal server URL is required') + + headers = {} + if form_data.auth_type == 'bearer' and form_data.key: + headers['Authorization'] = f'Bearer {form_data.key}' + + try: + async with aiohttp.ClientSession( + trust_env=True, + timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT), + ) as session: + # Orchestrators expose a policies API; plain terminals don't. + try: + async with session.get(f'{base_url}/api/v1/policies', headers=headers) as resp: + if resp.ok: + return {'status': True, 'type': 'orchestrator'} + except Exception: + pass + + # Fall back to open-terminal config endpoint. + try: + async with session.get(f'{base_url}/api/config', headers=headers) as resp: + if resp.ok: + return {'status': True, 'type': 'terminal'} + except Exception: + pass + + except Exception as e: + log.debug(f'Failed to connect to the terminal server: {e}') + + raise HTTPException(status_code=400, detail='Failed to connect to the terminal server') + + +class TerminalServerPolicyForm(BaseModel): + url: str + key: Optional[str] = '' + auth_type: Optional[str] = 'bearer' + policy_id: str + policy_data: dict + + +@router.post('/terminal_servers/policy') +async def put_terminal_server_policy( + request: Request, form_data: TerminalServerPolicyForm, user=Depends(get_admin_user) +): + """ + Proxy a policy PUT to an orchestrator terminal server. + """ + base_url = (form_data.url or '').rstrip('/') + if not base_url: + raise HTTPException(status_code=400, detail='Terminal server URL is required') + + headers = {'Content-Type': 'application/json'} + if form_data.auth_type == 'bearer' and form_data.key: + headers['Authorization'] = f'Bearer {form_data.key}' + + try: + async with aiohttp.ClientSession( + trust_env=True, + timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT), + ) as session: + policy_url = f'{base_url}/api/v1/policies/{form_data.policy_id}' + async with session.put(policy_url, headers=headers, json=form_data.policy_data) as resp: + if resp.ok: + return await resp.json() + detail = await resp.text() + raise HTTPException(status_code=resp.status, detail=detail) + except HTTPException: + raise + except Exception as e: + log.debug(f'Failed to save policy to terminal server: {e}') + raise HTTPException(status_code=400, detail='Failed to save policy to terminal server') + + @router.post('/tool_servers/verify') async def verify_tool_servers_config(request: Request, form_data: ToolServerConnection, user=Depends(get_admin_user)): """ diff --git a/backend/open_webui/routers/utils.py b/backend/open_webui/routers/utils.py index 7ea4150021..c79d8fe5d8 100644 --- a/backend/open_webui/routers/utils.py +++ b/backend/open_webui/routers/utils.py @@ -42,6 +42,12 @@ async def format_code(form_data: CodeForm, user=Depends(get_admin_user)): @router.post('/code/execute') async def execute_code(request: Request, form_data: CodeForm, user=Depends(get_verified_user)): + if not request.app.state.config.ENABLE_CODE_EXECUTION: + raise HTTPException( + status_code=403, + detail='Code execution is disabled', + ) + if request.app.state.config.CODE_EXECUTION_ENGINE == 'jupyter': output = await execute_code_jupyter( request.app.state.config.CODE_EXECUTION_JUPYTER_URL, diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index d98d6901cc..b64febd673 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -2607,12 +2607,17 @@ async def tool_function(**kwargs): # so system terminals work even when no other tools are selected) if terminal_id: try: - terminal_tools, system_prompt = await get_terminal_tools( + terminal_result = await get_terminal_tools( request, terminal_id, user, extra_params, ) + if isinstance(terminal_result, tuple): + terminal_tools, system_prompt = terminal_result + else: + terminal_tools = terminal_result + system_prompt = None if terminal_tools: tools_dict = {**tools_dict, **terminal_tools} if system_prompt: diff --git a/backend/open_webui/utils/models.py b/backend/open_webui/utils/models.py index 60ef87e5f5..b57c74744c 100644 --- a/backend/open_webui/utils/models.py +++ b/backend/open_webui/utils/models.py @@ -452,6 +452,10 @@ def get_filtered_models(models, user, db=None): or model['id'] in accessible_model_ids ): filtered_models.append(model) + elif user.role == 'admin': + # No DB entry means no access control configured yet; + # only admins can see unconfigured models. + filtered_models.append(model) return filtered_models else: diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index 210ff24085..226830a1fa 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -956,7 +956,7 @@ async def get_terminal_tools( terminal_id: str, user: UserModel, extra_params: dict, -) -> tuple[dict[str, dict], Optional[str]]: +) -> dict[str, dict] | tuple[dict[str, dict], Optional[str]]: """Resolve tools for a terminal server identified by terminal_id. - Finds the connection in TERMINAL_SERVER_CONNECTIONS diff --git a/package-lock.json b/package-lock.json index e876da5288..5e66c6b3dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "open-webui", - "version": "0.8.11.1", + "version": "0.8.12.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "open-webui", - "version": "0.8.11.1", + "version": "0.8.12.1", "dependencies": { "@azure/msal-browser": "^4.5.0", "@codemirror/lang-javascript": "^6.2.2", diff --git a/package.json b/package.json index e0d8bf97f6..29b7852279 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "open-webui", - "version": "0.8.11.1", + "version": "0.8.12.1", "private": true, "scripts": { "dev": "npm run pyodide:fetch && vite dev --host", diff --git a/pyproject.toml b/pyproject.toml index 7883a459e5..7a546e935f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,6 +80,7 @@ dependencies = [ "msoffcrypto-tool==6.0.0", "nltk==3.9.3", "Markdown==3.10.2", + "beautifulsoup4==4.14.3", "pypandoc==1.16.2", "pandas==3.0.1", "openpyxl==3.1.5", diff --git a/src/lib/apis/configs/index.ts b/src/lib/apis/configs/index.ts index be6cf0f626..60a2cfc613 100644 --- a/src/lib/apis/configs/index.ts +++ b/src/lib/apis/configs/index.ts @@ -268,9 +268,10 @@ export const detectTerminalServerType = async ( /** * Create or update a policy on the orchestrator. - * PUT {url}/api/v1/policies/{policyId} + * Proxied through the Open WebUI backend to keep API keys server-side. */ export const putOrchestratorPolicy = async ( + token: string, url: string, key: string, policyId: string, @@ -278,18 +279,52 @@ export const putOrchestratorPolicy = async ( ): Promise => { let error = null; - const baseUrl = url.replace(/\/$/, ''); - const headers: Record = { - 'Content-Type': 'application/json' - }; - if (key) { - headers['Authorization'] = `Bearer ${key}`; + const res = await fetch(`${WEBUI_API_BASE_URL}/configs/terminal_servers/policy`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + url: url.replace(/\/$/, ''), + key, + policy_id: policyId, + policy_data: policyData + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.error(err); + error = err.detail; + return null; + }); + + if (error) { + throw error; } - const res = await fetch(`${baseUrl}/api/v1/policies/${encodeURIComponent(policyId)}`, { - method: 'PUT', - headers, - body: JSON.stringify(policyData) + return res; +}; + +/** + * Verify a terminal server connection via the backend proxy. + * Used for system/admin connections to avoid CORS issues and API key exposure. + */ +export const verifyTerminalServerConnection = async (token: string, connection: object) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/configs/terminal_servers/verify`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + ...connection + }) }) .then(async (res) => { if (!res.ok) throw await res.json(); diff --git a/src/lib/components/AddTerminalServerModal.svelte b/src/lib/components/AddTerminalServerModal.svelte index b70a8b8233..e7c0236eb6 100644 --- a/src/lib/components/AddTerminalServerModal.svelte +++ b/src/lib/components/AddTerminalServerModal.svelte @@ -12,12 +12,16 @@ import LockClosed from '$lib/components/icons/LockClosed.svelte'; import Tooltip from '$lib/components/common/Tooltip.svelte'; import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; - import { detectTerminalServerType, putOrchestratorPolicy } from '$lib/apis/configs'; + import { + detectTerminalServerType, + verifyTerminalServerConnection, + putOrchestratorPolicy + } from '$lib/apis/configs'; import { getTerminalConfig } from '$lib/apis/terminal'; export let show = false; export let edit = false; - export let admin = false; + export let direct = false; export let connection = null; export let onSubmit: Function = () => {}; @@ -110,9 +114,14 @@ verifying = true; try { - if (admin) { - // Admin: detect orchestrator vs terminal - const type = await detectTerminalServerType(_url, key); + if (!direct) { + // System connection: proxy through backend to avoid CORS / key exposure + const result = await verifyTerminalServerConnection(localStorage.token, { + url: _url, + key, + auth_type + }); + const type = result?.type ?? null; if (type) { serverType = type; @@ -137,7 +146,7 @@ toast.error($i18n.t('Server connection failed')); } } else { - // Non-admin: simple terminal verification + // Direct connection: verify from browser const res = await getTerminalConfig(_url, key); if (res) { toast.success($i18n.t('Server connection verified')); @@ -192,9 +201,9 @@ url = url.replace(/\/$/, ''); // Save policy to orchestrator if applicable - if (serverType === 'orchestrator' && admin && policyId) { + if (serverType === 'orchestrator' && !direct && policyId) { try { - await putOrchestratorPolicy(url, key, policyId, buildPolicyData()); + await putOrchestratorPolicy(localStorage.token, url, key, policyId, buildPolicyData()); } catch (err) { toast.error($i18n.t('Failed to save policy: {{error}}', { error: err })); return; @@ -202,7 +211,7 @@ } const result = { - ...(admin && id.trim() ? { id: id.trim() } : {}), + ...(!direct && id.trim() ? { id: id.trim() } : {}), url, key, name, @@ -210,7 +219,7 @@ auth_type, enabled: enabled, config: { - ...(admin ? { access_grants: accessGrants } : {}) + ...(!direct ? { access_grants: accessGrants } : {}) }, // Policy fields ...(serverType ? { server_type: serverType } : {}), @@ -270,7 +279,7 @@ /> - {#if admin} + {#if !direct}