pygui: refactoring of nodeutils and its usage, fixed issue with drawing custom nodes and copying services

This commit is contained in:
Blake Harnden 2021-02-18 10:47:20 -08:00
parent 422a1a500e
commit 47ac4c850d
12 changed files with 194 additions and 191 deletions

View file

@ -7,7 +7,9 @@ from typing import Any, Dict, Optional, Type
import grpc
from core.gui import appconfig, themes
from core.gui import appconfig
from core.gui import nodeutils as nutils
from core.gui import themes
from core.gui.appconfig import GuiConfig
from core.gui.coreclient import CoreClient
from core.gui.dialogs.error import ErrorDialog
@ -16,7 +18,6 @@ from core.gui.frames.default import DefaultInfoFrame
from core.gui.graph.manager import CanvasManager
from core.gui.images import ImageEnum, Images
from core.gui.menubar import Menubar
from core.gui.nodeutils import NodeUtils
from core.gui.statusbar import StatusBar
from core.gui.themes import PADY
from core.gui.toolbar import Toolbar
@ -29,7 +30,7 @@ class Application(ttk.Frame):
def __init__(self, proxy: bool, session_id: int = None) -> None:
super().__init__()
# load node icons
NodeUtils.setup()
nutils.setup()
# widgets
self.menubar: Optional[Menubar] = None

View file

@ -38,6 +38,7 @@ from core.api.grpc.wrappers import (
SessionState,
ThroughputsEvent,
)
from core.gui import nodeutils as nutils
from core.gui.appconfig import XMLS_PATH, CoreServer, Observer
from core.gui.dialogs.emaneinstall import EmaneInstallDialog
from core.gui.dialogs.error import ErrorDialog
@ -47,7 +48,7 @@ from core.gui.graph.edges import CanvasEdge
from core.gui.graph.node import CanvasNode
from core.gui.graph.shape import Shape
from core.gui.interface import InterfaceManager
from core.gui.nodeutils import NodeDraw, NodeUtils
from core.gui.nodeutils import NodeDraw
if TYPE_CHECKING:
from core.gui.app import Application
@ -515,7 +516,7 @@ class CoreClient:
def show_mobility_players(self) -> None:
for node in self.session.nodes.values():
if not NodeUtils.is_mobility(node):
if not nutils.is_mobility(node):
continue
if node.mobility_config:
mobility_player = MobilityPlayer(self.app, node)
@ -779,7 +780,7 @@ class CoreClient:
node_id = self.next_node_id()
position = Position(x=x, y=y)
image = None
if NodeUtils.is_image_node(node_type):
if nutils.has_image(node_type):
image = "ubuntu:latest"
emane = None
if node_type == NodeType.EMANE:
@ -804,9 +805,9 @@ class CoreClient:
image=image,
emane=emane,
)
if NodeUtils.is_custom(node_type, model):
services = NodeUtils.get_custom_node_services(self.app.guiconfig, model)
node.services[:] = services
if nutils.is_custom(node):
services = nutils.get_custom_services(self.app.guiconfig, model)
node.services = set(services)
# assign default services to CORE node
else:
services = self.session.default_services.get(model)
@ -843,10 +844,10 @@ class CoreClient:
self.links[edge.token] = edge
src_node = edge.src.core_node
dst_node = edge.dst.core_node
if NodeUtils.is_container_node(src_node):
if nutils.is_container(src_node):
src_iface_id = edge.link.iface1.id
self.iface_to_edge[(src_node.id, src_iface_id)] = edge
if NodeUtils.is_container_node(dst_node):
if nutils.is_container(dst_node):
dst_iface_id = edge.link.iface2.id
self.iface_to_edge[(dst_node.id, dst_iface_id)] = edge
@ -865,7 +866,7 @@ class CoreClient:
def get_mobility_configs_proto(self) -> List[mobility_pb2.MobilityConfig]:
configs = []
for node in self.session.nodes.values():
if not NodeUtils.is_mobility(node):
if not nutils.is_mobility(node):
continue
if not node.mobility_config:
continue
@ -893,7 +894,7 @@ class CoreClient:
def get_service_configs_proto(self) -> List[services_pb2.ServiceConfig]:
configs = []
for node in self.session.nodes.values():
if not NodeUtils.is_container_node(node):
if not nutils.is_container(node):
continue
if not node.service_configs:
continue
@ -913,7 +914,7 @@ class CoreClient:
def get_service_file_configs_proto(self) -> List[services_pb2.ServiceFileConfig]:
configs = []
for node in self.session.nodes.values():
if not NodeUtils.is_container_node(node):
if not nutils.is_container(node):
continue
if not node.service_file_configs:
continue
@ -930,7 +931,7 @@ class CoreClient:
) -> List[configservices_pb2.ConfigServiceConfig]:
config_service_protos = []
for node in self.session.nodes.values():
if not NodeUtils.is_container_node(node):
if not nutils.is_container(node):
continue
if not node.config_service_configs:
continue

