Merge branch 'master' into 1.5

This commit is contained in:
Julien Duponchelle
2016-02-10 18:47:56 +01:00
29 changed files with 137787 additions and 136508 deletions

View File

@@ -0,0 +1,135 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2016 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
import copy
from gns3.qt import QtWidgets
from gns3.local_config import LocalConfig
from gns3.ui.console_command_dialog_ui import Ui_uiConsoleCommandDialog
from gns3.settings import PRECONFIGURED_TELNET_CONSOLE_COMMANDS, \
PRECONFIGURED_SERIAL_CONSOLE_COMMANDS, \
PRECONFIGURED_VNC_CONSOLE_COMMANDS, \
CUSTOM_CONSOLE_COMMANDS_SETTINGS
import logging
log = logging.getLogger(__name__)
class ConsoleCommandDialog(QtWidgets.QDialog, Ui_uiConsoleCommandDialog):
"""
This dialog allow user to select the command used to start a
console.
"""
def __init__(self, parent, console_type="telnet", current=None):
"""
:params console_type: telnet, serial or vnc
:params current: Current console command
"""
super().__init__(parent)
self.setupUi(self)
self._console_type = console_type
self._current = current
self._settings = LocalConfig.instance().loadSectionSettings("CustomConsoleCommands", CUSTOM_CONSOLE_COMMANDS_SETTINGS)
self.uiCommandComboBox.currentIndexChanged.connect(self.commandComboBoxCurrentIndexChangedSlot)
self.uiCommandPlainTextEdit.textChanged.connect(self.textChangedSlot)
self.uiSavePushButton.clicked.connect(self.savePushButtonClickedSlot)
self.uiRemovePushButton.clicked.connect(self.removePushButtonClickedSlot)
self._refreshList()
def _refreshList(self):
if self._console_type == "telnet":
self._consoles = copy.copy(PRECONFIGURED_TELNET_CONSOLE_COMMANDS)
self._consoles.update(self._settings[self._console_type])
elif self._console_type == "vnc":
self._consoles = copy.copy(PRECONFIGURED_VNC_CONSOLE_COMMANDS)
self._consoles.update(self._settings[self._console_type])
else:
self._consoles = copy.copy(PRECONFIGURED_SERIAL_CONSOLE_COMMANDS)
self._consoles.update(self._settings[self._console_type])
self.uiCommandComboBox.clear()
self.uiCommandComboBox.addItem("Custom", "")
for name, cmd in sorted(self._consoles.items(), key=(lambda item: item[0].lower())):
self.uiCommandComboBox.addItem(name, cmd)
if self._current:
self.uiCommandPlainTextEdit.setPlainText(self._current)
else:
self.uiCommandComboBox.setCurrentIndex(1)
def removePushButtonClickedSlot(self):
"""
Remove the custom command from the custom list
"""
self._settings[self._console_type].pop(self.uiCommandComboBox.currentText())
LocalConfig.instance().saveSectionSettings("CustomConsoleCommands", self._settings)
self._current = None
self._refreshList()
def savePushButtonClickedSlot(self):
"""
Save a custom command to the list
"""
name, ok = QtWidgets.QInputDialog.getText(self, "Add a command", "Command name:", QtWidgets.QLineEdit.Normal)
command = self.uiCommandPlainTextEdit.toPlainText().strip()
if ok and len(command) > 0:
if command not in self._consoles.values():
self._settings[self._console_type][name] = command
self._current = command
LocalConfig.instance().saveSectionSettings("CustomConsoleCommands", self._settings)
self._refreshList()
def textChangedSlot(self):
index = self.uiCommandComboBox.findData(self.uiCommandPlainTextEdit.toPlainText())
if index == -1:
index = 0
self.uiCommandComboBox.setCurrentIndex(index)
def commandComboBoxCurrentIndexChangedSlot(self, index):
self.uiRemovePushButton.hide()
# Ignore custom command
if index != 0:
self.uiCommandPlainTextEdit.setPlainText(self.uiCommandComboBox.currentData())
self.uiSavePushButton.hide()
if self.uiCommandComboBox.currentText() in self._settings[self._console_type].keys():
self.uiRemovePushButton.show()
else:
self.uiSavePushButton.show()
@staticmethod
def getCommand(parent, console_type="telnet", current=None):
dialog = ConsoleCommandDialog(parent, console_type=console_type, current=current)
dialog.show()
if dialog.exec_():
return (True, dialog.uiCommandPlainTextEdit.toPlainText().replace("\n", " "))
return (False, None)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
main = QtWidgets.QMainWindow()
(ok, command) = ConsoleCommandDialog.getCommand(main, console_type="telnet", current=list(PRECONFIGURED_TELNET_CONSOLE_COMMANDS.items())[0][1])
print(ok)
print(command)

View File

@@ -167,6 +167,19 @@ class DoctorDialog(QtWidgets.QDialog, Ui_DoctorDialog):
return(2, "Dynamips require CAP_NET_RAW. Run sudo setcap cap_net_raw,cap_net_admin+eip {path}".format(path=path))
return (0, None)
def checkGNS3InstalledTwice(self):
"""Check if gns3 is not installed twice"""
if not sys.platform.startswith("win"):
return (0, None)
try:
if os.path.exists("/usr/local/bin/gns3server") and os.path.exists("/usr/bin/gns3server"):
return(2, "GNS3 is installed twice please remove it from /usr/local/bin")
except OSError:
pass
return (0, None)
if __name__ == '__main__':
import sys

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

