Merge pull request #334 from coreemu/coredev-serviceconfig

Coredev serviceconfig
This commit is contained in:
bharnden 2020-01-09 11:13:01 -08:00 committed by GitHub
commit 02172e94ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 243 additions and 24 deletions

View file

@ -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,

View 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)

View file

@ -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)

View file

@ -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,26 +398,29 @@ 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
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")
try: try:
config = self.core.set_node_service( if self.is_custom_service_config():
self.node_id, startup_commands = self.startup_commands_listbox.get(0, "end")
self.service_name, shutdown_commands = self.shutdown_commands_listbox.get(0, "end")
startup_commands, validate_commands = self.validate_commands_listbox.get(0, "end")
validate_commands, config = self.core.set_node_service(
shutdown_commands, self.node_id,
) self.service_name,
if self.node_id not in self.service_configs: startup_commands,
self.service_configs[self.node_id] = {} validate_commands,
self.service_configs[self.node_id][self.service_name] = config 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: 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)

View file

@ -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 == "":