admindb 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +250 -352
- package/dist/app.d.ts +25 -4
- package/dist/app.js +79 -15
- package/dist/app.js.map +1 -1
- package/dist/auth/config.d.ts +19 -0
- package/dist/auth/config.js +147 -0
- package/dist/auth/config.js.map +1 -0
- package/dist/auth/crypto.d.ts +27 -0
- package/dist/auth/crypto.js +133 -0
- package/dist/auth/crypto.js.map +1 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.js +22 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/middleware.d.ts +14 -0
- package/dist/auth/middleware.js +100 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/routes.d.ts +7 -0
- package/dist/auth/routes.js +78 -0
- package/dist/auth/routes.js.map +1 -0
- package/dist/auth/types.d.ts +35 -0
- package/dist/auth/types.js +11 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/cli/args.d.ts +45 -0
- package/dist/cli/args.js +328 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/config.d.ts +56 -0
- package/dist/cli/config.js +133 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.js +21 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/runner.d.ts +1 -0
- package/dist/cli/runner.js +222 -0
- package/dist/cli/runner.js.map +1 -0
- package/dist/cli.js +2 -127
- package/dist/cli.js.map +1 -1
- package/dist/data/datasets.d.ts +17 -0
- package/dist/data/datasets.js +133 -0
- package/dist/data/datasets.js.map +1 -0
- package/dist/data/detector.d.ts +30 -0
- package/dist/data/detector.js +472 -0
- package/dist/data/detector.js.map +1 -0
- package/dist/data/engine.d.ts +10 -0
- package/dist/data/engine.js +211 -0
- package/dist/data/engine.js.map +1 -0
- package/dist/data/index.d.ts +5 -0
- package/dist/data/index.js +22 -0
- package/dist/data/index.js.map +1 -0
- package/dist/data/strategies.d.ts +30 -0
- package/dist/data/strategies.js +343 -0
- package/dist/data/strategies.js.map +1 -0
- package/dist/data/types.d.ts +73 -0
- package/dist/data/types.js +8 -0
- package/dist/data/types.js.map +1 -0
- package/dist/db/database.d.ts +14 -142
- package/dist/db/database.js +209 -435
- package/dist/db/database.js.map +1 -1
- package/dist/db/export.d.ts +2 -2
- package/dist/db/export.js +7 -2
- package/dist/db/export.js.map +1 -1
- package/dist/db/filters.d.ts +22 -0
- package/dist/db/filters.js +158 -0
- package/dist/db/filters.js.map +1 -0
- package/dist/db/index.d.ts +8 -0
- package/dist/db/index.js +25 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/introspection.d.ts +6 -0
- package/dist/db/introspection.js +172 -0
- package/dist/db/introspection.js.map +1 -0
- package/dist/db/manager.d.ts +32 -19
- package/dist/db/manager.js +203 -44
- package/dist/db/manager.js.map +1 -1
- package/dist/db/migrations.d.ts +5 -0
- package/dist/db/migrations.js +110 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/postgres.d.ts +85 -0
- package/dist/db/postgres.js +745 -0
- package/dist/db/postgres.js.map +1 -0
- package/dist/db/types.d.ts +206 -0
- package/dist/db/types.js +7 -0
- package/dist/db/types.js.map +1 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.js +46 -4
- package/dist/index.js.map +1 -1
- package/dist/public/css/app.css +1 -1
- package/dist/public/css/input.css +83 -13
- package/dist/public/js/app.js +142 -0
- package/dist/public/js/browse.js +591 -104
- package/dist/public/js/databases.js +105 -13
- package/dist/public/js/designer.js +40 -2
- package/dist/public/js/forms.js +610 -28
- package/dist/public/js/icons.js +51 -0
- package/dist/public/js/inspector.js +638 -0
- package/dist/public/js/query.js +93 -3
- package/dist/public/js/schema.js +170 -52
- package/dist/public/js/seed.js +1090 -0
- package/dist/routes/api/helpers.d.ts +42 -0
- package/dist/routes/api/helpers.js +133 -0
- package/dist/routes/api/helpers.js.map +1 -0
- package/dist/routes/api/import-export.d.ts +3 -0
- package/dist/routes/api/import-export.js +85 -0
- package/dist/routes/api/import-export.js.map +1 -0
- package/dist/routes/api/index.d.ts +4 -0
- package/dist/routes/api/index.js +37 -0
- package/dist/routes/api/index.js.map +1 -0
- package/dist/routes/api/query.d.ts +3 -0
- package/dist/routes/api/query.js +76 -0
- package/dist/routes/api/query.js.map +1 -0
- package/dist/routes/api/rows.d.ts +3 -0
- package/dist/routes/api/rows.js +373 -0
- package/dist/routes/api/rows.js.map +1 -0
- package/dist/routes/api/seed.d.ts +3 -0
- package/dist/routes/api/seed.js +82 -0
- package/dist/routes/api/seed.js.map +1 -0
- package/dist/routes/api/tables.d.ts +3 -0
- package/dist/routes/api/tables.js +160 -0
- package/dist/routes/api/tables.js.map +1 -0
- package/dist/routes/databases.d.ts +8 -11
- package/dist/routes/databases.js +100 -66
- package/dist/routes/databases.js.map +1 -1
- package/dist/routes/index.d.ts +3 -0
- package/dist/routes/index.js +20 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/pages.d.ts +3 -3
- package/dist/routes/pages.js +163 -54
- package/dist/routes/pages.js.map +1 -1
- package/dist/serverless.d.ts +40 -0
- package/dist/serverless.js +210 -0
- package/dist/serverless.js.map +1 -0
- package/dist/sql/generator.d.ts +4 -3
- package/dist/sql/generator.js +103 -6
- package/dist/sql/generator.js.map +1 -1
- package/dist/sql/index.d.ts +2 -0
- package/dist/sql/index.js +19 -0
- package/dist/sql/index.js.map +1 -0
- package/dist/types/api.d.ts +412 -0
- package/dist/types/api.js +9 -0
- package/dist/types/api.js.map +1 -0
- package/dist/utils/colors.d.ts +40 -0
- package/dist/utils/colors.js +64 -0
- package/dist/utils/colors.js.map +1 -0
- package/dist/{util.d.ts → utils/common.d.ts} +10 -6
- package/dist/{util.js → utils/common.js} +80 -13
- package/dist/utils/common.js.map +1 -0
- package/dist/utils/csv.js.map +1 -0
- package/dist/utils/datatype.d.ts +49 -0
- package/dist/utils/datatype.js +222 -0
- package/dist/utils/datatype.js.map +1 -0
- package/dist/utils/icons.d.ts +11 -0
- package/dist/utils/icons.js +72 -0
- package/dist/utils/icons.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +21 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/{logger.d.ts → utils/logger.d.ts} +1 -1
- package/dist/utils/logger.js +40 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/views/layouts/main.hbs +33 -11
- package/dist/views/pages/databases.hbs +182 -74
- package/dist/views/pages/designer.hbs +9 -6
- package/dist/views/pages/error.hbs +3 -3
- package/dist/views/pages/form.hbs +5 -3
- package/dist/views/pages/home.hbs +22 -13
- package/dist/views/pages/login.hbs +111 -0
- package/dist/views/pages/query.hbs +10 -8
- package/dist/views/pages/schema.hbs +41 -13
- package/dist/views/pages/seed.hbs +232 -0
- package/dist/views/pages/table.hbs +155 -70
- package/dist/views/partials/icon.hbs +2 -0
- package/dist/views/partials/navbar.hbs +70 -31
- package/dist/views/partials/sidebar.hbs +108 -99
- package/package.json +12 -5
- package/dist/args.d.ts +0 -28
- package/dist/args.js +0 -187
- package/dist/args.js.map +0 -1
- package/dist/config.d.ts +0 -17
- package/dist/config.js +0 -23
- package/dist/config.js.map +0 -1
- package/dist/csv.js.map +0 -1
- package/dist/logger.js +0 -25
- package/dist/logger.js.map +0 -1
- package/dist/routes/api.d.ts +0 -9
- package/dist/routes/api.js +0 -786
- package/dist/routes/api.js.map +0 -1
- package/dist/test/classifier.test.d.ts +0 -1
- package/dist/test/classifier.test.js +0 -44
- package/dist/test/classifier.test.js.map +0 -1
- package/dist/test/csv.test.d.ts +0 -1
- package/dist/test/csv.test.js +0 -64
- package/dist/test/csv.test.js.map +0 -1
- package/dist/test/database.test.d.ts +0 -1
- package/dist/test/database.test.js +0 -402
- package/dist/test/database.test.js.map +0 -1
- package/dist/test/generator.test.d.ts +0 -1
- package/dist/test/generator.test.js +0 -132
- package/dist/test/generator.test.js.map +0 -1
- package/dist/test/manager.test.d.ts +0 -1
- package/dist/test/manager.test.js +0 -146
- package/dist/test/manager.test.js.map +0 -1
- package/dist/util.js.map +0 -1
- /package/dist/{csv.d.ts → utils/csv.d.ts} +0 -0
- /package/dist/{csv.js → utils/csv.js} +0 -0
package/dist/public/js/forms.js
CHANGED
|
@@ -24,28 +24,64 @@
|
|
|
24
24
|
return (window.UI && UI.escapeHtml) ? UI.escapeHtml(s) : String(s == null ? '' : s);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
function toLocalInput(v) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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;
|
|
31
48
|
}
|
|
32
49
|
|
|
33
|
-
function toLocalNow() {
|
|
50
|
+
function toLocalNow(dateOnly, timeOnly) {
|
|
34
51
|
const d = new Date();
|
|
35
52
|
const p = (n) => String(n).padStart(2, '0');
|
|
36
|
-
|
|
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
|
+
});
|
|
37
71
|
}
|
|
38
72
|
|
|
39
73
|
/**
|
|
40
74
|
* Convert a column's raw default expression (dflt_value) into a form-friendly
|
|
41
|
-
* value. Handles literals we can read safely; anything complex returns null
|
|
42
|
-
* (the DB applies the real default when the field is left empty).
|
|
75
|
+
* value. Handles literals we can read safely; anything complex returns null.
|
|
43
76
|
*/
|
|
44
77
|
function defaultToValue(col, dflt) {
|
|
45
78
|
if (dflt === null || dflt === undefined) return null;
|
|
46
|
-
|
|
79
|
+
let s = String(dflt).trim();
|
|
47
80
|
if (s === '' || /^null$/i.test(s)) return null;
|
|
48
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
|
+
|
|
49
85
|
const type = (col.type || '').toUpperCase();
|
|
50
86
|
|
|
51
87
|
// Single-quoted string literal, e.g. 'N/A', 'user'
|
|
@@ -58,15 +94,23 @@
|
|
|
58
94
|
// Boolean literal
|
|
59
95
|
if (/^(true|false)$/i.test(s)) return /^true$/i.test(s);
|
|
60
96
|
|
|
61
|
-
//
|
|
62
|
-
if (type === '
|
|
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') {
|
|
63
104
|
if (s === '1' || /^true$/i.test(s)) return 1;
|
|
64
105
|
if (s === '0' || /^false$/i.test(s)) return 0;
|
|
65
106
|
}
|
|
66
107
|
|
|
67
|
-
// current-timestamp
|
|
68
|
-
if ((type === 'DATE' || type === 'DATETIME') && /current_timestamp|now\s*\(|datetime\s*\(|date\s*\(/i.test(s)) {
|
|
69
|
-
return toLocalNow();
|
|
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);
|
|
70
114
|
}
|
|
71
115
|
|
|
72
116
|
return null;
|
|
@@ -83,6 +127,422 @@
|
|
|
83
127
|
return v === 1 || v === true || v === '1' || v === 'true';
|
|
84
128
|
}
|
|
85
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">×</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
|
+
|
|
86
546
|
function renderField(col) {
|
|
87
547
|
const isPk = info.primaryKey.indexOf(col.name) !== -1;
|
|
88
548
|
const wrapper = document.createElement('div');
|
|
@@ -100,24 +560,33 @@
|
|
|
100
560
|
|
|
101
561
|
const fk = info.foreignKeys.find((f) => f.from === col.name);
|
|
102
562
|
const type = (col.type || '').toUpperCase();
|
|
103
|
-
const isBool = type === 'BOOLEAN';
|
|
104
|
-
const isNum = type.startsWith('
|
|
105
|
-
const isBlob = type === 'BLOB';
|
|
106
|
-
const isDate = type === 'DATE'
|
|
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(');
|
|
107
576
|
const iv = initialValue(col);
|
|
108
577
|
|
|
109
578
|
let control;
|
|
110
579
|
if (isBool) {
|
|
111
580
|
const boolWrap = document.createElement('label');
|
|
112
|
-
boolWrap.className = 'flex cursor-pointer items-center gap-2';
|
|
581
|
+
boolWrap.className = 'flex cursor-pointer items-center gap-2 pt-1';
|
|
113
582
|
control = document.createElement('input');
|
|
114
583
|
control.type = 'checkbox';
|
|
115
584
|
control.className = 'field-check';
|
|
116
585
|
control.dataset.column = col.name;
|
|
117
586
|
if (isTruthy(iv)) control.checked = true;
|
|
118
587
|
const span = document.createElement('span');
|
|
119
|
-
span.className = 'text-sm text-base-content/
|
|
120
|
-
span.textContent = 'Enabled';
|
|
588
|
+
span.className = 'text-sm text-base-content/70';
|
|
589
|
+
span.textContent = 'True / Enabled';
|
|
121
590
|
boolWrap.appendChild(control);
|
|
122
591
|
boolWrap.appendChild(span);
|
|
123
592
|
wrapper.appendChild(boolWrap);
|
|
@@ -138,24 +607,137 @@
|
|
|
138
607
|
if (iv != null && String(iv) === String(o.value)) opt.selected = true;
|
|
139
608
|
control.appendChild(opt);
|
|
140
609
|
});
|
|
141
|
-
} else if (isDate) {
|
|
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';
|
|
142
629
|
control = document.createElement('input');
|
|
143
|
-
control.type = '
|
|
144
|
-
control.className = 'field-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)'";
|
|
145
655
|
control.dataset.column = col.name;
|
|
146
|
-
if (iv != null) control.value =
|
|
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);
|
|
147
671
|
} else if (isNum) {
|
|
148
672
|
control = document.createElement('input');
|
|
149
673
|
control.type = 'number';
|
|
150
|
-
|
|
151
|
-
control.
|
|
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';
|
|
152
677
|
control.dataset.column = col.name;
|
|
678
|
+
if (type.startsWith('SERIAL') && cfg.mode === 'insert' && (iv == null || iv === '')) {
|
|
679
|
+
control.placeholder = '(auto-increment serial)';
|
|
680
|
+
}
|
|
153
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;
|
|
154
737
|
} else {
|
|
155
738
|
control = document.createElement('input');
|
|
156
739
|
control.type = 'text';
|
|
157
740
|
control.className = 'field-input';
|
|
158
|
-
control.placeholder = isBlob ? '0x… hex or text' : '';
|
|
159
741
|
control.dataset.column = col.name;
|
|
160
742
|
if (iv != null) control.value = String(iv);
|
|
161
743
|
}
|