@@ -186,6 +186,8 @@ class NodeItem():
when a the node has been updated.
"""
if self is None:
return
if self._node_label:
if self._node_label.toPlainText() != self._node.name():
self._node_label.setPlainText(self._node.name())

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

@@ -200,14 +200,9 @@ class DockerVM(VM):
:returns: representation of the node (dictionary)
"""
container = {
"id": self.id(),
"vm_id": self._vm_id,
"type": self.__class__.__name__,
"description": str(self),
"properties": {},
"server_id": self._server.id()
}
docker = super().dump()
docker["id"] = self.id()
docker["vm_id"] = self._vm_id
# add the properties
for name, value in self._settings.items():
@@ -259,13 +254,13 @@ class DockerVM(VM):
:param node_info: representation of the node (dictionary)
"""
super().load(node_info)
settings = node_info["properties"]
name = settings.pop("name")
image = settings.pop("image")
log.info("Docker container {} is loading".format(name))
self._loading = True
self._node_info = node_info
self.loaded_signal.connect(self._updatePortSettings)
self.setup(image, name=name, additional_settings=settings)
def _updatePortSettings(self):

View File

@@ -340,10 +340,8 @@ class Router(VM):
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
return
if not super()._updateCallback(result, error=error, **kwargs):
return False
updated = False
for name, value in result.items():
@@ -599,25 +597,15 @@ 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():
if value is not None and value != "":
router["properties"][name] = value
# add the ports
if self._ports:
ports = router["ports"] = []
for port in self._ports:
ports.append(port.dump())
return router
def load(self, node_info):
@@ -628,6 +616,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:
@@ -657,35 +647,8 @@ class Router(VM):
log.info("router {} is loading".format(name))
self.setName(name)
self._loading = True
self._node_info = node_info
self.loaded_signal.connect(self._updatePortSettings)
self.setup(image, ram, name, vm_id, dynamips_id, vm_settings)
def _updatePortSettings(self):
"""
Updates port settings when loading a topology.
"""
self.loaded_signal.disconnect(self._updatePortSettings)
# update the port with the correct names and IDs
if "ports" in self._node_info:
ports = self._node_info["ports"]
for topology_port in ports:
for port in self._ports:
if topology_port["port_number"] == port.portNumber() and (topology_port.get("adapter_number", None) == port.adapterNumber() or topology_port.get("slot_number", None) == port.adapterNumber()):
port.setName(topology_port["name"])
port.setId(topology_port["id"])
# now we can set the node as initialized and trigger the created signal
self.setInitialized(True)
log.info("router {} has been loaded".format(self.name()))
self.created_signal.emit(self.id())
self._module.addNode(self)
self._loading = False
self._node_info = None
def saveConfig(self):
"""
Save the configs

View File

@@ -241,10 +241,8 @@ class IOUDevice(VM):
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
return
if not super()._updateCallback(result, error=error, **kwargs):
return False
updated = False
nb_adapters_changed = False
@@ -323,24 +321,14 @@ 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():
if value is not None and value != "":
iou["properties"][name] = value
# add the ports
if self._ports:
ports = iou["ports"] = []
for port in self._ports:
ports.append(port.dump())
return iou
def load(self, node_info):
@@ -351,6 +339,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:
@@ -382,30 +372,6 @@ class IOUDevice(VM):
self.loaded_signal.connect(self._updatePortSettings)
self.setup(path, name, vm_id, vm_settings)
def _updatePortSettings(self):
"""
Updates port settings when loading a topology.
"""
self.loaded_signal.disconnect(self._updatePortSettings)
# assign the correct names and IDs to the ports
if "ports" in self._node_info:
ports = self._node_info["ports"]
for topology_port in ports:
for port in self._ports:
if topology_port["port_number"] == port.portNumber() and (topology_port.get("adapter_number", None) == port.adapterNumber() or topology_port.get("slot_number", None) == port.adapterNumber()):
port.setName(topology_port["name"])
port.setId(topology_port["id"])
# now we can set the node as initialized and trigger the created signal
self.setInitialized(True)
log.info("IOU device {} has been loaded".format(self.name()))
self.created_signal.emit(self.id())
self._module.addNode(self)
self._loading = False
self._node_info = None
def saveConfig(self):
"""
Save the configs

View File

@@ -207,10 +207,8 @@ class QemuVM(VM):
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
return
if not super()._updateCallback(result, error=error, **kwargs):
return False
updated = False
nb_adapters_changed = False
@@ -296,14 +294,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
@@ -315,12 +309,6 @@ class QemuVM(VM):
if value is not None and value != "":
qemu_vm["properties"][name] = value
# add the ports
if self._ports:
ports = qemu_vm["ports"] = []
for port in self._ports:
ports.append(port.dump())
return qemu_vm
def info(self):
@@ -369,6 +357,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:
@@ -387,36 +376,8 @@ class QemuVM(VM):
qemu_path = vm_settings.pop("qemu_path")
log.info("QEMU VM {} is loading".format(name))
self.setName(name)
self._loading = True
self._node_info = node_info
self.loaded_signal.connect(self._updatePortSettings)
self.setup(qemu_path, name, vm_id, port_name_format, port_segment_size, first_port_name, linked_clone, vm_settings)
def _updatePortSettings(self):
"""
Updates port settings when loading a topology.
"""
self.loaded_signal.disconnect(self._updatePortSettings)
# assign the correct names and IDs to the ports
if "ports" in self._node_info:
ports = self._node_info["ports"]
for topology_port in ports:
for port in self._ports:
adapter_number = topology_port.get("adapter_number", topology_port["port_number"])
if adapter_number == port.adapterNumber():
port.setName(topology_port["name"])
port.setId(topology_port["id"])
# now we can set the node as initialized and trigger the created signal
self.setInitialized(True)
log.info("QEMU VM {} has been loaded".format(self.name()))
self.created_signal.emit(self.id())
self._module.addNode(self)
self._loading = False
self._node_info = None
def name(self):
"""
Returns the name of this QEMU VM instance.

View File

