From f13f38c9ca20b0b2c0c814f9a24c799dee66c51a Mon Sep 17 00:00:00 2001 From: Aleksei Fedorov Date: Wed, 3 Sep 2025 17:46:45 +0000 Subject: [PATCH] Add active network connections functionality - Add ActiveConnectionsManager class for managing network connections - Display active connections below system calls - Auto-update every 3 seconds - Temporarily show all connections (including local) - Integrate with existing system calls visualization --- app.py | 148 ++++++++++++++++++++++++-------- index.html | 1 + static/js/active-connections.js | 124 ++++++++++++++++++++++++++ static/js/bezier_curves.js | 0 static/js/main.js | 7 ++ static/js/nginx_files.js | 0 static/js/syscalls.js | 6 +- 7 files changed, 248 insertions(+), 38 deletions(-) mode change 100755 => 100644 app.py create mode 100755 static/js/active-connections.js mode change 100644 => 100755 static/js/bezier_curves.js mode change 100644 => 100755 static/js/main.js mode change 100644 => 100755 static/js/nginx_files.js diff --git a/app.py b/app.py old mode 100755 new mode 100644 index c65bb90..b70b62e --- a/app.py +++ b/app.py @@ -158,37 +158,6 @@ def get_mock_process_kernel_map(): "cron": ["kernel/time/timer.c", "kernel/sched/clock.c"] } -def get_nginx_open_files(): - """Get open files for nginx process""" - try: - nginx_files = [] - for proc in psutil.process_iter(["pid", "name"]): - if proc.info["name"] == "nginx": - try: - files = proc.open_files() - for file in files: - if file.path and os.path.exists(file.path): - nginx_files.append({ - "path": file.path, - "fd": file.fd, - "pid": proc.info["pid"] - }) - except (psutil.AccessDenied, psutil.NoSuchProcess): - continue - return get_mock_nginx_files() - except Exception as e: - print(f"Error getting nginx files: {e}") - return get_mock_nginx_files() - -def get_mock_nginx_files(): - """Mock data for nginx open files""" - return [ - {"path": "/etc/nginx/nginx.conf", "fd": 0, "pid": 123}, - {"path": "/var/log/nginx/access.log", "fd": 1, "pid": 123}, - {"path": "/var/log/nginx/error.log", "fd": 2, "pid": 123}, - {"path": "/var/www/html/index.html", "fd": 3, "pid": 123}, - {"path": "/etc/nginx/sites-enabled/default", "fd": 4, "pid": 123} - ] # API Endpoints @app.route('/') @@ -249,6 +218,63 @@ def health_check(): }) # Static files handling +@app.route('/static/') +def static_files(filename): + """Serve static files""" + return send_from_directory(app.config['STATIC_FOLDER'], filename) + +# Error handling +# Active connections functions +# Nginx files functions +def get_nginx_open_files(): + """Get open files for Nginx process""" + try: + import psutil + nginx_processes = [] + for proc in psutil.process_iter(["pid", "name", "open_files"]): + try: + if proc.info["name"] and "nginx" in proc.info["name"].lower(): + nginx_processes.append(proc.info["pid"]) + except (psutil.NoSuchProcess, psutil.AccessDenied): + continue + + if nginx_processes: + # Get open files for first nginx process + proc = psutil.Process(nginx_processes[0]) + open_files = proc.open_files() + + # Filter and format file paths + files = [] + for file in open_files: + if file.path: + # Extract relative path from full path + if "/etc/nginx/" in file.path: + rel_path = file.path.split("/etc/nginx/")[-1] + files.append({"path": f"nginx/{rel_path}", "type": "config"}) + elif "/var/log/nginx/" in file.path: + rel_path = file.path.split("/var/log/nginx/")[-1] + files.append({"path": f"nginx/logs/{rel_path}", "type": "log"}) + else: + files.append({"path": file.path, "type": "other"}) + + return files[:10] # Limit to 10 files + else: + return get_mock_nginx_files() + + except Exception as e: + print(f"Error getting nginx files: {e}") + return get_mock_nginx_files() + +def get_mock_nginx_files(): + """Mock data for nginx files""" + return [ + {"path": "nginx/nginx.conf", "type": "config"}, + {"path": "nginx/sites-enabled/default", "type": "config"}, + {"path": "nginx/conf.d/default.conf", "type": "config"}, + {"path": "nginx/logs/access.log", "type": "log"}, + {"path": "nginx/logs/error.log", "type": "log"} + ] + @app.route("/api/nginx-files") def nginx_files(): """API for nginx open files""" @@ -257,12 +283,60 @@ def nginx_files(): return jsonify({"files": files}) except Exception as e: return jsonify({"error": str(e)}), 500 -@app.route('/static/') -def static_files(filename): - """Serve static files""" - return send_from_directory(app.config['STATIC_FOLDER'], filename) +def get_active_connections(): + """Get active network connections""" + try: + connections = [] + # Get TCP connections + with open("/proc/net/tcp", "r") as f: + lines = f.readlines()[1:] # Skip header + for line in lines: + parts = line.strip().split() + if len(parts) >= 4: + local_addr = parts[1] + remote_addr = parts[2] + state = parts[3] + + # Convert hex addresses to readable format + local_ip = ".".join([str(int(local_addr.split(":")[0][i:i+2], 16)) for i in range(0, 8, 2)]) + local_port = int(local_addr.split(":")[1], 16) + + if remote_addr != "00000000:0000": # Not listening + remote_ip = ".".join([str(int(remote_addr.split(":")[0][i:i+2], 16)) for i in range(0, 8, 2)]) + remote_port = int(remote_addr.split(":")[1], 16) + + connections.append({ + "local": f"{local_ip}:{local_port}", + "remote": f"{remote_ip}:{remote_port}", + "state": state, + "type": "TCP" + }) + + # Limit to first 20 connections for display + return connections[:20] + + except Exception as e: + print(f"Error getting active connections: {e}") + return get_mock_active_connections() -# Error handling +def get_mock_active_connections(): + """Mock data for active connections""" + return [ + {"local": "127.0.0.1:22", "remote": "192.168.1.100:54321", "state": "01", "type": "TCP"}, + {"local": "0.0.0.0:80", "remote": "10.0.0.50:12345", "state": "01", "type": "TCP"}, + {"local": "127.0.0.1:3306", "remote": "172.16.0.10:65432", "state": "01", "type": "TCP"}, + {"local": "0.0.0.0:443", "remote": "203.0.113.0:54321", "state": "01", "type": "TCP"}, + {"local": "127.0.0.1:5001", "remote": "192.168.1.101:12345", "state": "01", "type": "TCP"} + ] + +@app.route("/api/active-connections") +def active_connections(): + """API for active network connections""" + try: + connections = get_active_connections() + return jsonify({"connections": connections}) + except Exception as e: + return jsonify({"error": str(e)}), 500 @app.errorhandler(404) def not_found(error): return jsonify({'error': 'Not found'}), 404 @@ -282,6 +356,7 @@ def internal_error(error): print(f" - {Config.API_PREFIX}/syscalls-realtime") print(f" - {Config.API_PREFIX}/kernel-data") print(f" - {Config.API_PREFIX}/process-kernel-map") + print(f" - {Config.API_PREFIX}/nginx-files") print(f" - /health") app.run( @@ -290,3 +365,4 @@ def internal_error(error): debug=Config.DEBUG, threaded=True ) + diff --git a/index.html b/index.html index 2ac4c05..22db10e 100755 --- a/index.html +++ b/index.html @@ -25,6 +25,7 @@ + diff --git a/static/js/active-connections.js b/static/js/active-connections.js new file mode 100755 index 0000000..b02916a --- /dev/null +++ b/static/js/active-connections.js @@ -0,0 +1,124 @@ +// Module for working with active network connections +class ActiveConnectionsManager { + constructor() { + console.log("ActiveConnectionsManager: constructor called"); + this.currentConnections = []; + this.updateInterval = null; + this.updateCallback = null; + } + + // Update active connections data + async updateConnectionsTable() { + try { + console.log("ActiveConnectionsManager: updateConnectionsTable called"); + const response = await fetch('/api/active-connections'); + const data = await response.json(); + + console.log("API response:", data); + + if (data.connections) { + console.log("Total connections:", data.connections.length); + + // Filter out local connections (127.0.0.1, 0.0.0.0) + this.currentConnections = data.connections.filter(conn => { + const localIP = conn.local.split(':')[0]; + return true; // Temporarily show all connections + }); + + console.log("Filtered connections:", this.currentConnections.length); + + this.renderConnectionsTable(); + + // Call callback if set + if (this.updateCallback) { + this.updateCallback(data); + } + } + } catch (error) { + console.error('Error getting active connections:', error); + this.useFallbackData(); + } + } + + // Fallback data + useFallbackData() { + this.currentConnections = [ + {local: '192.168.1.100:22', remote: '10.0.0.50:54321', state: '01', type: 'TCP'}, + {local: '203.0.113.0:80', remote: '172.16.0.10:12345', state: '01', type: 'TCP'}, + {local: '198.51.100.0:443', remote: '192.168.1.101:65432', state: '01', type: 'TCP'}, + {local: '203.0.113.0:8080', remote: '10.0.0.100:54321', state: '01', type: 'TCP'}, + {local: '198.51.100.0:3306', remote: '172.16.0.20:12345', state: '01', type: 'TCP'} + ]; + this.renderConnectionsTable(); + } + + // Render active connections table below system calls + renderConnectionsTable() { + const svg = d3.select('svg'); + + // Clear old elements + svg.selectAll('.connection-box, .connection-text').remove(); + + // Calculate starting Y position (below system calls) + const startY = 35 + 10 * 30 + 20; // 10 system calls * 30px + 20px gap + + // Create new elements for active connections + this.currentConnections.forEach((connection, i) => { + const displayText = `${connection.local} → ${connection.remote}`; + + svg.append('rect') + .attr('x', 30) + .attr('y', startY + i * 30) + .attr('width', 230) + .attr('height', 22) + .attr('class', 'item-box connection-box'); + + svg.append('text') + .attr('x', 38) + .attr('y', startY + 15 + i * 30) + .text(displayText) + .attr('class', 'socket-text connection-text'); + }); + } + + // Start auto update + startAutoUpdate(intervalMs = 3000) { + console.log("ActiveConnectionsManager: startAutoUpdate called"); + this.updateConnectionsTable(); + this.updateInterval = setInterval(() => { + this.updateConnectionsTable(); + }, intervalMs); + } + + // Stop auto update + stopAutoUpdate() { + if (this.updateInterval) { + clearInterval(this.updateInterval); + this.updateInterval = null; + } + } + + // Set update callback + setUpdateCallback(callback) { + this.updateCallback = callback; + } + + // Get current data + getCurrentConnections() { + return this.currentConnections; + } + + // Restore state + restoreState() { + if (this.currentConnections.length > 0) { + this.renderConnectionsTable(); + } else { + this.updateConnectionsTable(); + } + } +} + +// Export for use in other modules +if (typeof module !== 'undefined' && module.exports) { + module.exports = ActiveConnectionsManager; +} diff --git a/static/js/bezier_curves.js b/static/js/bezier_curves.js old mode 100644 new mode 100755 diff --git a/static/js/main.js b/static/js/main.js old mode 100644 new mode 100755 index 41b3977..f634608 --- a/static/js/main.js +++ b/static/js/main.js @@ -179,6 +179,10 @@ function initApp() { // Initialize system calls manager syscallsManager = new SyscallsManager(); + // Initialize active connections manager + const connectionsManager = new ActiveConnectionsManager(); + connectionsManager.startAutoUpdate(3000); + window.nginxFilesManager = new NginxFilesManager(); // Draw main interface draw(); @@ -205,6 +209,9 @@ function setupEventListeners() { if (syscallsManager) { syscallsManager.stopAutoUpdate(); } + if (connectionsManager) { + connectionsManager.stopAutoUpdate(); + } }); } diff --git a/static/js/nginx_files.js b/static/js/nginx_files.js old mode 100644 new mode 100755 diff --git a/static/js/syscalls.js b/static/js/syscalls.js index 58b60b7..0621ac6 100755 --- a/static/js/syscalls.js +++ b/static/js/syscalls.js @@ -9,7 +9,7 @@ class SyscallsManager { // Update system calls data async updateSyscallsTable() { try { - const response = await fetch('/api/syscalls-realtime'); + const response = await fetch("/api/syscalls-realtime"); const data = await response.json(); if (data.syscalls) { @@ -22,7 +22,7 @@ class SyscallsManager { } } } catch (error) { - console.error('Error getting system calls:', error); + console.error('Error getting active connections:', error); this.useFallbackData(); } } @@ -68,6 +68,8 @@ class SyscallsManager { .text(displayText) .attr("class", "socket-text syscall-text"); }); + // Display active connections below system calls + // this.displayActiveConnections(); } // Start auto update