diff --git a/daemon/core/gui/dialogs/dialog.py b/daemon/core/gui/dialogs/dialog.py index 00532793..21222f39 100644 --- a/daemon/core/gui/dialogs/dialog.py +++ b/daemon/core/gui/dialogs/dialog.py @@ -1,4 +1,5 @@ import tkinter as tk +import webbrowser from tkinter import ttk from typing import TYPE_CHECKING @@ -41,3 +42,7 @@ class Dialog(tk.Toplevel): frame.grid(row=row, sticky="nsew") frame.rowconfigure(0, weight=1) self.top.rowconfigure(frame.grid_info()["row"], weight=1) + + @classmethod + def navigate_link(cls, link: str): + webbrowser.open_new(link) diff --git a/daemon/core/gui/dialogs/emaneconfig.py b/daemon/core/gui/dialogs/emaneconfig.py index 1bcdd78b..c9760c00 100644 --- a/daemon/core/gui/dialogs/emaneconfig.py +++ b/daemon/core/gui/dialogs/emaneconfig.py @@ -3,6 +3,7 @@ emane configuration """ import tkinter as tk import webbrowser +from enum import Enum from tkinter import ttk from typing import TYPE_CHECKING, Any @@ -20,6 +21,18 @@ if TYPE_CHECKING: from core.gui.graph.node import CanvasNode +class EmaneModelEnum(Enum): + RFPIPE = "emane_rfpipe" + IEEE80211ABG = "emane_ieee80211abg" + COMMEFFECT = "emane_commeffect" + BYPASS = "emane_bypass" + TDMA = "emane_tdma" + + +def get_model(enum_class: EmaneModelEnum): + return enum_class.value + + class GlobalEmaneDialog(Dialog): def __init__(self, master: Any, app: "Application"): super().__init__(master, app, "EMANE Configuration", modal=True) @@ -38,7 +51,7 @@ class GlobalEmaneDialog(Dialog): def draw_buttons(self): frame = ttk.Frame(self.top) frame.grid(sticky="ew") - for i in range(2): + for i in range(3): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Apply", command=self.click_apply) button.grid(row=0, column=0, sticky="ew", padx=PADX) @@ -46,6 +59,15 @@ class GlobalEmaneDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") + button = ttk.Button( + frame, + text="Info Link", + command=lambda: self.navigate_link( + "https://github.com/adjacentlink/emane/wiki/Configuring-the-Emulator" + ), + ) + button.grid(row=0, column=2, sticky="ew") + def click_apply(self): self.config_frame.parse_config() self.destroy() @@ -86,7 +108,7 @@ class EmaneModelDialog(Dialog): def draw_buttons(self): frame = ttk.Frame(self.top) frame.grid(sticky="ew") - for i in range(2): + for i in range(3): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Apply", command=self.click_apply) button.grid(row=0, column=0, sticky="ew", padx=PADX) @@ -94,6 +116,9 @@ class EmaneModelDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") + button = ttk.Button(frame, text="Info Link ", command=self.wiki_link) + button.grid(row=0, column=2, sticky="ew") + def click_apply(self): self.config_frame.parse_config() self.app.core.set_emane_model_config( @@ -101,6 +126,24 @@ class EmaneModelDialog(Dialog): ) self.destroy() + def wiki_link(self): + if self.model == get_model(EmaneModelEnum.RFPIPE): + self.navigate_link( + "https://github.com/adjacentlink/emane/wiki/RF-Pipe-Model" + ) + elif self.model == get_model(EmaneModelEnum.COMMEFFECT): + self.navigate_link( + "https://github.com/adjacentlink/emane/wiki/Comm-Effect-Model" + ) + elif self.model == get_model(EmaneModelEnum.TDMA): + self.navigate_link("https://github.com/adjacentlink/emane/wiki/TDMA-Model") + elif self.model == get_model(EmaneModelEnum.IEEE80211ABG): + self.navigate_link( + "https://github.com/adjacentlink/emane/wiki/IEEE-802.11abg-Model" + ) + else: + return + class EmaneConfigDialog(Dialog): def __init__( diff --git a/daemon/core/gui/dialogs/nodeservice.py b/daemon/core/gui/dialogs/nodeservice.py index c61983f7..57f9bd20 100644 --- a/daemon/core/gui/dialogs/nodeservice.py +++ b/daemon/core/gui/dialogs/nodeservice.py @@ -133,11 +133,12 @@ class NodeServiceDialog(Dialog): def click_configure(self): current_selection = self.current.listbox.curselection() + service_name = self.current.listbox.get(current_selection[0]) if len(current_selection): dialog = ServiceConfigDialog( master=self, app=self.app, - service_name=self.current.listbox.get(current_selection[0]), + service_name=service_name, node_id=self.node_id, ) dialog.show() diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index 804e7e3f..3b728fe6 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -65,7 +65,7 @@ class ServiceConfigDialog(Dialog): self.temp_service_files = {} self.modified_files = set() self.load() - self.draw() + # self.draw() def load(self): try: @@ -108,8 +108,12 @@ class ServiceConfigDialog(Dialog): ): for file, data in file_configs[self.node_id][self.service_name].items(): self.temp_service_files[file] = data + self.draw() except grpc.RpcError as e: - show_grpc_error(e) + if not self.is_ipsec(): + show_grpc_error(e) + else: + self.draw() def draw(self): self.top.columnconfigure(0, weight=1) @@ -127,13 +131,139 @@ class ServiceConfigDialog(Dialog): # draw notebook self.notebook = ttk.Notebook(self.top) self.notebook.grid(sticky="nsew", pady=PADY) - self.draw_tab_files() + if not self.is_ipsec(): + self.draw_tab_files() + else: + self.draw_ipsec_tab() self.draw_tab_directories() self.draw_tab_startstop() self.draw_tab_configuration() self.draw_buttons() + def draw_ipsec_tab(self): + tab = ttk.Frame(self.notebook, padding=FRAME_PAD) + tab.grid(sticky="nsew") + tab.columnconfigure(0, weight=1) + self.notebook.add(tab, text="IPsec") + + text = ( + "This IPsec service helper will assist with building an ipsec.sh file (located on the Files tab).\nThe IPsec service builds ESP tunnels between" + "the specified peers using the racoon IKEv2\nkeying daemon. You need to provide keys and the addresses of peers, along with the\nsubnet to tunnel." + ) + label = ttk.Label(tab, text=text) + label.grid(row=0, column=0, sticky="nsew") + + label_frame = ttk.LabelFrame(tab, text="Keys", padding=FRAME_PAD) + label_frame.grid(row=1, column=0, sticky="nsew") + label_frame.columnconfigure(0, weight=1) + for i in range(3): + label_frame.rowconfigure(i, weight=1) + + frame = ttk.Frame(label_frame, padding=FRAME_PAD) + frame.grid(row=0, column=0, sticky="nsew") + frame.columnconfigure(0, weight=1) + frame.columnconfigure(1, weight=2) + frame.columnconfigure(2, weight=1) + + label = ttk.Label(frame, text="Key directory: ") + label.grid(row=0, column=0, sticky="ew") + entry = ttk.Entry(frame) + entry.grid(row=0, column=1, stick="ew") + button = ttk.Button(frame, text="...") + button.grid(row=0, column=2, sticky="ew") + + frame = ttk.Frame(label_frame, padding=FRAME_PAD) + frame.grid(row=1, column=0, sticky="nsew") + frame.columnconfigure(0, weight=1) + frame.columnconfigure(1, weight=3) + label = ttk.Label(frame, text="Key base name: ") + label.grid(row=0, column=0, sticky="ew") + entry = ttk.Entry(frame) + entry.grid(row=0, column=1, sticky="ew") + + text = ( + "The (name).pem x509 certificate and (name).key RSA private key need to exist in the\n" + "specified directory. These can be generated using the openssl tool. Also, a ca-cert.pem\n" + "file should exist in the key directory for the CA that issue the certs." + ) + label = ttk.Label(label_frame, text=text, padding=FRAME_PAD) + label.grid(row=2, column=0, sticky="ew") + + label_frame = ttk.LabelFrame( + tab, text="IPsec Tunnel Endpoints", padding=FRAME_PAD + ) + label_frame.grid(row=2, column=0, sticky="nsew") + + i = 0 + text = ( + "(1) Define tunnel endpoints (select peer node using the button, then select" + "address from the list)" + ) + label = ttk.Label(label_frame, text=text, padding=FRAME_PAD) + label.grid(row=i, column=0, sticky="nsew") + i = i + 1 + + frame = ttk.Frame(label_frame, padding=FRAME_PAD) + frame.grid(row=i, column=0, sticky="nsew") + frame.columnconfigure(0, weight=1) + frame.columnconfigure(1, weight=3) + i = i + 1 + label = ttk.Label(frame, text="Local: ") + label.grid(row=0, column=0, sticky="ew") + combobox = ttk.Combobox(frame) + combobox.grid(row=0, column=1, sticky="ew") + + frame = ttk.Frame(label_frame, padding=FRAME_PAD) + frame.grid(row=i, column=0, sticky="nsew") + i = i + 1 + label = ttk.Label(frame, text="Peer node: (none)") + label.grid(row=0, column=0) + + frame = ttk.Frame(label_frame, padding=FRAME_PAD) + frame.grid(row=i, column=0, sticky="nsew") + frame.columnconfigure(0, weight=1) + frame.columnconfigure(1, weight=3) + i = i + 1 + label = ttk.Label(frame, text="Peer: ") + label.grid(row=0, column=0, sticky="ew") + combobox = ttk.Combobox(frame) + combobox.grid(row=0, column=1, sticky="ew") + + text = "(2) Select endpoints below and add the subnets to be encrypted" + label = ttk.Label(label_frame, text=text, padding=FRAME_PAD) + label.grid(row=i, column=0, sticky="ew") + i = i + i + + frame = ttk.Frame(label_frame, padding=FRAME_PAD) + frame.grid(row=i, column=0, sticky="nsew") + frame.columnconfigure(0, weight=1) + frame.columnconfigure(1, weight=3) + i = i + 1 + label = ttk.Label(frame, text="Local subnet: ") + label.grid(row=0, column=0, sticky="ew") + combobox = ttk.Combobox(frame) + combobox.grid(row=0, column=1, sticky="ew") + + frame = ttk.Frame(label_frame, padding=FRAME_PAD) + frame.grid(row=i, column=0, sticky="nsew") + frame.columnconfigure(0, weight=1) + frame.columnconfigure(1, weight=3) + i = i + 1 + label = ttk.Label(frame, text="Remote subnet: ") + label.grid(row=0, column=0, sticky="ew") + combobox = ttk.Combobox(frame) + combobox.grid(row=0, column=1, sticky="ew") + + frame = ttk.Frame(tab, padding=FRAME_PAD) + frame.grid(row=3, column=0, sticky="nsew") + for i in range(2): + frame.columnconfigure(i, weight=1) + button = ttk.Button(frame, text="Trash") + button.grid(row=0, column=0, sticky="ew") + button = ttk.Button(frame, text="Generate ipsec.sh") + button.grid(row=0, column=1, sticky="ew") + def draw_tab_files(self): tab = ttk.Frame(self.notebook, padding=FRAME_PAD) tab.grid(sticky="nsew") @@ -342,7 +472,7 @@ class ServiceConfigDialog(Dialog): def draw_buttons(self): frame = ttk.Frame(self.top) frame.grid(sticky="ew") - for i in range(4): + for i in range(5): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Apply", command=self.click_apply) button.grid(row=0, column=0, sticky="ew", padx=PADX) @@ -352,6 +482,14 @@ class ServiceConfigDialog(Dialog): button.grid(row=0, column=2, sticky="ew", padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=3, sticky="ew") + button = ttk.Button( + frame, + text="Info Link", + command=lambda: self.navigate_link( + "http://coreemu.github.io/core/services.html" + ), + ) + button.grid(row=0, column=4, sticky="ew") def add_filename(self, event: tk.Event): # not worry about it for now @@ -504,3 +642,6 @@ class ServiceConfigDialog(Dialog): for cmd in to_add: commands.append(cmd) listbox.insert(tk.END, cmd) + + def is_ipsec(self): + return self.service_name == "IPsec"