@@ -188,10 +188,8 @@ class VirtualBoxVM(VM):
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
return
if not super()._updateCallback(result, error=error, **kwargs):
return False
updated = False
nb_adapters_changed = False
@@ -293,14 +291,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
@@ -312,12 +306,6 @@ class VirtualBoxVM(VM):
if value is not None and value != "":
vbox_vm["properties"][name] = value
# add the ports
if self._ports:
ports = vbox_vm["ports"] = []
for port in self._ports:
ports.append(port.dump())
return vbox_vm
def load(self, node_info):
@@ -328,6 +316,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:
@@ -347,36 +337,8 @@ class VirtualBoxVM(VM):
log.info("VirtualBox VM {} is loading".format(name))
self.setName(name)
self._loading = True
self._node_info = node_info
self.loaded_signal.connect(self._updatePortSettings)
self.setup(vmname, name, vm_id, port_name_format, port_segment_size, first_port_name, linked_clone, vm_settings)
def _updatePortSettings(self):
"""
Updates port settings when loading a topology.
"""
self.loaded_signal.disconnect(self._updatePortSettings)
# assign the correct names and IDs to the ports
if "ports" in self._node_info:
ports = self._node_info["ports"]
for topology_port in ports:
for port in self._ports:
adapter_number = topology_port.get("adapter_number", topology_port["port_number"])
if adapter_number == port.adapterNumber():
port.setName(topology_port["name"])
port.setId(topology_port["id"])
# now we can set the node as initialized and trigger the created signal
self.setInitialized(True)
log.info("VirtualBox VM {} has been loaded".format(self.name()))
self.created_signal.emit(self.id())
self._module.addNode(self)
self._loading = False
self._node_info = None
def name(self):
"""
Returns the name of this VirtualBox VM instance.

View File

@@ -191,10 +191,8 @@ class VMwareVM(VM):
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
return
if not super()._updateCallback(result, error=error, **kwargs):
return False
updated = False
nb_adapters_changed = False
@@ -300,14 +298,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
@@ -319,12 +313,6 @@ class VMwareVM(VM):
if value is not None and value != "":
vmware_vm["properties"][name] = value
# add the ports
if self._ports:
ports = vmware_vm["ports"] = []
for port in self._ports:
ports.append(port.dump())
return vmware_vm
def load(self, node_info):
@@ -335,6 +323,8 @@ class VMwareVM(VM):
:param node_info: representation of the node (dictionary)
"""
super().load(node_info)
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}")
@@ -350,35 +340,8 @@ class VMwareVM(VM):
log.info("VMware VM {} is loading".format(name))
self.setName(name)
self._loading = True
self._node_info = node_info
self.loaded_signal.connect(self._updatePortSettings)
self.setup(vmx_path, name, vm_id, port_name_format, port_segment_size, first_port_name, linked_clone, vm_settings)
def _updatePortSettings(self):
"""
Updates port settings when loading a topology.
"""
self.loaded_signal.disconnect(self._updatePortSettings)
# update the port with the correct names and IDs
if "ports" in self._node_info:
ports = self._node_info["ports"]
for topology_port in ports:
for port in self._ports:
adapter_number = topology_port.get("adapter_number")
if adapter_number == port.adapterNumber():
port.setName(topology_port["name"])
port.setId(topology_port["id"])
# now we can set the node as initialized and trigger the created signal
self.setInitialized(True)
log.info("VMware VM {} has been loaded".format(self.name()))
self.created_signal.emit(self.id())
self._module.addNode(self)
self._loading = False
self._node_info = None
def allocateVMnetInterface(self, port_id):
"""
Requests an UDP port allocation.

View File

@@ -157,10 +157,8 @@ class VPCSDevice(VM):
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
return
if not super()._updateCallback(result, error=error, **kwargs):
return False
updated = False
for name, value in result.items():
@@ -218,12 +216,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():
@@ -233,12 +227,6 @@ class VPCSDevice(VM):
value = os.path.basename(value)
vpcs_device["properties"][name] = value
# add the ports
if self._ports:
ports = vpcs_device["ports"] = []
for port in self._ports:
ports.append(port.dump())
return vpcs_device
def load(self, node_info):
@@ -249,6 +237,8 @@ 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:
@@ -263,35 +253,8 @@ class VPCSDevice(VM):
log.info("VPCS device {} is loading".format(name))
self.setName(name)
self._loading = True
self._node_info = node_info
self.loaded_signal.connect(self._updatePortSettings)
self.setup(name, vm_id, vm_settings)
def _updatePortSettings(self):
"""
Updates port settings when loading a topology.
"""
self.loaded_signal.disconnect(self._updatePortSettings)
# assign the correct names and IDs to the ports
if "ports" in self._node_info:
ports = self._node_info["ports"]
for topology_port in ports:
for port in self._ports:
if topology_port["port_number"] == port.portNumber():
port.setName(topology_port["name"])
port.setId(topology_port["id"])
# now we can set the node as initialized and trigger the created signal
self.setInitialized(True)
log.info("VPCS device {} has been loaded".format(self.name()))
self.created_signal.emit(self.id())
self._module.addNode(self)
self._loading = False
self._node_info = None
def exportConfig(self, config_export_path):
"""
Exports the script file.

View File

