Base for the GNS3 console & some minor fixes.

This commit is contained in:
grossmj
2014-03-15 18:49:06 -06:00
parent c5321dc95d
commit fc360ec66e
13 changed files with 643 additions and 14 deletions

58
gns3/console_cmd.py Normal file
View File

@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Handles commands typed in the GNS3 console.
"""
import sys
import cmd
import logging
from .version import __version__
class ConsoleCmd(cmd.Cmd):
def __init__(self):
cmd.Cmd.__init__(self)
def do_version(self, args):
print("GNS3 version {}".format(__version__))
def do_debug(self, args):
"""
debug [level] (0 or 1).
"""
root = logging.getLogger()
ch = logging.StreamHandler(sys.stdout)
if len(args) == 1:
try:
level = int(args[0])
if level == 0:
print("Deactivating debugging")
root.removeHandler(ch)
else:
print("Activating debugging")
root.addHandler(ch)
except:
print(self.do_debug.__doc__)
else:
print(self.do_debug.__doc__)

143
gns3/console_view.py Normal file
View File

@@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
from .version import __version__
from .console_cmd import ConsoleCmd
from .pycutext import PyCutExt
class ConsoleView(PyCutExt, ConsoleCmd):
# list of keywords to color
keywords = set(["aux",
"capture",
"clear",
"console",
"export",
"filter",
"help",
"hist",
"idlepc",
"import",
"list",
"no",
"push",
"reload",
"resume",
"save",
"send",
"show",
"start",
"stop",
"suspend",
"telnet",
"vboxexec",
"qmonitor",
"ver"])
def __init__(self, parent):
# Set the prompt PyCutExt
self.prompt = '=> '
sys.ps1 = '=> '
# Set introduction message
self.intro = "GNS3 management console. Running GNS3 version %s.\nCopyright (c) 2006-2014 GNS3 Project." % __version__
# Parent class initialization
try:
PyCutExt.__init__(self, None, self.intro, parent=parent)
# put our own keywords list
self.colorizer.keywords = self.keywords
#self._Dynagen_Console_init()
except Exception as e:
sys.stderr.write(e)
def onKeyPress_Tab(self):
"""
Imitate cmd.Cmd.complete(self, text, state) function.
"""
line = str(self.line).lstrip()
cmd = line
args = ''
if len(self.line) > 0:
cmd, args, _ = self.parseline(line)
if cmd == '':
compfunc = self.completedefault
else:
try:
compfunc = getattr(self, 'complete_' + cmd)
except AttributeError:
compfunc = self.completenames
else:
compfunc = self.completenames
self.completion_matches = compfunc(cmd, line, 0, 0)
if self.completion_matches is not None:
# Eliminate repeating values
matches = []
for m in self.completion_matches:
try:
matches.index(m)
except ValueError:
matches.append(m)
# Update original list
self.completion_matches = matches
# In case we only have one possible value replace it on cmdline
if len(self.completion_matches) == 1:
newLine = self.completion_matches[0] + " " + args
self.line = newLine
self.point = len(newLine)
# Else, display possible values
else:
self.write("\n")
self.columnize(self.completion_matches)
# In any case, reprint promt + line
self.write("\n" + sys.ps1 + str(self.line))
def write_error(self, name, code, message):
print("Error received from {} with code {} and message: {}\n".format(name, code, message))
def _run(self):
"""
Runs as command as the cmd.Cmd class would do.
PyCutExt was originally using as Interpreter to exec user's commands.
Here we use directly the cmd.Cmd class.
"""
self.pointer = 0
if len(self.line):
self.history.append(self.line)
try:
self.lines.append(self.line)
source = "\n".join(self.lines)
self.more = self.onecmd(source)
except Exception as e:
print("Unknown command")
#print(str(e))
self.write(self.prompt)
self.lines = []
self._clearLine()

View File

@@ -659,7 +659,7 @@ class GraphicsView(QtGui.QGraphicsView):
contextual menu.
"""
from .console import telnetConsole
from .telnet_console import telnetConsole
for item in self.scene().selectedItems():
if hasattr(item.node(), "console"):
node = item.node()
@@ -761,6 +761,9 @@ class GraphicsView(QtGui.QGraphicsView):
if not node_module:
raise ModuleError("Could not find any module for {}".format(node_class))
node = node_module.createNode(node_class)
# from .main_window import MainWindow
# mainwindow = MainWindow.instance()
# node.error_signal.connect(mainwindow.uiConsoleTextEdit.write_error)
node_item = NodeItem(node)
node_module.setupNode(node)
except ModuleError as e:

