@qwickapps/server 1.3.0 → 1.4.0

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 (241) hide show
  1. package/README.md +311 -0
  2. package/dist/core/control-panel.d.ts.map +1 -1
  3. package/dist/core/control-panel.js +144 -2
  4. package/dist/core/control-panel.js.map +1 -1
  5. package/dist/core/plugin-registry.d.ts +36 -0
  6. package/dist/core/plugin-registry.d.ts.map +1 -1
  7. package/dist/core/plugin-registry.js +26 -0
  8. package/dist/core/plugin-registry.js.map +1 -1
  9. package/dist/core/types.d.ts +19 -0
  10. package/dist/core/types.d.ts.map +1 -1
  11. package/dist/index.d.ts +2 -2
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +4 -2
  14. package/dist/index.js.map +1 -1
  15. package/dist/plugins/auth/adapter-wrapper.d.ts +47 -0
  16. package/dist/plugins/auth/adapter-wrapper.d.ts.map +1 -0
  17. package/dist/plugins/auth/adapter-wrapper.js +166 -0
  18. package/dist/plugins/auth/adapter-wrapper.js.map +1 -0
  19. package/dist/plugins/auth/adapter-wrapper.test.d.ts +7 -0
  20. package/dist/plugins/auth/adapter-wrapper.test.d.ts.map +1 -0
  21. package/dist/plugins/auth/adapter-wrapper.test.js +303 -0
  22. package/dist/plugins/auth/adapter-wrapper.test.js.map +1 -0
  23. package/dist/plugins/auth/adapters/index.d.ts +1 -0
  24. package/dist/plugins/auth/adapters/index.d.ts.map +1 -1
  25. package/dist/plugins/auth/adapters/index.js +1 -0
  26. package/dist/plugins/auth/adapters/index.js.map +1 -1
  27. package/dist/plugins/auth/adapters/supabase-adapter.d.ts.map +1 -1
  28. package/dist/plugins/auth/adapters/supabase-adapter.js.map +1 -1
  29. package/dist/plugins/auth/adapters/supertokens-adapter.d.ts +18 -0
  30. package/dist/plugins/auth/adapters/supertokens-adapter.d.ts.map +1 -0
  31. package/dist/plugins/auth/adapters/supertokens-adapter.js +267 -0
  32. package/dist/plugins/auth/adapters/supertokens-adapter.js.map +1 -0
  33. package/dist/plugins/auth/config-store.d.ts +11 -0
  34. package/dist/plugins/auth/config-store.d.ts.map +1 -0
  35. package/dist/plugins/auth/config-store.js +232 -0
  36. package/dist/plugins/auth/config-store.js.map +1 -0
  37. package/dist/plugins/auth/config-store.test.d.ts +7 -0
  38. package/dist/plugins/auth/config-store.test.d.ts.map +1 -0
  39. package/dist/plugins/auth/config-store.test.js +299 -0
  40. package/dist/plugins/auth/config-store.test.js.map +1 -0
  41. package/dist/plugins/auth/env-config.d.ts +138 -0
  42. package/dist/plugins/auth/env-config.d.ts.map +1 -0
  43. package/dist/plugins/auth/env-config.js +1122 -0
  44. package/dist/plugins/auth/env-config.js.map +1 -0
  45. package/dist/plugins/auth/index.d.ts +7 -1
  46. package/dist/plugins/auth/index.d.ts.map +1 -1
  47. package/dist/plugins/auth/index.js +7 -0
  48. package/dist/plugins/auth/index.js.map +1 -1
  49. package/dist/plugins/auth/supertokens-adapter.test.d.ts +10 -0
  50. package/dist/plugins/auth/supertokens-adapter.test.d.ts.map +1 -0
  51. package/dist/plugins/auth/supertokens-adapter.test.js +486 -0
  52. package/dist/plugins/auth/supertokens-adapter.test.js.map +1 -0
  53. package/dist/plugins/auth/types.d.ts +176 -0
  54. package/dist/plugins/auth/types.d.ts.map +1 -1
  55. package/dist/plugins/auth/types.js.map +1 -1
  56. package/dist/plugins/cache-plugin.test.js +3 -0
  57. package/dist/plugins/cache-plugin.test.js.map +1 -1
  58. package/dist/plugins/index.d.ts +6 -2
  59. package/dist/plugins/index.d.ts.map +1 -1
  60. package/dist/plugins/index.js +5 -1
  61. package/dist/plugins/index.js.map +1 -1
  62. package/dist/plugins/postgres-plugin.test.js +3 -0
  63. package/dist/plugins/postgres-plugin.test.js.map +1 -1
  64. package/dist/plugins/preferences/__tests__/deep-merge.test.d.ts +7 -0
  65. package/dist/plugins/preferences/__tests__/deep-merge.test.d.ts.map +1 -0
  66. package/dist/plugins/preferences/__tests__/deep-merge.test.js +215 -0
  67. package/dist/plugins/preferences/__tests__/deep-merge.test.js.map +1 -0
  68. package/dist/plugins/preferences/__tests__/preferences-plugin.test.d.ts +7 -0
  69. package/dist/plugins/preferences/__tests__/preferences-plugin.test.d.ts.map +1 -0
  70. package/dist/plugins/preferences/__tests__/preferences-plugin.test.js +265 -0
  71. package/dist/plugins/preferences/__tests__/preferences-plugin.test.js.map +1 -0
  72. package/dist/plugins/preferences/index.d.ts +12 -0
  73. package/dist/plugins/preferences/index.d.ts.map +1 -0
  74. package/dist/plugins/preferences/index.js +13 -0
  75. package/dist/plugins/preferences/index.js.map +1 -0
  76. package/dist/plugins/preferences/preferences-plugin.d.ts +39 -0
  77. package/dist/plugins/preferences/preferences-plugin.d.ts.map +1 -0
  78. package/dist/plugins/preferences/preferences-plugin.js +226 -0
  79. package/dist/plugins/preferences/preferences-plugin.js.map +1 -0
  80. package/dist/plugins/preferences/stores/index.d.ts +9 -0
  81. package/dist/plugins/preferences/stores/index.d.ts.map +1 -0
  82. package/dist/plugins/preferences/stores/index.js +9 -0
  83. package/dist/plugins/preferences/stores/index.js.map +1 -0
  84. package/dist/plugins/preferences/stores/postgres-store.d.ts +41 -0
  85. package/dist/plugins/preferences/stores/postgres-store.d.ts.map +1 -0
  86. package/dist/plugins/preferences/stores/postgres-store.js +181 -0
  87. package/dist/plugins/preferences/stores/postgres-store.js.map +1 -0
  88. package/dist/plugins/preferences/types.d.ts +91 -0
  89. package/dist/plugins/preferences/types.d.ts.map +1 -0
  90. package/dist/plugins/preferences/types.js +10 -0
  91. package/dist/plugins/preferences/types.js.map +1 -0
  92. package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.d.ts +7 -0
  93. package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.d.ts.map +1 -0
  94. package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.js +220 -0
  95. package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.js.map +1 -0
  96. package/dist/plugins/rate-limit/cleanup.d.ts +40 -0
  97. package/dist/plugins/rate-limit/cleanup.d.ts.map +1 -0
  98. package/dist/plugins/rate-limit/cleanup.js +72 -0
  99. package/dist/plugins/rate-limit/cleanup.js.map +1 -0
  100. package/dist/plugins/rate-limit/env-config.d.ts +91 -0
  101. package/dist/plugins/rate-limit/env-config.d.ts.map +1 -0
  102. package/dist/plugins/rate-limit/env-config.js +318 -0
  103. package/dist/plugins/rate-limit/env-config.js.map +1 -0
  104. package/dist/plugins/rate-limit/index.d.ts +76 -0
  105. package/dist/plugins/rate-limit/index.d.ts.map +1 -0
  106. package/dist/plugins/rate-limit/index.js +79 -0
  107. package/dist/plugins/rate-limit/index.js.map +1 -0
  108. package/dist/plugins/rate-limit/middleware.d.ts +40 -0
  109. package/dist/plugins/rate-limit/middleware.d.ts.map +1 -0
  110. package/dist/plugins/rate-limit/middleware.js +169 -0
  111. package/dist/plugins/rate-limit/middleware.js.map +1 -0
  112. package/dist/plugins/rate-limit/rate-limit-plugin.d.ts +44 -0
  113. package/dist/plugins/rate-limit/rate-limit-plugin.d.ts.map +1 -0
  114. package/dist/plugins/rate-limit/rate-limit-plugin.js +354 -0
  115. package/dist/plugins/rate-limit/rate-limit-plugin.js.map +1 -0
  116. package/dist/plugins/rate-limit/rate-limit-service.d.ts +110 -0
  117. package/dist/plugins/rate-limit/rate-limit-service.d.ts.map +1 -0
  118. package/dist/plugins/rate-limit/rate-limit-service.js +172 -0
  119. package/dist/plugins/rate-limit/rate-limit-service.js.map +1 -0
  120. package/dist/plugins/rate-limit/stores/cache-store.d.ts +33 -0
  121. package/dist/plugins/rate-limit/stores/cache-store.d.ts.map +1 -0
  122. package/dist/plugins/rate-limit/stores/cache-store.js +225 -0
  123. package/dist/plugins/rate-limit/stores/cache-store.js.map +1 -0
  124. package/dist/plugins/rate-limit/stores/index.d.ts +8 -0
  125. package/dist/plugins/rate-limit/stores/index.d.ts.map +1 -0
  126. package/dist/plugins/rate-limit/stores/index.js +8 -0
  127. package/dist/plugins/rate-limit/stores/index.js.map +1 -0
  128. package/dist/plugins/rate-limit/stores/postgres-store.d.ts +34 -0
  129. package/dist/plugins/rate-limit/stores/postgres-store.d.ts.map +1 -0
  130. package/dist/plugins/rate-limit/stores/postgres-store.js +320 -0
  131. package/dist/plugins/rate-limit/stores/postgres-store.js.map +1 -0
  132. package/dist/plugins/rate-limit/strategies/fixed-window.d.ts +21 -0
  133. package/dist/plugins/rate-limit/strategies/fixed-window.d.ts.map +1 -0
  134. package/dist/plugins/rate-limit/strategies/fixed-window.js +97 -0
  135. package/dist/plugins/rate-limit/strategies/fixed-window.js.map +1 -0
  136. package/dist/plugins/rate-limit/strategies/index.d.ts +14 -0
  137. package/dist/plugins/rate-limit/strategies/index.d.ts.map +1 -0
  138. package/dist/plugins/rate-limit/strategies/index.js +27 -0
  139. package/dist/plugins/rate-limit/strategies/index.js.map +1 -0
  140. package/dist/plugins/rate-limit/strategies/sliding-window.d.ts +22 -0
  141. package/dist/plugins/rate-limit/strategies/sliding-window.d.ts.map +1 -0
  142. package/dist/plugins/rate-limit/strategies/sliding-window.js +122 -0
  143. package/dist/plugins/rate-limit/strategies/sliding-window.js.map +1 -0
  144. package/dist/plugins/rate-limit/strategies/token-bucket.d.ts +28 -0
  145. package/dist/plugins/rate-limit/strategies/token-bucket.d.ts.map +1 -0
  146. package/dist/plugins/rate-limit/strategies/token-bucket.js +121 -0
  147. package/dist/plugins/rate-limit/strategies/token-bucket.js.map +1 -0
  148. package/dist/plugins/rate-limit/types.d.ts +265 -0
  149. package/dist/plugins/rate-limit/types.d.ts.map +1 -0
  150. package/dist/plugins/rate-limit/types.js +9 -0
  151. package/dist/plugins/rate-limit/types.js.map +1 -0
  152. package/dist/plugins/users/__tests__/users-plugin.test.d.ts +9 -0
  153. package/dist/plugins/users/__tests__/users-plugin.test.d.ts.map +1 -0
  154. package/dist/plugins/users/__tests__/users-plugin.test.js +546 -0
  155. package/dist/plugins/users/__tests__/users-plugin.test.js.map +1 -0
  156. package/dist/plugins/users/index.d.ts +2 -2
  157. package/dist/plugins/users/index.d.ts.map +1 -1
  158. package/dist/plugins/users/index.js +1 -1
  159. package/dist/plugins/users/index.js.map +1 -1
  160. package/dist/plugins/users/types.d.ts +36 -0
  161. package/dist/plugins/users/types.d.ts.map +1 -1
  162. package/dist/plugins/users/users-plugin.d.ts +8 -2
  163. package/dist/plugins/users/users-plugin.d.ts.map +1 -1
  164. package/dist/plugins/users/users-plugin.js +122 -5
  165. package/dist/plugins/users/users-plugin.js.map +1 -1
  166. package/dist-ui/assets/index-D7DoZ9rL.js +478 -0
  167. package/dist-ui/assets/index-D7DoZ9rL.js.map +1 -0
  168. package/dist-ui/index.html +1 -1
  169. package/dist-ui-lib/api/controlPanelApi.d.ts +194 -7
  170. package/dist-ui-lib/dashboard/WidgetComponentRegistry.d.ts +9 -5
  171. package/dist-ui-lib/dashboard/builtInWidgets.d.ts +7 -1
  172. package/dist-ui-lib/dashboard/widgets/AuthStatusWidget.d.ts +9 -0
  173. package/dist-ui-lib/dashboard/widgets/IntegrationStatusWidget.d.ts +9 -0
  174. package/dist-ui-lib/dashboard/widgets/index.d.ts +2 -0
  175. package/dist-ui-lib/index.js +3665 -3945
  176. package/dist-ui-lib/index.js.map +1 -1
  177. package/dist-ui-lib/pages/AuthPage.d.ts +1 -0
  178. package/dist-ui-lib/pages/IntegrationsPage.d.ts +1 -0
  179. package/dist-ui-lib/pages/PluginsPage.d.ts +1 -0
  180. package/dist-ui-lib/pages/RateLimitPage.d.ts +1 -0
  181. package/package.json +7 -2
  182. package/src/core/control-panel.ts +161 -2
  183. package/src/core/plugin-registry.ts +63 -0
  184. package/src/core/types.ts +17 -0
  185. package/src/index.ts +45 -0
  186. package/src/plugins/auth/adapter-wrapper.test.ts +395 -0
  187. package/src/plugins/auth/adapter-wrapper.ts +205 -0
  188. package/src/plugins/auth/adapters/index.ts +1 -0
  189. package/src/plugins/auth/adapters/supabase-adapter.ts +22 -14
  190. package/src/plugins/auth/adapters/supertokens-adapter.ts +326 -0
  191. package/src/plugins/auth/config-store.test.ts +417 -0
  192. package/src/plugins/auth/config-store.ts +305 -0
  193. package/src/plugins/auth/env-config.ts +1279 -0
  194. package/src/plugins/auth/index.ts +30 -0
  195. package/src/plugins/auth/supertokens-adapter.test.ts +621 -0
  196. package/src/plugins/auth/types.ts +218 -0
  197. package/src/plugins/cache-plugin.test.ts +3 -0
  198. package/src/plugins/index.ts +75 -0
  199. package/src/plugins/postgres-plugin.test.ts +3 -0
  200. package/src/plugins/preferences/__tests__/deep-merge.test.ts +242 -0
  201. package/src/plugins/preferences/__tests__/preferences-plugin.test.ts +350 -0
  202. package/src/plugins/preferences/index.ts +30 -0
  203. package/src/plugins/preferences/preferences-plugin.ts +270 -0
  204. package/src/plugins/preferences/stores/index.ts +9 -0
  205. package/src/plugins/preferences/stores/postgres-store.ts +252 -0
  206. package/src/plugins/preferences/types.ts +100 -0
  207. package/src/plugins/rate-limit/__tests__/rate-limit-plugin.test.ts +259 -0
  208. package/src/plugins/rate-limit/cleanup.ts +117 -0
  209. package/src/plugins/rate-limit/env-config.ts +400 -0
  210. package/src/plugins/rate-limit/index.ts +128 -0
  211. package/src/plugins/rate-limit/middleware.ts +212 -0
  212. package/src/plugins/rate-limit/rate-limit-plugin.ts +400 -0
  213. package/src/plugins/rate-limit/rate-limit-service.ts +228 -0
  214. package/src/plugins/rate-limit/stores/cache-store.ts +261 -0
  215. package/src/plugins/rate-limit/stores/index.ts +8 -0
  216. package/src/plugins/rate-limit/stores/postgres-store.ts +402 -0
  217. package/src/plugins/rate-limit/strategies/fixed-window.ts +116 -0
  218. package/src/plugins/rate-limit/strategies/index.ts +30 -0
  219. package/src/plugins/rate-limit/strategies/sliding-window.ts +157 -0
  220. package/src/plugins/rate-limit/strategies/token-bucket.ts +154 -0
  221. package/src/plugins/rate-limit/types.ts +338 -0
  222. package/src/plugins/users/__tests__/users-plugin.test.ts +690 -0
  223. package/src/plugins/users/index.ts +3 -0
  224. package/src/plugins/users/types.ts +38 -0
  225. package/src/plugins/users/users-plugin.ts +142 -5
  226. package/ui/src/App.tsx +35 -14
  227. package/ui/src/api/controlPanelApi.ts +326 -1
  228. package/ui/src/components/ControlPanelApp.tsx +3 -0
  229. package/ui/src/dashboard/PluginWidgetRenderer.tsx +13 -10
  230. package/ui/src/dashboard/WidgetComponentRegistry.tsx +13 -9
  231. package/ui/src/dashboard/builtInWidgets.tsx +13 -3
  232. package/ui/src/dashboard/widgets/AuthStatusWidget.tsx +143 -0
  233. package/ui/src/dashboard/widgets/IntegrationStatusWidget.tsx +135 -0
  234. package/ui/src/dashboard/widgets/index.ts +2 -0
  235. package/ui/src/pages/AuthPage.tsx +1103 -0
  236. package/ui/src/pages/IntegrationsPage.tsx +288 -0
  237. package/ui/src/pages/PluginsPage.tsx +394 -0
  238. package/ui/src/pages/RateLimitPage.tsx +292 -0
  239. package/ui/vite.lib.config.ts +5 -0
  240. package/dist-ui/assets/index-Bsp2ntcw.js +0 -465
  241. package/dist-ui/assets/index-Bsp2ntcw.js.map +0 -1
