import * as plugins from '../plugins.js'; import * as interfaces from '../../ts_interfaces/index.js'; import { appState } from './shared.js'; import { getActionContext } from './login.js'; import { runBackgroundRefresh } from './shared.js'; const IP_INTELLIGENCE_REFRESH_INTERVAL_MS = 60_000; let lastIpIntelligenceRefreshAt = 0; export interface INetworkState { connections: interfaces.data.IConnectionInfo[]; connectionsByIP: { [ip: string]: number }; throughputRate: { bytesInPerSecond: number; bytesOutPerSecond: number }; totalBytes: { in: number; out: number }; topIPs: Array<{ ip: string; count: number }>; topIPsByBandwidth: Array<{ ip: string; count: number; bwIn: number; bwOut: number }>; topASNs: interfaces.data.IAsnActivity[]; throughputByIP: Array<{ ip: string; in: number; out: number }>; ipIntelligence: interfaces.data.IIpIntelligenceRecord[]; domainActivity: interfaces.data.IDomainActivity[]; throughputHistory: Array<{ timestamp: number; in: number; out: number }>; frontendConnectionHistory: interfaces.data.IProtocolConnectionHistoryPoint[]; backendConnectionHistory: interfaces.data.IProtocolConnectionHistoryPoint[]; requestsPerSecond: number; requestsTotal: number; backends: interfaces.data.IBackendInfo[]; frontendProtocols: interfaces.data.IProtocolDistribution | null; backendProtocols: interfaces.data.IProtocolDistribution | null; lastUpdated: number; isLoading: boolean; error: string | null; } export const networkStatePart = await appState.getStatePart( 'network', { connections: [], connectionsByIP: {}, throughputRate: { bytesInPerSecond: 0, bytesOutPerSecond: 0 }, totalBytes: { in: 0, out: 0 }, topIPs: [], topIPsByBandwidth: [], topASNs: [], throughputByIP: [], ipIntelligence: [], domainActivity: [], throughputHistory: [], frontendConnectionHistory: [], backendConnectionHistory: [], requestsPerSecond: 0, requestsTotal: 0, backends: [], frontendProtocols: null, backendProtocols: null, lastUpdated: 0, isLoading: false, error: null, }, 'soft' ); export function refreshNetworkIpIntelligence(identity: interfaces.data.IIdentity, ipAddresses: string[]): void { const ips = [...new Set(ipAddresses.map((ip) => ip.trim()).filter(Boolean))] .sort() .slice(0, 100); if (ips.length === 0) return; const now = Date.now(); if (now - lastIpIntelligenceRefreshAt < IP_INTELLIGENCE_REFRESH_INTERVAL_MS) { return; } lastIpIntelligenceRefreshAt = now; runBackgroundRefresh('networkIpIntelligence', 'IP intelligence refresh failed:', async () => { const intelligenceRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_ListIpIntelligence >('/typedrequest', 'listIpIntelligence'); const intelligenceResponse = await intelligenceRequest.fire({ identity, ipAddresses: ips, limit: Math.max(100, ips.length), }); networkStatePart.setState({ ...networkStatePart.getState()!, ipIntelligence: intelligenceResponse.records || [], }); }); } // Fetch Network Stats Action export const fetchNetworkStatsAction = networkStatePart.createAction(async (statePartArg): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; if (!context.identity) return currentState; try { // Get network stats for throughput and IP data const networkStatsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetNetworkStats >('/typedrequest', 'getNetworkStats'); const networkStatsResponse = await networkStatsRequest.fire({ identity: context.identity, }); // Use the connections data for the connection list // and network stats for throughput and IP analytics const connectionsByIP: { [ip: string]: number } = {}; const throughputByIP = new Map(); for (const item of networkStatsResponse.throughputByIP || []) { throughputByIP.set(item.ip, { in: item.in, out: item.out }); } // 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; }); } const connections: interfaces.data.IConnectionInfo[] = Object.entries(connectionsByIP).map(([ip, count]) => { const tp = throughputByIP.get(ip); return { id: `ip-${ip}`, remoteAddress: ip, localAddress: 'server', startTime: 0, protocol: 'https', state: 'connected', bytesReceived: tp?.in || 0, bytesSent: tp?.out || 0, connectionCount: count, }; }); refreshNetworkIpIntelligence(context.identity, [ ...Object.keys(connectionsByIP), ...(networkStatsResponse.topIPs || []).map((item) => item.ip), ...(networkStatsResponse.topIPsByBandwidth || []).map((item) => item.ip), ]); return { connections, connectionsByIP, throughputRate: networkStatsResponse.throughputRate || { bytesInPerSecond: 0, bytesOutPerSecond: 0 }, totalBytes: networkStatsResponse.totalDataTransferred ? { in: networkStatsResponse.totalDataTransferred.bytesIn, out: networkStatsResponse.totalDataTransferred.bytesOut } : { in: 0, out: 0 }, topIPs: networkStatsResponse.topIPs || [], topIPsByBandwidth: networkStatsResponse.topIPsByBandwidth || [], topASNs: networkStatsResponse.topASNs || [], throughputByIP: networkStatsResponse.throughputByIP || [], ipIntelligence: currentState.ipIntelligence, domainActivity: networkStatsResponse.domainActivity || [], throughputHistory: networkStatsResponse.throughputHistory || [], frontendConnectionHistory: networkStatsResponse.frontendConnectionHistory || [], backendConnectionHistory: networkStatsResponse.backendConnectionHistory || [], requestsPerSecond: networkStatsResponse.requestsPerSecond || 0, requestsTotal: networkStatsResponse.requestsTotal || 0, backends: networkStatsResponse.backends || [], frontendProtocols: networkStatsResponse.frontendProtocols || null, backendProtocols: networkStatsResponse.backendProtocols || null, 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', }; } }); // ============================================================================