@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,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Users Plugin
|
|
3
|
+
*
|
|
4
|
+
* User identity management plugin for @qwickapps/server.
|
|
5
|
+
* Provides CRUD operations, search, and user lookup functionality.
|
|
6
|
+
*
|
|
7
|
+
* Note: Ban management is handled by the separate Bans Plugin.
|
|
8
|
+
*
|
|
9
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { Request, Response } from 'express';
|
|
13
|
+
import type { Plugin, PluginConfig, PluginRegistry } from '../../core/plugin-registry.js';
|
|
14
|
+
import type {
|
|
15
|
+
UsersPluginConfig,
|
|
16
|
+
UserStore,
|
|
17
|
+
User,
|
|
18
|
+
CreateUserInput,
|
|
19
|
+
UpdateUserInput,
|
|
20
|
+
UserSearchParams,
|
|
21
|
+
UserInfo,
|
|
22
|
+
UserSyncInput,
|
|
23
|
+
} from './types.js';
|
|
24
|
+
// Import helpers from other plugins for buildUserInfo
|
|
25
|
+
// Note: These imports are used dynamically based on registry.hasPlugin() checks
|
|
26
|
+
import { getEntitlements } from '../entitlements/entitlements-plugin.js';
|
|
27
|
+
import { getPreferences } from '../preferences/preferences-plugin.js';
|
|
28
|
+
import { getActiveBan } from '../bans/bans-plugin.js';
|
|
29
|
+
|
|
30
|
+
// Store instance for helper access
|
|
31
|
+
let currentStore: UserStore | null = null;
|
|
32
|
+
let currentRegistry: PluginRegistry | null = null;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create the Users plugin
|
|
36
|
+
*/
|
|
37
|
+
export function createUsersPlugin(config: UsersPluginConfig): Plugin {
|
|
38
|
+
const debug = config.debug || false;
|
|
39
|
+
// Routes are mounted under /api by the control panel, so don't include /api in prefix
|
|
40
|
+
const apiPrefix = config.api?.prefix || '/users';
|
|
41
|
+
|
|
42
|
+
function log(message: string, data?: Record<string, unknown>) {
|
|
43
|
+
if (debug) {
|
|
44
|
+
console.log(`[UsersPlugin] ${message}`, data || '');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
id: 'users',
|
|
50
|
+
name: 'Users',
|
|
51
|
+
version: '1.0.0',
|
|
52
|
+
|
|
53
|
+
async onStart(_pluginConfig: PluginConfig, registry: PluginRegistry): Promise<void> {
|
|
54
|
+
log('Starting users plugin');
|
|
55
|
+
|
|
56
|
+
// Initialize the store (creates tables if needed)
|
|
57
|
+
await config.store.initialize();
|
|
58
|
+
log('Users plugin migrations complete');
|
|
59
|
+
|
|
60
|
+
// Store references for helper access
|
|
61
|
+
currentStore = config.store;
|
|
62
|
+
currentRegistry = registry;
|
|
63
|
+
|
|
64
|
+
// Register health check
|
|
65
|
+
registry.registerHealthCheck({
|
|
66
|
+
name: 'users-store',
|
|
67
|
+
type: 'custom',
|
|
68
|
+
check: async () => {
|
|
69
|
+
try {
|
|
70
|
+
// Simple health check - try to search with limit 1
|
|
71
|
+
await config.store.search({ limit: 1 });
|
|
72
|
+
return { healthy: true };
|
|
73
|
+
} catch {
|
|
74
|
+
return { healthy: false };
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Add API routes if enabled
|
|
80
|
+
if (config.api?.crud !== false) {
|
|
81
|
+
// List/Search users
|
|
82
|
+
registry.addRoute({
|
|
83
|
+
method: 'get',
|
|
84
|
+
path: apiPrefix,
|
|
85
|
+
pluginId: 'users',
|
|
86
|
+
handler: async (req: Request, res: Response) => {
|
|
87
|
+
try {
|
|
88
|
+
const params: UserSearchParams = {
|
|
89
|
+
query: req.query.q as string,
|
|
90
|
+
provider: req.query.provider as string,
|
|
91
|
+
page: parseInt(req.query.page as string) || 1,
|
|
92
|
+
limit: Math.min(parseInt(req.query.limit as string) || 20, 100),
|
|
93
|
+
sortBy: (req.query.sortBy as UserSearchParams['sortBy']) || 'created_at',
|
|
94
|
+
sortOrder: (req.query.sortOrder as UserSearchParams['sortOrder']) || 'desc',
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const result = await config.store.search(params);
|
|
98
|
+
res.json(result);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error('[UsersPlugin] Search error:', error);
|
|
101
|
+
res.status(500).json({ error: 'Failed to search users' });
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Get user by ID
|
|
107
|
+
registry.addRoute({
|
|
108
|
+
method: 'get',
|
|
109
|
+
path: `${apiPrefix}/:id`,
|
|
110
|
+
pluginId: 'users',
|
|
111
|
+
handler: async (req: Request, res: Response) => {
|
|
112
|
+
try {
|
|
113
|
+
const user = await config.store.getById(req.params.id);
|
|
114
|
+
if (!user) {
|
|
115
|
+
return res.status(404).json({ error: 'User not found' });
|
|
116
|
+
}
|
|
117
|
+
res.json(user);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error('[UsersPlugin] Get user error:', error);
|
|
120
|
+
res.status(500).json({ error: 'Failed to get user' });
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Create user
|
|
126
|
+
registry.addRoute({
|
|
127
|
+
method: 'post',
|
|
128
|
+
path: apiPrefix,
|
|
129
|
+
pluginId: 'users',
|
|
130
|
+
handler: async (req: Request, res: Response) => {
|
|
131
|
+
try {
|
|
132
|
+
const input: CreateUserInput = {
|
|
133
|
+
email: req.body.email,
|
|
134
|
+
name: req.body.name,
|
|
135
|
+
external_id: req.body.external_id,
|
|
136
|
+
provider: req.body.provider,
|
|
137
|
+
picture: req.body.picture,
|
|
138
|
+
metadata: req.body.metadata,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
if (!input.email) {
|
|
142
|
+
return res.status(400).json({ error: 'Email is required' });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Check if user already exists
|
|
146
|
+
const existing = await config.store.getByEmail(input.email);
|
|
147
|
+
if (existing) {
|
|
148
|
+
return res.status(409).json({ error: 'User with this email already exists' });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const user = await config.store.create(input);
|
|
152
|
+
res.status(201).json(user);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error('[UsersPlugin] Create user error:', error);
|
|
155
|
+
res.status(500).json({ error: 'Failed to create user' });
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Update user
|
|
161
|
+
registry.addRoute({
|
|
162
|
+
method: 'put',
|
|
163
|
+
path: `${apiPrefix}/:id`,
|
|
164
|
+
pluginId: 'users',
|
|
165
|
+
handler: async (req: Request, res: Response) => {
|
|
166
|
+
try {
|
|
167
|
+
const input: UpdateUserInput = {
|
|
168
|
+
name: req.body.name,
|
|
169
|
+
picture: req.body.picture,
|
|
170
|
+
metadata: req.body.metadata,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const user = await config.store.update(req.params.id, input);
|
|
174
|
+
if (!user) {
|
|
175
|
+
return res.status(404).json({ error: 'User not found' });
|
|
176
|
+
}
|
|
177
|
+
res.json(user);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error('[UsersPlugin] Update user error:', error);
|
|
180
|
+
res.status(500).json({ error: 'Failed to update user' });
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Delete user
|
|
186
|
+
registry.addRoute({
|
|
187
|
+
method: 'delete',
|
|
188
|
+
path: `${apiPrefix}/:id`,
|
|
189
|
+
pluginId: 'users',
|
|
190
|
+
handler: async (req: Request, res: Response) => {
|
|
191
|
+
try {
|
|
192
|
+
const deleted = await config.store.delete(req.params.id);
|
|
193
|
+
if (!deleted) {
|
|
194
|
+
return res.status(404).json({ error: 'User not found' });
|
|
195
|
+
}
|
|
196
|
+
res.status(204).send();
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.error('[UsersPlugin] Delete user error:', error);
|
|
199
|
+
res.status(500).json({ error: 'Failed to delete user' });
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// GET /users/:id/info - Get comprehensive user info
|
|
205
|
+
registry.addRoute({
|
|
206
|
+
method: 'get',
|
|
207
|
+
path: `${apiPrefix}/:id/info`,
|
|
208
|
+
pluginId: 'users',
|
|
209
|
+
handler: async (req: Request, res: Response) => {
|
|
210
|
+
try {
|
|
211
|
+
const user = await config.store.getById(req.params.id);
|
|
212
|
+
if (!user) {
|
|
213
|
+
return res.status(404).json({ error: 'User not found' });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const info = await buildUserInfo(user, registry);
|
|
217
|
+
res.json(info);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error('[UsersPlugin] Get user info error:', error);
|
|
220
|
+
res.status(500).json({ error: 'Failed to get user info' });
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// POST /users/sync - Find or create user, return comprehensive info
|
|
226
|
+
registry.addRoute({
|
|
227
|
+
method: 'post',
|
|
228
|
+
path: `${apiPrefix}/sync`,
|
|
229
|
+
pluginId: 'users',
|
|
230
|
+
handler: async (req: Request, res: Response) => {
|
|
231
|
+
try {
|
|
232
|
+
const input = req.body as UserSyncInput;
|
|
233
|
+
|
|
234
|
+
// Normalize and validate email
|
|
235
|
+
const email = input.email?.trim().toLowerCase();
|
|
236
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
237
|
+
if (!email || !emailRegex.test(email)) {
|
|
238
|
+
return res.status(400).json({ error: 'Valid email is required' });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Validate required fields
|
|
242
|
+
if (!input.external_id) {
|
|
243
|
+
return res.status(400).json({ error: 'external_id is required' });
|
|
244
|
+
}
|
|
245
|
+
if (!input.provider) {
|
|
246
|
+
return res.status(400).json({ error: 'provider is required' });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Find or create user
|
|
250
|
+
const user = await findOrCreateUser({
|
|
251
|
+
email: email,
|
|
252
|
+
external_id: input.external_id,
|
|
253
|
+
provider: input.provider,
|
|
254
|
+
name: input.name?.trim(),
|
|
255
|
+
picture: input.picture?.trim(),
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const info = await buildUserInfo(user, registry);
|
|
259
|
+
res.json(info);
|
|
260
|
+
} catch (error) {
|
|
261
|
+
console.error('[UsersPlugin] User sync error:', error);
|
|
262
|
+
res.status(500).json({ error: 'Failed to sync user' });
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
log('Users plugin started');
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
async onStop(): Promise<void> {
|
|
272
|
+
log('Stopping users plugin');
|
|
273
|
+
await config.store.shutdown();
|
|
274
|
+
currentStore = null;
|
|
275
|
+
currentRegistry = null;
|
|
276
|
+
log('Users plugin stopped');
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ========================================
|
|
282
|
+
// Helper Functions
|
|
283
|
+
// ========================================
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Get the current user store instance
|
|
287
|
+
*/
|
|
288
|
+
export function getUserStore(): UserStore | null {
|
|
289
|
+
return currentStore;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Get a user by ID
|
|
294
|
+
*/
|
|
295
|
+
export async function getUserById(id: string): Promise<User | null> {
|
|
296
|
+
if (!currentStore) {
|
|
297
|
+
throw new Error('Users plugin not initialized');
|
|
298
|
+
}
|
|
299
|
+
return currentStore.getById(id);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Get a user by email
|
|
304
|
+
*/
|
|
305
|
+
export async function getUserByEmail(email: string): Promise<User | null> {
|
|
306
|
+
if (!currentStore) {
|
|
307
|
+
throw new Error('Users plugin not initialized');
|
|
308
|
+
}
|
|
309
|
+
return currentStore.getByEmail(email);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Find or create a user from auth provider data
|
|
314
|
+
*/
|
|
315
|
+
export async function findOrCreateUser(data: {
|
|
316
|
+
email: string;
|
|
317
|
+
name?: string;
|
|
318
|
+
external_id: string;
|
|
319
|
+
provider: string;
|
|
320
|
+
picture?: string;
|
|
321
|
+
}): Promise<User> {
|
|
322
|
+
if (!currentStore) {
|
|
323
|
+
throw new Error('Users plugin not initialized');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Try to find by external ID first
|
|
327
|
+
let user = await currentStore.getByExternalId(data.external_id, data.provider);
|
|
328
|
+
if (user) {
|
|
329
|
+
await currentStore.updateLastLogin(user.id);
|
|
330
|
+
return user;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Try to find by email
|
|
334
|
+
user = await currentStore.getByEmail(data.email);
|
|
335
|
+
if (user) {
|
|
336
|
+
// Note: external_id cannot be updated after user creation for security reasons
|
|
337
|
+
await currentStore.updateLastLogin(user.id);
|
|
338
|
+
return user;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Create new user
|
|
342
|
+
user = await currentStore.create({
|
|
343
|
+
email: data.email,
|
|
344
|
+
name: data.name,
|
|
345
|
+
external_id: data.external_id,
|
|
346
|
+
provider: data.provider,
|
|
347
|
+
picture: data.picture,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
return user;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Build comprehensive user info by aggregating data from all loaded plugins.
|
|
355
|
+
* This helper fetches data from entitlements, preferences, and bans plugins
|
|
356
|
+
* in parallel (if they are loaded) and returns a unified UserInfo object.
|
|
357
|
+
*/
|
|
358
|
+
export async function buildUserInfo(user: User, registry: PluginRegistry): Promise<UserInfo> {
|
|
359
|
+
const info: UserInfo = { user };
|
|
360
|
+
|
|
361
|
+
// Fetch data from other plugins in parallel
|
|
362
|
+
const promises: Promise<void>[] = [];
|
|
363
|
+
|
|
364
|
+
if (registry.hasPlugin('entitlements')) {
|
|
365
|
+
promises.push(
|
|
366
|
+
getEntitlements(user.email)
|
|
367
|
+
.then((result) => {
|
|
368
|
+
info.entitlements = result.entitlements;
|
|
369
|
+
})
|
|
370
|
+
.catch((error) => {
|
|
371
|
+
console.error('[UsersPlugin] Failed to fetch entitlements:', error);
|
|
372
|
+
// Continue without entitlements - don't fail the whole request
|
|
373
|
+
})
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (registry.hasPlugin('preferences')) {
|
|
378
|
+
promises.push(
|
|
379
|
+
getPreferences(user.id)
|
|
380
|
+
.then((prefs) => {
|
|
381
|
+
info.preferences = prefs;
|
|
382
|
+
})
|
|
383
|
+
.catch((error) => {
|
|
384
|
+
console.error('[UsersPlugin] Failed to fetch preferences:', error);
|
|
385
|
+
// Continue without preferences - don't fail the whole request
|
|
386
|
+
})
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (registry.hasPlugin('bans')) {
|
|
391
|
+
promises.push(
|
|
392
|
+
getActiveBan(user.id)
|
|
393
|
+
.then((ban) => {
|
|
394
|
+
// Transform Ban to UserInfo.ban shape (only include relevant fields)
|
|
395
|
+
info.ban = ban
|
|
396
|
+
? {
|
|
397
|
+
id: ban.id,
|
|
398
|
+
reason: ban.reason,
|
|
399
|
+
banned_at: ban.banned_at,
|
|
400
|
+
expires_at: ban.expires_at,
|
|
401
|
+
}
|
|
402
|
+
: null;
|
|
403
|
+
})
|
|
404
|
+
.catch((error) => {
|
|
405
|
+
console.error('[UsersPlugin] Failed to fetch ban status:', error);
|
|
406
|
+
// Continue without ban info - don't fail the whole request
|
|
407
|
+
})
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Future: roles plugin
|
|
412
|
+
// if (registry.hasPlugin('roles')) {
|
|
413
|
+
// promises.push(getUserRoles(user.id).then(roles => info.roles = roles));
|
|
414
|
+
// }
|
|
415
|
+
|
|
416
|
+
await Promise.all(promises);
|
|
417
|
+
return info;
|
|
418
|
+
}
|
package/ui/src/App.tsx
CHANGED
|
@@ -1,21 +1,66 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
1
2
|
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
|
2
3
|
import { QwickApp, ProductLogo, Text } from '@qwickapps/react-framework';
|
|
3
4
|
import { Link, Box } from '@mui/material';
|
|
4
5
|
import { defaultConfig } from './config/AppConfig';
|
|
6
|
+
import { DashboardWidgetProvider } from './dashboard';
|
|
5
7
|
import { DashboardPage } from './pages/DashboardPage';
|
|
6
|
-
import { HealthPage } from './pages/HealthPage';
|
|
7
8
|
import { LogsPage } from './pages/LogsPage';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
9
|
+
import { SystemPage } from './pages/SystemPage';
|
|
10
|
+
import { PluginsPage } from './pages/PluginsPage';
|
|
11
|
+
import { UsersPage } from './pages/UsersPage';
|
|
12
|
+
import { EntitlementsPage } from './pages/EntitlementsPage';
|
|
13
|
+
import { PluginPage } from './pages/PluginPage';
|
|
10
14
|
import { NotFoundPage } from './pages/NotFoundPage';
|
|
15
|
+
import { api, type MenuContribution } from './api/controlPanelApi';
|
|
16
|
+
|
|
17
|
+
// Navigation item type
|
|
18
|
+
interface NavigationItem {
|
|
19
|
+
id: string;
|
|
20
|
+
label: string;
|
|
21
|
+
route: string;
|
|
22
|
+
icon: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Core navigation items always shown
|
|
26
|
+
const coreNavigationItems: NavigationItem[] = [
|
|
27
|
+
{ id: 'dashboard', label: 'Dashboard', route: '/', icon: 'dashboard' },
|
|
28
|
+
{ id: 'plugins', label: 'Plugins', route: '/plugins', icon: 'extension' },
|
|
29
|
+
{ id: 'logs', label: 'Logs', route: '/logs', icon: 'article' },
|
|
30
|
+
{ id: 'system', label: 'System', route: '/system', icon: 'settings' },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// Built-in optional navigation items - shown if corresponding plugin is registered
|
|
34
|
+
const builtInPluginNavItems: Record<string, NavigationItem> = {
|
|
35
|
+
users: { id: 'users', label: 'Users', route: '/users', icon: 'people' },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Routes that have dedicated page components
|
|
39
|
+
const dedicatedRoutes = new Set(['/', '/plugins', '/logs', '/system', '/users', '/entitlements']);
|
|
11
40
|
|
|
12
41
|
// Package version - injected at build time or fallback
|
|
13
42
|
const SERVER_VERSION = '1.0.0';
|
|
14
43
|
|
|
15
|
-
//
|
|
16
|
-
|
|
44
|
+
// Declare global type for injected base path
|
|
45
|
+
declare global {
|
|
46
|
+
interface Window {
|
|
47
|
+
__APP_BASE_PATH__?: string;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get the base path for the application.
|
|
53
|
+
*
|
|
54
|
+
* The server injects window.__APP_BASE_PATH__ at runtime based on
|
|
55
|
+
* either the configured mountPath or X-Forwarded-Prefix header.
|
|
56
|
+
* This is a simple, robust approach - no complex detection needed.
|
|
57
|
+
*/
|
|
58
|
+
const basePath = window.__APP_BASE_PATH__ ?? '';
|
|
59
|
+
|
|
60
|
+
// Configure API with the detected base path
|
|
61
|
+
api.setBaseUrl(basePath);
|
|
17
62
|
|
|
18
|
-
//
|
|
63
|
+
// Footer content with QwickApps Server branding
|
|
19
64
|
const footerContent = (
|
|
20
65
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 0.5, py: 2 }}>
|
|
21
66
|
<Text variant="caption" customColor="var(--theme-text-secondary)">
|
|
@@ -34,32 +79,144 @@ const footerContent = (
|
|
|
34
79
|
);
|
|
35
80
|
|
|
36
81
|
export function App() {
|
|
82
|
+
const [navigationItems, setNavigationItems] = useState<NavigationItem[]>(coreNavigationItems);
|
|
83
|
+
const [registeredPlugins, setRegisteredPlugins] = useState<Set<string>>(new Set());
|
|
84
|
+
const [pluginMenuItems, setPluginMenuItems] = useState<MenuContribution[]>([]);
|
|
85
|
+
const [logoName, setLogoName] = useState<string>('Control Panel');
|
|
86
|
+
const [logoIconUrl, setLogoIconUrl] = useState<string | undefined>();
|
|
87
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
88
|
+
|
|
89
|
+
// Fetch product info and UI contributions on mount
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
const loadData = async () => {
|
|
92
|
+
try {
|
|
93
|
+
// Fetch both in parallel
|
|
94
|
+
const [infoResult, contributionsResult] = await Promise.allSettled([
|
|
95
|
+
api.getInfo(),
|
|
96
|
+
api.getUiContributions(),
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
// Update logo name and icon URL if info fetch succeeded
|
|
100
|
+
if (infoResult.status === 'fulfilled') {
|
|
101
|
+
setLogoName(infoResult.value.logoName);
|
|
102
|
+
setLogoIconUrl(infoResult.value.logoIconUrl);
|
|
103
|
+
} else {
|
|
104
|
+
console.warn('Failed to fetch product info:', infoResult.reason);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Update navigation from UI contributions
|
|
108
|
+
if (contributionsResult.status === 'fulfilled') {
|
|
109
|
+
const { plugins, menuItems } = contributionsResult.value;
|
|
110
|
+
const pluginIds = new Set(plugins.map((p) => p.id));
|
|
111
|
+
setRegisteredPlugins(pluginIds);
|
|
112
|
+
setPluginMenuItems(menuItems);
|
|
113
|
+
|
|
114
|
+
// Build navigation: core items + built-in plugin items + dynamic menu items
|
|
115
|
+
const dynamicNav = [...coreNavigationItems];
|
|
116
|
+
|
|
117
|
+
// Add built-in plugin nav items (like Users)
|
|
118
|
+
for (const [pluginId, navItem] of Object.entries(builtInPluginNavItems)) {
|
|
119
|
+
if (pluginIds.has(pluginId)) {
|
|
120
|
+
dynamicNav.push(navItem);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Add plugin-contributed menu items (sorted by order)
|
|
125
|
+
const sortedMenuItems = [...menuItems].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
|
|
126
|
+
for (const menuItem of sortedMenuItems) {
|
|
127
|
+
// Skip if we already have a nav item for this route
|
|
128
|
+
if (dynamicNav.some(nav => nav.route === menuItem.route)) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
dynamicNav.push({
|
|
132
|
+
id: menuItem.id,
|
|
133
|
+
label: menuItem.label,
|
|
134
|
+
route: menuItem.route,
|
|
135
|
+
icon: menuItem.icon || 'extension',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
setNavigationItems(dynamicNav);
|
|
140
|
+
} else {
|
|
141
|
+
console.warn('Failed to fetch UI contributions:', contributionsResult.reason);
|
|
142
|
+
}
|
|
143
|
+
} finally {
|
|
144
|
+
setIsLoading(false);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
loadData();
|
|
149
|
+
}, []);
|
|
150
|
+
|
|
151
|
+
// Dynamic logo based on logoName and logoIconUrl from API
|
|
152
|
+
// When logoIconUrl is provided, use it as a custom icon instead of the default QwickIcon
|
|
153
|
+
const logoIcon = logoIconUrl ? (
|
|
154
|
+
<img
|
|
155
|
+
src={logoIconUrl}
|
|
156
|
+
alt={logoName}
|
|
157
|
+
style={{ width: 32, height: 32, objectFit: 'contain' }}
|
|
158
|
+
/>
|
|
159
|
+
) : undefined;
|
|
160
|
+
const logo = <ProductLogo icon={logoIcon} name={logoName} />;
|
|
161
|
+
|
|
162
|
+
// Show loading state until plugins are loaded
|
|
163
|
+
// This ensures QwickApp receives the correct navigation on first render
|
|
164
|
+
if (isLoading) {
|
|
165
|
+
return (
|
|
166
|
+
<Box
|
|
167
|
+
sx={{
|
|
168
|
+
display: 'flex',
|
|
169
|
+
justifyContent: 'center',
|
|
170
|
+
alignItems: 'center',
|
|
171
|
+
minHeight: '100vh',
|
|
172
|
+
bgcolor: 'var(--theme-background, #1a1a2e)',
|
|
173
|
+
}}
|
|
174
|
+
/>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
37
178
|
return (
|
|
38
|
-
<BrowserRouter>
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
{
|
|
46
|
-
{
|
|
47
|
-
{
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
179
|
+
<BrowserRouter basename={basePath || undefined}>
|
|
180
|
+
<DashboardWidgetProvider>
|
|
181
|
+
<QwickApp
|
|
182
|
+
config={defaultConfig}
|
|
183
|
+
logo={logo}
|
|
184
|
+
footerContent={footerContent}
|
|
185
|
+
enableScaffolding={true}
|
|
186
|
+
navigationItems={navigationItems}
|
|
187
|
+
showThemeSwitcher={true}
|
|
188
|
+
showPaletteSwitcher={true}
|
|
189
|
+
>
|
|
190
|
+
<Routes>
|
|
191
|
+
{/* Core routes */}
|
|
192
|
+
<Route path="/" element={<DashboardPage />} />
|
|
193
|
+
<Route path="/plugins" element={<PluginsPage />} />
|
|
194
|
+
<Route path="/logs" element={<LogsPage />} />
|
|
195
|
+
<Route path="/system" element={<SystemPage />} />
|
|
196
|
+
|
|
197
|
+
{/* Built-in plugin routes */}
|
|
198
|
+
{registeredPlugins.has('users') && (
|
|
199
|
+
<Route path="/users" element={<UsersPage />} />
|
|
200
|
+
)}
|
|
201
|
+
{registeredPlugins.has('entitlements') && (
|
|
202
|
+
<Route path="/entitlements" element={<EntitlementsPage />} />
|
|
203
|
+
)}
|
|
204
|
+
|
|
205
|
+
{/* Dynamic plugin routes - render generic PluginPage for non-dedicated routes */}
|
|
206
|
+
{pluginMenuItems
|
|
207
|
+
.filter(item => !dedicatedRoutes.has(item.route))
|
|
208
|
+
.map(item => (
|
|
209
|
+
<Route
|
|
210
|
+
key={item.id}
|
|
211
|
+
path={item.route}
|
|
212
|
+
element={<PluginPage pluginId={item.pluginId} title={item.label} route={item.route} />}
|
|
213
|
+
/>
|
|
214
|
+
))}
|
|
215
|
+
|
|
216
|
+
<Route path="*" element={<NotFoundPage />} />
|
|
217
|
+
</Routes>
|
|
218
|
+
</QwickApp>
|
|
219
|
+
</DashboardWidgetProvider>
|
|
63
220
|
</BrowserRouter>
|
|
64
221
|
);
|
|
65
222
|
}
|