@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
|
@@ -20,12 +20,13 @@ export interface HealthResponse {
|
|
|
20
20
|
|
|
21
21
|
export interface InfoResponse {
|
|
22
22
|
product: string;
|
|
23
|
+
logoName: string;
|
|
24
|
+
logoIconUrl?: string;
|
|
23
25
|
version: string;
|
|
24
26
|
uptime: number;
|
|
25
27
|
links: Array<{ label: string; url: string; external?: boolean }>;
|
|
26
28
|
branding?: {
|
|
27
29
|
primaryColor?: string;
|
|
28
|
-
logo?: string;
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -33,6 +34,8 @@ export interface DiagnosticsResponse {
|
|
|
33
34
|
timestamp: string;
|
|
34
35
|
product: string;
|
|
35
36
|
version?: string;
|
|
37
|
+
/** @qwickapps/server framework version */
|
|
38
|
+
frameworkVersion?: string;
|
|
36
39
|
uptime: number;
|
|
37
40
|
health: Record<string, HealthCheck>;
|
|
38
41
|
system: {
|
|
@@ -75,6 +78,192 @@ export interface LogSource {
|
|
|
75
78
|
available: boolean;
|
|
76
79
|
}
|
|
77
80
|
|
|
81
|
+
// ==================
|
|
82
|
+
// Users API Types
|
|
83
|
+
// ==================
|
|
84
|
+
export interface User {
|
|
85
|
+
id: string;
|
|
86
|
+
email: string;
|
|
87
|
+
name?: string;
|
|
88
|
+
created_at?: string;
|
|
89
|
+
updated_at?: string;
|
|
90
|
+
last_login?: string;
|
|
91
|
+
metadata?: Record<string, unknown>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface UsersResponse {
|
|
95
|
+
users: User[];
|
|
96
|
+
total: number;
|
|
97
|
+
page: number;
|
|
98
|
+
limit: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ==================
|
|
102
|
+
// Bans API Types
|
|
103
|
+
// ==================
|
|
104
|
+
export interface Ban {
|
|
105
|
+
id: string;
|
|
106
|
+
user_id?: string;
|
|
107
|
+
email: string;
|
|
108
|
+
reason: string;
|
|
109
|
+
banned_at: string;
|
|
110
|
+
banned_by: string;
|
|
111
|
+
expires_at?: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface BansResponse {
|
|
115
|
+
bans: Ban[];
|
|
116
|
+
total: number;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ==================
|
|
120
|
+
// Entitlements API Types
|
|
121
|
+
// ==================
|
|
122
|
+
export interface EntitlementDefinition {
|
|
123
|
+
id: string;
|
|
124
|
+
name: string;
|
|
125
|
+
category?: string;
|
|
126
|
+
description?: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface EntitlementResult {
|
|
130
|
+
identifier: string;
|
|
131
|
+
entitlements: string[];
|
|
132
|
+
source: string;
|
|
133
|
+
cached?: boolean;
|
|
134
|
+
cachedAt?: string;
|
|
135
|
+
expiresAt?: string;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ==================
|
|
139
|
+
// Entitlements Status
|
|
140
|
+
// ==================
|
|
141
|
+
export interface EntitlementSourceInfo {
|
|
142
|
+
name: string;
|
|
143
|
+
description?: string;
|
|
144
|
+
readonly: boolean;
|
|
145
|
+
primary: boolean;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface EntitlementsStatus {
|
|
149
|
+
readonly: boolean;
|
|
150
|
+
writeEnabled: boolean;
|
|
151
|
+
cacheEnabled: boolean;
|
|
152
|
+
cacheTtl: number;
|
|
153
|
+
sources: EntitlementSourceInfo[];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ==================
|
|
157
|
+
// Plugin Feature Detection
|
|
158
|
+
// ==================
|
|
159
|
+
export interface PluginFeatures {
|
|
160
|
+
users: boolean;
|
|
161
|
+
bans: boolean;
|
|
162
|
+
entitlements: boolean;
|
|
163
|
+
entitlementsReadonly?: boolean;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ==================
|
|
167
|
+
// UI Contributions Types
|
|
168
|
+
// ==================
|
|
169
|
+
|
|
170
|
+
export interface MenuContribution {
|
|
171
|
+
id: string;
|
|
172
|
+
label: string;
|
|
173
|
+
icon?: string;
|
|
174
|
+
route: string;
|
|
175
|
+
order?: number;
|
|
176
|
+
pluginId: string;
|
|
177
|
+
parent?: string;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export interface PageContribution {
|
|
181
|
+
id: string;
|
|
182
|
+
route: string;
|
|
183
|
+
title: string;
|
|
184
|
+
pluginId: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface WidgetContribution {
|
|
188
|
+
id: string;
|
|
189
|
+
title: string;
|
|
190
|
+
/** Component name to render (matched by frontend widget registry) */
|
|
191
|
+
component: string;
|
|
192
|
+
/** Priority for ordering (lower = first, default: 100) */
|
|
193
|
+
priority?: number;
|
|
194
|
+
/** Whether this widget is shown by default */
|
|
195
|
+
showByDefault?: boolean;
|
|
196
|
+
pluginId: string;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export interface UiContributionsResponse {
|
|
200
|
+
menuItems: MenuContribution[];
|
|
201
|
+
pages: PageContribution[];
|
|
202
|
+
widgets: WidgetContribution[];
|
|
203
|
+
plugins: Array<{ id: string; name: string; version?: string; status: string }>;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ==================
|
|
207
|
+
// Plugin Detail Types
|
|
208
|
+
// ==================
|
|
209
|
+
|
|
210
|
+
export interface ConfigContribution {
|
|
211
|
+
id: string;
|
|
212
|
+
component: string;
|
|
213
|
+
title?: string;
|
|
214
|
+
pluginId: string;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export interface PluginContributions {
|
|
218
|
+
routes: Array<{ method: string; path: string }>;
|
|
219
|
+
menuItems: MenuContribution[];
|
|
220
|
+
pages: PageContribution[];
|
|
221
|
+
widgets: WidgetContribution[];
|
|
222
|
+
config?: ConfigContribution;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export interface PluginInfo {
|
|
226
|
+
id: string;
|
|
227
|
+
name: string;
|
|
228
|
+
version?: string;
|
|
229
|
+
status: 'starting' | 'active' | 'stopped' | 'error';
|
|
230
|
+
error?: string;
|
|
231
|
+
contributionCounts: {
|
|
232
|
+
routes: number;
|
|
233
|
+
menuItems: number;
|
|
234
|
+
pages: number;
|
|
235
|
+
widgets: number;
|
|
236
|
+
hasConfig: boolean;
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export interface PluginsResponse {
|
|
241
|
+
plugins: PluginInfo[];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export interface PluginDetailResponse {
|
|
245
|
+
id: string;
|
|
246
|
+
name: string;
|
|
247
|
+
version?: string;
|
|
248
|
+
status: 'starting' | 'active' | 'stopped' | 'error';
|
|
249
|
+
error?: string;
|
|
250
|
+
contributions: PluginContributions;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ==================
|
|
254
|
+
// Auth Config Types
|
|
255
|
+
// ==================
|
|
256
|
+
|
|
257
|
+
export type AuthPluginState = 'disabled' | 'enabled' | 'error';
|
|
258
|
+
|
|
259
|
+
export interface AuthConfigStatus {
|
|
260
|
+
state: AuthPluginState;
|
|
261
|
+
adapter: string | null;
|
|
262
|
+
error?: string;
|
|
263
|
+
missingVars?: string[];
|
|
264
|
+
config?: Record<string, string>;
|
|
265
|
+
}
|
|
266
|
+
|
|
78
267
|
class ControlPanelApi {
|
|
79
268
|
private baseUrl: string;
|
|
80
269
|
|
|
@@ -82,6 +271,207 @@ class ControlPanelApi {
|
|
|
82
271
|
this.baseUrl = baseUrl;
|
|
83
272
|
}
|
|
84
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Set the base URL for API requests.
|
|
276
|
+
* Call this when the control panel is mounted at a custom path.
|
|
277
|
+
*/
|
|
278
|
+
setBaseUrl(baseUrl: string): void {
|
|
279
|
+
this.baseUrl = baseUrl;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ==================
|
|
283
|
+
// Plugin Feature Detection
|
|
284
|
+
// ==================
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Detect which user management plugins are available by probing their endpoints
|
|
288
|
+
*/
|
|
289
|
+
async detectFeatures(): Promise<PluginFeatures> {
|
|
290
|
+
const [users, bans, entitlements] = await Promise.all([
|
|
291
|
+
this.checkEndpoint('/api/users'),
|
|
292
|
+
this.checkEndpoint('/api/bans'),
|
|
293
|
+
this.checkEndpoint('/api/entitlements/available'),
|
|
294
|
+
]);
|
|
295
|
+
|
|
296
|
+
// If entitlements is available, get readonly status
|
|
297
|
+
let entitlementsReadonly = true;
|
|
298
|
+
if (entitlements) {
|
|
299
|
+
try {
|
|
300
|
+
const status = await this.getEntitlementsStatus();
|
|
301
|
+
entitlementsReadonly = status.readonly;
|
|
302
|
+
} catch {
|
|
303
|
+
// Default to readonly if we can't get status
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return { users, bans, entitlements, entitlementsReadonly };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private async checkEndpoint(path: string): Promise<boolean> {
|
|
311
|
+
try {
|
|
312
|
+
const response = await fetch(`${this.baseUrl}${path}`, { method: 'HEAD' });
|
|
313
|
+
// 200, 401, 403 mean the endpoint exists (might need auth)
|
|
314
|
+
// 404 means it doesn't exist
|
|
315
|
+
return response.status !== 404;
|
|
316
|
+
} catch {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ==================
|
|
322
|
+
// Users API
|
|
323
|
+
// ==================
|
|
324
|
+
|
|
325
|
+
async getUsers(options: {
|
|
326
|
+
limit?: number;
|
|
327
|
+
page?: number;
|
|
328
|
+
search?: string;
|
|
329
|
+
} = {}): Promise<UsersResponse> {
|
|
330
|
+
const params = new URLSearchParams();
|
|
331
|
+
if (options.limit) params.set('limit', options.limit.toString());
|
|
332
|
+
if (options.page) params.set('page', options.page.toString());
|
|
333
|
+
if (options.search) params.set('search', options.search);
|
|
334
|
+
|
|
335
|
+
const response = await fetch(`${this.baseUrl}/api/users?${params}`);
|
|
336
|
+
if (!response.ok) {
|
|
337
|
+
throw new Error(`Users request failed: ${response.statusText}`);
|
|
338
|
+
}
|
|
339
|
+
return response.json();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async getUserById(id: string): Promise<User> {
|
|
343
|
+
const response = await fetch(`${this.baseUrl}/api/users/${id}`);
|
|
344
|
+
if (!response.ok) {
|
|
345
|
+
throw new Error(`User request failed: ${response.statusText}`);
|
|
346
|
+
}
|
|
347
|
+
return response.json();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ==================
|
|
351
|
+
// Bans API
|
|
352
|
+
// ==================
|
|
353
|
+
|
|
354
|
+
async getBans(): Promise<BansResponse> {
|
|
355
|
+
const response = await fetch(`${this.baseUrl}/api/bans`);
|
|
356
|
+
if (!response.ok) {
|
|
357
|
+
throw new Error(`Bans request failed: ${response.statusText}`);
|
|
358
|
+
}
|
|
359
|
+
return response.json();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async banUser(email: string, reason: string, expiresAt?: string): Promise<void> {
|
|
363
|
+
const response = await fetch(`${this.baseUrl}/api/bans`, {
|
|
364
|
+
method: 'POST',
|
|
365
|
+
headers: { 'Content-Type': 'application/json' },
|
|
366
|
+
body: JSON.stringify({ email, reason, expiresAt }),
|
|
367
|
+
});
|
|
368
|
+
if (!response.ok) {
|
|
369
|
+
const error = await response.json().catch(() => ({}));
|
|
370
|
+
throw new Error(error.error || `Ban request failed: ${response.statusText}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async unbanUser(email: string): Promise<void> {
|
|
375
|
+
const response = await fetch(`${this.baseUrl}/api/bans/${encodeURIComponent(email)}`, {
|
|
376
|
+
method: 'DELETE',
|
|
377
|
+
});
|
|
378
|
+
if (!response.ok) {
|
|
379
|
+
throw new Error(`Unban request failed: ${response.statusText}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async checkBan(email: string): Promise<{ banned: boolean; ban?: Ban }> {
|
|
384
|
+
const response = await fetch(`${this.baseUrl}/api/bans/check/${encodeURIComponent(email)}`);
|
|
385
|
+
if (!response.ok) {
|
|
386
|
+
throw new Error(`Ban check failed: ${response.statusText}`);
|
|
387
|
+
}
|
|
388
|
+
return response.json();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// ==================
|
|
392
|
+
// Entitlements API
|
|
393
|
+
// ==================
|
|
394
|
+
|
|
395
|
+
async getEntitlements(email: string): Promise<EntitlementResult> {
|
|
396
|
+
const response = await fetch(`${this.baseUrl}/api/entitlements/${encodeURIComponent(email)}`);
|
|
397
|
+
if (!response.ok) {
|
|
398
|
+
throw new Error(`Entitlements request failed: ${response.statusText}`);
|
|
399
|
+
}
|
|
400
|
+
return response.json();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async refreshEntitlements(email: string): Promise<EntitlementResult> {
|
|
404
|
+
const response = await fetch(`${this.baseUrl}/api/entitlements/${encodeURIComponent(email)}/refresh`, {
|
|
405
|
+
method: 'POST',
|
|
406
|
+
});
|
|
407
|
+
if (!response.ok) {
|
|
408
|
+
throw new Error(`Entitlements refresh failed: ${response.statusText}`);
|
|
409
|
+
}
|
|
410
|
+
return response.json();
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async checkEntitlement(email: string, entitlement: string): Promise<{ has: boolean }> {
|
|
414
|
+
const response = await fetch(
|
|
415
|
+
`${this.baseUrl}/api/entitlements/${encodeURIComponent(email)}/check/${encodeURIComponent(entitlement)}`
|
|
416
|
+
);
|
|
417
|
+
if (!response.ok) {
|
|
418
|
+
throw new Error(`Entitlement check failed: ${response.statusText}`);
|
|
419
|
+
}
|
|
420
|
+
return response.json();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async getAvailableEntitlements(): Promise<EntitlementDefinition[]> {
|
|
424
|
+
const response = await fetch(`${this.baseUrl}/api/entitlements/available`);
|
|
425
|
+
if (!response.ok) {
|
|
426
|
+
throw new Error(`Available entitlements request failed: ${response.statusText}`);
|
|
427
|
+
}
|
|
428
|
+
const data = await response.json();
|
|
429
|
+
return data.entitlements;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async grantEntitlement(email: string, entitlement: string): Promise<void> {
|
|
433
|
+
const response = await fetch(`${this.baseUrl}/api/entitlements/${encodeURIComponent(email)}`, {
|
|
434
|
+
method: 'POST',
|
|
435
|
+
headers: { 'Content-Type': 'application/json' },
|
|
436
|
+
body: JSON.stringify({ entitlement }),
|
|
437
|
+
});
|
|
438
|
+
if (!response.ok) {
|
|
439
|
+
const error = await response.json().catch(() => ({}));
|
|
440
|
+
throw new Error(error.error || `Grant entitlement failed: ${response.statusText}`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async revokeEntitlement(email: string, entitlement: string): Promise<void> {
|
|
445
|
+
const response = await fetch(
|
|
446
|
+
`${this.baseUrl}/api/entitlements/${encodeURIComponent(email)}/${encodeURIComponent(entitlement)}`,
|
|
447
|
+
{ method: 'DELETE' }
|
|
448
|
+
);
|
|
449
|
+
if (!response.ok) {
|
|
450
|
+
throw new Error(`Revoke entitlement failed: ${response.statusText}`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async invalidateEntitlementCache(email: string): Promise<void> {
|
|
455
|
+
const response = await fetch(`${this.baseUrl}/api/entitlements/cache/${encodeURIComponent(email)}`, {
|
|
456
|
+
method: 'DELETE',
|
|
457
|
+
});
|
|
458
|
+
if (!response.ok) {
|
|
459
|
+
throw new Error(`Cache invalidation failed: ${response.statusText}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async getEntitlementsStatus(): Promise<EntitlementsStatus> {
|
|
464
|
+
const response = await fetch(`${this.baseUrl}/api/entitlements/status`);
|
|
465
|
+
if (!response.ok) {
|
|
466
|
+
throw new Error(`Entitlements status request failed: ${response.statusText}`);
|
|
467
|
+
}
|
|
468
|
+
return response.json();
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// ==================
|
|
472
|
+
// Health API
|
|
473
|
+
// ==================
|
|
474
|
+
|
|
85
475
|
async getHealth(): Promise<HealthResponse> {
|
|
86
476
|
const response = await fetch(`${this.baseUrl}/api/health`);
|
|
87
477
|
if (!response.ok) {
|
|
@@ -143,6 +533,68 @@ class ControlPanelApi {
|
|
|
143
533
|
const data = await response.json();
|
|
144
534
|
return data.sources;
|
|
145
535
|
}
|
|
536
|
+
|
|
537
|
+
// ==================
|
|
538
|
+
// Plugins API
|
|
539
|
+
// ==================
|
|
540
|
+
|
|
541
|
+
async getPlugins(): Promise<PluginsResponse> {
|
|
542
|
+
const response = await fetch(`${this.baseUrl}/api/plugins`);
|
|
543
|
+
if (!response.ok) {
|
|
544
|
+
throw new Error(`Plugins request failed: ${response.statusText}`);
|
|
545
|
+
}
|
|
546
|
+
return response.json();
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
async getPluginDetail(id: string): Promise<PluginDetailResponse> {
|
|
550
|
+
const response = await fetch(`${this.baseUrl}/api/plugins/${encodeURIComponent(id)}`);
|
|
551
|
+
if (!response.ok) {
|
|
552
|
+
if (response.status === 404) {
|
|
553
|
+
throw new Error(`Plugin not found: ${id}`);
|
|
554
|
+
}
|
|
555
|
+
throw new Error(`Plugin detail request failed: ${response.statusText}`);
|
|
556
|
+
}
|
|
557
|
+
return response.json();
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// ==================
|
|
561
|
+
// UI Contributions API
|
|
562
|
+
// ==================
|
|
563
|
+
|
|
564
|
+
async getUiContributions(): Promise<UiContributionsResponse> {
|
|
565
|
+
const response = await fetch(`${this.baseUrl}/api/ui-contributions`);
|
|
566
|
+
if (!response.ok) {
|
|
567
|
+
throw new Error(`UI contributions request failed: ${response.statusText}`);
|
|
568
|
+
}
|
|
569
|
+
return response.json();
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// ==================
|
|
573
|
+
// Auth Config API
|
|
574
|
+
// ==================
|
|
575
|
+
|
|
576
|
+
async getAuthConfigStatus(): Promise<AuthConfigStatus> {
|
|
577
|
+
const response = await fetch(`${this.baseUrl}/api/auth/config/status`);
|
|
578
|
+
if (!response.ok) {
|
|
579
|
+
// Return disabled state if endpoint not available
|
|
580
|
+
if (response.status === 404) {
|
|
581
|
+
return { state: 'disabled', adapter: null };
|
|
582
|
+
}
|
|
583
|
+
throw new Error(`Auth config status request failed: ${response.statusText}`);
|
|
584
|
+
}
|
|
585
|
+
return response.json();
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
async getAuthConfig(): Promise<AuthConfigStatus> {
|
|
589
|
+
const response = await fetch(`${this.baseUrl}/api/auth/config`);
|
|
590
|
+
if (!response.ok) {
|
|
591
|
+
if (response.status === 404) {
|
|
592
|
+
return { state: 'disabled', adapter: null };
|
|
593
|
+
}
|
|
594
|
+
throw new Error(`Auth config request failed: ${response.statusText}`);
|
|
595
|
+
}
|
|
596
|
+
return response.json();
|
|
597
|
+
}
|
|
146
598
|
}
|
|
147
599
|
|
|
148
600
|
export const api = new ControlPanelApi();
|
|
@@ -0,0 +1,212 @@
|
|
|
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 { AuthPage } from '../pages/AuthPage';
|
|
40
|
+
import { NotFoundPage } from '../pages/NotFoundPage';
|
|
41
|
+
|
|
42
|
+
// Dashboard widget system
|
|
43
|
+
import {
|
|
44
|
+
DashboardWidgetProvider,
|
|
45
|
+
WidgetComponentRegistryProvider,
|
|
46
|
+
getBuiltInWidgetComponents,
|
|
47
|
+
type DashboardWidget,
|
|
48
|
+
type WidgetComponent,
|
|
49
|
+
} from '../dashboard';
|
|
50
|
+
|
|
51
|
+
// API
|
|
52
|
+
import { api } from '../api/controlPanelApi';
|
|
53
|
+
|
|
54
|
+
export interface ControlPanelAppProps {
|
|
55
|
+
/** Product name displayed in the header */
|
|
56
|
+
productName?: string;
|
|
57
|
+
|
|
58
|
+
/** Custom logo component */
|
|
59
|
+
logo?: ReactNode;
|
|
60
|
+
|
|
61
|
+
/** Custom footer content (replaces default) */
|
|
62
|
+
footerContent?: ReactNode;
|
|
63
|
+
|
|
64
|
+
/** Initial dashboard widgets to register (legacy context-based system) */
|
|
65
|
+
dashboardWidgets?: DashboardWidget[];
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Widget components to register for the plugin-based widget system.
|
|
69
|
+
* These map component names (from server WidgetContribution) to React components.
|
|
70
|
+
* Built-in widgets (ServiceHealthWidget, etc.) are registered automatically.
|
|
71
|
+
*/
|
|
72
|
+
widgetComponents?: WidgetComponent[];
|
|
73
|
+
|
|
74
|
+
/** Additional navigation items to add to the base control panel nav */
|
|
75
|
+
navigationItems?: MenuItem[];
|
|
76
|
+
|
|
77
|
+
/** Whether to show the base control panel navigation (Dashboard, Health, etc.) */
|
|
78
|
+
showBaseNavigation?: boolean;
|
|
79
|
+
|
|
80
|
+
/** Base navigation item IDs to hide (e.g., ['health'] to hide the Health page) */
|
|
81
|
+
hideBaseNavItems?: string[];
|
|
82
|
+
|
|
83
|
+
/** Whether to show theme switcher in settings */
|
|
84
|
+
showThemeSwitcher?: boolean;
|
|
85
|
+
|
|
86
|
+
/** Whether to show palette switcher in settings */
|
|
87
|
+
showPaletteSwitcher?: boolean;
|
|
88
|
+
|
|
89
|
+
/** Base path for the control panel (e.g., '/cpanel') */
|
|
90
|
+
basePath?: string;
|
|
91
|
+
|
|
92
|
+
/** Custom routes to add (as Route elements) */
|
|
93
|
+
children?: ReactNode;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Default footer with QwickApps Server branding
|
|
98
|
+
*/
|
|
99
|
+
function DefaultFooter({ version }: { version: string }) {
|
|
100
|
+
return (
|
|
101
|
+
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 0.5, py: 2 }}>
|
|
102
|
+
<Text variant="caption" customColor="var(--theme-text-secondary)">
|
|
103
|
+
Built with{' '}
|
|
104
|
+
<Link
|
|
105
|
+
href="https://qwickapps.com/products/qwickapps-server"
|
|
106
|
+
target="_blank"
|
|
107
|
+
rel="noopener noreferrer"
|
|
108
|
+
sx={{ color: 'primary.main' }}
|
|
109
|
+
>
|
|
110
|
+
QwickApps Server
|
|
111
|
+
</Link>
|
|
112
|
+
{version && ` v${version}`}
|
|
113
|
+
</Text>
|
|
114
|
+
</Box>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Base navigation items for control panel
|
|
120
|
+
* Routes are relative to BrowserRouter's basename (handled by NavigationProvider in QwickApp)
|
|
121
|
+
*/
|
|
122
|
+
function getBaseNavigationItems(): MenuItem[] {
|
|
123
|
+
return [
|
|
124
|
+
{ id: 'dashboard', label: 'Dashboard', route: '/', icon: 'dashboard' },
|
|
125
|
+
{ id: 'logs', label: 'Logs', route: '/logs', icon: 'article' },
|
|
126
|
+
{ id: 'auth', label: 'Auth', route: '/auth', icon: 'lock' },
|
|
127
|
+
{ id: 'system', label: 'System', route: '/system', icon: 'settings' },
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function ControlPanelApp({
|
|
132
|
+
productName = 'Control Panel',
|
|
133
|
+
logo,
|
|
134
|
+
footerContent,
|
|
135
|
+
dashboardWidgets = [],
|
|
136
|
+
widgetComponents = [],
|
|
137
|
+
navigationItems = [],
|
|
138
|
+
showBaseNavigation = true,
|
|
139
|
+
hideBaseNavItems = [],
|
|
140
|
+
showThemeSwitcher = true,
|
|
141
|
+
showPaletteSwitcher = true,
|
|
142
|
+
basePath = '',
|
|
143
|
+
children,
|
|
144
|
+
}: ControlPanelAppProps) {
|
|
145
|
+
const [version, setVersion] = useState<string>('');
|
|
146
|
+
|
|
147
|
+
// Combine built-in widget components with custom ones
|
|
148
|
+
const allWidgetComponents = [...getBuiltInWidgetComponents(), ...widgetComponents];
|
|
149
|
+
|
|
150
|
+
// Configure API base URL based on basePath - do this synchronously before any renders
|
|
151
|
+
// If basePath is '/cpanel', API is at '/cpanel/api'
|
|
152
|
+
// If basePath is '' or '/', API is at '/api'
|
|
153
|
+
const apiBasePath = basePath && basePath !== '/' ? basePath : '';
|
|
154
|
+
api.setBaseUrl(apiBasePath);
|
|
155
|
+
|
|
156
|
+
// Fetch version from API
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
api.getInfo()
|
|
159
|
+
.then((info) => setVersion(info.version || ''))
|
|
160
|
+
.catch(() => {});
|
|
161
|
+
}, [apiBasePath]); // Re-fetch when apiBasePath changes
|
|
162
|
+
|
|
163
|
+
// Build navigation: base items (filtered by hideBaseNavItems) + custom items
|
|
164
|
+
// Navigation routes are relative to BrowserRouter's basename
|
|
165
|
+
const filteredBaseItems = showBaseNavigation
|
|
166
|
+
? getBaseNavigationItems().filter(item => !hideBaseNavItems.includes(item.id))
|
|
167
|
+
: [];
|
|
168
|
+
const allNavigationItems: MenuItem[] = [
|
|
169
|
+
...filteredBaseItems,
|
|
170
|
+
...navigationItems,
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
// Default logo if not provided
|
|
174
|
+
const effectiveLogo = logo || <ProductLogo name={productName} />;
|
|
175
|
+
|
|
176
|
+
// Default footer if not provided
|
|
177
|
+
const effectiveFooter = footerContent || <DefaultFooter version={version} />;
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<WidgetComponentRegistryProvider initialComponents={allWidgetComponents}>
|
|
181
|
+
<DashboardWidgetProvider initialWidgets={dashboardWidgets}>
|
|
182
|
+
<QwickApp
|
|
183
|
+
config={defaultConfig}
|
|
184
|
+
logo={effectiveLogo}
|
|
185
|
+
footerContent={effectiveFooter}
|
|
186
|
+
enableScaffolding={true}
|
|
187
|
+
navigationItems={allNavigationItems}
|
|
188
|
+
showThemeSwitcher={showThemeSwitcher}
|
|
189
|
+
showPaletteSwitcher={showPaletteSwitcher}
|
|
190
|
+
>
|
|
191
|
+
<Routes>
|
|
192
|
+
{/* Base control panel routes (filtered by hideBaseNavItems) */}
|
|
193
|
+
{showBaseNavigation && (
|
|
194
|
+
<>
|
|
195
|
+
{!hideBaseNavItems.includes('dashboard') && <Route path="/" element={<DashboardPage />} />}
|
|
196
|
+
{!hideBaseNavItems.includes('logs') && <Route path="/logs" element={<LogsPage />} />}
|
|
197
|
+
{!hideBaseNavItems.includes('auth') && <Route path="/auth" element={<AuthPage />} />}
|
|
198
|
+
{!hideBaseNavItems.includes('system') && <Route path="/system" element={<SystemPage />} />}
|
|
199
|
+
</>
|
|
200
|
+
)}
|
|
201
|
+
|
|
202
|
+
{/* Custom routes from consumer */}
|
|
203
|
+
{children}
|
|
204
|
+
|
|
205
|
+
{/* Catch-all for 404 */}
|
|
206
|
+
<Route path="*" element={<NotFoundPage />} />
|
|
207
|
+
</Routes>
|
|
208
|
+
</QwickApp>
|
|
209
|
+
</DashboardWidgetProvider>
|
|
210
|
+
</WidgetComponentRegistryProvider>
|
|
211
|
+
);
|
|
212
|
+
}
|