mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-06-07 03:02:08 +03:00
Compare commits
111 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7f7a688d3 | ||
|
|
bb66555896 | ||
|
|
37726630ce | ||
|
|
e26762fce3 | ||
|
|
32e59fcce4 | ||
|
|
9b43330e95 | ||
|
|
b3b94243b4 | ||
|
|
fba1032388 | ||
|
|
ccc0803c5b | ||
|
|
f0340bcc98 | ||
|
|
8d19b8fcbf | ||
|
|
dbf66d5a9a | ||
|
|
0fa8d61b19 | ||
|
|
02d326419b | ||
|
|
608e80a80b | ||
|
|
d90f11eb86 | ||
|
|
dc39187091 | ||
|
|
e4cd418533 | ||
|
|
8559469c73 | ||
|
|
e6ba7bdd98 | ||
|
|
784055689f | ||
|
|
c937811f45 | ||
|
|
79c8021faa | ||
|
|
a428730f59 | ||
|
|
3cad8ea046 | ||
|
|
ab1775e44b | ||
|
|
9c3facb07a | ||
|
|
e5ae7b2d25 | ||
|
|
60462ff986 | ||
|
|
39443cd676 | ||
|
|
25ab8249ae | ||
|
|
6e86c606cc | ||
|
|
8878b96e74 | ||
|
|
c3ef2edbab | ||
|
|
f7e398edc3 | ||
|
|
8d4fc9585e | ||
|
|
5fe65e26ce | ||
|
|
daaebe7f96 | ||
|
|
f9f6a52e8b | ||
|
|
601c0217b9 | ||
|
|
b0971b4ba3 | ||
|
|
5a4549c36c | ||
|
|
fe2cc362f8 | ||
|
|
dd0220fd59 | ||
|
|
26fdd9ef6f | ||
|
|
733ee259e5 | ||
|
|
762fecbcff | ||
|
|
5e85dfe5fd | ||
|
|
e01632d60a | ||
|
|
60916e8b80 | ||
|
|
4fe55634ae | ||
|
|
b1b861c99d | ||
|
|
07c0474386 | ||
|
|
755fb5c8f3 | ||
|
|
fdb382874d | ||
|
|
471b3e1009 | ||
|
|
f64a226336 | ||
|
|
dec257bb6b | ||
|
|
e736fbbb87 | ||
|
|
aeb42b3ffe | ||
|
|
1694a57ed9 | ||
|
|
26001463a0 | ||
|
|
36c1197f1f | ||
|
|
1fa16936fe | ||
|
|
fca65784ee | ||
|
|
8983e6c5a9 | ||
|
|
c6e06f3941 | ||
|
|
b7e32a60ce | ||
|
|
da100e494b | ||
|
|
9d7b5bccb8 | ||
|
|
112b05c3dd | ||
|
|
987e85cce8 | ||
|
|
8142d0baa7 | ||
|
|
cc32b2661c | ||
|
|
781857f598 | ||
|
|
69179dcb63 | ||
|
|
8017838d60 | ||
|
|
81946493ec | ||
|
|
a7f40c3d50 | ||
|
|
c28723287f | ||
|
|
c7a8588647 | ||
|
|
4a64261c5d | ||
|
|
5f4365542c | ||
|
|
2e2c951ffc | ||
|
|
2ba7dde326 | ||
|
|
a1579ca86b | ||
|
|
81c4ddb85f | ||
|
|
285a8d413a | ||
|
|
672c86b38d | ||
|
|
905611d2a8 | ||
|
|
339fb22217 | ||
|
|
a8f5fa5dd5 | ||
|
|
bd365dd6eb | ||
|
|
8e4a6169e0 | ||
|
|
b1790844f3 | ||
|
|
4b0050d26c | ||
|
|
19bf40dc89 | ||
|
|
23cda61d17 | ||
|
|
c74ffde65a | ||
|
|
0a9c10e748 | ||
|
|
6973eaaa02 | ||
|
|
9ce483398b | ||
|
|
55744ab129 | ||
|
|
0e1cb47aa1 | ||
|
|
3bf12753df | ||
|
|
8907659220 | ||
|
|
a4ccb0b620 | ||
|
|
7d845c0ef8 | ||
|
|
8282ec1da6 | ||
|
|
990cb49854 | ||
|
|
c530924d8a |
@@ -13,6 +13,9 @@ notifications:
|
||||
# on_success: change
|
||||
# on_failure: always
|
||||
|
||||
install:
|
||||
- "pip install --upgrade -r dev-requirements.txt"
|
||||
|
||||
script:
|
||||
- docker build -t gns3-gui-test .
|
||||
- docker run gns3-gui-test
|
||||
|
||||
110
CHANGELOG
110
CHANGELOG
@@ -1,5 +1,115 @@
|
||||
# Change Log
|
||||
|
||||
## 1.4.0 12/01/2016
|
||||
|
||||
* Fix rare crash when showing the progress dialog
|
||||
* Fix crash on Windows when a gui is already running
|
||||
* Add default idle-pc value for c7200-adventerprisek9-mz.155-2.XB. Fixes #389.
|
||||
|
||||
## 1.4.0rc3 05/01/2016
|
||||
|
||||
* Add information about antivirus and firewall in case of connection fail
|
||||
* Change link to doc for missing router image
|
||||
* On windows and OSX experimental Qemu GNS3A support
|
||||
* Wait server in thread
|
||||
* Fix local server non avaible in appliance wizard when local server started by hand.
|
||||
* Improve pid checks
|
||||
* Allow Qemu appliances to optionally specify desired disk interfaces in their configuration (defaults to "ide").
|
||||
* Fix project non closing when you have only remote servers
|
||||
* Fix Windows layout not saved in some scenario
|
||||
* Added the ability to name Qemu/VirtualBox/VMware interfaces with numbers starting from 1, along with named aliases for numbers starting from 0, and a tooltip explaining the possible name format variables.
|
||||
* Added missing Qemu adapters to the topology schema.
|
||||
* Fix Cannot save my topology getting an error message for temporary topology
|
||||
* Fix creation of ASA devices
|
||||
* Turn off Docker until 1.5
|
||||
* Fix display of server preferences on small screen
|
||||
* Fix If you turn off the local server and close the gui and reopen preferences you have an issue
|
||||
* Zoc 7 support
|
||||
* Fix warning when closing GUI
|
||||
* Fix crash when opening a new topologies after gns3 converter failure
|
||||
|
||||
## 1.3.13 11/12/2015
|
||||
|
||||
* Release server 1.3.13
|
||||
|
||||
## 1.3.12 11/12/2015
|
||||
|
||||
* Fix warning when closing GUI
|
||||
* Update links for new website.
|
||||
* Ask user to send bug reports to GNS3.com
|
||||
* Fix travis dependencies installation
|
||||
* Drop Webkit from 1.3.X
|
||||
* Fix crash when opening a new topologies after gns3 converter failure
|
||||
* Set Wireshark 2.0 as default OSX version
|
||||
* iTerm 2.9 support
|
||||
* Add informations about GNS3 VM
|
||||
* Drops securecrt.vbs
|
||||
* Fix analytics report on OSX
|
||||
* Analytics send windows
|
||||
* Fix the progress dialog freeze bug
|
||||
* Fix typo in analytics
|
||||
* Send stats to GNS3 team
|
||||
* Licenses compliance.
|
||||
* Fix error when importing dynamips config from non existent directory
|
||||
* Fix crash when url is invalid
|
||||
* Add a debug level 2 in the console
|
||||
|
||||
## 1.4.0rc2 10/12/2015
|
||||
|
||||
* Prevent user turning off the Local server when using the GNS3 VM
|
||||
* Force VM wizard to be modal
|
||||
* Fix unicode error when exporting debug informations
|
||||
* Fix Debug can't be deactivated for current session
|
||||
* Support relative path for configuration file.
|
||||
* Report bug to GNS3.com
|
||||
* Avoid crash when cancel connection to a server
|
||||
* Fix version number display twice when installing appliance
|
||||
* Show a warning when you try to save as a remote topology
|
||||
* Fix application restart after self upgrade
|
||||
* Cleanup server autostart
|
||||
* Have default console port start from 2000.
|
||||
* Fix crash when opening a new topologies after gns3 converter failure
|
||||
* Fix SSH support
|
||||
* Fix bus error when writting on console
|
||||
* Fix Ok & Cancel button in preferences are broken
|
||||
* Fix After a project load failure you can't open new project
|
||||
* More fix around closing the GUI
|
||||
* Fix crash in progress dialog on OSX
|
||||
* Kill already running zombie server
|
||||
* Store the pid of the server when started
|
||||
* Fix a crash in rare case after a PyQT garbage collect
|
||||
* Allow to use the VNC port range for console
|
||||
* Preferences dialog resize
|
||||
* Fix a race condition when opening telnet from apple script
|
||||
* Experimental support for tabbed terminal on OSX
|
||||
* Fix validation error when saving topology with an usage
|
||||
* Fix another case of not closing windows
|
||||
* Fix GUI doesn't close after connection error to remote server
|
||||
* Add usage text to device template and on hover
|
||||
* Fix a rare crash in progress dialog
|
||||
* Fix crash when displaying an error from the update
|
||||
* Url encode royal tx url
|
||||
* Use Royal TX URI scheme thanks to @lemonmojo
|
||||
* OSX support for Royal TSX
|
||||
* Merge branch 'master' into unstable
|
||||
* Set Wireshark 2.0 as default OSX version
|
||||
* iTerm 2.9 support
|
||||
* Update debug information dialog.
|
||||
* Change text for export debug information.
|
||||
* Add informations about GNS3 VM
|
||||
|
||||
## 1.4.0rc1 12/15/2015
|
||||
|
||||
* Rename an appliance if the default name is already taken
|
||||
* Existing image option should be hidden when none is available
|
||||
* Warn users about the need to uncompress the image
|
||||
* Fix crash when you have no qemu vms and use gns3a
|
||||
* Change sentry key
|
||||
* Fix format_exception() missing 2 required positional arguments: 'value' and 'tb' in topologyFile
|
||||
* Fix dialog box not returning their result
|
||||
* Log to console the Qt Message Boxes
|
||||
* Drops securecrt.vbs
|
||||
|
||||
## 1.4.0b5 02/11/2015
|
||||
|
||||
* Fix crash when loading invalid appliance file
|
||||
|
||||
50
CONTRIBUTING.md
Normal file
50
CONTRIBUTING.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Contributing to GNS3
|
||||
|
||||
We welcome contributions and bugs reports from everyone.
|
||||
We are friendly so don't be afraid to ask questions.
|
||||
|
||||
## Bug reports
|
||||
|
||||
Before reporting an issue:
|
||||
* check our website over at https://gns3.com
|
||||
* check if an issue already exists on https://github.com/GNS3/gns3-gui
|
||||
* check if an issue already exists on https://github.com/GNS3/gns3-server
|
||||
|
||||
Please post on our community website if you are unsure you found a bug,
|
||||
you will get faster support and be able to exchange with more users.
|
||||
|
||||
If you are unsure which project you should create an issue for, just do
|
||||
it on https://github.com/GNS3/gns3-gui we will take care of the triage.
|
||||
|
||||
For bugs specific to the GNS3 VM, please report on https://github.com/GNS3/gns3-vm
|
||||
|
||||
## Asking for new features
|
||||
|
||||
The best is to start a discussion on the community website in order to get feedback
|
||||
from the whole community.
|
||||
|
||||
|
||||
## Contributing code
|
||||
|
||||
We welcome code contribution from everyone including beginners.
|
||||
Don't be afraid to submit a half finished or mediocre contribution and we will help you.
|
||||
|
||||
Don't hesitate to share your plans before starting working on a contribution, we can help
|
||||
you to find the best approach.
|
||||
|
||||
### Contributors License Agreements
|
||||
|
||||
We at GNS3 are eager to work with you. For small changes — little bugfixes, correcting typos, and the like — please just submit pull requests to any of our projects. For larger changes, though, we have to ask you to jump through a little hoop.
|
||||
|
||||
In particular, in order for us to accept any major patches from you, you will have to electronically sign a statement that indicates two things:
|
||||
|
||||
- You are willingly licensing your contributions under the terms of the open source license of the project that you’re contributing to.
|
||||
- You are legally able to license your contributions as stated.
|
||||
|
||||
The reason we do this is to ensure, to the extent possible, that we don’t “taint” the projects we manage with contributions that turn out to be improper. This protects everyone who wants to use the projects, including you!
|
||||
|
||||
More information there: https://github.com/GNS3/cla
|
||||
|
||||
### Pull requests
|
||||
|
||||
Creating a pull request is the easiest way to contribute code. Do not hesitate to create one early when contributing for new feature in order to get our feedback.
|
||||
@@ -1,3 +1,4 @@
|
||||
# Run tests inside a container
|
||||
FROM ubuntu:vivid
|
||||
|
||||
MAINTAINER GNS3 Team
|
||||
|
||||
@@ -50,7 +50,7 @@ Finally these commands will install the GUI as well as the rest of the dependenc
|
||||
Windows
|
||||
-------
|
||||
|
||||
Please use our `all-in-one installer <https://community.gns3.com/community/software/download>`_ to install the stable build.
|
||||
Please use our `all-in-one installer <https://gns3.com/software/download>`_ to install the stable build.
|
||||
|
||||
If you install via source you need to first install:
|
||||
|
||||
|
||||
@@ -208,16 +208,17 @@ class ConsoleCmd(cmd.Cmd):
|
||||
return
|
||||
|
||||
root = logging.getLogger()
|
||||
ch = logging.StreamHandler(sys.stdout)
|
||||
|
||||
if len(args) == 1:
|
||||
level = int(args[0])
|
||||
if level == 0:
|
||||
print("Deactivating debugging")
|
||||
root.removeHandler(ch)
|
||||
for handler in root.handlers:
|
||||
if isinstance(handler, logging.StreamHandler):
|
||||
root.removeHandler(handler)
|
||||
root.setLevel(logging.INFO)
|
||||
else:
|
||||
root.addHandler(ch)
|
||||
root.addHandler(logging.StreamHandler(sys.stdout))
|
||||
if level == 1:
|
||||
print("Activating debugging")
|
||||
else:
|
||||
|
||||
@@ -49,7 +49,7 @@ class CrashReport:
|
||||
Report crash to a third party service
|
||||
"""
|
||||
|
||||
DSN = "sync+https://a86f12c42f7746288a81af9a9c1145a4:db8b6973bd2c448ea0a98675119ff8ee@app.getsentry.com/38506"
|
||||
DSN = "sync+https://3d44e34021504514a5fb0539ae6f8f92:af41562761754b4c9beca492d1b9115d@app.getsentry.com/38506"
|
||||
if hasattr(sys, "frozen"):
|
||||
cacert = get_resource("cacert.pem")
|
||||
if cacert is not None and os.path.isfile(cacert):
|
||||
@@ -94,7 +94,7 @@ class CrashReport:
|
||||
"python:encoding": sys.getdefaultencoding(),
|
||||
"python:frozen": "{}".format(hasattr(sys, "frozen"))
|
||||
}
|
||||
context = self._add_qt_informations(context)
|
||||
context = self._add_qt_information(context)
|
||||
client.tags_context(context)
|
||||
try:
|
||||
report = client.captureException((exception, value, tb))
|
||||
@@ -103,7 +103,7 @@ class CrashReport:
|
||||
return
|
||||
log.info("Crash report sent with event ID: {}".format(client.get_ident(report)))
|
||||
|
||||
def _add_qt_informations(self, context):
|
||||
def _add_qt_information(self, context):
|
||||
try:
|
||||
from .qt import QtCore
|
||||
import sip
|
||||
|
||||
@@ -18,9 +18,10 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from ..qt import QtWidgets, QtCore, QtGui
|
||||
from ..qt import QtWidgets, QtCore, QtGui, qpartial
|
||||
from ..ui.appliance_wizard_ui import Ui_ApplianceWizard
|
||||
from ..image_manager import ImageManager
|
||||
from ..modules import Qemu
|
||||
from ..registry.appliance import Appliance
|
||||
from ..registry.registry import Registry
|
||||
from ..registry.config import Config, ConfigException
|
||||
@@ -30,7 +31,7 @@ from ..utils.wait_for_lambda_worker import WaitForLambdaWorker
|
||||
from ..utils.progress_dialog import ProgressDialog
|
||||
from ..servers import Servers
|
||||
from ..gns3_vm import GNS3VM
|
||||
|
||||
from ..local_config import LocalConfig
|
||||
|
||||
class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
|
||||
@@ -40,13 +41,13 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
self._path = path
|
||||
self.setupUi(self)
|
||||
images_directories = list()
|
||||
images_directories.append(os.path.join(ImageManager.instance().getDirectory(), "QEMU"))
|
||||
images_directories.append(os.path.dirname(self._path))
|
||||
download_directory = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DownloadLocation)
|
||||
if download_directory != "" and download_directory != os.path.dirname(self._path):
|
||||
images_directories.append(download_directory)
|
||||
self._registry = Registry(images_directories)
|
||||
self._appliance = Appliance(self._registry, self._path)
|
||||
self._registry.appendImageDirectory(os.path.join(ImageManager.instance().getDirectory(), self._appliance.image_dir_name()))
|
||||
|
||||
self.uiApplianceVersionTreeWidget.currentItemChanged.connect(self._applianceVersionCurrentItemChangedSlot)
|
||||
self.uiRefreshPushButton.clicked.connect(self._refreshVersions)
|
||||
@@ -61,6 +62,8 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
if hasattr(self, "uiLoadBalanceCheckBox"):
|
||||
self.uiLoadBalanceCheckBox.toggled.connect(self._loadBalanceToggledSlot)
|
||||
|
||||
self.uiServerWizardPage.isComplete = self._uiServerWizardPage_isComplete
|
||||
|
||||
def initializePage(self, page_id):
|
||||
"""
|
||||
Initialize Wizard pages.
|
||||
@@ -75,6 +78,13 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
symbol = ":/symbols/{}.svg".format(self._appliance["category"])
|
||||
self.page(page_id).setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(symbol))
|
||||
|
||||
if "qemu" in self._appliance:
|
||||
type = "qemu"
|
||||
elif "iou" in self._appliance:
|
||||
type = "iou"
|
||||
elif "dynamips" in self._appliance:
|
||||
type = "dynamips"
|
||||
|
||||
if self.page(page_id) == self.uiInfoWizardPage:
|
||||
self.uiInfoWizardPage.setTitle(self._appliance["product_name"])
|
||||
self.uiDescriptionLabel.setText(self._appliance["description"])
|
||||
@@ -84,16 +94,18 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
("Product", "product_name"),
|
||||
("Vendor", "vendor_name"),
|
||||
("Status", "status"),
|
||||
("Maintainer", "maintainer")
|
||||
("Maintainer", "maintainer"),
|
||||
("KVM", "kvm")
|
||||
)
|
||||
|
||||
self.uiInfoTreeWidget.clear()
|
||||
for (name, key) in info:
|
||||
item = QtWidgets.QTreeWidgetItem([name + ":", self._appliance[key]])
|
||||
font = item.font(0)
|
||||
font.setBold(True)
|
||||
item.setFont(0, font)
|
||||
self.uiInfoTreeWidget.addTopLevelItem(item)
|
||||
if key in self._appliance:
|
||||
item = QtWidgets.QTreeWidgetItem([name + ":", self._appliance[key]])
|
||||
font = item.font(0)
|
||||
font.setBold(True)
|
||||
item.setFont(0, font)
|
||||
self.uiInfoTreeWidget.addTopLevelItem(item)
|
||||
|
||||
elif self.page(page_id) == self.uiServerWizardPage:
|
||||
self.uiRemoteServersComboBox.clear()
|
||||
@@ -104,12 +116,12 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
self.uiVMRadioButton.setEnabled(False)
|
||||
|
||||
# Qemu has issues on OSX and Windows we disallow usage of the local server
|
||||
if sys.platform.startswith("darwin") or sys.platform.startswith("win"):
|
||||
if (sys.platform.startswith("darwin") or sys.platform.startswith("win")) and not LocalConfig.instance().experimental():
|
||||
self.uiLocalRadioButton.setEnabled(False)
|
||||
|
||||
if GNS3VM.instance().isRunning():
|
||||
self.uiVMRadioButton.setChecked(True)
|
||||
elif Servers.instance().localServerIsRunning():
|
||||
elif Servers.instance().localServer().isLocalServerRunning():
|
||||
self.uiLocalRadioButton.setChecked(True)
|
||||
elif len(Servers.instance().remoteServers().values()) > 0:
|
||||
self.uiRemoteRadioButton.setChecked(True)
|
||||
@@ -119,10 +131,14 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
elif self.page(page_id) == self.uiFilesWizardPage:
|
||||
self._refreshVersions()
|
||||
|
||||
elif self.page(page_id) == self.uiQemuWizardPage:
|
||||
Qemu.instance().getQemuBinariesFromServer(self._server, qpartial(self._getQemuBinariesFromServerCallback), [self._appliance["qemu"]["arch"]])
|
||||
|
||||
elif self.page(page_id) == self.uiSummaryWizardPage:
|
||||
self.uiSummaryTreeWidget.clear()
|
||||
for key in self._appliance["qemu"]:
|
||||
item = QtWidgets.QTreeWidgetItem([key.replace('_', ' ').capitalize() + ":", str(self._appliance["qemu"][key])])
|
||||
|
||||
for key in self._appliance[type]:
|
||||
item = QtWidgets.QTreeWidgetItem([key.replace('_', ' ').capitalize() + ":", str(self._appliance[type][key])])
|
||||
font = item.font(0)
|
||||
font.setBold(True)
|
||||
item.setFont(0, font)
|
||||
@@ -135,6 +151,9 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
self._appliance.get("usage", ""))
|
||||
)
|
||||
|
||||
def _uiServerWizardPage_isComplete(self):
|
||||
return self.uiRemoteRadioButton.isEnabled() or self.uiVMRadioButton.isEnabled() or self.uiLocalRadioButton.isEnabled()
|
||||
|
||||
def _refreshVersions(self):
|
||||
"""
|
||||
Refresh the list of files for different version of the appliance
|
||||
@@ -236,6 +255,8 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
if data is not None:
|
||||
if "direct_download_url" in data:
|
||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl(data["direct_download_url"]))
|
||||
if "compression" in data:
|
||||
QtWidgets.QMessageBox.warning(self, "Add appliance", "The image is compressed with {} you need to uncompress it before using it.".format(data["compression"]))
|
||||
else:
|
||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl(data["download_url"]))
|
||||
|
||||
@@ -254,16 +275,36 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
|
||||
image = Image(path)
|
||||
if image.md5sum != disk["md5sum"]:
|
||||
QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "This is not the correct image file.")
|
||||
QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "This is not the correct image file. The MD5 sum is {} and should be {}".format(image.md5sum, disk["md5sum"]))
|
||||
return
|
||||
|
||||
config = Config()
|
||||
worker = WaitForLambdaWorker(lambda: image.copy(os.path.join(config.images_dir, "QEMU"), disk["filename"]), allowed_exceptions=[OSError])
|
||||
worker = WaitForLambdaWorker(lambda: image.copy(os.path.join(config.images_dir, self._appliance.image_dir_name()), disk["filename"]), allowed_exceptions=[OSError])
|
||||
progress_dialog = ProgressDialog(worker, "Add appliance", "Import the appliance...", None, busy=True, parent=self)
|
||||
if not progress_dialog.exec_():
|
||||
return
|
||||
self._refreshVersions()
|
||||
|
||||
def _getQemuBinariesFromServerCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
Callback for getQemuBinariesFromServer.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
QtWidgets.QMessageBox.critical(self, "Qemu binaries", "{}".format(result["message"]))
|
||||
else:
|
||||
self.uiQemuListComboBox.clear()
|
||||
for qemu in result:
|
||||
if qemu["version"]:
|
||||
self.uiQemuListComboBox.addItem("{path} (v{version})".format(path=qemu["path"], version=qemu["version"]), qemu["path"])
|
||||
else:
|
||||
self.uiQemuListComboBox.addItem("{path}".format(path=qemu["path"]), qemu["path"])
|
||||
if self.uiQemuListComboBox.count() == 1:
|
||||
self.next()
|
||||
|
||||
def _install(self, version):
|
||||
"""
|
||||
Install the appliance to GNS3
|
||||
@@ -286,6 +327,13 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
else:
|
||||
server_string = self._server.url()
|
||||
|
||||
while len(appliance_configuration["name"]) == 0 or not config.is_name_available(appliance_configuration["name"]):
|
||||
QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "The name \"{}\" is already used by another appliance".format(appliance_configuration["name"]))
|
||||
appliance_configuration["name"], ok = QtWidgets.QInputDialog.getText(self.parent(), "Add appliance", "New name:", QtWidgets.QLineEdit.Normal, appliance_configuration["name"])
|
||||
appliance_configuration["name"] = appliance_configuration["name"].strip()
|
||||
|
||||
appliance_configuration["qemu"]["path"] = self.uiQemuListComboBox.currentData()
|
||||
|
||||
worker = WaitForLambdaWorker(lambda: config.add_appliance(appliance_configuration, server_string), allowed_exceptions=[ConfigException, OSError])
|
||||
progress_dialog = ProgressDialog(worker, "Add appliance", "Install the appliance...", None, busy=True, parent=self)
|
||||
progress_dialog.show()
|
||||
@@ -296,7 +344,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
progress_dialog = ProgressDialog(worker, "Add appliance", "Install the appliance...", None, busy=True, parent=self)
|
||||
progress_dialog.show()
|
||||
if progress_dialog.exec_():
|
||||
QtWidgets.QMessageBox.information(self.parent(), "Add appliance", "{} {} installed!".format(self._appliance["name"], version))
|
||||
QtWidgets.QMessageBox.information(self.parent(), "Add appliance", "{} installed!".format(appliance_configuration["name"]))
|
||||
return True
|
||||
|
||||
def validateCurrentPage(self):
|
||||
@@ -334,8 +382,18 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
return False
|
||||
self._server = gns3_vm_server
|
||||
else:
|
||||
if (sys.platform.startswith("darwin") or sys.platform.startswith("win")):
|
||||
reply = QtWidgets.QMessageBox.question(self, "Appliance", "Qemu on Windows and MacOSX is not supported by the GNS3 team. Are you sur to continue?", QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
|
||||
if reply == QtWidgets.QMessageBox.No:
|
||||
return False
|
||||
|
||||
self._server = Servers.instance().localServer()
|
||||
|
||||
elif self.currentPage() == self.uiQemuWizardPage:
|
||||
if self.uiQemuListComboBox.currentIndex() == -1:
|
||||
QtWidgets.QMessageBox.critical(self, "Qemu binary", "No compatible Qemu binary selected")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _vmToggledSlot(self, checked):
|
||||
|
||||
@@ -31,7 +31,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
class ExportDebugDialog(QtWidgets.QDialog, Ui_ExportDebugDialog):
|
||||
"""
|
||||
This dialog allow user to export informations usefull
|
||||
This dialog allow user to export useful information
|
||||
for remote debugging by a GNS3 developers.
|
||||
"""
|
||||
|
||||
@@ -49,7 +49,7 @@ class ExportDebugDialog(QtWidgets.QDialog, Ui_ExportDebugDialog):
|
||||
self.reject()
|
||||
return
|
||||
|
||||
log.info("Export debug informations to %s", path)
|
||||
log.info("Export debug information to %s", path)
|
||||
|
||||
try:
|
||||
with ZipFile(path, 'w') as zip:
|
||||
@@ -67,7 +67,7 @@ class ExportDebugDialog(QtWidgets.QDialog, Ui_ExportDebugDialog):
|
||||
if os.path.isfile(path):
|
||||
zip.write(path, filename)
|
||||
except OSError as e:
|
||||
QtWidgets.QMessageBox.critical(self, "Debug", "Can't export debug informations: {}".format(str(e)))
|
||||
QtWidgets.QMessageBox.critical(self, "Debug", "Can't export debug information: {}".format(str(e)))
|
||||
self.accept()
|
||||
|
||||
def _getDebugData(self):
|
||||
@@ -77,6 +77,11 @@ class ExportDebugDialog(QtWidgets.QDialog, Ui_ExportDebugDialog):
|
||||
except psutil.AccessDenied:
|
||||
connections = None
|
||||
|
||||
try:
|
||||
addrs = ["* {}: {}".format(key, val) for key, val in psutil.net_if_addrs().items()]
|
||||
except UnicodeDecodeError:
|
||||
addrs = ["INVALID ADDR WITH UNICODE CHARACTERS"]
|
||||
|
||||
data = """Version: {version}
|
||||
OS: {os}
|
||||
Python: {python}
|
||||
@@ -99,7 +104,7 @@ Processus:
|
||||
memory=psutil.virtual_memory(),
|
||||
cpu=psutil.cpu_times(),
|
||||
connections=connections,
|
||||
addrs="\n".join(["* {}: {}".format(key, val) for key, val in psutil.net_if_addrs().items()])
|
||||
addrs="\n".join(addrs)
|
||||
)
|
||||
for proc in psutil.process_iter():
|
||||
try:
|
||||
|
||||
@@ -38,8 +38,16 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
|
||||
def __init__(self, parent):
|
||||
|
||||
super().__init__(parent)
|
||||
|
||||
self.setupUi(self)
|
||||
|
||||
# We adapt the max size to the screen resolution
|
||||
height = QtWidgets.QDesktopWidget().screenGeometry().height() - 100
|
||||
if height > 800:
|
||||
height = 800
|
||||
self.setMaximumSize(QtCore.QSize(900, height))
|
||||
self.resize(900, height)
|
||||
|
||||
self.uiTreeWidget.currentItemChanged.connect(self._showPreferencesPageSlot)
|
||||
self._applyButton = self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply)
|
||||
self._applyButton.clicked.connect(self._applyPreferences)
|
||||
@@ -54,6 +62,8 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
|
||||
# Something has change?
|
||||
self._modified = False
|
||||
|
||||
|
||||
|
||||
def _loadPreferencePages(self):
|
||||
"""
|
||||
Loads all preference pages (built-ins and from modules).
|
||||
|
||||
@@ -66,9 +66,9 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
|
||||
def _VMwareBannerButtonClickedSlot(self):
|
||||
if sys.platform.startswith("darwin"):
|
||||
url = "http://send.onenetworkdirect.net/z/616454/CD225091/"
|
||||
url = "http://send.onenetworkdirect.net/z/616461/CD225091/"
|
||||
else:
|
||||
url = "http://send.onenetworkdirect.net/z/616455/CD225091/"
|
||||
url = "http://send.onenetworkdirect.net/z/616460/CD225091/"
|
||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl(url))
|
||||
|
||||
def _listVMwareVMsSlot(self):
|
||||
|
||||
@@ -160,6 +160,14 @@ class VMWithImagesWizard(VMWizard):
|
||||
QtWidgets.QMessageBox.critical(self, "Images", "Error while getting the VMs: {}".format(result["message"]))
|
||||
return
|
||||
|
||||
if len(result) == 0:
|
||||
for radio_button in self._radio_existing_images_buttons:
|
||||
if radio_button.isChecked():
|
||||
for button in radio_button.parent().findChildren(QtWidgets.QRadioButton):
|
||||
if button != radio_button:
|
||||
button.setChecked(True)
|
||||
button.hide()
|
||||
|
||||
for combo_box in self._images_combo_boxes:
|
||||
combo_box.clear()
|
||||
for vm in result:
|
||||
|
||||
@@ -35,6 +35,8 @@ class VMWizard(QtWidgets.QWizard):
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self.setModal(True)
|
||||
|
||||
self._devices = devices
|
||||
self._use_local_server = use_local_server
|
||||
|
||||
|
||||
@@ -295,13 +295,9 @@ class HTTPClient(QtCore.QObject):
|
||||
if json_data is None or status != 200:
|
||||
return False
|
||||
else:
|
||||
version = json_data.get("version")
|
||||
local_server = json_data.get("local", False)
|
||||
if version != __version__:
|
||||
log.debug("Client version {} differs with server version {}".format(__version__, version))
|
||||
return False
|
||||
if not local_server:
|
||||
log.debug("Running server is not a GNS3 local server (not started with --local)")
|
||||
version = json_data.get("version", None)
|
||||
if version is None:
|
||||
log.debug("Server is not a GNS3 server")
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -451,7 +447,10 @@ class HTTPClient(QtCore.QObject):
|
||||
if len(msg) > 0:
|
||||
msg = "Cannot connect to {}: {}".format(server, msg)
|
||||
else:
|
||||
msg = "Cannot connect to {}".format(server)
|
||||
if self.isLocal():
|
||||
msg = "Cannot connect to {}. Please check if GNS3 is allowed in your antivirus and firewall.".format(server)
|
||||
else:
|
||||
msg = "Cannot connect to {}".format(server)
|
||||
log.error(msg)
|
||||
if callback is not None:
|
||||
callback({"message": msg}, error=True, server=self)
|
||||
|
||||
@@ -228,7 +228,8 @@ class NodeItem():
|
||||
:param message: error message
|
||||
"""
|
||||
|
||||
self._last_error = "{message}".format(message=message)
|
||||
if self:
|
||||
self._last_error = "{message}".format(message=message)
|
||||
|
||||
def errorSlot(self, node_id, message):
|
||||
"""
|
||||
@@ -239,7 +240,8 @@ class NodeItem():
|
||||
:param message: error message
|
||||
"""
|
||||
|
||||
self._last_error = "{message}".format(message=message)
|
||||
if self:
|
||||
self._last_error = "{message}".format(message=message)
|
||||
|
||||
def setCustomToolTip(self):
|
||||
"""
|
||||
|
||||
@@ -21,6 +21,8 @@ import json
|
||||
import shutil
|
||||
import copy
|
||||
|
||||
import psutil
|
||||
|
||||
from .qt import QtCore
|
||||
from .version import __version__
|
||||
from .utils import parse_version
|
||||
@@ -305,3 +307,41 @@ class LocalConfig(QtCore.QObject):
|
||||
if not hasattr(LocalConfig, "_instance") or LocalConfig._instance is None:
|
||||
LocalConfig._instance = LocalConfig(config_file=config_file)
|
||||
return LocalConfig._instance
|
||||
|
||||
@staticmethod
|
||||
def isMainGui():
|
||||
"""
|
||||
:returns: Return true if we are the main gui (first gui to start)
|
||||
"""
|
||||
|
||||
my_pid = os.getpid()
|
||||
pid_path = os.path.join(LocalConfig.configDirectory(), "gns3_gui.pid")
|
||||
|
||||
if os.path.exists(pid_path):
|
||||
try:
|
||||
with open(pid_path) as f:
|
||||
pid = int(f.read())
|
||||
if pid != my_pid:
|
||||
try:
|
||||
process = psutil.Process(pid=pid)
|
||||
ps_name = process.name()
|
||||
except (OSError, psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
pass
|
||||
else:
|
||||
if "gns3" in ps_name or "python" in ps_name:
|
||||
# Process run under the same user id
|
||||
if sys.platform.startswith("win") or process.uids()[0] == os.getuid():
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
except (OSError, ValueError) as e:
|
||||
log.critical("Can't read pid file %s: %s", pid_path, str(e))
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(pid_path, 'w+') as f:
|
||||
f.write(str(my_pid))
|
||||
except OSError as e:
|
||||
log.critical("Can't write pid file %s: %s", pid_path, str(e))
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -25,7 +25,7 @@ try:
|
||||
if gns3.update_manager.UpdateManager().installDownloadedUpdates():
|
||||
print("Update installed restart the application")
|
||||
python = sys.executable
|
||||
os.execl(python, python, * sys.argv)
|
||||
os.execl(python, *sys.argv)
|
||||
except Exception as e:
|
||||
print("Fail update installation: {}".format(str(e)))
|
||||
|
||||
@@ -156,7 +156,7 @@ def main():
|
||||
|
||||
lines = traceback.format_exception(exception, value, tb)
|
||||
print("****** Exception detected, traceback information saved in {} ******".format(exception_file_path))
|
||||
print("\nPLEASE REPORT ON https://community.gns3.com/community/software/bug\n")
|
||||
print("\nPLEASE REPORT ON https://www.gns3.com\n")
|
||||
print("".join(lines))
|
||||
try:
|
||||
curdate = time.strftime("%d %b %Y %H:%M:%S")
|
||||
|
||||
@@ -248,7 +248,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.uiLabInstructionsAction.triggered.connect(self._labInstructionsActionSlot)
|
||||
self.uiAboutQtAction.triggered.connect(self._aboutQtActionSlot)
|
||||
self.uiAboutAction.triggered.connect(self._aboutActionSlot)
|
||||
self.uiExportDebugInformationsAction.triggered.connect(self._exportDebugInformationsSlot)
|
||||
self.uiExportDebugInformationAction.triggered.connect(self._exportDebugInformationSlot)
|
||||
self.uiIOUVMConverterAction.triggered.connect(self._IOUVMConverterActionSlot)
|
||||
|
||||
# browsers tool bar connections
|
||||
@@ -459,8 +459,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self._appliance_wizard.exec_()
|
||||
elif self.checkForUnsavedChanges():
|
||||
self._open_project_path = path
|
||||
self._project.project_closed_signal.connect(self._projectClosedContinueLoadPath)
|
||||
self._project.close()
|
||||
if self._project.closed():
|
||||
self._projectClosedContinueLoadPath()
|
||||
else:
|
||||
self._project.project_closed_signal.connect(self._projectClosedContinueLoadPath)
|
||||
self._project.close()
|
||||
|
||||
def _projectClosedContinueLoadPath(self):
|
||||
|
||||
@@ -853,7 +856,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
Slot to launch a browser pointing to the documentation page.
|
||||
"""
|
||||
|
||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl("http://www.gns3.net/documentation/"))
|
||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl("https://gns3.com/support/docs"))
|
||||
|
||||
def _checkForUpdateActionSlot(self, silent=False):
|
||||
"""
|
||||
@@ -910,9 +913,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
dialog.show()
|
||||
dialog.exec_()
|
||||
|
||||
def _exportDebugInformationsSlot(self):
|
||||
def _exportDebugInformationSlot(self):
|
||||
"""
|
||||
Slot to display a window for exporting debug informations
|
||||
Slot to display a window for exporting debug information
|
||||
"""
|
||||
|
||||
dialog = ExportDebugDialog(self, self._project)
|
||||
@@ -1030,30 +1033,25 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
|
||||
log.debug("Close the main Windows")
|
||||
servers = Servers.instance()
|
||||
if self._project.closed() and not servers.localServerIsRunning():
|
||||
if self._project.closed():
|
||||
log.debug("Project is closed killing server and closing main windows")
|
||||
self._finish_application_closing(close_windows=False)
|
||||
event.accept()
|
||||
self.uiConsoleTextEdit.closeIO()
|
||||
elif not self._soft_exit or self.checkForUnsavedChanges():
|
||||
log.debug("Project is not closed asking for project closing")
|
||||
self._project.project_closed_signal.connect(self._finish_application_closing)
|
||||
if servers.localServerIsRunning():
|
||||
self._project.close(local_server_shutdown=True)
|
||||
else:
|
||||
self._project.close(local_server_shutdown=False)
|
||||
|
||||
if self._project.closed() and not servers.localServerIsRunning():
|
||||
log.debug("Disconnect all servers")
|
||||
servers.disconnectAllServers()
|
||||
event.accept()
|
||||
self.uiConsoleTextEdit.closeIO()
|
||||
else:
|
||||
event.ignore()
|
||||
self._project.close(local_server_shutdown=True)
|
||||
event.ignore()
|
||||
else:
|
||||
event.ignore()
|
||||
|
||||
def _finish_application_closing(self):
|
||||
def _finish_application_closing(self, close_windows=True):
|
||||
"""
|
||||
Handles the event when the main window is closed.
|
||||
And project closed.
|
||||
|
||||
:params closing: True the windows is currently closing do not try to reclose it
|
||||
"""
|
||||
|
||||
log.debug("_finish_application_closing")
|
||||
@@ -1070,7 +1068,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
time_spent = "{:.0f}".format(time.time() - self._start_time)
|
||||
log.debug("Time spend in the software is {}".format(time_spent))
|
||||
self._analytics_client.sendScreenView("Main Window", session_start=False)
|
||||
self.close()
|
||||
|
||||
if close_windows:
|
||||
self.close()
|
||||
|
||||
def checkForUnsavedChanges(self):
|
||||
"""
|
||||
@@ -1134,23 +1134,20 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
|
||||
# start and connect to the local server
|
||||
server = servers.localServer()
|
||||
if servers.localServerAutoStart():
|
||||
if server.isLocalServerRunning():
|
||||
log.info("A local server already running on this host")
|
||||
else:
|
||||
if servers.initLocalServer() and servers.startLocalServer():
|
||||
worker = WaitForConnectionWorker(server.host(), server.port())
|
||||
progress_dialog = ProgressDialog(worker,
|
||||
"Local server",
|
||||
"Connecting to server {} on port {}...".format(server.host(), server.port()),
|
||||
"Cancel", busy=True, parent=self)
|
||||
progress_dialog.show()
|
||||
if not progress_dialog.exec_():
|
||||
return
|
||||
else:
|
||||
if servers.shouldLocalServerAutoStart():
|
||||
if not servers.localServerAutoStart():
|
||||
QtWidgets.QMessageBox.critical(self, "Local server", "Could not start the local server process: {}".format(servers.localServerPath()))
|
||||
return
|
||||
|
||||
worker = WaitForConnectionWorker(server.host(), server.port())
|
||||
progress_dialog = ProgressDialog(worker,
|
||||
"Local server",
|
||||
"Connecting to server {} on port {}...".format(server.host(), server.port()),
|
||||
"Cancel", busy=True, parent=self)
|
||||
progress_dialog.show()
|
||||
if not progress_dialog.exec_():
|
||||
return
|
||||
|
||||
# show the setup wizard
|
||||
with Progress.instance().context(min_duration=0):
|
||||
setup_wizard = SetupWizard(self)
|
||||
@@ -1202,6 +1199,17 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
running_nodes.append(node.name())
|
||||
return running_nodes
|
||||
|
||||
def _isTopologyOnRemoteServer(self):
|
||||
"""
|
||||
:returns: Boolean True if topology run on a remote server
|
||||
"""
|
||||
topology = Topology.instance()
|
||||
running_nodes = []
|
||||
for node in topology.nodes():
|
||||
if not node.server().isLocal():
|
||||
return True
|
||||
return False
|
||||
|
||||
def saveProjectAs(self):
|
||||
"""
|
||||
Saves a project to another location/name.
|
||||
@@ -1219,6 +1227,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
MessageBox(self, "Save project", "Please stop the following nodes before saving the topology to a new location", nodes)
|
||||
return
|
||||
|
||||
if self._isTopologyOnRemoteServer() and not self._project.temporary():
|
||||
MessageBox(self, "Save project", "You can not use the save as function on a remote project for the moment.")
|
||||
return
|
||||
|
||||
if self._project.temporary():
|
||||
default_project_name = "untitled"
|
||||
else:
|
||||
@@ -1326,6 +1338,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
except ImportError as e:
|
||||
log.error("GNS3 converter is missing: {}".format(str(e)))
|
||||
QtWidgets.QMessageBox.critical(self, "GNS3 converter", "Please install gns3-converter in order to open old ini-style GNS3 projects")
|
||||
self._createTemporaryProject()
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -1341,6 +1354,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
project_name = text
|
||||
project_dir = os.path.join(self.projectsDirPath(), project_name)
|
||||
else:
|
||||
self._createTemporaryProject()
|
||||
return
|
||||
|
||||
for snapshot_def in get_snapshots(path):
|
||||
@@ -1350,12 +1364,14 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
do_conversion(topology_def, project_name, project_dir, quiet=True)
|
||||
except ConvertError as e:
|
||||
QtWidgets.QMessageBox.critical(self, "GNS3 converter", "Could not convert {}: {}".format(path, e))
|
||||
self._createTemporaryProject()
|
||||
return
|
||||
except Exception:
|
||||
exc_type, exc_value, exc_tb = sys.exc_info()
|
||||
lines = traceback.format_exception(exc_type, exc_value, exc_tb)
|
||||
tb = "".join(lines)
|
||||
MessageBox(self, "GNS3 converter", "Unexpected exception while converting {}".format(path), details=tb)
|
||||
self._createTemporaryProject()
|
||||
return
|
||||
|
||||
QtWidgets.QMessageBox.information(self, "GNS3 converter", "Your project has been converted to a new format and can be found in: {}".format(project_dir))
|
||||
|
||||
@@ -22,6 +22,5 @@ from gns3.modules.vpcs import VPCS
|
||||
from gns3.modules.virtualbox import VirtualBox
|
||||
from gns3.modules.qemu import Qemu
|
||||
from gns3.modules.vmware import VMware
|
||||
from gns3.modules.docker import Docker
|
||||
|
||||
MODULES = [VPCS, Dynamips, IOU, Qemu, VirtualBox, VMware, Docker, Builtin]
|
||||
MODULES = [VPCS, Dynamips, IOU, Qemu, VirtualBox, VMware, Builtin]
|
||||
|
||||
@@ -258,18 +258,16 @@ class Router(VM):
|
||||
|
||||
# push the startup-config
|
||||
if not vm_id and "startup_config" in additional_settings:
|
||||
if additional_settings["startup_config"] and os.path.isfile(additional_settings["startup_config"]):
|
||||
base_config_content = self._readBaseConfig(additional_settings["startup_config"])
|
||||
if base_config_content is not None:
|
||||
params["startup_config_content"] = base_config_content
|
||||
base_config_content = self._readBaseConfig(additional_settings["startup_config"])
|
||||
if base_config_content is not None:
|
||||
params["startup_config_content"] = base_config_content
|
||||
del additional_settings["startup_config"]
|
||||
|
||||
# push the private-config
|
||||
if not vm_id and "private_config" in additional_settings:
|
||||
if additional_settings["private_config"] and os.path.isfile(additional_settings["private_config"]):
|
||||
base_config_content = self._readBaseConfig(additional_settings["private_config"])
|
||||
if base_config_content is not None:
|
||||
params["private_config_content"] = base_config_content
|
||||
base_config_content = self._readBaseConfig(additional_settings["private_config"])
|
||||
if base_config_content is not None:
|
||||
params["private_config_content"] = base_config_content
|
||||
del additional_settings["private_config"]
|
||||
|
||||
params.update(additional_settings)
|
||||
@@ -316,10 +314,9 @@ class Router(VM):
|
||||
|
||||
params = {}
|
||||
if "startup_config" in new_settings:
|
||||
if new_settings["startup_config"] and os.path.isfile(new_settings["startup_config"]):
|
||||
base_config_content = self._readBaseConfig(new_settings["startup_config"])
|
||||
if base_config_content is not None:
|
||||
params["startup_config_content"] = base_config_content
|
||||
base_config_content = self._readBaseConfig(new_settings["startup_config"])
|
||||
if base_config_content is not None:
|
||||
params["startup_config_content"] = base_config_content
|
||||
del new_settings["startup_config"]
|
||||
|
||||
if "private_config" in new_settings:
|
||||
|
||||
@@ -523,7 +523,7 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
|
||||
if not startup_config:
|
||||
settings["startup_config"] = ""
|
||||
elif startup_config != settings["startup_config"]:
|
||||
if os.access(startup_config, os.R_OK):
|
||||
if self._configFileValid(startup_config):
|
||||
settings["startup_config"] = startup_config
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(self, "Startup-config", "Cannot read the startup-config file")
|
||||
@@ -532,7 +532,7 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
|
||||
if not private_config:
|
||||
settings["private_config"] = ""
|
||||
elif private_config != settings["private_config"]:
|
||||
if os.access(private_config, os.R_OK):
|
||||
if self._configFileValid(private_config):
|
||||
settings["private_config"] = private_config
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(self, "Private-config", "Cannot read the private-config file")
|
||||
@@ -636,3 +636,11 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
|
||||
if node:
|
||||
self._checkForLinkConnectedToWIC(wic_number, settings, node)
|
||||
settings["wic" + str(wic_number)] = None
|
||||
|
||||
def _configFileValid(self, path):
|
||||
"""
|
||||
Return true if it's a valid configuration file
|
||||
"""
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.join(Servers.instance().localServerSettings()["configs_path"], path)
|
||||
return os.access(path, os.R_OK)
|
||||
|
||||
@@ -73,6 +73,7 @@ PLATFORMS_DEFAULT_NVRAM = {"c1700": 128,
|
||||
"c3745": 256,
|
||||
"c7200": 512}
|
||||
|
||||
# MD5 checksum done on uncompressed IOS images
|
||||
DEFAULT_IDLEPC = {"7f4ae12a098391bc0edcaf4f44caaf9d": "0x80358a60", # c1700-adventerprisek9-mz.124-25d
|
||||
"3aaecd2222e812c16c211bc9f7c77512": "0x824a4dc4", # c1700-adventerprisek9-mz.124-15.T14
|
||||
"062a32e9e3f59aeec930ea5694fda9c9": "0x80519c48", # c2600-adventerprisek9-mz.124-25d
|
||||
@@ -87,7 +88,8 @@ DEFAULT_IDLEPC = {"7f4ae12a098391bc0edcaf4f44caaf9d": "0x80358a60", # c1700-adv
|
||||
"64f8c427ed48fd21bd02cf1ff254c4eb": "0x60c09aa0", # c3725-adventerprisek9-mz.124-15.T14
|
||||
"ddbaf74274822b50fa9670e10c75b08f": "0x60aa1da0", # c3745-adventerprisek9-mz.124-25d
|
||||
"4af2e752220ed1397924150ff7bbe4ce": "0x602701e4", # c3745-adventerprisek9-mz.124-15.T14
|
||||
"6b89d0d804e1f2bb5b8bda66b5692047": "0x606df838"} # c7200-adventerprisek9-mz.124-24.T5
|
||||
"6b89d0d804e1f2bb5b8bda66b5692047": "0x606df838", # c7200-adventerprisek9-mz.124-24.T5
|
||||
"dda82f22a39215bc6b27af891e12b8f6": "0x6018c294"} # c7200-adventerprisek9-mz.155-2.XB
|
||||
|
||||
# platforms with supported chassis
|
||||
CHASSIS = {"c1700": ("1720", "1721", "1750", "1751", "1760"),
|
||||
|
||||
@@ -129,18 +129,16 @@ class IOUDevice(VM):
|
||||
|
||||
# push the startup-config
|
||||
if "startup_config" in additional_settings:
|
||||
if additional_settings["startup_config"] and os.path.isfile(additional_settings["startup_config"]):
|
||||
base_config_content = self._readBaseConfig(additional_settings["startup_config"])
|
||||
if base_config_content is not None:
|
||||
params["startup_config_content"] = base_config_content
|
||||
base_config_content = self._readBaseConfig(additional_settings["startup_config"])
|
||||
if base_config_content is not None:
|
||||
params["startup_config_content"] = base_config_content
|
||||
del additional_settings["startup_config"]
|
||||
|
||||
# push the startup-config
|
||||
if "private_config" in additional_settings:
|
||||
if additional_settings["private_config"] and os.path.isfile(additional_settings["private_config"]):
|
||||
base_config_content = self._readBaseConfig(additional_settings["private_config"])
|
||||
if base_config_content is not None:
|
||||
params["private_config_content"] = base_config_content
|
||||
base_config_content = self._readBaseConfig(additional_settings["private_config"])
|
||||
if base_config_content is not None:
|
||||
params["private_config_content"] = base_config_content
|
||||
del additional_settings["private_config"]
|
||||
|
||||
params = self._addIourcContentToParams(params)
|
||||
@@ -218,17 +216,15 @@ class IOUDevice(VM):
|
||||
|
||||
params = {}
|
||||
if "startup_config" in new_settings:
|
||||
if new_settings["startup_config"] and os.path.isfile(new_settings["startup_config"]):
|
||||
base_config_content = self._readBaseConfig(new_settings["startup_config"])
|
||||
if base_config_content is not None:
|
||||
params["startup_config_content"] = base_config_content
|
||||
base_config_content = self._readBaseConfig(new_settings["startup_config"])
|
||||
if base_config_content is not None:
|
||||
params["startup_config_content"] = base_config_content
|
||||
del new_settings["startup_config"]
|
||||
|
||||
if "private_config" in new_settings:
|
||||
if new_settings["private_config"] and os.path.isfile(new_settings["private_config"]):
|
||||
base_config_content = self._readBaseConfig(new_settings["private_config"])
|
||||
if base_config_content is not None:
|
||||
params["private_config_content"] = base_config_content
|
||||
base_config_content = self._readBaseConfig(new_settings["private_config"])
|
||||
if base_config_content is not None:
|
||||
params["private_config_content"] = base_config_content
|
||||
del new_settings["private_config"]
|
||||
|
||||
for name, value in new_settings.items():
|
||||
|
||||
@@ -249,7 +249,7 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
|
||||
if not startup_config:
|
||||
settings["startup_config"] = ""
|
||||
elif startup_config != settings["startup_config"]:
|
||||
if os.access(startup_config, os.R_OK):
|
||||
if self._configFileValid(startup_config):
|
||||
settings["startup_config"] = startup_config
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(self, "Startup-config", "Cannot read the startup-config file")
|
||||
@@ -259,7 +259,7 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
|
||||
if not private_config:
|
||||
settings["private_config"] = ""
|
||||
elif private_config != settings["private_config"]:
|
||||
if os.access(private_config, os.R_OK):
|
||||
if self._configFileValid(private_config):
|
||||
settings["private_config"] = private_config
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(self, "Private-config", "Cannot read the private-config file")
|
||||
@@ -296,3 +296,11 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
|
||||
|
||||
settings["ethernet_adapters"] = ethernet_adapters
|
||||
settings["serial_adapters"] = serial_adapters
|
||||
|
||||
def _configFileValid(self, path):
|
||||
"""
|
||||
Return true if it's a valid configuration file
|
||||
"""
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.join(Servers.instance().localServerSettings()["configs_path"], path)
|
||||
return os.access(path, os.R_OK)
|
||||
|
||||
@@ -249,15 +249,19 @@ class Qemu(Module):
|
||||
log.info("QEMU module reset")
|
||||
self._nodes.clear()
|
||||
|
||||
def getQemuBinariesFromServer(self, server, callback):
|
||||
def getQemuBinariesFromServer(self, server, callback, archs=None):
|
||||
"""
|
||||
Gets the QEMU binaries list from a server.
|
||||
|
||||
:param server: server to send the request to
|
||||
:param callback: callback for the reply from the server
|
||||
:param archs: A list of architectures. Only binaries matching the specified architectures are returned.
|
||||
"""
|
||||
|
||||
server.get("/qemu/binaries", callback)
|
||||
request_body = None
|
||||
if archs is not None:
|
||||
request_body = {"archs": archs}
|
||||
server.get("/qemu/binaries", callback, body=request_body)
|
||||
|
||||
def getQemuImgBinariesFromServer(self, server, callback):
|
||||
"""
|
||||
@@ -269,6 +273,16 @@ class Qemu(Module):
|
||||
|
||||
server.get(r"/qemu/img-binaries", callback)
|
||||
|
||||
def getQemuCapabilitiesFromServer(self, server, callback):
|
||||
"""
|
||||
Gets the capabilities of Qemu at a server.
|
||||
|
||||
:param server: server to send the request to
|
||||
:param callback: callback for the reply from the server
|
||||
"""
|
||||
|
||||
server.get(r"/qemu/capabilities", callback)
|
||||
|
||||
def createDiskImage(self, server, callback, options):
|
||||
"""
|
||||
Create a disk image on the remote server
|
||||
|
||||
@@ -205,6 +205,7 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
|
||||
elif self.uiTypeComboBox.currentText() == "ASA 8.4(2)":
|
||||
settings["adapters"] = 4
|
||||
settings["initrd"] = self.uiInitrdImageLineEdit.text()
|
||||
settings["hda_disk_image"] = self.uiHdaDiskImageLineEdit.text()
|
||||
settings["kernel_image"] = self.uiKernelImageLineEdit.text()
|
||||
settings["kernel_command_line"] = "ide_generic.probe_mask=0x01 ide_core.chs=0.0:980,16,32 auto nousb console=ttyS0,9600 bigphysarea=65536 ide1=noprobe no-hlt -net nic"
|
||||
settings["options"] = "-no-kvm -icount auto -hdachs 980,16,32"
|
||||
|
||||
@@ -501,14 +501,14 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
|
||||
settings["category"] = self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex())
|
||||
port_name_format = self.uiPortNameFormatLineEdit.text()
|
||||
if '{0}' not in port_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}")
|
||||
if '{0}' not in port_name_format and '{port0}' not in port_name_format and '{port1}' not in port_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}, {port0} or {port1}")
|
||||
else:
|
||||
settings["port_name_format"] = self.uiPortNameFormatLineEdit.text()
|
||||
|
||||
port_segment_size = self.uiPortSegmentSizeSpinBox.value()
|
||||
if port_segment_size and '{1}' not in port_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain {1} if the segment size is not 0")
|
||||
if port_segment_size and '{1}' not in port_name_format and '{segment0}' not in port_name_format and '{segment1}' not in port_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Port name format", "If the segment size is not 0, the format must contain {1}, {segment0} or {segment1}")
|
||||
else:
|
||||
settings["port_segment_size"] = port_segment_size
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ class QemuVM(VM):
|
||||
self._linked_clone = True
|
||||
|
||||
self._settings = {"name": "",
|
||||
"usage": "",
|
||||
"qemu_path": "",
|
||||
"hda_disk_image": "",
|
||||
"hdb_disk_image": "",
|
||||
@@ -98,7 +99,14 @@ class QemuVM(VM):
|
||||
if self._first_port_name and adapter_number == 0:
|
||||
port_name = self._first_port_name
|
||||
else:
|
||||
port_name = self._port_name_format.format(interface_number, segment_number)
|
||||
port_name = self._port_name_format.format(
|
||||
interface_number,
|
||||
segment_number,
|
||||
port0 = interface_number,
|
||||
port1 = 1 + interface_number,
|
||||
segment0 = segment_number,
|
||||
segment1 = 1 + segment_number
|
||||
)
|
||||
interface_number += 1
|
||||
if self._port_segment_size and interface_number % self._port_segment_size == 0:
|
||||
segment_number += 1
|
||||
@@ -351,6 +359,9 @@ class QemuVM(VM):
|
||||
port_info += " {port_name} {port_description}\n".format(port_name=port.name(),
|
||||
port_description=port.description())
|
||||
|
||||
if "usage" in self._settings and len(self._settings["usage"]) > 0:
|
||||
info += " Usage: {}\n".format(self._settings["usage"])
|
||||
|
||||
return info + port_info
|
||||
|
||||
def load(self, node_info):
|
||||
|
||||
@@ -28,6 +28,7 @@ QEMU_SETTINGS = {
|
||||
|
||||
QEMU_VM_SETTINGS = {
|
||||
"name": "",
|
||||
"usage": "",
|
||||
"symbol": ":/symbols/qemu_guest.svg",
|
||||
"category": Node.end_devices,
|
||||
"port_name_format": "Ethernet{0}",
|
||||
|
||||
@@ -523,6 +523,9 @@
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="uiPortNameFormatLabel">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>{0} - the port number, from 0 to the number of adapters-1.</p><p>{1} - the segment number, from 0 to the number of segments-1.</p><p>{port0} - named alias for {0}.</p><p>{port1} - the port number, from 1 to the number of adapters.</p><p>{segment0} - named alias for {1}.</p><p>{segment1} - the segment number, from 1 to the number of segments.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Name format:</string>
|
||||
</property>
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/qemu/ui/qemu_vm_configuration_page.ui'
|
||||
# Form implementation generated from reading ui file 'D:\Vasko\PyCharmProjects\gns3-gui\gns3\modules\qemu\ui\qemu_vm_configuration_page.ui'
|
||||
#
|
||||
# Created: Sun Oct 18 19:06:42 2015
|
||||
# by: PyQt5 UI code generator 5.2.1
|
||||
# Created by: PyQt5 UI code generator 5.5.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_QemuVMConfigPageWidget(object):
|
||||
|
||||
def setupUi(self, QemuVMConfigPageWidget):
|
||||
QemuVMConfigPageWidget.setObjectName("QemuVMConfigPageWidget")
|
||||
QemuVMConfigPageWidget.resize(611, 524)
|
||||
@@ -388,6 +385,10 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
self.uiACPIShutdownCheckBox = QtWidgets.QCheckBox(self.groupBox)
|
||||
self.uiACPIShutdownCheckBox.setObjectName("uiACPIShutdownCheckBox")
|
||||
self.gridLayout_3.addWidget(self.uiACPIShutdownCheckBox, 2, 0, 1, 2)
|
||||
self.uiQemuOptionsLineEdit.raise_()
|
||||
self.uiQemuOptionsLabel.raise_()
|
||||
self.uiACPIShutdownCheckBox.raise_()
|
||||
self.uiBaseVMCheckBox.raise_()
|
||||
self.verticalLayout_2.addWidget(self.groupBox)
|
||||
spacerItem4 = QtWidgets.QSpacerItem(20, 90, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.verticalLayout_2.addItem(spacerItem4)
|
||||
@@ -446,6 +447,7 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
self.uiAdapterTypesLabel.setText(_translate("QemuVMConfigPageWidget", "Type:"))
|
||||
self.uiAdaptersLabel.setText(_translate("QemuVMConfigPageWidget", "Adapters:"))
|
||||
self.uiMacAddrLabel.setText(_translate("QemuVMConfigPageWidget", "Base MAC:"))
|
||||
self.uiPortNameFormatLabel.setToolTip(_translate("QemuVMConfigPageWidget", "<html><head/><body><p>{0} - the port number, from 0 to the number of adapters-1.</p><p>{1} - the segment number, from 0 to the number of segments-1.</p><p>{port0} - named alias for {0}.</p><p>{port1} - the port number, from 1 to the number of adapters.</p><p>{segment0} - named alias for {1}.</p><p>{segment1} - the segment number, from 1 to the number of segments.</p></body></html>"))
|
||||
self.uiPortNameFormatLabel.setText(_translate("QemuVMConfigPageWidget", "Name format:"))
|
||||
self.uiFirstPortNameLabel.setText(_translate("QemuVMConfigPageWidget", "First port name:"))
|
||||
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiNetworkTab), _translate("QemuVMConfigPageWidget", "Network"))
|
||||
@@ -471,3 +473,4 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
self.uiBaseVMCheckBox.setText(_translate("QemuVMConfigPageWidget", "Use as a linked base VM"))
|
||||
self.uiACPIShutdownCheckBox.setText(_translate("QemuVMConfigPageWidget", "Enable ACPI shutdown (experimental)"))
|
||||
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiAdvancedSettingsTab), _translate("QemuVMConfigPageWidget", "Advanced settings"))
|
||||
|
||||
|
||||
@@ -185,14 +185,14 @@ class VirtualBoxVMConfigurationPage(QtWidgets.QWidget, Ui_virtualBoxVMConfigPage
|
||||
|
||||
settings["category"] = self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex())
|
||||
port_name_format = self.uiPortNameFormatLineEdit.text()
|
||||
if '{0}' not in port_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}")
|
||||
if '{0}' not in port_name_format and '{port0}' not in port_name_format and '{port1}' not in port_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}, {port0} or {port1}")
|
||||
else:
|
||||
settings["port_name_format"] = self.uiPortNameFormatLineEdit.text()
|
||||
|
||||
port_segment_size = self.uiPortSegmentSizeSpinBox.value()
|
||||
if port_segment_size and '{1}' not in port_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain {1} if the segment size is not 0")
|
||||
if port_segment_size and '{1}' not in port_name_format and '{segment0}' not in port_name_format and '{segment1}' not in port_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Port name format", "If the segment size is not 0, the format must contain {1}, {segment0} or {segment1}")
|
||||
else:
|
||||
settings["port_segment_size"] = port_segment_size
|
||||
|
||||
|
||||
@@ -256,6 +256,9 @@
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="uiPortNameFormatLabel">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>{0} - the port number, from 0 to the number of adapters-1.</p><p>{1} - the segment number, from 0 to the number of segments-1.</p><p>{port0} - named alias for {0}.</p><p>{port1} - the port number, from 1 to the number of adapters.</p><p>{segment0} - named alias for {1}.</p><p>{segment1} - the segment number, from 1 to the number of segments.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Name format:</string>
|
||||
</property>
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '/home/cetko/projects/gns3/gns3-gui/gns3/modules/virtualbox/ui/virtualbox_vm_configuration_page.ui'
|
||||
# Form implementation generated from reading ui file 'D:\Vasko\PyCharmProjects\gns3-gui\gns3\modules\virtualbox\ui\virtualbox_vm_configuration_page.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.4.2
|
||||
# Created by: PyQt5 UI code generator 5.5.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_virtualBoxVMConfigPageWidget(object):
|
||||
|
||||
def setupUi(self, virtualBoxVMConfigPageWidget):
|
||||
virtualBoxVMConfigPageWidget.setObjectName("virtualBoxVMConfigPageWidget")
|
||||
virtualBoxVMConfigPageWidget.resize(510, 463)
|
||||
@@ -170,6 +168,8 @@ class Ui_virtualBoxVMConfigPageWidget(object):
|
||||
self.label.setText(_translate("virtualBoxVMConfigPageWidget", "Type:"))
|
||||
self.uiUseAnyAdapterCheckBox.setText(_translate("virtualBoxVMConfigPageWidget", "Allow GNS3 to use any configured VirtualBox adapter"))
|
||||
self.uiPortSegmentSizeLabel.setText(_translate("virtualBoxVMConfigPageWidget", "Segment size:"))
|
||||
self.uiPortNameFormatLabel.setToolTip(_translate("virtualBoxVMConfigPageWidget", "<html><head/><body><p>{0} - the port number, from 0 to the number of adapters-1.</p><p>{1} - the segment number, from 0 to the number of segments-1.</p><p>{port0} - named alias for {0}.</p><p>{port1} - the port number, from 1 to the number of adapters.</p><p>{segment0} - named alias for {1}.</p><p>{segment1} - the segment number, from 1 to the number of segments.</p></body></html>"))
|
||||
self.uiPortNameFormatLabel.setText(_translate("virtualBoxVMConfigPageWidget", "Name format:"))
|
||||
self.uiFirstPortNameLabel.setText(_translate("virtualBoxVMConfigPageWidget", "First port name:"))
|
||||
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.tab_2), _translate("virtualBoxVMConfigPageWidget", "Network"))
|
||||
|
||||
|
||||
@@ -78,7 +78,14 @@ class VirtualBoxVM(VM):
|
||||
if self._first_port_name and adapter_number == 0:
|
||||
port_name = self._first_port_name
|
||||
else:
|
||||
port_name = self._port_name_format.format(interface_number, segment_number)
|
||||
port_name = self._port_name_format.format(
|
||||
interface_number,
|
||||
segment_number,
|
||||
port0 = interface_number,
|
||||
port1 = 1 + interface_number,
|
||||
segment0 = segment_number,
|
||||
segment1 = 1 + segment_number
|
||||
)
|
||||
interface_number += 1
|
||||
if self._port_segment_size and interface_number % self._port_segment_size == 0:
|
||||
segment_number += 1
|
||||
|
||||
@@ -183,14 +183,14 @@ class VMwareVMConfigurationPage(QtWidgets.QWidget, Ui_VMwareVMConfigPageWidget):
|
||||
|
||||
settings["category"] = self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex())
|
||||
port_name_format = self.uiPortNameFormatLineEdit.text()
|
||||
if '{0}' not in port_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}")
|
||||
if '{0}' not in port_name_format and '{port0}' not in port_name_format and '{port1}' not in port_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}, {port0} or {port1}")
|
||||
else:
|
||||
settings["port_name_format"] = self.uiPortNameFormatLineEdit.text()
|
||||
|
||||
port_segment_size = self.uiPortSegmentSizeSpinBox.value()
|
||||
if port_segment_size and '{1}' not in port_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain {1} if the segment size is not 0")
|
||||
if port_segment_size and '{1}' not in port_name_format and '{segment0}' not in port_name_format and '{segment1}' not in port_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Port name format", "If the segment size is not 0, the format must contain {1}, {segment0} or {segment1}")
|
||||
else:
|
||||
settings["port_segment_size"] = port_segment_size
|
||||
|
||||
|
||||
@@ -145,6 +145,9 @@
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="uiPortNameFormatLabel">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>{0} - the port number, from 0 to the number of adapters-1.</p><p>{1} - the segment number, from 0 to the number of segments-1.</p><p>{port0} - named alias for {0}.</p><p>{port1} - the port number, from 1 to the number of adapters.</p><p>{segment0} - named alias for {1}.</p><p>{segment1} - the segment number, from 1 to the number of segments.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Name format:</string>
|
||||
</property>
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '/home/cetko/projects/gns3/gns3-gui/gns3/modules/vmware/ui/vmware_vm_configuration_page.ui'
|
||||
# Form implementation generated from reading ui file 'D:\Vasko\PyCharmProjects\gns3-gui\gns3\modules\vmware\ui\vmware_vm_configuration_page.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.5
|
||||
# Created by: PyQt5 UI code generator 5.5.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_VMwareVMConfigPageWidget(object):
|
||||
|
||||
def setupUi(self, VMwareVMConfigPageWidget):
|
||||
VMwareVMConfigPageWidget.setObjectName("VMwareVMConfigPageWidget")
|
||||
VMwareVMConfigPageWidget.resize(494, 381)
|
||||
@@ -143,6 +141,7 @@ class Ui_VMwareVMConfigPageWidget(object):
|
||||
self.uiSymbolToolButton.setText(_translate("VMwareVMConfigPageWidget", "&Browse..."))
|
||||
self.uiCategoryLabel.setText(_translate("VMwareVMConfigPageWidget", "Category:"))
|
||||
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.tab), _translate("VMwareVMConfigPageWidget", "General settings"))
|
||||
self.uiPortNameFormatLabel.setToolTip(_translate("VMwareVMConfigPageWidget", "<html><head/><body><p>{0} - the port number, from 0 to the number of adapters-1.</p><p>{1} - the segment number, from 0 to the number of segments-1.</p><p>{port0} - named alias for {0}.</p><p>{port1} - the port number, from 1 to the number of adapters.</p><p>{segment0} - named alias for {1}.</p><p>{segment1} - the segment number, from 1 to the number of segments.</p></body></html>"))
|
||||
self.uiPortNameFormatLabel.setText(_translate("VMwareVMConfigPageWidget", "Name format:"))
|
||||
self.uiPortSegmentSizeLabel.setText(_translate("VMwareVMConfigPageWidget", "Segment size:"))
|
||||
self.uiAdaptersLabel.setText(_translate("VMwareVMConfigPageWidget", "Adapters:"))
|
||||
@@ -151,3 +150,4 @@ class Ui_VMwareVMConfigPageWidget(object):
|
||||
self.uiFirstPortNameLabel.setText(_translate("VMwareVMConfigPageWidget", "First port name:"))
|
||||
self.uiUseUbridgeCheckBox.setText(_translate("VMwareVMConfigPageWidget", "Use uBridge for network connections"))
|
||||
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.tab_2), _translate("VMwareVMConfigPageWidget", "Network"))
|
||||
|
||||
|
||||
@@ -81,7 +81,14 @@ class VMwareVM(VM):
|
||||
if self._first_port_name and adapter_number == 0:
|
||||
port_name = self._first_port_name
|
||||
else:
|
||||
port_name = self._port_name_format.format(interface_number, segment_number)
|
||||
port_name = self._port_name_format.format(
|
||||
interface_number,
|
||||
segment_number,
|
||||
port0 = interface_number,
|
||||
port1 = 1 + interface_number,
|
||||
segment0 = segment_number,
|
||||
segment1 = 1 + segment_number
|
||||
)
|
||||
interface_number += 1
|
||||
if self._port_segment_size and interface_number % self._port_segment_size == 0:
|
||||
segment_number += 1
|
||||
|
||||
@@ -77,7 +77,7 @@ class NodesView(QtWidgets.QTreeWidget):
|
||||
|
||||
self.sortByColumn(0, QtCore.Qt.AscendingOrder)
|
||||
if not self.topLevelItemCount() and category == Node.routers:
|
||||
QtWidgets.QMessageBox.warning(self, 'Routers', 'No routers have been configured.<br>You must provide your own router images in order to use GNS3.<br><br><a href="https://community.gns3.com/community/software/documentation">Show documentation</a>')
|
||||
QtWidgets.QMessageBox.warning(self, 'Routers', 'No routers have been configured.<br>You must provide your own router images in order to use GNS3.<br><br><a href="https://gns3.com/support/docs/adding-ios-or-iou-qemu-virtual-2">Show documentation</a>')
|
||||
# TODO: would be nicer to use QErrorMessage but the link cannot be clicked by default
|
||||
#QtWidgets.QErrorMessage.qtHandler().showMessage('No routers have been configured.<br>You must provide your own router images in order to use GNS3.<br><br><a href="https://community.gns3.com/community/software/documentation">Show documentation</a>')
|
||||
|
||||
|
||||
@@ -142,6 +142,10 @@ class ServerPreferencesPage(QtWidgets.QWidget, Ui_ServerPreferencesPageWidget):
|
||||
"""
|
||||
|
||||
if state:
|
||||
if not self.uiLocalServerAutoStartCheckBox.isChecked():
|
||||
QtWidgets.QMessageBox.critical(self, "GNS3 VM", "The local server need to be enable in order to use the GNS3 VM")
|
||||
self.uiEnableVMCheckBox.setChecked(False)
|
||||
return
|
||||
self.uiGNS3VMSettingsGroupBox.setEnabled(True)
|
||||
self._refreshVMListSlot()
|
||||
else:
|
||||
@@ -188,6 +192,10 @@ class ServerPreferencesPage(QtWidgets.QWidget, Ui_ServerPreferencesPageWidget):
|
||||
self.uiConsolePortRangeGroupBox.setEnabled(True)
|
||||
self.uiUDPPortRangeGroupBox.setEnabled(True)
|
||||
else:
|
||||
if self.uiEnableVMCheckBox.isChecked():
|
||||
QtWidgets.QMessageBox.critical(self, "GNS3 VM", "The local server need to be enable in order to use the GNS3 VM. Please turn off the GNS3 VM before turning off the local server.")
|
||||
self.uiLocalServerAutoStartCheckBox.setChecked(True)
|
||||
return
|
||||
self.uiGeneralSettingsGroupBox.setEnabled(False)
|
||||
self.uiConsolePortRangeGroupBox.setEnabled(False)
|
||||
self.uiUDPPortRangeGroupBox.setEnabled(False)
|
||||
@@ -447,11 +455,6 @@ class ServerPreferencesPage(QtWidgets.QWidget, Ui_ServerPreferencesPageWidget):
|
||||
new_local_server_settings["udp_end_port_range"]))
|
||||
return
|
||||
|
||||
if 5900 <= new_local_server_settings["console_start_port_range"] <= 6000 or 5900 <= new_local_server_settings["console_end_port_range"] <= 6000 \
|
||||
or new_local_server_settings["console_start_port_range"] < 5900 and new_local_server_settings["console_end_port_range"] > 6000:
|
||||
QtWidgets.QMessageBox.critical(self, "Port range", "Console port range between 5900 and 6000 is reserved for VNC")
|
||||
return
|
||||
|
||||
if new_local_server_settings["auto_start"]:
|
||||
if not os.path.isfile(new_local_server_settings["path"]):
|
||||
QtWidgets.QMessageBox.critical(self, "Local server", "Could not find local server {}".format(new_local_server_settings["path"]))
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
|
||||
from .qt import QtCore, QtWidgets, Qt, QtNetwork
|
||||
@@ -41,7 +42,14 @@ class Progress(QtCore.QObject):
|
||||
from .main_window import MainWindow
|
||||
self._parent = MainWindow.instance()
|
||||
|
||||
self._stimer = QtCore.QTimer()
|
||||
# Timer called for refreshing the progress dialog status
|
||||
self._rtimer = QtCore.QTimer()
|
||||
self._rtimer.timeout.connect(self.update)
|
||||
self._rtimer.start(500)
|
||||
|
||||
# When in millisecond we start to show the progress dialog
|
||||
self._display_start_time = 0
|
||||
|
||||
self._finished_query_during_display = 0
|
||||
self._queries = {}
|
||||
# QtCore.Qt.QueuedConnection warranty that we execute the slot
|
||||
@@ -56,10 +64,11 @@ class Progress(QtCore.QObject):
|
||||
self._allow_cancel_query = False
|
||||
self._enable = True
|
||||
|
||||
self._mutex = QtCore.QMutex()
|
||||
|
||||
def _addQuerySlot(self, query_id, explanation, response):
|
||||
|
||||
self._queries[query_id] = {"explanation": explanation, "current": 0, "maximum": 0, "response": response}
|
||||
self.show()
|
||||
|
||||
def _removeQuerySlot(self, query_id):
|
||||
|
||||
@@ -67,11 +76,6 @@ class Progress(QtCore.QObject):
|
||||
if query_id in self._queries:
|
||||
del self._queries[query_id]
|
||||
|
||||
if len(self._queries) == 0:
|
||||
self.hide()
|
||||
else:
|
||||
self.show()
|
||||
|
||||
def progress_dialog(self):
|
||||
|
||||
return self._progress_dialog
|
||||
@@ -80,7 +84,6 @@ class Progress(QtCore.QObject):
|
||||
if query_id in self._queries:
|
||||
self._queries[query_id]["current"] = current
|
||||
self._queries[query_id]["maximum"] = maximum
|
||||
self.show()
|
||||
|
||||
def setAllowCancelQuery(self, allow_cancel_query):
|
||||
self._allow_cancel_query = allow_cancel_query
|
||||
@@ -95,8 +98,13 @@ class Progress(QtCore.QObject):
|
||||
for query in self._queries.copy().values():
|
||||
query["response"].abort()
|
||||
|
||||
def show(self):
|
||||
def update(self):
|
||||
if len(self._queries) == 0 and (time.time() * 1000) >= self._display_start_time + self._minimum_duration:
|
||||
self.hide()
|
||||
return
|
||||
self.show()
|
||||
|
||||
def show(self):
|
||||
if self._progress_dialog is None or self._progress_dialog.wasCanceled():
|
||||
progress_dialog = QtWidgets.QProgressDialog("Waiting for server response", None, 0, 0, self._parent)
|
||||
progress_dialog.canceled.connect(self._cancelSlot)
|
||||
@@ -111,7 +119,8 @@ class Progress(QtCore.QObject):
|
||||
|
||||
self._progress_dialog = progress_dialog
|
||||
self._finished_query_during_display = 0
|
||||
start_timer = True
|
||||
self._display_start_time = time.time() * 1000
|
||||
self._progress_dialog.show()
|
||||
else:
|
||||
start_timer = False
|
||||
progress_dialog = self._progress_dialog
|
||||
@@ -130,14 +139,6 @@ class Progress(QtCore.QObject):
|
||||
text = list(self._queries.values())[0]["explanation"]
|
||||
progress_dialog.setLabelText(text)
|
||||
|
||||
if start_timer:
|
||||
self._stimer.singleShot(self._minimum_duration, self._show_dialog)
|
||||
|
||||
def _show_dialog(self):
|
||||
if self._progress_dialog is not None and self._enable:
|
||||
self._progress_dialog.show()
|
||||
self._progress_dialog.exec_()
|
||||
|
||||
def hide(self):
|
||||
"""
|
||||
Hide and cancel the progress dialog
|
||||
|
||||
@@ -139,7 +139,8 @@ class Project(QtCore.QObject):
|
||||
assert self._files_dir is not None
|
||||
assert self._name is not None
|
||||
except AssertionError:
|
||||
lines = traceback.format_exception(sys.exc_info())
|
||||
exc_type, exc_value, exc_tb = sys.exc_info()
|
||||
lines = traceback.format_exception(exc_type, exc_value, exc_tb)
|
||||
tb = "".join(lines)
|
||||
log.debug("Assertion detected: {}".format(tb))
|
||||
raise
|
||||
|
||||
@@ -24,14 +24,17 @@ import sys
|
||||
from .qt import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class MultipleRedirection:
|
||||
class MultipleRedirection(QtCore.QObject):
|
||||
writed = QtCore.pyqtSignal(str)
|
||||
|
||||
def __init__(self, console, stdout):
|
||||
super().__init__()
|
||||
|
||||
self.console = console
|
||||
self.stdout = stdout
|
||||
|
||||
def write(self, str):
|
||||
self.console.write(str)
|
||||
self.writed.emit(str)
|
||||
if self.stdout.encoding is None:
|
||||
str = str.encode("ascii", "ignore").decode("ascii", "ignore")
|
||||
elif self.stdout.encoding != "UTF-8":
|
||||
@@ -79,6 +82,7 @@ class PyCutExt(QtWidgets.QTextEdit):
|
||||
# capture all interactive input/output
|
||||
self._old_stdout = sys.stdout
|
||||
sys.stdout = MultipleRedirection(self, sys.stdout)
|
||||
sys.stdout.writed.connect(self.write)
|
||||
# sys.stderr = MultipleRedirection((sys.stderr, self))
|
||||
self._old_stdin = sys.stdin
|
||||
sys.stdin = self
|
||||
|
||||
27
gns3/qt.py
27
gns3/qt.py
@@ -74,7 +74,6 @@ from PyQt5.QtWidgets import QFileDialog as OldFileDialog
|
||||
|
||||
|
||||
class QFileDialog(OldFileDialog):
|
||||
|
||||
@staticmethod
|
||||
def getExistingDirectory(parent=None, caption='', dir='', options=OldFileDialog.ShowDirsOnly):
|
||||
path = OldFileDialog.getExistingDirectory(parent, caption, dir, options)
|
||||
@@ -123,6 +122,32 @@ class StatsQtWidgetsQDialog(QtWidgets.QDialog):
|
||||
QtWidgets.QDialog = StatsQtWidgetsQDialog
|
||||
|
||||
|
||||
class LogQMessageBox(QtWidgets.QMessageBox):
|
||||
"""
|
||||
Replace the standard message box for logging errors to console. And
|
||||
show a stack trace when a critical message box is shown in debug mode
|
||||
"""
|
||||
@staticmethod
|
||||
def critical(parent, title, message, *args):
|
||||
log.critical(message, stack_info=LogQMessageBox.stack_info())
|
||||
return super(QtWidgets.QMessageBox, QtWidgets.QMessageBox).critical(parent, title, message, *args)
|
||||
|
||||
@staticmethod
|
||||
def warning(parent, title, message, *args):
|
||||
log.warning(message)
|
||||
return super(QtWidgets.QMessageBox, QtWidgets.QMessageBox).warning(parent, title, message, *args)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def stack_info():
|
||||
"""
|
||||
Show stack trace
|
||||
"""
|
||||
return logging.getLogger().getEffectiveLevel() == logging.DEBUG
|
||||
|
||||
QtWidgets.QMessageBox = LogQMessageBox
|
||||
|
||||
|
||||
class StatsQtWidgetsQWizard(QtWidgets.QWizard):
|
||||
"""
|
||||
Send stats from all the QWizard
|
||||
|
||||
@@ -49,7 +49,7 @@ class Appliance(collections.Mapping):
|
||||
"""
|
||||
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"] != 1:
|
||||
if self._appliance["registry_version"] > 2:
|
||||
raise ApplianceError("Please update GNS3 in order to install this appliance")
|
||||
|
||||
def __getitem__(self, key):
|
||||
@@ -74,6 +74,9 @@ class Appliance(collections.Mapping):
|
||||
for file in self._appliance["images"]:
|
||||
file = copy.copy(file)
|
||||
|
||||
if "idlepc" in version:
|
||||
file["idlepc"] = version["idlepc"]
|
||||
|
||||
if "/" in filename:
|
||||
parent, name = filename.split("/")
|
||||
filename = os.path.join(parent, name)
|
||||
@@ -133,3 +136,14 @@ class Appliance(collections.Mapping):
|
||||
return True
|
||||
except ApplianceError:
|
||||
return False
|
||||
|
||||
def image_dir_name(self):
|
||||
"""
|
||||
:returns: The name of directory where image should be located
|
||||
"""
|
||||
if "qemu" in self._appliance:
|
||||
return "QEMU"
|
||||
if "iou" in self._appliance:
|
||||
return "IOU"
|
||||
return "IOS"
|
||||
|
||||
|
||||
@@ -95,6 +95,16 @@ class Config:
|
||||
home = os.path.expanduser("~")
|
||||
return os.path.join(home, ".config", appname, filename)
|
||||
|
||||
def is_name_available(self, name):
|
||||
"""
|
||||
:param name: Appliance name
|
||||
:returns: True if name is not already used
|
||||
"""
|
||||
for item in self._config["Qemu"].get("vms", []):
|
||||
if item["name"] == name:
|
||||
return False
|
||||
return True
|
||||
|
||||
def add_appliance(self, appliance_config, server):
|
||||
"""
|
||||
Add appliance to the user configuration
|
||||
@@ -107,6 +117,10 @@ class Config:
|
||||
"server": server,
|
||||
"name": appliance_config["name"]
|
||||
}
|
||||
|
||||
if "usage" in appliance_config:
|
||||
new_config["usage"] = appliance_config["usage"]
|
||||
|
||||
if appliance_config["category"] == "guest":
|
||||
new_config["category"] = 2
|
||||
elif appliance_config["category"] == "router":
|
||||
@@ -118,42 +132,6 @@ class Config:
|
||||
elif appliance_config["category"] == "multilayer_switch":
|
||||
new_config["category"] = 1
|
||||
|
||||
# Raise error if VM already exists
|
||||
for item in self._config["Qemu"]["vms"]:
|
||||
if item["name"] == new_config["name"]:
|
||||
raise ConfigException("{} already exists".format(item["name"]))
|
||||
|
||||
if "qemu" in appliance_config:
|
||||
self._add_qemu_config(new_config, appliance_config)
|
||||
return
|
||||
raise ConfigException("{} no configuration found for Qemu".format(item["name"]))
|
||||
|
||||
def _add_qemu_config(self, new_config, appliance_config):
|
||||
|
||||
new_config["adapter_type"] = appliance_config["qemu"]["adapter_type"]
|
||||
new_config["adapters"] = appliance_config["qemu"]["adapters"]
|
||||
new_config["cpu_throttling"] = appliance_config["qemu"].get("cpu_throttling", 0)
|
||||
new_config["ram"] = appliance_config["qemu"]["ram"]
|
||||
new_config["console_type"] = appliance_config["qemu"]["console_type"]
|
||||
new_config["legacy_networking"] = False
|
||||
new_config["process_priority"] = appliance_config["qemu"].get("process_priority", "normal")
|
||||
|
||||
options = appliance_config["qemu"].get("options", "")
|
||||
if "-nographic" not in options:
|
||||
options += " -nographic"
|
||||
new_config["options"] = options.strip()
|
||||
|
||||
new_config["hda_disk_image"] = appliance_config["qemu"].get("hda_disk_image", "")
|
||||
new_config["hdb_disk_image"] = appliance_config["qemu"].get("hdb_disk_image", "")
|
||||
new_config["hdc_disk_image"] = appliance_config["qemu"].get("hdc_disk_image", "")
|
||||
new_config["hdd_disk_image"] = appliance_config["qemu"].get("hdd_disk_image", "")
|
||||
new_config["cdrom_image"] = appliance_config["qemu"].get("cdrom_image", "")
|
||||
new_config["initrd"] = appliance_config["qemu"].get("initrd", "")
|
||||
new_config["kernel_command_line"] = appliance_config["qemu"].get("kernel_command_line", "")
|
||||
new_config["kernel_image"] = appliance_config["qemu"].get("kernel_image", "")
|
||||
|
||||
new_config["qemu_path"] = "qemu-system-{}".format(appliance_config["qemu"]["arch"])
|
||||
|
||||
if "symbol" in appliance_config:
|
||||
new_config["symbol"] = self._set_symbol(appliance_config["symbol"])
|
||||
|
||||
@@ -169,8 +147,101 @@ class Config:
|
||||
elif appliance_config["category"] == "firewall":
|
||||
new_config["symbol"] = ":/symbols/firewall.svg"
|
||||
|
||||
# Raise error if VM already exists
|
||||
if not self.is_name_available(new_config["name"]):
|
||||
raise ConfigException("{} already exists".format(new_config["name"]))
|
||||
|
||||
if "qemu" in appliance_config:
|
||||
self._add_qemu_config(new_config, appliance_config)
|
||||
return
|
||||
if "iou" in appliance_config:
|
||||
self._add_iou_config(new_config, appliance_config)
|
||||
return
|
||||
if "dynamips" in appliance_config:
|
||||
self._add_dynamips_config(new_config, appliance_config)
|
||||
return
|
||||
raise ConfigException("{} no configuration found for know emulators".format(new_config["name"]))
|
||||
|
||||
def _add_dynamips_config(self, new_config, appliance_config):
|
||||
new_config["auto_delete_disks"] = True
|
||||
new_config["disk0"] = 0
|
||||
new_config["disk1"] = 0
|
||||
new_config["exec_area"] = 64
|
||||
new_config["idlemax"] = 500
|
||||
new_config["idlesleep"] = 30
|
||||
new_config["system_id"] = "FTX0945W0MY"
|
||||
new_config["sparsemem"] = True
|
||||
new_config["private_config"] = ""
|
||||
new_config["mac_addr"] = ""
|
||||
new_config["iomem"] = 5
|
||||
new_config["mmap"] = True
|
||||
|
||||
for key, value in appliance_config["dynamips"].items():
|
||||
new_config[key] = value
|
||||
|
||||
for image in appliance_config["images"]:
|
||||
new_config[image["type"]] = self._relative_image_path(image["filename"], image["path"])
|
||||
new_config[image["type"]] = self._relative_image_path("IOS", image["filename"], image["path"])
|
||||
new_config["idlepc"] = image["idlepc"]
|
||||
|
||||
log.debug("Add appliance Dynamips: %s", str(new_config))
|
||||
self._config["Dynamips"]["routers"].append(new_config)
|
||||
|
||||
def _add_iou_config(self, new_config, appliance_config):
|
||||
new_config["ethernet_adapters"] = appliance_config["iou"]["ethernet_adapters"]
|
||||
new_config["serial_adapters"] = appliance_config["iou"]["serial_adapters"]
|
||||
new_config["startup_config"] = appliance_config["iou"]["startup_config"]
|
||||
new_config["private_config"] = ""
|
||||
new_config["l1_keepalives"] = False
|
||||
new_config["use_default_iou_values"] = True
|
||||
new_config["nvram"] = appliance_config["iou"]["nvram"]
|
||||
new_config["ram"] = appliance_config["iou"]["ram"]
|
||||
|
||||
for image in appliance_config["images"]:
|
||||
new_config[image["type"]] = self._relative_image_path("IOU", image["filename"], image["path"])
|
||||
new_config["path"] = new_config["image"]
|
||||
|
||||
log.debug("Add appliance IOU: %s", str(new_config))
|
||||
self._config["IOU"]["devices"].append(new_config)
|
||||
|
||||
def _add_qemu_config(self, new_config, appliance_config):
|
||||
|
||||
new_config["adapter_type"] = appliance_config["qemu"]["adapter_type"]
|
||||
new_config["adapters"] = appliance_config["qemu"]["adapters"]
|
||||
new_config["cpu_throttling"] = appliance_config["qemu"].get("cpu_throttling", 0)
|
||||
new_config["ram"] = appliance_config["qemu"]["ram"]
|
||||
new_config["console_type"] = appliance_config["qemu"]["console_type"]
|
||||
new_config["legacy_networking"] = False
|
||||
new_config["process_priority"] = appliance_config["qemu"].get("process_priority", "normal")
|
||||
|
||||
options = appliance_config["qemu"].get("options", "")
|
||||
if "-nographic" not in options:
|
||||
options += " -nographic"
|
||||
if appliance_config["qemu"].get("kvm", "allow") == "disable" and "-no-kvm" not in options:
|
||||
options += " -no-kvm"
|
||||
new_config["options"] = options.strip()
|
||||
|
||||
for image in appliance_config["images"]:
|
||||
new_config[image["type"]] = self._relative_image_path("QEMU", image["filename"], image["path"])
|
||||
|
||||
new_config.setdefault("hda_disk_image", "")
|
||||
new_config.setdefault("hdb_disk_image", "")
|
||||
new_config.setdefault("hdc_disk_image", "")
|
||||
new_config.setdefault("hdd_disk_image", "")
|
||||
new_config.setdefault("cdrom_image", "")
|
||||
new_config.setdefault("initrd", "")
|
||||
new_config.setdefault("kernel_image", "")
|
||||
|
||||
new_config["hda_disk_interface"] = appliance_config["qemu"].get("hda_disk_interface", "ide")
|
||||
new_config["hdb_disk_interface"] = appliance_config["qemu"].get("hdb_disk_interface", "ide")
|
||||
new_config["hdc_disk_interface"] = appliance_config["qemu"].get("hdc_disk_interface", "ide")
|
||||
new_config["hdd_disk_interface"] = appliance_config["qemu"].get("hdd_disk_interface", "ide")
|
||||
|
||||
new_config["kernel_command_line"] = appliance_config["qemu"].get("kernel_command_line", "")
|
||||
|
||||
if "path" in appliance_config["qemu"]:
|
||||
new_config["qemu_path"] = appliance_config["qemu"]["path"]
|
||||
else:
|
||||
new_config["qemu_path"] = "qemu-system-{}".format(appliance_config["qemu"]["arch"])
|
||||
|
||||
if "boot_priority" in appliance_config:
|
||||
new_config["boot_priority"] = appliance_config["boot_priority"]
|
||||
@@ -210,13 +281,16 @@ class Config:
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
def _relative_image_path(self, filename, path):
|
||||
def _relative_image_path(self, image_dir_type, filename, path):
|
||||
"""
|
||||
:param image_dir_type: Type of image directory
|
||||
:param filename: Filename at the end of the processus
|
||||
:param path: Full path to the file
|
||||
:returns: Path relative to image directory.
|
||||
Copy the image to the directory if not already in the directory
|
||||
"""
|
||||
|
||||
images_dir = os.path.join(self.images_dir, "QEMU")
|
||||
images_dir = os.path.join(self.images_dir, image_dir_type)
|
||||
path = os.path.abspath(path)
|
||||
if os.path.commonprefix([images_dir, path]) == images_dir:
|
||||
return path.replace(images_dir, '').strip('/\\')
|
||||
@@ -226,7 +300,7 @@ class Config:
|
||||
base_file = re.split(r'[/\\]', filename)[0]
|
||||
else:
|
||||
base_file = filename
|
||||
Image(path).copy(os.path.join(self.images_dir, "QEMU"), base_file)
|
||||
Image(path).copy(images_dir, base_file)
|
||||
return filename
|
||||
|
||||
def save(self):
|
||||
|
||||
@@ -33,6 +33,14 @@ class Registry:
|
||||
def __init__(self, images_dirs):
|
||||
self._images_dirs = images_dirs
|
||||
|
||||
def appendImageDirectory(self, image_directory):
|
||||
"""
|
||||
Add a folder to the list of we need to scan
|
||||
|
||||
:param image_directory: Folder we need to add
|
||||
"""
|
||||
self._images_dirs.append(image_directory)
|
||||
|
||||
def search_image_file(self, filename, md5sum, size):
|
||||
"""
|
||||
Search an image based on its MD5 checksum
|
||||
|
||||
@@ -397,9 +397,31 @@
|
||||
"console_type": { "$ref": "#/definitions/mandatory_string" },
|
||||
"monitor": { "$ref": "#/definitions/network_port" },
|
||||
"name": { "$ref": "#/definitions/mandatory_string" },
|
||||
"usage": { "type": "string" },
|
||||
"acpi_shutdown": { "type": "boolean" },
|
||||
"adapter_type": {
|
||||
"enum": ["e1000", "i82551", "i82557b", "i82559er", "ne2k_pci", "pcnet", "rtl8139", "virtio", "virtio-net-pci"]
|
||||
"enum": [
|
||||
"e1000",
|
||||
"i82550",
|
||||
"i82551",
|
||||
"i82557a",
|
||||
"i82557b",
|
||||
"i82557c",
|
||||
"i82558a",
|
||||
"i82558b",
|
||||
"i82559a",
|
||||
"i82559b",
|
||||
"i82559c",
|
||||
"i82559er",
|
||||
"i82562",
|
||||
"i82801",
|
||||
"ne2k_pci",
|
||||
"pcnet",
|
||||
"rtl8139",
|
||||
"virtio",
|
||||
"virtio-net-pci",
|
||||
"vmxnet3"
|
||||
]
|
||||
},
|
||||
"adapters": {"type": "integer", "minimum": 1},
|
||||
"cpu_throttling": {"type": "integer", "minimum": 0, "maximum": 100},
|
||||
@@ -573,7 +595,7 @@
|
||||
"idlepc": {"type": "string", "pattern": "^0x[0-9a-f]{8}"},
|
||||
"idlesleep": {"type": "integer", "minimum": 1},
|
||||
"image": { "$ref": "#/definitions/mandatory_string" },
|
||||
"image_md5sum": { "$ref": "#/definitions/md5" },
|
||||
"image_md5sum": { "$ref": "#/definitions/md5" },
|
||||
"mac_addr": { "$ref": "#/definitions/mandatory_string" },
|
||||
"midplane": { "enum": ["std", "vxr"] },
|
||||
"chassis": {
|
||||
|
||||
@@ -32,6 +32,7 @@ import subprocess
|
||||
import binascii
|
||||
import stat
|
||||
import struct
|
||||
import psutil
|
||||
|
||||
from .qt import QtNetwork, QtWidgets
|
||||
from .network_client import getNetworkClientInstance, getNetworkUrl
|
||||
@@ -67,6 +68,7 @@ class Servers():
|
||||
self._network_manager.sslErrors.connect(self._handleSslErrors)
|
||||
self._remote_server_iter_pos = 0
|
||||
self._loadSettings()
|
||||
self._pid_path = os.path.join(LocalConfig.configDirectory(), "gns3_server.pid")
|
||||
self.registerLocalServer()
|
||||
|
||||
def registerLocalServer(self):
|
||||
@@ -347,7 +349,7 @@ class Servers():
|
||||
vm_settings = self._settings["vm"]
|
||||
vm_settings.update(settings)
|
||||
|
||||
def localServerAutoStart(self):
|
||||
def shouldLocalServerAutoStart(self):
|
||||
"""
|
||||
Returns either the local server
|
||||
is automatically started on startup.
|
||||
@@ -366,6 +368,46 @@ class Servers():
|
||||
|
||||
return self._settings["local_server"]["path"]
|
||||
|
||||
def _killAlreadyRunningServer(self):
|
||||
"""
|
||||
Kill a running zombie server (started by a gui that no longer exists)
|
||||
This will not kill server started by hand.
|
||||
"""
|
||||
try:
|
||||
if os.path.exists(self._pid_path):
|
||||
with open(self._pid_path) as f:
|
||||
pid = int(f.read())
|
||||
process = psutil.Process(pid=pid)
|
||||
log.info("Kill already running server with PID %d", pid)
|
||||
process.kill()
|
||||
except (OSError, psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
# Permission issue, or process no longer exists
|
||||
return
|
||||
|
||||
def localServerAutoStart(self):
|
||||
"""
|
||||
Try to start the embed gns3 server.
|
||||
"""
|
||||
|
||||
# We check if two gui are not launched at the same time
|
||||
# to avoid killing the server of the other GUI
|
||||
if not LocalConfig.isMainGui():
|
||||
log.info("Not the main GUI, will not autostart the server")
|
||||
return True
|
||||
|
||||
if self.localServer().isLocalServerRunning():
|
||||
log.info("A local server already running on this host")
|
||||
# Try to kill the server. The server can be still running after
|
||||
# if the server was started by hand
|
||||
self._killAlreadyRunningServer()
|
||||
|
||||
if not self.localServer().isLocalServerRunning():
|
||||
if not self.initLocalServer():
|
||||
return False
|
||||
if not self.startLocalServer():
|
||||
return False
|
||||
return True
|
||||
|
||||
def initLocalServer(self):
|
||||
"""
|
||||
Initialize the local server.
|
||||
@@ -469,7 +511,7 @@ class Servers():
|
||||
pass
|
||||
except OSError as e:
|
||||
log.warn("could not delete server log file {}: {}".format(logpath, e))
|
||||
command += ' --log="{}"'.format(logpath)
|
||||
command += ' --log="{}" --pid="{}"'.format(logpath, self._pid_path)
|
||||
|
||||
log.info("Starting local server process with {}".format(command))
|
||||
try:
|
||||
|
||||
@@ -75,6 +75,20 @@ elif sys.platform.startswith("darwin"):
|
||||
" -e 'delay 1'"
|
||||
" -e 'end repeat'"
|
||||
" -e 'end tell'",
|
||||
'Terminal tabbed (experimental)': "osascript -e 'tell application \"Terminal\"'"
|
||||
" -e 'activate'"
|
||||
" -e 'tell application \"System Events\" to tell process \"Terminal\" to keystroke \"t\" using command down'"
|
||||
" -e 'if (the (count of the window) = 0) then'"
|
||||
" -e 'repeat while contents of selected tab of window 1 starts with linefeed'"
|
||||
" -e 'delay 0.01'"
|
||||
" -e 'end repeat'"
|
||||
" -e 'tell application \"System Events\" to keystroke \"n\" using command down'"
|
||||
" -e 'end if'"
|
||||
" -e 'repeat while the busy of window 1 = true'"
|
||||
" -e 'delay 0.01'"
|
||||
" -e 'end repeat'"
|
||||
" -e 'do script \"echo -n -e \\\"\\\\033]0;%d\\\\007\\\" ; telnet %h %p ; exit\" in window 1'"
|
||||
" -e 'end tell'",
|
||||
'iTerm': "osascript -e 'tell application \"iTerm\"'"
|
||||
" -e 'activate'"
|
||||
" -e 'if (count of terminals) = 0 then'"
|
||||
@@ -93,8 +107,29 @@ elif sys.platform.startswith("darwin"):
|
||||
" -e ' end tell'"
|
||||
" -e 'end tell'"
|
||||
" -e 'end tell'",
|
||||
'iTerm 2.9': "osascript -e 'tell application \"iTerm\"'"
|
||||
" -e 'activate'"
|
||||
" -e 'if (count of windows) = 0 then'"
|
||||
" -e ' set t to (create window with default profile)'"
|
||||
" -e 'else'"
|
||||
" -e ' set t to current window'"
|
||||
" -e 'end if'"
|
||||
" -e 'tell t'"
|
||||
" -e ' create tab with default profile'"
|
||||
" -e ' set s to current session'"
|
||||
" -e ' tell s'"
|
||||
" -e ' write text \"telnet %h %p\"'"
|
||||
" -e ' delay 1'"
|
||||
" -e ' repeat while s exists'"
|
||||
" -e ' delay 1'"
|
||||
" -e ' end repeat'"
|
||||
" -e ' end tell'"
|
||||
" -e 'end tell'"
|
||||
" -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 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"'
|
||||
}
|
||||
|
||||
# default Mac OS X Telnet console command
|
||||
@@ -167,7 +202,8 @@ elif sys.platform.startswith("darwin"):
|
||||
" -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 'end tell'",
|
||||
'Chicken of the VNC': "/Applications/Chicken\ of\ the\ VNC.app/Contents/MacOS/Chicken\ of\ the\ VNC %h:%p"
|
||||
'Chicken of the VNC': "/Applications/Chicken\ of\ the\ VNC.app/Contents/MacOS/Chicken\ of\ the\ VNC %h:%p",
|
||||
'Royal TSX': "open 'rtsx://vnc%3A%2F%2F%h:%p'",
|
||||
}
|
||||
|
||||
# default Mac OS X VNC console command
|
||||
@@ -193,7 +229,8 @@ if sys.platform.startswith("win"):
|
||||
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_LIVE_TRAFFIC_CAPTURE: 'tail -f -c +0 %c | /Applications/Wireshark.app/Contents/Resources/bin/wireshark -o "gui.window_title:%d" -k -i -'}
|
||||
"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 -'}
|
||||
|
||||
elif sys.platform.startswith("freebsd"):
|
||||
# FreeBSD
|
||||
@@ -240,7 +277,8 @@ GENERAL_SETTINGS = {
|
||||
"debug_level": 0,
|
||||
"recent_files": [],
|
||||
"geometry": "",
|
||||
"state": ""
|
||||
"state": "",
|
||||
"debug_level": 0,
|
||||
}
|
||||
|
||||
GRAPHICS_VIEW_SETTINGS = {
|
||||
@@ -268,7 +306,7 @@ SERVERS_SETTINGS = {
|
||||
"auth": True,
|
||||
"user": "",
|
||||
"password": "",
|
||||
"console_start_port_range": 2001,
|
||||
"console_start_port_range": 2000,
|
||||
"console_end_port_range": 7000,
|
||||
"udp_start_port_range": 10000,
|
||||
"udp_end_port_range": 20000,
|
||||
|
||||
@@ -34,7 +34,6 @@ class SSHConnectionThread(QtCore.QThread):
|
||||
super().__init__(parent)
|
||||
|
||||
def run(self):
|
||||
log.info("SSH connection to %s with key %s", self._ssh_client.url(), self._ssh_client.ssh_key())
|
||||
port = Endpoint.find_unused_port(1000, 10000)
|
||||
if port is None:
|
||||
self.error_signal.emit("No port available in order to create SSH tunnel")
|
||||
@@ -79,7 +78,7 @@ class SSHClient(HTTPClient):
|
||||
assert settings["ssh_key"] is not None
|
||||
super().__init__(settings, network_manager)
|
||||
|
||||
def connect(self, query, callback):
|
||||
def _connect(self, query):
|
||||
"""
|
||||
Initialize the connection
|
||||
|
||||
@@ -87,15 +86,16 @@ class SSHClient(HTTPClient):
|
||||
:param callback: User callback when connection is finish
|
||||
"""
|
||||
|
||||
log.info("SSH connection to %s with key %s", self.url(), self.ssh_key())
|
||||
thread = SSHConnectionThread(self, parent=self)
|
||||
thread.error_signal.connect(lambda msg: self._connectionError(callback, msg))
|
||||
thread.connected_signal.connect(lambda: super(SSHClient, self).connect(query, callback))
|
||||
thread.error_signal.connect(lambda msg: self._connectionError(None, msg))
|
||||
thread.connected_signal.connect(lambda: super(SSHClient, self)._connect(query ))
|
||||
thread.start()
|
||||
|
||||
def getTunnel(self, port):
|
||||
"""
|
||||
Get a tunnel to the remote port.
|
||||
For HTTP standard client it's the same port. For SSH it will be
|
||||
For HTTP standard client it's the same port. For SSH it will be different
|
||||
|
||||
:param port: Remote port
|
||||
:returns: Tuple host, port to connect
|
||||
|
||||
0
gns3/static/.keep
Normal file
0
gns3/static/.keep
Normal file
@@ -1,9 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>GNS3 Jungle</title>
|
||||
</head>
|
||||
<body leftmargin=0 marginwidth=0 topmargin=0 marginheight=0>
|
||||
<a href="https://community.gns3.com"><img width="200" height="200" src="images/gns3_jungle.png"></a>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.9 KiB |
@@ -30,10 +30,10 @@ from .main_window import MainWindow
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
console_mutex = QtCore.QMutex()
|
||||
|
||||
class ConsoleThread(QtCore.QThread):
|
||||
|
||||
consoleDone = QtCore.pyqtSignal(str, str, int)
|
||||
consoleError = QtCore.pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent, command, name, server, port):
|
||||
@@ -67,15 +67,23 @@ class ConsoleThread(QtCore.QThread):
|
||||
command = command.replace("%p", str(port))
|
||||
command = command.replace("%d", self._name)
|
||||
|
||||
# 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:
|
||||
console_mutex.lock()
|
||||
|
||||
try:
|
||||
self.exec_command(command)
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
pass
|
||||
# log.warning('could not start Telnet console "{}": {}'.format(self._command, e))
|
||||
finally:
|
||||
# emit signal upon completion
|
||||
self.consoleDone.emit(self._name, host, port)
|
||||
self._server.releaseTunnel(port)
|
||||
log.info('Telnet console {}:{} closed'.format(host, port))
|
||||
if sys.platform.startswith("darwin") and "osascript" in command:
|
||||
console_mutex.unlock()
|
||||
else:
|
||||
#TODO: For apple script we can't detect when the console is closed. This mean we leak a port each time you close the console
|
||||
self._server.releaseTunnel(port)
|
||||
|
||||
|
||||
def nodeTelnetConsole(name, server, port):
|
||||
@@ -91,10 +99,8 @@ def nodeTelnetConsole(name, server, port):
|
||||
if not command:
|
||||
return
|
||||
|
||||
# FIXME: do we still need to run the console from a thread?
|
||||
log.info('Starting telnet console in thread "{}"'.format(command))
|
||||
console_thread = ConsoleThread(MainWindow.instance(), command, name, server, port)
|
||||
# console_thread.consoleDone.connect(callback)
|
||||
console_thread.consoleError.connect(_consoleErrorSlot)
|
||||
console_thread.start()
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>On Windows and OSX the local server is not supported. Please use the GNS3 VM.</string>
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -588,6 +588,39 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWizardPage" name="uiQemuWizardPage">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Qemu settings</string>
|
||||
</property>
|
||||
<property name="subTitle">
|
||||
<string>Please choose the qemu binary that we will use for running this appliance.</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="uiQemuListLabel">
|
||||
<property name="text">
|
||||
<string>Qemu binary:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="uiQemuListComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWizardPage" name="uiUsageWizardPage">
|
||||
<property name="title">
|
||||
<string>Usage</string>
|
||||
@@ -602,7 +635,7 @@
|
||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'.Helvetica Neue DeskInterface'; font-size:13pt; font-weight:400; font-style:normal;">
|
||||
</style></head><body style=" font-family:'.SF NS Text'; font-size:13pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt;">The default username/password is admin/admin. A default configuration is present.</span></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
|
||||
@@ -2,15 +2,13 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/ui/appliance_wizard.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.5
|
||||
# Created by: PyQt5 UI code generator 5.5.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_ApplianceWizard(object):
|
||||
|
||||
def setupUi(self, ApplianceWizard):
|
||||
ApplianceWizard.setObjectName("ApplianceWizard")
|
||||
ApplianceWizard.resize(688, 469)
|
||||
@@ -46,7 +44,7 @@ class Ui_ApplianceWizard(object):
|
||||
self.gridLayout_4.addWidget(self.uiInfoTreeWidget, 1, 0, 1, 1)
|
||||
self.uiDescriptionLabel = QtWidgets.QLabel(self.uiInfoWizardPage)
|
||||
self.uiDescriptionLabel.setScaledContents(False)
|
||||
self.uiDescriptionLabel.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
|
||||
self.uiDescriptionLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
|
||||
self.uiDescriptionLabel.setWordWrap(True)
|
||||
self.uiDescriptionLabel.setObjectName("uiDescriptionLabel")
|
||||
self.gridLayout_4.addWidget(self.uiDescriptionLabel, 0, 0, 1, 1)
|
||||
@@ -56,6 +54,7 @@ class Ui_ApplianceWizard(object):
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.uiServerWizardPage)
|
||||
self.verticalLayout_2.setObjectName("verticalLayout_2")
|
||||
self.label = QtWidgets.QLabel(self.uiServerWizardPage)
|
||||
self.label.setText("")
|
||||
self.label.setObjectName("label")
|
||||
self.verticalLayout_2.addWidget(self.label)
|
||||
self.uiServerTypeGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
|
||||
@@ -176,6 +175,27 @@ class Ui_ApplianceWizard(object):
|
||||
self.uiSummaryTreeWidget.header().setStretchLastSection(True)
|
||||
self.gridLayout_2.addWidget(self.uiSummaryTreeWidget, 0, 0, 1, 1)
|
||||
ApplianceWizard.addPage(self.uiSummaryWizardPage)
|
||||
self.uiQemuWizardPage = QtWidgets.QWizardPage()
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiQemuWizardPage.sizePolicy().hasHeightForWidth())
|
||||
self.uiQemuWizardPage.setSizePolicy(sizePolicy)
|
||||
self.uiQemuWizardPage.setObjectName("uiQemuWizardPage")
|
||||
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.uiQemuWizardPage)
|
||||
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
||||
self.uiQemuListLabel = QtWidgets.QLabel(self.uiQemuWizardPage)
|
||||
self.uiQemuListLabel.setObjectName("uiQemuListLabel")
|
||||
self.horizontalLayout_2.addWidget(self.uiQemuListLabel)
|
||||
self.uiQemuListComboBox = QtWidgets.QComboBox(self.uiQemuWizardPage)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiQemuListComboBox.sizePolicy().hasHeightForWidth())
|
||||
self.uiQemuListComboBox.setSizePolicy(sizePolicy)
|
||||
self.uiQemuListComboBox.setObjectName("uiQemuListComboBox")
|
||||
self.horizontalLayout_2.addWidget(self.uiQemuListComboBox)
|
||||
ApplianceWizard.addPage(self.uiQemuWizardPage)
|
||||
self.uiUsageWizardPage = QtWidgets.QWizardPage()
|
||||
self.uiUsageWizardPage.setObjectName("uiUsageWizardPage")
|
||||
self.gridLayout_3 = QtWidgets.QGridLayout(self.uiUsageWizardPage)
|
||||
@@ -210,7 +230,6 @@ class Ui_ApplianceWizard(object):
|
||||
self.uiDescriptionLabel.setText(_translate("ApplianceWizard", "NX-OSv is a reference platform for an implementation of the Cisco Nexus operating system, based on the Nexus 7000-series platforms, running as a full virtual machine on a hypervisor."))
|
||||
self.uiServerWizardPage.setTitle(_translate("ApplianceWizard", "Server"))
|
||||
self.uiServerWizardPage.setSubTitle(_translate("ApplianceWizard", "Please choose a server type to run your new Appliance."))
|
||||
self.label.setText(_translate("ApplianceWizard", "On Windows and OSX the local server is not supported. Please use the GNS3 VM."))
|
||||
self.uiServerTypeGroupBox.setTitle(_translate("ApplianceWizard", "Server type"))
|
||||
self.uiRemoteRadioButton.setText(_translate("ApplianceWizard", "Remote"))
|
||||
self.uiVMRadioButton.setText(_translate("ApplianceWizard", "GNS3 VM"))
|
||||
@@ -272,12 +291,15 @@ class Ui_ApplianceWizard(object):
|
||||
self.uiSummaryTreeWidget.topLevelItem(5).setText(0, _translate("ApplianceWizard", "kernel command line"))
|
||||
self.uiSummaryTreeWidget.topLevelItem(5).setText(1, _translate("ApplianceWizard", "user=gns3"))
|
||||
self.uiSummaryTreeWidget.setSortingEnabled(__sortingEnabled)
|
||||
self.uiQemuWizardPage.setTitle(_translate("ApplianceWizard", "Qemu settings"))
|
||||
self.uiQemuWizardPage.setSubTitle(_translate("ApplianceWizard", "Please choose the qemu binary that we will use for running this appliance."))
|
||||
self.uiQemuListLabel.setText(_translate("ApplianceWizard", "Qemu binary:"))
|
||||
self.uiUsageWizardPage.setTitle(_translate("ApplianceWizard", "Usage"))
|
||||
self.uiUsageWizardPage.setSubTitle(_translate("ApplianceWizard", "Please read the following instructions in order to use your new appliance."))
|
||||
self.uiUsageTextEdit.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:\'.Helvetica Neue DeskInterface\'; font-size:13pt; 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;\"><span style=\" font-family:\'Ubuntu\'; font-size:11pt;\">The default username/password is admin/admin. A default configuration is present.</span></p></body></html>"))
|
||||
"<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:\'.SF NS Text\'; font-size:13pt; 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;\"><span style=\" font-family:\'Ubuntu\'; font-size:11pt;\">The default username/password is admin/admin. A default configuration is present.</span></p></body></html>"))
|
||||
|
||||
from . import resources_rc
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>592</width>
|
||||
<height>223</height>
|
||||
<width>555</width>
|
||||
<height>215</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Export debug informations</string>
|
||||
<string>Export debug information</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
@@ -26,7 +26,7 @@
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>We will export debug informations. <span style=" font-weight:600;">Be carefull</span> this file can contain <span style=" font-weight:600;">private informations</span> about your topologies, GNS3 settings or your computer (list of running process for example). You can unzip the file in order to control the content.</p><p><br/>You need to<span style=" font-weight:600;"> save the project before</span> exporting the informations.</p><p><br/>Thanks a lot to helping the GNS3 community.</p></body></html></string>
|
||||
<string><html><head/><body><p>This will export a debug information file. You must first<span style=" font-weight:600;"> save a project before</span> you are allowed to export the debug data.</p><p><span style=" font-weight:600;">Be aware</span> this file can contain <span style=" font-weight:600;">private information</span> about your project, your GNS3 settings or your computer (list of running processes, opened ports etc.). You can unzip the file in order to edit its content.</p><p><br/>Thanks a lot for helping GNS3.</p></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/ui/export_debug_dialog.ui'
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/export_debug_dialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.5.1
|
||||
# Created: Sat Nov 14 18:32:47 2015
|
||||
# by: PyQt5 UI code generator 5.2.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_ExportDebugDialog(object):
|
||||
|
||||
def setupUi(self, ExportDebugDialog):
|
||||
ExportDebugDialog.setObjectName("ExportDebugDialog")
|
||||
ExportDebugDialog.setWindowModality(QtCore.Qt.WindowModal)
|
||||
ExportDebugDialog.resize(592, 223)
|
||||
ExportDebugDialog.resize(555, 215)
|
||||
ExportDebugDialog.setModal(True)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(ExportDebugDialog)
|
||||
self.verticalLayout.setContentsMargins(-1, -1, 12, -1)
|
||||
@@ -22,7 +21,7 @@ class Ui_ExportDebugDialog(object):
|
||||
self.label = QtWidgets.QLabel(ExportDebugDialog)
|
||||
self.label.setTextFormat(QtCore.Qt.RichText)
|
||||
self.label.setScaledContents(False)
|
||||
self.label.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
|
||||
self.label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
|
||||
self.label.setWordWrap(True)
|
||||
self.label.setObjectName("label")
|
||||
self.verticalLayout.addWidget(self.label)
|
||||
@@ -50,7 +49,6 @@ class Ui_ExportDebugDialog(object):
|
||||
self.uiOkButton.setObjectName("uiOkButton")
|
||||
self.horizontalLayout.addWidget(self.uiOkButton)
|
||||
self.verticalLayout.addLayout(self.horizontalLayout)
|
||||
self.label.raise_()
|
||||
|
||||
self.retranslateUi(ExportDebugDialog)
|
||||
self.uiCancelButton.clicked.connect(ExportDebugDialog.reject)
|
||||
@@ -58,8 +56,8 @@ class Ui_ExportDebugDialog(object):
|
||||
|
||||
def retranslateUi(self, ExportDebugDialog):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
ExportDebugDialog.setWindowTitle(_translate("ExportDebugDialog", "Export debug informations"))
|
||||
self.label.setText(_translate("ExportDebugDialog", "<html><head/><body><p>We will export debug informations. <span style=\" font-weight:600;\">Be carefull</span> this file can contain <span style=\" font-weight:600;\">private informations</span> about your topologies, GNS3 settings or your computer (list of running process for example). You can unzip the file in order to control the content.</p><p><br/>You need to<span style=\" font-weight:600;\"> save the project before</span> exporting the informations.</p><p><br/>Thanks a lot to helping the GNS3 community.</p></body></html>"))
|
||||
ExportDebugDialog.setWindowTitle(_translate("ExportDebugDialog", "Export debug information"))
|
||||
self.label.setText(_translate("ExportDebugDialog", "<html><head/><body><p>This will export a debug information file. You must first<span style=\" font-weight:600;\"> save a project before</span> you are allowed to export the debug data.</p><p><span style=\" font-weight:600;\">Be aware</span> this file can contain <span style=\" font-weight:600;\">private information</span> about your project, your GNS3 settings or your computer (list of running processes, opened ports etc.). You can unzip the file in order to edit its content.</p><p><br/>Thanks a lot for helping GNS3.</p></body></html>"))
|
||||
self.uiCancelButton.setText(_translate("ExportDebugDialog", "Cancel"))
|
||||
|
||||
from . import resources_rc
|
||||
|
||||
@@ -26,6 +26,12 @@
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="uiLocalPathsGroupBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Local paths</string>
|
||||
</property>
|
||||
@@ -158,6 +164,12 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="uiStyleGroupBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Style</string>
|
||||
</property>
|
||||
@@ -170,6 +182,12 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="uiConfigurationFileGroupBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Configuration file</string>
|
||||
</property>
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/general_preferences_page.ui'
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/ui/general_preferences_page.ui'
|
||||
#
|
||||
# Created: Thu Oct 15 21:17:06 2015
|
||||
# by: PyQt5 UI code generator 5.2.1
|
||||
# Created by: PyQt5 UI code generator 5.5.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_GeneralPreferencesPageWidget(object):
|
||||
|
||||
def setupUi(self, GeneralPreferencesPageWidget):
|
||||
GeneralPreferencesPageWidget.setObjectName("GeneralPreferencesPageWidget")
|
||||
GeneralPreferencesPageWidget.resize(538, 623)
|
||||
@@ -24,6 +21,11 @@ class Ui_GeneralPreferencesPageWidget(object):
|
||||
self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.uiGeneralTab)
|
||||
self.verticalLayout_5.setObjectName("verticalLayout_5")
|
||||
self.uiLocalPathsGroupBox = QtWidgets.QGroupBox(self.uiGeneralTab)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiLocalPathsGroupBox.sizePolicy().hasHeightForWidth())
|
||||
self.uiLocalPathsGroupBox.setSizePolicy(sizePolicy)
|
||||
self.uiLocalPathsGroupBox.setObjectName("uiLocalPathsGroupBox")
|
||||
self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.uiLocalPathsGroupBox)
|
||||
self.verticalLayout_7.setObjectName("verticalLayout_7")
|
||||
@@ -95,6 +97,11 @@ class Ui_GeneralPreferencesPageWidget(object):
|
||||
self.verticalLayout_7.addLayout(self.horizontalLayout_7)
|
||||
self.verticalLayout_5.addWidget(self.uiLocalPathsGroupBox)
|
||||
self.uiStyleGroupBox = QtWidgets.QGroupBox(self.uiGeneralTab)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiStyleGroupBox.sizePolicy().hasHeightForWidth())
|
||||
self.uiStyleGroupBox.setSizePolicy(sizePolicy)
|
||||
self.uiStyleGroupBox.setObjectName("uiStyleGroupBox")
|
||||
self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.uiStyleGroupBox)
|
||||
self.verticalLayout_4.setObjectName("verticalLayout_4")
|
||||
@@ -103,6 +110,11 @@ class Ui_GeneralPreferencesPageWidget(object):
|
||||
self.verticalLayout_4.addWidget(self.uiStyleComboBox)
|
||||
self.verticalLayout_5.addWidget(self.uiStyleGroupBox)
|
||||
self.uiConfigurationFileGroupBox = QtWidgets.QGroupBox(self.uiGeneralTab)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiConfigurationFileGroupBox.sizePolicy().hasHeightForWidth())
|
||||
self.uiConfigurationFileGroupBox.setSizePolicy(sizePolicy)
|
||||
self.uiConfigurationFileGroupBox.setObjectName("uiConfigurationFileGroupBox")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self.uiConfigurationFileGroupBox)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
@@ -429,3 +441,4 @@ class Ui_GeneralPreferencesPageWidget(object):
|
||||
self.uiSlowStartAllSpinBox.setSuffix(_translate("GeneralPreferencesPageWidget", " seconds"))
|
||||
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.tab), _translate("GeneralPreferencesPageWidget", "Miscellaneous"))
|
||||
self.uiRestoreDefaultsPushButton.setText(_translate("GeneralPreferencesPageWidget", "Restore defaults"))
|
||||
|
||||
|
||||
@@ -415,8 +415,8 @@ class Ui_MainWindow(object):
|
||||
self.uiOpenApplianceAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiOpenApplianceAction.setIcon(icon1)
|
||||
self.uiOpenApplianceAction.setObjectName("uiOpenApplianceAction")
|
||||
self.uiExportDebugInformationsAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiExportDebugInformationsAction.setObjectName("uiExportDebugInformationsAction")
|
||||
self.uiExportDebugInformationAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiExportDebugInformationAction.setObjectName("uiExportDebugInformationAction")
|
||||
self.uiEditMenu.addAction(self.uiSelectAllAction)
|
||||
self.uiEditMenu.addAction(self.uiSelectNoneAction)
|
||||
self.uiEditMenu.addSeparator()
|
||||
@@ -442,7 +442,7 @@ class Ui_MainWindow(object):
|
||||
self.uiHelpMenu.addAction(self.uiCheckForUpdateAction)
|
||||
self.uiHelpMenu.addAction(self.uiSetupWizard)
|
||||
self.uiHelpMenu.addAction(self.uiLabInstructionsAction)
|
||||
self.uiHelpMenu.addAction(self.uiExportDebugInformationsAction)
|
||||
self.uiHelpMenu.addAction(self.uiExportDebugInformationAction)
|
||||
self.uiHelpMenu.addAction(self.uiAboutQtAction)
|
||||
self.uiHelpMenu.addAction(self.uiAboutAction)
|
||||
self.uiViewMenu.addAction(self.uiActionFullscreen)
|
||||
@@ -676,7 +676,7 @@ class Ui_MainWindow(object):
|
||||
self.uiSetupWizard.setText(_translate("MainWindow", "&Setup Wizard"))
|
||||
self.uiIOUVMConverterAction.setText(_translate("MainWindow", "IOU VM Converter"))
|
||||
self.uiOpenApplianceAction.setText(_translate("MainWindow", "Import appliance"))
|
||||
self.uiExportDebugInformationsAction.setText(_translate("MainWindow", "Export debug informations"))
|
||||
self.uiExportDebugInformationAction.setText(_translate("MainWindow", "Export debug information"))
|
||||
|
||||
from ..console_view import ConsoleView
|
||||
from ..graphics_view import GraphicsView
|
||||
|
||||
@@ -6,37 +6,40 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>590</width>
|
||||
<height>534</height>
|
||||
<width>900</width>
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>2</horstretch>
|
||||
<verstretch>2</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>900</width>
|
||||
<height>600</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Preferences</string>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout">
|
||||
<item row="2" column="1" colspan="2">
|
||||
<widget class="QDialogButtonBox" name="uiButtonBox">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="Line" name="uiLine">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
<property name="centerButtons">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="uiTreeWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
|
||||
@@ -70,15 +73,8 @@
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="3">
|
||||
<widget class="Line" name="uiLine">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="vbox">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
@@ -104,23 +100,52 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="uiStackedWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>680</width>
|
||||
<height>519</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="uiStackedWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<widget class="QWidget" name="uiPageWidget"/>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="uiButtonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
<property name="centerButtons">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="uiPageWidget"/>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>uiButtonBox</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../resources/resources.qrc"/>
|
||||
</resources>
|
||||
@@ -132,12 +157,12 @@
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>270</y>
|
||||
<x>540</x>
|
||||
<y>571</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
<x>449</x>
|
||||
<y>299</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
@@ -148,12 +173,12 @@
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>276</y>
|
||||
<x>540</x>
|
||||
<y>571</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
<x>449</x>
|
||||
<y>299</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
|
||||
@@ -2,32 +2,31 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/ui/preferences_dialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.4.2
|
||||
# Created by: PyQt5 UI code generator 5.5.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_PreferencesDialog(object):
|
||||
|
||||
def setupUi(self, PreferencesDialog):
|
||||
PreferencesDialog.setObjectName("PreferencesDialog")
|
||||
PreferencesDialog.resize(590, 534)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
|
||||
PreferencesDialog.resize(900, 600)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(2)
|
||||
sizePolicy.setVerticalStretch(2)
|
||||
sizePolicy.setHeightForWidth(PreferencesDialog.sizePolicy().hasHeightForWidth())
|
||||
PreferencesDialog.setSizePolicy(sizePolicy)
|
||||
PreferencesDialog.setMaximumSize(QtCore.QSize(900, 600))
|
||||
PreferencesDialog.setSizeGripEnabled(False)
|
||||
PreferencesDialog.setModal(True)
|
||||
self.gridlayout = QtWidgets.QGridLayout(PreferencesDialog)
|
||||
self.gridlayout.setObjectName("gridlayout")
|
||||
self.uiButtonBox = QtWidgets.QDialogButtonBox(PreferencesDialog)
|
||||
self.uiButtonBox.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.uiButtonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply | QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok)
|
||||
self.uiButtonBox.setCenterButtons(False)
|
||||
self.uiButtonBox.setObjectName("uiButtonBox")
|
||||
self.gridlayout.addWidget(self.uiButtonBox, 2, 1, 1, 2)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout(PreferencesDialog)
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.uiLine = QtWidgets.QFrame(PreferencesDialog)
|
||||
self.uiLine.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
self.uiLine.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
self.uiLine.setObjectName("uiLine")
|
||||
self.horizontalLayout.addWidget(self.uiLine)
|
||||
self.uiTreeWidget = QtWidgets.QTreeWidget(PreferencesDialog)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
@@ -44,15 +43,10 @@ class Ui_PreferencesDialog(object):
|
||||
self.uiTreeWidget.setObjectName("uiTreeWidget")
|
||||
self.uiTreeWidget.headerItem().setText(0, "1")
|
||||
self.uiTreeWidget.header().setVisible(False)
|
||||
self.gridlayout.addWidget(self.uiTreeWidget, 0, 0, 1, 1)
|
||||
self.uiLine = QtWidgets.QFrame(PreferencesDialog)
|
||||
self.uiLine.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
self.uiLine.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
self.uiLine.setObjectName("uiLine")
|
||||
self.gridlayout.addWidget(self.uiLine, 1, 0, 1, 3)
|
||||
self.vboxlayout = QtWidgets.QVBoxLayout()
|
||||
self.vboxlayout.setSpacing(3)
|
||||
self.vboxlayout.setObjectName("vboxlayout")
|
||||
self.horizontalLayout.addWidget(self.uiTreeWidget)
|
||||
self.vbox = QtWidgets.QVBoxLayout()
|
||||
self.vbox.setSpacing(3)
|
||||
self.vbox.setObjectName("vbox")
|
||||
self.uiTitleLabel = QtWidgets.QLabel(PreferencesDialog)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)
|
||||
sizePolicy.setHorizontalStretch(1)
|
||||
@@ -67,8 +61,16 @@ class Ui_PreferencesDialog(object):
|
||||
self.uiTitleLabel.setFont(font)
|
||||
self.uiTitleLabel.setFrameShape(QtWidgets.QFrame.Box)
|
||||
self.uiTitleLabel.setObjectName("uiTitleLabel")
|
||||
self.vboxlayout.addWidget(self.uiTitleLabel)
|
||||
self.uiStackedWidget = QtWidgets.QStackedWidget(PreferencesDialog)
|
||||
self.vbox.addWidget(self.uiTitleLabel)
|
||||
self.scrollArea = QtWidgets.QScrollArea(PreferencesDialog)
|
||||
self.scrollArea.setWidgetResizable(True)
|
||||
self.scrollArea.setObjectName("scrollArea")
|
||||
self.scrollAreaWidgetContents_2 = QtWidgets.QWidget()
|
||||
self.scrollAreaWidgetContents_2.setGeometry(QtCore.QRect(0, 0, 680, 519))
|
||||
self.scrollAreaWidgetContents_2.setObjectName("scrollAreaWidgetContents_2")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents_2)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiStackedWidget = QtWidgets.QStackedWidget(self.scrollAreaWidgetContents_2)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
sizePolicy.setHorizontalStretch(1)
|
||||
sizePolicy.setVerticalStretch(1)
|
||||
@@ -78,8 +80,16 @@ class Ui_PreferencesDialog(object):
|
||||
self.uiPageWidget = QtWidgets.QWidget()
|
||||
self.uiPageWidget.setObjectName("uiPageWidget")
|
||||
self.uiStackedWidget.addWidget(self.uiPageWidget)
|
||||
self.vboxlayout.addWidget(self.uiStackedWidget)
|
||||
self.gridlayout.addLayout(self.vboxlayout, 0, 2, 1, 1)
|
||||
self.verticalLayout.addWidget(self.uiStackedWidget)
|
||||
self.scrollArea.setWidget(self.scrollAreaWidgetContents_2)
|
||||
self.vbox.addWidget(self.scrollArea)
|
||||
self.uiButtonBox = QtWidgets.QDialogButtonBox(PreferencesDialog)
|
||||
self.uiButtonBox.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.uiButtonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
|
||||
self.uiButtonBox.setCenterButtons(False)
|
||||
self.uiButtonBox.setObjectName("uiButtonBox")
|
||||
self.vbox.addWidget(self.uiButtonBox)
|
||||
self.horizontalLayout.addLayout(self.vbox)
|
||||
|
||||
self.retranslateUi(PreferencesDialog)
|
||||
self.uiButtonBox.accepted.connect(PreferencesDialog.accept)
|
||||
|
||||
@@ -22,8 +22,14 @@
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QTabWidget" name="uiServerPreferenceTabWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
<number>2</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="uiLocalTabWidget">
|
||||
<attribute name="title">
|
||||
@@ -48,7 +54,7 @@
|
||||
<string>Enable local server</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -64,6 +70,9 @@
|
||||
<string>General settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMinimumSize</enum>
|
||||
</property>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiLocalServerPathLabel">
|
||||
<property name="text">
|
||||
@@ -162,7 +171,7 @@
|
||||
<item>
|
||||
<widget class="QGroupBox" name="uiConsolePortRangeGroupBox">
|
||||
<property name="title">
|
||||
<string>Console port range</string>
|
||||
<string>Console port range (5900 => 6000 is shared with VNC)</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<item>
|
||||
@@ -312,7 +321,7 @@
|
||||
<string>Enable the local GNS3 VM</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -322,6 +331,9 @@
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMinimumSize</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiVirtualizationSoftwarLabel">
|
||||
<property name="text">
|
||||
@@ -437,7 +449,7 @@
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="uiVMPasswordLineEdit">
|
||||
<property name="inputMethodHints">
|
||||
<set>Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText</set>
|
||||
<set>Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText|Qt::ImhSensitiveData</set>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
@@ -561,6 +573,12 @@
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QTreeWidget" name="uiRemoteServersTreeWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>4</number>
|
||||
</property>
|
||||
@@ -601,7 +619,7 @@
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="currentText" stdset="0">
|
||||
<property name="currentText">
|
||||
<string>HTTP</string>
|
||||
</property>
|
||||
<item>
|
||||
@@ -703,7 +721,7 @@
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="uiRemoteServerPasswordLineEdit">
|
||||
<property name="inputMethodHints">
|
||||
<set>Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText</set>
|
||||
<set>Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText|Qt::ImhSensitiveData</set>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/server_preferences_page.ui'
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/ui/server_preferences_page.ui'
|
||||
#
|
||||
# Created: Sun Nov 1 18:17:05 2015
|
||||
# by: PyQt5 UI code generator 5.2.1
|
||||
# Created by: PyQt5 UI code generator 5.5.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_ServerPreferencesPageWidget(object):
|
||||
|
||||
def setupUi(self, ServerPreferencesPageWidget):
|
||||
ServerPreferencesPageWidget.setObjectName("ServerPreferencesPageWidget")
|
||||
ServerPreferencesPageWidget.resize(500, 609)
|
||||
@@ -19,6 +16,11 @@ class Ui_ServerPreferencesPageWidget(object):
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(ServerPreferencesPageWidget)
|
||||
self.verticalLayout_2.setObjectName("verticalLayout_2")
|
||||
self.uiServerPreferenceTabWidget = QtWidgets.QTabWidget(ServerPreferencesPageWidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiServerPreferenceTabWidget.sizePolicy().hasHeightForWidth())
|
||||
self.uiServerPreferenceTabWidget.setSizePolicy(sizePolicy)
|
||||
self.uiServerPreferenceTabWidget.setObjectName("uiServerPreferenceTabWidget")
|
||||
self.uiLocalTabWidget = QtWidgets.QWidget()
|
||||
self.uiLocalTabWidget.setObjectName("uiLocalTabWidget")
|
||||
@@ -31,7 +33,7 @@ class Ui_ServerPreferencesPageWidget(object):
|
||||
sizePolicy.setHeightForWidth(self.uiLocalServerAutoStartCheckBox.sizePolicy().hasHeightForWidth())
|
||||
self.uiLocalServerAutoStartCheckBox.setSizePolicy(sizePolicy)
|
||||
self.uiLocalServerAutoStartCheckBox.setMinimumSize(QtCore.QSize(0, 0))
|
||||
self.uiLocalServerAutoStartCheckBox.setChecked(True)
|
||||
self.uiLocalServerAutoStartCheckBox.setChecked(False)
|
||||
self.uiLocalServerAutoStartCheckBox.setObjectName("uiLocalServerAutoStartCheckBox")
|
||||
self.verticalLayout.addWidget(self.uiLocalServerAutoStartCheckBox)
|
||||
self.uiGeneralSettingsGroupBox = QtWidgets.QGroupBox(self.uiLocalTabWidget)
|
||||
@@ -42,6 +44,7 @@ class Ui_ServerPreferencesPageWidget(object):
|
||||
self.uiGeneralSettingsGroupBox.setSizePolicy(sizePolicy)
|
||||
self.uiGeneralSettingsGroupBox.setObjectName("uiGeneralSettingsGroupBox")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self.uiGeneralSettingsGroupBox)
|
||||
self.gridLayout.setSizeConstraint(QtWidgets.QLayout.SetMinimumSize)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.uiLocalServerPathLabel = QtWidgets.QLabel(self.uiGeneralSettingsGroupBox)
|
||||
self.uiLocalServerPathLabel.setObjectName("uiLocalServerPathLabel")
|
||||
@@ -137,6 +140,10 @@ class Ui_ServerPreferencesPageWidget(object):
|
||||
self.verticalLayout.addWidget(self.uiUDPPortRangeGroupBox)
|
||||
spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.verticalLayout.addItem(spacerItem2)
|
||||
self.uiGeneralSettingsGroupBox.raise_()
|
||||
self.uiConsolePortRangeGroupBox.raise_()
|
||||
self.uiUDPPortRangeGroupBox.raise_()
|
||||
self.uiLocalServerAutoStartCheckBox.raise_()
|
||||
self.uiServerPreferenceTabWidget.addTab(self.uiLocalTabWidget, "")
|
||||
self.uiGNS3VMTabWidget = QtWidgets.QWidget()
|
||||
self.uiGNS3VMTabWidget.setObjectName("uiGNS3VMTabWidget")
|
||||
@@ -149,12 +156,13 @@ class Ui_ServerPreferencesPageWidget(object):
|
||||
sizePolicy.setHeightForWidth(self.uiEnableVMCheckBox.sizePolicy().hasHeightForWidth())
|
||||
self.uiEnableVMCheckBox.setSizePolicy(sizePolicy)
|
||||
self.uiEnableVMCheckBox.setMinimumSize(QtCore.QSize(0, 0))
|
||||
self.uiEnableVMCheckBox.setChecked(True)
|
||||
self.uiEnableVMCheckBox.setChecked(False)
|
||||
self.uiEnableVMCheckBox.setObjectName("uiEnableVMCheckBox")
|
||||
self.verticalLayout_3.addWidget(self.uiEnableVMCheckBox)
|
||||
self.uiGNS3VMSettingsGroupBox = QtWidgets.QGroupBox(self.uiGNS3VMTabWidget)
|
||||
self.uiGNS3VMSettingsGroupBox.setObjectName("uiGNS3VMSettingsGroupBox")
|
||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.uiGNS3VMSettingsGroupBox)
|
||||
self.gridLayout_2.setSizeConstraint(QtWidgets.QLayout.SetMinimumSize)
|
||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||
self.uiVirtualizationSoftwarLabel = QtWidgets.QLabel(self.uiGNS3VMSettingsGroupBox)
|
||||
self.uiVirtualizationSoftwarLabel.setObjectName("uiVirtualizationSoftwarLabel")
|
||||
@@ -209,7 +217,7 @@ class Ui_ServerPreferencesPageWidget(object):
|
||||
self.uiVMPasswordLabel.setObjectName("uiVMPasswordLabel")
|
||||
self.gridLayout_3.addWidget(self.uiVMPasswordLabel, 1, 0, 1, 1)
|
||||
self.uiVMPasswordLineEdit = QtWidgets.QLineEdit(self.groupBox)
|
||||
self.uiVMPasswordLineEdit.setInputMethodHints(QtCore.Qt.ImhHiddenText | QtCore.Qt.ImhNoAutoUppercase | QtCore.Qt.ImhNoPredictiveText)
|
||||
self.uiVMPasswordLineEdit.setInputMethodHints(QtCore.Qt.ImhHiddenText|QtCore.Qt.ImhNoAutoUppercase|QtCore.Qt.ImhNoPredictiveText|QtCore.Qt.ImhSensitiveData)
|
||||
self.uiVMPasswordLineEdit.setEchoMode(QtWidgets.QLineEdit.Password)
|
||||
self.uiVMPasswordLineEdit.setObjectName("uiVMPasswordLineEdit")
|
||||
self.gridLayout_3.addWidget(self.uiVMPasswordLineEdit, 1, 1, 1, 1)
|
||||
@@ -262,6 +270,11 @@ class Ui_ServerPreferencesPageWidget(object):
|
||||
spacerItem6 = QtWidgets.QSpacerItem(390, 12, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout_5.addItem(spacerItem6, 10, 0, 1, 2)
|
||||
self.uiRemoteServersTreeWidget = QtWidgets.QTreeWidget(self.uiRemoteTabWidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiRemoteServersTreeWidget.sizePolicy().hasHeightForWidth())
|
||||
self.uiRemoteServersTreeWidget.setSizePolicy(sizePolicy)
|
||||
self.uiRemoteServersTreeWidget.setColumnCount(4)
|
||||
self.uiRemoteServersTreeWidget.setObjectName("uiRemoteServersTreeWidget")
|
||||
self.uiRemoteServersTreeWidget.headerItem().setText(0, "Protocol")
|
||||
@@ -321,7 +334,7 @@ class Ui_ServerPreferencesPageWidget(object):
|
||||
self.uiRemoteServerSSHPortSpinBox.setObjectName("uiRemoteServerSSHPortSpinBox")
|
||||
self.gridLayout_5.addWidget(self.uiRemoteServerSSHPortSpinBox, 7, 1, 1, 1)
|
||||
self.uiRemoteServerPasswordLineEdit = QtWidgets.QLineEdit(self.uiRemoteTabWidget)
|
||||
self.uiRemoteServerPasswordLineEdit.setInputMethodHints(QtCore.Qt.ImhHiddenText | QtCore.Qt.ImhNoAutoUppercase | QtCore.Qt.ImhNoPredictiveText)
|
||||
self.uiRemoteServerPasswordLineEdit.setInputMethodHints(QtCore.Qt.ImhHiddenText|QtCore.Qt.ImhNoAutoUppercase|QtCore.Qt.ImhNoPredictiveText|QtCore.Qt.ImhSensitiveData)
|
||||
self.uiRemoteServerPasswordLineEdit.setText("")
|
||||
self.uiRemoteServerPasswordLineEdit.setEchoMode(QtWidgets.QLineEdit.Password)
|
||||
self.uiRemoteServerPasswordLineEdit.setObjectName("uiRemoteServerPasswordLineEdit")
|
||||
@@ -329,6 +342,22 @@ class Ui_ServerPreferencesPageWidget(object):
|
||||
self.uiRemoteServerPasswordLabel = QtWidgets.QLabel(self.uiRemoteTabWidget)
|
||||
self.uiRemoteServerPasswordLabel.setObjectName("uiRemoteServerPasswordLabel")
|
||||
self.gridLayout_5.addWidget(self.uiRemoteServerPasswordLabel, 6, 0, 1, 1)
|
||||
self.uiRemoteServerProtocolComboBox.raise_()
|
||||
self.uiRemoteServerHostLabel.raise_()
|
||||
self.uiRemoteServerPortLineEdit.raise_()
|
||||
self.uiRemoteServerPortLabel.raise_()
|
||||
self.uiRemoteServerPortSpinBox.raise_()
|
||||
self.uiRAMLimitLabel.raise_()
|
||||
self.uiRAMLimitSpinBox.raise_()
|
||||
self.uiRemoteServerUserLabel.raise_()
|
||||
self.uiRemoteServerUserLineEdit.raise_()
|
||||
self.uiRemoteServerSSHPortLabel.raise_()
|
||||
self.uiRemoteServerSSHPortSpinBox.raise_()
|
||||
self.uiRemoteServerSSHKeyLabel.raise_()
|
||||
self.uiRemoteServersTreeWidget.raise_()
|
||||
self.uiRemoteServerProtocolLabel.raise_()
|
||||
self.uiRemoteServerPasswordLineEdit.raise_()
|
||||
self.uiRemoteServerPasswordLabel.raise_()
|
||||
self.uiServerPreferenceTabWidget.addTab(self.uiRemoteTabWidget, "")
|
||||
self.uiLoadBalancingTabWidget = QtWidgets.QWidget()
|
||||
self.uiLoadBalancingTabWidget.setObjectName("uiLoadBalancingTabWidget")
|
||||
@@ -364,7 +393,7 @@ class Ui_ServerPreferencesPageWidget(object):
|
||||
self.verticalLayout_2.addLayout(self.horizontalLayout_2)
|
||||
|
||||
self.retranslateUi(ServerPreferencesPageWidget)
|
||||
self.uiServerPreferenceTabWidget.setCurrentIndex(0)
|
||||
self.uiServerPreferenceTabWidget.setCurrentIndex(2)
|
||||
QtCore.QMetaObject.connectSlotsByName(ServerPreferencesPageWidget)
|
||||
ServerPreferencesPageWidget.setTabOrder(self.uiServerPreferenceTabWidget, self.uiLocalServerAutoStartCheckBox)
|
||||
ServerPreferencesPageWidget.setTabOrder(self.uiLocalServerAutoStartCheckBox, self.uiLocalServerPathLineEdit)
|
||||
@@ -418,7 +447,7 @@ class Ui_ServerPreferencesPageWidget(object):
|
||||
self.uiLocalServerPortLabel.setText(_translate("ServerPreferencesPageWidget", "Port:"))
|
||||
self.uiConsoleConnectionsToAnyIPCheckBox.setText(_translate("ServerPreferencesPageWidget", "Allow console connections to any local IP address"))
|
||||
self.uiLocalServerAuthCheckBox.setText(_translate("ServerPreferencesPageWidget", "Protect server with password (recommended)"))
|
||||
self.uiConsolePortRangeGroupBox.setTitle(_translate("ServerPreferencesPageWidget", "Console port range"))
|
||||
self.uiConsolePortRangeGroupBox.setTitle(_translate("ServerPreferencesPageWidget", "Console port range (5900 => 6000 is shared with VNC)"))
|
||||
self.uiConsolePortRangeLabel.setText(_translate("ServerPreferencesPageWidget", "to"))
|
||||
self.uiUDPPortRangeGroupBox.setTitle(_translate("ServerPreferencesPageWidget", "UDP tunneling port range"))
|
||||
self.uiUDPPortRangeLabel.setText(_translate("ServerPreferencesPageWidget", "to"))
|
||||
@@ -445,7 +474,7 @@ class Ui_ServerPreferencesPageWidget(object):
|
||||
self.uiDeleteRemoteServerPushButton.setText(_translate("ServerPreferencesPageWidget", "&Delete"))
|
||||
self.uiRemoteServersTreeWidget.headerItem().setText(3, _translate("ServerPreferencesPageWidget", "User"))
|
||||
self.uiRemoteServerProtocolLabel.setText(_translate("ServerPreferencesPageWidget", "Protocol:"))
|
||||
self.uiRemoteServerProtocolComboBox.setProperty("currentText", _translate("ServerPreferencesPageWidget", "HTTP"))
|
||||
self.uiRemoteServerProtocolComboBox.setCurrentText(_translate("ServerPreferencesPageWidget", "HTTP"))
|
||||
self.uiRemoteServerProtocolComboBox.setItemText(0, _translate("ServerPreferencesPageWidget", "HTTP"))
|
||||
self.uiRemoteServerProtocolComboBox.setItemText(1, _translate("ServerPreferencesPageWidget", "HTTPS"))
|
||||
self.uiRemoteServerProtocolComboBox.setItemText(2, _translate("ServerPreferencesPageWidget", "SSH"))
|
||||
@@ -463,3 +492,4 @@ class Ui_ServerPreferencesPageWidget(object):
|
||||
self.uiRendezVousHashingRadioButton.setText(_translate("ServerPreferencesPageWidget", "Rendezvous hashing"))
|
||||
self.uiServerPreferenceTabWidget.setTabText(self.uiServerPreferenceTabWidget.indexOf(self.uiLoadBalancingTabWidget), _translate("ServerPreferencesPageWidget", "Load Balancing"))
|
||||
self.uiRestoreDefaultsPushButton.setText(_translate("ServerPreferencesPageWidget", "Restore defaults"))
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ class UpdateManager(QtCore.QObject):
|
||||
network_reply = self.sender()
|
||||
if network_reply.error() != QtNetwork.QNetworkReply.NoError:
|
||||
if not self._silent:
|
||||
QtWidgets.QMessageBox.critical(self, "Check For Update", "Cannot check for update: {}".format(network_reply.errorString()))
|
||||
QtWidgets.QMessageBox.critical(self._parent, "Check For Update", "Cannot check for update: {}".format(network_reply.errorString()))
|
||||
return
|
||||
try:
|
||||
latest_release = bytes(network_reply.readAll()).decode("utf-8").rstrip()
|
||||
|
||||
@@ -50,21 +50,18 @@ class ProgressDialog(QtWidgets.QProgressDialog):
|
||||
|
||||
super().__init__(label_text, cancel_button_text, minimum, maximum, parent)
|
||||
|
||||
self._thread = QtCore.QThread(self)
|
||||
|
||||
self.setModal(True)
|
||||
self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
|
||||
self._errors = []
|
||||
self.setWindowTitle(title)
|
||||
self._worker = worker
|
||||
self.canceled.connect(self._worker.cancel)
|
||||
self.canceled.connect(self._canceledSlot)
|
||||
self.finished.connect(self.close)
|
||||
self.destroyed.connect(self._cleanup)
|
||||
# self.canceled.connect(self._cancel)
|
||||
|
||||
# create the thread and set the self._worker
|
||||
self._thread = QtCore.QThread(self)
|
||||
self._worker.moveToThread(self._thread)
|
||||
|
||||
# connect self._worker signals
|
||||
self._worker.updated.connect(self._updateProgress)
|
||||
self._worker.error.connect(self._error)
|
||||
self._worker.finished.connect(self._cleanup)
|
||||
@@ -75,6 +72,10 @@ class ProgressDialog(QtWidgets.QProgressDialog):
|
||||
self._thread.started.connect(self._worker.run)
|
||||
self._thread.start()
|
||||
|
||||
def _canceledSlot(self):
|
||||
self._worker.cancel()
|
||||
self._cleanup()
|
||||
|
||||
def __del__(self):
|
||||
|
||||
self._cleanup()
|
||||
@@ -102,7 +103,7 @@ class ProgressDialog(QtWidgets.QProgressDialog):
|
||||
:param value: value for the progress bar (integer)
|
||||
"""
|
||||
|
||||
if self._thread is not None:
|
||||
if self is not None and self._thread is not None:
|
||||
# It seem in some cases this is called on a deleted object and crash
|
||||
if not sip.isdeleted(self):
|
||||
self.setValue(value)
|
||||
|
||||
@@ -20,7 +20,6 @@ Thread to wait for the GNS3 VM.
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import socket
|
||||
import subprocess
|
||||
|
||||
@@ -144,8 +143,7 @@ class WaitForVMWorker(QtCore.QObject):
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _waitForServer(vm_server, endpoint, retry=0):
|
||||
def _waitForServer(self, vm_server, endpoint, retry=0):
|
||||
"""
|
||||
Wait for a VM server to reply to a request.
|
||||
|
||||
@@ -159,7 +157,7 @@ class WaitForVMWorker(QtCore.QObject):
|
||||
status, json_data = vm_server.getSynchronous(endpoint, timeout=1)
|
||||
if status != 0:
|
||||
break
|
||||
time.sleep(1)
|
||||
self.thread().sleep(1)
|
||||
retry -= 1
|
||||
return status, json_data
|
||||
|
||||
|
||||
@@ -25,5 +25,5 @@ or negative for a release candidate or beta (after the base version
|
||||
number has been incremented)
|
||||
"""
|
||||
|
||||
__version__ = "1.4.0dev11"
|
||||
__version_info__ = (1, 4, 0, 11)
|
||||
__version__ = "1.4.0"
|
||||
__version_info__ = (1, 4, 0, 0)
|
||||
|
||||
12
gns3/vm.py
12
gns3/vm.py
@@ -19,6 +19,9 @@
|
||||
Base class for VM classes.
|
||||
"""
|
||||
|
||||
import os
|
||||
from gns3.servers import Servers
|
||||
|
||||
from .node import Node
|
||||
|
||||
import logging
|
||||
@@ -261,6 +264,15 @@ class VM(Node):
|
||||
:returns: config content
|
||||
"""
|
||||
|
||||
if config_path is None or len(config_path.strip()) == 0:
|
||||
return None
|
||||
|
||||
if not os.path.isabs(config_path):
|
||||
config_path = os.path.join(Servers.instance().localServerSettings()["configs_path"], config_path)
|
||||
|
||||
if not os.path.isfile(config_path):
|
||||
return None
|
||||
|
||||
try:
|
||||
with open(config_path, "rb") as f:
|
||||
log.info("Opening configuration file: {}".format(config_path))
|
||||
|
||||
@@ -2,4 +2,4 @@ jsonschema>=2.4.0
|
||||
paramiko>=1.15.1
|
||||
raven>=5.2.0
|
||||
psutil>=2.2.1
|
||||
gns3-converter>=1.2.3
|
||||
gns3-converter>=1.2.4
|
||||
|
||||
2
setup.py
2
setup.py
@@ -51,7 +51,7 @@ setup(
|
||||
install_requires=[
|
||||
"jsonschema>=2.4.0",
|
||||
"paramiko>=1.15.1",
|
||||
"gns3-converter>=1.2.3",
|
||||
"gns3-converter>=1.2.4",
|
||||
"raven>=5.2.0",
|
||||
"psutil>=2.2.1",
|
||||
],
|
||||
|
||||
@@ -200,6 +200,8 @@ def main_window():
|
||||
@pytest.fixture
|
||||
def images_dir(tmpdir):
|
||||
os.makedirs(os.path.join(str(tmpdir), "images", "QEMU"), exist_ok=True)
|
||||
os.makedirs(os.path.join(str(tmpdir), "images", "IOS"), exist_ok=True)
|
||||
os.makedirs(os.path.join(str(tmpdir), "images", "IOU"), exist_ok=True)
|
||||
return os.path.join(str(tmpdir), "images")
|
||||
|
||||
|
||||
@@ -221,6 +223,30 @@ def linux_microcore_img(images_dir):
|
||||
return path
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def iou_l3(images_dir):
|
||||
"""
|
||||
Create a fake image and return the path. The md5sum of the file will be 5d41402abc4b2a76b9719d911017c592
|
||||
"""
|
||||
|
||||
path = os.path.join(images_dir, "IOU", "i86bi-linux-l3-adventerprisek9-15.4.1T.bin")
|
||||
with open(path, 'w+') as f:
|
||||
f.write("hello")
|
||||
return path
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cisco_3745(images_dir):
|
||||
"""
|
||||
Create a fake image and return the path. The md5sum of the file will be 5d41402abc4b2a76b9719d911017c592
|
||||
"""
|
||||
|
||||
path = os.path.join(images_dir, "IOS", "c3745-adventerprisek9-mz.124-25d.image")
|
||||
with open(path, 'w+') as f:
|
||||
f.write("hello")
|
||||
return path
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
"""
|
||||
Use to detect in code if we are running from pytest
|
||||
|
||||
47
tests/registry/appliances/cisco-3745.gns3a
Normal file
47
tests/registry/appliances/cisco-3745.gns3a
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"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": 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",
|
||||
"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"
|
||||
},
|
||||
|
||||
"versions": [
|
||||
{
|
||||
"images": {
|
||||
"image": "c3745-adventerprisek9-mz.124-25d.image"
|
||||
},
|
||||
"idlepc": "0x60aa1da0",
|
||||
"name": "124-25d"
|
||||
}
|
||||
],
|
||||
"images": [
|
||||
{
|
||||
"filesize": 82053028,
|
||||
"md5sum": "ddbaf74274822b50fa9670e10c75b08f",
|
||||
"version": "124-25d",
|
||||
"filename": "c3745-adventerprisek9-mz.124-25d.image"
|
||||
}
|
||||
]
|
||||
}
|
||||
37
tests/registry/appliances/cisco-iou-l3.gns3a
Normal file
37
tests/registry/appliances/cisco-iou-l3.gns3a
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"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": 2,
|
||||
"maintainer_email": "developers@gns3.net",
|
||||
|
||||
"iou": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2,39 +2,40 @@
|
||||
"name": "Micro Core Linux",
|
||||
"category": "guest",
|
||||
"description": "Micro Core Linux® i is a smaller variant of Tiny Core without a graphical desktop.\n\nIt's provide a complete Linux system in few MB.",
|
||||
"vendor_name": "Team Tiny Core",
|
||||
"vendor_url": "http://distro.ibiblio.org/tinycorelinux",
|
||||
"documentation_url": "http://wiki.tinycorelinux.net/",
|
||||
"vendor_name": "Team Tiny Core",
|
||||
"vendor_url": "http://distro.ibiblio.org/tinycorelinux",
|
||||
"documentation_url": "http://wiki.tinycorelinux.net/",
|
||||
"product_name": "Micro Core Linux",
|
||||
"product_url": "http://distro.ibiblio.org/tinycorelinux",
|
||||
"registry_version": 1,
|
||||
"product_url": "http://distro.ibiblio.org/tinycorelinux",
|
||||
"registry_version": 1,
|
||||
"status": "stable",
|
||||
"maintainer": "GNS3 Team",
|
||||
"maintainer_email": "developers@gns3.net",
|
||||
|
||||
"usage": "Just start the appliance",
|
||||
|
||||
"qemu": {
|
||||
"adapter_type": "e1000",
|
||||
"adapters": 1,
|
||||
"ram": 32,
|
||||
"arch": "i386",
|
||||
"console_type": "telnet"
|
||||
"console_type": "telnet"
|
||||
},
|
||||
|
||||
"images": [
|
||||
"images": [
|
||||
{
|
||||
"filename": "linux-microcore-3.4.1.img",
|
||||
"version": "3.4.1",
|
||||
"md5sum": "5d41402abc4b2a76b9719d911017c592",
|
||||
"filesize": 5,
|
||||
"download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
|
||||
"direct_download_url": "http://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/linux-microcore-3.4.1.img"
|
||||
"download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
|
||||
"direct_download_url": "http://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/linux-microcore-3.4.1.img"
|
||||
},
|
||||
{
|
||||
"filename": "linux-microcore-4.0.2-clean.img",
|
||||
"version": "4.0.2",
|
||||
"md5sum": "e13d0d1c0b3999ae2386bba70417930c",
|
||||
"filesize": 26411008,
|
||||
"download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
|
||||
"filesize": 26411008,
|
||||
"download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
|
||||
"direct_download_url": "http://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/linux-microcore-4.0.2-clean.img"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -56,10 +56,9 @@ def test_check_config(tmpdir, registry):
|
||||
with pytest.raises(ApplianceError):
|
||||
Appliance(registry, test_path)
|
||||
|
||||
with open(test_path, "w+", encoding="utf-8") as f:
|
||||
f.write('{"registry_version": 2}')
|
||||
|
||||
with pytest.raises(ApplianceError):
|
||||
with open(test_path, "w+", encoding="utf-8") as f:
|
||||
f.write('{"registry_version": 42}')
|
||||
Appliance(registry, test_path)
|
||||
|
||||
Appliance(registry, "tests/registry/appliances/microcore-linux.json")
|
||||
@@ -76,6 +75,18 @@ def test_resolve_version(tmpdir):
|
||||
assert new_config["versions"][0]["images"] == {"hda_disk_image": hda}
|
||||
|
||||
|
||||
def test_resolve_version_dynamips(tmpdir):
|
||||
|
||||
with open("tests/registry/appliances/cisco-3745.gns3a", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
|
||||
hda = config["images"][0]
|
||||
hda["idlepc"] = "0x60aa1da0"
|
||||
|
||||
new_config = Appliance(registry, "tests/registry/appliances/cisco-3745.gns3a")
|
||||
assert new_config["versions"][0]["images"] == {"image": hda}
|
||||
|
||||
|
||||
def test_resolve_version_invalid_file(tmpdir):
|
||||
|
||||
with pytest.raises(ApplianceError):
|
||||
@@ -121,3 +132,10 @@ def test_is_version_installable(linux_microcore_img, microcore_appliance):
|
||||
|
||||
assert microcore_appliance.is_version_installable("3.4.1")
|
||||
assert not microcore_appliance.is_version_installable("4.0.2")
|
||||
|
||||
|
||||
def test_image_dir_name(microcore_appliance):
|
||||
|
||||
assert Appliance(registry, "tests/registry/appliances/microcore-linux.json").image_dir_name() == "QEMU"
|
||||
assert Appliance(registry, "tests/registry/appliances/cisco-iou-l3.gns3a").image_dir_name() == "IOU"
|
||||
|
||||
|
||||
@@ -51,16 +51,12 @@ def empty_config(tmpdir, images_dir, symbols_dir):
|
||||
"ghost_ios_support": True,
|
||||
"mmap_support": True,
|
||||
"routers": [
|
||||
{
|
||||
}
|
||||
],
|
||||
"sparse_memory_support": True,
|
||||
"use_local_server": True
|
||||
},
|
||||
"IOU": {
|
||||
"appliances": [
|
||||
{
|
||||
}
|
||||
"devices": [
|
||||
],
|
||||
"iourc_path": "/Users/noplay/code/gns3/gns3-vagrant/images/iou/iourc.txt",
|
||||
"iouyap_path": "",
|
||||
@@ -115,6 +111,82 @@ def test_list_servers_remote_servers(tmpdir):
|
||||
assert config.servers == ["local", "http://darkside.moon:4242"]
|
||||
|
||||
|
||||
def test_add_appliance_iou(empty_config, iou_l3):
|
||||
with open("tests/registry/appliances/cisco-iou-l3.gns3a", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
config["images"] = [
|
||||
{
|
||||
"type": "image",
|
||||
"filename": "i86bi-linux-l3-adventerprisek9-15.4.1T.bin",
|
||||
"path": iou_l3
|
||||
}
|
||||
]
|
||||
empty_config.add_appliance(config, "local")
|
||||
assert empty_config._config["IOU"]["devices"][0] == {
|
||||
"category": 0,
|
||||
"symbol": ":/symbols/router.svg",
|
||||
"server": "local",
|
||||
"name": "Cisco IOU L3",
|
||||
"l1_keepalives": False,
|
||||
"nvram": 128,
|
||||
"private_config": "",
|
||||
"ram": 256,
|
||||
"serial_adapters": 2,
|
||||
"ethernet_adapters": 2,
|
||||
"use_default_iou_values": True,
|
||||
"startup_config": "iou_l3_base_startup-config.txt",
|
||||
"image": os.path.basename(iou_l3),
|
||||
"path": os.path.basename(iou_l3)
|
||||
}
|
||||
|
||||
|
||||
def test_add_appliance_dynamips(empty_config, cisco_3745):
|
||||
with open("tests/registry/appliances/cisco-3745.gns3a", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
config["images"] = [
|
||||
{
|
||||
"type": "image",
|
||||
"filename": "c3745-adventerprisek9-mz.124-25d.image",
|
||||
"path": cisco_3745,
|
||||
"idlepc": "0x60aa1da0"
|
||||
}
|
||||
]
|
||||
empty_config.add_appliance(config, "local")
|
||||
assert empty_config._config["Dynamips"]["routers"][0] == {
|
||||
"auto_delete_disks": True,
|
||||
"category": 0,
|
||||
"chassis": "",
|
||||
"disk0": 0,
|
||||
"disk1": 0,
|
||||
"exec_area": 64,
|
||||
"idlemax": 500,
|
||||
"idlepc": "0x60aa1da0",
|
||||
"idlesleep": 30,
|
||||
"image": "c3745-adventerprisek9-mz.124-25d.image",
|
||||
"iomem": 5,
|
||||
"mac_addr": "",
|
||||
"mmap": True,
|
||||
"name": "Cisco 3745",
|
||||
"nvram": 256,
|
||||
"platform": "c3745",
|
||||
"private_config": "",
|
||||
"ram": 256,
|
||||
"server": "local",
|
||||
"slot0": "GT96100-FE",
|
||||
"slot1": "NM-1FE-TX",
|
||||
"slot2": "NM-4T",
|
||||
"slot3": "",
|
||||
"slot4": "",
|
||||
"sparsemem": True,
|
||||
"startup_config": "ios_base_startup-config.txt",
|
||||
"symbol": ":/symbols/router.svg",
|
||||
"system_id": "FTX0945W0MY",
|
||||
"wic0": "WIC-1T",
|
||||
"wic1": "WIC-1T",
|
||||
"wic2": "WIC-1T"
|
||||
}
|
||||
|
||||
|
||||
def test_add_appliance_guest(empty_config, linux_microcore_img):
|
||||
with open("tests/registry/appliances/microcore-linux.json", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
@@ -146,8 +218,13 @@ def test_add_appliance_guest(empty_config, linux_microcore_img):
|
||||
"options": "-nographic",
|
||||
"process_priority": "normal",
|
||||
"qemu_path": "qemu-system-i386",
|
||||
"usage": "Just start the appliance",
|
||||
"ram": 32,
|
||||
"server": "local"
|
||||
"server": "local",
|
||||
"hda_disk_interface": "ide",
|
||||
"hdb_disk_interface": "ide",
|
||||
"hdc_disk_interface": "ide",
|
||||
"hdd_disk_interface": "ide"
|
||||
}
|
||||
|
||||
|
||||
@@ -296,7 +373,11 @@ def test_add_appliance_router_two_disk(empty_config, images_dir):
|
||||
"qemu_path": "qemu-system-x86_64",
|
||||
"ram": 2048,
|
||||
"console_type": "telnet",
|
||||
"server": "local"
|
||||
"server": "local",
|
||||
"hda_disk_interface": "ide",
|
||||
"hdb_disk_interface": "ide",
|
||||
"hdc_disk_interface": "ide",
|
||||
"hdd_disk_interface": "ide"
|
||||
}
|
||||
|
||||
|
||||
@@ -354,7 +435,7 @@ def test_add_appliance_ova(empty_config, tmpdir, images_dir):
|
||||
]
|
||||
|
||||
empty_config.add_appliance(config, "local")
|
||||
assert empty_config._config["Qemu"]["vms"][0]["hda_disk_image"] == "junos-vsrx-12.1X47-D10.4-domestic.ova/junos-vsrx-12.1X47-D10.4-domestic-disk1.vmdk"
|
||||
assert empty_config._config["Qemu"]["vms"][0]["hda_disk_image"] == os.path.join("junos-vsrx-12.1X47-D10.4-domestic.ova", "junos-vsrx-12.1X47-D10.4-domestic-disk1.vmdk")
|
||||
|
||||
|
||||
def test_add_appliance_path_non_relative_to_images_dir(empty_config, tmpdir, images_dir):
|
||||
@@ -394,43 +475,62 @@ def test_save(empty_config, linux_microcore_img):
|
||||
assert "Micro Core" in f.read()
|
||||
|
||||
|
||||
def test_is_name_available(empty_config, linux_microcore_img):
|
||||
|
||||
with open("tests/registry/appliances/microcore-linux.json", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
|
||||
config["images"] = [
|
||||
{
|
||||
"type": "hda_disk_image",
|
||||
"filename": "linux-microcore-3.4.1.img",
|
||||
"path": linux_microcore_img
|
||||
}
|
||||
]
|
||||
|
||||
assert empty_config.is_name_available(config["name"]) is True
|
||||
empty_config.add_appliance(config, "local")
|
||||
empty_config.save()
|
||||
assert empty_config.is_name_available(config["name"]) is False
|
||||
|
||||
|
||||
def test_relative_image_path(empty_config, images_dir, tmpdir):
|
||||
|
||||
# Image in image directory no need to copy it
|
||||
open(os.path.join(images_dir, "QEMU", "a"), "w+").close()
|
||||
with patch("gns3.registry.image.Image.copy") as mock:
|
||||
assert empty_config._relative_image_path("a", os.path.join(images_dir, "QEMU", "a")) == "a"
|
||||
assert empty_config._relative_image_path("QEMU", "a", os.path.join(images_dir, "QEMU", "a")) == "a"
|
||||
assert not mock.called
|
||||
|
||||
# Image in image directory no need to copy it but with a different file name
|
||||
open(os.path.join(images_dir, "QEMU", "a"), "w+").close()
|
||||
with patch("gns3.registry.image.Image.copy") as mock:
|
||||
assert empty_config._relative_image_path("h", os.path.join(images_dir, "QEMU", "a")) == "a"
|
||||
assert empty_config._relative_image_path("QEMU", "h", os.path.join(images_dir, "QEMU", "a")) == "a"
|
||||
assert not mock.called
|
||||
|
||||
# Image outside image directory we need to copy it
|
||||
open(str(tmpdir / "b"), "w+").close()
|
||||
with patch("gns3.registry.image.Image.copy") as mock:
|
||||
assert empty_config._relative_image_path("b", str(tmpdir / "b")) == "b"
|
||||
assert empty_config._relative_image_path("QEMU", "b", str(tmpdir / "b")) == "b"
|
||||
assert mock.called
|
||||
|
||||
# OVA in images directory no need to copy
|
||||
os.makedirs(os.path.join(images_dir, "QEMU", "c.ova"))
|
||||
open(os.path.join(images_dir, "QEMU", "c.ova", "c.vmdk"), "w+").close()
|
||||
with patch("gns3.registry.image.Image.copy") as mock:
|
||||
assert empty_config._relative_image_path("c.ova/c.vmdk", os.path.join(images_dir, "QEMU", "c.ova", "c.vmdk")) == "c.ova/c.vmdk"
|
||||
assert empty_config._relative_image_path("QEMU", "c.ova/c.vmdk", os.path.join(images_dir, "QEMU", "c.ova", "c.vmdk")) == os.path.join("c.ova", "c.vmdk")
|
||||
assert not mock.called
|
||||
|
||||
# OVA outside images directory need to copy
|
||||
os.makedirs(os.path.join(str(tmpdir), "QEMU", "d.ova"))
|
||||
open(os.path.join(str(tmpdir), "QEMU", "d.ova", "d.vmdk"), "w+").close()
|
||||
with patch("gns3.registry.image.Image.copy") as mock:
|
||||
assert empty_config._relative_image_path("d.ova/d.vmdk", os.path.join(str(tmpdir), "QEMU", "d.ova", "d.vmdk")) == "d.ova/d.vmdk"
|
||||
assert empty_config._relative_image_path("QEMU", "d.ova/d.vmdk", os.path.join(str(tmpdir), "QEMU", "d.ova", "d.vmdk")) == "d.ova/d.vmdk"
|
||||
assert mock.called
|
||||
|
||||
# OVA in images directory no need to copy but with a different file name
|
||||
os.makedirs(os.path.join(images_dir, "QEMU", "e.ova"))
|
||||
open(os.path.join(images_dir, "QEMU", "e.ova", "c.vmdk"), "w+").close()
|
||||
with patch("gns3.registry.image.Image.copy") as mock:
|
||||
assert empty_config._relative_image_path("x.ova/c.vmdk", os.path.join(images_dir, "QEMU", "e.ova", "c.vmdk")) == "e.ova/c.vmdk"
|
||||
assert empty_config._relative_image_path("QEMU", "x.ova/c.vmdk", os.path.join(images_dir, "QEMU", "e.ova", "c.vmdk")) == os.path.join("e.ova", "c.vmdk")
|
||||
assert not mock.called
|
||||
|
||||
@@ -99,7 +99,7 @@ def test_addMissingImageOVAWithMultipleVMDK(image_manager, remote_server, images
|
||||
assert args[2] == 'QEMU'
|
||||
|
||||
args, kwargs = mock.call_args_list[1]
|
||||
assert args[0] == str(images_dir / 'QEMU' / 'test.ova' / 'test.vmdk')
|
||||
assert os.path.normpath(args[0]) == str(images_dir / 'QEMU' / 'test.ova' / 'test.vmdk')
|
||||
assert args[1] == remote_server
|
||||
assert args[2] == 'QEMU'
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import pytest
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from gns3.local_config import LocalConfig
|
||||
|
||||
@@ -228,3 +228,45 @@ def test_migrate13ConfigOldOsxServerPath(tmpdir):
|
||||
# The old config should not be erased in order to avoid losing data when rollback to 1.3
|
||||
assert local_config._settings["LocalServer"]["path"] == "/Applications/GNS3.app/Contents/Resources/server/Contents/MacOS/gns3server"
|
||||
assert local_config._settings["Servers"]["local_server"]["path"] == "/Applications/GNS3.app/Contents/MacOS/gns3server"
|
||||
|
||||
|
||||
def test_isMainGui_pid_file_not_exist(tmpdir):
|
||||
with patch("gns3.local_config.LocalConfig.configDirectory") as mock_config_directory:
|
||||
mock_config_directory.return_value = str(tmpdir)
|
||||
assert LocalConfig().isMainGui() is True
|
||||
assert os.path.exists(str(tmpdir / "gns3_gui.pid"))
|
||||
|
||||
|
||||
def test_isMainGui_pid_file_exist_but_different(tmpdir):
|
||||
with open(str(tmpdir / "gns3_gui.pid"), "w+") as f:
|
||||
f.write("42")
|
||||
|
||||
mock_process = MagicMock()
|
||||
mock_process.name.return_value = "gns3.exe"
|
||||
mock_process.uids.return_value = (os.getuid(), os.getuid(), os.getuid())
|
||||
with patch("psutil.Process", return_value=mock_process) as mock:
|
||||
with patch("gns3.local_config.LocalConfig.configDirectory") as mock_config_directory:
|
||||
mock_config_directory.return_value = str(tmpdir)
|
||||
assert LocalConfig().isMainGui() is False
|
||||
|
||||
|
||||
def test_isMainGui_pid_file_exist_but_different_proces_dead(tmpdir):
|
||||
with open(str(tmpdir / "gns3_gui.pid"), "w+") as f:
|
||||
f.write("42")
|
||||
|
||||
mock_process = MagicMock()
|
||||
with patch("psutil.Process", return_value=mock_process) as mock:
|
||||
mock.name.side_effect = (lambda: exec('raise(OSError())'))
|
||||
with patch("gns3.local_config.LocalConfig.configDirectory") as mock_config_directory:
|
||||
mock_config_directory.return_value = str(tmpdir)
|
||||
assert LocalConfig().isMainGui() is True
|
||||
|
||||
|
||||
def test_isMainGui_pid_file_exist_but_same_pid(tmpdir):
|
||||
with open(str(tmpdir / "gns3_gui.pid"), "w+") as f:
|
||||
f.write("42")
|
||||
|
||||
with patch("os.getpid", return_value=42):
|
||||
with patch("gns3.local_config.LocalConfig.configDirectory") as mock_config_directory:
|
||||
mock_config_directory.return_value = str(tmpdir)
|
||||
assert LocalConfig().isMainGui() is True
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# 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 json
|
||||
import pytest
|
||||
import binascii
|
||||
@@ -208,3 +209,50 @@ def test_handle_handleSslErrors():
|
||||
'user': 'root'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform.startswith('win') is True, reason='Not for windows')
|
||||
def test_startLocalServer(tmpdir, local_config):
|
||||
local_server_path = str(tmpdir / "gns3server")
|
||||
open(local_server_path, "w+").close()
|
||||
|
||||
with open(str(tmpdir / "test.cfg"), "w+") as f:
|
||||
json.dump({
|
||||
"Servers": {
|
||||
"local_server": {
|
||||
"path": local_server_path,
|
||||
}
|
||||
},
|
||||
"version": "1.4"
|
||||
}, f)
|
||||
|
||||
local_config.setConfigFilePath(str(tmpdir / "test.cfg"))
|
||||
Servers._instance = None
|
||||
|
||||
with patch("gns3.local_config.LocalConfig.configDirectory") as mock_local_config:
|
||||
mock_local_config.return_value = str(tmpdir)
|
||||
with patch("subprocess.Popen") as mock:
|
||||
Servers.instance().startLocalServer()
|
||||
mock.assert_called_with([local_server_path,
|
||||
'--host=127.0.0.1',
|
||||
'--port=8000',
|
||||
'--local',
|
||||
'--debug',
|
||||
'--log=' + str(tmpdir / "gns3_server.log"),
|
||||
'--pid=' + str(tmpdir / "gns3_server.pid")
|
||||
])
|
||||
|
||||
|
||||
def test_killAlreadyRunningServer(tmpdir):
|
||||
with patch("gns3.local_config.LocalConfig.configDirectory") as mock_local_config:
|
||||
mock_local_config.return_value = str(tmpdir)
|
||||
|
||||
with open(str(tmpdir / "gns3_server.pid"), "w+") as f:
|
||||
f.write("42")
|
||||
|
||||
mock_process = MagicMock()
|
||||
with patch("psutil.Process", return_value=mock_process) as mock:
|
||||
Servers.instance()._killAlreadyRunningServer()
|
||||
mock.assert_called_with(pid=42)
|
||||
assert mock_process.kill.called
|
||||
|
||||
|
||||
@@ -92,3 +92,18 @@ def test_deleteNIO(vpcs_device):
|
||||
|
||||
args, kwargs = mock_delete.call_args
|
||||
assert args[0] == "/vpcs/vms/{vm_id}/adapters/0/ports/0/nio".format(vm_id=vpcs_device.vm_id())
|
||||
|
||||
|
||||
def test_readBaseConfig(vpcs_device, tmpdir):
|
||||
assert vpcs_device._readBaseConfig("") is None
|
||||
with open(str(tmpdir / "test.cfg"), "w+") as f:
|
||||
f.write("42")
|
||||
assert vpcs_device._readBaseConfig(str(tmpdir / "test.cfg")) == "42"
|
||||
|
||||
|
||||
def test_readBaseConfigRelative(vpcs_device, tmpdir):
|
||||
with open(str(tmpdir / "test.cfg"), "w+") as f:
|
||||
f.write("42")
|
||||
with patch('gns3.servers.Servers.localServerSettings', return_value={'configs_path': str(tmpdir)}):
|
||||
assert vpcs_device._readBaseConfig(str("test.cfg")) == "42"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user