pygui: added wrappers for most usages of protobufs within pygui

This commit is contained in:
Blake Harnden 2020-07-25 10:30:14 -07:00
parent 154fa8b77d
commit 77f6577bce
23 changed files with 475 additions and 173 deletions

View file

@ -13,27 +13,8 @@ from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Set, Tuple
import grpc import grpc
from core.api.grpc import client 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.configservices_pb2 import ConfigService, ConfigServiceConfig
from core.api.grpc.core_pb2 import ( from core.api.grpc.core_pb2 import CpuUsageEvent, Event, ThroughputsEvent
CpuUsageEvent,
Event,
ExceptionEvent,
Interface,
Link,
LinkEvent,
LinkType,
MessageType,
Node,
NodeEvent,
NodeType,
Position,
SessionLocation,
SessionState,
StartSessionResponse,
StopSessionResponse,
ThroughputsEvent,
)
from core.api.grpc.emane_pb2 import EmaneModelConfig from core.api.grpc.emane_pb2 import EmaneModelConfig
from core.api.grpc.mobility_pb2 import MobilityConfig from core.api.grpc.mobility_pb2 import MobilityConfig
from core.api.grpc.services_pb2 import NodeServiceData, ServiceConfig, ServiceFileConfig 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.graph.shapeutils import ShapeType
from core.gui.interface import InterfaceManager from core.gui.interface import InterfaceManager
from core.gui.nodeutils import NodeDraw, NodeUtils 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: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -165,12 +161,13 @@ class CoreClient:
return return
if event.HasField("link_event"): 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"): elif event.HasField("session_event"):
logging.info("session event: %s", event) logging.info("session event: %s", event)
session_event = event.session_event session_event = event.session_event
if session_event.event <= SessionState.SHUTDOWN: if session_event.event <= SessionState.SHUTDOWN.value:
self.state = event.session_event.event self.state = SessionState(session_event.event)
elif session_event.event in {7, 8, 9}: elif session_event.event in {7, 8, 9}:
node_id = session_event.node_id node_id = session_event.node_id
dialog = self.mobility_players.get(node_id) dialog = self.mobility_players.get(node_id)
@ -184,10 +181,12 @@ class CoreClient:
else: else:
logging.warning("unknown session event: %s", session_event) logging.warning("unknown session event: %s", session_event)
elif event.HasField("node_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"): elif event.HasField("config_event"):
logging.info("config event: %s", event) logging.info("config event: %s", event)
elif event.HasField("exception_event"): elif event.HasField("exception_event"):
event = ExceptionEvent.from_proto(event.session_id, event.exception_event)
self.handle_exception_event(event) self.handle_exception_event(event)
else: else:
logging.info("unhandled event: %s", event) logging.info("unhandled event: %s", event)
@ -307,7 +306,7 @@ class CoreClient:
try: try:
response = self.client.get_session(self.session_id) response = self.client.get_session(self.session_id)
session = response.session session = response.session
self.state = session.state self.state = SessionState(session.state)
self.handling_events = self.client.events( self.handling_events = self.client.events(
self.session_id, self.handle_events self.session_id, self.handle_events
) )
@ -324,7 +323,7 @@ class CoreClient:
# get location # get location
if query_location: if query_location:
response = self.client.get_session_location(self.session_id) response = self.client.get_session_location(self.session_id)
self.location = response.location self.location = SessionLocation.from_proto(response.location)
# get emane models # get emane models
response = self.client.get_emane_models(self.session_id) response = self.client.get_emane_models(self.session_id)
@ -338,20 +337,22 @@ class CoreClient:
# get emane config # get emane config
response = self.client.get_emane_config(self.session_id) 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 # update interface manager
self.ifaces_manager.joined(session.links) self.ifaces_manager.joined(session.links)
# draw session # 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 # get mobility configs
response = self.client.get_mobility_configs(self.session_id) response = self.client.get_mobility_configs(self.session_id)
for node_id in response.configs: for node_id in response.configs:
config = response.configs[node_id].config config = response.configs[node_id].config
canvas_node = self.canvas_nodes[node_id] 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 # get emane model config
response = self.client.get_emane_model_configs(self.session_id) response = self.client.get_emane_model_configs(self.session_id)
@ -360,16 +361,16 @@ class CoreClient:
if config.iface_id != -1: if config.iface_id != -1:
iface_id = config.iface_id iface_id = config.iface_id
canvas_node = self.canvas_nodes[config.node_id] canvas_node = self.canvas_nodes[config.node_id]
canvas_node.emane_model_configs[(config.model, iface_id)] = dict( canvas_node.emane_model_configs[
config.config (config.model, iface_id)
) ] = ConfigOption.from_dict(config.config)
# get wlan configurations # get wlan configurations
response = self.client.get_wlan_configs(self.session_id) response = self.client.get_wlan_configs(self.session_id)
for _id in response.configs: for _id in response.configs:
mapped_config = response.configs[_id] mapped_config = response.configs[_id]
canvas_node = self.canvas_nodes[_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 # get service configurations
response = self.client.get_node_service_configs(self.session_id) response = self.client.get_node_service_configs(self.session_id)
@ -501,7 +502,6 @@ class CoreClient:
""" """
try: try:
self.client.connect() self.client.connect()
self.setup_cpu_usage()
# get service information # get service information
response = self.client.get_services() response = self.client.get_services()
@ -546,8 +546,9 @@ class CoreClient:
def edit_node(self, core_node: Node) -> None: def edit_node(self, core_node: Node) -> None:
try: try:
position = core_node.position.to_proto()
self.client.edit_node( 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: except grpc.RpcError as e:
self.app.show_grpc_exception("Edit Node Error", e) self.app.show_grpc_exception("Edit Node Error", e)
@ -556,18 +557,17 @@ class CoreClient:
for server in self.servers.values(): 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) -> StartSessionResponse: def start_session(self) -> Tuple[bool, List[str]]:
self.ifaces_manager.reset_mac() 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 = [] links = []
for edge in self.links.values(): for edge in self.links.values():
link = Link() link = edge.link
link.CopyFrom(edge.link) if link.iface1 and not link.iface1.mac:
if link.HasField("iface1") and not link.iface1.mac:
link.iface1.mac = self.ifaces_manager.next_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() link.iface2.mac = self.ifaces_manager.next_mac()
links.append(link) links.append(link.to_proto())
wlan_configs = self.get_wlan_configs_proto() wlan_configs = self.get_wlan_configs_proto()
mobility_configs = self.get_mobility_configs_proto() mobility_configs = self.get_mobility_configs_proto()
emane_model_configs = self.get_emane_model_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} emane_config = {x: self.emane_config[x].value for x in self.emane_config}
else: else:
emane_config = None emane_config = None
response = StartSessionResponse(result=False) result = False
exceptions = []
try: try:
self.send_servers() self.send_servers()
response = self.client.start_session( response = self.client.start_session(
self.session_id, self.session_id,
nodes, nodes,
links, links,
self.location, self.location.to_proto(),
hooks, hooks,
emane_config, emane_config,
emane_model_configs, emane_model_configs,
@ -605,20 +606,23 @@ class CoreClient:
) )
if response.result: if response.result:
self.set_metadata() self.set_metadata()
result = response.result
exceptions = response.exceptions
except grpc.RpcError as e: except grpc.RpcError as e:
self.app.show_grpc_exception("Start Session Error", 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: if not session_id:
session_id = self.session_id session_id = self.session_id
response = StopSessionResponse(result=False) result = False
try: try:
response = self.client.stop_session(session_id) response = self.client.stop_session(session_id)
logging.info("stopped session(%s), result: %s", session_id, response) logging.info("stopped session(%s), result: %s", session_id, response)
result = response.result
except grpc.RpcError as e: except grpc.RpcError as e:
self.app.show_grpc_exception("Stop Session Error", e) self.app.show_grpc_exception("Stop Session Error", e)
return response return result
def show_mobility_players(self) -> None: def show_mobility_players(self) -> None:
for canvas_node in self.canvas_nodes.values(): for canvas_node in self.canvas_nodes.values():
@ -920,7 +924,7 @@ class CoreClient:
) )
return node 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 remove the nodes selected by the user and anything related to that node
such as link, configurations, interfaces such as link, configurations, interfaces
@ -951,13 +955,7 @@ class CoreClient:
ip6=ip6, ip6=ip6,
ip6_mask=ip6_mask, ip6_mask=ip6_mask,
) )
logging.info( logging.info("create node(%s) interface(%s)", node.name, iface)
"create node(%s) interface(%s) IPv4(%s) IPv6(%s)",
node.name,
iface.name,
iface.ip4,
iface.ip6,
)
return iface return iface
def create_link( def create_link(
@ -1010,8 +1008,7 @@ class CoreClient:
continue continue
if not canvas_node.wlan_config: if not canvas_node.wlan_config:
continue continue
config = canvas_node.wlan_config config = ConfigOption.to_dict(canvas_node.wlan_config)
config = {x: config[x].value for x in config}
node_id = canvas_node.core_node.id node_id = canvas_node.core_node.id
wlan_config = WlanConfig(node_id=node_id, config=config) wlan_config = WlanConfig(node_id=node_id, config=config)
configs.append(wlan_config) configs.append(wlan_config)
@ -1024,8 +1021,7 @@ class CoreClient:
continue continue
if not canvas_node.mobility_config: if not canvas_node.mobility_config:
continue continue
config = canvas_node.mobility_config config = ConfigOption.to_dict(canvas_node.mobility_config)
config = {x: config[x].value for x in config}
node_id = canvas_node.core_node.id node_id = canvas_node.core_node.id
mobility_config = MobilityConfig(node_id=node_id, config=config) mobility_config = MobilityConfig(node_id=node_id, config=config)
configs.append(mobility_config) configs.append(mobility_config)
@ -1039,7 +1035,7 @@ class CoreClient:
node_id = canvas_node.core_node.id node_id = canvas_node.core_node.id
for key, config in canvas_node.emane_model_configs.items(): for key, config in canvas_node.emane_model_configs.items():
model, iface_id = key model, iface_id = key
config = {x: config[x].value for x in config} config = ConfigOption.to_dict(config)
if iface_id is None: if iface_id is None:
iface_id = -1 iface_id = -1
config_proto = EmaneModelConfig( config_proto = EmaneModelConfig(
@ -1116,7 +1112,7 @@ class CoreClient:
node_id, node_id,
config, config,
) )
return dict(config) return ConfigOption.from_dict(config)
def get_mobility_config(self, node_id: int) -> Dict[str, ConfigOption]: 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)
@ -1126,7 +1122,7 @@ class CoreClient:
node_id, node_id,
config, config,
) )
return dict(config) return ConfigOption.from_dict(config)
def get_emane_model_config( def get_emane_model_config(
self, node_id: int, model: str, iface_id: int = None self, node_id: int, model: str, iface_id: int = None
@ -1145,7 +1141,7 @@ class CoreClient:
iface_id, iface_id,
config, config,
) )
return dict(config) return ConfigOption.from_dict(config)
def execute_script(self, script) -> None: def execute_script(self, script) -> None:
response = self.client.execute_script(script) response = self.client.execute_script(script)

