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.
Files changed (178) hide show
  1. package/README.md +1 -1
  2. package/dist/app.d.ts +1 -69
  3. package/dist/app.js +1 -257
  4. package/dist/auth/config.d.ts +1 -19
  5. package/dist/auth/config.js +1 -147
  6. package/dist/auth/crypto.d.ts +1 -27
  7. package/dist/auth/crypto.js +1 -133
  8. package/dist/auth/index.d.ts +1 -5
  9. package/dist/auth/index.js +1 -22
  10. package/dist/auth/middleware.d.ts +1 -14
  11. package/dist/auth/middleware.js +1 -100
  12. package/dist/auth/routes.d.ts +1 -7
  13. package/dist/auth/routes.js +1 -78
  14. package/dist/auth/types.d.ts +1 -35
  15. package/dist/auth/types.js +1 -11
  16. package/dist/cli/args.d.ts +1 -45
  17. package/dist/cli/args.js +1 -328
  18. package/dist/cli/config.d.ts +1 -56
  19. package/dist/cli/config.js +1 -133
  20. package/dist/cli/index.d.ts +1 -4
  21. package/dist/cli/index.js +1 -21
  22. package/dist/cli/runner.d.ts +1 -1
  23. package/dist/cli/runner.js +1 -222
  24. package/dist/cli.d.ts +1 -2
  25. package/dist/cli.js +1 -5
  26. package/dist/data/datasets.d.ts +1 -17
  27. package/dist/data/datasets.js +1 -133
  28. package/dist/data/detector.d.ts +1 -30
  29. package/dist/data/detector.js +1 -472
  30. package/dist/data/engine.d.ts +1 -10
  31. package/dist/data/engine.js +1 -211
  32. package/dist/data/index.d.ts +1 -5
  33. package/dist/data/index.js +1 -22
  34. package/dist/data/strategies.d.ts +1 -30
  35. package/dist/data/strategies.js +1 -343
  36. package/dist/data/types.d.ts +1 -73
  37. package/dist/data/types.js +1 -8
  38. package/dist/db/database.d.ts +1 -119
  39. package/dist/db/database.js +1 -471
  40. package/dist/db/export.d.ts +1 -12
  41. package/dist/db/export.js +1 -42
  42. package/dist/db/filters.d.ts +1 -22
  43. package/dist/db/filters.js +1 -158
  44. package/dist/db/index.d.ts +1 -8
  45. package/dist/db/index.js +1 -25
  46. package/dist/db/introspection.d.ts +1 -6
  47. package/dist/db/introspection.js +1 -172
  48. package/dist/db/manager.d.ts +1 -81
  49. package/dist/db/manager.js +1 -397
  50. package/dist/db/migrations.d.ts +1 -5
  51. package/dist/db/migrations.js +1 -110
  52. package/dist/db/postgres.d.ts +1 -85
  53. package/dist/db/postgres.js +1 -745
  54. package/dist/db/types.d.ts +1 -206
  55. package/dist/db/types.js +1 -7
  56. package/dist/index.d.ts +1 -23
  57. package/dist/index.js +1 -66
  58. package/dist/public/js/api.js +1 -47
  59. package/dist/public/js/app.js +1 -262
  60. package/dist/public/js/browse.js +1 -1371
  61. package/dist/public/js/databases.js +1 -235
  62. package/dist/public/js/designer.js +1 -258
  63. package/dist/public/js/forms.js +1 -826
  64. package/dist/public/js/icons.js +1 -51
  65. package/dist/public/js/inspector.js +1 -638
  66. package/dist/public/js/query.js +1 -322
  67. package/dist/public/js/schema.js +1 -447
  68. package/dist/public/js/seed.js +1 -1090
  69. package/dist/routes/api/helpers.d.ts +1 -42
  70. package/dist/routes/api/helpers.js +1 -133
  71. package/dist/routes/api/import-export.d.ts +1 -3
  72. package/dist/routes/api/import-export.js +1 -85
  73. package/dist/routes/api/index.d.ts +1 -4
  74. package/dist/routes/api/index.js +1 -37
  75. package/dist/routes/api/query.d.ts +1 -3
  76. package/dist/routes/api/query.js +1 -76
  77. package/dist/routes/api/rows.d.ts +1 -3
  78. package/dist/routes/api/rows.js +1 -373
  79. package/dist/routes/api/seed.d.ts +1 -3
  80. package/dist/routes/api/seed.js +1 -82
  81. package/dist/routes/api/tables.d.ts +1 -3
  82. package/dist/routes/api/tables.js +1 -160
  83. package/dist/routes/databases.d.ts +1 -26
  84. package/dist/routes/databases.js +1 -230
  85. package/dist/routes/index.d.ts +1 -3
  86. package/dist/routes/index.js +1 -20
  87. package/dist/routes/pages.d.ts +1 -9
  88. package/dist/routes/pages.js +1 -390
  89. package/dist/serverless.d.ts +1 -40
  90. package/dist/serverless.js +1 -210
  91. package/dist/sql/classifier.d.ts +1 -19
  92. package/dist/sql/classifier.js +1 -37
  93. package/dist/sql/generator.d.ts +1 -81
  94. package/dist/sql/generator.js +1 -348
  95. package/dist/sql/index.d.ts +1 -2
  96. package/dist/sql/index.js +1 -19
  97. package/dist/types/api.d.ts +1 -412
  98. package/dist/types/api.js +1 -9
  99. package/dist/utils/colors.d.ts +1 -40
  100. package/dist/utils/colors.js +1 -64
  101. package/dist/utils/common.d.ts +1 -50
  102. package/dist/utils/common.js +1 -229
  103. package/dist/utils/csv.d.ts +1 -15
  104. package/dist/utils/csv.js +1 -80
  105. package/dist/utils/datatype.d.ts +1 -49
  106. package/dist/utils/datatype.js +1 -222
  107. package/dist/utils/icons.d.ts +1 -11
  108. package/dist/utils/icons.js +1 -72
  109. package/dist/utils/index.d.ts +1 -4
  110. package/dist/utils/index.js +1 -21
  111. package/dist/utils/logger.d.ts +1 -10
  112. package/dist/utils/logger.js +1 -40
  113. package/dist/views/layouts/main.hbs +0 -4
  114. package/dist/views/pages/databases.hbs +0 -18
  115. package/dist/views/pages/designer.hbs +0 -10
  116. package/dist/views/pages/error.hbs +0 -2
  117. package/dist/views/pages/form.hbs +0 -5
  118. package/dist/views/pages/home.hbs +0 -8
  119. package/dist/views/pages/login.hbs +0 -13
  120. package/dist/views/pages/query.hbs +0 -9
  121. package/dist/views/pages/schema.hbs +0 -20
  122. package/dist/views/pages/seed.hbs +0 -31
  123. package/dist/views/pages/table.hbs +0 -14
  124. package/dist/views/partials/icon.hbs +0 -1
  125. package/dist/views/partials/navbar.hbs +0 -6
  126. package/dist/views/partials/sidebar.hbs +0 -8
  127. package/package.json +3 -2
  128. package/dist/app.js.map +0 -1
  129. package/dist/auth/config.js.map +0 -1
  130. package/dist/auth/crypto.js.map +0 -1
  131. package/dist/auth/index.js.map +0 -1
  132. package/dist/auth/middleware.js.map +0 -1
  133. package/dist/auth/routes.js.map +0 -1
  134. package/dist/auth/types.js.map +0 -1
  135. package/dist/cli/args.js.map +0 -1
  136. package/dist/cli/config.js.map +0 -1
  137. package/dist/cli/index.js.map +0 -1
  138. package/dist/cli/runner.js.map +0 -1
  139. package/dist/cli.js.map +0 -1
  140. package/dist/data/datasets.js.map +0 -1
  141. package/dist/data/detector.js.map +0 -1
  142. package/dist/data/engine.js.map +0 -1
  143. package/dist/data/index.js.map +0 -1
  144. package/dist/data/strategies.js.map +0 -1
  145. package/dist/data/types.js.map +0 -1
  146. package/dist/db/database.js.map +0 -1
  147. package/dist/db/export.js.map +0 -1
  148. package/dist/db/filters.js.map +0 -1
  149. package/dist/db/index.js.map +0 -1
  150. package/dist/db/introspection.js.map +0 -1
  151. package/dist/db/manager.js.map +0 -1
  152. package/dist/db/migrations.js.map +0 -1
  153. package/dist/db/postgres.js.map +0 -1
  154. package/dist/db/types.js.map +0 -1
  155. package/dist/index.js.map +0 -1
  156. package/dist/public/css/input.css +0 -302
  157. package/dist/routes/api/helpers.js.map +0 -1
  158. package/dist/routes/api/import-export.js.map +0 -1
  159. package/dist/routes/api/index.js.map +0 -1
  160. package/dist/routes/api/query.js.map +0 -1
  161. package/dist/routes/api/rows.js.map +0 -1
  162. package/dist/routes/api/seed.js.map +0 -1
  163. package/dist/routes/api/tables.js.map +0 -1
  164. package/dist/routes/databases.js.map +0 -1
  165. package/dist/routes/index.js.map +0 -1
  166. package/dist/routes/pages.js.map +0 -1
  167. package/dist/serverless.js.map +0 -1
  168. package/dist/sql/classifier.js.map +0 -1
  169. package/dist/sql/generator.js.map +0 -1
  170. package/dist/sql/index.js.map +0 -1
  171. package/dist/types/api.js.map +0 -1
  172. package/dist/utils/colors.js.map +0 -1
  173. package/dist/utils/common.js.map +0 -1
  174. package/dist/utils/csv.js.map +0 -1
  175. package/dist/utils/datatype.js.map +0 -1
  176. package/dist/utils/icons.js.map +0 -1
  177. package/dist/utils/index.js.map +0 -1
  178. package/dist/utils/logger.js.map +0 -1
@@ -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()}();