Allow to set a custom console per node

This require some refactoring:
* all VM inherit of the same dump and load common code
* move some code for opening a console from graphic views to the VM

Fix #675
This commit is contained in:
Julien Duponchelle
2016-02-05 19:00:53 +01:00
parent ddd6de24ce
commit 4d2a4f3433
17 changed files with 101739 additions and 100620 deletions

View File

@@ -41,6 +41,7 @@ from .dialogs.style_editor_dialog import StyleEditorDialog
from .dialogs.text_editor_dialog import TextEditorDialog
from .dialogs.symbol_selection_dialog import SymbolSelectionDialog
from .dialogs.idlepc_dialog import IdlePCDialog
from .dialogs.console_command_dialog import ConsoleCommandDialog
from .local_config import LocalConfig
from .progress import Progress
from .utils.server_select import server_select
@@ -760,6 +761,12 @@ class GraphicsView(QtWidgets.QGraphicsView):
console_action.triggered.connect(self.consoleActionSlot)
menu.addAction(console_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "console"), items)):
console_edit_action = QtWidgets.QAction("Edit console", menu)
console_edit_action.setIcon(QtGui.QIcon(':/icons/console_edit.svg'))
console_edit_action.triggered.connect(self.consoleEditActionSlot)
menu.addAction(console_edit_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "auxConsole"), items)):
aux_console_action = QtWidgets.QAction("Auxiliary console", menu)
aux_console_action.setIcon(QtGui.QIcon(':/icons/aux-console.svg'))
@@ -1006,38 +1013,11 @@ class GraphicsView(QtWidgets.QGraphicsView):
# returns True to ignore this node.
return True
if hasattr(node, "serialConsole") and node.serialConsole():
try:
from .serial_console import serialConsole
serialConsole(node.name(), node.serialPipe())
except (OSError, ValueError) as e:
QtWidgets.QMessageBox.critical(self, "Console", "Cannot start serial console application: {}".format(e))
return False
else:
name = node.name()
if aux:
console_port = node.auxConsole()
if console_port is None:
QtWidgets.QMessageBox.critical(self, "Console", "AUX console port not allocated for {}".format(name))
return False
else:
console_port = node.console()
console_type = "telnet"
if "console_type" in node.settings():
console_type = node.settings()["console_type"]
try:
from .telnet_console import nodeTelnetConsole
from .vnc_console import vncConsole
if console_type == "telnet":
nodeTelnetConsole(name, node.server(), console_port)
elif console_type == "vnc":
vncConsole(node.server().host(), console_port)
except (OSError, ValueError) as e:
QtWidgets.QMessageBox.critical(self, "Console", "Cannot start console application: {}".format(e))
return False
try:
node.openConsole(aux)
except (OSError, ValueError) as e:
QtWidgets.QMessageBox.critical(self, "Console", "Cannot start console application: {}".format(e))
return False
return True
def consoleFromItems(self, items):
@@ -1069,6 +1049,25 @@ class GraphicsView(QtWidgets.QGraphicsView):
self.consoleFromItems(self.scene().selectedItems())
def consoleEditActionSlot(self):
"""
Allow user to use a custom console for this VM
"""
current_cmd = None
console_type = "telnet"
for item in self.scene().selectedItems():
if isinstance(item, NodeItem) and hasattr(item.node(), "console"):
current_cmd = item.node().consoleCommand()
console_type = item.node().consoleType()
(ok, cmd) = ConsoleCommandDialog.getCommand(self, console_type=console_type, current=current_cmd)
if ok:
for item in self.scene().selectedItems():
if isinstance(item, NodeItem) and hasattr(item.node(), "console"):
node = item.node()
node.setCustomConsoleCommand(cmd)
def auxConsoleFromItems(self, items):
"""
Aux console from scene items.

View File

@@ -289,33 +289,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._project = project
self._setCurrentFile(project.topologyFile())
def telnetConsoleCommand(self):
"""
Returns the Telnet console command line.
:returns: command (string)
"""
return self._settings["telnet_console_command"]
def serialConsoleCommand(self):
"""
Returns the Serial console command line.
:returns: command (string)
"""
return self._settings["serial_console_command"]
def vncConsoleCommand(self):
"""
Returns the VNC command line.
:returns: command (string)
"""
return self._settings["vnc_console_command"]
def setUnsavedState(self):
"""
Sets the project in a unsaved state.

View File

