fix merge conflict

This commit is contained in:
Huy Pham 2020-03-04 11:14:20 -08:00
commit 6ce29bea75
21 changed files with 371 additions and 178 deletions

View file

@ -475,9 +475,10 @@ class CoreGrpcClient:
self,
session_id: int,
node_id: int,
position: core_pb2.Position,
position: core_pb2.Position = None,
icon: str = None,
source: str = None,
geo: core_pb2.Geo = None,
) -> core_pb2.EditNodeResponse:
"""
Edit a node, currently only changes position.
@ -487,6 +488,7 @@ class CoreGrpcClient:
:param position: position to set node to
:param icon: path to icon for gui to use for node
:param source: application source editing node
:param geo: lon,lat,alt location for node
:return: response with result of success or failure
:raises grpc.RpcError: when session or node doesn't exist
"""
@ -496,6 +498,7 @@ class CoreGrpcClient:
position=position,
icon=icon,
source=source,
geo=geo,
)
return self.stub.EditNode(request)

View file

@ -44,7 +44,9 @@ def add_node_data(node_proto: core_pb2.Node) -> Tuple[NodeTypes, int, NodeOption
position = node_proto.position
options.set_position(position.x, position.y)
options.set_location(position.lat, position.lon, position.alt)
if node_proto.HasField("geo"):
geo = node_proto.geo
options.set_location(geo.lat, geo.lon, geo.alt)
return _type, _id, options
@ -379,7 +381,7 @@ def service_configuration(session: Session, config: core_pb2.ServiceConfig) -> N
if config.files:
service.configs = tuple(config.files)
if config.directories:
service.directories = tuple(config.directories)
service.dirs = tuple(config.directories)
if config.startup:
service.startup = tuple(config.startup)
if config.validate:

View file

@ -688,21 +688,26 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
node = self.get_node(session, request.node_id, context)
options = NodeOptions()
options.icon = request.icon
x = request.position.x
y = request.position.y
options.set_position(x, y)
lat = request.position.lat
lon = request.position.lon
alt = request.position.alt
options.set_location(lat, lon, alt)
if request.HasField("position"):
x = request.position.x
y = request.position.y
options.set_position(x, y)
lat, lon, alt = None, None, None
has_geo = request.HasField("geo")
if has_geo:
lat = request.geo.lat
lon = request.geo.lon
alt = request.geo.alt
options.set_location(lat, lon, alt)
result = True
try:
session.edit_node(node.id, options)
source = None
if request.source:
source = request.source
node_data = node.data(0, source=source)
session.broadcast_node(node_data)
if not has_geo:
node_data = node.data(0, source=source)
session.broadcast_node(node_data)
except CoreError:
result = False
return core_pb2.EditNodeResponse(result=result)

View file

@ -782,7 +782,8 @@ class Session:
node.canvas = options.canvas
node.icon = options.icon
self.sdt.edit_node(node)
# provide edits to sdt
self.sdt.edit_node(node, options.lon, options.lat, options.alt)
def set_node_position(self, node: NodeBase, options: NodeOptions) -> None:
"""
@ -812,9 +813,11 @@ class Session:
# broadcast updated location when using lat/lon/alt
if using_lat_lon_alt:
self.broadcast_node_location(node)
self.broadcast_node_location(node, lon, lat, alt)
def broadcast_node_location(self, node: NodeBase) -> None:
def broadcast_node_location(
self, node: NodeBase, lon: float, lat: float, alt: float
) -> None:
"""
Broadcast node location to all listeners.
@ -826,6 +829,9 @@ class Session:
id=node.id,
x_position=node.position.x,
y_position=node.position.y,
latitude=lat,
longitude=lon,
altitude=alt,
)
self.broadcast_node(node_data)

View file

@ -1,3 +1,4 @@
import math
import tkinter as tk
from tkinter import font, ttk
@ -47,11 +48,10 @@ class Application(tk.Frame):
def setup_scaling(self):
self.fonts_size = {name: font.nametofont(name)["size"] for name in font.names()}
text_scale = self.app_scale if self.app_scale < 1 else math.sqrt(self.app_scale)
themes.scale_fonts(self.fonts_size, self.app_scale)
self.icon_text_font = font.Font(
family="TkIconFont", size=int(12 * self.app_scale)
)
self.edge_font = font.Font(family="TkDefaultFont", size=int(8 * self.app_scale))
self.icon_text_font = font.Font(family="TkIconFont", size=int(12 * text_scale))
self.edge_font = font.Font(family="TkDefaultFont", size=int(8 * text_scale))
def setup_theme(self):
themes.load(self.style)

