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,322 +1 @@
1
- /* Query editor: run arbitrary SQL, save/load named queries, render results. */
2
- (function () {
3
- 'use strict';
4
- const editor = document.getElementById('query-sql');
5
- const gutter = document.getElementById('query-gutter');
6
- const runBtn = document.getElementById('run-query');
7
- const messageEl = document.getElementById('query-message');
8
- const resultEl = document.getElementById('query-result');
9
- const headEl = document.getElementById('query-result-head');
10
- const bodyEl = document.getElementById('query-result-body');
11
- const resultSummary = document.getElementById('result-summary');
12
- const saveBtns = Array.prototype.slice.call(document.querySelectorAll('#save-query, #save-query-inline'));
13
- const saveName = document.getElementById('save-query-name');
14
- const selectEl = document.getElementById('saved-query-select');
15
- const deleteBtn = document.getElementById('delete-query');
16
- const exportCsvBtn = document.getElementById('export-csv');
17
- const exportJsonBtn = document.getElementById('export-json');
18
-
19
- let queriesCache = [];
20
-
21
- function setExportEnabled(enabled) {
22
- if (exportCsvBtn) exportCsvBtn.disabled = !enabled;
23
- if (exportJsonBtn) exportJsonBtn.disabled = !enabled;
24
- }
25
-
26
- // ---- Export results ------------------------------------------------------
27
-
28
- async function exportResults(format) {
29
- const sql = editor.value.trim();
30
- if (!sql) return;
31
- try {
32
- const res = await fetch(Api.url('/api/query/export'), {
33
- method: 'POST',
34
- headers: { 'Content-Type': 'application/json' },
35
- body: JSON.stringify({ sql, format }),
36
- });
37
- if (!res.ok) {
38
- let msg = 'Export failed (' + res.status + ').';
39
- try {
40
- const data = await res.json();
41
- if (data && data.error) msg = data.error;
42
- } catch (e) { /* ignore */ }
43
- throw new Error(msg);
44
- }
45
- const blob = await res.blob();
46
- const url = URL.createObjectURL(blob);
47
- const a = document.createElement('a');
48
- const stamp = new Date().toISOString().replace(/[:.]/g, '-');
49
- a.href = url;
50
- a.download = 'query-' + stamp + '.' + format;
51
- document.body.appendChild(a);
52
- a.click();
53
- a.remove();
54
- setTimeout(() => URL.revokeObjectURL(url), 1000);
55
- UI.showToast('Exported as ' + format.toUpperCase() + '.', 'success');
56
- } catch (e) {
57
- setMessage(e.message, 'error');
58
- }
59
- }
60
-
61
- if (exportCsvBtn) exportCsvBtn.addEventListener('click', () => exportResults('csv'));
62
- if (exportJsonBtn) exportJsonBtn.addEventListener('click', () => exportResults('json'));
63
-
64
- function escapeHtml(s) {
65
- return (window.UI && UI.escapeHtml) ? UI.escapeHtml(s) : String(s == null ? '' : s);
66
- }
67
-
68
- function setMessage(text, type) {
69
- if (!text) {
70
- messageEl.innerHTML = '';
71
- return;
72
- }
73
- const palette = {
74
- error: 'border-error/40 bg-error/10 text-error',
75
- success: 'border-success/40 bg-success/10 text-success',
76
- info: 'border-info/40 bg-info/10 text-info',
77
- warning: 'border-warning/40 bg-warning/10 text-warning',
78
- };
79
- messageEl.innerHTML = '<div class="flex items-start gap-2.5 rounded-xl border px-4 py-3 text-sm font-medium ' +
80
- (palette[type] || palette.info) + '"><span>' + escapeHtml(text) + '</span></div>';
81
- }
82
-
83
- function isJsonStr(s) {
84
- if (typeof s !== 'string') return false;
85
- const t = s.trim();
86
- if (!((t.startsWith('{') && t.endsWith('}')) || (t.startsWith('[') && t.endsWith(']')))) return false;
87
- try { JSON.parse(t); return true; } catch { return false; }
88
- }
89
-
90
- function isHexBlobStr(s) {
91
- return typeof s === 'string' && /^0x[0-9a-f]{4,}$/i.test(s.trim());
92
- }
93
-
94
- function renderTable(columns, rows) {
95
- headEl.innerHTML = '<tr>' + columns.map((c) => '<th class="whitespace-nowrap font-semibold"><span class="font-mono normal-case">' + escapeHtml(c) + '</span></th>').join('') + '</tr>';
96
- if (!rows.length) {
97
- bodyEl.innerHTML = '<tr><td colspan="' + Math.max(1, columns.length) + '" class="py-10 text-center text-base-content/45">No rows returned.</td></tr>';
98
- return;
99
- }
100
- bodyEl.innerHTML = rows.map((row) =>
101
- '<tr class="hover:bg-primary/[0.04]">' + columns.map((c) => {
102
- const v = row[c];
103
- if (v === null || v === undefined) {
104
- return '<td class="align-middle"><span class="chip chip-null">NULL</span></td>';
105
- }
106
- const s = String(v);
107
- const json = isJsonStr(s);
108
- const blob = isHexBlobStr(s);
109
- const short = s.length > 140 ? s.slice(0, 140) + '…' : s;
110
-
111
- if (json) {
112
- return '<td class="max-w-xs align-middle js-query-cell" data-col="' + escapeHtml(c) + '" data-value="' + escapeHtml(s) + '" data-json="true">' +
113
- '<div class="flex items-center gap-1.5">' +
114
- '<span class="inline-flex items-center gap-1 rounded-md bg-info/10 px-1.5 py-0.5 text-[11px] font-mono font-medium text-info">{ } JSON</span>' +
115
- '<span class="block max-w-[120px] truncate font-mono text-[12px] text-base-content/70">' + escapeHtml(short) + '</span>' +
116
- '<button type="button" class="btn btn-ghost btn-xs btn-circle js-query-inspect" title="Inspect JSON"><svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg></button>' +
117
- '</div></td>';
118
- }
119
-
120
- if (blob) {
121
- return '<td class="max-w-xs align-middle js-query-cell" data-col="' + escapeHtml(c) + '" data-value="' + escapeHtml(s) + '" data-blob="true">' +
122
- '<div class="flex items-center gap-1.5">' +
123
- '<span class="inline-flex items-center gap-1 rounded-md bg-secondary/10 px-1.5 py-0.5 text-[11px] font-mono font-medium text-secondary">🗃️ BLOB</span>' +
124
- '<span class="block max-w-[120px] truncate font-mono text-[12px] text-base-content/70">' + escapeHtml(short) + '</span>' +
125
- '<button type="button" class="btn btn-ghost btn-xs btn-circle js-query-inspect" title="Inspect BLOB"><svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg></button>' +
126
- '</div></td>';
127
- }
128
-
129
- return '<td class="max-w-xs align-middle js-query-cell" data-col="' + escapeHtml(c) + '" data-value="' + escapeHtml(s) + '">' +
130
- '<div class="flex items-center justify-between gap-1 group/cell">' +
131
- '<span class="truncate font-mono text-[13px]" title="' + escapeHtml(s) + '">' + escapeHtml(short) + '</span>' +
132
- (s.length > 25 ? '<button type="button" class="btn btn-ghost btn-xs btn-circle opacity-0 group-hover/cell:opacity-100 transition-opacity js-query-inspect shrink-0" title="Inspect text"><svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg></button>' : '') +
133
- '</div></td>';
134
- }).join('') + '</tr>',
135
- ).join('');
136
- }
137
-
138
- // Delegate inspection click
139
- if (bodyEl) {
140
- bodyEl.addEventListener('click', function (e) {
141
- const inspectBtn = e.target.closest('.js-query-inspect');
142
- const cell = e.target.closest('.js-query-cell');
143
- if (!inspectBtn && (!cell || e.detail < 2)) return;
144
- const targetCell = inspectBtn ? inspectBtn.closest('.js-query-cell') : cell;
145
- if (!targetCell) return;
146
-
147
- const col = targetCell.dataset.col || 'result';
148
- const val = targetCell.dataset.value || '';
149
- const isBlob = targetCell.dataset.blob === 'true';
150
- const isJson = targetCell.dataset.json === 'true';
151
-
152
- if (window.Inspector && window.Inspector.open) {
153
- window.Inspector.open({
154
- col: col,
155
- value: val,
156
- isNull: false,
157
- isBlob: isBlob,
158
- isJson: isJson,
159
- readonly: true,
160
- });
161
- }
162
- });
163
- }
164
-
165
- function renderMessageRow(text, kindLabel) {
166
- headEl.innerHTML = '';
167
- bodyEl.innerHTML = '<tr><td class="px-4 py-8 text-center"><p class="text-2xl font-extrabold text-primary">' +
168
- escapeHtml(text) + '</p><p class="mt-1 text-sm text-base-content/45">' + escapeHtml(kindLabel) + '</p></td></tr>';
169
- }
170
-
171
- function setSummary(text) {
172
- if (resultSummary) resultSummary.textContent = text;
173
- }
174
-
175
- function isDestructiveSql(sql) {
176
- var cleaned = sql.replace(/--.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '').trim();
177
- if (/\b(drop\s+(table|view|index|trigger)|truncate\b)/i.test(cleaned)) return true;
178
- if (/\bdelete\s+from\b/i.test(cleaned) && !/\bwhere\b/i.test(cleaned)) return true;
179
- return false;
180
- }
181
-
182
- async function run() {
183
- const sql = editor.value.trim();
184
- if (!sql) {
185
- setMessage('Enter a SQL statement first.', 'warning');
186
- return;
187
- }
188
- if (isDestructiveSql(sql)) {
189
- const confirmed = await UI.confirm({
190
- title: 'Execute Destructive SQL',
191
- message: 'This SQL statement contains potentially destructive operations (e.g. DROP, TRUNCATE, or unrestricted DELETE) that may permanently erase database schemas or data.',
192
- detailsHtml: '<pre class="font-mono text-xs text-base-content/85 whitespace-pre-wrap break-all">' + escapeHtml(sql.length > 350 ? sql.slice(0, 350) + '…' : sql) + '</pre>',
193
- confirmText: 'Execute anyway',
194
- danger: true,
195
- });
196
- if (!confirmed) return;
197
- }
198
- messageEl.innerHTML = '';
199
- resultEl.classList.add('hidden');
200
- setExportEnabled(false);
201
- const started = performance.now();
202
- try {
203
- const data = await Api.post('/api/query', { sql });
204
- const elapsed = Math.max(1, Math.round(performance.now() - started));
205
- resultEl.classList.remove('hidden');
206
- if (data.kind === 'count') {
207
- renderMessageRow(String(data.count), 'count');
208
- setSummary('Count query · ' + elapsed + 'ms');
209
- } else if (data.kind === 'select') {
210
- renderTable(data.columns, data.rows);
211
- setSummary(data.rows.length + ' row(s) · ' + elapsed + 'ms');
212
- setExportEnabled(true);
213
- } else {
214
- renderMessageRow(data.message || 'Statement executed.', 'write');
215
- setSummary(data.changes != null ? data.changes + ' row(s) affected · ' + elapsed + 'ms' : elapsed + 'ms');
216
- }
217
- } catch (e) {
218
- resultEl.classList.add('hidden');
219
- setMessage(e.message, 'error');
220
- }
221
- }
222
-
223
- // ---- Line numbers ------------------------------------------------------
224
-
225
- function updateGutter() {
226
- if (!gutter) return;
227
- const lines = editor.value.split('\n').length;
228
- let html = '';
229
- for (let i = 1; i <= lines; i++) html += i + '\n';
230
- gutter.textContent = html;
231
- gutter.scrollTop = editor.scrollTop;
232
- }
233
-
234
- if (editor && gutter) {
235
- editor.addEventListener('scroll', () => {
236
- gutter.scrollTop = editor.scrollTop;
237
- });
238
- editor.addEventListener('input', updateGutter);
239
- editor.addEventListener('keydown', (e) => {
240
- if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
241
- e.preventDefault();
242
- run();
243
- }
244
- });
245
- updateGutter();
246
- }
247
-
248
- // ---- Saved queries -----------------------------------------------------
249
-
250
- async function refreshQueries(selectName) {
251
- try {
252
- queriesCache = await Api.get('/api/queries');
253
- } catch (e) {
254
- queriesCache = [];
255
- }
256
- selectEl.innerHTML = '<option value="">— select a saved query —</option>' + queriesCache.map((q) =>
257
- '<option value="' + q.id + '"' + (selectName && q.name === selectName ? ' selected' : '') + '>' + escapeHtml(q.name) + '</option>',
258
- ).join('');
259
- deleteBtn.disabled = true;
260
- }
261
-
262
- async function saveQuery() {
263
- const sql = editor.value.trim();
264
- const name = saveName.value.trim();
265
- if (!sql) {
266
- setMessage('Write some SQL before saving.', 'warning');
267
- return;
268
- }
269
- if (!name) {
270
- setMessage('Give the query a name.', 'warning');
271
- return;
272
- }
273
- try {
274
- await Api.post('/api/queries', { name, sql });
275
- UI.showToast('Query saved.', 'success');
276
- await refreshQueries(name);
277
- } catch (e) {
278
- setMessage(e.message, 'error');
279
- }
280
- }
281
-
282
- async function deleteQuery() {
283
- const id = selectEl.value;
284
- if (!id) return;
285
- const q = queriesCache.find((x) => String(x.id) === String(id));
286
- const queryName = q ? q.name : 'Saved query';
287
- const confirmed = await UI.confirm({
288
- title: 'Delete Saved Query',
289
- message: 'Are you sure you want to delete the saved query "' + queryName + '"?',
290
- item: queryName,
291
- itemType: 'Saved Query',
292
- confirmText: 'Delete query',
293
- danger: true,
294
- });
295
- if (!confirmed) return;
296
- try {
297
- await Api.del('/api/queries/' + id);
298
- UI.showToast('Query deleted.', 'success');
299
- await refreshQueries();
300
- } catch (e) {
301
- setMessage(e.message, 'error');
302
- }
303
- }
304
-
305
- if (runBtn) runBtn.addEventListener('click', run);
306
- saveBtns.forEach((btn) => btn.addEventListener('click', saveQuery));
307
- if (selectEl) {
308
- selectEl.addEventListener('change', () => {
309
- const id = selectEl.value;
310
- deleteBtn.disabled = !id;
311
- if (!id) return;
312
- const q = queriesCache.find((x) => String(x.id) === String(id));
313
- if (q) {
314
- editor.value = q.sql;
315
- saveName.value = q.name;
316
- updateGutter();
317
- setMessage('Loaded "' + q.name + '".', 'info');
318
- }
319
- });
320
- }
321
- if (deleteBtn) deleteBtn.addEventListener('click', deleteQuery);
322
- })();
1
+ !function(){"use strict";const e=document.getElementById("query-sql"),t=document.getElementById("query-gutter"),n=document.getElementById("run-query"),s=document.getElementById("query-message"),r=document.getElementById("query-result"),o=document.getElementById("query-result-head"),a=document.getElementById("query-result-body"),i=document.getElementById("result-summary"),c=Array.prototype.slice.call(document.querySelectorAll("#save-query, #save-query-inline")),l=document.getElementById("save-query-name"),d=document.getElementById("saved-query-select"),u=document.getElementById("delete-query"),p=document.getElementById("export-csv"),m=document.getElementById("export-json");let y=[];function g(e){p&&(p.disabled=!e),m&&(m.disabled=!e)}async function f(t){const n=e.value.trim();if(n)try{const e=await fetch(Api.url("/api/query/export"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sql:n,format:t})});if(!e.ok){let t="Export failed ("+e.status+").";try{const n=await e.json();n&&n.error&&(t=n.error)}catch(e){}throw new Error(t)}const s=await e.blob(),r=URL.createObjectURL(s),o=document.createElement("a"),a=(new Date).toISOString().replace(/[:.]/g,"-");o.href=r,o.download="query-"+a+"."+t,document.body.appendChild(o),o.click(),o.remove(),setTimeout(()=>URL.revokeObjectURL(r),1e3),UI.showToast("Exported as "+t.toUpperCase()+".","success")}catch(e){x(e.message,"error")}}function w(e){return window.UI&&UI.escapeHtml?UI.escapeHtml(e):String(null==e?"":e)}function x(e,t){if(!e)return void(s.innerHTML="");const n={error:"border-error/40 bg-error/10 text-error",success:"border-success/40 bg-success/10 text-success",info:"border-info/40 bg-info/10 text-info",warning:"border-warning/40 bg-warning/10 text-warning"};s.innerHTML='<div class="flex items-start gap-2.5 rounded-xl border px-4 py-3 text-sm font-medium '+(n[t]||n.info)+'"><span>'+w(e)+"</span></div>"}function h(e,t){o.innerHTML="<tr>"+e.map(e=>'<th class="whitespace-nowrap font-semibold"><span class="font-mono normal-case">'+w(e)+"</span></th>").join("")+"</tr>",t.length?a.innerHTML=t.map(t=>'<tr class="hover:bg-primary/[0.04]">'+e.map(e=>{const n=t[e];if(null==n)return'<td class="align-middle"><span class="chip chip-null">NULL</span></td>';const s=String(n),r=function(e){if("string"!=typeof e)return!1;const t=e.trim();if(!(t.startsWith("{")&&t.endsWith("}")||t.startsWith("[")&&t.endsWith("]")))return!1;try{return JSON.parse(t),!0}catch{return!1}}(s),o=function(e){return"string"==typeof e&&/^0x[0-9a-f]{4,}$/i.test(e.trim())}(s),a=s.length>140?s.slice(0,140)+"…":s;return r?'<td class="max-w-xs align-middle js-query-cell" data-col="'+w(e)+'" data-value="'+w(s)+'" data-json="true"><div class="flex items-center gap-1.5"><span class="inline-flex items-center gap-1 rounded-md bg-info/10 px-1.5 py-0.5 text-[11px] font-mono font-medium text-info">{ } JSON</span><span class="block max-w-[120px] truncate font-mono text-[12px] text-base-content/70">'+w(a)+'</span><button type="button" class="btn btn-ghost btn-xs btn-circle js-query-inspect" title="Inspect JSON"><svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg></button></div></td>':o?'<td class="max-w-xs align-middle js-query-cell" data-col="'+w(e)+'" data-value="'+w(s)+'" data-blob="true"><div class="flex items-center gap-1.5"><span class="inline-flex items-center gap-1 rounded-md bg-secondary/10 px-1.5 py-0.5 text-[11px] font-mono font-medium text-secondary">🗃️ BLOB</span><span class="block max-w-[120px] truncate font-mono text-[12px] text-base-content/70">'+w(a)+'</span><button type="button" class="btn btn-ghost btn-xs btn-circle js-query-inspect" title="Inspect BLOB"><svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg></button></div></td>':'<td class="max-w-xs align-middle js-query-cell" data-col="'+w(e)+'" data-value="'+w(s)+'"><div class="flex items-center justify-between gap-1 group/cell"><span class="truncate font-mono text-[13px]" title="'+w(s)+'">'+w(a)+"</span>"+(s.length>25?'<button type="button" class="btn btn-ghost btn-xs btn-circle opacity-0 group-hover/cell:opacity-100 transition-opacity js-query-inspect shrink-0" title="Inspect text"><svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg></button>':"")+"</div></td>"}).join("")+"</tr>").join(""):a.innerHTML='<tr><td colspan="'+Math.max(1,e.length)+'" class="py-10 text-center text-base-content/45">No rows returned.</td></tr>'}function v(e,t){o.innerHTML="",a.innerHTML='<tr><td class="px-4 py-8 text-center"><p class="text-2xl font-extrabold text-primary">'+w(e)+'</p><p class="mt-1 text-sm text-base-content/45">'+w(t)+"</p></td></tr>"}function b(e){i&&(i.textContent=e)}async function q(){const t=e.value.trim();if(!t)return void x("Enter a SQL statement first.","warning");if(function(e){var t=e.replace(/--.*$/gm,"").replace(/\/\*[\s\S]*?\*\//g,"").trim();return!!/\b(drop\s+(table|view|index|trigger)|truncate\b)/i.test(t)||!(!/\bdelete\s+from\b/i.test(t)||/\bwhere\b/i.test(t))}(t)){if(!await UI.confirm({title:"Execute Destructive SQL",message:"This SQL statement contains potentially destructive operations (e.g. DROP, TRUNCATE, or unrestricted DELETE) that may permanently erase database schemas or data.",detailsHtml:'<pre class="font-mono text-xs text-base-content/85 whitespace-pre-wrap break-all">'+w(t.length>350?t.slice(0,350)+"…":t)+"</pre>",confirmText:"Execute anyway",danger:!0}))return}s.innerHTML="",r.classList.add("hidden"),g(!1);const n=performance.now();try{const e=await Api.post("/api/query",{sql:t}),s=Math.max(1,Math.round(performance.now()-n));r.classList.remove("hidden"),"count"===e.kind?(v(String(e.count),"count"),b("Count query · "+s+"ms")):"select"===e.kind?(h(e.columns,e.rows),b(e.rows.length+" row(s) · "+s+"ms"),g(!0)):(v(e.message||"Statement executed.","write"),b(null!=e.changes?e.changes+" row(s) affected · "+s+"ms":s+"ms"))}catch(e){r.classList.add("hidden"),x(e.message,"error")}}function k(){if(!t)return;const n=e.value.split("\n").length;let s="";for(let e=1;e<=n;e++)s+=e+"\n";t.textContent=s,t.scrollTop=e.scrollTop}async function E(e){try{y=await Api.get("/api/queries")}catch(e){y=[]}d.innerHTML='<option value="">— select a saved query —</option>'+y.map(t=>'<option value="'+t.id+'"'+(e&&t.name===e?" selected":"")+">"+w(t.name)+"</option>").join(""),u.disabled=!0}async function L(){const t=e.value.trim(),n=l.value.trim();if(t)if(n)try{await Api.post("/api/queries",{name:n,sql:t}),UI.showToast("Query saved.","success"),await E(n)}catch(e){x(e.message,"error")}else x("Give the query a name.","warning");else x("Write some SQL before saving.","warning")}p&&p.addEventListener("click",()=>f("csv")),m&&m.addEventListener("click",()=>f("json")),a&&a.addEventListener("click",function(e){const t=e.target.closest(".js-query-inspect"),n=e.target.closest(".js-query-cell");if(!t&&(!n||e.detail<2))return;const s=t?t.closest(".js-query-cell"):n;if(!s)return;const r=s.dataset.col||"result",o=s.dataset.value||"",a="true"===s.dataset.blob,i="true"===s.dataset.json;window.Inspector&&window.Inspector.open&&window.Inspector.open({col:r,value:o,isNull:!1,isBlob:a,isJson:i,readonly:!0})}),e&&t&&(e.addEventListener("scroll",()=>{t.scrollTop=e.scrollTop}),e.addEventListener("input",k),e.addEventListener("keydown",e=>{(e.ctrlKey||e.metaKey)&&"Enter"===e.key&&(e.preventDefault(),q())}),k()),n&&n.addEventListener("click",q),c.forEach(e=>e.addEventListener("click",L)),d&&d.addEventListener("change",()=>{const t=d.value;if(u.disabled=!t,!t)return;const n=y.find(e=>String(e.id)===String(t));n&&(e.value=n.sql,l.value=n.name,k(),x('Loaded "'+n.name+'".',"info"))}),u&&u.addEventListener("click",async function(){const e=d.value;if(!e)return;const t=y.find(t=>String(t.id)===String(e)),n=t?t.name:"Saved query";if(await UI.confirm({title:"Delete Saved Query",message:'Are you sure you want to delete the saved query "'+n+'"?',item:n,itemType:"Saved Query",confirmText:"Delete query",danger:!0}))try{await Api.del("/api/queries/"+e),UI.showToast("Query deleted.","success"),await E()}catch(e){x(e.message,"error")}})}();