Compare commits

...

112 Commits

Author SHA1 Message Date
grossmj
bb89fe2275 Release v2.2.3 2019-11-12 15:29:54 +08:00
grossmj
7eb2a923b2 Fix issue when binding on 0.0.0.0. Fixes #2892 2019-11-11 17:49:09 +08:00
grossmj
58052e3cce Allow double click on cloud with configured console to open session. Fixes #2894 2019-11-11 14:31:35 +08:00
grossmj
e431104f6b Officially support Python 3.8. Ref https://github.com/GNS3/gns3-gui/issues/2895 2019-11-11 12:56:11 +08:00
grossmj
a4e9d6b8ce Set psutil to version 5.6.3 in requirements.txt 2019-11-08 10:44:17 +08:00
grossmj
f58f5c7b95 Developement version on 2.2.3dev1 2019-11-04 19:45:18 +08:00
grossmj
37faa39309 Release v2.2.2 2019-11-04 18:33:29 +08:00
grossmj
4d64598ed2 Fix KeyError: 'spice+agent'. Fixes #2890 2019-11-03 15:19:18 +08:00
grossmj
5132c4e172 Fix wrong log.error() call when exporting file. 2019-11-02 23:19:04 +08:00
grossmj
3c3fdd9ffd Revert "Explicitly cleanup the cache directory."
This reverts commit 8095fef228.
2019-11-02 15:47:24 +08:00
grossmj
efa50571c6 Fix "UnboundLocalError: local variable 'pywintypes' referenced before assignment" 2019-11-02 15:45:16 +08:00
grossmj
ca9b10fcca Fix version string. 2019-11-02 15:27:28 +08:00
grossmj
8660161b10 Fix GUI uses only telnet console. Fixes #2885 2019-11-02 02:23:13 +08:00
grossmj
50ebfb9c06 Fix missing sys module in sudo.py Fixes #2886 2019-11-02 02:06:33 +08:00
grossmj
26df59d6b6 Development on 2.2.2dev1 2019-11-01 18:43:36 +08:00
grossmj
b903e2ad73 Release v2.2.1 2019-11-01 17:53:20 +08:00
grossmj
b2fe7eb643 Check if console_type is None. 2019-11-01 17:47:26 +08:00
grossmj
8095fef228 Explicitly cleanup the cache directory. 2019-11-01 17:46:53 +08:00
grossmj
b8da5440f5 Get Windows interface from registry if cannot load win32com module. 2019-11-01 17:44:47 +08:00
grossmj
6cea094e4e Ignore OSError returned by psutil when bringing console to front. 2019-11-01 17:44:06 +08:00
grossmj
9ac46c9d50 Catch error if NPF or NPCAP service cannot be detected. Ref https://github.com/GNS3/gns3-server/issues/1670 2019-10-30 17:59:19 +08:00
grossmj
6dc44d5108 Better handling for reading synchronous JSON response from server. Ref #2874 2019-10-17 12:45:40 +08:00
grossmj
9c6be0341b Fix tests. 2019-10-17 12:34:34 +08:00
grossmj
011a49e998 Fix JSONDecodeError when getting server version. Fixes #2874 2019-10-17 12:31:15 +08:00
grossmj
e18c2df5f5 Fix FileNotFoundError exceptions when launching SPICE or VNC clients. 2019-10-17 12:19:55 +08:00
grossmj
1794b8389f Fix UnboundLocalError local variable 'win32serviceutil' referenced before assignment 2019-10-17 12:12:46 +08:00
grossmj
0379c370eb Merge remote-tracking branch 'remotes/origin/master' into 2.2 2019-10-15 23:36:49 +08:00
Jeremy Grossmann
e03550a89b Merge pull request #2872 from fatoms/Tab_Order
Tab Order in Preferences and Project Dialog
2019-10-15 22:35:53 +07:00
Dominic
c6ea775e81 'Fix' tab order in preferences dialog so it follows the layout 2019-10-12 22:06:30 +02:00
Dominic
38233ba5e9 'Fix' tab order in edit project dialog so it follows the layout 2019-10-12 22:05:52 +02:00
grossmj
89c1272bc1 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 2019-10-09 17:02:30 +08:00
grossmj
8bbb46c599 Use 0.0.0.0 by default for server host. Fixes https://github.com/GNS3/gns3-server/issues/1663 2019-10-09 16:35:42 +08:00
grossmj
74fca3d736 Set default host to "localhost". Fixes https://github.com/GNS3/gns3-server/issues/1663 2019-10-08 18:28:11 +08:00
grossmj
7aeed7aa59 Catch IndexError when configuring port names. Fixes #2865 2019-10-08 16:15:36 +08:00
grossmj
aa15ace887 Bump version to 2.2.1dev1 2019-10-08 16:05:37 +08:00
grossmj
2d0a7b5f58 Release v2.2.0 2019-09-30 16:24:26 +08:00
grossmj
20a09b56c1 Merge branch 'master' into 2.2
# Conflicts:
#	gns3/version.py
2019-09-24 14:07:38 +08:00
grossmj
1938cdabae Bump version to 2.2.0dev18 2019-09-24 14:02:36 +08:00
grossmj
8d1bff782c Release v2.2.0rc5 2019-09-09 15:06:14 +07:00
grossmj
4e3eee2383 Adjust size for setup dialog and remove question about running the wizard again. Ref #2846 2019-09-07 23:46:02 +07:00
grossmj
da8aa0d2fd Release v2.2.0rc4 2019-08-30 15:23:32 +07:00
grossmj
5b4481c43a Fix issue when asking to run the setup wizard again. Ref #2846 2019-08-29 15:59:40 +07:00
grossmj
593cb8c1fd Remove warning about VirtualBox not supporting nested virtualization. Ref https://github.com/GNS3/gns3-server/issues/1610 2019-08-27 17:28:44 +07:00
grossmj
210cf63fe2 Ask user if they want to see the wizard again. Ref #2846 2019-08-27 16:15:50 +07:00
grossmj
3b178013c0 Bump version to 2.2.0dev17 2019-08-23 18:20:44 +07:00
grossmj
6e44d6b919 Merge branch 'master' into 2.2
# Conflicts:
#	gns3/version.py
2019-08-20 17:35:13 +07:00
grossmj
6b520b8036 Bump version to 2.2.0dev16 2019-08-20 17:33:48 +07:00
grossmj
803782b9d8 Release v2.2.0rc3 2019-08-11 19:14:56 -07:00
grossmj
d3d6ca3f2e Revert to jsonschema 2.6.0 due to packaging problem. 2019-08-11 19:11:41 -07:00
grossmj
f545c793f8 Release v2.2.0rc2 2019-08-10 12:04:19 -05:00
grossmj
47d6a4fef6 Release v2.2.0rc1 2019-08-10 11:42:13 -05:00
grossmj
8862b608cf Bump jsonschema to version 3.0.2 2019-08-09 13:47:58 -05:00
grossmj
76832ab83f Fix "Unable to change Remote Main Server IP". Fixes #2823 2019-08-02 17:23:31 -02:30
grossmj
fed245fd34 Fix "AttributeError: 'QGraphicsTextItem' object has no attribute 'locked'". Fixes #2814 2019-07-30 15:50:40 -02:30
grossmj
3e0f1affd0 Merge branch '2.2' 2019-07-12 12:07:33 +02:00
grossmj
2110c2805e Development on 2.2.0dev15 2019-07-11 17:34:56 +02:00
grossmj
46cfdd8314 Release v2.2.0b4 2019-07-11 16:58:35 +02:00
grossmj
f8f648c2b6 Fix issue preventing to open the QFileDialog in the correct directory. 2019-07-11 16:50:59 +02:00
grossmj
7cd0187f33 Remove unused edit readme action. Fixes #2816 2019-07-10 12:10:17 +02:00
grossmj
4d8f362f11 Remove deprecated Qemu parameter to run legacy ASA VMs. Fixes #2827 2019-07-10 11:33:04 +02:00
grossmj
469eaa4737 Upload images on remote controller. Fixes #2828 2019-07-10 11:23:29 +02:00
grossmj
c921224b30 Preferences dialog: send API request only if connected to controller 2019-07-05 18:07:48 +02:00
grossmj
61487b2e2f Fix AttributeError: 'QGraphicsTextItem' object has no attribute 'locked'. Fixes #2814 2019-06-24 15:29:07 +02:00
grossmj
9affca495e Fix KeyError: 'chassis' when converting old IOS templates. Fixes #2813 2019-06-24 15:14:20 +02:00
grossmj
9d8886a640 Development on 2.2.0dev14 2019-06-15 16:38:06 +02:00
grossmj
98cfec1b77 Release v2.2.0b3 2019-06-15 15:39:32 +02:00
grossmj
aed174953e Merge 2.1 into 2.2 branch. 2019-06-15 15:26:20 +02:00
grossmj
f0feea8262 Fix template migration issues from GUI to controller. Fixes https://github.com/GNS3/gns3-gui/issues/2803 2019-06-15 12:52:50 +02:00
grossmj
e2aeaf0a78 Development on 2.1.22dev1 2019-06-14 19:42:18 +02:00
grossmj
b92bb94875 Release v2.1.21 2019-06-14 10:47:08 +02:00
grossmj
c56db59353 Increase timeout from 2 to 5 seconds for synchronous check. Ref #2805 2019-06-10 22:20:40 +02:00
grossmj
a87c4e21d7 %guest-cid% variable implementation for Qemu VMs. Fixes https://github.com/GNS3/gns3-gui/issues/2804 2019-06-04 18:00:44 +02:00
grossmj
ed99a989d7 Fix "General Preferences dialog displays misleading information". Fixes #2801 2019-06-04 17:05:01 +02:00
grossmj
f9a4c9399a Increase timeout from 2 to 5 seconds for synchronous check. Ref #2805 2019-05-31 09:17:34 +02:00
grossmj
efb5c8ca9a Development on 2.2.0dev13 2019-05-29 17:52:38 +07:00
grossmj
0946dff3a0 Release v2.2.0b2 2019-05-29 17:16:59 +07:00
grossmj
d7d96b10e5 Merging 2.1 into 2.2 branch 2019-05-29 16:50:36 +07:00
grossmj
0c0b2d5cb3 Development on 2.1.21dev1 2019-05-29 16:37:43 +07:00
grossmj
450fbc9af3 Release v2.1.20 2019-05-29 15:44:25 +07:00
grossmj
469ee8fab8 Fix KeyError: 'endpoint' issue. Fixes #2802 2019-05-28 23:17:55 +07:00
grossmj
6ccfcaf76e Development on 2.1.20dev1 2019-05-28 16:33:43 +07:00
grossmj
520e857874 Release v2.1.19 2019-05-28 15:23:35 +07:00
grossmj
012c7b4241 Fix wrong aligment of symbols in saved/exported projects. Fixes #2800 2019-05-27 16:33:51 +07:00
grossmj
1d71cd5bf0 Replace urllib.request by Qt implementation for local server synchronous check. Fixes #2793 2019-05-27 16:03:55 +07:00
grossmj
17d1a7f4ed Support snapshots for portable projects. Fixes https://github.com/GNS3/gns3-gui/issues/2792 2019-05-27 15:35:47 +07:00
grossmj
0cd5c08c6b Fix event notification problem for projects and how snapshots are restored. 2019-05-27 15:24:36 +07:00
grossmj
20ac503fe9 Do not close the nodes dock widget when creating project. 2019-05-26 15:20:53 +07:00
grossmj
5f737c2c7c Fix no scan for images on remote controller. Fixes #2799 2019-05-26 15:12:22 +07:00
grossmj
eb1a37be36 Remove problematic tests. 2019-05-25 17:49:33 +07:00
grossmj
07c64b5432 Use QNetworkAccessManager to download custom appliance symbols. 2019-05-25 16:25:02 +07:00
grossmj
ce981d1c49 Experimental auto upgrade should not be available for "frozen" app. Fixes #2797 2019-05-25 15:17:23 +07:00
grossmj
32a9f2556e Use QNetworkAccessManager for synchronous local server check. Ref #2793
Remove unused code.
2019-05-25 15:04:37 +07:00
grossmj
7f08675121 Merging 2.2 into master 2019-05-24 15:27:07 +07:00
grossmj
1dc3c13df2 Don't allow link labels to be moved for locked nodes. Fixes #2794 2019-05-23 15:10:40 +07:00
grossmj
6a6e86b325 Merge 2.1 into 2.2 branch 2019-05-23 14:51:53 +07:00
grossmj
d96277882a Set grid's minimum to 5. Fixes #2795 2019-05-23 14:41:53 +07:00
grossmj
ecec917752 Development on 2.1.19dev1 2019-05-23 14:37:37 +07:00
grossmj
ea9c1a8ee1 Release v2.1.18 2019-05-22 16:13:28 +07:00
grossmj
cfbb09fb57 Fix error in HTTPConnection.request for Python3.6. Fixes #2793 2019-05-22 16:05:34 +07:00
grossmj
dc8aa1fb92 Catch more OSError/PermissionError when checking md5 on remote images. Fixes #2582 2019-05-22 15:23:56 +07:00
grossmj
786cc8aa65 Fix exception when grid size is 0. Fixes #2790 2019-05-22 15:13:16 +07:00
grossmj
4a353e08e3 Catch PermissionError when scanning local image directories. Fixes #2791 2019-05-22 14:55:07 +07:00
grossmj
1371921586 Development on 2.2.0dev12 2019-05-21 19:16:19 +07:00
Jeremy Grossmann
a9a2a541c0 Update gns3-feature-request.md 2019-02-27 15:46:57 +07:00
Jeremy Grossmann
8998c07e0e Update gns3-bug-report.md 2019-02-27 15:46:28 +07:00
Jeremy Grossmann
ba01a89af1 Update gns3-feature-request.md 2019-02-27 15:43:07 +07:00
Jeremy Grossmann
eae07d62ad Update gns3-bug-report.md 2019-02-27 15:42:21 +07:00
Jeremy Grossmann
23903cf0c9 Delete feature_request.md 2019-02-27 15:36:33 +07:00
Jeremy Grossmann
4d908fd855 Delete bug_report.md 2019-02-27 15:36:18 +07:00
Jeremy Grossmann
bb0e67be4f Update issue templates 2019-02-27 15:32:18 +07:00
Jeremy Grossmann
0410c446fc Merge pull request #2466 from dhalperi/fix-typo
Fix a minor typo in the setup wizard
2018-04-05 13:19:38 +07:00
Daniel Halperin
18486e4772 Fix a minor typo in the setup wizard
Eveything -> Everything
2018-04-04 22:53:09 -07:00
68 changed files with 707 additions and 429 deletions

