@serve.zone/dcrouter 11.9.1 → 11.10.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.
@@ -558,6 +558,7 @@ export class MetricsManager {
558
558
  throughputByIP: new Map<string, { in: number; out: number }>(),
559
559
  requestsPerSecond: 0,
560
560
  requestsTotal: 0,
561
+ backends: [] as Array<any>,
561
562
  };
562
563
  }
563
564
 
@@ -590,6 +591,73 @@ export class MetricsManager {
590
591
  const requestsPerSecond = proxyMetrics.requests.perSecond();
591
592
  const requestsTotal = proxyMetrics.requests.total();
592
593
 
594
+ // Collect backend protocol data
595
+ const backendMetrics = proxyMetrics.backends.byBackend();
596
+ const protocolCache = proxyMetrics.backends.detectedProtocols();
597
+
598
+ // Index protocol cache by "host:port"
599
+ const cacheByKey = new Map<string, (typeof protocolCache)[number]>();
600
+ for (const entry of protocolCache) {
601
+ cacheByKey.set(`${entry.host}:${entry.port}`, entry);
602
+ }
603
+
604
+ const backends: Array<any> = [];
605
+ const seen = new Set<string>();
606
+
607
+ for (const [key, bm] of backendMetrics) {
608
+ seen.add(key);
609
+ const cache = cacheByKey.get(key);
610
+ backends.push({
611
+ backend: key,
612
+ domain: cache?.domain ?? null,
613
+ protocol: bm.protocol,
614
+ activeConnections: bm.activeConnections,
615
+ totalConnections: bm.totalConnections,
616
+ connectErrors: bm.connectErrors,
617
+ handshakeErrors: bm.handshakeErrors,
618
+ requestErrors: bm.requestErrors,
619
+ avgConnectTimeMs: Math.round(bm.avgConnectTimeMs * 10) / 10,
620
+ poolHitRate: Math.round(bm.poolHitRate * 1000) / 1000,
621
+ h2Failures: bm.h2Failures,
622
+ h2Suppressed: cache?.h2Suppressed ?? false,
623
+ h3Suppressed: cache?.h3Suppressed ?? false,
624
+ h2CooldownRemainingSecs: cache?.h2CooldownRemainingSecs ?? null,
625
+ h3CooldownRemainingSecs: cache?.h3CooldownRemainingSecs ?? null,
626
+ h2ConsecutiveFailures: cache?.h2ConsecutiveFailures ?? null,
627
+ h3ConsecutiveFailures: cache?.h3ConsecutiveFailures ?? null,
628
+ h3Port: cache?.h3Port ?? null,
629
+ cacheAgeSecs: cache?.ageSecs ?? null,
630
+ });
631
+ }
632
+
633
+ // Include protocol cache entries with no matching backend metric
634
+ for (const entry of protocolCache) {
635
+ const key = `${entry.host}:${entry.port}`;
636
+ if (!seen.has(key)) {
637
+ backends.push({
638
+ backend: key,
639
+ domain: entry.domain,
640
+ protocol: entry.protocol,
641
+ activeConnections: 0,
642
+ totalConnections: 0,
643
+ connectErrors: 0,
644
+ handshakeErrors: 0,
645
+ requestErrors: 0,
646
+ avgConnectTimeMs: 0,
647
+ poolHitRate: 0,
648
+ h2Failures: 0,
649
+ h2Suppressed: entry.h2Suppressed,
650
+ h3Suppressed: entry.h3Suppressed,
651
+ h2CooldownRemainingSecs: entry.h2CooldownRemainingSecs,
652
+ h3CooldownRemainingSecs: entry.h3CooldownRemainingSecs,
653
+ h2ConsecutiveFailures: entry.h2ConsecutiveFailures,
654
+ h3ConsecutiveFailures: entry.h3ConsecutiveFailures,
655
+ h3Port: entry.h3Port,
656
+ cacheAgeSecs: entry.ageSecs,
657
+ });
658
+ }
659
+ }
660
+
593
661
  return {
594
662
  connectionsByIP,
595
663
  throughputRate,
@@ -599,6 +667,7 @@ export class MetricsManager {
599
667
  throughputByIP,
600
668
  requestsPerSecond,
601
669
  requestsTotal,
670
+ backends,
602
671
  };
603
672
  }, 1000); // 1s cache — matches typical dashboard poll interval
604
673
  }
@@ -101,6 +101,7 @@ export class SecurityHandler {
101
101
  throughputByIP,
102
102
  requestsPerSecond: networkStats.requestsPerSecond || 0,
103
103
  requestsTotal: networkStats.requestsTotal || 0,
104
+ backends: networkStats.backends || [],
104
105
  };
105
106
  }
106
107
 
@@ -114,6 +115,7 @@ export class SecurityHandler {
114
115
  throughputByIP: [],
115
116
  requestsPerSecond: 0,
116
117
  requestsTotal: 0,
118
+ backends: [],
117
119
  };