@@ -26,9 +26,9 @@ import json
from gns3.qt import QtGui, QtCore, QtWidgets
from gns3.local_config import LocalConfig
from ..ui.general_preferences_page_ui import Ui_GeneralPreferencesPageWidget
from ..settings import GRAPHICS_VIEW_SETTINGS, GENERAL_SETTINGS, PRECONFIGURED_TELNET_CONSOLE_COMMANDS, \
PRECONFIGURED_SERIAL_CONSOLE_COMMANDS, PRECONFIGURED_VNC_CONSOLE_COMMANDS, STYLES
from gns3.servers import Servers
from ..settings import GRAPHICS_VIEW_SETTINGS, GENERAL_SETTINGS, STYLES
from ..dialogs.console_command_dialog import ConsoleCommandDialog
class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget):
@@ -44,14 +44,6 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
self._remote_servers = {}
self._preferences_dialog = parent
# Load the pre-configured console & VNC commands
for name, cmd in sorted(PRECONFIGURED_TELNET_CONSOLE_COMMANDS.items()):
self.uiTelnetConsolePreconfiguredCommandComboBox.addItem(name, cmd)
for name, cmd in sorted(PRECONFIGURED_SERIAL_CONSOLE_COMMANDS.items()):
self.uiSerialConsolePreconfiguredCommandComboBox.addItem(name, cmd)
for name, cmd in sorted(PRECONFIGURED_VNC_CONSOLE_COMMANDS.items()):
self.uiVNCConsolePreconfiguredCommandComboBox.addItem(name, cmd)
# Display the path of the config file
config_file_path = LocalConfig.instance().configFilePath()
self.uiConfigurationFileLabel.setText(config_file_path)
@@ -136,30 +128,30 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
Slot to set a chosen pre-configured Telnet console command.
"""
self.uiTelnetConsoleCommandLineEdit.clear()
command = self.uiTelnetConsolePreconfiguredCommandComboBox.itemData(self.uiTelnetConsolePreconfiguredCommandComboBox.currentIndex(), QtCore.Qt.UserRole)
self.uiTelnetConsoleCommandLineEdit.setText(command)
self.uiTelnetConsoleCommandLineEdit.setCursorPosition(0)
cmd = self.uiTelnetConsoleCommandLineEdit.text()
(ok, cmd) = ConsoleCommandDialog.getCommand(self, console_type="telnet", current=cmd)
if ok:
self.uiTelnetConsoleCommandLineEdit.setText(cmd)
def _serialConsolePreconfiguredCommandSlot(self):
"""
Slot to set a chosen pre-configured serial console command.
"""
self.uiSerialConsoleCommandLineEdit.clear()
command = self.uiSerialConsolePreconfiguredCommandComboBox.itemData(self.uiSerialConsolePreconfiguredCommandComboBox.currentIndex(), QtCore.Qt.UserRole)
self.uiSerialConsoleCommandLineEdit.setText(command)
self.uiSerialConsoleCommandLineEdit.setCursorPosition(0)
cmd = self.uiSerialConsoleCommandLineEdit.text()
(ok, cmd) = ConsoleCommandDialog.getCommand(self, console_type="serial", current=cmd)
if ok:
self.uiSerialConsoleCommandLineEdit.setText(cmd)
def _vncConsolePreconfiguredCommandSlot(self):
"""
Slot to set a chosen pre-configured VNC console command.
"""
self.uiVNCConsoleCommandLineEdit.clear()
command = self.uiVNCConsolePreconfiguredCommandComboBox.itemData(self.uiVNCConsolePreconfiguredCommandComboBox.currentIndex(), QtCore.Qt.UserRole)
self.uiVNCConsoleCommandLineEdit.setText(command)
self.uiVNCConsoleCommandLineEdit.setCursorPosition(0)
cmd = self.uiVNCConsoleCommandLineEdit.text()
(ok, cmd) = ConsoleCommandDialog.getCommand(self, console_type="vnc", current=cmd)
if ok:
self.uiVNCConsoleCommandLineEdit.setText(cmd)
def _importConfigurationFileSlot(self):
"""
@@ -262,23 +254,14 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
index = self.uiStyleComboBox.findText(settings["style"])
if index != -1:
self.uiStyleComboBox.setCurrentIndex(index)
index = self.uiTelnetConsolePreconfiguredCommandComboBox.findData(settings["telnet_console_command"])
if index != -1:
self.uiTelnetConsolePreconfiguredCommandComboBox.setCurrentIndex(index)
self.uiSerialConsoleCommandLineEdit.setText(settings["serial_console_command"])
self.uiSerialConsoleCommandLineEdit.setCursorPosition(0)
index = self.uiSerialConsolePreconfiguredCommandComboBox.findData(settings["serial_console_command"])
if index != -1:
self.uiSerialConsolePreconfiguredCommandComboBox.setCurrentIndex(index)
self.uiCloseConsoleWindowsOnDeleteCheckBox.setChecked(settings["auto_close_console"])
self.uiBringConsoleWindowToFrontCheckBox.setChecked(settings["bring_console_to_front"])
self.uiDelayConsoleAllSpinBox.setValue(settings["delay_console_all"])
self.uiVNCConsoleCommandLineEdit.setText(settings["vnc_console_command"])
self.uiVNCConsoleCommandLineEdit.setCursorPosition(0)
index = self.uiVNCConsolePreconfiguredCommandComboBox.findData(settings["vnc_console_command"])
if index != -1:
self.uiVNCConsolePreconfiguredCommandComboBox.setCurrentIndex(index)
def _populateGraphicsViewSettingWidgets(self, settings):
"""

View File

@@ -28,6 +28,7 @@ For PyQt4 and PyQt5 differences please see http://pyqt.sourceforge.net/Docs/PyQt
import sys
import sip
import os
import re
import functools
import inspect
@@ -105,23 +106,6 @@ class QFileDialog(OldFileDialog):
QtWidgets.QFileDialog = QFileDialog
class StatsQtWidgetsQDialog(QtWidgets.QDialog):
"""
Send stats from all the QWizard
"""
def __init__(self, *args):
super().__init__(*args)
import re
from .utils.analytics import AnalyticsClient
name = self.__class__.__name__
name = re.sub(r"([A-Z])", r" \1", name).strip()
AnalyticsClient.instance().sendScreenView(name)
QtWidgets.QDialog = StatsQtWidgetsQDialog
class LogQMessageBox(QtWidgets.QMessageBox):
"""
Replace the standard message box for logging errors to console. And
@@ -148,39 +132,6 @@ class LogQMessageBox(QtWidgets.QMessageBox):
QtWidgets.QMessageBox = LogQMessageBox
class StatsQtWidgetsQWizard(QtWidgets.QWizard):
"""
Send stats from all the QWizard
"""
def __init__(self, *args):
super().__init__(*args)
import re
from .utils.analytics import AnalyticsClient
name = self.__class__.__name__
name = re.sub(r"([A-Z])", r" \1", name).strip()
AnalyticsClient.instance().sendScreenView(name)
QtWidgets.QWizard = StatsQtWidgetsQWizard
class StatsQtWidgetsQMainWindow(QtWidgets.QMainWindow):
"""
Send stats from all the QMainWindow
"""
def __init__(self, *args):
super().__init__(*args)
import re
from .utils.analytics import AnalyticsClient
name = self.__class__.__name__
name = re.sub(r"([A-Z])", r" \1", name).strip()
AnalyticsClient.instance().sendScreenView(name)
QtWidgets.QMainWindow = StatsQtWidgetsQMainWindow
# If we run from a test we replace the signal by a synchronous version
if hasattr(sys, '_called_from_test'):
class FakeQtSignal:
@@ -212,6 +163,54 @@ if hasattr(sys, '_called_from_test'):
QtCore.pyqtSignal = FakeQtSignal
class StatsQtWidgetsQWizard(QtWidgets.QWizard):
"""
Send stats from all the QWizard
"""
def __init__(self, *args):
super().__init__(*args)
from ..utils.analytics import AnalyticsClient
name = self.__class__.__name__
name = re.sub(r"([A-Z])", r" \1", name).strip()
AnalyticsClient.instance().sendScreenView(name)
QtWidgets.QWizard = StatsQtWidgetsQWizard
class StatsQtWidgetsQMainWindow(QtWidgets.QMainWindow):
"""
Send stats from all the QMainWindow
"""
def __init__(self, *args):
super().__init__(*args)
from ..utils.analytics import AnalyticsClient
name = self.__class__.__name__
name = re.sub(r"([A-Z])", r" \1", name).strip()
AnalyticsClient.instance().sendScreenView(name)
QtWidgets.QMainWindow = StatsQtWidgetsQMainWindow
class StatsQtWidgetsQDialog(QtWidgets.QDialog):
"""
Send stats from all the QWizard
"""
def __init__(self, *args):
super().__init__(*args)
from ..utils.analytics import AnalyticsClient
name = self.__class__.__name__
name = re.sub(r"([A-Z])", r" \1", name).strip()
AnalyticsClient.instance().sendScreenView(name)
QtWidgets.QDialog = StatsQtWidgetsQDialog
def qpartial(func, *args, **kwargs):
"""
A functools partial that you can use on qobject. If the targeted qobject is
@@ -230,3 +229,4 @@ def qpartial(func, *args, **kwargs):
return functools.partial(partial, *args, **kwargs)
return functools.partial(func, *args, **kwargs)

View File

@@ -291,6 +291,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

@@ -93,6 +93,9 @@ class ServerSummaryView(QtWidgets.QTreeWidget):
def __init__(self, parent):
super().__init__(parent)
self._servers = set()
Servers.instance().server_added_signal.connect(self._serverAddedSlot)
for server in Servers.instance().servers():
self._serverAddedSlot(server.url())
@@ -103,6 +106,10 @@ class ServerSummaryView(QtWidgets.QTreeWidget):
:params url: URL of the server
"""
if url in self._servers:
return
self._servers.add(url)
server = Servers.instance().getServerFromString(url)
ServerItem(self, server)

View File

@@ -322,3 +322,9 @@ PACKET_CAPTURE_SETTINGS = {
"command_auto_start": True,
"packet_capture_analyzer_command": DEFAULT_PACKET_CAPTURE_ANALYZER_COMMAND,
}
CUSTOM_CONSOLE_COMMANDS_SETTINGS = {
"telnet": {},
"vnc": {},
"serial": {}
}

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

View File

