Compare commits

..

59 Commits

Author SHA1 Message Date
grossmj
29b851207b Release v2.2.44 2023-11-06 16:02:23 +10:00
grossmj
ca5557e579 Upgrade sentry-sdk 2023-11-06 15:44:30 +10:00
grossmj
7331ae29ef Fix timeout issue when creating Qemu disk image. Fixes https://github.com/GNS3/gns3-server/issues/2313 2023-11-05 15:41:46 +10:00
grossmj
3a7e06e14b Revert to subprocess.Popen since subprocess.call is the old API 2023-11-03 14:43:25 +10:00
grossmj
0a81af8248 Merge remote-tracking branch 'origin/2.2' into 2.2 2023-10-31 15:53:13 +10:00
grossmj
a882956ec9 Merge branch 'master' into 2.2 2023-10-31 15:52:45 +10:00
Jeremy Grossmann
9f4361d66f Merge pull request #3534 from GNS3/command-variables
Refactor command variables
2023-10-31 15:44:23 +10:00
grossmj
d10c3c7308 Fix tests 2023-10-31 15:40:50 +10:00
Jeremy Grossmann
0cd7d7e4c2 Merge branch '2.2' into command-variables 2023-10-31 15:07:49 +10:00
grossmj
8f3f72ff54 Fix tests 2023-10-31 15:07:35 +10:00
Jeremy Grossmann
7fbc0befa1 Merge branch '2.2' into command-variables 2023-10-31 14:51:53 +10:00
grossmj
9d9668442e Use Python 3.8 in appveyor.yml 2023-10-31 14:48:08 +10:00
grossmj
932083be88 Update custom command help and protect against double quote in project name 2023-10-31 14:43:50 +10:00
grossmj
82e7c151c4 Refactor command variables support 2023-10-31 12:33:52 +10:00
Jeremy Grossmann
7e5c363bc3 Merge pull request #3533 from GNS3/fix/2306
Allow local server to get $PATH environment variable
2023-10-30 14:06:17 +10:00
grossmj
15d029a7fb Pass os.environ in Popen() 2023-10-30 14:03:10 +10:00
Jeremy Grossmann
9087ba8f5a Merge pull request #3512 from magister990/style_edit_width_and_height
Add the ability to edit width and hight in the style edit dialog.
2023-10-24 18:25:58 +10:00
grossmj
3d89d6e6cc Fix issue with line item 2023-10-24 18:01:49 +10:00
grossmj
91bae81300 Merge branch '2.2' into style_edit_width_and_height 2023-10-24 17:40:35 +10:00
Jeremy Grossmann
7de5bf6bd5 Merge pull request #3516 from ventaquil/feature/add-qemu-igb-nic
Add Qemu IGB network device
2023-10-24 17:15:21 +10:00
Jeremy Grossmann
9de238619a Merge branch '2.2' into feature/add-qemu-igb-nic 2023-10-24 17:14:30 +10:00
grossmj
ed88466d63 Upgrade to actions/checkout@v3 and actions/setup-python@v3 2023-10-23 16:17:31 +10:00
grossmj
478b793b04 Merge branch 'master' into 2.2 2023-10-18 17:46:45 +10:00
grossmj
841c29e6f6 Upgrade sentry and psutil dependencies 2023-10-18 14:53:25 +10:00
grossmj
f9d96051f5 Downgrade to PyQt5 v5.15.9 2023-10-18 14:33:27 +10:00
grossmj
607e201674 Fix packaging issue on macOS 2023-10-18 14:29:53 +10:00
grossmj
18950ca64f Upgrade to PyQt5 v5.15.10 2023-10-18 13:24:45 +10:00
grossmj
c0b5f39c4c Add Python 3.12 support. Fixes https://github.com/GNS3/gns3-server/issues/2273 2023-10-09 16:54:47 +10:00
grossmj
3e717999ca Add vendor_logo_url in appliance schemas. Ref https://github.com/GNS3/gns3-registry/pull/825 2023-10-09 16:52:52 +10:00
Konrad Goławski
800d14363d Add Qemu IGB network device 2023-10-02 14:35:09 +02:00
Alex Scott
2dd9d61c57 Add the ability to edit width and hight in the style edit dialog. 2023-09-27 17:08:33 -05:00
grossmj
10afb5a8de Revert "Revert "Install importlib-resources only with Python < '3.9'. Ref #2147""
This reverts commit 3413afe952.
2023-09-23 20:50:39 +10:00
grossmj
3413afe952 Revert "Install importlib-resources only with Python < '3.9'. Ref #2147"
This reverts commit 7222da9512.
2023-09-23 14:47:56 +10:00
grossmj
7222da9512 Install importlib-resources only with Python < '3.9'. Ref #2147 2023-09-23 14:33:15 +10:00
grossmj
dbe8df5a37 Development on 2.2.44.dev1 2023-09-19 21:08:24 +07:00
Jeremy Grossmann
a9890265b9 Merge pull request #3509 from GNS3/release-v2.2.43
Release v2.2.43
2023-09-19 21:04:43 +07:00
grossmj
97b777ceea Release v2.2.43 2023-09-19 20:16:52 +07:00
grossmj
c06e534935 Merge branch 'master' into 2.2 2023-09-19 20:08:11 +07:00
grossmj
025276f8a7 Upgrade sentry-sdk and truststore 2023-09-19 20:07:57 +07:00
grossmj
6777961d29 Add KiTTY to preconfigured telnet consoles. Fixes #3507 2023-09-18 20:23:09 +07:00
grossmj
7f6cace0d5 Fix generic icon in Wayland. Ref #3501 2023-09-15 16:08:45 +07:00
Jeremy Grossmann
1a739c0c37 Merge pull request #3490 from GNS3/appliance-v8-support
Support for appliance version 8 format
2023-08-16 14:18:59 +10:00
grossmj
6d855045ef Show installation instructions when available and fix regression when installing Docker appliance. 2023-08-16 14:11:55 +10:00
grossmj
fef734bbbe Finalize appliance v8 support and add tests. 2023-08-16 00:30:02 +10:00
grossmj
b079443735 Merge remote-tracking branch 'origin/appliance-v8-support' into appliance-v8-support 2023-08-14 12:01:18 +10:00
grossmj
4a32ae9736 Drop "kvm" field. 2023-08-14 12:01:09 +10:00
Jeremy Grossmann
9793d00131 Merge branch '2.2' into appliance-v8-support 2023-08-13 12:21:18 +10:00
grossmj
2b7840279a Downgrade jsonschema 2023-08-12 17:51:24 +10:00
grossmj
9243083321 Upgrade dependencies 2023-08-12 17:47:48 +10:00
Jeremy Grossmann
3a8b3e5c4a Merge pull request #3506 from GNS3/fix/3505
Use importlib instead of pkg_resources
2023-08-12 17:26:51 +10:00
grossmj
e2168a3c81 Use importlib instead of pkg_resources 2023-08-12 17:20:33 +10:00
grossmj
01deb01e6a Upgrade to PyQt 5.15.9 and pywin32 2023-08-11 18:13:41 +10:00
grossmj
1133ee6e1b Development on v2.2.43.dev1 2023-08-09 22:15:15 +10:00
Jeremy Grossmann
7512ffec64 Merge pull request #3503 from GNS3/release-v2.2.42
Release v2.2.42
2023-08-09 22:08:18 +10:00
grossmj
a58451a552 Update to support template_type & template_properties 2023-07-11 17:38:00 +10:00
grossmj
0a43b9e6e9 Fix tests 2023-07-09 20:18:22 +10:00
grossmj
8a5ab6b374 Update schema for appliance version 8 2023-07-09 14:10:15 +10:00
grossmj
4b7cf4e553 Support legacy "idlepc" field 2023-07-03 19:12:44 +10:00
grossmj
b72358461c Add support for appliance version 8 format 2023-06-25 16:54:12 +09:30
59 changed files with 1987 additions and 326 deletions

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Build and run Docker image
run: |
docker build -t gns3-gui-test .

View File

@@ -1,5 +1,22 @@
# Change Log
## 2.2.44 06/11/2023
* Fix timeout issue when creating Qemu disk image. Fixes https://github.com/GNS3/gns3-server/issues/2313
* Refactor command variables support
* Add vendor_logo_url in appliance schemas. Ref https://github.com/GNS3/gns3-registry/pull/825
* Add Qemu IGB network device
* Add the ability to edit width and height in the style edit dialog.
## 2.2.43 19/09/2023
* Add KiTTY to preconfigured telnet consoles. Fixes #3507
* Fix generic icon in Wayland. Ref #3501
* Support for appliance format version 8.
* Use importlib instead of pkg_resources
* Upgrade to PyQt 5.15.9 and pywin32
* Add support for appliance version 8 format
## 2.2.42 09/08/2023
* Use the system's certificate store for SSL connections

View File

@@ -5,7 +5,7 @@ image: Visual Studio 2022
platform: x64
environment:
PYTHON: "C:\\Python37-x64"
PYTHON: "C:\\Python38-x64"
DISTUTILS_USE_SDK: "1"
install:

View File

@@ -46,6 +46,9 @@ class Application(QtWidgets.QApplication):
super().__init__(argv)
# this is tell Wayland what is the name of the desktop file (gns3.desktop)
self.setDesktopFileName("gns3")
# this info is necessary for QSettings
self.setOrganizationName("GNS3")
self.setOrganizationDomain("gns3.net")

View File

