Merge pull request #334 from coreemu/coredev-serviceconfig
Coredev serviceconfig
This commit is contained in:
commit
02172e94ab
5 changed files with 243 additions and 24 deletions
|
@ -702,10 +702,17 @@ class CoreClient:
|
||||||
emane = None
|
emane = None
|
||||||
if node_type == core_pb2.NodeType.EMANE:
|
if node_type == core_pb2.NodeType.EMANE:
|
||||||
emane = self.emane_models[0]
|
emane = self.emane_models[0]
|
||||||
|
name = f"EMANE{node_id}"
|
||||||
|
elif node_type == core_pb2.NodeType.WIRELESS_LAN:
|
||||||
|
name = f"WLAN{node_id}"
|
||||||
|
elif node_type in [core_pb2.NodeType.RJ45, core_pb2.NodeType.TUNNEL]:
|
||||||
|
name = "UNASSIGNED"
|
||||||
|
else:
|
||||||
|
name = f"n{node_id}"
|
||||||
node = core_pb2.Node(
|
node = core_pb2.Node(
|
||||||
id=node_id,
|
id=node_id,
|
||||||
type=node_type,
|
type=node_type,
|
||||||
name=f"n{node_id}",
|
name=name,
|
||||||
model=model,
|
model=model,
|
||||||
position=position,
|
position=position,
|
||||||
image=image,
|
image=image,
|
||||||
|
|
178
daemon/core/gui/dialogs/copyserviceconfig.py
Normal file
178
daemon/core/gui/dialogs/copyserviceconfig.py
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
"""
|
||||||
|
copy service config dialog
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
from core.gui.dialogs.dialog import Dialog
|
||||||
|
from core.gui.themes import FRAME_PAD, PADX
|
||||||
|
from core.gui.widgets import CodeText
|
||||||
|
|
||||||
|
|
||||||
|
class CopyServiceConfigDialog(Dialog):
|
||||||
|
def __init__(self, master, app, node_id):
|
||||||
|
super().__init__(master, app, f"Copy services to node {node_id}", modal=True)
|
||||||
|
self.parent = master
|
||||||
|
self.app = app
|
||||||
|
self.node_id = node_id
|
||||||
|
self.service_configs = app.core.service_configs
|
||||||
|
self.file_configs = app.core.file_configs
|
||||||
|
|
||||||
|
self.tree = None
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.tree = ttk.Treeview(self.top)
|
||||||
|
self.tree.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
self.tree["columns"] = ()
|
||||||
|
self.tree.column("#0", width=270, minwidth=270, stretch=tk.YES)
|
||||||
|
self.tree.heading("#0", text="Service configuration items", anchor=tk.CENTER)
|
||||||
|
custom_nodes = set(self.service_configs).union(set(self.file_configs))
|
||||||
|
for nid in custom_nodes:
|
||||||
|
treeid = self.tree.insert("", "end", text=f"n{nid}", tags="node")
|
||||||
|
services = self.service_configs.get(nid, None)
|
||||||
|
files = self.file_configs.get(nid, None)
|
||||||
|
tree_ids = {}
|
||||||
|
if services:
|
||||||
|
for service, config in services.items():
|
||||||
|
serviceid = self.tree.insert(
|
||||||
|
treeid, "end", text=service, tags="service"
|
||||||
|
)
|
||||||
|
tree_ids[service] = serviceid
|
||||||
|
cmdup = config.startup[:]
|
||||||
|
cmddown = config.shutdown[:]
|
||||||
|
cmdval = config.validate[:]
|
||||||
|
self.tree.insert(
|
||||||
|
serviceid,
|
||||||
|
"end",
|
||||||
|
text=f"cmdup=({str(cmdup)[1:-1]})",
|
||||||
|
tags=("cmd", "up"),
|
||||||
|
)
|
||||||
|
self.tree.insert(
|
||||||
|
serviceid,
|
||||||
|
"end",
|
||||||
|
text=f"cmddown=({str(cmddown)[1:-1]})",
|
||||||
|
tags=("cmd", "down"),
|
||||||
|
)
|
||||||
|
self.tree.insert(
|
||||||
|
serviceid,
|
||||||
|
"end",
|
||||||
|
text=f"cmdval=({str(cmdval)[1:-1]})",
|
||||||
|
tags=("cmd", "val"),
|
||||||
|
)
|
||||||
|
if files:
|
||||||
|
for service, configs in files.items():
|
||||||
|
if service in tree_ids:
|
||||||
|
serviceid = tree_ids[service]
|
||||||
|
else:
|
||||||
|
serviceid = self.tree.insert(
|
||||||
|
treeid, "end", text=service, tags="service"
|
||||||
|
)
|
||||||
|
tree_ids[service] = serviceid
|
||||||
|
for filename, data in configs.items():
|
||||||
|
self.tree.insert(serviceid, "end", text=filename, tags="file")
|
||||||
|
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(row=1, column=0)
|
||||||
|
for i in range(3):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
button = ttk.Button(frame, text="Copy", command=self.click_copy)
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
button = ttk.Button(frame, text="View", command=self.click_view)
|
||||||
|
button.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
|
button.grid(row=0, column=2, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
def click_copy(self):
|
||||||
|
logging.debug("click copy")
|
||||||
|
selected = self.tree.selection()
|
||||||
|
if selected:
|
||||||
|
item = self.tree.item(selected[0])
|
||||||
|
if "file" in item["tags"]:
|
||||||
|
filename = item["text"]
|
||||||
|
nid, service = self.get_node_service(selected)
|
||||||
|
data = self.file_configs[nid][service][filename]
|
||||||
|
if service == self.parent.service_name:
|
||||||
|
self.parent.temp_service_files[filename] = data
|
||||||
|
self.parent.modified_files.add(filename)
|
||||||
|
if self.parent.filename_combobox.get() == filename:
|
||||||
|
self.parent.service_file_data.text.delete(1.0, "end")
|
||||||
|
self.parent.service_file_data.text.insert("end", data)
|
||||||
|
if "cmd" in item["tags"]:
|
||||||
|
nid, service = self.get_node_service(selected)
|
||||||
|
if service == self.master.service_name:
|
||||||
|
cmds = self.service_configs[nid][service]
|
||||||
|
if "up" in item["tags"]:
|
||||||
|
self.master.append_commands(
|
||||||
|
self.master.startup_commands,
|
||||||
|
self.master.startup_commands_listbox,
|
||||||
|
cmds.startup,
|
||||||
|
)
|
||||||
|
elif "down" in item["tags"]:
|
||||||
|
self.master.append_commands(
|
||||||
|
self.master.shutdown_commands,
|
||||||
|
self.master.shutdown_commands_listbox,
|
||||||
|
cmds.shutdown,
|
||||||
|
)
|
||||||
|
|
||||||
|
elif "val" in item["tags"]:
|
||||||
|
self.master.append_commands(
|
||||||
|
self.master.validate_commands,
|
||||||
|
self.master.validate_commands_listbox,
|
||||||
|
cmds.validate,
|
||||||
|
)
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def click_view(self):
|
||||||
|
selected = self.tree.selection()
|
||||||
|
if selected:
|
||||||
|
item = self.tree.item(selected[0])
|
||||||
|
if "file" in item["tags"]:
|
||||||
|
nid, service = self.get_node_service(selected)
|
||||||
|
data = self.file_configs[nid][service][item["text"]]
|
||||||
|
dialog = ViewConfigDialog(self, self.app, self.node_id, data)
|
||||||
|
dialog.show()
|
||||||
|
if "cmd" in item["tags"]:
|
||||||
|
nid, service = self.get_node_service(selected)
|
||||||
|
cmds = self.service_configs[nid][service]
|
||||||
|
if "up" in item["tags"]:
|
||||||
|
data = f"({str(cmds.startup[:])[1:-1]})"
|
||||||
|
elif "down" in item["tags"]:
|
||||||
|
data = f"({str(cmds.shutdown[:])[1:-1]})"
|
||||||
|
elif "val" in item["tags"]:
|
||||||
|
data = f"({str(cmds.validate[:])[1:-1]})"
|
||||||
|
dialog = ViewConfigDialog(self, self.app, self.node_id, data)
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
def get_node_service(self, selected):
|
||||||
|
service_tree_id = self.tree.parent(selected[0])
|
||||||
|
service_name = self.tree.item(service_tree_id)["text"]
|
||||||
|
node_tree_id = self.tree.parent(service_tree_id)
|
||||||
|
node_id = int(self.tree.item(node_tree_id)["text"][1:])
|
||||||
|
return node_id, service_name
|
||||||
|
|
||||||
|
|
||||||
|
class ViewConfigDialog(Dialog):
|
||||||
|
def __init__(self, master, app, node_id, data):
|
||||||
|
super().__init__(master, app, f"n{node_id} config data", modal=True)
|
||||||
|
self.data = data
|
||||||
|
self.service_data = None
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
frame = ttk.Frame(self.top, padding=FRAME_PAD)
|
||||||
|
frame.grid(row=0, column=0)
|
||||||
|
label = ttk.Label(frame, text="File: ")
|
||||||
|
label.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
self.service_data = CodeText(self.top)
|
||||||
|
self.service_data.grid(row=1, column=0, sticky="nsew")
|
||||||
|
self.service_data.text.insert("end", self.data)
|
||||||
|
self.service_data.text.config(state="disabled")
|
||||||
|
|
||||||
|
button = ttk.Button(self.top, text="Close", command=self.destroy)
|
||||||
|
button.grid(row=2, column=0, sticky="ew", padx=PADX)
|
|
@ -10,7 +10,7 @@ from core.gui.dialogs.emaneconfig import EmaneModelDialog
|
||||||
from core.gui.images import Images
|
from core.gui.images import Images
|
||||||
from core.gui.nodeutils import NodeUtils
|
from core.gui.nodeutils import NodeUtils
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
from core.gui.widgets import image_chooser
|
from core.gui.widgets import ListboxScroll, image_chooser
|
||||||
|
|
||||||
|
|
||||||
def mac_auto(is_auto, entry):
|
def mac_auto(is_auto, entry):
|
||||||
|
@ -131,6 +131,18 @@ class NodeConfigDialog(Dialog):
|
||||||
combobox.grid(row=row, column=1, sticky="ew")
|
combobox.grid(row=row, column=1, sticky="ew")
|
||||||
row += 1
|
row += 1
|
||||||
|
|
||||||
|
if NodeUtils.is_rj45_node(self.node.type):
|
||||||
|
response = self.app.core.client.get_interfaces()
|
||||||
|
logging.debug("host machine available interfaces: %s", response)
|
||||||
|
interfaces = ListboxScroll(frame)
|
||||||
|
interfaces.grid(
|
||||||
|
row=row, column=0, columnspan=2, sticky="ew", padx=PADX, pady=PADY
|
||||||
|
)
|
||||||
|
for inf in sorted(response.interfaces[:]):
|
||||||
|
interfaces.listbox.insert(tk.END, inf)
|
||||||
|
row += 1
|
||||||
|
interfaces.listbox.bind("<<ListboxSelect>>", self.interface_select)
|
||||||
|
|
||||||
# interfaces
|
# interfaces
|
||||||
if self.canvas_node.interfaces:
|
if self.canvas_node.interfaces:
|
||||||
self.draw_interfaces()
|
self.draw_interfaces()
|
||||||
|
@ -235,3 +247,10 @@ class NodeConfigDialog(Dialog):
|
||||||
# redraw
|
# redraw
|
||||||
self.canvas_node.redraw()
|
self.canvas_node.redraw()
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
|
def interface_select(self, event):
|
||||||
|
listbox = event.widget
|
||||||
|
cur = listbox.curselection()
|
||||||
|
if cur:
|
||||||
|
interface = listbox.get(cur[0])
|
||||||
|
self.name.set(interface)
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
"Service configuration dialog"
|
"Service configuration dialog"
|
||||||
import logging
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
from core.api.grpc import core_pb2
|
from core.api.grpc import core_pb2
|
||||||
|
from core.gui.dialogs.copyserviceconfig import CopyServiceConfigDialog
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.errors import show_grpc_error
|
from core.gui.errors import show_grpc_error
|
||||||
from core.gui.images import ImageEnum, Images
|
from core.gui.images import ImageEnum, Images
|
||||||
|
@ -340,9 +340,7 @@ class ServiceConfigDialog(Dialog):
|
||||||
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
button = ttk.Button(frame, text="Defaults", command=self.click_defaults)
|
button = ttk.Button(frame, text="Defaults", command=self.click_defaults)
|
||||||
button.grid(row=0, column=1, sticky="ew", padx=PADX)
|
button.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
button = ttk.Button(
|
button = ttk.Button(frame, text="Copy...", command=self.click_copy)
|
||||||
frame, text="Copy...", command=self.click_copy, state="disabled"
|
|
||||||
)
|
|
||||||
button.grid(row=0, column=2, sticky="ew", padx=PADX)
|
button.grid(row=0, column=2, sticky="ew", padx=PADX)
|
||||||
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")
|
||||||
|
@ -400,16 +398,18 @@ class ServiceConfigDialog(Dialog):
|
||||||
|
|
||||||
def click_apply(self):
|
def click_apply(self):
|
||||||
current_listbox = self.master.current.listbox
|
current_listbox = self.master.current.listbox
|
||||||
if not self.is_custom_service():
|
if not self.is_custom_service_config() and not self.is_custom_service_file():
|
||||||
if self.node_id in self.service_configs:
|
if self.node_id in self.service_configs:
|
||||||
self.service_configs[self.node_id].pop(self.service_name, None)
|
self.service_configs[self.node_id].pop(self.service_name, None)
|
||||||
current_listbox.itemconfig(current_listbox.curselection()[0], bg="")
|
current_listbox.itemconfig(current_listbox.curselection()[0], bg="")
|
||||||
self.destroy()
|
self.destroy()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.is_custom_service_config():
|
||||||
startup_commands = self.startup_commands_listbox.get(0, "end")
|
startup_commands = self.startup_commands_listbox.get(0, "end")
|
||||||
shutdown_commands = self.shutdown_commands_listbox.get(0, "end")
|
shutdown_commands = self.shutdown_commands_listbox.get(0, "end")
|
||||||
validate_commands = self.validate_commands_listbox.get(0, "end")
|
validate_commands = self.validate_commands_listbox.get(0, "end")
|
||||||
try:
|
|
||||||
config = self.core.set_node_service(
|
config = self.core.set_node_service(
|
||||||
self.node_id,
|
self.node_id,
|
||||||
self.service_name,
|
self.service_name,
|
||||||
|
@ -420,6 +420,7 @@ class ServiceConfigDialog(Dialog):
|
||||||
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] = {}
|
||||||
|
@ -432,7 +433,8 @@ class ServiceConfigDialog(Dialog):
|
||||||
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]
|
||||||
)
|
)
|
||||||
current_listbox.itemconfig(current_listbox.curselection()[0], bg="green")
|
all_current = current_listbox.get(0, tk.END)
|
||||||
|
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)
|
show_grpc_error(e)
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
@ -452,7 +454,7 @@ class ServiceConfigDialog(Dialog):
|
||||||
else:
|
else:
|
||||||
self.modified_files.discard(filename)
|
self.modified_files.discard(filename)
|
||||||
|
|
||||||
def is_custom_service(self):
|
def is_custom_service_config(self):
|
||||||
startup_commands = self.startup_commands_listbox.get(0, "end")
|
startup_commands = self.startup_commands_listbox.get(0, "end")
|
||||||
shutdown_commands = self.shutdown_commands_listbox.get(0, "end")
|
shutdown_commands = self.shutdown_commands_listbox.get(0, "end")
|
||||||
validate_commands = self.validate_commands_listbox.get(0, "end")
|
validate_commands = self.validate_commands_listbox.get(0, "end")
|
||||||
|
@ -460,9 +462,11 @@ class ServiceConfigDialog(Dialog):
|
||||||
set(self.default_startup) != set(startup_commands)
|
set(self.default_startup) != set(startup_commands)
|
||||||
or set(self.default_validate) != set(validate_commands)
|
or set(self.default_validate) != set(validate_commands)
|
||||||
or set(self.default_shutdown) != set(shutdown_commands)
|
or set(self.default_shutdown) != set(shutdown_commands)
|
||||||
or len(self.modified_files) > 0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def is_custom_service_file(self):
|
||||||
|
return len(self.modified_files) > 0
|
||||||
|
|
||||||
def click_defaults(self):
|
def click_defaults(self):
|
||||||
if self.node_id in self.service_configs:
|
if self.node_id in self.service_configs:
|
||||||
self.service_configs[self.node_id].pop(self.service_name, None)
|
self.service_configs[self.node_id].pop(self.service_name, None)
|
||||||
|
@ -483,4 +487,10 @@ class ServiceConfigDialog(Dialog):
|
||||||
self.shutdown_commands_listbox.insert(tk.END, cmd)
|
self.shutdown_commands_listbox.insert(tk.END, cmd)
|
||||||
|
|
||||||
def click_copy(self):
|
def click_copy(self):
|
||||||
logging.info("not implemented")
|
dialog = CopyServiceConfigDialog(self, self.app, self.node_id)
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
def append_commands(self, commands, listbox, to_add):
|
||||||
|
for cmd in to_add:
|
||||||
|
commands.append(cmd)
|
||||||
|
listbox.insert(tk.END, cmd)
|
||||||
|
|
|
@ -47,6 +47,7 @@ class NodeUtils:
|
||||||
CONTAINER_NODES = {NodeType.DEFAULT, NodeType.DOCKER, NodeType.LXC}
|
CONTAINER_NODES = {NodeType.DEFAULT, NodeType.DOCKER, NodeType.LXC}
|
||||||
IMAGE_NODES = {NodeType.DOCKER, NodeType.LXC}
|
IMAGE_NODES = {NodeType.DOCKER, NodeType.LXC}
|
||||||
WIRELESS_NODES = {NodeType.WIRELESS_LAN, NodeType.EMANE}
|
WIRELESS_NODES = {NodeType.WIRELESS_LAN, NodeType.EMANE}
|
||||||
|
RJ45_NODES = {NodeType.RJ45}
|
||||||
IGNORE_NODES = {NodeType.CONTROL_NET, NodeType.PEER_TO_PEER}
|
IGNORE_NODES = {NodeType.CONTROL_NET, NodeType.PEER_TO_PEER}
|
||||||
NODE_MODELS = {"router", "host", "PC", "mdr", "prouter"}
|
NODE_MODELS = {"router", "host", "PC", "mdr", "prouter"}
|
||||||
ANTENNA_ICON = None
|
ANTENNA_ICON = None
|
||||||
|
@ -71,6 +72,10 @@ class NodeUtils:
|
||||||
def is_wireless_node(cls, node_type):
|
def is_wireless_node(cls, node_type):
|
||||||
return node_type in cls.WIRELESS_NODES
|
return node_type in cls.WIRELESS_NODES
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_rj45_node(cls, node_type):
|
||||||
|
return node_type in cls.RJ45_NODES
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def node_icon(cls, node_type, model):
|
def node_icon(cls, node_type, model):
|
||||||
if model == "":
|
if model == "":
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue