webspresso 0.0.99 → 0.0.101

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 (108) hide show
  1. package/.agents/ADMIN.md +47 -4
  2. package/.agents/AGENTS.md +4 -2
  3. package/.agents/AUTH.md +309 -0
  4. package/.agents/CLI.md +128 -0
  5. package/.agents/EMAIL.md +84 -2
  6. package/.agents/ERRORS.md +59 -0
  7. package/.agents/EVENTS.md +179 -0
  8. package/.agents/ORM.md +149 -0
  9. package/.agents/PLUGINS.md +44 -7
  10. package/.agents/QUEUE.md +18 -10
  11. package/.agents/ROUTING.md +97 -1
  12. package/.agents/STREAMING.md +35 -0
  13. package/AGENTS.md +2 -0
  14. package/README.md +145 -93
  15. package/bin/commands/add-docker.js +467 -0
  16. package/bin/commands/db-makemigrations.js +83 -0
  17. package/bin/commands/deploy-check.js +339 -0
  18. package/bin/commands/list.js +96 -0
  19. package/bin/commands/make-command.js +93 -0
  20. package/bin/commands/make-event.js +80 -0
  21. package/bin/commands/make-factory.js +72 -0
  22. package/bin/commands/make-mail.js +93 -0
  23. package/bin/commands/make-policy.js +106 -0
  24. package/bin/commands/run.js +195 -0
  25. package/bin/webspresso.js +21 -0
  26. package/core/auth/bouncer.js +196 -0
  27. package/core/auth/index.js +73 -1
  28. package/core/auth/magic-link.js +170 -0
  29. package/core/auth/manager.js +343 -3
  30. package/core/auth/middleware.js +45 -6
  31. package/core/auth/session-manager.js +255 -0
  32. package/core/auth/sessions.js +351 -0
  33. package/core/auth/tokens.js +1 -0
  34. package/core/auth/totp.js +4 -1
  35. package/core/auth/user-agent.js +161 -0
  36. package/core/commands/define-command.js +48 -0
  37. package/core/commands/index.js +18 -0
  38. package/core/commands/registry.js +156 -0
  39. package/core/commands/runner.js +76 -0
  40. package/core/compression/compressible.js +1 -1
  41. package/core/compression/middleware.js +35 -251
  42. package/core/content/service.js +8 -2
  43. package/core/events/discovery.js +142 -0
  44. package/core/events/dispatcher.js +552 -0
  45. package/core/events/index.js +23 -0
  46. package/core/events/signal.js +92 -0
  47. package/core/forms/flash.js +234 -0
  48. package/core/forms/form-builder.js +214 -0
  49. package/core/forms/index.js +16 -0
  50. package/core/mail/index.js +23 -0
  51. package/core/mail/mailable.js +250 -0
  52. package/core/mail/mailer.js +184 -0
  53. package/core/mail/transports.js +238 -0
  54. package/core/orm/factory.js +250 -0
  55. package/core/orm/index.js +8 -1
  56. package/core/orm/migrations/diff.js +185 -0
  57. package/core/orm/migrations/index.js +22 -4
  58. package/core/orm/model.js +55 -1
  59. package/core/orm/repository.js +283 -13
  60. package/core/orm/scopes.js +3 -2
  61. package/core/orm/types.js +6 -1
  62. package/core/queue/cron.js +252 -39
  63. package/core/queue/manager.js +20 -2
  64. package/core/queue/scheduler.js +115 -59
  65. package/core/ssr/stream.js +62 -2
  66. package/index.d.ts +540 -14
  67. package/index.js +44 -0
  68. package/package.json +11 -5
  69. package/plugins/admin-panel/api.js +509 -3
  70. package/plugins/admin-panel/index.js +17 -0
  71. package/plugins/admin-panel/modules/admin-users.js +1 -1
  72. package/plugins/audit-log/admin-component.js +54 -54
  73. package/plugins/audit-log/index.js +2 -2
  74. package/plugins/content/client/inline-edit.js +2 -46
  75. package/plugins/content/models/content-entry.js +4 -3
  76. package/plugins/content/models/content-type.js +4 -3
  77. package/plugins/cors.js +15 -4
  78. package/plugins/email/auth-bridge.js +66 -0
  79. package/plugins/email/bundled-templates.js +15 -0
  80. package/plugins/email/index.js +48 -0
  81. package/plugins/email/preview-handler.js +555 -0
  82. package/plugins/email/service.js +23 -12
  83. package/plugins/email/templates/auth-magic-link.mjml +13 -0
  84. package/plugins/email/transport.js +30 -11
  85. package/plugins/file-manager/admin-component.js +49 -49
  86. package/plugins/file-manager/index.js +4 -4
  87. package/plugins/index.js +2 -0
  88. package/plugins/redirect/index.js +1 -1
  89. package/plugins/webhooks/admin-component.js +608 -0
  90. package/plugins/webhooks/api-handlers.js +237 -0
  91. package/plugins/webhooks/crypto.js +144 -0
  92. package/plugins/webhooks/dispatcher.js +708 -0
  93. package/plugins/webhooks/index.js +263 -0
  94. package/plugins/webhooks/matcher.js +113 -0
  95. package/plugins/webhooks/migration-template.js +75 -0
  96. package/plugins/webhooks/models.js +98 -0
  97. package/plugins/webhooks/sender.js +176 -0
  98. package/src/api/api-loader.js +1 -1
  99. package/src/app-context.js +48 -6
  100. package/src/bundle/index.js +2 -1
  101. package/src/dev-reload/index.js +9 -1
  102. package/src/discovery/scan-routes.js +10 -1
  103. package/src/file-router.js +173 -4
  104. package/src/helpers.js +150 -1
  105. package/src/njk-frontmatter.js +3 -0
  106. package/src/pages/page-loader.js +1 -1
  107. package/src/routing/cascading.js +120 -4
  108. package/src/server.js +180 -26