View File

@@ -443,7 +443,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
Slot called when connecting to all the nodes using the console.
"""
from .console import telnetConsole
from .telnet_console import telnetConsole
for item in self.uiGraphicsView.scene().items():
if isinstance(item, NodeItem) and hasattr(item.node(), "console"):
node = item.node()

View File

@@ -285,6 +285,8 @@ class Router(Node):
# update the node with setup initial settings if any
if self._inital_settings:
self.update(self._inital_settings)
elif self._loading:
self.updated_signal.emit()
else:
self.setInitialized(True)
log.debug("router {} has been created".format(self.name()))

View File

@@ -237,6 +237,8 @@ class RouterConfigurationPage(QtGui.QWidget, Ui_routerConfigPageWidget):
self.uiNameLineEdit.setText(settings["name"])
self.uiConsolePortSpinBox.setValue(settings["console"])
self.uiAuxPortSpinBox.setValue(settings["aux"])
# load the startup-config
self.uiStartupConfigTextLabel.setText(settings["startup_config"])
# load the MAC address setting
if settings["mac_addr"]:
self.uiBaseMACLineEdit.setText(settings["mac_addr"])
@@ -245,6 +247,8 @@ class RouterConfigurationPage(QtGui.QWidget, Ui_routerConfigPageWidget):
else:
self.uiNameLabel.hide()
self.uiNameLineEdit.hide()
self.uiStartupConfigLabel.hide()
self.uiStartupConfigTextLabel.hide()
self.uiConsolePortLabel.hide()
self.uiConsolePortSpinBox.hide()
self.uiAuxPortLabel.hide()
@@ -266,8 +270,7 @@ class RouterConfigurationPage(QtGui.QWidget, Ui_routerConfigPageWidget):
# load the IOS image name without the full path
self.uiIOSImageTextLabel.setText(os.path.basename(settings["image"]))
# load the startup-config
self.uiStartupConfigTextLabel.setText(settings["startup_config"])
#TODO: startup-config setting
#self.uiStartupConfigTextLabel.setText("None")
@@ -392,6 +395,7 @@ class RouterConfigurationPage(QtGui.QWidget, Ui_routerConfigPageWidget):
del settings["console"]
del settings["aux"]
del settings["mac_addr"]
del settings["startup_config"]
#del self._settings["image"]
# get the platform and chassis if applicable

View File

@@ -143,6 +143,8 @@ class IOUDevice(Node):
# update the node with setup initial settings if any
if self._inital_settings:
self.update(self._inital_settings)
elif self._loading:
self.updated_signal.emit()
else:
self.setInitialized(True)
log.info("IOU instance {} has been created".format(self.name()))
@@ -307,6 +309,28 @@ class IOUDevice(Node):
port.setStatus(Port.stopped)
self.stopped_signal.emit()
def reload(self):
"""
Reloads this IOU instance.
"""
log.debug("{} is being reloaded".format(self.name()))
self._server.send_message("iou.reload", {"id": self._iou_id}, self._reloadCallback)
def _reloadCallback(self, result, error=False):
"""
Callback for reload.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while suspending {}: {}".format(self.name(), result["message"]))
self.error_signal.emit(self.name(), result["code"], result["message"])
else:
log.info("{} has reloaded".format(self.name()))
def allocateUDPPort(self, port_id):
"""
Requests an UDP port allocation.
@@ -529,7 +553,7 @@ class IOUDevice(Node):
:returns: symbol path (or resource).
"""
return ":/symbols/router.normal.svg"
return ":/symbols/multilayer_switch.normal.svg"
@staticmethod
def hoverSymbol():
@@ -539,7 +563,7 @@ class IOUDevice(Node):
:returns: symbol path (or resource).
"""
return ":/symbols/router.selected.svg"
return ":/symbols/multilayer_switch.selected.svg"
@staticmethod
def symbolName():

389
gns3/pycutext.py Normal file
View File

@@ -0,0 +1,389 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
This module implements a QT4 Python interpreter widget.
It is inspired by PyCute : http://gerard.vermeulen.free.fr
"""
import sys
from .qt import QtCore, QtGui
#from code import InteractiveInterpreter as Interpreter
#===============================================================================
# class MultipleRedirection:
# """ Dummy file which redirects stream to multiple file """
#
# def __init__(self, files):
# """ The stream is redirect to the file list 'files' """
#
# self.files = files
#
# def write(self, str):
# """ Emulate write function """
#
# for f in self.files:
# f.write(str)
#===============================================================================
class PyCutExt(QtGui.QTextEdit):
"""
PyCute is a Python shell for PyQt.
Creating, displaying and controlling PyQt widgets from the Python command
line interpreter is very hard, if not, impossible. PyCute solves this
problem by interfacing the Python interpreter to a PyQt widget.
"""
def __init__(self, interpreter, message="", log="", parent=None):
QtGui.QTextEdit.__init__(self, parent)
self.interpreter = interpreter
self.colorizer = SyntaxColor()
# session log
self.log = log or ''
# to exit the main interpreter by a Ctrl-D if PyCute has no parent
if parent is None:
self.eofKey = QtCore.Qt.Key_D
else:
self.eofKey = None
# capture all interactive input/output
sys.stdout = self
#sys.stderr = MultipleRedirection((sys.stderr, self))
sys.stdin = self
# last line + last incomplete lines
self.line = ""
self.lines = []
# the cursor position in the last line
self.point = 0
# flag: the interpreter needs more input to run the last lines.
self.more = 0
# flag: readline() is being used for e.g. raw_input() and input()
self.reading = 0
# history
self.history = []
self.pointer = 0
self.cursor_pos = 0
self.setLineWrapMode(QtGui.QTextEdit.NoWrap)
try:
sys.ps1
except AttributeError:
sys.ps1 = ">>> "
try:
sys.ps2
except AttributeError:
sys.ps2 = "... "
self.write(message + '\n\n')
self.write(sys.ps1)
def get_interpreter(self):
"""
Return the interpreter object
"""
return self.interpreter
def moveCursor(self, operation, mode=QtGui.QTextCursor.MoveAnchor):
"""
Convenience function to move the cursor
This function will be present in PyQT4.2
"""
cursor = self.textCursor()
cursor.movePosition(operation, mode)
self.setTextCursor(cursor)
def flush(self):
"""
Simulate stdin, stdout, and stderr.
"""
pass
def isatty(self):
"""
Simulate stdin, stdout, and stderr.
"""
return 1
def readline(self):
"""
Simulate stdin, stdout, and stderr.
"""
self.reading = 1
self._clearLine()
self.moveCursor(QtGui.QTextCursor.End)
while self.reading:
QtGui.QApplication.processEvents(QtCore.QEventLoop.AllEvents, 1000)
if len(self.line) == 0:
return '\n'
else:
return self.line
def write(self, text):
"""
Simulates stdin, stdout, and stderr.
"""
cursor = self.textCursor()
cursor.movePosition(QtGui.QTextCursor.End)
pos1 = cursor.position()
cursor.insertText(text)
self.cursor_pos = cursor.position()
self.setTextCursor(cursor)
self.ensureCursorVisible()
# Set the format
cursor.setPosition(pos1, QtGui.QTextCursor.KeepAnchor)
char_format = cursor.charFormat()
char_format.setForeground(QtGui.QBrush(QtGui.QColor(0, 0, 0)))
cursor.setCharFormat(char_format)
def writelines(self, text):
"""
Simulate stdin, stdout, and stderr.
"""
map(self.write, text)
def _run(self):
"""
Append the last line to the history list, let the interpreter execute
the last line(s), and clean up accounting for the interpreter results:
(1) the interpreter succeeds
(2) the interpreter fails, finds no errors and wants more line(s)
(3) the interpreter fails, finds errors and writes them to sys.stderr
"""
self.pointer = 0
self.history.append(self.line)
try:
self.lines.append(self.line)
except Exception as e:
print(e)
source = '\n'.join(self.lines)
self.more = self.interpreter.runsource(source)
if self.more:
self.write(sys.ps2)
else:
self.write(sys.ps1)
self.lines = []
self._clearLine()
def _clearLine(self):
"""
Clear input line buffer
"""
self.line = ""
self.point = 0
def _insertText(self, text):
"""
Inserts text at the current cursor position.
"""
self.line = self.line[:self.point] + text + self.line[self.point:]
self.point += len(text)
cursor = self.textCursor()
cursor.insertText(text)
self.color_line()
def keyPressEvent(self, e):
"""
Handle user input a key at a time.
"""
text = e.text()
key = e.key()
# Keep the cursor after the last prompt.
self.moveCursor(QtGui.QTextCursor.End)
if key == QtCore.Qt.Key_Backspace:
if self.point:
cursor = self.textCursor()
cursor.movePosition(QtGui.QTextCursor.PreviousCharacter, QtGui.QTextCursor.KeepAnchor)
cursor.removeSelectedText()
self.color_line()
self.point -= 1
self.line = self.line[:-1]
elif key == QtCore.Qt.Key_Delete:
cursor = self.textCursor()
cursor.movePosition(QtGui.QTextCursor.NextCharacter, QtGui.QTextCursor.KeepAnchor)
cursor.removeSelectedText()
self.color_line()
self.line = self.line[:-1]
elif key == QtCore.Qt.Key_Return or key == QtCore.Qt.Key_Enter:
self.write("\n")
if self.reading:
self.reading = 0
else:
self._run()
elif key == QtCore.Qt.Key_Tab:
self.onKeyPress_Tab()
elif key == QtCore.Qt.Key_Left:
if self.point:
self.moveCursor(QtGui.QTextCursor.Left)
self.point -= 1
elif key == QtCore.Qt.Key_Right:
if self.point < len(self.line):
self.moveCursor(QtGui.QTextCursor.Right)
self.point += 1
elif key == QtCore.Qt.Key_Home:
cursor = self.textCursor()
cursor.setPosition(self.cursor_pos)
self.setTextCursor(cursor)
self.point = 0
elif key == QtCore.Qt.Key_End:
self.moveCursor(QtGui.QTextCursor.EndOfLine)
self.point = len(self.line)
elif key == QtCore.Qt.Key_Up:
if len(self.history):
if self.pointer == 0:
self.pointer = len(self.history)
self.pointer -= 1
self._recall()
elif key == QtCore.Qt.Key_Down:
if len(self.history):
self.pointer += 1
if self.pointer == len(self.history):
self.pointer = 0
self._recall()
elif len(text):
self._insertText(text)
return
else:
e.ignore()
def _recall(self):
"""
Display the current item from the command history.
"""
cursor = self.textCursor()
cursor.select(QtGui.QTextCursor.LineUnderCursor)
cursor.removeSelectedText()
if self.more:
self.write(sys.ps2)
else:
self.write(sys.ps1)
self._clearLine()
self._insertText(self.history[self.pointer])
def contentsContextMenuEvent(self, ev):
"""
Suppress the right button context menu.
"""
return
def color_line(self):
"""
Color the current line.
"""
cursor = self.textCursor()
cursor.movePosition(QtGui.QTextCursor.StartOfLine)
newpos = cursor.position()
pos = -1
while(newpos != pos):
cursor.movePosition(QtGui.QTextCursor.NextWord)
pos = newpos
newpos = cursor.position()
cursor.select(QtGui.QTextCursor.WordUnderCursor)
word = cursor.selectedText()
if not word:
continue
(R, G, B) = self.colorizer.get_color(word)
char_format = cursor.charFormat()
char_format.setForeground(QtGui.QBrush(QtGui.QColor(R, G, B)))
cursor.setCharFormat(char_format)
class SyntaxColor(object):
"""
Allows to color python keywords.
"""
keywords = set(["and", "del", "from", "not", "while",
"as", "elif", "global", "or", "with",
"assert", "else", "if", "pass", "yield",
"break", "except", "import", "print",
"class", "exec", "in", "raise",
"continue", "finally", "is", "return",
"def", "for", "lambda", "try"])
def get_color(self, word):
""" Return a color tuple (R,G,B) depending of the string word """
stripped = word.strip()
if(stripped in self.keywords):
return (165, 42, 42) # brown
elif(self.is_python_string(stripped)):
return (61, 120, 9) # dark green
else:
return (0, 0, 0)
def is_python_string(self, string):
"""
Return True if string is enclosed by a string mark
"""
# return (
# (string.startswith("'''") and string.endswith("'''")) or
# (string.startswith('"""') and string.endswith('"""')) or
# (string.startswith("'") and string.endswith("'")) or
# (string.startswith('"') and string.endswith('"'))
# )
return False

