@qwickapps/server 1.1.6 → 1.1.9

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.
Files changed (67) hide show
  1. package/README.md +1 -1
  2. package/dist/core/control-panel.d.ts.map +1 -1
  3. package/dist/core/control-panel.js +5 -8
  4. package/dist/core/control-panel.js.map +1 -1
  5. package/dist/core/gateway.d.ts +5 -0
  6. package/dist/core/gateway.d.ts.map +1 -1
  7. package/dist/core/gateway.js +390 -28
  8. package/dist/core/gateway.js.map +1 -1
  9. package/dist/core/health-manager.d.ts.map +1 -1
  10. package/dist/core/health-manager.js +3 -9
  11. package/dist/core/health-manager.js.map +1 -1
  12. package/dist/core/logging.d.ts.map +1 -1
  13. package/dist/core/logging.js +2 -6
  14. package/dist/core/logging.js.map +1 -1
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +7 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/plugins/cache-plugin.d.ts +219 -0
  20. package/dist/plugins/cache-plugin.d.ts.map +1 -0
  21. package/dist/plugins/cache-plugin.js +326 -0
  22. package/dist/plugins/cache-plugin.js.map +1 -0
  23. package/dist/plugins/cache-plugin.test.d.ts +8 -0
  24. package/dist/plugins/cache-plugin.test.d.ts.map +1 -0
  25. package/dist/plugins/cache-plugin.test.js +188 -0
  26. package/dist/plugins/cache-plugin.test.js.map +1 -0
  27. package/dist/plugins/config-plugin.js +1 -1
  28. package/dist/plugins/config-plugin.js.map +1 -1
  29. package/dist/plugins/diagnostics-plugin.js +1 -1
  30. package/dist/plugins/diagnostics-plugin.js.map +1 -1
  31. package/dist/plugins/health-plugin.js +1 -1
  32. package/dist/plugins/health-plugin.js.map +1 -1
  33. package/dist/plugins/index.d.ts +6 -0
  34. package/dist/plugins/index.d.ts.map +1 -1
  35. package/dist/plugins/index.js +4 -0
  36. package/dist/plugins/index.js.map +1 -1
  37. package/dist/plugins/logs-plugin.d.ts.map +1 -1
  38. package/dist/plugins/logs-plugin.js +1 -3
  39. package/dist/plugins/logs-plugin.js.map +1 -1
  40. package/dist/plugins/postgres-plugin.d.ts +155 -0
  41. package/dist/plugins/postgres-plugin.d.ts.map +1 -0
  42. package/dist/plugins/postgres-plugin.js +244 -0
  43. package/dist/plugins/postgres-plugin.js.map +1 -0
  44. package/dist/plugins/postgres-plugin.test.d.ts +8 -0
  45. package/dist/plugins/postgres-plugin.test.d.ts.map +1 -0
  46. package/dist/plugins/postgres-plugin.test.js +165 -0
  47. package/dist/plugins/postgres-plugin.test.js.map +1 -0
  48. package/dist-ui/assets/{index-Bk7ypbI4.js → index-CW1BviRn.js} +2 -2
  49. package/dist-ui/assets/{index-Bk7ypbI4.js.map → index-CW1BviRn.js.map} +1 -1
  50. package/dist-ui/index.html +1 -1
  51. package/package.json +13 -2
  52. package/src/core/control-panel.ts +5 -8
  53. package/src/core/gateway.ts +412 -30
  54. package/src/core/health-manager.ts +3 -9
  55. package/src/core/logging.ts +2 -6
  56. package/src/index.ts +22 -0
  57. package/src/plugins/cache-plugin.test.ts +241 -0
  58. package/src/plugins/cache-plugin.ts +503 -0
  59. package/src/plugins/config-plugin.ts +1 -1
  60. package/src/plugins/diagnostics-plugin.ts +1 -1
  61. package/src/plugins/health-plugin.ts +1 -1
  62. package/src/plugins/index.ts +10 -0
  63. package/src/plugins/logs-plugin.ts +1 -3
  64. package/src/plugins/postgres-plugin.test.ts +213 -0
  65. package/src/plugins/postgres-plugin.ts +345 -0
  66. package/ui/src/api/controlPanelApi.ts +1 -1
  67. package/ui/src/pages/LogsPage.tsx +6 -10
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Control Panel</title>
7
- <script type="module" crossorigin src="/assets/index-Bk7ypbI4.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-CW1BviRn.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-CiizQQnb.css">
9
9
  </head>
