From ddaba7c477045cc47a690cede56da07e2cd9fd24 Mon Sep 17 00:00:00 2001 From: Huy Pham <42948410+hpham@users.noreply.github.com> Date: Mon, 24 Feb 2020 10:58:01 -0800 Subject: [PATCH 1/8] remove code for deleting wireless links and nodes during runtime --- daemon/core/gui/graph/node.py | 43 ++--------------------------------- daemon/core/gui/nodeutils.py | 4 ---- 2 files changed, 2 insertions(+), 45 deletions(-) diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index 7b4ccf31..3ed5b1d9 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -17,7 +17,7 @@ from core.gui.errors import show_grpc_error from core.gui.graph import tags from core.gui.graph.tooltip import CanvasTooltip from core.gui.images import ImageEnum, Images -from core.gui.nodeutils import ANTENNA_SIZE, EdgeUtils, NodeUtils +from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils if TYPE_CHECKING: from core.gui.app import Application @@ -66,48 +66,9 @@ class CanvasNode: def delete(self): logging.debug("Delete canvas node for %s", self.core_node) - - # if node is wlan, EMANE type, remove any existing wireless links between nodes connetect to this node - if NodeUtils.is_wireless_node(self.core_node.type): - nodes = [] - for edge in self.edges: - token = edge.token - if self.id == token[0]: - nodes.append(token[1]) - else: - nodes.append(token[0]) - for i in range(len(nodes)): - for j in range(i + 1, len(nodes)): - token = EdgeUtils.get_token(nodes[i], nodes[j]) - wireless_edge = self.canvas.wireless_edges.pop(token, None) - if wireless_edge: - - self.canvas.nodes[nodes[i]].wireless_edges.remove(wireless_edge) - self.canvas.nodes[nodes[j]].wireless_edges.remove(wireless_edge) - self.canvas.delete(wireless_edge.id) - else: - logging.debug("%s is not a wireless edge", token) - # if node is MDR, remove wireless links to other MDRs - elif NodeUtils.is_mdr_node(self.core_node.type, self.core_node.model): - for wireless_edge in self.wireless_edges: - token = wireless_edge.token - other = token[0] - if other == self.id: - other = token[1] - self.canvas.nodes[other].wireless_edges.discard(wireless_edge) - try: - wlan_edge = self.canvas.wireless_edges.pop(token) - self.canvas.delete(wlan_edge.id) - except KeyError: - logging.error( - "wireless link not found, potentially multiple wireless link issue" - ) - self.delete_antennas() - - self.wireless_edges.clear() - self.canvas.delete(self.id) self.canvas.delete(self.text_id) + self.delete_antennas() def add_antenna(self): x, y = self.canvas.coords(self.id) diff --git a/daemon/core/gui/nodeutils.py b/daemon/core/gui/nodeutils.py index 870ac8bf..81aa2cba 100644 --- a/daemon/core/gui/nodeutils.py +++ b/daemon/core/gui/nodeutils.py @@ -90,10 +90,6 @@ class NodeUtils: def is_rj45_node(cls, node_type: NodeType) -> bool: return node_type in cls.RJ45_NODES - @classmethod - def is_mdr_node(cls, node_type: NodeType, model: str) -> bool: - return cls.is_container_node(node_type) and model == "mdr" - @classmethod def node_icon( cls, From 1dca477e6df5b1af996c2e73e51e1e51ba5ab615 Mon Sep 17 00:00:00 2001 From: Huy Pham <42948410+hpham@users.noreply.github.com> Date: Mon, 24 Feb 2020 11:17:06 -0800 Subject: [PATCH 2/8] disable delete, copy, paste during runtime --- daemon/core/gui/coreclient.py | 3 +++ daemon/core/gui/graph/graph.py | 9 ++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 7d8e832c..89ce7ba0 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -1064,3 +1064,6 @@ class CoreClient: def service_been_modified(self, node_id: int) -> bool: return node_id in self.modified_service_nodes + + def is_runtime_state(self): + return self.state == core_pb2.SessionState.RUNTIME diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 7905ed8c..d07039d4 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -656,8 +656,11 @@ class CanvasGraph(tk.Canvas): delete selected nodes and any data that relates to it """ logging.debug("press delete key") - nodes = self.delete_selection_objects() - self.core.delete_graph_nodes(nodes) + if not self.app.core.is_runtime_state(): + nodes = self.delete_selection_objects() + self.core.delete_graph_nodes(nodes) + else: + logging.debug("node deletion is disabled during runtime") def double_click(self, event: tk.Event): selected = self.get_selected(event) @@ -850,7 +853,7 @@ class CanvasGraph(tk.Canvas): self.core.create_link(edge, source, dest) def copy(self): - if self.selection: + if self.selection and not self.app.core.is_runtime_state(): logging.debug("to copy %s nodes", len(self.selection)) self.to_copy = self.selection.keys() From 8a0257d130c6e393aeca8c215bb68e1500c86b8d Mon Sep 17 00:00:00 2001 From: Huy Pham <42948410+hpham@users.noreply.github.com> Date: Mon, 24 Feb 2020 12:51:47 -0800 Subject: [PATCH 3/8] disable copy/paste/delete shortcuts as well as commands during runtime state --- daemon/core/gui/coreclient.py | 3 --- daemon/core/gui/graph/graph.py | 12 +++++++++--- daemon/core/gui/menubar.py | 14 ++++++++++++++ daemon/core/gui/toolbar.py | 2 ++ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 89ce7ba0..7d8e832c 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -1064,6 +1064,3 @@ class CoreClient: def service_been_modified(self, node_id: int) -> bool: return node_id in self.modified_service_nodes - - def is_runtime_state(self): - return self.state == core_pb2.SessionState.RUNTIME diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index d07039d4..5652fa40 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -656,11 +656,11 @@ class CanvasGraph(tk.Canvas): delete selected nodes and any data that relates to it """ logging.debug("press delete key") - if not self.app.core.is_runtime_state(): + if not self.app.core.is_runtime(): nodes = self.delete_selection_objects() self.core.delete_graph_nodes(nodes) else: - logging.debug("node deletion is disabled during runtime") + logging.info("node deletion is disabled during runtime state") def double_click(self, event: tk.Event): selected = self.get_selected(event) @@ -853,11 +853,17 @@ class CanvasGraph(tk.Canvas): self.core.create_link(edge, source, dest) def copy(self): - if self.selection and not self.app.core.is_runtime_state(): + if self.app.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() def paste(self): + if self.app.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 diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index 935e0b92..f4c12014 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -25,6 +25,7 @@ class Menubar(tk.Menu): self.app = app self.menuaction = action.MenuAction(app, master) self.recent_menu = None + self.edit_menu = None self.draw() def draw(self): @@ -110,6 +111,7 @@ class Menubar(tk.Menu): self.app.master.bind_all("", self.menuaction.copy) self.app.master.bind_all("", self.menuaction.paste) + self.edit_menu = menu def draw_canvas_menu(self): """ @@ -439,3 +441,15 @@ class Menubar(tk.Menu): self.app.core.save_xml(xml_file) else: self.menuaction.file_save_as_xml() + + def change_menubar_item_state(self, is_runtime: bool): + for i in range(self.edit_menu.index("end")): + 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") + except tk.TclError: + logging.debug("Ignore separators") diff --git a/daemon/core/gui/toolbar.py b/daemon/core/gui/toolbar.py index eff37257..3b4828d0 100644 --- a/daemon/core/gui/toolbar.py +++ b/daemon/core/gui/toolbar.py @@ -280,6 +280,7 @@ class Toolbar(ttk.Frame): server. """ self.app.canvas.hide_context() + self.app.menubar.change_menubar_item_state(is_runtime=True) self.app.statusbar.progress_bar.start(5) self.app.canvas.mode = GraphMode.SELECT self.time = time.perf_counter() @@ -469,6 +470,7 @@ class Toolbar(ttk.Frame): """ logging.info("Click stop button") self.app.canvas.hide_context() + self.app.menubar.change_menubar_item_state(is_runtime=False) self.app.statusbar.progress_bar.start(5) self.time = time.perf_counter() task = BackgroundTask(self, self.app.core.stop_session, self.stop_callback) From 014707580f9e8b08fbb3f58e756098a39b2f7859 Mon Sep 17 00:00:00 2001 From: Huy Pham <42948410+hpham@users.noreply.github.com> Date: Tue, 25 Feb 2020 11:38:58 -0800 Subject: [PATCH 4/8] allow custom service file to be created --- daemon/core/gui/coreclient.py | 6 ++- daemon/core/gui/dialogs/serviceconfig.py | 47 +++++++++++++++--------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 7d8e832c..10e0820d 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -521,7 +521,6 @@ class CoreClient: logging.info( "start session(%s), result: %s", self.session_id, response.result ) - if response.result: self.set_metadata() except grpc.RpcError as e: @@ -620,6 +619,7 @@ class CoreClient: self, node_id: int, service_name: str, + files: List[str], startups: List[str], validations: List[str], shutdowns: List[str], @@ -628,14 +628,16 @@ class CoreClient: self.session_id, node_id, service_name, + files=files, startup=startups, validate=validations, shutdown=shutdowns, ) logging.info( - "Set %s service for node(%s), Startup: %s, Validation: %s, Shutdown: %s, Result: %s", + "Set %s service for node(%s), files: %s, Startup: %s, Validation: %s, Shutdown: %s, Result: %s", service_name, node_id, + files, startups, validations, shutdowns, diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index b528e32a..5a2fede3 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -1,6 +1,7 @@ """ Service configuration dialog """ +import logging import tkinter as tk from tkinter import ttk from typing import TYPE_CHECKING, Any, List @@ -155,15 +156,15 @@ class ServiceConfigDialog(Dialog): frame.columnconfigure(1, weight=1) label = ttk.Label(frame, text="File Name") label.grid(row=0, column=0, padx=PADX, sticky="w") - self.filename_combobox = ttk.Combobox( - frame, values=self.filenames, state="readonly" - ) + self.filename_combobox = ttk.Combobox(frame, values=self.filenames) self.filename_combobox.bind( "<>", self.display_service_file_data ) self.filename_combobox.grid(row=0, column=1, sticky="ew", padx=PADX) - button = ttk.Button(frame, image=self.documentnew_img, state="disabled") - button.bind("", self.add_filename) + button = ttk.Button( + frame, image=self.documentnew_img, command=self.add_filename + ) + # button.bind("", self.add_filename) button.grid(row=0, column=2, padx=PADX) button = ttk.Button(frame, image=self.editdelete_img, state="disabled") button.bind("", self.delete_filename) @@ -358,14 +359,16 @@ class ServiceConfigDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=3, sticky="ew") - def add_filename(self, event: tk.Event): - # not worry about it for now - return - frame_contains_button = event.widget.master - combobox = frame_contains_button.grid_slaves(row=0, column=1)[0] - filename = combobox.get() - if filename not in combobox["values"]: - combobox["values"] += (filename,) + def add_filename(self): + filename = self.filename_combobox.get() + if filename not in self.filename_combobox["values"]: + self.filename_combobox["values"] += (filename,) + self.filename_combobox.set(filename) + self.temp_service_files[filename] = self.service_file_data.text.get( + 1.0, "end" + ) + else: + logging.debug("file already existed") def delete_filename(self, event: tk.Event): # not worry about it for now @@ -411,7 +414,11 @@ class ServiceConfigDialog(Dialog): def click_apply(self): current_listbox = self.master.current.listbox - if not self.is_custom_service_config() and not self.is_custom_service_file(): + if ( + not self.is_custom_service_config() + and not self.is_custom_service_file() + and not self.has_new_files() + ): if self.node_id in self.service_configs: self.service_configs[self.node_id].pop(self.service_name, None) current_listbox.itemconfig(current_listbox.curselection()[0], bg="") @@ -419,13 +426,14 @@ class ServiceConfigDialog(Dialog): return try: - if self.is_custom_service_config(): + if self.is_custom_service_config() or self.has_new_files(): startup_commands = self.startup_commands_listbox.get(0, "end") shutdown_commands = self.shutdown_commands_listbox.get(0, "end") validate_commands = self.validate_commands_listbox.get(0, "end") config = self.core.set_node_service( self.node_id, self.service_name, + files=list(self.filename_combobox["values"]), startups=startup_commands, validations=validate_commands, shutdowns=shutdown_commands, @@ -433,7 +441,6 @@ class ServiceConfigDialog(Dialog): 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 - for file in self.modified_files: if self.node_id not in self.file_configs: self.file_configs[self.node_id] = {} @@ -442,7 +449,6 @@ class ServiceConfigDialog(Dialog): self.file_configs[self.node_id][self.service_name][ file ] = self.temp_service_files[file] - self.app.core.set_node_service_file( self.node_id, self.service_name, file, self.temp_service_files[file] ) @@ -462,7 +468,9 @@ class ServiceConfigDialog(Dialog): scrolledtext = event.widget filename = self.filename_combobox.get() self.temp_service_files[filename] = scrolledtext.get(1.0, "end") - if self.temp_service_files[filename] != self.original_service_files[filename]: + if self.temp_service_files[filename] != self.original_service_files.get( + filename, "" + ): self.modified_files.add(filename) else: self.modified_files.discard(filename) @@ -477,6 +485,9 @@ class ServiceConfigDialog(Dialog): or set(self.default_shutdown) != set(shutdown_commands) ) + def has_new_files(self): + return set(self.filenames) != set(self.filename_combobox["values"]) + def is_custom_service_file(self): return len(self.modified_files) > 0 From 696fda00ea85fc3416bf2315faf9009aec927975 Mon Sep 17 00:00:00 2001 From: Huy Pham <42948410+hpham@users.noreply.github.com> Date: Wed, 26 Feb 2020 08:31:28 -0800 Subject: [PATCH 5/8] add/delete custom service file to node --- daemon/core/gui/coreclient.py | 1 + daemon/core/gui/dialogs/serviceconfig.py | 73 +++++++++++++----------- 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 10e0820d..bd4e4aad 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -935,6 +935,7 @@ class CoreClient: config_proto = core_pb2.ServiceConfig( node_id=node_id, service=name, + files=config.configs, startup=config.startup, validate=config.validate, shutdown=config.shutdown, diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index 5a2fede3..af5a9a37 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -49,8 +49,8 @@ class ServiceConfigDialog(Dialog): self.validation_mode = None self.validation_time = None self.validation_period = None - self.documentnew_img = Images.get(ImageEnum.DOCUMENTNEW, 16) - self.editdelete_img = Images.get(ImageEnum.EDITDELETE, 16) + self.documentnew_img = Images.get(ImageEnum.DOCUMENTNEW, 16 * app.app_scale) + self.editdelete_img = Images.get(ImageEnum.EDITDELETE, 16 * app.app_scale) self.notebook = None self.metadata_entry = None @@ -103,7 +103,7 @@ class ServiceConfigDialog(Dialog): x: self.app.core.get_node_service_file( self.node_id, self.service_name, x ) - for x in self.filenames + for x in default_config.configs } self.temp_service_files = dict(self.original_service_files) file_configs = self.file_configs @@ -164,10 +164,11 @@ class ServiceConfigDialog(Dialog): button = ttk.Button( frame, image=self.documentnew_img, command=self.add_filename ) - # button.bind("", self.add_filename) button.grid(row=0, column=2, padx=PADX) - button = ttk.Button(frame, image=self.editdelete_img, state="disabled") - button.bind("", self.delete_filename) + button = ttk.Button( + frame, image=self.editdelete_img, command=self.delete_filename + ) + # button.bind("", self.delete_filename) button.grid(row=0, column=3) frame = ttk.Frame(tab) @@ -370,17 +371,19 @@ class ServiceConfigDialog(Dialog): else: logging.debug("file already existed") - def delete_filename(self, event: tk.Event): - # not worry about it for now - return - frame_comntains_button = event.widget.master - combobox = frame_comntains_button.grid_slaves(row=0, column=1)[0] - filename = combobox.get() - if filename in combobox["values"]: - combobox["values"] = tuple([x for x in combobox["values"] if x != filename]) - combobox.set("") + def delete_filename(self): + cbb = self.filename_combobox + filename = cbb.get() + if filename in cbb["values"]: + cbb["values"] = tuple([x for x in cbb["values"] if x != filename]) + cbb.set("") + self.service_file_data.text.delete(1.0, "end") + self.temp_service_files.pop(filename, None) + if filename in self.modified_files: + self.modified_files.remove(filename) - def add_command(self, event: tk.Event): + @classmethod + def add_command(cls, event: tk.Event): frame_contains_button = event.widget.master listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox command_to_add = frame_contains_button.grid_slaves(row=0, column=0)[0].get() @@ -391,7 +394,8 @@ class ServiceConfigDialog(Dialog): return listbox.insert(tk.END, command_to_add) - def update_entry(self, event: tk.Event): + @classmethod + def update_entry(cls, event: tk.Event): listbox = event.widget current_selection = listbox.curselection() if len(current_selection) > 0: @@ -402,7 +406,8 @@ class ServiceConfigDialog(Dialog): entry.delete(0, "end") entry.insert(0, cmd) - def delete_command(self, event: tk.Event): + @classmethod + def delete_command(cls, event: tk.Event): button = event.widget frame_contains_button = button.master listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox @@ -415,7 +420,7 @@ class ServiceConfigDialog(Dialog): def click_apply(self): current_listbox = self.master.current.listbox if ( - not self.is_custom_service_config() + not self.is_custom_command() and not self.is_custom_service_file() and not self.has_new_files() ): @@ -426,17 +431,15 @@ class ServiceConfigDialog(Dialog): return try: - if self.is_custom_service_config() or self.has_new_files(): - startup_commands = self.startup_commands_listbox.get(0, "end") - shutdown_commands = self.shutdown_commands_listbox.get(0, "end") - validate_commands = self.validate_commands_listbox.get(0, "end") + if self.is_custom_command() or self.has_new_files(): + startup, validate, shutdown = self.get_commands() config = self.core.set_node_service( self.node_id, self.service_name, files=list(self.filename_combobox["values"]), - startups=startup_commands, - validations=validate_commands, - shutdowns=shutdown_commands, + startups=startup, + validations=validate, + shutdowns=shutdown, ) if self.node_id not in self.service_configs: self.service_configs[self.node_id] = {} @@ -475,14 +478,12 @@ class ServiceConfigDialog(Dialog): else: self.modified_files.discard(filename) - def is_custom_service_config(self): - startup_commands = self.startup_commands_listbox.get(0, "end") - shutdown_commands = self.shutdown_commands_listbox.get(0, "end") - validate_commands = self.validate_commands_listbox.get(0, "end") + def is_custom_command(self): + startup, validate, shutdown = self.get_commands() return ( - set(self.default_startup) != set(startup_commands) - or set(self.default_validate) != set(validate_commands) - or set(self.default_shutdown) != set(shutdown_commands) + set(self.default_startup) != set(startup) + or set(self.default_validate) != set(validate) + or set(self.default_shutdown) != set(shutdown) ) def has_new_files(self): @@ -520,3 +521,9 @@ class ServiceConfigDialog(Dialog): for cmd in to_add: commands.append(cmd) listbox.insert(tk.END, cmd) + + def get_commands(self): + startup = self.startup_commands_listbox.get(0, "end") + shutdown = self.shutdown_commands_listbox.get(0, "end") + validate = self.validate_commands_listbox.get(0, "end") + return startup, validate, shutdown From 764a61e89e8a61bc9017b29086eb5fc054f4dcb1 Mon Sep 17 00:00:00 2001 From: Huy Pham <42948410+hpham@users.noreply.github.com> Date: Wed, 26 Feb 2020 10:43:01 -0800 Subject: [PATCH 6/8] create layout for service config - directory tab --- daemon/core/gui/dialogs/serviceconfig.py | 71 ++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index af5a9a37..7f0f12e1 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -2,8 +2,9 @@ Service configuration dialog """ import logging +import os import tkinter as tk -from tkinter import ttk +from tkinter import filedialog, ttk from typing import TYPE_CHECKING, Any, List import grpc @@ -49,12 +50,18 @@ class ServiceConfigDialog(Dialog): self.validation_mode = None self.validation_time = None self.validation_period = None - self.documentnew_img = Images.get(ImageEnum.DOCUMENTNEW, 16 * app.app_scale) - self.editdelete_img = Images.get(ImageEnum.EDITDELETE, 16 * app.app_scale) + self.directory_entry = None + self.default_directories = [] + self.temp_directories = [] + self.documentnew_img = Images.get( + 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 + self.dir_list = None self.startup_commands_listbox = None self.shutdown_commands_listbox = None self.validate_commands_listbox = None @@ -81,6 +88,7 @@ class ServiceConfigDialog(Dialog): self.default_startup = default_config.startup[:] self.default_validate = default_config.validate[:] self.default_shutdown = default_config.shutdown[:] + self.default_directories = default_config.dirs[:] custom_configs = self.service_configs if ( self.node_id in custom_configs @@ -99,6 +107,7 @@ class ServiceConfigDialog(Dialog): self.shutdown_commands = service_config.shutdown[:] self.validation_mode = service_config.validation_mode self.validation_time = service_config.validation_timer + self.temp_directories = service_config.dirs[:] self.original_service_files = { x: self.app.core.get_node_service_file( self.node_id, self.service_name, x @@ -231,7 +240,30 @@ class ServiceConfigDialog(Dialog): tab, text="Directories required by this service that are unique for each node.", ) - label.grid() + label.grid(row=0, column=0, sticky="ew") + frame = ttk.Frame(tab, padding=FRAME_PAD) + frame.columnconfigure(0, weight=1) + frame.columnconfigure(1, weight=1) + frame.grid(row=1, column=0, sticky="nsew") + var = tk.StringVar(value="") + self.directory_entry = ttk.Entry(frame, textvariable=var) + self.directory_entry.grid(row=0, column=0, sticky="ew") + button = ttk.Button(frame, text="...", command=self.find_directory_button) + button.grid(row=0, column=1, sticky="ew") + self.dir_list = ListboxScroll(tab) + self.dir_list.grid(row=2, column=0, sticky="nsew") + self.dir_list.listbox.bind("<>", self.directory_select) + for d in self.temp_directories: + self.dir_list.listbox.insert("end", d) + + frame = ttk.Frame(tab) + frame.grid(row=3, column=0, sticky="nsew") + frame.columnconfigure(0, weight=1) + frame.columnconfigure(1, weight=1) + button = ttk.Button(frame, text="Add", command=self.add_directory) + button.grid(row=0, column=0, sticky="ew") + button = ttk.Button(frame, text="Remove", command=self.remove_directory) + button.grid(row=0, column=1, sticky="ew") def draw_tab_startstop(self): tab = ttk.Frame(self.notebook, padding=FRAME_PAD) @@ -527,3 +559,34 @@ class ServiceConfigDialog(Dialog): shutdown = self.shutdown_commands_listbox.get(0, "end") validate = self.validate_commands_listbox.get(0, "end") return startup, validate, shutdown + + def find_directory_button(self): + d = filedialog.askdirectory(initialdir="/") + self.directory_entry.delete(0, "end") + self.directory_entry.insert("end", d) + + def add_directory(self): + d = self.directory_entry.get() + if os.path.isdir(d): + if d not in self.temp_directories: + self.dir_list.listbox.insert("end", d) + self.temp_directories.append(d) + + def remove_directory(self): + d = self.directory_entry.get() + dirs = self.dir_list.listbox.get(0, "end") + if d and d in self.temp_directories: + self.temp_directories.remove(d) + try: + i = dirs.index(d) + self.dir_list.listbox.delete(i) + except ValueError: + logging.debug("directory is not in the list") + self.directory_entry.delete(0, "end") + + def directory_select(self, event): + i = self.dir_list.listbox.curselection() + if i: + d = self.dir_list.listbox.get(i) + self.directory_entry.delete(0, "end") + self.directory_entry.insert("end", d) From c36f060d4499591f7580b304d0308fed84856dc4 Mon Sep 17 00:00:00 2001 From: Huy Pham <42948410+hpham@users.noreply.github.com> Date: Wed, 26 Feb 2020 15:43:31 -0800 Subject: [PATCH 7/8] fixed wrong variable used for configuring service in grpcutils, add/delete directories for node's service configuration, clean up some old code --- daemon/core/api/grpc/grpcutils.py | 2 +- daemon/core/gui/coreclient.py | 4 +- daemon/core/gui/dialogs/serviceconfig.py | 56 ++++++++++++------------ 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 94cfce56..43f20442 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -379,7 +379,7 @@ def service_configuration(session: Session, config: core_pb2.ServiceConfig) -> N if config.files: service.configs = tuple(config.files) if config.directories: - service.directories = tuple(config.directories) + service.dirs = tuple(config.directories) if config.startup: service.startup = tuple(config.startup) if config.validate: diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index bd4e4aad..e20f1506 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -500,7 +500,6 @@ class CoreClient: emane_config = {x: self.emane_config[x].value for x in self.emane_config} else: emane_config = None - response = core_pb2.StartSessionResponse(result=False) try: response = self.client.start_session( @@ -619,6 +618,7 @@ class CoreClient: self, node_id: int, service_name: str, + dirs: List[str], files: List[str], startups: List[str], validations: List[str], @@ -628,6 +628,7 @@ class CoreClient: self.session_id, node_id, service_name, + directories=dirs, files=files, startup=startups, validate=validations, @@ -935,6 +936,7 @@ class CoreClient: config_proto = core_pb2.ServiceConfig( node_id=node_id, service=name, + directories=config.dirs, files=config.configs, startup=config.startup, validate=config.validate, diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index 7f0f12e1..c95e8cd7 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -1,6 +1,3 @@ -""" -Service configuration dialog -""" import logging import os import tkinter as tk @@ -79,7 +76,7 @@ class ServiceConfigDialog(Dialog): if not self.has_error: self.draw() - def load(self) -> bool: + def load(self): try: self.app.core.create_nodes_and_links() default_config = self.app.core.get_node_service( @@ -89,15 +86,12 @@ class ServiceConfigDialog(Dialog): self.default_validate = default_config.validate[:] self.default_shutdown = default_config.shutdown[:] self.default_directories = default_config.dirs[:] - custom_configs = self.service_configs - if ( - self.node_id in custom_configs - and self.service_name in custom_configs[self.node_id] - ): - service_config = custom_configs[self.node_id][self.service_name] - else: - service_config = default_config - + custom_service_config = self.service_configs.get(self.node_id, {}).get( + self.service_name, None + ) + service_config = ( + custom_service_config if custom_service_config else default_config + ) self.dependencies = service_config.dependencies[:] self.executables = service_config.executables[:] self.metadata = service_config.meta @@ -115,13 +109,11 @@ class ServiceConfigDialog(Dialog): for x in default_config.configs } self.temp_service_files = dict(self.original_service_files) - file_configs = self.file_configs - if ( - self.node_id in file_configs - and self.service_name in file_configs[self.node_id] - ): - for file, data in file_configs[self.node_id][self.service_name].items(): - self.temp_service_files[file] = data + file_config = self.file_configs.get(self.node_id, {}).get( + self.service_name, {} + ) + for file, data in file_config.items(): + self.temp_service_files[file] = data except grpc.RpcError as e: self.has_error = True show_grpc_error(e, self.master, self.app) @@ -451,23 +443,30 @@ class ServiceConfigDialog(Dialog): def click_apply(self): current_listbox = self.master.current.listbox + all_current = current_listbox.get(0, tk.END) if ( not self.is_custom_command() and not self.is_custom_service_file() and not self.has_new_files() + and not self.is_custom_directory() ): if self.node_id in self.service_configs: self.service_configs[self.node_id].pop(self.service_name, None) - current_listbox.itemconfig(current_listbox.curselection()[0], bg="") + current_listbox.itemconfig(all_current.index(self.service_name), bg="") self.destroy() return try: - if self.is_custom_command() or self.has_new_files(): + if ( + self.is_custom_command() + or self.has_new_files() + or self.is_custom_directory() + ): startup, validate, shutdown = self.get_commands() config = self.core.set_node_service( self.node_id, self.service_name, + dirs=self.temp_directories, files=list(self.filename_combobox["values"]), startups=startup, validations=validate, @@ -487,22 +486,19 @@ class ServiceConfigDialog(Dialog): self.app.core.set_node_service_file( self.node_id, self.service_name, file, self.temp_service_files[file] ) - all_current = current_listbox.get(0, tk.END) current_listbox.itemconfig(all_current.index(self.service_name), bg="green") except grpc.RpcError as e: show_grpc_error(e, self.top, self.app) self.destroy() def display_service_file_data(self, event: tk.Event): - combobox = event.widget - filename = combobox.get() + filename = self.filename_combobox.get() self.service_file_data.text.delete(1.0, "end") self.service_file_data.text.insert("end", self.temp_service_files[filename]) def update_temp_service_file_data(self, event: tk.Event): - scrolledtext = event.widget filename = self.filename_combobox.get() - self.temp_service_files[filename] = scrolledtext.get(1.0, "end") + self.temp_service_files[filename] = self.service_file_data.text.get(1.0, "end") if self.temp_service_files[filename] != self.original_service_files.get( filename, "" ): @@ -524,6 +520,9 @@ class ServiceConfigDialog(Dialog): def is_custom_service_file(self): return len(self.modified_files) > 0 + def is_custom_directory(self): + return set(self.default_directories) != set(self.dir_list.listbox.get(0, "end")) + def click_defaults(self): if self.node_id in self.service_configs: self.service_configs[self.node_id].pop(self.service_name, None) @@ -547,8 +546,9 @@ class ServiceConfigDialog(Dialog): dialog = CopyServiceConfigDialog(self, self.app, self.node_id) dialog.show() + @classmethod def append_commands( - self, commands: List[str], listbox: tk.Listbox, to_add: List[str] + cls, commands: List[str], listbox: tk.Listbox, to_add: List[str] ): for cmd in to_add: commands.append(cmd) From 1cba11d9e05de40636eda5fefb65fbfc26334520 Mon Sep 17 00:00:00 2001 From: Huy Pham <42948410+hpham@users.noreply.github.com> Date: Thu, 27 Feb 2020 10:57:22 -0800 Subject: [PATCH 8/8] clean up more code, click defaults in service configuration correctly reset files tab as well as directories tab --- daemon/core/gui/dialogs/serviceconfig.py | 53 ++++++++++++++++++------ 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index c95e8cd7..e610cf94 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -67,6 +67,7 @@ class ServiceConfigDialog(Dialog): self.service_file_data = None self.validation_period_entry = None self.original_service_files = {} + self.default_config = None self.temp_service_files = {} self.modified_files = set() @@ -89,6 +90,7 @@ class ServiceConfigDialog(Dialog): custom_service_config = self.service_configs.get(self.node_id, {}).get( self.service_name, None ) + self.default_config = default_config service_config = ( custom_service_config if custom_service_config else default_config ) @@ -169,7 +171,6 @@ class ServiceConfigDialog(Dialog): button = ttk.Button( frame, image=self.editdelete_img, command=self.delete_filename ) - # button.bind("", self.delete_filename) button.grid(row=0, column=3) frame = ttk.Frame(tab) @@ -442,17 +443,14 @@ class ServiceConfigDialog(Dialog): entry.delete(0, tk.END) def click_apply(self): - current_listbox = self.master.current.listbox - all_current = current_listbox.get(0, tk.END) if ( not self.is_custom_command() and not self.is_custom_service_file() and not self.has_new_files() and not self.is_custom_directory() ): - if self.node_id in self.service_configs: - self.service_configs[self.node_id].pop(self.service_name, None) - current_listbox.itemconfig(all_current.index(self.service_name), bg="") + self.service_configs.get(self.node_id, {}).pop(self.service_name, None) + self.current_service_color("") self.destroy() return @@ -486,7 +484,7 @@ class ServiceConfigDialog(Dialog): self.app.core.set_node_service_file( self.node_id, self.service_name, file, self.temp_service_files[file] ) - current_listbox.itemconfig(all_current.index(self.service_name), bg="green") + self.current_service_color("green") except grpc.RpcError as e: show_grpc_error(e, self.top, self.app) self.destroy() @@ -524,14 +522,26 @@ class ServiceConfigDialog(Dialog): return set(self.default_directories) != set(self.dir_list.listbox.get(0, "end")) def click_defaults(self): - if self.node_id in self.service_configs: - self.service_configs[self.node_id].pop(self.service_name, None) - if self.node_id in self.file_configs: - self.file_configs[self.node_id].pop(self.service_name, None) + """ + 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.temp_service_files = dict(self.original_service_files) - filename = self.filename_combobox.get() + self.modified_files.clear() + + # reset files tab + files = list(self.default_config.configs[:]) + self.filenames = files + self.filename_combobox.config(values=files) self.service_file_data.text.delete(1.0, "end") - self.service_file_data.text.insert("end", self.temp_service_files[filename]) + if len(files) > 0: + filename = files[0] + self.filename_combobox.set(filename) + self.service_file_data.text.insert("end", self.temp_service_files[filename]) + + # reset commands self.startup_commands_listbox.delete(0, tk.END) self.validate_commands_listbox.delete(0, tk.END) self.shutdown_commands_listbox.delete(0, tk.END) @@ -542,6 +552,15 @@ class ServiceConfigDialog(Dialog): for cmd in self.default_shutdown: self.shutdown_commands_listbox.insert(tk.END, cmd) + # reset directories + self.directory_entry.delete(0, "end") + self.dir_list.listbox.delete(0, "end") + self.temp_directories = list(self.default_directories) + for d in self.default_directories: + self.dir_list.listbox.insert("end", d) + + self.current_service_color("") + def click_copy(self): dialog = CopyServiceConfigDialog(self, self.app, self.node_id) dialog.show() @@ -590,3 +609,11 @@ class ServiceConfigDialog(Dialog): d = self.dir_list.listbox.get(i) self.directory_entry.delete(0, "end") self.directory_entry.insert("end", d) + + def current_service_color(self, color=""): + """ + change the current service label color + """ + listbox = self.master.current.listbox + services = listbox.get(0, tk.END) + listbox.itemconfig(services.index(self.service_name), bg=color)