View file

@ -5,10 +5,10 @@ import tkinter as tk
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, Dict, Optional 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.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY from core.gui.themes import PADX, PADY
from core.gui.widgets import CodeText from core.gui.widgets import CodeText
from core.gui.wrappers import ExceptionEvent, ExceptionLevel
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -49,9 +49,8 @@ class AlertsDialog(Dialog):
self.tree.heading("source", text="Source") self.tree.heading("source", text="Source")
self.tree.bind("<<TreeviewSelect>>", self.click_select) self.tree.bind("<<TreeviewSelect>>", self.click_select)
for alarm in self.app.statusbar.core_alarms: for exception in self.app.statusbar.core_alarms:
exception = alarm.exception_event level_name = exception.level.name
level_name = ExceptionLevel.Enum.Name(exception.level)
node_id = exception.node_id if exception.node_id else "" node_id = exception.node_id if exception.node_id else ""
insert_id = self.tree.insert( insert_id = self.tree.insert(
"", "",
@ -60,21 +59,21 @@ class AlertsDialog(Dialog):
values=( values=(
exception.date, exception.date,
level_name, level_name,
alarm.session_id, exception.session_id,
node_id, node_id,
exception.source, exception.source,
), ),
tags=(level_name,), 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") 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") 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") 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") self.tree.tag_configure(notice_name, background="#85e085")
yscrollbar = ttk.Scrollbar(frame, orient="vertical", command=self.tree.yview) 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: def click_select(self, event: tk.Event) -> None:
current = self.tree.selection()[0] 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.config(state=tk.NORMAL)
self.codetext.text.delete(1.0, tk.END) 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) self.codetext.text.config(state=tk.DISABLED)

