@qwickapps/server 1.1.9 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +318 -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 +99 -60
- 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 +683 -315
- package/dist/core/gateway.js.map +1 -1
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/plugin-registry.d.ts +271 -0
- package/dist/core/plugin-registry.d.ts.map +1 -0
- package/dist/core/plugin-registry.js +326 -0
- package/dist/core/plugin-registry.js.map +1 -0
- package/dist/core/types.d.ts +16 -33
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -7
- package/dist/index.js.map +1 -1
- package/dist/plugins/auth/adapters/auth0-adapter.d.ts +14 -0
- package/dist/plugins/auth/adapters/auth0-adapter.d.ts.map +1 -0
- package/dist/plugins/auth/adapters/auth0-adapter.js +179 -0
- package/dist/plugins/auth/adapters/auth0-adapter.js.map +1 -0
- package/dist/plugins/auth/adapters/basic-adapter.d.ts +13 -0
- package/dist/plugins/auth/adapters/basic-adapter.d.ts.map +1 -0
- package/dist/plugins/auth/adapters/basic-adapter.js +51 -0
- package/dist/plugins/auth/adapters/basic-adapter.js.map +1 -0
- package/dist/plugins/auth/adapters/index.d.ts +9 -0
- package/dist/plugins/auth/adapters/index.d.ts.map +1 -0
- package/dist/plugins/auth/adapters/index.js +9 -0
- package/dist/plugins/auth/adapters/index.js.map +1 -0
- package/dist/plugins/auth/adapters/supabase-adapter.d.ts +13 -0
- package/dist/plugins/auth/adapters/supabase-adapter.d.ts.map +1 -0
- package/dist/plugins/auth/adapters/supabase-adapter.js +109 -0
- package/dist/plugins/auth/adapters/supabase-adapter.js.map +1 -0
- package/dist/plugins/auth/auth-plugin.d.ts +40 -0
- package/dist/plugins/auth/auth-plugin.d.ts.map +1 -0
- package/dist/plugins/auth/auth-plugin.js +255 -0
- package/dist/plugins/auth/auth-plugin.js.map +1 -0
- package/dist/plugins/auth/auth-plugin.test.d.ts +9 -0
- package/dist/plugins/auth/auth-plugin.test.d.ts.map +1 -0
- package/dist/plugins/auth/auth-plugin.test.js +147 -0
- package/dist/plugins/auth/auth-plugin.test.js.map +1 -0
- package/dist/plugins/auth/index.d.ts +12 -0
- package/dist/plugins/auth/index.d.ts.map +1 -0
- package/dist/plugins/auth/index.js +13 -0
- package/dist/plugins/auth/index.js.map +1 -0
- package/dist/plugins/auth/types.d.ts +148 -0
- package/dist/plugins/auth/types.d.ts.map +1 -0
- package/dist/plugins/auth/types.js +14 -0
- package/dist/plugins/auth/types.js.map +1 -0
- package/dist/plugins/bans/bans-plugin.d.ts +59 -0
- package/dist/plugins/bans/bans-plugin.d.ts.map +1 -0
- package/dist/plugins/bans/bans-plugin.js +428 -0
- package/dist/plugins/bans/bans-plugin.js.map +1 -0
- package/dist/plugins/bans/index.d.ts +9 -0
- package/dist/plugins/bans/index.d.ts.map +1 -0
- package/dist/plugins/bans/index.js +10 -0
- package/dist/plugins/bans/index.js.map +1 -0
- package/dist/plugins/bans/stores/index.d.ts +7 -0
- package/dist/plugins/bans/stores/index.d.ts.map +1 -0
- package/dist/plugins/bans/stores/index.js +7 -0
- package/dist/plugins/bans/stores/index.js.map +1 -0
- package/dist/plugins/bans/stores/postgres-store.d.ts +29 -0
- package/dist/plugins/bans/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/bans/stores/postgres-store.js +132 -0
- package/dist/plugins/bans/stores/postgres-store.js.map +1 -0
- package/dist/plugins/bans/types.d.ts +128 -0
- package/dist/plugins/bans/types.d.ts.map +1 -0
- package/dist/plugins/bans/types.js +11 -0
- package/dist/plugins/bans/types.js.map +1 -0
- package/dist/plugins/cache-plugin.d.ts +14 -3
- package/dist/plugins/cache-plugin.d.ts.map +1 -1
- package/dist/plugins/cache-plugin.js +27 -7
- package/dist/plugins/cache-plugin.js.map +1 -1
- package/dist/plugins/cache-plugin.test.js +96 -32
- package/dist/plugins/cache-plugin.test.js.map +1 -1
- package/dist/plugins/config-plugin.d.ts +3 -2
- package/dist/plugins/config-plugin.d.ts.map +1 -1
- package/dist/plugins/config-plugin.js +17 -10
- package/dist/plugins/config-plugin.js.map +1 -1
- package/dist/plugins/diagnostics-plugin.d.ts +2 -2
- package/dist/plugins/diagnostics-plugin.d.ts.map +1 -1
- package/dist/plugins/diagnostics-plugin.js +17 -10
- package/dist/plugins/diagnostics-plugin.js.map +1 -1
- package/dist/plugins/entitlements/entitlements-plugin.d.ts +95 -0
- package/dist/plugins/entitlements/entitlements-plugin.d.ts.map +1 -0
- package/dist/plugins/entitlements/entitlements-plugin.js +707 -0
- package/dist/plugins/entitlements/entitlements-plugin.js.map +1 -0
- package/dist/plugins/entitlements/index.d.ts +12 -0
- package/dist/plugins/entitlements/index.d.ts.map +1 -0
- package/dist/plugins/entitlements/index.js +16 -0
- package/dist/plugins/entitlements/index.js.map +1 -0
- package/dist/plugins/entitlements/sources/index.d.ts +9 -0
- package/dist/plugins/entitlements/sources/index.d.ts.map +1 -0
- package/dist/plugins/entitlements/sources/index.js +9 -0
- package/dist/plugins/entitlements/sources/index.js.map +1 -0
- package/dist/plugins/entitlements/sources/postgres-source.d.ts +29 -0
- package/dist/plugins/entitlements/sources/postgres-source.d.ts.map +1 -0
- package/dist/plugins/entitlements/sources/postgres-source.js +169 -0
- package/dist/plugins/entitlements/sources/postgres-source.js.map +1 -0
- package/dist/plugins/entitlements/types.d.ts +232 -0
- package/dist/plugins/entitlements/types.d.ts.map +1 -0
- package/dist/plugins/entitlements/types.js +11 -0
- package/dist/plugins/entitlements/types.js.map +1 -0
- package/dist/plugins/frontend-app-plugin.d.ts +9 -3
- package/dist/plugins/frontend-app-plugin.d.ts.map +1 -1
- package/dist/plugins/frontend-app-plugin.js +14 -9
- package/dist/plugins/frontend-app-plugin.js.map +1 -1
- package/dist/plugins/health-plugin.d.ts +5 -2
- package/dist/plugins/health-plugin.d.ts.map +1 -1
- package/dist/plugins/health-plugin.js +20 -5
- package/dist/plugins/health-plugin.js.map +1 -1
- package/dist/plugins/index.d.ts +8 -2
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +8 -2
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/logs-plugin.d.ts +3 -2
- package/dist/plugins/logs-plugin.d.ts.map +1 -1
- package/dist/plugins/logs-plugin.js +21 -12
- package/dist/plugins/logs-plugin.js.map +1 -1
- package/dist/plugins/postgres-plugin.d.ts +3 -3
- package/dist/plugins/postgres-plugin.d.ts.map +1 -1
- package/dist/plugins/postgres-plugin.js +9 -7
- package/dist/plugins/postgres-plugin.js.map +1 -1
- package/dist/plugins/postgres-plugin.test.js +47 -29
- package/dist/plugins/postgres-plugin.test.js.map +1 -1
- package/dist/plugins/users/index.d.ts +12 -0
- package/dist/plugins/users/index.d.ts.map +1 -0
- package/dist/plugins/users/index.js +13 -0
- package/dist/plugins/users/index.js.map +1 -0
- package/dist/plugins/users/stores/index.d.ts +7 -0
- package/dist/plugins/users/stores/index.d.ts.map +1 -0
- package/dist/plugins/users/stores/index.js +7 -0
- package/dist/plugins/users/stores/index.js.map +1 -0
- package/dist/plugins/users/stores/postgres-store.d.ts +28 -0
- package/dist/plugins/users/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/users/stores/postgres-store.js +157 -0
- package/dist/plugins/users/stores/postgres-store.js.map +1 -0
- package/dist/plugins/users/types.d.ts +189 -0
- package/dist/plugins/users/types.d.ts.map +1 -0
- package/dist/plugins/users/types.js +12 -0
- package/dist/plugins/users/types.js.map +1 -0
- package/dist/plugins/users/users-plugin.d.ts +39 -0
- package/dist/plugins/users/users-plugin.d.ts.map +1 -0
- package/dist/plugins/users/users-plugin.js +242 -0
- package/dist/plugins/users/users-plugin.js.map +1 -0
- package/dist-ui/assets/index-Bsp2ntcw.js +465 -0
- package/dist-ui/assets/index-Bsp2ntcw.js.map +1 -0
- package/dist-ui/index.html +1 -1
- package/dist-ui-lib/api/controlPanelApi.d.ts +232 -0
- package/dist-ui-lib/components/ControlPanelApp.d.ts +61 -0
- package/dist-ui-lib/components/index.d.ts +18 -0
- package/dist-ui-lib/config/AppConfig.d.ts +7 -0
- package/dist-ui-lib/dashboard/DashboardWidgetRegistry.d.ts +62 -0
- package/dist-ui-lib/dashboard/DashboardWidgetRenderer.d.ts +8 -0
- package/dist-ui-lib/dashboard/PluginWidgetRenderer.d.ts +19 -0
- package/dist-ui-lib/dashboard/WidgetComponentRegistry.d.ts +44 -0
- package/dist-ui-lib/dashboard/builtInWidgets.d.ts +19 -0
- package/dist-ui-lib/dashboard/index.d.ts +13 -0
- package/dist-ui-lib/dashboard/widgets/ServiceHealthWidget.d.ts +12 -0
- package/dist-ui-lib/dashboard/widgets/index.d.ts +6 -0
- package/dist-ui-lib/index.js +6441 -0
- package/dist-ui-lib/index.js.map +1 -0
- package/dist-ui-lib/pages/ConfigPage.d.ts +1 -0
- package/dist-ui-lib/pages/DashboardPage.d.ts +1 -0
- package/dist-ui-lib/pages/DiagnosticsPage.d.ts +1 -0
- package/dist-ui-lib/pages/EntitlementsPage.d.ts +17 -0
- package/dist-ui-lib/pages/LogsPage.d.ts +1 -0
- package/dist-ui-lib/pages/NotFoundPage.d.ts +1 -0
- package/dist-ui-lib/pages/PluginPage.d.ts +15 -0
- package/dist-ui-lib/pages/SystemPage.d.ts +1 -0
- package/dist-ui-lib/pages/UsersPage.d.ts +22 -0
- package/package.json +18 -6
- package/src/core/control-panel.ts +122 -68
- package/src/core/gateway.ts +870 -399
- package/src/core/index.ts +21 -2
- package/src/core/plugin-registry.ts +653 -0
- package/src/core/types.ts +31 -37
- package/src/index.ts +118 -19
- package/src/plugins/auth/adapters/auth0-adapter.ts +214 -0
- package/src/plugins/auth/adapters/basic-adapter.ts +61 -0
- package/src/plugins/auth/adapters/index.ts +9 -0
- package/src/plugins/auth/adapters/supabase-adapter.ts +141 -0
- package/src/plugins/auth/auth-plugin.test.ts +176 -0
- package/src/plugins/auth/auth-plugin.ts +303 -0
- package/src/plugins/auth/index.ts +33 -0
- package/src/plugins/auth/types.ts +165 -0
- package/src/plugins/bans/bans-plugin.ts +485 -0
- package/src/plugins/bans/index.ts +31 -0
- package/src/plugins/bans/stores/index.ts +7 -0
- package/src/plugins/bans/stores/postgres-store.ts +195 -0
- package/src/plugins/bans/types.ts +141 -0
- package/src/plugins/cache-plugin.test.ts +105 -32
- package/src/plugins/cache-plugin.ts +40 -9
- package/src/plugins/config-plugin.ts +23 -12
- package/src/plugins/diagnostics-plugin.ts +22 -12
- package/src/plugins/entitlements/entitlements-plugin.ts +820 -0
- package/src/plugins/entitlements/index.ts +51 -0
- package/src/plugins/entitlements/sources/index.ts +9 -0
- package/src/plugins/entitlements/sources/postgres-source.ts +253 -0
- package/src/plugins/entitlements/types.ts +256 -0
- package/src/plugins/frontend-app-plugin.ts +24 -12
- package/src/plugins/health-plugin.ts +27 -7
- package/src/plugins/index.ts +106 -4
- package/src/plugins/logs-plugin.ts +28 -14
- package/src/plugins/postgres-plugin.test.ts +49 -29
- package/src/plugins/postgres-plugin.ts +11 -9
- package/src/plugins/users/index.ts +35 -0
- package/src/plugins/users/stores/index.ts +7 -0
- package/src/plugins/users/stores/postgres-store.ts +225 -0
- package/src/plugins/users/types.ts +209 -0
- package/src/plugins/users/users-plugin.ts +281 -0
- package/ui/src/App.tsx +185 -31
- package/ui/src/api/controlPanelApi.ts +354 -1
- package/ui/src/components/ControlPanelApp.tsx +209 -0
- package/ui/src/components/index.ts +62 -0
- package/ui/src/dashboard/DashboardWidgetRegistry.tsx +129 -0
- package/ui/src/dashboard/DashboardWidgetRenderer.tsx +34 -0
- package/ui/src/dashboard/PluginWidgetRenderer.tsx +115 -0
- package/ui/src/dashboard/WidgetComponentRegistry.tsx +116 -0
- package/ui/src/dashboard/builtInWidgets.tsx +29 -0
- package/ui/src/dashboard/index.ts +35 -0
- package/ui/src/dashboard/widgets/ServiceHealthWidget.tsx +140 -0
- package/ui/src/dashboard/widgets/index.ts +7 -0
- package/ui/src/pages/DashboardPage.tsx +28 -149
- package/ui/src/pages/EntitlementsPage.tsx +557 -0
- package/ui/src/pages/LogsPage.tsx +174 -8
- package/ui/src/pages/PluginPage.tsx +148 -0
- package/ui/src/pages/SystemPage.tsx +445 -0
- package/ui/src/pages/UsersPage.tsx +837 -0
- package/ui/tsconfig.lib.json +11 -0
- package/ui/vite.lib.config.ts +51 -0
- package/dist-ui/assets/index-CW1BviRn.js +0 -465
- package/dist-ui/assets/index-CW1BviRn.js.map +0 -1
- package/ui/src/pages/HealthPage.tsx +0 -204
package/README.md
CHANGED
|
@@ -61,6 +61,8 @@ For production deployments, use `createGateway` to run a gateway that:
|
|
|
61
61
|
1. Serves the control panel UI (always responsive, even if the API crashes)
|
|
62
62
|
2. Proxies API requests to an internal service
|
|
63
63
|
3. Handles graceful error responses when the internal service is down
|
|
64
|
+
4. Supports maintenance mode with customizable status pages
|
|
65
|
+
5. Shows service unavailable pages when mounted apps are unreachable
|
|
64
66
|
|
|
65
67
|
```typescript
|
|
66
68
|
import { createGateway, createHealthPlugin } from '@qwickapps/server';
|
|
@@ -115,6 +117,68 @@ Internet → Gateway (3101, public) → API Service (3100, internal)
|
|
|
115
117
|
|
|
116
118
|
The gateway is always responsive even if the internal API service crashes, allowing you to view diagnostics and error information.
|
|
117
119
|
|
|
120
|
+
### Mounted Apps with Maintenance Mode
|
|
121
|
+
|
|
122
|
+
Mount frontend apps or proxy services with full maintenance and fallback support:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const gateway = createGateway({
|
|
126
|
+
// ... base config
|
|
127
|
+
mountedApps: [
|
|
128
|
+
{
|
|
129
|
+
path: '/app',
|
|
130
|
+
name: 'Main App',
|
|
131
|
+
type: 'proxy',
|
|
132
|
+
target: 'http://localhost:4000',
|
|
133
|
+
maintenance: {
|
|
134
|
+
enabled: false, // Toggle to enable maintenance mode
|
|
135
|
+
title: 'Scheduled Maintenance',
|
|
136
|
+
message: 'We are upgrading our systems.',
|
|
137
|
+
expectedBackAt: '2 hours', // or ISO date, or "soon"
|
|
138
|
+
contactUrl: 'https://status.example.com',
|
|
139
|
+
bypassPaths: ['/app/health', '/app/api/status'],
|
|
140
|
+
},
|
|
141
|
+
fallback: {
|
|
142
|
+
title: 'Service Unavailable',
|
|
143
|
+
message: 'The application is temporarily unavailable.',
|
|
144
|
+
showRetry: true,
|
|
145
|
+
autoRefresh: 30, // seconds
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
path: '/docs',
|
|
150
|
+
name: 'Documentation',
|
|
151
|
+
type: 'static',
|
|
152
|
+
staticPath: './docs-dist',
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Maintenance and Fallback Configuration
|
|
159
|
+
|
|
160
|
+
**MaintenanceConfig** - Shown when maintenance mode is enabled:
|
|
161
|
+
|
|
162
|
+
| Property | Type | Description |
|
|
163
|
+
|----------|------|-------------|
|
|
164
|
+
| `enabled` | `boolean` | Enable/disable maintenance mode |
|
|
165
|
+
| `title` | `string` | Page title (default: "Under Maintenance") |
|
|
166
|
+
| `message` | `string` | Custom message to display |
|
|
167
|
+
| `expectedBackAt` | `string` | ETA: ISO date, relative time ("2 hours"), or "soon" |
|
|
168
|
+
| `contactUrl` | `string` | Link to status page or contact |
|
|
169
|
+
| `bypassPaths` | `string[]` | Paths that bypass maintenance (e.g., health checks) |
|
|
170
|
+
|
|
171
|
+
**FallbackConfig** - Shown when the proxied service is unreachable:
|
|
172
|
+
|
|
173
|
+
| Property | Type | Description |
|
|
174
|
+
|----------|------|-------------|
|
|
175
|
+
| `title` | `string` | Page title (default: "Service Unavailable") |
|
|
176
|
+
| `message` | `string` | Custom message to display |
|
|
177
|
+
| `showRetry` | `boolean` | Show retry button (default: true) |
|
|
178
|
+
| `autoRefresh` | `number` | Auto-refresh countdown in seconds (default: 30) |
|
|
179
|
+
|
|
180
|
+
Both pages feature modern, responsive designs with automatic dark mode support.
|
|
181
|
+
|
|
118
182
|
## Configuration
|
|
119
183
|
|
|
120
184
|
### ControlPanelConfig
|
|
@@ -130,6 +194,7 @@ The gateway is always responsive even if the internal API service crashes, allow
|
|
|
130
194
|
| `cors` | `object` | No | CORS origins configuration |
|
|
131
195
|
| `links` | `array` | No | Quick links for the dashboard |
|
|
132
196
|
| `skipBodyParserPaths` | `string[]` | No | Paths to skip body parsing (for proxy) |
|
|
197
|
+
| `logoUrl` | `string` | No | Custom logo URL for the landing page |
|
|
133
198
|
|
|
134
199
|
### Route Guards
|
|
135
200
|
|
|
@@ -297,6 +362,259 @@ createLogsPlugin({
|
|
|
297
362
|
});
|
|
298
363
|
```
|
|
299
364
|
|
|
365
|
+
#### PostgreSQL Plugin
|
|
366
|
+
|
|
367
|
+
Provides connection pooling, transactions, and health checks for PostgreSQL databases.
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import { createPostgresPlugin, getPostgres } from '@qwickapps/server';
|
|
371
|
+
|
|
372
|
+
// Register the plugin
|
|
373
|
+
createPostgresPlugin({
|
|
374
|
+
connectionString: process.env.DATABASE_URL,
|
|
375
|
+
// Or individual options:
|
|
376
|
+
// host: 'localhost',
|
|
377
|
+
// port: 5432,
|
|
378
|
+
// database: 'mydb',
|
|
379
|
+
// user: 'postgres',
|
|
380
|
+
// password: 'secret',
|
|
381
|
+
maxConnections: 20,
|
|
382
|
+
healthCheckInterval: 30000,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// Use in your code
|
|
386
|
+
const pg = getPostgres();
|
|
387
|
+
const result = await pg.query('SELECT * FROM users WHERE id = $1', [userId]);
|
|
388
|
+
|
|
389
|
+
// With transactions
|
|
390
|
+
await pg.withTransaction(async (client) => {
|
|
391
|
+
await client.query('INSERT INTO orders ...');
|
|
392
|
+
await client.query('UPDATE inventory ...');
|
|
393
|
+
});
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
**Exports:**
|
|
397
|
+
- `createPostgresPlugin(config)` - Create and register the plugin
|
|
398
|
+
- `getPostgres(name?)` - Get a PostgreSQL instance (throws if not registered)
|
|
399
|
+
- `hasPostgres(name?)` - Check if an instance is registered
|
|
400
|
+
- `PostgresInstance` - TypeScript type for the instance
|
|
401
|
+
|
|
402
|
+
#### Cache Plugin
|
|
403
|
+
|
|
404
|
+
Redis-based caching with key prefixing, TTL support, and pattern operations.
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
import { createCachePlugin, getCache } from '@qwickapps/server';
|
|
408
|
+
|
|
409
|
+
// Register the plugin
|
|
410
|
+
createCachePlugin({
|
|
411
|
+
url: process.env.REDIS_URL,
|
|
412
|
+
// Or individual options:
|
|
413
|
+
// host: 'localhost',
|
|
414
|
+
// port: 6379,
|
|
415
|
+
// password: 'secret',
|
|
416
|
+
keyPrefix: 'myapp:',
|
|
417
|
+
defaultTTL: 3600, // 1 hour default
|
|
418
|
+
healthCheckInterval: 30000,
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Use in your code
|
|
422
|
+
const cache = getCache();
|
|
423
|
+
|
|
424
|
+
// Basic operations
|
|
425
|
+
await cache.set('user:123', userData, 600); // TTL in seconds
|
|
426
|
+
const user = await cache.get('user:123');
|
|
427
|
+
await cache.delete('user:123');
|
|
428
|
+
|
|
429
|
+
// Pattern operations
|
|
430
|
+
const keys = await cache.keys('user:*');
|
|
431
|
+
await cache.deletePattern('session:*');
|
|
432
|
+
|
|
433
|
+
// Stats and maintenance
|
|
434
|
+
const stats = await cache.getStats();
|
|
435
|
+
await cache.flush(); // Clear all keys with prefix
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
**Exports:**
|
|
439
|
+
- `createCachePlugin(config)` - Create and register the plugin
|
|
440
|
+
- `getCache(name?)` - Get a cache instance (throws if not registered)
|
|
441
|
+
- `hasCache(name?)` - Check if an instance is registered
|
|
442
|
+
- `CacheInstance` - TypeScript type for the instance
|
|
443
|
+
|
|
444
|
+
#### Auth Plugin
|
|
445
|
+
|
|
446
|
+
Pluggable authentication with support for multiple providers via the adapter pattern.
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
import { createAuthPlugin, auth0Adapter, basicAdapter } from '@qwickapps/server';
|
|
450
|
+
|
|
451
|
+
// Auth0 with RBAC and domain restrictions
|
|
452
|
+
createAuthPlugin({
|
|
453
|
+
adapter: auth0Adapter({
|
|
454
|
+
domain: process.env.AUTH0_DOMAIN!,
|
|
455
|
+
clientId: process.env.AUTH0_CLIENT_ID!,
|
|
456
|
+
clientSecret: process.env.AUTH0_CLIENT_SECRET!,
|
|
457
|
+
baseUrl: 'https://myapp.example.com',
|
|
458
|
+
secret: process.env.SESSION_SECRET!,
|
|
459
|
+
audience: process.env.AUTH0_AUDIENCE, // For API access tokens
|
|
460
|
+
allowedRoles: ['admin', 'support'], // RBAC filtering
|
|
461
|
+
allowedDomains: ['@company.com'], // Domain whitelist
|
|
462
|
+
exposeAccessToken: true, // For downstream API calls
|
|
463
|
+
}),
|
|
464
|
+
excludePaths: ['/health', '/api/public'],
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// Basic auth fallback
|
|
468
|
+
createAuthPlugin({
|
|
469
|
+
adapter: basicAdapter({
|
|
470
|
+
username: 'admin',
|
|
471
|
+
password: process.env.ADMIN_PASSWORD!,
|
|
472
|
+
}),
|
|
473
|
+
});
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
**Available Adapters:**
|
|
477
|
+
- `auth0Adapter` - Auth0 OIDC (requires `express-openid-connect`)
|
|
478
|
+
- `supabaseAdapter` - Supabase JWT validation
|
|
479
|
+
- `basicAdapter` - HTTP Basic authentication
|
|
480
|
+
|
|
481
|
+
**Helper Functions:**
|
|
482
|
+
```typescript
|
|
483
|
+
import { isAuthenticated, getAuthenticatedUser, getAccessToken } from '@qwickapps/server';
|
|
484
|
+
|
|
485
|
+
// In your route handlers
|
|
486
|
+
if (isAuthenticated(req)) {
|
|
487
|
+
const user = getAuthenticatedUser(req);
|
|
488
|
+
// { id, email, name, picture, emailVerified, roles }
|
|
489
|
+
|
|
490
|
+
const accessToken = getAccessToken(req);
|
|
491
|
+
// Use for downstream API calls
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
**Middleware Helpers:**
|
|
496
|
+
```typescript
|
|
497
|
+
import { requireAuth, requireRoles, requireAnyRole } from '@qwickapps/server';
|
|
498
|
+
|
|
499
|
+
// Require authentication
|
|
500
|
+
app.get('/admin', requireAuth(), (req, res) => { ... });
|
|
501
|
+
|
|
502
|
+
// Require specific roles (all required)
|
|
503
|
+
app.get('/admin/users', requireRoles('admin', 'user-manager'), (req, res) => { ... });
|
|
504
|
+
|
|
505
|
+
// Require any of the roles
|
|
506
|
+
app.get('/dashboard', requireAnyRole('admin', 'editor', 'viewer'), (req, res) => { ... });
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
#### Users Plugin
|
|
510
|
+
|
|
511
|
+
Storage-agnostic user management with ban support.
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
import { createUsersPlugin, postgresUserStore, getPostgres } from '@qwickapps/server';
|
|
515
|
+
import { Pool } from 'pg';
|
|
516
|
+
|
|
517
|
+
// Create with PostgreSQL storage
|
|
518
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
519
|
+
|
|
520
|
+
createUsersPlugin({
|
|
521
|
+
store: postgresUserStore({
|
|
522
|
+
pool,
|
|
523
|
+
usersTable: 'users',
|
|
524
|
+
bansTable: 'user_bans',
|
|
525
|
+
autoCreateTables: true,
|
|
526
|
+
}),
|
|
527
|
+
bans: {
|
|
528
|
+
enabled: true,
|
|
529
|
+
supportTemporary: true, // Enable expiring bans
|
|
530
|
+
onBan: async (user, ban) => {
|
|
531
|
+
// Notify external systems, revoke sessions, etc.
|
|
532
|
+
console.log(`User ${user.email} banned: ${ban.reason}`);
|
|
533
|
+
},
|
|
534
|
+
onUnban: async (user) => {
|
|
535
|
+
console.log(`User ${user.email} unbanned`);
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
api: {
|
|
539
|
+
prefix: '/api/users',
|
|
540
|
+
crud: true, // GET/POST/PUT/DELETE /api/users
|
|
541
|
+
search: true, // GET /api/users?q=...
|
|
542
|
+
bans: true, // Ban management endpoints
|
|
543
|
+
},
|
|
544
|
+
});
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
**REST API Endpoints:**
|
|
548
|
+
| Endpoint | Method | Description |
|
|
549
|
+
|----------|--------|-------------|
|
|
550
|
+
| `/api/users` | GET | List/search users |
|
|
551
|
+
| `/api/users` | POST | Create user |
|
|
552
|
+
| `/api/users/:id` | GET | Get user by ID |
|
|
553
|
+
| `/api/users/:id` | PUT | Update user |
|
|
554
|
+
| `/api/users/:id` | DELETE | Delete user |
|
|
555
|
+
| `/api/users/bans` | GET | List active bans |
|
|
556
|
+
| `/api/users/:id/ban` | GET | Get user's ban status |
|
|
557
|
+
| `/api/users/:id/ban` | POST | Ban user |
|
|
558
|
+
| `/api/users/:id/ban` | DELETE | Unban user |
|
|
559
|
+
| `/api/users/:id/bans` | GET | Get user's ban history |
|
|
560
|
+
|
|
561
|
+
**Helper Functions:**
|
|
562
|
+
```typescript
|
|
563
|
+
import { getUserById, getUserByEmail, isUserBanned, findOrCreateUser } from '@qwickapps/server';
|
|
564
|
+
|
|
565
|
+
// Get user
|
|
566
|
+
const user = await getUserById('user-123');
|
|
567
|
+
const userByEmail = await getUserByEmail('test@example.com');
|
|
568
|
+
|
|
569
|
+
// Check ban status
|
|
570
|
+
const banned = await isUserBanned('user-123');
|
|
571
|
+
|
|
572
|
+
// Find or create from auth provider
|
|
573
|
+
const user = await findOrCreateUser({
|
|
574
|
+
email: 'user@example.com',
|
|
575
|
+
name: 'Test User',
|
|
576
|
+
external_id: 'auth0|12345',
|
|
577
|
+
provider: 'auth0',
|
|
578
|
+
});
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
**Email Ban Support (for auth-only scenarios):**
|
|
582
|
+
|
|
583
|
+
For cases where you don't store users locally but need to ban by email:
|
|
584
|
+
|
|
585
|
+
```typescript
|
|
586
|
+
import { isEmailBanned, getEmailBan, banEmail, unbanEmail } from '@qwickapps/server';
|
|
587
|
+
|
|
588
|
+
// Check if email is banned
|
|
589
|
+
const banned = await isEmailBanned('user@example.com');
|
|
590
|
+
|
|
591
|
+
// Get ban details
|
|
592
|
+
const ban = await getEmailBan('user@example.com');
|
|
593
|
+
|
|
594
|
+
// Ban an email
|
|
595
|
+
await banEmail({
|
|
596
|
+
email: 'user@example.com',
|
|
597
|
+
reason: 'Spam activity',
|
|
598
|
+
banned_by: 'admin@company.com',
|
|
599
|
+
duration: 86400, // 24 hours (optional, null = permanent)
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
// Unban an email
|
|
603
|
+
await unbanEmail({
|
|
604
|
+
email: 'user@example.com',
|
|
605
|
+
unbanned_by: 'admin@company.com',
|
|
606
|
+
note: 'Cleared after review',
|
|
607
|
+
});
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
**Email Ban API Endpoints:**
|
|
611
|
+
| Endpoint | Method | Description |
|
|
612
|
+
|----------|--------|-------------|
|
|
613
|
+
| `/api/users/email-bans` | GET | List active email bans |
|
|
614
|
+
| `/api/users/email-bans/:email` | GET | Check email ban status |
|
|
615
|
+
| `/api/users/email-bans` | POST | Ban an email |
|
|
616
|
+
| `/api/users/email-bans/:email` | DELETE | Unban an email |
|
|
617
|
+
|
|
300
618
|
### Creating Custom Plugins
|
|
301
619
|
|
|
302
620
|
```typescript
|
|
@@ -6,10 +6,15 @@
|
|
|
6
6
|
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
7
7
|
*/
|
|
8
8
|
import { type LoggingConfig } from './logging.js';
|
|
9
|
-
import type { ControlPanelConfig,
|
|
9
|
+
import type { ControlPanelConfig, ControlPanelInstance, Logger } from './types.js';
|
|
10
|
+
import { type Plugin, type PluginConfig } from './plugin-registry.js';
|
|
10
11
|
export interface CreateControlPanelOptions {
|
|
11
12
|
config: ControlPanelConfig;
|
|
12
|
-
|
|
13
|
+
/** Plugins to start with the control panel */
|
|
14
|
+
plugins?: Array<{
|
|
15
|
+
plugin: Plugin;
|
|
16
|
+
config?: PluginConfig;
|
|
17
|
+
}>;
|
|
13
18
|
logger?: Logger;
|
|
14
19
|
/** Logging configuration */
|
|
15
20
|
logging?: LoggingConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control-panel.d.ts","sourceRoot":"","sources":["../../src/core/control-panel.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH,OAAO,EAA4C,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAE5F,OAAO,KAAK,EACV,kBAAkB,EAClB,
|
|
1
|
+
{"version":3,"file":"control-panel.d.ts","sourceRoot":"","sources":["../../src/core/control-panel.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH,OAAO,EAA4C,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAE5F,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EAGpB,MAAM,EACP,MAAM,YAAY,CAAC;AACpB,OAAO,EAEL,KAAK,MAAM,EACX,KAAK,YAAY,EAElB,MAAM,sBAAsB,CAAC;AAuB9B,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,8CAA8C;IAC9C,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,YAAY,CAAA;KAAE,CAAC,CAAC;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4BAA4B;IAC5B,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,oBAAoB,CAgT3F"}
|
|
@@ -9,12 +9,13 @@ import express from 'express';
|
|
|
9
9
|
import helmet from 'helmet';
|
|
10
10
|
import cors from 'cors';
|
|
11
11
|
import compression from 'compression';
|
|
12
|
-
import { existsSync } from 'node:fs';
|
|
12
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
13
13
|
import { fileURLToPath } from 'node:url';
|
|
14
14
|
import { dirname, join } from 'node:path';
|
|
15
15
|
import { HealthManager } from './health-manager.js';
|
|
16
16
|
import { initializeLogging, getControlPanelLogger } from './logging.js';
|
|
17
17
|
import { createRouteGuard } from './guards.js';
|
|
18
|
+
import { createPluginRegistry, } from './plugin-registry.js';
|
|
18
19
|
// Get the package root directory for serving UI assets
|
|
19
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
20
21
|
const __dirname = dirname(__filename);
|
|
@@ -23,6 +24,18 @@ const packageRoot = __dirname.includes('/src/')
|
|
|
23
24
|
? join(__dirname, '..', '..')
|
|
24
25
|
: join(__dirname, '..', '..');
|
|
25
26
|
const uiDistPath = join(packageRoot, 'dist-ui');
|
|
27
|
+
// Read @qwickapps/server package version
|
|
28
|
+
let frameworkVersion = '1.0.0';
|
|
29
|
+
try {
|
|
30
|
+
const packageJsonPath = join(packageRoot, 'package.json');
|
|
31
|
+
if (existsSync(packageJsonPath)) {
|
|
32
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
33
|
+
frameworkVersion = packageJson.version || '1.0.0';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Keep default version if reading fails
|
|
38
|
+
}
|
|
26
39
|
/**
|
|
27
40
|
* Create a control panel instance
|
|
28
41
|
*/
|
|
@@ -38,9 +51,10 @@ export function createControlPanel(options) {
|
|
|
38
51
|
const app = express();
|
|
39
52
|
const router = express.Router();
|
|
40
53
|
const healthManager = new HealthManager(logger);
|
|
41
|
-
const registeredPlugins = [];
|
|
42
54
|
let server = null;
|
|
43
55
|
const startTime = Date.now();
|
|
56
|
+
// Initialize the new plugin registry
|
|
57
|
+
const pluginRegistry = createPluginRegistry(app, router, logger, healthManager, getControlPanelLogger);
|
|
44
58
|
// Security middleware
|
|
45
59
|
app.use(helmet({
|
|
46
60
|
contentSecurityPolicy: false, // Allow inline scripts for simple UI
|
|
@@ -65,13 +79,14 @@ export function createControlPanel(options) {
|
|
|
65
79
|
app.use(express.json());
|
|
66
80
|
}
|
|
67
81
|
app.use(compression());
|
|
68
|
-
//
|
|
82
|
+
// Get mount path (defaults to /cpanel)
|
|
83
|
+
const mountPath = config.mountPath || '/cpanel';
|
|
84
|
+
// Apply route guard if configured - only to the control panel mount path
|
|
69
85
|
if (config.guard && config.guard.type !== 'none') {
|
|
70
86
|
const guardMiddleware = createRouteGuard(config.guard);
|
|
71
|
-
|
|
87
|
+
// Only protect the control panel path, not the root or other paths
|
|
88
|
+
app.use(mountPath, guardMiddleware);
|
|
72
89
|
}
|
|
73
|
-
// Get mount path (defaults to /cpanel)
|
|
74
|
-
const mountPath = config.mountPath || '/cpanel';
|
|
75
90
|
const apiBasePath = mountPath === '/' ? '/api' : `${mountPath}/api`;
|
|
76
91
|
// Request logging
|
|
77
92
|
app.use((req, _res, next) => {
|
|
@@ -101,6 +116,8 @@ export function createControlPanel(options) {
|
|
|
101
116
|
router.get('/info', (_req, res) => {
|
|
102
117
|
res.json({
|
|
103
118
|
product: config.productName,
|
|
119
|
+
logoName: config.logoName || config.productName,
|
|
120
|
+
logoIconUrl: config.logoIconUrl,
|
|
104
121
|
version: config.version || 'unknown',
|
|
105
122
|
uptime: Date.now() - startTime,
|
|
106
123
|
links: config.links || [],
|
|
@@ -114,6 +131,28 @@ export function createControlPanel(options) {
|
|
|
114
131
|
const report = getDiagnostics();
|
|
115
132
|
res.json(report);
|
|
116
133
|
});
|
|
134
|
+
/**
|
|
135
|
+
* GET /api/ui-contributions - UI contributions from all plugins
|
|
136
|
+
*
|
|
137
|
+
* Returns menu items, pages, and widgets registered by plugins.
|
|
138
|
+
* Used by the React UI to build dynamic navigation and pages.
|
|
139
|
+
*/
|
|
140
|
+
router.get('/ui-contributions', (_req, res) => {
|
|
141
|
+
res.json({
|
|
142
|
+
menuItems: pluginRegistry.getMenuItems(),
|
|
143
|
+
pages: pluginRegistry.getPages(),
|
|
144
|
+
widgets: pluginRegistry.getWidgets(),
|
|
145
|
+
plugins: pluginRegistry.listPlugins(),
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
/**
|
|
149
|
+
* GET /api/plugins - List all registered plugins
|
|
150
|
+
*/
|
|
151
|
+
router.get('/plugins', (_req, res) => {
|
|
152
|
+
res.json({
|
|
153
|
+
plugins: pluginRegistry.listPlugins(),
|
|
154
|
+
});
|
|
155
|
+
});
|
|
117
156
|
/**
|
|
118
157
|
* Serve dashboard UI at the configured mount path
|
|
119
158
|
*
|
|
@@ -129,20 +168,53 @@ export function createControlPanel(options) {
|
|
|
129
168
|
logger.debug(`Dashboard config: mountPath=${mountPath}, effectiveUiPath=${effectiveUiPath}, hasRichUI=${hasRichUI}, useRichUI=${useRichUI}`);
|
|
130
169
|
if (useRichUI) {
|
|
131
170
|
logger.debug(`Serving React UI from ${effectiveUiPath}`);
|
|
171
|
+
// Read index.html template
|
|
172
|
+
const indexHtmlPath = join(effectiveUiPath, 'index.html');
|
|
173
|
+
const indexHtmlTemplate = readFileSync(indexHtmlPath, 'utf-8');
|
|
174
|
+
/**
|
|
175
|
+
* Get index.html with the base path injected.
|
|
176
|
+
*
|
|
177
|
+
* The server injects the base path as window.__APP_BASE_PATH__ so the React app
|
|
178
|
+
* can read it at runtime without complex detection logic. This is the standard
|
|
179
|
+
* pattern used by frameworks like Next.js (__NEXT_DATA__).
|
|
180
|
+
*
|
|
181
|
+
* When served behind a gateway proxy, use X-Forwarded-Prefix to determine
|
|
182
|
+
* the public path for assets and the React Router basename.
|
|
183
|
+
*/
|
|
184
|
+
const getIndexHtml = (req) => {
|
|
185
|
+
// Determine the effective public path:
|
|
186
|
+
// - If X-Forwarded-Prefix header is set (proxied), use that
|
|
187
|
+
// - Otherwise, use the configured mountPath
|
|
188
|
+
const forwardedPrefix = req.get('X-Forwarded-Prefix');
|
|
189
|
+
const effectivePath = forwardedPrefix || mountPath;
|
|
190
|
+
const normalizedPath = effectivePath === '/' ? '' : effectivePath;
|
|
191
|
+
// Inject base path as global variable before other scripts
|
|
192
|
+
const basePathScript = `<script>window.__APP_BASE_PATH__="${normalizedPath}";</script>`;
|
|
193
|
+
let html = indexHtmlTemplate.replace('<head>', `<head>\n ${basePathScript}`);
|
|
194
|
+
// Rewrite asset paths if mounted at a subpath
|
|
195
|
+
if (normalizedPath) {
|
|
196
|
+
html = html.replace(/src="\/assets\//g, `src="${normalizedPath}/assets/`);
|
|
197
|
+
html = html.replace(/href="\/assets\//g, `href="${normalizedPath}/assets/`);
|
|
198
|
+
}
|
|
199
|
+
return html;
|
|
200
|
+
};
|
|
132
201
|
// Serve static assets from dist-ui at the mount path
|
|
133
|
-
|
|
202
|
+
// Disable index: false to prevent serving index.html automatically
|
|
203
|
+
// We handle index.html separately with rewritten asset paths
|
|
204
|
+
app.use(mountPath, express.static(effectiveUiPath, { index: false }));
|
|
134
205
|
// SPA fallback - serve index.html for all non-API routes under the mount path
|
|
135
|
-
|
|
206
|
+
const spaFallbackPath = mountPath === '/' ? '/*' : `${mountPath}/*`;
|
|
207
|
+
app.get(spaFallbackPath, (req, res, next) => {
|
|
136
208
|
// Skip API routes
|
|
137
209
|
if (req.path.startsWith(apiBasePath)) {
|
|
138
210
|
return next();
|
|
139
211
|
}
|
|
140
|
-
res.
|
|
212
|
+
res.type('html').send(getIndexHtml(req));
|
|
141
213
|
});
|
|
142
214
|
// Also serve the mount path root
|
|
143
215
|
if (mountPath !== '/') {
|
|
144
|
-
app.get(mountPath, (
|
|
145
|
-
res.
|
|
216
|
+
app.get(mountPath, (req, res) => {
|
|
217
|
+
res.type('html').send(getIndexHtml(req));
|
|
146
218
|
});
|
|
147
219
|
}
|
|
148
220
|
}
|
|
@@ -155,43 +227,9 @@ export function createControlPanel(options) {
|
|
|
155
227
|
});
|
|
156
228
|
}
|
|
157
229
|
}
|
|
158
|
-
//
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
app,
|
|
162
|
-
router,
|
|
163
|
-
logger: getControlPanelLogger(pluginName),
|
|
164
|
-
registerHealthCheck: (check) => healthManager.register(check),
|
|
165
|
-
});
|
|
166
|
-
// Register plugin
|
|
167
|
-
const registerPlugin = async (plugin) => {
|
|
168
|
-
logger.debug(`Registering plugin: ${plugin.name}`);
|
|
169
|
-
// Register routes
|
|
170
|
-
if (plugin.routes) {
|
|
171
|
-
for (const route of plugin.routes) {
|
|
172
|
-
switch (route.method) {
|
|
173
|
-
case 'get':
|
|
174
|
-
router.get(route.path, route.handler);
|
|
175
|
-
break;
|
|
176
|
-
case 'post':
|
|
177
|
-
router.post(route.path, route.handler);
|
|
178
|
-
break;
|
|
179
|
-
case 'put':
|
|
180
|
-
router.put(route.path, route.handler);
|
|
181
|
-
break;
|
|
182
|
-
case 'delete':
|
|
183
|
-
router.delete(route.path, route.handler);
|
|
184
|
-
break;
|
|
185
|
-
}
|
|
186
|
-
logger.debug(`Registered route: ${route.method.toUpperCase()} ${route.path}`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
// Initialize plugin with plugin-specific logger
|
|
190
|
-
if (plugin.onInit) {
|
|
191
|
-
await plugin.onInit(createPluginContext(plugin.name));
|
|
192
|
-
}
|
|
193
|
-
registeredPlugins.push(plugin);
|
|
194
|
-
logger.debug(`Plugin registered: ${plugin.name}`);
|
|
230
|
+
// Start a plugin with the registry
|
|
231
|
+
const startPlugin = async (plugin, pluginConfig = {}) => {
|
|
232
|
+
return pluginRegistry.startPlugin(plugin, pluginConfig);
|
|
195
233
|
};
|
|
196
234
|
// Get diagnostics report
|
|
197
235
|
const getDiagnostics = () => {
|
|
@@ -200,6 +238,7 @@ export function createControlPanel(options) {
|
|
|
200
238
|
timestamp: new Date().toISOString(),
|
|
201
239
|
product: config.productName,
|
|
202
240
|
version: config.version,
|
|
241
|
+
frameworkVersion,
|
|
203
242
|
uptime: Date.now() - startTime,
|
|
204
243
|
health: healthManager.getResults(),
|
|
205
244
|
system: {
|
|
@@ -219,32 +258,31 @@ export function createControlPanel(options) {
|
|
|
219
258
|
};
|
|
220
259
|
// Start server
|
|
221
260
|
const start = async () => {
|
|
222
|
-
//
|
|
223
|
-
for (const plugin of plugins) {
|
|
224
|
-
await
|
|
261
|
+
// Start initial plugins via registry
|
|
262
|
+
for (const { plugin, config: pluginConfig } of plugins) {
|
|
263
|
+
const success = await pluginRegistry.startPlugin(plugin, pluginConfig || {});
|
|
264
|
+
if (!success) {
|
|
265
|
+
logger.error(`Failed to start plugin: ${plugin.id}`);
|
|
266
|
+
}
|
|
225
267
|
}
|
|
226
268
|
return new Promise((resolve) => {
|
|
227
269
|
server = app.listen(config.port, () => {
|
|
228
|
-
logger.
|
|
270
|
+
logger.debug(`Control panel listening on port ${config.port}`);
|
|
229
271
|
resolve();
|
|
230
272
|
});
|
|
231
273
|
});
|
|
232
274
|
};
|
|
233
275
|
// Stop server
|
|
234
276
|
const stop = async () => {
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
if (plugin.onShutdown) {
|
|
238
|
-
await plugin.onShutdown();
|
|
239
|
-
}
|
|
240
|
-
}
|
|
277
|
+
// Stop all plugins via registry
|
|
278
|
+
await pluginRegistry.stopAllPlugins();
|
|
241
279
|
// Shutdown health manager
|
|
242
280
|
healthManager.shutdown();
|
|
243
281
|
// Close server
|
|
244
282
|
if (server) {
|
|
245
283
|
return new Promise((resolve) => {
|
|
246
284
|
server.close(() => {
|
|
247
|
-
logger.
|
|
285
|
+
logger.debug('Control panel stopped');
|
|
248
286
|
resolve();
|
|
249
287
|
});
|
|
250
288
|
});
|
|
@@ -254,9 +292,10 @@ export function createControlPanel(options) {
|
|
|
254
292
|
app,
|
|
255
293
|
start,
|
|
256
294
|
stop,
|
|
257
|
-
|
|
295
|
+
startPlugin,
|
|
258
296
|
getHealthStatus: () => healthManager.getResults(),
|
|
259
297
|
getDiagnostics,
|
|
298
|
+
getPluginRegistry: () => pluginRegistry,
|
|
260
299
|
};
|
|
261
300
|
}
|
|
262
301
|
/**
|