diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index dc9bb82b..7c69f954 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -90,13 +90,7 @@ class CoreClient: self.location = None self.links = {} self.hooks = {} - self.wlan_configs = {} - self.mobility_configs = {} - self.emane_model_configs = {} self.emane_config = None - self.service_configs = {} - self.config_service_configs = {} - self.file_configs = {} self.mobility_players = {} self.handling_throughputs = None self.handling_events = None @@ -128,12 +122,7 @@ class CoreClient: self.canvas_nodes.clear() self.links.clear() self.hooks.clear() - self.wlan_configs.clear() - self.mobility_configs.clear() - self.emane_model_configs.clear() self.emane_config = None - self.service_configs.clear() - self.file_configs.clear() self.modified_service_nodes.clear() for mobility_player in self.mobility_players.values(): mobility_player.handle_close() @@ -303,63 +292,66 @@ class CoreClient: for hook in response.hooks: self.hooks[hook.file] = hook - # get mobility configs - response = self.client.get_mobility_configs(self.session_id) - for node_id in response.configs: - node_config = response.configs[node_id].config - self.mobility_configs[node_id] = node_config - # get emane config response = self.client.get_emane_config(self.session_id) self.emane_config = response.config + # draw session + self.app.canvas.reset_and_redraw(session) + + # get mobility configs + response = self.client.get_mobility_configs(self.session_id) + for node_id in response.configs: + config = response.configs[node_id].config + canvas_node = self.canvas_nodes[node_id] + canvas_node.mobility_config = dict(config) + # get emane model config response = self.client.get_emane_model_configs(self.session_id) for config in response.configs: interface = None if config.interface != -1: interface = config.interface - self.set_emane_model_config( - config.node_id, config.model, config.config, interface + canvas_node = self.canvas_nodes[config.node_id] + canvas_node.emane_model_configs[(config.model, interface)] = dict( + config.config ) # get wlan configurations response = self.client.get_wlan_configs(self.session_id) for _id in response.configs: mapped_config = response.configs[_id] - self.wlan_configs[_id] = mapped_config.config + canvas_node = self.canvas_nodes[_id] + canvas_node.wlan_config = dict(mapped_config.config) # get service configurations response = self.client.get_node_service_configs(self.session_id) for config in response.configs: - service_configs = self.service_configs.setdefault(config.node_id, {}) - service_configs[config.service] = config.data + canvas_node = self.canvas_nodes[config.node_id] + canvas_node.service_configs[config.service] = config.data logging.debug("service file configs: %s", config.files) for file_name in config.files: - file_configs = self.file_configs.setdefault(config.node_id, {}) - files = file_configs.setdefault(config.service, {}) data = config.files[file_name] + files = canvas_node.service_file_configs.setdefault( + config.service, {} + ) files[file_name] = data # get config service configurations response = self.client.get_node_config_service_configs(self.session_id) for config in response.configs: - node_configs = self.config_service_configs.setdefault( - config.node_id, {} + canvas_node = self.canvas_nodes[config.node_id] + service_config = canvas_node.config_service_configs.setdefault( + config.name, {} ) - service_config = node_configs.setdefault(config.name, {}) if config.templates: service_config["templates"] = config.templates if config.config: service_config["config"] = config.config - # draw session - self.app.canvas.reset_and_redraw(session) - # get metadata response = self.client.get_session_metadata(self.session_id) self.parse_metadata(response.config) - except grpc.RpcError as e: self.app.after(0, show_grpc_error, e, self.app, self.app) @@ -556,11 +548,16 @@ class CoreClient: return response def show_mobility_players(self): - for node_id, config in self.mobility_configs.items(): - canvas_node = self.canvas_nodes[node_id] - mobility_player = MobilityPlayer(self.app, self.app, canvas_node, config) - mobility_player.show() - self.mobility_players[node_id] = mobility_player + for canvas_node in self.canvas_nodes.values(): + if canvas_node.core_node.type != core_pb2.NodeType.WIRELESS_LAN: + continue + if canvas_node.mobility_config: + mobility_player = MobilityPlayer( + self.app, self.app, canvas_node, canvas_node.mobility_config + ) + node_id = canvas_node.core_node.id + self.mobility_players[node_id] = mobility_player + mobility_player.show() def set_metadata(self): # create canvas data @@ -841,18 +838,7 @@ class CoreClient: logging.error("unknown node: %s", node_id) continue del self.canvas_nodes[node_id] - self.modified_service_nodes.discard(node_id) - - if node_id in self.mobility_configs: - del self.mobility_configs[node_id] - if node_id in self.wlan_configs: - del self.wlan_configs[node_id] - for key in list(self.emane_model_configs): - node_id, _, _ = key - if node_id == node_id: - del self.emane_model_configs[key] - for edge in canvas_node.edges: if edge in edges: continue @@ -922,37 +908,54 @@ class CoreClient: def get_wlan_configs_proto(self) -> List[WlanConfig]: configs = [] - for node_id, config in self.wlan_configs.items(): + for canvas_node in self.canvas_nodes.values(): + if canvas_node.core_node.type != core_pb2.NodeType.WIRELESS_LAN: + continue + config = canvas_node.wlan_config config = {x: config[x].value for x in config} + node_id = canvas_node.core_node.id wlan_config = WlanConfig(node_id=node_id, config=config) configs.append(wlan_config) return configs def get_mobility_configs_proto(self) -> List[MobilityConfig]: configs = [] - for node_id, config in self.mobility_configs.items(): + for canvas_node in self.canvas_nodes.values(): + if canvas_node.core_node.type != core_pb2.NodeType.WIRELESS_LAN: + continue + config = canvas_node.mobility_config config = {x: config[x].value for x in config} + node_id = canvas_node.core_node.id mobility_config = MobilityConfig(node_id=node_id, config=config) configs.append(mobility_config) return configs def get_emane_model_configs_proto(self) -> List[EmaneModelConfig]: configs = [] - for key, config in self.emane_model_configs.items(): - node_id, model, interface = key - config = {x: config[x].value for x in config} - if interface is None: - interface = -1 - config_proto = EmaneModelConfig( - node_id=node_id, interface_id=interface, model=model, config=config - ) - configs.append(config_proto) + for canvas_node in self.canvas_nodes.values(): + if canvas_node.core_node.type != core_pb2.NodeType.EMANE: + continue + node_id = canvas_node.core_node.id + for key, config in canvas_node.emane_model_configs.items(): + model, interface = key + config = {x: config[x].value for x in config} + if interface is None: + interface = -1 + config_proto = EmaneModelConfig( + node_id=node_id, interface_id=interface, model=model, config=config + ) + configs.append(config_proto) return configs def get_service_configs_proto(self) -> List[ServiceConfig]: configs = [] - for node_id, services in self.service_configs.items(): - for name, config in services.items(): + for canvas_node in self.canvas_nodes.values(): + if not NodeUtils.is_container_node(canvas_node.core_node.type): + continue + if not canvas_node.service_configs: + continue + node_id = canvas_node.core_node.id + for name, config in canvas_node.service_configs.items(): config_proto = ServiceConfig( node_id=node_id, service=name, @@ -967,9 +970,14 @@ class CoreClient: def get_service_file_configs_proto(self) -> List[ServiceFileConfig]: configs = [] - for (node_id, file_configs) in self.file_configs.items(): - for service, file_config in file_configs.items(): - for file, data in file_config.items(): + for canvas_node in self.canvas_nodes.values(): + if not NodeUtils.is_container_node(canvas_node.core_node.type): + continue + if not canvas_node.service_file_configs: + continue + node_id = canvas_node.core_node.id + for service, file_configs in canvas_node.service_file_configs.items(): + for file, data in file_configs.items(): config_proto = ServiceFileConfig( node_id=node_id, service=service, file=file, data=data ) @@ -980,8 +988,13 @@ class CoreClient: self ) -> List[configservices_pb2.ConfigServiceConfig]: config_service_protos = [] - for node_id, node_config in self.config_service_configs.items(): - for name, service_config in node_config.items(): + for canvas_node in self.canvas_nodes.values(): + if not NodeUtils.is_container_node(canvas_node.core_node.type): + continue + if not canvas_node.config_service_configs: + continue + node_id = canvas_node.core_node.id + for name, service_config in canvas_node.config_service_configs.items(): config = service_config.get("config", {}) config_proto = configservices_pb2.ConfigServiceConfig( node_id=node_id, @@ -997,40 +1010,34 @@ class CoreClient: return self.client.node_command(self.session_id, node_id, self.observer).output def get_wlan_config(self, node_id: int) -> Dict[str, common_pb2.ConfigOption]: - config = self.wlan_configs.get(node_id) - if not config: - response = self.client.get_wlan_config(self.session_id, node_id) - config = response.config + response = self.client.get_wlan_config(self.session_id, node_id) + config = response.config logging.debug( "get wlan configuration from node %s, result configuration: %s", node_id, config, ) - return config + return dict(config) def get_mobility_config(self, node_id: int) -> Dict[str, common_pb2.ConfigOption]: - config = self.mobility_configs.get(node_id) - if not config: - response = self.client.get_mobility_config(self.session_id, node_id) - config = response.config + response = self.client.get_mobility_config(self.session_id, node_id) + config = response.config logging.debug( "get mobility config from node %s, result configuration: %s", node_id, config, ) - return config + return dict(config) def get_emane_model_config( self, node_id: int, model: str, interface: int = None ) -> Dict[str, common_pb2.ConfigOption]: - config = self.emane_model_configs.get((node_id, model, interface)) - if not config: - if interface is None: - interface = -1 - response = self.client.get_emane_model_config( - self.session_id, node_id, model, interface - ) - config = response.config + if interface is None: + interface = -1 + response = self.client.get_emane_model_config( + self.session_id, node_id, model, interface + ) + config = response.config logging.debug( "get emane model config: node id: %s, EMANE model: %s, interface: %s, config: %s", node_id, @@ -1038,54 +1045,7 @@ class CoreClient: interface, config, ) - return config - - def set_emane_model_config( - self, - node_id: int, - model: str, - config: Dict[str, common_pb2.ConfigOption], - interface: int = None, - ): - logging.info( - "set emane model config: node id: %s, EMANE Model: %s, interface: %s, config: %s", - node_id, - model, - interface, - config, - ) - self.emane_model_configs[(node_id, model, interface)] = config - - def copy_node_service(self, _from: int, _to: int): - services = self.canvas_nodes[_from].core_node.services - self.canvas_nodes[_to].core_node.services[:] = services - logging.debug("copying node %s service to node %s", _from, _to) - - def copy_node_config(self, _from: int, _to: int): - node_type = self.canvas_nodes[_from].core_node.type - if node_type == core_pb2.NodeType.DEFAULT: - services = self.canvas_nodes[_from].core_node.services - self.canvas_nodes[_to].core_node.services[:] = services - config = self.service_configs.get(_from) - if config: - self.service_configs[_to] = config - file_configs = self.file_configs.get(_from) - if file_configs: - for key, value in file_configs.items(): - if _to not in self.file_configs: - self.file_configs[_to] = {} - self.file_configs[_to][key] = value - elif node_type == core_pb2.NodeType.WIRELESS_LAN: - config = self.wlan_configs.get(_from) - if config: - self.wlan_configs[_to] = config - config = self.mobility_configs.get(_from) - if config: - self.mobility_configs[_to] = config - elif node_type == core_pb2.NodeType.EMANE: - config = self.emane_model_configs.get(_from) - if config: - self.emane_model_configs[_to] = config + return dict(config) def service_been_modified(self, node_id: int) -> bool: return node_id in self.modified_service_nodes diff --git a/daemon/core/gui/dialogs/configserviceconfig.py b/daemon/core/gui/dialogs/configserviceconfig.py index 36c4ccfe..2239034e 100644 --- a/daemon/core/gui/dialogs/configserviceconfig.py +++ b/daemon/core/gui/dialogs/configserviceconfig.py @@ -16,21 +16,26 @@ from core.gui.widgets import CodeText, ConfigFrame, ListboxScroll if TYPE_CHECKING: from core.gui.app import Application + from core.gui.graph.node import CanvasNode class ConfigServiceConfigDialog(Dialog): def __init__( - self, master: Any, app: "Application", service_name: str, node_id: int + self, + master: Any, + app: "Application", + service_name: str, + canvas_node: "CanvasNode", + node_id: int, ): title = f"{service_name} Config Service" super().__init__(master, app, title, modal=True) self.master = master self.app = app self.core = app.core + self.canvas_node = canvas_node self.node_id = node_id self.service_name = service_name - self.service_configs = app.core.config_service_configs - self.radiovar = tk.IntVar() self.radiovar.set(2) self.directories = [] @@ -95,9 +100,9 @@ class ConfigServiceConfigDialog(Dialog): self.modes = sorted(x.name for x in response.modes) self.mode_configs = {x.name: x.config for x in response.modes} - node_configs = self.service_configs.get(self.node_id, {}) - service_config = node_configs.get(self.service_name, {}) - + service_config = self.canvas_node.config_service_configs.get( + self.service_name, {} + ) self.config = response.config self.default_config = {x.name: x.value for x in self.config.values()} custom_config = service_config.get("config") @@ -313,15 +318,15 @@ class ConfigServiceConfigDialog(Dialog): def click_apply(self): current_listbox = self.master.current.listbox if not self.is_custom(): - if self.node_id in self.service_configs: - self.service_configs[self.node_id].pop(self.service_name, None) + self.canvas_node.config_service_configs.pop(self.service_name, None) current_listbox.itemconfig(current_listbox.curselection()[0], bg="") self.destroy() return try: - node_config = self.service_configs.setdefault(self.node_id, {}) - service_config = node_config.setdefault(self.service_name, {}) + service_config = self.canvas_node.config_service_configs.setdefault( + self.service_name, {} + ) if self.config_frame: self.config_frame.parse_config() service_config["config"] = { @@ -365,9 +370,10 @@ class ConfigServiceConfigDialog(Dialog): return has_custom_templates or has_custom_config def click_defaults(self): - if self.node_id in self.service_configs: - node_config = self.service_configs.get(self.node_id, {}) - node_config.pop(self.service_name, None) + self.canvas_node.config_service_configs.pop(self.service_name, None) + logging.info( + "cleared config service config: %s", self.canvas_node.config_service_configs + ) self.temp_service_files = dict(self.original_service_files) filename = self.templates_combobox.get() self.template_text.text.delete(1.0, "end") diff --git a/daemon/core/gui/dialogs/emaneconfig.py b/daemon/core/gui/dialogs/emaneconfig.py index e5f403fe..f200cd6e 100644 --- a/daemon/core/gui/dialogs/emaneconfig.py +++ b/daemon/core/gui/dialogs/emaneconfig.py @@ -8,7 +8,6 @@ from typing import TYPE_CHECKING, Any import grpc -from core.api.grpc import core_pb2 from core.gui.dialogs.dialog import Dialog from core.gui.errors import show_grpc_error from core.gui.images import ImageEnum, Images @@ -56,20 +55,30 @@ class EmaneModelDialog(Dialog): self, master: Any, app: "Application", - node: core_pb2.Node, + canvas_node: "CanvasNode", model: str, interface: int = None, ): - super().__init__(master, app, f"{node.name} {model} Configuration", modal=True) - self.node = node + super().__init__( + master, + app, + f"{canvas_node.core_node.name} {model} Configuration", + modal=True, + ) + self.canvas_node = canvas_node + self.node = canvas_node.core_node self.model = f"emane_{model}" self.interface = interface self.config_frame = None self.has_error = False try: - self.config = self.app.core.get_emane_model_config( - self.node.id, self.model, self.interface + self.config = self.canvas_node.emane_model_configs.get( + (self.model, self.interface) ) + if not self.config: + self.config = self.app.core.get_emane_model_config( + self.node.id, self.model, self.interface + ) self.draw() except grpc.RpcError as e: show_grpc_error(e, self.app, self.app) @@ -98,9 +107,8 @@ class EmaneModelDialog(Dialog): def click_apply(self): self.config_frame.parse_config() - self.app.core.set_emane_model_config( - self.node.id, self.model, self.config, self.interface - ) + key = (self.model, self.interface) + self.canvas_node.emane_model_configs[key] = self.config self.destroy() @@ -224,9 +232,7 @@ class EmaneConfigDialog(Dialog): draw emane model configuration """ model_name = self.emane_model.get() - dialog = EmaneModelDialog( - self, self.app, self.canvas_node.core_node, model_name - ) + dialog = EmaneModelDialog(self, self.app, self.canvas_node, model_name) if not dialog.has_error: dialog.show() diff --git a/daemon/core/gui/dialogs/mobilityconfig.py b/daemon/core/gui/dialogs/mobilityconfig.py index f7192ca4..61cbfc14 100644 --- a/daemon/core/gui/dialogs/mobilityconfig.py +++ b/daemon/core/gui/dialogs/mobilityconfig.py @@ -31,7 +31,9 @@ class MobilityConfigDialog(Dialog): self.config_frame = None self.has_error = False try: - self.config = self.app.core.get_mobility_config(self.node.id) + self.config = self.canvas_node.mobility_config + if not self.config: + self.config = self.app.core.get_mobility_config(self.node.id) self.draw() except grpc.RpcError as e: self.has_error = True @@ -60,5 +62,5 @@ class MobilityConfigDialog(Dialog): def click_apply(self): self.config_frame.parse_config() - self.app.core.mobility_configs[self.node.id] = self.config + self.canvas_node.mobility_config = self.config self.destroy() diff --git a/daemon/core/gui/dialogs/mobilityplayer.py b/daemon/core/gui/dialogs/mobilityplayer.py index b7cbb400..6b7b7869 100644 --- a/daemon/core/gui/dialogs/mobilityplayer.py +++ b/daemon/core/gui/dialogs/mobilityplayer.py @@ -48,8 +48,9 @@ class MobilityPlayer: self.dialog.show() def handle_close(self): - self.dialog.destroy() - self.dialog = None + if self.dialog: + self.dialog.destroy() + self.dialog = None def set_play(self): self.state = MobilityAction.START diff --git a/daemon/core/gui/dialogs/nodeconfig.py b/daemon/core/gui/dialogs/nodeconfig.py index d77c69c4..ce8ea802 100644 --- a/daemon/core/gui/dialogs/nodeconfig.py +++ b/daemon/core/gui/dialogs/nodeconfig.py @@ -290,7 +290,9 @@ class NodeConfigDialog(Dialog): button.grid(row=0, column=1, sticky="ew") def click_emane_config(self, emane_model: str, interface_id: int): - dialog = EmaneModelDialog(self, self.app, self.node, emane_model, interface_id) + dialog = EmaneModelDialog( + self, self.app, self.canvas_node, emane_model, interface_id + ) dialog.show() def click_icon(self): diff --git a/daemon/core/gui/dialogs/nodeconfigservice.py b/daemon/core/gui/dialogs/nodeconfigservice.py index 8bdbc539..c86d8887 100644 --- a/daemon/core/gui/dialogs/nodeconfigservice.py +++ b/daemon/core/gui/dialogs/nodeconfigservice.py @@ -70,12 +70,10 @@ class NodeConfigServiceDialog(Dialog): label_frame.grid(row=0, column=2, sticky="nsew") label_frame.rowconfigure(0, weight=1) label_frame.columnconfigure(0, weight=1) + self.current = ListboxScroll(label_frame) self.current.grid(sticky="nsew") - for service in sorted(self.current_services): - self.current.listbox.insert(tk.END, service) - if self.is_custom_service(service): - self.current.listbox.itemconfig(tk.END, bg="green") + self.draw_current_services() frame = ttk.Frame(self.top) frame.grid(stick="ew") @@ -108,24 +106,22 @@ class NodeConfigServiceDialog(Dialog): self.current_services.add(name) elif not var.get() and name in self.current_services: self.current_services.remove(name) - self.current.listbox.delete(0, tk.END) - for name in sorted(self.current_services): - self.current.listbox.insert(tk.END, name) - if self.is_custom_service(name): - self.current.listbox.itemconfig(tk.END, bg="green") + self.draw_current_services() self.canvas_node.core_node.config_services[:] = self.current_services def click_configure(self): current_selection = self.current.listbox.curselection() if len(current_selection): dialog = ConfigServiceConfigDialog( - master=self, - app=self.app, - service_name=self.current.listbox.get(current_selection[0]), - node_id=self.node_id, + self, + self.app, + self.current.listbox.get(current_selection[0]), + self.canvas_node, + self.node_id, ) if not dialog.has_error: dialog.show() + self.draw_current_services() else: messagebox.showinfo( "Config Service Configuration", @@ -133,6 +129,13 @@ class NodeConfigServiceDialog(Dialog): parent=self, ) + def draw_current_services(self): + self.current.listbox.delete(0, tk.END) + for name in sorted(self.current_services): + self.current.listbox.insert(tk.END, name) + if self.is_custom_service(name): + self.current.listbox.itemconfig(tk.END, bg="green") + def click_save(self): self.canvas_node.core_node.config_services[:] = self.current_services logging.info( @@ -156,9 +159,4 @@ class NodeConfigServiceDialog(Dialog): return def is_custom_service(self, service: str) -> bool: - node_configs = self.app.core.config_service_configs.get(self.node_id, {}) - service_config = node_configs.get(service) - if node_configs and service_config: - return True - else: - return False + return service in self.canvas_node.config_service_configs diff --git a/daemon/core/gui/dialogs/nodeservice.py b/daemon/core/gui/dialogs/nodeservice.py index 691bd331..4927fece 100644 --- a/daemon/core/gui/dialogs/nodeservice.py +++ b/daemon/core/gui/dialogs/nodeservice.py @@ -135,10 +135,11 @@ class NodeServiceDialog(Dialog): current_selection = self.current.listbox.curselection() if len(current_selection): dialog = ServiceConfigDialog( - master=self, - app=self.app, - service_name=self.current.listbox.get(current_selection[0]), - node_id=self.node_id, + self, + self.app, + self.current.listbox.get(current_selection[0]), + self.canvas_node, + self.node_id, ) # if error occurred when creating ServiceConfigDialog, don't show the dialog @@ -182,14 +183,6 @@ class NodeServiceDialog(Dialog): return def is_custom_service(self, service: str) -> bool: - service_configs = self.app.core.service_configs - file_configs = self.app.core.file_configs - if self.node_id in service_configs and service in service_configs[self.node_id]: - return True - if ( - self.node_id in file_configs - and service in file_configs[self.node_id] - and file_configs[self.node_id][service] - ): - return True - return False + has_service_config = service in self.canvas_node.service_configs + has_file_config = service in self.canvas_node.service_file_configs + return has_service_config or has_file_config diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index 94b71787..038564a3 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -16,22 +16,26 @@ from core.gui.widgets import CodeText, ListboxScroll if TYPE_CHECKING: from core.gui.app import Application + from core.gui.graph.node import CanvasNode class ServiceConfigDialog(Dialog): def __init__( - self, master: Any, app: "Application", service_name: str, node_id: int + self, + master: Any, + app: "Application", + service_name: str, + canvas_node: "CanvasNode", + node_id: int, ): title = f"{service_name} Service" super().__init__(master, app, title, modal=True) self.master = master self.app = app self.core = app.core + self.canvas_node = canvas_node self.node_id = node_id self.service_name = service_name - self.service_configs = app.core.service_configs - self.file_configs = app.core.file_configs - self.radiovar = tk.IntVar() self.radiovar.set(2) self.metadata = "" @@ -54,7 +58,6 @@ class ServiceConfigDialog(Dialog): ImageEnum.DOCUMENTNEW, int(16 * app.app_scale) ) self.editdelete_img = Images.get(ImageEnum.EDITDELETE, int(16 * app.app_scale)) - self.notebook = None self.metadata_entry = None self.filename_combobox = None @@ -70,9 +73,7 @@ class ServiceConfigDialog(Dialog): self.default_config = None self.temp_service_files = {} self.modified_files = set() - self.has_error = False - self.load() if not self.has_error: self.draw() @@ -87,8 +88,8 @@ class ServiceConfigDialog(Dialog): self.default_validate = default_config.validate[:] self.default_shutdown = default_config.shutdown[:] self.default_directories = default_config.dirs[:] - custom_service_config = self.service_configs.get(self.node_id, {}).get( - self.service_name, None + custom_service_config = self.canvas_node.service_configs.get( + self.service_name ) self.default_config = default_config service_config = ( @@ -111,10 +112,11 @@ class ServiceConfigDialog(Dialog): for x in default_config.configs } self.temp_service_files = dict(self.original_service_files) - file_config = self.file_configs.get(self.node_id, {}).get( + + file_configs = self.canvas_node.service_file_configs.get( self.service_name, {} ) - for file, data in file_config.items(): + for file, data in file_configs.items(): self.temp_service_files[file] = data except grpc.RpcError as e: self.has_error = True @@ -449,7 +451,7 @@ class ServiceConfigDialog(Dialog): and not self.has_new_files() and not self.is_custom_directory() ): - self.service_configs.get(self.node_id, {}).pop(self.service_name, None) + self.canvas_node.service_configs.pop(self.service_name, None) self.current_service_color("") self.destroy() return @@ -470,17 +472,13 @@ class ServiceConfigDialog(Dialog): validations=validate, shutdowns=shutdown, ) - if self.node_id not in self.service_configs: - self.service_configs[self.node_id] = {} - self.service_configs[self.node_id][self.service_name] = config + self.canvas_node.service_configs[self.service_name] = config for file in self.modified_files: - if self.node_id not in self.file_configs: - self.file_configs[self.node_id] = {} - if self.service_name not in self.file_configs[self.node_id]: - self.file_configs[self.node_id][self.service_name] = {} - self.file_configs[self.node_id][self.service_name][ - file - ] = self.temp_service_files[file] + file_configs = self.canvas_node.service_file_configs.setdefault( + self.service_name, {} + ) + file_configs[file] = self.temp_service_files[file] + # TODO: check if this is really needed self.app.core.set_node_service_file( self.node_id, self.service_name, file, self.temp_service_files[file] ) @@ -526,8 +524,9 @@ class ServiceConfigDialog(Dialog): clears out any custom configuration permanently """ # clear coreclient data - self.service_configs.get(self.node_id, {}).pop(self.service_name, None) - self.file_configs.get(self.node_id, {}).pop(self.service_name, None) + self.canvas_node.service_configs.pop(self.service_name, None) + file_configs = self.canvas_node.service_file_configs.pop(self.service_name, {}) + file_configs.pop(self.service_name, None) self.temp_service_files = dict(self.original_service_files) self.modified_files.clear() diff --git a/daemon/core/gui/dialogs/wlanconfig.py b/daemon/core/gui/dialogs/wlanconfig.py index c0c8c845..a62c28ca 100644 --- a/daemon/core/gui/dialogs/wlanconfig.py +++ b/daemon/core/gui/dialogs/wlanconfig.py @@ -32,7 +32,9 @@ class WlanConfigDialog(Dialog): self.ranges = {} self.positive_int = self.app.master.register(self.validate_and_update) try: - self.config = self.app.core.get_wlan_config(self.node.id) + self.config = self.canvas_node.wlan_config + if not self.config: + self.config = self.app.core.get_wlan_config(self.node.id) self.init_draw_range() self.draw() except grpc.RpcError as e: @@ -83,7 +85,7 @@ class WlanConfigDialog(Dialog): retrieve user's wlan configuration and store the new configuration values """ config = self.config_frame.parse_config() - self.app.core.wlan_configs[self.node.id] = self.config + self.canvas_node.wlan_config = self.config if self.app.core.is_runtime(): session_id = self.app.core.session_id self.app.core.client.set_wlan_config(session_id, self.node.id, config) diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index e7908d35..e4963f45 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -1,5 +1,6 @@ import logging import tkinter as tk +from copy import deepcopy from tkinter import BooleanVar from typing import TYPE_CHECKING, Tuple @@ -304,7 +305,7 @@ class CanvasGraph(tk.Canvas): token = create_edge_token(canvas_node_one.id, canvas_node_two.id) if link.type == core_pb2.LinkType.WIRELESS: - self.add_wireless_edge(canvas_node_one, canvas_node_two) + self.add_wireless_edge(canvas_node_one, canvas_node_two, link) else: if token not in self.edges: src_pos = (node_one.position.x, node_one.position.y) @@ -420,7 +421,7 @@ class CanvasGraph(tk.Canvas): self.mode = GraphMode.NODE self.selected = None - def handle_edge_release(self, event: tk.Event): + def handle_edge_release(self, _event: tk.Event): edge = self.drawing_edge self.drawing_edge = None @@ -701,7 +702,7 @@ class CanvasGraph(tk.Canvas): else: self.hide_context() - def press_delete(self, event: tk.Event): + def press_delete(self, _event: tk.Event): """ delete selected nodes and any data that relates to it """ @@ -894,61 +895,72 @@ class CanvasGraph(tk.Canvas): self.core.create_link(edge, source, dest) def copy(self): - if self.app.core.is_runtime(): + if self.core.is_runtime(): logging.info("copy is disabled during runtime state") return if self.selection: - logging.debug("to copy %s nodes", len(self.selection)) - self.to_copy = self.selection.keys() + logging.info("to copy nodes: %s", self.selection) + self.to_copy.clear() + for node_id in self.selection.keys(): + canvas_node = self.nodes[node_id] + self.to_copy.append(canvas_node) def paste(self): - if self.app.core.is_runtime(): + if self.core.is_runtime(): logging.info("paste is disabled during runtime state") return # maps original node canvas id to copy node canvas id copy_map = {} # the edges that will be copy over to_copy_edges = [] - for canvas_nid in self.to_copy: - core_node = self.nodes[canvas_nid].core_node + for canvas_node in self.to_copy: + core_node = canvas_node.core_node actual_x = core_node.position.x + 50 actual_y = core_node.position.y + 50 scaled_x, scaled_y = self.get_scaled_coords(actual_x, actual_y) - copy = self.core.create_node( actual_x, actual_y, core_node.type, core_node.model ) - node = CanvasNode( - self.master, scaled_x, scaled_y, copy, self.nodes[canvas_nid].image - ) + node = CanvasNode(self.master, scaled_x, scaled_y, copy, canvas_node.image) + + # copy configurations and services + node.core_node.services[:] = canvas_node.core_node.services + node.core_node.config_services[:] = canvas_node.core_node.config_services + node.emane_model_configs = deepcopy(canvas_node.emane_model_configs) + node.wlan_config = deepcopy(canvas_node.wlan_config) + node.mobility_config = deepcopy(canvas_node.mobility_config) + node.service_configs = deepcopy(canvas_node.service_configs) + node.service_file_configs = deepcopy(canvas_node.service_file_configs) + node.config_service_configs = deepcopy(canvas_node.config_service_configs) # add new node to modified_service_nodes set if that set contains the # to_copy node - if self.app.core.service_been_modified(core_node.id): - self.app.core.modified_service_nodes.add(copy.id) + if self.core.service_been_modified(core_node.id): + self.core.modified_service_nodes.add(copy.id) - copy_map[canvas_nid] = node.id + copy_map[canvas_node.id] = node.id self.core.canvas_nodes[copy.id] = node self.nodes[node.id] = node - self.core.copy_node_config(core_node.id, copy.id) - - edges = self.nodes[canvas_nid].edges - for edge in edges: + for edge in canvas_node.edges: if edge.src not in self.to_copy or edge.dst not in self.to_copy: - if canvas_nid == edge.src: - self.create_edge(node, self.nodes[edge.dst]) - elif canvas_nid == edge.dst: - self.create_edge(self.nodes[edge.src], node) + if canvas_node.id == edge.src: + dst_node = self.nodes[edge.dst] + self.create_edge(node, dst_node) + elif canvas_node.id == edge.dst: + src_node = self.nodes[edge.src] + self.create_edge(src_node, node) else: to_copy_edges.append(edge) + # copy link and link config for edge in to_copy_edges: - source_node_copy = self.nodes[copy_map[edge.token[0]]] - dest_node_copy = self.nodes[copy_map[edge.token[1]]] - self.create_edge(source_node_copy, dest_node_copy) - copy_edge = self.edges[ - create_edge_token(source_node_copy.id, dest_node_copy.id) - ] + src_node_id = copy_map[edge.token[0]] + dst_node_id = copy_map[edge.token[1]] + src_node_copy = self.nodes[src_node_id] + dst_node_copy = self.nodes[dst_node_id] + self.create_edge(src_node_copy, dst_node_copy) + token = create_edge_token(src_node_copy.id, dst_node_copy.id) + copy_edge = self.edges[token] copy_link = copy_edge.link options = edge.link.options copy_link.options.CopyFrom(options) diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index c9cc3dd0..90896284 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -58,6 +58,13 @@ class CanvasNode: self.wireless_edges = set() self.antennas = [] self.antenna_images = {} + # possible configurations + self.emane_model_configs = {} + self.wlan_config = {} + self.mobility_config = {} + self.service_configs = {} + self.service_file_configs = {} + self.config_service_configs = {} self.setup_bindings() def setup_bindings(self): @@ -225,12 +232,16 @@ class CanvasNode: context.add_command(label="Select Members", state=tk.DISABLED) edit_menu = tk.Menu(context) themes.style_menu(edit_menu) - edit_menu.add_command(label="Cut", state=tk.DISABLED) + edit_menu.add_command(label="Cut", command=self.click_cut) edit_menu.add_command(label="Copy", command=self.canvas_copy) edit_menu.add_command(label="Delete", command=self.canvas_delete) context.add_cascade(label="Edit", menu=edit_menu) return context + def click_cut(self) -> None: + self.canvas_copy() + self.canvas_delete() + def canvas_delete(self) -> None: self.canvas.clear_selection() self.canvas.selection[self.id] = self diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index 0f016374..b445845a 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -103,14 +103,14 @@ class Menubar(tk.Menu): 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="Cut", accelerator="Ctrl+X", command=self.click_cut) menu.add_command(label="Copy", accelerator="Ctrl+C", command=self.click_copy) menu.add_command(label="Paste", accelerator="Ctrl+V", command=self.click_paste) menu.add_command( label="Delete", accelerator="Ctrl+D", command=self.click_delete ) self.add_cascade(label="Edit", menu=menu) - + self.app.master.bind_all("", self.click_cut) self.app.master.bind_all("", self.click_copy) self.app.master.bind_all("", self.click_paste) self.app.master.bind_all("", self.click_delete) @@ -343,16 +343,16 @@ class Menubar(tk.Menu): self.app.menubar.update_recent_files() def change_menubar_item_state(self, is_runtime: bool) -> None: - for i in range(self.edit_menu.index("end")): + labels = {"Copy", "Paste", "Delete", "Cut"} + for i in range(self.edit_menu.index(tk.END) + 1): try: - label_name = self.edit_menu.entrycget(i, "label") - if label_name in ["Copy", "Paste"]: - if is_runtime: - self.edit_menu.entryconfig(i, state="disabled") - else: - self.edit_menu.entryconfig(i, state="normal") + label = self.edit_menu.entrycget(i, "label") + if label not in labels: + continue + state = tk.DISABLED if is_runtime else tk.NORMAL + self.edit_menu.entryconfig(i, state=state) except tk.TclError: - logging.debug("Ignore separators") + pass def prompt_save_running_session(self, quit_app: bool = False) -> None: """ @@ -410,13 +410,17 @@ class Menubar(tk.Menu): dialog.show() def click_copy(self, _event: tk.Event = None) -> None: - self.app.canvas.copy() + self.canvas.copy() def click_paste(self, _event: tk.Event = None) -> None: - self.app.canvas.paste() + self.canvas.paste() def click_delete(self, _event: tk.Event = None) -> None: - self.app.canvas.delete_selected_objects() + self.canvas.delete_selected_objects() + + def click_cut(self, _event: tk.Event = None) -> None: + self.canvas.copy() + self.canvas.delete_selected_objects() def click_session_options(self) -> None: logging.debug("Click options") @@ -444,14 +448,14 @@ class Menubar(tk.Menu): dialog.show() def click_autogrid(self) -> None: - width, height = self.app.canvas.current_dimensions + width, height = self.canvas.current_dimensions padding = (ICON_SIZE / 2) + 10 layout_size = padding + ICON_SIZE col_count = width // layout_size logging.info( - "auto grid layout: dimens(%s, %s) col(%s)", width, height, col_count + "auto grid layout: dimension(%s, %s) col(%s)", width, height, col_count ) - for i, node in enumerate(self.app.canvas.nodes.values()): + for i, node in enumerate(self.canvas.nodes.values()): col = i % col_count row = i // col_count x = (col * layout_size) + padding diff --git a/daemon/core/gui/widgets.py b/daemon/core/gui/widgets.py index 9c07e8c7..bf54bba9 100644 --- a/daemon/core/gui/widgets.py +++ b/daemon/core/gui/widgets.py @@ -181,7 +181,6 @@ class ConfigFrame(ttk.Notebook): option.value = "0" else: option.value = config_value - return {x: self.config[x].value for x in self.config} def set_values(self, config: Dict[str, str]) -> None: