diff --git a/coretk/coretk/app.py b/coretk/coretk/app.py index 4bb23bc0..7330f21a 100644 --- a/coretk/coretk/app.py +++ b/coretk/coretk/app.py @@ -65,12 +65,6 @@ class Application(tk.Frame): def draw_status(self): self.statusbar = tk.Frame(self) self.statusbar.pack(side=tk.BOTTOM, fill=tk.X) - button = tk.Button(self.statusbar, text="Button 1") - button.pack(side=tk.LEFT, padx=1) - button = tk.Button(self.statusbar, text="Button 2") - button.pack(side=tk.LEFT, padx=1) - button = tk.Button(self.statusbar, text="Button 3") - button.pack(side=tk.LEFT, padx=1) def on_closing(self): menu_action = MenuAction(self, self.master) @@ -78,7 +72,8 @@ class Application(tk.Frame): if __name__ == "__main__": - logging.basicConfig(level=logging.DEBUG) + log_format = "%(asctime)s - %(levelname)s - %(module)s:%(funcName)s - %(message)s" + logging.basicConfig(level=logging.DEBUG, format=log_format) appdirs.check_directory() app = Application() app.mainloop() diff --git a/coretk/coretk/canvastooltip.py b/coretk/coretk/canvastooltip.py new file mode 100644 index 00000000..42270809 --- /dev/null +++ b/coretk/coretk/canvastooltip.py @@ -0,0 +1,134 @@ +import tkinter as tk +from tkinter import ttk + + +class CanvasTooltip: + """ + It creates a tooltip for a given canvas tag or id as the mouse is + above it. + + This class has been derived from the original Tooltip class updated + and posted back to StackOverflow at the following link: + + https://stackoverflow.com/questions/3221956/ + what-is-the-simplest-way-to-make-tooltips-in-tkinter/ + 41079350#41079350 + + Alberto Vassena on 2016.12.10. + """ + + def __init__( + self, + canvas, + tag_or_id, + *, + bg="#FFFFEA", + pad=(5, 3, 5, 3), + text="canvas info", + waittime=400, + wraplength=250 + ): + # in miliseconds, originally 500 + self.waittime = waittime + # in pixels, originally 180 + self.wraplength = wraplength + self.canvas = canvas + self.text = text + self.canvas.tag_bind(tag_or_id, "", self.on_enter) + self.canvas.tag_bind(tag_or_id, "", self.on_leave) + self.canvas.tag_bind(tag_or_id, "", self.on_leave) + self.bg = bg + self.pad = pad + self.id = None + self.tw = None + + def on_enter(self, event=None): + self.schedule() + + def on_leave(self, event=None): + self.unschedule() + self.hide() + + def schedule(self): + self.unschedule() + self.id = self.canvas.after(self.waittime, self.show) + + def unschedule(self): + id_ = self.id + self.id = None + if id_: + self.canvas.after_cancel(id_) + + def show(self, event=None): + def tip_pos_calculator(canvas, label, *, tip_delta=(10, 5), pad=(5, 3, 5, 3)): + + c = canvas + + s_width, s_height = c.winfo_screenwidth(), c.winfo_screenheight() + + width, height = ( + pad[0] + label.winfo_reqwidth() + pad[2], + pad[1] + label.winfo_reqheight() + pad[3], + ) + + mouse_x, mouse_y = c.winfo_pointerxy() + + x1, y1 = mouse_x + tip_delta[0], mouse_y + tip_delta[1] + x2, y2 = x1 + width, y1 + height + + x_delta = x2 - s_width + if x_delta < 0: + x_delta = 0 + y_delta = y2 - s_height + if y_delta < 0: + y_delta = 0 + + offscreen = (x_delta, y_delta) != (0, 0) + + if offscreen: + + if x_delta: + x1 = mouse_x - tip_delta[0] - width + + if y_delta: + y1 = mouse_y - tip_delta[1] - height + + offscreen_again = y1 < 0 # out on the top + + if offscreen_again: + y1 = 0 + + return x1, y1 + + bg = self.bg + pad = self.pad + canvas = self.canvas + + # creates a toplevel window + self.tw = tk.Toplevel(canvas.master) + + # Leaves only the label and removes the app window + self.tw.wm_overrideredirect(True) + + win = tk.Frame(self.tw, background=bg, borderwidth=0) + label = ttk.Label( + win, + text=self.text, + justify=tk.LEFT, + background=bg, + relief=tk.SOLID, + borderwidth=0, + wraplength=self.wraplength, + ) + + label.grid(padx=(pad[0], pad[2]), pady=(pad[1], pad[3]), sticky=tk.NSEW) + win.grid() + + x, y = tip_pos_calculator(canvas, label) + + self.tw.wm_geometry("+%d+%d" % (x, y)) + + def hide(self): + if self.tw: + self.tw.destroy() + self.tw = None diff --git a/coretk/coretk/coreclient.py b/coretk/coretk/coreclient.py index 05a2b1e9..c2ab3a2f 100644 --- a/coretk/coretk/coreclient.py +++ b/coretk/coretk/coreclient.py @@ -14,8 +14,8 @@ from coretk.mobilitynodeconfig import MobilityNodeConfig from coretk.servicenodeconfig import ServiceNodeConfig from coretk.wlannodeconfig import WlanNodeConfig -link_layer_nodes = ["switch", "hub", "wlan", "rj45", "tunnel", "emane"] -network_layer_nodes = ["router", "host", "PC", "mdr", "prouter"] +NETWORK_NODES = {"switch", "hub", "wlan", "rj45", "tunnel", "emane"} +DEFAULT_NODES = {"router", "host", "PC", "mdr", "prouter"} class Node: @@ -418,6 +418,9 @@ class CoreClient: else: return self.reusable.pop(0) + def is_model_node(self, name): + return name in DEFAULT_NODES or name in self.custom_nodes + def add_graph_node(self, session_id, canvas_id, x, y, name): """ Add node, with information filled in, to grpc manager @@ -431,7 +434,7 @@ class CoreClient: """ node_type = None node_model = None - if name in link_layer_nodes: + if name in NETWORK_NODES: if name == "switch": node_type = core_pb2.NodeType.SWITCH elif name == "hub": @@ -446,7 +449,7 @@ class CoreClient: node_type = core_pb2.NodeType.TUNNEL elif name == "emane": node_type = core_pb2.NodeType.EMANE - elif name in network_layer_nodes: + elif self.is_model_node(name): node_type = core_pb2.NodeType.DEFAULT node_model = name else: @@ -621,7 +624,7 @@ class CoreClient: self.interfaces_manager.new_subnet() src_node = self.nodes[src_canvas_id] - if src_node.model in network_layer_nodes: + if self.is_model_node(src_node.model): ifid = len(src_node.interfaces) name = "eth" + str(ifid) src_interface = Interface( @@ -635,7 +638,7 @@ class CoreClient: ) dst_node = self.nodes[dst_canvas_id] - if dst_node.model in network_layer_nodes: + if self.is_model_node(dst_node.model): ifid = len(dst_node.interfaces) name = "eth" + str(ifid) dst_interface = Interface( diff --git a/coretk/coretk/dialogs/customnodes.py b/coretk/coretk/dialogs/customnodes.py index f2423bf0..20c5ae95 100644 --- a/coretk/coretk/dialogs/customnodes.py +++ b/coretk/coretk/dialogs/customnodes.py @@ -15,7 +15,7 @@ class ServicesSelectDialog(Dialog): self.groups = None self.services = None self.current = None - self.current_services = current_services + self.current_services = set(current_services) self.draw() def draw(self): @@ -48,7 +48,7 @@ class ServicesSelectDialog(Dialog): frame.grid(stick="ew") for i in range(2): frame.columnconfigure(i, weight=1) - button = tk.Button(frame, text="Save", command=self.click_cancel) + button = tk.Button(frame, text="Save", command=self.destroy) button.grid(row=0, column=0, sticky="ew") button = tk.Button(frame, text="Cancel", command=self.click_cancel) button.grid(row=0, column=1, sticky="ew") @@ -174,7 +174,8 @@ class CustomNodesDialog(Dialog): dialog = ServicesSelectDialog(self, self.app, self.services) dialog.show() if dialog.current_services is not None: - self.services = dialog.current_services + self.services.clear() + self.services.update(dialog.current_services) def click_save(self): self.app.config["nodes"].clear() @@ -208,7 +209,7 @@ class CustomNodesDialog(Dialog): custom_node = self.app.core.custom_nodes.pop(previous_name) custom_node.name = name custom_node.image = self.image - custom_node.image_file = Path(self.image_file).name + custom_node.image_file = Path(self.image_file).stem custom_node.services = self.services self.app.core.custom_nodes[name] = custom_node self.nodes_list.listbox.delete(self.selected_index) diff --git a/coretk/coretk/graph.py b/coretk/coretk/graph.py index 19491180..80360c44 100644 --- a/coretk/coretk/graph.py +++ b/coretk/coretk/graph.py @@ -4,6 +4,7 @@ import tkinter as tk from core.api.grpc import core_pb2 from coretk.canvasaction import CanvasAction +from coretk.canvastooltip import CanvasTooltip from coretk.graph_helper import GraphHelper, WlanAntennaManager from coretk.images import Images from coretk.interface import Interface @@ -512,6 +513,7 @@ class CanvasNode: self.canvas.tag_bind(self.id, "", self.context) self.canvas.tag_bind(self.id, "", self.double_click) self.canvas.tag_bind(self.id, "", self.select_multiple) + self.tooltip = CanvasTooltip(self.canvas, self.id, text=self.name) self.edges = set() self.wlans = [] diff --git a/coretk/coretk/menuaction.py b/coretk/coretk/menuaction.py index 88f32bf4..3b33377c 100644 --- a/coretk/coretk/menuaction.py +++ b/coretk/coretk/menuaction.py @@ -17,214 +17,6 @@ from coretk.dialogs.sessionoptions import SessionOptionsDialog from coretk.dialogs.sessions import SessionsDialog -def sub_menu_items(): - logging.debug("Click on sub menu items") - - -def file_new(event=None): - logging.debug("Click file New") - - -def file_reload(): - logging.debug("Click file Reload") - - -def file_export_python_script(): - logging.debug("Click file export python script") - - -def file_execute_xml_or_python_script(): - logging.debug("Execute XML or Python script") - - -def file_execute_python_script_with_options(): - logging.debug("Click execute Python script with options") - - -def file_open_current_file_in_editor(): - logging.debug("Click file open current in editor") - - -def file_print(): - logging.debug("Click file Print") - - -def file_save_screenshot(): - logging.debug("Click file save screenshot") - - -def edit_undo(event=None): - logging.debug("Click edit undo") - - -def edit_redo(event=None): - logging.debug("Click edit redo") - - -def edit_cut(event=None): - logging.debug("Click edit cut") - - -def edit_copy(event=None): - logging.debug("Click edit copy") - - -def edit_paste(event=None): - logging.debug("Click edit paste") - - -def edit_select_all(event=None): - logging.debug("Click edit select all") - - -def edit_select_adjacent(event=None): - logging.debug("Click edit select adjacent") - - -def edit_find(event=None): - logging.debug("CLick edit find") - - -def edit_clear_marker(): - logging.debug("Click edit clear marker") - - -def edit_preferences(): - logging.debug("Click preferences") - - -def canvas_new(): - logging.debug("Click canvas new") - - -def canvas_manage(): - logging.debug("Click canvas manage") - - -def canvas_delete(): - logging.debug("Click canvas delete") - - -def canvas_previous(event=None): - logging.debug("Click canvas previous") - - -def canvas_next(event=None): - logging.debug("Click canvas next") - - -def canvas_first(event=None): - logging.debug("CLick canvas first") - - -def canvas_last(event=None): - logging.debug("CLick canvas last") - - -def view_show(): - logging.debug("Click view show") - - -def view_show_hidden_nodes(): - logging.debug("Click view show hidden nodes") - - -def view_locked(): - logging.debug("Click view locked") - - -def view_3d_gui(): - logging.debug("CLick view 3D GUI") - - -def view_zoom_in(event=None): - logging.debug("Click view zoom in") - - -def view_zoom_out(event=None): - logging.debug("Click view zoom out") - - -def tools_auto_rearrange_all(): - logging.debug("Click tools, auto rearrange all") - - -def tools_auto_rearrange_selected(): - logging.debug("CLick tools auto rearrange selected") - - -def tools_align_to_grid(): - logging.debug("Click tools align to grid") - - -def tools_traffic(): - logging.debug("Click tools traffic") - - -def tools_ip_addresses(): - logging.debug("Click tools ip addresses") - - -def tools_mac_addresses(): - logging.debug("Click tools mac addresses") - - -def tools_build_hosts_file(): - logging.debug("Click tools build hosts file") - - -def tools_renumber_nodes(): - logging.debug("Click tools renumber nodes") - - -def tools_experimental(): - logging.debug("Click tools experimental") - - -def tools_topology_generator(): - logging.debug("Click tools topology generator") - - -def tools_debugger(): - logging.debug("Click tools debugger") - - -def widgets_observer_widgets(): - logging.debug("Click widgets observer widgets") - - -def widgets_adjacency(): - logging.debug("Click widgets adjacency") - - -def widgets_throughput(): - logging.debug("Click widgets throughput") - - -def widgets_configure_adjacency(): - logging.debug("Click widgets configure adjacency") - - -def widgets_configure_throughput(): - logging.debug("Click widgets configure throughput") - - -def session_node_types(): - logging.debug("Click session node types") - - -def session_comments(): - logging.debug("Click session comments") - - -def session_reset_node_positions(): - logging.debug("Click session reset node positions") - - -def help_about(): - logging.debug("Click help About") - - class MenuAction: """ Actions performed when choosing menu items @@ -258,7 +50,7 @@ class MenuAction: self.app.core.stop_session() self.app.core.delete_session() - def on_quit(self): + def on_quit(self, event=None): """ Prompt user whether so save running session, and then close the application diff --git a/coretk/coretk/menubar.py b/coretk/coretk/menubar.py index 143833b7..3ef38d73 100644 --- a/coretk/coretk/menubar.py +++ b/coretk/coretk/menubar.py @@ -43,52 +43,33 @@ class Menubar(tk.Menu): :return: nothing """ - file_menu = tk.Menu(self) - file_menu.add_command( - label="New Session", - command=action.file_new, - accelerator="Ctrl+N", - underline=0, - ) - self.app.bind_all("", action.file_new) - file_menu.add_command( - label="Open...", - command=self.menuaction.file_open_xml, - accelerator="Ctrl+O", - underline=0, + menu = tk.Menu(self) + menu.add_command(label="New Session", accelerator="Ctrl+N", state=tk.DISABLED) + menu.add_command( + label="Open...", command=self.menuaction.file_open_xml, accelerator="Ctrl+O" ) self.app.bind_all("", self.menuaction.file_open_xml) - file_menu.add_command(label="Reload", command=action.file_reload, underline=0) - file_menu.add_command( + menu.add_command(label="Reload", underline=0, state=tk.DISABLED) + menu.add_command( label="Save", accelerator="Ctrl+S", command=self.menuaction.file_save_as_xml ) self.app.bind_all("", self.menuaction.file_save_as_xml) - file_menu.add_separator() - file_menu.add_command( - label="Export Python script...", command=action.file_export_python_script + menu.add_separator() + menu.add_command(label="Export Python script...", state=tk.DISABLED) + menu.add_command(label="Execute XML or Python script...", state=tk.DISABLED) + menu.add_command( + label="Execute Python script with options...", state=tk.DISABLED ) - file_menu.add_command( - label="Execute XML or Python script...", - command=action.file_execute_xml_or_python_script, + menu.add_separator() + menu.add_command(label="Open current file in editor...", state=tk.DISABLED) + menu.add_command(label="Print...", underline=0, state=tk.DISABLED) + menu.add_command(label="Save screenshot...", state=tk.DISABLED) + menu.add_separator() + menu.add_command( + label="Quit", accelerator="Ctrl+Q", command=self.menuaction.on_quit ) - file_menu.add_command( - label="Execute Python script with options...", - command=action.file_execute_python_script_with_options, - ) - file_menu.add_separator() - file_menu.add_command( - label="Open current file in editor...", - command=action.file_open_current_file_in_editor, - ) - file_menu.add_command(label="Print...", command=action.file_print, underline=0) - file_menu.add_command( - label="Save screenshot...", command=action.file_save_screenshot - ) - file_menu.add_separator() - file_menu.add_command( - label="Quit", command=self.menuaction.on_quit, underline=0 - ) - self.add_cascade(label="File", menu=file_menu, underline=0) + self.app.bind_all("", self.menuaction.on_quit) + self.add_cascade(label="File", menu=menu) def draw_edit_menu(self): """ @@ -96,47 +77,23 @@ class Menubar(tk.Menu): :return: nothing """ - edit_menu = tk.Menu(self) - edit_menu.add_command( - label="Undo", command=action.edit_undo, accelerator="Ctrl+Z", underline=0 + menu = tk.Menu(self) + menu.add_command(label="Undo", accelerator="Ctrl+Z", state=tk.DISABLED) + menu.add_command(label="Redo", accelerator="Ctrl+Y", state=tk.DISABLED) + menu.add_separator() + menu.add_command(label="Cut", accelerator="Ctrl+X", state=tk.DISABLED) + menu.add_command(label="Copy", accelerator="Ctrl+C", state=tk.DISABLED) + menu.add_command(label="Paste", accelerator="Ctrl+V", state=tk.DISABLED) + menu.add_separator() + menu.add_command(label="Select all", accelerator="Ctrl+A", state=tk.DISABLED) + menu.add_command( + label="Select Adjacent", accelerator="Ctrl+J", state=tk.DISABLED ) - self.app.bind_all("", action.edit_undo) - edit_menu.add_command( - label="Redo", command=action.edit_redo, accelerator="Ctrl+Y", underline=0 - ) - self.app.bind_all("", action.edit_redo) - edit_menu.add_separator() - edit_menu.add_command( - label="Cut", command=action.edit_cut, accelerator="Ctrl+X", underline=0 - ) - self.app.bind_all("", action.edit_cut) - edit_menu.add_command( - label="Copy", command=action.edit_copy, accelerator="Ctrl+C", underline=0 - ) - self.app.bind_all("", action.edit_copy) - edit_menu.add_command( - label="Paste", command=action.edit_paste, accelerator="Ctrl+V", underline=0 - ) - self.app.bind_all("", action.edit_paste) - edit_menu.add_separator() - edit_menu.add_command( - label="Select all", command=action.edit_select_all, accelerator="Ctrl+A" - ) - self.app.bind_all("", action.edit_select_all) - edit_menu.add_command( - label="Select Adjacent", - command=action.edit_select_adjacent, - accelerator="Ctrl+J", - ) - self.app.bind_all("", action.edit_select_adjacent) - edit_menu.add_separator() - edit_menu.add_command( - label="Find...", command=action.edit_find, accelerator="Ctrl+F", underline=0 - ) - self.app.bind_all("", action.edit_find) - edit_menu.add_command(label="Clear marker", command=action.edit_clear_marker) - edit_menu.add_command(label="Preferences...", command=action.edit_preferences) - self.add_cascade(label="Edit", menu=edit_menu, underline=0) + menu.add_separator() + menu.add_command(label="Find...", accelerator="Ctrl+F", state=tk.DISABLED) + menu.add_command(label="Clear marker", state=tk.DISABLED) + menu.add_command(label="Preferences...", state=tk.DISABLED) + self.add_cascade(label="Edit", menu=menu) def draw_canvas_menu(self): """ @@ -144,55 +101,23 @@ class Menubar(tk.Menu): :return: nothing """ - canvas_menu = tk.Menu(self) - canvas_menu.add_command(label="New", command=action.canvas_new) - canvas_menu.add_command(label="Manage...", command=action.canvas_manage) - canvas_menu.add_command(label="Delete", command=action.canvas_delete) - canvas_menu.add_separator() - canvas_menu.add_command( + menu = tk.Menu(self) + menu.add_command(label="New", state=tk.DISABLED) + menu.add_command(label="Manage...", state=tk.DISABLED) + menu.add_command(label="Delete", state=tk.DISABLED) + menu.add_separator() + menu.add_command( label="Size/scale...", command=self.menuaction.canvas_size_and_scale ) - canvas_menu.add_command( + menu.add_command( label="Wallpaper...", command=self.menuaction.canvas_set_wallpaper ) - canvas_menu.add_separator() - canvas_menu.add_command( - label="Previous", command=action.canvas_previous, accelerator="PgUp" - ) - self.app.bind_all("", action.canvas_previous) - canvas_menu.add_command( - label="Next", command=action.canvas_next, accelerator="PgDown" - ) - self.app.bind_all("", action.canvas_next) - canvas_menu.add_command( - label="First", command=action.canvas_first, accelerator="Home" - ) - self.app.bind_all("", action.canvas_first) - canvas_menu.add_command( - label="Last", command=action.canvas_last, accelerator="End" - ) - self.app.bind_all("", action.canvas_last) - self.add_cascade(label="Canvas", menu=canvas_menu, underline=0) - - def create_show_menu(self, view_menu): - """ - Create the menu items in View/Show - - :param tkinter.Menu view_menu: the view menu - :return: nothing - """ - show_menu = tk.Menu(view_menu) - show_menu.add_command(label="All", command=action.sub_menu_items) - show_menu.add_command(label="None", command=action.sub_menu_items) - show_menu.add_separator() - show_menu.add_command(label="Interface Names", command=action.sub_menu_items) - show_menu.add_command(label="IPv4 Addresses", command=action.sub_menu_items) - show_menu.add_command(label="IPv6 Addresses", command=action.sub_menu_items) - show_menu.add_command(label="Node Labels", command=action.sub_menu_items) - show_menu.add_command(label="Annotations", command=action.sub_menu_items) - show_menu.add_command(label="Grid", command=action.sub_menu_items) - show_menu.add_command(label="API Messages", command=action.sub_menu_items) - view_menu.add_cascade(label="Show", menu=show_menu) + menu.add_separator() + menu.add_command(label="Previous", accelerator="PgUp", state=tk.DISABLED) + menu.add_command(label="Next", accelerator="PgDown", state=tk.DISABLED) + menu.add_command(label="First", accelerator="Home", state=tk.DISABLED) + menu.add_command(label="Last", accelerator="End", state=tk.DISABLED) + self.add_cascade(label="Canvas", menu=menu) def draw_view_menu(self): """ @@ -202,21 +127,33 @@ class Menubar(tk.Menu): """ view_menu = tk.Menu(self) self.create_show_menu(view_menu) - view_menu.add_command( - label="Show hidden nodes", command=action.view_show_hidden_nodes - ) - view_menu.add_command(label="Locked", command=action.view_locked) - view_menu.add_command(label="3D GUI...", command=action.view_3d_gui) + view_menu.add_command(label="Show hidden nodes", state=tk.DISABLED) + view_menu.add_command(label="Locked", state=tk.DISABLED) + view_menu.add_command(label="3D GUI...", state=tk.DISABLED) view_menu.add_separator() - view_menu.add_command( - label="Zoom in", command=action.view_zoom_in, accelerator="+" - ) - self.app.bind_all("", action.view_zoom_in) - view_menu.add_command( - label="Zoom out", command=action.view_zoom_out, accelerator="-" - ) - self.app.bind_all("", action.view_zoom_out) - self.add_cascade(label="View", menu=view_menu, underline=0) + view_menu.add_command(label="Zoom in", accelerator="+", state=tk.DISABLED) + view_menu.add_command(label="Zoom out", accelerator="-", state=tk.DISABLED) + self.add_cascade(label="View", menu=view_menu) + + def create_show_menu(self, view_menu): + """ + Create the menu items in View/Show + + :param tkinter.Menu view_menu: the view menu + :return: nothing + """ + menu = tk.Menu(view_menu) + menu.add_command(label="All", state=tk.DISABLED) + menu.add_command(label="None", state=tk.DISABLED) + menu.add_separator() + menu.add_command(label="Interface Names", state=tk.DISABLED) + menu.add_command(label="IPv4 Addresses", state=tk.DISABLED) + menu.add_command(label="IPv6 Addresses", state=tk.DISABLED) + menu.add_command(label="Node Labels", state=tk.DISABLED) + menu.add_command(label="Annotations", state=tk.DISABLED) + menu.add_command(label="Grid", state=tk.DISABLED) + menu.add_command(label="API Messages", state=tk.DISABLED) + view_menu.add_cascade(label="Show", menu=menu) def create_experimental_menu(self, tools_menu): """ @@ -225,19 +162,11 @@ class Menubar(tk.Menu): :param tkinter.Menu tools_menu: tools menu :return: nothing """ - experimental_menu = tk.Menu(tools_menu) - experimental_menu.add_command( - label="Plugins...", command=action.sub_menu_items, underline=0 - ) - experimental_menu.add_command( - label="ns2immunes converter...", command=action.sub_menu_items, underline=0 - ) - experimental_menu.add_command( - label="Topology partitioning...", command=action.sub_menu_items - ) - tools_menu.add_cascade( - label="Experimental", menu=experimental_menu, underline=0 - ) + menu = tk.Menu(tools_menu) + menu.add_command(label="Plugins...", state=tk.DISABLED) + menu.add_command(label="ns2immunes converter...", state=tk.DISABLED) + menu.add_command(label="Topology partitioning...", state=tk.DISABLED) + tools_menu.add_cascade(label="Experimental", menu=menu) def create_random_menu(self, topology_generator_menu): """ @@ -246,15 +175,13 @@ class Menubar(tk.Menu): :param tkinter.Menu topology_generator_menu: topology generator menu :return: nothing """ - random_menu = tk.Menu(topology_generator_menu) + menu = tk.Menu(topology_generator_menu) # list of number of random nodes to create nums = [1, 5, 10, 15, 20, 30, 40, 50, 75, 100] for i in nums: - the_label = "R(" + str(i) + ")" - random_menu.add_command(label=the_label, command=action.sub_menu_items) - topology_generator_menu.add_cascade( - label="Random", menu=random_menu, underline=0 - ) + label = f"R({i})" + menu.add_command(label=label, state=tk.DISABLED) + topology_generator_menu.add_cascade(label="Random", menu=menu) def create_grid_menu(self, topology_generator_menu): """ @@ -263,13 +190,13 @@ class Menubar(tk.Menu): :param tkinter.Menu topology_generator_menu: topology_generator_menu :return: nothing """ - grid_menu = tk.Menu(topology_generator_menu) + menu = tk.Menu(topology_generator_menu) # list of number of nodes to create nums = [1, 5, 10, 15, 20, 25, 30, 35, 40, 50, 60, 70, 80, 90, 100] for i in nums: - the_label = "G(" + str(i) + ")" - grid_menu.add_command(label=the_label, command=action.sub_menu_items) - topology_generator_menu.add_cascade(label="Grid", menu=grid_menu, underline=0) + label = f"G({i})" + menu.add_command(label=label, state=tk.DISABLED) + topology_generator_menu.add_cascade(label="Grid", menu=menu) def create_connected_grid_menu(self, topology_generator_menu): """ @@ -278,17 +205,15 @@ class Menubar(tk.Menu): :param tkinter.Menu topology_generator_menu: topology generator menu :return: nothing """ - grid_menu = tk.Menu(topology_generator_menu) + menu = tk.Menu(topology_generator_menu) for i in range(1, 11, 1): - i_n_menu = tk.Menu(grid_menu) + submenu = tk.Menu(menu) for j in range(1, 11, 1): - i_j_label = str(i) + " X " + str(j) - i_n_menu.add_command(label=i_j_label, command=action.sub_menu_items) - i_n_label = str(i) + " X N" - grid_menu.add_cascade(label=i_n_label, menu=i_n_menu) - topology_generator_menu.add_cascade( - label="Connected Grid", menu=grid_menu, underline=0 - ) + label = f"{i} X {j}" + submenu.add_command(label=label, state=tk.DISABLED) + label = str(i) + " X N" + menu.add_cascade(label=label, menu=submenu) + topology_generator_menu.add_cascade(label="Connected Grid", menu=menu) def create_chain_menu(self, topology_generator_menu): """ @@ -297,13 +222,13 @@ class Menubar(tk.Menu): :param tkinter.Menu topology_generator_menu: topology generator menu :return: nothing """ - chain_menu = tk.Menu(topology_generator_menu) + menu = tk.Menu(topology_generator_menu) # number of nodes to create nums = list(range(2, 25, 1)) + [32, 64, 128] for i in nums: - the_label = "P(" + str(i) + ")" - chain_menu.add_command(label=the_label, command=action.sub_menu_items) - topology_generator_menu.add_cascade(label="Chain", menu=chain_menu, underline=0) + label = f"P({i})" + menu.add_command(label=label, state=tk.DISABLED) + topology_generator_menu.add_cascade(label="Chain", menu=menu) def create_star_menu(self, topology_generator_menu): """ @@ -312,11 +237,11 @@ class Menubar(tk.Menu): :param tkinter.Menu topology_generator_menu: topology generator menu :return: nothing """ - star_menu = tk.Menu(topology_generator_menu) + menu = tk.Menu(topology_generator_menu) for i in range(3, 26, 1): - the_label = "C(" + str(i) + ")" - star_menu.add_command(label=the_label, command=action.sub_menu_items) - topology_generator_menu.add_cascade(label="Star", menu=star_menu, underline=0) + label = f"C({i})" + menu.add_command(label=label, state=tk.DISABLED) + topology_generator_menu.add_cascade(label="Star", menu=menu) def create_cycle_menu(self, topology_generator_menu): """ @@ -325,11 +250,11 @@ class Menubar(tk.Menu): :param tkinter.Menu topology_generator_menu: topology generator menu :return: nothing """ - cycle_menu = tk.Menu(topology_generator_menu) + menu = tk.Menu(topology_generator_menu) for i in range(3, 25, 1): - the_label = "C(" + str(i) + ")" - cycle_menu.add_command(label=the_label, command=action.sub_menu_items) - topology_generator_menu.add_cascade(label="Cycle", menu=cycle_menu, underline=0) + label = f"C({i})" + menu.add_command(label=label, state=tk.DISABLED) + topology_generator_menu.add_cascade(label="Cycle", menu=menu) def create_wheel_menu(self, topology_generator_menu): """ @@ -338,11 +263,11 @@ class Menubar(tk.Menu): :param tkinter.Menu topology_generator_menu: topology generator menu :return: nothing """ - wheel_menu = tk.Menu(topology_generator_menu) + menu = tk.Menu(topology_generator_menu) for i in range(4, 26, 1): - the_label = "W(" + str(i) + ")" - wheel_menu.add_command(label=the_label, command=action.sub_menu_items) - topology_generator_menu.add_cascade(label="Wheel", menu=wheel_menu, underline=0) + label = f"W({i})" + menu.add_command(label=label, state=tk.DISABLED) + topology_generator_menu.add_cascade(label="Wheel", menu=menu) def create_cube_menu(self, topology_generator_menu): """ @@ -351,11 +276,11 @@ class Menubar(tk.Menu): :param tkinter.Menu topology_generator_menu: topology generator menu :return: nothing """ - cube_menu = tk.Menu(topology_generator_menu) + menu = tk.Menu(topology_generator_menu) for i in range(2, 7, 1): - the_label = "Q(" + str(i) + ")" - cube_menu.add_command(label=the_label, command=action.sub_menu_items) - topology_generator_menu.add_cascade(label="Cube", menu=cube_menu, underline=0) + label = f"Q({i})" + menu.add_command(label=label, state=tk.DISABLED) + topology_generator_menu.add_cascade(label="Cube", menu=menu) def create_clique_menu(self, topology_generator_menu): """ @@ -364,13 +289,11 @@ class Menubar(tk.Menu): :param tkinter.Menu topology_generator_menu: topology generator menu :return: nothing """ - clique_menu = tk.Menu(topology_generator_menu) + menu = tk.Menu(topology_generator_menu) for i in range(3, 25, 1): - the_label = "K(" + str(i) + ")" - clique_menu.add_command(label=the_label, command=action.sub_menu_items) - topology_generator_menu.add_cascade( - label="Clique", menu=clique_menu, underline=0 - ) + label = f"K({i})" + menu.add_command(label=label, state=tk.DISABLED) + topology_generator_menu.add_cascade(label="Clique", menu=menu) def create_bipartite_menu(self, topology_generator_menu): """ @@ -379,19 +302,17 @@ class Menubar(tk.Menu): :param tkinter.Menu topology_generator_menu: topology_generator_menu :return: nothing """ - bipartite_menu = tk.Menu(topology_generator_menu) + menu = tk.Menu(topology_generator_menu) temp = 24 for i in range(1, 13, 1): - i_n_menu = tk.Menu(bipartite_menu) + submenu = tk.Menu(menu) for j in range(i, temp, 1): - i_j_label = "K(" + str(i) + " X " + str(j) + ")" - i_n_menu.add_command(label=i_j_label, command=action.sub_menu_items) - i_n_label = "K(" + str(i) + " X N)" - bipartite_menu.add_cascade(label=i_n_label, menu=i_n_menu) + label = f"K({i} X {j})" + submenu.add_command(label=label, state=tk.DISABLED) + label = f"K({i})" + menu.add_cascade(label=label, menu=submenu) temp = temp - 1 - topology_generator_menu.add_cascade( - label="Bipartite", menu=bipartite_menu, underline=0 - ) + topology_generator_menu.add_cascade(label="Bipartite", menu=menu) def create_topology_generator_menu(self, tools_menu): """ @@ -401,20 +322,18 @@ class Menubar(tk.Menu): :return: nothing """ - topology_generator_menu = tk.Menu(tools_menu) - self.create_random_menu(topology_generator_menu) - self.create_grid_menu(topology_generator_menu) - self.create_connected_grid_menu(topology_generator_menu) - self.create_chain_menu(topology_generator_menu) - self.create_star_menu(topology_generator_menu) - self.create_cycle_menu(topology_generator_menu) - self.create_wheel_menu(topology_generator_menu) - self.create_cube_menu(topology_generator_menu) - self.create_clique_menu(topology_generator_menu) - self.create_bipartite_menu(topology_generator_menu) - tools_menu.add_cascade( - label="Topology generator", menu=topology_generator_menu, underline=0 - ) + menu = tk.Menu(tools_menu) + self.create_random_menu(menu) + self.create_grid_menu(menu) + self.create_connected_grid_menu(menu) + self.create_chain_menu(menu) + self.create_star_menu(menu) + self.create_cycle_menu(menu) + self.create_wheel_menu(menu) + self.create_cube_menu(menu) + self.create_clique_menu(menu) + self.create_bipartite_menu(menu) + tools_menu.add_cascade(label="Topology generator", menu=menu) def draw_tools_menu(self): """ @@ -422,41 +341,21 @@ class Menubar(tk.Menu): :return: nothing """ - tools_menu = tk.Menu(self) - tools_menu.add_command( - label="Auto rearrange all", - command=action.tools_auto_rearrange_all, - underline=0, - ) - tools_menu.add_command( - label="Auto rearrange selected", - command=action.tools_auto_rearrange_selected, - underline=0, - ) - tools_menu.add_separator() - tools_menu.add_command( - label="Align to grid", command=action.tools_align_to_grid, underline=0 - ) - tools_menu.add_separator() - tools_menu.add_command(label="Traffic...", command=action.tools_traffic) - tools_menu.add_command( - label="IP addresses...", command=action.tools_ip_addresses, underline=0 - ) - tools_menu.add_command( - label="MAC addresses...", command=action.tools_mac_addresses, underline=0 - ) - tools_menu.add_command( - label="Build hosts file...", - command=action.tools_build_hosts_file, - underline=0, - ) - tools_menu.add_command( - label="Renumber nodes...", command=action.tools_renumber_nodes, underline=0 - ) - self.create_experimental_menu(tools_menu) - self.create_topology_generator_menu(tools_menu) - tools_menu.add_command(label="Debugger...", command=action.tools_debugger) - self.add_cascade(label="Tools", menu=tools_menu, underline=0) + menu = tk.Menu(self) + menu.add_command(label="Auto rearrange all", state=tk.DISABLED) + menu.add_command(label="Auto rearrange selected", state=tk.DISABLED) + menu.add_separator() + menu.add_command(label="Align to grid", state=tk.DISABLED) + menu.add_separator() + menu.add_command(label="Traffic...", state=tk.DISABLED) + menu.add_command(label="IP addresses...", state=tk.DISABLED) + menu.add_command(label="MAC addresses...", state=tk.DISABLED) + menu.add_command(label="Build hosts file...", state=tk.DISABLED) + menu.add_command(label="Renumber nodes...", state=tk.DISABLED) + self.create_experimental_menu(menu) + self.create_topology_generator_menu(menu) + menu.add_command(label="Debugger...", state=tk.DISABLED) + self.add_cascade(label="Tools", menu=menu) def create_observer_widgets_menu(self, widget_menu): """ @@ -465,54 +364,24 @@ class Menubar(tk.Menu): :param tkinter.Menu widget_menu: widget_menu :return: nothing """ - observer_widget_menu = tk.Menu(widget_menu) - observer_widget_menu.add_command(label="None", command=action.sub_menu_items) - observer_widget_menu.add_command( - label="processes", command=action.sub_menu_items - ) - observer_widget_menu.add_command( - label="ifconfig", command=action.sub_menu_items - ) - observer_widget_menu.add_command( - label="IPv4 routes", command=action.sub_menu_items - ) - observer_widget_menu.add_command( - label="IPv6 routes", command=action.sub_menu_items - ) - observer_widget_menu.add_command( - label="OSPFv2 neighbors", command=action.sub_menu_items - ) - observer_widget_menu.add_command( - label="OSPFv3 neighbors", command=action.sub_menu_items - ) - observer_widget_menu.add_command( - label="Listening sockets", command=action.sub_menu_items - ) - observer_widget_menu.add_command( - label="IPv4 MFC entries", command=action.sub_menu_items - ) - observer_widget_menu.add_command( - label="IPv6 MFC entries", command=action.sub_menu_items - ) - observer_widget_menu.add_command( - label="firewall rules", command=action.sub_menu_items - ) - observer_widget_menu.add_command( - label="IPsec policies", command=action.sub_menu_items - ) - observer_widget_menu.add_command( - label="docker logs", command=action.sub_menu_items - ) - observer_widget_menu.add_command( - label="OSPFv3 MDR level", command=action.sub_menu_items - ) - observer_widget_menu.add_command( - label="PIM neighbors", command=action.sub_menu_items - ) - observer_widget_menu.add_command( - label="Edit...", command=self.menuaction.edit_observer_widgets - ) - widget_menu.add_cascade(label="Observer Widgets", menu=observer_widget_menu) + menu = tk.Menu(widget_menu) + menu.add_command(label="None", state=tk.DISABLED) + menu.add_command(label="processes", state=tk.DISABLED) + menu.add_command(label="ifconfig", state=tk.DISABLED) + menu.add_command(label="IPv4 routes", state=tk.DISABLED) + menu.add_command(label="IPv6 routes", state=tk.DISABLED) + menu.add_command(label="OSPFv2 neighbors", state=tk.DISABLED) + menu.add_command(label="OSPFv3 neighbors", state=tk.DISABLED) + menu.add_command(label="Listening sockets", state=tk.DISABLED) + menu.add_command(label="IPv4 MFC entries", state=tk.DISABLED) + menu.add_command(label="IPv6 MFC entries", state=tk.DISABLED) + menu.add_command(label="firewall rules", state=tk.DISABLED) + menu.add_command(label="IPsec policies", state=tk.DISABLED) + menu.add_command(label="docker logs", state=tk.DISABLED) + menu.add_command(label="OSPFv3 MDR level", state=tk.DISABLED) + menu.add_command(label="PIM neighbors", state=tk.DISABLED) + menu.add_command(label="Edit...", command=self.menuaction.edit_observer_widgets) + widget_menu.add_cascade(label="Observer Widgets", menu=menu) def create_adjacency_menu(self, widget_menu): """ @@ -521,12 +390,12 @@ class Menubar(tk.Menu): :param tkinter.Menu widget_menu: widget menu :return: nothing """ - adjacency_menu = tk.Menu(widget_menu) - adjacency_menu.add_command(label="OSPFv2", command=action.sub_menu_items) - adjacency_menu.add_command(label="OSPFv3", command=action.sub_menu_items) - adjacency_menu.add_command(label="OSLR", command=action.sub_menu_items) - adjacency_menu.add_command(label="OSLRv2", command=action.sub_menu_items) - widget_menu.add_cascade(label="Adjacency", menu=adjacency_menu) + menu = tk.Menu(widget_menu) + menu.add_command(label="OSPFv2", state=tk.DISABLED) + menu.add_command(label="OSPFv3", state=tk.DISABLED) + menu.add_command(label="OSLR", state=tk.DISABLED) + menu.add_command(label="OSLRv2", state=tk.DISABLED) + widget_menu.add_cascade(label="Adjacency", menu=menu) def draw_widgets_menu(self): """ @@ -534,18 +403,14 @@ class Menubar(tk.Menu): :return: nothing """ - widget_menu = tk.Menu(self) - self.create_observer_widgets_menu(widget_menu) - self.create_adjacency_menu(widget_menu) - widget_menu.add_command(label="Throughput", command=action.widgets_throughput) - widget_menu.add_separator() - widget_menu.add_command( - label="Configure Adjacency...", command=action.widgets_configure_adjacency - ) - widget_menu.add_command( - label="Configure Throughput...", command=action.widgets_configure_throughput - ) - self.add_cascade(label="Widgets", menu=widget_menu, underline=0) + menu = tk.Menu(self) + self.create_observer_widgets_menu(menu) + self.create_adjacency_menu(menu) + menu.add_command(label="Throughput", state=tk.DISABLED) + menu.add_separator() + menu.add_command(label="Configure Adjacency...", state=tk.DISABLED) + menu.add_command(label="Configure Throughput...", state=tk.DISABLED) + self.add_cascade(label="Widgets", menu=menu) def draw_session_menu(self): """ @@ -553,36 +418,21 @@ class Menubar(tk.Menu): :return: nothing """ - session_menu = tk.Menu(self) - session_menu.add_command( + menu = tk.Menu(self) + menu.add_command( label="Change sessions...", command=self.menuaction.session_change_sessions, underline=0, ) - session_menu.add_separator() - session_menu.add_command( - label="Node types...", command=action.session_node_types, underline=0 + menu.add_separator() + menu.add_command(label="Comments...", state=tk.DISABLED) + menu.add_command(label="Hooks...", command=self.menuaction.session_hooks) + menu.add_command(label="Reset node positions", state=tk.DISABLED) + menu.add_command( + label="Emulation servers...", command=self.menuaction.session_servers ) - session_menu.add_command( - label="Comments...", command=action.session_comments, underline=0 - ) - session_menu.add_command( - label="Hooks...", command=self.menuaction.session_hooks, underline=0 - ) - session_menu.add_command( - label="Reset node positions", - command=action.session_reset_node_positions, - underline=0, - ) - session_menu.add_command( - label="Emulation servers...", - command=self.menuaction.session_servers, - underline=0, - ) - session_menu.add_command( - label="Options...", command=self.menuaction.session_options, underline=0 - ) - self.add_cascade(label="Session", menu=session_menu, underline=0) + menu.add_command(label="Options...", command=self.menuaction.session_options) + self.add_cascade(label="Session", menu=menu) def draw_help_menu(self): """ @@ -590,13 +440,13 @@ class Menubar(tk.Menu): :return: nothing """ - help_menu = tk.Menu(self) - help_menu.add_command( + menu = tk.Menu(self) + menu.add_command( label="Core Github (www)", command=self.menuaction.help_core_github ) - help_menu.add_command( + menu.add_command( label="Core Documentation (www)", command=self.menuaction.help_core_documentation, ) - help_menu.add_command(label="About", command=action.help_about) - self.add_cascade(label="Help", menu=help_menu) + menu.add_command(label="About", state=tk.DISABLED) + self.add_cascade(label="Help", menu=menu) diff --git a/coretk/coretk/toolbar.py b/coretk/coretk/toolbar.py index 298cc24a..5b2c83bd 100644 --- a/coretk/coretk/toolbar.py +++ b/coretk/coretk/toolbar.py @@ -83,8 +83,8 @@ class Toolbar(tk.Frame): "link tool", ) self.create_node_button() - self.create_link_layer_button() - self.create_marker_button() + self.create_network_button() + self.create_annotation_button() self.radio_value.set(1) def draw_runtime_frame(self): @@ -145,15 +145,23 @@ class Toolbar(tk.Frame): (ImageEnum.PC, "PC"), (ImageEnum.MDR, "mdr"), (ImageEnum.PROUTER, "prouter"), - (ImageEnum.EDITNODE, "custom node types"), ] + # draw default nodes for image_enum, tooltip in nodes: - self.create_button( - Images.get(image_enum), - partial(self.update_button, self.node_button, image_enum, tooltip), - self.node_picker, - tooltip, - ) + image = Images.get(image_enum) + func = partial(self.update_button, self.node_button, image, tooltip) + self.create_button(image, func, self.node_picker, tooltip) + # draw custom nodes + for name in sorted(self.app.core.custom_nodes): + custom_node = self.app.core.custom_nodes[name] + image = custom_node.image + func = partial(self.update_button, self.node_button, image, name) + self.create_button(image, func, self.node_picker, name) + # draw edit node + image = Images.get(ImageEnum.EDITNODE) + self.create_button( + image, self.click_edit_node, self.node_picker, "custom nodes" + ) self.show_picker(self.node_button, self.node_picker) def show_picker(self, button, picker): @@ -161,22 +169,24 @@ class Toolbar(tk.Frame): x = button.winfo_rootx() - first_button.winfo_rootx() + 40 y = button.winfo_rooty() - first_button.winfo_rooty() - 1 picker.place(x=x, y=y) - self.app.bind_all("", lambda e: self.hide_pickers()) + self.app.bind_all("", lambda e: self.hide_pickers()) + picker.wait_visibility() + picker.grab_set() self.wait_window(picker) - self.app.unbind_all("") + self.app.unbind_all("") - def create_button(self, img, func, frame, tooltip): + def create_button(self, image, func, frame, tooltip): """ Create button and put it on the frame - :param PIL.Image img: button image + :param PIL.Image image: button image :param func: the command that is executed when button is clicked :param tkinter.Frame frame: frame that contains the button :param str tooltip: tooltip text :return: nothing """ - button = tk.Button(frame, width=self.width, height=self.height, image=img) - button.bind("", lambda e: func()) + button = tk.Button(frame, width=self.width, height=self.height, image=image) + button.bind("", lambda e: func()) button.grid(pady=1) CreateToolTip(button, tooltip) @@ -221,19 +231,18 @@ class Toolbar(tk.Frame): logging.debug("Click LINK button") self.app.canvas.mode = GraphMode.EDGE - def update_button(self, button, image_enum, name): - logging.info("update button(%s): %s, %s", button, image_enum, name) + def click_edit_node(self): self.hide_pickers() - if image_enum == ImageEnum.EDITNODE: - dialog = CustomNodesDialog(self.app, self.app) - dialog.show() - else: - image = Images.get(image_enum) - logging.info("updating button(%s): %s", button, name) - button.configure(image=image) - self.app.canvas.mode = GraphMode.NODE - self.app.canvas.draw_node_image = image - self.app.canvas.draw_node_name = name + dialog = CustomNodesDialog(self.app, self.app) + dialog.show() + + def update_button(self, button, image, name): + logging.info("update button(%s): %s", button, name) + self.hide_pickers() + button.configure(image=image) + self.app.canvas.mode = GraphMode.NODE + self.app.canvas.draw_node_image = image + self.app.canvas.draw_node_name = name def hide_pickers(self): logging.info("hiding pickers") @@ -262,8 +271,8 @@ class Toolbar(tk.Frame): width=self.width, height=self.height, image=router_image, - command=self.draw_node_picker, ) + self.node_button.bind("", lambda e: self.draw_node_picker()) self.node_button.grid() CreateToolTip(self.node_button, "Network-layer virtual nodes") @@ -285,15 +294,16 @@ class Toolbar(tk.Frame): (ImageEnum.TUNNEL, "tunnel", "tunnel tool"), ] for image_enum, name, tooltip in nodes: + image = Images.get(image_enum) self.create_button( - Images.get(image_enum), - partial(self.update_button, self.network_button, image_enum, name), + image, + partial(self.update_button, self.network_button, image, name), self.network_picker, tooltip, ) self.show_picker(self.network_button, self.network_picker) - def create_link_layer_button(self): + def create_network_button(self): """ Create link-layer node button and the options that represent different link-layer node types @@ -308,7 +318,9 @@ class Toolbar(tk.Frame): width=self.width, height=self.height, image=hub_image, - command=self.draw_network_picker, + ) + self.network_button.bind( + "", lambda e: self.draw_network_picker() ) self.network_button.grid() CreateToolTip(self.network_button, "link-layer nodes") @@ -337,7 +349,7 @@ class Toolbar(tk.Frame): ) self.show_picker(self.annotation_button, self.annotation_picker) - def create_marker_button(self): + def create_annotation_button(self): """ Create marker button and options that represent different marker types @@ -352,7 +364,9 @@ class Toolbar(tk.Frame): width=self.width, height=self.height, image=marker_image, - command=self.draw_annotation_picker, + ) + self.annotation_button.bind( + "", lambda e: self.draw_annotation_picker() ) self.annotation_button.grid() CreateToolTip(self.annotation_button, "background annotation tools") diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 01a7b1f6..ceec0448 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -159,6 +159,8 @@ class CoreGrpcClient: emane_model_configs=None, wlan_configs=None, mobility_configs=None, + service_configs=None, + service_file_configs=None, ): """ Start a session. @@ -169,9 +171,11 @@ class CoreGrpcClient: :param core_pb2.SessionLocation location: location to set :param list[core_pb2.Hook] hooks: session hooks to set :param dict emane_config: emane configuration to set - :param list emane_model_configs: emane model configurations to set - :param list wlan_configs: wlan configurations to set - :param list mobility_configs: mobility configurations to set + :param list emane_model_configs: node emane model configurations + :param list wlan_configs: node wlan configurations + :param list mobility_configs: node mobility configurations + :param list service_configs: node service configurations + :param list service_file_configs: node service file configurations :return: start session response :rtype: core_pb2.StartSessionResponse """ @@ -185,6 +189,8 @@ class CoreGrpcClient: emane_model_configs=emane_model_configs, wlan_configs=wlan_configs, mobility_configs=mobility_configs, + service_configs=service_configs, + service_file_configs=service_file_configs, ) return self.stub.StartSession(request) @@ -768,14 +774,14 @@ class CoreGrpcClient: :rtype: core_pb2.SetNodeServiceResponse :raises grpc.RpcError: when session or node doesn't exist """ - request = core_pb2.SetNodeServiceRequest( - session_id=session_id, + config = core_pb2.ServiceConfig( node_id=node_id, service=service, startup=startup, validate=validate, shutdown=shutdown, ) + request = core_pb2.SetNodeServiceRequest(session_id=session_id, config=config) return self.stub.SetNodeService(request) def set_node_service_file(self, session_id, node_id, service, file_name, data): @@ -791,12 +797,11 @@ class CoreGrpcClient: :rtype: core_pb2.SetNodeServiceFileResponse :raises grpc.RpcError: when session or node doesn't exist """ + config = core_pb2.ServiceFileConfig( + node_id=node_id, service=service, file=file_name, data=data + ) request = core_pb2.SetNodeServiceFileRequest( - session_id=session_id, - node_id=node_id, - service=service, - file=file_name, - data=data, + session_id=session_id, config=config ) return self.stub.SetNodeServiceFile(request) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 166807d0..c6b625fb 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -319,3 +319,18 @@ def session_location(session, location): session.location.refxyz = (location.x, location.y, location.z) session.location.setrefgeo(location.lat, location.lon, location.alt) session.location.refscale = location.scale + + +def service_configuration(session, config): + """ + Convenience method for setting a node service configuration. + + :param core.emulator.session.Session session: session for service configuration + :param core_pb2.ServiceConfig config: service configuration + :return: + """ + session.services.set_service(config.node_id, config.service) + service = session.services.get_service(config.node_id, config.service) + service.startup = tuple(config.startup) + service.validate = tuple(config.validate) + service.shutdown = tuple(config.shutdown) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index e1e1f24d..c00cc3af 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -153,6 +153,16 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): config.node_id, Ns2ScriptedMobility.name, config.config ) + # service configs + for config in request.service_configs: + grpcutils.service_configuration(session, config) + + # service file configs + for config in request.service_file_configs: + session.services.set_service_file( + config.node_id, config.service, config.file, config.data + ) + # create links grpcutils.create_links(session, request.links) @@ -1172,11 +1182,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("set node service: %s", request) session = self.get_session(request.session_id, context) - session.services.set_service(request.node_id, request.service) - service = session.services.get_service(request.node_id, request.service) - service.startup = tuple(request.startup) - service.validate = tuple(request.validate) - service.shutdown = tuple(request.shutdown) + config = request.config + grpcutils.service_configuration(session, config) return core_pb2.SetNodeServiceResponse(result=True) def SetNodeServiceFile(self, request, context): @@ -1191,8 +1198,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("set node service file: %s", request) session = self.get_session(request.session_id, context) + config = request.config session.services.set_service_file( - request.node_id, request.service, request.file, request.data + config.node_id, config.service, config.file, config.data ) return core_pb2.SetNodeServiceFileResponse(result=True) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index a53a11bb..ac7cc2ed 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -144,6 +144,8 @@ message StartSessionRequest { repeated WlanConfig wlan_configs = 7; repeated EmaneModelConfig emane_model_configs = 8; repeated MobilityConfig mobility_configs = 9; + repeated ServiceConfig service_configs = 10; + repeated ServiceFileConfig service_file_configs = 11; } message StartSessionResponse { @@ -554,11 +556,7 @@ message GetNodeServiceFileResponse { message SetNodeServiceRequest { int32 session_id = 1; - int32 node_id = 2; - string service = 3; - repeated string startup = 4; - repeated string validate = 5; - repeated string shutdown = 6; + ServiceConfig config = 2; } message SetNodeServiceResponse { @@ -567,10 +565,7 @@ message SetNodeServiceResponse { message SetNodeServiceFileRequest { int32 session_id = 1; - int32 node_id = 2; - string service = 3; - string file = 4; - string data = 5; + ServiceFileConfig config = 2; } message SetNodeServiceFileResponse { @@ -718,6 +713,21 @@ message EmaneModelConfig { map config = 4; } +message ServiceConfig { + int32 node_id = 1; + string service = 2; + repeated string startup = 3; + repeated string validate = 4; + repeated string shutdown = 5; +} + +message ServiceFileConfig { + int32 node_id = 1; + string service = 2; + string file = 3; + string data = 4; +} + message MessageType { enum Enum { NONE = 0; diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 62ff3a22..5f934b64 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -27,7 +27,6 @@ class TestGrpc: # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() - nodes = [] position = core_pb2.Position(x=50, y=100) node_one = core_pb2.Node(id=1, position=position, model="PC") position = core_pb2.Position(x=100, y=100) @@ -36,8 +35,7 @@ class TestGrpc: wlan_node = core_pb2.Node( id=3, type=NodeTypes.WIRELESS_LAN.value, position=position ) - nodes.extend([node_one, node_two, wlan_node]) - links = [] + nodes = [node_one, node_two, wlan_node] interface_helper = InterfaceHelper(ip4_prefix="10.83.0.0/16") interface_one = interface_helper.create_interface(node_one.id, 0) interface_two = interface_helper.create_interface(node_two.id, 0) @@ -48,12 +46,11 @@ class TestGrpc: interface_one=interface_one, interface_two=interface_two, ) - links.append(link) - hooks = [] + links = [link] hook = core_pb2.Hook( state=core_pb2.SessionState.RUNTIME, file="echo.sh", data="echo hello" ) - hooks.append(hook) + hooks = [hook] location_x = 5 location_y = 10 location_z = 15 @@ -73,7 +70,6 @@ class TestGrpc: emane_config_key = "platform_id_start" emane_config_value = "2" emane_config = {emane_config_key: emane_config_value} - model_configs = [] model_node_id = 20 model_config_key = "bandwidth" model_config_value = "500000" @@ -83,21 +79,30 @@ class TestGrpc: model=EmaneIeee80211abgModel.name, config={model_config_key: model_config_value}, ) - model_configs.append(model_config) - wlan_configs = [] + model_configs = [model_config] wlan_config_key = "range" wlan_config_value = "333" wlan_config = core_pb2.WlanConfig( node_id=wlan_node.id, config={wlan_config_key: wlan_config_value} ) - wlan_configs.append(wlan_config) + wlan_configs = [wlan_config] mobility_config_key = "refresh_ms" mobility_config_value = "60" - mobility_configs = [] mobility_config = core_pb2.MobilityConfig( node_id=wlan_node.id, config={mobility_config_key: mobility_config_value} ) - mobility_configs.append(mobility_config) + mobility_configs = [mobility_config] + service_config = core_pb2.ServiceConfig( + node_id=node_one.id, service="DefaultRoute", validate=["echo hello"] + ) + service_configs = [service_config] + service_file_config = core_pb2.ServiceFileConfig( + node_id=node_one.id, + service="DefaultRoute", + file="defaultroute.sh", + data="echo hello", + ) + service_file_configs = [service_file_config] # when with patch.object(CoreXmlWriter, "write"): @@ -112,6 +117,8 @@ class TestGrpc: model_configs, wlan_configs, mobility_configs, + service_configs, + service_file_configs, ) # then @@ -139,6 +146,14 @@ class TestGrpc: model_node_id, EmaneIeee80211abgModel.name ) assert set_model_config[model_config_key] == model_config_value + service = session.services.get_service( + node_one.id, service_config.service, default_service=True + ) + assert service.validate == tuple(service_config.validate) + service_file = session.services.get_service_file( + node_one, service_file_config.service, service_file_config.file + ) + assert service_file.data == service_file_config.data @pytest.mark.parametrize("session_id", [None, 6013]) def test_create_session(self, grpc_server, session_id):