View file

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

View file

@ -8,6 +8,7 @@ from typing import TYPE_CHECKING
from core.gui.dialogs.colorpicker import ColorPickerDialog
from core.gui.dialogs.dialog import Dialog
from core.gui.graph import tags
if TYPE_CHECKING:
from core.gui.app import Application
@ -19,7 +20,7 @@ class MarkerDialog(Dialog):
def __init__(
self, master: "Application", app: "Application", initcolor: str = "#000000"
):
super().__init__(master, app, "marker tool", modal=False)
super().__init__(master, app, "Marker Tool", modal=False)
self.app = app
self.color = initcolor
self.radius = MARKER_THICKNESS[0]
@ -56,8 +57,7 @@ class MarkerDialog(Dialog):
def clear_marker(self):
canvas = self.app.canvas
for i in canvas.find_withtag("marker"):
canvas.delete(i)
canvas.delete(tags.MARKER)
def change_color(self, event: tk.Event):
color_picker = ColorPickerDialog(self, self.app, self.color)

View file

@ -1,4 +1,5 @@
import logging
import math
import tkinter as tk
from tkinter import ttk
from typing import TYPE_CHECKING
@ -6,10 +7,13 @@ from typing import TYPE_CHECKING
from core.gui import appconfig
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import FRAME_PAD, PADX, PADY, scale_fonts
from core.gui.validation import LARGEST_SCALE, SMALLEST_SCALE
if TYPE_CHECKING:
from core.gui.app import Application
SCALE_INTERVAL = 0.01
class PreferencesDialog(Dialog):
def __init__(self, master: "Application", app: "Application"):
@ -73,18 +77,25 @@ class PreferencesDialog(Dialog):
scale_frame.columnconfigure(0, weight=1)
scale = ttk.Scale(
scale_frame,
from_=0.5,
to=5,
from_=SMALLEST_SCALE,
to=LARGEST_SCALE,
value=1,
orient=tk.HORIZONTAL,
variable=self.gui_scale,
)
scale.grid(row=0, column=0, sticky="ew")
entry = ttk.Entry(
scale_frame, textvariable=self.gui_scale, width=4, state="disabled"
scale_frame,
textvariable=self.gui_scale,
width=4,
validate="key",
validatecommand=(self.app.validation.app_scale, "%P"),
)
entry.grid(row=0, column=1)
scrollbar = ttk.Scrollbar(scale_frame, command=self.adjust_scale)
scrollbar.grid(row=0, column=2)
def draw_buttons(self):
frame = ttk.Frame(self.top)
frame.grid(sticky="ew")
@ -123,8 +134,9 @@ class PreferencesDialog(Dialog):
# scale fonts
scale_fonts(self.app.fonts_size, app_scale)
self.app.icon_text_font.config(size=int(12 * app_scale))
self.app.edge_font.config(size=int(8 * app_scale))
text_scale = app_scale if app_scale < 1 else math.sqrt(app_scale)
self.app.icon_text_font.config(size=int(12 * text_scale))
self.app.edge_font.config(size=int(8 * text_scale))
# scale application window
self.app.center()
@ -132,3 +144,16 @@ class PreferencesDialog(Dialog):
# scale toolbar and canvas items
self.app.toolbar.scale()
self.app.canvas.scale_graph()
def adjust_scale(self, arg1: str, arg2: str, arg3: str):
scale_value = self.gui_scale.get()
if arg2 == "-1":
if scale_value <= LARGEST_SCALE - SCALE_INTERVAL:
self.gui_scale.set(round(scale_value + SCALE_INTERVAL, 2))
else:
self.gui_scale.set(round(LARGEST_SCALE, 2))
elif arg2 == "1":
if scale_value >= SMALLEST_SCALE + SCALE_INTERVAL:
self.gui_scale.set(round(scale_value - SCALE_INTERVAL, 2))
else:
self.gui_scale.set(round(SMALLEST_SCALE, 2))

View file

