@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.
- package/README.md +238 -0
- package/dist/core/control-panel.d.ts +7 -2
- package/dist/core/control-panel.d.ts.map +1 -1
- package/dist/core/control-panel.js +92 -54
- package/dist/core/control-panel.js.map +1 -1
- package/dist/core/gateway.d.ts +159 -79
- package/dist/core/gateway.d.ts.map +1 -1
- package/dist/core/gateway.js +679 -319
- package/dist/core/gateway.js.map +1 -1
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/plugin-registry.d.ts +271 -0
- package/dist/core/plugin-registry.d.ts.map +1 -0
- package/dist/core/plugin-registry.js +326 -0
- package/dist/core/plugin-registry.js.map +1 -0
- package/dist/core/types.d.ts +16 -33
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -7
- package/dist/index.js.map +1 -1
- package/dist/plugins/auth/adapters/auth0-adapter.d.ts +14 -0
- package/dist/plugins/auth/adapters/auth0-adapter.d.ts.map +1 -0
- package/dist/plugins/auth/adapters/auth0-adapter.js +179 -0
- package/dist/plugins/auth/adapters/auth0-adapter.js.map +1 -0
- package/dist/plugins/auth/adapters/basic-adapter.d.ts +13 -0
- package/dist/plugins/auth/adapters/basic-adapter.d.ts.map +1 -0
- package/dist/plugins/auth/adapters/basic-adapter.js +51 -0
- package/dist/plugins/auth/adapters/basic-adapter.js.map +1 -0
- package/dist/plugins/auth/adapters/index.d.ts +9 -0
- package/dist/plugins/auth/adapters/index.d.ts.map +1 -0
- package/dist/plugins/auth/adapters/index.js +9 -0
- package/dist/plugins/auth/adapters/index.js.map +1 -0
- package/dist/plugins/auth/adapters/supabase-adapter.d.ts +13 -0
- package/dist/plugins/auth/adapters/supabase-adapter.d.ts.map +1 -0
- package/dist/plugins/auth/adapters/supabase-adapter.js +109 -0
- package/dist/plugins/auth/adapters/supabase-adapter.js.map +1 -0
- package/dist/plugins/auth/auth-plugin.d.ts +40 -0
- package/dist/plugins/auth/auth-plugin.d.ts.map +1 -0
- package/dist/plugins/auth/auth-plugin.js +255 -0
- package/dist/plugins/auth/auth-plugin.js.map +1 -0
- package/dist/plugins/auth/auth-plugin.test.d.ts +9 -0
- package/dist/plugins/auth/auth-plugin.test.d.ts.map +1 -0
- package/dist/plugins/auth/auth-plugin.test.js +147 -0
- package/dist/plugins/auth/auth-plugin.test.js.map +1 -0
- package/dist/plugins/auth/index.d.ts +12 -0
- package/dist/plugins/auth/index.d.ts.map +1 -0
- package/dist/plugins/auth/index.js +13 -0
- package/dist/plugins/auth/index.js.map +1 -0
- package/dist/plugins/auth/types.d.ts +148 -0
- package/dist/plugins/auth/types.d.ts.map +1 -0
- package/dist/plugins/auth/types.js +14 -0
- package/dist/plugins/auth/types.js.map +1 -0
- package/dist/plugins/bans/bans-plugin.d.ts +59 -0
- package/dist/plugins/bans/bans-plugin.d.ts.map +1 -0
- package/dist/plugins/bans/bans-plugin.js +428 -0
- package/dist/plugins/bans/bans-plugin.js.map +1 -0
- package/dist/plugins/bans/index.d.ts +9 -0
- package/dist/plugins/bans/index.d.ts.map +1 -0
- package/dist/plugins/bans/index.js +10 -0
- package/dist/plugins/bans/index.js.map +1 -0
- package/dist/plugins/bans/stores/index.d.ts +7 -0
- package/dist/plugins/bans/stores/index.d.ts.map +1 -0
- package/dist/plugins/bans/stores/index.js +7 -0
- package/dist/plugins/bans/stores/index.js.map +1 -0
- package/dist/plugins/bans/stores/postgres-store.d.ts +29 -0
- package/dist/plugins/bans/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/bans/stores/postgres-store.js +132 -0
- package/dist/plugins/bans/stores/postgres-store.js.map +1 -0
- package/dist/plugins/bans/types.d.ts +128 -0
- package/dist/plugins/bans/types.d.ts.map +1 -0
- package/dist/plugins/bans/types.js +11 -0
- package/dist/plugins/bans/types.js.map +1 -0
- package/dist/plugins/cache-plugin.d.ts +14 -3
- package/dist/plugins/cache-plugin.d.ts.map +1 -1
- package/dist/plugins/cache-plugin.js +27 -7
- package/dist/plugins/cache-plugin.js.map +1 -1
- package/dist/plugins/cache-plugin.test.js +96 -32
- package/dist/plugins/cache-plugin.test.js.map +1 -1
- package/dist/plugins/config-plugin.d.ts +3 -2
- package/dist/plugins/config-plugin.d.ts.map +1 -1
- package/dist/plugins/config-plugin.js +17 -10
- package/dist/plugins/config-plugin.js.map +1 -1
- package/dist/plugins/diagnostics-plugin.d.ts +2 -2
- package/dist/plugins/diagnostics-plugin.d.ts.map +1 -1
- package/dist/plugins/diagnostics-plugin.js +17 -10
- package/dist/plugins/diagnostics-plugin.js.map +1 -1
- package/dist/plugins/entitlements/entitlements-plugin.d.ts +95 -0
- package/dist/plugins/entitlements/entitlements-plugin.d.ts.map +1 -0
- package/dist/plugins/entitlements/entitlements-plugin.js +707 -0
- package/dist/plugins/entitlements/entitlements-plugin.js.map +1 -0
- package/dist/plugins/entitlements/index.d.ts +12 -0
- package/dist/plugins/entitlements/index.d.ts.map +1 -0
- package/dist/plugins/entitlements/index.js +16 -0
- package/dist/plugins/entitlements/index.js.map +1 -0
- package/dist/plugins/entitlements/sources/index.d.ts +9 -0
- package/dist/plugins/entitlements/sources/index.d.ts.map +1 -0
- package/dist/plugins/entitlements/sources/index.js +9 -0
- package/dist/plugins/entitlements/sources/index.js.map +1 -0
- package/dist/plugins/entitlements/sources/postgres-source.d.ts +29 -0
- package/dist/plugins/entitlements/sources/postgres-source.d.ts.map +1 -0
- package/dist/plugins/entitlements/sources/postgres-source.js +169 -0
- package/dist/plugins/entitlements/sources/postgres-source.js.map +1 -0
- package/dist/plugins/entitlements/types.d.ts +232 -0
- package/dist/plugins/entitlements/types.d.ts.map +1 -0
- package/dist/plugins/entitlements/types.js +11 -0
- package/dist/plugins/entitlements/types.js.map +1 -0
- package/dist/plugins/frontend-app-plugin.d.ts +9 -3
- package/dist/plugins/frontend-app-plugin.d.ts.map +1 -1
- package/dist/plugins/frontend-app-plugin.js +14 -9
- package/dist/plugins/frontend-app-plugin.js.map +1 -1
- package/dist/plugins/health-plugin.d.ts +5 -2
- package/dist/plugins/health-plugin.d.ts.map +1 -1
- package/dist/plugins/health-plugin.js +20 -5
- package/dist/plugins/health-plugin.js.map +1 -1
- package/dist/plugins/index.d.ts +8 -2
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +8 -2
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/logs-plugin.d.ts +3 -2
- package/dist/plugins/logs-plugin.d.ts.map +1 -1
- package/dist/plugins/logs-plugin.js +21 -12
- package/dist/plugins/logs-plugin.js.map +1 -1
- package/dist/plugins/postgres-plugin.d.ts +3 -3
- package/dist/plugins/postgres-plugin.d.ts.map +1 -1
- package/dist/plugins/postgres-plugin.js +9 -7
- package/dist/plugins/postgres-plugin.js.map +1 -1
- package/dist/plugins/postgres-plugin.test.js +47 -29
- package/dist/plugins/postgres-plugin.test.js.map +1 -1
- package/dist/plugins/users/index.d.ts +12 -0
- package/dist/plugins/users/index.d.ts.map +1 -0
- package/dist/plugins/users/index.js +13 -0
- package/dist/plugins/users/index.js.map +1 -0
- package/dist/plugins/users/stores/index.d.ts +7 -0
- package/dist/plugins/users/stores/index.d.ts.map +1 -0
- package/dist/plugins/users/stores/index.js +7 -0
- package/dist/plugins/users/stores/index.js.map +1 -0
- package/dist/plugins/users/stores/postgres-store.d.ts +28 -0
- package/dist/plugins/users/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/users/stores/postgres-store.js +157 -0
- package/dist/plugins/users/stores/postgres-store.js.map +1 -0
- package/dist/plugins/users/types.d.ts +189 -0
- package/dist/plugins/users/types.d.ts.map +1 -0
- package/dist/plugins/users/types.js +12 -0
- package/dist/plugins/users/types.js.map +1 -0
- package/dist/plugins/users/users-plugin.d.ts +39 -0
- package/dist/plugins/users/users-plugin.d.ts.map +1 -0
- package/dist/plugins/users/users-plugin.js +242 -0
- package/dist/plugins/users/users-plugin.js.map +1 -0
- package/dist-ui/assets/index-Bsp2ntcw.js +465 -0
- package/dist-ui/assets/index-Bsp2ntcw.js.map +1 -0
- package/dist-ui/index.html +1 -1
- package/dist-ui-lib/api/controlPanelApi.d.ts +232 -0
- package/dist-ui-lib/components/ControlPanelApp.d.ts +61 -0
- package/dist-ui-lib/components/index.d.ts +18 -0
- package/dist-ui-lib/config/AppConfig.d.ts +7 -0
- package/dist-ui-lib/dashboard/DashboardWidgetRegistry.d.ts +62 -0
- package/dist-ui-lib/dashboard/DashboardWidgetRenderer.d.ts +8 -0
- package/dist-ui-lib/dashboard/PluginWidgetRenderer.d.ts +19 -0
- package/dist-ui-lib/dashboard/WidgetComponentRegistry.d.ts +44 -0
- package/dist-ui-lib/dashboard/builtInWidgets.d.ts +19 -0
- package/dist-ui-lib/dashboard/index.d.ts +13 -0
- package/dist-ui-lib/dashboard/widgets/ServiceHealthWidget.d.ts +12 -0
- package/dist-ui-lib/dashboard/widgets/index.d.ts +6 -0
- package/dist-ui-lib/index.js +6441 -0
- package/dist-ui-lib/index.js.map +1 -0
- package/dist-ui-lib/pages/ConfigPage.d.ts +1 -0
- package/dist-ui-lib/pages/DashboardPage.d.ts +1 -0
- package/dist-ui-lib/pages/DiagnosticsPage.d.ts +1 -0
- package/dist-ui-lib/pages/EntitlementsPage.d.ts +17 -0
- package/dist-ui-lib/pages/LogsPage.d.ts +1 -0
- package/dist-ui-lib/pages/NotFoundPage.d.ts +1 -0
- package/dist-ui-lib/pages/PluginPage.d.ts +15 -0
- package/dist-ui-lib/pages/SystemPage.d.ts +1 -0
- package/dist-ui-lib/pages/UsersPage.d.ts +22 -0
- package/package.json +18 -6
- package/src/core/control-panel.ts +114 -61
- package/src/core/gateway.ts +863 -403
- package/src/core/index.ts +21 -2
- package/src/core/plugin-registry.ts +653 -0
- package/src/core/types.ts +31 -37
- package/src/index.ts +118 -19
- package/src/plugins/auth/adapters/auth0-adapter.ts +214 -0
- package/src/plugins/auth/adapters/basic-adapter.ts +61 -0
- package/src/plugins/auth/adapters/index.ts +9 -0
- package/src/plugins/auth/adapters/supabase-adapter.ts +141 -0
- package/src/plugins/auth/auth-plugin.test.ts +176 -0
- package/src/plugins/auth/auth-plugin.ts +303 -0
- package/src/plugins/auth/index.ts +33 -0
- package/src/plugins/auth/types.ts +165 -0
- package/src/plugins/bans/bans-plugin.ts +485 -0
- package/src/plugins/bans/index.ts +31 -0
- package/src/plugins/bans/stores/index.ts +7 -0
- package/src/plugins/bans/stores/postgres-store.ts +195 -0
- package/src/plugins/bans/types.ts +141 -0
- package/src/plugins/cache-plugin.test.ts +105 -32
- package/src/plugins/cache-plugin.ts +40 -9
- package/src/plugins/config-plugin.ts +23 -12
- package/src/plugins/diagnostics-plugin.ts +22 -12
- package/src/plugins/entitlements/entitlements-plugin.ts +820 -0
- package/src/plugins/entitlements/index.ts +51 -0
- package/src/plugins/entitlements/sources/index.ts +9 -0
- package/src/plugins/entitlements/sources/postgres-source.ts +253 -0
- package/src/plugins/entitlements/types.ts +256 -0
- package/src/plugins/frontend-app-plugin.ts +24 -12
- package/src/plugins/health-plugin.ts +27 -7
- package/src/plugins/index.ts +106 -4
- package/src/plugins/logs-plugin.ts +28 -14
- package/src/plugins/postgres-plugin.test.ts +49 -29
- package/src/plugins/postgres-plugin.ts +11 -9
- package/src/plugins/users/index.ts +35 -0
- package/src/plugins/users/stores/index.ts +7 -0
- package/src/plugins/users/stores/postgres-store.ts +225 -0
- package/src/plugins/users/types.ts +209 -0
- package/src/plugins/users/users-plugin.ts +281 -0
- package/ui/src/App.tsx +185 -31
- package/ui/src/api/controlPanelApi.ts +354 -1
- package/ui/src/components/ControlPanelApp.tsx +209 -0
- package/ui/src/components/index.ts +62 -0
- package/ui/src/dashboard/DashboardWidgetRegistry.tsx +129 -0
- package/ui/src/dashboard/DashboardWidgetRenderer.tsx +34 -0
- package/ui/src/dashboard/PluginWidgetRenderer.tsx +115 -0
- package/ui/src/dashboard/WidgetComponentRegistry.tsx +116 -0
- package/ui/src/dashboard/builtInWidgets.tsx +29 -0
- package/ui/src/dashboard/index.ts +35 -0
- package/ui/src/dashboard/widgets/ServiceHealthWidget.tsx +140 -0
- package/ui/src/dashboard/widgets/index.ts +7 -0
- package/ui/src/pages/DashboardPage.tsx +28 -149
- package/ui/src/pages/EntitlementsPage.tsx +557 -0
- package/ui/src/pages/LogsPage.tsx +174 -8
- package/ui/src/pages/PluginPage.tsx +148 -0
- package/ui/src/pages/SystemPage.tsx +445 -0
- package/ui/src/pages/UsersPage.tsx +837 -0
- package/ui/tsconfig.lib.json +11 -0
- package/ui/vite.lib.config.ts +51 -0
- package/dist-ui/assets/index-CW1BviRn.js +0 -465
- package/dist-ui/assets/index-CW1BviRn.js.map +0 -1
- 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
|
+
}
|