@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,342 @@
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>SaaSBackend API Laboratory</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css">
9
+ <style>
10
+ :root {
11
+ --bg-main: #0f172a;
12
+ --bg-card: #1e293b;
13
+ --text-main: #f8fafc;
14
+ --accent: #3b82f6;
15
+ }
16
+ body { background-color: var(--bg-main); color: var(--text-main); }
17
+ .toast { animation: slideIn 0.3s ease-out; }
18
+ @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
19
+ .sidebar-group-content[data-collapsed="true"] { display: none; }
20
+ .caret { transition: transform 0.2s ease-out; }
21
+ .caret.down { transform: rotate(90deg); }
22
+ pre { border-radius: 0.5rem !important; }
23
+ .custom-scrollbar::-webkit-scrollbar { width: 6px; }
24
+ .custom-scrollbar::-webkit-scrollbar-track { background: transparent; }
25
+ .custom-scrollbar::-webkit-scrollbar-thumb { background: #334155; border-radius: 10px; }
26
+ </style>
27
+ </head>
28
+ <body class="antialiased">
29
+ <div class="flex h-screen overflow-hidden">
30
+ <!-- Sidebar -->
31
+ <aside class="w-80 bg-slate-900 border-r border-slate-800 flex flex-col h-full">
32
+ <div class="p-6">
33
+ <h1 class="text-xl font-bold flex items-center gap-2">
34
+ <span class="text-blue-500">🧪</span> API Laboratory
35
+ </h1>
36
+ <div class="mt-4 relative">
37
+ <input type="text" id="sidebar-search" placeholder="Search endpoints..."
38
+ class="w-full bg-slate-800 border border-slate-700 rounded-md py-2 px-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
39
+ </div>
40
+ </div>
41
+
42
+ <nav id="endpoint-sidebar" class="flex-1 overflow-y-auto px-4 custom-scrollbar space-y-2 pb-6">
43
+ <!-- Endpoints will be rendered here -->
44
+ </nav>
45
+
46
+ <div class="p-4 border-t border-slate-800 bg-slate-900/50">
47
+ <div class="flex items-center justify-between text-xs text-slate-500 mb-2">
48
+ <span>SaaSBackend Framework</span>
49
+ <span class="px-2 py-0.5 bg-slate-800 rounded">v1.2.0</span>
50
+ </div>
51
+ </div>
52
+ </aside>
53
+
54
+ <!-- Main Content -->
55
+ <main class="flex-1 flex flex-col h-full bg-slate-950 overflow-hidden">
56
+ <!-- Top Bar -->
57
+ <header class="h-16 border-b border-slate-800 flex items-center justify-between px-8 bg-slate-900/50">
58
+ <div id="breadcrumb" class="text-sm font-medium text-slate-400">
59
+ Select an endpoint to begin
60
+ </div>
61
+ <div class="flex items-center gap-4">
62
+ <a href="#" id="back-to-admin" class="text-xs px-3 py-1.5 bg-slate-800 rounded-full border border-slate-700 text-slate-200 hover:bg-slate-700 transition">
63
+ ← Back to Admin
64
+ </a>
65
+ <div class="flex items-center gap-2 bg-slate-800 rounded-full px-4 py-1.5 border border-slate-700">
66
+ <div class="w-2 h-2 rounded-full bg-green-500 animate-pulse"></div>
67
+ <span class="text-xs font-mono">DB CONNECTED</span>
68
+ </div>
69
+ </div>
70
+ </header>
71
+
72
+ <!-- Dashboard/Form Area -->
73
+ <div id="main-scroll" class="flex-1 overflow-y-auto custom-scrollbar p-8">
74
+ <div id="content" class="max-w-5xl mx-auto space-y-6">
75
+ <div class="bg-slate-900 rounded-xl border border-slate-800 p-12 text-center space-y-4">
76
+ <div class="text-6xl mb-6">🚀</div>
77
+ <h2 class="text-2xl font-bold">Framework Technical Console</h2>
78
+ <p class="text-slate-400 max-w-lg mx-auto">
79
+ Test and debug SaaSBackend internal APIs. Documented endpoints are automatically grouped by service.
80
+ </p>
81
+ <div class="grid grid-cols-3 gap-4 pt-8 max-w-2xl mx-auto">
82
+ <div class="p-4 bg-slate-800/50 rounded-lg border border-slate-700">
83
+ <div class="text-blue-400 font-bold mb-1">Secure</div>
84
+ <div class="text-xs text-slate-500">Basic & JWT support</div>
85
+ </div>
86
+ <div class="p-4 bg-slate-800/50 rounded-lg border border-slate-700">
87
+ <div class="text-green-400 font-bold mb-1">Fast</div>
88
+ <div class="text-xs text-slate-500">Instant response</div>
89
+ </div>
90
+ <div class="p-4 bg-slate-800/50 rounded-lg border border-slate-700">
91
+ <div class="text-purple-400 font-bold mb-1">Local</div>
92
+ <div class="text-xs text-slate-500">Persistent history</div>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ </main>
99
+ </div>
100
+
101
+ <!-- Toast Container -->
102
+ <div id="toast-container" class="fixed top-4 right-4 space-y-2 z-50"></div>
103
+
104
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
105
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script>
106
+ <script>
107
+ const API_BASE = window.location.origin + "<%= baseUrl %>";
108
+ const ADMIN_BASE = API_BASE + "/admin";
109
+ const ENDPOINT_REGISTRY = <%- JSON.stringify(endpointRegistry || []) %>;
110
+
111
+ let history = JSON.parse(localStorage.getItem('api_history') || '[]');
112
+
113
+ function showToast(message, type = 'success') {
114
+ const container = document.getElementById('toast-container');
115
+ const toast = document.createElement('div');
116
+ toast.className = `toast px-6 py-4 rounded-lg shadow-2xl text-white font-medium flex items-center gap-3 ${
117
+ type === 'success' ? 'bg-blue-600' : 'bg-red-600'
118
+ }`;
119
+ toast.innerHTML = `<span>${type === 'success' ? '✅' : '❌'}</span> ${message}`;
120
+ container.appendChild(toast);
121
+ setTimeout(() => {
122
+ toast.classList.add('opacity-0', 'transition-opacity', 'duration-300');
123
+ setTimeout(() => toast.remove(), 300);
124
+ }, 3000);
125
+ }
126
+
127
+ async function makeRequest(endpoint, method, body = null, authType = 'none') {
128
+ const headers = { 'Content-Type': 'application/json' };
129
+
130
+ if (authType === 'jwt') {
131
+ const token = document.getElementById('jwt-input')?.value || localStorage.getItem('last_jwt');
132
+ if (!token) {
133
+ showToast('JWT token required', 'error');
134
+ return;
135
+ }
136
+ headers['Authorization'] = `Bearer ${token}`;
137
+ localStorage.setItem('last_jwt', token);
138
+ }
139
+
140
+ const options = { method, headers };
141
+ if (body) options.body = JSON.stringify(body);
142
+
143
+ const startTime = performance.now();
144
+ try {
145
+ const response = await fetch(`${API_BASE}${endpoint}`, options);
146
+ const duration = Math.round(performance.now() - startTime);
147
+
148
+ let data;
149
+ const contentType = response.headers.get('content-type');
150
+ if (contentType && contentType.includes('application/json')) {
151
+ data = await response.json();
152
+ } else {
153
+ data = { text: await response.text() };
154
+ }
155
+
156
+ const responseEl = document.getElementById('response-container');
157
+ if (responseEl) {
158
+ responseEl.innerHTML = `<div class="flex items-center justify-between mb-2">
159
+ <span class="text-xs font-mono ${response.ok ? 'text-green-400' : 'text-red-400'}">
160
+ STATUS: ${response.status} ${response.statusText} • ${duration}ms
161
+ </span>
162
+ <button onclick="copyToClipboard('response-pre')" class="text-xs text-slate-500 hover:text-white">Copy</button>
163
+ </div>
164
+ <pre id="response-pre" class="language-json"><code>${JSON.stringify(data, null, 2)}</code></pre>`;
165
+ Prism.highlightElement(responseEl.querySelector('code'));
166
+ }
167
+
168
+ // Add to history
169
+ addToHistory({ endpoint, method, status: response.status, timestamp: new Date().toISOString() });
170
+
171
+ if (response.ok) showToast('Request successful!');
172
+ else showToast(data.error || 'Request failed', 'error');
173
+
174
+ } catch (error) {
175
+ showToast(error.message, 'error');
176
+ }
177
+ }
178
+
179
+ function addToHistory(item) {
180
+ history.unshift(item);
181
+ history = history.slice(0, 10);
182
+ localStorage.setItem('api_history', JSON.stringify(history));
183
+ }
184
+
185
+ function renderSidebar() {
186
+ const sidebar = document.getElementById('endpoint-sidebar');
187
+ const searchValue = document.getElementById('sidebar-search').value.toLowerCase();
188
+ sidebar.innerHTML = '';
189
+
190
+ ENDPOINT_REGISTRY.forEach(group => {
191
+ const filteredEndpoints = group.endpoints.filter(ep =>
192
+ ep.path.toLowerCase().includes(searchValue) ||
193
+ group.title.toLowerCase().includes(searchValue)
194
+ );
195
+
196
+ if (filteredEndpoints.length === 0) return;
197
+
198
+ const groupDiv = document.createElement('div');
199
+ groupDiv.className = 'group-container';
200
+
201
+ const header = document.createElement('button');
202
+ header.className = 'w-full flex items-center justify-between px-3 py-2 text-xs font-bold text-slate-500 uppercase tracking-wider hover:text-slate-300 transition-colors';
203
+ header.innerHTML = `<span>${group.title}</span><span class="caret">▸</span>`;
204
+
205
+ const content = document.createElement('div');
206
+ content.className = 'sidebar-group-content space-y-0.5 mt-1';
207
+ content.dataset.collapsed = searchValue ? 'false' : 'true';
208
+
209
+ header.onclick = () => {
210
+ const isCollapsed = content.dataset.collapsed === 'true';
211
+ content.dataset.collapsed = !isCollapsed;
212
+ header.querySelector('.caret').classList.toggle('down', isCollapsed);
213
+ };
214
+
215
+ filteredEndpoints.forEach(ep => {
216
+ const btn = document.createElement('button');
217
+ btn.className = 'w-full text-left px-3 py-2 rounded-md text-sm text-slate-400 hover:bg-slate-800 hover:text-white transition-all flex items-center gap-3 group';
218
+ btn.innerHTML = `
219
+ <span class="text-[10px] font-bold px-1.5 py-0.5 rounded min-w-[45px] text-center ${getMethodColor(ep.method)}">${ep.method}</span>
220
+ <span class="truncate">${ep.path.replace('/api/admin', '')}</span>
221
+ `;
222
+ btn.onclick = () => showEndpoint(group, ep);
223
+ content.appendChild(btn);
224
+ });
225
+
226
+ groupDiv.appendChild(header);
227
+ groupDiv.appendChild(content);
228
+ sidebar.appendChild(groupDiv);
229
+
230
+ if (searchValue) header.querySelector('.caret').classList.add('down');
231
+ });
232
+ }
233
+
234
+ function getMethodColor(method) {
235
+ switch(method) {
236
+ case 'GET': return 'bg-blue-500/10 text-blue-400 border border-blue-500/20';
237
+ case 'POST': return 'bg-green-500/10 text-green-400 border border-green-500/20';
238
+ case 'PUT': return 'bg-yellow-500/10 text-yellow-400 border border-yellow-500/20';
239
+ case 'DELETE': return 'bg-red-500/10 text-red-400 border border-red-500/20';
240
+ default: return 'bg-slate-500/10 text-slate-400';
241
+ }
242
+ }
243
+
244
+ function showEndpoint(group, ep) {
245
+ document.getElementById('breadcrumb').textContent = `${group.title} / ${ep.path}`;
246
+ const content = document.getElementById('content');
247
+
248
+ const pathParams = (ep.path.match(/:[^\/]+/g) || []).map(p => p.substring(1));
249
+
250
+ content.innerHTML = `
251
+ <div class="bg-slate-900 rounded-xl border border-slate-800 overflow-hidden">
252
+ <div class="p-6 border-b border-slate-800 bg-slate-800/30 flex items-center justify-between">
253
+ <div class="flex items-center gap-4">
254
+ <span class="px-3 py-1 rounded-md font-bold text-sm ${getMethodColor(ep.method)}">${ep.method}</span>
255
+ <h2 class="text-xl font-mono font-bold">${ep.path}</h2>
256
+ </div>
257
+ <div class="flex items-center gap-2">
258
+ <button onclick="copyCurl()" class="text-xs px-3 py-1.5 bg-slate-800 rounded hover:bg-slate-700 transition">Copy cURL</button>
259
+ </div>
260
+ </div>
261
+
262
+ <div class="p-6 grid grid-cols-1 md:grid-cols-2 gap-8">
263
+ <div class="space-y-6">
264
+ <form id="endpoint-form" class="space-y-4">
265
+ ${ep.auth === 'jwt' ? `
266
+ <div class="space-y-2">
267
+ <label class="block text-xs font-bold text-slate-500 uppercase">JWT Authorization</label>
268
+ <textarea id="jwt-input" class="w-full bg-slate-950 border border-slate-800 rounded-lg px-3 py-2 text-sm font-mono focus:ring-2 focus:ring-blue-500 outline-none" rows="2" placeholder="Paste Bearer token here...">${localStorage.getItem('last_jwt') || ''}</textarea>
269
+ </div>
270
+ ` : ''}
271
+
272
+ ${pathParams.map(p => `
273
+ <div class="space-y-2">
274
+ <label class="block text-xs font-bold text-slate-500 uppercase">Path Parameter: ${p}</label>
275
+ <input type="text" id="param-${p}" class="w-full bg-slate-950 border border-slate-800 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none" required>
276
+ </div>
277
+ `).join('')}
278
+
279
+ ${ep.method !== 'GET' && ep.method !== 'DELETE' ? `
280
+ <div class="space-y-2">
281
+ <label class="block text-xs font-bold text-slate-500 uppercase">JSON Request Body</label>
282
+ <textarea id="json-body" class="w-full bg-slate-950 border border-slate-800 rounded-lg px-3 py-2 text-sm font-mono focus:ring-2 focus:ring-blue-500 outline-none custom-scrollbar" rows="10">${JSON.stringify(ep.bodyExample || {}, null, 2)}</textarea>
283
+ </div>
284
+ ` : ''}
285
+
286
+ <button type="submit" class="w-full py-3 bg-blue-600 hover:bg-blue-500 text-white font-bold rounded-lg transition-all transform active:scale-95 shadow-lg shadow-blue-900/20">
287
+ Execute Request
288
+ </button>
289
+ </form>
290
+ </div>
291
+
292
+ <div class="space-y-4">
293
+ <label class="block text-xs font-bold text-slate-500 uppercase">Response</label>
294
+ <div id="response-container" class="bg-slate-950 rounded-lg border border-slate-800 p-4 min-h-[400px] font-mono text-sm overflow-hidden custom-scrollbar">
295
+ <div class="flex items-center justify-center h-full text-slate-700 italic">
296
+ Run request to see response...
297
+ </div>
298
+ </div>
299
+ </div>
300
+ </div>
301
+ </div>
302
+ `;
303
+
304
+ const form = document.getElementById('endpoint-form');
305
+ form.onsubmit = async (e) => {
306
+ e.preventDefault();
307
+
308
+ let resolvedPath = ep.path;
309
+ pathParams.forEach(p => {
310
+ const val = document.getElementById(`param-${p}`)?.value;
311
+ resolvedPath = resolvedPath.replace(`:${p}`, encodeURIComponent(val));
312
+ });
313
+
314
+ let body = null;
315
+ if (ep.method !== 'GET' && ep.method !== 'DELETE') {
316
+ try {
317
+ body = JSON.parse(document.getElementById('json-body').value);
318
+ } catch(err) {
319
+ showToast('Invalid JSON in request body', 'error');
320
+ return;
321
+ }
322
+ }
323
+
324
+ makeRequest(resolvedPath, ep.method, body, ep.auth);
325
+ };
326
+ }
327
+
328
+ async function copyToClipboard(elementId) {
329
+ const text = document.getElementById(elementId).textContent;
330
+ await navigator.clipboard.writeText(text);
331
+ showToast('Copied to clipboard');
332
+ }
333
+
334
+ document.getElementById('sidebar-search').oninput = renderSidebar;
335
+ document.getElementById('back-to-admin').onclick = (e) => {
336
+ e.preventDefault();
337
+ window.location.href = ADMIN_BASE;
338
+ };
339
+ renderSidebar();
340
+ </script>
341
+ </body>
342
+ </html>