@@ -0,0 +1,137 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>uiConsoleCommandDialog</class>
<widget class="QDialog" name="uiConsoleCommandDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>524</width>
<height>350</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Command</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Choose a predefined command:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="uiCommandComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="uiRemovePushButton">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="uiSavePushButton">
<property name="text">
<string>Save</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Or customize the command in the next input field. &lt;br/&gt;The following variables are replaced by GNS3: &lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%h: console IP or hostname&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%p: console port&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%s: path of the serial connection&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%d: title of the console&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="uiCommandPlainTextEdit">
<property name="minimumSize">
<size>
<width>500</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="uiButtonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>uiButtonBox</sender>
<signal>accepted()</signal>
<receiver>uiConsoleCommandDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>uiButtonBox</sender>
<signal>rejected()</signal>
<receiver>uiConsoleCommandDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/ui/console_command_dialog.ui'
#
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_uiConsoleCommandDialog(object):
def setupUi(self, uiConsoleCommandDialog):
uiConsoleCommandDialog.setObjectName("uiConsoleCommandDialog")
uiConsoleCommandDialog.resize(524, 350)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(uiConsoleCommandDialog.sizePolicy().hasHeightForWidth())
uiConsoleCommandDialog.setSizePolicy(sizePolicy)
uiConsoleCommandDialog.setMinimumSize(QtCore.QSize(0, 0))
uiConsoleCommandDialog.setModal(True)
self.verticalLayout = QtWidgets.QVBoxLayout(uiConsoleCommandDialog)
self.verticalLayout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize)
self.verticalLayout.setObjectName("verticalLayout")
self.label_2 = QtWidgets.QLabel(uiConsoleCommandDialog)
self.label_2.setObjectName("label_2")
self.verticalLayout.addWidget(self.label_2)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.uiCommandComboBox = QtWidgets.QComboBox(uiConsoleCommandDialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiCommandComboBox.sizePolicy().hasHeightForWidth())
self.uiCommandComboBox.setSizePolicy(sizePolicy)
self.uiCommandComboBox.setObjectName("uiCommandComboBox")
self.horizontalLayout.addWidget(self.uiCommandComboBox)
self.uiRemovePushButton = QtWidgets.QPushButton(uiConsoleCommandDialog)
self.uiRemovePushButton.setObjectName("uiRemovePushButton")
self.horizontalLayout.addWidget(self.uiRemovePushButton)
self.uiSavePushButton = QtWidgets.QPushButton(uiConsoleCommandDialog)
self.uiSavePushButton.setObjectName("uiSavePushButton")
self.horizontalLayout.addWidget(self.uiSavePushButton)
self.verticalLayout.addLayout(self.horizontalLayout)
self.label = QtWidgets.QLabel(uiConsoleCommandDialog)
self.label.setTextFormat(QtCore.Qt.RichText)
self.label.setObjectName("label")
self.verticalLayout.addWidget(self.label)
self.uiCommandPlainTextEdit = QtWidgets.QPlainTextEdit(uiConsoleCommandDialog)
self.uiCommandPlainTextEdit.setMinimumSize(QtCore.QSize(500, 0))
self.uiCommandPlainTextEdit.setObjectName("uiCommandPlainTextEdit")
self.verticalLayout.addWidget(self.uiCommandPlainTextEdit)
self.uiButtonBox = QtWidgets.QDialogButtonBox(uiConsoleCommandDialog)
self.uiButtonBox.setOrientation(QtCore.Qt.Horizontal)
self.uiButtonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.uiButtonBox.setObjectName("uiButtonBox")
self.verticalLayout.addWidget(self.uiButtonBox)
self.retranslateUi(uiConsoleCommandDialog)
self.uiButtonBox.accepted.connect(uiConsoleCommandDialog.accept)
self.uiButtonBox.rejected.connect(uiConsoleCommandDialog.reject)
QtCore.QMetaObject.connectSlotsByName(uiConsoleCommandDialog)
def retranslateUi(self, uiConsoleCommandDialog):
_translate = QtCore.QCoreApplication.translate
uiConsoleCommandDialog.setWindowTitle(_translate("uiConsoleCommandDialog", "Command"))
self.label_2.setText(_translate("uiConsoleCommandDialog", "Choose a predefined command:"))
self.uiRemovePushButton.setText(_translate("uiConsoleCommandDialog", "Remove"))
self.uiSavePushButton.setText(_translate("uiConsoleCommandDialog", "Save"))
self.label.setText(_translate("uiConsoleCommandDialog", "<html><head/><body><p>Or customize the command in the next input field. <br/>The following variables are replaced by GNS3: </p><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%h: console IP or hostname</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%p: console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%s: path of the serial connection</li><li style=\" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d: title of the console</li></ul></body></html>"))

View File

@@ -256,103 +256,88 @@
<item>
<widget class="QGroupBox" name="uiTelnetConsoleSettingsGroupBox">
<property name="title">
<string>Console settings for Telnet connections</string>
<string>Console settings</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="uiTelnetConsolePreconfiguredCommandLabel">
<property name="text">
<string>Preconfigured commands:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QComboBox" name="uiTelnetConsolePreconfiguredCommandComboBox"/>
</item>
<item row="3" column="0">
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<widget class="QLabel" name="uiTelnetConsoleCommandLabel">
<property name="text">
<string>Console application command:</string>
<string>Console application command for Telnet:</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QLineEdit" name="uiTelnetConsoleCommandLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Command line replacements:&lt;/p&gt;&lt;p&gt;%h = device server &lt;/p&gt;&lt;p&gt;%p = device port&lt;/p&gt;&lt;p&gt;%d = device hostname&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QLineEdit" name="uiTelnetConsoleCommandLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Command line replacements:&lt;/p&gt;&lt;p&gt;%h = device server &lt;/p&gt;&lt;p&gt;%p = device port&lt;/p&gt;&lt;p&gt;%d = device hostname&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="uiTelnetConsolePreconfiguredCommandPushButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Edit</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="uiTelnetConsolePreconfiguredCommandPushButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Set</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="uiSerialConsoleSettingsGroupBox">
<property name="title">
<string>Console settings for local serial connections</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="3" column="0">
<item>
<widget class="QLabel" name="uiSerialConsoleCommandLabel">
<property name="text">
<string>Console application command:</string>
<string>Console application command for Serial:</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="uiSerialConsolePreconfiguredCommandLabel">
<property name="text">
<string>Preconfigured commands:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QComboBox" name="uiSerialConsolePreconfiguredCommandComboBox"/>
</item>
<item row="4" column="0" colspan="2">
<widget class="QLineEdit" name="uiSerialConsoleCommandLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Command line replacements:&lt;/p&gt;&lt;p&gt;%d = device hostname&lt;/p&gt;&lt;p&gt;%s = device pipe file&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="uiSerialConsolePreconfiguredCommandPushButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Set</string>
</property>
</widget>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QLineEdit" name="uiSerialConsoleCommandLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Command line replacements:&lt;/p&gt;&lt;p&gt;%d = device hostname&lt;/p&gt;&lt;p&gt;%s = device pipe file&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="uiSerialConsolePreconfiguredCommandPushButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Edit</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
@@ -428,49 +413,46 @@
<property name="title">
<string>Settings for VNC connections</string>
</property>
<layout class="QGridLayout" name="gridLayout_6">
<item row="0" column="0">
<widget class="QLabel" name="uiVNCConsolePreconfiguredCommandLabel">
<property name="text">
<string>Preconfigured commands:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QComboBox" name="uiVNCConsolePreconfiguredCommandComboBox"/>
</item>
<item row="3" column="0">
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QLabel" name="uiVNCConsoleCommandLabel">
<property name="text">
<string>Console application command:</string>
<string>Console application command for VNC:</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QLineEdit" name="uiVNCConsoleCommandLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Command line replacements:&lt;/p&gt;&lt;p&gt;%h = device server &lt;/p&gt;&lt;p&gt;%p = device port&lt;/p&gt;&lt;p&gt;%d = device hostname&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="uiVNCConsolePreconfiguredCommandPushButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Set</string>
</property>
</widget>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QLineEdit" name="uiVNCConsoleCommandLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Command line replacements:&lt;/p&gt;&lt;p&gt;%h = device server &lt;/p&gt;&lt;p&gt;%p = device port&lt;/p&gt;&lt;p&gt;%d = device hostname&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="uiVNCConsolePreconfiguredCommandPushButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Edit</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>