View File

@@ -45,7 +45,7 @@ if sys.platform.startswith("win") and "PROGRAMFILES(X86)" in os.environ and os.p
'Xshell 4': 'C:\Program Files (x86)\\NetSarang\\Xshell 4\\xshell.exe -url telnet://%h:%p'}
# default Windows 64-bit Telnet console command
if os.path.exists(os.getcwdu() + os.sep + "SuperPutty.exe"):
if os.path.exists(os.getcwd() + os.sep + "SuperPutty.exe"):
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["SuperPutty"]
else:
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Putty (included with GNS3)"]
@@ -60,7 +60,7 @@ elif sys.platform.startswith("win"):
'Xshell 4': 'C:\Program Files\\NetSarang\\Xshell 4\\xshell.exe -url telnet://%h:%p'}
# default Windows 32-bit Telnet console command
if os.path.exists(os.getcwdu() + os.sep + "SuperPutty.exe"):
if os.path.exists(os.getcwd() + os.sep + "SuperPutty.exe"):
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["SuperPutty"]
else:
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Putty (included with GNS3)"]
@@ -97,7 +97,7 @@ if sys.platform.startswith("win"):
'SuperPutty': 'SuperPutty.exe -serial "%s -wt \"%d\" -gns3 5 -skin 4"'}
# default Windows serial console command
if os.path.exists(os.getcwdu() + os.sep + "SuperPutty.exe"):
if os.path.exists(os.getcwd() + os.sep + "SuperPutty.exe"):
DEFAULT_SERIAL_CONSOLE_COMMAND = PRECONFIGURED_SERIAL_CONSOLE_COMMANDS["SuperPutty"]
else:
DEFAULT_SERIAL_CONSOLE_COMMAND = PRECONFIGURED_SERIAL_CONSOLE_COMMANDS["Putty (included with GNS3)"]

