webspresso 0.0.97 → 0.0.99

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.
Files changed (136) hide show
  1. package/.agents/ADMIN.md +163 -5
  2. package/.agents/AGENTS.md +1 -1
  3. package/.agents/ARCHITECTURE.md +10 -0
  4. package/.agents/AUTH.md +169 -0
  5. package/.agents/CLI.md +195 -1
  6. package/.agents/ORM.md +88 -4
  7. package/.agents/PLUGINS.md +221 -9
  8. package/.agents/QUEUE.md +127 -0
  9. package/.agents/ROUTING.md +127 -7
  10. package/AGENTS.md +1 -0
  11. package/bin/commands/add-esbuild.js +80 -0
  12. package/bin/commands/add-tailwind.js +76 -31
  13. package/bin/commands/analytics-prune.js +57 -0
  14. package/bin/commands/api.js +207 -15
  15. package/bin/commands/build.js +99 -0
  16. package/bin/commands/dev.js +64 -35
  17. package/bin/commands/doctor.js +26 -0
  18. package/bin/commands/email-prune.js +47 -0
  19. package/bin/commands/make-job.js +86 -0
  20. package/bin/commands/make-model.js +250 -0
  21. package/bin/commands/new.js +45 -28
  22. package/bin/commands/openapi.js +102 -0
  23. package/bin/commands/page.js +6 -3
  24. package/bin/commands/queue-prune.js +59 -0
  25. package/bin/commands/routes.js +52 -0
  26. package/bin/commands/service.js +6 -3
  27. package/bin/commands/typegen.js +106 -0
  28. package/bin/commands/upload-prune.js +79 -0
  29. package/bin/webspresso.js +22 -0
  30. package/core/applySchema.js +13 -6
  31. package/core/auth/hash.js +21 -12
  32. package/core/auth/index.js +60 -0
  33. package/core/auth/manager.js +134 -0
  34. package/core/auth/middleware.js +88 -0
  35. package/core/auth/oauth.js +517 -0
  36. package/core/auth/qrcode.js +499 -0
  37. package/core/auth/totp.js +504 -0
  38. package/core/compression/middleware.js +23 -17
  39. package/core/openapi/build-from-api-routes.js +160 -29
  40. package/core/orm/cache/fingerprint.js +15 -1
  41. package/core/orm/cache/memory-provider.js +27 -2
  42. package/core/orm/eager-loader.js +144 -10
  43. package/core/orm/index.js +20 -7
  44. package/core/orm/model.js +53 -7
  45. package/core/orm/query-builder.js +90 -4
  46. package/core/orm/repository.js +44 -3
  47. package/core/orm/search.js +213 -0
  48. package/core/queue/adapters/database.js +94 -0
  49. package/core/queue/adapters/memory.js +62 -0
  50. package/core/queue/adapters/redis.js +80 -0
  51. package/core/queue/cron.js +331 -0
  52. package/core/queue/index.js +8 -1
  53. package/core/queue/manager.js +205 -1
  54. package/core/queue/scheduler.js +359 -0
  55. package/core/sse/index.js +19 -0
  56. package/core/sse/stream.js +430 -0
  57. package/core/ssr/stream.js +11 -0
  58. package/index.d.ts +1188 -10
  59. package/index.js +88 -96
  60. package/package.json +1 -1
  61. package/plugins/admin-panel/api.js +279 -17
  62. package/plugins/admin-panel/app.js +114 -22
  63. package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +314 -9
  64. package/plugins/admin-panel/client/parts/04-field-renderers.js +264 -67
  65. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +333 -36
  66. package/plugins/admin-panel/client/parts/06-login-setup-forms.js +119 -28
  67. package/plugins/admin-panel/client/parts/07-model-list.js +49 -8
  68. package/plugins/admin-panel/client/parts/08-record-list.js +248 -23
  69. package/plugins/admin-panel/client/parts/09-record-form.js +59 -12
  70. package/plugins/admin-panel/client/parts/10-export-registry.js +23 -0
  71. package/plugins/admin-panel/client/verify-spa-parts.js +2 -0
  72. package/plugins/admin-panel/core/admin-module.js +13 -7
  73. package/plugins/admin-panel/core/api-extensions.js +30 -2
  74. package/plugins/admin-panel/core/registry.js +96 -16
  75. package/plugins/admin-panel/field-renderers/file-upload.js +187 -104
  76. package/plugins/admin-panel/field-renderers/index.js +2 -0
  77. package/plugins/admin-panel/index.js +96 -0
  78. package/plugins/admin-panel/lib/login-rate-limiter.js +119 -0
  79. package/plugins/admin-panel/modules/admin-users.js +16 -1
  80. package/plugins/admin-panel/modules/bulk-actions.js +13 -5
  81. package/plugins/admin-panel/modules/custom-pages.js +492 -3
  82. package/plugins/admin-panel/modules/dashboard.js +283 -0
  83. package/plugins/admin-panel/modules/menu.js +56 -18
  84. package/plugins/admin-panel/modules/user-management.js +24 -6
  85. package/plugins/api-key/crypto.js +101 -0
  86. package/plugins/api-key/index.js +288 -0
  87. package/plugins/api-key/middleware.js +461 -0
  88. package/plugins/api-key/migration-template.js +48 -0
  89. package/plugins/api-key/model.js +50 -0
  90. package/plugins/api-key/scopes.js +125 -0
  91. package/plugins/audit-log/admin-component.js +487 -59
  92. package/plugins/audit-log/api-handlers.js +148 -0
  93. package/plugins/audit-log/index.js +153 -3
  94. package/plugins/audit-log/purge.js +60 -10
  95. package/plugins/dashboard/app.js +152 -27
  96. package/plugins/dashboard/index.js +143 -39
  97. package/plugins/dashboard/styles.js +78 -0
  98. package/plugins/data-exchange/export-xlsx.js +9 -2
  99. package/plugins/data-exchange/import.js +9 -2
  100. package/plugins/data-exchange/index.js +6 -3
  101. package/plugins/email/index.js +132 -2
  102. package/plugins/email/purge.js +60 -10
  103. package/plugins/file-manager/admin-component.js +20 -4
  104. package/plugins/file-manager/index.js +21 -4
  105. package/plugins/index.js +10 -0
  106. package/plugins/oauth/index.js +338 -0
  107. package/plugins/orm-cache-admin/index.js +4 -2
  108. package/plugins/queue/index.js +15 -0
  109. package/plugins/schema-explorer/admin-component.js +714 -0
  110. package/plugins/schema-explorer/relations-graph.js +205 -0
  111. package/plugins/schema-explorer.js +248 -69
  112. package/plugins/site-analytics/admin-component.js +23 -0
  113. package/plugins/site-analytics/api-handlers.js +42 -0
  114. package/plugins/site-analytics/index.js +147 -2
  115. package/plugins/site-analytics/purge.js +168 -0
  116. package/plugins/swagger.js +261 -13
  117. package/plugins/upload/index.js +26 -0
  118. package/plugins/upload/orphan-pruner.js +292 -0
  119. package/src/api/api-loader.js +4 -1
  120. package/src/bundle/index.js +517 -0
  121. package/src/bundle/manifest.js +165 -0
  122. package/src/dev/banner.js +296 -0
  123. package/src/dev/colors.js +70 -0
  124. package/src/dev/index.js +62 -0
  125. package/src/dev/profiler.js +354 -0
  126. package/src/dev/typegen.js +497 -0
  127. package/src/dev-reload/index.js +611 -0
  128. package/src/discovery/file-route-parser.js +44 -1
  129. package/src/discovery/scan-routes.js +2 -2
  130. package/src/file-router.js +347 -162
  131. package/src/helpers.js +967 -98
  132. package/src/njk-frontmatter.js +12 -0
  133. package/src/pages/page-loader.js +61 -11
  134. package/src/routing/cascading.js +378 -0
  135. package/src/routing/middleware-resolver.js +92 -0
  136. package/src/server.js +635 -63