118
120
  }
119
121
  )
@@ -309,6 +309,7 @@ export class StatsHandler {
309
309
  throughputHistory: stats.throughputHistory || [],
310
310
  requestsPerSecond: stats.requestsPerSecond || 0,
311
311
  requestsTotal: stats.requestsTotal || 0,
312
+ backends: stats.backends || [],
312
313
  };
313
314
  })()
314
315
  );
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '11.9.1',
6
+ version: '11.10.1',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -53,6 +53,7 @@ export interface INetworkState {
53
53
  throughputHistory: Array<{ timestamp: number; in: number; out: number }>;
54
54
  requestsPerSecond: number;
55
55
  requestsTotal: number;
56
+ backends: interfaces.data.IBackendInfo[];
56
57
  lastUpdated: number;
57
58
  isLoading: boolean;
58
59
  error: string | null;
@@ -148,6 +149,7 @@ export const networkStatePart = await appState.getStatePart<INetworkState>(
148
149
  throughputHistory: [],
149
150
  requestsPerSecond: 0,
150
151
  requestsTotal: 0,
152
+ backends: [],
151
153
  lastUpdated: 0,
152
154
  isLoading: false,
153
155
  error: null,
@@ -503,6 +505,7 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat
503
505
  throughputHistory: networkStatsResponse.throughputHistory || [],
504
506
  requestsPerSecond: networkStatsResponse.requestsPerSecond || 0,
505
507
  requestsTotal: networkStatsResponse.requestsTotal || 0,
508
+ backends: networkStatsResponse.backends || [],
506
509
  lastUpdated: Date.now(),
507
510
  isLoading: false,
508
511
  error: null,
@@ -1,5 +1,6 @@
1
1
  import { DeesElement, property, html, customElement, type TemplateResult, css, state, cssManager } from '@design.estate/dees-element';
2
2
  import * as appstate from '../appstate.js';
3
+ import * as interfaces from '../../dist_ts_interfaces/index.js';
3
4
  import { viewHostCss } from './shared/css.js';
4
5
  import { type IStatsTile } from '@design.estate/dees-catalog';
5
6
 
@@ -198,6 +199,38 @@ export class OpsViewNetwork extends DeesElement {
198
199
  color: ${cssManager.bdTheme('#00796b', '#4db6ac')};
199
200
  }
200
201
 
202
+ .protocolBadge.h1 {
203
+ background: ${cssManager.bdTheme('#e3f2fd', '#1a2c3a')};
204
+ color: ${cssManager.bdTheme('#1976d2', '#5a9fd4')};
205
+ }
206
+
207
+ .protocolBadge.h2 {
208
+ background: ${cssManager.bdTheme('#e8f5e9', '#1a3a1a')};
209
+ color: ${cssManager.bdTheme('#388e3c', '#66bb6a')};
210
+ }
211
+
212
+ .protocolBadge.h3 {
213
+ background: ${cssManager.bdTheme('#f3e5f5', '#2a1a3a')};
214
+ color: ${cssManager.bdTheme('#7b1fa2', '#ba68c8')};
215
+ }
216
+
217
+ .protocolBadge.unknown {
218
+ background: ${cssManager.bdTheme('#f5f5f5', '#2a2a2a')};
219
+ color: ${cssManager.bdTheme('#757575', '#999999')};
220
+ }
221
+
222
+ .suppressionBadge {
223
+ display: inline-flex;
224
+ align-items: center;
225
+ padding: 2px 6px;
226
+ border-radius: 3px;
227
+ font-size: 11px;
228
+ font-weight: 500;
229
+ background: ${cssManager.bdTheme('#fff3e0', '#3a2a1a')};
230
+ color: ${cssManager.bdTheme('#f57c00', '#ff9933')};
231
+ margin-left: 4px;
232
+ }
233
+
201
234
  .statusBadge {
202
235
  display: inline-flex;
203
236
  align-items: center;
@@ -265,6 +298,9 @@ export class OpsViewNetwork extends DeesElement {
265
298
  <!-- Top IPs Section -->
266
299
  ${this.renderTopIPs()}
267
300
 
301
+ <!-- Backend Protocols Section -->
302
+ ${this.renderBackendProtocols()}
303
+
268
304
  <!-- Requests Table -->
269
305
  <dees-table
270
306
  .data=${this.networkRequests}
@@ -519,6 +555,106 @@ export class OpsViewNetwork extends DeesElement {
519
555
  `;
520
556
  }
521
557
 
558
+ private renderBackendProtocols(): TemplateResult {
559
+ const backends = this.networkState.backends;
560
+ if (!backends || backends.length === 0) {
561
+ return html``;
562
+ }
563
+
564
+ return html`
565
+ <dees-table
566
+ .data=${backends}
567
+ .displayFunction=${(item: interfaces.data.IBackendInfo) => {
568
+ const totalErrors = item.connectErrors + item.handshakeErrors + item.requestErrors;
569
+ const protocolClass = item.protocol.toLowerCase().replace(/[^a-z0-9]/g, '');
570
+
571
+ return {
572
+ 'Backend': item.backend,
573
+ 'Domain': item.domain || '-',
574
+ 'Protocol': html`
575
+ <span class="protocolBadge ${protocolClass}">${item.protocol.toUpperCase()}</span>
576
+ ${item.h2Suppressed ? html`<span class="suppressionBadge" title="H2 suppressed: ${item.h2ConsecutiveFailures ?? 0} failures, cooldown ${item.h2CooldownRemainingSecs ?? 0}s">H2 suppressed</span>` : ''}
577
+ ${item.h3Suppressed ? html`<span class="suppressionBadge" title="H3 suppressed: ${item.h3ConsecutiveFailures ?? 0} failures, cooldown ${item.h3CooldownRemainingSecs ?? 0}s">H3 suppressed</span>` : ''}
578
+ `,
579
+ 'Active': item.activeConnections,
580
+ 'Total': this.formatNumber(item.totalConnections),
581
+ 'Avg Connect': item.avgConnectTimeMs > 0 ? `${item.avgConnectTimeMs.toFixed(1)}ms` : '-',
582
+ 'Pool Hit Rate': item.poolHitRate > 0 ? `${(item.poolHitRate * 100).toFixed(1)}%` : '-',
583
+ 'Errors': totalErrors > 0
584
+ ? html`<span class="statusBadge error">${totalErrors}</span>`
585
+ : html`<span class="statusBadge success">0</span>`,
586
+ 'Cache Age': item.cacheAgeSecs != null ? `${Math.round(item.cacheAgeSecs)}s` : '-',
587
+ };
588
+ }}
589
+ .dataActions=${[
590
+ {
591
+ name: 'View Details',
592
+ iconName: 'lucide:info',
593
+ type: ['inRow', 'doubleClick', 'contextmenu'] as any,
594
+ actionFunc: async (actionData: any) => {
595
+ await this.showBackendDetails(actionData.item);
596
+ }
597
+ }
598
+ ]}
599
+ heading1="Backend Protocols"
600
+ heading2="Auto-detected backend protocols and connection pool health"
601
+ searchable
602
+ .pagination=${false}
603
+ dataName="backend"
604
+ ></dees-table>
605
+ `;
606
+ }
607
+
608
+ private async showBackendDetails(backend: interfaces.data.IBackendInfo) {
609
+ const { DeesModal } = await import('@design.estate/dees-catalog');
610
+
611
+ await DeesModal.createAndShow({
612
+ heading: `Backend: ${backend.backend}`,
613
+ content: html`
614
+ <div style="padding: 20px;">
615
+ <dees-dataview-codebox
616
+ .heading=${'Backend Details'}
617
+ progLang="json"
618
+ .codeToDisplay=${JSON.stringify({
619
+ backend: backend.backend,
620
+ domain: backend.domain,
621
+ protocol: backend.protocol,
622
+ activeConnections: backend.activeConnections,
623
+ totalConnections: backend.totalConnections,
624
+ avgConnectTimeMs: backend.avgConnectTimeMs,
625
+ poolHitRate: backend.poolHitRate,
626
+ errors: {
627
+ connect: backend.connectErrors,
628
+ handshake: backend.handshakeErrors,
629
+ request: backend.requestErrors,
630
+ h2Failures: backend.h2Failures,
631
+ },
632
+ suppression: {
633
+ h2Suppressed: backend.h2Suppressed,
634
+ h3Suppressed: backend.h3Suppressed,
635
+ h2CooldownRemainingSecs: backend.h2CooldownRemainingSecs,
636
+ h3CooldownRemainingSecs: backend.h3CooldownRemainingSecs,
637
+ h2ConsecutiveFailures: backend.h2ConsecutiveFailures,
638
+ h3ConsecutiveFailures: backend.h3ConsecutiveFailures,
639
+ },
640
+ h3Port: backend.h3Port,
641
+ cacheAgeSecs: backend.cacheAgeSecs,
642
+ }, null, 2)}
643
+ ></dees-dataview-codebox>
644
+ </div>
645
+ `,
646
+ menuOptions: [
647
+ {
648
+ name: 'Copy Backend Key',
649
+ iconName: 'lucide:Copy',
650
+ action: async () => {
651
+ await navigator.clipboard.writeText(backend.backend);
652
+ }
653
+ }
654
+ ]
655
+ });
656
+ }
657
+
522
658
  private async updateNetworkData() {
523
659
  // Track requests/sec history for the trend sparkline (moved out of render)
524
660
  const reqPerSec = this.networkState.requestsPerSecond || 0;