dsh-db-tool 0.1.3 → 0.1.4
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 +46 -7
- package/dist/adapters/sql-shared/pg-like.js +8 -0
- package/dist/http/index.js +3 -0
- package/dist/manager.js +15 -0
- package/package.json +1 -1
- package/skills/db-admin/SKILL.md +1 -0
package/client/client.js
CHANGED
|
@@ -66,6 +66,7 @@ window.__ModuleLoader__.load({
|
|
|
66
66
|
modeRw: "读写",
|
|
67
67
|
// 浏览
|
|
68
68
|
noDatabases: "无可用数据库",
|
|
69
|
+
noSchemas: "无模式",
|
|
69
70
|
noTables: "无表",
|
|
70
71
|
loadFailed: "加载失败",
|
|
71
72
|
structure: "结构",
|
|
@@ -150,6 +151,7 @@ window.__ModuleLoader__.load({
|
|
|
150
151
|
modeRo: "read-only ro",
|
|
151
152
|
modeRw: "read-write rw",
|
|
152
153
|
noDatabases: "No databases available",
|
|
154
|
+
noSchemas: "No schemas",
|
|
153
155
|
noTables: "No tables",
|
|
154
156
|
loadFailed: "Failed to load",
|
|
155
157
|
structure: "Structure",
|
|
@@ -708,7 +710,8 @@ window.__ModuleLoader__.load({
|
|
|
708
710
|
const [loading, setLoading] = React.useState({});
|
|
709
711
|
const [error, setError] = React.useState({}); // 树节点加载失败信息(就地显示,可点重试)
|
|
710
712
|
const [dbs, setDbs] = React.useState({}); // connId -> string[]
|
|
711
|
-
const [
|
|
713
|
+
const [schemasMap, setSchemasMap] = React.useState({}); // "<connId>/<db>" -> string[](PG/GaussDB 库内 schema 层)
|
|
714
|
+
const [tablesMap, setTablesMap] = React.useState({}); // "<connId>/<db|schema>" -> TableInfo[]
|
|
712
715
|
const [sel, setSel] = React.useState(null); // {connId, db, table: TableInfo}
|
|
713
716
|
const [schema, setSchema] = React.useState([]);
|
|
714
717
|
const [preview, setPreview] = React.useState(null); // QueryResult
|
|
@@ -718,7 +721,7 @@ window.__ModuleLoader__.load({
|
|
|
718
721
|
|
|
719
722
|
// 会话项目切换 / 连接列表变化时清空树缓存,避免陈旧授权下的旧数据
|
|
720
723
|
React.useEffect(() => {
|
|
721
|
-
setOpen({}); setLoading({}); setError({}); setDbs({}); setTablesMap({}); setSel(null); setSchema([]); setPreview(null);
|
|
724
|
+
setOpen({}); setLoading({}); setError({}); setDbs({}); setSchemasMap({}); setTablesMap({}); setSel(null); setSchema([]); setPreview(null);
|
|
722
725
|
}, [projectPath]);
|
|
723
726
|
|
|
724
727
|
function toggle(key, load) {
|
|
@@ -744,11 +747,23 @@ window.__ModuleLoader__.load({
|
|
|
744
747
|
setDbs((m) => Object.assign({}, m, { [c.id]: list || [] }));
|
|
745
748
|
}));
|
|
746
749
|
}
|
|
750
|
+
// PG/GaussDB 官方层级为 数据库 → 模式(schema) → 表:库节点下先列 schema 再列表
|
|
751
|
+
const HAS_SCHEMAS = { postgresql: true, gaussdb: true };
|
|
747
752
|
function toggleDb(c, d) {
|
|
748
753
|
if (!projectPath) return;
|
|
754
|
+
const useSchemas = !!HAS_SCHEMAS[c.kind];
|
|
755
|
+
const what = useSchemas ? "schemas" : "tables";
|
|
749
756
|
toggle("d:" + c.id + "/" + d, () =>
|
|
750
|
-
api(
|
|
751
|
-
|
|
757
|
+
api(what + qs({ project: projectPath, connId: c.id, database: d })).then((list) => {
|
|
758
|
+
const setter = useSchemas ? setSchemasMap : setTablesMap;
|
|
759
|
+
setter((m) => Object.assign({}, m, { [c.id + "/" + d]: list || [] }));
|
|
760
|
+
}));
|
|
761
|
+
}
|
|
762
|
+
function toggleSchema(c, d, s) {
|
|
763
|
+
if (!projectPath) return;
|
|
764
|
+
toggle("s:" + c.id + "/" + d + "/" + s, () =>
|
|
765
|
+
api("tables" + qs({ project: projectPath, connId: c.id, database: s })).then((list) => {
|
|
766
|
+
setTablesMap((m) => Object.assign({}, m, { [c.id + "/" + s]: list || [] }));
|
|
752
767
|
}));
|
|
753
768
|
}
|
|
754
769
|
// 请求序号守卫:快速切换选中表/翻页时,丢弃晚到的旧响应,防止旧数据覆盖新选中项
|
|
@@ -795,19 +810,43 @@ window.__ModuleLoader__.load({
|
|
|
795
810
|
if (!list) continue;
|
|
796
811
|
if (list.length === 0) { treeRows.push(treerow(ck + ":e", 1, false, true, t("noDatabases"))); continue; }
|
|
797
812
|
for (const d of list) {
|
|
813
|
+
const useSchemas = !!HAS_SCHEMAS[c.kind];
|
|
798
814
|
const dk = "d:" + c.id + "/" + d;
|
|
799
815
|
const dOpen = !!open[dk];
|
|
800
816
|
treeRows.push(treerow(dk, 1, dOpen, false, d, () => toggleDb(c, d)));
|
|
801
817
|
if (!dOpen) continue;
|
|
802
|
-
const tkey = c.id + "/" + d;
|
|
803
|
-
const tlist = tablesMap[tkey];
|
|
804
818
|
if (loading[dk]) { treeRows.push(treerow(dk + ":l", 2, false, true, "…")); continue; }
|
|
805
819
|
if (error[dk]) { treeRows.push(treerow(dk + ":x", 2, false, true, t("loadFailed") + ":" + error[dk], () => retry(dk, () => toggleDb(c, d)))); continue; }
|
|
820
|
+
// PG/GaussDB:库 → 模式 → 表(三层,Navicat 官方层级);schema 节点复用 open/loading/error 状态
|
|
821
|
+
if (useSchemas) {
|
|
822
|
+
const slist = schemasMap[c.id + "/" + d];
|
|
823
|
+
if (!slist) continue;
|
|
824
|
+
if (slist.length === 0) { treeRows.push(treerow(dk + ":e", 2, false, true, t("noSchemas"))); continue; }
|
|
825
|
+
for (const s of slist) {
|
|
826
|
+
const sk = "s:" + c.id + "/" + d + "/" + s;
|
|
827
|
+
const sOpen = !!open[sk];
|
|
828
|
+
treeRows.push(treerow(sk, 2, sOpen, false, s, () => toggleSchema(c, d, s)));
|
|
829
|
+
if (!sOpen) continue;
|
|
830
|
+
const tlist = tablesMap[c.id + "/" + s];
|
|
831
|
+
if (loading[sk]) { treeRows.push(treerow(sk + ":l", 3, false, true, "…")); continue; }
|
|
832
|
+
if (error[sk]) { treeRows.push(treerow(sk + ":x", 3, false, true, t("loadFailed") + ":" + error[sk], () => retry(sk, () => toggleSchema(c, d, s)))); continue; }
|
|
833
|
+
if (!tlist) continue;
|
|
834
|
+
if (tlist.length === 0) { treeRows.push(treerow(sk + ":e", 3, false, true, t("noTables"))); continue; }
|
|
835
|
+
for (const tb of tlist) {
|
|
836
|
+
const active = !!sel && sel.connId === c.id && sel.db === s && sel.table.name === tb.name;
|
|
837
|
+
treeRows.push(treerow("t:" + sk + "/" + tb.name, 3, false, true,
|
|
838
|
+
tb.name + (tb.type && tb.type !== "table" ? " · " + tb.type : ""),
|
|
839
|
+
() => setSel({ connId: c.id, db: s, table: tb }), active));
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
const tlist = tablesMap[c.id + "/" + d];
|
|
806
845
|
if (!tlist) continue;
|
|
807
846
|
if (tlist.length === 0) { treeRows.push(treerow(dk + ":e", 2, false, true, t("noTables"))); continue; }
|
|
808
847
|
for (const tb of tlist) {
|
|
809
848
|
const active = !!sel && sel.connId === c.id && sel.db === d && sel.table.name === tb.name;
|
|
810
|
-
treeRows.push(treerow("t:" +
|
|
849
|
+
treeRows.push(treerow("t:" + c.id + "/" + d + "/" + tb.name, 2, false, true,
|
|
811
850
|
tb.name + (tb.type && tb.type !== "table" ? " · " + tb.type : ""),
|
|
812
851
|
() => setSel({ connId: c.id, db: d, table: tb }), active));
|
|
813
852
|
}
|
|
@@ -116,6 +116,14 @@ export async function createPgLikeAdapter(kind, Driver, conn, opts) {
|
|
|
116
116
|
const res = await pool.query('SELECT datname FROM pg_database WHERE datistemplate = false AND datallowconn = true ORDER BY datname');
|
|
117
117
|
return res.rows.map((r) => String(r.datname));
|
|
118
118
|
}),
|
|
119
|
+
// 库内 schema 清单(Navicat 官方层级:数据库 → 模式 → 表)。
|
|
120
|
+
// PG 连接固定单库,database 参数无法跨库,忽略;pg_* 前缀覆盖 pg_catalog/pg_toast/pg_temp 系。
|
|
121
|
+
listSchemas: () => humanize(`${kind} 列出模式`, async () => {
|
|
122
|
+
const res = await pool.query(`SELECT nspname FROM pg_catalog.pg_namespace
|
|
123
|
+
WHERE nspname NOT LIKE 'pg\\_%' AND nspname <> 'information_schema'
|
|
124
|
+
ORDER BY nspname`);
|
|
125
|
+
return res.rows.map((r) => String(r.nspname));
|
|
126
|
+
}),
|
|
119
127
|
listTables: (database) => humanize(`${kind} 列出表`, async () => {
|
|
120
128
|
const schema = database ? assertIdent(database, 'schema') : defaultSchema();
|
|
121
129
|
const res = await pool.query(`SELECT table_name, table_type FROM information_schema.tables
|
package/dist/http/index.js
CHANGED
|
@@ -210,6 +210,9 @@ async function route(req, res, service, path, q, opts) {
|
|
|
210
210
|
if (path === '/api/tables' && req.method === 'GET') {
|
|
211
211
|
return sendOk(res, await service.tables(projectOf(q), required(q, 'connId'), q.get('database') ?? undefined));
|
|
212
212
|
}
|
|
213
|
+
if (path === '/api/schemas' && req.method === 'GET') {
|
|
214
|
+
return sendOk(res, await service.schemas(projectOf(q), required(q, 'connId'), q.get('database') ?? undefined));
|
|
215
|
+
}
|
|
213
216
|
if (path === '/api/schema' && req.method === 'GET') {
|
|
214
217
|
return sendOk(res, await service.schema(projectOf(q), required(q, 'connId'), required(q, 'table'), q.get('database') ?? undefined));
|
|
215
218
|
}
|
package/dist/manager.js
CHANGED
|
@@ -146,6 +146,21 @@ export class DbToolService {
|
|
|
146
146
|
throw this.toDriverError(e);
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
|
+
/** 库内 schema 清单(仅 PG/GaussDB 等三层语义适配器实现;ro 即可)。 */
|
|
150
|
+
async schemas(projectPath, connId, database) {
|
|
151
|
+
const { key, adapter } = await this.authorize(projectPath, connId, false);
|
|
152
|
+
if (!adapter.listSchemas)
|
|
153
|
+
return [];
|
|
154
|
+
try {
|
|
155
|
+
const result = await adapter.listSchemas(database);
|
|
156
|
+
this.audit(key, connId, 'schemas', database ?? 'default', 'none', false, true);
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
160
|
+
this.audit(key, connId, 'schemas', database ?? 'default', 'none', false, false, this.errText(e));
|
|
161
|
+
throw this.toDriverError(e);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
149
164
|
async schema(projectPath, connId, table, database) {
|
|
150
165
|
const t = assertNonEmpty(table, 'table');
|
|
151
166
|
const { key, adapter } = await this.authorize(projectPath, connId, false);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-db-tool",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
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",
|
package/skills/db-admin/SKILL.md
CHANGED
|
@@ -57,6 +57,7 @@ DatabaseManager({ action: "schema", conn_id: "c1", database: "shop", table: "use
|
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
- 表列表返回 `TableInfo[]`:`{name, type?, comment?, database?}`(Redis 的 type 为 hash|list|set|zset|string|stream)。
|
|
60
|
+
- PostgreSQL/GaussDB 为「数据库 → 模式(schema) → 表」三层语义:`database` 参数此时应传 **schema 名**(如 `public`);不确定时先执行 `SELECT nspname FROM pg_catalog.pg_namespace WHERE nspname NOT LIKE 'pg\_%' AND nspname <> 'information_schema'` 列出可用 schema。
|
|
60
61
|
- 列结构返回 `ColumnInfo[]`:`{name, dataType, nullable, key?, default?, comment?}`。
|
|
61
62
|
|
|
62
63
|
### 5. preview — 预览表数据
|