diff --git a/coretk/coretk/dialogs/about.py b/coretk/coretk/dialogs/about.py index 28ddea33..9e3ff7a9 100644 --- a/coretk/coretk/dialogs/about.py +++ b/coretk/coretk/dialogs/about.py @@ -38,7 +38,7 @@ class AboutDialog(Dialog): self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) - text = CodeText(self.top) - text.insert("1.0", LICENSE) - text.config(state=tk.DISABLED) - text.grid(sticky="nsew") + codetext = CodeText(self.top) + codetext.text.insert("1.0", LICENSE) + codetext.text.config(state=tk.DISABLED) + codetext.grid(sticky="nsew") diff --git a/coretk/coretk/dialogs/alerts.py b/coretk/coretk/dialogs/alerts.py index 89e78f02..e782547f 100644 --- a/coretk/coretk/dialogs/alerts.py +++ b/coretk/coretk/dialogs/alerts.py @@ -17,7 +17,7 @@ class AlertsDialog(Dialog): super().__init__(master, app, "Alerts", modal=True) self.app = app self.tree = None - self.text = None + self.codetext = None self.draw() def draw(self): @@ -76,9 +76,9 @@ class AlertsDialog(Dialog): xscrollbar.grid(row=1, sticky="ew") self.tree.configure(xscrollcommand=xscrollbar.set) - self.text = CodeText(self.top) - self.text.config(state=tk.DISABLED) - self.text.grid(sticky="nsew", pady=PADY) + self.codetext = CodeText(self.top) + self.codetext.text.config(state=tk.DISABLED) + self.codetext.grid(sticky="nsew", pady=PADY) frame = ttk.Frame(self.top) frame.grid(sticky="ew") @@ -96,7 +96,7 @@ class AlertsDialog(Dialog): button.grid(row=0, column=3, sticky="ew") def reset_alerts(self): - self.text.delete("1.0", tk.END) + self.codetext.text.delete("1.0", tk.END) for item in self.tree.get_children(): self.tree.delete(item) self.app.statusbar.core_alarms.clear() @@ -137,8 +137,8 @@ class AlertsDialog(Dialog): text = text + "node created" except RpcError: text = text + "node not created" - self.text.delete("1.0", "end") - self.text.insert("1.0", text) + self.codetext.text.delete("1.0", "end") + self.codetext.text.insert("1.0", text) class DaemonLog(Dialog): @@ -155,8 +155,8 @@ class DaemonLog(Dialog): frame.grid(row=0, column=0, sticky="ew", pady=PADY) frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=9) - label = ttk.Label(frame, text="File: ") - label.grid(row=0, column=0) + label = ttk.Label(frame, text="File", anchor="w") + label.grid(row=0, column=0, sticky="ew") entry = ttk.Entry(frame, textvariable=self.path, state="disabled") entry.grid(row=0, column=1, sticky="ew") try: @@ -164,8 +164,8 @@ class DaemonLog(Dialog): log = file.readlines() except FileNotFoundError: log = "Log file not found" - text = CodeText(self.top) - text.insert("1.0", log) - text.see("end") - text.config(state=tk.DISABLED) - text.grid(row=1, column=0, sticky="nsew") + codetext = CodeText(self.top) + codetext.text.insert("1.0", log) + codetext.text.see("end") + codetext.text.config(state=tk.DISABLED) + codetext.grid(row=1, column=0, sticky="nsew") diff --git a/coretk/coretk/dialogs/canvaswallpaper.py b/coretk/coretk/dialogs/canvaswallpaper.py index 923e4d5f..570bfa08 100644 --- a/coretk/coretk/dialogs/canvaswallpaper.py +++ b/coretk/coretk/dialogs/canvaswallpaper.py @@ -3,12 +3,13 @@ set wallpaper """ import logging import tkinter as tk -from tkinter import filedialog, ttk +from tkinter import ttk from coretk.appconfig import BACKGROUNDS_PATH from coretk.dialogs.dialog import Dialog from coretk.images import Images from coretk.themes import PADX, PADY +from coretk.widgets import image_chooser class CanvasBackgroundDialog(Dialog): @@ -126,14 +127,7 @@ class CanvasBackgroundDialog(Dialog): button.grid(row=0, column=1, sticky="ew") def click_open_image(self): - filename = filedialog.askopenfilename( - initialdir=str(BACKGROUNDS_PATH), - title="Open", - filetypes=( - ("images", "*.gif *.jpg *.png *.bmp *pcx *.tga ..."), - ("All Files", "*"), - ), - ) + filename = image_chooser(self, BACKGROUNDS_PATH) if filename: self.filename.set(filename) self.draw_preview() diff --git a/coretk/coretk/dialogs/customnodes.py b/coretk/coretk/dialogs/customnodes.py index 7effd11a..6416d24a 100644 --- a/coretk/coretk/dialogs/customnodes.py +++ b/coretk/coretk/dialogs/customnodes.py @@ -4,6 +4,7 @@ from pathlib import Path from tkinter import ttk from coretk import nodeutils +from coretk.appconfig import ICONS_PATH from coretk.dialogs.dialog import Dialog from coretk.images import Images from coretk.nodeutils import NodeDraw @@ -179,7 +180,7 @@ class CustomNodesDialog(Dialog): self.image_button.config(image="") def click_icon(self): - file_path = image_chooser(self) + file_path = image_chooser(self, ICONS_PATH) if file_path: image = Images.create(file_path, nodeutils.ICON_SIZE) self.image = image diff --git a/coretk/coretk/dialogs/hooks.py b/coretk/coretk/dialogs/hooks.py index 9aeebecc..37503d66 100644 --- a/coretk/coretk/dialogs/hooks.py +++ b/coretk/coretk/dialogs/hooks.py @@ -11,7 +11,7 @@ class HookDialog(Dialog): def __init__(self, master, app): super().__init__(master, app, "Hook", modal=True) self.name = tk.StringVar() - self.data = None + self.codetext = None self.hook = core_pb2.Hook() self.state = tk.StringVar() self.draw() @@ -41,8 +41,8 @@ class HookDialog(Dialog): combobox.bind("<>", self.state_change) # data - self.data = CodeText(self.top) - self.data.insert( + self.codetext = CodeText(self.top) + self.codetext.text.insert( 1.0, ( "#!/bin/sh\n" @@ -50,7 +50,7 @@ class HookDialog(Dialog): "# specified state\n" ), ) - self.data.grid(sticky="nsew") + self.codetext.grid(sticky="nsew", pady=PADY) # button row frame = ttk.Frame(self.top) @@ -69,13 +69,13 @@ class HookDialog(Dialog): def set(self, hook): self.hook = hook self.name.set(hook.file) - self.data.delete(1.0, tk.END) - self.data.insert(tk.END, hook.data) + self.codetext.text.delete(1.0, tk.END) + self.codetext.text.insert(tk.END, hook.data) state_name = core_pb2.SessionState.Enum.Name(hook.state) self.state.set(state_name) def save(self): - data = self.data.get("1.0", tk.END).strip() + data = self.codetext.text.get("1.0", tk.END).strip() state_value = core_pb2.SessionState.Enum.Value(self.state.get()) self.hook.file = self.name.get() self.hook.data = data diff --git a/coretk/coretk/dialogs/nodeconfig.py b/coretk/coretk/dialogs/nodeconfig.py index cfec3b24..4e5bc864 100644 --- a/coretk/coretk/dialogs/nodeconfig.py +++ b/coretk/coretk/dialogs/nodeconfig.py @@ -4,11 +4,13 @@ from functools import partial from tkinter import ttk from coretk import nodeutils +from coretk.appconfig import ICONS_PATH from coretk.dialogs.dialog import Dialog +from coretk.dialogs.emaneconfig import EmaneModelDialog from coretk.images import Images from coretk.nodeutils import NodeUtils from coretk.themes import FRAME_PAD, PADX, PADY -from coretk.widgets import FrameScroll, image_chooser +from coretk.widgets import image_chooser def mac_auto(is_auto, entry): @@ -137,42 +139,57 @@ class NodeConfigDialog(Dialog): self.draw_buttons() def draw_interfaces(self): - scroll = FrameScroll(self.top, self.app, text="Interfaces") - scroll.grid(sticky="nsew") - scroll.frame.columnconfigure(0, weight=1) - scroll.frame.rowconfigure(0, weight=1) + notebook = ttk.Notebook(self.top) + notebook.grid(sticky="nsew", pady=PADY) + self.top.rowconfigure(notebook.grid_info()["row"], weight=1) + for interface in self.canvas_node.interfaces: logging.info("interface: %s", interface) - frame = ttk.LabelFrame(scroll.frame, text=interface.name, padding=FRAME_PAD) - frame.grid(sticky="ew", pady=PADY) - frame.columnconfigure(1, weight=1) - frame.columnconfigure(2, weight=1) + tab = ttk.Frame(notebook, padding=FRAME_PAD) + tab.grid(sticky="nsew", pady=PADY) + tab.columnconfigure(1, weight=1) + tab.columnconfigure(2, weight=1) + notebook.add(tab, text=interface.name) - label = ttk.Label(frame, text="MAC") - label.grid(row=0, column=0, padx=PADX, pady=PADY) + row = 0 + emane_node = self.canvas_node.has_emane_link(interface.id) + if emane_node: + emane_model = emane_node.emane.split("_")[1] + button = ttk.Button( + tab, + text=f"Configure EMANE {emane_model}", + command=lambda: self.click_emane_config(emane_model, interface.id), + ) + button.grid(row=row, sticky="ew", columnspan=3, pady=PADY) + row += 1 + + label = ttk.Label(tab, text="MAC") + label.grid(row=row, column=0, padx=PADX, pady=PADY) is_auto = tk.BooleanVar(value=True) - checkbutton = ttk.Checkbutton(frame, text="Auto?", variable=is_auto) + checkbutton = ttk.Checkbutton(tab, text="Auto?", variable=is_auto) checkbutton.var = is_auto - checkbutton.grid(row=0, column=1, padx=PADX) + checkbutton.grid(row=row, column=1, padx=PADX) mac = tk.StringVar(value=interface.mac) - entry = ttk.Entry(frame, textvariable=mac, state=tk.DISABLED) - entry.grid(row=0, column=2, sticky="ew") + entry = ttk.Entry(tab, textvariable=mac, state=tk.DISABLED) + entry.grid(row=row, column=2, sticky="ew") func = partial(mac_auto, is_auto, entry) checkbutton.config(command=func) + row += 1 - label = ttk.Label(frame, text="IPv4") - label.grid(row=1, column=0, padx=PADX, pady=PADY) + label = ttk.Label(tab, text="IPv4") + label.grid(row=row, column=0, padx=PADX, pady=PADY) ip4 = tk.StringVar(value=f"{interface.ip4}/{interface.ip4mask}") - entry = ttk.Entry(frame, textvariable=ip4) + entry = ttk.Entry(tab, textvariable=ip4) entry.bind("", self.app.validation.ip_focus_out) - entry.grid(row=1, column=1, columnspan=2, sticky="ew") + entry.grid(row=row, column=1, columnspan=2, sticky="ew") + row += 1 - label = ttk.Label(frame, text="IPv6") - label.grid(row=2, column=0, padx=PADX, pady=PADY) + label = ttk.Label(tab, text="IPv6") + label.grid(row=row, column=0, padx=PADX, pady=PADY) ip6 = tk.StringVar(value=f"{interface.ip6}/{interface.ip6mask}") - entry = ttk.Entry(frame, textvariable=ip6) + entry = ttk.Entry(tab, textvariable=ip6) entry.bind("", self.app.validation.ip_focus_out) - entry.grid(row=2, column=1, columnspan=2, sticky="ew") + entry.grid(row=row, column=1, columnspan=2, sticky="ew") self.interfaces[interface.id] = InterfaceData(is_auto, mac, ip4, ip6) @@ -188,8 +205,12 @@ class NodeConfigDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") + def click_emane_config(self, emane_model, interface_id): + dialog = EmaneModelDialog(self, self.app, self.node, emane_model, interface_id) + dialog.show() + def click_icon(self): - file_path = image_chooser(self) + file_path = image_chooser(self, ICONS_PATH) if file_path: self.image = Images.create(file_path, nodeutils.ICON_SIZE) self.image_button.config(image=self.image) diff --git a/coretk/coretk/dialogs/serviceconfiguration.py b/coretk/coretk/dialogs/serviceconfiguration.py index 0031ee31..53aca1b3 100644 --- a/coretk/coretk/dialogs/serviceconfiguration.py +++ b/coretk/coretk/dialogs/serviceconfiguration.py @@ -110,7 +110,7 @@ class ServiceConfiguration(Dialog): # draw notebook self.notebook = ttk.Notebook(self.top) - self.notebook.grid(sticky="nsew") + self.notebook.grid(sticky="nsew", pady=PADY) self.draw_tab_files() self.draw_tab_directories() self.draw_tab_startstop() @@ -192,11 +192,13 @@ class ServiceConfiguration(Dialog): tab.rowconfigure(self.service_file_data.grid_info()["row"], weight=1) if len(self.filenames) > 0: self.filename_combobox.current(0) - self.service_file_data.delete(1.0, "end") - self.service_file_data.insert( + self.service_file_data.text.delete(1.0, "end") + self.service_file_data.text.insert( "end", self.temp_service_files[self.filenames[0]] ) - self.service_file_data.bind("", self.update_temp_service_file_data) + self.service_file_data.text.bind( + "", self.update_temp_service_file_data + ) def draw_tab_directories(self): tab = ttk.Frame(self.notebook, padding=FRAME_PAD) @@ -275,14 +277,14 @@ class ServiceConfiguration(Dialog): frame.columnconfigure(1, weight=1) label = ttk.Label(frame, text="Validation Time") - label.grid(row=0, column=0, sticky="w") + label.grid(row=0, column=0, sticky="w", padx=PADX) self.validation_time_entry = ttk.Entry(frame) self.validation_time_entry.insert("end", self.validation_time) self.validation_time_entry.config(state=tk.DISABLED) - self.validation_time_entry.grid(row=0, column=1, sticky="ew") + self.validation_time_entry.grid(row=0, column=1, sticky="ew", pady=PADY) label = ttk.Label(frame, text="Validation Mode") - label.grid(row=1, column=0, sticky="w") + label.grid(row=1, column=0, sticky="w", padx=PADX) if self.validation_mode == core_pb2.ServiceValidationMode.BLOCKING: mode = "BLOCKING" elif self.validation_mode == core_pb2.ServiceValidationMode.NON_BLOCKING: @@ -294,14 +296,14 @@ class ServiceConfiguration(Dialog): ) self.validation_mode_entry.insert("end", mode) self.validation_mode_entry.config(state=tk.DISABLED) - self.validation_mode_entry.grid(row=1, column=1, sticky="ew") + self.validation_mode_entry.grid(row=1, column=1, sticky="ew", pady=PADY) label = ttk.Label(frame, text="Validation Period") - label.grid(row=2, column=0, sticky="w") + label.grid(row=2, column=0, sticky="w", padx=PADX) self.validation_period_entry = ttk.Entry( frame, state=tk.DISABLED, textvariable=tk.StringVar() ) - self.validation_period_entry.grid(row=2, column=1, sticky="ew") + self.validation_period_entry.grid(row=2, column=1, sticky="ew", pady=PADY) label_frame = ttk.LabelFrame(tab, text="Executables", padding=FRAME_PAD) label_frame.grid(sticky="nsew", pady=PADY) @@ -429,8 +431,8 @@ class ServiceConfiguration(Dialog): def display_service_file_data(self, event): combobox = event.widget filename = combobox.get() - self.service_file_data.delete(1.0, "end") - self.service_file_data.insert("end", self.temp_service_files[filename]) + 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): scrolledtext = event.widget diff --git a/coretk/coretk/graph/node.py b/coretk/coretk/graph/node.py index e9f59882..4836d2d2 100644 --- a/coretk/coretk/graph/node.py +++ b/coretk/coretk/graph/node.py @@ -246,6 +246,23 @@ class CanvasNode: dialog = NodeService(self.app.master, self.app, self) dialog.show() + def has_emane_link(self, interface_id): + result = None + for edge in self.edges: + if self.id == edge.src: + other_id = edge.dst + edge_interface_id = edge.src_interface.id + else: + other_id = edge.src + edge_interface_id = edge.dst_interface.id + if edge_interface_id != interface_id: + continue + other_node = self.canvas.nodes[other_id] + if other_node.core_node.type == NodeType.EMANE: + result = other_node.core_node + break + return result + def wireless_link_selected(self): self.canvas.context = None for canvas_nid in [ diff --git a/coretk/coretk/widgets.py b/coretk/coretk/widgets.py index 9ca96c0e..6567ca75 100644 --- a/coretk/coretk/widgets.py +++ b/coretk/coretk/widgets.py @@ -2,11 +2,9 @@ import logging import tkinter as tk from functools import partial from tkinter import filedialog, font, ttk -from tkinter.scrolledtext import ScrolledText from core.api.grpc import core_pb2 from coretk import themes -from coretk.appconfig import ICONS_PATH from coretk.themes import FRAME_PAD, PADX, PADY INT_TYPES = { @@ -208,10 +206,13 @@ class CodeFont(font.Font): super().__init__(font="TkFixedFont", color="green") -class CodeText(ScrolledText): +class CodeText(ttk.Frame): def __init__(self, master, **kwargs): - super().__init__( - master, + super().__init__(master, **kwargs) + self.rowconfigure(0, weight=1) + self.columnconfigure(0, weight=1) + self.text = tk.Text( + self, bd=0, bg="black", cursor="xterm lime lime", @@ -222,8 +223,11 @@ class CodeText(ScrolledText): selectbackground="lime", selectforeground="black", relief=tk.FLAT, - **kwargs ) + self.text.grid(row=0, column=0, sticky="nsew") + yscrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.text.yview) + yscrollbar.grid(row=0, column=1, sticky="ns") + self.text.configure(yscrollcommand=yscrollbar.set) class Spinbox(ttk.Entry): @@ -234,11 +238,11 @@ class Spinbox(ttk.Entry): self.tk.call(self._w, "set", value) -def image_chooser(parent): +def image_chooser(parent, path): return filedialog.askopenfilename( parent=parent, - initialdir=str(ICONS_PATH), - title="Select Icon", + initialdir=str(path), + title="Select", filetypes=( ("images", "*.gif *.jpg *.png *.bmp *pcx *.tga ..."), ("All Files", "*"), diff --git a/docs/install.md b/docs/install.md index 12242b4b..e10c1450 100644 --- a/docs/install.md +++ b/docs/install.md @@ -68,7 +68,7 @@ Virtual networks generally require some form of routing in order to work (e.g. t tables for routing packets from one subnet to another.) CORE builds OSPF routing protocol configurations by default when the blue router node type is used. -* [OSPF MANET Designated Routers](http://www.nrl.navy.mil/itd/ncs/products/ospf-manet) (MDR) - the Quagga routing +* [OSPF MANET Designated Routers](https://github.com/USNavalResearchLaboratory/ospf-mdr) (MDR) - the Quagga routing suite with a modified version of OSPFv3, optimized for use with mobile wireless networks. The **mdr** node type (and the MDR service) requires this variant of Quagga. @@ -77,7 +77,7 @@ suite with a modified version of OSPFv3, optimized for use with mobile wireless There is a built package which can be used. ```shell -wget https://downloads.pf.itd.nrl.navy.mil/ospf-manet/quagga-0.99.21mr2.2/quagga-mr_0.99.21mr2.2_amd64.deb +wget https://github.com/USNavalResearchLaboratory/ospf-mdr/releases/download/v0.99.21mr2.2/quagga-mr_0.99.21mr2.2_amd64.deb sudo dpkg -i quagga-mr_0.99.21mr2.2_amd64.deb ``` @@ -89,9 +89,8 @@ Requires building from source, from the latest nightly snapshot. # packages needed beyond what's normally required to build core on ubuntu sudo apt install libtool libreadline-dev autoconf -wget https://downloads.pf.itd.nrl.navy.mil/ospf-manet/nightly_snapshots/quagga-svnsnap.tgz -tar xzf quagga-svnsnap.tgz -cd quagga +git clone https://github.com/USNavalResearchLaboratory/ospf-mdr +cd ospf-mdr ./bootstrap.sh ./configure --disable-doc --enable-user=root --enable-group=root --with-cflags=-ggdb \ --sysconfdir=/usr/local/etc/quagga --enable-vtysh \ diff --git a/docs/services.md b/docs/services.md index f2a1a38a..692bcedd 100644 --- a/docs/services.md +++ b/docs/services.md @@ -312,17 +312,17 @@ Currently the Naval Research Laboratory uses this library to develop a wide vari * arouted #### NRL Installation -In order to be able to use the different protocols that NRL offers, you must first download the support library itself. You can get the source code from their [official nightly snapshots website](https://downloads.pf.itd.nrl.navy.mil/protolib/nightly_snapshots/). +In order to be able to use the different protocols that NRL offers, you must first download the support library itself. You can get the source code from their [NRL Protolib Repo](https://github.com/USNavalResearchLaboratory/protolib). #### Multi-Generator (MGEN) -Download MGEN from the [NRL MGEN nightly snapshots](https://downloads.pf.itd.nrl.navy.mil/mgen/nightly_snapshots/), unpack it and copy the protolib library into the main folder *mgen*. Execute the following commands to build the protocol. +Download MGEN from the [NRL MGEN Repo](https://github.com/USNavalResearchLaboratory/mgen), unpack it and copy the protolib library into the main folder *mgen*. Execute the following commands to build the protocol. ```shell cd mgen/makefiles make -f Makefile.{os} mgen ``` #### Neighborhood Discovery Protocol (NHDP) -Download NHDP from the [NRL NHDP nightly snapshots](https://downloads.pf.itd.nrl.navy.mil/nhdp/nightly_snapshots/). +Download NHDP from the [NRL NHDP Repo](https://github.com/USNavalResearchLaboratory/NCS-Downloads/tree/master/nhdp). ```shell sudo apt-get install libpcap-dev libboost-all-dev wget https://github.com/protocolbuffers/protobuf/releases/download/v3.8.0/protoc-3.8.0-linux-x86_64.zip @@ -339,14 +339,14 @@ make -f Makefile.{os} ``` #### Simplified Multicast Forwarding (SMF) -Download SMF from the [NRL SMF nightly snapshot](https://downloads.pf.itd.nrl.navy.mil/smf/nightly_snapshots/) , unpack it and place the protolib library inside the *smf* main folder. +Download SMF from the [NRL SMF Repo](https://github.com/USNavalResearchLaboratory/nrlsmf) , unpack it and place the protolib library inside the *smf* main folder. ```shell cd mgen/makefiles make -f Makefile.{os} ``` #### Optimized Link State Routing Protocol (OLSR) -To install the OLSR protocol, download their source code from their [nightly snapshots](https://downloads.pf.itd.nrl.navy.mil/olsr/nightly_snapshots/nrlolsr-svnsnap.tgz). Unpack it and place the previously downloaded protolib library inside the *nrlolsr* main directory. Then execute the following commands: +To install the OLSR protocol, download their source code from their [NRL OLSR Repo](https://github.com/USNavalResearchLaboratory/nrlolsr). Unpack it and place the previously downloaded protolib library inside the *nrlolsr* main directory. Then execute the following commands: ```shell cd ./unix make -f Makefile.{os} diff --git a/docs/usage.md b/docs/usage.md index 7f8cf672..02e1295f 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -650,7 +650,7 @@ Any time you edit the topology file, you will need to stop the emulation if it were running and reload the file. -The **.xml** [file schema is specified by NRL](http://www.nrl.navy.mil/itd/ncs/products/mnmtools) and there are two versions to date: +The **.xml** [file schema is specified by NRL](https://github.com/USNavalResearchLaboratory/NCS-Downloads/blob/master/mnmtools/EmulationScriptSchemaDescription.pdf) and there are two versions to date: version 0.0 and version 1.0, with 1.0 as the current default. CORE can open either XML version. However, the xmlfilever line in **/etc/core/core.conf** controls the version of the XML file