@qwickapps/server 1.1.7 → 1.2.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 +80 -0
- package/dist/core/control-panel.d.ts.map +1 -1
- package/dist/core/control-panel.js +7 -6
- package/dist/core/control-panel.js.map +1 -1
- package/dist/core/gateway.d.ts +5 -0
- package/dist/core/gateway.d.ts.map +1 -1
- package/dist/core/gateway.js +413 -31
- package/dist/core/gateway.js.map +1 -1
- package/dist/core/logging.js +1 -1
- package/dist/core/logging.js.map +1 -1
- package/package.json +1 -1
- package/src/core/control-panel.ts +8 -7
- package/src/core/gateway.ts +437 -32
- package/src/core/logging.ts +1 -1
package/src/core/gateway.ts
CHANGED
|
@@ -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,382 @@ 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="/logo.svg" 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
|
+
// Use /health (public, proxied from service) instead of control panel API
|
|
599
|
+
const res = await fetch('/health');
|
|
600
|
+
const data = await res.json();
|
|
601
|
+
|
|
602
|
+
badge.classList.remove('loading');
|
|
603
|
+
|
|
604
|
+
if (data.status === 'healthy') {
|
|
605
|
+
dot.className = 'status-dot';
|
|
606
|
+
text.textContent = 'All systems operational';
|
|
607
|
+
desc.textContent = 'The service is running smoothly and ready to handle requests.';
|
|
608
|
+
} else if (data.status === 'degraded') {
|
|
609
|
+
dot.className = 'status-dot degraded';
|
|
610
|
+
text.textContent = 'Degraded performance';
|
|
611
|
+
desc.textContent = 'Some services may be experiencing issues. Core functionality remains available.';
|
|
612
|
+
} else {
|
|
613
|
+
dot.className = 'status-dot unhealthy';
|
|
614
|
+
text.textContent = 'System maintenance';
|
|
615
|
+
desc.textContent = 'The service is currently undergoing maintenance. Please check back shortly.';
|
|
616
|
+
}
|
|
617
|
+
} catch (e) {
|
|
618
|
+
badge.classList.remove('loading');
|
|
619
|
+
dot.className = 'status-dot unhealthy';
|
|
620
|
+
text.textContent = 'Unable to connect';
|
|
621
|
+
desc.textContent = 'Could not reach the service. Please try again later.';
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Check status on load and every 30 seconds
|
|
626
|
+
checkStatus();
|
|
627
|
+
setInterval(checkStatus, 30000);
|
|
628
|
+
</script>
|
|
629
|
+
</body>
|
|
630
|
+
</html>`;
|
|
631
|
+
}
|
|
632
|
+
|
|
251
633
|
/**
|
|
252
634
|
* Create a gateway that proxies to an internal service
|
|
253
635
|
*
|
|
@@ -305,6 +687,9 @@ export function createGateway(
|
|
|
305
687
|
// API paths to proxy
|
|
306
688
|
const proxyPaths = config.proxyPaths || ['/api/v1'];
|
|
307
689
|
|
|
690
|
+
// Version for display
|
|
691
|
+
const version = config.version || process.env.npm_package_version || '1.0.0';
|
|
692
|
+
|
|
308
693
|
let service: GatewayInstance['service'] = null;
|
|
309
694
|
|
|
310
695
|
// Create control panel
|
|
@@ -312,7 +697,7 @@ export function createGateway(
|
|
|
312
697
|
config: {
|
|
313
698
|
productName: config.productName,
|
|
314
699
|
port: gatewayPort,
|
|
315
|
-
version
|
|
700
|
+
version,
|
|
316
701
|
branding: config.branding,
|
|
317
702
|
cors: config.corsOrigins ? { origins: config.corsOrigins } : undefined,
|
|
318
703
|
// Skip body parsing for proxied paths
|
|
@@ -381,9 +766,35 @@ export function createGateway(
|
|
|
381
766
|
controlPanel.app.use(createProxyMiddleware(healthProxyOptions));
|
|
382
767
|
};
|
|
383
768
|
|
|
769
|
+
// Calculate API base path for landing page
|
|
770
|
+
const apiBasePath = controlPanelPath === '/' ? '/api' : `${controlPanelPath}/api`;
|
|
771
|
+
|
|
384
772
|
// Setup frontend app at root path
|
|
385
773
|
const setupFrontendApp = () => {
|
|
774
|
+
// Serve logo at /logo.svg if logoUrl is configured and customUiPath exists
|
|
775
|
+
if (config.logoUrl && config.customUiPath) {
|
|
776
|
+
const logoPath = resolve(config.customUiPath, 'logo.svg');
|
|
777
|
+
if (existsSync(logoPath)) {
|
|
778
|
+
controlPanel.app.get('/logo.svg', (_req, res) => {
|
|
779
|
+
res.sendFile(logoPath);
|
|
780
|
+
});
|
|
781
|
+
logger.debug('Frontend app: Serving logo at /logo.svg');
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// If no frontend app configured, serve default landing page with status
|
|
386
786
|
if (!config.frontendApp) {
|
|
787
|
+
logger.debug('Frontend app: Serving default landing page');
|
|
788
|
+
controlPanel.app.get('/', (_req, res) => {
|
|
789
|
+
const html = generateDefaultLandingPageHtml(
|
|
790
|
+
config.productName,
|
|
791
|
+
controlPanelPath,
|
|
792
|
+
apiBasePath,
|
|
793
|
+
version,
|
|
794
|
+
config.logoUrl
|
|
795
|
+
);
|
|
796
|
+
res.type('html').send(html);
|
|
797
|
+
});
|
|
387
798
|
return;
|
|
388
799
|
}
|
|
389
800
|
|
|
@@ -391,7 +802,7 @@ export function createGateway(
|
|
|
391
802
|
|
|
392
803
|
// Priority 1: Redirect
|
|
393
804
|
if (redirectUrl) {
|
|
394
|
-
logger.
|
|
805
|
+
logger.debug(`Frontend app: Redirecting / to ${redirectUrl}`);
|
|
395
806
|
controlPanel.app.get('/', (_req, res) => {
|
|
396
807
|
res.redirect(redirectUrl);
|
|
397
808
|
});
|
|
@@ -400,7 +811,7 @@ export function createGateway(
|
|
|
400
811
|
|
|
401
812
|
// Priority 2: Serve static files
|
|
402
813
|
if (staticPath && existsSync(staticPath)) {
|
|
403
|
-
logger.
|
|
814
|
+
logger.debug(`Frontend app: Serving static files from ${staticPath}`);
|
|
404
815
|
controlPanel.app.use('/', express.static(staticPath));
|
|
405
816
|
|
|
406
817
|
// SPA fallback for root
|
|
@@ -412,7 +823,7 @@ export function createGateway(
|
|
|
412
823
|
|
|
413
824
|
// Priority 3: Landing page
|
|
414
825
|
if (landingPage) {
|
|
415
|
-
logger.
|
|
826
|
+
logger.debug(`Frontend app: Serving landing page`);
|
|
416
827
|
controlPanel.app.get('/', (_req, res) => {
|
|
417
828
|
const html = generateLandingPageHtml(landingPage, controlPanelPath);
|
|
418
829
|
res.type('html').send(html);
|
|
@@ -421,12 +832,12 @@ export function createGateway(
|
|
|
421
832
|
};
|
|
422
833
|
|
|
423
834
|
const start = async (): Promise<void> => {
|
|
424
|
-
logger.
|
|
835
|
+
logger.debug('Starting gateway...');
|
|
425
836
|
|
|
426
837
|
// 1. Start internal service
|
|
427
|
-
logger.
|
|
838
|
+
logger.debug(`Starting internal service on port ${servicePort}...`);
|
|
428
839
|
service = await serviceFactory(servicePort);
|
|
429
|
-
logger.
|
|
840
|
+
logger.debug(`Internal service started on port ${servicePort}`);
|
|
430
841
|
|
|
431
842
|
// 2. Setup proxy middleware (after service is started)
|
|
432
843
|
setupProxyMiddleware();
|
|
@@ -437,35 +848,29 @@ export function createGateway(
|
|
|
437
848
|
// 4. Start control panel gateway
|
|
438
849
|
await controlPanel.start();
|
|
439
850
|
|
|
440
|
-
//
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (config.frontendApp) {
|
|
457
|
-
logger.info(`Frontend App: GET /`);
|
|
458
|
-
}
|
|
459
|
-
logger.info(`Control Panel UI: GET ${controlPanelPath.padEnd(20)}`);
|
|
460
|
-
logger.info(`Gateway Health: GET ${apiBasePath}/health`);
|
|
461
|
-
logger.info(`Service Health: GET /health`);
|
|
851
|
+
// Log concise startup info
|
|
852
|
+
const authInfo = guardConfig?.type === 'basic'
|
|
853
|
+
? `(auth: ${guardConfig.username})`
|
|
854
|
+
: guardConfig?.type && guardConfig.type !== 'none'
|
|
855
|
+
? `(auth: ${guardConfig.type})`
|
|
856
|
+
: '(no auth)';
|
|
857
|
+
|
|
858
|
+
logger.info(`${config.productName} started on port ${gatewayPort} ${authInfo}`);
|
|
859
|
+
|
|
860
|
+
// Log detailed route info at debug level
|
|
861
|
+
logger.debug(`Gateway Port: ${gatewayPort} (public)`);
|
|
862
|
+
logger.debug(`Service Port: ${servicePort} (internal)`);
|
|
863
|
+
logger.debug(`Frontend App: GET /`);
|
|
864
|
+
logger.debug(`Control Panel UI: GET ${controlPanelPath}`);
|
|
865
|
+
logger.debug(`Gateway Health: GET ${apiBasePath}/health`);
|
|
866
|
+
logger.debug(`Service Health: GET /health`);
|
|
462
867
|
for (const apiPath of proxyPaths) {
|
|
463
|
-
logger.
|
|
868
|
+
logger.debug(`Service API: * ${apiPath}/*`);
|
|
464
869
|
}
|
|
465
870
|
};
|
|
466
871
|
|
|
467
872
|
const stop = async (): Promise<void> => {
|
|
468
|
-
logger.
|
|
873
|
+
logger.debug('Shutting down gateway...');
|
|
469
874
|
|
|
470
875
|
// Stop control panel
|
|
471
876
|
await controlPanel.stop();
|
|
@@ -476,7 +881,7 @@ export function createGateway(
|
|
|
476
881
|
service.server.close();
|
|
477
882
|
}
|
|
478
883
|
|
|
479
|
-
logger.
|
|
884
|
+
logger.debug('Gateway shutdown complete');
|
|
480
885
|
};
|
|
481
886
|
|
|
482
887
|
return {
|
package/src/core/logging.ts
CHANGED
|
@@ -34,7 +34,7 @@ export interface LoggingConfig {
|
|
|
34
34
|
|
|
35
35
|
// Default configuration
|
|
36
36
|
const DEFAULT_CONFIG: Required<LoggingConfig> = {
|
|
37
|
-
namespace: '
|
|
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',
|