dsh-db-mcp 0.6.4 → 0.6.5
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/README.md +6 -4
- package/client.js +116 -19
- package/docs/requirements.md +23 -0
- package/package.json +1 -1
- package/src/host.js +1 -1
package/README.md
CHANGED
|
@@ -223,10 +223,12 @@ open reads the live catalog.
|
|
|
223
223
|
|
|
224
224
|
Every source carries an optional **group** label (`group` in the registry,
|
|
225
225
|
editable under *More settings*). Groups are user-owned: type a name in the
|
|
226
|
-
**新建分组** box to create one,
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
226
|
+
**新建分组** box to create one, **double-click a group name to rename it** (or the
|
|
227
|
+
trash button to delete the group), and drag a source row onto a group to move it
|
|
228
|
+
there (drag it onto **未分组** to clear the label). Double-clicking a source name
|
|
229
|
+
renames just that source — its id never changes. The list renders one section per
|
|
230
|
+
group, sorted by name with the ungrouped sources last; while no group exists the
|
|
231
|
+
list keeps its flat look.
|
|
230
232
|
|
|
231
233
|
Each group header carries a select-all box, so sources inside a group take part in
|
|
232
234
|
bulk work exactly like loose ones. Tick any row (or click its text) to open the
|
package/client.js
CHANGED
|
@@ -182,7 +182,7 @@ window.__ModuleLoader__.load({
|
|
|
182
182
|
'group.new': 'New group',
|
|
183
183
|
'group.newPlaceholder': 'Group name',
|
|
184
184
|
'group.add': 'Add group',
|
|
185
|
-
'group.
|
|
185
|
+
'group.renameHint': 'Double-click to rename',
|
|
186
186
|
'group.save': 'Save',
|
|
187
187
|
'group.cancel': 'Cancel',
|
|
188
188
|
'group.delete': 'Delete',
|
|
@@ -354,7 +354,7 @@ window.__ModuleLoader__.load({
|
|
|
354
354
|
'group.new': '新建分组',
|
|
355
355
|
'group.newPlaceholder': '分组名称',
|
|
356
356
|
'group.add': '新建',
|
|
357
|
-
'group.
|
|
357
|
+
'group.renameHint': '双击重命名',
|
|
358
358
|
'group.save': '保存',
|
|
359
359
|
'group.cancel': '取消',
|
|
360
360
|
'group.delete': '删除',
|
|
@@ -852,7 +852,7 @@ window.__ModuleLoader__.load({
|
|
|
852
852
|
}
|
|
853
853
|
.dbmcp-group-head {
|
|
854
854
|
display: flex;
|
|
855
|
-
align-items:
|
|
855
|
+
align-items: center;
|
|
856
856
|
gap: 8px;
|
|
857
857
|
padding: 0 2px;
|
|
858
858
|
border-bottom: 1px solid var(--dsw-alias-border-l1);
|
|
@@ -883,7 +883,16 @@ window.__ModuleLoader__.load({
|
|
|
883
883
|
display: flex;
|
|
884
884
|
align-items: center;
|
|
885
885
|
gap: 6px;
|
|
886
|
-
flex: 0 1
|
|
886
|
+
flex: 0 1 auto;
|
|
887
|
+
}
|
|
888
|
+
.dbmcp-group-edit .dbmcp-input {
|
|
889
|
+
width: 170px;
|
|
890
|
+
}
|
|
891
|
+
.dbmcp-group-edit .dbmcp-btn,
|
|
892
|
+
.dbmcp-item-rename .dbmcp-btn {
|
|
893
|
+
flex: 0 0 auto;
|
|
894
|
+
white-space: nowrap;
|
|
895
|
+
padding: 3px 10px;
|
|
887
896
|
}
|
|
888
897
|
.dbmcp-group.is-over {
|
|
889
898
|
outline: 1px dashed var(--dsw-alias-brand-primary);
|
|
@@ -960,6 +969,19 @@ window.__ModuleLoader__.load({
|
|
|
960
969
|
word-break: break-all;
|
|
961
970
|
margin-top: 2px;
|
|
962
971
|
}
|
|
972
|
+
.dbmcp-item-name > .is-renamable,
|
|
973
|
+
.dbmcp-group-name.is-renamable {
|
|
974
|
+
cursor: text;
|
|
975
|
+
}
|
|
976
|
+
.dbmcp-item-rename {
|
|
977
|
+
display: flex;
|
|
978
|
+
align-items: center;
|
|
979
|
+
gap: 6px;
|
|
980
|
+
}
|
|
981
|
+
.dbmcp-item-rename .dbmcp-input {
|
|
982
|
+
flex: 0 0 200px;
|
|
983
|
+
width: 200px;
|
|
984
|
+
}
|
|
963
985
|
.dbmcp-item-actions {
|
|
964
986
|
display: flex;
|
|
965
987
|
align-items: center;
|
|
@@ -1226,6 +1248,7 @@ window.__ModuleLoader__.load({
|
|
|
1226
1248
|
disabled: props.disabled === true,
|
|
1227
1249
|
title: props.title || undefined,
|
|
1228
1250
|
onChange: (event) => props.onChange(event.target.value),
|
|
1251
|
+
onKeyDown: props.onKeyDown || undefined,
|
|
1229
1252
|
})
|
|
1230
1253
|
}
|
|
1231
1254
|
|
|
@@ -2128,6 +2151,7 @@ window.__ModuleLoader__.load({
|
|
|
2128
2151
|
const [pendingGroupDelete, setPendingGroupDelete] = React.useState(null)
|
|
2129
2152
|
const [draggingId, setDraggingId] = React.useState(null)
|
|
2130
2153
|
const [dragOverGroup, setDragOverGroup] = React.useState(null)
|
|
2154
|
+
const [renamingSource, setRenamingSource] = React.useState(null)
|
|
2131
2155
|
|
|
2132
2156
|
/** Switching a tab only swaps the card below; the chrome never moves. */
|
|
2133
2157
|
const selectTab = (id) => {
|
|
@@ -2339,6 +2363,34 @@ window.__ModuleLoader__.load({
|
|
|
2339
2363
|
}
|
|
2340
2364
|
}
|
|
2341
2365
|
|
|
2366
|
+
/** A source keeps its id; double-clicking the name only changes the label. */
|
|
2367
|
+
const renameSource = async () => {
|
|
2368
|
+
if (!renamingSource) return
|
|
2369
|
+
const name = String(renamingSource.name || '').trim()
|
|
2370
|
+
const source = sources.find((item) => item.id === renamingSource.id)
|
|
2371
|
+
if (!source || !name) {
|
|
2372
|
+
setRenamingSource(null)
|
|
2373
|
+
return
|
|
2374
|
+
}
|
|
2375
|
+
try {
|
|
2376
|
+
await apiPost('/datasources/save', {
|
|
2377
|
+
id: source.id,
|
|
2378
|
+
name,
|
|
2379
|
+
kind: source.kind,
|
|
2380
|
+
readOnly: source.readOnly,
|
|
2381
|
+
isDefault: source.isDefault,
|
|
2382
|
+
tags: source.tags,
|
|
2383
|
+
group: source.group,
|
|
2384
|
+
config: source.config,
|
|
2385
|
+
})
|
|
2386
|
+
setRenamingSource(null)
|
|
2387
|
+
setNotice({ message: t('notice.saved').replace('{name}', name), isError: false })
|
|
2388
|
+
load()
|
|
2389
|
+
} catch (error) {
|
|
2390
|
+
reportError(error)
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2342
2394
|
const removeGroup = async (name) => {
|
|
2343
2395
|
if (pendingGroupDelete !== name) {
|
|
2344
2396
|
setPendingGroupDelete(name)
|
|
@@ -2405,15 +2457,52 @@ window.__ModuleLoader__.load({
|
|
|
2405
2457
|
),
|
|
2406
2458
|
h(
|
|
2407
2459
|
'div',
|
|
2408
|
-
{ className: 'dbmcp-item-main'
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2460
|
+
{ className: 'dbmcp-item-main' },
|
|
2461
|
+
renamingSource && renamingSource.id === source.id
|
|
2462
|
+
? h(
|
|
2463
|
+
'form',
|
|
2464
|
+
{
|
|
2465
|
+
className: 'dbmcp-item-rename',
|
|
2466
|
+
onClick: (event) => event.stopPropagation(),
|
|
2467
|
+
onSubmit: (event) => {
|
|
2468
|
+
event.preventDefault()
|
|
2469
|
+
void renameSource()
|
|
2470
|
+
},
|
|
2471
|
+
},
|
|
2472
|
+
h(TextInput, {
|
|
2473
|
+
value: renamingSource.name,
|
|
2474
|
+
onChange: (value) => setRenamingSource({ id: source.id, name: value }),
|
|
2475
|
+
autoFocus: true,
|
|
2476
|
+
onKeyDown: (event) => {
|
|
2477
|
+
if (event.key === 'Escape') setRenamingSource(null)
|
|
2478
|
+
},
|
|
2479
|
+
}),
|
|
2480
|
+
h('button', { type: 'submit', className: 'dbmcp-btn is-small is-primary' }, t('group.save')),
|
|
2481
|
+
h(
|
|
2482
|
+
'button',
|
|
2483
|
+
{ type: 'button', className: 'dbmcp-btn is-small', onClick: () => setRenamingSource(null) },
|
|
2484
|
+
t('group.cancel'),
|
|
2485
|
+
),
|
|
2486
|
+
)
|
|
2487
|
+
: h(
|
|
2488
|
+
'div',
|
|
2489
|
+
{ className: 'dbmcp-item-name' },
|
|
2490
|
+
h(
|
|
2491
|
+
'span',
|
|
2492
|
+
{
|
|
2493
|
+
className: 'is-renamable',
|
|
2494
|
+
title: t('group.renameHint'),
|
|
2495
|
+
onDoubleClick: (event) => {
|
|
2496
|
+
event.stopPropagation()
|
|
2497
|
+
setRenamingSource({ id: source.id, name: source.name || source.id })
|
|
2498
|
+
},
|
|
2499
|
+
},
|
|
2500
|
+
source.name || source.id,
|
|
2501
|
+
),
|
|
2502
|
+
h('span', { className: 'dbmcp-pill' }, source.kind),
|
|
2503
|
+
source.isDefault ? h('span', { className: 'dbmcp-pill' }, t('sources.default')) : null,
|
|
2504
|
+
h('span', { className: 'dbmcp-pill' }, source.readOnly ? t('sources.readonly') : t('sources.readwrite')),
|
|
2505
|
+
),
|
|
2417
2506
|
h('div', { className: 'dbmcp-item-target' }, [source.target, source.id].filter(Boolean).join(' · ')),
|
|
2418
2507
|
h(
|
|
2419
2508
|
'div',
|
|
@@ -2503,22 +2592,30 @@ window.__ModuleLoader__.load({
|
|
|
2503
2592
|
value: editingGroup.name,
|
|
2504
2593
|
onChange: (value) => setEditingGroup({ from: name, name: value }),
|
|
2505
2594
|
autoFocus: true,
|
|
2595
|
+
onKeyDown: (event) => {
|
|
2596
|
+
if (event.key === 'Escape') setEditingGroup(null)
|
|
2597
|
+
},
|
|
2506
2598
|
}),
|
|
2507
2599
|
h('button', { type: 'submit', className: 'dbmcp-btn is-small is-primary' }, t('group.save')),
|
|
2508
2600
|
h('button', { type: 'button', className: 'dbmcp-btn is-small', onClick: () => setEditingGroup(null) }, t('group.cancel')),
|
|
2509
2601
|
)
|
|
2510
|
-
: h(
|
|
2602
|
+
: h(
|
|
2603
|
+
'span',
|
|
2604
|
+
{
|
|
2605
|
+
className: 'dbmcp-group-name' + (name ? ' is-renamable' : ''),
|
|
2606
|
+
title: name ? t('group.renameHint') : undefined,
|
|
2607
|
+
onDoubleClick: () => {
|
|
2608
|
+
if (name) setEditingGroup({ from: name, name })
|
|
2609
|
+
},
|
|
2610
|
+
},
|
|
2611
|
+
name || t('group.unassigned'),
|
|
2612
|
+
),
|
|
2511
2613
|
h('span', { className: 'dbmcp-hint' }, t('group.count').replace('{n}', String(items.length))),
|
|
2512
2614
|
name === '' || renaming
|
|
2513
2615
|
? null
|
|
2514
2616
|
: h(
|
|
2515
2617
|
'span',
|
|
2516
2618
|
{ className: 'dbmcp-group-actions' },
|
|
2517
|
-
h(
|
|
2518
|
-
'button',
|
|
2519
|
-
{ type: 'button', className: 'dbmcp-btn is-small', onClick: () => setEditingGroup({ from: name, name }) },
|
|
2520
|
-
t('group.rename'),
|
|
2521
|
-
),
|
|
2522
2619
|
h(
|
|
2523
2620
|
'button',
|
|
2524
2621
|
{
|
package/docs/requirements.md
CHANGED
|
@@ -958,3 +958,26 @@ PostgreSQL/MySQL/Oracle 只做 TCP 端口探测,无法证明口令与服务的
|
|
|
958
958
|
- `dev/selftest.mjs` 新增 5 项断言,共 **46/46**:扫描标记重复、重复导入被跳过、删掉重复项后可再次导入、第二次导入被跳过、`sourceIdentity` 对大小写与端口字符串不敏感。
|
|
959
959
|
- 其余:render 37/37、parse 24/24、i18n 12/12、p5 15/15、mcp 19/19、shims ok。
|
|
960
960
|
- 线上:`GET /dbeaver/scan` 两条连接均为 `alreadyImported: true`(sqlite → `dbeaver-sample-database-sqlite-1`,mysql → `mysql`)。
|
|
961
|
+
|
|
962
|
+
## 27. 0.6.5:双击重命名(分组与数据源)
|
|
963
|
+
|
|
964
|
+
### 27.0 0.6.5 修订
|
|
965
|
+
- 首次实现保留了「点正文即选中」,结果是双击组名/数据源名会先切换选中、再进入编辑,观感是「双击只选中了,没重命名」。现已移除行正文的 `onClick`,选中统一由复选框负责。
|
|
966
|
+
|
|
967
|
+
|
|
968
|
+
### 27.1 交互
|
|
969
|
+
- 分组头不再有「重命名」按钮,只保留「删除」。双击组名进入内联编辑,回车保存、Esc 取消。
|
|
970
|
+
- 数据源名称同理:双击名称进入内联编辑,只改显示名称,id 不变;提交走 `POST /datasources/save`,其余字段原样带回(口令仍由 secrets.json 持有,不参与往返)。
|
|
971
|
+
- 「未分组」是投放目标,不是真实分组,因此不可重命名。
|
|
972
|
+
- 行正文不再承担「点一下就选中」的职责:选中只走复选框(含组头全选),否则双击必然先触发两次 `click`,与重命名抢事件。
|
|
973
|
+
|
|
974
|
+
### 27.2 实现
|
|
975
|
+
- `TextInput` 增加 `onKeyDown` 透传,用于 Escape 取消。
|
|
976
|
+
- `renderGroup` 的组名改为 `span.dbmcp-group-name.is-renamable` + `onDoubleClick`,标题提示用 `group.renameHint`。
|
|
977
|
+
- `renderItem` 在 `renamingSource.id === source.id` 时渲染 `form.dbmcp-item-rename`(输入框 + 保存),否则渲染可双击的名称。
|
|
978
|
+
- 新增状态 `renamingSource`;新增 `renameSource()`;CSS 增加 `.is-renamable { cursor: text }` 与 `.dbmcp-item-rename` 布局。
|
|
979
|
+
- 字典:删除 `group.rename`(按钮文案),新增 `group.renameHint`(`Double-click to rename` / `双击重命名`),避免死键。
|
|
980
|
+
|
|
981
|
+
### 27.3 验证
|
|
982
|
+
- `node --check client.js` 通过;i18n 12/12(无死键)、render 37/37、selftest 46/46、parse 24/24、p5 15/15、shims ok。
|
|
983
|
+
- 版本 0.6.5。
|
package/package.json
CHANGED
package/src/host.js
CHANGED
|
@@ -33,7 +33,7 @@ const { createBridge } = await import(new URL(`./tools.js?v=${GENERATION}`, impo
|
|
|
33
33
|
|
|
34
34
|
const API_PREFIX = '/plugins/db-mcp/api'
|
|
35
35
|
const PLUGIN_NAME = 'dsh-db-mcp'
|
|
36
|
-
const PLUGIN_VERSION = '0.6.
|
|
36
|
+
const PLUGIN_VERSION = '0.6.5'
|
|
37
37
|
const SCHEMA_VERSION = 1
|
|
38
38
|
const REGISTRY_FILE = 'datasources.json'
|
|
39
39
|
const SECRETS_FILE = 'secrets.json'
|