webspresso 0.0.89 → 0.0.91
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 +450 -53
- package/bin/commands/service.js +193 -0
- package/bin/webspresso.js +2 -0
- package/core/auth/manager.js +36 -26
- package/core/auth/middleware.js +21 -2
- package/core/compression/compressible.js +51 -0
- package/core/compression/compression-stream.js +59 -0
- package/core/compression/index.js +25 -0
- package/core/compression/middleware.js +301 -0
- package/core/compression/negotiate.js +153 -0
- package/core/errors/base.js +31 -0
- package/core/errors/domain.js +193 -0
- package/core/errors/http.js +133 -0
- package/core/errors/index.js +77 -0
- package/core/errors/middleware.js +162 -0
- package/core/errors/normalize.js +171 -0
- package/core/orm/complexity.js +120 -0
- package/core/orm/events.js +64 -14
- package/core/orm/index.js +45 -10
- package/core/orm/model.js +10 -2
- package/core/orm/query-builder.js +30 -4
- package/core/orm/repository.js +57 -25
- package/core/orm/scopes.js +2 -1
- package/core/orm/transaction.js +39 -5
- package/core/orm/utils.js +6 -3
- package/core/queue/adapters/database.js +284 -0
- package/core/queue/adapters/memory.js +155 -0
- package/core/queue/adapters/redis.js +336 -0
- package/core/queue/index.js +21 -0
- package/core/queue/job.js +103 -0
- package/core/queue/manager.js +387 -0
- package/core/realtime/adapters/redis.js +431 -0
- package/core/realtime/adapters/socket-io.js +203 -0
- package/core/realtime/adapters/sse.js +210 -0
- package/core/realtime/adapters/websocket.js +337 -0
- package/core/realtime/auth-manager.js +140 -0
- package/core/realtime/core.js +359 -0
- package/core/realtime/emitter.js +98 -0
- package/core/realtime/errors.js +71 -0
- package/core/realtime/index.js +79 -0
- package/core/realtime/reconnect-manager.js +118 -0
- package/core/realtime/registry.js +243 -0
- package/core/shutdown/http-adapter.js +134 -0
- package/core/shutdown/index.js +12 -0
- package/core/shutdown/shutdown-manager.js +312 -0
- package/core/ssr/index.js +13 -0
- package/core/ssr/stream.js +174 -0
- package/index.d.ts +829 -52
- package/index.js +103 -0
- package/package.json +22 -2
- package/plugins/admin-panel/api.js +37 -4
- package/plugins/admin-panel/app.js +67 -17
- package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +4 -1
- package/plugins/admin-panel/client/parts/03-pagination-intro.js +25 -18
- package/plugins/admin-panel/client/parts/04-field-renderers.js +102 -58
- package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +37 -23
- package/plugins/admin-panel/client/parts/08-record-list.js +27 -34
- package/plugins/admin-panel/client/parts/09-record-form.js +168 -19
- package/plugins/admin-panel/core/admin-module.js +2 -2
- package/plugins/admin-panel/core/api-extensions.js +149 -0
- package/plugins/admin-panel/field-renderers/rich-text.js +3 -2
- package/plugins/admin-panel/index.js +131 -18
- package/plugins/admin-panel/modules/admin-users.js +770 -0
- package/plugins/admin-panel/modules/custom-pages.js +144 -45
- package/plugins/admin-panel/modules/menu.js +85 -0
- package/plugins/admin-panel/modules/user-management.js +5 -2
- package/plugins/admin-panel/vendor/mithril.min.js +1 -0
- package/plugins/admin-panel/vendor/quill.min.js +7 -0
- package/plugins/admin-panel/vendor/quill.snow.min.css +945 -0
- package/plugins/admin-panel/vendor/tailwind.min.js +65 -0
- package/plugins/basic-auth/index.js +293 -0
- package/plugins/content/client/inline-edit.js +41 -17
- package/plugins/cors.js +12 -8
- package/plugins/csrf/index.js +8 -8
- package/plugins/data-exchange/export-xlsx.js +5 -1
- package/plugins/data-exchange/import.js +16 -2
- package/plugins/data-exchange/index.js +14 -0
- package/plugins/email/index.js +14 -1
- package/plugins/index.js +29 -0
- package/plugins/queue/index.js +108 -0
- package/plugins/realtime/index.js +150 -0
- package/plugins/recaptcha/index.js +11 -0
- package/plugins/redirect/index.js +9 -1
- package/plugins/rest-resources/index.js +280 -11
- package/plugins/upload/index.js +16 -0
- package/src/app-context.js +46 -6
- package/src/file-router.js +220 -28
- package/src/helpers.js +24 -5
- package/src/plugin-manager.js +134 -7
- package/src/server.js +470 -44
- package/src/services/builtins/auth.js +273 -0
- package/src/services/builtins/exchange.js +229 -0
- package/src/services/builtins/mail.js +134 -0
- package/src/services/builtins/media.js +196 -0
- package/src/services/builtins/system.js +221 -0
- package/src/services/discovery.js +140 -0
- package/src/services/executor.js +292 -0
- package/src/services/index.js +70 -0
- package/src/services/memoize.js +256 -0
- package/src/services/registry.js +288 -0
- package/src/services/validator.js +281 -0
- package/templates/skills/webspresso-usage/REFERENCE-framework.md +2 -2
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* @module plugins/admin-panel
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const fs = require('fs');
|
|
7
9
|
const { createAdminUserModel } = require('./admin-user-model');
|
|
8
10
|
const { generateAdminUsersMigration } = require('./migration-template');
|
|
9
11
|
const { createApiHandlers } = require('./api');
|
|
@@ -16,6 +18,7 @@ const { registerDefaultBulkActions, generateBulkActionsComponent } = require('./
|
|
|
16
18
|
const { registerDefaultPages, createCustomPageApiHandlers, generateCustomPageComponent } = require('./modules/custom-pages');
|
|
17
19
|
const { registerModelMenuItems, registerSystemMenuItems, generateMenuComponent } = require('./modules/menu');
|
|
18
20
|
const { generateProfileComponent } = require('./modules/profile');
|
|
21
|
+
const { registerAdminUsersManagement, createAdminUsersApiHandlers, generateAdminUsersComponent } = require('./modules/admin-users');
|
|
19
22
|
const { registerModule, registerPageDir } = require('./core/admin-module');
|
|
20
23
|
|
|
21
24
|
|
|
@@ -44,11 +47,14 @@ function adminPanelPlugin(options = {}) {
|
|
|
44
47
|
db,
|
|
45
48
|
auth,
|
|
46
49
|
userManagement: userMgmtConfig,
|
|
50
|
+
adminUsers: adminUsersOption = true,
|
|
47
51
|
configure,
|
|
48
52
|
uploadUrl: uploadUrlOption,
|
|
49
53
|
richTextSanitize = true,
|
|
50
54
|
} = options;
|
|
51
55
|
|
|
56
|
+
const adminUsersConfig = typeof adminUsersOption === 'object' ? adminUsersOption : { enabled: adminUsersOption !== false };
|
|
57
|
+
|
|
52
58
|
// Validate required options
|
|
53
59
|
if (!db) {
|
|
54
60
|
throw new Error('Admin panel plugin requires a database instance. Pass `db` in options.');
|
|
@@ -71,22 +77,26 @@ function adminPanelPlugin(options = {}) {
|
|
|
71
77
|
version: '2.0.0',
|
|
72
78
|
description: 'Modular admin panel for Webspresso with extensions support',
|
|
73
79
|
|
|
74
|
-
// CSP requirements for admin panel scripts, styles, and custom page iframe sources
|
|
75
|
-
// Note: cdn.quilljs.com 301-redirects to cdn.jsdelivr.net; CSP is enforced on the final URL.
|
|
80
|
+
// CSP requirements for admin panel scripts, styles, and custom page iframe sources (Offline Zero-CDN)
|
|
76
81
|
csp: {
|
|
77
82
|
styleSrc: [
|
|
83
|
+
"'self'",
|
|
84
|
+
"'unsafe-inline'",
|
|
78
85
|
'https://cdn.quilljs.com',
|
|
79
86
|
'https://cdn.jsdelivr.net',
|
|
80
87
|
'https://unpkg.com',
|
|
81
88
|
'https://cdn.tailwindcss.com',
|
|
82
89
|
],
|
|
83
90
|
scriptSrc: [
|
|
91
|
+
"'self'",
|
|
92
|
+
"'unsafe-inline'",
|
|
93
|
+
"'unsafe-eval'",
|
|
84
94
|
'https://cdn.quilljs.com',
|
|
85
95
|
'https://cdn.jsdelivr.net',
|
|
86
96
|
'https://unpkg.com',
|
|
87
97
|
'https://cdn.tailwindcss.com',
|
|
88
98
|
],
|
|
89
|
-
connectSrc: ['https://unpkg.com', 'https://cdn.tailwindcss.com', 'https://cdn.jsdelivr.net'],
|
|
99
|
+
connectSrc: ["'self'", 'https://unpkg.com', 'https://cdn.tailwindcss.com', 'https://cdn.jsdelivr.net'],
|
|
90
100
|
frameSrc: ["'self'", 'data:', 'blob:', 'https:'],
|
|
91
101
|
},
|
|
92
102
|
enabled,
|
|
@@ -152,13 +162,29 @@ function adminPanelPlugin(options = {}) {
|
|
|
152
162
|
// Session must be registered before file routes so SSR pages can read adminUser.
|
|
153
163
|
if (enabled && ctx.app && !ctx.app._webspressoSessionInitialized) {
|
|
154
164
|
const secret = sessionSecret || process.env.SESSION_SECRET || 'webspresso-admin-secret-change-in-production';
|
|
165
|
+
const secureMode =
|
|
166
|
+
process.env.NODE_ENV === 'production' ||
|
|
167
|
+
process.env.COOKIE_SECURE === 'true' ||
|
|
168
|
+
/^https:/i.test(String(process.env.BASE_URL || '').trim())
|
|
169
|
+
? true
|
|
170
|
+
: 'auto';
|
|
171
|
+
if (process.env.NODE_ENV === 'production') {
|
|
172
|
+
const warnMsg = '[webspresso:admin] Warning: Admin panel is using default MemoryStore in production. Please configure a persistent session store to prevent memory leaks.';
|
|
173
|
+
if (typeof process !== 'undefined' && typeof process.emitWarning === 'function') {
|
|
174
|
+
process.emitWarning(warnMsg, 'SecurityWarning');
|
|
175
|
+
} else {
|
|
176
|
+
console.warn(warnMsg);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
155
180
|
ctx.app.use(session({
|
|
156
181
|
secret,
|
|
157
182
|
resave: false,
|
|
158
183
|
saveUninitialized: false,
|
|
159
184
|
cookie: {
|
|
160
|
-
secure:
|
|
185
|
+
secure: secureMode,
|
|
161
186
|
httpOnly: true,
|
|
187
|
+
sameSite: 'lax',
|
|
162
188
|
maxAge: 24 * 60 * 60 * 1000, // 24 hours
|
|
163
189
|
},
|
|
164
190
|
}));
|
|
@@ -178,22 +204,24 @@ function adminPanelPlugin(options = {}) {
|
|
|
178
204
|
const { app } = ctx;
|
|
179
205
|
|
|
180
206
|
const uploadUrlResolved =
|
|
181
|
-
uploadUrlOption || app.get('webspresso.uploadPath')
|
|
207
|
+
uploadUrlOption || (app && typeof app.get === 'function' ? app.get('webspresso.uploadPath') : null);
|
|
182
208
|
if (uploadUrlResolved) {
|
|
183
209
|
registry.configure({ uploadUrl: uploadUrlResolved });
|
|
184
210
|
}
|
|
185
211
|
|
|
186
212
|
// Check if admin_users table exists (migration run)
|
|
187
|
-
db
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
213
|
+
if (db?.knex?.schema?.hasTable) {
|
|
214
|
+
db.knex.schema.hasTable('admin_users').then((hasAdminTable) => {
|
|
215
|
+
if (!hasAdminTable) {
|
|
216
|
+
console.warn(
|
|
217
|
+
'\n⚠️ [admin-panel] admin_users table not found. Run: webspresso admin:setup\n' +
|
|
218
|
+
' Or create the table manually via migration. See: AdminUser model / getMigrationTemplate()\n'
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}).catch((err) => {
|
|
222
|
+
console.warn('[admin-panel] Could not check admin_users table:', err.message);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
197
225
|
|
|
198
226
|
// Create and register AdminUser model
|
|
199
227
|
const { hasModel: hasGlobalModel, getModel: getGlobalModel } = require('../../core/orm/model');
|
|
@@ -227,6 +255,14 @@ function adminPanelPlugin(options = {}) {
|
|
|
227
255
|
});
|
|
228
256
|
}
|
|
229
257
|
|
|
258
|
+
// Register admin user management if enabled
|
|
259
|
+
if (adminUsersConfig.enabled !== false) {
|
|
260
|
+
registerAdminUsersManagement({
|
|
261
|
+
registry,
|
|
262
|
+
config: adminUsersConfig,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
230
266
|
// Create API handlers
|
|
231
267
|
const apiHandlers = createApiHandlers({
|
|
232
268
|
path: adminPath,
|
|
@@ -245,6 +281,16 @@ function adminPanelPlugin(options = {}) {
|
|
|
245
281
|
|
|
246
282
|
const pageHandlers = createCustomPageApiHandlers({ registry, db });
|
|
247
283
|
|
|
284
|
+
// Admin users management API handlers
|
|
285
|
+
let adminUsersHandlers = null;
|
|
286
|
+
if (adminUsersConfig.enabled !== false) {
|
|
287
|
+
adminUsersHandlers = createAdminUsersApiHandlers({
|
|
288
|
+
db,
|
|
289
|
+
AdminUser,
|
|
290
|
+
hashPassword: (password, rounds) => bcrypt.hash(password, rounds),
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
248
294
|
// User management API handlers
|
|
249
295
|
let userHandlers = null;
|
|
250
296
|
if (userMgmtConfig?.enabled) {
|
|
@@ -284,11 +330,22 @@ function adminPanelPlugin(options = {}) {
|
|
|
284
330
|
// Settings API routes
|
|
285
331
|
ctx.addRoute('get', `${adminPath}/api/extensions/settings`, requireAuth, extensionHandlers.settingsGetHandler);
|
|
286
332
|
ctx.addRoute('post', `${adminPath}/api/extensions/settings`, requireAuth, extensionHandlers.settingsUpdateHandler);
|
|
333
|
+
ctx.addRoute('get', `${adminPath}/api/extensions/system-info`, requireAuth, extensionHandlers.systemInfoHandler);
|
|
334
|
+
ctx.addRoute('get', `${adminPath}/api/system-info`, requireAuth, extensionHandlers.systemInfoHandler);
|
|
287
335
|
|
|
288
336
|
// Custom pages API routes
|
|
289
337
|
ctx.addRoute('get', `${adminPath}/api/extensions/pages/:pageId/data`, requireAuth, pageHandlers.getPageData);
|
|
290
338
|
ctx.addRoute('all', `${adminPath}/api/extensions/pages/:pageId/actions/:actionId`, requireAuth, pageHandlers.executePageAction);
|
|
291
339
|
|
|
340
|
+
// Admin users management API routes
|
|
341
|
+
if (adminUsersHandlers) {
|
|
342
|
+
ctx.addRoute('get', `${adminPath}/api/admins`, requireAuth, adminUsersHandlers.listAdmins);
|
|
343
|
+
ctx.addRoute('get', `${adminPath}/api/admins/:id`, requireAuth, adminUsersHandlers.getAdmin);
|
|
344
|
+
ctx.addRoute('post', `${adminPath}/api/admins`, requireAuth, adminUsersHandlers.createAdmin);
|
|
345
|
+
ctx.addRoute('put', `${adminPath}/api/admins/:id`, requireAuth, adminUsersHandlers.updateAdmin);
|
|
346
|
+
ctx.addRoute('delete', `${adminPath}/api/admins/:id`, requireAuth, adminUsersHandlers.deleteAdmin);
|
|
347
|
+
}
|
|
348
|
+
|
|
292
349
|
// User management API routes
|
|
293
350
|
if (userHandlers) {
|
|
294
351
|
ctx.addRoute('get', `${adminPath}/api/users`, requireAuth, userHandlers.listUsers);
|
|
@@ -322,6 +379,30 @@ function adminPanelPlugin(options = {}) {
|
|
|
322
379
|
// Query API routes (auth required)
|
|
323
380
|
ctx.addRoute('get', `${adminPath}/api/models/:model/queries/:query`, requireAuth, apiHandlers.queryHandler);
|
|
324
381
|
|
|
382
|
+
// ==========================================
|
|
383
|
+
// Static Vendor Assets (Offline Zero-CDN)
|
|
384
|
+
// ==========================================
|
|
385
|
+
const vendorDir = path.join(__dirname, 'vendor');
|
|
386
|
+
ctx.addRoute('get', `${adminPath}/vendor/:file`, (req, res) => {
|
|
387
|
+
const fileName = path.basename(req.params.file);
|
|
388
|
+
const filePath = path.join(vendorDir, fileName);
|
|
389
|
+
|
|
390
|
+
if (!fs.existsSync(filePath)) {
|
|
391
|
+
return res.status(404).send('Vendor asset not found');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (fileName.endsWith('.js')) {
|
|
395
|
+
res.type('application/javascript');
|
|
396
|
+
} else if (fileName.endsWith('.css')) {
|
|
397
|
+
res.type('text/css');
|
|
398
|
+
} else {
|
|
399
|
+
res.type('application/octet-stream');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
res.setHeader('Cache-Control', 'public, max-age=86400');
|
|
403
|
+
res.sendFile(filePath);
|
|
404
|
+
});
|
|
405
|
+
|
|
325
406
|
// ==========================================
|
|
326
407
|
// HTML Endpoints
|
|
327
408
|
// ==========================================
|
|
@@ -391,6 +472,7 @@ function generateAdminPanelHtml(adminPath, registry) {
|
|
|
391
472
|
const bulkActionsComponent = generateBulkActionsComponent();
|
|
392
473
|
const customPageComponent = generateCustomPageComponent();
|
|
393
474
|
const profileComponent = generateProfileComponent();
|
|
475
|
+
const adminUsersComponent = generateAdminUsersComponent();
|
|
394
476
|
|
|
395
477
|
const settings = registry.settings;
|
|
396
478
|
const fieldRenderersCode = Array.from(registry.fieldRenderers.entries()).map(([type, r]) => {
|
|
@@ -426,9 +508,9 @@ function generateAdminPanelHtml(adminPath, registry) {
|
|
|
426
508
|
});
|
|
427
509
|
} catch (e) {}
|
|
428
510
|
})();
|
|
429
|
-
|
|
430
|
-
<script src="
|
|
431
|
-
<script src="
|
|
511
|
+
<script>window.__ADMIN_PATH__ = ${JSON.stringify(adminPath)};</script>
|
|
512
|
+
<script src="${adminPath}/vendor/mithril.min.js"></script>
|
|
513
|
+
<script src="${adminPath}/vendor/tailwind.min.js"></script>
|
|
432
514
|
<script>tailwind.config = { darkMode: 'class' };</script>
|
|
433
515
|
<style>
|
|
434
516
|
body { margin: 0; font-family: system-ui, -apple-system, sans-serif; }
|
|
@@ -445,6 +527,35 @@ function generateAdminPanelHtml(adminPath, registry) {
|
|
|
445
527
|
.dark .ql-snow .ql-stroke { stroke: #94a3b8; }
|
|
446
528
|
.dark .ql-snow .ql-fill { fill: #94a3b8; }
|
|
447
529
|
.dark .ql-picker { color: #cbd5e1; }
|
|
530
|
+
@media (min-width: 768px) {
|
|
531
|
+
.admin-table-sticky-check {
|
|
532
|
+
position: sticky;
|
|
533
|
+
left: 0;
|
|
534
|
+
z-index: 5;
|
|
535
|
+
box-shadow: 4px 0 8px -4px rgba(0, 0, 0, 0.08);
|
|
536
|
+
}
|
|
537
|
+
th.admin-table-sticky-check {
|
|
538
|
+
z-index: 15;
|
|
539
|
+
}
|
|
540
|
+
.admin-table-sticky-first {
|
|
541
|
+
position: sticky;
|
|
542
|
+
left: 40px;
|
|
543
|
+
z-index: 5;
|
|
544
|
+
box-shadow: 4px 0 8px -4px rgba(0, 0, 0, 0.08);
|
|
545
|
+
}
|
|
546
|
+
th.admin-table-sticky-first {
|
|
547
|
+
z-index: 15;
|
|
548
|
+
}
|
|
549
|
+
.admin-table-sticky-actions {
|
|
550
|
+
position: sticky;
|
|
551
|
+
right: 0;
|
|
552
|
+
z-index: 5;
|
|
553
|
+
box-shadow: -4px 0 8px -4px rgba(0, 0, 0, 0.08);
|
|
554
|
+
}
|
|
555
|
+
th.admin-table-sticky-actions {
|
|
556
|
+
z-index: 15;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
448
559
|
</style>
|
|
449
560
|
${registry.getStylesHtml()}
|
|
450
561
|
${registry.getScriptsHtml('head')}
|
|
@@ -473,6 +584,7 @@ function generateAdminPanelHtml(adminPath, registry) {
|
|
|
473
584
|
${bulkActionsComponent}
|
|
474
585
|
${customPageComponent}
|
|
475
586
|
${profileComponent}
|
|
587
|
+
${adminUsersComponent}
|
|
476
588
|
|
|
477
589
|
// Custom page components container
|
|
478
590
|
window.__customPages = window.__customPages || {};
|
|
@@ -490,5 +602,6 @@ function generateAdminPanelHtml(adminPath, registry) {
|
|
|
490
602
|
|
|
491
603
|
// Export registry and utilities for external use
|
|
492
604
|
module.exports = adminPanelPlugin;
|
|
605
|
+
module.exports.adminPanelPlugin = adminPanelPlugin;
|
|
493
606
|
module.exports.AdminRegistry = AdminRegistry;
|
|
494
607
|
module.exports.defaultRegistry = defaultRegistry;
|