Merge pull request #383 from coreemu/coretk-enhance/disable-runtime-editing-custom-service-config

Coretk enhance/disable runtime editing custom service config
This commit is contained in:
bharnden 2020-03-03 12:54:47 -08:00 committed by GitHub
commit 7c8e115bc7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 233 additions and 138 deletions

View file

@ -381,7 +381,7 @@ def service_configuration(session: Session, config: core_pb2.ServiceConfig) -> N
if config.files: if config.files:
service.configs = tuple(config.files) service.configs = tuple(config.files)
if config.directories: if config.directories:
service.directories = tuple(config.directories) service.dirs = tuple(config.directories)
if config.startup: if config.startup:
service.startup = tuple(config.startup) service.startup = tuple(config.startup)
if config.validate: if config.validate:

View file

@ -500,7 +500,6 @@ 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 = core_pb2.StartSessionResponse(result=False) response = core_pb2.StartSessionResponse(result=False)
try: try:
response = self.client.start_session( response = self.client.start_session(
@ -521,7 +520,6 @@ class CoreClient:
logging.info( logging.info(
"start session(%s), result: %s", self.session_id, response.result "start session(%s), result: %s", self.session_id, response.result
) )
if response.result: if response.result:
self.set_metadata() self.set_metadata()
except grpc.RpcError as e: except grpc.RpcError as e:
@ -620,6 +618,8 @@ class CoreClient:
self, self,
node_id: int, node_id: int,
service_name: str, service_name: str,
dirs: List[str],
files: List[str],
startups: List[str], startups: List[str],
validations: List[str], validations: List[str],
shutdowns: List[str], shutdowns: List[str],
@ -628,14 +628,17 @@ class CoreClient:
self.session_id, self.session_id,
node_id, node_id,
service_name, service_name,
directories=dirs,
files=files,
startup=startups, startup=startups,
validate=validations, validate=validations,
shutdown=shutdowns, shutdown=shutdowns,
) )
logging.info( logging.info(
"Set %s service for node(%s), Startup: %s, Validation: %s, Shutdown: %s, Result: %s", "Set %s service for node(%s), files: %s, Startup: %s, Validation: %s, Shutdown: %s, Result: %s",
service_name, service_name,
node_id, node_id,
files,
startups, startups,
validations, validations,
shutdowns, shutdowns,
@ -933,6 +936,8 @@ class CoreClient:
config_proto = core_pb2.ServiceConfig( config_proto = core_pb2.ServiceConfig(
node_id=node_id, node_id=node_id,
service=name, service=name,
directories=config.dirs,
files=config.configs,
startup=config.startup, startup=config.startup,
validate=config.validate, validate=config.validate,
shutdown=config.shutdown, shutdown=config.shutdown,

View file

@ -1,8 +1,7 @@
""" import logging
Service configuration dialog import os
"""
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import filedialog, ttk
from typing import TYPE_CHECKING, Any, List from typing import TYPE_CHECKING, Any, List
import grpc import grpc
@ -48,12 +47,18 @@ class ServiceConfigDialog(Dialog):
self.validation_mode = None self.validation_mode = None
self.validation_time = None self.validation_time = None
self.validation_period = None self.validation_period = None
self.documentnew_img = Images.get(ImageEnum.DOCUMENTNEW, 16) self.directory_entry = None
self.editdelete_img = Images.get(ImageEnum.EDITDELETE, 16) self.default_directories = []
self.temp_directories = []
self.documentnew_img = Images.get(
ImageEnum.DOCUMENTNEW, int(16 * app.app_scale)
)
self.editdelete_img = Images.get(ImageEnum.EDITDELETE, int(16 * app.app_scale))
self.notebook = None self.notebook = None
self.metadata_entry = None self.metadata_entry = None
self.filename_combobox = None self.filename_combobox = None
self.dir_list = None
self.startup_commands_listbox = None self.startup_commands_listbox = None
self.shutdown_commands_listbox = None self.shutdown_commands_listbox = None
self.validate_commands_listbox = None self.validate_commands_listbox = None
@ -62,6 +67,7 @@ class ServiceConfigDialog(Dialog):
self.service_file_data = None self.service_file_data = None
self.validation_period_entry = None self.validation_period_entry = None
self.original_service_files = {} self.original_service_files = {}
self.default_config = None
self.temp_service_files = {} self.temp_service_files = {}
self.modified_files = set() self.modified_files = set()
@ -71,7 +77,7 @@ class ServiceConfigDialog(Dialog):
if not self.has_error: if not self.has_error:
self.draw() self.draw()
def load(self) -> bool: def load(self):
try: try:
self.app.core.create_nodes_and_links() self.app.core.create_nodes_and_links()
default_config = self.app.core.get_node_service( default_config = self.app.core.get_node_service(
@ -80,15 +86,14 @@ class ServiceConfigDialog(Dialog):
self.default_startup = default_config.startup[:] self.default_startup = default_config.startup[:]
self.default_validate = default_config.validate[:] self.default_validate = default_config.validate[:]
self.default_shutdown = default_config.shutdown[:] self.default_shutdown = default_config.shutdown[:]
custom_configs = self.service_configs self.default_directories = default_config.dirs[:]
if ( custom_service_config = self.service_configs.get(self.node_id, {}).get(
self.node_id in custom_configs self.service_name, None
and self.service_name in custom_configs[self.node_id] )
): self.default_config = default_config
service_config = custom_configs[self.node_id][self.service_name] service_config = (
else: custom_service_config if custom_service_config else default_config
service_config = default_config )
self.dependencies = service_config.dependencies[:] self.dependencies = service_config.dependencies[:]
self.executables = service_config.executables[:] self.executables = service_config.executables[:]
self.metadata = service_config.meta self.metadata = service_config.meta
@ -98,19 +103,18 @@ class ServiceConfigDialog(Dialog):
self.shutdown_commands = service_config.shutdown[:] self.shutdown_commands = service_config.shutdown[:]
self.validation_mode = service_config.validation_mode self.validation_mode = service_config.validation_mode
self.validation_time = service_config.validation_timer self.validation_time = service_config.validation_timer
self.temp_directories = service_config.dirs[:]
self.original_service_files = { self.original_service_files = {
x: self.app.core.get_node_service_file( x: self.app.core.get_node_service_file(
self.node_id, self.service_name, x self.node_id, self.service_name, x
) )
for x in self.filenames for x in default_config.configs
} }
self.temp_service_files = dict(self.original_service_files) self.temp_service_files = dict(self.original_service_files)
file_configs = self.file_configs file_config = self.file_configs.get(self.node_id, {}).get(
if ( self.service_name, {}
self.node_id in file_configs )
and self.service_name in file_configs[self.node_id] for file, data in file_config.items():
):
for file, data in file_configs[self.node_id][self.service_name].items():
self.temp_service_files[file] = data self.temp_service_files[file] = data
except grpc.RpcError as e: except grpc.RpcError as e:
self.has_error = True self.has_error = True
@ -155,18 +159,18 @@ class ServiceConfigDialog(Dialog):
frame.columnconfigure(1, weight=1) frame.columnconfigure(1, weight=1)
label = ttk.Label(frame, text="File Name") label = ttk.Label(frame, text="File Name")
label.grid(row=0, column=0, padx=PADX, sticky="w") label.grid(row=0, column=0, padx=PADX, sticky="w")
self.filename_combobox = ttk.Combobox( self.filename_combobox = ttk.Combobox(frame, values=self.filenames)
frame, values=self.filenames, state="readonly"
)
self.filename_combobox.bind( self.filename_combobox.bind(
"<<ComboboxSelected>>", self.display_service_file_data "<<ComboboxSelected>>", self.display_service_file_data
) )
self.filename_combobox.grid(row=0, column=1, sticky="ew", padx=PADX) self.filename_combobox.grid(row=0, column=1, sticky="ew", padx=PADX)
button = ttk.Button(frame, image=self.documentnew_img, state="disabled") button = ttk.Button(
button.bind("<Button-1>", self.add_filename) frame, image=self.documentnew_img, command=self.add_filename
)
button.grid(row=0, column=2, padx=PADX) button.grid(row=0, column=2, padx=PADX)
button = ttk.Button(frame, image=self.editdelete_img, state="disabled") button = ttk.Button(
button.bind("<Button-1>", self.delete_filename) frame, image=self.editdelete_img, command=self.delete_filename
)
button.grid(row=0, column=3) button.grid(row=0, column=3)
frame = ttk.Frame(tab) frame = ttk.Frame(tab)
@ -229,7 +233,30 @@ class ServiceConfigDialog(Dialog):
tab, tab,
text="Directories required by this service that are unique for each node.", text="Directories required by this service that are unique for each node.",
) )
label.grid() label.grid(row=0, column=0, sticky="ew")
frame = ttk.Frame(tab, padding=FRAME_PAD)
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1)
frame.grid(row=1, column=0, sticky="nsew")
var = tk.StringVar(value="")
self.directory_entry = ttk.Entry(frame, textvariable=var)
self.directory_entry.grid(row=0, column=0, sticky="ew")
button = ttk.Button(frame, text="...", command=self.find_directory_button)
button.grid(row=0, column=1, sticky="ew")
self.dir_list = ListboxScroll(tab)
self.dir_list.grid(row=2, column=0, sticky="nsew")
self.dir_list.listbox.bind("<<ListboxSelect>>", self.directory_select)
for d in self.temp_directories:
self.dir_list.listbox.insert("end", d)
frame = ttk.Frame(tab)
frame.grid(row=3, column=0, sticky="nsew")
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1)
button = ttk.Button(frame, text="Add", command=self.add_directory)
button.grid(row=0, column=0, sticky="ew")
button = ttk.Button(frame, text="Remove", command=self.remove_directory)
button.grid(row=0, column=1, sticky="ew")
def draw_tab_startstop(self): def draw_tab_startstop(self):
tab = ttk.Frame(self.notebook, padding=FRAME_PAD) tab = ttk.Frame(self.notebook, padding=FRAME_PAD)
@ -358,26 +385,30 @@ class ServiceConfigDialog(Dialog):
button = ttk.Button(frame, text="Cancel", command=self.destroy) button = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=3, sticky="ew") button.grid(row=0, column=3, sticky="ew")
def add_filename(self, event: tk.Event): def add_filename(self):
# not worry about it for now filename = self.filename_combobox.get()
return if filename not in self.filename_combobox["values"]:
frame_contains_button = event.widget.master self.filename_combobox["values"] += (filename,)
combobox = frame_contains_button.grid_slaves(row=0, column=1)[0] self.filename_combobox.set(filename)
filename = combobox.get() self.temp_service_files[filename] = self.service_file_data.text.get(
if filename not in combobox["values"]: 1.0, "end"
combobox["values"] += (filename,) )
else:
logging.debug("file already existed")
def delete_filename(self, event: tk.Event): def delete_filename(self):
# not worry about it for now cbb = self.filename_combobox
return filename = cbb.get()
frame_comntains_button = event.widget.master if filename in cbb["values"]:
combobox = frame_comntains_button.grid_slaves(row=0, column=1)[0] cbb["values"] = tuple([x for x in cbb["values"] if x != filename])
filename = combobox.get() cbb.set("")
if filename in combobox["values"]: self.service_file_data.text.delete(1.0, "end")
combobox["values"] = tuple([x for x in combobox["values"] if x != filename]) self.temp_service_files.pop(filename, None)
combobox.set("") if filename in self.modified_files:
self.modified_files.remove(filename)
def add_command(self, event: tk.Event): @classmethod
def add_command(cls, event: tk.Event):
frame_contains_button = event.widget.master frame_contains_button = event.widget.master
listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox 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() command_to_add = frame_contains_button.grid_slaves(row=0, column=0)[0].get()
@ -388,7 +419,8 @@ class ServiceConfigDialog(Dialog):
return return
listbox.insert(tk.END, command_to_add) listbox.insert(tk.END, command_to_add)
def update_entry(self, event: tk.Event): @classmethod
def update_entry(cls, event: tk.Event):
listbox = event.widget listbox = event.widget
current_selection = listbox.curselection() current_selection = listbox.curselection()
if len(current_selection) > 0: if len(current_selection) > 0:
@ -399,7 +431,8 @@ class ServiceConfigDialog(Dialog):
entry.delete(0, "end") entry.delete(0, "end")
entry.insert(0, cmd) entry.insert(0, cmd)
def delete_command(self, event: tk.Event): @classmethod
def delete_command(cls, event: tk.Event):
button = event.widget button = event.widget
frame_contains_button = button.master frame_contains_button = button.master
listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox
@ -410,30 +443,36 @@ class ServiceConfigDialog(Dialog):
entry.delete(0, tk.END) entry.delete(0, tk.END)
def click_apply(self): def click_apply(self):
current_listbox = self.master.current.listbox if (
if not self.is_custom_service_config() and not self.is_custom_service_file(): not self.is_custom_command()
if self.node_id in self.service_configs: and not self.is_custom_service_file()
self.service_configs[self.node_id].pop(self.service_name, None) and not self.has_new_files()
current_listbox.itemconfig(current_listbox.curselection()[0], bg="") and not self.is_custom_directory()
):
self.service_configs.get(self.node_id, {}).pop(self.service_name, None)
self.current_service_color("")
self.destroy() self.destroy()
return return
try: try:
if self.is_custom_service_config(): if (
startup_commands = self.startup_commands_listbox.get(0, "end") self.is_custom_command()
shutdown_commands = self.shutdown_commands_listbox.get(0, "end") or self.has_new_files()
validate_commands = self.validate_commands_listbox.get(0, "end") or self.is_custom_directory()
):
startup, validate, shutdown = self.get_commands()
config = self.core.set_node_service( config = self.core.set_node_service(
self.node_id, self.node_id,
self.service_name, self.service_name,
startups=startup_commands, dirs=self.temp_directories,
validations=validate_commands, files=list(self.filename_combobox["values"]),
shutdowns=shutdown_commands, startups=startup,
validations=validate,
shutdowns=shutdown,
) )
if self.node_id not in self.service_configs: if self.node_id not in self.service_configs:
self.service_configs[self.node_id] = {} self.service_configs[self.node_id] = {}
self.service_configs[self.node_id][self.service_name] = config self.service_configs[self.node_id][self.service_name] = config
for file in self.modified_files: for file in self.modified_files:
if self.node_id not in self.file_configs: if self.node_id not in self.file_configs:
self.file_configs[self.node_id] = {} self.file_configs[self.node_id] = {}
@ -442,53 +481,67 @@ class ServiceConfigDialog(Dialog):
self.file_configs[self.node_id][self.service_name][ self.file_configs[self.node_id][self.service_name][
file file
] = self.temp_service_files[file] ] = self.temp_service_files[file]
self.app.core.set_node_service_file( self.app.core.set_node_service_file(
self.node_id, self.service_name, file, self.temp_service_files[file] self.node_id, self.service_name, file, self.temp_service_files[file]
) )
all_current = current_listbox.get(0, tk.END) self.current_service_color("green")
current_listbox.itemconfig(all_current.index(self.service_name), bg="green")
except grpc.RpcError as e: except grpc.RpcError as e:
show_grpc_error(e, self.top, self.app) show_grpc_error(e, self.top, self.app)
self.destroy() self.destroy()
def display_service_file_data(self, event: tk.Event): def display_service_file_data(self, event: tk.Event):
combobox = event.widget filename = self.filename_combobox.get()
filename = combobox.get()
self.service_file_data.text.delete(1.0, "end") self.service_file_data.text.delete(1.0, "end")
self.service_file_data.text.insert("end", self.temp_service_files[filename]) self.service_file_data.text.insert("end", self.temp_service_files[filename])
def update_temp_service_file_data(self, event: tk.Event): def update_temp_service_file_data(self, event: tk.Event):
scrolledtext = event.widget
filename = self.filename_combobox.get() filename = self.filename_combobox.get()
self.temp_service_files[filename] = scrolledtext.get(1.0, "end") self.temp_service_files[filename] = self.service_file_data.text.get(1.0, "end")
if self.temp_service_files[filename] != self.original_service_files[filename]: if self.temp_service_files[filename] != self.original_service_files.get(
filename, ""
):
self.modified_files.add(filename) self.modified_files.add(filename)
else: else:
self.modified_files.discard(filename) self.modified_files.discard(filename)
def is_custom_service_config(self): def is_custom_command(self):
startup_commands = self.startup_commands_listbox.get(0, "end") startup, validate, shutdown = self.get_commands()
shutdown_commands = self.shutdown_commands_listbox.get(0, "end")
validate_commands = self.validate_commands_listbox.get(0, "end")
return ( return (
set(self.default_startup) != set(startup_commands) set(self.default_startup) != set(startup)
or set(self.default_validate) != set(validate_commands) or set(self.default_validate) != set(validate)
or set(self.default_shutdown) != set(shutdown_commands) or set(self.default_shutdown) != set(shutdown)
) )
def has_new_files(self):
return set(self.filenames) != set(self.filename_combobox["values"])
def is_custom_service_file(self): def is_custom_service_file(self):
return len(self.modified_files) > 0 return len(self.modified_files) > 0
def is_custom_directory(self):
return set(self.default_directories) != set(self.dir_list.listbox.get(0, "end"))
def click_defaults(self): def click_defaults(self):
if self.node_id in self.service_configs: """
self.service_configs[self.node_id].pop(self.service_name, None) clears out any custom configuration permanently
if self.node_id in self.file_configs: """
self.file_configs[self.node_id].pop(self.service_name, None) # clear coreclient data
self.service_configs.get(self.node_id, {}).pop(self.service_name, None)
self.file_configs.get(self.node_id, {}).pop(self.service_name, None)
self.temp_service_files = dict(self.original_service_files) self.temp_service_files = dict(self.original_service_files)
filename = self.filename_combobox.get() self.modified_files.clear()
# reset files tab
files = list(self.default_config.configs[:])
self.filenames = files
self.filename_combobox.config(values=files)
self.service_file_data.text.delete(1.0, "end") self.service_file_data.text.delete(1.0, "end")
if len(files) > 0:
filename = files[0]
self.filename_combobox.set(filename)
self.service_file_data.text.insert("end", self.temp_service_files[filename]) self.service_file_data.text.insert("end", self.temp_service_files[filename])
# reset commands
self.startup_commands_listbox.delete(0, tk.END) self.startup_commands_listbox.delete(0, tk.END)
self.validate_commands_listbox.delete(0, tk.END) self.validate_commands_listbox.delete(0, tk.END)
self.shutdown_commands_listbox.delete(0, tk.END) self.shutdown_commands_listbox.delete(0, tk.END)
@ -499,13 +552,68 @@ class ServiceConfigDialog(Dialog):
for cmd in self.default_shutdown: for cmd in self.default_shutdown:
self.shutdown_commands_listbox.insert(tk.END, cmd) self.shutdown_commands_listbox.insert(tk.END, cmd)
# reset directories
self.directory_entry.delete(0, "end")
self.dir_list.listbox.delete(0, "end")
self.temp_directories = list(self.default_directories)
for d in self.default_directories:
self.dir_list.listbox.insert("end", d)
self.current_service_color("")
def click_copy(self): def click_copy(self):
dialog = CopyServiceConfigDialog(self, self.app, self.node_id) dialog = CopyServiceConfigDialog(self, self.app, self.node_id)
dialog.show() dialog.show()
@classmethod
def append_commands( def append_commands(
self, commands: List[str], listbox: tk.Listbox, to_add: List[str] cls, commands: List[str], listbox: tk.Listbox, to_add: List[str]
): ):
for cmd in to_add: for cmd in to_add:
commands.append(cmd) commands.append(cmd)
listbox.insert(tk.END, cmd) listbox.insert(tk.END, cmd)
def get_commands(self):
startup = self.startup_commands_listbox.get(0, "end")
shutdown = self.shutdown_commands_listbox.get(0, "end")
validate = self.validate_commands_listbox.get(0, "end")
return startup, validate, shutdown
def find_directory_button(self):
d = filedialog.askdirectory(initialdir="/")
self.directory_entry.delete(0, "end")
self.directory_entry.insert("end", d)
def add_directory(self):
d = self.directory_entry.get()
if os.path.isdir(d):
if d not in self.temp_directories:
self.dir_list.listbox.insert("end", d)
self.temp_directories.append(d)
def remove_directory(self):
d = self.directory_entry.get()
dirs = self.dir_list.listbox.get(0, "end")
if d and d in self.temp_directories:
self.temp_directories.remove(d)
try:
i = dirs.index(d)
self.dir_list.listbox.delete(i)
except ValueError:
logging.debug("directory is not in the list")
self.directory_entry.delete(0, "end")
def directory_select(self, event):
i = self.dir_list.listbox.curselection()
if i:
d = self.dir_list.listbox.get(i)
self.directory_entry.delete(0, "end")
self.directory_entry.insert("end", d)
def current_service_color(self, color=""):
"""
change the current service label color
"""
listbox = self.master.current.listbox
services = listbox.get(0, tk.END)
listbox.itemconfig(services.index(self.service_name), bg=color)

View file

@ -656,8 +656,11 @@ class CanvasGraph(tk.Canvas):
delete selected nodes and any data that relates to it delete selected nodes and any data that relates to it
""" """
logging.debug("press delete key") logging.debug("press delete key")
if not self.app.core.is_runtime():
nodes = self.delete_selection_objects() nodes = self.delete_selection_objects()
self.core.delete_graph_nodes(nodes) self.core.delete_graph_nodes(nodes)
else:
logging.info("node deletion is disabled during runtime state")
def double_click(self, event: tk.Event): def double_click(self, event: tk.Event):
selected = self.get_selected(event) selected = self.get_selected(event)
@ -850,11 +853,17 @@ class CanvasGraph(tk.Canvas):
self.core.create_link(edge, source, dest) self.core.create_link(edge, source, dest)
def copy(self): def copy(self):
if self.app.core.is_runtime():
logging.info("copy is disabled during runtime state")
return
if self.selection: if self.selection:
logging.debug("to copy %s nodes", len(self.selection)) logging.debug("to copy %s nodes", len(self.selection))
self.to_copy = self.selection.keys() self.to_copy = self.selection.keys()
def paste(self): def paste(self):
if self.app.core.is_runtime():
logging.info("paste is disabled during runtime state")
return
# maps original node canvas id to copy node canvas id # maps original node canvas id to copy node canvas id
copy_map = {} copy_map = {}
# the edges that will be copy over # the edges that will be copy over

View file

@ -17,7 +17,7 @@ from core.gui.errors import show_grpc_error
from core.gui.graph import tags from core.gui.graph import tags
from core.gui.graph.tooltip import CanvasTooltip from core.gui.graph.tooltip import CanvasTooltip
from core.gui.images import ImageEnum, Images from core.gui.images import ImageEnum, Images
from core.gui.nodeutils import ANTENNA_SIZE, EdgeUtils, NodeUtils from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -66,48 +66,9 @@ class CanvasNode:
def delete(self): def delete(self):
logging.debug("Delete canvas node for %s", self.core_node) logging.debug("Delete canvas node for %s", self.core_node)
# if node is wlan, EMANE type, remove any existing wireless links between nodes connetect to this node
if NodeUtils.is_wireless_node(self.core_node.type):
nodes = []
for edge in self.edges:
token = edge.token
if self.id == token[0]:
nodes.append(token[1])
else:
nodes.append(token[0])
for i in range(len(nodes)):
for j in range(i + 1, len(nodes)):
token = EdgeUtils.get_token(nodes[i], nodes[j])
wireless_edge = self.canvas.wireless_edges.pop(token, None)
if wireless_edge:
self.canvas.nodes[nodes[i]].wireless_edges.remove(wireless_edge)
self.canvas.nodes[nodes[j]].wireless_edges.remove(wireless_edge)
self.canvas.delete(wireless_edge.id)
else:
logging.debug("%s is not a wireless edge", token)
# if node is MDR, remove wireless links to other MDRs
elif NodeUtils.is_mdr_node(self.core_node.type, self.core_node.model):
for wireless_edge in self.wireless_edges:
token = wireless_edge.token
other = token[0]
if other == self.id:
other = token[1]
self.canvas.nodes[other].wireless_edges.discard(wireless_edge)
try:
wlan_edge = self.canvas.wireless_edges.pop(token)
self.canvas.delete(wlan_edge.id)
except KeyError:
logging.error(
"wireless link not found, potentially multiple wireless link issue"
)
self.delete_antennas()
self.wireless_edges.clear()
self.canvas.delete(self.id) self.canvas.delete(self.id)
self.canvas.delete(self.text_id) self.canvas.delete(self.text_id)
self.delete_antennas()
def add_antenna(self): def add_antenna(self):
x, y = self.canvas.coords(self.id) x, y = self.canvas.coords(self.id)

View file

@ -25,6 +25,7 @@ class Menubar(tk.Menu):
self.app = app self.app = app
self.menuaction = action.MenuAction(app, master) self.menuaction = action.MenuAction(app, master)
self.recent_menu = None self.recent_menu = None
self.edit_menu = None
self.draw() self.draw()
def draw(self): def draw(self):
@ -110,6 +111,7 @@ class Menubar(tk.Menu):
self.app.master.bind_all("<Control-c>", self.menuaction.copy) self.app.master.bind_all("<Control-c>", self.menuaction.copy)
self.app.master.bind_all("<Control-v>", self.menuaction.paste) self.app.master.bind_all("<Control-v>", self.menuaction.paste)
self.edit_menu = menu
def draw_canvas_menu(self): def draw_canvas_menu(self):
""" """
@ -439,3 +441,15 @@ class Menubar(tk.Menu):
self.app.core.save_xml(xml_file) self.app.core.save_xml(xml_file)
else: else:
self.menuaction.file_save_as_xml() self.menuaction.file_save_as_xml()
def change_menubar_item_state(self, is_runtime: bool):
for i in range(self.edit_menu.index("end")):
try:
label_name = self.edit_menu.entrycget(i, "label")
if label_name in ["Copy", "Paste"]:
if is_runtime:
self.edit_menu.entryconfig(i, state="disabled")
else:
self.edit_menu.entryconfig(i, state="normal")
except tk.TclError:
logging.debug("Ignore separators")

View file

@ -90,10 +90,6 @@ class NodeUtils:
def is_rj45_node(cls, node_type: NodeType) -> bool: def is_rj45_node(cls, node_type: NodeType) -> bool:
return node_type in cls.RJ45_NODES return node_type in cls.RJ45_NODES
@classmethod
def is_mdr_node(cls, node_type: NodeType, model: str) -> bool:
return cls.is_container_node(node_type) and model == "mdr"
@classmethod @classmethod
def node_icon( def node_icon(
cls, cls,

View file

@ -280,6 +280,7 @@ class Toolbar(ttk.Frame):
server. server.
""" """
self.app.canvas.hide_context() self.app.canvas.hide_context()
self.app.menubar.change_menubar_item_state(is_runtime=True)
self.app.statusbar.progress_bar.start(5) self.app.statusbar.progress_bar.start(5)
self.app.canvas.mode = GraphMode.SELECT self.app.canvas.mode = GraphMode.SELECT
self.time = time.perf_counter() self.time = time.perf_counter()
@ -469,6 +470,7 @@ class Toolbar(ttk.Frame):
""" """
logging.info("Click stop button") logging.info("Click stop button")
self.app.canvas.hide_context() self.app.canvas.hide_context()
self.app.menubar.change_menubar_item_state(is_runtime=False)
self.app.statusbar.progress_bar.start(5) self.app.statusbar.progress_bar.start(5)
self.time = time.perf_counter() self.time = time.perf_counter()
task = BackgroundTask(self, self.app.core.stop_session, self.stop_callback) task = BackgroundTask(self, self.app.core.stop_session, self.stop_callback)