View file

@ -8,12 +8,12 @@ import netaddr
from PIL.ImageTk import PhotoImage
from core.api.grpc.wrappers import Interface, Node
from core.gui import nodeutils, validation
from core.gui import nodeutils as nutils
from core.gui import validation
from core.gui.appconfig import ICONS_PATH
from core.gui.dialogs.dialog import Dialog
from core.gui.dialogs.emaneconfig import EmaneModelDialog
from core.gui.images import Images
from core.gui.nodeutils import NodeUtils
from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import ListboxScroll, image_chooser
@ -225,27 +225,27 @@ class NodeConfigDialog(Dialog):
row += 1
# node type field
if NodeUtils.is_model_node(self.node):
if nutils.is_model(self.node):
label = ttk.Label(frame, text="Type")
label.grid(row=row, column=0, sticky=tk.EW, padx=PADX, pady=PADY)
combobox = ttk.Combobox(
frame,
textvariable=self.type,
values=list(NodeUtils.NODE_MODELS),
values=list(nutils.NODE_MODELS),
state=combo_state,
)
combobox.grid(row=row, column=1, sticky=tk.EW)
row += 1
# container image field
if NodeUtils.is_image_node(self.node.type):
if nutils.has_image(self.node.type):
label = ttk.Label(frame, text="Image")
label.grid(row=row, column=0, sticky=tk.EW, padx=PADX, pady=PADY)
entry = ttk.Entry(frame, textvariable=self.container_image, state=state)
entry.grid(row=row, column=1, sticky=tk.EW)
row += 1
if NodeUtils.is_container_node(self.node):
if nutils.is_container(self.node):
# server
frame.grid(sticky=tk.EW)
frame.columnconfigure(1, weight=1)
@ -259,7 +259,7 @@ class NodeConfigDialog(Dialog):
combobox.grid(row=row, column=1, sticky=tk.EW)
row += 1
if NodeUtils.is_rj45_node(self.node):
if nutils.is_rj45(self.node):
response = self.app.core.client.get_ifaces()
logging.debug("host machine available interfaces: %s", response)
ifaces = ListboxScroll(frame)
@ -371,7 +371,7 @@ class NodeConfigDialog(Dialog):
def click_icon(self) -> None:
file_path = image_chooser(self, ICONS_PATH)
if file_path:
self.image = Images.create(file_path, nodeutils.ICON_SIZE)
self.image = Images.create(file_path, nutils.ICON_SIZE)
self.image_button.config(image=self.image)
self.image_file = file_path
@ -380,10 +380,10 @@ class NodeConfigDialog(Dialog):
# update core node
self.node.name = self.name.get()
if NodeUtils.is_image_node(self.node.type):
if nutils.has_image(self.node.type):
self.node.image = self.container_image.get()
server = self.server.get()
if NodeUtils.is_container_node(self.node):
if nutils.is_container(self.node):
if server == DEFAULT_SERVER:
self.node.server = None
else:

View file

