@intranefr/superbackend 1.4.3

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 (188) hide show
  1. package/.commiat +4 -0
  2. package/.env.example +47 -0
  3. package/README.md +110 -0
  4. package/index.js +94 -0
  5. package/package.json +67 -0
  6. package/public/css/styles.css +139 -0
  7. package/public/js/animations.js +41 -0
  8. package/sdk/error-tracking/browser/package.json +16 -0
  9. package/sdk/error-tracking/browser/src/core.js +270 -0
  10. package/sdk/error-tracking/browser/src/embed.js +18 -0
  11. package/sdk/error-tracking/browser/src/index.js +1 -0
  12. package/server.js +5 -0
  13. package/src/admin/endpointRegistry.js +300 -0
  14. package/src/controllers/admin.controller.js +321 -0
  15. package/src/controllers/adminAssets.controller.js +530 -0
  16. package/src/controllers/adminAssetsStorage.controller.js +260 -0
  17. package/src/controllers/adminEjsVirtual.controller.js +354 -0
  18. package/src/controllers/adminFeatureFlags.controller.js +155 -0
  19. package/src/controllers/adminHeadless.controller.js +1071 -0
  20. package/src/controllers/adminI18n.controller.js +604 -0
  21. package/src/controllers/adminJsonConfigs.controller.js +97 -0
  22. package/src/controllers/adminLlm.controller.js +273 -0
  23. package/src/controllers/adminMigration.controller.js +257 -0
  24. package/src/controllers/adminSeoConfig.controller.js +515 -0
  25. package/src/controllers/adminStats.controller.js +121 -0
  26. package/src/controllers/adminUploadNamespaces.controller.js +208 -0
  27. package/src/controllers/assets.controller.js +248 -0
  28. package/src/controllers/auth.controller.js +93 -0
  29. package/src/controllers/billing.controller.js +223 -0
  30. package/src/controllers/featureFlags.controller.js +35 -0
  31. package/src/controllers/forms.controller.js +217 -0
  32. package/src/controllers/globalSettings.controller.js +252 -0
  33. package/src/controllers/headlessCrud.controller.js +126 -0
  34. package/src/controllers/i18n.controller.js +12 -0
  35. package/src/controllers/invite.controller.js +249 -0
  36. package/src/controllers/jsonConfigs.controller.js +19 -0
  37. package/src/controllers/metrics.controller.js +149 -0
  38. package/src/controllers/notificationAdmin.controller.js +264 -0
  39. package/src/controllers/notifications.controller.js +131 -0
  40. package/src/controllers/org.controller.js +357 -0
  41. package/src/controllers/orgAdmin.controller.js +491 -0
  42. package/src/controllers/stripeAdmin.controller.js +410 -0
  43. package/src/controllers/user.controller.js +361 -0
  44. package/src/controllers/userAdmin.controller.js +277 -0
  45. package/src/controllers/waitingList.controller.js +167 -0
  46. package/src/controllers/webhook.controller.js +200 -0
  47. package/src/middleware/auth.js +66 -0
  48. package/src/middleware/errorCapture.js +170 -0
  49. package/src/middleware/headlessApiTokenAuth.js +57 -0
  50. package/src/middleware/org.js +108 -0
  51. package/src/middleware.js +901 -0
  52. package/src/models/ActionEvent.js +31 -0
  53. package/src/models/ActivityLog.js +41 -0
  54. package/src/models/Asset.js +84 -0
  55. package/src/models/AuditEvent.js +93 -0
  56. package/src/models/EmailLog.js +28 -0
  57. package/src/models/ErrorAggregate.js +72 -0
  58. package/src/models/FormSubmission.js +41 -0
  59. package/src/models/GlobalSetting.js +38 -0
  60. package/src/models/HeadlessApiToken.js +24 -0
  61. package/src/models/HeadlessModelDefinition.js +41 -0
  62. package/src/models/I18nEntry.js +77 -0
  63. package/src/models/I18nLocale.js +33 -0
  64. package/src/models/Invite.js +70 -0
  65. package/src/models/JsonConfig.js +46 -0
  66. package/src/models/Notification.js +60 -0
  67. package/src/models/Organization.js +57 -0
  68. package/src/models/OrganizationMember.js +43 -0
  69. package/src/models/StripeCatalogItem.js +77 -0
  70. package/src/models/StripeWebhookEvent.js +57 -0
  71. package/src/models/User.js +89 -0
  72. package/src/models/VirtualEjsFile.js +60 -0
  73. package/src/models/VirtualEjsFileVersion.js +43 -0
  74. package/src/models/VirtualEjsGroupChange.js +32 -0
  75. package/src/models/WaitingList.js +41 -0
  76. package/src/models/Webhook.js +63 -0
  77. package/src/models/Workflow.js +29 -0
  78. package/src/models/WorkflowExecution.js +12 -0
  79. package/src/routes/admin.routes.js +26 -0
  80. package/src/routes/adminAssets.routes.js +28 -0
  81. package/src/routes/adminAssetsStorage.routes.js +13 -0
  82. package/src/routes/adminAudit.routes.js +196 -0
  83. package/src/routes/adminEjsVirtual.routes.js +17 -0
  84. package/src/routes/adminErrors.routes.js +164 -0
  85. package/src/routes/adminFeatureFlags.routes.js +12 -0
  86. package/src/routes/adminHeadless.routes.js +38 -0
  87. package/src/routes/adminI18n.routes.js +22 -0
  88. package/src/routes/adminJsonConfigs.routes.js +15 -0
  89. package/src/routes/adminLlm.routes.js +12 -0
  90. package/src/routes/adminMigration.routes.js +81 -0
  91. package/src/routes/adminSeoConfig.routes.js +20 -0
  92. package/src/routes/adminUploadNamespaces.routes.js +13 -0
  93. package/src/routes/assets.routes.js +21 -0
  94. package/src/routes/auth.routes.js +12 -0
  95. package/src/routes/billing.routes.js +11 -0
  96. package/src/routes/errorTracking.routes.js +31 -0
  97. package/src/routes/featureFlags.routes.js +9 -0
  98. package/src/routes/forms.routes.js +9 -0
  99. package/src/routes/formsAdmin.routes.js +13 -0
  100. package/src/routes/globalSettings.routes.js +18 -0
  101. package/src/routes/headless.routes.js +15 -0
  102. package/src/routes/i18n.routes.js +8 -0
  103. package/src/routes/invite.routes.js +9 -0
  104. package/src/routes/jsonConfigs.routes.js +8 -0
  105. package/src/routes/log.routes.js +111 -0
  106. package/src/routes/metrics.routes.js +9 -0
  107. package/src/routes/notificationAdmin.routes.js +15 -0
  108. package/src/routes/notifications.routes.js +12 -0
  109. package/src/routes/org.routes.js +31 -0
  110. package/src/routes/orgAdmin.routes.js +20 -0
  111. package/src/routes/publicAssets.routes.js +7 -0
  112. package/src/routes/stripeAdmin.routes.js +20 -0
  113. package/src/routes/user.routes.js +22 -0
  114. package/src/routes/userAdmin.routes.js +15 -0
  115. package/src/routes/waitingList.routes.js +13 -0
  116. package/src/routes/waitingListAdmin.routes.js +9 -0
  117. package/src/routes/webhook.routes.js +32 -0
  118. package/src/routes/workflowWebhook.routes.js +54 -0
  119. package/src/routes/workflows.routes.js +110 -0
  120. package/src/services/assets.service.js +110 -0
  121. package/src/services/audit.service.js +62 -0
  122. package/src/services/auditLogger.js +165 -0
  123. package/src/services/ejsVirtual.service.js +614 -0
  124. package/src/services/email.service.js +351 -0
  125. package/src/services/errorLogger.js +221 -0
  126. package/src/services/featureFlags.service.js +202 -0
  127. package/src/services/forms.service.js +214 -0
  128. package/src/services/globalSettings.service.js +49 -0
  129. package/src/services/headlessApiTokens.service.js +158 -0
  130. package/src/services/headlessCrypto.service.js +31 -0
  131. package/src/services/headlessModels.service.js +356 -0
  132. package/src/services/i18n.service.js +314 -0
  133. package/src/services/i18nInferredKeys.service.js +337 -0
  134. package/src/services/jsonConfigs.service.js +392 -0
  135. package/src/services/llm.service.js +749 -0
  136. package/src/services/migration.service.js +581 -0
  137. package/src/services/migrationAssets/fsLocal.js +58 -0
  138. package/src/services/migrationAssets/index.js +134 -0
  139. package/src/services/migrationAssets/s3.js +75 -0
  140. package/src/services/migrationAssets/sftp.js +92 -0
  141. package/src/services/notification.service.js +212 -0
  142. package/src/services/objectStorage.service.js +514 -0
  143. package/src/services/seoConfig.service.js +402 -0
  144. package/src/services/storage.js +150 -0
  145. package/src/services/stripe.service.js +185 -0
  146. package/src/services/stripeHelper.service.js +264 -0
  147. package/src/services/uploadNamespaces.service.js +326 -0
  148. package/src/services/webhook.service.js +157 -0
  149. package/src/services/workflow.service.js +271 -0
  150. package/src/utils/asyncHandler.js +5 -0
  151. package/src/utils/encryption.js +80 -0
  152. package/src/utils/jwt.js +40 -0
  153. package/src/utils/orgRoles.js +156 -0
  154. package/src/utils/validation.js +26 -0
  155. package/src/utils/webhookRetry.js +93 -0
  156. package/views/admin-assets.ejs +444 -0
  157. package/views/admin-audit.ejs +283 -0
  158. package/views/admin-coolify-deploy.ejs +207 -0
  159. package/views/admin-dashboard-home.ejs +291 -0
  160. package/views/admin-dashboard.ejs +397 -0
  161. package/views/admin-ejs-virtual.ejs +280 -0
  162. package/views/admin-errors.ejs +368 -0
  163. package/views/admin-feature-flags.ejs +390 -0
  164. package/views/admin-forms.ejs +526 -0
  165. package/views/admin-global-settings.ejs +436 -0
  166. package/views/admin-headless.ejs +2020 -0
  167. package/views/admin-i18n-locales.ejs +221 -0
  168. package/views/admin-i18n.ejs +728 -0
  169. package/views/admin-json-configs.ejs +410 -0
  170. package/views/admin-llm.ejs +884 -0
  171. package/views/admin-metrics.ejs +274 -0
  172. package/views/admin-migration.ejs +814 -0
  173. package/views/admin-notifications.ejs +430 -0
  174. package/views/admin-organizations.ejs +984 -0
  175. package/views/admin-seo-config.ejs +673 -0
  176. package/views/admin-stripe-pricing.ejs +558 -0
  177. package/views/admin-test.ejs +342 -0
  178. package/views/admin-users.ejs +452 -0
  179. package/views/admin-waiting-list.ejs +547 -0
  180. package/views/admin-webhooks.ejs +329 -0
  181. package/views/admin-workflows.ejs +310 -0
  182. package/views/partials/admin-assets-script.ejs +2022 -0
  183. package/views/partials/admin-test-sidebar.ejs +14 -0
  184. package/views/partials/dashboard/nav-items.ejs +66 -0
  185. package/views/partials/dashboard/palette.ejs +63 -0
  186. package/views/partials/dashboard/sidebar.ejs +21 -0
  187. package/views/partials/dashboard/tab-bar.ejs +26 -0
  188. package/views/partials/footer.ejs +3 -0
