From f72d0d8a696266bd0ed4c3642e09682875d3c309 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 20 Jul 2020 23:50:01 -0700 Subject: [PATCH 001/598] initial classes to help support creating layers for toggling visibility on the canvas for associated items --- daemon/core/gui/dialogs/layers.py | 60 +++++++++++++++++++++++++++++++ daemon/core/gui/dialogs/simple.py | 48 +++++++++++++++++++++++++ daemon/core/gui/graph/graph.py | 2 ++ daemon/core/gui/graph/layers.py | 58 ++++++++++++++++++++++++++++++ daemon/core/gui/toolbar.py | 8 +++++ 5 files changed, 176 insertions(+) create mode 100644 daemon/core/gui/dialogs/layers.py create mode 100644 daemon/core/gui/dialogs/simple.py create mode 100644 daemon/core/gui/graph/layers.py diff --git a/daemon/core/gui/dialogs/layers.py b/daemon/core/gui/dialogs/layers.py new file mode 100644 index 00000000..fc72899a --- /dev/null +++ b/daemon/core/gui/dialogs/layers.py @@ -0,0 +1,60 @@ +import tkinter as tk +from tkinter import messagebox, ttk +from typing import TYPE_CHECKING, Optional + +from core.gui.dialogs.dialog import Dialog +from core.gui.dialogs.simple import SimpleStringDialog +from core.gui.themes import PADX, PADY +from core.gui.widgets import ListboxScroll + +if TYPE_CHECKING: + from core.gui.app import Application + + +class LayersDialog(Dialog): + def __init__(self, app: "Application") -> None: + super().__init__(app, "Canvas Layers", modal=False) + self.list: Optional[ListboxScroll] = None + self.draw() + + def draw(self) -> None: + self.top.columnconfigure(0, weight=1) + self.list = ListboxScroll(self.top) + self.list.grid(sticky=tk.EW, pady=PADY) + for name in self.app.canvas.layers.names(): + self.list.listbox.insert(tk.END, name) + frame = ttk.Frame(self.top) + frame.grid(sticky=tk.EW) + for i in range(3): + frame.columnconfigure(i, weight=1) + button = ttk.Button(frame, text="Add", command=self.click_add) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) + button = ttk.Button(frame, text="Delete", command=self.click_delete) + button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) + button = ttk.Button(frame, text="Toggle", command=self.click_toggle) + button.grid(row=0, column=2, sticky=tk.EW) + + def click_add(self): + name = SimpleStringDialog(self, self.app, "Add Layer", "Layer Name").ask() + if name: + result = self.app.canvas.layers.add_layer(name) + if result: + self.list.listbox.insert(tk.END, name) + else: + messagebox.showerror( + "Add Layer", f"Duplicate Layer: {name}", parent=self + ) + + def click_delete(self): + selection = self.list.listbox.curselection() + if not selection: + return + name = self.list.listbox.get(selection) + print(name) + + def click_toggle(self): + selection = self.list.listbox.curselection() + if not selection: + return + name = self.list.listbox.get(selection) + print(name) diff --git a/daemon/core/gui/dialogs/simple.py b/daemon/core/gui/dialogs/simple.py new file mode 100644 index 00000000..0068f398 --- /dev/null +++ b/daemon/core/gui/dialogs/simple.py @@ -0,0 +1,48 @@ +import tkinter as tk +from tkinter import ttk +from typing import TYPE_CHECKING, Optional + +from core.gui.dialogs.dialog import Dialog +from core.gui.themes import PADX, PADY + +if TYPE_CHECKING: + from core.gui.app import Application + + +class SimpleStringDialog(Dialog): + def __init__( + self, master: tk.BaseWidget, app: "Application", title: str, prompt: str + ): + super().__init__(app, title, master=master) + self.prompt: str = prompt + self.value = tk.StringVar() + self.entry: Optional[ttk.Entry] = None + self.canceled = False + self.draw() + + def draw(self) -> None: + self.top.columnconfigure(0, weight=1) + label = ttk.Label(self.top, text=self.prompt) + label.grid(sticky=tk.EW, pady=PADY) + entry = ttk.Entry(self.top, textvariable=self.value) + entry.grid(stick=tk.EW, pady=PADY) + entry.focus_set() + frame = ttk.Frame(self.top) + frame.grid(sticky=tk.EW) + for i in range(2): + frame.columnconfigure(i, weight=1) + button = ttk.Button(frame, text="Submit", command=self.destroy) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) + button = ttk.Button(frame, text="Cancel", command=self.click_cancel) + button.grid(row=0, column=1, sticky=tk.EW) + + def click_cancel(self): + self.canceled = True + self.destroy() + + def ask(self) -> Optional[str]: + self.show() + if self.canceled: + return None + else: + return self.value.get() diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 9cb3b109..b4d89d88 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -25,6 +25,7 @@ from core.gui.graph.edges import ( create_edge_token, ) from core.gui.graph.enums import GraphMode, ScaleOption +from core.gui.graph.layers import CanvasLayers from core.gui.graph.node import CanvasNode from core.gui.graph.shape import Shape from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker @@ -62,6 +63,7 @@ class CanvasGraph(tk.Canvas): super().__init__(master, highlightthickness=0, background="#cccccc") self.app: "Application" = app self.core: "CoreClient" = core + self.layers = CanvasLayers(self) self.mode: GraphMode = GraphMode.SELECT self.annotation_type: Optional[ShapeType] = None self.selection: Dict[int, int] = {} diff --git a/daemon/core/gui/graph/layers.py b/daemon/core/gui/graph/layers.py new file mode 100644 index 00000000..ba8031b0 --- /dev/null +++ b/daemon/core/gui/graph/layers.py @@ -0,0 +1,58 @@ +import tkinter as tk +from typing import TYPE_CHECKING, Dict, Iterable, Set + +if TYPE_CHECKING: + from core.gui.graph.graph import CanvasGraph + + +class CanvasLayers: + def __init__(self, canvas: "CanvasGraph"): + self.canvas: "CanvasGraph" = canvas + self.layers: Dict[str, Set[int]] = {} + self.hidden: Set[str] = set() + + def names(self) -> Iterable[str]: + return self.layers.keys() + + def add_layer(self, name: str) -> bool: + if name in self.layers: + return False + else: + self.layers[name] = set() + return True + + def delete_layer(self, name: str) -> None: + items = self.layers.pop(name, set()) + hidden_items = self.all_hidden() + items -= hidden_items + self.canvas.config(items, state=tk.NORMAL) + + def add_item(self, name: str, item: int) -> None: + if name in self.layers: + self.layers[name].add(item) + if name in self.hidden: + self.canvas.config(item, state=tk.HIDDEN) + + def delete_item(self, name: str, item: int) -> None: + if name in self.layers: + self.layers[name].remove(item) + hidden_items = self.all_hidden() + if item not in hidden_items: + self.canvas.config(item, state=tk.NORMAL) + + def toggle_layer(self, name: str) -> None: + items = self.layers[name] + if name in self.hidden: + self.hidden.remove(name) + hidden_items = self.all_hidden() + items -= hidden_items + self.canvas.config(items, state=tk.NORMAL) + else: + self.hidden.add(name) + self.canvas.config(items, state=tk.HIDDEN) + + def all_hidden(self) -> Set[int]: + items = set() + for name in self.hidden: + items |= self.layers[name] + return items diff --git a/daemon/core/gui/toolbar.py b/daemon/core/gui/toolbar.py index 406a88ca..0dd3c083 100644 --- a/daemon/core/gui/toolbar.py +++ b/daemon/core/gui/toolbar.py @@ -9,6 +9,7 @@ from PIL.ImageTk import PhotoImage from core.api.grpc import core_pb2 from core.gui.dialogs.colorpicker import ColorPickerDialog +from core.gui.dialogs.layers import LayersDialog from core.gui.dialogs.runtool import RunToolDialog from core.gui.graph import tags from core.gui.graph.enums import GraphMode @@ -237,6 +238,9 @@ class Toolbar(ttk.Frame): "Annotation Tools", radio=True, ) + self.design_frame.create_button( + self.annotation_enum, self.show_layers, "Layers" + ) def draw_runtime_frame(self) -> None: self.runtime_frame = ButtonBar(self, self.app) @@ -256,6 +260,10 @@ class Toolbar(ttk.Frame): ImageEnum.RUN, self.click_run_button, "Run Tool" ) + def show_layers(self) -> None: + dialog = LayersDialog(self.app) + dialog.show() + def draw_node_picker(self) -> None: self.hide_marker() self.app.canvas.mode = GraphMode.NODE From 1f55432ba2e383e333a5289efc4dac7d25191614 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 21 Jul 2020 09:16:00 -0700 Subject: [PATCH 002/598] pygui: added layer menu to nodes, simple toggle support for nodes alone --- daemon/core/gui/dialogs/layers.py | 48 ++++++++++++++++++++----------- daemon/core/gui/dialogs/simple.py | 1 + daemon/core/gui/graph/layers.py | 46 +++++++++++++++++++++++++---- daemon/core/gui/graph/node.py | 4 +++ 4 files changed, 77 insertions(+), 22 deletions(-) diff --git a/daemon/core/gui/dialogs/layers.py b/daemon/core/gui/dialogs/layers.py index fc72899a..66ebcce0 100644 --- a/daemon/core/gui/dialogs/layers.py +++ b/daemon/core/gui/dialogs/layers.py @@ -15,6 +15,10 @@ class LayersDialog(Dialog): def __init__(self, app: "Application") -> None: super().__init__(app, "Canvas Layers", modal=False) self.list: Optional[ListboxScroll] = None + self.selection: Optional[str] = None + self.selection_index: Optional[int] = None + self.delete_button: Optional[ttk.Button] = None + self.toggle_button: Optional[ttk.Button] = None self.draw() def draw(self) -> None: @@ -23,18 +27,23 @@ class LayersDialog(Dialog): self.list.grid(sticky=tk.EW, pady=PADY) for name in self.app.canvas.layers.names(): self.list.listbox.insert(tk.END, name) + self.list.listbox.bind("<>", self.list_select) frame = ttk.Frame(self.top) frame.grid(sticky=tk.EW) for i in range(3): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Add", command=self.click_add) button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) - button = ttk.Button(frame, text="Delete", command=self.click_delete) - button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) - button = ttk.Button(frame, text="Toggle", command=self.click_toggle) - button.grid(row=0, column=2, sticky=tk.EW) + self.delete_button = ttk.Button( + frame, text="Delete", command=self.click_delete, state=tk.DISABLED + ) + self.delete_button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) + self.toggle_button = ttk.Button( + frame, text="Toggle", command=self.click_toggle, state=tk.DISABLED + ) + self.toggle_button.grid(row=0, column=2, sticky=tk.EW) - def click_add(self): + def click_add(self) -> None: name = SimpleStringDialog(self, self.app, "Add Layer", "Layer Name").ask() if name: result = self.app.canvas.layers.add_layer(name) @@ -45,16 +54,21 @@ class LayersDialog(Dialog): "Add Layer", f"Duplicate Layer: {name}", parent=self ) - def click_delete(self): - selection = self.list.listbox.curselection() - if not selection: - return - name = self.list.listbox.get(selection) - print(name) + def list_select(self, event: tk.Event) -> None: + self.selection_index = self.list.listbox.curselection() + if not self.selection_index: + self.selection = None + state = tk.DISABLED + else: + self.selection = self.list.listbox.get(self.selection_index) + state = tk.NORMAL + self.toggle_button.config(state=state) + self.delete_button.config(state=state) - def click_toggle(self): - selection = self.list.listbox.curselection() - if not selection: - return - name = self.list.listbox.get(selection) - print(name) + def click_delete(self) -> None: + self.app.canvas.layers.delete_layer(self.selection) + self.list.listbox.delete(self.selection_index) + self.list.listbox.event_generate("<>") + + def click_toggle(self) -> None: + self.app.canvas.layers.toggle_layer(self.selection) diff --git a/daemon/core/gui/dialogs/simple.py b/daemon/core/gui/dialogs/simple.py index 0068f398..2d893e64 100644 --- a/daemon/core/gui/dialogs/simple.py +++ b/daemon/core/gui/dialogs/simple.py @@ -14,6 +14,7 @@ class SimpleStringDialog(Dialog): self, master: tk.BaseWidget, app: "Application", title: str, prompt: str ): super().__init__(app, title, master=master) + self.bind("", lambda e: self.destroy()) self.prompt: str = prompt self.value = tk.StringVar() self.entry: Optional[ttk.Entry] = None diff --git a/daemon/core/gui/graph/layers.py b/daemon/core/gui/graph/layers.py index ba8031b0..b57899fe 100644 --- a/daemon/core/gui/graph/layers.py +++ b/daemon/core/gui/graph/layers.py @@ -1,10 +1,40 @@ import tkinter as tk +from functools import partial from typing import TYPE_CHECKING, Dict, Iterable, Set +from core.gui import themes + if TYPE_CHECKING: + from core.gui.app import Application from core.gui.graph.graph import CanvasGraph +class LayersMenu(tk.Menu): + def __init__(self, master: tk.BaseWidget, app: "Application", item: int) -> None: + super().__init__(master) + themes.style_menu(self) + self.app: "Application" = app + self.item: int = item + self.buttons: Dict[str, tk.BooleanVar] = {} + self.draw() + + def draw(self) -> None: + for name in self.app.canvas.layers.names(): + value = self.app.canvas.layers.in_layer(name, self.item) + var = tk.BooleanVar(value=value) + self.buttons[name] = var + self.add_checkbutton( + label=name, variable=var, command=partial(self.click_layer, name) + ) + + def click_layer(self, name): + value = self.buttons[name].get() + if value: + self.app.canvas.layers.add_item(name, self.item) + else: + self.app.canvas.layers.delete_item(name, self.item) + + class CanvasLayers: def __init__(self, canvas: "CanvasGraph"): self.canvas: "CanvasGraph" = canvas @@ -25,20 +55,24 @@ class CanvasLayers: items = self.layers.pop(name, set()) hidden_items = self.all_hidden() items -= hidden_items - self.canvas.config(items, state=tk.NORMAL) + for item in items: + self.canvas.itemconfig(item, state=tk.NORMAL) + + def in_layer(self, name: str, item: int) -> bool: + return item in self.layers.get(name, set()) def add_item(self, name: str, item: int) -> None: if name in self.layers: self.layers[name].add(item) if name in self.hidden: - self.canvas.config(item, state=tk.HIDDEN) + self.canvas.itemconfig(item, state=tk.HIDDEN) def delete_item(self, name: str, item: int) -> None: if name in self.layers: self.layers[name].remove(item) hidden_items = self.all_hidden() if item not in hidden_items: - self.canvas.config(item, state=tk.NORMAL) + self.canvas.itemconfig(item, state=tk.NORMAL) def toggle_layer(self, name: str) -> None: items = self.layers[name] @@ -46,10 +80,12 @@ class CanvasLayers: self.hidden.remove(name) hidden_items = self.all_hidden() items -= hidden_items - self.canvas.config(items, state=tk.NORMAL) + for item in items: + self.canvas.itemconfig(item, state=tk.NORMAL) else: self.hidden.add(name) - self.canvas.config(items, state=tk.HIDDEN) + for item in items: + self.canvas.itemconfig(item, state=tk.HIDDEN) def all_hidden(self) -> Set[int]: items = set() diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index 7b5cd2f3..189e9263 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -19,6 +19,7 @@ from core.gui.dialogs.wlanconfig import WlanConfigDialog from core.gui.frames.node import NodeInfoFrame from core.gui.graph import tags from core.gui.graph.edges import CanvasEdge, CanvasWirelessEdge +from core.gui.graph.layers import LayersMenu from core.gui.graph.tooltip import CanvasTooltip from core.gui.images import ImageEnum from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils @@ -271,6 +272,9 @@ class CanvasNode: edit_menu.add_command(label="Copy", command=self.canvas_copy) edit_menu.add_command(label="Delete", command=self.canvas_delete) self.context.add_cascade(label="Edit", menu=edit_menu) + + layer_menu = LayersMenu(self.context, self.app, self.id) + self.context.add_cascade(label="Layer", menu=layer_menu) self.context.tk_popup(event.x_root, event.y_root) def click_cut(self) -> None: From d5d5da72560c866e2450640e04cb9e7488d9e9b2 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 21 Jul 2020 10:08:12 -0700 Subject: [PATCH 003/598] bumped version to 7.0.0 --- configure.ac | 2 +- daemon/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 10d30c20..60f6709e 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. # this defines the CORE version number, must be static for AC_INIT -AC_INIT(core, 6.5.0) +AC_INIT(core, 7.0.0) # autoconf and automake initialization AC_CONFIG_SRCDIR([netns/version.h.in]) diff --git a/daemon/pyproject.toml b/daemon/pyproject.toml index 1fdc9d1a..b75f1ee3 100644 --- a/daemon/pyproject.toml +++ b/daemon/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "core" -version = "6.6.0" +version = "7.0.0" description = "CORE Common Open Research Emulator" authors = ["Boeing Research and Technology"] license = "BSD-2-Clause" From 45bfa9fdadf0b95da3d4755c1a682d34be8319ce Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 21 Jul 2020 16:52:17 -0700 Subject: [PATCH 004/598] small tweaks to docs --- docs/devguide.md | 2 +- docs/install.md | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/devguide.md b/docs/devguide.md index 9b9d61c8..ba34a211 100644 --- a/docs/devguide.md +++ b/docs/devguide.md @@ -62,7 +62,7 @@ core-pygui core-gui # run mocked unit tests -cd $REPO +cd inv test-mock ``` diff --git a/docs/install.md b/docs/install.md index 12d47802..604ac509 100644 --- a/docs/install.md +++ b/docs/install.md @@ -58,7 +58,7 @@ before proceeding to install. Previous install was built from source: ```shell -cd $REPO +cd sudo make uninstall make clean ./bootstrap.sh clean @@ -138,7 +138,7 @@ installed virtual environment. There is an invoke task to help with this case. ```shell -cd $REPO +cd inv -h run Usage: inv[oke] [--core-opts] run [--options] [other tasks here ...] @@ -153,7 +153,7 @@ Options: Another way would be to enable the core virtual environment shell. Which would allow you to run scripts in a more **normal** way. ```shell -cd $REPO/daemon +cd /daemon poetry shell python run /path/to/script.py ``` @@ -168,7 +168,7 @@ which attempts to build EMANE from source, but has issue on systems with older protobuf-compilers. ```shell -cd $REPO +cd inv install-emane ``` @@ -180,8 +180,8 @@ bindings into the core virtual environment. The following would install the EMANE python bindings after being successfully built. ```shell -cd $REPO/daemon -poetry run pip install $EMANE_REPO/src/python +cd /daemon +poetry run pip install /src/python ``` ## Using Invoke Tasks From 165e404184e92858280b16eb2cd013651ad75abe Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 22 Jul 2020 12:49:11 -0700 Subject: [PATCH 005/598] added example dockerfile and build command to readme --- daemon/examples/docker/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/daemon/examples/docker/README.md b/daemon/examples/docker/README.md index 3c2b1372..17c6cb90 100644 --- a/daemon/examples/docker/README.md +++ b/daemon/examples/docker/README.md @@ -44,3 +44,18 @@ newgrp docker This directory provides a few small examples creating Docker nodes and linking them to themselves or with standard CORE nodes. + +Images used by nodes need to have networking tools installed for CORE to automate +setup and configuration of the container. + +Example Dockerfile: +``` +FROM ubuntu:latest +RUN apt-get update +RUN apt-get install -y iproute2 ethtool +``` + +Build image: +```shell +sudo docker build -t . +``` From e34002b851ec51fd74db8e56e6885f98aa8e41d9 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 22 Jul 2020 17:18:35 -0700 Subject: [PATCH 006/598] pygui: added option to launch core-pygui into a specific session using an id --- daemon/core/gui/app.py | 4 ++-- daemon/core/gui/coreclient.py | 24 ++++++++++++++++++------ daemon/core/gui/dialogs/error.py | 25 ++++++++++--------------- daemon/examples/grpc/switch.py | 4 ++-- daemon/scripts/core-pygui | 3 ++- 5 files changed, 34 insertions(+), 26 deletions(-) diff --git a/daemon/core/gui/app.py b/daemon/core/gui/app.py index e0121d14..176b31e3 100644 --- a/daemon/core/gui/app.py +++ b/daemon/core/gui/app.py @@ -26,7 +26,7 @@ HEIGHT: int = 800 class Application(ttk.Frame): - def __init__(self, proxy: bool) -> None: + def __init__(self, proxy: bool, session_id: int = None) -> None: super().__init__() # load node icons NodeUtils.setup() @@ -56,7 +56,7 @@ class Application(ttk.Frame): self.core: CoreClient = CoreClient(self, proxy) self.setup_app() self.draw() - self.core.setup() + self.core.setup(session_id) def setup_scaling(self) -> None: self.fonts_size = {name: font.nametofont(name)["size"] for name in font.names()} diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 52023e14..26a5a390 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -473,7 +473,7 @@ class CoreClient: except grpc.RpcError as e: self.app.show_grpc_exception("Delete Session Error", e) - def setup(self) -> None: + def setup(self, session_id: int = None) -> None: """ Query sessions, if there exist any, prompt whether to join one """ @@ -494,14 +494,26 @@ class CoreClient: ) group_services.add(service.name) - # if there are no sessions, create a new session, else join a session + # join provided session, create new session, or show dialog to select an + # existing session response = self.client.get_sessions() sessions = response.sessions - if len(sessions) == 0: - self.create_new_session() + if session_id: + session_ids = set(x.id for x in sessions) + if session_id not in session_ids: + dialog = ErrorDialog( + self.app, "Join Session Error", f"{session_id} does not exist" + ) + dialog.show() + self.app.close() + else: + self.join_session(session_id) else: - dialog = SessionsDialog(self.app, True) - dialog.show() + if not sessions: + self.create_new_session() + else: + dialog = SessionsDialog(self.app, True) + dialog.show() except grpc.RpcError as e: logging.exception("core setup error") dialog = ErrorDialog(self.app, "Setup Error", e.details()) diff --git a/daemon/core/gui/dialogs/error.py b/daemon/core/gui/dialogs/error.py index 7fb81077..9d215e82 100644 --- a/daemon/core/gui/dialogs/error.py +++ b/daemon/core/gui/dialogs/error.py @@ -1,9 +1,10 @@ +import tkinter as tk from tkinter import ttk from typing import TYPE_CHECKING, Optional from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum, Images -from core.gui.themes import FRAME_PAD, PADX, PADY +from core.gui.themes import PADY from core.gui.widgets import CodeText if TYPE_CHECKING: @@ -21,21 +22,15 @@ class ErrorDialog(Dialog): def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(1, weight=1) - - frame = ttk.Frame(self.top, padding=FRAME_PAD) - frame.grid(pady=PADY, sticky="ew") - frame.columnconfigure(1, weight=1) - image = Images.get(ImageEnum.ERROR, 36) - label = ttk.Label(frame, image=image) + image = Images.get(ImageEnum.ERROR, 24) + label = ttk.Label( + self.top, text=self.title, image=image, compound=tk.LEFT, anchor=tk.CENTER + ) label.image = image - label.grid(row=0, column=0, padx=PADX) - label = ttk.Label(frame, text=self.title) - label.grid(row=0, column=1, sticky="ew") - + label.grid(sticky=tk.EW, pady=PADY) self.error_message = CodeText(self.top) self.error_message.text.insert("1.0", self.details) - self.error_message.text.config(state="disabled") - self.error_message.grid(sticky="nsew", pady=PADY) - + self.error_message.text.config(state=tk.DISABLED) + self.error_message.grid(sticky=tk.NSEW, pady=PADY) button = ttk.Button(self.top, text="Close", command=lambda: self.destroy()) - button.grid(sticky="ew") + button.grid(sticky=tk.EW) diff --git a/daemon/examples/grpc/switch.py b/daemon/examples/grpc/switch.py index 1ed7c684..79a4e621 100644 --- a/daemon/examples/grpc/switch.py +++ b/daemon/examples/grpc/switch.py @@ -40,14 +40,14 @@ def main(): # create node one position = Position(x=100, y=100) - node1 = Node(type=NodeType.DEFAULT, position=position) + node1 = Node(type=NodeType.DEFAULT, position=position, model="PC") response = core.add_node(session_id, node1) logging.info("created node: %s", response) node1_id = response.node_id # create node two position = Position(x=300, y=100) - node2 = Node(type=NodeType.DEFAULT, position=position) + node2 = Node(type=NodeType.DEFAULT, position=position, model="PC") response = core.add_node(session_id, node2) logging.info("created node: %s", response) node2_id = response.node_id diff --git a/daemon/scripts/core-pygui b/daemon/scripts/core-pygui index 46860ce9..888f4171 100755 --- a/daemon/scripts/core-pygui +++ b/daemon/scripts/core-pygui @@ -13,6 +13,7 @@ if __name__ == "__main__": parser.add_argument("-l", "--level", choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], default="INFO", help="logging level") parser.add_argument("-p", "--proxy", action="store_true", help="enable proxy") + parser.add_argument("-s", "--session", type=int, help="session id to join") args = parser.parse_args() # check home directory exists and create if necessary @@ -28,5 +29,5 @@ if __name__ == "__main__": # start app Images.load_all() - app = Application(args.proxy) + app = Application(args.proxy, args.session) app.mainloop() From f8d862a296d995b242e86a1decb25d90587f5f6e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 22 Jul 2020 19:19:22 -0700 Subject: [PATCH 007/598] grpc/pygui: added grpc alert api, updated pygui to better handle and display alerts --- daemon/core/api/grpc/client.py | 17 +++++++++++ daemon/core/api/grpc/server.py | 16 ++++++++++- daemon/core/gui/coreclient.py | 2 +- daemon/core/gui/dialogs/alerts.py | 13 +++++---- daemon/core/gui/statusbar.py | 25 ++++++++++++++-- daemon/core/gui/themes.py | 41 +++++++++++++-------------- daemon/proto/core/api/grpc/core.proto | 14 +++++++++ 7 files changed, 97 insertions(+), 31 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 3e974233..aacfa4f6 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -445,6 +445,23 @@ class CoreGrpcClient: ) return self.stub.AddSessionServer(request) + def alert( + self, + session_id: int, + level: core_pb2.ExceptionLevel, + source: str, + text: str, + node_id: int = None, + ) -> core_pb2.SessionAlertResponse: + request = core_pb2.SessionAlertRequest( + session_id=session_id, + level=level, + source=source, + text=text, + node_id=node_id, + ) + return self.stub.SessionAlert(request) + def events( self, session_id: int, diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index da2d53c3..4c204845 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -109,7 +109,12 @@ from core.api.grpc.wlan_pb2 import ( ) from core.emulator.coreemu import CoreEmu from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions -from core.emulator.enumerations import EventTypes, LinkTypes, MessageFlags +from core.emulator.enumerations import ( + EventTypes, + ExceptionLevels, + LinkTypes, + MessageFlags, +) from core.emulator.session import NT, Session from core.errors import CoreCommandError, CoreError from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility @@ -584,6 +589,15 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): session.distributed.add_server(request.name, request.host) return core_pb2.AddSessionServerResponse(result=True) + def SessionAlert( + self, request: core_pb2.SessionAlertRequest, context: ServicerContext + ) -> core_pb2.SessionAlertResponse: + session = self.get_session(request.session_id, context) + level = ExceptionLevels(request.level) + node_id = request.node_id if request.node_id else None + session.exception(level, request.source, request.text, node_id) + return core_pb2.SessionAlertResponse(result=True) + def Events(self, request: core_pb2.EventsRequest, context: ServicerContext) -> None: session = self.get_session(request.session_id, context) event_types = set(request.events) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 26a5a390..8474b3cb 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -271,7 +271,7 @@ class CoreClient: def handle_exception_event(self, event: ExceptionEvent) -> None: logging.info("exception event: %s", event) - self.app.statusbar.core_alarms.append(event) + self.app.statusbar.add_alert(event) def join_session(self, session_id: int, query_location: bool = True) -> None: logging.info("join session(%s)", session_id) diff --git a/daemon/core/gui/dialogs/alerts.py b/daemon/core/gui/dialogs/alerts.py index 00ef1e8c..8e0aa02e 100644 --- a/daemon/core/gui/dialogs/alerts.py +++ b/daemon/core/gui/dialogs/alerts.py @@ -52,6 +52,7 @@ class AlertsDialog(Dialog): for alarm in self.app.statusbar.core_alarms: exception = alarm.exception_event level_name = ExceptionLevel.Enum.Name(exception.level) + node_id = exception.node_id if exception.node_id else "" insert_id = self.tree.insert( "", tk.END, @@ -60,7 +61,7 @@ class AlertsDialog(Dialog): exception.date, level_name, alarm.session_id, - exception.node_id, + node_id, exception.source, ), tags=(level_name,), @@ -98,15 +99,17 @@ class AlertsDialog(Dialog): button.grid(row=0, column=1, sticky="ew") def reset_alerts(self) -> None: - self.codetext.text.delete("1.0", tk.END) + self.codetext.text.config(state=tk.NORMAL) + self.codetext.text.delete(1.0, tk.END) + self.codetext.text.config(state=tk.DISABLED) for item in self.tree.get_children(): self.tree.delete(item) - self.app.statusbar.core_alarms.clear() + self.app.statusbar.clear_alerts() def click_select(self, event: tk.Event) -> None: current = self.tree.selection()[0] alarm = self.alarm_map[current] self.codetext.text.config(state=tk.NORMAL) - self.codetext.text.delete("1.0", "end") - self.codetext.text.insert("1.0", alarm.exception_event.text) + self.codetext.text.delete(1.0, tk.END) + self.codetext.text.insert(1.0, alarm.exception_event.text) self.codetext.text.config(state=tk.DISABLED) diff --git a/daemon/core/gui/statusbar.py b/daemon/core/gui/statusbar.py index 2b597b63..67da0efa 100644 --- a/daemon/core/gui/statusbar.py +++ b/daemon/core/gui/statusbar.py @@ -5,7 +5,7 @@ import tkinter as tk from tkinter import ttk from typing import TYPE_CHECKING, List, Optional -from core.api.grpc.core_pb2 import ExceptionEvent +from core.api.grpc.core_pb2 import ExceptionEvent, ExceptionLevel from core.gui.dialogs.alerts import AlertsDialog from core.gui.themes import Styles @@ -22,6 +22,7 @@ class StatusBar(ttk.Frame): self.zoom: Optional[ttk.Label] = None self.cpu_usage: Optional[ttk.Label] = None self.alerts_button: Optional[ttk.Button] = None + self.alert_style = Styles.no_alert self.running: bool = False self.core_alarms: List[ExceptionEvent] = [] self.draw() @@ -60,10 +61,30 @@ class StatusBar(ttk.Frame): self.cpu_usage.grid(row=0, column=2, sticky="ew") self.alerts_button = ttk.Button( - self, text="Alerts", command=self.click_alerts, style=Styles.green_alert + self, text="Alerts", command=self.click_alerts, style=self.alert_style ) self.alerts_button.grid(row=0, column=3, sticky="ew") + def add_alert(self, event: ExceptionEvent) -> None: + self.core_alarms.append(event) + level = event.exception_event.level + self._set_alert_style(level) + label = f"Alerts ({len(self.core_alarms)})" + self.alerts_button.config(text=label, style=self.alert_style) + + def _set_alert_style(self, level: ExceptionLevel) -> None: + if level in [ExceptionLevel.FATAL, ExceptionLevel.ERROR]: + self.alert_style = Styles.red_alert + elif level == ExceptionLevel.WARNING and self.alert_style != Styles.red_alert: + self.alert_style = Styles.yellow_alert + elif self.alert_style == Styles.no_alert: + self.alert_style = Styles.green_alert + + def clear_alerts(self): + self.core_alarms.clear() + self.alert_style = Styles.no_alert + self.alerts_button.config(text="Alerts", style=self.alert_style) + def click_alerts(self) -> None: dialog = AlertsDialog(self.app) dialog.show() diff --git a/daemon/core/gui/themes.py b/daemon/core/gui/themes.py index 93a0a599..45b109f0 100644 --- a/daemon/core/gui/themes.py +++ b/daemon/core/gui/themes.py @@ -14,6 +14,7 @@ class Styles: tooltip_frame: str = "Tooltip.TFrame" service_checkbutton: str = "Service.TCheckbutton" picker_button: str = "Picker.TButton" + no_alert: str = "NAlert.TButton" green_alert: str = "GAlert.TButton" red_alert: str = "RAlert.TButton" yellow_alert: str = "YAlert.TButton" @@ -175,33 +176,29 @@ def style_listbox(widget: tk.Widget) -> None: ) +def _alert_style(style: ttk.Style, name: str, background: str): + style.configure( + name, + background=background, + padding=0, + relief=tk.RIDGE, + borderwidth=1, + font="TkDefaultFont", + foreground="black", + highlightbackground="white", + ) + style.map(name, background=[("!active", background), ("active", "white")]) + + def theme_change(event: tk.Event) -> None: style = ttk.Style() style.configure(Styles.picker_button, font="TkSmallCaptionFont") style.configure( - Styles.green_alert, - background="green", - padding=0, - relief=tk.RIDGE, - borderwidth=1, - font="TkDefaultFont", - ) - style.configure( - Styles.yellow_alert, - background="yellow", - padding=0, - relief=tk.RIDGE, - borderwidth=1, - font="TkDefaultFont", - ) - style.configure( - Styles.red_alert, - background="red", - padding=0, - relief=tk.RIDGE, - borderwidth=1, - font="TkDefaultFont", + Styles.no_alert, padding=0, relief=tk.RIDGE, borderwidth=1, font="TkDefaultFont" ) + _alert_style(style, Styles.green_alert, "green") + _alert_style(style, Styles.yellow_alert, "yellow") + _alert_style(style, Styles.red_alert, "red") def scale_fonts(fonts_size: Dict[str, int], scale: float) -> None: diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 5ca4812c..eb889d14 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -43,6 +43,8 @@ service CoreApi { } rpc AddSessionServer (AddSessionServerRequest) returns (AddSessionServerResponse) { } + rpc SessionAlert (SessionAlertRequest) returns (SessionAlertResponse) { + } // streams rpc Events (EventsRequest) returns (stream Event) { @@ -318,6 +320,18 @@ message AddSessionServerResponse { bool result = 1; } +message SessionAlertRequest { + int32 session_id = 1; + ExceptionLevel.Enum level = 2; + string source = 3; + string text = 4; + int32 node_id = 5; +} + +message SessionAlertResponse { + bool result = 1; +} + message EventsRequest { int32 session_id = 1; repeated EventType.Enum events = 2; From 3544d004317b5ddbc5053e8f38c09e005fd582e7 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 22 Jul 2020 21:57:05 -0700 Subject: [PATCH 008/598] pygui: implemented cpu usage monitor to status bar --- daemon/core/gui/graph/graph.py | 3 +- daemon/core/gui/statusbar.py | 64 ++++++++++++++++++++++++++++------ 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 9cb3b109..56a31c3f 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -590,8 +590,7 @@ class CanvasGraph(tk.Canvas): ) logging.debug("ratio: %s", self.ratio) logging.debug("offset: %s", self.offset) - zoom_label = f"{self.ratio * 100:.0f}%" - self.app.statusbar.zoom.config(text=zoom_label) + self.app.statusbar.set_zoom(self.ratio) if self.wallpaper: self.redraw_wallpaper() diff --git a/daemon/core/gui/statusbar.py b/daemon/core/gui/statusbar.py index 67da0efa..e9fc03b2 100644 --- a/daemon/core/gui/statusbar.py +++ b/daemon/core/gui/statusbar.py @@ -1,7 +1,10 @@ """ status bar """ +import sched import tkinter as tk +from pathlib import Path +from threading import Thread from tkinter import ttk from typing import TYPE_CHECKING, List, Optional @@ -13,6 +16,41 @@ if TYPE_CHECKING: from core.gui.app import Application +class CpuUsage: + def __init__(self, statusbar: "StatusBar") -> None: + self.scheduler: sched.scheduler = sched.scheduler() + self.running: bool = False + self.thread: Optional[Thread] = None + self.prev_idle: int = 0 + self.prev_total: int = 0 + self.stat_file: Path = Path("/proc/stat") + self.statusbar: "StatusBar" = statusbar + + def start(self) -> None: + self.running = True + self.thread = Thread(target=self._start, daemon=True) + self.thread.start() + + def _start(self): + self.scheduler.enter(0, 0, self.run) + self.scheduler.run() + + def run(self) -> None: + lines = self.stat_file.read_text().splitlines()[0] + values = [int(x) for x in lines.split()[1:]] + idle = sum(values[3:5]) + non_idle = sum(values[:3] + values[5:8]) + total = idle + non_idle + total_diff = total - self.prev_total + idle_diff = idle - self.prev_idle + cpu_percent = (total_diff - idle_diff) / total_diff + self.statusbar.after(0, self.statusbar.set_cpu, cpu_percent) + self.prev_idle = idle + self.prev_total = total + if self.running: + self.scheduler.enter(3, 0, self.run) + + class StatusBar(ttk.Frame): def __init__(self, master: tk.Widget, app: "Application") -> None: super().__init__(master) @@ -20,12 +58,14 @@ class StatusBar(ttk.Frame): self.status: Optional[ttk.Label] = None self.statusvar: tk.StringVar = tk.StringVar() self.zoom: Optional[ttk.Label] = None - self.cpu_usage: Optional[ttk.Label] = None + self.cpu_label: Optional[ttk.Label] = None self.alerts_button: Optional[ttk.Button] = None self.alert_style = Styles.no_alert self.running: bool = False self.core_alarms: List[ExceptionEvent] = [] self.draw() + self.cpu_usage: CpuUsage = CpuUsage(self) + self.cpu_usage.start() def draw(self) -> None: self.columnconfigure(0, weight=7) @@ -46,25 +86,27 @@ class StatusBar(ttk.Frame): ) self.status.grid(row=0, column=0, sticky="ew") - self.zoom = ttk.Label( - self, - text="%s" % (int(self.app.canvas.ratio * 100)) + "%", - anchor=tk.CENTER, - borderwidth=1, - relief=tk.RIDGE, - ) + self.zoom = ttk.Label(self, anchor=tk.CENTER, borderwidth=1, relief=tk.RIDGE) self.zoom.grid(row=0, column=1, sticky="ew") + self.set_zoom(self.app.canvas.ratio) - self.cpu_usage = ttk.Label( - self, text="CPU TBD", anchor=tk.CENTER, borderwidth=1, relief=tk.RIDGE + self.cpu_label = ttk.Label( + self, anchor=tk.CENTER, borderwidth=1, relief=tk.RIDGE ) - self.cpu_usage.grid(row=0, column=2, sticky="ew") + self.cpu_label.grid(row=0, column=2, sticky="ew") + self.set_cpu(0.0) self.alerts_button = ttk.Button( self, text="Alerts", command=self.click_alerts, style=self.alert_style ) self.alerts_button.grid(row=0, column=3, sticky="ew") + def set_cpu(self, usage: float) -> None: + self.cpu_label.config(text=f"CPU {usage * 100:.2f}%") + + def set_zoom(self, zoom: float) -> None: + self.zoom.config(text=f"ZOOM {zoom * 100:.0f}%") + def add_alert(self, event: ExceptionEvent) -> None: self.core_alarms.append(event) level = event.exception_event.level From fff4bd796358797e3b88babc9f005055d035d949 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 23 Jul 2020 09:41:39 -0700 Subject: [PATCH 009/598] moved cpu usage to a grpc call that the gui will listen to, fixed grpc stream typing to be grpc.Future, fixed pygui issue for start callback when a start fails, but there are no exceptions --- daemon/core/api/grpc/client.py | 19 +++++++++++-- daemon/core/api/grpc/grpcutils.py | 20 ++++++++++++++ daemon/core/api/grpc/server.py | 9 ++++++ daemon/core/gui/coreclient.py | 27 ++++++++++++++++-- daemon/core/gui/statusbar.py | 40 --------------------------- daemon/core/gui/toolbar.py | 7 +++-- daemon/proto/core/api/grpc/core.proto | 10 +++++++ 7 files changed, 85 insertions(+), 47 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index aacfa4f6..0674a0eb 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -467,7 +467,7 @@ class CoreGrpcClient: session_id: int, handler: Callable[[core_pb2.Event], None], events: List[core_pb2.Event] = None, - ) -> grpc.Channel: + ) -> grpc.Future: """ Listen for session events. @@ -484,7 +484,7 @@ class CoreGrpcClient: def throughputs( self, session_id: int, handler: Callable[[core_pb2.ThroughputsEvent], None] - ) -> grpc.Channel: + ) -> grpc.Future: """ Listen for throughput events with information for interfaces and bridges. @@ -498,6 +498,21 @@ class CoreGrpcClient: start_streamer(stream, handler) return stream + def cpu_usage( + self, delay: int, handler: Callable[[core_pb2.CpuUsageEvent], None] + ) -> grpc.Future: + """ + Listen for cpu usage events with the given repeat delay. + + :param delay: delay between receiving events + :param handler: handler for every event + :return: stream processing events, can be used to cancel stream + """ + request = core_pb2.CpuUsageRequest(delay=delay) + stream = self.stub.CpuUsage(request) + start_streamer(stream, handler) + return stream + def add_node( self, session_id: int, node: core_pb2.Node, source: str = None ) -> core_pb2.AddNodeResponse: diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index bd3519f7..84b8ee6a 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -1,5 +1,6 @@ import logging import time +from pathlib import Path from typing import Any, Dict, List, Tuple, Type, Union import grpc @@ -20,6 +21,25 @@ from core.services.coreservices import CoreService WORKERS = 10 +class CpuUsage: + def __init__(self) -> None: + self.stat_file: Path = Path("/proc/stat") + self.prev_idle: int = 0 + self.prev_total: int = 0 + + def run(self) -> float: + lines = self.stat_file.read_text().splitlines()[0] + values = [int(x) for x in lines.split()[1:]] + idle = sum(values[3:5]) + non_idle = sum(values[:3] + values[5:8]) + total = idle + non_idle + total_diff = total - self.prev_total + idle_diff = idle - self.prev_idle + self.prev_idle = idle + self.prev_total = total + return (total_diff - idle_diff) / total_diff + + def add_node_data(node_proto: core_pb2.Node) -> Tuple[NodeTypes, int, NodeOptions]: """ Convert node protobuf message to data for creating a node. diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 4c204845..38100e05 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -681,6 +681,15 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): last_stats = stats time.sleep(delay) + def CpuUsage( + self, request: core_pb2.CpuUsageRequest, context: ServicerContext + ) -> None: + cpu_usage = grpcutils.CpuUsage() + while self._is_running(context): + usage = cpu_usage.run() + yield core_pb2.CpuUsageEvent(usage=usage) + time.sleep(request.delay) + def AddNode( self, request: core_pb2.AddNodeRequest, context: ServicerContext ) -> core_pb2.AddNodeResponse: diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 8474b3cb..fc0bd520 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -16,6 +16,7 @@ from core.api.grpc import client from core.api.grpc.common_pb2 import ConfigOption from core.api.grpc.configservices_pb2 import ConfigService, ConfigServiceConfig from core.api.grpc.core_pb2 import ( + CpuUsageEvent, Event, ExceptionEvent, Hook, @@ -55,6 +56,7 @@ if TYPE_CHECKING: from core.gui.app import Application GUI_SOURCE = "gui" +CPU_USAGE_DELAY = 3 class CoreClient: @@ -92,8 +94,9 @@ class CoreClient: self.hooks: Dict[str, Hook] = {} self.emane_config: Dict[str, ConfigOption] = {} self.mobility_players: Dict[int, MobilityPlayer] = {} - self.handling_throughputs: Optional[grpc.Channel] = None - self.handling_events: Optional[grpc.Channel] = None + self.handling_throughputs: Optional[grpc.Future] = None + self.handling_cpu_usage: Optional[grpc.Future] = None + self.handling_events: Optional[grpc.Future] = None self.xml_dir: Optional[str] = None self.xml_file: Optional[str] = None @@ -111,6 +114,7 @@ class CoreClient: ) if throughputs_enabled: self.enable_throughputs() + self.setup_cpu_usage() return self._client def reset(self) -> None: @@ -258,6 +262,20 @@ class CoreClient: self.handling_events.cancel() self.handling_events = None + def cancel_cpu_usage(self) -> None: + if self.handling_cpu_usage: + self.handling_cpu_usage.cancel() + self.handling_cpu_usage = None + + def setup_cpu_usage(self) -> None: + if self.handling_cpu_usage and self.handling_cpu_usage.running(): + return + if self.handling_cpu_usage: + self.handling_cpu_usage.cancel() + self.handling_cpu_usage = self._client.cpu_usage( + CPU_USAGE_DELAY, self.handle_cpu_event + ) + def handle_throughputs(self, event: ThroughputsEvent) -> None: if event.session_id != self.session_id: logging.warning( @@ -269,6 +287,9 @@ class CoreClient: logging.debug("handling throughputs event: %s", event) self.app.after(0, self.app.canvas.set_throughputs, event) + def handle_cpu_event(self, event: CpuUsageEvent) -> None: + self.app.after(0, self.app.statusbar.set_cpu, event.usage) + def handle_exception_event(self, event: ExceptionEvent) -> None: logging.info("exception event: %s", event) self.app.statusbar.add_alert(event) @@ -479,6 +500,8 @@ class CoreClient: """ try: self.client.connect() + self.setup_cpu_usage() + # get service information response = self.client.get_services() for service in response.services: diff --git a/daemon/core/gui/statusbar.py b/daemon/core/gui/statusbar.py index e9fc03b2..6989593e 100644 --- a/daemon/core/gui/statusbar.py +++ b/daemon/core/gui/statusbar.py @@ -1,10 +1,7 @@ """ status bar """ -import sched import tkinter as tk -from pathlib import Path -from threading import Thread from tkinter import ttk from typing import TYPE_CHECKING, List, Optional @@ -16,41 +13,6 @@ if TYPE_CHECKING: from core.gui.app import Application -class CpuUsage: - def __init__(self, statusbar: "StatusBar") -> None: - self.scheduler: sched.scheduler = sched.scheduler() - self.running: bool = False - self.thread: Optional[Thread] = None - self.prev_idle: int = 0 - self.prev_total: int = 0 - self.stat_file: Path = Path("/proc/stat") - self.statusbar: "StatusBar" = statusbar - - def start(self) -> None: - self.running = True - self.thread = Thread(target=self._start, daemon=True) - self.thread.start() - - def _start(self): - self.scheduler.enter(0, 0, self.run) - self.scheduler.run() - - def run(self) -> None: - lines = self.stat_file.read_text().splitlines()[0] - values = [int(x) for x in lines.split()[1:]] - idle = sum(values[3:5]) - non_idle = sum(values[:3] + values[5:8]) - total = idle + non_idle - total_diff = total - self.prev_total - idle_diff = idle - self.prev_idle - cpu_percent = (total_diff - idle_diff) / total_diff - self.statusbar.after(0, self.statusbar.set_cpu, cpu_percent) - self.prev_idle = idle - self.prev_total = total - if self.running: - self.scheduler.enter(3, 0, self.run) - - class StatusBar(ttk.Frame): def __init__(self, master: tk.Widget, app: "Application") -> None: super().__init__(master) @@ -64,8 +26,6 @@ class StatusBar(ttk.Frame): self.running: bool = False self.core_alarms: List[ExceptionEvent] = [] self.draw() - self.cpu_usage: CpuUsage = CpuUsage(self) - self.cpu_usage.start() def draw(self) -> None: self.columnconfigure(0, weight=7) diff --git a/daemon/core/gui/toolbar.py b/daemon/core/gui/toolbar.py index 406a88ca..968b447d 100644 --- a/daemon/core/gui/toolbar.py +++ b/daemon/core/gui/toolbar.py @@ -305,10 +305,11 @@ class Toolbar(ttk.Frame): self.set_runtime() self.app.core.set_metadata() self.app.core.show_mobility_players() - elif response.exceptions: + else: enable_buttons(self.design_frame, enabled=True) - message = "\n".join(response.exceptions) - self.app.show_error("Start Session Error", message) + if response.exceptions: + message = "\n".join(response.exceptions) + self.app.show_error("Start Session Error", message) def set_runtime(self) -> None: enable_buttons(self.runtime_frame, enabled=True) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index eb889d14..9214ad1b 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -51,6 +51,8 @@ service CoreApi { } rpc Throughputs (ThroughputsRequest) returns (stream ThroughputsEvent) { } + rpc CpuUsage (CpuUsageRequest) returns (stream CpuUsageEvent) { + } // node rpc rpc AddNode (AddNodeRequest) returns (AddNodeResponse) { @@ -347,6 +349,14 @@ message ThroughputsEvent { repeated InterfaceThroughput iface_throughputs = 3; } +message CpuUsageRequest { + int32 delay = 1; +} + +message CpuUsageEvent { + double usage = 1; +} + message InterfaceThroughput { int32 node_id = 1; int32 iface_id = 2; From ba3a2474957394b196b47e79432daf6fc5e77cf1 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 23 Jul 2020 21:21:43 -0700 Subject: [PATCH 010/598] updated changelog for 7.0.0 --- CHANGELOG.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96f7b30a..375a7607 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,53 @@ +## 2020-07-23 CORE 7.0.0 + +* Breaking Changes + * core.emudata and core.data combined and cleaned up into core.data + * updates to consistently use mac instead of hwaddr/mac + * \#468 - code related to adding/editing/deleting links cleaned up + * \#469 - usages of per all changed to loss to be consistent + * \#470 - variables with numbered names now use numbers directly + * \#471 - node startup is no longer embedded within its constructor + * \#472 - code updated to refer to interfaces consistently as iface + * \#475 - code updates changing how ip addresses are stored on interfaces + * \#476 - executables to check for moved into own module core.executables + * \#486 - core will now install into its own python virtual environment managed by poetry +* core-daemon + * updates to properly save/load distributed servers to xml + * \#474 - added type hinting to all service files + * \#478 - fixed typo in config service directory + * \#479 - opening an xml file will now cycle through states like a normal session + * \#480 - ovs configuration will now save/load from xml and display in guis + * \#484 - changes to support adding emane links during runtime +* core-pygui + * fixed issue not displaying services for the default group in service dialogs + * fixed issue starting a session when the daemon is not present + * fixed issue attempting to open terminals for invalid nodes + * fixed issue syncing session location + * fixed issue joining a session with mobility, not in runtime + * added cpu usage monitor to status bar + * emane configurations can now be seen during runtime + * rj45 nodes can only have one link + * disabling throughputs will clear labels + * improvements to custom service copy + * link options will now be drawn on as a label + * updates to handle runtime link events + * \#477 - added optional details pane for a quick view of node/link details + * \#485 - pygui fixed observer widget for invalid nodes + * \#496 - improved alert handling +* core-gui + * \#493 - increased frame size to show all emane configuration options +* gRPC API + * added set session user rpc + * added cpu usage stream + * interface objects returned from get_node will now provide node_id, net_id, and net2_id data + * peer to peer nodes will not be included in get_session calls + * pathloss events will now throw an error when nem id not found + * \#481 - link rpc calls will broadcast out + * \#496 - added alert rpc call +* Services + * fixed issue reading files in security services + * \#494 - add staticd to daemons list for frr services + ## 2020-06-11 CORE 6.5.0 * Breaking Changes * CoreNode.newnetif - both parameters are required and now takes an InterfaceData object as its second parameter From 154fa8b77d873ff892937eb12994ce237e2f00c8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 24 Jul 2020 22:00:38 -0700 Subject: [PATCH 011/598] pygui: replaced hook with wrapped hook class, fixed hook dialog edit --- daemon/core/gui/coreclient.py | 11 ++- daemon/core/gui/dialogs/hooks.py | 39 ++++++---- daemon/core/gui/wrappers.py | 126 +++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 17 deletions(-) create mode 100644 daemon/core/gui/wrappers.py diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index fc0bd520..97399556 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -19,7 +19,6 @@ from core.api.grpc.core_pb2 import ( CpuUsageEvent, Event, ExceptionEvent, - Hook, Interface, Link, LinkEvent, @@ -51,6 +50,7 @@ from core.gui.graph.shape import AnnotationData, Shape from core.gui.graph.shapeutils import ShapeType from core.gui.interface import InterfaceManager from core.gui.nodeutils import NodeDraw, NodeUtils +from core.gui.wrappers import Hook if TYPE_CHECKING: from core.gui.app import Application @@ -332,7 +332,8 @@ class CoreClient: # get hooks response = self.client.get_hooks(self.session_id) - for hook in response.hooks: + for hook_proto in response.hooks: + hook = Hook.from_proto(hook_proto) self.hooks[hook.file] = hook # get emane config @@ -570,7 +571,7 @@ class CoreClient: wlan_configs = self.get_wlan_configs_proto() mobility_configs = self.get_mobility_configs_proto() emane_model_configs = self.get_emane_model_configs_proto() - hooks = list(self.hooks.values()) + hooks = [x.to_proto() for x in self.hooks.values()] service_configs = self.get_service_configs_proto() file_configs = self.get_service_file_configs_proto() asymmetric_links = [ @@ -823,7 +824,9 @@ class CoreClient: config_proto.data, ) for hook in self.hooks.values(): - self.client.add_hook(self.session_id, hook.state, hook.file, hook.data) + self.client.add_hook( + self.session_id, hook.state.value, hook.file, hook.data + ) for config_proto in self.get_emane_model_configs_proto(): self.client.set_emane_model_config( self.session_id, diff --git a/daemon/core/gui/dialogs/hooks.py b/daemon/core/gui/dialogs/hooks.py index 08d666ba..b004dae2 100644 --- a/daemon/core/gui/dialogs/hooks.py +++ b/daemon/core/gui/dialogs/hooks.py @@ -2,10 +2,10 @@ import tkinter as tk from tkinter import ttk from typing import TYPE_CHECKING, Optional -from core.api.grpc import core_pb2 from core.gui.dialogs.dialog import Dialog from core.gui.themes import PADX, PADY from core.gui.widgets import CodeText, ListboxScroll +from core.gui.wrappers import Hook, SessionState if TYPE_CHECKING: from core.gui.app import Application @@ -16,8 +16,9 @@ class HookDialog(Dialog): super().__init__(app, "Hook", master=master) self.name: tk.StringVar = tk.StringVar() self.codetext: Optional[CodeText] = None - self.hook: core_pb2.Hook = core_pb2.Hook() + self.hook: Optional[Hook] = None self.state: tk.StringVar = tk.StringVar() + self.editing: bool = False self.draw() def draw(self) -> None: @@ -34,8 +35,8 @@ class HookDialog(Dialog): label.grid(row=0, column=0, sticky="ew", padx=PADX) entry = ttk.Entry(frame, textvariable=self.name) entry.grid(row=0, column=1, sticky="ew", padx=PADX) - values = tuple(x for x in core_pb2.SessionState.Enum.keys() if x != "NONE") - initial_state = core_pb2.SessionState.Enum.Name(core_pb2.SessionState.RUNTIME) + values = tuple(x.name for x in SessionState) + initial_state = SessionState.RUNTIME.name self.state.set(initial_state) self.name.set(f"{initial_state.lower()}_hook.sh") combobox = ttk.Combobox( @@ -67,23 +68,30 @@ class HookDialog(Dialog): button.grid(row=0, column=1, sticky="ew") def state_change(self, event: tk.Event) -> None: + if self.editing: + return state_name = self.state.get() self.name.set(f"{state_name.lower()}_hook.sh") - def set(self, hook: core_pb2.Hook) -> None: + def set(self, hook: Hook) -> None: + self.editing = True self.hook = hook self.name.set(hook.file) 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) + state_name = hook.state.name self.state.set(state_name) def save(self) -> None: 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 - self.hook.state = state_value + state = SessionState[self.state.get()] + file_name = self.name.get() + if self.editing: + self.hook.state = state + self.hook.file = file_name + self.hook.data = data + else: + self.hook = Hook(state=state, file=file_name, data=data) self.destroy() @@ -94,6 +102,7 @@ class HooksDialog(Dialog): self.edit_button: Optional[ttk.Button] = None self.delete_button: Optional[ttk.Button] = None self.selected: Optional[str] = None + self.selected_index: Optional[int] = None self.draw() def draw(self) -> None: @@ -133,10 +142,13 @@ class HooksDialog(Dialog): self.listbox.insert(tk.END, hook.file) def click_edit(self) -> None: - hook = self.app.core.hooks[self.selected] + hook = self.app.core.hooks.pop(self.selected) dialog = HookDialog(self, self.app) dialog.set(hook) dialog.show() + self.app.core.hooks[hook.file] = hook + self.listbox.delete(self.selected_index) + self.listbox.insert(self.selected_index, hook.file) def click_delete(self) -> None: del self.app.core.hooks[self.selected] @@ -146,11 +158,12 @@ class HooksDialog(Dialog): def select(self, event: tk.Event) -> None: if self.listbox.curselection(): - index = self.listbox.curselection()[0] - self.selected = self.listbox.get(index) + self.selected_index = self.listbox.curselection()[0] + self.selected = self.listbox.get(self.selected_index) self.edit_button.config(state=tk.NORMAL) self.delete_button.config(state=tk.NORMAL) else: self.selected = None + self.selected_index = None self.edit_button.config(state=tk.DISABLED) self.delete_button.config(state=tk.DISABLED) diff --git a/daemon/core/gui/wrappers.py b/daemon/core/gui/wrappers.py new file mode 100644 index 00000000..217ab321 --- /dev/null +++ b/daemon/core/gui/wrappers.py @@ -0,0 +1,126 @@ +from dataclasses import dataclass +from enum import Enum +from typing import List + +from core.api.grpc import core_pb2 + + +class SessionState(Enum): + DEFINITION = 1 + CONFIGURATION = 2 + INSTANTIATION = 3 + RUNTIME = 4 + DATACOLLECT = 5 + SHUTDOWN = 6 + + +class NodeType(Enum): + DEFAULT = 0 + PHYSICAL = 1 + SWITCH = 4 + HUB = 5 + WIRELESS_LAN = 6 + RJ45 = 7 + TUNNEL = 8 + EMANE = 10 + TAP_BRIDGE = 11 + PEER_TO_PEER = 12 + CONTROL_NET = 13 + DOCKER = 15 + LXC = 16 + + +@dataclass +class Hook: + state: SessionState + file: str + data: str + + @classmethod + def from_proto(cls, hook: core_pb2.Hook) -> "Hook": + return Hook(state=SessionState(hook.state), file=hook.file, data=hook.data) + + def to_proto(self) -> core_pb2.Hook: + return core_pb2.Hook(state=self.state.value, file=self.file, data=self.data) + + +@dataclass +class Position: + x: float + y: float + + @classmethod + def from_proto(cls, position: core_pb2.Position) -> "Position": + return Position(x=position.x, y=position.y) + + def to_proto(self) -> core_pb2.Position: + return core_pb2.Position(x=self.x, y=self.y) + + +@dataclass +class Geo: + lat: float = None + lon: float = None + alt: float = None + + @classmethod + def from_proto(cls, geo: core_pb2.Geo) -> "Geo": + return Geo(lat=geo.lat, lon=geo.lon, alt=geo.alt) + + def to_proto(self) -> core_pb2.Geo: + return core_pb2.Geo(lat=self.lat, lon=self.lon, alt=self.alt) + + +@dataclass +class Node: + id: int + name: str + type: NodeType + model: str = None + position: Position = None + services: List[str] = None + config_services: List[str] = None + emane: str = None + icon: str = None + image: str = None + server: str = None + geo: Geo = None + dir: str = None + channel: str = None + + @classmethod + def from_proto(cls, node: core_pb2.Node) -> "Node": + return Node( + id=node.id, + name=node.name, + type=NodeType(node.type), + model=node.model, + position=Position.from_proto(node.position), + services=list(node.services), + config_services=list(node.config_services), + emane=node.emane, + icon=node.icon, + image=node.image, + server=node.server, + geo=Geo.from_proto(node.geo), + dir=node.dir, + channel=node.channel, + ) + + def to_proto(self) -> core_pb2.Node: + return core_pb2.Node( + id=self.id, + name=self.name, + type=self.type.value, + model=self.model, + position=self.position.to_proto(), + services=self.services, + config_services=self.config_services, + emane=self.emane, + icon=self.icon, + image=self.image, + server=self.server, + geo=self.geo.to_proto(), + dir=self.dir, + channel=self.channel, + ) From 77f6577bce08437f3dae737143ff8482e0501e06 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 25 Jul 2020 10:30:14 -0700 Subject: [PATCH 012/598] pygui: added wrappers for most usages of protobufs within pygui --- daemon/core/gui/coreclient.py | 126 ++++--- daemon/core/gui/dialogs/alerts.py | 23 +- .../core/gui/dialogs/configserviceconfig.py | 4 +- daemon/core/gui/dialogs/emaneconfig.py | 3 +- daemon/core/gui/dialogs/linkconfig.py | 29 +- daemon/core/gui/dialogs/mobilityconfig.py | 3 +- daemon/core/gui/dialogs/mobilityplayer.py | 3 +- daemon/core/gui/dialogs/nodeconfig.py | 2 +- daemon/core/gui/dialogs/sessionoptions.py | 4 +- daemon/core/gui/dialogs/sessions.py | 7 +- daemon/core/gui/dialogs/wlanconfig.py | 3 +- daemon/core/gui/frames/link.py | 4 +- daemon/core/gui/frames/node.py | 2 +- daemon/core/gui/graph/edges.py | 11 +- daemon/core/gui/graph/graph.py | 35 +- daemon/core/gui/graph/node.py | 3 +- daemon/core/gui/images.py | 2 +- daemon/core/gui/interface.py | 14 +- daemon/core/gui/nodeutils.py | 4 +- daemon/core/gui/statusbar.py | 4 +- daemon/core/gui/toolbar.py | 11 +- daemon/core/gui/widgets.py | 14 +- daemon/core/gui/wrappers.py | 337 +++++++++++++++++- 23 files changed, 475 insertions(+), 173 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 97399556..c41caeca 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -13,27 +13,8 @@ from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Set, Tuple import grpc from core.api.grpc import client -from core.api.grpc.common_pb2 import ConfigOption from core.api.grpc.configservices_pb2 import ConfigService, ConfigServiceConfig -from core.api.grpc.core_pb2 import ( - CpuUsageEvent, - Event, - ExceptionEvent, - Interface, - Link, - LinkEvent, - LinkType, - MessageType, - Node, - NodeEvent, - NodeType, - Position, - SessionLocation, - SessionState, - StartSessionResponse, - StopSessionResponse, - ThroughputsEvent, -) +from core.api.grpc.core_pb2 import CpuUsageEvent, Event, ThroughputsEvent from core.api.grpc.emane_pb2 import EmaneModelConfig from core.api.grpc.mobility_pb2 import MobilityConfig from core.api.grpc.services_pb2 import NodeServiceData, ServiceConfig, ServiceFileConfig @@ -50,7 +31,22 @@ from core.gui.graph.shape import AnnotationData, Shape from core.gui.graph.shapeutils import ShapeType from core.gui.interface import InterfaceManager from core.gui.nodeutils import NodeDraw, NodeUtils -from core.gui.wrappers import Hook +from core.gui.wrappers import ( + ConfigOption, + ExceptionEvent, + Hook, + Interface, + Link, + LinkEvent, + LinkType, + MessageType, + Node, + NodeEvent, + NodeType, + Position, + SessionLocation, + SessionState, +) if TYPE_CHECKING: from core.gui.app import Application @@ -165,12 +161,13 @@ class CoreClient: return if event.HasField("link_event"): - self.app.after(0, self.handle_link_event, event.link_event) + link_event = LinkEvent.from_proto(event.link_event) + self.app.after(0, self.handle_link_event, link_event) elif event.HasField("session_event"): logging.info("session event: %s", event) session_event = event.session_event - if session_event.event <= SessionState.SHUTDOWN: - self.state = event.session_event.event + if session_event.event <= SessionState.SHUTDOWN.value: + self.state = SessionState(session_event.event) elif session_event.event in {7, 8, 9}: node_id = session_event.node_id dialog = self.mobility_players.get(node_id) @@ -184,10 +181,12 @@ class CoreClient: else: logging.warning("unknown session event: %s", session_event) elif event.HasField("node_event"): - self.app.after(0, self.handle_node_event, event.node_event) + node_event = NodeEvent.from_proto(event.node_event) + self.app.after(0, self.handle_node_event, node_event) elif event.HasField("config_event"): logging.info("config event: %s", event) elif event.HasField("exception_event"): + event = ExceptionEvent.from_proto(event.session_id, event.exception_event) self.handle_exception_event(event) else: logging.info("unhandled event: %s", event) @@ -307,7 +306,7 @@ class CoreClient: try: response = self.client.get_session(self.session_id) session = response.session - self.state = session.state + self.state = SessionState(session.state) self.handling_events = self.client.events( self.session_id, self.handle_events ) @@ -324,7 +323,7 @@ class CoreClient: # get location if query_location: response = self.client.get_session_location(self.session_id) - self.location = response.location + self.location = SessionLocation.from_proto(response.location) # get emane models response = self.client.get_emane_models(self.session_id) @@ -338,20 +337,22 @@ class CoreClient: # get emane config response = self.client.get_emane_config(self.session_id) - self.emane_config = response.config + self.emane_config = ConfigOption.from_dict(response.config) # update interface manager self.ifaces_manager.joined(session.links) # draw session - self.app.canvas.reset_and_redraw(session) + nodes = [Node.from_proto(x) for x in session.nodes] + links = [Link.from_proto(x) for x in session.links] + self.app.canvas.reset_and_redraw(nodes, links) # 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) + canvas_node.mobility_config = ConfigOption.from_dict(config) # get emane model config response = self.client.get_emane_model_configs(self.session_id) @@ -360,16 +361,16 @@ class CoreClient: if config.iface_id != -1: iface_id = config.iface_id canvas_node = self.canvas_nodes[config.node_id] - canvas_node.emane_model_configs[(config.model, iface_id)] = dict( - config.config - ) + canvas_node.emane_model_configs[ + (config.model, iface_id) + ] = ConfigOption.from_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] canvas_node = self.canvas_nodes[_id] - canvas_node.wlan_config = dict(mapped_config.config) + canvas_node.wlan_config = ConfigOption.from_dict(mapped_config.config) # get service configurations response = self.client.get_node_service_configs(self.session_id) @@ -501,7 +502,6 @@ class CoreClient: """ try: self.client.connect() - self.setup_cpu_usage() # get service information response = self.client.get_services() @@ -546,8 +546,9 @@ class CoreClient: def edit_node(self, core_node: Node) -> None: try: + position = core_node.position.to_proto() self.client.edit_node( - self.session_id, core_node.id, core_node.position, source=GUI_SOURCE + self.session_id, core_node.id, position, source=GUI_SOURCE ) except grpc.RpcError as e: self.app.show_grpc_exception("Edit Node Error", e) @@ -556,18 +557,17 @@ class CoreClient: for server in self.servers.values(): self.client.add_session_server(self.session_id, server.name, server.address) - def start_session(self) -> StartSessionResponse: + def start_session(self) -> Tuple[bool, List[str]]: self.ifaces_manager.reset_mac() - nodes = [x.core_node for x in self.canvas_nodes.values()] + nodes = [x.core_node.to_proto() for x in self.canvas_nodes.values()] links = [] for edge in self.links.values(): - link = Link() - link.CopyFrom(edge.link) - if link.HasField("iface1") and not link.iface1.mac: + link = edge.link + if link.iface1 and not link.iface1.mac: link.iface1.mac = self.ifaces_manager.next_mac() - if link.HasField("iface2") and not link.iface2.mac: + if link.iface2 and not link.iface2.mac: link.iface2.mac = self.ifaces_manager.next_mac() - links.append(link) + links.append(link.to_proto()) wlan_configs = self.get_wlan_configs_proto() mobility_configs = self.get_mobility_configs_proto() emane_model_configs = self.get_emane_model_configs_proto() @@ -582,14 +582,15 @@ class CoreClient: emane_config = {x: self.emane_config[x].value for x in self.emane_config} else: emane_config = None - response = StartSessionResponse(result=False) + result = False + exceptions = [] try: self.send_servers() response = self.client.start_session( self.session_id, nodes, links, - self.location, + self.location.to_proto(), hooks, emane_config, emane_model_configs, @@ -605,20 +606,23 @@ class CoreClient: ) if response.result: self.set_metadata() + result = response.result + exceptions = response.exceptions except grpc.RpcError as e: self.app.show_grpc_exception("Start Session Error", e) - return response + return result, exceptions - def stop_session(self, session_id: int = None) -> StopSessionResponse: + def stop_session(self, session_id: int = None) -> bool: if not session_id: session_id = self.session_id - response = StopSessionResponse(result=False) + result = False try: response = self.client.stop_session(session_id) logging.info("stopped session(%s), result: %s", session_id, response) + result = response.result except grpc.RpcError as e: self.app.show_grpc_exception("Stop Session Error", e) - return response + return result def show_mobility_players(self) -> None: for canvas_node in self.canvas_nodes.values(): @@ -920,7 +924,7 @@ class CoreClient: ) return node - def deleted_graph_nodes(self, canvas_nodes: List[Node]) -> None: + def deleted_graph_nodes(self, canvas_nodes: List[CanvasNode]) -> None: """ remove the nodes selected by the user and anything related to that node such as link, configurations, interfaces @@ -951,13 +955,7 @@ class CoreClient: ip6=ip6, ip6_mask=ip6_mask, ) - logging.info( - "create node(%s) interface(%s) IPv4(%s) IPv6(%s)", - node.name, - iface.name, - iface.ip4, - iface.ip6, - ) + logging.info("create node(%s) interface(%s)", node.name, iface) return iface def create_link( @@ -1010,8 +1008,7 @@ class CoreClient: continue if not canvas_node.wlan_config: continue - config = canvas_node.wlan_config - config = {x: config[x].value for x in config} + config = ConfigOption.to_dict(canvas_node.wlan_config) node_id = canvas_node.core_node.id wlan_config = WlanConfig(node_id=node_id, config=config) configs.append(wlan_config) @@ -1024,8 +1021,7 @@ class CoreClient: continue if not canvas_node.mobility_config: continue - config = canvas_node.mobility_config - config = {x: config[x].value for x in config} + config = ConfigOption.to_dict(canvas_node.mobility_config) node_id = canvas_node.core_node.id mobility_config = MobilityConfig(node_id=node_id, config=config) configs.append(mobility_config) @@ -1039,7 +1035,7 @@ class CoreClient: node_id = canvas_node.core_node.id for key, config in canvas_node.emane_model_configs.items(): model, iface_id = key - config = {x: config[x].value for x in config} + config = ConfigOption.to_dict(config) if iface_id is None: iface_id = -1 config_proto = EmaneModelConfig( @@ -1116,7 +1112,7 @@ class CoreClient: node_id, config, ) - return dict(config) + return ConfigOption.from_dict(config) def get_mobility_config(self, node_id: int) -> Dict[str, ConfigOption]: response = self.client.get_mobility_config(self.session_id, node_id) @@ -1126,7 +1122,7 @@ class CoreClient: node_id, config, ) - return dict(config) + return ConfigOption.from_dict(config) def get_emane_model_config( self, node_id: int, model: str, iface_id: int = None @@ -1145,7 +1141,7 @@ class CoreClient: iface_id, config, ) - return dict(config) + return ConfigOption.from_dict(config) def execute_script(self, script) -> None: response = self.client.execute_script(script) diff --git a/daemon/core/gui/dialogs/alerts.py b/daemon/core/gui/dialogs/alerts.py index 8e0aa02e..fd6d342e 100644 --- a/daemon/core/gui/dialogs/alerts.py +++ b/daemon/core/gui/dialogs/alerts.py @@ -5,10 +5,10 @@ import tkinter as tk from tkinter import ttk from typing import TYPE_CHECKING, Dict, Optional -from core.api.grpc.core_pb2 import ExceptionEvent, ExceptionLevel from core.gui.dialogs.dialog import Dialog from core.gui.themes import PADX, PADY from core.gui.widgets import CodeText +from core.gui.wrappers import ExceptionEvent, ExceptionLevel if TYPE_CHECKING: from core.gui.app import Application @@ -49,9 +49,8 @@ class AlertsDialog(Dialog): self.tree.heading("source", text="Source") self.tree.bind("<>", self.click_select) - for alarm in self.app.statusbar.core_alarms: - exception = alarm.exception_event - level_name = ExceptionLevel.Enum.Name(exception.level) + for exception in self.app.statusbar.core_alarms: + level_name = exception.level.name node_id = exception.node_id if exception.node_id else "" insert_id = self.tree.insert( "", @@ -60,21 +59,21 @@ class AlertsDialog(Dialog): values=( exception.date, level_name, - alarm.session_id, + exception.session_id, node_id, exception.source, ), tags=(level_name,), ) - self.alarm_map[insert_id] = alarm + self.alarm_map[insert_id] = exception - error_name = ExceptionLevel.Enum.Name(ExceptionLevel.ERROR) + error_name = ExceptionLevel.ERROR.name self.tree.tag_configure(error_name, background="#ff6666") - fatal_name = ExceptionLevel.Enum.Name(ExceptionLevel.FATAL) + fatal_name = ExceptionLevel.FATAL.name self.tree.tag_configure(fatal_name, background="#d9d9d9") - warning_name = ExceptionLevel.Enum.Name(ExceptionLevel.WARNING) + warning_name = ExceptionLevel.WARNING.name self.tree.tag_configure(warning_name, background="#ffff99") - notice_name = ExceptionLevel.Enum.Name(ExceptionLevel.NOTICE) + notice_name = ExceptionLevel.NOTICE.name self.tree.tag_configure(notice_name, background="#85e085") yscrollbar = ttk.Scrollbar(frame, orient="vertical", command=self.tree.yview) @@ -108,8 +107,8 @@ class AlertsDialog(Dialog): def click_select(self, event: tk.Event) -> None: current = self.tree.selection()[0] - alarm = self.alarm_map[current] + exception = self.alarm_map[current] self.codetext.text.config(state=tk.NORMAL) self.codetext.text.delete(1.0, tk.END) - self.codetext.text.insert(1.0, alarm.exception_event.text) + self.codetext.text.insert(1.0, exception.text) self.codetext.text.config(state=tk.DISABLED) diff --git a/daemon/core/gui/dialogs/configserviceconfig.py b/daemon/core/gui/dialogs/configserviceconfig.py index c2d42ee4..5a6a89a8 100644 --- a/daemon/core/gui/dialogs/configserviceconfig.py +++ b/daemon/core/gui/dialogs/configserviceconfig.py @@ -8,11 +8,11 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set import grpc -from core.api.grpc.common_pb2 import ConfigOption from core.api.grpc.services_pb2 import ServiceValidationMode from core.gui.dialogs.dialog import Dialog from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.widgets import CodeText, ConfigFrame, ListboxScroll +from core.gui.wrappers import ConfigOption if TYPE_CHECKING: from core.gui.app import Application @@ -99,7 +99,7 @@ class ConfigServiceConfigDialog(Dialog): service_config = self.canvas_node.config_service_configs.get( self.service_name, {} ) - self.config = response.config + self.config = ConfigOption.from_dict(response.config) self.default_config = {x.name: x.value for x in self.config.values()} custom_config = service_config.get("config") if custom_config: diff --git a/daemon/core/gui/dialogs/emaneconfig.py b/daemon/core/gui/dialogs/emaneconfig.py index bb334757..d87e935a 100644 --- a/daemon/core/gui/dialogs/emaneconfig.py +++ b/daemon/core/gui/dialogs/emaneconfig.py @@ -8,12 +8,11 @@ from typing import TYPE_CHECKING, Dict, List, Optional import grpc -from core.api.grpc.common_pb2 import ConfigOption -from core.api.grpc.core_pb2 import Node from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum, Images from core.gui.themes import PADX, PADY from core.gui.widgets import ConfigFrame +from core.gui.wrappers import ConfigOption, Node if TYPE_CHECKING: from core.gui.app import Application diff --git a/daemon/core/gui/dialogs/linkconfig.py b/daemon/core/gui/dialogs/linkconfig.py index 28798ec1..87f43284 100644 --- a/daemon/core/gui/dialogs/linkconfig.py +++ b/daemon/core/gui/dialogs/linkconfig.py @@ -5,11 +5,11 @@ import tkinter as tk from tkinter import ttk from typing import TYPE_CHECKING, Optional -from core.api.grpc import core_pb2 from core.gui import validation from core.gui.dialogs.colorpicker import ColorPickerDialog from core.gui.dialogs.dialog import Dialog from core.gui.themes import PADX, PADY +from core.gui.wrappers import Interface, Link, LinkOptions if TYPE_CHECKING: from core.gui.app import Application @@ -21,7 +21,7 @@ def get_int(var: tk.StringVar) -> Optional[int]: if value != "": return int(value) else: - return None + return 0 def get_float(var: tk.StringVar) -> Optional[float]: @@ -29,14 +29,15 @@ def get_float(var: tk.StringVar) -> Optional[float]: if value != "": return float(value) else: - return None + return 0.0 class LinkConfigurationDialog(Dialog): def __init__(self, app: "Application", edge: "CanvasEdge") -> None: super().__init__(app, "Link Configuration") self.edge: "CanvasEdge" = edge - self.is_symmetric: bool = edge.link.options.unidirectional is False + + self.is_symmetric: bool = edge.link.is_symmetric() if self.is_symmetric: symmetry_var = tk.StringVar(value=">>") else: @@ -223,32 +224,32 @@ class LinkConfigurationDialog(Dialog): delay = get_int(self.delay) duplicate = get_int(self.duplicate) loss = get_float(self.loss) - options = core_pb2.LinkOptions( + options = LinkOptions( bandwidth=bandwidth, jitter=jitter, delay=delay, dup=duplicate, loss=loss ) - link.options.CopyFrom(options) + link.options = options iface1_id = None - if link.HasField("iface1"): + if link.iface1: iface1_id = link.iface1.id iface2_id = None - if link.HasField("iface2"): + if link.iface2: iface2_id = link.iface2.id if not self.is_symmetric: link.options.unidirectional = True asym_iface1 = None if iface1_id: - asym_iface1 = core_pb2.Interface(id=iface1_id) + asym_iface1 = Interface(id=iface1_id) asym_iface2 = None if iface2_id: - asym_iface2 = core_pb2.Interface(id=iface2_id) + asym_iface2 = Interface(id=iface2_id) down_bandwidth = get_int(self.down_bandwidth) down_jitter = get_int(self.down_jitter) down_delay = get_int(self.down_delay) down_duplicate = get_int(self.down_duplicate) down_loss = get_float(self.down_loss) - options = core_pb2.LinkOptions( + options = LinkOptions( bandwidth=down_bandwidth, jitter=down_jitter, delay=down_delay, @@ -256,7 +257,7 @@ class LinkConfigurationDialog(Dialog): loss=down_loss, unidirectional=True, ) - self.edge.asymmetric_link = core_pb2.Link( + self.edge.asymmetric_link = Link( node1_id=link.node2_id, node2_id=link.node1_id, iface1=asym_iface1, @@ -267,7 +268,7 @@ class LinkConfigurationDialog(Dialog): link.options.unidirectional = False self.edge.asymmetric_link = None - if self.app.core.is_runtime() and link.HasField("options"): + if self.app.core.is_runtime() and link.options: session_id = self.app.core.session_id self.app.core.client.edit_link( session_id, @@ -316,7 +317,7 @@ class LinkConfigurationDialog(Dialog): color = self.app.canvas.itemcget(self.edge.id, "fill") self.color.set(color) link = self.edge.link - if link.HasField("options"): + if link.options: self.bandwidth.set(str(link.options.bandwidth)) self.jitter.set(str(link.options.jitter)) self.duplicate.set(str(link.options.dup)) diff --git a/daemon/core/gui/dialogs/mobilityconfig.py b/daemon/core/gui/dialogs/mobilityconfig.py index daaf9ea5..ca9caf43 100644 --- a/daemon/core/gui/dialogs/mobilityconfig.py +++ b/daemon/core/gui/dialogs/mobilityconfig.py @@ -6,11 +6,10 @@ from typing import TYPE_CHECKING, Dict, Optional import grpc -from core.api.grpc.common_pb2 import ConfigOption -from core.api.grpc.core_pb2 import Node from core.gui.dialogs.dialog import Dialog from core.gui.themes import PADX, PADY from core.gui.widgets import ConfigFrame +from core.gui.wrappers import ConfigOption, Node if TYPE_CHECKING: from core.gui.app import Application diff --git a/daemon/core/gui/dialogs/mobilityplayer.py b/daemon/core/gui/dialogs/mobilityplayer.py index e6ef62ea..16aa8ea0 100644 --- a/daemon/core/gui/dialogs/mobilityplayer.py +++ b/daemon/core/gui/dialogs/mobilityplayer.py @@ -4,12 +4,11 @@ from typing import TYPE_CHECKING, Dict, Optional import grpc -from core.api.grpc.common_pb2 import ConfigOption -from core.api.grpc.core_pb2 import Node from core.api.grpc.mobility_pb2 import MobilityAction from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum from core.gui.themes import PADX, PADY +from core.gui.wrappers import ConfigOption, Node if TYPE_CHECKING: from core.gui.app import Application diff --git a/daemon/core/gui/dialogs/nodeconfig.py b/daemon/core/gui/dialogs/nodeconfig.py index 9e958283..33c8fb32 100644 --- a/daemon/core/gui/dialogs/nodeconfig.py +++ b/daemon/core/gui/dialogs/nodeconfig.py @@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Dict, Optional import netaddr from PIL.ImageTk import PhotoImage -from core.api.grpc.core_pb2 import Node from core.gui import nodeutils, validation from core.gui.appconfig import ICONS_PATH from core.gui.dialogs.dialog import Dialog @@ -16,6 +15,7 @@ from core.gui.images import Images from core.gui.nodeutils import NodeUtils from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.widgets import ListboxScroll, image_chooser +from core.gui.wrappers import Node if TYPE_CHECKING: from core.gui.app import Application diff --git a/daemon/core/gui/dialogs/sessionoptions.py b/daemon/core/gui/dialogs/sessionoptions.py index fd021fee..24bacb30 100644 --- a/daemon/core/gui/dialogs/sessionoptions.py +++ b/daemon/core/gui/dialogs/sessionoptions.py @@ -5,10 +5,10 @@ from typing import TYPE_CHECKING, Dict, Optional import grpc -from core.api.grpc.common_pb2 import ConfigOption from core.gui.dialogs.dialog import Dialog from core.gui.themes import PADX, PADY from core.gui.widgets import ConfigFrame +from core.gui.wrappers import ConfigOption if TYPE_CHECKING: from core.gui.app import Application @@ -28,7 +28,7 @@ class SessionOptionsDialog(Dialog): try: session_id = self.app.core.session_id response = self.app.core.client.get_session_options(session_id) - return response.config + return ConfigOption.from_dict(response.config) except grpc.RpcError as e: self.app.show_grpc_exception("Get Session Options Error", e) self.has_error = True diff --git a/daemon/core/gui/dialogs/sessions.py b/daemon/core/gui/dialogs/sessions.py index a7d702eb..75b9dcf4 100644 --- a/daemon/core/gui/dialogs/sessions.py +++ b/daemon/core/gui/dialogs/sessions.py @@ -5,12 +5,11 @@ from typing import TYPE_CHECKING, List, Optional import grpc -from core.api.grpc import core_pb2 -from core.api.grpc.core_pb2 import SessionSummary from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum, Images from core.gui.task import ProgressTask from core.gui.themes import PADX, PADY +from core.gui.wrappers import SessionState, SessionSummary if TYPE_CHECKING: from core.gui.app import Application @@ -33,7 +32,7 @@ class SessionsDialog(Dialog): try: response = self.app.core.client.get_sessions() logging.info("sessions: %s", response) - return response.sessions + return [SessionSummary.from_proto(x) for x in response.sessions] except grpc.RpcError as e: self.app.show_grpc_exception("Get Sessions Error", e) self.destroy() @@ -82,7 +81,7 @@ class SessionsDialog(Dialog): self.tree.heading("nodes", text="Node Count") for index, session in enumerate(self.sessions): - state_name = core_pb2.SessionState.Enum.Name(session.state) + state_name = SessionState(session.state).name self.tree.insert( "", tk.END, diff --git a/daemon/core/gui/dialogs/wlanconfig.py b/daemon/core/gui/dialogs/wlanconfig.py index 326b3195..17f62dfb 100644 --- a/daemon/core/gui/dialogs/wlanconfig.py +++ b/daemon/core/gui/dialogs/wlanconfig.py @@ -3,11 +3,10 @@ from typing import TYPE_CHECKING, Dict, Optional import grpc -from core.api.grpc.common_pb2 import ConfigOption -from core.api.grpc.core_pb2 import Node from core.gui.dialogs.dialog import Dialog from core.gui.themes import PADX, PADY from core.gui.widgets import ConfigFrame +from core.gui.wrappers import ConfigOption, Node if TYPE_CHECKING: from core.gui.app import Application diff --git a/daemon/core/gui/frames/link.py b/daemon/core/gui/frames/link.py index 57b1bf66..cbea9982 100644 --- a/daemon/core/gui/frames/link.py +++ b/daemon/core/gui/frames/link.py @@ -1,9 +1,9 @@ import tkinter as tk from typing import TYPE_CHECKING, Optional -from core.api.grpc.core_pb2 import Interface from core.gui.frames.base import DetailsFrame, InfoFrameBase from core.gui.utils import bandwidth_text +from core.gui.wrappers import Interface if TYPE_CHECKING: from core.gui.app import Application @@ -62,7 +62,7 @@ class EdgeInfoFrame(InfoFrameBase): ip6 = f"{iface2.ip6}/{iface2.ip6_mask}" if iface2.ip6 else "" frame.add_detail("IP6", ip6) - if link.HasField("options"): + if link.options: frame.add_separator() bandwidth = bandwidth_text(options.bandwidth) frame.add_detail("Bandwidth", bandwidth) diff --git a/daemon/core/gui/frames/node.py b/daemon/core/gui/frames/node.py index 7480e056..577cc489 100644 --- a/daemon/core/gui/frames/node.py +++ b/daemon/core/gui/frames/node.py @@ -1,8 +1,8 @@ from typing import TYPE_CHECKING -from core.api.grpc.core_pb2 import NodeType from core.gui.frames.base import DetailsFrame, InfoFrameBase from core.gui.nodeutils import NodeUtils +from core.gui.wrappers import NodeType if TYPE_CHECKING: from core.gui.app import Application diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index d9085910..610b6cc0 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -3,14 +3,13 @@ import math import tkinter as tk from typing import TYPE_CHECKING, Optional, Tuple -from core.api.grpc import core_pb2 -from core.api.grpc.core_pb2 import Interface, Link from core.gui import themes from core.gui.dialogs.linkconfig import LinkConfigurationDialog from core.gui.frames.link import EdgeInfoFrame, WirelessEdgeInfoFrame from core.gui.graph import tags from core.gui.nodeutils import NodeUtils from core.gui.utils import bandwidth_text +from core.gui.wrappers import Interface, Link if TYPE_CHECKING: from core.gui.graph.graph import CanvasGraph @@ -305,7 +304,7 @@ class CanvasEdge(Edge): self.link = link self.draw_labels() - def iface_label(self, iface: core_pb2.Interface) -> str: + def iface_label(self, iface: Interface) -> str: label = "" if iface.name and self.canvas.show_iface_names.get(): label = f"{iface.name}" @@ -319,10 +318,10 @@ class CanvasEdge(Edge): def create_node_labels(self) -> Tuple[str, str]: label1 = None - if self.link.HasField("iface1"): + if self.link.iface1: label1 = self.iface_label(self.link.iface1) label2 = None - if self.link.HasField("iface2"): + if self.link.iface2: label2 = self.iface_label(self.link.iface2) return label1, label2 @@ -417,6 +416,8 @@ class CanvasEdge(Edge): dialog.show() def draw_link_options(self): + if not self.link.options: + return options = self.link.options lines = [] bandwidth = options.bandwidth diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 56a31c3f..8b053c78 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -7,14 +7,7 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple from PIL import Image from PIL.ImageTk import PhotoImage -from core.api.grpc.core_pb2 import ( - Interface, - Link, - LinkType, - Node, - Session, - ThroughputsEvent, -) +from core.api.grpc.core_pb2 import ThroughputsEvent from core.gui.dialogs.shapemod import ShapeDialog from core.gui.graph import tags from core.gui.graph.edges import ( @@ -30,6 +23,7 @@ from core.gui.graph.shape import Shape from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker from core.gui.images import ImageEnum, TypeToImage from core.gui.nodeutils import NodeDraw, NodeUtils +from core.gui.wrappers import Interface, Link, LinkType, Node if TYPE_CHECKING: from core.gui.app import Application @@ -134,12 +128,7 @@ class CanvasGraph(tk.Canvas): ) self.configure(scrollregion=self.bbox(tk.ALL)) - def reset_and_redraw(self, session: Session) -> None: - """ - Reset the private variables CanvasGraph object, redraw nodes given the new grpc - client. - :param session: session to draw - """ + def reset_and_redraw(self, nodes: List[Node], links: List[Link]) -> None: # reset view options to default state self.show_node_labels.set(True) self.show_link_labels.set(True) @@ -164,7 +153,7 @@ class CanvasGraph(tk.Canvas): self.wireless_edges.clear() self.wireless_network.clear() self.drawing_edge = None - self.draw_session(session) + self.draw_session(nodes, links) def setup_bindings(self) -> None: """ @@ -251,12 +240,12 @@ class CanvasGraph(tk.Canvas): dst.edges.add(edge) self.edges[edge.token] = edge self.core.links[edge.token] = edge - if link.HasField("iface1"): + if link.iface1: iface1 = link.iface1 self.core.iface_to_edge[(node1.id, iface1.id)] = token src.ifaces[iface1.id] = iface1 edge.src_iface = iface1 - if link.HasField("iface2"): + if link.iface2: iface2 = link.iface2 self.core.iface_to_edge[(node2.id, iface2.id)] = edge.token dst.ifaces[iface2.id] = iface2 @@ -337,19 +326,19 @@ class CanvasGraph(tk.Canvas): self.nodes[node.id] = node self.core.canvas_nodes[core_node.id] = node - def draw_session(self, session: Session) -> None: + def draw_session(self, nodes: List[Node], links: List[Link]) -> None: """ Draw existing session. """ # draw existing nodes - for core_node in session.nodes: + for core_node in nodes: # peer to peer node is not drawn on the GUI if NodeUtils.is_ignore_node(core_node.type): continue self.add_core_node(core_node) # draw existing links - for link in session.links: + for link in links: logging.debug("drawing link: %s", link) canvas_node1 = self.core.canvas_nodes[link.node1_id] canvas_node2 = self.core.canvas_nodes[link.node2_id] @@ -987,12 +976,12 @@ class CanvasGraph(tk.Canvas): copy_edge = self.edges[token] copy_link = copy_edge.link options = edge.link.options - copy_link.options.CopyFrom(options) + copy_link.options = deepcopy(options) iface1_id = None - if copy_link.HasField("iface1"): + if copy_link.iface1: iface1_id = copy_link.iface1.id iface2_id = None - if copy_link.HasField("iface2"): + if copy_link.iface2: iface2_id = copy_link.iface2.id if not options.unidirectional: copy_edge.asymmetric_link = None diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index 7b5cd2f3..df6476c7 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -6,8 +6,6 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple import grpc from PIL.ImageTk import PhotoImage -from core.api.grpc.common_pb2 import ConfigOption -from core.api.grpc.core_pb2 import Interface, Node, NodeType from core.api.grpc.services_pb2 import NodeServiceData from core.gui import themes from core.gui.dialogs.emaneconfig import EmaneConfigDialog @@ -22,6 +20,7 @@ from core.gui.graph.edges import CanvasEdge, CanvasWirelessEdge from core.gui.graph.tooltip import CanvasTooltip from core.gui.images import ImageEnum from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils +from core.gui.wrappers import ConfigOption, Interface, Node, NodeType if TYPE_CHECKING: from core.gui.app import Application diff --git a/daemon/core/gui/images.py b/daemon/core/gui/images.py index 22719457..0a2f4d5d 100644 --- a/daemon/core/gui/images.py +++ b/daemon/core/gui/images.py @@ -5,8 +5,8 @@ from typing import Dict, Optional, Tuple from PIL import Image from PIL.ImageTk import PhotoImage -from core.api.grpc.core_pb2 import NodeType from core.gui.appconfig import LOCAL_ICONS_PATH +from core.gui.wrappers import NodeType class Images: diff --git a/daemon/core/gui/interface.py b/daemon/core/gui/interface.py index f4f2e3cc..f5b1461e 100644 --- a/daemon/core/gui/interface.py +++ b/daemon/core/gui/interface.py @@ -4,9 +4,9 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple import netaddr from netaddr import EUI, IPNetwork -from core.api.grpc.core_pb2 import Interface, Link, Node from core.gui.graph.node import CanvasNode from core.gui.nodeutils import NodeUtils +from core.gui.wrappers import Interface, Link, Node if TYPE_CHECKING: from core.gui.app import Application @@ -89,10 +89,10 @@ class InterfaceManager: remaining_subnets = set() for edge in self.app.core.links.values(): link = edge.link - if link.HasField("iface1"): + if link.iface1: subnets = self.get_subnets(link.iface1) remaining_subnets.add(subnets) - if link.HasField("iface2"): + if link.iface2: subnets = self.get_subnets(link.iface2) remaining_subnets.add(subnets) @@ -100,9 +100,9 @@ class InterfaceManager: # or remove used indexes from subnet ifaces = [] for link in links: - if link.HasField("iface1"): + if link.iface1: ifaces.append(link.iface1) - if link.HasField("iface2"): + if link.iface2: ifaces.append(link.iface2) for iface in ifaces: subnets = self.get_subnets(iface) @@ -117,9 +117,9 @@ class InterfaceManager: def joined(self, links: List[Link]) -> None: ifaces = [] for link in links: - if link.HasField("iface1"): + if link.iface1: ifaces.append(link.iface1) - if link.HasField("iface2"): + if link.iface2: ifaces.append(link.iface2) # add to used subnets and mark used indexes diff --git a/daemon/core/gui/nodeutils.py b/daemon/core/gui/nodeutils.py index 08c8f31c..6c451303 100644 --- a/daemon/core/gui/nodeutils.py +++ b/daemon/core/gui/nodeutils.py @@ -3,9 +3,9 @@ from typing import List, Optional, Set from PIL.ImageTk import PhotoImage -from core.api.grpc.core_pb2 import Node, NodeType from core.gui.appconfig import CustomNode, GuiConfig from core.gui.images import ImageEnum, Images, TypeToImage +from core.gui.wrappers import Node, NodeType ICON_SIZE: int = 48 ANTENNA_SIZE: int = 32 @@ -17,7 +17,7 @@ class NodeDraw: self.image: Optional[PhotoImage] = None self.image_enum: Optional[ImageEnum] = None self.image_file: Optional[str] = None - self.node_type: NodeType = None + self.node_type: Optional[NodeType] = None self.model: Optional[str] = None self.services: Set[str] = set() self.label: Optional[str] = None diff --git a/daemon/core/gui/statusbar.py b/daemon/core/gui/statusbar.py index 6989593e..d4304b6e 100644 --- a/daemon/core/gui/statusbar.py +++ b/daemon/core/gui/statusbar.py @@ -5,9 +5,9 @@ import tkinter as tk from tkinter import ttk from typing import TYPE_CHECKING, List, Optional -from core.api.grpc.core_pb2 import ExceptionEvent, ExceptionLevel from core.gui.dialogs.alerts import AlertsDialog from core.gui.themes import Styles +from core.gui.wrappers import ExceptionEvent, ExceptionLevel if TYPE_CHECKING: from core.gui.app import Application @@ -69,7 +69,7 @@ class StatusBar(ttk.Frame): def add_alert(self, event: ExceptionEvent) -> None: self.core_alarms.append(event) - level = event.exception_event.level + level = event.level self._set_alert_style(level) label = f"Alerts ({len(self.core_alarms)})" self.alerts_button.config(text=label, style=self.alert_style) diff --git a/daemon/core/gui/toolbar.py b/daemon/core/gui/toolbar.py index 968b447d..b7b67338 100644 --- a/daemon/core/gui/toolbar.py +++ b/daemon/core/gui/toolbar.py @@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Callable, List, Optional from PIL.ImageTk import PhotoImage -from core.api.grpc import core_pb2 from core.gui.dialogs.colorpicker import ColorPickerDialog from core.gui.dialogs.runtool import RunToolDialog from core.gui.graph import tags @@ -300,15 +299,15 @@ class Toolbar(ttk.Frame): ) task.start() - def start_callback(self, response: core_pb2.StartSessionResponse) -> None: - if response.result: + def start_callback(self, result: bool, exceptions: List[str]) -> None: + if result: self.set_runtime() self.app.core.set_metadata() self.app.core.show_mobility_players() else: enable_buttons(self.design_frame, enabled=True) - if response.exceptions: - message = "\n".join(response.exceptions) + if exceptions: + message = "\n".join(exceptions) self.app.show_error("Start Session Error", message) def set_runtime(self) -> None: @@ -405,7 +404,7 @@ class Toolbar(ttk.Frame): ) task.start() - def stop_callback(self, response: core_pb2.StopSessionResponse) -> None: + def stop_callback(self, result: bool) -> None: self.set_design() self.app.canvas.stopped_session() diff --git a/daemon/core/gui/widgets.py b/daemon/core/gui/widgets.py index 81bad0f5..85f3da10 100644 --- a/daemon/core/gui/widgets.py +++ b/daemon/core/gui/widgets.py @@ -5,12 +5,10 @@ from pathlib import Path from tkinter import filedialog, font, ttk from typing import TYPE_CHECKING, Any, Callable, Dict, Set, Type -from core.api.grpc import core_pb2 -from core.api.grpc.common_pb2 import ConfigOption -from core.api.grpc.core_pb2 import ConfigOptionType from core.gui import themes, validation from core.gui.dialogs.dialog import Dialog from core.gui.themes import FRAME_PAD, PADX, PADY +from core.gui.wrappers import ConfigOption, ConfigOptionType if TYPE_CHECKING: from core.gui.app import Application @@ -110,7 +108,7 @@ class ConfigFrame(ttk.Notebook): label = ttk.Label(tab.frame, text=option.label) label.grid(row=index, pady=PADY, padx=PADX, sticky="w") value = tk.StringVar() - if option.type == core_pb2.ConfigOptionType.BOOL: + if option.type == ConfigOptionType.BOOL: select = ("On", "Off") state = "readonly" if self.enabled else tk.DISABLED combobox = ttk.Combobox( @@ -129,7 +127,7 @@ class ConfigFrame(ttk.Notebook): tab.frame, textvariable=value, values=select, state=state ) combobox.grid(row=index, column=1, sticky="ew") - elif option.type == core_pb2.ConfigOptionType.STRING: + elif option.type == ConfigOptionType.STRING: value.set(option.value) state = tk.NORMAL if self.enabled else tk.DISABLED if "file" in option.label: @@ -153,7 +151,7 @@ class ConfigFrame(ttk.Notebook): tab.frame, textvariable=value, state=state ) entry.grid(row=index, column=1, sticky="ew") - elif option.type == core_pb2.ConfigOptionType.FLOAT: + elif option.type == ConfigOptionType.FLOAT: value.set(option.value) state = tk.NORMAL if self.enabled else tk.DISABLED entry = validation.PositiveFloatEntry( @@ -169,7 +167,7 @@ class ConfigFrame(ttk.Notebook): option = self.config[key] value = self.values[key] config_value = value.get() - if option.type == core_pb2.ConfigOptionType.BOOL: + if option.type == ConfigOptionType.BOOL: if config_value == "On": option.value = "1" else: @@ -182,7 +180,7 @@ class ConfigFrame(ttk.Notebook): for name, data in config.items(): option = self.config[name] value = self.values[name] - if option.type == core_pb2.ConfigOptionType.BOOL: + if option.type == ConfigOptionType.BOOL: if data == "1": data = "On" else: diff --git a/daemon/core/gui/wrappers.py b/daemon/core/gui/wrappers.py index 217ab321..f72cbac4 100644 --- a/daemon/core/gui/wrappers.py +++ b/daemon/core/gui/wrappers.py @@ -1,8 +1,22 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from enum import Enum -from typing import List +from typing import Dict, List -from core.api.grpc import core_pb2 +from core.api.grpc import common_pb2, core_pb2 + + +class ConfigOptionType(Enum): + UINT8 = 1 + UINT16 = 2 + UINT32 = 3 + UINT64 = 4 + INT8 = 5 + INT16 = 6 + INT32 = 7 + INT64 = 8 + FLOAT = 9 + STRING = 10 + BOOL = 11 class SessionState(Enum): @@ -30,6 +44,292 @@ class NodeType(Enum): LXC = 16 +class LinkType(Enum): + WIRELESS = 0 + WIRED = 1 + + +class ExceptionLevel(Enum): + DEFAULT = 0 + FATAL = 1 + ERROR = 2 + WARNING = 3 + NOTICE = 4 + + +class MessageType(Enum): + NONE = 0 + ADD = 1 + DELETE = 2 + CRI = 4 + LOCAL = 8 + STRING = 16 + TEXT = 32 + TTY = 64 + + +@dataclass +class SessionLocation: + x: float + y: float + z: float + lat: float + lon: float + alt: float + scale: float + + @classmethod + def from_proto(cls, location: core_pb2.SessionLocation) -> "SessionLocation": + return SessionLocation( + x=location.x, + y=location.y, + z=location.z, + lat=location.lat, + lon=location.lon, + alt=location.alt, + scale=location.scale, + ) + + def to_proto(self) -> core_pb2.SessionLocation: + return core_pb2.SessionLocation( + x=self.x, + y=self.y, + z=self.z, + lat=self.lat, + lon=self.lon, + alt=self.alt, + scale=self.scale, + ) + + +@dataclass +class ExceptionEvent: + session_id: int + node_id: int + level: ExceptionLevel + source: str + date: str + text: str + opaque: str + + @classmethod + def from_proto( + cls, session_id: int, event: core_pb2.ExceptionEvent + ) -> "ExceptionEvent": + return ExceptionEvent( + session_id=session_id, + node_id=event.node_id, + level=ExceptionLevel(event.level), + source=event.source, + date=event.date, + text=event.text, + opaque=event.opaque, + ) + + +@dataclass +class ConfigOption: + label: str + name: str + value: str + type: ConfigOptionType + group: str + select: List[str] = None + + @classmethod + def from_dict( + cls, config: Dict[str, common_pb2.ConfigOption] + ) -> Dict[str, "ConfigOption"]: + d = {} + for key, value in config.items(): + d[key] = ConfigOption.from_proto(value) + return d + + @classmethod + def to_dict(cls, config: Dict[str, "ConfigOption"]) -> Dict[str, str]: + return {k: v.value for k, v in config.items()} + + @classmethod + def from_proto(cls, option: common_pb2.ConfigOption) -> "ConfigOption": + return ConfigOption( + label=option.label, + name=option.name, + value=option.value, + type=ConfigOptionType(option.type), + group=option.group, + select=option.select, + ) + + +@dataclass +class Interface: + id: int + name: str = None + mac: str = None + ip4: str = None + ip4_mask: int = None + ip6: str = None + ip6_mask: int = None + net_id: int = None + flow_id: int = None + mtu: int = None + node_id: int = None + net2_id: int = None + + @classmethod + def from_proto(cls, iface: core_pb2.Interface) -> "Interface": + return Interface( + id=iface.id, + name=iface.name, + mac=iface.mac, + ip4=iface.ip4, + ip4_mask=iface.ip4_mask, + ip6=iface.ip6, + ip6_mask=iface.ip6_mask, + net_id=iface.net_id, + flow_id=iface.flow_id, + mtu=iface.mtu, + node_id=iface.node_id, + net2_id=iface.net2_id, + ) + + def to_proto(self) -> core_pb2.Interface: + return core_pb2.Interface( + id=self.id, + name=self.name, + mac=self.mac, + ip4=self.ip4, + ip4_mask=self.ip4_mask, + ip6=self.ip6, + ip6_mask=self.ip6_mask, + net_id=self.net_id, + flow_id=self.flow_id, + mtu=self.mtu, + node_id=self.node_id, + net2_id=self.net2_id, + ) + + +@dataclass +class LinkOptions: + jitter: int = 0 + key: int = 0 + mburst: int = 0 + mer: int = 0 + loss: float = 0.0 + bandwidth: int = 0 + burst: int = 0 + delay: int = 0 + dup: int = 0 + unidirectional: bool = False + + @classmethod + def from_proto(cls, options: core_pb2.LinkOptions) -> "LinkOptions": + return LinkOptions( + jitter=options.jitter, + key=options.key, + mburst=options.mburst, + mer=options.mer, + loss=options.loss, + bandwidth=options.bandwidth, + burst=options.burst, + delay=options.delay, + dup=options.dup, + unidirectional=options.unidirectional, + ) + + def to_proto(self) -> core_pb2.LinkOptions: + return core_pb2.LinkOptions( + jitter=self.jitter, + key=self.key, + mburst=self.mburst, + mer=self.mer, + loss=self.loss, + bandwidth=self.bandwidth, + burst=self.burst, + delay=self.delay, + dup=self.dup, + unidirectional=self.unidirectional, + ) + + +@dataclass +class Link: + node1_id: int + node2_id: int + type: LinkType = LinkType.WIRED + iface1: Interface = None + iface2: Interface = None + options: LinkOptions = None + network_id: int = None + label: str = None + color: str = None + + @classmethod + def from_proto(cls, link: core_pb2.Link) -> "Link": + iface1 = None + if link.HasField("iface1"): + iface1 = Interface.from_proto(link.iface1) + iface2 = None + if link.HasField("iface2"): + iface2 = Interface.from_proto(link.iface2) + options = None + if link.HasField("options"): + options = LinkOptions.from_proto(link.options) + return Link( + type=LinkType(link.type), + node1_id=link.node1_id, + node2_id=link.node2_id, + iface1=iface1, + iface2=iface2, + options=options, + network_id=link.network_id, + label=link.label, + color=link.color, + ) + + def to_proto(self) -> core_pb2.Link: + iface1 = self.iface1.to_proto() if self.iface1 else None + iface2 = self.iface2.to_proto() if self.iface2 else None + options = self.options.to_proto() if self.options else None + return core_pb2.Link( + type=self.type.value, + node1_id=self.node1_id, + node2_id=self.node2_id, + iface1=iface1, + iface2=iface2, + options=options, + network_id=self.network_id, + label=self.label, + color=self.color, + ) + + def is_symmetric(self) -> bool: + result = True + if self.options: + result = self.options.unidirectional is False + return result + + +@dataclass +class SessionSummary: + id: int + state: SessionState + nodes: int + file: str + dir: str + + @classmethod + def from_proto(cls, summary: core_pb2.SessionSummary) -> "SessionSummary": + return SessionSummary( + id=summary.id, + state=SessionState(summary.state), + nodes=summary.nodes, + file=summary.file, + dir=summary.dir, + ) + + @dataclass class Hook: state: SessionState @@ -78,8 +378,8 @@ class Node: type: NodeType model: str = None position: Position = None - services: List[str] = None - config_services: List[str] = None + services: List[str] = field(default_factory=list) + config_services: List[str] = field(default_factory=list) emane: str = None icon: str = None image: str = None @@ -120,7 +420,32 @@ class Node: icon=self.icon, image=self.image, server=self.server, - geo=self.geo.to_proto(), dir=self.dir, channel=self.channel, ) + + +@dataclass +class LinkEvent: + message_type: MessageType + link: Link + + @classmethod + def from_proto(cls, event: core_pb2.LinkEvent) -> "LinkEvent": + return LinkEvent( + message_type=MessageType(event.message_type), + link=Link.from_proto(event.link), + ) + + +@dataclass +class NodeEvent: + message_type: MessageType + node: Node + + @classmethod + def from_proto(cls, event: core_pb2.NodeEvent) -> "NodeEvent": + return NodeEvent( + message_type=MessageType(event.message_type), + node=Node.from_proto(event.node), + ) From a9a2fb8e46080770947bb5b0953c38644f54c56b Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 25 Jul 2020 19:43:24 -0700 Subject: [PATCH 013/598] pygui: added wrapper for throughput events, fixed sending nodes/links for configuration --- daemon/core/gui/coreclient.py | 17 +++++++-------- daemon/core/gui/graph/graph.py | 3 +-- daemon/core/gui/wrappers.py | 40 ++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index c41caeca..099ce043 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -12,9 +12,9 @@ from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Set, Tuple import grpc -from core.api.grpc import client +from core.api.grpc import client, core_pb2 from core.api.grpc.configservices_pb2 import ConfigService, ConfigServiceConfig -from core.api.grpc.core_pb2 import CpuUsageEvent, Event, ThroughputsEvent +from core.api.grpc.core_pb2 import CpuUsageEvent, Event from core.api.grpc.emane_pb2 import EmaneModelConfig from core.api.grpc.mobility_pb2 import MobilityConfig from core.api.grpc.services_pb2 import NodeServiceData, ServiceConfig, ServiceFileConfig @@ -46,6 +46,7 @@ from core.gui.wrappers import ( Position, SessionLocation, SessionState, + ThroughputsEvent, ) if TYPE_CHECKING: @@ -275,7 +276,8 @@ class CoreClient: CPU_USAGE_DELAY, self.handle_cpu_event ) - def handle_throughputs(self, event: ThroughputsEvent) -> None: + def handle_throughputs(self, event: core_pb2.ThroughputsEvent) -> None: + event = ThroughputsEvent.from_proto(event) if event.session_id != self.session_id: logging.warning( "ignoring throughput event session(%s) current(%s)", @@ -776,12 +778,9 @@ class CoreClient: """ create nodes and links that have not been created yet """ - node_protos = [x.core_node for x in self.canvas_nodes.values()] - link_protos = [x.link for x in self.links.values()] - if self.state != SessionState.DEFINITION: - self.client.set_session_state(self.session_id, SessionState.DEFINITION) - - self.client.set_session_state(self.session_id, SessionState.DEFINITION) + node_protos = [x.core_node.to_proto() for x in self.canvas_nodes.values()] + link_protos = [x.link.to_proto() for x in self.links.values()] + self.client.set_session_state(self.session_id, SessionState.DEFINITION.value) for node_proto in node_protos: response = self.client.add_node(self.session_id, node_proto) logging.debug("create node: %s", response) diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 8b053c78..ae0b00c0 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple from PIL import Image from PIL.ImageTk import PhotoImage -from core.api.grpc.core_pb2 import ThroughputsEvent from core.gui.dialogs.shapemod import ShapeDialog from core.gui.graph import tags from core.gui.graph.edges import ( @@ -23,7 +22,7 @@ from core.gui.graph.shape import Shape from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker from core.gui.images import ImageEnum, TypeToImage from core.gui.nodeutils import NodeDraw, NodeUtils -from core.gui.wrappers import Interface, Link, LinkType, Node +from core.gui.wrappers import Interface, Link, LinkType, Node, ThroughputsEvent if TYPE_CHECKING: from core.gui.app import Application diff --git a/daemon/core/gui/wrappers.py b/daemon/core/gui/wrappers.py index f72cbac4..4098a4df 100644 --- a/daemon/core/gui/wrappers.py +++ b/daemon/core/gui/wrappers.py @@ -68,6 +68,46 @@ class MessageType(Enum): TTY = 64 +@dataclass +class BridgeThroughput: + node_id: int + throughput: float + + @classmethod + def from_proto(cls, proto: core_pb2.BridgeThroughput) -> "BridgeThroughput": + return BridgeThroughput(node_id=proto.node_id, throughput=proto.throughput) + + +@dataclass +class InterfaceThroughput: + node_id: int + iface_id: int + throughput: float + + @classmethod + def from_proto(cls, proto: core_pb2.InterfaceThroughput) -> "InterfaceThroughput": + return InterfaceThroughput( + node_id=proto.node_id, iface_id=proto.iface_id, throughput=proto.throughput + ) + + +@dataclass +class ThroughputsEvent: + session_id: int + bridge_throughputs: List[BridgeThroughput] + iface_throughputs: List[InterfaceThroughput] + + @classmethod + def from_proto(cls, proto: core_pb2.ThroughputsEvent) -> "ThroughputsEvent": + bridges = [BridgeThroughput.from_proto(x) for x in proto.bridge_throughputs] + ifaces = [InterfaceThroughput.from_proto(x) for x in proto.iface_throughputs] + return ThroughputsEvent( + session_id=proto.session_id, + bridge_throughputs=bridges, + iface_throughputs=ifaces, + ) + + @dataclass class SessionLocation: x: float From 82a212d1cfc409ba0fa754e6d032ef12550d6743 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 25 Jul 2020 20:27:11 -0700 Subject: [PATCH 014/598] pygui: modified usages of protobufs within coreclient to use module namespace to make more obvious, replaced config services and services with wrappers --- daemon/core/gui/coreclient.py | 56 ++-- .../core/gui/dialogs/configserviceconfig.py | 3 +- daemon/core/gui/dialogs/mobilityplayer.py | 9 +- daemon/core/gui/dialogs/serviceconfig.py | 4 +- daemon/core/gui/graph/node.py | 3 +- daemon/core/gui/wrappers.py | 266 ++++++++++++------ 6 files changed, 213 insertions(+), 128 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 099ce043..fd1abc34 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -12,13 +12,15 @@ from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Set, Tuple import grpc -from core.api.grpc import client, core_pb2 -from core.api.grpc.configservices_pb2 import ConfigService, ConfigServiceConfig -from core.api.grpc.core_pb2 import CpuUsageEvent, Event -from core.api.grpc.emane_pb2 import EmaneModelConfig -from core.api.grpc.mobility_pb2 import MobilityConfig -from core.api.grpc.services_pb2 import NodeServiceData, ServiceConfig, ServiceFileConfig -from core.api.grpc.wlan_pb2 import WlanConfig +from core.api.grpc import ( + client, + configservices_pb2, + core_pb2, + emane_pb2, + mobility_pb2, + services_pb2, + wlan_pb2, +) from core.gui import appconfig from core.gui.appconfig import CoreServer, Observer from core.gui.dialogs.emaneinstall import EmaneInstallDialog @@ -33,6 +35,7 @@ from core.gui.interface import InterfaceManager from core.gui.nodeutils import NodeDraw, NodeUtils from core.gui.wrappers import ( ConfigOption, + ConfigService, ExceptionEvent, Hook, Interface, @@ -42,6 +45,7 @@ from core.gui.wrappers import ( MessageType, Node, NodeEvent, + NodeServiceData, NodeType, Position, SessionLocation, @@ -150,7 +154,7 @@ class CoreClient: for observer in self.app.guiconfig.observers: self.custom_observers[observer.name] = observer - def handle_events(self, event: Event) -> None: + def handle_events(self, event: core_pb2.Event) -> None: if event.source == GUI_SOURCE: return if event.session_id != self.session_id: @@ -288,7 +292,7 @@ class CoreClient: logging.debug("handling throughputs event: %s", event) self.app.after(0, self.app.canvas.set_throughputs, event) - def handle_cpu_event(self, event: CpuUsageEvent) -> None: + def handle_cpu_event(self, event: core_pb2.CpuUsageEvent) -> None: self.app.after(0, self.app.statusbar.set_cpu, event.usage) def handle_exception_event(self, event: ExceptionEvent) -> None: @@ -514,7 +518,7 @@ class CoreClient: # get config service informations response = self.client.get_config_services() for service in response.services: - self.config_services[service.name] = service + self.config_services[service.name] = ConfigService.from_proto(service) group_services = self.config_services_groups.setdefault( service.group, set() ) @@ -708,7 +712,7 @@ class CoreClient: logging.debug( "get node(%s) %s service, response: %s", node_id, service_name, response ) - return response.service + return NodeServiceData.from_proto(response.service) def set_node_service( self, @@ -742,7 +746,7 @@ class CoreClient: response, ) response = self.client.get_node_service(self.session_id, node_id, service_name) - return response.service + return NodeServiceData.from_proto(response.service) def get_node_service_file( self, node_id: int, service_name: str, file_name: str @@ -1000,7 +1004,7 @@ class CoreClient: self.links[edge.token] = edge logging.info("Add link between %s and %s", src_node.name, dst_node.name) - def get_wlan_configs_proto(self) -> List[WlanConfig]: + def get_wlan_configs_proto(self) -> List[wlan_pb2.WlanConfig]: configs = [] for canvas_node in self.canvas_nodes.values(): if canvas_node.core_node.type != NodeType.WIRELESS_LAN: @@ -1009,11 +1013,11 @@ class CoreClient: continue config = ConfigOption.to_dict(canvas_node.wlan_config) node_id = canvas_node.core_node.id - wlan_config = WlanConfig(node_id=node_id, config=config) + wlan_config = wlan_pb2.WlanConfig(node_id=node_id, config=config) configs.append(wlan_config) return configs - def get_mobility_configs_proto(self) -> List[MobilityConfig]: + def get_mobility_configs_proto(self) -> List[mobility_pb2.MobilityConfig]: configs = [] for canvas_node in self.canvas_nodes.values(): if canvas_node.core_node.type != NodeType.WIRELESS_LAN: @@ -1022,11 +1026,13 @@ class CoreClient: continue config = ConfigOption.to_dict(canvas_node.mobility_config) node_id = canvas_node.core_node.id - mobility_config = MobilityConfig(node_id=node_id, config=config) + mobility_config = mobility_pb2.MobilityConfig( + node_id=node_id, config=config + ) configs.append(mobility_config) return configs - def get_emane_model_configs_proto(self) -> List[EmaneModelConfig]: + def get_emane_model_configs_proto(self) -> List[emane_pb2.EmaneModelConfig]: configs = [] for canvas_node in self.canvas_nodes.values(): if canvas_node.core_node.type != NodeType.EMANE: @@ -1037,13 +1043,13 @@ class CoreClient: config = ConfigOption.to_dict(config) if iface_id is None: iface_id = -1 - config_proto = EmaneModelConfig( + config_proto = emane_pb2.EmaneModelConfig( node_id=node_id, iface_id=iface_id, model=model, config=config ) configs.append(config_proto) return configs - def get_service_configs_proto(self) -> List[ServiceConfig]: + def get_service_configs_proto(self) -> List[services_pb2.ServiceConfig]: configs = [] for canvas_node in self.canvas_nodes.values(): if not NodeUtils.is_container_node(canvas_node.core_node.type): @@ -1052,7 +1058,7 @@ class CoreClient: continue node_id = canvas_node.core_node.id for name, config in canvas_node.service_configs.items(): - config_proto = ServiceConfig( + config_proto = services_pb2.ServiceConfig( node_id=node_id, service=name, directories=config.dirs, @@ -1064,7 +1070,7 @@ class CoreClient: configs.append(config_proto) return configs - def get_service_file_configs_proto(self) -> List[ServiceFileConfig]: + def get_service_file_configs_proto(self) -> List[services_pb2.ServiceFileConfig]: configs = [] for canvas_node in self.canvas_nodes.values(): if not NodeUtils.is_container_node(canvas_node.core_node.type): @@ -1074,13 +1080,15 @@ class CoreClient: 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( + config_proto = services_pb2.ServiceFileConfig( node_id=node_id, service=service, file=file, data=data ) configs.append(config_proto) return configs - def get_config_service_configs_proto(self) -> List[ConfigServiceConfig]: + def get_config_service_configs_proto( + self + ) -> List[configservices_pb2.ConfigServiceConfig]: config_service_protos = [] for canvas_node in self.canvas_nodes.values(): if not NodeUtils.is_container_node(canvas_node.core_node.type): @@ -1090,7 +1098,7 @@ class CoreClient: 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 = ConfigServiceConfig( + config_proto = configservices_pb2.ConfigServiceConfig( node_id=node_id, name=name, templates=service_config["templates"], diff --git a/daemon/core/gui/dialogs/configserviceconfig.py b/daemon/core/gui/dialogs/configserviceconfig.py index 5a6a89a8..5463d88e 100644 --- a/daemon/core/gui/dialogs/configserviceconfig.py +++ b/daemon/core/gui/dialogs/configserviceconfig.py @@ -8,11 +8,10 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set import grpc -from core.api.grpc.services_pb2 import ServiceValidationMode from core.gui.dialogs.dialog import Dialog from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.widgets import CodeText, ConfigFrame, ListboxScroll -from core.gui.wrappers import ConfigOption +from core.gui.wrappers import ConfigOption, ServiceValidationMode if TYPE_CHECKING: from core.gui.app import Application diff --git a/daemon/core/gui/dialogs/mobilityplayer.py b/daemon/core/gui/dialogs/mobilityplayer.py index 16aa8ea0..66833aff 100644 --- a/daemon/core/gui/dialogs/mobilityplayer.py +++ b/daemon/core/gui/dialogs/mobilityplayer.py @@ -4,11 +4,10 @@ from typing import TYPE_CHECKING, Dict, Optional import grpc -from core.api.grpc.mobility_pb2 import MobilityAction from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum from core.gui.themes import PADX, PADY -from core.gui.wrappers import ConfigOption, Node +from core.gui.wrappers import ConfigOption, MobilityAction, Node if TYPE_CHECKING: from core.gui.app import Application @@ -150,7 +149,7 @@ class MobilityPlayerDialog(Dialog): session_id = self.app.core.session_id try: self.app.core.client.mobility_action( - session_id, self.node.id, MobilityAction.START + session_id, self.node.id, MobilityAction.START.value ) except grpc.RpcError as e: self.app.show_grpc_exception("Mobility Error", e) @@ -160,7 +159,7 @@ class MobilityPlayerDialog(Dialog): session_id = self.app.core.session_id try: self.app.core.client.mobility_action( - session_id, self.node.id, MobilityAction.PAUSE + session_id, self.node.id, MobilityAction.PAUSE.value ) except grpc.RpcError as e: self.app.show_grpc_exception("Mobility Error", e) @@ -170,7 +169,7 @@ class MobilityPlayerDialog(Dialog): session_id = self.app.core.session_id try: self.app.core.client.mobility_action( - session_id, self.node.id, MobilityAction.STOP + session_id, self.node.id, MobilityAction.STOP.value ) except grpc.RpcError as e: self.app.show_grpc_exception("Mobility Error", e) diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index 4e615db0..c033cfdc 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -7,12 +7,12 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple import grpc from PIL.ImageTk import PhotoImage -from core.api.grpc.services_pb2 import NodeServiceData, ServiceValidationMode from core.gui.dialogs.copyserviceconfig import CopyServiceConfigDialog from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum, Images from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.widgets import CodeText, ListboxScroll +from core.gui.wrappers import NodeServiceData, ServiceValidationMode if TYPE_CHECKING: from core.gui.app import Application @@ -72,7 +72,7 @@ class ServiceConfigDialog(Dialog): self.service_file_data: Optional[CodeText] = None self.validation_period_entry: Optional[ttk.Entry] = None self.original_service_files: Dict[str, str] = {} - self.default_config: NodeServiceData = None + self.default_config: Optional[NodeServiceData] = None self.temp_service_files: Dict[str, str] = {} self.modified_files: Set[str] = set() self.has_error: bool = False diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index df6476c7..217389c0 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -6,7 +6,6 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple import grpc from PIL.ImageTk import PhotoImage -from core.api.grpc.services_pb2 import NodeServiceData from core.gui import themes from core.gui.dialogs.emaneconfig import EmaneConfigDialog from core.gui.dialogs.mobilityconfig import MobilityConfigDialog @@ -20,7 +19,7 @@ from core.gui.graph.edges import CanvasEdge, CanvasWirelessEdge from core.gui.graph.tooltip import CanvasTooltip from core.gui.images import ImageEnum from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils -from core.gui.wrappers import ConfigOption, Interface, Node, NodeType +from core.gui.wrappers import ConfigOption, Interface, Node, NodeServiceData, NodeType if TYPE_CHECKING: from core.gui.app import Application diff --git a/daemon/core/gui/wrappers.py b/daemon/core/gui/wrappers.py index 4098a4df..5fb12837 100644 --- a/daemon/core/gui/wrappers.py +++ b/daemon/core/gui/wrappers.py @@ -2,7 +2,25 @@ from dataclasses import dataclass, field from enum import Enum from typing import Dict, List -from core.api.grpc import common_pb2, core_pb2 +from core.api.grpc import common_pb2, configservices_pb2, core_pb2, services_pb2 + + +class ConfigServiceValidationMode(Enum): + BLOCKING = 0 + NON_BLOCKING = 1 + TIMER = 2 + + +class ServiceValidationMode(Enum): + BLOCKING = 0 + NON_BLOCKING = 1 + TIMER = 2 + + +class MobilityAction(Enum): + START = 0 + PAUSE = 1 + STOP = 2 class ConfigOptionType(Enum): @@ -68,6 +86,68 @@ class MessageType(Enum): TTY = 64 +@dataclass +class ConfigService: + group: str + name: str + executables: List[str] + dependencies: List[str] + directories: List[str] + files: List[str] + startup: List[str] + validate: List[str] + shutdown: List[str] + validation_mode: ConfigServiceValidationMode + validation_timer: int + validation_period: float + + @classmethod + def from_proto(cls, proto: configservices_pb2.ConfigService) -> "ConfigService": + return ConfigService( + group=proto.group, + name=proto.name, + executables=proto.executables, + dependencies=proto.dependencies, + directories=proto.directories, + files=proto.files, + startup=proto.startup, + validate=proto.validate, + shutdown=proto.shutdown, + validation_mode=ConfigServiceValidationMode(proto.validation_mode), + validation_timer=proto.validation_timer, + validation_period=proto.validation_period, + ) + + +@dataclass +class NodeServiceData: + executables: List[str] + dependencies: List[str] + dirs: List[str] + configs: List[str] + startup: List[str] + validate: List[str] + validation_mode: ServiceValidationMode + validation_timer: int + shutdown: List[str] + meta: str + + @classmethod + def from_proto(cls, proto: services_pb2.NodeServiceData) -> "NodeServiceData": + return NodeServiceData( + executables=proto.executables, + dependencies=proto.dependencies, + dirs=proto.dirs, + configs=proto.configs, + startup=proto.startup, + validate=proto.validate, + validation_mode=proto.validation_mode, + validation_timer=proto.validation_timer, + shutdown=proto.shutdown, + meta=proto.meta, + ) + + @dataclass class BridgeThroughput: node_id: int @@ -119,15 +199,15 @@ class SessionLocation: scale: float @classmethod - def from_proto(cls, location: core_pb2.SessionLocation) -> "SessionLocation": + def from_proto(cls, proto: core_pb2.SessionLocation) -> "SessionLocation": return SessionLocation( - x=location.x, - y=location.y, - z=location.z, - lat=location.lat, - lon=location.lon, - alt=location.alt, - scale=location.scale, + x=proto.x, + y=proto.y, + z=proto.z, + lat=proto.lat, + lon=proto.lon, + alt=proto.alt, + scale=proto.scale, ) def to_proto(self) -> core_pb2.SessionLocation: @@ -154,16 +234,16 @@ class ExceptionEvent: @classmethod def from_proto( - cls, session_id: int, event: core_pb2.ExceptionEvent + cls, session_id: int, proto: core_pb2.ExceptionEvent ) -> "ExceptionEvent": return ExceptionEvent( session_id=session_id, - node_id=event.node_id, - level=ExceptionLevel(event.level), - source=event.source, - date=event.date, - text=event.text, - opaque=event.opaque, + node_id=proto.node_id, + level=ExceptionLevel(proto.level), + source=proto.source, + date=proto.date, + text=proto.text, + opaque=proto.opaque, ) @@ -190,14 +270,14 @@ class ConfigOption: return {k: v.value for k, v in config.items()} @classmethod - def from_proto(cls, option: common_pb2.ConfigOption) -> "ConfigOption": + def from_proto(cls, proto: common_pb2.ConfigOption) -> "ConfigOption": return ConfigOption( - label=option.label, - name=option.name, - value=option.value, - type=ConfigOptionType(option.type), - group=option.group, - select=option.select, + label=proto.label, + name=proto.name, + value=proto.value, + type=ConfigOptionType(proto.type), + group=proto.group, + select=proto.select, ) @@ -217,20 +297,20 @@ class Interface: net2_id: int = None @classmethod - def from_proto(cls, iface: core_pb2.Interface) -> "Interface": + def from_proto(cls, proto: core_pb2.Interface) -> "Interface": return Interface( - id=iface.id, - name=iface.name, - mac=iface.mac, - ip4=iface.ip4, - ip4_mask=iface.ip4_mask, - ip6=iface.ip6, - ip6_mask=iface.ip6_mask, - net_id=iface.net_id, - flow_id=iface.flow_id, - mtu=iface.mtu, - node_id=iface.node_id, - net2_id=iface.net2_id, + id=proto.id, + name=proto.name, + mac=proto.mac, + ip4=proto.ip4, + ip4_mask=proto.ip4_mask, + ip6=proto.ip6, + ip6_mask=proto.ip6_mask, + net_id=proto.net_id, + flow_id=proto.flow_id, + mtu=proto.mtu, + node_id=proto.node_id, + net2_id=proto.net2_id, ) def to_proto(self) -> core_pb2.Interface: @@ -264,18 +344,18 @@ class LinkOptions: unidirectional: bool = False @classmethod - def from_proto(cls, options: core_pb2.LinkOptions) -> "LinkOptions": + def from_proto(cls, proto: core_pb2.LinkOptions) -> "LinkOptions": return LinkOptions( - jitter=options.jitter, - key=options.key, - mburst=options.mburst, - mer=options.mer, - loss=options.loss, - bandwidth=options.bandwidth, - burst=options.burst, - delay=options.delay, - dup=options.dup, - unidirectional=options.unidirectional, + jitter=proto.jitter, + key=proto.key, + mburst=proto.mburst, + mer=proto.mer, + loss=proto.loss, + bandwidth=proto.bandwidth, + burst=proto.burst, + delay=proto.delay, + dup=proto.dup, + unidirectional=proto.unidirectional, ) def to_proto(self) -> core_pb2.LinkOptions: @@ -306,26 +386,26 @@ class Link: color: str = None @classmethod - def from_proto(cls, link: core_pb2.Link) -> "Link": + def from_proto(cls, proto: core_pb2.Link) -> "Link": iface1 = None - if link.HasField("iface1"): - iface1 = Interface.from_proto(link.iface1) + if proto.HasField("iface1"): + iface1 = Interface.from_proto(proto.iface1) iface2 = None - if link.HasField("iface2"): - iface2 = Interface.from_proto(link.iface2) + if proto.HasField("iface2"): + iface2 = Interface.from_proto(proto.iface2) options = None - if link.HasField("options"): - options = LinkOptions.from_proto(link.options) + if proto.HasField("options"): + options = LinkOptions.from_proto(proto.options) return Link( - type=LinkType(link.type), - node1_id=link.node1_id, - node2_id=link.node2_id, + type=LinkType(proto.type), + node1_id=proto.node1_id, + node2_id=proto.node2_id, iface1=iface1, iface2=iface2, options=options, - network_id=link.network_id, - label=link.label, - color=link.color, + network_id=proto.network_id, + label=proto.label, + color=proto.color, ) def to_proto(self) -> core_pb2.Link: @@ -360,13 +440,13 @@ class SessionSummary: dir: str @classmethod - def from_proto(cls, summary: core_pb2.SessionSummary) -> "SessionSummary": + def from_proto(cls, proto: core_pb2.SessionSummary) -> "SessionSummary": return SessionSummary( - id=summary.id, - state=SessionState(summary.state), - nodes=summary.nodes, - file=summary.file, - dir=summary.dir, + id=proto.id, + state=SessionState(proto.state), + nodes=proto.nodes, + file=proto.file, + dir=proto.dir, ) @@ -377,8 +457,8 @@ class Hook: data: str @classmethod - def from_proto(cls, hook: core_pb2.Hook) -> "Hook": - return Hook(state=SessionState(hook.state), file=hook.file, data=hook.data) + def from_proto(cls, proto: core_pb2.Hook) -> "Hook": + return Hook(state=SessionState(proto.state), file=proto.file, data=proto.data) def to_proto(self) -> core_pb2.Hook: return core_pb2.Hook(state=self.state.value, file=self.file, data=self.data) @@ -390,8 +470,8 @@ class Position: y: float @classmethod - def from_proto(cls, position: core_pb2.Position) -> "Position": - return Position(x=position.x, y=position.y) + def from_proto(cls, proto: core_pb2.Position) -> "Position": + return Position(x=proto.x, y=proto.y) def to_proto(self) -> core_pb2.Position: return core_pb2.Position(x=self.x, y=self.y) @@ -404,8 +484,8 @@ class Geo: alt: float = None @classmethod - def from_proto(cls, geo: core_pb2.Geo) -> "Geo": - return Geo(lat=geo.lat, lon=geo.lon, alt=geo.alt) + def from_proto(cls, proto: core_pb2.Geo) -> "Geo": + return Geo(lat=proto.lat, lon=proto.lon, alt=proto.alt) def to_proto(self) -> core_pb2.Geo: return core_pb2.Geo(lat=self.lat, lon=self.lon, alt=self.alt) @@ -429,22 +509,22 @@ class Node: channel: str = None @classmethod - def from_proto(cls, node: core_pb2.Node) -> "Node": + def from_proto(cls, proto: core_pb2.Node) -> "Node": return Node( - id=node.id, - name=node.name, - type=NodeType(node.type), - model=node.model, - position=Position.from_proto(node.position), - services=list(node.services), - config_services=list(node.config_services), - emane=node.emane, - icon=node.icon, - image=node.image, - server=node.server, - geo=Geo.from_proto(node.geo), - dir=node.dir, - channel=node.channel, + id=proto.id, + name=proto.name, + type=NodeType(proto.type), + model=proto.model, + position=Position.from_proto(proto.position), + services=list(proto.services), + config_services=list(proto.config_services), + emane=proto.emane, + icon=proto.icon, + image=proto.image, + server=proto.server, + geo=Geo.from_proto(proto.geo), + dir=proto.dir, + channel=proto.channel, ) def to_proto(self) -> core_pb2.Node: @@ -471,10 +551,10 @@ class LinkEvent: link: Link @classmethod - def from_proto(cls, event: core_pb2.LinkEvent) -> "LinkEvent": + def from_proto(cls, proto: core_pb2.LinkEvent) -> "LinkEvent": return LinkEvent( - message_type=MessageType(event.message_type), - link=Link.from_proto(event.link), + message_type=MessageType(proto.message_type), + link=Link.from_proto(proto.link), ) @@ -484,8 +564,8 @@ class NodeEvent: node: Node @classmethod - def from_proto(cls, event: core_pb2.NodeEvent) -> "NodeEvent": + def from_proto(cls, proto: core_pb2.NodeEvent) -> "NodeEvent": return NodeEvent( - message_type=MessageType(event.message_type), - node=Node.from_proto(event.node), + message_type=MessageType(proto.message_type), + node=Node.from_proto(proto.node), ) From 41a3c5fd7fa2d2de040b39f93b721cb80b3da9e2 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sun, 26 Jul 2020 11:45:40 -0700 Subject: [PATCH 015/598] pygui: added wrapper class for sessions returned by grpc GetSession --- daemon/core/gui/coreclient.py | 9 ++++----- daemon/core/gui/graph/graph.py | 13 +++++++------ daemon/core/gui/wrappers.py | 21 +++++++++++++++++++++ 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index fd1abc34..5ddc28d7 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -48,6 +48,7 @@ from core.gui.wrappers import ( NodeServiceData, NodeType, Position, + Session, SessionLocation, SessionState, ThroughputsEvent, @@ -311,8 +312,8 @@ class CoreClient: # get session data try: response = self.client.get_session(self.session_id) - session = response.session - self.state = SessionState(session.state) + session = Session.from_proto(response.session) + self.state = session.state self.handling_events = self.client.events( self.session_id, self.handle_events ) @@ -349,9 +350,7 @@ class CoreClient: self.ifaces_manager.joined(session.links) # draw session - nodes = [Node.from_proto(x) for x in session.nodes] - links = [Link.from_proto(x) for x in session.links] - self.app.canvas.reset_and_redraw(nodes, links) + self.app.canvas.reset_and_redraw(session) # get mobility configs response = self.client.get_mobility_configs(self.session_id) diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index ae0b00c0..81e0d1c6 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -22,7 +22,7 @@ from core.gui.graph.shape import Shape from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker from core.gui.images import ImageEnum, TypeToImage from core.gui.nodeutils import NodeDraw, NodeUtils -from core.gui.wrappers import Interface, Link, LinkType, Node, ThroughputsEvent +from core.gui.wrappers import Interface, Link, LinkType, Node, Session, ThroughputsEvent if TYPE_CHECKING: from core.gui.app import Application @@ -127,7 +127,7 @@ class CanvasGraph(tk.Canvas): ) self.configure(scrollregion=self.bbox(tk.ALL)) - def reset_and_redraw(self, nodes: List[Node], links: List[Link]) -> None: + def reset_and_redraw(self, session: Session) -> None: # reset view options to default state self.show_node_labels.set(True) self.show_link_labels.set(True) @@ -152,7 +152,7 @@ class CanvasGraph(tk.Canvas): self.wireless_edges.clear() self.wireless_network.clear() self.drawing_edge = None - self.draw_session(nodes, links) + self.draw_session(session) def setup_bindings(self) -> None: """ @@ -325,19 +325,20 @@ class CanvasGraph(tk.Canvas): self.nodes[node.id] = node self.core.canvas_nodes[core_node.id] = node - def draw_session(self, nodes: List[Node], links: List[Link]) -> None: + def draw_session(self, session: Session) -> None: """ Draw existing session. """ # draw existing nodes - for core_node in nodes: + for core_node in session.nodes: + logging.debug("drawing node: %s", core_node) # peer to peer node is not drawn on the GUI if NodeUtils.is_ignore_node(core_node.type): continue self.add_core_node(core_node) # draw existing links - for link in links: + for link in session.links: logging.debug("drawing link: %s", link) canvas_node1 = self.core.canvas_nodes[link.node1_id] canvas_node2 = self.core.canvas_nodes[link.node2_id] diff --git a/daemon/core/gui/wrappers.py b/daemon/core/gui/wrappers.py index 5fb12837..612c7646 100644 --- a/daemon/core/gui/wrappers.py +++ b/daemon/core/gui/wrappers.py @@ -545,6 +545,27 @@ class Node: ) +@dataclass +class Session: + id: int + state: SessionState + nodes: List[Node] + links: List[Link] + dir: str + + @classmethod + def from_proto(cls, proto: core_pb2.Session) -> "Session": + nodes = [Node.from_proto(x) for x in proto.nodes] + links = [Link.from_proto(x) for x in proto.links] + return Session( + id=proto.id, + state=SessionState(proto.state), + nodes=nodes, + links=links, + dir=proto.dir, + ) + + @dataclass class LinkEvent: message_type: MessageType From 3bdd6292cdc0161d75be89c07180f0e79d71774f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 27 Jul 2020 18:19:51 -0700 Subject: [PATCH 016/598] grpc: update GetSession to return all session related information, rather than needing 8 different calls, pygui: updated session protobuf wrapper to handle all new data --- daemon/core/api/grpc/grpcutils.py | 129 +++++++++++++++++++- daemon/core/api/grpc/server.py | 137 +++++++--------------- daemon/core/emulator/session.py | 2 +- daemon/core/gui/graph/graph.py | 2 +- daemon/core/gui/wrappers.py | 70 ++++++++++- daemon/proto/core/api/grpc/core.proto | 12 ++ daemon/proto/core/api/grpc/emane.proto | 8 +- daemon/proto/core/api/grpc/services.proto | 15 +-- 8 files changed, 260 insertions(+), 115 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 84b8ee6a..a024c064 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -8,13 +8,22 @@ from grpc import ServicerContext from core import utils from core.api.grpc import common_pb2, core_pb2 -from core.api.grpc.services_pb2 import NodeServiceData, ServiceConfig +from core.api.grpc.common_pb2 import MappedConfig +from core.api.grpc.configservices_pb2 import ConfigServiceConfig +from core.api.grpc.emane_pb2 import EmaneModelConfig +from core.api.grpc.services_pb2 import ( + NodeServiceConfig, + NodeServiceData, + ServiceConfig, + ServiceDefaults, +) from core.config import ConfigurableOptions from core.emane.nodes import EmaneNet from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions from core.emulator.enumerations import LinkTypes, NodeTypes from core.emulator.session import Session -from core.nodes.base import CoreNode, NodeBase +from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility +from core.nodes.base import CoreNode, CoreNodeBase, NodeBase from core.nodes.interface import CoreInterface from core.services.coreservices import CoreService @@ -536,3 +545,119 @@ def get_nem_id( message = f"{node.name} interface {iface_id} nem id does not exist" context.abort(grpc.StatusCode.INVALID_ARGUMENT, message) return nem_id + + +def get_emane_model_configs(session: Session) -> List[EmaneModelConfig]: + configs = [] + for _id in session.emane.node_configurations: + if _id == -1: + continue + model_configs = session.emane.node_configurations[_id] + for model_name in model_configs: + model = session.emane.models[model_name] + current_config = session.emane.get_model_config(_id, model_name) + config = get_config_options(current_config, model) + node_id, iface_id = parse_emane_model_id(_id) + model_config = EmaneModelConfig( + node_id=node_id, model=model_name, iface_id=iface_id, config=config + ) + configs.append(model_config) + return configs + + +def get_wlan_configs(session: Session) -> Dict[int, MappedConfig]: + configs = {} + for node_id in session.mobility.node_configurations: + model_config = session.mobility.node_configurations[node_id] + if node_id == -1: + continue + for model_name in model_config: + if model_name != BasicRangeModel.name: + continue + current_config = session.mobility.get_model_config(node_id, model_name) + config = get_config_options(current_config, BasicRangeModel) + mapped_config = MappedConfig(config=config) + configs[node_id] = mapped_config + return configs + + +def get_mobility_configs(session: Session) -> Dict[int, MappedConfig]: + configs = {} + for node_id in session.mobility.node_configurations: + model_config = session.mobility.node_configurations[node_id] + if node_id == -1: + continue + for model_name in model_config: + if model_name != Ns2ScriptedMobility.name: + continue + current_config = session.mobility.get_model_config(node_id, model_name) + config = get_config_options(current_config, Ns2ScriptedMobility) + mapped_config = MappedConfig(config=config) + configs[node_id] = mapped_config + return configs + + +def get_hooks(session: Session) -> List[core_pb2.Hook]: + hooks = [] + for state in session.hooks: + state_hooks = session.hooks[state] + for file_name, file_data in state_hooks: + hook = core_pb2.Hook(state=state.value, file=file_name, data=file_data) + hooks.append(hook) + return hooks + + +def get_emane_models(session: Session) -> List[str]: + emane_models = [] + for model in session.emane.models.keys(): + if len(model.split("_")) != 2: + continue + emane_models.append(model) + return emane_models + + +def get_default_services(session: Session) -> List[ServiceDefaults]: + default_services = [] + for name, services in session.services.default_services.items(): + default_service = ServiceDefaults(node_type=name, services=services) + default_services.append(default_service) + return default_services + + +def get_node_service_configs(session: Session) -> List[NodeServiceConfig]: + configs = [] + for node_id, service_configs in session.services.custom_services.items(): + for name in service_configs: + service = session.services.get_service(node_id, name) + service_proto = get_service_configuration(service) + config = NodeServiceConfig( + node_id=node_id, + service=name, + data=service_proto, + files=service.config_data, + ) + configs.append(config) + return configs + + +def get_node_config_service_configs(session: Session) -> List[ConfigServiceConfig]: + configs = [] + for node in session.nodes.values(): + if not isinstance(node, CoreNodeBase): + continue + for name, service in node.config_services.items(): + if not service.custom_templates and not service.custom_config: + continue + config_proto = ConfigServiceConfig( + node_id=node.id, + name=name, + templates=service.custom_templates, + config=service.custom_config, + ) + configs.append(config_proto) + return configs + + +def get_emane_config(session: Session) -> Dict[str, common_pb2.ConfigOption]: + current_config = session.emane.get_configs() + return get_config_options(current_config, session.emane.emane_config) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 38100e05..65029e0a 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -19,7 +19,6 @@ from core.api.grpc import ( core_pb2_grpc, grpcutils, ) -from core.api.grpc.common_pb2 import MappedConfig from core.api.grpc.configservices_pb2 import ( ConfigService, GetConfigServiceDefaultsRequest, @@ -89,7 +88,6 @@ from core.api.grpc.services_pb2 import ( ServiceAction, ServiceActionRequest, ServiceActionResponse, - ServiceDefaults, SetNodeServiceFileRequest, SetNodeServiceFileResponse, SetNodeServiceRequest, @@ -118,7 +116,7 @@ from core.emulator.enumerations import ( from core.emulator.session import NT, Session from core.errors import CoreCommandError, CoreError from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility -from core.nodes.base import CoreNode, CoreNodeBase, NodeBase +from core.nodes.base import CoreNode, NodeBase from core.nodes.network import PtpNet, WlanNode from core.services.coreservices import ServiceManager @@ -558,7 +556,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("get session: %s", request) session = self.get_session(request.session_id, context) - links = [] nodes = [] for _id in session.nodes: @@ -568,9 +565,37 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): nodes.append(node_proto) node_links = get_links(node) links.extend(node_links) - + default_services = grpcutils.get_default_services(session) + x, y, z = session.location.refxyz + lat, lon, alt = session.location.refgeo + location = core_pb2.SessionLocation( + x=x, y=y, z=z, lat=lat, lon=lon, alt=alt, scale=session.location.refscale + ) + hooks = grpcutils.get_hooks(session) + emane_models = grpcutils.get_emane_models(session) + emane_config = grpcutils.get_emane_config(session) + emane_model_configs = grpcutils.get_emane_model_configs(session) + wlan_configs = grpcutils.get_wlan_configs(session) + mobility_configs = grpcutils.get_mobility_configs(session) + service_configs = grpcutils.get_node_service_configs(session) + config_service_configs = grpcutils.get_node_config_service_configs(session) session_proto = core_pb2.Session( - state=session.state.value, nodes=nodes, links=links, dir=session.session_dir + state=session.state.value, + nodes=nodes, + links=links, + dir=session.session_dir, + user=session.user, + default_services=default_services, + location=location, + hooks=hooks, + emane_models=emane_models, + emane_config=emane_config, + emane_model_configs=emane_model_configs, + wlan_configs=wlan_configs, + service_configs=service_configs, + config_service_configs=config_service_configs, + mobility_configs=mobility_configs, + metadata=session.metadata, ) return core_pb2.GetSessionResponse(session=session_proto) @@ -1012,12 +1037,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("get hooks: %s", request) session = self.get_session(request.session_id, context) - hooks = [] - for state in session.hooks: - state_hooks = session.hooks[state] - for file_name, file_data in state_hooks: - hook = core_pb2.Hook(state=state.value, file=file_name, data=file_data) - hooks.append(hook) + hooks = grpcutils.get_hooks(session) return core_pb2.GetHooksResponse(hooks=hooks) def AddHook( @@ -1050,19 +1070,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("get mobility configs: %s", request) session = self.get_session(request.session_id, context) - response = GetMobilityConfigsResponse() - for node_id in session.mobility.node_configurations: - model_config = session.mobility.node_configurations[node_id] - if node_id == -1: - continue - for model_name in model_config: - if model_name != Ns2ScriptedMobility.name: - continue - current_config = session.mobility.get_model_config(node_id, model_name) - config = get_config_options(current_config, Ns2ScriptedMobility) - mapped_config = MappedConfig(config=config) - response.configs[node_id].CopyFrom(mapped_config) - return response + configs = grpcutils.get_mobility_configs(session) + return GetMobilityConfigsResponse(configs=configs) def GetMobilityConfig( self, request: GetMobilityConfigRequest, context: ServicerContext @@ -1157,12 +1166,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("get service defaults: %s", request) session = self.get_session(request.session_id, context) - all_service_defaults = [] - for node_type in session.services.default_services: - services = session.services.default_services[node_type] - service_defaults = ServiceDefaults(node_type=node_type, services=services) - all_service_defaults.append(service_defaults) - return GetServiceDefaultsResponse(defaults=all_service_defaults) + defaults = grpcutils.get_default_services(session) + return GetServiceDefaultsResponse(defaults=defaults) def SetServiceDefaults( self, request: SetServiceDefaultsRequest, context: ServicerContext @@ -1196,18 +1201,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("get node service configs: %s", request) session = self.get_session(request.session_id, context) - configs = [] - for node_id, service_configs in session.services.custom_services.items(): - for name in service_configs: - service = session.services.get_service(node_id, name) - service_proto = grpcutils.get_service_configuration(service) - config = GetNodeServiceConfigsResponse.ServiceConfig( - node_id=node_id, - service=name, - data=service_proto, - files=service.config_data, - ) - configs.append(config) + configs = grpcutils.get_node_service_configs(session) return GetNodeServiceConfigsResponse(configs=configs) def GetNodeService( @@ -1337,19 +1331,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("get wlan configs: %s", request) session = self.get_session(request.session_id, context) - response = GetWlanConfigsResponse() - for node_id in session.mobility.node_configurations: - model_config = session.mobility.node_configurations[node_id] - if node_id == -1: - continue - for model_name in model_config: - if model_name != BasicRangeModel.name: - continue - current_config = session.mobility.get_model_config(node_id, model_name) - config = get_config_options(current_config, BasicRangeModel) - mapped_config = MappedConfig(config=config) - response.configs[node_id].CopyFrom(mapped_config) - return response + configs = grpcutils.get_wlan_configs(session) + return GetWlanConfigsResponse(configs=configs) def GetWlanConfig( self, request: GetWlanConfigRequest, context: ServicerContext @@ -1401,8 +1384,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("get emane config: %s", request) session = self.get_session(request.session_id, context) - current_config = session.emane.get_configs() - config = get_config_options(current_config, session.emane.emane_config) + config = grpcutils.get_emane_config(session) return GetEmaneConfigResponse(config=config) def SetEmaneConfig( @@ -1433,11 +1415,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("get emane models: %s", request) session = self.get_session(request.session_id, context) - models = [] - for model in session.emane.models.keys(): - if len(model.split("_")) != 2: - continue - models.append(model) + models = grpcutils.get_emane_models(session) return GetEmaneModelsResponse(models=models) def GetEmaneModelConfig( @@ -1491,22 +1469,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("get emane model configs: %s", request) session = self.get_session(request.session_id, context) - - configs = [] - for _id in session.emane.node_configurations: - if _id == -1: - continue - - model_configs = session.emane.node_configurations[_id] - for model_name in model_configs: - model = session.emane.models[model_name] - current_config = session.emane.get_model_config(_id, model_name) - config = get_config_options(current_config, model) - node_id, iface_id = grpcutils.parse_emane_model_id(_id) - model_config = GetEmaneModelConfigsResponse.ModelConfig( - node_id=node_id, model=model_name, iface_id=iface_id, config=config - ) - configs.append(model_config) + configs = grpcutils.get_emane_model_configs(session) return GetEmaneModelConfigsResponse(configs=configs) def SaveXml( @@ -1713,21 +1676,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :return: get node config service configs response """ session = self.get_session(request.session_id, context) - configs = [] - for node in session.nodes.values(): - if not isinstance(node, CoreNodeBase): - continue - - for name, service in node.config_services.items(): - if not service.custom_templates and not service.custom_config: - continue - config_proto = configservices_pb2.ConfigServiceConfig( - node_id=node.id, - name=name, - templates=service.custom_templates, - config=service.custom_config, - ) - configs.append(config_proto) + configs = grpcutils.get_node_config_service_configs(session) return GetNodeConfigServiceConfigsResponse(configs=configs) def GetNodeConfigServices( diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index cad6ae3c..4127b141 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -119,7 +119,7 @@ class Session: # states and hooks handlers self.state: EventTypes = EventTypes.DEFINITION_STATE self.state_time: float = time.monotonic() - self.hooks: Dict[EventTypes, Tuple[str, str]] = {} + self.hooks: Dict[EventTypes, List[Tuple[str, str]]] = {} self.state_hooks: Dict[EventTypes, List[Callable[[EventTypes], None]]] = {} self.add_state_hook( state=EventTypes.RUNTIME_STATE, hook=self.runtime_state_hook diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 81e0d1c6..f2a27444 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -330,7 +330,7 @@ class CanvasGraph(tk.Canvas): Draw existing session. """ # draw existing nodes - for core_node in session.nodes: + for core_node in session.nodes.values(): logging.debug("drawing node: %s", core_node) # peer to peer node is not drawn on the GUI if NodeUtils.is_ignore_node(core_node.type): diff --git a/daemon/core/gui/wrappers.py b/daemon/core/gui/wrappers.py index 612c7646..835a9d17 100644 --- a/daemon/core/gui/wrappers.py +++ b/daemon/core/gui/wrappers.py @@ -1,6 +1,6 @@ from dataclasses import dataclass, field from enum import Enum -from typing import Dict, List +from typing import Dict, List, Optional, Tuple from core.api.grpc import common_pb2, configservices_pb2, core_pb2, services_pb2 @@ -119,6 +119,12 @@ class ConfigService: ) +@dataclass +class ConfigServiceData: + templates: Dict[str, str] + config: Dict[str, str] + + @dataclass class NodeServiceData: executables: List[str] @@ -508,6 +514,22 @@ class Node: dir: str = None channel: str = None + # configurations + emane_model_configs: Dict[ + Tuple[str, Optional[int]], Dict[str, ConfigOption] + ] = field(default_factory=dict, repr=False) + wlan_config: Dict[str, ConfigOption] = field(default_factory=dict, repr=False) + mobility_config: Dict[str, ConfigOption] = field(default_factory=dict, repr=False) + service_configs: Dict[str, NodeServiceData] = field( + default_factory=dict, repr=False + ) + service_file_configs: Dict[str, Dict[str, str]] = field( + default_factory=dict, repr=False + ) + config_service_configs: Dict[str, ConfigServiceData] = field( + default_factory=dict, repr=False + ) + @classmethod def from_proto(cls, proto: core_pb2.Node) -> "Node": return Node( @@ -549,20 +571,62 @@ class Node: class Session: id: int state: SessionState - nodes: List[Node] + nodes: Dict[int, Node] links: List[Link] dir: str + user: str + default_services: Dict[str, List[str]] + location: SessionLocation + hooks: List[Hook] + emane_models: List[str] + emane_config: Dict[str, ConfigOption] + metadata: Dict[str, str] @classmethod def from_proto(cls, proto: core_pb2.Session) -> "Session": - nodes = [Node.from_proto(x) for x in proto.nodes] + nodes: Dict[int, Node] = {x.id: Node.from_proto(x) for x in proto.nodes} links = [Link.from_proto(x) for x in proto.links] + default_services = {x.node_type: x.services for x in proto.default_services} + hooks = [Hook.from_proto(x) for x in proto.hooks] + # update nodes with their current configurations + for model in proto.emane_model_configs: + iface_id = None + if model.iface_id != -1: + iface_id = model.iface_id + node = nodes[model.node_id] + key = (model.model, iface_id) + node.emane_model_configs[key] = ConfigOption.from_dict(model.config) + for node_id, mapped_config in proto.wlan_configs.items(): + node = nodes[node_id] + node.wlan_config = ConfigOption.from_dict(mapped_config.config) + for config in proto.service_configs: + service = config.service + node = nodes[config.node_id] + node.service_configs[service] = NodeServiceData.from_proto(config.data) + for file, data in config.files.items(): + files = node.service_file_configs.setdefault(service, {}) + files[file] = data + for config in proto.config_service_configs: + node = nodes[config.node_id] + node.config_service_configs[config.name] = ConfigServiceData( + templates=dict(config.templates), config=dict(config.config) + ) + for node_id, mapped_config in proto.mobility_configs.items(): + node = nodes[node_id] + node.mobility_config = ConfigOption.from_dict(mapped_config.config) return Session( id=proto.id, state=SessionState(proto.state), nodes=nodes, links=links, dir=proto.dir, + user=proto.user, + default_services=default_services, + location=SessionLocation.from_proto(proto.location), + hooks=hooks, + emane_models=list(proto.emane_models), + emane_config=ConfigOption.from_dict(proto.emane_config), + metadata=dict(proto.metadata), ) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 9214ad1b..1b20257c 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -714,6 +714,18 @@ message Session { repeated Node nodes = 3; repeated Link links = 4; string dir = 5; + string user = 6; + repeated services.ServiceDefaults default_services = 7; + SessionLocation location = 8; + repeated Hook hooks = 9; + repeated string emane_models = 10; + map emane_config = 11; + repeated emane.EmaneModelConfig emane_model_configs = 12; + map wlan_configs = 13; + repeated services.NodeServiceConfig service_configs = 14; + repeated configservices.ConfigServiceConfig config_service_configs = 15; + map mobility_configs = 16; + map metadata = 17; } message SessionSummary { diff --git a/daemon/proto/core/api/grpc/emane.proto b/daemon/proto/core/api/grpc/emane.proto index ac5456fd..ce9a4297 100644 --- a/daemon/proto/core/api/grpc/emane.proto +++ b/daemon/proto/core/api/grpc/emane.proto @@ -54,13 +54,7 @@ message GetEmaneModelConfigsRequest { } message GetEmaneModelConfigsResponse { - message ModelConfig { - int32 node_id = 1; - string model = 2; - int32 iface_id = 3; - map config = 4; - } - repeated ModelConfig configs = 1; + repeated EmaneModelConfig configs = 1; } message GetEmaneEventChannelRequest { diff --git a/daemon/proto/core/api/grpc/services.proto b/daemon/proto/core/api/grpc/services.proto index 7e8498a7..cf6d9cbf 100644 --- a/daemon/proto/core/api/grpc/services.proto +++ b/daemon/proto/core/api/grpc/services.proto @@ -59,6 +59,13 @@ message NodeServiceData { string meta = 10; } +message NodeServiceConfig { + int32 node_id = 1; + string service = 2; + NodeServiceData data = 3; + map files = 4; +} + message GetServicesRequest { } @@ -89,13 +96,7 @@ message GetNodeServiceConfigsRequest { } message GetNodeServiceConfigsResponse { - message ServiceConfig { - int32 node_id = 1; - string service = 2; - NodeServiceData data = 3; - map files = 4; - } - repeated ServiceConfig configs = 1; + repeated NodeServiceConfig configs = 1; } message GetNodeServiceRequest { From 588afaad13c8996c790f91bd743bf21cc06b1de6 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 28 Jul 2020 00:03:15 -0700 Subject: [PATCH 017/598] pygui: changes to make use of wrapped session object and wrapped nodes to maintain and retrieving configurations information --- daemon/core/api/grpc/server.py | 1 + daemon/core/gui/coreclient.py | 379 ++++++------------ daemon/core/gui/dialogs/canvassizeandscale.py | 4 +- .../core/gui/dialogs/configserviceconfig.py | 59 ++- daemon/core/gui/dialogs/copyserviceconfig.py | 21 +- daemon/core/gui/dialogs/emaneconfig.py | 30 +- daemon/core/gui/dialogs/hooks.py | 15 +- daemon/core/gui/dialogs/linkconfig.py | 2 +- daemon/core/gui/dialogs/mobilityconfig.py | 12 +- daemon/core/gui/dialogs/mobilityplayer.py | 45 +-- daemon/core/gui/dialogs/nodeconfigservice.py | 24 +- daemon/core/gui/dialogs/nodeservice.py | 23 +- daemon/core/gui/dialogs/runtool.py | 2 +- daemon/core/gui/dialogs/serviceconfig.py | 42 +- daemon/core/gui/dialogs/sessionoptions.py | 4 +- daemon/core/gui/dialogs/sessions.py | 2 +- daemon/core/gui/dialogs/wlanconfig.py | 6 +- daemon/core/gui/graph/graph.py | 21 +- daemon/core/gui/graph/node.py | 21 +- daemon/core/gui/task.py | 2 - daemon/core/gui/wrappers.py | 24 +- 21 files changed, 284 insertions(+), 455 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 65029e0a..cd9cf714 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -580,6 +580,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): service_configs = grpcutils.get_node_service_configs(session) config_service_configs = grpcutils.get_node_config_service_configs(session) session_proto = core_pb2.Session( + id=session.id, state=session.state.value, nodes=nodes, links=links, diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 5ddc28d7..36adf189 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -37,7 +37,6 @@ from core.gui.wrappers import ( ConfigOption, ConfigService, ExceptionEvent, - Hook, Interface, Link, LinkEvent, @@ -61,6 +60,10 @@ GUI_SOURCE = "gui" CPU_USAGE_DELAY = 3 +def to_dict(config: Dict[str, ConfigOption]) -> Dict[str, str]: + return {x: y.value for x, y in config.items()} + + class CoreClient: def __init__(self, app: "Application", proxy: bool) -> None: """ @@ -69,14 +72,13 @@ class CoreClient: self.app: "Application" = app self.master: tk.Tk = app.master self._client: client.CoreGrpcClient = client.CoreGrpcClient(proxy=proxy) - self.session_id: Optional[int] = None + self.session: Optional[Session] = None + self.user = getpass.getuser() + + # global service settings self.services: Dict[str, Set[str]] = {} self.config_services_groups: Dict[str, Set[str]] = {} self.config_services: Dict[str, ConfigService] = {} - self.default_services: Dict[NodeType, Set[str]] = {} - self.emane_models: List[str] = [] - self.observer: Optional[str] = None - self.user = getpass.getuser() # loaded configuration data self.servers: Dict[str, CoreServer] = {} @@ -87,15 +89,12 @@ class CoreClient: # helpers self.iface_to_edge: Dict[Tuple[int, ...], Tuple[int, ...]] = {} self.ifaces_manager: InterfaceManager = InterfaceManager(self.app) + self.observer: Optional[str] = None # session data - self.state: Optional[SessionState] = None - self.canvas_nodes: Dict[int, CanvasNode] = {} - self.location: Optional[SessionLocation] = None - self.links: Dict[Tuple[int, int], CanvasEdge] = {} - self.hooks: Dict[str, Hook] = {} - self.emane_config: Dict[str, ConfigOption] = {} self.mobility_players: Dict[int, MobilityPlayer] = {} + self.canvas_nodes: Dict[int, CanvasNode] = {} + self.links: Dict[Tuple[int, int], CanvasEdge] = {} self.handling_throughputs: Optional[grpc.Future] = None self.handling_cpu_usage: Optional[grpc.Future] = None self.handling_events: Optional[grpc.Future] = None @@ -104,15 +103,15 @@ class CoreClient: @property def client(self) -> client.CoreGrpcClient: - if self.session_id: - response = self._client.check_session(self.session_id) + if self.session: + response = self._client.check_session(self.session.id) if not response.result: throughputs_enabled = self.handling_throughputs is not None self.cancel_throughputs() self.cancel_events() - self._client.create_session(self.session_id) + self._client.create_session(self.session.id) self.handling_events = self._client.events( - self.session_id, self.handle_events + self.session.id, self.handle_events ) if throughputs_enabled: self.enable_throughputs() @@ -126,8 +125,6 @@ class CoreClient: # session data self.canvas_nodes.clear() self.links.clear() - self.hooks.clear() - self.emane_config = None self.close_mobility_players() self.mobility_players.clear() # clear streams @@ -145,12 +142,10 @@ class CoreClient: # read distributed servers for server in self.app.guiconfig.servers: self.servers[server.name] = server - # read custom nodes for custom_node in self.app.guiconfig.nodes: node_draw = NodeDraw.from_custom(custom_node) self.custom_nodes[custom_node.name] = node_draw - # read observers for observer in self.app.guiconfig.observers: self.custom_observers[observer.name] = observer @@ -158,11 +153,11 @@ class CoreClient: def handle_events(self, event: core_pb2.Event) -> None: if event.source == GUI_SOURCE: return - if event.session_id != self.session_id: + if event.session_id != self.session.id: logging.warning( "ignoring event session(%s) current(%s)", event.session_id, - self.session_id, + self.session.id, ) return @@ -173,7 +168,7 @@ class CoreClient: logging.info("session event: %s", event) session_event = event.session_event if session_event.event <= SessionState.SHUTDOWN.value: - self.state = SessionState(session_event.event) + self.session.state = SessionState(session_event.event) elif session_event.event in {7, 8, 9}: node_id = session_event.node_id dialog = self.mobility_players.get(node_id) @@ -253,7 +248,7 @@ class CoreClient: def enable_throughputs(self) -> None: self.handling_throughputs = self.client.throughputs( - self.session_id, self.handle_throughputs + self.session.id, self.handle_throughputs ) def cancel_throughputs(self) -> None: @@ -283,11 +278,11 @@ class CoreClient: def handle_throughputs(self, event: core_pb2.ThroughputsEvent) -> None: event = ThroughputsEvent.from_proto(event) - if event.session_id != self.session_id: + if event.session_id != self.session.id: logging.warning( "ignoring throughput event session(%s) current(%s)", event.session_id, - self.session_id, + self.session.id, ) return logging.debug("handling throughputs event: %s", event) @@ -300,126 +295,33 @@ class CoreClient: logging.info("exception event: %s", event) self.app.statusbar.add_alert(event) - def join_session(self, session_id: int, query_location: bool = True) -> None: - logging.info("join session(%s)", session_id) - # update session and title - self.session_id = session_id - self.master.title(f"CORE Session({self.session_id})") - - # clear session data + def join_session(self, session_id: int) -> None: + logging.info("joining session(%s)", session_id) self.reset() - - # get session data try: - response = self.client.get_session(self.session_id) - session = Session.from_proto(response.session) - self.state = session.state + response = self.client.get_session(session_id) + self.session = Session.from_proto(response.session) + self.client.set_session_user(self.session.id, self.user) + self.master.title(f"CORE Session({self.session.id})") self.handling_events = self.client.events( - self.session_id, self.handle_events + self.session.id, self.handle_events ) - - # set session user - self.client.set_session_user(self.session_id, self.user) - - # get session service defaults - response = self.client.get_service_defaults(self.session_id) - self.default_services = { - x.node_type: set(x.services) for x in response.defaults - } - - # get location - if query_location: - response = self.client.get_session_location(self.session_id) - self.location = SessionLocation.from_proto(response.location) - - # get emane models - response = self.client.get_emane_models(self.session_id) - self.emane_models = response.models - - # get hooks - response = self.client.get_hooks(self.session_id) - for hook_proto in response.hooks: - hook = Hook.from_proto(hook_proto) - self.hooks[hook.file] = hook - - # get emane config - response = self.client.get_emane_config(self.session_id) - self.emane_config = ConfigOption.from_dict(response.config) - - # update interface manager - self.ifaces_manager.joined(session.links) - - # 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 = ConfigOption.from_dict(config) - - # get emane model config - response = self.client.get_emane_model_configs(self.session_id) - for config in response.configs: - iface_id = None - if config.iface_id != -1: - iface_id = config.iface_id - canvas_node = self.canvas_nodes[config.node_id] - canvas_node.emane_model_configs[ - (config.model, iface_id) - ] = ConfigOption.from_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] - canvas_node = self.canvas_nodes[_id] - canvas_node.wlan_config = ConfigOption.from_dict(mapped_config.config) - - # get service configurations - response = self.client.get_node_service_configs(self.session_id) - for config in response.configs: - 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: - 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: - canvas_node = self.canvas_nodes[config.node_id] - service_config = canvas_node.config_service_configs.setdefault( - config.name, {} - ) - if config.templates: - service_config["templates"] = config.templates - if config.config: - service_config["config"] = config.config - - # get metadata - response = self.client.get_session_metadata(self.session_id) - self.parse_metadata(response.config) + self.ifaces_manager.joined(self.session.links) + self.app.canvas.reset_and_redraw(self.session) + self.parse_metadata() + self.app.canvas.organize() + if self.is_runtime(): + self.show_mobility_players() + self.app.after(0, self.app.joined_session_update) except grpc.RpcError as e: self.app.show_grpc_exception("Join Session Error", e) - # organize canvas - self.app.canvas.organize() - if self.is_runtime(): - self.show_mobility_players() - # update ui to represent current state - self.app.after(0, self.app.joined_session_update) - def is_runtime(self) -> bool: - return self.state == SessionState.RUNTIME + return self.session and self.session.state == SessionState.RUNTIME - def parse_metadata(self, config: Dict[str, str]) -> None: + def parse_metadata(self) -> None: # canvas setting + config = self.session.metadata canvas_config = config.get("canvas") logging.debug("canvas metadata: %s", canvas_config) if canvas_config: @@ -447,7 +349,7 @@ class CoreClient: if shapes_config: shapes_config = json.loads(shapes_config) for shape_config in shapes_config: - logging.info("loading shape: %s", shape_config) + logging.debug("loading shape: %s", shape_config) shape_type = shape_config["type"] try: shape_type = ShapeType(shape_type) @@ -478,8 +380,9 @@ class CoreClient: try: response = self.client.create_session() logging.info("created session: %s", response) + self.join_session(response.session_id) location_config = self.app.guiconfig.location - self.location = SessionLocation( + self.session.location = SessionLocation( x=location_config.x, y=location_config.y, z=location_config.z, @@ -488,13 +391,12 @@ class CoreClient: alt=location_config.alt, scale=location_config.scale, ) - self.join_session(response.session_id, query_location=False) except grpc.RpcError as e: self.app.show_grpc_exception("New Session Error", e) def delete_session(self, session_id: int = None) -> None: if session_id is None: - session_id = self.session_id + session_id = self.session.id try: response = self.client.delete_session(session_id) logging.info("deleted session(%s), Result: %s", session_id, response) @@ -507,13 +409,11 @@ class CoreClient: """ try: self.client.connect() - - # get service information + # get all available services response = self.client.get_services() for service in response.services: group_services = self.services.setdefault(service.group, set()) group_services.add(service.name) - # get config service informations response = self.client.get_config_services() for service in response.services: @@ -522,7 +422,6 @@ class CoreClient: service.group, set() ) group_services.add(service.name) - # join provided session, create new session, or show dialog to select an # existing session response = self.client.get_sessions() @@ -553,14 +452,14 @@ class CoreClient: try: position = core_node.position.to_proto() self.client.edit_node( - self.session_id, core_node.id, position, source=GUI_SOURCE + self.session.id, core_node.id, position, source=GUI_SOURCE ) except grpc.RpcError as e: self.app.show_grpc_exception("Edit Node Error", e) def send_servers(self) -> None: for server in self.servers.values(): - self.client.add_session_server(self.session_id, server.name, server.address) + self.client.add_session_server(self.session.id, server.name, server.address) def start_session(self) -> Tuple[bool, List[str]]: self.ifaces_manager.reset_mac() @@ -576,26 +475,23 @@ class CoreClient: wlan_configs = self.get_wlan_configs_proto() mobility_configs = self.get_mobility_configs_proto() emane_model_configs = self.get_emane_model_configs_proto() - hooks = [x.to_proto() for x in self.hooks.values()] + hooks = [x.to_proto() for x in self.session.hooks.values()] service_configs = self.get_service_configs_proto() file_configs = self.get_service_file_configs_proto() asymmetric_links = [ x.asymmetric_link for x in self.links.values() if x.asymmetric_link ] config_service_configs = self.get_config_service_configs_proto() - if self.emane_config: - emane_config = {x: self.emane_config[x].value for x in self.emane_config} - else: - emane_config = None + emane_config = to_dict(self.session.emane_config) result = False exceptions = [] try: self.send_servers() response = self.client.start_session( - self.session_id, + self.session.id, nodes, links, - self.location.to_proto(), + self.session.location.to_proto(), hooks, emane_config, emane_model_configs, @@ -607,7 +503,7 @@ class CoreClient: config_service_configs, ) logging.info( - "start session(%s), result: %s", self.session_id, response.result + "start session(%s), result: %s", self.session.id, response.result ) if response.result: self.set_metadata() @@ -619,7 +515,7 @@ class CoreClient: def stop_session(self, session_id: int = None) -> bool: if not session_id: - session_id = self.session_id + session_id = self.session.id result = False try: response = self.client.stop_session(session_id) @@ -630,15 +526,12 @@ class CoreClient: return result def show_mobility_players(self) -> None: - for canvas_node in self.canvas_nodes.values(): - if canvas_node.core_node.type != NodeType.WIRELESS_LAN: + for node in self.session.nodes.values(): + if node.type != NodeType.WIRELESS_LAN: continue - if canvas_node.mobility_config: - mobility_player = MobilityPlayer( - self.app, canvas_node, canvas_node.mobility_config - ) - node_id = canvas_node.core_node.id - self.mobility_players[node_id] = mobility_player + if node.mobility_config: + mobility_player = MobilityPlayer(self.app, node) + self.mobility_players[node.id] = mobility_player mobility_player.show() def set_metadata(self) -> None: @@ -662,8 +555,8 @@ class CoreClient: shapes = json.dumps(shapes) metadata = {"canvas": canvas_config, "shapes": shapes} - response = self.client.set_session_metadata(self.session_id, metadata) - logging.info("set session metadata %s, result: %s", metadata, response) + response = self.client.set_session_metadata(self.session.id, metadata) + logging.debug("set session metadata %s, result: %s", metadata, response) def launch_terminal(self, node_id: int) -> None: try: @@ -675,7 +568,7 @@ class CoreClient: parent=self.app, ) return - response = self.client.get_node_terminal(self.session_id, node_id) + response = self.client.get_node_terminal(self.session.id, node_id) cmd = f"{terminal} {response.terminal} &" logging.info("launching terminal %s", cmd) os.system(cmd) @@ -687,10 +580,10 @@ class CoreClient: Save core session as to an xml file """ try: - if self.state != SessionState.RUNTIME: + if not self.is_runtime(): logging.debug("Send session data to the daemon") self.send_data() - response = self.client.save_xml(self.session_id, file_path) + response = self.client.save_xml(self.session.id, file_path) logging.info("saved xml file %s, result: %s", file_path, response) except grpc.RpcError as e: self.app.show_grpc_exception("Save XML Error", e) @@ -707,7 +600,7 @@ class CoreClient: self.app.show_grpc_exception("Open XML Error", e) def get_node_service(self, node_id: int, service_name: str) -> NodeServiceData: - response = self.client.get_node_service(self.session_id, node_id, service_name) + response = self.client.get_node_service(self.session.id, node_id, service_name) logging.debug( "get node(%s) %s service, response: %s", node_id, service_name, response ) @@ -724,7 +617,7 @@ class CoreClient: shutdowns: List[str], ) -> NodeServiceData: response = self.client.set_node_service( - self.session_id, + self.session.id, node_id, service_name, directories=dirs, @@ -744,14 +637,14 @@ class CoreClient: shutdowns, response, ) - response = self.client.get_node_service(self.session_id, node_id, service_name) + response = self.client.get_node_service(self.session.id, node_id, service_name) return NodeServiceData.from_proto(response.service) def get_node_service_file( self, node_id: int, service_name: str, file_name: str ) -> str: response = self.client.get_node_service_file( - self.session_id, node_id, service_name, file_name + self.session.id, node_id, service_name, file_name ) logging.debug( "get service file for node(%s), service: %s, file: %s, result: %s", @@ -766,7 +659,7 @@ class CoreClient: self, node_id: int, service_name: str, file_name: str, data: str ) -> None: response = self.client.set_node_service_file( - self.session_id, node_id, service_name, file_name, data + self.session.id, node_id, service_name, file_name, data ) logging.info( "set node(%s) service file, service: %s, file: %s, data: %s, result: %s", @@ -783,13 +676,13 @@ class CoreClient: """ node_protos = [x.core_node.to_proto() for x in self.canvas_nodes.values()] link_protos = [x.link.to_proto() for x in self.links.values()] - self.client.set_session_state(self.session_id, SessionState.DEFINITION.value) + self.client.set_session_state(self.session.id, SessionState.DEFINITION.value) for node_proto in node_protos: - response = self.client.add_node(self.session_id, node_proto) + response = self.client.add_node(self.session.id, node_proto) logging.debug("create node: %s", response) for link_proto in link_protos: response = self.client.add_link( - self.session_id, + self.session.id, link_proto.node1_id, link_proto.node2_id, link_proto.iface1, @@ -806,15 +699,15 @@ class CoreClient: self.create_nodes_and_links() for config_proto in self.get_wlan_configs_proto(): self.client.set_wlan_config( - self.session_id, config_proto.node_id, config_proto.config + self.session.id, config_proto.node_id, config_proto.config ) for config_proto in self.get_mobility_configs_proto(): self.client.set_mobility_config( - self.session_id, config_proto.node_id, config_proto.config + self.session.id, config_proto.node_id, config_proto.config ) for config_proto in self.get_service_configs_proto(): self.client.set_node_service( - self.session_id, + self.session.id, config_proto.node_id, config_proto.service, startup=config_proto.startup, @@ -823,38 +716,37 @@ class CoreClient: ) for config_proto in self.get_service_file_configs_proto(): self.client.set_node_service_file( - self.session_id, + self.session.id, config_proto.node_id, config_proto.service, config_proto.file, config_proto.data, ) - for hook in self.hooks.values(): + for hook in self.session.hooks.values(): self.client.add_hook( - self.session_id, hook.state.value, hook.file, hook.data + self.session.id, hook.state.value, hook.file, hook.data ) for config_proto in self.get_emane_model_configs_proto(): self.client.set_emane_model_config( - self.session_id, + self.session.id, config_proto.node_id, config_proto.model, config_proto.config, config_proto.iface_id, ) - if self.emane_config: - config = {x: self.emane_config[x].value for x in self.emane_config} - self.client.set_emane_config(self.session_id, config) - if self.location: - self.client.set_session_location( - self.session_id, - self.location.x, - self.location.y, - self.location.z, - self.location.lat, - self.location.lon, - self.location.alt, - self.location.scale, - ) + config = to_dict(self.session.emane_config) + self.client.set_emane_config(self.session.id, config) + location = self.session.location + self.client.set_session_location( + self.session.id, + location.x, + location.y, + location.z, + location.lat, + location.lon, + location.alt, + location.scale, + ) self.set_metadata() def close(self) -> None: @@ -888,16 +780,16 @@ class CoreClient: image = "ubuntu:latest" emane = None if node_type == NodeType.EMANE: - if not self.emane_models: + if not self.session.emane_models: dialog = EmaneInstallDialog(self.app) dialog.show() return - emane = self.emane_models[0] - name = f"EMANE{node_id}" + emane = self.session.emane_models[0] + name = f"emane{node_id}" elif node_type == NodeType.WIRELESS_LAN: - name = f"WLAN{node_id}" + name = f"wlan{node_id}" elif node_type in [NodeType.RJ45, NodeType.TUNNEL]: - name = "UNASSIGNED" + name = "unassigned" else: name = f"n{node_id}" node = Node( @@ -914,13 +806,13 @@ class CoreClient: node.services[:] = services # assign default services to CORE node else: - services = self.default_services.get(model) + services = self.session.default_services.get(model) if services: - node.services[:] = services + node.services = services.copy() logging.info( "add node(%s) to session(%s), coordinates(%s, %s)", node.name, - self.session_id, + self.session.id, x, y, ) @@ -1005,60 +897,56 @@ class CoreClient: def get_wlan_configs_proto(self) -> List[wlan_pb2.WlanConfig]: configs = [] - for canvas_node in self.canvas_nodes.values(): - if canvas_node.core_node.type != NodeType.WIRELESS_LAN: + for node in self.session.nodes.values(): + if node.type != NodeType.WIRELESS_LAN: continue - if not canvas_node.wlan_config: + if not node.wlan_config: continue - config = ConfigOption.to_dict(canvas_node.wlan_config) - node_id = canvas_node.core_node.id - wlan_config = wlan_pb2.WlanConfig(node_id=node_id, config=config) + config = ConfigOption.to_dict(node.wlan_config) + wlan_config = wlan_pb2.WlanConfig(node_id=node.id, config=config) configs.append(wlan_config) return configs def get_mobility_configs_proto(self) -> List[mobility_pb2.MobilityConfig]: configs = [] - for canvas_node in self.canvas_nodes.values(): - if canvas_node.core_node.type != NodeType.WIRELESS_LAN: + for node in self.session.nodes.values(): + if node.type != NodeType.WIRELESS_LAN: continue - if not canvas_node.mobility_config: + if not node.mobility_config: continue - config = ConfigOption.to_dict(canvas_node.mobility_config) - node_id = canvas_node.core_node.id + config = ConfigOption.to_dict(node.mobility_config) mobility_config = mobility_pb2.MobilityConfig( - node_id=node_id, config=config + node_id=node.id, config=config ) configs.append(mobility_config) return configs def get_emane_model_configs_proto(self) -> List[emane_pb2.EmaneModelConfig]: configs = [] - for canvas_node in self.canvas_nodes.values(): - if canvas_node.core_node.type != NodeType.EMANE: + for node in self.session.nodes.values(): + if node.type != NodeType.EMANE: continue - node_id = canvas_node.core_node.id - for key, config in canvas_node.emane_model_configs.items(): + for key, config in node.emane_model_configs.items(): model, iface_id = key config = ConfigOption.to_dict(config) if iface_id is None: iface_id = -1 config_proto = emane_pb2.EmaneModelConfig( - node_id=node_id, iface_id=iface_id, model=model, config=config + node_id=node.id, iface_id=iface_id, model=model, config=config ) configs.append(config_proto) return configs def get_service_configs_proto(self) -> List[services_pb2.ServiceConfig]: configs = [] - for canvas_node in self.canvas_nodes.values(): - if not NodeUtils.is_container_node(canvas_node.core_node.type): + for node in self.session.nodes.values(): + if not NodeUtils.is_container_node(node.type): continue - if not canvas_node.service_configs: + if not node.service_configs: continue - node_id = canvas_node.core_node.id - for name, config in canvas_node.service_configs.items(): + for name, config in node.service_configs.items(): config_proto = services_pb2.ServiceConfig( - node_id=node_id, + node_id=node.id, service=name, directories=config.dirs, files=config.configs, @@ -1071,16 +959,15 @@ class CoreClient: def get_service_file_configs_proto(self) -> List[services_pb2.ServiceFileConfig]: configs = [] - for canvas_node in self.canvas_nodes.values(): - if not NodeUtils.is_container_node(canvas_node.core_node.type): + for node in self.session.nodes.values(): + if not NodeUtils.is_container_node(node.type): continue - if not canvas_node.service_file_configs: + if not node.service_file_configs: continue - node_id = canvas_node.core_node.id - for service, file_configs in canvas_node.service_file_configs.items(): + for service, file_configs in node.service_file_configs.items(): for file, data in file_configs.items(): config_proto = services_pb2.ServiceFileConfig( - node_id=node_id, service=service, file=file, data=data + node_id=node.id, service=service, file=file, data=data ) configs.append(config_proto) return configs @@ -1089,29 +976,27 @@ class CoreClient: self ) -> List[configservices_pb2.ConfigServiceConfig]: config_service_protos = [] - for canvas_node in self.canvas_nodes.values(): - if not NodeUtils.is_container_node(canvas_node.core_node.type): + for node in self.session.nodes.values(): + if not NodeUtils.is_container_node(node.type): continue - if not canvas_node.config_service_configs: + if not 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", {}) + for name, service_config in node.config_service_configs.items(): config_proto = configservices_pb2.ConfigServiceConfig( - node_id=node_id, + node_id=node.id, name=name, - templates=service_config["templates"], - config=config, + templates=service_config.templates, + config=service_config.config, ) config_service_protos.append(config_proto) return config_service_protos def run(self, node_id: int) -> str: logging.info("running node(%s) cmd: %s", node_id, self.observer) - return self.client.node_command(self.session_id, node_id, self.observer).output + return self.client.node_command(self.session.id, node_id, self.observer).output def get_wlan_config(self, node_id: int) -> Dict[str, ConfigOption]: - response = self.client.get_wlan_config(self.session_id, node_id) + 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", @@ -1121,7 +1006,7 @@ class CoreClient: return ConfigOption.from_dict(config) def get_mobility_config(self, node_id: int) -> Dict[str, ConfigOption]: - response = self.client.get_mobility_config(self.session_id, node_id) + 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", @@ -1136,7 +1021,7 @@ class CoreClient: if iface_id is None: iface_id = -1 response = self.client.get_emane_model_config( - self.session_id, node_id, model, iface_id + self.session.id, node_id, model, iface_id ) config = response.config logging.debug( diff --git a/daemon/core/gui/dialogs/canvassizeandscale.py b/daemon/core/gui/dialogs/canvassizeandscale.py index 38cecc83..e8ad6693 100644 --- a/daemon/core/gui/dialogs/canvassizeandscale.py +++ b/daemon/core/gui/dialogs/canvassizeandscale.py @@ -27,7 +27,7 @@ class SizeAndScaleDialog(Dialog): width, height = self.canvas.current_dimensions self.pixel_width: tk.IntVar = tk.IntVar(value=width) self.pixel_height: tk.IntVar = tk.IntVar(value=height) - location = self.app.core.location + location = self.app.core.session.location self.x: tk.DoubleVar = tk.DoubleVar(value=location.x) self.y: tk.DoubleVar = tk.DoubleVar(value=location.y) self.lat: tk.DoubleVar = tk.DoubleVar(value=location.lat) @@ -192,7 +192,7 @@ class SizeAndScaleDialog(Dialog): self.canvas.redraw_canvas((width, height)) if self.canvas.wallpaper: self.canvas.redraw_wallpaper() - location = self.app.core.location + location = self.app.core.session.location location.x = self.x.get() location.y = self.y.get() location.lat = self.lat.get() diff --git a/daemon/core/gui/dialogs/configserviceconfig.py b/daemon/core/gui/dialogs/configserviceconfig.py index 5463d88e..f778cf15 100644 --- a/daemon/core/gui/dialogs/configserviceconfig.py +++ b/daemon/core/gui/dialogs/configserviceconfig.py @@ -11,28 +11,26 @@ import grpc from core.gui.dialogs.dialog import Dialog from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.widgets import CodeText, ConfigFrame, ListboxScroll -from core.gui.wrappers import ConfigOption, ServiceValidationMode +from core.gui.wrappers import ( + ConfigOption, + ConfigServiceData, + Node, + ServiceValidationMode, +) if TYPE_CHECKING: from core.gui.app import Application - from core.gui.graph.node import CanvasNode from core.gui.coreclient import CoreClient class ConfigServiceConfigDialog(Dialog): def __init__( - self, - master: tk.BaseWidget, - app: "Application", - service_name: str, - canvas_node: "CanvasNode", - node_id: int, + self, master: tk.BaseWidget, app: "Application", service_name: str, node: Node ) -> None: title = f"{service_name} Config Service" super().__init__(app, title, master=master) self.core: "CoreClient" = app.core - self.canvas_node: "CanvasNode" = canvas_node - self.node_id: int = node_id + self.node: Node = node self.service_name: str = service_name self.radiovar: tk.IntVar = tk.IntVar() self.radiovar.set(2) @@ -50,7 +48,7 @@ class ConfigServiceConfigDialog(Dialog): self.validation_time: Optional[int] = None self.validation_period: tk.StringVar = tk.StringVar() self.modes: List[str] = [] - self.mode_configs: Dict[str, str] = {} + self.mode_configs: Dict[str, Dict[str, str]] = {} self.notebook: Optional[ttk.Notebook] = None self.templates_combobox: Optional[ttk.Combobox] = None @@ -91,25 +89,18 @@ class ConfigServiceConfigDialog(Dialog): response = self.core.client.get_config_service_defaults(self.service_name) self.original_service_files = response.templates self.temp_service_files = dict(self.original_service_files) - self.modes = sorted(x.name for x in response.modes) self.mode_configs = {x.name: x.config for x in response.modes} - - service_config = self.canvas_node.config_service_configs.get( - self.service_name, {} - ) self.config = ConfigOption.from_dict(response.config) self.default_config = {x.name: x.value for x in self.config.values()} - custom_config = service_config.get("config") - if custom_config: - for key, value in custom_config.items(): + service_config = self.node.config_service_configs.get(self.service_name) + if service_config: + for key, value in service_config.config.items(): self.config[key].value = value - logging.info("default config: %s", self.default_config) - - custom_templates = service_config.get("templates", {}) - for file, data in custom_templates.items(): - self.modified_files.add(file) - self.temp_service_files[file] = data + logging.info("default config: %s", self.default_config) + for file, data in service_config.templates.items(): + self.modified_files.add(file) + self.temp_service_files[file] = data except grpc.RpcError as e: self.app.show_grpc_exception("Get Config Service Error", e) self.has_error = True @@ -313,20 +304,18 @@ class ConfigServiceConfigDialog(Dialog): def click_apply(self) -> None: current_listbox = self.master.current.listbox if not self.is_custom(): - self.canvas_node.config_service_configs.pop(self.service_name, None) + self.node.config_service_configs.pop(self.service_name, None) current_listbox.itemconfig(current_listbox.curselection()[0], bg="") self.destroy() return - - service_config = self.canvas_node.config_service_configs.setdefault( - self.service_name, {} - ) + service_config = self.node.config_service_configs.get(self.service_name) + if not service_config: + service_config = ConfigServiceData() if self.config_frame: self.config_frame.parse_config() - service_config["config"] = {x.name: x.value for x in self.config.values()} - templates_config = service_config.setdefault("templates", {}) + service_config.config = {x.name: x.value for x in self.config.values()} for file in self.modified_files: - templates_config[file] = self.temp_service_files[file] + service_config.templates[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") self.destroy() @@ -360,9 +349,9 @@ class ConfigServiceConfigDialog(Dialog): return has_custom_templates or has_custom_config def click_defaults(self) -> None: - self.canvas_node.config_service_configs.pop(self.service_name, None) + self.node.config_service_configs.pop(self.service_name, None) logging.info( - "cleared config service config: %s", self.canvas_node.config_service_configs + "cleared config service config: %s", self.node.config_service_configs ) self.temp_service_files = dict(self.original_service_files) filename = self.templates_combobox.get() diff --git a/daemon/core/gui/dialogs/copyserviceconfig.py b/daemon/core/gui/dialogs/copyserviceconfig.py index 2a01249d..b60d5a0d 100644 --- a/daemon/core/gui/dialogs/copyserviceconfig.py +++ b/daemon/core/gui/dialogs/copyserviceconfig.py @@ -43,16 +43,15 @@ class CopyServiceConfigDialog(Dialog): listbox_scroll = ListboxScroll(self.top) listbox_scroll.grid(sticky="nsew", pady=PADY) self.listbox = listbox_scroll.listbox - for canvas_node in self.app.canvas.nodes.values(): - file_configs = canvas_node.service_file_configs.get(self.service) + for node in self.app.core.session.nodes.values(): + file_configs = node.service_file_configs.get(self.service) if not file_configs: continue data = file_configs.get(self.file_name) if not data: continue - name = canvas_node.core_node.name - self.nodes[name] = canvas_node.id - self.listbox.insert(tk.END, name) + self.nodes[node.name] = node.id + self.listbox.insert(tk.END, node.name) frame = ttk.Frame(self.top) frame.grid(sticky="ew") @@ -70,9 +69,9 @@ class CopyServiceConfigDialog(Dialog): if not selection: return name = self.listbox.get(selection) - canvas_node_id = self.nodes[name] - canvas_node = self.app.canvas.nodes[canvas_node_id] - data = canvas_node.service_file_configs[self.service][self.file_name] + node_id = self.nodes[name] + node = self.app.core.session.nodes[node_id] + data = node.service_file_configs[self.service][self.file_name] self.dialog.temp_service_files[self.file_name] = data self.dialog.modified_files.add(self.file_name) self.dialog.service_file_data.text.delete(1.0, tk.END) @@ -84,9 +83,9 @@ class CopyServiceConfigDialog(Dialog): if not selection: return name = self.listbox.get(selection) - canvas_node_id = self.nodes[name] - canvas_node = self.app.canvas.nodes[canvas_node_id] - data = canvas_node.service_file_configs[self.service][self.file_name] + node_id = self.nodes[name] + node = self.app.core.session.nodes[node_id] + data = node.service_file_configs[self.service][self.file_name] dialog = ViewConfigDialog( self.app, self, name, self.service, self.file_name, data ) diff --git a/daemon/core/gui/dialogs/emaneconfig.py b/daemon/core/gui/dialogs/emaneconfig.py index d87e935a..019eeaa9 100644 --- a/daemon/core/gui/dialogs/emaneconfig.py +++ b/daemon/core/gui/dialogs/emaneconfig.py @@ -16,7 +16,6 @@ from core.gui.wrappers import ConfigOption, Node if TYPE_CHECKING: from core.gui.app import Application - from core.gui.graph.node import CanvasNode class GlobalEmaneDialog(Dialog): @@ -29,8 +28,9 @@ class GlobalEmaneDialog(Dialog): def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) + session = self.app.core.session self.config_frame = ConfigFrame( - self.top, self.app, self.app.core.emane_config, self.enabled + self.top, self.app, session.emane_config, self.enabled ) self.config_frame.draw_config() self.config_frame.grid(sticky="nsew", pady=PADY) @@ -58,24 +58,19 @@ class EmaneModelDialog(Dialog): self, master: tk.BaseWidget, app: "Application", - canvas_node: "CanvasNode", + node: Node, model: str, iface_id: int = None, ) -> None: - super().__init__( - app, f"{canvas_node.core_node.name} {model} Configuration", master=master - ) - self.canvas_node: "CanvasNode" = canvas_node - self.node: Node = canvas_node.core_node + super().__init__(app, f"{node.name} {model} Configuration", master=master) + self.node: Node = node self.model: str = f"emane_{model}" self.iface_id: int = iface_id self.config_frame: Optional[ConfigFrame] = None self.enabled: bool = not self.app.core.is_runtime() self.has_error: bool = False try: - config = self.canvas_node.emane_model_configs.get( - (self.model, self.iface_id) - ) + config = self.node.emane_model_configs.get((self.model, self.iface_id)) if not config: config = self.app.core.get_emane_model_config( self.node.id, self.model, self.iface_id @@ -110,19 +105,18 @@ class EmaneModelDialog(Dialog): def click_apply(self) -> None: self.config_frame.parse_config() key = (self.model, self.iface_id) - self.canvas_node.emane_model_configs[key] = self.config + self.node.emane_model_configs[key] = self.config self.destroy() class EmaneConfigDialog(Dialog): - def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None: - super().__init__(app, f"{canvas_node.core_node.name} EMANE Configuration") - self.canvas_node: "CanvasNode" = canvas_node - self.node: Node = canvas_node.core_node + def __init__(self, app: "Application", node: Node) -> None: + super().__init__(app, f"{node.name} EMANE Configuration") + self.node: Node = node self.radiovar: tk.IntVar = tk.IntVar() self.radiovar.set(1) self.emane_models: List[str] = [ - x.split("_")[1] for x in self.app.core.emane_models + x.split("_")[1] for x in self.app.core.session.emane_models ] model = self.node.emane.split("_")[1] self.emane_model: tk.StringVar = tk.StringVar(value=model) @@ -231,7 +225,7 @@ class EmaneConfigDialog(Dialog): draw emane model configuration """ model_name = self.emane_model.get() - dialog = EmaneModelDialog(self, self.app, self.canvas_node, model_name) + dialog = EmaneModelDialog(self, self.app, self.node, model_name) if not dialog.has_error: dialog.show() diff --git a/daemon/core/gui/dialogs/hooks.py b/daemon/core/gui/dialogs/hooks.py index b004dae2..31ef3e15 100644 --- a/daemon/core/gui/dialogs/hooks.py +++ b/daemon/core/gui/dialogs/hooks.py @@ -113,8 +113,9 @@ class HooksDialog(Dialog): listbox_scroll.grid(sticky="nsew", pady=PADY) self.listbox = listbox_scroll.listbox self.listbox.bind("<>", self.select) - for hook_file in self.app.core.hooks: - self.listbox.insert(tk.END, hook_file) + session = self.app.core.session + for file in session.hooks: + self.listbox.insert(tk.END, file) frame = ttk.Frame(self.top) frame.grid(sticky="ew") @@ -138,20 +139,22 @@ class HooksDialog(Dialog): dialog.show() hook = dialog.hook if hook: - self.app.core.hooks[hook.file] = hook + self.app.core.session.hooks[hook.file] = hook self.listbox.insert(tk.END, hook.file) def click_edit(self) -> None: - hook = self.app.core.hooks.pop(self.selected) + session = self.app.core.session + hook = session.hooks.pop(self.selected) dialog = HookDialog(self, self.app) dialog.set(hook) dialog.show() - self.app.core.hooks[hook.file] = hook + session.hooks[hook.file] = hook self.listbox.delete(self.selected_index) self.listbox.insert(self.selected_index, hook.file) def click_delete(self) -> None: - del self.app.core.hooks[self.selected] + session = self.app.core.session + del session.hooks[self.selected] self.listbox.delete(tk.ANCHOR) self.edit_button.config(state=tk.DISABLED) self.delete_button.config(state=tk.DISABLED) diff --git a/daemon/core/gui/dialogs/linkconfig.py b/daemon/core/gui/dialogs/linkconfig.py index 87f43284..2a91da30 100644 --- a/daemon/core/gui/dialogs/linkconfig.py +++ b/daemon/core/gui/dialogs/linkconfig.py @@ -269,7 +269,7 @@ class LinkConfigurationDialog(Dialog): self.edge.asymmetric_link = None if self.app.core.is_runtime() and link.options: - session_id = self.app.core.session_id + session_id = self.app.core.session.id self.app.core.client.edit_link( session_id, link.node1_id, diff --git a/daemon/core/gui/dialogs/mobilityconfig.py b/daemon/core/gui/dialogs/mobilityconfig.py index ca9caf43..857167be 100644 --- a/daemon/core/gui/dialogs/mobilityconfig.py +++ b/daemon/core/gui/dialogs/mobilityconfig.py @@ -13,18 +13,16 @@ from core.gui.wrappers import ConfigOption, Node if TYPE_CHECKING: from core.gui.app import Application - from core.gui.graph.node import CanvasNode class MobilityConfigDialog(Dialog): - def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None: - super().__init__(app, f"{canvas_node.core_node.name} Mobility Configuration") - self.canvas_node: "CanvasNode" = canvas_node - self.node: Node = canvas_node.core_node + def __init__(self, app: "Application", node: Node) -> None: + super().__init__(app, f"{node.name} Mobility Configuration") + self.node: Node = node self.config_frame: Optional[ConfigFrame] = None self.has_error: bool = False try: - config = self.canvas_node.mobility_config + config = self.node.mobility_config if not config: config = self.app.core.get_mobility_config(self.node.id) self.config: Dict[str, ConfigOption] = config @@ -56,5 +54,5 @@ class MobilityConfigDialog(Dialog): def click_apply(self) -> None: self.config_frame.parse_config() - self.canvas_node.mobility_config = self.config + self.node.mobility_config = self.config self.destroy() diff --git a/daemon/core/gui/dialogs/mobilityplayer.py b/daemon/core/gui/dialogs/mobilityplayer.py index 66833aff..1bee97d2 100644 --- a/daemon/core/gui/dialogs/mobilityplayer.py +++ b/daemon/core/gui/dialogs/mobilityplayer.py @@ -1,38 +1,31 @@ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING, Dict, Optional +from typing import TYPE_CHECKING, Optional import grpc from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum from core.gui.themes import PADX, PADY -from core.gui.wrappers import ConfigOption, MobilityAction, Node +from core.gui.wrappers import MobilityAction, Node if TYPE_CHECKING: from core.gui.app import Application - from core.gui.graph.node import CanvasNode ICON_SIZE: int = 16 class MobilityPlayer: - def __init__( - self, - app: "Application", - canvas_node: "CanvasNode", - config: Dict[str, ConfigOption], - ) -> None: + def __init__(self, app: "Application", node: Node) -> None: self.app: "Application" = app - self.canvas_node: "CanvasNode" = canvas_node - self.config: Dict[str, ConfigOption] = config + self.node: Node = node self.dialog: Optional[MobilityPlayerDialog] = None self.state: Optional[MobilityAction] = None def show(self) -> None: if self.dialog: self.dialog.destroy() - self.dialog = MobilityPlayerDialog(self.app, self.canvas_node, self.config) + self.dialog = MobilityPlayerDialog(self.app, self.node) self.dialog.protocol("WM_DELETE_WINDOW", self.close) if self.state == MobilityAction.START: self.set_play() @@ -64,20 +57,11 @@ class MobilityPlayer: class MobilityPlayerDialog(Dialog): - def __init__( - self, - app: "Application", - canvas_node: "CanvasNode", - config: Dict[str, ConfigOption], - ) -> None: - super().__init__( - app, f"{canvas_node.core_node.name} Mobility Player", modal=False - ) + def __init__(self, app: "Application", node: Node) -> None: + super().__init__(app, f"{node.name} Mobility Player", modal=False) self.resizable(False, False) self.geometry("") - self.canvas_node: "CanvasNode" = canvas_node - self.node: Node = canvas_node.core_node - self.config: Dict[str, ConfigOption] = config + self.node: Node = node self.play_button: Optional[ttk.Button] = None self.pause_button: Optional[ttk.Button] = None self.stop_button: Optional[ttk.Button] = None @@ -85,9 +69,10 @@ class MobilityPlayerDialog(Dialog): self.draw() def draw(self) -> None: + config = self.node.mobility_config self.top.columnconfigure(0, weight=1) - file_name = self.config["file"].value + file_name = config["file"].value label = ttk.Label(self.top, text=file_name) label.grid(sticky="ew", pady=PADY) @@ -114,13 +99,13 @@ class MobilityPlayerDialog(Dialog): self.stop_button.image = image self.stop_button.grid(row=0, column=2, sticky="ew", padx=PADX) - loop = tk.IntVar(value=int(self.config["loop"].value == "1")) + loop = tk.IntVar(value=int(config["loop"].value == "1")) checkbutton = ttk.Checkbutton( frame, text="Loop?", variable=loop, state=tk.DISABLED ) checkbutton.grid(row=0, column=3, padx=PADX) - rate = self.config["refresh_ms"].value + rate = config["refresh_ms"].value label = ttk.Label(frame, text=f"rate {rate} ms") label.grid(row=0, column=4) @@ -146,7 +131,7 @@ class MobilityPlayerDialog(Dialog): def click_play(self) -> None: self.set_play() - session_id = self.app.core.session_id + session_id = self.app.core.session.id try: self.app.core.client.mobility_action( session_id, self.node.id, MobilityAction.START.value @@ -156,7 +141,7 @@ class MobilityPlayerDialog(Dialog): def click_pause(self) -> None: self.set_pause() - session_id = self.app.core.session_id + session_id = self.app.core.session.id try: self.app.core.client.mobility_action( session_id, self.node.id, MobilityAction.PAUSE.value @@ -166,7 +151,7 @@ class MobilityPlayerDialog(Dialog): def click_stop(self) -> None: self.set_stop() - session_id = self.app.core.session_id + session_id = self.app.core.session.id try: self.app.core.client.mobility_action( session_id, self.node.id, MobilityAction.STOP.value diff --git a/daemon/core/gui/dialogs/nodeconfigservice.py b/daemon/core/gui/dialogs/nodeconfigservice.py index b9a9a1f5..dee34f71 100644 --- a/daemon/core/gui/dialogs/nodeconfigservice.py +++ b/daemon/core/gui/dialogs/nodeconfigservice.py @@ -10,25 +10,24 @@ from core.gui.dialogs.configserviceconfig import ConfigServiceConfigDialog from core.gui.dialogs.dialog import Dialog from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.widgets import CheckboxList, ListboxScroll +from core.gui.wrappers import Node if TYPE_CHECKING: from core.gui.app import Application - from core.gui.graph.node import CanvasNode class NodeConfigServiceDialog(Dialog): def __init__( - self, app: "Application", canvas_node: "CanvasNode", services: Set[str] = None + self, app: "Application", node: Node, services: Set[str] = None ) -> None: - title = f"{canvas_node.core_node.name} Config Services" + title = f"{node.name} Config Services" super().__init__(app, title) - self.canvas_node: "CanvasNode" = canvas_node - self.node_id: int = canvas_node.core_node.id + self.node: Node = node self.groups: Optional[ListboxScroll] = None self.services: Optional[CheckboxList] = None self.current: Optional[ListboxScroll] = None if services is None: - services = set(canvas_node.core_node.config_services) + services = set(node.config_services) self.current_services: Set[str] = services self.draw() @@ -102,7 +101,7 @@ class NodeConfigServiceDialog(Dialog): elif not var.get() and name in self.current_services: self.current_services.remove(name) self.draw_current_services() - self.canvas_node.core_node.config_services[:] = self.current_services + self.node.config_services[:] = self.current_services def click_configure(self) -> None: current_selection = self.current.listbox.curselection() @@ -111,8 +110,7 @@ class NodeConfigServiceDialog(Dialog): self, self.app, self.current.listbox.get(current_selection[0]), - self.canvas_node, - self.node_id, + self.node, ) if not dialog.has_error: dialog.show() @@ -132,10 +130,8 @@ class NodeConfigServiceDialog(Dialog): self.current.listbox.itemconfig(tk.END, bg="green") def click_save(self) -> None: - self.canvas_node.core_node.config_services[:] = self.current_services - logging.info( - "saved node config services: %s", self.canvas_node.core_node.config_services - ) + self.node.config_services[:] = self.current_services + logging.info("saved node config services: %s", self.node.config_services) self.destroy() def click_cancel(self) -> None: @@ -154,4 +150,4 @@ class NodeConfigServiceDialog(Dialog): return def is_custom_service(self, service: str) -> bool: - return service in self.canvas_node.config_service_configs + return service in self.node.config_service_configs diff --git a/daemon/core/gui/dialogs/nodeservice.py b/daemon/core/gui/dialogs/nodeservice.py index 6fcc2912..a56736d5 100644 --- a/daemon/core/gui/dialogs/nodeservice.py +++ b/daemon/core/gui/dialogs/nodeservice.py @@ -9,22 +9,21 @@ from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.serviceconfig import ServiceConfigDialog from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.widgets import CheckboxList, ListboxScroll +from core.gui.wrappers import Node if TYPE_CHECKING: from core.gui.app import Application - from core.gui.graph.node import CanvasNode class NodeServiceDialog(Dialog): - def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None: - title = f"{canvas_node.core_node.name} Services" + def __init__(self, app: "Application", node: Node) -> None: + title = f"{node.name} Services" super().__init__(app, title) - self.canvas_node: "CanvasNode" = canvas_node - self.node_id: int = canvas_node.core_node.id + self.node: Node = node self.groups: Optional[ListboxScroll] = None self.services: Optional[CheckboxList] = None self.current: Optional[ListboxScroll] = None - services = set(canvas_node.core_node.services) + services = set(node.services) self.current_services: Set[str] = services self.draw() @@ -104,7 +103,7 @@ class NodeServiceDialog(Dialog): self.current.listbox.insert(tk.END, name) if self.is_custom_service(name): self.current.listbox.itemconfig(tk.END, bg="green") - self.canvas_node.core_node.services[:] = self.current_services + self.node.services = self.current_services.copy() def click_configure(self) -> None: current_selection = self.current.listbox.curselection() @@ -113,8 +112,7 @@ class NodeServiceDialog(Dialog): self, self.app, self.current.listbox.get(current_selection[0]), - self.canvas_node, - self.node_id, + self.node, ) # if error occurred when creating ServiceConfigDialog, don't show the dialog @@ -128,8 +126,7 @@ class NodeServiceDialog(Dialog): ) def click_save(self) -> None: - core_node = self.canvas_node.core_node - core_node.services[:] = self.current_services + self.node.services[:] = self.current_services self.destroy() def click_remove(self) -> None: @@ -144,6 +141,6 @@ class NodeServiceDialog(Dialog): return def is_custom_service(self, service: str) -> bool: - has_service_config = service in self.canvas_node.service_configs - has_file_config = service in self.canvas_node.service_file_configs + has_service_config = service in self.node.service_configs + has_file_config = service in self.node.service_file_configs return has_service_config or has_file_config diff --git a/daemon/core/gui/dialogs/runtool.py b/daemon/core/gui/dialogs/runtool.py index c66fea8f..a1517593 100644 --- a/daemon/core/gui/dialogs/runtool.py +++ b/daemon/core/gui/dialogs/runtool.py @@ -107,7 +107,7 @@ class RunToolDialog(Dialog): node_name = self.node_list.listbox.get(selection) node_id = self.executable_nodes[node_name] response = self.app.core.client.node_command( - self.app.core.session_id, node_id, command + self.app.core.session.id, node_id, command ) self.result.text.insert( tk.END, f"> {node_name} > {command}:\n{response.output}\n" diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index c033cfdc..13be0bcd 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -12,11 +12,10 @@ from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum, Images from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.widgets import CodeText, ListboxScroll -from core.gui.wrappers import NodeServiceData, ServiceValidationMode +from core.gui.wrappers import Node, NodeServiceData, ServiceValidationMode if TYPE_CHECKING: from core.gui.app import Application - from core.gui.graph.node import CanvasNode from core.gui.coreclient import CoreClient ICON_SIZE: int = 16 @@ -24,18 +23,12 @@ ICON_SIZE: int = 16 class ServiceConfigDialog(Dialog): def __init__( - self, - master: tk.BaseWidget, - app: "Application", - service_name: str, - canvas_node: "CanvasNode", - node_id: int, + self, master: tk.BaseWidget, app: "Application", service_name: str, node: Node ) -> None: title = f"{service_name} Service" super().__init__(app, title, master=master) self.core: "CoreClient" = app.core - self.canvas_node: "CanvasNode" = canvas_node - self.node_id: int = node_id + self.node: Node = node self.service_name: str = service_name self.radiovar: tk.IntVar = tk.IntVar(value=2) self.metadata: str = "" @@ -84,15 +77,13 @@ class ServiceConfigDialog(Dialog): try: self.app.core.create_nodes_and_links() default_config = self.app.core.get_node_service( - self.node_id, self.service_name + self.node.id, self.service_name ) 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_service_config = self.canvas_node.service_configs.get( - self.service_name - ) + custom_service_config = self.node.service_configs.get(self.service_name) self.default_config = default_config service_config = ( custom_service_config if custom_service_config else default_config @@ -109,15 +100,13 @@ class ServiceConfigDialog(Dialog): 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 + self.node.id, self.service_name, x ) for x in default_config.configs } self.temp_service_files = dict(self.original_service_files) - file_configs = self.canvas_node.service_file_configs.get( - self.service_name, {} - ) + file_configs = self.node.service_file_configs.get(self.service_name, {}) for file, data in file_configs.items(): self.temp_service_files[file] = data except grpc.RpcError as e: @@ -453,7 +442,7 @@ class ServiceConfigDialog(Dialog): and not self.has_new_files() and not self.is_custom_directory() ): - self.canvas_node.service_configs.pop(self.service_name, None) + self.node.service_configs.pop(self.service_name, None) self.current_service_color("") self.destroy() return @@ -466,7 +455,7 @@ class ServiceConfigDialog(Dialog): ): startup, validate, shutdown = self.get_commands() config = self.core.set_node_service( - self.node_id, + self.node.id, self.service_name, dirs=self.temp_directories, files=list(self.filename_combobox["values"]), @@ -474,15 +463,15 @@ class ServiceConfigDialog(Dialog): validations=validate, shutdowns=shutdown, ) - self.canvas_node.service_configs[self.service_name] = config + self.node.service_configs[self.service_name] = config for file in self.modified_files: - file_configs = self.canvas_node.service_file_configs.setdefault( + file_configs = self.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] + self.node.id, self.service_name, file, self.temp_service_files[file] ) self.current_service_color("green") except grpc.RpcError as e: @@ -526,8 +515,8 @@ class ServiceConfigDialog(Dialog): clears out any custom configuration permanently """ # clear coreclient data - self.canvas_node.service_configs.pop(self.service_name, None) - file_configs = self.canvas_node.service_file_configs.pop(self.service_name, {}) + self.node.service_configs.pop(self.service_name, None) + file_configs = self.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() @@ -564,9 +553,8 @@ class ServiceConfigDialog(Dialog): def click_copy(self) -> None: file_name = self.filename_combobox.get() - name = self.canvas_node.core_node.name dialog = CopyServiceConfigDialog( - self.app, self, name, self.service_name, file_name + self.app, self, self.node.name, self.service_name, file_name ) dialog.show() diff --git a/daemon/core/gui/dialogs/sessionoptions.py b/daemon/core/gui/dialogs/sessionoptions.py index 24bacb30..570bfbde 100644 --- a/daemon/core/gui/dialogs/sessionoptions.py +++ b/daemon/core/gui/dialogs/sessionoptions.py @@ -26,7 +26,7 @@ class SessionOptionsDialog(Dialog): def get_config(self) -> Dict[str, ConfigOption]: try: - session_id = self.app.core.session_id + session_id = self.app.core.session.id response = self.app.core.client.get_session_options(session_id) return ConfigOption.from_dict(response.config) except grpc.RpcError as e: @@ -54,7 +54,7 @@ class SessionOptionsDialog(Dialog): def save(self) -> None: config = self.config_frame.parse_config() try: - session_id = self.app.core.session_id + session_id = self.app.core.session.id response = self.app.core.client.set_session_options(session_id, config) logging.info("saved session config: %s", response) except grpc.RpcError as e: diff --git a/daemon/core/gui/dialogs/sessions.py b/daemon/core/gui/dialogs/sessions.py index 75b9dcf4..d41e2052 100644 --- a/daemon/core/gui/dialogs/sessions.py +++ b/daemon/core/gui/dialogs/sessions.py @@ -201,7 +201,7 @@ class SessionsDialog(Dialog): logging.debug("delete session: %s", self.selected_session) self.tree.delete(self.selected_id) self.app.core.delete_session(self.selected_session) - if self.selected_session == self.app.core.session_id: + if self.selected_session == self.app.core.session.id: self.click_new() self.destroy() self.click_select() diff --git a/daemon/core/gui/dialogs/wlanconfig.py b/daemon/core/gui/dialogs/wlanconfig.py index 17f62dfb..d4595556 100644 --- a/daemon/core/gui/dialogs/wlanconfig.py +++ b/daemon/core/gui/dialogs/wlanconfig.py @@ -29,7 +29,7 @@ class WlanConfigDialog(Dialog): self.ranges: Dict[int, int] = {} self.positive_int: int = self.app.master.register(self.validate_and_update) try: - config = self.canvas_node.wlan_config + config = self.node.wlan_config if not config: config = self.app.core.get_wlan_config(self.node.id) self.config: Dict[str, ConfigOption] = config @@ -83,9 +83,9 @@ class WlanConfigDialog(Dialog): retrieve user's wlan configuration and store the new configuration values """ config = self.config_frame.parse_config() - self.canvas_node.wlan_config = self.config + self.node.wlan_config = self.config if self.app.core.is_runtime(): - session_id = self.app.core.session_id + session_id = self.app.core.session.id self.app.core.client.set_wlan_config(session_id, self.node.id, config) self.remove_ranges() self.destroy() diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index f2a27444..54d2cae1 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -940,16 +940,19 @@ class CanvasGraph(tk.Canvas): if not copy: continue node = CanvasNode(self.app, 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) + node.core_node.services = core_node.services.copy() + node.core_node.config_services = core_node.config_services.copy() + node.core_node.emane_model_configs = deepcopy(core_node.emane_model_configs) + node.core_node.wlan_config = deepcopy(core_node.wlan_config) + node.core_node.mobility_config = deepcopy(core_node.mobility_config) + node.core_node.service_configs = deepcopy(core_node.service_configs) + node.core_node.service_file_configs = deepcopy( + core_node.service_file_configs + ) + node.core_node.config_service_configs = deepcopy( + core_node.config_service_configs + ) copy_map[canvas_node.id] = node.id self.core.canvas_nodes[copy.id] = node diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index 217389c0..ffc72fbf 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -1,7 +1,7 @@ import functools import logging import tkinter as tk -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple +from typing import TYPE_CHECKING, Dict, List, Set import grpc from PIL.ImageTk import PhotoImage @@ -19,7 +19,7 @@ from core.gui.graph.edges import CanvasEdge, CanvasWirelessEdge from core.gui.graph.tooltip import CanvasTooltip from core.gui.images import ImageEnum from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils -from core.gui.wrappers import ConfigOption, Interface, Node, NodeServiceData, NodeType +from core.gui.wrappers import Interface, Node, NodeType if TYPE_CHECKING: from core.gui.app import Application @@ -56,15 +56,6 @@ class CanvasNode: self.wireless_edges: Set[CanvasWirelessEdge] = set() self.antennas: List[int] = [] self.antenna_images: Dict[int, PhotoImage] = {} - # possible configurations - self.emane_model_configs: Dict[ - Tuple[str, Optional[int]], Dict[str, ConfigOption] - ] = {} - self.wlan_config: Dict[str, ConfigOption] = {} - self.mobility_config: Dict[str, ConfigOption] = {} - self.service_configs: Dict[str, NodeServiceData] = {} - self.service_file_configs: Dict[str, Dict[str, str]] = {} - self.config_service_configs: Dict[str, Any] = {} self.setup_bindings() self.context: tk.Menu = tk.Menu(self.canvas) themes.style_menu(self.context) @@ -299,7 +290,7 @@ class CanvasNode: dialog.show() def show_mobility_config(self) -> None: - dialog = MobilityConfigDialog(self.app, self) + dialog = MobilityConfigDialog(self.app, self.core_node) if not dialog.has_error: dialog.show() @@ -308,15 +299,15 @@ class CanvasNode: mobility_player.show() def show_emane_config(self) -> None: - dialog = EmaneConfigDialog(self.app, self) + dialog = EmaneConfigDialog(self.app, self.core_node) dialog.show() def show_services(self) -> None: - dialog = NodeServiceDialog(self.app, self) + dialog = NodeServiceDialog(self.app, self.core_node) dialog.show() def show_config_services(self) -> None: - dialog = NodeConfigServiceDialog(self.app, self) + dialog = NodeConfigServiceDialog(self.app, self.core_node) dialog.show() def has_emane_link(self, iface_id: int) -> Node: diff --git a/daemon/core/gui/task.py b/daemon/core/gui/task.py index c60350f9..f56fd54b 100644 --- a/daemon/core/gui/task.py +++ b/daemon/core/gui/task.py @@ -33,7 +33,6 @@ class ProgressTask: thread.start() def run(self) -> None: - logging.info("running task") try: values = self.task(*self.args) if values is None: @@ -41,7 +40,6 @@ class ProgressTask: elif values and not isinstance(values, tuple): values = (values,) if self.callback: - logging.info("calling callback") self.app.after(0, self.callback, *values) except Exception as e: logging.exception("progress task exception") diff --git a/daemon/core/gui/wrappers.py b/daemon/core/gui/wrappers.py index 835a9d17..d86e20dd 100644 --- a/daemon/core/gui/wrappers.py +++ b/daemon/core/gui/wrappers.py @@ -1,6 +1,6 @@ from dataclasses import dataclass, field from enum import Enum -from typing import Dict, List, Optional, Tuple +from typing import Dict, List, Optional, Set, Tuple from core.api.grpc import common_pb2, configservices_pb2, core_pb2, services_pb2 @@ -121,8 +121,8 @@ class ConfigService: @dataclass class ConfigServiceData: - templates: Dict[str, str] - config: Dict[str, str] + templates: Dict[str, str] = field(default_factory=dict) + config: Dict[str, str] = field(default_factory=dict) @dataclass @@ -504,8 +504,8 @@ class Node: type: NodeType model: str = None position: Position = None - services: List[str] = field(default_factory=list) - config_services: List[str] = field(default_factory=list) + services: Set[str] = field(default_factory=set) + config_services: Set[str] = field(default_factory=set) emane: str = None icon: str = None image: str = None @@ -538,8 +538,8 @@ class Node: type=NodeType(proto.type), model=proto.model, position=Position.from_proto(proto.position), - services=list(proto.services), - config_services=list(proto.config_services), + services=set(proto.services), + config_services=set(proto.config_services), emane=proto.emane, icon=proto.icon, image=proto.image, @@ -575,9 +575,9 @@ class Session: links: List[Link] dir: str user: str - default_services: Dict[str, List[str]] + default_services: Dict[str, Set[str]] location: SessionLocation - hooks: List[Hook] + hooks: Dict[str, Hook] emane_models: List[str] emane_config: Dict[str, ConfigOption] metadata: Dict[str, str] @@ -586,8 +586,10 @@ class Session: def from_proto(cls, proto: core_pb2.Session) -> "Session": nodes: Dict[int, Node] = {x.id: Node.from_proto(x) for x in proto.nodes} links = [Link.from_proto(x) for x in proto.links] - default_services = {x.node_type: x.services for x in proto.default_services} - hooks = [Hook.from_proto(x) for x in proto.hooks] + default_services = { + x.node_type: set(x.services) for x in proto.default_services + } + hooks = {x.file: Hook.from_proto(x) for x in proto.hooks} # update nodes with their current configurations for model in proto.emane_model_configs: iface_id = None From 27495cbda10341fe0a802742cb0aed28d1524316 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 28 Jul 2020 10:24:01 -0700 Subject: [PATCH 018/598] pygui: changes around using session.nodes instead of canvas_nodes when possible --- daemon/core/gui/coreclient.py | 68 +++++++++++++++--------------- daemon/core/gui/dialogs/find.py | 17 +++----- daemon/core/gui/dialogs/runtool.py | 6 +-- daemon/core/gui/frames/link.py | 10 ++--- daemon/core/gui/graph/graph.py | 24 +++++------ 5 files changed, 58 insertions(+), 67 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 36adf189..6129031a 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -118,6 +118,12 @@ class CoreClient: self.setup_cpu_usage() return self._client + def set_canvas_node(self, node: Node, canvas_node: CanvasNode) -> None: + self.canvas_nodes[node.id] = canvas_node + + def get_canvas_node(self, node_id: int) -> CanvasNode: + return self.canvas_nodes[node_id] + def reset(self) -> None: # helpers self.ifaces_manager.reset() @@ -231,18 +237,21 @@ class CoreClient: def handle_node_event(self, event: NodeEvent) -> None: logging.debug("node event: %s", event) + node = event.node if event.message_type == MessageType.NONE: - canvas_node = self.canvas_nodes[event.node.id] - x = event.node.position.x - y = event.node.position.y + canvas_node = self.canvas_nodes[node.id] + x = node.position.x + y = node.position.y canvas_node.move(x, y) elif event.message_type == MessageType.DELETE: - canvas_node = self.canvas_nodes[event.node.id] + canvas_node = self.canvas_nodes[node.id] self.app.canvas.clear_selection() self.app.canvas.select_object(canvas_node.id) self.app.canvas.delete_selected_objects() elif event.message_type == MessageType.ADD: - self.app.canvas.add_core_node(event.node) + if node.id in self.session.nodes: + logging.error("core node already exists: %s", node) + self.app.canvas.add_core_node(node) else: logging.warning("unknown node event: %s", event) @@ -463,10 +472,9 @@ class CoreClient: def start_session(self) -> Tuple[bool, List[str]]: self.ifaces_manager.reset_mac() - nodes = [x.core_node.to_proto() for x in self.canvas_nodes.values()] + nodes = [x.to_proto() for x in self.session.nodes.values()] links = [] - for edge in self.links.values(): - link = edge.link + for link in self.session.links: if link.iface1 and not link.iface1.mac: link.iface1.mac = self.ifaces_manager.next_mac() if link.iface2 and not link.iface2.mac: @@ -674,13 +682,12 @@ class CoreClient: """ create nodes and links that have not been created yet """ - node_protos = [x.core_node.to_proto() for x in self.canvas_nodes.values()] - link_protos = [x.link.to_proto() for x in self.links.values()] self.client.set_session_state(self.session.id, SessionState.DEFINITION.value) - for node_proto in node_protos: - response = self.client.add_node(self.session.id, node_proto) - logging.debug("create node: %s", response) - for link_proto in link_protos: + for node in self.session.nodes.values(): + response = self.client.add_node(self.session.id, node.to_proto()) + logging.debug("created node: %s", response) + for link in self.session.links: + link_proto = link.to_proto() response = self.client.add_link( self.session.id, link_proto.node1_id, @@ -689,7 +696,7 @@ class CoreClient: link_proto.iface2, link_proto.options, ) - logging.debug("create link: %s", response) + logging.debug("created link: %s", response) def send_data(self) -> None: """ @@ -762,7 +769,7 @@ class CoreClient: """ i = 1 while True: - if i not in self.canvas_nodes: + if i not in self.session.nodes: break i += 1 return i @@ -816,18 +823,20 @@ class CoreClient: x, y, ) + self.session.nodes[node.id] = node return node - def deleted_graph_nodes(self, canvas_nodes: List[CanvasNode]) -> None: + def deleted_canvas_nodes(self, canvas_nodes: List[CanvasNode]) -> None: """ remove the nodes selected by the user and anything related to that node such as link, configurations, interfaces """ for canvas_node in canvas_nodes: - node_id = canvas_node.core_node.id - del self.canvas_nodes[node_id] + node = canvas_node.core_node + del self.canvas_nodes[node.id] + del self.session.nodes[node.id] - def deleted_graph_edges(self, edges: Iterable[CanvasEdge]) -> None: + def deleted_canvas_edges(self, edges: Iterable[CanvasEdge]) -> None: links = [] for edge in edges: del self.links[edge.token] @@ -861,20 +870,19 @@ class CoreClient: """ src_node = canvas_src_node.core_node dst_node = canvas_dst_node.core_node - - # determine subnet self.ifaces_manager.determine_subnets(canvas_src_node, canvas_dst_node) - src_iface = None if NodeUtils.is_container_node(src_node.type): src_iface = self.create_iface(canvas_src_node) self.iface_to_edge[(src_node.id, src_iface.id)] = edge.token - + edge.src_iface = src_iface + canvas_src_node.ifaces[src_iface.id] = src_iface dst_iface = None if NodeUtils.is_container_node(dst_node.type): dst_iface = self.create_iface(canvas_dst_node) self.iface_to_edge[(dst_node.id, dst_iface.id)] = edge.token - + edge.dst_iface = dst_iface + canvas_dst_node.ifaces[dst_iface.id] = dst_iface link = Link( type=LinkType.WIRED, node1_id=src_node.id, @@ -882,17 +890,9 @@ class CoreClient: iface1=src_iface, iface2=dst_iface, ) - # assign after creating link proto, since interfaces are copied - if src_iface: - iface1 = link.iface1 - edge.src_iface = iface1 - canvas_src_node.ifaces[iface1.id] = iface1 - if dst_iface: - iface2 = link.iface2 - edge.dst_iface = iface2 - canvas_dst_node.ifaces[iface2.id] = iface2 edge.set_link(link) self.links[edge.token] = edge + self.session.links.append(link) logging.info("Add link between %s and %s", src_node.name, dst_node.name) def get_wlan_configs_proto(self) -> List[wlan_pb2.WlanConfig]: diff --git a/daemon/core/gui/dialogs/find.py b/daemon/core/gui/dialogs/find.py index 328f673e..a4600847 100644 --- a/daemon/core/gui/dialogs/find.py +++ b/daemon/core/gui/dialogs/find.py @@ -87,22 +87,19 @@ class FindDialog(Dialog): """ node_name = self.find_text.get().strip() self.clear_treeview_items() - for node_id, node in sorted( - self.app.core.canvas_nodes.items(), key=lambda x: x[0] - ): - name = node.core_node.name + for node in self.app.core.session.nodes.values(): + name = node.name if not node_name or node_name == name: - pos_x = round(node.core_node.position.x, 1) - pos_y = round(node.core_node.position.y, 1) + pos_x = round(node.position.x, 1) + pos_y = round(node.position.y, 1) # TODO: I am not sure what to insert for Detail column # leaving it blank for now self.tree.insert( "", tk.END, - text=str(node_id), - values=(node_id, name, f"<{pos_x}, {pos_y}>", ""), + text=str(node.id), + values=(node.id, name, f"<{pos_x}, {pos_y}>", ""), ) - results = self.tree.get_children("") if results: self.tree.selection_set(results[0]) @@ -121,7 +118,7 @@ class FindDialog(Dialog): if item: self.app.canvas.delete("find") node_id = int(self.tree.item(item, "text")) - canvas_node = self.app.core.canvas_nodes[node_id] + canvas_node = self.app.core.get_canvas_node(node_id) x0, y0, x1, y1 = self.app.canvas.bbox(canvas_node.id) dist = 5 * self.app.guiconfig.scale diff --git a/daemon/core/gui/dialogs/runtool.py b/daemon/core/gui/dialogs/runtool.py index a1517593..e36c4c9a 100644 --- a/daemon/core/gui/dialogs/runtool.py +++ b/daemon/core/gui/dialogs/runtool.py @@ -25,9 +25,9 @@ class RunToolDialog(Dialog): """ store all CORE nodes (nodes that execute commands) from all existing nodes """ - for nid, node in self.app.core.canvas_nodes.items(): - if NodeUtils.is_container_node(node.core_node.type): - self.executable_nodes[node.core_node.name] = nid + for node in self.app.core.session.nodes.values(): + if NodeUtils.is_container_node(node.type): + self.executable_nodes[node.name] = node.id def draw(self) -> None: self.top.rowconfigure(0, weight=1) diff --git a/daemon/core/gui/frames/link.py b/daemon/core/gui/frames/link.py index cbea9982..093f39eb 100644 --- a/daemon/core/gui/frames/link.py +++ b/daemon/core/gui/frames/link.py @@ -34,10 +34,8 @@ class EdgeInfoFrame(InfoFrameBase): self.columnconfigure(0, weight=1) link = self.edge.link options = link.options - src_canvas_node = self.app.core.canvas_nodes[link.node1_id] - src_node = src_canvas_node.core_node - dst_canvas_node = self.app.core.canvas_nodes[link.node2_id] - dst_node = dst_canvas_node.core_node + src_node = self.app.core.session.nodes[link.node1_id] + dst_node = self.app.core.session.nodes[link.node2_id] frame = DetailsFrame(self) frame.grid(sticky="ew") @@ -81,9 +79,9 @@ class WirelessEdgeInfoFrame(InfoFrameBase): def draw(self) -> None: link = self.edge.link - src_canvas_node = self.app.core.canvas_nodes[link.node1_id] + src_canvas_node = self.app.canvas.nodes[self.edge.src] src_node = src_canvas_node.core_node - dst_canvas_node = self.app.core.canvas_nodes[link.node2_id] + dst_canvas_node = self.app.canvas.nodes[self.edge.dst] dst_node = dst_canvas_node.core_node # find interface for each node connected to network diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 54d2cae1..69ae87cc 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -311,10 +311,7 @@ class CanvasGraph(tk.Canvas): edge.middle_label_text(link.label) def add_core_node(self, core_node: Node) -> None: - if core_node.id in self.core.canvas_nodes: - logging.error("core node already exists: %s", core_node) - return - logging.debug("adding node %s", core_node) + logging.debug("adding node: %s", core_node) # if the gui can't find node's image, default to the "edit-node" image image = NodeUtils.node_image(core_node, self.app.guiconfig, self.app.app_scale) if not image: @@ -323,7 +320,7 @@ class CanvasGraph(tk.Canvas): y = core_node.position.y node = CanvasNode(self.app, x, y, core_node, image) self.nodes[node.id] = node - self.core.canvas_nodes[core_node.id] = node + self.core.set_canvas_node(core_node, node) def draw_session(self, session: Session) -> None: """ @@ -336,12 +333,11 @@ class CanvasGraph(tk.Canvas): if NodeUtils.is_ignore_node(core_node.type): continue self.add_core_node(core_node) - - # draw existing links + # draw existing links for link in session.links: logging.debug("drawing link: %s", link) - canvas_node1 = self.core.canvas_nodes[link.node1_id] - canvas_node2 = self.core.canvas_nodes[link.node2_id] + canvas_node1 = self.core.get_canvas_node(link.node1_id) + canvas_node2 = self.core.get_canvas_node(link.node2_id) if link.type == LinkType.WIRELESS: self.add_wireless_edge(canvas_node1, canvas_node2, link) else: @@ -544,8 +540,8 @@ class CanvasGraph(tk.Canvas): shape.delete() self.selection.clear() - self.core.deleted_graph_nodes(nodes) - self.core.deleted_graph_edges(edges) + self.core.deleted_canvas_nodes(nodes) + self.core.deleted_canvas_edges(edges) def delete_edge(self, edge: CanvasEdge) -> None: edge.delete() @@ -564,7 +560,7 @@ class CanvasGraph(tk.Canvas): dst_wireless = NodeUtils.is_wireless_node(dst_node.core_node.type) if dst_wireless: src_node.delete_antenna() - self.core.deleted_graph_edges([edge]) + self.core.deleted_canvas_edges([edge]) def zoom(self, event: tk.Event, factor: float = None) -> None: if not factor: @@ -750,8 +746,8 @@ class CanvasGraph(tk.Canvas): image_file = self.node_draw.image_file self.node_draw.image = self.app.get_custom_icon(image_file, ICON_SIZE) node = CanvasNode(self.app, x, y, core_node, self.node_draw.image) - self.core.canvas_nodes[core_node.id] = node self.nodes[node.id] = node + self.core.set_canvas_node(core_node, node) def width_and_height(self) -> Tuple[int, int]: """ @@ -955,8 +951,8 @@ class CanvasGraph(tk.Canvas): ) copy_map[canvas_node.id] = node.id - self.core.canvas_nodes[copy.id] = node self.nodes[node.id] = node + self.core.set_canvas_node(copy, node) for edge in canvas_node.edges: if edge.src not in self.to_copy or edge.dst not in self.to_copy: if canvas_node.id == edge.src: From 0d2dd70727ab6c8ab5b0055539d9587c146dce21 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 28 Jul 2020 16:13:37 -0700 Subject: [PATCH 019/598] daemon: changes usage of running scripts using /bin/sh to bash to help provide consistency in what could be ran, added bash as a dependency in installation scripts, added bash as an executable check during startup --- .../configservices/frrservices/services.py | 2 +- .../configservices/nrlservices/services.py | 16 ++++++++-------- .../configservices/quaggaservices/services.py | 2 +- .../securityservices/services.py | 10 +++++----- .../configservices/utilservices/services.py | 18 +++++++++--------- daemon/core/executables.py | 12 +++++++++++- daemon/core/location/mobility.py | 3 ++- daemon/core/nodes/base.py | 2 +- daemon/core/nodes/client.py | 8 +++++--- daemon/core/nodes/interface.py | 4 ++-- daemon/core/nodes/netclient.py | 6 +----- daemon/core/services/emaneservices.py | 2 +- daemon/core/services/frr.py | 2 +- daemon/core/services/nrl.py | 6 +++--- daemon/core/services/quagga.py | 2 +- daemon/core/services/sdn.py | 4 ++-- daemon/core/services/security.py | 10 +++++----- daemon/core/services/ucarp.py | 2 +- daemon/core/services/utility.py | 18 +++++++++--------- tasks.py | 4 ++-- 20 files changed, 71 insertions(+), 62 deletions(-) diff --git a/daemon/core/configservices/frrservices/services.py b/daemon/core/configservices/frrservices/services.py index 72050077..fa6f599a 100644 --- a/daemon/core/configservices/frrservices/services.py +++ b/daemon/core/configservices/frrservices/services.py @@ -65,7 +65,7 @@ class FRRZebra(ConfigService): ] executables: List[str] = ["zebra"] dependencies: List[str] = [] - startup: List[str] = ["sh frrboot.sh zebra"] + startup: List[str] = ["bash frrboot.sh zebra"] validate: List[str] = ["pidof zebra"] shutdown: List[str] = ["killall zebra"] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING diff --git a/daemon/core/configservices/nrlservices/services.py b/daemon/core/configservices/nrlservices/services.py index cf9b4c88..3f911aef 100644 --- a/daemon/core/configservices/nrlservices/services.py +++ b/daemon/core/configservices/nrlservices/services.py @@ -14,7 +14,7 @@ class MgenSinkService(ConfigService): files: List[str] = ["mgensink.sh", "sink.mgen"] executables: List[str] = ["mgen"] dependencies: List[str] = [] - startup: List[str] = ["sh mgensink.sh"] + startup: List[str] = ["bash mgensink.sh"] validate: List[str] = ["pidof mgen"] shutdown: List[str] = ["killall mgen"] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING @@ -36,7 +36,7 @@ class NrlNhdp(ConfigService): files: List[str] = ["nrlnhdp.sh"] executables: List[str] = ["nrlnhdp"] dependencies: List[str] = [] - startup: List[str] = ["sh nrlnhdp.sh"] + startup: List[str] = ["bash nrlnhdp.sh"] validate: List[str] = ["pidof nrlnhdp"] shutdown: List[str] = ["killall nrlnhdp"] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING @@ -58,7 +58,7 @@ class NrlSmf(ConfigService): files: List[str] = ["startsmf.sh"] executables: List[str] = ["nrlsmf", "killall"] dependencies: List[str] = [] - startup: List[str] = ["sh startsmf.sh"] + startup: List[str] = ["bash startsmf.sh"] validate: List[str] = ["pidof nrlsmf"] shutdown: List[str] = ["killall nrlsmf"] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING @@ -93,7 +93,7 @@ class NrlOlsr(ConfigService): files: List[str] = ["nrlolsrd.sh"] executables: List[str] = ["nrlolsrd"] dependencies: List[str] = [] - startup: List[str] = ["sh nrlolsrd.sh"] + startup: List[str] = ["bash nrlolsrd.sh"] validate: List[str] = ["pidof nrlolsrd"] shutdown: List[str] = ["killall nrlolsrd"] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING @@ -117,7 +117,7 @@ class NrlOlsrv2(ConfigService): files: List[str] = ["nrlolsrv2.sh"] executables: List[str] = ["nrlolsrv2"] dependencies: List[str] = [] - startup: List[str] = ["sh nrlolsrv2.sh"] + startup: List[str] = ["bash nrlolsrv2.sh"] validate: List[str] = ["pidof nrlolsrv2"] shutdown: List[str] = ["killall nrlolsrv2"] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING @@ -139,7 +139,7 @@ class OlsrOrg(ConfigService): files: List[str] = ["olsrd.sh", "/etc/olsrd/olsrd.conf"] executables: List[str] = ["olsrd"] dependencies: List[str] = [] - startup: List[str] = ["sh olsrd.sh"] + startup: List[str] = ["bash olsrd.sh"] validate: List[str] = ["pidof olsrd"] shutdown: List[str] = ["killall olsrd"] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING @@ -161,7 +161,7 @@ class MgenActor(ConfigService): files: List[str] = ["start_mgen_actor.sh"] executables: List[str] = ["mgen"] dependencies: List[str] = [] - startup: List[str] = ["sh start_mgen_actor.sh"] + startup: List[str] = ["bash start_mgen_actor.sh"] validate: List[str] = ["pidof mgen"] shutdown: List[str] = ["killall mgen"] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING @@ -176,7 +176,7 @@ class Arouted(ConfigService): files: List[str] = ["startarouted.sh"] executables: List[str] = ["arouted"] dependencies: List[str] = [] - startup: List[str] = ["sh startarouted.sh"] + startup: List[str] = ["bash startarouted.sh"] validate: List[str] = ["pidof arouted"] shutdown: List[str] = ["pkill arouted"] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING diff --git a/daemon/core/configservices/quaggaservices/services.py b/daemon/core/configservices/quaggaservices/services.py index 19430664..bf23e00c 100644 --- a/daemon/core/configservices/quaggaservices/services.py +++ b/daemon/core/configservices/quaggaservices/services.py @@ -65,7 +65,7 @@ class Zebra(ConfigService): ] executables: List[str] = ["zebra"] dependencies: List[str] = [] - startup: List[str] = ["sh quaggaboot.sh zebra"] + startup: List[str] = ["bash quaggaboot.sh zebra"] validate: List[str] = ["pidof zebra"] shutdown: List[str] = ["killall zebra"] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING diff --git a/daemon/core/configservices/securityservices/services.py b/daemon/core/configservices/securityservices/services.py index 4a58fd8c..c656f5ca 100644 --- a/daemon/core/configservices/securityservices/services.py +++ b/daemon/core/configservices/securityservices/services.py @@ -14,7 +14,7 @@ class VpnClient(ConfigService): files: List[str] = ["vpnclient.sh"] executables: List[str] = ["openvpn", "ip", "killall"] dependencies: List[str] = [] - startup: List[str] = ["sh vpnclient.sh"] + startup: List[str] = ["bash vpnclient.sh"] validate: List[str] = ["pidof openvpn"] shutdown: List[str] = ["killall openvpn"] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING @@ -48,7 +48,7 @@ class VpnServer(ConfigService): files: List[str] = ["vpnserver.sh"] executables: List[str] = ["openvpn", "ip", "killall"] dependencies: List[str] = [] - startup: List[str] = ["sh vpnserver.sh"] + startup: List[str] = ["bash vpnserver.sh"] validate: List[str] = ["pidof openvpn"] shutdown: List[str] = ["killall openvpn"] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING @@ -91,7 +91,7 @@ class IPsec(ConfigService): files: List[str] = ["ipsec.sh"] executables: List[str] = ["racoon", "ip", "setkey", "killall"] dependencies: List[str] = [] - startup: List[str] = ["sh ipsec.sh"] + startup: List[str] = ["bash ipsec.sh"] validate: List[str] = ["pidof racoon"] shutdown: List[str] = ["killall racoon"] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING @@ -106,7 +106,7 @@ class Firewall(ConfigService): files: List[str] = ["firewall.sh"] executables: List[str] = ["iptables"] dependencies: List[str] = [] - startup: List[str] = ["sh firewall.sh"] + startup: List[str] = ["bash firewall.sh"] validate: List[str] = [] shutdown: List[str] = [] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING @@ -121,7 +121,7 @@ class Nat(ConfigService): files: List[str] = ["nat.sh"] executables: List[str] = ["iptables"] dependencies: List[str] = [] - startup: List[str] = ["sh nat.sh"] + startup: List[str] = ["bash nat.sh"] validate: List[str] = [] shutdown: List[str] = [] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING diff --git a/daemon/core/configservices/utilservices/services.py b/daemon/core/configservices/utilservices/services.py index b6bc0eb5..9b3369db 100644 --- a/daemon/core/configservices/utilservices/services.py +++ b/daemon/core/configservices/utilservices/services.py @@ -16,7 +16,7 @@ class DefaultRouteService(ConfigService): files: List[str] = ["defaultroute.sh"] executables: List[str] = ["ip"] dependencies: List[str] = [] - startup: List[str] = ["sh defaultroute.sh"] + startup: List[str] = ["bash defaultroute.sh"] validate: List[str] = [] shutdown: List[str] = [] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING @@ -44,7 +44,7 @@ class DefaultMulticastRouteService(ConfigService): files: List[str] = ["defaultmroute.sh"] executables: List[str] = [] dependencies: List[str] = [] - startup: List[str] = ["sh defaultmroute.sh"] + startup: List[str] = ["bash defaultmroute.sh"] validate: List[str] = [] shutdown: List[str] = [] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING @@ -66,7 +66,7 @@ class StaticRouteService(ConfigService): files: List[str] = ["staticroute.sh"] executables: List[str] = [] dependencies: List[str] = [] - startup: List[str] = ["sh staticroute.sh"] + startup: List[str] = ["bash staticroute.sh"] validate: List[str] = [] shutdown: List[str] = [] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING @@ -94,7 +94,7 @@ class IpForwardService(ConfigService): files: List[str] = ["ipforward.sh"] executables: List[str] = ["sysctl"] dependencies: List[str] = [] - startup: List[str] = ["sh ipforward.sh"] + startup: List[str] = ["bash ipforward.sh"] validate: List[str] = [] shutdown: List[str] = [] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING @@ -116,7 +116,7 @@ class SshService(ConfigService): files: List[str] = ["startsshd.sh", "/etc/ssh/sshd_config"] executables: List[str] = ["sshd"] dependencies: List[str] = [] - startup: List[str] = ["sh startsshd.sh"] + startup: List[str] = ["bash startsshd.sh"] validate: List[str] = [] shutdown: List[str] = ["killall sshd"] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING @@ -164,7 +164,7 @@ class DhcpClientService(ConfigService): files: List[str] = ["startdhcpclient.sh"] executables: List[str] = ["dhclient"] dependencies: List[str] = [] - startup: List[str] = ["sh startdhcpclient.sh"] + startup: List[str] = ["bash startdhcpclient.sh"] validate: List[str] = ["pidof dhclient"] shutdown: List[str] = ["killall dhclient"] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING @@ -200,9 +200,9 @@ class PcapService(ConfigService): files: List[str] = ["pcap.sh"] executables: List[str] = ["tcpdump"] dependencies: List[str] = [] - startup: List[str] = ["sh pcap.sh start"] + startup: List[str] = ["bash pcap.sh start"] validate: List[str] = ["pidof tcpdump"] - shutdown: List[str] = ["sh pcap.sh stop"] + shutdown: List[str] = ["bash pcap.sh stop"] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING default_configs: List[Configuration] = [] modes: Dict[str, Dict[str, str]] = {} @@ -249,7 +249,7 @@ class AtdService(ConfigService): files: List[str] = ["startatd.sh"] executables: List[str] = ["atd"] dependencies: List[str] = [] - startup: List[str] = ["sh startatd.sh"] + startup: List[str] = ["bash startatd.sh"] validate: List[str] = ["pidof atd"] shutdown: List[str] = ["pkill atd"] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING diff --git a/daemon/core/executables.py b/daemon/core/executables.py index 6eb0214a..7b7f80b7 100644 --- a/daemon/core/executables.py +++ b/daemon/core/executables.py @@ -1,5 +1,6 @@ from typing import List +BASH: str = "bash" VNODED: str = "vnoded" VCMD: str = "vcmd" SYSCTL: str = "sysctl" @@ -11,7 +12,16 @@ MOUNT: str = "mount" UMOUNT: str = "umount" OVS_VSCTL: str = "ovs-vsctl" -COMMON_REQUIREMENTS: List[str] = [SYSCTL, IP, ETHTOOL, TC, EBTABLES, MOUNT, UMOUNT] +COMMON_REQUIREMENTS: List[str] = [ + BASH, + EBTABLES, + ETHTOOL, + IP, + MOUNT, + SYSCTL, + TC, + UMOUNT, +] VCMD_REQUIREMENTS: List[str] = [VNODED, VCMD] OVS_REQUIREMENTS: List[str] = [OVS_VSCTL] diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index e982c5c1..0e9b2e32 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -22,6 +22,7 @@ from core.emulator.enumerations import ( RegisterTlvs, ) from core.errors import CoreError +from core.executables import BASH from core.nodes.base import CoreNode from core.nodes.interface import CoreInterface from core.nodes.network import WlanNode @@ -1167,7 +1168,7 @@ class Ns2ScriptedMobility(WayPointMobility): if filename is None or filename == "": return filename = self.findfile(filename) - args = f"/bin/sh {filename} {typestr}" + args = f"{BASH} {filename} {typestr}" utils.cmd( args, cwd=self.session.session_dir, env=self.session.get_environment() ) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index cea1e81b..4cf6ea8d 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -599,7 +599,7 @@ class CoreNode(CoreNodeBase): if self.server is None: return self.client.check_cmd(args, wait=wait, shell=shell) else: - args = self.client.create_cmd(args) + args = self.client.create_cmd(args, shell) return self.server.remote_cmd(args, wait=wait) def termcmdstring(self, sh: str = "/bin/sh") -> str: diff --git a/daemon/core/nodes/client.py b/daemon/core/nodes/client.py index 93e099cf..710724b1 100644 --- a/daemon/core/nodes/client.py +++ b/daemon/core/nodes/client.py @@ -5,7 +5,7 @@ The control channel can be accessed via calls using the vcmd shell. """ from core import utils -from core.executables import VCMD +from core.executables import BASH, VCMD class VnodeClient: @@ -49,7 +49,9 @@ class VnodeClient: """ pass - def create_cmd(self, args: str) -> str: + def create_cmd(self, args: str, shell: bool = False) -> str: + if shell: + args = f'{BASH} -c "{args}"' return f"{VCMD} -c {self.ctrlchnlname} -- {args}" def check_cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: @@ -63,5 +65,5 @@ class VnodeClient: :raises core.CoreCommandError: when there is a non-zero exit status """ self._verify_connection() - args = self.create_cmd(args) + args = self.create_cmd(args, shell) return utils.cmd(args, wait=wait, shell=shell) diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 7f33973e..20dc8fd3 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -383,12 +383,12 @@ class Veth(CoreInterface): try: self.node.node_net_client.device_flush(self.name) except CoreCommandError: - logging.exception("error shutting down interface") + pass if self.localname: try: self.net_client.delete_device(self.localname) except CoreCommandError: - logging.info("link already removed: %s", self.localname) + pass self.up = False diff --git a/daemon/core/nodes/netclient.py b/daemon/core/nodes/netclient.py index 96a1f4be..68fbef98 100644 --- a/daemon/core/nodes/netclient.py +++ b/daemon/core/nodes/netclient.py @@ -121,11 +121,7 @@ class LinuxNetClient: :param device: device to flush :return: nothing """ - self.run( - f"[ -e /sys/class/net/{device} ] && " - f"{IP} address flush dev {device} || true", - shell=True, - ) + self.run(f"{IP} address flush dev {device}") def device_mac(self, device: str, mac: str) -> None: """ diff --git a/daemon/core/services/emaneservices.py b/daemon/core/services/emaneservices.py index d694317a..4fd78ec1 100644 --- a/daemon/core/services/emaneservices.py +++ b/daemon/core/services/emaneservices.py @@ -13,7 +13,7 @@ class EmaneTransportService(CoreService): dependencies: Tuple[str, ...] = () dirs: Tuple[str, ...] = () configs: Tuple[str, ...] = ("emanetransport.sh",) - startup: Tuple[str, ...] = (f"sh {configs[0]}",) + startup: Tuple[str, ...] = (f"bash {configs[0]}",) validate: Tuple[str, ...] = (f"pidof {executables[0]}",) validation_timer: float = 0.5 shutdown: Tuple[str, ...] = (f"killall {executables[0]}",) diff --git a/daemon/core/services/frr.py b/daemon/core/services/frr.py index b130fd8c..cec9d860 100644 --- a/daemon/core/services/frr.py +++ b/daemon/core/services/frr.py @@ -26,7 +26,7 @@ class FRRZebra(CoreService): "/usr/local/etc/frr/vtysh.conf", "/usr/local/etc/frr/daemons", ) - startup: Tuple[str, ...] = ("sh frrboot.sh zebra",) + startup: Tuple[str, ...] = ("bash frrboot.sh zebra",) shutdown: Tuple[str, ...] = ("killall zebra",) validate: Tuple[str, ...] = ("pidof zebra",) diff --git a/daemon/core/services/nrl.py b/daemon/core/services/nrl.py index 697f4eee..91e053b2 100644 --- a/daemon/core/services/nrl.py +++ b/daemon/core/services/nrl.py @@ -97,7 +97,7 @@ class NrlSmf(NrlService): name: str = "SMF" executables: Tuple[str, ...] = ("nrlsmf",) - startup: Tuple[str, ...] = ("sh startsmf.sh",) + startup: Tuple[str, ...] = ("bash startsmf.sh",) shutdown: Tuple[str, ...] = ("killall nrlsmf",) validate: Tuple[str, ...] = ("pidof nrlsmf",) configs: Tuple[str, ...] = ("startsmf.sh",) @@ -566,7 +566,7 @@ class MgenActor(NrlService): group: str = "ProtoSvc" executables: Tuple[str, ...] = ("mgen",) configs: Tuple[str, ...] = ("start_mgen_actor.sh",) - startup: Tuple[str, ...] = ("sh start_mgen_actor.sh",) + startup: Tuple[str, ...] = ("bash start_mgen_actor.sh",) validate: Tuple[str, ...] = ("pidof mgen",) shutdown: Tuple[str, ...] = ("killall mgen",) @@ -596,7 +596,7 @@ class Arouted(NrlService): name: str = "arouted" executables: Tuple[str, ...] = ("arouted",) configs: Tuple[str, ...] = ("startarouted.sh",) - startup: Tuple[str, ...] = ("sh startarouted.sh",) + startup: Tuple[str, ...] = ("bash startarouted.sh",) shutdown: Tuple[str, ...] = ("pkill arouted",) validate: Tuple[str, ...] = ("pidof arouted",) diff --git a/daemon/core/services/quagga.py b/daemon/core/services/quagga.py index 9e2c7cc0..8c474fd8 100644 --- a/daemon/core/services/quagga.py +++ b/daemon/core/services/quagga.py @@ -25,7 +25,7 @@ class Zebra(CoreService): "quaggaboot.sh", "/usr/local/etc/quagga/vtysh.conf", ) - startup: Tuple[str, ...] = ("sh quaggaboot.sh zebra",) + startup: Tuple[str, ...] = ("bash quaggaboot.sh zebra",) shutdown: Tuple[str, ...] = ("killall zebra",) validate: Tuple[str, ...] = ("pidof zebra",) diff --git a/daemon/core/services/sdn.py b/daemon/core/services/sdn.py index ef077662..e72b5138 100644 --- a/daemon/core/services/sdn.py +++ b/daemon/core/services/sdn.py @@ -31,7 +31,7 @@ class OvsService(SdnService): "/var/log/openvswitch", ) configs: Tuple[str, ...] = ("OvsService.sh",) - startup: Tuple[str, ...] = ("sh OvsService.sh",) + startup: Tuple[str, ...] = ("bash OvsService.sh",) shutdown: Tuple[str, ...] = ("killall ovs-vswitchd", "killall ovsdb-server") @classmethod @@ -119,7 +119,7 @@ class RyuService(SdnService): group: str = "SDN" executables: Tuple[str, ...] = ("ryu-manager",) configs: Tuple[str, ...] = ("ryuService.sh",) - startup: Tuple[str, ...] = ("sh ryuService.sh",) + startup: Tuple[str, ...] = ("bash ryuService.sh",) shutdown: Tuple[str, ...] = ("killall ryu-manager",) @classmethod diff --git a/daemon/core/services/security.py b/daemon/core/services/security.py index b813579e..788988c9 100644 --- a/daemon/core/services/security.py +++ b/daemon/core/services/security.py @@ -16,7 +16,7 @@ class VPNClient(CoreService): name: str = "VPNClient" group: str = "Security" configs: Tuple[str, ...] = ("vpnclient.sh",) - startup: Tuple[str, ...] = ("sh vpnclient.sh",) + startup: Tuple[str, ...] = ("bash vpnclient.sh",) shutdown: Tuple[str, ...] = ("killall openvpn",) validate: Tuple[str, ...] = ("pidof openvpn",) custom_needed: bool = True @@ -43,7 +43,7 @@ class VPNServer(CoreService): name: str = "VPNServer" group: str = "Security" configs: Tuple[str, ...] = ("vpnserver.sh",) - startup: Tuple[str, ...] = ("sh vpnserver.sh",) + startup: Tuple[str, ...] = ("bash vpnserver.sh",) shutdown: Tuple[str, ...] = ("killall openvpn",) validate: Tuple[str, ...] = ("pidof openvpn",) custom_needed: bool = True @@ -71,7 +71,7 @@ class IPsec(CoreService): name: str = "IPsec" group: str = "Security" configs: Tuple[str, ...] = ("ipsec.sh",) - startup: Tuple[str, ...] = ("sh ipsec.sh",) + startup: Tuple[str, ...] = ("bash ipsec.sh",) shutdown: Tuple[str, ...] = ("killall racoon",) custom_needed: bool = True @@ -97,7 +97,7 @@ class Firewall(CoreService): name: str = "Firewall" group: str = "Security" configs: Tuple[str, ...] = ("firewall.sh",) - startup: Tuple[str, ...] = ("sh firewall.sh",) + startup: Tuple[str, ...] = ("bash firewall.sh",) custom_needed: bool = True @classmethod @@ -127,7 +127,7 @@ class Nat(CoreService): group: str = "Security" executables: Tuple[str, ...] = ("iptables",) configs: Tuple[str, ...] = ("nat.sh",) - startup: Tuple[str, ...] = ("sh nat.sh",) + startup: Tuple[str, ...] = ("bash nat.sh",) custom_needed: bool = False @classmethod diff --git a/daemon/core/services/ucarp.py b/daemon/core/services/ucarp.py index 8ac92dd3..522eeaf6 100644 --- a/daemon/core/services/ucarp.py +++ b/daemon/core/services/ucarp.py @@ -19,7 +19,7 @@ class Ucarp(CoreService): UCARP_ETC + "/default-down.sh", "ucarpboot.sh", ) - startup: Tuple[str, ...] = ("sh ucarpboot.sh",) + startup: Tuple[str, ...] = ("bash ucarpboot.sh",) shutdown: Tuple[str, ...] = ("killall ucarp",) validate: Tuple[str, ...] = ("pidof ucarp",) diff --git a/daemon/core/services/utility.py b/daemon/core/services/utility.py index 774c4104..a30d1f62 100644 --- a/daemon/core/services/utility.py +++ b/daemon/core/services/utility.py @@ -28,7 +28,7 @@ class UtilService(CoreService): class IPForwardService(UtilService): name: str = "IPForward" configs: Tuple[str, ...] = ("ipforward.sh",) - startup: Tuple[str, ...] = ("sh ipforward.sh",) + startup: Tuple[str, ...] = ("bash ipforward.sh",) @classmethod def generate_config(cls, node: CoreNode, filename: str) -> str: @@ -61,7 +61,7 @@ class IPForwardService(UtilService): class DefaultRouteService(UtilService): name: str = "DefaultRoute" configs: Tuple[str, ...] = ("defaultroute.sh",) - startup: Tuple[str, ...] = ("sh defaultroute.sh",) + startup: Tuple[str, ...] = ("bash defaultroute.sh",) @classmethod def generate_config(cls, node: CoreNode, filename: str) -> str: @@ -84,7 +84,7 @@ class DefaultRouteService(UtilService): class DefaultMulticastRouteService(UtilService): name: str = "DefaultMulticastRoute" configs: Tuple[str, ...] = ("defaultmroute.sh",) - startup: Tuple[str, ...] = ("sh defaultmroute.sh",) + startup: Tuple[str, ...] = ("bash defaultmroute.sh",) @classmethod def generate_config(cls, node: CoreNode, filename: str) -> str: @@ -103,7 +103,7 @@ class DefaultMulticastRouteService(UtilService): class StaticRouteService(UtilService): name: str = "StaticRoute" configs: Tuple[str, ...] = ("staticroute.sh",) - startup: Tuple[str, ...] = ("sh staticroute.sh",) + startup: Tuple[str, ...] = ("bash staticroute.sh",) custom_needed: bool = True @classmethod @@ -135,7 +135,7 @@ class SshService(UtilService): name: str = "SSH" configs: Tuple[str, ...] = ("startsshd.sh", "/etc/ssh/sshd_config") dirs: Tuple[str, ...] = ("/etc/ssh", "/var/run/sshd") - startup: Tuple[str, ...] = ("sh startsshd.sh",) + startup: Tuple[str, ...] = ("bash startsshd.sh",) shutdown: Tuple[str, ...] = ("killall sshd",) validation_mode: ServiceMode = ServiceMode.BLOCKING @@ -278,7 +278,7 @@ class DhcpClientService(UtilService): name: str = "DHCPClient" configs: Tuple[str, ...] = ("startdhcpclient.sh",) - startup: Tuple[str, ...] = ("sh startdhcpclient.sh",) + startup: Tuple[str, ...] = ("bash startdhcpclient.sh",) shutdown: Tuple[str, ...] = ("killall dhclient",) validate: Tuple[str, ...] = ("pidof dhclient",) @@ -561,8 +561,8 @@ class PcapService(UtilService): name: str = "pcap" configs: Tuple[str, ...] = ("pcap.sh",) - startup: Tuple[str, ...] = ("sh pcap.sh start",) - shutdown: Tuple[str, ...] = ("sh pcap.sh stop",) + startup: Tuple[str, ...] = ("bash pcap.sh start",) + shutdown: Tuple[str, ...] = ("bash pcap.sh stop",) validate: Tuple[str, ...] = ("pidof tcpdump",) meta: str = "logs network traffic to pcap packet capture files" @@ -671,7 +671,7 @@ class AtdService(UtilService): name: str = "atd" configs: Tuple[str, ...] = ("startatd.sh",) dirs: Tuple[str, ...] = ("/var/spool/cron/atjobs", "/var/spool/cron/atspool") - startup: Tuple[str, ...] = ("sh startatd.sh",) + startup: Tuple[str, ...] = ("bash startatd.sh",) shutdown: Tuple[str, ...] = ("pkill atd",) @classmethod diff --git a/tasks.py b/tasks.py index 5f52f444..c3e6d2bb 100644 --- a/tasks.py +++ b/tasks.py @@ -120,14 +120,14 @@ def install_system(c: Context, os_info: OsInfo, hide: bool) -> None: if os_info.like == OsLike.DEBIAN: c.run( "sudo apt install -y automake pkg-config gcc libev-dev ebtables " - "iproute2 ethtool tk python3-tk", + "iproute2 ethtool tk python3-tk bash", hide=hide ) elif os_info.like == OsLike.REDHAT: c.run( "sudo yum install -y automake pkgconf-pkg-config gcc gcc-c++ " "libev-devel iptables-ebtables iproute python3-devel python3-tkinter " - "tk ethtool make", + "tk ethtool make bash", hide=hide ) # centos 8+ does not support netem by default From 9e3e0e0326345b8efb07622f5f383728153ce7bd Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 28 Jul 2020 16:34:05 -0700 Subject: [PATCH 020/598] install: fixed issue identifying python versions to install dataclasses for, using ~ should account for any version up to 3.7 properly --- daemon/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/pyproject.toml b/daemon/pyproject.toml index b75f1ee3..7ce3a125 100644 --- a/daemon/pyproject.toml +++ b/daemon/pyproject.toml @@ -10,7 +10,7 @@ include = ["core/gui/data/**/*", "core/configservices/*/templates"] [tool.poetry.dependencies] python = "^3.6" -dataclasses = { version = "*", python = "3.6" } +dataclasses = { version = "*", python = "~3.6" } fabric = "*" grpcio = "1.27.2" invoke = "*" From 511a3037a8d0cc09b36e7d07849dbe08ea53fd18 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 28 Jul 2020 16:35:46 -0700 Subject: [PATCH 021/598] bumped versions for release --- configure.ac | 2 +- daemon/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 60f6709e..6ed18b69 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. # this defines the CORE version number, must be static for AC_INIT -AC_INIT(core, 7.0.0) +AC_INIT(core, 7.0.1) # autoconf and automake initialization AC_CONFIG_SRCDIR([netns/version.h.in]) diff --git a/daemon/pyproject.toml b/daemon/pyproject.toml index 7ce3a125..dec01670 100644 --- a/daemon/pyproject.toml +++ b/daemon/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "core" -version = "7.0.0" +version = "7.0.1" description = "CORE Common Open Research Emulator" authors = ["Boeing Research and Technology"] license = "BSD-2-Clause" From afe434f25cbf2e01a89fb75f4c914c514675a55e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 28 Jul 2020 16:39:16 -0700 Subject: [PATCH 022/598] updated changelog for bugfix release 7.0.1 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 375a7607..21eb0a57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2020-07-28 CORE 7.0.1 + +* Bugfixes + * \#500 - fixed issue running node commands with shell=True + * fixed issue for poetry based install not properly vetting requirements for dataclasses dependency + ## 2020-07-23 CORE 7.0.0 * Breaking Changes From 858e771efd8f040a18c7e222e4dd971536afe145 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 28 Jul 2020 21:49:34 -0700 Subject: [PATCH 023/598] pygui: fixes for copying links/asymmetric links, fixes for configuring asymmetric links, fixed issues adding nodes/links and editing links from gui due to not being able to identify same source changes --- daemon/core/gui/coreclient.py | 56 +++++++++++++++++++------ daemon/core/gui/dialogs/linkconfig.py | 37 ++++------------- daemon/core/gui/graph/graph.py | 59 ++++++++++++++++++--------- 3 files changed, 91 insertions(+), 61 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 6129031a..9fe8130a 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -474,21 +474,22 @@ class CoreClient: self.ifaces_manager.reset_mac() nodes = [x.to_proto() for x in self.session.nodes.values()] links = [] - for link in self.session.links: + asymmetric_links = [] + for edge in self.links.values(): + link = edge.link if link.iface1 and not link.iface1.mac: link.iface1.mac = self.ifaces_manager.next_mac() if link.iface2 and not link.iface2.mac: link.iface2.mac = self.ifaces_manager.next_mac() links.append(link.to_proto()) + if edge.asymmetric_link: + asymmetric_links.append(edge.asymmetric_link.to_proto()) wlan_configs = self.get_wlan_configs_proto() mobility_configs = self.get_mobility_configs_proto() emane_model_configs = self.get_emane_model_configs_proto() hooks = [x.to_proto() for x in self.session.hooks.values()] service_configs = self.get_service_configs_proto() file_configs = self.get_service_file_configs_proto() - asymmetric_links = [ - x.asymmetric_link for x in self.links.values() if x.asymmetric_link - ] config_service_configs = self.get_config_service_configs_proto() emane_config = to_dict(self.session.emane_config) result = False @@ -686,17 +687,32 @@ class CoreClient: for node in self.session.nodes.values(): response = self.client.add_node(self.session.id, node.to_proto()) logging.debug("created node: %s", response) - for link in self.session.links: - link_proto = link.to_proto() + asymmetric_links = [] + for edge in self.links.values(): + link = edge.link response = self.client.add_link( self.session.id, - link_proto.node1_id, - link_proto.node2_id, - link_proto.iface1, - link_proto.iface2, - link_proto.options, + link.node1_id, + link.node2_id, + link.iface1, + link.iface2, + link.options, + source=GUI_SOURCE, ) logging.debug("created link: %s", response) + if edge.asymmetric_link: + asymmetric_links.append(edge.asymmetric_link) + for link in asymmetric_links: + response = self.client.add_link( + self.session.id, + link.node1_id, + link.node2_id, + link.iface1, + link.iface2, + link.options, + source=GUI_SOURCE, + ) + logging.debug("created asymmetric link: %s", response) def send_data(self) -> None: """ @@ -892,8 +908,7 @@ class CoreClient: ) edge.set_link(link) self.links[edge.token] = edge - self.session.links.append(link) - logging.info("Add link between %s and %s", src_node.name, dst_node.name) + logging.info("added link between %s and %s", src_node.name, dst_node.name) def get_wlan_configs_proto(self) -> List[wlan_pb2.WlanConfig]: configs = [] @@ -1039,3 +1054,18 @@ class CoreClient: logging.info("execute python script %s", response) if response.session_id != -1: self.join_session(response.session_id) + + def edit_link(self, link: Link) -> None: + iface1_id = link.iface1.id if link.iface1 else None + iface2_id = link.iface2.id if link.iface2 else None + response = self.client.edit_link( + self.session.id, + link.node1_id, + link.node2_id, + link.options.to_proto(), + iface1_id, + iface2_id, + source=GUI_SOURCE, + ) + if not response.result: + logging.error("error editing link: %s", link) diff --git a/daemon/core/gui/dialogs/linkconfig.py b/daemon/core/gui/dialogs/linkconfig.py index 2a91da30..914bad1e 100644 --- a/daemon/core/gui/dialogs/linkconfig.py +++ b/daemon/core/gui/dialogs/linkconfig.py @@ -228,21 +228,15 @@ class LinkConfigurationDialog(Dialog): bandwidth=bandwidth, jitter=jitter, delay=delay, dup=duplicate, loss=loss ) link.options = options - - iface1_id = None - if link.iface1: - iface1_id = link.iface1.id - iface2_id = None - if link.iface2: - iface2_id = link.iface2.id - + iface1_id = link.iface1.id if link.iface1 else None + iface2_id = link.iface2.id if link.iface2 else None if not self.is_symmetric: link.options.unidirectional = True asym_iface1 = None - if iface1_id: + if iface1_id is not None: asym_iface1 = Interface(id=iface1_id) asym_iface2 = None - if iface2_id: + if iface2_id is not None: asym_iface2 = Interface(id=iface2_id) down_bandwidth = get_int(self.down_bandwidth) down_jitter = get_int(self.down_jitter) @@ -260,8 +254,8 @@ class LinkConfigurationDialog(Dialog): self.edge.asymmetric_link = Link( node1_id=link.node2_id, node2_id=link.node1_id, - iface1=asym_iface1, - iface2=asym_iface2, + iface1=asym_iface2, + iface2=asym_iface1, options=options, ) else: @@ -269,24 +263,9 @@ class LinkConfigurationDialog(Dialog): self.edge.asymmetric_link = None if self.app.core.is_runtime() and link.options: - session_id = self.app.core.session.id - self.app.core.client.edit_link( - session_id, - link.node1_id, - link.node2_id, - link.options, - iface1_id, - iface2_id, - ) + self.app.core.edit_link(link) if self.edge.asymmetric_link: - self.app.core.client.edit_link( - session_id, - link.node2_id, - link.node1_id, - self.edge.asymmetric_link.options, - iface1_id, - iface2_id, - ) + self.app.core.edit_link(self.edge.asymmetric_link) # update edge label self.edge.draw_link_options() diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 69ae87cc..bb762bb8 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -262,7 +262,7 @@ class CanvasGraph(tk.Canvas): edge = self.edges.get(token) if not edge: return - edge.link.options.CopyFrom(link.options) + edge.link.options = deepcopy(link.options) def add_wireless_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None: network_id = link.network_id if link.network_id else None @@ -924,7 +924,8 @@ class CanvasGraph(tk.Canvas): # maps original node canvas id to copy node canvas id copy_map = {} # the edges that will be copy over - to_copy_edges = [] + to_copy_edges = set() + to_copy_ids = {x.id for x in self.to_copy} for canvas_node in self.to_copy: core_node = canvas_node.core_node actual_x = core_node.position.x + 50 @@ -954,15 +955,39 @@ class CanvasGraph(tk.Canvas): self.nodes[node.id] = node self.core.set_canvas_node(copy, node) for edge in canvas_node.edges: - if edge.src not in self.to_copy or edge.dst not in self.to_copy: + if edge.src not in to_copy_ids or edge.dst not in to_copy_ids: if canvas_node.id == edge.src: dst_node = self.nodes[edge.dst] self.create_edge(node, dst_node) + token = create_edge_token(node.id, dst_node.id) elif canvas_node.id == edge.dst: src_node = self.nodes[edge.src] self.create_edge(src_node, node) + token = create_edge_token(src_node.id, node.id) + copy_edge = self.edges[token] + copy_link = copy_edge.link + iface1_id = copy_link.iface1.id if copy_link.iface1 else None + iface2_id = copy_link.iface2.id if copy_link.iface2 else None + options = edge.link.options + if options: + copy_edge.link.options = deepcopy(options) + if options and options.unidirectional: + asym_iface1 = None + if iface1_id is not None: + asym_iface1 = Interface(id=iface1_id) + asym_iface2 = None + if iface2_id is not None: + asym_iface2 = Interface(id=iface2_id) + copy_edge.asymmetric_link = Link( + node1_id=copy_link.node2_id, + node2_id=copy_link.node1_id, + iface1=asym_iface2, + iface2=asym_iface1, + options=deepcopy(edge.asymmetric_link.options), + ) + copy_edge.redraw() else: - to_copy_edges.append(edge) + to_copy_edges.add(edge) # copy link and link config for edge in to_copy_edges: @@ -974,30 +999,26 @@ class CanvasGraph(tk.Canvas): token = create_edge_token(src_node_copy.id, dst_node_copy.id) copy_edge = self.edges[token] copy_link = copy_edge.link + iface1_id = copy_link.iface1.id if copy_link.iface1 else None + iface2_id = copy_link.iface2.id if copy_link.iface2 else None options = edge.link.options - copy_link.options = deepcopy(options) - iface1_id = None - if copy_link.iface1: - iface1_id = copy_link.iface1.id - iface2_id = None - if copy_link.iface2: - iface2_id = copy_link.iface2.id - if not options.unidirectional: - copy_edge.asymmetric_link = None - else: + if options: + copy_link.options = deepcopy(options) + if options and options.unidirectional: asym_iface1 = None - if iface1_id: + if iface1_id is not None: asym_iface1 = Interface(id=iface1_id) asym_iface2 = None - if iface2_id: + if iface2_id is not None: asym_iface2 = Interface(id=iface2_id) copy_edge.asymmetric_link = Link( node1_id=copy_link.node2_id, node2_id=copy_link.node1_id, - iface1=asym_iface1, - iface2=asym_iface2, - options=edge.asymmetric_link.options, + iface1=asym_iface2, + iface2=asym_iface1, + options=deepcopy(edge.asymmetric_link.options), ) + copy_edge.redraw() self.itemconfig( copy_edge.id, width=self.itemcget(edge.id, "width"), From fe36d28522f31559aa27f913db4d4bb23d867fc6 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 28 Jul 2020 22:45:42 -0700 Subject: [PATCH 024/598] pygui: fixed issue with changes to protobuf files for getting emane model configs on a session --- daemon/core/api/grpc/grpcutils.py | 6 +++--- daemon/core/gui/coreclient.py | 2 ++ daemon/proto/core/api/grpc/core.proto | 2 +- daemon/proto/core/api/grpc/emane.proto | 9 ++++++++- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index a024c064..51be85fe 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -10,7 +10,7 @@ from core import utils from core.api.grpc import common_pb2, core_pb2 from core.api.grpc.common_pb2 import MappedConfig from core.api.grpc.configservices_pb2 import ConfigServiceConfig -from core.api.grpc.emane_pb2 import EmaneModelConfig +from core.api.grpc.emane_pb2 import GetEmaneModelConfig from core.api.grpc.services_pb2 import ( NodeServiceConfig, NodeServiceData, @@ -547,7 +547,7 @@ def get_nem_id( return nem_id -def get_emane_model_configs(session: Session) -> List[EmaneModelConfig]: +def get_emane_model_configs(session: Session) -> List[GetEmaneModelConfig]: configs = [] for _id in session.emane.node_configurations: if _id == -1: @@ -558,7 +558,7 @@ def get_emane_model_configs(session: Session) -> List[EmaneModelConfig]: current_config = session.emane.get_model_config(_id, model_name) config = get_config_options(current_config, model) node_id, iface_id = parse_emane_model_id(_id) - model_config = EmaneModelConfig( + model_config = GetEmaneModelConfig( node_id=node_id, model=model_name, iface_id=iface_id, config=config ) configs.append(model_config) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 9fe8130a..b30a265b 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -404,6 +404,8 @@ class CoreClient: self.app.show_grpc_exception("New Session Error", e) def delete_session(self, session_id: int = None) -> None: + if session_id is None and not self.session: + return if session_id is None: session_id = self.session.id try: diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 1b20257c..d5ffda59 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -720,7 +720,7 @@ message Session { repeated Hook hooks = 9; repeated string emane_models = 10; map emane_config = 11; - repeated emane.EmaneModelConfig emane_model_configs = 12; + repeated emane.GetEmaneModelConfig emane_model_configs = 12; map wlan_configs = 13; repeated services.NodeServiceConfig service_configs = 14; repeated configservices.ConfigServiceConfig config_service_configs = 15; diff --git a/daemon/proto/core/api/grpc/emane.proto b/daemon/proto/core/api/grpc/emane.proto index ce9a4297..ad6a22ca 100644 --- a/daemon/proto/core/api/grpc/emane.proto +++ b/daemon/proto/core/api/grpc/emane.proto @@ -53,8 +53,15 @@ message GetEmaneModelConfigsRequest { int32 session_id = 1; } +message GetEmaneModelConfig { + int32 node_id = 1; + string model = 2; + int32 iface_id = 3; + map config = 4; +} + message GetEmaneModelConfigsResponse { - repeated EmaneModelConfig configs = 1; + repeated GetEmaneModelConfig configs = 1; } message GetEmaneEventChannelRequest { From d30778b2382ec9eb480c5c0552c0ecbbabfd26a6 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 29 Jul 2020 16:55:42 -0700 Subject: [PATCH 025/598] daemon: fixed mobility checks to allow both wlan/emane, pygui: enabled emane nodes to configure mobility --- daemon/core/api/grpc/grpcutils.py | 14 ++++++ daemon/core/api/grpc/server.py | 6 ++- daemon/core/gui/coreclient.py | 4 +- daemon/core/gui/graph/node.py | 4 +- daemon/core/gui/nodeutils.py | 5 +++ daemon/core/location/mobility.py | 74 +++++++++++++++---------------- 6 files changed, 65 insertions(+), 42 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 51be85fe..8f666508 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -22,9 +22,11 @@ from core.emane.nodes import EmaneNet from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions from core.emulator.enumerations import LinkTypes, NodeTypes from core.emulator.session import Session +from core.errors import CoreError from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility from core.nodes.base import CoreNode, CoreNodeBase, NodeBase from core.nodes.interface import CoreInterface +from core.nodes.network import WlanNode from core.services.coreservices import CoreService WORKERS = 10 @@ -661,3 +663,15 @@ def get_node_config_service_configs(session: Session) -> List[ConfigServiceConfi def get_emane_config(session: Session) -> Dict[str, common_pb2.ConfigOption]: current_config = session.emane.get_configs() return get_config_options(current_config, session.emane.emane_config) + + +def get_mobility_node( + session: Session, node_id: int, context: ServicerContext +) -> Union[WlanNode, EmaneNet]: + try: + return session.get_node(node_id, WlanNode) + except CoreError: + try: + return session.get_node(node_id, EmaneNet) + except CoreError: + context.abort(grpc.StatusCode.NOT_FOUND, "node id is not for wlan or emane") diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index cd9cf714..81f4335e 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -1125,7 +1125,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("mobility action: %s", request) session = self.get_session(request.session_id, context) - node = self.get_node(session, request.node_id, context, WlanNode) + node = grpcutils.get_mobility_node(session, request.node_id, context) + if not node.mobility: + context.abort( + grpc.StatusCode.NOT_FOUND, f"node({node.name}) does not have mobility" + ) result = True if request.action == MobilityAction.START: node.mobility.start() diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index b30a265b..bb6c4f95 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -538,7 +538,7 @@ class CoreClient: def show_mobility_players(self) -> None: for node in self.session.nodes.values(): - if node.type != NodeType.WIRELESS_LAN: + if not NodeUtils.is_mobility(node): continue if node.mobility_config: mobility_player = MobilityPlayer(self.app, node) @@ -927,7 +927,7 @@ class CoreClient: def get_mobility_configs_proto(self) -> List[mobility_pb2.MobilityConfig]: configs = [] for node in self.session.nodes.values(): - if node.type != NodeType.WIRELESS_LAN: + if not NodeUtils.is_mobility(node): continue if not node.mobility_config: continue diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index ffc72fbf..100404ef 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -206,6 +206,7 @@ class CanvasNode: self.context.delete(0, tk.END) is_wlan = self.core_node.type == NodeType.WIRELESS_LAN is_emane = self.core_node.type == NodeType.EMANE + is_mobility = is_wlan or is_emane if self.app.core.is_runtime(): self.context.add_command(label="Configure", command=self.show_config) if is_emane: @@ -216,7 +217,7 @@ class CanvasNode: self.context.add_command( label="WLAN Config", command=self.show_wlan_config ) - if is_wlan and self.core_node.id in self.app.core.mobility_players: + if is_mobility and self.core_node.id in self.app.core.mobility_players: self.context.add_command( label="Mobility Player", command=self.show_mobility_player ) @@ -235,6 +236,7 @@ class CanvasNode: self.context.add_command( label="WLAN Config", command=self.show_wlan_config ) + if is_mobility: self.context.add_command( label="Mobility Config", command=self.show_mobility_config ) diff --git a/daemon/core/gui/nodeutils.py b/daemon/core/gui/nodeutils.py index 6c451303..8cba5bf0 100644 --- a/daemon/core/gui/nodeutils.py +++ b/daemon/core/gui/nodeutils.py @@ -63,10 +63,15 @@ class NodeUtils: WIRELESS_NODES: Set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE} RJ45_NODES: Set[NodeType] = {NodeType.RJ45} IGNORE_NODES: Set[NodeType] = {NodeType.CONTROL_NET} + MOBILITY_NODES: Set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE} NODE_MODELS: Set[str] = {"router", "host", "PC", "mdr", "prouter"} ROUTER_NODES: Set[str] = {"router", "mdr"} ANTENNA_ICON: PhotoImage = None + @classmethod + def is_mobility(cls, node: Node) -> bool: + return node.type in cls.MOBILITY_NODES + @classmethod def is_router_node(cls, node: Node) -> bool: return cls.is_model_node(node.type) and node.model in cls.ROUTER_NODES diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 0e9b2e32..ce422277 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -9,10 +9,11 @@ import threading import time from functools import total_ordering from pathlib import Path -from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple +from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple, Union from core import utils from core.config import ConfigGroup, ConfigurableOptions, Configuration, ModelManager +from core.emane.nodes import EmaneNet from core.emulator.data import EventData, LinkData, LinkOptions from core.emulator.enumerations import ( ConfigDataTypes, @@ -31,6 +32,13 @@ if TYPE_CHECKING: from core.emulator.session import Session +def get_mobility_node(session: "Session", node_id: int) -> Union[WlanNode, EmaneNet]: + try: + return session.get_node(node_id, WlanNode) + except CoreError: + return session.get_node(node_id, EmaneNet) + + class MobilityManager(ModelManager): """ Member of session class for handling configuration data for mobility and @@ -69,30 +77,25 @@ class MobilityManager(ModelManager): """ if node_ids is None: node_ids = self.nodes() - for node_id in node_ids: - logging.debug("checking mobility startup for node: %s", node_id) logging.debug( - "node mobility configurations: %s", self.get_all_configs(node_id) + "node(%s) mobility startup: %s", node_id, self.get_all_configs(node_id) ) - try: - node = self.session.get_node(node_id, WlanNode) + node = get_mobility_node(self.session, node_id) + # TODO: may be an issue if there are multiple mobility models + for model in self.models.values(): + config = self.get_configs(node_id, model.name) + if not config: + continue + self.set_model(node, model, config) + if node.mobility: + self.session.event_loop.add_event(0.0, node.mobility.startup) except CoreError: + logging.exception("mobility startup error") logging.warning( "skipping mobility configuration for unknown node: %s", node_id ) - continue - - for model_name in self.models: - config = self.get_configs(node_id, model_name) - if not config: - continue - model_class = self.models[model_name] - self.set_model(node, model_class, config) - - if node.mobility: - self.session.event_loop.add_event(0.0, node.mobility.startup) def handleevent(self, event_data: EventData) -> None: """ @@ -106,40 +109,35 @@ class MobilityManager(ModelManager): node_id = event_data.node name = event_data.name try: - node = self.session.get_node(node_id, WlanNode) + node = get_mobility_node(self.session, node_id) except CoreError: logging.exception( - "Ignoring event for model '%s', unknown node '%s'", name, node_id + "ignoring event for model(%s), unknown node(%s)", name, node_id ) return # name is e.g. "mobility:ns2script" models = name[9:].split(",") for model in models: - try: - cls = self.models[model] - except KeyError: - logging.warning("Ignoring event for unknown model '%s'", model) + cls = self.models.get(model) + if not cls: + logging.warning("ignoring event for unknown model '%s'", model) continue - if cls.config_type in [RegisterTlvs.WIRELESS, RegisterTlvs.MOBILITY]: model = node.mobility else: continue - if model is None: - logging.warning("Ignoring event, %s has no model", node.name) + logging.warning("ignoring event, %s has no model", node.name) continue - if cls.name != model.name: logging.warning( - "Ignoring event for %s wrong model %s,%s", + "ignoring event for %s wrong model %s,%s", node.name, cls.name, model.name, ) continue - if event_type in [EventTypes.STOP, EventTypes.RESTART]: model.stop(move_initial=True) if event_type in [EventTypes.START, EventTypes.RESTART]: @@ -162,11 +160,9 @@ class MobilityManager(ModelManager): event_type = EventTypes.START elif model.state == model.STATE_PAUSED: event_type = EventTypes.PAUSE - start_time = int(model.lasttime - model.timezero) end_time = int(model.endtime) data = f"start={start_time} end={end_time}" - event_data = EventData( node=model.id, event_type=event_type, @@ -174,7 +170,6 @@ class MobilityManager(ModelManager): data=data, time=str(time.monotonic()), ) - self.session.broadcast_event(event_data) def updatewlans( @@ -593,7 +588,7 @@ class WayPointMobility(WirelessModel): self.lasttime: Optional[float] = None self.endtime: Optional[int] = None self.timezero: float = 0.0 - self.wlan: WlanNode = session.get_node(_id, WlanNode) + self.net: Union[WlanNode, EmaneNet] = get_mobility_node(self.session, self.id) # these are really set in child class via confmatrix self.loop: bool = False self.refresh_ms: int = 50 @@ -601,6 +596,9 @@ class WayPointMobility(WirelessModel): # (ns-3 sets this to False as new waypoints may be added from trace) self.empty_queue_stop: bool = True + def startup(self): + raise NotImplementedError + def runround(self) -> None: """ Advance script time and move nodes. @@ -643,7 +641,7 @@ class WayPointMobility(WirelessModel): # only move interfaces attached to self.wlan, or all nodenum in script? moved = [] moved_ifaces = [] - for iface in self.wlan.get_ifaces(): + for iface in self.net.get_ifaces(): node = iface.node if self.movenode(node, dt): moved.append(node) @@ -723,7 +721,7 @@ class WayPointMobility(WirelessModel): """ moved = [] moved_ifaces = [] - for iface in self.wlan.get_ifaces(): + for iface in self.net.get_ifaces(): node = iface.node if node.id not in self.initial: continue @@ -1094,7 +1092,7 @@ class Ns2ScriptedMobility(WayPointMobility): :return: nothing """ if self.autostart == "": - logging.info("not auto-starting ns-2 script for %s", self.wlan.name) + logging.info("not auto-starting ns-2 script for %s", self.net.name) return try: t = float(self.autostart) @@ -1102,11 +1100,11 @@ class Ns2ScriptedMobility(WayPointMobility): logging.exception( "Invalid auto-start seconds specified '%s' for %s", self.autostart, - self.wlan.name, + self.net.name, ) return self.movenodesinitial() - logging.info("scheduling ns-2 script for %s autostart at %s", self.wlan.name, t) + logging.info("scheduling ns-2 script for %s autostart at %s", self.net.name, t) self.state = self.STATE_RUNNING self.session.event_loop.add_event(t, self.run) From 46f896925c67e5a4bc261270d35c7b82bab389a8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 29 Jul 2020 17:08:20 -0700 Subject: [PATCH 026/598] daemon: fixed mobility manager updates to support emane/wlan --- daemon/core/emane/emanemodel.py | 4 ++-- daemon/core/location/mobility.py | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 8672163d..0ee9aa40 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -130,8 +130,8 @@ class EmaneModel(WirelessModel): :return: nothing """ try: - wlan = self.session.get_node(self.id, EmaneNet) - wlan.setnempositions(moved_ifaces) + emane_net = self.session.get_node(self.id, EmaneNet) + emane_net.setnempositions(moved_ifaces) except CoreError: logging.exception("error during update") diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index ce422277..a548433c 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -100,7 +100,7 @@ class MobilityManager(ModelManager): def handleevent(self, event_data: EventData) -> None: """ Handle an Event Message used to start, stop, or pause - mobility scripts for a given WlanNode. + mobility scripts for a given mobility network. :param event_data: event data to handle :return: nothing @@ -172,12 +172,12 @@ class MobilityManager(ModelManager): ) self.session.broadcast_event(event_data) - def updatewlans( + def update_nets( self, moved: List[CoreNode], moved_ifaces: List[CoreInterface] ) -> None: """ A mobility script has caused nodes in the 'moved' list to move. - Update every WlanNode. This saves range calculations if the model + Update every mobility network. This saves range calculations if the model were to recalculate for each individual node movement. :param moved: moved nodes @@ -186,11 +186,11 @@ class MobilityManager(ModelManager): """ for node_id in self.nodes(): try: - node = self.session.get_node(node_id, WlanNode) + node = get_mobility_node(self.session, node_id) + if node.model: + node.model.update(moved, moved_ifaces) except CoreError: - continue - if node.model: - node.model.update(moved, moved_ifaces) + logging.exception("error updating mobility node") class WirelessModel(ConfigurableOptions): @@ -648,7 +648,7 @@ class WayPointMobility(WirelessModel): moved_ifaces.append(iface) # calculate all ranges after moving nodes; this saves calculations - self.session.mobility.updatewlans(moved, moved_ifaces) + self.session.mobility.update_nets(moved, moved_ifaces) # TODO: check session state self.session.event_loop.add_event(0.001 * self.refresh_ms, self.runround) @@ -729,7 +729,7 @@ class WayPointMobility(WirelessModel): self.setnodeposition(node, x, y, z) moved.append(node) moved_ifaces.append(iface) - self.session.mobility.updatewlans(moved, moved_ifaces) + self.session.mobility.update_nets(moved, moved_ifaces) def addwaypoint( self, From 63103ab25086ffd43e89fb56459f5ab4d429c40f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 31 Jul 2020 23:09:26 -0700 Subject: [PATCH 027/598] pygui: removed unused unlimited button from linkconfig dialog --- daemon/core/gui/dialogs/linkconfig.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/daemon/core/gui/dialogs/linkconfig.py b/daemon/core/gui/dialogs/linkconfig.py index 914bad1e..1aa2d7f8 100644 --- a/daemon/core/gui/dialogs/linkconfig.py +++ b/daemon/core/gui/dialogs/linkconfig.py @@ -77,10 +77,7 @@ class LinkConfigurationDialog(Dialog): frame = ttk.Frame(self.top) frame.columnconfigure(0, weight=1) - frame.columnconfigure(1, weight=1) frame.grid(row=1, column=0, sticky="ew", pady=PADY) - button = ttk.Button(frame, text="Unlimited") - button.grid(row=0, column=0, sticky="ew", padx=PADX) if self.is_symmetric: button = ttk.Button( frame, textvariable=self.symmetry_var, command=self.change_symmetry @@ -89,7 +86,7 @@ class LinkConfigurationDialog(Dialog): button = ttk.Button( frame, textvariable=self.symmetry_var, command=self.change_symmetry ) - button.grid(row=0, column=1, sticky="ew") + button.grid(sticky="ew") if self.is_symmetric: self.symmetric_frame = self.get_frame() From eb422f5bab665fcd0b592be6072a5c573766bdba Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 31 Jul 2020 23:13:07 -0700 Subject: [PATCH 028/598] pygui: mac editing disabled for nodes during runtime --- daemon/core/gui/dialogs/nodeconfig.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/daemon/core/gui/dialogs/nodeconfig.py b/daemon/core/gui/dialogs/nodeconfig.py index 33c8fb32..604e933a 100644 --- a/daemon/core/gui/dialogs/nodeconfig.py +++ b/daemon/core/gui/dialogs/nodeconfig.py @@ -232,8 +232,10 @@ class NodeConfigDialog(Dialog): label = ttk.Label(tab, text="MAC") label.grid(row=row, column=0, padx=PADX, pady=PADY) auto_set = not iface.mac - mac_state = tk.DISABLED if auto_set else tk.NORMAL is_auto = tk.BooleanVar(value=auto_set) + mac_state = tk.DISABLED if auto_set else tk.NORMAL + if state == tk.DISABLED: + mac_state = tk.DISABLED checkbutton = ttk.Checkbutton( tab, text="Auto?", variable=is_auto, state=state ) From e7a93e7fd6ef885cc6c47c61ccc76975eec11449 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 31 Jul 2020 23:18:11 -0700 Subject: [PATCH 029/598] pygui: config dialogs that allow selecting a file default to ~/.coregui --- daemon/core/gui/widgets.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/daemon/core/gui/widgets.py b/daemon/core/gui/widgets.py index 85f3da10..0d5bff22 100644 --- a/daemon/core/gui/widgets.py +++ b/daemon/core/gui/widgets.py @@ -5,7 +5,7 @@ from pathlib import Path from tkinter import filedialog, font, ttk from typing import TYPE_CHECKING, Any, Callable, Dict, Set, Type -from core.gui import themes, validation +from core.gui import appconfig, themes, validation from core.gui.dialogs.dialog import Dialog from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.wrappers import ConfigOption, ConfigOptionType @@ -26,7 +26,9 @@ INT_TYPES: Set[ConfigOptionType] = { def file_button_click(value: tk.StringVar, parent: tk.Widget) -> None: - file_path = filedialog.askopenfilename(title="Select File", parent=parent) + file_path = filedialog.askopenfilename( + title="Select File", initialdir=str(appconfig.HOME_PATH), parent=parent + ) if file_path: value.set(file_path) From 04f7bc561b7a59130c2cf080997f56583b0517bf Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 31 Jul 2020 23:23:18 -0700 Subject: [PATCH 030/598] pygui: fixed exception from bad check when double clicking in sessions dialog --- daemon/core/gui/dialogs/sessions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/core/gui/dialogs/sessions.py b/daemon/core/gui/dialogs/sessions.py index d41e2052..3f9f3c9b 100644 --- a/daemon/core/gui/dialogs/sessions.py +++ b/daemon/core/gui/dialogs/sessions.py @@ -190,7 +190,7 @@ class SessionsDialog(Dialog): def double_click_join(self, _event: tk.Event) -> None: item = self.tree.selection() - if item is None: + if not item: return session_id = int(self.tree.item(item, "text")) self.join_session(session_id) From fc44ad6fe84bb52ee35ea1c2fafc95fe6de90501 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 1 Aug 2020 11:00:26 -0700 Subject: [PATCH 031/598] pygui: update title to show xml file when one is opened, fixed issue creating nodes/links when not runtime due to refactoring, removed xml_file from coreclient and depend on the grpc GetSession wrapped data, grpc: added opened file information to GetSession call --- daemon/core/api/grpc/grpcutils.py | 2 +- daemon/core/api/grpc/server.py | 1 + daemon/core/gui/coreclient.py | 61 +++++++++++++++------------ daemon/core/gui/dialogs/sessions.py | 2 - daemon/core/gui/menubar.py | 24 +++-------- daemon/core/gui/wrappers.py | 4 ++ daemon/proto/core/api/grpc/core.proto | 1 + 7 files changed, 48 insertions(+), 47 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 8f666508..eaec2359 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -229,7 +229,7 @@ def get_config_options( """ results = {} for configuration in configurable_options.configurations(): - value = config[configuration.id] + value = config.get(configuration.id, configuration.default) config_option = common_pb2.ConfigOption( label=configuration.label, name=configuration.id, diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 81f4335e..55bdc802 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -597,6 +597,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): config_service_configs=config_service_configs, mobility_configs=mobility_configs, metadata=session.metadata, + file=session.file_name, ) return core_pb2.GetSessionResponse(session=session_proto) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index bb6c4f95..8a881945 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -22,7 +22,7 @@ from core.api.grpc import ( wlan_pb2, ) from core.gui import appconfig -from core.gui.appconfig import CoreServer, Observer +from core.gui.appconfig import XMLS_PATH, CoreServer, Observer from core.gui.dialogs.emaneinstall import EmaneInstallDialog from core.gui.dialogs.error import ErrorDialog from core.gui.dialogs.mobilityplayer import MobilityPlayer @@ -98,8 +98,6 @@ class CoreClient: self.handling_throughputs: Optional[grpc.Future] = None self.handling_cpu_usage: Optional[grpc.Future] = None self.handling_events: Optional[grpc.Future] = None - self.xml_dir: Optional[str] = None - self.xml_file: Optional[str] = None @property def client(self) -> client.CoreGrpcClient: @@ -311,7 +309,8 @@ class CoreClient: response = self.client.get_session(session_id) self.session = Session.from_proto(response.session) self.client.set_session_user(self.session.id, self.user) - self.master.title(f"CORE Session({self.session.id})") + title_file = self.session.file.name if self.session.file else "" + self.master.title(f"CORE Session({self.session.id}) {title_file}") self.handling_events = self.client.events( self.session.id, self.handle_events ) @@ -586,10 +585,18 @@ class CoreClient: except grpc.RpcError as e: self.app.show_grpc_exception("Node Terminal Error", e) - def save_xml(self, file_path: str) -> None: + def get_xml_dir(self) -> str: + return str(self.session.file.parent) if self.session.file else str(XMLS_PATH) + + def save_xml(self, file_path: str = None) -> None: """ Save core session as to an xml file """ + if not file_path and not self.session.file: + logging.error("trying to save xml for session with no file") + return + if not file_path: + file_path = str(self.session.file) try: if not self.is_runtime(): logging.debug("Send session data to the daemon") @@ -687,34 +694,17 @@ class CoreClient: """ self.client.set_session_state(self.session.id, SessionState.DEFINITION.value) for node in self.session.nodes.values(): - response = self.client.add_node(self.session.id, node.to_proto()) + response = self.client.add_node( + self.session.id, node.to_proto(), source=GUI_SOURCE + ) logging.debug("created node: %s", response) asymmetric_links = [] for edge in self.links.values(): - link = edge.link - response = self.client.add_link( - self.session.id, - link.node1_id, - link.node2_id, - link.iface1, - link.iface2, - link.options, - source=GUI_SOURCE, - ) - logging.debug("created link: %s", response) + self.add_link(edge.link) if edge.asymmetric_link: asymmetric_links.append(edge.asymmetric_link) for link in asymmetric_links: - response = self.client.add_link( - self.session.id, - link.node1_id, - link.node2_id, - link.iface1, - link.iface2, - link.options, - source=GUI_SOURCE, - ) - logging.debug("created asymmetric link: %s", response) + self.add_link(link) def send_data(self) -> None: """ @@ -1057,6 +1047,23 @@ class CoreClient: if response.session_id != -1: self.join_session(response.session_id) + def add_link(self, link: Link) -> None: + iface1 = link.iface1.to_proto() if link.iface1 else None + iface2 = link.iface2.to_proto() if link.iface2 else None + options = link.options.to_proto() if link.options else None + response = self.client.add_link( + self.session.id, + link.node1_id, + link.node2_id, + iface1, + iface2, + options, + source=GUI_SOURCE, + ) + logging.debug("added link: %s", response) + if not response.result: + logging.error("error adding link: %s", link) + def edit_link(self, link: Link) -> None: iface1_id = link.iface1.id if link.iface1 else None iface2_id = link.iface2.id if link.iface2 else None diff --git a/daemon/core/gui/dialogs/sessions.py b/daemon/core/gui/dialogs/sessions.py index 3f9f3c9b..83e4001a 100644 --- a/daemon/core/gui/dialogs/sessions.py +++ b/daemon/core/gui/dialogs/sessions.py @@ -181,8 +181,6 @@ class SessionsDialog(Dialog): def join_session(self, session_id: int) -> None: self.destroy() - if self.app.core.xml_file: - self.app.core.xml_file = None task = ProgressTask( self.app, "Join", self.app.core.join_session, args=(session_id,) ) diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index 3b85ac6f..fd1413b6 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -6,7 +6,6 @@ from functools import partial from tkinter import filedialog, messagebox from typing import TYPE_CHECKING, Optional -from core.gui.appconfig import XMLS_PATH from core.gui.coreclient import CoreClient from core.gui.dialogs.about import AboutDialog from core.gui.dialogs.canvassizeandscale import SizeAndScaleDialog @@ -265,16 +264,13 @@ class Menubar(tk.Menu): ) def click_save(self, _event=None) -> None: - xml_file = self.core.xml_file - if xml_file: - self.core.save_xml(xml_file) + if self.core.session.file: + self.core.save_xml() else: self.click_save_xml() def click_save_xml(self, _event: tk.Event = None) -> None: - init_dir = self.core.xml_dir - if not init_dir: - init_dir = str(XMLS_PATH) + init_dir = self.core.get_xml_dir() file_path = filedialog.asksaveasfilename( initialdir=init_dir, title="Save As", @@ -284,12 +280,9 @@ class Menubar(tk.Menu): if file_path: self.add_recent_file_to_gui_config(file_path) self.core.save_xml(file_path) - self.core.xml_file = file_path def click_open_xml(self, _event: tk.Event = None) -> None: - init_dir = self.core.xml_dir - if not init_dir: - init_dir = str(XMLS_PATH) + init_dir = self.core.get_xml_dir() file_path = filedialog.askopenfilename( initialdir=init_dir, title="Open", @@ -298,12 +291,10 @@ class Menubar(tk.Menu): if file_path: self.open_xml_task(file_path) - def open_xml_task(self, filename: str) -> None: - self.add_recent_file_to_gui_config(filename) - self.core.xml_file = filename - self.core.xml_dir = str(os.path.dirname(filename)) + def open_xml_task(self, file_path: str) -> None: + self.add_recent_file_to_gui_config(file_path) self.prompt_save_running_session() - task = ProgressTask(self.app, "Open XML", self.core.open_xml, args=(filename,)) + task = ProgressTask(self.app, "Open XML", self.core.open_xml, args=(file_path,)) task.start() def execute_python(self) -> None: @@ -357,7 +348,6 @@ class Menubar(tk.Menu): def click_new(self) -> None: self.prompt_save_running_session() self.core.create_new_session() - self.core.xml_file = None def click_find(self, _event: tk.Event = None) -> None: dialog = FindDialog(self.app) diff --git a/daemon/core/gui/wrappers.py b/daemon/core/gui/wrappers.py index d86e20dd..52384fe2 100644 --- a/daemon/core/gui/wrappers.py +++ b/daemon/core/gui/wrappers.py @@ -1,5 +1,6 @@ from dataclasses import dataclass, field from enum import Enum +from pathlib import Path from typing import Dict, List, Optional, Set, Tuple from core.api.grpc import common_pb2, configservices_pb2, core_pb2, services_pb2 @@ -581,6 +582,7 @@ class Session: emane_models: List[str] emane_config: Dict[str, ConfigOption] metadata: Dict[str, str] + file: Path @classmethod def from_proto(cls, proto: core_pb2.Session) -> "Session": @@ -616,6 +618,7 @@ class Session: for node_id, mapped_config in proto.mobility_configs.items(): node = nodes[node_id] node.mobility_config = ConfigOption.from_dict(mapped_config.config) + file_path = Path(proto.file) if proto.file else None return Session( id=proto.id, state=SessionState(proto.state), @@ -629,6 +632,7 @@ class Session: emane_models=list(proto.emane_models), emane_config=ConfigOption.from_dict(proto.emane_config), metadata=dict(proto.metadata), + file=file_path, ) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index d5ffda59..4727bbef 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -726,6 +726,7 @@ message Session { repeated configservices.ConfigServiceConfig config_service_configs = 15; map mobility_configs = 16; map metadata = 17; + string file = 18; } message SessionSummary { From 06563d59539cefc9da2bcb082816a8374cda5f74 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 1 Aug 2020 11:07:11 -0700 Subject: [PATCH 032/598] pygui: fixed issue editing hook with a new name --- daemon/core/gui/dialogs/hooks.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/daemon/core/gui/dialogs/hooks.py b/daemon/core/gui/dialogs/hooks.py index 31ef3e15..ce7caf29 100644 --- a/daemon/core/gui/dialogs/hooks.py +++ b/daemon/core/gui/dialogs/hooks.py @@ -149,13 +149,15 @@ class HooksDialog(Dialog): dialog.set(hook) dialog.show() session.hooks[hook.file] = hook + self.selected = hook.file self.listbox.delete(self.selected_index) self.listbox.insert(self.selected_index, hook.file) + self.listbox.select_set(self.selected_index) def click_delete(self) -> None: session = self.app.core.session del session.hooks[self.selected] - self.listbox.delete(tk.ANCHOR) + self.listbox.delete(self.selected_index) self.edit_button.config(state=tk.DISABLED) self.delete_button.config(state=tk.DISABLED) From 2aeb119b0455b3fee5b41f134db1cca5da481f5d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sun, 2 Aug 2020 10:03:21 -0700 Subject: [PATCH 033/598] pygui: changes to display both link and asym link options on edges in canvas --- daemon/core/gui/graph/edges.py | 46 ++++++++++++++++++++++------------ daemon/core/gui/utils.py | 12 +++++++++ 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index 610b6cc0..b313957d 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -8,7 +8,7 @@ from core.gui.dialogs.linkconfig import LinkConfigurationDialog from core.gui.frames.link import EdgeInfoFrame, WirelessEdgeInfoFrame from core.gui.graph import tags from core.gui.nodeutils import NodeUtils -from core.gui.utils import bandwidth_text +from core.gui.utils import bandwidth_text, delay_jitter_text from core.gui.wrappers import Interface, Link if TYPE_CHECKING: @@ -419,21 +419,35 @@ class CanvasEdge(Edge): if not self.link.options: return options = self.link.options + asym_options = None + if self.asymmetric_link and self.asymmetric_link.options: + asym_options = self.asymmetric_link.options lines = [] - bandwidth = options.bandwidth - if bandwidth > 0: - lines.append(bandwidth_text(bandwidth)) - delay = options.delay - jitter = options.jitter - if delay > 0 and jitter > 0: - lines.append(f"{delay} us (\u00B1{jitter} us)") - elif jitter > 0: - lines.append(f"0 us (\u00B1{jitter} us)") - loss = options.loss - if loss > 0: - lines.append(f"loss={loss}%") - dup = options.dup - if dup > 0: - lines.append(f"dup={dup}%") + # bandwidth + if options.bandwidth > 0: + bandwidth_line = bandwidth_text(options.bandwidth) + if asym_options and asym_options.bandwidth > 0: + bandwidth_line += f" / {bandwidth_text(asym_options.bandwidth)}" + lines.append(bandwidth_line) + # delay/jitter + dj_line = delay_jitter_text(options.delay, options.jitter) + if dj_line and asym_options: + asym_dj_line = delay_jitter_text(asym_options.delay, asym_options.jitter) + if asym_dj_line: + dj_line += f" / {asym_dj_line}" + if dj_line: + lines.append(dj_line) + # loss + if options.loss > 0: + loss_line = f"loss={options.loss}%" + if asym_options and asym_options.loss > 0: + loss_line += f" / loss={asym_options.loss}%" + lines.append(loss_line) + # duplicate + if options.dup > 0: + dup_line = f"dup={options.dup}%" + if asym_options and asym_options.dup > 0: + dup_line += f" / dup={asym_options.dup}%" + lines.append(dup_line) label = "\n".join(lines) self.middle_label_text(label) diff --git a/daemon/core/gui/utils.py b/daemon/core/gui/utils.py index ee5ad8cb..59171ae9 100644 --- a/daemon/core/gui/utils.py +++ b/daemon/core/gui/utils.py @@ -1,3 +1,6 @@ +from typing import Optional + + def bandwidth_text(bandwidth: int) -> str: size = {0: "bps", 1: "Kbps", 2: "Mbps", 3: "Gbps"} unit = 1000 @@ -8,3 +11,12 @@ def bandwidth_text(bandwidth: int) -> str: if i == 3: break return f"{bandwidth} {size[i]}" + + +def delay_jitter_text(delay: int, jitter: int) -> Optional[str]: + line = None + if delay > 0 and jitter > 0: + line = f"{delay} us (\u00B1{jitter} us)" + elif jitter > 0: + line = f"0 us (\u00B1{jitter} us)" + return line From f0bc3bbc998b6437fc077529de03181663b0c917 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sun, 2 Aug 2020 10:36:14 -0700 Subject: [PATCH 034/598] pygui: updates to leverage tk provided constants for sticky configuration, instead of duplicate strings everywhere --- daemon/core/gui/app.py | 20 ++-- daemon/core/gui/dialogs/about.py | 4 +- daemon/core/gui/dialogs/alerts.py | 16 ++-- daemon/core/gui/dialogs/canvassizeandscale.py | 72 +++++++------- daemon/core/gui/dialogs/canvaswallpaper.py | 28 +++--- daemon/core/gui/dialogs/colorpicker.py | 36 +++---- .../core/gui/dialogs/configserviceconfig.py | 66 ++++++------- daemon/core/gui/dialogs/copyserviceconfig.py | 16 ++-- daemon/core/gui/dialogs/customnodes.py | 46 ++++----- daemon/core/gui/dialogs/dialog.py | 4 +- daemon/core/gui/dialogs/emaneconfig.py | 36 +++---- daemon/core/gui/dialogs/emaneinstall.py | 7 +- daemon/core/gui/dialogs/executepython.py | 16 ++-- daemon/core/gui/dialogs/find.py | 18 ++-- daemon/core/gui/dialogs/hooks.py | 28 +++--- daemon/core/gui/dialogs/ipdialog.py | 26 ++--- daemon/core/gui/dialogs/linkconfig.py | 56 +++++------ daemon/core/gui/dialogs/macdialog.py | 10 +- daemon/core/gui/dialogs/mobilityconfig.py | 9 +- daemon/core/gui/dialogs/mobilityplayer.py | 12 +-- daemon/core/gui/dialogs/nodeconfig.py | 44 ++++----- daemon/core/gui/dialogs/nodeconfigservice.py | 20 ++-- daemon/core/gui/dialogs/nodeservice.py | 20 ++-- daemon/core/gui/dialogs/observers.py | 28 +++--- daemon/core/gui/dialogs/preferences.py | 30 +++--- daemon/core/gui/dialogs/runtool.py | 26 ++--- daemon/core/gui/dialogs/servers.py | 28 +++--- daemon/core/gui/dialogs/serviceconfig.py | 94 +++++++++---------- daemon/core/gui/dialogs/sessionoptions.py | 8 +- daemon/core/gui/dialogs/sessions.py | 18 ++-- daemon/core/gui/dialogs/shapemod.py | 48 +++++----- daemon/core/gui/dialogs/throughput.py | 32 +++---- daemon/core/gui/dialogs/wlanconfig.py | 9 +- daemon/core/gui/frames/link.py | 4 +- daemon/core/gui/frames/node.py | 3 +- daemon/core/gui/statusbar.py | 10 +- daemon/core/gui/task.py | 3 +- daemon/core/gui/toolbar.py | 14 +-- daemon/core/gui/tooltip.py | 2 +- daemon/core/gui/widgets.py | 30 +++--- 40 files changed, 501 insertions(+), 496 deletions(-) diff --git a/daemon/core/gui/app.py b/daemon/core/gui/app.py index 176b31e3..be744bb4 100644 --- a/daemon/core/gui/app.py +++ b/daemon/core/gui/app.py @@ -111,13 +111,13 @@ class Application(ttk.Frame): self.master.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) self.columnconfigure(1, weight=1) - self.grid(sticky="nsew") + self.grid(sticky=tk.NSEW) self.toolbar = Toolbar(self) - self.toolbar.grid(sticky="ns") + self.toolbar.grid(sticky=tk.NS) self.right_frame = ttk.Frame(self) self.right_frame.columnconfigure(0, weight=1) self.right_frame.rowconfigure(0, weight=1) - self.right_frame.grid(row=0, column=1, sticky="nsew") + self.right_frame.grid(row=0, column=1, sticky=tk.NSEW) self.draw_canvas() self.draw_infobar() self.draw_status() @@ -139,21 +139,21 @@ class Application(ttk.Frame): canvas_frame = ttk.Frame(self.right_frame) canvas_frame.rowconfigure(0, weight=1) canvas_frame.columnconfigure(0, weight=1) - canvas_frame.grid(row=0, column=0, sticky="nsew", pady=1) + canvas_frame.grid(row=0, column=0, sticky=tk.NSEW, pady=1) self.canvas = CanvasGraph(canvas_frame, self, self.core) - self.canvas.grid(sticky="nsew") + self.canvas.grid(sticky=tk.NSEW) scroll_y = ttk.Scrollbar(canvas_frame, command=self.canvas.yview) - scroll_y.grid(row=0, column=1, sticky="ns") + scroll_y.grid(row=0, column=1, sticky=tk.NS) scroll_x = ttk.Scrollbar( canvas_frame, orient=tk.HORIZONTAL, command=self.canvas.xview ) - scroll_x.grid(row=1, column=0, sticky="ew") + scroll_x.grid(row=1, column=0, sticky=tk.EW) self.canvas.configure(xscrollcommand=scroll_x.set) self.canvas.configure(yscrollcommand=scroll_y.set) def draw_status(self) -> None: self.statusbar = StatusBar(self.right_frame, self) - self.statusbar.grid(sticky="ew", columnspan=2) + self.statusbar.grid(sticky=tk.EW, columnspan=2) def display_info(self, frame_class: Type[InfoFrameBase], **kwargs: Any) -> None: if not self.show_infobar.get(): @@ -161,7 +161,7 @@ class Application(ttk.Frame): self.clear_info() self.info_frame = frame_class(self.infobar, **kwargs) self.info_frame.draw() - self.info_frame.grid(sticky="nsew") + self.info_frame.grid(sticky=tk.NSEW) def clear_info(self) -> None: if self.info_frame: @@ -174,7 +174,7 @@ class Application(ttk.Frame): def show_info(self) -> None: self.default_info() - self.infobar.grid(row=0, column=1, sticky="nsew") + self.infobar.grid(row=0, column=1, sticky=tk.NSEW) def hide_info(self) -> None: self.infobar.grid_forget() diff --git a/daemon/core/gui/dialogs/about.py b/daemon/core/gui/dialogs/about.py index fa96e218..c932807d 100644 --- a/daemon/core/gui/dialogs/about.py +++ b/daemon/core/gui/dialogs/about.py @@ -46,9 +46,9 @@ class AboutDialog(Dialog): codetext = CodeText(self.top) codetext.text.insert("1.0", LICENSE) codetext.text.config(state=tk.DISABLED) - codetext.grid(sticky="nsew") + codetext.grid(sticky=tk.NSEW) label = ttk.Label( self.top, text="Icons from https://icons8.com", anchor=tk.CENTER ) - label.grid(sticky="ew") + label.grid(sticky=tk.EW) diff --git a/daemon/core/gui/dialogs/alerts.py b/daemon/core/gui/dialogs/alerts.py index fd6d342e..a0193727 100644 --- a/daemon/core/gui/dialogs/alerts.py +++ b/daemon/core/gui/dialogs/alerts.py @@ -30,13 +30,13 @@ class AlertsDialog(Dialog): frame = ttk.Frame(self.top) frame.columnconfigure(0, weight=1) frame.rowconfigure(0, weight=1) - frame.grid(sticky="nsew", pady=PADY) + frame.grid(sticky=tk.NSEW, pady=PADY) self.tree = ttk.Treeview( frame, columns=("time", "level", "session_id", "node", "source"), show="headings", ) - self.tree.grid(row=0, column=0, sticky="nsew") + self.tree.grid(row=0, column=0, sticky=tk.NSEW) self.tree.column("time", stretch=tk.YES) self.tree.heading("time", text="Time") self.tree.column("level", stretch=tk.YES, width=100) @@ -77,25 +77,25 @@ class AlertsDialog(Dialog): self.tree.tag_configure(notice_name, background="#85e085") yscrollbar = ttk.Scrollbar(frame, orient="vertical", command=self.tree.yview) - yscrollbar.grid(row=0, column=1, sticky="ns") + yscrollbar.grid(row=0, column=1, sticky=tk.NS) self.tree.configure(yscrollcommand=yscrollbar.set) xscrollbar = ttk.Scrollbar(frame, orient="horizontal", command=self.tree.xview) - xscrollbar.grid(row=1, sticky="ew") + xscrollbar.grid(row=1, sticky=tk.EW) self.tree.configure(xscrollcommand=xscrollbar.set) self.codetext = CodeText(self.top) self.codetext.text.config(state=tk.DISABLED, height=11) - self.codetext.grid(sticky="nsew", pady=PADY) + self.codetext.grid(sticky=tk.NSEW, pady=PADY) frame = ttk.Frame(self.top) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) button = ttk.Button(frame, text="Reset", command=self.reset_alerts) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Close", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def reset_alerts(self) -> None: self.codetext.text.config(state=tk.NORMAL) diff --git a/daemon/core/gui/dialogs/canvassizeandscale.py b/daemon/core/gui/dialogs/canvassizeandscale.py index e8ad6693..e50bf986 100644 --- a/daemon/core/gui/dialogs/canvassizeandscale.py +++ b/daemon/core/gui/dialogs/canvassizeandscale.py @@ -23,7 +23,7 @@ class SizeAndScaleDialog(Dialog): """ super().__init__(app, "Canvas Size and Scale") self.canvas: CanvasGraph = self.app.canvas - self.section_font: font.Font = font.Font(weight="bold") + self.section_font: font.Font = font.Font(weight=font.BOLD) width, height = self.canvas.current_dimensions self.pixel_width: tk.IntVar = tk.IntVar(value=width) self.pixel_height: tk.IntVar = tk.IntVar(value=height) @@ -54,68 +54,68 @@ class SizeAndScaleDialog(Dialog): def draw_size(self) -> None: label_frame = ttk.Labelframe(self.top, text="Size", padding=FRAME_PAD) - label_frame.grid(sticky="ew") + label_frame.grid(sticky=tk.EW) label_frame.columnconfigure(0, weight=1) # draw size row 1 frame = ttk.Frame(label_frame) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) frame.columnconfigure(1, weight=1) frame.columnconfigure(3, weight=1) label = ttk.Label(frame, text="Width") - label.grid(row=0, column=0, sticky="w", padx=PADX) + label.grid(row=0, column=0, sticky=tk.W, padx=PADX) entry = validation.PositiveIntEntry(frame, textvariable=self.pixel_width) - entry.grid(row=0, column=1, sticky="ew", padx=PADX) + entry.grid(row=0, column=1, sticky=tk.EW, padx=PADX) entry.bind("", self.size_scale_keyup) label = ttk.Label(frame, text="x Height") - label.grid(row=0, column=2, sticky="w", padx=PADX) + label.grid(row=0, column=2, sticky=tk.W, padx=PADX) entry = validation.PositiveIntEntry(frame, textvariable=self.pixel_height) - entry.grid(row=0, column=3, sticky="ew", padx=PADX) + entry.grid(row=0, column=3, sticky=tk.EW, padx=PADX) entry.bind("", self.size_scale_keyup) label = ttk.Label(frame, text="Pixels") - label.grid(row=0, column=4, sticky="w") + label.grid(row=0, column=4, sticky=tk.W) # draw size row 2 frame = ttk.Frame(label_frame) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) frame.columnconfigure(1, weight=1) frame.columnconfigure(3, weight=1) label = ttk.Label(frame, text="Width") - label.grid(row=0, column=0, sticky="w", padx=PADX) + label.grid(row=0, column=0, sticky=tk.W, padx=PADX) entry = validation.PositiveFloatEntry( frame, textvariable=self.meters_width, state=tk.DISABLED ) - entry.grid(row=0, column=1, sticky="ew", padx=PADX) + entry.grid(row=0, column=1, sticky=tk.EW, padx=PADX) label = ttk.Label(frame, text="x Height") - label.grid(row=0, column=2, sticky="w", padx=PADX) + label.grid(row=0, column=2, sticky=tk.W, padx=PADX) entry = validation.PositiveFloatEntry( frame, textvariable=self.meters_height, state=tk.DISABLED ) - entry.grid(row=0, column=3, sticky="ew", padx=PADX) + entry.grid(row=0, column=3, sticky=tk.EW, padx=PADX) label = ttk.Label(frame, text="Meters") - label.grid(row=0, column=4, sticky="w") + label.grid(row=0, column=4, sticky=tk.W) def draw_scale(self) -> None: label_frame = ttk.Labelframe(self.top, text="Scale", padding=FRAME_PAD) - label_frame.grid(sticky="ew") + label_frame.grid(sticky=tk.EW) label_frame.columnconfigure(0, weight=1) frame = ttk.Frame(label_frame) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) frame.columnconfigure(1, weight=1) label = ttk.Label(frame, text=f"{PIXEL_SCALE} Pixels =") - label.grid(row=0, column=0, sticky="w", padx=PADX) + label.grid(row=0, column=0, sticky=tk.W, padx=PADX) entry = validation.PositiveFloatEntry(frame, textvariable=self.scale) - entry.grid(row=0, column=1, sticky="ew", padx=PADX) + entry.grid(row=0, column=1, sticky=tk.EW, padx=PADX) entry.bind("", self.size_scale_keyup) label = ttk.Label(frame, text="Meters") - label.grid(row=0, column=2, sticky="w") + label.grid(row=0, column=2, sticky=tk.W) def draw_reference_point(self) -> None: label_frame = ttk.Labelframe( self.top, text="Reference Point", padding=FRAME_PAD ) - label_frame.grid(sticky="ew") + label_frame.grid(sticky=tk.EW) label_frame.columnconfigure(0, weight=1) label = ttk.Label( @@ -124,61 +124,61 @@ class SizeAndScaleDialog(Dialog): label.grid() frame = ttk.Frame(label_frame) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) frame.columnconfigure(1, weight=1) frame.columnconfigure(3, weight=1) label = ttk.Label(frame, text="X") - label.grid(row=0, column=0, sticky="w", padx=PADX) + label.grid(row=0, column=0, sticky=tk.W, padx=PADX) entry = validation.PositiveFloatEntry(frame, textvariable=self.x) - entry.grid(row=0, column=1, sticky="ew", padx=PADX) + entry.grid(row=0, column=1, sticky=tk.EW, padx=PADX) label = ttk.Label(frame, text="Y") - label.grid(row=0, column=2, sticky="w", padx=PADX) + label.grid(row=0, column=2, sticky=tk.W, padx=PADX) entry = validation.PositiveFloatEntry(frame, textvariable=self.y) - entry.grid(row=0, column=3, sticky="ew", padx=PADX) + entry.grid(row=0, column=3, sticky=tk.EW, padx=PADX) label = ttk.Label(label_frame, text="Translates To") label.grid() frame = ttk.Frame(label_frame) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) frame.columnconfigure(1, weight=1) frame.columnconfigure(3, weight=1) frame.columnconfigure(5, weight=1) label = ttk.Label(frame, text="Lat") - label.grid(row=0, column=0, sticky="w", padx=PADX) + label.grid(row=0, column=0, sticky=tk.W, padx=PADX) entry = validation.FloatEntry(frame, textvariable=self.lat) - entry.grid(row=0, column=1, sticky="ew", padx=PADX) + entry.grid(row=0, column=1, sticky=tk.EW, padx=PADX) label = ttk.Label(frame, text="Lon") - label.grid(row=0, column=2, sticky="w", padx=PADX) + label.grid(row=0, column=2, sticky=tk.W, padx=PADX) entry = validation.FloatEntry(frame, textvariable=self.lon) - entry.grid(row=0, column=3, sticky="ew", padx=PADX) + entry.grid(row=0, column=3, sticky=tk.EW, padx=PADX) label = ttk.Label(frame, text="Alt") - label.grid(row=0, column=4, sticky="w", padx=PADX) + label.grid(row=0, column=4, sticky=tk.W, padx=PADX) entry = validation.FloatEntry(frame, textvariable=self.alt) - entry.grid(row=0, column=5, sticky="ew") + entry.grid(row=0, column=5, sticky=tk.EW) def draw_save_as_default(self) -> None: button = ttk.Checkbutton( self.top, text="Save as default?", variable=self.save_default ) - button.grid(sticky="w", pady=PADY) + button.grid(sticky=tk.W, pady=PADY) def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) button = ttk.Button(frame, text="Apply", command=self.click_apply) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def size_scale_keyup(self, _event: tk.Event) -> None: scale = self.scale.get() diff --git a/daemon/core/gui/dialogs/canvaswallpaper.py b/daemon/core/gui/dialogs/canvaswallpaper.py index 8a1e71d8..629f9f36 100644 --- a/daemon/core/gui/dialogs/canvaswallpaper.py +++ b/daemon/core/gui/dialogs/canvaswallpaper.py @@ -51,7 +51,7 @@ class CanvasWallpaperDialog(Dialog): def draw_image_label(self) -> None: label = ttk.Label(self.top, text="Image filename: ") - label.grid(sticky="ew") + label.grid(sticky=tk.EW) if self.filename.get(): self.draw_preview() @@ -60,17 +60,17 @@ class CanvasWallpaperDialog(Dialog): frame.columnconfigure(0, weight=2) frame.columnconfigure(1, weight=1) frame.columnconfigure(2, weight=1) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW, pady=PADY) entry = ttk.Entry(frame, textvariable=self.filename) entry.focus() - entry.grid(row=0, column=0, sticky="ew", padx=PADX) + entry.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="...", command=self.click_open_image) - button.grid(row=0, column=1, sticky="ew", padx=PADX) + button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Clear", command=self.click_clear) - button.grid(row=0, column=2, sticky="ew") + button.grid(row=0, column=2, sticky=tk.EW) def draw_options(self) -> None: frame = ttk.Frame(self.top) @@ -78,30 +78,30 @@ class CanvasWallpaperDialog(Dialog): frame.columnconfigure(1, weight=1) frame.columnconfigure(2, weight=1) frame.columnconfigure(3, weight=1) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW, pady=PADY) button = ttk.Radiobutton( frame, text="upper-left", value=1, variable=self.scale_option ) - button.grid(row=0, column=0, sticky="ew") + button.grid(row=0, column=0, sticky=tk.EW) self.options.append(button) button = ttk.Radiobutton( frame, text="centered", value=2, variable=self.scale_option ) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) self.options.append(button) button = ttk.Radiobutton( frame, text="scaled", value=3, variable=self.scale_option ) - button.grid(row=0, column=2, sticky="ew") + button.grid(row=0, column=2, sticky=tk.EW) self.options.append(button) button = ttk.Radiobutton( frame, text="titled", value=4, variable=self.scale_option ) - button.grid(row=0, column=3, sticky="ew") + button.grid(row=0, column=3, sticky=tk.EW) self.options.append(button) def draw_additional_options(self) -> None: @@ -111,19 +111,19 @@ class CanvasWallpaperDialog(Dialog): variable=self.adjust_to_dim, command=self.click_adjust_canvas, ) - checkbutton.grid(sticky="ew", padx=PADX) + checkbutton.grid(sticky=tk.EW, padx=PADX, pady=PADY) def draw_buttons(self) -> None: frame = ttk.Frame(self.top) - frame.grid(pady=PADY, sticky="ew") + frame.grid(sticky=tk.EW) frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) button = ttk.Button(frame, text="Apply", command=self.click_apply) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def click_open_image(self) -> None: filename = image_chooser(self, BACKGROUNDS_PATH) diff --git a/daemon/core/gui/dialogs/colorpicker.py b/daemon/core/gui/dialogs/colorpicker.py index 908b8acb..a2f131d4 100644 --- a/daemon/core/gui/dialogs/colorpicker.py +++ b/daemon/core/gui/dialogs/colorpicker.py @@ -48,13 +48,13 @@ class ColorPickerDialog(Dialog): # rgb frames frame = ttk.Frame(self.top) - frame.grid(row=0, column=0, sticky="ew", pady=PADY) + frame.grid(row=0, column=0, sticky=tk.EW, pady=PADY) frame.columnconfigure(2, weight=3) frame.columnconfigure(3, weight=1) label = ttk.Label(frame, text="R") label.grid(row=0, column=0, padx=PADX) self.red_entry = validation.RgbEntry(frame, width=3, textvariable=self.red) - self.red_entry.grid(row=0, column=1, sticky="ew", padx=PADX) + self.red_entry.grid(row=0, column=1, sticky=tk.EW, padx=PADX) scale = ttk.Scale( frame, from_=0, @@ -64,20 +64,20 @@ class ColorPickerDialog(Dialog): variable=self.red_scale, command=lambda x: self.scale_callback(self.red_scale, self.red), ) - scale.grid(row=0, column=2, sticky="ew", padx=PADX) + scale.grid(row=0, column=2, sticky=tk.EW, padx=PADX) self.red_label = ttk.Label( frame, background="#%02x%02x%02x" % (self.red.get(), 0, 0), width=5 ) - self.red_label.grid(row=0, column=3, sticky="ew") + self.red_label.grid(row=0, column=3, sticky=tk.EW) frame = ttk.Frame(self.top) - frame.grid(row=1, column=0, sticky="ew", pady=PADY) + frame.grid(row=1, column=0, sticky=tk.EW, pady=PADY) frame.columnconfigure(2, weight=3) frame.columnconfigure(3, weight=1) label = ttk.Label(frame, text="G") label.grid(row=0, column=0, padx=PADX) self.green_entry = validation.RgbEntry(frame, width=3, textvariable=self.green) - self.green_entry.grid(row=0, column=1, sticky="ew", padx=PADX) + self.green_entry.grid(row=0, column=1, sticky=tk.EW, padx=PADX) scale = ttk.Scale( frame, from_=0, @@ -87,20 +87,20 @@ class ColorPickerDialog(Dialog): variable=self.green_scale, command=lambda x: self.scale_callback(self.green_scale, self.green), ) - scale.grid(row=0, column=2, sticky="ew", padx=PADX) + scale.grid(row=0, column=2, sticky=tk.EW, padx=PADX) self.green_label = ttk.Label( frame, background="#%02x%02x%02x" % (0, self.green.get(), 0), width=5 ) - self.green_label.grid(row=0, column=3, sticky="ew") + self.green_label.grid(row=0, column=3, sticky=tk.EW) frame = ttk.Frame(self.top) - frame.grid(row=2, column=0, sticky="ew", pady=PADY) + frame.grid(row=2, column=0, sticky=tk.EW, pady=PADY) frame.columnconfigure(2, weight=3) frame.columnconfigure(3, weight=1) label = ttk.Label(frame, text="B") label.grid(row=0, column=0, padx=PADX) self.blue_entry = validation.RgbEntry(frame, width=3, textvariable=self.blue) - self.blue_entry.grid(row=0, column=1, sticky="ew", padx=PADX) + self.blue_entry.grid(row=0, column=1, sticky=tk.EW, padx=PADX) scale = ttk.Scale( frame, from_=0, @@ -110,31 +110,31 @@ class ColorPickerDialog(Dialog): variable=self.blue_scale, command=lambda x: self.scale_callback(self.blue_scale, self.blue), ) - scale.grid(row=0, column=2, sticky="ew", padx=PADX) + scale.grid(row=0, column=2, sticky=tk.EW, padx=PADX) self.blue_label = ttk.Label( frame, background="#%02x%02x%02x" % (0, 0, self.blue.get()), width=5 ) - self.blue_label.grid(row=0, column=3, sticky="ew") + self.blue_label.grid(row=0, column=3, sticky=tk.EW) # hex code and color display frame = ttk.Frame(self.top) frame.columnconfigure(0, weight=1) frame.rowconfigure(1, weight=1) self.hex_entry = validation.HexEntry(frame, textvariable=self.hex) - self.hex_entry.grid(sticky="ew", pady=PADY) + self.hex_entry.grid(sticky=tk.EW, pady=PADY) self.display = tk.Frame(frame, background=self.color, width=100, height=100) - self.display.grid(sticky="nsew") - frame.grid(row=3, column=0, sticky="nsew", pady=PADY) + self.display.grid(sticky=tk.NSEW) + frame.grid(row=3, column=0, sticky=tk.NSEW, pady=PADY) # button frame frame = ttk.Frame(self.top) - frame.grid(row=4, column=0, sticky="ew") + frame.grid(row=4, column=0, sticky=tk.EW) frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) button = ttk.Button(frame, text="OK", command=self.button_ok) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def set_bindings(self) -> None: self.red_entry.bind("", lambda x: self.current_focus("rgb")) diff --git a/daemon/core/gui/dialogs/configserviceconfig.py b/daemon/core/gui/dialogs/configserviceconfig.py index f778cf15..a085afd1 100644 --- a/daemon/core/gui/dialogs/configserviceconfig.py +++ b/daemon/core/gui/dialogs/configserviceconfig.py @@ -111,7 +111,7 @@ class ConfigServiceConfigDialog(Dialog): # draw notebook self.notebook = ttk.Notebook(self.top) - self.notebook.grid(sticky="nsew", pady=PADY) + self.notebook.grid(sticky=tk.NSEW, pady=PADY) self.draw_tab_files() if self.config: self.draw_tab_config() @@ -121,7 +121,7 @@ class ConfigServiceConfigDialog(Dialog): def draw_tab_files(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) - tab.grid(sticky="nsew") + tab.grid(sticky=tk.NSEW) tab.columnconfigure(0, weight=1) self.notebook.add(tab, text="Directories/Files") @@ -131,29 +131,29 @@ class ConfigServiceConfigDialog(Dialog): label.grid(pady=PADY) frame = ttk.Frame(tab) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) frame.columnconfigure(1, weight=1) label = ttk.Label(frame, text="Directories") - label.grid(row=0, column=0, sticky="w", padx=PADX) + label.grid(row=0, column=0, sticky=tk.W, padx=PADX) directories_combobox = ttk.Combobox( frame, values=self.directories, state="readonly" ) - directories_combobox.grid(row=0, column=1, sticky="ew", pady=PADY) + directories_combobox.grid(row=0, column=1, sticky=tk.EW, pady=PADY) if self.directories: directories_combobox.current(0) label = ttk.Label(frame, text="Templates") - label.grid(row=1, column=0, sticky="w", padx=PADX) + label.grid(row=1, column=0, sticky=tk.W, padx=PADX) self.templates_combobox = ttk.Combobox( frame, values=self.templates, state="readonly" ) self.templates_combobox.bind( "<>", self.handle_template_changed ) - self.templates_combobox.grid(row=1, column=1, sticky="ew", pady=PADY) + self.templates_combobox.grid(row=1, column=1, sticky=tk.EW, pady=PADY) self.template_text = CodeText(tab) - self.template_text.grid(sticky="nsew") + self.template_text.grid(sticky=tk.NSEW) tab.rowconfigure(self.template_text.grid_info()["row"], weight=1) if self.templates: self.templates_combobox.current(0) @@ -165,13 +165,13 @@ class ConfigServiceConfigDialog(Dialog): def draw_tab_config(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) - tab.grid(sticky="nsew") + tab.grid(sticky=tk.NSEW) tab.columnconfigure(0, weight=1) self.notebook.add(tab, text="Configuration") if self.modes: frame = ttk.Frame(tab) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) frame.columnconfigure(1, weight=1) label = ttk.Label(frame, text="Modes") label.grid(row=0, column=0, padx=PADX) @@ -179,17 +179,17 @@ class ConfigServiceConfigDialog(Dialog): frame, values=self.modes, state="readonly" ) self.modes_combobox.bind("<>", self.handle_mode_changed) - self.modes_combobox.grid(row=0, column=1, sticky="ew", pady=PADY) + self.modes_combobox.grid(row=0, column=1, sticky=tk.EW, pady=PADY) logging.info("config service config: %s", self.config) self.config_frame = ConfigFrame(tab, self.app, self.config) self.config_frame.draw_config() - self.config_frame.grid(sticky="nsew", pady=PADY) + self.config_frame.grid(sticky=tk.NSEW, pady=PADY) tab.rowconfigure(self.config_frame.grid_info()["row"], weight=1) def draw_tab_startstop(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) - tab.grid(sticky="nsew") + tab.grid(sticky=tk.NSEW) tab.columnconfigure(0, weight=1) for i in range(3): tab.rowconfigure(i, weight=1) @@ -215,12 +215,12 @@ class ConfigServiceConfigDialog(Dialog): commands = self.validation_commands label_frame.columnconfigure(0, weight=1) label_frame.rowconfigure(0, weight=1) - label_frame.grid(row=i, column=0, sticky="nsew", pady=PADY) + label_frame.grid(row=i, column=0, sticky=tk.NSEW, pady=PADY) listbox_scroll = ListboxScroll(label_frame) for command in commands: listbox_scroll.listbox.insert("end", command) listbox_scroll.listbox.config(height=4) - listbox_scroll.grid(sticky="nsew") + listbox_scroll.grid(sticky=tk.NSEW) if i == 0: self.startup_commands_listbox = listbox_scroll.listbox elif i == 1: @@ -230,23 +230,23 @@ class ConfigServiceConfigDialog(Dialog): def draw_tab_validation(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) - tab.grid(sticky="ew") + tab.grid(sticky=tk.EW) tab.columnconfigure(0, weight=1) - self.notebook.add(tab, text="Validation", sticky="nsew") + self.notebook.add(tab, text="Validation", sticky=tk.NSEW) frame = ttk.Frame(tab) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) frame.columnconfigure(1, weight=1) label = ttk.Label(frame, text="Validation Time") - label.grid(row=0, column=0, sticky="w", padx=PADX) + label.grid(row=0, column=0, sticky=tk.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", pady=PADY) + self.validation_time_entry.grid(row=0, column=1, sticky=tk.EW, pady=PADY) label = ttk.Label(frame, text="Validation Mode") - label.grid(row=1, column=0, sticky="w", padx=PADX) + label.grid(row=1, column=0, sticky=tk.W, padx=PADX) if self.validation_mode == ServiceValidationMode.BLOCKING: mode = "BLOCKING" elif self.validation_mode == ServiceValidationMode.NON_BLOCKING: @@ -258,48 +258,48 @@ class ConfigServiceConfigDialog(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", pady=PADY) + self.validation_mode_entry.grid(row=1, column=1, sticky=tk.EW, pady=PADY) label = ttk.Label(frame, text="Validation Period") - label.grid(row=2, column=0, sticky="w", padx=PADX) + label.grid(row=2, column=0, sticky=tk.W, padx=PADX) self.validation_period_entry = ttk.Entry( frame, state=tk.DISABLED, textvariable=self.validation_period ) - self.validation_period_entry.grid(row=2, column=1, sticky="ew", pady=PADY) + self.validation_period_entry.grid(row=2, column=1, sticky=tk.EW, pady=PADY) label_frame = ttk.LabelFrame(tab, text="Executables", padding=FRAME_PAD) - label_frame.grid(sticky="nsew", pady=PADY) + label_frame.grid(sticky=tk.NSEW, pady=PADY) label_frame.columnconfigure(0, weight=1) label_frame.rowconfigure(0, weight=1) listbox_scroll = ListboxScroll(label_frame) - listbox_scroll.grid(sticky="nsew") + listbox_scroll.grid(sticky=tk.NSEW) tab.rowconfigure(listbox_scroll.grid_info()["row"], weight=1) for executable in self.executables: listbox_scroll.listbox.insert("end", executable) label_frame = ttk.LabelFrame(tab, text="Dependencies", padding=FRAME_PAD) - label_frame.grid(sticky="nsew", pady=PADY) + label_frame.grid(sticky=tk.NSEW, pady=PADY) label_frame.columnconfigure(0, weight=1) label_frame.rowconfigure(0, weight=1) listbox_scroll = ListboxScroll(label_frame) - listbox_scroll.grid(sticky="nsew") + listbox_scroll.grid(sticky=tk.NSEW) tab.rowconfigure(listbox_scroll.grid_info()["row"], weight=1) for dependency in self.dependencies: listbox_scroll.listbox.insert("end", dependency) def draw_buttons(self) -> None: frame = ttk.Frame(self.top) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) for i in range(4): 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) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Defaults", command=self.click_defaults) - button.grid(row=0, column=1, sticky="ew", padx=PADX) + button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Copy...", command=self.click_copy) - button.grid(row=0, column=2, sticky="ew", padx=PADX) + button.grid(row=0, column=2, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=3, sticky="ew") + button.grid(row=0, column=3, sticky=tk.EW) def click_apply(self) -> None: current_listbox = self.master.current.listbox diff --git a/daemon/core/gui/dialogs/copyserviceconfig.py b/daemon/core/gui/dialogs/copyserviceconfig.py index b60d5a0d..b205e175 100644 --- a/daemon/core/gui/dialogs/copyserviceconfig.py +++ b/daemon/core/gui/dialogs/copyserviceconfig.py @@ -38,10 +38,10 @@ class CopyServiceConfigDialog(Dialog): label = ttk.Label( self.top, text=f"{self.service} - {self.file_name}", anchor=tk.CENTER ) - label.grid(sticky="ew", pady=PADY) + label.grid(sticky=tk.EW, pady=PADY) listbox_scroll = ListboxScroll(self.top) - listbox_scroll.grid(sticky="nsew", pady=PADY) + listbox_scroll.grid(sticky=tk.NSEW, pady=PADY) self.listbox = listbox_scroll.listbox for node in self.app.core.session.nodes.values(): file_configs = node.service_file_configs.get(self.service) @@ -54,15 +54,15 @@ class CopyServiceConfigDialog(Dialog): self.listbox.insert(tk.END, node.name) frame = ttk.Frame(self.top) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) for i in range(3): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Copy", command=self.click_copy) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="View", command=self.click_view) - button.grid(row=0, column=1, sticky="ew", padx=PADX) + button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=2, sticky="ew") + button.grid(row=0, column=2, sticky=tk.EW) def click_copy(self) -> None: selection = self.listbox.curselection() @@ -112,8 +112,8 @@ class ViewConfigDialog(Dialog): self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) self.service_data = CodeText(self.top) - self.service_data.grid(sticky="nsew", pady=PADY) + self.service_data.grid(sticky=tk.NSEW, pady=PADY) self.service_data.text.insert(tk.END, self.data) self.service_data.text.config(state=tk.DISABLED) button = ttk.Button(self.top, text="Close", command=self.destroy) - button.grid(sticky="ew") + button.grid(sticky=tk.EW) diff --git a/daemon/core/gui/dialogs/customnodes.py b/daemon/core/gui/dialogs/customnodes.py index df3bafa7..53451ab1 100644 --- a/daemon/core/gui/dialogs/customnodes.py +++ b/daemon/core/gui/dialogs/customnodes.py @@ -34,47 +34,47 @@ class ServicesSelectDialog(Dialog): self.top.rowconfigure(0, weight=1) frame = ttk.LabelFrame(self.top) - frame.grid(stick="nsew", pady=PADY) + frame.grid(stick=tk.NSEW, pady=PADY) frame.rowconfigure(0, weight=1) for i in range(3): frame.columnconfigure(i, weight=1) label_frame = ttk.LabelFrame(frame, text="Groups", padding=FRAME_PAD) - label_frame.grid(row=0, column=0, sticky="nsew") + label_frame.grid(row=0, column=0, sticky=tk.NSEW) label_frame.rowconfigure(0, weight=1) label_frame.columnconfigure(0, weight=1) self.groups = ListboxScroll(label_frame) - self.groups.grid(sticky="nsew") + self.groups.grid(sticky=tk.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) label_frame = ttk.LabelFrame(frame, text="Services") - label_frame.grid(row=0, column=1, sticky="nsew") + label_frame.grid(row=0, column=1, sticky=tk.NSEW) label_frame.columnconfigure(0, weight=1) label_frame.rowconfigure(0, weight=1) self.services = CheckboxList( label_frame, self.app, clicked=self.service_clicked, padding=FRAME_PAD ) - self.services.grid(sticky="nsew") + self.services.grid(sticky=tk.NSEW) label_frame = ttk.LabelFrame(frame, text="Selected", padding=FRAME_PAD) - label_frame.grid(row=0, column=2, sticky="nsew") + label_frame.grid(row=0, column=2, sticky=tk.NSEW) label_frame.rowconfigure(0, weight=1) label_frame.columnconfigure(0, weight=1) self.current = ListboxScroll(label_frame) - self.current.grid(sticky="nsew") + self.current.grid(sticky=tk.NSEW) for service in sorted(self.current_services): self.current.listbox.insert(tk.END, service) frame = ttk.Frame(self.top) - frame.grid(stick="ew") + frame.grid(stick=tk.EW) for i in range(2): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Save", command=self.destroy) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.click_cancel) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) # trigger group change self.groups.listbox.event_generate("<>") @@ -127,58 +127,58 @@ class CustomNodesDialog(Dialog): def draw_node_config(self) -> None: frame = ttk.LabelFrame(self.top, text="Nodes", padding=FRAME_PAD) - frame.grid(sticky="nsew", pady=PADY) + frame.grid(sticky=tk.NSEW, pady=PADY) frame.columnconfigure(0, weight=1) frame.rowconfigure(0, weight=1) self.nodes_list = ListboxScroll(frame) - self.nodes_list.grid(row=0, column=0, sticky="nsew", padx=PADX) + self.nodes_list.grid(row=0, column=0, sticky=tk.NSEW, padx=PADX) self.nodes_list.listbox.bind("<>", self.handle_node_select) for name in sorted(self.app.core.custom_nodes): self.nodes_list.listbox.insert(tk.END, name) frame = ttk.Frame(frame) - frame.grid(row=0, column=2, sticky="nsew") + frame.grid(row=0, column=2, sticky=tk.NSEW) frame.columnconfigure(0, weight=1) entry = ttk.Entry(frame, textvariable=self.name) - entry.grid(sticky="ew", pady=PADY) + entry.grid(sticky=tk.EW, pady=PADY) self.image_button = ttk.Button( frame, text="Icon", compound=tk.LEFT, command=self.click_icon ) - self.image_button.grid(sticky="ew", pady=PADY) + self.image_button.grid(sticky=tk.EW, pady=PADY) button = ttk.Button(frame, text="Services", command=self.click_services) - button.grid(sticky="ew") + button.grid(sticky=tk.EW) def draw_node_buttons(self) -> None: frame = ttk.Frame(self.top) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) for i in range(3): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Create", command=self.click_create) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) self.edit_button = ttk.Button( frame, text="Edit", state=tk.DISABLED, command=self.click_edit ) - self.edit_button.grid(row=0, column=1, sticky="ew", padx=PADX) + self.edit_button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) self.delete_button = ttk.Button( frame, text="Delete", state=tk.DISABLED, command=self.click_delete ) - self.delete_button.grid(row=0, column=2, sticky="ew") + self.delete_button.grid(row=0, column=2, sticky=tk.EW) def draw_buttons(self) -> None: frame = ttk.Frame(self.top) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) for i in range(2): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Save", command=self.click_save) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def reset_values(self) -> None: self.name.set("") diff --git a/daemon/core/gui/dialogs/dialog.py b/daemon/core/gui/dialogs/dialog.py index 962170e7..ce05a5d5 100644 --- a/daemon/core/gui/dialogs/dialog.py +++ b/daemon/core/gui/dialogs/dialog.py @@ -30,7 +30,7 @@ class Dialog(tk.Toplevel): self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) self.top: ttk.Frame = ttk.Frame(self, padding=DIALOG_PAD) - self.top.grid(sticky="nsew") + self.top.grid(sticky=tk.NSEW) def show(self) -> None: self.transient(self.master) @@ -44,6 +44,6 @@ class Dialog(tk.Toplevel): def draw_spacer(self, row: int = None) -> None: frame = ttk.Frame(self.top) - frame.grid(row=row, sticky="nsew") + frame.grid(row=row, sticky=tk.NSEW) frame.rowconfigure(0, weight=1) self.top.rowconfigure(frame.grid_info()["row"], weight=1) diff --git a/daemon/core/gui/dialogs/emaneconfig.py b/daemon/core/gui/dialogs/emaneconfig.py index 019eeaa9..f7925d16 100644 --- a/daemon/core/gui/dialogs/emaneconfig.py +++ b/daemon/core/gui/dialogs/emaneconfig.py @@ -33,20 +33,20 @@ class GlobalEmaneDialog(Dialog): self.top, self.app, session.emane_config, self.enabled ) self.config_frame.draw_config() - self.config_frame.grid(sticky="nsew", pady=PADY) + self.config_frame.grid(sticky=tk.NSEW, pady=PADY) self.draw_spacer() self.draw_buttons() def draw_buttons(self) -> None: frame = ttk.Frame(self.top) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) for i in range(2): frame.columnconfigure(i, weight=1) state = tk.NORMAL if self.enabled else tk.DISABLED button = ttk.Button(frame, text="Apply", command=self.click_apply, state=state) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def click_apply(self) -> None: self.config_frame.parse_config() @@ -87,20 +87,20 @@ class EmaneModelDialog(Dialog): self.top.rowconfigure(0, weight=1) self.config_frame = ConfigFrame(self.top, self.app, self.config, self.enabled) self.config_frame.draw_config() - self.config_frame.grid(sticky="nsew", pady=PADY) + self.config_frame.grid(sticky=tk.NSEW, pady=PADY) self.draw_spacer() self.draw_buttons() def draw_buttons(self) -> None: frame = ttk.Frame(self.top) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) for i in range(2): frame.columnconfigure(i, weight=1) state = tk.NORMAL if self.enabled else tk.DISABLED button = ttk.Button(frame, text="Apply", command=self.click_apply, state=state) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def click_apply(self) -> None: self.config_frame.parse_config() @@ -156,30 +156,30 @@ class EmaneConfigDialog(Dialog): ), ) button.image = image - button.grid(sticky="ew", pady=PADY) + button.grid(sticky=tk.EW, pady=PADY) def draw_emane_models(self) -> None: """ create a combobox that has all the known emane models """ frame = ttk.Frame(self.top) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) frame.columnconfigure(1, weight=1) label = ttk.Label(frame, text="Model") - label.grid(row=0, column=0, sticky="w") + label.grid(row=0, column=0, sticky=tk.W) # create combo box and its binding state = "readonly" if self.enabled else tk.DISABLED combobox = ttk.Combobox( frame, textvariable=self.emane_model, values=self.emane_models, state=state ) - combobox.grid(row=0, column=1, sticky="ew") + combobox.grid(row=0, column=1, sticky=tk.EW) combobox.bind("<>", self.emane_model_change) def draw_emane_buttons(self) -> None: frame = ttk.Frame(self.top) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) for i in range(2): frame.columnconfigure(i, weight=1) @@ -192,7 +192,7 @@ class EmaneConfigDialog(Dialog): command=self.click_model_config, ) self.emane_model_button.image = image - self.emane_model_button.grid(row=0, column=0, padx=PADX, sticky="ew") + self.emane_model_button.grid(row=0, column=0, padx=PADX, sticky=tk.EW) image = Images.get(ImageEnum.EDITNODE, 16) button = ttk.Button( @@ -203,18 +203,18 @@ class EmaneConfigDialog(Dialog): command=self.click_emane_config, ) button.image = image - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def draw_apply_and_cancel(self) -> None: frame = ttk.Frame(self.top) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) for i in range(2): frame.columnconfigure(i, weight=1) state = tk.NORMAL if self.enabled else tk.DISABLED button = ttk.Button(frame, text="Apply", command=self.click_apply, state=state) - button.grid(row=0, column=0, padx=PADX, sticky="ew") + button.grid(row=0, column=0, padx=PADX, sticky=tk.EW) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def click_emane_config(self) -> None: dialog = GlobalEmaneDialog(self, self.app) diff --git a/daemon/core/gui/dialogs/emaneinstall.py b/daemon/core/gui/dialogs/emaneinstall.py index 3ad9396b..9f9f2f5c 100644 --- a/daemon/core/gui/dialogs/emaneinstall.py +++ b/daemon/core/gui/dialogs/emaneinstall.py @@ -1,3 +1,4 @@ +import tkinter as tk import webbrowser from tkinter import ttk @@ -13,13 +14,13 @@ class EmaneInstallDialog(Dialog): def draw(self) -> None: self.top.columnconfigure(0, weight=1) label = ttk.Label(self.top, text="EMANE needs to be installed!") - label.grid(sticky="ew", pady=PADY) + label.grid(sticky=tk.EW, pady=PADY) button = ttk.Button( self.top, text="EMANE Documentation", command=self.click_doc ) - button.grid(sticky="ew", pady=PADY) + button.grid(sticky=tk.EW, pady=PADY) button = ttk.Button(self.top, text="Close", command=self.destroy) - button.grid(sticky="ew") + button.grid(sticky=tk.EW) def click_doc(self) -> None: webbrowser.open_new("https://coreemu.github.io/core/emane.html") diff --git a/daemon/core/gui/dialogs/executepython.py b/daemon/core/gui/dialogs/executepython.py index a4516df1..0bef9dc1 100644 --- a/daemon/core/gui/dialogs/executepython.py +++ b/daemon/core/gui/dialogs/executepython.py @@ -25,13 +25,13 @@ class ExecutePythonDialog(Dialog): frame = ttk.Frame(self.top, padding=FRAME_PAD) frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) - frame.grid(row=i, column=0, sticky="nsew") + frame.grid(row=i, column=0, sticky=tk.NSEW) i = i + 1 var = tk.StringVar(value="") self.file_entry = ttk.Entry(frame, textvariable=var) - self.file_entry.grid(row=0, column=0, sticky="ew") + self.file_entry.grid(row=0, column=0, sticky=tk.EW) button = ttk.Button(frame, text="...", command=self.select_file) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) self.top.columnconfigure(0, weight=1) button = ttk.Checkbutton( @@ -40,18 +40,18 @@ class ExecutePythonDialog(Dialog): variable=self.with_options, command=self.add_options, ) - button.grid(row=i, column=0, sticky="ew") + button.grid(row=i, column=0, sticky=tk.EW) i = i + 1 label = ttk.Label( self.top, text="Any command-line options for running the Python script" ) - label.grid(row=i, column=0, sticky="ew") + label.grid(row=i, column=0, sticky=tk.EW) i = i + 1 self.option_entry = ttk.Entry( self.top, textvariable=self.options, state="disabled" ) - self.option_entry.grid(row=i, column=0, sticky="ew") + self.option_entry.grid(row=i, column=0, sticky=tk.EW) i = i + 1 frame = ttk.Frame(self.top, padding=FRAME_PAD) @@ -59,9 +59,9 @@ class ExecutePythonDialog(Dialog): frame.columnconfigure(1, weight=1) frame.grid(row=i, column=0) button = ttk.Button(frame, text="Execute", command=self.script_execute) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky="ew", padx=PADX) + button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) def add_options(self) -> None: if self.with_options.get(): diff --git a/daemon/core/gui/dialogs/find.py b/daemon/core/gui/dialogs/find.py index a4600847..6bfac47b 100644 --- a/daemon/core/gui/dialogs/find.py +++ b/daemon/core/gui/dialogs/find.py @@ -25,25 +25,25 @@ class FindDialog(Dialog): # Find node frame frame = ttk.Frame(self.top, padding=FRAME_PAD) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) frame.columnconfigure(1, weight=1) label = ttk.Label(frame, text="Find:") label.grid() entry = ttk.Entry(frame, textvariable=self.find_text) - entry.grid(row=0, column=1, sticky="nsew") + entry.grid(row=0, column=1, sticky=tk.NSEW) # node list frame frame = ttk.Frame(self.top) frame.columnconfigure(0, weight=1) frame.rowconfigure(0, weight=1) - frame.grid(sticky="nsew", pady=PADY) + frame.grid(sticky=tk.NSEW, pady=PADY) self.tree = ttk.Treeview( frame, columns=("nodeid", "name", "location", "detail"), show="headings", selectmode=tk.BROWSE, ) - self.tree.grid(sticky="nsew", pady=PADY) + self.tree.grid(sticky=tk.NSEW, pady=PADY) style = ttk.Style() heading_size = int(self.app.guiconfig.scale * 10) style.configure("Treeview.Heading", font=(None, heading_size, "bold")) @@ -57,21 +57,21 @@ class FindDialog(Dialog): self.tree.heading("detail", text="Detail") self.tree.bind("<>", self.click_select) yscrollbar = ttk.Scrollbar(frame, orient="vertical", command=self.tree.yview) - yscrollbar.grid(row=0, column=1, sticky="ns") + yscrollbar.grid(row=0, column=1, sticky=tk.NS) self.tree.configure(yscrollcommand=yscrollbar.set) xscrollbar = ttk.Scrollbar(frame, orient="horizontal", command=self.tree.xview) - xscrollbar.grid(row=1, sticky="ew") + xscrollbar.grid(row=1, sticky=tk.EW) self.tree.configure(xscrollcommand=xscrollbar.set) # button frame frame = ttk.Frame(self.top) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) button = ttk.Button(frame, text="Find", command=self.find_node) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.close_dialog) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def clear_treeview_items(self) -> None: """ diff --git a/daemon/core/gui/dialogs/hooks.py b/daemon/core/gui/dialogs/hooks.py index ce7caf29..e831b4f9 100644 --- a/daemon/core/gui/dialogs/hooks.py +++ b/daemon/core/gui/dialogs/hooks.py @@ -27,14 +27,14 @@ class HookDialog(Dialog): # name and states frame = ttk.Frame(self.top) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) frame.columnconfigure(0, weight=2) frame.columnconfigure(1, weight=7) frame.columnconfigure(2, weight=1) label = ttk.Label(frame, text="Name") - label.grid(row=0, column=0, sticky="ew", padx=PADX) + label.grid(row=0, column=0, sticky=tk.EW, padx=PADX) entry = ttk.Entry(frame, textvariable=self.name) - entry.grid(row=0, column=1, sticky="ew", padx=PADX) + entry.grid(row=0, column=1, sticky=tk.EW, padx=PADX) values = tuple(x.name for x in SessionState) initial_state = SessionState.RUNTIME.name self.state.set(initial_state) @@ -42,7 +42,7 @@ class HookDialog(Dialog): combobox = ttk.Combobox( frame, textvariable=self.state, values=values, state="readonly" ) - combobox.grid(row=0, column=2, sticky="ew") + combobox.grid(row=0, column=2, sticky=tk.EW) combobox.bind("<>", self.state_change) # data @@ -55,17 +55,17 @@ class HookDialog(Dialog): "# specified state\n" ), ) - self.codetext.grid(sticky="nsew", pady=PADY) + self.codetext.grid(sticky=tk.NSEW, pady=PADY) # button row frame = ttk.Frame(self.top) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) for i in range(2): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Save", command=lambda: self.save()) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=lambda: self.destroy()) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def state_change(self, event: tk.Event) -> None: if self.editing: @@ -110,7 +110,7 @@ class HooksDialog(Dialog): self.top.rowconfigure(0, weight=1) listbox_scroll = ListboxScroll(self.top) - listbox_scroll.grid(sticky="nsew", pady=PADY) + listbox_scroll.grid(sticky=tk.NSEW, pady=PADY) self.listbox = listbox_scroll.listbox self.listbox.bind("<>", self.select) session = self.app.core.session @@ -118,21 +118,21 @@ class HooksDialog(Dialog): self.listbox.insert(tk.END, file) frame = ttk.Frame(self.top) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) for i in range(4): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Create", command=self.click_create) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) self.edit_button = ttk.Button( frame, text="Edit", state=tk.DISABLED, command=self.click_edit ) - self.edit_button.grid(row=0, column=1, sticky="ew", padx=PADX) + self.edit_button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) self.delete_button = ttk.Button( frame, text="Delete", state=tk.DISABLED, command=self.click_delete ) - self.delete_button.grid(row=0, column=2, sticky="ew", padx=PADX) + self.delete_button.grid(row=0, column=2, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=lambda: self.destroy()) - button.grid(row=0, column=3, sticky="ew") + button.grid(row=0, column=3, sticky=tk.EW) def click_create(self) -> None: dialog = HookDialog(self, self.app) diff --git a/daemon/core/gui/dialogs/ipdialog.py b/daemon/core/gui/dialogs/ipdialog.py index 351bfffc..a09ca097 100644 --- a/daemon/core/gui/dialogs/ipdialog.py +++ b/daemon/core/gui/dialogs/ipdialog.py @@ -34,7 +34,7 @@ class IpConfigDialog(Dialog): frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) frame.rowconfigure(0, weight=1) - frame.grid(sticky="nsew", pady=PADY) + frame.grid(sticky=tk.NSEW, pady=PADY) ip4_frame = ttk.LabelFrame(frame, text="IPv4", padding=FRAME_PAD) ip4_frame.columnconfigure(0, weight=1) @@ -42,23 +42,23 @@ class IpConfigDialog(Dialog): ip4_frame.grid(row=0, column=0, stick="nsew") self.ip4_listbox = ListboxScroll(ip4_frame) self.ip4_listbox.listbox.bind("<>", self.select_ip4) - self.ip4_listbox.grid(sticky="nsew", pady=PADY) + self.ip4_listbox.grid(sticky=tk.NSEW, pady=PADY) for index, ip4 in enumerate(self.ip4s): self.ip4_listbox.listbox.insert(tk.END, ip4) if self.ip4 == ip4: self.ip4_listbox.listbox.select_set(index) self.ip4_entry = ttk.Entry(ip4_frame) - self.ip4_entry.grid(sticky="ew", pady=PADY) + self.ip4_entry.grid(sticky=tk.EW, pady=PADY) ip4_button_frame = ttk.Frame(ip4_frame) ip4_button_frame.columnconfigure(0, weight=1) ip4_button_frame.columnconfigure(1, weight=1) - ip4_button_frame.grid(sticky="ew") + ip4_button_frame.grid(sticky=tk.EW) ip4_add = ttk.Button(ip4_button_frame, text="Add", command=self.click_add_ip4) - ip4_add.grid(row=0, column=0, sticky="ew") + ip4_add.grid(row=0, column=0, sticky=tk.EW) ip4_del = ttk.Button( ip4_button_frame, text="Delete", command=self.click_del_ip4 ) - ip4_del.grid(row=0, column=1, sticky="ew") + ip4_del.grid(row=0, column=1, sticky=tk.EW) ip6_frame = ttk.LabelFrame(frame, text="IPv6", padding=FRAME_PAD) ip6_frame.columnconfigure(0, weight=1) @@ -66,23 +66,23 @@ class IpConfigDialog(Dialog): ip6_frame.grid(row=0, column=1, stick="nsew") self.ip6_listbox = ListboxScroll(ip6_frame) self.ip6_listbox.listbox.bind("<>", self.select_ip6) - self.ip6_listbox.grid(sticky="nsew", pady=PADY) + self.ip6_listbox.grid(sticky=tk.NSEW, pady=PADY) for index, ip6 in enumerate(self.ip6s): self.ip6_listbox.listbox.insert(tk.END, ip6) if self.ip6 == ip6: self.ip6_listbox.listbox.select_set(index) self.ip6_entry = ttk.Entry(ip6_frame) - self.ip6_entry.grid(sticky="ew", pady=PADY) + self.ip6_entry.grid(sticky=tk.EW, pady=PADY) ip6_button_frame = ttk.Frame(ip6_frame) ip6_button_frame.columnconfigure(0, weight=1) ip6_button_frame.columnconfigure(1, weight=1) - ip6_button_frame.grid(sticky="ew") + ip6_button_frame.grid(sticky=tk.EW) ip6_add = ttk.Button(ip6_button_frame, text="Add", command=self.click_add_ip6) - ip6_add.grid(row=0, column=0, sticky="ew") + ip6_add.grid(row=0, column=0, sticky=tk.EW) ip6_del = ttk.Button( ip6_button_frame, text="Delete", command=self.click_del_ip6 ) - ip6_del.grid(row=0, column=1, sticky="ew") + ip6_del.grid(row=0, column=1, sticky=tk.EW) # draw buttons frame = ttk.Frame(self.top) @@ -90,9 +90,9 @@ class IpConfigDialog(Dialog): for i in range(2): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Save", command=self.click_save) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def click_add_ip4(self) -> None: ip4 = self.ip4_entry.get() diff --git a/daemon/core/gui/dialogs/linkconfig.py b/daemon/core/gui/dialogs/linkconfig.py index 1aa2d7f8..a09cfe7f 100644 --- a/daemon/core/gui/dialogs/linkconfig.py +++ b/daemon/core/gui/dialogs/linkconfig.py @@ -73,11 +73,11 @@ class LinkConfigurationDialog(Dialog): label = ttk.Label( self.top, text=f"Link from {source_name} to {dest_name}", anchor=tk.CENTER ) - label.grid(row=0, column=0, sticky="ew", pady=PADY) + label.grid(row=0, column=0, sticky=tk.EW, pady=PADY) frame = ttk.Frame(self.top) frame.columnconfigure(0, weight=1) - frame.grid(row=1, column=0, sticky="ew", pady=PADY) + frame.grid(row=1, column=0, sticky=tk.EW, pady=PADY) if self.is_symmetric: button = ttk.Button( frame, textvariable=self.symmetry_var, command=self.change_symmetry @@ -86,25 +86,25 @@ class LinkConfigurationDialog(Dialog): button = ttk.Button( frame, textvariable=self.symmetry_var, command=self.change_symmetry ) - button.grid(sticky="ew") + button.grid(sticky=tk.EW) if self.is_symmetric: self.symmetric_frame = self.get_frame() - self.symmetric_frame.grid(row=2, column=0, sticky="ew", pady=PADY) + self.symmetric_frame.grid(row=2, column=0, sticky=tk.EW, pady=PADY) else: self.asymmetric_frame = self.get_frame() - self.asymmetric_frame.grid(row=2, column=0, sticky="ew", pady=PADY) + self.asymmetric_frame.grid(row=2, column=0, sticky=tk.EW, pady=PADY) self.draw_spacer(row=3) frame = ttk.Frame(self.top) frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) - frame.grid(row=4, column=0, sticky="ew") + frame.grid(row=4, column=0, sticky=tk.EW) button = ttk.Button(frame, text="Apply", command=self.click_apply) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def get_frame(self) -> ttk.Frame: frame = ttk.Frame(self.top) @@ -115,76 +115,76 @@ class LinkConfigurationDialog(Dialog): label_name = "Asymmetric Effects: Downstream / Upstream " row = 0 label = ttk.Label(frame, text=label_name, anchor=tk.CENTER) - label.grid(row=row, column=0, columnspan=2, sticky="ew", pady=PADY) + label.grid(row=row, column=0, columnspan=2, sticky=tk.EW, pady=PADY) row = row + 1 label = ttk.Label(frame, text="Bandwidth (bps)") - label.grid(row=row, column=0, sticky="ew") + label.grid(row=row, column=0, sticky=tk.EW) entry = validation.PositiveIntEntry( frame, empty_enabled=False, textvariable=self.bandwidth ) - entry.grid(row=row, column=1, sticky="ew", pady=PADY) + entry.grid(row=row, column=1, sticky=tk.EW, pady=PADY) if not self.is_symmetric: entry = validation.PositiveIntEntry( frame, empty_enabled=False, textvariable=self.down_bandwidth ) - entry.grid(row=row, column=2, sticky="ew", pady=PADY) + entry.grid(row=row, column=2, sticky=tk.EW, pady=PADY) row = row + 1 label = ttk.Label(frame, text="Delay (us)") - label.grid(row=row, column=0, sticky="ew") + label.grid(row=row, column=0, sticky=tk.EW) entry = validation.PositiveIntEntry( frame, empty_enabled=False, textvariable=self.delay ) - entry.grid(row=row, column=1, sticky="ew", pady=PADY) + entry.grid(row=row, column=1, sticky=tk.EW, pady=PADY) if not self.is_symmetric: entry = validation.PositiveIntEntry( frame, empty_enabled=False, textvariable=self.down_delay ) - entry.grid(row=row, column=2, sticky="ew", pady=PADY) + entry.grid(row=row, column=2, sticky=tk.EW, pady=PADY) row = row + 1 label = ttk.Label(frame, text="Jitter (us)") - label.grid(row=row, column=0, sticky="ew") + label.grid(row=row, column=0, sticky=tk.EW) entry = validation.PositiveIntEntry( frame, empty_enabled=False, textvariable=self.jitter ) - entry.grid(row=row, column=1, sticky="ew", pady=PADY) + entry.grid(row=row, column=1, sticky=tk.EW, pady=PADY) if not self.is_symmetric: entry = validation.PositiveIntEntry( frame, empty_enabled=False, textvariable=self.down_jitter ) - entry.grid(row=row, column=2, sticky="ew", pady=PADY) + entry.grid(row=row, column=2, sticky=tk.EW, pady=PADY) row = row + 1 label = ttk.Label(frame, text="Loss (%)") - label.grid(row=row, column=0, sticky="ew") + label.grid(row=row, column=0, sticky=tk.EW) entry = validation.PositiveFloatEntry( frame, empty_enabled=False, textvariable=self.loss ) - entry.grid(row=row, column=1, sticky="ew", pady=PADY) + entry.grid(row=row, column=1, sticky=tk.EW, pady=PADY) if not self.is_symmetric: entry = validation.PositiveFloatEntry( frame, empty_enabled=False, textvariable=self.down_loss ) - entry.grid(row=row, column=2, sticky="ew", pady=PADY) + entry.grid(row=row, column=2, sticky=tk.EW, pady=PADY) row = row + 1 label = ttk.Label(frame, text="Duplicate (%)") - label.grid(row=row, column=0, sticky="ew") + label.grid(row=row, column=0, sticky=tk.EW) entry = validation.PositiveIntEntry( frame, empty_enabled=False, textvariable=self.duplicate ) - entry.grid(row=row, column=1, sticky="ew", pady=PADY) + entry.grid(row=row, column=1, sticky=tk.EW, pady=PADY) if not self.is_symmetric: entry = validation.PositiveIntEntry( frame, empty_enabled=False, textvariable=self.down_duplicate ) - entry.grid(row=row, column=2, sticky="ew", pady=PADY) + entry.grid(row=row, column=2, sticky=tk.EW, pady=PADY) row = row + 1 label = ttk.Label(frame, text="Color") - label.grid(row=row, column=0, sticky="ew") + label.grid(row=row, column=0, sticky=tk.EW) self.color_button = tk.Button( frame, textvariable=self.color, @@ -194,15 +194,15 @@ class LinkConfigurationDialog(Dialog): highlightthickness=0, command=self.click_color, ) - self.color_button.grid(row=row, column=1, sticky="ew", pady=PADY) + self.color_button.grid(row=row, column=1, sticky=tk.EW, pady=PADY) row = row + 1 label = ttk.Label(frame, text="Width") - label.grid(row=row, column=0, sticky="ew") + label.grid(row=row, column=0, sticky=tk.EW) entry = validation.PositiveFloatEntry( frame, empty_enabled=False, textvariable=self.width ) - entry.grid(row=row, column=1, sticky="ew", pady=PADY) + entry.grid(row=row, column=1, sticky=tk.EW, pady=PADY) return frame diff --git a/daemon/core/gui/dialogs/macdialog.py b/daemon/core/gui/dialogs/macdialog.py index 4d89439b..c8cd7f45 100644 --- a/daemon/core/gui/dialogs/macdialog.py +++ b/daemon/core/gui/dialogs/macdialog.py @@ -28,7 +28,7 @@ class MacConfigDialog(Dialog): "provided value below and increment by value in order." ) label = ttk.Label(self.top, text=text) - label.grid(sticky="ew", pady=PADY) + label.grid(sticky=tk.EW, pady=PADY) # draw input frame = ttk.Frame(self.top) @@ -36,9 +36,9 @@ class MacConfigDialog(Dialog): frame.columnconfigure(1, weight=3) frame.grid(stick="ew", pady=PADY) label = ttk.Label(frame, text="Starting MAC") - label.grid(row=0, column=0, sticky="ew", padx=PADX) + label.grid(row=0, column=0, sticky=tk.EW, padx=PADX) entry = ttk.Entry(frame, textvariable=self.mac_var) - entry.grid(row=0, column=1, sticky="ew") + entry.grid(row=0, column=1, sticky=tk.EW) # draw buttons frame = ttk.Frame(self.top) @@ -46,9 +46,9 @@ class MacConfigDialog(Dialog): for i in range(2): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Save", command=self.click_save) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def click_save(self) -> None: mac = self.mac_var.get() diff --git a/daemon/core/gui/dialogs/mobilityconfig.py b/daemon/core/gui/dialogs/mobilityconfig.py index 857167be..80c3ca22 100644 --- a/daemon/core/gui/dialogs/mobilityconfig.py +++ b/daemon/core/gui/dialogs/mobilityconfig.py @@ -1,6 +1,7 @@ """ mobility configuration """ +import tkinter as tk from tkinter import ttk from typing import TYPE_CHECKING, Dict, Optional @@ -37,20 +38,20 @@ class MobilityConfigDialog(Dialog): self.top.rowconfigure(0, weight=1) self.config_frame = ConfigFrame(self.top, self.app, self.config) self.config_frame.draw_config() - self.config_frame.grid(sticky="nsew", pady=PADY) + self.config_frame.grid(sticky=tk.NSEW, pady=PADY) self.draw_apply_buttons() def draw_apply_buttons(self) -> None: frame = ttk.Frame(self.top) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) for i in range(2): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Apply", command=self.click_apply) - button.grid(row=0, column=0, padx=PADX, sticky="ew") + button.grid(row=0, column=0, padx=PADX, sticky=tk.EW) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def click_apply(self) -> None: self.config_frame.parse_config() diff --git a/daemon/core/gui/dialogs/mobilityplayer.py b/daemon/core/gui/dialogs/mobilityplayer.py index 1bee97d2..352a3739 100644 --- a/daemon/core/gui/dialogs/mobilityplayer.py +++ b/daemon/core/gui/dialogs/mobilityplayer.py @@ -74,30 +74,30 @@ class MobilityPlayerDialog(Dialog): file_name = config["file"].value label = ttk.Label(self.top, text=file_name) - label.grid(sticky="ew", pady=PADY) + label.grid(sticky=tk.EW, pady=PADY) self.progressbar = ttk.Progressbar(self.top, mode="indeterminate") - self.progressbar.grid(sticky="ew", pady=PADY) + self.progressbar.grid(sticky=tk.EW, pady=PADY) frame = ttk.Frame(self.top) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) for i in range(3): frame.columnconfigure(i, weight=1) image = self.app.get_icon(ImageEnum.START, ICON_SIZE) self.play_button = ttk.Button(frame, image=image, command=self.click_play) self.play_button.image = image - self.play_button.grid(row=0, column=0, sticky="ew", padx=PADX) + self.play_button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) image = self.app.get_icon(ImageEnum.PAUSE, ICON_SIZE) self.pause_button = ttk.Button(frame, image=image, command=self.click_pause) self.pause_button.image = image - self.pause_button.grid(row=0, column=1, sticky="ew", padx=PADX) + self.pause_button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) image = self.app.get_icon(ImageEnum.STOP, ICON_SIZE) self.stop_button = ttk.Button(frame, image=image, command=self.click_stop) self.stop_button.image = image - self.stop_button.grid(row=0, column=2, sticky="ew", padx=PADX) + self.stop_button.grid(row=0, column=2, sticky=tk.EW, padx=PADX) loop = tk.IntVar(value=int(config["loop"].value == "1")) checkbutton = ttk.Checkbutton( diff --git a/daemon/core/gui/dialogs/nodeconfig.py b/daemon/core/gui/dialogs/nodeconfig.py index 604e933a..d8103283 100644 --- a/daemon/core/gui/dialogs/nodeconfig.py +++ b/daemon/core/gui/dialogs/nodeconfig.py @@ -126,12 +126,12 @@ class NodeConfigDialog(Dialog): # field frame frame = ttk.Frame(self.top) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) frame.columnconfigure(1, weight=1) # icon field label = ttk.Label(frame, text="Icon") - label.grid(row=row, column=0, sticky="ew", padx=PADX, pady=PADY) + label.grid(row=row, column=0, sticky=tk.EW, padx=PADX, pady=PADY) self.image_button = ttk.Button( frame, text="Icon", @@ -139,49 +139,49 @@ class NodeConfigDialog(Dialog): compound=tk.NONE, command=self.click_icon, ) - self.image_button.grid(row=row, column=1, sticky="ew") + self.image_button.grid(row=row, column=1, sticky=tk.EW) row += 1 # name field label = ttk.Label(frame, text="Name") - label.grid(row=row, column=0, sticky="ew", padx=PADX, pady=PADY) + label.grid(row=row, column=0, sticky=tk.EW, padx=PADX, pady=PADY) entry = validation.NodeNameEntry(frame, textvariable=self.name, state=state) - entry.grid(row=row, column=1, sticky="ew") + entry.grid(row=row, column=1, sticky=tk.EW) row += 1 # node type field if NodeUtils.is_model_node(self.node.type): label = ttk.Label(frame, text="Type") - label.grid(row=row, column=0, sticky="ew", padx=PADX, pady=PADY) + label.grid(row=row, column=0, sticky=tk.EW, padx=PADX, pady=PADY) combobox = ttk.Combobox( frame, textvariable=self.type, values=list(NodeUtils.NODE_MODELS), state=combo_state, ) - combobox.grid(row=row, column=1, sticky="ew") + combobox.grid(row=row, column=1, sticky=tk.EW) row += 1 # container image field if NodeUtils.is_image_node(self.node.type): label = ttk.Label(frame, text="Image") - label.grid(row=row, column=0, sticky="ew", padx=PADX, pady=PADY) + label.grid(row=row, column=0, sticky=tk.EW, padx=PADX, pady=PADY) entry = ttk.Entry(frame, textvariable=self.container_image, state=state) - entry.grid(row=row, column=1, sticky="ew") + entry.grid(row=row, column=1, sticky=tk.EW) row += 1 if NodeUtils.is_container_node(self.node.type): # server - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) frame.columnconfigure(1, weight=1) label = ttk.Label(frame, text="Server") - label.grid(row=row, column=0, sticky="ew", padx=PADX, pady=PADY) + label.grid(row=row, column=0, sticky=tk.EW, padx=PADX, pady=PADY) servers = ["localhost"] servers.extend(list(sorted(self.app.core.servers.keys()))) combobox = ttk.Combobox( frame, textvariable=self.server, values=servers, state=combo_state ) - combobox.grid(row=row, column=1, sticky="ew") + combobox.grid(row=row, column=1, sticky=tk.EW) row += 1 if NodeUtils.is_rj45_node(self.node.type): @@ -190,7 +190,7 @@ class NodeConfigDialog(Dialog): ifaces = ListboxScroll(frame) ifaces.listbox.config(state=state) ifaces.grid( - row=row, column=0, columnspan=2, sticky="ew", padx=PADX, pady=PADY + row=row, column=0, columnspan=2, sticky=tk.EW, padx=PADX, pady=PADY ) for inf in sorted(response.ifaces[:]): ifaces.listbox.insert(tk.END, inf) @@ -206,13 +206,13 @@ class NodeConfigDialog(Dialog): def draw_ifaces(self) -> None: notebook = ttk.Notebook(self.top) - notebook.grid(sticky="nsew", pady=PADY) + notebook.grid(sticky=tk.NSEW, pady=PADY) self.top.rowconfigure(notebook.grid_info()["row"], weight=1) state = tk.DISABLED if self.app.core.is_runtime() else tk.NORMAL for iface_id in sorted(self.canvas_node.ifaces): iface = self.canvas_node.ifaces[iface_id] tab = ttk.Frame(notebook, padding=FRAME_PAD) - tab.grid(sticky="nsew", pady=PADY) + tab.grid(sticky=tk.NSEW, pady=PADY) tab.columnconfigure(1, weight=1) tab.columnconfigure(2, weight=1) notebook.add(tab, text=iface.name) @@ -226,7 +226,7 @@ class NodeConfigDialog(Dialog): text=f"Configure EMANE {emane_model}", command=lambda: self.click_emane_config(emane_model, iface.id), ) - button.grid(row=row, sticky="ew", columnspan=3, pady=PADY) + button.grid(row=row, sticky=tk.EW, columnspan=3, pady=PADY) row += 1 label = ttk.Label(tab, text="MAC") @@ -243,7 +243,7 @@ class NodeConfigDialog(Dialog): checkbutton.grid(row=row, column=1, padx=PADX) mac = tk.StringVar(value=iface.mac) entry = ttk.Entry(tab, textvariable=mac, state=mac_state) - entry.grid(row=row, column=2, sticky="ew") + entry.grid(row=row, column=2, sticky=tk.EW) func = partial(mac_auto, is_auto, entry, mac) checkbutton.config(command=func) row += 1 @@ -255,7 +255,7 @@ class NodeConfigDialog(Dialog): ip4_net = f"{iface.ip4}/{iface.ip4_mask}" ip4 = tk.StringVar(value=ip4_net) entry = ttk.Entry(tab, textvariable=ip4, state=state) - entry.grid(row=row, column=1, columnspan=2, sticky="ew") + entry.grid(row=row, column=1, columnspan=2, sticky=tk.EW) row += 1 label = ttk.Label(tab, text="IPv6") @@ -265,21 +265,21 @@ class NodeConfigDialog(Dialog): ip6_net = f"{iface.ip6}/{iface.ip6_mask}" ip6 = tk.StringVar(value=ip6_net) entry = ttk.Entry(tab, textvariable=ip6, state=state) - entry.grid(row=row, column=1, columnspan=2, sticky="ew") + entry.grid(row=row, column=1, columnspan=2, sticky=tk.EW) self.ifaces[iface.id] = InterfaceData(is_auto, mac, ip4, ip6) def draw_buttons(self) -> None: frame = ttk.Frame(self.top) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) button = ttk.Button(frame, text="Apply", command=self.click_apply) - button.grid(row=0, column=0, padx=PADX, sticky="ew") + button.grid(row=0, column=0, padx=PADX, sticky=tk.EW) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def click_emane_config(self, emane_model: str, iface_id: int) -> None: dialog = EmaneModelDialog( diff --git a/daemon/core/gui/dialogs/nodeconfigservice.py b/daemon/core/gui/dialogs/nodeconfigservice.py index dee34f71..1c67e4b3 100644 --- a/daemon/core/gui/dialogs/nodeconfigservice.py +++ b/daemon/core/gui/dialogs/nodeconfigservice.py @@ -41,32 +41,32 @@ class NodeConfigServiceDialog(Dialog): for i in range(3): frame.columnconfigure(i, weight=1) label_frame = ttk.LabelFrame(frame, text="Groups", padding=FRAME_PAD) - label_frame.grid(row=0, column=0, sticky="nsew") + label_frame.grid(row=0, column=0, sticky=tk.NSEW) label_frame.rowconfigure(0, weight=1) label_frame.columnconfigure(0, weight=1) self.groups = ListboxScroll(label_frame) - self.groups.grid(sticky="nsew") + self.groups.grid(sticky=tk.NSEW) for group in sorted(self.app.core.config_services_groups): self.groups.listbox.insert(tk.END, group) self.groups.listbox.bind("<>", self.handle_group_change) self.groups.listbox.selection_set(0) label_frame = ttk.LabelFrame(frame, text="Services") - label_frame.grid(row=0, column=1, sticky="nsew") + label_frame.grid(row=0, column=1, sticky=tk.NSEW) label_frame.columnconfigure(0, weight=1) label_frame.rowconfigure(0, weight=1) self.services = CheckboxList( label_frame, self.app, clicked=self.service_clicked, padding=FRAME_PAD ) - self.services.grid(sticky="nsew") + self.services.grid(sticky=tk.NSEW) label_frame = ttk.LabelFrame(frame, text="Selected", padding=FRAME_PAD) - label_frame.grid(row=0, column=2, sticky="nsew") + label_frame.grid(row=0, column=2, sticky=tk.NSEW) label_frame.rowconfigure(0, weight=1) label_frame.columnconfigure(0, weight=1) self.current = ListboxScroll(label_frame) - self.current.grid(sticky="nsew") + self.current.grid(sticky=tk.NSEW) self.draw_current_services() frame = ttk.Frame(self.top) @@ -74,13 +74,13 @@ class NodeConfigServiceDialog(Dialog): for i in range(4): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Configure", command=self.click_configure) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Save", command=self.click_save) - button.grid(row=0, column=1, sticky="ew", padx=PADX) + button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Remove", command=self.click_remove) - button.grid(row=0, column=2, sticky="ew", padx=PADX) + button.grid(row=0, column=2, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.click_cancel) - button.grid(row=0, column=3, sticky="ew") + button.grid(row=0, column=3, sticky=tk.EW) # trigger group change self.handle_group_change() diff --git a/daemon/core/gui/dialogs/nodeservice.py b/daemon/core/gui/dialogs/nodeservice.py index a56736d5..5ec78a93 100644 --- a/daemon/core/gui/dialogs/nodeservice.py +++ b/daemon/core/gui/dialogs/nodeservice.py @@ -37,31 +37,31 @@ class NodeServiceDialog(Dialog): for i in range(3): frame.columnconfigure(i, weight=1) label_frame = ttk.LabelFrame(frame, text="Groups", padding=FRAME_PAD) - label_frame.grid(row=0, column=0, sticky="nsew") + label_frame.grid(row=0, column=0, sticky=tk.NSEW) label_frame.rowconfigure(0, weight=1) label_frame.columnconfigure(0, weight=1) self.groups = ListboxScroll(label_frame) - self.groups.grid(sticky="nsew") + self.groups.grid(sticky=tk.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) label_frame = ttk.LabelFrame(frame, text="Services") - label_frame.grid(row=0, column=1, sticky="nsew") + label_frame.grid(row=0, column=1, sticky=tk.NSEW) label_frame.columnconfigure(0, weight=1) label_frame.rowconfigure(0, weight=1) self.services = CheckboxList( label_frame, self.app, clicked=self.service_clicked, padding=FRAME_PAD ) - self.services.grid(sticky="nsew") + self.services.grid(sticky=tk.NSEW) label_frame = ttk.LabelFrame(frame, text="Selected", padding=FRAME_PAD) - label_frame.grid(row=0, column=2, sticky="nsew") + label_frame.grid(row=0, column=2, sticky=tk.NSEW) label_frame.rowconfigure(0, weight=1) label_frame.columnconfigure(0, weight=1) self.current = ListboxScroll(label_frame) - self.current.grid(sticky="nsew") + self.current.grid(sticky=tk.NSEW) for service in sorted(self.current_services): self.current.listbox.insert(tk.END, service) if self.is_custom_service(service): @@ -72,13 +72,13 @@ class NodeServiceDialog(Dialog): for i in range(4): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Configure", command=self.click_configure) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Save", command=self.click_save) - button.grid(row=0, column=1, sticky="ew", padx=PADX) + button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Remove", command=self.click_remove) - button.grid(row=0, column=2, sticky="ew", padx=PADX) + button.grid(row=0, column=2, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=3, sticky="ew") + button.grid(row=0, column=3, sticky=tk.EW) # trigger group change self.handle_group_change() diff --git a/daemon/core/gui/dialogs/observers.py b/daemon/core/gui/dialogs/observers.py index 286fc2c9..b815d45b 100644 --- a/daemon/core/gui/dialogs/observers.py +++ b/daemon/core/gui/dialogs/observers.py @@ -33,60 +33,60 @@ class ObserverDialog(Dialog): def draw_listbox(self) -> None: listbox_scroll = ListboxScroll(self.top) - listbox_scroll.grid(sticky="nsew", pady=PADY) + listbox_scroll.grid(sticky=tk.NSEW, pady=PADY) listbox_scroll.columnconfigure(0, weight=1) listbox_scroll.rowconfigure(0, weight=1) self.observers = listbox_scroll.listbox - self.observers.grid(row=0, column=0, sticky="nsew") + self.observers.grid(row=0, column=0, sticky=tk.NSEW) self.observers.bind("<>", self.handle_observer_change) for name in sorted(self.app.core.custom_observers): self.observers.insert(tk.END, name) def draw_form_fields(self) -> None: frame = ttk.Frame(self.top) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) frame.columnconfigure(1, weight=1) label = ttk.Label(frame, text="Name") - label.grid(row=0, column=0, sticky="w", padx=PADX) + label.grid(row=0, column=0, sticky=tk.W, padx=PADX) entry = ttk.Entry(frame, textvariable=self.name) - entry.grid(row=0, column=1, sticky="ew") + entry.grid(row=0, column=1, sticky=tk.EW) label = ttk.Label(frame, text="Command") - label.grid(row=1, column=0, sticky="w", padx=PADX) + label.grid(row=1, column=0, sticky=tk.W, padx=PADX) entry = ttk.Entry(frame, textvariable=self.cmd) - entry.grid(row=1, column=1, sticky="ew") + entry.grid(row=1, column=1, sticky=tk.EW) def draw_config_buttons(self) -> None: frame = ttk.Frame(self.top) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) for i in range(3): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Create", command=self.click_create) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) self.save_button = ttk.Button( frame, text="Save", state=tk.DISABLED, command=self.click_save ) - self.save_button.grid(row=0, column=1, sticky="ew", padx=PADX) + self.save_button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) self.delete_button = ttk.Button( frame, text="Delete", state=tk.DISABLED, command=self.click_delete ) - self.delete_button.grid(row=0, column=2, sticky="ew") + self.delete_button.grid(row=0, column=2, sticky=tk.EW) def draw_apply_buttons(self) -> None: frame = ttk.Frame(self.top) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) for i in range(2): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Save", command=self.click_save_config) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def click_save_config(self) -> None: self.app.guiconfig.observers.clear() diff --git a/daemon/core/gui/dialogs/preferences.py b/daemon/core/gui/dialogs/preferences.py index 839ebd3b..d0c58dfa 100644 --- a/daemon/core/gui/dialogs/preferences.py +++ b/daemon/core/gui/dialogs/preferences.py @@ -34,42 +34,42 @@ class PreferencesDialog(Dialog): def draw_preferences(self) -> None: frame = ttk.LabelFrame(self.top, text="Preferences", padding=FRAME_PAD) - frame.grid(sticky="nsew", pady=PADY) + frame.grid(sticky=tk.NSEW, pady=PADY) frame.columnconfigure(1, weight=1) label = ttk.Label(frame, text="Theme") - label.grid(row=0, column=0, pady=PADY, padx=PADX, sticky="w") + label.grid(row=0, column=0, pady=PADY, padx=PADX, sticky=tk.W) themes = self.app.style.theme_names() combobox = ttk.Combobox( frame, textvariable=self.theme, values=themes, state="readonly" ) combobox.set(self.theme.get()) - combobox.grid(row=0, column=1, sticky="ew") + combobox.grid(row=0, column=1, sticky=tk.EW) combobox.bind("<>", self.theme_change) label = ttk.Label(frame, text="Editor") - label.grid(row=1, column=0, pady=PADY, padx=PADX, sticky="w") + label.grid(row=1, column=0, pady=PADY, padx=PADX, sticky=tk.W) combobox = ttk.Combobox( frame, textvariable=self.editor, values=appconfig.EDITORS, state="readonly" ) - combobox.grid(row=1, column=1, sticky="ew") + combobox.grid(row=1, column=1, sticky=tk.EW) label = ttk.Label(frame, text="Terminal") - label.grid(row=2, column=0, pady=PADY, padx=PADX, sticky="w") + label.grid(row=2, column=0, pady=PADY, padx=PADX, sticky=tk.W) terminals = sorted(appconfig.TERMINALS.values()) combobox = ttk.Combobox(frame, textvariable=self.terminal, values=terminals) - combobox.grid(row=2, column=1, sticky="ew") + combobox.grid(row=2, column=1, sticky=tk.EW) label = ttk.Label(frame, text="3D GUI") - label.grid(row=3, column=0, pady=PADY, padx=PADX, sticky="w") + label.grid(row=3, column=0, pady=PADY, padx=PADX, sticky=tk.W) entry = ttk.Entry(frame, textvariable=self.gui3d) - entry.grid(row=3, column=1, sticky="ew") + entry.grid(row=3, column=1, sticky=tk.EW) label = ttk.Label(frame, text="Scaling") - label.grid(row=4, column=0, pady=PADY, padx=PADX, sticky="w") + label.grid(row=4, column=0, pady=PADY, padx=PADX, sticky=tk.W) scale_frame = ttk.Frame(frame) - scale_frame.grid(row=4, column=1, sticky="ew") + scale_frame.grid(row=4, column=1, sticky=tk.EW) scale_frame.columnconfigure(0, weight=1) scale = ttk.Scale( scale_frame, @@ -79,7 +79,7 @@ class PreferencesDialog(Dialog): orient=tk.HORIZONTAL, variable=self.gui_scale, ) - scale.grid(row=0, column=0, sticky="ew") + scale.grid(row=0, column=0, sticky=tk.EW) entry = validation.AppScaleEntry( scale_frame, textvariable=self.gui_scale, width=4 ) @@ -90,15 +90,15 @@ class PreferencesDialog(Dialog): def draw_buttons(self) -> None: frame = ttk.Frame(self.top) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) for i in range(2): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Save", command=self.click_save) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def theme_change(self, event: tk.Event) -> None: theme = self.theme.get() diff --git a/daemon/core/gui/dialogs/runtool.py b/daemon/core/gui/dialogs/runtool.py index e36c4c9a..45e21182 100644 --- a/daemon/core/gui/dialogs/runtool.py +++ b/daemon/core/gui/dialogs/runtool.py @@ -38,56 +38,56 @@ class RunToolDialog(Dialog): def draw_command_frame(self) -> None: # the main frame frame = ttk.Frame(self.top) - frame.grid(row=0, column=0, sticky="nsew", padx=PADX) + frame.grid(row=0, column=0, sticky=tk.NSEW, padx=PADX) frame.columnconfigure(0, weight=1) frame.rowconfigure(1, weight=1) labeled_frame = ttk.LabelFrame(frame, text="Command", padding=FRAME_PAD) - labeled_frame.grid(sticky="ew", pady=PADY) + labeled_frame.grid(sticky=tk.EW, pady=PADY) labeled_frame.rowconfigure(0, weight=1) labeled_frame.columnconfigure(0, weight=1) entry = ttk.Entry(labeled_frame, textvariable=self.cmd) - entry.grid(sticky="ew") + entry.grid(sticky=tk.EW) # results frame labeled_frame = ttk.LabelFrame(frame, text="Output", padding=FRAME_PAD) - labeled_frame.grid(sticky="nsew", pady=PADY) + labeled_frame.grid(sticky=tk.NSEW, pady=PADY) labeled_frame.columnconfigure(0, weight=1) labeled_frame.rowconfigure(0, weight=1) self.result = CodeText(labeled_frame) self.result.text.config(state=tk.DISABLED, height=15) - self.result.grid(sticky="nsew", pady=PADY) + self.result.grid(sticky=tk.NSEW, pady=PADY) button_frame = ttk.Frame(labeled_frame) - button_frame.grid(sticky="nsew") + button_frame.grid(sticky=tk.NSEW) button_frame.columnconfigure(0, weight=1) button_frame.columnconfigure(1, weight=1) button = ttk.Button(button_frame, text="Run", command=self.click_run) - button.grid(sticky="ew", padx=PADX) + button.grid(sticky=tk.EW, padx=PADX) button = ttk.Button(button_frame, text="Close", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def draw_nodes_frame(self) -> None: labeled_frame = ttk.LabelFrame(self.top, text="Nodes", padding=FRAME_PAD) - labeled_frame.grid(row=0, column=1, sticky="nsew") + labeled_frame.grid(row=0, column=1, sticky=tk.NSEW) labeled_frame.columnconfigure(0, weight=1) labeled_frame.rowconfigure(0, weight=1) self.node_list = ListboxScroll(labeled_frame) self.node_list.listbox.config(selectmode=tk.MULTIPLE) - self.node_list.grid(sticky="nsew", pady=PADY) + self.node_list.grid(sticky=tk.NSEW, pady=PADY) for n in sorted(self.executable_nodes.keys()): self.node_list.listbox.insert(tk.END, n) button_frame = ttk.Frame(labeled_frame, padding=FRAME_PAD) - button_frame.grid(sticky="nsew") + button_frame.grid(sticky=tk.NSEW) button_frame.columnconfigure(0, weight=1) button_frame.columnconfigure(1, weight=1) button = ttk.Button(button_frame, text="All", command=self.click_all) - button.grid(sticky="nsew", padx=PADX) + button.grid(sticky=tk.NSEW, padx=PADX) button = ttk.Button(button_frame, text="None", command=self.click_none) - button.grid(row=0, column=1, sticky="nsew") + button.grid(row=0, column=1, sticky=tk.NSEW) def click_all(self) -> None: self.node_list.listbox.selection_set(0, self.node_list.listbox.size() - 1) diff --git a/daemon/core/gui/dialogs/servers.py b/daemon/core/gui/dialogs/servers.py index 45121a20..38efad22 100644 --- a/daemon/core/gui/dialogs/servers.py +++ b/daemon/core/gui/dialogs/servers.py @@ -37,12 +37,12 @@ class ServersDialog(Dialog): def draw_servers(self) -> None: listbox_scroll = ListboxScroll(self.top) - listbox_scroll.grid(pady=PADY, sticky="nsew") + listbox_scroll.grid(pady=PADY, sticky=tk.NSEW) listbox_scroll.columnconfigure(0, weight=1) listbox_scroll.rowconfigure(0, weight=1) self.servers = listbox_scroll.listbox - self.servers.grid(row=0, column=0, sticky="nsew") + self.servers.grid(row=0, column=0, sticky=tk.NSEW) self.servers.bind("<>", self.handle_server_change) for server in self.app.core.servers: @@ -50,52 +50,52 @@ class ServersDialog(Dialog): def draw_server_configuration(self) -> None: frame = ttk.LabelFrame(self.top, text="Server Configuration", padding=FRAME_PAD) - frame.grid(pady=PADY, sticky="ew") + frame.grid(pady=PADY, sticky=tk.EW) frame.columnconfigure(1, weight=1) frame.columnconfigure(3, weight=1) label = ttk.Label(frame, text="Name") - label.grid(row=0, column=0, sticky="w", padx=PADX) + label.grid(row=0, column=0, sticky=tk.W, padx=PADX) entry = ttk.Entry(frame, textvariable=self.name) - entry.grid(row=0, column=1, sticky="ew") + entry.grid(row=0, column=1, sticky=tk.EW) label = ttk.Label(frame, text="Address") - label.grid(row=0, column=2, sticky="w", padx=PADX) + label.grid(row=0, column=2, sticky=tk.W, padx=PADX) entry = ttk.Entry(frame, textvariable=self.address) - entry.grid(row=0, column=3, sticky="ew") + entry.grid(row=0, column=3, sticky=tk.EW) def draw_servers_buttons(self) -> None: frame = ttk.Frame(self.top) - frame.grid(pady=PADY, sticky="ew") + frame.grid(pady=PADY, sticky=tk.EW) for i in range(3): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Create", command=self.click_create) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) self.save_button = ttk.Button( frame, text="Save", state=tk.DISABLED, command=self.click_save ) - self.save_button.grid(row=0, column=1, sticky="ew", padx=PADX) + self.save_button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) self.delete_button = ttk.Button( frame, text="Delete", state=tk.DISABLED, command=self.click_delete ) - self.delete_button.grid(row=0, column=2, sticky="ew") + self.delete_button.grid(row=0, column=2, sticky=tk.EW) def draw_apply_buttons(self) -> None: frame = ttk.Frame(self.top) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) for i in range(2): frame.columnconfigure(i, weight=1) button = ttk.Button( frame, text="Save Configuration", command=self.click_save_configuration ) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def click_save_configuration(self): self.app.guiconfig.servers.clear() diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index 13be0bcd..a22b1afd 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -119,16 +119,16 @@ class ServiceConfigDialog(Dialog): # draw metadata frame = ttk.Frame(self.top) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) frame.columnconfigure(1, weight=1) label = ttk.Label(frame, text="Meta-data") - label.grid(row=0, column=0, sticky="w", padx=PADX) + label.grid(row=0, column=0, sticky=tk.W, padx=PADX) self.metadata_entry = ttk.Entry(frame, textvariable=self.metadata) - self.metadata_entry.grid(row=0, column=1, sticky="ew") + self.metadata_entry.grid(row=0, column=1, sticky=tk.EW) # draw notebook self.notebook = ttk.Notebook(self.top) - self.notebook.grid(sticky="nsew", pady=PADY) + self.notebook.grid(sticky=tk.NSEW, pady=PADY) self.draw_tab_files() self.draw_tab_directories() self.draw_tab_startstop() @@ -138,7 +138,7 @@ class ServiceConfigDialog(Dialog): def draw_tab_files(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) - tab.grid(sticky="nsew") + tab.grid(sticky=tk.NSEW) tab.columnconfigure(0, weight=1) self.notebook.add(tab, text="Files") @@ -148,15 +148,15 @@ class ServiceConfigDialog(Dialog): label.grid() frame = ttk.Frame(tab) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) frame.columnconfigure(1, weight=1) label = ttk.Label(frame, text="File Name") - label.grid(row=0, column=0, padx=PADX, sticky="w") + label.grid(row=0, column=0, padx=PADX, sticky=tk.W) 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) + self.filename_combobox.grid(row=0, column=1, sticky=tk.EW, padx=PADX) button = ttk.Button( frame, image=self.documentnew_img, command=self.add_filename ) @@ -167,7 +167,7 @@ class ServiceConfigDialog(Dialog): button.grid(row=0, column=3) frame = ttk.Frame(tab) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) frame.columnconfigure(1, weight=1) button = ttk.Radiobutton( frame, @@ -176,16 +176,16 @@ class ServiceConfigDialog(Dialog): value=1, state=tk.DISABLED, ) - button.grid(row=0, column=0, sticky="w", padx=PADX) + button.grid(row=0, column=0, sticky=tk.W, padx=PADX) entry = ttk.Entry(frame, state=tk.DISABLED) - entry.grid(row=0, column=1, sticky="ew", padx=PADX) + entry.grid(row=0, column=1, sticky=tk.EW, padx=PADX) image = Images.get(ImageEnum.FILEOPEN, 16) button = ttk.Button(frame, image=image) button.image = image button.grid(row=0, column=2) frame = ttk.Frame(tab) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) frame.columnconfigure(0, weight=1) button = ttk.Radiobutton( frame, @@ -193,7 +193,7 @@ class ServiceConfigDialog(Dialog): text="Use text below for file contents", value=2, ) - button.grid(row=0, column=0, sticky="ew") + button.grid(row=0, column=0, sticky=tk.EW) image = Images.get(ImageEnum.FILEOPEN, 16) button = ttk.Button(frame, image=image) button.image = image @@ -204,7 +204,7 @@ class ServiceConfigDialog(Dialog): button.grid(row=0, column=2) self.service_file_data = CodeText(tab) - self.service_file_data.grid(sticky="nsew") + self.service_file_data.grid(sticky=tk.NSEW) tab.rowconfigure(self.service_file_data.grid_info()["row"], weight=1) if len(self.filenames) > 0: self.filename_combobox.current(0) @@ -218,7 +218,7 @@ class ServiceConfigDialog(Dialog): def draw_tab_directories(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) - tab.grid(sticky="nsew") + tab.grid(sticky=tk.NSEW) tab.columnconfigure(0, weight=1) tab.rowconfigure(2, weight=1) self.notebook.add(tab, text="Directories") @@ -227,33 +227,33 @@ class ServiceConfigDialog(Dialog): tab, text="Directories required by this service that are unique for each node.", ) - label.grid(row=0, column=0, sticky="ew") + label.grid(row=0, column=0, sticky=tk.EW) frame = ttk.Frame(tab, padding=FRAME_PAD) frame.columnconfigure(0, weight=1) - frame.grid(row=1, column=0, sticky="nsew") + frame.grid(row=1, column=0, sticky=tk.NSEW) var = tk.StringVar(value="") self.directory_entry = ttk.Entry(frame, textvariable=var) - self.directory_entry.grid(row=0, column=0, sticky="ew", padx=PADX) + self.directory_entry.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="...", command=self.find_directory_button) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) self.dir_list = ListboxScroll(tab) - self.dir_list.grid(row=2, column=0, sticky="nsew", pady=PADY) + self.dir_list.grid(row=2, column=0, sticky=tk.NSEW, pady=PADY) 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.grid(row=3, column=0, sticky=tk.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", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Remove", command=self.remove_directory) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def draw_tab_startstop(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) - tab.grid(sticky="nsew") + tab.grid(sticky=tk.NSEW) tab.columnconfigure(0, weight=1) for i in range(3): tab.rowconfigure(i, weight=1) @@ -279,25 +279,25 @@ class ServiceConfigDialog(Dialog): commands = self.validation_commands label_frame.columnconfigure(0, weight=1) label_frame.rowconfigure(1, weight=1) - label_frame.grid(row=i, column=0, sticky="nsew", pady=PADY) + label_frame.grid(row=i, column=0, sticky=tk.NSEW, pady=PADY) frame = ttk.Frame(label_frame) - frame.grid(row=0, column=0, sticky="nsew", pady=PADY) + frame.grid(row=0, column=0, sticky=tk.NSEW, pady=PADY) frame.columnconfigure(0, weight=1) entry = ttk.Entry(frame, textvariable=tk.StringVar()) entry.grid(row=0, column=0, stick="ew", padx=PADX) button = ttk.Button(frame, image=self.documentnew_img) button.bind("", self.add_command) - button.grid(row=0, column=1, sticky="ew", padx=PADX) + button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, image=self.editdelete_img) - button.grid(row=0, column=2, sticky="ew") + button.grid(row=0, column=2, sticky=tk.EW) button.bind("", self.delete_command) listbox_scroll = ListboxScroll(label_frame) listbox_scroll.listbox.bind("<>", self.update_entry) for command in commands: listbox_scroll.listbox.insert("end", command) listbox_scroll.listbox.config(height=4) - listbox_scroll.grid(row=1, column=0, sticky="nsew") + listbox_scroll.grid(row=1, column=0, sticky=tk.NSEW) if i == 0: self.startup_commands_listbox = listbox_scroll.listbox elif i == 1: @@ -307,23 +307,23 @@ class ServiceConfigDialog(Dialog): def draw_tab_configuration(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) - tab.grid(sticky="nsew") + tab.grid(sticky=tk.NSEW) tab.columnconfigure(0, weight=1) - self.notebook.add(tab, text="Configuration", sticky="nsew") + self.notebook.add(tab, text="Configuration", sticky=tk.NSEW) frame = ttk.Frame(tab) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) frame.columnconfigure(1, weight=1) label = ttk.Label(frame, text="Validation Time") - label.grid(row=0, column=0, sticky="w", padx=PADX) + label.grid(row=0, column=0, sticky=tk.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", pady=PADY) + self.validation_time_entry.grid(row=0, column=1, sticky=tk.EW, pady=PADY) label = ttk.Label(frame, text="Validation Mode") - label.grid(row=1, column=0, sticky="w", padx=PADX) + label.grid(row=1, column=0, sticky=tk.W, padx=PADX) if self.validation_mode == ServiceValidationMode.BLOCKING: mode = "BLOCKING" elif self.validation_mode == ServiceValidationMode.NON_BLOCKING: @@ -335,48 +335,48 @@ class ServiceConfigDialog(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", pady=PADY) + self.validation_mode_entry.grid(row=1, column=1, sticky=tk.EW, pady=PADY) label = ttk.Label(frame, text="Validation Period") - label.grid(row=2, column=0, sticky="w", padx=PADX) + label.grid(row=2, column=0, sticky=tk.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", pady=PADY) + self.validation_period_entry.grid(row=2, column=1, sticky=tk.EW, pady=PADY) label_frame = ttk.LabelFrame(tab, text="Executables", padding=FRAME_PAD) - label_frame.grid(sticky="nsew", pady=PADY) + label_frame.grid(sticky=tk.NSEW, pady=PADY) label_frame.columnconfigure(0, weight=1) label_frame.rowconfigure(0, weight=1) listbox_scroll = ListboxScroll(label_frame) - listbox_scroll.grid(sticky="nsew") + listbox_scroll.grid(sticky=tk.NSEW) tab.rowconfigure(listbox_scroll.grid_info()["row"], weight=1) for executable in self.executables: listbox_scroll.listbox.insert("end", executable) label_frame = ttk.LabelFrame(tab, text="Dependencies", padding=FRAME_PAD) - label_frame.grid(sticky="nsew", pady=PADY) + label_frame.grid(sticky=tk.NSEW, pady=PADY) label_frame.columnconfigure(0, weight=1) label_frame.rowconfigure(0, weight=1) listbox_scroll = ListboxScroll(label_frame) - listbox_scroll.grid(sticky="nsew") + listbox_scroll.grid(sticky=tk.NSEW) tab.rowconfigure(listbox_scroll.grid_info()["row"], weight=1) for dependency in self.dependencies: listbox_scroll.listbox.insert("end", dependency) def draw_buttons(self) -> None: frame = ttk.Frame(self.top) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) for i in range(4): 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) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Defaults", command=self.click_defaults) - button.grid(row=0, column=1, sticky="ew", padx=PADX) + button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Copy...", command=self.click_copy) - button.grid(row=0, column=2, sticky="ew", padx=PADX) + button.grid(row=0, column=2, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=3, sticky="ew") + button.grid(row=0, column=3, sticky=tk.EW) def add_filename(self) -> None: filename = self.filename_combobox.get() diff --git a/daemon/core/gui/dialogs/sessionoptions.py b/daemon/core/gui/dialogs/sessionoptions.py index 570bfbde..e9b032e0 100644 --- a/daemon/core/gui/dialogs/sessionoptions.py +++ b/daemon/core/gui/dialogs/sessionoptions.py @@ -39,17 +39,17 @@ class SessionOptionsDialog(Dialog): self.top.rowconfigure(0, weight=1) self.config_frame = ConfigFrame(self.top, self.app, self.config, self.enabled) self.config_frame.draw_config() - self.config_frame.grid(sticky="nsew", pady=PADY) + self.config_frame.grid(sticky=tk.NSEW, pady=PADY) frame = ttk.Frame(self.top) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) for i in range(2): frame.columnconfigure(i, weight=1) state = tk.NORMAL if self.enabled else tk.DISABLED button = ttk.Button(frame, text="Save", command=self.save, state=state) - button.grid(row=0, column=0, padx=PADX, sticky="ew") + button.grid(row=0, column=0, padx=PADX, sticky=tk.EW) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def save(self) -> None: config = self.config_frame.parse_config() diff --git a/daemon/core/gui/dialogs/sessions.py b/daemon/core/gui/dialogs/sessions.py index 83e4001a..4c9ae0ca 100644 --- a/daemon/core/gui/dialogs/sessions.py +++ b/daemon/core/gui/dialogs/sessions.py @@ -62,7 +62,7 @@ class SessionsDialog(Dialog): frame = ttk.Frame(self.top) frame.columnconfigure(0, weight=1) frame.rowconfigure(0, weight=1) - frame.grid(sticky="nsew", pady=PADY) + frame.grid(sticky=tk.NSEW, pady=PADY) self.tree = ttk.Treeview( frame, columns=("id", "state", "nodes"), @@ -72,7 +72,7 @@ class SessionsDialog(Dialog): style = ttk.Style() heading_size = int(self.app.guiconfig.scale * 10) style.configure("Treeview.Heading", font=(None, heading_size, "bold")) - self.tree.grid(sticky="nsew") + self.tree.grid(sticky=tk.NSEW) self.tree.column("id", stretch=tk.YES, anchor="center") self.tree.heading("id", text="ID") self.tree.column("state", stretch=tk.YES, anchor="center") @@ -92,25 +92,25 @@ class SessionsDialog(Dialog): self.tree.bind("<>", self.click_select) yscrollbar = ttk.Scrollbar(frame, orient="vertical", command=self.tree.yview) - yscrollbar.grid(row=0, column=1, sticky="ns") + yscrollbar.grid(row=0, column=1, sticky=tk.NS) self.tree.configure(yscrollcommand=yscrollbar.set) xscrollbar = ttk.Scrollbar(frame, orient="horizontal", command=self.tree.xview) - xscrollbar.grid(row=1, sticky="ew") + xscrollbar.grid(row=1, sticky=tk.EW) self.tree.configure(xscrollcommand=xscrollbar.set) def draw_buttons(self) -> None: frame = ttk.Frame(self.top) for i in range(4): frame.columnconfigure(i, weight=1) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) image = Images.get(ImageEnum.DOCUMENTNEW, 16) b = ttk.Button( frame, image=image, text="New", compound=tk.LEFT, command=self.click_new ) b.image = image - b.grid(row=0, padx=PADX, sticky="ew") + b.grid(row=0, padx=PADX, sticky=tk.EW) image = Images.get(ImageEnum.FILEOPEN, 16) self.connect_button = ttk.Button( @@ -122,7 +122,7 @@ class SessionsDialog(Dialog): state=tk.DISABLED, ) self.connect_button.image = image - self.connect_button.grid(row=0, column=1, padx=PADX, sticky="ew") + self.connect_button.grid(row=0, column=1, padx=PADX, sticky=tk.EW) image = Images.get(ImageEnum.DELETE, 16) self.delete_button = ttk.Button( @@ -134,7 +134,7 @@ class SessionsDialog(Dialog): state=tk.DISABLED, ) self.delete_button.image = image - self.delete_button.grid(row=0, column=2, padx=PADX, sticky="ew") + self.delete_button.grid(row=0, column=2, padx=PADX, sticky=tk.EW) image = Images.get(ImageEnum.CANCEL, 16) if self.is_start_app: @@ -154,7 +154,7 @@ class SessionsDialog(Dialog): command=self.destroy, ) b.image = image - b.grid(row=0, column=3, sticky="ew") + b.grid(row=0, column=3, sticky=tk.EW) def click_new(self) -> None: self.app.core.create_new_session() diff --git a/daemon/core/gui/dialogs/shapemod.py b/daemon/core/gui/dialogs/shapemod.py index 2ca06772..255092ec 100644 --- a/daemon/core/gui/dialogs/shapemod.py +++ b/daemon/core/gui/dialogs/shapemod.py @@ -57,15 +57,15 @@ class ShapeDialog(Dialog): def draw_label_options(self) -> None: label_frame = ttk.LabelFrame(self.top, text="Label", padding=FRAME_PAD) - label_frame.grid(sticky="ew") + label_frame.grid(sticky=tk.EW) label_frame.columnconfigure(0, weight=1) entry = ttk.Entry(label_frame, textvariable=self.shape_text) - entry.grid(sticky="ew", pady=PADY) + entry.grid(sticky=tk.EW, pady=PADY) # font options frame = ttk.Frame(label_frame) - frame.grid(sticky="nsew", pady=PADY) + frame.grid(sticky=tk.NSEW, pady=PADY) frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) frame.columnconfigure(2, weight=1) @@ -75,70 +75,70 @@ class ShapeDialog(Dialog): values=sorted(font.families()), state="readonly", ) - combobox.grid(row=0, column=0, sticky="nsew") + combobox.grid(row=0, column=0, sticky=tk.NSEW) combobox = ttk.Combobox( frame, textvariable=self.font_size, values=FONT_SIZES, state="readonly" ) - combobox.grid(row=0, column=1, padx=PADX, sticky="nsew") + combobox.grid(row=0, column=1, padx=PADX, sticky=tk.NSEW) button = ttk.Button(frame, text="Color", command=self.choose_text_color) - button.grid(row=0, column=2, sticky="nsew") + button.grid(row=0, column=2, sticky=tk.NSEW) # style options frame = ttk.Frame(label_frame) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) for i in range(3): frame.columnconfigure(i, weight=1) button = ttk.Checkbutton(frame, variable=self.bold, text="Bold") - button.grid(row=0, column=0, sticky="ew") + button.grid(row=0, column=0, sticky=tk.EW) button = ttk.Checkbutton(frame, variable=self.italic, text="Italic") - button.grid(row=0, column=1, padx=PADX, sticky="ew") + button.grid(row=0, column=1, padx=PADX, sticky=tk.EW) button = ttk.Checkbutton(frame, variable=self.underline, text="Underline") - button.grid(row=0, column=2, sticky="ew") + button.grid(row=0, column=2, sticky=tk.EW) def draw_shape_options(self) -> None: label_frame = ttk.LabelFrame(self.top, text="Shape", padding=FRAME_PAD) - label_frame.grid(sticky="ew", pady=PADY) + label_frame.grid(sticky=tk.EW, pady=PADY) label_frame.columnconfigure(0, weight=1) frame = ttk.Frame(label_frame) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) for i in range(1, 3): frame.columnconfigure(i, weight=1) label = ttk.Label(frame, text="Fill Color") - label.grid(row=0, column=0, padx=PADX, sticky="w") + label.grid(row=0, column=0, padx=PADX, sticky=tk.W) self.fill = ttk.Label(frame, text=self.fill_color, background=self.fill_color) - self.fill.grid(row=0, column=1, sticky="ew", padx=PADX) + self.fill.grid(row=0, column=1, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Color", command=self.choose_fill_color) - button.grid(row=0, column=2, sticky="ew") + button.grid(row=0, column=2, sticky=tk.EW) label = ttk.Label(frame, text="Border Color") - label.grid(row=1, column=0, sticky="w", padx=PADX) + label.grid(row=1, column=0, sticky=tk.W, padx=PADX) self.border = ttk.Label( frame, text=self.border_color, background=self.border_color ) - self.border.grid(row=1, column=1, sticky="ew", padx=PADX) + self.border.grid(row=1, column=1, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Color", command=self.choose_border_color) - button.grid(row=1, column=2, sticky="ew") + button.grid(row=1, column=2, sticky=tk.EW) frame = ttk.Frame(label_frame) - frame.grid(sticky="ew", pady=PADY) + frame.grid(sticky=tk.EW, pady=PADY) frame.columnconfigure(1, weight=1) label = ttk.Label(frame, text="Border Width") - label.grid(row=0, column=0, sticky="w", padx=PADX) + label.grid(row=0, column=0, sticky=tk.W, padx=PADX) combobox = ttk.Combobox( frame, textvariable=self.border_width, values=BORDER_WIDTH, state="readonly" ) - combobox.grid(row=0, column=1, sticky="nsew") + combobox.grid(row=0, column=1, sticky=tk.NSEW) def draw_buttons(self) -> None: frame = ttk.Frame(self.top) - frame.grid(sticky="nsew") + frame.grid(sticky=tk.NSEW) frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) button = ttk.Button(frame, text="Add shape", command=self.click_add) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.cancel) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def choose_text_color(self) -> None: color_picker = ColorPickerDialog(self, self.app, self.text_color) diff --git a/daemon/core/gui/dialogs/throughput.py b/daemon/core/gui/dialogs/throughput.py index 5b3cc9b3..0b59a6ac 100644 --- a/daemon/core/gui/dialogs/throughput.py +++ b/daemon/core/gui/dialogs/throughput.py @@ -37,25 +37,25 @@ class ThroughputDialog(Dialog): variable=self.show_throughput, text="Show Throughput Level On Every Link", ) - button.grid(sticky="ew") + button.grid(sticky=tk.EW) button = ttk.Checkbutton( self.top, variable=self.exponential_weight, text="Use Exponential Weighted Moving Average", ) - button.grid(sticky="ew") + button.grid(sticky=tk.EW) button = ttk.Checkbutton( self.top, variable=self.transmission, text="Include Transmissions" ) - button.grid(sticky="ew") + button.grid(sticky=tk.EW) button = ttk.Checkbutton( self.top, variable=self.reception, text="Include Receptions" ) - button.grid(sticky="ew") + button.grid(sticky=tk.EW) label_frame = ttk.LabelFrame(self.top, text="Link Highlight", padding=FRAME_PAD) label_frame.columnconfigure(0, weight=1) - label_frame.grid(sticky="ew") + label_frame.grid(sticky=tk.EW) scale = ttk.Scale( label_frame, @@ -65,21 +65,21 @@ class ThroughputDialog(Dialog): orient=tk.HORIZONTAL, variable=self.threshold, ) - scale.grid(sticky="ew", pady=PADY) + scale.grid(sticky=tk.EW, pady=PADY) frame = ttk.Frame(label_frame) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) frame.columnconfigure(1, weight=1) label = ttk.Label(frame, text="Threshold Kbps (0 disabled)") - label.grid(row=0, column=0, sticky="ew", padx=PADX) + label.grid(row=0, column=0, sticky=tk.EW, padx=PADX) entry = ttk.Entry(frame, textvariable=self.threshold) - entry.grid(row=0, column=1, sticky="ew", pady=PADY) + entry.grid(row=0, column=1, sticky=tk.EW, pady=PADY) label = ttk.Label(frame, text="Width") - label.grid(row=1, column=0, sticky="ew", padx=PADX) + label.grid(row=1, column=0, sticky=tk.EW, padx=PADX) entry = ttk.Entry(frame, textvariable=self.width) - entry.grid(row=1, column=1, sticky="ew", pady=PADY) + entry.grid(row=1, column=1, sticky=tk.EW, pady=PADY) label = ttk.Label(frame, text="Color") - label.grid(row=2, column=0, sticky="ew", padx=PADX) + label.grid(row=2, column=0, sticky=tk.EW, padx=PADX) self.color_button = tk.Button( frame, text=self.color, @@ -87,18 +87,18 @@ class ThroughputDialog(Dialog): bg=self.color, highlightthickness=0, ) - self.color_button.grid(row=2, column=1, sticky="ew") + self.color_button.grid(row=2, column=1, sticky=tk.EW) self.draw_spacer() frame = ttk.Frame(self.top) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) for i in range(2): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Save", command=self.click_save) - button.grid(row=0, column=0, sticky="ew", padx=PADX) + button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def click_color(self) -> None: color_picker = ColorPickerDialog(self, self.app, self.color) diff --git a/daemon/core/gui/dialogs/wlanconfig.py b/daemon/core/gui/dialogs/wlanconfig.py index d4595556..283a96cd 100644 --- a/daemon/core/gui/dialogs/wlanconfig.py +++ b/daemon/core/gui/dialogs/wlanconfig.py @@ -1,3 +1,4 @@ +import tkinter as tk from tkinter import ttk from typing import TYPE_CHECKING, Dict, Optional @@ -54,7 +55,7 @@ class WlanConfigDialog(Dialog): self.top.rowconfigure(0, weight=1) self.config_frame = ConfigFrame(self.top, self.app, self.config) self.config_frame.draw_config() - self.config_frame.grid(sticky="nsew", pady=PADY) + self.config_frame.grid(sticky=tk.NSEW, pady=PADY) self.draw_apply_buttons() self.top.bind("", self.remove_ranges) @@ -63,7 +64,7 @@ class WlanConfigDialog(Dialog): create node configuration options """ frame = ttk.Frame(self.top) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) for i in range(2): frame.columnconfigure(i, weight=1) @@ -73,10 +74,10 @@ class WlanConfigDialog(Dialog): self.range_entry.config(validatecommand=(self.positive_int, "%P")) button = ttk.Button(frame, text="Apply", command=self.click_apply) - button.grid(row=0, column=0, padx=PADX, sticky="ew") + button.grid(row=0, column=0, padx=PADX, sticky=tk.EW) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky=tk.EW) def click_apply(self) -> None: """ diff --git a/daemon/core/gui/frames/link.py b/daemon/core/gui/frames/link.py index 093f39eb..339c39f0 100644 --- a/daemon/core/gui/frames/link.py +++ b/daemon/core/gui/frames/link.py @@ -38,7 +38,7 @@ class EdgeInfoFrame(InfoFrameBase): dst_node = self.app.core.session.nodes[link.node2_id] frame = DetailsFrame(self) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) frame.add_detail("Source", src_node.name) iface1 = link.iface1 if iface1: @@ -90,7 +90,7 @@ class WirelessEdgeInfoFrame(InfoFrameBase): iface2 = get_iface(dst_canvas_node, net_id) frame = DetailsFrame(self) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) frame.add_detail("Source", src_node.name) if iface1: mac = iface1.mac if iface1.mac else "auto" diff --git a/daemon/core/gui/frames/node.py b/daemon/core/gui/frames/node.py index 577cc489..394ecd85 100644 --- a/daemon/core/gui/frames/node.py +++ b/daemon/core/gui/frames/node.py @@ -1,3 +1,4 @@ +import tkinter as tk from typing import TYPE_CHECKING from core.gui.frames.base import DetailsFrame, InfoFrameBase @@ -18,7 +19,7 @@ class NodeInfoFrame(InfoFrameBase): self.columnconfigure(0, weight=1) node = self.canvas_node.core_node frame = DetailsFrame(self) - frame.grid(sticky="ew") + frame.grid(sticky=tk.EW) frame.add_detail("ID", node.id) frame.add_detail("Name", node.name) if NodeUtils.is_model_node(node.type): diff --git a/daemon/core/gui/statusbar.py b/daemon/core/gui/statusbar.py index d4304b6e..518a82f9 100644 --- a/daemon/core/gui/statusbar.py +++ b/daemon/core/gui/statusbar.py @@ -34,7 +34,7 @@ class StatusBar(ttk.Frame): self.columnconfigure(3, weight=1) frame = ttk.Frame(self, borderwidth=1, relief=tk.RIDGE) - frame.grid(row=0, column=0, sticky="ew") + frame.grid(row=0, column=0, sticky=tk.EW) frame.columnconfigure(0, weight=1) self.status = ttk.Label( @@ -44,22 +44,22 @@ class StatusBar(ttk.Frame): borderwidth=1, relief=tk.RIDGE, ) - self.status.grid(row=0, column=0, sticky="ew") + self.status.grid(row=0, column=0, sticky=tk.EW) self.zoom = ttk.Label(self, anchor=tk.CENTER, borderwidth=1, relief=tk.RIDGE) - self.zoom.grid(row=0, column=1, sticky="ew") + self.zoom.grid(row=0, column=1, sticky=tk.EW) self.set_zoom(self.app.canvas.ratio) self.cpu_label = ttk.Label( self, anchor=tk.CENTER, borderwidth=1, relief=tk.RIDGE ) - self.cpu_label.grid(row=0, column=2, sticky="ew") + self.cpu_label.grid(row=0, column=2, sticky=tk.EW) self.set_cpu(0.0) self.alerts_button = ttk.Button( self, text="Alerts", command=self.click_alerts, style=self.alert_style ) - self.alerts_button.grid(row=0, column=3, sticky="ew") + self.alerts_button.grid(row=0, column=3, sticky=tk.EW) def set_cpu(self, usage: float) -> None: self.cpu_label.config(text=f"CPU {usage * 100:.2f}%") diff --git a/daemon/core/gui/task.py b/daemon/core/gui/task.py index f56fd54b..b2ab9765 100644 --- a/daemon/core/gui/task.py +++ b/daemon/core/gui/task.py @@ -1,6 +1,7 @@ import logging import threading import time +import tkinter as tk from typing import TYPE_CHECKING, Any, Callable, Optional, Tuple if TYPE_CHECKING: @@ -26,7 +27,7 @@ class ProgressTask: self.time: Optional[float] = None def start(self) -> None: - self.app.progress.grid(sticky="ew", columnspan=2) + self.app.progress.grid(sticky=tk.EW, columnspan=2) self.app.progress.start() self.time = time.perf_counter() thread = threading.Thread(target=self.run, daemon=True) diff --git a/daemon/core/gui/toolbar.py b/daemon/core/gui/toolbar.py index b7b67338..1f5589ba 100644 --- a/daemon/core/gui/toolbar.py +++ b/daemon/core/gui/toolbar.py @@ -95,7 +95,7 @@ class ButtonBar(ttk.Frame): image = self.app.get_icon(image_enum, TOOLBAR_SIZE) button = ttk.Button(self, image=image, command=func) button.image = image - button.grid(sticky="ew") + button.grid(sticky=tk.EW) Tooltip(button, tooltip) if radio: self.radio_buttons.append(button) @@ -124,7 +124,7 @@ class MarkerFrame(ttk.Frame): image = self.app.get_icon(ImageEnum.DELETE, 16) button = ttk.Button(self, image=image, width=2, command=self.click_clear) button.image = image - button.grid(sticky="ew", pady=self.PAD) + button.grid(sticky=tk.EW, pady=self.PAD) Tooltip(button, "Delete Marker") sizes = [1, 3, 8, 10] @@ -132,14 +132,14 @@ class MarkerFrame(ttk.Frame): sizes = ttk.Combobox( self, state="readonly", textvariable=self.size, value=sizes, width=2 ) - sizes.grid(sticky="ew", pady=self.PAD) + sizes.grid(sticky=tk.EW, pady=self.PAD) Tooltip(sizes, "Marker Size") frame_size = TOOLBAR_SIZE self.color_frame = tk.Frame( self, background=self.color, height=frame_size, width=frame_size ) - self.color_frame.grid(sticky="ew") + self.color_frame.grid(sticky=tk.EW) self.color_frame.bind("", self.click_color) Tooltip(self.color_frame, "Marker Color") @@ -207,7 +207,7 @@ class Toolbar(ttk.Frame): def draw_design_frame(self) -> None: self.design_frame = ButtonBar(self, self.app) - self.design_frame.grid(row=0, column=0, sticky="nsew") + self.design_frame.grid(row=0, column=0, sticky=tk.NSEW) self.design_frame.columnconfigure(0, weight=1) self.play_button = self.design_frame.create_button( ImageEnum.START, self.click_start, "Start Session" @@ -239,7 +239,7 @@ class Toolbar(ttk.Frame): def draw_runtime_frame(self) -> None: self.runtime_frame = ButtonBar(self, self.app) - self.runtime_frame.grid(row=0, column=0, sticky="nsew") + self.runtime_frame.grid(row=0, column=0, sticky=tk.NSEW) self.runtime_frame.columnconfigure(0, weight=1) self.stop_button = self.runtime_frame.create_button( ImageEnum.STOP, self.click_stop, "Stop Session" @@ -387,7 +387,7 @@ class Toolbar(ttk.Frame): self.runtime_frame, image=image, direction=tk.RIGHT ) menu_button.image = image - menu_button.grid(sticky="ew") + menu_button.grid(sticky=tk.EW) self.observers_menu = ObserversMenu(menu_button, self.app) menu_button["menu"] = self.observers_menu diff --git a/daemon/core/gui/tooltip.py b/daemon/core/gui/tooltip.py index c2978510..84a3178f 100644 --- a/daemon/core/gui/tooltip.py +++ b/daemon/core/gui/tooltip.py @@ -46,7 +46,7 @@ class Tooltip(object): self.tw.rowconfigure(0, weight=1) self.tw.columnconfigure(0, weight=1) frame = ttk.Frame(self.tw, style=Styles.tooltip_frame, padding=3) - frame.grid(sticky="nsew") + frame.grid(sticky=tk.NSEW) label = ttk.Label(frame, text=self.text, style=Styles.tooltip) label.grid() diff --git a/daemon/core/gui/widgets.py b/daemon/core/gui/widgets.py index 0d5bff22..eff1a2a3 100644 --- a/daemon/core/gui/widgets.py +++ b/daemon/core/gui/widgets.py @@ -47,13 +47,13 @@ class FrameScroll(ttk.Frame): self.columnconfigure(0, weight=1) bg = self.app.style.lookup(".", "background") self.canvas: tk.Canvas = tk.Canvas(self, highlightthickness=0, background=bg) - self.canvas.grid(row=0, sticky="nsew", padx=2, pady=2) + self.canvas.grid(row=0, sticky=tk.NSEW, padx=2, pady=2) self.canvas.columnconfigure(0, weight=1) self.canvas.rowconfigure(0, weight=1) self.scrollbar: ttk.Scrollbar = ttk.Scrollbar( self, orient="vertical", command=self.canvas.yview ) - self.scrollbar.grid(row=0, column=1, sticky="ns") + self.scrollbar.grid(row=0, column=1, sticky=tk.NS) self.frame: ttk.Frame = _cls(self.canvas) self.frame_id: int = self.canvas.create_window( 0, 0, anchor="nw", window=self.frame @@ -108,7 +108,7 @@ class ConfigFrame(ttk.Notebook): self.add(tab, text=group_name) for index, option in enumerate(sorted(group, key=lambda x: x.name)): label = ttk.Label(tab.frame, text=option.label) - label.grid(row=index, pady=PADY, padx=PADX, sticky="w") + label.grid(row=index, pady=PADY, padx=PADX, sticky=tk.W) value = tk.StringVar() if option.type == ConfigOptionType.BOOL: select = ("On", "Off") @@ -116,7 +116,7 @@ class ConfigFrame(ttk.Notebook): combobox = ttk.Combobox( tab.frame, textvariable=value, values=select, state=state ) - combobox.grid(row=index, column=1, sticky="ew") + combobox.grid(row=index, column=1, sticky=tk.EW) if option.value == "1": value.set("On") else: @@ -128,16 +128,16 @@ class ConfigFrame(ttk.Notebook): combobox = ttk.Combobox( tab.frame, textvariable=value, values=select, state=state ) - combobox.grid(row=index, column=1, sticky="ew") + combobox.grid(row=index, column=1, sticky=tk.EW) elif option.type == ConfigOptionType.STRING: value.set(option.value) state = tk.NORMAL if self.enabled else tk.DISABLED if "file" in option.label: file_frame = ttk.Frame(tab.frame) - file_frame.grid(row=index, column=1, sticky="ew") + file_frame.grid(row=index, column=1, sticky=tk.EW) file_frame.columnconfigure(0, weight=1) entry = ttk.Entry(file_frame, textvariable=value, state=state) - entry.grid(row=0, column=0, sticky="ew", padx=PADX) + entry.grid(row=0, column=0, sticky=tk.EW, padx=PADX) func = partial(file_button_click, value, self) button = ttk.Button( file_frame, text="...", command=func, state=state @@ -145,21 +145,21 @@ class ConfigFrame(ttk.Notebook): button.grid(row=0, column=1) else: entry = ttk.Entry(tab.frame, textvariable=value, state=state) - entry.grid(row=index, column=1, sticky="ew") + entry.grid(row=index, column=1, sticky=tk.EW) elif option.type in INT_TYPES: value.set(option.value) state = tk.NORMAL if self.enabled else tk.DISABLED entry = validation.PositiveIntEntry( tab.frame, textvariable=value, state=state ) - entry.grid(row=index, column=1, sticky="ew") + entry.grid(row=index, column=1, sticky=tk.EW) elif option.type == ConfigOptionType.FLOAT: value.set(option.value) state = tk.NORMAL if self.enabled else tk.DISABLED entry = validation.PositiveFloatEntry( tab.frame, textvariable=value, state=state ) - entry.grid(row=index, column=1, sticky="ew") + entry.grid(row=index, column=1, sticky=tk.EW) else: logging.error("unhandled config option type: %s", option.type) self.values[option.name] = value @@ -196,7 +196,7 @@ class ListboxScroll(ttk.Frame): self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) self.scrollbar: ttk.Scrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL) - self.scrollbar.grid(row=0, column=1, sticky="ns") + self.scrollbar.grid(row=0, column=1, sticky=tk.NS) self.listbox: tk.Listbox = tk.Listbox( self, selectmode=tk.BROWSE, @@ -204,7 +204,7 @@ class ListboxScroll(ttk.Frame): exportselection=False, ) themes.style_listbox(self.listbox) - self.listbox.grid(row=0, column=0, sticky="nsew") + self.listbox.grid(row=0, column=0, sticky=tk.NSEW) self.scrollbar.config(command=self.listbox.yview) @@ -224,7 +224,7 @@ class CheckboxList(FrameScroll): var = tk.BooleanVar(value=checked) func = partial(self.clicked, name, var) checkbox = ttk.Checkbutton(self.frame, text=name, variable=var, command=func) - checkbox.grid(sticky="w") + checkbox.grid(sticky=tk.W) class CodeFont(font.Font): @@ -250,9 +250,9 @@ class CodeText(ttk.Frame): selectforeground="black", relief=tk.FLAT, ) - self.text.grid(row=0, column=0, sticky="nsew") + self.text.grid(row=0, column=0, sticky=tk.NSEW) yscrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.text.yview) - yscrollbar.grid(row=0, column=1, sticky="ns") + yscrollbar.grid(row=0, column=1, sticky=tk.NS) self.text.configure(yscrollcommand=yscrollbar.set) From b7e3d1c8775696da39ff7b30e4032c3ba74677d5 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sun, 2 Aug 2020 10:47:01 -0700 Subject: [PATCH 035/598] pygui: fixed emane config dialog and emane model config dialogs to expand tabs the full height of the dialog --- daemon/core/gui/dialogs/emaneconfig.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/daemon/core/gui/dialogs/emaneconfig.py b/daemon/core/gui/dialogs/emaneconfig.py index f7925d16..d47a3c0d 100644 --- a/daemon/core/gui/dialogs/emaneconfig.py +++ b/daemon/core/gui/dialogs/emaneconfig.py @@ -34,7 +34,6 @@ class GlobalEmaneDialog(Dialog): ) self.config_frame.draw_config() self.config_frame.grid(sticky=tk.NSEW, pady=PADY) - self.draw_spacer() self.draw_buttons() def draw_buttons(self) -> None: @@ -88,7 +87,6 @@ class EmaneModelDialog(Dialog): self.config_frame = ConfigFrame(self.top, self.app, self.config, self.enabled) self.config_frame.draw_config() self.config_frame.grid(sticky=tk.NSEW, pady=PADY) - self.draw_spacer() self.draw_buttons() def draw_buttons(self) -> None: From 06e43f619d5d43f62d54254e299d186225f10133 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 3 Aug 2020 10:40:48 -0700 Subject: [PATCH 036/598] install: update install complete message to avoid implying invoke is needed to run core --- tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index c3e6d2bb..38b219e6 100644 --- a/tasks.py +++ b/tasks.py @@ -312,7 +312,7 @@ def install(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX): install_service(c, hide, prefix) with p.start("installing ospf mdr"): install_ospf_mdr(c, os_info, hide) - print("\nyou may need to open a new terminal to leverage invoke for running core") + print("\ninstall complete!") @task( From f41ce8e3a67f3bb3b24649dc62aa58096612d5a1 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 3 Aug 2020 16:04:07 -0700 Subject: [PATCH 037/598] daemon: add core python environment variable to be able to refer to the virtual environment executable --- daemon/core/emulator/session.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 4127b141..fe0b07bc 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -8,6 +8,7 @@ import os import pwd import shutil import subprocess +import sys import tempfile import threading import time @@ -991,6 +992,7 @@ class Session: :return: environment variables """ env = os.environ.copy() + env["CORE_PYTHON"] = sys.executable env["SESSION"] = str(self.id) env["SESSION_SHORT"] = self.short_session_id() env["SESSION_DIR"] = self.session_dir From 4bcaa32fdbbdc49f1ad29263b7e02ed052605b20 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 3 Aug 2020 16:29:35 -0700 Subject: [PATCH 038/598] pygui: fixed issue in task handling a returned boolean value, should be doing a none check --- daemon/core/gui/task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/core/gui/task.py b/daemon/core/gui/task.py index b2ab9765..02148f5a 100644 --- a/daemon/core/gui/task.py +++ b/daemon/core/gui/task.py @@ -38,7 +38,7 @@ class ProgressTask: values = self.task(*self.args) if values is None: values = () - elif values and not isinstance(values, tuple): + elif values is not None and not isinstance(values, tuple): values = (values,) if self.callback: self.app.after(0, self.callback, *values) From 082677c17bb2cdb8647d9f1e322ca9906f973df3 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 3 Aug 2020 16:37:31 -0700 Subject: [PATCH 039/598] pygui: fixed issue saving selected background to xml when not located within the ~/.coregui/backgrounds directory --- daemon/core/gui/coreclient.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 8a881945..c3ca2385 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -22,7 +22,7 @@ from core.api.grpc import ( wlan_pb2, ) from core.gui import appconfig -from core.gui.appconfig import XMLS_PATH, CoreServer, Observer +from core.gui.appconfig import BACKGROUNDS_PATH, XMLS_PATH, CoreServer, Observer from core.gui.dialogs.emaneinstall import EmaneInstallDialog from core.gui.dialogs.error import ErrorDialog from core.gui.dialogs.mobilityplayer import MobilityPlayer @@ -546,11 +546,15 @@ class CoreClient: def set_metadata(self) -> None: # create canvas data - wallpaper = None + wallpaper_path = None if self.app.canvas.wallpaper_file: - wallpaper = Path(self.app.canvas.wallpaper_file).name + wallpaper = Path(self.app.canvas.wallpaper_file) + if BACKGROUNDS_PATH == wallpaper.parent: + wallpaper_path = wallpaper.name + else: + wallpaper_path = str(wallpaper) canvas_config = { - "wallpaper": wallpaper, + "wallpaper": wallpaper_path, "wallpaper-style": self.app.canvas.scale_option.get(), "gridlines": self.app.canvas.show_grid.get(), "fit_image": self.app.canvas.adjust_to_dim.get(), From e2b3a2dc6da8b55480fdfe08365528f4a4643491 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 4 Aug 2020 12:29:08 -0700 Subject: [PATCH 040/598] pygui: fixed issues with configuring services on nodes due to refactoring changes --- daemon/core/gui/dialogs/nodeconfigservice.py | 4 ++-- daemon/core/gui/dialogs/nodeservice.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/core/gui/dialogs/nodeconfigservice.py b/daemon/core/gui/dialogs/nodeconfigservice.py index 1c67e4b3..fefdc4c5 100644 --- a/daemon/core/gui/dialogs/nodeconfigservice.py +++ b/daemon/core/gui/dialogs/nodeconfigservice.py @@ -101,7 +101,7 @@ class NodeConfigServiceDialog(Dialog): elif not var.get() and name in self.current_services: self.current_services.remove(name) self.draw_current_services() - self.node.config_services[:] = self.current_services + self.node.config_services = self.current_services.copy() def click_configure(self) -> None: current_selection = self.current.listbox.curselection() @@ -130,7 +130,7 @@ class NodeConfigServiceDialog(Dialog): self.current.listbox.itemconfig(tk.END, bg="green") def click_save(self) -> None: - self.node.config_services[:] = self.current_services + self.node.config_services = self.current_services.copy() logging.info("saved node config services: %s", self.node.config_services) self.destroy() diff --git a/daemon/core/gui/dialogs/nodeservice.py b/daemon/core/gui/dialogs/nodeservice.py index 5ec78a93..a35e1d53 100644 --- a/daemon/core/gui/dialogs/nodeservice.py +++ b/daemon/core/gui/dialogs/nodeservice.py @@ -126,7 +126,7 @@ class NodeServiceDialog(Dialog): ) def click_save(self) -> None: - self.node.services[:] = self.current_services + self.node.services = self.current_services.copy() self.destroy() def click_remove(self) -> None: From cd0351c818a7fe556548fbe2cb51fc66d9c048d5 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 4 Aug 2020 16:20:51 -0700 Subject: [PATCH 041/598] pygui: added view option to toggle wireless edges --- daemon/core/gui/graph/edges.py | 9 ++++++--- daemon/core/gui/graph/graph.py | 1 + daemon/core/gui/menubar.py | 5 +++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index b313957d..93749370 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -110,7 +110,9 @@ class Edge: arc_y = (perp_m * arc_x) + b return arc_x, arc_y - def draw(self, src_pos: Tuple[float, float], dst_pos: Tuple[float, float]) -> None: + def draw( + self, src_pos: Tuple[float, float], dst_pos: Tuple[float, float], state: str + ) -> None: arc_pos = self._get_arcpoint(src_pos, dst_pos) self.id = self.canvas.create_line( *src_pos, @@ -120,6 +122,7 @@ class Edge: tags=self.tag, width=self.scaled_width(), fill=self.color, + state=state, ) def redraw(self) -> None: @@ -249,7 +252,7 @@ class CanvasWirelessEdge(Edge): self.width: float = WIRELESS_WIDTH color = link.color if link.color else WIRELESS_COLOR self.color: str = color - self.draw(src_pos, dst_pos) + self.draw(src_pos, dst_pos, self.canvas.show_wireless.state()) if link.label: self.middle_label_text(link.label) self.set_binding() @@ -286,7 +289,7 @@ class CanvasEdge(Edge): self.link: Optional[Link] = None self.asymmetric_link: Optional[Link] = None self.throughput: Optional[float] = None - self.draw(src_pos, dst_pos) + self.draw(src_pos, dst_pos, tk.NORMAL) self.set_binding() self.context: tk.Menu = tk.Menu(self.canvas) self.create_context() diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index bb762bb8..b9dd5dba 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -97,6 +97,7 @@ class CanvasGraph(tk.Canvas): # drawing related self.show_node_labels: ShowVar = ShowVar(self, tags.NODE_LABEL, value=True) self.show_link_labels: ShowVar = ShowVar(self, tags.LINK_LABEL, value=True) + self.show_wireless: ShowVar = ShowVar(self, tags.WIRELESS_EDGE, value=True) self.show_grid: ShowVar = ShowVar(self, tags.GRIDLINE, value=True) self.show_annotations: ShowVar = ShowVar(self, tags.ANNOTATION, value=True) self.show_iface_names: BooleanVar = BooleanVar(value=False) diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index fd1413b6..dfe11eca 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -167,6 +167,11 @@ class Menubar(tk.Menu): command=self.canvas.show_link_labels.click_handler, variable=self.canvas.show_link_labels, ) + menu.add_checkbutton( + label="Wireless Links", + command=self.canvas.show_wireless.click_handler, + variable=self.canvas.show_wireless, + ) menu.add_checkbutton( label="Annotations", command=self.canvas.show_annotations.click_handler, From 5976bca34b2e068c2a6ebf0691d7395f8dcac434 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 4 Aug 2020 16:32:39 -0700 Subject: [PATCH 042/598] pygui: added view toggle for normal links --- daemon/core/gui/graph/edges.py | 1 + daemon/core/gui/graph/graph.py | 1 + daemon/core/gui/menubar.py | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index 93749370..d94d47d9 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -381,6 +381,7 @@ class CanvasEdge(Edge): def check_wireless(self) -> None: if self.is_wireless(): self.canvas.itemconfig(self.id, state=tk.HIDDEN) + self.canvas.dtag(self.id, tags.EDGE) self._check_antenna() def _check_antenna(self) -> None: diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index b9dd5dba..cbf3fbb2 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -97,6 +97,7 @@ class CanvasGraph(tk.Canvas): # drawing related self.show_node_labels: ShowVar = ShowVar(self, tags.NODE_LABEL, value=True) self.show_link_labels: ShowVar = ShowVar(self, tags.LINK_LABEL, value=True) + self.show_links: ShowVar = ShowVar(self, tags.EDGE, value=True) self.show_wireless: ShowVar = ShowVar(self, tags.WIRELESS_EDGE, value=True) self.show_grid: ShowVar = ShowVar(self, tags.GRIDLINE, value=True) self.show_annotations: ShowVar = ShowVar(self, tags.ANNOTATION, value=True) diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index dfe11eca..ebbac677 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -167,6 +167,11 @@ class Menubar(tk.Menu): command=self.canvas.show_link_labels.click_handler, variable=self.canvas.show_link_labels, ) + menu.add_checkbutton( + label="Links", + command=self.canvas.show_links.click_handler, + variable=self.canvas.show_links, + ) menu.add_checkbutton( label="Wireless Links", command=self.canvas.show_wireless.click_handler, From 9352c0eafeb645b2103814e54cfba6886ec43b71 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 4 Aug 2020 21:11:17 -0700 Subject: [PATCH 043/598] install: added core-python wrapper script to core virtual environment --- tasks.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tasks.py b/tasks.py index 38b219e6..d76bc01d 100644 --- a/tasks.py +++ b/tasks.py @@ -271,6 +271,18 @@ def install_scripts(c, verbose=False, prefix=DEFAULT_PREFIX): else: c.run(f"sudo cp {script} {dest}", hide=hide) + # setup core python helper + core_python = bin_dir.joinpath("core-python") + temp = NamedTemporaryFile("w", delete=False) + temp.writelines([ + "#!/bin/bash\n", + f'exec "{python}" "$@"\n', + ]) + temp.close() + c.run(f"sudo cp {temp.name} {core_python}", hide=hide) + c.run(f"sudo chmod 755 {core_python}", hide=hide) + os.unlink(temp.name) + # install core configuration file config_dir = "/etc/core" c.run(f"sudo mkdir -p {config_dir}", hide=hide) @@ -402,6 +414,10 @@ def uninstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX): dest = bin_dir.joinpath(script.name) c.run(f"sudo rm -f {dest}", hide=hide) + # remove core-python symlink + core_python = bin_dir.joinpath("core-python") + c.run(f"sudo rm -f {core_python}", hide=hide) + # install service systemd_dir = Path("/lib/systemd/system/") service_name = "core-daemon.service" From 8004be6e7c82cec1e807a346eefb69ab9a47e2be Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 5 Aug 2020 09:37:23 -0700 Subject: [PATCH 044/598] grpc: update client edit_node doc --- daemon/core/api/grpc/client.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 0674a0eb..e28233fc 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -552,11 +552,12 @@ class CoreGrpcClient: source: str = None, ) -> core_pb2.EditNodeResponse: """ - Edit a node, currently only changes position. + Edit a node's icon and/or location, can only use position(x,y) or + geo(lon, lat, alt), not both. :param session_id: session id :param node_id: node id - :param position: position to set node to + :param position: x,y location for node :param icon: path to icon for gui to use for node :param geo: lon,lat,alt location for node :param source: application source From b89a19a18e203e0d74e9a212415987a4ab9d5298 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 5 Aug 2020 12:10:27 -0700 Subject: [PATCH 045/598] grpc: update node events to include icon, pygui: updated handling node events to update icon when there is a change --- daemon/core/api/grpc/events.py | 1 + daemon/core/gui/coreclient.py | 2 ++ daemon/core/gui/graph/node.py | 13 +++++++++++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/daemon/core/api/grpc/events.py b/daemon/core/api/grpc/events.py index fb6eaff8..aff3c5e5 100644 --- a/daemon/core/api/grpc/events.py +++ b/daemon/core/api/grpc/events.py @@ -32,6 +32,7 @@ def handle_node_event(node_data: NodeData) -> core_pb2.Event: id=node.id, name=node.name, model=node.type, + icon=node.icon, position=position, geo=geo, services=services, diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index c3ca2385..902f780a 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -241,6 +241,8 @@ class CoreClient: x = node.position.x y = node.position.y canvas_node.move(x, y) + if node.icon and node.icon != canvas_node.core_node.icon: + canvas_node.update_icon(node.icon) elif event.message_type == MessageType.DELETE: canvas_node = self.canvas_nodes[node.id] self.app.canvas.clear_selection() diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index 100404ef..e63a8b80 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -1,12 +1,13 @@ import functools import logging import tkinter as tk +from pathlib import Path from typing import TYPE_CHECKING, Dict, List, Set import grpc from PIL.ImageTk import PhotoImage -from core.gui import themes +from core.gui import nodeutils, themes from core.gui.dialogs.emaneconfig import EmaneConfigDialog from core.gui.dialogs.mobilityconfig import MobilityConfigDialog from core.gui.dialogs.nodeconfig import NodeConfigDialog @@ -17,7 +18,7 @@ from core.gui.frames.node import NodeInfoFrame from core.gui.graph import tags from core.gui.graph.edges import CanvasEdge, CanvasWirelessEdge from core.gui.graph.tooltip import CanvasTooltip -from core.gui.images import ImageEnum +from core.gui.images import ImageEnum, Images from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils from core.gui.wrappers import Interface, Node, NodeType @@ -347,3 +348,11 @@ class CanvasNode: dx = node_x - 16 + (i * 8 * self.app.app_scale) - x dy = node_y - int(23 * self.app.app_scale) - y self.canvas.move(antenna_id, dx, dy) + + def update_icon(self, icon_path: str) -> None: + if not Path(icon_path).exists(): + logging.error(f"node icon does not exist: {icon_path}") + return + self.core_node.icon = icon_path + self.image = Images.create(icon_path, nodeutils.ICON_SIZE) + self.canvas.itemconfig(self.id, image=self.image) From 6dd7ce731e4d90d3bb6230959bbba1a597029f6f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 7 Aug 2020 22:04:34 -0700 Subject: [PATCH 046/598] removed invoke run task, since core-python provides a better means to do the same thing, updated install doc page to reflect this, removed old emane install from emane docs page --- docs/emane.md | 17 ----------------- docs/install.md | 25 ++++--------------------- tasks.py | 22 ---------------------- 3 files changed, 4 insertions(+), 60 deletions(-) diff --git a/docs/emane.md b/docs/emane.md index 716d7059..bbd3b0a4 100644 --- a/docs/emane.md +++ b/docs/emane.md @@ -50,23 +50,6 @@ can also subscribe to EMANE location events and move the nodes on the canvas as they are moved in the EMANE emulation. This would occur when an Emulation Script Generator, for example, is running a mobility script. -## EMANE Installation - -EMANE can be installed from deb or RPM packages or from source. See the -[EMANE GitHub](https://github.com/adjacentlink/emane) for full details. - -Here are quick instructions for installing all EMANE packages for Ubuntu 18.04: -```shell -# install dependencies -sudo apt-get install libssl-dev libxml-libxml-perl libxml-simple-perl -wget https://adjacentlink.com/downloads/emane/emane-1.2.5-release-1.ubuntu-18_04.amd64.tar.gz -tar xzf emane-1.2.5-release-1.ubuntu-18_04.amd64.tar.gz -# install base emane packages -sudo dpkg -i emane-1.2.5-release-1/deb/ubuntu-18_04/amd64/emane*.deb -# install python3 bindings -sudo dpkg -i emane-1.2.5-release-1/deb/ubuntu-18_04/amd64/python3*.deb -``` - ## EMANE Configuration The CORE configuration file **/etc/core/core.conf** has options specific to diff --git a/docs/install.md b/docs/install.md index 604ac509..7b5014c4 100644 --- a/docs/install.md +++ b/docs/install.md @@ -132,30 +132,14 @@ After the installation complete it will have installed the following scripts. If you create your own python scripts to run CORE directly or using the gRPC/TLV APIs you will need to make sure you are running them within context of the -installed virtual environment. +installed virtual environment. To help support this CORE provides the `core-python` +executable. This executable will allow you to enter CORE's python virtual +environment interpreter or to run a script within it. > **NOTE:** the following assumes CORE has been installed successfully -There is an invoke task to help with this case. ```shell -cd -inv -h run -Usage: inv[oke] [--core-opts] run [--options] [other tasks here ...] - -Docstring: - runs a user script in the core virtual environment - -Options: - -f STRING, --file=STRING script file to run in the core virtual environment - -s, --sudo run script as sudo -``` - -Another way would be to enable the core virtual environment shell. Which -would allow you to run scripts in a more **normal** way. -```shell -cd /daemon -poetry shell -python run /path/to/script.py +core-python