View File

@@ -343,7 +343,7 @@ background-none;
<number>0</number>
</property>
<item>
<widget class="QTextEdit" name="uiConsoleTextEdit">
<widget class="ConsoleView" name="uiConsoleTextEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
@@ -1147,6 +1147,11 @@ background-none;
<extends>QTreeWidget</extends>
<header>..nodes_view.h</header>
</customwidget>
<customwidget>
<class>ConsoleView</class>
<extends>QTextEdit</extends>
<header>..console_view.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>uiGraphicsView</tabstop>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/workspace/git/gns3-gui/gns3/ui/main_window.ui'
#
# Created: Thu Feb 13 15:42:20 2014
# Created: Tue Mar 11 18:26:17 2014
# by: PyQt4 UI code generator 4.10
#
# WARNING! All changes made in this file will be lost!
@@ -141,7 +141,7 @@ class Ui_MainWindow(object):
self.vboxlayout1.setSpacing(0)
self.vboxlayout1.setMargin(0)
self.vboxlayout1.setObjectName(_fromUtf8("vboxlayout1"))
self.uiConsoleTextEdit = QtGui.QTextEdit(self.uiConsoleDockWidgetContents)
self.uiConsoleTextEdit = ConsoleView(self.uiConsoleDockWidgetContents)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@@ -654,6 +654,7 @@ class Ui_MainWindow(object):
self.uiLabInstructionsAction.setText(_translate("MainWindow", "Lab instructions", None))
self.uiFitInViewAction.setText(_translate("MainWindow", "Fit in view", None))
from ..console_view import ConsoleView
from ..nodes_view import NodesView
from ..graphics_view import GraphicsView
from . import resources_rc

View File

@@ -25,5 +25,5 @@ or negative for a release candidate or beta (after the base version
number has been incremented)
"""
__version__ = "0.1.dev"
__version_info__ = (0, 1, 0, -99)
__version__ = "0.9.dev"
__version_info__ = (0, 9, 0, -99)