10
10
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qwickapps/server",
3
- "version": "1.1.6",
3
+ "version": "1.1.9",
4
4
  "description": "Plugin-based application server framework for building websites, APIs, admin dashboards, and full-stack products",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -55,7 +55,10 @@
55
55
  "@types/cors": "^2.8.17",
56
56
  "@types/express": "^4.17.21",
57
57
  "@types/node": "^20.10.5",
58
+ "@types/pg": "^8.11.0",
58
59
  "@types/react": "^18.2.0",
60
+ "ioredis": "^5.4.0",
61
+ "pg": "^8.13.0",
59
62
  "@types/react-dom": "^18.2.0",
60
63
  "@vitejs/plugin-react": "^4.3.4",
61
64
  "express-openid-connect": "^2.19.3",
@@ -68,7 +71,9 @@
68
71
  },
69
72
  "peerDependencies": {
70
73
  "@qwickapps/react-framework": ">=1.0.0",
71
- "express-openid-connect": ">=2.0.0"
74
+ "express-openid-connect": ">=2.0.0",
75
+ "ioredis": ">=5.0.0",
76
+ "pg": ">=8.0.0"
72
77
  },
73
78
  "peerDependenciesMeta": {
74
79
  "@qwickapps/react-framework": {
@@ -76,6 +81,12 @@
76
81
  },
77
82
  "express-openid-connect": {
78
83
  "optional": true
84
+ },
85
+ "ioredis": {
86
+ "optional": true
87
+ },
88
+ "pg": {
89
+ "optional": true
79
90
  }
80
91
  },
81
92
  "keywords": [
@@ -169,7 +169,7 @@ export function createControlPanel(options: CreateControlPanelOptions): ControlP
169
169
  logger.debug(`Dashboard config: mountPath=${mountPath}, effectiveUiPath=${effectiveUiPath}, hasRichUI=${hasRichUI}, useRichUI=${useRichUI}`);
170
170
 
171
171
  if (useRichUI) {
172
- logger.info(`Serving rich React UI from ${effectiveUiPath} at ${mountPath}`);
172
+ logger.debug(`Serving React UI from ${effectiveUiPath}`);
173
173
  // Serve static assets from dist-ui at the mount path
174
174
  app.use(mountPath, express.static(effectiveUiPath));
175
175
 
@@ -189,7 +189,7 @@ export function createControlPanel(options: CreateControlPanelOptions): ControlP
189
189
  });
190
190
  }
