admindb 2.1.0 → 2.1.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 +1 -1
- package/dist/app.d.ts +1 -69
- package/dist/app.js +1 -257
- package/dist/auth/config.d.ts +1 -19
- package/dist/auth/config.js +1 -147
- package/dist/auth/crypto.d.ts +1 -27
- package/dist/auth/crypto.js +1 -133
- package/dist/auth/index.d.ts +1 -5
- package/dist/auth/index.js +1 -22
- package/dist/auth/middleware.d.ts +1 -14
- package/dist/auth/middleware.js +1 -100
- package/dist/auth/routes.d.ts +1 -7
- package/dist/auth/routes.js +1 -78
- package/dist/auth/types.d.ts +1 -35
- package/dist/auth/types.js +1 -11
- package/dist/cli/args.d.ts +1 -45
- package/dist/cli/args.js +1 -328
- package/dist/cli/config.d.ts +1 -56
- package/dist/cli/config.js +1 -133
- package/dist/cli/index.d.ts +1 -4
- package/dist/cli/index.js +1 -21
- package/dist/cli/runner.d.ts +1 -1
- package/dist/cli/runner.js +1 -222
- package/dist/cli.d.ts +1 -2
- package/dist/cli.js +1 -5
- package/dist/data/datasets.d.ts +1 -17
- package/dist/data/datasets.js +1 -133
- package/dist/data/detector.d.ts +1 -30
- package/dist/data/detector.js +1 -472
- package/dist/data/engine.d.ts +1 -10
- package/dist/data/engine.js +1 -211
- package/dist/data/index.d.ts +1 -5
- package/dist/data/index.js +1 -22
- package/dist/data/strategies.d.ts +1 -30
- package/dist/data/strategies.js +1 -343
- package/dist/data/types.d.ts +1 -73
- package/dist/data/types.js +1 -8
- package/dist/db/database.d.ts +1 -119
- package/dist/db/database.js +1 -471
- package/dist/db/export.d.ts +1 -12
- package/dist/db/export.js +1 -42
- package/dist/db/filters.d.ts +1 -22
- package/dist/db/filters.js +1 -158
- package/dist/db/index.d.ts +1 -8
- package/dist/db/index.js +1 -25
- package/dist/db/introspection.d.ts +1 -6
- package/dist/db/introspection.js +1 -172
- package/dist/db/manager.d.ts +1 -81
- package/dist/db/manager.js +1 -397
- package/dist/db/migrations.d.ts +1 -5
- package/dist/db/migrations.js +1 -110
- package/dist/db/postgres.d.ts +1 -85
- package/dist/db/postgres.js +1 -745
- package/dist/db/types.d.ts +1 -206
- package/dist/db/types.js +1 -7
- package/dist/index.d.ts +1 -23
- package/dist/index.js +1 -66
- package/dist/public/js/api.js +1 -47
- package/dist/public/js/app.js +1 -262
- package/dist/public/js/browse.js +1 -1371
- package/dist/public/js/databases.js +1 -235
- package/dist/public/js/designer.js +1 -258
- package/dist/public/js/forms.js +1 -826
- package/dist/public/js/icons.js +1 -51
- package/dist/public/js/inspector.js +1 -638
- package/dist/public/js/query.js +1 -322
- package/dist/public/js/schema.js +1 -447
- package/dist/public/js/seed.js +1 -1090
- package/dist/routes/api/helpers.d.ts +1 -42
- package/dist/routes/api/helpers.js +1 -133
- package/dist/routes/api/import-export.d.ts +1 -3
- package/dist/routes/api/import-export.js +1 -85
- package/dist/routes/api/index.d.ts +1 -4
- package/dist/routes/api/index.js +1 -37
- package/dist/routes/api/query.d.ts +1 -3
- package/dist/routes/api/query.js +1 -76
- package/dist/routes/api/rows.d.ts +1 -3
- package/dist/routes/api/rows.js +1 -373
- package/dist/routes/api/seed.d.ts +1 -3
- package/dist/routes/api/seed.js +1 -82
- package/dist/routes/api/tables.d.ts +1 -3
- package/dist/routes/api/tables.js +1 -160
- package/dist/routes/databases.d.ts +1 -26
- package/dist/routes/databases.js +1 -230
- package/dist/routes/index.d.ts +1 -3
- package/dist/routes/index.js +1 -20
- package/dist/routes/pages.d.ts +1 -9
- package/dist/routes/pages.js +1 -390
- package/dist/serverless.d.ts +1 -40
- package/dist/serverless.js +1 -210
- package/dist/sql/classifier.d.ts +1 -19
- package/dist/sql/classifier.js +1 -37
- package/dist/sql/generator.d.ts +1 -81
- package/dist/sql/generator.js +1 -348
- package/dist/sql/index.d.ts +1 -2
- package/dist/sql/index.js +1 -19
- package/dist/types/api.d.ts +1 -412
- package/dist/types/api.js +1 -9
- package/dist/utils/colors.d.ts +1 -40
- package/dist/utils/colors.js +1 -64
- package/dist/utils/common.d.ts +1 -50
- package/dist/utils/common.js +1 -229
- package/dist/utils/csv.d.ts +1 -15
- package/dist/utils/csv.js +1 -80
- package/dist/utils/datatype.d.ts +1 -49
- package/dist/utils/datatype.js +1 -222
- package/dist/utils/icons.d.ts +1 -11
- package/dist/utils/icons.js +1 -72
- package/dist/utils/index.d.ts +1 -4
- package/dist/utils/index.js +1 -21
- package/dist/utils/logger.d.ts +1 -10
- package/dist/utils/logger.js +1 -40
- package/dist/views/layouts/main.hbs +0 -4
- package/dist/views/pages/databases.hbs +0 -18
- package/dist/views/pages/designer.hbs +0 -10
- package/dist/views/pages/error.hbs +0 -2
- package/dist/views/pages/form.hbs +0 -5
- package/dist/views/pages/home.hbs +0 -8
- package/dist/views/pages/login.hbs +0 -13
- package/dist/views/pages/query.hbs +0 -9
- package/dist/views/pages/schema.hbs +0 -20
- package/dist/views/pages/seed.hbs +0 -31
- package/dist/views/pages/table.hbs +0 -14
- package/dist/views/partials/icon.hbs +0 -1
- package/dist/views/partials/navbar.hbs +0 -6
- package/dist/views/partials/sidebar.hbs +0 -8
- package/package.json +3 -2
- package/dist/app.js.map +0 -1
- package/dist/auth/config.js.map +0 -1
- package/dist/auth/crypto.js.map +0 -1
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/middleware.js.map +0 -1
- package/dist/auth/routes.js.map +0 -1
- package/dist/auth/types.js.map +0 -1
- package/dist/cli/args.js.map +0 -1
- package/dist/cli/config.js.map +0 -1
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/runner.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/data/datasets.js.map +0 -1
- package/dist/data/detector.js.map +0 -1
- package/dist/data/engine.js.map +0 -1
- package/dist/data/index.js.map +0 -1
- package/dist/data/strategies.js.map +0 -1
- package/dist/data/types.js.map +0 -1
- package/dist/db/database.js.map +0 -1
- package/dist/db/export.js.map +0 -1
- package/dist/db/filters.js.map +0 -1
- package/dist/db/index.js.map +0 -1
- package/dist/db/introspection.js.map +0 -1
- package/dist/db/manager.js.map +0 -1
- package/dist/db/migrations.js.map +0 -1
- package/dist/db/postgres.js.map +0 -1
- package/dist/db/types.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/public/css/input.css +0 -302
- package/dist/routes/api/helpers.js.map +0 -1
- package/dist/routes/api/import-export.js.map +0 -1
- package/dist/routes/api/index.js.map +0 -1
- package/dist/routes/api/query.js.map +0 -1
- package/dist/routes/api/rows.js.map +0 -1
- package/dist/routes/api/seed.js.map +0 -1
- package/dist/routes/api/tables.js.map +0 -1
- package/dist/routes/databases.js.map +0 -1
- package/dist/routes/index.js.map +0 -1
- package/dist/routes/pages.js.map +0 -1
- package/dist/serverless.js.map +0 -1
- package/dist/sql/classifier.js.map +0 -1
- package/dist/sql/generator.js.map +0 -1
- package/dist/sql/index.js.map +0 -1
- package/dist/types/api.js.map +0 -1
- package/dist/utils/colors.js.map +0 -1
- package/dist/utils/common.js.map +0 -1
- package/dist/utils/csv.js.map +0 -1
- package/dist/utils/datatype.js.map +0 -1
- package/dist/utils/icons.js.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/logger.js.map +0 -1
package/dist/public/js/schema.js
CHANGED
|
@@ -1,447 +1 @@
|
|
|
1
|
-
/* Table schema editor: rename table, add/rename/drop columns, drop table. */
|
|
2
|
-
(function () {
|
|
3
|
-
'use strict';
|
|
4
|
-
const cfgEl = document.getElementById('schema-config');
|
|
5
|
-
if (!cfgEl) return;
|
|
6
|
-
const cfg = JSON.parse(cfgEl.textContent);
|
|
7
|
-
const SQLITE_TYPES = [
|
|
8
|
-
'TEXT',
|
|
9
|
-
'INTEGER',
|
|
10
|
-
'REAL',
|
|
11
|
-
'BLOB',
|
|
12
|
-
'BOOLEAN',
|
|
13
|
-
'DATE',
|
|
14
|
-
'DATETIME',
|
|
15
|
-
'NUMERIC',
|
|
16
|
-
'VARCHAR(255)',
|
|
17
|
-
'JSON',
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
const POSTGRES_TYPES = [
|
|
21
|
-
'TEXT',
|
|
22
|
-
'VARCHAR(255)',
|
|
23
|
-
'INTEGER',
|
|
24
|
-
'BIGINT',
|
|
25
|
-
'SMALLINT',
|
|
26
|
-
'SERIAL',
|
|
27
|
-
'BIGSERIAL',
|
|
28
|
-
'REAL',
|
|
29
|
-
'DECIMAL(10,2)',
|
|
30
|
-
'NUMERIC',
|
|
31
|
-
'BOOLEAN',
|
|
32
|
-
'DATE',
|
|
33
|
-
'DATETIME',
|
|
34
|
-
'TIMESTAMP',
|
|
35
|
-
'TIMESTAMPTZ',
|
|
36
|
-
'TIME',
|
|
37
|
-
'INTERVAL',
|
|
38
|
-
'JSON',
|
|
39
|
-
'JSONB',
|
|
40
|
-
'UUID',
|
|
41
|
-
'BYTEA',
|
|
42
|
-
'INET',
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
const isPostgres = (window.APP && window.APP.dialect === 'postgres') || (cfg && cfg.dialect === 'postgres');
|
|
46
|
-
const DESIGNER_TYPES = isPostgres ? POSTGRES_TYPES : SQLITE_TYPES;
|
|
47
|
-
|
|
48
|
-
const NAME_RE = /^[A-Za-z_][A-Za-z0-9_$]*$/;
|
|
49
|
-
const t = encodeURIComponent(cfg.table);
|
|
50
|
-
|
|
51
|
-
function escapeHtml(s) {
|
|
52
|
-
return (window.UI && UI.escapeHtml) ? UI.escapeHtml(s) : String(s == null ? '' : s);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function showError(el, msg) {
|
|
56
|
-
el.classList.remove('hidden');
|
|
57
|
-
el.innerHTML = '<div class="flex items-start gap-2.5 rounded-xl border border-error/40 bg-error/10 px-4 py-3 text-sm font-medium text-error"><span>' + escapeHtml(msg) + '</span></div>';
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ---- Rename table ------------------------------------------------------
|
|
61
|
-
|
|
62
|
-
const renameInput = document.getElementById('table-rename-input');
|
|
63
|
-
const renameBtn = document.getElementById('rename-table');
|
|
64
|
-
|
|
65
|
-
async function renameTable() {
|
|
66
|
-
const name = (renameInput.value || '').trim();
|
|
67
|
-
if (!name) return UI.showError('Table name is required.');
|
|
68
|
-
if (name === cfg.table) return UI.showToast('Name unchanged.', 'info');
|
|
69
|
-
try {
|
|
70
|
-
const data = await Api.post('/api/tables/' + t + '/rename', { name });
|
|
71
|
-
UI.showToast(data.message || 'Table renamed.', 'success');
|
|
72
|
-
setTimeout(() => {
|
|
73
|
-
window.location.href = Api.basePath + '/tables/' + encodeURIComponent(name) + '/schema';
|
|
74
|
-
}, 400);
|
|
75
|
-
} catch (e) {
|
|
76
|
-
UI.showError(e.message);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
if (renameBtn) renameBtn.addEventListener('click', renameTable);
|
|
80
|
-
if (renameInput) renameInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') renameTable(); });
|
|
81
|
-
|
|
82
|
-
// ---- Add column ---------------------------------------------------------
|
|
83
|
-
|
|
84
|
-
const addBtn = document.getElementById('add-column');
|
|
85
|
-
const panel = document.getElementById('add-column-panel');
|
|
86
|
-
const formEl = document.getElementById('add-column-form');
|
|
87
|
-
const errEl = document.getElementById('add-column-error');
|
|
88
|
-
const submitBtn = document.getElementById('add-column-submit');
|
|
89
|
-
const cancelBtn = document.getElementById('add-column-cancel');
|
|
90
|
-
let fkTables = [];
|
|
91
|
-
|
|
92
|
-
async function loadFkTables() {
|
|
93
|
-
try {
|
|
94
|
-
const tables = await Api.get('/api/tables');
|
|
95
|
-
fkTables = tables.map((x) => x.name).filter((n) => n.charAt(0) !== '_');
|
|
96
|
-
} catch (e) {
|
|
97
|
-
fkTables = [];
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function renderTypeOptions(selectedType) {
|
|
102
|
-
const upper = (selectedType || 'TEXT').toUpperCase();
|
|
103
|
-
const all = [...DESIGNER_TYPES];
|
|
104
|
-
if (selectedType && !all.map((x) => x.toUpperCase()).includes(upper)) {
|
|
105
|
-
all.unshift(selectedType);
|
|
106
|
-
}
|
|
107
|
-
return all.map((x) => '<option value="' + escapeHtml(x) + '"' + (x.toUpperCase() === upper ? ' selected' : '') + '>' + escapeHtml(x) + '</option>').join('');
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async function buildAddForm() {
|
|
111
|
-
if (!fkTables.length) await loadFkTables();
|
|
112
|
-
formEl.innerHTML = '';
|
|
113
|
-
formEl.innerHTML = [
|
|
114
|
-
'<div class="col-span-2 sm:col-span-3"><label class="field-label">Name</label><input class="field-input ac-name" placeholder="column_name" autocomplete="off" spellcheck="false"></div>',
|
|
115
|
-
'<div class="col-span-1 sm:col-span-2"><label class="field-label">Type</label><select class="field-select ac-type">' + renderTypeOptions('TEXT') + '</select></div>',
|
|
116
|
-
'<div class="col-span-1 sm:col-span-2"><label class="field-label">FK table</label><select class="field-select ac-fk-table"><option value="">— none —</option>' + fkTables.map((x) => '<option>' + escapeHtml(x) + '</option>').join('') + '</select></div>',
|
|
117
|
-
'<div class="col-span-1 sm:col-span-2"><label class="field-label">FK column</label><select class="field-select ac-fk-col"><option value="">—</option></select></div>',
|
|
118
|
-
'<div class="col-span-1 sm:col-span-2"><label class="field-label">Default</label><input class="field-input ac-default" placeholder="0, CURRENT_TIMESTAMP" autocomplete="off" spellcheck="false"></div>',
|
|
119
|
-
'<div class="col-span-2 flex items-end gap-1.5 pb-0.5 sm:col-span-1">',
|
|
120
|
-
'<label class="flex cursor-pointer flex-col items-center gap-1" title="Primary key"><input type="checkbox" class="checkbox checkbox-xs ac-pk"><span class="text-[10px] font-semibold uppercase text-base-content/50">PK</span></label>',
|
|
121
|
-
'<label class="flex cursor-pointer flex-col items-center gap-1" title="Not null"><input type="checkbox" class="checkbox checkbox-xs ac-notnull"><span class="text-[10px] font-semibold uppercase text-base-content/50">NN</span></label>',
|
|
122
|
-
'<label class="flex cursor-pointer flex-col items-center gap-1" title="Unique"><input type="checkbox" class="checkbox checkbox-xs ac-unique"><span class="text-[10px] font-semibold uppercase text-base-content/50">UQ</span></label>',
|
|
123
|
-
'</div>',
|
|
124
|
-
].join('');
|
|
125
|
-
|
|
126
|
-
const fkTableSel = formEl.querySelector('.ac-fk-table');
|
|
127
|
-
const fkColSel = formEl.querySelector('.ac-fk-col');
|
|
128
|
-
fkTableSel.addEventListener('change', async () => {
|
|
129
|
-
fkColSel.innerHTML = '<option value="">—</option>';
|
|
130
|
-
const target = fkTableSel.value;
|
|
131
|
-
if (!target) return;
|
|
132
|
-
try {
|
|
133
|
-
const info = await Api.get('/api/tables/' + encodeURIComponent(target) + '/info');
|
|
134
|
-
fkColSel.innerHTML = '<option value="">—</option>' + info.columns.map((c) => '<option>' + escapeHtml(c.name) + '</option>').join('');
|
|
135
|
-
} catch (e) { /* ignore */ }
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
async function addColumn() {
|
|
140
|
-
errEl.classList.add('hidden');
|
|
141
|
-
const name = (formEl.querySelector('.ac-name').value || '').trim();
|
|
142
|
-
if (!name) return showError(errEl, 'Column name is required.');
|
|
143
|
-
if (!NAME_RE.test(name)) return showError(errEl, 'Invalid column name: "' + name + '". Use letters, digits and underscores.');
|
|
144
|
-
const fkTable = formEl.querySelector('.ac-fk-table').value;
|
|
145
|
-
const fkCol = formEl.querySelector('.ac-fk-col').value;
|
|
146
|
-
const column = {
|
|
147
|
-
name,
|
|
148
|
-
type: formEl.querySelector('.ac-type').value,
|
|
149
|
-
primaryKey: formEl.querySelector('.ac-pk').checked,
|
|
150
|
-
notNull: formEl.querySelector('.ac-notnull').checked,
|
|
151
|
-
unique: formEl.querySelector('.ac-unique').checked,
|
|
152
|
-
defaultValue: formEl.querySelector('.ac-default').value.trim() || null,
|
|
153
|
-
foreignKey: fkTable && fkCol ? { table: fkTable, column: fkCol } : null,
|
|
154
|
-
};
|
|
155
|
-
try {
|
|
156
|
-
const data = await Api.post('/api/tables/' + t + '/columns', { column });
|
|
157
|
-
UI.showToast(data.message || 'Column added.', 'success');
|
|
158
|
-
window.location.reload();
|
|
159
|
-
} catch (e) {
|
|
160
|
-
showError(errEl, e.message);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (addBtn) {
|
|
165
|
-
addBtn.addEventListener('click', async () => {
|
|
166
|
-
if (editPanel) editPanel.classList.add('hidden');
|
|
167
|
-
panel.classList.toggle('hidden');
|
|
168
|
-
if (!panel.classList.contains('hidden')) {
|
|
169
|
-
errEl.classList.add('hidden');
|
|
170
|
-
await buildAddForm();
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
if (cancelBtn) cancelBtn.addEventListener('click', () => { panel.classList.add('hidden'); formEl.innerHTML = ''; });
|
|
175
|
-
if (submitBtn) submitBtn.addEventListener('click', addColumn);
|
|
176
|
-
|
|
177
|
-
// ---- Edit column (type & constraints) ------------------------------------
|
|
178
|
-
|
|
179
|
-
const editPanel = document.getElementById('edit-column-panel');
|
|
180
|
-
const editFormEl = document.getElementById('edit-column-form');
|
|
181
|
-
const editErrEl = document.getElementById('edit-column-error');
|
|
182
|
-
const editSubmitBtn = document.getElementById('edit-column-submit');
|
|
183
|
-
const editCancelBtn = document.getElementById('edit-column-cancel');
|
|
184
|
-
const editTargetSpan = document.getElementById('edit-column-target');
|
|
185
|
-
let currentEditingCol = null;
|
|
186
|
-
|
|
187
|
-
async function openEditColumn(colData) {
|
|
188
|
-
if (!editPanel || !editFormEl) return;
|
|
189
|
-
if (panel) panel.classList.add('hidden');
|
|
190
|
-
if (!fkTables.length) await loadFkTables();
|
|
191
|
-
currentEditingCol = colData.name;
|
|
192
|
-
if (editTargetSpan) editTargetSpan.textContent = '"' + colData.name + '"';
|
|
193
|
-
editErrEl.classList.add('hidden');
|
|
194
|
-
|
|
195
|
-
editFormEl.innerHTML = [
|
|
196
|
-
'<div class="col-span-2 sm:col-span-3"><label class="field-label">Name</label><input class="field-input ec-name" value="' + escapeHtml(colData.name) + '" autocomplete="off" spellcheck="false"></div>',
|
|
197
|
-
'<div class="col-span-1 sm:col-span-2"><label class="field-label">Type</label><select class="field-select ec-type">' + renderTypeOptions(colData.type) + '</select></div>',
|
|
198
|
-
'<div class="col-span-1 sm:col-span-2"><label class="field-label">FK table</label><select class="field-select ec-fk-table"><option value="">— none —</option>' + fkTables.map((x) => '<option value="' + escapeHtml(x) + '"' + (colData.fkTable === x ? ' selected' : '') + '>' + escapeHtml(x) + '</option>').join('') + '</select></div>',
|
|
199
|
-
'<div class="col-span-1 sm:col-span-2"><label class="field-label">FK column</label><select class="field-select ec-fk-col"><option value="">—</option></select></div>',
|
|
200
|
-
'<div class="col-span-1 sm:col-span-2"><label class="field-label">Default</label><input class="field-input ec-default" value="' + escapeHtml(colData.defaultValue || '') + '" placeholder="0, CURRENT_TIMESTAMP" autocomplete="off" spellcheck="false"></div>',
|
|
201
|
-
'<div class="col-span-2 flex items-end gap-1.5 pb-0.5 sm:col-span-1">',
|
|
202
|
-
'<label class="flex cursor-pointer flex-col items-center gap-1" title="Primary key"><input type="checkbox" class="checkbox checkbox-xs ec-pk"' + (colData.primaryKey ? ' checked' : '') + '><span class="text-[10px] font-semibold uppercase text-base-content/50">PK</span></label>',
|
|
203
|
-
'<label class="flex cursor-pointer flex-col items-center gap-1" title="Not null"><input type="checkbox" class="checkbox checkbox-xs ec-notnull"' + (colData.notNull ? ' checked' : '') + '><span class="text-[10px] font-semibold uppercase text-base-content/50">NN</span></label>',
|
|
204
|
-
'<label class="flex cursor-pointer flex-col items-center gap-1" title="Unique"><input type="checkbox" class="checkbox checkbox-xs ec-unique"' + (colData.unique ? ' checked' : '') + '><span class="text-[10px] font-semibold uppercase text-base-content/50">UQ</span></label>',
|
|
205
|
-
'</div>',
|
|
206
|
-
].join('');
|
|
207
|
-
|
|
208
|
-
const fkTableSel = editFormEl.querySelector('.ec-fk-table');
|
|
209
|
-
const fkColSel = editFormEl.querySelector('.ec-fk-col');
|
|
210
|
-
|
|
211
|
-
async function updateFkCols(table, selectedCol) {
|
|
212
|
-
fkColSel.innerHTML = '<option value="">—</option>';
|
|
213
|
-
if (!table) return;
|
|
214
|
-
try {
|
|
215
|
-
const targetInfo = await Api.get('/api/tables/' + encodeURIComponent(table) + '/info');
|
|
216
|
-
fkColSel.innerHTML = '<option value="">—</option>' + targetInfo.columns.map((c) => '<option value="' + escapeHtml(c.name) + '"' + (c.name === selectedCol ? ' selected' : '') + '>' + escapeHtml(c.name) + '</option>').join('');
|
|
217
|
-
} catch (e) { /* ignore */ }
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (colData.fkTable) {
|
|
221
|
-
await updateFkCols(colData.fkTable, colData.fkTo);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
fkTableSel.addEventListener('change', () => updateFkCols(fkTableSel.value, ''));
|
|
225
|
-
|
|
226
|
-
editPanel.classList.remove('hidden');
|
|
227
|
-
editPanel.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
async function saveEditedColumn() {
|
|
231
|
-
if (!currentEditingCol) return;
|
|
232
|
-
editErrEl.classList.add('hidden');
|
|
233
|
-
const name = (editFormEl.querySelector('.ec-name').value || '').trim();
|
|
234
|
-
if (!name) return showError(editErrEl, 'Column name is required.');
|
|
235
|
-
if (!NAME_RE.test(name)) return showError(editErrEl, 'Invalid column name: "' + name + '". Use letters, digits and underscores.');
|
|
236
|
-
const fkTable = editFormEl.querySelector('.ec-fk-table').value;
|
|
237
|
-
const fkCol = editFormEl.querySelector('.ec-fk-col').value;
|
|
238
|
-
const column = {
|
|
239
|
-
name,
|
|
240
|
-
type: editFormEl.querySelector('.ec-type').value,
|
|
241
|
-
primaryKey: editFormEl.querySelector('.ec-pk').checked,
|
|
242
|
-
notNull: editFormEl.querySelector('.ec-notnull').checked,
|
|
243
|
-
unique: editFormEl.querySelector('.ec-unique').checked,
|
|
244
|
-
defaultValue: editFormEl.querySelector('.ec-default').value.trim() || null,
|
|
245
|
-
foreignKey: fkTable && fkCol ? { table: fkTable, column: fkCol } : null,
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
if (editSubmitBtn) editSubmitBtn.disabled = true;
|
|
249
|
-
try {
|
|
250
|
-
const data = await Api.put('/api/tables/' + t + '/columns/' + encodeURIComponent(currentEditingCol), { column });
|
|
251
|
-
UI.showToast(data.message || 'Column updated.', 'success');
|
|
252
|
-
window.location.reload();
|
|
253
|
-
} catch (e) {
|
|
254
|
-
showError(editErrEl, e.message);
|
|
255
|
-
} finally {
|
|
256
|
-
if (editSubmitBtn) editSubmitBtn.disabled = false;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (editCancelBtn) editCancelBtn.addEventListener('click', () => { editPanel.classList.add('hidden'); editFormEl.innerHTML = ''; currentEditingCol = null; });
|
|
261
|
-
if (editSubmitBtn) editSubmitBtn.addEventListener('click', saveEditedColumn);
|
|
262
|
-
|
|
263
|
-
document.querySelectorAll('.col-edit').forEach((btn) => {
|
|
264
|
-
btn.addEventListener('click', () => {
|
|
265
|
-
openEditColumn({
|
|
266
|
-
name: btn.dataset.column || '',
|
|
267
|
-
type: btn.dataset.type || 'TEXT',
|
|
268
|
-
primaryKey: btn.dataset.pk === 'true',
|
|
269
|
-
notNull: btn.dataset.notnull === 'true',
|
|
270
|
-
unique: btn.dataset.unique === 'true',
|
|
271
|
-
defaultValue: btn.dataset.default || '',
|
|
272
|
-
fkTable: btn.dataset.fkTable || '',
|
|
273
|
-
fkTo: btn.dataset.fkTo || '',
|
|
274
|
-
});
|
|
275
|
-
});
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
// ---- Drop column ---------------------------------------------------------
|
|
279
|
-
|
|
280
|
-
document.querySelectorAll('.col-drop').forEach((btn) => {
|
|
281
|
-
btn.addEventListener('click', async () => {
|
|
282
|
-
const tr = btn.closest('tr');
|
|
283
|
-
if (!tr) return;
|
|
284
|
-
const cell = tr.querySelector('.col-name-cell');
|
|
285
|
-
const name = cell ? cell.dataset.column : '';
|
|
286
|
-
if (!name) return;
|
|
287
|
-
const confirmed = await UI.confirm({
|
|
288
|
-
title: 'Drop Column',
|
|
289
|
-
message: 'Dropping column "' + name + '" will permanently delete all data stored in this column for all rows in table "' + cfg.table + '". This action cannot be undone.',
|
|
290
|
-
item: name,
|
|
291
|
-
itemType: 'Column',
|
|
292
|
-
confirmText: 'Drop column',
|
|
293
|
-
danger: true,
|
|
294
|
-
});
|
|
295
|
-
if (!confirmed) return;
|
|
296
|
-
Api.del('/api/tables/' + t + '/columns/' + encodeURIComponent(name))
|
|
297
|
-
.then((data) => {
|
|
298
|
-
UI.showToast(data.message || 'Column dropped.', 'success');
|
|
299
|
-
window.location.reload();
|
|
300
|
-
})
|
|
301
|
-
.catch((e) => UI.showError(e.message));
|
|
302
|
-
});
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
// ---- Drop table -----------------------------------------------------------
|
|
306
|
-
|
|
307
|
-
const dropBtn = document.getElementById('drop-table');
|
|
308
|
-
if (dropBtn) {
|
|
309
|
-
dropBtn.addEventListener('click', async () => {
|
|
310
|
-
const confirmed = await UI.confirm({
|
|
311
|
-
title: 'Drop Table',
|
|
312
|
-
message: 'Dropping table "' + cfg.table + '" permanently erases the table schema, all indexes, and all stored records. This action cannot be undone.',
|
|
313
|
-
item: cfg.table,
|
|
314
|
-
itemType: 'Table',
|
|
315
|
-
requireInputMatch: cfg.table,
|
|
316
|
-
confirmText: 'Drop table',
|
|
317
|
-
danger: true,
|
|
318
|
-
});
|
|
319
|
-
if (!confirmed) return;
|
|
320
|
-
Api.del('/api/tables/' + t)
|
|
321
|
-
.then(() => {
|
|
322
|
-
UI.showToast('Table dropped.', 'success');
|
|
323
|
-
setTimeout(() => { window.location.href = Api.basePath + '/'; }, 400);
|
|
324
|
-
})
|
|
325
|
-
.catch((e) => UI.showError(e.message));
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// ---- Index management ------------------------------------------------------
|
|
330
|
-
|
|
331
|
-
const addIndexBtn = document.getElementById('add-index');
|
|
332
|
-
const addIndexPanel = document.getElementById('add-index-panel');
|
|
333
|
-
const indexNameInput = document.getElementById('index-name');
|
|
334
|
-
const indexUniqueInput = document.getElementById('index-unique');
|
|
335
|
-
const indexErrEl = document.getElementById('add-index-error');
|
|
336
|
-
const addIndexSubmit = document.getElementById('add-index-submit');
|
|
337
|
-
const addIndexCancel = document.getElementById('add-index-cancel');
|
|
338
|
-
const indexPreview = document.getElementById('index-preview');
|
|
339
|
-
const indexPreviewCode = document.getElementById('index-preview-code');
|
|
340
|
-
const indexColBtns = Array.prototype.slice.call(document.querySelectorAll('.index-col-btn'));
|
|
341
|
-
|
|
342
|
-
// Columns selected for the new index, in click order (index order matters!).
|
|
343
|
-
let indexColumns = [];
|
|
344
|
-
|
|
345
|
-
function renderIndexPreview() {
|
|
346
|
-
if (!indexPreview || !indexPreviewCode) return;
|
|
347
|
-
const cols = indexColumns.slice();
|
|
348
|
-
const name = (indexNameInput.value || '').trim();
|
|
349
|
-
const unique = indexUniqueInput && indexUniqueInput.checked ? 'UNIQUE ' : '';
|
|
350
|
-
if (!cols.length) {
|
|
351
|
-
indexPreview.classList.add('hidden');
|
|
352
|
-
indexPreviewCode.textContent = '';
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
const finalName = name || 'idx_' + cfg.table + '_' + cols.join('_');
|
|
356
|
-
indexPreviewCode.textContent =
|
|
357
|
-
'CREATE ' + unique + 'INDEX "' + finalName + '" ON "' + cfg.table + '" (' +
|
|
358
|
-
cols.map((c) => '"' + c + '"').join(', ') + ');';
|
|
359
|
-
indexPreview.classList.remove('hidden');
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function toggleIndexColumn(btn) {
|
|
363
|
-
const col = btn.dataset.column;
|
|
364
|
-
const i = indexColumns.indexOf(col);
|
|
365
|
-
const num = btn.querySelector('.index-col-num');
|
|
366
|
-
if (i !== -1) {
|
|
367
|
-
indexColumns.splice(i, 1);
|
|
368
|
-
btn.classList.remove('border-primary', 'bg-primary/10');
|
|
369
|
-
if (num) num.classList.add('hidden');
|
|
370
|
-
} else {
|
|
371
|
-
indexColumns.push(col);
|
|
372
|
-
btn.classList.add('border-primary', 'bg-primary/10');
|
|
373
|
-
if (num) num.classList.remove('hidden');
|
|
374
|
-
}
|
|
375
|
-
indexColumns.forEach((c, idx) => {
|
|
376
|
-
const b = indexColBtns.find((x) => x.dataset.column === c);
|
|
377
|
-
const n = b && b.querySelector('.index-col-num');
|
|
378
|
-
if (n) n.textContent = String(idx + 1);
|
|
379
|
-
});
|
|
380
|
-
renderIndexPreview();
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
indexColBtns.forEach((btn) => btn.addEventListener('click', () => toggleIndexColumn(btn)));
|
|
384
|
-
if (indexNameInput) indexNameInput.addEventListener('input', renderIndexPreview);
|
|
385
|
-
if (indexUniqueInput) indexUniqueInput.addEventListener('change', renderIndexPreview);
|
|
386
|
-
|
|
387
|
-
if (addIndexBtn && addIndexPanel) {
|
|
388
|
-
addIndexBtn.addEventListener('click', () => {
|
|
389
|
-
addIndexPanel.classList.toggle('hidden');
|
|
390
|
-
if (!addIndexPanel.classList.contains('hidden')) {
|
|
391
|
-
indexErrEl.classList.add('hidden');
|
|
392
|
-
indexNameInput.focus();
|
|
393
|
-
renderIndexPreview();
|
|
394
|
-
}
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
if (addIndexCancel) {
|
|
398
|
-
addIndexCancel.addEventListener('click', () => {
|
|
399
|
-
addIndexPanel.classList.add('hidden');
|
|
400
|
-
indexErrEl.classList.add('hidden');
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
if (addIndexSubmit) {
|
|
404
|
-
addIndexSubmit.addEventListener('click', async () => {
|
|
405
|
-
indexErrEl.classList.add('hidden');
|
|
406
|
-
const columns = indexColumns.slice();
|
|
407
|
-
if (!columns.length) return showError(indexErrEl, 'Select at least one column for the index.');
|
|
408
|
-
const body = { columns, unique: !!(indexUniqueInput && indexUniqueInput.checked) };
|
|
409
|
-
const name = (indexNameInput.value || '').trim();
|
|
410
|
-
if (name) body.name = name;
|
|
411
|
-
addIndexSubmit.disabled = true;
|
|
412
|
-
try {
|
|
413
|
-
const data = await Api.post('/api/tables/' + t + '/indexes', body);
|
|
414
|
-
UI.showToast(data.message || 'Index created.', 'success');
|
|
415
|
-
window.location.reload();
|
|
416
|
-
} catch (e) {
|
|
417
|
-
showError(indexErrEl, e.message);
|
|
418
|
-
} finally {
|
|
419
|
-
addIndexSubmit.disabled = false;
|
|
420
|
-
}
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
document.querySelectorAll('.index-drop').forEach((btn) => {
|
|
425
|
-
btn.addEventListener('click', async () => {
|
|
426
|
-
const name = btn.dataset.index;
|
|
427
|
-
if (!name) return;
|
|
428
|
-
const confirmed = await UI.confirm({
|
|
429
|
-
title: 'Drop Index',
|
|
430
|
-
message: 'Are you sure you want to permanently drop index "' + name + '" on table "' + cfg.table + '"?',
|
|
431
|
-
item: name,
|
|
432
|
-
itemType: 'Index',
|
|
433
|
-
confirmText: 'Drop index',
|
|
434
|
-
danger: true,
|
|
435
|
-
});
|
|
436
|
-
if (!confirmed) return;
|
|
437
|
-
Api.del('/api/tables/' + t + '/indexes/' + encodeURIComponent(name))
|
|
438
|
-
.then((data) => {
|
|
439
|
-
UI.showToast(data.message || 'Index dropped.', 'success');
|
|
440
|
-
window.location.reload();
|
|
441
|
-
})
|
|
442
|
-
.catch((e) => UI.showError(e.message));
|
|
443
|
-
});
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
loadFkTables();
|
|
447
|
-
})();
|
|
1
|
+
!function(){"use strict";const e=document.getElementById("schema-config");if(!e)return;const t=JSON.parse(e.textContent),l=window.APP&&"postgres"===window.APP.dialect||t&&"postgres"===t.dialect?["TEXT","VARCHAR(255)","INTEGER","BIGINT","SMALLINT","SERIAL","BIGSERIAL","REAL","DECIMAL(10,2)","NUMERIC","BOOLEAN","DATE","DATETIME","TIMESTAMP","TIMESTAMPTZ","TIME","INTERVAL","JSON","JSONB","UUID","BYTEA","INET"]:["TEXT","INTEGER","REAL","BLOB","BOOLEAN","DATE","DATETIME","NUMERIC","VARCHAR(255)","JSON"],n=/^[A-Za-z_][A-Za-z0-9_$]*$/,a=encodeURIComponent(t.table);function c(e){return window.UI&&UI.escapeHtml?UI.escapeHtml(e):String(null==e?"":e)}function s(e,t){e.classList.remove("hidden"),e.innerHTML='<div class="flex items-start gap-2.5 rounded-xl border border-error/40 bg-error/10 px-4 py-3 text-sm font-medium text-error"><span>'+c(t)+"</span></div>"}const o=document.getElementById("table-rename-input"),i=document.getElementById("rename-table");async function d(){const e=(o.value||"").trim();if(!e)return UI.showError("Table name is required.");if(e===t.table)return UI.showToast("Name unchanged.","info");try{const t=await Api.post("/api/tables/"+a+"/rename",{name:e});UI.showToast(t.message||"Table renamed.","success"),setTimeout(()=>{window.location.href=Api.basePath+"/tables/"+encodeURIComponent(e)+"/schema"},400)}catch(e){UI.showError(e.message)}}i&&i.addEventListener("click",d),o&&o.addEventListener("keydown",e=>{"Enter"===e.key&&d()});const r=document.getElementById("add-column"),u=document.getElementById("add-column-panel"),p=document.getElementById("add-column-form"),m=document.getElementById("add-column-error"),f=document.getElementById("add-column-submit"),b=document.getElementById("add-column-cancel");let y=[];async function h(){try{const e=await Api.get("/api/tables");y=e.map(e=>e.name).filter(e=>"_"!==e.charAt(0))}catch(e){y=[]}}function x(e){const t=(e||"TEXT").toUpperCase(),n=[...l];return e&&!n.map(e=>e.toUpperCase()).includes(t)&&n.unshift(e),n.map(e=>'<option value="'+c(e)+'"'+(e.toUpperCase()===t?" selected":"")+">"+c(e)+"</option>").join("")}r&&r.addEventListener("click",async()=>{E&&E.classList.add("hidden"),u.classList.toggle("hidden"),u.classList.contains("hidden")||(m.classList.add("hidden"),await async function(){y.length||await h(),p.innerHTML="",p.innerHTML=['<div class="col-span-2 sm:col-span-3"><label class="field-label">Name</label><input class="field-input ac-name" placeholder="column_name" autocomplete="off" spellcheck="false"></div>','<div class="col-span-1 sm:col-span-2"><label class="field-label">Type</label><select class="field-select ac-type">'+x("TEXT")+"</select></div>",'<div class="col-span-1 sm:col-span-2"><label class="field-label">FK table</label><select class="field-select ac-fk-table"><option value="">— none —</option>'+y.map(e=>"<option>"+c(e)+"</option>").join("")+"</select></div>",'<div class="col-span-1 sm:col-span-2"><label class="field-label">FK column</label><select class="field-select ac-fk-col"><option value="">—</option></select></div>','<div class="col-span-1 sm:col-span-2"><label class="field-label">Default</label><input class="field-input ac-default" placeholder="0, CURRENT_TIMESTAMP" autocomplete="off" spellcheck="false"></div>','<div class="col-span-2 flex items-end gap-1.5 pb-0.5 sm:col-span-1">','<label class="flex cursor-pointer flex-col items-center gap-1" title="Primary key"><input type="checkbox" class="checkbox checkbox-xs ac-pk"><span class="text-[10px] font-semibold uppercase text-base-content/50">PK</span></label>','<label class="flex cursor-pointer flex-col items-center gap-1" title="Not null"><input type="checkbox" class="checkbox checkbox-xs ac-notnull"><span class="text-[10px] font-semibold uppercase text-base-content/50">NN</span></label>','<label class="flex cursor-pointer flex-col items-center gap-1" title="Unique"><input type="checkbox" class="checkbox checkbox-xs ac-unique"><span class="text-[10px] font-semibold uppercase text-base-content/50">UQ</span></label>',"</div>"].join("");const e=p.querySelector(".ac-fk-table"),t=p.querySelector(".ac-fk-col");e.addEventListener("change",async()=>{t.innerHTML='<option value="">—</option>';const l=e.value;if(l)try{const e=await Api.get("/api/tables/"+encodeURIComponent(l)+"/info");t.innerHTML='<option value="">—</option>'+e.columns.map(e=>"<option>"+c(e.name)+"</option>").join("")}catch(e){}})}())}),b&&b.addEventListener("click",()=>{u.classList.add("hidden"),p.innerHTML=""}),f&&f.addEventListener("click",async function(){m.classList.add("hidden");const e=(p.querySelector(".ac-name").value||"").trim();if(!e)return s(m,"Column name is required.");if(!n.test(e))return s(m,'Invalid column name: "'+e+'". Use letters, digits and underscores.');const t=p.querySelector(".ac-fk-table").value,l=p.querySelector(".ac-fk-col").value,c={name:e,type:p.querySelector(".ac-type").value,primaryKey:p.querySelector(".ac-pk").checked,notNull:p.querySelector(".ac-notnull").checked,unique:p.querySelector(".ac-unique").checked,defaultValue:p.querySelector(".ac-default").value.trim()||null,foreignKey:t&&l?{table:t,column:l}:null};try{const e=await Api.post("/api/tables/"+a+"/columns",{column:c});UI.showToast(e.message||"Column added.","success"),window.location.reload()}catch(e){s(m,e.message)}});const E=document.getElementById("edit-column-panel"),v=document.getElementById("edit-column-form"),g=document.getElementById("edit-column-error"),I=document.getElementById("edit-column-submit"),T=document.getElementById("edit-column-cancel"),k=document.getElementById("edit-column-target");let L=null;T&&T.addEventListener("click",()=>{E.classList.add("hidden"),v.innerHTML="",L=null}),I&&I.addEventListener("click",async function(){if(!L)return;g.classList.add("hidden");const e=(v.querySelector(".ec-name").value||"").trim();if(!e)return s(g,"Column name is required.");if(!n.test(e))return s(g,'Invalid column name: "'+e+'". Use letters, digits and underscores.');const t=v.querySelector(".ec-fk-table").value,l=v.querySelector(".ec-fk-col").value,c={name:e,type:v.querySelector(".ec-type").value,primaryKey:v.querySelector(".ec-pk").checked,notNull:v.querySelector(".ec-notnull").checked,unique:v.querySelector(".ec-unique").checked,defaultValue:v.querySelector(".ec-default").value.trim()||null,foreignKey:t&&l?{table:t,column:l}:null};I&&(I.disabled=!0);try{const e=await Api.put("/api/tables/"+a+"/columns/"+encodeURIComponent(L),{column:c});UI.showToast(e.message||"Column updated.","success"),window.location.reload()}catch(e){s(g,e.message)}finally{I&&(I.disabled=!1)}}),document.querySelectorAll(".col-edit").forEach(e=>{e.addEventListener("click",()=>{!async function(e){if(!E||!v)return;u&&u.classList.add("hidden"),y.length||await h(),L=e.name,k&&(k.textContent='"'+e.name+'"'),g.classList.add("hidden"),v.innerHTML=['<div class="col-span-2 sm:col-span-3"><label class="field-label">Name</label><input class="field-input ec-name" value="'+c(e.name)+'" autocomplete="off" spellcheck="false"></div>','<div class="col-span-1 sm:col-span-2"><label class="field-label">Type</label><select class="field-select ec-type">'+x(e.type)+"</select></div>",'<div class="col-span-1 sm:col-span-2"><label class="field-label">FK table</label><select class="field-select ec-fk-table"><option value="">— none —</option>'+y.map(t=>'<option value="'+c(t)+'"'+(e.fkTable===t?" selected":"")+">"+c(t)+"</option>").join("")+"</select></div>",'<div class="col-span-1 sm:col-span-2"><label class="field-label">FK column</label><select class="field-select ec-fk-col"><option value="">—</option></select></div>','<div class="col-span-1 sm:col-span-2"><label class="field-label">Default</label><input class="field-input ec-default" value="'+c(e.defaultValue||"")+'" placeholder="0, CURRENT_TIMESTAMP" autocomplete="off" spellcheck="false"></div>','<div class="col-span-2 flex items-end gap-1.5 pb-0.5 sm:col-span-1">','<label class="flex cursor-pointer flex-col items-center gap-1" title="Primary key"><input type="checkbox" class="checkbox checkbox-xs ec-pk"'+(e.primaryKey?" checked":"")+'><span class="text-[10px] font-semibold uppercase text-base-content/50">PK</span></label>','<label class="flex cursor-pointer flex-col items-center gap-1" title="Not null"><input type="checkbox" class="checkbox checkbox-xs ec-notnull"'+(e.notNull?" checked":"")+'><span class="text-[10px] font-semibold uppercase text-base-content/50">NN</span></label>','<label class="flex cursor-pointer flex-col items-center gap-1" title="Unique"><input type="checkbox" class="checkbox checkbox-xs ec-unique"'+(e.unique?" checked":"")+'><span class="text-[10px] font-semibold uppercase text-base-content/50">UQ</span></label>',"</div>"].join("");const t=v.querySelector(".ec-fk-table"),l=v.querySelector(".ec-fk-col");async function n(e,t){if(l.innerHTML='<option value="">—</option>',e)try{const n=await Api.get("/api/tables/"+encodeURIComponent(e)+"/info");l.innerHTML='<option value="">—</option>'+n.columns.map(e=>'<option value="'+c(e.name)+'"'+(e.name===t?" selected":"")+">"+c(e.name)+"</option>").join("")}catch(e){}}e.fkTable&&await n(e.fkTable,e.fkTo),t.addEventListener("change",()=>n(t.value,"")),E.classList.remove("hidden"),E.scrollIntoView({behavior:"smooth",block:"nearest"})}({name:e.dataset.column||"",type:e.dataset.type||"TEXT",primaryKey:"true"===e.dataset.pk,notNull:"true"===e.dataset.notnull,unique:"true"===e.dataset.unique,defaultValue:e.dataset.default||"",fkTable:e.dataset.fkTable||"",fkTo:e.dataset.fkTo||""})})}),document.querySelectorAll(".col-drop").forEach(e=>{e.addEventListener("click",async()=>{const l=e.closest("tr");if(!l)return;const n=l.querySelector(".col-name-cell"),c=n?n.dataset.column:"";if(!c)return;await UI.confirm({title:"Drop Column",message:'Dropping column "'+c+'" will permanently delete all data stored in this column for all rows in table "'+t.table+'". This action cannot be undone.',item:c,itemType:"Column",confirmText:"Drop column",danger:!0})&&Api.del("/api/tables/"+a+"/columns/"+encodeURIComponent(c)).then(e=>{UI.showToast(e.message||"Column dropped.","success"),window.location.reload()}).catch(e=>UI.showError(e.message))})});const w=document.getElementById("drop-table");w&&w.addEventListener("click",async()=>{await UI.confirm({title:"Drop Table",message:'Dropping table "'+t.table+'" permanently erases the table schema, all indexes, and all stored records. This action cannot be undone.',item:t.table,itemType:"Table",requireInputMatch:t.table,confirmText:"Drop table",danger:!0})&&Api.del("/api/tables/"+a).then(()=>{UI.showToast("Table dropped.","success"),setTimeout(()=>{window.location.href=Api.basePath+"/"},400)}).catch(e=>UI.showError(e.message))});const A=document.getElementById("add-index"),q=document.getElementById("add-index-panel"),U=document.getElementById("index-name"),S=document.getElementById("index-unique"),B=document.getElementById("add-index-error"),N=document.getElementById("add-index-submit"),C=document.getElementById("add-index-cancel"),M=document.getElementById("index-preview"),R=document.getElementById("index-preview-code"),D=Array.prototype.slice.call(document.querySelectorAll(".index-col-btn"));let H=[];function P(){if(!M||!R)return;const e=H.slice(),l=(U.value||"").trim(),n=S&&S.checked?"UNIQUE ":"";if(!e.length)return M.classList.add("hidden"),void(R.textContent="");const a=l||"idx_"+t.table+"_"+e.join("_");R.textContent="CREATE "+n+'INDEX "'+a+'" ON "'+t.table+'" ('+e.map(e=>'"'+e+'"').join(", ")+");",M.classList.remove("hidden")}D.forEach(e=>e.addEventListener("click",()=>function(e){const t=e.dataset.column,l=H.indexOf(t),n=e.querySelector(".index-col-num");-1!==l?(H.splice(l,1),e.classList.remove("border-primary","bg-primary/10"),n&&n.classList.add("hidden")):(H.push(t),e.classList.add("border-primary","bg-primary/10"),n&&n.classList.remove("hidden")),H.forEach((e,t)=>{const l=D.find(t=>t.dataset.column===e),n=l&&l.querySelector(".index-col-num");n&&(n.textContent=String(t+1))}),P()}(e))),U&&U.addEventListener("input",P),S&&S.addEventListener("change",P),A&&q&&A.addEventListener("click",()=>{q.classList.toggle("hidden"),q.classList.contains("hidden")||(B.classList.add("hidden"),U.focus(),P())}),C&&C.addEventListener("click",()=>{q.classList.add("hidden"),B.classList.add("hidden")}),N&&N.addEventListener("click",async()=>{B.classList.add("hidden");const e=H.slice();if(!e.length)return s(B,"Select at least one column for the index.");const t={columns:e,unique:!(!S||!S.checked)},l=(U.value||"").trim();l&&(t.name=l),N.disabled=!0;try{const e=await Api.post("/api/tables/"+a+"/indexes",t);UI.showToast(e.message||"Index created.","success"),window.location.reload()}catch(e){s(B,e.message)}finally{N.disabled=!1}}),document.querySelectorAll(".index-drop").forEach(e=>{e.addEventListener("click",async()=>{const l=e.dataset.index;if(!l)return;await UI.confirm({title:"Drop Index",message:'Are you sure you want to permanently drop index "'+l+'" on table "'+t.table+'"?',item:l,itemType:"Index",confirmText:"Drop index",danger:!0})&&Api.del("/api/tables/"+a+"/indexes/"+encodeURIComponent(l)).then(e=>{UI.showToast(e.message||"Index dropped.","success"),window.location.reload()}).catch(e=>UI.showError(e.message))})}),h()}();
|