@ -1,8 +1,7 @@
"""
Service configuration dialog
"""
import logging
import os
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog, ttk
from typing import TYPE_CHECKING, Any, List
import grpc
@ -48,12 +47,18 @@ class ServiceConfigDialog(Dialog):
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.directory_entry = None
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.metadata_entry = None
self.filename_combobox = None
self.dir_list = None
self.startup_commands_listbox = None
self.shutdown_commands_listbox = None
self.validate_commands_listbox = None
@ -62,6 +67,7 @@ class ServiceConfigDialog(Dialog):
self.service_file_data = None
self.validation_period_entry = None
self.original_service_files = {}
self.default_config = None
self.temp_service_files = {}
self.modified_files = set()
@ -71,7 +77,7 @@ class ServiceConfigDialog(Dialog):
if not self.has_error:
self.draw()
def load(self) -> bool:
def load(self):
try:
self.app.core.create_nodes_and_links()
default_config = self.app.core.get_node_service(
@ -80,15 +86,14 @@ class ServiceConfigDialog(Dialog):
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.default_directories = default_config.dirs[:]
custom_service_config = self.service_configs.get(self.node_id, {}).get(
self.service_name, None
)
self.default_config = default_config
service_config = (
custom_service_config if custom_service_config else default_config
)
self.dependencies = service_config.dependencies[:]
self.executables = service_config.executables[:]
self.metadata = service_config.meta
@ -98,20 +103,19 @@ class ServiceConfigDialog(Dialog):
self.shutdown_commands = service_config.shutdown[:]
self.validation_mode = service_config.validation_mode
self.validation_time = service_config.validation_timer
self.temp_directories = service_config.dirs[:]
self.original_service_files = {
x: self.app.core.get_node_service_file(
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)
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
file_config = self.file_configs.get(self.node_id, {}).get(
self.service_name, {}
)
for file, data in file_config.items():
self.temp_service_files[file] = data
except grpc.RpcError as e:
self.has_error = True
show_grpc_error(e, self.master, self.app)
@ -155,18 +159,18 @@ class ServiceConfigDialog(Dialog):
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 = ttk.Combobox(frame, values=self.filenames)
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 = ttk.Button(
frame, image=self.documentnew_img, command=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 = ttk.Button(
frame, image=self.editdelete_img, command=self.delete_filename
)
button.grid(row=0, column=3)
frame = ttk.Frame(tab)
@ -229,7 +233,30 @@ class ServiceConfigDialog(Dialog):
tab,
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):
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.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 add_filename(self):
filename = self.filename_combobox.get()
if filename not in self.filename_combobox["values"]:
self.filename_combobox["values"] += (filename,)
self.filename_combobox.set(filename)
self.temp_service_files[filename] = self.service_file_data.text.get(
1.0, "end"
)
else:
logging.debug("file already existed")
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 delete_filename(self):
cbb = self.filename_combobox
filename = cbb.get()
if filename in cbb["values"]:
cbb["values"] = tuple([x for x in cbb["values"] if x != filename])
cbb.set("")
self.service_file_data.text.delete(1.0, "end")
self.temp_service_files.pop(filename, None)
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
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()
@ -388,7 +419,8 @@ class ServiceConfigDialog(Dialog):
return
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
current_selection = listbox.curselection()
if len(current_selection) > 0:
@ -399,7 +431,8 @@ class ServiceConfigDialog(Dialog):
entry.delete(0, "end")
entry.insert(0, cmd)
def delete_command(self, event: tk.Event):
@classmethod
def delete_command(cls, event: tk.Event):
button = event.widget
frame_contains_button = button.master
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)
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="")
if (
not self.is_custom_command()
and not self.is_custom_service_file()
and not self.has_new_files()
and not self.is_custom_directory()
):
self.service_configs.get(self.node_id, {}).pop(self.service_name, None)
self.current_service_color("")
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")
if (
self.is_custom_command()
or self.has_new_files()
or self.is_custom_directory()
):
startup, validate, shutdown = self.get_commands()
config = self.core.set_node_service(
self.node_id,
self.service_name,
startups=startup_commands,
validations=validate_commands,
shutdowns=shutdown_commands,
dirs=self.temp_directories,
files=list(self.filename_combobox["values"]),
startups=startup,
validations=validate,
shutdowns=shutdown,
)
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] = {}
@ -442,53 +481,67 @@ class ServiceConfigDialog(Dialog):
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")
self.current_service_color("green")
except grpc.RpcError as e:
show_grpc_error(e, self.top, self.app)
self.destroy()
def display_service_file_data(self, event: tk.Event):
combobox = event.widget
filename = combobox.get()
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])
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.temp_service_files[filename] = self.service_file_data.text.get(1.0, "end")
if self.temp_service_files[filename] != self.original_service_files.get(
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")
def is_custom_command(self):
startup, validate, shutdown = self.get_commands()
return (
set(self.default_startup) != set(startup_commands)
or set(self.default_validate) != set(validate_commands)
or set(self.default_shutdown) != set(shutdown_commands)
set(self.default_startup) != set(startup)
or set(self.default_validate) != set(validate)
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):
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):
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)
"""
clears out any custom configuration permanently
"""
# 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)
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.insert("end", self.temp_service_files[filename])
if len(files) > 0:
filename = files[0]
self.filename_combobox.set(filename)
self.service_file_data.text.insert("end", self.temp_service_files[filename])
# reset commands
self.startup_commands_listbox.delete(0, tk.END)
self.validate_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:
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):
dialog = CopyServiceConfigDialog(self, self.app, self.node_id)
dialog.show()
@classmethod
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:
commands.append(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

@ -534,7 +534,7 @@ class CanvasGraph(tk.Canvas):
y + r,
fill=self.app.toolbar.marker_tool.color,
outline="",
tags="marker",
tags=tags.MARKER,
)
return
if selected is None:
@ -656,8 +656,11 @@ class CanvasGraph(tk.Canvas):
delete selected nodes and any data that relates to it
"""
logging.debug("press delete key")
nodes = self.delete_selection_objects()
self.core.delete_graph_nodes(nodes)
if not self.app.core.is_runtime():
nodes = self.delete_selection_objects()
self.core.delete_graph_nodes(nodes)
else:
logging.info("node deletion is disabled during runtime state")
def double_click(self, event: tk.Event):
selected = self.get_selected(event)
@ -850,11 +853,17 @@ class CanvasGraph(tk.Canvas):
self.core.create_link(edge, source, dest)
def copy(self):
if self.app.core.is_runtime():
logging.info("copy is disabled during runtime state")
return
if self.selection:
logging.debug("to copy %s nodes", len(self.selection))
self.to_copy = self.selection.keys()
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
copy_map = {}
# 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.tooltip import CanvasTooltip
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:
from core.gui.app import Application
@ -66,48 +66,9 @@ class CanvasNode:
def delete(self):
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.text_id)
self.delete_antennas()
def add_antenna(self):
x, y = self.canvas.coords(self.id)

View file

@ -10,6 +10,7 @@ NODE = "node"
WALLPAPER = "wallpaper"
SELECTION = "selectednodes"
THROUGHPUT = "throughput"
MARKER = "marker"
ABOVE_WALLPAPER_TAGS = [
GRIDLINE,
SHAPE,
@ -33,4 +34,5 @@ COMPONENT_TAGS = [
SELECTION,
SHAPE,
SHAPE_TEXT,
MARKER,
]

View file

@ -106,6 +106,8 @@ class TypeToImage:
(core_pb2.NodeType.EMANE, ""): ImageEnum.EMANE,
(core_pb2.NodeType.RJ45, ""): ImageEnum.RJ45,
(core_pb2.NodeType.TUNNEL, ""): ImageEnum.TUNNEL,
(core_pb2.NodeType.DOCKER, ""): ImageEnum.DOCKER,
(core_pb2.NodeType.LXC, ""): ImageEnum.LXC,
}
@classmethod

View file

@ -26,6 +26,7 @@ class Menubar(tk.Menu):
self.app = app
self.menuaction = action.MenuAction(app, master)
self.recent_menu = None
self.edit_menu = None
self.draw()
def draw(self):
@ -111,6 +112,7 @@ class Menubar(tk.Menu):
self.app.master.bind_all("<Control-c>", self.menuaction.copy)
self.app.master.bind_all("<Control-v>", self.menuaction.paste)
self.edit_menu = menu
def draw_canvas_menu(self):
"""
@ -444,3 +446,15 @@ class Menubar(tk.Menu):
def execute_python(self):
dialog = ExecutePythonDialog(self.app, self.app)
dialog.show()
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:
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
def node_icon(
cls,

View file

@ -182,21 +182,21 @@ def theme_change(event: tk.Event):
background="green",
padding=0,
relief=tk.NONE,
font="TkSmallCaptionFont",
font="TkDefaultFont",
)
style.configure(
Styles.yellow_alert,
background="yellow",
padding=0,
relief=tk.NONE,
font="TkSmallCaptionFont",
font="TkDefaultFont",
)
style.configure(
Styles.red_alert,
background="red",
padding=0,
relief=tk.NONE,
font="TkSmallCaptionFont",
font="TkDefaultFont",
)

