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

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

View file

@ -12,33 +12,15 @@ from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Set, Tuple
import grpc
from core.api.grpc import client
from core.api.grpc.common_pb2 import ConfigOption
from core.api.grpc.configservices_pb2 import ConfigService, ConfigServiceConfig
from core.api.grpc.core_pb2 import (
CpuUsageEvent,
Event,
ExceptionEvent,
Hook,
Interface,
Link,
LinkEvent,
LinkType,
MessageType,
Node,
NodeEvent,
NodeType,
Position,
SessionLocation,
SessionState,
StartSessionResponse,
StopSessionResponse,
ThroughputsEvent,
from core.api.grpc import (
client,
configservices_pb2,
core_pb2,
emane_pb2,
mobility_pb2,
services_pb2,
wlan_pb2,
)
from core.api.grpc.emane_pb2 import EmaneModelConfig
from core.api.grpc.mobility_pb2 import MobilityConfig
from core.api.grpc.services_pb2 import NodeServiceData, ServiceConfig, ServiceFileConfig
from core.api.grpc.wlan_pb2 import WlanConfig
from core.gui import appconfig
from core.gui.appconfig import CoreServer, Observer
from core.gui.dialogs.emaneinstall import EmaneInstallDialog
@ -51,6 +33,26 @@ from core.gui.graph.shape import AnnotationData, Shape
from core.gui.graph.shapeutils import ShapeType
from core.gui.interface import InterfaceManager
from core.gui.nodeutils import NodeDraw, NodeUtils
from core.gui.wrappers import (
ConfigOption,
ConfigService,
ExceptionEvent,
Hook,
Interface,
Link,
LinkEvent,
LinkType,
MessageType,
Node,
NodeEvent,
NodeServiceData,
NodeType,
Position,
Session,
SessionLocation,
SessionState,
ThroughputsEvent,
)
if TYPE_CHECKING:
from core.gui.app import Application
@ -153,7 +155,7 @@ class CoreClient:
for observer in self.app.guiconfig.observers:
self.custom_observers[observer.name] = observer
def handle_events(self, event: Event) -> None:
def handle_events(self, event: core_pb2.Event) -> None:
if event.source == GUI_SOURCE:
return
if event.session_id != self.session_id:
@ -165,12 +167,13 @@ class CoreClient:
return
if event.HasField("link_event"):
self.app.after(0, self.handle_link_event, event.link_event)
link_event = LinkEvent.from_proto(event.link_event)
self.app.after(0, self.handle_link_event, link_event)
elif event.HasField("session_event"):
logging.info("session event: %s", event)
session_event = event.session_event
if session_event.event <= SessionState.SHUTDOWN:
self.state = event.session_event.event
if session_event.event <= SessionState.SHUTDOWN.value:
self.state = SessionState(session_event.event)
elif session_event.event in {7, 8, 9}:
node_id = session_event.node_id
dialog = self.mobility_players.get(node_id)
@ -184,10 +187,12 @@ class CoreClient:
else:
logging.warning("unknown session event: %s", session_event)
elif event.HasField("node_event"):
self.app.after(0, self.handle_node_event, event.node_event)
node_event = NodeEvent.from_proto(event.node_event)
self.app.after(0, self.handle_node_event, node_event)
elif event.HasField("config_event"):
logging.info("config event: %s", event)
elif event.HasField("exception_event"):
event = ExceptionEvent.from_proto(event.session_id, event.exception_event)
self.handle_exception_event(event)
else:
logging.info("unhandled event: %s", event)
@ -276,7 +281,8 @@ class CoreClient:
CPU_USAGE_DELAY, self.handle_cpu_event
)
def handle_throughputs(self, event: ThroughputsEvent) -> None:
def handle_throughputs(self, event: core_pb2.ThroughputsEvent) -> None:
event = ThroughputsEvent.from_proto(event)
if event.session_id != self.session_id:
logging.warning(
"ignoring throughput event session(%s) current(%s)",
@ -287,7 +293,7 @@ class CoreClient:
logging.debug("handling throughputs event: %s", event)
self.app.after(0, self.app.canvas.set_throughputs, event)
def handle_cpu_event(self, event: CpuUsageEvent) -> None:
def handle_cpu_event(self, event: core_pb2.CpuUsageEvent) -> None:
self.app.after(0, self.app.statusbar.set_cpu, event.usage)
def handle_exception_event(self, event: ExceptionEvent) -> None:
@ -306,7 +312,7 @@ class CoreClient:
# get session data
try:
response = self.client.get_session(self.session_id)
session = response.session
session = Session.from_proto(response.session)
self.state = session.state
self.handling_events = self.client.events(
self.session_id, self.handle_events
@ -324,7 +330,7 @@ class CoreClient:
# get location
if query_location:
response = self.client.get_session_location(self.session_id)
self.location = response.location
self.location = SessionLocation.from_proto(response.location)
# get emane models
response = self.client.get_emane_models(self.session_id)
@ -332,12 +338,13 @@ class CoreClient:
# get hooks
response = self.client.get_hooks(self.session_id)
for hook in response.hooks:
for hook_proto in response.hooks:
hook = Hook.from_proto(hook_proto)
self.hooks[hook.file] = hook
# get emane config
response = self.client.get_emane_config(self.session_id)
self.emane_config = response.config
self.emane_config = ConfigOption.from_dict(response.config)
# update interface manager
self.ifaces_manager.joined(session.links)
@ -350,7 +357,7 @@ class CoreClient:
for node_id in response.configs:
config = response.configs[node_id].config
canvas_node = self.canvas_nodes[node_id]
canvas_node.mobility_config = dict(config)
canvas_node.mobility_config = ConfigOption.from_dict(config)
# get emane model config
response = self.client.get_emane_model_configs(self.session_id)
@ -359,16 +366,16 @@ class CoreClient:
if config.iface_id != -1:
iface_id = config.iface_id
canvas_node = self.canvas_nodes[config.node_id]
canvas_node.emane_model_configs[(config.model, iface_id)] = dict(
config.config
)
canvas_node.emane_model_configs[
(config.model, iface_id)
] = ConfigOption.from_dict(config.config)
# get wlan configurations
response = self.client.get_wlan_configs(self.session_id)
for _id in response.configs:
mapped_config = response.configs[_id]
canvas_node = self.canvas_nodes[_id]
canvas_node.wlan_config = dict(mapped_config.config)
canvas_node.wlan_config = ConfigOption.from_dict(mapped_config.config)
# get service configurations
response = self.client.get_node_service_configs(self.session_id)
@ -500,7 +507,6 @@ class CoreClient:
"""
try:
self.client.connect()
self.setup_cpu_usage()
# get service information
response = self.client.get_services()
@ -511,7 +517,7 @@ class CoreClient:
# get config service informations
response = self.client.get_config_services()
for service in response.services:
self.config_services[service.name] = service
self.config_services[service.name] = ConfigService.from_proto(service)
group_services = self.config_services_groups.setdefault(
service.group, set()
)
@ -545,8 +551,9 @@ class CoreClient:
def edit_node(self, core_node: Node) -> None:
try:
position = core_node.position.to_proto()
self.client.edit_node(
self.session_id, core_node.id, core_node.position, source=GUI_SOURCE
self.session_id, core_node.id, position, source=GUI_SOURCE
)
except grpc.RpcError as e:
self.app.show_grpc_exception("Edit Node Error", e)
@ -555,22 +562,21 @@ class CoreClient:
for server in self.servers.values():
self.client.add_session_server(self.session_id, server.name, server.address)
def start_session(self) -> StartSessionResponse:
def start_session(self) -> Tuple[bool, List[str]]:
self.ifaces_manager.reset_mac()
nodes = [x.core_node for x in self.canvas_nodes.values()]
nodes = [x.core_node.to_proto() for x in self.canvas_nodes.values()]
links = []
for edge in self.links.values():
link = Link()
link.CopyFrom(edge.link)
if link.HasField("iface1") and not link.iface1.mac:
link = edge.link
if link.iface1 and not link.iface1.mac:
link.iface1.mac = self.ifaces_manager.next_mac()
if link.HasField("iface2") and not link.iface2.mac:
if link.iface2 and not link.iface2.mac:
link.iface2.mac = self.ifaces_manager.next_mac()
links.append(link)
links.append(link.to_proto())
wlan_configs = self.get_wlan_configs_proto()
mobility_configs = self.get_mobility_configs_proto()
emane_model_configs = self.get_emane_model_configs_proto()
hooks = list(self.hooks.values())
hooks = [x.to_proto() for x in self.hooks.values()]
service_configs = self.get_service_configs_proto()
file_configs = self.get_service_file_configs_proto()
asymmetric_links = [
@ -581,14 +587,15 @@ class CoreClient:
emane_config = {x: self.emane_config[x].value for x in self.emane_config}
else:
emane_config = None
response = StartSessionResponse(result=False)
result = False
exceptions = []
try:
self.send_servers()
response = self.client.start_session(
self.session_id,
nodes,
links,
self.location,
self.location.to_proto(),
hooks,
emane_config,
emane_model_configs,
@ -604,20 +611,23 @@ class CoreClient:
)
if response.result:
self.set_metadata()
result = response.result
exceptions = response.exceptions
except grpc.RpcError as e:
self.app.show_grpc_exception("Start Session Error", e)
return response
return result, exceptions
def stop_session(self, session_id: int = None) -> StopSessionResponse:
def stop_session(self, session_id: int = None) -> bool:
if not session_id:
session_id = self.session_id
response = StopSessionResponse(result=False)
result = False
try:
response = self.client.stop_session(session_id)
logging.info("stopped session(%s), result: %s", session_id, response)
result = response.result
except grpc.RpcError as e:
self.app.show_grpc_exception("Stop Session Error", e)
return response
return result
def show_mobility_players(self) -> None:
for canvas_node in self.canvas_nodes.values():
@ -701,7 +711,7 @@ class CoreClient:
logging.debug(
"get node(%s) %s service, response: %s", node_id, service_name, response
)
return response.service
return NodeServiceData.from_proto(response.service)
def set_node_service(
self,
@ -735,7 +745,7 @@ class CoreClient:
response,
)
response = self.client.get_node_service(self.session_id, node_id, service_name)
return response.service
return NodeServiceData.from_proto(response.service)
def get_node_service_file(
self, node_id: int, service_name: str, file_name: str
@ -771,12 +781,9 @@ class CoreClient:
"""
create nodes and links that have not been created yet
"""
node_protos = [x.core_node for x in self.canvas_nodes.values()]
link_protos = [x.link for x in self.links.values()]
if self.state != SessionState.DEFINITION:
self.client.set_session_state(self.session_id, SessionState.DEFINITION)
self.client.set_session_state(self.session_id, SessionState.DEFINITION)
node_protos = [x.core_node.to_proto() for x in self.canvas_nodes.values()]
link_protos = [x.link.to_proto() for x in self.links.values()]
self.client.set_session_state(self.session_id, SessionState.DEFINITION.value)
for node_proto in node_protos:
response = self.client.add_node(self.session_id, node_proto)
logging.debug("create node: %s", response)
@ -823,7 +830,9 @@ class CoreClient:
config_proto.data,
)
for hook in self.hooks.values():
self.client.add_hook(self.session_id, hook.state, hook.file, hook.data)
self.client.add_hook(
self.session_id, hook.state.value, hook.file, hook.data
)
for config_proto in self.get_emane_model_configs_proto():
self.client.set_emane_model_config(
self.session_id,
@ -917,7 +926,7 @@ class CoreClient:
)
return node
def deleted_graph_nodes(self, canvas_nodes: List[Node]) -> None:
def deleted_graph_nodes(self, canvas_nodes: List[CanvasNode]) -> None:
"""
remove the nodes selected by the user and anything related to that node
such as link, configurations, interfaces
@ -948,13 +957,7 @@ class CoreClient:
ip6=ip6,
ip6_mask=ip6_mask,
)
logging.info(
"create node(%s) interface(%s) IPv4(%s) IPv6(%s)",
node.name,
iface.name,
iface.ip4,
iface.ip6,
)
logging.info("create node(%s) interface(%s)", node.name, iface)
return iface
def create_link(
@ -1000,35 +1003,35 @@ class CoreClient:
self.links[edge.token] = edge
logging.info("Add link between %s and %s", src_node.name, dst_node.name)
def get_wlan_configs_proto(self) -> List[WlanConfig]:
def get_wlan_configs_proto(self) -> List[wlan_pb2.WlanConfig]:
configs = []
for canvas_node in self.canvas_nodes.values():
if canvas_node.core_node.type != NodeType.WIRELESS_LAN:
continue
if not canvas_node.wlan_config:
continue
config = canvas_node.wlan_config
config = {x: config[x].value for x in config}
config = ConfigOption.to_dict(canvas_node.wlan_config)
node_id = canvas_node.core_node.id
wlan_config = WlanConfig(node_id=node_id, config=config)
wlan_config = wlan_pb2.WlanConfig(node_id=node_id, config=config)
configs.append(wlan_config)
return configs
def get_mobility_configs_proto(self) -> List[MobilityConfig]:
def get_mobility_configs_proto(self) -> List[mobility_pb2.MobilityConfig]:
configs = []
for canvas_node in self.canvas_nodes.values():
if canvas_node.core_node.type != NodeType.WIRELESS_LAN:
continue
if not canvas_node.mobility_config:
continue
config = canvas_node.mobility_config
config = {x: config[x].value for x in config}
config = ConfigOption.to_dict(canvas_node.mobility_config)
node_id = canvas_node.core_node.id
mobility_config = MobilityConfig(node_id=node_id, config=config)
mobility_config = mobility_pb2.MobilityConfig(
node_id=node_id, config=config
)
configs.append(mobility_config)
return configs
def get_emane_model_configs_proto(self) -> List[EmaneModelConfig]:
def get_emane_model_configs_proto(self) -> List[emane_pb2.EmaneModelConfig]:
configs = []
for canvas_node in self.canvas_nodes.values():
if canvas_node.core_node.type != NodeType.EMANE:
@ -1036,16 +1039,16 @@ class CoreClient:
node_id = canvas_node.core_node.id
for key, config in canvas_node.emane_model_configs.items():
model, iface_id = key
config = {x: config[x].value for x in config}
config = ConfigOption.to_dict(config)
if iface_id is None:
iface_id = -1
config_proto = EmaneModelConfig(
config_proto = emane_pb2.EmaneModelConfig(
node_id=node_id, iface_id=iface_id, model=model, config=config
)
configs.append(config_proto)
return configs
def get_service_configs_proto(self) -> List[ServiceConfig]:
def get_service_configs_proto(self) -> List[services_pb2.ServiceConfig]:
configs = []
for canvas_node in self.canvas_nodes.values():
if not NodeUtils.is_container_node(canvas_node.core_node.type):
@ -1054,7 +1057,7 @@ class CoreClient:
continue
node_id = canvas_node.core_node.id
for name, config in canvas_node.service_configs.items():
config_proto = ServiceConfig(
config_proto = services_pb2.ServiceConfig(
node_id=node_id,
service=name,
directories=config.dirs,
@ -1066,7 +1069,7 @@ class CoreClient:
configs.append(config_proto)
return configs
def get_service_file_configs_proto(self) -> List[ServiceFileConfig]:
def get_service_file_configs_proto(self) -> List[services_pb2.ServiceFileConfig]:
configs = []
for canvas_node in self.canvas_nodes.values():
if not NodeUtils.is_container_node(canvas_node.core_node.type):
@ -1076,13 +1079,15 @@ class CoreClient:
node_id = canvas_node.core_node.id
for service, file_configs in canvas_node.service_file_configs.items():
for file, data in file_configs.items():
config_proto = ServiceFileConfig(
config_proto = services_pb2.ServiceFileConfig(
node_id=node_id, service=service, file=file, data=data
)
configs.append(config_proto)
return configs
def get_config_service_configs_proto(self) -> List[ConfigServiceConfig]:
def get_config_service_configs_proto(
self
) -> List[configservices_pb2.ConfigServiceConfig]:
config_service_protos = []
for canvas_node in self.canvas_nodes.values():
if not NodeUtils.is_container_node(canvas_node.core_node.type):
@ -1092,7 +1097,7 @@ class CoreClient:
node_id = canvas_node.core_node.id
for name, service_config in canvas_node.config_service_configs.items():
config = service_config.get("config", {})
config_proto = ConfigServiceConfig(
config_proto = configservices_pb2.ConfigServiceConfig(
node_id=node_id,
name=name,
templates=service_config["templates"],
@ -1113,7 +1118,7 @@ class CoreClient:
node_id,
config,
)
return dict(config)
return ConfigOption.from_dict(config)
def get_mobility_config(self, node_id: int) -> Dict[str, ConfigOption]:
response = self.client.get_mobility_config(self.session_id, node_id)
@ -1123,7 +1128,7 @@ class CoreClient:
node_id,
config,
)
return dict(config)
return ConfigOption.from_dict(config)
def get_emane_model_config(
self, node_id: int, model: str, iface_id: int = None
@ -1142,7 +1147,7 @@ class CoreClient:
iface_id,
config,
)
return dict(config)
return ConfigOption.from_dict(config)
def execute_script(self, script) -> None:
response = self.client.execute_script(script)

View file

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

View file

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

View file

@ -8,12 +8,11 @@ from typing import TYPE_CHECKING, Dict, List, Optional
import grpc
from core.api.grpc.common_pb2 import ConfigOption
from core.api.grpc.core_pb2 import Node
from core.gui.dialogs.dialog import Dialog
from core.gui.images import ImageEnum, Images
from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame
from core.gui.wrappers import ConfigOption, Node
if TYPE_CHECKING:
from core.gui.app import Application

View file

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

View file

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

View file

@ -6,11 +6,10 @@ from typing import TYPE_CHECKING, Dict, Optional
import grpc
from core.api.grpc.common_pb2 import ConfigOption
from core.api.grpc.core_pb2 import Node
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame
from core.gui.wrappers import ConfigOption, Node
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -4,12 +4,10 @@ from typing import TYPE_CHECKING, Dict, Optional
import grpc
from core.api.grpc.common_pb2 import ConfigOption
from core.api.grpc.core_pb2 import Node
from core.api.grpc.mobility_pb2 import MobilityAction
from core.gui.dialogs.dialog import Dialog
from core.gui.images import ImageEnum
from core.gui.themes import PADX, PADY
from core.gui.wrappers import ConfigOption, MobilityAction, Node
if TYPE_CHECKING:
from core.gui.app import Application
@ -151,7 +149,7 @@ class MobilityPlayerDialog(Dialog):
session_id = self.app.core.session_id
try:
self.app.core.client.mobility_action(
session_id, self.node.id, MobilityAction.START
session_id, self.node.id, MobilityAction.START.value
)
except grpc.RpcError as e:
self.app.show_grpc_exception("Mobility Error", e)
@ -161,7 +159,7 @@ class MobilityPlayerDialog(Dialog):
session_id = self.app.core.session_id
try:
self.app.core.client.mobility_action(
session_id, self.node.id, MobilityAction.PAUSE
session_id, self.node.id, MobilityAction.PAUSE.value
)
except grpc.RpcError as e:
self.app.show_grpc_exception("Mobility Error", e)
@ -171,7 +169,7 @@ class MobilityPlayerDialog(Dialog):
session_id = self.app.core.session_id
try:
self.app.core.client.mobility_action(
session_id, self.node.id, MobilityAction.STOP
session_id, self.node.id, MobilityAction.STOP.value
)
except grpc.RpcError as e:
self.app.show_grpc_exception("Mobility Error", e)

View file

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

View file

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

View file

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

View file

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

View file

@ -3,11 +3,10 @@ from typing import TYPE_CHECKING, Dict, Optional
import grpc
from core.api.grpc.common_pb2 import ConfigOption
from core.api.grpc.core_pb2 import Node
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame
from core.gui.wrappers import ConfigOption, Node
if TYPE_CHECKING:
from core.gui.app import Application

View file

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

View file

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

View file

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

View file

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

View file

@ -6,9 +6,6 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple
import grpc
from PIL.ImageTk import PhotoImage
from core.api.grpc.common_pb2 import ConfigOption
from core.api.grpc.core_pb2 import Interface, Node, NodeType
from core.api.grpc.services_pb2 import NodeServiceData
from core.gui import themes
from core.gui.dialogs.emaneconfig import EmaneConfigDialog
from core.gui.dialogs.mobilityconfig import MobilityConfigDialog
@ -22,6 +19,7 @@ from core.gui.graph.edges import CanvasEdge, CanvasWirelessEdge
from core.gui.graph.tooltip import CanvasTooltip
from core.gui.images import ImageEnum
from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils
from core.gui.wrappers import ConfigOption, Interface, Node, NodeServiceData, NodeType
if TYPE_CHECKING:
from core.gui.app import Application

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

@ -0,0 +1,592 @@
from dataclasses import dataclass, field
from enum import Enum
from typing import Dict, List
from core.api.grpc import common_pb2, configservices_pb2, core_pb2, services_pb2
class ConfigServiceValidationMode(Enum):
BLOCKING = 0
NON_BLOCKING = 1
TIMER = 2
class ServiceValidationMode(Enum):
BLOCKING = 0
NON_BLOCKING = 1
TIMER = 2
class MobilityAction(Enum):
START = 0
PAUSE = 1
STOP = 2
class ConfigOptionType(Enum):
UINT8 = 1
UINT16 = 2
UINT32 = 3
UINT64 = 4
INT8 = 5
INT16 = 6
INT32 = 7
INT64 = 8
FLOAT = 9
STRING = 10
BOOL = 11
class SessionState(Enum):
DEFINITION = 1
CONFIGURATION = 2
INSTANTIATION = 3
RUNTIME = 4
DATACOLLECT = 5
SHUTDOWN = 6
class NodeType(Enum):
DEFAULT = 0
PHYSICAL = 1
SWITCH = 4
HUB = 5
WIRELESS_LAN = 6
RJ45 = 7
TUNNEL = 8
EMANE = 10
TAP_BRIDGE = 11
PEER_TO_PEER = 12
CONTROL_NET = 13
DOCKER = 15
LXC = 16
class LinkType(Enum):
WIRELESS = 0
WIRED = 1
class ExceptionLevel(Enum):
DEFAULT = 0
FATAL = 1
ERROR = 2
WARNING = 3
NOTICE = 4
class MessageType(Enum):
NONE = 0
ADD = 1
DELETE = 2
CRI = 4
LOCAL = 8
STRING = 16
TEXT = 32
TTY = 64
@dataclass
class ConfigService:
group: str
name: str
executables: List[str]
dependencies: List[str]
directories: List[str]
files: List[str]
startup: List[str]
validate: List[str]
shutdown: List[str]
validation_mode: ConfigServiceValidationMode
validation_timer: int
validation_period: float
@classmethod
def from_proto(cls, proto: configservices_pb2.ConfigService) -> "ConfigService":
return ConfigService(
group=proto.group,
name=proto.name,
executables=proto.executables,
dependencies=proto.dependencies,
directories=proto.directories,
files=proto.files,
startup=proto.startup,
validate=proto.validate,
shutdown=proto.shutdown,
validation_mode=ConfigServiceValidationMode(proto.validation_mode),
validation_timer=proto.validation_timer,
validation_period=proto.validation_period,
)
@dataclass
class NodeServiceData:
executables: List[str]
dependencies: List[str]
dirs: List[str]
configs: List[str]
startup: List[str]
validate: List[str]
validation_mode: ServiceValidationMode
validation_timer: int
shutdown: List[str]
meta: str
@classmethod
def from_proto(cls, proto: services_pb2.NodeServiceData) -> "NodeServiceData":
return NodeServiceData(
executables=proto.executables,
dependencies=proto.dependencies,
dirs=proto.dirs,
configs=proto.configs,
startup=proto.startup,
validate=proto.validate,
validation_mode=proto.validation_mode,
validation_timer=proto.validation_timer,
shutdown=proto.shutdown,
meta=proto.meta,
)
@dataclass
class BridgeThroughput:
node_id: int
throughput: float
@classmethod
def from_proto(cls, proto: core_pb2.BridgeThroughput) -> "BridgeThroughput":
return BridgeThroughput(node_id=proto.node_id, throughput=proto.throughput)
@dataclass
class InterfaceThroughput:
node_id: int
iface_id: int
throughput: float
@classmethod
def from_proto(cls, proto: core_pb2.InterfaceThroughput) -> "InterfaceThroughput":
return InterfaceThroughput(
node_id=proto.node_id, iface_id=proto.iface_id, throughput=proto.throughput
)
@dataclass
class ThroughputsEvent:
session_id: int
bridge_throughputs: List[BridgeThroughput]
iface_throughputs: List[InterfaceThroughput]
@classmethod
def from_proto(cls, proto: core_pb2.ThroughputsEvent) -> "ThroughputsEvent":
bridges = [BridgeThroughput.from_proto(x) for x in proto.bridge_throughputs]
ifaces = [InterfaceThroughput.from_proto(x) for x in proto.iface_throughputs]
return ThroughputsEvent(
session_id=proto.session_id,
bridge_throughputs=bridges,
iface_throughputs=ifaces,
)
@dataclass
class SessionLocation:
x: float
y: float
z: float
lat: float
lon: float
alt: float
scale: float
@classmethod
def from_proto(cls, proto: core_pb2.SessionLocation) -> "SessionLocation":
return SessionLocation(
x=proto.x,
y=proto.y,
z=proto.z,
lat=proto.lat,
lon=proto.lon,
alt=proto.alt,
scale=proto.scale,
)
def to_proto(self) -> core_pb2.SessionLocation:
return core_pb2.SessionLocation(
x=self.x,
y=self.y,
z=self.z,
lat=self.lat,
lon=self.lon,
alt=self.alt,
scale=self.scale,
)
@dataclass
class ExceptionEvent:
session_id: int
node_id: int
level: ExceptionLevel
source: str
date: str
text: str
opaque: str
@classmethod
def from_proto(
cls, session_id: int, proto: core_pb2.ExceptionEvent
) -> "ExceptionEvent":
return ExceptionEvent(
session_id=session_id,
node_id=proto.node_id,
level=ExceptionLevel(proto.level),
source=proto.source,
date=proto.date,
text=proto.text,
opaque=proto.opaque,
)
@dataclass
class ConfigOption:
label: str
name: str
value: str
type: ConfigOptionType
group: str
select: List[str] = None
@classmethod
def from_dict(
cls, config: Dict[str, common_pb2.ConfigOption]
) -> Dict[str, "ConfigOption"]:
d = {}
for key, value in config.items():
d[key] = ConfigOption.from_proto(value)
return d
@classmethod
def to_dict(cls, config: Dict[str, "ConfigOption"]) -> Dict[str, str]:
return {k: v.value for k, v in config.items()}
@classmethod
def from_proto(cls, proto: common_pb2.ConfigOption) -> "ConfigOption":
return ConfigOption(
label=proto.label,
name=proto.name,
value=proto.value,
type=ConfigOptionType(proto.type),
group=proto.group,
select=proto.select,
)
@dataclass
class Interface:
id: int
name: str = None
mac: str = None
ip4: str = None
ip4_mask: int = None
ip6: str = None
ip6_mask: int = None
net_id: int = None
flow_id: int = None
mtu: int = None
node_id: int = None
net2_id: int = None
@classmethod
def from_proto(cls, proto: core_pb2.Interface) -> "Interface":
return Interface(
id=proto.id,
name=proto.name,
mac=proto.mac,
ip4=proto.ip4,
ip4_mask=proto.ip4_mask,
ip6=proto.ip6,
ip6_mask=proto.ip6_mask,
net_id=proto.net_id,
flow_id=proto.flow_id,
mtu=proto.mtu,
node_id=proto.node_id,
net2_id=proto.net2_id,
)
def to_proto(self) -> core_pb2.Interface:
return core_pb2.Interface(
id=self.id,
name=self.name,
mac=self.mac,
ip4=self.ip4,
ip4_mask=self.ip4_mask,
ip6=self.ip6,
ip6_mask=self.ip6_mask,
net_id=self.net_id,
flow_id=self.flow_id,
mtu=self.mtu,
node_id=self.node_id,
net2_id=self.net2_id,
)
@dataclass
class LinkOptions:
jitter: int = 0
key: int = 0
mburst: int = 0
mer: int = 0
loss: float = 0.0
bandwidth: int = 0
burst: int = 0
delay: int = 0
dup: int = 0
unidirectional: bool = False
@classmethod
def from_proto(cls, proto: core_pb2.LinkOptions) -> "LinkOptions":
return LinkOptions(
jitter=proto.jitter,
key=proto.key,
mburst=proto.mburst,
mer=proto.mer,
loss=proto.loss,
bandwidth=proto.bandwidth,
burst=proto.burst,
delay=proto.delay,
dup=proto.dup,
unidirectional=proto.unidirectional,
)
def to_proto(self) -> core_pb2.LinkOptions:
return core_pb2.LinkOptions(
jitter=self.jitter,
key=self.key,
mburst=self.mburst,
mer=self.mer,
loss=self.loss,
bandwidth=self.bandwidth,
burst=self.burst,
delay=self.delay,
dup=self.dup,
unidirectional=self.unidirectional,
)
@dataclass
class Link:
node1_id: int
node2_id: int
type: LinkType = LinkType.WIRED
iface1: Interface = None
iface2: Interface = None
options: LinkOptions = None
network_id: int = None
label: str = None
color: str = None
@classmethod
def from_proto(cls, proto: core_pb2.Link) -> "Link":
iface1 = None
if proto.HasField("iface1"):
iface1 = Interface.from_proto(proto.iface1)
iface2 = None
if proto.HasField("iface2"):
iface2 = Interface.from_proto(proto.iface2)
options = None
if proto.HasField("options"):
options = LinkOptions.from_proto(proto.options)
return Link(
type=LinkType(proto.type),
node1_id=proto.node1_id,
node2_id=proto.node2_id,
iface1=iface1,
iface2=iface2,
options=options,
network_id=proto.network_id,
label=proto.label,
color=proto.color,
)
def to_proto(self) -> core_pb2.Link:
iface1 = self.iface1.to_proto() if self.iface1 else None
iface2 = self.iface2.to_proto() if self.iface2 else None
options = self.options.to_proto() if self.options else None
return core_pb2.Link(
type=self.type.value,
node1_id=self.node1_id,
node2_id=self.node2_id,
iface1=iface1,
iface2=iface2,
options=options,
network_id=self.network_id,
label=self.label,
color=self.color,
)
def is_symmetric(self) -> bool:
result = True
if self.options:
result = self.options.unidirectional is False
return result
@dataclass
class SessionSummary:
id: int
state: SessionState
nodes: int
file: str
dir: str
@classmethod
def from_proto(cls, proto: core_pb2.SessionSummary) -> "SessionSummary":
return SessionSummary(
id=proto.id,
state=SessionState(proto.state),
nodes=proto.nodes,
file=proto.file,
dir=proto.dir,
)
@dataclass
class Hook:
state: SessionState
file: str
data: str
@classmethod
def from_proto(cls, proto: core_pb2.Hook) -> "Hook":
return Hook(state=SessionState(proto.state), file=proto.file, data=proto.data)
def to_proto(self) -> core_pb2.Hook:
return core_pb2.Hook(state=self.state.value, file=self.file, data=self.data)
@dataclass
class Position:
x: float
y: float
@classmethod
def from_proto(cls, proto: core_pb2.Position) -> "Position":
return Position(x=proto.x, y=proto.y)
def to_proto(self) -> core_pb2.Position:
return core_pb2.Position(x=self.x, y=self.y)
@dataclass
class Geo:
lat: float = None
lon: float = None
alt: float = None
@classmethod
def from_proto(cls, proto: core_pb2.Geo) -> "Geo":
return Geo(lat=proto.lat, lon=proto.lon, alt=proto.alt)
def to_proto(self) -> core_pb2.Geo:
return core_pb2.Geo(lat=self.lat, lon=self.lon, alt=self.alt)
@dataclass
class Node:
id: int
name: str
type: NodeType
model: str = None
position: Position = None
services: List[str] = field(default_factory=list)
config_services: List[str] = field(default_factory=list)
emane: str = None
icon: str = None
image: str = None
server: str = None
geo: Geo = None
dir: str = None
channel: str = None
@classmethod
def from_proto(cls, proto: core_pb2.Node) -> "Node":
return Node(
id=proto.id,
name=proto.name,
type=NodeType(proto.type),
model=proto.model,
position=Position.from_proto(proto.position),
services=list(proto.services),
config_services=list(proto.config_services),
emane=proto.emane,
icon=proto.icon,
image=proto.image,
server=proto.server,
geo=Geo.from_proto(proto.geo),
dir=proto.dir,
channel=proto.channel,
)
def to_proto(self) -> core_pb2.Node:
return core_pb2.Node(
id=self.id,
name=self.name,
type=self.type.value,
model=self.model,
position=self.position.to_proto(),
services=self.services,
config_services=self.config_services,
emane=self.emane,
icon=self.icon,
image=self.image,
server=self.server,
dir=self.dir,
channel=self.channel,
)
@dataclass
class Session:
id: int
state: SessionState
nodes: List[Node]
links: List[Link]
dir: str
@classmethod
def from_proto(cls, proto: core_pb2.Session) -> "Session":
nodes = [Node.from_proto(x) for x in proto.nodes]
links = [Link.from_proto(x) for x in proto.links]
return Session(
id=proto.id,
state=SessionState(proto.state),
nodes=nodes,
links=links,
dir=proto.dir,
)
@dataclass
class LinkEvent:
message_type: MessageType
link: Link
@classmethod
def from_proto(cls, proto: core_pb2.LinkEvent) -> "LinkEvent":
return LinkEvent(
message_type=MessageType(proto.message_type),
link=Link.from_proto(proto.link),
)
@dataclass
class NodeEvent:
message_type: MessageType
node: Node
@classmethod
def from_proto(cls, proto: core_pb2.NodeEvent) -> "NodeEvent":
return NodeEvent(
message_type=MessageType(proto.message_type),
node=Node.from_proto(proto.node),
)