@qwickapps/server 1.2.0 → 1.3.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 (240) hide show
  1. package/README.md +238 -0
  2. package/dist/core/control-panel.d.ts +7 -2
  3. package/dist/core/control-panel.d.ts.map +1 -1
  4. package/dist/core/control-panel.js +92 -54
  5. package/dist/core/control-panel.js.map +1 -1
  6. package/dist/core/gateway.d.ts +159 -79
  7. package/dist/core/gateway.d.ts.map +1 -1
  8. package/dist/core/gateway.js +679 -319
  9. package/dist/core/gateway.js.map +1 -1
  10. package/dist/core/index.d.ts +3 -1
  11. package/dist/core/index.d.ts.map +1 -1
  12. package/dist/core/index.js +2 -0
  13. package/dist/core/index.js.map +1 -1
  14. package/dist/core/plugin-registry.d.ts +271 -0
  15. package/dist/core/plugin-registry.d.ts.map +1 -0
  16. package/dist/core/plugin-registry.js +326 -0
  17. package/dist/core/plugin-registry.js.map +1 -0
  18. package/dist/core/types.d.ts +16 -33
  19. package/dist/core/types.d.ts.map +1 -1
  20. package/dist/index.d.ts +8 -5
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +15 -7
  23. package/dist/index.js.map +1 -1
  24. package/dist/plugins/auth/adapters/auth0-adapter.d.ts +14 -0
  25. package/dist/plugins/auth/adapters/auth0-adapter.d.ts.map +1 -0
  26. package/dist/plugins/auth/adapters/auth0-adapter.js +179 -0
  27. package/dist/plugins/auth/adapters/auth0-adapter.js.map +1 -0
  28. package/dist/plugins/auth/adapters/basic-adapter.d.ts +13 -0
  29. package/dist/plugins/auth/adapters/basic-adapter.d.ts.map +1 -0
  30. package/dist/plugins/auth/adapters/basic-adapter.js +51 -0
  31. package/dist/plugins/auth/adapters/basic-adapter.js.map +1 -0
  32. package/dist/plugins/auth/adapters/index.d.ts +9 -0
  33. package/dist/plugins/auth/adapters/index.d.ts.map +1 -0
  34. package/dist/plugins/auth/adapters/index.js +9 -0
  35. package/dist/plugins/auth/adapters/index.js.map +1 -0
  36. package/dist/plugins/auth/adapters/supabase-adapter.d.ts +13 -0
  37. package/dist/plugins/auth/adapters/supabase-adapter.d.ts.map +1 -0
  38. package/dist/plugins/auth/adapters/supabase-adapter.js +109 -0
  39. package/dist/plugins/auth/adapters/supabase-adapter.js.map +1 -0
  40. package/dist/plugins/auth/auth-plugin.d.ts +40 -0
  41. package/dist/plugins/auth/auth-plugin.d.ts.map +1 -0
  42. package/dist/plugins/auth/auth-plugin.js +255 -0
  43. package/dist/plugins/auth/auth-plugin.js.map +1 -0
  44. package/dist/plugins/auth/auth-plugin.test.d.ts +9 -0
  45. package/dist/plugins/auth/auth-plugin.test.d.ts.map +1 -0
  46. package/dist/plugins/auth/auth-plugin.test.js +147 -0
  47. package/dist/plugins/auth/auth-plugin.test.js.map +1 -0
  48. package/dist/plugins/auth/index.d.ts +12 -0
  49. package/dist/plugins/auth/index.d.ts.map +1 -0
  50. package/dist/plugins/auth/index.js +13 -0
  51. package/dist/plugins/auth/index.js.map +1 -0
  52. package/dist/plugins/auth/types.d.ts +148 -0
  53. package/dist/plugins/auth/types.d.ts.map +1 -0
  54. package/dist/plugins/auth/types.js +14 -0
  55. package/dist/plugins/auth/types.js.map +1 -0
  56. package/dist/plugins/bans/bans-plugin.d.ts +59 -0
  57. package/dist/plugins/bans/bans-plugin.d.ts.map +1 -0
  58. package/dist/plugins/bans/bans-plugin.js +428 -0
  59. package/dist/plugins/bans/bans-plugin.js.map +1 -0
  60. package/dist/plugins/bans/index.d.ts +9 -0
  61. package/dist/plugins/bans/index.d.ts.map +1 -0
  62. package/dist/plugins/bans/index.js +10 -0
  63. package/dist/plugins/bans/index.js.map +1 -0
  64. package/dist/plugins/bans/stores/index.d.ts +7 -0
  65. package/dist/plugins/bans/stores/index.d.ts.map +1 -0
  66. package/dist/plugins/bans/stores/index.js +7 -0
  67. package/dist/plugins/bans/stores/index.js.map +1 -0
  68. package/dist/plugins/bans/stores/postgres-store.d.ts +29 -0
  69. package/dist/plugins/bans/stores/postgres-store.d.ts.map +1 -0
  70. package/dist/plugins/bans/stores/postgres-store.js +132 -0
  71. package/dist/plugins/bans/stores/postgres-store.js.map +1 -0
  72. package/dist/plugins/bans/types.d.ts +128 -0
  73. package/dist/plugins/bans/types.d.ts.map +1 -0
  74. package/dist/plugins/bans/types.js +11 -0
  75. package/dist/plugins/bans/types.js.map +1 -0
  76. package/dist/plugins/cache-plugin.d.ts +14 -3
  77. package/dist/plugins/cache-plugin.d.ts.map +1 -1
  78. package/dist/plugins/cache-plugin.js +27 -7
  79. package/dist/plugins/cache-plugin.js.map +1 -1
  80. package/dist/plugins/cache-plugin.test.js +96 -32
  81. package/dist/plugins/cache-plugin.test.js.map +1 -1
  82. package/dist/plugins/config-plugin.d.ts +3 -2
  83. package/dist/plugins/config-plugin.d.ts.map +1 -1
  84. package/dist/plugins/config-plugin.js +17 -10
  85. package/dist/plugins/config-plugin.js.map +1 -1
  86. package/dist/plugins/diagnostics-plugin.d.ts +2 -2
  87. package/dist/plugins/diagnostics-plugin.d.ts.map +1 -1
  88. package/dist/plugins/diagnostics-plugin.js +17 -10
  89. package/dist/plugins/diagnostics-plugin.js.map +1 -1
  90. package/dist/plugins/entitlements/entitlements-plugin.d.ts +95 -0
  91. package/dist/plugins/entitlements/entitlements-plugin.d.ts.map +1 -0
  92. package/dist/plugins/entitlements/entitlements-plugin.js +707 -0
  93. package/dist/plugins/entitlements/entitlements-plugin.js.map +1 -0
  94. package/dist/plugins/entitlements/index.d.ts +12 -0
  95. package/dist/plugins/entitlements/index.d.ts.map +1 -0
  96. package/dist/plugins/entitlements/index.js +16 -0
  97. package/dist/plugins/entitlements/index.js.map +1 -0
  98. package/dist/plugins/entitlements/sources/index.d.ts +9 -0
  99. package/dist/plugins/entitlements/sources/index.d.ts.map +1 -0
  100. package/dist/plugins/entitlements/sources/index.js +9 -0
  101. package/dist/plugins/entitlements/sources/index.js.map +1 -0
  102. package/dist/plugins/entitlements/sources/postgres-source.d.ts +29 -0
  103. package/dist/plugins/entitlements/sources/postgres-source.d.ts.map +1 -0
  104. package/dist/plugins/entitlements/sources/postgres-source.js +169 -0
  105. package/dist/plugins/entitlements/sources/postgres-source.js.map +1 -0
  106. package/dist/plugins/entitlements/types.d.ts +232 -0
  107. package/dist/plugins/entitlements/types.d.ts.map +1 -0
  108. package/dist/plugins/entitlements/types.js +11 -0
  109. package/dist/plugins/entitlements/types.js.map +1 -0
  110. package/dist/plugins/frontend-app-plugin.d.ts +9 -3
  111. package/dist/plugins/frontend-app-plugin.d.ts.map +1 -1
  112. package/dist/plugins/frontend-app-plugin.js +14 -9
  113. package/dist/plugins/frontend-app-plugin.js.map +1 -1
  114. package/dist/plugins/health-plugin.d.ts +5 -2
  115. package/dist/plugins/health-plugin.d.ts.map +1 -1
  116. package/dist/plugins/health-plugin.js +20 -5
  117. package/dist/plugins/health-plugin.js.map +1 -1
  118. package/dist/plugins/index.d.ts +8 -2
  119. package/dist/plugins/index.d.ts.map +1 -1
  120. package/dist/plugins/index.js +8 -2
  121. package/dist/plugins/index.js.map +1 -1
  122. package/dist/plugins/logs-plugin.d.ts +3 -2
  123. package/dist/plugins/logs-plugin.d.ts.map +1 -1
  124. package/dist/plugins/logs-plugin.js +21 -12
  125. package/dist/plugins/logs-plugin.js.map +1 -1
  126. package/dist/plugins/postgres-plugin.d.ts +3 -3
  127. package/dist/plugins/postgres-plugin.d.ts.map +1 -1
  128. package/dist/plugins/postgres-plugin.js +9 -7
  129. package/dist/plugins/postgres-plugin.js.map +1 -1
  130. package/dist/plugins/postgres-plugin.test.js +47 -29
  131. package/dist/plugins/postgres-plugin.test.js.map +1 -1
  132. package/dist/plugins/users/index.d.ts +12 -0
  133. package/dist/plugins/users/index.d.ts.map +1 -0
  134. package/dist/plugins/users/index.js +13 -0
  135. package/dist/plugins/users/index.js.map +1 -0
  136. package/dist/plugins/users/stores/index.d.ts +7 -0
  137. package/dist/plugins/users/stores/index.d.ts.map +1 -0
  138. package/dist/plugins/users/stores/index.js +7 -0
  139. package/dist/plugins/users/stores/index.js.map +1 -0
  140. package/dist/plugins/users/stores/postgres-store.d.ts +28 -0
  141. package/dist/plugins/users/stores/postgres-store.d.ts.map +1 -0
  142. package/dist/plugins/users/stores/postgres-store.js +157 -0
  143. package/dist/plugins/users/stores/postgres-store.js.map +1 -0
  144. package/dist/plugins/users/types.d.ts +189 -0
  145. package/dist/plugins/users/types.d.ts.map +1 -0
  146. package/dist/plugins/users/types.js +12 -0
  147. package/dist/plugins/users/types.js.map +1 -0
  148. package/dist/plugins/users/users-plugin.d.ts +39 -0
  149. package/dist/plugins/users/users-plugin.d.ts.map +1 -0
  150. package/dist/plugins/users/users-plugin.js +242 -0
  151. package/dist/plugins/users/users-plugin.js.map +1 -0
  152. package/dist-ui/assets/index-Bsp2ntcw.js +465 -0
  153. package/dist-ui/assets/index-Bsp2ntcw.js.map +1 -0
  154. package/dist-ui/index.html +1 -1
  155. package/dist-ui-lib/api/controlPanelApi.d.ts +232 -0
  156. package/dist-ui-lib/components/ControlPanelApp.d.ts +61 -0
  157. package/dist-ui-lib/components/index.d.ts +18 -0
  158. package/dist-ui-lib/config/AppConfig.d.ts +7 -0
  159. package/dist-ui-lib/dashboard/DashboardWidgetRegistry.d.ts +62 -0
  160. package/dist-ui-lib/dashboard/DashboardWidgetRenderer.d.ts +8 -0
  161. package/dist-ui-lib/dashboard/PluginWidgetRenderer.d.ts +19 -0
  162. package/dist-ui-lib/dashboard/WidgetComponentRegistry.d.ts +44 -0
  163. package/dist-ui-lib/dashboard/builtInWidgets.d.ts +19 -0
  164. package/dist-ui-lib/dashboard/index.d.ts +13 -0
  165. package/dist-ui-lib/dashboard/widgets/ServiceHealthWidget.d.ts +12 -0
  166. package/dist-ui-lib/dashboard/widgets/index.d.ts +6 -0
  167. package/dist-ui-lib/index.js +6441 -0
  168. package/dist-ui-lib/index.js.map +1 -0
  169. package/dist-ui-lib/pages/ConfigPage.d.ts +1 -0
  170. package/dist-ui-lib/pages/DashboardPage.d.ts +1 -0
  171. package/dist-ui-lib/pages/DiagnosticsPage.d.ts +1 -0
  172. package/dist-ui-lib/pages/EntitlementsPage.d.ts +17 -0
  173. package/dist-ui-lib/pages/LogsPage.d.ts +1 -0
  174. package/dist-ui-lib/pages/NotFoundPage.d.ts +1 -0
  175. package/dist-ui-lib/pages/PluginPage.d.ts +15 -0
  176. package/dist-ui-lib/pages/SystemPage.d.ts +1 -0
  177. package/dist-ui-lib/pages/UsersPage.d.ts +22 -0
  178. package/package.json +18 -6
  179. package/src/core/control-panel.ts +114 -61
  180. package/src/core/gateway.ts +863 -403
  181. package/src/core/index.ts +21 -2
  182. package/src/core/plugin-registry.ts +653 -0
  183. package/src/core/types.ts +31 -37
  184. package/src/index.ts +118 -19
  185. package/src/plugins/auth/adapters/auth0-adapter.ts +214 -0
  186. package/src/plugins/auth/adapters/basic-adapter.ts +61 -0
  187. package/src/plugins/auth/adapters/index.ts +9 -0
  188. package/src/plugins/auth/adapters/supabase-adapter.ts +141 -0
  189. package/src/plugins/auth/auth-plugin.test.ts +176 -0
  190. package/src/plugins/auth/auth-plugin.ts +303 -0
  191. package/src/plugins/auth/index.ts +33 -0
  192. package/src/plugins/auth/types.ts +165 -0
  193. package/src/plugins/bans/bans-plugin.ts +485 -0
  194. package/src/plugins/bans/index.ts +31 -0
  195. package/src/plugins/bans/stores/index.ts +7 -0
  196. package/src/plugins/bans/stores/postgres-store.ts +195 -0
  197. package/src/plugins/bans/types.ts +141 -0
  198. package/src/plugins/cache-plugin.test.ts +105 -32
  199. package/src/plugins/cache-plugin.ts +40 -9
  200. package/src/plugins/config-plugin.ts +23 -12
  201. package/src/plugins/diagnostics-plugin.ts +22 -12
  202. package/src/plugins/entitlements/entitlements-plugin.ts +820 -0
  203. package/src/plugins/entitlements/index.ts +51 -0
  204. package/src/plugins/entitlements/sources/index.ts +9 -0
  205. package/src/plugins/entitlements/sources/postgres-source.ts +253 -0
  206. package/src/plugins/entitlements/types.ts +256 -0
  207. package/src/plugins/frontend-app-plugin.ts +24 -12
  208. package/src/plugins/health-plugin.ts +27 -7
  209. package/src/plugins/index.ts +106 -4
  210. package/src/plugins/logs-plugin.ts +28 -14
  211. package/src/plugins/postgres-plugin.test.ts +49 -29
  212. package/src/plugins/postgres-plugin.ts +11 -9
  213. package/src/plugins/users/index.ts +35 -0
  214. package/src/plugins/users/stores/index.ts +7 -0
  215. package/src/plugins/users/stores/postgres-store.ts +225 -0
  216. package/src/plugins/users/types.ts +209 -0
  217. package/src/plugins/users/users-plugin.ts +281 -0
  218. package/ui/src/App.tsx +185 -31
  219. package/ui/src/api/controlPanelApi.ts +354 -1
  220. package/ui/src/components/ControlPanelApp.tsx +209 -0
  221. package/ui/src/components/index.ts +62 -0
  222. package/ui/src/dashboard/DashboardWidgetRegistry.tsx +129 -0
  223. package/ui/src/dashboard/DashboardWidgetRenderer.tsx +34 -0
  224. package/ui/src/dashboard/PluginWidgetRenderer.tsx +115 -0
  225. package/ui/src/dashboard/WidgetComponentRegistry.tsx +116 -0
  226. package/ui/src/dashboard/builtInWidgets.tsx +29 -0
  227. package/ui/src/dashboard/index.ts +35 -0
  228. package/ui/src/dashboard/widgets/ServiceHealthWidget.tsx +140 -0
  229. package/ui/src/dashboard/widgets/index.ts +7 -0
  230. package/ui/src/pages/DashboardPage.tsx +28 -149
  231. package/ui/src/pages/EntitlementsPage.tsx +557 -0
  232. package/ui/src/pages/LogsPage.tsx +174 -8
  233. package/ui/src/pages/PluginPage.tsx +148 -0
  234. package/ui/src/pages/SystemPage.tsx +445 -0
  235. package/ui/src/pages/UsersPage.tsx +837 -0
  236. package/ui/tsconfig.lib.json +11 -0
  237. package/ui/vite.lib.config.ts +51 -0
  238. package/dist-ui/assets/index-CW1BviRn.js +0 -465
  239. package/dist-ui/assets/index-CW1BviRn.js.map +0 -1
  240. package/ui/src/pages/HealthPage.tsx +0 -204
