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 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, rename or delete it from the group header, and drag
227
- a source row onto a group to move it there (drag it onto **未分组** to clear the
228
- label). The list renders one section per group, sorted by name with the ungrouped
229
- sources last; while no group exists the list keeps its flat look.
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.rename': 'Rename',
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.rename': '重命名',
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: baseline;
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 320px;
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', onClick: () => toggleSelected(source.id) },
2409
- h(
2410
- 'div',
2411
- { className: 'dbmcp-item-name' },
2412
- h('span', null, source.name || source.id),
2413
- h('span', { className: 'dbmcp-pill' }, source.kind),
2414
- source.isDefault ? h('span', { className: 'dbmcp-pill' }, t('sources.default')) : null,
2415
- h('span', { className: 'dbmcp-pill' }, source.readOnly ? t('sources.readonly') : t('sources.readwrite')),
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('span', { className: 'dbmcp-group-name' }, name || t('group.unassigned')),
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
  {
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-db-mcp",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "description": "DSH database plugin: a data-source management panel plus a Python MCP server that exposes those sources to the agent.",
5
5
  "keywords": [
6
6
  "dsh",
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.4'
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'