@@ -663,13 +663,9 @@ class Router(VM):
:returns: representation of the node (dictionary)
"""
router = {"id": self.id(),
"vm_id": self._vm_id,
"dynamips_id": self._dynamips_id,
"type": self.__class__.__name__,
"description": str(self),
"properties": {},
"server_id": self._server.id()}
router = super().dump()
router["vm_id"] = self._vm_id,
router["dynamips_id"] = self._dynamips_id
# add the properties
for name, value in self._settings.items():
@@ -692,6 +688,8 @@ class Router(VM):
:param node_info: representation of the node (dictionary)
"""
super().load(node_info)
# for backward compatibility
vm_id = dynamips_id = node_info.get("router_id")
if not vm_id:

View File

@@ -396,12 +396,8 @@ class IOUDevice(VM):
:returns: representation of the node (dictionary)
"""
iou = {"id": self.id(),
"vm_id": self._vm_id,
"type": self.__class__.__name__,
"description": str(self),
"properties": {},
"server_id": self._server.id()}
iou = super().dump()
iou["vm_id"] = self._vm_id
# add the properties
for name, value in self._settings.items():
@@ -424,6 +420,8 @@ class IOUDevice(VM):
:param node_info: representation of the node (dictionary)
"""
super().load(node_info)
# for backward compatibility
vm_id = node_info.get("iou_id")
if not vm_id:

View File

@@ -296,14 +296,10 @@ class QemuVM(VM):
:returns: representation of the node (dictionary)
"""
qemu_vm = {"id": self.id(),
"vm_id": self._vm_id,
"linked_clone": self._linked_clone,
"type": self.__class__.__name__,
"description": str(self),
"properties": {},
"port_name_format": self._port_name_format,
"server_id": self._server.id()}
qemu_vm = super().dump()
qemu_vm["vm_id"] = self._vm_id,
qemu_vm["linked_clone"] = self._linked_clone
qemu_vm["port_name_format"] = self._port_name_format
if self._port_segment_size:
qemu_vm["port_segment_size"] = self._port_segment_size
@@ -369,6 +365,7 @@ class QemuVM(VM):
:param node_info: representation of the node (dictionary)
"""
super().load(node_info)
# for backward compatibility
vm_id = node_info.get("qemu_id")
if not vm_id:

View File

@@ -356,14 +356,10 @@ class VirtualBoxVM(VM):
:returns: representation of the node (dictionary)
"""
vbox_vm = {"id": self.id(),
"vm_id": self._vm_id,
"linked_clone": self._linked_clone,
"type": self.__class__.__name__,
"description": str(self),
"properties": {},
"port_name_format": self._port_name_format,
"server_id": self._server.id()}
vbox_vm = super().dump()
vbox_vm["vm_id"] = self._vm_id
vbox_vm["linked_clone"] = self._linked_clone
vbox_vm["port_name_format"] = self._port_name_format
if self._port_segment_size:
vbox_vm["port_segment_size"] = self._port_segment_size
@@ -391,6 +387,8 @@ class VirtualBoxVM(VM):
:param node_info: representation of the node (dictionary)
"""
super().load(node_info)
# for backward compatibility
vm_id = node_info.get("vbox_id")
if not vm_id:

View File

@@ -361,14 +361,10 @@ class VMwareVM(VM):
:returns: representation of the node (dictionary)
"""
vmware_vm = {"id": self.id(),
"vm_id": self._vm_id,
"linked_clone": self._linked_clone,
"type": self.__class__.__name__,
"description": str(self),
"properties": {},
"port_name_format": self._port_name_format,
"server_id": self._server.id()}
vmware_vm = super().dump()
vmware_vm["vm_id"] = self._vm_id
vmware_vm["linked_clone"] = self._linked_clone
vmware_vm["port_name_format"] = self._port_name_format
if self._port_segment_size:
vmware_vm["port_segment_size"] = self._port_segment_size
@@ -396,6 +392,8 @@ class VMwareVM(VM):
:param node_info: representation of the node (dictionary)
"""
super().load()
vm_id = node_info["vm_id"]
linked_clone = node_info.get("linked_clone", False)
port_name_format = node_info.get("port_name_format", "Ethernet{0}")

View File

@@ -218,12 +218,8 @@ class VPCSDevice(VM):
:returns: representation of the node (dictionary)
"""
vpcs_device = {"id": self.id(),
"vm_id": self._vm_id,
"type": self.__class__.__name__,
"description": str(self),
"properties": {},
"server_id": self._server.id()}
vpcs_device = super().dump()
vpcs_device["vm_id"] = self._vm_id
# add the properties
for name, value in self._settings.items():
@@ -249,6 +245,7 @@ class VPCSDevice(VM):
:param node_info: representation of the node (dictionary)
"""
super().load(node_info)
# for backward compatibility
vm_id = node_info.get("vpcs_id")
if not vm_id:

