pygui: initial changes to allow multiple wired links between nodes, updated to commonize logic for creating edges and adjustments in the UI for several edge related cases
This commit is contained in:
parent
96dddb687d
commit
29bd6ef7f8
5 changed files with 119 additions and 114 deletions
|
@ -87,14 +87,14 @@ class CoreClient:
|
|||
self.read_config()
|
||||
|
||||
# helpers
|
||||
self.iface_to_edge: Dict[Tuple[int, ...], Tuple[int, ...]] = {}
|
||||
self.iface_to_edge: Dict[Tuple[int, ...], CanvasEdge] = {}
|
||||
self.ifaces_manager: InterfaceManager = InterfaceManager(self.app)
|
||||
self.observer: Optional[str] = None
|
||||
|
||||
# session data
|
||||
self.mobility_players: Dict[int, MobilityPlayer] = {}
|
||||
self.canvas_nodes: Dict[int, CanvasNode] = {}
|
||||
self.links: Dict[Tuple[int, int], CanvasEdge] = {}
|
||||
self.links: Dict[str, CanvasEdge] = {}
|
||||
self.handling_throughputs: Optional[grpc.Future] = None
|
||||
self.handling_cpu_usage: Optional[grpc.Future] = None
|
||||
self.handling_events: Optional[grpc.Future] = None
|
||||
|
@ -225,7 +225,9 @@ class CoreClient:
|
|||
self.app.canvas.add_wired_edge(canvas_node1, canvas_node2, event.link)
|
||||
self.app.canvas.organize()
|
||||
elif event.message_type == MessageType.DELETE:
|
||||
self.app.canvas.delete_wired_edge(canvas_node1, canvas_node2)
|
||||
self.app.canvas.delete_wired_edge(
|
||||
canvas_node1, canvas_node2, event.link
|
||||
)
|
||||
elif event.message_type == MessageType.NONE:
|
||||
self.app.canvas.update_wired_edge(
|
||||
canvas_node1, canvas_node2, event.link
|
||||
|
@ -877,7 +879,7 @@ class CoreClient:
|
|||
|
||||
def create_link(
|
||||
self, edge: CanvasEdge, canvas_src_node: CanvasNode, canvas_dst_node: CanvasNode
|
||||
) -> None:
|
||||
) -> Link:
|
||||
"""
|
||||
Create core link for a pair of canvas nodes, with token referencing
|
||||
the canvas edge.
|
||||
|
@ -888,15 +890,9 @@ class CoreClient:
|
|||
src_iface = None
|
||||
if NodeUtils.is_container_node(src_node.type):
|
||||
src_iface = self.create_iface(canvas_src_node)
|
||||
self.iface_to_edge[(src_node.id, src_iface.id)] = edge.token
|
||||
edge.src_iface = src_iface
|
||||
canvas_src_node.ifaces[src_iface.id] = src_iface
|
||||
dst_iface = None
|
||||
if NodeUtils.is_container_node(dst_node.type):
|
||||
dst_iface = self.create_iface(canvas_dst_node)
|
||||
self.iface_to_edge[(dst_node.id, dst_iface.id)] = edge.token
|
||||
edge.dst_iface = dst_iface
|
||||
canvas_dst_node.ifaces[dst_iface.id] = dst_iface
|
||||
link = Link(
|
||||
type=LinkType.WIRED,
|
||||
node1_id=src_node.id,
|
||||
|
@ -904,9 +900,21 @@ class CoreClient:
|
|||
iface1=src_iface,
|
||||
iface2=dst_iface,
|
||||
)
|
||||
edge.set_link(link)
|
||||
self.links[edge.token] = edge
|
||||
logging.info("added link between %s and %s", src_node.name, dst_node.name)
|
||||
return link
|
||||
|
||||
def save_edge(
|
||||
self, edge: CanvasEdge, canvas_src_node: CanvasNode, canvas_dst_node: CanvasNode
|
||||
) -> None:
|
||||
self.links[edge.token] = edge
|
||||
src_node = canvas_src_node.core_node
|
||||
dst_node = canvas_dst_node.core_node
|
||||
if NodeUtils.is_container_node(src_node.type):
|
||||
src_iface_id = edge.src_iface.id
|
||||
self.iface_to_edge[(src_node.id, src_iface_id)] = edge
|
||||
if NodeUtils.is_container_node(dst_node.type):
|
||||
dst_iface_id = edge.dst_iface.id
|
||||
self.iface_to_edge[(dst_node.id, dst_iface_id)] = edge
|
||||
|
||||
def get_wlan_configs_proto(self) -> List[wlan_pb2.WlanConfig]:
|
||||
configs = []
|
||||
|
|
|
@ -68,10 +68,14 @@ class LinkConfigurationDialog(Dialog):
|
|||
|
||||
def draw(self) -> None:
|
||||
self.top.columnconfigure(0, weight=1)
|
||||
source_name = self.app.canvas.nodes[self.edge.src].core_node.name
|
||||
dest_name = self.app.canvas.nodes[self.edge.dst].core_node.name
|
||||
src_label = self.app.canvas.nodes[self.edge.src].core_node.name
|
||||
if self.edge.link.iface1:
|
||||
src_label += f":{self.edge.link.iface1.name}"
|
||||
dst_label = self.app.canvas.nodes[self.edge.dst].core_node.name
|
||||
if self.edge.link.iface2:
|
||||
dst_label += f":{self.edge.link.iface2.name}"
|
||||
label = ttk.Label(
|
||||
self.top, text=f"Link from {source_name} to {dest_name}", anchor=tk.CENTER
|
||||
self.top, text=f"{src_label} to {dst_label}", anchor=tk.CENTER
|
||||
)
|
||||
label.grid(row=0, column=0, sticky=tk.EW, pady=PADY)
|
||||
|
||||
|
|
|
@ -22,11 +22,14 @@ WIRELESS_COLOR: str = "#009933"
|
|||
ARC_DISTANCE: int = 50
|
||||
|
||||
|
||||
def create_edge_token(src: int, dst: int, network: int = None) -> Tuple[int, ...]:
|
||||
values = [src, dst]
|
||||
if network is not None:
|
||||
values.append(network)
|
||||
return tuple(sorted(values))
|
||||
def create_wireless_token(src: int, dst: int, network: int) -> str:
|
||||
return f"{src}-{dst}-{network}"
|
||||
|
||||
|
||||
def create_edge_token(src: int, dst: int, link: Link) -> str:
|
||||
iface1_id = link.iface1.id if link.iface1 else None
|
||||
iface2_id = link.iface2.id if link.iface2 else None
|
||||
return f"{src}-{iface1_id}-{dst}-{iface2_id}"
|
||||
|
||||
|
||||
def arc_edges(edges) -> None:
|
||||
|
@ -67,17 +70,13 @@ class Edge:
|
|||
self.src: int = src
|
||||
self.dst: int = dst
|
||||
self.arc: int = 0
|
||||
self.token: Optional[Tuple[int, ...]] = None
|
||||
self.token: Optional[str] = None
|
||||
self.src_label: Optional[int] = None
|
||||
self.middle_label: Optional[int] = None
|
||||
self.dst_label: Optional[int] = None
|
||||
self.color: str = EDGE_COLOR
|
||||
self.width: int = EDGE_WIDTH
|
||||
|
||||
@classmethod
|
||||
def create_token(cls, src: int, dst: int) -> Tuple[int, ...]:
|
||||
return tuple(sorted([src, dst]))
|
||||
|
||||
def scaled_width(self) -> float:
|
||||
return self.width * self.canvas.app.app_scale
|
||||
|
||||
|
@ -242,15 +241,17 @@ class CanvasWirelessEdge(Edge):
|
|||
canvas: "CanvasGraph",
|
||||
src: int,
|
||||
dst: int,
|
||||
network_id: int,
|
||||
token: str,
|
||||
src_pos: Tuple[float, float],
|
||||
dst_pos: Tuple[float, float],
|
||||
token: Tuple[int, ...],
|
||||
link: Link,
|
||||
) -> None:
|
||||
logging.debug("drawing wireless link from node %s to node %s", src, dst)
|
||||
super().__init__(canvas, src, dst)
|
||||
self.network_id: int = network_id
|
||||
self.link: Link = link
|
||||
self.token: Tuple[int, ...] = token
|
||||
self.token: str = token
|
||||
self.width: float = WIRELESS_WIDTH
|
||||
color = link.color if link.color else WIRELESS_COLOR
|
||||
self.color: str = color
|
||||
|
@ -362,7 +363,6 @@ class CanvasEdge(Edge):
|
|||
def complete(self, dst: int, linked_wireless: bool) -> None:
|
||||
self.dst = dst
|
||||
self.linked_wireless = linked_wireless
|
||||
self.token = create_edge_token(self.src, self.dst)
|
||||
dst_pos = self.canvas.coords(self.dst)
|
||||
self.move_dst(dst_pos)
|
||||
self.check_wireless()
|
||||
|
|
|
@ -21,8 +21,10 @@ from core.gui.graph.edges import (
|
|||
EDGE_WIDTH,
|
||||
CanvasEdge,
|
||||
CanvasWirelessEdge,
|
||||
Edge,
|
||||
arc_edges,
|
||||
create_edge_token,
|
||||
create_wireless_token,
|
||||
)
|
||||
from core.gui.graph.enums import GraphMode, ScaleOption
|
||||
from core.gui.graph.node import CanvasNode
|
||||
|
@ -69,9 +71,9 @@ class CanvasGraph(tk.Canvas):
|
|||
self.selected: Optional[int] = None
|
||||
self.node_draw: Optional[NodeDraw] = None
|
||||
self.nodes: Dict[int, CanvasNode] = {}
|
||||
self.edges: Dict[int, CanvasEdge] = {}
|
||||
self.edges: Dict[str, CanvasEdge] = {}
|
||||
self.shapes: Dict[int, Shape] = {}
|
||||
self.wireless_edges: Dict[Tuple[int, ...], CanvasWirelessEdge] = {}
|
||||
self.wireless_edges: Dict[str, CanvasWirelessEdge] = {}
|
||||
|
||||
# map wireless/EMANE node to the set of MDRs connected to that node
|
||||
self.wireless_network: Dict[int, Set[int]] = {}
|
||||
|
@ -206,14 +208,9 @@ class CanvasGraph(tk.Canvas):
|
|||
iface_id = iface_throughput.iface_id
|
||||
throughput = iface_throughput.throughput
|
||||
iface_to_edge_id = (node_id, iface_id)
|
||||
token = self.core.iface_to_edge.get(iface_to_edge_id)
|
||||
if not token:
|
||||
continue
|
||||
edge = self.edges.get(token)
|
||||
edge = self.core.iface_to_edge.get(iface_to_edge_id)
|
||||
if edge:
|
||||
edge.set_throughput(throughput)
|
||||
else:
|
||||
del self.core.iface_to_edge[iface_to_edge_id]
|
||||
|
||||
def draw_grid(self) -> None:
|
||||
"""
|
||||
|
@ -230,7 +227,7 @@ class CanvasGraph(tk.Canvas):
|
|||
self.tag_lower(self.rect)
|
||||
|
||||
def add_wired_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None:
|
||||
token = create_edge_token(src.id, dst.id)
|
||||
token = create_edge_token(src.id, dst.id, link)
|
||||
if token in self.edges and link.options.unidirectional:
|
||||
edge = self.edges[token]
|
||||
edge.asymmetric_link = link
|
||||
|
@ -240,72 +237,50 @@ class CanvasGraph(tk.Canvas):
|
|||
src_pos = (node1.position.x, node1.position.y)
|
||||
dst_pos = (node2.position.x, node2.position.y)
|
||||
edge = CanvasEdge(self, src.id, src_pos, dst_pos)
|
||||
edge.linked_wireless = self.is_linked_wireless(src.id, dst.id)
|
||||
edge.token = token
|
||||
edge.dst = dst.id
|
||||
edge.set_link(link)
|
||||
edge.check_wireless()
|
||||
src.edges.add(edge)
|
||||
dst.edges.add(edge)
|
||||
self.edges[edge.token] = edge
|
||||
self.core.links[edge.token] = edge
|
||||
if link.iface1:
|
||||
iface1 = link.iface1
|
||||
self.core.iface_to_edge[(node1.id, iface1.id)] = token
|
||||
src.ifaces[iface1.id] = iface1
|
||||
edge.src_iface = iface1
|
||||
if link.iface2:
|
||||
iface2 = link.iface2
|
||||
self.core.iface_to_edge[(node2.id, iface2.id)] = edge.token
|
||||
dst.ifaces[iface2.id] = iface2
|
||||
edge.dst_iface = iface2
|
||||
self.complete_edge(src, dst, edge)
|
||||
|
||||
def delete_wired_edge(self, src: CanvasNode, dst: CanvasNode) -> None:
|
||||
token = create_edge_token(src.id, dst.id)
|
||||
def delete_wired_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None:
|
||||
token = create_edge_token(src.id, dst.id, link)
|
||||
edge = self.edges.get(token)
|
||||
if not edge:
|
||||
return
|
||||
self.delete_edge(edge)
|
||||
if edge:
|
||||
self.delete_edge(edge)
|
||||
|
||||
def update_wired_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None:
|
||||
token = create_edge_token(src.id, dst.id)
|
||||
token = create_edge_token(src.id, dst.id, link)
|
||||
edge = self.edges.get(token)
|
||||
if not edge:
|
||||
return
|
||||
edge.link.options = deepcopy(link.options)
|
||||
if edge:
|
||||
edge.link.options = deepcopy(link.options)
|
||||
|
||||
def add_wireless_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None:
|
||||
network_id = link.network_id if link.network_id else None
|
||||
token = create_edge_token(src.id, dst.id, network_id)
|
||||
token = create_wireless_token(src.id, dst.id, network_id)
|
||||
if token in self.wireless_edges:
|
||||
logging.warning("ignoring link that already exists: %s", link)
|
||||
return
|
||||
src_pos = self.coords(src.id)
|
||||
dst_pos = self.coords(dst.id)
|
||||
edge = CanvasWirelessEdge(self, src.id, dst.id, src_pos, dst_pos, token, link)
|
||||
edge = CanvasWirelessEdge(
|
||||
self, src.id, dst.id, network_id, token, src_pos, dst_pos, link
|
||||
)
|
||||
self.wireless_edges[token] = edge
|
||||
src.wireless_edges.add(edge)
|
||||
dst.wireless_edges.add(edge)
|
||||
self.tag_raise(src.id)
|
||||
self.tag_raise(dst.id)
|
||||
# update arcs when there are multiple links
|
||||
common_edges = list(src.wireless_edges & dst.wireless_edges)
|
||||
arc_edges(common_edges)
|
||||
self.arc_common_edges(edge)
|
||||
|
||||
def delete_wireless_edge(
|
||||
self, src: CanvasNode, dst: CanvasNode, link: Link
|
||||
) -> None:
|
||||
network_id = link.network_id if link.network_id else None
|
||||
token = create_edge_token(src.id, dst.id, network_id)
|
||||
token = create_wireless_token(src.id, dst.id, network_id)
|
||||
if token not in self.wireless_edges:
|
||||
return
|
||||
edge = self.wireless_edges.pop(token)
|
||||
edge.delete()
|
||||
src.wireless_edges.remove(edge)
|
||||
dst.wireless_edges.remove(edge)
|
||||
# update arcs when there are multiple links
|
||||
common_edges = list(src.wireless_edges & dst.wireless_edges)
|
||||
arc_edges(common_edges)
|
||||
self.arc_common_edges(edge)
|
||||
|
||||
def update_wireless_edge(
|
||||
self, src: CanvasNode, dst: CanvasNode, link: Link
|
||||
|
@ -313,7 +288,7 @@ class CanvasGraph(tk.Canvas):
|
|||
if not link.label:
|
||||
return
|
||||
network_id = link.network_id if link.network_id else None
|
||||
token = create_edge_token(src.id, dst.id, network_id)
|
||||
token = create_wireless_token(src.id, dst.id, network_id)
|
||||
if token not in self.wireless_edges:
|
||||
self.add_wireless_edge(src, dst, link)
|
||||
else:
|
||||
|
@ -454,12 +429,6 @@ class CanvasGraph(tk.Canvas):
|
|||
edge.delete()
|
||||
return
|
||||
|
||||
# ignore repeated edges
|
||||
token = create_edge_token(edge.src, self.selected)
|
||||
if token in self.edges:
|
||||
edge.delete()
|
||||
return
|
||||
|
||||
# rj45 nodes can only support one link
|
||||
if NodeUtils.is_rj45_node(src_node.core_node.type) and src_node.edges:
|
||||
edge.delete()
|
||||
|
@ -468,14 +437,15 @@ class CanvasGraph(tk.Canvas):
|
|||
edge.delete()
|
||||
return
|
||||
|
||||
# set dst node and snap edge to center
|
||||
linked_wireless = self.is_linked_wireless(src_node.id, self.selected)
|
||||
edge.complete(self.selected, linked_wireless)
|
||||
# finalize edge creation
|
||||
self.complete_edge(src_node, dst_node, edge)
|
||||
|
||||
self.edges[edge.token] = edge
|
||||
src_node.edges.add(edge)
|
||||
dst_node.edges.add(edge)
|
||||
self.core.create_link(edge, src_node, dst_node)
|
||||
def arc_common_edges(self, edge: Edge) -> None:
|
||||
src_node = self.nodes[edge.src]
|
||||
dst_node = self.nodes[edge.dst]
|
||||
common_edges = list(src_node.edges & dst_node.edges)
|
||||
common_edges += list(src_node.wireless_edges & dst_node.wireless_edges)
|
||||
arc_edges(common_edges)
|
||||
|
||||
def select_object(self, object_id: int, choose_multiple: bool = False) -> None:
|
||||
"""
|
||||
|
@ -572,6 +542,7 @@ class CanvasGraph(tk.Canvas):
|
|||
if dst_wireless:
|
||||
src_node.delete_antenna()
|
||||
self.core.deleted_canvas_edges([edge])
|
||||
self.arc_common_edges(edge)
|
||||
|
||||
def zoom(self, event: tk.Event, factor: float = None) -> None:
|
||||
if not factor:
|
||||
|
@ -903,20 +874,41 @@ class CanvasGraph(tk.Canvas):
|
|||
def is_selection_mode(self) -> bool:
|
||||
return self.mode == GraphMode.SELECT
|
||||
|
||||
def create_edge(self, source: CanvasNode, dest: CanvasNode) -> None:
|
||||
def create_edge(self, src: CanvasNode, dst: CanvasNode) -> CanvasEdge:
|
||||
"""
|
||||
create an edge between source node and destination node
|
||||
"""
|
||||
token = create_edge_token(source.id, dest.id)
|
||||
if token not in self.edges:
|
||||
pos = (source.core_node.position.x, source.core_node.position.y)
|
||||
edge = CanvasEdge(self, source.id, pos, pos)
|
||||
linked_wireless = self.is_linked_wireless(source.id, dest.id)
|
||||
edge.complete(dest.id, linked_wireless)
|
||||
self.edges[edge.token] = edge
|
||||
self.nodes[source.id].edges.add(edge)
|
||||
self.nodes[dest.id].edges.add(edge)
|
||||
self.core.create_link(edge, source, dest)
|
||||
pos = (src.core_node.position.x, src.core_node.position.y)
|
||||
edge = CanvasEdge(self, src.id, pos, pos)
|
||||
self.complete_edge(src, dst, edge)
|
||||
return edge
|
||||
|
||||
def complete_edge(
|
||||
self,
|
||||
src: CanvasNode,
|
||||
dst: CanvasNode,
|
||||
edge: CanvasEdge,
|
||||
link: Optional[Link] = None,
|
||||
) -> None:
|
||||
linked_wireless = self.is_linked_wireless(src.id, dst.id)
|
||||
edge.complete(dst.id, linked_wireless)
|
||||
src.edges.add(edge)
|
||||
dst.edges.add(edge)
|
||||
if link is None:
|
||||
link = self.core.create_link(edge, src, dst)
|
||||
if link.iface1:
|
||||
iface1 = link.iface1
|
||||
src.ifaces[iface1.id] = iface1
|
||||
edge.src_iface = iface1
|
||||
if link.iface2:
|
||||
iface2 = link.iface2
|
||||
dst.ifaces[iface2.id] = iface2
|
||||
edge.dst_iface = iface2
|
||||
edge.set_link(link)
|
||||
edge.token = create_edge_token(src.id, dst.id, edge.link)
|
||||
self.arc_common_edges(edge)
|
||||
self.edges[edge.token] = edge
|
||||
self.core.save_edge(edge, src, dst)
|
||||
|
||||
def copy(self) -> None:
|
||||
if self.core.is_runtime():
|
||||
|
@ -970,13 +962,12 @@ class CanvasGraph(tk.Canvas):
|
|||
if edge.src not in to_copy_ids or edge.dst not in to_copy_ids:
|
||||
if canvas_node.id == edge.src:
|
||||
dst_node = self.nodes[edge.dst]
|
||||
self.create_edge(node, dst_node)
|
||||
token = create_edge_token(node.id, dst_node.id)
|
||||
copy_edge = self.create_edge(node, dst_node)
|
||||
elif canvas_node.id == edge.dst:
|
||||
src_node = self.nodes[edge.src]
|
||||
self.create_edge(src_node, node)
|
||||
token = create_edge_token(src_node.id, node.id)
|
||||
copy_edge = self.edges[token]
|
||||
copy_edge = self.create_edge(src_node, node)
|
||||
else:
|
||||
continue
|
||||
copy_link = copy_edge.link
|
||||
iface1_id = copy_link.iface1.id if copy_link.iface1 else None
|
||||
iface2_id = copy_link.iface2.id if copy_link.iface2 else None
|
||||
|
@ -1003,13 +994,11 @@ class CanvasGraph(tk.Canvas):
|
|||
|
||||
# copy link and link config
|
||||
for edge in to_copy_edges:
|
||||
src_node_id = copy_map[edge.token[0]]
|
||||
dst_node_id = copy_map[edge.token[1]]
|
||||
src_node_id = copy_map[edge.src]
|
||||
dst_node_id = copy_map[edge.dst]
|
||||
src_node_copy = self.nodes[src_node_id]
|
||||
dst_node_copy = self.nodes[dst_node_id]
|
||||
self.create_edge(src_node_copy, dst_node_copy)
|
||||
token = create_edge_token(src_node_copy.id, dst_node_copy.id)
|
||||
copy_edge = self.edges[token]
|
||||
copy_edge = self.create_edge(src_node_copy, dst_node_copy)
|
||||
copy_link = copy_edge.link
|
||||
iface1_id = copy_link.iface1.id if copy_link.iface1 else None
|
||||
iface2_id = copy_link.iface2.id if copy_link.iface2 else None
|
||||
|
|
|
@ -247,14 +247,18 @@ class CanvasNode:
|
|||
)
|
||||
unlink_menu = tk.Menu(self.context)
|
||||
for edge in self.edges:
|
||||
other_id = edge.src
|
||||
if self.id == other_id:
|
||||
link = edge.link
|
||||
if self.id == edge.src:
|
||||
other_id = edge.dst
|
||||
other_iface = link.iface2.name if link.iface2 else None
|
||||
else:
|
||||
other_id = edge.src
|
||||
other_iface = link.iface1.name if link.iface1 else None
|
||||
other_node = self.canvas.nodes[other_id]
|
||||
other_name = other_node.core_node.name
|
||||
label = f"{other_name}:{other_iface}" if other_iface else other_name
|
||||
func_unlink = functools.partial(self.click_unlink, edge)
|
||||
unlink_menu.add_command(
|
||||
label=other_node.core_node.name, command=func_unlink
|
||||
)
|
||||
unlink_menu.add_command(label=label, command=func_unlink)
|
||||
themes.style_menu(unlink_menu)
|
||||
self.context.add_cascade(label="Unlink", menu=unlink_menu)
|
||||
edit_menu = tk.Menu(self.context)
|
||||
|
|
Loading…
Reference in a new issue