View File

@@ -142,25 +142,22 @@ class Ui_GeneralPreferencesPageWidget(object):
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.uiTelnetConsoleSettingsGroupBox = QtWidgets.QGroupBox(self.uiConsoleTab)
self.uiTelnetConsoleSettingsGroupBox.setObjectName("uiTelnetConsoleSettingsGroupBox")
self.gridLayout_4 = QtWidgets.QGridLayout(self.uiTelnetConsoleSettingsGroupBox)
self.gridLayout_4.setObjectName("gridLayout_4")
self.uiTelnetConsolePreconfiguredCommandLabel = QtWidgets.QLabel(self.uiTelnetConsoleSettingsGroupBox)
self.uiTelnetConsolePreconfiguredCommandLabel.setObjectName("uiTelnetConsolePreconfiguredCommandLabel")
self.gridLayout_4.addWidget(self.uiTelnetConsolePreconfiguredCommandLabel, 0, 0, 1, 1)
self.uiTelnetConsolePreconfiguredCommandComboBox = QtWidgets.QComboBox(self.uiTelnetConsoleSettingsGroupBox)
self.uiTelnetConsolePreconfiguredCommandComboBox.setObjectName("uiTelnetConsolePreconfiguredCommandComboBox")
self.gridLayout_4.addWidget(self.uiTelnetConsolePreconfiguredCommandComboBox, 1, 0, 1, 1)
self.verticalLayout_9 = QtWidgets.QVBoxLayout(self.uiTelnetConsoleSettingsGroupBox)
self.verticalLayout_9.setObjectName("verticalLayout_9")
self.uiTelnetConsoleCommandLabel = QtWidgets.QLabel(self.uiTelnetConsoleSettingsGroupBox)
self.uiTelnetConsoleCommandLabel.setObjectName("uiTelnetConsoleCommandLabel")
self.gridLayout_4.addWidget(self.uiTelnetConsoleCommandLabel, 3, 0, 1, 1)
self.verticalLayout_9.addWidget(self.uiTelnetConsoleCommandLabel)
self.horizontalLayout_9 = QtWidgets.QHBoxLayout()
self.horizontalLayout_9.setObjectName("horizontalLayout_9")
self.uiTelnetConsoleCommandLineEdit = QtWidgets.QLineEdit(self.uiTelnetConsoleSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiTelnetConsoleCommandLineEdit.sizePolicy().hasHeightForWidth())
self.uiTelnetConsoleCommandLineEdit.setSizePolicy(sizePolicy)
self.uiTelnetConsoleCommandLineEdit.setReadOnly(True)
self.uiTelnetConsoleCommandLineEdit.setObjectName("uiTelnetConsoleCommandLineEdit")
self.gridLayout_4.addWidget(self.uiTelnetConsoleCommandLineEdit, 4, 0, 1, 2)
self.horizontalLayout_9.addWidget(self.uiTelnetConsoleCommandLineEdit)
self.uiTelnetConsolePreconfiguredCommandPushButton = QtWidgets.QPushButton(self.uiTelnetConsoleSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -168,38 +165,32 @@ class Ui_GeneralPreferencesPageWidget(object):
sizePolicy.setHeightForWidth(self.uiTelnetConsolePreconfiguredCommandPushButton.sizePolicy().hasHeightForWidth())
self.uiTelnetConsolePreconfiguredCommandPushButton.setSizePolicy(sizePolicy)
self.uiTelnetConsolePreconfiguredCommandPushButton.setObjectName("uiTelnetConsolePreconfiguredCommandPushButton")
self.gridLayout_4.addWidget(self.uiTelnetConsolePreconfiguredCommandPushButton, 1, 1, 1, 1)
self.verticalLayout_3.addWidget(self.uiTelnetConsoleSettingsGroupBox)
self.uiSerialConsoleSettingsGroupBox = QtWidgets.QGroupBox(self.uiConsoleTab)
self.uiSerialConsoleSettingsGroupBox.setObjectName("uiSerialConsoleSettingsGroupBox")
self.gridLayout_5 = QtWidgets.QGridLayout(self.uiSerialConsoleSettingsGroupBox)
self.gridLayout_5.setObjectName("gridLayout_5")
self.uiSerialConsoleCommandLabel = QtWidgets.QLabel(self.uiSerialConsoleSettingsGroupBox)
self.horizontalLayout_9.addWidget(self.uiTelnetConsolePreconfiguredCommandPushButton)
self.verticalLayout_9.addLayout(self.horizontalLayout_9)
self.uiSerialConsoleCommandLabel = QtWidgets.QLabel(self.uiTelnetConsoleSettingsGroupBox)
self.uiSerialConsoleCommandLabel.setObjectName("uiSerialConsoleCommandLabel")
self.gridLayout_5.addWidget(self.uiSerialConsoleCommandLabel, 3, 0, 1, 1)
self.uiSerialConsolePreconfiguredCommandLabel = QtWidgets.QLabel(self.uiSerialConsoleSettingsGroupBox)
self.uiSerialConsolePreconfiguredCommandLabel.setObjectName("uiSerialConsolePreconfiguredCommandLabel")
self.gridLayout_5.addWidget(self.uiSerialConsolePreconfiguredCommandLabel, 0, 0, 1, 1)
self.uiSerialConsolePreconfiguredCommandComboBox = QtWidgets.QComboBox(self.uiSerialConsoleSettingsGroupBox)
self.uiSerialConsolePreconfiguredCommandComboBox.setObjectName("uiSerialConsolePreconfiguredCommandComboBox")
self.gridLayout_5.addWidget(self.uiSerialConsolePreconfiguredCommandComboBox, 1, 0, 1, 1)
self.uiSerialConsoleCommandLineEdit = QtWidgets.QLineEdit(self.uiSerialConsoleSettingsGroupBox)
self.verticalLayout_9.addWidget(self.uiSerialConsoleCommandLabel)
self.horizontalLayout_10 = QtWidgets.QHBoxLayout()
self.horizontalLayout_10.setObjectName("horizontalLayout_10")
self.uiSerialConsoleCommandLineEdit = QtWidgets.QLineEdit(self.uiTelnetConsoleSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiSerialConsoleCommandLineEdit.sizePolicy().hasHeightForWidth())
self.uiSerialConsoleCommandLineEdit.setSizePolicy(sizePolicy)
self.uiSerialConsoleCommandLineEdit.setReadOnly(True)
self.uiSerialConsoleCommandLineEdit.setObjectName("uiSerialConsoleCommandLineEdit")
self.gridLayout_5.addWidget(self.uiSerialConsoleCommandLineEdit, 4, 0, 1, 2)
self.uiSerialConsolePreconfiguredCommandPushButton = QtWidgets.QPushButton(self.uiSerialConsoleSettingsGroupBox)
self.horizontalLayout_10.addWidget(self.uiSerialConsoleCommandLineEdit)
self.uiSerialConsolePreconfiguredCommandPushButton = QtWidgets.QPushButton(self.uiTelnetConsoleSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiSerialConsolePreconfiguredCommandPushButton.sizePolicy().hasHeightForWidth())
self.uiSerialConsolePreconfiguredCommandPushButton.setSizePolicy(sizePolicy)
self.uiSerialConsolePreconfiguredCommandPushButton.setObjectName("uiSerialConsolePreconfiguredCommandPushButton")
self.gridLayout_5.addWidget(self.uiSerialConsolePreconfiguredCommandPushButton, 1, 1, 1, 1)
self.verticalLayout_3.addWidget(self.uiSerialConsoleSettingsGroupBox)
self.horizontalLayout_10.addWidget(self.uiSerialConsolePreconfiguredCommandPushButton)
self.verticalLayout_9.addLayout(self.horizontalLayout_10)
self.verticalLayout_3.addWidget(self.uiTelnetConsoleSettingsGroupBox)
self.uiConsoleMiscGroupBox = QtWidgets.QGroupBox(self.uiConsoleTab)
self.uiConsoleMiscGroupBox.setObjectName("uiConsoleMiscGroupBox")
self.gridLayout_7 = QtWidgets.QGridLayout(self.uiConsoleMiscGroupBox)
@@ -228,25 +219,22 @@ class Ui_GeneralPreferencesPageWidget(object):
self.verticalLayout_6.setObjectName("verticalLayout_6")
self.uiVNCConsoleSettingsGroupBox = QtWidgets.QGroupBox(self.uiVNCTab)
self.uiVNCConsoleSettingsGroupBox.setObjectName("uiVNCConsoleSettingsGroupBox")
self.gridLayout_6 = QtWidgets.QGridLayout(self.uiVNCConsoleSettingsGroupBox)
self.gridLayout_6.setObjectName("gridLayout_6")
self.uiVNCConsolePreconfiguredCommandLabel = QtWidgets.QLabel(self.uiVNCConsoleSettingsGroupBox)
self.uiVNCConsolePreconfiguredCommandLabel.setObjectName("uiVNCConsolePreconfiguredCommandLabel")
self.gridLayout_6.addWidget(self.uiVNCConsolePreconfiguredCommandLabel, 0, 0, 1, 1)
self.uiVNCConsolePreconfiguredCommandComboBox = QtWidgets.QComboBox(self.uiVNCConsoleSettingsGroupBox)
self.uiVNCConsolePreconfiguredCommandComboBox.setObjectName("uiVNCConsolePreconfiguredCommandComboBox")
self.gridLayout_6.addWidget(self.uiVNCConsolePreconfiguredCommandComboBox, 1, 0, 1, 1)
self.verticalLayout_8 = QtWidgets.QVBoxLayout(self.uiVNCConsoleSettingsGroupBox)
self.verticalLayout_8.setObjectName("verticalLayout_8")
self.uiVNCConsoleCommandLabel = QtWidgets.QLabel(self.uiVNCConsoleSettingsGroupBox)
self.uiVNCConsoleCommandLabel.setObjectName("uiVNCConsoleCommandLabel")
self.gridLayout_6.addWidget(self.uiVNCConsoleCommandLabel, 3, 0, 1, 1)
self.verticalLayout_8.addWidget(self.uiVNCConsoleCommandLabel)
self.horizontalLayout_8 = QtWidgets.QHBoxLayout()
self.horizontalLayout_8.setObjectName("horizontalLayout_8")
self.uiVNCConsoleCommandLineEdit = QtWidgets.QLineEdit(self.uiVNCConsoleSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiVNCConsoleCommandLineEdit.sizePolicy().hasHeightForWidth())
self.uiVNCConsoleCommandLineEdit.setSizePolicy(sizePolicy)
self.uiVNCConsoleCommandLineEdit.setReadOnly(True)
self.uiVNCConsoleCommandLineEdit.setObjectName("uiVNCConsoleCommandLineEdit")
self.gridLayout_6.addWidget(self.uiVNCConsoleCommandLineEdit, 4, 0, 1, 2)
self.horizontalLayout_8.addWidget(self.uiVNCConsoleCommandLineEdit)
self.uiVNCConsolePreconfiguredCommandPushButton = QtWidgets.QPushButton(self.uiVNCConsoleSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -254,7 +242,8 @@ class Ui_GeneralPreferencesPageWidget(object):
sizePolicy.setHeightForWidth(self.uiVNCConsolePreconfiguredCommandPushButton.sizePolicy().hasHeightForWidth())
self.uiVNCConsolePreconfiguredCommandPushButton.setSizePolicy(sizePolicy)
self.uiVNCConsolePreconfiguredCommandPushButton.setObjectName("uiVNCConsolePreconfiguredCommandPushButton")
self.gridLayout_6.addWidget(self.uiVNCConsolePreconfiguredCommandPushButton, 1, 1, 1, 1)
self.horizontalLayout_8.addWidget(self.uiVNCConsolePreconfiguredCommandPushButton)
self.verticalLayout_8.addLayout(self.horizontalLayout_8)
self.verticalLayout_6.addWidget(self.uiVNCConsoleSettingsGroupBox)
spacerItem3 = QtWidgets.QSpacerItem(20, 294, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_6.addItem(spacerItem3)
@@ -396,16 +385,13 @@ class Ui_GeneralPreferencesPageWidget(object):
self.uiExportConfigurationFilePushButton.setText(_translate("GeneralPreferencesPageWidget", "&Export"))
self.uiConfigurationFileLabel.setText(_translate("GeneralPreferencesPageWidget", "Unknown location"))
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.uiGeneralTab), _translate("GeneralPreferencesPageWidget", "General"))
self.uiTelnetConsoleSettingsGroupBox.setTitle(_translate("GeneralPreferencesPageWidget", "Console settings for Telnet connections"))
self.uiTelnetConsolePreconfiguredCommandLabel.setText(_translate("GeneralPreferencesPageWidget", "Preconfigured commands:"))
self.uiTelnetConsoleCommandLabel.setText(_translate("GeneralPreferencesPageWidget", "Console application command:"))
self.uiTelnetConsoleSettingsGroupBox.setTitle(_translate("GeneralPreferencesPageWidget", "Console settings"))
self.uiTelnetConsoleCommandLabel.setText(_translate("GeneralPreferencesPageWidget", "Console application command for Telnet:"))
self.uiTelnetConsoleCommandLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><p>%h = device server </p><p>%p = device port</p><p>%d = device hostname</p></body></html>"))
self.uiTelnetConsolePreconfiguredCommandPushButton.setText(_translate("GeneralPreferencesPageWidget", "&Set"))
self.uiSerialConsoleSettingsGroupBox.setTitle(_translate("GeneralPreferencesPageWidget", "Console settings for local serial connections"))
self.uiSerialConsoleCommandLabel.setText(_translate("GeneralPreferencesPageWidget", "Console application command:"))
self.uiSerialConsolePreconfiguredCommandLabel.setText(_translate("GeneralPreferencesPageWidget", "Preconfigured commands:"))
self.uiTelnetConsolePreconfiguredCommandPushButton.setText(_translate("GeneralPreferencesPageWidget", "&Edit"))
self.uiSerialConsoleCommandLabel.setText(_translate("GeneralPreferencesPageWidget", "Console application command for Serial:"))
self.uiSerialConsoleCommandLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><p>%d = device hostname</p><p>%s = device pipe file</p></body></html>"))
self.uiSerialConsolePreconfiguredCommandPushButton.setText(_translate("GeneralPreferencesPageWidget", "&Set"))
self.uiSerialConsolePreconfiguredCommandPushButton.setText(_translate("GeneralPreferencesPageWidget", "&Edit"))
self.uiConsoleMiscGroupBox.setTitle(_translate("GeneralPreferencesPageWidget", "Miscellaneous"))
self.uiCloseConsoleWindowsOnDeleteCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Close any connected console window when deleting a node"))
self.uiBringConsoleWindowToFrontCheckBox.setToolTip(_translate("GeneralPreferencesPageWidget", "<html>This option will attempt to bring existing opened console window to front, instead of opening a new window.<br>If no existing opened console window exists, it will start a new console window.</html>"))
@@ -414,10 +400,9 @@ class Ui_GeneralPreferencesPageWidget(object):
self.uiDelayConsoleAllSpinBox.setSuffix(_translate("GeneralPreferencesPageWidget", " ms"))
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.uiConsoleTab), _translate("GeneralPreferencesPageWidget", "Console applications"))
self.uiVNCConsoleSettingsGroupBox.setTitle(_translate("GeneralPreferencesPageWidget", "Settings for VNC connections"))
self.uiVNCConsolePreconfiguredCommandLabel.setText(_translate("GeneralPreferencesPageWidget", "Preconfigured commands:"))
self.uiVNCConsoleCommandLabel.setText(_translate("GeneralPreferencesPageWidget", "Console application command:"))
self.uiVNCConsoleCommandLabel.setText(_translate("GeneralPreferencesPageWidget", "Console application command for VNC:"))
self.uiVNCConsoleCommandLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><p>%h = device server </p><p>%p = device port</p><p>%d = device hostname</p></body></html>"))
self.uiVNCConsolePreconfiguredCommandPushButton.setText(_translate("GeneralPreferencesPageWidget", "&Set"))
self.uiVNCConsolePreconfiguredCommandPushButton.setText(_translate("GeneralPreferencesPageWidget", "&Edit"))
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.uiVNCTab), _translate("GeneralPreferencesPageWidget", "VNC"))
self.uiSceneWidthLabel.setText(_translate("GeneralPreferencesPageWidget", "Default width:"))
self.uiSceneHeightLabel.setText(_translate("GeneralPreferencesPageWidget", "Default height:"))

