pygui cleaned up error display by creating top level app methods for displaying exceptions and errors, logging exceptions, and making sure they work for background tasks
This commit is contained in:
parent
0999fabb14
commit
1dd45f4424
14 changed files with 57 additions and 82 deletions
|
@ -1,11 +1,15 @@
|
|||
import logging
|
||||
import math
|
||||
import time
|
||||
import tkinter as tk
|
||||
from tkinter import font, ttk
|
||||
from tkinter.ttk import Progressbar
|
||||
|
||||
import grpc
|
||||
|
||||
from core.gui import appconfig, themes
|
||||
from core.gui.coreclient import CoreClient
|
||||
from core.gui.dialogs.error import ErrorDialog
|
||||
from core.gui.graph.graph import CanvasGraph
|
||||
from core.gui.images import ImageEnum, Images
|
||||
from core.gui.menubar import Menubar
|
||||
|
@ -138,6 +142,18 @@ class Application(ttk.Frame):
|
|||
message = f"Task ran for {total:.3f} seconds"
|
||||
self.statusbar.set_status(message)
|
||||
|
||||
def show_grpc_exception(self, title: str, e: grpc.RpcError) -> None:
|
||||
logging.exception("app grpc exception", exc_info=e)
|
||||
message = e.details()
|
||||
self.show_error(title, message)
|
||||
|
||||
def show_exception(self, title: str, e: Exception) -> None:
|
||||
logging.exception("app exception", exc_info=e)
|
||||
self.show_error(title, str(e))
|
||||
|
||||
def show_error(self, title: str, message: str) -> None:
|
||||
self.after(0, lambda: ErrorDialog(self, self, title, message).show())
|
||||
|
||||
def on_closing(self):
|
||||
self.menubar.prompt_save_running_session(True)
|
||||
|
||||
|
|
|
@ -16,9 +16,9 @@ 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.gui import appconfig
|
||||
from core.gui.dialogs.error import ErrorDialog
|
||||
from core.gui.dialogs.mobilityplayer import MobilityPlayer
|
||||
from core.gui.dialogs.sessions import SessionsDialog
|
||||
from core.gui.errors import show_grpc_error
|
||||
from core.gui.graph import tags
|
||||
from core.gui.graph.edges import CanvasEdge
|
||||
from core.gui.graph.node import CanvasNode
|
||||
|
@ -343,7 +343,7 @@ class CoreClient:
|
|||
response = self.client.get_session_metadata(self.session_id)
|
||||
self.parse_metadata(response.config)
|
||||
except grpc.RpcError as e:
|
||||
self.app.after(0, show_grpc_error, e, self.app, self.app)
|
||||
self.app.show_grpc_exception("Join Session Error", e)
|
||||
|
||||
# update ui to represent current state
|
||||
self.app.after(0, self.app.joined_session_update)
|
||||
|
@ -426,21 +426,16 @@ class CoreClient:
|
|||
)
|
||||
self.join_session(response.session_id, query_location=False)
|
||||
except grpc.RpcError as e:
|
||||
self.app.after(0, show_grpc_error, e, self.app, self.app)
|
||||
self.app.show_grpc_exception("New Session Error", e)
|
||||
|
||||
def delete_session(self, session_id: int = None, parent_frame=None):
|
||||
def delete_session(self, session_id: int = None):
|
||||
if session_id is None:
|
||||
session_id = self.session_id
|
||||
try:
|
||||
response = self.client.delete_session(session_id)
|
||||
logging.info("deleted session(%s), Result: %s", session_id, response)
|
||||
except grpc.RpcError as e:
|
||||
# use the right master widget so the error dialog displays
|
||||
# right on top of it
|
||||
master = self.app
|
||||
if parent_frame:
|
||||
master = parent_frame
|
||||
self.app.after(0, show_grpc_error, e, master, self.app)
|
||||
self.app.show_grpc_exception("Delete Session Error", e)
|
||||
|
||||
def setup(self):
|
||||
"""
|
||||
|
@ -472,7 +467,9 @@ class CoreClient:
|
|||
dialog = SessionsDialog(self.app, self.app, True)
|
||||
dialog.show()
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e, self.app, self.app)
|
||||
logging.exception("core setup error")
|
||||
dialog = ErrorDialog(self.app, self.app, "Setup Error", e.details())
|
||||
dialog.show()
|
||||
self.app.close()
|
||||
|
||||
def edit_node(self, core_node: core_pb2.Node):
|
||||
|
@ -481,7 +478,7 @@ class CoreClient:
|
|||
self.session_id, core_node.id, core_node.position, source=GUI_SOURCE
|
||||
)
|
||||
except grpc.RpcError as e:
|
||||
self.app.after(0, show_grpc_error, e, self.app, self.app)
|
||||
self.app.show_grpc_exception("Edit Node Error", e)
|
||||
|
||||
def start_session(self) -> core_pb2.StartSessionResponse:
|
||||
self.interfaces_manager.reset_mac()
|
||||
|
@ -532,7 +529,7 @@ class CoreClient:
|
|||
if response.result:
|
||||
self.set_metadata()
|
||||
except grpc.RpcError as e:
|
||||
self.app.after(0, show_grpc_error, e, self.app, self.app)
|
||||
self.app.show_grpc_exception("Start Session Error", e)
|
||||
return response
|
||||
|
||||
def stop_session(self, session_id: int = None) -> core_pb2.StartSessionResponse:
|
||||
|
@ -543,7 +540,7 @@ class CoreClient:
|
|||
response = self.client.stop_session(session_id)
|
||||
logging.info("stopped session(%s), result: %s", session_id, response)
|
||||
except grpc.RpcError as e:
|
||||
self.app.after(0, show_grpc_error, e, self.app, self.app)
|
||||
self.app.show_grpc_exception("Stop Session Error", e)
|
||||
return response
|
||||
|
||||
def show_mobility_players(self):
|
||||
|
@ -597,7 +594,7 @@ class CoreClient:
|
|||
logging.info("launching terminal %s", cmd)
|
||||
os.system(cmd)
|
||||
except grpc.RpcError as e:
|
||||
self.app.after(0, show_grpc_error, e, self.app, self.app)
|
||||
self.app.show_grpc_exception("Node Terminal Error", e)
|
||||
|
||||
def save_xml(self, file_path: str):
|
||||
"""
|
||||
|
@ -610,7 +607,7 @@ class CoreClient:
|
|||
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.after(0, show_grpc_error, e, self.app, self.app)
|
||||
self.app.show_grpc_exception("Save XML Error", e)
|
||||
|
||||
def open_xml(self, file_path: str):
|
||||
"""
|
||||
|
@ -621,7 +618,7 @@ class CoreClient:
|
|||
logging.info("open xml file %s, response: %s", file_path, response)
|
||||
self.join_session(response.session_id)
|
||||
except grpc.RpcError as e:
|
||||
self.app.after(0, show_grpc_error, e, self.app, self.app)
|
||||
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)
|
||||
|
|
|
@ -10,7 +10,6 @@ import grpc
|
|||
|
||||
from core.api.grpc.services_pb2 import ServiceValidationMode
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.errors import show_grpc_error
|
||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||
from core.gui.widgets import CodeText, ConfigFrame, ListboxScroll
|
||||
|
||||
|
@ -116,8 +115,8 @@ class ConfigServiceConfigDialog(Dialog):
|
|||
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
|
||||
show_grpc_error(e, self.app, self.app)
|
||||
|
||||
def draw(self):
|
||||
self.top.columnconfigure(0, weight=1)
|
||||
|
@ -323,22 +322,17 @@ class ConfigServiceConfigDialog(Dialog):
|
|||
self.destroy()
|
||||
return
|
||||
|
||||
try:
|
||||
service_config = self.canvas_node.config_service_configs.setdefault(
|
||||
self.service_name, {}
|
||||
)
|
||||
if self.config_frame:
|
||||
self.config_frame.parse_config()
|
||||
service_config["config"] = {
|
||||
x.name: x.value for x in self.config.values()
|
||||
}
|
||||
service_config["config"] = {x.name: x.value for x in self.config.values()}
|
||||
templates_config = service_config.setdefault("templates", {})
|
||||
for file in self.modified_files:
|
||||
templates_config[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")
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e, self.top, self.app)
|
||||
self.destroy()
|
||||
|
||||
def handle_template_changed(self, event: tk.Event):
|
||||
|
|
|
@ -9,7 +9,6 @@ from typing import TYPE_CHECKING, Any
|
|||
import grpc
|
||||
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.errors import show_grpc_error
|
||||
from core.gui.images import ImageEnum, Images
|
||||
from core.gui.themes import PADX, PADY
|
||||
from core.gui.widgets import ConfigFrame
|
||||
|
@ -78,7 +77,7 @@ class EmaneModelDialog(Dialog):
|
|||
)
|
||||
self.draw()
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e, self.app, self.app)
|
||||
self.app.show_grpc_exception("Get EMANE Config Error", e)
|
||||
self.has_error = True
|
||||
self.destroy()
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import grpc
|
||||
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.images import ImageEnum, Images
|
||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||
|
@ -41,21 +39,3 @@ class ErrorDialog(Dialog):
|
|||
|
||||
button = ttk.Button(self.top, text="Close", command=lambda: self.destroy())
|
||||
button.grid(sticky="ew")
|
||||
|
||||
|
||||
def show_exception(app: "Application", title: str, exception: Exception) -> None:
|
||||
dialog = ErrorDialog(app, app, title, str(exception))
|
||||
dialog.show()
|
||||
|
||||
|
||||
def show_grpc_error(e: grpc.RpcError, master, app: "Application") -> None:
|
||||
title = [x.capitalize() for x in e.code().name.lower().split("_")]
|
||||
title = " ".join(title)
|
||||
title = f"GRPC {title}"
|
||||
dialog = ErrorDialog(master, app, title, e.details())
|
||||
dialog.show()
|
||||
|
||||
|
||||
def show_error(app: "Application", title: str, message: str) -> None:
|
||||
dialog = ErrorDialog(app, app, title, message)
|
||||
dialog.show()
|
|
@ -7,7 +7,6 @@ from typing import TYPE_CHECKING
|
|||
import grpc
|
||||
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.errors import show_grpc_error
|
||||
from core.gui.themes import PADX, PADY
|
||||
from core.gui.widgets import ConfigFrame
|
||||
|
||||
|
@ -33,8 +32,8 @@ class MobilityConfigDialog(Dialog):
|
|||
self.config = self.app.core.get_mobility_config(self.node.id)
|
||||
self.draw()
|
||||
except grpc.RpcError as e:
|
||||
self.app.show_grpc_exception("Get Mobility Config Error", e)
|
||||
self.has_error = True
|
||||
show_grpc_error(e, self.app, self.app)
|
||||
self.destroy()
|
||||
|
||||
def draw(self):
|
||||
|
|
|
@ -6,7 +6,6 @@ import grpc
|
|||
|
||||
from core.api.grpc.mobility_pb2 import MobilityAction
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.errors import show_grpc_error
|
||||
from core.gui.images import ImageEnum, Images
|
||||
from core.gui.themes import PADX, PADY
|
||||
|
||||
|
@ -154,7 +153,7 @@ class MobilityPlayerDialog(Dialog):
|
|||
session_id, self.node.id, MobilityAction.START
|
||||
)
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e, self.top, self.app)
|
||||
self.app.show_grpc_exception("Mobility Error", e)
|
||||
|
||||
def click_pause(self):
|
||||
self.set_pause()
|
||||
|
@ -164,7 +163,7 @@ class MobilityPlayerDialog(Dialog):
|
|||
session_id, self.node.id, MobilityAction.PAUSE
|
||||
)
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e, self.top, self.app)
|
||||
self.app.show_grpc_exception("Mobility Error", e)
|
||||
|
||||
def click_stop(self):
|
||||
self.set_stop()
|
||||
|
@ -174,4 +173,4 @@ class MobilityPlayerDialog(Dialog):
|
|||
session_id, self.node.id, MobilityAction.STOP
|
||||
)
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e, self.top, self.app)
|
||||
self.app.show_grpc_exception("Mobility Error", e)
|
||||
|
|
|
@ -9,7 +9,6 @@ import grpc
|
|||
from core.api.grpc.services_pb2 import ServiceValidationMode
|
||||
from core.gui.dialogs.copyserviceconfig import CopyServiceConfigDialog
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.errors import show_grpc_error
|
||||
from core.gui.images import ImageEnum, Images
|
||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||
from core.gui.widgets import CodeText, ListboxScroll
|
||||
|
@ -119,8 +118,8 @@ class ServiceConfigDialog(Dialog):
|
|||
for file, data in file_configs.items():
|
||||
self.temp_service_files[file] = data
|
||||
except grpc.RpcError as e:
|
||||
self.app.show_grpc_exception("Get Node Service Error", e)
|
||||
self.has_error = True
|
||||
show_grpc_error(e, self.master, self.app)
|
||||
|
||||
def draw(self):
|
||||
self.top.columnconfigure(0, weight=1)
|
||||
|
@ -484,7 +483,7 @@ class ServiceConfigDialog(Dialog):
|
|||
)
|
||||
self.current_service_color("green")
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e, self.top, self.app)
|
||||
self.app.show_grpc_exception("Save Service Config Error", e)
|
||||
self.destroy()
|
||||
|
||||
def display_service_file_data(self, event: tk.Event):
|
||||
|
|
|
@ -5,7 +5,6 @@ from typing import TYPE_CHECKING
|
|||
import grpc
|
||||
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.errors import show_grpc_error
|
||||
from core.gui.themes import PADX, PADY
|
||||
from core.gui.widgets import ConfigFrame
|
||||
|
||||
|
@ -28,8 +27,8 @@ class SessionOptionsDialog(Dialog):
|
|||
response = self.app.core.client.get_session_options(session_id)
|
||||
return response.config
|
||||
except grpc.RpcError as e:
|
||||
self.app.show_grpc_exception("Get Session Options Error", e)
|
||||
self.has_error = True
|
||||
show_grpc_error(e, self.app, self.app)
|
||||
self.destroy()
|
||||
|
||||
def draw(self):
|
||||
|
@ -56,5 +55,5 @@ class SessionOptionsDialog(Dialog):
|
|||
response = self.app.core.client.set_session_options(session_id, config)
|
||||
logging.info("saved session config: %s", response)
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e, self.top, self.app)
|
||||
self.app.show_grpc_exception("Set Session Options Error", e)
|
||||
self.destroy()
|
||||
|
|
|
@ -7,7 +7,6 @@ import grpc
|
|||
|
||||
from core.api.grpc import core_pb2
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.errors import show_grpc_error
|
||||
from core.gui.images import ImageEnum, Images
|
||||
from core.gui.task import ProgressTask
|
||||
from core.gui.themes import PADX, PADY
|
||||
|
@ -37,7 +36,7 @@ class SessionsDialog(Dialog):
|
|||
logging.info("sessions: %s", response)
|
||||
return response.sessions
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e, self.app, self.app)
|
||||
self.app.show_grpc_exception("Get Sessions Error", e)
|
||||
self.destroy()
|
||||
|
||||
def draw(self) -> None:
|
||||
|
|
|
@ -4,7 +4,6 @@ from typing import TYPE_CHECKING
|
|||
import grpc
|
||||
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.errors import show_grpc_error
|
||||
from core.gui.themes import PADX, PADY
|
||||
from core.gui.widgets import ConfigFrame
|
||||
|
||||
|
@ -21,7 +20,7 @@ class WlanConfigDialog(Dialog):
|
|||
self, master: "Application", app: "Application", canvas_node: "CanvasNode"
|
||||
):
|
||||
super().__init__(
|
||||
master, app, f"{canvas_node.core_node.name} Wlan Configuration"
|
||||
master, app, f"{canvas_node.core_node.name} WLAN Configuration"
|
||||
)
|
||||
self.canvas_node = canvas_node
|
||||
self.node = canvas_node.core_node
|
||||
|
@ -38,7 +37,7 @@ class WlanConfigDialog(Dialog):
|
|||
self.init_draw_range()
|
||||
self.draw()
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e, self.app, self.app)
|
||||
self.app.show_grpc_exception("WLAN Config Error", e)
|
||||
self.has_error = True
|
||||
self.destroy()
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ from core.gui.dialogs.nodeconfig import NodeConfigDialog
|
|||
from core.gui.dialogs.nodeconfigservice import NodeConfigServiceDialog
|
||||
from core.gui.dialogs.nodeservice import NodeServiceDialog
|
||||
from core.gui.dialogs.wlanconfig import WlanConfigDialog
|
||||
from core.gui.errors import show_grpc_error
|
||||
from core.gui.graph import tags
|
||||
from core.gui.graph.edges import CanvasEdge
|
||||
from core.gui.graph.tooltip import CanvasTooltip
|
||||
|
@ -180,7 +179,7 @@ class CanvasNode:
|
|||
output = self.app.core.run(self.core_node.id)
|
||||
self.tooltip.text.set(output)
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e, self.app, self.app)
|
||||
self.app.show_grpc_exception("Observer Error", e)
|
||||
|
||||
def on_leave(self, event: tk.Event):
|
||||
self.tooltip.on_leave(event)
|
||||
|
|
|
@ -2,8 +2,6 @@ import logging
|
|||
import threading
|
||||
from typing import Any, Callable, Tuple
|
||||
|
||||
from core.gui.errors import show_exception
|
||||
|
||||
|
||||
class ProgressTask:
|
||||
def __init__(
|
||||
|
@ -33,7 +31,6 @@ class ProgressTask:
|
|||
self.app.after(0, self.callback, *values)
|
||||
except Exception as e:
|
||||
logging.exception("progress task exception")
|
||||
args = (self.app, "Task Error", e)
|
||||
self.app.after(0, show_exception, *args)
|
||||
self.app.show_exception("Task Error", e)
|
||||
finally:
|
||||
self.app.after(0, self.app.progress_task_complete)
|
||||
|
|
|
@ -9,7 +9,6 @@ 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.errors import show_error
|
||||
from core.gui.graph.enums import GraphMode
|
||||
from core.gui.graph.shapeutils import ShapeType, is_marker
|
||||
from core.gui.images import ImageEnum, Images
|
||||
|
@ -273,7 +272,7 @@ class Toolbar(ttk.Frame):
|
|||
self.app.core.show_mobility_players()
|
||||
else:
|
||||
message = "\n".join(response.exceptions)
|
||||
show_error(self.app, "Start Session Error", message)
|
||||
self.app.show_error("Start Session Error", message)
|
||||
|
||||
def set_runtime(self):
|
||||
self.runtime_frame.tkraise()
|
||||
|
|
Loading…
Reference in a new issue