From 2c73a643e93430bbc95ae120bf14a2948dc7cb5f Mon Sep 17 00:00:00 2001 From: sitzmaa Date: Sat, 15 Feb 2025 14:58:44 -0800 Subject: [PATCH] some network graphing improvements --- core/run.py | 2 +- frontend/go_server/back_client.go | 81 +++++++++++++++ frontend/go_server/main.go | 148 +++++++++++++++++----------- frontend/go_server/socket_client.go | 36 ------- frontend/gui/gui.py | 49 ++++++--- frontend/gui/main.py | 61 +++++++++++- 6 files changed, 270 insertions(+), 107 deletions(-) create mode 100644 frontend/go_server/back_client.go delete mode 100644 frontend/go_server/socket_client.go diff --git a/core/run.py b/core/run.py index 299659f..0c0d94c 100644 --- a/core/run.py +++ b/core/run.py @@ -42,7 +42,7 @@ def main(): if not args.no_gui: go_server_cmd ="cd ./frontend/go_server && go run main.go" open_terminal(go_server_cmd) - time.sleep(0.5) + time.sleep(2) gui_cmd = "source venv/bin/activate && python3 ./frontend/gui/main.py" open_terminal(gui_cmd) diff --git a/frontend/go_server/back_client.go b/frontend/go_server/back_client.go new file mode 100644 index 0000000..744bb69 --- /dev/null +++ b/frontend/go_server/back_client.go @@ -0,0 +1,81 @@ +package main + +import ( + "fmt" + "log" + "net" + "os" + "time" +) + +const ( + defaultServerAddress = "localhost:12345" // Default address if no flag is provided +) + +// Establish connection to the C++ backend server +func connectToBackend(serverAddress string) (net.Conn, error) { + conn, err := net.Dial("tcp", serverAddress) + if err != nil { + return nil, fmt.Errorf("error connecting to server: %v", err) + } + return conn, nil +} + +// Send request to fetch network state +func sendRequest(conn net.Conn) (string, error) { + _, err := conn.Write([]byte("GET_NETWORK_STATE\n")) + if err != nil { + return "", fmt.Errorf("error sending request: %v", err) + } + + // Read the response from the server + buf := make([]byte, 1024) + n, err := conn.Read(buf) + if err != nil { + return "", fmt.Errorf("error reading response: %v", err) + } + + return string(buf[:n]), nil +} + +// Fetch network state from the C++ backend +func fetchNetworkState(serverAddress string) (string, error) { + conn, err := connectToBackend(serverAddress) + if err != nil { + return "", err + } + defer conn.Close() + + return sendRequest(conn) +} + + +func main() { + // Establish connection to the C++ backend + conn, err := net.Dial("tcp", serverAddress) + if err != nil { + log.Fatalf("Error connecting to server: %v", err) + } + defer conn.Close() + + // Send true state to alert the server that this is a GUI-type connection + _, err = conn.Write([]byte("1\n")) // Sending "1" to indicate GUI type + if err != nil { + log.Fatalf("Error sending connection type: %v", err) + } + + // Send request to fetch network state + _, err = conn.Write([]byte("GET_NETWORK_STATE\n")) + if err != nil { + log.Fatalf("Error sending request: %v", err) + } + + // Read the response from the server + buf := make([]byte, 1024) + n, err := conn.Read(buf) + if err != nil { + log.Fatalf("Error reading response: %v", err) + } + + fmt.Printf("Received network state: %s\n", string(buf[:n])) +} \ No newline at end of file diff --git a/frontend/go_server/main.go b/frontend/go_server/main.go index 64e7983..8807fbb 100644 --- a/frontend/go_server/main.go +++ b/frontend/go_server/main.go @@ -1,75 +1,113 @@ package main import ( - "context" - "encoding/json" - "fmt" - "log" - "net" - - pb "netmap/proto" // Ensure this is the correct relative path to your generated proto file - "google.golang.org/grpc" + "context" + "fmt" + "log" + "net" + "time" + + "google.golang.org/grpc" + "netmap/proto" // Adjust this import path as needed +) + +const ( + grpcServerAddress = "localhost:50051" // Python gRPC server address + backendServerAddr = "localhost:12345" // C++ backend server address ) -// NetMapServer struct implements the gRPC server interface -type NetMapServer struct { - pb.UnimplementedNetMapServer +// Define the server struct implementing the proto interface for gRPC communication with Python +type server struct { + proto.UnimplementedNetMapServer } -// GetState: Fetch current netmap state and send it to the Python client -func (s *NetMapServer) GetState(ctx context.Context, req *pb.NetworkDataRequest) (*pb.NetworkDataResponse, error) { - // Fetch network state from the C++ backend - data := queryCPlusPlusBackend() +// SendNetworkData is the method that sends data to the Python client and receives a response +func (s *server) SendNetworkData(ctx context.Context, req *proto.NetworkDataRequest) (*proto.NetworkDataResponse, error) { + fmt.Println("Received data from Python frontend:", req.NetworkData) + // Process the data if needed, then send a response back to the Python client + return &proto.NetworkDataResponse{Message: "Data received and processed!"}, nil +} - // Convert to JSON for Python GUI - jsonData, err := json.Marshal(data) - if err != nil { - return nil, fmt.Errorf("failed to marshal data: %v", err) - } +func startGRPCServer() { + // Set up the gRPC server to listen on port 50051 for the Python client + listener, err := net.Listen("tcp", grpcServerAddress) + if err != nil { + log.Fatalf("Failed to listen on port 50051: %v", err) + } + defer listener.Close() - // Return the response with the JSON data - return &pb.NetworkDataResponse{Message: string(jsonData)}, nil + // Create a new gRPC server + grpcServer := grpc.NewServer() + + // Register the gRPC server with the NetMap implementation + proto.RegisterNetMapServer(grpcServer, &server{}) + + // Start serving requests for the Python frontend + fmt.Println("Starting gRPC server for Python frontend on port 50051...") + if err := grpcServer.Serve(listener); err != nil { + log.Fatalf("Failed to serve gRPC server: %v", err) + } } -func (s *NetMapServer) SendNetworkData(ctx context.Context, req *pb.NetworkDataRequest) (*pb.NetworkDataResponse, error) { - // Handle the request and return a response - fmt.Println("Received network data:", req.GetNetworkData()) - return &pb.NetworkDataResponse{ - Message: "Network data received successfully", - }, nil +func startBackendClient() { + // Set up the connection to the C++ backend server + conn, err := net.Dial("tcp", backendServerAddr) + if err != nil { + log.Fatalf("Failed to connect to C++ backend server: %v", err) + } + defer conn.Close() + + // Send request to the C++ backend for network state + _, err = conn.Write([]byte("1\n")) + if err != nil { + log.Fatalf("Error sending request to C++ backend: %v", err) + } + + // Read the response from the C++ backend + buf := make([]byte, 1024) + n, err := conn.Read(buf) + if err != nil { + log.Fatalf("Error reading response from C++ backend: %v", err) + } + + networkState := string(buf[:n]) + fmt.Printf("Received network state from C++ backend: %s\n", networkState) + + // Sending the network state to the Python client (if necessary) + sendToPythonFrontend(networkState) } -// Function to query C++ backend (over a socket or another method) -func queryCPlusPlusBackend() map[string]interface{} { - // Placeholder: Implement the actual socket or inter-process communication with C++ backend - // Example mock data returned from the C++ backend for demonstration - return map[string]interface{}{ - "nodes": []map[string]string{ - {"ip": "192.168.1.1", "mac": "AA:BB:CC:DD:EE:FF"}, - }, - "links": []map[string]string{ - {"source": "192.168.1.1", "target": "192.168.1.2"}, - }, - } +func sendToPythonFrontend(networkState string) { + // Set up connection to Python gRPC server + conn, err := grpc.Dial(grpcServerAddress, grpc.WithInsecure(), grpc.WithBlock()) + if err != nil { + log.Fatalf("Failed to connect to Python gRPC server: %v", err) + } + defer conn.Close() + + client := proto.NewNetMapClient(conn) + + // Send network data to the Python frontend + req := &proto.NetworkDataRequest{NetworkData: networkState} + resp, err := client.SendNetworkData(context.Background(), req) + if err != nil { + log.Fatalf("Failed to send request to Python frontend: %v", err) + } + + // Print the response from the Python frontend + fmt.Println("Response from Python frontend:", resp.GetMessage()) } func main() { - // Listen for incoming connections on TCP port 50051 - lis, err := net.Listen("tcp", ":50051") - if err != nil { - log.Fatalf("Failed to listen on port 50051: %v", err) - } - - // Create a new gRPC server - grpcServer := grpc.NewServer() + // Start the gRPC server in a separate goroutine for the Python frontend + go startGRPCServer() - // Register the NetMapServer with the gRPC server - pb.RegisterNetMapServer(grpcServer, &NetMapServer{}) + // Simulate a delay to ensure the gRPC server is ready + time.Sleep(1 * time.Second) - fmt.Println("gRPC Server is running on port 50051") + // Start the backend client to fetch network data and send it to Python frontend + startBackendClient() - // Start serving requests - if err := grpcServer.Serve(lis); err != nil { - log.Fatalf("Failed to serve gRPC server: %v", err) - } + // To keep the main function running while servers are active + select {} } \ No newline at end of file diff --git a/frontend/go_server/socket_client.go b/frontend/go_server/socket_client.go deleted file mode 100644 index d29b61a..0000000 --- a/frontend/go_server/socket_client.go +++ /dev/null @@ -1,36 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net" - "os" -) - -const ( - serverAddress = "localhost:12345" // Address of your C++ backend -) - -func main() { - // Establish connection to the C++ backend server - conn, err := net.Dial("tcp", serverAddress) - if err != nil { - log.Fatalf("Error connecting to server: %v", err) - } - defer conn.Close() - - // Send request to fetch network state - _, err = conn.Write([]byte("GET_NETWORK_STATE\n")) - if err != nil { - log.Fatalf("Error sending request: %v", err) - } - - // Read the response from the server - buf := make([]byte, 1024) - n, err := conn.Read(buf) - if err != nil { - log.Fatalf("Error reading response: %v", err) - } - - fmt.Printf("Received network state: %s\n", string(buf[:n])) -} \ No newline at end of file diff --git a/frontend/gui/gui.py b/frontend/gui/gui.py index 013fd42..22f8413 100644 --- a/frontend/gui/gui.py +++ b/frontend/gui/gui.py @@ -5,6 +5,7 @@ QGraphicsScene, QGraphicsView, QGraphicsEllipseItem, QGraphicsTextItem, QVBoxLayout, QWidget, QLineEdit) from PyQt6.QtCore import Qt, QThread, pyqtSignal from PyQt6.QtGui import QBrush, QColor +import seed # Assuming SEED library is installed class NetworkNode(QGraphicsEllipseItem): def __init__(self, x, y, ip, details): @@ -58,6 +59,9 @@ def __init__(self, port): self.backend_thread.new_data.connect(self.update_network) self.backend_thread.start() + # SEED Network Visualization Setup + self.network = seed.Network() + def scan_network(self): self.backend_thread.send_command("scan") @@ -71,16 +75,39 @@ def update_network(self, data): self.scene.clear() nodes = data.split(";") x, y = 50, 50 - for node_info in nodes: - parts = node_info.split(",") - if len(parts) >= 2: - ip, details = parts[0], parts[1] - node = NetworkNode(x, y, ip, details) - self.scene.addItem(node) - x += 100 - if x > 800: - x = 50 - y += 100 + + # Example static network data for visualization + # Normally, this would come from your backend data + example_nodes = [ + {"name": "Node 1", "type": "Router", "ip": "192.168.0.1", "details": "Main Router"}, + {"name": "Node 2", "type": "Device", "ip": "192.168.0.2", "details": "Device A"}, + {"name": "Node 3", "type": "Device", "ip": "192.168.0.3", "details": "Device B"} + ] + example_edges = [ + {"source": "Node 1", "destination": "Node 2"}, + {"source": "Node 1", "destination": "Node 3"} + ] + + # Add nodes to SEED network (for visualization) + for node_info in example_nodes: + self.network.add_node(node_info['name'], **node_info) + + # Add edges to SEED network + for edge in example_edges: + self.network.add_edge(edge['source'], edge['destination']) + + # Visualization with SEED + self.network.display() + + # Also adding graphical elements to PyQt6's QGraphicsView + for node_info in example_nodes: + ip, details = node_info['ip'], node_info['details'] + node = NetworkNode(x, y, ip, details) + self.scene.addItem(node) + x += 100 + if x > 800: + x = 50 + y += 100 class BackendThread(QThread): new_data = pyqtSignal(str) @@ -112,4 +139,4 @@ def run(): sys.exit(app.exec()) if __name__ == "__main__": - run() + run() \ No newline at end of file diff --git a/frontend/gui/main.py b/frontend/gui/main.py index 568da3c..14f84fd 100644 --- a/frontend/gui/main.py +++ b/frontend/gui/main.py @@ -1,6 +1,10 @@ import sys from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel, QVBoxLayout, QWidget -import grpc_client +import grpc_client # Ensure this is your gRPC client module +import networkx as nx +import pyqtgraph as pg +from pyqtgraph.Qt import QtCore + class MainWindow(QMainWindow): def __init__(self): @@ -9,26 +13,75 @@ def __init__(self): self.setWindowTitle("Network Visualizer") self.setGeometry(100, 100, 800, 600) + # Central widget and layout self.central_widget = QWidget() self.setCentralWidget(self.central_widget) self.layout = QVBoxLayout(self.central_widget) + # Label for status self.label = QLabel("Waiting for network state...", self) self.layout.addWidget(self.label) + # PyQtGraph widget for NetworkX graph + self.graph_widget = pg.GraphicsLayoutWidget() + self.layout.addWidget(self.graph_widget) + + # Create a NetworkX graph (will be updated later) + self.network_graph = nx.Graph() + self.plot = self.graph_widget.addPlot(title="Network Visualization") + + # Create a network visualization plot + self.network_plot = self.plot.plot() + self.show() - def update_network_state(self, message): + def update_network_state(self, message, graph_data): + """Update the GUI with network data and refresh the graph visualization.""" self.label.setText(message) + self.update_network_graph(graph_data) + + def update_network_graph(self, graph_data): + """Update the NetworkX graph and render it on the PyQt6 window.""" + # Clear existing graph data + self.network_graph.clear() + + # Add nodes and edges from the graph data + for node in graph_data['nodes']: + self.network_graph.add_node(node['name'], **node) + for edge in graph_data['edges']: + self.network_graph.add_edge(edge['source'], edge['destination']) + + # Create positions for nodes using spring layout + pos = nx.spring_layout(self.network_graph) + + # Extract node positions and names for plotting + node_positions = {node: (x * 100, y * 100) for node, (x, y) in pos.items()} + + # Prepare edge data for plotting + edges = [] + for edge in self.network_graph.edges(): + node1, node2 = edge + edges.append((node_positions[node1], node_positions[node2])) + + # Render the network graph + self.plot.clear() + for edge in edges: + self.plot.plot([edge[0][0], edge[1][0]], [edge[0][1], edge[1][1]], pen='r') + for node, (x, y) in node_positions.items(): + self.plot.plot([x], [y], symbol='o', symbolSize=10, symbolBrush='b') + def main(): app = QApplication(sys.argv) window = MainWindow() - # Run the gRPC client to communicate with the Go server and update the UI - grpc_client.run() + graph_data = grpc_client.run() + + # Update the window with the fetched network data + #window.update_network_state(message, graph_data) sys.exit(app.exec()) + if __name__ == "__main__": main() \ No newline at end of file