diff --git a/daemon/core/gui/dialogs/wlanconfig.py b/daemon/core/gui/dialogs/wlanconfig.py index d6da667e..c0c8c845 100644 --- a/daemon/core/gui/dialogs/wlanconfig.py +++ b/daemon/core/gui/dialogs/wlanconfig.py @@ -1,7 +1,3 @@ -""" -wlan configuration -""" - from tkinter import ttk from typing import TYPE_CHECKING @@ -16,6 +12,9 @@ if TYPE_CHECKING: from core.gui.app import Application from core.gui.graph.node import CanvasNode +RANGE_COLOR = "#009933" +RANGE_WIDTH = 3 + class WlanConfigDialog(Dialog): def __init__( @@ -27,15 +26,29 @@ class WlanConfigDialog(Dialog): self.canvas_node = canvas_node self.node = canvas_node.core_node self.config_frame = None + self.range_entry = None self.has_error = False + self.canvas = app.canvas + self.ranges = {} + self.positive_int = self.app.master.register(self.validate_and_update) try: self.config = self.app.core.get_wlan_config(self.node.id) + self.init_draw_range() self.draw() except grpc.RpcError as e: show_grpc_error(e, self.app, self.app) self.has_error = True self.destroy() + def init_draw_range(self): + if self.canvas_node.id in self.canvas.wireless_network: + for cid in self.canvas.wireless_network[self.canvas_node.id]: + x, y = self.canvas.coords(cid) + range_id = self.canvas.create_oval( + x, y, x, y, width=RANGE_WIDTH, outline=RANGE_COLOR, tags="range" + ) + self.ranges[cid] = range_id + def draw(self): self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) @@ -43,6 +56,7 @@ class WlanConfigDialog(Dialog): self.config_frame.draw_config() self.config_frame.grid(sticky="nsew", pady=PADY) self.draw_apply_buttons() + self.top.bind("<Destroy>", self.remove_ranges) def draw_apply_buttons(self): """ @@ -53,6 +67,11 @@ class WlanConfigDialog(Dialog): for i in range(2): frame.columnconfigure(i, weight=1) + self.range_entry = self.config_frame.winfo_children()[0].frame.winfo_children()[ + -1 + ] + self.range_entry.config(validatecommand=(self.positive_int, "%P")) + button = ttk.Button(frame, text="Apply", command=self.click_apply) button.grid(row=0, column=0, padx=PADX, sticky="ew") @@ -68,4 +87,35 @@ class WlanConfigDialog(Dialog): if self.app.core.is_runtime(): session_id = self.app.core.session_id self.app.core.client.set_wlan_config(session_id, self.node.id, config) + self.remove_ranges() self.destroy() + + def remove_ranges(self, event=None): + for cid in self.canvas.find_withtag("range"): + self.canvas.delete(cid) + self.ranges.clear() + + def validate_and_update(self, s: str) -> bool: + """ + custom validation to also redraw the mdr ranges when the range value changes + """ + if len(s) == 0: + return True + try: + int_value = int(s) + if int_value >= 0: + net_range = int_value * self.canvas.ratio + if self.canvas_node.id in self.canvas.wireless_network: + for cid in self.canvas.wireless_network[self.canvas_node.id]: + x, y = self.canvas.coords(cid) + self.canvas.coords( + self.ranges[cid], + x - net_range, + y - net_range, + x + net_range, + y + net_range, + ) + return True + return False + except ValueError: + return False diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index e5d7b5db..0659767d 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -186,6 +186,17 @@ class CanvasEdge: dst_node_type = dst_node.core_node.type is_src_wireless = NodeUtils.is_wireless_node(src_node_type) is_dst_wireless = NodeUtils.is_wireless_node(dst_node_type) + + # update the wlan/EMANE network + wlan_network = self.canvas.wireless_network + if is_src_wireless and not is_dst_wireless: + if self.src not in wlan_network: + wlan_network[self.src] = set() + wlan_network[self.src].add(self.dst) + elif not is_src_wireless and is_dst_wireless: + if self.dst not in wlan_network: + wlan_network[self.dst] = set() + wlan_network[self.dst].add(self.src) return is_src_wireless or is_dst_wireless def check_wireless(self): diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 0869a0d0..7905ed8c 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -42,6 +42,10 @@ class CanvasGraph(tk.Canvas): self.edges = {} self.shapes = {} self.wireless_edges = {} + + # map wireless/EMANE node to the set of MDRs connected to that node + self.wireless_network = {} + self.drawing_edge = None self.grid = None self.shape_drawing = False @@ -113,6 +117,7 @@ class CanvasGraph(tk.Canvas): self.edges.clear() self.shapes.clear() self.wireless_edges.clear() + self.wireless_network.clear() self.drawing_edge = None self.draw_session(session) diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index 3ed5b1d9..7b4ccf31 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -17,7 +17,7 @@ from core.gui.errors import show_grpc_error from core.gui.graph import tags from core.gui.graph.tooltip import CanvasTooltip from core.gui.images import ImageEnum, Images -from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils +from core.gui.nodeutils import ANTENNA_SIZE, EdgeUtils, NodeUtils if TYPE_CHECKING: from core.gui.app import Application @@ -66,9 +66,48 @@ class CanvasNode: def delete(self): logging.debug("Delete canvas node for %s", self.core_node) + + # if node is wlan, EMANE type, remove any existing wireless links between nodes connetect to this node + if NodeUtils.is_wireless_node(self.core_node.type): + nodes = [] + for edge in self.edges: + token = edge.token + if self.id == token[0]: + nodes.append(token[1]) + else: + nodes.append(token[0]) + for i in range(len(nodes)): + for j in range(i + 1, len(nodes)): + token = EdgeUtils.get_token(nodes[i], nodes[j]) + wireless_edge = self.canvas.wireless_edges.pop(token, None) + if wireless_edge: + + self.canvas.nodes[nodes[i]].wireless_edges.remove(wireless_edge) + self.canvas.nodes[nodes[j]].wireless_edges.remove(wireless_edge) + self.canvas.delete(wireless_edge.id) + else: + logging.debug("%s is not a wireless edge", token) + # if node is MDR, remove wireless links to other MDRs + elif NodeUtils.is_mdr_node(self.core_node.type, self.core_node.model): + for wireless_edge in self.wireless_edges: + token = wireless_edge.token + other = token[0] + if other == self.id: + other = token[1] + self.canvas.nodes[other].wireless_edges.discard(wireless_edge) + try: + wlan_edge = self.canvas.wireless_edges.pop(token) + self.canvas.delete(wlan_edge.id) + except KeyError: + logging.error( + "wireless link not found, potentially multiple wireless link issue" + ) + self.delete_antennas() + + self.wireless_edges.clear() + self.canvas.delete(self.id) self.canvas.delete(self.text_id) - self.delete_antennas() def add_antenna(self): x, y = self.canvas.coords(self.id) diff --git a/daemon/core/gui/nodeutils.py b/daemon/core/gui/nodeutils.py index 81aa2cba..870ac8bf 100644 --- a/daemon/core/gui/nodeutils.py +++ b/daemon/core/gui/nodeutils.py @@ -90,6 +90,10 @@ class NodeUtils: def is_rj45_node(cls, node_type: NodeType) -> bool: return node_type in cls.RJ45_NODES + @classmethod + def is_mdr_node(cls, node_type: NodeType, model: str) -> bool: + return cls.is_container_node(node_type) and model == "mdr" + @classmethod def node_icon( cls,