View file

@ -8,11 +8,11 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set
import grpc import grpc
from core.api.grpc.common_pb2 import ConfigOption
from core.api.grpc.services_pb2 import ServiceValidationMode from core.api.grpc.services_pb2 import ServiceValidationMode
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import CodeText, ConfigFrame, ListboxScroll from core.gui.widgets import CodeText, ConfigFrame, ListboxScroll
from core.gui.wrappers import ConfigOption
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -99,7 +99,7 @@ class ConfigServiceConfigDialog(Dialog):
service_config = self.canvas_node.config_service_configs.get( service_config = self.canvas_node.config_service_configs.get(
self.service_name, {} 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()} self.default_config = {x.name: x.value for x in self.config.values()}
custom_config = service_config.get("config") custom_config = service_config.get("config")
if custom_config: if custom_config:

View file

@ -8,12 +8,11 @@ from typing import TYPE_CHECKING, Dict, List, Optional
import grpc 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.dialogs.dialog import Dialog
from core.gui.images import ImageEnum, Images from core.gui.images import ImageEnum, Images
from core.gui.themes import PADX, PADY from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame from core.gui.widgets import ConfigFrame
from core.gui.wrappers import ConfigOption, Node
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application

View file

@ -5,11 +5,11 @@ import tkinter as tk
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
from core.api.grpc import core_pb2
from core.gui import validation from core.gui import validation
from core.gui.dialogs.colorpicker import ColorPickerDialog from core.gui.dialogs.colorpicker import ColorPickerDialog
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY from core.gui.themes import PADX, PADY
from core.gui.wrappers import Interface, Link, LinkOptions
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -21,7 +21,7 @@ def get_int(var: tk.StringVar) -> Optional[int]:
if value != "": if value != "":
return int(value) return int(value)
else: else:
return None return 0
def get_float(var: tk.StringVar) -> Optional[float]: def get_float(var: tk.StringVar) -> Optional[float]:
@ -29,14 +29,15 @@ def get_float(var: tk.StringVar) -> Optional[float]:
if value != "": if value != "":
return float(value) return float(value)
else: else:
return None return 0.0
class LinkConfigurationDialog(Dialog): class LinkConfigurationDialog(Dialog):
def __init__(self, app: "Application", edge: "CanvasEdge") -> None: def __init__(self, app: "Application", edge: "CanvasEdge") -> None:
super().__init__(app, "Link Configuration") super().__init__(app, "Link Configuration")
self.edge: "CanvasEdge" = edge 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: if self.is_symmetric:
symmetry_var = tk.StringVar(value=">>") symmetry_var = tk.StringVar(value=">>")
else: else:
@ -223,32 +224,32 @@ class LinkConfigurationDialog(Dialog):
delay = get_int(self.delay) delay = get_int(self.delay)
duplicate = get_int(self.duplicate) duplicate = get_int(self.duplicate)
loss = get_float(self.loss) loss = get_float(self.loss)
options = core_pb2.LinkOptions( options = LinkOptions(
bandwidth=bandwidth, jitter=jitter, delay=delay, dup=duplicate, loss=loss bandwidth=bandwidth, jitter=jitter, delay=delay, dup=duplicate, loss=loss
) )
link.options.CopyFrom(options) link.options = options
iface1_id = None iface1_id = None
if link.HasField("iface1"): if link.iface1:
iface1_id = link.iface1.id iface1_id = link.iface1.id
iface2_id = None iface2_id = None
if link.HasField("iface2"): if link.iface2:
iface2_id = link.iface2.id iface2_id = link.iface2.id
if not self.is_symmetric: if not self.is_symmetric:
link.options.unidirectional = True link.options.unidirectional = True
asym_iface1 = None asym_iface1 = None
if iface1_id: if iface1_id:
asym_iface1 = core_pb2.Interface(id=iface1_id) asym_iface1 = Interface(id=iface1_id)
asym_iface2 = None asym_iface2 = None
if iface2_id: 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_bandwidth = get_int(self.down_bandwidth)
down_jitter = get_int(self.down_jitter) down_jitter = get_int(self.down_jitter)
down_delay = get_int(self.down_delay) down_delay = get_int(self.down_delay)
down_duplicate = get_int(self.down_duplicate) down_duplicate = get_int(self.down_duplicate)
down_loss = get_float(self.down_loss) down_loss = get_float(self.down_loss)
options = core_pb2.LinkOptions( options = LinkOptions(
bandwidth=down_bandwidth, bandwidth=down_bandwidth,
jitter=down_jitter, jitter=down_jitter,
delay=down_delay, delay=down_delay,
@ -256,7 +257,7 @@ class LinkConfigurationDialog(Dialog):
loss=down_loss, loss=down_loss,
unidirectional=True, unidirectional=True,
) )
self.edge.asymmetric_link = core_pb2.Link( self.edge.asymmetric_link = Link(
node1_id=link.node2_id, node1_id=link.node2_id,
node2_id=link.node1_id, node2_id=link.node1_id,
iface1=asym_iface1, iface1=asym_iface1,
@ -267,7 +268,7 @@ class LinkConfigurationDialog(Dialog):
link.options.unidirectional = False link.options.unidirectional = False
self.edge.asymmetric_link = None 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 session_id = self.app.core.session_id
self.app.core.client.edit_link( self.app.core.client.edit_link(
session_id, session_id,
@ -316,7 +317,7 @@ class LinkConfigurationDialog(Dialog):
color = self.app.canvas.itemcget(self.edge.id, "fill") color = self.app.canvas.itemcget(self.edge.id, "fill")
self.color.set(color) self.color.set(color)
link = self.edge.link link = self.edge.link
if link.HasField("options"): if link.options:
self.bandwidth.set(str(link.options.bandwidth)) self.bandwidth.set(str(link.options.bandwidth))
self.jitter.set(str(link.options.jitter)) self.jitter.set(str(link.options.jitter))
self.duplicate.set(str(link.options.dup)) self.duplicate.set(str(link.options.dup))

View file

@ -6,11 +6,10 @@ from typing import TYPE_CHECKING, Dict, Optional
import grpc 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.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame from core.gui.widgets import ConfigFrame
from core.gui.wrappers import ConfigOption, Node
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application

View file

@ -4,12 +4,11 @@ from typing import TYPE_CHECKING, Dict, Optional
import grpc 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.api.grpc.mobility_pb2 import MobilityAction
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.images import ImageEnum from core.gui.images import ImageEnum
from core.gui.themes import PADX, PADY from core.gui.themes import PADX, PADY
from core.gui.wrappers import ConfigOption, Node
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application

View file

@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Dict, Optional
import netaddr import netaddr
from PIL.ImageTk import PhotoImage from PIL.ImageTk import PhotoImage
from core.api.grpc.core_pb2 import Node
from core.gui import nodeutils, validation from core.gui import nodeutils, validation
from core.gui.appconfig import ICONS_PATH from core.gui.appconfig import ICONS_PATH
from core.gui.dialogs.dialog import Dialog 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.nodeutils import NodeUtils
from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import ListboxScroll, image_chooser from core.gui.widgets import ListboxScroll, image_chooser
from core.gui.wrappers import Node
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application

View file

@ -5,10 +5,10 @@ from typing import TYPE_CHECKING, Dict, Optional
import grpc import grpc
from core.api.grpc.common_pb2 import ConfigOption
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame from core.gui.widgets import ConfigFrame
from core.gui.wrappers import ConfigOption
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -28,7 +28,7 @@ class SessionOptionsDialog(Dialog):
try: 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) response = self.app.core.client.get_session_options(session_id)
return response.config return ConfigOption.from_dict(response.config)
except grpc.RpcError as e: except grpc.RpcError as e:
self.app.show_grpc_exception("Get Session Options Error", e) self.app.show_grpc_exception("Get Session Options Error", e)
self.has_error = True self.has_error = True

View file

@ -5,12 +5,11 @@ from typing import TYPE_CHECKING, List, Optional
import grpc 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.dialogs.dialog import Dialog
from core.gui.images import ImageEnum, Images from core.gui.images import ImageEnum, Images
from core.gui.task import ProgressTask from core.gui.task import ProgressTask
from core.gui.themes import PADX, PADY from core.gui.themes import PADX, PADY
from core.gui.wrappers import SessionState, SessionSummary
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -33,7 +32,7 @@ class SessionsDialog(Dialog):
try: try:
response = self.app.core.client.get_sessions() response = self.app.core.client.get_sessions()
logging.info("sessions: %s", response) logging.info("sessions: %s", response)
return response.sessions return [SessionSummary.from_proto(x) for x in response.sessions]
except grpc.RpcError as e: except grpc.RpcError as e:
self.app.show_grpc_exception("Get Sessions Error", e) self.app.show_grpc_exception("Get Sessions Error", e)
self.destroy() self.destroy()
@ -82,7 +81,7 @@ class SessionsDialog(Dialog):
self.tree.heading("nodes", text="Node Count") self.tree.heading("nodes", text="Node Count")
for index, session in enumerate(self.sessions): 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( self.tree.insert(
"", "",
tk.END, tk.END,

View file

@ -3,11 +3,10 @@ from typing import TYPE_CHECKING, Dict, Optional
import grpc 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.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame from core.gui.widgets import ConfigFrame
from core.gui.wrappers import ConfigOption, Node
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application

View file

@ -1,9 +1,9 @@
import tkinter as tk import tkinter as tk
from typing import TYPE_CHECKING, Optional 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.frames.base import DetailsFrame, InfoFrameBase
from core.gui.utils import bandwidth_text from core.gui.utils import bandwidth_text
from core.gui.wrappers import Interface
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -62,7 +62,7 @@ class EdgeInfoFrame(InfoFrameBase):
ip6 = f"{iface2.ip6}/{iface2.ip6_mask}" if iface2.ip6 else "" ip6 = f"{iface2.ip6}/{iface2.ip6_mask}" if iface2.ip6 else ""
frame.add_detail("IP6", ip6) frame.add_detail("IP6", ip6)
if link.HasField("options"): if link.options:
frame.add_separator() frame.add_separator()
bandwidth = bandwidth_text(options.bandwidth) bandwidth = bandwidth_text(options.bandwidth)
frame.add_detail("Bandwidth", bandwidth) frame.add_detail("Bandwidth", bandwidth)

View file

@ -1,8 +1,8 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from core.api.grpc.core_pb2 import NodeType
from core.gui.frames.base import DetailsFrame, InfoFrameBase from core.gui.frames.base import DetailsFrame, InfoFrameBase
from core.gui.nodeutils import NodeUtils from core.gui.nodeutils import NodeUtils
from core.gui.wrappers import NodeType
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application

View file

@ -3,14 +3,13 @@ import math
import tkinter as tk import tkinter as tk
from typing import TYPE_CHECKING, Optional, Tuple 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 import themes
from core.gui.dialogs.linkconfig import LinkConfigurationDialog from core.gui.dialogs.linkconfig import LinkConfigurationDialog
from core.gui.frames.link import EdgeInfoFrame, WirelessEdgeInfoFrame from core.gui.frames.link import EdgeInfoFrame, WirelessEdgeInfoFrame
from core.gui.graph import tags from core.gui.graph import tags
from core.gui.nodeutils import NodeUtils from core.gui.nodeutils import NodeUtils
from core.gui.utils import bandwidth_text from core.gui.utils import bandwidth_text
from core.gui.wrappers import Interface, Link
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.graph.graph import CanvasGraph from core.gui.graph.graph import CanvasGraph
@ -305,7 +304,7 @@ class CanvasEdge(Edge):
self.link = link self.link = link
self.draw_labels() self.draw_labels()
def iface_label(self, iface: core_pb2.Interface) -> str: def iface_label(self, iface: Interface) -> str:
label = "" label = ""
if iface.name and self.canvas.show_iface_names.get(): if iface.name and self.canvas.show_iface_names.get():
label = f"{iface.name}" label = f"{iface.name}"
@ -319,10 +318,10 @@ class CanvasEdge(Edge):
def create_node_labels(self) -> Tuple[str, str]: def create_node_labels(self) -> Tuple[str, str]:
label1 = None label1 = None
if self.link.HasField("iface1"): if self.link.iface1:
label1 = self.iface_label(self.link.iface1) label1 = self.iface_label(self.link.iface1)
label2 = None label2 = None
if self.link.HasField("iface2"): if self.link.iface2:
label2 = self.iface_label(self.link.iface2) label2 = self.iface_label(self.link.iface2)
return label1, label2 return label1, label2
@ -417,6 +416,8 @@ class CanvasEdge(Edge):
dialog.show() dialog.show()
def draw_link_options(self): def draw_link_options(self):
if not self.link.options:
return
options = self.link.options options = self.link.options
lines = [] lines = []
bandwidth = options.bandwidth bandwidth = options.bandwidth

View file

@ -7,14 +7,7 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
from PIL import Image from PIL import Image
from PIL.ImageTk import PhotoImage from PIL.ImageTk import PhotoImage
from core.api.grpc.core_pb2 import ( from core.api.grpc.core_pb2 import ThroughputsEvent
Interface,
Link,
LinkType,
Node,
Session,
ThroughputsEvent,
)
from core.gui.dialogs.shapemod import ShapeDialog from core.gui.dialogs.shapemod import ShapeDialog
from core.gui.graph import tags from core.gui.graph import tags
from core.gui.graph.edges import ( 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.graph.shapeutils import ShapeType, is_draw_shape, is_marker
from core.gui.images import ImageEnum, TypeToImage from core.gui.images import ImageEnum, TypeToImage
from core.gui.nodeutils import NodeDraw, NodeUtils from core.gui.nodeutils import NodeDraw, NodeUtils
from core.gui.wrappers import Interface, Link, LinkType, Node
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -134,12 +128,7 @@ class CanvasGraph(tk.Canvas):
) )
self.configure(scrollregion=self.bbox(tk.ALL)) self.configure(scrollregion=self.bbox(tk.ALL))
def reset_and_redraw(self, session: Session) -> None: def reset_and_redraw(self, nodes: List[Node], links: List[Link]) -> None:
"""
Reset the private variables CanvasGraph object, redraw nodes given the new grpc
client.
:param session: session to draw
"""
# reset view options to default state # reset view options to default state
self.show_node_labels.set(True) self.show_node_labels.set(True)
self.show_link_labels.set(True) self.show_link_labels.set(True)
@ -164,7 +153,7 @@ class CanvasGraph(tk.Canvas):
self.wireless_edges.clear() self.wireless_edges.clear()
self.wireless_network.clear() self.wireless_network.clear()
self.drawing_edge = None self.drawing_edge = None
self.draw_session(session) self.draw_session(nodes, links)
def setup_bindings(self) -> None: def setup_bindings(self) -> None:
""" """
@ -251,12 +240,12 @@ class CanvasGraph(tk.Canvas):
dst.edges.add(edge) dst.edges.add(edge)
self.edges[edge.token] = edge self.edges[edge.token] = edge
self.core.links[edge.token] = edge self.core.links[edge.token] = edge
if link.HasField("iface1"): if link.iface1:
iface1 = link.iface1 iface1 = link.iface1
self.core.iface_to_edge[(node1.id, iface1.id)] = token self.core.iface_to_edge[(node1.id, iface1.id)] = token
src.ifaces[iface1.id] = iface1 src.ifaces[iface1.id] = iface1
edge.src_iface = iface1 edge.src_iface = iface1
if link.HasField("iface2"): if link.iface2:
iface2 = link.iface2 iface2 = link.iface2
self.core.iface_to_edge[(node2.id, iface2.id)] = edge.token self.core.iface_to_edge[(node2.id, iface2.id)] = edge.token
dst.ifaces[iface2.id] = iface2 dst.ifaces[iface2.id] = iface2
@ -337,19 +326,19 @@ class CanvasGraph(tk.Canvas):
self.nodes[node.id] = node self.nodes[node.id] = node
self.core.canvas_nodes[core_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 session.
""" """
# draw existing nodes # draw existing nodes
for core_node in session.nodes: for core_node in nodes:
# peer to peer node is not drawn on the GUI # peer to peer node is not drawn on the GUI
if NodeUtils.is_ignore_node(core_node.type): if NodeUtils.is_ignore_node(core_node.type):
continue continue
self.add_core_node(core_node) self.add_core_node(core_node)
# draw existing links # draw existing links
for link in session.links: for link in links:
logging.debug("drawing link: %s", link) logging.debug("drawing link: %s", link)
canvas_node1 = self.core.canvas_nodes[link.node1_id] canvas_node1 = self.core.canvas_nodes[link.node1_id]
canvas_node2 = self.core.canvas_nodes[link.node2_id] canvas_node2 = self.core.canvas_nodes[link.node2_id]
@ -987,12 +976,12 @@ class CanvasGraph(tk.Canvas):
copy_edge = self.edges[token] copy_edge = self.edges[token]
copy_link = copy_edge.link copy_link = copy_edge.link
options = edge.link.options options = edge.link.options
copy_link.options.CopyFrom(options) copy_link.options = deepcopy(options)
iface1_id = None iface1_id = None
if copy_link.HasField("iface1"): if copy_link.iface1:
iface1_id = copy_link.iface1.id iface1_id = copy_link.iface1.id
iface2_id = None iface2_id = None
if copy_link.HasField("iface2"): if copy_link.iface2:
iface2_id = copy_link.iface2.id iface2_id = copy_link.iface2.id
if not options.unidirectional: if not options.unidirectional:
copy_edge.asymmetric_link = None copy_edge.asymmetric_link = None

View file

@ -6,8 +6,6 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple
import grpc import grpc
from PIL.ImageTk import PhotoImage 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.api.grpc.services_pb2 import NodeServiceData
from core.gui import themes from core.gui import themes
from core.gui.dialogs.emaneconfig import EmaneConfigDialog 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.graph.tooltip import CanvasTooltip
from core.gui.images import ImageEnum from core.gui.images import ImageEnum
from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils
from core.gui.wrappers import ConfigOption, Interface, Node, NodeType
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application

View file

@ -5,8 +5,8 @@ from typing import Dict, Optional, Tuple
from PIL import Image from PIL import Image
from PIL.ImageTk import PhotoImage from PIL.ImageTk import PhotoImage
from core.api.grpc.core_pb2 import NodeType
from core.gui.appconfig import LOCAL_ICONS_PATH from core.gui.appconfig import LOCAL_ICONS_PATH
from core.gui.wrappers import NodeType
class Images: class Images:

View file

@ -4,9 +4,9 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple
import netaddr import netaddr
from netaddr import EUI, IPNetwork 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.graph.node import CanvasNode
from core.gui.nodeutils import NodeUtils from core.gui.nodeutils import NodeUtils
from core.gui.wrappers import Interface, Link, Node
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -89,10 +89,10 @@ class InterfaceManager:
remaining_subnets = set() remaining_subnets = set()
for edge in self.app.core.links.values(): for edge in self.app.core.links.values():
link = edge.link link = edge.link
if link.HasField("iface1"): if link.iface1:
subnets = self.get_subnets(link.iface1) subnets = self.get_subnets(link.iface1)
remaining_subnets.add(subnets) remaining_subnets.add(subnets)
if link.HasField("iface2"): if link.iface2:
subnets = self.get_subnets(link.iface2) subnets = self.get_subnets(link.iface2)
remaining_subnets.add(subnets) remaining_subnets.add(subnets)
@ -100,9 +100,9 @@ class InterfaceManager:
# or remove used indexes from subnet # or remove used indexes from subnet
ifaces = [] ifaces = []
for link in links: for link in links:
if link.HasField("iface1"): if link.iface1:
ifaces.append(link.iface1) ifaces.append(link.iface1)
if link.HasField("iface2"): if link.iface2:
ifaces.append(link.iface2) ifaces.append(link.iface2)
for iface in ifaces: for iface in ifaces:
subnets = self.get_subnets(iface) subnets = self.get_subnets(iface)
@ -117,9 +117,9 @@ class InterfaceManager:
def joined(self, links: List[Link]) -> None: def joined(self, links: List[Link]) -> None:
ifaces = [] ifaces = []
for link in links: for link in links:
if link.HasField("iface1"): if link.iface1:
ifaces.append(link.iface1) ifaces.append(link.iface1)
if link.HasField("iface2"): if link.iface2:
ifaces.append(link.iface2) ifaces.append(link.iface2)
# add to used subnets and mark used indexes # add to used subnets and mark used indexes

View file

@ -3,9 +3,9 @@ from typing import List, Optional, Set
from PIL.ImageTk import PhotoImage from PIL.ImageTk import PhotoImage
from core.api.grpc.core_pb2 import Node, NodeType
from core.gui.appconfig import CustomNode, GuiConfig from core.gui.appconfig import CustomNode, GuiConfig
from core.gui.images import ImageEnum, Images, TypeToImage from core.gui.images import ImageEnum, Images, TypeToImage
from core.gui.wrappers import Node, NodeType
ICON_SIZE: int = 48 ICON_SIZE: int = 48
ANTENNA_SIZE: int = 32 ANTENNA_SIZE: int = 32
@ -17,7 +17,7 @@ class NodeDraw:
self.image: Optional[PhotoImage] = None self.image: Optional[PhotoImage] = None
self.image_enum: Optional[ImageEnum] = None self.image_enum: Optional[ImageEnum] = None
self.image_file: Optional[str] = None self.image_file: Optional[str] = None
self.node_type: NodeType = None self.node_type: Optional[NodeType] = None
self.model: Optional[str] = None self.model: Optional[str] = None
self.services: Set[str] = set() self.services: Set[str] = set()
self.label: Optional[str] = None self.label: Optional[str] = None

View file

@ -5,9 +5,9 @@ import tkinter as tk
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, List, Optional 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.dialogs.alerts import AlertsDialog
from core.gui.themes import Styles from core.gui.themes import Styles
from core.gui.wrappers import ExceptionEvent, ExceptionLevel
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -69,7 +69,7 @@ class StatusBar(ttk.Frame):
def add_alert(self, event: ExceptionEvent) -> None: def add_alert(self, event: ExceptionEvent) -> None:
self.core_alarms.append(event) self.core_alarms.append(event)
level = event.exception_event.level level = event.level
self._set_alert_style(level) self._set_alert_style(level)
label = f"Alerts ({len(self.core_alarms)})" label = f"Alerts ({len(self.core_alarms)})"
self.alerts_button.config(text=label, style=self.alert_style) self.alerts_button.config(text=label, style=self.alert_style)

View file

@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Callable, List, Optional
from PIL.ImageTk import PhotoImage from PIL.ImageTk import PhotoImage
from core.api.grpc import core_pb2
from core.gui.dialogs.colorpicker import ColorPickerDialog from core.gui.dialogs.colorpicker import ColorPickerDialog
from core.gui.dialogs.runtool import RunToolDialog from core.gui.dialogs.runtool import RunToolDialog
from core.gui.graph import tags from core.gui.graph import tags
@ -300,15 +299,15 @@ class Toolbar(ttk.Frame):
) )
task.start() task.start()
def start_callback(self, response: core_pb2.StartSessionResponse) -> None: def start_callback(self, result: bool, exceptions: List[str]) -> None:
if response.result: if result:
self.set_runtime() self.set_runtime()
self.app.core.set_metadata() self.app.core.set_metadata()
self.app.core.show_mobility_players() self.app.core.show_mobility_players()
else: else:
enable_buttons(self.design_frame, enabled=True) enable_buttons(self.design_frame, enabled=True)
if response.exceptions: if exceptions:
message = "\n".join(response.exceptions) message = "\n".join(exceptions)
self.app.show_error("Start Session Error", message) self.app.show_error("Start Session Error", message)
def set_runtime(self) -> None: def set_runtime(self) -> None:
@ -405,7 +404,7 @@ class Toolbar(ttk.Frame):
) )
task.start() task.start()
def stop_callback(self, response: core_pb2.StopSessionResponse) -> None: def stop_callback(self, result: bool) -> None:
self.set_design() self.set_design()
self.app.canvas.stopped_session() self.app.canvas.stopped_session()

View file

@ -5,12 +5,10 @@ from pathlib import Path
from tkinter import filedialog, font, ttk from tkinter import filedialog, font, ttk
from typing import TYPE_CHECKING, Any, Callable, Dict, Set, Type 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 import themes, validation
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.wrappers import ConfigOption, ConfigOptionType
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -110,7 +108,7 @@ class ConfigFrame(ttk.Notebook):
label = ttk.Label(tab.frame, text=option.label) 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="w")
value = tk.StringVar() value = tk.StringVar()
if option.type == core_pb2.ConfigOptionType.BOOL: if option.type == ConfigOptionType.BOOL:
select = ("On", "Off") select = ("On", "Off")
state = "readonly" if self.enabled else tk.DISABLED state = "readonly" if self.enabled else tk.DISABLED
combobox = ttk.Combobox( combobox = ttk.Combobox(
@ -129,7 +127,7 @@ class ConfigFrame(ttk.Notebook):
tab.frame, textvariable=value, values=select, state=state tab.frame, textvariable=value, values=select, state=state
) )
combobox.grid(row=index, column=1, sticky="ew") combobox.grid(row=index, column=1, sticky="ew")
elif option.type == core_pb2.ConfigOptionType.STRING: elif option.type == ConfigOptionType.STRING:
value.set(option.value) value.set(option.value)
state = tk.NORMAL if self.enabled else tk.DISABLED state = tk.NORMAL if self.enabled else tk.DISABLED
if "file" in option.label: if "file" in option.label:
@ -153,7 +151,7 @@ class ConfigFrame(ttk.Notebook):
tab.frame, textvariable=value, state=state tab.frame, textvariable=value, state=state
) )
entry.grid(row=index, column=1, sticky="ew") entry.grid(row=index, column=1, sticky="ew")
elif option.type == core_pb2.ConfigOptionType.FLOAT: elif option.type == ConfigOptionType.FLOAT:
value.set(option.value) value.set(option.value)
state = tk.NORMAL if self.enabled else tk.DISABLED state = tk.NORMAL if self.enabled else tk.DISABLED
entry = validation.PositiveFloatEntry( entry = validation.PositiveFloatEntry(
@ -169,7 +167,7 @@ class ConfigFrame(ttk.Notebook):
option = self.config[key] option = self.config[key]
value = self.values[key] value = self.values[key]
config_value = value.get() config_value = value.get()
if option.type == core_pb2.ConfigOptionType.BOOL: if option.type == ConfigOptionType.BOOL:
if config_value == "On": if config_value == "On":
option.value = "1" option.value = "1"
else: else:
@ -182,7 +180,7 @@ class ConfigFrame(ttk.Notebook):
for name, data in config.items(): for name, data in config.items():
option = self.config[name] option = self.config[name]
value = self.values[name] value = self.values[name]
if option.type == core_pb2.ConfigOptionType.BOOL: if option.type == ConfigOptionType.BOOL:
if data == "1": if data == "1":
data = "On" data = "On"
else: else:

View file

@ -1,8 +1,22 @@
from dataclasses import dataclass from dataclasses import dataclass, field
from enum import Enum 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): class SessionState(Enum):
@ -30,6 +44,292 @@ class NodeType(Enum):
LXC = 16 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 @dataclass
class Hook: class Hook:
state: SessionState state: SessionState
@ -78,8 +378,8 @@ class Node:
type: NodeType type: NodeType
model: str = None model: str = None
position: Position = None position: Position = None
services: List[str] = None services: List[str] = field(default_factory=list)
config_services: List[str] = None config_services: List[str] = field(default_factory=list)
emane: str = None emane: str = None
icon: str = None icon: str = None
image: str = None image: str = None
@ -120,7 +420,32 @@ class Node:
icon=self.icon, icon=self.icon,
image=self.image, image=self.image,
server=self.server, server=self.server,
geo=self.geo.to_proto(),
dir=self.dir, dir=self.dir,
channel=self.channel, 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),
)