mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-06-04 09:42:05 +03:00
Compare commits
220 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb89fe2275 | ||
|
|
7eb2a923b2 | ||
|
|
58052e3cce | ||
|
|
e431104f6b | ||
|
|
a4e9d6b8ce | ||
|
|
f58f5c7b95 | ||
|
|
37faa39309 | ||
|
|
4d64598ed2 | ||
|
|
5132c4e172 | ||
|
|
3c3fdd9ffd | ||
|
|
efa50571c6 | ||
|
|
ca9b10fcca | ||
|
|
8660161b10 | ||
|
|
50ebfb9c06 | ||
|
|
26df59d6b6 | ||
|
|
b903e2ad73 | ||
|
|
b2fe7eb643 | ||
|
|
8095fef228 | ||
|
|
b8da5440f5 | ||
|
|
6cea094e4e | ||
|
|
9ac46c9d50 | ||
|
|
6dc44d5108 | ||
|
|
9c6be0341b | ||
|
|
011a49e998 | ||
|
|
e18c2df5f5 | ||
|
|
1794b8389f | ||
|
|
0379c370eb | ||
|
|
e03550a89b | ||
|
|
c6ea775e81 | ||
|
|
38233ba5e9 | ||
|
|
89c1272bc1 | ||
|
|
8bbb46c599 | ||
|
|
74fca3d736 | ||
|
|
7aeed7aa59 | ||
|
|
aa15ace887 | ||
|
|
2d0a7b5f58 | ||
|
|
20a09b56c1 | ||
|
|
1938cdabae | ||
|
|
8d1bff782c | ||
|
|
4e3eee2383 | ||
|
|
da8aa0d2fd | ||
|
|
5b4481c43a | ||
|
|
593cb8c1fd | ||
|
|
210cf63fe2 | ||
|
|
3b178013c0 | ||
|
|
6e44d6b919 | ||
|
|
6b520b8036 | ||
|
|
803782b9d8 | ||
|
|
d3d6ca3f2e | ||
|
|
f545c793f8 | ||
|
|
47d6a4fef6 | ||
|
|
8862b608cf | ||
|
|
76832ab83f | ||
|
|
fed245fd34 | ||
|
|
3e0f1affd0 | ||
|
|
2110c2805e | ||
|
|
46cfdd8314 | ||
|
|
f8f648c2b6 | ||
|
|
7cd0187f33 | ||
|
|
4d8f362f11 | ||
|
|
469eaa4737 | ||
|
|
c921224b30 | ||
|
|
61487b2e2f | ||
|
|
9affca495e | ||
|
|
9d8886a640 | ||
|
|
98cfec1b77 | ||
|
|
aed174953e | ||
|
|
f0feea8262 | ||
|
|
e2aeaf0a78 | ||
|
|
b92bb94875 | ||
|
|
c56db59353 | ||
|
|
a87c4e21d7 | ||
|
|
ed99a989d7 | ||
|
|
f9a4c9399a | ||
|
|
efb5c8ca9a | ||
|
|
0946dff3a0 | ||
|
|
d7d96b10e5 | ||
|
|
0c0b2d5cb3 | ||
|
|
450fbc9af3 | ||
|
|
469ee8fab8 | ||
|
|
6ccfcaf76e | ||
|
|
520e857874 | ||
|
|
012c7b4241 | ||
|
|
1d71cd5bf0 | ||
|
|
17d1a7f4ed | ||
|
|
0cd5c08c6b | ||
|
|
20ac503fe9 | ||
|
|
5f737c2c7c | ||
|
|
eb1a37be36 | ||
|
|
07c64b5432 | ||
|
|
ce981d1c49 | ||
|
|
32a9f2556e | ||
|
|
7f08675121 | ||
|
|
1dc3c13df2 | ||
|
|
6a6e86b325 | ||
|
|
d96277882a | ||
|
|
ecec917752 | ||
|
|
ea9c1a8ee1 | ||
|
|
cfbb09fb57 | ||
|
|
dc8aa1fb92 | ||
|
|
786cc8aa65 | ||
|
|
4a353e08e3 | ||
|
|
1371921586 | ||
|
|
cd8696a714 | ||
|
|
17799719d6 | ||
|
|
2a59013604 | ||
|
|
1c46299dd9 | ||
|
|
628d7cb909 | ||
|
|
b23c92c0fb | ||
|
|
49ce5a9f38 | ||
|
|
4575ea9f6d | ||
|
|
fd6a00df6a | ||
|
|
58ab4b424a | ||
|
|
1ea1abf582 | ||
|
|
e8caab74f4 | ||
|
|
9fce393fd1 | ||
|
|
827c11ae97 | ||
|
|
eb370d5672 | ||
|
|
7732aaf9a5 | ||
|
|
63161eb760 | ||
|
|
5dba814d1b | ||
|
|
aecdc71f3a | ||
|
|
3209c1d0e6 | ||
|
|
2b3fb53ef2 | ||
|
|
cbbbece0e5 | ||
|
|
56d742b19f | ||
|
|
1f566a31cf | ||
|
|
10d75e15da | ||
|
|
17def7e00a | ||
|
|
106afd0987 | ||
|
|
bba9c5e1d8 | ||
|
|
ae8e8013d4 | ||
|
|
3a5f1d60f9 | ||
|
|
3f6eb61382 | ||
|
|
32bfff381d | ||
|
|
f68a8ea829 | ||
|
|
50066b2f12 | ||
|
|
21a99d4376 | ||
|
|
f97d3041b8 | ||
|
|
31d6a065b0 | ||
|
|
20bf63dbbf | ||
|
|
1c3e0ef640 | ||
|
|
5b58d3ab6d | ||
|
|
554c9205f3 | ||
|
|
543a8e7c33 | ||
|
|
69ef35c674 | ||
|
|
45102a07b6 | ||
|
|
f0b8b22e8a | ||
|
|
d94f5a2d8c | ||
|
|
a768661c05 | ||
|
|
4657b005b6 | ||
|
|
e71da830b0 | ||
|
|
ebf2563200 | ||
|
|
e8eaa00244 | ||
|
|
d750e7a427 | ||
|
|
bfc8adc904 | ||
|
|
4de38ea590 | ||
|
|
cc0c6d0a7a | ||
|
|
d1d0810233 | ||
|
|
ee3c758bb7 | ||
|
|
8f077456b1 | ||
|
|
a29f3e35c0 | ||
|
|
b12cb5c939 | ||
|
|
ba646f5efa | ||
|
|
edafc29cdc | ||
|
|
5aa67d18c0 | ||
|
|
8067aaadd4 | ||
|
|
4a012c4d88 | ||
|
|
7f234aa648 | ||
|
|
dbbcdf0f73 | ||
|
|
a6c56a0963 | ||
|
|
466d427295 | ||
|
|
e5b8bdc106 | ||
|
|
25a6b6b3b1 | ||
|
|
39723a2212 | ||
|
|
9cd0597879 | ||
|
|
c2472bcb22 | ||
|
|
b9caf7216a | ||
|
|
6b23de94b0 | ||
|
|
ab1324ffba | ||
|
|
21bcfde8f3 | ||
|
|
3616bd6c85 | ||
|
|
740e9bab87 | ||
|
|
198cf833e9 | ||
|
|
21f5a64b07 | ||
|
|
fc3781550a | ||
|
|
a9a2a541c0 | ||
|
|
8998c07e0e | ||
|
|
ba01a89af1 | ||
|
|
eae07d62ad | ||
|
|
23903cf0c9 | ||
|
|
4d908fd855 | ||
|
|
bb0e67be4f | ||
|
|
d285e62c04 | ||
|
|
44d70de687 | ||
|
|
752c516f82 | ||
|
|
e1ec6c5771 | ||
|
|
e8308869d9 | ||
|
|
484c5abe9d | ||
|
|
c85112978d | ||
|
|
e57f6db9f0 | ||
|
|
edee26c77c | ||
|
|
fe222b873f | ||
|
|
1acf44de21 | ||
|
|
f8bb6661dd | ||
|
|
ac50dffabd | ||
|
|
fbb28a4325 | ||
|
|
3e47267e35 | ||
|
|
0f9aab9230 | ||
|
|
a5cf5e16b7 | ||
|
|
7f8269bb44 | ||
|
|
097458d108 | ||
|
|
2e0ce6afe0 | ||
|
|
f4cafac9c7 | ||
|
|
7f132fdc36 | ||
|
|
6b7d629755 | ||
|
|
b7ccc37ea5 | ||
|
|
bbe2826c77 | ||
|
|
0410c446fc | ||
|
|
18486e4772 |
34
.github/ISSUE_TEMPLATE/gns3-bug-report.md
vendored
Normal file
34
.github/ISSUE_TEMPLATE/gns3-bug-report.md
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: GNS3 bug report
|
||||
about: Create a report to help us fix a bug
|
||||
title: 'Short description of the bug'
|
||||
labels: Bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Before you start**
|
||||
Please open an issue only if you suspect there is a bug or any problem with GNS3. Go to https://gns3.com/community for any other questions or for requesting help with GNS3.
|
||||
|
||||
You may also post this issue directly on the GNS3 server repository if you know the bug comes from the server: https://github.com/GNS3/gns3-server/issues/new
|
||||
|
||||
**Describe the bug**
|
||||
Please provide a clear and detailed description of what the bug is.
|
||||
|
||||
**GNS3 version and operating system (please complete the following information):**
|
||||
- OS: [e.g. Windows, Linux or macOS]
|
||||
- GNS3 version [e.g. 2.1.14]
|
||||
- Any use of the GNS3 VM or remote server (ESXi, bare metal etc.)
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Screenshots or videos**
|
||||
If applicable, add screenshots (e.g. of the topology and/or error message) or links to videos to help explain the problem. This will help us a lot to quickly find the bug and fix it.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
10
.github/ISSUE_TEMPLATE/gns3-development.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/gns3-development.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: GNS3 development
|
||||
about: Any question or discussion regarding GNS3 development
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
25
.github/ISSUE_TEMPLATE/gns3-feature-request.md
vendored
Normal file
25
.github/ISSUE_TEMPLATE/gns3-feature-request.md
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: GNS3 feature request
|
||||
about: Suggest an idea for GNS3
|
||||
title: 'Short description of the feature request'
|
||||
labels: Enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Before you start**
|
||||
Please check if a similar feature request has already been submitted.
|
||||
|
||||
You may also post this issue directly on the GNS3 server repository if you know the feature request only applies to the server: https://github.com/GNS3/gns3-server/issues/new
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen. If applicable, please provide screenshots
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
193
CHANGELOG
193
CHANGELOG
@@ -1,5 +1,182 @@
|
||||
# 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
|
||||
* Fix cannot load new profile. Fixes #2784
|
||||
* Fix remote packet capture when controller is also remote. Fixes #2785
|
||||
* Set console type to "none" by default for Ethernet switches and add a warning if trying to use "telnet". Fixes https://github.com/GNS3/gns3-gui/issues/2776
|
||||
* 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
|
||||
* Do not make NPF or NPCAP service mandatory to start the local server on Windows.
|
||||
* Do not try to upload a local image that is already installed on the local server.
|
||||
* Back to the major.minor version for config files. Ref https://github.com/GNS3/gns3-gui/issues/2756
|
||||
* Some adjustments with compute WebSocket handling. Ref https://github.com/GNS3/gns3-server/issues/1564
|
||||
* Fix AttributeError: 'GraphicsView' object has no attribute '_import_config_dir'. Fixes #2768
|
||||
* Do not try to lock a SvgIconItem. Fixes #2766
|
||||
* Prevent locked nodes to be deleted. Fixes https://github.com/GNS3/gns3-gui/issues/2764
|
||||
* Add PuTTY 0.71 and mark GNS3 PuTTY as deprecated. Fixes #2758
|
||||
* Fix bug with IOS platform detection. Fixes #2760
|
||||
|
||||
## 2.1.16 15/04/2019
|
||||
|
||||
* Do not make NPF or NPCAP service mandatory to start the local server on Windows.
|
||||
* Fix OverflowError error with progress dialog. Fixes #2767
|
||||
* More fixes for stuck progress window. Fixes #2765
|
||||
* Fix adding multiple devices - stuck progress window. Fixes #2765
|
||||
* Make sure the latest PyQt5 version 5.12.x is used on Windows.
|
||||
* Show a warning when a config export is not supported. Ref #2762
|
||||
|
||||
## 2.1.15 21/03/2019
|
||||
|
||||
* No changes on the GUI.
|
||||
|
||||
## 2.2.0a4 05/04/2019
|
||||
|
||||
* Use the full version number for path to config files. Ref https://github.com/GNS3/gns3-gui/issues/2756
|
||||
* Fix error message when shutting down GUI without a started server.
|
||||
* Fix remote packet capture and make sure packet capture is stopped when deleting an NIO. Fixes https://github.com/GNS3/gns3-gui/issues/2753
|
||||
* Store config files in version specific location
|
||||
* Update pytest from 4.3.1 to 4.4.0
|
||||
* Fix error messages on closing GNS3 application. Fixes https://github.com/GNS3/gns3-gui/issues/2750
|
||||
* Fix bug when list of files for an appliance is not displayed.
|
||||
* Update 'local' to 'bundled' in server & gui, Fixes: #1561
|
||||
|
||||
## 2.2.0a3 25/03/2019
|
||||
|
||||
* Fix bug when changing symbol. Fixes #2740
|
||||
* Fix issue when images are not uploaded from appliance wizard. Ref https://github.com/GNS3/gns3-gui/issues/2738
|
||||
|
||||
## 2.2.0a2 14/03/2019
|
||||
|
||||
* Try to handle stacked widget layout differently. Ref #2605
|
||||
* Early support for symbol themes.
|
||||
* Download custom appliance symbols from GitHub Fix symbol cache issue. Ref https://github.com/GNS3/gns3-gui/issues/2671 Fix temporary directory for symbols was not deleted Fix temporary appliance file was not deleted
|
||||
* New export project wizard.
|
||||
* Update paths for binaries moved to the MacOS directory in GNS3.app
|
||||
* Prevent to change layer position for locked items. Ref #2679
|
||||
* Display available appliances in a hierarchical folder structure. Fixes #2702
|
||||
* Handle locking/unlocking items independently from the layer position.
|
||||
* Better description to why an appliance cannot be installed.
|
||||
* Force jsonschema dependency to 2.6.0
|
||||
* Fix broken idle-pc support. Fixes #1515
|
||||
|
||||
## 2.2.0a1 29/01/2019
|
||||
|
||||
* Fix default NAT interface not restored on Windows. Fixes #2681
|
||||
@@ -99,6 +276,22 @@
|
||||
* Move console to all devices icon after the separation bar. Ref #1272
|
||||
* Lock icons. Ref #1134.
|
||||
|
||||
## 2.1.14 27/02/2019
|
||||
|
||||
* Better description to why an appliance cannot be installed.
|
||||
|
||||
## 2.1.13 26/02/2019
|
||||
|
||||
* Disable computer hibernation detection mechanism. Ref #2678
|
||||
* Add some advice for request timeout message. Fixes #2652
|
||||
* Show/Hide interface labels when status points are not shown. Fixes #2690
|
||||
* Do not print critical message twice on stderr. Replace QMessageBox calls with no parent by log.error()/log.warning().
|
||||
* Show critical messages before the main window runs.
|
||||
* Avoid using PyQt5.Qt, which imports unneeded stuff. Fixes #2592
|
||||
* Fix SIP import error with recent PyQt versions. Fixes #2709
|
||||
* Upgrade to Qt 5.12. Fixes #2636
|
||||
* Adjust the setup wizard (VMware image size, layouts).
|
||||
|
||||
## 2.1.12 23/01/2019
|
||||
|
||||
* Option to resize SVG symbols that are too big (height above 80px, activated by default). Ref #2674.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Run tests inside a container
|
||||
FROM ubuntu:17.10
|
||||
FROM ubuntu:18.04
|
||||
|
||||
MAINTAINER GNS3 Team
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-rrequirements.txt
|
||||
|
||||
pep8==1.7.0
|
||||
pytest==4.1.1
|
||||
pytest==4.4.1
|
||||
pytest-pythonpath==0.7.3 # useful for running tests outside tox
|
||||
pytest-timeout==1.3.3
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
|
||||
from .qt import QtCore
|
||||
from .controller import Controller
|
||||
from .local_config import LocalConfig
|
||||
from .settings import GENERAL_SETTINGS
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -43,10 +46,12 @@ class ApplianceManager(QtCore.QObject):
|
||||
"""
|
||||
|
||||
if self._controller.connected():
|
||||
settings = LocalConfig.instance().loadSectionSettings("MainWindow", GENERAL_SETTINGS)
|
||||
symbol_theme = settings["symbol_theme"]
|
||||
if update is True:
|
||||
self._controller.get("/appliances?update=yes", self._listAppliancesCallback, progressText="Downloading appliances from online registry...")
|
||||
self._controller.get("/appliances?update=yes&symbol_theme={}".format(symbol_theme), self._listAppliancesCallback, progressText="Downloading appliances from online registry...")
|
||||
else:
|
||||
self._controller.get("/appliances", self._listAppliancesCallback)
|
||||
self._controller.get("/appliances?symbol_theme={}".format(symbol_theme), self._listAppliancesCallback)
|
||||
|
||||
def _controllerDisconnectedSlot(self):
|
||||
"""
|
||||
|
||||
@@ -22,7 +22,7 @@ Handles commands typed in the GNS3 console.
|
||||
import sys
|
||||
import cmd
|
||||
import struct
|
||||
import sip
|
||||
from .qt import sip
|
||||
|
||||
from .node import Node
|
||||
from .qt import QtCore
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import sip
|
||||
from .qt import sip
|
||||
import struct
|
||||
import inspect
|
||||
import datetime
|
||||
import platform
|
||||
|
||||
from .qt import QtCore, Qt
|
||||
from .qt import QtCore
|
||||
from .topology import Topology
|
||||
from .version import __version__
|
||||
from .console_cmd import ConsoleCmd
|
||||
@@ -75,7 +75,7 @@ class ConsoleView(PyCutExt, ConsoleCmd):
|
||||
self.intro = "GNS3 management console.\nRunning GNS3 version {} on {} ({}-bit) with Python {} Qt {} and PyQt {}.\n" \
|
||||
"Copyright (c) 2006-{} GNS3 Technologies.\n" \
|
||||
"Use Help -> GNS3 Doctor to detect common issues." \
|
||||
"".format(__version__, platform.system(), bitness, platform.python_version(), QtCore.QT_VERSION_STR, Qt.PYQT_VERSION_STR, current_year)
|
||||
"".format(__version__, platform.system(), bitness, platform.python_version(), QtCore.QT_VERSION_STR, QtCore.PYQT_VERSION_STR, current_year)
|
||||
|
||||
# Parent class initialization
|
||||
try:
|
||||
|
||||
@@ -19,6 +19,7 @@ import os
|
||||
import hashlib
|
||||
import tempfile
|
||||
import json
|
||||
import pathlib
|
||||
|
||||
from .qt import QtCore, QtNetwork, QtGui, QtWidgets, QtWebSockets, qpartial, qslot
|
||||
from .symbol import Symbol
|
||||
@@ -45,15 +46,15 @@ class Controller(QtCore.QObject):
|
||||
super().__init__()
|
||||
self._connected = False
|
||||
self._connecting = False
|
||||
self._notification_stream = None
|
||||
self._version = None
|
||||
self._cache_directory = tempfile.mkdtemp()
|
||||
self._cache_directory = tempfile.TemporaryDirectory(suffix="-gns3")
|
||||
self._http_client = None
|
||||
self._first_error = True
|
||||
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 = {}
|
||||
@@ -245,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():
|
||||
"""
|
||||
@@ -274,7 +269,6 @@ class Controller(QtCore.QObject):
|
||||
if not self._http_client:
|
||||
return
|
||||
|
||||
|
||||
path = self.getStaticCachedPath(url)
|
||||
|
||||
if os.path.exists(path):
|
||||
@@ -313,8 +307,8 @@ class Controller(QtCore.QObject):
|
||||
def getStaticCachedPath(self, url):
|
||||
"""
|
||||
Returns static cached (hashed) path
|
||||
|
||||
:param url:
|
||||
:return:
|
||||
"""
|
||||
m = hashlib.md5()
|
||||
m.update(url.encode())
|
||||
@@ -322,9 +316,22 @@ class Controller(QtCore.QObject):
|
||||
extension = ".svg"
|
||||
else:
|
||||
extension = ".png"
|
||||
path = os.path.join(self._cache_directory, m.hexdigest() + extension)
|
||||
path = os.path.join(self._cache_directory.name, m.hexdigest() + extension)
|
||||
return path
|
||||
|
||||
def clearStaticCache(self):
|
||||
"""
|
||||
Clear the cache directory.
|
||||
"""
|
||||
|
||||
for filename in os.listdir(self._cache_directory.name):
|
||||
if filename.endswith(".svg") or filename.endswith(".png"):
|
||||
try:
|
||||
os.remove(os.path.join(self._cache_directory.name, filename))
|
||||
except OSError as e:
|
||||
log.debug("Error deleting cached symbol '{}':{}".format(filename, e))
|
||||
continue
|
||||
|
||||
def getSymbolIcon(self, symbol_id, callback, fallback=None):
|
||||
"""
|
||||
Get a QIcon for a symbol from the controller
|
||||
@@ -341,10 +348,31 @@ class Controller(QtCore.QObject):
|
||||
self.getStatic(Symbol(symbol_id).url(), qpartial(self._getIconCallback, callback), fallback=fallback)
|
||||
|
||||
def _getIconCallback(self, callback, path):
|
||||
|
||||
pixmap = QtGui.QPixmap(path)
|
||||
if pixmap.isNull():
|
||||
log.debug("Invalid symbol {}".format(path))
|
||||
path = ":/icons/cancel.svg"
|
||||
icon = QtGui.QIcon()
|
||||
icon.addFile(path)
|
||||
callback(icon)
|
||||
|
||||
def uploadSymbol(self, symbol_id, path):
|
||||
|
||||
self.post("/symbols/" + symbol_id + "/raw",
|
||||
qpartial(self._finishSymbolUpload, path),
|
||||
body=pathlib.Path(path), progressText="Uploading {}".format(symbol_id), timeout=None)
|
||||
|
||||
def _finishSymbolUpload(self, path, result, error=False, **kwargs):
|
||||
|
||||
if error:
|
||||
log.error("Error while uploading symbol: {}: {}".format(path, result.get("message", "unknown")))
|
||||
return
|
||||
|
||||
# Refresh the templates list
|
||||
from .template_manager import TemplateManager
|
||||
TemplateManager.instance().templates_changed_signal.emit()
|
||||
|
||||
def getSymbols(self, callback):
|
||||
self.get('/symbols', callback=callback)
|
||||
|
||||
@@ -392,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)
|
||||
|
||||
@@ -415,7 +443,7 @@ class Controller(QtCore.QObject):
|
||||
@qslot
|
||||
def _websocket_error(self, error):
|
||||
if self._notification_stream:
|
||||
log.error(self._notification_stream.errorString())
|
||||
log.error("Websocket notification stream error: {}".format(self._notification_stream.errorString()))
|
||||
self._notification_stream = None
|
||||
self._startListenNotifications()
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ class CrashReport:
|
||||
Report crash to a third party service
|
||||
"""
|
||||
|
||||
DSN = "https://aefc0ee91527434c838dd8bac4abf623:f917ff7284594db68b880e64ae794ebf@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):
|
||||
@@ -133,7 +133,7 @@ class CrashReport:
|
||||
def _add_qt_information(self, context):
|
||||
try:
|
||||
from .qt import QtCore
|
||||
import sip
|
||||
from .qt import sip
|
||||
except ImportError:
|
||||
return context
|
||||
context["psutil:version"] = psutil.__version__
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import sip
|
||||
from ..qt import sip
|
||||
import shutil
|
||||
|
||||
from ..qt import QtWidgets, QtCore, QtGui, qpartial, qslot
|
||||
@@ -36,6 +36,7 @@ from ..compute_manager import ComputeManager
|
||||
from ..controller import Controller
|
||||
from ..local_config import LocalConfig
|
||||
from ..image_upload_manager import ImageUploadManager
|
||||
from ..image_manager import ImageManager
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -76,6 +77,12 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
|
||||
# directories where to search for images
|
||||
images_directories = list()
|
||||
|
||||
for emulator in ("QEMU", "IOU", "DYNAMIPS"):
|
||||
emulator_images_dir = ImageManager.instance().getDirectoryForType(emulator)
|
||||
if os.path.exists(emulator_images_dir):
|
||||
images_directories.append(emulator_images_dir)
|
||||
|
||||
images_directories.append(os.path.dirname(self._path))
|
||||
download_directory = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DownloadLocation)
|
||||
if download_directory != "" and download_directory != os.path.dirname(self._path):
|
||||
@@ -190,7 +197,10 @@ 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:
|
||||
self._registry.getRemoteImageList(self._appliance.emulator(), self._compute_id)
|
||||
if Controller.instance().isRemote() or self._compute_id != "local":
|
||||
self._registry.getRemoteImageList(self._appliance.emulator(), self._compute_id)
|
||||
else:
|
||||
self.images_changed_signal.emit()
|
||||
|
||||
elif self.page(page_id) == self.uiQemuWizardPage:
|
||||
if self._appliance['qemu'].get('kvm', 'require') == 'require':
|
||||
@@ -221,8 +231,15 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
def _uiServerWizardPage_isComplete(self):
|
||||
return self.uiRemoteRadioButton.isEnabled() or self.uiVMRadioButton.isEnabled() or self.uiLocalRadioButton.isEnabled()
|
||||
|
||||
def _imageUploadedCallback(self, result, error=False, **kwargs):
|
||||
self._registry.getRemoteImageList(self._appliance.emulator(), self._compute_id)
|
||||
def _imageUploadedCallback(self, result, error=False, context=None, **kwargs):
|
||||
if context is None:
|
||||
context = {}
|
||||
image_path = context.get("image_path", "unknown")
|
||||
if error:
|
||||
log.error("Error while uploading image '{}': {}".format(image_path, result["message"]))
|
||||
else:
|
||||
log.info("Image '{}' has been successfully uploaded".format(image_path))
|
||||
self._registry.getRemoteImageList(self._appliance.emulator(), self._compute_id)
|
||||
|
||||
def _showApplianceInfoSlot(self):
|
||||
"""
|
||||
@@ -339,6 +356,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
image_widget.setForeground(2, QtGui.QBrush(QtGui.QColor("red")))
|
||||
else:
|
||||
image_widget.setForeground(2, QtGui.QBrush(QtGui.QColor("green")))
|
||||
image_widget.setToolTip(2, image["path"])
|
||||
|
||||
# Associated data stored are col 0: version, col 1: image
|
||||
image_widget.setData(0, QtCore.Qt.UserRole, version)
|
||||
@@ -398,9 +416,14 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
image.get("filesize"),
|
||||
strict_md5_check=not self.allowCustomFiles.isChecked())
|
||||
if img:
|
||||
image["status"] = "Found"
|
||||
if img.location == "local":
|
||||
image["status"] = "Found locally"
|
||||
else:
|
||||
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
|
||||
else:
|
||||
image["status"] = "Missing"
|
||||
self._refreshing = False
|
||||
@@ -495,9 +518,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "Can't access to the image file {}: {}.".format(path, str(e)))
|
||||
return
|
||||
|
||||
image_upload_manger = ImageUploadManager(
|
||||
image, Controller.instance(), self._compute_id,
|
||||
self._imageUploadedCallback, LocalConfig.instance().directFileUpload())
|
||||
image_upload_manger = ImageUploadManager(image, Controller.instance(), self._compute_id, self._imageUploadedCallback, LocalConfig.instance().directFileUpload())
|
||||
image_upload_manger.upload()
|
||||
|
||||
def _getQemuBinariesFromServerCallback(self, result, error=False, **kwargs):
|
||||
@@ -533,7 +554,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
|
||||
if version is None:
|
||||
appliance_configuration = self._appliance.copy()
|
||||
if not "docker" in appliance_configuration:
|
||||
if "docker" not in appliance_configuration:
|
||||
# only Docker do not have version
|
||||
return False
|
||||
else:
|
||||
@@ -554,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
|
||||
|
||||
@@ -583,24 +604,35 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
self._template_created = True
|
||||
self.done(True)
|
||||
|
||||
def _uploadImages(self, version):
|
||||
def _uploadImages(self, name, version):
|
||||
"""
|
||||
Upload an image the compute.
|
||||
"""
|
||||
|
||||
appliance_configuration = self._appliance.search_images_for_version(version)
|
||||
try:
|
||||
appliance_configuration = self._appliance.search_images_for_version(version)
|
||||
except ApplianceError as e:
|
||||
QtWidgets.QMessageBox.critical(self, "Appliance","Cannot install {} version {}: {}".format(name, version, e))
|
||||
return
|
||||
for image in appliance_configuration["images"]:
|
||||
if image["location"] == "local":
|
||||
if not Controller.instance().isRemote() and self._compute_id == "local" and image["path"].startswith(ImageManager.instance().getDirectory()):
|
||||
log.debug("{} is already on the local server".format(image["path"]))
|
||||
return
|
||||
image = Image(self._appliance.emulator(), image["path"], filename=image["filename"])
|
||||
|
||||
image_upload_manger = ImageUploadManager(
|
||||
image, Controller.instance(), self._compute_id,
|
||||
self._applianceImageUploadedCallback, LocalConfig.instance().directFileUpload())
|
||||
image_upload_manger.upload()
|
||||
image_upload_manager = ImageUploadManager(image, Controller.instance(), self._compute_id, self._applianceImageUploadedCallback, LocalConfig.instance().directFileUpload())
|
||||
image_upload_manager.upload()
|
||||
self._image_uploading_count += 1
|
||||
|
||||
def _applianceImageUploadedCallback(self, result, error=False, **kwargs):
|
||||
self._image_uploading_count -= 1
|
||||
def _applianceImageUploadedCallback(self, result, error=False, context=None, **kwargs):
|
||||
if context is None:
|
||||
context = {}
|
||||
image_path = context.get("image_path", "unknown")
|
||||
if error:
|
||||
log.error("Error while uploading image '{}': {}".format(image_path, result["message"]))
|
||||
else:
|
||||
log.info("Image '{}' has been successfully uploaded".format(image_path))
|
||||
self._image_uploading_count -= 1
|
||||
|
||||
def nextId(self):
|
||||
if self.currentPage() == self.uiServerWizardPage:
|
||||
@@ -629,14 +661,17 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
if version is None:
|
||||
return False
|
||||
appliance = current.data(2, QtCore.Qt.UserRole)
|
||||
if not self._appliance.is_version_installable(version["name"]):
|
||||
QtWidgets.QMessageBox.warning(self, "Appliance", "Sorry, you cannot install the '{}' appliance (version {}) with missing files".format(appliance["name"], version["name"]))
|
||||
try:
|
||||
self._appliance.search_images_for_version(version["name"])
|
||||
except ApplianceError as e:
|
||||
QtWidgets.QMessageBox.critical(self, "Appliance", "Cannot install {} version {}: {}".format(appliance["name"], version["name"], e))
|
||||
return False
|
||||
reply = QtWidgets.QMessageBox.question(self, "Appliance", "Would you like to install {} version {}?".format(appliance["name"], version["name"]),
|
||||
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
|
||||
if reply == QtWidgets.QMessageBox.No:
|
||||
return False
|
||||
self._uploadImages(version["name"])
|
||||
|
||||
self._uploadImages(appliance["name"], version["name"])
|
||||
|
||||
elif self.currentPage() == self.uiUsageWizardPage:
|
||||
# validate the usage page
|
||||
|
||||
@@ -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__':
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
from ..qt import QtGui, QtWidgets, qslot
|
||||
from ..ui.filter_dialog_ui import Ui_FilterDialog
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FilterDialog(QtWidgets.QDialog, Ui_FilterDialog):
|
||||
|
||||
@@ -41,7 +44,7 @@ class FilterDialog(QtWidgets.QDialog, Ui_FilterDialog):
|
||||
|
||||
def _listAvailableFiltersCallback(self, result, error=False, *args, **kwargs):
|
||||
if error:
|
||||
QtWidgets.QMessageBox.warning(None, "Link", "Error while listing information about the link: {}".format(result["message"]))
|
||||
log.warning("Error while listing information about the link: {}".format(result["message"]))
|
||||
return
|
||||
self._filters = result
|
||||
self._initialized = True
|
||||
|
||||
@@ -62,7 +62,7 @@ Select each value that appears in the list and click Apply, and note the CPU usa
|
||||
"""
|
||||
QtWidgets.QMessageBox.information(self, "Hints for Idle-PC", help_text)
|
||||
|
||||
def _applySlot(self):
|
||||
def _applySlot(self, update_template=False):
|
||||
"""
|
||||
Applies an Idle-PC value.
|
||||
"""
|
||||
@@ -78,8 +78,9 @@ Select each value that appears in the list and click Apply, and note the CPU usa
|
||||
if hasattr(node, "idlepc") and node.settings()["image"] == ios_image:
|
||||
node.setIdlepc(idlepc)
|
||||
|
||||
# apply the idle-pc to templates with the same IOS image
|
||||
self._router.module().updateImageIdlepc(ios_image, idlepc)
|
||||
if update_template:
|
||||
# apply the idle-pc to templates with the same IOS image
|
||||
self._router.module().updateImageIdlepc(ios_image, idlepc)
|
||||
|
||||
def done(self, result):
|
||||
"""
|
||||
@@ -89,5 +90,5 @@ Select each value that appears in the list and click Apply, and note the CPU usa
|
||||
"""
|
||||
|
||||
if result:
|
||||
self._applySlot()
|
||||
self._applySlot(update_template=True)
|
||||
super().done(result)
|
||||
|
||||
@@ -19,6 +19,7 @@ import sys
|
||||
import tempfile
|
||||
import json
|
||||
import sip
|
||||
import os
|
||||
|
||||
from gns3.qt import QtCore, QtWidgets, qpartial
|
||||
from gns3.controller import Controller
|
||||
@@ -45,9 +46,6 @@ class NewTemplateWizard(QtWidgets.QWizard, Ui_NewTemplateWizard):
|
||||
# we want to see the cancel button on OSX
|
||||
self.setOptions(QtWidgets.QWizard.NoDefaultButton)
|
||||
|
||||
self.uiCreateTemplateManuallyRadioButton.toggled.connect(self._addTemplateToggledSlot)
|
||||
self.uiImportApplianceFromFileRadioButton.toggled.connect(self._addTemplateToggledSlot)
|
||||
|
||||
# add a custom button to show appliance information
|
||||
self.setButtonText(QtWidgets.QWizard.CustomButton1, "&Update from online registry")
|
||||
self.setOption(QtWidgets.QWizard.HaveCustomButton1, True)
|
||||
@@ -56,21 +54,13 @@ class NewTemplateWizard(QtWidgets.QWizard, Ui_NewTemplateWizard):
|
||||
self.uiFilterLineEdit.textChanged.connect(self._filterTextChangedSlot)
|
||||
ApplianceManager.instance().appliances_changed_signal.connect(self._appliancesChangedSlot)
|
||||
|
||||
def _addTemplateToggledSlot(self, checked):
|
||||
|
||||
if checked:
|
||||
self.button(QtWidgets.QWizard.FinishButton).setEnabled(True)
|
||||
self.button(QtWidgets.QWizard.NextButton).setEnabled(False)
|
||||
else:
|
||||
self.button(QtWidgets.QWizard.FinishButton).setEnabled(False)
|
||||
self.button(QtWidgets.QWizard.NextButton).setEnabled(True)
|
||||
|
||||
def _downloadAppliancesSlot(self):
|
||||
"""
|
||||
Request server to update appliances from online registry.
|
||||
"""
|
||||
|
||||
ApplianceManager.instance().refresh(update=True)
|
||||
Controller.instance().clearStaticCache()
|
||||
|
||||
def _appliancesChangedSlot(self):
|
||||
"""
|
||||
@@ -162,33 +152,54 @@ class NewTemplateWizard(QtWidgets.QWizard, Ui_NewTemplateWizard):
|
||||
"""
|
||||
|
||||
self.uiAppliancesTreeWidget.clear()
|
||||
parent_routers = QtWidgets.QTreeWidgetItem(self.uiAppliancesTreeWidget)
|
||||
parent_routers.setText(0, "Routers")
|
||||
parent_routers.setFlags(parent_routers.flags() & ~QtCore.Qt.ItemIsSelectable)
|
||||
parent_switches = QtWidgets.QTreeWidgetItem(self.uiAppliancesTreeWidget)
|
||||
parent_switches.setText(0, "Switches")
|
||||
parent_switches.setFlags(parent_switches.flags() & ~QtCore.Qt.ItemIsSelectable)
|
||||
parent_guests = QtWidgets.QTreeWidgetItem(self.uiAppliancesTreeWidget)
|
||||
parent_guests.setText(0, "Guests")
|
||||
parent_guests.setFlags(parent_guests.flags() & ~QtCore.Qt.ItemIsSelectable)
|
||||
parent_firewalls = QtWidgets.QTreeWidgetItem(self.uiAppliancesTreeWidget)
|
||||
parent_firewalls.setText(0, "Firewalls")
|
||||
parent_firewalls.setFlags(parent_firewalls.flags() & ~QtCore.Qt.ItemIsSelectable)
|
||||
self.uiAppliancesTreeWidget.expandAll()
|
||||
|
||||
for appliance in ApplianceManager.instance().appliances():
|
||||
if appliance_filter is None:
|
||||
appliance_filter = self.uiFilterLineEdit.text().strip()
|
||||
if appliance_filter and appliance_filter.lower() not in appliance["name"].lower():
|
||||
continue
|
||||
|
||||
item = QtWidgets.QTreeWidgetItem(self.uiAppliancesTreeWidget)
|
||||
if appliance["category"] == "router":
|
||||
item = QtWidgets.QTreeWidgetItem(parent_routers)
|
||||
elif appliance["category"].endswith("switch"):
|
||||
item = QtWidgets.QTreeWidgetItem(parent_switches)
|
||||
elif appliance["category"] == "firewall":
|
||||
item = QtWidgets.QTreeWidgetItem(parent_firewalls)
|
||||
elif appliance["category"] == "guest":
|
||||
item = QtWidgets.QTreeWidgetItem(parent_guests)
|
||||
if appliance["builtin"]:
|
||||
appliance_name = appliance["name"]
|
||||
else:
|
||||
appliance_name = "{} (custom)".format(appliance["name"])
|
||||
|
||||
item.setText(0, appliance_name)
|
||||
item.setText(1, appliance["category"].capitalize().replace("_", " "))
|
||||
#item.setText(1, appliance["category"].capitalize().replace("_", " "))
|
||||
|
||||
if "qemu" in appliance:
|
||||
item.setText(2, "Qemu")
|
||||
item.setText(1, "Qemu")
|
||||
elif "iou" in appliance:
|
||||
item.setText(2, "IOU")
|
||||
item.setText(1, "IOU")
|
||||
elif "dynamips" in appliance:
|
||||
item.setText(2, "Dynamips")
|
||||
item.setText(1, "Dynamips")
|
||||
elif "docker" in appliance:
|
||||
item.setText(2, "Docker")
|
||||
item.setText(1, "Docker")
|
||||
else:
|
||||
item.setText(2, "N/A")
|
||||
item.setText(1, "N/A")
|
||||
|
||||
item.setText(3, appliance["vendor_name"])
|
||||
item.setText(2, appliance["vendor_name"])
|
||||
item.setData(0, QtCore.Qt.UserRole, appliance)
|
||||
|
||||
#item.setSizeHint(0, QtCore.QSize(32, 32))
|
||||
@@ -198,6 +209,8 @@ class NewTemplateWizard(QtWidgets.QWizard, Ui_NewTemplateWizard):
|
||||
|
||||
self.uiAppliancesTreeWidget.sortByColumn(0, QtCore.Qt.AscendingOrder)
|
||||
self.uiAppliancesTreeWidget.resizeColumnToContents(0)
|
||||
if not appliance_filter:
|
||||
self.uiAppliancesTreeWidget.collapseAll()
|
||||
|
||||
def initializePage(self, page_id):
|
||||
"""
|
||||
@@ -237,24 +250,39 @@ class NewTemplateWizard(QtWidgets.QWizard, Ui_NewTemplateWizard):
|
||||
return False
|
||||
return True
|
||||
|
||||
def nextId(self):
|
||||
"""
|
||||
Wizard rules!
|
||||
"""
|
||||
|
||||
current_id = self.currentId()
|
||||
if self.page(current_id) == self.uiSelectTemplateSourceWizardPage and \
|
||||
(self.uiImportApplianceFromFileRadioButton.isChecked() or self.uiCreateTemplateManuallyRadioButton.isChecked()):
|
||||
self.done(True)
|
||||
return super().nextId()
|
||||
|
||||
def done(self, result):
|
||||
"""
|
||||
This dialog is closed.
|
||||
|
||||
:param result: ignored
|
||||
"""
|
||||
|
||||
super().done(result)
|
||||
from gns3.main_window import MainWindow
|
||||
if self.currentPage() == self.uiApplianceFromServerWizardPage:
|
||||
items = self.uiAppliancesTreeWidget.selectedItems()
|
||||
for item in items:
|
||||
f = tempfile.NamedTemporaryFile(mode="w+", suffix=".builtin.gns3a", delete=False)
|
||||
json.dump(item.data(0, QtCore.Qt.UserRole), f)
|
||||
f.close()
|
||||
MainWindow.instance().loadPath(f.name)
|
||||
elif self.uiCreateTemplateManuallyRadioButton.isChecked():
|
||||
MainWindow.instance().preferencesActionSlot()
|
||||
elif self.uiImportApplianceFromFileRadioButton.isChecked():
|
||||
if result:
|
||||
#ApplianceManager.instance().appliances_changed_signal.disconnect(self._appliancesChangedSlot)
|
||||
from gns3.main_window import MainWindow
|
||||
MainWindow.instance().openApplianceActionSlot()
|
||||
if self.currentPage() == self.uiApplianceFromServerWizardPage:
|
||||
items = self.uiAppliancesTreeWidget.selectedItems()
|
||||
for item in items:
|
||||
f = tempfile.NamedTemporaryFile(mode="w+", suffix=".builtin.gns3a", delete=False)
|
||||
json.dump(item.data(0, QtCore.Qt.UserRole), f)
|
||||
f.close()
|
||||
MainWindow.instance().loadPath(f.name)
|
||||
try:
|
||||
os.remove(f.name)
|
||||
except OSError:
|
||||
pass
|
||||
elif self.uiCreateTemplateManuallyRadioButton.isChecked():
|
||||
MainWindow.instance().preferencesActionSlot()
|
||||
elif self.uiImportApplianceFromFileRadioButton.isChecked():
|
||||
from gns3.main_window import MainWindow
|
||||
MainWindow.instance().openApplianceActionSlot()
|
||||
|
||||
@@ -184,10 +184,17 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
|
||||
self.uiTitleLabel.setText("{} preferences".format(name))
|
||||
index = self.uiStackedWidget.indexOf(preferences_page)
|
||||
widget = self.uiStackedWidget.widget(index)
|
||||
# self.uiStackedWidget.setMinimumSize(widget.size())
|
||||
self.uiStackedWidget.resize(widget.size())
|
||||
#self.uiStackedWidget.setMinimumSize(widget.size()) # FIXME: this seems to not work on Windows and OSX
|
||||
#self.uiStackedWidget.resize(widget.size())
|
||||
self.uiStackedWidget.setCurrentIndex(index)
|
||||
|
||||
for index in range(0, self.uiStackedWidget.count()):
|
||||
page = self.uiStackedWidget.widget(index)
|
||||
if self.uiStackedWidget.currentIndex() == index:
|
||||
page.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
|
||||
else:
|
||||
page.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored)
|
||||
|
||||
def _applyPreferences(self):
|
||||
"""
|
||||
Saves all the preferences.
|
||||
|
||||
@@ -22,6 +22,7 @@ import shutil
|
||||
from gns3.qt import QtWidgets
|
||||
from gns3.local_config import LocalConfig
|
||||
from gns3.ui.profile_select_dialog_ui import Ui_ProfileSelectDialog
|
||||
from gns3.version import __version_info__
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -39,8 +40,8 @@ class ProfileSelectDialog(QtWidgets.QDialog, Ui_ProfileSelectDialog):
|
||||
self._main.hide()
|
||||
parent = self._main
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self.setupUi(self)
|
||||
self.uiNewPushButton.clicked.connect(self._newPushButtonSlot)
|
||||
self.uiDeletePushButton.clicked.connect(self._deletePushButtonSlot)
|
||||
|
||||
@@ -48,12 +49,13 @@ class ProfileSelectDialog(QtWidgets.QDialog, Ui_ProfileSelectDialog):
|
||||
screen = QtWidgets.QApplication.desktop().screenGeometry()
|
||||
self.move(screen.center() - self.rect().center())
|
||||
|
||||
version = "{}.{}".format(__version_info__[0], __version_info__[1])
|
||||
if sys.platform.startswith("win"):
|
||||
appdata = os.path.expandvars("%APPDATA%")
|
||||
path = os.path.join(appdata, "GNS3")
|
||||
path = os.path.join(appdata, "GNS3", version)
|
||||
else:
|
||||
home = os.path.expanduser("~")
|
||||
path = os.path.join(home, ".config", "GNS3")
|
||||
path = os.path.join(home, ".config", "GNS3", version)
|
||||
self.profiles_path = os.path.join(path, "profiles")
|
||||
|
||||
self.uiShowAtStartupCheckBox.setChecked(LocalConfig.instance().multiProfiles())
|
||||
@@ -65,9 +67,9 @@ class ProfileSelectDialog(QtWidgets.QDialog, Ui_ProfileSelectDialog):
|
||||
|
||||
try:
|
||||
if os.path.exists(self.profiles_path):
|
||||
for profil in sorted(os.listdir(self.profiles_path)):
|
||||
if not profil.startswith("."):
|
||||
self.uiProfileSelectComboBox.addItem(profil)
|
||||
for profile in sorted(os.listdir(self.profiles_path)):
|
||||
if not profile.startswith("."):
|
||||
self.uiProfileSelectComboBox.addItem(profile)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
@@ -79,7 +81,7 @@ class ProfileSelectDialog(QtWidgets.QDialog, Ui_ProfileSelectDialog):
|
||||
super().accept()
|
||||
|
||||
def _newPushButtonSlot(self):
|
||||
profile, ok = QtWidgets.QInputDialog.getText(self.parent(), "New profile", "Profile name:")
|
||||
profile, ok = QtWidgets.QInputDialog.getText(self, "New profile", "Profile name:")
|
||||
if ok:
|
||||
self.uiProfileSelectComboBox.addItem(profile)
|
||||
self.uiProfileSelectComboBox.setCurrentText(profile)
|
||||
@@ -88,13 +90,13 @@ class ProfileSelectDialog(QtWidgets.QDialog, Ui_ProfileSelectDialog):
|
||||
def _deletePushButtonSlot(self):
|
||||
profile = self.uiProfileSelectComboBox.currentText()
|
||||
if profile == "default":
|
||||
QtWidgets.QMessageBox.critical(self.parentWidget(), "Delete profile", "You can't delete the default profile")
|
||||
QtWidgets.QMessageBox.critical(self, "Delete profile", "The default profile cannot be deleted")
|
||||
else:
|
||||
try:
|
||||
shutil.rmtree(os.path.join(self.profiles_path, profile))
|
||||
self._refresh()
|
||||
except (OSError, PermissionError) as e:
|
||||
QtWidgets.QMessageBox.critical(self.parentWidget(), "Delete profile", str(e))
|
||||
QtWidgets.QMessageBox.critical(self, "Cannot delete profile", str(e))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
131
gns3/dialogs/project_export_wizard.py
Normal file
131
gns3/dialogs/project_export_wizard.py
Normal file
@@ -0,0 +1,131 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2018 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 datetime
|
||||
|
||||
from gns3.qt import QtCore, QtWidgets
|
||||
from ..local_server import LocalServer
|
||||
from ..utils.progress_dialog import ProgressDialog
|
||||
from ..utils.export_project_worker import ExportProjectWorker
|
||||
from ..ui.export_project_wizard_ui import Ui_ExportProjectWizard
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ExportProjectWizard(QtWidgets.QWizard, Ui_ExportProjectWizard):
|
||||
"""
|
||||
Export project wizard.
|
||||
"""
|
||||
|
||||
def __init__(self, project, parent):
|
||||
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self._project = project
|
||||
self._path = None
|
||||
self.setWizardStyle(QtWidgets.QWizard.ModernStyle)
|
||||
if sys.platform.startswith("darwin"):
|
||||
# we want to see the cancel button on OSX
|
||||
self.setOptions(QtWidgets.QWizard.NoDefaultButton)
|
||||
|
||||
self.uiCompressionComboBox.addItem("None", "none")
|
||||
self.uiCompressionComboBox.addItem("Zip compression (deflate)", "zip")
|
||||
self.uiCompressionComboBox.addItem("Bzip2 compression", "bzip2")
|
||||
self.uiCompressionComboBox.addItem("Lzma compression", "lzma")
|
||||
|
||||
# set zip compression by default
|
||||
self.uiCompressionComboBox.setCurrentIndex(1)
|
||||
self.helpRequested.connect(self._showHelpSlot)
|
||||
self.uiPathBrowserToolButton.clicked.connect(self._pathBrowserSlot)
|
||||
|
||||
readme_text = "Project: '{}' created on {}\nAuthor: John Doe <john.doe@example.com>\n\nNo project description was given".format(self._project.name(), datetime.date.today())
|
||||
self.uiReadmeTextEdit.setPlainText(readme_text)
|
||||
|
||||
def _pathBrowserSlot(self):
|
||||
|
||||
directory = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DocumentsLocation)
|
||||
if len(directory) == 0:
|
||||
directory = LocalServer.instance().localServerSettings()["projects_path"]
|
||||
|
||||
path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export portable project", directory,
|
||||
"GNS3 Portable Project (*.gns3project *.gns3p)",
|
||||
"GNS3 Portable Project (*.gns3project *.gns3p)")
|
||||
if path is None or len(path) == 0:
|
||||
return
|
||||
|
||||
self.uiPathLineEdit.setText(path)
|
||||
|
||||
def _showHelpSlot(self):
|
||||
|
||||
include_image_help = """Including base images means additional images will not be requested to
|
||||
import the project on another computer, however the resulting file will be much bigger.
|
||||
Also, you are responsible to check if you have the right to distribute the image(s) as part of the project.
|
||||
"""
|
||||
QtWidgets.QMessageBox.information(self, "Help about export a project", include_image_help)
|
||||
|
||||
def validateCurrentPage(self):
|
||||
"""
|
||||
Validates if the project can be exported.
|
||||
"""
|
||||
|
||||
if self.currentPage() == self.uiExportOptionsWizardPage:
|
||||
path = self.uiPathLineEdit.text().strip()
|
||||
if not path:
|
||||
QtWidgets.QMessageBox.critical(self, "Export project", "Please select a path where to export the project")
|
||||
return False
|
||||
|
||||
if not path.endswith(".gns3project") and not path.endswith(".gns3p"):
|
||||
path += ".gns3project"
|
||||
try:
|
||||
open(path, 'wb+').close()
|
||||
except OSError as e:
|
||||
QtWidgets.QMessageBox.critical(self, "Export project", "Cannot export project to '{}': {}".format(path, e))
|
||||
return False
|
||||
self._path = path
|
||||
elif self.currentPage() == self.uiProjectReadmeWizardPage:
|
||||
text = self.uiReadmeTextEdit.toPlainText().strip()
|
||||
if text:
|
||||
self._project.post("/files/README.txt", self._saveReadmeCallback, body=text)
|
||||
return True
|
||||
|
||||
def _saveReadmeCallback(self, result, error=False, **kwargs):
|
||||
if error:
|
||||
QtWidgets.QMessageBox.critical(self, "Export project", "Could not created readme file")
|
||||
|
||||
def done(self, result):
|
||||
"""
|
||||
This dialog is closed.
|
||||
"""
|
||||
|
||||
if result:
|
||||
if self.uiIncludeImagesCheckBox.isChecked():
|
||||
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, 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_()
|
||||
super().done(result)
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import pathlib
|
||||
from ..qt import QtCore, QtGui, QtWidgets, qpartial, sip_is_deleted
|
||||
from ..qt.qimage_svg_renderer import QImageSvgRenderer
|
||||
from ..ui.symbol_selection_dialog_ui import Ui_SymbolSelectionDialog
|
||||
from ..local_server import LocalServer
|
||||
from ..controller import Controller
|
||||
from ..symbol import Symbol
|
||||
|
||||
@@ -56,7 +55,6 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
|
||||
self.uiCustomSymbolRadioButton.toggled.connect(self._customSymbolToggledSlot)
|
||||
self.uiBuiltInSymbolRadioButton.toggled.connect(self._builtInSymbolToggledSlot)
|
||||
self.uiSearchLineEdit.textChanged.connect(self._searchTextChangedSlot)
|
||||
self.uiBuiltinSymbolOnlyCheckBox.toggled.connect(self._builtinSymbolOnlyToggledSlot)
|
||||
if not SymbolSelectionDialog._symbols_dir:
|
||||
SymbolSelectionDialog._symbols_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.PicturesLocation)
|
||||
|
||||
@@ -64,9 +62,10 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
|
||||
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).hide()
|
||||
|
||||
self.uiBuiltInSymbolRadioButton.setChecked(True)
|
||||
self.uiSymbolListWidget.setFocus()
|
||||
self.uiSymbolListWidget.setIconSize(QtCore.QSize(64, 64))
|
||||
self.uiSymbolTreeWidget.setFocus()
|
||||
self.uiSymbolTreeWidget.setIconSize(QtCore.QSize(64, 64))
|
||||
self._symbol_items = []
|
||||
self._parents = {}
|
||||
|
||||
Controller.instance().get("/symbols", self._listSymbolsCallback)
|
||||
|
||||
@@ -78,17 +77,24 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
|
||||
self._symbol_items = []
|
||||
for symbol in result:
|
||||
symbol = Symbol(**symbol)
|
||||
name = os.path.splitext(symbol.filename())[0]
|
||||
item = QtWidgets.QListWidgetItem(self.uiSymbolListWidget)
|
||||
item.setData(QtCore.Qt.UserRole, symbol)
|
||||
self._symbol_items.append(item)
|
||||
item.setText(name)
|
||||
theme = symbol.theme()
|
||||
if theme not in self._parents:
|
||||
parent = QtWidgets.QTreeWidgetItem(self.uiSymbolTreeWidget)
|
||||
parent.setText(0, theme)
|
||||
font = parent.font(0)
|
||||
font.setBold(True)
|
||||
parent.setFont(0, font)
|
||||
parent.setFlags(parent.flags() & ~QtCore.Qt.ItemIsSelectable)
|
||||
self._parents[theme] = parent
|
||||
else:
|
||||
parent = self._parents[theme]
|
||||
|
||||
image = QtGui.QImage(64, 64, QtGui.QImage.Format_ARGB32)
|
||||
# Set the ARGB to 0 to prevent rendering artifacts
|
||||
image.fill(0x00000000)
|
||||
icon = QtGui.QIcon(QtGui.QPixmap.fromImage(image))
|
||||
item.setIcon(icon)
|
||||
name = os.path.splitext(symbol.filename())[0]
|
||||
item = QtWidgets.QTreeWidgetItem(parent)
|
||||
item.setData(0, QtCore.Qt.UserRole, symbol)
|
||||
item.setToolTip(0, symbol.id())
|
||||
self._symbol_items.append(item)
|
||||
item.setText(0, name)
|
||||
|
||||
def render(item, path):
|
||||
if sip_is_deleted(item):
|
||||
@@ -99,14 +105,11 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
|
||||
image.fill(0x00000000)
|
||||
svg_renderer.render(QtGui.QPainter(image))
|
||||
icon = QtGui.QIcon(QtGui.QPixmap.fromImage(image))
|
||||
item.setIcon(icon)
|
||||
item.setIcon(0, icon)
|
||||
|
||||
Controller.instance().getStatic(symbol.url(), qpartial(render, item))
|
||||
self.adjustSize()
|
||||
|
||||
def _builtinSymbolOnlyToggledSlot(self, checked):
|
||||
self._filter()
|
||||
|
||||
def _searchTextChangedSlot(self, text):
|
||||
self._filter()
|
||||
|
||||
@@ -116,10 +119,10 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
|
||||
"""
|
||||
text = self.uiSearchLineEdit.text()
|
||||
for item in self._symbol_items:
|
||||
if self.uiBuiltinSymbolOnlyCheckBox.isChecked() and not item.data(QtCore.Qt.UserRole).builtin():
|
||||
if not item.data(0, QtCore.Qt.UserRole).builtin():
|
||||
item.setHidden(True)
|
||||
else:
|
||||
if len(text.strip()) == 0 or text.strip().lower() in item.text().lower():
|
||||
if len(text.strip()) == 0 or text.strip().lower() in item.text(0).lower():
|
||||
item.setHidden(False)
|
||||
else:
|
||||
item.setHidden(True)
|
||||
@@ -158,16 +161,18 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
|
||||
"""
|
||||
|
||||
symbol_path = self.getSymbol()
|
||||
if not symbol_path:
|
||||
return False
|
||||
for item in self._items:
|
||||
item.setSymbol(symbol_path)
|
||||
return True
|
||||
|
||||
def getSymbol(self):
|
||||
|
||||
if self.uiSymbolListWidget.isEnabled():
|
||||
current = self.uiSymbolListWidget.currentItem()
|
||||
if current:
|
||||
return current.data(QtCore.Qt.UserRole).id()
|
||||
if self.uiSymbolTreeWidget.isEnabled():
|
||||
current = self.uiSymbolTreeWidget.currentItem()
|
||||
if current and current.parent():
|
||||
return current.data(0, QtCore.Qt.UserRole).id()
|
||||
else:
|
||||
return os.path.basename(self.uiSymbolLineEdit.text())
|
||||
return None
|
||||
|
||||
@@ -21,7 +21,7 @@ Graphical view on the scene where items are drawn.
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sip
|
||||
from .qt import sip
|
||||
import sys
|
||||
|
||||
from .qt import QtCore, QtGui, QtNetwork, QtWidgets, qpartial, qslot
|
||||
@@ -49,7 +49,7 @@ from .compute_manager import ComputeManager
|
||||
from .utils.get_icon import get_icon
|
||||
|
||||
# link items
|
||||
from .items.link_item import LinkItem
|
||||
from .items.link_item import LinkItem, SvgIconItem
|
||||
from .items.ethernet_link_item import EthernetLinkItem
|
||||
from .items.serial_link_item import SerialLinkItem
|
||||
|
||||
@@ -466,13 +466,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
if not event.modifiers() & QtCore.Qt.ControlModifier:
|
||||
for it in self.scene().items():
|
||||
it.setSelected(False)
|
||||
if item.zValue() < 0:
|
||||
item.setFlag(item.ItemIsSelectable, True)
|
||||
item.setSelected(True)
|
||||
self._showDeviceContextualMenu(QtGui.QCursor.pos())
|
||||
if not sip.isdeleted(item) and item.zValue() < 0:
|
||||
item.setFlag(item.ItemIsSelectable, False)
|
||||
|
||||
else:
|
||||
self._showDeviceContextualMenu(QtGui.QCursor.pos())
|
||||
# when more than one item is selected display the contextual menu even if mouse is not above an item
|
||||
@@ -632,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:
|
||||
@@ -1059,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)
|
||||
@@ -1121,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))
|
||||
|
||||
@@ -1204,12 +1194,12 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self,
|
||||
"Import {}".format(os.path.basename(config_file)),
|
||||
self._import_config_dir,
|
||||
self._import_config_directory,
|
||||
"All files (*.*);;Config files (*.cfg)",
|
||||
"Config files (*.cfg)")
|
||||
if not path:
|
||||
continue
|
||||
self._import_config_dir = os.path.dirname(path)
|
||||
self._import_config_directory = os.path.dirname(path)
|
||||
item.node().importFile(config_file, path)
|
||||
|
||||
def editConfigActionSlot(self):
|
||||
@@ -1351,9 +1341,9 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
node.setIdlepc(idlepc)
|
||||
# apply the idle-pc to templates with the same IOS image
|
||||
router.module().updateImageIdlepc(ios_image, idlepc)
|
||||
QtWidgets.QMessageBox.information(self, "Auto Idle-PC", "Idle-PC value {} has been applied on {} and all routers with IOS image {}".format(idlepc,
|
||||
router.name(),
|
||||
ios_image))
|
||||
QtWidgets.QMessageBox.information(self, "Auto Idle-PC", "Idle-PC value {} has been applied on {} and all templates with IOS image {}".format(idlepc,
|
||||
router.name(),
|
||||
ios_image))
|
||||
|
||||
def duplicateActionSlot(self):
|
||||
"""
|
||||
@@ -1461,11 +1451,10 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
|
||||
for item in self.scene().selectedItems():
|
||||
if item.parentItem() is None:
|
||||
current_zvalue = item.zValue()
|
||||
if not (item.flags() & QtWidgets.QGraphicsItem.ItemIsMovable):
|
||||
log.error("Cannot move object to a upper layer because it is locked")
|
||||
continue
|
||||
item.setZValue(current_zvalue + 1)
|
||||
item.setZValue(item.zValue() + 1)
|
||||
item.updateNode()
|
||||
item.update()
|
||||
|
||||
@@ -1477,17 +1466,13 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
|
||||
for item in self.scene().selectedItems():
|
||||
if item.parentItem() is None:
|
||||
current_zvalue = item.zValue()
|
||||
if not (item.flags() & QtWidgets.QGraphicsItem.ItemIsMovable):
|
||||
log.error("Cannot move object to a lower layer because it is locked")
|
||||
continue
|
||||
item.setZValue(current_zvalue - 1)
|
||||
item.setZValue(item.zValue() - 1)
|
||||
item.updateNode()
|
||||
item.update()
|
||||
|
||||
if item.zValue() == -1:
|
||||
self._background_warning_msgbox.showMessage("Object moved to a background layer. You will now have to use the right-click action to select this object in the future and raise it to layer 0 to be able to move it")
|
||||
|
||||
def lockActionSlot(self):
|
||||
"""
|
||||
Slot to receive events from the lock action in the
|
||||
@@ -1495,8 +1480,11 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
"""
|
||||
|
||||
for item in self.scene().selectedItems():
|
||||
if not isinstance(item, LinkItem):
|
||||
item.setZValue(-(item.zValue() + 1))
|
||||
if not isinstance(item, LinkItem) and not isinstance(item, LabelItem) and not isinstance(item, SvgIconItem):
|
||||
if item.locked() is True:
|
||||
item.setLocked(False)
|
||||
else:
|
||||
item.setLocked(True)
|
||||
if item.parentItem() is None:
|
||||
item.updateNode()
|
||||
item.update()
|
||||
@@ -1510,7 +1498,11 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
selected_nodes = []
|
||||
for item in self.scene().selectedItems():
|
||||
if isinstance(item, NodeItem):
|
||||
selected_nodes.append(item.node())
|
||||
node = item.node()
|
||||
if node.locked():
|
||||
QtWidgets.QMessageBox.critical(self, "Delete", "Cannot delete node '{}' because it is locked".format(node.name()))
|
||||
return
|
||||
selected_nodes.append(node)
|
||||
if selected_nodes:
|
||||
if len(selected_nodes) > 1:
|
||||
question = "Do you want to permanently delete these {} nodes?".format(len(selected_nodes))
|
||||
@@ -1557,9 +1549,9 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
pos = self.mapToScene(pos)
|
||||
return TemplateManager().instance().createNodeFromTemplateId(self._topology.project(), template_id, pos.x(), pos.y())
|
||||
|
||||
def createNodeItem(self, node, symbol, x, y, z):
|
||||
def createNodeItem(self, node, symbol, x, y):
|
||||
node.setSymbol(symbol)
|
||||
node.setPos(x, y, z)
|
||||
node.setPos(x, y)
|
||||
node_item = NodeItem(node)
|
||||
|
||||
self.scene().addItem(node_item)
|
||||
@@ -1582,18 +1574,18 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
if self._main_window and not sip.isdeleted(self._main_window):
|
||||
QtWidgets.QMessageBox.critical(self._main_window, name, message.strip())
|
||||
|
||||
def createDrawingItem(self, type, x, y, z, rotation=0, svg=None, drawing_id=None):
|
||||
def createDrawingItem(self, type, x, y, z, locked=False, rotation=0, svg=None, drawing_id=None):
|
||||
|
||||
if type == "ellipse":
|
||||
item = EllipseItem(pos=QtCore.QPoint(x, y), z=z, rotation=rotation, project=self._topology.project(), drawing_id=drawing_id, svg=svg)
|
||||
item = EllipseItem(pos=QtCore.QPoint(x, y), z=z, locked=locked, rotation=rotation, project=self._topology.project(), drawing_id=drawing_id, svg=svg)
|
||||
elif type == "rect":
|
||||
item = RectangleItem(pos=QtCore.QPoint(x, y), z=z, rotation=rotation, project=self._topology.project(), drawing_id=drawing_id, svg=svg)
|
||||
item = RectangleItem(pos=QtCore.QPoint(x, y), z=z, locked=locked, rotation=rotation, project=self._topology.project(), drawing_id=drawing_id, svg=svg)
|
||||
elif type == "line":
|
||||
item = LineItem(pos=QtCore.QPoint(x, y), dst=QtCore.QPoint(200, 0), z=z, rotation=rotation, project=self._topology.project(), drawing_id=drawing_id, svg=svg)
|
||||
item = LineItem(pos=QtCore.QPoint(x, y), dst=QtCore.QPoint(200, 0), z=z, locked=locked, rotation=rotation, project=self._topology.project(), drawing_id=drawing_id, svg=svg)
|
||||
elif type == "image":
|
||||
item = ImageItem(pos=QtCore.QPoint(x, y), z=z, rotation=rotation, project=self._topology.project(), drawing_id=drawing_id, svg=svg)
|
||||
item = ImageItem(pos=QtCore.QPoint(x, y), z=z, rotation=rotation, locked=locked, project=self._topology.project(), drawing_id=drawing_id, svg=svg)
|
||||
elif type == "text":
|
||||
item = TextItem(pos=QtCore.QPoint(x, y), z=z, rotation=rotation, project=self._topology.project(), drawing_id=drawing_id, svg=svg)
|
||||
item = TextItem(pos=QtCore.QPoint(x, y), z=z, rotation=rotation, locked=locked, project=self._topology.project(), drawing_id=drawing_id, svg=svg)
|
||||
|
||||
if drawing_id is None:
|
||||
item.create()
|
||||
@@ -1605,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)
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sip
|
||||
from .qt import sip
|
||||
import json
|
||||
import copy
|
||||
import http
|
||||
@@ -207,16 +207,16 @@ class HTTPClient(QtCore.QObject):
|
||||
Called when a query upload progress
|
||||
"""
|
||||
if not sip_is_deleted(HTTPClient._progress_callback):
|
||||
HTTPClient._progress_callback.progress_signal.emit(query_id, str(sent), str(total))
|
||||
HTTPClient._progress_callback.progress_signal.emit(query_id, str(abs(sent)), str(abs(total)))
|
||||
|
||||
def _notify_progress_download(self, query_id, sent, total):
|
||||
"""
|
||||
Called when a query download progress
|
||||
"""
|
||||
if not sip_is_deleted(HTTPClient._progress_callback):
|
||||
# abs() for maxium because sometimes the system send negative
|
||||
# abs() for maximum because sometimes the system send negative
|
||||
# values
|
||||
HTTPClient._progress_callback.progress_signal.emit(query_id, str(sent), str(abs(total)))
|
||||
HTTPClient._progress_callback.progress_signal.emit(query_id, str(abs(sent)), str(abs(total)))
|
||||
|
||||
@classmethod
|
||||
def setProgressCallback(cls, progress_callback):
|
||||
@@ -301,16 +301,17 @@ class HTTPClient(QtCore.QObject):
|
||||
if self._shutdown:
|
||||
return
|
||||
|
||||
# TODO: clean this
|
||||
# We try to detect computer hibernation
|
||||
# if time between two query is too long we trigger a disconnect
|
||||
if self._max_time_difference_between_queries:
|
||||
now = datetime.datetime.now().timestamp()
|
||||
if self._last_query_timestamp is not None and now > self._last_query_timestamp + self._max_time_difference_between_queries:
|
||||
log.warning("Synchronisation lost with the server.")
|
||||
self.disconnect()
|
||||
self._last_query_timestamp = None
|
||||
return
|
||||
self._last_query_timestamp = now
|
||||
# if self._max_time_difference_between_queries:
|
||||
# now = datetime.datetime.now().timestamp()
|
||||
# if self._last_query_timestamp is not None and now > self._last_query_timestamp + self._max_time_difference_between_queries:
|
||||
# log.warning("Synchronisation lost with the server.")
|
||||
# self.disconnect()
|
||||
# self._last_query_timestamp = None
|
||||
# return
|
||||
# self._last_query_timestamp = now
|
||||
|
||||
request = qpartial(self._executeHTTPQuery, method, path, qpartial(callback), body, context,
|
||||
downloadProgressCallback=downloadProgressCallback,
|
||||
@@ -469,7 +470,9 @@ class HTTPClient(QtCore.QObject):
|
||||
"""
|
||||
host = self._getHostForQuery()
|
||||
request = websocket.request()
|
||||
request.setUrl(QtCore.QUrl("ws://{host}:{port}{prefix}{path}".format(host=host, port=self._port, path=path, prefix=prefix)))
|
||||
ws_url = "ws://{host}:{port}{prefix}{path}".format(host=host, port=self._port, path=path, prefix=prefix)
|
||||
log.debug("Connecting to WebSocket endpoint: {}".format(ws_url))
|
||||
request.setUrl(QtCore.QUrl(ws_url))
|
||||
self._addAuth(request)
|
||||
websocket.open(request)
|
||||
return websocket
|
||||
@@ -489,7 +492,7 @@ class HTTPClient(QtCore.QObject):
|
||||
|
||||
def _paramsToQueryString(self, params):
|
||||
"""
|
||||
:param params: Dictionnary of query string parameters
|
||||
:param params: Dictionary of query string parameters
|
||||
:returns: String of the query string
|
||||
"""
|
||||
if params == {}:
|
||||
@@ -617,7 +620,7 @@ class HTTPClient(QtCore.QObject):
|
||||
# We check if we received HTTP headers
|
||||
if not sip.isdeleted(response) and response.isRunning() and not len(response.rawHeaderList()) > 0:
|
||||
if not response.error() != QtNetwork.QNetworkReply.NoError:
|
||||
log.warning("Timeout after {} seconds for request {}".format(timeout, response.url().toString()))
|
||||
log.warning("Timeout after {} seconds for request {}. Please check the connection is not blocked by a firewall or an anti-virus.".format(timeout, response.url().toString()))
|
||||
response.abort()
|
||||
|
||||
def disconnect(self):
|
||||
@@ -637,7 +640,7 @@ class HTTPClient(QtCore.QObject):
|
||||
|
||||
def _processError(self, response, server, callback, context, request_body, ignore_errors, error_code):
|
||||
if error_code != QtNetwork.QNetworkReply.NoError:
|
||||
error_message = response.errorString()
|
||||
error_message = "{} ({}:{})".format(response.errorString(), self._host, self._port)
|
||||
|
||||
if not ignore_errors:
|
||||
log.debug("Response error: %s for %s (error: %d)", error_message, response.url().toString(), error_code)
|
||||
@@ -724,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):
|
||||
|
||||
@@ -176,7 +176,7 @@ class ImageManager:
|
||||
if node_type == 'DYNAMIPS':
|
||||
return os.path.join(self.getDirectory(), 'IOS')
|
||||
else:
|
||||
return os.path.join(self.getDirectory(), node_type)
|
||||
return os.path.join(self.getDirectory(), node_type.upper())
|
||||
|
||||
@staticmethod
|
||||
def instance():
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import urllib.parse
|
||||
|
||||
@@ -37,6 +38,9 @@ class ImageUploadManager(object):
|
||||
self._controller = controller
|
||||
|
||||
def upload(self):
|
||||
if not os.path.exists(self._image.path):
|
||||
log.error("Image '{}' could not be found".format(self._image.path))
|
||||
return
|
||||
if self._directFileUpload:
|
||||
# first obtain endpoint and know when target request
|
||||
self._controller.getEndpoint(self._getComputePath(), self._compute_id, self._onLoadEndpointCallback, showProgress=False)
|
||||
@@ -71,19 +75,16 @@ class ImageUploadManager(object):
|
||||
self._callback(result, error, **kwargs)
|
||||
|
||||
def _fileUploadToCompute(self, endpoint):
|
||||
log.info("Uploading file to compute: {}".format(endpoint))
|
||||
|
||||
log.debug("Uploading image '{}' to compute".format(self._image.path))
|
||||
parse_results = urllib.parse.urlparse(endpoint)
|
||||
network_manager = self._controller.getHttpClient().getNetworkManager()
|
||||
client = HTTPClient.fromUrl(endpoint, network_manager=network_manager)
|
||||
# We don't retry connection as in case of fail we try direct file upload
|
||||
client.setMaxRetryConnection(0)
|
||||
client.createHTTPQuery(
|
||||
'POST', parse_results.path, self._checkIfSuccessfulCallback, body=pathlib.Path(self._image.path),
|
||||
progressText="Uploading {}".format(self._image.filename), timeout=None, prefix="")
|
||||
client.createHTTPQuery('POST', parse_results.path, self._checkIfSuccessfulCallback, body=pathlib.Path(self._image.path),
|
||||
context={"image_path": self._image.path}, progressText="Uploading {}".format(self._image.filename), timeout=None, prefix="")
|
||||
|
||||
def _fileUploadToController(self):
|
||||
log.info("Uploading file to controller: {}".format(self._getComputePath()))
|
||||
self._controller.postCompute(
|
||||
self._getComputePath(), self._compute_id, self._callback, body=pathlib.Path(self._image.path),
|
||||
progressText="Uploading {}".format(self._image.filename), timeout=None)
|
||||
log.debug("Uploading image '{}' to controller".format(self._image.path))
|
||||
self._controller.postCompute(self._getComputePath(), self._compute_id, self._callback, body=pathlib.Path(self._image.path),
|
||||
context={"image_path": self._image.path}, progressText="Uploading {}".format(self._image.filename), timeout=None)
|
||||
|
||||
@@ -41,9 +41,10 @@ class DrawingItem:
|
||||
Base class for non emulation item
|
||||
"""
|
||||
|
||||
def __init__(self, project=None, pos=None, drawing_id=None, svg=None, z=0, rotation=0, **kws):
|
||||
def __init__(self, project=None, pos=None, drawing_id=None, svg=None, z=0, locked=False, rotation=0, **kws):
|
||||
self._id = drawing_id
|
||||
self._deleting = False
|
||||
self._locked = locked
|
||||
if self._id is None:
|
||||
self._id = str(uuid.uuid4())
|
||||
self.setFlags(QtWidgets.QGraphicsItem.ItemIsMovable | QtWidgets.QGraphicsItem.ItemIsFocusable | QtWidgets.QGraphicsItem.ItemIsSelectable | QtWidgets.QGraphicsItem.ItemSendsGeometryChanges)
|
||||
@@ -65,6 +66,8 @@ class DrawingItem:
|
||||
if rotation:
|
||||
self.setRotation(rotation)
|
||||
|
||||
self.setLocked(locked)
|
||||
|
||||
def drawing_id(self):
|
||||
return self._id
|
||||
|
||||
@@ -106,6 +109,7 @@ class DrawingItem:
|
||||
return False
|
||||
self.setPos(QtCore.QPoint(result["x"], result["y"]))
|
||||
self.setZValue(result["z"])
|
||||
self.setLocked(result["locked"])
|
||||
self.setRotation(result["rotation"])
|
||||
if "svg" in result:
|
||||
self.fromSvg(result["svg"])
|
||||
@@ -149,6 +153,7 @@ class DrawingItem:
|
||||
"x": int(self.pos().x()),
|
||||
"y": int(self.pos().y()),
|
||||
"z": int(self.zValue()),
|
||||
"locked": self._locked,
|
||||
"rotation": int(self.rotation())
|
||||
}
|
||||
svg = self.toSvg()
|
||||
@@ -158,23 +163,29 @@ class DrawingItem:
|
||||
self._hash_svg = hash_svg
|
||||
return data
|
||||
|
||||
def setZValue(self, value):
|
||||
def locked(self):
|
||||
"""
|
||||
Sets a new Z value.
|
||||
Is the drawing locked
|
||||
"""
|
||||
|
||||
return self._locked
|
||||
|
||||
def setLocked(self, locked):
|
||||
"""
|
||||
Sets the locked value.
|
||||
|
||||
:param value: Z value
|
||||
"""
|
||||
|
||||
QtWidgets.QGraphicsItem.setZValue(self, value)
|
||||
|
||||
if self.zValue() < 0:
|
||||
if locked is True:
|
||||
self.setFlag(self.ItemIsMovable, False)
|
||||
else:
|
||||
self.setFlag(self.ItemIsMovable, True)
|
||||
self._locked = locked
|
||||
|
||||
def deleting(self):
|
||||
"""
|
||||
Is the link being deleted
|
||||
Is the drawing being deleted
|
||||
"""
|
||||
|
||||
return self._deleting
|
||||
|
||||
@@ -22,7 +22,7 @@ Graphical representation of an ellipse on the QGraphicsScene.
|
||||
import math
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from ..qt import QtCore, QtGui, QtWidgets
|
||||
from ..qt import QtWidgets
|
||||
from .shape_item import ShapeItem
|
||||
|
||||
|
||||
@@ -48,13 +48,6 @@ class EllipseItem(QtWidgets.QGraphicsEllipseItem, ShapeItem):
|
||||
super().paint(painter, option, widget)
|
||||
self.drawLayerInfo(painter)
|
||||
|
||||
def setZValue(self, value):
|
||||
"""
|
||||
Sets Z value of the item
|
||||
:param value: z layer
|
||||
"""
|
||||
return ShapeItem.setZValue(self, value)
|
||||
|
||||
def toSvg(self):
|
||||
"""
|
||||
Return an SVG version of the shape
|
||||
|
||||
@@ -106,7 +106,7 @@ class EthernetLinkItem(LinkItem):
|
||||
"""
|
||||
|
||||
QtWidgets.QGraphicsPathItem.paint(self, painter, option, widget)
|
||||
if not self._adding_flag and self._settings["draw_link_status_points"]:
|
||||
if not self._adding_flag:
|
||||
|
||||
# points disappears if nodes are too close to each others.
|
||||
if self.length < 100:
|
||||
@@ -147,11 +147,13 @@ 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()
|
||||
|
||||
painter.drawPoint(point1)
|
||||
if self._settings["draw_link_status_points"]:
|
||||
painter.drawPoint(point1)
|
||||
|
||||
if self._link.suspended() or self._destination_port.status() == Port.suspended:
|
||||
# link or port is suspended
|
||||
@@ -188,10 +190,12 @@ 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()
|
||||
|
||||
painter.drawPoint(point2)
|
||||
if self._settings["draw_link_status_points"]:
|
||||
painter.drawPoint(point2)
|
||||
|
||||
self._drawSymbol()
|
||||
|
||||
@@ -67,13 +67,6 @@ class ImageItem(QtSvg.QGraphicsSvgItem, DrawingItem):
|
||||
super().paint(painter, option, widget)
|
||||
self.drawLayerInfo(painter)
|
||||
|
||||
def setZValue(self, value):
|
||||
"""
|
||||
Sets Z value of the item
|
||||
:param value: z layer
|
||||
"""
|
||||
return DrawingItem.setZValue(self, value)
|
||||
|
||||
def fromSvg(self, svg):
|
||||
renderer = QImageSvgRenderer(svg)
|
||||
self.setSharedRenderer(renderer)
|
||||
|
||||
@@ -170,19 +170,6 @@ class LabelItem(QtWidgets.QGraphicsTextItem):
|
||||
zval = str(int(self.zValue()))
|
||||
painter.drawText(QtCore.QPointF(center.x(), center.y()), zval)
|
||||
|
||||
def setZValue(self, value):
|
||||
"""
|
||||
Sets a new Z value.
|
||||
|
||||
:param value: Z value
|
||||
"""
|
||||
|
||||
super().setZValue(value)
|
||||
if self.zValue() < 0:
|
||||
self.setFlag(self.ItemIsMovable, False)
|
||||
else:
|
||||
self.setFlag(self.ItemIsMovable, True)
|
||||
|
||||
def setStyle(self, new_style):
|
||||
"""
|
||||
Set text style using a SVG style
|
||||
|
||||
@@ -63,13 +63,6 @@ class LineItem(QtWidgets.QGraphicsLineItem, DrawingItem):
|
||||
super().paint(painter, option, widget)
|
||||
self.drawLayerInfo(painter)
|
||||
|
||||
def setZValue(self, value):
|
||||
"""
|
||||
Sets Z value of the item
|
||||
:param value: z layer
|
||||
"""
|
||||
return DrawingItem.setZValue(self, value)
|
||||
|
||||
def toSvg(self):
|
||||
"""
|
||||
Return an SVG version of the shape
|
||||
@@ -126,8 +119,8 @@ class LineItem(QtWidgets.QGraphicsLineItem, DrawingItem):
|
||||
:param event: QGraphicsSceneHoverEvent instance
|
||||
"""
|
||||
|
||||
# objects on the background layer don't need cursors
|
||||
if self.zValue() >= 0:
|
||||
# locked objects don't need cursors
|
||||
if not self.locked():
|
||||
|
||||
if self._isHorizontalLine():
|
||||
if event.pos().x() > (self.line().x2() - self._border):
|
||||
@@ -218,6 +211,6 @@ class LineItem(QtWidgets.QGraphicsLineItem, DrawingItem):
|
||||
:param event: QGraphicsSceneHoverEvent instance
|
||||
"""
|
||||
|
||||
# objects on the background layer don't need cursors
|
||||
if self.zValue() >= 0:
|
||||
# locked objects don't need cursors
|
||||
if not self.locked():
|
||||
self._graphics_view.setCursor(QtCore.Qt.ArrowCursor)
|
||||
|
||||
@@ -36,7 +36,8 @@ class SvgIconItem(QtSvg.QGraphicsSvgItem):
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
|
||||
self.parentItem().mousePressEvent(event)
|
||||
if self.parentItem():
|
||||
self.parentItem().mousePressEvent(event)
|
||||
event.accept()
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
Graphical representation of a node on the QGraphicsScene.
|
||||
"""
|
||||
|
||||
import sip
|
||||
from ..qt import sip
|
||||
|
||||
from ..qt import QtCore, QtGui, QtWidgets, QtSvg, qslot
|
||||
from ..qt.qimage_svg_renderer import QImageSvgRenderer
|
||||
@@ -50,6 +50,7 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
|
||||
# link items connected to this node item.
|
||||
self._links = []
|
||||
self._symbol = None
|
||||
self._locked = False
|
||||
|
||||
# says if the attached node has been initialized
|
||||
# by the server.
|
||||
@@ -78,7 +79,8 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
|
||||
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges)
|
||||
self.setAcceptHoverEvents(True)
|
||||
|
||||
# update z value and set proper flags - not movable in case z < 0
|
||||
# update z value and locked state
|
||||
self.setLocked(self._node.locked())
|
||||
self.setZValue(self._node.z())
|
||||
|
||||
# connect signals to know about some events
|
||||
@@ -101,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
|
||||
@@ -272,7 +263,7 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
|
||||
self.setSymbol(self._node.settings().get("symbol"))
|
||||
self.setPos(self._node.settings().get("x", 0), self._node.settings().get("y", 0))
|
||||
self.setZValue(self._node.settings().get("z", 0))
|
||||
|
||||
self.setLocked(self._node.settings().get("locked", False))
|
||||
self._updateLabel()
|
||||
|
||||
# update the link tooltips in case the
|
||||
@@ -388,7 +379,7 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
|
||||
self._node_label.setStyle(style)
|
||||
self._node_label.setRotation(label_data.get("rotation", 0))
|
||||
|
||||
if self._node.z() < 0:
|
||||
if self._node.locked():
|
||||
self._node_label.setFlag(self.ItemIsMovable, False)
|
||||
|
||||
if label_data["x"] is None:
|
||||
@@ -531,7 +522,21 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
|
||||
"""
|
||||
|
||||
super().setZValue(value)
|
||||
if self.zValue() < 0:
|
||||
for link in self._links:
|
||||
link.adjust()
|
||||
|
||||
def locked(self):
|
||||
|
||||
return self._locked
|
||||
|
||||
def setLocked(self, locked):
|
||||
"""
|
||||
Sets the locked value.
|
||||
|
||||
:param value: Z value
|
||||
"""
|
||||
|
||||
if locked is True:
|
||||
self.setFlag(self.ItemIsMovable, False)
|
||||
if self._node_label:
|
||||
self._node_label.setFlag(self.ItemIsMovable, False)
|
||||
@@ -541,6 +546,7 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
|
||||
self._node_label.setFlag(self.ItemIsMovable, True)
|
||||
for link in self._links:
|
||||
link.adjust()
|
||||
self._locked = locked
|
||||
|
||||
def hoverEnterEvent(self, event):
|
||||
"""
|
||||
|
||||
@@ -46,13 +46,6 @@ class RectangleItem(QtWidgets.QGraphicsRectItem, ShapeItem):
|
||||
super().paint(painter, option, widget)
|
||||
self.drawLayerInfo(painter)
|
||||
|
||||
def setZValue(self, value):
|
||||
"""
|
||||
Sets Z value of the item
|
||||
:param value: z layer
|
||||
"""
|
||||
return ShapeItem.setZValue(self, value)
|
||||
|
||||
def toSvg(self):
|
||||
"""
|
||||
Return an SVG version of the shape
|
||||
|
||||
@@ -107,7 +107,7 @@ class SerialLinkItem(LinkItem):
|
||||
|
||||
QtWidgets.QGraphicsPathItem.paint(self, painter, option, widget)
|
||||
|
||||
if not self._adding_flag and self._settings["draw_link_status_points"]:
|
||||
if not self._adding_flag:
|
||||
|
||||
# points disappears if nodes are too close to each others.
|
||||
if self.length < 80:
|
||||
@@ -136,11 +136,13 @@ 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()
|
||||
|
||||
painter.drawPoint(self.source_point)
|
||||
if self._settings["draw_link_status_points"]:
|
||||
painter.drawPoint(self.source_point)
|
||||
|
||||
# destination point color
|
||||
if self._link.suspended() or self._destination_port.status() == Port.suspended:
|
||||
@@ -166,10 +168,12 @@ 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()
|
||||
|
||||
painter.drawPoint(self.destination_point)
|
||||
if self._settings["draw_link_status_points"]:
|
||||
painter.drawPoint(self.destination_point)
|
||||
|
||||
self._drawSymbol()
|
||||
|
||||
@@ -147,8 +147,8 @@ class ShapeItem(DrawingItem):
|
||||
:param event: QGraphicsSceneHoverEvent instance
|
||||
"""
|
||||
|
||||
# objects on the background layer don't need cursors
|
||||
if self.zValue() >= 0:
|
||||
# locked objects don't need cursors
|
||||
if not self.locked():
|
||||
if event.pos().x() > (self.rect().right() - self._border):
|
||||
self._graphics_view.setCursor(QtCore.Qt.SizeHorCursor)
|
||||
elif event.pos().x() < (self.rect().left() + self._border):
|
||||
@@ -167,8 +167,8 @@ class ShapeItem(DrawingItem):
|
||||
:param event: QGraphicsSceneHoverEvent instance
|
||||
"""
|
||||
|
||||
# objects on the background layer don't need cursors
|
||||
if self.zValue() >= 0:
|
||||
# locked objects don't need cursors
|
||||
if not self.locked():
|
||||
self._graphics_view.setCursor(QtCore.Qt.ArrowCursor)
|
||||
|
||||
def fromSvg(self, svg):
|
||||
|
||||
@@ -111,13 +111,6 @@ class TextItem(QtWidgets.QGraphicsTextItem, DrawingItem):
|
||||
super().paint(painter, option, widget)
|
||||
self.drawLayerInfo(painter)
|
||||
|
||||
def setZValue(self, value):
|
||||
"""
|
||||
Sets Z value of the item
|
||||
:param value: z layer
|
||||
"""
|
||||
return DrawingItem.setZValue(self, value)
|
||||
|
||||
def toSvg(self):
|
||||
"""
|
||||
Return an SVG version of the text
|
||||
|
||||
54
gns3/link.py
54
gns3/link.py
@@ -21,10 +21,10 @@ Manages and stores everything needed for a connection between 2 devices.
|
||||
|
||||
import os
|
||||
import re
|
||||
import sip
|
||||
from .qt import sip
|
||||
import uuid
|
||||
|
||||
from .qt import QtCore, QtWidgets
|
||||
from .qt import QtCore
|
||||
from .controller import Controller
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ class Link(QtCore.QObject):
|
||||
self._deleting = False
|
||||
self._capture_file_path = None
|
||||
self._capture_file = None
|
||||
self._capture_compute_id = None
|
||||
self._initialized = False
|
||||
self._filters = {}
|
||||
self._suspend = False
|
||||
@@ -103,24 +104,28 @@ class Link(QtCore.QObject):
|
||||
Controller.instance().post("/projects/{project_id}/links".format(project_id=source_node.project().id()), self._linkCreatedCallback, body=body)
|
||||
|
||||
def _parseResponse(self, result):
|
||||
self._capturing = result.get("capturing", False)
|
||||
|
||||
# If the controller is remote the capture path should be rewrite to something local
|
||||
self._capturing = result.get("capturing", False)
|
||||
if self._capturing:
|
||||
if Controller.instance().isRemote():
|
||||
if self._capture_file_path is None and result.get("capture_file_path", None) is not None:
|
||||
self._capture_compute_id = result.get("capture_compute_id", None)
|
||||
self._capture_file_path = result.get("capture_file_path", None)
|
||||
if Controller.instance().isRemote() or (self._capture_compute_id and self._capture_compute_id != "local"):
|
||||
# We need to stream the pcap file content if the controller or compute is remote
|
||||
if Controller.instance().isRemote() or self._capture_file_path is None:
|
||||
self._capture_file = QtCore.QTemporaryFile()
|
||||
self._capture_file.open(QtCore.QFile.WriteOnly)
|
||||
self._capture_file.setAutoRemove(True)
|
||||
self._capture_file_path = self._capture_file.fileName()
|
||||
Controller.instance().get("/projects/{project_id}/links/{link_id}/pcap".format(project_id=self.project().id(),link_id=self._link_id),
|
||||
None,
|
||||
showProgress=False,
|
||||
downloadProgressCallback=self._downloadPcapProgress,
|
||||
ignoreErrors=True, # If something is wrong avoid disconnect us from server
|
||||
timeout=None)
|
||||
else:
|
||||
self._capture_file_path = result["capture_file_path"]
|
||||
else:
|
||||
self._capture_file = QtCore.QFile(self._capture_file_path)
|
||||
self._capture_file.open(QtCore.QFile.WriteOnly)
|
||||
Controller.instance().get("/projects/{project_id}/links/{link_id}/pcap".format(project_id=self.project().id(), link_id=self._link_id),
|
||||
None,
|
||||
showProgress=False,
|
||||
downloadProgressCallback=self._downloadPcapProgress,
|
||||
ignoreErrors=True, # If something is wrong avoid disconnect us from server
|
||||
timeout=None)
|
||||
log.debug("Capturing packets to '{}'".format(self._capture_file_path))
|
||||
|
||||
if "nodes" in result:
|
||||
self._nodes = result["nodes"]
|
||||
@@ -169,7 +174,7 @@ class Link(QtCore.QObject):
|
||||
|
||||
def updateLinkCallback(self, result, error=False, *args, **kwargs):
|
||||
if error:
|
||||
QtWidgets.QMessageBox.warning(None, "Update link", "Error while updating link: {}".format(result["message"]))
|
||||
log.warning("Error while updating link: {}".format(result["message"]))
|
||||
return
|
||||
self._parseResponse(result)
|
||||
|
||||
@@ -219,7 +224,7 @@ class Link(QtCore.QObject):
|
||||
|
||||
def _linkCreatedCallback(self, result, error=False, **kwargs):
|
||||
if error:
|
||||
QtWidgets.QMessageBox.warning(None, "Create link", "Error while creating link: {}".format(result["message"]))
|
||||
log.warning("Error while creating link: {}".format(result["message"]))
|
||||
self.deleteLink(skip_controller=True)
|
||||
return
|
||||
|
||||
@@ -353,7 +358,7 @@ class Link(QtCore.QObject):
|
||||
if error:
|
||||
log.error("Error while starting capture on link: {}".format(result["message"]))
|
||||
return
|
||||
self._parseResponse(result)
|
||||
#self._parseResponse(result)
|
||||
|
||||
def _downloadPcapProgress(self, content, server=None, context={}, **kwargs):
|
||||
"""
|
||||
@@ -366,15 +371,16 @@ class Link(QtCore.QObject):
|
||||
self._capture_file.flush()
|
||||
|
||||
def stopCapture(self):
|
||||
if Controller.instance().isRemote():
|
||||
|
||||
if Controller.instance().isRemote() or (self._capture_compute_id and self._capture_compute_id != "local"):
|
||||
if self._capture_file:
|
||||
self._capture_file.close()
|
||||
self._capture_file = None
|
||||
if self._capture_file_path and os.path.exists(self._capture_file_path):
|
||||
try:
|
||||
os.remove(self._capture_file_path)
|
||||
except OSError as e:
|
||||
log.error("Cannot remove file {}: {}".format(self._capture_file_path, e))
|
||||
# if self._capture_file_path and os.path.exists(self._capture_file_path):
|
||||
# try:
|
||||
# os.remove(self._capture_file_path)
|
||||
# except OSError as e:
|
||||
# log.error("Cannot remove file {}: {}".format(self._capture_file_path, e))
|
||||
self._capture_file_path = None
|
||||
Controller.instance().post("/projects/{project_id}/links/{link_id}/stop_capture".format(project_id=self.project().id(),
|
||||
link_id=self._link_id),
|
||||
@@ -384,7 +390,7 @@ class Link(QtCore.QObject):
|
||||
if error:
|
||||
log.error("Error while stopping capture on link: {}".format(result["message"]))
|
||||
return
|
||||
self._parseResponse(result)
|
||||
#self._parseResponse(result)
|
||||
|
||||
def get(self, path, callback, **kwargs):
|
||||
"""
|
||||
|
||||
@@ -24,8 +24,10 @@ import copy
|
||||
import psutil
|
||||
|
||||
from .qt import QtCore, QtWidgets
|
||||
from .version import __version__
|
||||
from .version import __version__, __version_info__
|
||||
from .utils import parse_version
|
||||
from .local_server_config import LocalServerConfig
|
||||
from .settings import LOCAL_SERVER_SETTINGS
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -87,8 +89,25 @@ class LocalConfig(QtCore.QObject):
|
||||
try:
|
||||
# create the config file if it doesn't exist
|
||||
os.makedirs(os.path.dirname(self._config_file), exist_ok=True)
|
||||
with open(self._config_file, "w", encoding="utf-8") as f:
|
||||
json.dump({"version": __version__, "type": "settings"}, f)
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
old_config_path = os.path.join(os.path.expandvars("%APPDATA%"), "GNS3", filename)
|
||||
else:
|
||||
old_config_path = os.path.join(os.path.expanduser("~"), ".config", "GNS3", filename)
|
||||
|
||||
# TODO: migrate versioned config file from a previous version of GNS3 (for instance 2.2 -> 2.3) + support profiles
|
||||
if os.path.exists(old_config_path):
|
||||
# migrate post version 2.2.0 configuration file
|
||||
shutil.copyfile(old_config_path, self._config_file)
|
||||
# reset the local server path and ubridge path
|
||||
settings = LocalServerConfig.instance().loadSettings("Server", LOCAL_SERVER_SETTINGS)
|
||||
settings["path"] = ""
|
||||
settings["ubridge_path"] = ""
|
||||
LocalServerConfig.instance().saveSettings("Server", settings)
|
||||
else:
|
||||
# create a new config
|
||||
with open(self._config_file, "w", encoding="utf-8") as f:
|
||||
json.dump({"version": __version__, "type": "settings"}, f)
|
||||
except OSError as e:
|
||||
log.error("Could not create the config file {}: {}".format(self._config_file, e))
|
||||
|
||||
@@ -114,17 +133,18 @@ class LocalConfig(QtCore.QObject):
|
||||
self._config_file = None
|
||||
self._resetLoadConfig()
|
||||
|
||||
|
||||
def configDirectory(self):
|
||||
"""
|
||||
Get the configuration directory
|
||||
"""
|
||||
|
||||
version = "{}.{}".format(__version_info__[0], __version_info__[1])
|
||||
if sys.platform.startswith("win"):
|
||||
appdata = os.path.expandvars("%APPDATA%")
|
||||
path = os.path.join(appdata, "GNS3")
|
||||
path = os.path.join(appdata, "GNS3", version)
|
||||
else:
|
||||
home = os.path.expanduser("~")
|
||||
path = os.path.join(home, ".config", "GNS3")
|
||||
path = os.path.join(home, ".config", "GNS3", version)
|
||||
|
||||
if self._profile is not None:
|
||||
path = os.path.join(path, "profiles", self._profile)
|
||||
@@ -146,8 +166,9 @@ class LocalConfig(QtCore.QObject):
|
||||
# In < 1.4 on Mac the config was in a gns3.net directory
|
||||
# We have move to same location as Linux
|
||||
if sys.platform.startswith("darwin"):
|
||||
version = "{}.{}".format(__version_info__[0], __version_info__[1])
|
||||
old_path = os.path.join(os.path.expanduser("~"), ".config", "gns3.net")
|
||||
new_path = os.path.join(os.path.expanduser("~"), ".config", "GNS3")
|
||||
new_path = os.path.join(os.path.expanduser("~"), ".config", "GNS3", version)
|
||||
if os.path.exists(old_path) and not os.path.exists(new_path):
|
||||
try:
|
||||
shutil.copytree(old_path, new_path)
|
||||
@@ -156,7 +177,7 @@ class LocalConfig(QtCore.QObject):
|
||||
|
||||
def _migrateOldConfig(self):
|
||||
"""
|
||||
Migrate pre 1.4 config
|
||||
Migrate config from a previous version.
|
||||
"""
|
||||
|
||||
# Display an error if settings come from a more recent version of GNS3
|
||||
@@ -164,28 +185,31 @@ class LocalConfig(QtCore.QObject):
|
||||
# settings from 1.6.1 with 1.5.1 you will have an error
|
||||
if "version" in self._settings:
|
||||
if parse_version(self._settings["version"])[:2] > parse_version(__version__)[:2]:
|
||||
QtWidgets.QApplication(sys.argv) # We need to create an application because settings are loaded before Qt init
|
||||
QtWidgets.QMessageBox.critical(None, "Version error", "Your settings are for version {} of GNS3. You cannot use a previous version of GNS3 without risking losing data. If you want to reset delete the settings in {}".format(self._settings["version"], self.configDirectory()))
|
||||
app = QtWidgets.QApplication(sys.argv) # We need to create an application because settings are loaded before Qt init
|
||||
error_message = "Settings are for version {} of GNS3. It is not possible to use a previous version of GNS3 without risking losing data. Delete the settings in '{}' to start GNS3".format(self._settings["version"], self.configDirectory())
|
||||
QtWidgets.QMessageBox.critical(False, "Version error", error_message)
|
||||
# Exit immediately not clean but we want to avoid any side effect that could corrupt the file
|
||||
QtCore.QTimer.singleShot(0, app.quit)
|
||||
app.exec_()
|
||||
sys.exit(1)
|
||||
|
||||
if "version" not in self._settings or parse_version(self._settings["version"]) < parse_version("1.4.0alpha1"):
|
||||
|
||||
servers = self._settings.get("Servers", {})
|
||||
servers = self._settings.get("Servers", {})
|
||||
|
||||
if "LocalServer" in self._settings:
|
||||
if "LocalServer" in self._settings:
|
||||
servers["local_server"] = copy.copy(self._settings["LocalServer"])
|
||||
|
||||
# We migrate the server binary for OSX due to the change from py2app to CX freeze
|
||||
# We migrate the server binary for OSX due to the change from py2app to CX freeze
|
||||
if servers["local_server"]["path"] == "/Applications/GNS3.app/Contents/Resources/server/Contents/MacOS/gns3server":
|
||||
servers["local_server"]["path"] = "gns3server"
|
||||
|
||||
if "RemoteServers" in self._settings:
|
||||
if "RemoteServers" in self._settings:
|
||||
servers["remote_servers"] = copy.copy(self._settings["RemoteServers"])
|
||||
|
||||
self._settings["Servers"] = servers
|
||||
self._settings["Servers"] = servers
|
||||
|
||||
if "GUI" in self._settings:
|
||||
if "GUI" in self._settings:
|
||||
main_window = self._settings.get("MainWindow", {})
|
||||
main_window["hide_getting_started_dialog"] = self._settings["GUI"].get("hide_getting_started_dialog", False)
|
||||
self._settings["MainWindow"] = main_window
|
||||
@@ -198,7 +222,7 @@ class LocalConfig(QtCore.QObject):
|
||||
if self._settings["MainWindow"].get("telnet_console_command") not in PRECONFIGURED_TELNET_CONSOLE_COMMANDS.values():
|
||||
self._settings["MainWindow"]["telnet_console_command"] = DEFAULT_TELNET_CONSOLE_COMMAND
|
||||
|
||||
# Migrate 1.X to 2.0
|
||||
# Migrate 1.X to 2.0
|
||||
if "version" not in self._settings or parse_version(self._settings["version"]) < parse_version("2.0.0"):
|
||||
if "Qemu" in self._settings:
|
||||
# The internet VM is replaced by the nat Node
|
||||
@@ -209,7 +233,7 @@ class LocalConfig(QtCore.QObject):
|
||||
vms.append(vm)
|
||||
self._settings["Qemu"]["vms"] = vms
|
||||
|
||||
# Starting with 2.0.0dev5 IOU licence is stored in the settings
|
||||
# Starting with 2.0.0dev5 IOU licence is stored in the settings
|
||||
if "version" not in self._settings or parse_version(self._settings["version"]) < parse_version("2.0.0"):
|
||||
if "IOU" in self._settings and "iourc_path" in self._settings["IOU"] and "iourc_content" not in self._settings["IOU"]:
|
||||
try:
|
||||
|
||||
@@ -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,13 +372,15 @@ class LocalServer(QtCore.QObject):
|
||||
|
||||
self._checkUbridgePermissions()
|
||||
|
||||
if sys.platform.startswith('win'):
|
||||
if not self._checkWindowsService("npf") and not self._checkWindowsService("npcap"):
|
||||
QtWidgets.QMessageBox.critical(self.parent(), "Error", "The NPF or NPCAP service is not installed, please install Winpcap or Npcap and reboot.")
|
||||
return False
|
||||
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
|
||||
local_server_path = self.localServerPath()
|
||||
if not local_server_path:
|
||||
@@ -516,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:
|
||||
|
||||
13
gns3/main.py
13
gns3/main.py
@@ -137,10 +137,7 @@ def main():
|
||||
# packaged binary
|
||||
frozen_dir = os.path.dirname(os.path.abspath(sys.executable))
|
||||
if sys.platform.startswith("darwin"):
|
||||
frozen_dirs = [
|
||||
frozen_dir,
|
||||
os.path.normpath(os.path.join(frozen_dir, '..', 'Resources'))
|
||||
]
|
||||
frozen_dirs = [frozen_dir]
|
||||
elif sys.platform.startswith("win"):
|
||||
frozen_dirs = [
|
||||
frozen_dir,
|
||||
@@ -268,7 +265,10 @@ def main():
|
||||
# issue when people run GNS3 from the .dmg
|
||||
if sys.platform.startswith("darwin") and hasattr(sys, "frozen"):
|
||||
if not os.path.realpath(sys.executable).startswith("/Applications"):
|
||||
QtWidgets.QMessageBox.critical(None, "Error", "You need to copy GNS3 in your /Applications folder before using it.")
|
||||
error_message = "GNS3.app must be moved to the '/Applications' folder before it can be used"
|
||||
QtWidgets.QMessageBox.critical(False, "Loading error", error_message)
|
||||
QtCore.QTimer.singleShot(0, app.quit)
|
||||
app.exec_()
|
||||
sys.exit(1)
|
||||
|
||||
global mainwindow
|
||||
@@ -292,7 +292,6 @@ def main():
|
||||
mainwindow.show()
|
||||
|
||||
exit_code = app.exec_()
|
||||
|
||||
signal.signal(signal.SIGINT, orig_sigint)
|
||||
signal.signal(signal.SIGTERM, orig_sigterm)
|
||||
|
||||
@@ -301,7 +300,7 @@ def main():
|
||||
# We force deleting the app object otherwise it's segfault on Fedora
|
||||
del app
|
||||
# We force a full garbage collect before exit
|
||||
# for unknow reason otherwise Qt Segfault on OSX in some
|
||||
# for unknown reason otherwise Qt Segfault on OSX in some
|
||||
# conditions
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
@@ -42,8 +42,9 @@ from .dialogs.edit_project_dialog import EditProjectDialog
|
||||
from .dialogs.setup_wizard import SetupWizard
|
||||
from .settings import GENERAL_SETTINGS
|
||||
from .items.node_item import NodeItem
|
||||
from .items.link_item import LinkItem
|
||||
from .items.link_item import LinkItem, SvgIconItem
|
||||
from .items.shape_item import ShapeItem
|
||||
from .items.label_item import LabelItem
|
||||
from .topology import Topology
|
||||
from .http_client import HTTPClient
|
||||
from .progress import Progress
|
||||
@@ -189,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)
|
||||
@@ -258,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)
|
||||
@@ -328,7 +329,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
def _openWebInterfaceActionSlot(self):
|
||||
if Controller.instance().connected():
|
||||
base_url = Controller.instance().httpClient().fullUrl()
|
||||
webui_url = "{}/static/web-ui/local".format(base_url)
|
||||
webui_url = "{}/static/web-ui/bundled".format(base_url)
|
||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl(webui_url))
|
||||
|
||||
def _showGridActionSlot(self):
|
||||
@@ -362,15 +363,13 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
:return: None
|
||||
"""
|
||||
|
||||
for item in self.uiGraphicsView.items():
|
||||
if not isinstance(item, LinkItem):
|
||||
if self.uiLockAllAction.isChecked() and item.zValue() >= 0:
|
||||
item.setZValue(-(item.zValue() + 1))
|
||||
if item.parentItem() is None:
|
||||
item.updateNode()
|
||||
item.update()
|
||||
elif not self.uiLockAllAction.isChecked() and item.zValue() < 0:
|
||||
item.setZValue(-(item.zValue() + 1))
|
||||
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()
|
||||
@@ -394,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())
|
||||
@@ -438,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)",
|
||||
@@ -1068,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)
|
||||
@@ -1132,6 +1122,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self._settings["state"] = bytes(self.saveState().toBase64()).decode()
|
||||
self.setSettings(self._settings)
|
||||
|
||||
Controller.instance().stopListenNotifications()
|
||||
server = LocalServer.instance()
|
||||
server.stopLocalServer(wait=True)
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class EthernetSwitch(Node):
|
||||
# this is an always-on node
|
||||
self.setStatus(Node.started)
|
||||
self._always_on = True
|
||||
self.settings().update({"ports_mapping": [], "console_type": "telnet"})
|
||||
self.settings().update({"ports_mapping": [], "console_type": "none"})
|
||||
|
||||
def info(self):
|
||||
"""
|
||||
|
||||
@@ -59,7 +59,7 @@ ETHERNET_SWITCH_SETTINGS = {
|
||||
"default_name_format": "Switch{0}",
|
||||
"symbol": ":/symbols/ethernet_switch.svg",
|
||||
"category": Node.switches,
|
||||
"console_type": "telnet",
|
||||
"console_type": "none",
|
||||
"ports_mapping": [],
|
||||
"node_type": "ethernet_switch"
|
||||
}
|
||||
|
||||
@@ -51,7 +51,8 @@ class DockerVM(Node):
|
||||
"console_resolution": DOCKER_CONTAINER_SETTINGS["console_resolution"],
|
||||
"console_http_port": DOCKER_CONTAINER_SETTINGS["console_http_port"],
|
||||
"console_http_path": DOCKER_CONTAINER_SETTINGS["console_http_path"],
|
||||
"extra_hosts": DOCKER_CONTAINER_SETTINGS["extra_hosts"]}
|
||||
"extra_hosts": DOCKER_CONTAINER_SETTINGS["extra_hosts"],
|
||||
"extra_volumes": DOCKER_CONTAINER_SETTINGS["extra_volumes"]}
|
||||
|
||||
self.settings().update(docker_vm_settings)
|
||||
|
||||
|
||||
@@ -103,7 +103,8 @@ class DockerVMConfigurationPage(QtWidgets.QWidget, Ui_dockerVMConfigPageWidget):
|
||||
self.uiConsoleResolutionComboBox.setCurrentIndex(self.uiConsoleResolutionComboBox.findText(settings["console_resolution"]))
|
||||
self.uiConsoleHttpPortSpinBox.setValue(settings["console_http_port"])
|
||||
self.uiHttpConsolePathLineEdit.setText(settings["console_http_path"])
|
||||
self.uiExtraHostsTextEdit.setText(settings["extra_hosts"])
|
||||
self.uiExtraHostsTextEdit.setPlainText(settings["extra_hosts"])
|
||||
self.uiExtraVolumeTextEdit.setPlainText("\n".join(settings["extra_volumes"]))
|
||||
|
||||
if not group:
|
||||
self.uiNameLineEdit.setText(settings["name"])
|
||||
@@ -175,6 +176,8 @@ class DockerVMConfigurationPage(QtWidgets.QWidget, Ui_dockerVMConfigPageWidget):
|
||||
settings["console_http_port"] = self.uiConsoleHttpPortSpinBox.value()
|
||||
settings["console_http_path"] = self.uiHttpConsolePathLineEdit.text()
|
||||
settings["extra_hosts"] = self.uiExtraHostsTextEdit.toPlainText()
|
||||
# only tidy input here, validation is performed server side
|
||||
settings["extra_volumes"] = [ y for x in self.uiExtraVolumeTextEdit.toPlainText().split("\n") for y in [ x.strip() ] if y ]
|
||||
|
||||
if not group:
|
||||
adapters = self.uiAdapterSpinBox.value()
|
||||
|
||||
@@ -95,6 +95,9 @@ class DockerVMPreferencesPage(QtWidgets.QWidget, Ui_DockerVMPreferencesPageWidge
|
||||
if docker_container["extra_hosts"]:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Extra hosts:", str(docker_container["extra_hosts"])])
|
||||
|
||||
if docker_container["extra_volumes"]:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Extra volumes:", "\n".join(docker_container["extra_volumes"])])
|
||||
|
||||
self.uiDockerVMInfoTreeWidget.expandAll()
|
||||
self.uiDockerVMInfoTreeWidget.resizeColumnToContents(0)
|
||||
self.uiDockerVMInfoTreeWidget.resizeColumnToContents(1)
|
||||
|
||||
@@ -43,5 +43,6 @@ DOCKER_CONTAINER_SETTINGS = {
|
||||
"console_http_port": 80,
|
||||
"console_http_path": "/",
|
||||
"extra_hosts": "",
|
||||
"extra_volumes": [],
|
||||
"node_type": "docker"
|
||||
}
|
||||
|
||||
@@ -297,9 +297,34 @@ one per line)</string>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QTextEdit" name="uiExtraHostsTextEdit"/>
|
||||
<widget class="QPlainTextEdit" name="uiExtraHostsTextEdit">
|
||||
<property name="placeholderText">
|
||||
<string>e.g. router:192.168.0.1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiExtraVolumeLabel">
|
||||
<property name="text">
|
||||
<string>Additional directories to
|
||||
make persistent that are
|
||||
not included in the image
|
||||
VOLUMES config. One
|
||||
directory per line.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPlainTextEdit" name="uiExtraVolumeTextEdit">
|
||||
<property name="plainText">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>e.g. /etc/sysctl.d</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
|
||||
@@ -143,11 +143,18 @@ class Ui_dockerVMConfigPageWidget(object):
|
||||
self.uiExtraHostsLabel.setWordWrap(True)
|
||||
self.uiExtraHostsLabel.setObjectName("uiExtraHostsLabel")
|
||||
self.gridLayout_2.addWidget(self.uiExtraHostsLabel, 0, 0, 1, 1)
|
||||
self.uiExtraHostsTextEdit = QtWidgets.QTextEdit(self.tab_2)
|
||||
self.uiExtraHostsTextEdit = QtWidgets.QPlainTextEdit(self.tab_2)
|
||||
self.uiExtraHostsTextEdit.setObjectName("uiExtraHostsTextEdit")
|
||||
self.gridLayout_2.addWidget(self.uiExtraHostsTextEdit, 0, 1, 1, 1)
|
||||
self.uiExtraVolumeLabel = QtWidgets.QLabel(self.tab_2)
|
||||
self.uiExtraVolumeLabel.setObjectName("uiExtraVolumeLabel")
|
||||
self.gridLayout_2.addWidget(self.uiExtraVolumeLabel, 1, 0, 1, 1)
|
||||
self.uiExtraVolumeTextEdit = QtWidgets.QPlainTextEdit(self.tab_2)
|
||||
self.uiExtraVolumeTextEdit.setPlainText("")
|
||||
self.uiExtraVolumeTextEdit.setObjectName("uiExtraVolumeTextEdit")
|
||||
self.gridLayout_2.addWidget(self.uiExtraVolumeTextEdit, 1, 1, 1, 1)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 388, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout_2.addItem(spacerItem, 1, 1, 1, 1)
|
||||
self.gridLayout_2.addItem(spacerItem, 2, 1, 1, 1)
|
||||
self.uiTabWidget.addTab(self.tab_2, "")
|
||||
self.tab_3 = QtWidgets.QWidget()
|
||||
self.tab_3.setObjectName("tab_3")
|
||||
@@ -201,6 +208,13 @@ class Ui_dockerVMConfigPageWidget(object):
|
||||
"to the /etc/hosts file.\n"
|
||||
"(hostname:IP\n"
|
||||
"one per line)"))
|
||||
self.uiExtraHostsTextEdit.setPlaceholderText(_translate("dockerVMConfigPageWidget", "e.g. router:192.168.0.1"))
|
||||
self.uiExtraVolumeLabel.setText(_translate("dockerVMConfigPageWidget", "Additional directories to\n"
|
||||
"make persistent that are\n"
|
||||
"not included in the image\n"
|
||||
"VOLUMES config. One\n"
|
||||
"directory per line."))
|
||||
self.uiExtraVolumeTextEdit.setPlaceholderText(_translate("dockerVMConfigPageWidget", "e.g. /etc/sysctl.d"))
|
||||
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.tab_2), _translate("dockerVMConfigPageWidget", "Advanced"))
|
||||
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.tab_3), _translate("dockerVMConfigPageWidget", "Usage"))
|
||||
|
||||
|
||||
@@ -19,122 +19,127 @@
|
||||
<property name="accessibleDescription">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="uiDockerVMInfoTreeWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="QTreeWidget" name="uiDockerVMsTreeWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>160</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>11</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">1</string>
|
||||
</property>
|
||||
<property name="indentation">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="allColumnsShowFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
</column>
|
||||
</widget>
|
||||
<widget class="QWidget" name="">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiNewDockerVMPushButton">
|
||||
<property name="text">
|
||||
<string>&New</string>
|
||||
<widget class="QTreeWidget" name="uiDockerVMInfoTreeWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="indentation">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="allColumnsShowFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiCopyDockerVMPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Copy</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiEditDockerVMPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiDeleteDockerVMPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Delete</string>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiNewDockerVMPushButton">
|
||||
<property name="text">
|
||||
<string>&New</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiCopyDockerVMPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Copy</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiEditDockerVMPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiDeleteDockerVMPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Delete</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QTreeWidget" name="uiDockerVMsTreeWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>160</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>11</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">1</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
@@ -13,41 +13,12 @@ class Ui_DockerVMPreferencesPageWidget(object):
|
||||
DockerVMPreferencesPageWidget.setObjectName("DockerVMPreferencesPageWidget")
|
||||
DockerVMPreferencesPageWidget.resize(575, 435)
|
||||
DockerVMPreferencesPageWidget.setAccessibleDescription("")
|
||||
self.gridLayout = QtWidgets.QGridLayout(DockerVMPreferencesPageWidget)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout()
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiDockerVMInfoTreeWidget = QtWidgets.QTreeWidget(DockerVMPreferencesPageWidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiDockerVMInfoTreeWidget.sizePolicy().hasHeightForWidth())
|
||||
self.uiDockerVMInfoTreeWidget.setSizePolicy(sizePolicy)
|
||||
self.uiDockerVMInfoTreeWidget.setIndentation(10)
|
||||
self.uiDockerVMInfoTreeWidget.setAllColumnsShowFocus(True)
|
||||
self.uiDockerVMInfoTreeWidget.setObjectName("uiDockerVMInfoTreeWidget")
|
||||
self.uiDockerVMInfoTreeWidget.header().setVisible(False)
|
||||
self.verticalLayout.addWidget(self.uiDockerVMInfoTreeWidget)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.uiNewDockerVMPushButton = QtWidgets.QPushButton(DockerVMPreferencesPageWidget)
|
||||
self.uiNewDockerVMPushButton.setObjectName("uiNewDockerVMPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiNewDockerVMPushButton)
|
||||
self.uiCopyDockerVMPushButton = QtWidgets.QPushButton(DockerVMPreferencesPageWidget)
|
||||
self.uiCopyDockerVMPushButton.setEnabled(False)
|
||||
self.uiCopyDockerVMPushButton.setObjectName("uiCopyDockerVMPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiCopyDockerVMPushButton)
|
||||
self.uiEditDockerVMPushButton = QtWidgets.QPushButton(DockerVMPreferencesPageWidget)
|
||||
self.uiEditDockerVMPushButton.setEnabled(False)
|
||||
self.uiEditDockerVMPushButton.setObjectName("uiEditDockerVMPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiEditDockerVMPushButton)
|
||||
self.uiDeleteDockerVMPushButton = QtWidgets.QPushButton(DockerVMPreferencesPageWidget)
|
||||
self.uiDeleteDockerVMPushButton.setEnabled(False)
|
||||
self.uiDeleteDockerVMPushButton.setObjectName("uiDeleteDockerVMPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiDeleteDockerVMPushButton)
|
||||
self.verticalLayout.addLayout(self.horizontalLayout)
|
||||
self.gridLayout.addLayout(self.verticalLayout, 0, 1, 1, 1)
|
||||
self.uiDockerVMsTreeWidget = QtWidgets.QTreeWidget(DockerVMPreferencesPageWidget)
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(DockerVMPreferencesPageWidget)
|
||||
self.verticalLayout_2.setObjectName("verticalLayout_2")
|
||||
self.splitter = QtWidgets.QSplitter(DockerVMPreferencesPageWidget)
|
||||
self.splitter.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.splitter.setObjectName("splitter")
|
||||
self.uiDockerVMsTreeWidget = QtWidgets.QTreeWidget(self.splitter)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@@ -65,7 +36,41 @@ class Ui_DockerVMPreferencesPageWidget(object):
|
||||
self.uiDockerVMsTreeWidget.setObjectName("uiDockerVMsTreeWidget")
|
||||
self.uiDockerVMsTreeWidget.headerItem().setText(0, "1")
|
||||
self.uiDockerVMsTreeWidget.header().setVisible(False)
|
||||
self.gridLayout.addWidget(self.uiDockerVMsTreeWidget, 0, 0, 1, 1)
|
||||
self.widget = QtWidgets.QWidget(self.splitter)
|
||||
self.widget.setObjectName("widget")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.widget)
|
||||
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiDockerVMInfoTreeWidget = QtWidgets.QTreeWidget(self.widget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiDockerVMInfoTreeWidget.sizePolicy().hasHeightForWidth())
|
||||
self.uiDockerVMInfoTreeWidget.setSizePolicy(sizePolicy)
|
||||
self.uiDockerVMInfoTreeWidget.setIndentation(10)
|
||||
self.uiDockerVMInfoTreeWidget.setAllColumnsShowFocus(True)
|
||||
self.uiDockerVMInfoTreeWidget.setObjectName("uiDockerVMInfoTreeWidget")
|
||||
self.uiDockerVMInfoTreeWidget.header().setVisible(False)
|
||||
self.verticalLayout.addWidget(self.uiDockerVMInfoTreeWidget)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.uiNewDockerVMPushButton = QtWidgets.QPushButton(self.widget)
|
||||
self.uiNewDockerVMPushButton.setObjectName("uiNewDockerVMPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiNewDockerVMPushButton)
|
||||
self.uiCopyDockerVMPushButton = QtWidgets.QPushButton(self.widget)
|
||||
self.uiCopyDockerVMPushButton.setEnabled(False)
|
||||
self.uiCopyDockerVMPushButton.setObjectName("uiCopyDockerVMPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiCopyDockerVMPushButton)
|
||||
self.uiEditDockerVMPushButton = QtWidgets.QPushButton(self.widget)
|
||||
self.uiEditDockerVMPushButton.setEnabled(False)
|
||||
self.uiEditDockerVMPushButton.setObjectName("uiEditDockerVMPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiEditDockerVMPushButton)
|
||||
self.uiDeleteDockerVMPushButton = QtWidgets.QPushButton(self.widget)
|
||||
self.uiDeleteDockerVMPushButton.setEnabled(False)
|
||||
self.uiDeleteDockerVMPushButton.setObjectName("uiDeleteDockerVMPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiDeleteDockerVMPushButton)
|
||||
self.verticalLayout.addLayout(self.horizontalLayout)
|
||||
self.verticalLayout_2.addWidget(self.splitter)
|
||||
|
||||
self.retranslateUi(DockerVMPreferencesPageWidget)
|
||||
QtCore.QMetaObject.connectSlotsByName(DockerVMPreferencesPageWidget)
|
||||
|
||||
@@ -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)
|
||||
@@ -166,12 +166,16 @@ class Dynamips(Module):
|
||||
:param idlepc: Idle-PC value
|
||||
"""
|
||||
|
||||
for ios_router in self._ios_routers.values():
|
||||
if os.path.basename(ios_router["image"]) == image_path:
|
||||
if ios_router["idlepc"] != idlepc:
|
||||
ios_router["idlepc"] = idlepc
|
||||
log.debug("Idle-PC value {} saved into '{}' template".format(idlepc, ios_router["name"]))
|
||||
self._saveIOSRouters()
|
||||
for template in TemplateManager.instance().templates().values():
|
||||
if template.template_type() == "dynamips":
|
||||
template_settings = template.settings()
|
||||
router_image = template_settings.get("image")
|
||||
old_idlepc = template_settings.get("idlepc")
|
||||
if os.path.basename(router_image) == image_path and old_idlepc != idlepc:
|
||||
template_settings["idlepc"] = idlepc
|
||||
template.setSettings(template_settings)
|
||||
log.debug("Idle-PC value {} saved into '{}' template".format(idlepc, template.name()))
|
||||
TemplateManager.instance().updateTemplate(template)
|
||||
|
||||
@staticmethod
|
||||
def configurationPage():
|
||||
|
||||
@@ -123,7 +123,7 @@ class IOSRouterWizard(VMWithImagesWizard, Ui_IOSRouterWizard):
|
||||
|
||||
# try to guess the platform
|
||||
image = os.path.basename(self.uiIOSImageLineEdit.text())
|
||||
match = re.match(r"^(c[0-9]+)p?\\-\w+", image.lower())
|
||||
match = re.match(r"^(c[0-9]+)p?-\w+", image.lower())
|
||||
if not match:
|
||||
QtWidgets.QMessageBox.warning(self, "IOS image", "Could not detect the platform, make sure this is a valid IOS image!")
|
||||
return
|
||||
|
||||
@@ -55,7 +55,7 @@ class DynamipsPreferencesPage(QtWidgets.QWidget, Ui_DynamipsPreferencesPageWidge
|
||||
|
||||
dynamips_path = shutil.which("dynamips")
|
||||
if sys.platform.startswith("darwin") and dynamips_path is None:
|
||||
dynamips_path = "/Applications/GNS3.app/Contents/Resources/dynamips"
|
||||
dynamips_path = "/Applications/GNS3.app/Contents/MacOS/dynamips"
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select Dynamips", dynamips_path, file_filter)
|
||||
if not path:
|
||||
return
|
||||
|
||||
@@ -111,7 +111,7 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
|
||||
|
||||
# try to guess the platform
|
||||
image = os.path.basename(path)
|
||||
match = re.match(r"^(c[0-9]+)\\-\w+", image)
|
||||
match = re.match(r"^(c[0-9]+)p?-\w+", image)
|
||||
if not match:
|
||||
QtWidgets.QMessageBox.warning(self, "IOS image", "Could not detect the platform, make sure this is a valid IOS image!")
|
||||
return
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>700</width>
|
||||
<height>511</height>
|
||||
<width>715</width>
|
||||
<height>440</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -16,133 +16,138 @@
|
||||
<property name="accessibleName">
|
||||
<string>IOS router templates</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QTreeWidget" name="uiIOSRoutersTreeWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>160</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>11</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">1</string>
|
||||
<widget class="QTreeWidget" name="uiIOSRoutersTreeWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="uiIOSRouterInfoTreeWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>160</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>11</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">1</string>
|
||||
</property>
|
||||
<property name="indentation">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="allColumnsShowFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
</column>
|
||||
</widget>
|
||||
<widget class="QWidget" name="">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiNewIOSRouterPushButton">
|
||||
<property name="text">
|
||||
<string>&New</string>
|
||||
<widget class="QTreeWidget" name="uiIOSRouterInfoTreeWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="indentation">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="allColumnsShowFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiDecompressIOSPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Decompress</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiCopyIOSRouterPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Copy</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiEditIOSRouterPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiDeleteIOSRouterPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Delete</string>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiNewIOSRouterPushButton">
|
||||
<property name="text">
|
||||
<string>&New</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiDecompressIOSPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Decompress</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiCopyIOSRouterPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Copy</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiEditIOSRouterPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiDeleteIOSRouterPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Delete</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
||||
@@ -11,10 +11,13 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
class Ui_IOSRouterPreferencesPageWidget(object):
|
||||
def setupUi(self, IOSRouterPreferencesPageWidget):
|
||||
IOSRouterPreferencesPageWidget.setObjectName("IOSRouterPreferencesPageWidget")
|
||||
IOSRouterPreferencesPageWidget.resize(700, 511)
|
||||
self.gridLayout = QtWidgets.QGridLayout(IOSRouterPreferencesPageWidget)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.uiIOSRoutersTreeWidget = QtWidgets.QTreeWidget(IOSRouterPreferencesPageWidget)
|
||||
IOSRouterPreferencesPageWidget.resize(715, 440)
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(IOSRouterPreferencesPageWidget)
|
||||
self.verticalLayout_2.setObjectName("verticalLayout_2")
|
||||
self.splitter = QtWidgets.QSplitter(IOSRouterPreferencesPageWidget)
|
||||
self.splitter.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.splitter.setObjectName("splitter")
|
||||
self.uiIOSRoutersTreeWidget = QtWidgets.QTreeWidget(self.splitter)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@@ -32,10 +35,12 @@ class Ui_IOSRouterPreferencesPageWidget(object):
|
||||
self.uiIOSRoutersTreeWidget.setObjectName("uiIOSRoutersTreeWidget")
|
||||
self.uiIOSRoutersTreeWidget.headerItem().setText(0, "1")
|
||||
self.uiIOSRoutersTreeWidget.header().setVisible(False)
|
||||
self.gridLayout.addWidget(self.uiIOSRoutersTreeWidget, 0, 0, 1, 1)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout()
|
||||
self.widget = QtWidgets.QWidget(self.splitter)
|
||||
self.widget.setObjectName("widget")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.widget)
|
||||
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiIOSRouterInfoTreeWidget = QtWidgets.QTreeWidget(IOSRouterPreferencesPageWidget)
|
||||
self.uiIOSRouterInfoTreeWidget = QtWidgets.QTreeWidget(self.widget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@@ -48,27 +53,27 @@ class Ui_IOSRouterPreferencesPageWidget(object):
|
||||
self.verticalLayout.addWidget(self.uiIOSRouterInfoTreeWidget)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.uiNewIOSRouterPushButton = QtWidgets.QPushButton(IOSRouterPreferencesPageWidget)
|
||||
self.uiNewIOSRouterPushButton = QtWidgets.QPushButton(self.widget)
|
||||
self.uiNewIOSRouterPushButton.setObjectName("uiNewIOSRouterPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiNewIOSRouterPushButton)
|
||||
self.uiDecompressIOSPushButton = QtWidgets.QPushButton(IOSRouterPreferencesPageWidget)
|
||||
self.uiDecompressIOSPushButton = QtWidgets.QPushButton(self.widget)
|
||||
self.uiDecompressIOSPushButton.setEnabled(False)
|
||||
self.uiDecompressIOSPushButton.setObjectName("uiDecompressIOSPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiDecompressIOSPushButton)
|
||||
self.uiCopyIOSRouterPushButton = QtWidgets.QPushButton(IOSRouterPreferencesPageWidget)
|
||||
self.uiCopyIOSRouterPushButton = QtWidgets.QPushButton(self.widget)
|
||||
self.uiCopyIOSRouterPushButton.setEnabled(False)
|
||||
self.uiCopyIOSRouterPushButton.setObjectName("uiCopyIOSRouterPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiCopyIOSRouterPushButton)
|
||||
self.uiEditIOSRouterPushButton = QtWidgets.QPushButton(IOSRouterPreferencesPageWidget)
|
||||
self.uiEditIOSRouterPushButton = QtWidgets.QPushButton(self.widget)
|
||||
self.uiEditIOSRouterPushButton.setEnabled(False)
|
||||
self.uiEditIOSRouterPushButton.setObjectName("uiEditIOSRouterPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiEditIOSRouterPushButton)
|
||||
self.uiDeleteIOSRouterPushButton = QtWidgets.QPushButton(IOSRouterPreferencesPageWidget)
|
||||
self.uiDeleteIOSRouterPushButton = QtWidgets.QPushButton(self.widget)
|
||||
self.uiDeleteIOSRouterPushButton.setEnabled(False)
|
||||
self.uiDeleteIOSRouterPushButton.setObjectName("uiDeleteIOSRouterPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiDeleteIOSRouterPushButton)
|
||||
self.verticalLayout.addLayout(self.horizontalLayout)
|
||||
self.gridLayout.addLayout(self.verticalLayout, 0, 1, 1, 1)
|
||||
self.verticalLayout_2.addWidget(self.splitter)
|
||||
|
||||
self.retranslateUi(IOSRouterPreferencesPageWidget)
|
||||
QtCore.QMetaObject.connectSlotsByName(IOSRouterPreferencesPageWidget)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -16,123 +16,128 @@
|
||||
<property name="accessibleName">
|
||||
<string>IOU device templates</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QTreeWidget" name="uiIOUDevicesTreeWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>160</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>11</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">1</string>
|
||||
<widget class="QTreeWidget" name="uiIOUDevicesTreeWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="uiIOUDeviceInfoTreeWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>160</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>11</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">1</string>
|
||||
</property>
|
||||
<property name="indentation">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="allColumnsShowFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
</column>
|
||||
</widget>
|
||||
<widget class="QWidget" name="">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiNewIOUDevicePushButton">
|
||||
<property name="text">
|
||||
<string>&New</string>
|
||||
<widget class="QTreeWidget" name="uiIOUDeviceInfoTreeWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="indentation">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="allColumnsShowFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiCopyIOUDevicePushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Copy</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiEditIOUDevicePushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiDeleteIOUDevicePushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Delete</string>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiNewIOUDevicePushButton">
|
||||
<property name="text">
|
||||
<string>&New</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiCopyIOUDevicePushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Copy</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiEditIOUDevicePushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiDeleteIOUDevicePushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Delete</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
||||
@@ -12,9 +12,12 @@ class Ui_IOUDevicePreferencesPageWidget(object):
|
||||
def setupUi(self, IOUDevicePreferencesPageWidget):
|
||||
IOUDevicePreferencesPageWidget.setObjectName("IOUDevicePreferencesPageWidget")
|
||||
IOUDevicePreferencesPageWidget.resize(542, 449)
|
||||
self.gridLayout = QtWidgets.QGridLayout(IOUDevicePreferencesPageWidget)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.uiIOUDevicesTreeWidget = QtWidgets.QTreeWidget(IOUDevicePreferencesPageWidget)
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(IOUDevicePreferencesPageWidget)
|
||||
self.verticalLayout_2.setObjectName("verticalLayout_2")
|
||||
self.splitter = QtWidgets.QSplitter(IOUDevicePreferencesPageWidget)
|
||||
self.splitter.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.splitter.setObjectName("splitter")
|
||||
self.uiIOUDevicesTreeWidget = QtWidgets.QTreeWidget(self.splitter)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@@ -32,10 +35,12 @@ class Ui_IOUDevicePreferencesPageWidget(object):
|
||||
self.uiIOUDevicesTreeWidget.setObjectName("uiIOUDevicesTreeWidget")
|
||||
self.uiIOUDevicesTreeWidget.headerItem().setText(0, "1")
|
||||
self.uiIOUDevicesTreeWidget.header().setVisible(False)
|
||||
self.gridLayout.addWidget(self.uiIOUDevicesTreeWidget, 0, 0, 1, 1)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout()
|
||||
self.widget = QtWidgets.QWidget(self.splitter)
|
||||
self.widget.setObjectName("widget")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.widget)
|
||||
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiIOUDeviceInfoTreeWidget = QtWidgets.QTreeWidget(IOUDevicePreferencesPageWidget)
|
||||
self.uiIOUDeviceInfoTreeWidget = QtWidgets.QTreeWidget(self.widget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@@ -48,23 +53,23 @@ class Ui_IOUDevicePreferencesPageWidget(object):
|
||||
self.verticalLayout.addWidget(self.uiIOUDeviceInfoTreeWidget)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.uiNewIOUDevicePushButton = QtWidgets.QPushButton(IOUDevicePreferencesPageWidget)
|
||||
self.uiNewIOUDevicePushButton = QtWidgets.QPushButton(self.widget)
|
||||
self.uiNewIOUDevicePushButton.setObjectName("uiNewIOUDevicePushButton")
|
||||
self.horizontalLayout.addWidget(self.uiNewIOUDevicePushButton)
|
||||
self.uiCopyIOUDevicePushButton = QtWidgets.QPushButton(IOUDevicePreferencesPageWidget)
|
||||
self.uiCopyIOUDevicePushButton = QtWidgets.QPushButton(self.widget)
|
||||
self.uiCopyIOUDevicePushButton.setEnabled(False)
|
||||
self.uiCopyIOUDevicePushButton.setObjectName("uiCopyIOUDevicePushButton")
|
||||
self.horizontalLayout.addWidget(self.uiCopyIOUDevicePushButton)
|
||||
self.uiEditIOUDevicePushButton = QtWidgets.QPushButton(IOUDevicePreferencesPageWidget)
|
||||
self.uiEditIOUDevicePushButton = QtWidgets.QPushButton(self.widget)
|
||||
self.uiEditIOUDevicePushButton.setEnabled(False)
|
||||
self.uiEditIOUDevicePushButton.setObjectName("uiEditIOUDevicePushButton")
|
||||
self.horizontalLayout.addWidget(self.uiEditIOUDevicePushButton)
|
||||
self.uiDeleteIOUDevicePushButton = QtWidgets.QPushButton(IOUDevicePreferencesPageWidget)
|
||||
self.uiDeleteIOUDevicePushButton = QtWidgets.QPushButton(self.widget)
|
||||
self.uiDeleteIOUDevicePushButton.setEnabled(False)
|
||||
self.uiDeleteIOUDevicePushButton.setObjectName("uiDeleteIOUDevicePushButton")
|
||||
self.horizontalLayout.addWidget(self.uiDeleteIOUDevicePushButton)
|
||||
self.verticalLayout.addLayout(self.horizontalLayout)
|
||||
self.gridLayout.addLayout(self.verticalLayout, 0, 1, 1, 1)
|
||||
self.verticalLayout_2.addWidget(self.splitter)
|
||||
|
||||
self.retranslateUi(IOUDevicePreferencesPageWidget)
|
||||
QtCore.QMetaObject.connectSlotsByName(IOUDevicePreferencesPageWidget)
|
||||
|
||||
@@ -14,6 +14,13 @@
|
||||
<string>IOS on UNIX</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>IOU licence (iourc file):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/iou/ui/iou_preferences_page.ui'
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/iou/ui/iou_preferences_page.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.7.1
|
||||
# Created by: PyQt5 UI code generator 5.9
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_IOUPreferencesPageWidget(object):
|
||||
|
||||
def setupUi(self, IOUPreferencesPageWidget):
|
||||
IOUPreferencesPageWidget.setObjectName("IOUPreferencesPageWidget")
|
||||
IOUPreferencesPageWidget.resize(490, 532)
|
||||
self.vboxlayout = QtWidgets.QVBoxLayout(IOUPreferencesPageWidget)
|
||||
self.vboxlayout.setObjectName("vboxlayout")
|
||||
self.label = QtWidgets.QLabel(IOUPreferencesPageWidget)
|
||||
self.label.setObjectName("label")
|
||||
self.vboxlayout.addWidget(self.label)
|
||||
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
|
||||
self.IOULicenceTextEdit = QtWidgets.QPlainTextEdit(IOUPreferencesPageWidget)
|
||||
@@ -47,7 +48,9 @@ class Ui_IOUPreferencesPageWidget(object):
|
||||
def retranslateUi(self, IOUPreferencesPageWidget):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
IOUPreferencesPageWidget.setWindowTitle(_translate("IOUPreferencesPageWidget", "IOS on UNIX"))
|
||||
self.label.setText(_translate("IOUPreferencesPageWidget", "IOU licence (iourc file):"))
|
||||
self.IOULicenceTextEdit.setToolTip(_translate("IOUPreferencesPageWidget", "A license is required to run IOU. Copy & paste the content of your iourc file here or use the browse button to select a file. The license will be pushed to remote servers."))
|
||||
self.uiIOURCPathToolButton.setText(_translate("IOUPreferencesPageWidget", "&Browse..."))
|
||||
self.uiLicensecheckBox.setText(_translate("IOUPreferencesPageWidget", "Check for a valid IOU license key"))
|
||||
self.uiRestoreDefaultsPushButton.setText(_translate("IOUPreferencesPageWidget", "Restore defaults"))
|
||||
|
||||
|
||||
@@ -152,9 +152,14 @@ class Module(QtCore.QObject):
|
||||
:param directory: destination directory path
|
||||
"""
|
||||
|
||||
node_names_cannot_export = []
|
||||
for node in self._nodes:
|
||||
if hasattr(node, "initialized") and node.initialized():
|
||||
node.exportConfigsToDirectory(directory)
|
||||
if not node.exportConfigsToDirectory(directory):
|
||||
node_names_cannot_export.append(node.name())
|
||||
|
||||
if node_names_cannot_export:
|
||||
log.warning("Config export is not supported by the following nodes: {}".format(" ".join(node_names_cannot_export)))
|
||||
|
||||
def importConfigs(self, directory):
|
||||
"""
|
||||
|
||||
@@ -126,7 +126,7 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
|
||||
# default is qemu-system-i386w.exe on Windows 32-bit with a remote server
|
||||
search_string = "i386w.exe"
|
||||
elif ComputeManager.instance().localPlatform().startswith("darwin") and hasattr(sys, "frozen") and self.uiLocalRadioButton.isChecked():
|
||||
search_string = "GNS3.app/Contents/Resources/qemu/bin/qemu-system-x86_64"
|
||||
search_string = "GNS3.app/Contents/MacOS/qemu/bin/qemu-system-x86_64"
|
||||
elif is_64bit:
|
||||
# default is qemu-system-x86_64 on other 64-bit platforms
|
||||
search_string = "x86_64"
|
||||
@@ -163,7 +163,7 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
|
||||
settings["initrd"] = self.uiInitrdImageLineEdit.text()
|
||||
settings["kernel_image"] = self.uiKernelImageLineEdit.text()
|
||||
settings["kernel_command_line"] = "ide_generic.probe_mask=0x01 ide_core.chs=0.0:980,16,32 auto nousb console=ttyS0,9600 bigphysarea=65536 ide1=noprobe no-hlt -net nic"
|
||||
settings["options"] = "-no-kvm -icount auto -hdachs 980,16,32"
|
||||
settings["options"] = "-no-kvm -icount auto"
|
||||
if not sys.platform.startswith("darwin"):
|
||||
settings["cpu_throttling"] = 80 # limit to 80% CPU usage
|
||||
settings["process_priority"] = "low"
|
||||
|
||||
@@ -391,7 +391,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
|
||||
try:
|
||||
ports = StandardPortNameFactory(adapters, first_port_name, port_name_format, port_segment_size)
|
||||
except (ValueError, KeyError):
|
||||
except (IndexError, ValueError, KeyError):
|
||||
QtWidgets.QMessageBox.critical(self, "Invalid format", "Invalid port name format")
|
||||
return
|
||||
|
||||
|
||||
@@ -860,6 +860,8 @@
|
||||
<li>%vm-id% =VM ID</li>
|
||||
<li>%project-id% = project ID</li>
|
||||
<li>%project-path% = project path</li>
|
||||
<li>%console-port% = console port number</li>
|
||||
<li>%guest-cid% = unique ID from 3 to 65535</li>
|
||||
</ul>
|
||||
</body></html></string>
|
||||
</property>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/qemu/ui/qemu_vm_configuration_page.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.11.3
|
||||
# Created by: PyQt5 UI code generator 5.9
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@@ -541,6 +541,8 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
"<li>%vm-id% =VM ID</li>\n"
|
||||
"<li>%project-id% = project ID</li>\n"
|
||||
"<li>%project-path% = project path</li>\n"
|
||||
"<li>%console-port% = console port number</li>\n"
|
||||
"<li>%guest-cid% = unique ID from 3 to 65535</li>\n"
|
||||
"</ul>\n"
|
||||
"</body></html>"))
|
||||
self.uiBaseVMCheckBox.setText(_translate("QemuVMConfigPageWidget", "Use as a linked base VM"))
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>526</width>
|
||||
<height>400</height>
|
||||
<width>535</width>
|
||||
<height>355</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -16,123 +16,128 @@
|
||||
<property name="accessibleName">
|
||||
<string>Qemu VM templates</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QTreeWidget" name="uiQemuVMsTreeWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>160</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>11</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">1</string>
|
||||
<widget class="QTreeWidget" name="uiQemuVMsTreeWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="uiQemuVMInfoTreeWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>160</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>11</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">1</string>
|
||||
</property>
|
||||
<property name="indentation">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="allColumnsShowFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
</column>
|
||||
</widget>
|
||||
<widget class="QWidget" name="">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiNewQemuVMPushButton">
|
||||
<property name="text">
|
||||
<string>&New</string>
|
||||
<widget class="QTreeWidget" name="uiQemuVMInfoTreeWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="indentation">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="allColumnsShowFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiCopyQemuVMPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Copy</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiEditQemuVMPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiDeleteQemuVMPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Delete</string>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiNewQemuVMPushButton">
|
||||
<property name="text">
|
||||
<string>&New</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiCopyQemuVMPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Copy</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiEditQemuVMPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiDeleteQemuVMPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Delete</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
||||
@@ -11,10 +11,13 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
class Ui_QemuVMPreferencesPageWidget(object):
|
||||
def setupUi(self, QemuVMPreferencesPageWidget):
|
||||
QemuVMPreferencesPageWidget.setObjectName("QemuVMPreferencesPageWidget")
|
||||
QemuVMPreferencesPageWidget.resize(526, 400)
|
||||
self.gridLayout = QtWidgets.QGridLayout(QemuVMPreferencesPageWidget)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.uiQemuVMsTreeWidget = QtWidgets.QTreeWidget(QemuVMPreferencesPageWidget)
|
||||
QemuVMPreferencesPageWidget.resize(535, 355)
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(QemuVMPreferencesPageWidget)
|
||||
self.verticalLayout_2.setObjectName("verticalLayout_2")
|
||||
self.splitter = QtWidgets.QSplitter(QemuVMPreferencesPageWidget)
|
||||
self.splitter.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.splitter.setObjectName("splitter")
|
||||
self.uiQemuVMsTreeWidget = QtWidgets.QTreeWidget(self.splitter)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@@ -32,10 +35,12 @@ class Ui_QemuVMPreferencesPageWidget(object):
|
||||
self.uiQemuVMsTreeWidget.setObjectName("uiQemuVMsTreeWidget")
|
||||
self.uiQemuVMsTreeWidget.headerItem().setText(0, "1")
|
||||
self.uiQemuVMsTreeWidget.header().setVisible(False)
|
||||
self.gridLayout.addWidget(self.uiQemuVMsTreeWidget, 0, 0, 1, 1)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout()
|
||||
self.widget = QtWidgets.QWidget(self.splitter)
|
||||
self.widget.setObjectName("widget")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.widget)
|
||||
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiQemuVMInfoTreeWidget = QtWidgets.QTreeWidget(QemuVMPreferencesPageWidget)
|
||||
self.uiQemuVMInfoTreeWidget = QtWidgets.QTreeWidget(self.widget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@@ -48,23 +53,23 @@ class Ui_QemuVMPreferencesPageWidget(object):
|
||||
self.verticalLayout.addWidget(self.uiQemuVMInfoTreeWidget)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.uiNewQemuVMPushButton = QtWidgets.QPushButton(QemuVMPreferencesPageWidget)
|
||||
self.uiNewQemuVMPushButton = QtWidgets.QPushButton(self.widget)
|
||||
self.uiNewQemuVMPushButton.setObjectName("uiNewQemuVMPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiNewQemuVMPushButton)
|
||||
self.uiCopyQemuVMPushButton = QtWidgets.QPushButton(QemuVMPreferencesPageWidget)
|
||||
self.uiCopyQemuVMPushButton = QtWidgets.QPushButton(self.widget)
|
||||
self.uiCopyQemuVMPushButton.setEnabled(False)
|
||||
self.uiCopyQemuVMPushButton.setObjectName("uiCopyQemuVMPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiCopyQemuVMPushButton)
|
||||
self.uiEditQemuVMPushButton = QtWidgets.QPushButton(QemuVMPreferencesPageWidget)
|
||||
self.uiEditQemuVMPushButton = QtWidgets.QPushButton(self.widget)
|
||||
self.uiEditQemuVMPushButton.setEnabled(False)
|
||||
self.uiEditQemuVMPushButton.setObjectName("uiEditQemuVMPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiEditQemuVMPushButton)
|
||||
self.uiDeleteQemuVMPushButton = QtWidgets.QPushButton(QemuVMPreferencesPageWidget)
|
||||
self.uiDeleteQemuVMPushButton = QtWidgets.QPushButton(self.widget)
|
||||
self.uiDeleteQemuVMPushButton.setEnabled(False)
|
||||
self.uiDeleteQemuVMPushButton.setObjectName("uiDeleteQemuVMPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiDeleteQemuVMPushButton)
|
||||
self.verticalLayout.addLayout(self.horizontalLayout)
|
||||
self.gridLayout.addLayout(self.verticalLayout, 0, 1, 1, 1)
|
||||
self.verticalLayout_2.addWidget(self.splitter)
|
||||
|
||||
self.retranslateUi(QemuVMPreferencesPageWidget)
|
||||
QtCore.QMetaObject.connectSlotsByName(QemuVMPreferencesPageWidget)
|
||||
|
||||
@@ -72,7 +72,7 @@ class TraceNGNode(Node):
|
||||
self._last_destination = destination
|
||||
params = {"destination": destination}
|
||||
log.debug("{} is starting".format(self.name()))
|
||||
self.controllerHttpPost("/nodes/{node_id}/start".format(node_id=self._node_id), self._startCallback, body=params, timeout=None, progressText="{} is starting".format(self.name()))
|
||||
self.controllerHttpPost("/nodes/{node_id}/start".format(node_id=self._node_id), self._startCallback, body=params, timeout=None, showProgress=False)
|
||||
|
||||
def info(self):
|
||||
"""
|
||||
|
||||
@@ -99,7 +99,7 @@ class VirtualBoxVMConfigurationPage(QtWidgets.QWidget, Ui_virtualBoxVMConfigPage
|
||||
|
||||
try:
|
||||
ports = StandardPortNameFactory(adapters, first_port_name, port_name_format, port_segment_size)
|
||||
except (ValueError, KeyError):
|
||||
except (IndexError, ValueError, KeyError):
|
||||
QtWidgets.QMessageBox.critical(self, "Invalid format", "Invalid port name format")
|
||||
return
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ class VMwareVMConfigurationPage(QtWidgets.QWidget, Ui_VMwareVMConfigPageWidget):
|
||||
|
||||
try:
|
||||
ports = StandardPortNameFactory(adapters, first_port_name, port_name_format, port_segment_size)
|
||||
except (ValueError, KeyError):
|
||||
except (IndexError, ValueError, KeyError):
|
||||
QtWidgets.QMessageBox.critical(self, "Invalid format", "Invalid port name format")
|
||||
return
|
||||
|
||||
|
||||
@@ -83,7 +83,6 @@ class VPCS(Module):
|
||||
"""
|
||||
|
||||
# save the settings
|
||||
print(self._settings)
|
||||
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
|
||||
|
||||
server_settings = {}
|
||||
|
||||
45
gns3/node.py
45
gns3/node.py
@@ -46,7 +46,8 @@ class Node(BaseNode):
|
||||
self._settings = {"name": "",
|
||||
"x": None,
|
||||
"y": None,
|
||||
"z": 1}
|
||||
"z": 1,
|
||||
"locked": False}
|
||||
|
||||
def settings(self):
|
||||
"""
|
||||
@@ -148,6 +149,13 @@ class Node(BaseNode):
|
||||
|
||||
return self._settings["z"]
|
||||
|
||||
def locked(self):
|
||||
"""
|
||||
Is the node locked
|
||||
"""
|
||||
|
||||
return self._settings["locked"]
|
||||
|
||||
def setSymbol(self, symbol):
|
||||
"""
|
||||
Sets the symbol for this node.
|
||||
@@ -215,7 +223,7 @@ class Node(BaseNode):
|
||||
return
|
||||
|
||||
log.debug("{} is starting".format(self.name()))
|
||||
self.post("/start", self._startCallback, timeout=None, progressText="{} is starting".format(self.name()))
|
||||
self.post("/start", self._startCallback, timeout=None, showProgress=False)
|
||||
|
||||
def _startCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
@@ -241,7 +249,7 @@ class Node(BaseNode):
|
||||
return
|
||||
|
||||
log.debug("{} is stopping".format(self.name()))
|
||||
self.post("/stop", self._stopCallback, timeout=None, progressText="{} is stopping".format(self.name()))
|
||||
self.post("/stop", self._stopCallback, timeout=None, showProgress=False)
|
||||
|
||||
def _stopCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
@@ -271,7 +279,7 @@ class Node(BaseNode):
|
||||
return
|
||||
|
||||
log.debug("{} is being suspended".format(self.name()))
|
||||
self.post("/suspend", self._suspendCallback, timeout=None, progressText="{} is suspending".format(self.name()))
|
||||
self.post("/suspend", self._suspendCallback, timeout=None, showProgress=False)
|
||||
|
||||
def _suspendCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
@@ -293,7 +301,7 @@ class Node(BaseNode):
|
||||
"""
|
||||
|
||||
log.debug("{} is being reloaded".format(self.name()))
|
||||
self.post("/reload", self._reloadCallback, timeout=None, progressText="{} is reloading".format(self.name()))
|
||||
self.post("/reload", self._reloadCallback, timeout=None, showProgress=False)
|
||||
|
||||
def _reloadCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
@@ -373,6 +381,7 @@ class Node(BaseNode):
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"locked",
|
||||
"symbol",
|
||||
"label",
|
||||
"port_name_format",
|
||||
@@ -446,7 +455,7 @@ class Node(BaseNode):
|
||||
if not skip_controller:
|
||||
for link in self.links():
|
||||
link.setDeleting()
|
||||
self.controllerHttpDelete("/nodes/{node_id}".format(node_id=self._node_id), self._deleteCallback)
|
||||
self.controllerHttpDelete("/nodes/{node_id}".format(node_id=self._node_id), self._deleteCallback, showProgress=False)
|
||||
else:
|
||||
self.deleted_signal.emit()
|
||||
self._module.removeNode(self)
|
||||
@@ -476,7 +485,7 @@ class Node(BaseNode):
|
||||
"y": int(y),
|
||||
"z": int(z)}
|
||||
|
||||
self.post("/duplicate", self._duplicateCallback, body=params, timeout=None, progressText="{} is being duplicated".format(self.name()))
|
||||
self.post("/duplicate", self._duplicateCallback, body=params, timeout=None, showProgress=False)
|
||||
|
||||
def _duplicateCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
@@ -529,7 +538,7 @@ class Node(BaseNode):
|
||||
del result["properties"]
|
||||
|
||||
# Update common element of all nodes
|
||||
for key in ["x", "y", "z", "symbol", "label", "console_host", "console", "console_type", "console_auto_start", "custom_adapters"]:
|
||||
for key in ["x", "y", "z", "locked", "symbol", "label", "console_host", "console", "console_type", "console_auto_start", "custom_adapters"]:
|
||||
if key in result:
|
||||
self._settings[key] = result[key]
|
||||
|
||||
@@ -578,7 +587,8 @@ class Node(BaseNode):
|
||||
data = {"x": int(node_item.pos().x()),
|
||||
"y": int(node_item.pos().y()),
|
||||
"z": int(node_item.zValue()),
|
||||
"symbol": node_item.symbol()}
|
||||
"symbol": node_item.symbol(),
|
||||
"locked": node_item.locked()}
|
||||
|
||||
if node_item.label() is not None:
|
||||
data["label"] = node_item.label().dump()
|
||||
@@ -618,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):
|
||||
@@ -631,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
|
||||
@@ -755,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):
|
||||
"""
|
||||
@@ -765,12 +775,13 @@ class Node(BaseNode):
|
||||
"""
|
||||
|
||||
if not hasattr(self, "configFiles"):
|
||||
return
|
||||
return False
|
||||
for file in self.configFiles():
|
||||
self.get("/files/{file}".format(file=file),
|
||||
self._exportConfigsToDirectoryCallback,
|
||||
context={"directory": directory, "file": file},
|
||||
raw=True)
|
||||
return True
|
||||
|
||||
def _exportConfigsToDirectoryCallback(self, result, error=False, raw_body=None, context={}, **kwargs):
|
||||
"""
|
||||
|
||||
@@ -20,7 +20,7 @@ Nodes view that list all the available nodes to be dragged and dropped
|
||||
on the QGraphics scene.
|
||||
"""
|
||||
|
||||
import sip
|
||||
from .qt import sip
|
||||
|
||||
from .qt import QtCore, QtGui, QtWidgets, qpartial
|
||||
from .controller import Controller
|
||||
|
||||
@@ -27,7 +27,7 @@ from gns3.qt import QtGui, QtCore, QtWidgets
|
||||
from gns3.local_config import LocalConfig
|
||||
from ..ui.general_preferences_page_ui import Ui_GeneralPreferencesPageWidget
|
||||
from gns3.local_server import LocalServer
|
||||
from ..settings import GRAPHICS_VIEW_SETTINGS, GENERAL_SETTINGS, STYLES
|
||||
from ..settings import GRAPHICS_VIEW_SETTINGS, GENERAL_SETTINGS, STYLES, SYMBOL_THEMES
|
||||
from ..dialogs.console_command_dialog import ConsoleCommandDialog
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
|
||||
self.uiBrowseConfigurationPushButton.clicked.connect(self._browseConfigurationDirectorySlot)
|
||||
self._default_label_color = QtGui.QColor(QtCore.Qt.black)
|
||||
self.uiStyleComboBox.addItems(STYLES)
|
||||
self.uiSymbolThemeComboBox.addItems(SYMBOL_THEMES)
|
||||
self.uiImageDirectoriesAddPushButton.clicked.connect(self._imageDirectoriesAddPushButtonSlot)
|
||||
self.uiImageDirectoriesDeletePushButton.clicked.connect(self._imageDirectoriesDeletePushButtonSlot)
|
||||
|
||||
@@ -308,9 +309,15 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
|
||||
self.uiHdpiCheckBox.setChecked(settings["hdpi"])
|
||||
self.uiTelnetConsoleCommandLineEdit.setText(settings["telnet_console_command"])
|
||||
self.uiTelnetConsoleCommandLineEdit.setCursorPosition(0)
|
||||
|
||||
index = self.uiStyleComboBox.findText(settings["style"])
|
||||
if index != -1:
|
||||
self.uiStyleComboBox.setCurrentIndex(index)
|
||||
|
||||
index = self.uiSymbolThemeComboBox.findText(settings["symbol_theme"])
|
||||
if index != -1:
|
||||
self.uiSymbolThemeComboBox.setCurrentIndex(index)
|
||||
|
||||
self.uiDelayConsoleAllSpinBox.setValue(settings["delay_console_all"])
|
||||
|
||||
self.uiVNCConsoleCommandLineEdit.setText(settings["vnc_console_command"])
|
||||
@@ -395,6 +402,7 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
|
||||
|
||||
new_general_settings = {
|
||||
"style": self.uiStyleComboBox.currentText(),
|
||||
"symbol_theme": self.uiSymbolThemeComboBox.currentText(),
|
||||
"experimental_features": self.uiExperimentalFeaturesCheckBox.isChecked(),
|
||||
"hdpi": self.uiHdpiCheckBox.isChecked(),
|
||||
"check_for_update": self.uiCheckForUpdateCheckBox.isChecked(),
|
||||
|
||||
@@ -72,7 +72,11 @@ class GNS3VMPreferencesPage(QtWidgets.QWidget, Ui_GNS3VMPreferencesPageWidget):
|
||||
"""
|
||||
Loads the preference from controller.
|
||||
"""
|
||||
Controller.instance().get("/gns3vm", self._getSettingsCallback)
|
||||
|
||||
if Controller.instance().connected():
|
||||
Controller.instance().get("/gns3vm", self._getSettingsCallback)
|
||||
else:
|
||||
log.error("Cannot load the GNS3 VM settings in the preferences dialog: not connected to the controller")
|
||||
|
||||
@qslot
|
||||
def _getSettingsCallback(self, result, error=False, **kwargs):
|
||||
@@ -81,7 +85,7 @@ class GNS3VMPreferencesPage(QtWidgets.QWidget, Ui_GNS3VMPreferencesPageWidget):
|
||||
return
|
||||
if error:
|
||||
if "message" in result:
|
||||
log.error("Error while getting settings : {}".format(result["message"]))
|
||||
log.error("Error while getting the GNS3 VM settings : {}".format(result["message"]))
|
||||
return
|
||||
self._old_settings = copy.copy(result)
|
||||
self._settings = result
|
||||
|
||||
@@ -67,6 +67,7 @@ class ServerPreferencesPage(QtWidgets.QWidget, Ui_ServerPreferencesPageWidget):
|
||||
# ignore link-local addresses
|
||||
continue
|
||||
self.uiLocalServerHostComboBox.addItem(address_string, address_string)
|
||||
self.uiLocalServerHostComboBox.addItem("localhost", "localhost") # local host
|
||||
self.uiLocalServerHostComboBox.addItem("::", "::") # all IPv6 addresses
|
||||
self.uiLocalServerHostComboBox.addItem("0.0.0.0", "0.0.0.0") # all IPv4 addresses
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
Base class for port objects.
|
||||
"""
|
||||
|
||||
import sip
|
||||
from ..qt import sip
|
||||
|
||||
from ..qt import qslot
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sip
|
||||
from .qt import sip
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
|
||||
@@ -189,7 +189,10 @@ class Progress(QtCore.QObject):
|
||||
# Due to Qt limitations for large numbers (above 32bit int) we calculate "progress" ourselves
|
||||
current, maximum = self._normalize(query['current'], query['maximum'])
|
||||
progress_dialog.setMaximum(maximum)
|
||||
progress_dialog.setValue(current)
|
||||
try:
|
||||
progress_dialog.setValue(current)
|
||||
except OverflowError:
|
||||
progress_dialog.setValue(100)
|
||||
|
||||
if text and query["maximum"] > 1000:
|
||||
text += "\n{} / {}".format(human_filesize(query["current"]), human_filesize(query["maximum"]))
|
||||
|
||||
@@ -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
|
||||
@@ -62,8 +59,7 @@ class Project(QtCore.QObject):
|
||||
self._auto_close = False
|
||||
|
||||
config = LocalConfig.instance()
|
||||
|
||||
graphic_settings = LocalConfig.instance().loadSectionSettings("GraphicsView", GRAPHICS_VIEW_SETTINGS)
|
||||
graphic_settings = config.loadSectionSettings("GraphicsView", GRAPHICS_VIEW_SETTINGS)
|
||||
self._scene_width = graphic_settings["scene_width"]
|
||||
self._scene_height = graphic_settings["scene_height"]
|
||||
self._zoom = graphic_settings.get("zoom", None)
|
||||
@@ -85,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__()
|
||||
|
||||
@@ -336,7 +333,7 @@ class Project(QtCore.QObject):
|
||||
def _duplicateCallback(self, callback, result, error=False, **kwargs):
|
||||
if error:
|
||||
if "message" in result:
|
||||
QtWidgets.QMessageBox.critical(None, "Duplicate project", "Error while duplicating: {}".format(result["message"]))
|
||||
log.error("Error while duplicating project: {}".format(result["message"]))
|
||||
return
|
||||
if callback:
|
||||
callback(result["project_id"])
|
||||
@@ -486,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()
|
||||
@@ -538,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)
|
||||
@@ -610,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():
|
||||
@@ -628,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)
|
||||
|
||||
@@ -701,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":
|
||||
|
||||
@@ -24,7 +24,6 @@ Compatibility layer for Qt bindings, so it is easier to switch to PySide if need
|
||||
|
||||
|
||||
import sys
|
||||
import sip
|
||||
import os
|
||||
import re
|
||||
import inspect
|
||||
@@ -33,12 +32,17 @@ import functools
|
||||
import logging
|
||||
log = logging.getLogger("qt/__init__.py")
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtNetwork, QtWidgets, Qt
|
||||
try:
|
||||
from PyQt5 import sip
|
||||
except ImportError:
|
||||
import sip
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtNetwork, QtWidgets
|
||||
sys.modules[__name__ + '.QtCore'] = QtCore
|
||||
sys.modules[__name__ + '.QtGui'] = QtGui
|
||||
sys.modules[__name__ + '.QtNetwork'] = QtNetwork
|
||||
sys.modules[__name__ + '.QtWidgets'] = QtWidgets
|
||||
sys.modules[__name__ + '.Qt'] = Qt
|
||||
sys.modules[__name__ + '.sip'] = sip
|
||||
|
||||
try:
|
||||
from PyQt5 import QtSvg
|
||||
@@ -115,14 +119,20 @@ class LogQMessageBox(QtWidgets.QMessageBox):
|
||||
if message.startswith("QXcbConnection"): # Qt noise not relevant
|
||||
return
|
||||
LogQMessageBox._get_logger().critical(re.sub(r"<[^<]+?>", "", message), stack_info=LogQMessageBox.stack_info())
|
||||
if sip_is_deleted(parent):
|
||||
if parent is False:
|
||||
# special case to display a QMessageBox before the main window is created.
|
||||
parent = None
|
||||
elif sip_is_deleted(parent):
|
||||
return
|
||||
return super(QtWidgets.QMessageBox, QtWidgets.QMessageBox).critical(parent, title, message, *args)
|
||||
|
||||
@staticmethod
|
||||
def warning(parent, title, message, *args):
|
||||
LogQMessageBox._get_logger().warning(re.sub(r"<[^<]+?>", "", message))
|
||||
if sip_is_deleted(parent):
|
||||
if parent is False:
|
||||
# special case to display a QMessageBox before the main window is created.
|
||||
parent = None
|
||||
elif sip_is_deleted(parent):
|
||||
return
|
||||
return super(QtWidgets.QMessageBox, QtWidgets.QMessageBox).warning(parent, title, message, *args)
|
||||
|
||||
@@ -130,7 +140,7 @@ class LogQMessageBox(QtWidgets.QMessageBox):
|
||||
def _get_logger():
|
||||
"""
|
||||
Return a logger in the context of the caller
|
||||
in order to have the correct informations in the log
|
||||
in order to have the correct information in the log
|
||||
"""
|
||||
if sys.version_info < (3, 5):
|
||||
return logging.getLogger('qt')
|
||||
|
||||
@@ -150,9 +150,7 @@ class Appliance(collections.Mapping):
|
||||
for image_type, image in version["images"].items():
|
||||
image["type"] = image_type
|
||||
|
||||
img = self._registry.search_image_file(
|
||||
self.emulator(), image["filename"], image.get("md5sum"), image.get("filesize")
|
||||
)
|
||||
img = self._registry.search_image_file(self.emulator(), image["filename"], image.get("md5sum"), image.get("filesize"))
|
||||
if img is None:
|
||||
if "md5sum" in image:
|
||||
raise ApplianceError("File {} with checksum {} not found for {}".format(image["filename"], image["md5sum"], appliance["name"]))
|
||||
|
||||
@@ -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"]
|
||||
@@ -99,8 +100,6 @@ class ApplianceToTemplate:
|
||||
new_config.pop("arch", None)
|
||||
|
||||
options = appliance_config["qemu"].get("options", "")
|
||||
if "-nographic" not in options:
|
||||
options += " -nographic"
|
||||
if appliance_config["qemu"].get("kvm", "allow") == "disable" and "-no-kvm" not in options:
|
||||
options += " -no-kvm"
|
||||
new_config["options"] = options.strip()
|
||||
@@ -170,37 +169,66 @@ class ApplianceToTemplate:
|
||||
|
||||
return os.path.basename(path)
|
||||
|
||||
def _set_symbol(self, symbol, controller_symbols):
|
||||
def _set_symbol(self, symbol_id, controller_symbols):
|
||||
"""
|
||||
Check if exists on controller or download symbol from the web if needed
|
||||
"""
|
||||
|
||||
# GNS3 builtin symbol
|
||||
if symbol.startswith(":/symbols/"):
|
||||
return symbol
|
||||
if symbol_id.startswith(":/symbols/"):
|
||||
return symbol_id
|
||||
|
||||
path = os.path.join(Config().symbols_dir, symbol)
|
||||
path = os.path.join(Config().symbols_dir, symbol_id)
|
||||
if os.path.exists(path):
|
||||
return os.path.basename(path)
|
||||
|
||||
if controller_symbols:
|
||||
is_symbol_on_controller = len([s for s in controller_symbols
|
||||
if s['symbol_id'] == symbol]) > 0
|
||||
is_symbol_on_controller = len([s for s in controller_symbols if s['symbol_id'] == symbol_id]) > 0
|
||||
|
||||
if is_symbol_on_controller:
|
||||
cached = Controller.instance().getStaticCachedPath(symbol)
|
||||
cached = Controller.instance().getStaticCachedPath(symbol_id)
|
||||
if os.path.exists(cached):
|
||||
try:
|
||||
shutil.copy(cached, path)
|
||||
except IOError as e:
|
||||
log.warning("Cannot copy cached symbol from `{}` to `{}` due `{}`".format(
|
||||
cached, path, str(e)
|
||||
))
|
||||
return symbol
|
||||
log.warning("Cannot copy cached symbol from `{}` to `{}` due `{}`".format(cached, path, e))
|
||||
return symbol_id
|
||||
|
||||
url = "https://raw.githubusercontent.com/GNS3/gns3-registry/master/symbols/{}".format(symbol)
|
||||
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():
|
||||
controller.uploadSymbol(symbol_id, path)
|
||||
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()))
|
||||
|
||||
@@ -38,7 +38,7 @@ class Image:
|
||||
|
||||
self._location = "local"
|
||||
self._emulator = emulator
|
||||
self.path = path
|
||||
self._path = path
|
||||
if filename is None:
|
||||
self._filename = os.path.basename(self.path)
|
||||
else:
|
||||
@@ -66,6 +66,13 @@ class Image:
|
||||
"""
|
||||
return self._filename
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"""
|
||||
:returns: Image path
|
||||
"""
|
||||
return self._path
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
"""
|
||||
@@ -90,12 +97,12 @@ class Image:
|
||||
"""
|
||||
|
||||
if self._md5sum is None:
|
||||
from_cache = Image._cache.get(self.path)
|
||||
from_cache = Image._cache.get(self._path)
|
||||
if from_cache:
|
||||
self._md5sum = from_cache
|
||||
return self._md5sum
|
||||
|
||||
md5_file = self.path + ".md5sum"
|
||||
md5_file = self._path + ".md5sum"
|
||||
if os.path.exists(md5_file):
|
||||
try:
|
||||
with open(md5_file) as f:
|
||||
@@ -105,20 +112,20 @@ class Image:
|
||||
log.debug("Could not read '{}': {}".format(md5_file, e))
|
||||
|
||||
try:
|
||||
if not os.path.isfile(self.path):
|
||||
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))
|
||||
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
|
||||
Image._cache[self._path] = self._md5sum
|
||||
return self._md5sum
|
||||
|
||||
@md5sum.setter
|
||||
@@ -133,7 +140,7 @@ class Image:
|
||||
if self._filesize is not None:
|
||||
return self._filesize
|
||||
try:
|
||||
self._filesize = os.path.getsize(self.path)
|
||||
self._filesize = os.path.getsize(self._path)
|
||||
return self._filesize
|
||||
except OSError:
|
||||
return 0
|
||||
|
||||
@@ -87,12 +87,12 @@ class Registry(QtCore.QObject):
|
||||
return remote_image
|
||||
|
||||
for directory in self._images_dirs:
|
||||
log.debug("Search images %s (%s) in %s", filename, md5sum, directory)
|
||||
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):
|
||||
@@ -104,9 +104,9 @@ class Registry(QtCore.QObject):
|
||||
if size is None or (file_size - 10 < size and file_size + 10 > size):
|
||||
image = Image(emulator, path)
|
||||
if image.md5sum == md5sum:
|
||||
log.debug("Found images %s (%s) in %s", filename, md5sum, image.path)
|
||||
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
|
||||
|
||||
@@ -166,6 +166,13 @@
|
||||
"extra_hosts": {
|
||||
"description": "Hosts which will be written to /etc/hosts into container" ,
|
||||
"type": "string"
|
||||
},
|
||||
"extra_volumes": {
|
||||
"description": "Additional directories to make persistent that are not included in the images VOLUME directive" ,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -55,7 +55,8 @@ if sys.platform.startswith("win"):
|
||||
# windows 32-bit
|
||||
program_files_x86 = program_files = os.environ["PROGRAMFILES"]
|
||||
|
||||
PRECONFIGURED_TELNET_CONSOLE_COMMANDS = {'Putty (included with GNS3)': 'putty.exe -telnet %h %p -wt "%d" -gns3 5 -skin 4',
|
||||
PRECONFIGURED_TELNET_CONSOLE_COMMANDS = {'Putty (normal standalone version)': 'putty_standalone.exe -telnet %h %p -loghost "%d"',
|
||||
'Putty (custom deprecated version)': 'putty.exe -telnet %h %p -wt "%d" -gns3 5 -skin 4',
|
||||
'MobaXterm': r'"{}\Mobatek\MobaXterm Personal Edition\MobaXterm.exe" -newtab "telnet %h %p"'.format(program_files_x86),
|
||||
'Royal TS': r'{}\code4ward.net\Royal TS V3\RTS3App.exe /connectadhoc:%h /adhoctype:terminal /p:IsTelnetConnection="true" /p:ConnectionType="telnet;Telnet Connection" /p:Port="%p" /p:Name="%d"'.format(program_files),
|
||||
'SuperPutty': r'SuperPutty.exe -telnet "%h -P %p -wt \"%d\""',
|
||||
@@ -75,7 +76,7 @@ if sys.platform.startswith("win"):
|
||||
DEFAULT_DELAY_CONSOLE_ALL = 1500
|
||||
else:
|
||||
PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Solar-Putty (included with GNS3 downloaded from gns3.com)"] = 'Solar-PuTTY.exe --telnet --hostname %h --port %p --name "%d"'
|
||||
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Putty (included with GNS3)"]
|
||||
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Putty (normal standalone version)"]
|
||||
|
||||
elif sys.platform.startswith("darwin"):
|
||||
# Mac OS X
|
||||
@@ -260,6 +261,14 @@ if sys.platform.startswith("win"):
|
||||
|
||||
STYLES = ["Charcoal", "Classic", "Legacy"]
|
||||
|
||||
SYMBOL_THEMES = ["Classic",
|
||||
"Affinity-square-blue",
|
||||
"Affinity-square-gray",
|
||||
"Affinity-square-red",
|
||||
"Affinity-circle-blue",
|
||||
"Affinity-circle-gray",
|
||||
"Affinity-circle-red"]
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
DEFAULT_STYLE = "Classic"
|
||||
else:
|
||||
@@ -288,7 +297,8 @@ GENERAL_SETTINGS = {
|
||||
"debug_level": 0,
|
||||
"multi_profiles": False,
|
||||
"hdpi": not sys.platform.startswith("linux"),
|
||||
"direct_file_upload": False
|
||||
"direct_file_upload": False,
|
||||
"symbol_theme": "Classic"
|
||||
}
|
||||
|
||||
NODES_VIEW_SETTINGS = {
|
||||
@@ -320,7 +330,7 @@ GRAPHICS_VIEW_SETTINGS = {
|
||||
LOCAL_SERVER_SETTINGS = {
|
||||
"path": "gns3server",
|
||||
"ubridge_path": "ubridge",
|
||||
"host": None,
|
||||
"host": "localhost",
|
||||
"port": DEFAULT_LOCAL_SERVER_PORT,
|
||||
"images_path": DEFAULT_IMAGES_PATH,
|
||||
"projects_path": DEFAULT_PROJECTS_PATH,
|
||||
|
||||
@@ -38,7 +38,7 @@ def spiceConsole(host, port, command):
|
||||
"""
|
||||
|
||||
if len(command.strip(' ')) == 0:
|
||||
log.warning('SPICE client is not configured')
|
||||
log.error("SPICE client is not configured")
|
||||
return
|
||||
|
||||
# ipv6 support
|
||||
@@ -50,7 +50,7 @@ def spiceConsole(host, port, command):
|
||||
command = command.replace("%p", str(port))
|
||||
|
||||
try:
|
||||
log.info('starting SPICE program "{}"'.format(command))
|
||||
log.debug('starting SPICE program "{}"'.format(command))
|
||||
if sys.platform.startswith("win"):
|
||||
# use the string on Windows
|
||||
subprocess.Popen(command)
|
||||
@@ -59,5 +59,4 @@ def spiceConsole(host, port, command):
|
||||
args = shlex.split(command)
|
||||
subprocess.Popen(args, env=os.environ)
|
||||
except (OSError, ValueError, subprocess.SubprocessError) as e:
|
||||
log.warning('could not start SPICE program "{}": {}'.format(command, e))
|
||||
raise
|
||||
log.error("Could not start SPICE program with command '{}': {}".format(command, e))
|
||||
|
||||
@@ -76,7 +76,6 @@ class Style:
|
||||
self._mw.uiDrawRectangleAction.setIcon(self._getStyleIcon(":/icons/rectangle.svg", ":/icons/rectangle-hover.svg"))
|
||||
self._mw.uiDrawEllipseAction.setIcon(self._getStyleIcon(":/icons/ellipse.svg", ":/icons/ellipse-hover.svg"))
|
||||
self._mw.uiDrawLineAction.setIcon(QtGui.QIcon(":/icons/vertically.svg"))
|
||||
self._mw.uiEditReadmeAction.setIcon(QtGui.QIcon(":/icons/edit.svg"))
|
||||
self._mw.uiOnlineHelpAction.setIcon(QtGui.QIcon(":/icons/help.svg"))
|
||||
self._mw.uiBrowseRoutersAction.setIcon(self._getStyleIcon(":/icons/router.png", ":/icons/router-hover.png"))
|
||||
self._mw.uiBrowseSwitchesAction.setIcon(self._getStyleIcon(":/icons/switch.png", ":/icons/switch-hover.png"))
|
||||
@@ -128,7 +127,6 @@ class Style:
|
||||
self._mw.uiDrawRectangleAction.setIcon(self._getStyleIcon(":/classic_icons/rectangle.svg", ":/classic_icons/rectangle-hover.svg"))
|
||||
self._mw.uiDrawEllipseAction.setIcon(self._getStyleIcon(":/classic_icons/ellipse.svg", ":/classic_icons/ellipse-hover.svg"))
|
||||
self._mw.uiDrawLineAction.setIcon(self._getStyleIcon(":/classic_icons/line.svg", ":/classic_icons/line-hover.svg"))
|
||||
self._mw.uiEditReadmeAction.setIcon(self._getStyleIcon(":/classic_icons/edit.svg", ":/classic_icons/edit-hover.svg"))
|
||||
self._mw.uiOnlineHelpAction.setIcon(self._getStyleIcon(":/classic_icons/help.svg", ":/classic_icons/help-hover.svg"))
|
||||
self._mw.uiBrowseRoutersAction.setIcon(self._getStyleIcon(":/classic_icons/router.svg", ":/classic_icons/router-hover.svg"))
|
||||
self._mw.uiBrowseSwitchesAction.setIcon(self._getStyleIcon(":/classic_icons/switch.svg", ":/classic_icons/switch-hover.svg"))
|
||||
@@ -190,7 +188,6 @@ class Style:
|
||||
self._mw.uiDrawRectangleAction.setIcon(self._getStyleIcon(":/charcoal_icons/rectangle.svg", ":/charcoal_icons/rectangle-hover.svg"))
|
||||
self._mw.uiDrawEllipseAction.setIcon(self._getStyleIcon(":/charcoal_icons/ellipse.svg", ":/charcoal_icons/ellipse-hover.svg"))
|
||||
self._mw.uiDrawLineAction.setIcon(self._getStyleIcon(":/charcoal_icons/line.svg", ":/charcoal_icons/line-hover.svg"))
|
||||
self._mw.uiEditReadmeAction.setIcon(self._getStyleIcon(":/charcoal_icons/edit.svg", ":/charcoal_icons/edit-hover.svg"))
|
||||
self._mw.uiOnlineHelpAction.setIcon(self._getStyleIcon(":/charcoal_icons/help.svg", ":/charcoal_icons/help-hover.svg"))
|
||||
self._mw.uiBrowseRoutersAction.setIcon(self._getStyleIcon(":/charcoal_icons/router.svg", ":/charcoal_icons/router-hover.svg"))
|
||||
self._mw.uiBrowseSwitchesAction.setIcon(self._getStyleIcon(":/charcoal_icons/switch.svg", ":/charcoal_icons/switch-hover.svg"))
|
||||
|
||||
@@ -19,10 +19,11 @@ import urllib.parse
|
||||
|
||||
|
||||
class Symbol:
|
||||
def __init__(self, symbol_id=None, builtin=False, filename=None):
|
||||
def __init__(self, symbol_id=None, builtin=False, filename=None, theme=None):
|
||||
self._id = symbol_id
|
||||
self._builtin = builtin
|
||||
self._filename = filename
|
||||
self._theme = theme
|
||||
|
||||
def id(self):
|
||||
return self._id
|
||||
@@ -33,6 +34,9 @@ class Symbol:
|
||||
def builtin(self):
|
||||
return self._builtin
|
||||
|
||||
def theme(self):
|
||||
return self._theme
|
||||
|
||||
def url(self):
|
||||
return urllib.parse.quote("/symbols/" + self._id + "/raw")
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -213,6 +213,7 @@ class TemplateManager(QtCore.QObject):
|
||||
self._controller.post("/projects/{project_id}/templates/{template_id}".format(project_id=project.id(), template_id=template_id),
|
||||
self._createNodeFromTemplateCallback,
|
||||
params,
|
||||
showProgress=False,
|
||||
timeout=None)
|
||||
return True
|
||||
|
||||
|
||||
@@ -27,9 +27,8 @@ from .local_server import LocalServer
|
||||
from .qt import QtCore, QtWidgets
|
||||
|
||||
from .utils.progress_dialog import ProgressDialog
|
||||
from .utils.export_project_worker import ExportProjectWorker
|
||||
from .utils.import_project_worker import ImportProjectWorker
|
||||
from .dialogs.file_editor_dialog import FileEditorDialog
|
||||
from .dialogs.project_export_wizard import ExportProjectWizard
|
||||
from .dialogs.project_welcome_dialog import ProjectWelcomeDialog
|
||||
|
||||
from .modules import MODULES
|
||||
@@ -110,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()
|
||||
@@ -135,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
|
||||
@@ -194,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()
|
||||
@@ -216,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.
|
||||
@@ -233,13 +243,6 @@ class Topology(QtCore.QObject):
|
||||
self._main_window.uiStatusBar.showMessage("Project loaded {}".format(path), 2000)
|
||||
return True
|
||||
|
||||
def editReadme(self):
|
||||
if self.project() is None:
|
||||
return
|
||||
dialog = FileEditorDialog(self.project(), "/README.txt", parent=self._main_window, default="Project title\n\nAuthor: Grace Hopper <grace@example.org>\n\nThis project is about...")
|
||||
dialog.show()
|
||||
dialog.exec_()
|
||||
|
||||
def _projectCreationErrorSlot(self, message):
|
||||
if self._project:
|
||||
self._project.project_creation_error_signal.disconnect(self._projectCreationErrorSlot)
|
||||
@@ -247,41 +250,12 @@ class Topology(QtCore.QObject):
|
||||
QtWidgets.QMessageBox.critical(self._main_window, "New project", message)
|
||||
|
||||
def exportProject(self):
|
||||
include_image_question = """Would you like to include any base image?
|
||||
The project will not require additional images to run on another host, however the resulting file will be much bigger.
|
||||
It is your responsability to check if you have the right to distribute the image(s) as part of the project.
|
||||
"""
|
||||
|
||||
reply = QtWidgets.QMessageBox.question(self._main_window, "Export project", include_image_question,
|
||||
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
|
||||
QtWidgets.QMessageBox.No)
|
||||
include_images = int(reply == QtWidgets.QMessageBox.Yes)
|
||||
|
||||
directory = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DocumentsLocation)
|
||||
if len(directory) == 0:
|
||||
directory = self.projectsDirPath()
|
||||
|
||||
path, _ = QtWidgets.QFileDialog.getSaveFileName(self._main_window, "Export portable project", directory,
|
||||
"GNS3 Portable Project (*.gns3project *.gns3p)",
|
||||
"GNS3 Portable Project (*.gns3project *.gns3p)")
|
||||
if path is None or len(path) == 0:
|
||||
if self._project is None:
|
||||
QtWidgets.QMessageBox.critical(self._main_window, "Export project", "No project has been opened")
|
||||
return
|
||||
|
||||
if not path.endswith(".gns3project") and not path.endswith(".gns3p"):
|
||||
path += ".gns3project"
|
||||
|
||||
try:
|
||||
open(path, 'wb+').close()
|
||||
except OSError as e:
|
||||
QtWidgets.QMessageBox.critical(self._main_window, "Export project", "Could not write {}: {}".format(path, e))
|
||||
return
|
||||
|
||||
self.editReadme()
|
||||
|
||||
export_worker = ExportProjectWorker(self._project, path, include_images)
|
||||
progress_dialog = ProgressDialog(export_worker, "Exporting project", "Exporting portable project files...", "Cancel", parent=self._main_window, create_thread=False)
|
||||
progress_dialog.show()
|
||||
progress_dialog.exec_()
|
||||
export_wizard = ExportProjectWizard(self.project(), parent=self._main_window)
|
||||
export_wizard.show()
|
||||
export_wizard.exec_()
|
||||
|
||||
def importProject(self, project_file):
|
||||
from .dialogs.project_dialog import ProjectDialog
|
||||
@@ -541,7 +515,7 @@ It is your responsability to check if you have the right to distribute the image
|
||||
|
||||
node = node_module.instantiateNode(node_class, ComputeManager.instance().getCompute(node_data["compute_id"]), self._project)
|
||||
node.createNodeCallback(node_data)
|
||||
self._main_window.uiGraphicsView.createNodeItem(node, node_data["symbol"], node_data["x"], node_data["y"], node_data.get("z", 1))
|
||||
self._main_window.uiGraphicsView.createNodeItem(node, node_data["symbol"], node_data["x"], node_data["y"])
|
||||
|
||||
def createLink(self, link_data):
|
||||
source_port = None
|
||||
@@ -597,7 +571,7 @@ It is your responsability to check if you have the right to distribute the image
|
||||
except IndexError:
|
||||
# If unknow we render it as a raw SVG image
|
||||
type = "image"
|
||||
self._main_window.uiGraphicsView.createDrawingItem(type, drawing_data["x"], drawing_data["y"], drawing_data["z"], rotation=drawing_data["rotation"], drawing_id=drawing_data["drawing_id"], svg=drawing_data["svg"])
|
||||
self._main_window.uiGraphicsView.createDrawingItem(type, drawing_data["x"], drawing_data["y"], drawing_data["z"], locked=drawing_data["locked"], rotation=drawing_data["rotation"], drawing_id=drawing_data["drawing_id"], svg=drawing_data["svg"])
|
||||
|
||||
@staticmethod
|
||||
def instance():
|
||||
|
||||
@@ -67,13 +67,13 @@
|
||||
<item row="4" column="1">
|
||||
<widget class="QSpinBox" name="uiNodeGridSizeSpinBox">
|
||||
<property name="minimum">
|
||||
<number>10</number>
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>150</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>10</number>
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>75</number>
|
||||
@@ -83,13 +83,13 @@
|
||||
<item row="5" column="1">
|
||||
<widget class="QSpinBox" name="uiDrawingGridSizeSpinBox">
|
||||
<property name="minimum">
|
||||
<number>10</number>
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>10</number>
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>25</number>
|
||||
@@ -179,6 +179,17 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>uiProjectNameLineEdit</tabstop>
|
||||
<tabstop>uiSceneWidthSpinBox</tabstop>
|
||||
<tabstop>uiSceneHeightSpinBox</tabstop>
|
||||
<tabstop>uiNodeGridSizeSpinBox</tabstop>
|
||||
<tabstop>uiDrawingGridSizeSpinBox</tabstop>
|
||||
<tabstop>uiProjectAutoOpenCheckBox</tabstop>
|
||||
<tabstop>uiProjectAutoStartCheckBox</tabstop>
|
||||
<tabstop>uiProjectAutoCloseCheckBox</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
|
||||
@@ -37,16 +37,16 @@ class Ui_EditProjectDialog(object):
|
||||
self.uiSceneWidthSpinBox.setObjectName("uiSceneWidthSpinBox")
|
||||
self.uiGeneralGrid.addWidget(self.uiSceneWidthSpinBox, 2, 1, 1, 1)
|
||||
self.uiNodeGridSizeSpinBox = QtWidgets.QSpinBox(self.uiGeneralTab)
|
||||
self.uiNodeGridSizeSpinBox.setMinimum(10)
|
||||
self.uiNodeGridSizeSpinBox.setMinimum(5)
|
||||
self.uiNodeGridSizeSpinBox.setMaximum(150)
|
||||
self.uiNodeGridSizeSpinBox.setSingleStep(10)
|
||||
self.uiNodeGridSizeSpinBox.setSingleStep(5)
|
||||
self.uiNodeGridSizeSpinBox.setProperty("value", 75)
|
||||
self.uiNodeGridSizeSpinBox.setObjectName("uiNodeGridSizeSpinBox")
|
||||
self.uiGeneralGrid.addWidget(self.uiNodeGridSizeSpinBox, 4, 1, 1, 1)
|
||||
self.uiDrawingGridSizeSpinBox = QtWidgets.QSpinBox(self.uiGeneralTab)
|
||||
self.uiDrawingGridSizeSpinBox.setMinimum(10)
|
||||
self.uiDrawingGridSizeSpinBox.setMinimum(5)
|
||||
self.uiDrawingGridSizeSpinBox.setMaximum(100)
|
||||
self.uiDrawingGridSizeSpinBox.setSingleStep(10)
|
||||
self.uiDrawingGridSizeSpinBox.setSingleStep(5)
|
||||
self.uiDrawingGridSizeSpinBox.setProperty("value", 25)
|
||||
self.uiDrawingGridSizeSpinBox.setObjectName("uiDrawingGridSizeSpinBox")
|
||||
self.uiGeneralGrid.addWidget(self.uiDrawingGridSizeSpinBox, 5, 1, 1, 1)
|
||||
|
||||
120
gns3/ui/export_project_wizard.ui
Normal file
120
gns3/ui/export_project_wizard.ui
Normal file
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ExportProjectWizard</class>
|
||||
<widget class="QWizard" name="ExportProjectWizard">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::ApplicationModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>900</width>
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Export project</string>
|
||||
</property>
|
||||
<property name="options">
|
||||
<set>QWizard::HaveHelpButton</set>
|
||||
</property>
|
||||
<widget class="QWizardPage" name="uiExportOptionsWizardPage">
|
||||
<property name="title">
|
||||
<string>Export project</string>
|
||||
</property>
|
||||
<property name="subTitle">
|
||||
<string>Please select the location, whether to include base images or not and the compression type.</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiPathLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Path:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="uiPathLineEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="uiPathBrowserToolButton">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiCompressionLabel">
|
||||
<property name="text">
|
||||
<string>Compression:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<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="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>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>247</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWizardPage" name="uiProjectReadmeWizardPage">
|
||||
<property name="title">
|
||||
<string>Readme file</string>
|
||||
</property>
|
||||
<property name="subTitle">
|
||||
<string>Write a summary of the project.</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QTextEdit" name="uiReadmeTextEdit">
|
||||
<property name="html">
|
||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'.SF NS Text'; font-size:13pt;"><br /></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
82
gns3/ui/export_project_wizard_ui.py
Normal file
82
gns3/ui/export_project_wizard_ui.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/export_project_wizard.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.9
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
class Ui_ExportProjectWizard(object):
|
||||
def setupUi(self, ExportProjectWizard):
|
||||
ExportProjectWizard.setObjectName("ExportProjectWizard")
|
||||
ExportProjectWizard.setWindowModality(QtCore.Qt.ApplicationModal)
|
||||
ExportProjectWizard.resize(900, 600)
|
||||
ExportProjectWizard.setOptions(QtWidgets.QWizard.HaveHelpButton)
|
||||
self.uiExportOptionsWizardPage = QtWidgets.QWizardPage()
|
||||
self.uiExportOptionsWizardPage.setObjectName("uiExportOptionsWizardPage")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self.uiExportOptionsWizardPage)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.uiPathLabel = QtWidgets.QLabel(self.uiExportOptionsWizardPage)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiPathLabel.sizePolicy().hasHeightForWidth())
|
||||
self.uiPathLabel.setSizePolicy(sizePolicy)
|
||||
self.uiPathLabel.setObjectName("uiPathLabel")
|
||||
self.gridLayout.addWidget(self.uiPathLabel, 0, 0, 1, 1)
|
||||
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
|
||||
self.uiPathLineEdit = QtWidgets.QLineEdit(self.uiExportOptionsWizardPage)
|
||||
self.uiPathLineEdit.setObjectName("uiPathLineEdit")
|
||||
self.horizontalLayout_3.addWidget(self.uiPathLineEdit)
|
||||
self.uiPathBrowserToolButton = QtWidgets.QToolButton(self.uiExportOptionsWizardPage)
|
||||
self.uiPathBrowserToolButton.setObjectName("uiPathBrowserToolButton")
|
||||
self.horizontalLayout_3.addWidget(self.uiPathBrowserToolButton)
|
||||
self.gridLayout.addLayout(self.horizontalLayout_3, 0, 1, 1, 2)
|
||||
self.uiCompressionLabel = QtWidgets.QLabel(self.uiExportOptionsWizardPage)
|
||||
self.uiCompressionLabel.setObjectName("uiCompressionLabel")
|
||||
self.gridLayout.addWidget(self.uiCompressionLabel, 1, 0, 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")
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.uiProjectReadmeWizardPage)
|
||||
self.verticalLayout_2.setObjectName("verticalLayout_2")
|
||||
self.uiReadmeTextEdit = QtWidgets.QTextEdit(self.uiProjectReadmeWizardPage)
|
||||
self.uiReadmeTextEdit.setObjectName("uiReadmeTextEdit")
|
||||
self.verticalLayout_2.addWidget(self.uiReadmeTextEdit)
|
||||
ExportProjectWizard.addPage(self.uiProjectReadmeWizardPage)
|
||||
|
||||
self.retranslateUi(ExportProjectWizard)
|
||||
QtCore.QMetaObject.connectSlotsByName(ExportProjectWizard)
|
||||
|
||||
def retranslateUi(self, ExportProjectWizard):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
ExportProjectWizard.setWindowTitle(_translate("ExportProjectWizard", "Export project"))
|
||||
self.uiExportOptionsWizardPage.setTitle(_translate("ExportProjectWizard", "Export project"))
|
||||
self.uiExportOptionsWizardPage.setSubTitle(_translate("ExportProjectWizard", "Please select the location, whether to include base images or not and the compression type."))
|
||||
self.uiPathLabel.setText(_translate("ExportProjectWizard", "Path:"))
|
||||
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"
|
||||
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
|
||||
"p, li { white-space: pre-wrap; }\n"
|
||||
"</style></head><body style=\" font-family:\'Ubuntu\'; font-size:11pt; font-weight:400; font-style:normal;\">\n"
|
||||
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:\'.SF NS Text\'; font-size:13pt;\"><br /></p></body></html>"))
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1324</width>
|
||||
<height>1038</height>
|
||||
<width>931</width>
|
||||
<height>878</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -30,124 +30,93 @@
|
||||
<string>General</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<property name="margin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="uiLocalPathsGroupBox">
|
||||
<property name="title">
|
||||
<string>Local paths</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiProjectsPathLabel">
|
||||
<property name="text">
|
||||
<string>My projects:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="uiProjectsPathLineEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Directory where your GNS3 projects are stored</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="uiProjectsPathToolButton">
|
||||
<property name="text">
|
||||
<string>&Browse...</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextOnly</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="uiProjectsPathLineEdit">
|
||||
<property name="toolTip">
|
||||
<string>Directory where your GNS3 projects are stored</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QToolButton" name="uiProjectsPathToolButton">
|
||||
<property name="text">
|
||||
<string>&Browse...</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextOnly</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>My symbols:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="uiSymbolsPathLineEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="uiSymbolsPathToolButton">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLineEdit" name="uiSymbolsPathLineEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QToolButton" name="uiSymbolsPathToolButton">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="uiConfigsPathLabel">
|
||||
<property name="text">
|
||||
<string>My configs:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="uiConfigsPathLineEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Directory where your binary images (e.g. IOS) are stored</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="uiConfigsPathToolButton">
|
||||
<property name="text">
|
||||
<string>&Browse...</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextOnly</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLineEdit" name="uiConfigsPathLineEdit">
|
||||
<property name="toolTip">
|
||||
<string>Directory where your binary images (e.g. IOS) are stored</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QToolButton" name="uiConfigsPathToolButton">
|
||||
<property name="text">
|
||||
<string>&Browse...</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextOnly</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>My custom appliances:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_12">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="uiAppliancesPathLineEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="uiAppliancesPathToolButton">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLineEdit" name="uiAppliancesPathLineEdit"/>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QToolButton" name="uiAppliancesPathToolButton">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@@ -155,7 +124,7 @@
|
||||
<item>
|
||||
<widget class="QGroupBox" name="uiStyleGroupBox">
|
||||
<property name="title">
|
||||
<string>Style</string>
|
||||
<string>Interface style</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_11">
|
||||
<item>
|
||||
@@ -164,6 +133,22 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="uiSymbolThemeGroupBox">
|
||||
<property name="title">
|
||||
<string>Symbol theme for new templates</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_15">
|
||||
<item>
|
||||
<widget class="QComboBox" name="uiSymbolThemeComboBox">
|
||||
<property name="toolTip">
|
||||
<string>Symbol theme support only works when adding a new template using the recommended method in the template wizard.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="uiConfigurationFileGroupBox">
|
||||
<property name="title">
|
||||
@@ -219,14 +204,14 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_5">
|
||||
<spacer name="verticalSpacer_8">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>5</height>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
@@ -238,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>
|
||||
@@ -290,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>
|
||||
@@ -365,6 +362,19 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_7">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="uiConsoleTab">
|
||||
@@ -372,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>
|
||||
@@ -483,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>
|
||||
@@ -651,143 +679,28 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="uiSceneHeightLabel">
|
||||
<property name="text">
|
||||
<string>Default height:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="uiNodeGridSizeLabel">
|
||||
<property name="text">
|
||||
<string>Default node grid size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="2">
|
||||
<widget class="QLabel" name="uiDrawingGridSizeLabel">
|
||||
<property name="text">
|
||||
<string>Default drawing (e.g. labels and rectangles) grid size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiRectangleSelectedItemCheckBox">
|
||||
<property name="text">
|
||||
<string>Draw a rectangle when an item is selected</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" 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="10" column="0">
|
||||
<widget class="QCheckBox" name="uiShowInterfaceLabelsOnNewProject">
|
||||
<property name="text">
|
||||
<string>Show interface labels on new project</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<widget class="QCheckBox" name="uiShowGridOnNewProject">
|
||||
<property name="text">
|
||||
<string>Show grid on new project</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0">
|
||||
<widget class="QCheckBox" name="uiSnapToGridOnNewProject">
|
||||
<property name="text">
|
||||
<string>Snap to grid on new project</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="0">
|
||||
<widget class="QCheckBox" name="uiLimitSizeNodeSymbolCheckBox">
|
||||
<property name="text">
|
||||
<string>Limit the size of node symbols</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="0">
|
||||
<widget class="QLabel" name="uiLabelPreviewLabel">
|
||||
<property name="text">
|
||||
<string>Default label style:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="15" column="0" colspan="2">
|
||||
<widget class="QPlainTextEdit" name="uiDefaultLabelStylePlainTextEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="plainText">
|
||||
<string>AaBbYyZz</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="16" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiDefaultLabelFontPushButton">
|
||||
<property name="text">
|
||||
<string>&Select default font</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiDefaultLabelColorPushButton">
|
||||
<property name="text">
|
||||
<string>&Select default color</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="17" column="0">
|
||||
<widget class="QLabel" name="uiNotePreviewLabel">
|
||||
<property name="text">
|
||||
<string>Default note style:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="18" column="0" colspan="2">
|
||||
<item row="6" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiShowInterfaceLabelsOnNewProject">
|
||||
<property name="text">
|
||||
<string>Show interface labels on new project</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiSceneHeightLabel">
|
||||
<property name="text">
|
||||
<string>Default height:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="0" colspan="3">
|
||||
<widget class="QPlainTextEdit" name="uiDefaultNoteStylePlainTextEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
||||
@@ -801,6 +714,9 @@
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
@@ -809,7 +725,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="19" column="0" colspan="2">
|
||||
<item row="15" column="0" colspan="3">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_14">
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiDefaultNoteFontPushButton">
|
||||
@@ -840,40 +756,80 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="20" column="1">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>5</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QSpinBox" name="uiSceneWidthSpinBox">
|
||||
<property name="suffix">
|
||||
<string> pixels</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>500</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000000</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>2000</number>
|
||||
<item row="9" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiLimitSizeNodeSymbolCheckBox">
|
||||
<property name="text">
|
||||
<string>Limit the size of node symbols</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<item row="12" column="0" colspan="3">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiDefaultLabelFontPushButton">
|
||||
<property name="text">
|
||||
<string>&Select default font</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiDefaultLabelColorPushButton">
|
||||
<property name="text">
|
||||
<string>&Select default color</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="11" column="0" colspan="3">
|
||||
<widget class="QPlainTextEdit" name="uiDefaultLabelStylePlainTextEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="plainText">
|
||||
<string>AaBbYyZz</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="uiSceneHeightSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> pixels</string>
|
||||
</property>
|
||||
@@ -891,16 +847,106 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="QSpinBox" name="uiNodeGridSizeSpinBox">
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="uiSceneWidthSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> pixels</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>10</number>
|
||||
<number>500</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000000</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>2000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiSnapToGridOnNewProject">
|
||||
<property name="text">
|
||||
<string>Snap to grid on new project</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="uiDrawingGridSizeLabel">
|
||||
<property name="text">
|
||||
<string>Default drawing grid size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="uiLabelPreviewLabel">
|
||||
<property name="text">
|
||||
<string>Default label style:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="uiNodeGridSizeLabel">
|
||||
<property name="text">
|
||||
<string>Default node grid size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="uiDrawingGridSizeSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>25</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="uiNodeGridSizeSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<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>
|
||||
@@ -908,18 +954,42 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<widget class="QSpinBox" name="uiDrawingGridSizeSpinBox">
|
||||
<property name="minimum">
|
||||
<number>10</number>
|
||||
<widget class="QCheckBox" name="uiShowGridOnNewProject">
|
||||
<property name="text">
|
||||
<string>Show grid on new project</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="3">
|
||||
<widget class="QCheckBox" name="uiRectangleSelectedItemCheckBox">
|
||||
<property name="text">
|
||||
<string>Draw a rectangle when an item is selected</string>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>10</number>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>25</number>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="16" column="0">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>5</height>
|
||||
</size>
|
||||
</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>
|
||||
@@ -930,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>
|
||||
@@ -966,7 +1045,7 @@
|
||||
<item>
|
||||
<widget class="QCheckBox" name="uiOverlayNotificationsCheckBox">
|
||||
<property name="text">
|
||||
<string>Display error, warning and info in a overlay popup</string>
|
||||
<string>Display error, warning and info in an overlay popup</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -980,21 +1059,24 @@
|
||||
<item>
|
||||
<widget class="QCheckBox" name="uiHdpiCheckBox">
|
||||
<property name="text">
|
||||
<string>Enable HDPI mode (this may crash the application on Linux, restart required)</string>
|
||||
<string>Enable HDPI mode (this may crash on Linux, restart required)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="uiMultiProfilesCheckBox">
|
||||
<property name="text">
|
||||
<string>Request for profile settings at application startup (work profile / home profile)</string>
|
||||
<string>Request for profile settings at application startup</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="uiDirectFileUpload">
|
||||
<property name="toolTip">
|
||||
<string>Experimental, requires computes visibility from GUI network</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Upload files directly to computes (experimental, requires computes visibility from GUI network)</string>
|
||||
<string>Upload files directly to computes (experimental)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -1041,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>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user