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,826 +1 @@
1
- /* Dynamic insert/edit row form.
2
- * The form is built entirely from column metadata returned by the API, so the
3
- * same page handles every table. Supports run mode and "get query" preview. */
4
- (function () {
5
- 'use strict';
6
- const cfgEl = document.getElementById('form-config');
7
- if (!cfgEl) return;
8
-
9
- const cfg = JSON.parse(cfgEl.textContent);
10
- const form = document.getElementById('row-fields');
11
- const errorBox = document.getElementById('form-error');
12
- const sqlBox = document.getElementById('generated-sql');
13
- const sqlCode = document.getElementById('generated-sql-code');
14
- const submitBtn = document.getElementById('row-submit');
15
- const generateBtn = document.getElementById('row-generate');
16
- const copyBtn = document.getElementById('copy-sql');
17
- const t = encodeURIComponent(cfg.table);
18
-
19
- let info = null;
20
- let record = null;
21
- let fkOptions = {};
22
-
23
- function escapeHtml(s) {
24
- return (window.UI && UI.escapeHtml) ? UI.escapeHtml(s) : String(s == null ? '' : s);
25
- }
26
-
27
- function toLocalInput(v, type) {
28
- if (v == null || v === '') return '';
29
- const s = String(v);
30
- const t = (type || '').toUpperCase();
31
-
32
- // Date only
33
- if (t === 'DATE') {
34
- const dm = s.match(/^(\d{4}-\d{2}-\d{2})/);
35
- return dm ? dm[1] : s;
36
- }
37
-
38
- // Time only
39
- if (t === 'TIME' || t === 'TIMETZ' || t.startsWith('TIME ') || t.startsWith('TIME(')) {
40
- const tm = s.match(/(\d{2}:\d{2}(?::\d{2})?)/);
41
- return tm ? tm[1] : s;
42
- }
43
-
44
- // Datetime / Timestamp
45
- const m = s.match(/^(\d{4}-\d{2}-\d{2})[ T](\d{2}):(\d{2})(?::(\d{2}))?/);
46
- if (m) return m[1] + 'T' + m[2] + ':' + m[3] + (m[4] ? ':' + m[4] : '');
47
- return s;
48
- }
49
-
50
- function toLocalNow(dateOnly, timeOnly) {
51
- const d = new Date();
52
- const p = (n) => String(n).padStart(2, '0');
53
- if (dateOnly) {
54
- return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate());
55
- }
56
- if (timeOnly) {
57
- return p(d.getHours()) + ':' + p(d.getMinutes()) + ':' + p(d.getSeconds());
58
- }
59
- return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate()) + 'T' + p(d.getHours()) + ':' + p(d.getMinutes()) + ':' + p(d.getSeconds());
60
- }
61
-
62
- function generateUuidV4() {
63
- if (typeof crypto !== 'undefined' && crypto.randomUUID) {
64
- return crypto.randomUUID();
65
- }
66
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
67
- const r = (Math.random() * 16) | 0;
68
- const v = c === 'x' ? r : (r & 0x3) | 0x8;
69
- return v.toString(16);
70
- });
71
- }
72
-
73
- /**
74
- * Convert a column's raw default expression (dflt_value) into a form-friendly
75
- * value. Handles literals we can read safely; anything complex returns null.
76
- */
77
- function defaultToValue(col, dflt) {
78
- if (dflt === null || dflt === undefined) return null;
79
- let s = String(dflt).trim();
80
- if (s === '' || /^null$/i.test(s)) return null;
81
-
82
- // Strip PostgreSQL type casts, e.g. 'active'::character varying, 0::bigint, true
83
- s = s.replace(/::[a-zA-Z0-9_\s"\[\]]+$/i, '').trim();
84
-
85
- const type = (col.type || '').toUpperCase();
86
-
87
- // Single-quoted string literal, e.g. 'N/A', 'user'
88
- const str = s.match(/^'(.*)'$/s);
89
- if (str) return str[1].replace(/''/g, "'");
90
-
91
- // Numeric literal, e.g. 0, 42, 3.14
92
- if (/^[-+]?\d+(\.\d+)?$/.test(s)) return Number(s);
93
-
94
- // Boolean literal
95
- if (/^(true|false)$/i.test(s)) return /^true$/i.test(s);
96
-
97
- // UUID generators
98
- if (type === 'UUID' && /gen_random_uuid|uuid_generate/i.test(s)) {
99
- return generateUuidV4();
100
- }
101
-
102
- // BOOLEAN columns stored as 0/1 or true/false
103
- if (type === 'BOOLEAN' || type === 'BOOL') {
104
- if (s === '1' || /^true$/i.test(s)) return 1;
105
- if (s === '0' || /^false$/i.test(s)) return 0;
106
- }
107
-
108
- // current-timestamp / now() style defaults
109
- if ((type === 'DATE' || type === 'DATETIME' || type.includes('TIMESTAMP')) && /current_timestamp|now\s*\(|datetime\s*\(|date\s*\(|clock_timestamp|statement_timestamp|transaction_timestamp/i.test(s)) {
110
- return toLocalNow(type === 'DATE', false);
111
- }
112
- if ((type === 'TIME' || type === 'TIMETZ') && /current_time|now\s*\(|time\s*\(/i.test(s)) {
113
- return toLocalNow(false, true);
114
- }
115
-
116
- return null;
117
- }
118
-
119
- /** Value to pre-fill a control with: edit โ†’ the record's value; insert โ†’ the column default. */
120
- function initialValue(col) {
121
- if (cfg.mode === 'edit' && record) return record[col.name];
122
- if (cfg.mode === 'insert') return defaultToValue(col, col.dflt_value);
123
- return null;
124
- }
125
-
126
- function isTruthy(v) {
127
- return v === 1 || v === true || v === '1' || v === 'true';
128
- }
129
-
130
- // ---- Widgets --------------------------------------------------------------
131
-
132
- function createCalendarWidget(colName, colType, iv, isPk) {
133
- const type = (colType || '').toUpperCase();
134
- const isDateOnly = type === 'DATE';
135
- const isTimeOnly = type === 'TIME' || type === 'TIMETZ' || type.startsWith('TIME ') || type.startsWith('TIME(');
136
- const isTimestamp = type === 'DATETIME' || type.includes('TIMESTAMP');
137
-
138
- const container = document.createElement('div');
139
- container.className = 'space-y-1';
140
-
141
- const inputRow = document.createElement('div');
142
- inputRow.className = 'flex items-center gap-1.5';
143
-
144
- const input = document.createElement('input');
145
- input.type = isDateOnly ? 'date' : (isTimeOnly ? 'time' : 'datetime-local');
146
- if (isTimeOnly || isTimestamp) input.step = '1';
147
- input.className = 'field-input flex-1 font-mono text-[13px]';
148
- input.dataset.column = colName;
149
- if (iv != null) input.value = toLocalInput(iv, type);
150
-
151
- const formatLabel = document.createElement('div');
152
- formatLabel.className = 'text-[11px] text-base-content/50 font-mono transition-all';
153
- function updateLabel() {
154
- if (!input.value) {
155
- formatLabel.textContent = '(empty / NULL)';
156
- return;
157
- }
158
- try {
159
- if (isDateOnly) {
160
- const parts = input.value.split('-');
161
- const dt = new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
162
- formatLabel.textContent = '๐Ÿ“… ' + dt.toLocaleDateString(undefined, { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' });
163
- } else if (isTimeOnly) {
164
- formatLabel.textContent = 'โฐ ' + input.value;
165
- } else {
166
- const dt = new Date(input.value);
167
- if (!isNaN(dt.getTime())) {
168
- formatLabel.textContent = '๐Ÿ“… ' + dt.toLocaleString(undefined, { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' });
169
- } else {
170
- formatLabel.textContent = input.value;
171
- }
172
- }
173
- } catch (e) {
174
- formatLabel.textContent = input.value;
175
- }
176
- }
177
- input.addEventListener('input', updateLabel);
178
- input.addEventListener('change', updateLabel);
179
- updateLabel();
180
-
181
- const dd = document.createElement('div');
182
- dd.className = 'dropdown dropdown-end';
183
-
184
- const ddBtn = document.createElement('button');
185
- ddBtn.type = 'button';
186
- ddBtn.tabIndex = 0;
187
- ddBtn.className = 'btn btn-ghost btn-sm btn-square text-primary hover:bg-primary/10 transition';
188
- ddBtn.title = 'Open calendar selector & presets';
189
- ddBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>';
190
-
191
- const ddMenu = document.createElement('ul');
192
- ddMenu.tabIndex = 0;
193
- ddMenu.className = 'dropdown-content z-50 menu p-2 shadow-2xl bg-base-100 rounded-xl w-56 border border-base-300 text-xs space-y-0.5';
194
-
195
- function addPreset(label, icon, onClick) {
196
- const li = document.createElement('li');
197
- const btn = document.createElement('button');
198
- btn.type = 'button';
199
- btn.className = 'flex items-center gap-2 py-1.5 font-medium';
200
- btn.innerHTML = '<span>' + icon + '</span><span>' + label + '</span>';
201
- btn.addEventListener('click', () => {
202
- onClick();
203
- updateLabel();
204
- if (document.activeElement && typeof document.activeElement.blur === 'function') {
205
- document.activeElement.blur();
206
- }
207
- });
208
- li.appendChild(btn);
209
- ddMenu.appendChild(li);
210
- }
211
-
212
- addPreset('Open Picker', '๐Ÿ“…', () => {
213
- if (typeof input.showPicker === 'function') {
214
- try { input.showPicker(); } catch (e) { input.focus(); }
215
- } else {
216
- input.focus();
217
- }
218
- });
219
-
220
- if (isTimeOnly) {
221
- addPreset('Now', 'โšก', () => { input.value = toLocalNow(false, true); });
222
- addPreset('Start of Hour', 'โฑ๏ธ', () => {
223
- const d = new Date();
224
- const p = (n) => String(n).padStart(2, '0');
225
- input.value = p(d.getHours()) + ':00:00';
226
- });
227
- } else if (isDateOnly) {
228
- addPreset('Today', 'โšก', () => { input.value = toLocalNow(true, false); });
229
- addPreset('Yesterday', 'โช', () => {
230
- const d = new Date();
231
- d.setDate(d.getDate() - 1);
232
- const p = (n) => String(n).padStart(2, '0');
233
- input.value = d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate());
234
- });
235
- addPreset('Tomorrow', 'โฉ', () => {
236
- const d = new Date();
237
- d.setDate(d.getDate() + 1);
238
- const p = (n) => String(n).padStart(2, '0');
239
- input.value = d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate());
240
- });
241
- addPreset('+7 Days (1 Week)', '๐Ÿ—“๏ธ', () => {
242
- const d = new Date();
243
- d.setDate(d.getDate() + 7);
244
- const p = (n) => String(n).padStart(2, '0');
245
- input.value = d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate());
246
- });
247
- addPreset('+30 Days (1 Month)', '๐Ÿ“†', () => {
248
- const d = new Date();
249
- d.setDate(d.getDate() + 30);
250
- const p = (n) => String(n).padStart(2, '0');
251
- input.value = d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate());
252
- });
253
- } else {
254
- addPreset('Now', 'โšก', () => { input.value = toLocalNow(false, false); });
255
- addPreset('Today (Start of Day)', '๐ŸŒ…', () => {
256
- input.value = toLocalNow(true, false) + 'T00:00:00';
257
- });
258
- addPreset('Today (End of Day)', '๐ŸŒƒ', () => {
259
- input.value = toLocalNow(true, false) + 'T23:59:59';
260
- });
261
- addPreset('Yesterday', 'โช', () => {
262
- const d = new Date();
263
- d.setDate(d.getDate() - 1);
264
- const p = (n) => String(n).padStart(2, '0');
265
- input.value = d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate()) + 'T' + p(d.getHours()) + ':' + p(d.getMinutes()) + ':' + p(d.getSeconds());
266
- });
267
- addPreset('Tomorrow', 'โฉ', () => {
268
- const d = new Date();
269
- d.setDate(d.getDate() + 1);
270
- const p = (n) => String(n).padStart(2, '0');
271
- input.value = d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate()) + 'T' + p(d.getHours()) + ':' + p(d.getMinutes()) + ':' + p(d.getSeconds());
272
- });
273
- addPreset('+7 Days (1 Week)', '๐Ÿ—“๏ธ', () => {
274
- const d = new Date();
275
- d.setDate(d.getDate() + 7);
276
- const p = (n) => String(n).padStart(2, '0');
277
- input.value = d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate()) + 'T' + p(d.getHours()) + ':' + p(d.getMinutes()) + ':' + p(d.getSeconds());
278
- });
279
- }
280
-
281
- const divider = document.createElement('li');
282
- divider.className = 'border-t border-base-200 my-1';
283
- ddMenu.appendChild(divider);
284
-
285
- addPreset('Clear / Set NULL', '๐Ÿงน', () => { input.value = ''; });
286
-
287
- dd.appendChild(ddBtn);
288
- dd.appendChild(ddMenu);
289
-
290
- inputRow.appendChild(input);
291
- inputRow.appendChild(dd);
292
- container.appendChild(inputRow);
293
- container.appendChild(formatLabel);
294
-
295
- if (isPk && cfg.mode === 'edit') input.disabled = true;
296
- return container;
297
- }
298
-
299
- function createArrayWidget(colName, colType, iv, isPk) {
300
- const container = document.createElement('div');
301
- container.className = 'space-y-2';
302
-
303
- const hiddenInput = document.createElement('input');
304
- hiddenInput.type = 'hidden';
305
- hiddenInput.dataset.column = colName;
306
-
307
- let items = [];
308
- if (Array.isArray(iv)) {
309
- items = iv.map(String);
310
- } else if (typeof iv === 'string' && iv.trim().startsWith('{') && iv.trim().endsWith('}')) {
311
- const inner = iv.trim().slice(1, -1);
312
- items = inner ? inner.split(',').map((s) => s.trim().replace(/^"(.*)"$/, '$1')) : [];
313
- } else if (typeof iv === 'string' && iv.trim().startsWith('[') && iv.trim().endsWith(']')) {
314
- try {
315
- const parsed = JSON.parse(iv);
316
- if (Array.isArray(parsed)) items = parsed.map(String);
317
- } catch (e) {
318
- items = [];
319
- }
320
- } else if (iv != null && String(iv).trim() !== '') {
321
- items = [String(iv)];
322
- }
323
-
324
- function sync() {
325
- hiddenInput.value = items.length ? '{' + items.map((x) => (x.includes(',') || x.includes('"') ? '"' + x.replace(/"/g, '\\"') + '"' : x)).join(',') + '}' : '';
326
- renderChips();
327
- }
328
-
329
- const chipsContainer = document.createElement('div');
330
- chipsContainer.className = 'flex flex-wrap gap-1.5 min-h-[32px] p-1.5 rounded-lg border border-base-300 bg-base-100';
331
-
332
- function renderChips() {
333
- chipsContainer.innerHTML = '';
334
- if (!items.length) {
335
- chipsContainer.innerHTML = '<span class="text-xs text-base-content/40 italic self-center px-1.5">No array items (empty / NULL)</span>';
336
- return;
337
- }
338
- items.forEach((item, idx) => {
339
- const chip = document.createElement('span');
340
- chip.className = 'inline-flex items-center gap-1 rounded bg-primary/10 px-2 py-1 text-xs font-mono font-medium text-primary';
341
- chip.innerHTML = '<span>' + escapeHtml(item) + '</span><button type="button" class="text-primary hover:text-error ml-1 font-bold">&times;</button>';
342
- chip.querySelector('button').addEventListener('click', () => {
343
- items.splice(idx, 1);
344
- sync();
345
- });
346
- chipsContainer.appendChild(chip);
347
- });
348
- }
349
-
350
- const addRow = document.createElement('div');
351
- addRow.className = 'flex gap-1.5';
352
- const textInput = document.createElement('input');
353
- textInput.type = 'text';
354
- textInput.className = 'field-input flex-1 font-mono text-xs';
355
- textInput.placeholder = 'Type item and press Enter or click Addโ€ฆ';
356
- const addBtn = document.createElement('button');
357
- addBtn.type = 'button';
358
- addBtn.className = 'btn btn-ghost btn-xs text-primary font-medium';
359
- addBtn.textContent = '+ Add Item';
360
-
361
- function addItem() {
362
- const val = textInput.value.trim();
363
- if (val) {
364
- if (val.includes(',')) {
365
- val.split(',').map((x) => x.trim()).filter(Boolean).forEach((x) => items.push(x));
366
- } else {
367
- items.push(val);
368
- }
369
- textInput.value = '';
370
- sync();
371
- }
372
- }
373
-
374
- addBtn.addEventListener('click', addItem);
375
- textInput.addEventListener('keydown', (e) => {
376
- if (e.key === 'Enter') {
377
- e.preventDefault();
378
- addItem();
379
- }
380
- });
381
-
382
- addRow.appendChild(textInput);
383
- addRow.appendChild(addBtn);
384
-
385
- container.appendChild(hiddenInput);
386
- container.appendChild(chipsContainer);
387
- container.appendChild(addRow);
388
- sync();
389
-
390
- if (isPk && cfg.mode === 'edit') textInput.disabled = true;
391
- return container;
392
- }
393
-
394
- function createUuidWidget(colName, iv, isPk) {
395
- const uuidWrap = document.createElement('div');
396
- uuidWrap.className = 'space-y-1';
397
-
398
- const inputRow = document.createElement('div');
399
- inputRow.className = 'flex items-center gap-1.5';
400
-
401
- const control = document.createElement('input');
402
- control.type = 'text';
403
- control.className = 'field-input font-mono text-xs flex-1';
404
- control.placeholder = '00000000-0000-0000-0000-000000000000';
405
- control.dataset.column = colName;
406
- if (iv != null) control.value = String(iv);
407
-
408
- const validBadge = document.createElement('span');
409
- validBadge.className = 'text-[10px] font-mono transition-all text-base-content/40';
410
-
411
- function checkUuid() {
412
- const v = control.value.trim();
413
- if (!v) {
414
- validBadge.textContent = '';
415
- return;
416
- }
417
- const isOk = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(v);
418
- if (isOk) {
419
- validBadge.className = 'text-[10px] font-mono text-success font-semibold';
420
- validBadge.textContent = 'โœ“ Valid UUID v4';
421
- } else {
422
- validBadge.className = 'text-[10px] font-mono text-warning';
423
- validBadge.textContent = 'โš  Custom format';
424
- }
425
- }
426
- control.addEventListener('input', checkUuid);
427
- checkUuid();
428
-
429
- const genBtn = document.createElement('button');
430
- genBtn.type = 'button';
431
- genBtn.className = 'btn btn-ghost btn-xs gap-1 text-primary text-xs shrink-0';
432
- genBtn.innerHTML = 'โœจ Gen UUID';
433
- genBtn.addEventListener('click', () => {
434
- control.value = generateUuidV4();
435
- checkUuid();
436
- });
437
-
438
- inputRow.appendChild(control);
439
- inputRow.appendChild(genBtn);
440
- uuidWrap.appendChild(inputRow);
441
- uuidWrap.appendChild(validBadge);
442
-
443
- if (isPk && cfg.mode === 'edit') control.disabled = true;
444
- return uuidWrap;
445
- }
446
-
447
- function createJsonWidget(colName, colType, iv, isPk) {
448
- const jsonContainer = document.createElement('div');
449
- jsonContainer.className = 'space-y-1.5';
450
-
451
- const control = document.createElement('textarea');
452
- control.rows = 4;
453
- control.className = 'field-input font-mono text-[13px] w-full';
454
- control.placeholder = '{\n "key": "value"\n}';
455
- control.dataset.column = colName;
456
- if (iv != null) control.value = typeof iv === 'object' ? JSON.stringify(iv, null, 2) : String(iv);
457
-
458
- const btnRow = document.createElement('div');
459
- btnRow.className = 'flex items-center justify-between text-xs';
460
-
461
- const statusBadge = document.createElement('span');
462
- statusBadge.className = 'font-mono text-[11px] text-base-content/40';
463
-
464
- function validateJson() {
465
- const v = control.value.trim();
466
- if (!v) {
467
- statusBadge.textContent = '(empty / NULL)';
468
- statusBadge.className = 'font-mono text-[11px] text-base-content/40';
469
- return;
470
- }
471
- try {
472
- JSON.parse(v);
473
- statusBadge.textContent = 'โœ“ Valid JSON';
474
- statusBadge.className = 'font-mono text-[11px] text-success font-semibold';
475
- } catch (e) {
476
- statusBadge.textContent = 'โœ— Invalid JSON syntax';
477
- statusBadge.className = 'font-mono text-[11px] text-error font-medium';
478
- }
479
- }
480
- control.addEventListener('input', validateJson);
481
- validateJson();
482
-
483
- const actions = document.createElement('div');
484
- actions.className = 'flex items-center gap-1';
485
-
486
- const formatBtn = document.createElement('button');
487
- formatBtn.type = 'button';
488
- formatBtn.className = 'btn btn-ghost btn-xs text-xs';
489
- formatBtn.textContent = 'Beautify';
490
- formatBtn.addEventListener('click', () => {
491
- try {
492
- const parsed = JSON.parse(control.value);
493
- control.value = JSON.stringify(parsed, null, 2);
494
- validateJson();
495
- } catch (e) { UI.showError('Invalid JSON: ' + e.message); }
496
- });
497
-
498
- const minifyBtn = document.createElement('button');
499
- minifyBtn.type = 'button';
500
- minifyBtn.className = 'btn btn-ghost btn-xs text-xs';
501
- minifyBtn.textContent = 'Minify';
502
- minifyBtn.addEventListener('click', () => {
503
- try {
504
- const parsed = JSON.parse(control.value);
505
- control.value = JSON.stringify(parsed);
506
- validateJson();
507
- } catch (e) { UI.showError('Invalid JSON: ' + e.message); }
508
- });
509
-
510
- const inspectBtn = document.createElement('button');
511
- inspectBtn.type = 'button';
512
- inspectBtn.className = 'btn btn-ghost btn-xs gap-1 text-primary text-xs font-semibold';
513
- inspectBtn.innerHTML = '๐Ÿงฉ Inspector';
514
- inspectBtn.addEventListener('click', () => {
515
- if (window.Inspector && window.Inspector.open) {
516
- window.Inspector.open({
517
- table: cfg.table,
518
- pk: cfg.pk || '',
519
- col: colName,
520
- colType: colType,
521
- value: control.value,
522
- isNull: !control.value,
523
- isJson: true,
524
- readonly: false,
525
- onSave: (newVal) => {
526
- control.value = newVal;
527
- validateJson();
528
- },
529
- });
530
- }
531
- });
532
-
533
- actions.appendChild(formatBtn);
534
- actions.appendChild(minifyBtn);
535
- actions.appendChild(inspectBtn);
536
-
537
- btnRow.appendChild(statusBadge);
538
- btnRow.appendChild(actions);
539
-
540
- jsonContainer.appendChild(control);
541
- jsonContainer.appendChild(btnRow);
542
- if (isPk && cfg.mode === 'edit') control.disabled = true;
543
- return jsonContainer;
544
- }
545
-
546
- function renderField(col) {
547
- const isPk = info.primaryKey.indexOf(col.name) !== -1;
548
- const wrapper = document.createElement('div');
549
- wrapper.className = 'space-y-1.5';
550
-
551
- const label = document.createElement('label');
552
- label.className = 'field-label';
553
- label.innerHTML =
554
- '<span>' + escapeHtml(col.name) + '</span>' +
555
- (col.notnull ? '<span class="text-error">*</span>' : '') +
556
- (isPk ? '<span class="chip chip-pk">PK</span>' : '') +
557
- (col.type ? '<span class="chip chip-type">' + escapeHtml(col.type) + '</span>' : '') +
558
- (cfg.mode === 'insert' && col.dflt_value ? '<span class="chip chip-type">default</span>' : '');
559
- wrapper.appendChild(label);
560
-
561
- const fk = info.foreignKeys.find((f) => f.from === col.name);
562
- const type = (col.type || '').toUpperCase();
563
- const isBool = type === 'BOOLEAN' || type === 'BOOL';
564
- const isNum = type.startsWith('INT') || type === 'REAL' || type === 'NUMERIC' || type.startsWith('DECIMAL') || type === 'FLOAT' || type === 'DOUBLE' || type.startsWith('BIGINT') || type.startsWith('SMALLINT') || type.startsWith('SERIAL') || type === 'MONEY';
565
- const isBlob = type === 'BLOB' || type === 'BYTEA' || type === 'BINARY';
566
- const isDate = type === 'DATE';
567
- const isTime = type === 'TIME' || type === 'TIMETZ' || type.startsWith('TIME ') || type.startsWith('TIME(');
568
- const isTimestamp = type === 'DATETIME' || type.includes('TIMESTAMP');
569
- const isUuid = type === 'UUID';
570
- const isJson = type.includes('JSON') || col.name.toLowerCase().includes('json');
571
- const isArray = type.endsWith('[]') || type.startsWith('_') || type.includes('ARRAY');
572
- const isInet = type === 'INET' || type === 'CIDR' || type === 'MACADDR' || type === 'MACADDR8';
573
- const isInterval = type === 'INTERVAL';
574
- const isGeometry = type === 'POINT' || type === 'GEOMETRY' || type === 'BOX' || type === 'CIRCLE' || type === 'POLYGON';
575
- const isBit = type === 'BIT' || type === 'VARBIT' || type.startsWith('BIT(') || type.startsWith('VARBIT(');
576
- const iv = initialValue(col);
577
-
578
- let control;
579
- if (isBool) {
580
- const boolWrap = document.createElement('label');
581
- boolWrap.className = 'flex cursor-pointer items-center gap-2 pt-1';
582
- control = document.createElement('input');
583
- control.type = 'checkbox';
584
- control.className = 'field-check';
585
- control.dataset.column = col.name;
586
- if (isTruthy(iv)) control.checked = true;
587
- const span = document.createElement('span');
588
- span.className = 'text-sm text-base-content/70';
589
- span.textContent = 'True / Enabled';
590
- boolWrap.appendChild(control);
591
- boolWrap.appendChild(span);
592
- wrapper.appendChild(boolWrap);
593
- return wrapper;
594
- } else if (fk) {
595
- control = document.createElement('select');
596
- control.className = 'field-select';
597
- control.dataset.column = col.name;
598
- const opts = fkOptions[col.name] || [];
599
- const empty = document.createElement('option');
600
- empty.value = '';
601
- empty.textContent = '(not set)';
602
- control.appendChild(empty);
603
- opts.forEach((o) => {
604
- const opt = document.createElement('option');
605
- opt.value = String(o.value);
606
- opt.textContent = o.label === '' ? String(o.value) : o.label;
607
- if (iv != null && String(iv) === String(o.value)) opt.selected = true;
608
- control.appendChild(opt);
609
- });
610
- } else if (isDate || isTime || isTimestamp) {
611
- const calWidget = createCalendarWidget(col.name, col.type, iv, isPk);
612
- wrapper.appendChild(calWidget);
613
- return wrapper;
614
- } else if (isArray) {
615
- const arrWidget = createArrayWidget(col.name, col.type, iv, isPk);
616
- wrapper.appendChild(arrWidget);
617
- return wrapper;
618
- } else if (isUuid) {
619
- const uuidWidget = createUuidWidget(col.name, iv, isPk);
620
- wrapper.appendChild(uuidWidget);
621
- return wrapper;
622
- } else if (isJson || (iv && typeof iv === 'object')) {
623
- const jsonWidget = createJsonWidget(col.name, col.type, iv, isPk);
624
- wrapper.appendChild(jsonWidget);
625
- return wrapper;
626
- } else if (isInterval) {
627
- const intWrap = document.createElement('div');
628
- intWrap.className = 'space-y-1.5';
629
- control = document.createElement('input');
630
- control.type = 'text';
631
- control.className = 'field-input font-mono text-xs';
632
- control.placeholder = "e.g. '1 day', '02:30:00', '1 month 2 days'";
633
- control.dataset.column = col.name;
634
- if (iv != null) control.value = String(iv);
635
- const presets = document.createElement('div');
636
- presets.className = 'flex flex-wrap gap-1';
637
- ['1 day', '1 week', '1 month', '1 year', '1 hour', '30 mins'].forEach((preset) => {
638
- const btn = document.createElement('button');
639
- btn.type = 'button';
640
- btn.className = 'btn btn-ghost btn-xs text-[11px] font-mono';
641
- btn.textContent = preset;
642
- btn.addEventListener('click', () => { control.value = preset; });
643
- presets.appendChild(btn);
644
- });
645
- intWrap.appendChild(control);
646
- intWrap.appendChild(presets);
647
- if (isPk && cfg.mode === 'edit') control.disabled = true;
648
- wrapper.appendChild(intWrap);
649
- return wrapper;
650
- } else if (isGeometry) {
651
- control = document.createElement('input');
652
- control.type = 'text';
653
- control.className = 'field-input font-mono text-xs';
654
- control.placeholder = "(x, y) e.g. '(12.345, 67.890)'";
655
- control.dataset.column = col.name;
656
- if (iv != null) control.value = String(iv);
657
- } else if (isBit) {
658
- control = document.createElement('input');
659
- control.type = 'text';
660
- control.className = 'field-input font-mono text-xs';
661
- control.placeholder = "Binary e.g. '101010'";
662
- control.dataset.column = col.name;
663
- if (iv != null) control.value = String(iv);
664
- } else if (isInet) {
665
- control = document.createElement('input');
666
- control.type = 'text';
667
- control.className = 'field-input font-mono text-xs';
668
- control.placeholder = type === 'MACADDR' ? '08:00:2b:01:02:03' : '192.168.1.1 or 2001:db8::1';
669
- control.dataset.column = col.name;
670
- if (iv != null) control.value = String(iv);
671
- } else if (isNum) {
672
- control = document.createElement('input');
673
- control.type = 'number';
674
- const isInteger = type.startsWith('INTEGER') || type === 'INT' || type.startsWith('BIGINT') || type.startsWith('SMALLINT') || type.startsWith('SERIAL');
675
- control.step = isInteger ? '1' : 'any';
676
- control.className = 'field-input font-mono';
677
- control.dataset.column = col.name;
678
- if (type.startsWith('SERIAL') && cfg.mode === 'insert' && (iv == null || iv === '')) {
679
- control.placeholder = '(auto-increment serial)';
680
- }
681
- if (iv != null) control.value = String(iv);
682
- } else if (isBlob) {
683
- const blobContainer = document.createElement('div');
684
- blobContainer.className = 'space-y-2';
685
-
686
- control = document.createElement('input');
687
- control.type = 'text';
688
- control.className = 'field-input font-mono text-xs';
689
- control.placeholder = type === 'BYTEA' ? '\\xโ€ฆ or 0xโ€ฆ hex' : '0xโ€ฆ hex or plain text';
690
- control.dataset.column = col.name;
691
- if (iv != null) control.value = String(iv);
692
-
693
- const fileRow = document.createElement('div');
694
- fileRow.className = 'flex flex-wrap items-center gap-2';
695
-
696
- const fileInput = document.createElement('input');
697
- fileInput.type = 'file';
698
- fileInput.className = 'file-input file-input-bordered file-input-xs w-full max-w-xs';
699
-
700
- const previewThumb = document.createElement('img');
701
- previewThumb.className = 'h-7 max-w-12 rounded object-cover border border-base-300 bg-base-200 hidden';
702
-
703
- fileInput.addEventListener('change', (e) => {
704
- const file = e.target.files && e.target.files[0];
705
- if (!file) return;
706
- const reader = new FileReader();
707
- reader.onload = () => {
708
- const arrBuffer = reader.result;
709
- const bytes = new Uint8Array(arrBuffer);
710
- let hex = type === 'BYTEA' ? '\\x' : '0x';
711
- for (let i = 0; i < bytes.length; i++) {
712
- hex += ('00' + bytes[i].toString(16)).slice(-2);
713
- }
714
- control.value = hex;
715
-
716
- if (file.type.startsWith('image/')) {
717
- const dataUrlReader = new FileReader();
718
- dataUrlReader.onload = () => {
719
- previewThumb.src = dataUrlReader.result;
720
- previewThumb.classList.remove('hidden');
721
- };
722
- dataUrlReader.readAsDataURL(file);
723
- } else {
724
- previewThumb.classList.add('hidden');
725
- }
726
- };
727
- reader.readAsArrayBuffer(file);
728
- });
729
-
730
- fileRow.appendChild(fileInput);
731
- fileRow.appendChild(previewThumb);
732
- blobContainer.appendChild(control);
733
- blobContainer.appendChild(fileRow);
734
- if (isPk && cfg.mode === 'edit') control.disabled = true;
735
- wrapper.appendChild(blobContainer);
736
- return wrapper;
737
- } else {
738
- control = document.createElement('input');
739
- control.type = 'text';
740
- control.className = 'field-input';
741
- control.dataset.column = col.name;
742
- if (iv != null) control.value = String(iv);
743
- }
744
- if (isPk && cfg.mode === 'edit') control.disabled = true;
745
- wrapper.appendChild(control);
746
- return wrapper;
747
- }
748
-
749
- function render() {
750
- form.innerHTML = '';
751
- info.columns.forEach((col) => form.appendChild(renderField(col)));
752
- }
753
-
754
- function collect() {
755
- const values = {};
756
- form.querySelectorAll('[data-column]').forEach((el) => {
757
- const col = el.dataset.column;
758
- if (el.type === 'checkbox') values[col] = el.checked ? 'true' : '';
759
- else values[col] = el.value;
760
- });
761
- return values;
762
- }
763
-
764
- function showSql(sql) {
765
- sqlCode.textContent = sql;
766
- sqlBox.classList.remove('hidden');
767
- sqlBox.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
768
- }
769
-
770
- async function submit(runMode) {
771
- errorBox.classList.add('hidden');
772
- const values = collect();
773
- const edit = cfg.mode === 'edit';
774
- let path;
775
- if (edit) {
776
- path = '/api/tables/' + t + '/row/' + encodeURIComponent(cfg.pk) + (runMode ? '' : '/generate');
777
- } else {
778
- path = '/api/tables/' + t + '/rows' + (runMode ? '' : '/generate');
779
- }
780
- const method = edit ? 'PUT' : 'POST';
781
- try {
782
- const data = await Api.request(method, path, { values });
783
- if (runMode) {
784
- UI.showToast(data.message || 'Saved.', 'success');
785
- setTimeout(() => {
786
- window.location.href = Api.basePath + '/tables/' + encodeURIComponent(cfg.table);
787
- }, 500);
788
- } else {
789
- showSql(data.sql);
790
- }
791
- } catch (e) {
792
- errorBox.classList.remove('hidden');
793
- errorBox.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(e.message) + '</span></div>';
794
- }
795
- }
796
-
797
- async function init() {
798
- try {
799
- info = await Api.get('/api/tables/' + t + '/info');
800
- if (cfg.mode === 'edit') {
801
- record = await Api.get('/api/tables/' + t + '/row/' + encodeURIComponent(cfg.pk));
802
- }
803
- try {
804
- fkOptions = await Api.get('/api/tables/' + t + '/fk-options');
805
- } catch (e) {
806
- fkOptions = {};
807
- }
808
- render();
809
- } catch (e) {
810
- errorBox.classList.remove('hidden');
811
- errorBox.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(e.message) + '</span></div>';
812
- }
813
- }
814
-
815
- if (submitBtn) submitBtn.addEventListener('click', () => submit(true));
816
- if (generateBtn) generateBtn.addEventListener('click', () => submit(false));
817
- if (copyBtn) {
818
- copyBtn.addEventListener('click', () => {
819
- navigator.clipboard.writeText(sqlCode.textContent)
820
- .then(() => UI.showToast('Copied to clipboard.', 'success'))
821
- .catch(() => UI.showError('Could not copy.'));
822
- });
823
- }
824
-
825
- init();
826
- })();
1
+ !function(){"use strict";const e=document.getElementById("form-config");if(!e)return;const t=JSON.parse(e.textContent),n=document.getElementById("row-fields"),a=document.getElementById("form-error"),s=document.getElementById("generated-sql"),o=document.getElementById("generated-sql-code"),r=document.getElementById("row-submit"),l=document.getElementById("row-generate"),i=document.getElementById("copy-sql"),c=encodeURIComponent(t.table);let d=null,m=null,u={};function p(e){return window.UI&&UI.escapeHtml?UI.escapeHtml(e):String(null==e?"":e)}function x(e,t){const n=new Date,a=e=>String(e).padStart(2,"0");return e?n.getFullYear()+"-"+a(n.getMonth()+1)+"-"+a(n.getDate()):t?a(n.getHours())+":"+a(n.getMinutes())+":"+a(n.getSeconds()):n.getFullYear()+"-"+a(n.getMonth()+1)+"-"+a(n.getDate())+"T"+a(n.getHours())+":"+a(n.getMinutes())+":"+a(n.getSeconds())}function f(){return"undefined"!=typeof crypto&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{const t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})}function h(e){return"edit"===t.mode&&m?m[e.name]:"insert"===t.mode?function(e,t){if(null==t)return null;let n=String(t).trim();if(""===n||/^null$/i.test(n))return null;n=n.replace(/::[a-zA-Z0-9_\s"\[\]]+$/i,"").trim();const a=(e.type||"").toUpperCase(),s=n.match(/^'(.*)'$/s);if(s)return s[1].replace(/''/g,"'");if(/^[-+]?\d+(\.\d+)?$/.test(n))return Number(n);if(/^(true|false)$/i.test(n))return/^true$/i.test(n);if("UUID"===a&&/gen_random_uuid|uuid_generate/i.test(n))return f();if("BOOLEAN"===a||"BOOL"===a){if("1"===n||/^true$/i.test(n))return 1;if("0"===n||/^false$/i.test(n))return 0}return("DATE"===a||"DATETIME"===a||a.includes("TIMESTAMP"))&&/current_timestamp|now\s*\(|datetime\s*\(|date\s*\(|clock_timestamp|statement_timestamp|transaction_timestamp/i.test(n)?x("DATE"===a,!1):"TIME"!==a&&"TIMETZ"!==a||!/current_time|now\s*\(|time\s*\(/i.test(n)?null:x(!1,!0)}(e,e.dflt_value):null}function g(e,n,a,s){const o=(n||"").toUpperCase(),r="DATE"===o,l="TIME"===o||"TIMETZ"===o||o.startsWith("TIME ")||o.startsWith("TIME("),i="DATETIME"===o||o.includes("TIMESTAMP"),c=document.createElement("div");c.className="space-y-1";const d=document.createElement("div");d.className="flex items-center gap-1.5";const m=document.createElement("input");m.type=r?"date":l?"time":"datetime-local",(l||i)&&(m.step="1"),m.className="field-input flex-1 font-mono text-[13px]",m.dataset.column=e,null!=a&&(m.value=function(e,t){if(null==e||""===e)return"";const n=String(e),a=(t||"").toUpperCase();if("DATE"===a){const e=n.match(/^(\d{4}-\d{2}-\d{2})/);return e?e[1]:n}if("TIME"===a||"TIMETZ"===a||a.startsWith("TIME ")||a.startsWith("TIME(")){const e=n.match(/(\d{2}:\d{2}(?::\d{2})?)/);return e?e[1]:n}const s=n.match(/^(\d{4}-\d{2}-\d{2})[ T](\d{2}):(\d{2})(?::(\d{2}))?/);return s?s[1]+"T"+s[2]+":"+s[3]+(s[4]?":"+s[4]:""):n}(a,o));const u=document.createElement("div");function p(){if(m.value)try{if(r){const e=m.value.split("-"),t=new Date(Number(e[0]),Number(e[1])-1,Number(e[2]));u.textContent="๐Ÿ“… "+t.toLocaleDateString(void 0,{weekday:"short",year:"numeric",month:"short",day:"numeric"})}else if(l)u.textContent="โฐ "+m.value;else{const e=new Date(m.value);isNaN(e.getTime())?u.textContent=m.value:u.textContent="๐Ÿ“… "+e.toLocaleString(void 0,{weekday:"short",year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}}catch(e){u.textContent=m.value}else u.textContent="(empty / NULL)"}u.className="text-[11px] text-base-content/50 font-mono transition-all",m.addEventListener("input",p),m.addEventListener("change",p),p();const f=document.createElement("div");f.className="dropdown dropdown-end";const h=document.createElement("button");h.type="button",h.tabIndex=0,h.className="btn btn-ghost btn-sm btn-square text-primary hover:bg-primary/10 transition",h.title="Open calendar selector & presets",h.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>';const g=document.createElement("ul");function E(e,t,n){const a=document.createElement("li"),s=document.createElement("button");s.type="button",s.className="flex items-center gap-2 py-1.5 font-medium",s.innerHTML="<span>"+t+"</span><span>"+e+"</span>",s.addEventListener("click",()=>{n(),p(),document.activeElement&&"function"==typeof document.activeElement.blur&&document.activeElement.blur()}),a.appendChild(s),g.appendChild(a)}g.tabIndex=0,g.className="dropdown-content z-50 menu p-2 shadow-2xl bg-base-100 rounded-xl w-56 border border-base-300 text-xs space-y-0.5",E("Open Picker","๐Ÿ“…",()=>{if("function"==typeof m.showPicker)try{m.showPicker()}catch(e){m.focus()}else m.focus()}),l?(E("Now","โšก",()=>{m.value=x(!1,!0)}),E("Start of Hour","โฑ๏ธ",()=>{const e=new Date;var t;m.value=(t=e.getHours(),String(t).padStart(2,"0")+":00:00")})):r?(E("Today","โšก",()=>{m.value=x(!0,!1)}),E("Yesterday","โช",()=>{const e=new Date;e.setDate(e.getDate()-1);const t=e=>String(e).padStart(2,"0");m.value=e.getFullYear()+"-"+t(e.getMonth()+1)+"-"+t(e.getDate())}),E("Tomorrow","โฉ",()=>{const e=new Date;e.setDate(e.getDate()+1);const t=e=>String(e).padStart(2,"0");m.value=e.getFullYear()+"-"+t(e.getMonth()+1)+"-"+t(e.getDate())}),E("+7 Days (1 Week)","๐Ÿ—“๏ธ",()=>{const e=new Date;e.setDate(e.getDate()+7);const t=e=>String(e).padStart(2,"0");m.value=e.getFullYear()+"-"+t(e.getMonth()+1)+"-"+t(e.getDate())}),E("+30 Days (1 Month)","๐Ÿ“†",()=>{const e=new Date;e.setDate(e.getDate()+30);const t=e=>String(e).padStart(2,"0");m.value=e.getFullYear()+"-"+t(e.getMonth()+1)+"-"+t(e.getDate())})):(E("Now","โšก",()=>{m.value=x(!1,!1)}),E("Today (Start of Day)","๐ŸŒ…",()=>{m.value=x(!0,!1)+"T00:00:00"}),E("Today (End of Day)","๐ŸŒƒ",()=>{m.value=x(!0,!1)+"T23:59:59"}),E("Yesterday","โช",()=>{const e=new Date;e.setDate(e.getDate()-1);const t=e=>String(e).padStart(2,"0");m.value=e.getFullYear()+"-"+t(e.getMonth()+1)+"-"+t(e.getDate())+"T"+t(e.getHours())+":"+t(e.getMinutes())+":"+t(e.getSeconds())}),E("Tomorrow","โฉ",()=>{const e=new Date;e.setDate(e.getDate()+1);const t=e=>String(e).padStart(2,"0");m.value=e.getFullYear()+"-"+t(e.getMonth()+1)+"-"+t(e.getDate())+"T"+t(e.getHours())+":"+t(e.getMinutes())+":"+t(e.getSeconds())}),E("+7 Days (1 Week)","๐Ÿ—“๏ธ",()=>{const e=new Date;e.setDate(e.getDate()+7);const t=e=>String(e).padStart(2,"0");m.value=e.getFullYear()+"-"+t(e.getMonth()+1)+"-"+t(e.getDate())+"T"+t(e.getHours())+":"+t(e.getMinutes())+":"+t(e.getSeconds())}));const y=document.createElement("li");return y.className="border-t border-base-200 my-1",g.appendChild(y),E("Clear / Set NULL","๐Ÿงน",()=>{m.value=""}),f.appendChild(h),f.appendChild(g),d.appendChild(m),d.appendChild(f),c.appendChild(d),c.appendChild(u),s&&"edit"===t.mode&&(m.disabled=!0),c}function E(e,n,a,s){const o=document.createElement("div");o.className="space-y-2";const r=document.createElement("input");r.type="hidden",r.dataset.column=e;let l=[];if(Array.isArray(a))l=a.map(String);else if("string"==typeof a&&a.trim().startsWith("{")&&a.trim().endsWith("}")){const e=a.trim().slice(1,-1);l=e?e.split(",").map(e=>e.trim().replace(/^"(.*)"$/,"$1")):[]}else if("string"==typeof a&&a.trim().startsWith("[")&&a.trim().endsWith("]"))try{const e=JSON.parse(a);Array.isArray(e)&&(l=e.map(String))}catch(e){l=[]}else null!=a&&""!==String(a).trim()&&(l=[String(a)]);function i(){r.value=l.length?"{"+l.map(e=>e.includes(",")||e.includes('"')?'"'+e.replace(/"/g,'\\"')+'"':e).join(",")+"}":"",function(){if(c.innerHTML="",!l.length)return void(c.innerHTML='<span class="text-xs text-base-content/40 italic self-center px-1.5">No array items (empty / NULL)</span>');l.forEach((e,t)=>{const n=document.createElement("span");n.className="inline-flex items-center gap-1 rounded bg-primary/10 px-2 py-1 text-xs font-mono font-medium text-primary",n.innerHTML="<span>"+p(e)+'</span><button type="button" class="text-primary hover:text-error ml-1 font-bold">&times;</button>',n.querySelector("button").addEventListener("click",()=>{l.splice(t,1),i()}),c.appendChild(n)})}()}const c=document.createElement("div");c.className="flex flex-wrap gap-1.5 min-h-[32px] p-1.5 rounded-lg border border-base-300 bg-base-100";const d=document.createElement("div");d.className="flex gap-1.5";const m=document.createElement("input");m.type="text",m.className="field-input flex-1 font-mono text-xs",m.placeholder="Type item and press Enter or click Addโ€ฆ";const u=document.createElement("button");function x(){const e=m.value.trim();e&&(e.includes(",")?e.split(",").map(e=>e.trim()).filter(Boolean).forEach(e=>l.push(e)):l.push(e),m.value="",i())}return u.type="button",u.className="btn btn-ghost btn-xs text-primary font-medium",u.textContent="+ Add Item",u.addEventListener("click",x),m.addEventListener("keydown",e=>{"Enter"===e.key&&(e.preventDefault(),x())}),d.appendChild(m),d.appendChild(u),o.appendChild(r),o.appendChild(c),o.appendChild(d),i(),s&&"edit"===t.mode&&(m.disabled=!0),o}function y(e){const n=-1!==d.primaryKey.indexOf(e.name),a=document.createElement("div");a.className="space-y-1.5";const s=document.createElement("label");s.className="field-label",s.innerHTML="<span>"+p(e.name)+"</span>"+(e.notnull?'<span class="text-error">*</span>':"")+(n?'<span class="chip chip-pk">PK</span>':"")+(e.type?'<span class="chip chip-type">'+p(e.type)+"</span>":"")+("insert"===t.mode&&e.dflt_value?'<span class="chip chip-type">default</span>':""),a.appendChild(s);const o=d.foreignKeys.find(t=>t.from===e.name),r=(e.type||"").toUpperCase(),l="BOOLEAN"===r||"BOOL"===r,i=r.startsWith("INT")||"REAL"===r||"NUMERIC"===r||r.startsWith("DECIMAL")||"FLOAT"===r||"DOUBLE"===r||r.startsWith("BIGINT")||r.startsWith("SMALLINT")||r.startsWith("SERIAL")||"MONEY"===r,c="BLOB"===r||"BYTEA"===r||"BINARY"===r,m="DATE"===r,x="TIME"===r||"TIMETZ"===r||r.startsWith("TIME ")||r.startsWith("TIME("),y="DATETIME"===r||r.includes("TIMESTAMP"),b="UUID"===r,v=r.includes("JSON")||e.name.toLowerCase().includes("json"),N=r.endsWith("[]")||r.startsWith("_")||r.includes("ARRAY"),C="INET"===r||"CIDR"===r||"MACADDR"===r||"MACADDR8"===r,T="INTERVAL"===r,I="POINT"===r||"GEOMETRY"===r||"BOX"===r||"CIRCLE"===r||"POLYGON"===r,w="BIT"===r||"VARBIT"===r||r.startsWith("BIT(")||r.startsWith("VARBIT("),S=h(e);let D;if(l){const t=document.createElement("label");t.className="flex cursor-pointer items-center gap-2 pt-1",D=document.createElement("input"),D.type="checkbox",D.className="field-check",D.dataset.column=e.name,(1===(L=S)||!0===L||"1"===L||"true"===L)&&(D.checked=!0);const n=document.createElement("span");return n.className="text-sm text-base-content/70",n.textContent="True / Enabled",t.appendChild(D),t.appendChild(n),a.appendChild(t),a}if(o){D=document.createElement("select"),D.className="field-select",D.dataset.column=e.name;const t=u[e.name]||[],n=document.createElement("option");n.value="",n.textContent="(not set)",D.appendChild(n),t.forEach(e=>{const t=document.createElement("option");t.value=String(e.value),t.textContent=""===e.label?String(e.value):e.label,null!=S&&String(S)===String(e.value)&&(t.selected=!0),D.appendChild(t)})}else{if(m||x||y){const t=g(e.name,e.type,S,n);return a.appendChild(t),a}if(N){const t=E(e.name,e.type,S,n);return a.appendChild(t),a}if(b){const s=function(e,n,a){const s=document.createElement("div");s.className="space-y-1";const o=document.createElement("div");o.className="flex items-center gap-1.5";const r=document.createElement("input");r.type="text",r.className="field-input font-mono text-xs flex-1",r.placeholder="00000000-0000-0000-0000-000000000000",r.dataset.column=e,null!=n&&(r.value=String(n));const l=document.createElement("span");function i(){const e=r.value.trim();e?/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(e)?(l.className="text-[10px] font-mono text-success font-semibold",l.textContent="โœ“ Valid UUID v4"):(l.className="text-[10px] font-mono text-warning",l.textContent="โš  Custom format"):l.textContent=""}l.className="text-[10px] font-mono transition-all text-base-content/40",r.addEventListener("input",i),i();const c=document.createElement("button");return c.type="button",c.className="btn btn-ghost btn-xs gap-1 text-primary text-xs shrink-0",c.innerHTML="โœจ Gen UUID",c.addEventListener("click",()=>{r.value=f(),i()}),o.appendChild(r),o.appendChild(c),s.appendChild(o),s.appendChild(l),a&&"edit"===t.mode&&(r.disabled=!0),s}(e.name,S,n);return a.appendChild(s),a}if(v||S&&"object"==typeof S){const s=function(e,n,a,s){const o=document.createElement("div");o.className="space-y-1.5";const r=document.createElement("textarea");r.rows=4,r.className="field-input font-mono text-[13px] w-full",r.placeholder='{\n "key": "value"\n}',r.dataset.column=e,null!=a&&(r.value="object"==typeof a?JSON.stringify(a,null,2):String(a));const l=document.createElement("div");l.className="flex items-center justify-between text-xs";const i=document.createElement("span");function c(){const e=r.value.trim();if(!e)return i.textContent="(empty / NULL)",void(i.className="font-mono text-[11px] text-base-content/40");try{JSON.parse(e),i.textContent="โœ“ Valid JSON",i.className="font-mono text-[11px] text-success font-semibold"}catch(e){i.textContent="โœ— Invalid JSON syntax",i.className="font-mono text-[11px] text-error font-medium"}}i.className="font-mono text-[11px] text-base-content/40",r.addEventListener("input",c),c();const d=document.createElement("div");d.className="flex items-center gap-1";const m=document.createElement("button");m.type="button",m.className="btn btn-ghost btn-xs text-xs",m.textContent="Beautify",m.addEventListener("click",()=>{try{const e=JSON.parse(r.value);r.value=JSON.stringify(e,null,2),c()}catch(e){UI.showError("Invalid JSON: "+e.message)}});const u=document.createElement("button");u.type="button",u.className="btn btn-ghost btn-xs text-xs",u.textContent="Minify",u.addEventListener("click",()=>{try{const e=JSON.parse(r.value);r.value=JSON.stringify(e),c()}catch(e){UI.showError("Invalid JSON: "+e.message)}});const p=document.createElement("button");return p.type="button",p.className="btn btn-ghost btn-xs gap-1 text-primary text-xs font-semibold",p.innerHTML="๐Ÿงฉ Inspector",p.addEventListener("click",()=>{window.Inspector&&window.Inspector.open&&window.Inspector.open({table:t.table,pk:t.pk||"",col:e,colType:n,value:r.value,isNull:!r.value,isJson:!0,readonly:!1,onSave:e=>{r.value=e,c()}})}),d.appendChild(m),d.appendChild(u),d.appendChild(p),l.appendChild(i),l.appendChild(d),o.appendChild(r),o.appendChild(l),s&&"edit"===t.mode&&(r.disabled=!0),o}(e.name,e.type,S,n);return a.appendChild(s),a}if(T){const s=document.createElement("div");s.className="space-y-1.5",D=document.createElement("input"),D.type="text",D.className="field-input font-mono text-xs",D.placeholder="e.g. '1 day', '02:30:00', '1 month 2 days'",D.dataset.column=e.name,null!=S&&(D.value=String(S));const o=document.createElement("div");return o.className="flex flex-wrap gap-1",["1 day","1 week","1 month","1 year","1 hour","30 mins"].forEach(e=>{const t=document.createElement("button");t.type="button",t.className="btn btn-ghost btn-xs text-[11px] font-mono",t.textContent=e,t.addEventListener("click",()=>{D.value=e}),o.appendChild(t)}),s.appendChild(D),s.appendChild(o),n&&"edit"===t.mode&&(D.disabled=!0),a.appendChild(s),a}if(I)D=document.createElement("input"),D.type="text",D.className="field-input font-mono text-xs",D.placeholder="(x, y) e.g. '(12.345, 67.890)'",D.dataset.column=e.name,null!=S&&(D.value=String(S));else if(w)D=document.createElement("input"),D.type="text",D.className="field-input font-mono text-xs",D.placeholder="Binary e.g. '101010'",D.dataset.column=e.name,null!=S&&(D.value=String(S));else if(C)D=document.createElement("input"),D.type="text",D.className="field-input font-mono text-xs",D.placeholder="MACADDR"===r?"08:00:2b:01:02:03":"192.168.1.1 or 2001:db8::1",D.dataset.column=e.name,null!=S&&(D.value=String(S));else if(i){D=document.createElement("input"),D.type="number";const n=r.startsWith("INTEGER")||"INT"===r||r.startsWith("BIGINT")||r.startsWith("SMALLINT")||r.startsWith("SERIAL");D.step=n?"1":"any",D.className="field-input font-mono",D.dataset.column=e.name,!r.startsWith("SERIAL")||"insert"!==t.mode||null!=S&&""!==S||(D.placeholder="(auto-increment serial)"),null!=S&&(D.value=String(S))}else{if(c){const s=document.createElement("div");s.className="space-y-2",D=document.createElement("input"),D.type="text",D.className="field-input font-mono text-xs",D.placeholder="BYTEA"===r?"\\xโ€ฆ or 0xโ€ฆ hex":"0xโ€ฆ hex or plain text",D.dataset.column=e.name,null!=S&&(D.value=String(S));const o=document.createElement("div");o.className="flex flex-wrap items-center gap-2";const l=document.createElement("input");l.type="file",l.className="file-input file-input-bordered file-input-xs w-full max-w-xs";const i=document.createElement("img");return i.className="h-7 max-w-12 rounded object-cover border border-base-300 bg-base-200 hidden",l.addEventListener("change",e=>{const t=e.target.files&&e.target.files[0];if(!t)return;const n=new FileReader;n.onload=()=>{const e=n.result,a=new Uint8Array(e);let s="BYTEA"===r?"\\x":"0x";for(let e=0;e<a.length;e++)s+=("00"+a[e].toString(16)).slice(-2);if(D.value=s,t.type.startsWith("image/")){const e=new FileReader;e.onload=()=>{i.src=e.result,i.classList.remove("hidden")},e.readAsDataURL(t)}else i.classList.add("hidden")},n.readAsArrayBuffer(t)}),o.appendChild(l),o.appendChild(i),s.appendChild(D),s.appendChild(o),n&&"edit"===t.mode&&(D.disabled=!0),a.appendChild(s),a}D=document.createElement("input"),D.type="text",D.className="field-input",D.dataset.column=e.name,null!=S&&(D.value=String(S))}}var L;return n&&"edit"===t.mode&&(D.disabled=!0),a.appendChild(D),a}async function b(e){a.classList.add("hidden");const r=function(){const e={};return n.querySelectorAll("[data-column]").forEach(t=>{const n=t.dataset.column;"checkbox"===t.type?e[n]=t.checked?"true":"":e[n]=t.value}),e}(),l="edit"===t.mode;let i;i=l?"/api/tables/"+c+"/row/"+encodeURIComponent(t.pk)+(e?"":"/generate"):"/api/tables/"+c+"/rows"+(e?"":"/generate");const d=l?"PUT":"POST";try{const n=await Api.request(d,i,{values:r});e?(UI.showToast(n.message||"Saved.","success"),setTimeout(()=>{window.location.href=Api.basePath+"/tables/"+encodeURIComponent(t.table)},500)):(m=n.sql,o.textContent=m,s.classList.remove("hidden"),s.scrollIntoView({behavior:"smooth",block:"nearest"}))}catch(e){a.classList.remove("hidden"),a.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>'+p(e.message)+"</span></div>"}var m}r&&r.addEventListener("click",()=>b(!0)),l&&l.addEventListener("click",()=>b(!1)),i&&i.addEventListener("click",()=>{navigator.clipboard.writeText(o.textContent).then(()=>UI.showToast("Copied to clipboard.","success")).catch(()=>UI.showError("Could not copy."))}),async function(){try{d=await Api.get("/api/tables/"+c+"/info"),"edit"===t.mode&&(m=await Api.get("/api/tables/"+c+"/row/"+encodeURIComponent(t.pk)));try{u=await Api.get("/api/tables/"+c+"/fk-options")}catch(e){u={}}n.innerHTML="",d.columns.forEach(e=>n.appendChild(y(e)))}catch(e){a.classList.remove("hidden"),a.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>'+p(e.message)+"</span></div>"}}()}();