admindb 2.1.0 โ 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -12
- package/dist/app.d.ts +1 -69
- package/dist/app.js +3 -30
- package/dist/auth/config.d.ts +1 -19
- package/dist/auth/config.js +0 -26
- package/dist/auth/crypto.d.ts +1 -27
- package/dist/auth/crypto.js +1 -26
- package/dist/auth/index.d.ts +1 -5
- package/dist/auth/index.js +0 -1
- package/dist/auth/middleware.d.ts +1 -14
- package/dist/auth/middleware.js +0 -15
- package/dist/auth/routes.d.ts +1 -7
- package/dist/auth/routes.js +0 -8
- package/dist/auth/types.d.ts +1 -35
- package/dist/auth/types.js +1 -5
- package/dist/cli/args.d.ts +1 -45
- package/dist/cli/args.js +3 -11
- package/dist/cli/config.d.ts +1 -56
- package/dist/cli/config.js +0 -5
- package/dist/cli/index.d.ts +1 -4
- package/dist/cli/index.js +0 -1
- package/dist/cli/runner.d.ts +1 -1
- package/dist/cli/runner.js +6 -17
- package/dist/cli.d.ts +1 -2
- package/dist/cli.js +0 -1
- package/dist/data/chain.d.ts +1 -0
- package/dist/data/chain.js +707 -0
- package/dist/data/datasets.d.ts +1 -17
- package/dist/data/datasets.js +0 -4
- package/dist/data/detector.d.ts +1 -30
- package/dist/data/detector.js +53 -14
- package/dist/data/engine.d.ts +1 -10
- package/dist/data/engine.js +82 -48
- package/dist/data/index.d.ts +1 -5
- package/dist/data/index.js +1 -1
- package/dist/data/strategies.d.ts +1 -30
- package/dist/data/strategies.js +4 -8
- package/dist/data/types.d.ts +1 -73
- package/dist/data/types.js +0 -3
- package/dist/db/database.d.ts +1 -119
- package/dist/db/database.js +20 -14
- package/dist/db/export.d.ts +1 -12
- package/dist/db/export.js +54 -6
- package/dist/db/filters.d.ts +1 -22
- package/dist/db/filters.js +0 -16
- package/dist/db/index.d.ts +1 -8
- package/dist/db/index.js +0 -1
- package/dist/db/introspection.d.ts +1 -6
- package/dist/db/introspection.js +0 -5
- package/dist/db/manager.d.ts +1 -81
- package/dist/db/manager.js +13 -59
- package/dist/db/migrations.d.ts +1 -5
- package/dist/db/migrations.js +2 -5
- package/dist/db/postgres.d.ts +1 -85
- package/dist/db/postgres.js +21 -31
- package/dist/db/types.d.ts +1 -206
- package/dist/db/types.js +0 -1
- package/dist/index.d.ts +1 -23
- package/dist/index.js +0 -14
- package/dist/public/css/app.css +1 -1
- package/dist/public/js/api.js +1 -47
- package/dist/public/js/app.js +1 -262
- package/dist/public/js/browse.js +1 -1371
- package/dist/public/js/common.js +1 -0
- package/dist/public/js/databases.js +1 -235
- package/dist/public/js/designer.js +1 -258
- package/dist/public/js/forms.js +1 -826
- package/dist/public/js/home.js +1 -0
- package/dist/public/js/inspector.js +1 -638
- package/dist/public/js/query.js +1 -322
- package/dist/public/js/schema.js +1 -447
- package/dist/public/js/seed.js +1 -1090
- package/dist/routes/api/erd.d.ts +1 -0
- package/dist/routes/api/erd.js +50 -0
- package/dist/routes/api/helpers.d.ts +1 -42
- package/dist/routes/api/helpers.js +33 -3
- package/dist/routes/api/import-export.d.ts +1 -3
- package/dist/routes/api/import-export.js +0 -2
- package/dist/routes/api/index.d.ts +1 -4
- package/dist/routes/api/index.js +15 -4
- package/dist/routes/api/query.d.ts +1 -3
- package/dist/routes/api/query.js +13 -11
- package/dist/routes/api/rows.d.ts +1 -3
- package/dist/routes/api/rows.js +0 -11
- package/dist/routes/api/seed.d.ts +1 -3
- package/dist/routes/api/seed.js +79 -8
- package/dist/routes/api/tables.d.ts +1 -3
- package/dist/routes/api/tables.js +77 -6
- package/dist/routes/databases.d.ts +1 -26
- package/dist/routes/databases.js +1 -3
- package/dist/routes/index.d.ts +1 -3
- package/dist/routes/index.js +0 -1
- package/dist/routes/pages.d.ts +1 -9
- package/dist/routes/pages.js +18 -11
- package/dist/serverless.d.ts +1 -40
- package/dist/serverless.js +3 -39
- package/dist/sql/classifier.d.ts +1 -19
- package/dist/sql/classifier.js +0 -13
- package/dist/sql/error-analyzer.d.ts +1 -0
- package/dist/sql/error-analyzer.js +550 -0
- package/dist/sql/generator.d.ts +1 -81
- package/dist/sql/generator.js +0 -29
- package/dist/sql/index.d.ts +1 -2
- package/dist/sql/index.js +0 -1
- package/dist/types/api.d.ts +1 -412
- package/dist/types/api.js +0 -7
- package/dist/utils/colors.d.ts +1 -40
- package/dist/utils/colors.js +0 -10
- package/dist/utils/common.d.ts +1 -50
- package/dist/utils/common.js +19 -23
- package/dist/utils/csv.d.ts +1 -15
- package/dist/utils/csv.js +0 -13
- package/dist/utils/datatype.d.ts +1 -49
- package/dist/utils/datatype.js +0 -41
- package/dist/utils/icons.d.ts +1 -11
- package/dist/utils/icons.js +25 -16
- package/dist/utils/index.d.ts +1 -4
- package/dist/utils/index.js +0 -1
- package/dist/utils/logger.d.ts +1 -10
- package/dist/utils/logger.js +0 -4
- package/dist/views/layouts/main.hbs +14 -5
- package/dist/views/pages/databases.hbs +0 -18
- package/dist/views/pages/designer.hbs +0 -10
- package/dist/views/pages/erd.hbs +1241 -0
- package/dist/views/pages/error.hbs +0 -2
- package/dist/views/pages/form.hbs +0 -5
- package/dist/views/pages/home.hbs +98 -10
- package/dist/views/pages/info.hbs +107 -0
- package/dist/views/pages/login.hbs +0 -13
- package/dist/views/pages/query.hbs +54 -21
- package/dist/views/pages/schema.hbs +0 -20
- package/dist/views/pages/seed.hbs +435 -202
- package/dist/views/pages/table.hbs +0 -14
- package/dist/views/partials/icon.hbs +0 -1
- package/dist/views/partials/navbar.hbs +0 -6
- package/dist/views/partials/sidebar.hbs +12 -8
- package/docs/API.md +6 -1
- package/docs/EXAMPLES.md +3 -0
- package/package.json +7 -5
- package/dist/app.js.map +0 -1
- package/dist/auth/config.js.map +0 -1
- package/dist/auth/crypto.js.map +0 -1
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/middleware.js.map +0 -1
- package/dist/auth/routes.js.map +0 -1
- package/dist/auth/types.js.map +0 -1
- package/dist/cli/args.js.map +0 -1
- package/dist/cli/config.js.map +0 -1
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/runner.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/data/datasets.js.map +0 -1
- package/dist/data/detector.js.map +0 -1
- package/dist/data/engine.js.map +0 -1
- package/dist/data/index.js.map +0 -1
- package/dist/data/strategies.js.map +0 -1
- package/dist/data/types.js.map +0 -1
- package/dist/db/database.js.map +0 -1
- package/dist/db/export.js.map +0 -1
- package/dist/db/filters.js.map +0 -1
- package/dist/db/index.js.map +0 -1
- package/dist/db/introspection.js.map +0 -1
- package/dist/db/manager.js.map +0 -1
- package/dist/db/migrations.js.map +0 -1
- package/dist/db/postgres.js.map +0 -1
- package/dist/db/types.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/public/css/input.css +0 -302
- package/dist/public/js/icons.js +0 -51
- package/dist/routes/api/helpers.js.map +0 -1
- package/dist/routes/api/import-export.js.map +0 -1
- package/dist/routes/api/index.js.map +0 -1
- package/dist/routes/api/query.js.map +0 -1
- package/dist/routes/api/rows.js.map +0 -1
- package/dist/routes/api/seed.js.map +0 -1
- package/dist/routes/api/tables.js.map +0 -1
- package/dist/routes/databases.js.map +0 -1
- package/dist/routes/index.js.map +0 -1
- package/dist/routes/pages.js.map +0 -1
- package/dist/serverless.js.map +0 -1
- package/dist/sql/classifier.js.map +0 -1
- package/dist/sql/generator.js.map +0 -1
- package/dist/sql/index.js.map +0 -1
- package/dist/types/api.js.map +0 -1
- package/dist/utils/colors.js.map +0 -1
- package/dist/utils/common.js.map +0 -1
- package/dist/utils/csv.js.map +0 -1
- package/dist/utils/datatype.js.map +0 -1
- package/dist/utils/icons.js.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/logger.js.map +0 -1
package/dist/public/js/forms.js
CHANGED
|
@@ -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">×</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"),l=document.getElementById("row-submit"),r=document.getElementById("row-generate"),i=document.getElementById("copy-sql"),c=encodeURIComponent(t.table);let d=null,m=null,u={};const p=window.Utils?window.Utils.escapeHtml:function(e){return String(null==e?"":e)},x=window.Utils&&window.Utils.toLocalInput?window.Utils.toLocalInput:function(e){return null==e?"":String(e)},f=window.Utils&&window.Utils.toLocalNow?window.Utils.toLocalNow:function(){return(new Date).toISOString()};function h(){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 g(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 h();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)?f("DATE"===a,!1):"TIME"!==a&&"TIMETZ"!==a||!/current_time|now\s*\(|time\s*\(/i.test(n)?null:f(!1,!0)}(e,e.dflt_value):null}function y(e,n,a,s){const o=document.createElement("div");o.className="space-y-2";const l=document.createElement("input");l.type="hidden",l.dataset.column=e;let r=[];if(Array.isArray(a))r=a.map(String);else if("string"==typeof a&&a.trim().startsWith("{")&&a.trim().endsWith("}")){const e=a.trim().slice(1,-1);r=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)&&(r=e.map(String))}catch(e){r=[]}else null!=a&&""!==String(a).trim()&&(r=[String(a)]);function i(){l.value=r.length?"{"+r.map(e=>e.includes(",")||e.includes('"')?'"'+e.replace(/"/g,'\\"')+'"':e).join(",")+"}":"",function(){if(c.innerHTML="",!r.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>');r.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">×</button>',n.querySelector("button").addEventListener("click",()=>{r.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=>r.push(e)):r.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(l),o.appendChild(c),o.appendChild(d),i(),s&&"edit"===t.mode&&(m.disabled=!0),o}function E(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),l=(e.type||"").toUpperCase(),r="BOOLEAN"===l||"BOOL"===l,i=l.startsWith("INT")||"REAL"===l||"NUMERIC"===l||l.startsWith("DECIMAL")||"FLOAT"===l||"DOUBLE"===l||l.startsWith("BIGINT")||l.startsWith("SMALLINT")||l.startsWith("SERIAL")||"MONEY"===l,c="BLOB"===l||"BYTEA"===l||"BINARY"===l,m="DATE"===l,E="TIME"===l||"TIMETZ"===l||l.startsWith("TIME ")||l.startsWith("TIME("),b="DATETIME"===l||l.includes("TIMESTAMP"),v="UUID"===l,N=l.includes("JSON")||e.name.toLowerCase().includes("json"),w=l.endsWith("[]")||l.startsWith("_")||l.includes("ARRAY"),C="INET"===l||"CIDR"===l||"MACADDR"===l||"MACADDR8"===l,T="INTERVAL"===l,I="POINT"===l||"GEOMETRY"===l||"BOX"===l||"CIRCLE"===l||"POLYGON"===l,S="BIT"===l||"VARBIT"===l||l.startsWith("BIT(")||l.startsWith("VARBIT("),L=g(e);let D;if(r){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===(M=L)||!0===M||"1"===M||"true"===M)&&(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!=L&&String(L)===String(e.value)&&(t.selected=!0),D.appendChild(t)})}else{if(m||E||b){const s=function(e,n,a,s){const o=(n||"").toUpperCase(),l="DATE"===o,r="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=l?"date":r?"time":"datetime-local",(r||i)&&(m.step="1"),m.className="field-input flex-1 font-mono text-[13px]",m.dataset.column=e,null!=a&&(m.value=x(a,o));const u=document.createElement("div");function p(){if(m.value)try{if(l){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(r)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 h=document.createElement("div");h.className="dropdown dropdown-end";const g=document.createElement("button");g.type="button",g.tabIndex=0,g.className="btn btn-ghost btn-sm btn-square text-primary hover:bg-primary/10 transition",g.title="Open calendar selector & presets",g.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 y=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),y.appendChild(a)}y.tabIndex=0,y.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()}),r?(E("Now","โก",()=>{m.value=f(!1,!0)}),E("Start of Hour","โฑ๏ธ",()=>{const e=new Date;var t;m.value=(t=e.getHours(),String(t).padStart(2,"0")+":00:00")})):l?(E("Today","โก",()=>{m.value=f(!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=f(!1,!1)}),E("Today (Start of Day)","๐
",()=>{m.value=f(!0,!1)+"T00:00:00"}),E("Today (End of Day)","๐",()=>{m.value=f(!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 b=document.createElement("li");return b.className="border-t border-base-200 my-1",y.appendChild(b),E("Clear / Set NULL","๐งน",()=>{m.value=""}),h.appendChild(g),h.appendChild(y),d.appendChild(m),d.appendChild(h),c.appendChild(d),c.appendChild(u),s&&"edit"===t.mode&&(m.disabled=!0),c}(e.name,e.type,L,n);return a.appendChild(s),a}if(w){const t=y(e.name,e.type,L,n);return a.appendChild(t),a}if(v){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 l=document.createElement("input");l.type="text",l.className="field-input font-mono text-xs flex-1",l.placeholder="00000000-0000-0000-0000-000000000000",l.dataset.column=e,null!=n&&(l.value=String(n));const r=document.createElement("span");function i(){const e=l.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)?(r.className="text-[10px] font-mono text-success font-semibold",r.textContent="โ Valid UUID v4"):(r.className="text-[10px] font-mono text-warning",r.textContent="โ Custom format"):r.textContent=""}r.className="text-[10px] font-mono transition-all text-base-content/40",l.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",()=>{l.value=h(),i()}),o.appendChild(l),o.appendChild(c),s.appendChild(o),s.appendChild(r),a&&"edit"===t.mode&&(l.disabled=!0),s}(e.name,L,n);return a.appendChild(s),a}if(N||L&&"object"==typeof L){const s=function(e,n,a,s){const o=document.createElement("div");o.className="space-y-1.5";const l=document.createElement("textarea");l.rows=4,l.className="field-input font-mono text-[13px] w-full",l.placeholder='{\n "key": "value"\n}',l.dataset.column=e,null!=a&&(l.value="object"==typeof a?JSON.stringify(a,null,2):String(a));const r=document.createElement("div");r.className="flex items-center justify-between text-xs";const i=document.createElement("span");function c(){const e=l.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",l.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(l.value);l.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(l.value);l.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:l.value,isNull:!l.value,isJson:!0,readonly:!1,onSave:e=>{l.value=e,c()}})}),d.appendChild(m),d.appendChild(u),d.appendChild(p),r.appendChild(i),r.appendChild(d),o.appendChild(l),o.appendChild(r),s&&"edit"===t.mode&&(l.disabled=!0),o}(e.name,e.type,L,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!=L&&(D.value=String(L));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!=L&&(D.value=String(L));else if(S)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!=L&&(D.value=String(L));else if(C)D=document.createElement("input"),D.type="text",D.className="field-input font-mono text-xs",D.placeholder="MACADDR"===l?"08:00:2b:01:02:03":"192.168.1.1 or 2001:db8::1",D.dataset.column=e.name,null!=L&&(D.value=String(L));else if(i){D=document.createElement("input"),D.type="number";const n=l.startsWith("INTEGER")||"INT"===l||l.startsWith("BIGINT")||l.startsWith("SMALLINT")||l.startsWith("SERIAL");D.step=n?"1":"any",D.className="field-input font-mono",D.dataset.column=e.name,!l.startsWith("SERIAL")||"insert"!==t.mode||null!=L&&""!==L||(D.placeholder="(auto-increment serial)"),null!=L&&(D.value=String(L))}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"===l?"\\xโฆ or 0xโฆ hex":"0xโฆ hex or plain text",D.dataset.column=e.name,null!=L&&(D.value=String(L));const o=document.createElement("div");o.className="flex flex-wrap items-center gap-2";const r=document.createElement("input");r.type="file",r.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",r.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"===l?"\\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(r),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!=L&&(D.value=String(L))}}var M;return n&&"edit"===t.mode&&(D.disabled=!0),a.appendChild(D),a}async function b(e){a.classList.add("hidden");const l=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}(),r="edit"===t.mode;let i;i=r?"/api/tables/"+c+"/row/"+encodeURIComponent(t.pk)+(e?"":"/generate"):"/api/tables/"+c+"/rows"+(e?"":"/generate");const d=r?"PUT":"POST";try{const n=await Api.request(d,i,{values:l});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}l&&l.addEventListener("click",()=>b(!0)),r&&r.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(E(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>"}}()}();
|