@@ -0,0 +1,329 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Webhook Management | SaaSBackend</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
9
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/dist/tabler-icons.min.css">
10
+ </head>
11
+ <body class="bg-gray-50">
12
+ <div id="app" class="max-w-7xl mx-auto p-6 space-y-8">
13
+ <!-- Header -->
14
+ <div class="flex justify-between items-center">
15
+ <div>
16
+ <h1 class="text-2xl font-bold text-gray-900">Outgoing Webhooks</h1>
17
+ <p class="text-sm text-gray-500">Deliver real-time events to your external systems.</p>
18
+ </div>
19
+ <button @click="openCreateModal" class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-lg hover:bg-indigo-700 transition-colors shadow-sm">
20
+ <i class="ti ti-plus mr-2"></i>Add Webhook
21
+ </button>
22
+ </div>
23
+
24
+ <!-- Webhooks List -->
25
+ <div class="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden">
26
+ <table class="min-w-full divide-y divide-gray-200">
27
+ <thead class="bg-gray-50">
28
+ <tr>
29
+ <th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase">Webhook Name</th>
30
+ <th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase">Target URL</th>
31
+ <th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase">Events</th>
32
+ <th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase">Status</th>
33
+ <th class="px-6 py-3 text-right text-xs font-semibold text-gray-500 uppercase">Actions</th>
34
+ </tr>
35
+ </thead>
36
+ <tbody class="divide-y divide-gray-200">
37
+ <tr v-for="webhook in webhooks" :key="webhook._id" class="hover:bg-gray-50 transition-colors">
38
+ <td class="px-6 py-4">
39
+ <div class="text-sm font-bold text-gray-900">{{ webhook.name }}</div>
40
+ <div class="text-[10px] text-gray-400 font-mono mt-1">ID: {{ webhook._id }}</div>
41
+ </td>
42
+ <td class="px-6 py-4">
43
+ <div class="text-xs font-medium text-gray-600 truncate max-w-xs">{{ webhook.targetUrl }}</div>
44
+ <div class="text-[10px] text-gray-400 font-mono mt-1">Secret: ••••••••••••••••</div>
45
+ </td>
46
+ <td class="px-6 py-4">
47
+ <div class="flex flex-wrap gap-1">
48
+ <span v-for="event in webhook.events" :key="event" class="px-2 py-0.5 bg-indigo-50 text-indigo-700 text-[10px] font-bold rounded-full border border-indigo-100 uppercase">
49
+ {{ event }}
50
+ </span>
51
+ </div>
52
+ </td>
53
+ <td class="px-6 py-4">
54
+ <span :class="['px-2 py-0.5 rounded-full text-[10px] font-bold uppercase',
55
+ webhook.status === 'active' ? 'bg-green-100 text-green-700' :
56
+ webhook.status === 'failed' ? 'bg-red-100 text-red-700' : 'bg-gray-100 text-gray-700']">
57
+ {{ webhook.status }}
58
+ </span>
59
+ </td>
60
+ <td class="px-6 py-4 text-right space-x-3">
61
+ <button @click="viewHistory(webhook)" class="text-gray-600 hover:text-gray-900 text-sm font-medium">History</button>
62
+ <button @click="openEditModal(webhook)" class="text-indigo-600 hover:text-indigo-900 text-sm font-medium">Edit</button>
63
+ <button @click="testWebhook(webhook._id)" class="text-indigo-600 hover:text-indigo-900 text-sm font-medium">Test</button>
64
+ <button @click="deleteWebhook(webhook._id)" class="text-red-600 hover:text-red-900 text-sm font-medium">Delete</button>
65
+ </td>
66
+ </tr>
67
+ <tr v-if="webhooks.length === 0">
68
+ <td colspan="4" class="px-6 py-12 text-center text-gray-400 italic text-sm">
69
+ No webhooks configured. Add one to start streaming events.
70
+ </td>
71
+ </tr>
72
+ </tbody>
73
+ </table>
74
+ </div>
75
+
76
+ <!-- History Slide-over -->
77
+ <div v-if="selectedWebhook" class="fixed inset-0 overflow-hidden z-50">
78
+ <div class="absolute inset-0 overflow-hidden">
79
+ <div class="absolute inset-0 bg-gray-500 bg-opacity-75 transition-opacity" @click="selectedWebhook = null"></div>
80
+ <div class="fixed inset-y-0 right-0 pl-10 max-w-full flex">
81
+ <div class="w-screen max-w-2xl">
82
+ <div class="h-full flex flex-col bg-white shadow-xl overflow-y-scroll">
83
+ <div class="px-4 py-6 bg-gray-50 sm:px-6">
84
+ <div class="flex items-start justify-between">
85
+ <h2 class="text-lg font-medium text-gray-900">Delivery History</h2>
86
+ <div class="ml-3 h-7 flex items-center">
87
+ <button @click="selectedWebhook = null" class="bg-white rounded-md text-gray-400 hover:text-gray-500 focus:outline-none">
88
+ <i class="ti ti-x text-xl"></i>
89
+ </button>
90
+ </div>
91
+ </div>
92
+ <div class="mt-1">
93
+ <p class="text-sm text-gray-500 truncate">{{ selectedWebhook.targetUrl }}</p>
94
+ </div>
95
+ </div>
96
+ <div class="relative flex-1 px-4 sm:px-6 py-6">
97
+ <div v-if="loadingHistory" class="flex justify-center py-10">
98
+ <div class="animate-spin rounded-full h-8 w-8 border-b-2 border-indigo-600"></div>
99
+ </div>
100
+ <div v-else-if="history.length === 0" class="text-center py-10 text-gray-500 italic">
101
+ No delivery attempts recorded yet.
102
+ </div>
103
+ <div v-else class="space-y-4">
104
+ <div v-for="log in history" :key="log._id" class="border rounded-lg overflow-hidden">
105
+ <div :class="['px-4 py-3 flex items-center justify-between', log.action === 'WEBHOOK_DELIVERY_SUCCESS' ? 'bg-green-50' : 'bg-red-50']">
106
+ <div class="flex items-center gap-2">
107
+ <i :class="['ti', log.action === 'WEBHOOK_DELIVERY_SUCCESS' ? 'ti-check text-green-600' : 'ti-alert-circle text-red-600']"></i>
108
+ <span class="text-xs font-bold uppercase tracking-wider text-gray-700">{{ log.meta.event }}</span>
109
+ </div>
110
+ <span class="text-[10px] text-gray-500">{{ new Date(log.createdAt).toLocaleString() }}</span>
111
+ </div>
112
+ <div class="p-4 space-y-3">
113
+ <div class="flex justify-between text-xs">
114
+ <span class="text-gray-500">Status Code</span>
115
+ <span :class="['font-mono font-bold', log.meta.statusCode >= 200 && log.meta.statusCode < 300 ? 'text-green-600' : 'text-red-600']">
116
+ {{ log.meta.statusCode || 'ERROR' }}
117
+ </span>
118
+ </div>
119
+ <div v-if="log.meta.error" class="text-xs text-red-600 bg-red-50 p-2 rounded">
120
+ {{ log.meta.error }}
121
+ </div>
122
+ <div>
123
+ <h4 class="text-[10px] font-bold text-gray-400 uppercase mb-1">Payload</h4>
124
+ <pre class="bg-gray-900 text-gray-100 p-3 rounded text-[10px] overflow-x-auto">{{ JSON.stringify(log.meta.payload, null, 2) }}</pre>
125
+ </div>
126
+ </div>
127
+ </div>
128
+ </div>
129
+ </div>
130
+ </div>
131
+ </div>
132
+ </div>
133
+ </div>
134
+ </div>
135
+
136
+ <!-- Create/Edit Modal -->
137
+ <div v-if="showModal" class="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50">
138
+ <div class="bg-white rounded-xl shadow-xl max-w-lg w-full p-6 space-y-6">
139
+ <h2 class="text-xl font-bold text-gray-900">{{ editingId ? 'Edit Webhook' : 'Configure New Webhook' }}</h2>
140
+ <div class="space-y-4">
141
+ <div>
142
+ <label class="block text-sm font-medium text-gray-700 mb-1">Webhook Name (Optional)</label>
143
+ <input v-model="newWebhook.name" type="text" placeholder="e.g. My Zapier Feed" class="w-full border border-gray-300 rounded-lg p-2 text-sm focus:ring-2 focus:ring-indigo-500 outline-none">
144
+ </div>
145
+ <div>
146
+ <label class="block text-sm font-medium text-gray-700 mb-1">Target URL</label>
147
+ <input v-model="newWebhook.targetUrl" type="url" placeholder="https://api.yoursite.com/webhook" class="w-full border border-gray-300 rounded-lg p-2 text-sm focus:ring-2 focus:ring-indigo-500 outline-none">
148
+ </div>
149
+ <div class="grid grid-cols-2 gap-4">
150
+ <div>
151
+ <label class="block text-sm font-medium text-gray-700 mb-1">Timeout (ms)</label>
152
+ <input v-model.number="newWebhook.timeout" type="number" step="500" min="1000" max="30000" class="w-full border border-gray-300 rounded-lg p-2 text-sm focus:ring-2 focus:ring-indigo-500 outline-none">
153
+ </div>
154
+ <div class="flex items-center pt-6">
155
+ <label class="flex items-center gap-2 cursor-pointer">
156
+ <input v-model="newWebhook.isAsync" type="checkbox" class="rounded text-indigo-600">
157
+ <span class="text-sm font-medium text-gray-700">Async Delivery</span>
158
+ </label>
159
+ </div>
160
+ </div>
161
+ <div>
162
+ <label class="block text-sm font-medium text-gray-700 mb-2">Events to Subscribe</label>
163
+ <div class="grid grid-cols-2 gap-2">
164
+ <label v-for="event in availableEvents" :key="event" class="flex items-center gap-2 p-2 border rounded-lg hover:bg-gray-50 cursor-pointer">
165
+ <input type="checkbox" v-model="newWebhook.events" :value="event" class="rounded text-indigo-600">
166
+ <span class="text-xs text-gray-600">{{ event }}</span>
167
+ </label>
168
+ </div>
169
+ </div>
170
+ </div>
171
+ <div class="flex justify-end gap-3 pt-4">
172
+ <button @click="showModal = false" class="px-4 py-2 text-sm font-medium text-gray-600 hover:text-gray-800">Cancel</button>
173
+ <button @click="saveWebhook" class="px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-lg hover:bg-indigo-700 shadow-sm">
174
+ {{ editingId ? 'Update Webhook' : 'Create Webhook' }}
175
+ </button>
176
+ </div>
177
+ </div>
178
+ </div>
179
+
180
+ <!-- Toast -->
181
+ <div v-if="toast" :class="['fixed bottom-6 right-6 px-6 py-3 rounded-lg text-white shadow-lg transition-all duration-300 z-50', toast.type === 'success' ? 'bg-green-600' : 'bg-red-600']">
182
+ {{ toast.message }}
183
+ </div>
184
+ </div>
185
+
186
+ <script>
187
+ const { createApp, ref, onMounted } = Vue;
188
+
189
+ createApp({
190
+ setup() {
191
+ const webhooks = ref([]);
192
+ const showModal = ref(false);
193
+ const toast = ref(null);
194
+ const selectedWebhook = ref(null);
195
+ const history = ref([]);
196
+ const loadingHistory = ref(false);
197
+ const editingId = ref(null);
198
+ const availableEvents = [
199
+ 'user.login',
200
+ 'user.registered',
201
+ 'organization.updated',
202
+ 'member.added',
203
+ 'form.submitted',
204
+ 'audit.event'
205
+ ];
206
+
207
+ const newWebhook = ref({
208
+ name: '',
209
+ targetUrl: '',
210
+ events: [],
211
+ timeout: 5000,
212
+ isAsync: false
213
+ });
214
+
215
+ const showToast = (message, type = 'success') => {
216
+ toast.value = { message, type };
217
+ setTimeout(() => toast.value = null, 3000);
218
+ };
219
+
220
+ const fetchWebhooks = async () => {
221
+ try {
222
+ const res = await fetch('<%= baseUrl %>/api/webhooks');
223
+ webhooks.value = await res.json();
224
+ } catch (e) {
225
+ showToast('Failed to load webhooks', 'error');
226
+ }
227
+ };
228
+
229
+ const viewHistory = async (webhook) => {
230
+ selectedWebhook.value = webhook;
231
+ loadingHistory.value = true;
232
+ history.value = [];
233
+ try {
234
+ const res = await fetch(`<%= baseUrl %>/api/webhooks/${webhook._id}/history`);
235
+ if (res.ok) {
236
+ history.value = await res.json();
237
+ } else {
238
+ showToast('Failed to load history', 'error');
239
+ }
240
+ } catch (e) {
241
+ showToast('Error loading history', 'error');
242
+ } finally {
243
+ loadingHistory.value = false;
244
+ }
245
+ };
246
+
247
+ const openCreateModal = () => {
248
+ editingId.value = null;
249
+ newWebhook.value = { name: '', targetUrl: '', events: [], timeout: 5000, isAsync: false };
250
+ showModal.value = true;
251
+ };
252
+
253
+ const openEditModal = (webhook) => {
254
+ editingId.value = webhook._id;
255
+ newWebhook.value = {
256
+ name: webhook.name,
257
+ targetUrl: webhook.targetUrl,
258
+ events: [...webhook.events],
259
+ timeout: webhook.timeout || 5000,
260
+ isAsync: webhook.isAsync || false
261
+ };
262
+ showModal.value = true;
263
+ };
264
+
265
+ const saveWebhook = async () => {
266
+ if (!newWebhook.value.targetUrl || newWebhook.value.events.length === 0) {
267
+ showToast('Please provide a URL and select at least one event', 'error');
268
+ return;
269
+ }
270
+ try {
271
+ const method = editingId.value ? 'PATCH' : 'POST';
272
+ const url = editingId.value ? `<%= baseUrl %>/api/webhooks/${editingId.value}` : '<%= baseUrl %>/api/webhooks';
273
+
274
+ const res = await fetch(url, {
275
+ method,
276
+ headers: { 'Content-Type': 'application/json' },
277
+ body: JSON.stringify(newWebhook.value)
278
+ });
279
+ if (res.ok) {
280
+ showToast(`Webhook ${editingId.value ? 'updated' : 'created'} successfully`);
281
+ showModal.value = false;
282
+ fetchWebhooks();
283
+ } else {
284
+ const err = await res.json();
285
+ showToast(err.error || 'Failed to save webhook', 'error');
286
+ }
287
+ } catch (e) {
288
+ showToast('Error saving webhook', 'error');
289
+ }
290
+ };
291
+
292
+ const deleteWebhook = async (id) => {
293
+ if (!confirm('Are you sure you want to delete this webhook?')) return;
294
+ try {
295
+ const res = await fetch(`<%= baseUrl %>/api/webhooks/${id}`, { method: 'DELETE' });
296
+ if (res.ok) {
297
+ showToast('Webhook deleted');
298
+ fetchWebhooks();
299
+ }
300
+ } catch (e) {
301
+ showToast('Error deleting webhook', 'error');
302
+ }
303
+ };
304
+
305
+ const testWebhook = async (id) => {
306
+ try {
307
+ const res = await fetch(`<%= baseUrl %>/api/webhooks/${id}/test`, { method: 'POST' });
308
+ if (res.ok) {
309
+ showToast('Test payload dispatched');
310
+ } else {
311
+ showToast('Test delivery failed', 'error');
312
+ }
313
+ } catch (e) {
314
+ showToast('Error testing webhook', 'error');
315
+ }
316
+ };
317
+
318
+ onMounted(fetchWebhooks);
319
+
320
+ return {
321
+ webhooks, showModal, toast, availableEvents, newWebhook,
322
+ selectedWebhook, history, loadingHistory, editingId,
323
+ openCreateModal, openEditModal, saveWebhook, deleteWebhook, testWebhook, viewHistory
324
+ };
325
+ }
326
+ }).mount('#app');
327
+ </script>
328
+ </body>
329
+ </html>