@@ -0,0 +1,608 @@
1
+ /**
2
+ * Webhooks Admin Panel Component — Mithril SPA Component Generator
3
+ * Features endpoints management, real-time ping testing, delivery log inspector, and retry
4
+ * @module plugins/webhooks/admin-component
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ /**
10
+ * Generate client-side Mithril SPA component script string for Webhooks
11
+ *
12
+ * @param {Object} [options]
13
+ * @param {string} [options.apiPrefix='/webhooks']
14
+ * @returns {string}
15
+ */
16
+ function generateWebhooksComponent(options = {}) {
17
+ const apiPrefix = options.apiPrefix || '/webhooks';
18
+
19
+ return `
20
+ (function() {
21
+ var API = '${apiPrefix}';
22
+
23
+ function formatRelativeTime(dateStr) {
24
+ if (!dateStr) return '—';
25
+ try {
26
+ var d = new Date(dateStr);
27
+ var now = new Date();
28
+ var diffMs = now - d;
29
+ var diffSec = Math.floor(diffMs / 1000);
30
+ var diffMin = Math.floor(diffSec / 60);
31
+ var diffHour = Math.floor(diffMin / 60);
32
+ var diffDay = Math.floor(diffHour / 24);
33
+
34
+ if (diffSec < 60) return 'Just now';
35
+ if (diffMin < 60) return diffMin + 'm ago';
36
+ if (diffHour < 24) return diffHour + 'h ago';
37
+ if (diffDay === 1) return 'Yesterday';
38
+ if (diffDay < 30) return diffDay + 'd ago';
39
+ return d.toLocaleDateString();
40
+ } catch (e) {
41
+ return dateStr;
42
+ }
43
+ }
44
+
45
+ function parseEvents(events) {
46
+ if (!events) return ['*'];
47
+ if (Array.isArray(events)) return events;
48
+ if (typeof events === 'string') {
49
+ try {
50
+ var parsed = JSON.parse(events);
51
+ if (Array.isArray(parsed)) return parsed;
52
+ } catch (e) {}
53
+ return events.split(',').map(function(s) { return s.trim(); }).filter(Boolean);
54
+ }
55
+ return ['*'];
56
+ }
57
+
58
+ function WebhooksPage() {
59
+ var activeTab = 'endpoints'; // 'endpoints' | 'deliveries'
60
+ var loading = false;
61
+ var error = null;
62
+ var successToast = null;
63
+
64
+ // Endpoints state
65
+ var endpoints = [];
66
+ var showEndpointModal = false;
67
+ var editingEndpoint = null;
68
+ var endpointForm = {
69
+ name: '',
70
+ url: '',
71
+ secret: '',
72
+ events: '*',
73
+ is_active: true,
74
+ description: '',
75
+ };
76
+
77
+ // Deliveries state
78
+ var deliveries = [];
79
+ var deliveryTotal = 0;
80
+ var deliveryPage = 1;
81
+ var deliveryLimit = 25;
82
+ var deliveryPages = 1;
83
+ var filterEvent = '';
84
+ var filterSuccess = '';
85
+ var activeDeliveryModal = null;
86
+
87
+ function setToast(msg) {
88
+ successToast = msg;
89
+ setTimeout(function() {
90
+ successToast = null;
91
+ m.redraw();
92
+ }, 3500);
93
+ }
94
+
95
+ function generateRandomSecret() {
96
+ var chars = '0123456789abcdef';
97
+ var res = 'whsec_';
98
+ for (var i = 0; i < 48; i++) {
99
+ res += chars[Math.floor(Math.random() * chars.length)];
100
+ }
101
+ return res;
102
+ }
103
+
104
+ function loadEndpoints() {
105
+ loading = true;
106
+ error = null;
107
+ return m.request({
108
+ method: 'GET',
109
+ url: API + '/endpoints',
110
+ }).then(function(res) {
111
+ endpoints = res.items || [];
112
+ loading = false;
113
+ m.redraw();
114
+ }).catch(function(err) {
115
+ loading = false;
116
+ error = (err.response && err.response.error) || err.message || 'Failed to load endpoints.';
117
+ m.redraw();
118
+ });
119
+ }
120
+
121
+ function loadDeliveries() {
122
+ loading = true;
123
+ error = null;
124
+ var q = '?page=' + deliveryPage + '&limit=' + deliveryLimit;
125
+ if (filterEvent) q += '&event=' + encodeURIComponent(filterEvent);
126
+ if (filterSuccess !== '') q += '&success=' + encodeURIComponent(filterSuccess);
127
+
128
+ return m.request({
129
+ method: 'GET',
130
+ url: API + '/deliveries' + q,
131
+ }).then(function(res) {
132
+ deliveries = res.items || [];
133
+ deliveryTotal = res.total || 0;
134
+ deliveryPage = res.page || 1;
135
+ deliveryPages = res.pages || 1;
136
+ loading = false;
137
+ m.redraw();
138
+ }).catch(function(err) {
139
+ loading = false;
140
+ error = (err.response && err.response.error) || err.message || 'Failed to load delivery records.';
141
+ m.redraw();
142
+ });
143
+ }
144
+
145
+ function saveEndpoint() {
146
+ var isEdit = !!editingEndpoint;
147
+ var method = isEdit ? 'PUT' : 'POST';
148
+ var url = isEdit ? API + '/endpoints/' + editingEndpoint.id : API + '/endpoints';
149
+
150
+ var payload = {
151
+ name: endpointForm.name,
152
+ url: endpointForm.url,
153
+ secret: endpointForm.secret || generateRandomSecret(),
154
+ events: endpointForm.events,
155
+ is_active: endpointForm.is_active,
156
+ description: endpointForm.description,
157
+ };
158
+
159
+ return m.request({
160
+ method: method,
161
+ url: url,
162
+ body: payload,
163
+ }).then(function() {
164
+ showEndpointModal = false;
165
+ editingEndpoint = null;
166
+ setToast(isEdit ? 'Endpoint updated.' : 'New endpoint created.');
167
+ loadEndpoints();
168
+ }).catch(function(err) {
169
+ alert((err.response && err.response.error) || err.message || 'Save failed.');
170
+ });
171
+ }
172
+
173
+ function deleteEndpoint(ep) {
174
+ if (!confirm('Are you sure you want to delete endpoint "' + ep.name + '"?')) {
175
+ return;
176
+ }
177
+ m.request({
178
+ method: 'DELETE',
179
+ url: API + '/endpoints/' + ep.id,
180
+ }).then(function() {
181
+ setToast('Endpoint deleted.');
182
+ loadEndpoints();
183
+ }).catch(function(err) {
184
+ alert((err.response && err.response.error) || err.message || 'Delete failed.');
185
+ });
186
+ }
187
+
188
+ function pingEndpoint(ep) {
189
+ setToast('Sending ping to "' + ep.name + '"...');
190
+ m.request({
191
+ method: 'POST',
192
+ url: API + '/endpoints/' + ep.id + '/ping',
193
+ }).then(function(res) {
194
+ if (res.success) {
195
+ setToast('Ping successful! Response code: ' + res.statusCode + ' (' + (res.durationMs || 0) + 'ms)');
196
+ } else {
197
+ alert('Ping failed: ' + (res.error || ('HTTP ' + res.statusCode)));
198
+ }
199
+ if (activeTab === 'deliveries') loadDeliveries();
200
+ }).catch(function(err) {
201
+ alert('Failed to send ping request: ' + (err.message || 'Network error'));
202
+ });
203
+ }
204
+
205
+ function retryDelivery(dlv) {
206
+ setToast('Retrying delivery #' + dlv.id + '...');
207
+ m.request({
208
+ method: 'POST',
209
+ url: API + '/deliveries/' + dlv.id + '/retry',
210
+ }).then(function(res) {
211
+ if (res.success) {
212
+ setToast('Retry successful! (' + res.statusCode + ')');
213
+ } else {
214
+ alert('Retry failed: ' + (res.error || ('HTTP ' + res.statusCode)));
215
+ }
216
+ loadDeliveries();
217
+ }).catch(function(err) {
218
+ alert('Failed to send retry request: ' + err.message);
219
+ });
220
+ }
221
+
222
+ function openNewModal() {
223
+ editingEndpoint = null;
224
+ endpointForm = {
225
+ name: '',
226
+ url: '',
227
+ secret: generateRandomSecret(),
228
+ events: '*',
229
+ is_active: true,
230
+ description: '',
231
+ };
232
+ showEndpointModal = true;
233
+ }
234
+
235
+ function openEditModal(ep) {
236
+ editingEndpoint = ep;
237
+ var evts = parseEvents(ep.events).join(', ');
238
+ endpointForm = {
239
+ name: ep.name || '',
240
+ url: ep.url || '',
241
+ secret: ep.secret || '',
242
+ events: evts,
243
+ is_active: ep.is_active !== false,
244
+ description: ep.description || '',
245
+ };
246
+ showEndpointModal = true;
247
+ }
248
+
249
+ return {
250
+ oninit: function() {
251
+ loadEndpoints();
252
+ },
253
+
254
+ view: function() {
255
+ var totalEndpoints = endpoints.length;
256
+ var activeEndpoints = endpoints.filter(function(e) { return e.is_active; }).length;
257
+
258
+ return m('div.space-y-6', [
259
+ // Toast Notification
260
+ successToast ? m('div.fixed.bottom-5.right-5.z-50.px-4.py-2.5.rounded-xl.bg-emerald-600.text-white.text-sm.font-medium.shadow-lg.flex.items-center.gap-2.animate-fade-in', [
261
+ m('span', '✓'),
262
+ m('span', successToast),
263
+ ]) : null,
264
+
265
+ // Page Header
266
+ m('div.flex.flex-col.sm:flex-row.sm:items-center.sm:justify-between.gap-4.border-b.border-gray-200.dark:border-slate-800.pb-5', [
267
+ m('div', [
268
+ m('h1.text-2xl.font-bold.text-gray-900.dark:text-white', 'Webhooks'),
269
+ m('p.text-sm.text-gray-500.dark:text-slate-400.mt-1',
270
+ 'Secure HMAC-SHA256 event notifications and delivery management for external systems'
271
+ ),
272
+ ]),
273
+ m('div.flex.items-center.gap-3', [
274
+ m('button.inline-flex.items-center.gap-2.px-4.py-2.text-sm.font-semibold.rounded-xl.bg-indigo-600.text-white.hover:bg-indigo-500.shadow-sm.transition-all', {
275
+ onclick: openNewModal,
276
+ }, [
277
+ m('span', '+'),
278
+ m('span', 'Add New Endpoint'),
279
+ ]),
280
+ ]),
281
+ ]),
282
+
283
+ // Stats Bar
284
+ m('div.grid.grid-cols-2.sm:grid-cols-4.gap-4', [
285
+ m('div.p-4.rounded-2xl.bg-white.dark:bg-slate-800.border.border-gray-200.dark:border-slate-700.shadow-sm', [
286
+ m('div.text-xs.font-medium.text-gray-500.dark:text-slate-400', 'Registered Endpoints'),
287
+ m('div.text-2xl.font-bold.text-gray-900.dark:text-white.mt-1', totalEndpoints),
288
+ ]),
289
+ m('div.p-4.rounded-2xl.bg-white.dark:bg-slate-800.border.border-gray-200.dark:border-slate-700.shadow-sm', [
290
+ m('div.text-xs.font-medium.text-gray-500.dark:text-slate-400', 'Active Endpoints'),
291
+ m('div.text-2xl.font-bold.text-emerald-600.dark:text-emerald-400.mt-1', activeEndpoints),
292
+ ]),
293
+ m('div.p-4.rounded-2xl.bg-white.dark:bg-slate-800.border.border-gray-200.dark:border-slate-700.shadow-sm', [
294
+ m('div.text-xs.font-medium.text-gray-500.dark:text-slate-400', 'Deliveries Count'),
295
+ m('div.text-2xl.font-bold.text-indigo-600.dark:text-indigo-400.mt-1', deliveryTotal || '—'),
296
+ ]),
297
+ m('div.p-4.rounded-2xl.bg-white.dark:bg-slate-800.border.border-gray-200.dark:border-slate-700.shadow-sm', [
298
+ m('div.text-xs.font-medium.text-gray-500.dark:text-slate-400', 'Signature Standard'),
299
+ m('div.text-sm.font-semibold.text-gray-700.dark:text-slate-200.mt-2.font-mono', 'HMAC SHA-256'),
300
+ ]),
301
+ ]),
302
+
303
+ // Tabs Switcher
304
+ m('div.flex.border-b.border-gray-200.dark:border-slate-700', [
305
+ m('button.px-5.py-2.5.text-sm.font-medium.border-b-2.transition-colors', {
306
+ class: activeTab === 'endpoints'
307
+ ? 'border-indigo-600.text-indigo-600.dark:text-indigo-400.dark:border-indigo-400'
308
+ : 'border-transparent.text-gray-500.hover:text-gray-700.dark:text-slate-400.dark:hover:text-slate-200',
309
+ onclick: function() { activeTab = 'endpoints'; loadEndpoints(); },
310
+ }, 'Endpoints (' + totalEndpoints + ')'),
311
+ m('button.px-5.py-2.5.text-sm.font-medium.border-b-2.transition-colors', {
312
+ class: activeTab === 'deliveries'
313
+ ? 'border-indigo-600.text-indigo-600.dark:text-indigo-400.dark:border-indigo-400'
314
+ : 'border-transparent.text-gray-500.hover:text-gray-700.dark:text-slate-400.dark:hover:text-slate-200',
315
+ onclick: function() { activeTab = 'deliveries'; loadDeliveries(); },
316
+ }, 'Delivery History'),
317
+ ]),
318
+
319
+ // TAB 1: ENDPOINTS LIST
320
+ activeTab === 'endpoints' ? m('div.bg-white.dark:bg-slate-800.border.border-gray-200.dark:border-slate-700.rounded-2xl.overflow-hidden.shadow-sm', [
321
+ endpoints.length === 0 ? m('div.p-12.text-center.text-gray-500.dark:text-slate-400', [
322
+ m('p.text-base.font-medium', 'No webhook endpoints registered yet.'),
323
+ m('p.text-xs.mt-1', 'Add a new endpoint above to forward system events to Zapier, Slack, or your custom server.'),
324
+ ]) : m('div.overflow-x-auto', [
325
+ m('table.w-full.text-left.text-sm', [
326
+ m('thead.bg-gray-50.dark:bg-slate-750.text-xs.uppercase.text-gray-500.dark:text-slate-400.border-b.border-gray-200.dark:border-slate-700', [
327
+ m('tr', [
328
+ m('th.p-4', 'Name & Target URL'),
329
+ m('th.p-4', 'Subscribed Events'),
330
+ m('th.p-4', 'Status'),
331
+ m('th.p-4', 'Success Rate'),
332
+ m('th.p-4.text-right', 'Actions'),
333
+ ]),
334
+ ]),
335
+ m('tbody.divide-y.divide-gray-100.dark:divide-slate-700', endpoints.map(function(ep) {
336
+ var evts = parseEvents(ep.events);
337
+ var stats = ep.stats || { total: 0, successRate: 100 };
338
+
339
+ return m('tr.hover:bg-gray-50/60.dark:hover:bg-slate-750/50.transition-colors', [
340
+ m('td.p-4', [
341
+ m('div.font-semibold.text-gray-900.dark:text-white', ep.name),
342
+ m('div.text-xs.font-mono.text-gray-500.dark:text-slate-400.truncate.max-w-md.mt-0.5', { title: ep.url }, ep.url),
343
+ ep.description ? m('div.text-xs.text-gray-400.mt-1', ep.description) : null,
344
+ ]),
345
+ m('td.p-4', [
346
+ m('div.flex.flex-wrap.gap-1', evts.map(function(evt) {
347
+ return m('span.px-2.py-0.5.text-xs.font-mono.rounded-md.bg-gray-100.dark:bg-slate-700.text-gray-700.dark:text-slate-300', evt);
348
+ })),
349
+ ]),
350
+ m('td.p-4', [
351
+ ep.is_active
352
+ ? m('span.inline-flex.items-center.gap-1.5.px-2.5.py-1.rounded-full.text-xs.font-medium.bg-emerald-50.text-emerald-700.dark:bg-emerald-950/40.dark:text-emerald-300', [
353
+ m('span.w-1.5.h-1.5.rounded-full.bg-emerald-500'),
354
+ 'Active'
355
+ ])
356
+ : m('span.inline-flex.items-center.gap-1.5.px-2.5.py-1.rounded-full.text-xs.font-medium.bg-gray-100.text-gray-600.dark:bg-slate-700.dark:text-slate-400', [
357
+ m('span.w-1.5.h-1.5.rounded-full.bg-gray-400'),
358
+ 'Inactive'
359
+ ]),
360
+ ]),
361
+ m('td.p-4', [
362
+ stats.total > 0
363
+ ? m('div.text-xs', [
364
+ m('span.font-medium', stats.successRate + '% '),
365
+ m('span.text-gray-400', '(' + stats.successful + '/' + stats.total + ')'),
366
+ ])
367
+ : m('span.text-xs.text-gray-400', 'No deliveries'),
368
+ ]),
369
+ m('td.p-4.text-right', [
370
+ m('div.inline-flex.items-center.gap-2', [
371
+ m('button.px-2.5.py-1.text-xs.font-medium.rounded-lg.border.border-gray-200.dark:border-slate-700.text-gray-700.dark:text-slate-200.hover:bg-gray-100.dark:hover:bg-slate-700.transition-colors', {
372
+ onclick: function() { pingEndpoint(ep); },
373
+ }, '⚡ Ping'),
374
+ m('button.px-2.5.py-1.text-xs.font-medium.rounded-lg.border.border-gray-200.dark:border-slate-700.text-indigo-600.dark:text-indigo-400.hover:bg-indigo-50.dark:hover:bg-slate-700.transition-colors', {
375
+ onclick: function() { openEditModal(ep); },
376
+ }, 'Edit'),
377
+ m('button.px-2.5.py-1.text-xs.font-medium.rounded-lg.border.border-gray-200.dark:border-slate-700.text-red-600.hover:bg-red-50.dark:hover:bg-slate-700.transition-colors', {
378
+ onclick: function() { deleteEndpoint(ep); },
379
+ }, 'Delete'),
380
+ ]),
381
+ ]),
382
+ ]);
383
+ })),
384
+ ]),
385
+ ]),
386
+ ]) : null,
387
+
388
+ // TAB 2: DELIVERIES LIST
389
+ activeTab === 'deliveries' ? m('div.space-y-4', [
390
+ // Filter Bar
391
+ m('div.flex.flex-col.sm:flex-row.gap-3.items-center.justify-between.bg-white.dark:bg-slate-800.p-3.rounded-xl.border.border-gray-200.dark:border-slate-700', [
392
+ m('div.flex.items-center.gap-3.w-full.sm:w-auto', [
393
+ m('input.px-3.py-1.5.text-xs.border.border-gray-200.dark:border-slate-700.rounded-lg.bg-transparent.text-gray-900.dark:text-white', {
394
+ placeholder: 'Filter event (e.g. user.*)',
395
+ value: filterEvent,
396
+ oninput: function(e) { filterEvent = e.target.value; },
397
+ }),
398
+ m('select.px-3.py-1.5.text-xs.border.border-gray-200.dark:border-slate-700.rounded-lg.bg-transparent.text-gray-900.dark:text-white', {
399
+ value: filterSuccess,
400
+ onchange: function(e) { filterSuccess = e.target.value; },
401
+ }, [
402
+ m('option', { value: '' }, 'All Statuses'),
403
+ m('option', { value: 'true' }, 'Success Only'),
404
+ m('option', { value: 'false' }, 'Failed Only'),
405
+ ]),
406
+ m('button.px-3.py-1.5.text-xs.font-medium.rounded-lg.bg-gray-100.dark:bg-slate-700.text-gray-700.dark:text-slate-200.hover:bg-gray-200', {
407
+ onclick: function() { deliveryPage = 1; loadDeliveries(); },
408
+ }, 'Filter'),
409
+ ]),
410
+ m('div.text-xs.text-gray-500.dark:text-slate-400', 'Total: ' + deliveryTotal + ' records'),
411
+ ]),
412
+
413
+ // Deliveries Table
414
+ m('div.bg-white.dark:bg-slate-800.border.border-gray-200.dark:border-slate-700.rounded-2xl.overflow-hidden.shadow-sm', [
415
+ deliveries.length === 0 ? m('div.p-12.text-center.text-gray-500.dark:text-slate-400', [
416
+ m('p.text-base.font-medium', 'No delivery records found yet.'),
417
+ ]) : m('div.overflow-x-auto', [
418
+ m('table.w-full.text-left.text-sm', [
419
+ m('thead.bg-gray-50.dark:bg-slate-750.text-xs.uppercase.text-gray-500.dark:text-slate-400.border-b.border-gray-200.dark:border-slate-700', [
420
+ m('tr', [
421
+ m('th.p-4', 'Status'),
422
+ m('th.p-4', 'Event'),
423
+ m('th.p-4', 'Duration'),
424
+ m('th.p-4', 'Attempt'),
425
+ m('th.p-4', 'Time'),
426
+ m('th.p-4.text-right', 'Actions'),
427
+ ]),
428
+ ]),
429
+ m('tbody.divide-y.divide-gray-100.dark:divide-slate-700', deliveries.map(function(dlv) {
430
+ var isSuccess = dlv.success;
431
+ var badgeClass = isSuccess
432
+ ? 'bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-950/40 dark:text-emerald-300'
433
+ : 'bg-rose-50 text-rose-700 border-rose-200 dark:bg-rose-950/40 dark:text-rose-300';
434
+ var statusLabel = dlv.status_code ? 'HTTP ' + dlv.status_code : (isSuccess ? '200 OK' : 'Error');
435
+
436
+ return m('tr.hover:bg-gray-50/60.dark:hover:bg-slate-750/50.transition-colors', [
437
+ m('td.p-4', [
438
+ m('span.inline-flex.items-center.px-2.5.py-0.5.rounded-full.text-xs.font-semibold.border', {
439
+ class: badgeClass,
440
+ }, statusLabel),
441
+ ]),
442
+ m('td.p-4', [
443
+ m('span.font-mono.font-semibold.text-gray-900.dark:text-white', dlv.event),
444
+ m('div.text-xs.text-gray-400', 'Endpoint #' + dlv.endpoint_id),
445
+ ]),
446
+ m('td.p-4.text-xs.text-gray-600.dark:text-slate-300', (dlv.duration_ms != null ? dlv.duration_ms + ' ms' : '—')),
447
+ m('td.p-4.text-xs.text-gray-600.dark:text-slate-300', dlv.attempt + ' / ' + dlv.max_attempts),
448
+ m('td.p-4.text-xs.text-gray-500.dark:text-slate-400', formatRelativeTime(dlv.created_at)),
449
+ m('td.p-4.text-right', [
450
+ m('div.inline-flex.items-center.gap-2', [
451
+ m('button.px-2.5.py-1.text-xs.font-medium.rounded-lg.border.border-gray-200.dark:border-slate-700.text-indigo-600.hover:bg-indigo-50.dark:hover:bg-slate-700', {
452
+ onclick: function() { activeDeliveryModal = dlv; },
453
+ }, 'Details'),
454
+ !isSuccess ? m('button.px-2.5.py-1.text-xs.font-medium.rounded-lg.bg-indigo-50.text-indigo-600.dark:bg-indigo-950/50.dark:text-indigo-300.hover:bg-indigo-100', {
455
+ onclick: function() { retryDelivery(dlv); },
456
+ }, 'Retry') : null,
457
+ ]),
458
+ ]),
459
+ ]);
460
+ })),
461
+ ]),
462
+ ]),
463
+
464
+ // Pagination Bar
465
+ m('div.flex.items-center.justify-between.p-4.border-t.border-gray-100.dark:border-slate-700', [
466
+ m('div.text-xs.text-gray-500', deliveryTotal + ' records listed'),
467
+ m('div.flex.items-center.gap-2', [
468
+ m('button.px-3.py-1.text-xs.border.rounded-lg.disabled:opacity-40', {
469
+ disabled: deliveryPage <= 1,
470
+ onclick: function() { if (deliveryPage > 1) { deliveryPage--; loadDeliveries(); } },
471
+ }, 'Previous'),
472
+ m('span.text-xs.text-gray-600.dark:text-slate-300', deliveryPage + ' / ' + deliveryPages),
473
+ m('button.px-3.py-1.text-xs.border.rounded-lg.disabled:opacity-40', {
474
+ disabled: deliveryPage >= deliveryPages,
475
+ onclick: function() { if (deliveryPage < deliveryPages) { deliveryPage++; loadDeliveries(); } },
476
+ }, 'Next'),
477
+ ]),
478
+ ]),
479
+ ]),
480
+ ]) : null,
481
+
482
+ // MODAL: CREATE / EDIT ENDPOINT
483
+ showEndpointModal ? m('div.fixed.inset-0.z-50.flex.items-center.justify-center.p-4.bg-black/50.backdrop-blur-sm', [
484
+ m('div.bg-white.dark:bg-slate-800.rounded-2xl.border.border-gray-200.dark:border-slate-700.shadow-2xl.w-full.max-w-lg.p-6.space-y-4', [
485
+ m('div.flex.items-center.justify-between', [
486
+ m('h3.text-lg.font-bold.text-gray-900.dark:text-white', editingEndpoint ? 'Edit Endpoint' : 'New Webhook Endpoint'),
487
+ m('button.text-gray-400.hover:text-gray-600.dark:hover:text-white', {
488
+ onclick: function() { showEndpointModal = false; },
489
+ }, '✕'),
490
+ ]),
491
+ m('div.space-y-3', [
492
+ m('div', [
493
+ m('label.block.text-xs.font-semibold.text-gray-700.dark:text-slate-300.mb-1', 'Name / Description *'),
494
+ m('input.w-full.px-3.py-2.text-sm.border.border-gray-300.dark:border-slate-600.rounded-xl.bg-transparent.text-gray-900.dark:text-white', {
495
+ placeholder: 'e.g. Zapier Order Sync',
496
+ value: endpointForm.name,
497
+ oninput: function(e) { endpointForm.name = e.target.value; },
498
+ }),
499
+ ]),
500
+ m('div', [
501
+ m('label.block.text-xs.font-semibold.text-gray-700.dark:text-slate-300.mb-1', 'Target URL *'),
502
+ m('input.w-full.px-3.py-2.text-sm.border.border-gray-300.dark:border-slate-600.rounded-xl.bg-transparent.text-gray-900.dark:text-white', {
503
+ placeholder: 'https://example.com/api/webhook',
504
+ value: endpointForm.url,
505
+ oninput: function(e) { endpointForm.url = e.target.value; },
506
+ }),
507
+ ]),
508
+ m('div', [
509
+ m('div.flex.items-center.justify-between.mb-1', [
510
+ m('label.text-xs.font-semibold.text-gray-700.dark:text-slate-300', 'Signing Secret'),
511
+ m('button.text-xs.text-indigo-600.dark:text-indigo-400.hover:underline', {
512
+ type: 'button',
513
+ onclick: function() { endpointForm.secret = generateRandomSecret(); },
514
+ }, 'Generate New'),
515
+ ]),
516
+ m('input.w-full.px-3.py-2.text-xs.font-mono.border.border-gray-300.dark:border-slate-600.rounded-xl.bg-transparent.text-gray-900.dark:text-white', {
517
+ value: endpointForm.secret,
518
+ oninput: function(e) { endpointForm.secret = e.target.value; },
519
+ }),
520
+ ]),
521
+ m('div', [
522
+ m('label.block.text-xs.font-semibold.text-gray-700.dark:text-slate-300.mb-1', 'Subscribed Events (Comma-separated, * for all events)'),
523
+ m('input.w-full.px-3.py-2.text-sm.border.border-gray-300.dark:border-slate-600.rounded-xl.bg-transparent.text-gray-900.dark:text-white', {
524
+ placeholder: 'user.*, order.created, payment.success',
525
+ value: endpointForm.events,
526
+ oninput: function(e) { endpointForm.events = e.target.value; },
527
+ }),
528
+ ]),
529
+ m('div.flex.items-center.gap-2.pt-1', [
530
+ m('input[type=checkbox]', {
531
+ id: 'active_check',
532
+ checked: endpointForm.is_active,
533
+ onchange: function(e) { endpointForm.is_active = e.target.checked; },
534
+ }),
535
+ m('label.text-xs.font-medium.text-gray-700.dark:text-slate-300', { for: 'active_check' }, 'Enable this endpoint to receive events'),
536
+ ]),
537
+ ]),
538
+ m('div.flex.items-center.justify-end.gap-3.pt-3.border-t.border-gray-100.dark:border-slate-700', [
539
+ m('button.px-4.py-2.text-xs.font-medium.rounded-xl.border.border-gray-300.dark:border-slate-600.text-gray-700.dark:text-slate-200', {
540
+ onclick: function() { showEndpointModal = false; },
541
+ }, 'Cancel'),
542
+ m('button.px-4.py-2.text-xs.font-semibold.rounded-xl.bg-indigo-600.text-white.hover:bg-indigo-500', {
543
+ onclick: saveEndpoint,
544
+ }, editingEndpoint ? 'Update' : 'Save'),
545
+ ]),
546
+ ]),
547
+ ]) : null,
548
+
549
+ // MODAL: DELIVERY DETAILS
550
+ activeDeliveryModal ? m('div.fixed.inset-0.z-50.flex.items-center.justify-center.p-4.bg-black/50.backdrop-blur-sm', [
551
+ m('div.bg-white.dark:bg-slate-800.rounded-2xl.border.border-gray-200.dark:border-slate-700.shadow-2xl.w-full.max-w-2xl.max-h-[90vh].flex.flex-col.overflow-hidden', [
552
+ m('div.p-5.border-b.border-gray-200.dark:border-slate-700.flex.items-center.justify-between', [
553
+ m('div', [
554
+ m('h3.text-base.font-bold.text-gray-900.dark:text-white', 'Delivery Details #' + activeDeliveryModal.id),
555
+ m('span.text-xs.text-gray-500.dark:text-slate-400', activeDeliveryModal.event + ' — ' + formatRelativeTime(activeDeliveryModal.created_at)),
556
+ ]),
557
+ m('button.text-gray-400.hover:text-gray-600.dark:hover:text-white', {
558
+ onclick: function() { activeDeliveryModal = null; },
559
+ }, '✕'),
560
+ ]),
561
+ m('div.p-5.space-y-4.overflow-y-auto.flex-1', [
562
+ m('div.grid.grid-cols-2.sm:grid-cols-3.gap-2.text-xs.p-3.bg-gray-50.dark:bg-slate-750.rounded-xl', [
563
+ m('div', [m('span.text-gray-400.block', 'Status'), m('span.font-semibold', activeDeliveryModal.success ? 'Success' : 'Failed')]),
564
+ m('div', [m('span.text-gray-400.block', 'HTTP Code'), m('span.font-semibold', activeDeliveryModal.status_code || '—')]),
565
+ m('div', [m('span.text-gray-400.block', 'Response Time'), m('span.font-semibold', (activeDeliveryModal.duration_ms || 0) + ' ms')]),
566
+ m('div', [m('span.text-gray-400.block', 'Attempt'), m('span.font-semibold', activeDeliveryModal.attempt + ' / ' + activeDeliveryModal.max_attempts)]),
567
+ m('div.col-span-2', [m('span.text-gray-400.block', 'Error'), m('span.text-red-500', activeDeliveryModal.error || 'None')]),
568
+ ]),
569
+ m('div', [
570
+ m('label.block.text-xs.font-semibold.text-gray-700.dark:text-slate-300.mb-1', 'Request Payload (JSON)'),
571
+ m('pre.p-3.rounded-xl.bg-slate-900.text-slate-100.text-xs.font-mono.overflow-x-auto.max-h-60',
572
+ (function() {
573
+ try {
574
+ return JSON.stringify(JSON.parse(activeDeliveryModal.payload), null, 2);
575
+ } catch (e) {
576
+ return activeDeliveryModal.payload || '';
577
+ }
578
+ })()
579
+ ),
580
+ ]),
581
+ activeDeliveryModal.response_body ? m('div', [
582
+ m('label.block.text-xs.font-semibold.text-gray-700.dark:text-slate-300.mb-1', 'Target Response Body'),
583
+ m('pre.p-3.rounded-xl.bg-slate-900.text-slate-100.text-xs.font-mono.overflow-x-auto.max-h-40',
584
+ activeDeliveryModal.response_body
585
+ ),
586
+ ]) : null,
587
+ ]),
588
+ m('div.p-4.border-t.border-gray-200.dark:border-slate-700.flex.justify-end', [
589
+ m('button.px-4.py-2.text-xs.font-semibold.rounded-xl.bg-indigo-600.text-white.hover:bg-indigo-500', {
590
+ onclick: function() { activeDeliveryModal = null; },
591
+ }, 'Close'),
592
+ ]),
593
+ ]),
594
+ ]) : null,
595
+ ]);
596
+ },
597
+ };
598
+ }
599
+
600
+ window.__customPages = window.__customPages || {};
601
+ window.__customPages['webhooks'] = WebhooksPage;
602
+ })();
603
+ `;
604
+ }
605
+
606
+ module.exports = {
607
+ generateWebhooksComponent,
608
+ };