View 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.

View File

@@ -0,0 +1,10 @@
---
name: GNS3 development
about: Any question or discussion regarding GNS3 development
title: ''
labels: ''
assignees: ''
---

View 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
View File

@@ -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.

View File

@@ -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)

View File

@@ -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):

View File

@@ -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"])

View File

@@ -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__':

View File

@@ -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",

View File

@@ -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_()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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):

View File

@@ -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()

View File

@@ -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

View File

@@ -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()

View File

@@ -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:

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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"

View File

@@ -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

View File

@@ -860,6 +860,8 @@
&lt;li&gt;%vm-id% =VM ID&lt;/li&gt;
&lt;li&gt;%project-id% = project ID&lt;/li&gt;
&lt;li&gt;%project-path% = project path&lt;/li&gt;
&lt;li&gt;%console-port% = console port number&lt;/li&gt;
&lt;li&gt;%guest-cid% = unique ID from 3 to 65535&lt;/li&gt;
&lt;/ul&gt;
&lt;/body&gt;&lt;/html&gt;</string>
</property>

View File

@@ -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"))

View File

@@ -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

View File

@@ -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

View File

@@ -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):
"""

View File

@@ -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

View File

@@ -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

View File

@@ -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":

View File

@@ -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()))

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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))

View File

@@ -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"))

View File

@@ -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.

View File

@@ -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.

View File

@@ -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>

View File

@@ -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)

View File

@@ -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">

View File

@@ -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"

View File

@@ -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>

View File

@@ -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)

View File

@@ -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>

View File

@@ -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 &amp;Academy</string>

View File

@@ -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"))

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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:

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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()

View File

@@ -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)

View File

@@ -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()

View File

@@ -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

View File

@@ -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__:

View File

@@ -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

View File

@@ -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

View File

@@ -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",
],
)

View File

@@ -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)

View File

@@ -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():

View File

@@ -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')