diff --git a/daemon/core/gui/dialogs/runtool.py b/daemon/core/gui/dialogs/runtool.py new file mode 100644 index 00000000..8d9d9cb0 --- /dev/null +++ b/daemon/core/gui/dialogs/runtool.py @@ -0,0 +1,123 @@ +import tkinter as tk +from tkinter import ttk + +from core.api.grpc import core_pb2 +from core.gui.dialogs.dialog import Dialog +from core.gui.themes import FRAME_PAD, PADY +from core.gui.widgets import CodeText, ListboxScroll + + +class RunToolDialog(Dialog): + def __init__(self, master, app): + super().__init__(master, app, "Run Tool", modal=True) + + self.cmd = tk.StringVar(value="ps ax") + self.app = app + self.result = None + self.node_list = None + self.executable_nodes = {} + + self.store_nodes() + self.draw() + + def store_nodes(self): + """ + store all CORE nodes (nodes that execute commands) from all existing nodes + """ + for nid, node in self.app.core.canvas_nodes.items(): + if node.core_node.type == core_pb2.NodeType.DEFAULT: + self.executable_nodes[node.core_node.name] = nid + + def draw(self): + self.top.rowconfigure(0, weight=1) + self.top.columnconfigure(0, weight=5) + self.top.columnconfigure(1, weight=1) + self.draw_command_frame() + self.draw_nodes_frame() + return + + def draw_command_frame(self): + # the main frame + frame = ttk.Frame(self.top) + frame.grid(row=0, column=0, sticky="nsew") + frame.columnconfigure(0, weight=1) + frame.rowconfigure(0, weight=1) + frame.rowconfigure(1, weight=1) + + labeled_frame = ttk.LabelFrame(frame, text="Command line", padding=FRAME_PAD) + labeled_frame.grid(row=0, column=0, sticky="nsew", 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") + + # results frame + labeled_frame = ttk.LabelFrame(frame, text="Command results", padding=FRAME_PAD) + labeled_frame.grid(row=1, column=0, sticky="nsew", pady=PADY) + labeled_frame.columnconfigure(0, weight=1) + labeled_frame.rowconfigure(0, weight=5) + labeled_frame.rowconfigure(1, weight=1) + + self.result = CodeText(labeled_frame) + self.result.text.config(state=tk.DISABLED, height=15) + self.result.grid(sticky="nsew") + button_frame = ttk.Frame(labeled_frame, padding=FRAME_PAD) + button_frame.grid(sticky="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="nsew") + button = ttk.Button(button_frame, text="Close", command=self.destroy) + button.grid(row=0, column=1, sticky="nsew") + + def draw_nodes_frame(self): + labeled_frame = ttk.LabelFrame( + self.top, text="Run on these nodes", padding=FRAME_PAD + ) + labeled_frame.grid(row=0, column=1, sticky="nsew") + labeled_frame.columnconfigure(0, weight=1) + labeled_frame.rowconfigure(0, weight=5) + labeled_frame.rowconfigure(1, weight=1) + + self.node_list = ListboxScroll(labeled_frame) + self.node_list.listbox.config(selectmode=tk.MULTIPLE) + self.node_list.grid(sticky="nsew") + 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.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") + button = ttk.Button(button_frame, text="None", command=self.click_none) + button.grid(row=0, column=1, sticky="nsew") + + def click_all(self): + self.node_list.listbox.selection_set(0, self.node_list.listbox.size() - 1) + + def click_none(self): + self.node_list.listbox.selection_clear(0, self.node_list.listbox.size() - 1) + + def click_run(self): + """ + run the command on each of the selected nodes and display the output to result text box + """ + command = self.cmd.get().strip() + self.result.text.config(state=tk.NORMAL) + self.result.text.delete("1.0", tk.END) + self.result.text.insert( + tk.END, f"> {command}\n" * len(self.node_list.listbox.curselection()) + ) + for selection in self.node_list.listbox.curselection(): + 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.result.text.insert( + tk.END, f"> {node_name} > {command}:\n{response.output}\n" + ) + self.result.text.config(state=tk.DISABLED) diff --git a/daemon/core/gui/toolbar.py b/daemon/core/gui/toolbar.py index d6707fb2..11e2896a 100644 --- a/daemon/core/gui/toolbar.py +++ b/daemon/core/gui/toolbar.py @@ -9,6 +9,7 @@ from typing import TYPE_CHECKING, Callable from core.api.grpc import core_pb2 from core.gui.dialogs.customnodes import CustomNodesDialog from core.gui.dialogs.marker import MarkerDialog +from core.gui.dialogs.runtool import RunToolDialog from core.gui.graph.enums import GraphMode from core.gui.graph.shapeutils import ShapeType, is_marker from core.gui.images import ImageEnum, Images @@ -485,6 +486,8 @@ class Toolbar(ttk.Frame): def click_run_button(self): logging.debug("Click on RUN button") + dialog = RunToolDialog(self.app, self.app) + dialog.show() def click_marker_button(self): logging.debug("Click on marker button") diff --git a/docs/devguide.md b/docs/devguide.md index b6824128..c10bb007 100644 --- a/docs/devguide.md +++ b/docs/devguide.md @@ -89,7 +89,7 @@ Commands below can be used to run the core-daemon, the new core gui, and tests. sudo python3 -m pipenv run core # runs coretk gui -python3 -m pipenv run coretk +python3 -m pipenv run core-pygui # runs mocked unit tests python3 -m pipenv run test-mock