View file

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

View file

@ -11,12 +11,16 @@ from netaddr import IPNetwork
if TYPE_CHECKING:
from core.gui.app import Application
SMALLEST_SCALE = 0.5
LARGEST_SCALE = 5.0
class InputValidation:
def __init__(self, app: "Application"):
self.master = app.master
self.positive_int = None
self.positive_float = None
self.app_scale = None
self.name = None
self.ip4 = None
self.rgb = None
@ -26,6 +30,7 @@ class InputValidation:
def register(self):
self.positive_int = self.master.register(self.check_positive_int)
self.positive_float = self.master.register(self.check_positive_float)
self.app_scale = self.master.register(self.check_scale_value)
self.name = self.master.register(self.check_node_name)
self.ip4 = self.master.register(self.check_ip4)
self.rgb = self.master.register(self.check_rbg)
@ -105,6 +110,18 @@ class InputValidation:
except ValueError:
return False
@classmethod
def check_scale_value(cls, s: str) -> bool:
if not s:
return True
try:
float_value = float(s)
if SMALLEST_SCALE <= float_value <= LARGEST_SCALE or float_value == 0:
return True
return False
except ValueError:
return False
@classmethod
def check_ip4(cls, s: str) -> bool:
if not s:

View file

@ -255,20 +255,28 @@ class Sdt:
self.cmd(f"sprite {node_type} image {icon}")
self.cmd(f'node {node.id} type {node_type} label on,"{node.name}" {pos}')
def edit_node(self, node: NodeBase) -> None:
def edit_node(self, node: NodeBase, lon: float, lat: float, alt: float) -> None:
"""
Handle updating a node in SDT.
:param node: node to update
:param lon: node longitude
:param lat: node latitude
:param alt: node altitude
:return: nothing
"""
logging.debug("sdt update node: %s - %s", node.id, node.name)
if not self.connect():
return
pos = self.get_node_position(node)
if not pos:
return
self.cmd(f"node {node.id} {pos}")
if all([lat is not None, lon is not None, alt is not None]):
pos = f"pos {lon:.6f},{lat:.6f},{alt:.6f}"
self.cmd(f"node {node.id} {pos}")
else:
pos = self.get_node_position(node)
if not pos:
return
self.cmd(f"node {node.id} {pos}")
def delete_node(self, node_id: int) -> None:
"""

View file

@ -10,6 +10,8 @@ from core.emulator.data import LinkData
from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions
from core.emulator.enumerations import NodeTypes
from core.nodes.base import CoreNetworkBase, CoreNodeBase, NodeBase
from core.nodes.docker import DockerNode
from core.nodes.lxd import LxcNode
from core.nodes.network import CtrlNet
from core.services.coreservices import CoreService
@ -213,8 +215,21 @@ class DeviceElement(NodeElement):
def __init__(self, session: "Session", node: NodeBase) -> None:
super().__init__(session, node, "device")
add_attribute(self.element, "type", node.type)
self.add_class()
self.add_services()
def add_class(self) -> None:
clazz = ""
image = ""
if isinstance(self.node, DockerNode):
clazz = "docker"
image = self.node.image
elif isinstance(self.node, LxcNode):
clazz = "lxc"
image = self.node.image
add_attribute(self.element, "class", clazz)
add_attribute(self.element, "image", image)
def add_services(self) -> None:
service_elements = etree.Element("services")
for service in self.node.services:
@ -796,9 +811,17 @@ class CoreXmlReader:
name = device_element.get("name")
model = device_element.get("type")
icon = device_element.get("icon")
options = NodeOptions(name, model)
clazz = device_element.get("class")
image = device_element.get("image")
options = NodeOptions(name, model, image)
options.icon = icon
node_type = NodeTypes.DEFAULT
if clazz == "docker":
node_type = NodeTypes.DOCKER
elif clazz == "lxc":
node_type = NodeTypes.LXC
service_elements = device_element.find("services")
if service_elements is not None:
options.services = [x.get("name") for x in service_elements.iterchildren()]
@ -823,7 +846,7 @@ class CoreXmlReader:
options.set_location(lat, lon, alt)
logging.info("reading node id(%s) model(%s) name(%s)", node_id, model, name)
self.session.add_node(_id=node_id, options=options)
self.session.add_node(_type=node_type, _id=node_id, options=options)
def read_network(self, network_element: etree.Element) -> None:
node_id = get_int(network_element, "id")

View file

@ -408,6 +408,7 @@ message EditNodeRequest {
Position position = 3;
string icon = 4;
string source = 5;
Geo geo = 6;
}
message EditNodeResponse {
@ -977,6 +978,7 @@ message Node {
string image = 10;
string server = 11;
repeated string config_services = 12;
Geo geo = 13;
}
message Link {
@ -1029,7 +1031,10 @@ message Position {
float x = 1;
float y = 2;
float z = 3;
float lat = 4;
float lon = 5;
float alt = 6;
}
message Geo {
float lat = 1;
float lon = 2;
float alt = 3;
}