webspresso 0.0.63 → 0.0.65
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 +42 -1
- package/core/orm/model.js +6 -0
- package/core/orm/types.js +9 -0
- package/index.d.ts +482 -0
- package/index.js +2 -1
- package/package.json +7 -1
- package/plugins/admin-panel/components.js +120 -120
- package/plugins/admin-panel/field-renderers/array.js +2 -2
- package/plugins/admin-panel/field-renderers/basic.js +6 -6
- package/plugins/admin-panel/field-renderers/file-upload.js +2 -2
- package/plugins/admin-panel/field-renderers/json.js +1 -1
- package/plugins/admin-panel/field-renderers/relations.js +2 -2
- package/plugins/admin-panel/field-renderers/rich-text.js +3 -3
- package/plugins/admin-panel/index.js +35 -4
- package/plugins/admin-panel/modules/bulk-actions.js +6 -6
- package/plugins/admin-panel/modules/custom-pages.js +7 -7
- package/plugins/admin-panel/modules/dashboard.js +14 -14
- package/plugins/admin-panel/modules/menu.js +71 -26
- package/plugins/index.js +2 -0
- package/plugins/rest-resources/index.js +350 -0
- package/plugins/site-analytics/admin-component.js +34 -4
- package/plugins/site-analytics/api-handlers.js +74 -1
- package/plugins/site-analytics/index.js +1 -0
- package/src/server.js +128 -36
|
@@ -14,10 +14,10 @@ module.exports = {
|
|
|
14
14
|
meta.label || name,
|
|
15
15
|
required ? m('span.text-red-500', ' *') : null
|
|
16
16
|
),
|
|
17
|
-
m('.border.border-gray-300.rounded.p-4', [
|
|
17
|
+
m('.border.border-gray-300 dark:border-slate-600.rounded.p-4', [
|
|
18
18
|
items.map((item, index) =>
|
|
19
19
|
m('.flex.items-center.gap-2.mb-2', [
|
|
20
|
-
m('input.flex-1.px-3.py-2.border.border-gray-300.rounded', {
|
|
20
|
+
m('input.flex-1.px-3.py-2.border.border-gray-300 dark:border-slate-600.rounded', {
|
|
21
21
|
type: 'text',
|
|
22
22
|
value: String(item),
|
|
23
23
|
oninput: (e) => {
|
|
@@ -15,7 +15,7 @@ module.exports = {
|
|
|
15
15
|
meta.label || name,
|
|
16
16
|
required ? m('span.text-red-500', ' *') : null
|
|
17
17
|
),
|
|
18
|
-
m('input.w-full.px-3.py-2.border.border-gray-300.rounded', {
|
|
18
|
+
m('input.w-full.px-3.py-2.border.border-gray-300 dark:border-slate-600.rounded', {
|
|
19
19
|
id: name,
|
|
20
20
|
name,
|
|
21
21
|
type: 'text',
|
|
@@ -43,7 +43,7 @@ module.exports = {
|
|
|
43
43
|
meta.label || name,
|
|
44
44
|
required ? m('span.text-red-500', ' *') : null
|
|
45
45
|
),
|
|
46
|
-
m('textarea.w-full.px-3.py-2.border.border-gray-300.rounded', {
|
|
46
|
+
m('textarea.w-full.px-3.py-2.border.border-gray-300 dark:border-slate-600.rounded', {
|
|
47
47
|
id: name,
|
|
48
48
|
name,
|
|
49
49
|
rows: meta.rows || 5,
|
|
@@ -69,7 +69,7 @@ module.exports = {
|
|
|
69
69
|
meta.label || name,
|
|
70
70
|
required ? m('span.text-red-500', ' *') : null
|
|
71
71
|
),
|
|
72
|
-
m('input.w-full.px-3.py-2.border.border-gray-300.rounded', {
|
|
72
|
+
m('input.w-full.px-3.py-2.border.border-gray-300 dark:border-slate-600.rounded', {
|
|
73
73
|
id: name,
|
|
74
74
|
name,
|
|
75
75
|
type: 'number',
|
|
@@ -127,7 +127,7 @@ module.exports = {
|
|
|
127
127
|
meta.label || name,
|
|
128
128
|
required ? m('span.text-red-500', ' *') : null
|
|
129
129
|
),
|
|
130
|
-
m('input.w-full.px-3.py-2.border.border-gray-300.rounded', {
|
|
130
|
+
m('input.w-full.px-3.py-2.border.border-gray-300 dark:border-slate-600.rounded', {
|
|
131
131
|
id: name,
|
|
132
132
|
name,
|
|
133
133
|
type: 'date',
|
|
@@ -155,7 +155,7 @@ module.exports = {
|
|
|
155
155
|
meta.label || name,
|
|
156
156
|
required ? m('span.text-red-500', ' *') : null
|
|
157
157
|
),
|
|
158
|
-
m('input.w-full.px-3.py-2.border.border-gray-300.rounded', {
|
|
158
|
+
m('input.w-full.px-3.py-2.border.border-gray-300 dark:border-slate-600.rounded', {
|
|
159
159
|
id: name,
|
|
160
160
|
name,
|
|
161
161
|
type: 'datetime-local',
|
|
@@ -183,7 +183,7 @@ module.exports = {
|
|
|
183
183
|
meta.label || name,
|
|
184
184
|
required ? m('span.text-red-500', ' *') : null
|
|
185
185
|
),
|
|
186
|
-
m('select.w-full.px-3.py-2.border.border-gray-300.rounded', {
|
|
186
|
+
m('select.w-full.px-3.py-2.border.border-gray-300 dark:border-slate-600.rounded', {
|
|
187
187
|
id: name,
|
|
188
188
|
name,
|
|
189
189
|
value: String(value || ''),
|
|
@@ -63,7 +63,7 @@ module.exports = {
|
|
|
63
63
|
meta.label || name,
|
|
64
64
|
required ? m('span.text-red-500', ' *') : null
|
|
65
65
|
),
|
|
66
|
-
m('div#drop-zone-' + name + '.border-2.border-dashed.border-gray-300.rounded.p-8.text-center', {
|
|
66
|
+
m('div#drop-zone-' + name + '.border-2.border-dashed.border-gray-300 dark:border-slate-600.rounded.p-8.text-center', {
|
|
67
67
|
style: 'cursor: pointer;'
|
|
68
68
|
}, [
|
|
69
69
|
m('input[type=file]', {
|
|
@@ -77,7 +77,7 @@ module.exports = {
|
|
|
77
77
|
}
|
|
78
78
|
}),
|
|
79
79
|
m('div', [
|
|
80
|
-
m('p.text-gray-600.mb-2', 'Drag and drop a file here, or'),
|
|
80
|
+
m('p.text-gray-600 dark:text-slate-400.mb-2', 'Drag and drop a file here, or'),
|
|
81
81
|
m('label.text-blue-600.hover:text-blue-800.cursor-pointer', {
|
|
82
82
|
for: 'file-input-' + name
|
|
83
83
|
}, 'browse'),
|
|
@@ -22,7 +22,7 @@ module.exports = {
|
|
|
22
22
|
meta.label || name,
|
|
23
23
|
required ? m('span.text-red-500', ' *') : null
|
|
24
24
|
),
|
|
25
|
-
m('textarea.w-full.px-3.py-2.border.border-gray-300.rounded.font-mono.text-sm', {
|
|
25
|
+
m('textarea.w-full.px-3.py-2.border.border-gray-300 dark:border-slate-600.rounded.font-mono.text-sm', {
|
|
26
26
|
rows: 10,
|
|
27
27
|
value: jsonString,
|
|
28
28
|
oninput: (e) => {
|
|
@@ -18,7 +18,7 @@ module.exports = {
|
|
|
18
18
|
meta.label || name,
|
|
19
19
|
required ? m('span.text-red-500', ' *') : null
|
|
20
20
|
),
|
|
21
|
-
m('select.w-full.px-3.py-2.border.border-gray-300.rounded', {
|
|
21
|
+
m('select.w-full.px-3.py-2.border.border-gray-300 dark:border-slate-600.rounded', {
|
|
22
22
|
name,
|
|
23
23
|
value: value ? String(value) : '',
|
|
24
24
|
required,
|
|
@@ -58,7 +58,7 @@ module.exports = {
|
|
|
58
58
|
meta.label || name,
|
|
59
59
|
required ? m('span.text-red-500', ' *') : null
|
|
60
60
|
),
|
|
61
|
-
m('.border.border-gray-300.rounded.p-4.max-h-64.overflow-y-auto', [
|
|
61
|
+
m('.border.border-gray-300 dark:border-slate-600.rounded.p-4.max-h-64.overflow-y-auto', [
|
|
62
62
|
relationData.map(item => {
|
|
63
63
|
const itemValue = String(item[valueKey]);
|
|
64
64
|
const itemDisplay = item[displayKey] || itemValue;
|
|
@@ -95,11 +95,11 @@ module.exports = {
|
|
|
95
95
|
const editorId = 'quill-editor-' + name;
|
|
96
96
|
|
|
97
97
|
return m('.mb-4', [
|
|
98
|
-
m('label.block.text-sm.font-medium.text-gray-700.mb-1', { for: name },
|
|
98
|
+
m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-1', { for: name },
|
|
99
99
|
label,
|
|
100
100
|
required ? m('span.text-red-500', ' *') : null
|
|
101
101
|
),
|
|
102
|
-
m('div.border.border-gray-300.rounded', {
|
|
102
|
+
m('div.border.border-gray-300 dark:border-slate-600.rounded', {
|
|
103
103
|
id: editorId,
|
|
104
104
|
style: 'min-height: 200px;'
|
|
105
105
|
}),
|
|
@@ -108,7 +108,7 @@ module.exports = {
|
|
|
108
108
|
id: name + '-value',
|
|
109
109
|
value: value || '',
|
|
110
110
|
}),
|
|
111
|
-
hint ? m('p.text-xs.text-gray-500.mt-1', hint) : null,
|
|
111
|
+
hint ? m('p.text-xs.text-gray-500 dark:text-slate-400.mt-1', hint) : null,
|
|
112
112
|
]);
|
|
113
113
|
}
|
|
114
114
|
},
|
|
@@ -66,14 +66,16 @@ function adminPanelPlugin(options = {}) {
|
|
|
66
66
|
description: 'Modular admin panel for Webspresso with extensions support',
|
|
67
67
|
|
|
68
68
|
// CSP requirements for admin panel scripts
|
|
69
|
+
// Note: cdn.quilljs.com 301-redirects to cdn.jsdelivr.net; CSP is enforced on the final URL.
|
|
69
70
|
csp: {
|
|
70
|
-
styleSrc: ['https://cdn.quilljs.com'],
|
|
71
|
+
styleSrc: ['https://cdn.quilljs.com', 'https://cdn.jsdelivr.net'],
|
|
71
72
|
scriptSrc: [
|
|
72
73
|
'https://cdn.quilljs.com',
|
|
74
|
+
'https://cdn.jsdelivr.net',
|
|
73
75
|
'https://unpkg.com',
|
|
74
76
|
'https://cdn.tailwindcss.com',
|
|
75
77
|
],
|
|
76
|
-
connectSrc: ['https://unpkg.com', 'https://cdn.tailwindcss.com'],
|
|
78
|
+
connectSrc: ['https://unpkg.com', 'https://cdn.tailwindcss.com', 'https://cdn.jsdelivr.net'],
|
|
77
79
|
},
|
|
78
80
|
enabled,
|
|
79
81
|
registry, // Expose registry for external configuration
|
|
@@ -338,19 +340,48 @@ function generateAdminPanelHtml(adminPath, registry) {
|
|
|
338
340
|
<meta charset="UTF-8">
|
|
339
341
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
340
342
|
<title>${settings.title || 'Admin Panel'}</title>
|
|
343
|
+
<script>
|
|
344
|
+
(function () {
|
|
345
|
+
var K = 'webspresso-admin-theme';
|
|
346
|
+
function sync() {
|
|
347
|
+
try {
|
|
348
|
+
var v = localStorage.getItem(K);
|
|
349
|
+
var dark = (v === 'dark') || ((v !== 'light') && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
350
|
+
document.documentElement.classList.toggle('dark', dark);
|
|
351
|
+
} catch (e) {}
|
|
352
|
+
}
|
|
353
|
+
sync();
|
|
354
|
+
window.__syncAdminTheme = sync;
|
|
355
|
+
try {
|
|
356
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function () {
|
|
357
|
+
var v = localStorage.getItem(K);
|
|
358
|
+
if (v === 'light' || v === 'dark') return;
|
|
359
|
+
sync();
|
|
360
|
+
});
|
|
361
|
+
} catch (e) {}
|
|
362
|
+
})();
|
|
363
|
+
</script>
|
|
341
364
|
<script src="https://unpkg.com/mithril/mithril.js"></script>
|
|
342
365
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
366
|
+
<script>tailwind.config = { darkMode: 'class' };</script>
|
|
343
367
|
<style>
|
|
344
368
|
body { margin: 0; font-family: system-ui, -apple-system, sans-serif; }
|
|
369
|
+
html.dark { color-scheme: dark; }
|
|
345
370
|
:root {
|
|
346
371
|
--primary-color: ${settings.primaryColor || '#3B82F6'};
|
|
347
372
|
}
|
|
348
373
|
.bg-primary { background-color: var(--primary-color); }
|
|
349
374
|
.text-primary { color: var(--primary-color); }
|
|
350
375
|
.border-primary { border-color: var(--primary-color); }
|
|
376
|
+
.dark .ql-toolbar.ql-snow { border-color: #475569; background: #1e293b; }
|
|
377
|
+
.dark .ql-container.ql-snow { border-color: #475569; background: #0f172a; }
|
|
378
|
+
.dark .ql-editor { color: #f1f5f9; min-height: 8rem; }
|
|
379
|
+
.dark .ql-snow .ql-stroke { stroke: #94a3b8; }
|
|
380
|
+
.dark .ql-snow .ql-fill { fill: #94a3b8; }
|
|
381
|
+
.dark .ql-picker { color: #cbd5e1; }
|
|
351
382
|
</style>
|
|
352
383
|
</head>
|
|
353
|
-
<body>
|
|
384
|
+
<body class="min-h-screen bg-gray-50 dark:bg-slate-950 text-gray-900 dark:text-slate-100 antialiased">
|
|
354
385
|
<div id="app"></div>
|
|
355
386
|
<script>
|
|
356
387
|
window.__ADMIN_PATH__ = ${JSON.stringify(adminPath)};
|
|
@@ -366,7 +397,7 @@ function generateAdminPanelHtml(adminPath, registry) {
|
|
|
366
397
|
|
|
367
398
|
// Spinner Component
|
|
368
399
|
const Spinner = {
|
|
369
|
-
view: () => m('div.animate-spin.rounded-full.h-6.w-6.border-2.border-blue-500.border-t-transparent'),
|
|
400
|
+
view: () => m('div.animate-spin.rounded-full.h-6.w-6.border-2.border-blue-500.dark:border-blue-400.border-t-transparent'),
|
|
370
401
|
};
|
|
371
402
|
|
|
372
403
|
${menuComponent}
|
|
@@ -163,13 +163,13 @@ const BulkActionsBar = {
|
|
|
163
163
|
|
|
164
164
|
if (selectedCount === 0) return null;
|
|
165
165
|
|
|
166
|
-
return m('div.fixed.bottom-0.left-0.right-0.bg-white.border-t.shadow-lg.p-4.z-50', [
|
|
166
|
+
return m('div.fixed.bottom-0.left-0.right-0.bg-white dark:bg-slate-800.border-t.shadow-lg.p-4.z-50', [
|
|
167
167
|
m('div.max-w-7xl.mx-auto.flex.items-center.justify-between', [
|
|
168
168
|
m('div.flex.items-center.gap-4', [
|
|
169
169
|
m('span.text-sm.font-medium.text-gray-700',
|
|
170
170
|
selectedCount + ' record' + (selectedCount !== 1 ? 's' : '') + ' selected'
|
|
171
171
|
),
|
|
172
|
-
m('button.text-sm.text-gray-500.hover:text-gray-700.underline', {
|
|
172
|
+
m('button.text-sm.text-gray-500 dark:text-slate-400.hover:text-gray-700 dark:hover:text-slate-200 dark:hover:text-slate-200.underline', {
|
|
173
173
|
onclick: onClearSelection,
|
|
174
174
|
}, 'Clear selection'),
|
|
175
175
|
]),
|
|
@@ -182,7 +182,7 @@ const BulkActionsBar = {
|
|
|
182
182
|
? 'bg-green-100 text-green-700 hover:bg-green-200'
|
|
183
183
|
: action.color === 'blue'
|
|
184
184
|
? 'bg-blue-100 text-blue-700 hover:bg-blue-200'
|
|
185
|
-
: 'bg-gray-100 text-gray-700 hover:bg-gray-200',
|
|
185
|
+
: 'bg-gray-100 text-gray-700 dark:text-slate-300 hover:bg-gray-200 dark:hover:bg-slate-600 dark:hover:bg-slate-600',
|
|
186
186
|
onclick: () => onAction(action),
|
|
187
187
|
}, [
|
|
188
188
|
action.icon && m(Icon, { name: action.icon, class: 'w-4 h-4' }),
|
|
@@ -205,13 +205,13 @@ const ConfirmModal = {
|
|
|
205
205
|
if (e.target === e.currentTarget) onCancel();
|
|
206
206
|
},
|
|
207
207
|
}, [
|
|
208
|
-
m('div.bg-white.rounded-lg.shadow-xl.max-w-md.w-full.mx-4', [
|
|
208
|
+
m('div.bg-white dark:bg-slate-800.rounded-lg.shadow-xl.max-w-md.w-full.mx-4', [
|
|
209
209
|
m('div.p-6', [
|
|
210
210
|
m('h3.text-lg.font-medium.text-gray-900', title || 'Confirm Action'),
|
|
211
211
|
m('p.mt-2.text-sm.text-gray-500', message),
|
|
212
212
|
]),
|
|
213
|
-
m('div.bg-gray-50.px-6.py-4.flex.justify-end.gap-3.rounded-b-lg', [
|
|
214
|
-
m('button.px-4.py-2.text-sm.font-medium.text-gray-700.bg-white.border.border-gray-300.rounded.hover:bg-gray-50', {
|
|
213
|
+
m('div.bg-gray-50 dark:bg-slate-900.px-6.py-4.flex.justify-end.gap-3.rounded-b-lg', [
|
|
214
|
+
m('button.px-4.py-2.text-sm.font-medium.text-gray-700 dark:text-slate-300.bg-white dark:bg-slate-800.border.border-gray-300 dark:border-slate-600.rounded.hover:bg-gray-50 dark:hover:bg-slate-800/50 dark:hover:bg-slate-800/50', {
|
|
215
215
|
onclick: onCancel,
|
|
216
216
|
}, 'Cancel'),
|
|
217
217
|
m('button.px-4.py-2.text-sm.font-medium.text-white.rounded', {
|
|
@@ -182,7 +182,7 @@ function createCustomPage(pageConfig) {
|
|
|
182
182
|
|
|
183
183
|
m('div.mb-6', [
|
|
184
184
|
m('h1.text-2xl.font-bold.text-gray-900', pageConfig.title),
|
|
185
|
-
pageConfig.description && m('p.text-gray-500.mt-1', pageConfig.description),
|
|
185
|
+
pageConfig.description && m('p.text-gray-500 dark:text-slate-400.mt-1', pageConfig.description),
|
|
186
186
|
]),
|
|
187
187
|
|
|
188
188
|
loading
|
|
@@ -191,7 +191,7 @@ function createCustomPage(pageConfig) {
|
|
|
191
191
|
? m('div.bg-red-50.border.border-red-200.rounded.p-4.text-red-700', error)
|
|
192
192
|
: pageConfig.render
|
|
193
193
|
? pageConfig.render(data, vnode)
|
|
194
|
-
: m('div.bg-white.rounded-lg.shadow.p-6', [
|
|
194
|
+
: m('div.bg-white dark:bg-slate-800.rounded-lg.shadow.p-6', [
|
|
195
195
|
data
|
|
196
196
|
? m('pre.text-sm.overflow-auto', JSON.stringify(data, null, 2))
|
|
197
197
|
: m('p.text-gray-500', 'No content'),
|
|
@@ -239,11 +239,11 @@ const SettingsPage = {
|
|
|
239
239
|
|
|
240
240
|
error && m('div.bg-red-50.border.border-red-200.rounded.p-4.text-red-700.mb-4', error),
|
|
241
241
|
|
|
242
|
-
m('div.bg-white.rounded-lg.shadow', [
|
|
242
|
+
m('div.bg-white dark:bg-slate-800.rounded-lg.shadow', [
|
|
243
243
|
m('div.p-6.space-y-4', [
|
|
244
244
|
m('div', [
|
|
245
245
|
m('label.block.text-sm.font-medium.text-gray-700', 'Panel Title'),
|
|
246
|
-
m('input.mt-1.block.w-full.rounded.border-gray-300.shadow-sm.focus:border-blue-500.focus:ring-blue-500', {
|
|
246
|
+
m('input.mt-1.block.w-full.rounded.border-gray-300 dark:border-slate-600.shadow-sm.focus:border-blue-500.focus:ring-blue-500', {
|
|
247
247
|
type: 'text',
|
|
248
248
|
value: formData.title || '',
|
|
249
249
|
oninput: (e) => { formData.title = e.target.value; },
|
|
@@ -252,7 +252,7 @@ const SettingsPage = {
|
|
|
252
252
|
|
|
253
253
|
m('div', [
|
|
254
254
|
m('label.block.text-sm.font-medium.text-gray-700', 'Primary Color'),
|
|
255
|
-
m('input.mt-1.block.w-24.h-10.rounded.border-gray-300.shadow-sm', {
|
|
255
|
+
m('input.mt-1.block.w-24.h-10.rounded.border-gray-300 dark:border-slate-600.shadow-sm', {
|
|
256
256
|
type: 'color',
|
|
257
257
|
value: formData.primaryColor || '#3B82F6',
|
|
258
258
|
oninput: (e) => { formData.primaryColor = e.target.value; },
|
|
@@ -261,7 +261,7 @@ const SettingsPage = {
|
|
|
261
261
|
|
|
262
262
|
m('div', [
|
|
263
263
|
m('label.block.text-sm.font-medium.text-gray-700', 'Records Per Page'),
|
|
264
|
-
m('input.mt-1.block.w-32.rounded.border-gray-300.shadow-sm.focus:border-blue-500.focus:ring-blue-500', {
|
|
264
|
+
m('input.mt-1.block.w-32.rounded.border-gray-300 dark:border-slate-600.shadow-sm.focus:border-blue-500.focus:ring-blue-500', {
|
|
265
265
|
type: 'number',
|
|
266
266
|
min: 5,
|
|
267
267
|
max: 100,
|
|
@@ -271,7 +271,7 @@ const SettingsPage = {
|
|
|
271
271
|
]),
|
|
272
272
|
]),
|
|
273
273
|
|
|
274
|
-
m('div.bg-gray-50.px-6.py-4.flex.justify-end.gap-3.rounded-b-lg', [
|
|
274
|
+
m('div.bg-gray-50 dark:bg-slate-900.px-6.py-4.flex.justify-end.gap-3.rounded-b-lg', [
|
|
275
275
|
m('button.px-4.py-2.text-sm.font-medium.text-white.bg-blue-600.rounded.hover:bg-blue-700.disabled:opacity-50', {
|
|
276
276
|
disabled: saving,
|
|
277
277
|
onclick: async () => {
|
|
@@ -173,7 +173,7 @@ const WidgetRenderers = {
|
|
|
173
173
|
render: (data) => {
|
|
174
174
|
if (!data || !Array.isArray(data)) return m('div.text-gray-500', 'No data');
|
|
175
175
|
return m('div.grid.grid-cols-1.md:grid-cols-2.lg:grid-cols-3.gap-4', data.map(stat =>
|
|
176
|
-
m('a.block.bg-white.rounded-lg.shadow.hover:shadow-md.transition-shadow.overflow-hidden', {
|
|
176
|
+
m('a.block.bg-white dark:bg-slate-800.rounded-lg.shadow.hover:shadow-md dark:hover:shadow-slate-900/40 dark:hover:shadow-slate-900/40.transition-shadow.overflow-hidden', {
|
|
177
177
|
href: '/models/' + stat.name,
|
|
178
178
|
onclick: (e) => {
|
|
179
179
|
e.preventDefault();
|
|
@@ -193,17 +193,17 @@ const WidgetRenderers = {
|
|
|
193
193
|
]),
|
|
194
194
|
]),
|
|
195
195
|
// Stats row
|
|
196
|
-
m('div.px-4.py-3.bg-gray-50.grid.grid-cols-3.gap-2.text-center', [
|
|
196
|
+
m('div.px-4.py-3.bg-gray-50 dark:bg-slate-900.grid.grid-cols-3.gap-2.text-center', [
|
|
197
197
|
m('div', [
|
|
198
|
-
m('p.text-xs.text-gray-400.uppercase', 'Columns'),
|
|
198
|
+
m('p.text-xs.text-gray-400 dark:text-slate-500.uppercase', 'Columns'),
|
|
199
199
|
m('p.text-sm.font-semibold.text-gray-700', stat.columnCount || '-'),
|
|
200
200
|
]),
|
|
201
201
|
m('div', [
|
|
202
|
-
m('p.text-xs.text-gray-400.uppercase', 'Table'),
|
|
203
|
-
m('p.text-sm.font-semibold.text-gray-700.truncate', { title: stat.table }, stat.table || '-'),
|
|
202
|
+
m('p.text-xs.text-gray-400 dark:text-slate-500.uppercase', 'Table'),
|
|
203
|
+
m('p.text-sm.font-semibold.text-gray-700 dark:text-slate-300.truncate', { title: stat.table }, stat.table || '-'),
|
|
204
204
|
]),
|
|
205
205
|
m('div', [
|
|
206
|
-
m('p.text-xs.text-gray-400.uppercase', 'Last Update'),
|
|
206
|
+
m('p.text-xs.text-gray-400 dark:text-slate-500.uppercase', 'Last Update'),
|
|
207
207
|
m('p.text-sm.font-semibold.text-gray-700',
|
|
208
208
|
stat.lastUpdated || stat.lastCreated
|
|
209
209
|
? formatRelativeTime(stat.lastUpdated || stat.lastCreated)
|
|
@@ -220,10 +220,10 @@ const WidgetRenderers = {
|
|
|
220
220
|
'recent-activity': {
|
|
221
221
|
render: (data) => {
|
|
222
222
|
if (!data?.enabled) {
|
|
223
|
-
return m('div.text-gray-500.text-center.py-4', 'Activity logging not enabled');
|
|
223
|
+
return m('div.text-gray-500 dark:text-slate-400.text-center.py-4', 'Activity logging not enabled');
|
|
224
224
|
}
|
|
225
225
|
if (!data.activities?.length) {
|
|
226
|
-
return m('div.text-gray-500.text-center.py-4', 'No recent activity');
|
|
226
|
+
return m('div.text-gray-500 dark:text-slate-400.text-center.py-4', 'No recent activity');
|
|
227
227
|
}
|
|
228
228
|
return m('div.divide-y', data.activities.map(activity =>
|
|
229
229
|
m('div.py-3.flex.items-center.gap-3', [
|
|
@@ -240,7 +240,7 @@ const WidgetRenderers = {
|
|
|
240
240
|
}),
|
|
241
241
|
]),
|
|
242
242
|
m('div.flex-1.min-w-0', [
|
|
243
|
-
m('p.text-sm.text-gray-900.truncate', activity.description || \`\${activity.action} on \${activity.model}\`),
|
|
243
|
+
m('p.text-sm.text-gray-900 dark:text-slate-100.truncate', activity.description || \`\${activity.action} on \${activity.model}\`),
|
|
244
244
|
m('p.text-xs.text-gray-500', formatDate(activity.created_at)),
|
|
245
245
|
]),
|
|
246
246
|
activity.user_name && m('span.text-xs.text-gray-400', activity.user_name),
|
|
@@ -253,10 +253,10 @@ const WidgetRenderers = {
|
|
|
253
253
|
'quick-actions': {
|
|
254
254
|
render: (data) => {
|
|
255
255
|
if (!data || !Array.isArray(data) || data.length === 0) {
|
|
256
|
-
return m('div.text-gray-500.text-center.py-4', 'No quick actions available');
|
|
256
|
+
return m('div.text-gray-500 dark:text-slate-400.text-center.py-4', 'No quick actions available');
|
|
257
257
|
}
|
|
258
258
|
return m('div.space-y-2', data.map(action =>
|
|
259
|
-
m('a.flex.items-center.gap-2.px-3.py-2.bg-gray-50.rounded.hover:bg-gray-100.transition-colors', {
|
|
259
|
+
m('a.flex.items-center.gap-2.px-3.py-2.bg-gray-50 dark:bg-slate-900.rounded.hover:bg-gray-100 dark:hover:bg-slate-700 dark:hover:bg-slate-700.transition-colors', {
|
|
260
260
|
href: action.path,
|
|
261
261
|
onclick: (e) => {
|
|
262
262
|
e.preventDefault();
|
|
@@ -299,7 +299,7 @@ const WidgetRenderers = {
|
|
|
299
299
|
default: {
|
|
300
300
|
render: (data) => {
|
|
301
301
|
if (!data) return m('div.text-gray-500', 'No data');
|
|
302
|
-
return m('pre.text-xs.bg-gray-50.p-2.rounded.overflow-auto', JSON.stringify(data, null, 2));
|
|
302
|
+
return m('pre.text-xs.bg-gray-50 dark:bg-slate-900.p-2.rounded.overflow-auto', JSON.stringify(data, null, 2));
|
|
303
303
|
},
|
|
304
304
|
},
|
|
305
305
|
};
|
|
@@ -340,7 +340,7 @@ const Widget = {
|
|
|
340
340
|
full: 'col-span-full',
|
|
341
341
|
};
|
|
342
342
|
|
|
343
|
-
return m('div.bg-white.rounded-lg.shadow', {
|
|
343
|
+
return m('div.bg-white dark:bg-slate-800.rounded-lg.shadow', {
|
|
344
344
|
class: sizeClasses[widget.size] || sizeClasses.md,
|
|
345
345
|
}, [
|
|
346
346
|
m('div.px-4.py-3.border-b.border-gray-200', [
|
|
@@ -391,7 +391,7 @@ const Dashboard = {
|
|
|
391
391
|
return m(Layout, [
|
|
392
392
|
m('div.mb-6', [
|
|
393
393
|
m('h1.text-2xl.font-bold.text-gray-900', config?.settings?.title || 'Dashboard'),
|
|
394
|
-
m('p.text-gray-500.mt-1', 'Welcome back, ' + (state.user?.name || 'Admin')),
|
|
394
|
+
m('p.text-gray-500 dark:text-slate-400.mt-1', 'Welcome back, ' + (state.user?.name || 'Admin')),
|
|
395
395
|
]),
|
|
396
396
|
|
|
397
397
|
widgets.length > 0
|
|
@@ -99,6 +99,9 @@ const Icon = {
|
|
|
99
99
|
logout: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />',
|
|
100
100
|
menu: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />',
|
|
101
101
|
tool: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />',
|
|
102
|
+
sun: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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" />',
|
|
103
|
+
moon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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" />',
|
|
104
|
+
monitor: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />',
|
|
102
105
|
};
|
|
103
106
|
|
|
104
107
|
const path = icons[name] || icons.database;
|
|
@@ -113,6 +116,43 @@ const Icon = {
|
|
|
113
116
|
},
|
|
114
117
|
};
|
|
115
118
|
|
|
119
|
+
var ADMIN_THEME_KEY = 'webspresso-admin-theme';
|
|
120
|
+
function getAdminThemePref() {
|
|
121
|
+
try { return localStorage.getItem(ADMIN_THEME_KEY); } catch (e) { return null; }
|
|
122
|
+
}
|
|
123
|
+
function setAdminThemePref(mode) {
|
|
124
|
+
try {
|
|
125
|
+
if (mode === 'system') localStorage.removeItem(ADMIN_THEME_KEY);
|
|
126
|
+
else localStorage.setItem(ADMIN_THEME_KEY, mode);
|
|
127
|
+
} catch (e) {}
|
|
128
|
+
if (window.__syncAdminTheme) window.__syncAdminTheme();
|
|
129
|
+
m.redraw();
|
|
130
|
+
}
|
|
131
|
+
const ThemeToggle = {
|
|
132
|
+
view() {
|
|
133
|
+
var pref = getAdminThemePref();
|
|
134
|
+
var isSystem = !pref || pref === 'system';
|
|
135
|
+
var isLight = pref === 'light';
|
|
136
|
+
var isDark = pref === 'dark';
|
|
137
|
+
var btn = function(mode, title, iconName) {
|
|
138
|
+
var active = (mode === 'system' && isSystem) || (mode === 'light' && isLight) || (mode === 'dark' && isDark);
|
|
139
|
+
return m('button.p-1.5.rounded-md.transition-colors', {
|
|
140
|
+
type: 'button',
|
|
141
|
+
title: title,
|
|
142
|
+
class: active
|
|
143
|
+
? 'bg-white dark:bg-slate-600 text-blue-600 dark:text-blue-300 shadow-sm'
|
|
144
|
+
: 'text-gray-500 dark:text-slate-400 hover:bg-gray-100 dark:hover:bg-slate-700',
|
|
145
|
+
onclick: function() { setAdminThemePref(mode); },
|
|
146
|
+
}, m(Icon, { name: iconName, class: 'w-4 h-4' }));
|
|
147
|
+
};
|
|
148
|
+
return m('div.flex.items-center.gap-0.5.p-0.5.rounded-lg.border.border-gray-200 dark:border-slate-600.bg-gray-50 dark:bg-slate-900/80', [
|
|
149
|
+
btn('system', 'Match system', 'monitor'),
|
|
150
|
+
btn('light', 'Light', 'sun'),
|
|
151
|
+
btn('dark', 'Dark', 'moon'),
|
|
152
|
+
]);
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
116
156
|
// Menu Item Component
|
|
117
157
|
const MenuItem = {
|
|
118
158
|
view(vnode) {
|
|
@@ -121,8 +161,8 @@ const MenuItem = {
|
|
|
121
161
|
return m('a.flex.items-center.gap-3.px-3.py-2.rounded-lg.text-sm.font-medium.transition-colors', {
|
|
122
162
|
href: item.path,
|
|
123
163
|
class: active
|
|
124
|
-
? 'bg-blue-100 text-blue-700'
|
|
125
|
-
: 'text-gray-700 hover:bg-gray-100',
|
|
164
|
+
? 'bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-300'
|
|
165
|
+
: 'text-gray-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-slate-700',
|
|
126
166
|
onclick: (e) => {
|
|
127
167
|
e.preventDefault();
|
|
128
168
|
sidebarOpen = false;
|
|
@@ -131,7 +171,7 @@ const MenuItem = {
|
|
|
131
171
|
}, [
|
|
132
172
|
item.icon && m(Icon, { name: item.icon, class: 'w-5 h-5 flex-shrink-0' }),
|
|
133
173
|
m('span.truncate', item.label),
|
|
134
|
-
item.badge && m('span.ml-auto.bg-blue-100.text-blue-600.text-xs.px-2.py-0.5.rounded-full', item.badge),
|
|
174
|
+
item.badge && m('span.ml-auto.bg-blue-100.dark:bg-blue-900/50.text-blue-600.dark:text-blue-300.text-xs.px-2.py-0.5.rounded-full', item.badge),
|
|
135
175
|
]);
|
|
136
176
|
},
|
|
137
177
|
};
|
|
@@ -154,7 +194,7 @@ const MenuGroup = {
|
|
|
154
194
|
return m('div.mb-2', [
|
|
155
195
|
// Group header
|
|
156
196
|
group.collapsible !== false
|
|
157
|
-
? m('button.w-full.flex.items-center.gap-3.px-3.py-2.text-xs.font-semibold.text-gray-500.uppercase.tracking-wider.hover:text-gray-700', {
|
|
197
|
+
? m('button.w-full.flex.items-center.gap-3.px-3.py-2.text-xs.font-semibold.text-gray-500 dark:text-slate-400.uppercase.tracking-wider.hover:text-gray-700 dark:hover:text-slate-200', {
|
|
158
198
|
onclick: () => { vnode.state.collapsed = !collapsed; },
|
|
159
199
|
}, [
|
|
160
200
|
group.icon && m(Icon, { name: group.icon, class: 'w-4 h-4' }),
|
|
@@ -164,7 +204,7 @@ const MenuGroup = {
|
|
|
164
204
|
class: 'w-4 h-4 transition-transform'
|
|
165
205
|
}),
|
|
166
206
|
])
|
|
167
|
-
: m('div.px-3.py-2.text-xs.font-semibold.text-gray-500.uppercase.tracking-wider', [
|
|
207
|
+
: m('div.px-3.py-2.text-xs.font-semibold.text-gray-500 dark:text-slate-400.uppercase.tracking-wider', [
|
|
168
208
|
group.icon && m(Icon, { name: group.icon, class: 'w-4 h-4 inline mr-2' }),
|
|
169
209
|
group.label,
|
|
170
210
|
]),
|
|
@@ -217,24 +257,26 @@ const Sidebar = {
|
|
|
217
257
|
onclick: () => { sidebarOpen = false; },
|
|
218
258
|
}),
|
|
219
259
|
|
|
220
|
-
m('aside.w-64.bg-white.border-r.border-gray-200.flex.flex-col.h-screen.fixed.left-0.top-0.z-40.transition-transform.duration-200', {
|
|
260
|
+
m('aside.w-64.bg-white dark:bg-slate-800.border-r.border-gray-200 dark:border-slate-600.flex.flex-col.h-screen.fixed.left-0.top-0.z-40.transition-transform.duration-200', {
|
|
221
261
|
class: sidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0',
|
|
222
262
|
}, [
|
|
223
263
|
// Logo/Title
|
|
224
|
-
m('div.h-16.flex.items-center.justify-between.px-4.border-b.border-gray-200', [
|
|
225
|
-
m('a.flex.items-center.gap-2.text-lg.font-bold.text-gray-900', {
|
|
264
|
+
m('div.h-16.flex.items-center.justify-between.gap-2.px-4.border-b.border-gray-200.dark:border-slate-600', [
|
|
265
|
+
m('a.flex.items-center.gap-2.text-lg.font-bold.text-gray-900.dark:text-slate-100.min-w-0', {
|
|
226
266
|
href: '/',
|
|
227
267
|
onclick: (e) => { e.preventDefault(); sidebarOpen = false; m.route.set('/'); },
|
|
228
268
|
}, [
|
|
229
|
-
m('div.w-8.h-8.bg-blue-600.rounded-lg.flex.items-center.justify-center', [
|
|
269
|
+
m('div.w-8.h-8.bg-blue-600.rounded-lg.flex.items-center.justify-center.flex-shrink-0', [
|
|
230
270
|
m('span.text-white.font-bold', 'A'),
|
|
231
271
|
]),
|
|
232
|
-
m('span', settings?.title || 'Admin'),
|
|
272
|
+
m('span.truncate', settings?.title || 'Admin'),
|
|
273
|
+
]),
|
|
274
|
+
m('div.flex.items-center.gap-2.flex-shrink-0', [
|
|
275
|
+
m(ThemeToggle),
|
|
276
|
+
m('button.p-1.text-gray-400.dark:text-slate-500.hover:text-gray-600.dark:hover:text-slate-300.lg:hidden', {
|
|
277
|
+
onclick: () => { sidebarOpen = false; },
|
|
278
|
+
}, m(Icon, { name: 'x', class: 'w-5 h-5' })),
|
|
233
279
|
]),
|
|
234
|
-
// Close button (mobile only)
|
|
235
|
-
m('button.p-1.text-gray-400.hover:text-gray-600.lg:hidden', {
|
|
236
|
-
onclick: () => { sidebarOpen = false; },
|
|
237
|
-
}, m(Icon, { name: 'x', class: 'w-5 h-5' })),
|
|
238
280
|
]),
|
|
239
281
|
|
|
240
282
|
// Menu
|
|
@@ -249,18 +291,18 @@ const Sidebar = {
|
|
|
249
291
|
]),
|
|
250
292
|
|
|
251
293
|
// User section
|
|
252
|
-
state.user && m('div.p-4.border-t.border-gray-200', [
|
|
294
|
+
state.user && m('div.p-4.border-t.border-gray-200.dark:border-slate-600', [
|
|
253
295
|
m('div.flex.items-center.gap-3', [
|
|
254
|
-
m('div.w-8.h-8.bg-gray-200.rounded-full.flex.items-center.justify-center', [
|
|
255
|
-
m('span.text-sm.font-medium.text-gray-600',
|
|
296
|
+
m('div.w-8.h-8.bg-gray-200 dark:bg-slate-700.rounded-full.flex.items-center.justify-center', [
|
|
297
|
+
m('span.text-sm.font-medium.text-gray-600.dark:text-slate-300',
|
|
256
298
|
(state.user.name || state.user.email || 'A').charAt(0).toUpperCase()
|
|
257
299
|
),
|
|
258
300
|
]),
|
|
259
301
|
m('div.flex-1.min-w-0', [
|
|
260
|
-
m('p.text-sm.font-medium.text-gray-900.truncate', state.user.name || 'Admin'),
|
|
261
|
-
m('p.text-xs.text-gray-500.truncate', state.user.email),
|
|
302
|
+
m('p.text-sm.font-medium.text-gray-900 dark:text-slate-100.truncate', state.user.name || 'Admin'),
|
|
303
|
+
m('p.text-xs.text-gray-500 dark:text-slate-400.truncate', state.user.email),
|
|
262
304
|
]),
|
|
263
|
-
m('button.p-1.text-gray-400.hover:text-gray-600', {
|
|
305
|
+
m('button.p-1.text-gray-400 dark:text-slate-500.hover:text-gray-600 dark:hover:text-slate-300', {
|
|
264
306
|
title: 'Logout',
|
|
265
307
|
onclick: async () => {
|
|
266
308
|
await api.post('/auth/logout');
|
|
@@ -279,11 +321,14 @@ const Sidebar = {
|
|
|
279
321
|
// Mobile Header with hamburger button
|
|
280
322
|
const MobileHeader = {
|
|
281
323
|
view(vnode) {
|
|
282
|
-
return m('div.lg:hidden.fixed.top-0.left-0.right-0.h-14.bg-white.border-b.border-gray-200.flex.items-center.px-4.z-20', [
|
|
283
|
-
m('
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
324
|
+
return m('div.lg:hidden.fixed.top-0.left-0.right-0.h-14.bg-white.dark:bg-slate-800.border-b.border-gray-200.dark:border-slate-600.flex.items-center.justify-between.px-4.z-20', [
|
|
325
|
+
m('div.flex.items-center.min-w-0', [
|
|
326
|
+
m('button.p-2.-ml-1.text-gray-600.dark:text-slate-400.hover:text-gray-900.dark:hover:text-slate-100.rounded-lg.hover:bg-gray-100.dark:hover:bg-slate-700', {
|
|
327
|
+
onclick: () => { sidebarOpen = true; },
|
|
328
|
+
}, m(Icon, { name: 'menu', class: 'w-6 h-6' })),
|
|
329
|
+
m('span.ml-3.text-lg.font-semibold.text-gray-900.dark:text-slate-100.truncate', 'Admin'),
|
|
330
|
+
]),
|
|
331
|
+
m(ThemeToggle),
|
|
287
332
|
]);
|
|
288
333
|
},
|
|
289
334
|
};
|
|
@@ -291,7 +336,7 @@ const MobileHeader = {
|
|
|
291
336
|
// Layout Component (with sidebar)
|
|
292
337
|
const Layout = {
|
|
293
338
|
view(vnode) {
|
|
294
|
-
return m('div.min-h-screen.bg-gray-50', [
|
|
339
|
+
return m('div.min-h-screen.bg-gray-50.dark:bg-slate-950', [
|
|
295
340
|
m(MobileHeader),
|
|
296
341
|
m(Sidebar),
|
|
297
342
|
m('main.lg:ml-64.p-6.pt-20.lg:pt-6', vnode.children),
|
package/plugins/index.js
CHANGED
|
@@ -14,6 +14,7 @@ const auditLogPlugin = require('./audit-log');
|
|
|
14
14
|
const recaptchaPlugin = require('./recaptcha');
|
|
15
15
|
const swaggerPlugin = require('./swagger');
|
|
16
16
|
const healthCheckPlugin = require('./health-check');
|
|
17
|
+
const restResourcePlugin = require('./rest-resources');
|
|
17
18
|
|
|
18
19
|
module.exports = {
|
|
19
20
|
sitemapPlugin,
|
|
@@ -27,5 +28,6 @@ module.exports = {
|
|
|
27
28
|
recaptchaPlugin,
|
|
28
29
|
swaggerPlugin,
|
|
29
30
|
healthCheckPlugin,
|
|
31
|
+
restResourcePlugin,
|
|
30
32
|
};
|
|
31
33
|
|