Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 447cf44d68 | |||
| 82ce17a941 | |||
| 15da996e70 | |||
| 582e19e6a6 |
19
changelog.md
19
changelog.md
@@ -1,5 +1,24 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-02-21 - 7.4.2 - fix(monitoring,remoteingress,web)
|
||||
Prune old metrics buckets periodically, clear metrics caches on shutdown, simplify edge disconnect handling, and optimize network view data updates
|
||||
|
||||
- Call pruneOldBuckets() each minute to proactively remove stale time-series buckets in MetricsManager
|
||||
- Clear metricsCache, emailMinuteBuckets and dnsMinuteBuckets when MetricsManager stops to avoid stale state on shutdown
|
||||
- On edgeDisconnected remove the edgeStatuses entry instead of mutating an existing record (more explicit cleanup)
|
||||
- Remove unused traffic-timer variables and move requestsPerSec history updates from render() into updateNetworkData() to avoid unnecessary re-renders
|
||||
- Optimize traffic data array updates by shifting in-place then reassigning arrays to preserve Lit reactivity and reduce intermediate allocations
|
||||
|
||||
## 2026-02-21 - 7.4.1 - fix(dcrouter)
|
||||
replace console logging with structured logger, improve metrics logging, add terminal-ready wait in ops UI, bump dees-catalog patch
|
||||
|
||||
- Replace console.log/console.error calls in classes.dcrouter.ts with structured logger.log (info/debug/error) including contextual data and stringified errors
|
||||
- MetricsManager: create a dedicated Smartlog instance (metricsLogger) for SmartMetrics and use shared logger for lifecycle events (start/stop)
|
||||
- SmartProxy/ACME: convert startup/stop/cert events and error logging to structured logs; include generated route and cert metadata where relevant
|
||||
- Shutdown/startup flows: unify service start/stop/error messages through logger to provide consistent, structured output
|
||||
- UI change: ops-view-logs now waits for xterm terminalReady before pushing initial logs to avoid race conditions
|
||||
- Bump dependency @design.estate/dees-catalog from 3.43.0 to 3.43.1
|
||||
|
||||
## 2026-02-21 - 7.4.0 - feat(opsserver)
|
||||
add real-time log push to ops dashboard and recent DNS query tracking
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@serve.zone/dcrouter",
|
||||
"private": false,
|
||||
"version": "7.4.0",
|
||||
"version": "7.4.2",
|
||||
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@@ -32,7 +32,7 @@
|
||||
"@api.global/typedserver": "^8.3.0",
|
||||
"@api.global/typedsocket": "^4.1.0",
|
||||
"@apiclient.xyz/cloudflare": "^7.1.0",
|
||||
"@design.estate/dees-catalog": "^3.43.0",
|
||||
"@design.estate/dees-catalog": "^3.43.1",
|
||||
"@design.estate/dees-element": "^2.1.6",
|
||||
"@push.rocks/projectinfo": "^5.0.2",
|
||||
"@push.rocks/qenv": "^6.1.3",
|
||||
|
||||
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
@@ -24,8 +24,8 @@ importers:
|
||||
specifier: ^7.1.0
|
||||
version: 7.1.0
|
||||
'@design.estate/dees-catalog':
|
||||
specifier: ^3.43.0
|
||||
version: 3.43.0(@tiptap/pm@2.27.2)
|
||||
specifier: ^3.43.1
|
||||
version: 3.43.1(@tiptap/pm@2.27.2)
|
||||
'@design.estate/dees-element':
|
||||
specifier: ^2.1.6
|
||||
version: 2.1.6
|
||||
@@ -351,8 +351,8 @@ packages:
|
||||
'@configvault.io/interfaces@1.0.17':
|
||||
resolution: {integrity: sha512-bEcCUR2VBDJsTin8HQh8Uw/mlYl2v8A3jMIaQ+MTB9Hrqd6CZL2dL7iJdWyFl/3EIX+LDxWFR+Oq7liIq7w+1Q==}
|
||||
|
||||
'@design.estate/dees-catalog@3.43.0':
|
||||
resolution: {integrity: sha512-UFW8oThP9Mc4L0wVVgmuGux868Ct/TwZ1WP8hZCe4e/+5gmxDc+4EArnt5hePHENboe1Soobh9mmrMN6kQZ3xQ==}
|
||||
'@design.estate/dees-catalog@3.43.1':
|
||||
resolution: {integrity: sha512-WAWOV8dIgdKfAbS4Ciek8oDVIWC0OSPODhpQdLlsGBXERcFaBPaYxcpywmrjXB/TFeoAQPxBxhS7jb9/p2Rprg==}
|
||||
|
||||
'@design.estate/dees-comms@1.0.30':
|
||||
resolution: {integrity: sha512-KchMlklJfKAjQiJiR0xmofXtQ27VgZtBIxcMwPE9d+h3jJRv+lPZxzBQVOM0eyM0uS44S5vJMZ11IeV4uDXSHg==}
|
||||
@@ -4218,13 +4218,11 @@ packages:
|
||||
|
||||
xterm-addon-fit@0.8.0:
|
||||
resolution: {integrity: sha512-yj3Np7XlvxxhYF/EJ7p3KHaMt6OdwQ+HDu573Vx1lRXsVxOcnVJs51RgjZOouIZOczTsskaS+CpXspK81/DLqw==}
|
||||
deprecated: This package is now deprecated. Move to @xterm/addon-fit instead.
|
||||
peerDependencies:
|
||||
xterm: ^5.0.0
|
||||
|
||||
xterm@5.3.0:
|
||||
resolution: {integrity: sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==}
|
||||
deprecated: This package is now deprecated. Move to @xterm/xterm instead.
|
||||
|
||||
y18n@5.0.8:
|
||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||
@@ -4336,7 +4334,7 @@ snapshots:
|
||||
'@api.global/typedrequest-interfaces': 3.0.19
|
||||
'@api.global/typedsocket': 4.1.0(@push.rocks/smartserve@2.0.1)
|
||||
'@cloudflare/workers-types': 4.20260210.0
|
||||
'@design.estate/dees-catalog': 3.43.0(@tiptap/pm@2.27.2)
|
||||
'@design.estate/dees-catalog': 3.43.1(@tiptap/pm@2.27.2)
|
||||
'@design.estate/dees-comms': 1.0.30
|
||||
'@push.rocks/lik': 6.2.2
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
@@ -4934,7 +4932,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@api.global/typedrequest-interfaces': 3.0.19
|
||||
|
||||
'@design.estate/dees-catalog@3.43.0(@tiptap/pm@2.27.2)':
|
||||
'@design.estate/dees-catalog@3.43.1(@tiptap/pm@2.27.2)':
|
||||
dependencies:
|
||||
'@design.estate/dees-domtools': 2.3.8
|
||||
'@design.estate/dees-element': 2.1.6
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/dcrouter',
|
||||
version: '7.4.0',
|
||||
version: '7.4.2',
|
||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||
}
|
||||
|
||||
@@ -252,9 +252,7 @@ export class DcRouter {
|
||||
}
|
||||
|
||||
public async start() {
|
||||
console.log('╔═══════════════════════════════════════════════════════════════════╗');
|
||||
console.log('║ Starting DcRouter Services ║');
|
||||
console.log('╚═══════════════════════════════════════════════════════════════════╝');
|
||||
logger.log('info', 'Starting DcRouter Services');
|
||||
|
||||
|
||||
this.opsServer = new OpsServer(this);
|
||||
@@ -296,7 +294,7 @@ export class DcRouter {
|
||||
|
||||
this.logStartupSummary();
|
||||
} catch (error) {
|
||||
console.error('❌ Error starting DcRouter:', error);
|
||||
logger.log('error', 'Error starting DcRouter', { error: String(error) });
|
||||
// Try to clean up any services that may have started
|
||||
await this.stop();
|
||||
throw error;
|
||||
@@ -307,104 +305,60 @@ export class DcRouter {
|
||||
* Log comprehensive startup summary
|
||||
*/
|
||||
private logStartupSummary(): void {
|
||||
console.log('\n╔═══════════════════════════════════════════════════════════════════╗');
|
||||
console.log('║ DcRouter Started Successfully ║');
|
||||
console.log('╚═══════════════════════════════════════════════════════════════════╝\n');
|
||||
|
||||
logger.log('info', 'DcRouter Started Successfully');
|
||||
|
||||
// Metrics summary
|
||||
if (this.metricsManager) {
|
||||
console.log('📊 Metrics Service:');
|
||||
console.log(' ├─ SmartMetrics: Active');
|
||||
console.log(' ├─ SmartProxy Stats: Active');
|
||||
console.log(' └─ Real-time tracking: Enabled');
|
||||
logger.log('info', 'Metrics Service: SmartMetrics active, SmartProxy stats active, real-time tracking enabled');
|
||||
}
|
||||
|
||||
|
||||
// SmartProxy summary
|
||||
if (this.smartProxy) {
|
||||
console.log('🌐 SmartProxy Service:');
|
||||
const routeCount = this.options.smartProxyConfig?.routes?.length || 0;
|
||||
console.log(` ├─ Routes configured: ${routeCount}`);
|
||||
console.log(` ├─ ACME enabled: ${this.options.smartProxyConfig?.acme?.enabled || false}`);
|
||||
if (this.options.smartProxyConfig?.acme?.enabled) {
|
||||
console.log(` ├─ ACME email: ${this.options.smartProxyConfig.acme.email || 'not set'}`);
|
||||
console.log(` └─ ACME mode: ${this.options.smartProxyConfig.acme.useProduction ? 'production' : 'staging'}`);
|
||||
} else {
|
||||
console.log(' └─ ACME: disabled');
|
||||
}
|
||||
const acmeEnabled = this.options.smartProxyConfig?.acme?.enabled || false;
|
||||
const acmeMode = acmeEnabled
|
||||
? `email=${this.options.smartProxyConfig!.acme!.email || 'not set'}, mode=${this.options.smartProxyConfig!.acme!.useProduction ? 'production' : 'staging'}`
|
||||
: 'disabled';
|
||||
logger.log('info', `SmartProxy Service: ${routeCount} routes, ACME: ${acmeMode}`);
|
||||
}
|
||||
|
||||
|
||||
// Email service summary
|
||||
if (this.emailServer && this.options.emailConfig) {
|
||||
console.log('\n📧 Email Service:');
|
||||
const ports = this.options.emailConfig.ports || [];
|
||||
console.log(` ├─ Ports: ${ports.join(', ')}`);
|
||||
console.log(` ├─ Hostname: ${this.options.emailConfig.hostname || 'localhost'}`);
|
||||
console.log(` ├─ Domains configured: ${this.options.emailConfig.domains?.length || 0}`);
|
||||
if (this.options.emailConfig.domains && this.options.emailConfig.domains.length > 0) {
|
||||
this.options.emailConfig.domains.forEach((domain, index) => {
|
||||
const isLast = index === this.options.emailConfig!.domains!.length - 1;
|
||||
console.log(` ${isLast ? '└─' : '├─'} ${domain.domain} (${domain.dnsMode || 'default'})`);
|
||||
});
|
||||
}
|
||||
console.log(` └─ DKIM: Initialized for all domains`);
|
||||
const domainCount = this.options.emailConfig.domains?.length || 0;
|
||||
const domainNames = this.options.emailConfig.domains?.map(d => `${d.domain} (${d.dnsMode || 'default'})`).join(', ') || 'none';
|
||||
logger.log('info', `Email Service: ports=[${ports.join(', ')}], hostname=${this.options.emailConfig.hostname || 'localhost'}, domains=${domainCount} [${domainNames}], DKIM initialized`);
|
||||
}
|
||||
|
||||
|
||||
// DNS service summary
|
||||
if (this.dnsServer && this.options.dnsNsDomains && this.options.dnsScopes) {
|
||||
console.log('\n🌍 DNS Service:');
|
||||
console.log(` ├─ Nameservers: ${this.options.dnsNsDomains.join(', ')}`);
|
||||
console.log(` ├─ Primary NS: ${this.options.dnsNsDomains[0]}`);
|
||||
console.log(` ├─ Authoritative for: ${this.options.dnsScopes.length} domains`);
|
||||
console.log(` ├─ UDP Port: 53`);
|
||||
console.log(` ├─ DNS-over-HTTPS: Enabled via socket handler`);
|
||||
console.log(` └─ DNSSEC: ${this.options.dnsNsDomains[0] ? 'Enabled' : 'Disabled'}`);
|
||||
|
||||
// Show authoritative domains
|
||||
if (this.options.dnsScopes.length > 0) {
|
||||
console.log('\n Authoritative Domains:');
|
||||
this.options.dnsScopes.forEach((domain, index) => {
|
||||
const isLast = index === this.options.dnsScopes!.length - 1;
|
||||
console.log(` ${isLast ? '└─' : '├─'} ${domain}`);
|
||||
});
|
||||
}
|
||||
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`);
|
||||
}
|
||||
|
||||
|
||||
// RADIUS service summary
|
||||
if (this.radiusServer && this.options.radiusConfig) {
|
||||
console.log('\n🔐 RADIUS Service:');
|
||||
console.log(` ├─ Auth Port: ${this.options.radiusConfig.authPort || 1812}`);
|
||||
console.log(` ├─ Acct Port: ${this.options.radiusConfig.acctPort || 1813}`);
|
||||
console.log(` ├─ Clients configured: ${this.options.radiusConfig.clients?.length || 0}`);
|
||||
const vlanStats = this.radiusServer.getVlanManager().getStats();
|
||||
console.log(` ├─ VLAN mappings: ${vlanStats.totalMappings}`);
|
||||
console.log(` └─ Accounting: ${this.options.radiusConfig.accounting?.enabled ? 'Enabled' : 'Disabled'}`);
|
||||
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'}`);
|
||||
}
|
||||
|
||||
// Remote Ingress summary
|
||||
if (this.tunnelManager && this.options.remoteIngressConfig?.enabled) {
|
||||
console.log('\n🌐 Remote Ingress:');
|
||||
console.log(` ├─ Tunnel Port: ${this.options.remoteIngressConfig.tunnelPort || 8443}`);
|
||||
const edgeCount = this.remoteIngressManager?.getAllEdges().length || 0;
|
||||
const connectedCount = this.tunnelManager.getConnectedCount();
|
||||
console.log(` ├─ Registered Edges: ${edgeCount}`);
|
||||
console.log(` └─ Connected Edges: ${connectedCount}`);
|
||||
logger.log('info', `Remote Ingress: tunnel port=${this.options.remoteIngressConfig.tunnelPort || 8443}, edges=${edgeCount} registered/${connectedCount} connected`);
|
||||
}
|
||||
|
||||
// Storage summary
|
||||
if (this.storageManager && this.options.storage) {
|
||||
console.log('\n💾 Storage:');
|
||||
console.log(` └─ Path: ${this.options.storage.fsPath || 'default'}`);
|
||||
logger.log('info', `Storage: path=${this.options.storage.fsPath || 'default'}`);
|
||||
}
|
||||
|
||||
// Cache database summary
|
||||
if (this.cacheDb) {
|
||||
console.log('\n🗄️ Cache Database (smartdata + LocalTsmDb):');
|
||||
console.log(` ├─ Storage: ${this.cacheDb.getStoragePath()}`);
|
||||
console.log(` ├─ Database: ${this.cacheDb.getDbName()}`);
|
||||
console.log(` └─ Cleaner: ${this.cacheCleaner?.isActive() ? 'Active' : 'Inactive'} (${(this.options.cacheConfig?.cleanupIntervalHours || 1)}h interval)`);
|
||||
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)`);
|
||||
}
|
||||
|
||||
console.log('\n✅ All services are running\n');
|
||||
logger.log('info', 'All services are running');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -439,7 +393,7 @@ export class DcRouter {
|
||||
* Set up SmartProxy with direct configuration and automatic email routes
|
||||
*/
|
||||
private async setupSmartProxy(): Promise<void> {
|
||||
console.log('[DcRouter] Setting up SmartProxy...');
|
||||
logger.log('info', 'Setting up SmartProxy...');
|
||||
let routes: plugins.smartproxy.IRouteConfig[] = [];
|
||||
let acmeConfig: plugins.smartproxy.IAcmeOptions | undefined;
|
||||
|
||||
@@ -447,22 +401,20 @@ export class DcRouter {
|
||||
if (this.options.smartProxyConfig) {
|
||||
routes = this.options.smartProxyConfig.routes || [];
|
||||
acmeConfig = this.options.smartProxyConfig.acme;
|
||||
console.log(`[DcRouter] Found ${routes.length} routes in config`);
|
||||
console.log(`[DcRouter] ACME config present: ${!!acmeConfig}`);
|
||||
logger.log('info', `Found ${routes.length} routes in config, ACME config present: ${!!acmeConfig}`);
|
||||
}
|
||||
|
||||
// If email config exists, automatically add email routes
|
||||
if (this.options.emailConfig) {
|
||||
const emailRoutes = this.generateEmailRoutes(this.options.emailConfig);
|
||||
console.log(`Email Routes are:`)
|
||||
console.log(emailRoutes)
|
||||
logger.log('debug', 'Email routes generated', { routes: JSON.stringify(emailRoutes) });
|
||||
routes = [...routes, ...emailRoutes]; // Enable email routing through SmartProxy
|
||||
}
|
||||
|
||||
// If DNS is configured, add DNS routes
|
||||
if (this.options.dnsNsDomains && this.options.dnsNsDomains.length > 0) {
|
||||
const dnsRoutes = this.generateDnsRoutes();
|
||||
console.log(`DNS Routes for nameservers ${this.options.dnsNsDomains.join(', ')}:`, dnsRoutes);
|
||||
logger.log('debug', `DNS routes for nameservers ${this.options.dnsNsDomains.join(', ')}`, { routes: JSON.stringify(dnsRoutes) });
|
||||
routes = [...routes, ...dnsRoutes];
|
||||
}
|
||||
|
||||
@@ -480,7 +432,7 @@ export class DcRouter {
|
||||
// Configure DNS challenge if available
|
||||
let challengeHandlers: any[] = [];
|
||||
if (this.options.dnsChallenge?.cloudflareApiKey) {
|
||||
console.log('Configuring Cloudflare DNS challenge for ACME');
|
||||
logger.log('info', 'Configuring Cloudflare DNS challenge for ACME');
|
||||
const cloudflareAccount = new plugins.cloudflare.CloudflareAccount(this.options.dnsChallenge.cloudflareApiKey);
|
||||
const dns01Handler = new plugins.smartacme.handlers.Dns01Handler(cloudflareAccount);
|
||||
challengeHandlers.push(dns01Handler);
|
||||
@@ -488,7 +440,7 @@ export class DcRouter {
|
||||
|
||||
// If we have routes or need a basic SmartProxy instance, create it
|
||||
if (routes.length > 0 || this.options.smartProxyConfig) {
|
||||
console.log('Setting up SmartProxy with combined configuration');
|
||||
logger.log('info', 'Setting up SmartProxy with combined configuration');
|
||||
|
||||
// Track cert entries loaded from cert store so we can populate certificateStatusMap after start
|
||||
const loadedCertEntries: Array<{domain: string; publicKey: string; validUntil?: number; validFrom?: number}> = [];
|
||||
@@ -537,7 +489,7 @@ export class DcRouter {
|
||||
// Stop old SmartAcme if it exists (e.g., during updateSmartProxyConfig)
|
||||
if (this.smartAcme) {
|
||||
await this.smartAcme.stop().catch(err =>
|
||||
console.error('[DcRouter] Error stopping old SmartAcme:', err)
|
||||
logger.log('error', 'Error stopping old SmartAcme', { error: String(err) })
|
||||
);
|
||||
}
|
||||
this.smartAcme = new plugins.smartacme.SmartAcme({
|
||||
@@ -600,25 +552,19 @@ export class DcRouter {
|
||||
}
|
||||
|
||||
// Create SmartProxy instance
|
||||
console.log('[DcRouter] Creating SmartProxy instance with config:', JSON.stringify({
|
||||
routeCount: smartProxyConfig.routes?.length,
|
||||
acmeEnabled: smartProxyConfig.acme?.enabled,
|
||||
acmeEmail: smartProxyConfig.acme?.email,
|
||||
certProvisionFunction: !!smartProxyConfig.certProvisionFunction
|
||||
}, null, 2));
|
||||
logger.log('info', `Creating SmartProxy instance: routes=${smartProxyConfig.routes?.length}, acme=${smartProxyConfig.acme?.enabled}, certProvisionFunction=${!!smartProxyConfig.certProvisionFunction}`);
|
||||
|
||||
this.smartProxy = new plugins.smartproxy.SmartProxy(smartProxyConfig);
|
||||
|
||||
// Set up event listeners
|
||||
this.smartProxy.on('error', (err) => {
|
||||
console.error('[DcRouter] SmartProxy error:', err);
|
||||
console.error('[DcRouter] Error stack:', err.stack);
|
||||
logger.log('error', `SmartProxy error: ${err.message}`, { stack: err.stack });
|
||||
});
|
||||
|
||||
// Always listen for certificate events — emitted by both ACME and certProvisionFunction paths
|
||||
// Events are keyed by domain for domain-centric certificate tracking
|
||||
this.smartProxy.on('certificate-issued', (event: plugins.smartproxy.ICertificateIssuedEvent) => {
|
||||
console.log(`[DcRouter] Certificate issued for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
|
||||
logger.log('info', `Certificate issued for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
|
||||
const routeNames = this.findRouteNamesForDomain(event.domain);
|
||||
this.certificateStatusMap.set(event.domain, {
|
||||
status: 'valid', routeNames,
|
||||
@@ -628,7 +574,7 @@ export class DcRouter {
|
||||
});
|
||||
|
||||
this.smartProxy.on('certificate-renewed', (event: plugins.smartproxy.ICertificateIssuedEvent) => {
|
||||
console.log(`[DcRouter] Certificate renewed for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
|
||||
logger.log('info', `Certificate renewed for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
|
||||
const routeNames = this.findRouteNamesForDomain(event.domain);
|
||||
this.certificateStatusMap.set(event.domain, {
|
||||
status: 'valid', routeNames,
|
||||
@@ -638,7 +584,7 @@ export class DcRouter {
|
||||
});
|
||||
|
||||
this.smartProxy.on('certificate-failed', (event: plugins.smartproxy.ICertificateFailedEvent) => {
|
||||
console.error(`[DcRouter] Certificate failed for ${event.domain} (${event.source}):`, event.error);
|
||||
logger.log('error', `Certificate failed for ${event.domain} (${event.source}): ${event.error}`);
|
||||
const routeNames = this.findRouteNamesForDomain(event.domain);
|
||||
this.certificateStatusMap.set(event.domain, {
|
||||
status: 'failed', routeNames, error: event.error,
|
||||
@@ -647,9 +593,9 @@ export class DcRouter {
|
||||
});
|
||||
|
||||
// Start SmartProxy
|
||||
console.log('[DcRouter] Starting SmartProxy...');
|
||||
logger.log('info', 'Starting SmartProxy...');
|
||||
await this.smartProxy.start();
|
||||
console.log('[DcRouter] SmartProxy started successfully');
|
||||
logger.log('info', 'SmartProxy started successfully');
|
||||
|
||||
// Populate certificateStatusMap for certs loaded from store at startup
|
||||
for (const entry of loadedCertEntries) {
|
||||
@@ -701,10 +647,10 @@ export class DcRouter {
|
||||
}
|
||||
}
|
||||
if (loadedCertEntries.length > 0) {
|
||||
console.log(`[DcRouter] Populated certificate status for ${loadedCertEntries.length} store-loaded domain(s)`);
|
||||
logger.log('info', `Populated certificate status for ${loadedCertEntries.length} store-loaded domain(s)`);
|
||||
}
|
||||
|
||||
console.log(`SmartProxy started with ${routes.length} routes`);
|
||||
logger.log('info', `SmartProxy started with ${routes.length} routes`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -907,7 +853,7 @@ export class DcRouter {
|
||||
}
|
||||
|
||||
public async stop() {
|
||||
console.log('Stopping DcRouter services...');
|
||||
logger.log('info', 'Stopping DcRouter services...');
|
||||
|
||||
await this.opsServer.stop();
|
||||
|
||||
@@ -918,36 +864,36 @@ export class DcRouter {
|
||||
this.cacheCleaner ? Promise.resolve(this.cacheCleaner.stop()) : Promise.resolve(),
|
||||
|
||||
// Stop metrics manager if running
|
||||
this.metricsManager ? this.metricsManager.stop().catch(err => console.error('Error stopping MetricsManager:', err)) : Promise.resolve(),
|
||||
this.metricsManager ? this.metricsManager.stop().catch(err => logger.log('error', 'Error stopping MetricsManager', { error: String(err) })) : Promise.resolve(),
|
||||
|
||||
// Stop unified email server if running
|
||||
this.emailServer ? this.emailServer.stop().catch(err => console.error('Error stopping email server:', err)) : Promise.resolve(),
|
||||
this.emailServer ? this.emailServer.stop().catch(err => logger.log('error', 'Error stopping email server', { error: String(err) })) : Promise.resolve(),
|
||||
|
||||
// Stop SmartAcme if running
|
||||
this.smartAcme ? this.smartAcme.stop().catch(err => console.error('Error stopping SmartAcme:', err)) : Promise.resolve(),
|
||||
this.smartAcme ? this.smartAcme.stop().catch(err => logger.log('error', 'Error stopping SmartAcme', { error: String(err) })) : Promise.resolve(),
|
||||
|
||||
// Stop HTTP SmartProxy if running
|
||||
this.smartProxy ? this.smartProxy.stop().catch(err => console.error('Error stopping SmartProxy:', err)) : Promise.resolve(),
|
||||
this.smartProxy ? this.smartProxy.stop().catch(err => logger.log('error', 'Error stopping SmartProxy', { error: String(err) })) : Promise.resolve(),
|
||||
|
||||
// Stop DNS server if running
|
||||
this.dnsServer ?
|
||||
this.dnsServer.stop().catch(err => console.error('Error stopping DNS server:', err)) :
|
||||
this.dnsServer.stop().catch(err => logger.log('error', 'Error stopping DNS server', { error: String(err) })) :
|
||||
Promise.resolve(),
|
||||
|
||||
// Stop RADIUS server if running
|
||||
this.radiusServer ?
|
||||
this.radiusServer.stop().catch(err => console.error('Error stopping RADIUS server:', err)) :
|
||||
this.radiusServer.stop().catch(err => logger.log('error', 'Error stopping RADIUS server', { error: String(err) })) :
|
||||
Promise.resolve(),
|
||||
|
||||
// Stop Remote Ingress tunnel manager if running
|
||||
this.tunnelManager ?
|
||||
this.tunnelManager.stop().catch(err => console.error('Error stopping TunnelManager:', err)) :
|
||||
this.tunnelManager.stop().catch(err => logger.log('error', 'Error stopping TunnelManager', { error: String(err) })) :
|
||||
Promise.resolve()
|
||||
]);
|
||||
|
||||
// Stop cache database after other services (they may need it during shutdown)
|
||||
if (this.cacheDb) {
|
||||
await this.cacheDb.stop().catch(err => console.error('Error stopping CacheDb:', err));
|
||||
await this.cacheDb.stop().catch(err => logger.log('error', 'Error stopping CacheDb', { error: String(err) }));
|
||||
}
|
||||
|
||||
// Clear backoff cache in cert scheduler
|
||||
@@ -969,9 +915,9 @@ export class DcRouter {
|
||||
this.remoteIngressManager = undefined;
|
||||
this.certificateStatusMap.clear();
|
||||
|
||||
console.log('All DcRouter services stopped');
|
||||
logger.log('info', 'All DcRouter services stopped');
|
||||
} catch (error) {
|
||||
console.error('Error during DcRouter shutdown:', error);
|
||||
logger.log('error', 'Error during DcRouter shutdown', { error: String(error) });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -998,7 +944,7 @@ export class DcRouter {
|
||||
// Start new SmartProxy with updated configuration (will include email routes if configured)
|
||||
await this.setupSmartProxy();
|
||||
|
||||
console.log('SmartProxy configuration updated');
|
||||
logger.log('info', 'SmartProxy configuration updated');
|
||||
}
|
||||
|
||||
|
||||
@@ -1091,7 +1037,7 @@ export class DcRouter {
|
||||
// Start email handling with new configuration
|
||||
await this.setupUnifiedEmailHandling();
|
||||
|
||||
console.log('Unified email configuration updated');
|
||||
logger.log('info', 'Unified email configuration updated');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1131,7 +1077,7 @@ export class DcRouter {
|
||||
this.emailServer.updateEmailRoutes(routes);
|
||||
}
|
||||
|
||||
console.log(`Email routes updated with ${routes.length} routes`);
|
||||
logger.log('info', `Email routes updated with ${routes.length} routes`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,9 +2,10 @@ import * as plugins from '../plugins.js';
|
||||
import { DcRouter } from '../classes.dcrouter.js';
|
||||
import { MetricsCache } from './classes.metricscache.js';
|
||||
import { SecurityLogger, SecurityEventType } from '../security/classes.securitylogger.js';
|
||||
import { logger } from '../logger.js';
|
||||
|
||||
export class MetricsManager {
|
||||
private logger: plugins.smartlog.Smartlog;
|
||||
private metricsLogger: plugins.smartlog.Smartlog;
|
||||
private smartMetrics: plugins.smartmetrics.SmartMetrics;
|
||||
private dcRouter: DcRouter;
|
||||
private resetInterval?: NodeJS.Timeout;
|
||||
@@ -56,15 +57,15 @@ export class MetricsManager {
|
||||
|
||||
constructor(dcRouter: DcRouter) {
|
||||
this.dcRouter = dcRouter;
|
||||
// Create a new Smartlog instance for metrics
|
||||
this.logger = new plugins.smartlog.Smartlog({
|
||||
// Create a Smartlog instance for SmartMetrics (requires its own instance)
|
||||
this.metricsLogger = new plugins.smartlog.Smartlog({
|
||||
logContext: {
|
||||
environment: 'production',
|
||||
runtime: 'node',
|
||||
zone: 'dcrouter-metrics',
|
||||
}
|
||||
});
|
||||
this.smartMetrics = new plugins.smartmetrics.SmartMetrics(this.logger, 'dcrouter');
|
||||
this.smartMetrics = new plugins.smartmetrics.SmartMetrics(this.metricsLogger, 'dcrouter');
|
||||
// Initialize metrics cache with 500ms TTL
|
||||
this.metricsCache = new MetricsCache(500);
|
||||
}
|
||||
@@ -109,20 +110,29 @@ export class MetricsManager {
|
||||
this.securityMetrics.incidents = [];
|
||||
this.securityMetrics.lastResetDate = currentDate;
|
||||
}
|
||||
|
||||
// Prune old time-series buckets every minute (don't wait for lazy query)
|
||||
this.pruneOldBuckets();
|
||||
}, 60000); // Check every minute
|
||||
|
||||
this.logger.log('info', 'MetricsManager started');
|
||||
logger.log('info', 'MetricsManager started');
|
||||
}
|
||||
|
||||
|
||||
public async stop(): Promise<void> {
|
||||
// Clear the reset interval
|
||||
if (this.resetInterval) {
|
||||
clearInterval(this.resetInterval);
|
||||
this.resetInterval = undefined;
|
||||
}
|
||||
|
||||
|
||||
this.smartMetrics.stop();
|
||||
this.logger.log('info', 'MetricsManager stopped');
|
||||
|
||||
// Clear caches and time-series buckets on shutdown
|
||||
this.metricsCache.clear();
|
||||
this.emailMinuteBuckets.clear();
|
||||
this.dnsMinuteBuckets.clear();
|
||||
|
||||
logger.log('info', 'MetricsManager stopped');
|
||||
}
|
||||
|
||||
// Get server metrics from SmartMetrics and SmartProxy
|
||||
|
||||
@@ -35,11 +35,7 @@ export class TunnelManager {
|
||||
});
|
||||
|
||||
this.hub.on('edgeDisconnected', (data: { edgeId: string }) => {
|
||||
const existing = this.edgeStatuses.get(data.edgeId);
|
||||
if (existing) {
|
||||
existing.connected = false;
|
||||
existing.activeTunnels = 0;
|
||||
}
|
||||
this.edgeStatuses.delete(data.edgeId);
|
||||
});
|
||||
|
||||
this.hub.on('streamOpened', (data: { edgeId: string; streamId: number }) => {
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/dcrouter',
|
||||
version: '7.4.0',
|
||||
version: '7.4.2',
|
||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||
}
|
||||
|
||||
@@ -133,6 +133,17 @@ export class OpsViewLogs extends DeesElement {
|
||||
// Ensure the chart element has finished its own initialization
|
||||
await chartLog.updateComplete;
|
||||
|
||||
// Wait for xterm terminal to finish initializing (CDN load)
|
||||
if (!chartLog.terminalReady) {
|
||||
await new Promise<void>((resolve) => {
|
||||
const check = () => {
|
||||
if (chartLog.terminalReady) { resolve(); return; }
|
||||
setTimeout(check, 50);
|
||||
};
|
||||
check();
|
||||
});
|
||||
}
|
||||
|
||||
const allEntries = this.getMappedLogEntries();
|
||||
if (this.lastPushedCount === 0 && allEntries.length > 0) {
|
||||
// Initial load: push all entries
|
||||
|
||||
@@ -47,9 +47,6 @@ export class OpsViewNetwork extends DeesElement {
|
||||
private lastChartUpdate = 0;
|
||||
private chartUpdateThreshold = 1000; // Minimum ms between chart updates
|
||||
|
||||
private lastTrafficUpdateTime = 0;
|
||||
private trafficUpdateInterval = 1000; // Update every 1 second
|
||||
private requestCountHistory = new Map<number, number>(); // Track requests per time bucket
|
||||
private trafficUpdateTimer: any = null;
|
||||
private requestsPerSecHistory: number[] = []; // Track requests/sec over time for trend
|
||||
private historyLoaded = false; // Whether server-side throughput history has been loaded
|
||||
@@ -106,8 +103,6 @@ export class OpsViewNetwork extends DeesElement {
|
||||
|
||||
this.trafficDataIn = [...emptyData];
|
||||
this.trafficDataOut = emptyData.map(point => ({ ...point }));
|
||||
|
||||
this.lastTrafficUpdateTime = now;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -413,11 +408,7 @@ export class OpsViewNetwork extends DeesElement {
|
||||
const throughput = this.calculateThroughput();
|
||||
const activeConnections = this.statsState.serverStats?.activeConnections || 0;
|
||||
|
||||
// Track requests/sec history for the trend sparkline
|
||||
this.requestsPerSecHistory.push(reqPerSec);
|
||||
if (this.requestsPerSecHistory.length > 20) {
|
||||
this.requestsPerSecHistory.shift();
|
||||
}
|
||||
// Build trend data from pre-computed history (mutated in updateNetworkData, not here)
|
||||
const trendData = [...this.requestsPerSecHistory];
|
||||
while (trendData.length < 20) {
|
||||
trendData.unshift(0);
|
||||
@@ -529,6 +520,13 @@ export class OpsViewNetwork extends DeesElement {
|
||||
}
|
||||
|
||||
private async updateNetworkData() {
|
||||
// Track requests/sec history for the trend sparkline (moved out of render)
|
||||
const reqPerSec = this.networkState.requestsPerSecond || 0;
|
||||
this.requestsPerSecHistory.push(reqPerSec);
|
||||
if (this.requestsPerSecHistory.length > 20) {
|
||||
this.requestsPerSecHistory.shift();
|
||||
}
|
||||
|
||||
// Only update if connections changed significantly
|
||||
const newConnectionCount = this.networkState.connections.length;
|
||||
const oldConnectionCount = this.networkRequests.length;
|
||||
@@ -602,16 +600,13 @@ export class OpsViewNetwork extends DeesElement {
|
||||
y: Math.round(throughputOutMbps * 10) / 10
|
||||
};
|
||||
|
||||
// Efficient array updates - modify in place when possible
|
||||
// In-place mutation then reassign for Lit reactivity (avoids 4 intermediate arrays)
|
||||
if (this.trafficDataIn.length >= 60) {
|
||||
// Remove oldest and add newest
|
||||
this.trafficDataIn = [...this.trafficDataIn.slice(1), newDataPointIn];
|
||||
this.trafficDataOut = [...this.trafficDataOut.slice(1), newDataPointOut];
|
||||
} else {
|
||||
// Still filling up the initial data
|
||||
this.trafficDataIn = [...this.trafficDataIn, newDataPointIn];
|
||||
this.trafficDataOut = [...this.trafficDataOut, newDataPointOut];
|
||||
this.trafficDataIn.shift();
|
||||
this.trafficDataOut.shift();
|
||||
}
|
||||
this.trafficDataIn = [...this.trafficDataIn, newDataPointIn];
|
||||
this.trafficDataOut = [...this.trafficDataOut, newDataPointOut];
|
||||
|
||||
this.lastChartUpdate = now;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user