191
191
  } else {
192
- logger.info(`Serving basic HTML dashboard at ${mountPath}`);
192
+ logger.debug(`Serving basic HTML dashboard`);
193
193
  const dashboardPath = mountPath === '/' ? '/' : mountPath;
194
194
  app.get(dashboardPath, (_req: Request, res: Response) => {
195
195
  const html = generateDashboardHtml(config, healthManager.getResults(), mountPath);
@@ -209,7 +209,7 @@ export function createControlPanel(options: CreateControlPanelOptions): ControlP
209
209
 
210
210
  // Register plugin
211
211
  const registerPlugin = async (plugin: ControlPanelPlugin): Promise<void> => {
212
- logger.info(`Registering plugin: ${plugin.name}`);
212
+ logger.debug(`Registering plugin: ${plugin.name}`);
213
213
 
214
214
  // Register routes
215
215
  if (plugin.routes) {
@@ -238,7 +238,7 @@ export function createControlPanel(options: CreateControlPanelOptions): ControlP
238
238
  }
239
239
 
240
240
  registeredPlugins.push(plugin);
241
- logger.info(`Plugin registered: ${plugin.name}`);
241
+ logger.debug(`Plugin registered: ${plugin.name}`);
242
242
  };
243
243
 
244
244
  // Get diagnostics report
@@ -276,10 +276,7 @@ export function createControlPanel(options: CreateControlPanelOptions): ControlP
276
276
 
277
277
  return new Promise((resolve) => {
278
278
  server = app.listen(config.port, () => {
279
- logger.info(`Control panel started on port ${config.port}`);
280
- logger.info(`Dashboard: http://localhost:${config.port}${mountPath}`);
281
- logger.info(`Health: http://localhost:${config.port}${apiBasePath}/health`);
282
- logger.info(`Diagnostics: http://localhost:${config.port}${apiBasePath}/diagnostics`);
279
+ logger.info(`Control panel listening on port ${config.port}`);
283
280
  resolve();
284
281
  });
285
282
  });
@@ -43,6 +43,12 @@ export interface GatewayConfig {
43
43
  /** Product version */
44
44
  version?: string;
45
45
 
46
+ /**
47
+ * URL to the product logo image (SVG, PNG, etc.).
48
+ * Used on the default landing page when no frontend app is configured.
49
+ */
50
+ logoUrl?: string;
51
+
46
52
  /** Branding configuration */
47
53
  branding?: ControlPanelConfig['branding'];
48
54
 
@@ -248,6 +254,381 @@ function generateLandingPageHtml(
248
254
  </html>`;
249
255
  }
250
256
 
257
+ /**
258
+ * Generate default landing page HTML when no frontend app is configured
259
+ * Shows system status with animated background
260
+ */
261
+ function generateDefaultLandingPageHtml(
262
+ productName: string,
263
+ controlPanelPath: string,
264
+ apiBasePath: string,
265
+ version: string,
266
+ logoUrl?: string
267
+ ): string {
268
+ return `<!DOCTYPE html>
269
+ <html lang="en">
270
+ <head>
271
+ <meta charset="UTF-8">
272
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
273
+ <title>${productName}</title>
274
+ <style>
275
+ * { margin: 0; padding: 0; box-sizing: border-box; }
276
+
277
+ :root {
278
+ --primary: #6366f1;
279
+ --primary-glow: rgba(99, 102, 241, 0.4);
280
+ --success: #22c55e;
281
+ --warning: #f59e0b;
282
+ --error: #ef4444;
283
+ --bg-dark: #0a0a0f;
284
+ --bg-card: rgba(255, 255, 255, 0.03);
285
+ --text-primary: #f1f5f9;
286
+ --text-secondary: #94a3b8;
287
+ }
288
+
289
+ body {
290
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
291
+ background: var(--bg-dark);
292
+ color: var(--text-primary);
293
+ min-height: 100vh;
294
+ overflow: hidden;
295
+ display: flex;
296
+ align-items: center;
297
+ justify-content: center;
298
+ }
299
+
300
+ /* Animated gradient background */
301
+ .bg-gradient {
302
+ position: fixed;
303
+ top: 0;
304
+ left: 0;
305
+ right: 0;
306
+ bottom: 0;
307
+ background:
308
+ radial-gradient(ellipse at 20% 20%, rgba(99, 102, 241, 0.15) 0%, transparent 50%),
309
+ radial-gradient(ellipse at 80% 80%, rgba(139, 92, 246, 0.1) 0%, transparent 50%),
310
+ radial-gradient(ellipse at 50% 50%, rgba(59, 130, 246, 0.05) 0%, transparent 70%);
311
+ animation: gradientShift 15s ease-in-out infinite;
312
+ }
313
+
314
+ @keyframes gradientShift {
315
+ 0%, 100% {
316
+ background-position: 0% 0%, 100% 100%, 50% 50%;
317
+ opacity: 1;
318
+ }
319
+ 50% {
320
+ background-position: 100% 0%, 0% 100%, 50% 50%;
321
+ opacity: 0.8;
322
+ }
323
+ }
324
+
325
+ /* Floating particles */
326
+ .particles {
327
+ position: fixed;
328
+ top: 0;
329
+ left: 0;
330
+ right: 0;
331
+ bottom: 0;
332
+ overflow: hidden;
333
+ pointer-events: none;
334
+ }
335
+
336
+ .particle {
337
+ position: absolute;
338
+ width: 4px;
339
+ height: 4px;
340
+ background: var(--primary);
341
+ border-radius: 50%;
342
+ opacity: 0.3;
343
+ animation: float 20s infinite;
344
+ }
345
+
346
+ .particle:nth-child(1) { left: 10%; animation-delay: 0s; animation-duration: 25s; }
347
+ .particle:nth-child(2) { left: 20%; animation-delay: 2s; animation-duration: 20s; }
348
+ .particle:nth-child(3) { left: 30%; animation-delay: 4s; animation-duration: 28s; }
349
+ .particle:nth-child(4) { left: 40%; animation-delay: 1s; animation-duration: 22s; }
350
+ .particle:nth-child(5) { left: 50%; animation-delay: 3s; animation-duration: 24s; }
351
+ .particle:nth-child(6) { left: 60%; animation-delay: 5s; animation-duration: 26s; }
352
+ .particle:nth-child(7) { left: 70%; animation-delay: 2s; animation-duration: 21s; }
353
+ .particle:nth-child(8) { left: 80%; animation-delay: 4s; animation-duration: 23s; }
354
+ .particle:nth-child(9) { left: 90%; animation-delay: 1s; animation-duration: 27s; }
355
+
356
+ @keyframes float {
357
+ 0% { transform: translateY(100vh) scale(0); opacity: 0; }
358
+ 10% { opacity: 0.3; }
359
+ 90% { opacity: 0.3; }
360
+ 100% { transform: translateY(-100vh) scale(1); opacity: 0; }
361
+ }
362
+
363
+ /* Grid pattern overlay */
364
+ .grid-overlay {
365
+ position: fixed;
366
+ top: 0;
367
+ left: 0;
368
+ right: 0;
369
+ bottom: 0;
370
+ background-image:
371
+ linear-gradient(rgba(99, 102, 241, 0.03) 1px, transparent 1px),
372
+ linear-gradient(90deg, rgba(99, 102, 241, 0.03) 1px, transparent 1px);
373
+ background-size: 60px 60px;
374
+ pointer-events: none;
375
+ }
376
+
377
+ .container {
378
+ position: relative;
379
+ z-index: 10;
380
+ text-align: center;
381
+ max-width: 500px;
382
+ padding: 3rem 2rem;
383
+ }
384
+
385
+ .logo {
386
+ width: 80px;
387
+ height: 80px;
388
+ margin: 0 auto 2rem;
389
+ display: flex;
390
+ align-items: center;
391
+ justify-content: center;
392
+ animation: logoFloat 6s ease-in-out infinite;
393
+ }
394
+
395
+ .logo.default {
396
+ background: linear-gradient(135deg, var(--primary) 0%, #8b5cf6 100%);
397
+ border-radius: 20px;
398
+ box-shadow: 0 20px 40px var(--primary-glow);
399
+ }
400
+
401
+ .logo.custom {
402
+ filter: drop-shadow(0 20px 40px var(--primary-glow));
403
+ }
404
+
405
+ @keyframes logoFloat {
406
+ 0%, 100% { transform: translateY(0); }
407
+ 50% { transform: translateY(-10px); }
408
+ }
409
+
410
+ .logo svg {
411
+ width: 48px;
412
+ height: 48px;
413
+ fill: white;
414
+ }
415
+
416
+ .logo img {
417
+ width: 80px;
418
+ height: 80px;
419
+ object-fit: contain;
420
+ }
421
+
422
+ h1 {
423
+ font-size: 2.5rem;
424
+ font-weight: 700;
425
+ margin-bottom: 0.5rem;
426
+ background: linear-gradient(135deg, var(--text-primary) 0%, var(--primary) 100%);
427
+ -webkit-background-clip: text;
428
+ -webkit-text-fill-color: transparent;
429
+ background-clip: text;
430
+ }
431
+
432
+ .status-badge {
433
+ display: inline-flex;
434
+ align-items: center;
435
+ gap: 0.5rem;
436
+ padding: 0.75rem 1.5rem;
437
+ background: var(--bg-card);
438
+ border: 1px solid rgba(255, 255, 255, 0.1);
439
+ border-radius: 100px;
440
+ margin: 1.5rem 0 2rem;
441
+ backdrop-filter: blur(10px);
442
+ }
443
+
444
+ .status-dot {
445
+ width: 10px;
446
+ height: 10px;
447
+ border-radius: 50%;
448
+ background: var(--success);
449
+ box-shadow: 0 0 10px var(--success);
450
+ animation: pulse 2s ease-in-out infinite;
451
+ }
452
+
453
+ .status-dot.degraded {
454
+ background: var(--warning);
455
+ box-shadow: 0 0 10px var(--warning);
456
+ }
457
+
458
+ .status-dot.unhealthy {
459
+ background: var(--error);
460
+ box-shadow: 0 0 10px var(--error);
461
+ animation: none;
462
+ }
463
+
464
+ @keyframes pulse {
465
+ 0%, 100% { opacity: 1; transform: scale(1); }
466
+ 50% { opacity: 0.7; transform: scale(1.1); }
467
+ }
468
+
469
+ .status-text {
470
+ font-size: 0.95rem;
471
+ font-weight: 500;
472
+ color: var(--text-primary);
473
+ }
474
+
475
+ .description {
476
+ color: var(--text-secondary);
477
+ font-size: 1rem;
478
+ line-height: 1.6;
479
+ margin-bottom: 2rem;
480
+ }
481
+
482
+ .links {
483
+ display: flex;
484
+ flex-wrap: wrap;
485
+ gap: 1rem;
486
+ justify-content: center;
487
+ }
488
+
489
+ .link {
490
+ display: inline-flex;
491
+ align-items: center;
492
+ gap: 0.5rem;
493
+ padding: 0.875rem 1.75rem;
494
+ background: var(--primary);
495
+ color: white;
496
+ text-decoration: none;
497
+ border-radius: 12px;
498
+ font-weight: 500;
499
+ font-size: 0.95rem;
500
+ transition: all 0.3s ease;
501
+ box-shadow: 0 4px 15px var(--primary-glow);
502
+ }
503
+
504
+ .link:hover {
505
+ transform: translateY(-2px);
506
+ box-shadow: 0 8px 25px var(--primary-glow);
507
+ }
508
+
509
+ .footer {
510
+ position: fixed;
511
+ bottom: 1.5rem;
512
+ left: 0;
513
+ right: 0;
514
+ text-align: center;
515
+ color: var(--text-secondary);
516
+ font-size: 0.85rem;
517
+ z-index: 10;
518
+ }
519
+
520
+ .footer a {
521
+ color: var(--primary);
522
+ text-decoration: none;
523
+ font-weight: 500;
524
+ }
525
+
526
+ .footer a:hover {
527
+ text-decoration: underline;
528
+ }
529
+
530
+ /* Loading state */
531
+ .loading .status-dot {
532
+ background: var(--text-secondary);
533
+ box-shadow: none;
534
+ animation: none;
535
+ }
536
+ </style>
537
+ </head>
538
+ <body>
539
+ <div class="bg-gradient"></div>
540
+ <div class="particles">
541
+ <div class="particle"></div>
542
+ <div class="particle"></div>
543
+ <div class="particle"></div>
544
+ <div class="particle"></div>
545
+ <div class="particle"></div>
546
+ <div class="particle"></div>
547
+ <div class="particle"></div>
548
+ <div class="particle"></div>
549
+ <div class="particle"></div>
550
+ </div>
551
+ <div class="grid-overlay"></div>
552
+
553
+ <div class="container">
554
+ ${logoUrl
555
+ ? `<div class="logo custom"><img src="${logoUrl}" alt="${productName} logo" /></div>`
556
+ : `<div class="logo default">
557
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
558
+ <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
559
+ </svg>
560
+ </div>`}
561
+
562
+ <h1>${productName}</h1>
563
+
564
+ <div class="status-badge loading" id="status-badge">
565
+ <div class="status-dot" id="status-dot"></div>
566
+ <span class="status-text" id="status-text">Checking status...</span>
567
+ </div>
568
+
569
+ <p class="description" id="description">
570
+ Enterprise-grade service powered by QwickApps
571
+ </p>
572
+
573
+ <div class="links">
574
+ <a href="${controlPanelPath}" class="link">
575
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
576
+ <rect x="3" y="3" width="7" height="7"></rect>
577
+ <rect x="14" y="3" width="7" height="7"></rect>
578
+ <rect x="14" y="14" width="7" height="7"></rect>
579
+ <rect x="3" y="14" width="7" height="7"></rect>
580
+ </svg>
581
+ Control Panel
582
+ </a>
583
+ </div>
584
+ </div>
585
+
586
+ <div class="footer">
587
+ Powered by <a href="https://qwickapps.com" target="_blank">QwickApps Server</a> - <a href="https://github.com/qwickapps/server" target="_blank">Version ${version}</a>
588
+ </div>
589
+
590
+ <script>
591
+ async function checkStatus() {
592
+ const badge = document.getElementById('status-badge');
593
+ const dot = document.getElementById('status-dot');
594
+ const text = document.getElementById('status-text');
595
+ const desc = document.getElementById('description');
596
+
597
+ try {
598
+ const res = await fetch('${apiBasePath}/health');
599
+ const data = await res.json();
600
+
601
+ badge.classList.remove('loading');
602
+
603
+ if (data.status === 'healthy') {
604
+ dot.className = 'status-dot';
605
+ text.textContent = 'All systems operational';
606
+ desc.textContent = 'The service is running smoothly and ready to handle requests.';
607
+ } else if (data.status === 'degraded') {
608
+ dot.className = 'status-dot degraded';
609
+ text.textContent = 'Degraded performance';
610
+ desc.textContent = 'Some services may be experiencing issues. Core functionality remains available.';
611
+ } else {
612
+ dot.className = 'status-dot unhealthy';
613
+ text.textContent = 'System maintenance';
614
+ desc.textContent = 'The service is currently undergoing maintenance. Please check back shortly.';
615
+ }
616
+ } catch (e) {
617
+ badge.classList.remove('loading');
618
+ dot.className = 'status-dot unhealthy';
619
+ text.textContent = 'Unable to connect';
620
+ desc.textContent = 'Could not reach the service. Please try again later.';
621
+ }
622
+ }
623
+
624
+ // Check status on load and every 30 seconds
625
+ checkStatus();
626
+ setInterval(checkStatus, 30000);
627
+ </script>
628
+ </body>
629
+ </html>`;
630
+ }
631
+
251
632
  /**
252
633
  * Create a gateway that proxies to an internal service
253
634
  *
@@ -305,6 +686,9 @@ export function createGateway(
305
686
  // API paths to proxy
306
687
  const proxyPaths = config.proxyPaths || ['/api/v1'];
307
688
 
689
+ // Version for display
690
+ const version = config.version || process.env.npm_package_version || '1.0.0';
691
+
308
692
  let service: GatewayInstance['service'] = null;
309
693
 
310
694
  // Create control panel
@@ -312,7 +696,7 @@ export function createGateway(
312
696
  config: {
313
697
  productName: config.productName,
314
698
  port: gatewayPort,
315
- version: config.version || process.env.npm_package_version || '1.0.0',
699
+ version,
316
700
  branding: config.branding,
317
701
  cors: config.corsOrigins ? { origins: config.corsOrigins } : undefined,
318
702
  // Skip body parsing for proxied paths
@@ -381,9 +765,24 @@ export function createGateway(
381
765
  controlPanel.app.use(createProxyMiddleware(healthProxyOptions));
382
766
  };
383
767
 
768
+ // Calculate API base path for landing page
769
+ const apiBasePath = controlPanelPath === '/' ? '/api' : `${controlPanelPath}/api`;
770
+
384
771
  // Setup frontend app at root path
385
772
  const setupFrontendApp = () => {
773
+ // If no frontend app configured, serve default landing page with status
386
774
  if (!config.frontendApp) {
775
+ logger.info('Frontend app: Serving default landing page');
776
+ controlPanel.app.get('/', (_req, res) => {
777
+ const html = generateDefaultLandingPageHtml(
778
+ config.productName,
779
+ controlPanelPath,
780
+ apiBasePath,
781
+ version,
782
+ config.logoUrl
783
+ );
784
+ res.type('html').send(html);
785
+ });
387
786
  return;
388
787
  }
389
788
 
@@ -437,43 +836,26 @@ export function createGateway(
437
836
  // 4. Start control panel gateway
438
837
  await controlPanel.start();
439
838
 
440
- // Calculate API base path
441
- const apiBasePath = controlPanelPath === '/' ? '/api' : `${controlPanelPath}/api`;
442
-
443
839
  // Log startup info
444
- logger.info('');
445
- logger.info('========================================');
446
- logger.info(` ${config.productName} Gateway`);
447
- logger.info('========================================');
448
- logger.info('');
449
- logger.info(` Gateway Port: ${gatewayPort} (public)`);
450
- logger.info(` Service Port: ${servicePort} (internal)`);
451
- logger.info('');
452
-
840
+ logger.info(`${config.productName} Gateway`);
841
+ logger.info(`Gateway Port: ${gatewayPort} (public)`);
842
+ logger.info(`Service Port: ${servicePort} (internal)`);
843
+
453
844
  if (guardConfig && guardConfig.type === 'basic') {
454
- logger.info(' Control Panel Auth: HTTP Basic Auth');
455
- logger.info(' ----------------------------------------');
456
- logger.info(` Username: ${guardConfig.username}`);
457
- logger.info(' ----------------------------------------');
845
+ logger.info(`Control Panel Auth: HTTP Basic Auth - Username: ${guardConfig.username}`);
458
846
  } else if (guardConfig && guardConfig.type !== 'none') {
459
- logger.info(` Control Panel Auth: ${guardConfig.type}`);
847
+ logger.info(`Control Panel Auth: ${guardConfig.type}`);
460
848
  } else {
461
- logger.info(' Control Panel Auth: None (not recommended)');
849
+ logger.info('Control Panel Auth: None (not recommended)');
462
850
  }
463
851
 
464
- logger.info('');
465
- logger.info(' Endpoints:');
466
- if (config.frontendApp) {
467
- logger.info(` GET / - Frontend App`);
468
- }
469
- logger.info(` GET ${controlPanelPath.padEnd(20)} - Control Panel UI`);
470
- logger.info(` GET ${apiBasePath}/health - Gateway health`);
471
- logger.info(` GET /health - Service health (proxied)`);
852
+ logger.info(`Frontend App: GET /`);
853
+ logger.info(`Control Panel UI: GET ${controlPanelPath.padEnd(20)}`);
854
+ logger.info(`Gateway Health: GET ${apiBasePath}/health`);
855
+ logger.info(`Service Health: GET /health`);
472
856
  for (const apiPath of proxyPaths) {
473
- logger.info(` * ${apiPath}/* - Service API (proxied)`);
857
+ logger.info(`Service API: * ${apiPath}/*`);
474
858
  }
475
- logger.info('========================================');
476
- logger.info('');
477
859
  };
478
860
 
479
861
  const stop = async (): Promise<void> => {
@@ -40,10 +40,7 @@ export class HealthManager {
40
40
 
41
41
  this.intervals.set(check.name, timer);
42
42
 
43
- this.logger.info(`[HealthManager] Registered health check: ${check.name}`, {
44
- type: check.type,
45
- interval,
46
- });
43
+ this.logger.debug(`Health check registered: ${check.name} (${check.type}, ${interval}ms)`)
47
44
  }
48
45
 
49
46
  /**
@@ -101,10 +98,7 @@ export class HealthManager {
101
98
  lastChecked: new Date(),
102
99
  });
103
100
 
104
- this.logger.warn(`[HealthManager] Health check failed: ${name}`, {
105
- error: message,
106
- latency,
107
- });
101
+ this.logger.warn(`Health check failed: ${name} - ${message}`);
108
102
  }
109
103
  }
110
104
 
@@ -222,6 +216,6 @@ export class HealthManager {
222
216
  clearInterval(timer);
223
217
  }
224
218
  this.intervals.clear();
225
- this.logger.info('[HealthManager] Shutdown complete');
219
+ this.logger.debug('Health manager shutdown complete');
226
220
  }
227
221
  }
@@ -34,7 +34,7 @@ export interface LoggingConfig {
34
34
 
35
35
  // Default configuration
36
36
  const DEFAULT_CONFIG: Required<LoggingConfig> = {
37
- namespace: 'ControlPanel',
37
+ namespace: 'App',
38
38
  level: (process.env.LOG_LEVEL as LogLevel) || (process.env.NODE_ENV === 'production' ? 'info' : 'debug'),
39
39
  logDir: process.env.LOG_DIR || './logs',
40
40
  fileLogging: process.env.LOG_FILE !== 'false',
@@ -135,12 +135,8 @@ class LoggingSubsystem {
135
135
  }
136
136
 
137
137
  this.initialized = true;
138
- this.rootLogger.info('Logging subsystem initialized', {
139
- logDir: this.config.logDir,
138
+ this.rootLogger.debug('Logging initialized', {
140
139
  level: this.config.level,
141
- fileLogging: this.config.fileLogging,
142
- consoleOutput: this.config.consoleOutput,
143
- usingPino: this.rootLogger.isUsingPino(),
144
140
  });
145
141
  }
146
142
 
package/src/index.ts CHANGED
@@ -63,6 +63,18 @@ export {
63
63
  createConfigPlugin,
64
64
  createDiagnosticsPlugin,
65
65
  createFrontendAppPlugin,
66
+ // Database plugins
67
+ createPostgresPlugin,
68
+ getPostgres,
69
+ hasPostgres,
70
+ // Backward compatibility aliases (deprecated)
71
+ createPostgresPlugin as createDatabasePlugin,
72
+ getPostgres as getDatabase,
73
+ hasPostgres as hasDatabase,
74
+ // Cache plugins
75
+ createCachePlugin,
76
+ getCache,
77
+ hasCache,
66
78
  } from './plugins/index.js';
67
79
  export type {
68
80
  HealthPluginConfig,
@@ -70,4 +82,14 @@ export type {
70
82
  ConfigPluginConfig,
71
83
  DiagnosticsPluginConfig,
72
84
  FrontendAppPluginConfig,
85
+ // Database plugin types
86
+ PostgresPluginConfig,
87
+ PostgresInstance,
88
+ TransactionCallback,
89
+ // Backward compatibility aliases (deprecated)
90
+ PostgresPluginConfig as DatabasePluginConfig,
91
+ PostgresInstance as DatabaseInstance,
92
+ // Cache plugin types
93
+ CachePluginConfig,
94
+ CacheInstance,
73
95
  } from './plugins/index.js';