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 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.appconfig import GuiConfig
from core.gui.coreclient import CoreClient from core.gui.coreclient import CoreClient
from core.gui.dialogs.error import ErrorDialog 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.graph.manager import CanvasManager
from core.gui.images import ImageEnum, Images from core.gui.images import ImageEnum, Images
from core.gui.menubar import Menubar from core.gui.menubar import Menubar
from core.gui.nodeutils import NodeUtils
from core.gui.statusbar import StatusBar from core.gui.statusbar import StatusBar
from core.gui.themes import PADY from core.gui.themes import PADY
from core.gui.toolbar import Toolbar from core.gui.toolbar import Toolbar
@ -29,7 +30,7 @@ class Application(ttk.Frame):
def __init__(self, proxy: bool, session_id: int = None) -> None: def __init__(self, proxy: bool, session_id: int = None) -> None:
super().__init__() super().__init__()
# load node icons # load node icons
NodeUtils.setup() nutils.setup()
# widgets # widgets
self.menubar: Optional[Menubar] = None self.menubar: Optional[Menubar] = None

View file

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

View file

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

View file

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

View file

@ -2,8 +2,8 @@ import tkinter as tk
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from core.api.grpc.wrappers import NodeType 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.frames.base import DetailsFrame, InfoFrameBase
from core.gui.nodeutils import NodeUtils
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -22,9 +22,9 @@ class NodeInfoFrame(InfoFrameBase):
frame.grid(sticky=tk.EW) frame.grid(sticky=tk.EW)
frame.add_detail("ID", str(node.id)) frame.add_detail("ID", str(node.id))
frame.add_detail("Name", node.name) frame.add_detail("Name", node.name)
if NodeUtils.is_model_node(node): if nutils.is_model(node):
frame.add_detail("Type", node.model) 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)): for index, service in enumerate(sorted(node.services)):
if index == 0: if index == 0:
frame.add_detail("Services", service) frame.add_detail("Services", service)
@ -33,8 +33,8 @@ class NodeInfoFrame(InfoFrameBase):
if node.type == NodeType.EMANE: if node.type == NodeType.EMANE:
emane = "".join(node.emane.split("_")[1:]) emane = "".join(node.emane.split("_")[1:])
frame.add_detail("EMANE", emane) frame.add_detail("EMANE", emane)
if NodeUtils.is_image_node(node.type): if nutils.has_image(node.type):
frame.add_detail("Image", node.image) 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" server = node.server if node.server else "localhost"
frame.add_detail("Server", server) 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.api.grpc.wrappers import Interface, Link
from core.gui import appconfig from core.gui import appconfig
from core.gui import nodeutils as nutils
from core.gui.dialogs.shapemod import ShapeDialog from core.gui.dialogs.shapemod import ShapeDialog
from core.gui.graph import tags from core.gui.graph import tags
from core.gui.graph.edges import EDGE_WIDTH, CanvasEdge 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.shape import Shape
from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker
from core.gui.images import TypeToImage from core.gui.images import TypeToImage
from core.gui.nodeutils import NodeUtils
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -803,9 +803,7 @@ class CanvasGraph(tk.Canvas):
def scale_graph(self) -> None: def scale_graph(self) -> None:
for nid, canvas_node in self.nodes.items(): for nid, canvas_node in self.nodes.items():
img = None img = None
if NodeUtils.is_custom( if nutils.is_custom(canvas_node.core_node):
canvas_node.core_node.type, canvas_node.core_node.model
):
for custom_node in self.app.guiconfig.nodes: for custom_node in self.app.guiconfig.nodes:
if custom_node.name == canvas_node.core_node.model: if custom_node.name == canvas_node.core_node.model:
img = self.app.get_custom_icon(custom_node.image, ICON_SIZE) 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 typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, ValuesView
from core.api.grpc.wrappers import Link, LinkType, Node, Session, ThroughputsEvent 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 import tags
from core.gui.graph.edges import ( from core.gui.graph.edges import (
CanvasEdge, CanvasEdge,
@ -17,7 +18,7 @@ from core.gui.graph.graph import CanvasGraph
from core.gui.graph.node import CanvasNode from core.gui.graph.node import CanvasNode
from core.gui.graph.shapeutils import ShapeType from core.gui.graph.shapeutils import ShapeType
from core.gui.images import ImageEnum 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: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -235,7 +236,7 @@ class CanvasManager:
# create session nodes # create session nodes
for core_node in session.nodes.values(): for core_node in session.nodes.values():
# add node, avoiding ignored nodes # add node, avoiding ignored nodes
if NodeUtils.is_ignore_node(core_node): if nutils.should_ignore(core_node):
continue continue
self.add_core_node(core_node) 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) logging.info("adding core node canvas(%s): %s", core_node.name, canvas_id)
canvas = self.get(canvas_id) canvas = self.get(canvas_id)
# if the gui can't find node's image, default to the "edit-node" image # 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: if not image:
image = self.app.get_icon(ImageEnum.EDITNODE, ICON_SIZE) image = self.app.get_icon(ImageEnum.EDITNODE, ICON_SIZE)
x = core_node.position.x 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.services_pb2 import ServiceAction
from core.api.grpc.wrappers import Interface, Node, NodeType 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.emaneconfig import EmaneConfigDialog
from core.gui.dialogs.mobilityconfig import MobilityConfigDialog from core.gui.dialogs.mobilityconfig import MobilityConfigDialog
from core.gui.dialogs.nodeconfig import NodeConfigDialog 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.edges import CanvasEdge, CanvasWirelessEdge
from core.gui.graph.tooltip import CanvasTooltip from core.gui.graph.tooltip import CanvasTooltip
from core.gui.images import ImageEnum, Images 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: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -190,7 +191,7 @@ class CanvasNode:
def on_enter(self, event: tk.Event) -> None: def on_enter(self, event: tk.Event) -> None:
is_runtime = self.app.core.is_runtime() is_runtime = self.app.core.is_runtime()
has_observer = self.app.core.observer is not None 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: if is_runtime and has_observer and is_container:
self.tooltip.text.set("waiting...") self.tooltip.text.set("waiting...")
self.tooltip.on_enter(event) self.tooltip.on_enter(event)
@ -205,7 +206,7 @@ class CanvasNode:
def double_click(self, event: tk.Event) -> None: def double_click(self, event: tk.Event) -> None:
if self.app.core.is_runtime(): 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) self.canvas.core.launch_terminal(self.core_node.id)
else: else:
self.show_config() self.show_config()
@ -233,7 +234,7 @@ class CanvasNode:
self.context.add_command( self.context.add_command(
label="Mobility Player", command=self.show_mobility_player 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) services_menu = tk.Menu(self.context)
for service in sorted(self.core_node.services): for service in sorted(self.core_node.services):
service_menu = tk.Menu(services_menu) service_menu = tk.Menu(services_menu)
@ -251,7 +252,7 @@ class CanvasNode:
self.context.add_cascade(label="Services", menu=services_menu) self.context.add_cascade(label="Services", menu=services_menu)
else: else:
self.context.add_command(label="Configure", command=self.show_config) 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="Services", command=self.show_services)
self.context.add_command( self.context.add_command(
label="Config Services", command=self.show_config_services label="Config Services", command=self.show_config_services
@ -268,7 +269,7 @@ class CanvasNode:
self.context.add_command( self.context.add_command(
label="Mobility Config", command=self.show_mobility_config 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( self.context.add_command(
label="Link To Selected", command=self.wireless_link_selected 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}") logging.error(f"node icon does not exist: {icon_path}")
return return
self.core_node.icon = icon_path 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) self.canvas.itemconfig(self.id, image=self.image)
def is_linkable(self, node: "CanvasNode") -> bool: def is_linkable(self, node: "CanvasNode") -> bool:
@ -410,22 +411,19 @@ class CanvasNode:
if self == node: if self == node:
return False return False
# rj45 nodes can only support one link # 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 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 return False
# only 1 link between bridge based nodes # only 1 link between bridge based nodes
is_src_bridge = NodeUtils.is_bridge_node(self.core_node) is_src_bridge = nutils.is_bridge(self.core_node)
is_dst_bridge = NodeUtils.is_bridge_node(node.core_node) is_dst_bridge = nutils.is_bridge(node.core_node)
common_links = self.edges & node.edges common_links = self.edges & node.edges
if all([is_src_bridge, is_dst_bridge, common_links]): if all([is_src_bridge, is_dst_bridge, common_links]):
return False return False
# valid link # valid link
return True return True
def is_wireless(self) -> bool:
return NodeUtils.is_wireless_node(self.core_node)
def hide(self) -> None: def hide(self) -> None:
self.hidden = True self.hidden = True
self.canvas.itemconfig(self.id, state=tk.HIDDEN) self.canvas.itemconfig(self.id, state=tk.HIDDEN)
@ -481,6 +479,9 @@ class CanvasNode:
def validate_service(self, service: str) -> None: def validate_service(self, service: str) -> None:
self._service_action(service, ServiceAction.VALIDATE) self._service_action(service, ServiceAction.VALIDATE)
def is_wireless(self) -> bool:
return nutils.is_wireless(self.core_node)
class ShadowNode: class ShadowNode:
def __init__( def __init__(

View file

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

View file

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

View file

@ -10,6 +10,125 @@ from core.gui.images import ImageEnum, Images, TypeToImage
ICON_SIZE: int = 48 ICON_SIZE: int = 48
ANTENNA_SIZE: int = 32 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: class NodeDraw:
def __init__(self) -> None: def __init__(self) -> None:
@ -52,128 +171,3 @@ class NodeDraw:
node_draw.model = custom_node.name node_draw.model = custom_node.name
node_draw.tooltip = custom_node.name node_draw.tooltip = custom_node.name
return node_draw 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 PIL.ImageTk import PhotoImage
from core.gui import nodeutils as nutils
from core.gui.dialogs.colorpicker import ColorPickerDialog from core.gui.dialogs.colorpicker import ColorPickerDialog
from core.gui.dialogs.runtool import RunToolDialog from core.gui.dialogs.runtool import RunToolDialog
from core.gui.graph import tags from core.gui.graph import tags
from core.gui.graph.enums import GraphMode from core.gui.graph.enums import GraphMode
from core.gui.graph.shapeutils import ShapeType, is_marker from core.gui.graph.shapeutils import ShapeType, is_marker
from core.gui.images import ImageEnum 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.observers import ObserversMenu
from core.gui.task import ProgressTask from core.gui.task import ProgressTask
from core.gui.themes import Styles 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 # these variables help keep track of what images being drawn so that scaling
# is possible since PhotoImage does not have resize method # is possible since PhotoImage does not have resize method
self.current_node: NodeDraw = NodeUtils.NODES[0] self.current_node: NodeDraw = nutils.NODES[0]
self.current_network: NodeDraw = NodeUtils.NETWORK_NODES[0] self.current_network: NodeDraw = nutils.NETWORK_NODES[0]
self.current_annotation: ShapeType = ShapeType.MARKER self.current_annotation: ShapeType = ShapeType.MARKER
self.annotation_enum: ImageEnum = ImageEnum.MARKER self.annotation_enum: ImageEnum = ImageEnum.MARKER
@ -263,7 +264,7 @@ class Toolbar(ttk.Frame):
self.design_frame.select_radio(self.node_button) self.design_frame.select_radio(self.node_button)
self.picker = PickerFrame(self.app, self.node_button) self.picker = PickerFrame(self.app, self.node_button)
# draw default nodes # draw default nodes
for node_draw in NodeUtils.NODES: for node_draw in nutils.NODES:
func = partial( func = partial(
self.update_button, self.node_button, node_draw, NodeTypeEnum.NODE 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.app.manager.node_draw = self.current_network
self.design_frame.select_radio(self.network_button) self.design_frame.select_radio(self.network_button)
self.picker = PickerFrame(self.app, 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( func = partial(
self.update_button, self.network_button, node_draw, NodeTypeEnum.NETWORK self.update_button, self.network_button, node_draw, NodeTypeEnum.NETWORK
) )