@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
package/src/middleware.js CHANGED
@@ -1,3 +1,12 @@
1
+ const consoleOverride = require("./services/consoleOverride.service");
2
+ const consoleManager = require("./services/consoleManager.service");
3
+
4
+ // Initialize console override service early to capture all logs
5
+ // Avoid keeping timers/streams alive during Jest runs.
6
+ if (process.env.NODE_ENV !== "test" && !process.env.JEST_WORKER_ID) {
7
+ consoleOverride.init()
8
+ }
9
+
1
10
  const express = require("express");
2
11
  const path = require("path");
3
12
  const mongoose = require("mongoose");
@@ -6,17 +15,54 @@ const fs = require("fs");
6
15
  const ejs = require("ejs");
7
16
  const { basicAuth } = require("./middleware/auth");
8
17
  const endpointRegistry = require("./admin/endpointRegistry");
9
- const { createFeatureFlagsEjsMiddleware } = require("./services/featureFlags.service");
10
- const consoleOverride = require("./services/consoleOverride.service");
18
+ const {
19
+ createFeatureFlagsEjsMiddleware,
20
+ } = require("./services/featureFlags.service");
21
+ const globalSettingsService = require("./services/globalSettings.service");
22
+ const cronScheduler = require("./services/cronScheduler.service");
23
+ const healthChecksScheduler = require("./services/healthChecksScheduler.service");
24
+ const healthChecksBootstrap = require("./services/healthChecksBootstrap.service");
25
+ const blogCronsBootstrap = require("./services/blogCronsBootstrap.service");
11
26
  const {
12
27
  hookConsoleError,
13
28
  setupProcessHandlers,
14
29
  expressErrorMiddleware,
15
30
  requestIdMiddleware,
16
31
  } = require("./middleware/errorCapture");
32
+ const rateLimiter = require("./services/rateLimiter.service");
17
33
 
18
34
  let errorCaptureInitialized = false;
19
35
 
