admindb 1.0.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/LICENSE +21 -0
- package/README.md +325 -0
- package/dist/app.d.ts +36 -0
- package/dist/app.js +186 -0
- package/dist/app.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +61 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.js +23 -0
- package/dist/config.js.map +1 -0
- package/dist/csv.d.ts +15 -0
- package/dist/csv.js +80 -0
- package/dist/csv.js.map +1 -0
- package/dist/db/database.d.ts +225 -0
- package/dist/db/database.js +497 -0
- package/dist/db/database.js.map +1 -0
- package/dist/db/export.d.ts +12 -0
- package/dist/db/export.js +37 -0
- package/dist/db/export.js.map +1 -0
- package/dist/db/manager.d.ts +62 -0
- package/dist/db/manager.js +206 -0
- package/dist/db/manager.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +10 -0
- package/dist/logger.js +25 -0
- package/dist/logger.js.map +1 -0
- package/dist/public/css/app.css +1 -0
- package/dist/public/css/input.css +179 -0
- package/dist/public/js/api.js +47 -0
- package/dist/public/js/app.js +120 -0
- package/dist/public/js/browse.js +123 -0
- package/dist/public/js/databases.js +45 -0
- package/dist/public/js/designer.js +205 -0
- package/dist/public/js/forms.js +244 -0
- package/dist/public/js/query.js +232 -0
- package/dist/public/js/schema.js +314 -0
- package/dist/public/js/triggers.js +155 -0
- package/dist/public/js/views.js +176 -0
- package/dist/routes/api.d.ts +9 -0
- package/dist/routes/api.js +622 -0
- package/dist/routes/api.js.map +1 -0
- package/dist/routes/databases.d.ts +19 -0
- package/dist/routes/databases.js +99 -0
- package/dist/routes/databases.js.map +1 -0
- package/dist/routes/pages.d.ts +9 -0
- package/dist/routes/pages.js +253 -0
- package/dist/routes/pages.js.map +1 -0
- package/dist/sql/classifier.d.ts +19 -0
- package/dist/sql/classifier.js +37 -0
- package/dist/sql/classifier.js.map +1 -0
- package/dist/sql/generator.d.ts +99 -0
- package/dist/sql/generator.js +259 -0
- package/dist/sql/generator.js.map +1 -0
- package/dist/test/classifier.test.d.ts +1 -0
- package/dist/test/classifier.test.js +44 -0
- package/dist/test/classifier.test.js.map +1 -0
- package/dist/test/csv.test.d.ts +1 -0
- package/dist/test/csv.test.js +64 -0
- package/dist/test/csv.test.js.map +1 -0
- package/dist/test/database.test.d.ts +1 -0
- package/dist/test/database.test.js +224 -0
- package/dist/test/database.test.js.map +1 -0
- package/dist/test/generator.test.d.ts +1 -0
- package/dist/test/generator.test.js +164 -0
- package/dist/test/generator.test.js.map +1 -0
- package/dist/test/manager.test.d.ts +1 -0
- package/dist/test/manager.test.js +146 -0
- package/dist/test/manager.test.js.map +1 -0
- package/dist/util.d.ts +29 -0
- package/dist/util.js +127 -0
- package/dist/util.js.map +1 -0
- package/dist/views/layouts/main.hbs +49 -0
- package/dist/views/pages/databases.hbs +102 -0
- package/dist/views/pages/designer.hbs +81 -0
- package/dist/views/pages/error.hbs +12 -0
- package/dist/views/pages/form.hbs +62 -0
- package/dist/views/pages/home.hbs +123 -0
- package/dist/views/pages/query.hbs +94 -0
- package/dist/views/pages/schema.hbs +290 -0
- package/dist/views/pages/table.hbs +203 -0
- package/dist/views/pages/triggers.hbs +162 -0
- package/dist/views/pages/views.hbs +134 -0
- package/dist/views/partials/navbar.hbs +50 -0
- package/dist/views/partials/sidebar.hbs +128 -0
- package/package.json +62 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
@layer base {
|
|
6
|
+
html {
|
|
7
|
+
-webkit-font-smoothing: antialiased;
|
|
8
|
+
text-rendering: optimizeLegibility;
|
|
9
|
+
}
|
|
10
|
+
body {
|
|
11
|
+
@apply font-sans;
|
|
12
|
+
}
|
|
13
|
+
::selection {
|
|
14
|
+
@apply bg-primary/25;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* Thin, theme-aware scrollbars */
|
|
18
|
+
::-webkit-scrollbar {
|
|
19
|
+
width: 10px;
|
|
20
|
+
height: 10px;
|
|
21
|
+
}
|
|
22
|
+
::-webkit-scrollbar-track {
|
|
23
|
+
background: transparent;
|
|
24
|
+
}
|
|
25
|
+
::-webkit-scrollbar-thumb {
|
|
26
|
+
background: rgb(0 0 0 / 0.16);
|
|
27
|
+
border-radius: 999px;
|
|
28
|
+
border: 2px solid transparent;
|
|
29
|
+
background-clip: content-box;
|
|
30
|
+
}
|
|
31
|
+
::-webkit-scrollbar-thumb:hover {
|
|
32
|
+
background: rgb(0 0 0 / 0.28);
|
|
33
|
+
border-radius: 999px;
|
|
34
|
+
border: 2px solid transparent;
|
|
35
|
+
background-clip: content-box;
|
|
36
|
+
}
|
|
37
|
+
[data-theme='admindbDark'] ::-webkit-scrollbar-thumb {
|
|
38
|
+
background: rgb(255 255 255 / 0.18);
|
|
39
|
+
border-radius: 999px;
|
|
40
|
+
border: 2px solid transparent;
|
|
41
|
+
background-clip: content-box;
|
|
42
|
+
}
|
|
43
|
+
[data-theme='admindbDark'] ::-webkit-scrollbar-thumb:hover {
|
|
44
|
+
background: rgb(255 255 255 / 0.3);
|
|
45
|
+
border-radius: 999px;
|
|
46
|
+
border: 2px solid transparent;
|
|
47
|
+
background-clip: content-box;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@layer components {
|
|
52
|
+
/* Cards / panels */
|
|
53
|
+
.app-card {
|
|
54
|
+
@apply rounded-2xl border border-base-300/70 bg-base-100 shadow-soft;
|
|
55
|
+
}
|
|
56
|
+
.code-panel {
|
|
57
|
+
@apply overflow-x-auto rounded-xl border border-base-300 bg-neutral p-4 font-mono text-[13px] leading-relaxed text-neutral-content;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Sidebar */
|
|
61
|
+
.side-link {
|
|
62
|
+
@apply flex items-center gap-3 rounded-lg px-3 py-2 text-[13.5px] font-medium text-neutral-content/65 transition-colors duration-150;
|
|
63
|
+
}
|
|
64
|
+
.side-link:hover {
|
|
65
|
+
@apply bg-white/[0.06] text-neutral-content;
|
|
66
|
+
}
|
|
67
|
+
.side-link.active {
|
|
68
|
+
@apply bg-primary/20 text-white;
|
|
69
|
+
}
|
|
70
|
+
.side-link svg {
|
|
71
|
+
@apply h-[18px] w-[18px] shrink-0;
|
|
72
|
+
}
|
|
73
|
+
.table-icon {
|
|
74
|
+
@apply grid h-7 w-7 shrink-0 place-items-center rounded-md bg-white/[0.06] text-neutral-content/60;
|
|
75
|
+
}
|
|
76
|
+
.side-link.active .table-icon {
|
|
77
|
+
@apply bg-primary/25 text-white;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* Row actions revealed on hover */
|
|
81
|
+
.row-actions {
|
|
82
|
+
@apply flex items-center justify-end gap-1 opacity-100 transition-opacity duration-150 lg:opacity-100;
|
|
83
|
+
}
|
|
84
|
+
tr:hover .row-actions,
|
|
85
|
+
tr:focus-within .row-actions {
|
|
86
|
+
@apply opacity-100;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Sortable column headers */
|
|
90
|
+
.sort-link {
|
|
91
|
+
@apply inline-flex items-center gap-1.5 font-semibold text-base-content/70 transition-colors hover:text-primary;
|
|
92
|
+
}
|
|
93
|
+
.sort-link.active {
|
|
94
|
+
@apply text-primary;
|
|
95
|
+
}
|
|
96
|
+
.sort-link .sort-arrow {
|
|
97
|
+
@apply text-[10px] opacity-100 transition-opacity;
|
|
98
|
+
}
|
|
99
|
+
.sort-link:hover .sort-arrow,
|
|
100
|
+
.sort-link.active .sort-arrow {
|
|
101
|
+
@apply opacity-100;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Form controls */
|
|
105
|
+
.field-label {
|
|
106
|
+
@apply mb-1.5 flex flex-wrap items-center gap-1.5 text-[13px] font-medium text-base-content/70;
|
|
107
|
+
}
|
|
108
|
+
.field-input {
|
|
109
|
+
@apply w-full rounded-xl border border-base-300 bg-base-100 px-3.5 py-2.5 text-sm text-base-content shadow-soft outline-none transition duration-150 placeholder:text-base-content/30 focus:border-primary/70 focus:ring-4 focus:ring-primary/10 disabled:cursor-not-allowed disabled:bg-base-200 disabled:text-base-content/40;
|
|
110
|
+
}
|
|
111
|
+
.field-select {
|
|
112
|
+
@apply w-full rounded-xl border border-base-300 bg-base-100 px-3 py-2.5 text-sm text-base-content shadow-soft outline-none transition duration-150 focus:border-primary/70 focus:ring-4 focus:ring-primary/10;
|
|
113
|
+
}
|
|
114
|
+
.field-check {
|
|
115
|
+
@apply checkbox checkbox-primary checkbox-sm;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Badges / chips */
|
|
119
|
+
.chip {
|
|
120
|
+
@apply inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide;
|
|
121
|
+
}
|
|
122
|
+
.chip-pk {
|
|
123
|
+
@apply bg-primary/10 text-primary;
|
|
124
|
+
}
|
|
125
|
+
.chip-null {
|
|
126
|
+
@apply bg-warning/10 text-warning;
|
|
127
|
+
}
|
|
128
|
+
.chip-type {
|
|
129
|
+
@apply bg-base-200 text-base-content/45;
|
|
130
|
+
}
|
|
131
|
+
.chip-required {
|
|
132
|
+
@apply text-error;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Stat cards */
|
|
136
|
+
.stat-card {
|
|
137
|
+
@apply flex items-center gap-4 rounded-2xl border border-base-300/70 bg-base-100 p-5 shadow-soft;
|
|
138
|
+
}
|
|
139
|
+
.stat-icon {
|
|
140
|
+
@apply grid h-11 w-11 shrink-0 place-items-center rounded-xl;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* SQL editor (query page) */
|
|
144
|
+
.sql-editor-wrap {
|
|
145
|
+
@apply flex overflow-hidden rounded-xl border border-base-300 bg-base-100 shadow-soft transition duration-150 focus-within:border-primary/60 focus-within:ring-4 focus-within:ring-primary/10;
|
|
146
|
+
}
|
|
147
|
+
.sql-gutter {
|
|
148
|
+
@apply select-none overflow-hidden border-r border-base-200 bg-base-200/60 px-3 py-4 text-right font-mono text-[13px] leading-[1.55] text-base-content/35;
|
|
149
|
+
min-width: 3.2rem;
|
|
150
|
+
max-height: 460px;
|
|
151
|
+
}
|
|
152
|
+
.sql-input {
|
|
153
|
+
@apply w-full resize-none overflow-auto bg-base-100 px-4 py-4 font-mono text-[13px] leading-[1.55] text-base-content outline-none placeholder:text-base-content/30;
|
|
154
|
+
max-height: 460px;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* Empty state */
|
|
158
|
+
.empty-state {
|
|
159
|
+
@apply flex flex-col items-center justify-center gap-3 rounded-2xl border border-dashed border-base-300 px-6 py-14 text-center;
|
|
160
|
+
}
|
|
161
|
+
.empty-state .empty-icon {
|
|
162
|
+
@apply grid h-14 w-14 place-items-center rounded-2xl bg-base-200 text-base-content/40;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* Toast */
|
|
166
|
+
.toast-item {
|
|
167
|
+
animation: toast-in 0.22s cubic-bezier(0.16, 1, 0.3, 1);
|
|
168
|
+
}
|
|
169
|
+
@keyframes toast-in {
|
|
170
|
+
from {
|
|
171
|
+
opacity: 0;
|
|
172
|
+
transform: translateY(-8px) scale(0.98);
|
|
173
|
+
}
|
|
174
|
+
to {
|
|
175
|
+
opacity: 1;
|
|
176
|
+
transform: translateY(0) scale(1);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +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
|
+
})();
|
|
@@ -0,0 +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
|
+
})();
|
|
@@ -0,0 +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
|
+
})();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/* Databases landing page: create and delete SQLite database files. */
|
|
2
|
+
(function () {
|
|
3
|
+
'use strict';
|
|
4
|
+
const nameInput = document.getElementById('new-db-name');
|
|
5
|
+
const createBtn = document.getElementById('create-db');
|
|
6
|
+
|
|
7
|
+
function escapeHtml(s) {
|
|
8
|
+
return (window.UI && UI.escapeHtml) ? UI.escapeHtml(s) : String(s == null ? '' : s);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function createDb() {
|
|
12
|
+
const name = (nameInput.value || '').trim();
|
|
13
|
+
if (!name) return UI.showError('Enter a database name.');
|
|
14
|
+
try {
|
|
15
|
+
const data = await Api.post('/api/databases', { name });
|
|
16
|
+
UI.showToast(data.message || 'Database created.', 'success');
|
|
17
|
+
setTimeout(() => {
|
|
18
|
+
window.location.href = Api.basePath + '/' + encodeURIComponent(data.id);
|
|
19
|
+
}, 400);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
UI.showError(e.message);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (createBtn) createBtn.addEventListener('click', createDb);
|
|
26
|
+
if (nameInput) {
|
|
27
|
+
nameInput.addEventListener('keydown', (e) => {
|
|
28
|
+
if (e.key === 'Enter') createDb();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
document.querySelectorAll('.js-delete-db').forEach((btn) => {
|
|
33
|
+
btn.addEventListener('click', () => {
|
|
34
|
+
const id = btn.dataset.id;
|
|
35
|
+
if (!id) return;
|
|
36
|
+
if (!window.confirm('Delete database "' + id + '" permanently? This removes the file and all of its data.')) return;
|
|
37
|
+
Api.del('/api/databases/' + encodeURIComponent(id))
|
|
38
|
+
.then((data) => {
|
|
39
|
+
UI.showToast(data.message || 'Database deleted.', 'success');
|
|
40
|
+
window.location.reload();
|
|
41
|
+
})
|
|
42
|
+
.catch((e) => UI.showError(e.message));
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
})();
|