From c43e5f999ca27948a2dee8b1b6e22e2dcd9895a9 Mon Sep 17 00:00:00 2001 From: bharnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 20 Nov 2019 10:59:30 -0800 Subject: [PATCH 1/2] added node config interface display and updated canvas nodes to use core node porotbuf directly for display and saving data --- coretk/coretk/canvasaction.py | 2 +- coretk/coretk/dialogs/emaneconfig.py | 14 ++-- coretk/coretk/dialogs/mobilityconfig.py | 5 +- coretk/coretk/dialogs/nodeconfig.py | 93 ++++++++++++++++++++++--- coretk/coretk/dialogs/wlanconfig.py | 11 ++- coretk/coretk/graph.py | 45 ++++++------ coretk/coretk/themes.py | 3 +- 7 files changed, 124 insertions(+), 49 deletions(-) diff --git a/coretk/coretk/canvasaction.py b/coretk/coretk/canvasaction.py index 5b9c08c3..08aa8c3d 100644 --- a/coretk/coretk/canvasaction.py +++ b/coretk/coretk/canvasaction.py @@ -30,7 +30,7 @@ class CanvasAction: def display_wlan_configuration(self, canvas_node): wlan_config = self.master.core.wlanconfig_management.configurations[ - canvas_node.core_id + canvas_node.core_node.id ] dialog = WlanConfigDialog( self.master, self.master, self.node_to_show_config, wlan_config diff --git a/coretk/coretk/dialogs/emaneconfig.py b/coretk/coretk/dialogs/emaneconfig.py index d64e9089..2c885511 100644 --- a/coretk/coretk/dialogs/emaneconfig.py +++ b/coretk/coretk/dialogs/emaneconfig.py @@ -20,6 +20,7 @@ class EmaneConfiguration(Dialog): super().__init__(master, app, "emane configuration", modal=False) self.app = app self.canvas_node = canvas_node + self.node = canvas_node.core_node self.radiovar = tk.IntVar() self.radiovar.set(1) self.columnconfigure(0, weight=1) @@ -122,20 +123,15 @@ class EmaneConfiguration(Dialog): # add string emane_ infront for grpc call response = self.app.core.client.set_emane_model_config( - self.app.core.session_id, - self.canvas_node.core_id, - "emane_" + model_name, - config, + self.app.core.session_id, self.node.id, f"emane_{model_name}", config ) logging.info( - "emaneconfig.py config emane model (%s), result: %s", - self.canvas_node.core_id, - response, + "emaneconfig.py config emane model (%s), result: %s", self.node.id, response ) # store the change locally self.app.core.emaneconfig_management.set_custom_emane_cloud_config( - self.canvas_node.core_id, "emane_" + model_name + self.node.id, f"emane_{model_name}" ) self.emane_model_dialog.destroy() @@ -161,7 +157,7 @@ class EmaneConfiguration(Dialog): session_id = self.app.core.session_id # add string emane_ before model name for grpc call response = self.app.core.client.get_emane_model_config( - session_id, self.canvas_node.core_id, "emane_" + model_name + session_id, self.node.id, f"emane_{model_name}" ) logging.info("emane model config %s", response) diff --git a/coretk/coretk/dialogs/mobilityconfig.py b/coretk/coretk/dialogs/mobilityconfig.py index 1f53b4a0..2c20229a 100644 --- a/coretk/coretk/dialogs/mobilityconfig.py +++ b/coretk/coretk/dialogs/mobilityconfig.py @@ -19,9 +19,10 @@ class MobilityConfigDialog(Dialog): """ super().__init__(master, app, "ns2script configuration", modal=True) self.canvas_node = canvas_node + self.node = canvas_node.core_node logging.info(app.canvas.core.mobilityconfig_management.configurations) self.node_config = app.canvas.core.mobilityconfig_management.configurations[ - canvas_node.core_id + self.node.id ] self.mobility_script_parameters() @@ -208,7 +209,7 @@ class MobilityConfigDialog(Dialog): else: loop = "0" self.app.canvas.core.mobilityconfig_management.set_custom_configuration( - node_id=self.canvas_node.core_id, + node_id=self.node.id, file=file, refresh_ms=refresh_time, loop=loop, diff --git a/coretk/coretk/dialogs/nodeconfig.py b/coretk/coretk/dialogs/nodeconfig.py index a8252336..8a0d4522 100644 --- a/coretk/coretk/dialogs/nodeconfig.py +++ b/coretk/coretk/dialogs/nodeconfig.py @@ -1,14 +1,36 @@ +import logging import tkinter as tk +from functools import partial from tkinter import ttk from coretk.dialogs.dialog import Dialog from coretk.dialogs.icondialog import IconDialog from coretk.dialogs.nodeservice import NodeService +from coretk.widgets import FrameScroll DEFAULT_NODES = {"router", "host", "PC", "mdr", "prouter"} PAD = 5 +def mac_auto(is_auto, entry): + logging.info("mac auto clicked") + if is_auto.get(): + logging.info("disabling mac") + entry.var.set("") + entry.config(state=tk.DISABLED) + else: + entry.var.set("00:00:00:00:00:00") + entry.config(state=tk.NORMAL) + + +class InterfaceData: + def __init__(self, is_auto, mac, ip4, ip6): + self.is_auto = is_auto + self.mac = mac + self.ip4 = ip4 + self.ip6 = ip6 + + class NodeConfigDialog(Dialog): def __init__(self, master, app, canvas_node): """ @@ -18,13 +40,20 @@ class NodeConfigDialog(Dialog): :param coretk.app.Application: main app :param coretk.graph.CanvasNode canvas_node: canvas node object """ - super().__init__(master, app, f"{canvas_node.name} Configuration", modal=True) + super().__init__( + master, app, f"{canvas_node.core_node.name} Configuration", modal=True + ) self.canvas_node = canvas_node + self.node = canvas_node.core_node self.image = canvas_node.image self.image_button = None - self.name = tk.StringVar(value=canvas_node.name) - self.type = tk.StringVar(value=canvas_node.core_node.model) - self.server = tk.StringVar() + self.name = tk.StringVar(value=self.node.name) + self.type = tk.StringVar(value=self.node.model) + server = "localhost" + if self.node.server: + server = self.node.server + self.server = tk.StringVar(value=server) + self.interfaces = {} self.draw() def draw(self): @@ -82,8 +111,50 @@ class NodeConfigDialog(Dialog): button = ttk.Button(self.top, text="Services", command=self.click_services) button.grid(sticky="ew", pady=PAD) + # interfaces + if self.canvas_node.interfaces: + self.draw_interfaces() + self.draw_buttons() + def draw_interfaces(self): + scroll = FrameScroll(self.top, self.app, text="Interfaces") + scroll.grid(sticky="nsew") + scroll.frame.columnconfigure(0, weight=1) + scroll.frame.rowconfigure(0, weight=1) + for interface in self.canvas_node.interfaces: + logging.info("interface: %s", interface) + frame = ttk.LabelFrame(scroll.frame, text=interface.name, padding=PAD) + frame.grid(sticky="ew", pady=PAD) + frame.columnconfigure(1, weight=1) + frame.columnconfigure(2, weight=1) + + label = ttk.Label(frame, text="MAC") + label.grid(row=0, column=0, padx=PAD, pady=PAD) + is_auto = tk.BooleanVar(value=True) + checkbutton = ttk.Checkbutton(frame, text="Auto?", variable=is_auto) + checkbutton.var = is_auto + checkbutton.grid(row=0, column=1, padx=PAD) + mac = tk.StringVar(value=interface.mac) + entry = ttk.Entry(frame, textvariable=mac, state=tk.DISABLED) + entry.grid(row=0, column=2, sticky="ew") + func = partial(mac_auto, is_auto, entry) + checkbutton.config(command=func) + + label = ttk.Label(frame, text="IPv4") + label.grid(row=1, column=0, padx=PAD, pady=PAD) + ip4 = tk.StringVar(value=f"{interface.ip4}/{interface.ip4mask}") + entry = ttk.Entry(frame, textvariable=ip4) + entry.grid(row=1, column=1, columnspan=2, sticky="ew") + + label = ttk.Label(frame, text="IPv6") + label.grid(row=2, column=0, padx=PAD, pady=PAD) + ip6 = tk.StringVar(value=f"{interface.ip6}/{interface.ip6mask}") + entry = ttk.Entry(frame, textvariable=ip6) + entry.grid(row=2, column=1, columnspan=2, sticky="ew") + + self.interfaces[interface.id] = InterfaceData(is_auto, mac, ip4, ip6) + def draw_buttons(self): frame = ttk.Frame(self.top) frame.grid(sticky="ew") @@ -101,16 +172,20 @@ class NodeConfigDialog(Dialog): dialog.show() def click_icon(self): - dialog = IconDialog( - self, self.app, self.canvas_node.name, self.canvas_node.image - ) + dialog = IconDialog(self, self.app, self.node.name, self.canvas_node.image) dialog.show() if dialog.image: self.image = dialog.image self.image_button.config(image=self.image) def config_apply(self): - self.canvas_node.name = self.name.get() + # update core node + self.node.name = self.name.get() + + # update canvas node self.canvas_node.image = self.image - self.canvas_node.canvas.itemconfig(self.canvas_node.id, image=self.image) + + # redraw + self.canvas_node.redraw() + self.destroy() diff --git a/coretk/coretk/dialogs/wlanconfig.py b/coretk/coretk/dialogs/wlanconfig.py index 85a2a18f..4f213e68 100644 --- a/coretk/coretk/dialogs/wlanconfig.py +++ b/coretk/coretk/dialogs/wlanconfig.py @@ -13,13 +13,14 @@ from coretk.dialogs.mobilityconfig import MobilityConfigDialog class WlanConfigDialog(Dialog): def __init__(self, master, app, canvas_node, config): super().__init__( - master, app, f"{canvas_node.name} Wlan Configuration", modal=True + master, app, f"{canvas_node.core_node.name} Wlan Configuration", modal=True ) self.image = canvas_node.image self.canvas_node = canvas_node + self.node = canvas_node.core_node self.config = config - self.name = tk.StringVar(value=canvas_node.name) + self.name = tk.StringVar(value=self.node.name) self.range_var = tk.StringVar(value=config["range"]) self.bandwidth_var = tk.StringVar(value=config["bandwidth"]) self.delay_var = tk.StringVar(value=config["delay"]) @@ -169,9 +170,7 @@ class WlanConfigDialog(Dialog): dialog.show() def click_icon(self): - dialog = IconDialog( - self, self.app, self.canvas_node.name, self.canvas_node.image - ) + dialog = IconDialog(self, self.app, self.node.name, self.canvas_node.image) dialog.show() if dialog.image: self.image = dialog.image @@ -192,7 +191,7 @@ class WlanConfigDialog(Dialog): # set wireless node configuration here wlanconfig_manager = self.app.core.wlanconfig_management wlanconfig_manager.set_custom_config( - node_id=self.canvas_node.core_id, + node_id=self.node.id, range=basic_range, bandwidth=bandwidth, jitter=jitter, diff --git a/coretk/coretk/graph.py b/coretk/coretk/graph.py index 38d67079..3eb002e6 100644 --- a/coretk/coretk/graph.py +++ b/coretk/coretk/graph.py @@ -155,21 +155,22 @@ class CanvasGraph(tk.Canvas): # draw nodes on the canvas image = NodeUtils.node_icon(core_node.type, core_node.model) - position = core_node.position - node = CanvasNode(position.x, position.y, image, self.master, core_node) + node = CanvasNode(self.master, core_node, image) self.nodes[node.id] = node self.core.canvas_nodes[core_node.id] = node # draw existing links for link in session.links: canvas_node_one = self.core.canvas_nodes[link.node_one_id] + node_one = canvas_node_one.core_node canvas_node_two = self.core.canvas_nodes[link.node_two_id] + node_two = canvas_node_two.core_node is_wired = link.type == core_pb2.LinkType.WIRED edge = CanvasEdge( - canvas_node_one.x_coord, - canvas_node_one.y_coord, - canvas_node_two.x_coord, - canvas_node_two.y_coord, + node_one.position.x, + node_one.position.y, + node_two.position.x, + node_two.position.y, canvas_node_one.id, self, is_wired=is_wired, @@ -402,7 +403,7 @@ class CanvasGraph(tk.Canvas): core_node = self.core.create_node( int(x), int(y), self.node_draw.node_type, self.node_draw.model ) - node = CanvasNode(x, y, self.node_draw.image, self.master, core_node) + node = CanvasNode(self.master, core_node, self.node_draw.image) self.core.canvas_nodes[core_node.id] = node self.nodes[node.id] = node return node @@ -590,19 +591,18 @@ class CanvasEdge: class CanvasNode: - def __init__(self, x, y, image, app, core_node): - self.image = image + def __init__(self, app, core_node, image): self.app = app self.canvas = app.canvas + self.image = image + self.core_node = core_node + x = self.core_node.position.x + y = self.core_node.position.y self.id = self.canvas.create_image( x, y, anchor=tk.CENTER, image=self.image, tags="node" ) - self.core_node = core_node - self.name = core_node.name - self.x_coord = x - self.y_coord = y self.text_id = self.canvas.create_text( - x, y + 20, text=self.name, tags="nodename" + x, y + 20, text=self.core_node.name, tags="nodename" ) self.antenna_draw = WlanAntennaManager(self.canvas, self.id) self.tooltip = CanvasTooltip(self.canvas) @@ -620,6 +620,10 @@ class CanvasNode: self.wlans = [] self.moving = None + def redraw(self): + self.canvas.itemconfig(self.id, image=self.image) + self.canvas.itemconfig(self.text_id, text=self.core_node.name) + def on_enter(self, event): if self.app.core.is_runtime() and self.app.core.observer: self.tooltip.text.set("waiting...") @@ -640,18 +644,18 @@ class CanvasNode: self.canvas.canvas_action.display_configuration(self) def update_coords(self): - self.x_coord, self.y_coord = self.canvas.coords(self.id) - self.core_node.position.x = int(self.x_coord) - self.core_node.position.y = int(self.y_coord) + x, y = self.canvas.coords(self.id) + self.core_node.position.x = int(x) + self.core_node.position.y = int(y) def click_press(self, event): - logging.debug(f"node click press {self.name}: {event}") + logging.debug(f"node click press {self.core_node.name}: {event}") self.moving = self.canvas.canvas_xy(event) self.canvas.canvas_management.node_select(self) def click_release(self, event): - logging.debug(f"node click release {self.name}: {event}") + logging.debug(f"node click release {self.core_node.name}: {event}") self.update_coords() self.moving = None @@ -681,7 +685,6 @@ class CanvasNode: else: self.canvas.coords(edge.id, x1, y1, new_x, new_y) edge.link_info.recalculate_info() - # self.canvas.core_grpc.throughput_draw.update_throughtput_location(edge) self.canvas.helper.update_wlan_connection( old_x, old_y, new_x, new_y, self.wlans @@ -691,4 +694,4 @@ class CanvasNode: self.canvas.canvas_management.node_select(self, True) def context(self, event): - logging.debug(f"context click {self.name}: {event}") + logging.debug(f"context click {self.core_node.name}: {event}") diff --git a/coretk/coretk/themes.py b/coretk/coretk/themes.py index 0cf54eb8..267c3a3c 100644 --- a/coretk/coretk/themes.py +++ b/coretk/coretk/themes.py @@ -85,7 +85,8 @@ def load(style): "fieldbackground": Colors.white, "foreground": Colors.black, "padding": (2, 0), - } + }, + "map": {"fieldbackground": [("disabled", Colors.frame)]}, }, "TCombobox": { "configure": { From 07c07da0996eb911e14e5c9ff2e6b783630b9bb0 Mon Sep 17 00:00:00 2001 From: bharnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 20 Nov 2019 11:20:08 -0800 Subject: [PATCH 2/2] update node config dialog to display fields based on node type, added field for nodes with images --- coretk/coretk/canvasaction.py | 3 +- coretk/coretk/coreclient.py | 6 +++- coretk/coretk/dialogs/nodeconfig.py | 56 +++++++++++++++++++---------- coretk/coretk/nodeutils.py | 16 +++++++-- 4 files changed, 57 insertions(+), 24 deletions(-) diff --git a/coretk/coretk/canvasaction.py b/coretk/coretk/canvasaction.py index 08aa8c3d..509b9723 100644 --- a/coretk/coretk/canvasaction.py +++ b/coretk/coretk/canvasaction.py @@ -5,6 +5,7 @@ from core.api.grpc import core_pb2 from coretk.dialogs.emaneconfig import EmaneConfiguration from coretk.dialogs.nodeconfig import NodeConfigDialog from coretk.dialogs.wlanconfig import WlanConfigDialog +from coretk.nodeutils import NodeUtils class CanvasAction: @@ -16,7 +17,7 @@ class CanvasAction: def display_configuration(self, canvas_node): node_type = canvas_node.core_node.type self.node_to_show_config = canvas_node - if node_type == core_pb2.NodeType.DEFAULT: + if NodeUtils.is_container_node(node_type): self.display_node_configuration() elif node_type == core_pb2.NodeType.WIRELESS_LAN: self.display_wlan_configuration(canvas_node) diff --git a/coretk/coretk/coreclient.py b/coretk/coretk/coreclient.py index 14cc3a11..d9bbd659 100644 --- a/coretk/coretk/coreclient.py +++ b/coretk/coretk/coreclient.py @@ -440,12 +440,16 @@ class CoreClient: """ node_id = self.get_id() position = core_pb2.Position(x=x, y=y) + image = None + if NodeUtils.is_image_node(node_type): + image = "ubuntu:latest" node = core_pb2.Node( id=node_id, type=node_type, name=f"n{node_id}", model=model, position=position, + image=image, ) # set default configuration for wireless node @@ -530,7 +534,7 @@ class CoreClient: def create_interface(self, canvas_node): interface = None core_node = canvas_node.core_node - if NodeUtils.is_interface_node(core_node.type): + if NodeUtils.is_container_node(core_node.type): ifid = len(canvas_node.interfaces) name = f"eth{ifid}" interface = core_pb2.Interface( diff --git a/coretk/coretk/dialogs/nodeconfig.py b/coretk/coretk/dialogs/nodeconfig.py index 8a0d4522..4ed4baa6 100644 --- a/coretk/coretk/dialogs/nodeconfig.py +++ b/coretk/coretk/dialogs/nodeconfig.py @@ -6,9 +6,9 @@ from tkinter import ttk from coretk.dialogs.dialog import Dialog from coretk.dialogs.icondialog import IconDialog from coretk.dialogs.nodeservice import NodeService +from coretk.nodeutils import NodeUtils from coretk.widgets import FrameScroll -DEFAULT_NODES = {"router", "host", "PC", "mdr", "prouter"} PAD = 5 @@ -49,6 +49,7 @@ class NodeConfigDialog(Dialog): self.image_button = None self.name = tk.StringVar(value=self.node.name) self.type = tk.StringVar(value=self.node.model) + self.container_image = tk.StringVar(value=self.node.image) server = "localhost" if self.node.server: server = self.node.server @@ -86,26 +87,39 @@ class NodeConfigDialog(Dialog): row += 1 # node type field - label = ttk.Label(frame, text="Type") - label.grid(row=row, column=0, sticky="ew", padx=PAD, pady=PAD) - combobox = ttk.Combobox( - frame, textvariable=self.type, values=list(DEFAULT_NODES), state="readonly" - ) - combobox.grid(row=row, column=1, sticky="ew") - row += 1 + if NodeUtils.is_model_node(self.node.type): + label = ttk.Label(frame, text="Type") + label.grid(row=row, column=0, sticky="ew", padx=PAD, pady=PAD) + combobox = ttk.Combobox( + frame, + textvariable=self.type, + values=list(NodeUtils.NODE_MODELS), + state="readonly", + ) + combobox.grid(row=row, column=1, sticky="ew") + row += 1 + + # container image field + if NodeUtils.is_image_node(self.node.type): + label = ttk.Label(frame, text="Image") + label.grid(row=row, column=0, sticky="ew", padx=PAD, pady=PAD) + entry = ttk.Entry(frame, textvariable=self.container_image) + entry.grid(row=row, column=1, sticky="ew") + row += 1 # server - frame.grid(sticky="ew") - frame.columnconfigure(1, weight=1) - label = ttk.Label(frame, text="Server") - label.grid(row=row, column=0, sticky="ew", padx=PAD, pady=PAD) - servers = ["localhost"] - servers.extend(list(sorted(self.app.core.servers.keys()))) - combobox = ttk.Combobox( - frame, textvariable=self.server, values=servers, state="readonly" - ) - combobox.grid(row=row, column=1, sticky="ew") - row += 1 + if NodeUtils.is_container_node(self.node.type): + frame.grid(sticky="ew") + frame.columnconfigure(1, weight=1) + label = ttk.Label(frame, text="Server") + label.grid(row=row, column=0, sticky="ew", padx=PAD, pady=PAD) + servers = ["localhost"] + servers.extend(list(sorted(self.app.core.servers.keys()))) + combobox = ttk.Combobox( + frame, textvariable=self.server, values=servers, state="readonly" + ) + combobox.grid(row=row, column=1, sticky="ew") + row += 1 # services button = ttk.Button(self.top, text="Services", command=self.click_services) @@ -181,6 +195,10 @@ class NodeConfigDialog(Dialog): def config_apply(self): # update core node self.node.name = self.name.get() + if NodeUtils.is_image_node(self.node.type): + self.node.image = self.container_image.get() + if NodeUtils.is_container_node(self.node.type): + self.node.server = self.server.get() # update canvas node self.canvas_node.image = self.image diff --git a/coretk/coretk/nodeutils.py b/coretk/coretk/nodeutils.py index 6777718a..380c9a0b 100644 --- a/coretk/coretk/nodeutils.py +++ b/coretk/coretk/nodeutils.py @@ -44,11 +44,21 @@ class NodeUtils: NODES = [] NETWORK_NODES = [] NODE_ICONS = {} - INTERFACE_NODE = {NodeType.DEFAULT, NodeType.DOCKER, NodeType.LXC} + CONTAINER_NODES = {NodeType.DEFAULT, NodeType.DOCKER, NodeType.LXC} + IMAGE_NODES = {NodeType.DOCKER, NodeType.LXC} + NODE_MODELS = {"router", "host", "PC", "mdr", "prouter"} @classmethod - def is_interface_node(cls, node_type): - return node_type in cls.INTERFACE_NODE + def is_container_node(cls, node_type): + return node_type in cls.CONTAINER_NODES + + @classmethod + def is_model_node(cls, node_type): + return node_type == NodeType.DEFAULT + + @classmethod + def is_image_node(cls, node_type): + return node_type in cls.IMAGE_NODES @classmethod def node_icon(cls, node_type, model):