36
+ /**
37
+ * Check if console manager should be enabled based on environment variable and global settings
38
+ * Priority: Environment Variable > Global Settings > Default (true)
39
+ * @returns {Promise<boolean>} Whether console manager should be enabled
40
+ */
41
+ async function isConsoleManagerEnabled() {
42
+ // Environment variable takes highest priority
43
+ const envEnabled = process.env.CONSOLE_MANAGER_ENABLED;
44
+ if (envEnabled !== undefined) {
45
+ const enabled = String(envEnabled).toLowerCase() !== 'false';
46
+ console.log(`[Console Manager] Environment variable CONSOLE_MANAGER_ENABLED=${envEnabled}, ${enabled ? 'enabled' : 'disabled'}`);
47
+ return enabled;
48
+ }
49
+
50
+ // Check global settings if environment variable not set
51
+ try {
52
+ const enabledRaw = await globalSettingsService.getSettingValue(
53
+ "CONSOLE_MANAGER_ENABLED",
54
+ "true"
55
+ );
56
+ const enabled = String(enabledRaw) === "true";
57
+ console.log(`[Console Manager] Global setting CONSOLE_MANAGER_ENABLED=${enabledRaw}, ${enabled ? 'enabled' : 'disabled'}`);
58
+ return enabled;
59
+ } catch (error) {
60
+ console.error("[Console Manager] Error loading global setting:", error);
61
+ console.log("[Console Manager] Fallback to enabled due to error");
62
+ return true; // Fallback to enabled on error
63
+ }
64
+ }
65
+
20
66
  /**
21
67
  * Creates and configures the SaaS backend middleware
22
68
  * @param {Object} options - Configuration options
@@ -30,9 +76,50 @@ let errorCaptureInitialized = false;
30
76
  function createMiddleware(options = {}) {
31
77
  const router = express.Router();
32
78
  const adminPath = options.adminPath || "/admin";
79
+ const pagesPrefix = options.pagesPrefix || "/";
80
+
81
+ const normalizeBasePath = (value) => {
82
+ const v = String(value || "").trim();
83
+ if (!v) return "/files";
84
+ return v.startsWith("/") ? v : `/${v}`;
85
+ };
86
+
87
+ const fileManagerPublicConfig = {
88
+ enabled: false,
89
+ basePath: "/files",
90
+ loaded: false,
91
+ };
92
+
93
+ // Restart-required behavior: we load settings once and keep the values in memory.
94
+ (async () => {
95
+ try {
96
+ const enabledRaw = await globalSettingsService.getSettingValue(
97
+ "FILE_MANAGER_ENABLED",
98
+ "false",
99
+ );
100
+ const basePathRaw = await globalSettingsService.getSettingValue(
101
+ "FILE_MANAGER_BASE_PATH",
102
+ "/files",
103
+ );
33
104
 
34
- // Initialize console override service early to capture all logs
35
- consoleOverride.init();
105
+ fileManagerPublicConfig.enabled = String(enabledRaw) === "true";
106
+ fileManagerPublicConfig.basePath = normalizeBasePath(basePathRaw);
107
+ fileManagerPublicConfig.loaded = true;
108
+ } catch (error) {
109
+ console.error("Error loading File Manager public config:", error);
110
+ fileManagerPublicConfig.loaded = true;
111
+ }
112
+ })();
113
+
114
+ // Expose adminPath, pagesPrefix and WS attachment helper
115
+ router.adminPath = adminPath;
116
+ router.pagesPrefix = pagesPrefix;
117
+ router.attachWs = (server) => {
118
+ const {
119
+ attachTerminalWebsocketServer,
120
+ } = require("./services/terminalsWs.service");
121
+ attachTerminalWebsocketServer(server, { basePathPrefix: adminPath });
122
+ };
36
123
 
37
124
  if (!errorCaptureInitialized) {
38
125
  errorCaptureInitialized = true;
@@ -40,9 +127,14 @@ function createMiddleware(options = {}) {
40
127
  setupProcessHandlers();
41
128
  }
42
129
 
130
+ // Console manager will be initialized after database connection
131
+
43
132
  // Database connection
44
133
  const mongoUri =
45
- options.mongodbUri || options.dbConnection || process.env.MONGODB_URI || process.env.MONGO_URI;
134
+ options.mongodbUri ||
135
+ options.dbConnection ||
136
+ process.env.MONGODB_URI ||
137
+ process.env.MONGO_URI;
46
138
 
47
139
  if (!mongoUri && mongoose.connection.readyState !== 1) {
48
140
  console.warn(
@@ -53,23 +145,132 @@ function createMiddleware(options = {}) {
53
145
  serverSelectionTimeoutMS: 5000,
54
146
  maxPoolSize: 10,
55
147
  };
56
-
148
+
57
149
  // Return a promise that resolves when connection is established
58
150
  const connectionPromise = mongoose
59
151
  .connect(mongoUri, connectionOptions)
60
- .then(() => {
152
+ .then(async () => {
61
153
  console.log("✅ Middleware: Connected to MongoDB");
154
+ // Start cron scheduler after DB connection
155
+ await cronScheduler.start();
156
+ await healthChecksScheduler.start();
157
+ await healthChecksBootstrap.bootstrap();
158
+ await blogCronsBootstrap.bootstrap();
159
+
160
+ // Initialize console manager AFTER database is connected
161
+ if (process.env.NODE_ENV !== "test" && !process.env.JEST_WORKER_ID) {
162
+ const consoleManagerEnabled = await isConsoleManagerEnabled();
163
+ if (consoleManagerEnabled) {
164
+ consoleManager.init();
165
+ console.log("[Console Manager] Initialized");
166
+ } else {
167
+ console.log("[Console Manager] Disabled - console methods not overridden");
168
+ }
169
+ }
170
+
62
171
  return true;
63
172
  })
64
173
  .catch((err) => {
65
174
  console.error("❌ Middleware: MongoDB connection error:", err);
66
175
  return false;
67
176
  });
68
-
177
+
178
+ router.get(`${adminPath}/health-checks`, basicAuth, (req, res) => {
179
+ const templatePath = path.join(
180
+ __dirname,
181
+ "..",
182
+ "views",
183
+ "admin-health-checks.ejs",
184
+ );
185
+ fs.readFile(templatePath, "utf8", (err, template) => {
186
+ if (err) {
187
+ console.error("Error reading template:", err);
188
+ return res.status(500).send("Error loading page");
189
+ }
190
+ try {
191
+ const html = ejs.render(
192
+ template,
193
+ {
194
+ baseUrl: req.baseUrl,
195
+ adminPath,
196
+ },
197
+ {
198
+ filename: templatePath,
199
+ },
200
+ );
201
+ res.send(html);
202
+ } catch (renderErr) {
203
+ console.error("Error rendering template:", renderErr);
204
+ res.status(500).send("Error rendering page");
205
+ }
206
+ });
207
+ });
208
+
209
+ router.get(`${adminPath}/console-manager`, basicAuth, (req, res) => {
210
+ const templatePath = path.join(
211
+ __dirname,
212
+ "..",
213
+ "views",
214
+ "admin-console-manager.ejs",
215
+ );
216
+ fs.readFile(templatePath, "utf8", (err, template) => {
217
+ if (err) {
218
+ console.error("Error reading template:", err);
219
+ return res.status(500).send("Error loading page");
220
+ }
221
+ try {
222
+ const html = ejs.render(
223
+ template,
224
+ {
225
+ baseUrl: req.baseUrl,
226
+ adminPath,
227
+ },
228
+ {
229
+ filename: templatePath,
230
+ },
231
+ );
232
+ res.send(html);
233
+ } catch (renderErr) {
234
+ console.error("Error rendering template:", renderErr);
235
+ res.status(500).send("Error rendering page");
236
+ }
237
+ });
238
+ });
239
+
69
240
  // Store the promise so it can be awaited if needed
70
241
  router.connectionPromise = connectionPromise;
71
242
  } else if (mongoose.connection.readyState === 1) {
72
243
  console.log("✅ Middleware: Using existing MongoDB connection");
244
+ // Start cron scheduler for existing connection
245
+ cronScheduler.start().catch((err) => {
246
+ console.error("Failed to start cron scheduler:", err);
247
+ });
248
+ healthChecksScheduler.start().catch((err) => {
249
+ console.error("Failed to start health checks scheduler:", err);
250
+ });
251
+ healthChecksBootstrap.bootstrap().catch((err) => {
252
+ console.error("Failed to bootstrap health checks:", err);
253
+ });
254
+ blogCronsBootstrap.bootstrap().catch((err) => {
255
+ console.error("Failed to bootstrap blog crons:", err);
256
+ });
257
+
258
+ // Initialize console manager AFTER database is already connected
259
+ if (process.env.NODE_ENV !== "test" && !process.env.JEST_WORKER_ID) {
260
+ isConsoleManagerEnabled().then(consoleManagerEnabled => {
261
+ if (consoleManagerEnabled) {
262
+ consoleManager.init();
263
+ console.log("[Console Manager] Initialized");
264
+ } else {
265
+ console.log("[Console Manager] Disabled - console methods not overridden");
266
+ }
267
+ }).catch(error => {
268
+ console.error("[Console Manager] Error checking enabled status:", error);
269
+ console.log("[Console Manager] Fallback to enabled due to error");
270
+ consoleManager.init();
271
+ console.log("[Console Manager] Initialized (fallback)");
272
+ });
273
+ }
73
274
  }
74
275
 
75
276
  // CORS configuration
@@ -131,6 +332,12 @@ function createMiddleware(options = {}) {
131
332
  webhookHandler,
132
333
  );
133
334
 
335
+ router.use(
336
+ "/proxy",
337
+ express.raw({ type: "*/*", limit: "10mb" }),
338
+ require("./routes/proxy.routes"),
339
+ );
340
+
134
341
  // Regular JSON parsing for other routes (skip if parent app already handles it)