@@ -154,6 +154,480 @@ function createCustomPageApiHandlers(options) {
154
154
  */
155
155
  function generateCustomPageComponent() {
156
156
  return `
157
+ // Developer Error Boundary for Custom Pages
158
+ function renderCustomPageError(page, err) {
159
+ var isFullscreen = page && page.layout === false;
160
+ var pageTitle = (page && page.title) || (page && page.id) || 'Custom Page';
161
+ var pageId = (page && page.id) || 'unknown';
162
+ var errorMsg = (err && (err.message || String(err))) || 'Unknown error';
163
+ var stack = (err && err.stack) || '';
164
+
165
+ var errorCard = m('div.max-w-4xl.mx-auto.bg-white.dark:bg-slate-900.rounded-xl.shadow-lg.border.border-red-200.dark:border-red-900/60.overflow-hidden', [
166
+ // Header
167
+ m('div.px-6.py-4.bg-red-50.dark:bg-red-950/60.border-b.border-red-100.dark:border-red-900/40.flex.items-center.justify-between.gap-4', [
168
+ m('div.flex.items-center.gap-3', [
169
+ m('div.w-9.h-9.rounded-lg.bg-red-100.dark:bg-red-900/80.text-red-600.dark:text-red-300.flex.items-center.justify-center.flex-shrink-0', [
170
+ m('svg.w-5.h-5', { fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' },
171
+ m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', 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' })
172
+ )
173
+ ]),
174
+ m('div', [
175
+ m('h3.text-base.font-bold.text-red-900.dark:text-red-200', 'Custom Page Component Error'),
176
+ m('p.text-xs.text-red-600.dark:text-red-400', 'Page ID: ' + pageId + (pageTitle !== pageId ? ' (' + pageTitle + ')' : ''))
177
+ ])
178
+ ]),
179
+ m('span.px-2.5.py-1.rounded.text-xs.font-mono.font-semibold.bg-red-200/60.dark:bg-red-900.text-red-800.dark:text-red-200', 'Error Boundary')
180
+ ]),
181
+
182
+ // Body
183
+ m('div.p-6.space-y-4', [
184
+ m('div.p-4.rounded-lg.bg-red-50/50.dark:bg-red-950/30.border.border-red-200.dark:border-red-900/40', [
185
+ m('div.text-xs.font-semibold.uppercase.tracking-wider.text-red-700.dark:text-red-400.mb-1', 'Exception Message'),
186
+ m('div.text-sm.font-semibold.text-red-900.dark:text-red-200.break-words', errorMsg)
187
+ ]),
188
+
189
+ stack ? m('div.space-y-1.5', [
190
+ m('div.text-xs.font-semibold.uppercase.tracking-wider.text-gray-500.dark:text-slate-400', 'Stack Trace'),
191
+ m('pre.p-4.rounded-lg.bg-slate-900.text-red-300.dark:text-red-400.text-xs.font-mono.overflow-x-auto.leading-relaxed.border.border-slate-800.max-h-80.overflow-y-auto', stack)
192
+ ]) : null,
193
+
194
+ // Actions
195
+ m('div.pt-3.flex.items-center.justify-end.gap-3.border-t.border-gray-100.dark:border-slate-800', [
196
+ m('button.px-4.py-2.text-xs.font-medium.text-gray-700.dark:text-slate-300.bg-gray-100.dark:bg-slate-800.hover:bg-gray-200.dark:hover:bg-slate-700.rounded-lg.transition-colors', {
197
+ onclick: function() { m.route.set('/'); }
198
+ }, 'Back to Dashboard'),
199
+ m('button.inline-flex.items-center.gap-1.5.px-4.py-2.text-xs.font-medium.text-white.bg-red-600.hover:bg-red-700.rounded-lg.shadow-sm.transition-colors', {
200
+ onclick: function() { window.location.reload(); }
201
+ }, [
202
+ m('svg.w-3.5.h-3.5', { fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' },
203
+ m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15' })
204
+ ),
205
+ 'Reload Page'
206
+ ])
207
+ ])
208
+ ])
209
+ ]);
210
+
211
+ if (isFullscreen || typeof Layout === 'undefined') {
212
+ return m('div.min-h-screen.bg-slate-950.p-6.flex.items-center.justify-center', errorCard);
213
+ }
214
+
215
+ var breadcrumbs = [{ label: pageTitle, href: (page && page.path) || '/' }];
216
+ return m(Layout, [
217
+ typeof Breadcrumb !== 'undefined' ? m(Breadcrumb, { items: breadcrumbs }) : null,
218
+ errorCard
219
+ ]);
220
+ }
221
+
222
+ // Admin UI Component Library for Custom Pages & Widgets
223
+ var AdminUI = {
224
+ StatCard: {
225
+ view: function(vnode) {
226
+ var attrs = vnode.attrs || {};
227
+ var color = attrs.color || 'blue';
228
+ var colorMap = {
229
+ blue: { bg: 'bg-blue-50 dark:bg-blue-950/60', text: 'text-blue-600 dark:text-blue-400' },
230
+ green: { bg: 'bg-emerald-50 dark:bg-emerald-950/60', text: 'text-emerald-600 dark:text-emerald-400' },
231
+ emerald: { bg: 'bg-emerald-50 dark:bg-emerald-950/60', text: 'text-emerald-600 dark:text-emerald-400' },
232
+ indigo: { bg: 'bg-indigo-50 dark:bg-indigo-950/60', text: 'text-indigo-600 dark:text-indigo-400' },
233
+ purple: { bg: 'bg-purple-50 dark:bg-purple-950/60', text: 'text-purple-600 dark:text-purple-400' },
234
+ amber: { bg: 'bg-amber-50 dark:bg-amber-950/60', text: 'text-amber-600 dark:text-amber-400' },
235
+ red: { bg: 'bg-red-50 dark:bg-red-950/60', text: 'text-red-600 dark:text-red-400' },
236
+ gray: { bg: 'bg-gray-50 dark:bg-slate-800', text: 'text-gray-600 dark:text-slate-400' },
237
+ };
238
+ var theme = colorMap[color] || colorMap.blue;
239
+ var trend = attrs.trend || (attrs.change && String(attrs.change).startsWith('+') ? 'up' : attrs.change && String(attrs.change).startsWith('-') ? 'down' : 'neutral');
240
+
241
+ return m('div.bg-white.dark:bg-slate-800.rounded-xl.p-5.shadow-sm.border.border-gray-200.dark:border-slate-700.flex.flex-col.justify-between.gap-3', [
242
+ m('div.flex.items-center.justify-between.gap-2', [
243
+ m('span.text-xs.font-medium.text-gray-500.dark:text-slate-400.truncate', attrs.title || ''),
244
+ attrs.icon ? m('div.w-8.h-8.rounded-lg.flex.items-center.justify-center', { class: theme.bg + ' ' + theme.text }, attrs.icon) : null
245
+ ]),
246
+ m('div.flex.items-baseline.justify-between.gap-2', [
247
+ m('div.text-2xl.font-bold.text-gray-900.dark:text-slate-100', attrs.value !== undefined ? String(attrs.value) : '0'),
248
+ attrs.change ? m('span.inline-flex.items-center.gap-0.5.text-xs.font-semibold.px-2.py-0.5.rounded-full', {
249
+ class: trend === 'up'
250
+ ? 'bg-emerald-100 text-emerald-800 dark:bg-emerald-950/60 dark:text-emerald-300'
251
+ : trend === 'down'
252
+ ? 'bg-red-100 text-red-800 dark:bg-red-950/60 dark:text-red-300'
253
+ : 'bg-gray-100 text-gray-800 dark:bg-slate-700 dark:text-slate-300'
254
+ }, [
255
+ trend === 'up' ? '↑ ' : trend === 'down' ? '↓ ' : '',
256
+ attrs.change
257
+ ]) : null
258
+ ]),
259
+ attrs.subtitle ? m('p.text-xs.text-gray-400.dark:text-slate-500', attrs.subtitle) : null
260
+ ]);
261
+ }
262
+ },
263
+
264
+ Card: {
265
+ view: function(vnode) {
266
+ var attrs = vnode.attrs || {};
267
+ var hasHeader = attrs.title || attrs.subtitle || attrs.actions;
268
+ return m('div.bg-white.dark:bg-slate-800.rounded-xl.shadow-sm.border.border-gray-200.dark:border-slate-700.overflow-hidden', { class: attrs.class }, [
269
+ hasHeader ? m('div.px-6.py-4.border-b.border-gray-100.dark:border-slate-700.flex.items-center.justify-between.gap-4.bg-gray-50/50.dark:bg-slate-800/80', [
270
+ m('div', [
271
+ attrs.title ? m('h3.text-base.font-semibold.text-gray-900.dark:text-slate-100', attrs.title) : null,
272
+ attrs.subtitle ? m('p.text-xs.text-gray-500.dark:text-slate-400.mt-0.5', attrs.subtitle) : null
273
+ ]),
274
+ attrs.actions ? m('div.flex.items-center.gap-2', attrs.actions) : null
275
+ ]) : null,
276
+ m('div.p-6', vnode.children || attrs.children),
277
+ attrs.footer ? m('div.px-6.py-3.bg-gray-50.dark:bg-slate-900/50.border-t.border-gray-100.dark:border-slate-700', attrs.footer) : null
278
+ ]);
279
+ }
280
+ },
281
+
282
+ Button: {
283
+ view: function(vnode) {
284
+ var attrs = vnode.attrs || {};
285
+ var variant = attrs.variant || 'primary';
286
+ var size = attrs.size || 'md';
287
+ var variantClasses = {
288
+ primary: 'bg-blue-600 hover:bg-blue-700 text-white shadow-sm disabled:bg-blue-400',
289
+ secondary: 'bg-gray-100 dark:bg-slate-800 hover:bg-gray-200 dark:hover:bg-slate-700 text-gray-700 dark:text-slate-200',
290
+ danger: 'bg-red-600 hover:bg-red-700 text-white shadow-sm disabled:bg-red-400',
291
+ success: 'bg-emerald-600 hover:bg-emerald-700 text-white shadow-sm disabled:bg-emerald-400',
292
+ outline: 'border border-gray-300 dark:border-slate-600 text-gray-700 dark:text-slate-200 hover:bg-gray-50 dark:hover:bg-slate-700/50',
293
+ };
294
+ var sizeClasses = {
295
+ sm: 'px-2.5 py-1.5 text-xs',
296
+ md: 'px-3.5 py-2 text-sm',
297
+ lg: 'px-4.5 py-2.5 text-base',
298
+ };
299
+ var vCls = variantClasses[variant] || variantClasses.primary;
300
+ var sCls = sizeClasses[size] || sizeClasses.md;
301
+ var baseClasses = 'inline-flex items-center justify-center gap-2 font-medium rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed';
302
+ var fullClass = baseClasses + ' ' + vCls + ' ' + sCls + (attrs.class ? ' ' + attrs.class : '');
303
+
304
+ return m('button', {
305
+ type: attrs.type || 'button',
306
+ disabled: attrs.disabled || attrs.loading,
307
+ onclick: attrs.onclick,
308
+ class: fullClass,
309
+ }, [
310
+ attrs.loading ? m('span.inline-block.animate-spin', '⟳') : (attrs.icon || null),
311
+ vnode.children || attrs.children
312
+ ]);
313
+ }
314
+ },
315
+
316
+ Badge: {
317
+ view: function(vnode) {
318
+ var attrs = vnode.attrs || {};
319
+ var color = attrs.color || 'blue';
320
+ var size = attrs.size || 'md';
321
+ var colorMap = {
322
+ green: 'bg-emerald-100 text-emerald-800 dark:bg-emerald-950/60 dark:text-emerald-300 border-emerald-200 dark:border-emerald-800',
323
+ emerald: 'bg-emerald-100 text-emerald-800 dark:bg-emerald-950/60 dark:text-emerald-300 border-emerald-200 dark:border-emerald-800',
324
+ red: 'bg-red-100 text-red-800 dark:bg-red-950/60 dark:text-red-300 border-red-200 dark:border-red-800',
325
+ blue: 'bg-blue-100 text-blue-800 dark:bg-blue-950/60 dark:text-blue-300 border-blue-200 dark:border-blue-800',
326
+ amber: 'bg-amber-100 text-amber-800 dark:bg-amber-950/60 dark:text-amber-300 border-amber-200 dark:border-amber-800',
327
+ yellow: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-950/60 dark:text-yellow-300 border-yellow-200 dark:border-yellow-800',
328
+ purple: 'bg-purple-100 text-purple-800 dark:bg-purple-950/60 dark:text-purple-300 border-purple-200 dark:border-purple-800',
329
+ indigo: 'bg-indigo-100 text-indigo-800 dark:bg-indigo-950/60 dark:text-indigo-300 border-indigo-200 dark:border-indigo-800',
330
+ gray: 'bg-gray-100 text-gray-800 dark:bg-slate-700 dark:text-slate-300 border-gray-200 dark:border-slate-600',
331
+ };
332
+ var cCls = colorMap[color] || colorMap.blue;
333
+ var sCls = size === 'sm' ? 'text-[11px] px-2 py-0.5' : 'text-xs px-2.5 py-1';
334
+ var fullClass = 'inline-flex items-center gap-1.5 font-semibold rounded-full border ' + cCls + ' ' + sCls + (attrs.class ? ' ' + attrs.class : '');
335
+
336
+ return m('span', { class: fullClass }, [
337
+ attrs.dot ? m('span.w-1.5.h-1.5.rounded-full.bg-current') : null,
338
+ attrs.text || vnode.children || attrs.children
339
+ ]);
340
+ }
341
+ },
342
+
343
+ Modal: {
344
+ view: function(vnode) {
345
+ var attrs = vnode.attrs || {};
346
+ if (!attrs.isOpen) return null;
347
+ var size = attrs.size || 'md';
348
+ var sizeMap = {
349
+ sm: 'max-w-md',
350
+ md: 'max-w-lg',
351
+ lg: 'max-w-2xl',
352
+ xl: 'max-w-4xl',
353
+ };
354
+ var sCls = sizeMap[size] || sizeMap.md;
355
+
356
+ return m('div.fixed.inset-0.z-50.flex.items-center.justify-center.p-4.overflow-y-auto', [
357
+ m('div.fixed.inset-0.bg-black/50.backdrop-blur-sm.transition-opacity', {
358
+ onclick: function() { if (attrs.onClose) attrs.onClose(); }
359
+ }),
360
+ m('div.relative.bg-white.dark:bg-slate-900.rounded-xl.shadow-2xl.border.border-gray-200.dark:border-slate-700.w-full.overflow-hidden.z-10.transform.transition-all', {
361
+ class: sCls
362
+ }, [
363
+ m('div.px-6.py-4.border-b.border-gray-100.dark:border-slate-800.flex.items-center.justify-between', [
364
+ m('h3.text-lg.font-bold.text-gray-900.dark:text-slate-100', attrs.title || 'Modal'),
365
+ m('button.text-gray-400.hover:text-gray-600.dark:hover:text-slate-300.rounded-lg.p-1', {
366
+ onclick: function() { if (attrs.onClose) attrs.onClose(); }
367
+ }, '✕')
368
+ ]),
369
+ m('div.p-6', vnode.children || attrs.children),
370
+ (attrs.onConfirm || attrs.onClose) ? m('div.px-6.py-3.bg-gray-50.dark:bg-slate-800/50.border-t.border-gray-100.dark:border-slate-800.flex.items-center.justify-end.gap-3', [
371
+ attrs.onClose ? m('button.px-4.py-2.text-xs.font-medium.text-gray-700.dark:text-slate-300.bg-gray-100.dark:bg-slate-800.hover:bg-gray-200.dark:hover:bg-slate-700.rounded-lg', {
372
+ onclick: function() { attrs.onClose(); }
373
+ }, attrs.cancelText || 'Cancel') : null,
374
+ attrs.onConfirm ? m('button.px-4.py-2.text-xs.font-medium.text-white.rounded-lg.shadow-sm', {
375
+ class: attrs.confirmVariant === 'danger' ? 'bg-red-600 hover:bg-red-700' : 'bg-blue-600 hover:bg-blue-700',
376
+ onclick: function() { attrs.onConfirm(); }
377
+ }, attrs.confirmText || 'Confirm') : null,
378
+ ]) : null
379
+ ])
380
+ ]);
381
+ }
382
+ },
383
+
384
+ Tabs: {
385
+ view: function(vnode) {
386
+ var attrs = vnode.attrs || {};
387
+ var tabs = attrs.tabs || [];
388
+ var active = attrs.active;
389
+ var onChange = attrs.onChange || function() {};
390
+
391
+ return m('div.border-b.border-gray-200.dark:border-slate-700.mb-6', [
392
+ m('nav.flex.space-x-8', tabs.map(function(tab) {
393
+ var isActive = tab.id === active;
394
+ return m('button.flex.items-center.gap-2.py-3.px-1.border-b-2.text-sm.font-medium.transition-colors', {
395
+ class: isActive
396
+ ? 'border-blue-600 text-blue-600 dark:border-blue-400 dark:text-blue-400'
397
+ : 'border-transparent text-gray-500 dark:text-slate-400 hover:text-gray-700 dark:hover:text-slate-200 hover:border-gray-300',
398
+ onclick: function(e) {
399
+ e.preventDefault();
400
+ onChange(tab.id);
401
+ }
402
+ }, [
403
+ tab.icon ? (typeof tab.icon === 'function' ? tab.icon() : tab.icon) : null,
404
+ m('span', tab.label || tab.title || tab.id),
405
+ tab.badge !== undefined ? m('span.ml-1.px-2.py-0.5.text-xs.rounded-full', {
406
+ class: isActive ? 'bg-blue-100 text-blue-800 dark:bg-blue-950 dark:text-blue-300' : 'bg-gray-100 text-gray-700 dark:bg-slate-700 dark:text-slate-300'
407
+ }, String(tab.badge)) : null
408
+ ]);
409
+ }))
410
+ ]);
411
+ }
412
+ }
413
+ };
414
+
415
+ // Toast Notification State & Handlers
416
+ var toastState = {
417
+ items: [],
418
+ add: function(type, message, options) {
419
+ options = options || {};
420
+ if (typeof type === 'object') {
421
+ options = type;
422
+ type = options.type || 'info';
423
+ message = options.message || '';
424
+ }
425
+ var id = 'toast_' + Math.random().toString(36).substring(2, 9);
426
+ var item = {
427
+ id: id,
428
+ type: type || 'info',
429
+ message: message,
430
+ title: options.title || '',
431
+ duration: options.duration !== undefined ? options.duration : 4000
432
+ };
433
+ toastState.items.push(item);
434
+ if (item.duration > 0) {
435
+ setTimeout(function() {
436
+ toastState.remove(id);
437
+ }, item.duration);
438
+ }
439
+ try {
440
+ if (typeof m !== 'undefined' && typeof m.redraw === 'function') {
441
+ m.redraw();
442
+ }
443
+ } catch (e) {}
444
+ return id;
445
+ },
446
+ remove: function(id) {
447
+ toastState.items = toastState.items.filter(function(t) { return t.id !== id; });
448
+ try {
449
+ if (typeof m !== 'undefined' && typeof m.redraw === 'function') {
450
+ m.redraw();
451
+ }
452
+ } catch (e) {}
453
+ },
454
+ clear: function() {
455
+ toastState.items = [];
456
+ try {
457
+ if (typeof m !== 'undefined' && typeof m.redraw === 'function') {
458
+ m.redraw();
459
+ }
460
+ } catch (e) {}
461
+ }
462
+ };
463
+
464
+ function normalizeToastOptions(options) {
465
+ if (typeof options === 'number') {
466
+ return { duration: options };
467
+ }
468
+ return options || {};
469
+ }
470
+
471
+ var AdminToast = function(message, type, options) {
472
+ var opts = normalizeToastOptions(options);
473
+ if (typeof type === 'object') {
474
+ opts = type;
475
+ type = opts.type || 'info';
476
+ }
477
+ return toastState.add(type || 'info', message, opts);
478
+ };
479
+ AdminToast.success = function(message, options) { return toastState.add('success', message, normalizeToastOptions(options)); };
480
+ AdminToast.error = function(message, options) { return toastState.add('error', message, normalizeToastOptions(options)); };
481
+ AdminToast.warning = function(message, options) { return toastState.add('warning', message, normalizeToastOptions(options)); };
482
+ AdminToast.info = function(message, options) { return toastState.add('info', message, normalizeToastOptions(options)); };
483
+ AdminToast.remove = function(id) { return toastState.remove(id); };
484
+ AdminToast.clear = function() { return toastState.clear(); };
485
+
486
+ var ToastContainer = {
487
+ view: function() {
488
+ if (!toastState.items || toastState.items.length === 0) return null;
489
+ return m('div.fixed.top-4.right-4.z-50.flex.flex-col.gap-2.5.max-w-sm.w-full.pointer-events-none.px-4.sm:px-0',
490
+ toastState.items.map(function(t) {
491
+ var typeConfig = {
492
+ success: {
493
+ border: 'border-emerald-200 dark:border-emerald-800/80',
494
+ bg: 'bg-white dark:bg-slate-900',
495
+ iconColor: 'text-emerald-600 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-950/60',
496
+ symbol: '✓',
497
+ },
498
+ error: {
499
+ border: 'border-red-200 dark:border-red-800/80',
500
+ bg: 'bg-white dark:bg-slate-900',
501
+ iconColor: 'text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-950/60',
502
+ symbol: '!',
503
+ },
504
+ warning: {
505
+ border: 'border-amber-200 dark:border-amber-800/80',
506
+ bg: 'bg-white dark:bg-slate-900',
507
+ iconColor: 'text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-950/60',
508
+ symbol: '⚠',
509
+ },
510
+ info: {
511
+ border: 'border-blue-200 dark:border-blue-800/80',
512
+ bg: 'bg-white dark:bg-slate-900',
513
+ iconColor: 'text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-950/60',
514
+ symbol: 'ℹ',
515
+ },
516
+ };
517
+ var cfg = typeConfig[t.type] || typeConfig.info;
518
+
519
+ return m('div.pointer-events-auto.shadow-xl.rounded-xl.border.p-4.flex.items-start.gap-3.transition-all.transform', {
520
+ key: t.id,
521
+ class: cfg.border + ' ' + cfg.bg
522
+ }, [
523
+ m('div.w-7.h-7.rounded-lg.flex.items-center.justify-center.text-xs.font-bold.flex-shrink-0', {
524
+ class: cfg.iconColor
525
+ }, cfg.symbol),
526
+ m('div.flex-1.min-w-0', [
527
+ t.title ? m('h4.text-sm.font-semibold.text-gray-900.dark:text-slate-100', t.title) : null,
528
+ m('p.text-xs.text-gray-600.dark:text-slate-300.break-words', t.message)
529
+ ]),
530
+ m('button.text-gray-400.hover:text-gray-600.dark:hover:text-slate-200.p-1.text-xs', {
531
+ onclick: function() { toastState.remove(t.id); }
532
+ }, '✕')
533
+ ]);
534
+ })
535
+ );
536
+ }
537
+ };
538
+
539
+ // Confirm Modal State & Handlers
540
+ var confirmState = {
541
+ current: null,
542
+ open: function(options) {
543
+ options = options || {};
544
+ if (typeof options === 'string') {
545
+ options = { message: options };
546
+ }
547
+ return new Promise(function(resolve) {
548
+ confirmState.current = {
549
+ title: options.title || 'Confirmation',
550
+ message: options.message || 'Are you sure you want to proceed?',
551
+ confirmText: options.confirmText || 'Confirm',
552
+ cancelText: options.cancelText || 'Cancel',
553
+ confirmColor: options.confirmColor || (options.danger ? 'red' : 'blue'),
554
+ danger: options.danger || false,
555
+ resolve: resolve
556
+ };
557
+ try {
558
+ if (typeof m !== 'undefined' && typeof m.redraw === 'function') {
559
+ m.redraw();
560
+ }
561
+ } catch (e) {}
562
+ });
563
+ },
564
+ close: function(result) {
565
+ if (confirmState.current && confirmState.current.resolve) {
566
+ confirmState.current.resolve(Boolean(result));
567
+ }
568
+ confirmState.current = null;
569
+ try {
570
+ if (typeof m !== 'undefined' && typeof m.redraw === 'function') {
571
+ m.redraw();
572
+ }
573
+ } catch (e) {}
574
+ }
575
+ };
576
+
577
+ var AdminConfirm = function(options) {
578
+ return confirmState.open(options);
579
+ };
580
+
581
+ var ConfirmModalContainer = {
582
+ view: function() {
583
+ if (!confirmState.current) return null;
584
+ var cur = confirmState.current;
585
+ var isDanger = cur.danger || cur.confirmColor === 'red';
586
+
587
+ return m('div.fixed.inset-0.z-50.flex.items-center.justify-center.p-4', [
588
+ m('div.fixed.inset-0.bg-black/50.backdrop-blur-sm', {
589
+ onclick: function() { confirmState.close(false); }
590
+ }),
591
+ m('div.relative.bg-white.dark:bg-slate-900.rounded-xl.shadow-2xl.border.border-gray-200.dark:border-slate-700.max-w-md.w-full.p-6.z-10', [
592
+ m('div.flex.items-start.gap-4', [
593
+ m('div.w-10.h-10.rounded-full.flex.items-center.justify-center.flex-shrink-0', {
594
+ class: isDanger ? 'bg-red-100 dark:bg-red-950/60 text-red-600 dark:text-red-400' : 'bg-blue-100 dark:bg-blue-950/60 text-blue-600 dark:text-blue-400'
595
+ }, isDanger ? '⚠' : '?'),
596
+ m('div.flex-1', [
597
+ m('h3.text-base.font-bold.text-gray-900.dark:text-slate-100', cur.title),
598
+ m('p.text-xs.text-gray-500.dark:text-slate-400.mt-1.5', cur.message)
599
+ ])
600
+ ]),
601
+ m('div.mt-6.flex.items-center.justify-end.gap-3', [
602
+ m('button.px-4.py-2.text-xs.font-medium.text-gray-700.dark:text-slate-300.bg-gray-100.dark:bg-slate-800.hover:bg-gray-200.dark:hover:bg-slate-700.rounded-lg', {
603
+ onclick: function() { confirmState.close(false); }
604
+ }, cur.cancelText),
605
+ m('button.px-4.py-2.text-xs.font-medium.text-white.rounded-lg.shadow-sm', {
606
+ class: isDanger ? 'bg-red-600 hover:bg-red-700' : 'bg-blue-600 hover:bg-blue-700',
607
+ onclick: function() { confirmState.close(true); }
608
+ }, cur.confirmText)
609
+ ])
610
+ ])
611
+ ]);
612
+ }
613
+ };
614
+
615
+ // Global Exposure
616
+ if (typeof window !== 'undefined') {
617
+ window.AdminUI = AdminUI;
618
+ window.Admin = window.Admin || {};
619
+ window.Admin.ui = AdminUI;
620
+ window.Admin.toast = AdminToast;
621
+ window.Admin.confirm = AdminConfirm;
622
+ window.notify = AdminToast;
623
+ window.toast = AdminToast;
624
+ window.confirmModal = AdminConfirm;
625
+ window.toastState = toastState;
626
+ window.confirmState = confirmState;
627
+ window.ToastContainer = ToastContainer;
628
+ window.ConfirmModalContainer = ConfirmModalContainer;
629
+ }
630
+
157
631
  // Custom Page Component Factory
158
632
  function createCustomPage(pageConfig) {
159
633
  if (pageConfig && (pageConfig.url || (pageConfig.html && pageConfig.iframe))) {
@@ -167,7 +641,11 @@ function createCustomPage(pageConfig) {
167
641
  if (pageConfig.layout === false) {
168
642
  return {
169
643
  view: function() {
170
- return m('iframe', iframeAttrs);
644
+ return m('div.relative.w-full.h-full', [
645
+ m('iframe', iframeAttrs),
646
+ m(ToastContainer),
647
+ m(ConfirmModalContainer),
648
+ ]);
171
649
  }
172
650
  };
173
651
  }
@@ -206,7 +684,11 @@ function createCustomPage(pageConfig) {
206
684
  return {
207
685
  oncreate: executeScripts,
208
686
  view: function() {
209
- return m('div.custom-html-container', m.trust(pageConfig.html));
687
+ return m('div.custom-html-container', [
688
+ m.trust(pageConfig.html),
689
+ m(ToastContainer),
690
+ m(ConfirmModalContainer),
691
+ ]);
210
692
  }
211
693
  };
212
694
  }
@@ -300,7 +782,14 @@ function createCustomPage(pageConfig) {
300
782
  : error
301
783
  ? m('div.bg-red-50.border.border-red-200.rounded.p-4.text-red-700', error)
302
784
  : pageConfig.render
303
- ? pageConfig.render(data, vnode)
785
+ ? (function() {
786
+ try {
787
+ return pageConfig.render(data, vnode);
788
+ } catch (renderErr) {
789
+ console.error('[Admin Panel] Error rendering page "' + pageConfig.id + '":', renderErr);
790
+ return renderCustomPageError(pageConfig, renderErr);
791
+ }
792
+ })()
304
793
  : m('div.bg-white dark:bg-slate-800.rounded-lg.shadow.p-6', [
305
794
  data
306
795
  ? m('pre.text-sm.overflow-auto', JSON.stringify(data, null, 2))