@serve.zone/dcrouter 7.3.0 → 7.4.1
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 +592 -592
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.js +50 -104
- package/dist_ts/logger.d.ts +2 -0
- package/dist_ts/logger.js +3 -3
- package/dist_ts/monitoring/classes.metricsmanager.d.ts +9 -2
- package/dist_ts/monitoring/classes.metricsmanager.js +23 -8
- package/dist_ts/opsserver/handlers/logs.handler.d.ts +5 -0
- package/dist_ts/opsserver/handlers/logs.handler.js +43 -2
- package/dist_ts/opsserver/handlers/stats.handler.js +3 -1
- package/dist_ts_interfaces/data/stats.d.ts +7 -0
- package/dist_ts_interfaces/requests/logs.d.ts +7 -0
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.js +51 -1
- package/dist_ts_web/elements/ops-view-logs.d.ts +1 -0
- package/dist_ts_web/elements/ops-view-logs.js +31 -7
- package/dist_ts_web/elements/ops-view-overview.d.ts +1 -0
- package/dist_ts_web/elements/ops-view-overview.js +12 -3
- package/dist_ts_web/plugins.d.ts +2 -1
- package/dist_ts_web/plugins.js +4 -2
- package/package.json +3 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +54 -107
- package/ts/logger.ts +2 -2
- package/ts/monitoring/classes.metricsmanager.ts +25 -9
- package/ts/opsserver/handlers/logs.handler.ts +47 -2
- package/ts/opsserver/handlers/stats.handler.ts +4 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +61 -0
- package/ts_web/elements/ops-view-logs.ts +28 -5
- package/ts_web/elements/ops-view-overview.ts +12 -2
- package/ts_web/plugins.ts +5 -1
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
|
/**
|
|
@@ -1266,6 +1212,7 @@ export class DcRouter {
|
|
|
1266
1212
|
question.name,
|
|
1267
1213
|
false,
|
|
1268
1214
|
event.responseTimeMs,
|
|
1215
|
+
event.answered,
|
|
1269
1216
|
);
|
|
1270
1217
|
}
|
|
1271
1218
|
});
|
package/ts/logger.ts
CHANGED
|
@@ -14,8 +14,8 @@ const envMap: Record<string, 'local' | 'test' | 'staging' | 'production'> = {
|
|
|
14
14
|
// In-memory log buffer for the OpsServer UI
|
|
15
15
|
export const logBuffer = new SmartlogDestinationBuffer({ maxEntries: 2000 });
|
|
16
16
|
|
|
17
|
-
// Default Smartlog instance
|
|
18
|
-
const baseLogger = new plugins.smartlog.Smartlog({
|
|
17
|
+
// Default Smartlog instance (exported so OpsServer can add push destinations)
|
|
18
|
+
export const baseLogger = new plugins.smartlog.Smartlog({
|
|
19
19
|
logContext: {
|
|
20
20
|
environment: envMap[nodeEnv] || 'production',
|
|
21
21
|
runtime: 'node',
|
|
@@ -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;
|
|
@@ -36,6 +37,7 @@ export class MetricsManager {
|
|
|
36
37
|
lastResetDate: new Date().toDateString(),
|
|
37
38
|
queryTimestamps: [] as number[], // Track query timestamps for rate calculation
|
|
38
39
|
responseTimes: [] as number[], // Track response times in ms
|
|
40
|
+
recentQueries: [] as Array<{ timestamp: number; domain: string; type: string; answered: boolean; responseTimeMs: number }>,
|
|
39
41
|
};
|
|
40
42
|
|
|
41
43
|
// Per-minute time-series buckets for charts
|
|
@@ -55,15 +57,15 @@ export class MetricsManager {
|
|
|
55
57
|
|
|
56
58
|
constructor(dcRouter: DcRouter) {
|
|
57
59
|
this.dcRouter = dcRouter;
|
|
58
|
-
// Create a
|
|
59
|
-
this.
|
|
60
|
+
// Create a Smartlog instance for SmartMetrics (requires its own instance)
|
|
61
|
+
this.metricsLogger = new plugins.smartlog.Smartlog({
|
|
60
62
|
logContext: {
|
|
61
63
|
environment: 'production',
|
|
62
64
|
runtime: 'node',
|
|
63
65
|
zone: 'dcrouter-metrics',
|
|
64
66
|
}
|
|
65
67
|
});
|
|
66
|
-
this.smartMetrics = new plugins.smartmetrics.SmartMetrics(this.
|
|
68
|
+
this.smartMetrics = new plugins.smartmetrics.SmartMetrics(this.metricsLogger, 'dcrouter');
|
|
67
69
|
// Initialize metrics cache with 500ms TTL
|
|
68
70
|
this.metricsCache = new MetricsCache(500);
|
|
69
71
|
}
|
|
@@ -95,6 +97,7 @@ export class MetricsManager {
|
|
|
95
97
|
this.dnsMetrics.topDomains.clear();
|
|
96
98
|
this.dnsMetrics.queryTimestamps = [];
|
|
97
99
|
this.dnsMetrics.responseTimes = [];
|
|
100
|
+
this.dnsMetrics.recentQueries = [];
|
|
98
101
|
this.dnsMetrics.lastResetDate = currentDate;
|
|
99
102
|
}
|
|
100
103
|
|
|
@@ -109,18 +112,18 @@ export class MetricsManager {
|
|
|
109
112
|
}
|
|
110
113
|
}, 60000); // Check every minute
|
|
111
114
|
|
|
112
|
-
|
|
115
|
+
logger.log('info', 'MetricsManager started');
|
|
113
116
|
}
|
|
114
|
-
|
|
117
|
+
|
|
115
118
|
public async stop(): Promise<void> {
|
|
116
119
|
// Clear the reset interval
|
|
117
120
|
if (this.resetInterval) {
|
|
118
121
|
clearInterval(this.resetInterval);
|
|
119
122
|
this.resetInterval = undefined;
|
|
120
123
|
}
|
|
121
|
-
|
|
124
|
+
|
|
122
125
|
this.smartMetrics.stop();
|
|
123
|
-
|
|
126
|
+
logger.log('info', 'MetricsManager stopped');
|
|
124
127
|
}
|
|
125
128
|
|
|
126
129
|
// Get server metrics from SmartMetrics and SmartProxy
|
|
@@ -228,6 +231,7 @@ export class MetricsManager {
|
|
|
228
231
|
queryTypes: this.dnsMetrics.queryTypes,
|
|
229
232
|
averageResponseTime: Math.round(avgResponseTime),
|
|
230
233
|
activeDomains: this.dnsMetrics.topDomains.size,
|
|
234
|
+
recentQueries: this.dnsMetrics.recentQueries.slice(),
|
|
231
235
|
};
|
|
232
236
|
});
|
|
233
237
|
}
|
|
@@ -392,9 +396,21 @@ export class MetricsManager {
|
|
|
392
396
|
}
|
|
393
397
|
|
|
394
398
|
// DNS event tracking methods
|
|
395
|
-
public trackDnsQuery(queryType: string, domain: string, cacheHit: boolean, responseTimeMs?: number): void {
|
|
399
|
+
public trackDnsQuery(queryType: string, domain: string, cacheHit: boolean, responseTimeMs?: number, answered?: boolean): void {
|
|
396
400
|
this.dnsMetrics.totalQueries++;
|
|
397
401
|
this.incrementDnsBucket();
|
|
402
|
+
|
|
403
|
+
// Store recent query entry
|
|
404
|
+
this.dnsMetrics.recentQueries.push({
|
|
405
|
+
timestamp: Date.now(),
|
|
406
|
+
domain,
|
|
407
|
+
type: queryType,
|
|
408
|
+
answered: answered ?? true,
|
|
409
|
+
responseTimeMs: responseTimeMs ?? 0,
|
|
410
|
+
});
|
|
411
|
+
if (this.dnsMetrics.recentQueries.length > 100) {
|
|
412
|
+
this.dnsMetrics.recentQueries.shift();
|
|
413
|
+
}
|
|
398
414
|
|
|
399
415
|
if (cacheHit) {
|
|
400
416
|
this.dnsMetrics.cacheHits++;
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
2
|
import type { OpsServer } from '../classes.opsserver.js';
|
|
3
3
|
import * as interfaces from '../../../ts_interfaces/index.js';
|
|
4
|
-
import { logBuffer } from '../../logger.js';
|
|
4
|
+
import { logBuffer, baseLogger } from '../../logger.js';
|
|
5
5
|
|
|
6
6
|
export class LogsHandler {
|
|
7
7
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
constructor(private opsServerRef: OpsServer) {
|
|
10
10
|
// Add this handler's router to the parent
|
|
11
11
|
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
|
12
12
|
this.registerHandlers();
|
|
13
|
+
this.setupLogPushDestination();
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
private registerHandlers(): void {
|
|
@@ -165,6 +166,50 @@ export class LogsHandler {
|
|
|
165
166
|
return mapped;
|
|
166
167
|
}
|
|
167
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Add a log destination to the base logger that pushes entries
|
|
171
|
+
* to all connected ops_dashboard TypedSocket clients.
|
|
172
|
+
*/
|
|
173
|
+
private setupLogPushDestination(): void {
|
|
174
|
+
const opsServerRef = this.opsServerRef;
|
|
175
|
+
|
|
176
|
+
baseLogger.addLogDestination({
|
|
177
|
+
async handleLog(logPackage: any) {
|
|
178
|
+
// Access the TypedSocket server instance from OpsServer
|
|
179
|
+
const typedsocket = opsServerRef.server?.typedserver?.typedsocket;
|
|
180
|
+
if (!typedsocket) return;
|
|
181
|
+
|
|
182
|
+
let connections: any[];
|
|
183
|
+
try {
|
|
184
|
+
connections = await typedsocket.findAllTargetConnectionsByTag('role', 'ops_dashboard');
|
|
185
|
+
} catch {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (connections.length === 0) return;
|
|
189
|
+
|
|
190
|
+
const entry: interfaces.data.ILogEntry = {
|
|
191
|
+
timestamp: logPackage.timestamp || Date.now(),
|
|
192
|
+
level: LogsHandler.mapLogLevel(logPackage.level),
|
|
193
|
+
category: LogsHandler.deriveCategory(logPackage.context?.zone, logPackage.message),
|
|
194
|
+
message: logPackage.message,
|
|
195
|
+
metadata: logPackage.data,
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
for (const conn of connections) {
|
|
199
|
+
try {
|
|
200
|
+
const push = typedsocket.createTypedRequest<interfaces.requests.IReq_PushLogEntry>(
|
|
201
|
+
'pushLogEntry',
|
|
202
|
+
conn,
|
|
203
|
+
);
|
|
204
|
+
push.fire({ entry }).catch(() => {}); // fire-and-forget
|
|
205
|
+
} catch {
|
|
206
|
+
// connection may have closed
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
168
213
|
private setupLogStream(
|
|
169
214
|
virtualStream: plugins.typedrequest.VirtualStream<Uint8Array>,
|
|
170
215
|
levelFilter?: string[],
|
|
@@ -241,6 +241,7 @@ export class StatsHandler {
|
|
|
241
241
|
averageResponseTime: 0,
|
|
242
242
|
queryTypes: stats.queryTypes,
|
|
243
243
|
timeSeries,
|
|
244
|
+
recentQueries: stats.recentQueries,
|
|
244
245
|
};
|
|
245
246
|
})
|
|
246
247
|
);
|
|
@@ -422,6 +423,7 @@ export class StatsHandler {
|
|
|
422
423
|
count: number;
|
|
423
424
|
}>;
|
|
424
425
|
queryTypes: { [key: string]: number };
|
|
426
|
+
recentQueries?: Array<{ timestamp: number; domain: string; type: string; answered: boolean; responseTimeMs: number }>;
|
|
425
427
|
domainBreakdown?: { [domain: string]: interfaces.data.IDnsStats };
|
|
426
428
|
}> {
|
|
427
429
|
// Get metrics from MetricsManager if available
|
|
@@ -435,9 +437,10 @@ export class StatsHandler {
|
|
|
435
437
|
cacheHitRate: dnsStats.cacheHitRate,
|
|
436
438
|
topDomains: dnsStats.topDomains,
|
|
437
439
|
queryTypes: dnsStats.queryTypes,
|
|
440
|
+
recentQueries: dnsStats.recentQueries,
|
|
438
441
|
};
|
|
439
442
|
}
|
|
440
|
-
|
|
443
|
+
|
|
441
444
|
// Fallback if MetricsManager not available
|
|
442
445
|
return {
|
|
443
446
|
queriesPerSecond: 0,
|