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] 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, + )