Merge pull request #499 from coreemu/enhancement/pygui-wrappers

Enhancement/pygui wrappers
This commit is contained in:
bharnden 2020-07-26 23:31:05 -07:00 committed by GitHub
commit 160498336c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 802 additions and 214 deletions

View file

@ -12,33 +12,15 @@ 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 (
from core.api.grpc.common_pb2 import ConfigOption client,
from core.api.grpc.configservices_pb2 import ConfigService, ConfigServiceConfig configservices_pb2,
from core.api.grpc.core_pb2 import ( core_pb2,
CpuUsageEvent, emane_pb2,
Event, mobility_pb2,
ExceptionEvent, services_pb2,
Hook, wlan_pb2,
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.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 import appconfig
from core.gui.appconfig import CoreServer, Observer from core.gui.appconfig import CoreServer, Observer
from core.gui.dialogs.emaneinstall import EmaneInstallDialog from core.gui.dialogs.emaneinstall import EmaneInstallDialog
@ -51,6 +33,26 @@ 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 (
ConfigOption,
ConfigService,
ExceptionEvent,
Hook,
Interface,
Link,
LinkEvent,
LinkType,
MessageType,
Node,
NodeEvent,
NodeServiceData,
NodeType,
Position,
Session,
SessionLocation,
SessionState,
ThroughputsEvent,
)
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -153,7 +155,7 @@ class CoreClient:
for observer in self.app.guiconfig.observers: for observer in self.app.guiconfig.observers:
self.custom_observers[observer.name] = observer 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: if event.source == GUI_SOURCE:
return return
if event.session_id != self.session_id: if event.session_id != self.session_id:
@ -165,12 +167,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 +187,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)
@ -276,7 +281,8 @@ class CoreClient:
CPU_USAGE_DELAY, self.handle_cpu_event 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: if event.session_id != self.session_id:
logging.warning( logging.warning(
"ignoring throughput event session(%s) current(%s)", "ignoring throughput event session(%s) current(%s)",
@ -287,7 +293,7 @@ class CoreClient:
logging.debug("handling throughputs event: %s", event) logging.debug("handling throughputs event: %s", event)
self.app.after(0, self.app.canvas.set_throughputs, 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) self.app.after(0, self.app.statusbar.set_cpu, event.usage)
def handle_exception_event(self, event: ExceptionEvent) -> None: def handle_exception_event(self, event: ExceptionEvent) -> None:
@ -306,7 +312,7 @@ class CoreClient:
# get session data # get session data
try: try:
response = self.client.get_session(self.session_id) response = self.client.get_session(self.session_id)
session = response.session session = Session.from_proto(response.session)
self.state = session.state self.state = 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 +330,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)
@ -332,12 +338,13 @@ class CoreClient:
# get hooks # get hooks
response = self.client.get_hooks(self.session_id) 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 self.hooks[hook.file] = hook
# 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)
@ -350,7 +357,7 @@ class CoreClient:
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)
@ -359,16 +366,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)
@ -500,7 +507,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()
@ -511,7 +517,7 @@ class CoreClient:
# get config service informations # get config service informations
response = self.client.get_config_services() response = self.client.get_config_services()
for service in response.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( group_services = self.config_services_groups.setdefault(
service.group, set() service.group, set()
) )
@ -545,8 +551,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)
@ -555,22 +562,21 @@ 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()
hooks = list(self.hooks.values()) hooks = [x.to_proto() for x in self.hooks.values()]
service_configs = self.get_service_configs_proto() service_configs = self.get_service_configs_proto()
file_configs = self.get_service_file_configs_proto() file_configs = self.get_service_file_configs_proto()
asymmetric_links = [ asymmetric_links = [
@ -581,14 +587,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,
@ -604,20 +611,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():
@ -701,7 +711,7 @@ class CoreClient:
logging.debug( logging.debug(
"get node(%s) %s service, response: %s", node_id, service_name, response "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( def set_node_service(
self, self,
@ -735,7 +745,7 @@ class CoreClient:
response, 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 response.service return NodeServiceData.from_proto(response.service)
def get_node_service_file( def get_node_service_file(
self, node_id: int, service_name: str, file_name: str self, node_id: int, service_name: str, file_name: str
@ -771,12 +781,9 @@ class CoreClient:
""" """
create nodes and links that have not been created yet create nodes and links that have not been created yet
""" """
node_protos = [x.core_node for x in self.canvas_nodes.values()] node_protos = [x.core_node.to_proto() for x in self.canvas_nodes.values()]
link_protos = [x.link for x in self.links.values()] link_protos = [x.link.to_proto() for x in self.links.values()]
if self.state != SessionState.DEFINITION: self.client.set_session_state(self.session_id, SessionState.DEFINITION.value)
self.client.set_session_state(self.session_id, SessionState.DEFINITION)
self.client.set_session_state(self.session_id, SessionState.DEFINITION)
for node_proto in node_protos: 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) logging.debug("create node: %s", response)
@ -823,7 +830,9 @@ class CoreClient:
config_proto.data, config_proto.data,
) )
for hook in self.hooks.values(): 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(): for config_proto in self.get_emane_model_configs_proto():
self.client.set_emane_model_config( self.client.set_emane_model_config(
self.session_id, self.session_id,
@ -917,7 +926,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
@ -948,13 +957,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(
@ -1000,35 +1003,35 @@ class CoreClient:
self.links[edge.token] = edge self.links[edge.token] = edge
logging.info("Add link between %s and %s", src_node.name, dst_node.name) 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 = [] configs = []
for canvas_node in self.canvas_nodes.values(): for canvas_node in self.canvas_nodes.values():
if canvas_node.core_node.type != NodeType.WIRELESS_LAN: if canvas_node.core_node.type != NodeType.WIRELESS_LAN:
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 = wlan_pb2.WlanConfig(node_id=node_id, config=config)
configs.append(wlan_config) configs.append(wlan_config)
return configs return configs
def get_mobility_configs_proto(self) -> List[MobilityConfig]: def get_mobility_configs_proto(self) -> List[mobility_pb2.MobilityConfig]:
configs = [] configs = []
for canvas_node in self.canvas_nodes.values(): for canvas_node in self.canvas_nodes.values():
if canvas_node.core_node.type != NodeType.WIRELESS_LAN: if canvas_node.core_node.type != NodeType.WIRELESS_LAN:
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 = mobility_pb2.MobilityConfig(
node_id=node_id, config=config
)
configs.append(mobility_config) configs.append(mobility_config)
return configs return configs
def get_emane_model_configs_proto(self) -> List[EmaneModelConfig]: def get_emane_model_configs_proto(self) -> List[emane_pb2.EmaneModelConfig]:
configs = [] configs = []
for canvas_node in self.canvas_nodes.values(): for canvas_node in self.canvas_nodes.values():
if canvas_node.core_node.type != NodeType.EMANE: if canvas_node.core_node.type != NodeType.EMANE:
@ -1036,16 +1039,16 @@ 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 = 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) configs.append(config_proto)
return configs return configs
def get_service_configs_proto(self) -> List[ServiceConfig]: def get_service_configs_proto(self) -> List[services_pb2.ServiceConfig]:
configs = [] configs = []
for canvas_node in self.canvas_nodes.values(): for canvas_node in self.canvas_nodes.values():
if not NodeUtils.is_container_node(canvas_node.core_node.type): if not NodeUtils.is_container_node(canvas_node.core_node.type):
@ -1054,7 +1057,7 @@ class CoreClient:
continue continue
node_id = canvas_node.core_node.id node_id = canvas_node.core_node.id
for name, config in canvas_node.service_configs.items(): for name, config in canvas_node.service_configs.items():
config_proto = ServiceConfig( config_proto = services_pb2.ServiceConfig(
node_id=node_id, node_id=node_id,
service=name, service=name,
directories=config.dirs, directories=config.dirs,
@ -1066,7 +1069,7 @@ class CoreClient:
configs.append(config_proto) configs.append(config_proto)
return configs return configs
def get_service_file_configs_proto(self) -> List[ServiceFileConfig]: def get_service_file_configs_proto(self) -> List[services_pb2.ServiceFileConfig]:
configs = [] configs = []
for canvas_node in self.canvas_nodes.values(): for canvas_node in self.canvas_nodes.values():
if not NodeUtils.is_container_node(canvas_node.core_node.type): if not NodeUtils.is_container_node(canvas_node.core_node.type):
@ -1076,13 +1079,15 @@ class CoreClient:
node_id = canvas_node.core_node.id node_id = canvas_node.core_node.id
for service, file_configs in canvas_node.service_file_configs.items(): for service, file_configs in canvas_node.service_file_configs.items():
for file, data in 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 node_id=node_id, service=service, file=file, data=data
) )
configs.append(config_proto) configs.append(config_proto)
return configs 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 = [] config_service_protos = []
for canvas_node in self.canvas_nodes.values(): for canvas_node in self.canvas_nodes.values():
if not NodeUtils.is_container_node(canvas_node.core_node.type): if not NodeUtils.is_container_node(canvas_node.core_node.type):
@ -1092,7 +1097,7 @@ class CoreClient:
node_id = canvas_node.core_node.id node_id = canvas_node.core_node.id
for name, service_config in canvas_node.config_service_configs.items(): for name, service_config in canvas_node.config_service_configs.items():
config = service_config.get("config", {}) config = service_config.get("config", {})
config_proto = ConfigServiceConfig( config_proto = configservices_pb2.ConfigServiceConfig(
node_id=node_id, node_id=node_id,
name=name, name=name,
templates=service_config["templates"], templates=service_config["templates"],
@ -1113,7 +1118,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)
@ -1123,7 +1128,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
@ -1142,7 +1147,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,10 @@ 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.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, ServiceValidationMode
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -99,7 +98,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

@ -2,10 +2,10 @@ 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.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, ListboxScroll from core.gui.widgets import CodeText, ListboxScroll
from core.gui.wrappers import Hook, SessionState
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -16,8 +16,9 @@ class HookDialog(Dialog):
super().__init__(app, "Hook", master=master) super().__init__(app, "Hook", master=master)
self.name: tk.StringVar = tk.StringVar() self.name: tk.StringVar = tk.StringVar()
self.codetext: Optional[CodeText] = None 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.state: tk.StringVar = tk.StringVar()
self.editing: bool = False
self.draw() self.draw()
def draw(self) -> None: def draw(self) -> None:
@ -34,8 +35,8 @@ class HookDialog(Dialog):
label.grid(row=0, column=0, sticky="ew", padx=PADX) label.grid(row=0, column=0, sticky="ew", padx=PADX)
entry = ttk.Entry(frame, textvariable=self.name) entry = ttk.Entry(frame, textvariable=self.name)
entry.grid(row=0, column=1, sticky="ew", padx=PADX) entry.grid(row=0, column=1, sticky="ew", padx=PADX)
values = tuple(x for x in core_pb2.SessionState.Enum.keys() if x != "NONE") values = tuple(x.name for x in SessionState)
initial_state = core_pb2.SessionState.Enum.Name(core_pb2.SessionState.RUNTIME) initial_state = SessionState.RUNTIME.name
self.state.set(initial_state) self.state.set(initial_state)
self.name.set(f"{initial_state.lower()}_hook.sh") self.name.set(f"{initial_state.lower()}_hook.sh")
combobox = ttk.Combobox( combobox = ttk.Combobox(
@ -67,23 +68,30 @@ class HookDialog(Dialog):
button.grid(row=0, column=1, sticky="ew") button.grid(row=0, column=1, sticky="ew")
def state_change(self, event: tk.Event) -> None: def state_change(self, event: tk.Event) -> None:
if self.editing:
return
state_name = self.state.get() state_name = self.state.get()
self.name.set(f"{state_name.lower()}_hook.sh") 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.hook = hook
self.name.set(hook.file) self.name.set(hook.file)
self.codetext.text.delete(1.0, tk.END) self.codetext.text.delete(1.0, tk.END)
self.codetext.text.insert(tk.END, hook.data) 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) self.state.set(state_name)
def save(self) -> None: def save(self) -> None:
data = self.codetext.text.get("1.0", tk.END).strip() data = self.codetext.text.get("1.0", tk.END).strip()
state_value = core_pb2.SessionState.Enum.Value(self.state.get()) state = SessionState[self.state.get()]
self.hook.file = self.name.get() file_name = self.name.get()
self.hook.data = data if self.editing:
self.hook.state = state_value 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() self.destroy()
@ -94,6 +102,7 @@ class HooksDialog(Dialog):
self.edit_button: Optional[ttk.Button] = None self.edit_button: Optional[ttk.Button] = None
self.delete_button: Optional[ttk.Button] = None self.delete_button: Optional[ttk.Button] = None
self.selected: Optional[str] = None self.selected: Optional[str] = None
self.selected_index: Optional[int] = None
self.draw() self.draw()
def draw(self) -> None: def draw(self) -> None:
@ -133,10 +142,13 @@ class HooksDialog(Dialog):
self.listbox.insert(tk.END, hook.file) self.listbox.insert(tk.END, hook.file)
def click_edit(self) -> None: 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 = HookDialog(self, self.app)
dialog.set(hook) dialog.set(hook)
dialog.show() 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: def click_delete(self) -> None:
del self.app.core.hooks[self.selected] del self.app.core.hooks[self.selected]
@ -146,11 +158,12 @@ class HooksDialog(Dialog):
def select(self, event: tk.Event) -> None: def select(self, event: tk.Event) -> None:
if self.listbox.curselection(): if self.listbox.curselection():
index = self.listbox.curselection()[0] self.selected_index = self.listbox.curselection()[0]
self.selected = self.listbox.get(index) self.selected = self.listbox.get(self.selected_index)
self.edit_button.config(state=tk.NORMAL) self.edit_button.config(state=tk.NORMAL)
self.delete_button.config(state=tk.NORMAL) self.delete_button.config(state=tk.NORMAL)
else: else:
self.selected = None self.selected = None
self.selected_index = None
self.edit_button.config(state=tk.DISABLED) self.edit_button.config(state=tk.DISABLED)
self.delete_button.config(state=tk.DISABLED) self.delete_button.config(state=tk.DISABLED)

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,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.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, MobilityAction, Node
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -151,7 +149,7 @@ class MobilityPlayerDialog(Dialog):
session_id = self.app.core.session_id session_id = self.app.core.session_id
try: try:
self.app.core.client.mobility_action( 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: except grpc.RpcError as e:
self.app.show_grpc_exception("Mobility Error", e) self.app.show_grpc_exception("Mobility Error", e)
@ -161,7 +159,7 @@ class MobilityPlayerDialog(Dialog):
session_id = self.app.core.session_id session_id = self.app.core.session_id
try: try:
self.app.core.client.mobility_action( 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: except grpc.RpcError as e:
self.app.show_grpc_exception("Mobility Error", e) self.app.show_grpc_exception("Mobility Error", e)
@ -171,7 +169,7 @@ class MobilityPlayerDialog(Dialog):
session_id = self.app.core.session_id session_id = self.app.core.session_id
try: try:
self.app.core.client.mobility_action( 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: except grpc.RpcError as e:
self.app.show_grpc_exception("Mobility Error", e) self.app.show_grpc_exception("Mobility Error", e)

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

@ -7,12 +7,12 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
import grpc import grpc
from PIL.ImageTk import PhotoImage 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.copyserviceconfig import CopyServiceConfigDialog
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 FRAME_PAD, PADX, PADY from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import CodeText, ListboxScroll from core.gui.widgets import CodeText, ListboxScroll
from core.gui.wrappers import NodeServiceData, ServiceValidationMode
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -72,7 +72,7 @@ class ServiceConfigDialog(Dialog):
self.service_file_data: Optional[CodeText] = None self.service_file_data: Optional[CodeText] = None
self.validation_period_entry: Optional[ttk.Entry] = None self.validation_period_entry: Optional[ttk.Entry] = None
self.original_service_files: Dict[str, str] = {} 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.temp_service_files: Dict[str, str] = {}
self.modified_files: Set[str] = set() self.modified_files: Set[str] = set()
self.has_error: bool = False self.has_error: bool = False

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,6 @@ 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 (
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 +22,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, Session, ThroughputsEvent
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -135,11 +128,6 @@ 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, session: Session) -> 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)
@ -251,12 +239,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
@ -343,6 +331,7 @@ class CanvasGraph(tk.Canvas):
""" """
# draw existing nodes # draw existing nodes
for core_node in session.nodes: for core_node in session.nodes:
logging.debug("drawing node: %s", core_node)
# 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
@ -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,9 +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.gui import themes from core.gui import themes
from core.gui.dialogs.emaneconfig import EmaneConfigDialog from core.gui.dialogs.emaneconfig import EmaneConfigDialog
from core.gui.dialogs.mobilityconfig import MobilityConfigDialog from core.gui.dialogs.mobilityconfig import MobilityConfigDialog
@ -22,6 +19,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, NodeServiceData, 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:

592
daemon/core/gui/wrappers.py Normal file
View file

@ -0,0 +1,592 @@
from dataclasses import dataclass, field
from enum import Enum
from typing import Dict, List
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):
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):
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
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 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
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
y: float
z: float
lat: float
lon: float
alt: float
scale: float
@classmethod
def from_proto(cls, proto: core_pb2.SessionLocation) -> "SessionLocation":
return SessionLocation(
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:
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, proto: core_pb2.ExceptionEvent
) -> "ExceptionEvent":
return ExceptionEvent(
session_id=session_id,
node_id=proto.node_id,
level=ExceptionLevel(proto.level),
source=proto.source,
date=proto.date,
text=proto.text,
opaque=proto.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, proto: common_pb2.ConfigOption) -> "ConfigOption":
return ConfigOption(
label=proto.label,
name=proto.name,
value=proto.value,
type=ConfigOptionType(proto.type),
group=proto.group,
select=proto.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, proto: core_pb2.Interface) -> "Interface":
return Interface(
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:
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, proto: core_pb2.LinkOptions) -> "LinkOptions":
return LinkOptions(
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:
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, proto: core_pb2.Link) -> "Link":
iface1 = None
if proto.HasField("iface1"):
iface1 = Interface.from_proto(proto.iface1)
iface2 = None
if proto.HasField("iface2"):
iface2 = Interface.from_proto(proto.iface2)
options = None
if proto.HasField("options"):
options = LinkOptions.from_proto(proto.options)
return Link(
type=LinkType(proto.type),
node1_id=proto.node1_id,
node2_id=proto.node2_id,
iface1=iface1,
iface2=iface2,
options=options,
network_id=proto.network_id,
label=proto.label,
color=proto.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, proto: core_pb2.SessionSummary) -> "SessionSummary":
return SessionSummary(
id=proto.id,
state=SessionState(proto.state),
nodes=proto.nodes,
file=proto.file,
dir=proto.dir,
)
@dataclass
class Hook:
state: SessionState
file: str
data: str
@classmethod
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)
@dataclass
class Position:
x: float
y: float
@classmethod
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)
@dataclass
class Geo:
lat: float = None
lon: float = None
alt: float = None
@classmethod
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)
@dataclass
class Node:
id: int
name: str
type: NodeType
model: str = None
position: Position = 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
server: str = None
geo: Geo = None
dir: str = None
channel: str = None
@classmethod
def from_proto(cls, proto: core_pb2.Node) -> "Node":
return Node(
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:
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,
dir=self.dir,
channel=self.channel,
)
@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
link: Link
@classmethod
def from_proto(cls, proto: core_pb2.LinkEvent) -> "LinkEvent":
return LinkEvent(
message_type=MessageType(proto.message_type),
link=Link.from_proto(proto.link),
)
@dataclass
class NodeEvent:
message_type: MessageType
node: Node
@classmethod
def from_proto(cls, proto: core_pb2.NodeEvent) -> "NodeEvent":
return NodeEvent(
message_type=MessageType(proto.message_type),
node=Node.from_proto(proto.node),
)