@serve.zone/dcrouter 17.2.1 → 17.2.3

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.
@@ -1,3 +1,8 @@
1
+ import {
2
+ NETWORK_ACTIVITY_PROTOCOL_ORDER,
3
+ orderNetworkActivityProtocolNames,
4
+ } from './network-activity-visuals.js';
5
+
1
6
  export interface IChartPoint {
2
7
  x: string | number;
3
8
  y: number;
@@ -14,9 +19,19 @@ export type TProtocolChartSeries = Array<{
14
19
  data: IChartPoint[];
15
20
  }>;
16
21
 
17
- export const PROTOCOL_ORDER = ['HTTP/1.1', 'HTTP/2', 'HTTP/3', 'WebSocket', 'Other'] as const;
22
+ export const PROTOCOL_ORDER = NETWORK_ACTIVITY_PROTOCOL_ORDER;
18
23
  export const PROTOCOL_SERIES_ORDER = ['Total Active', ...PROTOCOL_ORDER] as const;
19
24
 
25
+ const getProtocolSeriesOrder = (
26
+ protocolNamesArg: string[],
27
+ ): string[] => [
28
+ 'Total Active',
29
+ ...orderNetworkActivityProtocolNames([
30
+ ...PROTOCOL_ORDER,
31
+ ...protocolNamesArg,
32
+ ]),
33
+ ];
34
+
20
35
  const toEpochSecond = (timestampArg: string | number): number => {
21
36
  const timestampMs = typeof timestampArg === 'number'
22
37
  ? timestampArg
@@ -59,7 +74,9 @@ export const mergeChartPoints = (
59
74
 
60
75
  export const createProtocolConnectionSeries = (
61
76
  historyArg: IProtocolConnectionHistoryPointLike[] = [],
62
- ): TProtocolChartSeries => PROTOCOL_SERIES_ORDER.map((seriesName) => ({
77
+ ): TProtocolChartSeries => getProtocolSeriesOrder(
78
+ historyArg.flatMap((point) => Object.keys(point.protocols)),
79
+ ).map((seriesName) => ({
63
80
  name: seriesName,
64
81
  data: historyArg.map((point) => ({
65
82
  x: new Date(point.timestamp).toISOString(),
@@ -79,8 +96,12 @@ export const mergeProtocolConnectionHistory = (
79
96
  createProtocolConnectionSeries(canonicalHistoryArg)
80
97
  .map((series) => [series.name, series]),
81
98
  );
99
+ const seriesOrder = getProtocolSeriesOrder([
100
+ ...localSeriesArg.map((series) => series.name),
101
+ ...canonicalHistoryArg.flatMap((point) => Object.keys(point.protocols)),
102
+ ]);
82
103
 
83
- return PROTOCOL_SERIES_ORDER.map((seriesName) => ({
104
+ return seriesOrder.map((seriesName) => ({
84
105
  name: seriesName,
85
106
  data: mergeChartPoints(
86
107
  localSeriesByName.get(seriesName)?.data ?? [],
@@ -92,10 +113,9 @@ export const mergeProtocolConnectionHistory = (
92
113
 
93
114
  export const getProtocolConnectionTotal = (
94
115
  protocolsArg: Record<string, number>,
95
- ): number => (protocolsArg['HTTP/1.1'] ?? 0)
96
- + (protocolsArg['HTTP/2'] ?? 0)
97
- + (protocolsArg['HTTP/3'] ?? 0)
98
- + (protocolsArg.Other ?? 0);
116
+ ): number => Object.entries(protocolsArg)
117
+ .filter(([name]) => name !== 'WebSocket')
118
+ .reduce((total, [, count]) => total + count, 0);
99
119
 
100
120
  export const appendProtocolConnectionSample = (
101
121
  localSeriesArg: TProtocolChartSeries,
@@ -104,7 +124,11 @@ export const appendProtocolConnectionSample = (
104
124
  maximumPointsArg: number,
105
125
  ): TProtocolChartSeries => {
106
126
  const localSeriesByName = new Map(localSeriesArg.map((series) => [series.name, series]));
107
- return PROTOCOL_SERIES_ORDER.map((seriesName) => ({
127
+ const seriesOrder = getProtocolSeriesOrder([
128
+ ...localSeriesArg.map((series) => series.name),
129
+ ...Object.keys(protocolsArg),
130
+ ]);
131
+ return seriesOrder.map((seriesName) => ({
108
132
  name: seriesName,
109
133
  data: mergeChartPoints(
110
134
  localSeriesByName.get(seriesName)?.data ?? [],
@@ -123,4 +147,3 @@ export const shouldRestartNetworkActivityTimer = (
123
147
  isConnectedArg: boolean,
124
148
  documentHiddenArg: boolean,
125
149
  ): boolean => isConnectedArg && !documentHiddenArg;
126
-
@@ -0,0 +1,93 @@
1
+ export const NETWORK_ACTIVITY_COLORS = {
2
+ 'Total Active': '#64748b',
3
+ 'HTTP/1.1': '#1976d2',
4
+ 'HTTP/2': '#388e3c',
5
+ 'HTTP/3': '#7b1fa2',
6
+ 'WebSocket': '#f57c00',
7
+ 'Other': '#757575',
8
+ 'Inbound': '#22c55e',
9
+ 'Outbound': '#8b5cf6',
10
+ 'Requests/sec': '#3b82f6',
11
+ } as const;
12
+
13
+ export const NETWORK_ACTIVITY_PROTOCOL_ORDER = [
14
+ 'HTTP/1.1',
15
+ 'HTTP/2',
16
+ 'HTTP/3',
17
+ 'WebSocket',
18
+ 'Other',
19
+ ] as const;
20
+
21
+ export const PROTOCOL_CONNECTION_LEGEND_STATS: Array<'latest'> = ['latest'];
22
+ export const PROTOCOL_CONNECTION_CHART_LINES: Array<'avg' | 'max'> = [];
23
+
24
+ export const NETWORK_ACTIVITY_FALLBACK_COLORS = [
25
+ '#007aff',
26
+ '#32ade6',
27
+ '#af52de',
28
+ '#ff9500',
29
+ '#34c759',
30
+ '#ff2d55',
31
+ ] as const;
32
+
33
+ const knownProtocolNames = new Set<string>(NETWORK_ACTIVITY_PROTOCOL_ORDER);
34
+
35
+ export const getNetworkActivityColor = (
36
+ nameArg: string,
37
+ fallbackIndexArg = 0,
38
+ ): string => {
39
+ const canonicalColor = NETWORK_ACTIVITY_COLORS[
40
+ nameArg as keyof typeof NETWORK_ACTIVITY_COLORS
41
+ ];
42
+ if (canonicalColor) return canonicalColor;
43
+ const fallbackIndex = Math.max(0, Math.trunc(fallbackIndexArg));
44
+ return NETWORK_ACTIVITY_FALLBACK_COLORS[
45
+ fallbackIndex % NETWORK_ACTIVITY_FALLBACK_COLORS.length
46
+ ];
47
+ };
48
+
49
+ export const orderNetworkActivityProtocolNames = (
50
+ namesArg: string[],
51
+ ): string[] => {
52
+ const names = new Set(namesArg.filter((name) => name && name !== 'Total Active'));
53
+ const orderedKnown = NETWORK_ACTIVITY_PROTOCOL_ORDER
54
+ .filter((name) => name !== 'Other' && names.has(name));
55
+ const orderedUnknown = [...names]
56
+ .filter((name) => !knownProtocolNames.has(name))
57
+ .sort((nameA, nameB) => nameA.localeCompare(nameB));
58
+ return [
59
+ ...orderedKnown,
60
+ ...orderedUnknown,
61
+ ...(names.has('Other') ? ['Other'] : []),
62
+ ];
63
+ };
64
+
65
+ export const createNetworkProtocolDonutData = (
66
+ countsArg: Record<string, number>,
67
+ paletteProtocolNamesArg: string[] = [],
68
+ ): Array<{ name: string; value: number; color: string }> => {
69
+ const orderedNames = orderNetworkActivityProtocolNames(Object.keys(countsArg));
70
+ const paletteNames = orderNetworkActivityProtocolNames([
71
+ ...orderedNames,
72
+ ...paletteProtocolNamesArg,
73
+ ]);
74
+ const unknownNames = paletteNames.filter((name) => !knownProtocolNames.has(name));
75
+ return orderedNames
76
+ .filter((name) => (countsArg[name] ?? 0) > 0)
77
+ .map((name) => ({
78
+ name,
79
+ value: countsArg[name] ?? 0,
80
+ color: getNetworkActivityColor(name, unknownNames.indexOf(name)),
81
+ }));
82
+ };
83
+
84
+ export const colorNetworkProtocolSeries = <T extends { name: string }>(
85
+ seriesArg: T[],
86
+ ): Array<T & { color: string }> => {
87
+ const orderedNames = orderNetworkActivityProtocolNames(seriesArg.map((series) => series.name));
88
+ const unknownNames = orderedNames.filter((name) => !knownProtocolNames.has(name));
89
+ return seriesArg.map((series) => ({
90
+ ...series,
91
+ color: getNetworkActivityColor(series.name, unknownNames.indexOf(series.name)),
92
+ }));
93
+ };
@@ -14,14 +14,14 @@ import {
14
14
  type IChartPoint,
15
15
  type TProtocolChartSeries,
16
16
  } from './network-activity-history.js';
17
-
18
- const PROTOCOL_COLORS: Record<string, string> = {
19
- 'HTTP/1.1': '#1976d2',
20
- 'HTTP/2': '#388e3c',
21
- 'HTTP/3': '#7b1fa2',
22
- 'WebSocket': '#f57c00',
23
- 'Other': '#757575',
24
- };
17
+ import {
18
+ NETWORK_ACTIVITY_COLORS,
19
+ PROTOCOL_CONNECTION_CHART_LINES,
20
+ PROTOCOL_CONNECTION_LEGEND_STATS,
21
+ colorNetworkProtocolSeries,
22
+ createNetworkProtocolDonutData,
23
+ getNetworkActivityColor,
24
+ } from './network-activity-visuals.js';
25
25
 
26
26
  declare global {
27
27
  interface HTMLElementTagNameMap {
@@ -181,13 +181,6 @@ export class OpsViewNetworkActivity extends DeesElement {
181
181
  };
182
182
  }
183
183
 
184
- private getProtocolSeriesWithColors(seriesArg: TProtocolChartSeries) {
185
- return seriesArg.map((series) => ({
186
- ...series,
187
- color: series.name === 'Total Active' ? '#ef4444' : (PROTOCOL_COLORS[series.name] ?? '#757575'),
188
- }));
189
- }
190
-
191
184
  private mergeProtocolConnectionHistories(): void {
192
185
  const frontendHistory = this.networkState.frontendConnectionHistory || [];
193
186
  const latestFrontendEpochSecond = getLatestHistoryEpochSecond(frontendHistory);
@@ -239,6 +232,7 @@ export class OpsViewNetworkActivity extends DeesElement {
239
232
  display: flex;
240
233
  flex-direction: column;
241
234
  gap: 24px;
235
+ container-type: inline-size;
242
236
  }
243
237
 
244
238
 
@@ -345,8 +339,9 @@ export class OpsViewNetworkActivity extends DeesElement {
345
339
 
346
340
  .protocolChartGrid {
347
341
  display: grid;
348
- grid-template-columns: 1fr;
342
+ grid-template-columns: repeat(2, minmax(0, 1fr));
349
343
  gap: 16px;
344
+ min-width: 0;
350
345
  }
351
346
 
352
347
  .protocolChartColumn {
@@ -356,6 +351,19 @@ export class OpsViewNetworkActivity extends DeesElement {
356
351
  min-width: 0;
357
352
  }
358
353
 
354
+ .protocolChartColumn > dees-chart-donut,
355
+ .protocolChartColumn > dees-chart-area {
356
+ width: 100%;
357
+ max-width: 100%;
358
+ min-width: 0;
359
+ }
360
+
361
+ @container (max-width: 800px) {
362
+ .protocolChartGrid {
363
+ grid-template-columns: minmax(0, 1fr);
364
+ }
365
+ }
366
+
359
367
  `,
360
368
  ];
361
369
 
@@ -374,12 +382,12 @@ export class OpsViewNetworkActivity extends DeesElement {
374
382
  {
375
383
  name: 'Inbound',
376
384
  data: this.trafficDataIn,
377
- color: '#22c55e',
385
+ color: NETWORK_ACTIVITY_COLORS.Inbound,
378
386
  },
379
387
  {
380
388
  name: 'Outbound',
381
389
  data: this.trafficDataOut,
382
- color: '#8b5cf6',
390
+ color: NETWORK_ACTIVITY_COLORS.Outbound,
383
391
  }
384
392
  ]}
385
393
  .realtimeMode=${true}
@@ -587,7 +595,7 @@ export class OpsViewNetworkActivity extends DeesElement {
587
595
  value: activeConnections,
588
596
  type: 'number',
589
597
  icon: 'lucide:Plug',
590
- color: activeConnections > 100 ? '#f59e0b' : '#22c55e',
598
+ color: NETWORK_ACTIVITY_COLORS['Total Active'],
591
599
  description: `Total: ${this.formatNumber(this.statsState.serverStats?.totalConnections || 0)} connections`,
592
600
  actions: [
593
601
  {
@@ -604,7 +612,7 @@ export class OpsViewNetworkActivity extends DeesElement {
604
612
  value: reqPerSec,
605
613
  type: 'trend',
606
614
  icon: 'lucide:ChartLine',
607
- color: '#3b82f6',
615
+ color: NETWORK_ACTIVITY_COLORS['Requests/sec'],
608
616
  trendData: trendData,
609
617
  description: `Total: ${this.formatNumber(this.networkState.requestsTotal || 0)} requests`,
610
618
  },
@@ -615,7 +623,7 @@ export class OpsViewNetworkActivity extends DeesElement {
615
623
  unit: '',
616
624
  type: 'number',
617
625
  icon: 'lucide:Download',
618
- color: '#22c55e',
626
+ color: NETWORK_ACTIVITY_COLORS.Inbound,
619
627
  description: `Total: ${this.formatBytes(this.networkState.totalBytes?.in || 0)}`,
620
628
  },
621
629
  {
@@ -625,7 +633,7 @@ export class OpsViewNetworkActivity extends DeesElement {
625
633
  unit: '',
626
634
  type: 'number',
627
635
  icon: 'lucide:Upload',
628
- color: '#8b5cf6',
636
+ color: NETWORK_ACTIVITY_COLORS.Outbound,
629
637
  description: `Total: ${this.formatBytes(this.networkState.totalBytes?.out || 0)}`,
630
638
  },
631
639
  ];
@@ -642,28 +650,25 @@ export class OpsViewNetworkActivity extends DeesElement {
642
650
  const fp = this.networkState.frontendProtocols;
643
651
  const bp = this.networkState.backendProtocols;
644
652
 
645
- const protoColors = PROTOCOL_COLORS;
646
-
647
- const buildDonutData = (dist: interfaces.data.IProtocolDistribution | null) => {
648
- if (!dist) return [];
649
- const items: Array<{ name: string; value: number; color: string }> = [];
650
- if (dist.h1Active > 0) items.push({ name: 'HTTP/1.1', value: dist.h1Active, color: protoColors['HTTP/1.1'] });
651
- if (dist.h2Active > 0) items.push({ name: 'HTTP/2', value: dist.h2Active, color: protoColors['HTTP/2'] });
652
- if (dist.h3Active > 0) items.push({ name: 'HTTP/3', value: dist.h3Active, color: protoColors['HTTP/3'] });
653
- if (dist.wsActive > 0) items.push({ name: 'WebSocket', value: dist.wsActive, color: protoColors['WebSocket'] });
654
- if (dist.otherActive > 0) items.push({ name: 'Other', value: dist.otherActive, color: protoColors['Other'] });
655
- return items;
656
- };
653
+ const buildDonutData = (
654
+ dist: interfaces.data.IProtocolDistribution | null,
655
+ historySeries: TProtocolChartSeries,
656
+ ) => dist
657
+ ? createNetworkProtocolDonutData(
658
+ this.getProtocolCounts(dist),
659
+ historySeries.map((series) => series.name),
660
+ )
661
+ : [];
657
662
 
658
- const frontendData = buildDonutData(fp);
659
- const backendData = buildDonutData(bp);
663
+ const frontendData = buildDonutData(fp, this.frontendConnectionSeries);
664
+ const backendData = buildDonutData(bp, this.backendConnectionSeries);
660
665
 
661
666
  return html`
662
667
  <div class="protocolChartGrid">
663
668
  <div class="protocolChartColumn">
664
669
  <dees-chart-donut
665
670
  .label=${'Frontend Protocols'}
666
- .data=${frontendData.length > 0 ? frontendData : [{ name: 'No Traffic', value: 1, color: '#757575' }]}
671
+ .data=${frontendData.length > 0 ? frontendData : [{ name: 'No Traffic', value: 1, color: getNetworkActivityColor('Other') }]}
667
672
  .showLegend=${true}
668
673
  .showLabels=${true}
669
674
  .innerRadiusPercent=${'55%'}
@@ -671,7 +676,9 @@ export class OpsViewNetworkActivity extends DeesElement {
671
676
  ></dees-chart-donut>
672
677
  <dees-chart-area
673
678
  .label=${'Frontend Active Connections'}
674
- .series=${this.getProtocolSeriesWithColors(this.frontendConnectionSeries)}
679
+ .series=${colorNetworkProtocolSeries(this.frontendConnectionSeries)}
680
+ .legendStats=${PROTOCOL_CONNECTION_LEGEND_STATS}
681
+ .chartLines=${PROTOCOL_CONNECTION_CHART_LINES}
675
682
  .realtimeMode=${true}
676
683
  .rollingWindow=${OpsViewNetworkActivity.CHART_WINDOW_MS}
677
684
  .yAxisFormatter=${(val: number) => `${Math.round(val)} active`}
@@ -680,7 +687,7 @@ export class OpsViewNetworkActivity extends DeesElement {
680
687
  <div class="protocolChartColumn">
681
688
  <dees-chart-donut
682
689
  .label=${'Backend Protocols'}
683
- .data=${backendData.length > 0 ? backendData : [{ name: 'No Traffic', value: 1, color: '#757575' }]}
690
+ .data=${backendData.length > 0 ? backendData : [{ name: 'No Traffic', value: 1, color: getNetworkActivityColor('Other') }]}
684
691
  .showLegend=${true}
685
692
  .showLabels=${true}
686
693
  .innerRadiusPercent=${'55%'}
@@ -688,7 +695,9 @@ export class OpsViewNetworkActivity extends DeesElement {
688
695
  ></dees-chart-donut>
689
696
  <dees-chart-area
690
697
  .label=${'Backend Active Connections'}
691
- .series=${this.getProtocolSeriesWithColors(this.backendConnectionSeries)}
698
+ .series=${colorNetworkProtocolSeries(this.backendConnectionSeries)}
699
+ .legendStats=${PROTOCOL_CONNECTION_LEGEND_STATS}
700
+ .chartLines=${PROTOCOL_CONNECTION_CHART_LINES}
692
701
  .realtimeMode=${true}
693
702
  .rollingWindow=${OpsViewNetworkActivity.CHART_WINDOW_MS}
694
703
  .yAxisFormatter=${(val: number) => `${Math.round(val)} active`}