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
|
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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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
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…
Add table
Reference in a new issue