@qwickapps/server 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +392 -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 +120 -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 +307 -0
- package/dist/core/plugin-registry.d.ts.map +1 -0
- package/dist/core/plugin-registry.js +352 -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 +10 -0
- package/dist/plugins/auth/adapters/index.d.ts.map +1 -0
- package/dist/plugins/auth/adapters/index.js +10 -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/adapters/supertokens-adapter.d.ts +18 -0
- package/dist/plugins/auth/adapters/supertokens-adapter.d.ts.map +1 -0
- package/dist/plugins/auth/adapters/supertokens-adapter.js +267 -0
- package/dist/plugins/auth/adapters/supertokens-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/env-config.d.ts +88 -0
- package/dist/plugins/auth/env-config.d.ts.map +1 -0
- package/dist/plugins/auth/env-config.js +489 -0
- package/dist/plugins/auth/env-config.js.map +1 -0
- package/dist/plugins/auth/index.d.ts +14 -0
- package/dist/plugins/auth/index.d.ts.map +1 -0
- package/dist/plugins/auth/index.js +16 -0
- package/dist/plugins/auth/index.js.map +1 -0
- package/dist/plugins/auth/supertokens-adapter.test.d.ts +10 -0
- package/dist/plugins/auth/supertokens-adapter.test.d.ts.map +1 -0
- package/dist/plugins/auth/supertokens-adapter.test.js +486 -0
- package/dist/plugins/auth/supertokens-adapter.test.js.map +1 -0
- package/dist/plugins/auth/types.d.ts +218 -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 +99 -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 +10 -2
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +10 -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 +50 -29
- package/dist/plugins/postgres-plugin.test.js.map +1 -1
- package/dist/plugins/preferences/__tests__/deep-merge.test.d.ts +7 -0
- package/dist/plugins/preferences/__tests__/deep-merge.test.d.ts.map +1 -0
- package/dist/plugins/preferences/__tests__/deep-merge.test.js +215 -0
- package/dist/plugins/preferences/__tests__/deep-merge.test.js.map +1 -0
- package/dist/plugins/preferences/__tests__/preferences-plugin.test.d.ts +7 -0
- package/dist/plugins/preferences/__tests__/preferences-plugin.test.d.ts.map +1 -0
- package/dist/plugins/preferences/__tests__/preferences-plugin.test.js +265 -0
- package/dist/plugins/preferences/__tests__/preferences-plugin.test.js.map +1 -0
- package/dist/plugins/preferences/index.d.ts +12 -0
- package/dist/plugins/preferences/index.d.ts.map +1 -0
- package/dist/plugins/preferences/index.js +13 -0
- package/dist/plugins/preferences/index.js.map +1 -0
- package/dist/plugins/preferences/preferences-plugin.d.ts +39 -0
- package/dist/plugins/preferences/preferences-plugin.d.ts.map +1 -0
- package/dist/plugins/preferences/preferences-plugin.js +226 -0
- package/dist/plugins/preferences/preferences-plugin.js.map +1 -0
- package/dist/plugins/preferences/stores/index.d.ts +9 -0
- package/dist/plugins/preferences/stores/index.d.ts.map +1 -0
- package/dist/plugins/preferences/stores/index.js +9 -0
- package/dist/plugins/preferences/stores/index.js.map +1 -0
- package/dist/plugins/preferences/stores/postgres-store.d.ts +41 -0
- package/dist/plugins/preferences/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/preferences/stores/postgres-store.js +181 -0
- package/dist/plugins/preferences/stores/postgres-store.js.map +1 -0
- package/dist/plugins/preferences/types.d.ts +91 -0
- package/dist/plugins/preferences/types.d.ts.map +1 -0
- package/dist/plugins/preferences/types.js +10 -0
- package/dist/plugins/preferences/types.js.map +1 -0
- package/dist/plugins/users/__tests__/users-plugin.test.d.ts +9 -0
- package/dist/plugins/users/__tests__/users-plugin.test.d.ts.map +1 -0
- package/dist/plugins/users/__tests__/users-plugin.test.js +546 -0
- package/dist/plugins/users/__tests__/users-plugin.test.js.map +1 -0
- 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 +225 -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 +45 -0
- package/dist/plugins/users/users-plugin.d.ts.map +1 -0
- package/dist/plugins/users/users-plugin.js +359 -0
- package/dist/plugins/users/users-plugin.js.map +1 -0
- package/dist-ui/assets/index-BY8OxNgO.js +465 -0
- package/dist-ui/assets/index-BY8OxNgO.js.map +1 -0
- package/dist-ui/index.html +1 -1
- package/dist-ui-lib/api/controlPanelApi.d.ts +278 -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 +48 -0
- package/dist-ui-lib/dashboard/builtInWidgets.d.ts +25 -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 +5172 -0
- package/dist-ui-lib/index.js.map +1 -0
- package/dist-ui-lib/pages/AuthPage.d.ts +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/PluginsPage.d.ts +1 -0
- package/dist-ui-lib/pages/SystemPage.d.ts +1 -0
- package/dist-ui-lib/pages/UsersPage.d.ts +22 -0
- package/package.json +24 -7
- package/src/core/control-panel.ts +145 -61
- package/src/core/gateway.ts +863 -403
- package/src/core/index.ts +21 -2
- package/src/core/plugin-registry.ts +716 -0
- package/src/core/types.ts +31 -37
- package/src/index.ts +125 -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 +10 -0
- package/src/plugins/auth/adapters/supabase-adapter.ts +149 -0
- package/src/plugins/auth/adapters/supertokens-adapter.ts +326 -0
- package/src/plugins/auth/auth-plugin.test.ts +176 -0
- package/src/plugins/auth/auth-plugin.ts +303 -0
- package/src/plugins/auth/env-config.ts +572 -0
- package/src/plugins/auth/index.ts +42 -0
- package/src/plugins/auth/supertokens-adapter.test.ts +621 -0
- package/src/plugins/auth/types.ts +245 -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 +108 -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 +132 -4
- package/src/plugins/logs-plugin.ts +28 -14
- package/src/plugins/postgres-plugin.test.ts +52 -29
- package/src/plugins/postgres-plugin.ts +11 -9
- package/src/plugins/preferences/__tests__/deep-merge.test.ts +242 -0
- package/src/plugins/preferences/__tests__/preferences-plugin.test.ts +350 -0
- package/src/plugins/preferences/index.ts +30 -0
- package/src/plugins/preferences/preferences-plugin.ts +270 -0
- package/src/plugins/preferences/stores/index.ts +9 -0
- package/src/plugins/preferences/stores/postgres-store.ts +252 -0
- package/src/plugins/preferences/types.ts +100 -0
- package/src/plugins/users/__tests__/users-plugin.test.ts +690 -0
- package/src/plugins/users/index.ts +38 -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 +247 -0
- package/src/plugins/users/users-plugin.ts +418 -0
- package/ui/src/App.tsx +188 -31
- package/ui/src/api/controlPanelApi.ts +453 -1
- package/ui/src/components/ControlPanelApp.tsx +212 -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 +118 -0
- package/ui/src/dashboard/WidgetComponentRegistry.tsx +120 -0
- package/ui/src/dashboard/builtInWidgets.tsx +35 -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/AuthPage.tsx +259 -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/PluginsPage.tsx +394 -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 +56 -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,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,118 @@
|
|
|
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
|
+
const Component = getComponent(widget.component);
|
|
105
|
+
return (
|
|
106
|
+
<Box key={widget.id} sx={{ mt: 4 }}>
|
|
107
|
+
{widget.title && (
|
|
108
|
+
<Typography variant="h6" sx={{ mb: 2, color: 'var(--theme-text-primary)' }}>
|
|
109
|
+
{widget.title}
|
|
110
|
+
</Typography>
|
|
111
|
+
)}
|
|
112
|
+
{Component && <Component />}
|
|
113
|
+
</Box>
|
|
114
|
+
);
|
|
115
|
+
})}
|
|
116
|
+
</>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
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 React, { createContext, useContext, useState, useCallback, useMemo, type ReactNode } from 'react';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Widget component definition
|
|
14
|
+
*
|
|
15
|
+
* IMPORTANT: We store component functions (ComponentType), not JSX instances (ReactNode).
|
|
16
|
+
* This ensures cross-React-version compatibility when the library is used in apps
|
|
17
|
+
* with different React versions.
|
|
18
|
+
*/
|
|
19
|
+
export interface WidgetComponent {
|
|
20
|
+
/** Component name (must match server-side WidgetContribution.component) */
|
|
21
|
+
name: string;
|
|
22
|
+
/** The React component function to render */
|
|
23
|
+
component: React.ComponentType;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface WidgetComponentRegistryContextValue {
|
|
27
|
+
/** Register a widget component */
|
|
28
|
+
registerComponent: (name: string, component: React.ComponentType) => void;
|
|
29
|
+
/** Register multiple widget components */
|
|
30
|
+
registerComponents: (components: WidgetComponent[]) => void;
|
|
31
|
+
/** Get a component by name */
|
|
32
|
+
getComponent: (name: string) => React.ComponentType | null;
|
|
33
|
+
/** Check if a component is registered */
|
|
34
|
+
hasComponent: (name: string) => boolean;
|
|
35
|
+
/** Get all registered component names */
|
|
36
|
+
getRegisteredNames: () => string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const WidgetComponentRegistryContext = createContext<WidgetComponentRegistryContextValue | null>(null);
|
|
40
|
+
|
|
41
|
+
export interface WidgetComponentRegistryProviderProps {
|
|
42
|
+
/** Initial components to register */
|
|
43
|
+
initialComponents?: WidgetComponent[];
|
|
44
|
+
children: ReactNode;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Provider for the widget component registry
|
|
49
|
+
*/
|
|
50
|
+
export function WidgetComponentRegistryProvider({
|
|
51
|
+
initialComponents = [],
|
|
52
|
+
children,
|
|
53
|
+
}: WidgetComponentRegistryProviderProps) {
|
|
54
|
+
const [components, setComponents] = useState<Map<string, React.ComponentType>>(() => {
|
|
55
|
+
const map = new Map<string, React.ComponentType>();
|
|
56
|
+
for (const comp of initialComponents) {
|
|
57
|
+
map.set(comp.name, comp.component);
|
|
58
|
+
}
|
|
59
|
+
return map;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const registerComponent = useCallback((name: string, component: React.ComponentType) => {
|
|
63
|
+
setComponents(prev => {
|
|
64
|
+
const next = new Map(prev);
|
|
65
|
+
next.set(name, component);
|
|
66
|
+
return next;
|
|
67
|
+
});
|
|
68
|
+
}, []);
|
|
69
|
+
|
|
70
|
+
const registerComponents = useCallback((comps: WidgetComponent[]) => {
|
|
71
|
+
setComponents(prev => {
|
|
72
|
+
const next = new Map(prev);
|
|
73
|
+
for (const comp of comps) {
|
|
74
|
+
next.set(comp.name, comp.component);
|
|
75
|
+
}
|
|
76
|
+
return next;
|
|
77
|
+
});
|
|
78
|
+
}, []);
|
|
79
|
+
|
|
80
|
+
const getComponent = useCallback((name: string): React.ComponentType | null => {
|
|
81
|
+
return components.get(name) ?? null;
|
|
82
|
+
}, [components]);
|
|
83
|
+
|
|
84
|
+
const hasComponent = useCallback((name: string): boolean => {
|
|
85
|
+
return components.has(name);
|
|
86
|
+
}, [components]);
|
|
87
|
+
|
|
88
|
+
const getRegisteredNames = useCallback((): string[] => {
|
|
89
|
+
return Array.from(components.keys());
|
|
90
|
+
}, [components]);
|
|
91
|
+
|
|
92
|
+
// Memoize context value to prevent unnecessary re-renders of consumers
|
|
93
|
+
const contextValue = useMemo(
|
|
94
|
+
() => ({
|
|
95
|
+
registerComponent,
|
|
96
|
+
registerComponents,
|
|
97
|
+
getComponent,
|
|
98
|
+
hasComponent,
|
|
99
|
+
getRegisteredNames,
|
|
100
|
+
}),
|
|
101
|
+
[registerComponent, registerComponents, getComponent, hasComponent, getRegisteredNames]
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<WidgetComponentRegistryContext.Provider value={contextValue}>
|
|
106
|
+
{children}
|
|
107
|
+
</WidgetComponentRegistryContext.Provider>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Hook to access the widget component registry
|
|
113
|
+
*/
|
|
114
|
+
export function useWidgetComponentRegistry() {
|
|
115
|
+
const context = useContext(WidgetComponentRegistryContext);
|
|
116
|
+
if (!context) {
|
|
117
|
+
throw new Error('useWidgetComponentRegistry must be used within a WidgetComponentRegistryProvider');
|
|
118
|
+
}
|
|
119
|
+
return context;
|
|
120
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
* IMPORTANT: We export component functions, not JSX instances.
|
|
8
|
+
* This ensures cross-React-version compatibility.
|
|
9
|
+
*
|
|
10
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { ServiceHealthWidget } from './widgets';
|
|
14
|
+
import type { WidgetComponent } from './WidgetComponentRegistry';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Map of built-in widget component names to their React component functions.
|
|
18
|
+
* Use this when you need to look up a component by name.
|
|
19
|
+
*/
|
|
20
|
+
export const builtInWidgetComponents: Record<string, React.ComponentType> = {
|
|
21
|
+
ServiceHealthWidget: ServiceHealthWidget,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get built-in widget components as WidgetComponent array.
|
|
26
|
+
* Use this when registering with WidgetComponentRegistryProvider.
|
|
27
|
+
*
|
|
28
|
+
* Returns component functions (not JSX instances) to ensure compatibility
|
|
29
|
+
* across different React versions.
|
|
30
|
+
*/
|
|
31
|
+
export function getBuiltInWidgetComponents(): WidgetComponent[] {
|
|
32
|
+
return [
|
|
33
|
+
{ name: 'ServiceHealthWidget', component: ServiceHealthWidget },
|
|
34
|
+
];
|
|
35
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard Module
|
|
3
|
+
*
|
|
4
|
+
* Exports the dashboard widget system for dynamic dashboard customization.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Legacy context-based widget system (for backwards compatibility)
|
|
10
|
+
export {
|
|
11
|
+
DashboardWidgetProvider,
|
|
12
|
+
useDashboardWidgets,
|
|
13
|
+
useRegisterWidget,
|
|
14
|
+
type DashboardWidget,
|
|
15
|
+
type DashboardWidgetProviderProps,
|
|
16
|
+
} from './DashboardWidgetRegistry';
|
|
17
|
+
|
|
18
|
+
export { DashboardWidgetRenderer } from './DashboardWidgetRenderer';
|
|
19
|
+
|
|
20
|
+
// New plugin-based widget system
|
|
21
|
+
export {
|
|
22
|
+
WidgetComponentRegistryProvider,
|
|
23
|
+
useWidgetComponentRegistry,
|
|
24
|
+
type WidgetComponent,
|
|
25
|
+
type WidgetComponentRegistryProviderProps,
|
|
26
|
+
} from './WidgetComponentRegistry';
|
|
27
|
+
|
|
28
|
+
export { PluginWidgetRenderer } from './PluginWidgetRenderer';
|
|
29
|
+
|
|
30
|
+
// Built-in widgets
|
|
31
|
+
export { ServiceHealthWidget } from './widgets';
|
|
32
|
+
|
|
33
|
+
// Built-in widget component map (component name -> React component function)
|
|
34
|
+
// Product code should use getBuiltInWidgetComponents() to get the full list with JSX
|
|
35
|
+
export { builtInWidgetComponents, getBuiltInWidgetComponents } from './builtInWidgets';
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Health Widget
|
|
3
|
+
*
|
|
4
|
+
* Displays health check status with latency for all registered health checks.
|
|
5
|
+
* This is a built-in widget provided by qwickapps-server.
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useState, useEffect } from 'react';
|
|
11
|
+
import { Box, Card, CardContent, Chip } from '@mui/material';
|
|
12
|
+
import { GridLayout, Text } from '@qwickapps/react-framework';
|
|
13
|
+
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
|
14
|
+
import ErrorIcon from '@mui/icons-material/Error';
|
|
15
|
+
import WarningIcon from '@mui/icons-material/Warning';
|
|
16
|
+
import { api, HealthResponse } from '../../api/controlPanelApi';
|
|
17
|
+
|
|
18
|
+
function getStatusIcon(status: string) {
|
|
19
|
+
switch (status) {
|
|
20
|
+
case 'healthy':
|
|
21
|
+
return <CheckCircleIcon sx={{ fontSize: 24, color: 'var(--theme-success)' }} />;
|
|
22
|
+
case 'degraded':
|
|
23
|
+
return <WarningIcon sx={{ fontSize: 24, color: 'var(--theme-warning)' }} />;
|
|
24
|
+
case 'unhealthy':
|
|
25
|
+
return <ErrorIcon sx={{ fontSize: 24, color: 'var(--theme-error)' }} />;
|
|
26
|
+
default:
|
|
27
|
+
return <WarningIcon sx={{ fontSize: 24, color: 'var(--theme-text-secondary)' }} />;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getStatusColor(status: string): string {
|
|
32
|
+
switch (status) {
|
|
33
|
+
case 'healthy':
|
|
34
|
+
return 'var(--theme-success)';
|
|
35
|
+
case 'degraded':
|
|
36
|
+
return 'var(--theme-warning)';
|
|
37
|
+
case 'unhealthy':
|
|
38
|
+
return 'var(--theme-error)';
|
|
39
|
+
default:
|
|
40
|
+
return 'var(--theme-text-secondary)';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Get grid columns count (1-4) based on item count */
|
|
45
|
+
function getGridColumns(count: number): 1 | 2 | 3 | 4 {
|
|
46
|
+
if (count <= 1) return 1;
|
|
47
|
+
if (count === 2) return 2;
|
|
48
|
+
if (count === 3) return 3;
|
|
49
|
+
return 4;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Service Health Widget Component
|
|
54
|
+
*/
|
|
55
|
+
export function ServiceHealthWidget() {
|
|
56
|
+
const [health, setHealth] = useState<HealthResponse | null>(null);
|
|
57
|
+
const [error, setError] = useState<string | null>(null);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const fetchHealth = async () => {
|
|
61
|
+
try {
|
|
62
|
+
const data = await api.getHealth();
|
|
63
|
+
setHealth(data);
|
|
64
|
+
setError(null);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
setError(err instanceof Error ? err.message : 'Failed to fetch health');
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
fetchHealth();
|
|
71
|
+
const interval = setInterval(fetchHealth, 10000);
|
|
72
|
+
return () => clearInterval(interval);
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
if (error) {
|
|
76
|
+
return (
|
|
77
|
+
<Card sx={{ bgcolor: 'var(--theme-surface)', border: '1px solid var(--theme-error)' }}>
|
|
78
|
+
<CardContent>
|
|
79
|
+
<Text variant="body2" customColor="var(--theme-error)" content={error} />
|
|
80
|
+
</CardContent>
|
|
81
|
+
</Card>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const healthChecks = health ? Object.entries(health.checks) : [];
|
|
86
|
+
|
|
87
|
+
if (healthChecks.length === 0) {
|
|
88
|
+
return (
|
|
89
|
+
<Card sx={{ bgcolor: 'var(--theme-surface)' }}>
|
|
90
|
+
<CardContent>
|
|
91
|
+
<Text variant="body2" customColor="var(--theme-text-secondary)" content="No health checks configured" />
|
|
92
|
+
</CardContent>
|
|
93
|
+
</Card>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Determine grid columns based on number of health checks (max 4)
|
|
98
|
+
const columns = getGridColumns(healthChecks.length);
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<GridLayout columns={columns} spacing="medium" equalHeight>
|
|
102
|
+
{healthChecks.map(([name, check]) => (
|
|
103
|
+
<Card key={name} sx={{ bgcolor: 'var(--theme-surface)' }}>
|
|
104
|
+
<CardContent>
|
|
105
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
|
106
|
+
{getStatusIcon(check.status)}
|
|
107
|
+
<Box sx={{ flex: 1, minWidth: 0 }}>
|
|
108
|
+
<Text
|
|
109
|
+
variant="body1"
|
|
110
|
+
fontWeight="500"
|
|
111
|
+
content={name.charAt(0).toUpperCase() + name.slice(1)}
|
|
112
|
+
customColor="var(--theme-text-primary)"
|
|
113
|
+
/>
|
|
114
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mt: 0.5 }}>
|
|
115
|
+
<Chip
|
|
116
|
+
label={check.status}
|
|
117
|
+
size="small"
|
|
118
|
+
sx={{
|
|
119
|
+
bgcolor: getStatusColor(check.status) + '20',
|
|
120
|
+
color: getStatusColor(check.status),
|
|
121
|
+
fontSize: '0.75rem',
|
|
122
|
+
height: 20,
|
|
123
|
+
}}
|
|
124
|
+
/>
|
|
125
|
+
{check.latency !== undefined && (
|
|
126
|
+
<Text
|
|
127
|
+
variant="caption"
|
|
128
|
+
content={`${check.latency}ms`}
|
|
129
|
+
customColor="var(--theme-text-secondary)"
|
|
130
|
+
/>
|
|
131
|
+
)}
|
|
132
|
+
</Box>
|
|
133
|
+
</Box>
|
|
134
|
+
</Box>
|
|
135
|
+
</CardContent>
|
|
136
|
+
</Card>
|
|
137
|
+
))}
|
|
138
|
+
</GridLayout>
|
|
139
|
+
);
|
|
140
|
+
}
|