Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dfb268bbfc | |||
| 6532c7ff22 | |||
| d2c63cf170 | |||
| 09d66e4528 | |||
| 3078fa9d7b | |||
| 57fbb128e6 | |||
| d73266eeb8 |
20
changelog.md
20
changelog.md
@@ -1,5 +1,25 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-02-14 - 5.4.6 - fix(deps)
|
||||||
|
bump @push.rocks/smartproxy dependency to ^25.2.2
|
||||||
|
|
||||||
|
- Updated dependency @push.rocks/smartproxy: ^25.2.0 → ^25.2.2
|
||||||
|
- Change is a dependency-only patch update, no source code modifications
|
||||||
|
- Current package version is 5.4.5; recommend a patch release
|
||||||
|
|
||||||
|
## 2026-02-14 - 5.4.5 - fix(dcrouter)
|
||||||
|
bump patch for release pipeline consistency - no code changes
|
||||||
|
|
||||||
|
- current version: 5.4.4 (from package.json)
|
||||||
|
- git diff: no changes detected
|
||||||
|
- recommend patch bump to trigger release artifacts if required
|
||||||
|
|
||||||
|
## 2026-02-14 - 5.4.4 - fix(deps)
|
||||||
|
bump @push.rocks/smartproxy to ^25.2.0
|
||||||
|
|
||||||
|
- Updated @push.rocks/smartproxy from ^25.1.0 to ^25.2.0 (patch, non-breaking).
|
||||||
|
- Current package version is 5.4.3; recommend a patch release to 5.4.4.
|
||||||
|
|
||||||
## 2026-02-14 - 5.4.3 - fix(dependencies)
|
## 2026-02-14 - 5.4.3 - fix(dependencies)
|
||||||
bump @push.rocks/smartproxy to ^25.1.0
|
bump @push.rocks/smartproxy to ^25.1.0
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/dcrouter",
|
"name": "@serve.zone/dcrouter",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "5.4.3",
|
"version": "5.4.6",
|
||||||
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
"@push.rocks/smartnetwork": "^4.4.0",
|
"@push.rocks/smartnetwork": "^4.4.0",
|
||||||
"@push.rocks/smartpath": "^6.0.0",
|
"@push.rocks/smartpath": "^6.0.0",
|
||||||
"@push.rocks/smartpromise": "^4.2.3",
|
"@push.rocks/smartpromise": "^4.2.3",
|
||||||
"@push.rocks/smartproxy": "^25.1.0",
|
"@push.rocks/smartproxy": "^25.2.2",
|
||||||
"@push.rocks/smartradius": "^1.1.1",
|
"@push.rocks/smartradius": "^1.1.1",
|
||||||
"@push.rocks/smartrequest": "^5.0.1",
|
"@push.rocks/smartrequest": "^5.0.1",
|
||||||
"@push.rocks/smartrx": "^3.0.10",
|
"@push.rocks/smartrx": "^3.0.10",
|
||||||
|
|||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -75,8 +75,8 @@ importers:
|
|||||||
specifier: ^4.2.3
|
specifier: ^4.2.3
|
||||||
version: 4.2.3
|
version: 4.2.3
|
||||||
'@push.rocks/smartproxy':
|
'@push.rocks/smartproxy':
|
||||||
specifier: ^25.1.0
|
specifier: ^25.2.2
|
||||||
version: 25.1.0(@push.rocks/smartserve@2.0.1)(socks@2.8.7)
|
version: 25.2.2(@push.rocks/smartserve@2.0.1)(socks@2.8.7)
|
||||||
'@push.rocks/smartradius':
|
'@push.rocks/smartradius':
|
||||||
specifier: ^1.1.1
|
specifier: ^1.1.1
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
@@ -1040,8 +1040,8 @@ packages:
|
|||||||
'@push.rocks/smartpromise@4.2.3':
|
'@push.rocks/smartpromise@4.2.3':
|
||||||
resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==}
|
resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==}
|
||||||
|
|
||||||
'@push.rocks/smartproxy@25.1.0':
|
'@push.rocks/smartproxy@25.2.2':
|
||||||
resolution: {integrity: sha512-VA2Vu4ne1XJRF7lbd2Z70WPnYjcaSE6q3fmLCXXNfeRAsOw28xWidgQaJer/64G8HWZb0M6ygYB0jZ3ac3WJ2Q==}
|
resolution: {integrity: sha512-a9ztUkT2N904O3/MrLKNCqlcznDXgJf7qS7N+1Aw+QeBzxl26ofvwDcffePOSRm6BKo+q6Df9wWJ4gHoAZURLw==}
|
||||||
|
|
||||||
'@push.rocks/smartpuppeteer@2.0.5':
|
'@push.rocks/smartpuppeteer@2.0.5':
|
||||||
resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==}
|
resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==}
|
||||||
@@ -6441,7 +6441,7 @@ snapshots:
|
|||||||
|
|
||||||
'@push.rocks/smartpromise@4.2.3': {}
|
'@push.rocks/smartpromise@4.2.3': {}
|
||||||
|
|
||||||
'@push.rocks/smartproxy@25.1.0(@push.rocks/smartserve@2.0.1)(socks@2.8.7)':
|
'@push.rocks/smartproxy@25.2.2(@push.rocks/smartserve@2.0.1)(socks@2.8.7)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
'@push.rocks/smartacme': 8.0.0(@push.rocks/smartserve@2.0.1)(socks@2.8.7)
|
'@push.rocks/smartacme': 8.0.0(@push.rocks/smartserve@2.0.1)(socks@2.8.7)
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/dcrouter',
|
name: '@serve.zone/dcrouter',
|
||||||
version: '5.4.3',
|
version: '5.4.6',
|
||||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -484,40 +484,58 @@ export class MetricsManager {
|
|||||||
// Use shorter cache TTL for network stats to ensure real-time updates
|
// Use shorter cache TTL for network stats to ensure real-time updates
|
||||||
return this.metricsCache.get('networkStats', () => {
|
return this.metricsCache.get('networkStats', () => {
|
||||||
const proxyMetrics = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getMetrics() : null;
|
const proxyMetrics = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getMetrics() : null;
|
||||||
|
|
||||||
if (!proxyMetrics) {
|
if (!proxyMetrics) {
|
||||||
return {
|
return {
|
||||||
connectionsByIP: new Map<string, number>(),
|
connectionsByIP: new Map<string, number>(),
|
||||||
throughputRate: { bytesInPerSecond: 0, bytesOutPerSecond: 0 },
|
throughputRate: { bytesInPerSecond: 0, bytesOutPerSecond: 0 },
|
||||||
topIPs: [],
|
topIPs: [] as Array<{ ip: string; count: number }>,
|
||||||
totalDataTransferred: { bytesIn: 0, bytesOut: 0 },
|
totalDataTransferred: { bytesIn: 0, bytesOut: 0 },
|
||||||
|
throughputHistory: [] as Array<{ timestamp: number; in: number; out: number }>,
|
||||||
|
throughputByIP: new Map<string, { in: number; out: number }>(),
|
||||||
|
requestsPerSecond: 0,
|
||||||
|
requestsTotal: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get metrics using the new API
|
// Get metrics using the new API
|
||||||
const connectionsByIP = proxyMetrics.connections.byIP();
|
const connectionsByIP = proxyMetrics.connections.byIP();
|
||||||
const instantThroughput = proxyMetrics.throughput.instant();
|
const instantThroughput = proxyMetrics.throughput.instant();
|
||||||
|
|
||||||
// Get throughput rate
|
// Get throughput rate
|
||||||
const throughputRate = {
|
const throughputRate = {
|
||||||
bytesInPerSecond: instantThroughput.in,
|
bytesInPerSecond: instantThroughput.in,
|
||||||
bytesOutPerSecond: instantThroughput.out
|
bytesOutPerSecond: instantThroughput.out
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get top IPs
|
// Get top IPs
|
||||||
const topIPs = proxyMetrics.connections.topIPs(10);
|
const topIPs = proxyMetrics.connections.topIPs(10);
|
||||||
|
|
||||||
// Get total data transferred
|
// Get total data transferred
|
||||||
const totalDataTransferred = {
|
const totalDataTransferred = {
|
||||||
bytesIn: proxyMetrics.totals.bytesIn(),
|
bytesIn: proxyMetrics.totals.bytesIn(),
|
||||||
bytesOut: proxyMetrics.totals.bytesOut()
|
bytesOut: proxyMetrics.totals.bytesOut()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get throughput history from Rust engine (up to 300 seconds)
|
||||||
|
const throughputHistory = proxyMetrics.throughput.history(300);
|
||||||
|
|
||||||
|
// Get per-IP throughput
|
||||||
|
const throughputByIP = proxyMetrics.throughput.byIP();
|
||||||
|
|
||||||
|
// Get HTTP request rates
|
||||||
|
const requestsPerSecond = proxyMetrics.requests.perSecond();
|
||||||
|
const requestsTotal = proxyMetrics.requests.total();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
connectionsByIP,
|
connectionsByIP,
|
||||||
throughputRate,
|
throughputRate,
|
||||||
topIPs,
|
topIPs,
|
||||||
totalDataTransferred,
|
totalDataTransferred,
|
||||||
|
throughputHistory,
|
||||||
|
throughputByIP,
|
||||||
|
requestsPerSecond,
|
||||||
|
requestsTotal,
|
||||||
};
|
};
|
||||||
}, 200); // Use 200ms cache for more frequent updates
|
}, 200); // Use 200ms cache for more frequent updates
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,21 +84,37 @@ export class SecurityHandler {
|
|||||||
// Get network stats from MetricsManager if available
|
// Get network stats from MetricsManager if available
|
||||||
if (this.opsServerRef.dcRouterRef.metricsManager) {
|
if (this.opsServerRef.dcRouterRef.metricsManager) {
|
||||||
const networkStats = await this.opsServerRef.dcRouterRef.metricsManager.getNetworkStats();
|
const networkStats = await this.opsServerRef.dcRouterRef.metricsManager.getNetworkStats();
|
||||||
|
|
||||||
|
// Convert per-IP throughput Map to serializable array
|
||||||
|
const throughputByIP: Array<{ ip: string; in: number; out: number }> = [];
|
||||||
|
if (networkStats.throughputByIP) {
|
||||||
|
for (const [ip, tp] of networkStats.throughputByIP) {
|
||||||
|
throughputByIP.push({ ip, in: tp.in, out: tp.out });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
connectionsByIP: Array.from(networkStats.connectionsByIP.entries()).map(([ip, count]) => ({ ip, count })),
|
connectionsByIP: Array.from(networkStats.connectionsByIP.entries()).map(([ip, count]) => ({ ip, count })),
|
||||||
throughputRate: networkStats.throughputRate,
|
throughputRate: networkStats.throughputRate,
|
||||||
topIPs: networkStats.topIPs,
|
topIPs: networkStats.topIPs,
|
||||||
totalDataTransferred: networkStats.totalDataTransferred,
|
totalDataTransferred: networkStats.totalDataTransferred,
|
||||||
|
throughputHistory: networkStats.throughputHistory || [],
|
||||||
|
throughputByIP,
|
||||||
|
requestsPerSecond: networkStats.requestsPerSecond || 0,
|
||||||
|
requestsTotal: networkStats.requestsTotal || 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback if MetricsManager not available
|
// Fallback if MetricsManager not available
|
||||||
return {
|
return {
|
||||||
connectionsByIP: [],
|
connectionsByIP: [],
|
||||||
throughputRate: { bytesInPerSecond: 0, bytesOutPerSecond: 0 },
|
throughputRate: { bytesInPerSecond: 0, bytesOutPerSecond: 0 },
|
||||||
topIPs: [],
|
topIPs: [],
|
||||||
totalDataTransferred: { bytesIn: 0, bytesOut: 0 },
|
totalDataTransferred: { bytesIn: 0, bytesOut: 0 },
|
||||||
|
throughputHistory: [],
|
||||||
|
throughputByIP: [],
|
||||||
|
requestsPerSecond: 0,
|
||||||
|
requestsTotal: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -255,6 +255,14 @@ export class StatsHandler {
|
|||||||
const stats = await this.opsServerRef.dcRouterRef.metricsManager.getNetworkStats();
|
const stats = await this.opsServerRef.dcRouterRef.metricsManager.getNetworkStats();
|
||||||
const serverStats = await this.collectServerStats();
|
const serverStats = await this.collectServerStats();
|
||||||
|
|
||||||
|
// Build per-IP bandwidth lookup from throughputByIP
|
||||||
|
const ipBandwidth = new Map<string, { in: number; out: number }>();
|
||||||
|
if (stats.throughputByIP) {
|
||||||
|
for (const [ip, tp] of stats.throughputByIP) {
|
||||||
|
ipBandwidth.set(ip, { in: tp.in, out: tp.out });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
metrics.network = {
|
metrics.network = {
|
||||||
totalBandwidth: {
|
totalBandwidth: {
|
||||||
in: stats.throughputRate.bytesInPerSecond,
|
in: stats.throughputRate.bytesInPerSecond,
|
||||||
@@ -269,11 +277,11 @@ export class StatsHandler {
|
|||||||
topEndpoints: stats.topIPs.map(ip => ({
|
topEndpoints: stats.topIPs.map(ip => ({
|
||||||
endpoint: ip.ip,
|
endpoint: ip.ip,
|
||||||
requests: ip.count,
|
requests: ip.count,
|
||||||
bandwidth: {
|
bandwidth: ipBandwidth.get(ip.ip) || { in: 0, out: 0 },
|
||||||
in: 0,
|
|
||||||
out: 0,
|
|
||||||
},
|
|
||||||
})),
|
})),
|
||||||
|
throughputHistory: stats.throughputHistory || [],
|
||||||
|
requestsPerSecond: stats.requestsPerSecond || 0,
|
||||||
|
requestsTotal: stats.requestsTotal || 0,
|
||||||
};
|
};
|
||||||
})()
|
})()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -130,6 +130,9 @@ export interface INetworkMetrics {
|
|||||||
out: number;
|
out: number;
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
|
throughputHistory?: Array<{ timestamp: number; in: number; out: number }>;
|
||||||
|
requestsPerSecond?: number;
|
||||||
|
requestsTotal?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IConnectionDetails {
|
export interface IConnectionDetails {
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/dcrouter',
|
name: '@serve.zone/dcrouter',
|
||||||
version: '5.4.3',
|
version: '5.4.6',
|
||||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ export interface INetworkState {
|
|||||||
throughputRate: { bytesInPerSecond: number; bytesOutPerSecond: number };
|
throughputRate: { bytesInPerSecond: number; bytesOutPerSecond: number };
|
||||||
totalBytes: { in: number; out: number };
|
totalBytes: { in: number; out: number };
|
||||||
topIPs: Array<{ ip: string; count: number }>;
|
topIPs: Array<{ ip: string; count: number }>;
|
||||||
|
throughputByIP: Array<{ ip: string; in: number; out: number }>;
|
||||||
|
throughputHistory: Array<{ timestamp: number; in: number; out: number }>;
|
||||||
|
requestsPerSecond: number;
|
||||||
|
requestsTotal: number;
|
||||||
lastUpdated: number;
|
lastUpdated: number;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
@@ -147,6 +151,10 @@ export const networkStatePart = await appState.getStatePart<INetworkState>(
|
|||||||
throughputRate: { bytesInPerSecond: 0, bytesOutPerSecond: 0 },
|
throughputRate: { bytesInPerSecond: 0, bytesOutPerSecond: 0 },
|
||||||
totalBytes: { in: 0, out: 0 },
|
totalBytes: { in: 0, out: 0 },
|
||||||
topIPs: [],
|
topIPs: [],
|
||||||
|
throughputByIP: [],
|
||||||
|
throughputHistory: [],
|
||||||
|
requestsPerSecond: 0,
|
||||||
|
requestsTotal: 0,
|
||||||
lastUpdated: 0,
|
lastUpdated: 0,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: null,
|
error: null,
|
||||||
@@ -427,6 +435,10 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat
|
|||||||
? { in: networkStatsResponse.totalDataTransferred.bytesIn, out: networkStatsResponse.totalDataTransferred.bytesOut }
|
? { in: networkStatsResponse.totalDataTransferred.bytesIn, out: networkStatsResponse.totalDataTransferred.bytesOut }
|
||||||
: { in: 0, out: 0 },
|
: { in: 0, out: 0 },
|
||||||
topIPs: networkStatsResponse.topIPs || [],
|
topIPs: networkStatsResponse.topIPs || [],
|
||||||
|
throughputByIP: networkStatsResponse.throughputByIP || [],
|
||||||
|
throughputHistory: networkStatsResponse.throughputHistory || [],
|
||||||
|
requestsPerSecond: networkStatsResponse.requestsPerSecond || 0,
|
||||||
|
requestsTotal: networkStatsResponse.requestsTotal || 0,
|
||||||
lastUpdated: Date.now(),
|
lastUpdated: Date.now(),
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: null,
|
error: null,
|
||||||
@@ -797,6 +809,10 @@ async function dispatchCombinedRefreshAction() {
|
|||||||
},
|
},
|
||||||
totalBytes: network.totalBytes || { in: 0, out: 0 },
|
totalBytes: network.totalBytes || { in: 0, out: 0 },
|
||||||
topIPs: network.topEndpoints.map(e => ({ ip: e.endpoint, count: e.requests })),
|
topIPs: network.topEndpoints.map(e => ({ ip: e.endpoint, count: e.requests })),
|
||||||
|
throughputByIP: network.topEndpoints.map(e => ({ ip: e.endpoint, in: e.bandwidth?.in || 0, out: e.bandwidth?.out || 0 })),
|
||||||
|
throughputHistory: network.throughputHistory || [],
|
||||||
|
requestsPerSecond: network.requestsPerSecond || 0,
|
||||||
|
requestsTotal: network.requestsTotal || 0,
|
||||||
lastUpdated: Date.now(),
|
lastUpdated: Date.now(),
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: null,
|
error: null,
|
||||||
@@ -813,6 +829,10 @@ async function dispatchCombinedRefreshAction() {
|
|||||||
},
|
},
|
||||||
totalBytes: network.totalBytes || { in: 0, out: 0 },
|
totalBytes: network.totalBytes || { in: 0, out: 0 },
|
||||||
topIPs: network.topEndpoints.map(e => ({ ip: e.endpoint, count: e.requests })),
|
topIPs: network.topEndpoints.map(e => ({ ip: e.endpoint, count: e.requests })),
|
||||||
|
throughputByIP: network.topEndpoints.map(e => ({ ip: e.endpoint, in: e.bandwidth?.in || 0, out: e.bandwidth?.out || 0 })),
|
||||||
|
throughputHistory: network.throughputHistory || [],
|
||||||
|
requestsPerSecond: network.requestsPerSecond || 0,
|
||||||
|
requestsTotal: network.requestsTotal || 0,
|
||||||
lastUpdated: Date.now(),
|
lastUpdated: Date.now(),
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: null,
|
error: null,
|
||||||
|
|||||||
@@ -52,8 +52,7 @@ export class OpsViewNetwork extends DeesElement {
|
|||||||
private requestCountHistory = new Map<number, number>(); // Track requests per time bucket
|
private requestCountHistory = new Map<number, number>(); // Track requests per time bucket
|
||||||
private trafficUpdateTimer: any = null;
|
private trafficUpdateTimer: any = null;
|
||||||
private requestsPerSecHistory: number[] = []; // Track requests/sec over time for trend
|
private requestsPerSecHistory: number[] = []; // Track requests/sec over time for trend
|
||||||
|
private historyLoaded = false; // Whether server-side throughput history has been loaded
|
||||||
// Removed byte tracking - now using real-time data from SmartProxy
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -95,7 +94,7 @@ export class OpsViewNetwork extends DeesElement {
|
|||||||
// Fixed 5 minute time range
|
// Fixed 5 minute time range
|
||||||
const range = 5 * 60 * 1000; // 5 minutes
|
const range = 5 * 60 * 1000; // 5 minutes
|
||||||
const bucketSize = range / 60; // 60 data points
|
const bucketSize = range / 60; // 60 data points
|
||||||
|
|
||||||
// Initialize with empty data points for both in and out
|
// Initialize with empty data points for both in and out
|
||||||
const emptyData = Array.from({ length: 60 }, (_, i) => {
|
const emptyData = Array.from({ length: 60 }, (_, i) => {
|
||||||
const time = now - ((59 - i) * bucketSize);
|
const time = now - ((59 - i) * bucketSize);
|
||||||
@@ -104,13 +103,61 @@ export class OpsViewNetwork extends DeesElement {
|
|||||||
y: 0,
|
y: 0,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
this.trafficDataIn = [...emptyData];
|
this.trafficDataIn = [...emptyData];
|
||||||
this.trafficDataOut = emptyData.map(point => ({ ...point }));
|
this.trafficDataOut = emptyData.map(point => ({ ...point }));
|
||||||
|
|
||||||
this.lastTrafficUpdateTime = now;
|
this.lastTrafficUpdateTime = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load server-side throughput history into the chart.
|
||||||
|
* Called once when history data first arrives from the Rust engine.
|
||||||
|
* This pre-populates the chart so users see historical data immediately
|
||||||
|
* instead of starting from all zeros.
|
||||||
|
*/
|
||||||
|
private loadThroughputHistory() {
|
||||||
|
const history = this.networkState.throughputHistory;
|
||||||
|
if (!history || history.length === 0) return;
|
||||||
|
|
||||||
|
this.historyLoaded = true;
|
||||||
|
|
||||||
|
// Convert history points to chart data format (bytes/sec → Mbit/s)
|
||||||
|
const historyIn = history.map(p => ({
|
||||||
|
x: new Date(p.timestamp).toISOString(),
|
||||||
|
y: Math.round((p.in * 8) / 1000000 * 10) / 10,
|
||||||
|
}));
|
||||||
|
const historyOut = history.map(p => ({
|
||||||
|
x: new Date(p.timestamp).toISOString(),
|
||||||
|
y: Math.round((p.out * 8) / 1000000 * 10) / 10,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Use history as the chart data, keeping the most recent 60 points (5 min window)
|
||||||
|
const sliceStart = Math.max(0, historyIn.length - 60);
|
||||||
|
this.trafficDataIn = historyIn.slice(sliceStart);
|
||||||
|
this.trafficDataOut = historyOut.slice(sliceStart);
|
||||||
|
|
||||||
|
// If fewer than 60 points, pad the front with zeros
|
||||||
|
if (this.trafficDataIn.length < 60) {
|
||||||
|
const now = Date.now();
|
||||||
|
const range = 5 * 60 * 1000;
|
||||||
|
const bucketSize = range / 60;
|
||||||
|
const padCount = 60 - this.trafficDataIn.length;
|
||||||
|
const firstTimestamp = this.trafficDataIn.length > 0
|
||||||
|
? new Date(this.trafficDataIn[0].x).getTime()
|
||||||
|
: now;
|
||||||
|
|
||||||
|
const padIn = Array.from({ length: padCount }, (_, i) => ({
|
||||||
|
x: new Date(firstTimestamp - ((padCount - i) * bucketSize)).toISOString(),
|
||||||
|
y: 0,
|
||||||
|
}));
|
||||||
|
const padOut = padIn.map(p => ({ ...p }));
|
||||||
|
|
||||||
|
this.trafficDataIn = [...padIn, ...this.trafficDataIn];
|
||||||
|
this.trafficDataOut = [...padOut, ...this.trafficDataOut];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
viewHostCss,
|
viewHostCss,
|
||||||
@@ -352,21 +399,6 @@ export class OpsViewNetwork extends DeesElement {
|
|||||||
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateRequestsPerSecond(): number {
|
|
||||||
// Calculate from actual request data in the last minute
|
|
||||||
const oneMinuteAgo = Date.now() - 60000;
|
|
||||||
const recentRequests = this.networkRequests.filter(req => req.timestamp >= oneMinuteAgo);
|
|
||||||
const reqPerSec = Math.round(recentRequests.length / 60);
|
|
||||||
|
|
||||||
// Track history for trend (keep last 20 values)
|
|
||||||
this.requestsPerSecHistory.push(reqPerSec);
|
|
||||||
if (this.requestsPerSecHistory.length > 20) {
|
|
||||||
this.requestsPerSecHistory.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
return reqPerSec;
|
|
||||||
}
|
|
||||||
|
|
||||||
private calculateThroughput(): { in: number; out: number } {
|
private calculateThroughput(): { in: number; out: number } {
|
||||||
// Use real throughput data from network state
|
// Use real throughput data from network state
|
||||||
return {
|
return {
|
||||||
@@ -376,16 +408,17 @@ export class OpsViewNetwork extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private renderNetworkStats(): TemplateResult {
|
private renderNetworkStats(): TemplateResult {
|
||||||
const reqPerSec = this.calculateRequestsPerSecond();
|
// Use server-side requests/sec from SmartProxy's Rust engine
|
||||||
|
const reqPerSec = this.networkState.requestsPerSecond || 0;
|
||||||
const throughput = this.calculateThroughput();
|
const throughput = this.calculateThroughput();
|
||||||
const activeConnections = this.statsState.serverStats?.activeConnections || 0;
|
const activeConnections = this.statsState.serverStats?.activeConnections || 0;
|
||||||
|
|
||||||
// Throughput data is now available in the stats tiles
|
|
||||||
|
|
||||||
// Use request count history for the requests/sec trend
|
// Track requests/sec history for the trend sparkline
|
||||||
|
this.requestsPerSecHistory.push(reqPerSec);
|
||||||
|
if (this.requestsPerSecHistory.length > 20) {
|
||||||
|
this.requestsPerSecHistory.shift();
|
||||||
|
}
|
||||||
const trendData = [...this.requestsPerSecHistory];
|
const trendData = [...this.requestsPerSecHistory];
|
||||||
|
|
||||||
// If we don't have enough data, pad with zeros
|
|
||||||
while (trendData.length < 20) {
|
while (trendData.length < 20) {
|
||||||
trendData.unshift(0);
|
trendData.unshift(0);
|
||||||
}
|
}
|
||||||
@@ -398,7 +431,7 @@ export class OpsViewNetwork extends DeesElement {
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
icon: 'plug',
|
icon: 'plug',
|
||||||
color: activeConnections > 100 ? '#f59e0b' : '#22c55e',
|
color: activeConnections > 100 ? '#f59e0b' : '#22c55e',
|
||||||
description: `Total: ${this.statsState.serverStats?.totalConnections || 0}`,
|
description: `Total: ${this.networkState.requestsTotal || this.statsState.serverStats?.totalConnections || 0}`,
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
name: 'View Details',
|
name: 'View Details',
|
||||||
@@ -416,7 +449,7 @@ export class OpsViewNetwork extends DeesElement {
|
|||||||
icon: 'chartLine',
|
icon: 'chartLine',
|
||||||
color: '#3b82f6',
|
color: '#3b82f6',
|
||||||
trendData: trendData,
|
trendData: trendData,
|
||||||
description: `Average over last minute`,
|
description: `Total: ${this.formatNumber(this.networkState.requestsTotal || 0)} requests`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'throughputIn',
|
id: 'throughputIn',
|
||||||
@@ -462,20 +495,33 @@ export class OpsViewNetwork extends DeesElement {
|
|||||||
if (this.networkState.topIPs.length === 0) {
|
if (this.networkState.topIPs.length === 0) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build per-IP bandwidth lookup
|
||||||
|
const bandwidthByIP = new Map<string, { in: number; out: number }>();
|
||||||
|
if (this.networkState.throughputByIP) {
|
||||||
|
for (const entry of this.networkState.throughputByIP) {
|
||||||
|
bandwidthByIP.set(entry.ip, { in: entry.in, out: entry.out });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate total connections across all top IPs
|
// Calculate total connections across all top IPs
|
||||||
const totalConnections = this.networkState.topIPs.reduce((sum, ipData) => sum + ipData.count, 0);
|
const totalConnections = this.networkState.topIPs.reduce((sum, ipData) => sum + ipData.count, 0);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<dees-table
|
<dees-table
|
||||||
.data=${this.networkState.topIPs}
|
.data=${this.networkState.topIPs}
|
||||||
.displayFunction=${(ipData: { ip: string; count: number }) => ({
|
.displayFunction=${(ipData: { ip: string; count: number }) => {
|
||||||
'IP Address': ipData.ip,
|
const bw = bandwidthByIP.get(ipData.ip);
|
||||||
'Connections': ipData.count,
|
return {
|
||||||
'Percentage': totalConnections > 0 ? ((ipData.count / totalConnections) * 100).toFixed(1) + '%' : '0%',
|
'IP Address': ipData.ip,
|
||||||
})}
|
'Connections': ipData.count,
|
||||||
|
'Bandwidth In': bw ? this.formatBitsPerSecond(bw.in) : '0 bit/s',
|
||||||
|
'Bandwidth Out': bw ? this.formatBitsPerSecond(bw.out) : '0 bit/s',
|
||||||
|
'Share': totalConnections > 0 ? ((ipData.count / totalConnections) * 100).toFixed(1) + '%' : '0%',
|
||||||
|
};
|
||||||
|
}}
|
||||||
heading1="Top Connected IPs"
|
heading1="Top Connected IPs"
|
||||||
heading2="IPs with most active connections"
|
heading2="IPs with most active connections and bandwidth"
|
||||||
.pagination=${false}
|
.pagination=${false}
|
||||||
dataName="ip"
|
dataName="ip"
|
||||||
></dees-table>
|
></dees-table>
|
||||||
@@ -515,13 +561,10 @@ export class OpsViewNetwork extends DeesElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate traffic data based on request history
|
// Load server-side throughput history into chart (once)
|
||||||
this.updateTrafficData();
|
if (!this.historyLoaded && this.networkState.throughputHistory && this.networkState.throughputHistory.length > 0) {
|
||||||
}
|
this.loadThroughputHistory();
|
||||||
|
}
|
||||||
private updateTrafficData() {
|
|
||||||
// This method is called when network data updates
|
|
||||||
// The actual chart updates are handled by the timer calling addTrafficDataPoint()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private startTrafficUpdateTimer() {
|
private startTrafficUpdateTimer() {
|
||||||
|
|||||||
Reference in New Issue
Block a user