@serve.zone/dcrouter 7.4.0 → 7.4.2
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/dist_serve/bundle.js +72 -72
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.js +49 -103
- package/dist_ts/monitoring/classes.metricsmanager.d.ts +1 -1
- package/dist_ts/monitoring/classes.metricsmanager.js +14 -7
- package/dist_ts/remoteingress/classes.tunnel-manager.js +2 -6
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/ops-view-logs.js +14 -1
- package/dist_ts_web/elements/ops-view-network.d.ts +0 -3
- package/dist_ts_web/elements/ops-view-network.js +13 -19
- package/package.json +2 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +53 -107
- package/ts/monitoring/classes.metricsmanager.ts +18 -8
- package/ts/remoteingress/classes.tunnel-manager.ts +1 -5
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/ops-view-logs.ts +11 -0
- package/ts_web/elements/ops-view-network.ts +13 -18
package/ts/classes.dcrouter.ts
CHANGED
|
@@ -252,9 +252,7 @@ export class DcRouter {
|
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
public async start() {
|
|
255
|
-
|
|
256
|
-
console.log('║ Starting DcRouter Services ║');
|
|
257
|
-
console.log('╚═══════════════════════════════════════════════════════════════════╝');
|
|
255
|
+
logger.log('info', 'Starting DcRouter Services');
|
|
258
256
|
|
|
259
257
|
|
|
260
258
|
this.opsServer = new OpsServer(this);
|
|
@@ -296,7 +294,7 @@ export class DcRouter {
|
|
|
296
294
|
|
|
297
295
|
this.logStartupSummary();
|
|
298
296
|
} catch (error) {
|
|
299
|
-
|
|
297
|
+
logger.log('error', 'Error starting DcRouter', { error: String(error) });
|
|
300
298
|
// Try to clean up any services that may have started
|
|
301
299
|
await this.stop();
|
|
302
300
|
throw error;
|
|
@@ -307,104 +305,60 @@ export class DcRouter {
|
|
|
307
305
|
* Log comprehensive startup summary
|
|
308
306
|
*/
|
|
309
307
|
private logStartupSummary(): void {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
console.log('╚═══════════════════════════════════════════════════════════════════╝\n');
|
|
313
|
-
|
|
308
|
+
logger.log('info', 'DcRouter Started Successfully');
|
|
309
|
+
|
|
314
310
|
// Metrics summary
|
|
315
311
|
if (this.metricsManager) {
|
|
316
|
-
|
|
317
|
-
console.log(' ├─ SmartMetrics: Active');
|
|
318
|
-
console.log(' ├─ SmartProxy Stats: Active');
|
|
319
|
-
console.log(' └─ Real-time tracking: Enabled');
|
|
312
|
+
logger.log('info', 'Metrics Service: SmartMetrics active, SmartProxy stats active, real-time tracking enabled');
|
|
320
313
|
}
|
|
321
|
-
|
|
314
|
+
|
|
322
315
|
// SmartProxy summary
|
|
323
316
|
if (this.smartProxy) {
|
|
324
|
-
console.log('🌐 SmartProxy Service:');
|
|
325
317
|
const routeCount = this.options.smartProxyConfig?.routes?.length || 0;
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
} else {
|
|
332
|
-
console.log(' └─ ACME: disabled');
|
|
333
|
-
}
|
|
318
|
+
const acmeEnabled = this.options.smartProxyConfig?.acme?.enabled || false;
|
|
319
|
+
const acmeMode = acmeEnabled
|
|
320
|
+
? `email=${this.options.smartProxyConfig!.acme!.email || 'not set'}, mode=${this.options.smartProxyConfig!.acme!.useProduction ? 'production' : 'staging'}`
|
|
321
|
+
: 'disabled';
|
|
322
|
+
logger.log('info', `SmartProxy Service: ${routeCount} routes, ACME: ${acmeMode}`);
|
|
334
323
|
}
|
|
335
|
-
|
|
324
|
+
|
|
336
325
|
// Email service summary
|
|
337
326
|
if (this.emailServer && this.options.emailConfig) {
|
|
338
|
-
console.log('\n📧 Email Service:');
|
|
339
327
|
const ports = this.options.emailConfig.ports || [];
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
if (this.options.emailConfig.domains && this.options.emailConfig.domains.length > 0) {
|
|
344
|
-
this.options.emailConfig.domains.forEach((domain, index) => {
|
|
345
|
-
const isLast = index === this.options.emailConfig!.domains!.length - 1;
|
|
346
|
-
console.log(` ${isLast ? '└─' : '├─'} ${domain.domain} (${domain.dnsMode || 'default'})`);
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
console.log(` └─ DKIM: Initialized for all domains`);
|
|
328
|
+
const domainCount = this.options.emailConfig.domains?.length || 0;
|
|
329
|
+
const domainNames = this.options.emailConfig.domains?.map(d => `${d.domain} (${d.dnsMode || 'default'})`).join(', ') || 'none';
|
|
330
|
+
logger.log('info', `Email Service: ports=[${ports.join(', ')}], hostname=${this.options.emailConfig.hostname || 'localhost'}, domains=${domainCount} [${domainNames}], DKIM initialized`);
|
|
350
331
|
}
|
|
351
|
-
|
|
332
|
+
|
|
352
333
|
// DNS service summary
|
|
353
334
|
if (this.dnsServer && this.options.dnsNsDomains && this.options.dnsScopes) {
|
|
354
|
-
|
|
355
|
-
console.log(` ├─ Nameservers: ${this.options.dnsNsDomains.join(', ')}`);
|
|
356
|
-
console.log(` ├─ Primary NS: ${this.options.dnsNsDomains[0]}`);
|
|
357
|
-
console.log(` ├─ Authoritative for: ${this.options.dnsScopes.length} domains`);
|
|
358
|
-
console.log(` ├─ UDP Port: 53`);
|
|
359
|
-
console.log(` ├─ DNS-over-HTTPS: Enabled via socket handler`);
|
|
360
|
-
console.log(` └─ DNSSEC: ${this.options.dnsNsDomains[0] ? 'Enabled' : 'Disabled'}`);
|
|
361
|
-
|
|
362
|
-
// Show authoritative domains
|
|
363
|
-
if (this.options.dnsScopes.length > 0) {
|
|
364
|
-
console.log('\n Authoritative Domains:');
|
|
365
|
-
this.options.dnsScopes.forEach((domain, index) => {
|
|
366
|
-
const isLast = index === this.options.dnsScopes!.length - 1;
|
|
367
|
-
console.log(` ${isLast ? '└─' : '├─'} ${domain}`);
|
|
368
|
-
});
|
|
369
|
-
}
|
|
335
|
+
logger.log('info', `DNS Service: nameservers=[${this.options.dnsNsDomains.join(', ')}], authoritative for ${this.options.dnsScopes.length} domains [${this.options.dnsScopes.join(', ')}], UDP:53, DoH enabled`);
|
|
370
336
|
}
|
|
371
|
-
|
|
337
|
+
|
|
372
338
|
// RADIUS service summary
|
|
373
339
|
if (this.radiusServer && this.options.radiusConfig) {
|
|
374
|
-
console.log('\n🔐 RADIUS Service:');
|
|
375
|
-
console.log(` ├─ Auth Port: ${this.options.radiusConfig.authPort || 1812}`);
|
|
376
|
-
console.log(` ├─ Acct Port: ${this.options.radiusConfig.acctPort || 1813}`);
|
|
377
|
-
console.log(` ├─ Clients configured: ${this.options.radiusConfig.clients?.length || 0}`);
|
|
378
340
|
const vlanStats = this.radiusServer.getVlanManager().getStats();
|
|
379
|
-
|
|
380
|
-
console.log(` └─ Accounting: ${this.options.radiusConfig.accounting?.enabled ? 'Enabled' : 'Disabled'}`);
|
|
341
|
+
logger.log('info', `RADIUS Service: auth=${this.options.radiusConfig.authPort || 1812}, acct=${this.options.radiusConfig.acctPort || 1813}, clients=${this.options.radiusConfig.clients?.length || 0}, VLANs=${vlanStats.totalMappings}, accounting=${this.options.radiusConfig.accounting?.enabled ? 'enabled' : 'disabled'}`);
|
|
381
342
|
}
|
|
382
343
|
|
|
383
344
|
// Remote Ingress summary
|
|
384
345
|
if (this.tunnelManager && this.options.remoteIngressConfig?.enabled) {
|
|
385
|
-
console.log('\n🌐 Remote Ingress:');
|
|
386
|
-
console.log(` ├─ Tunnel Port: ${this.options.remoteIngressConfig.tunnelPort || 8443}`);
|
|
387
346
|
const edgeCount = this.remoteIngressManager?.getAllEdges().length || 0;
|
|
388
347
|
const connectedCount = this.tunnelManager.getConnectedCount();
|
|
389
|
-
|
|
390
|
-
console.log(` └─ Connected Edges: ${connectedCount}`);
|
|
348
|
+
logger.log('info', `Remote Ingress: tunnel port=${this.options.remoteIngressConfig.tunnelPort || 8443}, edges=${edgeCount} registered/${connectedCount} connected`);
|
|
391
349
|
}
|
|
392
350
|
|
|
393
351
|
// Storage summary
|
|
394
352
|
if (this.storageManager && this.options.storage) {
|
|
395
|
-
|
|
396
|
-
console.log(` └─ Path: ${this.options.storage.fsPath || 'default'}`);
|
|
353
|
+
logger.log('info', `Storage: path=${this.options.storage.fsPath || 'default'}`);
|
|
397
354
|
}
|
|
398
355
|
|
|
399
356
|
// Cache database summary
|
|
400
357
|
if (this.cacheDb) {
|
|
401
|
-
|
|
402
|
-
console.log(` ├─ Storage: ${this.cacheDb.getStoragePath()}`);
|
|
403
|
-
console.log(` ├─ Database: ${this.cacheDb.getDbName()}`);
|
|
404
|
-
console.log(` └─ Cleaner: ${this.cacheCleaner?.isActive() ? 'Active' : 'Inactive'} (${(this.options.cacheConfig?.cleanupIntervalHours || 1)}h interval)`);
|
|
358
|
+
logger.log('info', `Cache Database: storage=${this.cacheDb.getStoragePath()}, db=${this.cacheDb.getDbName()}, cleaner=${this.cacheCleaner?.isActive() ? 'active' : 'inactive'} (${(this.options.cacheConfig?.cleanupIntervalHours || 1)}h interval)`);
|
|
405
359
|
}
|
|
406
360
|
|
|
407
|
-
|
|
361
|
+
logger.log('info', 'All services are running');
|
|
408
362
|
}
|
|
409
363
|
|
|
410
364
|
/**
|
|
@@ -439,7 +393,7 @@ export class DcRouter {
|
|
|
439
393
|
* Set up SmartProxy with direct configuration and automatic email routes
|
|
440
394
|
*/
|
|
441
395
|
private async setupSmartProxy(): Promise<void> {
|
|
442
|
-
|
|
396
|
+
logger.log('info', 'Setting up SmartProxy...');
|
|
443
397
|
let routes: plugins.smartproxy.IRouteConfig[] = [];
|
|
444
398
|
let acmeConfig: plugins.smartproxy.IAcmeOptions | undefined;
|
|
445
399
|
|
|
@@ -447,22 +401,20 @@ export class DcRouter {
|
|
|
447
401
|
if (this.options.smartProxyConfig) {
|
|
448
402
|
routes = this.options.smartProxyConfig.routes || [];
|
|
449
403
|
acmeConfig = this.options.smartProxyConfig.acme;
|
|
450
|
-
|
|
451
|
-
console.log(`[DcRouter] ACME config present: ${!!acmeConfig}`);
|
|
404
|
+
logger.log('info', `Found ${routes.length} routes in config, ACME config present: ${!!acmeConfig}`);
|
|
452
405
|
}
|
|
453
406
|
|
|
454
407
|
// If email config exists, automatically add email routes
|
|
455
408
|
if (this.options.emailConfig) {
|
|
456
409
|
const emailRoutes = this.generateEmailRoutes(this.options.emailConfig);
|
|
457
|
-
|
|
458
|
-
console.log(emailRoutes)
|
|
410
|
+
logger.log('debug', 'Email routes generated', { routes: JSON.stringify(emailRoutes) });
|
|
459
411
|
routes = [...routes, ...emailRoutes]; // Enable email routing through SmartProxy
|
|
460
412
|
}
|
|
461
413
|
|
|
462
414
|
// If DNS is configured, add DNS routes
|
|
463
415
|
if (this.options.dnsNsDomains && this.options.dnsNsDomains.length > 0) {
|
|
464
416
|
const dnsRoutes = this.generateDnsRoutes();
|
|
465
|
-
|
|
417
|
+
logger.log('debug', `DNS routes for nameservers ${this.options.dnsNsDomains.join(', ')}`, { routes: JSON.stringify(dnsRoutes) });
|
|
466
418
|
routes = [...routes, ...dnsRoutes];
|
|
467
419
|
}
|
|
468
420
|
|
|
@@ -480,7 +432,7 @@ export class DcRouter {
|
|
|
480
432
|
// Configure DNS challenge if available
|
|
481
433
|
let challengeHandlers: any[] = [];
|
|
482
434
|
if (this.options.dnsChallenge?.cloudflareApiKey) {
|
|
483
|
-
|
|
435
|
+
logger.log('info', 'Configuring Cloudflare DNS challenge for ACME');
|
|
484
436
|
const cloudflareAccount = new plugins.cloudflare.CloudflareAccount(this.options.dnsChallenge.cloudflareApiKey);
|
|
485
437
|
const dns01Handler = new plugins.smartacme.handlers.Dns01Handler(cloudflareAccount);
|
|
486
438
|
challengeHandlers.push(dns01Handler);
|
|
@@ -488,7 +440,7 @@ export class DcRouter {
|
|
|
488
440
|
|
|
489
441
|
// If we have routes or need a basic SmartProxy instance, create it
|
|
490
442
|
if (routes.length > 0 || this.options.smartProxyConfig) {
|
|
491
|
-
|
|
443
|
+
logger.log('info', 'Setting up SmartProxy with combined configuration');
|
|
492
444
|
|
|
493
445
|
// Track cert entries loaded from cert store so we can populate certificateStatusMap after start
|
|
494
446
|
const loadedCertEntries: Array<{domain: string; publicKey: string; validUntil?: number; validFrom?: number}> = [];
|
|
@@ -537,7 +489,7 @@ export class DcRouter {
|
|
|
537
489
|
// Stop old SmartAcme if it exists (e.g., during updateSmartProxyConfig)
|
|
538
490
|
if (this.smartAcme) {
|
|
539
491
|
await this.smartAcme.stop().catch(err =>
|
|
540
|
-
|
|
492
|
+
logger.log('error', 'Error stopping old SmartAcme', { error: String(err) })
|
|
541
493
|
);
|
|
542
494
|
}
|
|
543
495
|
this.smartAcme = new plugins.smartacme.SmartAcme({
|
|
@@ -600,25 +552,19 @@ export class DcRouter {
|
|
|
600
552
|
}
|
|
601
553
|
|
|
602
554
|
// Create SmartProxy instance
|
|
603
|
-
|
|
604
|
-
routeCount: smartProxyConfig.routes?.length,
|
|
605
|
-
acmeEnabled: smartProxyConfig.acme?.enabled,
|
|
606
|
-
acmeEmail: smartProxyConfig.acme?.email,
|
|
607
|
-
certProvisionFunction: !!smartProxyConfig.certProvisionFunction
|
|
608
|
-
}, null, 2));
|
|
555
|
+
logger.log('info', `Creating SmartProxy instance: routes=${smartProxyConfig.routes?.length}, acme=${smartProxyConfig.acme?.enabled}, certProvisionFunction=${!!smartProxyConfig.certProvisionFunction}`);
|
|
609
556
|
|
|
610
557
|
this.smartProxy = new plugins.smartproxy.SmartProxy(smartProxyConfig);
|
|
611
558
|
|
|
612
559
|
// Set up event listeners
|
|
613
560
|
this.smartProxy.on('error', (err) => {
|
|
614
|
-
|
|
615
|
-
console.error('[DcRouter] Error stack:', err.stack);
|
|
561
|
+
logger.log('error', `SmartProxy error: ${err.message}`, { stack: err.stack });
|
|
616
562
|
});
|
|
617
563
|
|
|
618
564
|
// Always listen for certificate events — emitted by both ACME and certProvisionFunction paths
|
|
619
565
|
// Events are keyed by domain for domain-centric certificate tracking
|
|
620
566
|
this.smartProxy.on('certificate-issued', (event: plugins.smartproxy.ICertificateIssuedEvent) => {
|
|
621
|
-
|
|
567
|
+
logger.log('info', `Certificate issued for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
|
|
622
568
|
const routeNames = this.findRouteNamesForDomain(event.domain);
|
|
623
569
|
this.certificateStatusMap.set(event.domain, {
|
|
624
570
|
status: 'valid', routeNames,
|
|
@@ -628,7 +574,7 @@ export class DcRouter {
|
|
|
628
574
|
});
|
|
629
575
|
|
|
630
576
|
this.smartProxy.on('certificate-renewed', (event: plugins.smartproxy.ICertificateIssuedEvent) => {
|
|
631
|
-
|
|
577
|
+
logger.log('info', `Certificate renewed for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
|
|
632
578
|
const routeNames = this.findRouteNamesForDomain(event.domain);
|
|
633
579
|
this.certificateStatusMap.set(event.domain, {
|
|
634
580
|
status: 'valid', routeNames,
|
|
@@ -638,7 +584,7 @@ export class DcRouter {
|
|
|
638
584
|
});
|
|
639
585
|
|
|
640
586
|
this.smartProxy.on('certificate-failed', (event: plugins.smartproxy.ICertificateFailedEvent) => {
|
|
641
|
-
|
|
587
|
+
logger.log('error', `Certificate failed for ${event.domain} (${event.source}): ${event.error}`);
|
|
642
588
|
const routeNames = this.findRouteNamesForDomain(event.domain);
|
|
643
589
|
this.certificateStatusMap.set(event.domain, {
|
|
644
590
|
status: 'failed', routeNames, error: event.error,
|
|
@@ -647,9 +593,9 @@ export class DcRouter {
|
|
|
647
593
|
});
|
|
648
594
|
|
|
649
595
|
// Start SmartProxy
|
|
650
|
-
|
|
596
|
+
logger.log('info', 'Starting SmartProxy...');
|
|
651
597
|
await this.smartProxy.start();
|
|
652
|
-
|
|
598
|
+
logger.log('info', 'SmartProxy started successfully');
|
|
653
599
|
|
|
654
600
|
// Populate certificateStatusMap for certs loaded from store at startup
|
|
655
601
|
for (const entry of loadedCertEntries) {
|
|
@@ -701,10 +647,10 @@ export class DcRouter {
|
|
|
701
647
|
}
|
|
702
648
|
}
|
|
703
649
|
if (loadedCertEntries.length > 0) {
|
|
704
|
-
|
|
650
|
+
logger.log('info', `Populated certificate status for ${loadedCertEntries.length} store-loaded domain(s)`);
|
|
705
651
|
}
|
|
706
652
|
|
|
707
|
-
|
|
653
|
+
logger.log('info', `SmartProxy started with ${routes.length} routes`);
|
|
708
654
|
}
|
|
709
655
|
}
|
|
710
656
|
|
|
@@ -907,7 +853,7 @@ export class DcRouter {
|
|
|
907
853
|
}
|
|
908
854
|
|
|
909
855
|
public async stop() {
|
|
910
|
-
|
|
856
|
+
logger.log('info', 'Stopping DcRouter services...');
|
|
911
857
|
|
|
912
858
|
await this.opsServer.stop();
|
|
913
859
|
|
|
@@ -918,36 +864,36 @@ export class DcRouter {
|
|
|
918
864
|
this.cacheCleaner ? Promise.resolve(this.cacheCleaner.stop()) : Promise.resolve(),
|
|
919
865
|
|
|
920
866
|
// Stop metrics manager if running
|
|
921
|
-
this.metricsManager ? this.metricsManager.stop().catch(err =>
|
|
867
|
+
this.metricsManager ? this.metricsManager.stop().catch(err => logger.log('error', 'Error stopping MetricsManager', { error: String(err) })) : Promise.resolve(),
|
|
922
868
|
|
|
923
869
|
// Stop unified email server if running
|
|
924
|
-
this.emailServer ? this.emailServer.stop().catch(err =>
|
|
870
|
+
this.emailServer ? this.emailServer.stop().catch(err => logger.log('error', 'Error stopping email server', { error: String(err) })) : Promise.resolve(),
|
|
925
871
|
|
|
926
872
|
// Stop SmartAcme if running
|
|
927
|
-
this.smartAcme ? this.smartAcme.stop().catch(err =>
|
|
873
|
+
this.smartAcme ? this.smartAcme.stop().catch(err => logger.log('error', 'Error stopping SmartAcme', { error: String(err) })) : Promise.resolve(),
|
|
928
874
|
|
|
929
875
|
// Stop HTTP SmartProxy if running
|
|
930
|
-
this.smartProxy ? this.smartProxy.stop().catch(err =>
|
|
876
|
+
this.smartProxy ? this.smartProxy.stop().catch(err => logger.log('error', 'Error stopping SmartProxy', { error: String(err) })) : Promise.resolve(),
|
|
931
877
|
|
|
932
878
|
// Stop DNS server if running
|
|
933
879
|
this.dnsServer ?
|
|
934
|
-
this.dnsServer.stop().catch(err =>
|
|
880
|
+
this.dnsServer.stop().catch(err => logger.log('error', 'Error stopping DNS server', { error: String(err) })) :
|
|
935
881
|
Promise.resolve(),
|
|
936
882
|
|
|
937
883
|
// Stop RADIUS server if running
|
|
938
884
|
this.radiusServer ?
|
|
939
|
-
this.radiusServer.stop().catch(err =>
|
|
885
|
+
this.radiusServer.stop().catch(err => logger.log('error', 'Error stopping RADIUS server', { error: String(err) })) :
|
|
940
886
|
Promise.resolve(),
|
|
941
887
|
|
|
942
888
|
// Stop Remote Ingress tunnel manager if running
|
|
943
889
|
this.tunnelManager ?
|
|
944
|
-
this.tunnelManager.stop().catch(err =>
|
|
890
|
+
this.tunnelManager.stop().catch(err => logger.log('error', 'Error stopping TunnelManager', { error: String(err) })) :
|
|
945
891
|
Promise.resolve()
|
|
946
892
|
]);
|
|
947
893
|
|
|
948
894
|
// Stop cache database after other services (they may need it during shutdown)
|
|
949
895
|
if (this.cacheDb) {
|
|
950
|
-
await this.cacheDb.stop().catch(err =>
|
|
896
|
+
await this.cacheDb.stop().catch(err => logger.log('error', 'Error stopping CacheDb', { error: String(err) }));
|
|
951
897
|
}
|
|
952
898
|
|
|
953
899
|
// Clear backoff cache in cert scheduler
|
|
@@ -969,9 +915,9 @@ export class DcRouter {
|
|
|
969
915
|
this.remoteIngressManager = undefined;
|
|
970
916
|
this.certificateStatusMap.clear();
|
|
971
917
|
|
|
972
|
-
|
|
918
|
+
logger.log('info', 'All DcRouter services stopped');
|
|
973
919
|
} catch (error) {
|
|
974
|
-
|
|
920
|
+
logger.log('error', 'Error during DcRouter shutdown', { error: String(error) });
|
|
975
921
|
throw error;
|
|
976
922
|
}
|
|
977
923
|
}
|
|
@@ -998,7 +944,7 @@ export class DcRouter {
|
|
|
998
944
|
// Start new SmartProxy with updated configuration (will include email routes if configured)
|
|
999
945
|
await this.setupSmartProxy();
|
|
1000
946
|
|
|
1001
|
-
|
|
947
|
+
logger.log('info', 'SmartProxy configuration updated');
|
|
1002
948
|
}
|
|
1003
949
|
|
|
1004
950
|
|
|
@@ -1091,7 +1037,7 @@ export class DcRouter {
|
|
|
1091
1037
|
// Start email handling with new configuration
|
|
1092
1038
|
await this.setupUnifiedEmailHandling();
|
|
1093
1039
|
|
|
1094
|
-
|
|
1040
|
+
logger.log('info', 'Unified email configuration updated');
|
|
1095
1041
|
}
|
|
1096
1042
|
|
|
1097
1043
|
/**
|
|
@@ -1131,7 +1077,7 @@ export class DcRouter {
|
|
|
1131
1077
|
this.emailServer.updateEmailRoutes(routes);
|
|
1132
1078
|
}
|
|
1133
1079
|
|
|
1134
|
-
|
|
1080
|
+
logger.log('info', `Email routes updated with ${routes.length} routes`);
|
|
1135
1081
|
}
|
|
1136
1082
|
|
|
1137
1083
|
/**
|
|
@@ -2,9 +2,10 @@ import * as plugins from '../plugins.js';
|
|
|
2
2
|
import { DcRouter } from '../classes.dcrouter.js';
|
|
3
3
|
import { MetricsCache } from './classes.metricscache.js';
|
|
4
4
|
import { SecurityLogger, SecurityEventType } from '../security/classes.securitylogger.js';
|
|
5
|
+
import { logger } from '../logger.js';
|
|
5
6
|
|
|
6
7
|
export class MetricsManager {
|
|
7
|
-
private
|
|
8
|
+
private metricsLogger: plugins.smartlog.Smartlog;
|
|
8
9
|
private smartMetrics: plugins.smartmetrics.SmartMetrics;
|
|
9
10
|
private dcRouter: DcRouter;
|
|
10
11
|
private resetInterval?: NodeJS.Timeout;
|
|
@@ -56,15 +57,15 @@ export class MetricsManager {
|
|
|
56
57
|
|
|
57
58
|
constructor(dcRouter: DcRouter) {
|
|
58
59
|
this.dcRouter = dcRouter;
|
|
59
|
-
// Create a
|
|
60
|
-
this.
|
|
60
|
+
// Create a Smartlog instance for SmartMetrics (requires its own instance)
|
|
61
|
+
this.metricsLogger = new plugins.smartlog.Smartlog({
|
|
61
62
|
logContext: {
|
|
62
63
|
environment: 'production',
|
|
63
64
|
runtime: 'node',
|
|
64
65
|
zone: 'dcrouter-metrics',
|
|
65
66
|
}
|
|
66
67
|
});
|
|
67
|
-
this.smartMetrics = new plugins.smartmetrics.SmartMetrics(this.
|
|
68
|
+
this.smartMetrics = new plugins.smartmetrics.SmartMetrics(this.metricsLogger, 'dcrouter');
|
|
68
69
|
// Initialize metrics cache with 500ms TTL
|
|
69
70
|
this.metricsCache = new MetricsCache(500);
|
|
70
71
|
}
|
|
@@ -109,20 +110,29 @@ export class MetricsManager {
|
|
|
109
110
|
this.securityMetrics.incidents = [];
|
|
110
111
|
this.securityMetrics.lastResetDate = currentDate;
|
|
111
112
|
}
|
|
113
|
+
|
|
114
|
+
// Prune old time-series buckets every minute (don't wait for lazy query)
|
|
115
|
+
this.pruneOldBuckets();
|
|
112
116
|
}, 60000); // Check every minute
|
|
113
117
|
|
|
114
|
-
|
|
118
|
+
logger.log('info', 'MetricsManager started');
|
|
115
119
|
}
|
|
116
|
-
|
|
120
|
+
|
|
117
121
|
public async stop(): Promise<void> {
|
|
118
122
|
// Clear the reset interval
|
|
119
123
|
if (this.resetInterval) {
|
|
120
124
|
clearInterval(this.resetInterval);
|
|
121
125
|
this.resetInterval = undefined;
|
|
122
126
|
}
|
|
123
|
-
|
|
127
|
+
|
|
124
128
|
this.smartMetrics.stop();
|
|
125
|
-
|
|
129
|
+
|
|
130
|
+
// Clear caches and time-series buckets on shutdown
|
|
131
|
+
this.metricsCache.clear();
|
|
132
|
+
this.emailMinuteBuckets.clear();
|
|
133
|
+
this.dnsMinuteBuckets.clear();
|
|
134
|
+
|
|
135
|
+
logger.log('info', 'MetricsManager stopped');
|
|
126
136
|
}
|
|
127
137
|
|
|
128
138
|
// Get server metrics from SmartMetrics and SmartProxy
|
|
@@ -35,11 +35,7 @@ export class TunnelManager {
|
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
this.hub.on('edgeDisconnected', (data: { edgeId: string }) => {
|
|
38
|
-
|
|
39
|
-
if (existing) {
|
|
40
|
-
existing.connected = false;
|
|
41
|
-
existing.activeTunnels = 0;
|
|
42
|
-
}
|
|
38
|
+
this.edgeStatuses.delete(data.edgeId);
|
|
43
39
|
});
|
|
44
40
|
|
|
45
41
|
this.hub.on('streamOpened', (data: { edgeId: string; streamId: number }) => {
|
|
@@ -133,6 +133,17 @@ export class OpsViewLogs extends DeesElement {
|
|
|
133
133
|
// Ensure the chart element has finished its own initialization
|
|
134
134
|
await chartLog.updateComplete;
|
|
135
135
|
|
|
136
|
+
// Wait for xterm terminal to finish initializing (CDN load)
|
|
137
|
+
if (!chartLog.terminalReady) {
|
|
138
|
+
await new Promise<void>((resolve) => {
|
|
139
|
+
const check = () => {
|
|
140
|
+
if (chartLog.terminalReady) { resolve(); return; }
|
|
141
|
+
setTimeout(check, 50);
|
|
142
|
+
};
|
|
143
|
+
check();
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
136
147
|
const allEntries = this.getMappedLogEntries();
|
|
137
148
|
if (this.lastPushedCount === 0 && allEntries.length > 0) {
|
|
138
149
|
// Initial load: push all entries
|
|
@@ -47,9 +47,6 @@ export class OpsViewNetwork extends DeesElement {
|
|
|
47
47
|
private lastChartUpdate = 0;
|
|
48
48
|
private chartUpdateThreshold = 1000; // Minimum ms between chart updates
|
|
49
49
|
|
|
50
|
-
private lastTrafficUpdateTime = 0;
|
|
51
|
-
private trafficUpdateInterval = 1000; // Update every 1 second
|
|
52
|
-
private requestCountHistory = new Map<number, number>(); // Track requests per time bucket
|
|
53
50
|
private trafficUpdateTimer: any = null;
|
|
54
51
|
private requestsPerSecHistory: number[] = []; // Track requests/sec over time for trend
|
|
55
52
|
private historyLoaded = false; // Whether server-side throughput history has been loaded
|
|
@@ -106,8 +103,6 @@ export class OpsViewNetwork extends DeesElement {
|
|
|
106
103
|
|
|
107
104
|
this.trafficDataIn = [...emptyData];
|
|
108
105
|
this.trafficDataOut = emptyData.map(point => ({ ...point }));
|
|
109
|
-
|
|
110
|
-
this.lastTrafficUpdateTime = now;
|
|
111
106
|
}
|
|
112
107
|
|
|
113
108
|
/**
|
|
@@ -413,11 +408,7 @@ export class OpsViewNetwork extends DeesElement {
|
|
|
413
408
|
const throughput = this.calculateThroughput();
|
|
414
409
|
const activeConnections = this.statsState.serverStats?.activeConnections || 0;
|
|
415
410
|
|
|
416
|
-
//
|
|
417
|
-
this.requestsPerSecHistory.push(reqPerSec);
|
|
418
|
-
if (this.requestsPerSecHistory.length > 20) {
|
|
419
|
-
this.requestsPerSecHistory.shift();
|
|
420
|
-
}
|
|
411
|
+
// Build trend data from pre-computed history (mutated in updateNetworkData, not here)
|
|
421
412
|
const trendData = [...this.requestsPerSecHistory];
|
|
422
413
|
while (trendData.length < 20) {
|
|
423
414
|
trendData.unshift(0);
|
|
@@ -529,6 +520,13 @@ export class OpsViewNetwork extends DeesElement {
|
|
|
529
520
|
}
|
|
530
521
|
|
|
531
522
|
private async updateNetworkData() {
|
|
523
|
+
// Track requests/sec history for the trend sparkline (moved out of render)
|
|
524
|
+
const reqPerSec = this.networkState.requestsPerSecond || 0;
|
|
525
|
+
this.requestsPerSecHistory.push(reqPerSec);
|
|
526
|
+
if (this.requestsPerSecHistory.length > 20) {
|
|
527
|
+
this.requestsPerSecHistory.shift();
|
|
528
|
+
}
|
|
529
|
+
|
|
532
530
|
// Only update if connections changed significantly
|
|
533
531
|
const newConnectionCount = this.networkState.connections.length;
|
|
534
532
|
const oldConnectionCount = this.networkRequests.length;
|
|
@@ -602,16 +600,13 @@ export class OpsViewNetwork extends DeesElement {
|
|
|
602
600
|
y: Math.round(throughputOutMbps * 10) / 10
|
|
603
601
|
};
|
|
604
602
|
|
|
605
|
-
//
|
|
603
|
+
// In-place mutation then reassign for Lit reactivity (avoids 4 intermediate arrays)
|
|
606
604
|
if (this.trafficDataIn.length >= 60) {
|
|
607
|
-
|
|
608
|
-
this.
|
|
609
|
-
this.trafficDataOut = [...this.trafficDataOut.slice(1), newDataPointOut];
|
|
610
|
-
} else {
|
|
611
|
-
// Still filling up the initial data
|
|
612
|
-
this.trafficDataIn = [...this.trafficDataIn, newDataPointIn];
|
|
613
|
-
this.trafficDataOut = [...this.trafficDataOut, newDataPointOut];
|
|
605
|
+
this.trafficDataIn.shift();
|
|
606
|
+
this.trafficDataOut.shift();
|
|
614
607
|
}
|
|
608
|
+
this.trafficDataIn = [...this.trafficDataIn, newDataPointIn];
|
|
609
|
+
this.trafficDataOut = [...this.trafficDataOut, newDataPointOut];
|
|
615
610
|
|
|
616
611
|
this.lastChartUpdate = now;
|
|
617
612
|
}
|