View File

@@ -290,6 +290,7 @@
"x": { "type": "number" },
"y": { "type": "number" },
"z": { "type": "number" },
"custom_console_command": { "$ref": "#/definitions/mandatory_string" },
"properties": {
"type": "object",
"oneOf": [

View File

@@ -28,10 +28,8 @@ from .main_window import MainWindow
import logging
log = logging.getLogger(__name__)
# TODO: support more than just Vbox (Qemu maybe?)
def serialConsole(vmname, pipe_path):
def serialConsole(vmname, pipe_path, command):
"""
:param vmname: Virtual machine name.
:param pipe_path: Virtual machine serial pipe path.
@@ -39,10 +37,6 @@ def serialConsole(vmname, pipe_path):
Start a Serial console program.
"""
command = MainWindow.instance().serialConsoleCommand()
if not command:
return
# replace the place-holders by the actual values
command = command.replace("%s", pipe_path)
command = command.replace("%d", vmname)

View File

@@ -84,19 +84,16 @@ class ConsoleThread(QtCore.QThread):
console_mutex.unlock()
def nodeTelnetConsole(name, server, port):
def nodeTelnetConsole(name, server, port, command):
"""
Start a Telnet console program for a topology node.
:param name: Name of the console
:param port: Port number of the console on remote host
:param server: Server where the console is running
:param command: Console command
"""
command = MainWindow.instance().telnetConsoleCommand()
if not command:
return
log.info('Starting telnet console in thread "{}"'.format(command))
console_thread = ConsoleThread(MainWindow.instance(), command, name, server, port)
console_thread.consoleError.connect(_consoleErrorSlot)
@@ -117,7 +114,8 @@ def telnetConsole(name, host, port):
:param server: Server where the console is running
"""
command = MainWindow.instance().telnetConsoleCommand()
general_settings = MainWindow.instance().settings()
command = general_settings["telnet_console_command"]
if not command:
return

File diff suppressed because it is too large Load Diff

View File

@@ -37,6 +37,46 @@ class VM(Node):
self._vm_id = None
self._vm_directory = None
self._command_line = None
self._custom_console_command = None
def consoleCommand(self):
"""
:returns: The console command for this host
"""
if self._custom_console_command:
return self._custom_console_command
else:
from .main_window import MainWindow
general_settings = MainWindow.instance().settings()
console_type = self.consoleType()
if console_type == "serial":
return general_settings["serial_console_command"]
elif console_type == "vnc":
return general_settings["vnc_console_command"]
return general_settings["telnet_console_command"]
def setCustomConsoleCommand(self, console_command):
"""
Set custom console command for this node
"""
console_command = console_command.strip()
if console_command == '':
self._custom_console_command = None
else:
self._custom_console_command = console_command
def consoleType(self):
"""
Get the console type (serial, telnet or VNC)
"""
console_type = "telnet"
if hasattr(self, "serialConsole") and self.serialConsole():
return "serial"
if "console_type" in self.settings():
return self.settings()["console_type"]
return console_type
def vm_id(self):
"""
@@ -301,3 +341,57 @@ class VM(Node):
self.error_signal.emit(self.id(), "Invalid configuration file {}: {}".format(config_path, e))
return None
return ""
def dump(self):
"""
Returns a representation of this device.
(to be saved in a topology file).
:returns: representation of the node (dictionary)
"""
device = {
"id": self.id(),
"type": self.__class__.__name__,
"description": str(self),
"properties": {},
"server_id": self._server.id()
}
if self._custom_console_command is not None:
device["custom_console_command"] = self._custom_console_command
return device
def load(self, node_info):
"""
Loads a device representation
(from a topology file).
:param node_info: representation of the node (dictionary)
"""
if "custom_console_command" in node_info:
self._custom_console_command = node_info["custom_console_command"]
def openConsole(self, aux):
if hasattr(self, "serialConsole") and self.serialConsole():
from .serial_console import serialConsole
serialConsole(self.name(), self.serialPipe(), self.consoleCommand())
if aux:
console_port = self.auxConsole()
if console_port is None:
raise ValueError("AUX console port not allocated for {}".format(self.name()))
else:
console_port = self.console()
console_type = "telnet"
if "console_type" in self.settings():
console_type = self.settings()["console_type"]
if console_type == "telnet":
from .telnet_console import nodeTelnetConsole
nodeTelnetConsole(self.name(), self.server(), console_port, self.consoleCommand())
elif console_type == "vnc":
from .vnc_console import vncConsole
vncConsole(self.server().host(), console_port, self.consoleCommand())

View File

@@ -29,7 +29,7 @@ import logging
log = logging.getLogger(__name__)
def vncConsole(host, port):
def vncConsole(host, port, command):
"""
Start a VNC console program.
@@ -37,10 +37,6 @@ def vncConsole(host, port):
:param port: port number
"""
command = MainWindow.instance().vncConsoleCommand()
if not command:
return
# replace the place-holders by the actual values
command = command.replace("%h", host)
command = command.replace("%p", str(port))

View File

@@ -0,0 +1,700 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48px"
height="48px"
id="svg1306"
sodipodi:version="0.32"
inkscape:version="0.91 r13725"
sodipodi:docname="console_edit.svg"
inkscape:export-filename="/home/andreas/projekt/bild/tango/terminal4.png"
inkscape:export-xdpi="240.00000"
inkscape:export-ydpi="240.00000"
version="1.1">
<defs
id="defs1308">
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5060"
id="radialGradient5031"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
cx="605.71429"
cy="486.64789"
fx="605.71429"
fy="486.64789"
r="117.14286" />
<linearGradient
inkscape:collect="always"
id="linearGradient5060">
<stop
style="stop-color:black;stop-opacity:1;"
offset="0"
id="stop5062" />
<stop
style="stop-color:black;stop-opacity:0;"
offset="1"
id="stop5064" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5060"
id="radialGradient5029"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
cx="605.71429"
cy="486.64789"
fx="605.71429"
fy="486.64789"
r="117.14286" />
<linearGradient
id="linearGradient5048">
<stop
style="stop-color:black;stop-opacity:0;"
offset="0"
id="stop5050" />
<stop
id="stop5056"
offset="0.5"
style="stop-color:black;stop-opacity:1;" />
<stop
style="stop-color:black;stop-opacity:0;"
offset="1"
id="stop5052" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5048"
id="linearGradient5027"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
x1="302.85715"
y1="366.64789"
x2="302.85715"
y2="609.50507" />
<linearGradient
inkscape:collect="always"
id="linearGradient6447">
<stop
style="stop-color:#777973;stop-opacity:1;"
offset="0"
id="stop6449" />
<stop
style="stop-color:#777973;stop-opacity:0;"
offset="1"
id="stop6451" />
</linearGradient>
<linearGradient
id="linearGradient4254">
<stop
style="stop-color:#616161;stop-opacity:1.0000000;"
offset="0.0000000"
id="stop4256" />
<stop
style="stop-color:#a0a0a0;stop-opacity:1.0000000;"
offset="1.0000000"
id="stop4258" />
</linearGradient>
<linearGradient
id="linearGradient5176">
<stop
id="stop5178"
offset="0.0000000"
style="stop-color:#a2a59c;stop-opacity:1.0000000;" />
<stop
id="stop5180"
offset="1.0000000"
style="stop-color:#535750;stop-opacity:1.0000000;" />
</linearGradient>
<linearGradient
id="linearGradient2667">
<stop
id="stop2669"
offset="0.0000000"
style="stop-color:#ffffff;stop-opacity:1.0000000;" />
<stop
id="stop2671"
offset="1.0000000"
style="stop-color:#fcfcff;stop-opacity:0.0000000;" />
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
y2="26.729263"
x2="17.199417"
y1="1.6537577"
x1="11.492236"
gradientTransform="matrix(1.236157,0.000000,0.000000,0.896051,-1.081820,2.830699)"
id="linearGradient2673"
xlink:href="#linearGradient2667"
inkscape:collect="always" />
<linearGradient
inkscape:collect="always"
id="linearGradient2238">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop2240" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop2242" />
</linearGradient>
<linearGradient
id="linearGradient2224">
<stop
style="stop-color:#32342f;stop-opacity:0.54639173;"
offset="0.0000000"
id="stop2226" />
<stop
style="stop-color:#32342f;stop-opacity:0;"
offset="1"
id="stop2228" />
</linearGradient>
<linearGradient
id="linearGradient2214">
<stop
style="stop-color:#a9aaa7;stop-opacity:1.0000000;"
offset="0.0000000"
id="stop2216" />
<stop
style="stop-color:#676964;stop-opacity:1.0000000;"
offset="1.0000000"
id="stop2218" />
</linearGradient>
<linearGradient
id="linearGradient2206">
<stop
style="stop-color:#777973;stop-opacity:1.0000000;"
offset="0.0000000"
id="stop2208" />
<stop
style="stop-color:#cbccca;stop-opacity:1.0000000;"
offset="1.0000000"
id="stop2210" />
</linearGradient>
<linearGradient
id="linearGradient2198">
<stop
style="stop-color:#748f48;stop-opacity:1.0000000;"
offset="0.0000000"
id="stop2200" />
<stop
style="stop-color:#1f2816;stop-opacity:1.0000000;"
offset="1.0000000"
id="stop2202" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2198"
id="linearGradient2204"
x1="23.118565"
y1="9.5830288"
x2="22.440805"
y2="34.225887"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.950085,0.000000,0.000000,0.965659,1.243978,0.255342)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2206"
id="linearGradient2212"
x1="29.870447"
y1="32.285740"
x2="24.841814"
y2="14.157946"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.957412,0.000000,0.000000,0.952331,1.022766,0.133307)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5176"
id="linearGradient2220"
x1="8.6529236"
y1="9.5865316"
x2="21.305075"
y2="32.497993"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.957412,0.000000,0.000000,0.952331,1.022766,0.133307)" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2224"
id="radialGradient2230"
cx="24.041630"
cy="42.242130"
fx="24.041630"
fy="42.242130"
r="17.576654"
gradientTransform="matrix(1.000000,0.000000,0.000000,0.304598,-1.841788e-16,29.37527)"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2238"
id="linearGradient2244"
x1="20.338758"
y1="19.636894"
x2="48.845253"
y2="49.730762"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.953506,0.000000,0.000000,0.947873,1.141528,1.205591)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4254"
id="linearGradient4260"
x1="11.048059"
y1="9.1463490"
x2="26.178129"
y2="30.343304"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.997583,0.000000,0.000000,0.989941,0.104141,7.028871e-2)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2214"
id="linearGradient5719"
x1="40.253334"
y1="42.318577"
x2="36.451904"
y2="37.999615"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.000000,0.000000,0.000000,0.744756,0.000000,9.569132)" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient6447"
id="radialGradient6453"
cx="37.495606"
cy="39.510023"
fx="37.495606"
fy="39.510023"
r="2.5100370"
gradientTransform="matrix(1.000000,0.000000,0.000000,0.737790,0.000000,9.844321)"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient2994">
<stop
id="stop2996"
offset="0"
style="stop-color:#000000;stop-opacity:1;" />
<stop
id="stop2998"
offset="1"
style="stop-color:#c9c9c9;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient2984"
inkscape:collect="always">
<stop
id="stop2986"
offset="0"
style="stop-color:#e7e2b8;stop-opacity:1;" />
<stop
id="stop2988"
offset="1"
style="stop-color:#e7e2b8;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient2974">
<stop
id="stop2976"
offset="0"
style="stop-color:#c1c1c1;stop-opacity:1;" />
<stop
id="stop2978"
offset="1"
style="stop-color:#acacac;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient2966">
<stop
id="stop2968"
offset="0"
style="stop-color:#ffd1d1;stop-opacity:1;" />
<stop
style="stop-color:#ff1d1d;stop-opacity:1;"
offset="0.5"
id="stop3006" />
<stop
id="stop2970"
offset="1"
style="stop-color:#6f0000;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient2919">
<stop
id="stop2921"
offset="0"
style="stop-color:#a3a4a0;stop-opacity:1;" />
<stop
id="stop2923"
offset="1"
style="stop-color:#888a85;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient2873">
<stop
id="stop2875"
offset="0"
style="stop-color:#939393;stop-opacity:1;" />
<stop
id="stop2877"
offset="1"
style="stop-color:#424242;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient2855">
<stop
id="stop2857"
offset="0"
style="stop-color:#dfdfdf;stop-opacity:1;" />
<stop
id="stop2859"
offset="1"
style="stop-color:#ffffff;stop-opacity:1;" />
</linearGradient>
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.000000,0.000000,0.000000,0.348243,0.000000,26.35543)"
r="19.5625"
fy="40.4375"
fx="23.5625"
cy="40.4375"
cx="23.5625"
id="radialGradient2871"
xlink:href="#linearGradient5060"
inkscape:collect="always" />
<linearGradient
gradientTransform="translate(-5.669292,0)"
gradientUnits="userSpaceOnUse"
y2="22.250591"
x2="50.988335"
y1="17.376184"
x1="48.90625"
id="linearGradient2972"
xlink:href="#linearGradient2966"
inkscape:collect="always" />
<linearGradient
gradientTransform="translate(-5.669292,0)"
gradientUnits="userSpaceOnUse"
y2="22.625"
x2="47.6875"
y1="19.8125"
x1="46"
id="linearGradient2980"
xlink:href="#linearGradient2974"
inkscape:collect="always" />
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.923565,0,0,2.029717,-61.55532,-27.88417)"
r="3.2408544"
fy="27.640751"
fx="29.053354"
cy="27.640751"
cx="29.053354"
id="radialGradient2990"
xlink:href="#linearGradient2984"
inkscape:collect="always" />
<linearGradient
gradientTransform="translate(-5.825542,0.125)"
gradientUnits="userSpaceOnUse"
y2="30.703125"
x2="25.514589"
y1="31.046875"
x1="25.71875"
id="linearGradient3000"
xlink:href="#linearGradient2994"
inkscape:collect="always" />
<radialGradient
r="19.5625"
fy="40.4375"
fx="23.5625"
cy="40.4375"
cx="23.5625"
gradientTransform="matrix(1,0,0,0.348243,0,26.35543)"
gradientUnits="userSpaceOnUse"
id="radialGradient3010"
xlink:href="#linearGradient5060"
inkscape:collect="always" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="0.19607843"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="3.3292944"
inkscape:cx="24"
inkscape:cy="-6.995453"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="926"
inkscape:window-height="975"
inkscape:window-x="1769"
inkscape:window-y="6"
showguides="true"
inkscape:guide-bbox="true"
inkscape:showpageshadow="false"
inkscape:window-maximized="0" />
<metadata
id="metadata1311">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<dc:date>2005-10-15</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Andreas Nilsson</dc:title>
</cc:Agent>
</dc:creator>
<dc:subject>
<rdf:Bag>
<rdf:li>terminal</rdf:li>
<rdf:li>emulator</rdf:li>
<rdf:li>term</rdf:li>
<rdf:li>command line</rdf:li>
</rdf:Bag>
</dc:subject>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
<dc:contributor>
<cc:Agent>
<dc:title>Jakub Steiner</dc:title>
</cc:Agent>
</dc:contributor>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
<cc:permits
rdf:resource="http://web.resource.org/cc/Reproduction" />
<cc:permits
rdf:resource="http://web.resource.org/cc/Distribution" />
<cc:requires
rdf:resource="http://web.resource.org/cc/Notice" />
<cc:requires
rdf:resource="http://web.resource.org/cc/Attribution" />
<cc:permits
rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
<cc:requires
rdf:resource="http://web.resource.org/cc/ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<g
id="g5022"
transform="matrix(2.454499e-2,0,0,2.086758e-2,46.14369,39.34109)">
<rect
y="-150.69685"
x="-1559.2523"
height="478.35718"
width="1339.6335"
id="rect4173"
style="opacity:0.40206185;color:black;fill:url(#linearGradient5027);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<path
sodipodi:nodetypes="cccc"
id="path5058"
d="M -219.61876,-150.68038 C -219.61876,-150.68038 -219.61876,327.65041 -219.61876,327.65041 C -76.744594,328.55086 125.78146,220.48075 125.78138,88.454235 C 125.78138,-43.572302 -33.655436,-150.68036 -219.61876,-150.68038 z "
style="opacity:0.40206185;color:black;fill:url(#radialGradient5029);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<path
style="opacity:0.40206185;color:black;fill:url(#radialGradient5031);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M -1559.2523,-150.68038 C -1559.2523,-150.68038 -1559.2523,327.65041 -1559.2523,327.65041 C -1702.1265,328.55086 -1904.6525,220.48075 -1904.6525,88.454235 C -1904.6525,-43.572302 -1745.2157,-150.68036 -1559.2523,-150.68038 z "
id="path5018"
sodipodi:nodetypes="cccc" />
</g>
<rect
style="opacity:1.0000000;fill:url(#linearGradient2212);fill-opacity:1.0000000;fill-rule:evenodd;stroke:url(#linearGradient2220);stroke-width:0.99999946;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000"
id="rect1316"
width="44.996037"
height="38.998734"
x="1.5026338"
y="3.5015533"
rx="4.8517075"
ry="4.8517079" />
<rect
style="opacity:1.0000000;fill:url(#linearGradient2204);fill-opacity:1.0000000;fill-rule:evenodd;stroke:url(#linearGradient4260);stroke-width:0.99495775;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000"
id="rect1314"
width="37.088005"
height="29.022322"
x="5.4962788"
y="7.4827089"
rx="1.6452150"
ry="1.6452144" />
<g
id="g2286"
style="opacity:0.25568182">
<path
id="path1345"
d="M 8.0152033,11.500361 L 39.994145,11.500361"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#181f10;stroke-width:1.00072134;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#181f10;stroke-width:1.00072134;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 8.0152033,13.500361 L 39.994145,13.500361"
id="path2264" />
<path
id="path2266"
d="M 8.0152033,15.500361 L 39.994145,15.500361"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#181f10;stroke-width:1.00072134;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#181f10;stroke-width:1.00072134;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 8.0152033,17.500361 L 39.994145,17.500361"
id="path2268" />
<path
id="path2270"
d="M 8.0152033,19.500361 L 39.994145,19.500361"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#181f10;stroke-width:1.00072134;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#181f10;stroke-width:1.00072134;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 8.0152033,21.500361 L 39.994145,21.500361"
id="path2272" />
<path
id="path2274"
d="M 8.0152033,23.500361 L 39.994145,23.500361"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#181f10;stroke-width:1.00072134;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#181f10;stroke-width:1.00072134;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 8.0152033,25.500361 L 39.994145,25.500361"
id="path2276" />
<path
id="path2278"
d="M 8.0152033,27.500361 L 39.994145,27.500361"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#181f10;stroke-width:1.00072134;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#181f10;stroke-width:1.00072134;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 8.0152033,29.500361 L 39.994145,29.500361"
id="path2280" />
<path
id="path2282"
d="M 8.0152033,31.500361 L 39.994145,31.500361"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#181f10;stroke-width:1.00072134;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#181f10;stroke-width:1.00072134;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 8.0152033,33.500361 L 39.994145,33.500361"
id="path2284" />
</g>
<rect
style="opacity:0.76373626;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient2244);stroke-width:0.99999946;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect2232"
width="42.945141"
height="37.000587"
x="2.5542557"
y="4.5007114"
rx="3.7910469"
ry="3.7910469" />
<path
style="font-size:18.585011px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125.00000%;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1.0000000;stroke:#6ed66e;stroke-width:1.0000000pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.27868852;font-family:Bitstream Vera Sans Mono"
d="M 11.625000,20.679392 L 11.625000,17.625000 L 20.609828,21.685794 L 20.609828,23.541713 L 11.625000,27.629147 L 11.625000,24.583829 L 18.589396,22.729971 L 11.625000,20.679392 z M 30.517635,30.705752 L 30.517635,32.679948 L 19.614229,32.679948 L 19.614229,30.705752 L 30.517635,30.705752"
id="text1340"
sodipodi:nodetypes="ccccccccccccc" />
<path
sodipodi:nodetypes="ccccccc"
style="opacity:0.53142856;fill:url(#linearGradient2673);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 7.625388,8 C 7.102102,8 6.05153,8.190188 6.05153,9.0259761 L 6.16958,25.542519 C 23.841567,24.579133 20.294433,17.286426 42,13.633318 L 41.937264,9.2913791 C 41.859002,8.1662868 41.397947,8.0594548 40.327115,8.066071 L 7.625388,8 z "
id="path2443" />
<rect
style="opacity:0.71428573;fill:none;fill-opacity:1.0000000;fill-rule:evenodd;stroke:#000000;stroke-width:1.9999992;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000"
id="rect1340"
width="34.026031"
height="26.057468"
x="6.9894562"
y="8.9805145"
rx="0.11773217"
ry="0.11773217" />
<rect
style="opacity:1;fill:url(#radialGradient6453);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient5719);stroke-width:1.00000119;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect5025"
width="4.0200734"
height="2.9590063"
x="35.485569"
y="37.514935"
rx="0.35819405"
ry="0.56022596" />
<rect
style="opacity:1;fill:#93d94c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect6458"
width="2"
height="2"
x="32"
y="38"
rx="0.56022543"
ry="0.56022543" />
<path
sodipodi:type="arc"
style="opacity:1;color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="path2300"
sodipodi:cx="28.3125"
sodipodi:cy="38.75"
sodipodi:rx="0.5625"
sodipodi:ry="0.5625"
d="M 28.875 38.75 A 0.5625 0.5625 0 1 1 27.75,38.75 A 0.5625 0.5625 0 1 1 28.875 38.75 z"
transform="translate(4.375000,-6.250000e-2)" />
<g
inkscape:label="Layer 1"
id="layer1-3"
transform="translate(-2.8179398,6.273626)">
<ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.31578944;fill:url(#radialGradient3010);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path3008"
transform="matrix(0.616613,0,0,0.440367,10.61425,13.94266)"
cx="23.5625"
cy="40.4375"
rx="19.5625"
ry="6.8125" />
<path
sodipodi:nodetypes="cccccc"
id="path2960"
d="m 17.34116,32.5 5.625,-5.625 20.093749,-9.75 c 3.25,-1.25 5.1875,3.375 2.3125,5 L 25.34116,31.5 l -8,1 z"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#cb9022;fill-opacity:1;fill-rule:evenodd;stroke:#5c410c;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
inkscape:connector-curvature="0" />
<path
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#linearGradient2972);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
d="m 38.330708,20 c 0,0 1.4375,0.09375 2,1.34375 0.579493,1.287761 0,2.65625 0,2.65625 l 5.03125,-2.46875 c 0,0 1.452032,-0.881367 0.65625,-2.84375 -0.784912,-1.935577 -2.6875,-1.15625 -2.6875,-1.15625 l -5,2.46875 z"
id="path2964"
sodipodi:nodetypes="czcczcc"
inkscape:connector-curvature="0" />
<path
sodipodi:nodetypes="czcczcc"
id="path2962"
d="m 38.330708,20 c 0,0 1.4375,0.09375 2,1.34375 0.579493,1.287761 0,2.65625 0,2.65625 l 2,-1 c 0,0 0.827032,-1.318867 0.21875,-2.6875 C 41.924458,18.90625 40.330708,19 40.330708,19 l -2,1 z"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#linearGradient2980);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
inkscape:connector-curvature="0" />
<path
sodipodi:nodetypes="cccc"
id="path2982"
d="m 18.768208,31.78125 4.5,-4.5 c 1.5,0.8125 2.28125,2.15625 1.875,3.71875 l -6.375,0.78125 z"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient2990);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
inkscape:connector-curvature="0" />
<path
sodipodi:nodetypes="cccc"
id="path2992"
d="m 20.111958,30.375 -1.625,1.59375 2.34375,-0.3125 c 0.21875,-0.71875 -0.1875,-1.0625 -0.71875,-1.28125 z"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#linearGradient3000);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
inkscape:connector-curvature="0" />
<path
sodipodi:nodetypes="ccccc"
id="path3002"
d="m 23.268208,27.25 1.5625,1.25 15.38734,-7.31867 C 39.773616,20.325286 38.976281,20.096733 38.314669,20.019068 L 23.268208,27.25 Z"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#ffffff;fill-opacity:0.36363639;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
inkscape:connector-curvature="0" />
<path
sodipodi:nodetypes="ccccc"
id="path3004"
d="m 25.143208,31.0625 0.1875,-0.75 15.23109,-7.1296 c 0,0 -0.11016,0.613627 -0.215879,0.74935 L 25.143208,31.0625 Z"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#000000;fill-opacity:0.36363639;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -49,6 +49,7 @@
<file>icons/connection-new.svg</file>
<file>icons/connection-new-hover.svg</file>
<file>icons/console.svg</file>
<file>icons/console_edit.svg</file>
<file>icons/aux-console.svg</file>
<file>icons/delete.svg</file>
<file>icons/led_green.svg</file>

