dsh-db-tool 1.8.9 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/README.md +18 -8
- package/README_EN.md +18 -8
- package/client/README.md +49 -13
- package/client/client.js +152 -31
- package/docs/sidebar-right-migration.md +477 -0
- package/package.json +7 -7
- package/skills/db-admin/SKILL.md +155 -10
- package/vendor/sqlserver-driver.cjs +1046 -747
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,54 @@
|
|
|
1
1
|
# Changelog — dsh-db-tool
|
|
2
2
|
|
|
3
|
+
## 1.9.1
|
|
4
|
+
|
|
5
|
+
依赖安全与维护性升级(无代码改动,仅 `package-lock.json`)。
|
|
6
|
+
|
|
7
|
+
### 安全
|
|
8
|
+
|
|
9
|
+
- **`fast-uri` 3.1.8 → 3.1.9**:修复 4 个 URI 解析/规范化漏洞——URI scheme 伪造(百分号编码 scheme 不再被解码为活跃 scheme,CVE-2026-110306)、域名规范化时的 host 混淆(CVE-2026-110180)、相对引用规范化语义错误(CVE-2026-110095)、后缀引用中的 scheme 注入(CVE-2026-110725)。该包经 `ajv` ← `@stryker-mutator/core` 引入,仅用于变异测试,不在运行时代码路径中;项目自身的连接串解析使用原生 `new URL()`,不经过 fast-uri。实测确认修复生效:`%68ttp%3A%2F%2F...` 不再被解析出 scheme。
|
|
10
|
+
|
|
11
|
+
### 杂项
|
|
12
|
+
|
|
13
|
+
- **`vitest` 4.1.11 → 4.1.12**:mocker 加固手动与重定向 mock。
|
|
14
|
+
- **`vite` 8.3.1 → 8.3.5**:修复 build 阶段的 chunk import map 与 preload 依赖处理。
|
|
15
|
+
- **`@azure/msal-node` 6.0.1 → 7.1.2 / `@azure/identity` 4.13.3 → 4.14.1**:主版本跳跃,新增依赖 `is-in-ssh`、`powershell-utils`(均由 sindresorhus 维护)。该链路经 `tedious` → `@azure/identity` 引入;本项目仅支持 SQL 认证、不支持 Azure AD 认证,故运行时不会走到,升级属跟随上游维护。
|
|
16
|
+
|
|
17
|
+
## 1.9.0
|
|
18
|
+
|
|
19
|
+
前端挂载点迁移到 DSH 官方右侧栏,摆脱第三方 `dsh-better-sidebar` 依赖。
|
|
20
|
+
|
|
21
|
+
### 变更
|
|
22
|
+
|
|
23
|
+
- **改用 DSH 官方右侧栏**:前端挂载点从第三方 `dsh-better-sidebar` 换成 DSH 官方右侧栏 `@deepseek-ai/dsh-client-ui-sidebar-right`,不再调用 `ctx.betterSidebar.registerTab(...)`,改走两段式公开注册路径——
|
|
24
|
+
1. `ctx.sidebarRightTabs.register({ id, kind, title, guide })` 声明页签类型 `dsh-db-tool`(`guide` 条目 order 45,右侧栏空态页显示「数据库」入口卡片);
|
|
25
|
+
2. `ctx.slots.inject("sidebar.right.pane.tab", …)` 按类型 `id` 注册面板正文;
|
|
26
|
+
3. `ctx.slots.inject("sidebar.right.pane.tab.title", …)` 注册页签胶囊标题。
|
|
27
|
+
正文的会话 `cwd` 改经宿主标准 props `useSessions(s => s.byId[sessionId]?.cwd)` 读取,`visible` 取自 `useTabInfo().tab.visible`(已同时表达「页签激活」与「右侧栏展开 / 非后台会话」,折叠时照旧停轮询)。
|
|
28
|
+
- **获得官方页签能力**:与官方页签同处一个页签系统,可并排、上下拆分、拖出为浮动面板、全屏,布局按会话持久化;这些都由宿主提供,无需本插件维护。
|
|
29
|
+
- **面板数修正为五个**:入口与文档此前按四个面板描述,补上「数据传输」后与实现一致。
|
|
30
|
+
|
|
31
|
+
### 修复
|
|
32
|
+
|
|
33
|
+
- **补 `keepMounted: true` 声明**:官方右侧栏默认按需卸载未激活页签的正文,而本插件面板状态(连接列表、项目路径、控制台多标签与结果、传输日志)全部存在 React 本地 state 中,不声明就会在切标签 / 切会话 / 收起右栏时全部归零。第三方 `dsh-better-sidebar` 是硬编码 `keepMounted: true` 全量保活的,迁移后该行为不再由宿主默认提供,必须在页签类型声明里显式补上。
|
|
34
|
+
- **删除绑定语言的 React `key`**:原先 `key` 随语言变化,切语言会重建整棵面板组件树,导致未提交的输入丢失。官方右侧栏的做法是文案走 thunk 在渲染时重新取词,不需要靠重挂载来换文案;`key` 因此只保留会话维度。
|
|
35
|
+
- **修 `t()` 读取链**:原先语言快照只在 `apply` 时读一次并闭包捕获,切语言后 `t()` 仍返回旧快照,文案实际不更新(页面看着语言变了、插件面板没变)。改为每次取词现读 locale 快照,语言切换即时生效。
|
|
36
|
+
- **显式声明 `priority: "extension"`**:本插件是页类型、不带 `patterns`,不参与宿主资源路由排序;声明只为让注册意图更可读,不改变行为。
|
|
37
|
+
- **右侧栏服务缺失时打印 warn**:原先服务取不到时静默 `return`,挂载失败无任何痕迹、排查只能靠猜;改为打印 `warn` 指明缺失的服务名。
|
|
38
|
+
|
|
39
|
+
### 清理
|
|
40
|
+
|
|
41
|
+
- 移除 `devDependencies` 与 `peerDependencies` 中的 `dsh-better-sidebar`,`package-lock.json` 一并移除该包及其独占的传递依赖(重新生成,未升级任何已有依赖)。
|
|
42
|
+
- 删除 Panel 中 `scope.repoRoot` / `scope.workspacePath` 的死兜底——`scope` 实际只有 `{ sessionId, cwd }` 两个字段,这些兜底分支永远不会命中。
|
|
43
|
+
- 清理全部沿用旧名词(side-bar / betterSidebar)的注释。
|
|
44
|
+
- `files` 白名单收录 `docs/sidebar-right-migration.md`(精确路径而非 `docs` 目录:npm 打包不看 `.gitignore`,目录级条目会把本地被忽略的草稿一并发布)。
|
|
45
|
+
|
|
46
|
+
### 兼容性
|
|
47
|
+
|
|
48
|
+
- `engines.dsh` 下限抬到 `0.1.7-rc.2`:`keepMounted` 字段自官方右侧栏 `0.1.7-rc.1` 起才存在,更早版本(含 `0.1.0-rc.6`、`0.1.5`/`0.1.6` 系)会静默忽略它,表现为切页签/切会话后面板状态归零且无报错,故不再宣称兼容。
|
|
49
|
+
- `apply` 增加能力探测:注册后读回该页签类型,若宿主未接受 `keepMounted` 则打印 warn 提示升级 DSH——收紧 `engines` 挡不住「旧 profile 已装好继续用」的情形。
|
|
50
|
+
- issue 模板的入口名与版本占位符同步为「右侧栏」与 `1.9.0`。
|
|
51
|
+
|
|
3
52
|
## 1.8.9
|
|
4
53
|
|
|
5
54
|
发布配置修正:`files` 白名单清理。
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README_EN.md) | 简体中文
|
|
4
4
|
|
|
5
|
-
在 DSH 聊天中安全操作数据库:9
|
|
5
|
+
在 DSH 聊天中安全操作数据库:9 种数据库、项目级细粒度授权、危险操作确认、全量审计,配套右侧栏管理台与 SQL 控制台。
|
|
6
6
|
|
|
7
7
|
## 功能流程
|
|
8
8
|
|
|
@@ -67,7 +67,7 @@ DDL、`FLUSHALL`、`dropDatabase` 等语句先返回 `NEEDS_CONFIRMATION`,不
|
|
|
67
67
|
|
|
68
68
|
### 全量审计
|
|
69
69
|
|
|
70
|
-
每次执行(包括被拒绝的请求)都追加一行到 `audit.jsonl
|
|
70
|
+
每次执行(包括被拒绝的请求)都追加一行到 `audit.jsonl`:语句、危险级别、是否经过确认、结果与耗时。右侧栏可实时查看。
|
|
71
71
|
|
|
72
72
|
### 数据浏览
|
|
73
73
|
|
|
@@ -77,7 +77,7 @@ DDL、`FLUSHALL`、`dropDatabase` 等语句先返回 `NEEDS_CONFIRMATION`,不
|
|
|
77
77
|
|
|
78
78
|
### SQL 控制台
|
|
79
79
|
|
|
80
|
-
Navicat
|
|
80
|
+
Navicat 风格的右侧栏多标签编辑器(CodeMirror 6):
|
|
81
81
|
|
|
82
82
|
- **多标签**:每标签独立保存 SQL、结果与历史(50 条上限,点击回填)
|
|
83
83
|
- **语法高亮与补全**:按方言自动切换(MySQL / PostgreSQL / SQLite / PL/SQL / T-SQL),表列元数据注入补全
|
|
@@ -107,9 +107,9 @@ Navicat 风格的侧边栏多标签编辑器(CodeMirror 6):
|
|
|
107
107
|
|
|
108
108
|
---
|
|
109
109
|
|
|
110
|
-
##
|
|
110
|
+
## 右侧栏
|
|
111
111
|
|
|
112
|
-
通过
|
|
112
|
+
通过 DSH **官方右侧栏**(`@deepseek-ai/dsh-client-ui-sidebar-right`)注册一个页签,不依赖任何第三方侧边栏。右侧栏展开时,「数据库」随侧边栏空态页(入口页)的卡片出现,可与其他官方页签并排、拆分或浮出为浮窗;页签内含五个面板(中英双语):
|
|
113
113
|
|
|
114
114
|
| 面板 | 用途 |
|
|
115
115
|
|---|---|
|
|
@@ -117,6 +117,14 @@ Navicat 风格的侧边栏多标签编辑器(CodeMirror 6):
|
|
|
117
117
|
| 项目授权 | 为当前项目勾选各连接的 ro/rw |
|
|
118
118
|
| 数据浏览 | 树形浏览库表、搜索、预览数据 |
|
|
119
119
|
| SQL 控制台 | 多标签编辑器、事务、历史、结果网格 |
|
|
120
|
+
| 数据传输 | 库表级数据迁移,批量/并行与写入模式可选,附传输日志 |
|
|
121
|
+
|
|
122
|
+
### 使用须知
|
|
123
|
+
|
|
124
|
+
两条都是官方右侧栏的契约行为,容易误判为插件缺陷:
|
|
125
|
+
|
|
126
|
+
- **未选择会话时,右侧栏不显示数据库面板。** 官方右侧栏的布局状态按会话 id 键控,没有会话就没有停靠面,也就没有入口卡片与页签——先选一个会话再展开右侧栏。
|
|
127
|
+
- **若当前 DSH 就绪的入口卡片只有一个,「+」新建格会直接打开数据库页签而不是引导页。** 这是官方默认页规则(入口恰为一个时直接打开该页;没有入口或多个入口时打开引导页),不是跳过引导页的异常。
|
|
120
128
|
|
|
121
129
|
---
|
|
122
130
|
|
|
@@ -182,7 +190,7 @@ DSH 桌面端 0.2.0-rc.2+ 已捆绑 `dsh` 命令:
|
|
|
182
190
|
2. 安装插件(npm 安装须钉精确版本,`@latest` 会因 release-age 校验回落):
|
|
183
191
|
|
|
184
192
|
```bash
|
|
185
|
-
dsh plugin --profile desktop add dsh-db-tool@1.
|
|
193
|
+
dsh plugin --profile desktop add dsh-db-tool@1.9.1 --registry=https://registry.npmjs.org/
|
|
186
194
|
```
|
|
187
195
|
|
|
188
196
|
3. **重启桌面端**生效
|
|
@@ -191,6 +199,8 @@ DSH 桌面端 0.2.0-rc.2+ 已捆绑 `dsh` 命令:
|
|
|
191
199
|
|
|
192
200
|
**运行环境要求**:Node.js ≥ 22。部分数据库驱动(tedious 及其 Azure 依赖)要求 Node 22 起,Node 20 会在建连时报 `Promise.withResolvers is not a function`。
|
|
193
201
|
|
|
202
|
+
**前端挂载位置**:数据库面板挂在 DSH 自带的右侧栏上,不需要额外安装任何侧边栏插件;`dsh.client.inject` 已声明 `@deepseek-ai/dsh-client-ui-sidebar-right` 作为加载前置。
|
|
203
|
+
|
|
194
204
|
---
|
|
195
205
|
|
|
196
206
|
## 数据存放
|
|
@@ -232,9 +242,9 @@ lib/ 插件主体
|
|
|
232
242
|
store/ 连接/授权/审计/密钥存储
|
|
233
243
|
guard/ 语句风险分级
|
|
234
244
|
manager.ts 业务编排(授权、分派、确认、审计)
|
|
235
|
-
http/
|
|
245
|
+
http/ 右侧栏 HTTP 路由
|
|
236
246
|
script/ run_script 沙箱与子进程
|
|
237
|
-
client/
|
|
247
|
+
client/ 右侧栏单文件产物
|
|
238
248
|
skills/ db-admin skill
|
|
239
249
|
scripts/ 构建与工具脚本
|
|
240
250
|
patches/ DSH 宿主热修复补丁
|
package/README_EN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[简体中文](README.md) | English
|
|
4
4
|
|
|
5
|
-
Operate databases safely from DSH chat: 9 database engines, project-scoped grants, dangerous-operation confirmation, full audit logging, plus a sidebar console and SQL editor.
|
|
5
|
+
Operate databases safely from DSH chat: 9 database engines, project-scoped grants, dangerous-operation confirmation, full audit logging, plus a right-sidebar console and SQL editor.
|
|
6
6
|
|
|
7
7
|
## Feature flow
|
|
8
8
|
|
|
@@ -67,7 +67,7 @@ Confirmation comes through chat for model calls and through a dialog in the SQL
|
|
|
67
67
|
|
|
68
68
|
### Full audit logging
|
|
69
69
|
|
|
70
|
-
Every execution — including rejected requests — appends one line to `audit.jsonl`: statement, danger level, whether it was confirmed, result and duration. The sidebar tails it live.
|
|
70
|
+
Every execution — including rejected requests — appends one line to `audit.jsonl`: statement, danger level, whether it was confirmed, result and duration. The right sidebar tails it live.
|
|
71
71
|
|
|
72
72
|
### Data browsing
|
|
73
73
|
|
|
@@ -77,7 +77,7 @@ Every execution — including rejected requests — appends one line to `audit.j
|
|
|
77
77
|
|
|
78
78
|
### SQL console
|
|
79
79
|
|
|
80
|
-
A Navicat-style multi-tab editor in the sidebar (CodeMirror 6):
|
|
80
|
+
A Navicat-style multi-tab editor in the right sidebar (CodeMirror 6):
|
|
81
81
|
|
|
82
82
|
- **Multiple tabs**: each keeps its own SQL, results and history (50 entries, click to refill)
|
|
83
83
|
- **Highlighting and completion**: dialect switches automatically (MySQL / PostgreSQL / SQLite / PL/SQL / T-SQL), with table/column metadata injected into completion
|
|
@@ -107,9 +107,9 @@ Export all connections (including passwords) to a passphrase-encrypted file for
|
|
|
107
107
|
|
|
108
108
|
---
|
|
109
109
|
|
|
110
|
-
##
|
|
110
|
+
## Right sidebar
|
|
111
111
|
|
|
112
|
-
One tab registered through
|
|
112
|
+
One tab registered through the official DSH right sidebar (`@deepseek-ai/dsh-client-ui-sidebar-right`) — no third-party sidebar required. When the panel is expanded, **Databases** appears as a card on the sidebar's guide (empty-state) page and can sit next to, split with, or float out as a floating window alongside any other official tab. The tab holds five panels (bilingual):
|
|
113
113
|
|
|
114
114
|
| Panel | Purpose |
|
|
115
115
|
|---|---|
|
|
@@ -117,6 +117,14 @@ One tab registered through `dsh-better-sidebar`, containing four panels (bilingu
|
|
|
117
117
|
| Grants | Tick ro/rw per connection for the current project |
|
|
118
118
|
| Browse | Tree navigation, search and data preview |
|
|
119
119
|
| SQL Console | Multi-tab editor, transactions, history, result grid |
|
|
120
|
+
| Transfer | Table-level data migration with batch/parallelism and write modes, plus a transfer log |
|
|
121
|
+
|
|
122
|
+
### Notes
|
|
123
|
+
|
|
124
|
+
Both are contract behaviours of the official right sidebar and are easy to mistake for plugin defects:
|
|
125
|
+
|
|
126
|
+
- **Without a selected session the right sidebar shows no database panel.** The official right sidebar keys its layout state by session id, so with no session there is no docking surface — hence no guide card and no tab. Select a session first, then expand the right sidebar.
|
|
127
|
+
- **If the only ready entry card is the database one, the "+" new-pane button opens the database tab directly instead of the guide page.** This is the official default-page rule (exactly one entry opens that page; zero or several entries open the guide), not a failure to show the guide.
|
|
120
128
|
|
|
121
129
|
---
|
|
122
130
|
|
|
@@ -182,7 +190,7 @@ DSH desktop 0.2.0-rc.2+ bundles the `dsh` command:
|
|
|
182
190
|
2. Install the plugin (npm installs must pin an exact version — `@latest` falls back to an older release due to release-age checks):
|
|
183
191
|
|
|
184
192
|
```bash
|
|
185
|
-
dsh plugin --profile desktop add dsh-db-tool@1.
|
|
193
|
+
dsh plugin --profile desktop add dsh-db-tool@1.9.1 --registry=https://registry.npmjs.org/
|
|
186
194
|
```
|
|
187
195
|
|
|
188
196
|
3. **Restart the desktop app** to load it
|
|
@@ -191,6 +199,8 @@ DSH desktop 0.2.0-rc.2+ bundles the `dsh` command:
|
|
|
191
199
|
|
|
192
200
|
**Runtime requirement**: Node.js ≥ 22. Some database drivers (tedious and its Azure dependencies) require Node 22+; on Node 20 connections fail with `Promise.withResolvers is not a function`.
|
|
193
201
|
|
|
202
|
+
**Where the UI is mounted**: the database panel docks into DSH's built-in right sidebar — no extra sidebar plugin needs installing. `dsh.client.inject` already declares `@deepseek-ai/dsh-client-ui-sidebar-right` as a load prerequisite.
|
|
203
|
+
|
|
194
204
|
---
|
|
195
205
|
|
|
196
206
|
## Data layout
|
|
@@ -232,9 +242,9 @@ lib/ plugin core
|
|
|
232
242
|
store/ connection / grant / audit / secret storage
|
|
233
243
|
guard/ statement risk classification
|
|
234
244
|
manager.ts business orchestration (auth, dispatch, confirm, audit)
|
|
235
|
-
http/ sidebar HTTP routes
|
|
245
|
+
http/ right-sidebar HTTP routes
|
|
236
246
|
script/ run_script sandbox and child process
|
|
237
|
-
client/ sidebar single-file bundle
|
|
247
|
+
client/ right-sidebar single-file bundle
|
|
238
248
|
skills/ db-admin skill
|
|
239
249
|
scripts/ build and tooling scripts
|
|
240
250
|
patches/ DSH host hotfix patches
|
package/client/README.md
CHANGED
|
@@ -1,47 +1,83 @@
|
|
|
1
|
-
# dsh-db-tool client
|
|
1
|
+
# dsh-db-tool client(右侧栏前端)
|
|
2
2
|
|
|
3
3
|
## 形态:无构建、单文件产物
|
|
4
4
|
|
|
5
|
-
本目录**不需要任何构建步骤**。`client.js` 即最终发布产物,与 dsh-ssh-tunnel 的发布形态一致:一个 `window.__ModuleLoader__.load({ id, factory })` 包裹的单文件 IIFE 模块,`factory(require)` 内 `require("react")` 取 React,运行于 DSH
|
|
5
|
+
本目录**不需要任何构建步骤**。`client.js` 即最终发布产物,与 dsh-ssh-tunnel 的发布形态一致:一个 `window.__ModuleLoader__.load({ id, factory })` 包裹的单文件 IIFE 模块,`factory(require)` 内 `require("react")` 取 React,运行于 DSH **官方右侧栏**(`@deepseek-ai/dsh-client-ui-sidebar-right`)。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
不存在第三方侧边栏依赖:页签走官方的两段式公开注册路径,与官方自带页签类型完全同一套接口。
|
|
8
|
+
|
|
9
|
+
这样做的好处:无打包器、无依赖漂移、产物可直接被插件安装流程原样打包,且随官方右侧栏升级自动适配——拆分窗格、浮动面板、全屏、布局持久化都由宿主提供,本插件不必自维护。
|
|
10
|
+
|
|
11
|
+
## 接入官方右侧栏
|
|
12
|
+
|
|
13
|
+
`exports.inject = ["slots", "sidebarRightTabs", "locale"]`,`apply(ctx)` 内三段注册:
|
|
14
|
+
|
|
15
|
+
| 阶段 | 调用 | 作用 |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| 1. 页签类型 | `ctx.sidebarRightTabs.register({ id, kind, title, guide, priority, keepMounted })` | 声明 `id = kind = "dsh-db-tool"` 的页签类型;`guide` 条目(order 45)让右侧栏空态页显示「数据库」入口卡片,点击即打开本页签;`priority: "extension"` 显式写明第三方档位;`keepMounted: true` 见下节 |
|
|
18
|
+
| 2. 页签正文 | `ctx.slots.inject("sidebar.right.pane.tab", () => ctx.slots.register({ name, key: id }, DbToolBody))` | 按类型 `id` 挂载面板正文 |
|
|
19
|
+
| 3. 页签标题 | `ctx.slots.inject("sidebar.right.pane.tab.title", () => ctx.slots.register({ name, key: id }, DbToolTitle))` | 页签胶囊上的图标 + 标题,随语言切换重渲染 |
|
|
20
|
+
|
|
21
|
+
两个必须记住的易错点:
|
|
22
|
+
|
|
23
|
+
- **席位 `key` 必须是类型的 `id`,不是 `kind`。** 宿主按 `definition.id` 派发席位;官方允许 extension 接管 builtin 的 `kind`,若按 `kind` 挂席位,同 kind 的两份实现会互相串位。本插件 `id` 与 `kind` 取值恰好相同,但这是两条独立的身份,改 `kind` 时不能顺手改席位 `key`。
|
|
24
|
+
- **每一处注册都要包在 `ctx.effect(...)` 里。** 类型注册与两个席位注册各自一个 effect,插件卸载时宿主据此自动注销;漏掉 effect 的注册会残留到下次加载,表现为同 id 二次注册报错。席位侧还必须用 `ctx.slots.inject` 包一层,让注册表按声明时序等待。
|
|
25
|
+
|
|
26
|
+
## 正文拿到的宿主标准 props
|
|
27
|
+
|
|
28
|
+
正文组件由宿主渲染,携带框架标准 props:
|
|
29
|
+
|
|
30
|
+
- `sessionId` —— 当前会话 id;
|
|
31
|
+
- `useTabInfo()` —— 返回 `{ sidebar, panel, tab }`,其中 `tab.visible` 已同时表达「页签激活」「右侧栏展开」「非后台会话」三重语义,直接当可见性用即可,业务侧不必再自判前后台;折叠或切到后台会话时面板应停止轮询与请求;
|
|
32
|
+
- `useSessions(selector)` —— 会话 store 的订阅钩子,会话工作目录取自 `useSessions(s => s.byId[sessionId]?.cwd)`,与官方各页签类型同一来源。
|
|
33
|
+
|
|
34
|
+
## 为什么声明 keepMounted
|
|
35
|
+
|
|
36
|
+
官方右侧栏**默认按需卸载正文**(`keepMounted` 缺省 `false`):切页签、切会话、收起右栏都会把正文卸载。而本插件的面板状态几乎全在 React 本地 state ——面板分段、项目路径、连接与授权列表、树刷新信号、SQL 控制台的多标签与结果、传输视图的选择项 —— 不声明 `keepMounted` 就会在上面的任一动作后全部归零。
|
|
37
|
+
|
|
38
|
+
因此类型声明里写 `keepMounted: true`:已访问过的正文在切页签、切会话、收起与停靠切换期间保留。注意它**不会提前挂载**正文(首次可见才挂载),也不保「插件卸载 / 页面刷新 / 关闭页签」之后的实例——那属于持久化范畴,本插件不做。
|
|
8
39
|
|
|
9
40
|
## 文件
|
|
10
41
|
|
|
11
|
-
- `client.js` — 全部前端逻辑(单文件,约
|
|
12
|
-
-
|
|
13
|
-
- zh/en 双语词典(zh 默认),经 `ctx.locale.register(NS, ...)` 注册,`useSyncExternalStore`
|
|
42
|
+
- `client.js` — 全部前端逻辑(单文件,约 3900 行),内含:
|
|
43
|
+
- 5 个面板视图:管理(连接 CRUD + 测试 + 审计查看)、授权(项目路径 → 连接 ro/rw/撤销)、浏览(树选库/表 → 点表打开 Navicat 式弹窗:左树 + 结构/数据 Tabs + 分页;portal 挂 body,Esc/遮罩/✕ 关闭)、SQL 控制台(连接/库选择、query/execute/script、危险确认对话框 → challengeId 重发)、传输(库表级数据迁移、写入模式、进度与传输日志)
|
|
44
|
+
- zh/en 双语词典(zh 默认),经 `ctx.locale.register(NS, ...)` 注册,`useSyncExternalStore` 订阅语言切换;文案一律走 thunk(`title()` / `description()` 每次调用现读),语言变化不需要重注册
|
|
14
45
|
- 危险确认:`runGuarded()` 先发请求,收到 `code:"NEEDS_CONFIRMATION"` 且有 `challengeId` 时弹 `DangerDialog`,用户确认后携 challengeId 重发(challenge 一次性、绑语句、5 分钟有效)
|
|
15
46
|
- REST API 调用走同源前缀 **`/dsh-db-tool/api`**(须与 host 端 `webServer.register` 的 prefix 一致)
|
|
16
47
|
|
|
17
48
|
## 验证(本地跑通的方式)
|
|
18
49
|
|
|
19
|
-
|
|
50
|
+
无构建器,验证分三项:
|
|
20
51
|
|
|
21
52
|
```powershell
|
|
22
53
|
node --check client/client.js # 语法检查
|
|
54
|
+
npx vitest run tests/client # 前端单测
|
|
23
55
|
```
|
|
24
56
|
|
|
25
|
-
|
|
57
|
+
再加一条注册契约冒烟(`tests/client/sidebar-registration.spec.ts`):stub `window.__ModuleLoader__` / `require("react")` 与最小 `document`,eval 加载文件,调 `factory` 得到 `exports`,断言 `exports.inject` 含 `"sidebarRightTabs"` 与 `"slots"`、`exports.apply(ctx)` 能注册 `kind:"dsh-db-tool"` 的页签类型并在两个席位各注册一个 `key:"dsh-db-tool"` 的组件(`key` 与类型 `id` 必须相等),并断言卸载后注册项全部撤销。桩 ctx 只需记录 `locale` / `effect` / `sidebarRightTabs` / `slots` 四项服务,依赖仅 node 内置模块。
|
|
26
58
|
|
|
27
59
|
## 打包接入(发布形态,需根 package.json 配合)
|
|
28
60
|
|
|
29
|
-
npm 包需暴露 client 供 DSH
|
|
61
|
+
npm 包需暴露 client 供 DSH 注入,并声明对官方右侧栏客户端的依赖(`dsh.client.inject` 里的包名会先于本插件加载):
|
|
30
62
|
|
|
31
63
|
```jsonc
|
|
32
64
|
{
|
|
33
65
|
"exports": { "./client": "./client/client.js" },
|
|
34
66
|
"dsh": {
|
|
35
67
|
"bundle": { "patch": "./cordis.patch.yml" },
|
|
36
|
-
"client": {
|
|
68
|
+
"client": {
|
|
69
|
+
"inject": ["@deepseek-ai/dsh-client-locale", "@deepseek-ai/dsh-client-ui-sidebar-right"],
|
|
70
|
+
"platform": "web"
|
|
71
|
+
}
|
|
37
72
|
},
|
|
38
73
|
"peerDependencies": {
|
|
39
|
-
"@deepseek-ai/cordis": "^4.0.1"
|
|
40
|
-
"dsh-better-sidebar": ">=0.12.0"
|
|
74
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
41
75
|
}
|
|
42
76
|
}
|
|
43
77
|
```
|
|
44
78
|
|
|
79
|
+
`dsh.client.inject` 负责「谁先加载」,`peerDependencies` 只声明框架本体——右侧栏包属于宿主提供的运行时,不放 peerDependencies。
|
|
80
|
+
|
|
45
81
|
## 本地开发
|
|
46
82
|
|
|
47
83
|
仓库根目录执行(Windows):
|
|
@@ -50,4 +86,4 @@ npm 包需暴露 client 供 DSH 注入(此字段属根 package.json,超出 c
|
|
|
50
86
|
pnpm dsh web --patch ./cordis.patch.yml
|
|
51
87
|
```
|
|
52
88
|
|
|
53
|
-
|
|
89
|
+
右侧栏空态页出现「数据库」入口卡片(order 45),点开后即为面板,即加载成功;若右侧栏已有页签,用页签条上的「+」打开入口页再点「数据库」。projectPath 取自当前会话的 `cwd`,也可在面板顶部手动输入后点「加载」。
|
package/client/client.js
CHANGED
|
@@ -7,18 +7,21 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
const React = require("react");
|
|
8
8
|
|
|
9
9
|
/* ============================================================
|
|
10
|
-
* dsh-db-tool
|
|
10
|
+
* dsh-db-tool 右侧栏客户端
|
|
11
11
|
* 形态与 dsh-ssh-tunnel 一致:单文件 __ModuleLoader__ 模块,
|
|
12
12
|
* exports.inject / exports.apply(ctx) 由 DSH web 宿主加载,
|
|
13
|
-
* 经 dsh-
|
|
13
|
+
* 经 DSH 官方右侧栏(@deepseek-ai/dsh-client-ui-sidebar-right)
|
|
14
|
+
* 的 sidebarRightTabs + slots 两段式注册页签,不依赖第三方侧边栏。
|
|
14
15
|
* ============================================================ */
|
|
15
16
|
|
|
16
|
-
// --- i18n(
|
|
17
|
+
// --- i18n(ctx.locale 注册 + useSyncExternalStore 订阅语言切换)---
|
|
17
18
|
// i18n 字典/HTTP 封装/CSS/对话框均无单元断言,仅变异下方纯函数层(Stryker 区段指令需精确匹配,不得带后缀文本)
|
|
18
19
|
// Stryker disable all
|
|
19
20
|
const LOCALE_NS = "dbTool";
|
|
20
21
|
const zh = {
|
|
21
22
|
tabTitle: "数据库",
|
|
23
|
+
tabDesc: "多库连接管理、项目授权、数据浏览与 SQL 控制台",
|
|
24
|
+
tabUnavailable: "页签尚未就绪,请切换页签后重试",
|
|
22
25
|
projectLabel: "项目: {path}",
|
|
23
26
|
projectUnbound: "未识别当前项目路径,请在下方填写工作区绝对路径",
|
|
24
27
|
projectPathPlaceholder: "工作区绝对路径(如 E:\\Code\\my-app)",
|
|
@@ -249,6 +252,8 @@ window.__ModuleLoader__.load({
|
|
|
249
252
|
};
|
|
250
253
|
const en = {
|
|
251
254
|
tabTitle: "Databases",
|
|
255
|
+
tabDesc: "Connection management, project grants, data browse and a SQL console",
|
|
256
|
+
tabUnavailable: "This tab is not ready yet; switch tabs and try again",
|
|
252
257
|
projectLabel: "Project: {path}",
|
|
253
258
|
projectUnbound: "Cannot detect current project path; enter the workspace absolute path below",
|
|
254
259
|
projectPathPlaceholder: "workspace absolute path (e.g. /home/me/my-app)",
|
|
@@ -473,13 +478,29 @@ window.__ModuleLoader__.load({
|
|
|
473
478
|
};
|
|
474
479
|
|
|
475
480
|
let activeLocale = "zh";
|
|
481
|
+
// 持有 locale 服务引用:切语言时不会重新 apply,故 t() 必须每次现读快照,
|
|
482
|
+
// activeLocale 仅作取不到服务时的兜底(apply 期写入一次即过期)。
|
|
483
|
+
let localeService = null;
|
|
476
484
|
function attachLocale(locale) {
|
|
477
485
|
try {
|
|
478
|
-
|
|
486
|
+
if (locale && typeof locale.getSnapshot === "function") localeService = locale;
|
|
487
|
+
// 取不到有效语言时保留原值:否则重新 apply 会把已切好的语言打回默认
|
|
488
|
+
const active = locale && locale.getSnapshot && locale.getSnapshot().active;
|
|
489
|
+
if (active) activeLocale = active;
|
|
479
490
|
} catch (e) { /* 保持默认 */ }
|
|
480
491
|
}
|
|
492
|
+
// 当前语言:优先读 locale 服务实时快照,读不到才回落 apply 期缓存值
|
|
493
|
+
function currentLocale() {
|
|
494
|
+
try {
|
|
495
|
+
if (localeService) {
|
|
496
|
+
const snapshot = localeService.getSnapshot();
|
|
497
|
+
if (snapshot && snapshot.active) return snapshot.active;
|
|
498
|
+
}
|
|
499
|
+
} catch (e) { /* 回落缓存值 */ }
|
|
500
|
+
return activeLocale;
|
|
501
|
+
}
|
|
481
502
|
function t(key, params) {
|
|
482
|
-
let text = (
|
|
503
|
+
let text = (currentLocale() === "en" ? en[key] : undefined) || zh[key] || en[key] || key;
|
|
483
504
|
if (params !== undefined && params !== null) {
|
|
484
505
|
for (const [name, value] of Object.entries(params)) {
|
|
485
506
|
text = String(text).split("{" + name + "}").join(String(value));
|
|
@@ -553,6 +574,12 @@ window.__ModuleLoader__.load({
|
|
|
553
574
|
);
|
|
554
575
|
}
|
|
555
576
|
|
|
577
|
+
// 官方右侧栏 guide 入口的 icon 是 React 组件(宿主按 <Icon size className /> 渲染),
|
|
578
|
+
// 这里把既有 icon(size) 适配成组件形态,保持同一枚数据库图标。
|
|
579
|
+
function DbIcon(props) {
|
|
580
|
+
return icon(props && props.size);
|
|
581
|
+
}
|
|
582
|
+
|
|
556
583
|
let styleEl = null;
|
|
557
584
|
function ensureStyles() {
|
|
558
585
|
if (styleEl) return;
|
|
@@ -2671,6 +2698,13 @@ window.__ModuleLoader__.load({
|
|
|
2671
2698
|
loadDbs();
|
|
2672
2699
|
}, [connId, projectPath]);
|
|
2673
2700
|
|
|
2701
|
+
// 换项目时作废事务:事务按创建时的 projectKey 授权与审计,
|
|
2702
|
+
// 残留的 sessionToken 会让后续语句写进旧项目的库、审计记旧项目,
|
|
2703
|
+
// 而顶栏显示的是新项目路径。不主动回滚,交服务端 TTL 清扫。
|
|
2704
|
+
React.useEffect(() => {
|
|
2705
|
+
setTxToken(null);
|
|
2706
|
+
}, [projectPath]);
|
|
2707
|
+
|
|
2674
2708
|
// vendor bundle 异步加载(失败降级 textarea,静默)
|
|
2675
2709
|
React.useEffect(() => {
|
|
2676
2710
|
let dead = false;
|
|
@@ -3622,10 +3656,11 @@ window.__ModuleLoader__.load({
|
|
|
3622
3656
|
const visible = props.visible;
|
|
3623
3657
|
const scope = props.scope || {};
|
|
3624
3658
|
const [view, setView] = React.useState("manage");
|
|
3625
|
-
//
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3659
|
+
// 项目路径初始值取 scope.cwd;scope 由本文件构造为 { sessionId, cwd },cwd 缺失时留空等下方异步解析或手填
|
|
3660
|
+
const [projectPath, setProjectPath] = React.useState(scope.cwd || "");
|
|
3661
|
+
// 是否「用户手填过」:只由输入框 onChange 置位。首帧 cwd 取不到时不能预设为已编辑,
|
|
3662
|
+
// 否则下方 project-context 解析被永久跳过,用户卡在空路径输入框且连接全显示未授权。
|
|
3663
|
+
const [projectEdited, setProjectEdited] = React.useState(false);
|
|
3629
3664
|
const [conns, setConns] = React.useState([]);
|
|
3630
3665
|
const [grants, setGrants] = React.useState([]);
|
|
3631
3666
|
const [busy, setBusy] = React.useState("");
|
|
@@ -3646,7 +3681,7 @@ window.__ModuleLoader__.load({
|
|
|
3646
3681
|
// host 端以会话 header.cwd 为准归一化出 projectPathKey,前端不自拼 key。
|
|
3647
3682
|
// scope.cwd/sessionId 变化(切换会话)时自动重新解析;用户手填(projectEdited)时不覆盖。
|
|
3648
3683
|
const sessionKey = scope.sessionId || "";
|
|
3649
|
-
const scopeCwd = scope.cwd ||
|
|
3684
|
+
const scopeCwd = scope.cwd || "";
|
|
3650
3685
|
const resolvedRef = React.useRef("");
|
|
3651
3686
|
React.useEffect(() => {
|
|
3652
3687
|
if (!visible || projectEdited) return;
|
|
@@ -3689,7 +3724,9 @@ window.__ModuleLoader__.load({
|
|
|
3689
3724
|
manage: React.createElement(ManageView, shared),
|
|
3690
3725
|
grants: React.createElement(GrantsView, shared),
|
|
3691
3726
|
console: React.createElement(ConsoleView, shared),
|
|
3692
|
-
|
|
3727
|
+
// visible 含本视图是否被选中:keepMounted 让正文在后台存活,
|
|
3728
|
+
// 只看顶层 visible 会让 transfer/live 在其他视图下继续 2s 轮询。
|
|
3729
|
+
transfer: React.createElement(TransferView, Object.assign({}, shared, { visible: visible && view === "transfer" })),
|
|
3693
3730
|
};
|
|
3694
3731
|
return React.createElement(
|
|
3695
3732
|
"div",
|
|
@@ -3711,7 +3748,7 @@ window.__ModuleLoader__.load({
|
|
|
3711
3748
|
// 填充式输入:裸 input 命中样式层 .dbt-row input 契约(去掉旧 dbt-card + 内联 padding hack)
|
|
3712
3749
|
React.createElement("input", {
|
|
3713
3750
|
placeholder: t("projectPathPlaceholder"), value: projectPath,
|
|
3714
|
-
onChange: (e) => setProjectPath(e.target.value),
|
|
3751
|
+
onChange: (e) => { setProjectPath(e.target.value); setProjectEdited(true); },
|
|
3715
3752
|
// 失焦时:输入与权威解析一致才收回自动跟随;用户填了自己的路径则保持手填态(不被权威值打回)
|
|
3716
3753
|
onBlur: (e) => {
|
|
3717
3754
|
const v = (e.target.value || "").trim();
|
|
@@ -3764,28 +3801,32 @@ window.__ModuleLoader__.load({
|
|
|
3764
3801
|
|
|
3765
3802
|
function DbToolRoot(props) {
|
|
3766
3803
|
const ctx = props.ctx;
|
|
3767
|
-
//
|
|
3768
|
-
|
|
3804
|
+
// 订阅宿主 locale 服务只为在语言切换时触发重渲染;文案由 t() 每次现读快照,
|
|
3805
|
+
// 因此不再把语言拼进 key —— 那会重建整棵面板,丢掉所有本地状态。
|
|
3806
|
+
React.useSyncExternalStore(
|
|
3769
3807
|
React.useCallback(function (cb) {
|
|
3770
3808
|
if (!ctx.locale || typeof ctx.locale.subscribe !== "function") return function () {};
|
|
3771
3809
|
return ctx.locale.subscribe(cb);
|
|
3772
3810
|
}, [ctx]),
|
|
3773
3811
|
React.useCallback(function () {
|
|
3774
3812
|
try {
|
|
3775
|
-
return ctx.locale && ctx.locale.getSnapshot ? ctx.locale.getSnapshot().active :
|
|
3776
|
-
} catch (e) { return
|
|
3813
|
+
return ctx.locale && ctx.locale.getSnapshot ? ctx.locale.getSnapshot().active : currentLocale();
|
|
3814
|
+
} catch (e) { return currentLocale(); }
|
|
3777
3815
|
}, [ctx]),
|
|
3778
3816
|
function () { return "zh"; },
|
|
3779
3817
|
);
|
|
3780
3818
|
return React.createElement(Panel, {
|
|
3781
|
-
key: "db-tool-" + String(localeKey || "zh"),
|
|
3782
3819
|
visible: props.visible,
|
|
3783
3820
|
scope: props.scope,
|
|
3784
3821
|
ctx: ctx, // 宿主 ctx 下传,Panel 透传给 BrowsePane
|
|
3785
3822
|
});
|
|
3786
3823
|
}
|
|
3787
3824
|
|
|
3788
|
-
|
|
3825
|
+
// 依赖官方右侧栏(@deepseek-ai/dsh-client-ui-sidebar-right)的两个服务:
|
|
3826
|
+
// sidebarRightTabs = 页签类型注册表,slots = 席位居中器(sidebar.right.pane.tab / .title)。
|
|
3827
|
+
// locale 为 DSH 客户端通用服务(dsh.client.inject 里已声明 @deepseek-ai/dsh-client-locale)。
|
|
3828
|
+
const inject = ["slots", "sidebarRightTabs", "locale"];
|
|
3829
|
+
|
|
3789
3830
|
function apply(ctx) {
|
|
3790
3831
|
ensureStyles();
|
|
3791
3832
|
if (ctx.locale) {
|
|
@@ -3799,23 +3840,103 @@ window.__ModuleLoader__.load({
|
|
|
3799
3840
|
};
|
|
3800
3841
|
}, "dsh-db-tool: dictionaries");
|
|
3801
3842
|
}
|
|
3802
|
-
|
|
3843
|
+
// 官方右侧栏两段式注册:先声明页签类型,再在席位里按类型 id 挂正文与标题。
|
|
3844
|
+
// 类型 id 同时是席位 key(宿主用 definition.id 作 entryKey),故正文/标题与 id 一致。
|
|
3845
|
+
if (!ctx.sidebarRightTabs || !ctx.slots) {
|
|
3846
|
+
console.warn("[dsh-db-tool] 宿主未提供 sidebarRightTabs / slots 服务,右侧栏面板不会挂载");
|
|
3847
|
+
return;
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
// 页签正文:宿主注入标准 props { sessionId, useTabInfo, useSessions? }。
|
|
3851
|
+
// useTabInfo().tab.visible 同时表达「页签激活」与「右侧栏展开/非后台会话」,可直接当 visible 用。
|
|
3852
|
+
function DbToolBody(props) {
|
|
3853
|
+
// useTabInfo 在页签记录未提交时会 throw。异常一旦冒到宿主 SlotErrorBoundary,
|
|
3854
|
+
// 该席位条目会被 abdicate 永久除名(宿主 entriesOfSlot 过滤后无 delete 路径),
|
|
3855
|
+
// 即数据库页签在本进程内彻底消失且无法自愈 —— 故就地降级,不让它冒出去。
|
|
3856
|
+
let info;
|
|
3857
|
+
try {
|
|
3858
|
+
info = props.useTabInfo();
|
|
3859
|
+
} catch (e) {
|
|
3860
|
+
return React.createElement("div", { className: "dbt-panel" },
|
|
3861
|
+
React.createElement("div", { className: "dbt-muted" }, t("tabUnavailable")));
|
|
3862
|
+
}
|
|
3863
|
+
const cwd = typeof props.useSessions === "function"
|
|
3864
|
+
? props.useSessions(function (snapshot) {
|
|
3865
|
+
const row = snapshot && snapshot.byId ? snapshot.byId[props.sessionId] : undefined;
|
|
3866
|
+
return row ? row.cwd : undefined;
|
|
3867
|
+
})
|
|
3868
|
+
: undefined;
|
|
3869
|
+
return React.createElement(DbToolRoot, {
|
|
3870
|
+
ctx: ctx,
|
|
3871
|
+
visible: info.tab.visible,
|
|
3872
|
+
scope: { sessionId: props.sessionId, cwd: cwd },
|
|
3873
|
+
});
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
// 页签标题(chip):数据库图标 + 当前语言标题,随语言切换重渲染。
|
|
3877
|
+
// 文案走 thunk(每次渲染现读 t()),不把语言拼进 key,避免切语言重建节点。
|
|
3878
|
+
function DbToolTitle() {
|
|
3879
|
+
React.useSyncExternalStore(
|
|
3880
|
+
React.useCallback(function (cb) {
|
|
3881
|
+
if (!ctx.locale || typeof ctx.locale.subscribe !== "function") return function () {};
|
|
3882
|
+
return ctx.locale.subscribe(cb);
|
|
3883
|
+
}, []),
|
|
3884
|
+
React.useCallback(function () {
|
|
3885
|
+
try {
|
|
3886
|
+
return ctx.locale && ctx.locale.getSnapshot ? ctx.locale.getSnapshot().active : currentLocale();
|
|
3887
|
+
} catch (e) { return currentLocale(); }
|
|
3888
|
+
}, []),
|
|
3889
|
+
function () { return "zh"; },
|
|
3890
|
+
);
|
|
3891
|
+
return React.createElement("span", {
|
|
3892
|
+
style: { display: "inline-flex", alignItems: "center", gap: 6 },
|
|
3893
|
+
}, icon(16), t("tabTitle"));
|
|
3894
|
+
}
|
|
3895
|
+
|
|
3803
3896
|
ctx.effect(function () {
|
|
3804
|
-
|
|
3897
|
+
const disposer = ctx.sidebarRightTabs.register({
|
|
3805
3898
|
id: TAB_ID,
|
|
3899
|
+
kind: TAB_ID,
|
|
3900
|
+
// 页签正文在切换页签 / 切会话 / 收起右侧栏时保持挂载:
|
|
3901
|
+
// Panel 的 view/projectPath/conns/grants 与 ConsoleView、TransferView 的状态全在 React 本地,
|
|
3902
|
+
// 默认的 keepMounted=false 会卸载正文,切回来就全丢。
|
|
3903
|
+
keepMounted: true,
|
|
3904
|
+
// 页签类型优先级:本插件无 patterns(不认领资源地址、是页类型),
|
|
3905
|
+
// priority 不参与路由排序,省略即 extension;显式声明只为可读性。
|
|
3906
|
+
priority: "extension",
|
|
3806
3907
|
title: function () { return t("tabTitle"); },
|
|
3807
|
-
|
|
3808
|
-
order
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3908
|
+
// guide 条目:右侧栏空态(无页签)时显示成入口卡片,点一下即打开本页签;
|
|
3909
|
+
// order 45 沿用旧工具栏的相对位置。
|
|
3910
|
+
guide: [{
|
|
3911
|
+
id: TAB_ID,
|
|
3912
|
+
order: 45,
|
|
3913
|
+
title: function () { return t("tabTitle"); },
|
|
3914
|
+
description: function () { return t("tabDesc"); },
|
|
3915
|
+
icon: DbIcon,
|
|
3916
|
+
}],
|
|
3917
|
+
});
|
|
3918
|
+
// 能力探测:keepMounted 自官方右侧栏 0.1.7-rc.1 起才有,旧版本会静默忽略它,
|
|
3919
|
+
// 表现为切页签/切会话后面板状态全部归零且无任何报错。读回注册结果比收紧 engines 更能覆盖实情。
|
|
3920
|
+
try {
|
|
3921
|
+
const registered = ctx.sidebarRightTabs.get && ctx.sidebarRightTabs.get(TAB_ID);
|
|
3922
|
+
if (registered && registered.keepMounted !== true) {
|
|
3923
|
+
console.warn("[dsh-db-tool] 当前 DSH 版本不支持 keepMounted,切换页签/会话后面板状态会丢失,请升级 DSH");
|
|
3924
|
+
}
|
|
3925
|
+
} catch (e) { /* 探测失败不影响主流程 */ }
|
|
3926
|
+
return disposer;
|
|
3927
|
+
}, "dsh-db-tool: register tab type");
|
|
3928
|
+
|
|
3929
|
+
ctx.effect(function () {
|
|
3930
|
+
return ctx.slots.inject("sidebar.right.pane.tab", function () {
|
|
3931
|
+
return ctx.slots.register({ name: "sidebar.right.pane.tab", key: TAB_ID }, DbToolBody);
|
|
3932
|
+
});
|
|
3933
|
+
}, "dsh-db-tool: register tab body");
|
|
3934
|
+
|
|
3935
|
+
ctx.effect(function () {
|
|
3936
|
+
return ctx.slots.inject("sidebar.right.pane.tab.title", function () {
|
|
3937
|
+
return ctx.slots.register({ name: "sidebar.right.pane.tab.title", key: TAB_ID }, DbToolTitle);
|
|
3817
3938
|
});
|
|
3818
|
-
}, "dsh-db-tool: register tab");
|
|
3939
|
+
}, "dsh-db-tool: register tab title");
|
|
3819
3940
|
}
|
|
3820
3941
|
exports.apply = apply;
|
|
3821
3942
|
exports.inject = inject;
|