@@ -50,7 +50,7 @@ class CrashReport:
Report crash to a third party service
"""
DSN = "https://bae0411a1718612ee8c25cdb12ec7f02@o19455.ingest.sentry.io/38506"
DSN = "https://5c5fb544e56f9d41179e57bfff78e151@o19455.ingest.sentry.io/38506"
_instance = None
def __init__(self):

View File

@@ -94,9 +94,11 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
self.setWindowTitle("Install {} appliance".format(self._appliance["name"]))
# add a custom button to show appliance information
self.setButtonText(QtWidgets.QWizard.CustomButton1, "&Appliance info")
self.setOption(QtWidgets.QWizard.HaveCustomButton1, True)
self.customButtonClicked.connect(self._showApplianceInfoSlot)
if self._appliance["registry_version"] < 8:
# FIXME: show appliance info for v8
self.setButtonText(QtWidgets.QWizard.CustomButton1, "&Appliance info")
self.setOption(QtWidgets.QWizard.HaveCustomButton1, True)
self.customButtonClicked.connect(self._showApplianceInfoSlot)
# customize the server selection
self.uiRemoteRadioButton.toggled.connect(self._remoteServerToggledSlot)
@@ -144,18 +146,9 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if self.page(page_id) == self.uiServerWizardPage:
Controller.instance().getSymbols(self._getSymbolsCallback)
if "qemu" in self._appliance:
emulator_type = "qemu"
elif "iou" in self._appliance:
emulator_type = "iou"
elif "docker" in self._appliance:
emulator_type = "docker"
elif "dynamips" in self._appliance:
emulator_type = "dynamips"
else:
QtWidgets.QMessageBox.warning(self, "Appliance", "Could not determine the emulator type")
template_type = self._appliance.template_type()
if not template_type:
raise ApplianceError("No template type found for appliance {}".format(self._appliance["name"]))
is_mac = ComputeManager.instance().localPlatform().startswith("darwin")
is_win = ComputeManager.instance().localPlatform().startswith("win")
@@ -173,11 +166,11 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if ComputeManager.instance().localPlatform() is None:
self.uiLocalRadioButton.setEnabled(False)
elif is_mac or is_win:
if emulator_type == "qemu":
if template_type == "qemu":
# disallow usage of the local server because Qemu has issues on OSX and Windows
if not LocalConfig.instance().experimental():
self.uiLocalRadioButton.setEnabled(False)
elif emulator_type != "dynamips":
elif template_type != "dynamips":
self.uiLocalRadioButton.setEnabled(False)
if ComputeManager.instance().vmCompute():
@@ -195,27 +188,55 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
elif self.page(page_id) == self.uiFilesWizardPage:
if Controller.instance().isRemote() or self._compute_id != "local":
self._registry.getRemoteImageList(self._appliance.emulator(), self._compute_id)
self._registry.getRemoteImageList(self._appliance.template_type(), self._compute_id)
else:
self.images_changed_signal.emit()
elif self.page(page_id) == self.uiQemuWizardPage:
if self._appliance['qemu'].get('kvm', 'require') == 'require':
if self._appliance.template_properties().get('kvm', 'require') == 'require':
self._server_check = False
Qemu.instance().getQemuCapabilitiesFromServer(self._compute_id, qpartial(self._qemuServerCapabilitiesCallback))
else:
self._server_check = True
Qemu.instance().getQemuBinariesFromServer(self._compute_id, qpartial(self._getQemuBinariesFromServerCallback), [self._appliance["qemu"]["arch"]])
if self._appliance["registry_version"] >= 8:
qemu_platform = self._appliance.template_properties()["platform"]
else:
qemu_platform = self._appliance.template_properties()["arch"]
Qemu.instance().getQemuBinariesFromServer(self._compute_id, qpartial(self._getQemuBinariesFromServerCallback), [qemu_platform])
elif self.page(page_id) == self.uiInstructionsPage:
installation_instructions = self._appliance.get("installation_instructions", "No installation instructions available")
self.uiInstructionsTextEdit.setText(installation_instructions.strip())
elif self.page(page_id) == self.uiUsageWizardPage:
self.uiUsageTextEdit.setText("The template will be available in the {} category.\n\n{}".format(self._appliance["category"].replace("_", " "), self._appliance.get("usage", "")))
# TODO: allow taking these info fields at the version level in v8
category = self._appliance["category"].replace("_", " ")
usage = self._appliance.get("usage", "No usage information available")
if self._appliance["registry_version"] >= 8:
default_username = self._appliance.get("default_username")
default_password = self._appliance.get("default_password")
if default_username and default_password:
usage += "\n\nDefault username: {}\nDefault password: {}".format(default_username, default_password)
usage_info = """
The template will be available in the {} category.
Usage: {}
""".format(category, usage)
self.uiUsageTextEdit.setText(usage_info.strip())
def _qemuServerCapabilitiesCallback(self, result, error=None, *args, **kwargs):
"""
Check if the server supports KVM or not
"""
if error is None and "kvm" in result and self._appliance["qemu"]["arch"] in result["kvm"]:
if self._appliance["registry_version"] >= 8:
qemu_platform = self._appliance.template_properties()["platform"]
else:
qemu_platform = self._appliance.template_properties()["arch"]
if error is None and "kvm" in result and qemu_platform in result["kvm"]:
self._server_check = True
else:
if error:
@@ -236,7 +257,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
log.error("Error while uploading image '{}': {}".format(image_path, result["message"]))
else:
log.info("Image '{}' has been successfully uploaded".format(image_path))
self._registry.getRemoteImageList(self._appliance.emulator(), self._compute_id)
self._registry.getRemoteImageList(self._appliance.template_type(), self._compute_id)
def _showApplianceInfoSlot(self):
"""
@@ -407,7 +428,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
for version in self._appliance["versions"]:
for image in version["images"].values():
img = self._registry.search_image_file(self._appliance.emulator(),
img = self._registry.search_image_file(self._appliance.template_type(),
image["filename"],
image.get("md5sum"),
image.get("filesize"),
@@ -519,7 +540,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if len(path) == 0:
return
image = Image(self._appliance.emulator(), path, filename=disk["filename"])
image = Image(self._appliance.template_type(), path, filename=disk["filename"])
try:
if "md5sum" in disk and image.md5sum != disk["md5sum"]:
reply = QtWidgets.QMessageBox.question(self, "Add appliance",
@@ -554,7 +575,11 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if self.uiQemuListComboBox.count() == 1:
self.next()
else:
i = self.uiQemuListComboBox.findData(self._appliance["qemu"]["arch"], flags=QtCore.Qt.MatchEndsWith)
if self._appliance["registry_version"] >= 8:
qemu_platform = self._appliance.template_properties()["platform"]
else:
qemu_platform = self._appliance.template_properties()["arch"]
i = self.uiQemuListComboBox.findData(qemu_platform, flags=QtCore.Qt.MatchEndsWith)
if i != -1:
self.uiQemuListComboBox.setCurrentIndex(i)
@@ -567,8 +592,8 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if version is None:
appliance_configuration = self._appliance.copy()
if "docker" not in appliance_configuration:
# only Docker do not have version
if self._appliance.template_type() != "docker":
# only Docker do not have versions
return False
else:
try:
@@ -585,10 +610,15 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
return False
appliance_configuration["name"] = appliance_configuration["name"].strip()
if "qemu" in appliance_configuration:
if self._appliance["registry_version"] >= 8:
if "settings" in appliance_configuration:
for settings in appliance_configuration["settings"]:
if settings["template_type"] == "qemu":
settings["template_properties"]["path"] = self.uiQemuListComboBox.currentData()
elif "qemu" in appliance_configuration:
appliance_configuration["qemu"]["path"] = self.uiQemuListComboBox.currentData()
new_template = ApplianceToTemplate().new_template(appliance_configuration, self._compute_id, self._symbols, parent=self)
new_template = ApplianceToTemplate().new_template(appliance_configuration, self._compute_id, version, self._symbols, parent=self)
TemplateManager.instance().createTemplate(Template(new_template), callback=self._templateCreatedCallback)
return False
@@ -632,7 +662,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if not Controller.instance().isRemote() and self._compute_id == "local" and image["path"].startswith(ImageManager.instance().getDirectory()):
log.debug("{} is already on the local server".format(image["path"]))
return
image = Image(self._appliance.emulator(), image["path"], filename=image["filename"])
image = Image(self._appliance.template_type(), image["path"], filename=image["filename"])
image_upload_manager = ImageUploadManager(image, Controller.instance(), self._compute_id, self._applianceImageUploadedCallback, LocalConfig.instance().directFileUpload())
image_upload_manager.upload()
self._image_uploading_count += 1
@@ -649,12 +679,16 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
def nextId(self):
if self.currentPage() == self.uiServerWizardPage:
if "docker" in self._appliance:
if self._appliance.template_type() == "docker":
# skip Qemu binary selection and files pages if this is a Docker appliance
return super().nextId() + 2
elif "qemu" not in self._appliance:
return super().nextId() + 3
elif self._appliance.template_type() != "qemu":
# skip the Qemu binary selection page if not a Qemu appliance
return super().nextId() + 1
if self.currentPage() == self.uiQemuWizardPage:
if not self._appliance.get("installation_instructions"):
# skip the installation instructions page if there are no instructions
return super().nextId() + 1
return super().nextId()
def validateCurrentPage(self):
@@ -722,7 +756,6 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
elif self.currentPage() == self.uiQemuWizardPage:
# validate the Qemu
if self._server_check is False:
QtWidgets.QMessageBox.critical(self, "Checking for KVM support", "Please wait for the server to reply...")
return False

View File

@@ -78,8 +78,20 @@ class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog):
if not corner_radius:
corner_radius = first_item.verticalCornerRadius()
self.uiCornerRadiusSpinBox.setValue(corner_radius)
else:
self.uiCornerRadiusLabel.hide()
self.uiCornerRadiusSpinBox.hide()
self.uiRotationSpinBox.setValue(int(first_item.rotation()))
self.uiBorderWidthSpinBox.setValue(pen.width())
if isinstance(first_item, ShapeItem):
rect = first_item.rect()
self.uiWidthSpinBox.setValue(int(rect.width()))
self.uiHeightSpinBox.setValue(int(rect.height()))
else:
self.uiWidthSpinBox.hide()
self.uiWidthLabel.hide()
self.uiHeightSpinBox.hide()
self.uiHeightLabel.hide()
index = self.uiBorderStyleComboBox.findData(pen.style())
if index != -1:
self.uiBorderStyleComboBox.setCurrentIndex(index)
@@ -134,6 +146,8 @@ class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog):
# maybe we support setting them separately in the future
item.setHorizontalCornerRadius(corner_radius)
item.setVerticalCornerRadius(corner_radius)
if isinstance(item, ShapeItem):
item.setWidthAndHeight(self.uiWidthSpinBox.value(), self.uiHeightSpinBox.value())
item.setRotation(self.uiRotationSpinBox.value())
def done(self, result):

View File

@@ -171,6 +171,9 @@ class ShapeItem(DrawingItem):
if not self.locked():
self._graphics_view.setCursor(QtCore.Qt.ArrowCursor)
def setWidthAndHeight(self, width, height):
self.setRect(0, 0, width, height)
def fromSvg(self, svg):
"""
Import element information from SVG

View File

@@ -482,11 +482,15 @@ class LocalServer(QtCore.QObject):
try:
if sys.platform.startswith("win"):
# use the string on Windows
self._local_server_process = subprocess.Popen(command, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, stderr=subprocess.PIPE)
self._local_server_process = subprocess.Popen(
command,
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP,
stderr=subprocess.PIPE,
env=os.environ)
else:
# use arguments on other platforms
args = shlex.split(command)
self._local_server_process = subprocess.Popen(args, stderr=subprocess.PIPE)
self._local_server_process = subprocess.Popen(args, stderr=subprocess.PIPE, env=os.environ)
except (OSError, subprocess.SubprocessError) as e:
log.warning('Could not start local server "{}": {}'.format(command, e))
return False

View File

@@ -30,16 +30,6 @@ try:
except Exception as e:
print("Fail update installation: {}".format(str(e)))
# WARNING
# Due to buggy user machines we choose to put this as the first loading modules
# otherwise the egg cache is initialized in his standard location and
# if is not writetable the application crash. It's the user fault
# because one day the user as used sudo to run an egg and break his
# filesystem permissions, but it's a common mistake.
from gns3.utils.get_resource import get_resource
import datetime
import traceback
import time

View File

@@ -57,6 +57,7 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
self.uiPrivateConfigToolButton.hide()
# location of the base config templates
# FIXME: this does not work
self._base_iou_l2_config_template = get_resource(os.path.join("configs", "iou_l2_base_startup-config.txt"))
self._base_iou_l3_config_template = get_resource(os.path.join("configs", "iou_l3_base_startup-config.txt"))
self._default_configs_dir = LocalServer.instance().localServerSettings()["configs_path"]

View File

@@ -121,7 +121,7 @@ class Qemu(Module):
:param options: Options for the image creation
"""
Controller.instance().postCompute("/qemu/img", compute_id, callback, body=options)
Controller.instance().postCompute("/qemu/img", compute_id, callback, timeout=None, body=options)
def updateDiskImage(self, compute_id, callback, options):
"""

View File

@@ -101,7 +101,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self.uiOnCloseComboBox.addItem(name, option_name)
# Supported NIC models: e1000, e1000-82544gc, e1000-82545em, e1000e, i82550, i82551, i82557a, i82557b, i82557c, i82558a
# i82558b, i82559a, i82559b, i82559c, i82559er, i82562, i82801, ne2k_pci, pcnet, rocker, rtl8139, virtio-net-pci, vmxnet3
# i82558b, i82559a, i82559b, i82559c, i82559er, i82562, i82801, igb, ne2k_pci, pcnet, rocker, rtl8139, virtio-net-pci, vmxnet3
# This list can be retrieved using "qemu-system-x86_64 -nic model=?" or "qemu-system-x86_64 -device help"
self._legacy_devices = ("e1000", "i82551", "i82557b", "i82559er", "ne2k_pci", "pcnet", "rtl8139", "virtio")
self._qemu_network_devices = OrderedDict([("e1000", "Intel Gigabit Ethernet"),
@@ -121,6 +121,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
("i82559er", "Intel i82559ER Ethernet"),
("i82562", "Intel i82562 Ethernet"),
("i82801", "Intel i82801 Ethernet"),
("igb", "Intel 82576 Gigabit Ethernet"),
("ne2k_pci", "NE2000 Ethernet"),
("pcnet", "AMD PCNet Ethernet"),
("rocker", "Rocker L2 switch device"),

View File

@@ -146,13 +146,19 @@ class PacketCapture:
# PCAP capture file path
command = command.replace("%c", '"' + capture_file_path + '"')
command = command.replace("{pcap_file}", '"' + capture_file_path + '"')
# Add description
# Add project name
command = command.replace("%P", link.project().name())
command = command.replace("{project}", link.project().name().replace('"', '\\"'))
# Add link description
description = "{}[{}]->{}[{}]".format(link.sourceNode().name(),
link.sourcePort().name(),
link.destinationNode().name(),
link.destinationPort().name())
command = command.replace("%d", description)
command = command.replace("{link_description}", description)
if not sys.platform.startswith("win"):
command = shlex.split(command)
@@ -160,7 +166,7 @@ class PacketCapture:
QtWidgets.QMessageBox.critical(self.parent(), "Packet Capture Analyzer", "No packet capture analyzer program configured")
return
try:
subprocess.Popen(command)
subprocess.Popen(command, env=os.environ)
except OSError as e:
QtWidgets.QMessageBox.critical(self.parent(), "Packet Capture Analyzer", "Can't start packet capture analyzer program {}".format(str(e)))
return
@@ -204,13 +210,19 @@ class PacketCapture:
# PCAP capture file path
command = command.replace("%c", '"' + capture_file_path + '"')
command = command.replace("{pcap_file}", '"' + capture_file_path + '"')
# Add description
# Add project name
command = command.replace("%P", link.project().name())
command = command.replace("{project}", link.project().name().replace('"', '\\"'))
# Add link description
description = "{} {} to {} {}".format(link.sourceNode().name(),
link.sourcePort().name(),
link.destinationNode().name(),
link.destinationPort().name())
command = command.replace("%d", description)
command = command.replace("{link_description}", description)
if "|" in command:
# live traffic capture (using tail)

View File

@@ -21,11 +21,13 @@ import copy
import os
import collections.abc
import jsonschema
from gns3.utils.get_resource import get_resource
import logging
log = logging.getLogger(__name__)
class ApplianceError(Exception):
pass
@@ -38,6 +40,7 @@ class Appliance(collections.abc.Mapping):
:params path: Path of the appliance file on disk or file content
"""
self._registry = registry
self._registry_version = None
if os.path.isabs(path):
try:
@@ -58,16 +61,25 @@ class Appliance(collections.abc.Mapping):
:param appliance: Sanity check on the appliance configuration
"""
if "registry_version" not in self._appliance:
raise ApplianceError("Invalid appliance configuration please report the issue on https://github.com/GNS3/gns3-registry")
if self._appliance["registry_version"] > 7:
raise ApplianceError("Please update GNS3 in order to install this appliance")
raise ApplianceError("Invalid appliance configuration please report the issue on https://github.com/GNS3/gns3-registry/issues")
with open(get_resource(os.path.join("schemas", "appliance.json"))) as f:
self._registry_version = self._appliance["registry_version"]
if self._registry_version > 8:
# we only support registry version 8 and below
raise ApplianceError("Registry version {} is not supported in this version of GNS3".format(self._registry_version))
if self._registry_version == 8:
# registry version 8 has a different schema with support for multiple settings sets
appliance_file = "appliance_v8.json"
else:
appliance_file = "appliance.json"
with open(get_resource("schemas/{}".format(appliance_file))) as f:
schema = json.load(f)
v = jsonschema.Draft4Validator(schema)
try:
v.validate(self._appliance)
except jsonschema.ValidationError as e:
except jsonschema.ValidationError:
error = jsonschema.exceptions.best_match(v.iter_errors(self._appliance)).message
raise ApplianceError("Invalid appliance file: {}".format(error))
@@ -82,10 +94,11 @@ class Appliance(collections.abc.Mapping):
def _resolve_version(self):
"""
Replace image field in versions by their the complete information from images
Replace image field in versions by the complete information from images
"""
if "versions" not in self._appliance:
log.debug("No versions found in appliance")
return
for version in self._appliance["versions"]:
@@ -96,7 +109,7 @@ class Appliance(collections.abc.Mapping):
for file in self._appliance["images"]:
file = copy.copy(file)
if "idlepc" in version:
if self._registry_version < 8 and "idlepc" in version:
file["idlepc"] = version["idlepc"]
if "/" in filename:
@@ -127,7 +140,7 @@ class Appliance(collections.abc.Mapping):
def search_images_for_version(self, version_name):
"""
Search on disk the images required by this version.
And keep only the require images in the images fields. Add to the images
And keep only the required images in the images fields. Add to the images
their disk type and path.
:param version_name: Version name
@@ -142,10 +155,18 @@ class Appliance(collections.abc.Mapping):
for image_type, image in version["images"].items():
image["type"] = image_type
img = self._registry.search_image_file(self.emulator(), image["filename"], image.get("md5sum"), image.get("filesize"))
checksum = image.get("md5sum")
if checksum is None and self._registry_version >= 8:
# registry version >= 8 has the checksum and checksum_type fields
checksum_type = image.get("checksum_type", "md5") # md5 is the default and only supported type
if checksum_type != "md5":
raise ApplianceError("Checksum type {} is not supported".format(checksum_type))
checksum = image.pop("checksum")
img = self._registry.search_image_file(self.template_type(), image["filename"], checksum, image.get("filesize"))
if img is None:
if "md5sum" in image:
raise ApplianceError("File {} with checksum {} not found for {}".format(image["filename"], image["md5sum"], appliance["name"]))
if checksum:
raise ApplianceError("File {} with checksum {} not found for {}".format(image["filename"], checksum, appliance["name"]))
else:
raise ApplianceError("File {} not found for {}".format(image["filename"], appliance["name"]))
@@ -186,9 +207,51 @@ class Appliance(collections.abc.Mapping):
except ApplianceError:
return False
def emulator(self):
if "qemu" in self._appliance:
return "qemu"
if "iou" in self._appliance:
return "iou"
return "dynamips"
def template_type(self):
if self._registry_version >= 8:
template_type = None
for settings in self._appliance["settings"]:
if settings["template_type"] and not template_type:
template_type = settings["template_type"]
elif settings["template_type"] and template_type != settings["template_type"]:
# we are currently not supporting multiple different template types in the same appliance
raise ApplianceError("Multiple different template types found in appliance")
if not template_type:
raise ApplianceError("No template type found in appliance {}".format(self._appliance["name"]))
return template_type
else:
if "qemu" in self._appliance:
return "qemu"
if "iou" in self._appliance:
return "iou"
if "dynamips" in self._appliance:
return "dynamips"
if "docker" in self._appliance:
return "docker"
return None
def template_properties(self):
"""
Get template properties
"""
if self._registry_version >= 8:
# find the default settings if any
for settings in self._appliance["settings"]:
if settings.get("default", False):
return settings["template_properties"]
# otherwise take the first settings we find
for settings in self._appliance["settings"]:
if settings["template_type"]:
return settings["template_properties"]
else:
if "qemu" in self._appliance:
return self._appliance["qemu"]
if "iou" in self._appliance:
return self._appliance["iou"]
if "dynamips" in self._appliance:
return self._appliance["dynamips"]
if "docker" in self._appliance:
return self._appliance["docker"]
return None

View File

@@ -34,7 +34,7 @@ class ApplianceToTemplate:
Appliance installation.
"""
def new_template(self, appliance_config, server, controller_symbols=None, parent=None):
def new_template(self, appliance_config, server, appliance_version=None, controller_symbols=None, parent=None):
"""
Creates a new template from an appliance.
@@ -43,6 +43,7 @@ class ApplianceToTemplate:
"""
self._parent = parent
self._registry_version = appliance_config["registry_version"]
new_template = {
"compute_id": server,
"name": appliance_config["name"]
@@ -74,45 +75,101 @@ class ApplianceToTemplate:
elif appliance_config["category"] == "firewall":
new_template["symbol"] = ":/symbols/firewall.svg"
if "qemu" in appliance_config:
new_template["template_type"] = "qemu"
self._add_qemu_config(new_template, appliance_config)
elif "iou" in appliance_config:
new_template["template_type"] = "iou"
self._add_iou_config(new_template, appliance_config)
elif "dynamips" in appliance_config:
new_template["template_type"] = "dynamips"
self._add_dynamips_config(new_template, appliance_config)
elif "docker" in appliance_config:
new_template["template_type"] = "docker"
self._add_docker_config(new_template, appliance_config)
if self._registry_version >= 8:
if appliance_version:
for version in appliance_config["versions"]:
if appliance_version and version["name"] == appliance_version:
# inject "usage", "category" and "symbol" specified at the version
# level into the template properties
usage = version.get("usage")
if usage:
new_template["usage"] = usage
new_template["symbol"] = version.get("symbol", new_template["symbol"])
new_template["category"] = version.get("category", new_template["category"])
settings = self._get_settings(appliance_config, version.get("settings"))
template_type = settings["template_type"]
if template_type == "qemu":
self._add_qemu_config(new_template, settings["template_properties"], appliance_config)
elif template_type == "iou":
self._add_iou_config(new_template, settings["template_properties"], appliance_config)
elif template_type == "dynamips":
self._add_dynamips_config(new_template, settings["template_properties"], appliance_config)
else:
# docker appliances have no version
settings = self._get_settings(appliance_config)
if settings["template_type"] == "docker":
self._add_docker_config(new_template, settings["template_properties"], appliance_config)
else:
raise ConfigException("{} no configuration found for known emulators".format(new_template["name"]))
if "qemu" in appliance_config:
self._add_qemu_config(new_template, appliance_config["qemu"], appliance_config)
elif "iou" in appliance_config:
self._add_iou_config(new_template, appliance_config["iou"], appliance_config)
elif "dynamips" in appliance_config:
self._add_dynamips_config(new_template, appliance_config["dynamips"], appliance_config)
elif "docker" in appliance_config:
self._add_docker_config(new_template, appliance_config["docker"], appliance_config)
else:
raise ConfigException("{} no configuration found for known emulators".format(new_template["name"]))
return new_template
def _add_qemu_config(self, new_config, appliance_config):
def _get_settings(self, appliance_config, settings_name=None):
new_config.update(appliance_config["qemu"])
default_settings = None
# first look for default settings, if any ('default' = true, first set that has it)
for settings in appliance_config["settings"]:
if settings.get("default", False):
default_settings = settings
break
# then look for specific settings set if a name is provided
if settings_name:
for settings in appliance_config["settings"]:
if settings.get("name") == settings_name:
if settings.get("inherit_default_properties", True) and \
default_settings and default_settings["template_type"] == settings["template_type"]:
default_settings["template_properties"].update(settings["template_properties"])
return default_settings
return settings
raise ConfigException("Settings '{}' cannot be found in the appliance file", settings_name)
elif default_settings:
return default_settings
if not appliance_config.get("settings"):
raise ConfigException("No settings found in the appliance file")
# if no default settings are specified, use the first available settings set
return appliance_config["settings"][0]
def _add_qemu_config(self, new_config, template_properties, appliance_config):
new_config["template_type"] = "qemu"
new_config.update(template_properties)
# the following properties are not valid for a template
new_config.pop("kvm", None)
new_config.pop("path", None)
new_config.pop("arch", None)
new_config.pop("kvm", None) # To check KVM setting against the server capabilities
new_config.pop("path", None) # Qemu binary selected in previous step
new_config.pop("arch", None) # Used for selecting the Qemu binary
options = appliance_config["qemu"].get("options", "")
if appliance_config["qemu"].get("kvm", "allow") == "disable" and "-machine accel=tcg" not in options:
options = template_properties.get("options", "")
if template_properties.get("kvm", "allow") == "disable" and "-machine accel=tcg" not in options:
options += " -machine accel=tcg"
new_config["options"] = options.strip()
options = options.strip()
if options:
new_config["options"] = options
for image in appliance_config["images"]:
if image.get("path"):
new_config[image["type"]] = self._relative_image_path("QEMU", image["path"])
if "path" in appliance_config["qemu"]:
new_config["qemu_path"] = appliance_config["qemu"]["path"]
if "path" in template_properties:
new_config["qemu_path"] = template_properties["path"]
else:
new_config["qemu_path"] = "qemu-system-{}".format(appliance_config["qemu"]["arch"])
if self._registry_version >= 8:
# the "arch" field was replaced by the "platform" field in registry version 8
new_config["qemu_path"] = "qemu-system-{}".format(template_properties["platform"])
else:
new_config["qemu_path"] = "qemu-system-{}".format(template_properties["arch"])
if "first_port_name" in appliance_config:
new_config["first_port_name"] = appliance_config["first_port_name"]
@@ -129,24 +186,28 @@ class ApplianceToTemplate:
if "linked_clone" in appliance_config:
new_config["linked_clone"] = appliance_config["linked_clone"]
def _add_docker_config(self, new_config, appliance_config):
def _add_docker_config(self, new_config, template_properties, appliance_config):
new_config.update(appliance_config["docker"])
new_config["template_type"] = "docker"
new_config.update(template_properties)
if "custom_adapters" in appliance_config:
new_config["custom_adapters"] = appliance_config["custom_adapters"]
def _add_dynamips_config(self, new_config, appliance_config):
def _add_dynamips_config(self, new_config, template_properties, appliance_config):
new_config.update(appliance_config["dynamips"])
new_config["template_type"] = "dynamips"
new_config.update(template_properties)
for image in appliance_config["images"]:
new_config[image["type"]] = self._relative_image_path("IOS", image["path"])
new_config["idlepc"] = image.get("idlepc", "")
if self._registry_version < 8:
new_config["idlepc"] = image.get("idlepc", "")
def _add_iou_config(self, new_config, appliance_config):
def _add_iou_config(self, new_config, template_properties, appliance_config):
new_config.update(appliance_config["iou"])
new_config["template_type"] = "iou"
new_config.update(template_properties)
for image in appliance_config["images"]:
if "path" not in image:
raise ConfigException("Disk image is missing")

View File

@@ -71,6 +71,11 @@
"format": "uri",
"title": "Website of the vendor"
},
"vendor_logo_url": {
"type": "string",
"format": "uri",
"title": "Link to the vendor logo (used by the GNS3 marketplace)"
},
"documentation_url": {
"type": "string",
"format": "uri",
@@ -86,7 +91,7 @@
"title": "An optional product url on vendor website"
},
"registry_version": {
"enum": [1, 2, 3, 4, 5, 6],
"enum": [1, 2, 3, 4, 5, 6, 7],
"title": "Version of the registry compatible with this appliance"
},
"status": {
@@ -282,6 +287,7 @@
"i82559er",
"i82562",
"i82801",
"igb",
"ne2k_pci",
"pcnet",
"rocker",
@@ -400,7 +406,6 @@
"md5sum": {
"type": "string",
"title": "md5sum of the file",
"type": "string",
"pattern": "^[a-f0-9]{32}$"
},
"filesize": {

View File

@@ -0,0 +1,905 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"title": "JSON schema validating a GNS3 appliance",
"definitions": {
"categories": {
"enum": [
"router",
"multilayer_switch",
"firewall",
"guest"
]
},
"docker_properties": {
"type": "object",
"title": "Docker template properties",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"title": "Name of the template"
},
"category": {
"$ref": "#/definitions/categories",
"title": "Category of the template"
},
"default_name_format": {
"type": "string",
"title": "Default name format"
},
"usage": {
"type": "string",
"title": "How to use the template"
},
"symbol": {
"type": "string",
"title": "Symbol of the template"
},
"image": {
"type": "string",
"title": "Docker image"
},
"adapters": {
"type": "integer",
"title": "Number of ethernet adapters"
},
"start_command": {
"type": "string",
"title": "Command executed when the container start. Empty will use the default"
},
"environment": {
"type": "string",
"title": "One KEY=VAR environment by line"
},
"console_type": {
"enum": [
"telnet",
"vnc",
"http",
"https"
],
"title": "Type of console"
},
"console_http_port": {
"title": "Internal port in the container of the HTTP server",
"type": "integer"
},
"console_http_path": {
"title": "Path of the web interface",
"type": "string"
},
"console_resolution": {
"title": "Console resolution for VNC, for example 1024x768",
"type": "string",
"pattern": "^[0-9]+x[0-9]+$"
},
"extra_hosts": {
"title": "Docker extra hosts (added to /etc/hosts)",
"type": "string"
},
"extra_volumes": {
"title": "Additional directories to make persistent",
"type": "array"
}
},
"required": [
"image"
]
},
"iou_properties": {
"type": "object",
"title": "IOU template properties",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"title": "Name of the template"
},
"category": {
"$ref": "#/definitions/categories",
"title": "Category of the template"
},
"default_name_format": {
"type": "string",
"title": "Default name format"
},
"usage": {
"type": "string",
"title": "How to use the template"
},
"symbol": {
"type": "string",
"title": "Symbol of the template"
},
"ethernet_adapters": {
"type": "integer",
"title": "Number of ethernet adapters"
},
"serial_adapters": {
"type": "integer",
"title": "Number of serial adapters"
},
"ram": {
"type": "integer",
"title": "Host RAM"
},
"nvram": {
"type": "integer",
"title": "Host NVRAM"
},
"startup_config": {
"type": "string",
"title": "Config loaded at startup"
}
}
},
"dynamips_slot": {
"enum": [
"C2600-MB-2FE",
"C2600-MB-1E",
"PA-A1",
"PA-8E",
"C1700-MB-1FE",
"PA-8T",
"PA-2FE-TX",
"PA-FE-TX",
"PA-GE",
"C2600-MB-2E",
"C7200-IO-FE",
"NM-4T",
"C2600-MB-1FE",
"C7200-IO-2FE",
"PA-POS-OC3",
"PA-4T+",
"C1700-MB-WIC1",
"NM-16ESW",
"C7200-IO-GE-E",
"NM-4E",
"GT96100-FE",
"NM-1FE-TX",
"Leopard-2FE",
"NM-1E",
"PA-4E",
""
]
},
"dynamips_wic": {
"enum": [
"WIC-1ENET",
"WIC-1T",
"WIC-2T",
""
]
},
"dynamips_properties": {
"type": "object",
"title": "Dynamips template properties",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"title": "Name of the template"
},
"category": {
"$ref": "#/definitions/categories",
"title": "Category of the template"
},
"default_name_format": {
"type": "string",
"title": "Default name format"
},
"usage": {
"type": "string",
"title": "How to use the template"
},
"symbol": {
"type": "string",
"title": "Symbol of the template"
},
"chassis": {
"title": "Chassis type",
"enum": [
"1720",
"1721",
"1750",
"1751",
"1760",
"2610",
"2620",
"2610XM",
"2620XM",
"2650XM",
"2621",
"2611XM",
"2621XM",
"2651XM",
"3620",
"3640",
"3660",
""
]
},
"platform": {
"title": "Platform type",
"enum": [
"c1700",
"c2600",
"c2691",
"c3725",
"c3745",
"c3600",
"c7200"
]
},
"ram": {
"title": "Amount of ram",
"type": "integer",
"minimum": 1
},
"nvram": {
"title": "Amount of nvram",
"type": "integer",
"minimum": 1
},
"idlepc": {
"type": "string",
"pattern": "^0x[0-9a-f]{8}"
},
"startup_config": {
"type": "string",
"title": "Config loaded at startup"
},
"wic0": {
"$ref": "#/definitions/dynamips_wic"
},
"wic1": {
"$ref": "#/definitions/dynamips_wic"
},
"wic2": {
"$ref": "#/definitions/dynamips_wic"
},
"slot0": {
"$ref": "#/definitions/dynamips_slot"
},
"slot1": {
"$ref": "#/definitions/dynamips_slot"
},
"slot2": {
"$ref": "#/definitions/dynamips_slot"
},
"slot3": {
"$ref": "#/definitions/dynamips_slot"
},
"slot4": {
"$ref": "#/definitions/dynamips_slot"
},
"slot5": {
"$ref": "#/definitions/dynamips_slot"
},
"slot6": {
"$ref": "#/definitions/dynamips_slot"
},
"midplane": {
"enum": [
"std",
"vxr"
]
},
"npe": {
"enum": [
"npe-100",
"npe-150",
"npe-175",
"npe-200",
"npe-225",
"npe-300",
"npe-400",
"npe-g2"
]
}
},
"required": [
"platform"
]
},
"qemu_disk_interfaces": {
"enum": [
"ide",
"scsi",
"sd",
"mtd",
"floppy",
"pflash",
"virtio",
"sata"
]
},
"qemu_properties": {
"type": "object",
"title": "Qemu template properties",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"title": "Name of the template"
},
"category": {
"$ref": "#/definitions/categories",
"title": "Category of the template"
},
"default_name_format": {
"type": "string",
"title": "Default name format"
},
"usage": {
"type": "string",
"title": "How to use the template"
},
"symbol": {
"type": "string",
"title": "Symbol of the template"
},
"adapter_type": {
"enum": [
"e1000",
"i82550",
"i82551",
"i82557a",
"i82557b",
"i82557c",
"i82558a",
"i82558b",
"i82559a",
"i82559b",
"i82559c",
"i82559er",
"i82562",
"i82801",
"igb",
"ne2k_pci",
"pcnet",
"rtl8139",
"virtio",
"virtio-net-pci",
"vmxnet3"
],
"title": "Type of network adapter"
},
"adapters": {
"type": "integer",
"title": "Number of adapters"
},
"custom_adapters": {
"type": "array",
"title": "Custom adapters",
"items": {
"type": "object",
"properties": {
"adapter_number": {
"title": "Adapter number",
"type": "integer"
},
"port_name": {
"title": "Custom port name",
"type": "string",
"minimum": 1
},
"adapter_type": {
"title": "Custom adapter type",
"type": "string",
"enum": [
"e1000",
"i82550",
"i82551",
"i82557a",
"i82557b",
"i82557c",
"i82558a",
"i82558b",
"i82559a",
"i82559b",
"i82559c",
"i82559er",
"i82562",
"i82801",
"igb",
"ne2k_pci",
"pcnet",
"rtl8139",
"virtio",
"virtio-net-pci",
"vmxnet3"
]
},
"mac_address": {
"title": "Custom MAC address",
"type": "string",
"minimum": 1,
"pattern": "^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$"
}
},
"required": [
"adapter_number"
]
}
},
"first_port_name": {
"type": "string",
"title": "Optional name of the first networking port example: eth0"
},
"port_name_format": {
"type": "string",
"title": "Optional formating of the networking port example: eth{0}"
},
"port_segment_size": {
"type": "integer",
"title": "Optional port segment size. A port segment is a block of port. For example Ethernet0/0 Ethernet0/1 is the module 0 with a port segment size of 2"
},
"linked_clone": {
"type": "boolean",
"title": "False if you don't want to use a single image for all nodes"
},
"ram": {
"type": "integer",
"title": "Ram allocated to the appliance (MB)"
},
"cpus": {
"type": "integer",
"title": "Number of Virtual CPU"
},
"hda_disk_interface": {
"$ref": "#/definitions/qemu_disk_interfaces",
"title": "Disk interface for the installed hda_disk_image"
},
"hdb_disk_interface": {
"$ref": "#/definitions/qemu_disk_interfaces",
"title": "Disk interface for the installed hdb_disk_image"
},
"hdc_disk_interface": {
"$ref": "#/definitions/qemu_disk_interfaces",
"title": "Disk interface for the installed hdc_disk_image"
},
"hdd_disk_interface": {
"$ref": "#/definitions/qemu_disk_interfaces",
"title": "Disk interface for the installed hdd_disk_image"
},
"platform": {
"enum": [
"aarch64",
"alpha",
"arm",
"cris",
"i386",
"lm32",
"m68k",
"microblaze",
"microblazeel",
"mips",
"mips64",
"mips64el",
"mipsel",
"moxie",
"or32",
"ppc",
"ppc64",
"ppcemb",
"s390x",
"sh4",
"sh4eb",
"sparc",
"sparc64",
"tricore",
"unicore32",
"x86_64",
"xtensa",
"xtensaeb"
],
"title": "Platform to emulate"
},
"console_type": {
"enum": [
"telnet",
"vnc",
"spice",
"spice+agent"
],
"title": "Type of console connection for the administration of the appliance"
},
"boot_priority": {
"enum": [
"d",
"c",
"dc",
"cd",
"n",
"nc",
"nd",
"cn",
"dn"
],
"title": "Optional define the disk boot priory. Refer to -boot option in qemu manual for more details."
},
"kernel_command_line": {
"type": "string",
"title": "Command line parameters send to the kernel"
},
"options": {
"type": "string",
"title": "Optional additional qemu command line options"
},
"cpu_throttling": {
"type": "number",
"minimum": 0,
"maximum": 100,
"title": "Throttle the CPU"
},
"tpm": {
"type": "boolean",
"title": "Enable the Trusted Platform Module (TPM)"
},
"uefi": {
"type": "boolean",
"title": "Enable the UEFI boot mode"
},
"on_close": {
"title": "Action to execute on the VM is closed",
"enum": [
"power_off",
"shutdown_signal",
"save_vm_state"
]
},
"process_priority": {
"title": "Process priority for QEMU",
"enum": [
"realtime",
"very high",
"high",
"normal",
"low",
"very low",
"null"
]
}
}
}
},
"properties": {
"appliance_id": {
"title": "Appliance ID",
"type": "string",
"minLength": 36,
"maxLength": 36,
"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
},
"name": {
"type": "string",
"title": "Appliance name"
},
"category": {
"$ref": "#/definitions/categories",
"title": "Category of the appliance"
},
"description": {
"type": "string",
"title": "Description of the appliance. Could be a marketing description"
},
"vendor_name": {
"type": "string",
"title": "Name of the vendor"
},
"vendor_url": {
"type": "string",
"format": "uri",
"title": "Website of the vendor"
},
"vendor_logo_url": {
"type": "string",
"format": "uri",
"title": "Link to the vendor logo (used by the GNS3 marketplace)"
},
"documentation_url": {
"type": "string",
"format": "uri",
"title": "An optional documentation for using the appliance on vendor website"
},
"product_name": {
"type": "string",
"title": "Product name"
},
"product_url": {
"type": "string",
"format": "uri",
"title": "An optional product url on vendor website"
},
"registry_version": {
"enum": [
8
],
"title": "Version of the registry compatible with this appliance (version >=8 introduced breaking changes)"
},
"status": {
"enum": [
"stable",
"experimental",
"broken"
],
"title": "Document if the appliance is working or not"
},
"availability": {
"enum": [
"free",
"with-registration",
"free-to-try",
"service-contract"
],
"title": "About image availability: can be downloaded directly; download requires a free registration; paid but a trial version (time or feature limited) is available; not available publicly"
},
"maintainer": {
"type": "string",
"title": "Maintainer name"
},
"maintainer_email": {
"type": "string",
"format": "email",
"title": "Maintainer email"
},
"installation_instructions": {
"type": "string",
"title": "Optional installation instructions"
},
"usage": {
"type": "string",
"title": "How to use the appliance"
},
"default_username": {
"type": "string",
"title": "Default username for the appliance"
},
"default_password": {
"type": "string",
"title": "Default password for the appliance"
},
"symbol": {
"type": "string",
"title": "An optional symbol for the appliance"
},
"settings": {
"type": "array",
"title": "Settings for running the appliance",
"items": {
"type": "object",
"title": "Emulator settings",
"properties": {
"name": {
"type": "string",
"title": "Name of the settings set"
},
"default": {
"type": "boolean",
"title": "Whether these are the default settings"
},
"inherit_default_properties": {
"type": "boolean",
"title": "Whether the default properties should be used",
"default": "true"
},
"template_type": {
"enum": [
"docker",
"iou",
"dynamips",
"qemu"
],
"title": "Type of emulator properties"
},
"template_properties": {
"type": "object",
"title": "Properties for the template",
"oneOf": [
{
"$ref": "#/definitions/qemu_properties"
},
{
"$ref": "#/definitions/dynamips_properties"
},
{
"$ref": "#/definitions/iou_properties"
},
{
"$ref": "#/definitions/docker_properties"
}
]
}
},
"required": [
"template_type",
"template_properties"
]
}
}
},
"images": {
"type": "array",
"title": "Images for this appliance",
"items": {
"type": "object",
"title": "An image file",
"properties": {
"filename": {
"type": "string",
"title": "Filename"
},
"version": {
"type": "string",
"title": "Version of the image file"
},
"md5sum": {
"type": "string",
"title": "MD5 cheksum of the image file",
"pattern": "^[a-f0-9]{32}$"
},
"checksum": {
"type": "string",
"title": "checksum of the image file"
},
"checksum_type": {
"title": "checksum type of the image file",
"enum": [
"md5"
]
},
"filesize": {
"type": "integer",
"title": "File size in bytes of the image file"
},
"download_url": {
"type": "string",
"format": "uri",
"title": "Download URL where you can download the image file from a browser"
},
"direct_download_url": {
"type": "string",
"format": "uri",
"title": "Optional. Non authenticated URL to the image file where you can download the image directly"
},
"compression": {
"enum": [
"bzip2",
"gzip",
"lzma",
"xz",
"rar",
"zip",
"7z"
],
"title": "Optional, compression type of direct download URL image."
},
"compression_target": {
"type": "string",
"title": "Optional, file name of the image file inside the compressed file."
}
},
"anyOf": [
{
"required": [
"filename",
"version",
"md5sum",
"filesize"
]
},
{
"required": [
"filename",
"version",
"checksum",
"filesize"
]
}
]
}
},
"versions": {
"type": "array",
"title": "Versions of the appliance",
"items": {
"type": "object",
"title": "A version of the appliance",
"properties": {
"name": {
"type": "string",
"title": "Name of the version"
},
"settings": {
"type": "string",
"title": "Template settings to use to run the version"
},
"category": {
"$ref": "#/definitions/categories",
"title": "Category of the version"
},
"installation_instructions": {
"type": "string",
"title": "Optional installation instructions for the version"
},
"usage": {
"type": "string",
"title": "Optional instructions about using the version"
},
"default_username": {
"type": "string",
"title": "Default username for the version"
},
"default_password": {
"type": "string",
"title": "Default password for the version"
},
"symbol": {
"type": "string",
"title": "An optional symbol for the version"
},
"images": {
"type": "object",
"title": "Images used for this version",
"properties": {
"kernel_image": {
"type": "string",
"title": "Kernel image (Qemu only)"
},
"initrd": {
"type": "string",
"title": "Initrd disk image (Qemu only)"
},
"image": {
"type": "string",
"title": "OS image (IOU and Dynamips only)"
},
"bios_image": {
"type": "string",
"title": "Bios image (Qemu only)"
},
"hda_disk_image": {
"type": "string",
"title": "Hda disk image (Qemu only)"
},
"hdb_disk_image": {
"type": "string",
"title": "Hdc disk image (Qemu only)"
},
"hdc_disk_image": {
"type": "string",
"title": "Hdd disk image (Qemu only)"
},
"hdd_disk_image": {
"type": "string",
"title": "Hdd disk image (Qemu only)"
},
"cdrom_image": {
"type": "string",
"title": "cdrom image (Qemu only)"
}
}
}
},
"required": [
"name"
]
}
},
"required": [
"appliance_id",
"name",
"category",
"description",
"vendor_name",
"vendor_url",
"product_name",
"registry_version",
"status",
"maintainer",
"maintainer_email",
"settings"
]
}

View File

@@ -55,28 +55,29 @@ if sys.platform.startswith("win"):
# windows 32-bit
program_files_x86 = program_files = os.environ["PROGRAMFILES"]
PRECONFIGURED_TELNET_CONSOLE_COMMANDS = {'Putty (normal standalone version)': 'putty_standalone.exe -telnet %h %p -loghost "%d"',
'MobaXterm': r'"{}\Mobatek\MobaXterm Personal Edition\MobaXterm.exe" -newtab "telnet %h %p"'.format(program_files_x86),
'Royal TS V3': r'{}\code4ward.net\Royal TS V3\RTS3App.exe /connectadhoc:%h /adhoctype:terminal /p:IsTelnetConnection="true" /p:ConnectionType="telnet;Telnet Connection" /p:Port="%p" /p:Name="%d"'.format(program_files),
'Royal TS V5': r'"{}\Royal TS V5\RoyalTS.exe" /protocol:terminal /using:adhoc /uri:"%h" /property:Port="%p" /property:IsTelnetConnection="true" /property:Name="%d"'.format(program_files_x86),
'SuperPutty': r'SuperPutty.exe -telnet "%h -P %p -wt \"%d\""',
'SecureCRT': r'"{}\VanDyke Software\SecureCRT\SecureCRT.exe" /N "%d" /T /TELNET %h %p'.format(program_files),
'SecureCRT (personal profile)': r'"{}\AppData\Local\VanDyke Software\SecureCRT\SecureCRT.exe" /T /N "%d" /TELNET %h %p'.format(userprofile),
'TeraTerm Pro': r'"{}\teraterm\ttermpro.exe" /W="%d" /M="ttstart.macro" /T=1 %h %p'.format(program_files_x86),
'Telnet': 'telnet %h %p',
'Windows Terminal': 'wt.exe -w 1 new-tab --title %d telnet %h %p',
'Xshell 4': r'"{}\NetSarang\Xshell 4\xshell.exe" -url telnet://%h:%p'.format(program_files_x86),
'Xshell 5': r'"{}\NetSarang\Xshell 5\xshell.exe" -url telnet://%h:%p -newtab %d'.format(program_files_x86),
'ZOC 6': r'"{}\ZOC6\zoc.exe" "/TELNET:%h:%p" /TABBED "/TITLE:%d"'.format(program_files_x86)}
PRECONFIGURED_TELNET_CONSOLE_COMMANDS = {'Putty (normal standalone version)': 'putty_standalone.exe -telnet {host} {port} -loghost "{name}"',
'KiTTY': r'kitty -title "{name}" telnet://{host} {port}',
'MobaXterm': r'"{}\Mobatek\MobaXterm Personal Edition\MobaXterm.exe" -newtab "telnet {{host}} {{port}}"'.format(program_files_x86),
'Royal TS V3': r'{}\code4ward.net\Royal TS V3\RTS3App.exe /connectadhoc:{{host}} /adhoctype:terminal /p:IsTelnetConnection="true" /p:ConnectionType="telnet;Telnet Connection" /p:Port="{{port}}" /p:Name="{{name}}"'.format(program_files),
'Royal TS V5': r'"{}\Royal TS V5\RoyalTS.exe" /protocol:terminal /using:adhoc /uri:"{{host}}" /property:Port="{{port}}" /property:IsTelnetConnection="true" /property:Name="{{name}}"'.format(program_files_x86),
'SuperPutty': r'SuperPutty.exe -telnet "{host} -P {port} -wt \"{name}\""',
'SecureCRT': r'"{}\VanDyke Software\SecureCRT\SecureCRT.exe" /N "{{name}}" /T /TELNET {{host}} {{port}}'.format(program_files),
'SecureCRT (personal profile)': r'"{}\AppData\Local\VanDyke Software\SecureCRT\SecureCRT.exe" /T /N "{{name}}" /TELNET {{host}} {{port}}'.format(userprofile),
'TeraTerm Pro': r'"{}\teraterm\ttermpro.exe" /W="{{name}}" /M="ttstart.macro" /T=1 {{host}} {{port}}'.format(program_files_x86),
'Telnet': 'telnet {host} {port}',
'Windows Terminal': 'wt.exe -w 1 new-tab --title {name} telnet {host} {port}',
'Xshell 4': r'"{}\NetSarang\Xshell 4\xshell.exe" -url telnet://{{host}}:{{port}}'.format(program_files_x86),
'Xshell 5': r'"{}\NetSarang\Xshell 5\xshell.exe" -url telnet://{{host}}:{{port}} -newtab {{name}}'.format(program_files_x86),
'ZOC 6': r'"{}\ZOC6\zoc.exe" "/TELNET:{{host}}:{{port}}" /TABBED "/TITLE:{{name}}"'.format(program_files_x86)}
# default on Windows
if shutil.which("Solar-PuTTY.exe"):
# Solar-Putty is the default if it is installed.
PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Solar-Putty (included with GNS3)"] = 'Solar-PuTTY.exe --telnet --hostname %h --port %p --name "%d"'
PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Solar-Putty (included with GNS3)"] = 'Solar-PuTTY.exe --telnet --hostname {host} --port {port} --name "{name}"'
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Solar-Putty (included with GNS3)"]
DEFAULT_DELAY_CONSOLE_ALL = 1500
else:
PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Solar-Putty (included with GNS3 downloaded from gns3.com)"] = 'Solar-PuTTY.exe --telnet --hostname %h --port %p --name "%d"'
PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Solar-Putty (included with GNS3 downloaded from gns3.com)"] = 'Solar-PuTTY.exe --telnet --hostname {host} --port {port} --name "{name}"'
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Putty (normal standalone version)"]
elif sys.platform.startswith("darwin"):
@@ -86,7 +87,7 @@ elif sys.platform.startswith("darwin"):
r""" -e 'set posix_path to do shell script "echo \"$PATH\""'"""
r""" -e 'tell application "Terminal"'"""
r""" -e 'activate'"""
r""" -e 'do script "echo -n -e \"\\033]0;%d\\007\"; clear; PATH=" & quoted form of posix_path & " telnet %h %p ; exit"'"""
r""" -e 'do script "echo -n -e \"\\033]0;{name}\\007\"; clear; PATH=" & quoted form of posix_path & " telnet {host} {port} ; exit"'"""
r""" -e 'end tell'""",
'Terminal tabbed (experimental)': r"""osascript"""
r""" -e 'set posix_path to do shell script "echo \"$PATH\""'"""
@@ -102,7 +103,7 @@ elif sys.platform.startswith("darwin"):
r""" -e 'repeat while the busy of window 1 = true'"""
r""" -e 'delay 0.01'"""
r""" -e 'end repeat'"""
r""" -e 'do script "echo -n -e \"\\033]0;%d\\007\"; clear; PATH=" & quoted form of posix_path & " telnet %h %p ; exit" in window 1'"""
r""" -e 'do script "echo -n -e \"\\033]0;{name}\\007\"; clear; PATH=" & quoted form of posix_path & " telnet {host} {port} ; exit" in window 1'"""
r""" -e 'end tell'""",
'iTerm2 2.x': r"""osascript"""
r""" -e 'set posix_path to do shell script "echo \"$PATH\""'"""
@@ -117,7 +118,7 @@ elif sys.platform.startswith("darwin"):
r""" -e ' set s to (make new session at the end of sessions)'"""
r""" -e ' tell s'"""
r""" -e ' exec command "sh"'"""
r""" -e ' write text "PATH=" & quoted form of posix_path & " exec telnet %h %p"'"""
r""" -e ' write text "PATH=" & quoted form of posix_path & " exec telnet {host} {port}"'"""
r""" -e ' end tell'"""
r""" -e 'end tell'"""
r""" -e 'end tell'""",
@@ -134,33 +135,33 @@ elif sys.platform.startswith("darwin"):
r""" -e ' create tab with default profile command "sh"'"""
r""" -e ' set s to current session'"""
r""" -e ' tell s'"""
r""" -e ' set name to "%d"'"""
r""" -e ' write text "PATH=" & quoted form of posix_path & " exec telnet %h %p"'"""
r""" -e ' set name to "{name}"'"""
r""" -e ' write text "PATH=" & quoted form of posix_path & " exec telnet {host} {port}"'"""
r""" -e ' end tell'"""
r""" -e 'end tell'"""
r""" -e 'end tell'""",
'Royal TSX': "open 'rtsx://telnet%3A%2F%2F%h:%p'",
'SecureCRT': '/Applications/SecureCRT.app/Contents/MacOS/SecureCRT /N "%d" /T /TELNET %h %p',
'ZOC 6': '/Applications/zoc6.app/Contents/MacOS/zoc6 "/TELNET:%h:%p" /TABBED "/TITLE:%d"',
'ZOC 7': '/Applications/zoc7.app/Contents/MacOS/zoc7 "/TELNET:%h:%p" /TABBED "/TITLE:%d"',
'ZOC 8': '/Applications/zoc8.app/Contents/MacOS/zoc8 "/TELNET:%h:%p" /TABBED "/TITLE:%d"'
'Royal TSX': "open 'rtsx://telnet%3A%2F%2F{host}:{port}'",
'SecureCRT': '/Applications/SecureCRT.app/Contents/MacOS/SecureCRT /N "{name}" /T /TELNET {host} {port}',
'ZOC 6': '/Applications/zoc6.app/Contents/MacOS/zoc6 "/TELNET:{host}:{port}" /TABBED "/TITLE:{name}"',
'ZOC 7': '/Applications/zoc7.app/Contents/MacOS/zoc7 "/TELNET:{host}:{port}" /TABBED "/TITLE:{name}"',
'ZOC 8': '/Applications/zoc8.app/Contents/MacOS/zoc8 "/TELNET:{host}:{port}" /TABBED "/TITLE:{name}"'
}
# default Mac OS X Telnet console command
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Terminal"]
else:
PRECONFIGURED_TELNET_CONSOLE_COMMANDS = {'Xterm': 'xterm -T "%d" -e "telnet %h %p"',
'Putty': 'putty -telnet %h %p -title "%d" -sl 2500 -fg SALMON1 -bg BLACK',
'Gnome Terminal': 'gnome-terminal --tab -t "%d" -- telnet %h %p',
'Xfce4 Terminal': 'xfce4-terminal --tab -T "%d" -e "telnet %h %p"',
'ROXTerm': 'roxterm -n "%d" --tab -e "telnet %h %p"',
'KDE Konsole': 'konsole --new-tab -p tabtitle="%d" -e "telnet %h %p"',
'SecureCRT': 'SecureCRT /T /N "%d" /TELNET %h %p',
'Mate Terminal': 'mate-terminal --tab -e "telnet %h %p" -t "%d"',
'terminator': 'terminator -e "telnet %h %p" -T "%d"',
'urxvt': 'urxvt -title %d -e telnet %h %p',
'kitty': 'kitty -T %d telnet %h %p'}
PRECONFIGURED_TELNET_CONSOLE_COMMANDS = {'Xterm': 'xterm -T "{name}" -e "telnet {host} {port}"',
'Putty': 'putty -telnet {host} {port} -title "{name}" -sl 2500 -fg SALMON1 -bg BLACK',
'Gnome Terminal': 'gnome-terminal --tab -t "{name}" -- telnet {host} {port}',
'Xfce4 Terminal': 'xfce4-terminal --tab -T "{name}" -e "telnet {host} {port}"',
'ROXTerm': 'roxterm -n "{name}" --tab -e "telnet {host} {port}"',
'KDE Konsole': 'konsole --new-tab -p tabtitle="{name}" -e "telnet {host} {port}"',
'SecureCRT': 'SecureCRT /T /N "{name}" /TELNET {host} {port}',
'Mate Terminal': 'mate-terminal --tab -e "telnet {host} {port}" -t "{name}"',
'terminator': 'terminator -e "telnet {host} {port}" -T "{name}"',
'urxvt': 'urxvt -title {name} -e telnet {host} {port}',
'kitty': 'kitty -T {name} telnet {host} {port}'}
# default Telnet console command on other systems
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Xterm"]
@@ -177,8 +178,8 @@ else:
if sys.platform.startswith("win"):
# Windows
PRECONFIGURED_VNC_CONSOLE_COMMANDS = {
'TightVNC (included with GNS3)': 'tvnviewer.exe %h:%p',
'UltraVNC': r'"{}\uvnc bvba\UltraVNC\vncviewer.exe" %h:%p'.format(program_files)
'TightVNC (included with GNS3)': 'tvnviewer.exe {host}:{port}',
'UltraVNC': r'"{}\uvnc bvba\UltraVNC\vncviewer.exe" {{host}}:{{port}}'.format(program_files)
}
# default Windows VNC console command
@@ -190,11 +191,11 @@ elif sys.platform.startswith("darwin"):
'OSX builtin screen sharing': "osascript"
" -e 'tell application \"Screen Sharing\"'"
" -e ' display dialog \"WARNING OSX VNC support is limited if you have trouble connecting to a device please use an alternative client like Chicken of the VNC.\" buttons {\"OK\"} default button 1 with icon caution with title \"GNS3\"'"
" -e ' open location \"vnc://%h:%p\"'"
" -e ' open location \"vnc://{host}:{port}\"'"
" -e 'end tell'",
'Chicken of the VNC': "/Applications/Chicken.app/Contents/MacOS/Chicken %h:%p",
'Chicken of the VNC < 2.2': r"/Applications/Chicken\ of\ the\ VNC.app/Contents/MacOS/Chicken\ of\ the\ VNC %h:%p",
'Royal TSX': "open 'rtsx://vnc%3A%2F%2F%h:%p'",
'Chicken of the VNC': "/Applications/Chicken.app/Contents/MacOS/Chicken {host}:{port}",
'Chicken of the VNC < 2.2': r"/Applications/Chicken\ of\ the\ VNC.app/Contents/MacOS/Chicken\ of\ the\ VNC {host}:{port}",
'Royal TSX': "open 'rtsx://vnc%3A%2F%2F{host}:{port}'",
}
# default Mac OS X VNC console command
@@ -202,10 +203,10 @@ elif sys.platform.startswith("darwin"):
else:
PRECONFIGURED_VNC_CONSOLE_COMMANDS = {
'TightVNC': 'vncviewer %h:%p',
'Vinagre': 'vinagre %h::%p',
'gvncviewer': 'gvncviewer %h:%P',
'Remote Viewer': 'remote-viewer vnc://%h:%p'
'TightVNC': 'vncviewer {host}:{port}',
'Vinagre': 'vinagre {host}::{port}',
'gvncviewer': 'gvncviewer {host}:{display}',
'Remote Viewer': 'remote-viewer vnc://{host}:{port}'
}
# default VNC console command on other systems
@@ -215,7 +216,7 @@ else:
if sys.platform.startswith("win"):
# Windows
PRECONFIGURED_SPICE_CONSOLE_COMMANDS = {
'Remote Viewer': r'"{}\VirtViewer v11.0-256\bin\remote-viewer.exe" spice://%h:%p'.format(program_files),
'Remote Viewer': r'"{}\VirtViewer v11.0-256\bin\remote-viewer.exe" spice://{{host}}:{{port}}'.format(program_files),
}
# default Windows SPICE console command
@@ -224,7 +225,7 @@ if sys.platform.startswith("win"):
elif sys.platform.startswith("darwin"):
# Mac OS X
PRECONFIGURED_SPICE_CONSOLE_COMMANDS = {
'Remote Viewer': '/Applications/RemoteViewer.app/Contents/MacOS/RemoteViewer spice://%h:%p',
'Remote Viewer': '/Applications/RemoteViewer.app/Contents/MacOS/RemoteViewer spice://{host}:{port}',
}
# default Mac OS X SPICE console command
@@ -232,7 +233,7 @@ elif sys.platform.startswith("darwin"):
else:
PRECONFIGURED_SPICE_CONSOLE_COMMANDS = {
'Remote Viewer': 'remote-viewer spice://%h:%p',
'Remote Viewer': 'remote-viewer spice://{host}:{port}',
}
# default SPICE console command on other systems
@@ -243,29 +244,28 @@ WIRESHARK_NORMAL_CAPTURE = "Wireshark Traditional Capture"
WIRESHARK_LIVE_TRAFFIC_CAPTURE = "Wireshark Live Traffic Capture"
if sys.platform.startswith("win"):
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: r"{}\Wireshark\wireshark.exe %c".format(program_files),
WIRESHARK_LIVE_TRAFFIC_CAPTURE: r'tail.exe -f -c +0b %c | "{}\Wireshark\wireshark.exe" -o "gui.window_title:%d" -k -i -'.format(program_files)}
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: r'{}\Wireshark\wireshark.exe {{pcap_file}} --capture-comment "{{project}} {{link_description}}"'.format(program_files),
WIRESHARK_LIVE_TRAFFIC_CAPTURE: r'tail.exe -f -c +0b {{pcap_file}} | "{}\Wireshark\wireshark.exe" --capture-comment "{{project}} {{link_description}}" -o "gui.window_title:{{link_description}}" -k -i -'.format(program_files)}
elif sys.platform.startswith("darwin"):
# Mac OS X
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: "/usr/bin/open -a /Applications/Wireshark.app %c",
"Wireshark V1.X Live Traffic Capture": 'tail -f -c +0 %c | /Applications/Wireshark.app/Contents/Resources/bin/wireshark -o "gui.window_title:%d" -k -i -',
WIRESHARK_LIVE_TRAFFIC_CAPTURE: 'tail -f -c +0 %c | /Applications/Wireshark.app/Contents/MacOS/Wireshark -o "gui.window_title:%d" -k -i -'}
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: '/usr/bin/open -a /Applications/Wireshark.app {pcap_file} --capture-comment {project} {link_description}"',
WIRESHARK_LIVE_TRAFFIC_CAPTURE: 'tail -f -c +0 {pcap_file} | /Applications/Wireshark.app/Contents/MacOS/Wireshark --capture-comment "{project} {link_description}" -o "gui.window_title:{link_description}" -k -i -'}
elif sys.platform.startswith("freebsd"):
# FreeBSD
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: "wireshark %c",
WIRESHARK_LIVE_TRAFFIC_CAPTURE: 'gtail -f -c +0b %c | wireshark -o "gui.window_title:%d" -k -i -'}
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: 'wireshark {pcap_file} --capture-comment "{project} {link_description}"',
WIRESHARK_LIVE_TRAFFIC_CAPTURE: 'gtail -f -c +0b {pcap_file} | wireshark --capture-comment "{project} {link_description}" -o "gui.window_title:{link_description}" -k -i -'}
else:
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: "wireshark %c",
WIRESHARK_LIVE_TRAFFIC_CAPTURE: 'tail -f -c +0b %c | wireshark -o "gui.window_title:%d" -k -i -'}
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: 'wireshark {pcap_file} --capture-comment "{project} {link_description}"',
WIRESHARK_LIVE_TRAFFIC_CAPTURE: 'tail -f -c +0b {pcap_file} | wireshark --capture-comment "{project} {link_description}" -o "gui.window_title:{link_description}" -k -i -'}
DEFAULT_PACKET_CAPTURE_READER_COMMAND = PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS[WIRESHARK_LIVE_TRAFFIC_CAPTURE]
DEFAULT_PACKET_CAPTURE_ANALYZER_COMMAND = ""
if sys.platform.startswith("win"):
# Windows 64-bit
DEFAULT_PACKET_CAPTURE_ANALYZER_COMMAND = r'"{}\SolarWinds\ResponseTimeViewer\ResponseTimeViewer.exe" %c'.format(program_files_x86)
DEFAULT_PACKET_CAPTURE_ANALYZER_COMMAND = r'"{}\SolarWinds\ResponseTimeViewer\ResponseTimeViewer.exe" {{pcap_file}}'.format(program_files_x86)
STYLES = ["Charcoal", "Classic", "Legacy"]

View File

@@ -54,14 +54,24 @@ def spiceConsole(node, port, command):
command = command.replace("%h", host)
command = command.replace("%p", str(port))
command = command.replace("%d", name.replace('"', '\\"'))
command = command.replace("%P", node.project().name().replace('"', '\\"'))
command = command.replace("%i", node.project().id())
command = command.replace("%n", str(node.id()))
command = command.replace("%c", Controller.instance().httpClient().fullUrl())
command = command.replace("{host}", host)
command = command.replace("{port}", str(port))
command = command.replace("{name}", name.replace('"', '\\"'))
command = command.replace("{project}", node.project().name())
command = command.replace("{project_id}", node.project().id())
command = command.replace("{node_id}", str(node.id()))
command = command.replace("{url}", Controller.instance().httpClient().fullUrl())
try:
log.debug('starting SPICE program "{}"'.format(command))
if sys.platform.startswith("win"):
# use the string on Windows
subprocess.Popen(command)
subprocess.Popen(command, env=os.environ)
else:
# use arguments on other platforms
args = shlex.split(command)

View File

@@ -87,7 +87,7 @@ class ConsoleThread(QtCore.QThread):
if sys.platform.startswith("win"):
# use the string on Windows
subprocess.call(command)
subprocess.Popen(command, env=os.environ)
else:
# use arguments on other platforms
try:
@@ -102,7 +102,7 @@ class ConsoleThread(QtCore.QThread):
# inject gnome-terminal environment variables
if "GNOME_TERMINAL_SERVICE" not in env or "GNOME_TERMINAL_SCREEN" not in env:
env.update(gnome_terminal_env())
subprocess.call(args, env=env)
subprocess.Popen(args, env=env)
def run(self):
@@ -113,10 +113,19 @@ class ConsoleThread(QtCore.QThread):
command = self._command.replace("%h", host)
command = command.replace("%p", str(port))
command = command.replace("%d", self._name.replace('"', '\\"'))
command = command.replace("%P", self._node.project().name().replace('"', '\\"'))
command = command.replace("%i", self._node.project().id())
command = command.replace("%n", str(self._node.id()))
command = command.replace("%c", Controller.instance().httpClient().fullUrl())
command = command.replace("{host}", host)
command = command.replace("{port}", str(port))
command = command.replace("{name}", self._name.replace('"', '\\"'))
command = command.replace("{project}", self._node.project().name().replace('"', '\\"'))
command = command.replace("{project_id}", self._node.project().id())
command = command.replace("{node_id}", str(self._node.id()))
command = command.replace("{url}", Controller.instance().httpClient().fullUrl())
# If the console use an apple script we lock to avoid multiple console
# to interact at the same time
if sys.platform.startswith("darwin") and "osascript" in command:

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>900</width>
<height>601</height>
<width>726</width>
<height>428</height>
</rect>
</property>
<property name="sizePolicy">
@@ -219,6 +219,27 @@
</item>
</layout>
</widget>
<widget class="QWizardPage" name="uiInstructionsPage">
<property name="title">
<string>Installation instructions</string>
</property>
<property name="subTitle">
<string>Please read the following instructions in order to install your new appliance.</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QTextEdit" name="uiInstructionsTextEdit">
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="uiFilesWizardPage">
<property name="title">
<string>Required files</string>
@@ -232,12 +253,12 @@
<property name="indentation">
<number>20</number>
</property>
<attribute name="headerDefaultSectionSize">
<number>120</number>
</attribute>
<attribute name="headerMinimumSectionSize">
<number>20</number>
</attribute>
<attribute name="headerDefaultSectionSize">
<number>120</number>
</attribute>
<column>
<property name="text">
<string>Appliance version and files</string>

View File

@@ -2,16 +2,19 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/appliance_wizard.ui'
#
# Created by: PyQt5 UI code generator 5.9
# Created by: PyQt5 UI code generator 5.15.9
#
# WARNING! All changes made in this file will be lost!
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_ApplianceWizard(object):
def setupUi(self, ApplianceWizard):
ApplianceWizard.setObjectName("ApplianceWizard")
ApplianceWizard.resize(900, 601)
ApplianceWizard.resize(726, 428)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@@ -112,6 +115,14 @@ class Ui_ApplianceWizard(object):
self.uiQemuListComboBox.setObjectName("uiQemuListComboBox")
self.horizontalLayout_2.addWidget(self.uiQemuListComboBox)
ApplianceWizard.addPage(self.uiQemuWizardPage)
self.uiInstructionsPage = QtWidgets.QWizardPage()
self.uiInstructionsPage.setObjectName("uiInstructionsPage")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.uiInstructionsPage)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.uiInstructionsTextEdit = QtWidgets.QTextEdit(self.uiInstructionsPage)
self.uiInstructionsTextEdit.setObjectName("uiInstructionsTextEdit")
self.verticalLayout_3.addWidget(self.uiInstructionsTextEdit)
ApplianceWizard.addPage(self.uiInstructionsPage)
self.uiFilesWizardPage = QtWidgets.QWizardPage()
self.uiFilesWizardPage.setObjectName("uiFilesWizardPage")
self.verticalLayout = QtWidgets.QVBoxLayout(self.uiFilesWizardPage)
@@ -174,6 +185,13 @@ class Ui_ApplianceWizard(object):
self.uiQemuWizardPage.setTitle(_translate("ApplianceWizard", "Qemu settings"))
self.uiQemuWizardPage.setSubTitle(_translate("ApplianceWizard", "Please choose the qemu binary that will be used to run this appliance."))
self.uiQemuListLabel.setText(_translate("ApplianceWizard", "Qemu binary:"))
self.uiInstructionsPage.setTitle(_translate("ApplianceWizard", "Installation instructions"))
self.uiInstructionsPage.setSubTitle(_translate("ApplianceWizard", "Please read the following instructions in order to install your new appliance."))
self.uiInstructionsTextEdit.setHtml(_translate("ApplianceWizard", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'Ubuntu\'; font-size:11pt; font-weight:400; font-style:normal;\">\n"
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p></body></html>"))
self.uiFilesWizardPage.setTitle(_translate("ApplianceWizard", "Required files"))
self.uiFilesWizardPage.setSubTitle(_translate("ApplianceWizard", "The following files are required to install the appliance"))
self.uiApplianceVersionTreeWidget.headerItem().setText(0, _translate("ApplianceWizard", "Appliance version and files"))
@@ -191,5 +209,4 @@ class Ui_ApplianceWizard(object):
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'Ubuntu\'; font-size:11pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">The default username/password is admin/admin. A default configuration is present.</p></body></html>"))
from . import resources_rc

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>965</width>
<height>547</height>
<width>576</width>
<height>346</height>
</rect>
</property>
<property name="sizePolicy">
@@ -70,7 +70,7 @@
<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;%P: VNC display&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;/ul&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:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%d: title of the console&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;%i: Project UUID&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;%c: server URL (&lt;span style=&quot; font-style:italic;&quot;&gt;http://user:password@server:port&lt;/span&gt;)&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
<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 or {host}: 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 or {port}: 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;%D or {display}: VNC display&lt;/li&gt;&lt;/ul&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:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%d or {name}: title of the console&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 or {project}: project name&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;%i or {project_id}: project UUID&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;%c or {url}: server URL (&lt;span style=&quot; font-style:italic;&quot;&gt;http://user:password@server:port&lt;/span&gt;)&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>

View File

@@ -2,16 +2,19 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/console_command_dialog.ui'
#
# Created by: PyQt5 UI code generator 5.5.1
# Created by: PyQt5 UI code generator 5.15.6
#
# WARNING! All changes made in this file will be lost!
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_uiConsoleCommandDialog(object):
def setupUi(self, uiConsoleCommandDialog):
uiConsoleCommandDialog.setObjectName("uiConsoleCommandDialog")
uiConsoleCommandDialog.resize(965, 547)
uiConsoleCommandDialog.resize(576, 346)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@@ -57,8 +60,8 @@ class Ui_uiConsoleCommandDialog(object):
self.verticalLayout.addWidget(self.uiButtonBox)
self.retranslateUi(uiConsoleCommandDialog)
self.uiButtonBox.accepted.connect(uiConsoleCommandDialog.accept)
self.uiButtonBox.rejected.connect(uiConsoleCommandDialog.reject)
self.uiButtonBox.accepted.connect(uiConsoleCommandDialog.accept) # type: ignore
self.uiButtonBox.rejected.connect(uiConsoleCommandDialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(uiConsoleCommandDialog)
def retranslateUi(self, uiConsoleCommandDialog):
@@ -67,5 +70,4 @@ class Ui_uiConsoleCommandDialog(object):
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;\">%P: VNC display</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></ul><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d: title of the console</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i: Project UUID</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%c: server URL (<span style=\" font-style:italic;\">http://user:password@server:port</span>)</li></ul></body></html>"))
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 or {host}: 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 or {port}: console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%D or {display}: VNC display</li></ul><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d or {name}: title of the console</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%P or {project}: project name</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i or {project_id}: project UUID</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%c or {url}: server URL (<span style=\" font-style:italic;\">http://user:password@server:port</span>)</li></ul></body></html>"))

View File

@@ -418,7 +418,7 @@
</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;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;%d = title of the console&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;%i = project UUID&lt;/li&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:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%n = node UUID&lt;/li&gt;&lt;/ul&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;%c = server URL&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Command line replacements:&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 or {host} = 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 or {port} = 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;%d or {name} = title of the console (node name)&lt;/li&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:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%P or {project} = project&lt;/li&gt;&lt;/ul&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;%i or {project_id} = project UUID&lt;/li&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:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%n or {node_id} = node UUID&lt;/li&gt;&lt;/ul&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;%c or {url} = server URL&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="readOnly">
<bool>true</bool>
@@ -528,7 +528,7 @@
</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;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;%P = VNC display&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;%d = title of the console&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;%i = project UUID&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;%n = node UUID&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Command line replacements:&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 or {host} = 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 or {port} = 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;%P or {display} = VNC display&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;%d or {name} = node name&lt;/li&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:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%T or {project} = project name&lt;/li&gt;&lt;/ul&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;%i or {project_id} = project UUID&lt;/li&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:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%n or {node_id} = node UUID&lt;/li&gt;&lt;/ul&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;%c or {url} = server URL&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="readOnly">
<bool>true</bool>
@@ -597,7 +597,7 @@
</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;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;%d = title of the console&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;%i = project UUID&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;%n = node UUID&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Command line replacements:&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 or {host} = 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 or {port} = 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;%d or {name} = node name&lt;/li&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:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%P or {project} = project name&lt;/li&gt;&lt;/ul&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;%i or {project_id} = project UUID&lt;/li&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:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%n or {node_id} = node UUID&lt;/li&gt;&lt;/ul&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;%c or {url} = server URL&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="readOnly">
<bool>true</bool>

View File

@@ -558,7 +558,7 @@ class Ui_GeneralPreferencesPageWidget(object):
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.uiImagesTab), _translate("GeneralPreferencesPageWidget", "Binary images"))
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><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;\">%d = title of the console</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i = project UUID</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%n = node UUID</li></ul><li style=\" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%c = server URL</li></ul></body></html>"))
self.uiTelnetConsoleCommandLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "<html><head/><body><p>Command line replacements:</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 or {host} = 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 or {port} = console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d or {name} = title of the console (node name)</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%P or {project} = project</li></ul><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i or {project_id} = project UUID</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%n or {node_id} = node UUID</li></ul><li style=\" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%c or {url} = server URL</li></ul></body></html>"))
self.uiTelnetConsolePreconfiguredCommandPushButton.setText(_translate("GeneralPreferencesPageWidget", "&Edit"))
self.uiConsoleMiscGroupBox.setTitle(_translate("GeneralPreferencesPageWidget", "Miscellaneous"))
self.uiDelayConsoleAllSpinBox.setSuffix(_translate("GeneralPreferencesPageWidget", " ms"))
@@ -566,12 +566,12 @@ class Ui_GeneralPreferencesPageWidget(object):
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.uiConsoleTab), _translate("GeneralPreferencesPageWidget", "Console applications"))
self.uiVNCConsoleSettingsGroupBox.setTitle(_translate("GeneralPreferencesPageWidget", "Settings for VNC connections"))
self.uiVNCConsoleCommandLabel.setText(_translate("GeneralPreferencesPageWidget", "Console application command for VNC:"))
self.uiVNCConsoleCommandLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "<html><head/><body><p>Command line replacements:</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;\">%P = VNC display</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d = title of the console</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i = project UUID</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%n = node UUID</li></ul></body></html>"))
self.uiVNCConsoleCommandLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "<html><head/><body><p>Command line replacements:</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 or {host} = 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 or {port} = console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%P or {display} = VNC display</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d or {name} = node name</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%T or {project} = project name</li></ul><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i or {project_id} = project UUID</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%n or {node_id} = node UUID</li></ul><li style=\" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%c or {url} = server URL</li></ul></body></html>"))
self.uiVNCConsolePreconfiguredCommandPushButton.setText(_translate("GeneralPreferencesPageWidget", "&Edit"))
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.uiVNCTab), _translate("GeneralPreferencesPageWidget", "VNC"))
self.uiSPICEConsoleSettingsGroupBox.setTitle(_translate("GeneralPreferencesPageWidget", "Settings for SPICE connections"))
self.uiSPICEConsoleCommandLabel.setText(_translate("GeneralPreferencesPageWidget", "Console application command for SPICE:"))
self.uiSPICEConsoleCommandLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "<html><head/><body><p>Command line replacements:</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;\">%d = title of the console</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i = project UUID</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%n = node UUID</li></ul><p><br/></p></body></html>"))
self.uiSPICEConsoleCommandLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "<html><head/><body><p>Command line replacements:</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 or {host} = 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 or {port} = console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d or {name} = node name</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%P or {project} = project name</li></ul><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i or {project_id} = project UUID</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%n or {node_id} = node UUID</li></ul><li style=\" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%c or {url} = server URL</li></ul></body></html>"))
self.uiSPICEConsolePreconfiguredCommandPushButton.setText(_translate("GeneralPreferencesPageWidget", "&Edit"))
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.uiSPICETab), _translate("GeneralPreferencesPageWidget", "SPICE"))
self.uiSceneWidthLabel.setText(_translate("GeneralPreferencesPageWidget", "Default width:"))

View File

@@ -79,7 +79,7 @@
<item row="3" column="0" colspan="2">
<widget class="QLineEdit" name="uiCaptureReaderCommandLineEdit">
<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;%c = capture file (PCAP format)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Command line replacements:&lt;/p&gt;&lt;p&gt;%c or {pcap_file} = capture file (PCAP format)&lt;/p&gt;&lt;p&gt;%P or {project} = project name&lt;/p&gt;&lt;p&gt;%d or {link_description} = link description&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>

View File

@@ -2,12 +2,15 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/packet_capture_preferences_page.ui'
#
# Created by: PyQt5 UI code generator 5.9
# Created by: PyQt5 UI code generator 5.15.6
#
# WARNING! All changes made in this file will be lost!
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_PacketCapturePreferencesPageWidget(object):
def setupUi(self, PacketCapturePreferencesPageWidget):
PacketCapturePreferencesPageWidget.setObjectName("PacketCapturePreferencesPageWidget")
@@ -64,6 +67,11 @@ class Ui_PacketCapturePreferencesPageWidget(object):
self.retranslateUi(PacketCapturePreferencesPageWidget)
QtCore.QMetaObject.connectSlotsByName(PacketCapturePreferencesPageWidget)
PacketCapturePreferencesPageWidget.setTabOrder(self.uiPreconfiguredCaptureReaderCommandComboBox, self.uiPreconfiguredCaptureReaderCommandPushButton)
PacketCapturePreferencesPageWidget.setTabOrder(self.uiPreconfiguredCaptureReaderCommandPushButton, self.uiCaptureReaderCommandLineEdit)
PacketCapturePreferencesPageWidget.setTabOrder(self.uiCaptureReaderCommandLineEdit, self.uiAutoStartCheckBox)
PacketCapturePreferencesPageWidget.setTabOrder(self.uiAutoStartCheckBox, self.uiCaptureAnalyzerCommandLineEdit)
PacketCapturePreferencesPageWidget.setTabOrder(self.uiCaptureAnalyzerCommandLineEdit, self.uiRestoreDefaultsPushButton)
def retranslateUi(self, PacketCapturePreferencesPageWidget):
_translate = QtCore.QCoreApplication.translate
@@ -74,6 +82,5 @@ class Ui_PacketCapturePreferencesPageWidget(object):
self.uiCaptureReaderCommandLabel.setText(_translate("PacketCapturePreferencesPageWidget", "Packet capture reader command:"))
self.uiAutoStartCheckBox.setText(_translate("PacketCapturePreferencesPageWidget", "Automatically start the packet capture application"))
self.uiCaptureAnalyzerCommandLabel.setText(_translate("PacketCapturePreferencesPageWidget", "Packet capture analyzer command:"))
self.uiCaptureReaderCommandLineEdit.setToolTip(_translate("PacketCapturePreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><p>%c = capture file (PCAP format)</p></body></html>"))
self.uiCaptureReaderCommandLineEdit.setToolTip(_translate("PacketCapturePreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><p>%c or {pcap_file} = capture file (PCAP format)</p><p>%P or {project} = project name</p><p>%d or {link_description} = link description</p></body></html>"))
self.uiRestoreDefaultsPushButton.setText(_translate("PacketCapturePreferencesPageWidget", "Restore defaults"))

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>270</width>
<height>294</height>
<width>288</width>
<height>358</height>
</rect>
</property>
<property name="windowTitle">
@@ -84,14 +84,14 @@
<item row="3" column="1">
<widget class="QComboBox" name="uiBorderStyleComboBox"/>
</item>
<item row="5" column="0">
<item row="7" column="0">
<widget class="QLabel" name="uiRotationLabel">
<property name="text">
<string>Rotation:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<item row="7" column="1">
<widget class="QSpinBox" name="uiRotationSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@@ -114,14 +114,14 @@ editing (notes only) with ALT and '+' (or P) / ALT and '-' (or M)</string>
</property>
</widget>
</item>
<item row="4" column="0">
<item row="6" column="0">
<widget class="QLabel" name="uiCornerRadiusLabel">
<property name="text">
<string>Corner radius:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="6" column="1">
<widget class="QSpinBox" name="uiCornerRadiusSpinBox">
<property name="suffix">
<string>°</string>
@@ -131,6 +131,46 @@ editing (notes only) with ALT and '+' (or P) / ALT and '-' (or M)</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="uiWidthLabel">
<property name="text">
<string>Width:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="uiWidthSpinBox">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="uiHeightLabel">
<property name="text">
<string>Height:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="uiHeightSpinBox">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/style_editor_dialog.ui'
#
# Created by: PyQt5 UI code generator 5.15.9
# Created by: PyQt5 UI code generator 5.15.6
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
@@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_StyleEditorDialog(object):
def setupUi(self, StyleEditorDialog):
StyleEditorDialog.setObjectName("StyleEditorDialog")
StyleEditorDialog.resize(270, 294)
StyleEditorDialog.resize(288, 358)
StyleEditorDialog.setModal(True)
self.verticalLayout = QtWidgets.QVBoxLayout(StyleEditorDialog)
self.verticalLayout.setObjectName("verticalLayout")
@@ -53,7 +53,7 @@ class Ui_StyleEditorDialog(object):
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.uiBorderStyleComboBox)
self.uiRotationLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox)
self.uiRotationLabel.setObjectName("uiRotationLabel")
self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.uiRotationLabel)
self.formLayout.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.uiRotationLabel)
self.uiRotationSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -63,14 +63,30 @@ class Ui_StyleEditorDialog(object):
self.uiRotationSpinBox.setMinimum(-360)
self.uiRotationSpinBox.setMaximum(360)
self.uiRotationSpinBox.setObjectName("uiRotationSpinBox")
self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.uiRotationSpinBox)
self.formLayout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.uiRotationSpinBox)
self.uiCornerRadiusLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox)
self.uiCornerRadiusLabel.setObjectName("uiCornerRadiusLabel")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.uiCornerRadiusLabel)
self.formLayout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.uiCornerRadiusLabel)
self.uiCornerRadiusSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox)
self.uiCornerRadiusSpinBox.setMaximum(100)
self.uiCornerRadiusSpinBox.setObjectName("uiCornerRadiusSpinBox")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.uiCornerRadiusSpinBox)
self.formLayout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.uiCornerRadiusSpinBox)
self.uiWidthLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox)
self.uiWidthLabel.setObjectName("uiWidthLabel")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.uiWidthLabel)
self.uiWidthSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox)
self.uiWidthSpinBox.setMinimum(10)
self.uiWidthSpinBox.setMaximum(1000)
self.uiWidthSpinBox.setObjectName("uiWidthSpinBox")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.uiWidthSpinBox)
self.uiHeightLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox)
self.uiHeightLabel.setObjectName("uiHeightLabel")
self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.uiHeightLabel)
self.uiHeightSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox)
self.uiHeightSpinBox.setMinimum(10)
self.uiHeightSpinBox.setMaximum(1000)
self.uiHeightSpinBox.setObjectName("uiHeightSpinBox")
self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.uiHeightSpinBox)
self.verticalLayout.addWidget(self.uiStyleSettingsGroupBox)
self.uiButtonBox = QtWidgets.QDialogButtonBox(StyleEditorDialog)
self.uiButtonBox.setOrientation(QtCore.Qt.Horizontal)
@@ -100,4 +116,8 @@ class Ui_StyleEditorDialog(object):
self.uiRotationSpinBox.setSuffix(_translate("StyleEditorDialog", "°"))
self.uiCornerRadiusLabel.setText(_translate("StyleEditorDialog", "Corner radius:"))
self.uiCornerRadiusSpinBox.setSuffix(_translate("StyleEditorDialog", "°"))
self.uiWidthLabel.setText(_translate("StyleEditorDialog", "Width:"))
self.uiWidthSpinBox.setSuffix(_translate("StyleEditorDialog", " px"))
self.uiHeightLabel.setText(_translate("StyleEditorDialog", "Height:"))
self.uiHeightSpinBox.setSuffix(_translate("StyleEditorDialog", " px"))
from . import resources_rc

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 GNS3 Technologies Inc.
# Copyright (C) 2023 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
@@ -15,50 +15,35 @@
# 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 os
import tempfile
import pkg_resources
import atexit
import logging
import os
import sys
try:
import importlib_resources
except ImportError:
from importlib import resources as importlib_resources
from contextlib import ExitStack
resource_manager = ExitStack()
atexit.register(resource_manager.close)
log = logging.getLogger(__name__)
try:
egg_cache_dir = tempfile.mkdtemp()
pkg_resources.set_extraction_path(egg_cache_dir)
except ValueError:
# If the path is already set the module throw an error
pass
@atexit.register
def clean_egg_cache():
try:
import shutil
shutil.rmtree(egg_cache_dir, ignore_errors=True)
except Exception:
# We don't care if we can not cleanup
pass
def get_resource(resource_name):
"""
Return a resource in current directory or in frozen package
"""
resource_path = None
if hasattr(sys, "frozen"):
resource_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable), resource_name))
if sys.platform.startswith("darwin") and not os.path.exists(resource_path):
resource_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable), "lib", resource_name))
elif not hasattr(sys, "frozen"):
if pkg_resources.resource_exists("gns3", resource_name):
try:
resource_path = pkg_resources.resource_filename("gns3", resource_name)
except pkg_resources.ExtractionError as e:
log.fatal(e)
sys.stderr.write(e)
sys.exit(1)
resource_path = os.path.normpath(resource_path)
else:
resource_path = os.path.dirname(os.path.realpath(__file__))
resource_path = os.path.join(resource_path, "..", "..", "resources", resource_name)
else:
ref = importlib_resources.files("gns3") / resource_name
path = resource_manager.enter_context(importlib_resources.as_file(ref))
if os.path.exists(path):
resource_path = os.path.normpath(path)
return resource_path

View File

@@ -23,8 +23,9 @@
# or negative for a release candidate or beta (after the base version
# number has been incremented)
__version__ = "2.2.42"
__version_info__ = (2, 2, 42, 0)
__version__ = "2.2.44"
__version_info__ = (2, 2, 44, 0)
if "dev" in __version__:
try:
import os

View File

@@ -48,16 +48,27 @@ def vncConsole(node, port, command):
# replace the place-holders by the actual values
command = command.replace("%h", host)
command = command.replace("%p", str(port))
command = command.replace("%P", str(port - 5900))
command = command.replace("%D", str(port - 5900))
command = command.replace("%d", name.replace('"', '\\"'))
command = command.replace("%P", node.project().name().replace('"', '\\"'))
command = command.replace("%i", node.project().id())
command = command.replace("%n", str(node.id()))
command = command.replace("%c", Controller.instance().httpClient().fullUrl())
command = command.replace("{host}", host)
command = command.replace("{port}", str(port))
command = command.replace("{display}", str(port - 5900))
command = command.replace("{name}", name.replace('"', '\\"'))
command = command.replace("{project}", node.project().name().replace('"', '\\"'))
command = command.replace("{project_id}", node.project().id())
command = command.replace("{node_id}", str(node.id()))
command = command.replace("{url}", Controller.instance().httpClient().fullUrl())
try:
log.debug('starting VNC program "{}"'.format(command))
if sys.platform.startswith("win"):
# use the string on Windows
subprocess.Popen(command)
subprocess.Popen(command, env=os.environ)
else:
# use arguments on other platforms
args = shlex.split(command)

View File

@@ -1,3 +1,5 @@
-rrequirements.txt
PyQt5==5.15.7
PyQt5-Qt5==5.15.2
PyQt5-sip==12.12.2
PyQt5==5.15.9

View File

@@ -1,8 +1,9 @@
jsonschema>=4.17.3,<4.18; python_version >= '3.7'
jsonschema>=4.17.3,<4.18; python_version >= '3.7' # v4.17.3 is the last version to support Python 3.7
jsonschema==3.2.0; python_version < '3.7' # v3.2.0 is the last version to support Python 3.6
sentry-sdk==1.29.2,<1.30
psutil==5.9.5
sentry-sdk==1.34.0,<1.35
psutil==5.9.6
distro>=1.8.0
truststore>=0.7.0; python_version >= '3.10'
truststore>=0.8.0; python_version >= '3.10'
importlib-resources>=1.3; python_version < '3.9'
setuptools>=60.8.1; python_version >= '3.7'
setuptools==59.6.0; python_version < '3.7' # v59.6.0 is the last version to support Python 3.6

View File

@@ -101,6 +101,7 @@ setup(
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: Implementation :: CPython",
],
)

View File

@@ -75,6 +75,7 @@ def controller():
Controller._instance = None
c = Controller.instance()
c._http_client = MagicMock()
c._http_client.fullUrl.return_value = "http://localhost:3080"
return c
@@ -172,7 +173,7 @@ def local_server_config():
return LocalServerConfig.instance()
@pytest.yield_fixture(autouse=True)
@pytest.fixture(autouse=True)
def run_around_tests(local_config, main_window):
"""
This setup a temporay environnement around tests

View File

@@ -0,0 +1,50 @@
{
"appliance_id": "1c784362-8aaf-4312-b0f5-4b138cf2e25b",
"name": "Arista vEOS",
"category": "router",
"description": "Arista EOS® is the core of Arista cloud networking solutions for next-generation data centers and cloud networks. Cloud architectures built with Arista EOS scale to tens of thousands of compute and storage nodes with management and provisioning capabilities that work at scale. Through its programmability, EOS enables a set of software applications that deliver workflow automation, high availability, unprecedented network visibility and analytics and rapid integration with a wide range of third-party applications for virtualization, management, automation and orchestration services.\n\nArista Extensible Operating System (EOS) is a fully programmable and highly modular, Linux-based network operation system, using familiar industry standard CLI and runs a single binary software image across the Arista switching family. Architected for resiliency and programmability, EOS has a unique multi-process state sharing architecture that separates state information and packet forwarding from protocol processing and application logic.",
"vendor_name": "Arista",
"vendor_url": "http://www.arista.com/",
"documentation_url": "http://www.arista.com/docs/Manuals/ConfigGuide.pdf",
"product_name": "Arista vEOS",
"product_url": "https://eos.arista.com/",
"registry_version": 8,
"status": "stable",
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"settings": [
{
"template_type": "qemu",
"template_properties": {
"adapter_type": "e1000",
"adapters": 8,
"ram": 2048,
"platform": "x86_64",
"console_type": "telnet"
}
}
],
"images": [
{
"filename": "Aboot-veos-serial-2.1.0.iso",
"version": "2.1.0",
"md5sum": "2687534f2ff11b998dec0511066457c0",
"download_url": "https://www.arista.com/en/support/software-download"
},
{
"filename": "vEOS-lab-4.13.8M.vmdk",
"version": "4.13.8M",
"md5sum": "a47145b9e6e7a24171c0850f8755535e",
"download_url": "https://www.arista.com/en/support/software-download"
}
],
"versions": [
{
"name": "4.13.8M",
"images": {
"hda_disk_image": "Aboot-veos-serial-2.1.0.iso",
"hdb_disk_image": "vEOS-lab-4.13.8M.vmdk"
}
}
]
}

View File

@@ -1,4 +1,5 @@
{
"appliance_id": "1c784362-8aaf-4312-b0f5-4b138cf2e25b",
"name": "Arista vEOS",
"category": "router",
"description": "Arista EOS® is the core of Arista cloud networking solutions for next-generation data centers and cloud networks. Cloud architectures built with Arista EOS scale to tens of thousands of compute and storage nodes with management and provisioning capabilities that work at scale. Through its programmability, EOS enables a set of software applications that deliver workflow automation, high availability, unprecedented network visibility and analytics and rapid integration with a wide range of third-party applications for virtualization, management, automation and orchestration services.\n\nArista Extensible Operating System (EOS) is a fully programmable and highly modular, Linux-based network operation system, using familiar industry standard CLI and runs a single binary software image across the Arista switching family. Architected for resiliency and programmability, EOS has a unique multi-process state sharing architecture that separates state information and packet forwarding from protocol processing and application logic.",
@@ -11,7 +12,6 @@
"status": "stable",
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"qemu": {
"adapter_type": "e1000",
"adapters": 8,
@@ -19,7 +19,6 @@
"arch": "x86_64",
"console_type": "telnet"
},
"images": [
{
"filename": "Aboot-veos-serial-2.1.0.iso",
@@ -34,7 +33,6 @@
"download_url": "https://www.arista.com/en/support/software-download"
}
],
"versions": [
{
"name": "4.13.8M",

View File

@@ -11,7 +11,6 @@
"status": "stable",
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"qemu": {
"adapter_type": "e1000",
"adapters": 1,
@@ -19,7 +18,6 @@
"arch": "i386",
"console_type": "telnet"
},
"images": [
{
"filename": "linux-microcore-3.4.1.img",
@@ -30,7 +28,6 @@
"direct_download_url": "http://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/linux-microcore-3.4.1.img"
}
],
"versions": [
{
"name": "3.4.1",

View File

@@ -0,0 +1,51 @@
{
"appliance_id": "96dac9ff-581c-4262-a9f0-5c68890d049e",
"category": "router",
"status": "experimental",
"maintainer": "GNS3 Team",
"name": "Cisco 3745",
"vendor_name": "Cisco",
"product_name": "3745",
"vendor_url": "http://www.cisco.com",
"description": "Cisco 3745 Multiservice Access Router",
"registry_version": 8,
"maintainer_email": "developers@gns3.net",
"documentation_url": "http://www.cisco.com/c/en/us/support/routers/3745-multiservice-access-router/model.html",
"settings": [
{
"template_type": "dynamips",
"template_properties": {
"chassis": "",
"platform": "c3745",
"ram": 256,
"nvram": 256,
"startup_config": "ios_base_startup-config.txt",
"slot0": "GT96100-FE",
"slot1": "NM-1FE-TX",
"slot2": "NM-4T",
"slot3": "",
"slot4": "",
"wic0": "WIC-1T",
"wic1": "WIC-1T",
"wic2": "WIC-1T",
"idlepc": "0x60aa1da0"
}
}
],
"versions": [
{
"name": "124-25d",
"images": {
"image": "c3745-adventerprisek9-mz.124-25d.image"
}
}
],
"images": [
{
"filesize": 82053028,
"md5sum": "ddbaf74274822b50fa9670e10c75b08f",
"version": "124-25d",
"filename": "c3745-adventerprisek9-mz.124-25d.image"
}
]
}

View File

@@ -1,4 +1,5 @@
{
"appliance_id": "96dac9ff-581c-4262-a9f0-5c68890d049e",
"category": "router",
"status": "experimental",
"maintainer": "GNS3 Team",
@@ -10,7 +11,6 @@
"registry_version": 2,
"maintainer_email": "developers@gns3.net",
"documentation_url": "http://www.cisco.com/c/en/us/support/routers/3745-multiservice-access-router/model.html",
"dynamips": {
"chassis": "",
"platform": "c3745",
@@ -26,7 +26,6 @@
"wic1": "WIC-1T",
"wic2": "WIC-1T"
},
"versions": [
{
"images": {

View File

@@ -0,0 +1,41 @@
{
"appliance_id": "a8f5935d-7229-4b32-8a01-24ef41758f2f",
"category": "router",
"status": "experimental",
"maintainer": "GNS3 Team",
"name": "Cisco IOU L3",
"vendor_name": "Cisco",
"product_name": "Cisco IOU L3",
"vendor_url": "http://www.cisco.com",
"description": "Cisco IOS on UNIX Layer 3 image.",
"registry_version": 8,
"maintainer_email": "developers@gns3.net",
"settings": [
{
"template_type": "iou",
"template_properties": {
"ethernet_adapters": 2,
"serial_adapters": 2,
"nvram": 128,
"ram": 256,
"startup_config": "iou_l3_base_startup-config.txt"
}
}
],
"versions": [
{
"images": {
"image": "i86bi-linux-l3-adventerprisek9-15.4.1T.bin"
},
"name": "15.4.1T"
}
],
"images": [
{
"filesize": 152677848,
"md5sum": "5d41402abc4b2a76b9719d911017c592",
"version": "15.4.1T",
"filename": "i86bi-linux-l3-adventerprisek9-15.4.1T.bin"
}
]
}

View File

@@ -1,4 +1,5 @@
{
"appliance_id": "a8f5935d-7229-4b32-8a01-24ef41758f2f",
"category": "router",
"status": "experimental",
"maintainer": "GNS3 Team",
@@ -9,7 +10,6 @@
"description": "Cisco IOS on UNIX Layer 3 image.",
"registry_version": 2,
"maintainer_email": "developers@gns3.net",
"iou": {
"ethernet_adapters": 2,
"serial_adapters": 2,
@@ -17,7 +17,6 @@
"ram": 256,
"startup_config": "iou_l3_base_startup-config.txt"
},
"versions": [
{
"images": {

View File

@@ -0,0 +1,115 @@
{
"appliance_id": "1cfdf900-7c30-4cb7-8f03-3f61d2581633",
"name": "Empty VM",
"category": "guest",
"description": "A empty VM with empty hard disks 8G, 30G, 100G & 200G.",
"vendor_name": "GNS3",
"vendor_url": "https://gns3.com",
"documentation_url": "",
"product_name": "QEMU",
"product_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
"registry_version": 8,
"status": "experimental",
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"usage": "Default at first boot the VM will start from the cdrom.",
"settings": [
{
"default": true,
"template_type": "qemu",
"template_properties": {
"adapter_type": "e1000",
"adapters": 1,
"ram": 1024,
"hda_disk_interface": "sata",
"platform": "x86_64",
"console_type": "vnc",
"boot_priority": "d"
}
},
{
"name": "i386 settings",
"template_type": "qemu",
"template_properties": {
"platform": "i386",
"adapters": 8
}
},
{
"name": "ARM settings",
"inherit_default_properties": false,
"template_type": "qemu",
"template_properties": {
"platform": "arm",
"ram": 512
}
}
],
"images": [
{
"filename": "empty8G.qcow2",
"version": "8G",
"md5sum": "f1d2c25b6990f99bd05b433ab603bdb4",
"filesize": 197120,
"download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty8G.qcow2/download"
},
{
"filename": "empty30G.qcow2",
"version": "30G",
"checksum": "3411a599e822f2ac6be560a26405821a",
"filesize": 197120,
"download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty30G.qcow2/download"
},
{
"filename": "empty100G.qcow2",
"version": "100G",
"checksum": "d08fdec95fffbda3f04e9a00db49295df73ae4a507396e442ba9e4ad5c14ce5a",
"checksum_type": "sha256",
"filesize": 198656,
"download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty100G.qcow2/download"
},
{
"filename": "empty200G.qcow2",
"version": "200G",
"md5sum": "d1686d2f25695dee32eab9a6f4652c7c",
"filesize": 200192,
"download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty200G.qcow2/download"
}
],
"versions": [
{
"name": "8G",
"images": {
"hda_disk_image": "empty8G.qcow2"
}
},
{
"name": "30G",
"settings": "i386 settings",
"images": {
"hda_disk_image": "empty30G.qcow2"
}
},
{
"name": "100G",
"settings": "ARM settings",
"images": {
"hda_disk_image": "empty100G.qcow2"
}
},
{
"name": "200G",
"settings": "ARM settings",
"usage": "This is how to use this version",
"symbol": "ethernet_switch",
"category": "switch",
"images": {
"hda_disk_image": "empty200G.qcow2"
}
}
]
}

View File

@@ -3,8 +3,6 @@
"vendor_name": "Juniper",
"product_name": "vSRX",
"name": "vSRX",
"description": "The vSRX delivers core firewall, networking, advanced security, and automated lifecycle management capabilities for enterprises and service providers. The industry\u2019s fastest virtual security platform, the vSRX offers firewall speeds up to 17 Gbps using only two virtual CPUs, providing scalable, secure protection across private, public, and hybrid clouds.",
"maintainer_email": "developers@gns3.net",
"documentation_url": "http://www.juniper.net/techpubs/",
@@ -13,7 +11,6 @@
"status": "experimental",
"vendor_url": "https://www.juniper.net",
"registry_version": 1,
"qemu": {
"console_type": "telnet",
"ram": 2000,
@@ -23,8 +20,6 @@
"options": "-smp 2",
"kvm": "require"
},
"images": [
{
"version": "12.1X47.4-domestic",
@@ -34,7 +29,6 @@
"download_url": "https://www.juniper.net/us/en/dm/free-vsrx-trial/"
}
],
"versions": [
{
"name": "12.1X47.4-domestic",
@@ -43,4 +37,4 @@
}
}
]
}
}

View File

@@ -12,7 +12,6 @@
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"usage": "Just start the appliance",
"qemu": {
"adapter_type": "e1000",
"adapters": 1,
@@ -21,7 +20,6 @@
"console_type": "telnet",
"kvm": "allow"
},
"images": [
{
"filename": "linux-microcore-3.4.1.img",
@@ -40,7 +38,6 @@
"direct_download_url": "http://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/linux-microcore-4.0.2-clean.img"
}
],
"versions": [
{
"name": "3.4.1",

View File

@@ -0,0 +1,24 @@
{
"appliance_id": "40c84186-752f-4a71-b6fb-961ba36e8cf7",
"name": "Open vSwitch",
"category": "multilayer_switch",
"description": "Open vSwitch is a production quality, multilayer virtual switch licensed under the open source Apache 2.0 license. It is designed to enable massive network automation through programmatic extension, while still supporting standard management interfaces and protocols (e.g. NetFlow, sFlow, IPFIX, RSPAN, CLI, LACP, 802.1ag). In addition, it is designed to support distribution across multiple physical servers similar to VMware's vNetwork distributed vswitch or Cisco's Nexus 1000V.",
"vendor_name": "Open vSwitch",
"vendor_url": "http://openvswitch.org/",
"documentation_url": "http://openvswitch.org/support/",
"product_name": "Open vSwitch",
"registry_version": 8,
"status": "stable",
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"usage": "By default all interfaces are connected to the br0",
"settings": [
{
"template_type": "docker",
"template_properties": {
"adapters": 16,
"image": "gns3/openvswitch:latest"
}
}
]
}

View File

@@ -1,4 +1,5 @@
{
"appliance_id": "40c84186-752f-4a71-b6fb-961ba36e8cf7",
"name": "Open vSwitch",
"category": "multilayer_switch",
"description": "Open vSwitch is a production quality, multilayer virtual switch licensed under the open source Apache 2.0 license. It is designed to enable massive network automation through programmatic extension, while still supporting standard management interfaces and protocols (e.g. NetFlow, sFlow, IPFIX, RSPAN, CLI, LACP, 802.1ag). In addition, it is designed to support distribution across multiple physical servers similar to VMware's vNetwork distributed vswitch or Cisco's Nexus 1000V.",

View File

@@ -202,6 +202,62 @@ def test_create_new_version():
os.remove(wrong_appliance_file)
def test_emulator():
assert Appliance(registry, os.path.abspath("tests/registry/appliances/microcore-linux.gns3a")).emulator() == "qemu"
assert Appliance(registry, os.path.abspath("tests/registry/appliances/cisco-iou-l3.gns3a")).emulator() == "iou"
def test_template_type():
assert Appliance(registry, os.path.abspath("tests/registry/appliances/microcore-linux.gns3a")).template_type() == "qemu"
assert Appliance(registry, os.path.abspath("tests/registry/appliances/cisco-iou-l3.gns3a")).template_type() == "iou"
def test_checksum_in_appliance_format_v8(registry, images_dir):
path_empty_8g = os.path.join(images_dir, "QEMU", "empty8G.qcow2")
with open(path_empty_8g, 'w+') as f:
f.write("hello")
appliance = Appliance(registry, os.path.abspath("tests/registry/appliances/empty-vm-v8.gns3a"))
appliance._appliance['versions'][0]['images']['hda_disk_image']['filesize'] = 5
appliance._appliance['versions'][0]['images']['hda_disk_image']['md5sum'] = "5d41402abc4b2a76b9719d911017c592"
detected = appliance.search_images_for_version("8G")
assert detected["images"][0]["md5sum"] == "5d41402abc4b2a76b9719d911017c592"
assert detected["images"][0]["filesize"] == 5
path_empty_30g = os.path.join(images_dir, "QEMU", "empty30G.qcow2")
os.rename(path_empty_8g, path_empty_30g)
appliance._appliance['versions'][1]['images']['hda_disk_image']['filesize'] = 5
appliance._appliance['versions'][1]['images']['hda_disk_image']['checksum'] = "5d41402abc4b2a76b9719d911017c592"
detected = appliance.search_images_for_version("30G")
assert detected["images"][0]["md5sum"] == "5d41402abc4b2a76b9719d911017c592"
assert detected["images"][0]["filesize"] == 5
with pytest.raises(ApplianceError):
appliance.search_images_for_version("100G")
def test_multiple_different_template_types_found():
appliance_path = "tests/registry/appliances/empty-vm-v8.gns3a"
wrong_appliance_fp, wrong_appliance_file = tempfile.mkstemp()
with open(appliance_path, encoding='utf-8') as f:
appliance = json.loads(f.read())
appliance["settings"][0]["template_type"] = "dynamips"
os.write(wrong_appliance_fp, json.dumps(appliance).encode())
os.close(wrong_appliance_fp)
with pytest.raises(ApplianceError):
Appliance(registry, wrong_appliance_file).template_type()
def test_appliance_without_settings():
appliance_path = "tests/registry/appliances/empty-vm-v8.gns3a"
wrong_appliance_fp, wrong_appliance_file = tempfile.mkstemp()
with open(appliance_path, encoding='utf-8') as f:
appliance = json.loads(f.read())
del appliance["settings"]
os.write(wrong_appliance_fp, json.dumps(appliance).encode())
os.close(wrong_appliance_fp)
with pytest.raises(ApplianceError):
Appliance(registry, wrong_appliance_file).template_type()

View File

@@ -64,8 +64,15 @@ def empty_config(tmpdir, images_dir, symbols_dir, local_server_config):
return Config(path)
def test_add_appliance_iou(iou_l3):
with open("tests/registry/appliances/cisco-iou-l3.gns3a", encoding="utf-8") as f:
@pytest.mark.parametrize(
"appliance_file",
[
"cisco-iou-l3.gns3a",
"cisco-iou-l3-v8.gns3a"
]
)
def test_add_appliance_iou(iou_l3, appliance_file):
with open("tests/registry/appliances/{}".format(appliance_file), encoding="utf-8") as f:
config = json.load(f)
config["images"] = [
{
@@ -74,7 +81,7 @@ def test_add_appliance_iou(iou_l3):
"path": iou_l3
}
]
new_template = ApplianceToTemplate().new_template(config, "local")
new_template = ApplianceToTemplate().new_template(config, "local", "15.4.1T")
assert new_template == {
"category": "router",
"template_type": "iou",
@@ -91,8 +98,15 @@ def test_add_appliance_iou(iou_l3):
}
def test_add_appliance_docker():
with open("tests/registry/appliances/openvswitch.gns3a", encoding="utf-8") as f:
@pytest.mark.parametrize(
"appliance_file",
[
"openvswitch.gns3a",
"openvswitch-v8.gns3a"
]
)
def test_add_appliance_docker(appliance_file):
with open("tests/registry/appliances/{}".format(appliance_file), encoding="utf-8") as f:
config = json.load(f)
new_template = ApplianceToTemplate().new_template(config, "local")
@@ -108,8 +122,15 @@ def test_add_appliance_docker():
}
def test_add_appliance_dynamips(cisco_3745):
with open("tests/registry/appliances/cisco-3745.gns3a", encoding="utf-8") as f:
@pytest.mark.parametrize(
"appliance_file",
[
"cisco-3745.gns3a",
"cisco-3745-v8.gns3a"
]
)
def test_add_appliance_dynamips(cisco_3745, appliance_file):
with open("tests/registry/appliances/{}".format(appliance_file), encoding="utf-8") as f:
config = json.load(f)
config["images"] = [
{
@@ -120,7 +141,7 @@ def test_add_appliance_dynamips(cisco_3745):
}
]
new_template = ApplianceToTemplate().new_template(config, "local")
new_template = ApplianceToTemplate().new_template(config, "local", "124-25d")
assert new_template == {
"template_type": "dynamips",
"category": "router",
@@ -166,7 +187,6 @@ def test_add_appliance_guest(linux_microcore_img):
"symbol": ":/symbols/qemu_guest.svg",
"hda_disk_image": "linux-microcore-3.4.1.img",
"name": "Micro Core Linux",
"options": "",
"qemu_path": "qemu-system-i386",
"usage": "Just start the appliance",
"ram": 32,
@@ -238,8 +258,15 @@ def test_add_appliance_with_boot_priority(linux_microcore_img):
assert new_template["boot_priority"] == "dc"
def test_add_appliance_router_two_disk(images_dir):
with open("tests/registry/appliances/arista-veos.gns3a", encoding="utf-8") as f:
@pytest.mark.parametrize(
"appliance_file",
[
"arista-veos.gns3a",
"arista-veos-v8.gns3a"
]
)
def test_add_appliance_router_two_disk(images_dir, appliance_file):
with open("tests/registry/appliances/{}".format(appliance_file), encoding="utf-8") as f:
config = json.load(f)
config["images"] = [
@@ -255,8 +282,8 @@ def test_add_appliance_router_two_disk(images_dir):
}
]
new_template = ApplianceToTemplate().new_template(config, "local")
assert new_template == {
new_template = ApplianceToTemplate().new_template(config, "local", "4.13.8M")
expected_result = {
"template_type": "qemu",
"adapter_type": "e1000",
"adapters": 8,
@@ -265,12 +292,76 @@ def test_add_appliance_router_two_disk(images_dir):
"hda_disk_image": "a",
"hdb_disk_image": "b",
"name": "Arista vEOS",
"options": "",
"qemu_path": "qemu-system-x86_64",
"ram": 2048,
"console_type": "telnet",
"compute_id": "local"
}
if "v8" in appliance_file:
expected_result["platform"] = "x86_64" # platform was added in v8
assert new_template == expected_result
def test_add_appliance_v8_default_properties_inheritance(images_dir):
with open("tests/registry/appliances/empty-vm-v8.gns3a", encoding="utf-8") as f:
config = json.load(f)
# check that default properties are used
new_template = ApplianceToTemplate().new_template(config, "local", "8G")
expected_result = {
"name": "Empty VM",
"template_type": "qemu",
"symbol": ":/symbols/qemu_guest.svg",
"category": "guest",
"adapter_type": "e1000",
"adapters": 1,
"ram": 1024,
"qemu_path": "qemu-system-x86_64",
"hda_disk_interface": "sata",
"platform": "x86_64",
"console_type": "vnc",
"boot_priority": "d",
"compute_id": "local",
"usage": "Default at first boot the VM will start from the cdrom."
}
assert new_template == expected_result
# check that specific properties are used along with default properties
new_template = ApplianceToTemplate().new_template(config, "local", "30G")
expected_result.update(
{
"adapters": 8,
"qemu_path": "qemu-system-i386",
"platform": "i386",
}
)
assert new_template == expected_result
# check that specific properties are used along without default properties
new_template = ApplianceToTemplate().new_template(config, "local", "100G")
expected_result = {
"name": "Empty VM",
"template_type": "qemu",
"symbol": ":/symbols/qemu_guest.svg",
"category": "guest",
"ram": 512,
"qemu_path": "qemu-system-arm",
"platform": "arm",
"compute_id": "local",
"usage": "Default at first boot the VM will start from the cdrom."
}
assert new_template == expected_result
# check that specific properties are used with "usage", "symbol" and "category" defined at the version level
new_template = ApplianceToTemplate().new_template(config, "local", "200G")
expected_result.update(
{
"usage": "This is how to use this version",
"symbol": "ethernet_switch",
"category": "switch"
}
)
assert new_template == expected_result
def test_add_appliance_path_relative_to_images_dir(tmpdir, linux_microcore_img):

View File

@@ -47,7 +47,7 @@ def http_client(http_request, network_manager):
return HTTPClient({"protocol": "http", "host": "127.0.0.1", "port": "3080"}, network_manager=network_manager)
@pytest.yield_fixture(autouse=True)
@pytest.fixture(autouse=True)
def http_request():
mock = unittest.mock.Mock()

View File

@@ -34,7 +34,7 @@ def images_dir(tmpdir):
return path
@pytest.yield_fixture
@pytest.fixture
def image_manager(tmpdir, images_dir):
ImageManager._instance = None
settings = LOCAL_SERVER_SETTINGS

View File

@@ -16,7 +16,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import json
import pytest
import logging
import subprocess
@@ -32,7 +31,7 @@ def local_server_path(tmpdir):
return str(tmpdir / "gns3server")
@pytest.yield_fixture
@pytest.fixture
def local_server(local_server_path, tmpdir):
with open(str(tmpdir / "test.cfg"), "w+") as f:
f.write("""
@@ -87,7 +86,7 @@ def test_startLocalServer(tmpdir, local_server, local_server_path):
'--debug',
'--log=' + str(tmpdir / "gns3_server.log"),
'--pid=' + str(tmpdir / "gns3_server.pid")
], stderr=unittest.mock.ANY)
], stderr=unittest.mock.ANY, env=unittest.mock.ANY)
def test_killAlreadyRunningServer(local_server):

View File

@@ -17,28 +17,30 @@
import os
import shlex
import pytest
from unittest.mock import patch
from gns3.spice_console import spiceConsole
def test_spice_console_on_linux_and_mac(vpcs_device):
with patch('subprocess.Popen') as popen, \
patch('sys.platform', new="linux"):
vpcs_device.settings()["console_host"] = "localhost"
spiceConsole(vpcs_device, '2525', 'command %h %p')
popen.assert_called_once_with(shlex.split('command localhost 2525'), env=os.environ)
popen.assert_called_with(shlex.split('command localhost 2525'), env=os.environ)
spiceConsole(vpcs_device, '2525', 'command {host} {port}')
popen.assert_called_with(shlex.split('command localhost 2525'), env=os.environ)
def test_spice_console_on_windows(vpcs_device):
with patch('subprocess.Popen') as popen, \
with patch('subprocess.Popen') as p, \
patch('sys.platform', new="win"):
vpcs_device.settings()["console_host"] = "localhost"
spiceConsole(vpcs_device, '2525', 'command %h %p')
popen.assert_called_once_with('command localhost 2525')
p.assert_called_with('command localhost 2525', env=os.environ)
spiceConsole(vpcs_device, '2525', 'command {host} {port}')
p.assert_called_with('command localhost 2525', env=os.environ)
# def test_spice_console_on_linux_with_popen_issues():
# with patch('subprocess.Popen', side_effect=OSError("Dummy")), \
@@ -49,9 +51,11 @@ def test_spice_console_on_windows(vpcs_device):
def test_spice_console_with_ipv6_support(vpcs_device):
with patch('subprocess.Popen') as popen, \
patch('sys.platform', new="linux"):
vpcs_device.settings()["console_host"] = "::1"
spiceConsole(vpcs_device, '2525', 'command %h %p')
popen.assert_called_once_with(shlex.split('command [::1] 2525'), env=os.environ)
popen.assert_called_with(shlex.split('command [::1] 2525'), env=os.environ)
spiceConsole(vpcs_device, '2525', 'command {host} {port}')
popen.assert_called_with(shlex.split('command [::1] 2525'), env=os.environ)

View File

@@ -26,14 +26,14 @@ from gns3.update_manager import UpdateManager
from gns3 import version
@pytest.yield_fixture
@pytest.fixture
def frozen():
sys.frozen = True
yield
delattr(sys, 'frozen')
@pytest.yield_fixture
@pytest.fixture
def devVersion():
old_version_info = version.__version_info__
old_version = version.__version__
@@ -44,7 +44,7 @@ def devVersion():
version.__version__ = old_version
@pytest.yield_fixture
@pytest.fixture
def stableVersion():
old_version_info = version.__version_info__
old_version = version.__version__

View File

@@ -17,31 +17,36 @@
import os
import shlex
import pytest
from unittest.mock import patch
from gns3.vnc_console import vncConsole
def test_vnc_console_on_linux_and_mac(vpcs_device):
with patch('subprocess.Popen') as popen, \
with patch('subprocess.Popen') as p, \
patch('sys.platform', new="linux"):
vpcs_device.settings()["console_host"] = "localhost"
vncConsole(vpcs_device, 6000, 'command %h %p %P')
popen.assert_called_once_with(shlex.split('command localhost 6000 100'), env=os.environ)
vncConsole(vpcs_device, 6000, 'command %h %p %D')
p.assert_called_with(shlex.split('command localhost 6000 100'), env=os.environ)
vncConsole(vpcs_device, 6000, 'command {host} {port} {display}')
p.assert_called_with(shlex.split('command localhost 6000 100'), env=os.environ)
def test_vnc_console_on_windows(vpcs_device):
with patch('subprocess.Popen') as popen, \
with patch('subprocess.Popen') as p, \
patch('sys.platform', new="win"):
vpcs_device.settings()["console_host"] = "localhost"
vncConsole(vpcs_device, 6000, 'command %h %p %P')
popen.assert_called_once_with('command localhost 6000 100')
vncConsole(vpcs_device, 6000, 'command %h %p %D')
p.assert_called_with('command localhost 6000 100', env=os.environ)
vncConsole(vpcs_device, 6000, 'command {host} {port} {display}')
p.assert_called_with('command localhost 6000 100', env=os.environ)
# def test_vnc_console_on_linux_with_popen_issues():
# with patch('subprocess.Popen', side_effect=OSError("Dummy")), \
# def test_vnc_console_on_linux_with_popen_issues(vpcs_device):
# with patch('subprocess.Popen', side_effect=subprocess.SubprocessError()), \
# patch('sys.platform', new="linux"):
# vpcs_device.settings()["console_host"] = "localhost"
#
# with pytest.raises(OSError):
# vncConsole('localhost', 6000, 'command %h %p %P')
# with pytest.raises(subprocess.SubprocessError):
# vncConsole(vpcs_device, 6000, 'command %h %p %P')

View File

@@ -1,4 +1,4 @@
-rrequirements.txt
PyQt5==5.15.7 # pyup: ignore
pywin32==305 # pyup: ignore
PyQt5==5.15.10 # pyup: ignore
pywin32==306 # pyup: ignore