From e53e91372d95c6d565fefb92bb89934222280e8d Mon Sep 17 00:00:00 2001 From: caoqianming Date: Tue, 11 Aug 2026 11:15:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(external-systems):=20=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E5=BC=B9=E6=A1=86=E7=AE=A1=E7=90=86=E8=BF=9E=E6=8E=A5=E5=B9=B6?= =?UTF-8?q?=E6=A0=87=E8=AF=86=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 + PROGRESS.md | 4 +- core/__init__.py | 2 +- tests/test_static_vendor.py | 16 ++++ web/static/dev.html | 78 ++++++++++++------- web/static/js/external_systems.js | 124 ++++++++++++++++++------------ 6 files changed, 147 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc50f80..05c1236 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ > 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。 > 工程口径的完整记录见 `PROGRESS.md` / git log。 +## 0.63.8 — 2026-08-11 + +- 外部系统的新建与凭据更新改用独立弹框;点击已有连接即可编辑,新建时不再列出已连接系统,连接卡片同时用图标和文字区分 OpenAPI 与 MCP。 + ## 0.63.7 — 2026-08-11 - 已连接的外部系统现在可直接在凭据表单中重新连接并验证,不再被“该外部系统已连接”的重复创建提示拦住。 diff --git a/PROGRESS.md b/PROGRESS.md index d8a5b8f..2820a32 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -2,7 +2,7 @@ > 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`。 -最后更新:2026-08-11(外部系统已有连接直接重连修复,bump 0.63.7) +最后更新:2026-08-11(外部系统连接弹框与类型标识,bump 0.63.8) --- @@ -23,6 +23,8 @@ ### 2026-08-11 +- **08-11 / 0.63.8 / 外部系统连接弹框 + connector 类型标识**:用户外部系统主页移除状态耦合的内联凭据表单,新建连接与更新凭据统一改为独立弹框;点击已有卡片直接进入编辑,新建弹框只列出尚未连接的 definition,避免重复创建。连接卡片读取既有 `connector` 字段,以不同 SVG、颜色和文字区分 OpenAPI 与 MCP。Web 无 DB 路由与静态资源共 36 项 unittest、JavaScript 语法及 diff 检查通过;本地无安全的 Web 实例可做真实点击,未启动会读取生产 `.env` 的应用,未连接生产 DB。无 schema、migration、HTTP API、依赖或运行方式变化。 + - **08-11 / 0.63.7 / 外部系统已有连接直接重连**:用户凭据表单按所选 definition 识别已有连接,自动切换为“重新连接并验证”并调用现有凭据更新接口,验证成功后保存新凭据和 active 状态,不再先按新连接提交后收到重复连接提示;卡片上的显式“更新凭据”入口保持不变。Web 无 DB 路由与静态资源共 35 项 unittest、JavaScript 语法及 diff 检查通过;无 schema、migration、HTTP API、依赖或运行方式变化,未连接生产 DB。 - **08-11 / 0.63.6 / 外部系统弹框动态字段 + 滚动隔离修复**:修复表单 label 样式覆盖 HTML `hidden` 语义,OpenAPI/MCP connector、认证方式和接口执行模式切换后只显示适用配置项;弹框改为独立可收缩滚动区,并在打开期间锁定根页面滚动,滚动到边界不再带动后台页面。Node 前端 19 项、Web 公共逻辑 unittest 3 项、JavaScript 语法及 diff 检查通过;无 schema、migration、HTTP API、依赖或运行方式变化,未连接生产 DB。 diff --git a/core/__init__.py b/core/__init__.py index a8e43f0..7628f10 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,3 +1,3 @@ # zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。 # 改版本只动这一行。 -__version__ = "0.63.7" +__version__ = "0.63.8" diff --git a/tests/test_static_vendor.py b/tests/test_static_vendor.py index b80b525..c5be74b 100644 --- a/tests/test_static_vendor.py +++ b/tests/test_static_vendor.py @@ -180,6 +180,22 @@ class StaticVendorTests(unittest.TestCase): embed_js, ) + def test_external_system_connections_use_separate_create_and_edit_modal(self) -> None: + html = DEV_HTML.read_text(encoding="utf-8") + external_js = (JS_DIR / "external_systems.js").read_text(encoding="utf-8") + + self.assertIn('id="ext-add" class="primary"', html) + self.assertIn('id="external-connection-modal" class="modal"', html) + self.assertLess(html.index('id="external-modal"'), html.index('id="external-connection-modal"')) + self.assertIn("function unconnectedDefinitions()", external_js) + self.assertIn("function connectorBadgeHtml(connector)", external_js) + self.assertIn('class="ext-connector ext-connector-openapi"', external_js) + self.assertIn('class="ext-connector ext-connector-mcp"', external_js) + self.assertIn('$("ext-add").onclick = () => openConnectionModal();', external_js) + self.assertIn("openConnectionModal(systemsById.get(id));", external_js) + self.assertIn('await api("PUT", `/v1/external-systems/${editingId}/credentials`', external_js) + self.assertNotIn("function connectionForDefinition(", external_js) + def test_static_js_is_served_with_revalidation_header(self) -> None: with tempfile.TemporaryDirectory() as tmp: static_dir = Path(tmp) diff --git a/web/static/dev.html b/web/static/dev.html index 8adb9be..1094832 100644 --- a/web/static/dev.html +++ b/web/static/dev.html @@ -743,23 +743,33 @@ /* ───── 外部系统 modal(用户凭据只写不回显)───── */ #external-modal { z-index: 112; } - #external-modal .card { width: 620px; max-width: 94vw; max-height: 84vh; display:flex; flex-direction:column; } - #external-modal h3 { margin:0; padding:12px 16px; font-size:16px; border-bottom:1px solid var(--border); display:flex; align-items:center; gap:8px; } - #external-modal h3 .spacer { flex:1; } - #external-modal .sk-x { border:none; background:transparent; font-size:16px; cursor:pointer; color:var(--muted); padding:2px 6px; } + #external-connection-modal { z-index: 114; } + #external-modal .card { width:620px; max-width:94vw; max-height:84vh; display:flex; flex-direction:column; } + #external-connection-modal .card { width:480px; max-width:94vw; max-height:84vh; display:flex; flex-direction:column; } + #external-modal h3, #external-connection-modal h3 { margin:0; padding:12px 16px; font-size:16px; border-bottom:1px solid var(--border); display:flex; align-items:center; gap:8px; } + #external-modal h3 .spacer, #external-connection-modal h3 .spacer { flex:1; } + #external-modal .sk-x, #external-connection-modal .sk-x { border:none; background:transparent; font-size:16px; cursor:pointer; color:var(--muted); padding:2px 6px; } #ext-body { padding:16px; overflow:auto; } - #ext-list { display:grid; gap:8px; margin-bottom:16px; } - .ext-card { border:1px solid var(--border); border-radius:8px; padding:12px; background:var(--panel); } - .ext-card-head { display:flex; align-items:center; gap:8px; } + #ext-toolbar { display:flex; align-items:flex-start; gap:12px; margin-bottom:12px; } + #ext-provider { flex:1; min-width:0; } + #ext-list { display:grid; gap:8px; } + .ext-card { border:1px solid var(--border); border-radius:8px; padding:12px; background:var(--panel); cursor:pointer; transition:border-color .15s, box-shadow .15s; } + .ext-card:hover { border-color:var(--accent); box-shadow:0 2px 10px rgba(0,0,0,.06); outline:none; } + .ext-card-head { display:flex; align-items:center; flex-wrap:wrap; gap:8px; } .ext-card-name { font-weight:600; } + .ext-connector { display:inline-flex; align-items:center; gap:4px; padding:2px 6px; border-radius:999px; font-size:11px; font-weight:600; line-height:1.2; } + .ext-connector svg { width:13px; height:13px; fill:none; stroke:currentColor; stroke-width:1.4; stroke-linecap:round; stroke-linejoin:round; } + .ext-connector-openapi { color:#1769aa; background:#e8f3fc; } + .ext-connector-mcp { color:#7048a8; background:#f1eafb; } .ext-card .meta { margin-top:5px; color:var(--muted); font-size:12px; } .ext-actions { display:flex; flex-wrap:wrap; gap:6px; margin-top:10px; } - #ext-form { border-top:1px solid var(--border); padding-top:14px; } - #ext-form-grid { display:grid; grid-template-columns:1fr 1fr; gap:10px; } + #ext-form { min-height:0; overflow:auto; } + #ext-form-grid { display:grid; grid-template-columns:1fr 1fr; gap:10px; padding:16px; } #ext-form-grid label { display:grid; gap:4px; color:var(--muted); font-size:12px; } #ext-form-grid .wide { grid-column:1 / -1; } - #ext-form-actions { display:flex; align-items:center; justify-content:flex-end; gap:8px; margin-top:12px; } - @media (max-width: 600px) { #ext-form-grid { grid-template-columns:1fr; } #ext-form-grid .wide { grid-column:auto; } } + #ext-err { margin:0 16px 10px; } + #ext-form-actions { display:flex; align-items:center; justify-content:flex-end; gap:8px; padding:12px 16px; border-top:1px solid var(--border); } + @media (max-width:600px) { #ext-toolbar { align-items:stretch; flex-direction:column; } #ext-add { align-self:flex-end; } #ext-form-grid { grid-template-columns:1fr; } #ext-form-grid .wide { grid-column:auto; } } .new-chat-start { width: min(560px, calc(100% - 32px)); margin: auto; @@ -1704,34 +1714,46 @@