move images files, create ip and mac for links, progress on node command and node terminal, progress on save and open xml
|
@ -6,7 +6,8 @@ from coretk.coregrpc import CoreGrpc
|
|||
from coretk.coremenubar import CoreMenubar
|
||||
from coretk.coretoolbar import CoreToolbar
|
||||
from coretk.graph import CanvasGraph
|
||||
from coretk.images import Images
|
||||
from coretk.images import ImageEnum, Images
|
||||
from coretk.menuaction import MenuAction
|
||||
|
||||
|
||||
class Application(tk.Frame):
|
||||
|
@ -15,13 +16,15 @@ class Application(tk.Frame):
|
|||
self.load_images()
|
||||
self.setup_app()
|
||||
self.menubar = None
|
||||
self.core_menu = None
|
||||
self.canvas = None
|
||||
|
||||
# start grpc
|
||||
self.core_grpc = CoreGrpc()
|
||||
self.core_editbar = None
|
||||
self.core_grpc = None
|
||||
|
||||
self.create_menu()
|
||||
self.create_widgets()
|
||||
self.draw_canvas()
|
||||
self.start_grpc()
|
||||
|
||||
def load_images(self):
|
||||
"""
|
||||
|
@ -33,23 +36,24 @@ class Application(tk.Frame):
|
|||
def setup_app(self):
|
||||
self.master.title("CORE")
|
||||
self.master.geometry("1000x800")
|
||||
image = Images.get("core")
|
||||
image = Images.get(ImageEnum.CORE.value)
|
||||
self.master.tk.call("wm", "iconphoto", self.master._w, image)
|
||||
self.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
def create_menu(self):
|
||||
self.master.option_add("*tearOff", tk.FALSE)
|
||||
self.menubar = tk.Menu(self.master)
|
||||
core_menu = CoreMenubar(self, self.master, self.menubar)
|
||||
core_menu.create_core_menubar()
|
||||
self.core_menu = CoreMenubar(self, self.master, self.menubar)
|
||||
self.core_menu.create_core_menubar()
|
||||
self.master.config(menu=self.menubar)
|
||||
|
||||
def create_widgets(self):
|
||||
edit_frame = tk.Frame(self)
|
||||
edit_frame.pack(side=tk.LEFT, fill=tk.Y, ipadx=2, ipady=2)
|
||||
core_editbar = CoreToolbar(self.master, edit_frame, self.menubar)
|
||||
core_editbar.create_toolbar()
|
||||
self.core_editbar = CoreToolbar(self.master, edit_frame, self.menubar)
|
||||
self.core_editbar.create_toolbar()
|
||||
|
||||
def draw_canvas(self):
|
||||
self.canvas = CanvasGraph(
|
||||
master=self,
|
||||
grpc=self.core_grpc,
|
||||
|
@ -58,7 +62,7 @@ class Application(tk.Frame):
|
|||
)
|
||||
self.canvas.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
core_editbar.update_canvas(self.canvas)
|
||||
self.core_editbar.update_canvas(self.canvas)
|
||||
|
||||
scroll_x = tk.Scrollbar(
|
||||
self.canvas, orient=tk.HORIZONTAL, command=self.canvas.xview
|
||||
|
@ -78,8 +82,26 @@ class Application(tk.Frame):
|
|||
b = tk.Button(status_bar, text="Button 3")
|
||||
b.pack(side=tk.LEFT, padx=1)
|
||||
|
||||
def start_grpc(self):
|
||||
"""
|
||||
Conect client to grpc, query sessions and prompt use to choose an existing session if there exist any
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
self.master.update()
|
||||
self.core_grpc = CoreGrpc(self.master)
|
||||
self.core_grpc.set_up()
|
||||
self.canvas.core_grpc = self.core_grpc
|
||||
self.canvas.draw_existing_component()
|
||||
|
||||
def on_closing(self):
|
||||
menu_action = MenuAction(self, self.master)
|
||||
menu_action.on_quit()
|
||||
# self.quit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
app = Application()
|
||||
app.master.protocol("WM_DELETE_WINDOW", app.on_closing)
|
||||
app.mainloop()
|
||||
|
|
|
@ -2,26 +2,28 @@
|
|||
Incorporate grpc into python tkinter GUI
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import tkinter as tk
|
||||
|
||||
from core.api.grpc import client, core_pb2
|
||||
|
||||
|
||||
class CoreGrpc:
|
||||
def __init__(self):
|
||||
def __init__(self, master):
|
||||
"""
|
||||
Create a CoreGrpc instance
|
||||
"""
|
||||
print("Create grpc instance")
|
||||
self.core = client.CoreGrpcClient()
|
||||
self.session_id = None
|
||||
self.set_up()
|
||||
self.master = master
|
||||
|
||||
# self.set_up()
|
||||
self.interface_helper = None
|
||||
|
||||
def log_event(self, event):
|
||||
logging.info("event: %s", event)
|
||||
|
||||
def redraw_canvas(self):
|
||||
return
|
||||
|
||||
def create_new_session(self):
|
||||
"""
|
||||
Create a new session
|
||||
|
@ -35,6 +37,29 @@ class CoreGrpc:
|
|||
self.session_id = response.session_id
|
||||
self.core.events(self.session_id, self.log_event)
|
||||
|
||||
def _enter_session(self, session_id, dialog):
|
||||
"""
|
||||
enter an existing session
|
||||
|
||||
:return:
|
||||
"""
|
||||
dialog.destroy()
|
||||
response = self.core.get_session(session_id)
|
||||
self.session_id = session_id
|
||||
print("set session id: %s", session_id)
|
||||
logging.info("Entering session_id %s.... Result: %s", session_id, response)
|
||||
# self.master.canvas.draw_existing_component()
|
||||
|
||||
def _create_session(self, dialog):
|
||||
"""
|
||||
create a new session
|
||||
|
||||
:param tkinter.Toplevel dialog: save core session prompt dialog
|
||||
:return: nothing
|
||||
"""
|
||||
dialog.destroy()
|
||||
self.create_new_session()
|
||||
|
||||
def query_existing_sessions(self, sessions):
|
||||
"""
|
||||
Query for existing sessions and prompt to join one
|
||||
|
@ -43,17 +68,30 @@ class CoreGrpc:
|
|||
|
||||
:return: nothing
|
||||
"""
|
||||
dialog = tk.Toplevel()
|
||||
dialog.title("CORE sessions")
|
||||
for session in sessions:
|
||||
logging.info("Session id: %s, Session state: %s", session.id, session.state)
|
||||
logging.info("Input a session you want to enter from the keyboard:")
|
||||
usr_input = int(input())
|
||||
if usr_input == 0:
|
||||
self.create_new_session()
|
||||
else:
|
||||
response = self.core.get_session(usr_input)
|
||||
self.session_id = usr_input
|
||||
# self.core.events(self.session_id, self.log_event)
|
||||
logging.info("Entering session_id %s.... Result: %s", usr_input, response)
|
||||
b = tk.Button(
|
||||
dialog,
|
||||
text="Session " + str(session.id),
|
||||
command=lambda sid=session.id: self._enter_session(sid, dialog),
|
||||
)
|
||||
b.pack(side=tk.TOP)
|
||||
b = tk.Button(
|
||||
dialog, text="create new", command=lambda: self._create_session(dialog)
|
||||
)
|
||||
b.pack(side=tk.TOP)
|
||||
dialog.update()
|
||||
x = (
|
||||
self.master.winfo_x()
|
||||
+ (self.master.winfo_width() - dialog.winfo_width()) / 2
|
||||
)
|
||||
y = (
|
||||
self.master.winfo_y()
|
||||
+ (self.master.winfo_height() / 2 - dialog.winfo_height()) / 2
|
||||
)
|
||||
dialog.geometry(f"+{int(x)}+{int(y)}")
|
||||
dialog.wait_window()
|
||||
|
||||
def delete_session(self):
|
||||
response = self.core.delete_session(self.session_id)
|
||||
|
@ -61,9 +99,9 @@ class CoreGrpc:
|
|||
|
||||
def set_up(self):
|
||||
"""
|
||||
Query sessions, if there exist any, promt whether to join one
|
||||
Query sessions, if there exist any, prompt whether to join one
|
||||
|
||||
:return: nothing
|
||||
:return: existing sessions
|
||||
"""
|
||||
self.core.connect()
|
||||
|
||||
|
@ -76,12 +114,12 @@ class CoreGrpc:
|
|||
if len(sessions) == 0:
|
||||
self.create_new_session()
|
||||
else:
|
||||
# self.create_new_session()
|
||||
|
||||
self.query_existing_sessions(sessions)
|
||||
|
||||
def get_session_state(self):
|
||||
response = self.core.get_session(self.session_id)
|
||||
logging.info("get sessio: %s", response)
|
||||
logging.info("get session: %s", response)
|
||||
return response.session.state
|
||||
|
||||
def set_session_state(self, state):
|
||||
|
@ -125,9 +163,6 @@ class CoreGrpc:
|
|||
|
||||
logging.info("set session state: %s", response)
|
||||
|
||||
def get_session_id(self):
|
||||
return self.session_id
|
||||
|
||||
def add_node(self, node_type, model, x, y, name, node_id):
|
||||
logging.info("coregrpc.py ADD NODE %s", name)
|
||||
position = core_pb2.Position(x=x, y=y)
|
||||
|
@ -136,9 +171,9 @@ class CoreGrpc:
|
|||
logging.info("created node: %s", response)
|
||||
return response.node_id
|
||||
|
||||
def edit_node(self, session_id, node_id, x, y):
|
||||
def edit_node(self, node_id, x, y):
|
||||
position = core_pb2.Position(x=x, y=y)
|
||||
response = self.core.edit_node(session_id, node_id, position)
|
||||
response = self.core.edit_node(self.session_id, node_id, position)
|
||||
logging.info("updated node id %s: %s", node_id, response)
|
||||
|
||||
def delete_nodes(self):
|
||||
|
@ -146,9 +181,6 @@ class CoreGrpc:
|
|||
response = self.core.delete_node(self.session_id, node.id)
|
||||
logging.info("delete node %s", response)
|
||||
|
||||
# def create_interface_helper(self):
|
||||
# self.interface_helper = self.core.InterfaceHelper(ip4_prefix="10.83.0.0/16")
|
||||
|
||||
def delete_links(self):
|
||||
for link in self.core.get_session(self.session_id).session.links:
|
||||
response = self.core.delete_link(
|
||||
|
@ -160,7 +192,7 @@ class CoreGrpc:
|
|||
)
|
||||
logging.info("delete link %s", response)
|
||||
|
||||
def add_link(self, id1, id2, type1, type2):
|
||||
def add_link(self, id1, id2, type1, type2, edge):
|
||||
"""
|
||||
Grpc client request add link
|
||||
|
||||
|
@ -171,19 +203,64 @@ class CoreGrpc:
|
|||
:param core_pb2.NodeType type2: node 2 core node type
|
||||
:return: nothing
|
||||
"""
|
||||
if not self.interface_helper:
|
||||
logging.debug("INTERFACE HELPER NOT CREATED YET, CREATING ONE...")
|
||||
self.interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/16")
|
||||
|
||||
interface1 = None
|
||||
interface2 = None
|
||||
if1 = None
|
||||
if2 = None
|
||||
if type1 == core_pb2.NodeType.DEFAULT:
|
||||
interface1 = self.interface_helper.create_interface(id1, 0)
|
||||
interface = edge.interface_1
|
||||
if1 = core_pb2.Interface(
|
||||
id=interface.id,
|
||||
name=interface.name,
|
||||
mac=interface.mac,
|
||||
ip4=interface.ipv4,
|
||||
ip4mask=interface.ip4prefix,
|
||||
)
|
||||
# if1 = core_pb2.Interface(id=id1, name=edge.interface_1.name, ip4=edge.interface_1.ipv4, ip4mask=edge.interface_1.ip4prefix)
|
||||
logging.debug("create interface 1 %s", if1)
|
||||
# interface1 = self.interface_helper.create_interface(id1, 0)
|
||||
|
||||
if type2 == core_pb2.NodeType.DEFAULT:
|
||||
interface2 = self.interface_helper.create_interface(id2, 0)
|
||||
response = self.core.add_link(self.session_id, id1, id2, interface1, interface2)
|
||||
interface = edge.interface_2
|
||||
if2 = core_pb2.Interface(
|
||||
id=interface.id,
|
||||
name=interface.name,
|
||||
mac=interface.mac,
|
||||
ip4=interface.ipv4,
|
||||
ip4mask=interface.ip4prefix,
|
||||
)
|
||||
# if2 = core_pb2.Interface(id=id2, name=edge.interface_2.name, ip4=edge.interface_2.ipv4, ip4mask=edge.interface_2.ip4prefix)
|
||||
logging.debug("create interface 2: %s", if2)
|
||||
# interface2 = self.interface_helper.create_interface(id2, 0)
|
||||
|
||||
# response = self.core.add_link(self.session_id, id1, id2, interface1, interface2)
|
||||
response = self.core.add_link(self.session_id, id1, id2, if1, if2)
|
||||
logging.info("created link: %s", response)
|
||||
|
||||
def launch_terminal(self, node_id):
|
||||
response = self.core.get_node_terminal(self.session_id, node_id)
|
||||
logging.info("get terminal %s", response.terminal)
|
||||
os.system("xterm -e %s &" % response.terminal)
|
||||
|
||||
def save_xml(self, file_path):
|
||||
"""
|
||||
Save core session as to an xml file
|
||||
|
||||
:param str file_path: file path that user pick
|
||||
:return: nothing
|
||||
"""
|
||||
response = self.core.save_xml(self.session_id, file_path)
|
||||
logging.info("coregrpc.py save xml %s", response)
|
||||
|
||||
def open_xml(self, file_path):
|
||||
"""
|
||||
Open core xml
|
||||
|
||||
:param str file_path: file to open
|
||||
:return: session id
|
||||
"""
|
||||
response = self.core.open_xml(file_path)
|
||||
return response.session_id
|
||||
# logging.info("coregrpc.py open_xml()", type(response))
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Clean ups when done using grpc
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import tkinter as tk
|
||||
|
||||
import coretk.menuaction as action
|
||||
from coretk.menuaction import MenuAction
|
||||
|
||||
|
||||
class CoreMenubar(object):
|
||||
|
@ -19,6 +20,33 @@ class CoreMenubar(object):
|
|||
self.menubar = menubar
|
||||
self.master = master
|
||||
self.application = application
|
||||
self.menuaction = action.MenuAction(application, master)
|
||||
self.menu_action = MenuAction(self.application, self.master)
|
||||
|
||||
# def on_quit(self):
|
||||
# """
|
||||
# Prompt use to stop running session before application is closed
|
||||
#
|
||||
# :return: nothing
|
||||
# """
|
||||
# state = self.application.core_grpc.get_session_state()
|
||||
#
|
||||
# if state == core_pb2.SessionState.SHUTDOWN or state == core_pb2.SessionState.DEFINITION:
|
||||
# self.application.core_grpc.delete_session()
|
||||
# self.application.core_grpc.core.close()
|
||||
# # self.application.quit()
|
||||
# else:
|
||||
# msgbox = tk.messagebox.askyesnocancel("stop", "Stop the running session?")
|
||||
#
|
||||
# if msgbox or msgbox == False:
|
||||
# if msgbox:
|
||||
# self.application.core_grpc.set_session_state("datacollect")
|
||||
# self.application.core_grpc.delete_links()
|
||||
# self.application.core_grpc.delete_nodes()
|
||||
# self.application.core_grpc.delete_session()
|
||||
#
|
||||
# self.application.core_grpc.core.close()
|
||||
# # self.application.quit()
|
||||
|
||||
def create_file_menu(self):
|
||||
"""
|
||||
|
@ -27,18 +55,24 @@ class CoreMenubar(object):
|
|||
:return: nothing
|
||||
"""
|
||||
file_menu = tk.Menu(self.menubar)
|
||||
# menu_action = MenuAction(self.application, self.master)
|
||||
file_menu.add_command(
|
||||
label="New", command=action.file_new, accelerator="Ctrl+N", underline=0
|
||||
)
|
||||
file_menu.add_command(
|
||||
label="Open...", command=action.file_open, accelerator="Ctrl+O", underline=0
|
||||
label="Open...",
|
||||
command=self.menu_action.file_open_xml,
|
||||
accelerator="Ctrl+O",
|
||||
underline=0,
|
||||
)
|
||||
file_menu.add_command(label="Reload", command=action.file_reload, underline=0)
|
||||
file_menu.add_command(
|
||||
label="Save", command=action.file_save, accelerator="Ctrl+S", underline=0
|
||||
)
|
||||
file_menu.add_command(label="Save As XML...", command=action.file_save_as_xml)
|
||||
file_menu.add_command(label="Save As imn...", command=action.file_save_as_imn)
|
||||
# file_menu.add_command(label="Save As XML...", command=action.file_save_as_xml)
|
||||
file_menu.add_command(
|
||||
label="Save As XML...", command=self.menu_action.file_save_as_xml
|
||||
)
|
||||
|
||||
file_menu.add_separator()
|
||||
|
||||
|
@ -68,13 +102,8 @@ class CoreMenubar(object):
|
|||
file_menu.add_separator()
|
||||
|
||||
file_menu.add_command(
|
||||
label="/home/ncs/.core/configs/sample1.imn",
|
||||
command=action.file_example_link,
|
||||
label="Quit", command=self.menuaction.on_quit, underline=0
|
||||
)
|
||||
|
||||
file_menu.add_separator()
|
||||
|
||||
file_menu.add_command(label="Quit", command=self.master.quit, underline=0)
|
||||
self.menubar.add_cascade(label="File", menu=file_menu, underline=0)
|
||||
|
||||
def create_edit_menu(self):
|
||||
|
@ -560,9 +589,7 @@ class CoreMenubar(object):
|
|||
:return: nothing
|
||||
"""
|
||||
session_menu = tk.Menu(self.menubar)
|
||||
session_menu.add_command(
|
||||
label="Start", command=action.session_start, underline=0
|
||||
)
|
||||
|
||||
session_menu.add_command(
|
||||
label="Change sessions...",
|
||||
command=action.session_change_sessions,
|
||||
|
@ -604,10 +631,11 @@ class CoreMenubar(object):
|
|||
"""
|
||||
help_menu = tk.Menu(self.menubar)
|
||||
help_menu.add_command(
|
||||
label="Core Github (www)", command=action.help_core_github
|
||||
label="Core Github (www)", command=self.menu_action.help_core_github
|
||||
)
|
||||
help_menu.add_command(
|
||||
label="Core Documentation (www)", command=action.help_core_documentation
|
||||
label="Core Documentation (www)",
|
||||
command=self.menu_action.help_core_documentation,
|
||||
)
|
||||
help_menu.add_command(label="About", command=action.help_about)
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import tkinter as tk
|
|||
|
||||
from core.api.grpc import core_pb2
|
||||
from coretk.graph import GraphMode
|
||||
from coretk.images import Images
|
||||
from coretk.images import ImageEnum, Images
|
||||
from coretk.tooltip import CreateToolTip
|
||||
|
||||
|
||||
|
@ -153,7 +153,6 @@ class CoreToolbar(object):
|
|||
logging.debug("Click START STOP SESSION button")
|
||||
self.destroy_children_widgets(self.edit_frame)
|
||||
self.canvas.mode = GraphMode.SELECT
|
||||
self.create_runtime_toolbar()
|
||||
|
||||
# set configuration state
|
||||
if self.canvas.core_grpc.get_session_state() == core_pb2.SessionState.SHUTDOWN:
|
||||
|
@ -166,10 +165,14 @@ class CoreToolbar(object):
|
|||
)
|
||||
|
||||
for edge in self.canvas.grpc_manager.edges.values():
|
||||
self.canvas.core_grpc.add_link(edge.id1, edge.id2, edge.type1, edge.type2)
|
||||
self.canvas.core_grpc.add_link(
|
||||
edge.id1, edge.id2, edge.type1, edge.type2, edge
|
||||
)
|
||||
|
||||
self.canvas.core_grpc.set_session_state("instantiation")
|
||||
|
||||
self.create_runtime_toolbar()
|
||||
|
||||
def click_link_tool(self):
|
||||
logging.debug("Click LINK button")
|
||||
self.canvas.mode = GraphMode.EDGE
|
||||
|
@ -177,55 +180,55 @@ class CoreToolbar(object):
|
|||
def pick_router(self, main_button):
|
||||
logging.debug("Pick router option")
|
||||
self.network_layer_option_menu.destroy()
|
||||
main_button.configure(image=Images.get("router"))
|
||||
main_button.configure(image=Images.get(ImageEnum.ROUTER.value))
|
||||
self.canvas.mode = GraphMode.PICKNODE
|
||||
self.canvas.draw_node_image = Images.get("router")
|
||||
self.canvas.draw_node_image = Images.get(ImageEnum.ROUTER.value)
|
||||
self.canvas.draw_node_name = "router"
|
||||
|
||||
def pick_host(self, main_button):
|
||||
logging.debug("Pick host option")
|
||||
self.network_layer_option_menu.destroy()
|
||||
main_button.configure(image=Images.get("host"))
|
||||
main_button.configure(image=Images.get(ImageEnum.HOST.value))
|
||||
self.canvas.mode = GraphMode.PICKNODE
|
||||
self.canvas.draw_node_image = Images.get("host")
|
||||
self.canvas.draw_node_image = Images.get(ImageEnum.HOST.value)
|
||||
self.canvas.draw_node_name = "host"
|
||||
|
||||
def pick_pc(self, main_button):
|
||||
logging.debug("Pick PC option")
|
||||
self.network_layer_option_menu.destroy()
|
||||
main_button.configure(image=Images.get("pc"))
|
||||
main_button.configure(image=Images.get(ImageEnum.PC.value))
|
||||
self.canvas.mode = GraphMode.PICKNODE
|
||||
self.canvas.draw_node_image = Images.get("pc")
|
||||
self.canvas.draw_node_image = Images.get(ImageEnum.PC.value)
|
||||
self.canvas.draw_node_name = "PC"
|
||||
|
||||
def pick_mdr(self, main_button):
|
||||
logging.debug("Pick MDR option")
|
||||
self.network_layer_option_menu.destroy()
|
||||
main_button.configure(image=Images.get("mdr"))
|
||||
main_button.configure(image=Images.get(ImageEnum.MDR.value))
|
||||
self.canvas.mode = GraphMode.PICKNODE
|
||||
self.canvas.draw_node_image = Images.get("mdr")
|
||||
self.canvas.draw_node_image = Images.get(ImageEnum.MD.value)
|
||||
self.canvas.draw_node_name = "mdr"
|
||||
|
||||
def pick_prouter(self, main_button):
|
||||
logging.debug("Pick prouter option")
|
||||
self.network_layer_option_menu.destroy()
|
||||
main_button.configure(image=Images.get("prouter"))
|
||||
main_button.configure(image=Images.get(ImageEnum.PROUTER.value))
|
||||
self.canvas.mode = GraphMode.PICKNODE
|
||||
self.canvas.draw_node_image = Images.get("prouter")
|
||||
self.canvas.draw_node_image = Images.get(ImageEnum.PROUTER.value)
|
||||
self.canvas.draw_node_name = "prouter"
|
||||
|
||||
def pick_ovs(self, main_button):
|
||||
logging.debug("Pick OVS option")
|
||||
self.network_layer_option_menu.destroy()
|
||||
main_button.configure(image=Images.get("ovs"))
|
||||
main_button.configure(image=Images.get(ImageEnum.OVS.value))
|
||||
self.canvas.mode = GraphMode.PICKNODE
|
||||
self.canvas.draw_node_image = Images.get("ovs")
|
||||
self.canvas.draw_node_image = Images.get(ImageEnum.OVS.value)
|
||||
self.canvas.draw_node_name = "OVS"
|
||||
|
||||
# TODO what graph node is this
|
||||
def pick_editnode(self, main_button):
|
||||
self.network_layer_option_menu.destroy()
|
||||
main_button.configure(image=Images.get("editnode"))
|
||||
main_button.configure(image=Images.get(ImageEnum.EDITNODE.value))
|
||||
logging.debug("Pick editnode option")
|
||||
|
||||
def draw_network_layer_options(self, network_layer_button):
|
||||
|
@ -239,13 +242,13 @@ class CoreToolbar(object):
|
|||
self.destroy_previous_frame()
|
||||
option_frame = tk.Frame(self.master, padx=1, pady=1)
|
||||
img_list = [
|
||||
Images.get("router"),
|
||||
Images.get("host"),
|
||||
Images.get("pc"),
|
||||
Images.get("mdr"),
|
||||
Images.get("prouter"),
|
||||
Images.get("ovs"),
|
||||
Images.get("editnode"),
|
||||
Images.get(ImageEnum.ROUTER.value),
|
||||
Images.get(ImageEnum.HOST.value),
|
||||
Images.get(ImageEnum.PC.value),
|
||||
Images.get(ImageEnum.MDR.value),
|
||||
Images.get(ImageEnum.PROUTER.value),
|
||||
Images.get(ImageEnum.OVS.value),
|
||||
Images.get(ImageEnum.EDITNODE.value),
|
||||
]
|
||||
func_list = [
|
||||
self.pick_router,
|
||||
|
@ -289,7 +292,7 @@ class CoreToolbar(object):
|
|||
|
||||
:return: nothing
|
||||
"""
|
||||
router_image = Images.get("router")
|
||||
router_image = Images.get(ImageEnum.ROUTER.value)
|
||||
network_layer_button = tk.Radiobutton(
|
||||
self.edit_frame,
|
||||
indicatoron=False,
|
||||
|
@ -306,41 +309,41 @@ class CoreToolbar(object):
|
|||
def pick_hub(self, main_button):
|
||||
logging.debug("Pick link-layer node HUB")
|
||||
self.link_layer_option_menu.destroy()
|
||||
main_button.configure(image=Images.get("hub"))
|
||||
main_button.configure(image=Images.get(ImageEnum.HUB.value))
|
||||
self.canvas.mode = GraphMode.PICKNODE
|
||||
self.canvas.draw_node_image = Images.get("hub")
|
||||
self.canvas.draw_node_image = Images.get(ImageEnum.HUB.value)
|
||||
self.canvas.draw_node_name = "hub"
|
||||
|
||||
def pick_switch(self, main_button):
|
||||
logging.debug("Pick link-layer node SWITCH")
|
||||
self.link_layer_option_menu.destroy()
|
||||
main_button.configure(image=Images.get("switch"))
|
||||
main_button.configure(image=Images.get(ImageEnum.SWITCH.value))
|
||||
self.canvas.mode = GraphMode.PICKNODE
|
||||
self.canvas.draw_node_image = Images.get("switch")
|
||||
self.canvas.draw_node_image = Images.get(ImageEnum.SWITCH.value)
|
||||
self.canvas.draw_node_name = "switch"
|
||||
|
||||
def pick_wlan(self, main_button):
|
||||
logging.debug("Pick link-layer node WLAN")
|
||||
self.link_layer_option_menu.destroy()
|
||||
main_button.configure(image=Images.get("wlan"))
|
||||
main_button.configure(image=Images.get(ImageEnum.WLAN.value))
|
||||
self.canvas.mode = GraphMode.PICKNODE
|
||||
self.canvas.draw_node_image = Images.get("wlan")
|
||||
self.canvas.draw_node_image = Images.get(ImageEnum.WLAN.value)
|
||||
self.canvas.draw_node_name = "wlan"
|
||||
|
||||
def pick_rj45(self, main_button):
|
||||
logging.debug("Pick link-layer node RJ45")
|
||||
self.link_layer_option_menu.destroy()
|
||||
main_button.configure(image=Images.get("rj45"))
|
||||
main_button.configure(image=Images.get(ImageEnum.RJ45.value))
|
||||
self.canvas.mode = GraphMode.PICKNODE
|
||||
self.canvas.draw_node_image = Images.get("rj45")
|
||||
self.canvas.draw_node_image = Images.get(ImageEnum.RJ45.value)
|
||||
self.canvas.draw_node_name = "rj45"
|
||||
|
||||
def pick_tunnel(self, main_button):
|
||||
logging.debug("Pick link-layer node TUNNEL")
|
||||
self.link_layer_option_menu.destroy()
|
||||
main_button.configure(image=Images.get("tunnel"))
|
||||
main_button.configure(image=Images.get(ImageEnum.TUNNEL.value))
|
||||
self.canvas.mode = GraphMode.PICKNODE
|
||||
self.canvas.draw_node_image = Images.get("tunnel")
|
||||
self.canvas.draw_node_image = Images.get(ImageEnum.TUNNEL.value)
|
||||
self.canvas.draw_node_name = "tunnel"
|
||||
|
||||
def draw_link_layer_options(self, link_layer_button):
|
||||
|
@ -354,11 +357,11 @@ class CoreToolbar(object):
|
|||
self.destroy_previous_frame()
|
||||
option_frame = tk.Frame(self.master, padx=1, pady=1)
|
||||
img_list = [
|
||||
Images.get("hub"),
|
||||
Images.get("switch"),
|
||||
Images.get("wlan"),
|
||||
Images.get("rj45"),
|
||||
Images.get("tunnel"),
|
||||
Images.get(ImageEnum.HUB.value),
|
||||
Images.get(ImageEnum.SWITCH.value),
|
||||
Images.get(ImageEnum.WLAN.value),
|
||||
Images.get(ImageEnum.RJ45.value),
|
||||
Images.get(ImageEnum.TUNNEL.value),
|
||||
]
|
||||
func_list = [
|
||||
self.pick_hub,
|
||||
|
@ -398,7 +401,7 @@ class CoreToolbar(object):
|
|||
|
||||
:return: nothing
|
||||
"""
|
||||
hub_image = Images.get("hub")
|
||||
hub_image = Images.get(ImageEnum.HUB.value)
|
||||
link_layer_button = tk.Radiobutton(
|
||||
self.edit_frame,
|
||||
indicatoron=False,
|
||||
|
@ -414,22 +417,22 @@ class CoreToolbar(object):
|
|||
|
||||
def pick_marker(self, main_button):
|
||||
self.marker_option_menu.destroy()
|
||||
main_button.configure(image=Images.get("marker"))
|
||||
main_button.configure(image=Images.get(ImageEnum.MARKER.value))
|
||||
logging.debug("Pick MARKER")
|
||||
|
||||
def pick_oval(self, main_button):
|
||||
self.marker_option_menu.destroy()
|
||||
main_button.configure(image=Images.get("oval"))
|
||||
main_button.configure(image=Images.get(ImageEnum.OVAL.value))
|
||||
logging.debug("Pick OVAL")
|
||||
|
||||
def pick_rectangle(self, main_button):
|
||||
self.marker_option_menu.destroy()
|
||||
main_button.configure(image=Images.get("rectangle"))
|
||||
main_button.configure(image=Images.get(ImageEnum.RECTANGLE.value))
|
||||
logging.debug("Pick RECTANGLE")
|
||||
|
||||
def pick_text(self, main_button):
|
||||
self.marker_option_menu.destroy()
|
||||
main_button.configure(image=Images.get("text"))
|
||||
main_button.configure(image=Images.get(ImageEnum.TEXT.value))
|
||||
logging.debug("Pick TEXT")
|
||||
|
||||
def draw_marker_options(self, main_button):
|
||||
|
@ -443,10 +446,10 @@ class CoreToolbar(object):
|
|||
self.destroy_previous_frame()
|
||||
option_frame = tk.Frame(self.master, padx=1, pady=1)
|
||||
img_list = [
|
||||
Images.get("marker"),
|
||||
Images.get("oval"),
|
||||
Images.get("rectangle"),
|
||||
Images.get("text"),
|
||||
Images.get(ImageEnum.MARKER.value),
|
||||
Images.get(ImageEnum.OVAL.value),
|
||||
Images.get(ImageEnum.RECTANGLE.value),
|
||||
Images.get(ImageEnum.TEXT.value),
|
||||
]
|
||||
func_list = [
|
||||
self.pick_marker,
|
||||
|
@ -475,7 +478,7 @@ class CoreToolbar(object):
|
|||
|
||||
:return: nothing
|
||||
"""
|
||||
marker_image = Images.get("marker")
|
||||
marker_image = Images.get(ImageEnum.MARKER.value)
|
||||
marker_main_button = tk.Radiobutton(
|
||||
self.edit_frame,
|
||||
indicatoron=False,
|
||||
|
@ -492,13 +495,13 @@ class CoreToolbar(object):
|
|||
def create_toolbar(self):
|
||||
self.create_regular_button(
|
||||
self.edit_frame,
|
||||
Images.get("start"),
|
||||
Images.get(ImageEnum.START.value),
|
||||
self.click_start_session_tool,
|
||||
"start the session",
|
||||
)
|
||||
self.create_radio_button(
|
||||
self.edit_frame,
|
||||
Images.get("select"),
|
||||
Images.get(ImageEnum.SELECT.value),
|
||||
self.click_selection_tool,
|
||||
self.radio_value,
|
||||
1,
|
||||
|
@ -506,7 +509,7 @@ class CoreToolbar(object):
|
|||
)
|
||||
self.create_radio_button(
|
||||
self.edit_frame,
|
||||
Images.get("link"),
|
||||
Images.get(ImageEnum.LINK.value),
|
||||
self.click_link_tool,
|
||||
self.radio_value,
|
||||
2,
|
||||
|
@ -520,7 +523,7 @@ class CoreToolbar(object):
|
|||
def create_observe_button(self):
|
||||
menu_button = tk.Menubutton(
|
||||
self.edit_frame,
|
||||
image=Images.get("observe"),
|
||||
image=Images.get(ImageEnum.OBSERVE.value),
|
||||
width=self.width,
|
||||
height=self.height,
|
||||
direction=tk.RIGHT,
|
||||
|
@ -550,10 +553,10 @@ class CoreToolbar(object):
|
|||
def click_stop_button(self):
|
||||
logging.debug("Click on STOP button ")
|
||||
self.destroy_children_widgets(self.edit_frame)
|
||||
self.create_toolbar()
|
||||
self.canvas.core_grpc.set_session_state("datacollect")
|
||||
self.canvas.core_grpc.delete_links()
|
||||
self.canvas.core_grpc.delete_nodes()
|
||||
self.create_toolbar()
|
||||
|
||||
def click_run_button(self):
|
||||
logging.debug("Click on RUN button")
|
||||
|
@ -570,13 +573,13 @@ class CoreToolbar(object):
|
|||
def create_runtime_toolbar(self):
|
||||
self.create_regular_button(
|
||||
self.edit_frame,
|
||||
Images.get("stop"),
|
||||
Images.get(ImageEnum.STOP.value),
|
||||
self.click_stop_button,
|
||||
"stop the session",
|
||||
)
|
||||
self.create_radio_button(
|
||||
self.edit_frame,
|
||||
Images.get("select"),
|
||||
Images.get(ImageEnum.SELECT.value),
|
||||
self.click_selection_tool,
|
||||
self.exec_radio_value,
|
||||
1,
|
||||
|
@ -585,7 +588,7 @@ class CoreToolbar(object):
|
|||
self.create_observe_button()
|
||||
self.create_radio_button(
|
||||
self.edit_frame,
|
||||
Images.get("plot"),
|
||||
Images.get(ImageEnum.PLOT.value),
|
||||
self.click_plot_button,
|
||||
self.exec_radio_value,
|
||||
2,
|
||||
|
@ -593,7 +596,7 @@ class CoreToolbar(object):
|
|||
)
|
||||
self.create_radio_button(
|
||||
self.edit_frame,
|
||||
Images.get("marker"),
|
||||
Images.get(ImageEnum.MARKER.value),
|
||||
self.click_marker_button,
|
||||
self.exec_radio_value,
|
||||
3,
|
||||
|
@ -601,13 +604,16 @@ class CoreToolbar(object):
|
|||
)
|
||||
self.create_radio_button(
|
||||
self.edit_frame,
|
||||
Images.get("twonode"),
|
||||
Images.get(ImageEnum.TWONODE.value),
|
||||
self.click_two_node_button,
|
||||
self.exec_radio_value,
|
||||
4,
|
||||
"run command from one node to another",
|
||||
)
|
||||
self.create_regular_button(
|
||||
self.edit_frame, Images.get("run"), self.click_run_button, "run"
|
||||
self.edit_frame,
|
||||
Images.get(ImageEnum.RUN.value),
|
||||
self.click_run_button,
|
||||
"run",
|
||||
)
|
||||
self.exec_radio_value.set(1)
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import enum
|
||||
import logging
|
||||
import math
|
||||
import tkinter as tk
|
||||
|
||||
from core.api.grpc import core_pb2
|
||||
from coretk.grpcmanagement import GrpcManager
|
||||
from coretk.images import Images
|
||||
from coretk.interface import Interface
|
||||
|
||||
|
||||
class GraphMode(enum.Enum):
|
||||
|
@ -34,7 +36,7 @@ class CanvasGraph(tk.Canvas):
|
|||
self.draw_grid()
|
||||
self.core_grpc = grpc
|
||||
self.grpc_manager = GrpcManager()
|
||||
self.draw_existing_component()
|
||||
# self.draw_existing_component()
|
||||
|
||||
def setup_menus(self):
|
||||
self.node_context = tk.Menu(self.master)
|
||||
|
@ -52,9 +54,6 @@ class CanvasGraph(tk.Canvas):
|
|||
self.bind("<ButtonRelease-1>", self.click_release)
|
||||
self.bind("<B1-Motion>", self.click_motion)
|
||||
self.bind("<Button-3>", self.context)
|
||||
# self.bind("e", self.set_mode)
|
||||
# self.bind("s", self.set_mode)
|
||||
# self.bind("n", self.set_mode)
|
||||
|
||||
def draw_grid(self, width=1000, height=750):
|
||||
"""
|
||||
|
@ -91,8 +90,7 @@ class CanvasGraph(tk.Canvas):
|
|||
|
||||
session_id = self.core_grpc.session_id
|
||||
session = self.core_grpc.core.get_session(session_id).session
|
||||
# nodes = response.session.nodes
|
||||
# if len(nodes) > 0:
|
||||
# redraw existing nodes
|
||||
for node in session.nodes:
|
||||
# peer to peer node is not drawn on the GUI
|
||||
if node.type != core_pb2.NodeType.PEER_TO_PEER:
|
||||
|
@ -104,18 +102,62 @@ class CanvasGraph(tk.Canvas):
|
|||
core_id_to_canvas_id[node.id] = n.id
|
||||
self.grpc_manager.add_preexisting_node(n, session_id, node, name)
|
||||
self.grpc_manager.update_reusable_id()
|
||||
|
||||
# draw existing links
|
||||
# links = response.session.links
|
||||
for link in session.links:
|
||||
n1 = self.nodes[core_id_to_canvas_id[link.node_one_id]]
|
||||
n2 = self.nodes[core_id_to_canvas_id[link.node_two_id]]
|
||||
e = CanvasEdge(n1.x_coord, n1.y_coord, n2.x_coord, n2.y_coord, n1.id, self)
|
||||
n1.edges.add(e)
|
||||
n2.edges.add(e)
|
||||
self.edges[e.token] = e
|
||||
self.grpc_manager.add_edge(session_id, e.token, n1.id, n2.id)
|
||||
|
||||
# TODO add back the link info to grpc manager also redraw
|
||||
grpc_if1 = link.interface_one
|
||||
grpc_if2 = link.interface_two
|
||||
ip4_src = None
|
||||
ip4_dst = None
|
||||
ip6_src = None
|
||||
ip6_dst = None
|
||||
if grpc_if1 is not None:
|
||||
ip4_src = grpc_if1.ip4
|
||||
ip6_src = grpc_if1.ip6
|
||||
if grpc_if2 is not None:
|
||||
ip4_dst = grpc_if2.ip4
|
||||
ip6_dst = grpc_if2.ip6
|
||||
e.link_info = LinkInfo(
|
||||
canvas=self,
|
||||
edge_id=e.id,
|
||||
ip4_src=ip4_src,
|
||||
ip6_src=ip6_src,
|
||||
ip4_dst=ip4_dst,
|
||||
ip6_dst=ip6_dst,
|
||||
throughput=None,
|
||||
)
|
||||
|
||||
# TODO will include throughput and ipv6 in the future
|
||||
if1 = Interface(grpc_if1.name, grpc_if1.ip4)
|
||||
if2 = Interface(grpc_if2.name, grpc_if2.ip4)
|
||||
self.grpc_manager.edges[e.token].interface_1 = if1
|
||||
self.grpc_manager.edges[e.token].interface_2 = if2
|
||||
self.grpc_manager.nodes[
|
||||
core_id_to_canvas_id[link.node_one_id]
|
||||
].interfaces.append(if1)
|
||||
self.grpc_manager.nodes[
|
||||
core_id_to_canvas_id[link.node_two_id]
|
||||
].interfaces.append(if2)
|
||||
|
||||
# lift the nodes so they on top of the links
|
||||
for i in core_id_to_canvas_id.values():
|
||||
self.lift(i)
|
||||
|
||||
def delete_components(self):
|
||||
tags = ["node", "edge", "linkinfo", "nodename"]
|
||||
for i in tags:
|
||||
for id in self.find_withtag(i):
|
||||
self.delete(id)
|
||||
|
||||
def canvas_xy(self, event):
|
||||
"""
|
||||
Convert window coordinate to canvas coordinate
|
||||
|
@ -171,6 +213,7 @@ class CanvasGraph(tk.Canvas):
|
|||
self.mode = GraphMode.NODE
|
||||
|
||||
def handle_edge_release(self, event):
|
||||
print("Calling edge release")
|
||||
edge = self.drawing_edge
|
||||
self.drawing_edge = None
|
||||
|
||||
|
@ -204,7 +247,20 @@ class CanvasGraph(tk.Canvas):
|
|||
node_dst.edges.add(edge)
|
||||
|
||||
self.grpc_manager.add_edge(
|
||||
self.core_grpc.get_session_id(), edge.token, node_src.id, node_dst.id
|
||||
self.core_grpc.session_id, edge.token, node_src.id, node_dst.id
|
||||
)
|
||||
|
||||
# draw link info on the edge
|
||||
if1 = self.grpc_manager.edges[edge.token].interface_1
|
||||
if2 = self.grpc_manager.edges[edge.token].interface_2
|
||||
edge.link_info = LinkInfo(
|
||||
self,
|
||||
edge.id,
|
||||
ip4_src=if1.ip4_and_prefix,
|
||||
ip6_src=None,
|
||||
ip4_dst=if2.ip4_and_prefix,
|
||||
ip6_dst=None,
|
||||
throughput=None,
|
||||
)
|
||||
|
||||
logging.debug(f"edges: {self.find_withtag('edge')}")
|
||||
|
@ -216,6 +272,7 @@ class CanvasGraph(tk.Canvas):
|
|||
:param event: mouse event
|
||||
:return: nothing
|
||||
"""
|
||||
print("click on the canvas")
|
||||
logging.debug(f"click press: {event}")
|
||||
selected = self.get_selected(event)
|
||||
is_node = selected in self.find_withtag("node")
|
||||
|
@ -243,14 +300,15 @@ class CanvasGraph(tk.Canvas):
|
|||
self.node_context.post(event.x_root, event.y_root)
|
||||
|
||||
def add_node(self, x, y, image, node_name):
|
||||
node = CanvasNode(
|
||||
x=x, y=y, image=image, canvas=self, core_id=self.grpc_manager.peek_id()
|
||||
)
|
||||
self.nodes[node.id] = node
|
||||
self.grpc_manager.add_node(
|
||||
self.core_grpc.get_session_id(), node.id, x, y, node_name
|
||||
)
|
||||
return node
|
||||
if self.selected == 1:
|
||||
node = CanvasNode(
|
||||
x=x, y=y, image=image, canvas=self, core_id=self.grpc_manager.peek_id()
|
||||
)
|
||||
self.nodes[node.id] = node
|
||||
self.grpc_manager.add_node(
|
||||
self.core_grpc.session_id, node.id, x, y, node_name
|
||||
)
|
||||
return node
|
||||
|
||||
|
||||
class CanvasEdge:
|
||||
|
@ -258,7 +316,7 @@ class CanvasEdge:
|
|||
Canvas edge class
|
||||
"""
|
||||
|
||||
width = 3
|
||||
width = 1.3
|
||||
|
||||
def __init__(self, x1, y1, x2, y2, src, canvas):
|
||||
"""
|
||||
|
@ -273,9 +331,13 @@ class CanvasEdge:
|
|||
self.src = src
|
||||
self.dst = None
|
||||
self.canvas = canvas
|
||||
self.id = self.canvas.create_line(x1, y1, x2, y2, tags="edge", width=self.width)
|
||||
self.id = self.canvas.create_line(
|
||||
x1, y1, x2, y2, tags="edge", width=self.width, fill="#ff0000"
|
||||
)
|
||||
self.token = None
|
||||
|
||||
# link info object
|
||||
self.link_info = None
|
||||
# TODO resolve this
|
||||
# self.canvas.tag_lower(self.id)
|
||||
|
||||
|
@ -301,29 +363,40 @@ class CanvasNode:
|
|||
self.core_id = core_id
|
||||
self.x_coord = x
|
||||
self.y_coord = y
|
||||
self.name = f"Node {self.core_id}"
|
||||
self.text_id = self.canvas.create_text(x, y + 20, text=self.name)
|
||||
self.name = f"N{self.core_id}"
|
||||
self.text_id = self.canvas.create_text(
|
||||
x, y + 20, text=self.name, tags="nodename"
|
||||
)
|
||||
self.canvas.tag_bind(self.id, "<ButtonPress-1>", self.click_press)
|
||||
self.canvas.tag_bind(self.id, "<ButtonRelease-1>", self.click_release)
|
||||
self.canvas.tag_bind(self.id, "<B1-Motion>", self.motion)
|
||||
self.canvas.tag_bind(self.id, "<Button-3>", self.context)
|
||||
self.canvas.tag_bind(self.id, "<Double-Button-1>", self.double_click)
|
||||
|
||||
self.edges = set()
|
||||
self.moving = None
|
||||
|
||||
#
|
||||
# def get_coords(self):
|
||||
# return self.x_coord, self.y_coord
|
||||
def double_click(self, event):
|
||||
node_id = self.canvas.grpc_manager.nodes[self.id].node_id
|
||||
state = self.canvas.core_grpc.get_session_state()
|
||||
if state == core_pb2.SessionState.RUNTIME:
|
||||
self.canvas.core_grpc.launch_terminal(node_id)
|
||||
|
||||
def update_coords(self):
|
||||
self.x_coord, self.y_coord = self.canvas.coords(self.id)
|
||||
|
||||
def click_press(self, event):
|
||||
print("click on the node")
|
||||
logging.debug(f"click press {self.name}: {event}")
|
||||
self.moving = self.canvas.canvas_xy(event)
|
||||
# return "break"
|
||||
|
||||
def click_release(self, event):
|
||||
logging.debug(f"click release {self.name}: {event}")
|
||||
self.update_coords()
|
||||
self.canvas.grpc_manager.update_node_location(
|
||||
self.id, self.x_coord, self.y_coord
|
||||
)
|
||||
self.moving = None
|
||||
|
||||
def motion(self, event):
|
||||
|
@ -344,6 +417,64 @@ class CanvasNode:
|
|||
self.canvas.coords(edge.id, new_x, new_y, x2, y2)
|
||||
else:
|
||||
self.canvas.coords(edge.id, x1, y1, new_x, new_y)
|
||||
edge.link_info.recalculate_info()
|
||||
|
||||
def context(self, event):
|
||||
logging.debug(f"context click {self.name}: {event}")
|
||||
|
||||
|
||||
class LinkInfo:
|
||||
def __init__(self, canvas, edge_id, ip4_src, ip6_src, ip4_dst, ip6_dst, throughput):
|
||||
self.canvas = canvas
|
||||
self.edge_id = edge_id
|
||||
self.radius = 37
|
||||
|
||||
self.ip4_address_1 = ip4_src
|
||||
self.ip6_address_1 = ip6_src
|
||||
self.ip4_address_2 = ip4_dst
|
||||
self.ip6_address_2 = ip6_dst
|
||||
self.throughput = throughput
|
||||
self.id1 = self.create_edge_src_info()
|
||||
self.id2 = self.create_edge_dst_info()
|
||||
|
||||
def slope_src_dst(self):
|
||||
x1, y1, x2, y2 = self.canvas.coords(self.edge_id)
|
||||
return (y2 - y1) / (x2 - x1)
|
||||
|
||||
def create_edge_src_info(self):
|
||||
x1, y1, x2, _ = self.canvas.coords(self.edge_id)
|
||||
m = self.slope_src_dst()
|
||||
distance = math.cos(math.atan(m)) * self.radius
|
||||
if x1 > x2:
|
||||
distance = -distance
|
||||
# id1 = self.canvas.create_text(x1, y1, text=self.ip4_address_1)
|
||||
print(self.ip4_address_1)
|
||||
id1 = self.canvas.create_text(
|
||||
x1 + distance, y1 + distance * m, text=self.ip4_address_1, tags="linkinfo"
|
||||
)
|
||||
return id1
|
||||
|
||||
def create_edge_dst_info(self):
|
||||
x1, _, x2, y2 = self.canvas.coords(self.edge_id)
|
||||
m = self.slope_src_dst()
|
||||
distance = math.cos(math.atan(m)) * self.radius
|
||||
if x1 > x2:
|
||||
distance = -distance
|
||||
# id2 = self.canvas.create_text(x2, y2, text=self.ip4_address_2)
|
||||
id2 = self.canvas.create_text(
|
||||
x2 - distance, y2 - distance * m, text=self.ip4_address_2, tags="linkinfo"
|
||||
)
|
||||
return id2
|
||||
|
||||
def recalculate_info(self):
|
||||
x1, y1, x2, y2 = self.canvas.coords(self.edge_id)
|
||||
m = self.slope_src_dst()
|
||||
distance = math.cos(math.atan(m)) * self.radius
|
||||
if x1 > x2:
|
||||
distance = -distance
|
||||
new_x1 = x1 + distance
|
||||
new_y1 = y1 + distance * m
|
||||
new_x2 = x2 - distance
|
||||
new_y2 = y2 - distance * m
|
||||
self.canvas.coords(self.id1, new_x1, new_y1)
|
||||
self.canvas.coords(self.id2, new_x2, new_y2)
|
||||
|
|
|
@ -5,6 +5,7 @@ that can be useful for grpc, acts like a session class
|
|||
import logging
|
||||
|
||||
from core.api.grpc import core_pb2
|
||||
from coretk.interface import Interface, InterfaceManager
|
||||
|
||||
link_layer_nodes = ["switch", "hub", "wlan", "rj45", "tunnel"]
|
||||
network_layer_nodes = ["router", "host", "PC", "mdr", "prouter", "OVS"]
|
||||
|
@ -29,6 +30,7 @@ class Node:
|
|||
self.y = y
|
||||
self.model = model
|
||||
self.name = name
|
||||
self.interfaces = []
|
||||
|
||||
|
||||
class Edge:
|
||||
|
@ -46,6 +48,8 @@ class Edge:
|
|||
self.id2 = node_id_2
|
||||
self.type1 = node_type_1
|
||||
self.type2 = node_type_2
|
||||
self.interface_1 = None
|
||||
self.interface_2 = None
|
||||
|
||||
|
||||
class GrpcManager:
|
||||
|
@ -57,6 +61,7 @@ class GrpcManager:
|
|||
self.reusable = []
|
||||
|
||||
self.preexisting = []
|
||||
self.interfaces_manager = InterfaceManager()
|
||||
|
||||
def peek_id(self):
|
||||
"""
|
||||
|
@ -146,6 +151,18 @@ class GrpcManager:
|
|||
)
|
||||
self.nodes[canvas_node.id] = n
|
||||
|
||||
def update_node_location(self, canvas_id, new_x, new_y):
|
||||
"""
|
||||
update node
|
||||
|
||||
:param int canvas_id: canvas id of that node
|
||||
:param int new_x: new x coord
|
||||
:param int new_y: new y coord
|
||||
:return: nothing
|
||||
"""
|
||||
self.nodes[canvas_id].x = new_x
|
||||
self.nodes[canvas_id].y = new_y
|
||||
|
||||
def update_reusable_id(self):
|
||||
"""
|
||||
Update available id for reuse
|
||||
|
@ -173,7 +190,60 @@ class GrpcManager:
|
|||
except KeyError:
|
||||
logging.error("grpcmanagement.py INVALID NODE CANVAS ID")
|
||||
|
||||
def create_interface(self, edge, src_canvas_id, dst_canvas_id):
|
||||
"""
|
||||
Create the interface for the two end of an edge, add a copy to node's interfaces
|
||||
|
||||
:param coretk.grpcmanagement.Edge edge: edge to add interfaces to
|
||||
:param int src_canvas_id: canvas id for the source node
|
||||
:param int dst_canvas_id: canvas id for the destination node
|
||||
:return: nothing
|
||||
"""
|
||||
src_interface = None
|
||||
dst_interface = None
|
||||
|
||||
src_node = self.nodes[src_canvas_id]
|
||||
if src_node.model in network_layer_nodes:
|
||||
ifid = len(src_node.interfaces)
|
||||
name = "eth" + str(ifid)
|
||||
src_interface = Interface(
|
||||
name=name, ifid=ifid, ipv4=str(self.interfaces_manager.get_address())
|
||||
)
|
||||
self.nodes[src_canvas_id].interfaces.append(src_interface)
|
||||
logging.debug(
|
||||
"Create source interface 1... IP: %s, name: %s",
|
||||
src_interface.ipv4,
|
||||
src_interface.name,
|
||||
)
|
||||
|
||||
dst_node = self.nodes[dst_canvas_id]
|
||||
if dst_node.model in network_layer_nodes:
|
||||
ifid = len(dst_node.interfaces)
|
||||
name = "veth" + str(ifid)
|
||||
dst_interface = Interface(
|
||||
name=name, ifid=ifid, ipv4=str(self.interfaces_manager.get_address())
|
||||
)
|
||||
self.nodes[dst_canvas_id].interfaces.append(dst_interface)
|
||||
logging.debug(
|
||||
"Create destination interface... IP: %s, name: %s",
|
||||
dst_interface.ipv4,
|
||||
dst_interface.name,
|
||||
)
|
||||
|
||||
edge.interface_1 = src_interface
|
||||
edge.interface_2 = dst_interface
|
||||
|
||||
def add_edge(self, session_id, token, canvas_id_1, canvas_id_2):
|
||||
"""
|
||||
Add an edge to grpc manager
|
||||
|
||||
:param int session_id: core session id
|
||||
:param tuple(int, int) token: edge's identification in the canvas
|
||||
:param int canvas_id_1: canvas id of source node
|
||||
:param int canvas_id_2: canvas_id of destination node
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
if canvas_id_1 in self.nodes and canvas_id_2 in self.nodes:
|
||||
edge = Edge(
|
||||
session_id,
|
||||
|
@ -183,6 +253,7 @@ class GrpcManager:
|
|||
self.nodes[canvas_id_2].type,
|
||||
)
|
||||
self.edges[token] = edge
|
||||
self.create_interface(edge, canvas_id_1, canvas_id_2)
|
||||
logging.debug("Adding edge to grpc manager...")
|
||||
else:
|
||||
logging.error("grpcmanagement.py INVALID CANVAS NODE ID")
|
||||
|
|
Before Width: | Height: | Size: 744 B After Width: | Height: | Size: 744 B |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 635 B After Width: | Height: | Size: 635 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 719 B After Width: | Height: | Size: 719 B |
Before Width: | Height: | Size: 744 B After Width: | Height: | Size: 744 B |
Before Width: | Height: | Size: 86 B After Width: | Height: | Size: 86 B |
Before Width: | Height: | Size: 375 B After Width: | Height: | Size: 375 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 174 B After Width: | Height: | Size: 174 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 265 B After Width: | Height: | Size: 265 B |
Before Width: | Height: | Size: 160 B After Width: | Height: | Size: 160 B |
Before Width: | Height: | Size: 755 B After Width: | Height: | Size: 755 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 753 B After Width: | Height: | Size: 753 B |
Before Width: | Height: | Size: 324 B After Width: | Height: | Size: 324 B |
Before Width: | Height: | Size: 925 B After Width: | Height: | Size: 925 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 127 B After Width: | Height: | Size: 127 B |
Before Width: | Height: | Size: 799 B After Width: | Height: | Size: 799 B |
Before Width: | Height: | Size: 220 B After Width: | Height: | Size: 220 B |
Before Width: | Height: | Size: 146 B After Width: | Height: | Size: 146 B |
|
@ -1,11 +1,13 @@
|
|||
import logging
|
||||
import os
|
||||
from enum import Enum
|
||||
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
from core.api.grpc import core_pb2
|
||||
|
||||
PATH = os.path.abspath(os.path.dirname(__file__))
|
||||
ICONS_DIR = os.path.join(PATH, "icons")
|
||||
|
||||
|
||||
class Images:
|
||||
|
@ -13,7 +15,7 @@ class Images:
|
|||
|
||||
@classmethod
|
||||
def load(cls, name, file_path):
|
||||
file_path = os.path.join(PATH, file_path)
|
||||
# file_path = os.path.join(PATH, file_path)
|
||||
image = Image.open(file_path)
|
||||
tk_image = ImageTk.PhotoImage(image)
|
||||
cls.images[name] = tk_image
|
||||
|
@ -33,55 +35,62 @@ class Images:
|
|||
:return: the matching image and its name
|
||||
"""
|
||||
if node_type == core_pb2.NodeType.SWITCH:
|
||||
return Images.get("switch"), "switch"
|
||||
return Images.get(ImageEnum.SWITCH.value), "switch"
|
||||
if node_type == core_pb2.NodeType.HUB:
|
||||
return Images.get("hub"), "hub"
|
||||
return Images.get(ImageEnum.HUB.value), "hub"
|
||||
if node_type == core_pb2.NodeType.WIRELESS_LAN:
|
||||
return Images.get("wlan"), "wlan"
|
||||
return Images.get(ImageEnum.WLAN.value), "wlan"
|
||||
if node_type == core_pb2.NodeType.RJ45:
|
||||
return Images.get("rj45"), "rj45"
|
||||
return Images.get(ImageEnum.RJ45.value), "rj45"
|
||||
if node_type == core_pb2.NodeType.TUNNEL:
|
||||
return Images.get("tunnel"), "tunnel"
|
||||
return Images.get(ImageEnum.TUNNEL.value), "tunnel"
|
||||
if node_type == core_pb2.NodeType.DEFAULT:
|
||||
if node_model == "router":
|
||||
return Images.get("router"), "router"
|
||||
return Images.get(ImageEnum.ROUTER.value), "router"
|
||||
if node_model == "host":
|
||||
return Images.get(("host")), "host"
|
||||
return Images.get((ImageEnum.HOST.value)), "host"
|
||||
if node_model == "PC":
|
||||
return Images.get("pc"), "PC"
|
||||
return Images.get(ImageEnum.PC.value), "PC"
|
||||
if node_model == "mdr":
|
||||
return Images.get("mdr"), "mdr"
|
||||
return Images.get(ImageEnum.MDR.value), "mdr"
|
||||
if node_model == "prouter":
|
||||
return Images.get("prouter"), "prouter"
|
||||
return Images.get(ImageEnum.PROUTER.value), "prouter"
|
||||
if node_model == "OVS":
|
||||
return Images.get("ovs"), "ovs"
|
||||
return Images.get(ImageEnum.OVS.value), "ovs"
|
||||
else:
|
||||
logging.debug("INVALID INPUT OR NOT CONSIDERED YET")
|
||||
|
||||
|
||||
class ImageEnum(Enum):
|
||||
SWITCH = "lanswitch"
|
||||
CORE = "core-icon"
|
||||
START = "start"
|
||||
MARKER = "marker"
|
||||
ROUTER = "router"
|
||||
SELECT = "select"
|
||||
LINK = "link"
|
||||
HUB = "hub"
|
||||
WLAN = "wlan"
|
||||
RJ45 = "rj45"
|
||||
TUNNEL = "tunnel"
|
||||
OVAL = "oval"
|
||||
RECTANGLE = "rectangle"
|
||||
TEXT = "text"
|
||||
HOST = "host"
|
||||
PC = "pc"
|
||||
MDR = "mdr"
|
||||
PROUTER = "router_green"
|
||||
OVS = "OVS"
|
||||
EDITNODE = "document-properties"
|
||||
PLOT = "plot"
|
||||
TWONODE = "twonode"
|
||||
STOP = "stop"
|
||||
OBSERVE = "observe"
|
||||
RUN = "run"
|
||||
|
||||
|
||||
def load_core_images(images):
|
||||
images.load("core", "core-icon.png")
|
||||
images.load("start", "start.gif")
|
||||
images.load("switch", "lanswitch.gif")
|
||||
images.load("marker", "marker.gif")
|
||||
images.load("router", "router.gif")
|
||||
images.load("select", "select.gif")
|
||||
images.load("link", "link.gif")
|
||||
images.load("hub", "hub.gif")
|
||||
images.load("wlan", "wlan.gif")
|
||||
images.load("rj45", "rj45.gif")
|
||||
images.load("tunnel", "tunnel.gif")
|
||||
images.load("oval", "oval.gif")
|
||||
images.load("rectangle", "rectangle.gif")
|
||||
images.load("text", "text.gif")
|
||||
images.load("host", "host.gif")
|
||||
images.load("pc", "pc.gif")
|
||||
images.load("mdr", "mdr.gif")
|
||||
images.load("prouter", "router_green.gif")
|
||||
images.load("ovs", "OVS.gif")
|
||||
images.load("editnode", "document-properties.gif")
|
||||
images.load("run", "run.gif")
|
||||
images.load("plot", "plot.gif")
|
||||
images.load("twonode", "twonode.gif")
|
||||
images.load("stop", "stop.gif")
|
||||
images.load("observe", "observe.gif")
|
||||
for file_name in os.listdir(ICONS_DIR):
|
||||
file_path = os.path.join(ICONS_DIR, file_name)
|
||||
name = file_name.split(".")[0]
|
||||
images.load(name, file_path)
|
||||
|
|
43
coretk/coretk/interface.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
import ipaddress
|
||||
import random
|
||||
|
||||
|
||||
class Interface:
|
||||
def __init__(self, name, ipv4, ifid=None):
|
||||
"""
|
||||
Create an interface instance
|
||||
|
||||
:param str name: interface name
|
||||
:param str ip4: IPv4
|
||||
:param str mac: MAC address
|
||||
:param int ifid: interface id
|
||||
"""
|
||||
self.name = name
|
||||
self.ipv4 = ipv4
|
||||
self.ip4prefix = 24
|
||||
self.ip4_and_prefix = ipv4 + "/" + str(self.ip4prefix)
|
||||
self.mac = self.random_mac_address()
|
||||
self.id = ifid
|
||||
|
||||
def random_mac_address(self):
|
||||
return "02:00:00:%02x:%02x:%02x" % (
|
||||
random.randint(0, 255),
|
||||
random.randint(0, 255),
|
||||
random.randint(0, 225),
|
||||
)
|
||||
|
||||
|
||||
class InterfaceManager:
|
||||
def __init__(self):
|
||||
self.addresses = list(ipaddress.ip_network("10.0.0.0/24").hosts())
|
||||
self.index = 0
|
||||
|
||||
def get_address(self):
|
||||
"""
|
||||
Retrieve a new ipv4 address
|
||||
|
||||
:return:
|
||||
"""
|
||||
i = self.index
|
||||
self.index = self.index + 1
|
||||
return self.addresses[i]
|
|
@ -3,7 +3,14 @@ The actions taken when each menubar option is clicked
|
|||
"""
|
||||
|
||||
import logging
|
||||
import tkinter as tk
|
||||
import webbrowser
|
||||
from tkinter import messagebox
|
||||
|
||||
from core.api.grpc import core_pb2
|
||||
from coretk.coregrpc import CoreGrpc
|
||||
|
||||
SAVEDIR = "/home/ncs/Desktop/"
|
||||
|
||||
|
||||
def sub_menu_items():
|
||||
|
@ -38,14 +45,6 @@ def file_save_shortcut(event):
|
|||
logging.debug("Shortcut for file save")
|
||||
|
||||
|
||||
def file_save_as_xml():
|
||||
logging.debug("Click save as XML")
|
||||
|
||||
|
||||
def file_save_as_imn():
|
||||
logging.debug("Click save as imn")
|
||||
|
||||
|
||||
def file_export_python_script():
|
||||
logging.debug("Click file export python script")
|
||||
|
||||
|
@ -70,10 +69,6 @@ def file_save_screenshot():
|
|||
logging.debug("Click file save screenshot")
|
||||
|
||||
|
||||
def file_example_link():
|
||||
logging.debug("Click file example link")
|
||||
|
||||
|
||||
def edit_undo():
|
||||
logging.debug("Click edit undo")
|
||||
|
||||
|
@ -294,10 +289,6 @@ def widgets_configure_throughput():
|
|||
logging.debug("Click widgets configure throughput")
|
||||
|
||||
|
||||
def session_start():
|
||||
logging.debug("Click session start")
|
||||
|
||||
|
||||
def session_change_sessions():
|
||||
logging.debug("Click session change sessions")
|
||||
|
||||
|
@ -326,13 +317,107 @@ def session_options():
|
|||
logging.debug("Click session options")
|
||||
|
||||
|
||||
def help_core_github():
|
||||
webbrowser.open_new("https://github.com/coreemu/core")
|
||||
|
||||
|
||||
def help_core_documentation():
|
||||
webbrowser.open_new("http://coreemu.github.io/core/")
|
||||
# def help_core_github():
|
||||
# webbrowser.open_new("https://github.com/coreemu/core")
|
||||
#
|
||||
#
|
||||
# def help_core_documentation():
|
||||
# webbrowser.open_new("http://coreemu.github.io/core/")
|
||||
|
||||
|
||||
def help_about():
|
||||
logging.debug("Click help About")
|
||||
|
||||
|
||||
class MenuAction:
|
||||
"""
|
||||
Actions performed when choosing menu items
|
||||
"""
|
||||
|
||||
def __init__(self, application, master):
|
||||
self.master = master
|
||||
self.application = application
|
||||
self.core_grpc = application.core_grpc
|
||||
|
||||
def clean_nodes_links_and_set_configuarations(self):
|
||||
"""
|
||||
Prompt use to stop running session before application is closed
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
logging.info(
|
||||
"menuaction.py: clean_nodes_links_and_set_configuration() Exiting the program"
|
||||
)
|
||||
grpc = self.application.core_grpc
|
||||
state = grpc.get_session_state()
|
||||
|
||||
if (
|
||||
state == core_pb2.SessionState.SHUTDOWN
|
||||
or state == core_pb2.SessionState.DEFINITION
|
||||
):
|
||||
grpc.delete_session()
|
||||
grpc.core.close()
|
||||
# self.application.quit()
|
||||
else:
|
||||
msgbox = messagebox.askyesnocancel("stop", "Stop the running session?")
|
||||
|
||||
if msgbox or msgbox is False:
|
||||
if msgbox:
|
||||
grpc.set_session_state("datacollect")
|
||||
grpc.delete_links()
|
||||
grpc.delete_nodes()
|
||||
grpc.delete_session()
|
||||
|
||||
grpc.core.close()
|
||||
# self.application.quit()
|
||||
|
||||
def on_quit(self):
|
||||
"""
|
||||
Prompt user whether so save running session, and then close the application
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
self.clean_nodes_links_and_set_configuarations()
|
||||
# self.application.core_grpc.close()
|
||||
self.application.quit()
|
||||
|
||||
def file_save_as_xml(self):
|
||||
logging.info("menuaction.py file_save_as_xml()")
|
||||
grpc = self.application.core_grpc
|
||||
file_path = tk.filedialog.asksaveasfilename(
|
||||
initialdir=SAVEDIR,
|
||||
title="Save As",
|
||||
filetypes=(("EmulationScript XML files", "*.xml"), ("All files", "*")),
|
||||
defaultextension=".xml",
|
||||
)
|
||||
with open("prev_saved_xml.txt", "a") as file:
|
||||
file.write(file_path + "\n")
|
||||
grpc.save_xml(file_path)
|
||||
|
||||
def file_open_xml(self):
|
||||
logging.info("menuaction.py file_open_xml()")
|
||||
# grpc = self.application.core_grpc
|
||||
file_path = tk.filedialog.askopenfilename(
|
||||
initialdir=SAVEDIR,
|
||||
title="Open",
|
||||
filetypes=(("EmulationScript XML File", "*.xml"), ("All Files", "*")),
|
||||
)
|
||||
# clean up before openning a new session
|
||||
# t0 = time.clock()
|
||||
self.clean_nodes_links_and_set_configuarations()
|
||||
grpc = CoreGrpc(self.application.master)
|
||||
grpc.core.connect()
|
||||
session_id = grpc.open_xml(file_path)
|
||||
grpc.session_id = session_id
|
||||
self.application.core_grpc = grpc
|
||||
self.application.canvas.core_grpc = grpc
|
||||
self.application.canvas.delete_components()
|
||||
self.application.canvas.draw_existing_component()
|
||||
# t1 = time.clock()
|
||||
# print(t1 - t0)
|
||||
|
||||
def help_core_github(self):
|
||||
webbrowser.open_new("https://github.com/coreemu/core")
|
||||
|
||||
def help_core_documentation(self):
|
||||
webbrowser.open_new("http://coreemu.github.io/core/")
|
||||
|
|
BIN
coretk/coretk/oldimage/OVS.gif
Executable file
After Width: | Height: | Size: 744 B |
BIN
coretk/coretk/oldimage/core-icon.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
coretk/coretk/oldimage/document-properties.gif
Normal file
After Width: | Height: | Size: 635 B |
BIN
coretk/coretk/oldimage/host.gif
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
coretk/coretk/oldimage/hub.gif
Normal file
After Width: | Height: | Size: 719 B |
BIN
coretk/coretk/oldimage/lanswitch.gif
Normal file
After Width: | Height: | Size: 744 B |
BIN
coretk/coretk/oldimage/link.gif
Normal file
After Width: | Height: | Size: 86 B |
BIN
coretk/coretk/oldimage/marker.gif
Normal file
After Width: | Height: | Size: 375 B |
BIN
coretk/coretk/oldimage/mdr.gif
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
coretk/coretk/oldimage/observe.gif
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
coretk/coretk/oldimage/oval.gif
Normal file
After Width: | Height: | Size: 174 B |
BIN
coretk/coretk/oldimage/pc.gif
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
coretk/coretk/oldimage/plot.gif
Normal file
After Width: | Height: | Size: 265 B |
BIN
coretk/coretk/oldimage/rectangle.gif
Normal file
After Width: | Height: | Size: 160 B |
BIN
coretk/coretk/oldimage/rj45.gif
Normal file
After Width: | Height: | Size: 755 B |
BIN
coretk/coretk/oldimage/router.gif
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
coretk/coretk/oldimage/router_green.gif
Normal file
After Width: | Height: | Size: 753 B |
BIN
coretk/coretk/oldimage/run.gif
Normal file
After Width: | Height: | Size: 324 B |
BIN
coretk/coretk/oldimage/select.gif
Normal file
After Width: | Height: | Size: 925 B |
BIN
coretk/coretk/oldimage/start.gif
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
coretk/coretk/oldimage/stop.gif
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
coretk/coretk/oldimage/text.gif
Normal file
After Width: | Height: | Size: 127 B |
BIN
coretk/coretk/oldimage/tunnel.gif
Normal file
After Width: | Height: | Size: 799 B |
BIN
coretk/coretk/oldimage/twonode.gif
Normal file
After Width: | Height: | Size: 220 B |
BIN
coretk/coretk/oldimage/wlan.gif
Normal file
After Width: | Height: | Size: 146 B |
5
coretk/coretk/prev_saved_xml.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
/home/ncs/Desktop/random.xml/home/ncs/Desktop/untitled.xml/home/ncs/Desktop/test.xml
|
||||
/home/ncs/Desktop/test1.xml
|
||||
|
||||
/home/ncs/Desktop/untitled.xml
|
||||
/home/ncs/Desktop/test1.xml
|