File diff suppressed because it is too large Load Diff

View File

@@ -38,6 +38,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):
"""
@@ -122,7 +162,7 @@ class VM(Node):
log.info("{} has started".format(self.name()))
self.setStatus(Node.started)
if result:
self._setupCallback(result)
self._updateCallback(result)
def _setupCallback(self, result, error=False, **kwargs):
"""
@@ -159,6 +199,24 @@ class VM(Node):
self._settings[name] = value
return True
def _updateCallback(self, result, error=False, **kwargs):
"""
Callback for update.
:param result: server response (dict)
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
return False
if "command_line" in result:
self._command_line = result["command_line"]
return True
def stop(self):
"""
Stops this VM instance.
@@ -367,3 +425,91 @@ class VM(Node):
self.server_error_signal.emit(self.id(), result["message"])
else:
PacketCapture.instance().stopCapture(self, context["port"])
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
# add the ports
if self._ports:
ports = device["ports"] = []
for port in self._ports:
ports.append(port.dump())
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"]
self._loading = True
self._node_info = node_info
self.loaded_signal.connect(self._updatePortSettings)
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())
def _updatePortSettings(self):
"""
Updates port settings when loading a topology.
"""
self.loaded_signal.disconnect(self._updatePortSettings)
# assign the correct names and IDs to the ports
if "ports" in self._node_info:
ports = self._node_info["ports"]
for topology_port in ports:
for port in self._ports:
if topology_port["port_number"] == port.portNumber():
# If the adapter is missing we consider that adapter_number == port_number
adapter_number = topology_port.get("adapter_number", topology_port["port_number"])
if port.adapterNumber() is None or adapter_number == port.adapterNumber() or topology_port.get("slot_number", None) == port.adapterNumber():
port.setName(topology_port["name"])
port.setId(topology_port["id"])
# now we can set the node as initialized and trigger the created signal
self.setInitialized(True)
log.info("{} has been loaded".format(self.name()))
self.created_signal.emit(self.id())
self._module.addNode(self)
self._loading = False
self._node_info = None

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

@@ -48,6 +48,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: