Merge pull request #499 from coreemu/enhancement/pygui-wrappers
Enhancement/pygui wrappers
This commit is contained in:
commit
160498336c
25 changed files with 802 additions and 214 deletions
|
@ -12,33 +12,15 @@ from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Set, Tuple
|
|||
|
||||
import grpc
|
||||
|
||||
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.core_pb2 import (
|
||||
CpuUsageEvent,
|
||||
Event,
|
||||
ExceptionEvent,
|
||||
Hook,
|
||||
Interface,
|
||||
Link,
|
||||
LinkEvent,
|
||||
LinkType,
|
||||
MessageType,
|
||||
Node,
|
||||
NodeEvent,
|
||||
NodeType,
|
||||
Position,
|
||||
SessionLocation,
|
||||
SessionState,
|
||||
StartSessionResponse,
|
||||
StopSessionResponse,
|
||||
ThroughputsEvent,
|
||||
from core.api.grpc import (
|
||||
client,
|
||||
configservices_pb2,
|
||||
core_pb2,
|
||||
emane_pb2,
|
||||
mobility_pb2,
|
||||
services_pb2,
|
||||
wlan_pb2,
|
||||
)
|
||||
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.appconfig import CoreServer, Observer
|
||||
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.interface import InterfaceManager
|
||||
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:
|
||||
from core.gui.app import Application
|
||||
|
@ -153,7 +155,7 @@ class CoreClient:
|
|||
for observer in self.app.guiconfig.observers:
|
||||
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:
|
||||
return
|
||||
if event.session_id != self.session_id:
|
||||
|
@ -165,12 +167,13 @@ class CoreClient:
|
|||
return
|
||||
|
||||
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"):
|
||||
logging.info("session event: %s", event)
|
||||
session_event = event.session_event
|
||||
if session_event.event <= SessionState.SHUTDOWN:
|
||||
self.state = event.session_event.event
|
||||
if session_event.event <= SessionState.SHUTDOWN.value:
|
||||
self.state = SessionState(session_event.event)
|
||||
elif session_event.event in {7, 8, 9}:
|
||||
node_id = session_event.node_id
|
||||
dialog = self.mobility_players.get(node_id)
|
||||
|
@ -184,10 +187,12 @@ class CoreClient:
|
|||
else:
|
||||
logging.warning("unknown session event: %s", session_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"):
|
||||
logging.info("config event: %s", event)
|
||||
elif event.HasField("exception_event"):
|
||||
event = ExceptionEvent.from_proto(event.session_id, event.exception_event)
|
||||
self.handle_exception_event(event)
|
||||
else:
|
||||
logging.info("unhandled event: %s", event)
|
||||
|
@ -276,7 +281,8 @@ class CoreClient:
|
|||
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:
|
||||
logging.warning(
|
||||
"ignoring throughput event session(%s) current(%s)",
|
||||
|
@ -287,7 +293,7 @@ class CoreClient:
|
|||
logging.debug("handling throughputs event: %s", 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)
|
||||
|
||||
def handle_exception_event(self, event: ExceptionEvent) -> None:
|
||||
|
@ -306,7 +312,7 @@ class CoreClient:
|
|||
# get session data
|
||||
try:
|
||||
response = self.client.get_session(self.session_id)
|
||||
session = response.session
|
||||
session = Session.from_proto(response.session)
|
||||
self.state = session.state
|
||||
self.handling_events = self.client.events(
|
||||
self.session_id, self.handle_events
|
||||
|
@ -324,7 +330,7 @@ class CoreClient:
|
|||
# get location
|
||||
if query_location:
|
||||
response = self.client.get_session_location(self.session_id)
|
||||
self.location = response.location
|
||||
self.location = SessionLocation.from_proto(response.location)
|
||||
|
||||
# get emane models
|
||||
response = self.client.get_emane_models(self.session_id)
|
||||
|
@ -332,12 +338,13 @@ class CoreClient:
|
|||
|
||||
# get hooks
|
||||
response = self.client.get_hooks(self.session_id)
|
||||
for hook in response.hooks:
|
||||
for hook_proto in response.hooks:
|
||||
hook = Hook.from_proto(hook_proto)
|
||||
self.hooks[hook.file] = hook
|
||||
|
||||
# get emane config
|
||||
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
|
||||
self.ifaces_manager.joined(session.links)
|
||||
|
@ -350,7 +357,7 @@ class CoreClient:
|
|||
for node_id in response.configs:
|
||||
config = response.configs[node_id].config
|
||||
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
|
||||
response = self.client.get_emane_model_configs(self.session_id)
|
||||
|
@ -359,16 +366,16 @@ class CoreClient:
|
|||
if config.iface_id != -1:
|
||||
iface_id = config.iface_id
|
||||
canvas_node = self.canvas_nodes[config.node_id]
|
||||
canvas_node.emane_model_configs[(config.model, iface_id)] = dict(
|
||||
config.config
|
||||
)
|
||||
canvas_node.emane_model_configs[
|
||||
(config.model, iface_id)
|
||||
] = ConfigOption.from_dict(config.config)
|
||||
|
||||
# get wlan configurations
|
||||
response = self.client.get_wlan_configs(self.session_id)
|
||||
for _id in response.configs:
|
||||
mapped_config = response.configs[_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
|
||||
response = self.client.get_node_service_configs(self.session_id)
|
||||
|
@ -500,7 +507,6 @@ class CoreClient:
|
|||
"""
|
||||
try:
|
||||
self.client.connect()
|
||||
self.setup_cpu_usage()
|
||||
|
||||
# get service information
|
||||
response = self.client.get_services()
|
||||
|
@ -511,7 +517,7 @@ class CoreClient:
|
|||
# get config service informations
|
||||
response = self.client.get_config_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(
|
||||
service.group, set()
|
||||
)
|
||||
|
@ -545,8 +551,9 @@ class CoreClient:
|
|||
|
||||
def edit_node(self, core_node: Node) -> None:
|
||||
try:
|
||||
position = core_node.position.to_proto()
|
||||
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:
|
||||
self.app.show_grpc_exception("Edit Node Error", e)
|
||||
|
@ -555,22 +562,21 @@ class CoreClient:
|
|||
for server in self.servers.values():
|
||||
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()
|
||||
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 = []
|
||||
for edge in self.links.values():
|
||||
link = Link()
|
||||
link.CopyFrom(edge.link)
|
||||
if link.HasField("iface1") and not link.iface1.mac:
|
||||
link = edge.link
|
||||
if link.iface1 and not link.iface1.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()
|
||||
links.append(link)
|
||||
links.append(link.to_proto())
|
||||
wlan_configs = self.get_wlan_configs_proto()
|
||||
mobility_configs = self.get_mobility_configs_proto()
|
||||
emane_model_configs = self.get_emane_model_configs_proto()
|
||||
hooks = list(self.hooks.values())
|
||||
hooks = [x.to_proto() for x in self.hooks.values()]
|
||||
service_configs = self.get_service_configs_proto()
|
||||
file_configs = self.get_service_file_configs_proto()
|
||||
asymmetric_links = [
|
||||
|
@ -581,14 +587,15 @@ class CoreClient:
|
|||
emane_config = {x: self.emane_config[x].value for x in self.emane_config}
|
||||
else:
|
||||
emane_config = None
|
||||
response = StartSessionResponse(result=False)
|
||||
result = False
|
||||
exceptions = []
|
||||
try:
|
||||
self.send_servers()
|
||||
response = self.client.start_session(
|
||||
self.session_id,
|
||||
nodes,
|
||||
links,
|
||||
self.location,
|
||||
self.location.to_proto(),
|
||||
hooks,
|
||||
emane_config,
|
||||
emane_model_configs,
|
||||
|
@ -604,20 +611,23 @@ class CoreClient:
|
|||
)
|
||||
if response.result:
|
||||
self.set_metadata()
|
||||
result = response.result
|
||||
exceptions = response.exceptions
|
||||
except grpc.RpcError as 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:
|
||||
session_id = self.session_id
|
||||
response = StopSessionResponse(result=False)
|
||||
result = False
|
||||
try:
|
||||
response = self.client.stop_session(session_id)
|
||||
logging.info("stopped session(%s), result: %s", session_id, response)
|
||||
result = response.result
|
||||
except grpc.RpcError as e:
|
||||
self.app.show_grpc_exception("Stop Session Error", e)
|
||||
return response
|
||||
return result
|
||||
|
||||
def show_mobility_players(self) -> None:
|
||||
for canvas_node in self.canvas_nodes.values():
|
||||
|
@ -701,7 +711,7 @@ class CoreClient:
|
|||
logging.debug(
|
||||
"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(
|
||||
self,
|
||||
|
@ -735,7 +745,7 @@ class CoreClient:
|
|||
response,
|
||||
)
|
||||
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(
|
||||
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
|
||||
"""
|
||||
node_protos = [x.core_node for x in self.canvas_nodes.values()]
|
||||
link_protos = [x.link for x in self.links.values()]
|
||||
if self.state != SessionState.DEFINITION:
|
||||
self.client.set_session_state(self.session_id, SessionState.DEFINITION)
|
||||
|
||||
self.client.set_session_state(self.session_id, SessionState.DEFINITION)
|
||||
node_protos = [x.core_node.to_proto() for x in self.canvas_nodes.values()]
|
||||
link_protos = [x.link.to_proto() for x in self.links.values()]
|
||||
self.client.set_session_state(self.session_id, SessionState.DEFINITION.value)
|
||||
for node_proto in node_protos:
|
||||
response = self.client.add_node(self.session_id, node_proto)
|
||||
logging.debug("create node: %s", response)
|
||||
|
@ -823,7 +830,9 @@ class CoreClient:
|
|||
config_proto.data,
|
||||
)
|
||||
for hook in self.hooks.values():
|
||||
self.client.add_hook(self.session_id, hook.state, hook.file, hook.data)
|
||||
self.client.add_hook(
|
||||
self.session_id, hook.state.value, hook.file, hook.data
|
||||
)
|
||||
for config_proto in self.get_emane_model_configs_proto():
|
||||
self.client.set_emane_model_config(
|
||||
self.session_id,
|
||||
|
@ -917,7 +926,7 @@ class CoreClient:
|
|||
)
|
||||
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
|
||||
such as link, configurations, interfaces
|
||||
|
@ -948,13 +957,7 @@ class CoreClient:
|
|||
ip6=ip6,
|
||||
ip6_mask=ip6_mask,
|
||||
)
|
||||
logging.info(
|
||||
"create node(%s) interface(%s) IPv4(%s) IPv6(%s)",
|
||||
node.name,
|
||||
iface.name,
|
||||
iface.ip4,
|
||||
iface.ip6,
|
||||
)
|
||||
logging.info("create node(%s) interface(%s)", node.name, iface)
|
||||
return iface
|
||||
|
||||
def create_link(
|
||||
|
@ -1000,35 +1003,35 @@ class CoreClient:
|
|||
self.links[edge.token] = edge
|
||||
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 = []
|
||||
for canvas_node in self.canvas_nodes.values():
|
||||
if canvas_node.core_node.type != NodeType.WIRELESS_LAN:
|
||||
continue
|
||||
if not canvas_node.wlan_config:
|
||||
continue
|
||||
config = canvas_node.wlan_config
|
||||
config = {x: config[x].value for x in config}
|
||||
config = ConfigOption.to_dict(canvas_node.wlan_config)
|
||||
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)
|
||||
return configs
|
||||
|
||||
def get_mobility_configs_proto(self) -> List[MobilityConfig]:
|
||||
def get_mobility_configs_proto(self) -> List[mobility_pb2.MobilityConfig]:
|
||||
configs = []
|
||||
for canvas_node in self.canvas_nodes.values():
|
||||
if canvas_node.core_node.type != NodeType.WIRELESS_LAN:
|
||||
continue
|
||||
if not canvas_node.mobility_config:
|
||||
continue
|
||||
config = canvas_node.mobility_config
|
||||
config = {x: config[x].value for x in config}
|
||||
config = ConfigOption.to_dict(canvas_node.mobility_config)
|
||||
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)
|
||||
return configs
|
||||
|
||||
def get_emane_model_configs_proto(self) -> List[EmaneModelConfig]:
|
||||
def get_emane_model_configs_proto(self) -> List[emane_pb2.EmaneModelConfig]:
|
||||
configs = []
|
||||
for canvas_node in self.canvas_nodes.values():
|
||||
if canvas_node.core_node.type != NodeType.EMANE:
|
||||
|
@ -1036,16 +1039,16 @@ class CoreClient:
|
|||
node_id = canvas_node.core_node.id
|
||||
for key, config in canvas_node.emane_model_configs.items():
|
||||
model, iface_id = key
|
||||
config = {x: config[x].value for x in config}
|
||||
config = ConfigOption.to_dict(config)
|
||||
if iface_id is None:
|
||||
iface_id = -1
|
||||
config_proto = EmaneModelConfig(
|
||||
config_proto = emane_pb2.EmaneModelConfig(
|
||||
node_id=node_id, iface_id=iface_id, model=model, config=config
|
||||
)
|
||||
configs.append(config_proto)
|
||||
return configs
|
||||
|
||||
def get_service_configs_proto(self) -> List[ServiceConfig]:
|
||||
def get_service_configs_proto(self) -> List[services_pb2.ServiceConfig]:
|
||||
configs = []
|
||||
for canvas_node in self.canvas_nodes.values():
|
||||
if not NodeUtils.is_container_node(canvas_node.core_node.type):
|
||||
|
@ -1054,7 +1057,7 @@ class CoreClient:
|
|||
continue
|
||||
node_id = canvas_node.core_node.id
|
||||
for name, config in canvas_node.service_configs.items():
|
||||
config_proto = ServiceConfig(
|
||||
config_proto = services_pb2.ServiceConfig(
|
||||
node_id=node_id,
|
||||
service=name,
|
||||
directories=config.dirs,
|
||||
|
@ -1066,7 +1069,7 @@ class CoreClient:
|
|||
configs.append(config_proto)
|
||||
return configs
|
||||
|
||||
def get_service_file_configs_proto(self) -> List[ServiceFileConfig]:
|
||||
def get_service_file_configs_proto(self) -> List[services_pb2.ServiceFileConfig]:
|
||||
configs = []
|
||||
for canvas_node in self.canvas_nodes.values():
|
||||
if not NodeUtils.is_container_node(canvas_node.core_node.type):
|
||||
|
@ -1076,13 +1079,15 @@ class CoreClient:
|
|||
node_id = canvas_node.core_node.id
|
||||
for service, file_configs in canvas_node.service_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
|
||||
)
|
||||
configs.append(config_proto)
|
||||
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 = []
|
||||
for canvas_node in self.canvas_nodes.values():
|
||||
if not NodeUtils.is_container_node(canvas_node.core_node.type):
|
||||
|
@ -1092,7 +1097,7 @@ class CoreClient:
|
|||
node_id = canvas_node.core_node.id
|
||||
for name, service_config in canvas_node.config_service_configs.items():
|
||||
config = service_config.get("config", {})
|
||||
config_proto = ConfigServiceConfig(
|
||||
config_proto = configservices_pb2.ConfigServiceConfig(
|
||||
node_id=node_id,
|
||||
name=name,
|
||||
templates=service_config["templates"],
|
||||
|
@ -1113,7 +1118,7 @@ class CoreClient:
|
|||
node_id,
|
||||
config,
|
||||
)
|
||||
return dict(config)
|
||||
return ConfigOption.from_dict(config)
|
||||
|
||||
def get_mobility_config(self, node_id: int) -> Dict[str, ConfigOption]:
|
||||
response = self.client.get_mobility_config(self.session_id, node_id)
|
||||
|
@ -1123,7 +1128,7 @@ class CoreClient:
|
|||
node_id,
|
||||
config,
|
||||
)
|
||||
return dict(config)
|
||||
return ConfigOption.from_dict(config)
|
||||
|
||||
def get_emane_model_config(
|
||||
self, node_id: int, model: str, iface_id: int = None
|
||||
|
@ -1142,7 +1147,7 @@ class CoreClient:
|
|||
iface_id,
|
||||
config,
|
||||
)
|
||||
return dict(config)
|
||||
return ConfigOption.from_dict(config)
|
||||
|
||||
def execute_script(self, script) -> None:
|
||||
response = self.client.execute_script(script)
|
||||
|
|
|
@ -5,10 +5,10 @@ import tkinter as tk
|
|||
from tkinter import ttk
|
||||
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.themes import PADX, PADY
|
||||
from core.gui.widgets import CodeText
|
||||
from core.gui.wrappers import ExceptionEvent, ExceptionLevel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
@ -49,9 +49,8 @@ class AlertsDialog(Dialog):
|
|||
self.tree.heading("source", text="Source")
|
||||
self.tree.bind("<<TreeviewSelect>>", self.click_select)
|
||||
|
||||
for alarm in self.app.statusbar.core_alarms:
|
||||
exception = alarm.exception_event
|
||||
level_name = ExceptionLevel.Enum.Name(exception.level)
|
||||
for exception in self.app.statusbar.core_alarms:
|
||||
level_name = exception.level.name
|
||||
node_id = exception.node_id if exception.node_id else ""
|
||||
insert_id = self.tree.insert(
|
||||
"",
|
||||
|
@ -60,21 +59,21 @@ class AlertsDialog(Dialog):
|
|||
values=(
|
||||
exception.date,
|
||||
level_name,
|
||||
alarm.session_id,
|
||||
exception.session_id,
|
||||
node_id,
|
||||
exception.source,
|
||||
),
|
||||
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")
|
||||
fatal_name = ExceptionLevel.Enum.Name(ExceptionLevel.FATAL)
|
||||
fatal_name = ExceptionLevel.FATAL.name
|
||||
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")
|
||||
notice_name = ExceptionLevel.Enum.Name(ExceptionLevel.NOTICE)
|
||||
notice_name = ExceptionLevel.NOTICE.name
|
||||
self.tree.tag_configure(notice_name, background="#85e085")
|
||||
|
||||
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:
|
||||
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.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)
|
||||
|
|
|
@ -8,11 +8,10 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set
|
|||
|
||||
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.themes import FRAME_PAD, PADX, PADY
|
||||
from core.gui.widgets import CodeText, ConfigFrame, ListboxScroll
|
||||
from core.gui.wrappers import ConfigOption, ServiceValidationMode
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
@ -99,7 +98,7 @@ class ConfigServiceConfigDialog(Dialog):
|
|||
service_config = self.canvas_node.config_service_configs.get(
|
||||
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()}
|
||||
custom_config = service_config.get("config")
|
||||
if custom_config:
|
||||
|
|
|
@ -8,12 +8,11 @@ from typing import TYPE_CHECKING, Dict, List, Optional
|
|||
|
||||
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.images import ImageEnum, Images
|
||||
from core.gui.themes import PADX, PADY
|
||||
from core.gui.widgets import ConfigFrame
|
||||
from core.gui.wrappers import ConfigOption, Node
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -2,10 +2,10 @@ import tkinter as tk
|
|||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from core.api.grpc import core_pb2
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.themes import PADX, PADY
|
||||
from core.gui.widgets import CodeText, ListboxScroll
|
||||
from core.gui.wrappers import Hook, SessionState
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
@ -16,8 +16,9 @@ class HookDialog(Dialog):
|
|||
super().__init__(app, "Hook", master=master)
|
||||
self.name: tk.StringVar = tk.StringVar()
|
||||
self.codetext: Optional[CodeText] = None
|
||||
self.hook: core_pb2.Hook = core_pb2.Hook()
|
||||
self.hook: Optional[Hook] = None
|
||||
self.state: tk.StringVar = tk.StringVar()
|
||||
self.editing: bool = False
|
||||
self.draw()
|
||||
|
||||
def draw(self) -> None:
|
||||
|
@ -34,8 +35,8 @@ class HookDialog(Dialog):
|
|||
label.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||
entry = ttk.Entry(frame, textvariable=self.name)
|
||||
entry.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||
values = tuple(x for x in core_pb2.SessionState.Enum.keys() if x != "NONE")
|
||||
initial_state = core_pb2.SessionState.Enum.Name(core_pb2.SessionState.RUNTIME)
|
||||
values = tuple(x.name for x in SessionState)
|
||||
initial_state = SessionState.RUNTIME.name
|
||||
self.state.set(initial_state)
|
||||
self.name.set(f"{initial_state.lower()}_hook.sh")
|
||||
combobox = ttk.Combobox(
|
||||
|
@ -67,23 +68,30 @@ class HookDialog(Dialog):
|
|||
button.grid(row=0, column=1, sticky="ew")
|
||||
|
||||
def state_change(self, event: tk.Event) -> None:
|
||||
if self.editing:
|
||||
return
|
||||
state_name = self.state.get()
|
||||
self.name.set(f"{state_name.lower()}_hook.sh")
|
||||
|
||||
def set(self, hook: core_pb2.Hook) -> None:
|
||||
def set(self, hook: Hook) -> None:
|
||||
self.editing = True
|
||||
self.hook = hook
|
||||
self.name.set(hook.file)
|
||||
self.codetext.text.delete(1.0, tk.END)
|
||||
self.codetext.text.insert(tk.END, hook.data)
|
||||
state_name = core_pb2.SessionState.Enum.Name(hook.state)
|
||||
state_name = hook.state.name
|
||||
self.state.set(state_name)
|
||||
|
||||
def save(self) -> None:
|
||||
data = self.codetext.text.get("1.0", tk.END).strip()
|
||||
state_value = core_pb2.SessionState.Enum.Value(self.state.get())
|
||||
self.hook.file = self.name.get()
|
||||
self.hook.data = data
|
||||
self.hook.state = state_value
|
||||
state = SessionState[self.state.get()]
|
||||
file_name = self.name.get()
|
||||
if self.editing:
|
||||
self.hook.state = state
|
||||
self.hook.file = file_name
|
||||
self.hook.data = data
|
||||
else:
|
||||
self.hook = Hook(state=state, file=file_name, data=data)
|
||||
self.destroy()
|
||||
|
||||
|
||||
|
@ -94,6 +102,7 @@ class HooksDialog(Dialog):
|
|||
self.edit_button: Optional[ttk.Button] = None
|
||||
self.delete_button: Optional[ttk.Button] = None
|
||||
self.selected: Optional[str] = None
|
||||
self.selected_index: Optional[int] = None
|
||||
self.draw()
|
||||
|
||||
def draw(self) -> None:
|
||||
|
@ -133,10 +142,13 @@ class HooksDialog(Dialog):
|
|||
self.listbox.insert(tk.END, hook.file)
|
||||
|
||||
def click_edit(self) -> None:
|
||||
hook = self.app.core.hooks[self.selected]
|
||||
hook = self.app.core.hooks.pop(self.selected)
|
||||
dialog = HookDialog(self, self.app)
|
||||
dialog.set(hook)
|
||||
dialog.show()
|
||||
self.app.core.hooks[hook.file] = hook
|
||||
self.listbox.delete(self.selected_index)
|
||||
self.listbox.insert(self.selected_index, hook.file)
|
||||
|
||||
def click_delete(self) -> None:
|
||||
del self.app.core.hooks[self.selected]
|
||||
|
@ -146,11 +158,12 @@ class HooksDialog(Dialog):
|
|||
|
||||
def select(self, event: tk.Event) -> None:
|
||||
if self.listbox.curselection():
|
||||
index = self.listbox.curselection()[0]
|
||||
self.selected = self.listbox.get(index)
|
||||
self.selected_index = self.listbox.curselection()[0]
|
||||
self.selected = self.listbox.get(self.selected_index)
|
||||
self.edit_button.config(state=tk.NORMAL)
|
||||
self.delete_button.config(state=tk.NORMAL)
|
||||
else:
|
||||
self.selected = None
|
||||
self.selected_index = None
|
||||
self.edit_button.config(state=tk.DISABLED)
|
||||
self.delete_button.config(state=tk.DISABLED)
|
||||
|
|
|
@ -5,11 +5,11 @@ import tkinter as tk
|
|||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from core.api.grpc import core_pb2
|
||||
from core.gui import validation
|
||||
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.themes import PADX, PADY
|
||||
from core.gui.wrappers import Interface, Link, LinkOptions
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
@ -21,7 +21,7 @@ def get_int(var: tk.StringVar) -> Optional[int]:
|
|||
if value != "":
|
||||
return int(value)
|
||||
else:
|
||||
return None
|
||||
return 0
|
||||
|
||||
|
||||
def get_float(var: tk.StringVar) -> Optional[float]:
|
||||
|
@ -29,14 +29,15 @@ def get_float(var: tk.StringVar) -> Optional[float]:
|
|||
if value != "":
|
||||
return float(value)
|
||||
else:
|
||||
return None
|
||||
return 0.0
|
||||
|
||||
|
||||
class LinkConfigurationDialog(Dialog):
|
||||
def __init__(self, app: "Application", edge: "CanvasEdge") -> None:
|
||||
super().__init__(app, "Link Configuration")
|
||||
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:
|
||||
symmetry_var = tk.StringVar(value=">>")
|
||||
else:
|
||||
|
@ -223,32 +224,32 @@ class LinkConfigurationDialog(Dialog):
|
|||
delay = get_int(self.delay)
|
||||
duplicate = get_int(self.duplicate)
|
||||
loss = get_float(self.loss)
|
||||
options = core_pb2.LinkOptions(
|
||||
options = LinkOptions(
|
||||
bandwidth=bandwidth, jitter=jitter, delay=delay, dup=duplicate, loss=loss
|
||||
)
|
||||
link.options.CopyFrom(options)
|
||||
link.options = options
|
||||
|
||||
iface1_id = None
|
||||
if link.HasField("iface1"):
|
||||
if link.iface1:
|
||||
iface1_id = link.iface1.id
|
||||
iface2_id = None
|
||||
if link.HasField("iface2"):
|
||||
if link.iface2:
|
||||
iface2_id = link.iface2.id
|
||||
|
||||
if not self.is_symmetric:
|
||||
link.options.unidirectional = True
|
||||
asym_iface1 = None
|
||||
if iface1_id:
|
||||
asym_iface1 = core_pb2.Interface(id=iface1_id)
|
||||
asym_iface1 = Interface(id=iface1_id)
|
||||
asym_iface2 = None
|
||||
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_jitter = get_int(self.down_jitter)
|
||||
down_delay = get_int(self.down_delay)
|
||||
down_duplicate = get_int(self.down_duplicate)
|
||||
down_loss = get_float(self.down_loss)
|
||||
options = core_pb2.LinkOptions(
|
||||
options = LinkOptions(
|
||||
bandwidth=down_bandwidth,
|
||||
jitter=down_jitter,
|
||||
delay=down_delay,
|
||||
|
@ -256,7 +257,7 @@ class LinkConfigurationDialog(Dialog):
|
|||
loss=down_loss,
|
||||
unidirectional=True,
|
||||
)
|
||||
self.edge.asymmetric_link = core_pb2.Link(
|
||||
self.edge.asymmetric_link = Link(
|
||||
node1_id=link.node2_id,
|
||||
node2_id=link.node1_id,
|
||||
iface1=asym_iface1,
|
||||
|
@ -267,7 +268,7 @@ class LinkConfigurationDialog(Dialog):
|
|||
link.options.unidirectional = False
|
||||
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
|
||||
self.app.core.client.edit_link(
|
||||
session_id,
|
||||
|
@ -316,7 +317,7 @@ class LinkConfigurationDialog(Dialog):
|
|||
color = self.app.canvas.itemcget(self.edge.id, "fill")
|
||||
self.color.set(color)
|
||||
link = self.edge.link
|
||||
if link.HasField("options"):
|
||||
if link.options:
|
||||
self.bandwidth.set(str(link.options.bandwidth))
|
||||
self.jitter.set(str(link.options.jitter))
|
||||
self.duplicate.set(str(link.options.dup))
|
||||
|
|
|
@ -6,11 +6,10 @@ from typing import TYPE_CHECKING, Dict, Optional
|
|||
|
||||
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.themes import PADX, PADY
|
||||
from core.gui.widgets import ConfigFrame
|
||||
from core.gui.wrappers import ConfigOption, Node
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -4,12 +4,10 @@ from typing import TYPE_CHECKING, Dict, Optional
|
|||
|
||||
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.images import ImageEnum
|
||||
from core.gui.themes import PADX, PADY
|
||||
from core.gui.wrappers import ConfigOption, MobilityAction, Node
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
@ -151,7 +149,7 @@ class MobilityPlayerDialog(Dialog):
|
|||
session_id = self.app.core.session_id
|
||||
try:
|
||||
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:
|
||||
self.app.show_grpc_exception("Mobility Error", e)
|
||||
|
@ -161,7 +159,7 @@ class MobilityPlayerDialog(Dialog):
|
|||
session_id = self.app.core.session_id
|
||||
try:
|
||||
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:
|
||||
self.app.show_grpc_exception("Mobility Error", e)
|
||||
|
@ -171,7 +169,7 @@ class MobilityPlayerDialog(Dialog):
|
|||
session_id = self.app.core.session_id
|
||||
try:
|
||||
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:
|
||||
self.app.show_grpc_exception("Mobility Error", e)
|
||||
|
|
|
@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Dict, Optional
|
|||
import netaddr
|
||||
from PIL.ImageTk import PhotoImage
|
||||
|
||||
from core.api.grpc.core_pb2 import Node
|
||||
from core.gui import nodeutils, validation
|
||||
from core.gui.appconfig import ICONS_PATH
|
||||
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.themes import FRAME_PAD, PADX, PADY
|
||||
from core.gui.widgets import ListboxScroll, image_chooser
|
||||
from core.gui.wrappers import Node
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -7,12 +7,12 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
|||
import grpc
|
||||
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.dialog import Dialog
|
||||
from core.gui.images import ImageEnum, Images
|
||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||
from core.gui.widgets import CodeText, ListboxScroll
|
||||
from core.gui.wrappers import NodeServiceData, ServiceValidationMode
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
@ -72,7 +72,7 @@ class ServiceConfigDialog(Dialog):
|
|||
self.service_file_data: Optional[CodeText] = None
|
||||
self.validation_period_entry: Optional[ttk.Entry] = None
|
||||
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.modified_files: Set[str] = set()
|
||||
self.has_error: bool = False
|
||||
|
|
|
@ -5,10 +5,10 @@ from typing import TYPE_CHECKING, Dict, Optional
|
|||
|
||||
import grpc
|
||||
|
||||
from core.api.grpc.common_pb2 import ConfigOption
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.themes import PADX, PADY
|
||||
from core.gui.widgets import ConfigFrame
|
||||
from core.gui.wrappers import ConfigOption
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
@ -28,7 +28,7 @@ class SessionOptionsDialog(Dialog):
|
|||
try:
|
||||
session_id = self.app.core.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:
|
||||
self.app.show_grpc_exception("Get Session Options Error", e)
|
||||
self.has_error = True
|
||||
|
|
|
@ -5,12 +5,11 @@ from typing import TYPE_CHECKING, List, Optional
|
|||
|
||||
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.images import ImageEnum, Images
|
||||
from core.gui.task import ProgressTask
|
||||
from core.gui.themes import PADX, PADY
|
||||
from core.gui.wrappers import SessionState, SessionSummary
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
@ -33,7 +32,7 @@ class SessionsDialog(Dialog):
|
|||
try:
|
||||
response = self.app.core.client.get_sessions()
|
||||
logging.info("sessions: %s", response)
|
||||
return response.sessions
|
||||
return [SessionSummary.from_proto(x) for x in response.sessions]
|
||||
except grpc.RpcError as e:
|
||||
self.app.show_grpc_exception("Get Sessions Error", e)
|
||||
self.destroy()
|
||||
|
@ -82,7 +81,7 @@ class SessionsDialog(Dialog):
|
|||
self.tree.heading("nodes", text="Node Count")
|
||||
|
||||
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(
|
||||
"",
|
||||
tk.END,
|
||||
|
|
|
@ -3,11 +3,10 @@ from typing import TYPE_CHECKING, Dict, Optional
|
|||
|
||||
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.themes import PADX, PADY
|
||||
from core.gui.widgets import ConfigFrame
|
||||
from core.gui.wrappers import ConfigOption, Node
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import tkinter as tk
|
||||
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.utils import bandwidth_text
|
||||
from core.gui.wrappers import Interface
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
@ -62,7 +62,7 @@ class EdgeInfoFrame(InfoFrameBase):
|
|||
ip6 = f"{iface2.ip6}/{iface2.ip6_mask}" if iface2.ip6 else ""
|
||||
frame.add_detail("IP6", ip6)
|
||||
|
||||
if link.HasField("options"):
|
||||
if link.options:
|
||||
frame.add_separator()
|
||||
bandwidth = bandwidth_text(options.bandwidth)
|
||||
frame.add_detail("Bandwidth", bandwidth)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from core.api.grpc.core_pb2 import NodeType
|
||||
from core.gui.frames.base import DetailsFrame, InfoFrameBase
|
||||
from core.gui.nodeutils import NodeUtils
|
||||
from core.gui.wrappers import NodeType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -3,14 +3,13 @@ import math
|
|||
import tkinter as tk
|
||||
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.dialogs.linkconfig import LinkConfigurationDialog
|
||||
from core.gui.frames.link import EdgeInfoFrame, WirelessEdgeInfoFrame
|
||||
from core.gui.graph import tags
|
||||
from core.gui.nodeutils import NodeUtils
|
||||
from core.gui.utils import bandwidth_text
|
||||
from core.gui.wrappers import Interface, Link
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.graph.graph import CanvasGraph
|
||||
|
@ -305,7 +304,7 @@ class CanvasEdge(Edge):
|
|||
self.link = link
|
||||
self.draw_labels()
|
||||
|
||||
def iface_label(self, iface: core_pb2.Interface) -> str:
|
||||
def iface_label(self, iface: Interface) -> str:
|
||||
label = ""
|
||||
if iface.name and self.canvas.show_iface_names.get():
|
||||
label = f"{iface.name}"
|
||||
|
@ -319,10 +318,10 @@ class CanvasEdge(Edge):
|
|||
|
||||
def create_node_labels(self) -> Tuple[str, str]:
|
||||
label1 = None
|
||||
if self.link.HasField("iface1"):
|
||||
if self.link.iface1:
|
||||
label1 = self.iface_label(self.link.iface1)
|
||||
label2 = None
|
||||
if self.link.HasField("iface2"):
|
||||
if self.link.iface2:
|
||||
label2 = self.iface_label(self.link.iface2)
|
||||
return label1, label2
|
||||
|
||||
|
@ -417,6 +416,8 @@ class CanvasEdge(Edge):
|
|||
dialog.show()
|
||||
|
||||
def draw_link_options(self):
|
||||
if not self.link.options:
|
||||
return
|
||||
options = self.link.options
|
||||
lines = []
|
||||
bandwidth = options.bandwidth
|
||||
|
|
|
@ -7,14 +7,6 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
|||
from PIL import Image
|
||||
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.graph import tags
|
||||
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.images import ImageEnum, TypeToImage
|
||||
from core.gui.nodeutils import NodeDraw, NodeUtils
|
||||
from core.gui.wrappers import Interface, Link, LinkType, Node, Session, ThroughputsEvent
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
@ -135,11 +128,6 @@ class CanvasGraph(tk.Canvas):
|
|||
self.configure(scrollregion=self.bbox(tk.ALL))
|
||||
|
||||
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
|
||||
self.show_node_labels.set(True)
|
||||
self.show_link_labels.set(True)
|
||||
|
@ -251,12 +239,12 @@ class CanvasGraph(tk.Canvas):
|
|||
dst.edges.add(edge)
|
||||
self.edges[edge.token] = edge
|
||||
self.core.links[edge.token] = edge
|
||||
if link.HasField("iface1"):
|
||||
if link.iface1:
|
||||
iface1 = link.iface1
|
||||
self.core.iface_to_edge[(node1.id, iface1.id)] = token
|
||||
src.ifaces[iface1.id] = iface1
|
||||
edge.src_iface = iface1
|
||||
if link.HasField("iface2"):
|
||||
if link.iface2:
|
||||
iface2 = link.iface2
|
||||
self.core.iface_to_edge[(node2.id, iface2.id)] = edge.token
|
||||
dst.ifaces[iface2.id] = iface2
|
||||
|
@ -343,6 +331,7 @@ class CanvasGraph(tk.Canvas):
|
|||
"""
|
||||
# draw existing nodes
|
||||
for core_node in session.nodes:
|
||||
logging.debug("drawing node: %s", core_node)
|
||||
# peer to peer node is not drawn on the GUI
|
||||
if NodeUtils.is_ignore_node(core_node.type):
|
||||
continue
|
||||
|
@ -987,12 +976,12 @@ class CanvasGraph(tk.Canvas):
|
|||
copy_edge = self.edges[token]
|
||||
copy_link = copy_edge.link
|
||||
options = edge.link.options
|
||||
copy_link.options.CopyFrom(options)
|
||||
copy_link.options = deepcopy(options)
|
||||
iface1_id = None
|
||||
if copy_link.HasField("iface1"):
|
||||
if copy_link.iface1:
|
||||
iface1_id = copy_link.iface1.id
|
||||
iface2_id = None
|
||||
if copy_link.HasField("iface2"):
|
||||
if copy_link.iface2:
|
||||
iface2_id = copy_link.iface2.id
|
||||
if not options.unidirectional:
|
||||
copy_edge.asymmetric_link = None
|
||||
|
|
|
@ -6,9 +6,6 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple
|
|||
import grpc
|
||||
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.dialogs.emaneconfig import EmaneConfigDialog
|
||||
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.images import ImageEnum
|
||||
from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils
|
||||
from core.gui.wrappers import ConfigOption, Interface, Node, NodeServiceData, NodeType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -5,8 +5,8 @@ from typing import Dict, Optional, Tuple
|
|||
from PIL import Image
|
||||
from PIL.ImageTk import PhotoImage
|
||||
|
||||
from core.api.grpc.core_pb2 import NodeType
|
||||
from core.gui.appconfig import LOCAL_ICONS_PATH
|
||||
from core.gui.wrappers import NodeType
|
||||
|
||||
|
||||
class Images:
|
||||
|
|
|
@ -4,9 +4,9 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple
|
|||
import netaddr
|
||||
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.nodeutils import NodeUtils
|
||||
from core.gui.wrappers import Interface, Link, Node
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
@ -89,10 +89,10 @@ class InterfaceManager:
|
|||
remaining_subnets = set()
|
||||
for edge in self.app.core.links.values():
|
||||
link = edge.link
|
||||
if link.HasField("iface1"):
|
||||
if link.iface1:
|
||||
subnets = self.get_subnets(link.iface1)
|
||||
remaining_subnets.add(subnets)
|
||||
if link.HasField("iface2"):
|
||||
if link.iface2:
|
||||
subnets = self.get_subnets(link.iface2)
|
||||
remaining_subnets.add(subnets)
|
||||
|
||||
|
@ -100,9 +100,9 @@ class InterfaceManager:
|
|||
# or remove used indexes from subnet
|
||||
ifaces = []
|
||||
for link in links:
|
||||
if link.HasField("iface1"):
|
||||
if link.iface1:
|
||||
ifaces.append(link.iface1)
|
||||
if link.HasField("iface2"):
|
||||
if link.iface2:
|
||||
ifaces.append(link.iface2)
|
||||
for iface in ifaces:
|
||||
subnets = self.get_subnets(iface)
|
||||
|
@ -117,9 +117,9 @@ class InterfaceManager:
|
|||
def joined(self, links: List[Link]) -> None:
|
||||
ifaces = []
|
||||
for link in links:
|
||||
if link.HasField("iface1"):
|
||||
if link.iface1:
|
||||
ifaces.append(link.iface1)
|
||||
if link.HasField("iface2"):
|
||||
if link.iface2:
|
||||
ifaces.append(link.iface2)
|
||||
|
||||
# add to used subnets and mark used indexes
|
||||
|
|
|
@ -3,9 +3,9 @@ from typing import List, Optional, Set
|
|||
|
||||
from PIL.ImageTk import PhotoImage
|
||||
|
||||
from core.api.grpc.core_pb2 import Node, NodeType
|
||||
from core.gui.appconfig import CustomNode, GuiConfig
|
||||
from core.gui.images import ImageEnum, Images, TypeToImage
|
||||
from core.gui.wrappers import Node, NodeType
|
||||
|
||||
ICON_SIZE: int = 48
|
||||
ANTENNA_SIZE: int = 32
|
||||
|
@ -17,7 +17,7 @@ class NodeDraw:
|
|||
self.image: Optional[PhotoImage] = None
|
||||
self.image_enum: Optional[ImageEnum] = None
|
||||
self.image_file: Optional[str] = None
|
||||
self.node_type: NodeType = None
|
||||
self.node_type: Optional[NodeType] = None
|
||||
self.model: Optional[str] = None
|
||||
self.services: Set[str] = set()
|
||||
self.label: Optional[str] = None
|
||||
|
|
|
@ -5,9 +5,9 @@ import tkinter as tk
|
|||
from tkinter import ttk
|
||||
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.themes import Styles
|
||||
from core.gui.wrappers import ExceptionEvent, ExceptionLevel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
@ -69,7 +69,7 @@ class StatusBar(ttk.Frame):
|
|||
|
||||
def add_alert(self, event: ExceptionEvent) -> None:
|
||||
self.core_alarms.append(event)
|
||||
level = event.exception_event.level
|
||||
level = event.level
|
||||
self._set_alert_style(level)
|
||||
label = f"Alerts ({len(self.core_alarms)})"
|
||||
self.alerts_button.config(text=label, style=self.alert_style)
|
||||
|
|
|
@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Callable, List, Optional
|
|||
|
||||
from PIL.ImageTk import PhotoImage
|
||||
|
||||
from core.api.grpc import core_pb2
|
||||
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
||||
from core.gui.dialogs.runtool import RunToolDialog
|
||||
from core.gui.graph import tags
|
||||
|
@ -300,15 +299,15 @@ class Toolbar(ttk.Frame):
|
|||
)
|
||||
task.start()
|
||||
|
||||
def start_callback(self, response: core_pb2.StartSessionResponse) -> None:
|
||||
if response.result:
|
||||
def start_callback(self, result: bool, exceptions: List[str]) -> None:
|
||||
if result:
|
||||
self.set_runtime()
|
||||
self.app.core.set_metadata()
|
||||
self.app.core.show_mobility_players()
|
||||
else:
|
||||
enable_buttons(self.design_frame, enabled=True)
|
||||
if response.exceptions:
|
||||
message = "\n".join(response.exceptions)
|
||||
if exceptions:
|
||||
message = "\n".join(exceptions)
|
||||
self.app.show_error("Start Session Error", message)
|
||||
|
||||
def set_runtime(self) -> None:
|
||||
|
@ -405,7 +404,7 @@ class Toolbar(ttk.Frame):
|
|||
)
|
||||
task.start()
|
||||
|
||||
def stop_callback(self, response: core_pb2.StopSessionResponse) -> None:
|
||||
def stop_callback(self, result: bool) -> None:
|
||||
self.set_design()
|
||||
self.app.canvas.stopped_session()
|
||||
|
||||
|
|
|
@ -5,12 +5,10 @@ from pathlib import Path
|
|||
from tkinter import filedialog, font, ttk
|
||||
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.dialogs.dialog import Dialog
|
||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||
from core.gui.wrappers import ConfigOption, ConfigOptionType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
@ -110,7 +108,7 @@ class ConfigFrame(ttk.Notebook):
|
|||
label = ttk.Label(tab.frame, text=option.label)
|
||||
label.grid(row=index, pady=PADY, padx=PADX, sticky="w")
|
||||
value = tk.StringVar()
|
||||
if option.type == core_pb2.ConfigOptionType.BOOL:
|
||||
if option.type == ConfigOptionType.BOOL:
|
||||
select = ("On", "Off")
|
||||
state = "readonly" if self.enabled else tk.DISABLED
|
||||
combobox = ttk.Combobox(
|
||||
|
@ -129,7 +127,7 @@ class ConfigFrame(ttk.Notebook):
|
|||
tab.frame, textvariable=value, values=select, state=state
|
||||
)
|
||||
combobox.grid(row=index, column=1, sticky="ew")
|
||||
elif option.type == core_pb2.ConfigOptionType.STRING:
|
||||
elif option.type == ConfigOptionType.STRING:
|
||||
value.set(option.value)
|
||||
state = tk.NORMAL if self.enabled else tk.DISABLED
|
||||
if "file" in option.label:
|
||||
|
@ -153,7 +151,7 @@ class ConfigFrame(ttk.Notebook):
|
|||
tab.frame, textvariable=value, state=state
|
||||
)
|
||||
entry.grid(row=index, column=1, sticky="ew")
|
||||
elif option.type == core_pb2.ConfigOptionType.FLOAT:
|
||||
elif option.type == ConfigOptionType.FLOAT:
|
||||
value.set(option.value)
|
||||
state = tk.NORMAL if self.enabled else tk.DISABLED
|
||||
entry = validation.PositiveFloatEntry(
|
||||
|
@ -169,7 +167,7 @@ class ConfigFrame(ttk.Notebook):
|
|||
option = self.config[key]
|
||||
value = self.values[key]
|
||||
config_value = value.get()
|
||||
if option.type == core_pb2.ConfigOptionType.BOOL:
|
||||
if option.type == ConfigOptionType.BOOL:
|
||||
if config_value == "On":
|
||||
option.value = "1"
|
||||
else:
|
||||
|
@ -182,7 +180,7 @@ class ConfigFrame(ttk.Notebook):
|
|||
for name, data in config.items():
|
||||
option = self.config[name]
|
||||
value = self.values[name]
|
||||
if option.type == core_pb2.ConfigOptionType.BOOL:
|
||||
if option.type == ConfigOptionType.BOOL:
|
||||
if data == "1":
|
||||
data = "On"
|
||||
else:
|
||||
|
|
592
daemon/core/gui/wrappers.py
Normal file
592
daemon/core/gui/wrappers.py
Normal 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),
|
||||
)
|
Loading…
Reference in a new issue