135
342
  if (!options.skipBodyParser) {
136
343
  router.use(express.json());
@@ -139,9 +346,17 @@ function createMiddleware(options = {}) {
139
346
 
140
347
  router.use(requestIdMiddleware);
141
348
 
349
+ router.use("/api", rateLimiter.limit("globalApiLimiter"));
350
+
142
351
  // Serve public static files (e.g. /og/og-default.png)
143
352
  router.use(express.static(path.join(__dirname, "..", "public")));
144
353
 
354
+ // Serve browser SDK bundles
355
+ router.use(
356
+ "/public/sdk",
357
+ express.static(path.join(__dirname, "..", "public", "sdk")),
358
+ );
359
+
145
360
  // Serve static files for admin views
146
361
  router.use(
147
362
  `${adminPath}/assets`,
@@ -151,6 +366,53 @@ function createMiddleware(options = {}) {
151
366
  // EJS locals: feature flags for server-rendered pages
152
367
  router.use(createFeatureFlagsEjsMiddleware());
153
368
 
369
+ // Public File Manager SPA (gated by global settings; restart required)
370
+ router.get("*", (req, res, next) => {
371
+ try {
372
+ if (!fileManagerPublicConfig.enabled) return next();
373
+
374
+ const basePath = fileManagerPublicConfig.basePath || "/files";
375
+ const reqPath = req.path;
376
+ const matches =
377
+ reqPath === basePath ||
378
+ reqPath === `${basePath}/` ||
379
+ reqPath.startsWith(`${basePath}/`);
380
+
381
+ if (!matches) return next();
382
+ if (req.method !== "GET") return next();
383
+
384
+ const templatePath = path.join(
385
+ __dirname,
386
+ "..",
387
+ "views",
388
+ "file-manager.ejs",
389
+ );
390
+ fs.readFile(templatePath, "utf8", (err, template) => {
391
+ if (err) {
392
+ console.error("Error reading template:", err);
393
+ return res.status(500).send("Error loading page");
394
+ }
395
+ try {
396
+ const html = ejs.render(
397
+ template,
398
+ {
399
+ baseUrl: req.baseUrl,
400
+ fileManagerBasePath: basePath,
401
+ },
402
+ { filename: templatePath },
403
+ );
404
+ res.send(html);
405
+ } catch (renderErr) {
406
+ console.error("Error rendering template:", renderErr);
407
+ res.status(500).send("Error rendering page");
408
+ }
409
+ });
410
+ } catch (error) {
411
+ console.error("Error serving File Manager SPA:", error);
412
+ next();
413
+ }
414
+ });
415
+
154
416
  // API Routes
155
417
  router.use("/api/auth", require("./routes/auth.routes"));
156
418
  router.use("/api/billing", require("./routes/billing.routes"));
@@ -164,22 +426,212 @@ function createMiddleware(options = {}) {
164
426
  );
165
427
  router.use("/api/admin/orgs", require("./routes/orgAdmin.routes"));
166
428
  router.use("/api/admin/users", require("./routes/userAdmin.routes"));
167
- router.use("/api/admin/notifications", require("./routes/notificationAdmin.routes"));
429
+ router.use("/api/admin/rbac", require("./routes/adminRbac.routes"));
430
+ router.use(
431
+ "/api/admin/notifications",
432
+ require("./routes/notificationAdmin.routes"),
433
+ );
168
434
  router.use("/api/admin/stripe", require("./routes/stripeAdmin.routes"));
169
-
435
+
170
436
  // Stats Routes
171
437
  const adminStatsController = require("./controllers/adminStats.controller");
172
- router.get("/api/admin/stats/overview", basicAuth, adminStatsController.getOverviewStats);
173
-
438
+ router.get(
439
+ "/api/admin/stats/overview",
440
+ basicAuth,
441
+ adminStatsController.getOverviewStats,
442
+ );
443
+
174
444
  router.get(`${adminPath}/stats/dashboard-home`, basicAuth, (req, res) => {
175
- const templatePath = path.join(__dirname, "..", "views", "admin-dashboard-home.ejs");
445
+ const templatePath = path.join(
446
+ __dirname,
447
+ "..",
448
+ "views",
449
+ "admin-dashboard-home.ejs",
450
+ );
451
+ fs.readFile(templatePath, "utf8", (err, template) => {
452
+ if (err) {
453
+ console.error("Error reading template:", err);
454
+ return res.status(500).send("Error loading page");
455
+ }
456
+ try {
457
+ const html = ejs.render(
458
+ template,
459
+ { baseUrl: req.baseUrl, adminPath },
460
+ { filename: templatePath },
461
+ );
462
+ res.send(html);
463
+ } catch (renderErr) {
464
+ console.error("Error rendering template:", renderErr);
465
+ res.status(500).send("Error rendering page");
466
+ }
467
+ });
468
+ });
469
+
470
+ router.get(`${adminPath}/rbac`, basicAuth, (req, res) => {
471
+ const templatePath = path.join(__dirname, "..", "views", "admin-rbac.ejs");
472
+ fs.readFile(templatePath, "utf8", (err, template) => {
473
+ if (err) {
474
+ console.error("Error reading template:", err);
475
+ return res.status(500).send("Error loading page");
476
+ }
477
+ try {
478
+ const html = ejs.render(
479
+ template,
480
+ {
481
+ baseUrl: req.baseUrl,
482
+ adminPath,
483
+ },
484
+ {
485
+ filename: templatePath,
486
+ },
487
+ );
488
+ res.send(html);
489
+ } catch (renderErr) {
490
+ console.error("Error rendering template:", renderErr);
491
+ res.status(500).send("Error rendering page");
492
+ }
493
+ });
494
+ });
495
+
496
+ router.get(`${adminPath}/terminals`, basicAuth, (req, res) => {
497
+ const templatePath = path.join(
498
+ __dirname,
499
+ "..",
500
+ "views",
501
+ "admin-terminals.ejs",
502
+ );
503
+ fs.readFile(templatePath, "utf8", (err, template) => {
504
+ if (err) {
505
+ console.error("Error reading template:", err);
506
+ return res.status(500).send("Error loading page");
507
+ }
508
+ try {
509
+ const html = ejs.render(
510
+ template,
511
+ {
512
+ baseUrl: req.baseUrl,
513
+ adminPath,
514
+ endpointRegistry,
515
+ },
516
+ {
517
+ filename: templatePath,
518
+ },
519
+ );
520
+ res.send(html);
521
+ } catch (renderErr) {
522
+ console.error("Error rendering template:", renderErr);
523
+ res.status(500).send("Error rendering page");
524
+ }
525
+ });
526
+ });
527
+
528
+ router.get(`${adminPath}/scripts`, basicAuth, (req, res) => {
529
+ const templatePath = path.join(
530
+ __dirname,
531
+ "..",
532
+ "views",
533
+ "admin-scripts.ejs",
534
+ );
535
+ fs.readFile(templatePath, "utf8", (err, template) => {
536
+ if (err) {
537
+ console.error("Error reading template:", err);
538
+ return res.status(500).send("Error loading page");
539
+ }
540
+ try {
541
+ const html = ejs.render(
542
+ template,
543
+ {
544
+ baseUrl: req.baseUrl,
545
+ adminPath,
546
+ endpointRegistry,
547
+ },
548
+ {
549
+ filename: templatePath,
550
+ },
551
+ );
552
+ res.send(html);
553
+ } catch (renderErr) {
554
+ console.error("Error rendering template:", renderErr);
555
+ res.status(500).send("Error rendering page");
556
+ }
557
+ });
558
+ });
559
+
560
+ router.get(`${adminPath}/crons`, basicAuth, (req, res) => {
561
+ const templatePath = path.join(__dirname, "..", "views", "admin-crons.ejs");
176
562
  fs.readFile(templatePath, "utf8", (err, template) => {
177
563
  if (err) {
178
564
  console.error("Error reading template:", err);
179
565
  return res.status(500).send("Error loading page");
180
566
  }
181
567
  try {
182
- const html = ejs.render(template, { baseUrl: req.baseUrl, adminPath }, { filename: templatePath });
568
+ const html = ejs.render(
569
+ template,
570
+ {
571
+ baseUrl: req.baseUrl,
572
+ adminPath,
573
+ },
574
+ {
575
+ filename: templatePath,
576
+ },
577
+ );
578
+ res.send(html);
579
+ } catch (renderErr) {
580
+ console.error("Error rendering template:", renderErr);
581
+ res.status(500).send("Error rendering page");
582
+ }
583
+ });
584
+ });
585
+
586
+ router.get(`${adminPath}/cache`, basicAuth, (req, res) => {
587
+ const templatePath = path.join(__dirname, "..", "views", "admin-cache.ejs");
588
+ fs.readFile(templatePath, "utf8", (err, template) => {
589
+ if (err) {
590
+ console.error("Error reading template:", err);
591
+ return res.status(500).send("Error loading page");
592
+ }
593
+ try {
594
+ const html = ejs.render(
595
+ template,
596
+ {
597
+ baseUrl: req.baseUrl,
598
+ adminPath,
599
+ },
600
+ {
601
+ filename: templatePath,
602
+ },
603
+ );
604
+ res.send(html);
605
+ } catch (renderErr) {
606
+ console.error("Error rendering template:", renderErr);
607
+ res.status(500).send("Error rendering page");
608
+ }
609
+ });
610
+ });
611
+
612
+ router.get(`${adminPath}/db-browser`, basicAuth, (req, res) => {
613
+ const templatePath = path.join(
614
+ __dirname,
615
+ "..",
616
+ "views",
617
+ "admin-db-browser.ejs",
618
+ );
619
+ fs.readFile(templatePath, "utf8", (err, template) => {
620
+ if (err) {
621
+ console.error("Error reading template:", err);
622
+ return res.status(500).send("Error loading page");
623
+ }
624
+ try {
625
+ const html = ejs.render(
626
+ template,
627
+ {
628
+ baseUrl: req.baseUrl,
629
+ adminPath,
630
+ },
631
+ {
632
+ filename: templatePath,
633
+ },
634
+ );
183
635
  res.send(html);
184
636
  } catch (renderErr) {
185
637
  console.error("Error rendering template:", renderErr);
@@ -198,23 +650,63 @@ function createMiddleware(options = {}) {
198
650
  "/api/admin/json-configs",
199
651
  require("./routes/adminJsonConfigs.routes"),
200
652
  );
653
+ router.use(
654
+ "/api/admin/rate-limits",
655
+ require("./routes/adminRateLimits.routes"),
656
+ );
657
+ router.use("/api/admin/proxy", require("./routes/adminProxy.routes"));
201
658
  router.use(
202
659
  "/api/admin/seo-config",
203
660
  require("./routes/adminSeoConfig.routes"),
204
661
  );
205
662
  router.use("/api/admin/i18n", require("./routes/adminI18n.routes"));
206
663
  router.use("/api/admin/headless", require("./routes/adminHeadless.routes"));
664
+ router.use("/api/admin/scripts", require("./routes/adminScripts.routes"));
665
+ router.use("/api/admin/crons", require("./routes/adminCrons.routes"));
666
+ router.use(
667
+ "/api/admin/health-checks",
668
+ require("./routes/adminHealthChecks.routes"),
669
+ );
670
+ router.use("/api/admin/cache", require("./routes/adminCache.routes"));
671
+ router.use(
672
+ "/api/admin/console-manager",
673
+ require("./routes/adminConsoleManager.routes"),
674
+ );
675
+ router.use(
676
+ "/api/admin/db-browser",
677
+ require("./routes/adminDbBrowser.routes"),
678
+ );
679
+ router.use("/api/admin/terminals", require("./routes/adminTerminals.routes"));
207
680
  router.use("/api/admin/assets", require("./routes/adminAssets.routes"));
208
681
  router.use(
209
682
  "/api/admin/upload-namespaces",
210
683
  require("./routes/adminUploadNamespaces.routes"),
211
684
  );
685
+ router.use(
686
+ "/api/admin/ui-components",
687
+ require("./routes/adminUiComponents.routes"),
688
+ );
212
689
  router.use("/api/admin/migration", require("./routes/adminMigration.routes"));
213
- router.use("/api/admin/errors", basicAuth, require("./routes/adminErrors.routes"));
214
- router.use("/api/admin/audit", basicAuth, require("./routes/adminAudit.routes"));
690
+ router.use(
691
+ "/api/admin/errors",
692
+ basicAuth,
693
+ require("./routes/adminErrors.routes"),
694
+ );
695
+ router.use(
696
+ "/api/admin/audit",
697
+ basicAuth,
698
+ require("./routes/adminAudit.routes"),
699
+ );
215
700
  router.use("/api/admin/llm", require("./routes/adminLlm.routes"));
216
- router.use("/api/admin/ejs-virtual", require("./routes/adminEjsVirtual.routes"));
217
- router.use("/api/workflows", basicAuth, require("./routes/workflows.routes"));
701
+ router.use(
702
+ "/api/admin/ejs-virtual",
703
+ require("./routes/adminEjsVirtual.routes"),
704
+ );
705
+ router.use("/api/admin/pages", require("./routes/adminPages.routes"));
706
+ router.use("/api/admin", require("./routes/adminBlog.routes"));
707
+ router.use("/api/admin", require("./routes/adminBlogAi.routes"));
708
+ router.use("/api/admin", require("./routes/adminBlogAutomation.routes"));
709
+ router.use("/api/admin/workflows", basicAuth, require("./routes/workflows.routes"));
218
710
  router.use("/w", require("./routes/workflowWebhook.routes"));
219
711
  router.use("/api/webhooks", require("./routes/webhook.routes"));
220
712
  router.use("/api/settings", require("./routes/globalSettings.routes"));
@@ -229,20 +721,104 @@ function createMiddleware(options = {}) {
229
721
  router.use("/api/invites", require("./routes/invite.routes"));
230
722
  router.use("/api/log", require("./routes/log.routes"));
231
723
  router.use("/api/error-tracking", require("./routes/errorTracking.routes"));
724
+ router.use("/api/ui-components", require("./routes/uiComponentsPublic.routes"));
725
+ router.use("/api/rbac", require("./routes/rbac.routes"));
726
+ router.use("/api/file-manager", require("./routes/fileManager.routes"));
727
+
728
+ // Public blog APIs (headless)
729
+ router.use("/api", require("./routes/blogPublic.routes"));
730
+
731
+ // Internal blog endpoints (used by HTTP CronJobs)
732
+ router.use("/api/internal", require("./routes/blogInternal.routes"));
733
+
734
+ // Public health checks status (gated by global setting)
735
+ router.use(
736
+ "/api/health-checks",
737
+ require("./routes/healthChecksPublic.routes"),
738
+ );
232
739
 
233
740
  // Public assets proxy
234
741
  router.use("/public/assets", require("./routes/publicAssets.routes"));
235
742
 
236
- // Admin dashboard (polished view)
237
- router.get(adminPath, basicAuth, (req, res) => {
238
- const templatePath = path.join(__dirname, "..", "views", "admin-dashboard.ejs");
743
+ // Admin dashboard (polished view)
744
+ router.get(adminPath, basicAuth, (req, res) => {
745
+ const templatePath = path.join(
746
+ __dirname,
747
+ "..",
748
+ "views",
749
+ "admin-dashboard.ejs",
750
+ );
751
+ fs.readFile(templatePath, "utf8", (err, template) => {
752
+ if (err) {
753
+ console.error("Error reading template:", err);
754
+ return res.status(500).send("Error loading page");
755
+ }
756
+ try {
757
+ const html = ejs.render(
758
+ template,
759
+ { baseUrl: req.baseUrl, adminPath },
760
+ { filename: templatePath },
761
+ );
762
+ res.send(html);
763
+ } catch (renderErr) {
764
+ console.error("Error rendering template:", renderErr);
765
+ res.status(500).send("Error rendering page");
766
+ }
767
+ });
768
+ });
769
+
770
+ // Admin technical API test page (protected by basic auth)
771
+ router.get(`${adminPath}/api/test`, basicAuth, (req, res) => {
772
+ const templatePath = path.join(__dirname, "..", "views", "admin-test.ejs");
773
+ fs.readFile(templatePath, "utf8", (err, template) => {
774
+ if (err) {
775
+ console.error("Error reading template:", err);
776
+ return res.status(500).send("Error loading page");
777
+ }
778
+ try {
779
+ const html = ejs.render(
780
+ template,
781
+ {
782
+ baseUrl: req.baseUrl,
783
+ adminPath,
784
+ endpointRegistry,
785
+ },
786
+ {
787
+ filename: templatePath,
788
+ },
789
+ );
790
+ res.send(html);
791
+ } catch (renderErr) {
792
+ console.error("Error rendering template:", renderErr);
793
+ res.status(500).send("Error rendering page");
794
+ }
795
+ });
796
+ });
797
+
798
+ router.get(`${adminPath}/migration`, basicAuth, (req, res) => {
799
+ const templatePath = path.join(
800
+ __dirname,
801
+ "..",
802
+ "views",
803
+ "admin-migration.ejs",
804
+ );
239
805
  fs.readFile(templatePath, "utf8", (err, template) => {
240
806
  if (err) {
241
807
  console.error("Error reading template:", err);
242
808
  return res.status(500).send("Error loading page");
243
809
  }
244
810
  try {
245
- const html = ejs.render(template, { baseUrl: req.baseUrl, adminPath }, { filename: templatePath });
811
+ const html = ejs.render(
812
+ template,
813
+ {
814
+ baseUrl: req.baseUrl,
815
+ adminPath,
816
+ endpointRegistry,
817
+ },
818
+ {
819
+ filename: templatePath,
820
+ },
821
+ );
246
822
  res.send(html);
247
823
  } catch (renderErr) {
248
824
  console.error("Error rendering template:", renderErr);
@@ -251,9 +827,9 @@ function createMiddleware(options = {}) {
251
827
  });
252
828
  });
253
829
 
254
- // Admin technical API test page (protected by basic auth)
255
- router.get(`${adminPath}/api/test`, basicAuth, (req, res) => {
256
- const templatePath = path.join(__dirname, "..", "views", "admin-test.ejs");
830
+ // Admin LLM/AI page (protected by basic auth)
831
+ router.get(`${adminPath}/admin-llm`, basicAuth, (req, res) => {
832
+ const templatePath = path.join(__dirname, "..", "views", "admin-llm.ejs");
257
833
  fs.readFile(templatePath, "utf8", (err, template) => {
258
834
  if (err) {
259
835
  console.error("Error reading template:", err);
@@ -265,7 +841,6 @@ function createMiddleware(options = {}) {
265
841
  {
266
842
  baseUrl: req.baseUrl,
267
843
  adminPath,
268
- endpointRegistry,
269
844
  },
270
845
  {
271
846
  filename: templatePath,
@@ -279,8 +854,34 @@ function createMiddleware(options = {}) {
279
854
  });
280
855
  });
281
856
 
282
- router.get(`${adminPath}/migration`, basicAuth, (req, res) => {
283
- const templatePath = path.join(__dirname, "..", "views", "admin-migration.ejs");
857
+ router.get(`${adminPath}/workflows/:id`, basicAuth, (req, res) => {
858
+ const templatePath = path.join(
859
+ __dirname,
860
+ "..",
861
+ "views",
862
+ "admin-workflows.ejs",
863
+ );
864
+ fs.readFile(templatePath, "utf8", (err, template) => {
865
+ if (err) {
866
+ console.error("Error reading template:", err);
867
+ return res.status(500).send("Error loading page");
868
+ }
869
+ try {
870
+ const html = ejs.render(
871
+ template,
872
+ { baseUrl: req.baseUrl, adminPath },
873
+ { filename: templatePath },
874
+ );
875
+ res.send(html);
876
+ } catch (renderErr) {
877
+ console.error("Error rendering template:", renderErr);
878
+ res.status(500).send("Error rendering page");
879
+ }
880
+ });
881
+ });
882
+
883
+ router.get(`${adminPath}/pages`, basicAuth, (req, res) => {
884
+ const templatePath = path.join(__dirname, "..", "views", "admin-pages.ejs");
284
885
  fs.readFile(templatePath, "utf8", (err, template) => {
285
886
  if (err) {
286
887
  console.error("Error reading template:", err);
@@ -292,7 +893,6 @@ function createMiddleware(options = {}) {
292
893
  {
293
894
  baseUrl: req.baseUrl,
294
895
  adminPath,
295
- endpointRegistry,
296
896
  },
297
897
  {
298
898
  filename: templatePath,
@@ -306,13 +906,85 @@ function createMiddleware(options = {}) {
306
906
  });
307
907
  });
308
908
 
309
- // Admin LLM/AI page (protected by basic auth)
310
- router.get(`${adminPath}/admin-llm`, basicAuth, (req, res) => {
909
+ router.get(`${adminPath}/blog`, basicAuth, (req, res) => {
910
+ const templatePath = path.join(__dirname, "..", "views", "admin-blog.ejs");
911
+ fs.readFile(templatePath, "utf8", (err, template) => {
912
+ if (err) {
913
+ console.error("Error reading template:", err);
914
+ return res.status(500).send("Error loading page");
915
+ }
916
+ try {
917
+ const html = ejs.render(
918
+ template,
919
+ { baseUrl: req.baseUrl, adminPath },
920
+ { filename: templatePath },
921
+ );
922
+ res.send(html);
923
+ } catch (renderErr) {
924
+ console.error("Error rendering template:", renderErr);
925
+ res.status(500).send("Error rendering page");
926
+ }
927
+ });
928
+ });
929
+
930
+ router.get(`${adminPath}/blog-automation`, basicAuth, (req, res) => {
931
+ const templatePath = path.join(
932
+ __dirname,
933
+ "..",
934
+ "views",
935
+ "admin-blog-automation.ejs",
936
+ );
937
+ fs.readFile(templatePath, "utf8", (err, template) => {
938
+ if (err) {
939
+ console.error("Error reading template:", err);
940
+ return res.status(500).send("Error loading page");
941
+ }
942
+ try {
943
+ const html = ejs.render(
944
+ template,
945
+ { baseUrl: req.baseUrl, adminPath },
946
+ { filename: templatePath },
947
+ );
948
+ res.send(html);
949
+ } catch (renderErr) {
950
+ console.error("Error rendering template:", renderErr);
951
+ res.status(500).send("Error rendering page");
952
+ }
953
+ });
954
+ });
955
+
956
+ router.get(`${adminPath}/blog/new`, basicAuth, (req, res) => {
957
+ const templatePath = path.join(
958
+ __dirname,
959
+ "..",
960
+ "views",
961
+ "admin-blog-edit.ejs",
962
+ );
963
+ fs.readFile(templatePath, "utf8", (err, template) => {
964
+ if (err) {
965
+ console.error("Error reading template:", err);
966
+ return res.status(500).send("Error loading page");
967
+ }
968
+ try {
969
+ const html = ejs.render(
970
+ template,
971
+ { baseUrl: req.baseUrl, adminPath, postId: "", mode: "new" },
972
+ { filename: templatePath },
973
+ );
974
+ res.send(html);
975
+ } catch (renderErr) {
976
+ console.error("Error rendering template:", renderErr);
977
+ res.status(500).send("Error rendering page");
978
+ }
979
+ });
980
+ });
981
+
982
+ router.get(`${adminPath}/blog/edit/:id`, basicAuth, (req, res) => {
311
983
  const templatePath = path.join(
312
984
  __dirname,
313
985
  "..",
314
986
  "views",
315
- "admin-llm.ejs",
987
+ "admin-blog-edit.ejs",
316
988
  );
317
989
  fs.readFile(templatePath, "utf8", (err, template) => {
318
990
  if (err) {
@@ -325,10 +997,10 @@ function createMiddleware(options = {}) {
325
997
  {
326
998
  baseUrl: req.baseUrl,
327
999
  adminPath,
1000
+ postId: String(req.params.id || ""),
1001
+ mode: "edit",
328
1002
  },
329
- {
330
- filename: templatePath,
331
- },
1003
+ { filename: templatePath },
332
1004
  );
333
1005
  res.send(html);
334
1006
  } catch (renderErr) {
@@ -338,15 +1010,29 @@ function createMiddleware(options = {}) {
338
1010
  });
339
1011
  });
340
1012
 
341
- router.get(`${adminPath}/workflows/:id`, basicAuth, (req, res) => {
342
- const templatePath = path.join(__dirname, "..", "views", "admin-workflows.ejs");
1013
+ router.get(`${adminPath}/file-manager`, basicAuth, (req, res) => {
1014
+ const templatePath = path.join(
1015
+ __dirname,
1016
+ "..",
1017
+ "views",
1018
+ "admin-file-manager.ejs",
1019
+ );
343
1020
  fs.readFile(templatePath, "utf8", (err, template) => {
344
1021
  if (err) {
345
1022
  console.error("Error reading template:", err);
346
1023
  return res.status(500).send("Error loading page");
347
1024
  }
348
1025
  try {
349
- const html = ejs.render(template, { baseUrl: req.baseUrl, adminPath }, { filename: templatePath });
1026
+ const html = ejs.render(
1027
+ template,
1028
+ {
1029
+ baseUrl: req.baseUrl,
1030
+ adminPath,
1031
+ },
1032
+ {
1033
+ filename: templatePath,
1034
+ },
1035
+ );
350
1036
  res.send(html);
351
1037
  } catch (renderErr) {
352
1038
  console.error("Error rendering template:", renderErr);
@@ -356,7 +1042,12 @@ function createMiddleware(options = {}) {
356
1042
  });
357
1043
 
358
1044
  router.get(`${adminPath}/ejs-virtual`, basicAuth, (req, res) => {
359
- const templatePath = path.join(__dirname, "..", "views", "admin-ejs-virtual.ejs");
1045
+ const templatePath = path.join(
1046
+ __dirname,
1047
+ "..",
1048
+ "views",
1049
+ "admin-ejs-virtual.ejs",
1050
+ );
360
1051
  fs.readFile(templatePath, "utf8", (err, template) => {
361
1052
  if (err) {
362
1053
  console.error("Error reading template:", err);
@@ -382,7 +1073,12 @@ function createMiddleware(options = {}) {
382
1073
  });
383
1074
 
384
1075
  router.get(`${adminPath}/seo-config`, basicAuth, (req, res) => {
385
- const templatePath = path.join(__dirname, "..", "views", "admin-seo-config.ejs");
1076
+ const templatePath = path.join(
1077
+ __dirname,
1078
+ "..",
1079
+ "views",
1080
+ "admin-seo-config.ejs",
1081
+ );
386
1082
  fs.readFile(templatePath, "utf8", (err, template) => {
387
1083
  if (err) {
388
1084
  console.error("Error reading template:", err);
@@ -416,7 +1112,11 @@ function createMiddleware(options = {}) {
416
1112
  return res.status(500).send("Error loading page");
417
1113
  }
418
1114
  try {
419
- const html = ejs.render(template, { baseUrl: req.baseUrl, adminPath }, { filename: templatePath });
1115
+ const html = ejs.render(
1116
+ template,
1117
+ { baseUrl: req.baseUrl, adminPath },
1118
+ { filename: templatePath },
1119
+ );
420
1120
  res.send(html);
421
1121
  } catch (renderErr) {
422
1122
  console.error("Error rendering template:", renderErr);
@@ -438,7 +1138,11 @@ function createMiddleware(options = {}) {
438
1138
  return res.status(500).send("Error loading page");
439
1139
  }
440
1140
  try {
441
- const html = ejs.render(template, { baseUrl: req.baseUrl, adminPath }, { filename: templatePath });
1141
+ const html = ejs.render(
1142
+ template,
1143
+ { baseUrl: req.baseUrl, adminPath },
1144
+ { filename: templatePath },
1145
+ );
442
1146
  res.send(html);
443
1147
  } catch (renderErr) {
444
1148
  console.error("Error rendering template:", renderErr);
@@ -541,6 +1245,39 @@ function createMiddleware(options = {}) {
541
1245
  });
542
1246
  });
543
1247
 
1248
+ // Admin UI Components page (protected by basic auth)
1249
+ router.get(`${adminPath}/ui-components`, basicAuth, (req, res) => {
1250
+ const templatePath = path.join(
1251
+ __dirname,
1252
+ "..",
1253
+ "views",
1254
+ "admin-ui-components.ejs",
1255
+ );
1256
+ fs.readFile(templatePath, "utf8", (err, template) => {
1257
+ if (err) {
1258
+ console.error("Error reading template:", err);
1259
+ return res.status(500).send("Error loading page");
1260
+ }
1261
+ try {
1262
+ const html = ejs.render(
1263
+ template,
1264
+ {
1265
+ baseUrl: req.baseUrl,
1266
+ adminPath,
1267
+ endpointRegistry,
1268
+ },
1269
+ {
1270
+ filename: templatePath,
1271
+ },
1272
+ );
1273
+ res.send(html);
1274
+ } catch (renderErr) {
1275
+ console.error("Error rendering template:", renderErr);
1276
+ res.status(500).send("Error rendering page");
1277
+ }
1278
+ });
1279
+ });
1280
+
544
1281
  // Admin JSON configs page (protected by basic auth)
545
1282
  router.get(`${adminPath}/json-configs`, basicAuth, (req, res) => {
546
1283
  const templatePath = path.join(
@@ -669,12 +1406,7 @@ function createMiddleware(options = {}) {
669
1406
 
670
1407
  // Admin users page (protected by basic auth)
671
1408
  router.get(`${adminPath}/users`, basicAuth, (req, res) => {
672
- const templatePath = path.join(
673
- __dirname,
674
- "..",
675
- "views",
676
- "admin-users.ejs",
677
- );
1409
+ const templatePath = path.join(__dirname, "..", "views", "admin-users.ejs");
678
1410
  fs.readFile(templatePath, "utf8", (err, template) => {
679
1411
  if (err) {
680
1412
  console.error("Error reading template:", err);
@@ -793,6 +1525,31 @@ function createMiddleware(options = {}) {
793
1525
  });
794
1526
  });
795
1527
 
1528
+ router.get(`${adminPath}/rate-limiter`, basicAuth, (req, res) => {
1529
+ const templatePath = path.join(
1530
+ __dirname,
1531
+ "..",
1532
+ "views",
1533
+ "admin-rate-limiter.ejs",
1534
+ );
1535
+ fs.readFile(templatePath, "utf8", (err, template) => {
1536
+ if (err) {
1537
+ console.error("Error reading template:", err);
1538
+ return res.status(500).send("Error loading page");
1539
+ }
1540
+ try {
1541
+ const html = ejs.render(template, {
1542
+ baseUrl: req.baseUrl,
1543
+ adminPath,
1544
+ });
1545
+ res.send(html);
1546
+ } catch (renderErr) {
1547
+ console.error("Error rendering template:", renderErr);
1548
+ res.status(500).send("Error rendering page");
1549
+ }
1550
+ });
1551
+ });
1552
+
796
1553
  // Admin global settings page (protected by basic auth) - render manually
797
1554
  router.get(`${adminPath}/global-settings`, basicAuth, (req, res) => {
798
1555
  const templatePath = path.join(
@@ -817,14 +1574,23 @@ function createMiddleware(options = {}) {
817
1574
  });
818
1575
 
819
1576
  router.get(`${adminPath}/errors`, basicAuth, (req, res) => {
820
- const templatePath = path.join(__dirname, "..", "views", "admin-errors.ejs");
1577
+ const templatePath = path.join(
1578
+ __dirname,
1579
+ "..",
1580
+ "views",
1581
+ "admin-errors.ejs",
1582
+ );
821
1583
  fs.readFile(templatePath, "utf8", (err, template) => {
822
1584
  if (err) {
823
1585
  console.error("Error reading template:", err);
824
1586
  return res.status(500).send("Error loading page");
825
1587
  }
826
1588
  try {
827
- const html = ejs.render(template, { baseUrl: req.baseUrl, adminPath }, { filename: templatePath });
1589
+ const html = ejs.render(
1590
+ template,
1591
+ { baseUrl: req.baseUrl, adminPath },
1592
+ { filename: templatePath },
1593
+ );
828
1594
  res.send(html);
829
1595
  } catch (renderErr) {
830
1596
  console.error("Error rendering template:", renderErr);
@@ -841,7 +1607,11 @@ function createMiddleware(options = {}) {
841
1607
  return res.status(500).send("Error loading page");
842
1608
  }
843
1609
  try {
844
- const html = ejs.render(template, { baseUrl: req.baseUrl, adminPath }, { filename: templatePath });
1610
+ const html = ejs.render(
1611
+ template,
1612
+ { baseUrl: req.baseUrl, adminPath },
1613
+ { filename: templatePath },
1614
+ );
845
1615
  res.send(html);
846
1616
  } catch (renderErr) {
847
1617
  console.error("Error rendering template:", renderErr);
@@ -851,14 +1621,44 @@ function createMiddleware(options = {}) {
851
1621
  });
852
1622
 
853
1623
  router.get(`${adminPath}/coolify-deploy`, basicAuth, (req, res) => {
854
- const templatePath = path.join(__dirname, "..", "views", "admin-coolify-deploy.ejs");
1624
+ const templatePath = path.join(
1625
+ __dirname,
1626
+ "..",
1627
+ "views",
1628
+ "admin-coolify-deploy.ejs",
1629
+ );
1630
+ fs.readFile(templatePath, "utf8", (err, template) => {
1631
+ if (err) {
1632
+ console.error("Error reading template:", err);
1633
+ return res.status(500).send("Error loading page");
1634
+ }
1635
+ try {
1636
+ const html = ejs.render(
1637
+ template,
1638
+ { baseUrl: req.baseUrl, adminPath },
1639
+ { filename: templatePath },
1640
+ );
1641
+ res.send(html);
1642
+ } catch (renderErr) {
1643
+ console.error("Error rendering template:", renderErr);
1644
+ res.status(500).send("Error rendering page");
1645
+ }
1646
+ });
1647
+ });
1648
+
1649
+ router.get(`${adminPath}/proxy`, basicAuth, (req, res) => {
1650
+ const templatePath = path.join(__dirname, "..", "views", "admin-proxy.ejs");
855
1651
  fs.readFile(templatePath, "utf8", (err, template) => {
856
1652
  if (err) {
857
1653
  console.error("Error reading template:", err);
858
1654
  return res.status(500).send("Error loading page");
859
1655
  }
860
1656
  try {
861
- const html = ejs.render(template, { baseUrl: req.baseUrl, adminPath }, { filename: templatePath });
1657
+ const html = ejs.render(
1658
+ template,
1659
+ { baseUrl: req.baseUrl, adminPath },
1660
+ { filename: templatePath },
1661
+ );
862
1662
  res.send(html);
863
1663
  } catch (renderErr) {
864
1664
  console.error("Error rendering template:", renderErr);
@@ -868,14 +1668,23 @@ function createMiddleware(options = {}) {
868
1668
  });
869
1669
 
870
1670
  router.get(`${adminPath}/webhooks`, basicAuth, (req, res) => {
871
- const templatePath = path.join(__dirname, "..", "views", "admin-webhooks.ejs");
1671
+ const templatePath = path.join(
1672
+ __dirname,
1673
+ "..",
1674
+ "views",
1675
+ "admin-webhooks.ejs",
1676
+ );
872
1677
  fs.readFile(templatePath, "utf8", (err, template) => {
873
1678
  if (err) {
874
1679
  console.error("Error reading template:", err);
875
1680
  return res.status(500).send("Error loading page");
876
1681
  }
877
1682
  try {
878
- const html = ejs.render(template, { baseUrl: req.baseUrl, adminPath }, { filename: templatePath });
1683
+ const html = ejs.render(
1684
+ template,
1685
+ { baseUrl: req.baseUrl, adminPath },
1686
+ { filename: templatePath },
1687
+ );
879
1688
  res.send(html);
880
1689
  } catch (renderErr) {
881
1690
  console.error("Error rendering template:", renderErr);
@@ -884,7 +1693,7 @@ function createMiddleware(options = {}) {
884
1693
  });
885
1694
  });
886
1695
 
887
- router.get("/health", (req, res) => {
1696
+ router.get("/health", rateLimiter.limit("healthRateLimiter"), (req, res) => {
888
1697
  res.json({
889
1698
  status: "ok",
890
1699
  mode: "middleware",
@@ -896,6 +1705,20 @@ function createMiddleware(options = {}) {
896
1705
  router.use("/api/ejs-virtual", require("./routes/adminEjsVirtual.routes"));
897
1706
  router.use("/api/webhooks", require("./routes/webhook.routes"));
898
1707
 
1708
+ // Store pagesPrefix and adminPath on app for pages router
1709
+ router.use((req, res, next) => {
1710
+ if (!req.app.get("pagesPrefix")) {
1711
+ req.app.set("pagesPrefix", pagesPrefix);
1712
+ }
1713
+ if (!req.app.get("adminPath")) {
1714
+ req.app.set("adminPath", adminPath);
1715
+ }
1716
+ next();
1717
+ });
1718
+
1719
+ // Public pages router (catch-all, must be last before error handler)
1720
+ router.use(require("./routes/pages.routes"));
1721
+
899
1722
  // Error handling middleware
900
1723
  router.use(expressErrorMiddleware);
901
1724