admindb 2.3.1 → 2.4.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/README.md +16 -16
- package/dist/app.js +119 -171
- package/dist/public/css/app.css +1 -1
- package/dist/public/js/browse.js +1 -1
- package/dist/public/js/databases.js +1 -1
- package/dist/public/js/designer.js +1 -1
- package/dist/public/js/forms.js +1 -1
- package/dist/public/js/home.js +1 -1
- package/dist/public/js/inspector.js +1 -1
- package/dist/public/js/query.js +1 -1
- package/dist/public/js/schema.js +1 -1
- package/dist/public/js/seed.js +1 -1
- package/dist/routes/api/erd.js +2 -2
- package/dist/routes/api/helpers.d.ts +1 -1
- package/dist/routes/api/import-export.js +8 -6
- package/dist/routes/api/index.js +16 -9
- package/dist/routes/api/query.js +8 -5
- package/dist/routes/api/rows.js +36 -21
- package/dist/routes/api/seed.js +29 -16
- package/dist/routes/api/tables.js +32 -18
- package/dist/routes/databases.js +2 -0
- package/dist/routes/pages.d.ts +1 -1
- package/dist/routes/pages.js +137 -43
- package/dist/views/layouts/main.hbs +1 -1
- package/dist/views/pages/databases.hbs +1 -1
- package/dist/views/pages/erd.hbs +1 -1
- package/dist/views/pages/form.hbs +1 -1
- package/dist/views/pages/home.hbs +1 -1
- package/dist/views/pages/info.hbs +1 -1
- package/dist/views/pages/schema.hbs +1 -1
- package/dist/views/pages/seed-select.hbs +6 -5
- package/dist/views/pages/seed.hbs +3 -3
- package/dist/views/pages/table.hbs +1 -1
- package/dist/views/partials/navbar.hbs +1 -1
- package/dist/views/partials/sidebar.hbs +1 -1
- package/docs/API.md +60 -55
- package/package.json +1 -1
|
@@ -4,20 +4,22 @@ exports.registerTableRoutes = registerTableRoutes;
|
|
|
4
4
|
const generator_1 = require("../../sql/generator");
|
|
5
5
|
const helpers_1 = require("./helpers");
|
|
6
6
|
function registerTableRoutes(router, ctx) {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
router.get('/api/tables/:db', (0, helpers_1.wrap)(async (req, res) => {
|
|
8
|
+
const db = ctx.getDb(req);
|
|
9
9
|
const r = await db.listTables();
|
|
10
10
|
if (!r.success)
|
|
11
11
|
return (0, helpers_1.fail)(res, r.error ?? 'Failed to list tables.', 500);
|
|
12
12
|
(0, helpers_1.ok)(res, r.data);
|
|
13
13
|
}));
|
|
14
|
-
router.get('/api/tables/:table/info', (0, helpers_1.wrap)(async (req, res) => {
|
|
14
|
+
router.get('/api/tables/:db/:table/info', (0, helpers_1.wrap)(async (req, res) => {
|
|
15
|
+
const db = ctx.getDb(req);
|
|
15
16
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
16
17
|
if (!info)
|
|
17
18
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
18
19
|
(0, helpers_1.ok)(res, info);
|
|
19
20
|
}));
|
|
20
|
-
router.get('/api/tables/:table/fk-options', (0, helpers_1.wrap)(async (req, res) => {
|
|
21
|
+
router.get('/api/tables/:db/:table/fk-options', (0, helpers_1.wrap)(async (req, res) => {
|
|
22
|
+
const db = ctx.getDb(req);
|
|
21
23
|
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
22
24
|
if (!info)
|
|
23
25
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
@@ -42,7 +44,8 @@ function registerTableRoutes(router, ctx) {
|
|
|
42
44
|
}
|
|
43
45
|
(0, helpers_1.ok)(res, options);
|
|
44
46
|
}));
|
|
45
|
-
router.get('/api/tables/:table/ddl', (0, helpers_1.wrap)(async (req, res) => {
|
|
47
|
+
router.get('/api/tables/:db/:table/ddl', (0, helpers_1.wrap)(async (req, res) => {
|
|
48
|
+
const db = ctx.getDb(req);
|
|
46
49
|
const r = await db.getCreateStatement(req.params.table);
|
|
47
50
|
if (!r.success)
|
|
48
51
|
return (0, helpers_1.fail)(res, r.error ?? 'Failed to fetch DDL.', 500);
|
|
@@ -50,7 +53,8 @@ function registerTableRoutes(router, ctx) {
|
|
|
50
53
|
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
51
54
|
(0, helpers_1.ok)(res, { sql: r.data });
|
|
52
55
|
}));
|
|
53
|
-
router.post('/api/tables/:table/rename', (0, helpers_1.wrap)(async (req, res) => {
|
|
56
|
+
router.post('/api/tables/:db/:table/rename', (0, helpers_1.wrap)(async (req, res) => {
|
|
57
|
+
const db = ctx.getDb(req);
|
|
54
58
|
const newName = String(req.body?.name ?? '').trim();
|
|
55
59
|
if (!newName)
|
|
56
60
|
return (0, helpers_1.fail)(res, 'New table name is required.');
|
|
@@ -59,13 +63,15 @@ function registerTableRoutes(router, ctx) {
|
|
|
59
63
|
return (0, helpers_1.fail)(res, r.error ?? 'Failed to rename table.');
|
|
60
64
|
(0, helpers_1.ok)(res, { message: `Table renamed to "${newName}".`, name: newName });
|
|
61
65
|
}));
|
|
62
|
-
router.delete('/api/tables/:table', (0, helpers_1.wrap)(async (req, res) => {
|
|
66
|
+
router.delete('/api/tables/:db/:table', (0, helpers_1.wrap)(async (req, res) => {
|
|
67
|
+
const db = ctx.getDb(req);
|
|
63
68
|
const r = await db.dropTable(req.params.table);
|
|
64
69
|
if (!r.success)
|
|
65
70
|
return (0, helpers_1.fail)(res, r.error ?? 'Failed to drop table.');
|
|
66
71
|
(0, helpers_1.ok)(res, { message: `Table "${req.params.table}" dropped.` });
|
|
67
72
|
}));
|
|
68
|
-
router.post('/api/tables/bulk-drop', (0, helpers_1.wrap)(async (req, res) => {
|
|
73
|
+
router.post('/api/tables/:db/bulk-drop', (0, helpers_1.wrap)(async (req, res) => {
|
|
74
|
+
const db = ctx.getDb(req);
|
|
69
75
|
const tables = Array.isArray(req.body?.tables)
|
|
70
76
|
? req.body.tables.filter((t) => typeof t === 'string' && t.trim().length > 0)
|
|
71
77
|
: [];
|
|
@@ -97,7 +103,8 @@ function registerTableRoutes(router, ctx) {
|
|
|
97
103
|
message: `Dropped ${dropped.length} table(s)${failed.length ? `, ${failed.length} failed` : ''}.`,
|
|
98
104
|
});
|
|
99
105
|
}));
|
|
100
|
-
router.post('/api/tables/bulk-truncate', (0, helpers_1.wrap)(async (req, res) => {
|
|
106
|
+
router.post('/api/tables/:db/bulk-truncate', (0, helpers_1.wrap)(async (req, res) => {
|
|
107
|
+
const db = ctx.getDb(req);
|
|
101
108
|
const tables = Array.isArray(req.body?.tables)
|
|
102
109
|
? req.body.tables.filter((t) => typeof t === 'string' && t.trim().length > 0)
|
|
103
110
|
: [];
|
|
@@ -129,7 +136,7 @@ function registerTableRoutes(router, ctx) {
|
|
|
129
136
|
message: `Cleared ${cleared.length} table(s)${failed.length ? `, ${failed.length} failed` : ''}.`,
|
|
130
137
|
});
|
|
131
138
|
}));
|
|
132
|
-
router.post('/api/tables/generate', (0, helpers_1.wrap)(async (req, res) => {
|
|
139
|
+
router.post('/api/tables/:db/generate', (0, helpers_1.wrap)(async (req, res) => {
|
|
133
140
|
const name = String(req.body?.name ?? '').trim();
|
|
134
141
|
const columns = (req.body?.columns ?? []);
|
|
135
142
|
if (!name)
|
|
@@ -144,6 +151,7 @@ function registerTableRoutes(router, ctx) {
|
|
|
144
151
|
}
|
|
145
152
|
}));
|
|
146
153
|
const createTableHandler = (0, helpers_1.wrap)(async (req, res) => {
|
|
154
|
+
const db = ctx.getDb(req);
|
|
147
155
|
const name = String(req.body?.name ?? '').trim();
|
|
148
156
|
const columns = (req.body?.columns ?? []);
|
|
149
157
|
if (!name)
|
|
@@ -162,9 +170,10 @@ function registerTableRoutes(router, ctx) {
|
|
|
162
170
|
return (0, helpers_1.fail)(res, r.error ?? 'Failed to create table.');
|
|
163
171
|
(0, helpers_1.ok)(res, { message: `Table "${name}" created.`, name }, 201);
|
|
164
172
|
});
|
|
165
|
-
router.post('/api/tables', createTableHandler);
|
|
166
|
-
router.post('/api/tables/create', createTableHandler);
|
|
167
|
-
router.post('/api/tables/:table/columns', (0, helpers_1.wrap)(async (req, res) => {
|
|
173
|
+
router.post('/api/tables/:db', createTableHandler);
|
|
174
|
+
router.post('/api/tables/:db/create', createTableHandler);
|
|
175
|
+
router.post('/api/tables/:db/:table/columns', (0, helpers_1.wrap)(async (req, res) => {
|
|
176
|
+
const db = ctx.getDb(req);
|
|
168
177
|
const col = req.body;
|
|
169
178
|
if (!col?.name)
|
|
170
179
|
return (0, helpers_1.fail)(res, 'Column name is required.');
|
|
@@ -173,7 +182,8 @@ function registerTableRoutes(router, ctx) {
|
|
|
173
182
|
return (0, helpers_1.fail)(res, r.error ?? 'Failed to add column.');
|
|
174
183
|
(0, helpers_1.ok)(res, { message: `Column "${col.name}" added.` }, 201);
|
|
175
184
|
}));
|
|
176
|
-
router.put('/api/tables/:table/columns/:column', (0, helpers_1.wrap)(async (req, res) => {
|
|
185
|
+
router.put('/api/tables/:db/:table/columns/:column', (0, helpers_1.wrap)(async (req, res) => {
|
|
186
|
+
const db = ctx.getDb(req);
|
|
177
187
|
const col = req.body;
|
|
178
188
|
if (!col)
|
|
179
189
|
return (0, helpers_1.fail)(res, 'Column definition is required.');
|
|
@@ -182,7 +192,8 @@ function registerTableRoutes(router, ctx) {
|
|
|
182
192
|
return (0, helpers_1.fail)(res, r.error ?? 'Failed to modify column.');
|
|
183
193
|
(0, helpers_1.ok)(res, { message: `Column "${req.params.column}" updated.` });
|
|
184
194
|
}));
|
|
185
|
-
router.post('/api/tables/:table/columns/:column/rename', (0, helpers_1.wrap)(async (req, res) => {
|
|
195
|
+
router.post('/api/tables/:db/:table/columns/:column/rename', (0, helpers_1.wrap)(async (req, res) => {
|
|
196
|
+
const db = ctx.getDb(req);
|
|
186
197
|
const newName = String(req.body?.name ?? '').trim();
|
|
187
198
|
if (!newName)
|
|
188
199
|
return (0, helpers_1.fail)(res, 'New column name is required.');
|
|
@@ -191,13 +202,15 @@ function registerTableRoutes(router, ctx) {
|
|
|
191
202
|
return (0, helpers_1.fail)(res, r.error ?? 'Failed to rename column.');
|
|
192
203
|
(0, helpers_1.ok)(res, { message: `Column renamed to "${newName}".`, name: newName });
|
|
193
204
|
}));
|
|
194
|
-
router.delete('/api/tables/:table/columns/:column', (0, helpers_1.wrap)(async (req, res) => {
|
|
205
|
+
router.delete('/api/tables/:db/:table/columns/:column', (0, helpers_1.wrap)(async (req, res) => {
|
|
206
|
+
const db = ctx.getDb(req);
|
|
195
207
|
const r = await db.dropColumn(req.params.table, req.params.column);
|
|
196
208
|
if (!r.success)
|
|
197
209
|
return (0, helpers_1.fail)(res, r.error ?? 'Failed to drop column.');
|
|
198
210
|
(0, helpers_1.ok)(res, { message: `Column "${req.params.column}" dropped.` });
|
|
199
211
|
}));
|
|
200
|
-
router.post('/api/tables/:table/indexes', (0, helpers_1.wrap)(async (req, res) => {
|
|
212
|
+
router.post('/api/tables/:db/:table/indexes', (0, helpers_1.wrap)(async (req, res) => {
|
|
213
|
+
const db = ctx.getDb(req);
|
|
201
214
|
const def = req.body;
|
|
202
215
|
if (!def || !Array.isArray(def.columns) || def.columns.length === 0) {
|
|
203
216
|
return (0, helpers_1.fail)(res, 'At least one column is required for an index.');
|
|
@@ -207,7 +220,8 @@ function registerTableRoutes(router, ctx) {
|
|
|
207
220
|
return (0, helpers_1.fail)(res, r.error ?? 'Failed to create index.');
|
|
208
221
|
(0, helpers_1.ok)(res, { message: 'Index created.' }, 201);
|
|
209
222
|
}));
|
|
210
|
-
router.delete('/api/tables/:table/indexes/:index', (0, helpers_1.wrap)(async (req, res) => {
|
|
223
|
+
router.delete('/api/tables/:db/:table/indexes/:index', (0, helpers_1.wrap)(async (req, res) => {
|
|
224
|
+
const db = ctx.getDb(req);
|
|
211
225
|
const r = await db.dropIndex(req.params.index);
|
|
212
226
|
if (!r.success)
|
|
213
227
|
return (0, helpers_1.fail)(res, r.error ?? 'Failed to drop index.');
|
package/dist/routes/databases.js
CHANGED
|
@@ -40,8 +40,10 @@ function registerDatabasesRoutes(router, ctx) {
|
|
|
40
40
|
};
|
|
41
41
|
router.get('/', async (_req, res, next) => {
|
|
42
42
|
try {
|
|
43
|
+
res.locals.databasesMode = true;
|
|
43
44
|
res.render('pages/databases', {
|
|
44
45
|
title: 'Databases',
|
|
46
|
+
databasesMode: true,
|
|
45
47
|
databases: await getDatabaseRows(manager),
|
|
46
48
|
dbDir: manager.directory,
|
|
47
49
|
readonly: Boolean(ctx.readonly),
|
package/dist/routes/pages.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import type {Router} from 'express';import type {IDatabase} from '../db/index';import type {Logger} from '../utils/logger';interface PageContext{
|
|
1
|
+
import type {Router,Request} from 'express';import type {IDatabase} from '../db/index';import type {Logger} from '../utils/logger';export interface PageContext{getDb:(req:Request)=>IDatabase;logger:Logger;}export declare function registerPages(router:Router,ctx:PageContext):void;
|
package/dist/routes/pages.js
CHANGED
|
@@ -6,7 +6,7 @@ const generator_1 = require("../sql/generator");
|
|
|
6
6
|
const common_1 = require("../utils/common");
|
|
7
7
|
const datatype_1 = require("../utils/datatype");
|
|
8
8
|
const index_1 = require("../data/index");
|
|
9
|
-
function buildDisplayRows(rawRows, info, table, basePath = '') {
|
|
9
|
+
function buildDisplayRows(rawRows, info, dbId, table, basePath = '') {
|
|
10
10
|
const pkCols = info.primaryKey.length ? info.primaryKey : ['_rowid_'];
|
|
11
11
|
return rawRows.map((row) => {
|
|
12
12
|
const pkEncoded = (0, common_1.encodePk)(pkCols.map((c) => row[c]));
|
|
@@ -49,8 +49,8 @@ function buildDisplayRows(rawRows, info, table, basePath = '') {
|
|
|
49
49
|
isAudio = mimeInfo.isAudio;
|
|
50
50
|
isVideo = mimeInfo.isVideo;
|
|
51
51
|
isPdf = mimeInfo.isPdf;
|
|
52
|
-
if (table && pkEncoded) {
|
|
53
|
-
blobUrl = `${basePath}/api/tables/${encodeURIComponent(table)}/row/${encodeURIComponent(pkEncoded)}/blob/${encodeURIComponent(c.name)}`;
|
|
52
|
+
if (table && pkEncoded && dbId) {
|
|
53
|
+
blobUrl = `${basePath}/api/tables/${encodeURIComponent(dbId)}/${encodeURIComponent(table)}/row/${encodeURIComponent(pkEncoded)}/blob/${encodeURIComponent(c.name)}`;
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
else if ((0, datatype_1.isJsonString)(v) || typeUpper.includes('JSON')) {
|
|
@@ -87,15 +87,32 @@ function buildDisplayRows(rawRows, info, table, basePath = '') {
|
|
|
87
87
|
};
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
|
+
function initPageLocals(res, db, dbId, allNames) {
|
|
91
|
+
const tables = allNames.filter((n) => !n.startsWith('_'));
|
|
92
|
+
const internalTables = allNames.filter((n) => n.startsWith('_'));
|
|
93
|
+
res.locals.dbId = dbId;
|
|
94
|
+
res.locals.dbPath = db.path;
|
|
95
|
+
res.locals.tables = tables;
|
|
96
|
+
res.locals.internalTables = internalTables;
|
|
97
|
+
res.locals.dialect = db.dialect;
|
|
98
|
+
res.locals.isPostgres = db.dialect === 'postgres';
|
|
99
|
+
res.locals.isSqlite = db.dialect === 'sqlite';
|
|
100
|
+
res.locals.dialectName = db.dialect === 'postgres' ? 'PostgreSQL' : 'SQLite';
|
|
101
|
+
res.locals.readonly = db.isReadOnly;
|
|
102
|
+
res.locals.databasesMode = false;
|
|
103
|
+
return { tables, internalTables };
|
|
104
|
+
}
|
|
90
105
|
function registerPages(router, ctx) {
|
|
91
|
-
const { db } = ctx;
|
|
92
106
|
const notFound = (res, message) => {
|
|
93
107
|
res.status(404).render('pages/error', { title: 'Not found', status: 404, error: message });
|
|
94
108
|
};
|
|
95
|
-
router.get('/', async (
|
|
109
|
+
router.get('/home/:db', async (req, res, next) => {
|
|
96
110
|
try {
|
|
97
|
-
const
|
|
98
|
-
const
|
|
111
|
+
const db = ctx.getDb(req);
|
|
112
|
+
const dbId = req.params.db;
|
|
113
|
+
const all = await db.listTables();
|
|
114
|
+
const allNames = (all.data ?? []).map((t) => t.name);
|
|
115
|
+
const { tables: names, internalTables: internalNames } = initPageLocals(res, db, dbId, allNames);
|
|
99
116
|
const colCountsMap = {};
|
|
100
117
|
if (db.dialect === 'postgres') {
|
|
101
118
|
const colRes = await db.all(`SELECT table_name, count(*)::int AS cols FROM information_schema.columns WHERE table_schema = 'public' GROUP BY table_name;`);
|
|
@@ -131,6 +148,7 @@ function registerPages(router, ctx) {
|
|
|
131
148
|
}
|
|
132
149
|
res.render('pages/home', {
|
|
133
150
|
title: 'Home',
|
|
151
|
+
dbId,
|
|
134
152
|
tables: names,
|
|
135
153
|
counts,
|
|
136
154
|
cols,
|
|
@@ -144,9 +162,15 @@ function registerPages(router, ctx) {
|
|
|
144
162
|
next(err);
|
|
145
163
|
}
|
|
146
164
|
});
|
|
147
|
-
router.get('/tables/:table', async (req, res, next) => {
|
|
165
|
+
router.get('/tables/:db/:table', async (req, res, next) => {
|
|
148
166
|
try {
|
|
167
|
+
const db = ctx.getDb(req);
|
|
168
|
+
const dbId = req.params.db;
|
|
149
169
|
const table = req.params.table;
|
|
170
|
+
res.locals.dbId = dbId;
|
|
171
|
+
const all = await db.listTables();
|
|
172
|
+
const allNames = (all.data ?? []).map((t) => t.name);
|
|
173
|
+
initPageLocals(res, db, dbId, allNames);
|
|
150
174
|
const info = await db.getTableInfo(table);
|
|
151
175
|
if (!info.success || !info.data || info.data.columns.length === 0) {
|
|
152
176
|
return notFound(res, `Table "${table}" does not exist.`);
|
|
@@ -177,7 +201,7 @@ function registerPages(router, ctx) {
|
|
|
177
201
|
type: c.type,
|
|
178
202
|
active,
|
|
179
203
|
dir: active ? orderDir : null,
|
|
180
|
-
href: `${basePath}/tables/${encodeURIComponent(table)}?${qs}${filterQS ? `&${filterQS}` : ''}`,
|
|
204
|
+
href: `${basePath}/tables/${encodeURIComponent(dbId)}/${encodeURIComponent(table)}?${qs}${filterQS ? `&${filterQS}` : ''}`,
|
|
181
205
|
};
|
|
182
206
|
});
|
|
183
207
|
const refColumns = (refsR.data ?? [])
|
|
@@ -197,7 +221,7 @@ function registerPages(router, ctx) {
|
|
|
197
221
|
return [col.table, map];
|
|
198
222
|
}));
|
|
199
223
|
const countMap = new Map(countEntries);
|
|
200
|
-
const rows = buildDisplayRows(rawRows, info.data, table, basePath).map((dr, i) => {
|
|
224
|
+
const rows = buildDisplayRows(rawRows, info.data, dbId, table, basePath).map((dr, i) => {
|
|
201
225
|
const raw = rawRows[i];
|
|
202
226
|
const refs = refColumns.map((col) => {
|
|
203
227
|
const v = raw[col.to];
|
|
@@ -214,6 +238,7 @@ function registerPages(router, ctx) {
|
|
|
214
238
|
res.locals.currentTable = table;
|
|
215
239
|
res.render('pages/table', {
|
|
216
240
|
title: table,
|
|
241
|
+
dbId,
|
|
217
242
|
table,
|
|
218
243
|
info: info.data,
|
|
219
244
|
rows,
|
|
@@ -234,6 +259,7 @@ function registerPages(router, ctx) {
|
|
|
234
259
|
firstRow: count === 0 ? 0 : (page - 1) * pageSize + 1,
|
|
235
260
|
lastRow: Math.min(page * pageSize, count),
|
|
236
261
|
browseConfig: {
|
|
262
|
+
dbId,
|
|
237
263
|
table,
|
|
238
264
|
filters,
|
|
239
265
|
pkCols: info.data.primaryKey.length ? info.data.primaryKey : ['_rowid_'],
|
|
@@ -246,9 +272,15 @@ function registerPages(router, ctx) {
|
|
|
246
272
|
next(err);
|
|
247
273
|
}
|
|
248
274
|
});
|
|
249
|
-
router.get('/
|
|
275
|
+
router.get('/schema/:db/:table', async (req, res, next) => {
|
|
250
276
|
try {
|
|
277
|
+
const db = ctx.getDb(req);
|
|
278
|
+
const dbId = req.params.db;
|
|
251
279
|
const table = req.params.table;
|
|
280
|
+
res.locals.dbId = dbId;
|
|
281
|
+
const all = await db.listTables();
|
|
282
|
+
const allNames = (all.data ?? []).map((t) => t.name);
|
|
283
|
+
initPageLocals(res, db, dbId, allNames);
|
|
252
284
|
const [info, schema] = await Promise.all([db.getTableInfo(table), db.getSchema(table)]);
|
|
253
285
|
if (!info.success || !info.data || info.data.columns.length === 0) {
|
|
254
286
|
return notFound(res, `Table "${table}" does not exist.`);
|
|
@@ -259,26 +291,34 @@ function registerPages(router, ctx) {
|
|
|
259
291
|
res.locals.currentTable = table;
|
|
260
292
|
res.render('pages/schema', {
|
|
261
293
|
title: `Schema · ${table}`,
|
|
294
|
+
dbId,
|
|
262
295
|
table,
|
|
263
296
|
schema: schema.data,
|
|
264
297
|
isInternal: table.startsWith('_'),
|
|
265
298
|
dialect: db.dialect,
|
|
266
299
|
isSqlite: db.dialect === 'sqlite',
|
|
267
300
|
isPostgres: db.dialect === 'postgres',
|
|
268
|
-
config: { table, isInternal: table.startsWith('_'), dialect: db.dialect },
|
|
301
|
+
config: { dbId, table, isInternal: table.startsWith('_'), dialect: db.dialect },
|
|
269
302
|
});
|
|
270
303
|
}
|
|
271
304
|
catch (err) {
|
|
272
305
|
next(err);
|
|
273
306
|
}
|
|
274
307
|
});
|
|
275
|
-
router.get('/seed', async (req, res, next) => {
|
|
308
|
+
router.get('/seed/:db', async (req, res, next) => {
|
|
276
309
|
try {
|
|
277
|
-
const
|
|
310
|
+
const db = ctx.getDb(req);
|
|
311
|
+
const dbId = req.params.db;
|
|
312
|
+
res.locals.dbId = dbId;
|
|
313
|
+
const all = await db.listTables();
|
|
314
|
+
const allNames = (all.data ?? []).map((t) => t.name);
|
|
315
|
+
const { tables: tableNames } = initPageLocals(res, db, dbId, allNames);
|
|
278
316
|
if (tableNames.length === 0) {
|
|
279
317
|
return res.render('pages/seed-select', {
|
|
280
318
|
title: 'Seed Data Generator',
|
|
281
|
-
|
|
319
|
+
dbId,
|
|
320
|
+
tables: tableNames,
|
|
321
|
+
seedTables: [],
|
|
282
322
|
hasTables: false,
|
|
283
323
|
dialect: db.dialect,
|
|
284
324
|
});
|
|
@@ -299,7 +339,9 @@ function registerPages(router, ctx) {
|
|
|
299
339
|
const defaultMode = req.query.mode === 'chain' ? 'chain' : 'single';
|
|
300
340
|
res.render('pages/seed-select', {
|
|
301
341
|
title: 'Seed Data Generator · Choose Table & Mode',
|
|
302
|
-
|
|
342
|
+
dbId,
|
|
343
|
+
tables: tableNames,
|
|
344
|
+
seedTables: tableStats,
|
|
303
345
|
hasTables: true,
|
|
304
346
|
defaultTable,
|
|
305
347
|
defaultMode,
|
|
@@ -310,16 +352,15 @@ function registerPages(router, ctx) {
|
|
|
310
352
|
next(err);
|
|
311
353
|
}
|
|
312
354
|
});
|
|
313
|
-
router.get('/
|
|
314
|
-
const table = req.params.table;
|
|
315
|
-
const qs = req.url.includes('?') ? req.url.slice(req.url.indexOf('?')) : '';
|
|
316
|
-
const bp = res.locals.basePath || '';
|
|
317
|
-
return res.redirect(301, `${bp}/seed/${encodeURIComponent(table)}${qs}`);
|
|
318
|
-
});
|
|
319
|
-
router.get('/seed/:table', async (req, res, next) => {
|
|
355
|
+
router.get('/seed/:db/:table', async (req, res, next) => {
|
|
320
356
|
try {
|
|
357
|
+
const db = ctx.getDb(req);
|
|
358
|
+
const dbId = req.params.db;
|
|
321
359
|
const table = req.params.table;
|
|
322
|
-
|
|
360
|
+
res.locals.dbId = dbId;
|
|
361
|
+
const all = await db.listTables();
|
|
362
|
+
const allNames = (all.data ?? []).map((t) => t.name);
|
|
363
|
+
const { tables: allTables } = initPageLocals(res, db, dbId, allNames);
|
|
323
364
|
const initialMode = req.query.mode === 'chain' ? 'chain' : 'single';
|
|
324
365
|
const [info, countRes] = await Promise.all([db.getTableInfo(table), db.getRowCount(table)]);
|
|
325
366
|
if (!info.success || !info.data || info.data.columns.length === 0) {
|
|
@@ -330,6 +371,7 @@ function registerPages(router, ctx) {
|
|
|
330
371
|
res.locals.currentTable = table;
|
|
331
372
|
res.render('pages/seed', {
|
|
332
373
|
title: `Seed data · ${table}`,
|
|
374
|
+
dbId,
|
|
333
375
|
table,
|
|
334
376
|
allTables,
|
|
335
377
|
mode: initialMode,
|
|
@@ -338,16 +380,22 @@ function registerPages(router, ctx) {
|
|
|
338
380
|
colCount: info.data.columns.length,
|
|
339
381
|
maxRows: index_1.MAX_SEED_ROWS,
|
|
340
382
|
quickCounts: [10, 50, 100, 500, 1000, index_1.MAX_SEED_ROWS],
|
|
341
|
-
seedConfig: { table, columns, rowCount, maxRows: index_1.MAX_SEED_ROWS, mode: initialMode },
|
|
383
|
+
seedConfig: { dbId, table, columns, rowCount, maxRows: index_1.MAX_SEED_ROWS, mode: initialMode },
|
|
342
384
|
});
|
|
343
385
|
}
|
|
344
386
|
catch (err) {
|
|
345
387
|
next(err);
|
|
346
388
|
}
|
|
347
389
|
});
|
|
348
|
-
router.get('/tables/:table/rows/new', async (req, res, next) => {
|
|
390
|
+
router.get('/tables/:db/:table/rows/new', async (req, res, next) => {
|
|
349
391
|
try {
|
|
392
|
+
const db = ctx.getDb(req);
|
|
393
|
+
const dbId = req.params.db;
|
|
350
394
|
const table = req.params.table;
|
|
395
|
+
res.locals.dbId = dbId;
|
|
396
|
+
const all = await db.listTables();
|
|
397
|
+
const allNames = (all.data ?? []).map((t) => t.name);
|
|
398
|
+
initPageLocals(res, db, dbId, allNames);
|
|
351
399
|
const info = await db.getTableInfo(table);
|
|
352
400
|
if (!info.success || !info.data || info.data.columns.length === 0) {
|
|
353
401
|
return notFound(res, `Table "${table}" does not exist.`);
|
|
@@ -356,21 +404,28 @@ function registerPages(router, ctx) {
|
|
|
356
404
|
res.locals.currentTable = table;
|
|
357
405
|
res.render('pages/form', {
|
|
358
406
|
title: `New row · ${table}`,
|
|
407
|
+
dbId,
|
|
359
408
|
table,
|
|
360
409
|
mode: 'insert',
|
|
361
410
|
pk: null,
|
|
362
411
|
pkColumns,
|
|
363
412
|
infoSummary: `${info.data.columns.length} column(s)` + (pkColumns.length ? ` · PK: ${pkColumns.join(', ')}` : ''),
|
|
364
|
-
config: { table, mode: 'insert', pk: null, pkColumns },
|
|
413
|
+
config: { dbId, table, mode: 'insert', pk: null, pkColumns },
|
|
365
414
|
});
|
|
366
415
|
}
|
|
367
416
|
catch (err) {
|
|
368
417
|
next(err);
|
|
369
418
|
}
|
|
370
419
|
});
|
|
371
|
-
router.get('/tables/:table/rows/:id/edit', async (req, res, next) => {
|
|
420
|
+
router.get('/tables/:db/:table/rows/:id/edit', async (req, res, next) => {
|
|
372
421
|
try {
|
|
422
|
+
const db = ctx.getDb(req);
|
|
423
|
+
const dbId = req.params.db;
|
|
373
424
|
const table = req.params.table;
|
|
425
|
+
res.locals.dbId = dbId;
|
|
426
|
+
const all = await db.listTables();
|
|
427
|
+
const allNames = (all.data ?? []).map((t) => t.name);
|
|
428
|
+
initPageLocals(res, db, dbId, allNames);
|
|
374
429
|
const info = await db.getTableInfo(table);
|
|
375
430
|
if (!info.success || !info.data || info.data.columns.length === 0) {
|
|
376
431
|
return notFound(res, `Table "${table}" does not exist.`);
|
|
@@ -383,57 +438,96 @@ function registerPages(router, ctx) {
|
|
|
383
438
|
res.locals.currentTable = table;
|
|
384
439
|
res.render('pages/form', {
|
|
385
440
|
title: `Edit row · ${table}`,
|
|
441
|
+
dbId,
|
|
386
442
|
table,
|
|
387
443
|
mode: 'edit',
|
|
388
444
|
pk: req.params.id,
|
|
389
445
|
pkColumns,
|
|
390
446
|
infoSummary: `${info.data.columns.length} column(s)` + (info.data.primaryKey.length ? ` · PK: ${info.data.primaryKey.join(', ')}` : ''),
|
|
391
|
-
config: { table, mode: 'edit', pk: req.params.id, pkColumns },
|
|
447
|
+
config: { dbId, table, mode: 'edit', pk: req.params.id, pkColumns },
|
|
392
448
|
});
|
|
393
449
|
}
|
|
394
450
|
catch (err) {
|
|
395
451
|
next(err);
|
|
396
452
|
}
|
|
397
453
|
});
|
|
398
|
-
router.get('/erd', (
|
|
399
|
-
|
|
454
|
+
router.get('/erd/:db', async (req, res, next) => {
|
|
455
|
+
try {
|
|
456
|
+
const db = ctx.getDb(req);
|
|
457
|
+
const dbId = req.params.db;
|
|
458
|
+
res.locals.dbId = dbId;
|
|
459
|
+
const all = await db.listTables();
|
|
460
|
+
const allNames = (all.data ?? []).map((t) => t.name);
|
|
461
|
+
initPageLocals(res, db, dbId, allNames);
|
|
462
|
+
res.render('pages/erd', { title: 'ER Diagram', dbId });
|
|
463
|
+
}
|
|
464
|
+
catch (err) {
|
|
465
|
+
next(err);
|
|
466
|
+
}
|
|
400
467
|
});
|
|
401
|
-
router.get('/designer', (
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
468
|
+
router.get('/designer/:db', async (req, res, next) => {
|
|
469
|
+
try {
|
|
470
|
+
const db = ctx.getDb(req);
|
|
471
|
+
const dbId = req.params.db;
|
|
472
|
+
res.locals.dbId = dbId;
|
|
473
|
+
const all = await db.listTables();
|
|
474
|
+
const allNames = (all.data ?? []).map((t) => t.name);
|
|
475
|
+
initPageLocals(res, db, dbId, allNames);
|
|
476
|
+
res.render('pages/designer', {
|
|
477
|
+
title: 'New table',
|
|
478
|
+
dbId,
|
|
479
|
+
dialect: db.dialect,
|
|
480
|
+
isSqlite: db.dialect === 'sqlite',
|
|
481
|
+
isPostgres: db.dialect === 'postgres',
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
catch (err) {
|
|
485
|
+
next(err);
|
|
486
|
+
}
|
|
408
487
|
});
|
|
409
|
-
router.get('/query', async (
|
|
488
|
+
router.get('/query/:db', async (req, res, next) => {
|
|
410
489
|
try {
|
|
490
|
+
const db = ctx.getDb(req);
|
|
491
|
+
const dbId = req.params.db;
|
|
492
|
+
res.locals.dbId = dbId;
|
|
493
|
+
const all = await db.listTables();
|
|
494
|
+
const allNames = (all.data ?? []).map((t) => t.name);
|
|
495
|
+
initPageLocals(res, db, dbId, allNames);
|
|
411
496
|
const queries = await db.listSavedQueries();
|
|
412
|
-
res.render('pages/query', { title: 'Query editor', savedQueries: queries.success ? queries.data : [] });
|
|
497
|
+
res.render('pages/query', { title: 'Query editor', dbId, savedQueries: queries.success ? queries.data : [] });
|
|
413
498
|
}
|
|
414
499
|
catch (err) {
|
|
415
500
|
next(err);
|
|
416
501
|
}
|
|
417
502
|
});
|
|
418
|
-
router.get('/export', async (
|
|
503
|
+
router.get('/export/:db', async (req, res, next) => {
|
|
419
504
|
try {
|
|
505
|
+
const db = ctx.getDb(req);
|
|
506
|
+
const dbId = req.params.db;
|
|
420
507
|
const dump = await (0, export_1.generateSqlDump)(db);
|
|
421
508
|
if (!dump.success || !dump.data)
|
|
422
509
|
throw new Error(dump.error ?? 'Failed to generate dump.');
|
|
423
510
|
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
424
511
|
res.setHeader('Content-Type', 'application/sql; charset=utf-8');
|
|
425
|
-
res.setHeader('Content-Disposition', `attachment; filename="
|
|
512
|
+
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(dbId)}-${stamp}.sql"`);
|
|
426
513
|
res.send(dump.data);
|
|
427
514
|
}
|
|
428
515
|
catch (err) {
|
|
429
516
|
next(err);
|
|
430
517
|
}
|
|
431
518
|
});
|
|
432
|
-
router.get('/info', async (
|
|
519
|
+
router.get('/info/:db', async (req, res, next) => {
|
|
433
520
|
try {
|
|
521
|
+
const db = ctx.getDb(req);
|
|
522
|
+
const dbId = req.params.db;
|
|
523
|
+
res.locals.dbId = dbId;
|
|
524
|
+
const all = await db.listTables();
|
|
525
|
+
const allNames = (all.data ?? []).map((t) => t.name);
|
|
526
|
+
initPageLocals(res, db, dbId, allNames);
|
|
434
527
|
const settingsRes = await db.getSettings();
|
|
435
528
|
res.render('pages/info', {
|
|
436
529
|
title: 'Database Info',
|
|
530
|
+
dbId,
|
|
437
531
|
settings: settingsRes.success ? settingsRes.data : {},
|
|
438
532
|
dialect: db.dialect,
|
|
439
533
|
dbPath: db.path,
|