@@ -0,0 +1 @@
1
+ export declare function AuthPage(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export declare function IntegrationsPage(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export declare function PluginsPage(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export declare function RateLimitPage(): import("react/jsx-runtime").JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qwickapps/server",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Plugin-based application server framework for building websites, APIs, admin dashboards, and full-stack products",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -76,6 +76,7 @@
76
76
  "react": "^18.2.0",
77
77
  "react-dom": "^18.2.0",
78
78
  "react-router-dom": "^6.30.1",
79
+ "supertokens-node": "^20.1.7",
79
80
  "tsx": "^4.20.6",
80
81
  "typescript": "^5.3.3",
81
82
  "vite": "^6.0.0",
@@ -85,7 +86,8 @@
85
86
  "@qwickapps/react-framework": ">=1.0.0",
86
87
  "express-openid-connect": ">=2.0.0",
87
88
  "ioredis": ">=5.0.0",
88
- "pg": ">=8.0.0"
89
+ "pg": ">=8.0.0",
90
+ "supertokens-node": ">=20.0.0"
89
91
  },
90
92
  "peerDependenciesMeta": {
91
93
  "@qwickapps/react-framework": {
@@ -99,6 +101,9 @@
99
101
  },
100
102
  "pg": {
101
103
  "optional": true
104
+ },
105
+ "supertokens-node": {
106
+ "optional": true
102
107
  }
103
108
  },
104
109
  "keywords": [
@@ -197,11 +197,42 @@ export function createControlPanel(options: CreateControlPanelOptions): ControlP
197
197
  });
198
198
 
199
199
  /**
200
- * GET /api/plugins - List all registered plugins
200
+ * GET /api/plugins - List all registered plugins with contribution counts
201
201
  */
202
202
  router.get('/plugins', (_req: Request, res: Response) => {
203
+ const plugins = pluginRegistry.listPlugins().map((plugin) => {
204
+ const contributions = pluginRegistry.getPluginContributions(plugin.id);
205
+ return {
206
+ ...plugin,
207
+ contributionCounts: {
208
+ routes: contributions.routes.length,
209
+ menuItems: contributions.menuItems.length,
210
+ pages: contributions.pages.length,
211
+ widgets: contributions.widgets.length,
212
+ hasConfig: !!contributions.config,
213
+ },
214
+ };
215
+ });
216
+ res.json({ plugins });
217
+ });
218
+
219
+ /**
220
+ * GET /api/plugins/:id - Get detailed plugin info with contributions
221
+ */
222
+ router.get('/plugins/:id', (req: Request, res: Response) => {
223
+ const { id } = req.params;
224
+ const plugins = pluginRegistry.listPlugins();
225
+ const plugin = plugins.find((p) => p.id === id);
226
+
227
+ if (!plugin) {
228
+ res.status(404).json({ error: `Plugin not found: ${id}` });
229
+ return;
230
+ }
231
+
232
+ const contributions = pluginRegistry.getPluginContributions(id);
203
233
  res.json({
204
- plugins: pluginRegistry.listPlugins(),
234
+ ...plugin,
235
+ contributions,
205
236
  });
206
237
  });
207
238
 
@@ -289,6 +320,22 @@ export function createControlPanel(options: CreateControlPanelOptions): ControlP
289
320
  }
