admindb 1.0.1 → 1.0.3
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/LICENSE +20 -20
- package/README.md +333 -325
- package/dist/db/database.d.ts +3 -2
- package/dist/db/database.js +12 -9
- package/dist/db/database.js.map +1 -1
- package/dist/db/manager.js +1 -1
- package/dist/db/manager.js.map +1 -1
- package/dist/public/css/input.css +179 -179
- package/dist/public/js/api.js +47 -47
- package/dist/public/js/app.js +120 -120
- package/dist/public/js/browse.js +123 -123
- package/dist/public/js/databases.js +45 -45
- package/dist/public/js/designer.js +205 -205
- package/dist/public/js/forms.js +244 -244
- package/dist/public/js/query.js +232 -232
- package/dist/public/js/schema.js +314 -314
- package/dist/public/js/triggers.js +155 -155
- package/dist/public/js/views.js +176 -176
- package/dist/views/layouts/main.hbs +49 -49
- package/dist/views/pages/databases.hbs +102 -102
- package/dist/views/pages/designer.hbs +81 -81
- package/dist/views/pages/error.hbs +12 -12
- package/dist/views/pages/form.hbs +62 -62
- package/dist/views/pages/home.hbs +123 -123
- package/dist/views/pages/query.hbs +94 -94
- package/dist/views/pages/schema.hbs +290 -290
- package/dist/views/pages/table.hbs +203 -203
- package/dist/views/pages/triggers.hbs +162 -162
- package/dist/views/pages/views.hbs +134 -134
- package/dist/views/partials/navbar.hbs +50 -50
- package/dist/views/partials/sidebar.hbs +128 -128
- package/package.json +66 -62
package/dist/public/js/api.js
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
/* Tiny JSON API client. Every request resolves to the `data` payload of the
|
|
2
|
-
* consistent { success, data?, error? } envelope, or throws with the message. */
|
|
3
|
-
(function () {
|
|
4
|
-
'use strict';
|
|
5
|
-
const base = (window.APP && window.APP.basePath) || '';
|
|
6
|
-
|
|
7
|
-
function url(path) {
|
|
8
|
-
return base + path;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
window.Api = {
|
|
12
|
-
basePath: base,
|
|
13
|
-
url: url,
|
|
14
|
-
|
|
15
|
-
async request(method, path, body) {
|
|
16
|
-
const opts = { method, headers: {} };
|
|
17
|
-
if (body !== undefined) {
|
|
18
|
-
opts.headers['Content-Type'] = 'application/json';
|
|
19
|
-
opts.body = JSON.stringify(body);
|
|
20
|
-
}
|
|
21
|
-
let res;
|
|
22
|
-
try {
|
|
23
|
-
res = await fetch(url(path), opts);
|
|
24
|
-
} catch (e) {
|
|
25
|
-
throw new Error('Network error: ' + e.message);
|
|
26
|
-
}
|
|
27
|
-
let data = null;
|
|
28
|
-
try {
|
|
29
|
-
data = await res.json();
|
|
30
|
-
} catch (e) {
|
|
31
|
-
data = null;
|
|
32
|
-
}
|
|
33
|
-
if (!res.ok) {
|
|
34
|
-
throw new Error((data && data.error) || 'Request failed (' + res.status + ')');
|
|
35
|
-
}
|
|
36
|
-
if (!data || data.success === false) {
|
|
37
|
-
throw new Error((data && data.error) || 'Request failed');
|
|
38
|
-
}
|
|
39
|
-
return data.data;
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
-
get(path) { return this.request('GET', path); },
|
|
43
|
-
post(path, body) { return this.request('POST', path, body); },
|
|
44
|
-
put(path, body) { return this.request('PUT', path, body); },
|
|
45
|
-
del(path) { return this.request('DELETE', path); },
|
|
46
|
-
};
|
|
47
|
-
})();
|
|
1
|
+
/* Tiny JSON API client. Every request resolves to the `data` payload of the
|
|
2
|
+
* consistent { success, data?, error? } envelope, or throws with the message. */
|
|
3
|
+
(function () {
|
|
4
|
+
'use strict';
|
|
5
|
+
const base = (window.APP && window.APP.basePath) || '';
|
|
6
|
+
|
|
7
|
+
function url(path) {
|
|
8
|
+
return base + path;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
window.Api = {
|
|
12
|
+
basePath: base,
|
|
13
|
+
url: url,
|
|
14
|
+
|
|
15
|
+
async request(method, path, body) {
|
|
16
|
+
const opts = { method, headers: {} };
|
|
17
|
+
if (body !== undefined) {
|
|
18
|
+
opts.headers['Content-Type'] = 'application/json';
|
|
19
|
+
opts.body = JSON.stringify(body);
|
|
20
|
+
}
|
|
21
|
+
let res;
|
|
22
|
+
try {
|
|
23
|
+
res = await fetch(url(path), opts);
|
|
24
|
+
} catch (e) {
|
|
25
|
+
throw new Error('Network error: ' + e.message);
|
|
26
|
+
}
|
|
27
|
+
let data = null;
|
|
28
|
+
try {
|
|
29
|
+
data = await res.json();
|
|
30
|
+
} catch (e) {
|
|
31
|
+
data = null;
|
|
32
|
+
}
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
throw new Error((data && data.error) || 'Request failed (' + res.status + ')');
|
|
35
|
+
}
|
|
36
|
+
if (!data || data.success === false) {
|
|
37
|
+
throw new Error((data && data.error) || 'Request failed');
|
|
38
|
+
}
|
|
39
|
+
return data.data;
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
get(path) { return this.request('GET', path); },
|
|
43
|
+
post(path, body) { return this.request('POST', path, body); },
|
|
44
|
+
put(path, body) { return this.request('PUT', path, body); },
|
|
45
|
+
del(path) { return this.request('DELETE', path); },
|
|
46
|
+
};
|
|
47
|
+
})();
|
package/dist/public/js/app.js
CHANGED
|
@@ -1,120 +1,120 @@
|
|
|
1
|
-
/* Shared UI helpers: HTML escaping, toasts, theme toggle, sidebar filtering. */
|
|
2
|
-
(function () {
|
|
3
|
-
'use strict';
|
|
4
|
-
|
|
5
|
-
function escapeHtml(s) {
|
|
6
|
-
return String(s == null ? '' : s)
|
|
7
|
-
.replace(/&/g, '&')
|
|
8
|
-
.replace(/</g, '<')
|
|
9
|
-
.replace(/>/g, '>')
|
|
10
|
-
.replace(/"/g, '"')
|
|
11
|
-
.replace(/'/g, ''');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
var ICONS = {
|
|
15
|
-
error:
|
|
16
|
-
'<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>',
|
|
17
|
-
success:
|
|
18
|
-
'<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" /></svg>',
|
|
19
|
-
info:
|
|
20
|
-
'<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>',
|
|
21
|
-
warning:
|
|
22
|
-
'<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>',
|
|
23
|
-
sun:
|
|
24
|
-
'<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" /></svg>',
|
|
25
|
-
moon:
|
|
26
|
-
'<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /></svg>',
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
var PALETTES = {
|
|
30
|
-
error: 'border-error/40 bg-error/10 text-error',
|
|
31
|
-
success: 'border-success/40 bg-success/10 text-success',
|
|
32
|
-
info: 'border-info/40 bg-info/10 text-info',
|
|
33
|
-
warning: 'border-warning/40 bg-warning/10 text-warning',
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
function showToast(message, type) {
|
|
37
|
-
var container = document.getElementById('toast-container');
|
|
38
|
-
if (!container) return;
|
|
39
|
-
type = type || 'info';
|
|
40
|
-
var el = document.createElement('div');
|
|
41
|
-
el.className =
|
|
42
|
-
'toast-item pointer-events-auto flex w-full max-w-sm items-start gap-3 rounded-xl border bg-base-100 px-4 py-3 shadow-lift ' +
|
|
43
|
-
(PALETTES[type] || PALETTES.info);
|
|
44
|
-
el.innerHTML =
|
|
45
|
-
'<span class="mt-0.5 shrink-0">' + (ICONS[type] || ICONS.info) + '</span>' +
|
|
46
|
-
'<span class="flex-1 text-sm font-medium text-base-content">' + escapeHtml(message) + '</span>' +
|
|
47
|
-
'<button type="button" class="toast-close mt-0.5 shrink-0 text-base-content/40 transition-colors hover:text-base-content" aria-label="Dismiss">×</button>';
|
|
48
|
-
container.appendChild(el);
|
|
49
|
-
|
|
50
|
-
var remove = function () {
|
|
51
|
-
el.style.transition = 'opacity .18s ease, transform .18s ease';
|
|
52
|
-
el.style.opacity = '0';
|
|
53
|
-
el.style.transform = 'translateY(-6px) scale(0.98)';
|
|
54
|
-
setTimeout(function () { el.remove(); }, 180);
|
|
55
|
-
};
|
|
56
|
-
el.querySelector('.toast-close').addEventListener('click', remove);
|
|
57
|
-
setTimeout(remove, 4200);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ---- Theme -------------------------------------------------------------
|
|
61
|
-
|
|
62
|
-
function applyTheme(dark) {
|
|
63
|
-
document.documentElement.setAttribute('data-theme', dark ? 'admindbDark' : 'admindb');
|
|
64
|
-
try {
|
|
65
|
-
localStorage.setItem('admindb-theme', dark ? 'dark' : 'light');
|
|
66
|
-
} catch (e) { /* ignore */ }
|
|
67
|
-
var btn = document.getElementById('theme-toggle');
|
|
68
|
-
if (btn) btn.innerHTML = dark ? ICONS.sun : ICONS.moon;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function initTheme() {
|
|
72
|
-
var stored = null;
|
|
73
|
-
try {
|
|
74
|
-
stored = localStorage.getItem('admindb-theme');
|
|
75
|
-
} catch (e) { /* ignore */ }
|
|
76
|
-
var dark = stored === 'dark' ||
|
|
77
|
-
(!stored && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
78
|
-
applyTheme(dark);
|
|
79
|
-
var btn = document.getElementById('theme-toggle');
|
|
80
|
-
if (btn) {
|
|
81
|
-
btn.addEventListener('click', function () {
|
|
82
|
-
var current = document.documentElement.getAttribute('data-theme') === 'admindbDark';
|
|
83
|
-
applyTheme(!current);
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ---- Sidebar table filter ----------------------------------------------
|
|
89
|
-
|
|
90
|
-
function initTableFilter() {
|
|
91
|
-
var input = document.getElementById('table-search');
|
|
92
|
-
var list = document.getElementById('table-list');
|
|
93
|
-
var countEl = document.getElementById('table-count');
|
|
94
|
-
if (!input || !list) return;
|
|
95
|
-
var items = Array.prototype.slice.call(list.querySelectorAll('.table-item'));
|
|
96
|
-
input.addEventListener('input', function () {
|
|
97
|
-
var q = input.value.trim().toLowerCase();
|
|
98
|
-
var shown = 0;
|
|
99
|
-
items.forEach(function (item) {
|
|
100
|
-
var name = (item.getAttribute('data-name') || '').toLowerCase();
|
|
101
|
-
var show = !q || name.indexOf(q) !== -1;
|
|
102
|
-
item.style.display = show ? '' : 'none';
|
|
103
|
-
if (show) shown += 1;
|
|
104
|
-
});
|
|
105
|
-
if (countEl) countEl.textContent = String(shown);
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
document.addEventListener('DOMContentLoaded', function () {
|
|
110
|
-
initTheme();
|
|
111
|
-
initTableFilter();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
window.UI = {
|
|
115
|
-
escapeHtml: escapeHtml,
|
|
116
|
-
showToast: showToast,
|
|
117
|
-
showError: function (message) { showToast(message, 'error'); },
|
|
118
|
-
applyTheme: applyTheme,
|
|
119
|
-
};
|
|
120
|
-
})();
|
|
1
|
+
/* Shared UI helpers: HTML escaping, toasts, theme toggle, sidebar filtering. */
|
|
2
|
+
(function () {
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
function escapeHtml(s) {
|
|
6
|
+
return String(s == null ? '' : s)
|
|
7
|
+
.replace(/&/g, '&')
|
|
8
|
+
.replace(/</g, '<')
|
|
9
|
+
.replace(/>/g, '>')
|
|
10
|
+
.replace(/"/g, '"')
|
|
11
|
+
.replace(/'/g, ''');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
var ICONS = {
|
|
15
|
+
error:
|
|
16
|
+
'<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>',
|
|
17
|
+
success:
|
|
18
|
+
'<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" /></svg>',
|
|
19
|
+
info:
|
|
20
|
+
'<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>',
|
|
21
|
+
warning:
|
|
22
|
+
'<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>',
|
|
23
|
+
sun:
|
|
24
|
+
'<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" /></svg>',
|
|
25
|
+
moon:
|
|
26
|
+
'<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /></svg>',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
var PALETTES = {
|
|
30
|
+
error: 'border-error/40 bg-error/10 text-error',
|
|
31
|
+
success: 'border-success/40 bg-success/10 text-success',
|
|
32
|
+
info: 'border-info/40 bg-info/10 text-info',
|
|
33
|
+
warning: 'border-warning/40 bg-warning/10 text-warning',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function showToast(message, type) {
|
|
37
|
+
var container = document.getElementById('toast-container');
|
|
38
|
+
if (!container) return;
|
|
39
|
+
type = type || 'info';
|
|
40
|
+
var el = document.createElement('div');
|
|
41
|
+
el.className =
|
|
42
|
+
'toast-item pointer-events-auto flex w-full max-w-sm items-start gap-3 rounded-xl border bg-base-100 px-4 py-3 shadow-lift ' +
|
|
43
|
+
(PALETTES[type] || PALETTES.info);
|
|
44
|
+
el.innerHTML =
|
|
45
|
+
'<span class="mt-0.5 shrink-0">' + (ICONS[type] || ICONS.info) + '</span>' +
|
|
46
|
+
'<span class="flex-1 text-sm font-medium text-base-content">' + escapeHtml(message) + '</span>' +
|
|
47
|
+
'<button type="button" class="toast-close mt-0.5 shrink-0 text-base-content/40 transition-colors hover:text-base-content" aria-label="Dismiss">×</button>';
|
|
48
|
+
container.appendChild(el);
|
|
49
|
+
|
|
50
|
+
var remove = function () {
|
|
51
|
+
el.style.transition = 'opacity .18s ease, transform .18s ease';
|
|
52
|
+
el.style.opacity = '0';
|
|
53
|
+
el.style.transform = 'translateY(-6px) scale(0.98)';
|
|
54
|
+
setTimeout(function () { el.remove(); }, 180);
|
|
55
|
+
};
|
|
56
|
+
el.querySelector('.toast-close').addEventListener('click', remove);
|
|
57
|
+
setTimeout(remove, 4200);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ---- Theme -------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
function applyTheme(dark) {
|
|
63
|
+
document.documentElement.setAttribute('data-theme', dark ? 'admindbDark' : 'admindb');
|
|
64
|
+
try {
|
|
65
|
+
localStorage.setItem('admindb-theme', dark ? 'dark' : 'light');
|
|
66
|
+
} catch (e) { /* ignore */ }
|
|
67
|
+
var btn = document.getElementById('theme-toggle');
|
|
68
|
+
if (btn) btn.innerHTML = dark ? ICONS.sun : ICONS.moon;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function initTheme() {
|
|
72
|
+
var stored = null;
|
|
73
|
+
try {
|
|
74
|
+
stored = localStorage.getItem('admindb-theme');
|
|
75
|
+
} catch (e) { /* ignore */ }
|
|
76
|
+
var dark = stored === 'dark' ||
|
|
77
|
+
(!stored && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
78
|
+
applyTheme(dark);
|
|
79
|
+
var btn = document.getElementById('theme-toggle');
|
|
80
|
+
if (btn) {
|
|
81
|
+
btn.addEventListener('click', function () {
|
|
82
|
+
var current = document.documentElement.getAttribute('data-theme') === 'admindbDark';
|
|
83
|
+
applyTheme(!current);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---- Sidebar table filter ----------------------------------------------
|
|
89
|
+
|
|
90
|
+
function initTableFilter() {
|
|
91
|
+
var input = document.getElementById('table-search');
|
|
92
|
+
var list = document.getElementById('table-list');
|
|
93
|
+
var countEl = document.getElementById('table-count');
|
|
94
|
+
if (!input || !list) return;
|
|
95
|
+
var items = Array.prototype.slice.call(list.querySelectorAll('.table-item'));
|
|
96
|
+
input.addEventListener('input', function () {
|
|
97
|
+
var q = input.value.trim().toLowerCase();
|
|
98
|
+
var shown = 0;
|
|
99
|
+
items.forEach(function (item) {
|
|
100
|
+
var name = (item.getAttribute('data-name') || '').toLowerCase();
|
|
101
|
+
var show = !q || name.indexOf(q) !== -1;
|
|
102
|
+
item.style.display = show ? '' : 'none';
|
|
103
|
+
if (show) shown += 1;
|
|
104
|
+
});
|
|
105
|
+
if (countEl) countEl.textContent = String(shown);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
110
|
+
initTheme();
|
|
111
|
+
initTableFilter();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
window.UI = {
|
|
115
|
+
escapeHtml: escapeHtml,
|
|
116
|
+
showToast: showToast,
|
|
117
|
+
showError: function (message) { showToast(message, 'error'); },
|
|
118
|
+
applyTheme: applyTheme,
|
|
119
|
+
};
|
|
120
|
+
})();
|
package/dist/public/js/browse.js
CHANGED
|
@@ -1,123 +1,123 @@
|
|
|
1
|
-
/* Row actions on the table browse page: delete, page-size, row filters, and
|
|
2
|
-
* CSV import. */
|
|
3
|
-
(function () {
|
|
4
|
-
'use strict';
|
|
5
|
-
|
|
6
|
-
const base = (window.APP && window.APP.basePath) || '';
|
|
7
|
-
|
|
8
|
-
// ---- Delete row --------------------------------------------------------
|
|
9
|
-
|
|
10
|
-
document.querySelectorAll('.js-delete-row').forEach((btn) => {
|
|
11
|
-
btn.addEventListener('click', () => {
|
|
12
|
-
const table = btn.dataset.table;
|
|
13
|
-
const pk = btn.dataset.pk;
|
|
14
|
-
if (!table || !pk) return;
|
|
15
|
-
if (!window.confirm('Delete this row? This cannot be undone.')) return;
|
|
16
|
-
Api.del('/api/tables/' + encodeURIComponent(table) + '/row/' + encodeURIComponent(pk))
|
|
17
|
-
.then(() => {
|
|
18
|
-
UI.showToast('Row deleted.', 'success');
|
|
19
|
-
window.location.reload();
|
|
20
|
-
})
|
|
21
|
-
.catch((e) => UI.showError(e.message));
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// ---- Page size (keeps current filters) ----------------------------------
|
|
26
|
-
|
|
27
|
-
const sizeSel = document.getElementById('page-size');
|
|
28
|
-
if (sizeSel) {
|
|
29
|
-
sizeSel.addEventListener('change', () => {
|
|
30
|
-
const table = sizeSel.dataset.table;
|
|
31
|
-
const size = sizeSel.value;
|
|
32
|
-
const orderBy = sizeSel.dataset.orderby || '';
|
|
33
|
-
const orderDir = sizeSel.dataset.orderdir || 'asc';
|
|
34
|
-
const filters = sizeSel.dataset.filters || '';
|
|
35
|
-
let href =
|
|
36
|
-
base + '/tables/' + encodeURIComponent(table) + '?page=1&size=' + encodeURIComponent(size) +
|
|
37
|
-
'&orderBy=' + encodeURIComponent(orderBy) + '&orderDir=' + encodeURIComponent(orderDir);
|
|
38
|
-
if (filters) href += '&' + filters;
|
|
39
|
-
window.location.href = href;
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ---- Row filters --------------------------------------------------------
|
|
44
|
-
|
|
45
|
-
const filterForm = document.getElementById('filter-form');
|
|
46
|
-
const clearBtn = document.getElementById('clear-filter');
|
|
47
|
-
if (filterForm) {
|
|
48
|
-
function currentFilters() {
|
|
49
|
-
const out = {};
|
|
50
|
-
filterForm.querySelectorAll('.filter-input').forEach((input) => {
|
|
51
|
-
const v = (input.value || '').trim();
|
|
52
|
-
if (v) out[input.name] = v;
|
|
53
|
-
});
|
|
54
|
-
return out;
|
|
55
|
-
}
|
|
56
|
-
function navigate(filters) {
|
|
57
|
-
let table = '';
|
|
58
|
-
const m = window.location.pathname.match(/\/tables\/([^/]+)(?:\/|$)/);
|
|
59
|
-
if (m) table = decodeURIComponent(m[1]);
|
|
60
|
-
if (!table) return;
|
|
61
|
-
const qs = new URLSearchParams({ page: '1', size: '50' });
|
|
62
|
-
const keys = Object.keys(filters);
|
|
63
|
-
if (keys.length) qs.set('f', JSON.stringify(filters));
|
|
64
|
-
window.location.href = base + '/tables/' + encodeURIComponent(table) + '?' + qs.toString();
|
|
65
|
-
}
|
|
66
|
-
filterForm.addEventListener('submit', (e) => {
|
|
67
|
-
e.preventDefault();
|
|
68
|
-
navigate(currentFilters());
|
|
69
|
-
});
|
|
70
|
-
if (clearBtn) {
|
|
71
|
-
clearBtn.addEventListener('click', () => navigate({}));
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ---- CSV import ---------------------------------------------------------
|
|
76
|
-
|
|
77
|
-
const importBtn = document.getElementById('import-csv');
|
|
78
|
-
const importPanel = document.getElementById('import-panel');
|
|
79
|
-
const importClose = document.getElementById('import-csv-close');
|
|
80
|
-
const importText = document.getElementById('import-csv-text');
|
|
81
|
-
const importFile = document.getElementById('import-csv-file');
|
|
82
|
-
const importSubmit = document.getElementById('import-csv-submit');
|
|
83
|
-
|
|
84
|
-
if (importBtn && importPanel) {
|
|
85
|
-
importBtn.addEventListener('click', () => {
|
|
86
|
-
importPanel.classList.toggle('hidden');
|
|
87
|
-
if (!importPanel.classList.contains('hidden')) importText.focus();
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
if (importClose) {
|
|
91
|
-
importClose.addEventListener('click', () => importPanel.classList.add('hidden'));
|
|
92
|
-
}
|
|
93
|
-
if (importFile) {
|
|
94
|
-
importFile.addEventListener('change', () => {
|
|
95
|
-
const file = importFile.files && importFile.files[0];
|
|
96
|
-
if (!file) return;
|
|
97
|
-
const reader = new FileReader();
|
|
98
|
-
reader.onload = () => {
|
|
99
|
-
importText.value = String(reader.result || '');
|
|
100
|
-
};
|
|
101
|
-
reader.readAsText(file);
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
if (importSubmit && importText) {
|
|
105
|
-
importSubmit.addEventListener('click', async () => {
|
|
106
|
-
const csv = importText.value.trim();
|
|
107
|
-
if (!csv) return UI.showError('Paste or choose a CSV file first.');
|
|
108
|
-
const m = window.location.pathname.match(/\/tables\/([^/]+)(?:\/|$)/);
|
|
109
|
-
const table = m ? decodeURIComponent(m[1]) : '';
|
|
110
|
-
if (!table) return UI.showError('Could not determine the table.');
|
|
111
|
-
importSubmit.disabled = true;
|
|
112
|
-
try {
|
|
113
|
-
const data = await Api.post('/api/tables/' + encodeURIComponent(table) + '/rows/import', { csv });
|
|
114
|
-
UI.showToast(data.message || 'Import complete.', 'success');
|
|
115
|
-
window.location.reload();
|
|
116
|
-
} catch (e) {
|
|
117
|
-
UI.showError(e.message);
|
|
118
|
-
} finally {
|
|
119
|
-
importSubmit.disabled = false;
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
})();
|
|
1
|
+
/* Row actions on the table browse page: delete, page-size, row filters, and
|
|
2
|
+
* CSV import. */
|
|
3
|
+
(function () {
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const base = (window.APP && window.APP.basePath) || '';
|
|
7
|
+
|
|
8
|
+
// ---- Delete row --------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
document.querySelectorAll('.js-delete-row').forEach((btn) => {
|
|
11
|
+
btn.addEventListener('click', () => {
|
|
12
|
+
const table = btn.dataset.table;
|
|
13
|
+
const pk = btn.dataset.pk;
|
|
14
|
+
if (!table || !pk) return;
|
|
15
|
+
if (!window.confirm('Delete this row? This cannot be undone.')) return;
|
|
16
|
+
Api.del('/api/tables/' + encodeURIComponent(table) + '/row/' + encodeURIComponent(pk))
|
|
17
|
+
.then(() => {
|
|
18
|
+
UI.showToast('Row deleted.', 'success');
|
|
19
|
+
window.location.reload();
|
|
20
|
+
})
|
|
21
|
+
.catch((e) => UI.showError(e.message));
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// ---- Page size (keeps current filters) ----------------------------------
|
|
26
|
+
|
|
27
|
+
const sizeSel = document.getElementById('page-size');
|
|
28
|
+
if (sizeSel) {
|
|
29
|
+
sizeSel.addEventListener('change', () => {
|
|
30
|
+
const table = sizeSel.dataset.table;
|
|
31
|
+
const size = sizeSel.value;
|
|
32
|
+
const orderBy = sizeSel.dataset.orderby || '';
|
|
33
|
+
const orderDir = sizeSel.dataset.orderdir || 'asc';
|
|
34
|
+
const filters = sizeSel.dataset.filters || '';
|
|
35
|
+
let href =
|
|
36
|
+
base + '/tables/' + encodeURIComponent(table) + '?page=1&size=' + encodeURIComponent(size) +
|
|
37
|
+
'&orderBy=' + encodeURIComponent(orderBy) + '&orderDir=' + encodeURIComponent(orderDir);
|
|
38
|
+
if (filters) href += '&' + filters;
|
|
39
|
+
window.location.href = href;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ---- Row filters --------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
const filterForm = document.getElementById('filter-form');
|
|
46
|
+
const clearBtn = document.getElementById('clear-filter');
|
|
47
|
+
if (filterForm) {
|
|
48
|
+
function currentFilters() {
|
|
49
|
+
const out = {};
|
|
50
|
+
filterForm.querySelectorAll('.filter-input').forEach((input) => {
|
|
51
|
+
const v = (input.value || '').trim();
|
|
52
|
+
if (v) out[input.name] = v;
|
|
53
|
+
});
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
function navigate(filters) {
|
|
57
|
+
let table = '';
|
|
58
|
+
const m = window.location.pathname.match(/\/tables\/([^/]+)(?:\/|$)/);
|
|
59
|
+
if (m) table = decodeURIComponent(m[1]);
|
|
60
|
+
if (!table) return;
|
|
61
|
+
const qs = new URLSearchParams({ page: '1', size: '50' });
|
|
62
|
+
const keys = Object.keys(filters);
|
|
63
|
+
if (keys.length) qs.set('f', JSON.stringify(filters));
|
|
64
|
+
window.location.href = base + '/tables/' + encodeURIComponent(table) + '?' + qs.toString();
|
|
65
|
+
}
|
|
66
|
+
filterForm.addEventListener('submit', (e) => {
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
navigate(currentFilters());
|
|
69
|
+
});
|
|
70
|
+
if (clearBtn) {
|
|
71
|
+
clearBtn.addEventListener('click', () => navigate({}));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ---- CSV import ---------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
const importBtn = document.getElementById('import-csv');
|
|
78
|
+
const importPanel = document.getElementById('import-panel');
|
|
79
|
+
const importClose = document.getElementById('import-csv-close');
|
|
80
|
+
const importText = document.getElementById('import-csv-text');
|
|
81
|
+
const importFile = document.getElementById('import-csv-file');
|
|
82
|
+
const importSubmit = document.getElementById('import-csv-submit');
|
|
83
|
+
|
|
84
|
+
if (importBtn && importPanel) {
|
|
85
|
+
importBtn.addEventListener('click', () => {
|
|
86
|
+
importPanel.classList.toggle('hidden');
|
|
87
|
+
if (!importPanel.classList.contains('hidden')) importText.focus();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (importClose) {
|
|
91
|
+
importClose.addEventListener('click', () => importPanel.classList.add('hidden'));
|
|
92
|
+
}
|
|
93
|
+
if (importFile) {
|
|
94
|
+
importFile.addEventListener('change', () => {
|
|
95
|
+
const file = importFile.files && importFile.files[0];
|
|
96
|
+
if (!file) return;
|
|
97
|
+
const reader = new FileReader();
|
|
98
|
+
reader.onload = () => {
|
|
99
|
+
importText.value = String(reader.result || '');
|
|
100
|
+
};
|
|
101
|
+
reader.readAsText(file);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
if (importSubmit && importText) {
|
|
105
|
+
importSubmit.addEventListener('click', async () => {
|
|
106
|
+
const csv = importText.value.trim();
|
|
107
|
+
if (!csv) return UI.showError('Paste or choose a CSV file first.');
|
|
108
|
+
const m = window.location.pathname.match(/\/tables\/([^/]+)(?:\/|$)/);
|
|
109
|
+
const table = m ? decodeURIComponent(m[1]) : '';
|
|
110
|
+
if (!table) return UI.showError('Could not determine the table.');
|
|
111
|
+
importSubmit.disabled = true;
|
|
112
|
+
try {
|
|
113
|
+
const data = await Api.post('/api/tables/' + encodeURIComponent(table) + '/rows/import', { csv });
|
|
114
|
+
UI.showToast(data.message || 'Import complete.', 'success');
|
|
115
|
+
window.location.reload();
|
|
116
|
+
} catch (e) {
|
|
117
|
+
UI.showError(e.message);
|
|
118
|
+
} finally {
|
|
119
|
+
importSubmit.disabled = false;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
})();
|