feat: Implement network metrics integration and UI updates for real-time data display
This commit is contained in:
@ -43,6 +43,16 @@ export interface ILogState {
|
||||
};
|
||||
}
|
||||
|
||||
export interface INetworkState {
|
||||
connections: interfaces.data.IConnectionInfo[];
|
||||
connectionsByIP: { [ip: string]: number };
|
||||
throughputRate: { bytesInPerSecond: number; bytesOutPerSecond: number };
|
||||
topIPs: Array<{ ip: string; count: number }>;
|
||||
lastUpdated: number;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
// Create state parts with appropriate persistence
|
||||
export const loginStatePart = await appState.getStatePart<ILoginState>(
|
||||
'login',
|
||||
@ -97,6 +107,20 @@ export const logStatePart = await appState.getStatePart<ILogState>(
|
||||
'soft'
|
||||
);
|
||||
|
||||
export const networkStatePart = await appState.getStatePart<INetworkState>(
|
||||
'network',
|
||||
{
|
||||
connections: [],
|
||||
connectionsByIP: {},
|
||||
throughputRate: { bytesInPerSecond: 0, bytesOutPerSecond: 0 },
|
||||
topIPs: [],
|
||||
lastUpdated: 0,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
},
|
||||
'soft'
|
||||
);
|
||||
|
||||
// Actions for state management
|
||||
interface IActionContext {
|
||||
identity: interfaces.data.IIdentity | null;
|
||||
@ -324,6 +348,68 @@ export const setActiveViewAction = uiStatePart.createAction<string>(async (state
|
||||
};
|
||||
});
|
||||
|
||||
// Fetch Network Stats Action
|
||||
export const fetchNetworkStatsAction = networkStatePart.createAction(async (statePartArg) => {
|
||||
const context = getActionContext();
|
||||
|
||||
const currentState = statePartArg.getState();
|
||||
|
||||
try {
|
||||
// Fetch active connections using the existing endpoint
|
||||
const connectionsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||
interfaces.requests.IReq_GetActiveConnections
|
||||
>('/typedrequest', 'getActiveConnections');
|
||||
|
||||
const connectionsResponse = await connectionsRequest.fire({
|
||||
identity: context.identity,
|
||||
});
|
||||
|
||||
// Get network stats for throughput and IP data
|
||||
const networkStatsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest(
|
||||
'/typedrequest',
|
||||
'getNetworkStats'
|
||||
);
|
||||
|
||||
const networkStatsResponse = await networkStatsRequest.fire({
|
||||
identity: context.identity,
|
||||
}) as any;
|
||||
|
||||
// Use the connections data for the connection list
|
||||
// and network stats for throughput and IP analytics
|
||||
const connectionsByIP: { [ip: string]: number } = {};
|
||||
|
||||
// Build connectionsByIP from network stats if available
|
||||
if (networkStatsResponse.connectionsByIP && Array.isArray(networkStatsResponse.connectionsByIP)) {
|
||||
networkStatsResponse.connectionsByIP.forEach((item: { ip: string; count: number }) => {
|
||||
connectionsByIP[item.ip] = item.count;
|
||||
});
|
||||
} else {
|
||||
// Fallback: calculate from connections
|
||||
connectionsResponse.connections.forEach(conn => {
|
||||
const ip = conn.remoteAddress;
|
||||
connectionsByIP[ip] = (connectionsByIP[ip] || 0) + 1;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
connections: connectionsResponse.connections,
|
||||
connectionsByIP,
|
||||
throughputRate: networkStatsResponse.throughputRate || { bytesInPerSecond: 0, bytesOutPerSecond: 0 },
|
||||
topIPs: networkStatsResponse.topIPs || [],
|
||||
lastUpdated: Date.now(),
|
||||
isLoading: false,
|
||||
error: null,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch network stats:', error);
|
||||
return {
|
||||
...currentState,
|
||||
isLoading: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to fetch network stats',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize auto-refresh
|
||||
let refreshInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
@ -334,6 +420,7 @@ let refreshInterval: NodeJS.Timeout | null = null;
|
||||
if (uiState.autoRefresh && loginStatePart.getState().isLoggedIn) {
|
||||
refreshInterval = setInterval(() => {
|
||||
statsStatePart.dispatchAction(fetchAllStatsAction, null);
|
||||
networkStatePart.dispatchAction(fetchNetworkStatsAction, null);
|
||||
}, uiState.refreshInterval);
|
||||
}
|
||||
};
|
||||
|
@ -30,6 +30,9 @@ export class OpsViewNetwork extends DeesElement {
|
||||
@state()
|
||||
private statsState = appstate.statsStatePart.getState();
|
||||
|
||||
@state()
|
||||
private networkState = appstate.networkStatePart.getState();
|
||||
|
||||
@state()
|
||||
private selectedTimeRange: '1m' | '5m' | '15m' | '1h' | '24h' = '5m';
|
||||
|
||||
@ -48,7 +51,7 @@ export class OpsViewNetwork extends DeesElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.subscribeToStateParts();
|
||||
this.generateMockData(); // TODO: Replace with real data from metrics
|
||||
this.updateNetworkData();
|
||||
}
|
||||
|
||||
private subscribeToStateParts() {
|
||||
@ -56,6 +59,11 @@ export class OpsViewNetwork extends DeesElement {
|
||||
this.statsState = state;
|
||||
this.updateNetworkData();
|
||||
});
|
||||
|
||||
appstate.networkStatePart.state.subscribe((state) => {
|
||||
this.networkState = state;
|
||||
this.updateNetworkData();
|
||||
});
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
@ -221,6 +229,9 @@ export class OpsViewNetwork extends DeesElement {
|
||||
]}
|
||||
></dees-chart-area>
|
||||
|
||||
<!-- Top IPs Section -->
|
||||
${this.renderTopIPs()}
|
||||
|
||||
<!-- Requests Table -->
|
||||
<dees-table
|
||||
.data=${this.getFilteredRequests()}
|
||||
@ -290,7 +301,6 @@ export class OpsViewNetwork extends DeesElement {
|
||||
iconName: 'copy',
|
||||
action: async () => {
|
||||
await navigator.clipboard.writeText(request.id);
|
||||
// TODO: Implement toast notification when DeesToast.show is available
|
||||
console.log('Request ID copied to clipboard');
|
||||
}
|
||||
}
|
||||
@ -365,18 +375,17 @@ export class OpsViewNetwork extends DeesElement {
|
||||
}
|
||||
|
||||
private calculateRequestsPerSecond(): number {
|
||||
// TODO: Calculate from real data based on connection metrics
|
||||
// For now, return a calculated value based on active connections
|
||||
return Math.floor((this.statsState.serverStats?.activeConnections || 0) * 0.8);
|
||||
// Calculate from actual request data in the last minute
|
||||
const oneMinuteAgo = Date.now() - 60000;
|
||||
const recentRequests = this.networkRequests.filter(req => req.timestamp >= oneMinuteAgo);
|
||||
return Math.round(recentRequests.length / 60);
|
||||
}
|
||||
|
||||
private calculateThroughput(): { in: number; out: number } {
|
||||
// TODO: Calculate from real connection data
|
||||
// For now, return estimated values
|
||||
const activeConnections = this.statsState.serverStats?.activeConnections || 0;
|
||||
// Use real throughput data from network state
|
||||
return {
|
||||
in: activeConnections * 1024 * 10, // 10KB per connection estimate
|
||||
out: activeConnections * 1024 * 50, // 50KB per connection estimate
|
||||
in: this.networkState.throughputRate.bytesInPerSecond,
|
||||
out: this.networkState.throughputRate.bytesOutPerSecond,
|
||||
};
|
||||
}
|
||||
|
||||
@ -404,7 +413,6 @@ export class OpsViewNetwork extends DeesElement {
|
||||
name: 'View Details',
|
||||
iconName: 'magnifyingGlass',
|
||||
action: async () => {
|
||||
// TODO: Show connection details
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -448,8 +456,6 @@ export class OpsViewNetwork extends DeesElement {
|
||||
name: 'Export Data',
|
||||
iconName: 'fileExport',
|
||||
action: async () => {
|
||||
// TODO: Export network data
|
||||
// TODO: Implement toast notification when DeesToast.show is available
|
||||
console.log('Export feature coming soon');
|
||||
},
|
||||
},
|
||||
@ -461,43 +467,90 @@ export class OpsViewNetwork extends DeesElement {
|
||||
private async refreshData() {
|
||||
this.isLoading = true;
|
||||
await appstate.statsStatePart.dispatchAction(appstate.fetchAllStatsAction, null);
|
||||
await appstate.networkStatePart.dispatchAction(appstate.fetchNetworkStatsAction, null);
|
||||
await this.updateNetworkData();
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
||||
private renderTopIPs(): TemplateResult {
|
||||
if (this.networkState.topIPs.length === 0) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<dees-table
|
||||
.data=${this.networkState.topIPs}
|
||||
.displayFunction=${(ipData: { ip: string; count: number }) => ({
|
||||
'IP Address': ipData.ip,
|
||||
'Connections': ipData.count,
|
||||
'Percentage': ((ipData.count / this.networkState.connections.length) * 100).toFixed(1) + '%',
|
||||
})}
|
||||
heading1="Top Connected IPs"
|
||||
heading2="IPs with most active connections"
|
||||
.pagination=${false}
|
||||
dataName="ip"
|
||||
></dees-table>
|
||||
`;
|
||||
}
|
||||
|
||||
private async updateNetworkData() {
|
||||
// TODO: Fetch real network data from the server
|
||||
// For now, using mock data
|
||||
this.generateMockData();
|
||||
// Convert connection data to network requests format
|
||||
if (this.networkState.connections.length > 0) {
|
||||
this.networkRequests = this.networkState.connections.map((conn, index) => ({
|
||||
id: conn.id,
|
||||
timestamp: conn.startTime,
|
||||
method: 'GET', // Default method for proxy connections
|
||||
url: '/',
|
||||
hostname: conn.remoteAddress,
|
||||
port: conn.protocol === 'https' ? 443 : 80,
|
||||
protocol: conn.protocol === 'https' || conn.protocol === 'http' ? conn.protocol : 'tcp',
|
||||
statusCode: conn.state === 'connected' ? 200 : undefined,
|
||||
duration: Date.now() - conn.startTime,
|
||||
bytesIn: conn.bytesReceived,
|
||||
bytesOut: conn.bytesSent,
|
||||
remoteIp: conn.remoteAddress,
|
||||
route: 'proxy',
|
||||
}));
|
||||
} else {
|
||||
this.networkRequests = [];
|
||||
}
|
||||
|
||||
// Generate traffic data based on request history
|
||||
this.updateTrafficData();
|
||||
}
|
||||
|
||||
private generateMockData() {
|
||||
// Generate mock network requests
|
||||
private updateTrafficData() {
|
||||
const now = Date.now();
|
||||
const protocols: Array<'http' | 'https' | 'tcp' | 'udp'> = ['http', 'https', 'tcp', 'udp'];
|
||||
const methods = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'];
|
||||
const hosts = ['api.example.com', 'app.local', 'mail.server.com', 'dns.resolver.net'];
|
||||
const timeRanges = {
|
||||
'1m': 60 * 1000,
|
||||
'5m': 5 * 60 * 1000,
|
||||
'15m': 15 * 60 * 1000,
|
||||
'1h': 60 * 60 * 1000,
|
||||
'24h': 24 * 60 * 60 * 1000,
|
||||
};
|
||||
|
||||
this.networkRequests = Array.from({ length: 100 }, (_, i) => ({
|
||||
id: `req-${i}`,
|
||||
timestamp: now - (i * 5000), // 5 seconds apart
|
||||
method: methods[Math.floor(Math.random() * methods.length)],
|
||||
url: `/api/v1/resource/${Math.floor(Math.random() * 100)}`,
|
||||
hostname: hosts[Math.floor(Math.random() * hosts.length)],
|
||||
port: Math.random() > 0.5 ? 443 : 80,
|
||||
protocol: protocols[Math.floor(Math.random() * protocols.length)],
|
||||
statusCode: Math.random() > 0.8 ? 404 : 200,
|
||||
duration: Math.floor(Math.random() * 500),
|
||||
bytesIn: Math.floor(Math.random() * 10000),
|
||||
bytesOut: Math.floor(Math.random() * 50000),
|
||||
remoteIp: `192.168.${Math.floor(Math.random() * 255)}.${Math.floor(Math.random() * 255)}`,
|
||||
route: 'main-route',
|
||||
}));
|
||||
|
||||
// Generate traffic data for chart
|
||||
this.trafficData = Array.from({ length: 60 }, (_, i) => ({
|
||||
x: now - (i * 60000), // 1 minute intervals
|
||||
y: Math.floor(Math.random() * 100) + 50,
|
||||
})).reverse();
|
||||
const range = timeRanges[this.selectedTimeRange];
|
||||
const bucketSize = range / 60; // 60 data points
|
||||
|
||||
// Create buckets for traffic data
|
||||
const buckets = new Map<number, number>();
|
||||
|
||||
// Count requests per bucket
|
||||
this.networkRequests.forEach(req => {
|
||||
if (req.timestamp >= now - range) {
|
||||
const bucketIndex = Math.floor((now - req.timestamp) / bucketSize);
|
||||
const bucketTime = now - (bucketIndex * bucketSize);
|
||||
buckets.set(bucketTime, (buckets.get(bucketTime) || 0) + 1);
|
||||
}
|
||||
});
|
||||
|
||||
// Convert to chart data
|
||||
this.trafficData = Array.from({ length: 60 }, (_, i) => {
|
||||
const time = now - (i * bucketSize);
|
||||
return {
|
||||
x: time,
|
||||
y: buckets.get(time) || 0,
|
||||
};
|
||||
}).reverse();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user