@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.
@@ -252,9 +252,7 @@ export class DcRouter {
252
252
  }
253
253
 
254
254
  public async start() {
255
- console.log('╔═══════════════════════════════════════════════════════════════════╗');
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
- console.error(' Error starting DcRouter:', error);
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
- console.log('\n╔═══════════════════════════════════════════════════════════════════╗');
311
- console.log('║ DcRouter Started Successfully ║');
312
- console.log('╚═══════════════════════════════════════════════════════════════════╝\n');
313
-
308
+ logger.log('info', 'DcRouter Started Successfully');
309
+
314
310
  // Metrics summary
315
311
  if (this.metricsManager) {
316
- console.log('📊 Metrics Service:');
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
- console.log(` ├─ Routes configured: ${routeCount}`);
327
- console.log(` ├─ ACME enabled: ${this.options.smartProxyConfig?.acme?.enabled || false}`);
328
- if (this.options.smartProxyConfig?.acme?.enabled) {
329
- console.log(` ├─ ACME email: ${this.options.smartProxyConfig.acme.email || 'not set'}`);
330
- console.log(` └─ ACME mode: ${this.options.smartProxyConfig.acme.useProduction ? 'production' : 'staging'}`);
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
- console.log(` ├─ Ports: ${ports.join(', ')}`);
341
- console.log(` ├─ Hostname: ${this.options.emailConfig.hostname || 'localhost'}`);
342
- console.log(` ├─ Domains configured: ${this.options.emailConfig.domains?.length || 0}`);
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
- console.log('\n🌍 DNS Service:');
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
- console.log(` ├─ VLAN mappings: ${vlanStats.totalMappings}`);
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
- console.log(` ├─ Registered Edges: ${edgeCount}`);
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
- console.log('\n💾 Storage:');
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
- console.log('\n🗄️ Cache Database (smartdata + LocalTsmDb):');
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
- console.log('\n✅ All services are running\n');
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
- console.log('[DcRouter] Setting up SmartProxy...');
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
- console.log(`[DcRouter] Found ${routes.length} routes in config`);
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
- console.log(`Email Routes are:`)
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
- console.log(`DNS Routes for nameservers ${this.options.dnsNsDomains.join(', ')}:`, dnsRoutes);
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
- console.log('Configuring Cloudflare DNS challenge for ACME');
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
- console.log('Setting up SmartProxy with combined configuration');
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
- console.error('[DcRouter] Error stopping old SmartAcme:', err)
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
- console.log('[DcRouter] Creating SmartProxy instance with config:', JSON.stringify({
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
- console.error('[DcRouter] SmartProxy error:', err);
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
- console.log(`[DcRouter] Certificate issued for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
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
- console.log(`[DcRouter] Certificate renewed for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
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
- console.error(`[DcRouter] Certificate failed for ${event.domain} (${event.source}):`, event.error);
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
- console.log('[DcRouter] Starting SmartProxy...');
596
+ logger.log('info', 'Starting SmartProxy...');
651
597
  await this.smartProxy.start();
652
- console.log('[DcRouter] SmartProxy started successfully');
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
- console.log(`[DcRouter] Populated certificate status for ${loadedCertEntries.length} store-loaded domain(s)`);
650
+ logger.log('info', `Populated certificate status for ${loadedCertEntries.length} store-loaded domain(s)`);
705
651
  }
706
652
 
707
- console.log(`SmartProxy started with ${routes.length} routes`);
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
- console.log('Stopping DcRouter services...');
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 => console.error('Error stopping MetricsManager:', err)) : Promise.resolve(),
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 => console.error('Error stopping email server:', err)) : Promise.resolve(),
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 => console.error('Error stopping SmartAcme:', err)) : Promise.resolve(),
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 => console.error('Error stopping SmartProxy:', err)) : Promise.resolve(),
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 => console.error('Error stopping DNS server:', 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 => console.error('Error stopping RADIUS server:', 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 => console.error('Error stopping TunnelManager:', 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 => console.error('Error stopping CacheDb:', 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
- console.log('All DcRouter services stopped');
918
+ logger.log('info', 'All DcRouter services stopped');
973
919
  } catch (error) {
974
- console.error('Error during DcRouter shutdown:', error);
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
- console.log('SmartProxy configuration updated');
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
- console.log('Unified email configuration updated');
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
- console.log(`Email routes updated with ${routes.length} routes`);
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 logger: plugins.smartlog.Smartlog;
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 new Smartlog instance for metrics
60
- this.logger = new plugins.smartlog.Smartlog({
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.logger, 'dcrouter');
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
- this.logger.log('info', 'MetricsManager started');
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
- this.logger.log('info', 'MetricsManager stopped');
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
- const existing = this.edgeStatuses.get(data.edgeId);
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 }) => {
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '7.4.0',
6
+ version: '7.4.2',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -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
- // Track requests/sec history for the trend sparkline
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
- // Efficient array updates - modify in place when possible
603
+ // In-place mutation then reassign for Lit reactivity (avoids 4 intermediate arrays)
606
604
  if (this.trafficDataIn.length >= 60) {
607
- // Remove oldest and add newest
608
- this.trafficDataIn = [...this.trafficDataIn.slice(1), newDataPointIn];
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
  }