@ -2,8 +2,8 @@ import tkinter as tk
from tkinter import ttk
from typing import TYPE_CHECKING, Dict, Optional
from core.gui import nodeutils as nutils
from core.gui.dialogs.dialog import Dialog
from core.gui.nodeutils import NodeUtils
from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import CodeText, ListboxScroll
@ -26,7 +26,7 @@ class RunToolDialog(Dialog):
store all CORE nodes (nodes that execute commands) from all existing nodes
"""
for node in self.app.core.session.nodes.values():
if NodeUtils.is_container_node(node):
if nutils.is_container(node):
self.executable_nodes[node.name] = node.id
def draw(self) -> None:

View file

@ -2,8 +2,8 @@ import tkinter as tk
from typing import TYPE_CHECKING
from core.api.grpc.wrappers import NodeType
from core.gui import nodeutils as nutils
from core.gui.frames.base import DetailsFrame, InfoFrameBase
from core.gui.nodeutils import NodeUtils
if TYPE_CHECKING:
from core.gui.app import Application
@ -22,9 +22,9 @@ class NodeInfoFrame(InfoFrameBase):
frame.grid(sticky=tk.EW)
frame.add_detail("ID", str(node.id))
frame.add_detail("Name", node.name)
if NodeUtils.is_model_node(node):
if nutils.is_model(node):
frame.add_detail("Type", node.model)
if NodeUtils.is_container_node(node):
if nutils.is_container(node):
for index, service in enumerate(sorted(node.services)):
if index == 0:
frame.add_detail("Services", service)
@ -33,8 +33,8 @@ class NodeInfoFrame(InfoFrameBase):
if node.type == NodeType.EMANE:
emane = "".join(node.emane.split("_")[1:])
frame.add_detail("EMANE", emane)
if NodeUtils.is_image_node(node.type):
if nutils.has_image(node.type):
frame.add_detail("Image", node.image)
if NodeUtils.is_container_node(node):
if nutils.is_container(node):
server = node.server if node.server else "localhost"
frame.add_detail("Server", server)

View file

@ -9,6 +9,7 @@ from PIL.ImageTk import PhotoImage
from core.api.grpc.wrappers import Interface, Link
from core.gui import appconfig
from core.gui import nodeutils as nutils
from core.gui.dialogs.shapemod import ShapeDialog
from core.gui.graph import tags
from core.gui.graph.edges import EDGE_WIDTH, CanvasEdge
@ -17,7 +18,6 @@ from core.gui.graph.node import CanvasNode, ShadowNode
from core.gui.graph.shape import Shape
from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker
from core.gui.images import TypeToImage
from core.gui.nodeutils import NodeUtils
if TYPE_CHECKING:
from core.gui.app import Application
@ -803,9 +803,7 @@ class CanvasGraph(tk.Canvas):
def scale_graph(self) -> None:
for nid, canvas_node in self.nodes.items():
img = None
if NodeUtils.is_custom(
canvas_node.core_node.type, canvas_node.core_node.model
):
if nutils.is_custom(canvas_node.core_node):
for custom_node in self.app.guiconfig.nodes:
if custom_node.name == canvas_node.core_node.model:
img = self.app.get_custom_icon(custom_node.image, ICON_SIZE)

View file

@ -5,6 +5,7 @@ from tkinter import BooleanVar, messagebox, ttk
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, ValuesView
from core.api.grpc.wrappers import Link, LinkType, Node, Session, ThroughputsEvent
from core.gui import nodeutils as nutils
from core.gui.graph import tags
from core.gui.graph.edges import (
CanvasEdge,
@ -17,7 +18,7 @@ from core.gui.graph.graph import CanvasGraph
from core.gui.graph.node import CanvasNode
from core.gui.graph.shapeutils import ShapeType
from core.gui.images import ImageEnum
from core.gui.nodeutils import ICON_SIZE, NodeDraw, NodeUtils
from core.gui.nodeutils import ICON_SIZE, NodeDraw
if TYPE_CHECKING:
from core.gui.app import Application
@ -235,7 +236,7 @@ class CanvasManager:
# create session nodes
for core_node in session.nodes.values():
# add node, avoiding ignored nodes
if NodeUtils.is_ignore_node(core_node):
if nutils.should_ignore(core_node):
continue
self.add_core_node(core_node)
@ -300,7 +301,7 @@ class CanvasManager:
logging.info("adding core node canvas(%s): %s", core_node.name, canvas_id)
canvas = self.get(canvas_id)
# if the gui can't find node's image, default to the "edit-node" image
image = NodeUtils.node_image(core_node, self.app.guiconfig, self.app.app_scale)
image = nutils.get_icon(core_node, self.app.guiconfig, self.app.app_scale)
if not image:
image = self.app.get_icon(ImageEnum.EDITNODE, ICON_SIZE)
x = core_node.position.x

View file

@ -9,7 +9,8 @@ from PIL.ImageTk import PhotoImage
from core.api.grpc.services_pb2 import ServiceAction
from core.api.grpc.wrappers import Interface, Node, NodeType
from core.gui import nodeutils, themes
from core.gui import nodeutils as nutils
from core.gui import themes
from core.gui.dialogs.emaneconfig import EmaneConfigDialog
from core.gui.dialogs.mobilityconfig import MobilityConfigDialog
from core.gui.dialogs.nodeconfig import NodeConfigDialog
@ -21,7 +22,7 @@ from core.gui.graph import tags
from core.gui.graph.edges import CanvasEdge, CanvasWirelessEdge
from core.gui.graph.tooltip import CanvasTooltip
from core.gui.images import ImageEnum, Images
from core.gui.nodeutils import ANTENNA_SIZE, ICON_SIZE, NodeUtils
from core.gui.nodeutils import ANTENNA_SIZE, ICON_SIZE
if TYPE_CHECKING:
from core.gui.app import Application
@ -190,7 +191,7 @@ class CanvasNode:
def on_enter(self, event: tk.Event) -> None:
is_runtime = self.app.core.is_runtime()
has_observer = self.app.core.observer is not None
is_container = NodeUtils.is_container_node(self.core_node)
is_container = nutils.is_container(self.core_node)
if is_runtime and has_observer and is_container:
self.tooltip.text.set("waiting...")
self.tooltip.on_enter(event)
@ -205,7 +206,7 @@ class CanvasNode:
def double_click(self, event: tk.Event) -> None:
if self.app.core.is_runtime():
if NodeUtils.is_container_node(self.core_node):
if nutils.is_container(self.core_node):
self.canvas.core.launch_terminal(self.core_node.id)
else:
self.show_config()
@ -233,7 +234,7 @@ class CanvasNode:
self.context.add_command(
label="Mobility Player", command=self.show_mobility_player
)
if NodeUtils.is_container_node(self.core_node):
if nutils.is_container(self.core_node):
services_menu = tk.Menu(self.context)
for service in sorted(self.core_node.services):
service_menu = tk.Menu(services_menu)
@ -251,7 +252,7 @@ class CanvasNode:
self.context.add_cascade(label="Services", menu=services_menu)
else:
self.context.add_command(label="Configure", command=self.show_config)
if NodeUtils.is_container_node(self.core_node):
if nutils.is_container(self.core_node):
self.context.add_command(label="Services", command=self.show_services)
self.context.add_command(
label="Config Services", command=self.show_config_services
@ -268,7 +269,7 @@ class CanvasNode:
self.context.add_command(
label="Mobility Config", command=self.show_mobility_config
)
if NodeUtils.is_wireless_node(self.core_node):
if nutils.is_wireless(self.core_node):
self.context.add_command(
label="Link To Selected", command=self.wireless_link_selected
)
@ -402,7 +403,7 @@ class CanvasNode:
logging.error(f"node icon does not exist: {icon_path}")
return
self.core_node.icon = icon_path
self.image = Images.create(icon_path, nodeutils.ICON_SIZE)
self.image = Images.create(icon_path)
self.canvas.itemconfig(self.id, image=self.image)
def is_linkable(self, node: "CanvasNode") -> bool:
@ -410,22 +411,19 @@ class CanvasNode:
if self == node:
return False
# rj45 nodes can only support one link
if NodeUtils.is_rj45_node(self.core_node) and self.edges:
if nutils.is_rj45(self.core_node) and self.edges:
return False
if NodeUtils.is_rj45_node(node.core_node) and node.edges:
if nutils.is_rj45(node.core_node) and node.edges:
return False
# only 1 link between bridge based nodes
is_src_bridge = NodeUtils.is_bridge_node(self.core_node)
is_dst_bridge = NodeUtils.is_bridge_node(node.core_node)
is_src_bridge = nutils.is_bridge(self.core_node)
is_dst_bridge = nutils.is_bridge(node.core_node)
common_links = self.edges & node.edges
if all([is_src_bridge, is_dst_bridge, common_links]):
return False
# valid link
return True
def is_wireless(self) -> bool:
return NodeUtils.is_wireless_node(self.core_node)
def hide(self) -> None:
self.hidden = True
self.canvas.itemconfig(self.id, state=tk.HIDDEN)
@ -481,6 +479,9 @@ class CanvasNode:
def validate_service(self, service: str) -> None:
self._service_action(service, ServiceAction.VALIDATE)
def is_wireless(self) -> bool:
return nutils.is_wireless(self.core_node)
class ShadowNode:
def __init__(

View file

@ -8,12 +8,18 @@ from PIL.ImageTk import PhotoImage
from core.api.grpc.wrappers import NodeType
from core.gui.appconfig import LOCAL_ICONS_PATH
ICON_SIZE: int = 48
class Images:
images: Dict[str, str] = {}
@classmethod
def create(cls, file_path: str, width: int, height: int = None) -> PhotoImage:
def create(
cls, file_path: str, width: int = None, height: int = None
) -> PhotoImage:
if width is None:
width = ICON_SIZE
if height is None:
height = width
image = Image.open(file_path)

View file

@ -5,9 +5,9 @@ import netaddr
from netaddr import EUI, IPNetwork
from core.api.grpc.wrappers import Interface, Link, LinkType, Node
from core.gui import nodeutils as nutils
from core.gui.graph.edges import CanvasEdge
from core.gui.graph.node import CanvasNode
from core.gui.nodeutils import NodeUtils
if TYPE_CHECKING:
from core.gui.app import Application
@ -147,7 +147,7 @@ class InterfaceManager:
self.used_subnets[subnets.key()] = subnets
def next_index(self, node: Node) -> int:
if NodeUtils.is_router_node(node):
if nutils.is_router(node):
index = 1
else:
index = 20
@ -179,8 +179,8 @@ class InterfaceManager:
) -> None:
src_node = canvas_src_node.core_node
dst_node = canvas_dst_node.core_node
is_src_container = NodeUtils.is_container_node(src_node)
is_dst_container = NodeUtils.is_container_node(dst_node)
is_src_container = nutils.is_container(src_node)
is_dst_container = nutils.is_container(dst_node)
if is_src_container and is_dst_container:
self.current_subnets = self.next_subnets()
elif is_src_container and not is_dst_container:
@ -232,10 +232,10 @@ class InterfaceManager:
dst_node = edge.dst.core_node
self.determine_subnets(edge.src, edge.dst)
src_iface = None
if NodeUtils.is_container_node(src_node):
if nutils.is_container(src_node):
src_iface = self.create_iface(edge.src, edge.linked_wireless)
dst_iface = None
if NodeUtils.is_container_node(dst_node):
if nutils.is_container(dst_node):
dst_iface = self.create_iface(edge.dst, edge.linked_wireless)
link = Link(
type=LinkType.WIRED,

View file

@ -10,6 +10,125 @@ from core.gui.images import ImageEnum, Images, TypeToImage
ICON_SIZE: int = 48
ANTENNA_SIZE: int = 32
NODES: List["NodeDraw"] = []
NETWORK_NODES: List["NodeDraw"] = []
NODE_ICONS = {}
CONTAINER_NODES: Set[NodeType] = {NodeType.DEFAULT, NodeType.DOCKER, NodeType.LXC}
IMAGE_NODES: Set[NodeType] = {NodeType.DOCKER, NodeType.LXC}
WIRELESS_NODES: Set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE}
RJ45_NODES: Set[NodeType] = {NodeType.RJ45}
BRIDGE_NODES: Set[NodeType] = {NodeType.HUB, NodeType.SWITCH}
IGNORE_NODES: Set[NodeType] = {NodeType.CONTROL_NET}
MOBILITY_NODES: Set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE}
NODE_MODELS: Set[str] = {"router", "host", "PC", "mdr", "prouter"}
ROUTER_NODES: Set[str] = {"router", "mdr"}
ANTENNA_ICON: Optional[PhotoImage] = None
def setup() -> None:
global ANTENNA_ICON
nodes = [
(ImageEnum.ROUTER, NodeType.DEFAULT, "Router", "router"),
(ImageEnum.HOST, NodeType.DEFAULT, "Host", "host"),
(ImageEnum.PC, NodeType.DEFAULT, "PC", "PC"),
(ImageEnum.MDR, NodeType.DEFAULT, "MDR", "mdr"),
(ImageEnum.PROUTER, NodeType.DEFAULT, "PRouter", "prouter"),
(ImageEnum.DOCKER, NodeType.DOCKER, "Docker", None),
(ImageEnum.LXC, NodeType.LXC, "LXC", None),
]
for image_enum, node_type, label, model in nodes:
node_draw = NodeDraw.from_setup(image_enum, node_type, label, model)
NODES.append(node_draw)
NODE_ICONS[(node_type, model)] = node_draw.image
network_nodes = [
(ImageEnum.HUB, NodeType.HUB, "Hub"),
(ImageEnum.SWITCH, NodeType.SWITCH, "Switch"),
(ImageEnum.WLAN, NodeType.WIRELESS_LAN, "WLAN"),
(ImageEnum.EMANE, NodeType.EMANE, "EMANE"),
(ImageEnum.RJ45, NodeType.RJ45, "RJ45"),
(ImageEnum.TUNNEL, NodeType.TUNNEL, "Tunnel"),
]
for image_enum, node_type, label in network_nodes:
node_draw = NodeDraw.from_setup(image_enum, node_type, label)
NETWORK_NODES.append(node_draw)
NODE_ICONS[(node_type, None)] = node_draw.image
ANTENNA_ICON = Images.get(ImageEnum.ANTENNA, ANTENNA_SIZE)
def is_bridge(node: Node) -> bool:
return node.type in BRIDGE_NODES
def is_mobility(node: Node) -> bool:
return node.type in MOBILITY_NODES
def is_router(node: Node) -> bool:
return is_model(node) and node.model in ROUTER_NODES
def should_ignore(node: Node) -> bool:
return node.type in IGNORE_NODES
def is_container(node: Node) -> bool:
return node.type in CONTAINER_NODES
def is_model(node: Node) -> bool:
return node.type == NodeType.DEFAULT
def has_image(node_type: NodeType) -> bool:
return node_type in IMAGE_NODES
def is_wireless(node: Node) -> bool:
return node.type in WIRELESS_NODES
def is_rj45(node: Node) -> bool:
return node.type in RJ45_NODES
def is_custom(node: Node) -> bool:
return is_model(node) and node.model not in NODE_MODELS
def get_custom_services(gui_config: GuiConfig, name: str) -> List[str]:
for custom_node in gui_config.nodes:
if custom_node.name == name:
return custom_node.services
return []
def _get_image_file(config: GuiConfig, name: str) -> Optional[str]:
for custom_node in config.nodes:
if custom_node.name == name:
return custom_node.image
return None
def get_icon(node: Node, config: GuiConfig, scale: float) -> Optional[PhotoImage]:
image = None
# node has defined a custom icon
if node.icon:
try:
image = Images.create(node.icon, int(ICON_SIZE * scale))
except OSError:
logging.error("invalid icon: %s", node.icon)
else:
# attempt to find default type/model image
image_enum = TypeToImage.get(node.type, node.model)
if image_enum:
image = Images.get(image_enum, int(ICON_SIZE * scale))
# attempt to find custom image file
else:
image_file = _get_image_file(config, node.model)
if image_file:
image = Images.get_with_image_file(image_file, int(ICON_SIZE * scale))
return image
class NodeDraw:
def __init__(self) -> None:
@ -52,128 +171,3 @@ class NodeDraw:
node_draw.model = custom_node.name
node_draw.tooltip = custom_node.name
return node_draw
class NodeUtils:
NODES: List[NodeDraw] = []
NETWORK_NODES: List[NodeDraw] = []
NODE_ICONS = {}
CONTAINER_NODES: Set[NodeType] = {NodeType.DEFAULT, NodeType.DOCKER, NodeType.LXC}
IMAGE_NODES: Set[NodeType] = {NodeType.DOCKER, NodeType.LXC}
WIRELESS_NODES: Set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE}
RJ45_NODES: Set[NodeType] = {NodeType.RJ45}
BRIDGE_NODES: Set[NodeType] = {NodeType.HUB, NodeType.SWITCH}
IGNORE_NODES: Set[NodeType] = {NodeType.CONTROL_NET}
MOBILITY_NODES: Set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE}
NODE_MODELS: Set[str] = {"router", "host", "PC", "mdr", "prouter"}
ROUTER_NODES: Set[str] = {"router", "mdr"}
ANTENNA_ICON: PhotoImage = None
@classmethod
def is_bridge_node(cls, node: Node) -> bool:
return node.type in cls.BRIDGE_NODES
@classmethod
def is_mobility(cls, node: Node) -> bool:
return node.type in cls.MOBILITY_NODES
@classmethod
def is_router_node(cls, node: Node) -> bool:
return cls.is_model_node(node) and node.model in cls.ROUTER_NODES
@classmethod
def is_ignore_node(cls, node: Node) -> bool:
return node.type in cls.IGNORE_NODES
@classmethod
def is_container_node(cls, node: Node) -> bool:
return node.type in cls.CONTAINER_NODES
@classmethod
def is_model_node(cls, node: Node) -> bool:
return node.type == NodeType.DEFAULT
@classmethod
def is_image_node(cls, node_type: NodeType) -> bool:
return node_type in cls.IMAGE_NODES
@classmethod
def is_wireless_node(cls, node: Node) -> bool:
return node.type in cls.WIRELESS_NODES
@classmethod
def is_rj45_node(cls, node: Node) -> bool:
return node.type in cls.RJ45_NODES
@classmethod
def node_icon(
cls, node: Node, gui_config: GuiConfig, scale: float = 1.0
) -> PhotoImage:
image_enum = TypeToImage.get(node.type, node.model)
if image_enum:
return Images.get(image_enum, int(ICON_SIZE * scale))
else:
image_stem = cls.get_image_file(gui_config, node.model)
if image_stem:
return Images.get_with_image_file(image_stem, int(ICON_SIZE * scale))
@classmethod
def node_image(
cls, core_node: Node, gui_config: GuiConfig, scale: float = 1.0
) -> PhotoImage:
image = cls.node_icon(core_node, gui_config, scale)
if core_node.icon:
try:
image = Images.create(core_node.icon, int(ICON_SIZE * scale))
except OSError:
logging.error("invalid icon: %s", core_node.icon)
return image
@classmethod
def is_custom(cls, node_type: NodeType, model: str) -> bool:
return node_type == NodeType.DEFAULT and model not in cls.NODE_MODELS
@classmethod
def get_custom_node_services(cls, gui_config: GuiConfig, name: str) -> List[str]:
for custom_node in gui_config.nodes:
if custom_node.name == name:
return custom_node.services
return []
@classmethod
def get_image_file(cls, gui_config: GuiConfig, name: str) -> Optional[str]:
for custom_node in gui_config.nodes:
if custom_node.name == name:
return custom_node.image
return None
@classmethod
def setup(cls) -> None:
nodes = [
(ImageEnum.ROUTER, NodeType.DEFAULT, "Router", "router"),
(ImageEnum.HOST, NodeType.DEFAULT, "Host", "host"),
(ImageEnum.PC, NodeType.DEFAULT, "PC", "PC"),
(ImageEnum.MDR, NodeType.DEFAULT, "MDR", "mdr"),
(ImageEnum.PROUTER, NodeType.DEFAULT, "PRouter", "prouter"),
(ImageEnum.DOCKER, NodeType.DOCKER, "Docker", None),
(ImageEnum.LXC, NodeType.LXC, "LXC", None),
]
for image_enum, node_type, label, model in nodes:
node_draw = NodeDraw.from_setup(image_enum, node_type, label, model)
cls.NODES.append(node_draw)
cls.NODE_ICONS[(node_type, model)] = node_draw.image
network_nodes = [
(ImageEnum.HUB, NodeType.HUB, "Hub"),
(ImageEnum.SWITCH, NodeType.SWITCH, "Switch"),
(ImageEnum.WLAN, NodeType.WIRELESS_LAN, "WLAN"),
(ImageEnum.EMANE, NodeType.EMANE, "EMANE"),
(ImageEnum.RJ45, NodeType.RJ45, "RJ45"),
(ImageEnum.TUNNEL, NodeType.TUNNEL, "Tunnel"),
]
for image_enum, node_type, label in network_nodes:
node_draw = NodeDraw.from_setup(image_enum, node_type, label)
cls.NETWORK_NODES.append(node_draw)
cls.NODE_ICONS[(node_type, None)] = node_draw.image
cls.ANTENNA_ICON = Images.get(ImageEnum.ANTENNA, ANTENNA_SIZE)

View file

@ -7,13 +7,14 @@ from typing import TYPE_CHECKING, Callable, List, Optional
from PIL.ImageTk import PhotoImage
from core.gui import nodeutils as nutils
from core.gui.dialogs.colorpicker import ColorPickerDialog
from core.gui.dialogs.runtool import RunToolDialog
from core.gui.graph import tags
from core.gui.graph.enums import GraphMode
from core.gui.graph.shapeutils import ShapeType, is_marker
from core.gui.images import ImageEnum
from core.gui.nodeutils import NodeDraw, NodeUtils
from core.gui.nodeutils import NodeDraw
from core.gui.observers import ObserversMenu
from core.gui.task import ProgressTask
from core.gui.themes import Styles
@ -190,8 +191,8 @@ class Toolbar(ttk.Frame):
# these variables help keep track of what images being drawn so that scaling
# is possible since PhotoImage does not have resize method
self.current_node: NodeDraw = NodeUtils.NODES[0]
self.current_network: NodeDraw = NodeUtils.NETWORK_NODES[0]
self.current_node: NodeDraw = nutils.NODES[0]
self.current_network: NodeDraw = nutils.NETWORK_NODES[0]
self.current_annotation: ShapeType = ShapeType.MARKER
self.annotation_enum: ImageEnum = ImageEnum.MARKER
@ -263,7 +264,7 @@ class Toolbar(ttk.Frame):
self.design_frame.select_radio(self.node_button)
self.picker = PickerFrame(self.app, self.node_button)
# draw default nodes
for node_draw in NodeUtils.NODES:
for node_draw in nutils.NODES:
func = partial(
self.update_button, self.node_button, node_draw, NodeTypeEnum.NODE
)
@ -353,7 +354,7 @@ class Toolbar(ttk.Frame):
self.app.manager.node_draw = self.current_network
self.design_frame.select_radio(self.network_button)
self.picker = PickerFrame(self.app, self.network_button)
for node_draw in NodeUtils.NETWORK_NODES:
for node_draw in nutils.NETWORK_NODES:
func = partial(
self.update_button, self.network_button, node_draw, NodeTypeEnum.NETWORK
)