dsh-db-tool 0.1.6 → 0.1.7
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/client/client.js +5 -1
- package/dist/http/index.js +2 -0
- package/dist/manager.js +18 -5
- package/dist/store/connections.js +27 -7
- package/package.json +1 -1
package/client/client.js
CHANGED
|
@@ -38,6 +38,7 @@ window.__ModuleLoader__.load({
|
|
|
38
38
|
fieldsPort: "端口",
|
|
39
39
|
fieldsUser: "用户名",
|
|
40
40
|
fieldsPassword: "密码",
|
|
41
|
+
passwordSaved: "已保存(留空保持不变)",
|
|
41
42
|
fieldsDatabase: "数据库名",
|
|
42
43
|
ssl: "启用 SSL",
|
|
43
44
|
test: "测试",
|
|
@@ -126,6 +127,7 @@ window.__ModuleLoader__.load({
|
|
|
126
127
|
fieldsPort: "Port",
|
|
127
128
|
fieldsUser: "User",
|
|
128
129
|
fieldsPassword: "Password",
|
|
130
|
+
passwordSaved: "Saved (leave blank to keep)",
|
|
129
131
|
fieldsDatabase: "Database",
|
|
130
132
|
ssl: "Enable SSL",
|
|
131
133
|
test: "Test",
|
|
@@ -486,6 +488,8 @@ window.__ModuleLoader__.load({
|
|
|
486
488
|
}
|
|
487
489
|
function draftBody() {
|
|
488
490
|
const body = { kind: form.kind, ssl: !!form.ssl };
|
|
491
|
+
// 编辑已有连接:透传 connId,服务端测试草稿时拼回已存机密(密码留空/url 未改动语义)
|
|
492
|
+
if (props.initial) body.connId = form.id;
|
|
489
493
|
// 编辑保存语义:url 模式未改动(仍等于回填的脱敏 url)→ 不发 url,保留 secrets 原值
|
|
490
494
|
const origUrl = props.initial && props.initial.mode === "url" ? props.initial.url : undefined;
|
|
491
495
|
if (form.mode === "url") {
|
|
@@ -547,7 +551,7 @@ window.__ModuleLoader__.load({
|
|
|
547
551
|
React.createElement("input", { placeholder: t("fieldsDatabase"), value: form.database, onChange: (e) => patch({ database: e.target.value }) })),
|
|
548
552
|
React.createElement("div", { className: "dbt-row" },
|
|
549
553
|
React.createElement("input", { placeholder: t("fieldsUser"), value: form.user, onChange: (e) => patch({ user: e.target.value }) }),
|
|
550
|
-
React.createElement("input", { type: "password", placeholder: t("fieldsPassword"), value: form.password, onChange: (e) => patch({ password: e.target.value }) })),
|
|
554
|
+
React.createElement("input", { type: "password", placeholder: props.initial && props.initial.hasPassword ? t("passwordSaved") : t("fieldsPassword"), value: form.password, onChange: (e) => patch({ password: e.target.value }) })),
|
|
551
555
|
),
|
|
552
556
|
// 分组 3:操作
|
|
553
557
|
React.createElement(
|
package/dist/http/index.js
CHANGED
|
@@ -160,6 +160,8 @@ async function route(req, res, service, path, q, opts) {
|
|
|
160
160
|
...(b['url'] !== undefined ? { url: str(b['url']) } : {}),
|
|
161
161
|
...(b['fields'] !== undefined ? { fields: b['fields'] } : {}),
|
|
162
162
|
...(ssl !== undefined ? { ssl } : {}),
|
|
163
|
+
// 编辑已有连接时透传:服务端拼回已存机密(留空密码/url 未改动的测试语义)
|
|
164
|
+
...(b['connId'] !== undefined ? { connId: optStr(b['connId']) } : {}),
|
|
163
165
|
}));
|
|
164
166
|
}
|
|
165
167
|
const connMatch = /^\/api\/connections\/([^/]+)(\/test)?$/.exec(path);
|
package/dist/manager.js
CHANGED
|
@@ -75,15 +75,28 @@ export class DbToolService {
|
|
|
75
75
|
/**
|
|
76
76
|
* 测试未保存的连接草稿(侧边栏「保存前测试」)。不落库、不写审计,
|
|
77
77
|
* 一次性适配器用完即关。url/fields 校验交给适配器层(与保存后测试同口径)。
|
|
78
|
+
* 编辑已有连接时传 connId:密码留空/url 未改动时自动拼回已存机密
|
|
79
|
+
* (仅注入本次测试,绝不回传客户端)。
|
|
78
80
|
*/
|
|
79
81
|
async testDraft(input) {
|
|
80
82
|
const rc = {
|
|
81
|
-
meta: { id: '(draft)', kind: input.kind, name: '(draft)' },
|
|
83
|
+
meta: { id: input.connId ?? '(draft)', kind: input.kind, name: '(draft)' },
|
|
82
84
|
};
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (input.
|
|
86
|
-
|
|
85
|
+
let url = input.url;
|
|
86
|
+
let fields = input.fields !== undefined ? { ...input.fields } : undefined;
|
|
87
|
+
if (input.connId) {
|
|
88
|
+
// 已存连接:编辑时客户端不发旧机密(留空语义),这里从 secrets 拼回
|
|
89
|
+
const sec = this.store.secrets.get(input.connId);
|
|
90
|
+
if (url === undefined && sec?.url !== undefined)
|
|
91
|
+
url = sec.url;
|
|
92
|
+
if (fields !== undefined && sec?.password !== undefined && fields.password === undefined) {
|
|
93
|
+
fields.password = sec.password;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (url !== undefined)
|
|
97
|
+
rc.url = url;
|
|
98
|
+
if (fields !== undefined)
|
|
99
|
+
rc.fields = fields;
|
|
87
100
|
if (input.ssl !== undefined)
|
|
88
101
|
rc.ssl = input.ssl;
|
|
89
102
|
try {
|
|
@@ -35,8 +35,9 @@ function splitPassword(fields) {
|
|
|
35
35
|
const { [PASSWORD_KEY]: password, ...clean } = fields;
|
|
36
36
|
return { clean, password: password };
|
|
37
37
|
}
|
|
38
|
-
/** ConnRecord → 用户可见的 ConnectionMeta(契约见 lib/adapters/types.ts
|
|
39
|
-
|
|
38
|
+
/** ConnRecord → 用户可见的 ConnectionMeta(契约见 lib/adapters/types.ts)。
|
|
39
|
+
* hasPassword 只回布尔指示,明文绝不出库。 */
|
|
40
|
+
function toMeta(rec, hasPassword = false) {
|
|
40
41
|
const meta = { id: rec.id, kind: rec.kind };
|
|
41
42
|
if (rec.name !== undefined)
|
|
42
43
|
meta.name = rec.name;
|
|
@@ -47,6 +48,8 @@ function toMeta(rec) {
|
|
|
47
48
|
else {
|
|
48
49
|
meta.mode = 'fields';
|
|
49
50
|
}
|
|
51
|
+
if (hasPassword)
|
|
52
|
+
meta.hasPassword = true;
|
|
50
53
|
if (rec.fields) {
|
|
51
54
|
const { host, port, user, database } = rec.fields;
|
|
52
55
|
if (typeof host === 'string')
|
|
@@ -78,12 +81,16 @@ export class ConnectionStore {
|
|
|
78
81
|
findRec(data, id) {
|
|
79
82
|
return data.connections.find((c) => c.id === id);
|
|
80
83
|
}
|
|
84
|
+
/** 该连接是否已存密码(布尔指示,不读明文) */
|
|
85
|
+
hasPassword(id) {
|
|
86
|
+
return this.secrets.get(id)?.password !== undefined;
|
|
87
|
+
}
|
|
81
88
|
list() {
|
|
82
|
-
return this.load().connections.map(toMeta);
|
|
89
|
+
return this.load().connections.map((r) => toMeta(r, this.hasPassword(r.id)));
|
|
83
90
|
}
|
|
84
91
|
get(id) {
|
|
85
92
|
const rec = this.findRec(this.load(), id);
|
|
86
|
-
return rec ? toMeta(rec) : undefined;
|
|
93
|
+
return rec ? toMeta(rec, this.hasPassword(id)) : undefined;
|
|
87
94
|
}
|
|
88
95
|
/** 新建连接;id 已存在时抛错 */
|
|
89
96
|
create(input) {
|
|
@@ -110,7 +117,7 @@ export class ConnectionStore {
|
|
|
110
117
|
this.secrets.set(input.id, secretPatch);
|
|
111
118
|
data.connections.push(rec);
|
|
112
119
|
this.save(data);
|
|
113
|
-
return toMeta(rec);
|
|
120
|
+
return toMeta(rec, this.hasPassword(input.id));
|
|
114
121
|
}
|
|
115
122
|
/** 部分更新;不存在返回 undefined。url/fields 变更时同步拆分 secrets */
|
|
116
123
|
update(id, patch) {
|
|
@@ -134,11 +141,24 @@ export class ConnectionStore {
|
|
|
134
141
|
}
|
|
135
142
|
if (patch.clearUrl && rec.urlSafe !== undefined) {
|
|
136
143
|
delete rec.urlSafe;
|
|
144
|
+
// 密码迁移:原 url 内嵌密码 → fields 密码(切方式后保持可连,编辑留空即保留)
|
|
145
|
+
const secUrl = this.secrets.get(id)?.url;
|
|
146
|
+
if (secUrl !== undefined) {
|
|
147
|
+
try {
|
|
148
|
+
const u = new URL(secUrl);
|
|
149
|
+
if (u.password && this.secrets.get(id)?.password === undefined) {
|
|
150
|
+
this.secrets.set(id, { password: decodeURIComponent(u.password) });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
/* 非 URL 形态,忽略迁移 */
|
|
155
|
+
}
|
|
156
|
+
}
|
|
137
157
|
// set 为合并写;显式置 undefined 经 JSON 序列化后等效删除该键
|
|
138
158
|
this.secrets.set(id, { url: undefined });
|
|
139
159
|
}
|
|
140
160
|
this.save(data);
|
|
141
|
-
return toMeta(rec);
|
|
161
|
+
return toMeta(rec, this.hasPassword(id));
|
|
142
162
|
}
|
|
143
163
|
/** 删除连接,级联删除 secrets 与该连接的所有项目授权。
|
|
144
164
|
* 顺序按"失败开放"原则:先删权限(grants),再删机密(secrets),最后改
|
|
@@ -162,7 +182,7 @@ export class ConnectionStore {
|
|
|
162
182
|
const rec = this.findRec(this.load(), id);
|
|
163
183
|
if (!rec)
|
|
164
184
|
throw new Error(`连接不存在: ${id}`);
|
|
165
|
-
const meta = toMeta(rec);
|
|
185
|
+
const meta = toMeta(rec, this.secrets.get(id)?.password !== undefined);
|
|
166
186
|
const rc = { meta };
|
|
167
187
|
const sec = this.secrets.get(id);
|
|
168
188
|
if (sec?.url !== undefined)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-db-tool",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "DSH community plugin: chat-operated multi-database admin tool with sidebar management, project-scoped grants, ro/rw modes and dangerous-operation confirmation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|