diff --git a/coretk/coretk/coreclient.py b/coretk/coretk/coreclient.py index d28ad49e..bfe5acc9 100644 --- a/coretk/coretk/coreclient.py +++ b/coretk/coretk/coreclient.py @@ -11,6 +11,7 @@ from coretk.emaneodelnodeconfig import EmaneModelNodeConfig from coretk.images import NODE_WIDTH, Images from coretk.interface import Interface, InterfaceManager from coretk.mobilitynodeconfig import MobilityNodeConfig +from coretk.servicenodeconfig import ServiceNodeConfig from coretk.wlannodeconfig import WlanNodeConfig NETWORK_NODES = {"switch", "hub", "wlan", "rj45", "tunnel", "emane"} @@ -124,6 +125,7 @@ class CoreClient: self.mobilityconfig_management = MobilityNodeConfig() self.emaneconfig_management = EmaneModelNodeConfig(app) self.emane_config = None + self.serviceconfig_manager = ServiceNodeConfig(app) def set_observer(self, value): self.observer = value @@ -359,6 +361,13 @@ class CoreClient: ) logging.debug("Start session %s, result: %s", self.session_id, response.result) + response = self.client.get_service_defaults(self.session_id) + for default in response.defaults: + print(default.node_type) + print(default.services) + response = self.client.get_node_service(self.session_id, 5, "FTP") + print(response) + def stop_session(self): response = self.client.stop_session(session_id=self.session_id) logging.debug("coregrpc.py Stop session, result: %s", response.result) @@ -488,6 +497,12 @@ class CoreClient: if node_type == core_pb2.NodeType.EMANE: self.emaneconfig_management.set_default_config(nid) + # set default service configurations + if node_type == core_pb2.NodeType.DEFAULT: + self.serviceconfig_manager.node_default_services_configuration( + node_id=nid, node_model=node_model + ) + self.nodes[canvas_id] = create_node self.core_mapping.map_core_id_to_canvas_id(nid, canvas_id) logging.debug( diff --git a/coretk/coretk/dialogs/nodeconfig.py b/coretk/coretk/dialogs/nodeconfig.py index be41afb5..ce7261e2 100644 --- a/coretk/coretk/dialogs/nodeconfig.py +++ b/coretk/coretk/dialogs/nodeconfig.py @@ -4,7 +4,7 @@ from tkinter import ttk from coretk.coreclient import DEFAULT_NODES from coretk.dialogs.dialog import Dialog from coretk.dialogs.icondialog import IconDialog -from coretk.dialogs.nodeservice import NodeServicesDialog +from coretk.dialogs.nodeservice import NodeService class NodeConfigDialog(Dialog): @@ -85,7 +85,7 @@ class NodeConfigDialog(Dialog): button.grid(row=0, column=1, sticky="ew") def click_services(self): - dialog = NodeServicesDialog(self, self.app, self.canvas_node) + dialog = NodeService(self, self.app, self.canvas_node) dialog.show() def click_icon(self): diff --git a/coretk/coretk/dialogs/nodeservice.py b/coretk/coretk/dialogs/nodeservice.py index fe31457c..49cc56d8 100644 --- a/coretk/coretk/dialogs/nodeservice.py +++ b/coretk/coretk/dialogs/nodeservice.py @@ -5,154 +5,100 @@ import tkinter as tk from tkinter import messagebox, ttk from coretk.dialogs.dialog import Dialog +from coretk.dialogs.serviceconfiguration import ServiceConfiguration +from coretk.widgets import CheckboxList, ListboxScroll -class NodeServicesDialog(Dialog): - def __init__(self, master, app, canvas_node): +class NodeService(Dialog): + def __init__(self, master, app, canvas_node, services=None): super().__init__(master, app, "Node Services", modal=True) self.canvas_node = canvas_node - self.core_groups = [] - self.service_to_config = None - self.config_frame = None - self.services_list = None + self.groups = None + self.services = None + self.current = None + if services is None: + services = set() + self.current_services = services self.draw() def draw(self): self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) - self.config_frame = ttk.Frame(self) - self.config_frame.columnconfigure(0, weight=1) - self.config_frame.columnconfigure(1, weight=1) - self.config_frame.columnconfigure(2, weight=1) - self.config_frame.rowconfigure(0, weight=1) - self.config_frame.grid(row=0, column=0, sticky="nsew") - self.draw_group() - self.draw_services() - self.draw_current_services() - self.draw_buttons() - def draw_group(self): - """ - draw the group tab - - :return: nothing - """ - frame = ttk.Frame(self.config_frame) - frame.columnconfigure(0, weight=1) - frame.rowconfigure(1, weight=1) - frame.grid(row=0, column=0, padx=3, pady=3, sticky="nsew") - - label = ttk.Label(frame, text="Group") - label.grid(row=0, column=0, sticky="ew") - - scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL) - scrollbar.grid(row=1, column=1, sticky="ns") - - listbox = tk.Listbox( - frame, - selectmode=tk.SINGLE, - yscrollcommand=scrollbar.set, - relief=tk.FLAT, - highlightthickness=0.5, - bd=0, - ) - listbox.grid(row=1, column=0, sticky="nsew") - listbox.bind("<>", self.handle_group_change) - - for group in sorted(self.app.core.services): - listbox.insert(tk.END, group) - - scrollbar.config(command=listbox.yview) - - def draw_services(self): - frame = ttk.Frame(self.config_frame) - frame.columnconfigure(0, weight=1) - frame.rowconfigure(1, weight=1) - frame.grid(row=0, column=1, padx=3, pady=3, sticky="nsew") - - label = ttk.Label(frame, text="Group services") - label.grid(row=0, column=0, sticky="ew") - - scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL) - scrollbar.grid(row=1, column=1, sticky="ns") - - self.services_list = tk.Listbox( - frame, - selectmode=tk.SINGLE, - yscrollcommand=scrollbar.set, - relief=tk.FLAT, - highlightthickness=0.5, - bd=0, - ) - self.services_list.grid(row=1, column=0, sticky="nsew") - self.services_list.bind("<>", self.handle_service_change) - - scrollbar.config(command=self.services_list.yview) - - def draw_current_services(self): - frame = ttk.Frame(self.config_frame) - frame.columnconfigure(0, weight=1) - frame.rowconfigure(1, weight=1) - frame.grid(row=0, column=2, padx=3, pady=3, sticky="nsew") - - label = ttk.Label(frame, text="Current services") - label.grid(row=0, column=0, sticky="ew") - - scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL) - scrollbar.grid(row=1, column=1, sticky="ns") - - listbox = tk.Listbox( - frame, - selectmode=tk.MULTIPLE, - yscrollcommand=scrollbar.set, - relief=tk.FLAT, - highlightthickness=0.5, - bd=0, - ) - listbox.grid(row=1, column=0, sticky="nsew") - - scrollbar.config(command=listbox.yview) - - def draw_buttons(self): frame = ttk.Frame(self) - frame.columnconfigure(0, weight=1) - frame.columnconfigure(1, weight=1) - frame.columnconfigure(2, weight=1) - frame.grid(row=1, column=0, sticky="ew") + frame.grid(stick="nsew") + frame.rowconfigure(0, weight=1) + for i in range(3): + frame.columnconfigure(i, weight=1) + self.groups = ListboxScroll(frame, text="Groups") + self.groups.grid(row=0, column=0, sticky="nsew") + for group in sorted(self.app.core.services): + self.groups.listbox.insert(tk.END, group) + self.groups.listbox.bind("<>", self.handle_group_change) + self.groups.listbox.selection_set(0) + self.services = CheckboxList( + frame, text="Services", clicked=self.service_clicked + ) + self.services.grid(row=0, column=1, sticky="nsew") + + self.current = ListboxScroll(frame, text="Selected") + self.current.grid(row=0, column=2, sticky="nsew") + for service in sorted(self.current_services): + self.current.listbox.insert(tk.END, service) + + frame = ttk.Frame(self) + frame.grid(stick="ew") + for i in range(3): + frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Configure", command=self.click_configure) button.grid(row=0, column=0, sticky="ew") - - button = ttk.Button(frame, text="Apply") + button = ttk.Button(frame, text="Save", command=self.click_save) button.grid(row=0, column=1, sticky="ew") - - button = ttk.Button(frame, text="Cancel", command=self.destroy) + button = ttk.Button(frame, text="Cancel", command=self.click_cancel) button.grid(row=0, column=2, sticky="ew") + # trigger group change + self.groups.listbox.event_generate("<>") + def handle_group_change(self, event): - listbox = event.widget - cur_selection = listbox.curselection() - if cur_selection: - s = listbox.get(listbox.curselection()) - self.display_group_services(s) + selection = self.groups.listbox.curselection() + if selection: + index = selection[0] + group = self.groups.listbox.get(index) + self.services.clear() + for service in sorted(self.app.core.services[group], key=lambda x: x.name): + checked = service.name in self.current_services + self.services.add(service.name, checked) - def display_group_services(self, group_name): - self.services_list.delete(0, tk.END) - for service in sorted(self.app.core.services[group_name], key=lambda x: x.name): - self.services_list.insert(tk.END, service.name) - - def handle_service_change(self, event): - print("select group service") - listbox = event.widget - cur_selection = listbox.curselection() - if cur_selection: - s = listbox.get(listbox.curselection()) - self.service_to_config = s - else: - self.service_to_config = None + def service_clicked(self, name, var): + if var.get() and name not in self.current_services: + 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) def click_configure(self): - if self.service_to_config is None: - messagebox.showinfo("CORE info", "Choose a service to configure.") + current_selection = self.current.listbox.curselection() + if len(current_selection): + dialog = ServiceConfiguration( + master=self, + app=self.app, + service_name=self.current.listbox.get(current_selection[0]), + canvas_node=self.canvas_node, + ) + dialog.show() else: - print(self.service_to_config) + messagebox.showinfo( + "Node service configuration", "Select a service to configure" + ) + + def click_save(self): + print("not implemented") + print(self.current_services) + + def click_cancel(self): + self.current_services = None + self.destroy() diff --git a/coretk/coretk/dialogs/serviceconfiguration.py b/coretk/coretk/dialogs/serviceconfiguration.py new file mode 100644 index 00000000..b0b35db9 --- /dev/null +++ b/coretk/coretk/dialogs/serviceconfiguration.py @@ -0,0 +1,281 @@ +"Service configuration dialog" +import logging +import tkinter as tk +from tkinter import ttk + +from coretk.dialogs.dialog import Dialog +from coretk.images import ImageEnum, Images +from coretk.widgets import ListboxScroll + + +class ServiceConfiguration(Dialog): + def __init__(self, master, app, service_name, canvas_node): + super().__init__(master, app, f"{service_name} service", modal=True) + self.app = app + self.service_name = service_name + self.metadata = tk.StringVar() + self.filename = tk.StringVar() + self.radiovar = tk.IntVar() + self.radiovar.set(2) + self.startup_index = tk.IntVar() + self.start_time = tk.IntVar() + self.documentnew_img = Images.get(ImageEnum.DOCUMENTNEW, 16) + self.editdelete_img = Images.get(ImageEnum.EDITDELETE, 16) + self.tab_parent = None + self.filenames = ["test1", "test2", "test3"] + + self.metadata_entry = None + self.filename_combobox = None + self.startup_commands_listbox = None + self.shutdown_commands_listbox = None + self.validate_commands_listbox = None + + self.draw() + + def draw(self): + # self.columnconfigure(1, weight=1) + frame = ttk.Frame(self) + frame1 = ttk.Frame(frame) + label = ttk.Label(frame1, text=self.service_name) + label.grid(row=0, column=0, sticky="ew") + frame1.grid(row=0, column=0) + frame2 = ttk.Frame(frame) + # frame2.columnconfigure(0, weight=1) + # frame2.columnconfigure(1, weight=4) + label = ttk.Label(frame2, text="Meta-data") + label.grid(row=0, column=0) + self.metadata_entry = ttk.Entry(frame2, textvariable=self.metadata) + self.metadata_entry.grid(row=0, column=1) + frame2.grid(row=1, column=0) + frame.grid(row=0, column=0) + + frame = ttk.Frame(self) + self.tab_parent = ttk.Notebook(frame) + tab1 = ttk.Frame(self.tab_parent) + tab2 = ttk.Frame(self.tab_parent) + tab3 = ttk.Frame(self.tab_parent) + tab4 = ttk.Frame(self.tab_parent) + tab1.columnconfigure(0, weight=1) + tab2.columnconfigure(0, weight=1) + tab3.columnconfigure(0, weight=1) + tab4.columnconfigure(0, weight=1) + + self.tab_parent.add(tab1, text="Files", sticky="nsew") + self.tab_parent.add(tab2, text="Directories", sticky="nsew") + self.tab_parent.add(tab3, text="Startup/shutdown", sticky="nsew") + self.tab_parent.add(tab4, text="Configuration", sticky="nsew") + self.tab_parent.grid(row=0, column=0, sticky="nsew") + frame.grid(row=1, column=0, sticky="nsew") + + # tab 1 + label = ttk.Label( + tab1, text="Config files and scripts that are generated for this service." + ) + label.grid(row=0, column=0, sticky="nsew") + + frame = ttk.Frame(tab1) + label = ttk.Label(frame, text="File name: ") + label.grid(row=0, column=0) + self.filename_combobox = ttk.Combobox(frame, values=self.filenames) + self.filename_combobox.grid(row=0, column=1) + self.filename_combobox.current(0) + button = ttk.Button(frame, image=self.documentnew_img) + button.bind("", self.add_filename) + button.grid(row=0, column=2) + button = ttk.Button(frame, image=self.editdelete_img) + button.bind("", self.delete_filename) + button.grid(row=0, column=3) + frame.grid(row=1, column=0, sticky="nsew") + + frame = ttk.Frame(tab1) + button = ttk.Radiobutton( + frame, + variable=self.radiovar, + text="Copy this source file:", + value=1, + state="disabled", + ) + button.grid(row=0, column=0) + entry = ttk.Entry(frame, state=tk.DISABLED) + entry.grid(row=0, column=1) + image = Images.get(ImageEnum.FILEOPEN, 16) + button = ttk.Button(frame, image=image) + button.image = image + button.grid(row=0, column=2) + frame.grid(row=2, column=0, sticky="nsew") + + frame = ttk.Frame(tab1) + button = ttk.Radiobutton( + frame, + variable=self.radiovar, + text="Use text below for file contents:", + value=2, + ) + button.grid(row=0, column=0) + image = Images.get(ImageEnum.FILEOPEN, 16) + button = ttk.Button(frame, image=image) + button.image = image + button.grid(row=0, column=1) + image = Images.get(ImageEnum.DOCUMENTSAVE, 16) + button = ttk.Button(frame, image=image) + button.image = image + button.grid(row=0, column=2) + frame.grid(row=3, column=0, sticky="nsew") + + # tab 2 + label = ttk.Label( + tab2, + text="Directories required by this service that are unique for each node.", + ) + label.grid(row=0, column=0, sticky="nsew") + + # tab 3 + for i in range(3): + label_frame = None + if i == 0: + label_frame = ttk.LabelFrame(tab3, text="Startup commands") + elif i == 1: + label_frame = ttk.LabelFrame(tab3, text="Shutdown commands") + elif i == 2: + label_frame = ttk.LabelFrame(tab3, text="Validation commands") + label_frame.columnconfigure(0, weight=1) + frame = ttk.Frame(label_frame) + frame.columnconfigure(0, weight=1) + entry = ttk.Entry(frame, textvariable=tk.StringVar()) + entry.grid(row=0, column=0, stick="nsew") + button = ttk.Button(frame, image=self.documentnew_img) + button.bind("", self.add_command) + button.grid(row=0, column=1, sticky="nsew") + button = ttk.Button(frame, image=self.editdelete_img) + button.grid(row=0, column=2, sticky="nsew") + button.bind("", self.delete_command) + frame.grid(row=0, column=0, sticky="nsew") + listbox_scroll = ListboxScroll(label_frame) + listbox_scroll.listbox.bind("<>", self.update_entry) + listbox_scroll.listbox.config(height=4) + listbox_scroll.grid(row=1, column=0, sticky="nsew") + if i == 0: + self.startup_commands_listbox = listbox_scroll.listbox + elif i == 1: + self.shutdown_commands_listbox = listbox_scroll.listbox + elif i == 2: + self.validate_commands_listbox = listbox_scroll.listbox + label_frame.grid(row=i, column=0, sticky="nsew") + + # tab 4 + for i in range(2): + if i == 0: + label_frame = ttk.LabelFrame(tab4, text="Executables") + elif i == 1: + label_frame = ttk.LabelFrame(tab4, text="Dependencies") + + label_frame.columnconfigure(0, weight=1) + listbox_scroll = ListboxScroll(label_frame) + listbox_scroll.listbox.config(height=4, state="disabled") + listbox_scroll.grid(row=0, column=0, sticky="nsew") + label_frame.grid(row=i, column=0, sticky="nsew") + + for i in range(3): + frame = ttk.Frame(tab4) + frame.columnconfigure(0, weight=1) + if i == 0: + label = ttk.Label(frame, text="Validation time:") + elif i == 1: + label = ttk.Label(frame, text="Validation mode:") + elif i == 2: + label = ttk.Label(frame, text="Validation period:") + label.grid(row=i, column=0) + entry = ttk.Entry(frame, state="disabled", textvariable=tk.StringVar()) + entry.grid(row=i, column=1) + frame.grid(row=2 + i, column=0, sticky="nsew") + + button = ttk.Button( + self, text="onle store values that have changed from their defaults" + ) + button.grid(row=2, column=0) + + frame = ttk.Frame(self) + button = ttk.Button(frame, text="Apply", command=self.click_apply) + button.grid(row=0, column=0, sticky="nsew") + button = ttk.Button( + frame, text="Dafults", command=self.click_defaults, state="disabled" + ) + button.grid(row=0, column=1, sticky="nsew") + button = ttk.Button( + frame, text="Copy...", command=self.click_copy, state="disabled" + ) + button.grid(row=0, column=2, sticky="nsew") + button = ttk.Button(frame, text="Cancel", command=self.destroy) + button.grid(row=0, column=3, sticky="nsew") + frame.grid(row=3, column=0) + + def add_filename(self, event): + 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 delete_filename(self, event): + 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 add_command(self, 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() + if command_to_add == "": + return + for cmd in listbox.get(0, tk.END): + if cmd == command_to_add: + return + listbox.insert(tk.END, command_to_add) + + def update_entry(self, event): + listbox = event.widget + current_selection = listbox.curselection() + if len(current_selection) > 0: + cmd = listbox.get(current_selection[0]) + entry = listbox.master.master.grid_slaves(row=0, column=0)[0].grid_slaves( + row=0, column=0 + )[0] + entry.delete(0, "end") + entry.insert(0, cmd) + + def delete_command(self, event): + button = event.widget + frame_contains_button = button.master + listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox + current_selection = listbox.curselection() + if len(current_selection) > 0: + listbox.delete(current_selection[0]) + entry = frame_contains_button.grid_slaves(row=0, column=0)[0] + entry.delete(0, tk.END) + + def click_apply(self): + metadata = self.metadata_entry.get() + filenames = list(self.filename_combobox["values"]) + 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") + logging.info( + "%s, %s, %s, %s, %s", + metadata, + filenames, + startup_commands, + shutdown_commands, + validate_commands, + ) + + def click_defaults(self): + logging.info("not implemented") + + def click_copy(self): + logging.info("not implemented") + + def click_cancel(self): + logging.info("not implemented") diff --git a/coretk/coretk/icons/document-save.gif b/coretk/coretk/icons/document-save.gif new file mode 100644 index 00000000..165bcb90 Binary files /dev/null and b/coretk/coretk/icons/document-save.gif differ diff --git a/coretk/coretk/images.py b/coretk/coretk/images.py index cebcb49c..7335e14e 100644 --- a/coretk/coretk/images.py +++ b/coretk/coretk/images.py @@ -117,6 +117,7 @@ class ImageEnum(Enum): OBSERVE = "observe" RUN = "run" DOCUMENTNEW = "document-new" + DOCUMENTSAVE = "document-save" FILEOPEN = "fileopen" EDITDELETE = "edit-delete" ANTENNA = "antenna" diff --git a/coretk/coretk/servicenodeconfig.py b/coretk/coretk/servicenodeconfig.py new file mode 100644 index 00000000..486e646d --- /dev/null +++ b/coretk/coretk/servicenodeconfig.py @@ -0,0 +1,37 @@ +""" +service node configuration +""" +import logging + + +class ServiceNodeConfig: + def __init__(self, app): + self.app = app + self.configurations = {} + self.default_services = {} + + def node_default_services_configuration(self, node_id, node_model): + """ + set the default configurations for the default services of a node + + :param coretk.graph.CanvasNode canvas_node: canvas node object + :return: nothing + """ + session_id = self.app.core.session_id + client = self.app.core.client + if len(self.default_services) == 0: + response = client.get_service_defaults(session_id) + logging.info("session default services: %s", response) + for default in response.defaults: + self.default_services[default.node_type] = default.services + + self.configurations[node_id] = {} + + for default in self.default_services[node_model]: + response = client.get_node_service(session_id, node_id, default) + logging.info( + "servicenodeconfig.py get node service (%s), result: %s", + node_id, + response, + ) + self.configurations[node_id][default] = response.service