View File

@@ -32,6 +32,49 @@ def test_vpcs_device_start(vpcs_device):
assert args[0] == "/vpcs/vms/{vm_id}/start".format(vm_id=vpcs_device.vm_id())
def test_vpcs_dump(vpcs_device):
dump = vpcs_device.dump()
assert dump["id"] == vpcs_device.id()
assert dump["type"] == "VPCSDevice"
assert dump["description"] == "VPCS device"
assert dump["properties"] == {"name": vpcs_device.name()}
assert dump["server_id"] == vpcs_device._server.id()
assert "custom_console_command" not in dump
def test_vpcs_dump_custom_console(vpcs_device):
vpcs_device.setCustomConsoleCommand("/bin/fake")
dump = vpcs_device.dump()
assert dump["id"] == vpcs_device.id()
assert dump["type"] == "VPCSDevice"
assert dump["description"] == "VPCS device"
assert dump["properties"] == {"name": vpcs_device.name()}
assert dump["server_id"] == vpcs_device._server.id()
assert dump["custom_console_command"] == "/bin/fake"
def test_vpcs_dump_custom_console(vpcs_device):
vpcs_device.setCustomConsoleCommand("/bin/fake")
dump = vpcs_device.dump()
assert dump["id"] == vpcs_device.id()
assert dump["type"] == "VPCSDevice"
assert dump["description"] == "VPCS device"
assert dump["properties"] == {"name": vpcs_device.name()}
assert dump["server_id"] == vpcs_device._server.id()
assert dump["custom_console_command"] == "/bin/fake"
def test_vpcs_load(vpcs_device):
dump = vpcs_device.dump()
dump["custom_console_command"] = "/bin/test"
vpcs_device.load(dump)
assert vpcs_device.consoleCommand() == "/bin/test"
def test_vpcs_device_stop(vpcs_device):
with patch('gns3.node.Node.httpPost') as mock: