@intranefr/superbackend 1.4.4 → 1.5.1

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 (195) hide show
  1. package/.env.example +5 -0
  2. package/README.md +11 -0
  3. package/index.js +39 -1
  4. package/package.json +11 -3
  5. package/public/sdk/ui-components.iife.js +191 -0
  6. package/sdk/ui-components/browser/src/index.js +228 -0
  7. package/src/admin/endpointRegistry.js +120 -0
  8. package/src/controllers/admin.controller.js +111 -5
  9. package/src/controllers/adminBlockDefinitions.controller.js +127 -0
  10. package/src/controllers/adminBlockDefinitionsAi.controller.js +54 -0
  11. package/src/controllers/adminCache.controller.js +342 -0
  12. package/src/controllers/adminContextBlockDefinitions.controller.js +141 -0
  13. package/src/controllers/adminCrons.controller.js +388 -0
  14. package/src/controllers/adminDbBrowser.controller.js +124 -0
  15. package/src/controllers/adminEjsVirtual.controller.js +13 -3
  16. package/src/controllers/adminHeadless.controller.js +91 -2
  17. package/src/controllers/adminHealthChecks.controller.js +570 -0
  18. package/src/controllers/adminI18n.controller.js +51 -29
  19. package/src/controllers/adminLlm.controller.js +126 -2
  20. package/src/controllers/adminPages.controller.js +720 -0
  21. package/src/controllers/adminPagesContextBlocksAi.controller.js +54 -0
  22. package/src/controllers/adminProxy.controller.js +113 -0
  23. package/src/controllers/adminRateLimits.controller.js +138 -0
  24. package/src/controllers/adminRbac.controller.js +803 -0
  25. package/src/controllers/adminScripts.controller.js +320 -0
  26. package/src/controllers/adminSeoConfig.controller.js +71 -48
  27. package/src/controllers/adminTerminals.controller.js +39 -0
  28. package/src/controllers/adminUiComponents.controller.js +315 -0
  29. package/src/controllers/adminUiComponentsAi.controller.js +34 -0
  30. package/src/controllers/blogAdmin.controller.js +279 -0
  31. package/src/controllers/blogAiAdmin.controller.js +224 -0
  32. package/src/controllers/blogAutomationAdmin.controller.js +141 -0
  33. package/src/controllers/blogInternal.controller.js +26 -0
  34. package/src/controllers/blogPublic.controller.js +89 -0
  35. package/src/controllers/fileManager.controller.js +190 -0
  36. package/src/controllers/fileManagerStoragePolicy.controller.js +23 -0
  37. package/src/controllers/healthChecksPublic.controller.js +196 -0
  38. package/src/controllers/metrics.controller.js +64 -4
  39. package/src/controllers/orgAdmin.controller.js +366 -0
  40. package/src/controllers/uiComponentsPublic.controller.js +118 -0
  41. package/src/middleware/auth.js +7 -0
  42. package/src/middleware/internalCronAuth.js +29 -0
  43. package/src/middleware/rbac.js +62 -0
  44. package/src/middleware.js +879 -56
  45. package/src/models/BlockDefinition.js +27 -0
  46. package/src/models/BlogAutomationLock.js +14 -0
  47. package/src/models/BlogAutomationRun.js +39 -0
  48. package/src/models/BlogPost.js +42 -0
  49. package/src/models/CacheEntry.js +26 -0
  50. package/src/models/ConsoleEntry.js +32 -0
  51. package/src/models/ConsoleLog.js +23 -0
  52. package/src/models/ContextBlockDefinition.js +33 -0
  53. package/src/models/CronExecution.js +47 -0
  54. package/src/models/CronJob.js +70 -0
  55. package/src/models/ExternalDbConnection.js +49 -0
  56. package/src/models/FileEntry.js +22 -0
  57. package/src/models/HeadlessModelDefinition.js +10 -0
  58. package/src/models/HealthAutoHealAttempt.js +57 -0
  59. package/src/models/HealthCheck.js +132 -0
  60. package/src/models/HealthCheckRun.js +51 -0
  61. package/src/models/HealthIncident.js +49 -0
  62. package/src/models/Page.js +95 -0
  63. package/src/models/PageCollection.js +42 -0
  64. package/src/models/ProxyEntry.js +66 -0
  65. package/src/models/RateLimitCounter.js +19 -0
  66. package/src/models/RateLimitMetricBucket.js +20 -0
  67. package/src/models/RbacGrant.js +25 -0
  68. package/src/models/RbacGroup.js +16 -0
  69. package/src/models/RbacGroupMember.js +13 -0
  70. package/src/models/RbacGroupRole.js +13 -0
  71. package/src/models/RbacRole.js +25 -0
  72. package/src/models/RbacUserRole.js +13 -0
  73. package/src/models/ScriptDefinition.js +42 -0
  74. package/src/models/ScriptRun.js +22 -0
  75. package/src/models/UiComponent.js +29 -0
  76. package/src/models/UiComponentProject.js +26 -0
  77. package/src/models/UiComponentProjectComponent.js +18 -0
  78. package/src/routes/admin.routes.js +1 -0
  79. package/src/routes/adminBlog.routes.js +21 -0
  80. package/src/routes/adminBlogAi.routes.js +16 -0
  81. package/src/routes/adminBlogAutomation.routes.js +27 -0
  82. package/src/routes/adminCache.routes.js +20 -0
  83. package/src/routes/adminConsoleManager.routes.js +302 -0
  84. package/src/routes/adminCrons.routes.js +25 -0
  85. package/src/routes/adminDbBrowser.routes.js +65 -0
  86. package/src/routes/adminEjsVirtual.routes.js +2 -1
  87. package/src/routes/adminHeadless.routes.js +8 -1
  88. package/src/routes/adminHealthChecks.routes.js +28 -0
  89. package/src/routes/adminI18n.routes.js +4 -3
  90. package/src/routes/adminLlm.routes.js +4 -2
  91. package/src/routes/adminPages.routes.js +55 -0
  92. package/src/routes/adminProxy.routes.js +15 -0
  93. package/src/routes/adminRateLimits.routes.js +17 -0
  94. package/src/routes/adminRbac.routes.js +38 -0
  95. package/src/routes/adminScripts.routes.js +21 -0
  96. package/src/routes/adminSeoConfig.routes.js +5 -4
  97. package/src/routes/adminTerminals.routes.js +13 -0
  98. package/src/routes/adminUiComponents.routes.js +30 -0
  99. package/src/routes/blogInternal.routes.js +14 -0
  100. package/src/routes/blogPublic.routes.js +9 -0
  101. package/src/routes/fileManager.routes.js +62 -0
  102. package/src/routes/fileManagerStoragePolicy.routes.js +9 -0
  103. package/src/routes/healthChecksPublic.routes.js +9 -0
  104. package/src/routes/log.routes.js +43 -60
  105. package/src/routes/metrics.routes.js +4 -2
  106. package/src/routes/orgAdmin.routes.js +6 -0
  107. package/src/routes/pages.routes.js +123 -0
  108. package/src/routes/proxy.routes.js +46 -0
  109. package/src/routes/rbac.routes.js +47 -0
  110. package/src/routes/uiComponentsPublic.routes.js +9 -0
  111. package/src/routes/webhook.routes.js +2 -1
  112. package/src/routes/workflows.routes.js +4 -0
  113. package/src/services/blockDefinitionsAi.service.js +247 -0
  114. package/src/services/blog.service.js +99 -0
  115. package/src/services/blogAutomation.service.js +978 -0
  116. package/src/services/blogCronsBootstrap.service.js +184 -0
  117. package/src/services/blogPublishing.service.js +58 -0
  118. package/src/services/cacheLayer.service.js +696 -0
  119. package/src/services/consoleManager.service.js +700 -0
  120. package/src/services/consoleOverride.service.js +6 -1
  121. package/src/services/cronScheduler.service.js +350 -0
  122. package/src/services/dbBrowser.service.js +536 -0
  123. package/src/services/ejsVirtual.service.js +102 -32
  124. package/src/services/fileManager.service.js +475 -0
  125. package/src/services/fileManagerStoragePolicy.service.js +285 -0
  126. package/src/services/headlessExternalModels.service.js +292 -0
  127. package/src/services/headlessModels.service.js +26 -6
  128. package/src/services/healthChecks.service.js +650 -0
  129. package/src/services/healthChecksBootstrap.service.js +109 -0
  130. package/src/services/healthChecksScheduler.service.js +106 -0
  131. package/src/services/llmDefaults.service.js +190 -0
  132. package/src/services/migrationAssets/s3.js +2 -2
  133. package/src/services/pages.service.js +602 -0
  134. package/src/services/pagesContext.service.js +331 -0
  135. package/src/services/pagesContextBlocksAi.service.js +349 -0
  136. package/src/services/proxy.service.js +535 -0
  137. package/src/services/rateLimiter.service.js +623 -0
  138. package/src/services/rbac.service.js +212 -0
  139. package/src/services/scriptsRunner.service.js +259 -0
  140. package/src/services/terminals.service.js +152 -0
  141. package/src/services/terminalsWs.service.js +100 -0
  142. package/src/services/uiComponentsAi.service.js +299 -0
  143. package/src/services/uiComponentsCrypto.service.js +39 -0
  144. package/src/services/workflow.service.js +23 -8
  145. package/src/utils/orgRoles.js +14 -0
  146. package/src/utils/rbac/engine.js +60 -0
  147. package/src/utils/rbac/rightsRegistry.js +29 -0
  148. package/views/admin-blog-automation.ejs +877 -0
  149. package/views/admin-blog-edit.ejs +542 -0
  150. package/views/admin-blog.ejs +399 -0
  151. package/views/admin-cache.ejs +681 -0
  152. package/views/admin-console-manager.ejs +680 -0
  153. package/views/admin-crons.ejs +645 -0
  154. package/views/admin-db-browser.ejs +445 -0
  155. package/views/admin-ejs-virtual.ejs +16 -10
  156. package/views/admin-file-manager.ejs +942 -0
  157. package/views/admin-headless.ejs +294 -24
  158. package/views/admin-health-checks.ejs +725 -0
  159. package/views/admin-i18n.ejs +59 -5
  160. package/views/admin-llm.ejs +99 -1
  161. package/views/admin-organizations.ejs +528 -10
  162. package/views/admin-pages.ejs +2424 -0
  163. package/views/admin-proxy.ejs +491 -0
  164. package/views/admin-rate-limiter.ejs +625 -0
  165. package/views/admin-rbac.ejs +1331 -0
  166. package/views/admin-scripts.ejs +497 -0
  167. package/views/admin-seo-config.ejs +61 -7
  168. package/views/admin-terminals.ejs +328 -0
  169. package/views/admin-ui-components.ejs +741 -0
  170. package/views/admin-users.ejs +261 -4
  171. package/views/admin-workflows.ejs +7 -7
  172. package/views/file-manager.ejs +866 -0
  173. package/views/pages/blocks/contact.ejs +27 -0
  174. package/views/pages/blocks/cta.ejs +18 -0
  175. package/views/pages/blocks/faq.ejs +20 -0
  176. package/views/pages/blocks/features.ejs +19 -0
  177. package/views/pages/blocks/hero.ejs +13 -0
  178. package/views/pages/blocks/html.ejs +5 -0
  179. package/views/pages/blocks/image.ejs +14 -0
  180. package/views/pages/blocks/testimonials.ejs +26 -0
  181. package/views/pages/blocks/text.ejs +10 -0
  182. package/views/pages/layouts/default.ejs +51 -0
  183. package/views/pages/layouts/minimal.ejs +42 -0
  184. package/views/pages/layouts/sidebar.ejs +54 -0
  185. package/views/pages/partials/footer.ejs +13 -0
  186. package/views/pages/partials/header.ejs +12 -0
  187. package/views/pages/partials/sidebar.ejs +8 -0
  188. package/views/pages/runtime/page.ejs +10 -0
  189. package/views/pages/templates/article.ejs +20 -0
  190. package/views/pages/templates/default.ejs +12 -0
  191. package/views/pages/templates/landing.ejs +14 -0
  192. package/views/pages/templates/listing.ejs +15 -0
  193. package/views/partials/admin-image-upload-modal.ejs +221 -0
  194. package/views/partials/dashboard/nav-items.ejs +14 -0
  195. package/views/partials/llm-provider-model-picker.ejs +183 -0
@@ -0,0 +1,328 @@
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>Admin Terminals</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://unpkg.com/xterm@latest/css/xterm.css" />
9
+ <style>
10
+ /* Fullscreen styles */
11
+ :fullscreen .max-w-7xl {
12
+ max-width: 100vw;
13
+ width: 100vw;
14
+ height: 100vh;
15
+ padding: 0;
16
+ display: flex;
17
+ flex-direction: column;
18
+ }
19
+ :fullscreen .bg-white {
20
+ flex: 1;
21
+ display: flex;
22
+ flex-direction: column;
23
+ border: none;
24
+ border-radius: 0;
25
+ }
26
+ :fullscreen #terminal-container {
27
+ flex: 1;
28
+ height: auto !important;
29
+ }
30
+ :fullscreen .h-\[70vh\] {
31
+ height: 100% !important;
32
+ }
33
+ </style>
34
+ </head>
35
+ <body class="bg-gray-50">
36
+ <div class="max-w-7xl mx-auto px-6 py-6">
37
+ <div class="flex items-center justify-between mb-4">
38
+ <div>
39
+ <h1 class="text-2xl font-semibold text-gray-900">Terminals</h1>
40
+ <div class="text-sm text-gray-500">Interactive shell sessions on the backend host</div>
41
+ </div>
42
+ <div class="flex items-center gap-2">
43
+ <button id="btn-new" class="px-3 py-2 rounded bg-blue-600 text-white text-sm hover:bg-blue-700">New</button>
44
+ <button id="btn-close" class="px-3 py-2 rounded bg-red-600 text-white text-sm hover:bg-red-700">Close</button>
45
+ <button id="btn-fullscreen" class="px-3 py-2 rounded bg-gray-600 text-white text-sm hover:bg-gray-700">Fullscreen</button>
46
+ </div>
47
+ </div>
48
+
49
+ <div class="bg-white border border-gray-200 rounded-lg">
50
+ <div class="border-b border-gray-200 flex items-center justify-between px-3 py-2">
51
+ <div id="tabs" class="flex items-center gap-2 overflow-auto"></div>
52
+ <div class="text-xs text-gray-500 whitespace-nowrap">
53
+ Ctrl+Shift+T new · Ctrl+Shift+W close · Ctrl+Tab next · Ctrl+Shift+Tab prev · Alt+1..9 switch
54
+ </div>
55
+ </div>
56
+ <div id="terminal-container" class="h-[70vh]"></div>
57
+ </div>
58
+ </div>
59
+
60
+ <script src="https://unpkg.com/xterm@latest/lib/xterm.js"></script>
61
+
62
+ <script>
63
+ window.BASE_URL = '<%= baseUrl %>';
64
+ window.ADMIN_PATH = '<%= adminPath %>';
65
+
66
+ const state = {
67
+ sessions: [],
68
+ activeId: null,
69
+ };
70
+
71
+ function baseWsUrl() {
72
+ const base = window.BASE_URL || '';
73
+ const u = new URL(base || window.location.origin);
74
+ const proto = u.protocol === 'https:' ? 'wss:' : 'ws:';
75
+ return proto + '//' + u.host + u.pathname.replace(/\/$/, '');
76
+ }
77
+
78
+ function apiUrl(path) {
79
+ return (window.BASE_URL || '') + path;
80
+ }
81
+
82
+ async function api(path, opts) {
83
+ const res = await fetch(apiUrl(path), {
84
+ credentials: 'same-origin',
85
+ headers: { 'Content-Type': 'application/json' },
86
+ ...opts,
87
+ });
88
+ const json = await res.json().catch(() => ({}));
89
+ if (!res.ok) throw new Error(json.error || 'Request failed');
90
+ return json;
91
+ }
92
+
93
+ function qs(id) { return document.getElementById(id); }
94
+
95
+ function renderTabs() {
96
+ const tabs = qs('tabs');
97
+ tabs.innerHTML = '';
98
+ state.sessions.forEach((s, idx) => {
99
+ const btn = document.createElement('button');
100
+ const active = s.sessionId === state.activeId;
101
+ btn.className = `px-3 py-1.5 rounded text-sm border ${active ? 'bg-blue-50 border-blue-200 text-blue-800' : 'bg-white border-gray-200 text-gray-700 hover:bg-gray-50'}`;
102
+ btn.textContent = `#${idx + 1} ${s.sessionId.slice(0, 6)}`;
103
+ btn.addEventListener('click', () => activateSession(s.sessionId));
104
+ tabs.appendChild(btn);
105
+ });
106
+ }
107
+
108
+ function destroySessionClient(s) {
109
+ try { if (s.ws) s.ws.close(); } catch {}
110
+ try { if (s.term) s.term.dispose(); } catch {}
111
+ s.ws = null;
112
+ s.term = null;
113
+ }
114
+
115
+ function activateSession(sessionId) {
116
+ state.activeId = sessionId;
117
+ renderTabs();
118
+
119
+ const container = qs('terminal-container');
120
+ container.innerHTML = '';
121
+
122
+ const s = state.sessions.find((x) => x.sessionId === sessionId);
123
+ if (!s) return;
124
+
125
+ container.appendChild(s.termElement);
126
+ // Only open the terminal if it hasn't been opened yet
127
+ if (!s.term._opened) {
128
+ s.term.open(s.termElement);
129
+ s.term._opened = true;
130
+ }
131
+ setTimeout(() => resizeActive(), 0);
132
+ }
133
+
134
+ function resizeActive() {
135
+ const s = state.sessions.find((x) => x.sessionId === state.activeId);
136
+ if (!s || !s.term || !s.ws) return;
137
+
138
+ const el = s.termElement;
139
+ const container = el.parentElement;
140
+ if (!container) return;
141
+
142
+ const cols = Math.max(40, Math.floor(container.clientWidth / 9));
143
+ const rows = Math.max(10, Math.floor(container.clientHeight / 18));
144
+
145
+ try {
146
+ s.term.resize(cols, rows);
147
+ } catch {}
148
+
149
+ try {
150
+ s.ws.send(JSON.stringify({ type: 'resize', cols, rows }));
151
+ } catch {}
152
+ }
153
+
154
+ async function newTerminal() {
155
+ return new Promise((resolve, reject) => {
156
+ ensureTerminal(() => {
157
+ (async () => {
158
+ try {
159
+ const created = await api('/api/admin/terminals/sessions', { method: 'POST', body: JSON.stringify({ cols: 120, rows: 30 }) });
160
+ const sessionId = created.sessionId;
161
+
162
+ const term = new Terminal({
163
+ cursorBlink: true,
164
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
165
+ fontSize: 13,
166
+ theme: { background: '#0b1020' },
167
+ });
168
+
169
+ const termElement = document.createElement('div');
170
+ termElement.className = 'w-full h-full';
171
+
172
+ const ws = new WebSocket(baseWsUrl() + '/api/admin/terminals/ws?sessionId=' + encodeURIComponent(sessionId));
173
+
174
+ const s = { sessionId, term, ws, termElement };
175
+ state.sessions.push(s);
176
+ state.activeId = sessionId;
177
+ renderTabs();
178
+
179
+ ws.onopen = () => {
180
+ activateSession(sessionId);
181
+ };
182
+
183
+ ws.onmessage = (ev) => {
184
+ let msg;
185
+ try { msg = JSON.parse(String(ev.data || '')); } catch { return; }
186
+ if (!msg || typeof msg !== 'object') return;
187
+
188
+ if (msg.type === 'output') {
189
+ term.write(msg.data || '');
190
+ }
191
+ };
192
+
193
+ ws.onclose = () => {
194
+ term.write('\r\n[disconnected]\r\n');
195
+ };
196
+
197
+ term.onData((data) => {
198
+ try {
199
+ ws.send(JSON.stringify({ type: 'input', data }));
200
+ } catch {}
201
+ });
202
+
203
+ window.addEventListener('resize', () => {
204
+ if (state.activeId === sessionId) resizeActive();
205
+ });
206
+
207
+ resolve();
208
+ } catch (e) {
209
+ reject(e);
210
+ }
211
+ })();
212
+ });
213
+ });
214
+ }
215
+
216
+ async function closeActive() {
217
+ const sessionId = state.activeId;
218
+ if (!sessionId) return;
219
+
220
+ const idx = state.sessions.findIndex((x) => x.sessionId === sessionId);
221
+ if (idx === -1) return;
222
+
223
+ const s = state.sessions[idx];
224
+ destroySessionClient(s);
225
+
226
+ state.sessions.splice(idx, 1);
227
+ state.activeId = state.sessions[idx - 1]?.sessionId || state.sessions[0]?.sessionId || null;
228
+ renderTabs();
229
+
230
+ qs('terminal-container').innerHTML = '';
231
+ if (state.activeId) activateSession(state.activeId);
232
+
233
+ try {
234
+ await api('/api/admin/terminals/sessions/' + encodeURIComponent(sessionId), { method: 'DELETE' });
235
+ } catch {}
236
+ }
237
+
238
+ function nextTab(dir) {
239
+ if (!state.sessions.length) return;
240
+ const idx = state.sessions.findIndex((x) => x.sessionId === state.activeId);
241
+ if (idx === -1) return;
242
+ const next = (idx + dir + state.sessions.length) % state.sessions.length;
243
+ activateSession(state.sessions[next].sessionId);
244
+ }
245
+
246
+ function switchToIndex(n) {
247
+ const idx = n - 1;
248
+ if (idx < 0 || idx >= state.sessions.length) return;
249
+ activateSession(state.sessions[idx].sessionId);
250
+ }
251
+
252
+ document.addEventListener('keydown', (e) => {
253
+ const key = e.key;
254
+
255
+ if (e.ctrlKey && e.shiftKey && (key === 'T' || key === 't')) {
256
+ e.preventDefault();
257
+ newTerminal();
258
+ }
259
+
260
+ if (e.ctrlKey && e.shiftKey && (key === 'W' || key === 'w')) {
261
+ e.preventDefault();
262
+ closeActive();
263
+ }
264
+
265
+ if (e.ctrlKey && !e.shiftKey && key === 'Tab') {
266
+ e.preventDefault();
267
+ nextTab(1);
268
+ }
269
+
270
+ if (e.ctrlKey && e.shiftKey && key === 'Tab') {
271
+ e.preventDefault();
272
+ nextTab(-1);
273
+ }
274
+
275
+ if (e.altKey && /^[1-9]$/.test(key)) {
276
+ e.preventDefault();
277
+ switchToIndex(Number(key));
278
+ }
279
+ });
280
+
281
+ qs('btn-new').addEventListener('click', () => newTerminal());
282
+ qs('btn-close').addEventListener('click', () => closeActive());
283
+
284
+ (async function init() {
285
+ const listed = await api('/api/admin/terminals/sessions');
286
+ const items = listed.items || [];
287
+ if (items.length) {
288
+ for (const it of items.slice(0, 1)) {
289
+ await newTerminal();
290
+ }
291
+ }
292
+ })();
293
+
294
+ function toggleFullscreen() {
295
+ if (!document.fullscreenElement) {
296
+ document.documentElement.requestFullscreen().catch(() => {});
297
+ } else {
298
+ document.exitFullscreen().catch(() => {});
299
+ }
300
+ }
301
+
302
+ document.addEventListener('fullscreenchange', () => {
303
+ const btn = qs('btn-fullscreen');
304
+ if (btn) {
305
+ btn.textContent = document.fullscreenElement ? 'Exit Fullscreen' : 'Fullscreen';
306
+ }
307
+ });
308
+
309
+ qs('btn-fullscreen').addEventListener('click', toggleFullscreen);
310
+
311
+ document.addEventListener('keydown', (e) => {
312
+ if (e.ctrlKey && e.shiftKey && e.key === 'F') {
313
+ e.preventDefault();
314
+ toggleFullscreen();
315
+ }
316
+ });
317
+
318
+ // Ensure Terminal is available (wait for script load if needed)
319
+ function ensureTerminal(cb) {
320
+ if (typeof Terminal !== 'undefined') {
321
+ cb();
322
+ } else {
323
+ setTimeout(() => ensureTerminal(cb), 50);
324
+ }
325
+ }
326
+ </script>
327
+ </body>
328
+ </html>