Compare commits

...

215 Commits

Author SHA1 Message Date
grossmj
98cc82e6fd Release v2.2.37 2023-01-25 15:06:12 +08:00
grossmj
4b795112b4 Merge branch 'master' into 2.2 2023-01-25 14:44:11 +08:00
grossmj
0e186afaf1 Comment AllocConsole() 2023-01-25 14:41:55 +08:00
grossmj
b218f7fdf8 Bump version to 2.2.37.dev3 2023-01-25 14:13:07 +08:00
grossmj
f1cb6d66f3 Try to fix hiding console. Ref #3290 2023-01-25 14:08:52 +08:00
grossmj
29758f1b4f Debug win32console 2023-01-24 22:25:39 +08:00
grossmj
445dcf3e3b Upgrade to PyQt5 v5.15.7 2023-01-24 18:54:15 +08:00
grossmj
f623f28509 Add venv to .gitignore 2023-01-17 13:40:37 +08:00
grossmj
9d02d57162 Bump version to 2.2.37.dev2 2023-01-17 13:30:09 +08:00
Jeremy Grossmann
df03f50e3d Merge pull request #3427 from eantowne/master
Changed Windows Terminal telnet console profile from OS X to Windows ref: #3193
2023-01-17 05:52:31 +05:45
Ean Towne
3b72a66ca5 Changed Windows Terminal telnet console profile from OS X to windows ref: issue #3193 2023-01-16 16:50:57 -05:00
grossmj
f8aee44442 Fix tests 2023-01-10 08:42:25 +08:00
grossmj
6dd4d1700e Convert README to Markdown 2023-01-10 08:16:17 +08:00
grossmj
9b674669db Development on 2.2.37.dev1 2023-01-05 09:13:30 +08:00
grossmj
53073d458f Merge branch '2.2' 2023-01-05 09:12:01 +08:00
grossmj
957d89d450 Release v2.2.36 2023-01-04 19:46:30 +08:00
Jeremy Grossmann
db02cbdb2f Merge pull request #3421 from GNS3/qemu-tpm-support
Trusted Platform Module (TPM) support for Qemu VMs
2023-01-04 10:55:52 +05:45
grossmj
0c9f70152f Add checkbox to enable TPM 2023-01-04 12:45:40 +08:00
grossmj
9016975958 Add Trusted Platform Module (TPM) support for Qemu VMs 2023-01-04 12:13:19 +08:00
grossmj
bf295060fd Add "on_close" setting to appliance schema. Fixes https://github.com/GNS3/gns3-server/issues/2148 2023-01-01 16:23:17 +08:00
grossmj
c22ec9f8bd Add default 'ide' disk interface when manually creating Qemu VM template. Fixes #3360 2022-12-29 08:54:40 +08:00
grossmj
bb49cadc6a Fix zoom factor is multiplied when loading projects. Fixes #3408 2022-12-29 08:49:10 +08:00
grossmj
131a49160c Update sentry-sdk dependency 2022-12-28 15:13:26 +08:00
grossmj
225b829eae Fix syntax error in .whitesource 2022-12-27 12:27:26 +08:00
grossmj
e5ef6180b1 Add more base branches for Mend to scan 2022-12-27 12:25:49 +08:00
grossmj
6e7947eea3 Remove deprecated PuTTY option in preferences. Ref https://github.com/GNS3/gns3-gui/discussions/3415 2022-12-24 11:19:29 +08:00
Jeremy Grossmann
3922d370a8 Create SECURITY.md 2022-12-20 21:28:08 +08:00
grossmj
833b9d00c9 Upgrade dependencies 2022-12-18 14:14:56 +08:00
Jeremy Grossmann
377b8dfcaf Create codeql.yml 2022-12-18 14:08:00 +08:00
grossmj
e68937475f Development on v2.2.36.dev2 2022-11-11 00:36:47 +08:00
grossmj
6f418f0853 Release v2.2.35.1 2022-11-10 22:21:14 +08:00
grossmj
8e59927ada Merge branch 'master' into 2.2 2022-11-10 22:06:25 +08:00
grossmj
1012686053 Development on 2.2.36.dev1 2022-11-09 20:02:20 +08:00
grossmj
672bd850ad Merge branch '2.2' 2022-11-09 20:00:38 +08:00
grossmj
5db5e1f9fe Release v2.2.35 2022-11-08 23:40:25 +08:00
grossmj
ca94c71bf2 Use Visual Studio 2022 in appveyor.yml 2022-11-08 23:09:23 +08:00
grossmj
76264c55ce Merge branch 'master' into 2.2 2022-11-08 19:29:49 +08:00
grossmj
fd243c42a8 Downgrade psutil to v5.9.2 2022-11-08 19:18:07 +08:00
grossmj
a6521ef9e4 Upgrade psutil to v5.9.4 2022-11-08 18:49:31 +08:00
grossmj
9fa833762c Upgrade pywin32 to v305 2022-11-08 18:29:51 +08:00
grossmj
ca0c6468b5 Fix "variables": [] in project file leads to unlimited increase of empty name/value pairs in GUI. Fixes #3397 2022-11-07 22:29:02 +08:00
grossmj
15f6945a94 Upgrade dependencies 2022-11-07 21:48:45 +08:00
grossmj
645deb8c79 Ignore local revision when comparing versions. 2022-11-07 20:03:05 +08:00
grossmj
428f12a2b3 Make version PEP 440 compliant 2022-11-06 17:51:31 +08:00
grossmj
9ad5760ee6 Support for Python 3.11 2022-10-30 19:04:54 +08:00
grossmj
82fc4fb3c9 Revert "Fix issue when using tail.exe with non-ascii paths. Fixes #3021"
This reverts commit df42147d92.
2022-10-19 19:38:43 +08:00
grossmj
df42147d92 Fix issue when using tail.exe with non-ascii paths. Fixes #3021 2022-10-19 19:04:29 +08:00
grossmj
da5520aa90 Upgrade PyQt to 5.15.7 and pywin32 to v304 2022-10-19 18:31:40 +08:00
Jeremy Grossmann
491c66a315 Merge pull request #3395 from GNS3/fix/3393
Update requirements.txt
2022-10-18 22:30:11 +08:00
Jeremy Grossmann
e5c81da700 Merge branch '2.2' into fix/3393 2022-10-18 22:28:32 +08:00
grossmj
65fad1b4f4 Upgrade to Visual Studio 2022 in appveyor.yml 2022-10-18 21:47:10 +08:00
grossmj
34661908d9 Upgrade to Python 3.7 in appveyor.yml 2022-10-18 21:42:51 +08:00
grossmj
aee5ffa17f Upgrade pip and setuptools in appveyor.yml 2022-10-18 21:40:08 +08:00
grossmj
e9e8be42b5 Upgrade pytest. Fixes #3399 2022-10-18 21:16:46 +08:00
grossmj
ae0d928383 Use jsonschema v3.2.0 for Python 3.6 2022-10-12 22:13:34 +08:00
grossmj
8db3c1be42 Allow for more dependency versions at patch level 2022-10-12 22:07:37 +08:00
grossmj
f50da3ebd7 Replace deprecated distro.linux_distribution() call 2022-10-11 23:28:11 +08:00
grossmj
75b52fc9a4 Update dev-requirements.txt 2022-10-11 23:23:15 +08:00
grossmj
1952da5876 Update requirements.txt 2022-10-11 23:01:24 +08:00
Jeremy Grossmann
1f620026d4 Merge pull request #3394 from KaisenCAS/master
CVE-2007-4559 patch
2022-10-10 23:31:52 +08:00
grossmj
1d293618e5 Upgrade dependencies 2022-10-10 14:29:04 +08:00
Kevin Chevreuil - Kaisen
2622549ce6 Add a fix for the CVE-2007-4559 2022-10-09 22:55:15 +02:00
grossmj
900bd1c0b4 Development on 2.2.35dev1 2022-08-29 11:14:44 +02:00
grossmj
0b3dbb2843 Release v2.2.34 2022-08-28 23:28:12 +02:00
grossmj
ef4f6b2b27 Upgrade Sentry dependency 2022-08-28 00:02:54 +02:00
grossmj
e9806345ca Downgrade to pytest v7.0.1 (last version to support 2022-08-27 19:35:36 +02:00
grossmj
ee23e32c75 Upgrade dev dependencies 2022-08-27 19:29:42 +02:00
grossmj
fbeacdcb2a Implement new option (Delete All) to contextual menu in "Console" dock. Fixes #3325 2022-08-16 17:48:58 +02:00
grossmj
b3937c7b94 Fix 2560x1440 resolution for Docker container 2022-08-07 23:56:11 +02:00
grossmj
f2711732db Back to development on v2.2.34dev2 2022-06-21 11:52:58 +02:00
grossmj
148ac4b072 Revert "Development on v2.2.34dev2"
This reverts commit 65eeb79b26.
2022-06-21 11:51:01 +02:00
grossmj
65eeb79b26 Development on v2.2.34dev2 2022-06-21 11:38:09 +02:00
grossmj
537304ce08 Release v2.2.33.1 2022-06-21 10:48:02 +02:00
grossmj
f22df5f016 Development on v2.2.34dev1 2022-06-20 21:47:42 +02:00
grossmj
8dfc8b7714 Release v2.2.33 2022-06-20 20:53:21 +02:00
grossmj
8c6fa9433f Upgrade sentry-sdk and psutil 2022-06-20 20:05:34 +02:00
Jeremy Grossmann
63837578c5 Merge pull request #3340 from GNS3/node-name-checks
Check node names
2022-06-20 19:02:37 +02:00
grossmj
b719703dbe Check that node names for Qemu and Docker are valid 2022-06-18 16:59:14 +02:00
grossmj
084d14c17e Backport reset all console connections. Fixes #2072 2022-06-15 15:58:15 +02:00
grossmj
8c0fca1dd7 Add more video resolutions to Docker containers using VNC. Fixes #3329 2022-06-09 00:26:39 +08:00
grossmj
863d05c923 Add python_requires=">=3.4" in setup.py. Fixes #3326 2022-06-07 18:27:01 +08:00
grossmj
3ebaac8a2c Only allow post release corrective versions of GUI and server to interact 2022-06-07 18:22:06 +08:00
grossmj
16878c9dfa Allow minor versions of GUI and server to interact 2022-06-07 18:06:53 +08:00
grossmj
45da18bb7c Update VirtViewer path. Fixes #3334 2022-06-07 17:35:55 +08:00
grossmj
7a6d06ea0c Development on 2.2.33dev1 2022-04-27 19:51:24 +07:00
grossmj
d371042647 Upgrade distro package to v1.7.0 2022-04-27 19:48:50 +07:00
grossmj
0321c11c34 Release v2.2.32 2022-04-27 18:47:20 +07:00
grossmj
522df41a57 Use public DSNs for Sentry 2022-04-20 18:41:18 +07:00
grossmj
afccdf5b9e Fix exception when doubleclick on NAT node. Fixes #3312 2022-04-20 17:55:16 +07:00
grossmj
b2cd24b511 Upgrade some packages 2022-04-20 17:38:54 +07:00
grossmj
6d131a05f1 Fix "Apply" button in the "Preferences" dialog stays gray when templates/nodes are opened by double-click. Fixes #3307 2022-04-20 16:49:22 +07:00
grossmj
35e6156c6c Add 'reset docks' in the view menu. Ref #3317 2022-04-20 15:53:32 +07:00
grossmj
96d8de4da8 Development on 2.2.32dev1 2022-02-26 20:39:42 +10:30
grossmj
6b5a6f3dfe Release v2.2.31 2022-02-26 18:22:17 +10:30
grossmj
8f82eac321 Development on 2.2.31dev1 2022-02-25 15:59:19 +10:30
grossmj
e03ed64f59 Install setuptools v59.6.0 when using Python 3.6 2022-02-25 15:50:35 +10:30
grossmj
3d702aabd0 Release v2.2.30 2022-02-25 14:51:39 +10:30
grossmj
f5e63c2321 Set setuptools to v60.6.0 2022-02-06 21:02:56 +10:30
grossmj
1047eb916a Upgrade dependencies 2022-02-06 17:33:10 +10:30
grossmj
5dc7d0fbda Upgrade to pywin32 v303. Ref #3290 2022-02-06 17:31:32 +10:30
grossmj
2609be98b6 Fix int() call. Ref #3283 2022-01-15 18:57:15 +10:30
grossmj
6286e596c0 Fix QPoint() as unexpected type 'float'. Fixes #3283 2022-01-15 18:55:38 +10:30
grossmj
3c546086ed Fix painter.drawRect() has unexpected type 'float'. Fixes #3282 2022-01-15 18:32:35 +10:30
grossmj
f4b2c1c5b9 Fix SpinBox.setValue() requires integer. Fixes #3281 2022-01-11 23:12:54 +10:30
grossmj
e578ecdd8a Development on 2.2.30dev1 2022-01-08 22:52:59 +10:30
grossmj
da8adbaa18 Release v2.2.29 2022-01-08 22:14:59 +10:30
grossmj
6d1333f5fe Clear cache when opening symbol selection dialog. Fixes #3256 2021-12-27 12:43:32 +10:30
grossmj
92c858dd07 Fix @ in username issue with HTTP authentication. Fixes #3275 2021-12-25 11:19:07 +10:30
grossmj
0c7a12f68c Merge branch 'master' into 2.2 2021-12-25 10:58:46 +10:30
Jeremy Grossmann
a4d08cce8c Merge pull request #3277 from etiennewan/etiennewan-patch-2
Fixed QPoint called with floats
2021-12-25 10:27:00 +10:00
grossmj
e0dd7a66e1 Use '//' operator instead of int() 2021-12-24 13:39:19 +10:30
grossmj
23be668c97 Fix create drawing item calls since mapToScene() returns a QPointF
https://doc.qt.io/qt-5/qgraphicsview.html#mapToScene-4
2021-12-24 13:38:26 +10:30
Etienne Wan
68d0278140 Fixed QPoint called with floats 2021-12-23 18:37:26 +01:00
Jeremy Grossmann
d8e4c1de4d Merge pull request #3273 from tsndqst/fix_create_link_test
Fix create_link test
2021-12-16 12:26:38 +10:00
Your Name
a5aa9bfb7a Remove problematic lines 2021-12-15 20:13:57 -06:00
grossmj
3e0273848f Development on 2.2.29dev1 2021-12-15 21:38:34 +10:30
grossmj
ec374f173c Release v2.2.28 2021-12-15 13:54:24 +10:30
grossmj
b8abdc79dc Merge branch 'master' into 2.2 2021-12-15 13:52:41 +10:30
Jeremy Grossmann
43744eab7e Merge pull request #3272 from etiennewan/patch-1
Fixed drawLine called with float arguments
2021-12-15 09:28:35 +10:00
Etienne Wan
e16f700e49 Fixed drawLine called with float arguments 2021-12-13 23:27:28 +01:00
Jeremy Grossmann
925d57b2f8 Merge pull request #3263 from FocusedOne/master
Fixed dead VIX API link
2021-11-23 09:15:54 +10:30
FocusedOne
eceaea1317 Fixed dead VIX API link
Replaced old dead vmware link with current 1.17 version download.
2021-11-22 16:40:01 -06:00
grossmj
4326785dfc Development on 2.2.28dev1 2021-11-13 16:31:21 +10:30
grossmj
3920c28bde Release v2.2.27 2021-11-12 15:33:53 +10:30
grossmj
b34f51e4b0 Merge branch 'master' into 2.2 2021-11-12 14:50:55 +10:30
grossmj
ef45b2e0f1 Fix symbols in "Symbol selection" dialog are not placed in alphabetical order. Fixes #3245 2021-11-08 22:20:22 +10:30
grossmj
545a9f53a8 Fix links duplicates in topology summary. Fixes #3251 2021-11-08 21:55:29 +10:30
grossmj
83d9367860 Development on 2.2.27dev1 2021-10-08 21:49:11 +10:30
grossmj
2131f07e5f Merge branch '2.2' 2021-10-08 21:46:38 +10:30
grossmj
cf3e716e63 Release v2.2.26 2021-10-08 21:02:04 +10:30
grossmj
c79f14bcab Open "template configuration" dialog with double click on template name in "Preferences". Fixes #3239 2021-10-08 16:35:25 +10:30
grossmj
acd044a88a Only show "virtio" network adapter when legacy node is enabled. Fixes https://github.com/GNS3/gns3-gui/issues/1969 2021-10-08 15:46:56 +10:30
Jeremy Grossmann
f26c638350 Merge pull request #3237 from SDN-Projects/optimization/pip-no-cache-dir
chore : use --no-cache-dir flag to pip in dockerfiles to save space
2021-09-23 09:59:58 +09:30
Pratik Raj
4ea24e622b chore : use --no-cache-dir flag to pip in dockerfiles to save space
using --no-cache-dir flag in pip install ,make sure downloaded packages
by pip don't cached on system . This is a best practice which make sure
to fetch from repo instead of using local cached one . Further , in case
of Docker Containers , by restricting caching , we can reduce image size.
In term of stats , it depends upon the number of python packages
multiplied by their respective size . e.g for heavy packages with a lot
of dependencies it reduce a lot by don't caching pip packages.

Further , more detail information can be found at

https://medium.com/sciforce/strategies-of-docker-images-optimization-2ca9cc5719b6

Signed-off-by: Pratik Raj <rajpratik71@gmail.com>
2021-09-22 15:13:38 +05:30
grossmj
ab854752d9 Double-click on a template opens "template configuration" dialog. Fixes #3236 2021-09-20 20:28:56 +09:30
grossmj
5cee045a65 Fix "Custom symbols" can't be unfolded after using "Filter" field. Fixes #3231 2021-09-20 18:39:32 +09:30
grossmj
37cd82fb44 Development on v2.2.26dev1 2021-09-14 21:13:04 +09:30
grossmj
334eb5175c Release v2.2.25 2021-09-14 19:20:11 +09:30
grossmj
25841ea7db Fix menu disabled for modal dialogs on macOS. Fixes #3007 2021-09-09 21:21:22 +09:30
grossmj
3d3b4f92b2 Change method to display the recent files menu. Fixes #3007 2021-09-09 09:23:45 +09:30
grossmj
82740da89d Fix bug when using empty port names for custom adapters. Fixes #3228 2021-09-08 16:16:27 +09:30
grossmj
ad19b3dda0 Upgrade PyQt5 to version 5.15.4 for macOS 2021-09-08 15:36:04 +09:30
grossmj
bb8fd18f98 Fix mouse zoom-in/out step value is two times bigger than keyboard one. Fixes #3226 2021-09-08 15:26:56 +09:30
grossmj
336eaf443a Upgrade to Qt 5.15.4 on Windows. Ref #3210 2021-09-08 14:25:25 +09:30
grossmj
0b94be6805 Fix issue with custom adapters at the node level. Fixes #3223 2021-09-05 21:15:30 +09:30
grossmj
671ced78ff Merge branch 'master' into 2.2 2021-09-02 14:45:00 +09:30
Jeremy Grossmann
c8766ce529 Merge pull request #3157 from hrnciar/setuptools
Explicitly require setuptools, utils/get_resource.py imports pkg_resources
2021-08-29 22:06:22 -07:00
Jeremy Grossmann
bec9512c78 Merge branch 'master' into setuptools 2021-08-29 22:02:55 -07:00
grossmj
b2ad5f4158 Development on 2.2.25dev1 2021-08-25 21:23:19 +09:30
grossmj
966873bc6c Release v2.2.24 2021-08-25 20:31:26 +09:30
grossmj
5b9111b55d Merge branch 'master' into 2.2 2021-08-25 20:08:45 +09:30
grossmj
56688f2236 Update dependencies 2021-08-24 21:12:27 +09:30
grossmj
2e656a9d53 Fix incorrect Qemu binary selected when importing template. Fixes https://github.com/GNS3/gns3-gui/issues/3216 2021-08-24 17:26:07 +09:30
grossmj
2790f707c3 Early support for Python3.10 2021-08-15 15:10:02 +09:30
Jeremy Grossmann
ee9002df61 Merge pull request #3217 from GNS3/dependabot/pip/pywin32-301
Bump pywin32 from 300 to 301
2021-08-09 17:59:04 -07:00
dependabot[bot]
52626e9fe9 Bump pywin32 from 300 to 301
Bumps [pywin32](https://github.com/mhammond/pywin32) from 300 to 301.
- [Release notes](https://github.com/mhammond/pywin32/releases)
- [Changelog](https://github.com/mhammond/pywin32/blob/master/CHANGES.txt)
- [Commits](https://github.com/mhammond/pywin32/commits)

---
updated-dependencies:
- dependency-name: pywin32
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-09 20:48:41 +00:00
grossmj
6619c6af97 Add PyQt5==5.12.3 for macOS build 2021-08-07 20:09:41 +09:30
grossmj
60e04c7248 Development on 2.2.24dev1 2021-08-05 21:16:10 +09:30
grossmj
724858f977 Release v2.2.23 2021-08-05 15:58:54 +09:30
Jeremy Grossmann
5a2e05a4fd Merge pull request #3212 from GNS3/handle_no-kvm_deprecated
Handle -no-kvm param deprecated in Qemu >= v5.2
2021-07-27 17:37:45 +09:30
grossmj
010888e3ca Handle -no-kvm param deprecated in Qemu >= v5.2 2021-07-27 16:34:51 +09:30
grossmj
3226921536 Support for invisible links. Fixes #2461 2021-07-27 15:30:58 +09:30
grossmj
022e918301 Add kitty console application command line. Fixes #3203 2021-07-25 16:12:35 +09:30
grossmj
846b19a9e7 Add Windows Terminal profile as an option for Console Applications. Fixes #3193 2021-06-14 13:10:01 +09:30
grossmj
45f5c6e010 Development on 2.2.23dev1 2021-06-10 16:20:10 +09:30
grossmj
963bbb7b89 Release v2.2.22 2021-06-10 15:41:58 +09:30
grossmj
016ad7a775 Fix exception shown when GNS3 is started with empty config. Fixes #3188 2021-06-10 12:28:25 +09:30
grossmj
e8c82566c6 Add ZOC8 console terminal for macOS command line 2021-06-07 19:41:09 +09:30
grossmj
1ed6fceade Fix tests. Ref https://github.com/GNS3/gns3-gui/issues/2461 2021-06-07 14:26:20 +09:30
grossmj
d945fd8b7b Minor changes to style editor dialog. 2021-06-07 14:10:32 +09:30
grossmj
fd6c7eccd0 Link style support. Fixes https://github.com/GNS3/gns3-gui/issues/2461 2021-06-07 14:09:58 +09:30
grossmj
7a1afe2aec Upgrade dependencies 2021-06-07 13:46:06 +09:30
grossmj
6debe56d8e Fix charcoal theme. Ref #3137 2021-06-06 21:45:16 +09:30
grossmj
a4c7d41c26 Fix issue when showing menu to select port. Fixes #3169 2021-05-20 22:15:21 -07:00
grossmj
ea9243dcd9 Merge remote-tracking branch 'origin/2.2' into 2.2 2021-05-20 15:00:49 +09:30
grossmj
e9d8337bd6 Revert "Downgrade to PyQt5 5.12.1. Fixes https://github.com/GNS3/gns3-gui/issues/3169"
This reverts commit ece4d51213.
2021-05-20 14:59:44 +09:30
Jeremy Grossmann
3c92e463f8 Update setup.py
Fixes https://github.com/GNS3/gns3-server/issues/1897
2021-05-16 17:45:11 +09:30
grossmj
3d07db5c5f Development on 2.2.22dev1 2021-05-10 23:44:40 +09:30
grossmj
20cc309ac8 Release v2.2.21 2021-05-10 22:42:47 +09:30
grossmj
262a2839c5 Fix issue with empty project variable name. Fixes #3162 2021-05-10 17:55:06 +09:30
grossmj
ece4d51213 Downgrade to PyQt5 5.12.1. Fixes https://github.com/GNS3/gns3-gui/issues/3169 2021-05-10 17:03:22 +09:30
grossmj
0ef39ba129 Development on 2.2.21dev1 2021-04-09 13:50:21 +09:30
grossmj
f90267b4f0 Release v2.2.20 2021-04-09 12:14:38 +09:30
grossmj
8f16706a22 Merge branch 'master' into 2.2 2021-04-09 12:05:15 +09:30
grossmj
2d3ee3abf9 Fix project does not load anymore. Fixes #3140 2021-04-07 16:47:09 +09:30
grossmj
b8b209fa55 Fix errors while connecting to server 2021-04-07 15:51:52 +09:30
grossmj
18129e3d29 Do not connect to server while waiting for user to accept/reject SSL certificate. Fixes #3144 2021-04-07 12:09:38 +09:30
grossmj
7a2b9c024f Fix invalid server version check request. Fixes #3144 2021-04-07 12:03:37 +09:30
grossmj
4923a6dc17 Revert to PyQt5 v5.12.3 because of SSL not working
Probably the SSL DLLs weren't properly found and included by cx_Freeze
2021-04-06 23:01:07 +09:30
grossmj
73dfc047aa Set PyQt5 version to 5.15.2 on Windows 2021-04-06 22:12:34 +09:30
grossmj
fe0a70c4be Upgrade dependencies 2021-04-06 13:57:00 +09:30
Tomas Hrnciar
67014965be Explicitly require setuptools, utils/get_resource.py imports pkg_resources 2021-03-31 11:53:28 +02:00
Jeremy Grossmann
f14cb43404 Merge pull request #3153 from VidVidex/master
Add terminator as a predefined custom console option
2021-03-26 14:53:00 +10:30
Vid
f8517ee5ac Add terminator as a predefined custom console option 2021-03-24 20:28:19 +01:00
grossmj
7dc607b4c5 Development on 2.2.20dev1 2021-03-05 16:48:09 +10:30
grossmj
882fa76550 Release v2.2.19 2021-03-05 14:51:03 +10:30
grossmj
1490a1ad8f Development on 2.2.19dev1 2021-02-16 20:44:58 +10:30
grossmj
aab0c99cc6 Release v2.2.18 2021-02-16 19:09:46 +10:30
grossmj
a6a987d74c Fix bug with SSL connection on projet websocket stream. 2021-02-16 17:42:18 +10:30
grossmj
9c58b18c20 Merge remote-tracking branch 'origin/2.2' into 2.2 2021-02-16 16:42:45 +10:30
grossmj
8bc499c68f Bump version to 2.2.18dev2 2021-02-16 16:35:26 +10:30
Jeremy Grossmann
bd5eb288b7 Merge pull request #3130 from GNS3/ssl-support
SSL support.
2021-02-16 16:16:32 +10:30
grossmj
465a289568 SSL support. 2021-02-16 16:08:27 +10:30
grossmj
d240ba3056 Merge remote-tracking branch 'origin/2.2' into 2.2 2021-01-26 23:11:11 +10:30
grossmj
3cedfd3649 Remove the useless file "zoom-in (copy).svg". Fixes #3114 2021-01-26 23:10:34 +10:30
Jeremy Grossmann
276d7abdd9 Merge pull request #3104 from b-ehlers/QemuConfig
Add Qemu config disk
2020-12-14 14:23:09 +10:30
grossmj
927e38bd6d Development on 2.2.18dev1 2020-12-04 18:10:11 +10:30
Bernhard Ehlers
d3d9e1e8ae Use HDD disk image as startup QEMU config disk 2020-10-19 03:45:27 +02:00
Bernhard Ehlers
05f8df345a Fix HDD configuration layout
(cherry picked from commit 4f631669e5)
2020-10-16 10:22:32 +02:00
Bernhard Ehlers
3e212fc629 Edit only text mode config files
(cherry picked from commit 880ac5e8c3)
2020-08-18 02:27:31 +02:00
Bernhard Ehlers
25e41dc0f1 Hide config import/export when configFiles attribute is empty
(cherry picked from commit fd7b915e96)
2020-08-17 13:09:59 +02:00
grossmj
c58c7774c4 Qemu disk interfaces must be set to "none" by default. Ref #3035
(cherry picked from commit 5fbb6cbf61)
2020-08-17 12:49:21 +09:30
grossmj
bd2bc8265c Do not allow image to be configured on Qemu VM secondary slave disk if create config disk option is enabled.
(cherry picked from commit 04f9a1cf8c)
2020-08-15 16:05:43 +09:30
grossmj
f2209a2780 Add explicit option to automatically create or not the config disk. Off by default.
(cherry picked from commit af79471afd)
2020-08-14 17:57:24 +09:30
Bernhard Ehlers
5dc2c77806 QEMU config disk - enable QEMU config import/export
(cherry picked from commit d01f15c4df)
2020-04-06 13:42:00 +02:00
85 changed files with 117685 additions and 122293 deletions

76
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,76 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master" ]
schedule:
- cron: '27 6 * * 2'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

1
.gitignore vendored
View File

@@ -63,3 +63,4 @@ __pycache__
# Virtualenv
env
venv

View File

@@ -2,7 +2,8 @@
"scanSettings": {
"configMode": "AUTO",
"configExternalURL": "",
"projectToken" : ""
"projectToken" : "",
"baseBranches": ["master", "2.2", "3.0"]
},
"checkRunSettings": {
"vulnerableCheckRunConclusionLevel": "failure"

160
CHANGELOG
View File

@@ -1,5 +1,165 @@
# Change Log
## 2.2.37 25/01/2023
* Upgrade to PyQt5 v5.15.7
* Changed Windows Terminal telnet console profile from OS X to windows ref: issue #3193
## 2.2.36 04/01/2023
* Add Trusted Platform Module (TPM) support for Qemu VMs
* Add "on_close" setting to appliance schema. Fixes https://github.com/GNS3/gns3-server/issues/2148
* Add default 'ide' disk interface when manually creating Qemu VM template. Fixes #3360
* Fix zoom factor is multiplied when loading projects. Fixes #3408
* Remove deprecated PuTTY option in preferences. Ref https://github.com/GNS3/gns3-gui/discussions/3415
## 2.2.35.1 10/11/2022
* Re-release Web-Ui v2.2.35
## 2.2.35 08/11/2022
* Fix "variables": [] in project file leads to unlimited increase of empty name/value pairs in GUI. Fixes #3397
* Make version PEP 440 compliant
* Support for Python 3.11
* Upgrade PyQt to 5.15.7 and pywin32 to v305
* Allow for more dependency versions at patch level
* Replace deprecated distro.linux_distribution() call
* Add a fix for the CVE-2007-4559
## 2.2.34 28/08/2022
* Upgrade dev dependencies
* Implement new option (Delete All) to contextual menu in "Console" dock. Fixes #3325
* Fix 2560x1440 resolution for Docker container
## 2.2.33.1 21/06/2022
* Match GNS3 server version
## 2.2.33 20/06/2022
* Upgrade sentry-sdk and psutil
* Check that node names for Qemu and Docker are valid
* Backport reset all console connections. Fixes #2072
* Add more video resolutions to Docker containers using VNC. Fixes #3329
* Add python_requires=">=3.4" in setup.py. Fixes #3326
* Only allow post release corrective versions of GUI and server to interact
* Allow minor versions of GUI and server to interact
* Update VirtViewer path. Fixes #3334
## 2.2.32 27/04/2022
* Use public DSNs for Sentry
* Fix exception when doubleclick on NAT node. Fixes #3312
* Fix "Apply" button in the "Preferences" dialog stays gray when templates/nodes are opened by double-click. Fixes #3307
* Add 'reset docks' in the view menu. Ref #3317
## 2.2.31 26/02/2022
* Install setuptools v59.6.0 when using Python 3.6
## 2.2.30 25/02/2022
* Set setuptools to v60.6.0
* Upgrade to pywin32 v303. Ref #3290
* Fix int() call. Ref #3283
* Fix QPoint() as unexpected type 'float'. Fixes #3283
* Fix painter.drawRect() has unexpected type 'float'. Fixes #3282
* Fix SpinBox.setValue() requires integer. Fixes #3281
## 2.2.29 08/01/2022
* Clear cache when opening symbol selection dialog. Fixes #3256
* Fix @ in username issue with HTTP authentication. Fixes #3275
* Use '//' operator instead of int()
* Fix create drawing item calls since mapToScene() returns a QPointF https://doc.qt.io/qt-5/qgraphicsview.html#mapToScene-4
* Fixed QPoint called with floats
## 2.2.28 15/12/2021
* Fixed drawLine called with float arguments
* Fixed dead VIX API link
## 2.2.27 12/11/2021
* Fix symbols in "Symbol selection" dialog are not placed in alphabetical order. Fixes #3245
* Fix links duplicates in topology summary. Fixes #3251
* chore : use --no-cache-dir flag to pip in dockerfiles to save space
## 2.2.26 08/10/2021
* Upgrade embedded Python to version 3.7 in Windows package
* Upgrade Visual C++ Redistributable for Visual Studio 2019 in Windows package
* Fix SSL support in Windows package
* Open "template configuration" dialog with double click on template name in "Preferences". Fixes #3239
* Only show "virtio" network adapter when legacy node is enabled. Fixes https://github.com/GNS3/gns3-gui/issues/1969
* Double-click on a template opens "template configuration" dialog. Fixes #3236
* Fix "Custom symbols" can't be unfolded after using "Filter" field. Fixes #3231
## 2.2.25 14/09/2021
* Fix menu disabled for modal dialogs on macOS. Fixes #3007
* Change method to display the recent files menu. Fixes #3007
* Fix bug when using empty port names for custom adapters. Fixes #3228
* Upgrade Qt to version 5.15.4 on macOS
* Fix mouse zoom-in/out step value is two times bigger than keyboard one. Fixes #3226
* Upgrade to Qt 5.15.4 on Windows. Ref #3210
* Fix issue with custom adapters at the node level. Fixes #3223
* Explicitly require setuptools, utils/get_resource.py imports pkg_resources
## 2.2.24 25/08/2021
* Fix incorrect Qemu binary selected when importing template. Fixes https://github.com/GNS3/gns3-gui/issues/3216
* Early support for Python3.10
* Bump pywin32 from 300 to 301
* Add PyQt5==5.12.3 for macOS build
## 2.2.23 05/08/2021
* Handle -no-kvm param deprecated in Qemu >= v5.2
* Support for invisible links. Fixes #2461
* Add kitty console application command line. Fixes #3203
* Add Windows Terminal profile as an option for Console Applications. Fixes #3193
## 2.2.22 10/06/2021
* Fix exception shown when GNS3 is started with empty config. Fixes #3188
* Add ZOC8 console terminal for macOS command line
* Link style support. Fixes https://github.com/GNS3/gns3-gui/issues/2461
* Fix charcoal theme. Ref #3137
* Fix issue when showing menu to select port. Fixes #3169
## 2.2.21 10/05/2021
* Fix issue with empty project variable name. Fixes #3162
* Downgrade to PyQt5 5.12.1. Fixes https://github.com/GNS3/gns3-gui/issues/3169
## 2.2.20 09/04/2021
* Fix project does not load anymore. Fixes #3140
* Do not connect to server while waiting for user to accept/reject SSL certificate. Fixes #3144
* Fix invalid server version check request. Fixes #3144
* Upgrade dependencies
* Add terminator as a predefined custom console option
## 2.2.19 05/03/2021
* No changes
## 2.2.18 16/02/2021
* SSL support.
* Remove the useless file "zoom-in (copy).svg". Fixes #3114
* Use HDD disk image as startup QEMU config disk
* Edit only text mode config files
* Hide config import/export when configFiles attribute is empty
* Qemu disk interfaces must be set to "none" by default. Ref #3035
* Do not allow image to be configured on Qemu VM secondary slave disk if create config disk option is enabled.
* Add explicit option to automatically create or not the config disk. Off by default.
* QEMU config disk support
## 2.2.17 04/12/2020
* Remove "-nographic" option by default for Qemu VM. Fixes #3094

View File

@@ -8,7 +8,7 @@ RUN apt-get clean
ADD dev-requirements.txt /dev-requirements.txt
ADD requirements.txt /requirements.txt
RUN pip3 install -r /dev-requirements.txt
RUN pip3 install --no-cache-dir -r /dev-requirements.txt
ADD . /src
WORKDIR /src

View File

@@ -1,4 +1,4 @@
include README.rst
include README.md
include AUTHORS
include LICENSE
include MANIFEST.in

62
README.md Normal file
View File

@@ -0,0 +1,62 @@
GNS3-gui
========
[![image](https://github.com/GNS3/gns3-gui/workflows/testing/badge.svg)](https://github.com/GNS3/gns3-gui/actions?query=workflow%3Atesting)
[![image](https://img.shields.io/pypi/v/gns3-gui.svg)](https://pypi.python.org/pypi/gns3-gui)
[![image](https://snyk.io/test/github/GNS3/gns3-gui/badge.svg)](https://snyk.io/test/github/GNS3/gns3-gui)
GNS3 GUI repository.
Installation
------------
Please see <https://docs.gns3.com/>
Software dependencies
---------------------
PyQt5 which is either part of the Linux distribution or installable from
PyPi. The other Python dependencies are automatically installed during
the GNS3 GUI installation and are listed
[here](https://github.com/GNS3/gns3-gui/blob/master/requirements.txt)
For connecting to nodes using Telnet, a Telnet client is required. On
Linux that's a terminal emulator like xterm, gnome-terminal, konsole
plus the telnet program. For connecting to nodes with a GUI, a VNC
client is required, optionally a SPICE client can be used for Qemu
nodes.
For using packet captures within GNS3, Wireshark should be installed.
It's recommended, but if you don't need that functionality you can go
without it.
Development
-----------
If you want to update the interface, modify the .ui files using QT
tools. And:
``` {.bash}
cd scripts
python build_pyqt.py
```
### Debug
If you want to see the full logs in the internal shell you can type:
``` {.bash}
debug 2
```
Or start the app with --debug flag.
Due to the fact PyQT intercept you can use a web debugger for inspecting
stuff: <https://github.com/Kozea/wdb>
Security issues
---------------
Please contact us at <security@gns3.net>

View File

@@ -1,60 +0,0 @@
GNS3-gui
========
.. image:: https://github.com/GNS3/gns3-gui/workflows/testing/badge.svg
:target: https://github.com/GNS3/gns3-gui/actions?query=workflow%3Atesting
.. image:: https://img.shields.io/pypi/v/gns3-gui.svg
:target: https://pypi.python.org/pypi/gns3-gui
.. image:: https://snyk.io/test/github/GNS3/gns3-gui/badge.svg
:target: https://snyk.io/test/github/GNS3/gns3-gui
GNS3 GUI repository.
Installation
------------
Please see https://docs.gns3.com/
Software dependencies
---------------------
PyQt5 which is either part of the Linux distribution or installable from PyPi. The other Python dependencies are automatically installed during the GNS3 GUI installation and are listed `here <https://github.com/GNS3/gns3-gui/blob/master/requirements.txt>`_
For connecting to nodes using Telnet, a Telnet client is required. On Linux that's a terminal emulator like xterm, gnome-terminal, konsole plus the telnet program. For connecting to nodes with a GUI, a VNC client is required, optionally a SPICE client can be used for Qemu nodes.
For using packet captures within GNS3, Wireshark should be installed. It's recommended, but if you don't need that functionality you can go without it.
Development
-------------
If you want to update the interface, modify the .ui files using QT tools. And:
.. code:: bash
cd scripts
python build_pyqt.py
Debug
"""""
If you want to see the full logs in the internal shell you can type:
.. code:: bash
debug 2
Or start the app with --debug flag.
Due to the fact PyQT intercept you can use a web debugger for inspecting stuff:
https://github.com/Kozea/wdb
Security issues
----------------
Please contact us at security@gns3.net

5
SECURITY.md Normal file
View File

@@ -0,0 +1,5 @@
# Security Policy
## Reporting a Vulnerability
Please use GitHub's report a vulnerability feature. More information can be found in https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability

View File

@@ -1,15 +1,16 @@
version: '{build}-{branch}'
image: Visual Studio 2017
image: Visual Studio 2022
platform: x64
environment:
PYTHON: "C:\\Python36-x64"
PYTHON: "C:\\Python37-x64"
DISTUTILS_USE_SDK: "1"
install:
- cinst nmap
- "%PYTHON%\\python.exe -m pip install -U pip setuptools" # upgrade pip & setuptools first
- "%PYTHON%\\python.exe -m pip install -r dev-requirements.txt"
- "%PYTHON%\\python.exe -m pip install -r win-requirements.txt"

View File

@@ -1,5 +1,6 @@
-rrequirements.txt
pytest==5.4.3
flake8==3.8.3
pytest-timeout==1.4.1
pytest==7.2.0; python_version >= '3.7'
pytest==7.0.1; python_version < '3.7' # v7.0.1 is the last version to support Python 3.6
flake8==5.0.4
pytest-timeout==2.1.0

View File

@@ -22,7 +22,7 @@ import inspect
import datetime
import platform
from .qt import QtCore
from .qt import QtCore, QtWidgets
from .topology import Topology
from .version import __version__
from .console_cmd import ConsoleCmd
@@ -109,6 +109,29 @@ class ConsoleView(PyCutExt, ConsoleCmd):
self.stdout = sys.stdout
self._topology = Topology.instance()
def contextMenuEvent(self, event):
"""
Handles all context menu events.
:param event: QContextMenuEvent instance
"""
menu = self.createStandardContextMenu()
delete_all_action = QtWidgets.QAction("Delete All", menu)
delete_all_action.triggered.connect(self._deleteAllActionSlot)
menu.addAction(delete_all_action)
menu.exec_(event.globalPos());
def _deleteAllActionSlot(self):
"""
Delete all action slot
"""
self.clear()
self.write(self.prompt)
self.lines = []
self._clearLine()
def _writeMessageSlot(self, message, level):
"""
Write a message in the console.

View File

@@ -130,7 +130,8 @@ class Controller(QtCore.QObject):
self._connected = False
self._connecting = True
self.get('/version', self._versionGetSlot)
status, json_data = self.httpClient().getSynchronous('GET', '/version', timeout=60)
self._versionGetSlot(json_data, status is None or status >= 300)
def _httpClientDisconnectedSlot(self):
if self._connected:
@@ -148,11 +149,14 @@ class Controller(QtCore.QObject):
if self._first_error:
self._connecting = False
self.connection_failed_signal.emit()
if "message" in result and self._display_error:
if self._display_error:
self._error_dialog = QtWidgets.QMessageBox(self.parent())
self._error_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
self._error_dialog.setWindowTitle("Connection to server")
self._error_dialog.setText("Error when connecting to the GNS3 server:\n{}".format(result["message"]))
if result and "message" in result:
self._error_dialog.setText("Error when connecting to the GNS3 server:\n{}".format(result["message"]))
else:
self._error_dialog.setText("Cannot connect to the GNS3 server")
self._error_dialog.setIcon(QtWidgets.QMessageBox.Critical)
self._error_dialog.show()
# Try to connect again in 5 seconds
@@ -164,6 +168,7 @@ class Controller(QtCore.QObject):
self._error_dialog.reject()
self._error_dialog = None
self._version = result.get("version")
self._http_client.connection_connected_signal.emit()
def _httpClientConnectedSlot(self):
@@ -423,6 +428,7 @@ class Controller(QtCore.QObject):
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)
self._notification_stream.sslErrors.connect(self._sslErrorsSlot)
def stopListenNotifications(self):
if self._notification_stream:
@@ -447,6 +453,11 @@ class Controller(QtCore.QObject):
self._notification_stream = None
self._startListenNotifications()
@qslot
def _sslErrorsSlot(self, ssl_errors):
self._http_client.handleSslError(self._notification_stream, ssl_errors)
@qslot
def _websocket_event_received(self, event):
try:

View File

@@ -51,7 +51,7 @@ class CrashReport:
Report crash to a third party service
"""
DSN = "https://32b3e599a2ea4172817ee1800d60bb42:a4e8b89f01124ce0b0508d59fdec690a@o19455.ingest.sentry.io/38506"
DSN = "https://433337b5780f4fe8abec8ee5e85c5897@o19455.ingest.sentry.io/38506"
_instance = None
def __init__(self):
@@ -86,7 +86,7 @@ class CrashReport:
"os:release": platform.release(),
"os:win_32": " ".join(platform.win32_ver()),
"os:mac": "{} {}".format(platform.mac_ver()[0], platform.mac_ver()[2]),
"os:linux": " ".join(distro.linux_distribution()),
"os:linux": distro.name(pretty=True),
}

View File

@@ -554,7 +554,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if self.uiQemuListComboBox.count() == 1:
self.next()
else:
i = self.uiQemuListComboBox.findText(self._appliance["qemu"]["arch"], QtCore.Qt.MatchContains)
i = self.uiQemuListComboBox.findData(self._appliance["qemu"]["arch"], flags=QtCore.Qt.MatchEndsWith)
if i != -1:
self.uiQemuListComboBox.setCurrentIndex(i)

View File

@@ -169,6 +169,9 @@ class CustomAdaptersConfigurationDialog(QtWidgets.QDialog, Ui_CustomAdaptersConf
adapter_number = item.data(0, QtCore.Qt.UserRole)
custom_adapter_settings["adapter_number"] = adapter_number
original_port_name = item.data(1, QtCore.Qt.UserRole)
if not port_name:
QtWidgets.QMessageBox.critical(self, "Port name", "Port name cannot be empty for adapter {}".format(adapter_number))
return False
if original_port_name != port_name:
custom_adapter_settings["port_name"] = port_name
if self._default_adapter_type and self._adapter_types:
@@ -180,13 +183,14 @@ class CustomAdaptersConfigurationDialog(QtWidgets.QDialog, Ui_CustomAdaptersConf
if mac_address and mac_address != ":::::":
if not re.search(r"""^([0-9a-fA-F]{2}[:]){5}[0-9a-fA-F]{2}$""", mac_address):
QtWidgets.QMessageBox.critical(self, "MAC address", "Invalid MAC address (format required: hh:hh:hh:hh:hh:hh)")
return
return False
default_mac_address = self._IntegerToMac(self._MacToInteger(self._base_mac_address) + adapter_number)
if mac_address != default_mac_address:
custom_adapter_settings["mac_address"] = mac_address
if len(custom_adapter_settings) > 1:
# only save if there is more than the adapter_number key
self._custom_adapters.append(custom_adapter_settings.copy())
return True
def done(self, result):
"""
@@ -196,5 +200,6 @@ class CustomAdaptersConfigurationDialog(QtWidgets.QDialog, Ui_CustomAdaptersConf
"""
if result:
self._updateCustomAdapters()
if not self._updateCustomAdapters():
return
super().done(result)

View File

@@ -41,6 +41,9 @@ class EditComputeDialog(QtWidgets.QDialog, Ui_EditComputeDialog):
self.uiServerHostLineEdit.setText(self._compute.host())
self.uiServerPortSpinBox.setValue(self._compute.port())
index = self.uiServerProtocolComboBox.findText(self._compute.protocol().upper())
self.uiServerProtocolComboBox.setCurrentIndex(index)
if self._compute.user():
self.uiEnableAuthenticationCheckBox.setChecked(True)
self.uiServerUserLineEdit.setText(self._compute.user())
@@ -78,7 +81,7 @@ class EditComputeDialog(QtWidgets.QDialog, Ui_EditComputeDialog):
host = self.uiServerHostLineEdit.text().strip()
name = self.uiServerNameLineEdit.text().strip()
protocol = "http"
protocol = self.uiServerProtocolComboBox.currentText().lower()
port = self.uiServerPortSpinBox.value()
user = self.uiServerUserLineEdit.text().strip()
password = self.uiServerPasswordLineEdit.text().strip()

View File

@@ -46,19 +46,11 @@ class EditProjectDialog(QtWidgets.QDialog, Ui_EditProjectDialog):
self.uiNewVarButton.clicked.connect(self.onAddNewVariable)
self.uiGlobalVariablesGrid.addWidget(self.uiNewVarButton, 0, 3, QtCore.Qt.AlignRight)
self._variables = self.setUpVariables()
self._variables = self._project.variables()
if not self._variables:
self._variables = [{"name": "", "value": ""}]
self.updateGlobalVariables()
def setUpVariables(self):
new_variable = {"name": "", "value": ""}
variables = self._project.variables()
if variables is not None:
variables.append(new_variable)
else:
variables = [new_variable]
return variables
def updateGlobalVariables(self):
while True:
item = self.uiGlobalVariablesGrid.takeAt(1)
@@ -98,7 +90,7 @@ class EditProjectDialog(QtWidgets.QDialog, Ui_EditProjectDialog):
variable["value"] = text
def _cleanVariables(self):
return [v for v in self._variables if v.get("name", "").strip() != ""]
return [v for v in self._variables if v.get("name").strip() != ""]
def done(self, result):
"""

View File

@@ -131,6 +131,7 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
QtWidgets.QLineEdit: "textChanged",
QtWidgets.QPlainTextEdit: "textChanged",
# QtWidgets.QTreeWidget: "itemChanged",
QtWidgets.QTreeWidget: "itemDoubleClicked",
QtWidgets.QComboBox: "currentIndexChanged",
QtWidgets.QSpinBox: "valueChanged",
QtWidgets.QAbstractButton: "pressed"

View File

@@ -53,7 +53,7 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
if show_open_options:
self.uiOpenProjectPushButton.clicked.connect(self._openProjectActionSlot)
self.uiRecentProjectsPushButton.clicked.connect(self._showRecentProjectsSlot)
self._addRecentFilesMenu()
else:
self.uiOpenProjectGroupBox.hide()
self.uiProjectTabWidget.removeTab(1)
@@ -231,12 +231,12 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
self._main_window.openProjectActionSlot()
self.reject()
def _showRecentProjectsSlot(self):
def _addRecentFilesMenu(self):
"""
lot to show all the recent projects in a menu.
Add recent projects in a menu.
"""
menu = QtWidgets.QMenu()
menu = QtWidgets.QMenu(parent=self)
if Controller.instance().isRemote():
for action in self._main_window.recent_project_actions:
menu.addAction(action)
@@ -244,7 +244,7 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
for action in self._main_window.recent_file_actions:
menu.addAction(action)
menu.triggered.connect(self._menuTriggeredSlot)
menu.exec_(QtGui.QCursor.pos())
self.uiRecentProjectsPushButton.setMenu(menu)
def _overwriteProjectCallback(self, result, error=False, **kwargs):
if error:

View File

@@ -127,7 +127,7 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
from gns3.modules import VMware
settings = VMware.instance().settings()
if not os.path.exists(settings["vmrun_path"]):
QtWidgets.QMessageBox.critical(self, "VMware", "VMware vmrun tool could not be found, VMware or the VIX API (required for VMware player) is probably not installed. You can download it from https://www.vmware.com/support/developer/vix-api/. After installation you need to restart GNS3.")
QtWidgets.QMessageBox.critical(self, "VMware", "VMware vmrun tool could not be found, VMware or the VIX API (required for VMware player) is probably not installed. You can download it from https://customerconnect.vmware.com/downloads/details?downloadGroup=PLAYER-1400-VIX1170&productId=687. After installation you need to restart GNS3.")
return
self._refreshVMListSlot()

View File

@@ -70,7 +70,7 @@ class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog):
self._border_color.green(),
self._border_color.blue(),
self._border_color.alpha()))
self.uiRotationSpinBox.setValue(first_item.rotation())
self.uiRotationSpinBox.setValue(int(first_item.rotation()))
self.uiBorderWidthSpinBox.setValue(pen.width())
index = self.uiBorderStyleComboBox.findData(pen.style())
if index != -1:

View File

@@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2019 Pekka Helenius
# Copyright (C) 2014 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/>.
"""
Style editor to edit Link items.
"""
from ..qt import QtCore, QtWidgets, QtGui
from ..ui.style_editor_dialog_ui import Ui_StyleEditorDialog
class StyleEditorDialogLink(QtWidgets.QDialog, Ui_StyleEditorDialog):
"""
Style editor dialog.
:param parent: parent widget
:param link: selected link
"""
def __init__(self, link, parent):
super().__init__(parent)
self.setupUi(self)
self._link = link
self._link_style = {}
self.uiBorderColorPushButton.clicked.connect(self._setBorderColorSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self._applyPreferencesSlot)
self.uiBorderStyleComboBox.addItem("Solid", QtCore.Qt.SolidLine)
self.uiBorderStyleComboBox.addItem("Dash", QtCore.Qt.DashLine)
self.uiBorderStyleComboBox.addItem("Dot", QtCore.Qt.DotLine)
self.uiBorderStyleComboBox.addItem("Dash Dot", QtCore.Qt.DashDotLine)
self.uiBorderStyleComboBox.addItem("Dash Dot Dot", QtCore.Qt.DashDotDotLine)
self.uiBorderStyleComboBox.addItem("Invisible", QtCore.Qt.NoPen)
self.uiColorLabel.hide()
self.uiColorPushButton.hide()
self._color = None
self.uiRotationLabel.hide()
self.uiRotationSpinBox.hide()
pen = link.pen()
self._border_color = pen.color()
self.uiBorderColorPushButton.setStyleSheet("background-color: rgba({}, {}, {}, {});".format(self._border_color.red(),
self._border_color.green(),
self._border_color.blue(),
self._border_color.alpha()))
self.uiBorderWidthSpinBox.setValue(pen.width())
index = self.uiBorderStyleComboBox.findData(pen.style())
if index != -1:
self.uiBorderStyleComboBox.setCurrentIndex(index)
self.adjustSize()
def _setBorderColorSlot(self):
"""
Slot to select the border color.
"""
color = QtWidgets.QColorDialog.getColor(self._border_color, self, "Select Color", QtWidgets.QColorDialog.ShowAlphaChannel)
if color.isValid():
self._border_color = color
self.uiBorderColorPushButton.setStyleSheet("background-color: rgba({}, {}, {}, {});".format(self._border_color.red(),
self._border_color.green(),
self._border_color.blue(),
self._border_color.alpha()))
def _applyPreferencesSlot(self):
"""
Applies the new style settings.
"""
border_style = QtCore.Qt.PenStyle(self.uiBorderStyleComboBox.itemData(self.uiBorderStyleComboBox.currentIndex()))
pen = QtGui.QPen(self._border_color, self.uiBorderWidthSpinBox.value(), border_style, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)
self._link.setPen(pen)
new_link_style = {}
new_link_style["color"] = self._border_color.name()
new_link_style["width"] = self.uiBorderWidthSpinBox.value()
new_link_style["type"] = border_style
# Store values
self._link.setLinkStyle(new_link_style)
def done(self, result):
"""
Called when the dialog is closed.
:param result: boolean (accepted or rejected)
"""
if result:
self._applyPreferencesSlot()
super().done(result)

View File

@@ -67,6 +67,7 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
self._symbol_items = []
self._parents = {}
Controller.instance().clearStaticCache() # TODO: use etag to know when to refresh the cache
Controller.instance().get("/symbols", self._listSymbolsCallback)
def _listSymbolsCallback(self, result, error=False, **kwargs):
@@ -108,6 +109,9 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
item.setIcon(0, icon)
Controller.instance().getStatic(symbol.url(), qpartial(render, item))
for parent in self._parents.values():
parent.sortChildren(0, QtCore.Qt.AscendingOrder)
self.adjustSize()
def _searchTextChangedSlot(self, text):
@@ -119,13 +123,13 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
"""
text = self.uiSearchLineEdit.text()
for item in self._symbol_items:
if not item.data(0, QtCore.Qt.UserRole).builtin():
item.setHidden(True)
# if not item.data(0, QtCore.Qt.UserRole).builtin():
# item.setHidden(True)
# else:
if not text.strip() or text.strip().lower() in item.text(0).lower():
item.setHidden(False)
else:
if len(text.strip()) == 0 or text.strip().lower() in item.text(0).lower():
item.setHidden(False)
else:
item.setHidden(True)
item.setHidden(True)
def _customSymbolToggledSlot(self, checked):
"""

View File

@@ -44,7 +44,7 @@ class TextEditorDialog(QtWidgets.QDialog, Ui_TextEditorDialog):
# use the first item in the list as the model
first_item = items[0]
self._setColor(first_item.defaultTextColor())
self.uiRotationSpinBox.setValue(first_item.rotation())
self.uiRotationSpinBox.setValue(int(first_item.rotation()))
self.uiPlainTextEdit.setPlainText(first_item.toPlainText())
self.uiPlainTextEdit.setFont(first_item.font())

View File

@@ -121,8 +121,6 @@ class GraphicsView(QtWidgets.QGraphicsView):
def setZoom(self, zoom):
"""
Sets zoom of the Graphics View
:param zoom:
:return:
"""
if zoom:
factor = zoom / 100.
@@ -193,6 +191,9 @@ class GraphicsView(QtWidgets.QGraphicsView):
# clear all objects on the scene
self.scene().clear()
# reset zoom / scale
self.resetTransform()
def _loadSettings(self):
"""
@@ -326,6 +327,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
# connect the signals that let the graphics view knows about events such as
# a new link creation or deletion.
if self._topology.addLink(link):
source_node.addLink(link)
destination_node.addLink(link)
link.add_link_signal.connect(self.addLinkSlot)
link.delete_link_signal.connect(self.deleteLinkSlot)
@@ -392,7 +395,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
# link addition code
if not self._newlink:
source_item = item
source_port = source_item.connectToPort()
source_port = source_item.connectToPort(event.globalPos())
if not source_port:
return
@@ -409,7 +412,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
source_item = self._newlink.sourceItem()
source_port = self._newlink.sourcePort()
destination_item = item
destination_port = destination_item.connectToPort()
destination_port = destination_item.connectToPort(event.globalPos())
if not destination_port:
return
@@ -471,7 +474,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
#QtWidgets.QApplication.sendEvent(self, context_event)
elif event.button() == QtCore.Qt.LeftButton and self._adding_note:
pos = self.mapToScene(event.pos())
note = self.createDrawingItem("text", pos.x(), pos.y(), 2)
note = self.createDrawingItem("text", int(pos.x()), int(pos.y()), 2)
pos_x = note.pos().x()
pos_y = note.pos().y() - (note.boundingRect().height() / 2)
note.setPos(pos_x, pos_y)
@@ -481,19 +484,19 @@ class GraphicsView(QtWidgets.QGraphicsView):
self._adding_note = False
elif event.button() == QtCore.Qt.LeftButton and self._adding_rectangle:
pos = self.mapToScene(event.pos())
self.createDrawingItem("rect", pos.x(), pos.y(), 1)
self.createDrawingItem("rect", int(pos.x()), int(pos.y()), 1)
self._main_window.uiDrawRectangleAction.setChecked(False)
self.setCursor(QtCore.Qt.ArrowCursor)
self._adding_rectangle = False
elif event.button() == QtCore.Qt.LeftButton and self._adding_ellipse:
pos = self.mapToScene(event.pos())
self.createDrawingItem("ellipse", pos.x(), pos.y(), 1)
self.createDrawingItem("ellipse", int(pos.x()), int(pos.y()), 1)
self._main_window.uiDrawEllipseAction.setChecked(False)
self.setCursor(QtCore.Qt.ArrowCursor)
self._adding_ellipse = False
elif event.button() == QtCore.Qt.LeftButton and self._adding_line:
pos = self.mapToScene(event.pos())
self.createDrawingItem("line", pos.x(), pos.y(), 1)
self.createDrawingItem("line", int(pos.x()), int(pos.y()), 1)
self._main_window.uiDrawLineAction.setChecked(False)
self.setCursor(QtCore.Qt.ArrowCursor)
self._adding_line = False
@@ -577,7 +580,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
delta = event.angleDelta()
if delta is not None and delta.x() == 0:
# CTRL is pressed then use the mouse wheel to zoom in or out.
self.scaleView(pow(2.0, delta.y() / 240.0))
self.scaleView(pow(2.0, (delta.y()/2) / 240.0))
self._topology.project().setZoom(round(self.transform().m11() * 100))
self._topology.project().update()
else:
@@ -657,7 +660,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().consoleType() == "none":
if item.node().status() == Node.stopped or item.node().consoleType() == "none" or item.node().consoleType() is None:
self.configureSlot()
return
else:
@@ -721,8 +724,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
integer, ok = QtWidgets.QInputDialog.getInt(self, "Nodes", "Number of nodes:", 2, 1, 100, 1)
if ok:
for node_number in range(integer):
x = event.pos().x() - (150 / 2) + (node_number % max_nodes_per_line) * offset
y = event.pos().y() - (70 / 2) + (node_number // max_nodes_per_line) * offset
x = event.pos().x() - (150 // 2) + (node_number % max_nodes_per_line) * offset
y = event.pos().y() - (70 // 2) + (node_number // max_nodes_per_line) * offset
if self.createNodeFromTemplateId(template_id, QtCore.QPoint(x, y)) is False:
event.ignore()
break
@@ -861,19 +864,19 @@ class GraphicsView(QtWidgets.QGraphicsView):
bring_to_front_action.triggered.connect(self.bringToFrontSlot)
menu.addAction(bring_to_front_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "configFiles"), items)):
if True in list(map(lambda item: isinstance(item, NodeItem) and bool(item.node().configFiles()), items)):
import_config_action = QtWidgets.QAction("Import config", menu)
import_config_action.setIcon(get_icon("import.svg"))
import_config_action.triggered.connect(self.importConfigActionSlot)
menu.addAction(import_config_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "configFiles"), items)):
if True in list(map(lambda item: isinstance(item, NodeItem) and bool(item.node().configFiles()), items)):
export_config_action = QtWidgets.QAction("Export config", menu)
export_config_action.setIcon(get_icon("export.svg"))
export_config_action.triggered.connect(self.exportConfigActionSlot)
menu.addAction(export_config_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "configFiles"), items)):
if True in list(map(lambda item: isinstance(item, NodeItem) and bool(item.node().configTextFiles()), items)):
export_config_action = QtWidgets.QAction("Edit config", menu)
export_config_action.setIcon(get_icon("edit.svg"))
export_config_action.triggered.connect(self.editConfigActionSlot)
@@ -1226,7 +1229,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
items = []
for item in self.scene().selectedItems():
if isinstance(item, NodeItem) and hasattr(item.node(), "configFiles") and item.node().initialized():
if isinstance(item, NodeItem) and item.node().configFiles() and item.node().initialized():
items.append(item)
if not items:
@@ -1257,17 +1260,17 @@ class GraphicsView(QtWidgets.QGraphicsView):
items = []
for item in self.scene().selectedItems():
if isinstance(item, NodeItem) and hasattr(item.node(), "configFiles") and item.node().initialized():
if isinstance(item, NodeItem) and item.node().configTextFiles() and item.node().initialized():
items.append(item)
if not items:
return
for item in items:
if len(item.node().configFiles()) == 1:
config_file = item.node().configFiles()[0]
if len(item.node().configTextFiles()) == 1:
config_file = item.node().configTextFiles()[0]
else:
config_file, ok = QtWidgets.QInputDialog.getItem(self, "Edit file", "File to edit?", item.node().configFiles(), 0, False)
config_file, ok = QtWidgets.QInputDialog.getItem(self, "Edit file", "File to edit?", item.node().configTextFiles(), 0, False)
if not ok:
continue
dialog = FileEditorDialog(item.node(), config_file, parent=self)
@@ -1282,7 +1285,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
items = []
for item in self.scene().selectedItems():
if isinstance(item, NodeItem) and hasattr(item.node(), "configFiles") and item.node().initialized():
if isinstance(item, NodeItem) and item.node().configFiles() and item.node().initialized():
items.append(item)
if not items:
@@ -1409,7 +1412,15 @@ class GraphicsView(QtWidgets.QGraphicsView):
type = "rect"
else:
type = "image"
self.createDrawingItem(type, item.pos().x() + 20, item.pos().y() + 20, item.zValue(), rotation=item.rotation(), svg=item.toSvg())
self.createDrawingItem(
type,
int(item.pos().x()) + 20,
int(item.pos().y()) + 20,
item.zValue(),
rotation=item.rotation(),
svg=item.toSvg()
)
elif isinstance(item, NodeItem):
item.node().duplicate(item.pos().x() + 20, item.pos().y() + 20, item.zValue())
@@ -1661,11 +1672,11 @@ class GraphicsView(QtWidgets.QGraphicsView):
x = left
while x < rect.right():
painter.drawLine(x, rect.top(), x, rect.bottom())
painter.drawLine(x, int(rect.top()), x, int(rect.bottom()))
x += grid
y = top
while y < rect.bottom():
painter.drawLine(rect.left(), y, rect.right(), y)
painter.drawLine(int(rect.left()), y, int(rect.right()), y)
y += grid
painter.restore()

View File

@@ -18,18 +18,15 @@
from .qt import sip
import json
import copy
import http
import uuid
import pathlib
import base64
import datetime
import ipaddress
import urllib.request
import urllib.parse
from .version import __version__, __version_info__
from .qt import QtCore, QtNetwork, qpartial, sip_is_deleted
from .qt import QtCore, QtNetwork, QtWidgets, qpartial, sip_is_deleted
from .utils import parse_version
import logging
@@ -79,6 +76,18 @@ class HTTPClient(QtCore.QObject):
self._shutdown = False # Shutdown in progress
self._accept_insecure_certificate = settings.get("accept_insecure_certificate", None)
# Add custom CA
# ssl_config = QtNetwork.QSslConfiguration.defaultConfiguration()
# if ssl_config.addCaCertificates("/path/to/rootCA.crt"):
# log.debug("CA certificate added")
# QtNetwork.QSslConfiguration.setDefaultConfiguration(ssl_config)
if self._protocol == "https":
if not QtNetwork.QSslSocket.supportsSsl():
log.error("SSL is not supported")
else:
log.debug(f"SSL is supported, version: {QtNetwork.QSslSocket().sslLibraryBuildVersionString()}")
# In order to detect computer hibernation we detect the date of the last
# query and disconnect if time is too long between two query
self._last_query_timestamp = None
@@ -93,6 +102,12 @@ class HTTPClient(QtCore.QObject):
# List of query waiting for the connection
self._query_waiting_connections = []
# To catch SSL errors
self._network_manager.sslErrors.connect(self._sslErrorsSlot)
# Store SSL error exceptions
self._ssl_exceptions = {}
def setMaxTimeDifferenceBetweenQueries(self, value):
self._max_time_difference_between_queries = value
@@ -389,17 +404,12 @@ class HTTPClient(QtCore.QObject):
self._query_waiting_connections = []
return
if params["version"].split("-")[0] != __version__.split("-")[0]:
if params["version"].split("+")[0] != __version__.split("+")[0]:
msg = "Client version {} is not the same as server (controller) version {}".format(__version__, params["version"])
# Stable release
if __version_info__[3] == 0:
log.error(msg)
for request, callback in self._query_waiting_connections:
if callback is not None:
callback({"message": msg}, error=True, server=server)
return
# We don't allow different major version to interact even with dev build
elif parse_version(__version__)[:2] != parse_version(params["version"])[:2]:
# We don't allow different versions to interact even with dev build
# (excepting post release corrections e.g 2.2.32.1, occassionally done when fixing a packaging problem)
# TODO: we should probably follow this standard starting with v3.0: https://semver.org/
if parse_version(__version__)[:3] != parse_version(params["version"])[:3]:
log.error(msg)
for request, callback in self._query_waiting_connections:
if callback is not None:
@@ -470,7 +480,14 @@ class HTTPClient(QtCore.QObject):
"""
host = self._getHostForQuery()
request = websocket.request()
ws_url = "ws://{host}:{port}{prefix}{path}".format(host=host, port=self._port, path=path, prefix=prefix)
ws_protocol = "ws"
if self._protocol == "https":
ws_protocol = "wss"
ws_url = "{protocol}://{host}:{port}{prefix}{path}".format(protocol=ws_protocol,
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)
@@ -532,14 +549,13 @@ class HTTPClient(QtCore.QObject):
query_string = self._paramsToQueryString(params)
log.debug("{method} {protocol}://{host}:{port}{prefix}{path} {body}{query_string}".format(method=method, protocol=self._protocol, host=host, port=self._port, path=path, body=body, prefix=prefix, query_string=query_string))
url = QtCore.QUrl("{protocol}://{host}:{port}{prefix}{path}{query_string}".format(protocol=self._protocol, host=host, port=self._port, path=path, prefix=prefix, query_string=query_string))
if self._user:
url = QtCore.QUrl("{protocol}://{user}@{host}:{port}{prefix}{path}{query_string}".format(protocol=self._protocol, user=self._user, host=host, port=self._port, path=path, prefix=prefix, query_string=query_string))
else:
url = QtCore.QUrl("{protocol}://{host}:{port}{prefix}{path}{query_string}".format(protocol=self._protocol, host=host, port=self._port, path=path, prefix=prefix, query_string=query_string))
url.setUserName(self._user)
request = self._request(url)
request = self._addAuth(request)
request.setRawHeader(b"User-Agent", "GNS3 QT Client v{version}".format(version=__version__).encode())
# By default QT doesn't support GET with body even if it's in the RFC that's why we need to use sendCustomRequest
@@ -737,10 +753,10 @@ class HTTPClient(QtCore.QObject):
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))
url = QtCore.QUrl("{protocol}://{host}:{port}{prefix}{endpoint}".format(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))
url.setUserName(self._user)
request = self._request(url)
request = self._addAuth(request)
@@ -776,6 +792,55 @@ class HTTPClient(QtCore.QObject):
return status, json_data
return status, None
def _sslErrorsSlot(self, response, ssl_errors):
self.handleSslError(response, ssl_errors)
def handleSslError(self, response, ssl_errors):
if self._accept_insecure_certificate:
response.ignoreSslErrors()
return
url = response.request().url()
host_port_key = f"{url.host()}:{url.port()}"
# get the certificate digest
ssl_config = response.sslConfiguration()
peer_cert = ssl_config.peerCertificate()
digest = peer_cert.digest()
if host_port_key in self._ssl_exceptions:
if self._ssl_exceptions[host_port_key] == digest:
response.ignoreSslErrors()
return
from gns3.main_window import MainWindow
main_window = MainWindow.instance()
msgbox = QtWidgets.QMessageBox(main_window)
msgbox.setWindowTitle("SSL error detected")
msgbox.setText(f"This server could not prove that it is {url.host()}:{url.port()}. Please carefully examine the certificate to make sure the server can be trusted.")
msgbox.setInformativeText(f"{ssl_errors[0].errorString()}")
msgbox.setDetailedText(peer_cert.toText())
msgbox.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
connect_button = QtWidgets.QPushButton(f"&Connect to {url.host()}:{url.port()}", msgbox)
msgbox.addButton(connect_button, QtWidgets.QMessageBox.YesRole)
abort_button = QtWidgets.QPushButton("&Abort", msgbox)
msgbox.addButton(abort_button, QtWidgets.QMessageBox.RejectRole)
msgbox.setDefaultButton(abort_button)
msgbox.setIcon(QtWidgets.QMessageBox.Critical)
msgbox.exec_()
if msgbox.clickedButton() == connect_button:
self._ssl_exceptions[host_port_key] = digest
response.ignoreSslErrors()
else:
for error in ssl_errors:
log.error(f"SSL error detected: {error.errorString()}")
main_window.close()
@classmethod
def fromUrl(cls, url, network_manager=None, base_settings=None):
"""

View File

@@ -246,7 +246,7 @@ class DrawingItem:
center = self.mapFromItem(self, brect.width() / 2.0, brect.height() / 2.0)
painter.setBrush(QtCore.Qt.red)
painter.setPen(QtCore.Qt.red)
painter.drawRect((brect.width() / 2.0) - 10, (brect.height() / 2.0) - 10, 20, 20)
painter.drawRect(QtCore.QRectF((brect.width() / 2.0) - 10, (brect.height() / 2.0) - 10, 20, 20))
painter.setPen(QtCore.Qt.black)
zval = str(int(self.zValue()))
painter.drawText(QtCore.QPointF(center.x() - 4, center.y() + 4), zval)

View File

@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2019 Pekka Helenius
# Copyright (C) 2014 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
@@ -51,10 +52,16 @@ class EthernetLinkItem(LinkItem):
LinkItem.adjust(self)
if self._hovered:
self.setPen(QtGui.QPen(QtCore.Qt.red, self._pen_width + 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
else:
self.setPen(QtGui.QPen(QtCore.Qt.black, self._pen_width, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
try:
if self._hovered:
self.setPen(QtGui.QPen(QtCore.Qt.red, self._link._link_style["width"] + 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
else:
self.setPen(QtGui.QPen(QtGui.QColor(self._link._link_style["color"]), self._link._link_style["width"], self._link._link_style["type"], QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
except:
if self._hovered:
self.setPen(QtGui.QPen(QtCore.Qt.red, self._pen_width + 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
else:
self.setPen(QtGui.QPen(QtGui.QColor("#000000"), self._pen_width, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
# draw a line between nodes
path = QtGui.QPainterPath(self.source)
@@ -152,7 +159,7 @@ class EthernetLinkItem(LinkItem):
else:
source_port_label.hide()
if self._settings["draw_link_status_points"]:
if self._settings["draw_link_status_points"] and self.pen().style() != QtCore.Qt.NoPen:
painter.drawPoint(point1)
if self._link.suspended() or self._destination_port.status() == Port.suspended:
@@ -195,7 +202,7 @@ class EthernetLinkItem(LinkItem):
else:
destination_port_label.hide()
if self._settings["draw_link_status_points"]:
if self._settings["draw_link_status_points"] and self.pen().style() != QtCore.Qt.NoPen:
painter.drawPoint(point2)
self._drawSymbol()

View File

@@ -165,7 +165,7 @@ class LabelItem(QtWidgets.QGraphicsTextItem):
center = self.mapFromItem(self, brect.width() / 2.0, brect.height() / 2.0)
painter.setBrush(QtCore.Qt.red)
painter.setPen(QtCore.Qt.red)
painter.drawRect((brect.width() / 2.0) - 10, (brect.height() / 2.0) - 10, 20, 20)
painter.drawRect(QtCore.QRectF((brect.width() / 2.0) - 10, (brect.height() / 2.0) - 10, 20, 20))
painter.setPen(QtCore.Qt.black)
zval = str(int(self.zValue()))
painter.drawText(QtCore.QPointF(center.x(), center.y()), zval)

View File

@@ -25,6 +25,7 @@ from ..qt import QtCore, QtGui, QtWidgets, QtSvg, qslot, sip_is_deleted
from ..packet_capture import PacketCapture
from ..dialogs.filter_dialog import FilterDialog
from ..dialogs.style_editor_dialog_link import StyleEditorDialogLink
from ..utils.get_icon import get_icon
@@ -137,6 +138,21 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
def _suspendActionSlot(self, *args):
self._link.toggleSuspend()
@qslot
def _styleActionSlot(self, *args):
style_dialog = StyleEditorDialogLink(self, self._main_window)
style_dialog.show()
style_dialog.exec_()
def setLinkStyle(self, link_style):
self._link._link_style["color"] = link_style["color"]
self._link._link_style["width"] = link_style["width"]
self._link._link_style["type"] = link_style["type"]
# This refers to functions in link.py!
self._link.setLinkStyle(link_style)
self._link.update()
def delete(self):
"""
Delete this link
@@ -266,6 +282,12 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
resume_action.triggered.connect(self._suspendActionSlot)
menu.addAction(resume_action)
# style
style_action = QtWidgets.QAction("Style", menu)
style_action.setIcon(get_icon("node_conception.svg"))
style_action.triggered.connect(self._styleActionSlot)
menu.addAction(style_action)
# delete
delete_action = QtWidgets.QAction("Delete", menu)
delete_action.setIcon(get_icon('delete.svg'))

View File

@@ -388,7 +388,7 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
else:
self._node_label.setPos(label_data["x"], label_data["y"])
def connectToPort(self, unavailable_ports=[]):
def connectToPort(self, pos, unavailable_ports=[]):
"""
Shows a contextual menu for the user to choose port or auto-select one.
@@ -436,7 +436,10 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
menu.addAction(QtGui.QIcon(':/icons/led_green.svg'), port_object.name())
menu.triggered.connect(self.selectedPortSlot)
menu.exec_(QtGui.QCursor.pos())
# add some delay before showing the menu
# https://github.com/GNS3/gns3-gui/issues/3169
QtCore.QThread.msleep(100)
menu.exec_(pos)
return self._selected_port
def selectedPortSlot(self, action):
@@ -504,7 +507,7 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
center = self.mapFromItem(self, brect.width() / 2.0, brect.height() / 2.0)
painter.setBrush(QtCore.Qt.red)
painter.setPen(QtCore.Qt.red)
painter.drawRect((brect.width() / 2.0) - 10, (brect.height() / 2.0) - 10, 20, 20)
painter.drawRect(QtCore.QRectF((brect.width() / 2.0) - 10, (brect.height() / 2.0) - 10, 20, 20))
painter.setPen(QtCore.Qt.black)
if self.show_layer:
text = str(int(self.zValue())) # Z value

View File

@@ -50,10 +50,16 @@ class SerialLinkItem(LinkItem):
LinkItem.adjust(self)
if self._hovered:
self.setPen(QtGui.QPen(QtCore.Qt.red, self._pen_width + 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
else:
self.setPen(QtGui.QPen(QtCore.Qt.darkRed, self._pen_width, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
try:
if self._hovered:
self.setPen(QtGui.QPen(QtCore.Qt.red, self._link._link_style["width"] + 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
else:
self.setPen(QtGui.QPen(QtGui.QColor(self._link._link_style["color"]), self._link._link_style["width"], self._link._link_style["type"], QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
except:
if self._hovered:
self.setPen(QtGui.QPen(QtCore.Qt.red, self._pen_width + 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
else:
self.setPen(QtGui.QPen(QtCore.Qt.darkRed, self._pen_width, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
# get source to destination angle
vector_angle = math.atan2(self.dy, self.dx)
@@ -141,7 +147,7 @@ class SerialLinkItem(LinkItem):
else:
source_port_label.hide()
if self._settings["draw_link_status_points"]:
if self._settings["draw_link_status_points"] and self.pen().style() != QtCore.Qt.NoPen:
painter.drawPoint(self.source_point)
# destination point color
@@ -173,7 +179,7 @@ class SerialLinkItem(LinkItem):
else:
destination_port_label.hide()
if self._settings["draw_link_status_points"]:
if self._settings["draw_link_status_points"] and self.pen().style() != QtCore.Qt.NoPen:
painter.drawPoint(self.destination_point)
self._drawSymbol()

View File

@@ -90,9 +90,7 @@ class Link(QtCore.QObject):
self._creator = False
self._nodes = []
self._source_node.addLink(self)
self._destination_node.addLink(self)
self._link_style = {}
body = self._prepareParams()
if self._link_id:
@@ -134,6 +132,8 @@ class Link(QtCore.QObject):
self._updateLabels()
if "filters" in result:
self._filters = result["filters"]
if "link_style" in result:
self._link_style = result["link_style"]
if "suspend" in result:
self._suspend = result["suspend"]
self.updated_link_signal.emit(self._id)
@@ -216,6 +216,7 @@ class Link(QtCore.QObject):
}
],
"filters": self._filters,
"link_style": self._link_style,
"suspend": self._suspend
}
if self._source_port.label():
@@ -470,3 +471,9 @@ class Link(QtCore.QObject):
:params filters: List of filters
"""
self._filters = filters
def setLinkStyle(self, link_style):
"""
:params _link_style: Set link style attributes
"""
self._link_style = link_style

View File

@@ -220,10 +220,14 @@ def main():
if not options.debug:
try:
# hide the console
# win32console.AllocConsole()
console_window = win32console.GetConsoleWindow()
win32gui.ShowWindow(console_window, win32con.SW_HIDE)
if console_window:
win32gui.ShowWindow(console_window, win32con.SW_HIDE)
else:
log.warning("Could not get the console window")
except win32console.error as e:
print("warning: could not allocate console: {}".format(e))
log.warning("Could not allocate console: {}".format(e))
local_config = LocalConfig.instance()

View File

@@ -86,6 +86,23 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.setupUi(self)
self.setUnifiedTitleAndToolBarOnMac(True)
# These widgets will be disabled when no project is loaded
self.disableWhenNoProjectWidgets = [
self.uiGraphicsView,
self.uiAnnotateMenu,
self.uiAnnotationToolBar,
self.uiControlToolBar,
self.uiControlMenu,
self.uiSaveProjectAsAction,
self.uiExportProjectAction,
self.uiScreenshotAction,
self.uiSnapshotAction,
self.uiEditProjectAction,
self.uiDeleteProjectAction,
self.uiImportExportConfigsAction,
self.uiLockAllAction
]
self._notif_dialog = NotifDialog(self)
# Setup logger
logging.getLogger().addHandler(NotifDialogHandler(self._notif_dialog))
@@ -178,28 +195,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.setWindowTitle("[*] GNS3")
# This widgets will be disable when you have no project loaded
self.disableWhenNoProjectWidgets = [
self.uiGraphicsView,
self.uiAnnotateMenu,
self.uiAnnotationToolBar,
self.uiControlToolBar,
self.uiControlMenu,
self.uiSaveProjectAsAction,
self.uiExportProjectAction,
self.uiScreenshotAction,
self.uiSnapshotAction,
self.uiEditProjectAction,
self.uiDeleteProjectAction,
self.uiImportExportConfigsAction,
self.uiLockAllAction
]
# This widgets are not enabled if it's a remote controller (no access to the local file system)
self.disableWhenRemoteContollerWidgets = [
# self.uiImportExportConfigsAction
]
# load initial stuff once the event loop isn't busy
self.run_later(0, self.startupLoading)
@@ -239,6 +234,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.uiShowGridAction.triggered.connect(self._showGridActionSlot)
self.uiSnapToGridAction.triggered.connect(self._snapToGridActionSlot)
self.uiLockAllAction.triggered.connect(self._lockActionSlot)
self.uiResetDocksAction.triggered.connect(self._resetDocksSlot)
# tool menu connections
self.uiWebUIAction.triggered.connect(self._openWebInterfaceActionSlot)
@@ -250,6 +246,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.uiReloadAllAction.triggered.connect(self._reloadAllActionSlot)
self.uiAuxConsoleAllAction.triggered.connect(self._auxConsoleAllActionSlot)
self.uiConsoleAllAction.triggered.connect(self._consoleAllActionSlot)
self.uiResetConsoleAllAction.triggered.connect(self._consoleResetAllActionSlot)
# device menu is contextual and is build on-the-fly
self.uiDeviceMenu.aboutToShow.connect(self._deviceMenuActionSlot)
@@ -371,6 +368,16 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
item.updateNode()
item.update()
def _resetDocksSlot(self):
"""
Reset the dock widgets.
"""
self.uiTopologySummaryDockWidget.setFloating(False)
self.uiComputeSummaryDockWidget.setFloating(False)
self.uiConsoleDockWidget.setFloating(False)
self.uiNodesDockWidget.setFloating(False)
def analyticsClient(self):
"""
Return the analytics client
@@ -540,8 +547,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""
Refresh widgets that should be visible or not
"""
for widget in self.disableWhenRemoteContollerWidgets:
widget.setVisible(not Controller.instance().isRemote())
# No projects
if Topology.instance().project() is None:
@@ -866,6 +871,15 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
if project is not None:
project.reload_all_nodes()
def _consoleResetAllActionSlot(self):
"""
Slot called when reset all console connections.
"""
project = Topology.instance().project()
if project is not None:
project.reset_console_all_nodes()
def _deviceMenuActionSlot(self):
"""
Slot to contextually show the device menu.

View File

@@ -52,6 +52,7 @@ class CloudPreferencesPage(QtWidgets.QWidget, Ui_CloudPreferencesPageWidget):
self.uiEditCloudNodePushButton.clicked.connect(self._editCloudNodeSlot)
self.uiDeleteCloudNodePushButton.clicked.connect(self._deleteCloudNodeSlot)
self.uiCloudNodesTreeWidget.itemSelectionChanged.connect(self._cloudNodeChangedSlot)
self.uiCloudNodesTreeWidget.itemDoubleClicked.connect(self._editCloudNodeSlot)
def _createSectionItem(self, name):
"""

View File

@@ -53,6 +53,8 @@ class EthernetHubPreferencesPage(QtWidgets.QWidget, Ui_EthernetHubPreferencesPag
self.uiEditEthernetHubPushButton.clicked.connect(self._editEthernetHubSlot)
self.uiDeleteEthernetHubPushButton.clicked.connect(self._deleteEthernetHubSlot)
self.uiEthernetHubsTreeWidget.itemSelectionChanged.connect(self._ethernetHubChangedSlot)
self.uiEthernetHubsTreeWidget.itemDoubleClicked.connect(self._editEthernetHubSlot)
def _createSectionItem(self, name):
"""

View File

@@ -53,6 +53,7 @@ class EthernetSwitchPreferencesPage(QtWidgets.QWidget, Ui_EthernetSwitchPreferen
self.uiEditEthernetSwitchPushButton.clicked.connect(self._editEthernetSwitchSlot)
self.uiDeleteEthernetSwitchPushButton.clicked.connect(self._deleteEthernetSwitchSlot)
self.uiEthernetSwitchesTreeWidget.itemSelectionChanged.connect(self._ethernetSwitchChangedSlot)
self.uiEthernetSwitchesTreeWidget.itemDoubleClicked.connect(self._editEthernetSwitchSlot)
def _createSectionItem(self, name):
"""

View File

@@ -116,6 +116,18 @@ class DockerVM(Node):
from .pages.docker_vm_configuration_page import DockerVMConfigurationPage
return DockerVMConfigurationPage
@staticmethod
def validateHostname(hostname):
"""
Checks if the hostname is valid.
:param hostname: hostname to check
:returns: boolean
"""
return DockerVM.isValidRfc1123Hostname(hostname)
@staticmethod
def defaultSymbol():
"""

View File

@@ -52,6 +52,7 @@ class DockerVMPreferencesPage(QtWidgets.QWidget, Ui_DockerVMPreferencesPageWidge
self.uiEditDockerVMPushButton.clicked.connect(self._dockerImageEditSlot)
self.uiDeleteDockerVMPushButton.clicked.connect(self._dockerImageDeleteSlot)
self.uiDockerVMsTreeWidget.itemSelectionChanged.connect(self._dockerImageChangedSlot)
self.uiDockerVMsTreeWidget.itemDoubleClicked.connect(self._dockerImageEditSlot)
def _createSectionItem(self, name):
"""

View File

@@ -175,11 +175,26 @@
</item>
<item row="8" column="1">
<widget class="QComboBox" name="uiConsoleResolutionComboBox">
<item>
<property name="text">
<string>2560x1440</string>
</property>
</item>
<item>
<property name="text">
<string>1920x1080</string>
</property>
</item>
<item>
<property name="text">
<string>1680x1050</string>
</property>
</item>
<item>
<property name="text">
<string>1440x900</string>
</property>
</item>
<item>
<property name="text">
<string>1366x768</string>

View File

@@ -2,12 +2,15 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/docker/ui/docker_vm_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING! All changes made in this file will be lost!
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_dockerVMConfigPageWidget(object):
def setupUi(self, dockerVMConfigPageWidget):
dockerVMConfigPageWidget.setObjectName("dockerVMConfigPageWidget")
@@ -100,6 +103,9 @@ class Ui_dockerVMConfigPageWidget(object):
self.uiConsoleResolutionComboBox.addItem("")
self.uiConsoleResolutionComboBox.addItem("")
self.uiConsoleResolutionComboBox.addItem("")
self.uiConsoleResolutionComboBox.addItem("")
self.uiConsoleResolutionComboBox.addItem("")
self.uiConsoleResolutionComboBox.addItem("")
self.gridLayout.addWidget(self.uiConsoleResolutionComboBox, 8, 1, 1, 1)
self.label = QtWidgets.QLabel(self.tab)
self.label.setObjectName("label")
@@ -190,13 +196,16 @@ class Ui_dockerVMConfigPageWidget(object):
self.uiConsoleTypeComboBox.setItemText(4, _translate("dockerVMConfigPageWidget", "none"))
self.uiConsoleAutoStartCheckBox.setText(_translate("dockerVMConfigPageWidget", "Auto start console"))
self.uiConsoleResolutionLabel.setText(_translate("dockerVMConfigPageWidget", "VNC console resolution:"))
self.uiConsoleResolutionComboBox.setItemText(0, _translate("dockerVMConfigPageWidget", "1920x1080"))
self.uiConsoleResolutionComboBox.setItemText(1, _translate("dockerVMConfigPageWidget", "1366x768"))
self.uiConsoleResolutionComboBox.setItemText(2, _translate("dockerVMConfigPageWidget", "1280x1024"))
self.uiConsoleResolutionComboBox.setItemText(3, _translate("dockerVMConfigPageWidget", "1280x800"))
self.uiConsoleResolutionComboBox.setItemText(4, _translate("dockerVMConfigPageWidget", "1024x768"))
self.uiConsoleResolutionComboBox.setItemText(5, _translate("dockerVMConfigPageWidget", "800x600"))
self.uiConsoleResolutionComboBox.setItemText(6, _translate("dockerVMConfigPageWidget", "640x480"))
self.uiConsoleResolutionComboBox.setItemText(0, _translate("dockerVMConfigPageWidget", "2560x1440"))
self.uiConsoleResolutionComboBox.setItemText(1, _translate("dockerVMConfigPageWidget", "1920x1080"))
self.uiConsoleResolutionComboBox.setItemText(2, _translate("dockerVMConfigPageWidget", "1680x1050"))
self.uiConsoleResolutionComboBox.setItemText(3, _translate("dockerVMConfigPageWidget", "1440x900"))
self.uiConsoleResolutionComboBox.setItemText(4, _translate("dockerVMConfigPageWidget", "1366x768"))
self.uiConsoleResolutionComboBox.setItemText(5, _translate("dockerVMConfigPageWidget", "1280x1024"))
self.uiConsoleResolutionComboBox.setItemText(6, _translate("dockerVMConfigPageWidget", "1280x800"))
self.uiConsoleResolutionComboBox.setItemText(7, _translate("dockerVMConfigPageWidget", "1024x768"))
self.uiConsoleResolutionComboBox.setItemText(8, _translate("dockerVMConfigPageWidget", "800x600"))
self.uiConsoleResolutionComboBox.setItemText(9, _translate("dockerVMConfigPageWidget", "640x480"))
self.label.setText(_translate("dockerVMConfigPageWidget", "HTTP port in the container:"))
self.label_2.setText(_translate("dockerVMConfigPageWidget", "HTTP path:"))
self.uiEnvironmentLabel.setText(_translate("dockerVMConfigPageWidget", "Environment variables:\n"
@@ -217,4 +226,3 @@ class Ui_dockerVMConfigPageWidget(object):
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"))

View File

@@ -310,8 +310,8 @@ class Router(Node):
# IOS names must start with a letter, end with a letter or digit, and
# have as interior characters only letters, digits, and hyphens.
# They must be 63 characters or fewer.
if re.search(r"""^[\-\w]+$""", hostname) and len(hostname) <= 63:
# They must be 63 characters or fewer (ARPANET rules).
if re.search(r"""^(?!-|[0-9])[a-zA-Z0-9-]{1,63}(?<!-)$""", hostname):
return True
return False

View File

@@ -70,6 +70,7 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
self.uiDeleteIOSRouterPushButton.clicked.connect(self._iosRouterDeleteSlot)
self.uiIOSRoutersTreeWidget.itemSelectionChanged.connect(self._iosRouterChangedSlot)
self.uiDecompressIOSPushButton.clicked.connect(self._decompressIOSSlot)
self.uiIOSRoutersTreeWidget.itemDoubleClicked.connect(self._iosRouterEditSlot)
def _iosRouterChangedSlot(self):
"""

View File

@@ -131,8 +131,8 @@ class IOUDevice(Node):
# IOS names must start with a letter, end with a letter or digit, and
# have as interior characters only letters, digits, and hyphens.
# They must be 63 characters or fewer.
if re.search(r"""^[\-\w]+$""", hostname) and len(hostname) <= 63:
# They must be 63 characters or fewer (ARPANET rules).
if re.search(r"""^(?!-|[0-9])[a-zA-Z0-9-]{1,63}(?<!-)$""", hostname):
return True
return False

View File

@@ -59,6 +59,7 @@ class IOUDevicePreferencesPage(QtWidgets.QWidget, Ui_IOUDevicePreferencesPageWid
self.uiEditIOUDevicePushButton.clicked.connect(self._iouDeviceEditSlot)
self.uiDeleteIOUDevicePushButton.clicked.connect(self._iouDeviceDeleteSlot)
self.uiIOUDevicesTreeWidget.itemSelectionChanged.connect(self._iouDeviceChangedSlot)
self.uiIOUDevicesTreeWidget.itemDoubleClicked.connect(self._iouDeviceEditSlot)
def _createSectionItem(self, name):
"""

View File

@@ -157,6 +157,7 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
if self.uiHdaDiskImageLineEdit.text().strip():
settings["hda_disk_image"] = self.uiHdaDiskImageLineEdit.text().strip()
settings["hda_disk_interface"] = "ide"
if self.uiLegacyASACheckBox.isChecked():
# special settings for legacy ASA VM
@@ -164,7 +165,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"
settings["options"] = "-machine accel=tcg -icount auto"
if not sys.platform.startswith("darwin"):
settings["cpu_throttling"] = 80 # limit to 80% CPU usage
settings["process_priority"] = "low"

View File

@@ -90,6 +90,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self.uiActivateCPUThrottlingCheckBox.stateChanged.connect(self._cpuThrottlingChangedSlot)
self.uiLegacyNetworkingCheckBox.stateChanged.connect(self._legacyNetworkingChangedSlot)
self.uiCustomAdaptersConfigurationPushButton.clicked.connect(self._customAdaptersConfigurationSlot)
self.uiCreateConfigDiskCheckBox.stateChanged.connect(self._createConfigDiskChangedSlot)
# add the categories
for name, category in Node.defaultCategories().items():
@@ -101,6 +102,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
# Supported NIC models: e1000, e1000-82544gc, e1000-82545em, e1000e, i82550, i82551, i82557a, i82557b, i82557c, i82558a
# i82558b, i82559a, i82559b, i82559c, i82559er, i82562, i82801, ne2k_pci, pcnet, rocker, rtl8139, virtio-net-pci, vmxnet3
# This list can be retrieved using "qemu-system-x86_64 -nic model=?" or "qemu-system-x86_64 -device help"
self._legacy_devices = ("e1000", "i82551", "i82557b", "i82559er", "ne2k_pci", "pcnet", "rtl8139", "virtio")
self._qemu_network_devices = OrderedDict([("e1000", "Intel Gigabit Ethernet"),
("e1000-82544gc", "Intel 82544GC Gigabit Ethernet"),
@@ -153,6 +155,9 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
for device_name, device_description in self._qemu_network_devices.items():
if legacy_networking and device_name not in self._legacy_devices:
continue
# special case for virtio legacy networking
if not legacy_networking and device_name == "virtio":
continue
self.uiAdapterTypesComboBox.addItem("{} ({})".format(device_description, device_name), device_name)
@staticmethod
@@ -366,6 +371,19 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
else:
self._refreshQemuNetworkDevices()
def _createConfigDiskChangedSlot(self, state):
"""
Slot to allow or not HDD disk to be configured based on the state of the config disk option.
"""
_translate = QtCore.QCoreApplication.translate
if state:
self.uiHddDiskImageLabel.setText(_translate("QemuVMConfigPageWidget", "Startup-cfg:"))
else:
self.uiHddDiskImageLabel.setText(_translate("QemuVMConfigPageWidget", "Disk image:"))
self.uiHddDiskImageCreateToolButton.setEnabled(not state)
self.uiHddDiskImageResizeToolButton.setEnabled(not state)
def _customAdaptersConfigurationSlot(self):
"""
Slot to open the custom adapters configuration dialog
@@ -407,7 +425,9 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
if nic in self._legacy_devices:
network_devices[nic] = desc
else:
network_devices = self._qemu_network_devices
network_devices = self._qemu_network_devices.copy()
# special case for virtio legacy networking
network_devices.pop("virtio")
dialog = CustomAdaptersConfigurationDialog(ports, self._custom_adapters, default_adapter, network_devices, base_mac_address, parent=self)
dialog.show()
@@ -453,6 +473,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self.uiHdbDiskInterfaceComboBox.setCurrentIndex(self.uiHdbDiskInterfaceComboBox.findText(settings["hdb_disk_interface"]))
self.uiHdcDiskInterfaceComboBox.setCurrentIndex(self.uiHdcDiskInterfaceComboBox.findText(settings["hdc_disk_interface"]))
self.uiHddDiskInterfaceComboBox.setCurrentIndex(self.uiHddDiskInterfaceComboBox.findText(settings["hdd_disk_interface"]))
self.uiCreateConfigDiskCheckBox.setChecked(settings["create_config_disk"])
self.uiCdromImageLineEdit.setText(settings["cdrom_image"])
self.uiBiosImageLineEdit.setText(settings["bios_image"])
self.uiInitrdLineEdit.setText(settings["initrd"])
@@ -557,6 +578,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self.uiProcessPriorityComboBox.setCurrentIndex(index)
self.uiQemuOptionsLineEdit.setText(settings["options"])
self.uiUsageTextEdit.setPlainText(settings["usage"])
self.uiTPMCheckBox.setChecked(settings["tpm"])
def saveSettings(self, settings, node=None, group=False):
"""
@@ -588,6 +610,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
settings["hdb_disk_interface"] = self.uiHdbDiskInterfaceComboBox.currentText()
settings["hdc_disk_interface"] = self.uiHdcDiskInterfaceComboBox.currentText()
settings["hdd_disk_interface"] = self.uiHddDiskInterfaceComboBox.currentText()
settings["create_config_disk"] = self.uiCreateConfigDiskCheckBox.isChecked()
settings["cdrom_image"] = self.uiCdromImageLineEdit.text().strip()
settings["bios_image"] = self.uiBiosImageLineEdit.text().strip()
settings["initrd"] = self.uiInitrdLineEdit.text().strip()
@@ -670,4 +693,5 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
settings["process_priority"] = self.uiProcessPriorityComboBox.currentText().lower()
settings["options"] = self.uiQemuOptionsLineEdit.text()
settings["usage"] = self.uiUsageTextEdit.toPlainText()
settings["tpm"] = self.uiTPMCheckBox.isChecked()
return settings

View File

@@ -54,6 +54,7 @@ class QemuVMPreferencesPage(QtWidgets.QWidget, Ui_QemuVMPreferencesPageWidget):
self.uiEditQemuVMPushButton.clicked.connect(self._qemuVMEditSlot)
self.uiDeleteQemuVMPushButton.clicked.connect(self._qemuVMDeleteSlot)
self.uiQemuVMsTreeWidget.itemSelectionChanged.connect(self._qemuVMChangedSlot)
self.uiQemuVMsTreeWidget.itemDoubleClicked.connect(self._qemuVMEditSlot)
def _createSectionItem(self, name):
"""

View File

@@ -19,6 +19,8 @@
QEMU VM implementation.
"""
import re
from gns3.node import Node
from .settings import QEMU_VM_SETTINGS
@@ -72,6 +74,8 @@ class QemuVM(Node):
"mac_address": QEMU_VM_SETTINGS["mac_address"],
"legacy_networking": QEMU_VM_SETTINGS["legacy_networking"],
"replicate_network_connection_state": QEMU_VM_SETTINGS["replicate_network_connection_state"],
"tpm": QEMU_VM_SETTINGS["tpm"],
"create_config_disk": QEMU_VM_SETTINGS["create_config_disk"],
"platform": QEMU_VM_SETTINGS["platform"],
"on_close": QEMU_VM_SETTINGS["on_close"],
"cpu_throttling": QEMU_VM_SETTINGS["cpu_throttling"],
@@ -134,6 +138,24 @@ class QemuVM(Node):
usage = "\n" + self._settings.get("usage")
return info + port_info + usage
def configFiles(self):
"""
Name of the configuration files
"""
if self._settings.get("create_config_disk"):
return ["config.zip"]
return None
def configTextFiles(self):
"""
Name of the configuration files, which are plain text files
:returns: List of configuration files, False if no files
"""
return None
def configPage(self):
"""
Returns the configuration page widget to be used by the node properties dialog.
@@ -144,6 +166,18 @@ class QemuVM(Node):
from .pages.qemu_vm_configuration_page import QemuVMConfigurationPage
return QemuVMConfigurationPage
@staticmethod
def validateHostname(hostname):
"""
Checks if the hostname is valid.
:param hostname: hostname to check
:returns: boolean
"""
return QemuVM.isValidRfc1123Hostname(hostname)
@staticmethod
def defaultSymbol():
"""

View File

@@ -41,10 +41,10 @@ QEMU_VM_SETTINGS = {
"hdb_disk_image": "",
"hdc_disk_image": "",
"hdd_disk_image": "",
"hda_disk_interface": "ide",
"hdb_disk_interface": "ide",
"hdc_disk_interface": "ide",
"hdd_disk_interface": "ide",
"hda_disk_interface": "none",
"hdb_disk_interface": "none",
"hdc_disk_interface": "none",
"hdd_disk_interface": "none",
"cdrom_image": "",
"bios_image": "",
"boot_priority": "c",
@@ -57,6 +57,8 @@ QEMU_VM_SETTINGS = {
"mac_address": "",
"legacy_networking": False,
"replicate_network_connection_state": True,
"tpm": False,
"create_config_disk": False,
"on_close": "power_off",
"platform": "",
"cpu_throttling": 0,

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>941</width>
<height>877</height>
<width>478</width>
<height>550</height>
</rect>
</property>
<property name="windowTitle">
@@ -399,14 +399,21 @@
<string>HDD (Secondary Slave)</string>
</property>
<layout class="QGridLayout" name="gridLayout_9">
<item row="0" column="0">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="uiCreateConfigDiskCheckBox">
<property name="text">
<string>Automatically create a config disk on HDD</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiHddDiskImageLabel">
<property name="text">
<string>Disk image:</string>
</property>
</widget>
</item>
<item row="0" column="1" rowspan="2">
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QLineEdit" name="uiHddDiskImageLineEdit"/>
@@ -437,7 +444,7 @@
</item>
</layout>
</item>
<item row="1" column="0" rowspan="2">
<item row="2" column="0">
<widget class="QLabel" name="uiHddDiskInterfaceLabel">
<property name="text">
<string>Disk interface:</string>
@@ -470,7 +477,16 @@
<string>CD/DVD</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<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>
@@ -874,7 +890,7 @@
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="uiBaseVMCheckBox">
<property name="enabled">
<bool>true</bool>
@@ -884,10 +900,18 @@
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="uiTPMCheckBox">
<property name="text">
<string>Enable Trusted Platform Module (TPM)</string>
</property>
</widget>
</item>
</layout>
<zorder>uiQemuOptionsLineEdit</zorder>
<zorder>uiQemuOptionsLabel</zorder>
<zorder>uiBaseVMCheckBox</zorder>
<zorder>uiTPMCheckBox</zorder>
</widget>
</item>
<item>

View File

@@ -2,9 +2,10 @@
# 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.13.2
# Created by: PyQt5 UI code generator 5.15.6
#
# WARNING! All changes made in this file will be lost!
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -13,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_QemuVMConfigPageWidget(object):
def setupUi(self, QemuVMConfigPageWidget):
QemuVMConfigPageWidget.setObjectName("QemuVMConfigPageWidget")
QemuVMConfigPageWidget.resize(941, 877)
QemuVMConfigPageWidget.resize(478, 550)
self.verticalLayout = QtWidgets.QVBoxLayout(QemuVMConfigPageWidget)
self.verticalLayout.setObjectName("verticalLayout")
self.uiQemutabWidget = QtWidgets.QTabWidget(QemuVMConfigPageWidget)
@@ -211,9 +212,12 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiHddGroupBox.setObjectName("uiHddGroupBox")
self.gridLayout_9 = QtWidgets.QGridLayout(self.uiHddGroupBox)
self.gridLayout_9.setObjectName("gridLayout_9")
self.uiCreateConfigDiskCheckBox = QtWidgets.QCheckBox(self.uiHddGroupBox)
self.uiCreateConfigDiskCheckBox.setObjectName("uiCreateConfigDiskCheckBox")
self.gridLayout_9.addWidget(self.uiCreateConfigDiskCheckBox, 0, 0, 1, 2)
self.uiHddDiskImageLabel = QtWidgets.QLabel(self.uiHddGroupBox)
self.uiHddDiskImageLabel.setObjectName("uiHddDiskImageLabel")
self.gridLayout_9.addWidget(self.uiHddDiskImageLabel, 0, 0, 1, 1)
self.gridLayout_9.addWidget(self.uiHddDiskImageLabel, 1, 0, 1, 1)
self.horizontalLayout_10 = QtWidgets.QHBoxLayout()
self.horizontalLayout_10.setObjectName("horizontalLayout_10")
self.uiHddDiskImageLineEdit = QtWidgets.QLineEdit(self.uiHddGroupBox)
@@ -229,10 +233,10 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiHddDiskImageResizeToolButton = QtWidgets.QToolButton(self.uiHddGroupBox)
self.uiHddDiskImageResizeToolButton.setObjectName("uiHddDiskImageResizeToolButton")
self.horizontalLayout_10.addWidget(self.uiHddDiskImageResizeToolButton)
self.gridLayout_9.addLayout(self.horizontalLayout_10, 0, 1, 2, 1)
self.gridLayout_9.addLayout(self.horizontalLayout_10, 1, 1, 1, 1)
self.uiHddDiskInterfaceLabel = QtWidgets.QLabel(self.uiHddGroupBox)
self.uiHddDiskInterfaceLabel.setObjectName("uiHddDiskInterfaceLabel")
self.gridLayout_9.addWidget(self.uiHddDiskInterfaceLabel, 1, 0, 2, 1)
self.gridLayout_9.addWidget(self.uiHddDiskInterfaceLabel, 2, 0, 1, 1)
self.uiHddDiskInterfaceComboBox = QtWidgets.QComboBox(self.uiHddGroupBox)
self.uiHddDiskInterfaceComboBox.setObjectName("uiHddDiskInterfaceComboBox")
self.gridLayout_9.addWidget(self.uiHddDiskInterfaceComboBox, 2, 1, 1, 1)
@@ -433,10 +437,14 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiBaseVMCheckBox = QtWidgets.QCheckBox(self.groupBox)
self.uiBaseVMCheckBox.setEnabled(True)
self.uiBaseVMCheckBox.setObjectName("uiBaseVMCheckBox")
self.gridLayout_3.addWidget(self.uiBaseVMCheckBox, 1, 0, 1, 2)
self.gridLayout_3.addWidget(self.uiBaseVMCheckBox, 2, 0, 1, 2)
self.uiTPMCheckBox = QtWidgets.QCheckBox(self.groupBox)
self.uiTPMCheckBox.setObjectName("uiTPMCheckBox")
self.gridLayout_3.addWidget(self.uiTPMCheckBox, 1, 0, 1, 2)
self.uiQemuOptionsLineEdit.raise_()
self.uiQemuOptionsLabel.raise_()
self.uiBaseVMCheckBox.raise_()
self.uiTPMCheckBox.raise_()
self.verticalLayout_2.addWidget(self.groupBox)
spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_2.addItem(spacerItem4)
@@ -497,6 +505,7 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiHdcDiskImageResizeToolButton.setText(_translate("QemuVMConfigPageWidget", "Resize..."))
self.uiHdcDiskInterfaceLabel.setText(_translate("QemuVMConfigPageWidget", "Disk interface:"))
self.uiHddGroupBox.setTitle(_translate("QemuVMConfigPageWidget", "HDD (Secondary Slave)"))
self.uiCreateConfigDiskCheckBox.setText(_translate("QemuVMConfigPageWidget", "Automatically create a config disk on HDD"))
self.uiHddDiskImageLabel.setText(_translate("QemuVMConfigPageWidget", "Disk image:"))
self.uiHddDiskImageToolButton.setText(_translate("QemuVMConfigPageWidget", "&Browse..."))
self.uiHddDiskImageCreateToolButton.setText(_translate("QemuVMConfigPageWidget", "Create..."))
@@ -552,5 +561,6 @@ class Ui_QemuVMConfigPageWidget(object):
"</ul>\n"
"</body></html>"))
self.uiBaseVMCheckBox.setText(_translate("QemuVMConfigPageWidget", "Use as a linked base VM"))
self.uiTPMCheckBox.setText(_translate("QemuVMConfigPageWidget", "Enable Trusted Platform Module (TPM)"))
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiAdvancedSettingsTab), _translate("QemuVMConfigPageWidget", "Advanced"))
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiUsageTab), _translate("QemuVMConfigPageWidget", "Usage"))

View File

@@ -54,6 +54,7 @@ class VirtualBoxVMPreferencesPage(QtWidgets.QWidget, Ui_VirtualBoxVMPreferencesP
self.uiEditVirtualBoxVMPushButton.clicked.connect(self._vboxVMEditSlot)
self.uiDeleteVirtualBoxVMPushButton.clicked.connect(self._vboxVMDeleteSlot)
self.uiVirtualBoxVMsTreeWidget.itemSelectionChanged.connect(self._vboxVMChangedSlot)
self.uiVirtualBoxVMsTreeWidget.itemDoubleClicked.connect(self._vboxVMEditSlot)
def _createSectionItem(self, name):
"""

View File

@@ -53,6 +53,7 @@ class VMwareVMPreferencesPage(QtWidgets.QWidget, Ui_VMwareVMPreferencesPageWidge
self.uiEditVMwareVMPushButton.clicked.connect(self._vmwareVMEditSlot)
self.uiDeleteVMwareVMPushButton.clicked.connect(self._vmwareVMDeleteSlot)
self.uiVMwareVMsTreeWidget.itemSelectionChanged.connect(self._vmwareVMChangedSlot)
self.uiVMwareVMsTreeWidget.itemDoubleClicked.connect(self._vmwareVMEditSlot)
def _createSectionItem(self, name):
"""

View File

@@ -53,6 +53,7 @@ class VPCSNodePreferencesPage(QtWidgets.QWidget, Ui_VPCSNodePageWidget):
self.uiEditVPCSPushButton.clicked.connect(self._editVPCSSlot)
self.uiDeleteVPCSPushButton.clicked.connect(self._deleteVPCSSlot)
self.uiVPCSTreeWidget.itemSelectionChanged.connect(self._vpcsChangedSlot)
self.uiVPCSTreeWidget.itemDoubleClicked.connect(self._editVPCSSlot)
def _createSectionItem(self, name):
"""

View File

@@ -17,6 +17,7 @@
import os
import pathlib
import re
from gns3.controller import Controller
from gns3.ports.ethernet_port import EthernetPort
@@ -199,6 +200,26 @@ class Node(BaseNode):
return self._always_on
def configFiles(self):
"""
Name of the configuration files
This method should be overridden in derived classes
:returns: List of configuration files, False if no files
"""
return None
def configTextFiles(self):
"""
Name of the configuration files, which are plain text files
:returns: List of configuration files, False if no files
"""
return self.configFiles()
def get(self, path, *args, **kwargs):
"""
GET on current server / project
@@ -538,7 +559,7 @@ class Node(BaseNode):
del result["properties"]
# Update common element of all nodes
for key in ["x", "y", "z", "locked", "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", "first_port_name", "port_name_format", "port_segment_size"]:
if key in result:
self._settings[key] = result[key]
@@ -630,10 +651,11 @@ class Node(BaseNode):
if not console_type:
console_type = self.consoleType()
if console_type == "vnc":
return general_settings["vnc_console_command"]
elif console_type.startswith("spice"):
return general_settings["spice_console_command"]
if console_type:
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):
@@ -774,7 +796,7 @@ class Node(BaseNode):
:param directory: destination directory path
"""
if not hasattr(self, "configFiles"):
if not self.configFiles():
return False
for file in self.configFiles():
self.get("/files/{file}".format(file=file),
@@ -813,7 +835,7 @@ class Node(BaseNode):
:param directory: source directory path
"""
if not hasattr(self, "configFiles"):
if not self.configFiles():
return
try:
@@ -843,6 +865,33 @@ class Node(BaseNode):
if error and "message" in result:
log.error("Error while import config: {}".format(result["message"]))
@staticmethod
def isValidRfc1123Hostname(hostname):
"""
Validate a hostname according to RFC 1123
Each element of the hostname must be from 1 to 63 characters long
and the entire hostname, including the dots, can be at most 253
characters long. Valid characters for hostnames are ASCII
letters from a to z, the digits from 0 to 9, and the hyphen (-).
A hostname may not start with a hyphen.
"""
if hostname[-1] == ".":
hostname = hostname[:-1] # strip exactly one dot from the right, if present
if len(hostname) > 253:
return False
labels = hostname.split(".")
# the TLD must be not all-numeric
if re.match(r"[0-9]+$", labels[-1]):
return False
allowed = re.compile(r"(?!-)[a-zA-Z0-9-]{1,63}(?<!-)$")
return all(allowed.match(label) for label in labels)
@staticmethod
def onCloseOptions():
"""

View File

@@ -151,6 +151,22 @@ class NodesView(QtWidgets.QTreeWidget):
self._showContextualMenu(event.globalPos())
def mouseDoubleClickEvent(self, event):
"""
Handles all mouse double click events.
:param event: QMouseEvent instance
"""
item = self.itemAt(event.pos())
if item:
template = TemplateManager.instance().getTemplate(item.data(0, QtCore.Qt.UserRole))
if template:
configuration_page = TEMPLATE_TYPE_TO_CONFIGURATION_PAGE.get(template.template_type())
if not template.builtin() and configuration_page:
self._configurationSlot(template, configuration_page)
super().mouseDoubleClickEvent(event)
def mouseMoveEvent(self, event):
"""
Handles all mouse move events.
@@ -204,14 +220,14 @@ class NodesView(QtWidgets.QTreeWidget):
menu.exec_(pos)
def _configurationSlot(self, template, configuration_page, source):
def _configurationSlot(self, template, configuration_page, source=None):
dialog = ConfigurationDialog(template.name(), template.settings(), configuration_page(), parent=self)
dialog.show()
if dialog.exec_():
TemplateManager.instance().updateTemplate(template)
def _deleteSlot(self, template, source):
def _deleteSlot(self, template, source=None):
reply = QtWidgets.QMessageBox.question(self, "Template", "Delete {} template?".format(template.name()),
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)

View File

@@ -191,6 +191,7 @@ class ServerPreferencesPage(QtWidgets.QWidget, Ui_ServerPreferencesPageWidget):
self.uiRemoteMainServerHostLineEdit.setText(servers_settings["host"])
self.uiRemoteMainServerPortSpinBox.setValue(servers_settings["port"])
self.uiRemoteMainServerProtocolComboBox.setCurrentText(servers_settings["protocol"].upper())
self.uiRemoteMainServerUserLineEdit.setText(servers_settings["user"])
self.uiRemoteMainServerPasswordLineEdit.setText(servers_settings["password"])
self.uiRemoteMainServerAuthCheckBox.setChecked(servers_settings["auth"])
@@ -285,7 +286,7 @@ class ServerPreferencesPage(QtWidgets.QWidget, Ui_ServerPreferencesPageWidget):
else:
new_local_server_settings["host"] = self.uiRemoteMainServerHostLineEdit.text()
new_local_server_settings["port"] = self.uiRemoteMainServerPortSpinBox.value()
new_local_server_settings["protocol"] = "http"
new_local_server_settings["protocol"] = self.uiRemoteMainServerProtocolComboBox.currentText().lower()
new_local_server_settings["user"] = self.uiRemoteMainServerUserLineEdit.text()
new_local_server_settings["password"] = self.uiRemoteMainServerPasswordLineEdit.text()
new_local_server_settings["auth"] = self.uiRemoteMainServerAuthCheckBox.isChecked()

View File

@@ -365,6 +365,15 @@ class Project(QtCore.QObject):
Controller.instance().post("/projects/{project_id}/nodes/reload".format(project_id=self._id), None, body={}, timeout=None)
def reset_console_all_nodes(self):
"""Reset console for all nodes belonging to this project"""
# Don't do anything if the project doesn't exist on the server
if self._id is None:
return
Controller.instance().post("/projects/{project_id}/nodes/console/reset".format(project_id=self._id), None, body={}, timeout=None)
def get(self, path, callback, **kwargs):
"""
HTTP GET on the remote server
@@ -632,6 +641,7 @@ class Project(QtCore.QObject):
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)
self._notification_stream.sslErrors.connect(self._sslErrorsSlot)
def _endListenNotificationCallback(self, result, error=False, **kwargs):
"""
@@ -648,6 +658,11 @@ class Project(QtCore.QObject):
self._notification_stream = None
self._startListenNotifications()
@qslot
def _sslErrorsSlot(self, ssl_errors):
Controller.instance().httpClient().handleSslError(self._notification_stream, ssl_errors)
@qslot
def _websocket_event_received(self, event):
try:

View File

@@ -59,7 +59,7 @@ class Appliance(collections.abc.Mapping):
"""
if "registry_version" not in self._appliance:
raise ApplianceError("Invalid appliance configuration please report the issue on https://github.com/GNS3/gns3-registry")
if self._appliance["registry_version"] > 6:
if self._appliance["registry_version"] > 7:
raise ApplianceError("Please update GNS3 in order to install this appliance")
with open(get_resource(os.path.join("schemas", "appliance.json"))) as f:

View File

@@ -100,8 +100,8 @@ class ApplianceToTemplate:
new_config.pop("arch", None)
options = appliance_config["qemu"].get("options", "")
if appliance_config["qemu"].get("kvm", "allow") == "disable" and "-no-kvm" not in options:
options += " -no-kvm"
if appliance_config["qemu"].get("kvm", "allow") == "disable" and "-machine accel=tcg" not in options:
options += " -machine accel=tcg"
new_config["options"] = options.strip()
for image in appliance_config["images"]:

View File

@@ -350,6 +350,14 @@
"maximum": 100,
"title": "Throttle the CPU"
},
"tpm": {
"type": "boolean",
"title": "Enable the Trusted Platform Module (TPM)"
},
"on_close": {
"title": "Action to execute on the VM is closed",
"enum": ["power_off", "shutdown_signal", "save_vm_state"]
},
"process_priority": {
"title": "Process priority for QEMU",
"enum": ["realtime",

View File

@@ -56,7 +56,6 @@ if sys.platform.startswith("win"):
program_files_x86 = program_files = os.environ["PROGRAMFILES"]
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 V3': r'{}\code4ward.net\Royal TS V3\RTS3App.exe /connectadhoc:%h /adhoctype:terminal /p:IsTelnetConnection="true" /p:ConnectionType="telnet;Telnet Connection" /p:Port="%p" /p:Name="%d"'.format(program_files),
'Royal TS V5': r'"{}\Royal TS V5\RoyalTS.exe" /protocol:terminal /using:adhoc /uri:"%h" /property:Port="%p" /property:IsTelnetConnection="true" /property:Name="%d"'.format(program_files_x86),
@@ -65,6 +64,7 @@ if sys.platform.startswith("win"):
'SecureCRT (personal profile)': r'"{}\AppData\Local\VanDyke Software\SecureCRT\SecureCRT.exe" /T /N "%d" /TELNET %h %p'.format(userprofile),
'TeraTerm Pro': r'"{}\teraterm\ttermpro.exe" /W="%d" /M="ttstart.macro" /T=1 %h %p'.format(program_files_x86),
'Telnet': 'telnet %h %p',
'Windows Terminal': 'wt.exe -w 1 new-tab --title %d telnet %h %p',
'Xshell 4': r'"{}\NetSarang\Xshell 4\xshell.exe" -url telnet://%h:%p'.format(program_files_x86),
'Xshell 5': r'"{}\NetSarang\Xshell 5\xshell.exe" -url telnet://%h:%p -newtab %d'.format(program_files_x86),
'ZOC 6': r'"{}\ZOC6\zoc.exe" "/TELNET:%h:%p" /TABBED "/TITLE:%d"'.format(program_files_x86)}
@@ -142,7 +142,8 @@ elif sys.platform.startswith("darwin"):
'Royal TSX': "open 'rtsx://telnet%3A%2F%2F%h:%p'",
'SecureCRT': '/Applications/SecureCRT.app/Contents/MacOS/SecureCRT /N "%d" /T /TELNET %h %p',
'ZOC 6': '/Applications/zoc6.app/Contents/MacOS/zoc6 "/TELNET:%h:%p" /TABBED "/TITLE:%d"',
'ZOC 7': '/Applications/zoc7.app/Contents/MacOS/zoc7 "/TELNET:%h:%p" /TABBED "/TITLE:%d"'
'ZOC 7': '/Applications/zoc7.app/Contents/MacOS/zoc7 "/TELNET:%h:%p" /TABBED "/TITLE:%d"',
'ZOC 8': '/Applications/zoc8.app/Contents/MacOS/zoc8 "/TELNET:%h:%p" /TABBED "/TITLE:%d"'
}
# default Mac OS X Telnet console command
@@ -157,7 +158,9 @@ else:
'KDE Konsole': 'konsole --new-tab -p tabtitle="%d" -e "telnet %h %p"',
'SecureCRT': 'SecureCRT /T /N "%d" /TELNET %h %p',
'Mate Terminal': 'mate-terminal --tab -e "telnet %h %p" -t "%d"',
'urxvt': 'urxvt -title %d -e telnet %h %p'}
'terminator': 'terminator -e "telnet %h %p" -T "%d"',
'urxvt': 'urxvt -title %d -e telnet %h %p',
'kitty': 'kitty -T %d telnet %h %p'}
# default Telnet console command on other systems
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Xterm"]
@@ -209,7 +212,7 @@ else:
if sys.platform.startswith("win"):
# Windows
PRECONFIGURED_SPICE_CONSOLE_COMMANDS = {
'Remote Viewer': r'"{}\VirtViewer v7.0-256\bin\remote-viewer.exe" spice://%h:%p'.format(program_files),
'Remote Viewer': r'"{}\VirtViewer v11.0-256\bin\remote-viewer.exe" spice://%h:%p'.format(program_files),
}
# default Windows SPICE console command

View File

@@ -62,7 +62,7 @@ class Topology(QtCore.QObject):
self._main_window = None
# If set the project is loaded when we got connection to the controller
# usefull when we open a project from cli or when server restart
# useful when we open a project from cli or when server restart
self._project_to_load_path = None
self._project_id_to_load = None

View File

@@ -9,8 +9,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>579</width>
<height>374</height>
<width>585</width>
<height>353</height>
</rect>
</property>
<property name="windowTitle">
@@ -23,10 +23,31 @@
<string>Server settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
<widget class="QLabel" name="uiServerPortLabel">
<property name="text">
<string>Port:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="uiServerNameLineEdit"/>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="uiServerHostLineEdit">
<property name="text">
<string>192.168.56.101</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="uiServerHostLabel">
<property name="text">
<string>Host:</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<widget class="QSpinBox" name="uiServerPortSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@@ -52,25 +73,25 @@
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QLineEdit" name="uiServerHostLineEdit">
<property name="text">
<string>192.168.56.101</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiServerHostLabel">
<widget class="QLabel" name="uiServerProtocolLabel">
<property name="text">
<string>Host:</string>
<string>Protocol:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="uiServerPortLabel">
<property name="text">
<string>Port:</string>
</property>
<item row="1" column="1">
<widget class="QComboBox" name="uiServerProtocolComboBox">
<item>
<property name="text">
<string>HTTP</string>
</property>
</item>
<item>
<property name="text">
<string>HTTPS</string>
</property>
</item>
</widget>
</item>
</layout>

View File

@@ -1,29 +1,39 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/ui/edit_compute_dialog.ui'
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/edit_compute_dialog.ui'
#
# Created by: PyQt5 UI code generator 5.8
# Created by: PyQt5 UI code generator 5.15.2
#
# WARNING! All changes made in this file will be lost!
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_EditComputeDialog(object):
def setupUi(self, EditComputeDialog):
EditComputeDialog.setObjectName("EditComputeDialog")
EditComputeDialog.setWindowModality(QtCore.Qt.WindowModal)
EditComputeDialog.resize(579, 374)
EditComputeDialog.resize(585, 353)
self.verticalLayout = QtWidgets.QVBoxLayout(EditComputeDialog)
self.verticalLayout.setObjectName("verticalLayout")
self.groupBox = QtWidgets.QGroupBox(EditComputeDialog)
self.groupBox.setObjectName("groupBox")
self.gridLayout = QtWidgets.QGridLayout(self.groupBox)
self.gridLayout.setObjectName("gridLayout")
self.uiServerPortLabel = QtWidgets.QLabel(self.groupBox)
self.uiServerPortLabel.setObjectName("uiServerPortLabel")
self.gridLayout.addWidget(self.uiServerPortLabel, 3, 0, 1, 1)
self.uiServerNameLineEdit = QtWidgets.QLineEdit(self.groupBox)
self.uiServerNameLineEdit.setObjectName("uiServerNameLineEdit")
self.gridLayout.addWidget(self.uiServerNameLineEdit, 0, 1, 1, 1)
self.uiServerHostLineEdit = QtWidgets.QLineEdit(self.groupBox)
self.uiServerHostLineEdit.setObjectName("uiServerHostLineEdit")
self.gridLayout.addWidget(self.uiServerHostLineEdit, 2, 1, 1, 2)
self.uiServerHostLabel = QtWidgets.QLabel(self.groupBox)
self.uiServerHostLabel.setObjectName("uiServerHostLabel")
self.gridLayout.addWidget(self.uiServerHostLabel, 2, 0, 1, 1)
self.uiServerPortSpinBox = QtWidgets.QSpinBox(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -34,19 +44,18 @@ class Ui_EditComputeDialog(object):
self.uiServerPortSpinBox.setMaximum(65535)
self.uiServerPortSpinBox.setProperty("value", 3080)
self.uiServerPortSpinBox.setObjectName("uiServerPortSpinBox")
self.gridLayout.addWidget(self.uiServerPortSpinBox, 2, 1, 1, 2)
self.gridLayout.addWidget(self.uiServerPortSpinBox, 3, 1, 1, 2)
self.label = QtWidgets.QLabel(self.groupBox)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
self.uiServerHostLineEdit = QtWidgets.QLineEdit(self.groupBox)
self.uiServerHostLineEdit.setObjectName("uiServerHostLineEdit")
self.gridLayout.addWidget(self.uiServerHostLineEdit, 1, 1, 1, 2)
self.uiServerHostLabel = QtWidgets.QLabel(self.groupBox)
self.uiServerHostLabel.setObjectName("uiServerHostLabel")
self.gridLayout.addWidget(self.uiServerHostLabel, 1, 0, 1, 1)
self.uiServerPortLabel = QtWidgets.QLabel(self.groupBox)
self.uiServerPortLabel.setObjectName("uiServerPortLabel")
self.gridLayout.addWidget(self.uiServerPortLabel, 2, 0, 1, 1)
self.uiServerProtocolLabel = QtWidgets.QLabel(self.groupBox)
self.uiServerProtocolLabel.setObjectName("uiServerProtocolLabel")
self.gridLayout.addWidget(self.uiServerProtocolLabel, 1, 0, 1, 1)
self.uiServerProtocolComboBox = QtWidgets.QComboBox(self.groupBox)
self.uiServerProtocolComboBox.setObjectName("uiServerProtocolComboBox")
self.uiServerProtocolComboBox.addItem("")
self.uiServerProtocolComboBox.addItem("")
self.gridLayout.addWidget(self.uiServerProtocolComboBox, 1, 1, 1, 1)
self.verticalLayout.addWidget(self.groupBox)
self.uiEnableAuthenticationCheckBox = QtWidgets.QGroupBox(EditComputeDialog)
self.uiEnableAuthenticationCheckBox.setCheckable(True)
@@ -67,7 +76,7 @@ class Ui_EditComputeDialog(object):
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.uiServerPasswordLabel)
self.uiServerPasswordLineEdit = QtWidgets.QLineEdit(self.uiEnableAuthenticationCheckBox)
self.uiServerPasswordLineEdit.setEnabled(True)
self.uiServerPasswordLineEdit.setInputMethodHints(QtCore.Qt.ImhHiddenText | QtCore.Qt.ImhNoAutoUppercase | QtCore.Qt.ImhNoPredictiveText | QtCore.Qt.ImhSensitiveData)
self.uiServerPasswordLineEdit.setInputMethodHints(QtCore.Qt.ImhHiddenText|QtCore.Qt.ImhNoAutoUppercase|QtCore.Qt.ImhNoPredictiveText|QtCore.Qt.ImhSensitiveData)
self.uiServerPasswordLineEdit.setEchoMode(QtWidgets.QLineEdit.Password)
self.uiServerPasswordLineEdit.setObjectName("uiServerPasswordLineEdit")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.uiServerPasswordLineEdit)
@@ -80,7 +89,7 @@ class Ui_EditComputeDialog(object):
self.verticalLayout.addItem(spacerItem)
self.buttonBox = QtWidgets.QDialogButtonBox(EditComputeDialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.verticalLayout.addWidget(self.buttonBox)
@@ -98,10 +107,13 @@ class Ui_EditComputeDialog(object):
_translate = QtCore.QCoreApplication.translate
EditComputeDialog.setWindowTitle(_translate("EditComputeDialog", "Edit server settings"))
self.groupBox.setTitle(_translate("EditComputeDialog", "Server settings"))
self.label.setText(_translate("EditComputeDialog", "Name:"))
self.uiServerPortLabel.setText(_translate("EditComputeDialog", "Port:"))
self.uiServerHostLineEdit.setText(_translate("EditComputeDialog", "192.168.56.101"))
self.uiServerHostLabel.setText(_translate("EditComputeDialog", "Host:"))
self.uiServerPortLabel.setText(_translate("EditComputeDialog", "Port:"))
self.label.setText(_translate("EditComputeDialog", "Name:"))
self.uiServerProtocolLabel.setText(_translate("EditComputeDialog", "Protocol:"))
self.uiServerProtocolComboBox.setItemText(0, _translate("EditComputeDialog", "HTTP"))
self.uiServerProtocolComboBox.setItemText(1, _translate("EditComputeDialog", "HTTPS"))
self.uiEnableAuthenticationCheckBox.setTitle(_translate("EditComputeDialog", "Enable authentication"))
self.uiServerUserLabel.setText(_translate("EditComputeDialog", "User:"))
self.uiServerPasswordLabel.setText(_translate("EditComputeDialog", "Password:"))

View File

@@ -10,7 +10,7 @@
<x>0</x>
<y>0</y>
<width>986</width>
<height>716</height>
<height>719</height>
</rect>
</property>
<property name="contextMenuPolicy">
@@ -38,7 +38,16 @@ background-none;
</property>
<widget class="QWidget" name="uiCentralWidget">
<layout class="QGridLayout">
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
@@ -62,7 +71,7 @@ background-none;
<x>0</x>
<y>0</y>
<width>986</width>
<height>42</height>
<height>22</height>
</rect>
</property>
<widget class="QMenu" name="uiEditMenu">
@@ -129,6 +138,7 @@ background-none;
<addaction name="uiShowPortNamesAction"/>
<addaction name="uiLockAllAction"/>
<addaction name="separator"/>
<addaction name="uiResetDocksAction"/>
<addaction name="uiDocksMenu"/>
</widget>
<widget class="QMenu" name="uiControlMenu">
@@ -141,6 +151,7 @@ background-none;
<addaction name="uiReloadAllAction"/>
<addaction name="uiAuxConsoleAllAction"/>
<addaction name="uiConsoleAllAction"/>
<addaction name="uiResetConsoleAllAction"/>
</widget>
<widget class="QMenu" name="uiAnnotateMenu">
<property name="title">
@@ -228,7 +239,16 @@ background-none;
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
@@ -372,7 +392,16 @@ background-none;
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
@@ -438,7 +467,16 @@ background-none;
</sizepolicy>
</property>
<layout class="QGridLayout">
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
@@ -482,7 +520,16 @@ background-none;
</attribute>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QGridLayout" name="gridLayout">
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
@@ -1243,6 +1290,16 @@ background-none;
<string>New template</string>
</property>
</action>
<action name="uiResetDocksAction">
<property name="text">
<string>Reset docks</string>
</property>
</action>
<action name="uiResetConsoleAllAction">
<property name="text">
<string>Reset all console connections</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/main_window.ui'
#
# Created by: PyQt5 UI code generator 5.13.2
# Created by: PyQt5 UI code generator 5.14.1
#
# WARNING! All changes made in this file will be lost!
@@ -48,7 +48,7 @@ class Ui_MainWindow(object):
self.gridlayout.addWidget(self.uiGraphicsView, 0, 0, 1, 1)
MainWindow.setCentralWidget(self.uiCentralWidget)
self.uiMenuBar = QtWidgets.QMenuBar(MainWindow)
self.uiMenuBar.setGeometry(QtCore.QRect(0, 0, 986, 42))
self.uiMenuBar.setGeometry(QtCore.QRect(0, 0, 986, 22))
self.uiMenuBar.setObjectName("uiMenuBar")
self.uiEditMenu = QtWidgets.QMenu(self.uiMenuBar)
self.uiEditMenu.setObjectName("uiEditMenu")
@@ -448,6 +448,13 @@ class Ui_MainWindow(object):
self.uiNewTemplateAction = QtWidgets.QAction(MainWindow)
self.uiNewTemplateAction.setIcon(icon)
self.uiNewTemplateAction.setObjectName("uiNewTemplateAction")
self.uiResetDocksAction = QtWidgets.QAction(MainWindow)
self.uiResetDocksAction.setObjectName("uiResetDocksAction")
self.uiEditReadmeAction = QtWidgets.QAction(MainWindow)
self.uiEditReadmeAction.setIcon(icon31)
self.uiEditReadmeAction.setObjectName("uiEditReadmeAction")
self.uiResetConsoleAllAction = QtWidgets.QAction(MainWindow)
self.uiResetConsoleAllAction.setObjectName("uiResetConsoleAllAction")
self.uiEditMenu.addAction(self.uiSelectAllAction)
self.uiEditMenu.addAction(self.uiSelectNoneAction)
self.uiEditMenu.addSeparator()
@@ -488,6 +495,7 @@ class Ui_MainWindow(object):
self.uiViewMenu.addAction(self.uiShowPortNamesAction)
self.uiViewMenu.addAction(self.uiLockAllAction)
self.uiViewMenu.addSeparator()
self.uiViewMenu.addAction(self.uiResetDocksAction)
self.uiViewMenu.addAction(self.uiDocksMenu.menuAction())
self.uiControlMenu.addAction(self.uiStartAllAction)
self.uiControlMenu.addAction(self.uiSuspendAllAction)
@@ -495,6 +503,7 @@ class Ui_MainWindow(object):
self.uiControlMenu.addAction(self.uiReloadAllAction)
self.uiControlMenu.addAction(self.uiAuxConsoleAllAction)
self.uiControlMenu.addAction(self.uiConsoleAllAction)
self.uiControlMenu.addAction(self.uiResetConsoleAllAction)
self.uiAnnotateMenu.addAction(self.uiAddNoteAction)
self.uiAnnotateMenu.addAction(self.uiInsertImageAction)
self.uiAnnotateMenu.addAction(self.uiDrawRectangleAction)
@@ -711,6 +720,10 @@ class Ui_MainWindow(object):
self.uiLockAllAction.setToolTip(_translate("MainWindow", "Lock or unlock all items"))
self.uiWebUIAction.setText(_translate("MainWindow", "Web UI - beta"))
self.uiNewTemplateAction.setText(_translate("MainWindow", "New template"))
self.uiResetDocksAction.setText(_translate("MainWindow", "Reset docks"))
self.uiEditReadmeAction.setText(_translate("MainWindow", "Edit readme"))
self.uiEditReadmeAction.setToolTip(_translate("MainWindow", "Edit readme"))
self.uiResetConsoleAllAction.setText(_translate("MainWindow", "Reset all console connections"))
from ..compute_summary_view import ComputeSummaryView
from ..console_view import ConsoleView
from ..graphics_view import GraphicsView

File diff suppressed because it is too large Load Diff

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>1273</width>
<height>1097</height>
<width>681</width>
<height>843</height>
</rect>
</property>
<property name="sizePolicy">
@@ -295,24 +295,14 @@
<string>Remote main server</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Host:</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QLineEdit" name="uiRemoteMainServerHostLineEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Port:</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<item row="2" column="1" colspan="2">
<widget class="QSpinBox" name="uiRemoteMainServerPortSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
@@ -331,44 +321,75 @@
</property>
</widget>
</item>
<item row="2" column="0">
<item row="3" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Auth:</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QCheckBox" name="uiRemoteMainServerAuthCheckBox">
<property name="text">
<string/>
</property>
</widget>
<item row="4" column="2">
<widget class="QLineEdit" name="uiRemoteMainServerUserLineEdit"/>
</item>
<item row="3" column="0">
<item row="4" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>User:</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLineEdit" name="uiRemoteMainServerUserLineEdit"/>
<item row="5" column="2">
<widget class="QLineEdit" name="uiRemoteMainServerPasswordLineEdit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Port:</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QLineEdit" name="uiRemoteMainServerHostLineEdit"/>
</item>
<item row="5" column="0" colspan="2">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Password:</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QLineEdit" name="uiRemoteMainServerPasswordLineEdit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
<item row="3" column="2">
<widget class="QCheckBox" name="uiRemoteMainServerAuthCheckBox">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="uiRemoteMainServerProtocolLabel">
<property name="text">
<string>Protocol:</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QComboBox" name="uiRemoteMainServerProtocolComboBox">
<item>
<property name="text">
<string>HTTP</string>
</property>
</item>
<item>
<property name="text">
<string>HTTPS</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
@@ -404,7 +425,16 @@
<string>Remote servers</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_5">
<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 row="0" column="0" colspan="2">

View File

@@ -2,16 +2,19 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/server_preferences_page.ui'
#
# Created by: PyQt5 UI code generator 5.9
# Created by: PyQt5 UI code generator 5.15.2
#
# WARNING! All changes made in this file will be lost!
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_ServerPreferencesPageWidget(object):
def setupUi(self, ServerPreferencesPageWidget):
ServerPreferencesPageWidget.setObjectName("ServerPreferencesPageWidget")
ServerPreferencesPageWidget.resize(1273, 1097)
ServerPreferencesPageWidget.resize(681, 843)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@@ -149,13 +152,7 @@ class Ui_ServerPreferencesPageWidget(object):
self.gridLayout_4.setObjectName("gridLayout_4")
self.label_2 = QtWidgets.QLabel(self.uiRemoteMainServerGroupBox)
self.label_2.setObjectName("label_2")
self.gridLayout_4.addWidget(self.label_2, 0, 0, 1, 1)
self.uiRemoteMainServerHostLineEdit = QtWidgets.QLineEdit(self.uiRemoteMainServerGroupBox)
self.uiRemoteMainServerHostLineEdit.setObjectName("uiRemoteMainServerHostLineEdit")
self.gridLayout_4.addWidget(self.uiRemoteMainServerHostLineEdit, 0, 1, 1, 2)
self.label_3 = QtWidgets.QLabel(self.uiRemoteMainServerGroupBox)
self.label_3.setObjectName("label_3")
self.gridLayout_4.addWidget(self.label_3, 1, 0, 1, 1)
self.gridLayout_4.addWidget(self.label_2, 1, 0, 1, 1)
self.uiRemoteMainServerPortSpinBox = QtWidgets.QSpinBox(self.uiRemoteMainServerGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
@@ -165,27 +162,41 @@ class Ui_ServerPreferencesPageWidget(object):
self.uiRemoteMainServerPortSpinBox.setMaximum(65535)
self.uiRemoteMainServerPortSpinBox.setProperty("value", 3080)
self.uiRemoteMainServerPortSpinBox.setObjectName("uiRemoteMainServerPortSpinBox")
self.gridLayout_4.addWidget(self.uiRemoteMainServerPortSpinBox, 1, 1, 1, 2)
self.gridLayout_4.addWidget(self.uiRemoteMainServerPortSpinBox, 2, 1, 1, 2)
self.label = QtWidgets.QLabel(self.uiRemoteMainServerGroupBox)
self.label.setObjectName("label")
self.gridLayout_4.addWidget(self.label, 2, 0, 1, 1)
self.uiRemoteMainServerAuthCheckBox = QtWidgets.QCheckBox(self.uiRemoteMainServerGroupBox)
self.uiRemoteMainServerAuthCheckBox.setText("")
self.uiRemoteMainServerAuthCheckBox.setObjectName("uiRemoteMainServerAuthCheckBox")
self.gridLayout_4.addWidget(self.uiRemoteMainServerAuthCheckBox, 2, 2, 1, 1)
self.label_4 = QtWidgets.QLabel(self.uiRemoteMainServerGroupBox)
self.label_4.setObjectName("label_4")
self.gridLayout_4.addWidget(self.label_4, 3, 0, 1, 1)
self.gridLayout_4.addWidget(self.label, 3, 0, 1, 1)
self.uiRemoteMainServerUserLineEdit = QtWidgets.QLineEdit(self.uiRemoteMainServerGroupBox)
self.uiRemoteMainServerUserLineEdit.setObjectName("uiRemoteMainServerUserLineEdit")
self.gridLayout_4.addWidget(self.uiRemoteMainServerUserLineEdit, 3, 2, 1, 1)
self.label_5 = QtWidgets.QLabel(self.uiRemoteMainServerGroupBox)
self.label_5.setObjectName("label_5")
self.gridLayout_4.addWidget(self.label_5, 4, 0, 1, 2)
self.gridLayout_4.addWidget(self.uiRemoteMainServerUserLineEdit, 4, 2, 1, 1)
self.label_4 = QtWidgets.QLabel(self.uiRemoteMainServerGroupBox)
self.label_4.setObjectName("label_4")
self.gridLayout_4.addWidget(self.label_4, 4, 0, 1, 1)
self.uiRemoteMainServerPasswordLineEdit = QtWidgets.QLineEdit(self.uiRemoteMainServerGroupBox)
self.uiRemoteMainServerPasswordLineEdit.setEchoMode(QtWidgets.QLineEdit.Password)
self.uiRemoteMainServerPasswordLineEdit.setObjectName("uiRemoteMainServerPasswordLineEdit")
self.gridLayout_4.addWidget(self.uiRemoteMainServerPasswordLineEdit, 4, 2, 1, 1)
self.gridLayout_4.addWidget(self.uiRemoteMainServerPasswordLineEdit, 5, 2, 1, 1)
self.label_3 = QtWidgets.QLabel(self.uiRemoteMainServerGroupBox)
self.label_3.setObjectName("label_3")
self.gridLayout_4.addWidget(self.label_3, 2, 0, 1, 1)
self.uiRemoteMainServerHostLineEdit = QtWidgets.QLineEdit(self.uiRemoteMainServerGroupBox)
self.uiRemoteMainServerHostLineEdit.setObjectName("uiRemoteMainServerHostLineEdit")
self.gridLayout_4.addWidget(self.uiRemoteMainServerHostLineEdit, 1, 1, 1, 2)
self.label_5 = QtWidgets.QLabel(self.uiRemoteMainServerGroupBox)
self.label_5.setObjectName("label_5")
self.gridLayout_4.addWidget(self.label_5, 5, 0, 1, 2)
self.uiRemoteMainServerAuthCheckBox = QtWidgets.QCheckBox(self.uiRemoteMainServerGroupBox)
self.uiRemoteMainServerAuthCheckBox.setText("")
self.uiRemoteMainServerAuthCheckBox.setObjectName("uiRemoteMainServerAuthCheckBox")
self.gridLayout_4.addWidget(self.uiRemoteMainServerAuthCheckBox, 3, 2, 1, 1)
self.uiRemoteMainServerProtocolLabel = QtWidgets.QLabel(self.uiRemoteMainServerGroupBox)
self.uiRemoteMainServerProtocolLabel.setObjectName("uiRemoteMainServerProtocolLabel")
self.gridLayout_4.addWidget(self.uiRemoteMainServerProtocolLabel, 0, 0, 1, 1)
self.uiRemoteMainServerProtocolComboBox = QtWidgets.QComboBox(self.uiRemoteMainServerGroupBox)
self.uiRemoteMainServerProtocolComboBox.setObjectName("uiRemoteMainServerProtocolComboBox")
self.uiRemoteMainServerProtocolComboBox.addItem("")
self.uiRemoteMainServerProtocolComboBox.addItem("")
self.gridLayout_4.addWidget(self.uiRemoteMainServerProtocolComboBox, 0, 1, 1, 2)
self.verticalLayout.addWidget(self.uiRemoteMainServerGroupBox)
spacerItem2 = QtWidgets.QSpacerItem(10, 10, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem2)
@@ -290,11 +301,14 @@ class Ui_ServerPreferencesPageWidget(object):
self.uiUDPPortRangeLabel.setText(_translate("ServerPreferencesPageWidget", "to"))
self.uiRemoteMainServerGroupBox.setTitle(_translate("ServerPreferencesPageWidget", "Remote main server"))
self.label_2.setText(_translate("ServerPreferencesPageWidget", "Host:"))
self.label_3.setText(_translate("ServerPreferencesPageWidget", "Port:"))
self.uiRemoteMainServerPortSpinBox.setSuffix(_translate("ServerPreferencesPageWidget", " TCP"))
self.label.setText(_translate("ServerPreferencesPageWidget", "Auth:"))
self.label_4.setText(_translate("ServerPreferencesPageWidget", "User:"))
self.label_3.setText(_translate("ServerPreferencesPageWidget", "Port:"))
self.label_5.setText(_translate("ServerPreferencesPageWidget", "Password:"))
self.uiRemoteMainServerProtocolLabel.setText(_translate("ServerPreferencesPageWidget", "Protocol:"))
self.uiRemoteMainServerProtocolComboBox.setItemText(0, _translate("ServerPreferencesPageWidget", "HTTP"))
self.uiRemoteMainServerProtocolComboBox.setItemText(1, _translate("ServerPreferencesPageWidget", "HTTPS"))
self.uiServerPreferenceTabWidget.setTabText(self.uiServerPreferenceTabWidget.indexOf(self.uiLocalTabWidget), _translate("ServerPreferencesPageWidget", "Main server"))
self.uiRemoteServersTreeWidget.headerItem().setText(0, _translate("ServerPreferencesPageWidget", "Name"))
self.uiRemoteServersTreeWidget.headerItem().setText(4, _translate("ServerPreferencesPageWidget", "User"))
@@ -304,4 +318,3 @@ class Ui_ServerPreferencesPageWidget(object):
self.label_7.setText(_translate("ServerPreferencesPageWidget", "Note: Changes are not visible in other part of the settings or application until you apply them."))
self.uiServerPreferenceTabWidget.setTabText(self.uiServerPreferenceTabWidget.indexOf(self.uiRemoteTabWidget), _translate("ServerPreferencesPageWidget", "Remote servers"))
self.uiRestoreDefaultsPushButton.setText(_translate("ServerPreferencesPageWidget", "Restore defaults"))

View File

@@ -2,14 +2,6 @@
<ui version="4.0">
<class>StyleEditorDialog</class>
<widget class="QDialog" name="StyleEditorDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>328</width>
<height>252</height>
</rect>
</property>
<property name="windowTitle">
<string>Style editor</string>
</property>
@@ -22,7 +14,7 @@
<property name="title">
<string>Style settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="uiColorLabel">
<property name="text">

View File

@@ -1,58 +1,58 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/ui/style_editor_dialog.ui'
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/style_editor_dialog.ui'
#
# Created by: PyQt5 UI code generator 5.4.2
# Created by: PyQt5 UI code generator 5.15.2
#
# WARNING! All changes made in this file will be lost!
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_StyleEditorDialog(object):
def setupUi(self, StyleEditorDialog):
StyleEditorDialog.setObjectName("StyleEditorDialog")
StyleEditorDialog.resize(328, 252)
StyleEditorDialog.setModal(True)
self.verticalLayout = QtWidgets.QVBoxLayout(StyleEditorDialog)
self.verticalLayout.setObjectName("verticalLayout")
self.uiStyleSettingsGroupBox = QtWidgets.QGroupBox(StyleEditorDialog)
self.uiStyleSettingsGroupBox.setObjectName("uiStyleSettingsGroupBox")
self.gridLayout = QtWidgets.QGridLayout(self.uiStyleSettingsGroupBox)
self.gridLayout.setObjectName("gridLayout")
self.formLayout = QtWidgets.QFormLayout(self.uiStyleSettingsGroupBox)
self.formLayout.setObjectName("formLayout")
self.uiColorLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox)
self.uiColorLabel.setObjectName("uiColorLabel")
self.gridLayout.addWidget(self.uiColorLabel, 0, 0, 1, 1)
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.uiColorLabel)
self.uiColorPushButton = QtWidgets.QPushButton(self.uiStyleSettingsGroupBox)
self.uiColorPushButton.setText("")
self.uiColorPushButton.setObjectName("uiColorPushButton")
self.gridLayout.addWidget(self.uiColorPushButton, 0, 1, 1, 1)
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.uiColorPushButton)
self.uiBorderColorLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox)
self.uiBorderColorLabel.setObjectName("uiBorderColorLabel")
self.gridLayout.addWidget(self.uiBorderColorLabel, 1, 0, 1, 1)
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.uiBorderColorLabel)
self.uiBorderColorPushButton = QtWidgets.QPushButton(self.uiStyleSettingsGroupBox)
self.uiBorderColorPushButton.setText("")
self.uiBorderColorPushButton.setObjectName("uiBorderColorPushButton")
self.gridLayout.addWidget(self.uiBorderColorPushButton, 1, 1, 1, 1)
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.uiBorderColorPushButton)
self.uiBorderWidthLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox)
self.uiBorderWidthLabel.setObjectName("uiBorderWidthLabel")
self.gridLayout.addWidget(self.uiBorderWidthLabel, 2, 0, 1, 1)
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.uiBorderWidthLabel)
self.uiBorderWidthSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox)
self.uiBorderWidthSpinBox.setMinimum(1)
self.uiBorderWidthSpinBox.setMaximum(100)
self.uiBorderWidthSpinBox.setProperty("value", 2)
self.uiBorderWidthSpinBox.setObjectName("uiBorderWidthSpinBox")
self.gridLayout.addWidget(self.uiBorderWidthSpinBox, 2, 1, 1, 1)
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.uiBorderWidthSpinBox)
self.uiBorderStyleLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox)
self.uiBorderStyleLabel.setObjectName("uiBorderStyleLabel")
self.gridLayout.addWidget(self.uiBorderStyleLabel, 3, 0, 1, 1)
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.uiBorderStyleLabel)
self.uiBorderStyleComboBox = QtWidgets.QComboBox(self.uiStyleSettingsGroupBox)
self.uiBorderStyleComboBox.setObjectName("uiBorderStyleComboBox")
self.gridLayout.addWidget(self.uiBorderStyleComboBox, 3, 1, 1, 1)
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.uiBorderStyleComboBox)
self.uiRotationLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox)
self.uiRotationLabel.setObjectName("uiRotationLabel")
self.gridLayout.addWidget(self.uiRotationLabel, 4, 0, 1, 1)
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.uiRotationLabel)
self.uiRotationSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -62,11 +62,11 @@ class Ui_StyleEditorDialog(object):
self.uiRotationSpinBox.setMinimum(-360)
self.uiRotationSpinBox.setMaximum(360)
self.uiRotationSpinBox.setObjectName("uiRotationSpinBox")
self.gridLayout.addWidget(self.uiRotationSpinBox, 4, 1, 1, 1)
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.uiRotationSpinBox)
self.verticalLayout.addWidget(self.uiStyleSettingsGroupBox)
self.uiButtonBox = QtWidgets.QDialogButtonBox(StyleEditorDialog)
self.uiButtonBox.setOrientation(QtCore.Qt.Horizontal)
self.uiButtonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply | QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok)
self.uiButtonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.uiButtonBox.setObjectName("uiButtonBox")
self.verticalLayout.addWidget(self.uiButtonBox)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
@@ -88,7 +88,6 @@ class Ui_StyleEditorDialog(object):
self.uiBorderStyleLabel.setText(_translate("StyleEditorDialog", "Border style:"))
self.uiRotationLabel.setText(_translate("StyleEditorDialog", "Rotation:"))
self.uiRotationSpinBox.setToolTip(_translate("StyleEditorDialog", "Rotation can be ajusted on the scene for a selected item while\n"
"editing (notes only) with ALT and \'+\' (or P) / ALT and \'-\' (or M)"))
"editing (notes only) with ALT and \'+\' (or P) / ALT and \'-\' (or M)"))
self.uiRotationSpinBox.setSuffix(_translate("StyleEditorDialog", "°"))
from . import resources_rc

View File

@@ -239,4 +239,22 @@ class UpdateManager(QtCore.QObject):
for member in members:
# Path separator is always / even on windows
member.name = member.name.split("/", 1)[1]
tar.extractall(path=self._package_directory, members=members)
def is_within_directory(directory, target):
abs_directory = os.path.abspath(directory)
abs_target = os.path.abspath(target)
prefix = os.path.commonprefix([abs_directory, abs_target])
return prefix == abs_directory
def safe_extract(tar, path=".", members=None, *, numeric_owner=False):
for member in tar.getmembers():
member_path = os.path.join(path, member.name)
if not is_within_directory(path, member_path):
raise Exception("Attempted Path Traversal in Tar File")
tar.extractall(path, members, numeric_owner=numeric_owner)
safe_extract(tar, path=self._package_directory, members=members)

View File

@@ -23,8 +23,8 @@
# or negative for a release candidate or beta (after the base version
# number has been incremented)
__version__ = "2.2.17"
__version_info__ = (2, 2, 17, 0)
__version__ = "2.2.37"
__version_info__ = (2, 2, 37, 0)
if "dev" in __version__:
try:
@@ -32,6 +32,6 @@ if "dev" in __version__:
import subprocess
if os.path.exists(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".git")):
r = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]).decode().strip("\n")
__version__ += "-" + r
__version__ += "+" + r
except Exception as e:
print(e)

View File

@@ -1,2 +1,3 @@
-rrequirements.txt
PyQt5==5.15.7

View File

@@ -1,4 +1,7 @@
jsonschema==3.2.0
sentry-sdk>=0.14.4
psutil==5.6.7
distro>=1.3.0
jsonschema>=4.17.3,<4.18; python_version >= '3.7'
jsonschema==3.2.0; python_version < '3.7' # v3.2.0 is the last version to support Python 3.6
sentry-sdk==1.12.1,<1.13
psutil==5.9.4
distro>=1.7.0
setuptools>=60.8.1; python_version >= '3.7'
setuptools==59.6.0; python_version < '3.7' # v59.6.0 is the last version to support Python 3.6

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
id="svg7631" xmlns:cc="http://creativecommons.org/ns#" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:svg="http://www.w3.org/2000/svg" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" sodipodi:docname="camera-photo.svg" sodipodi:version="0.32" inkscape:version="0.48.3.1 r9886" inkscape:output_extension="org.inkscape.output.svg.inkscape"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 48 48"
enable-background="new 0 0 48 48" xml:space="preserve">
<g>
<g>
<path fill="#231F20" d="M42.8,38.4l-7.4-9c1.6-2.6,2.5-5.6,2.5-8.8c0-9.3-7.5-16.8-16.8-16.8S4.3,11.3,4.3,20.6
c0,9.3,7.5,16.8,16.8,16.8c3.7,0,7.1-1.2,9.9-3.2l6.8,8.3c0.6,0.7,1.5,0.8,2.2,0.2l2.5-2.1C43.2,40.1,43.3,39.1,42.8,38.4z
M9,20.6c0-6.7,5.4-12.1,12.1-12.1s12.1,5.4,12.1,12.1c0,6.7-5.4,12.1-12.1,12.1S9,27.3,9,20.6z"/>
<polygon fill="#231F20" points="27.6,19 23.2,19 23.2,14.4 19,14.4 19,19 14.5,19 14.5,23.2 19,23.2 19,27.8 23.2,27.8 23.2,23.2
27.6,23.2 "/>
</g>
<g>
<path fill="#CDCDCD" d="M42.6,40l-7.4-9c1.6-2.6,2.5-5.6,2.5-8.8C37.7,13,30.2,5.4,21,5.4S4.2,13,4.2,22.2C4.2,31.5,11.7,39,21,39
c3.7,0,7.1-1.2,9.9-3.2l6.8,8.3c0.6,0.7,1.5,0.8,2.2,0.2l2.5-2.1C43.1,41.7,43.2,40.7,42.6,40z M8.9,22.2
c0-6.7,5.4-12.1,12.1-12.1S33,15.6,33,22.2c0,6.7-5.4,12.1-12.1,12.1S8.9,28.9,8.9,22.2z"/>
<polygon fill="#CDCDCD" points="27.8,20.6 23.2,20.6 23.2,16 18.9,16 18.9,20.6 14.4,20.6 14.4,24.9 18.9,24.9 18.9,29.4
23.2,29.4 23.2,24.9 27.8,24.9 "/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -1,4 +1,5 @@
QWidget {
color: black;
background-color: #535353;
}
@@ -7,10 +8,12 @@ QToolBar {
}
QGraphicsView, QTextEdit, QPlainTextEdit, QTreeWidget, QListWidget, QLineEdit, QSpinBox, QComboBox {
background-color: #dedede
color: black;
background-color: #dedede;
}
QLabel, QMenu, QStatusBar {
color: black;
color: #dedede;
}
@@ -48,6 +51,7 @@ QComboBox {
}
QComboBox QAbstractItemView {
color: black;
background-color: #dedede;
}

View File

@@ -66,7 +66,7 @@ setup(
author="Jeremy Grossmann",
author_email="package-maintainer@gns3.net",
description="GNS3 graphical interface for the GNS3 server.",
long_description=open("README.rst", "r").read(),
long_description=open("README.md", "r").read(),
install_requires=open("requirements.txt", "r").read().splitlines(),
entry_points={
"gui_scripts": [
@@ -78,6 +78,7 @@ setup(
include_package_data=True,
package_data={"gns3": ["configs/*.txt", "schemas/*.json"]},
platforms="any",
python_requires=">=3.4",
setup_requires=["setuptools>=17.1"],
classifiers=[
"Development Status :: 5 - Production/Stable",
@@ -96,6 +97,9 @@ setup(
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: Implementation :: CPython",
],
)

View File

@@ -311,7 +311,7 @@ def test_callbackConnect_major_version_invalid(http_client):
def test_callbackConnect_minor_version_invalid(http_client):
new_version = "{}.{}.{}".format(__version_info__[0], __version_info__[1], __version_info__[2] + 1)
new_version = "{}.{}.{}".format(__version_info__[0], __version_info__[1] + 1, 0)
params = {
"local": True,
"version": new_version
@@ -319,14 +319,23 @@ def test_callbackConnect_minor_version_invalid(http_client):
mock = unittest.mock.MagicMock()
http_client._query_waiting_connections.append((None, mock))
# Stable release
if __version_info__[3] == 0:
http_client._callbackConnect(params)
assert http_client._connected is False
mock.assert_called_with({"message": "Client version {} is not the same as server (controller) version {}".format(__version__, new_version)}, error=True, server=None)
else:
http_client._callbackConnect(params)
assert http_client._connected is True
http_client._callbackConnect(params)
assert http_client._connected is False
mock.assert_called_with({"message": "Client version {} is not the same as server (controller) version {}".format(__version__, new_version)}, error=True, server=None)
def test_callbackConnect_patch_version(http_client):
new_version = "{}.{}.{}.{}".format(__version_info__[0], __version_info__[1], __version_info__[2], 42)
params = {
"local": True,
"version": new_version
}
mock = unittest.mock.MagicMock()
http_client._query_waiting_connections.append((None, mock))
http_client._callbackConnect(params)
assert http_client._connected is True
def test_callbackConnect_non_gns3_server(http_client):

View File

@@ -65,6 +65,7 @@ def link(devices, controller, project):
{"node_id": devices[0].node_id(), "adapter_number": 0, "port_number": 0},
{"node_id": devices[1].node_id(), "adapter_number": 0, "port_number": 0}
],
"link_style": {},
"filters": {},
}
@@ -89,6 +90,7 @@ def test_create_link(devices, project, controller):
{"node_id": devices[0].node_id(), "adapter_number": 0, "port_number": 0},
{"node_id": devices[1].node_id(), "adapter_number": 0, "port_number": 0},
],
"link_style": {},
"filters": {},
}
@@ -102,9 +104,6 @@ def test_create_link(devices, project, controller):
assert link._link_id is not None
assert not devices[0].ports()[0].isFree()
assert link in devices[0].links()
assert link in devices[1].links()
assert link.getNodePort(devices[0]) == devices[0].ports()[0]
assert link.getNodePort(devices[1]) == devices[1].ports()[0]

View File

@@ -1,4 +1,4 @@
-rrequirements.txt
PyQt5==5.12.3 # pyup: ignore
pywin32>=223 # pyup: ignore
PyQt5==5.15.7 # pyup: ignore
pywin32==305 # pyup: ignore