initial changes to add config services to coretk gui

This commit is contained in:
Blake Harnden 2020-01-20 12:17:11 -08:00
parent 0e6d1535db
commit 9447ddb94f
5 changed files with 681 additions and 0 deletions

View file

@ -63,6 +63,7 @@ class CoreClient:
self.app = app
self.master = app.master
self.services = {}
self.config_services = {}
self.default_services = {}
self.emane_models = []
self.observer = None
@ -413,6 +414,12 @@ class CoreClient:
group_services = self.services.setdefault(service.group, set())
group_services.add(service.name)
# get config service informations
response = self.client.get_config_services()
for service in response.services:
group_services = self.config_services.setdefault(service.group, set())
group_services.add(service.name)
# if there are no sessions, create a new session, else join a session
response = self.client.get_sessions()
logging.info("current sessions: %s", response)

View file

@ -0,0 +1,494 @@
"""
Service configuration dialog
"""
import tkinter as tk
from tkinter import ttk
from typing import TYPE_CHECKING, Any, List
import grpc
from core.api.grpc import core_pb2
from core.gui.dialogs.copyserviceconfig import CopyServiceConfigDialog
from core.gui.dialogs.dialog import Dialog
from core.gui.errors import show_grpc_error
from core.gui.images import ImageEnum, Images
from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import CodeText, ListboxScroll
if TYPE_CHECKING:
from core.gui.app import Application
class ConfigServiceConfigDialog(Dialog):
def __init__(
self, master: Any, app: "Application", service_name: str, node_id: int
):
title = f"{service_name} Config Service"
super().__init__(master, app, title, modal=True)
self.master = master
self.app = app
self.core = app.core
self.node_id = node_id
self.service_name = service_name
self.service_configs = app.core.service_configs
self.file_configs = app.core.file_configs
self.radiovar = tk.IntVar()
self.radiovar.set(2)
self.filenames = []
self.dependencies = []
self.executables = []
self.startup_commands = []
self.validation_commands = []
self.shutdown_commands = []
self.default_startup = []
self.default_validate = []
self.default_shutdown = []
self.validation_mode = None
self.validation_time = None
self.validation_period = None
self.documentnew_img = Images.get(ImageEnum.DOCUMENTNEW, 16)
self.editdelete_img = Images.get(ImageEnum.EDITDELETE, 16)
self.notebook = None
self.filename_combobox = None
self.startup_commands_listbox = None
self.shutdown_commands_listbox = None
self.validate_commands_listbox = None
self.validation_time_entry = None
self.validation_mode_entry = None
self.service_file_data = None
self.validation_period_entry = None
self.original_service_files = {}
self.temp_service_files = {}
self.modified_files = set()
self.load()
self.draw()
def load(self):
try:
self.app.core.create_nodes_and_links()
default_config = self.app.core.get_node_service(
self.node_id, self.service_name
)
self.default_startup = default_config.startup[:]
self.default_validate = default_config.validate[:]
self.default_shutdown = default_config.shutdown[:]
custom_configs = self.service_configs
if (
self.node_id in custom_configs
and self.service_name in custom_configs[self.node_id]
):
service_config = custom_configs[self.node_id][self.service_name]
else:
service_config = default_config
self.dependencies = service_config.dependencies[:]
self.executables = service_config.executables[:]
self.filenames = service_config.configs[:]
self.startup_commands = service_config.startup[:]
self.validation_commands = service_config.validate[:]
self.shutdown_commands = service_config.shutdown[:]
self.validation_mode = service_config.validation_mode
self.validation_time = service_config.validation_timer
self.original_service_files = {
x: self.app.core.get_node_service_file(
self.node_id, self.service_name, x
)
for x in self.filenames
}
self.temp_service_files = dict(self.original_service_files)
file_configs = self.file_configs
if (
self.node_id in file_configs
and self.service_name in file_configs[self.node_id]
):
for file, data in file_configs[self.node_id][self.service_name].items():
self.temp_service_files[file] = data
except grpc.RpcError as e:
show_grpc_error(e)
def draw(self):
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(1, weight=1)
# draw notebook
self.notebook = ttk.Notebook(self.top)
self.notebook.grid(sticky="nsew", pady=PADY)
self.draw_tab_files()
self.draw_tab_directories()
self.draw_tab_startstop()
self.draw_tab_configuration()
self.draw_buttons()
def draw_tab_files(self):
tab = ttk.Frame(self.notebook, padding=FRAME_PAD)
tab.grid(sticky="nsew")
tab.columnconfigure(0, weight=1)
self.notebook.add(tab, text="Files")
label = ttk.Label(
tab, text="Config files and scripts that are generated for this service."
)
label.grid()
frame = ttk.Frame(tab)
frame.grid(sticky="ew", pady=PADY)
frame.columnconfigure(1, weight=1)
label = ttk.Label(frame, text="File Name")
label.grid(row=0, column=0, padx=PADX, sticky="w")
self.filename_combobox = ttk.Combobox(
frame, values=self.filenames, state="readonly"
)
self.filename_combobox.bind(
"<<ComboboxSelected>>", self.display_service_file_data
)
self.filename_combobox.grid(row=0, column=1, sticky="ew", padx=PADX)
button = ttk.Button(frame, image=self.documentnew_img, state="disabled")
button.bind("<Button-1>", self.add_filename)
button.grid(row=0, column=2, padx=PADX)
button = ttk.Button(frame, image=self.editdelete_img, state="disabled")
button.bind("<Button-1>", self.delete_filename)
button.grid(row=0, column=3)
frame = ttk.Frame(tab)
frame.grid(sticky="ew", pady=PADY)
frame.columnconfigure(1, weight=1)
button = ttk.Radiobutton(
frame,
variable=self.radiovar,
text="Copy Source File",
value=1,
state=tk.DISABLED,
)
button.grid(row=0, column=0, sticky="w", padx=PADX)
entry = ttk.Entry(frame, state=tk.DISABLED)
entry.grid(row=0, column=1, sticky="ew", padx=PADX)
image = Images.get(ImageEnum.FILEOPEN, 16)
button = ttk.Button(frame, image=image)
button.image = image
button.grid(row=0, column=2)
frame = ttk.Frame(tab)
frame.grid(sticky="ew", pady=PADY)
frame.columnconfigure(0, weight=1)
button = ttk.Radiobutton(
frame,
variable=self.radiovar,
text="Use text below for file contents",
value=2,
)
button.grid(row=0, column=0, sticky="ew")
image = Images.get(ImageEnum.FILEOPEN, 16)
button = ttk.Button(frame, image=image)
button.image = image
button.grid(row=0, column=1)
image = Images.get(ImageEnum.DOCUMENTSAVE, 16)
button = ttk.Button(frame, image=image)
button.image = image
button.grid(row=0, column=2)
self.service_file_data = CodeText(tab)
self.service_file_data.grid(sticky="nsew")
tab.rowconfigure(self.service_file_data.grid_info()["row"], weight=1)
if len(self.filenames) > 0:
self.filename_combobox.current(0)
self.service_file_data.text.delete(1.0, "end")
self.service_file_data.text.insert(
"end", self.temp_service_files[self.filenames[0]]
)
self.service_file_data.text.bind(
"<FocusOut>", self.update_temp_service_file_data
)
def draw_tab_directories(self):
tab = ttk.Frame(self.notebook, padding=FRAME_PAD)
tab.grid(sticky="nsew")
tab.columnconfigure(0, weight=1)
self.notebook.add(tab, text="Directories")
label = ttk.Label(
tab,
text="Directories required by this service that are unique for each node.",
)
label.grid()
def draw_tab_startstop(self):
tab = ttk.Frame(self.notebook, padding=FRAME_PAD)
tab.grid(sticky="nsew")
tab.columnconfigure(0, weight=1)
for i in range(3):
tab.rowconfigure(i, weight=1)
self.notebook.add(tab, text="Startup/Shutdown")
commands = []
# tab 3
for i in range(3):
label_frame = None
if i == 0:
label_frame = ttk.LabelFrame(
tab, text="Startup Commands", padding=FRAME_PAD
)
commands = self.startup_commands
elif i == 1:
label_frame = ttk.LabelFrame(
tab, text="Shutdown Commands", padding=FRAME_PAD
)
commands = self.shutdown_commands
elif i == 2:
label_frame = ttk.LabelFrame(
tab, text="Validation Commands", padding=FRAME_PAD
)
commands = self.validation_commands
label_frame.columnconfigure(0, weight=1)
label_frame.rowconfigure(1, weight=1)
label_frame.grid(row=i, column=0, sticky="nsew", pady=PADY)
frame = ttk.Frame(label_frame)
frame.grid(row=0, column=0, sticky="nsew", pady=PADY)
frame.columnconfigure(0, weight=1)
entry = ttk.Entry(frame, textvariable=tk.StringVar())
entry.grid(row=0, column=0, stick="ew", padx=PADX)
button = ttk.Button(frame, image=self.documentnew_img)
button.bind("<Button-1>", self.add_command)
button.grid(row=0, column=1, sticky="ew", padx=PADX)
button = ttk.Button(frame, image=self.editdelete_img)
button.grid(row=0, column=2, sticky="ew")
button.bind("<Button-1>", self.delete_command)
listbox_scroll = ListboxScroll(label_frame)
listbox_scroll.listbox.bind("<<ListboxSelect>>", self.update_entry)
for command in commands:
listbox_scroll.listbox.insert("end", command)
listbox_scroll.listbox.config(height=4)
listbox_scroll.grid(row=1, column=0, sticky="nsew")
if i == 0:
self.startup_commands_listbox = listbox_scroll.listbox
elif i == 1:
self.shutdown_commands_listbox = listbox_scroll.listbox
elif i == 2:
self.validate_commands_listbox = listbox_scroll.listbox
def draw_tab_configuration(self):
tab = ttk.Frame(self.notebook, padding=FRAME_PAD)
tab.grid(sticky="nsew")
tab.columnconfigure(0, weight=1)
self.notebook.add(tab, text="Configuration", sticky="nsew")
frame = ttk.Frame(tab)
frame.grid(sticky="ew", pady=PADY)
frame.columnconfigure(1, weight=1)
label = ttk.Label(frame, text="Validation Time")
label.grid(row=0, column=0, sticky="w", padx=PADX)
self.validation_time_entry = ttk.Entry(frame)
self.validation_time_entry.insert("end", self.validation_time)
self.validation_time_entry.config(state=tk.DISABLED)
self.validation_time_entry.grid(row=0, column=1, sticky="ew", pady=PADY)
label = ttk.Label(frame, text="Validation Mode")
label.grid(row=1, column=0, sticky="w", padx=PADX)
if self.validation_mode == core_pb2.ServiceValidationMode.BLOCKING:
mode = "BLOCKING"
elif self.validation_mode == core_pb2.ServiceValidationMode.NON_BLOCKING:
mode = "NON_BLOCKING"
else:
mode = "TIMER"
self.validation_mode_entry = ttk.Entry(
frame, textvariable=tk.StringVar(value=mode)
)
self.validation_mode_entry.insert("end", mode)
self.validation_mode_entry.config(state=tk.DISABLED)
self.validation_mode_entry.grid(row=1, column=1, sticky="ew", pady=PADY)
label = ttk.Label(frame, text="Validation Period")
label.grid(row=2, column=0, sticky="w", padx=PADX)
self.validation_period_entry = ttk.Entry(
frame, state=tk.DISABLED, textvariable=tk.StringVar()
)
self.validation_period_entry.grid(row=2, column=1, sticky="ew", pady=PADY)
label_frame = ttk.LabelFrame(tab, text="Executables", padding=FRAME_PAD)
label_frame.grid(sticky="nsew", pady=PADY)
label_frame.columnconfigure(0, weight=1)
label_frame.rowconfigure(0, weight=1)
listbox_scroll = ListboxScroll(label_frame)
listbox_scroll.grid(sticky="nsew")
tab.rowconfigure(listbox_scroll.grid_info()["row"], weight=1)
for executable in self.executables:
listbox_scroll.listbox.insert("end", executable)
label_frame = ttk.LabelFrame(tab, text="Dependencies", padding=FRAME_PAD)
label_frame.grid(sticky="nsew", pady=PADY)
label_frame.columnconfigure(0, weight=1)
label_frame.rowconfigure(0, weight=1)
listbox_scroll = ListboxScroll(label_frame)
listbox_scroll.grid(sticky="nsew")
tab.rowconfigure(listbox_scroll.grid_info()["row"], weight=1)
for dependency in self.dependencies:
listbox_scroll.listbox.insert("end", dependency)
def draw_buttons(self):
frame = ttk.Frame(self.top)
frame.grid(sticky="ew")
for i in range(4):
frame.columnconfigure(i, weight=1)
button = ttk.Button(frame, text="Apply", command=self.click_apply)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Defaults", command=self.click_defaults)
button.grid(row=0, column=1, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Copy...", command=self.click_copy)
button.grid(row=0, column=2, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=3, sticky="ew")
def add_filename(self, event: tk.Event):
# not worry about it for now
return
frame_contains_button = event.widget.master
combobox = frame_contains_button.grid_slaves(row=0, column=1)[0]
filename = combobox.get()
if filename not in combobox["values"]:
combobox["values"] += (filename,)
def delete_filename(self, event: tk.Event):
# not worry about it for now
return
frame_comntains_button = event.widget.master
combobox = frame_comntains_button.grid_slaves(row=0, column=1)[0]
filename = combobox.get()
if filename in combobox["values"]:
combobox["values"] = tuple([x for x in combobox["values"] if x != filename])
combobox.set("")
def add_command(self, event: tk.Event):
frame_contains_button = event.widget.master
listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox
command_to_add = frame_contains_button.grid_slaves(row=0, column=0)[0].get()
if command_to_add == "":
return
for cmd in listbox.get(0, tk.END):
if cmd == command_to_add:
return
listbox.insert(tk.END, command_to_add)
def update_entry(self, event: tk.Event):
listbox = event.widget
current_selection = listbox.curselection()
if len(current_selection) > 0:
cmd = listbox.get(current_selection[0])
entry = listbox.master.master.grid_slaves(row=0, column=0)[0].grid_slaves(
row=0, column=0
)[0]
entry.delete(0, "end")
entry.insert(0, cmd)
def delete_command(self, event: tk.Event):
button = event.widget
frame_contains_button = button.master
listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox
current_selection = listbox.curselection()
if len(current_selection) > 0:
listbox.delete(current_selection[0])
entry = frame_contains_button.grid_slaves(row=0, column=0)[0]
entry.delete(0, tk.END)
def click_apply(self):
current_listbox = self.master.current.listbox
if not self.is_custom_service_config() and not self.is_custom_service_file():
if self.node_id in self.service_configs:
self.service_configs[self.node_id].pop(self.service_name, None)
current_listbox.itemconfig(current_listbox.curselection()[0], bg="")
self.destroy()
return
try:
if self.is_custom_service_config():
startup_commands = self.startup_commands_listbox.get(0, "end")
shutdown_commands = self.shutdown_commands_listbox.get(0, "end")
validate_commands = self.validate_commands_listbox.get(0, "end")
config = self.core.set_node_service(
self.node_id,
self.service_name,
startup_commands,
validate_commands,
shutdown_commands,
)
if self.node_id not in self.service_configs:
self.service_configs[self.node_id] = {}
self.service_configs[self.node_id][self.service_name] = config
for file in self.modified_files:
if self.node_id not in self.file_configs:
self.file_configs[self.node_id] = {}
if self.service_name not in self.file_configs[self.node_id]:
self.file_configs[self.node_id][self.service_name] = {}
self.file_configs[self.node_id][self.service_name][
file
] = self.temp_service_files[file]
self.app.core.set_node_service_file(
self.node_id, self.service_name, file, self.temp_service_files[file]
)
all_current = current_listbox.get(0, tk.END)
current_listbox.itemconfig(all_current.index(self.service_name), bg="green")
except grpc.RpcError as e:
show_grpc_error(e)
self.destroy()
def display_service_file_data(self, event: tk.Event):
combobox = event.widget
filename = combobox.get()
self.service_file_data.text.delete(1.0, "end")
self.service_file_data.text.insert("end", self.temp_service_files[filename])
def update_temp_service_file_data(self, event: tk.Event):
scrolledtext = event.widget
filename = self.filename_combobox.get()
self.temp_service_files[filename] = scrolledtext.get(1.0, "end")
if self.temp_service_files[filename] != self.original_service_files[filename]:
self.modified_files.add(filename)
else:
self.modified_files.discard(filename)
def is_custom_service_config(self):
startup_commands = self.startup_commands_listbox.get(0, "end")
shutdown_commands = self.shutdown_commands_listbox.get(0, "end")
validate_commands = self.validate_commands_listbox.get(0, "end")
return (
set(self.default_startup) != set(startup_commands)
or set(self.default_validate) != set(validate_commands)
or set(self.default_shutdown) != set(shutdown_commands)
)
def is_custom_service_file(self):
return len(self.modified_files) > 0
def click_defaults(self):
if self.node_id in self.service_configs:
self.service_configs[self.node_id].pop(self.service_name, None)
if self.node_id in self.file_configs:
self.file_configs[self.node_id].pop(self.service_name, None)
self.temp_service_files = dict(self.original_service_files)
filename = self.filename_combobox.get()
self.service_file_data.text.delete(1.0, "end")
self.service_file_data.text.insert("end", self.temp_service_files[filename])
self.startup_commands_listbox.delete(0, tk.END)
self.validate_commands_listbox.delete(0, tk.END)
self.shutdown_commands_listbox.delete(0, tk.END)
for cmd in self.default_startup:
self.startup_commands_listbox.insert(tk.END, cmd)
for cmd in self.default_validate:
self.validate_commands_listbox.insert(tk.END, cmd)
for cmd in self.default_shutdown:
self.shutdown_commands_listbox.insert(tk.END, cmd)
def click_copy(self):
dialog = CopyServiceConfigDialog(self, self.app, self.node_id)
dialog.show()
def append_commands(
self, commands: List[str], listbox: tk.Listbox, to_add: List[str]
):
for cmd in to_add:
commands.append(cmd)
listbox.insert(tk.END, cmd)

View file

@ -0,0 +1,169 @@
"""
core node services
"""
import tkinter as tk
from tkinter import messagebox, ttk
from typing import TYPE_CHECKING, Any, Set
from core.gui.dialogs.configserviceconfig import ConfigServiceConfigDialog
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import CheckboxList, ListboxScroll
if TYPE_CHECKING:
from core.gui.app import Application
from core.gui.graph.node import CanvasNode
class NodeConfigServiceDialog(Dialog):
def __init__(
self,
master: Any,
app: "Application",
canvas_node: "CanvasNode",
services: Set[str] = None,
):
title = f"{canvas_node.core_node.name} Config Services"
super().__init__(master, app, title, modal=True)
self.app = app
self.canvas_node = canvas_node
self.node_id = canvas_node.core_node.id
self.groups = None
self.services = None
self.current = None
if services is None:
services = set(canvas_node.core_node.config_services)
self.current_services = services
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(0, weight=1)
frame = ttk.Frame(self.top)
frame.grid(stick="nsew", pady=PADY)
frame.rowconfigure(0, weight=1)
for i in range(3):
frame.columnconfigure(i, weight=1)
label_frame = ttk.LabelFrame(frame, text="Groups", padding=FRAME_PAD)
label_frame.grid(row=0, column=0, sticky="nsew")
label_frame.rowconfigure(0, weight=1)
label_frame.columnconfigure(0, weight=1)
self.groups = ListboxScroll(label_frame)
self.groups.grid(sticky="nsew")
for group in sorted(self.app.core.config_services):
self.groups.listbox.insert(tk.END, group)
self.groups.listbox.bind("<<ListboxSelect>>", self.handle_group_change)
self.groups.listbox.selection_set(0)
label_frame = ttk.LabelFrame(frame, text="Services")
label_frame.grid(row=0, column=1, sticky="nsew")
label_frame.columnconfigure(0, weight=1)
label_frame.rowconfigure(0, weight=1)
self.services = CheckboxList(
label_frame, self.app, clicked=self.service_clicked, padding=FRAME_PAD
)
self.services.grid(sticky="nsew")
label_frame = ttk.LabelFrame(frame, text="Selected", padding=FRAME_PAD)
label_frame.grid(row=0, column=2, sticky="nsew")
label_frame.rowconfigure(0, weight=1)
label_frame.columnconfigure(0, weight=1)
self.current = ListboxScroll(label_frame)
self.current.grid(sticky="nsew")
for service in sorted(self.current_services):
self.current.listbox.insert(tk.END, service)
if self.is_custom_service(service):
self.current.listbox.itemconfig(tk.END, bg="green")
frame = ttk.Frame(self.top)
frame.grid(stick="ew")
for i in range(4):
frame.columnconfigure(i, weight=1)
button = ttk.Button(frame, text="Configure", command=self.click_configure)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Save", command=self.click_save)
button.grid(row=0, column=1, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Remove", command=self.click_remove)
button.grid(row=0, column=2, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Cancel", command=self.click_cancel)
button.grid(row=0, column=3, sticky="ew")
# trigger group change
self.groups.listbox.event_generate("<<ListboxSelect>>")
def handle_group_change(self, event: tk.Event = None):
selection = self.groups.listbox.curselection()
if selection:
index = selection[0]
group = self.groups.listbox.get(index)
self.services.clear()
for name in sorted(self.app.core.config_services[group]):
checked = name in self.current_services
self.services.add(name, checked)
def service_clicked(self, name: str, var: tk.IntVar):
if var.get() and name not in self.current_services:
self.current_services.add(name)
elif not var.get() and name in self.current_services:
self.current_services.remove(name)
self.current.listbox.delete(0, tk.END)
for name in sorted(self.current_services):
self.current.listbox.insert(tk.END, name)
if self.is_custom_service(name):
self.current.listbox.itemconfig(tk.END, bg="green")
self.canvas_node.core_node.config_services[:] = self.current_services
def click_configure(self):
current_selection = self.current.listbox.curselection()
if len(current_selection):
dialog = ConfigServiceConfigDialog(
master=self,
app=self.app,
service_name=self.current.listbox.get(current_selection[0]),
node_id=self.node_id,
)
dialog.show()
else:
messagebox.showinfo(
"Node service configuration", "Select a service to configure"
)
def click_save(self):
if (
self.current_services
!= self.app.core.default_services[self.canvas_node.core_node.model]
):
self.canvas_node.core_node.config_services[:] = self.current_services
else:
if len(self.canvas_node.core_node.config_services) > 0:
self.canvas_node.core_node.config_services[:] = []
self.destroy()
def click_cancel(self):
self.current_services = None
self.destroy()
def click_remove(self):
cur = self.current.listbox.curselection()
if cur:
service = self.current.listbox.get(cur[0])
self.current.listbox.delete(cur[0])
self.current_services.remove(service)
for checkbutton in self.services.frame.winfo_children():
if checkbutton["text"] == service:
checkbutton.invoke()
return
def is_custom_service(self, service: str) -> bool:
service_configs = self.app.core.service_configs
file_configs = self.app.core.file_configs
if self.node_id in service_configs and service in service_configs[self.node_id]:
return True
if (
self.node_id in file_configs
and service in file_configs[self.node_id]
and file_configs[self.node_id][service]
):
return True
return False

View file

@ -10,6 +10,7 @@ from core.gui import themes
from core.gui.dialogs.emaneconfig import EmaneConfigDialog
from core.gui.dialogs.mobilityconfig import MobilityConfigDialog
from core.gui.dialogs.nodeconfig import NodeConfigDialog
from core.gui.dialogs.nodeconfigservice import NodeConfigServiceDialog
from core.gui.dialogs.nodeservice import NodeServiceDialog
from core.gui.dialogs.wlanconfig import WlanConfigDialog
from core.gui.errors import show_grpc_error
@ -180,6 +181,7 @@ class CanvasNode:
context.add_command(label="Configure", command=self.show_config)
if NodeUtils.is_container_node(self.core_node.type):
context.add_command(label="Services", state=tk.DISABLED)
context.add_command(label="Config Services", state=tk.DISABLED)
if is_wlan:
context.add_command(label="WLAN Config", command=self.show_wlan_config)
if is_wlan and self.core_node.id in self.app.core.mobility_players:
@ -198,6 +200,9 @@ class CanvasNode:
context.add_command(label="Configure", command=self.show_config)
if NodeUtils.is_container_node(self.core_node.type):
context.add_command(label="Services", command=self.show_services)
context.add_command(
label="Config Services", command=self.show_config_services
)
if is_emane:
context.add_command(
label="EMANE Config", command=self.show_emane_config
@ -253,6 +258,11 @@ class CanvasNode:
dialog = NodeServiceDialog(self.app.master, self.app, self)
dialog.show()
def show_config_services(self):
self.canvas.context = None
dialog = NodeConfigServiceDialog(self.app.master, self.app, self)
dialog.show()
def has_emane_link(self, interface_id: int) -> core_pb2.Node:
result = None
for edge in self.edges:

View file

@ -953,6 +953,7 @@ message Node {
string opaque = 9;
string image = 10;
string server = 11;
repeated string config_services = 12;
}
message Link {