@serve.zone/dcrouter 13.13.0 → 13.14.0
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 +811 -803
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/monitoring/classes.metricsmanager.d.ts +26 -0
- package/dist_ts/monitoring/classes.metricsmanager.js +72 -2
- package/dist_ts/opsserver/handlers/security.handler.js +27 -23
- package/dist_ts/opsserver/handlers/stats.handler.js +22 -3
- package/dist_ts_interfaces/data/stats.d.ts +17 -1
- package/dist_ts_oci_container/plugins.d.ts +3 -0
- package/dist_ts_oci_container/plugins.js +4 -0
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.d.ts +7 -0
- package/dist_ts_web/appstate.js +45 -54
- package/dist_ts_web/elements/domains/ops-view-domains.js +9 -11
- package/dist_ts_web/elements/network/ops-view-network-activity.d.ts +2 -20
- package/dist_ts_web/elements/network/ops-view-network-activity.js +65 -115
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/monitoring/classes.metricsmanager.ts +77 -1
- package/ts/opsserver/handlers/security.handler.ts +27 -23
- package/ts/opsserver/handlers/stats.handler.ts +22 -2
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +48 -57
- package/ts_web/elements/domains/ops-view-domains.ts +8 -10
- package/ts_web/elements/network/ops-view-network-activity.ts +67 -132
|
@@ -553,12 +553,14 @@ export class MetricsManager {
|
|
|
553
553
|
connectionsByIP: new Map<string, number>(),
|
|
554
554
|
throughputRate: { bytesInPerSecond: 0, bytesOutPerSecond: 0 },
|
|
555
555
|
topIPs: [] as Array<{ ip: string; count: number }>,
|
|
556
|
+
topIPsByBandwidth: [] as Array<{ ip: string; count: number; bwIn: number; bwOut: number }>,
|
|
556
557
|
totalDataTransferred: { bytesIn: 0, bytesOut: 0 },
|
|
557
558
|
throughputHistory: [] as Array<{ timestamp: number; in: number; out: number }>,
|
|
558
559
|
throughputByIP: new Map<string, { in: number; out: number }>(),
|
|
559
560
|
requestsPerSecond: 0,
|
|
560
561
|
requestsTotal: 0,
|
|
561
562
|
backends: [] as Array<any>,
|
|
563
|
+
domainActivity: [] as Array<{ domain: string; bytesInPerSecond: number; bytesOutPerSecond: number; activeConnections: number; routeCount: number }>,
|
|
562
564
|
};
|
|
563
565
|
}
|
|
564
566
|
|
|
@@ -572,7 +574,7 @@ export class MetricsManager {
|
|
|
572
574
|
bytesOutPerSecond: instantThroughput.out
|
|
573
575
|
};
|
|
574
576
|
|
|
575
|
-
// Get top IPs
|
|
577
|
+
// Get top IPs by connection count
|
|
576
578
|
const topIPs = proxyMetrics.connections.topIPs(10);
|
|
577
579
|
|
|
578
580
|
// Get total data transferred
|
|
@@ -699,10 +701,83 @@ export class MetricsManager {
|
|
|
699
701
|
}
|
|
700
702
|
}
|
|
701
703
|
|
|
704
|
+
// Build top 10 IPs by bandwidth (sorted by total throughput desc)
|
|
705
|
+
const allIPData = new Map<string, { count: number; bwIn: number; bwOut: number }>();
|
|
706
|
+
for (const [ip, count] of connectionsByIP) {
|
|
707
|
+
allIPData.set(ip, { count, bwIn: 0, bwOut: 0 });
|
|
708
|
+
}
|
|
709
|
+
for (const [ip, tp] of throughputByIP) {
|
|
710
|
+
const existing = allIPData.get(ip);
|
|
711
|
+
if (existing) {
|
|
712
|
+
existing.bwIn = tp.in;
|
|
713
|
+
existing.bwOut = tp.out;
|
|
714
|
+
} else {
|
|
715
|
+
allIPData.set(ip, { count: 0, bwIn: tp.in, bwOut: tp.out });
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
const topIPsByBandwidth = Array.from(allIPData.entries())
|
|
719
|
+
.sort((a, b) => (b[1].bwIn + b[1].bwOut) - (a[1].bwIn + a[1].bwOut))
|
|
720
|
+
.slice(0, 10)
|
|
721
|
+
.map(([ip, data]) => ({ ip, count: data.count, bwIn: data.bwIn, bwOut: data.bwOut }));
|
|
722
|
+
|
|
723
|
+
// Build domain activity from per-route metrics
|
|
724
|
+
const connectionsByRoute = proxyMetrics.connections.byRoute();
|
|
725
|
+
const throughputByRoute = proxyMetrics.throughput.byRoute();
|
|
726
|
+
|
|
727
|
+
// Map route name → primary domain using dcrouter's route configs
|
|
728
|
+
const routeToDomain = new Map<string, string>();
|
|
729
|
+
if (this.dcRouter.smartProxy) {
|
|
730
|
+
for (const route of this.dcRouter.smartProxy.routeManager.getRoutes()) {
|
|
731
|
+
if (!route.name || !route.match.domains) continue;
|
|
732
|
+
const domains = Array.isArray(route.match.domains)
|
|
733
|
+
? route.match.domains
|
|
734
|
+
: [route.match.domains];
|
|
735
|
+
if (domains.length > 0) {
|
|
736
|
+
routeToDomain.set(route.name, domains[0]);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Aggregate metrics by domain
|
|
742
|
+
const domainAgg = new Map<string, {
|
|
743
|
+
activeConnections: number;
|
|
744
|
+
bytesInPerSec: number;
|
|
745
|
+
bytesOutPerSec: number;
|
|
746
|
+
routeCount: number;
|
|
747
|
+
}>();
|
|
748
|
+
for (const [routeName, activeConns] of connectionsByRoute) {
|
|
749
|
+
const domain = routeToDomain.get(routeName) || routeName;
|
|
750
|
+
const tp = throughputByRoute.get(routeName) || { in: 0, out: 0 };
|
|
751
|
+
const existing = domainAgg.get(domain);
|
|
752
|
+
if (existing) {
|
|
753
|
+
existing.activeConnections += activeConns;
|
|
754
|
+
existing.bytesInPerSec += tp.in;
|
|
755
|
+
existing.bytesOutPerSec += tp.out;
|
|
756
|
+
existing.routeCount++;
|
|
757
|
+
} else {
|
|
758
|
+
domainAgg.set(domain, {
|
|
759
|
+
activeConnections: activeConns,
|
|
760
|
+
bytesInPerSec: tp.in,
|
|
761
|
+
bytesOutPerSec: tp.out,
|
|
762
|
+
routeCount: 1,
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
const domainActivity = Array.from(domainAgg.entries())
|
|
767
|
+
.map(([domain, data]) => ({
|
|
768
|
+
domain,
|
|
769
|
+
bytesInPerSecond: data.bytesInPerSec,
|
|
770
|
+
bytesOutPerSecond: data.bytesOutPerSec,
|
|
771
|
+
activeConnections: data.activeConnections,
|
|
772
|
+
routeCount: data.routeCount,
|
|
773
|
+
}))
|
|
774
|
+
.sort((a, b) => (b.bytesInPerSecond + b.bytesOutPerSecond) - (a.bytesInPerSecond + a.bytesOutPerSecond));
|
|
775
|
+
|
|
702
776
|
return {
|
|
703
777
|
connectionsByIP,
|
|
704
778
|
throughputRate,
|
|
705
779
|
topIPs,
|
|
780
|
+
topIPsByBandwidth,
|
|
706
781
|
totalDataTransferred,
|
|
707
782
|
throughputHistory,
|
|
708
783
|
throughputByIP,
|
|
@@ -711,6 +786,7 @@ export class MetricsManager {
|
|
|
711
786
|
backends,
|
|
712
787
|
frontendProtocols,
|
|
713
788
|
backendProtocols,
|
|
789
|
+
domainActivity,
|
|
714
790
|
};
|
|
715
791
|
}, 1000); // 1s cache — matches typical dashboard poll interval
|
|
716
792
|
}
|
|
@@ -51,8 +51,8 @@ export class SecurityHandler {
|
|
|
51
51
|
startTime: conn.startTime,
|
|
52
52
|
protocol: conn.type === 'http' ? 'https' : conn.type as any,
|
|
53
53
|
state: conn.status as any,
|
|
54
|
-
bytesReceived:
|
|
55
|
-
bytesSent:
|
|
54
|
+
bytesReceived: (conn as any)._throughputIn || 0,
|
|
55
|
+
bytesSent: (conn as any)._throughputOut || 0,
|
|
56
56
|
}));
|
|
57
57
|
|
|
58
58
|
const summary = {
|
|
@@ -96,9 +96,11 @@ export class SecurityHandler {
|
|
|
96
96
|
connectionsByIP: Array.from(networkStats.connectionsByIP.entries()).map(([ip, count]) => ({ ip, count })),
|
|
97
97
|
throughputRate: networkStats.throughputRate,
|
|
98
98
|
topIPs: networkStats.topIPs,
|
|
99
|
+
topIPsByBandwidth: networkStats.topIPsByBandwidth,
|
|
99
100
|
totalDataTransferred: networkStats.totalDataTransferred,
|
|
100
101
|
throughputHistory: networkStats.throughputHistory || [],
|
|
101
102
|
throughputByIP,
|
|
103
|
+
domainActivity: networkStats.domainActivity || [],
|
|
102
104
|
requestsPerSecond: networkStats.requestsPerSecond || 0,
|
|
103
105
|
requestsTotal: networkStats.requestsTotal || 0,
|
|
104
106
|
backends: networkStats.backends || [],
|
|
@@ -110,9 +112,11 @@ export class SecurityHandler {
|
|
|
110
112
|
connectionsByIP: [],
|
|
111
113
|
throughputRate: { bytesInPerSecond: 0, bytesOutPerSecond: 0 },
|
|
112
114
|
topIPs: [],
|
|
115
|
+
topIPsByBandwidth: [],
|
|
113
116
|
totalDataTransferred: { bytesIn: 0, bytesOut: 0 },
|
|
114
117
|
throughputHistory: [],
|
|
115
118
|
throughputByIP: [],
|
|
119
|
+
domainActivity: [],
|
|
116
120
|
requestsPerSecond: 0,
|
|
117
121
|
requestsTotal: 0,
|
|
118
122
|
backends: [],
|
|
@@ -251,31 +255,31 @@ export class SecurityHandler {
|
|
|
251
255
|
const connectionInfo = await this.opsServerRef.dcRouterRef.metricsManager.getConnectionInfo();
|
|
252
256
|
const networkStats = await this.opsServerRef.dcRouterRef.metricsManager.getNetworkStats();
|
|
253
257
|
|
|
254
|
-
//
|
|
258
|
+
// One aggregate row per IP with real throughput data
|
|
255
259
|
if (networkStats.connectionsByIP && networkStats.connectionsByIP.size > 0) {
|
|
256
260
|
let connIndex = 0;
|
|
257
261
|
const publicIp = this.opsServerRef.dcRouterRef.options.publicIp || 'server';
|
|
258
|
-
|
|
262
|
+
|
|
259
263
|
for (const [ip, count] of networkStats.connectionsByIP) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
})
|
|
278
|
-
}
|
|
264
|
+
const tp = networkStats.throughputByIP?.get(ip);
|
|
265
|
+
connections.push({
|
|
266
|
+
id: `ip-${connIndex++}`,
|
|
267
|
+
type: 'http',
|
|
268
|
+
source: {
|
|
269
|
+
ip: ip,
|
|
270
|
+
port: 0,
|
|
271
|
+
},
|
|
272
|
+
destination: {
|
|
273
|
+
ip: publicIp,
|
|
274
|
+
port: 443,
|
|
275
|
+
service: 'proxy',
|
|
276
|
+
},
|
|
277
|
+
startTime: 0,
|
|
278
|
+
bytesTransferred: count, // Store connection count here
|
|
279
|
+
status: 'active',
|
|
280
|
+
// Attach real throughput for the handler mapping
|
|
281
|
+
...(tp ? { _throughputIn: tp.in, _throughputOut: tp.out } : {}),
|
|
282
|
+
} as any);
|
|
279
283
|
}
|
|
280
284
|
} else if (connectionInfo.length > 0) {
|
|
281
285
|
// Fallback to route-based connection info if no IP data available
|
|
@@ -291,6 +291,20 @@ export class StatsHandler {
|
|
|
291
291
|
}
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
// Build connectionDetails from real per-IP data
|
|
295
|
+
const connectionDetails: interfaces.data.IConnectionDetails[] = [];
|
|
296
|
+
for (const [ip, count] of stats.connectionsByIP) {
|
|
297
|
+
const tp = stats.throughputByIP?.get(ip);
|
|
298
|
+
connectionDetails.push({
|
|
299
|
+
remoteAddress: ip,
|
|
300
|
+
protocol: 'https',
|
|
301
|
+
state: 'connected',
|
|
302
|
+
startTime: 0,
|
|
303
|
+
bytesIn: tp?.in || 0,
|
|
304
|
+
bytesOut: tp?.out || 0,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
294
308
|
metrics.network = {
|
|
295
309
|
totalBandwidth: {
|
|
296
310
|
in: stats.throughputRate.bytesInPerSecond,
|
|
@@ -301,12 +315,18 @@ export class StatsHandler {
|
|
|
301
315
|
out: stats.totalDataTransferred.bytesOut,
|
|
302
316
|
},
|
|
303
317
|
activeConnections: serverStats.activeConnections,
|
|
304
|
-
connectionDetails
|
|
318
|
+
connectionDetails,
|
|
305
319
|
topEndpoints: stats.topIPs.map(ip => ({
|
|
306
320
|
endpoint: ip.ip,
|
|
307
|
-
|
|
321
|
+
connections: ip.count,
|
|
308
322
|
bandwidth: ipBandwidth.get(ip.ip) || { in: 0, out: 0 },
|
|
309
323
|
})),
|
|
324
|
+
topEndpointsByBandwidth: stats.topIPsByBandwidth.map(ip => ({
|
|
325
|
+
endpoint: ip.ip,
|
|
326
|
+
connections: ip.count,
|
|
327
|
+
bandwidth: { in: ip.bwIn, out: ip.bwOut },
|
|
328
|
+
})),
|
|
329
|
+
domainActivity: stats.domainActivity || [],
|
|
310
330
|
throughputHistory: stats.throughputHistory || [],
|
|
311
331
|
requestsPerSecond: stats.requestsPerSecond || 0,
|
|
312
332
|
requestsTotal: stats.requestsTotal || 0,
|
package/ts_web/appstate.ts
CHANGED
|
@@ -52,7 +52,9 @@ export interface INetworkState {
|
|
|
52
52
|
throughputRate: { bytesInPerSecond: number; bytesOutPerSecond: number };
|
|
53
53
|
totalBytes: { in: number; out: number };
|
|
54
54
|
topIPs: Array<{ ip: string; count: number }>;
|
|
55
|
+
topIPsByBandwidth: Array<{ ip: string; count: number; bwIn: number; bwOut: number }>;
|
|
55
56
|
throughputByIP: Array<{ ip: string; in: number; out: number }>;
|
|
57
|
+
domainActivity: interfaces.data.IDomainActivity[];
|
|
56
58
|
throughputHistory: Array<{ timestamp: number; in: number; out: number }>;
|
|
57
59
|
requestsPerSecond: number;
|
|
58
60
|
requestsTotal: number;
|
|
@@ -160,7 +162,9 @@ export const networkStatePart = await appState.getStatePart<INetworkState>(
|
|
|
160
162
|
throughputRate: { bytesInPerSecond: 0, bytesOutPerSecond: 0 },
|
|
161
163
|
totalBytes: { in: 0, out: 0 },
|
|
162
164
|
topIPs: [],
|
|
165
|
+
topIPsByBandwidth: [],
|
|
163
166
|
throughputByIP: [],
|
|
167
|
+
domainActivity: [],
|
|
164
168
|
throughputHistory: [],
|
|
165
169
|
requestsPerSecond: 0,
|
|
166
170
|
requestsTotal: 0,
|
|
@@ -552,7 +556,9 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat
|
|
|
552
556
|
? { in: networkStatsResponse.totalDataTransferred.bytesIn, out: networkStatsResponse.totalDataTransferred.bytesOut }
|
|
553
557
|
: { in: 0, out: 0 },
|
|
554
558
|
topIPs: networkStatsResponse.topIPs || [],
|
|
559
|
+
topIPsByBandwidth: networkStatsResponse.topIPsByBandwidth || [],
|
|
555
560
|
throughputByIP: networkStatsResponse.throughputByIP || [],
|
|
561
|
+
domainActivity: networkStatsResponse.domainActivity || [],
|
|
556
562
|
throughputHistory: networkStatsResponse.throughputHistory || [],
|
|
557
563
|
requestsPerSecond: networkStatsResponse.requestsPerSecond || 0,
|
|
558
564
|
requestsTotal: networkStatsResponse.requestsTotal || 0,
|
|
@@ -2649,67 +2655,52 @@ async function dispatchCombinedRefreshActionInner() {
|
|
|
2649
2655
|
if (combinedResponse.metrics.network && currentView === 'network') {
|
|
2650
2656
|
const network = combinedResponse.metrics.network;
|
|
2651
2657
|
const connectionsByIP: { [ip: string]: number } = {};
|
|
2652
|
-
|
|
2653
|
-
//
|
|
2658
|
+
|
|
2659
|
+
// Build connectionsByIP from connectionDetails (now populated with real per-IP data)
|
|
2654
2660
|
network.connectionDetails.forEach(conn => {
|
|
2655
2661
|
connectionsByIP[conn.remoteAddress] = (connectionsByIP[conn.remoteAddress] || 0) + 1;
|
|
2656
2662
|
});
|
|
2657
2663
|
|
|
2658
|
-
//
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
},
|
|
2699
|
-
totalBytes: network.totalBytes || { in: 0, out: 0 },
|
|
2700
|
-
topIPs: network.topEndpoints.map(e => ({ ip: e.endpoint, count: e.requests })),
|
|
2701
|
-
throughputByIP: network.topEndpoints.map(e => ({ ip: e.endpoint, in: e.bandwidth?.in || 0, out: e.bandwidth?.out || 0 })),
|
|
2702
|
-
throughputHistory: network.throughputHistory || [],
|
|
2703
|
-
requestsPerSecond: network.requestsPerSecond || 0,
|
|
2704
|
-
requestsTotal: network.requestsTotal || 0,
|
|
2705
|
-
backends: network.backends || [],
|
|
2706
|
-
frontendProtocols: network.frontendProtocols || null,
|
|
2707
|
-
backendProtocols: network.backendProtocols || null,
|
|
2708
|
-
lastUpdated: Date.now(),
|
|
2709
|
-
isLoading: false,
|
|
2710
|
-
error: null,
|
|
2711
|
-
});
|
|
2712
|
-
}
|
|
2664
|
+
// Build connections from connectionDetails (real per-IP aggregates)
|
|
2665
|
+
const connections: interfaces.data.IConnectionInfo[] = network.connectionDetails.map((conn, i) => ({
|
|
2666
|
+
id: `ip-${conn.remoteAddress}`,
|
|
2667
|
+
remoteAddress: conn.remoteAddress,
|
|
2668
|
+
localAddress: 'server',
|
|
2669
|
+
startTime: conn.startTime,
|
|
2670
|
+
protocol: conn.protocol as any,
|
|
2671
|
+
state: conn.state as any,
|
|
2672
|
+
bytesReceived: conn.bytesIn,
|
|
2673
|
+
bytesSent: conn.bytesOut,
|
|
2674
|
+
}));
|
|
2675
|
+
|
|
2676
|
+
networkStatePart.setState({
|
|
2677
|
+
...networkStatePart.getState()!,
|
|
2678
|
+
connections,
|
|
2679
|
+
connectionsByIP,
|
|
2680
|
+
throughputRate: {
|
|
2681
|
+
bytesInPerSecond: network.totalBandwidth.in,
|
|
2682
|
+
bytesOutPerSecond: network.totalBandwidth.out,
|
|
2683
|
+
},
|
|
2684
|
+
totalBytes: network.totalBytes || { in: 0, out: 0 },
|
|
2685
|
+
topIPs: network.topEndpoints.map(e => ({ ip: e.endpoint, count: e.connections })),
|
|
2686
|
+
topIPsByBandwidth: (network.topEndpointsByBandwidth || []).map(e => ({
|
|
2687
|
+
ip: e.endpoint,
|
|
2688
|
+
count: e.connections,
|
|
2689
|
+
bwIn: e.bandwidth?.in || 0,
|
|
2690
|
+
bwOut: e.bandwidth?.out || 0,
|
|
2691
|
+
})),
|
|
2692
|
+
throughputByIP: network.topEndpoints.map(e => ({ ip: e.endpoint, in: e.bandwidth?.in || 0, out: e.bandwidth?.out || 0 })),
|
|
2693
|
+
domainActivity: network.domainActivity || [],
|
|
2694
|
+
throughputHistory: network.throughputHistory || [],
|
|
2695
|
+
requestsPerSecond: network.requestsPerSecond || 0,
|
|
2696
|
+
requestsTotal: network.requestsTotal || 0,
|
|
2697
|
+
backends: network.backends || [],
|
|
2698
|
+
frontendProtocols: network.frontendProtocols || null,
|
|
2699
|
+
backendProtocols: network.backendProtocols || null,
|
|
2700
|
+
lastUpdated: Date.now(),
|
|
2701
|
+
isLoading: false,
|
|
2702
|
+
error: null,
|
|
2703
|
+
});
|
|
2713
2704
|
}
|
|
2714
2705
|
|
|
2715
2706
|
// Refresh certificate data if on Domains > Certificates subview
|
|
@@ -323,16 +323,14 @@ export class OpsViewDomains extends DeesElement {
|
|
|
323
323
|
|
|
324
324
|
// Build target options based on current source
|
|
325
325
|
const targetOptions: { option: string; key: string }[] = [];
|
|
326
|
-
if (domain.source === 'provider') {
|
|
327
|
-
targetOptions.push({ option: 'DcRouter (authoritative)', key: 'dcrouter' });
|
|
328
|
-
}
|
|
329
|
-
// Add all providers (except the current one if already provider-managed)
|
|
330
326
|
for (const p of providers) {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
327
|
+
// Skip current source
|
|
328
|
+
if (p.builtIn && domain.source === 'dcrouter') continue;
|
|
329
|
+
if (!p.builtIn && domain.source === 'provider' && domain.providerId === p.id) continue;
|
|
330
|
+
|
|
331
|
+
const label = p.builtIn ? 'DcRouter (self)' : `${p.name} (${p.type})`;
|
|
332
|
+
const key = p.builtIn ? 'dcrouter' : `provider:${p.id}`;
|
|
333
|
+
targetOptions.push({ option: label, key });
|
|
336
334
|
}
|
|
337
335
|
|
|
338
336
|
if (targetOptions.length === 0) {
|
|
@@ -345,7 +343,7 @@ export class OpsViewDomains extends DeesElement {
|
|
|
345
343
|
}
|
|
346
344
|
|
|
347
345
|
const currentLabel = domain.source === 'dcrouter'
|
|
348
|
-
? 'DcRouter (
|
|
346
|
+
? 'DcRouter (self)'
|
|
349
347
|
: providers.find((p) => p.id === domain.providerId)?.name || 'Provider';
|
|
350
348
|
|
|
351
349
|
DeesModal.createAndShow({
|
|
@@ -10,22 +10,6 @@ declare global {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
interface INetworkRequest {
|
|
14
|
-
id: string;
|
|
15
|
-
timestamp: number;
|
|
16
|
-
method: string;
|
|
17
|
-
url: string;
|
|
18
|
-
hostname: string;
|
|
19
|
-
port: number;
|
|
20
|
-
protocol: 'http' | 'https' | 'tcp' | 'udp';
|
|
21
|
-
statusCode?: number;
|
|
22
|
-
duration: number;
|
|
23
|
-
bytesIn: number;
|
|
24
|
-
bytesOut: number;
|
|
25
|
-
remoteIp: string;
|
|
26
|
-
route?: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
13
|
@customElement('ops-view-network-activity')
|
|
30
14
|
export class OpsViewNetworkActivity extends DeesElement {
|
|
31
15
|
/** How far back the traffic chart shows */
|
|
@@ -42,9 +26,6 @@ export class OpsViewNetworkActivity extends DeesElement {
|
|
|
42
26
|
accessor networkState = appstate.networkStatePart.getState()!;
|
|
43
27
|
|
|
44
28
|
|
|
45
|
-
@state()
|
|
46
|
-
accessor networkRequests: INetworkRequest[] = [];
|
|
47
|
-
|
|
48
29
|
@state()
|
|
49
30
|
accessor trafficDataIn: Array<{ x: string | number; y: number }> = [];
|
|
50
31
|
|
|
@@ -314,108 +295,21 @@ export class OpsViewNetworkActivity extends DeesElement {
|
|
|
314
295
|
<!-- Protocol Distribution Charts -->
|
|
315
296
|
${this.renderProtocolCharts()}
|
|
316
297
|
|
|
317
|
-
<!-- Top IPs
|
|
298
|
+
<!-- Top IPs by Connection Count -->
|
|
318
299
|
${this.renderTopIPs()}
|
|
319
300
|
|
|
301
|
+
<!-- Top IPs by Bandwidth -->
|
|
302
|
+
${this.renderTopIPsByBandwidth()}
|
|
303
|
+
|
|
304
|
+
<!-- Domain Activity -->
|
|
305
|
+
${this.renderDomainActivity()}
|
|
306
|
+
|
|
320
307
|
<!-- Backend Protocols Section -->
|
|
321
308
|
${this.renderBackendProtocols()}
|
|
322
|
-
|
|
323
|
-
<!-- Requests Table -->
|
|
324
|
-
<dees-table
|
|
325
|
-
.data=${this.networkRequests}
|
|
326
|
-
.rowKey=${'id'}
|
|
327
|
-
.highlightUpdates=${'flash'}
|
|
328
|
-
.displayFunction=${(req: INetworkRequest) => ({
|
|
329
|
-
Time: new Date(req.timestamp).toLocaleTimeString(),
|
|
330
|
-
Protocol: html`<span class="protocolBadge ${req.protocol}">${req.protocol.toUpperCase()}</span>`,
|
|
331
|
-
Method: req.method,
|
|
332
|
-
'Host:Port': `${req.hostname}:${req.port}`,
|
|
333
|
-
Path: this.truncateUrl(req.url),
|
|
334
|
-
Status: this.renderStatus(req.statusCode),
|
|
335
|
-
Duration: `${req.duration}ms`,
|
|
336
|
-
'In/Out': `${this.formatBytes(req.bytesIn)} / ${this.formatBytes(req.bytesOut)}`,
|
|
337
|
-
'Remote IP': req.remoteIp,
|
|
338
|
-
})}
|
|
339
|
-
.dataActions=${[
|
|
340
|
-
{
|
|
341
|
-
name: 'View Details',
|
|
342
|
-
iconName: 'fa:magnifyingGlass',
|
|
343
|
-
type: ['inRow', 'doubleClick', 'contextmenu'],
|
|
344
|
-
actionFunc: async (actionData) => {
|
|
345
|
-
await this.showRequestDetails(actionData.item);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
]}
|
|
349
|
-
heading1="Recent Network Activity"
|
|
350
|
-
heading2="Recent network requests"
|
|
351
|
-
searchable
|
|
352
|
-
.showColumnFilters=${true}
|
|
353
|
-
.pagination=${true}
|
|
354
|
-
.paginationSize=${50}
|
|
355
|
-
dataName="request"
|
|
356
|
-
></dees-table>
|
|
357
309
|
</div>
|
|
358
310
|
`;
|
|
359
311
|
}
|
|
360
312
|
|
|
361
|
-
private async showRequestDetails(request: INetworkRequest) {
|
|
362
|
-
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
363
|
-
|
|
364
|
-
await DeesModal.createAndShow({
|
|
365
|
-
heading: 'Request Details',
|
|
366
|
-
content: html`
|
|
367
|
-
<div style="padding: 20px;">
|
|
368
|
-
<dees-dataview-codebox
|
|
369
|
-
.heading=${'Request Information'}
|
|
370
|
-
progLang="json"
|
|
371
|
-
.codeToDisplay=${JSON.stringify({
|
|
372
|
-
id: request.id,
|
|
373
|
-
timestamp: new Date(request.timestamp).toISOString(),
|
|
374
|
-
protocol: request.protocol,
|
|
375
|
-
method: request.method,
|
|
376
|
-
url: request.url,
|
|
377
|
-
hostname: request.hostname,
|
|
378
|
-
port: request.port,
|
|
379
|
-
statusCode: request.statusCode,
|
|
380
|
-
duration: `${request.duration}ms`,
|
|
381
|
-
bytesIn: request.bytesIn,
|
|
382
|
-
bytesOut: request.bytesOut,
|
|
383
|
-
remoteIp: request.remoteIp,
|
|
384
|
-
route: request.route,
|
|
385
|
-
}, null, 2)}
|
|
386
|
-
></dees-dataview-codebox>
|
|
387
|
-
</div>
|
|
388
|
-
`,
|
|
389
|
-
menuOptions: [
|
|
390
|
-
{
|
|
391
|
-
name: 'Copy Request ID',
|
|
392
|
-
iconName: 'lucide:Copy',
|
|
393
|
-
action: async () => {
|
|
394
|
-
await navigator.clipboard.writeText(request.id);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
]
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
private renderStatus(statusCode?: number): TemplateResult {
|
|
403
|
-
if (!statusCode) {
|
|
404
|
-
return html`<span class="statusBadge warning">N/A</span>`;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
const statusClass = statusCode >= 200 && statusCode < 300 ? 'success' :
|
|
408
|
-
statusCode >= 400 ? 'error' : 'warning';
|
|
409
|
-
|
|
410
|
-
return html`<span class="statusBadge ${statusClass}">${statusCode}</span>`;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
private truncateUrl(url: string, maxLength = 50): string {
|
|
414
|
-
if (url.length <= maxLength) return url;
|
|
415
|
-
return url.substring(0, maxLength - 3) + '...';
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
|
|
419
313
|
private formatNumber(num: number): string {
|
|
420
314
|
if (num >= 1000000) {
|
|
421
315
|
return (num / 1000000).toFixed(1) + 'M';
|
|
@@ -619,6 +513,66 @@ export class OpsViewNetworkActivity extends DeesElement {
|
|
|
619
513
|
`;
|
|
620
514
|
}
|
|
621
515
|
|
|
516
|
+
private renderTopIPsByBandwidth(): TemplateResult {
|
|
517
|
+
if (!this.networkState.topIPsByBandwidth || this.networkState.topIPsByBandwidth.length === 0) {
|
|
518
|
+
return html``;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return html`
|
|
522
|
+
<dees-table
|
|
523
|
+
.data=${this.networkState.topIPsByBandwidth}
|
|
524
|
+
.rowKey=${'ip'}
|
|
525
|
+
.highlightUpdates=${'flash'}
|
|
526
|
+
.displayFunction=${(ipData: { ip: string; count: number; bwIn: number; bwOut: number }) => {
|
|
527
|
+
return {
|
|
528
|
+
'IP Address': ipData.ip,
|
|
529
|
+
'Bandwidth In': this.formatBitsPerSecond(ipData.bwIn),
|
|
530
|
+
'Bandwidth Out': this.formatBitsPerSecond(ipData.bwOut),
|
|
531
|
+
'Total Bandwidth': this.formatBitsPerSecond(ipData.bwIn + ipData.bwOut),
|
|
532
|
+
'Connections': ipData.count,
|
|
533
|
+
};
|
|
534
|
+
}}
|
|
535
|
+
heading1="Top IPs by Bandwidth"
|
|
536
|
+
heading2="IPs with highest throughput"
|
|
537
|
+
searchable
|
|
538
|
+
.showColumnFilters=${true}
|
|
539
|
+
.pagination=${false}
|
|
540
|
+
dataName="ip"
|
|
541
|
+
></dees-table>
|
|
542
|
+
`;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
private renderDomainActivity(): TemplateResult {
|
|
546
|
+
if (!this.networkState.domainActivity || this.networkState.domainActivity.length === 0) {
|
|
547
|
+
return html``;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return html`
|
|
551
|
+
<dees-table
|
|
552
|
+
.data=${this.networkState.domainActivity}
|
|
553
|
+
.rowKey=${'domain'}
|
|
554
|
+
.highlightUpdates=${'flash'}
|
|
555
|
+
.displayFunction=${(item: interfaces.data.IDomainActivity) => {
|
|
556
|
+
const totalBytesPerMin = (item.bytesInPerSecond + item.bytesOutPerSecond) * 60;
|
|
557
|
+
return {
|
|
558
|
+
'Domain': item.domain,
|
|
559
|
+
'Throughput In': this.formatBitsPerSecond(item.bytesInPerSecond),
|
|
560
|
+
'Throughput Out': this.formatBitsPerSecond(item.bytesOutPerSecond),
|
|
561
|
+
'Transferred / min': this.formatBytes(totalBytesPerMin),
|
|
562
|
+
'Connections': item.activeConnections,
|
|
563
|
+
'Routes': item.routeCount,
|
|
564
|
+
};
|
|
565
|
+
}}
|
|
566
|
+
heading1="Domain Activity"
|
|
567
|
+
heading2="Per-domain network activity aggregated from route metrics"
|
|
568
|
+
searchable
|
|
569
|
+
.showColumnFilters=${true}
|
|
570
|
+
.pagination=${false}
|
|
571
|
+
dataName="domain"
|
|
572
|
+
></dees-table>
|
|
573
|
+
`;
|
|
574
|
+
}
|
|
575
|
+
|
|
622
576
|
private renderBackendProtocols(): TemplateResult {
|
|
623
577
|
const backends = this.networkState.backends;
|
|
624
578
|
if (!backends || backends.length === 0) {
|
|
@@ -730,25 +684,6 @@ export class OpsViewNetworkActivity extends DeesElement {
|
|
|
730
684
|
this.requestsPerSecHistory.shift();
|
|
731
685
|
}
|
|
732
686
|
|
|
733
|
-
// Reassign unconditionally so dees-table's flash diff can compare per-cell
|
|
734
|
-
// values against the previous snapshot. Row identity is preserved via
|
|
735
|
-
// rowKey='id', so DOM nodes are reused across ticks.
|
|
736
|
-
this.networkRequests = this.networkState.connections.map((conn) => ({
|
|
737
|
-
id: conn.id,
|
|
738
|
-
timestamp: conn.startTime,
|
|
739
|
-
method: 'GET', // Default method for proxy connections
|
|
740
|
-
url: '/',
|
|
741
|
-
hostname: conn.remoteAddress,
|
|
742
|
-
port: conn.protocol === 'https' ? 443 : 80,
|
|
743
|
-
protocol: conn.protocol === 'https' || conn.protocol === 'http' ? conn.protocol : 'tcp',
|
|
744
|
-
statusCode: conn.state === 'connected' ? 200 : undefined,
|
|
745
|
-
duration: Date.now() - conn.startTime,
|
|
746
|
-
bytesIn: conn.bytesReceived,
|
|
747
|
-
bytesOut: conn.bytesSent,
|
|
748
|
-
remoteIp: conn.remoteAddress,
|
|
749
|
-
route: 'proxy',
|
|
750
|
-
}));
|
|
751
|
-
|
|
752
687
|
// Load server-side throughput history into chart (once)
|
|
753
688
|
if (!this.historyLoaded && this.networkState.throughputHistory && this.networkState.throughputHistory.length > 0) {
|
|
754
689
|
this.loadThroughputHistory();
|