mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-06-03 17:22:03 +03:00
Compare commits
112 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb89fe2275 | ||
|
|
7eb2a923b2 | ||
|
|
58052e3cce | ||
|
|
e431104f6b | ||
|
|
a4e9d6b8ce | ||
|
|
f58f5c7b95 | ||
|
|
37faa39309 | ||
|
|
4d64598ed2 | ||
|
|
5132c4e172 | ||
|
|
3c3fdd9ffd | ||
|
|
efa50571c6 | ||
|
|
ca9b10fcca | ||
|
|
8660161b10 | ||
|
|
50ebfb9c06 | ||
|
|
26df59d6b6 | ||
|
|
b903e2ad73 | ||
|
|
b2fe7eb643 | ||
|
|
8095fef228 | ||
|
|
b8da5440f5 | ||
|
|
6cea094e4e | ||
|
|
9ac46c9d50 | ||
|
|
6dc44d5108 | ||
|
|
9c6be0341b | ||
|
|
011a49e998 | ||
|
|
e18c2df5f5 | ||
|
|
1794b8389f | ||
|
|
0379c370eb | ||
|
|
e03550a89b | ||
|
|
c6ea775e81 | ||
|
|
38233ba5e9 | ||
|
|
89c1272bc1 | ||
|
|
8bbb46c599 | ||
|
|
74fca3d736 | ||
|
|
7aeed7aa59 | ||
|
|
aa15ace887 | ||
|
|
2d0a7b5f58 | ||
|
|
20a09b56c1 | ||
|
|
1938cdabae | ||
|
|
8d1bff782c | ||
|
|
4e3eee2383 | ||
|
|
da8aa0d2fd | ||
|
|
5b4481c43a | ||
|
|
593cb8c1fd | ||
|
|
210cf63fe2 | ||
|
|
3b178013c0 | ||
|
|
6e44d6b919 | ||
|
|
6b520b8036 | ||
|
|
803782b9d8 | ||
|
|
d3d6ca3f2e | ||
|
|
f545c793f8 | ||
|
|
47d6a4fef6 | ||
|
|
8862b608cf | ||
|
|
76832ab83f | ||
|
|
fed245fd34 | ||
|
|
3e0f1affd0 | ||
|
|
2110c2805e | ||
|
|
46cfdd8314 | ||
|
|
f8f648c2b6 | ||
|
|
7cd0187f33 | ||
|
|
4d8f362f11 | ||
|
|
469eaa4737 | ||
|
|
c921224b30 | ||
|
|
61487b2e2f | ||
|
|
9affca495e | ||
|
|
9d8886a640 | ||
|
|
98cfec1b77 | ||
|
|
aed174953e | ||
|
|
f0feea8262 | ||
|
|
e2aeaf0a78 | ||
|
|
b92bb94875 | ||
|
|
c56db59353 | ||
|
|
a87c4e21d7 | ||
|
|
ed99a989d7 | ||
|
|
f9a4c9399a | ||
|
|
efb5c8ca9a | ||
|
|
0946dff3a0 | ||
|
|
d7d96b10e5 | ||
|
|
0c0b2d5cb3 | ||
|
|
450fbc9af3 | ||
|
|
469ee8fab8 | ||
|
|
6ccfcaf76e | ||
|
|
520e857874 | ||
|
|
012c7b4241 | ||
|
|
1d71cd5bf0 | ||
|
|
17d1a7f4ed | ||
|
|
0cd5c08c6b | ||
|
|
20ac503fe9 | ||
|
|
5f737c2c7c | ||
|
|
eb1a37be36 | ||
|
|
07c64b5432 | ||
|
|
ce981d1c49 | ||
|
|
32a9f2556e | ||
|
|
7f08675121 | ||
|
|
1dc3c13df2 | ||
|
|
6a6e86b325 | ||
|
|
d96277882a | ||
|
|
ecec917752 | ||
|
|
ea9c1a8ee1 | ||
|
|
cfbb09fb57 | ||
|
|
dc8aa1fb92 | ||
|
|
786cc8aa65 | ||
|
|
4a353e08e3 | ||
|
|
1371921586 | ||
|
|
a9a2a541c0 | ||
|
|
8998c07e0e | ||
|
|
ba01a89af1 | ||
|
|
eae07d62ad | ||
|
|
23903cf0c9 | ||
|
|
4d908fd855 | ||
|
|
bb0e67be4f | ||
|
|
0410c446fc | ||
|
|
18486e4772 |
34
.github/ISSUE_TEMPLATE/gns3-bug-report.md
vendored
Normal file
34
.github/ISSUE_TEMPLATE/gns3-bug-report.md
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: GNS3 bug report
|
||||
about: Create a report to help us fix a bug
|
||||
title: 'Short description of the bug'
|
||||
labels: Bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Before you start**
|
||||
Please open an issue only if you suspect there is a bug or any problem with GNS3. Go to https://gns3.com/community for any other questions or for requesting help with GNS3.
|
||||
|
||||
You may also post this issue directly on the GNS3 server repository if you know the bug comes from the server: https://github.com/GNS3/gns3-server/issues/new
|
||||
|
||||
**Describe the bug**
|
||||
Please provide a clear and detailed description of what the bug is.
|
||||
|
||||
**GNS3 version and operating system (please complete the following information):**
|
||||
- OS: [e.g. Windows, Linux or macOS]
|
||||
- GNS3 version [e.g. 2.1.14]
|
||||
- Any use of the GNS3 VM or remote server (ESXi, bare metal etc.)
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Screenshots or videos**
|
||||
If applicable, add screenshots (e.g. of the topology and/or error message) or links to videos to help explain the problem. This will help us a lot to quickly find the bug and fix it.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
10
.github/ISSUE_TEMPLATE/gns3-development.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/gns3-development.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: GNS3 development
|
||||
about: Any question or discussion regarding GNS3 development
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
25
.github/ISSUE_TEMPLATE/gns3-feature-request.md
vendored
Normal file
25
.github/ISSUE_TEMPLATE/gns3-feature-request.md
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: GNS3 feature request
|
||||
about: Suggest an idea for GNS3
|
||||
title: 'Short description of the feature request'
|
||||
labels: Enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Before you start**
|
||||
Please check if a similar feature request has already been submitted.
|
||||
|
||||
You may also post this issue directly on the GNS3 server repository if you know the feature request only applies to the server: https://github.com/GNS3/gns3-server/issues/new
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen. If applicable, please provide screenshots
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
116
CHANGELOG
116
CHANGELOG
@@ -1,5 +1,113 @@
|
||||
# Change Log
|
||||
|
||||
## 2.2.3 12/11/2019
|
||||
|
||||
* Fix issue when binding on 0.0.0.0. Fixes #2892
|
||||
* Allow double click on cloud with configured console to open session. Fixes #2894
|
||||
* Officially support Python 3.8. Ref https://github.com/GNS3/gns3-gui/issues/2895
|
||||
* Set psutil to version 5.6.3 in requirements.txt
|
||||
|
||||
## 2.2.2 04/11/2019
|
||||
|
||||
* Fix KeyError: 'spice+agent'. Fixes #2890
|
||||
* Fix wrong log.error() call when exporting file.
|
||||
* Revert "Explicitly cleanup the cache directory."
|
||||
* Fix "UnboundLocalError: local variable 'pywintypes' referenced before assignment"
|
||||
* Fix GUI uses only telnet console. Fixes #2885
|
||||
* Fix missing sys module in sudo.py Fixes #2886
|
||||
|
||||
## 2.2.1 01/11/2019
|
||||
|
||||
* Check if console_type is None.
|
||||
* Explicitly cleanup the cache directory.
|
||||
* Get Windows interface from registry if cannot load win32com module.
|
||||
* Ignore OSError returned by psutil when bringing console to front.
|
||||
* Catch error if NPF or NPCAP service cannot be detected. Ref https://github.com/GNS3/gns3-server/issues/1670
|
||||
* Better handling for reading synchronous JSON response from server. Ref #2874
|
||||
* Fix JSONDecodeError when getting server version. Fixes #2874
|
||||
* Fix FileNotFoundError exceptions when launching SPICE or VNC clients.
|
||||
* Fix UnboundLocalError local variable 'win32serviceutil' referenced before assignment
|
||||
* 'Fix' tab order in preferences dialog so it follows the layout
|
||||
* 'Fix' tab order in edit project dialog so it follows the layout
|
||||
* Use compatible shlex_quote to handle case where Windows needs double quotes around file names, not single quotes. Ref https://github.com/GNS3/gns3-gui/issues/2866
|
||||
* Use 0.0.0.0 by default for server host. Fixes https://github.com/GNS3/gns3-server/issues/1663
|
||||
* Catch IndexError when configuring port names. Fixes #2865
|
||||
|
||||
## 2.2.0 30/09/2019
|
||||
|
||||
* No changes
|
||||
|
||||
## 2.2.0rc5 09/09/2019
|
||||
|
||||
* Adjust size for setup dialog and remove question about running the wizard again. Ref #2846
|
||||
|
||||
## 2.2.0rc4 30/08/2019
|
||||
|
||||
* Fix issue when asking to run the setup wizard again. Ref #2846
|
||||
* Remove warning about VirtualBox not supporting nested virtualization. Ref https://github.com/GNS3/gns3-server/issues/1610
|
||||
* Ask user if they want to see the wizard again. Ref #2846
|
||||
|
||||
## 2.2.0rc3 12/08/2019
|
||||
|
||||
* Revert to jsonschema 2.6.0 due to packaging problem.
|
||||
|
||||
## 2.2.0rc2 10/08/2019
|
||||
|
||||
* Bump jsonschema to version 3.0.2
|
||||
* Fix "Unable to change Remote Main Server IP". Fixes #2823
|
||||
* Fix "AttributeError: 'QGraphicsTextItem' object has no attribute 'locked'". Fixes #2814
|
||||
* Fix a minor typo in the setup wizard
|
||||
|
||||
## 2.2.0b4 11/07/2019
|
||||
|
||||
* Fix issue preventing to open the QFileDialog in the correct directory.
|
||||
* Remove unused edit readme action. Fixes #2816
|
||||
* Remove deprecated Qemu parameter to run legacy ASA VMs. Fixes #2827
|
||||
* Upload images on remote controller. Fixes #2828
|
||||
* Preferences dialog: send API request only if connected to controller
|
||||
* Fix AttributeError: 'QGraphicsTextItem' object has no attribute 'locked'. Fixes #2814
|
||||
* Fix KeyError: 'chassis' when converting old IOS templates. Fixes #2813
|
||||
|
||||
## 2.2.0b3 15/06/2019
|
||||
|
||||
* Fix template migration issues from GUI to controller. Fixes https://github.com/GNS3/gns3-gui/issues/2803
|
||||
* %guest-cid% variable implementation for Qemu VMs. Fixes https://github.com/GNS3/gns3-gui/issues/2804
|
||||
* Increase timeout from 2 to 5 seconds for synchronous check. Ref #2805
|
||||
|
||||
## 2.2.0b2 29/05/2019
|
||||
|
||||
* Fix KeyError: 'endpoint' issue. Fixes #2802
|
||||
* Fix wrong aligment of symbols in saved/exported projects. Fixes #2800
|
||||
* Replace urllib.request by Qt implementation for local server synchronous check. Fixes #2793
|
||||
* Support snapshots for portable projects. Fixes https://github.com/GNS3/gns3-gui/issues/2792
|
||||
* Fix event notification problem for projects and how snapshots are restored.
|
||||
* Do not close the nodes dock widget when creating project.
|
||||
* Fix no scan for images on remote controller. Fixes #2799
|
||||
* Use QNetworkAccessManager to download custom appliance symbols.
|
||||
* Experimental auto upgrade should not be available for "frozen" app. Fixes #2797
|
||||
* Don't allow link labels to be moved for locked nodes. Fixes #2794
|
||||
* Catch more OSError/PermissionError when checking md5 on remote images. Fixes #2582
|
||||
* Fix exception when grid size is 0. Fixes #2790
|
||||
* Catch PermissionError when scanning local image directories. Fixes #2791
|
||||
|
||||
## 2.1.20 29/05/2019
|
||||
|
||||
* Fix KeyError: 'endpoint' issue. Fixes #2802
|
||||
|
||||
## 2.1.19 28/05/2019
|
||||
|
||||
* Fix wrong aligment of symbols in saved/exported projects. Fixes #2800
|
||||
* Replace urllib.request by Qt implementation for local server synchronous check. Fixes #2793
|
||||
* Set grid's minimum to 5. Fixes #2795
|
||||
|
||||
## 2.1.18 22/05/2019
|
||||
|
||||
* Fix error in HTTPConnection.request for Python3.6. Fixes #2793
|
||||
* Catch more OSError/PermissionError when checking md5 on remote images. Fixes #2582
|
||||
* Fix exception when grid size is 0. Fixes #2790
|
||||
* Catch PermissionError when scanning local image directories. Fixes #2791
|
||||
* Revert "Make sure the latest PyQt5 version 5.12.x is used on Windows." Ref #2778
|
||||
|
||||
## 2.2.0b1 21/05/2019
|
||||
|
||||
* Change behavior when an IOU license is verified. Fixes https://github.com/GNS3/gns3-server/issues/1555
|
||||
@@ -9,6 +117,10 @@
|
||||
* Add tooltip for symbol theme support in general preferences. Fixes #2770
|
||||
* Support for persistent docker volumes
|
||||
|
||||
## 2.1.17 17/05/2019
|
||||
|
||||
* No changes.
|
||||
|
||||
## 2.2.0a5 15/04/2019
|
||||
|
||||
* Revert "Drop old Qemu support (Windows and macOS) and legacy ASA support." Ref https://github.com/GNS3/gns3-server/issues/1579
|
||||
@@ -22,10 +134,6 @@
|
||||
* Add PuTTY 0.71 and mark GNS3 PuTTY as deprecated. Fixes #2758
|
||||
* Fix bug with IOS platform detection. Fixes #2760
|
||||
|
||||
## 2.1.17 17/05/2019
|
||||
|
||||
* No changes.
|
||||
|
||||
## 2.1.16 15/04/2019
|
||||
|
||||
* Do not make NPF or NPCAP service mandatory to start the local server on Windows.
|
||||
|
||||
@@ -54,8 +54,7 @@ class Controller(QtCore.QObject):
|
||||
self._error_dialog = None
|
||||
self._display_error = True
|
||||
self._projects = []
|
||||
self._controller_websocket = QtWebSockets.QWebSocket()
|
||||
self._project_websocket = QtWebSockets.QWebSocket()
|
||||
self._websocket = QtWebSockets.QWebSocket()
|
||||
|
||||
# If we do multiple call in order to download the same symbol we queue them
|
||||
self._static_asset_download_queue = {}
|
||||
@@ -247,12 +246,6 @@ class Controller(QtCore.QObject):
|
||||
if self._http_client:
|
||||
return self._http_client.createHTTPQuery(method, path, *args, **kwargs)
|
||||
|
||||
def getSynchronous(self, endpoint, timeout=2):
|
||||
return self._http_client.getSynchronous(endpoint, timeout)
|
||||
|
||||
def connectProjectWebSocket(self, path, *args):
|
||||
return self._http_client.connectWebSocket(self._project_websocket, path)
|
||||
|
||||
@staticmethod
|
||||
def instance():
|
||||
"""
|
||||
@@ -427,7 +420,7 @@ class Controller(QtCore.QObject):
|
||||
ignoreErrors=True)
|
||||
|
||||
else:
|
||||
self._notification_stream = self._http_client.connectWebSocket(self._controller_websocket, "/notifications/ws")
|
||||
self._notification_stream = self._http_client.connectWebSocket(self._websocket, "/notifications/ws")
|
||||
self._notification_stream.textMessageReceived.connect(self._websocket_event_received)
|
||||
self._notification_stream.error.connect(self._websocket_error)
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ class CrashReport:
|
||||
Report crash to a third party service
|
||||
"""
|
||||
|
||||
DSN = "https://01c9d795e546405f9bbc665bc3c3646d:c09e171479024df491d3f330d7a2b392@sentry.io/38506"
|
||||
DSN = "https://9090482e1e444838b80e5aa94596b9d8:65ab81b67c534e349550ae3766241cda@sentry.io/38506"
|
||||
if hasattr(sys, "frozen"):
|
||||
cacert = get_resource("cacert.pem")
|
||||
if cacert is not None and os.path.isfile(cacert):
|
||||
|
||||
@@ -197,7 +197,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
QtWidgets.QMessageBox.warning(self, "GNS3 VM", "The GNS3 VM is not available, please configure the GNS3 VM before adding a new appliance.")
|
||||
|
||||
elif self.page(page_id) == self.uiFilesWizardPage:
|
||||
if self._compute_id != "local":
|
||||
if Controller.instance().isRemote() or self._compute_id != "local":
|
||||
self._registry.getRemoteImageList(self._appliance.emulator(), self._compute_id)
|
||||
else:
|
||||
self.images_changed_signal.emit()
|
||||
@@ -419,7 +419,8 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
if img.location == "local":
|
||||
image["status"] = "Found locally"
|
||||
else:
|
||||
image["status"] = "Found on {}".format(self._compute_id)
|
||||
compute = ComputeManager.instance().getCompute(self._compute_id)
|
||||
image["status"] = "Found on {}".format(compute.name())
|
||||
image["md5sum"] = img.md5sum
|
||||
image["filesize"] = img.filesize
|
||||
image["path"] = img.path
|
||||
@@ -574,7 +575,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
if "qemu" in appliance_configuration:
|
||||
appliance_configuration["qemu"]["path"] = self.uiQemuListComboBox.currentData()
|
||||
|
||||
new_template = ApplianceToTemplate().new_template(appliance_configuration, self._compute_id, self._symbols)
|
||||
new_template = ApplianceToTemplate().new_template(appliance_configuration, self._compute_id, self._symbols, parent=self)
|
||||
TemplateManager.instance().createTemplate(Template(new_template), callback=self._templateCreatedCallback)
|
||||
return False
|
||||
|
||||
@@ -615,7 +616,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
return
|
||||
for image in appliance_configuration["images"]:
|
||||
if image["location"] == "local":
|
||||
if self._compute_id == "local" and image["path"].startswith(ImageManager.instance().getDirectory()):
|
||||
if not Controller.instance().isRemote() and self._compute_id == "local" and image["path"].startswith(ImageManager.instance().getDirectory()):
|
||||
log.debug("{} is already on the local server".format(image["path"]))
|
||||
return
|
||||
image = Image(self._appliance.emulator(), image["path"], filename=image["filename"])
|
||||
|
||||
@@ -44,6 +44,9 @@ class ConsoleCommandDialog(QtWidgets.QDialog, Ui_uiConsoleCommandDialog):
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
if console_type == "spice+agent":
|
||||
# special case for spice+agent, use the spice console type
|
||||
console_type = "spice"
|
||||
self._console_type = console_type
|
||||
self._current = current
|
||||
|
||||
@@ -63,7 +66,7 @@ class ConsoleCommandDialog(QtWidgets.QDialog, Ui_uiConsoleCommandDialog):
|
||||
elif self._console_type == "vnc":
|
||||
self._consoles = copy.copy(PRECONFIGURED_VNC_CONSOLE_COMMANDS)
|
||||
self._consoles.update(self._settings[self._console_type])
|
||||
elif self._console_type.startswith("spice"):
|
||||
elif self._console_type == "spice":
|
||||
self._consoles = copy.copy(PRECONFIGURED_SPICE_CONSOLE_COMMANDS)
|
||||
self._consoles.update(self._settings[self._console_type])
|
||||
|
||||
@@ -121,8 +124,8 @@ class ConsoleCommandDialog(QtWidgets.QDialog, Ui_uiConsoleCommandDialog):
|
||||
dialog = ConsoleCommandDialog(parent, console_type=console_type, current=current)
|
||||
dialog.show()
|
||||
if dialog.exec_():
|
||||
return (True, dialog.uiCommandPlainTextEdit.toPlainText().replace("\n", " "))
|
||||
return (False, None)
|
||||
return True, dialog.uiCommandPlainTextEdit.toPlainText().replace("\n", " ")
|
||||
return False, None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -128,7 +128,7 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
|
||||
|
||||
# Class name, changed signal
|
||||
widget_to_watch = {
|
||||
#QtWidgets.QLineEdit: "textChanged",
|
||||
QtWidgets.QLineEdit: "textChanged",
|
||||
QtWidgets.QPlainTextEdit: "textChanged",
|
||||
# QtWidgets.QTreeWidget: "itemChanged",
|
||||
QtWidgets.QComboBox: "currentIndexChanged",
|
||||
|
||||
@@ -119,8 +119,12 @@ class ExportProjectWizard(QtWidgets.QWizard, Ui_ExportProjectWizard):
|
||||
include_images = "yes"
|
||||
else:
|
||||
include_images = "no"
|
||||
if self.uiIncludeSnapshotsCheckBox.isChecked():
|
||||
include_snapshots = "yes"
|
||||
else:
|
||||
include_snapshots = "no"
|
||||
compression = self.uiCompressionComboBox.currentData()
|
||||
export_worker = ExportProjectWorker(self._project, self._path, include_images, compression)
|
||||
export_worker = ExportProjectWorker(self._project, self._path, include_images, include_snapshots, compression)
|
||||
progress_dialog = ProgressDialog(export_worker, "Exporting project", "Exporting portable project files...", "Cancel", parent=self, create_thread=False)
|
||||
progress_dialog.show()
|
||||
progress_dialog.exec_()
|
||||
|
||||
@@ -43,6 +43,7 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
self.adjustSize()
|
||||
|
||||
self._gns3_vm_settings = {
|
||||
"enable": True,
|
||||
@@ -83,10 +84,15 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
if address.protocol() in [QtNetwork.QAbstractSocket.IPv4Protocol, QtNetwork.QAbstractSocket.IPv6Protocol]:
|
||||
address_string = address.toString()
|
||||
if address_string.startswith("169.254") or address_string.startswith("fe80"):
|
||||
# ignore link-local addresses
|
||||
# ignore link-local addresses, could not use https://doc.qt.io/qt-5/qhostaddress.html#isLinkLocal
|
||||
# because it was introduced in Qt 5.11
|
||||
continue
|
||||
self.uiLocalServerHostComboBox.addItem(address_string, address_string)
|
||||
|
||||
self.uiLocalServerHostComboBox.addItem("localhost", "localhost") # local host
|
||||
self.uiLocalServerHostComboBox.addItem("::", "::") # all IPv6 addresses
|
||||
self.uiLocalServerHostComboBox.addItem("0.0.0.0", "0.0.0.0") # all IPv4 addresses
|
||||
|
||||
if sys.platform.startswith("darwin"):
|
||||
self.uiVMwareBannerButton.setIcon(QtGui.QIcon(":/images/vmware_fusion_banner.png"))
|
||||
else:
|
||||
@@ -141,7 +147,6 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
Slot to refresh the VirtualBox VMs list.
|
||||
"""
|
||||
|
||||
QtWidgets.QMessageBox.warning(self, "GNS3 VM on VirtualBox", "VirtualBox doesn't support nested virtualization, this means running Qemu based VM could be very slow")
|
||||
download_url = "https://github.com/GNS3/gns3-gui/releases/download/v{version}/GNS3.VM.VirtualBox.{version}.zip".format(version=__version__)
|
||||
self.uiGNS3VMDownloadLinkUrlLabel.setText('If you don\'t have the GNS3 Virtual Machine you can <a href="{download_url}">download it here</a>.<br>And import the VM in the virtualization software and hit refresh.'.format(download_url=download_url))
|
||||
self.uiVmwareRadioButton.setChecked(False)
|
||||
@@ -414,11 +419,8 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
settings["hide_setup_wizard"] = True
|
||||
else:
|
||||
local_server_settings = LocalServer.instance().localServerSettings()
|
||||
if local_server_settings["host"] is None:
|
||||
local_server_settings["host"] = DEFAULT_LOCAL_SERVER_HOST
|
||||
LocalServer.instance().updateLocalServerSettings(local_server_settings)
|
||||
settings["hide_setup_wizard"] = self.uiShowCheckBox.isChecked()
|
||||
|
||||
LocalServer.instance().updateLocalServerSettings(local_server_settings)
|
||||
settings["hide_setup_wizard"] = not self.uiShowCheckBox.isChecked()
|
||||
self.parentWidget().setSettings(settings)
|
||||
super().done(result)
|
||||
|
||||
|
||||
@@ -627,7 +627,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
if not self._adding_link:
|
||||
if isinstance(item, NodeItem) and item.node().initialized():
|
||||
item.setSelected(True)
|
||||
if item.node().status() == Node.stopped or item.node().isAlwaysOn():
|
||||
if item.node().status() == Node.stopped or item.node().consoleType() == "none":
|
||||
self.configureSlot()
|
||||
return
|
||||
else:
|
||||
@@ -1054,8 +1054,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
|
||||
# TightVNC has lack support of IPv6 host at this moment
|
||||
if "vncviewer" in node.consoleCommand() and ":" in node.consoleHost():
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self, "TightVNC", "TightVNC (vncviewer) may not start because of lack of IPv6 support.")
|
||||
QtWidgets.QMessageBox.warning(self, "TightVNC", "TightVNC (vncviewer) may not start because of lack of IPv6 support.")
|
||||
|
||||
try:
|
||||
node.openConsole(aux=aux)
|
||||
@@ -1116,24 +1115,20 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
Allow user to use a custom console for this VM
|
||||
"""
|
||||
|
||||
current_cmd = None
|
||||
console_type = "telnet"
|
||||
for item in self.scene().selectedItems():
|
||||
if isinstance(item, NodeItem) and item.node().console() is not None and item.node().initialized() and item.node().status() == Node.started:
|
||||
if isinstance(item, NodeItem) and item.node().console() is not None and item.node().initialized():
|
||||
if item.node().consoleType() not in ("telnet", "serial", "vnc", "spice", "spice+agent"):
|
||||
continue
|
||||
current_cmd = item.node().consoleCommand()
|
||||
console_type = item.node().consoleType()
|
||||
|
||||
(ok, cmd) = ConsoleCommandDialog.getCommand(self, console_type=console_type, current=current_cmd)
|
||||
if ok:
|
||||
for item in self.scene().selectedItems():
|
||||
if isinstance(item, NodeItem) and item.node().console() is not None and item.node().initialized() and item.node().status() == Node.started:
|
||||
node = item.node()
|
||||
if node.consoleType() not in ("telnet", "serial", "vnc", "spice", "spice+agent"):
|
||||
continue
|
||||
(ok, cmd) = ConsoleCommandDialog.getCommand(self, console_type=console_type, current=current_cmd)
|
||||
if ok:
|
||||
try:
|
||||
node.openConsole(command=cmd)
|
||||
if item.node().status() != Node.started:
|
||||
QtWidgets.QMessageBox.warning(self, "Console", "This node must be started before a console can be opened")
|
||||
continue
|
||||
item.node().openConsole(command=cmd)
|
||||
except (OSError, ValueError) as e:
|
||||
QtWidgets.QMessageBox.critical(self, "Console", "Cannot start console application: {}".format(e))
|
||||
|
||||
@@ -1602,10 +1597,12 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
def drawBackground(self, painter, rect):
|
||||
super().drawBackground(painter, rect)
|
||||
if self._main_window.uiShowGridAction.isChecked():
|
||||
grids = [(self.drawingGridSize(),QtGui.QColor(208, 208, 208)),
|
||||
(self.nodeGridSize(),QtGui.QColor(190, 190, 190))]
|
||||
grids = [(self.drawingGridSize(), QtGui.QColor(208, 208, 208)),
|
||||
(self.nodeGridSize(), QtGui.QColor(190, 190, 190))]
|
||||
painter.save()
|
||||
for (grid,colour) in grids:
|
||||
for (grid, colour) in grids:
|
||||
if not grid:
|
||||
continue
|
||||
painter.setPen(QtGui.QPen(colour))
|
||||
|
||||
left = int(rect.left()) - (int(rect.left()) % grid)
|
||||
|
||||
@@ -471,7 +471,7 @@ class HTTPClient(QtCore.QObject):
|
||||
host = self._getHostForQuery()
|
||||
request = websocket.request()
|
||||
ws_url = "ws://{host}:{port}{prefix}{path}".format(host=host, port=self._port, path=path, prefix=prefix)
|
||||
log.debug("Connectin to WebSocket endpoint: {}".format(ws_url))
|
||||
log.debug("Connecting to WebSocket endpoint: {}".format(ws_url))
|
||||
request.setUrl(QtCore.QUrl(ws_url))
|
||||
self._addAuth(request)
|
||||
websocket.open(request)
|
||||
@@ -727,47 +727,54 @@ class HTTPClient(QtCore.QObject):
|
||||
e = HttpBadRequest(body)
|
||||
raise e
|
||||
|
||||
def getSynchronous(self, endpoint, timeout=2):
|
||||
def getSynchronous(self, method, endpoint, prefix="/v2", timeout=5):
|
||||
"""
|
||||
Synchronous check if a server is running
|
||||
|
||||
:returns: Tuple (Status code, json of anwser). Status 0 is a non HTTP error
|
||||
:returns: Tuple (Status code, json of answer). Status 0 is a non HTTP error
|
||||
"""
|
||||
try:
|
||||
url = "{protocol}://{host}:{port}/v2/{endpoint}".format(protocol=self._protocol, host=self._host, port=self._port, endpoint=endpoint)
|
||||
|
||||
if self._user is not None and len(self._user) > 0:
|
||||
log.debug("Synchronous get {} with user '{}'".format(url, self._user))
|
||||
auth_handler = urllib.request.HTTPBasicAuthHandler()
|
||||
auth_handler.add_password(realm="GNS3 server",
|
||||
uri=url,
|
||||
user=self._user,
|
||||
passwd=self._password)
|
||||
opener = urllib.request.build_opener(auth_handler)
|
||||
urllib.request.install_opener(opener)
|
||||
else:
|
||||
log.debug("Synchronous get {} (no authentication)".format(url))
|
||||
response = urllib.request.urlopen(url, timeout=timeout)
|
||||
content_type = response.getheader("CONTENT-TYPE")
|
||||
if response.status == 200:
|
||||
if content_type == "application/json":
|
||||
content = response.read()
|
||||
host = self._getHostForQuery()
|
||||
|
||||
log.debug("{method} {protocol}://{host}:{port}{prefix}{endpoint}".format(method=method, protocol=self._protocol, host=host, port=self._port, prefix=prefix, endpoint=endpoint))
|
||||
if self._user:
|
||||
url = QtCore.QUrl("{protocol}://{user}@{host}:{port}{prefix}{endpoint}".format(protocol=self._protocol, user=self._user, host=host, port=self._port, prefix=prefix, endpoint=endpoint))
|
||||
else:
|
||||
url = QtCore.QUrl("{protocol}://{host}:{port}{prefix}{endpoint}".format(protocol=self._protocol, host=host, port=self._port, prefix=prefix, endpoint=endpoint))
|
||||
|
||||
request = self._request(url)
|
||||
request = self._addAuth(request)
|
||||
request.setRawHeader(b"User-Agent", "GNS3 QT Client v{version}".format(version=__version__).encode())
|
||||
|
||||
try:
|
||||
response = self._network_manager.sendCustomRequest(request, method.encode())
|
||||
except SystemError as e:
|
||||
log.error("Can't send query: {}".format(str(e)))
|
||||
return
|
||||
|
||||
loop = QtCore.QEventLoop()
|
||||
response.finished.connect(loop.quit)
|
||||
|
||||
if timeout is not None:
|
||||
QtCore.QTimer.singleShot(timeout * 1000, qpartial(self._timeoutSlot, response, timeout))
|
||||
|
||||
if not loop.isRunning():
|
||||
loop.exec_()
|
||||
|
||||
status = response.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)
|
||||
if response.error() != QtNetwork.QNetworkReply.NoError:
|
||||
log.debug("Error while connecting to local server {}".format(response.errorString()))
|
||||
else:
|
||||
content_type = response.header(QtNetwork.QNetworkRequest.ContentTypeHeader)
|
||||
if status == 200 and content_type == "application/json":
|
||||
content = bytes(response.readAll())
|
||||
try:
|
||||
json_data = json.loads(content.decode("utf-8"))
|
||||
return response.status, json_data
|
||||
else:
|
||||
return response.status, None
|
||||
except http.client.InvalidURL as e:
|
||||
log.warning("Invalid local server url: {}".format(e))
|
||||
return 0, None
|
||||
except urllib.error.URLError:
|
||||
# Connection refused. It's a normal behavior if server is not started
|
||||
return 0, None
|
||||
except urllib.error.HTTPError as e:
|
||||
log.debug("Error during get on {}:{}: {}".format(self.host(), self.port(), e))
|
||||
return e.code, None
|
||||
except (OSError, http.client.BadStatusLine, ValueError) as e:
|
||||
log.debug("Error during get on {}:{}: {}".format(self.host(), self.port(), e))
|
||||
return 0, None
|
||||
except (UnicodeEncodeError, ValueError) as e:
|
||||
log.warning("Could not read JSON data returned from {}: {}".format(url, e))
|
||||
else:
|
||||
return status, json_data
|
||||
return status, None
|
||||
|
||||
@classmethod
|
||||
def fromUrl(cls, url, network_manager=None, base_settings=None):
|
||||
|
||||
@@ -147,6 +147,7 @@ class EthernetLinkItem(LinkItem):
|
||||
self._source_port.setLabel(source_port_label)
|
||||
|
||||
if self._draw_port_labels:
|
||||
source_port_label.setFlag(source_port_label.ItemIsMovable, not self._source_item.locked())
|
||||
source_port_label.show()
|
||||
else:
|
||||
source_port_label.hide()
|
||||
@@ -189,6 +190,7 @@ class EthernetLinkItem(LinkItem):
|
||||
self._destination_port.setLabel(destination_port_label)
|
||||
|
||||
if self._draw_port_labels:
|
||||
destination_port_label.setFlag(destination_port_label.ItemIsMovable, not self._destination_item.locked())
|
||||
destination_port_label.show()
|
||||
else:
|
||||
destination_port_label.hide()
|
||||
|
||||
@@ -103,22 +103,11 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
|
||||
|
||||
from ..main_window import MainWindow
|
||||
self._main_window = MainWindow.instance()
|
||||
if self._main_window.uiSnapToGridAction.isChecked():
|
||||
self._snapToGrid()
|
||||
self._settings = self._main_window.uiGraphicsView.settings()
|
||||
|
||||
if node.initialized():
|
||||
self.createdSlot(node.id())
|
||||
|
||||
def _snapToGrid(self):
|
||||
|
||||
grid_size = self._main_window.uiGraphicsView.nodeGridSize()
|
||||
mid_x = self.boundingRect().width() / 2
|
||||
x = (grid_size * round((self.x() + mid_x) / grid_size)) - mid_x
|
||||
mid_y = self.boundingRect().height() / 2
|
||||
y = (grid_size * round((self.y() + mid_y) / grid_size)) - mid_y
|
||||
self.setPos(x, y)
|
||||
|
||||
def updateNode(self):
|
||||
"""
|
||||
Sync change to the node
|
||||
|
||||
@@ -136,6 +136,7 @@ class SerialLinkItem(LinkItem):
|
||||
self._source_port.setLabel(source_port_label)
|
||||
|
||||
if self._draw_port_labels:
|
||||
source_port_label.setFlag(source_port_label.ItemIsMovable, not self._source_item.locked())
|
||||
source_port_label.show()
|
||||
else:
|
||||
source_port_label.hide()
|
||||
@@ -167,6 +168,7 @@ class SerialLinkItem(LinkItem):
|
||||
self._destination_port.setLabel(destination_port_label)
|
||||
|
||||
if self._draw_port_labels:
|
||||
destination_port_label.setFlag(destination_port_label.ItemIsMovable, not self._destination_item.locked())
|
||||
destination_port_label.show()
|
||||
else:
|
||||
destination_port_label.hide()
|
||||
|
||||
@@ -31,12 +31,11 @@ import subprocess
|
||||
|
||||
|
||||
from gns3.qt import QtWidgets, QtCore, qslot
|
||||
from gns3.settings import LOCAL_SERVER_SETTINGS
|
||||
from gns3.settings import LOCAL_SERVER_SETTINGS, DEFAULT_LOCAL_SERVER_HOST
|
||||
from gns3.local_config import LocalConfig
|
||||
from gns3.local_server_config import LocalServerConfig
|
||||
from gns3.utils.wait_for_connection_worker import WaitForConnectionWorker
|
||||
from gns3.utils.progress_dialog import ProgressDialog
|
||||
from gns3.utils.http import getSynchronous
|
||||
from gns3.utils.sudo import sudo
|
||||
from gns3.http_client import HTTPClient
|
||||
from gns3.controller import Controller
|
||||
@@ -131,6 +130,7 @@ class LocalServer(QtCore.QObject):
|
||||
import win32serviceutil
|
||||
except ImportError as e:
|
||||
log.error("Could not check if the {} service is running: {}".format(service_name, e))
|
||||
return
|
||||
|
||||
try:
|
||||
if win32serviceutil.QueryServiceStatus(service_name, None)[1] != win32service.SERVICE_RUNNING:
|
||||
@@ -246,6 +246,9 @@ class LocalServer(QtCore.QObject):
|
||||
"""
|
||||
Update the local server settings. Keep the key not in new_settings
|
||||
"""
|
||||
|
||||
if "host" in new_settings and new_settings["host"] is None:
|
||||
new_settings["host"] = DEFAULT_LOCAL_SERVER_HOST
|
||||
old_settings = copy.copy(self._settings)
|
||||
if not self._settings:
|
||||
self._settings = new_settings
|
||||
@@ -369,9 +372,13 @@ class LocalServer(QtCore.QObject):
|
||||
|
||||
self._checkUbridgePermissions()
|
||||
|
||||
if sys.platform.startswith('win'):
|
||||
if not self._checkWindowsService("npf") and not self._checkWindowsService("npcap"):
|
||||
log.warning("The NPF or NPCAP service is not installed, please install Winpcap or Npcap and reboot.")
|
||||
if sys.platform.startswith("win"):
|
||||
import pywintypes
|
||||
try:
|
||||
if not self._checkWindowsService("npf") and not self._checkWindowsService("npcap"):
|
||||
log.warning("The NPF or NPCAP service is not installed, please install Winpcap or Npcap and reboot.")
|
||||
except pywintypes.error as e:
|
||||
log.warning("Could not check if the NPF or Npcap service is running: {}".format(e.strerror))
|
||||
|
||||
self._port = self._settings["port"]
|
||||
# check the local server path
|
||||
@@ -514,9 +521,7 @@ class LocalServer(QtCore.QObject):
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
status, json_data = getSynchronous(self._settings["protocol"], self._settings["host"], self._port, "version",
|
||||
timeout=2, user=self._settings["user"], password=self._settings["password"])
|
||||
|
||||
status, json_data = HTTPClient(self._settings).getSynchronous("GET", "/version")
|
||||
if status == 401: # Auth issue that need to be solved later
|
||||
return True
|
||||
elif json_data is None:
|
||||
|
||||
@@ -190,7 +190,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.uiSnapshotAction,
|
||||
self.uiEditProjectAction,
|
||||
self.uiDeleteProjectAction,
|
||||
self.uiImportExportConfigsAction
|
||||
self.uiImportExportConfigsAction,
|
||||
self.uiLockAllAction
|
||||
]
|
||||
|
||||
# This widgets are not enabled if it's a remote controller (no access to the local file system)
|
||||
@@ -259,7 +260,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.uiDrawRectangleAction.triggered.connect(self._drawRectangleActionSlot)
|
||||
self.uiDrawEllipseAction.triggered.connect(self._drawEllipseActionSlot)
|
||||
self.uiDrawLineAction.triggered.connect(self._drawLineActionSlot)
|
||||
self.uiEditReadmeAction.triggered.connect(self._editReadmeActionSlot)
|
||||
|
||||
# help menu connections
|
||||
self.uiOnlineHelpAction.triggered.connect(self._onlineHelpActionSlot)
|
||||
@@ -363,15 +363,16 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
:return: None
|
||||
"""
|
||||
|
||||
for item in self.uiGraphicsView.items():
|
||||
if not isinstance(item, LinkItem) and not isinstance(item, LabelItem) and not isinstance(item, SvgIconItem):
|
||||
if self.uiLockAllAction.isChecked() and not item.locked():
|
||||
item.setLocked(True)
|
||||
elif not self.uiLockAllAction.isChecked() and item.locked():
|
||||
item.setLocked(False)
|
||||
if item.parentItem() is None:
|
||||
item.updateNode()
|
||||
item.update()
|
||||
if self.uiGraphicsView.isEnabled():
|
||||
for item in self.uiGraphicsView.items():
|
||||
if not isinstance(item, LinkItem) and not isinstance(item, LabelItem) and not isinstance(item, SvgIconItem):
|
||||
if self.uiLockAllAction.isChecked() and not item.locked():
|
||||
item.setLocked(True)
|
||||
elif not self.uiLockAllAction.isChecked() and item.locked():
|
||||
item.setLocked(False)
|
||||
if item.parentItem() is None:
|
||||
item.updateNode()
|
||||
item.update()
|
||||
|
||||
def analyticsClient(self):
|
||||
"""
|
||||
@@ -392,9 +393,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self._project_dialog = ProjectDialog(self)
|
||||
self._project_dialog.show()
|
||||
create_new_project = self._project_dialog.exec_()
|
||||
# Close the device dock so it repopulates. Done in case switching between cloud and local.
|
||||
self.uiNodesDockWidget.setVisible(False)
|
||||
self.uiNodesDockWidget.setWindowTitle("")
|
||||
|
||||
if create_new_project:
|
||||
Topology.instance().createLoadProject(self._project_dialog.getProjectSettings())
|
||||
@@ -436,7 +434,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self._newProjectActionSlot()
|
||||
else:
|
||||
directory = self._project_dir
|
||||
if self._project_dir and not os.path.exists(self._project_dir):
|
||||
if self._project_dir is None or not os.path.exists(self._project_dir):
|
||||
directory = Topology.instance().projectsDirPath()
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open project", directory,
|
||||
"All files (*.*);;GNS3 Project (*.gns3);;GNS3 Portable Project (*.gns3project *.gns3p);;NET files (*.net)",
|
||||
@@ -1066,12 +1064,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self._settings["preferences_dialog_geometry"] = bytes(dialog.saveGeometry().toBase64()).decode()
|
||||
self.setSettings(self._settings)
|
||||
|
||||
def _editReadmeActionSlot(self):
|
||||
"""
|
||||
Slot to edit the README file
|
||||
"""
|
||||
Topology.instance().editReadme()
|
||||
|
||||
def resizeEvent(self, event):
|
||||
self._notif_dialog.resize()
|
||||
super().resizeEvent(event)
|
||||
|
||||
@@ -130,7 +130,7 @@ class Dynamips(Module):
|
||||
for router in self._settings.get("routers"):
|
||||
router_settings = IOS_ROUTER_SETTINGS.copy()
|
||||
router_settings.update(router)
|
||||
if not router_settings.get("chassis"):
|
||||
if router_settings.get("chassis"):
|
||||
del router_settings["chassis"]
|
||||
templates.append(Template(router_settings))
|
||||
TemplateManager.instance().updateList(templates)
|
||||
|
||||
@@ -98,7 +98,10 @@ class IOUPreferencesPage(QtWidgets.QWidget, Ui_IOUPreferencesPageWidget):
|
||||
:param settings: IOU settings
|
||||
"""
|
||||
|
||||
self.IOULicenceTextEdit.setPlainText(settings["iourc_content"])
|
||||
if settings["iourc_content"]:
|
||||
self.IOULicenceTextEdit.blockSignals(True)
|
||||
self.IOULicenceTextEdit.setPlainText(settings["iourc_content"])
|
||||
self.IOULicenceTextEdit.blockSignals(False)
|
||||
self.uiLicensecheckBox.setChecked(settings["license_check"])
|
||||
|
||||
def loadPreferences(self):
|
||||
@@ -106,7 +109,10 @@ class IOUPreferencesPage(QtWidgets.QWidget, Ui_IOUPreferencesPageWidget):
|
||||
Loads IOU preferences.
|
||||
"""
|
||||
|
||||
Controller.instance().get("/iou_license", self._getSettingsCallback)
|
||||
if Controller.instance().connected():
|
||||
Controller.instance().get("/iou_license", self._getSettingsCallback)
|
||||
else:
|
||||
log.error("Cannot load the IOU license in the preferences dialog: not connected to the controller")
|
||||
|
||||
@qslot
|
||||
def _getSettingsCallback(self, result, error=False, **kwargs):
|
||||
@@ -115,7 +121,7 @@ class IOUPreferencesPage(QtWidgets.QWidget, Ui_IOUPreferencesPageWidget):
|
||||
return
|
||||
if error:
|
||||
if "message" in result:
|
||||
log.error("Error while getting settings : {}".format(result["message"]))
|
||||
log.error("Error while getting the IOU license information: {}".format(result["message"]))
|
||||
return
|
||||
self._old_settings = copy.copy(result)
|
||||
self._populateWidgets(result)
|
||||
|
||||
@@ -163,7 +163,7 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
|
||||
settings["initrd"] = self.uiInitrdImageLineEdit.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"
|
||||
settings["options"] = "-no-kvm -icount auto"
|
||||
if not sys.platform.startswith("darwin"):
|
||||
settings["cpu_throttling"] = 80 # limit to 80% CPU usage
|
||||
settings["process_priority"] = "low"
|
||||
|
||||
@@ -391,7 +391,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
|
||||
try:
|
||||
ports = StandardPortNameFactory(adapters, first_port_name, port_name_format, port_segment_size)
|
||||
except (ValueError, KeyError):
|
||||
except (IndexError, ValueError, KeyError):
|
||||
QtWidgets.QMessageBox.critical(self, "Invalid format", "Invalid port name format")
|
||||
return
|
||||
|
||||
|
||||
@@ -860,6 +860,8 @@
|
||||
<li>%vm-id% =VM ID</li>
|
||||
<li>%project-id% = project ID</li>
|
||||
<li>%project-path% = project path</li>
|
||||
<li>%console-port% = console port number</li>
|
||||
<li>%guest-cid% = unique ID from 3 to 65535</li>
|
||||
</ul>
|
||||
</body></html></string>
|
||||
</property>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/qemu/ui/qemu_vm_configuration_page.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.11.3
|
||||
# Created by: PyQt5 UI code generator 5.9
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@@ -541,6 +541,8 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
"<li>%vm-id% =VM ID</li>\n"
|
||||
"<li>%project-id% = project ID</li>\n"
|
||||
"<li>%project-path% = project path</li>\n"
|
||||
"<li>%console-port% = console port number</li>\n"
|
||||
"<li>%guest-cid% = unique ID from 3 to 65535</li>\n"
|
||||
"</ul>\n"
|
||||
"</body></html>"))
|
||||
self.uiBaseVMCheckBox.setText(_translate("QemuVMConfigPageWidget", "Use as a linked base VM"))
|
||||
|
||||
@@ -99,7 +99,7 @@ class VirtualBoxVMConfigurationPage(QtWidgets.QWidget, Ui_virtualBoxVMConfigPage
|
||||
|
||||
try:
|
||||
ports = StandardPortNameFactory(adapters, first_port_name, port_name_format, port_segment_size)
|
||||
except (ValueError, KeyError):
|
||||
except (IndexError, ValueError, KeyError):
|
||||
QtWidgets.QMessageBox.critical(self, "Invalid format", "Invalid port name format")
|
||||
return
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ class VMwareVMConfigurationPage(QtWidgets.QWidget, Ui_VMwareVMConfigPageWidget):
|
||||
|
||||
try:
|
||||
ports = StandardPortNameFactory(adapters, first_port_name, port_name_format, port_segment_size)
|
||||
except (ValueError, KeyError):
|
||||
except (IndexError, ValueError, KeyError):
|
||||
QtWidgets.QMessageBox.critical(self, "Invalid format", "Invalid port name format")
|
||||
return
|
||||
|
||||
|
||||
14
gns3/node.py
14
gns3/node.py
@@ -628,12 +628,12 @@ class Node(BaseNode):
|
||||
from .main_window import MainWindow
|
||||
general_settings = MainWindow.instance().settings()
|
||||
|
||||
if console_type != "telnet":
|
||||
if not console_type:
|
||||
console_type = self.consoleType()
|
||||
if console_type == "vnc":
|
||||
return general_settings["vnc_console_command"]
|
||||
if console_type.startswith("spice"):
|
||||
return general_settings["spice_console_command"]
|
||||
if console_type == "vnc":
|
||||
return general_settings["vnc_console_command"]
|
||||
elif console_type.startswith("spice"):
|
||||
return general_settings["spice_console_command"]
|
||||
return general_settings["telnet_console_command"]
|
||||
|
||||
def consoleType(self):
|
||||
@@ -641,7 +641,7 @@ class Node(BaseNode):
|
||||
Get the console type (serial, telnet or VNC)
|
||||
"""
|
||||
|
||||
console_type = "telnet"
|
||||
console_type = "none"
|
||||
if "console_type" in self.settings():
|
||||
return self.settings()["console_type"]
|
||||
return console_type
|
||||
@@ -765,7 +765,7 @@ class Node(BaseNode):
|
||||
with open(context["path"], "wb+") as f:
|
||||
f.write(raw_body)
|
||||
except OSError as e:
|
||||
log.erro("Can't write %s: %s", context["path"], str(e))
|
||||
log.error("Cannot export file '{}': {}".format(context["path"], e))
|
||||
|
||||
def exportConfigsToDirectory(self, directory):
|
||||
"""
|
||||
|
||||
@@ -72,7 +72,11 @@ class GNS3VMPreferencesPage(QtWidgets.QWidget, Ui_GNS3VMPreferencesPageWidget):
|
||||
"""
|
||||
Loads the preference from controller.
|
||||
"""
|
||||
Controller.instance().get("/gns3vm", self._getSettingsCallback)
|
||||
|
||||
if Controller.instance().connected():
|
||||
Controller.instance().get("/gns3vm", self._getSettingsCallback)
|
||||
else:
|
||||
log.error("Cannot load the GNS3 VM settings in the preferences dialog: not connected to the controller")
|
||||
|
||||
@qslot
|
||||
def _getSettingsCallback(self, result, error=False, **kwargs):
|
||||
@@ -81,7 +85,7 @@ class GNS3VMPreferencesPage(QtWidgets.QWidget, Ui_GNS3VMPreferencesPageWidget):
|
||||
return
|
||||
if error:
|
||||
if "message" in result:
|
||||
log.error("Error while getting settings : {}".format(result["message"]))
|
||||
log.error("Error while getting the GNS3 VM settings : {}".format(result["message"]))
|
||||
return
|
||||
self._old_settings = copy.copy(result)
|
||||
self._settings = result
|
||||
|
||||
@@ -67,6 +67,7 @@ class ServerPreferencesPage(QtWidgets.QWidget, Ui_ServerPreferencesPageWidget):
|
||||
# ignore link-local addresses
|
||||
continue
|
||||
self.uiLocalServerHostComboBox.addItem(address_string, address_string)
|
||||
self.uiLocalServerHostComboBox.addItem("localhost", "localhost") # local host
|
||||
self.uiLocalServerHostComboBox.addItem("::", "::") # all IPv6 addresses
|
||||
self.uiLocalServerHostComboBox.addItem("0.0.0.0", "0.0.0.0") # all IPv4 addresses
|
||||
|
||||
|
||||
@@ -17,14 +17,12 @@
|
||||
|
||||
import os
|
||||
import json
|
||||
from .qt import QtCore, qpartial, QtWidgets, QtNetwork, qslot
|
||||
from .qt import QtCore, qpartial, QtNetwork, QtWebSockets, qslot
|
||||
|
||||
from gns3.controller import Controller
|
||||
from gns3.compute_manager import ComputeManager
|
||||
from gns3.topology import Topology
|
||||
from gns3.local_config import LocalConfig
|
||||
from gns3.settings import GRAPHICS_VIEW_SETTINGS
|
||||
from gns3.template_manager import TemplateManager
|
||||
from gns3.utils import parse_version
|
||||
|
||||
import logging
|
||||
@@ -49,7 +47,6 @@ class Project(QtCore.QObject):
|
||||
# Called when project is fully loaded
|
||||
project_loaded_signal = QtCore.Signal()
|
||||
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self._id = None
|
||||
@@ -84,6 +81,7 @@ class Project(QtCore.QObject):
|
||||
# Due to bug in Qt on some version we need a dedicated network manager
|
||||
self._notification_network_manager = QtNetwork.QNetworkAccessManager()
|
||||
self._notification_stream = None
|
||||
self._websocket = QtWebSockets.QWebSocket()
|
||||
|
||||
super().__init__()
|
||||
|
||||
@@ -485,7 +483,8 @@ class Project(QtCore.QObject):
|
||||
if self._closed:
|
||||
self._closed = False
|
||||
self._closing = False
|
||||
self._startListenNotifications()
|
||||
if not self._notification_stream:
|
||||
self._startListenNotifications()
|
||||
|
||||
self.project_updated_signal.emit()
|
||||
self.project_loaded_signal.emit()
|
||||
@@ -537,7 +536,8 @@ class Project(QtCore.QObject):
|
||||
if self._closed:
|
||||
self._closed = False
|
||||
self._closing = False
|
||||
self._startListenNotifications()
|
||||
if not self._notification_stream:
|
||||
self._startListenNotifications()
|
||||
self.project_updated_signal.emit()
|
||||
|
||||
self.get("/nodes", self._listNodesCallback)
|
||||
@@ -609,7 +609,7 @@ class Project(QtCore.QObject):
|
||||
log.debug("Stop listening for notifications from project %s", self._id)
|
||||
stream = self._notification_stream
|
||||
self._notification_stream = None
|
||||
stream.abort()
|
||||
stream.close()
|
||||
|
||||
def _startListenNotifications(self):
|
||||
if not Controller.instance().connected():
|
||||
@@ -627,7 +627,7 @@ class Project(QtCore.QObject):
|
||||
|
||||
else:
|
||||
path = "/projects/{project_id}/notifications/ws".format(project_id=self._id)
|
||||
self._notification_stream = Controller.instance().connectProjectWebSocket(path)
|
||||
self._notification_stream = Controller.instance().httpClient().connectWebSocket(self._websocket, path)
|
||||
self._notification_stream.textMessageReceived.connect(self._websocket_event_received)
|
||||
self._notification_stream.error.connect(self._websocket_error)
|
||||
|
||||
@@ -700,7 +700,7 @@ class Project(QtCore.QObject):
|
||||
elif result["action"] == "project.updated":
|
||||
self._projectUpdatedCallback(result["event"])
|
||||
elif result["action"] == "snapshot.restored":
|
||||
Topology.instance().createLoadProject({"project_id": result["event"]["project_id"]})
|
||||
Topology.instance().restoreSnapshot(result["event"]["project_id"])
|
||||
elif result["action"] == "log.error":
|
||||
log.error(result["event"]["message"])
|
||||
elif result["action"] == "log.warning":
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import urllib.request
|
||||
import shutil
|
||||
from ssl import CertificateError
|
||||
|
||||
from ..qt import QtCore, QtWidgets, QtNetwork
|
||||
from ..controller import Controller
|
||||
from .config import Config, ConfigException
|
||||
|
||||
@@ -33,7 +33,7 @@ class ApplianceToTemplate:
|
||||
Appliance installation.
|
||||
"""
|
||||
|
||||
def new_template(self, appliance_config, server, controller_symbols=None):
|
||||
def new_template(self, appliance_config, server, controller_symbols=None, parent=None):
|
||||
"""
|
||||
Creates a new template from an appliance.
|
||||
|
||||
@@ -41,6 +41,7 @@ class ApplianceToTemplate:
|
||||
:param server
|
||||
"""
|
||||
|
||||
self._parent = parent
|
||||
new_template = {
|
||||
"compute_id": server,
|
||||
"name": appliance_config["name"]
|
||||
@@ -195,7 +196,7 @@ class ApplianceToTemplate:
|
||||
|
||||
url = "https://raw.githubusercontent.com/GNS3/gns3-registry/master/symbols/{}".format(symbol_id)
|
||||
try:
|
||||
urllib.request.urlretrieve(url, path)
|
||||
self._downloadApplianceSymbol(url, path)
|
||||
controller = Controller.instance()
|
||||
controller.clearStaticCache()
|
||||
if controller.isRemote():
|
||||
@@ -203,3 +204,31 @@ class ApplianceToTemplate:
|
||||
return os.path.basename(path)
|
||||
except (OSError, CertificateError):
|
||||
return None
|
||||
|
||||
def _downloadApplianceSymbol(self, url, path, timeout=30):
|
||||
"""
|
||||
Download an appliance symbol in a synchronous way.
|
||||
"""
|
||||
|
||||
network_manager = QtNetwork.QNetworkAccessManager()
|
||||
request = QtNetwork.QNetworkRequest(QtCore.QUrl(url))
|
||||
request.setRawHeader(b'User-Agent', b'GNS3 symbol downloader')
|
||||
reply = network_manager.get(request)
|
||||
progress_dialog = QtWidgets.QProgressDialog("Downloading '{}' appliance symbol...".format(os.path.basename(path)), "Cancel", 0, 0, self._parent)
|
||||
progress_dialog.setMinimumDuration(0)
|
||||
reply.finished.connect(progress_dialog.close)
|
||||
QtCore.QTimer.singleShot(timeout * 1000, progress_dialog.close)
|
||||
log.debug("Downloading appliance symbol from '{}'".format(url))
|
||||
progress_dialog.show()
|
||||
progress_dialog.exec_()
|
||||
status = reply.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)
|
||||
if reply.error() == QtNetwork.QNetworkReply.NoError and status == 200:
|
||||
try:
|
||||
with open(path, 'wb+') as f:
|
||||
f.write(reply.readAll())
|
||||
except OSError as e:
|
||||
log.debug("Error while saving appliance symbol to '{}': {}".format(path, e))
|
||||
raise
|
||||
log.debug("Appliance symbol downloaded and saved to '{}'".format(path))
|
||||
else:
|
||||
log.warning("Error when downloading appliance symbol from '{}': {}".format(url, reply.errorString()))
|
||||
|
||||
@@ -114,16 +114,16 @@ class Image:
|
||||
try:
|
||||
if not os.path.isfile(self._path):
|
||||
return None
|
||||
m = hashlib.md5()
|
||||
with open(self._path, "rb") as f:
|
||||
while True:
|
||||
buf = f.read(4096)
|
||||
if not buf:
|
||||
break
|
||||
m.update(buf)
|
||||
except (OSError, PermissionError) as e:
|
||||
log.debug("Cannot access '{}': {}".format(self._path, e))
|
||||
return None
|
||||
m = hashlib.md5()
|
||||
with open(self._path, "rb") as f:
|
||||
while True:
|
||||
buf = f.read(4096)
|
||||
if not buf:
|
||||
break
|
||||
m.update(buf)
|
||||
self._md5sum = m.hexdigest()
|
||||
Image._cache[self._path] = self._md5sum
|
||||
return self._md5sum
|
||||
|
||||
@@ -89,10 +89,10 @@ class Registry(QtCore.QObject):
|
||||
for directory in self._images_dirs:
|
||||
log.debug("Search image {} (MD5={} SIZE={}) in '{}'".format(filename, md5sum, size, directory))
|
||||
if os.path.exists(directory):
|
||||
for file in os.listdir(directory):
|
||||
if not file.endswith(".md5sum") and not file.startswith("."):
|
||||
path = os.path.join(directory, file)
|
||||
try:
|
||||
try:
|
||||
for file in os.listdir(directory):
|
||||
if not file.endswith(".md5sum") and not file.startswith("."):
|
||||
path = os.path.join(directory, file)
|
||||
if os.path.isfile(path):
|
||||
if md5sum is None or strict_md5_check is False:
|
||||
if filename == os.path.basename(path):
|
||||
@@ -106,7 +106,7 @@ class Registry(QtCore.QObject):
|
||||
if image.md5sum == md5sum:
|
||||
log.debug("Found image {} (MD5={}) in {}".format(filename, md5sum, image.path))
|
||||
return image
|
||||
except (OSError, PermissionError) as e:
|
||||
log.error("Cannot scan {}: {}".format(path, e))
|
||||
except (OSError, PermissionError) as e:
|
||||
log.error("Cannot scan {}: {}".format(path, e))
|
||||
|
||||
return None
|
||||
|
||||
@@ -40,7 +40,7 @@ DEFAULT_CONFIGS_PATH = os.path.normpath(os.path.expanduser("~/GNS3/configs"))
|
||||
# Default appliances location
|
||||
DEFAULT_APPLIANCES_PATH = os.path.normpath(os.path.expanduser("~/GNS3/appliances"))
|
||||
|
||||
DEFAULT_LOCAL_SERVER_HOST = "127.0.0.1"
|
||||
DEFAULT_LOCAL_SERVER_HOST = "localhost"
|
||||
DEFAULT_LOCAL_SERVER_PORT = 3080
|
||||
DEFAULT_DELAY_CONSOLE_ALL = 500
|
||||
|
||||
@@ -330,7 +330,7 @@ GRAPHICS_VIEW_SETTINGS = {
|
||||
LOCAL_SERVER_SETTINGS = {
|
||||
"path": "gns3server",
|
||||
"ubridge_path": "ubridge",
|
||||
"host": None,
|
||||
"host": "localhost",
|
||||
"port": DEFAULT_LOCAL_SERVER_PORT,
|
||||
"images_path": DEFAULT_IMAGES_PATH,
|
||||
"projects_path": DEFAULT_PROJECTS_PATH,
|
||||
|
||||
@@ -38,7 +38,7 @@ def spiceConsole(host, port, command):
|
||||
"""
|
||||
|
||||
if len(command.strip(' ')) == 0:
|
||||
log.warning('SPICE client is not configured')
|
||||
log.error("SPICE client is not configured")
|
||||
return
|
||||
|
||||
# ipv6 support
|
||||
@@ -50,7 +50,7 @@ def spiceConsole(host, port, command):
|
||||
command = command.replace("%p", str(port))
|
||||
|
||||
try:
|
||||
log.info('starting SPICE program "{}"'.format(command))
|
||||
log.debug('starting SPICE program "{}"'.format(command))
|
||||
if sys.platform.startswith("win"):
|
||||
# use the string on Windows
|
||||
subprocess.Popen(command)
|
||||
@@ -59,5 +59,4 @@ def spiceConsole(host, port, command):
|
||||
args = shlex.split(command)
|
||||
subprocess.Popen(args, env=os.environ)
|
||||
except (OSError, ValueError, subprocess.SubprocessError) as e:
|
||||
log.warning('could not start SPICE program "{}": {}'.format(command, e))
|
||||
raise
|
||||
log.error("Could not start SPICE program with command '{}': {}".format(command, e))
|
||||
|
||||
@@ -76,7 +76,6 @@ class Style:
|
||||
self._mw.uiDrawRectangleAction.setIcon(self._getStyleIcon(":/icons/rectangle.svg", ":/icons/rectangle-hover.svg"))
|
||||
self._mw.uiDrawEllipseAction.setIcon(self._getStyleIcon(":/icons/ellipse.svg", ":/icons/ellipse-hover.svg"))
|
||||
self._mw.uiDrawLineAction.setIcon(QtGui.QIcon(":/icons/vertically.svg"))
|
||||
self._mw.uiEditReadmeAction.setIcon(QtGui.QIcon(":/icons/edit.svg"))
|
||||
self._mw.uiOnlineHelpAction.setIcon(QtGui.QIcon(":/icons/help.svg"))
|
||||
self._mw.uiBrowseRoutersAction.setIcon(self._getStyleIcon(":/icons/router.png", ":/icons/router-hover.png"))
|
||||
self._mw.uiBrowseSwitchesAction.setIcon(self._getStyleIcon(":/icons/switch.png", ":/icons/switch-hover.png"))
|
||||
@@ -128,7 +127,6 @@ class Style:
|
||||
self._mw.uiDrawRectangleAction.setIcon(self._getStyleIcon(":/classic_icons/rectangle.svg", ":/classic_icons/rectangle-hover.svg"))
|
||||
self._mw.uiDrawEllipseAction.setIcon(self._getStyleIcon(":/classic_icons/ellipse.svg", ":/classic_icons/ellipse-hover.svg"))
|
||||
self._mw.uiDrawLineAction.setIcon(self._getStyleIcon(":/classic_icons/line.svg", ":/classic_icons/line-hover.svg"))
|
||||
self._mw.uiEditReadmeAction.setIcon(self._getStyleIcon(":/classic_icons/edit.svg", ":/classic_icons/edit-hover.svg"))
|
||||
self._mw.uiOnlineHelpAction.setIcon(self._getStyleIcon(":/classic_icons/help.svg", ":/classic_icons/help-hover.svg"))
|
||||
self._mw.uiBrowseRoutersAction.setIcon(self._getStyleIcon(":/classic_icons/router.svg", ":/classic_icons/router-hover.svg"))
|
||||
self._mw.uiBrowseSwitchesAction.setIcon(self._getStyleIcon(":/classic_icons/switch.svg", ":/classic_icons/switch-hover.svg"))
|
||||
@@ -190,7 +188,6 @@ class Style:
|
||||
self._mw.uiDrawRectangleAction.setIcon(self._getStyleIcon(":/charcoal_icons/rectangle.svg", ":/charcoal_icons/rectangle-hover.svg"))
|
||||
self._mw.uiDrawEllipseAction.setIcon(self._getStyleIcon(":/charcoal_icons/ellipse.svg", ":/charcoal_icons/ellipse-hover.svg"))
|
||||
self._mw.uiDrawLineAction.setIcon(self._getStyleIcon(":/charcoal_icons/line.svg", ":/charcoal_icons/line-hover.svg"))
|
||||
self._mw.uiEditReadmeAction.setIcon(self._getStyleIcon(":/charcoal_icons/edit.svg", ":/charcoal_icons/edit-hover.svg"))
|
||||
self._mw.uiOnlineHelpAction.setIcon(self._getStyleIcon(":/charcoal_icons/help.svg", ":/charcoal_icons/help-hover.svg"))
|
||||
self._mw.uiBrowseRoutersAction.setIcon(self._getStyleIcon(":/charcoal_icons/router.svg", ":/charcoal_icons/router-hover.svg"))
|
||||
self._mw.uiBrowseSwitchesAction.setIcon(self._getStyleIcon(":/charcoal_icons/switch.svg", ":/charcoal_icons/switch-hover.svg"))
|
||||
|
||||
@@ -30,6 +30,10 @@ class Template:
|
||||
settings["template_id"] = str(uuid.uuid4())
|
||||
self._settings = copy.deepcopy(settings)
|
||||
|
||||
# The "appliance_id" setting has been replaced by "template_id" setting in version 2.2
|
||||
if "appliance_id" in self._settings:
|
||||
self._settings["template_id"] = self._settings.pop("appliance_id")
|
||||
|
||||
# The "node_type" setting has been replaced by "template_type" setting in version 2.2
|
||||
if "node_type" in self._settings:
|
||||
self._settings["template_type"] = self._settings.pop("node_type")
|
||||
@@ -38,6 +42,11 @@ class Template:
|
||||
if "server" in self._settings:
|
||||
self._settings["compute_id"] = self._settings.pop("server")
|
||||
|
||||
for setting in self._settings.copy():
|
||||
# remove deprecated settings
|
||||
if setting in ["enable_remote_console", "use_ubridge", "acpi_shutdown", "default_symbol", "hover_symbol"]:
|
||||
del self._settings[setting]
|
||||
|
||||
def id(self):
|
||||
"""
|
||||
Returns the template ID.
|
||||
|
||||
@@ -109,14 +109,14 @@ class Topology(QtCore.QObject):
|
||||
|
||||
return self._project
|
||||
|
||||
def setProject(self, project):
|
||||
def setProject(self, project, snapshot=False):
|
||||
"""
|
||||
Set current project
|
||||
|
||||
:param project: Project instance
|
||||
"""
|
||||
|
||||
if self._project:
|
||||
if self._project and snapshot is False:
|
||||
# Assert to detect when we create a new project object for the same project
|
||||
assert project is None or (project != self._project and project.id != self._project.id)
|
||||
self._project.stopListenNotifications()
|
||||
@@ -134,7 +134,6 @@ class Topology(QtCore.QObject):
|
||||
|
||||
self.project_changed_signal.emit()
|
||||
|
||||
|
||||
def _projectUpdatedSlot(self):
|
||||
if not self._project or not self._project.filesDir() or not self._project.filename():
|
||||
return
|
||||
@@ -193,6 +192,7 @@ class Topology(QtCore.QObject):
|
||||
"""
|
||||
Create load a project based on settings, not on the .gns3
|
||||
"""
|
||||
|
||||
self.setProject(None)
|
||||
from .project import Project
|
||||
project = Project()
|
||||
@@ -215,6 +215,17 @@ class Topology(QtCore.QObject):
|
||||
self._main_window.uiStatusBar.showMessage("Project created", 2000)
|
||||
return project
|
||||
|
||||
def restoreSnapshot(self, project_id):
|
||||
"""
|
||||
Restore a snapshot for a given project.
|
||||
"""
|
||||
|
||||
assert self._project.id() == project_id
|
||||
project = self._project
|
||||
self.setProject(project, snapshot=True)
|
||||
project.load()
|
||||
self._main_window.uiStatusBar.showMessage("Snapshot restored", 2000)
|
||||
|
||||
def loadProject(self, path):
|
||||
"""
|
||||
Loads a project into GNS3.
|
||||
|
||||
@@ -67,13 +67,13 @@
|
||||
<item row="4" column="1">
|
||||
<widget class="QSpinBox" name="uiNodeGridSizeSpinBox">
|
||||
<property name="minimum">
|
||||
<number>10</number>
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>150</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>10</number>
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>75</number>
|
||||
@@ -83,13 +83,13 @@
|
||||
<item row="5" column="1">
|
||||
<widget class="QSpinBox" name="uiDrawingGridSizeSpinBox">
|
||||
<property name="minimum">
|
||||
<number>10</number>
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>10</number>
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>25</number>
|
||||
@@ -179,6 +179,17 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>uiProjectNameLineEdit</tabstop>
|
||||
<tabstop>uiSceneWidthSpinBox</tabstop>
|
||||
<tabstop>uiSceneHeightSpinBox</tabstop>
|
||||
<tabstop>uiNodeGridSizeSpinBox</tabstop>
|
||||
<tabstop>uiDrawingGridSizeSpinBox</tabstop>
|
||||
<tabstop>uiProjectAutoOpenCheckBox</tabstop>
|
||||
<tabstop>uiProjectAutoStartCheckBox</tabstop>
|
||||
<tabstop>uiProjectAutoCloseCheckBox</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
|
||||
@@ -37,16 +37,16 @@ class Ui_EditProjectDialog(object):
|
||||
self.uiSceneWidthSpinBox.setObjectName("uiSceneWidthSpinBox")
|
||||
self.uiGeneralGrid.addWidget(self.uiSceneWidthSpinBox, 2, 1, 1, 1)
|
||||
self.uiNodeGridSizeSpinBox = QtWidgets.QSpinBox(self.uiGeneralTab)
|
||||
self.uiNodeGridSizeSpinBox.setMinimum(10)
|
||||
self.uiNodeGridSizeSpinBox.setMinimum(5)
|
||||
self.uiNodeGridSizeSpinBox.setMaximum(150)
|
||||
self.uiNodeGridSizeSpinBox.setSingleStep(10)
|
||||
self.uiNodeGridSizeSpinBox.setSingleStep(5)
|
||||
self.uiNodeGridSizeSpinBox.setProperty("value", 75)
|
||||
self.uiNodeGridSizeSpinBox.setObjectName("uiNodeGridSizeSpinBox")
|
||||
self.uiGeneralGrid.addWidget(self.uiNodeGridSizeSpinBox, 4, 1, 1, 1)
|
||||
self.uiDrawingGridSizeSpinBox = QtWidgets.QSpinBox(self.uiGeneralTab)
|
||||
self.uiDrawingGridSizeSpinBox.setMinimum(10)
|
||||
self.uiDrawingGridSizeSpinBox.setMinimum(5)
|
||||
self.uiDrawingGridSizeSpinBox.setMaximum(100)
|
||||
self.uiDrawingGridSizeSpinBox.setSingleStep(10)
|
||||
self.uiDrawingGridSizeSpinBox.setSingleStep(5)
|
||||
self.uiDrawingGridSizeSpinBox.setProperty("value", 25)
|
||||
self.uiDrawingGridSizeSpinBox.setObjectName("uiDrawingGridSizeSpinBox")
|
||||
self.uiGeneralGrid.addWidget(self.uiDrawingGridSizeSpinBox, 5, 1, 1, 1)
|
||||
|
||||
@@ -61,14 +61,24 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<item row="1" column="1" colspan="2">
|
||||
<widget class="QComboBox" name="uiCompressionComboBox"/>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="3">
|
||||
<widget class="QCheckBox" name="uiIncludeImagesCheckBox">
|
||||
<property name="text">
|
||||
<string>Include base images</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiIncludeSnapshotsCheckBox">
|
||||
<property name="text">
|
||||
<string>Include snapshots</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
@@ -81,9 +91,6 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="2">
|
||||
<widget class="QComboBox" name="uiCompressionComboBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWizardPage" name="uiProjectReadmeWizardPage">
|
||||
|
||||
@@ -38,14 +38,17 @@ class Ui_ExportProjectWizard(object):
|
||||
self.uiCompressionLabel = QtWidgets.QLabel(self.uiExportOptionsWizardPage)
|
||||
self.uiCompressionLabel.setObjectName("uiCompressionLabel")
|
||||
self.gridLayout.addWidget(self.uiCompressionLabel, 1, 0, 1, 1)
|
||||
self.uiIncludeImagesCheckBox = QtWidgets.QCheckBox(self.uiExportOptionsWizardPage)
|
||||
self.uiIncludeImagesCheckBox.setObjectName("uiIncludeImagesCheckBox")
|
||||
self.gridLayout.addWidget(self.uiIncludeImagesCheckBox, 2, 0, 1, 2)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 247, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout.addItem(spacerItem, 3, 1, 1, 1)
|
||||
self.uiCompressionComboBox = QtWidgets.QComboBox(self.uiExportOptionsWizardPage)
|
||||
self.uiCompressionComboBox.setObjectName("uiCompressionComboBox")
|
||||
self.gridLayout.addWidget(self.uiCompressionComboBox, 1, 1, 1, 2)
|
||||
self.uiIncludeImagesCheckBox = QtWidgets.QCheckBox(self.uiExportOptionsWizardPage)
|
||||
self.uiIncludeImagesCheckBox.setObjectName("uiIncludeImagesCheckBox")
|
||||
self.gridLayout.addWidget(self.uiIncludeImagesCheckBox, 2, 0, 1, 3)
|
||||
self.uiIncludeSnapshotsCheckBox = QtWidgets.QCheckBox(self.uiExportOptionsWizardPage)
|
||||
self.uiIncludeSnapshotsCheckBox.setObjectName("uiIncludeSnapshotsCheckBox")
|
||||
self.gridLayout.addWidget(self.uiIncludeSnapshotsCheckBox, 3, 0, 1, 2)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 247, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout.addItem(spacerItem, 4, 2, 1, 1)
|
||||
ExportProjectWizard.addPage(self.uiExportOptionsWizardPage)
|
||||
self.uiProjectReadmeWizardPage = QtWidgets.QWizardPage()
|
||||
self.uiProjectReadmeWizardPage.setObjectName("uiProjectReadmeWizardPage")
|
||||
@@ -68,6 +71,7 @@ class Ui_ExportProjectWizard(object):
|
||||
self.uiPathBrowserToolButton.setText(_translate("ExportProjectWizard", "Browse..."))
|
||||
self.uiCompressionLabel.setText(_translate("ExportProjectWizard", "Compression:"))
|
||||
self.uiIncludeImagesCheckBox.setText(_translate("ExportProjectWizard", "Include base images"))
|
||||
self.uiIncludeSnapshotsCheckBox.setText(_translate("ExportProjectWizard", "Include snapshots"))
|
||||
self.uiProjectReadmeWizardPage.setTitle(_translate("ExportProjectWizard", "Readme file"))
|
||||
self.uiProjectReadmeWizardPage.setSubTitle(_translate("ExportProjectWizard", "Write a summary of the project."))
|
||||
self.uiReadmeTextEdit.setHtml(_translate("ExportProjectWizard", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
|
||||
|
||||
@@ -223,7 +223,16 @@
|
||||
<string>Binary images</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
@@ -275,6 +284,9 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="uiImageDirectoriesListWidget">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::ClickFocus</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
@@ -370,7 +382,16 @@
|
||||
<string>Console applications</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
@@ -481,7 +502,16 @@
|
||||
<string>VNC</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
@@ -684,6 +714,9 @@
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
@@ -730,16 +763,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QCheckBox" name="uiDrawLinkStatusPointsCheckBox">
|
||||
<property name="text">
|
||||
<string>Draw link status points</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0" colspan="3">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
@@ -785,6 +808,9 @@
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
@@ -801,6 +827,9 @@
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> pixels</string>
|
||||
</property>
|
||||
@@ -826,6 +855,9 @@
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> pixels</string>
|
||||
</property>
|
||||
@@ -879,14 +911,17 @@
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>10</number>
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>10</number>
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>25</number>
|
||||
@@ -901,14 +936,17 @@
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>10</number>
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>150</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>10</number>
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>75</number>
|
||||
@@ -945,6 +983,16 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiDrawLinkStatusPointsCheckBox">
|
||||
<property name="text">
|
||||
<string>Draw link status points</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="uiMiscTab">
|
||||
@@ -952,7 +1000,16 @@
|
||||
<string>Miscellaneous</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
@@ -1066,6 +1123,56 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>uiProjectsPathLineEdit</tabstop>
|
||||
<tabstop>uiProjectsPathToolButton</tabstop>
|
||||
<tabstop>uiSymbolsPathLineEdit</tabstop>
|
||||
<tabstop>uiSymbolsPathToolButton</tabstop>
|
||||
<tabstop>uiConfigsPathLineEdit</tabstop>
|
||||
<tabstop>uiConfigsPathToolButton</tabstop>
|
||||
<tabstop>uiAppliancesPathLineEdit</tabstop>
|
||||
<tabstop>uiAppliancesPathToolButton</tabstop>
|
||||
<tabstop>uiStyleComboBox</tabstop>
|
||||
<tabstop>uiSymbolThemeComboBox</tabstop>
|
||||
<tabstop>uiImportConfigurationFilePushButton</tabstop>
|
||||
<tabstop>uiExportConfigurationFilePushButton</tabstop>
|
||||
<tabstop>uiBrowseConfigurationPushButton</tabstop>
|
||||
<tabstop>uiImagesPathLineEdit</tabstop>
|
||||
<tabstop>uiImagesPathToolButton</tabstop>
|
||||
<tabstop>uiImageDirectoriesAddPushButton</tabstop>
|
||||
<tabstop>uiImageDirectoriesDeletePushButton</tabstop>
|
||||
<tabstop>uiTelnetConsoleCommandLineEdit</tabstop>
|
||||
<tabstop>uiTelnetConsolePreconfiguredCommandPushButton</tabstop>
|
||||
<tabstop>uiDelayConsoleAllSpinBox</tabstop>
|
||||
<tabstop>uiVNCConsoleCommandLineEdit</tabstop>
|
||||
<tabstop>uiVNCConsolePreconfiguredCommandPushButton</tabstop>
|
||||
<tabstop>uiSPICEConsoleCommandLineEdit</tabstop>
|
||||
<tabstop>uiSPICEConsolePreconfiguredCommandPushButton</tabstop>
|
||||
<tabstop>uiSceneWidthSpinBox</tabstop>
|
||||
<tabstop>uiSceneHeightSpinBox</tabstop>
|
||||
<tabstop>uiNodeGridSizeSpinBox</tabstop>
|
||||
<tabstop>uiDrawingGridSizeSpinBox</tabstop>
|
||||
<tabstop>uiRectangleSelectedItemCheckBox</tabstop>
|
||||
<tabstop>uiDrawLinkStatusPointsCheckBox</tabstop>
|
||||
<tabstop>uiShowInterfaceLabelsOnNewProject</tabstop>
|
||||
<tabstop>uiShowGridOnNewProject</tabstop>
|
||||
<tabstop>uiSnapToGridOnNewProject</tabstop>
|
||||
<tabstop>uiLimitSizeNodeSymbolCheckBox</tabstop>
|
||||
<tabstop>uiDefaultLabelFontPushButton</tabstop>
|
||||
<tabstop>uiDefaultLabelColorPushButton</tabstop>
|
||||
<tabstop>uiDefaultNoteFontPushButton</tabstop>
|
||||
<tabstop>uiDefaultNoteColorPushButton</tabstop>
|
||||
<tabstop>uiCheckForUpdateCheckBox</tabstop>
|
||||
<tabstop>uiCrashReportCheckBox</tabstop>
|
||||
<tabstop>uiStatsCheckBox</tabstop>
|
||||
<tabstop>uiOverlayNotificationsCheckBox</tabstop>
|
||||
<tabstop>uiExperimentalFeaturesCheckBox</tabstop>
|
||||
<tabstop>uiHdpiCheckBox</tabstop>
|
||||
<tabstop>uiMultiProfilesCheckBox</tabstop>
|
||||
<tabstop>uiDirectFileUpload</tabstop>
|
||||
<tabstop>uiRestoreDefaultsPushButton</tabstop>
|
||||
<tabstop>uiMiscTabWidget</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
@@ -396,9 +396,9 @@ class Ui_GeneralPreferencesPageWidget(object):
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiDrawingGridSizeSpinBox.sizePolicy().hasHeightForWidth())
|
||||
self.uiDrawingGridSizeSpinBox.setSizePolicy(sizePolicy)
|
||||
self.uiDrawingGridSizeSpinBox.setMinimum(10)
|
||||
self.uiDrawingGridSizeSpinBox.setMinimum(5)
|
||||
self.uiDrawingGridSizeSpinBox.setMaximum(100)
|
||||
self.uiDrawingGridSizeSpinBox.setSingleStep(10)
|
||||
self.uiDrawingGridSizeSpinBox.setSingleStep(5)
|
||||
self.uiDrawingGridSizeSpinBox.setProperty("value", 25)
|
||||
self.uiDrawingGridSizeSpinBox.setObjectName("uiDrawingGridSizeSpinBox")
|
||||
self.gridLayout_3.addWidget(self.uiDrawingGridSizeSpinBox, 3, 1, 1, 1)
|
||||
@@ -408,9 +408,9 @@ class Ui_GeneralPreferencesPageWidget(object):
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiNodeGridSizeSpinBox.sizePolicy().hasHeightForWidth())
|
||||
self.uiNodeGridSizeSpinBox.setSizePolicy(sizePolicy)
|
||||
self.uiNodeGridSizeSpinBox.setMinimum(10)
|
||||
self.uiNodeGridSizeSpinBox.setMinimum(5)
|
||||
self.uiNodeGridSizeSpinBox.setMaximum(150)
|
||||
self.uiNodeGridSizeSpinBox.setSingleStep(10)
|
||||
self.uiNodeGridSizeSpinBox.setSingleStep(5)
|
||||
self.uiNodeGridSizeSpinBox.setProperty("value", 75)
|
||||
self.uiNodeGridSizeSpinBox.setObjectName("uiNodeGridSizeSpinBox")
|
||||
self.gridLayout_3.addWidget(self.uiNodeGridSizeSpinBox, 2, 1, 1, 1)
|
||||
|
||||
@@ -209,6 +209,18 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>uiEnableVMCheckBox</tabstop>
|
||||
<tabstop>uiGNS3VMEngineComboBox</tabstop>
|
||||
<tabstop>uiVMListComboBox</tabstop>
|
||||
<tabstop>uiRefreshPushButton</tabstop>
|
||||
<tabstop>uiHeadlessCheckBox</tabstop>
|
||||
<tabstop>uiRamSpinBox</tabstop>
|
||||
<tabstop>uiCpuSpinBox</tabstop>
|
||||
<tabstop>uiWhenExitKeepRadioButton</tabstop>
|
||||
<tabstop>uiWhenExitSuspendRadioButton</tabstop>
|
||||
<tabstop>uiWhenExitStopRadioButton</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<designerdata>
|
||||
|
||||
@@ -151,7 +151,6 @@ background-none;
|
||||
<addaction name="uiDrawRectangleAction"/>
|
||||
<addaction name="uiDrawEllipseAction"/>
|
||||
<addaction name="uiDrawLineAction"/>
|
||||
<addaction name="uiEditReadmeAction"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="uiDeviceMenu">
|
||||
<property name="title">
|
||||
@@ -1157,15 +1156,6 @@ background-none;
|
||||
<string>Import portable project</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="uiEditReadmeAction">
|
||||
<property name="icon">
|
||||
<iconset resource="../../resources/resources.qrc">
|
||||
<normaloff>:/icons/edit.svg</normaloff>:/icons/edit.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Edit readme</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="uiAcademyAction">
|
||||
<property name="text">
|
||||
<string>GNS3 &Academy</string>
|
||||
|
||||
@@ -214,22 +214,22 @@ class Ui_MainWindow(object):
|
||||
self.uiOnlineHelpAction.setObjectName("uiOnlineHelpAction")
|
||||
self.uiScreenshotAction = QtWidgets.QAction(MainWindow)
|
||||
icon4 = QtGui.QIcon()
|
||||
icon4.addPixmap(QtGui.QPixmap(":/icons/camera-photo.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon4.addPixmap(QtGui.QPixmap(":/icons/camera-photo-hover.svg"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon4.addPixmap(QtGui.QPixmap(":/icons/camera-photo.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiScreenshotAction.setIcon(icon4)
|
||||
self.uiScreenshotAction.setObjectName("uiScreenshotAction")
|
||||
self.uiStartAllAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiStartAllAction.setEnabled(True)
|
||||
icon5 = QtGui.QIcon()
|
||||
icon5.addPixmap(QtGui.QPixmap(":/icons/start.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon5.addPixmap(QtGui.QPixmap(":/icons/start-hover.svg"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon5.addPixmap(QtGui.QPixmap(":/icons/start.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiStartAllAction.setIcon(icon5)
|
||||
self.uiStartAllAction.setObjectName("uiStartAllAction")
|
||||
self.uiStopAllAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiStopAllAction.setEnabled(True)
|
||||
icon6 = QtGui.QIcon()
|
||||
icon6.addPixmap(QtGui.QPixmap(":/icons/stop.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon6.addPixmap(QtGui.QPixmap(":/icons/stop-hover.svg"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon6.addPixmap(QtGui.QPixmap(":/icons/stop.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiStopAllAction.setIcon(icon6)
|
||||
self.uiStopAllAction.setObjectName("uiStopAllAction")
|
||||
self.uiConsoleAllAction = QtWidgets.QAction(MainWindow)
|
||||
@@ -243,14 +243,14 @@ class Ui_MainWindow(object):
|
||||
self.uiAboutQtAction.setObjectName("uiAboutQtAction")
|
||||
self.uiZoomInAction = QtWidgets.QAction(MainWindow)
|
||||
icon8 = QtGui.QIcon()
|
||||
icon8.addPixmap(QtGui.QPixmap(":/icons/zoom-in.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon8.addPixmap(QtGui.QPixmap(":/icons/zoom-in-hover.png"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon8.addPixmap(QtGui.QPixmap(":/icons/zoom-in.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiZoomInAction.setIcon(icon8)
|
||||
self.uiZoomInAction.setObjectName("uiZoomInAction")
|
||||
self.uiZoomOutAction = QtWidgets.QAction(MainWindow)
|
||||
icon9 = QtGui.QIcon()
|
||||
icon9.addPixmap(QtGui.QPixmap(":/icons/zoom-out.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon9.addPixmap(QtGui.QPixmap(":/icons/zoom-out-hover.png"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon9.addPixmap(QtGui.QPixmap(":/icons/zoom-out.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiZoomOutAction.setIcon(icon9)
|
||||
self.uiZoomOutAction.setObjectName("uiZoomOutAction")
|
||||
self.uiZoomResetAction = QtWidgets.QAction(MainWindow)
|
||||
@@ -267,8 +267,8 @@ class Ui_MainWindow(object):
|
||||
self.uiPreferencesAction.setObjectName("uiPreferencesAction")
|
||||
self.uiSuspendAllAction = QtWidgets.QAction(MainWindow)
|
||||
icon11 = QtGui.QIcon()
|
||||
icon11.addPixmap(QtGui.QPixmap(":/icons/pause.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon11.addPixmap(QtGui.QPixmap(":/icons/pause-hover.svg"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon11.addPixmap(QtGui.QPixmap(":/icons/pause.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiSuspendAllAction.setIcon(icon11)
|
||||
self.uiSuspendAllAction.setObjectName("uiSuspendAllAction")
|
||||
self.uiAddNoteAction = QtWidgets.QAction(MainWindow)
|
||||
@@ -296,15 +296,15 @@ class Ui_MainWindow(object):
|
||||
self.uiDrawRectangleAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiDrawRectangleAction.setCheckable(True)
|
||||
icon16 = QtGui.QIcon()
|
||||
icon16.addPixmap(QtGui.QPixmap(":/icons/rectangle.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon16.addPixmap(QtGui.QPixmap(":/icons/rectangle-hover.svg"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon16.addPixmap(QtGui.QPixmap(":/icons/rectangle.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiDrawRectangleAction.setIcon(icon16)
|
||||
self.uiDrawRectangleAction.setObjectName("uiDrawRectangleAction")
|
||||
self.uiDrawEllipseAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiDrawEllipseAction.setCheckable(True)
|
||||
icon17 = QtGui.QIcon()
|
||||
icon17.addPixmap(QtGui.QPixmap(":/icons/ellipse.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon17.addPixmap(QtGui.QPixmap(":/icons/ellipse-hover.svg"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon17.addPixmap(QtGui.QPixmap(":/icons/ellipse.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiDrawEllipseAction.setIcon(icon17)
|
||||
self.uiDrawEllipseAction.setObjectName("uiDrawEllipseAction")
|
||||
self.uiShowPortNamesAction = QtWidgets.QAction(MainWindow)
|
||||
@@ -346,41 +346,41 @@ class Ui_MainWindow(object):
|
||||
self.uiCheckForUpdateAction.setObjectName("uiCheckForUpdateAction")
|
||||
self.uiBrowseRoutersAction = QtWidgets.QAction(MainWindow)
|
||||
icon23 = QtGui.QIcon()
|
||||
icon23.addPixmap(QtGui.QPixmap(":/icons/router.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon23.addPixmap(QtGui.QPixmap(":/icons/router-hover.png"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon23.addPixmap(QtGui.QPixmap(":/icons/router.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiBrowseRoutersAction.setIcon(icon23)
|
||||
self.uiBrowseRoutersAction.setObjectName("uiBrowseRoutersAction")
|
||||
self.uiBrowseSwitchesAction = QtWidgets.QAction(MainWindow)
|
||||
icon24 = QtGui.QIcon()
|
||||
icon24.addPixmap(QtGui.QPixmap(":/icons/switch.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon24.addPixmap(QtGui.QPixmap(":/icons/switch-hover.png"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon24.addPixmap(QtGui.QPixmap(":/icons/switch.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiBrowseSwitchesAction.setIcon(icon24)
|
||||
self.uiBrowseSwitchesAction.setObjectName("uiBrowseSwitchesAction")
|
||||
self.uiBrowseEndDevicesAction = QtWidgets.QAction(MainWindow)
|
||||
icon25 = QtGui.QIcon()
|
||||
icon25.addPixmap(QtGui.QPixmap(":/icons/PC.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon25.addPixmap(QtGui.QPixmap(":/icons/PC-hover.png"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon25.addPixmap(QtGui.QPixmap(":/icons/PC.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiBrowseEndDevicesAction.setIcon(icon25)
|
||||
self.uiBrowseEndDevicesAction.setObjectName("uiBrowseEndDevicesAction")
|
||||
self.uiBrowseSecurityDevicesAction = QtWidgets.QAction(MainWindow)
|
||||
icon26 = QtGui.QIcon()
|
||||
icon26.addPixmap(QtGui.QPixmap(":/icons/firewall.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon26.addPixmap(QtGui.QPixmap(":/icons/firewall-hover.png"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon26.addPixmap(QtGui.QPixmap(":/icons/firewall.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiBrowseSecurityDevicesAction.setIcon(icon26)
|
||||
self.uiBrowseSecurityDevicesAction.setObjectName("uiBrowseSecurityDevicesAction")
|
||||
self.uiBrowseAllDevicesAction = QtWidgets.QAction(MainWindow)
|
||||
icon27 = QtGui.QIcon()
|
||||
icon27.addPixmap(QtGui.QPixmap(":/icons/browse-all-icons.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon27.addPixmap(QtGui.QPixmap(":/icons/browse-all-icons-hover.png"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon27.addPixmap(QtGui.QPixmap(":/icons/browse-all-icons.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiBrowseAllDevicesAction.setIcon(icon27)
|
||||
self.uiBrowseAllDevicesAction.setObjectName("uiBrowseAllDevicesAction")
|
||||
self.uiAddLinkAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiAddLinkAction.setCheckable(True)
|
||||
icon28 = QtGui.QIcon()
|
||||
icon28.addPixmap(QtGui.QPixmap(":/icons/connection-new.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon28.addPixmap(QtGui.QPixmap(":/icons/cancel-connection.svg"), QtGui.QIcon.Active, QtGui.QIcon.On)
|
||||
icon28.addPixmap(QtGui.QPixmap(":/icons/cancel-connection.svg"), QtGui.QIcon.Normal, QtGui.QIcon.On)
|
||||
icon28.addPixmap(QtGui.QPixmap(":/icons/connection-new-hover.svg"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon28.addPixmap(QtGui.QPixmap(":/icons/connection-new.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon28.addPixmap(QtGui.QPixmap(":/icons/cancel-connection.svg"), QtGui.QIcon.Normal, QtGui.QIcon.On)
|
||||
icon28.addPixmap(QtGui.QPixmap(":/icons/cancel-connection.svg"), QtGui.QIcon.Active, QtGui.QIcon.On)
|
||||
self.uiAddLinkAction.setIcon(icon28)
|
||||
self.uiAddLinkAction.setObjectName("uiAddLinkAction")
|
||||
self.uiFitInViewAction = QtWidgets.QAction(MainWindow)
|
||||
@@ -407,23 +407,20 @@ class Ui_MainWindow(object):
|
||||
icon30.addPixmap(QtGui.QPixmap(":/icons/import.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiImportProjectAction.setIcon(icon30)
|
||||
self.uiImportProjectAction.setObjectName("uiImportProjectAction")
|
||||
self.uiEditReadmeAction = QtWidgets.QAction(MainWindow)
|
||||
icon31 = QtGui.QIcon()
|
||||
icon31.addPixmap(QtGui.QPixmap(":/icons/edit.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiEditReadmeAction.setIcon(icon31)
|
||||
self.uiEditReadmeAction.setObjectName("uiEditReadmeAction")
|
||||
self.uiAcademyAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiAcademyAction.setObjectName("uiAcademyAction")
|
||||
self.uiDeleteProjectAction = QtWidgets.QAction(MainWindow)
|
||||
icon32 = QtGui.QIcon()
|
||||
icon32.addPixmap(QtGui.QPixmap(":/icons/delete.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiDeleteProjectAction.setIcon(icon32)
|
||||
icon31 = QtGui.QIcon()
|
||||
icon31.addPixmap(QtGui.QPixmap(":/icons/delete.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiDeleteProjectAction.setIcon(icon31)
|
||||
self.uiDeleteProjectAction.setObjectName("uiDeleteProjectAction")
|
||||
self.uiShowGridAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiShowGridAction.setCheckable(True)
|
||||
self.uiShowGridAction.setObjectName("uiShowGridAction")
|
||||
self.uiEditProjectAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiEditProjectAction.setIcon(icon31)
|
||||
icon32 = QtGui.QIcon()
|
||||
icon32.addPixmap(QtGui.QPixmap(":/icons/edit.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiEditProjectAction.setIcon(icon32)
|
||||
self.uiEditProjectAction.setObjectName("uiEditProjectAction")
|
||||
self.uiWebInterfaceAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiWebInterfaceAction.setObjectName("uiWebInterfaceAction")
|
||||
@@ -437,10 +434,10 @@ class Ui_MainWindow(object):
|
||||
self.uiLockAllAction.setCheckable(True)
|
||||
self.uiLockAllAction.setChecked(False)
|
||||
icon34 = QtGui.QIcon()
|
||||
icon34.addPixmap(QtGui.QPixmap(":/icons/unlock.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon34.addPixmap(QtGui.QPixmap(":/icons/lock.svg"), QtGui.QIcon.Active, QtGui.QIcon.On)
|
||||
icon34.addPixmap(QtGui.QPixmap(":/icons/lock.svg"), QtGui.QIcon.Normal, QtGui.QIcon.On)
|
||||
icon34.addPixmap(QtGui.QPixmap(":/icons/unlock.svg"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon34.addPixmap(QtGui.QPixmap(":/icons/unlock.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon34.addPixmap(QtGui.QPixmap(":/icons/lock.svg"), QtGui.QIcon.Normal, QtGui.QIcon.On)
|
||||
icon34.addPixmap(QtGui.QPixmap(":/icons/lock.svg"), QtGui.QIcon.Active, QtGui.QIcon.On)
|
||||
self.uiLockAllAction.setIcon(icon34)
|
||||
self.uiLockAllAction.setObjectName("uiLockAllAction")
|
||||
self.uiWebUIAction = QtWidgets.QAction(MainWindow)
|
||||
@@ -500,7 +497,6 @@ class Ui_MainWindow(object):
|
||||
self.uiAnnotateMenu.addAction(self.uiDrawRectangleAction)
|
||||
self.uiAnnotateMenu.addAction(self.uiDrawEllipseAction)
|
||||
self.uiAnnotateMenu.addAction(self.uiDrawLineAction)
|
||||
self.uiAnnotateMenu.addAction(self.uiEditReadmeAction)
|
||||
self.uiToolsMenu.addAction(self.uiScreenshotAction)
|
||||
self.uiToolsMenu.addAction(self.uiImportExportConfigsAction)
|
||||
self.uiToolsMenu.addAction(self.uiWebInterfaceAction)
|
||||
@@ -702,7 +698,6 @@ class Ui_MainWindow(object):
|
||||
self.uiDoctorAction.setText(_translate("MainWindow", "GNS3 &Doctor"))
|
||||
self.uiExportProjectAction.setText(_translate("MainWindow", "Export portable project"))
|
||||
self.uiImportProjectAction.setText(_translate("MainWindow", "Import portable project"))
|
||||
self.uiEditReadmeAction.setText(_translate("MainWindow", "Edit readme"))
|
||||
self.uiAcademyAction.setText(_translate("MainWindow", "GNS3 &Academy"))
|
||||
self.uiDeleteProjectAction.setText(_translate("MainWindow", "Delete project"))
|
||||
self.uiShowGridAction.setText(_translate("MainWindow", "Show the grid"))
|
||||
|
||||
@@ -125,6 +125,14 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>uiPreconfiguredCaptureReaderCommandComboBox</tabstop>
|
||||
<tabstop>uiPreconfiguredCaptureReaderCommandPushButton</tabstop>
|
||||
<tabstop>uiCaptureReaderCommandLineEdit</tabstop>
|
||||
<tabstop>uiAutoStartCheckBox</tabstop>
|
||||
<tabstop>uiCaptureAnalyzerCommandLineEdit</tabstop>
|
||||
<tabstop>uiRestoreDefaultsPushButton</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>980</width>
|
||||
<height>680</height>
|
||||
<width>680</width>
|
||||
<height>517</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -36,6 +36,9 @@
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::ClickFocus</enum>
|
||||
</property>
|
||||
<property name="indentation">
|
||||
<number>10</number>
|
||||
</property>
|
||||
@@ -73,6 +76,9 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
@@ -81,8 +87,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>542</width>
|
||||
<height>559</height>
|
||||
<width>269</width>
|
||||
<height>457</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
@@ -103,6 +109,9 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="uiButtonBox">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
@@ -131,8 +140,8 @@
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>540</x>
|
||||
<y>571</y>
|
||||
<x>672</x>
|
||||
<y>509</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>449</x>
|
||||
@@ -147,8 +156,8 @@
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>540</x>
|
||||
<y>571</y>
|
||||
<x>672</x>
|
||||
<y>509</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>449</x>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<enum>QTabWidget::Rounded</enum>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
<number>1</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="uiNewProjectTab">
|
||||
<attribute name="title">
|
||||
@@ -284,6 +284,19 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>uiProjectTabWidget</tabstop>
|
||||
<tabstop>uiNameLineEdit</tabstop>
|
||||
<tabstop>uiLocationLineEdit</tabstop>
|
||||
<tabstop>uiLocationBrowserToolButton</tabstop>
|
||||
<tabstop>uiOpenProjectPushButton</tabstop>
|
||||
<tabstop>uiRecentProjectsPushButton</tabstop>
|
||||
<tabstop>uiSettingsPushButton</tabstop>
|
||||
<tabstop>uiProjectsTreeWidget</tabstop>
|
||||
<tabstop>uiDeleteProjectButton</tabstop>
|
||||
<tabstop>uiDuplicateProjectPushButton</tabstop>
|
||||
<tabstop>uiRefreshProjectsPushButton</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
|
||||
@@ -6,16 +6,10 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1069</width>
|
||||
<height>615</height>
|
||||
<width>1081</width>
|
||||
<height>534</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Setup Wizard</string>
|
||||
</property>
|
||||
@@ -83,7 +77,7 @@
|
||||
</font>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Eveything that is supported by your system will run on your computer.</string>
|
||||
<string>Everything that is supported by your system will run on your computer.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Run appliances on my local computer</string>
|
||||
|
||||
@@ -2,21 +2,18 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/setup_wizard.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.9
|
||||
# Created by: PyQt5 UI code generator 5.13.0
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_SetupWizard(object):
|
||||
def setupUi(self, SetupWizard):
|
||||
SetupWizard.setObjectName("SetupWizard")
|
||||
SetupWizard.resize(1069, 615)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(SetupWizard.sizePolicy().hasHeightForWidth())
|
||||
SetupWizard.setSizePolicy(sizePolicy)
|
||||
SetupWizard.resize(1081, 534)
|
||||
SetupWizard.setModal(True)
|
||||
SetupWizard.setWizardStyle(QtWidgets.QWizard.ModernStyle)
|
||||
SetupWizard.setOptions(QtWidgets.QWizard.NoBackButtonOnStartPage)
|
||||
@@ -247,7 +244,7 @@ class Ui_SetupWizard(object):
|
||||
self.uiVMRadioButton.setToolTip(_translate("SetupWizard", "Dynamips, IOU, VPCS and Qemu will use this virtual machine."))
|
||||
self.uiVMRadioButton.setText(_translate("SetupWizard", "Run appliances in a virtual machine"))
|
||||
self.label.setText(_translate("SetupWizard", "Requires to download and install the GNS3 VM (available for free) "))
|
||||
self.uiLocalRadioButton.setToolTip(_translate("SetupWizard", "Eveything that is supported by your system will run on your computer."))
|
||||
self.uiLocalRadioButton.setToolTip(_translate("SetupWizard", "Everything that is supported by your system will run on your computer."))
|
||||
self.uiLocalRadioButton.setText(_translate("SetupWizard", "Run appliances on my local computer"))
|
||||
self.uiLocalLabel.setText(_translate("SetupWizard", "A limited number of appliances like the Cisco IOS routers <= C7200 can be run"))
|
||||
self.uiRemoteControllerRadioButton.setText(_translate("SetupWizard", "Run appliances on a remote server (advanced usage)"))
|
||||
@@ -286,5 +283,4 @@ class Ui_SetupWizard(object):
|
||||
self.uiSummaryWizardPage.setSubTitle(_translate("SetupWizard", "The server type has been configured, please see the summary of the settings below"))
|
||||
self.uiSummaryTreeWidget.headerItem().setText(0, _translate("SetupWizard", "1"))
|
||||
self.uiSummaryTreeWidget.headerItem().setText(1, _translate("SetupWizard", "2"))
|
||||
|
||||
from . import resources_rc
|
||||
|
||||
@@ -92,7 +92,7 @@ class UpdateManager(QtCore.QObject):
|
||||
self._silent = silent
|
||||
self._parent = parent
|
||||
|
||||
if hasattr(sys, "frozen") and LocalConfig.instance().experimental():
|
||||
if not hasattr(sys, "frozen") and LocalConfig.instance().experimental():
|
||||
url = 'https://pypi.org/pypi/gns3-gui/json'
|
||||
self._get(url, self._pypiReplySlot)
|
||||
else:
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
# 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 shlex
|
||||
import importlib
|
||||
import hashlib
|
||||
import re
|
||||
@@ -108,3 +110,14 @@ def natural_sort_key(s):
|
||||
* pc10
|
||||
"""
|
||||
return [int(text) if text.isdecimal() else text.lower() for text in re.split('([0-9]+)', s)]
|
||||
|
||||
|
||||
def shlex_quote(s):
|
||||
"""
|
||||
Compatible shlex_quote to handle case where Windows needs double quotes around file names, not single quotes.
|
||||
"""
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
return s if re.match(r'^[-_\w./]+$', s) else '"%s"' % s.replace('"', '\\"')
|
||||
else:
|
||||
return shlex.quote(s)
|
||||
|
||||
@@ -66,7 +66,7 @@ def bring_window_to_front_from_process_name(process_name, title=None):
|
||||
elif title in win32gui.GetWindowText(hwnd):
|
||||
set_foreground_window(hwnd)
|
||||
return True
|
||||
except psutil.Error:
|
||||
except (OSError, psutil.Error):
|
||||
continue
|
||||
return False
|
||||
|
||||
|
||||
@@ -28,16 +28,17 @@ class ExportProjectWorker(QtCore.QObject):
|
||||
finished = QtCore.pyqtSignal()
|
||||
updated = QtCore.pyqtSignal(int)
|
||||
|
||||
def __init__(self, project, path, include_images, compression):
|
||||
def __init__(self, project, path, include_images, include_snapshots, compression):
|
||||
super().__init__()
|
||||
self._project = project
|
||||
self._include_images = include_images
|
||||
self._include_snapshots = include_snapshots
|
||||
self._path = path
|
||||
self._compression = compression
|
||||
|
||||
def run(self):
|
||||
if self._project:
|
||||
self._project.get("/export?include_images={}&compression={}".format(self._include_images, self._compression),
|
||||
self._project.get("/export?include_images={}&include_snapshots={}&compression={}".format(self._include_images, self._include_snapshots, self._compression),
|
||||
self._exportReceived,
|
||||
downloadProgressCallback=self._downloadFileProgress,
|
||||
timeout=None)
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2016 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import ssl
|
||||
import http
|
||||
import json
|
||||
import base64
|
||||
import urllib.request
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def getSynchronous(protocol, host, port, endpoint, timeout=2, user=None, password=None):
|
||||
"""
|
||||
:returns: Tuple (Status code, json of anwser). Status 0 is a non HTTP error
|
||||
"""
|
||||
try:
|
||||
url = "{protocol}://{host}:{port}/v2/{endpoint}".format(protocol=protocol, host=host, port=port, endpoint=endpoint)
|
||||
request = urllib.request.Request(url)
|
||||
|
||||
if user is not None and len(user) > 0:
|
||||
log.debug("Synchronous get {} with user '{}'".format(url, user))
|
||||
base64string = base64.encodebytes('{}:{}'.format(user, password).encode()).replace(b'\n', b'')
|
||||
request.add_header("Authorization", "Basic {}".format(base64string.decode()))
|
||||
else:
|
||||
log.debug("Synchronous get {} (no authentication)".format(url))
|
||||
|
||||
if sys.version_info >= (3, 5):
|
||||
ctx = ssl.create_default_context()
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
response = urllib.request.urlopen(request, timeout=timeout, context=ctx)
|
||||
else:
|
||||
response = urllib.request.urlopen(request, timeout=timeout)
|
||||
|
||||
content_type = response.getheader("CONTENT-TYPE")
|
||||
if response.status == 200:
|
||||
if content_type == "application/json":
|
||||
content = response.read()
|
||||
json_data = json.loads(content.decode("utf-8"))
|
||||
return response.status, json_data
|
||||
else:
|
||||
return response.status, None
|
||||
except http.client.InvalidURL as e:
|
||||
log.warning("Invalid local server url: {}".format(e))
|
||||
return 0, None
|
||||
except http.client.UnknownProtocol:
|
||||
log.warning("Unknown server running on {}:{}".format(host, port))
|
||||
return 0, None
|
||||
except urllib.error.URLError:
|
||||
# Connection refused. It's a normal behavior if server is not started
|
||||
return 0, None
|
||||
except urllib.error.HTTPError as e:
|
||||
log.debug("Error during get on {}:{}: {}".format(host, port, e))
|
||||
return e.code, None
|
||||
except (OSError, http.client.BadStatusLine, ValueError, SystemError) as e:
|
||||
log.debug("Error during get on {}:{}: {}".format(host, port, e))
|
||||
return 0, None
|
||||
@@ -80,11 +80,10 @@ def get_windows_interfaces():
|
||||
:returns: list of windows interfaces
|
||||
"""
|
||||
|
||||
import win32com.client
|
||||
import pywintypes
|
||||
|
||||
interfaces = []
|
||||
try:
|
||||
import win32com.client
|
||||
locator = win32com.client.Dispatch("WbemScripting.SWbemLocator")
|
||||
service = locator.ConnectServer(".", "root\cimv2")
|
||||
network_configs = service.InstancesOf("Win32_NetworkAdapterConfiguration")
|
||||
@@ -109,7 +108,7 @@ def get_windows_interfaces():
|
||||
"netcard": adapter.name,
|
||||
"netmask": netmask,
|
||||
"type": "ethernet"})
|
||||
except (AttributeError, pywintypes.com_error):
|
||||
except (ImportError, AttributeError, pywintypes.com_error):
|
||||
log.warning("Could not use the COM service to retrieve interface info, trying using the registry...")
|
||||
return _get_windows_interfaces_from_registry()
|
||||
|
||||
|
||||
@@ -97,6 +97,7 @@ def sudo(*commands, parent=None, shell=False):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
@@ -21,7 +21,7 @@ Thread to run a command and wait for its completion.
|
||||
|
||||
import tempfile
|
||||
import subprocess
|
||||
import shlex
|
||||
from ..utils import shlex_quote
|
||||
from ..qt import QtCore
|
||||
|
||||
import logging
|
||||
@@ -63,7 +63,7 @@ class WaitForCommandWorker(QtCore.QObject):
|
||||
timeout=self._timeout,
|
||||
shell=self._shell)
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
command_string = " ".join(shlex.quote(s) for s in self._command)
|
||||
command_string = " ".join(shlex_quote(s) for s in self._command)
|
||||
self.error.emit('Could not execute command "{}": {}'.format(command_string, e), True)
|
||||
return
|
||||
self.finished.emit()
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
Thread to run a command with administrator rights on Windows and wait for its completion.
|
||||
"""
|
||||
|
||||
import shlex
|
||||
from ..utils import shlex_quote
|
||||
from ..qt import QtCore
|
||||
|
||||
import logging
|
||||
@@ -69,7 +69,7 @@ class WaitForRunAsWorker(QtCore.QObject):
|
||||
lpFile=program,
|
||||
lpParameters=params)
|
||||
except pywintypes.error as e:
|
||||
command_string = " ".join(shlex.quote(s) for s in self._command)
|
||||
command_string = " ".join(shlex_quote(s) for s in self._command)
|
||||
self.error.emit('Could not execute command "{}": {}'.format(command_string, e), True)
|
||||
return
|
||||
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
# or negative for a release candidate or beta (after the base version
|
||||
# number has been incremented)
|
||||
|
||||
__version__ = "2.2.0b1"
|
||||
__version_info__ = (2, 2, 0, -99)
|
||||
__version__ = "2.2.3"
|
||||
__version_info__ = (2, 2, 3, 0)
|
||||
|
||||
# If it's a git checkout try to add the commit
|
||||
if "dev" in __version__:
|
||||
|
||||
@@ -37,7 +37,7 @@ def vncConsole(host, port, command):
|
||||
"""
|
||||
|
||||
if len(command.strip(' ')) == 0:
|
||||
log.warning('VNC client is not configured')
|
||||
log.error("VNC client is not configured")
|
||||
return
|
||||
|
||||
# replace the place-holders by the actual values
|
||||
@@ -56,4 +56,3 @@ def vncConsole(host, port, command):
|
||||
subprocess.Popen(args, env=os.environ)
|
||||
except (OSError, ValueError, subprocess.SubprocessError) as e:
|
||||
log.error("Could not start VNC program with command '{}': {}".format(command, e))
|
||||
raise
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
jsonschema==2.6.0 # pyup: ignore
|
||||
raven>=5.23.0
|
||||
psutil>=2.2.1
|
||||
psutil==5.6.3
|
||||
distro>=1.3.0
|
||||
1
setup.py
1
setup.py
@@ -74,6 +74,7 @@ setup(
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
import pytest
|
||||
import json
|
||||
import os
|
||||
from unittest.mock import patch
|
||||
|
||||
from gns3.registry.config import Config
|
||||
from gns3.settings import LOCAL_SERVER_SETTINGS
|
||||
@@ -209,47 +208,6 @@ def test_add_appliance_with_symbol_from_symbols_dir(empty_config, linux_microcor
|
||||
assert new_template["symbol"] == "linux_guest.svg"
|
||||
|
||||
|
||||
def test_add_appliance_with_symbol_from_web(empty_config, linux_microcore_img, symbols_dir):
|
||||
with open("tests/registry/appliances/microcore-linux.gns3a", 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
|
||||
}
|
||||
]
|
||||
config["symbol"] = "linux_guest.svg"
|
||||
symbol_path = os.path.join(symbols_dir, "linux_guest.svg")
|
||||
|
||||
with patch("urllib.request.urlretrieve") as mock:
|
||||
new_template = ApplianceToTemplate().new_template(config, "local")
|
||||
mock.assert_called_with("https://raw.githubusercontent.com/GNS3/gns3-registry/master/symbols/linux_guest.svg", symbol_path)
|
||||
assert new_template["symbol"] == "linux_guest.svg"
|
||||
|
||||
|
||||
def test_add_appliance_with_symbol_from_web_error(empty_config, linux_microcore_img, symbols_dir):
|
||||
with open("tests/registry/appliances/microcore-linux.gns3a", 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
|
||||
}
|
||||
]
|
||||
config["symbol"] = "linux_guest.svg"
|
||||
symbol_path = os.path.join(symbols_dir, "linux_guest.svg")
|
||||
|
||||
with patch("urllib.request.urlretrieve") as mock:
|
||||
mock.side_effect = OSError
|
||||
new_template = ApplianceToTemplate().new_template(config, "local")
|
||||
mock.assert_called_with("https://raw.githubusercontent.com/GNS3/gns3-registry/master/symbols/linux_guest.svg", symbol_path)
|
||||
|
||||
# In case of error we fallback on default symbol
|
||||
assert new_template["symbol"] == ":/symbols/qemu_guest.svg"
|
||||
|
||||
|
||||
def test_add_appliance_with_port_name_format(linux_microcore_img):
|
||||
with open("tests/registry/appliances/microcore-linux.gns3a", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
|
||||
@@ -39,12 +39,12 @@ def test_spice_console_on_windows():
|
||||
popen.assert_called_once_with('command localhost 2525')
|
||||
|
||||
|
||||
def test_spice_console_on_linux_with_popen_issues():
|
||||
with patch('subprocess.Popen', side_effect=OSError("Dummy")), \
|
||||
patch('sys.platform', new="linux"):
|
||||
|
||||
with pytest.raises(OSError):
|
||||
spiceConsole('localhost', '2525', 'command %h %p')
|
||||
# def test_spice_console_on_linux_with_popen_issues():
|
||||
# with patch('subprocess.Popen', side_effect=OSError("Dummy")), \
|
||||
# patch('sys.platform', new="linux"):
|
||||
#
|
||||
# with pytest.raises(OSError):
|
||||
# spiceConsole('localhost', '2525', 'command %h %p')
|
||||
|
||||
|
||||
def test_spice_console_with_ipv6_support():
|
||||
|
||||
@@ -37,9 +37,9 @@ def test_vnc_console_on_windows():
|
||||
popen.assert_called_once_with('command localhost 6000 100')
|
||||
|
||||
|
||||
def test_vnc_console_on_linux_with_popen_issues():
|
||||
with patch('subprocess.Popen', side_effect=OSError("Dummy")), \
|
||||
patch('sys.platform', new="linux"):
|
||||
|
||||
with pytest.raises(OSError):
|
||||
vncConsole('localhost', 6000, 'command %h %p %P')
|
||||
# def test_vnc_console_on_linux_with_popen_issues():
|
||||
# with patch('subprocess.Popen', side_effect=OSError("Dummy")), \
|
||||
# patch('sys.platform', new="linux"):
|
||||
#
|
||||
# with pytest.raises(OSError):
|
||||
# vncConsole('localhost', 6000, 'command %h %p %P')
|
||||
|
||||
Reference in New Issue
Block a user