@@ -0,0 +1,209 @@
1
+ /**
2
+ * ControlPanelApp Component
3
+ *
4
+ * A wrapper around QwickApp that provides control panel functionality.
5
+ * Injects base control panel routes (Dashboard, Health, Logs, System)
6
+ * and allows consumers to add custom routes.
7
+ *
8
+ * Usage:
9
+ * ```tsx
10
+ * import { ControlPanelApp } from '@qwickapps/server/ui';
11
+ *
12
+ * function App() {
13
+ * return (
14
+ * <ControlPanelApp
15
+ * productName="My Service"
16
+ * logo={<MyLogo />}
17
+ * customDashboard={<MyDashboard />}
18
+ * >
19
+ * <Route path="/users" element={<UsersPage />} />
20
+ * <Route path="/settings" element={<SettingsPage />} />
21
+ * </ControlPanelApp>
22
+ * );
23
+ * }
24
+ * ```
25
+ *
26
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
27
+ */
28
+
29
+ import { ReactNode, useState, useEffect } from 'react';
30
+ import { Routes, Route } from 'react-router-dom';
31
+ import { Box, Link } from '@mui/material';
32
+ import { QwickApp, ProductLogo, Text, type MenuItem } from '@qwickapps/react-framework';
33
+ import { defaultConfig } from '../config/AppConfig';
34
+
35
+ // Base pages
36
+ import { DashboardPage } from '../pages/DashboardPage';
37
+ import { LogsPage } from '../pages/LogsPage';
38
+ import { SystemPage } from '../pages/SystemPage';
39
+ import { NotFoundPage } from '../pages/NotFoundPage';
40
+
41
+ // Dashboard widget system
42
+ import {
43
+ DashboardWidgetProvider,
44
+ WidgetComponentRegistryProvider,
45
+ getBuiltInWidgetComponents,
46
+ type DashboardWidget,
47
+ type WidgetComponent,
48
+ } from '../dashboard';
49
+
50
+ // API
51
+ import { api } from '../api/controlPanelApi';
52
+
53
+ export interface ControlPanelAppProps {
54
+ /** Product name displayed in the header */
55
+ productName?: string;
56
+
57
+ /** Custom logo component */
58
+ logo?: ReactNode;
59
+
60
+ /** Custom footer content (replaces default) */
61
+ footerContent?: ReactNode;
62
+
63
+ /** Initial dashboard widgets to register (legacy context-based system) */
64
+ dashboardWidgets?: DashboardWidget[];
65
+
66
+ /**
67
+ * Widget components to register for the plugin-based widget system.
68
+ * These map component names (from server WidgetContribution) to React components.
69
+ * Built-in widgets (ServiceHealthWidget, etc.) are registered automatically.
70
+ */
71
+ widgetComponents?: WidgetComponent[];
72
+
73
+ /** Additional navigation items to add to the base control panel nav */
74
+ navigationItems?: MenuItem[];
75
+
76
+ /** Whether to show the base control panel navigation (Dashboard, Health, etc.) */
77
+ showBaseNavigation?: boolean;
78
+
79
+ /** Base navigation item IDs to hide (e.g., ['health'] to hide the Health page) */
80
+ hideBaseNavItems?: string[];
81
+
82
+ /** Whether to show theme switcher in settings */
83
+ showThemeSwitcher?: boolean;
84
+
85
+ /** Whether to show palette switcher in settings */
86
+ showPaletteSwitcher?: boolean;
87
+
88
+ /** Base path for the control panel (e.g., '/cpanel') */
89
+ basePath?: string;
90
+
91
+ /** Custom routes to add (as Route elements) */
92
+ children?: ReactNode;
93
+ }
94
+
95
+ /**
96
+ * Default footer with QwickApps Server branding
97
+ */
98
+ function DefaultFooter({ version }: { version: string }) {
99
+ return (
100
+ <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 0.5, py: 2 }}>
101
+ <Text variant="caption" customColor="var(--theme-text-secondary)">
102
+ Built with{' '}
103
+ <Link
104
+ href="https://qwickapps.com/products/qwickapps-server"
105
+ target="_blank"
106
+ rel="noopener noreferrer"
107
+ sx={{ color: 'primary.main' }}
108
+ >
109
+ QwickApps Server
110
+ </Link>
111
+ {version && ` v${version}`}
112
+ </Text>
113
+ </Box>
114
+ );
115
+ }
116
+
117
+ /**
118
+ * Base navigation items for control panel
119
+ * Routes are relative to BrowserRouter's basename (handled by NavigationProvider in QwickApp)
120
+ */
121
+ function getBaseNavigationItems(): MenuItem[] {
122
+ return [
123
+ { id: 'dashboard', label: 'Dashboard', route: '/', icon: 'dashboard' },
124
+ { id: 'logs', label: 'Logs', route: '/logs', icon: 'article' },
125
+ { id: 'system', label: 'System', route: '/system', icon: 'settings' },
126
+ ];
127
+ }
128
+
129
+ export function ControlPanelApp({
130
+ productName = 'Control Panel',
131
+ logo,
132
+ footerContent,
133
+ dashboardWidgets = [],
134
+ widgetComponents = [],
135
+ navigationItems = [],
136
+ showBaseNavigation = true,
137
+ hideBaseNavItems = [],
138
+ showThemeSwitcher = true,
139
+ showPaletteSwitcher = true,
140
+ basePath = '',
141
+ children,
142
+ }: ControlPanelAppProps) {
143
+ const [version, setVersion] = useState<string>('');
144
+
145
+ // Combine built-in widget components with custom ones
146
+ const allWidgetComponents = [...getBuiltInWidgetComponents(), ...widgetComponents];
147
+
148
+ // Configure API base URL based on basePath - do this synchronously before any renders
149
+ // If basePath is '/cpanel', API is at '/cpanel/api'
150
+ // If basePath is '' or '/', API is at '/api'
151
+ const apiBasePath = basePath && basePath !== '/' ? basePath : '';
152
+ api.setBaseUrl(apiBasePath);
153
+
154
+ // Fetch version from API
155
+ useEffect(() => {
156
+ api.getInfo()
157
+ .then((info) => setVersion(info.version || ''))
158
+ .catch(() => {});
159
+ }, [apiBasePath]); // Re-fetch when apiBasePath changes
160
+
161
+ // Build navigation: base items (filtered by hideBaseNavItems) + custom items
162
+ // Navigation routes are relative to BrowserRouter's basename
163
+ const filteredBaseItems = showBaseNavigation
164
+ ? getBaseNavigationItems().filter(item => !hideBaseNavItems.includes(item.id))
165
+ : [];
166
+ const allNavigationItems: MenuItem[] = [
167
+ ...filteredBaseItems,
168
+ ...navigationItems,
169
+ ];
170
+
171
+ // Default logo if not provided
172
+ const effectiveLogo = logo || <ProductLogo name={productName} />;
173
+
174
+ // Default footer if not provided
175
+ const effectiveFooter = footerContent || <DefaultFooter version={version} />;
176
+
177
+ return (
178
+ <WidgetComponentRegistryProvider initialComponents={allWidgetComponents}>
179
+ <DashboardWidgetProvider initialWidgets={dashboardWidgets}>
180
+ <QwickApp
181
+ config={defaultConfig}
182
+ logo={effectiveLogo}
183
+ footerContent={effectiveFooter}
184
+ enableScaffolding={true}
185
+ navigationItems={allNavigationItems}
186
+ showThemeSwitcher={showThemeSwitcher}
187
+ showPaletteSwitcher={showPaletteSwitcher}
188
+ >
189
+ <Routes>
190
+ {/* Base control panel routes (filtered by hideBaseNavItems) */}
191
+ {showBaseNavigation && (
192
+ <>
193
+ {!hideBaseNavItems.includes('dashboard') && <Route path="/" element={<DashboardPage />} />}
194
+ {!hideBaseNavItems.includes('logs') && <Route path="/logs" element={<LogsPage />} />}
195
+ {!hideBaseNavItems.includes('system') && <Route path="/system" element={<SystemPage />} />}
196
+ </>
197
+ )}
198
+
199
+ {/* Custom routes from consumer */}
200
+ {children}
201
+
202
+ {/* Catch-all for 404 */}
203
+ <Route path="*" element={<NotFoundPage />} />
204
+ </Routes>
205
+ </QwickApp>
206
+ </DashboardWidgetProvider>
207
+ </WidgetComponentRegistryProvider>
208
+ );
209
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Control Panel UI Components
3
+ *
4
+ * Re-exports all public UI components for use by consumers.
5
+ *
6
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
7
+ */
8
+
9
+ export { ControlPanelApp, type ControlPanelAppProps } from './ControlPanelApp';
10
+
11
+ // Re-export MenuItem from react-framework for convenience
12
+ export type { MenuItem } from '@qwickapps/react-framework';
13
+
14
+ // Re-export base pages for consumers who want to use them directly
15
+ export { DashboardPage } from '../pages/DashboardPage';
16
+ export { LogsPage } from '../pages/LogsPage';
17
+ export { SystemPage } from '../pages/SystemPage';
18
+ export { NotFoundPage } from '../pages/NotFoundPage';
19
+ export { UsersPage, type UsersPageProps } from '../pages/UsersPage';
20
+ export { EntitlementsPage, type EntitlementsPageProps } from '../pages/EntitlementsPage';
21
+
22
+ // Re-export dashboard widget system (legacy context-based + new plugin-based)
23
+ export {
24
+ // Legacy context-based widget system
25
+ DashboardWidgetProvider,
26
+ useDashboardWidgets,
27
+ useRegisterWidget,
28
+ DashboardWidgetRenderer,
29
+ type DashboardWidget,
30
+ type DashboardWidgetProviderProps,
31
+ // New plugin-based widget system
32
+ WidgetComponentRegistryProvider,
33
+ useWidgetComponentRegistry,
34
+ PluginWidgetRenderer,
35
+ getBuiltInWidgetComponents,
36
+ ServiceHealthWidget,
37
+ type WidgetComponent,
38
+ type WidgetComponentRegistryProviderProps,
39
+ } from '../dashboard';
40
+
41
+ // Re-export API client and types
42
+ export { api } from '../api/controlPanelApi';
43
+ export type {
44
+ HealthCheck,
45
+ HealthResponse,
46
+ InfoResponse,
47
+ DiagnosticsResponse,
48
+ ConfigResponse,
49
+ LogEntry,
50
+ LogsResponse,
51
+ LogSource,
52
+ // User management types
53
+ User,
54
+ UsersResponse,
55
+ Ban,
56
+ BansResponse,
57
+ EntitlementDefinition,
58
+ EntitlementResult,
59
+ EntitlementSourceInfo,
60
+ EntitlementsStatus,
61
+ PluginFeatures,
62
+ } from '../api/controlPanelApi';
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Dashboard Widget Registry
3
+ *
4
+ * A context-based registry for dashboard widgets that allows:
5
+ * - Registration of widgets at runtime
6
+ * - Dynamic adding/removing of widgets
7
+ * - Priority-based ordering of widgets
8
+ *
9
+ * Usage:
10
+ * ```tsx
11
+ * // In your app setup:
12
+ * const { registerWidget } = useDashboardWidgets();
13
+ * registerWidget({
14
+ * id: 'user-stats',
15
+ * title: 'User Statistics',
16
+ * component: <UserStatsWidget />,
17
+ * priority: 10,
18
+ * });
19
+ *
20
+ * // Or via the provider:
21
+ * <DashboardWidgetProvider initialWidgets={[...]} />
22
+ * ```
23
+ *
24
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
25
+ */
26
+
27
+ import { createContext, useContext, useState, useCallback, ReactNode } from 'react';
28
+
29
+ export interface DashboardWidget {
30
+ /** Unique identifier for the widget */
31
+ id: string;
32
+ /** Display title for the widget section */
33
+ title?: string;
34
+ /** The widget component to render */
35
+ component: ReactNode;
36
+ /** Priority for ordering (lower = first, default: 100) */
37
+ priority?: number;
38
+ /** Whether the widget is visible */
39
+ visible?: boolean;
40
+ }
41
+
42
+ interface DashboardWidgetContextValue {
43
+ /** All registered widgets */
44
+ widgets: DashboardWidget[];
45
+ /** Register a new widget */
46
+ registerWidget: (widget: DashboardWidget) => void;
47
+ /** Unregister a widget by ID */
48
+ unregisterWidget: (id: string) => void;
49
+ /** Toggle widget visibility */
50
+ toggleWidget: (id: string, visible?: boolean) => void;
51
+ /** Get visible widgets sorted by priority */
52
+ getVisibleWidgets: () => DashboardWidget[];
53
+ }
54
+
55
+ const DashboardWidgetContext = createContext<DashboardWidgetContextValue | null>(null);
56
+
57
+ export interface DashboardWidgetProviderProps {
58
+ /** Initial widgets to register */
59
+ initialWidgets?: DashboardWidget[];
60
+ children: ReactNode;
61
+ }
62
+
63
+ export function DashboardWidgetProvider({ initialWidgets = [], children }: DashboardWidgetProviderProps) {
64
+ const [widgets, setWidgets] = useState<DashboardWidget[]>(
65
+ initialWidgets.map(w => ({ ...w, visible: w.visible !== false, priority: w.priority ?? 100 }))
66
+ );
67
+
68
+ const registerWidget = useCallback((widget: DashboardWidget) => {
69
+ setWidgets(prev => {
70
+ // Check if widget already exists
71
+ const exists = prev.some(w => w.id === widget.id);
72
+ if (exists) {
73
+ // Update existing widget
74
+ return prev.map(w => w.id === widget.id ? { ...widget, visible: widget.visible !== false, priority: widget.priority ?? 100 } : w);
75
+ }
76
+ // Add new widget
77
+ return [...prev, { ...widget, visible: widget.visible !== false, priority: widget.priority ?? 100 }];
78
+ });
79
+ }, []);
80
+
81
+ const unregisterWidget = useCallback((id: string) => {
82
+ setWidgets(prev => prev.filter(w => w.id !== id));
83
+ }, []);
84
+
85
+ const toggleWidget = useCallback((id: string, visible?: boolean) => {
86
+ setWidgets(prev => prev.map(w => {
87
+ if (w.id === id) {
88
+ return { ...w, visible: visible ?? !w.visible };
89
+ }
90
+ return w;
91
+ }));
92
+ }, []);
93
+
94
+ const getVisibleWidgets = useCallback(() => {
95
+ return widgets
96
+ .filter(w => w.visible !== false)
97
+ .sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));
98
+ }, [widgets]);
99
+
100
+ return (
101
+ <DashboardWidgetContext.Provider value={{ widgets, registerWidget, unregisterWidget, toggleWidget, getVisibleWidgets }}>
102
+ {children}
103
+ </DashboardWidgetContext.Provider>
104
+ );
105
+ }
106
+
107
+ export function useDashboardWidgets() {
108
+ const context = useContext(DashboardWidgetContext);
109
+ if (!context) {
110
+ throw new Error('useDashboardWidgets must be used within a DashboardWidgetProvider');
111
+ }
112
+ return context;
113
+ }
114
+
115
+ /**
116
+ * Hook to register a widget on mount and unregister on unmount
117
+ */
118
+ export function useRegisterWidget(widget: DashboardWidget) {
119
+ const { registerWidget, unregisterWidget } = useDashboardWidgets();
120
+
121
+ // Register on mount
122
+ useState(() => {
123
+ registerWidget(widget);
124
+ return null;
125
+ });
126
+
127
+ // Return unregister function for manual cleanup
128
+ return () => unregisterWidget(widget.id);
129
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Dashboard Widget Renderer
3
+ *
4
+ * Renders all visible dashboard widgets from the registry.
5
+ *
6
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
7
+ */
8
+
9
+ import { Box, Typography } from '@mui/material';
10
+ import { useDashboardWidgets } from './DashboardWidgetRegistry';
11
+
12
+ export function DashboardWidgetRenderer() {
13
+ const { getVisibleWidgets } = useDashboardWidgets();
14
+ const widgets = getVisibleWidgets();
15
+
16
+ if (widgets.length === 0) {
17
+ return null;
18
+ }
19
+
20
+ return (
21
+ <>
22
+ {widgets.map(widget => (
23
+ <Box key={widget.id} sx={{ mt: 4 }}>
24
+ {widget.title && (
25
+ <Typography variant="h6" sx={{ mb: 2, color: 'var(--theme-text-primary)' }}>
26
+ {widget.title}
27
+ </Typography>
28
+ )}
29
+ {widget.component}
30
+ </Box>
31
+ ))}
32
+ </>
33
+ );
34
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Plugin Widget Renderer
3
+ *
4
+ * Fetches widget contributions from the server API and renders them using
5
+ * the WidgetComponentRegistry to resolve component names to React components.
6
+ *
7
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
8
+ */
9
+
10
+ import { useState, useEffect } from 'react';
11
+ import { Box, Typography, CircularProgress, Alert } from '@mui/material';
12
+ import { api } from '../api/controlPanelApi';
13
+ import { useWidgetComponentRegistry } from './WidgetComponentRegistry';
14
+
15
+ /**
16
+ * Widget contribution from the server
17
+ */
18
+ interface WidgetContribution {
19
+ id: string;
20
+ title: string;
21
+ component: string;
22
+ priority?: number;
23
+ showByDefault?: boolean;
24
+ pluginId: string;
25
+ }
26
+
27
+ interface PluginWidgetRendererProps {
28
+ /** Only show widgets marked as showByDefault (default: true) */
29
+ defaultOnly?: boolean;
30
+ /** Additional widget IDs to show (beyond showByDefault) */
31
+ additionalWidgetIds?: string[];
32
+ }
33
+
34
+ /**
35
+ * Renders widgets from plugins that have registered them via the server API
36
+ */
37
+ export function PluginWidgetRenderer({
38
+ defaultOnly = true,
39
+ additionalWidgetIds = [],
40
+ }: PluginWidgetRendererProps) {
41
+ const [widgets, setWidgets] = useState<WidgetContribution[]>([]);
42
+ const [loading, setLoading] = useState(true);
43
+ const [error, setError] = useState<string | null>(null);
44
+ const { getComponent, hasComponent } = useWidgetComponentRegistry();
45
+
46
+ useEffect(() => {
47
+ const fetchWidgets = async () => {
48
+ try {
49
+ const data = await api.getUiContributions();
50
+ setWidgets(data.widgets || []);
51
+ setError(null);
52
+ } catch (err) {
53
+ setError(err instanceof Error ? err.message : 'Failed to fetch widgets');
54
+ } finally {
55
+ setLoading(false);
56
+ }
57
+ };
58
+
59
+ fetchWidgets();
60
+ }, []);
61
+
62
+ if (loading) {
63
+ return (
64
+ <Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
65
+ <CircularProgress size={24} />
66
+ </Box>
67
+ );
68
+ }
69
+
70
+ if (error) {
71
+ return (
72
+ <Alert severity="error" sx={{ mt: 2 }}>
73
+ {error}
74
+ </Alert>
75
+ );
76
+ }
77
+
78
+ // Filter widgets to show
79
+ const visibleWidgets = widgets
80
+ .filter(widget => {
81
+ // Show if marked as default, or if in additionalWidgetIds
82
+ if (defaultOnly) {
83
+ return widget.showByDefault || additionalWidgetIds.includes(widget.id);
84
+ }
85
+ return true;
86
+ })
87
+ .filter(widget => {
88
+ // Only show widgets that have a registered component
89
+ if (!hasComponent(widget.component)) {
90
+ console.warn(`Widget "${widget.id}" references unregistered component "${widget.component}"`);
91
+ return false;
92
+ }
93
+ return true;
94
+ })
95
+ .sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));
96
+
97
+ if (visibleWidgets.length === 0) {
98
+ return null;
99
+ }
100
+
101
+ return (
102
+ <>
103
+ {visibleWidgets.map(widget => (
104
+ <Box key={widget.id} sx={{ mt: 4 }}>
105
+ {widget.title && (
106
+ <Typography variant="h6" sx={{ mb: 2, color: 'var(--theme-text-primary)' }}>
107
+ {widget.title}
108
+ </Typography>
109
+ )}
110
+ {getComponent(widget.component)}
111
+ </Box>
112
+ ))}
113
+ </>
114
+ );
115
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Widget Component Registry
3
+ *
4
+ * Maps widget component names (from server-side WidgetContribution) to actual React components.
5
+ * Plugins register their widgets here so the dashboard can render them.
6
+ *
7
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
8
+ */
9
+
10
+ import { ReactNode, createContext, useContext, useState, useCallback, useMemo } from 'react';
11
+
12
+ /**
13
+ * Widget component definition
14
+ */
15
+ export interface WidgetComponent {
16
+ /** Component name (must match server-side WidgetContribution.component) */
17
+ name: string;
18
+ /** The React component to render */
19
+ component: ReactNode;
20
+ }
21
+
22
+ interface WidgetComponentRegistryContextValue {
23
+ /** Register a widget component */
24
+ registerComponent: (name: string, component: ReactNode) => void;
25
+ /** Register multiple widget components */
26
+ registerComponents: (components: WidgetComponent[]) => void;
27
+ /** Get a component by name */
28
+ getComponent: (name: string) => ReactNode | null;
29
+ /** Check if a component is registered */
30
+ hasComponent: (name: string) => boolean;
31
+ /** Get all registered component names */
32
+ getRegisteredNames: () => string[];
33
+ }
34
+
35
+ const WidgetComponentRegistryContext = createContext<WidgetComponentRegistryContextValue | null>(null);
36
+
37
+ export interface WidgetComponentRegistryProviderProps {
38
+ /** Initial components to register */
39
+ initialComponents?: WidgetComponent[];
40
+ children: ReactNode;
41
+ }
42
+
43
+ /**
44
+ * Provider for the widget component registry
45
+ */
46
+ export function WidgetComponentRegistryProvider({
47
+ initialComponents = [],
48
+ children,
49
+ }: WidgetComponentRegistryProviderProps) {
50
+ const [components, setComponents] = useState<Map<string, ReactNode>>(() => {
51
+ const map = new Map<string, ReactNode>();
52
+ for (const comp of initialComponents) {
53
+ map.set(comp.name, comp.component);
54
+ }
55
+ return map;
56
+ });
57
+
58
+ const registerComponent = useCallback((name: string, component: ReactNode) => {
59
+ setComponents(prev => {
60
+ const next = new Map(prev);
61
+ next.set(name, component);
62
+ return next;
63
+ });
64
+ }, []);
65
+
66
+ const registerComponents = useCallback((comps: WidgetComponent[]) => {
67
+ setComponents(prev => {
68
+ const next = new Map(prev);
69
+ for (const comp of comps) {
70
+ next.set(comp.name, comp.component);
71
+ }
72
+ return next;
73
+ });
74
+ }, []);
75
+
76
+ const getComponent = useCallback((name: string): ReactNode | null => {
77
+ return components.get(name) ?? null;
78
+ }, [components]);
79
+
80
+ const hasComponent = useCallback((name: string): boolean => {
81
+ return components.has(name);
82
+ }, [components]);
83
+
84
+ const getRegisteredNames = useCallback((): string[] => {
85
+ return Array.from(components.keys());
86
+ }, [components]);
87
+
88
+ // Memoize context value to prevent unnecessary re-renders of consumers
89
+ const contextValue = useMemo(
90
+ () => ({
91
+ registerComponent,
92
+ registerComponents,
93
+ getComponent,
94
+ hasComponent,
95
+ getRegisteredNames,
96
+ }),
97
+ [registerComponent, registerComponents, getComponent, hasComponent, getRegisteredNames]
98
+ );
99
+
100
+ return (
101
+ <WidgetComponentRegistryContext.Provider value={contextValue}>
102
+ {children}
103
+ </WidgetComponentRegistryContext.Provider>
104
+ );
105
+ }
106
+
107
+ /**
108
+ * Hook to access the widget component registry
109
+ */
110
+ export function useWidgetComponentRegistry() {
111
+ const context = useContext(WidgetComponentRegistryContext);
112
+ if (!context) {
113
+ throw new Error('useWidgetComponentRegistry must be used within a WidgetComponentRegistryProvider');
114
+ }
115
+ return context;
116
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Built-in Widget Components
3
+ *
4
+ * Maps built-in widget component names to their React components.
5
+ * These are the widgets that qwickapps-server provides out of the box.
6
+ *
7
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
8
+ */
9
+
10
+ import { ServiceHealthWidget } from './widgets';
11
+ import type { WidgetComponent } from './WidgetComponentRegistry';
12
+
13
+ /**
14
+ * Map of built-in widget component names to their React component functions.
15
+ * Use this when you need to look up a component by name.
16
+ */
17
+ export const builtInWidgetComponents: Record<string, React.ComponentType> = {
18
+ ServiceHealthWidget: ServiceHealthWidget,
19
+ };
20
+
21
+ /**
22
+ * Get built-in widget components as WidgetComponent array with JSX elements.
23
+ * Use this when registering with WidgetComponentRegistryProvider.
24
+ */
25
+ export function getBuiltInWidgetComponents(): WidgetComponent[] {
26
+ return [
27
+ { name: 'ServiceHealthWidget', component: <ServiceHealthWidget /> },
28
+ ];
29
+ }