290
321
  }
291
322
 
323
+ // Serve landing page at root (/) when control panel is mounted elsewhere
324
+ if (mountPath !== '/' && config.landingPage !== false) {
325
+ const landingConfig = config.landingPage || {};
326
+ app.get('/', (_req: Request, res: Response) => {
327
+ const html = generateLandingPageHtml({
328
+ productName: config.productName,
329
+ title: landingConfig.title || config.productName,
330
+ heading: landingConfig.heading || `Welcome to ${config.productName}`,
331
+ description: landingConfig.description || `${config.productName} is running.`,
332
+ controlPanelPath: mountPath,
333
+ links: landingConfig.links,
334
+ });
335
+ res.type('html').send(html);
336
+ });
337
+ }
338
+
292
339
  // Start a plugin with the registry
293
340
  const startPlugin = async (plugin: Plugin, pluginConfig: PluginConfig = {}): Promise<boolean> => {
294
341
  return pluginRegistry.startPlugin(plugin, pluginConfig);
@@ -533,3 +580,115 @@ function generateDashboardHtml(
533
580
  </body>
534
581
  </html>`;
535
582
  }
583
+
584
+ /**
585
+ * Generate landing page HTML for root path
586
+ */
587
+ function generateLandingPageHtml(options: {
588
+ productName: string;
589
+ title: string;
590
+ heading: string;
591
+ description: string;
592
+ controlPanelPath: string;
593
+ links?: Array<{ label: string; url: string }>;
594
+ }): string {
595
+ const { productName, title, heading, description, controlPanelPath, links = [] } = options;
596
+
597
+ const linksHtml = [
598
+ { label: 'Control Panel', url: controlPanelPath },
599
+ ...links,
600
+ ]
601
+ .map(
602
+ (link) =>
603
+ `<a href="${link.url}" class="link">${link.label}</a>`
604
+ )
605
+ .join('');
606
+
607
+ return `<!DOCTYPE html>
608
+ <html lang="en">
609
+ <head>
610
+ <meta charset="UTF-8">
611
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
612
+ <title>${title}</title>
613
+ <style>
614
+ * { margin: 0; padding: 0; box-sizing: border-box; }
615
+ body {
616
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
617
+ background: linear-gradient(135deg, #0f172a 0%, #1e1b4b 100%);
618
+ color: #e2e8f0;
619
+ min-height: 100vh;
620
+ display: flex;
621
+ align-items: center;
622
+ justify-content: center;
623
+ }
624
+ .container {
625
+ text-align: center;
626
+ max-width: 600px;
627
+ padding: 2rem;
628
+ }
629
+ .logo {
630
+ width: 80px;
631
+ height: 80px;
632
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
633
+ border-radius: 20px;
634
+ margin: 0 auto 2rem;
635
+ display: flex;
636
+ align-items: center;
637
+ justify-content: center;
638
+ font-size: 2rem;
639
+ font-weight: bold;
640
+ }
641
+ h1 {
642
+ font-size: 2.5rem;
643
+ margin-bottom: 1rem;
644
+ background: linear-gradient(135deg, #6366f1 0%, #a78bfa 100%);
645
+ -webkit-background-clip: text;
646
+ -webkit-text-fill-color: transparent;
647
+ background-clip: text;
648
+ }
649
+ p {
650
+ color: #94a3b8;
651
+ font-size: 1.125rem;
652
+ margin-bottom: 2rem;
653
+ line-height: 1.6;
654
+ }
655
+ .links {
656
+ display: flex;
657
+ flex-wrap: wrap;
658
+ gap: 1rem;
659
+ justify-content: center;
660
+ }
661
+ .link {
662
+ display: inline-block;
663
+ padding: 0.875rem 2rem;
664
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
665
+ color: white;
666
+ text-decoration: none;
667
+ border-radius: 0.75rem;
668
+ font-weight: 600;
669
+ transition: transform 0.2s, box-shadow 0.2s;
670
+ }
671
+ .link:hover {
672
+ transform: translateY(-2px);
673
+ box-shadow: 0 10px 25px -5px rgba(99, 102, 241, 0.4);
674
+ }
675
+ .footer {
676
+ margin-top: 3rem;
677
+ color: #475569;
678
+ font-size: 0.875rem;
679
+ }
680
+ </style>
681
+ </head>
682
+ <body>
683
+ <div class="container">
684
+ <div class="logo">${productName.charAt(0)}</div>
685
+ <h1>${heading}</h1>
686
+ <p>${description}</p>
687
+ <div class="links">${linksHtml}</div>
688
+ <div class="footer">
689
+ Powered by QwickApps Server
690
+ </div>
691
+ </div>
692
+ </body>
693
+ </html>`;
694
+ }
@@ -164,6 +164,31 @@ export interface RouteDefinition {
164
164
  pluginId: string;
165
165
  }
166
166
 
167
+ /**
168
+ * Configuration UI contribution for plugin settings
169
+ */
170
+ export interface ConfigContribution {
171
+ /** Unique ID for this config contribution */
172
+ id: string;
173
+ /** React component name to render (matched by frontend registry) */
174
+ component: string;
175
+ /** Display title for the config section */
176
+ title?: string;
177
+ /** Plugin ID that contributed this */
178
+ pluginId: string;
179
+ }
180
+
181
+ /**
182
+ * Aggregated contributions for a specific plugin
183
+ */
184
+ export interface PluginContributions {
185
+ routes: Array<{ method: string; path: string }>;
186
+ menuItems: MenuContribution[];
187
+ pages: PageContribution[];
188
+ widgets: WidgetContribution[];
189
+ config?: ConfigContribution;
190
+ }
191
+
167
192
  // =============================================================================
168
193
  // Plugin Registry Interface
169
194
  // =============================================================================
@@ -204,6 +229,9 @@ export interface PluginRegistry {
204
229
  /** Register a widget */
205
230
  addWidget(widget: WidgetContribution): void;
206
231
 
232
+ /** Register a config component for plugin settings UI */
233
+ addConfigComponent(config: ConfigContribution): void;
234
+
207
235
  // ---------------------------------------------------------------------------
208
236
  // Contribution queries
209
237
  // ---------------------------------------------------------------------------
@@ -220,6 +248,12 @@ export interface PluginRegistry {
220
248
  /** Get all widgets */
221
249
  getWidgets(): WidgetContribution[];
222
250
 
251
+ /** Get all config components */
252
+ getConfigComponents(): ConfigContribution[];
253
+
254
+ /** Get all contributions for a specific plugin */
255
+ getPluginContributions(pluginId: string): PluginContributions;
256
+
223
257
  // ---------------------------------------------------------------------------
224
258
  // Configuration
225
259
  // ---------------------------------------------------------------------------
@@ -292,6 +326,7 @@ export class PluginRegistryImpl implements PluginRegistry {
292
326
  private menuItems: MenuContribution[] = [];
293
327
  private pages: PageContribution[] = [];
294
328
  private widgets: WidgetContribution[] = [];
329
+ private configComponents: ConfigContribution[] = [];
295
330
 
296
331
  private eventHandlers = new Set<PluginEventHandler>();
297
332
 
@@ -385,6 +420,17 @@ export class PluginRegistryImpl implements PluginRegistry {
385
420
  this.logger.debug(`Widget registered: ${widget.title} by ${widget.pluginId}`);
386
421
  }
387
422
 
423
+ addConfigComponent(config: ConfigContribution): void {
424
+ // Only one config component per plugin - warn if replacing
425
+ const existing = this.configComponents.find((c) => c.pluginId === config.pluginId);
426
+ if (existing) {
427
+ this.logger.warn(`Replacing config component for plugin ${config.pluginId}: ${existing.component} → ${config.component}`);
428
+ }
429
+ this.configComponents = this.configComponents.filter((c) => c.pluginId !== config.pluginId);
430
+ this.configComponents.push(config);
431
+ this.logger.debug(`Config component registered: ${config.component} by ${config.pluginId}`);
432
+ }
433
+
388
434
  // ---------------------------------------------------------------------------
389
435
  // Contribution queries
390
436
  // ---------------------------------------------------------------------------
@@ -405,6 +451,22 @@ export class PluginRegistryImpl implements PluginRegistry {
405
451
  return [...this.widgets];
406
452
  }
407
453
 
454
+ getConfigComponents(): ConfigContribution[] {
455
+ return [...this.configComponents];
456
+ }
457
+
458
+ getPluginContributions(pluginId: string): PluginContributions {
459
+ return {
460
+ routes: this.routes
461
+ .filter((r) => r.pluginId === pluginId)
462
+ .map((r) => ({ method: r.method, path: r.path })),
463
+ menuItems: this.menuItems.filter((m) => m.pluginId === pluginId),
464
+ pages: this.pages.filter((p) => p.pluginId === pluginId),
465
+ widgets: this.widgets.filter((w) => w.pluginId === pluginId),
466
+ config: this.configComponents.find((c) => c.pluginId === pluginId),
467
+ };
468
+ }
469
+
408
470
  // ---------------------------------------------------------------------------
409
471
  // Configuration
410
472
  // ---------------------------------------------------------------------------
@@ -577,6 +639,7 @@ export class PluginRegistryImpl implements PluginRegistry {
577
639
  this.menuItems = this.menuItems.filter((m) => m.pluginId !== pluginId);
578
640
  this.pages = this.pages.filter((p) => p.pluginId !== pluginId);
579
641
  this.widgets = this.widgets.filter((w) => w.pluginId !== pluginId);
642
+ this.configComponents = this.configComponents.filter((c) => c.pluginId !== pluginId);
580
643
 
581
644
  this.emit({
582
645
  type: 'plugin:stopped',
package/src/core/types.ts CHANGED
@@ -175,6 +175,23 @@ export interface ControlPanelConfig {
175
175
 
176
176
  /** Optional: Custom path to a dist-ui folder for serving a custom React UI */
177
177
  customUiPath?: string;
178
+
179
+ /**
180
+ * Optional: Landing page configuration for root path (/).
181
+ * Only used when mountPath is not '/'.
182
+ * If not provided, a default landing page is generated.
183
+ * Set to false to disable the landing page.
184
+ */
185
+ landingPage?: {
186
+ /** Page title */
187
+ title?: string;
188
+ /** Main heading */
189
+ heading?: string;
190
+ /** Description text */
191
+ description?: string;
192
+ /** Additional links */
193
+ links?: Array<{ label: string; url: string }>;
194
+ } | false;
178
195
  }
179
196
 
180
197
  // Plugin types are now in plugin-registry.ts
package/src/index.ts CHANGED
@@ -89,6 +89,10 @@ export {
89
89
  hasCache,
90
90
  // Auth plugin
91
91
  createAuthPlugin,
92
+ createAuthPluginFromEnv,
93
+ getAuthStatus,
94
+ setAuthConfigStore,
95
+ postgresAuthConfigStore,
92
96
  isAuthenticated,
93
97
  getAuthenticatedUser,
94
98
  getAccessToken,
@@ -98,6 +102,7 @@ export {
98
102
  auth0Adapter,
99
103
  basicAdapter,
100
104
  supabaseAdapter,
105
+ supertokensAdapter,
101
106
  isAuthenticatedRequest,
102
107
  // Users plugin
103
108
  createUsersPlugin,
@@ -137,6 +142,28 @@ export {
137
142
  requireAnyEntitlement,
138
143
  requireAllEntitlements,
139
144
  postgresEntitlementSource,
145
+ // Rate Limit plugin
146
+ createRateLimitPlugin,
147
+ createRateLimitPluginFromEnv,
148
+ getRateLimitConfigStatus,
149
+ postgresRateLimitStore,
150
+ createRateLimitCache,
151
+ createNoOpCache,
152
+ createSlidingWindowStrategy,
153
+ createFixedWindowStrategy,
154
+ createTokenBucketStrategy,
155
+ getStrategy,
156
+ rateLimitMiddleware,
157
+ rateLimitStatusMiddleware,
158
+ RateLimitService,
159
+ getRateLimitService,
160
+ isLimited,
161
+ checkLimit,
162
+ incrementLimit,
163
+ getRemainingRequests,
164
+ getLimitStatus,
165
+ clearLimit,
166
+ createCleanupJob,
140
167
  } from './plugins/index.js';
141
168
  export type {
142
169
  HealthPluginConfig,
@@ -159,6 +186,12 @@ export type {
159
186
  Auth0AdapterConfig,
160
187
  SupabaseAdapterConfig,
161
188
  BasicAdapterConfig,
189
+ SupertokensAdapterConfig,
190
+ AuthPluginState,
191
+ AuthEnvPluginOptions,
192
+ AuthConfigStatus,
193
+ AuthConfigStore,
194
+ PostgresAuthConfigStoreConfig,
162
195
  // Users plugin types
163
196
  UsersPluginConfig,
164
197
  UserStore,
@@ -191,4 +224,16 @@ export type {
191
224
  UserEntitlement,
192
225
  CachedEntitlements,
193
226
  EntitlementStats,
227
+ // Rate Limit plugin types
228
+ RateLimitPluginConfig,
229
+ RateLimitEnvPluginOptions,
230
+ RateLimitStrategy,
231
+ LimitStatus,
232
+ StoredLimit,
233
+ IncrementOptions,
234
+ RateLimitStore,
235
+ PostgresRateLimitStoreConfig,
236
+ RateLimitCache,
237
+ RateLimitCacheConfig,
238
+ RateLimitMiddlewareOptions,
194
239
  } from './plugins/index.js';