Compare commits

...

168 Commits

Author SHA1 Message Date
grossmj
812aedebe3 Release v2.2.47 2024-05-15 12:14:48 +07:00
grossmj
2f0d2063cf Remove maximum size for capture dialog. Ref #3576 2024-05-10 12:58:35 +07:00
Jeremy Grossmann
ce0515f0ae Merge pull request #3582 from GNS3/drop-python3.7
Drop Python 3.7
2024-05-09 19:09:14 +07:00
grossmj
bc10c69a2d Change sentry-sdk version 2024-05-09 19:03:08 +07:00
grossmj
3707758388 Upgrade aiohttp, sentry-sdk and truststore 2024-05-09 18:58:38 +07:00
grossmj
8aaefac91b Upgrade jsonschema and aiohttp 2024-05-09 18:37:41 +07:00
grossmj
19157ab49d Drop Python 3.7 2024-05-09 18:23:59 +07:00
grossmj
38b98cd883 Remove dev requirements for Python 3.6 2024-05-09 18:05:08 +07:00
Jeremy Grossmann
e9419924c5 Merge pull request #3580 from GNS3/feature/nat-symbols
NAT symbols
2024-04-22 19:00:15 +07:00
grossmj
cf3c5c09fa Add NAT symbols 2024-04-22 18:51:29 +07:00
grossmj
9e89cf5ad5 Only show log message if event has "message" 2024-03-07 17:18:30 +01:00
grossmj
131ef09b55 Development on 2.2.47.dev1 2024-03-05 01:07:17 +08:00
grossmj
2df18ee04e Upgrade sentry-sdk to version 1.40.6 2024-03-05 00:45:26 +08:00
grossmj
31711a9d4e Release v2.2.46 2024-02-26 16:55:13 +08:00
grossmj
fd2e236927 Add GNS3 console command "env" to show what environment variables are used. Ref https://github.com/GNS3/gns3-server/issues/2306 2024-02-14 19:29:07 +08:00
grossmj
21409a899d Add CTRL+C shortcut to copy status bar message. Ref #3561 2024-02-14 17:33:15 +08:00
grossmj
25be9e7ec7 Key modifier (ALT) to ignore snap to grid. Fixes #3538 2024-02-12 17:32:22 +11:00
grossmj
1260c2bc2d Increase timeout to 5s for status bar messages.
The coordinates message has no timeout and can be reset when clicking on the scene. Ref #3561
2024-02-12 16:32:17 +11:00
grossmj
1441e38876 Add reset GUI state feature. Ref #3549 2024-02-12 16:16:07 +11:00
grossmj
3c8cff20b7 Possible fix for hiding Windows terminal. Ref #3290 2024-02-12 15:05:49 +11:00
Jeremy Grossmann
f831d71c3f Merge pull request #3566 from GNS3/feature/drop-python-3.6
Drop support for Python 3.6
2024-02-09 17:02:27 +11:00
grossmj
f71b6dcda1 Change runtime checks for Python version 2024-02-09 16:49:59 +11:00
grossmj
62289e7be3 Drop support for Python 3.6 2024-02-09 16:28:23 +11:00
grossmj
7447e9b7d4 Merge branch 'master' into 2.2 2024-01-27 17:04:54 +11:00
grossmj
c5961f400e Upgrade sentry-sdk, psutil and distro dependencies 2024-01-27 17:04:02 +11:00
grossmj
6ca61905b2 Development on 2.2.46.dev1 2024-01-14 23:45:35 +11:00
Jeremy Grossmann
130e91da76 Merge pull request #3559 from GNS3/2.2
Release v2.2.45
2024-01-14 22:02:24 +11:00
grossmj
bbc5b3e4ac Release v2.2.45 2024-01-12 21:38:26 +11:00
grossmj
2a72ad5e0b Add missing console_type values in appliance_v8.json. Ref https://github.com/GNS3/gns3-registry/issues/849 2024-01-12 16:39:06 +11:00
grossmj
88ed9407b9 Move PATH debug message 2024-01-12 15:46:20 +11:00
grossmj
18be274fed Handle moved project notifications on controller stream 2024-01-12 13:22:48 +11:00
grossmj
fefda50378 Add debug for PATH env variable 2024-01-11 22:57:14 +11:00
grossmj
3df374e784 Add custom executable paths on Windows 2023-12-05 21:24:41 +10:00
grossmj
04fb449b44 Add --suppressApplicationTitle for Windows terminal. Fixes https://github.com/GNS3/gns3-gui/issues/3544 2023-11-28 11:23:20 +10:00
grossmj
fc54b76ee1 Upgrade sentry-sdk and aiohttp 2023-11-22 10:32:51 +10:00
grossmj
59b284e18b Development in 2.2.45.dev3 2023-11-07 19:11:57 +10:00
Jeremy Grossmann
1d4492c911 Merge pull request #3537 from GNS3/release-v2.2.44.1
Release v2.2.44.1
2023-11-07 18:34:54 +10:00
grossmj
9d8b6a172e Release v2.2.44.1 2023-11-07 14:59:28 +10:00
grossmj
8c3ef7a968 Bump version to v2.2.45.dev2 2023-11-07 12:21:39 +10:00
Jeremy Grossmann
947733aada Merge pull request #3536 from GNS3/release-v2.2.44
Release v2.2.44
2023-11-06 17:01:17 +10:00
grossmj
a199fef03b Development on 2.2.45.dev1 2023-11-06 17:00:17 +10:00
grossmj
29b851207b Release v2.2.44 2023-11-06 16:02:23 +10:00
grossmj
ca5557e579 Upgrade sentry-sdk 2023-11-06 15:44:30 +10:00
grossmj
7331ae29ef Fix timeout issue when creating Qemu disk image. Fixes https://github.com/GNS3/gns3-server/issues/2313 2023-11-05 15:41:46 +10:00
grossmj
3a7e06e14b Revert to subprocess.Popen since subprocess.call is the old API 2023-11-03 14:43:25 +10:00
grossmj
0a81af8248 Merge remote-tracking branch 'origin/2.2' into 2.2 2023-10-31 15:53:13 +10:00
grossmj
a882956ec9 Merge branch 'master' into 2.2 2023-10-31 15:52:45 +10:00
Jeremy Grossmann
9f4361d66f Merge pull request #3534 from GNS3/command-variables
Refactor command variables
2023-10-31 15:44:23 +10:00
grossmj
d10c3c7308 Fix tests 2023-10-31 15:40:50 +10:00
Jeremy Grossmann
0cd7d7e4c2 Merge branch '2.2' into command-variables 2023-10-31 15:07:49 +10:00
grossmj
8f3f72ff54 Fix tests 2023-10-31 15:07:35 +10:00
Jeremy Grossmann
7fbc0befa1 Merge branch '2.2' into command-variables 2023-10-31 14:51:53 +10:00
grossmj
9d9668442e Use Python 3.8 in appveyor.yml 2023-10-31 14:48:08 +10:00
grossmj
932083be88 Update custom command help and protect against double quote in project name 2023-10-31 14:43:50 +10:00
grossmj
82e7c151c4 Refactor command variables support 2023-10-31 12:33:52 +10:00
Jeremy Grossmann
7e5c363bc3 Merge pull request #3533 from GNS3/fix/2306
Allow local server to get $PATH environment variable
2023-10-30 14:06:17 +10:00
grossmj
15d029a7fb Pass os.environ in Popen() 2023-10-30 14:03:10 +10:00
Jeremy Grossmann
9087ba8f5a Merge pull request #3512 from magister990/style_edit_width_and_height
Add the ability to edit width and hight in the style edit dialog.
2023-10-24 18:25:58 +10:00
grossmj
3d89d6e6cc Fix issue with line item 2023-10-24 18:01:49 +10:00
grossmj
91bae81300 Merge branch '2.2' into style_edit_width_and_height 2023-10-24 17:40:35 +10:00
Jeremy Grossmann
7de5bf6bd5 Merge pull request #3516 from ventaquil/feature/add-qemu-igb-nic
Add Qemu IGB network device
2023-10-24 17:15:21 +10:00
Jeremy Grossmann
9de238619a Merge branch '2.2' into feature/add-qemu-igb-nic 2023-10-24 17:14:30 +10:00
grossmj
ed88466d63 Upgrade to actions/checkout@v3 and actions/setup-python@v3 2023-10-23 16:17:31 +10:00
grossmj
478b793b04 Merge branch 'master' into 2.2 2023-10-18 17:46:45 +10:00
grossmj
841c29e6f6 Upgrade sentry and psutil dependencies 2023-10-18 14:53:25 +10:00
grossmj
f9d96051f5 Downgrade to PyQt5 v5.15.9 2023-10-18 14:33:27 +10:00
grossmj
607e201674 Fix packaging issue on macOS 2023-10-18 14:29:53 +10:00
grossmj
18950ca64f Upgrade to PyQt5 v5.15.10 2023-10-18 13:24:45 +10:00
grossmj
c0b5f39c4c Add Python 3.12 support. Fixes https://github.com/GNS3/gns3-server/issues/2273 2023-10-09 16:54:47 +10:00
grossmj
3e717999ca Add vendor_logo_url in appliance schemas. Ref https://github.com/GNS3/gns3-registry/pull/825 2023-10-09 16:52:52 +10:00
Konrad Goławski
800d14363d Add Qemu IGB network device 2023-10-02 14:35:09 +02:00
Alex Scott
2dd9d61c57 Add the ability to edit width and hight in the style edit dialog. 2023-09-27 17:08:33 -05:00
grossmj
10afb5a8de Revert "Revert "Install importlib-resources only with Python < '3.9'. Ref #2147""
This reverts commit 3413afe952.
2023-09-23 20:50:39 +10:00
grossmj
3413afe952 Revert "Install importlib-resources only with Python < '3.9'. Ref #2147"
This reverts commit 7222da9512.
2023-09-23 14:47:56 +10:00
grossmj
7222da9512 Install importlib-resources only with Python < '3.9'. Ref #2147 2023-09-23 14:33:15 +10:00
grossmj
dbe8df5a37 Development on 2.2.44.dev1 2023-09-19 21:08:24 +07:00
Jeremy Grossmann
a9890265b9 Merge pull request #3509 from GNS3/release-v2.2.43
Release v2.2.43
2023-09-19 21:04:43 +07:00
grossmj
97b777ceea Release v2.2.43 2023-09-19 20:16:52 +07:00
grossmj
c06e534935 Merge branch 'master' into 2.2 2023-09-19 20:08:11 +07:00
grossmj
025276f8a7 Upgrade sentry-sdk and truststore 2023-09-19 20:07:57 +07:00
grossmj
6777961d29 Add KiTTY to preconfigured telnet consoles. Fixes #3507 2023-09-18 20:23:09 +07:00
grossmj
7f6cace0d5 Fix generic icon in Wayland. Ref #3501 2023-09-15 16:08:45 +07:00
Jeremy Grossmann
1a739c0c37 Merge pull request #3490 from GNS3/appliance-v8-support
Support for appliance version 8 format
2023-08-16 14:18:59 +10:00
grossmj
6d855045ef Show installation instructions when available and fix regression when installing Docker appliance. 2023-08-16 14:11:55 +10:00
grossmj
fef734bbbe Finalize appliance v8 support and add tests. 2023-08-16 00:30:02 +10:00
grossmj
b079443735 Merge remote-tracking branch 'origin/appliance-v8-support' into appliance-v8-support 2023-08-14 12:01:18 +10:00
grossmj
4a32ae9736 Drop "kvm" field. 2023-08-14 12:01:09 +10:00
Jeremy Grossmann
9793d00131 Merge branch '2.2' into appliance-v8-support 2023-08-13 12:21:18 +10:00
grossmj
2b7840279a Downgrade jsonschema 2023-08-12 17:51:24 +10:00
grossmj
9243083321 Upgrade dependencies 2023-08-12 17:47:48 +10:00
Jeremy Grossmann
3a8b3e5c4a Merge pull request #3506 from GNS3/fix/3505
Use importlib instead of pkg_resources
2023-08-12 17:26:51 +10:00
grossmj
e2168a3c81 Use importlib instead of pkg_resources 2023-08-12 17:20:33 +10:00
grossmj
01deb01e6a Upgrade to PyQt 5.15.9 and pywin32 2023-08-11 18:13:41 +10:00
grossmj
1133ee6e1b Development on v2.2.43.dev1 2023-08-09 22:15:15 +10:00
Jeremy Grossmann
7512ffec64 Merge pull request #3503 from GNS3/release-v2.2.42
Release v2.2.42
2023-08-09 22:08:18 +10:00
grossmj
3527e5551c Release v2.2.42 2023-08-09 21:11:57 +10:00
grossmj
72960f8f2b Enable system certificate store later in the code and bump version to 2.2.42.dev4 2023-08-08 17:22:27 +10:00
grossmj
8abb502c72 Use the system's certificate store for SSL connections 2023-08-07 21:33:25 +10:00
grossmj
08c729e83a Upgrade dependencies 2023-08-06 20:39:27 +10:00
grossmj
aac004bd2f Use certifi to get SSL root certificates 2023-08-06 20:37:10 +10:00
grossmj
70677d8f18 Bump version to 2.2.42.dev3 2023-08-06 18:14:22 +10:00
Jeremy Grossmann
fba1ff4208 Merge pull request #3502 from GNS3/use-bundled-cacert
Use bundled cacert file for frozen app
2023-08-05 22:37:08 +10:00
grossmj
e4edbefc23 Use bundled cacert file on Windows and macOS 2023-08-05 22:21:08 +10:00
grossmj
d93f9afe74 Bump version to 2.2.42.dev2 2023-08-05 20:16:30 +10:00
grossmj
162d197e36 Give a node some time to start before opening the console (for console auto start). Fixes #3474 2023-08-02 11:08:58 +10:00
grossmj
5c21dd8a2f Merge branch 'master' into 2.2 2023-08-01 15:50:51 +10:00
Jeremy Grossmann
aa9b9d3b0b Merge pull request #3364 from AbdelbakiBoukerche/feature_rounded_rectangle
Rounded Rectangle
2023-08-01 15:46:32 +10:00
grossmj
eae9eec15b Support for horizontal and vertical corner radius 2023-08-01 15:34:30 +10:00
Jeremy Grossmann
a3bf832721 Merge branch 'master' into feature_rounded_rectangle 2023-07-31 18:34:50 +10:00
Jeremy Grossmann
67890d74d9 Merge pull request #3500 from GNS3/fix/3449
Support for gnome-terminal tabs to be opened in the same window
2023-07-31 18:30:22 +10:00
grossmj
ab4325f951 Remove warning to set open new terminals in tabs for gnome-terminal 2023-07-31 12:32:49 +10:00
grossmj
2a947b9cc5 Add comments for gnome-terminal special case 2023-07-31 01:44:39 +10:00
grossmj
601c082288 Use Mate Terminal by default if installed on Debian, Ubuntu and Linux Mint. 2023-07-31 01:40:20 +10:00
grossmj
7701d57bd0 Check that gnome-terminal is configured to open new terminals in tabs. 2023-07-31 01:34:59 +10:00
grossmj
f0b4148a20 Support for gnome-terminal tabs to be opened in the same window. 2023-07-30 22:15:38 +10:00
grossmj
2fdcbafbc1 Revert "Support for Python 3.12"
This reverts commit 5d82cea935.
2023-07-30 17:50:21 +10:00
grossmj
5d82cea935 Support for Python 3.12 2023-07-30 17:48:30 +10:00
grossmj
b0e3e93c41 Remove import urllib3 and let sentry_sdk import and patch it. Fixes https://github.com/GNS3/gns3-gui/issues/3498 2023-07-30 17:42:52 +10:00
grossmj
535f53737d Merge remote-tracking branch 'origin/master' into 2.2 2023-07-23 12:35:21 +10:00
Jeremy Grossmann
354f3eecec Merge pull request #3497 from kevinchevreuil/master
Add import sys in sudo.py
2023-07-23 12:04:29 +10:00
Kevin Chevreuil - Kaisen
35a6a5c8c7 Add import sys in sudo.py 2023-07-22 23:12:34 +02:00
grossmj
23cba0a28d Development on 2.2.42.dev1 2023-07-12 18:26:26 +10:00
Jeremy Grossmann
9c3d7bc95a Merge pull request #3495 from GNS3/release-v2.2.41
Release v2.2.41
2023-07-12 18:24:05 +10:00
grossmj
cf2802b15a Release v2.2.41 2023-07-12 17:07:39 +10:00
grossmj
b162c55078 Merge remote-tracking branch 'origin/master' into 2.2 2023-07-12 17:03:50 +10:00
grossmj
bb42b0ed0b Change chown in authorize_ubridge.py 2023-07-12 13:28:24 +10:00
grossmj
e108b5194d Bump version to 2.2.41.dev3 2023-07-12 13:26:22 +10:00
grossmj
e9ef8735be Use a small executable to set the correct permissions on uBridge on macOS 2023-07-12 12:40:53 +10:00
grossmj
e8e90bb16a Add debugging for AuthorizationExecuteWithPrivileges 2023-07-11 21:23:15 +10:00
grossmj
49f77930f4 Use alternative method to setuid uBridge on macOS 2023-07-11 18:27:19 +10:00
grossmj
a58451a552 Update to support template_type & template_properties 2023-07-11 17:38:00 +10:00
grossmj
0a43b9e6e9 Fix tests 2023-07-09 20:18:22 +10:00
Jeremy Grossmann
4f32619ed8 Merge pull request #3493 from GNS3/remove-analytics
Remove sending usage stats
2023-07-09 19:04:40 +10:00
grossmj
20740748c1 Remove sending stats to GA 2023-07-09 18:55:31 +10:00
grossmj
8a5ab6b374 Update schema for appliance version 8 2023-07-09 14:10:15 +10:00
grossmj
e11ce27f7b Catch urllib3 exceptions when sending crash report. Ref https://github.com/GNS3/gns3-gui/issues/3483 2023-07-06 17:16:05 +10:00
grossmj
4b7cf4e553 Support legacy "idlepc" field 2023-07-03 19:12:44 +10:00
grossmj
b72358461c Add support for appliance version 8 format 2023-06-25 16:54:12 +09:30
Jeremy Grossmann
2987bcf91a Merge pull request #3489 from GNS3/backport-uefi-boot-mode
Backport UEFI boot mode support for Qemu VMs
2023-06-23 11:31:49 +09:30
grossmj
5e97bc0f86 Backport UEFI boot mode support for Qemu VMs 2023-06-23 11:18:25 +09:30
grossmj
e3a3de5df7 Add debug for dropEvent. Ref https://github.com/GNS3/gns3-server/issues/2242 2023-06-20 14:49:25 +09:30
grossmj
e1693ce113 Developement on v2.2.41.dev2 2023-06-10 21:40:39 +09:30
Jeremy Grossmann
522091d219 Merge pull request #3481 from GNS3/release-v2.2.40.1
Release v2.2.40.1
2023-06-10 21:36:42 +09:30
grossmj
1446748934 Release v2.2.40.1 2023-06-10 20:06:40 +09:30
grossmj
3f0ce380e8 Merge branch 'master' into 2.2 2023-06-10 17:22:50 +09:30
grossmj
bd71383354 Development on v2.2.41.dev1 2023-06-06 12:43:20 +09:30
Jeremy Grossmann
9649895378 Merge pull request #3479 from GNS3/release-v2.2.40
Release v2.2.40
2023-06-06 12:41:46 +09:30
grossmj
8579ffa20a Release v2.2.40 2023-06-06 10:23:42 +09:30
grossmj
3206743329 Merge branch 'master' into 2.2 2023-06-06 10:19:45 +09:30
grossmj
29c87b6e96 Change log messages for Websocket errors 2023-06-03 21:05:26 +09:30
grossmj
93b2721d6a Do not proceed if an appliance symbol cannot be downloaded. Ref #3466 2023-05-23 16:31:51 +08:00
grossmj
1ff369683f Revert "Fix open IPv6 address for HTTP consoles. Fixes #3448"
This reverts commit 7c56a2467c.
2023-05-22 19:39:03 +08:00
grossmj
7c56a2467c Fix open IPv6 address for HTTP consoles. Fixes #3448 2023-05-22 16:46:57 +08:00
grossmj
59ef34c17d Delete a node or link from topology summary view using Delete key. Ref #3445 2023-05-14 21:19:19 +08:00
grossmj
d1fae54049 Fix "Start the capture visualization program" checkbox works only one (first) time for a given link. Fixes #3442 2023-05-14 20:38:34 +08:00
grossmj
49bd61f769 Let the selected link style applied when editing a link. Fixes #3460 2023-05-14 15:33:08 +08:00
grossmj
9a4faddd10 Fix hovered color shown in style editing dialog. Fixes #3460 2023-05-14 14:52:27 +08:00
Jeremy Grossmann
7654681a94 Merge pull request #3468 from GNS3/release-v2.2.39
Release v2.2.39
2023-05-08 20:28:43 +08:00
grossmj
9bfecde957 Development on v2.2.40.dev1 2023-05-08 20:26:40 +08:00
grossmj
705cbf8bb9 Release v2.2.39 2023-05-08 19:17:02 +08:00
Jeremy Grossmann
ab6e0ce496 Merge pull request #3461 from GNS3/fix/3441
Fix nodes are not snapped to the grid at the moment of creation
2023-05-08 17:13:20 +08:00
grossmj
8042c9eb6f Fix tests 2023-05-08 17:08:10 +08:00
grossmj
ad3c8a09db Fix nodes are not snapped to the grid at the moment of creation 2023-04-23 05:18:02 -10:00
grossmj
ea4a7f201e Upgrade sentry-sdk 2023-03-23 21:41:50 -10:00
grossmj
28c82b8718 Upgrade distro and aiohttp dependencies 2023-03-01 18:03:28 +10:00
grossmj
6a4dd59e81 Development on 2.2.39.dev1 2023-02-28 17:09:39 +10:00
Jeremy Grossmann
7418c190a8 Merge pull request #3447 from GNS3/2.2
Release v2.2.38
2023-02-28 14:22:10 +08:00
Abdelbaki Boukerche
181bf3f360 Rounded Rectangle 2022-08-04 18:20:54 +01:00
91 changed files with 2811 additions and 670 deletions

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Build and run Docker image
run: |
docker build -t gns3-gui-test .

View File

@@ -1,5 +1,93 @@
# Change Log
## 2.2.47 15/05/2024
* Remove maximum size for capture dialog. Ref #3576
* Change sentry-sdk version
* Upgrade aiohttp, sentry-sdk and truststore
* Upgrade jsonschema and aiohttp
* Drop Python 3.7
* Remove dev requirements for Python 3.6
* Add NAT symbols
* Only show log message if event has "message"
## 2.2.46 26/02/2024
* Add GNS3 console command "env" to show what environment variables are used. Ref https://github.com/GNS3/gns3-server/issues/2306
* Add CTRL+C shortcut to copy status bar message. Ref #3561
* Key modifier (ALT) to ignore snap to grid. Fixes #3538
* Increase timeout to 5s for status bar messages. The coordinates message has no timeout and can be reset when clicking on the scene. Ref #3561
* Add reset GUI state feature. Ref #3549
* Fix for hiding Windows terminal. Ref #3290
* Drop support for Python 3.6
* Upgrade sentry-sdk, psutil and distro dependencies
## 2.2.45 12/01/2024
* Add missing console_type values in appliance_v8.json. Ref https://github.com/GNS3/gns3-registry/issues/849
* Handle moved project notifications on controller stream
* Add debug for PATH env variable
* Add custom executable paths on Windows
* Add --suppressApplicationTitle for Windows terminal. Fixes https://github.com/GNS3/gns3-gui/issues/3544
* Upgrade sentry-sdk and aiohttp
## 2.2.44.1 07/11/2023
* No changes
## 2.2.44 06/11/2023
* Fix timeout issue when creating Qemu disk image. Fixes https://github.com/GNS3/gns3-server/issues/2313
* Refactor command variables support
* Add vendor_logo_url in appliance schemas. Ref https://github.com/GNS3/gns3-registry/pull/825
* Add Qemu IGB network device
* Add the ability to edit width and height in the style edit dialog.
## 2.2.43 19/09/2023
* Add KiTTY to preconfigured telnet consoles. Fixes #3507
* Fix generic icon in Wayland. Ref #3501
* Support for appliance format version 8.
* Use importlib instead of pkg_resources
* Upgrade to PyQt 5.15.9 and pywin32
* Add support for appliance version 8 format
## 2.2.42 09/08/2023
* Use the system's certificate store for SSL connections
* Give a node some time to start before opening the console (for console auto start). Fixes #3474
* Use Mate Terminal by default if installed on Debian, Ubuntu and Linux Mint.
* Support for gnome-terminal tabs to be opened in the same window.
* Remove import urllib3 and let sentry_sdk import and patch it. Fixes https://github.com/GNS3/gns3-gui/issues/3498
* Add import sys in sudo.py
* Rounded Rectangle support
## 2.2.41 12/07/2023
* Use alternative method to set the correct permissions for uBridge on macOS
* Remove sending stats to GA
* Catch urllib3 exceptions when sending crash report. Ref https://github.com/GNS3/gns3-gui/issues/3483
* Backport UEFI boot mode support for Qemu VMs
* Add debug for dropEvent. Ref https://github.com/GNS3/gns3-server/issues/2242
## 2.2.40.1 10/06/2023
* No changes
## 2.2.40 06/06/2023
* Change log messages for Websocket errors
* Do not proceed if an appliance symbol cannot be downloaded. Ref #3466
* Delete a node or link from topology summary view using Delete key. Ref #3445
* Fix "Start the capture visualization program" checkbox works only one (first) time for a given link. Fixes #3442
* Let the selected link style applied when editing a link. Fixes #3460
* Fix hovered color shown in style editing dialog. Fixes #3460
## 2.2.39 08/05/2023
* Fix nodes are not snapped to the grid at the moment of creation
* Upgrade distro and aiohttp dependencies
## 2.2.38 28/02/2023
* Add long description content type in setup.py

View File

@@ -5,7 +5,7 @@ image: Visual Studio 2022
platform: x64
environment:
PYTHON: "C:\\Python37-x64"
PYTHON: "C:\\Python38-x64"
DISTUTILS_USE_SDK: "1"
install:

View File

@@ -1,6 +1,5 @@
-rrequirements.txt
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
pytest==7.2.0
flake8==5.0.4
pytest-timeout==2.1.0

View File

@@ -46,6 +46,9 @@ class Application(QtWidgets.QApplication):
super().__init__(argv)
# this is tell Wayland what is the name of the desktop file (gns3.desktop)
self.setDesktopFileName("gns3")
# this info is necessary for QSettings
self.setOrganizationName("GNS3")
self.setOrganizationDomain("gns3.net")

View File

@@ -19,6 +19,7 @@
Handles commands typed in the GNS3 console.
"""
import os
import sys
import cmd
import struct
@@ -34,6 +35,14 @@ log = logging.getLogger(__name__)
class ConsoleCmd(cmd.Cmd):
def do_env(self, args):
"""
Show the environment variables used by GNS3.
"""
for key, val in os.environ.items():
print("{}={}".format(key, val))
def do_version(self, args):
"""
Show the version of GNS3 and its dependencies.

View File

@@ -449,7 +449,7 @@ class Controller(QtCore.QObject):
@qslot
def _websocket_error(self, error):
if self._notification_stream:
log.error("Websocket notification stream error: {}".format(self._notification_stream.errorString()))
log.error("Websocket controller notification stream error: {}".format(self._notification_stream.errorString()))
self._notification_stream = None
self._startListenNotifications()
@@ -479,11 +479,21 @@ class Controller(QtCore.QObject):
elif result["action"] == "compute.created" or result["action"] == "compute.updated":
from .compute_manager import ComputeManager
ComputeManager.instance().computeDataReceivedCallback(result["event"])
elif result["action"] == "log.error":
log.error(result["event"]["message"])
elif result["action"] == "log.warning":
log.warning(result["event"]["message"])
elif result["action"] == "log.info":
log.info(result["event"]["message"], extra={"show": True})
elif result["action"] == "project.closed":
from .topology import Topology
project = Topology.instance().project()
if project and project.id() == result["event"]["project_id"]:
Topology.instance().setProject(None)
elif result["action"] == "project.updated":
from .topology import Topology
project = Topology.instance().project()
if project and project.id() == result["event"]["project_id"]:
project.projectUpdatedCallback(result["event"])
elif result["action"] == "log.error" and result["event"].get("message"):
log.error(result["event"].get("message"))
elif result["action"] == "log.warning" and result["event"].get("message"):
log.warning(result["event"].get("message"))
elif result["action"] == "log.info" and result["event"].get("message"):
log.info(result["event"].get("message"), extra={"show": True})
elif result["action"] == "ping":
pass

View File

@@ -15,12 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import os
import platform
import struct
import distro
try:
import sentry_sdk
from sentry_sdk.integrations.logging import LoggingIntegration
@@ -29,7 +23,12 @@ except ImportError:
# Sentry SDK is not installed with deb package in order to simplify packaging
SENTRY_SDK_AVAILABLE = False
from .utils.get_resource import get_resource
import sys
import os
import platform
import struct
import distro
from .version import __version__, __version_info__
import logging
@@ -51,7 +50,7 @@ class CrashReport:
Report crash to a third party service
"""
DSN = "https://e1c08ac6a2f44c1f96055affc49b2e72@o19455.ingest.sentry.io/38506"
DSN = "https://235ecbc961abe34327a4a397d8ce427a@o19455.ingest.us.sentry.io/38506"
_instance = None
def __init__(self):
@@ -64,22 +63,16 @@ class CrashReport:
self._sentry_initialized = False
if SENTRY_SDK_AVAILABLE:
cacert = None
if hasattr(sys, "frozen"):
cacert_resource = get_resource("cacert.pem")
if cacert_resource is not None and os.path.isfile(cacert_resource):
cacert = cacert_resource
else:
log.error("The SSL certificate bundle file '{}' could not be found".format(cacert_resource))
# Don't send log records as events.
sentry_logging = LoggingIntegration(level=logging.INFO, event_level=None)
sentry_sdk.init(dsn=CrashReport.DSN,
release=__version__,
ca_certs=cacert,
default_integrations=False,
integrations=[sentry_logging])
try:
sentry_sdk.init(dsn=CrashReport.DSN,
release=__version__,
default_integrations=False,
integrations=[sentry_logging])
except Exception as e:
log.error("Crash report could not be sent: {}".format(e))
return
tags = {
"os:name": platform.system(),

View File

@@ -94,9 +94,11 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
self.setWindowTitle("Install {} appliance".format(self._appliance["name"]))
# add a custom button to show appliance information
self.setButtonText(QtWidgets.QWizard.CustomButton1, "&Appliance info")
self.setOption(QtWidgets.QWizard.HaveCustomButton1, True)
self.customButtonClicked.connect(self._showApplianceInfoSlot)
if self._appliance["registry_version"] < 8:
# FIXME: show appliance info for v8
self.setButtonText(QtWidgets.QWizard.CustomButton1, "&Appliance info")
self.setOption(QtWidgets.QWizard.HaveCustomButton1, True)
self.customButtonClicked.connect(self._showApplianceInfoSlot)
# customize the server selection
self.uiRemoteRadioButton.toggled.connect(self._remoteServerToggledSlot)
@@ -144,18 +146,9 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if self.page(page_id) == self.uiServerWizardPage:
Controller.instance().getSymbols(self._getSymbolsCallback)
if "qemu" in self._appliance:
emulator_type = "qemu"
elif "iou" in self._appliance:
emulator_type = "iou"
elif "docker" in self._appliance:
emulator_type = "docker"
elif "dynamips" in self._appliance:
emulator_type = "dynamips"
else:
QtWidgets.QMessageBox.warning(self, "Appliance", "Could not determine the emulator type")
template_type = self._appliance.template_type()
if not template_type:
raise ApplianceError("No template type found for appliance {}".format(self._appliance["name"]))
is_mac = ComputeManager.instance().localPlatform().startswith("darwin")
is_win = ComputeManager.instance().localPlatform().startswith("win")
@@ -173,11 +166,11 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if ComputeManager.instance().localPlatform() is None:
self.uiLocalRadioButton.setEnabled(False)
elif is_mac or is_win:
if emulator_type == "qemu":
if template_type == "qemu":
# disallow usage of the local server because Qemu has issues on OSX and Windows
if not LocalConfig.instance().experimental():
self.uiLocalRadioButton.setEnabled(False)
elif emulator_type != "dynamips":
elif template_type != "dynamips":
self.uiLocalRadioButton.setEnabled(False)
if ComputeManager.instance().vmCompute():
@@ -195,27 +188,55 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
elif self.page(page_id) == self.uiFilesWizardPage:
if Controller.instance().isRemote() or self._compute_id != "local":
self._registry.getRemoteImageList(self._appliance.emulator(), self._compute_id)
self._registry.getRemoteImageList(self._appliance.template_type(), self._compute_id)
else:
self.images_changed_signal.emit()
elif self.page(page_id) == self.uiQemuWizardPage:
if self._appliance['qemu'].get('kvm', 'require') == 'require':
if self._appliance.template_properties().get('kvm', 'require') == 'require':
self._server_check = False
Qemu.instance().getQemuCapabilitiesFromServer(self._compute_id, qpartial(self._qemuServerCapabilitiesCallback))
else:
self._server_check = True
Qemu.instance().getQemuBinariesFromServer(self._compute_id, qpartial(self._getQemuBinariesFromServerCallback), [self._appliance["qemu"]["arch"]])
if self._appliance["registry_version"] >= 8:
qemu_platform = self._appliance.template_properties()["platform"]
else:
qemu_platform = self._appliance.template_properties()["arch"]
Qemu.instance().getQemuBinariesFromServer(self._compute_id, qpartial(self._getQemuBinariesFromServerCallback), [qemu_platform])
elif self.page(page_id) == self.uiInstructionsPage:
installation_instructions = self._appliance.get("installation_instructions", "No installation instructions available")
self.uiInstructionsTextEdit.setText(installation_instructions.strip())
elif self.page(page_id) == self.uiUsageWizardPage:
self.uiUsageTextEdit.setText("The template will be available in the {} category.\n\n{}".format(self._appliance["category"].replace("_", " "), self._appliance.get("usage", "")))
# TODO: allow taking these info fields at the version level in v8
category = self._appliance["category"].replace("_", " ")
usage = self._appliance.get("usage", "No usage information available")
if self._appliance["registry_version"] >= 8:
default_username = self._appliance.get("default_username")
default_password = self._appliance.get("default_password")
if default_username and default_password:
usage += "\n\nDefault username: {}\nDefault password: {}".format(default_username, default_password)
usage_info = """
The template will be available in the {} category.
Usage: {}
""".format(category, usage)
self.uiUsageTextEdit.setText(usage_info.strip())
def _qemuServerCapabilitiesCallback(self, result, error=None, *args, **kwargs):
"""
Check if the server supports KVM or not
"""
if error is None and "kvm" in result and self._appliance["qemu"]["arch"] in result["kvm"]:
if self._appliance["registry_version"] >= 8:
qemu_platform = self._appliance.template_properties()["platform"]
else:
qemu_platform = self._appliance.template_properties()["arch"]
if error is None and "kvm" in result and qemu_platform in result["kvm"]:
self._server_check = True
else:
if error:
@@ -236,7 +257,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
log.error("Error while uploading image '{}': {}".format(image_path, result["message"]))
else:
log.info("Image '{}' has been successfully uploaded".format(image_path))
self._registry.getRemoteImageList(self._appliance.emulator(), self._compute_id)
self._registry.getRemoteImageList(self._appliance.template_type(), self._compute_id)
def _showApplianceInfoSlot(self):
"""
@@ -407,7 +428,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
for version in self._appliance["versions"]:
for image in version["images"].values():
img = self._registry.search_image_file(self._appliance.emulator(),
img = self._registry.search_image_file(self._appliance.template_type(),
image["filename"],
image.get("md5sum"),
image.get("filesize"),
@@ -519,7 +540,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if len(path) == 0:
return
image = Image(self._appliance.emulator(), path, filename=disk["filename"])
image = Image(self._appliance.template_type(), path, filename=disk["filename"])
try:
if "md5sum" in disk and image.md5sum != disk["md5sum"]:
reply = QtWidgets.QMessageBox.question(self, "Add appliance",
@@ -554,7 +575,11 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if self.uiQemuListComboBox.count() == 1:
self.next()
else:
i = self.uiQemuListComboBox.findData(self._appliance["qemu"]["arch"], flags=QtCore.Qt.MatchEndsWith)
if self._appliance["registry_version"] >= 8:
qemu_platform = self._appliance.template_properties()["platform"]
else:
qemu_platform = self._appliance.template_properties()["arch"]
i = self.uiQemuListComboBox.findData(qemu_platform, flags=QtCore.Qt.MatchEndsWith)
if i != -1:
self.uiQemuListComboBox.setCurrentIndex(i)
@@ -567,8 +592,8 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if version is None:
appliance_configuration = self._appliance.copy()
if "docker" not in appliance_configuration:
# only Docker do not have version
if self._appliance.template_type() != "docker":
# only Docker do not have versions
return False
else:
try:
@@ -585,10 +610,15 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
return False
appliance_configuration["name"] = appliance_configuration["name"].strip()
if "qemu" in appliance_configuration:
if self._appliance["registry_version"] >= 8:
if "settings" in appliance_configuration:
for settings in appliance_configuration["settings"]:
if settings["template_type"] == "qemu":
settings["template_properties"]["path"] = self.uiQemuListComboBox.currentData()
elif "qemu" in appliance_configuration:
appliance_configuration["qemu"]["path"] = self.uiQemuListComboBox.currentData()
new_template = ApplianceToTemplate().new_template(appliance_configuration, self._compute_id, self._symbols, parent=self)
new_template = ApplianceToTemplate().new_template(appliance_configuration, self._compute_id, version, self._symbols, parent=self)
TemplateManager.instance().createTemplate(Template(new_template), callback=self._templateCreatedCallback)
return False
@@ -632,7 +662,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if not Controller.instance().isRemote() and self._compute_id == "local" and image["path"].startswith(ImageManager.instance().getDirectory()):
log.debug("{} is already on the local server".format(image["path"]))
return
image = Image(self._appliance.emulator(), image["path"], filename=image["filename"])
image = Image(self._appliance.template_type(), image["path"], filename=image["filename"])
image_upload_manager = ImageUploadManager(image, Controller.instance(), self._compute_id, self._applianceImageUploadedCallback, LocalConfig.instance().directFileUpload())
image_upload_manager.upload()
self._image_uploading_count += 1
@@ -649,12 +679,16 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
def nextId(self):
if self.currentPage() == self.uiServerWizardPage:
if "docker" in self._appliance:
if self._appliance.template_type() == "docker":
# skip Qemu binary selection and files pages if this is a Docker appliance
return super().nextId() + 2
elif "qemu" not in self._appliance:
return super().nextId() + 3
elif self._appliance.template_type() != "qemu":
# skip the Qemu binary selection page if not a Qemu appliance
return super().nextId() + 1
if self.currentPage() == self.uiQemuWizardPage:
if not self._appliance.get("installation_instructions"):
# skip the installation instructions page if there are no instructions
return super().nextId() + 1
return super().nextId()
def validateCurrentPage(self):
@@ -722,7 +756,6 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
elif self.currentPage() == self.uiQemuWizardPage:
# validate the Qemu
if self._server_check is False:
QtWidgets.QMessageBox.critical(self, "Checking for KVM support", "Please wait for the server to reply...")
return False

View File

@@ -22,6 +22,7 @@ Style editor to edit Shape items.
from ..qt import QtCore, QtWidgets, QtGui
from ..ui.style_editor_dialog_ui import Ui_StyleEditorDialog
from ..items.shape_item import ShapeItem
from ..items.rectangle_item import RectangleItem
class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog):
@@ -70,8 +71,27 @@ class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog):
self._border_color.green(),
self._border_color.blue(),
self._border_color.alpha()))
if isinstance(first_item, RectangleItem):
# use the horizontal corner radius first and then the vertical one if it's not set
# maybe we allow configuring them separately in the future
corner_radius = first_item.horizontalCornerRadius()
if not corner_radius:
corner_radius = first_item.verticalCornerRadius()
self.uiCornerRadiusSpinBox.setValue(corner_radius)
else:
self.uiCornerRadiusLabel.hide()
self.uiCornerRadiusSpinBox.hide()
self.uiRotationSpinBox.setValue(int(first_item.rotation()))
self.uiBorderWidthSpinBox.setValue(pen.width())
if isinstance(first_item, ShapeItem):
rect = first_item.rect()
self.uiWidthSpinBox.setValue(int(rect.width()))
self.uiHeightSpinBox.setValue(int(rect.height()))
else:
self.uiWidthSpinBox.hide()
self.uiWidthLabel.hide()
self.uiHeightSpinBox.hide()
self.uiHeightLabel.hide()
index = self.uiBorderStyleComboBox.findData(pen.style())
if index != -1:
self.uiBorderStyleComboBox.setCurrentIndex(index)
@@ -116,10 +136,18 @@ class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog):
for item in self._items:
item.setPen(pen)
# on multiselection it's possible to select many type of items
# on multi-selection it's possible to select many type of items
# but brush can be applied only on ShapeItem,
if brush and isinstance(item, ShapeItem):
item.setBrush(brush)
if isinstance(item, RectangleItem):
corner_radius = self.uiCornerRadiusSpinBox.value()
# use the corner radius for both horizontal (rx) and vertical (ry)
# maybe we support setting them separately in the future
item.setHorizontalCornerRadius(corner_radius)
item.setVerticalCornerRadius(corner_radius)
if isinstance(item, ShapeItem):
item.setWidthAndHeight(self.uiWidthSpinBox.value(), self.uiHeightSpinBox.value())
item.setRotation(self.uiRotationSpinBox.value())
def done(self, result):

View File

@@ -54,11 +54,15 @@ class StyleEditorDialogLink(QtWidgets.QDialog, Ui_StyleEditorDialog):
self.uiColorLabel.hide()
self.uiColorPushButton.hide()
self._color = None
self.uiCornerRadiusLabel.hide()
self.uiCornerRadiusSpinBox.hide()
self.uiRotationLabel.hide()
self.uiRotationSpinBox.hide()
link.setHovered(False) # make sure we use the right style
pen = link.pen()
link.setHovered(True)
self._border_color = pen.color()
self.uiBorderColorPushButton.setStyleSheet("background-color: rgba({}, {}, {}, {});".format(self._border_color.red(),
@@ -102,6 +106,7 @@ class StyleEditorDialogLink(QtWidgets.QDialog, Ui_StyleEditorDialog):
# Store values
self._link.setLinkStyle(new_link_style)
self._link.setHovered(False) # allow to see the new style
def done(self, result):
"""

View File

@@ -438,6 +438,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
item = self.itemAt(event.pos())
if item and sip.isdeleted(item):
return
elif not item:
self._main_window.uiStatusBar.clearMessage() # reset the status bar message when clicking on the scene
if item and (isinstance(item, LinkItem) or isinstance(item.parentItem(), LinkItem)):
is_not_link = False
@@ -595,7 +597,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
if factor < 0.10 or factor > 10:
return
self.scale(scale_factor, scale_factor)
self._main_window.uiStatusBar.showMessage("Zoom: {}%".format(round(self.transform().m11() * 100)), 2000)
self._main_window.uiStatusBar.showMessage("Zoom: {}%".format(round(self.transform().m11() * 100)), 5000)
def keyPressEvent(self, event):
"""
@@ -640,7 +642,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
if item:
# show item coords in the status bar
coords = "X: {} Y: {} Z: {}".format(item.x(), item.y(), item.zValue())
self._main_window.uiStatusBar.showMessage(coords, 2000)
self._main_window.uiStatusBar.showMessage(coords)
# force the children to redraw because of a problem with QGraphicsEffect
for item in self.scene().selectedItems():
@@ -713,6 +715,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
:param event: QDropEvent instance
"""
log.debug("Drop event received with mime data: {}".format(event.mimeData().formats()))
# check if what has been dropped is handled by this view
if event.mimeData().hasFormat("application/x-gns3-template"):
template_id = event.mimeData().data("application/x-gns3-template").data().decode()

View File

@@ -44,6 +44,7 @@ class DrawingItem:
def __init__(self, project=None, pos=None, drawing_id=None, svg=None, z=0, locked=False, rotation=0, **kws):
self._id = drawing_id
self._deleting = False
self._allow_snap_to_grid = True
self._locked = locked
if self._id is None:
self._id = str(uuid.uuid4())
@@ -135,6 +136,9 @@ class DrawingItem:
if self.rotation() < 360.0:
self.setRotation(self.rotation() + 1)
return True
elif modifiers & QtCore.Qt.AltModifier:
self._allow_snap_to_grid = False
return True
return False
def keyPressEvent(self, event):
@@ -147,6 +151,15 @@ class DrawingItem:
if not self.handleKeyPressEvent(event):
QtWidgets.QGraphicsItem.keyPressEvent(self, event)
def keyReleaseEvent(self, event):
"""
Handles all key release events
:param event: QKeyEvent
"""
self._allow_snap_to_grid = True
def __json__(self):
data = {
"drawing_id": self._id,
@@ -213,7 +226,8 @@ class DrawingItem:
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.isActive() and self._main_window.uiSnapToGridAction.isChecked():
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self._main_window.uiSnapToGridAction.isChecked() \
and self._allow_snap_to_grid:
grid_size = self._graphics_view.drawingGridSize()
mid_x = self.boundingRect().width() / 2
value.setX((grid_size * round((value.x() + mid_x) / grid_size)) - mid_x)

View File

@@ -320,13 +320,13 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
if not sip_is_deleted(self):
# create the contextual menu
self.setHovered(True)
self.setAcceptHoverEvents(False)
menu = QtWidgets.QMenu()
self.populateLinkContextualMenu(menu)
menu.exec_(QtGui.QCursor.pos())
self.setAcceptHoverEvents(True)
self._hovered = False
self.adjust()
self.setHovered(False)
def keyPressEvent(self, event):
"""

View File

@@ -51,6 +51,7 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
self._links = []
self._symbol = None
self._locked = False
self._allow_snap_to_grid = True
# says if the attached node has been initialized
# by the server.
@@ -108,6 +109,9 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
if node.initialized():
self.createdSlot(node.id())
if self._main_window.uiSnapToGridAction.isChecked():
self.setPos(QtCore.QPointF(self._node.x() + 0.1, self._node.y()))
def updateNode(self):
"""
Sync change to the node
@@ -466,7 +470,8 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
:param value: value of the change
"""
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.isActive() and self._main_window.uiSnapToGridAction.isChecked():
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self._main_window.uiSnapToGridAction.isChecked() \
and self._allow_snap_to_grid:
grid_size = self._main_window.uiGraphicsView.nodeGridSize()
mid_x = self.boundingRect().width() / 2
value.setX((grid_size * round((value.x() + mid_x) / grid_size)) - mid_x)
@@ -528,6 +533,27 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
for link in self._links:
link.adjust()
def keyPressEvent(self, event):
"""
Handles all key press events
:param event: QKeyEvent
"""
if event.modifiers() & QtCore.Qt.AltModifier:
self._allow_snap_to_grid = False
else:
super().keyPressEvent(event)
def keyReleaseEvent(self, event):
"""
Handles all key release events
:param event: QKeyEvent
"""
self._allow_snap_to_grid = True
def locked(self):
return self._locked

View File

@@ -32,8 +32,22 @@ class RectangleItem(QtWidgets.QGraphicsRectItem, ShapeItem):
"""
def __init__(self, width=200, height=100, **kws):
self._rx = 0
self._ry = 0
super().__init__(width=width, height=height, **kws)
def setHorizontalCornerRadius(self, radius: int):
self._rx = radius
def horizontalCornerRadius(self):
return self._rx
def setVerticalCornerRadius(self, radius: int):
self._ry = radius
def verticalCornerRadius(self):
return self._ry
def paint(self, painter, option, widget=None):
"""
Paints the contents of an item in local coordinates.
@@ -43,7 +57,9 @@ class RectangleItem(QtWidgets.QGraphicsRectItem, ShapeItem):
:param widget: QWidget instance
"""
super().paint(painter, option, widget)
painter.setPen(self.pen())
painter.setBrush(self.brush())
painter.drawRoundedRect(self.rect(), self._rx, self._ry)
self.drawLayerInfo(painter)
def toSvg(self):
@@ -57,7 +73,27 @@ class RectangleItem(QtWidgets.QGraphicsRectItem, ShapeItem):
rect = ET.SubElement(svg, "rect")
rect.set("width", str(int(self.rect().width())))
rect.set("height", str(int(self.rect().height())))
if self._rx:
rect.set("rx", str(self._rx))
if self._ry:
rect.set("ry", str(self._ry))
rect = self._styleSvg(rect)
return ET.tostring(svg, encoding="utf-8").decode("utf-8")
def fromSvg(self, svg):
svg_elem = ET.fromstring(svg)
if len(svg_elem):
# handle horizontal corner radius and vertical corner radius (specific to rectangles)
rx = svg_elem[0].get("rx")
ry = svg_elem[0].get("ry")
if rx:
self._rx = int(rx)
elif ry:
self._rx = int(ry) # defaults to ry if it is specified
if ry:
self._ry = int(ry)
elif rx:
self._ry = int(rx) # defaults to rx if it is specified
super().fromSvg(svg)

View File

@@ -171,9 +171,12 @@ class ShapeItem(DrawingItem):
if not self.locked():
self._graphics_view.setCursor(QtCore.Qt.ArrowCursor)
def setWidthAndHeight(self, width, height):
self.setRect(0, 0, width, height)
def fromSvg(self, svg):
"""
Import element informations from an SVG
Import element information from SVG
"""
svg = ET.fromstring(svg)
width = float(svg.get("width", self.rect().width()))

View File

@@ -193,7 +193,11 @@ class LocalServer(QtCore.QObject):
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if proceed == QtWidgets.QMessageBox.Yes:
sudo(["chown", "root:admin", path], ["chmod", "4750", path])
from gns3.utils.macos_ubridge_setuid import macos_ubridge_setuid
if sys.platform.startswith("darwin") and hasattr(sys, "frozen"):
macos_ubridge_setuid()
else:
sudo(["chown", "root:admin", path], ["chmod", "4750", path])
except OSError as e:
QtWidgets.QMessageBox.critical(self.parent(), "uBridge", "Can't set root permissions to uBridge {}: {}".format(path, str(e)))
return False
@@ -478,11 +482,15 @@ class LocalServer(QtCore.QObject):
try:
if sys.platform.startswith("win"):
# use the string on Windows
self._local_server_process = subprocess.Popen(command, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, stderr=subprocess.PIPE)
self._local_server_process = subprocess.Popen(
command,
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP,
stderr=subprocess.PIPE,
env=os.environ)
else:
# use arguments on other platforms
args = shlex.split(command)
self._local_server_process = subprocess.Popen(args, stderr=subprocess.PIPE)
self._local_server_process = subprocess.Popen(args, stderr=subprocess.PIPE, env=os.environ)
except (OSError, subprocess.SubprocessError) as e:
log.warning('Could not start local server "{}": {}'.format(command, e))
return False

View File

@@ -30,16 +30,6 @@ try:
except Exception as e:
print("Fail update installation: {}".format(str(e)))
# WARNING
# Due to buggy user machines we choose to put this as the first loading modules
# otherwise the egg cache is initialized in his standard location and
# if is not writetable the application crash. It's the user fault
# because one day the user as used sudo to run an egg and break his
# filesystem permissions, but it's a common mistake.
from gns3.utils.get_resource import get_resource
import datetime
import traceback
import time
@@ -60,12 +50,12 @@ from gns3.local_config import LocalConfig
from gns3.application import Application
from gns3.utils import parse_version
from gns3.dialogs.profile_select import ProfileSelectDialog
from gns3.version import __version__
import logging
log = logging.getLogger(__name__)
from gns3.version import __version__
def locale_check():
"""
@@ -135,6 +125,13 @@ def main():
if options.project:
options.project = os.path.abspath(options.project)
try:
import truststore
truststore.inject_into_ssl()
log.info("Using system certificate store for SSL connections")
except ImportError:
pass
if hasattr(sys, "frozen"):
# We add to the path where the OS search executable our binary location starting by GNS3
# packaged binary
@@ -145,6 +142,7 @@ def main():
frozen_dirs = [
frozen_dir,
os.path.normpath(os.path.join(frozen_dir, 'dynamips')),
os.path.normpath(os.path.join(frozen_dir, 'ubridge')),
os.path.normpath(os.path.join(frozen_dir, 'vpcs')),
os.path.normpath(os.path.join(frozen_dir, 'traceng'))
]
@@ -154,6 +152,7 @@ def main():
if options.project:
os.chdir(frozen_dir)
def exceptionHook(exception, value, tb):
if exception == KeyboardInterrupt:
@@ -185,9 +184,9 @@ def main():
# catch exceptions to write them in a file
sys.excepthook = exceptionHook
# we only support Python 3 version >= 3.4
if sys.version_info < (3, 4):
raise SystemExit("Python 3.4 or higher is required")
# we only support Python 3 version >= 3.8
if sys.version_info < (3, 8):
raise SystemExit("Python 3.8 or higher is required")
if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.5.0"):
raise SystemExit("Requirement is PyQt5 version 5.5.0 or higher, got version {}".format(QtCore.QT_VERSION_STR))
@@ -222,8 +221,11 @@ def main():
# hide the console
# win32console.AllocConsole()
console_window = win32console.GetConsoleWindow()
if console_window:
parent_window = win32gui.GetParent(console_window)
if not parent_window and console_window:
win32gui.ShowWindow(console_window, win32con.SW_HIDE)
elif parent_window:
win32gui.ShowWindow(parent_window, win32con.SW_HIDE)
else:
log.warning("Could not get the console window")
except win32console.error as e:
@@ -263,6 +265,7 @@ def main():
log.info("GNS3 GUI version {}".format(__version__))
log.info("Copyright (c) 2007-{} GNS3 Technologies Inc.".format(current_year))
log.info("Application started with {}".format(" ".join(sys.argv)))
log.debug("PATH={}".format(os.environ["PATH"]))
# update the exception file path to have it in the same directory as the settings file.
exception_file_path = os.path.join(LocalConfig.instance().configDirectory(), exception_file_path)

View File

@@ -49,7 +49,6 @@ from .topology import Topology
from .http_client import HTTPClient
from .progress import Progress
from .update_manager import UpdateManager
from .utils.analytics import AnalyticsClient
from .dialogs.appliance_wizard import ApplianceWizard
from .dialogs.new_template_wizard import NewTemplateWizard
from .dialogs.notif_dialog import NotifDialog, NotifDialogHandler
@@ -133,11 +132,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._local_config_timer = QtCore.QTimer(self)
self._local_config_timer.timeout.connect(local_config.checkConfigChanged)
self._local_config_timer.start(1000) # milliseconds
self._analytics_client = AnalyticsClient()
self._template_manager = TemplateManager().instance()
self._appliance_manager = ApplianceManager().instance()
# restore the geometry and state of the main window.
self._save_gui_state_geometry = True
self.restoreGeometry(QtCore.QByteArray().fromBase64(self._settings["geometry"].encode()))
self.restoreState(QtCore.QByteArray().fromBase64(self._settings["state"].encode()))
@@ -234,6 +233,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.uiResetGUIStateAction.triggered.connect(self._resetGUIState)
self.uiResetDocksAction.triggered.connect(self._resetDocksSlot)
# tool menu connections
@@ -368,6 +368,18 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
item.updateNode()
item.update()
def _resetGUIState(self):
"""
Reset the GUI state.
"""
self._save_gui_state_geometry = False
self.close()
if hasattr(sys, "frozen"):
QtCore.QProcess.startDetached(os.path.abspath(sys.executable), sys.argv)
else:
QtWidgets.QMessageBox.information(self, "GUI state","The GUI state has been reset, please restart the application")
def _resetDocksSlot(self):
"""
Reset the dock widgets.
@@ -378,13 +390,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.uiConsoleDockWidget.setFloating(False)
self.uiNodesDockWidget.setFloating(False)
def analyticsClient(self):
"""
Return the analytics client
"""
return self._analytics_client
def _newProjectActionSlot(self):
"""
Slot called to create a new project.
@@ -1122,6 +1127,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
if self.uiAddLinkAction.isChecked() and key == QtCore.Qt.Key_Escape:
self.uiAddLinkAction.setChecked(False)
self._addLinkActionSlot()
elif key == QtCore.Qt.Key_C and event.modifiers() & QtCore.Qt.ControlModifier:
status_bar_message = self.uiStatusBar.currentMessage()
if status_bar_message:
QtWidgets.QApplication.clipboard().setText(status_bar_message)
else:
super().keyPressEvent(event)
@@ -1144,8 +1153,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
progress.setCancelButtonText("Force quit")
log.debug("Close the Main Window")
self._analytics_client.sendScreenView("Main Window", session_start=False)
self._finish_application_closing(close_windows=False)
event.accept()
self.uiConsoleTextEdit.closeIO()
@@ -1160,8 +1167,12 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
log.debug("_finish_application_closing")
self._settings["geometry"] = bytes(self.saveGeometry().toBase64()).decode()
self._settings["state"] = bytes(self.saveState().toBase64()).decode()
if self._save_gui_state_geometry:
self._settings["geometry"] = bytes(self.saveGeometry().toBase64()).decode()
self._settings["state"] = bytes(self.saveState().toBase64()).decode()
else:
self._settings["geometry"] = ""
self._settings["state"] = ""
self.setSettings(self._settings)
Controller.instance().stopListenNotifications()
@@ -1234,7 +1245,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
Controller.instance().connected_signal.connect(self._controllerConnectedSlot)
Controller.instance().project_list_updated_signal.connect(self.updateRecentProjectActions)
self._analytics_client.sendScreenView("Main Window")
self.uiGraphicsView.setEnabled(False)
# show the setup wizard

View File

@@ -73,7 +73,7 @@ class Nat(Node):
:returns: symbol path (or resource).
"""
return ":/symbols/cloud.svg"
return ":/symbols/nat.svg"
@staticmethod
def categories():

View File

@@ -57,6 +57,7 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
self.uiPrivateConfigToolButton.hide()
# location of the base config templates
# FIXME: this does not work
self._base_iou_l2_config_template = get_resource(os.path.join("configs", "iou_l2_base_startup-config.txt"))
self._base_iou_l3_config_template = get_resource(os.path.join("configs", "iou_l3_base_startup-config.txt"))
self._default_configs_dir = LocalServer.instance().localServerSettings()["configs_path"]

View File

@@ -121,7 +121,7 @@ class Qemu(Module):
:param options: Options for the image creation
"""
Controller.instance().postCompute("/qemu/img", compute_id, callback, body=options)
Controller.instance().postCompute("/qemu/img", compute_id, callback, timeout=None, body=options)
def updateDiskImage(self, compute_id, callback, options):
"""

View File

@@ -101,7 +101,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self.uiOnCloseComboBox.addItem(name, option_name)
# 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
# i82558b, i82559a, i82559b, i82559c, i82559er, i82562, i82801, igb, 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"),
@@ -121,6 +121,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
("i82559er", "Intel i82559ER Ethernet"),
("i82562", "Intel i82562 Ethernet"),
("i82801", "Intel i82801 Ethernet"),
("igb", "Intel 82576 Gigabit Ethernet"),
("ne2k_pci", "NE2000 Ethernet"),
("pcnet", "AMD PCNet Ethernet"),
("rocker", "Rocker L2 switch device"),
@@ -579,6 +580,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self.uiQemuOptionsLineEdit.setText(settings["options"])
self.uiUsageTextEdit.setPlainText(settings["usage"])
self.uiTPMCheckBox.setChecked(settings["tpm"])
self.uiUEFICheckBox.setChecked(settings["uefi"])
def saveSettings(self, settings, node=None, group=False):
"""
@@ -694,4 +696,5 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
settings["options"] = self.uiQemuOptionsLineEdit.text()
settings["usage"] = self.uiUsageTextEdit.toPlainText()
settings["tpm"] = self.uiTPMCheckBox.isChecked()
settings["uefi"] = self.uiUEFICheckBox.isChecked()
return settings

View File

@@ -75,6 +75,7 @@ class QemuVM(Node):
"legacy_networking": QEMU_VM_SETTINGS["legacy_networking"],
"replicate_network_connection_state": QEMU_VM_SETTINGS["replicate_network_connection_state"],
"tpm": QEMU_VM_SETTINGS["tpm"],
"uefi": QEMU_VM_SETTINGS["uefi"],
"create_config_disk": QEMU_VM_SETTINGS["create_config_disk"],
"platform": QEMU_VM_SETTINGS["platform"],
"on_close": QEMU_VM_SETTINGS["on_close"],

View File

@@ -58,6 +58,7 @@ QEMU_VM_SETTINGS = {
"legacy_networking": False,
"replicate_network_connection_state": True,
"tpm": False,
"uefi": False,
"create_config_disk": False,
"on_close": "power_off",
"platform": "",

View File

@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>478</width>
<height>550</height>
<height>579</height>
</rect>
</property>
<property name="windowTitle">
@@ -867,13 +867,6 @@
<string>Additional settings</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="uiQemuOptionsLabel">
<property name="text">
<string>Options:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="uiQemuOptionsLineEdit">
<property name="toolTip">
@@ -890,7 +883,7 @@
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="uiBaseVMCheckBox">
<property name="enabled">
<bool>true</bool>
@@ -907,11 +900,26 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="uiQemuOptionsLabel">
<property name="text">
<string>Options:</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="uiUEFICheckBox">
<property name="text">
<string>Enable UEFI boot mode</string>
</property>
</widget>
</item>
</layout>
<zorder>uiQemuOptionsLineEdit</zorder>
<zorder>uiQemuOptionsLabel</zorder>
<zorder>uiBaseVMCheckBox</zorder>
<zorder>uiTPMCheckBox</zorder>
<zorder>uiUEFICheckBox</zorder>
</widget>
</item>
<item>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/qemu/ui/qemu_vm_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.15.6
# Created by: PyQt5 UI code generator 5.15.9
#
# 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.
@@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_QemuVMConfigPageWidget(object):
def setupUi(self, QemuVMConfigPageWidget):
QemuVMConfigPageWidget.setObjectName("QemuVMConfigPageWidget")
QemuVMConfigPageWidget.resize(478, 550)
QemuVMConfigPageWidget.resize(478, 579)
self.verticalLayout = QtWidgets.QVBoxLayout(QemuVMConfigPageWidget)
self.verticalLayout.setObjectName("verticalLayout")
self.uiQemutabWidget = QtWidgets.QTabWidget(QemuVMConfigPageWidget)
@@ -428,23 +428,27 @@ class Ui_QemuVMConfigPageWidget(object):
self.groupBox.setObjectName("groupBox")
self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox)
self.gridLayout_3.setObjectName("gridLayout_3")
self.uiQemuOptionsLabel = QtWidgets.QLabel(self.groupBox)
self.uiQemuOptionsLabel.setObjectName("uiQemuOptionsLabel")
self.gridLayout_3.addWidget(self.uiQemuOptionsLabel, 0, 0, 1, 1)
self.uiQemuOptionsLineEdit = QtWidgets.QLineEdit(self.groupBox)
self.uiQemuOptionsLineEdit.setObjectName("uiQemuOptionsLineEdit")
self.gridLayout_3.addWidget(self.uiQemuOptionsLineEdit, 0, 1, 1, 1)
self.uiBaseVMCheckBox = QtWidgets.QCheckBox(self.groupBox)
self.uiBaseVMCheckBox.setEnabled(True)
self.uiBaseVMCheckBox.setObjectName("uiBaseVMCheckBox")
self.gridLayout_3.addWidget(self.uiBaseVMCheckBox, 2, 0, 1, 2)
self.gridLayout_3.addWidget(self.uiBaseVMCheckBox, 3, 0, 1, 2)
self.uiTPMCheckBox = QtWidgets.QCheckBox(self.groupBox)
self.uiTPMCheckBox.setObjectName("uiTPMCheckBox")
self.gridLayout_3.addWidget(self.uiTPMCheckBox, 1, 0, 1, 2)
self.uiQemuOptionsLabel = QtWidgets.QLabel(self.groupBox)
self.uiQemuOptionsLabel.setObjectName("uiQemuOptionsLabel")
self.gridLayout_3.addWidget(self.uiQemuOptionsLabel, 0, 0, 1, 1)
self.uiUEFICheckBox = QtWidgets.QCheckBox(self.groupBox)
self.uiUEFICheckBox.setObjectName("uiUEFICheckBox")
self.gridLayout_3.addWidget(self.uiUEFICheckBox, 2, 0, 1, 2)
self.uiQemuOptionsLineEdit.raise_()
self.uiQemuOptionsLabel.raise_()
self.uiBaseVMCheckBox.raise_()
self.uiTPMCheckBox.raise_()
self.uiUEFICheckBox.raise_()
self.verticalLayout_2.addWidget(self.groupBox)
spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_2.addItem(spacerItem4)
@@ -549,7 +553,6 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiProcessPriorityComboBox.setItemText(4, _translate("QemuVMConfigPageWidget", "Low"))
self.uiProcessPriorityComboBox.setItemText(5, _translate("QemuVMConfigPageWidget", "Very low"))
self.groupBox.setTitle(_translate("QemuVMConfigPageWidget", "Additional settings"))
self.uiQemuOptionsLabel.setText(_translate("QemuVMConfigPageWidget", "Options:"))
self.uiQemuOptionsLineEdit.setToolTip(_translate("QemuVMConfigPageWidget", "<html><head/><body><p>Variable replacements:</p>\n"
"<ul>\n"
"<li>%vm-name% =VM name</li>\n"
@@ -562,5 +565,7 @@ class Ui_QemuVMConfigPageWidget(object):
"</body></html>"))
self.uiBaseVMCheckBox.setText(_translate("QemuVMConfigPageWidget", "Use as a linked base VM"))
self.uiTPMCheckBox.setText(_translate("QemuVMConfigPageWidget", "Enable Trusted Platform Module (TPM)"))
self.uiQemuOptionsLabel.setText(_translate("QemuVMConfigPageWidget", "Options:"))
self.uiUEFICheckBox.setText(_translate("QemuVMConfigPageWidget", "Enable UEFI boot mode"))
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiAdvancedSettingsTab), _translate("QemuVMConfigPageWidget", "Advanced"))
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiUsageTab), _translate("QemuVMConfigPageWidget", "Usage"))

View File

@@ -689,7 +689,8 @@ class Node(BaseNode):
return
super().setStatus(status)
if status == self.started and "console_auto_start" in self.settings() and self.settings()["console_auto_start"]:
self.openConsole()
# give the node some time to start before opening the console
QtCore.QTimer.singleShot(1000, self.openConsole)
def openConsole(self, command=None, aux=False):
"""

View File

@@ -112,15 +112,20 @@ class PacketCapture:
"""
Starts the packet capture reader.
"""
self._startPacketCommand(link, self.settings()["packet_capture_reader_command"])
def stopPacketCaptureReader(self, link):
"""
Stop the packet capture reader
"""
if link in self._tail_process and self._tail_process[link].poll() is None:
if link in self._tail_process:
log.debug("Stopping packet capture reader for link {}".format(link.link_id()))
self._tail_process[link].kill()
try:
self._tail_process[link].kill()
except (PermissionError, OSError):
pass
del self._tail_process[link]
def startPacketCaptureAnalyzer(self, link):
@@ -141,13 +146,19 @@ class PacketCapture:
# PCAP capture file path
command = command.replace("%c", '"' + capture_file_path + '"')
command = command.replace("{pcap_file}", '"' + capture_file_path + '"')
# Add description
# Add project name
command = command.replace("%P", link.project().name())
command = command.replace("{project}", link.project().name().replace('"', '\\"'))
# Add link description
description = "{}[{}]->{}[{}]".format(link.sourceNode().name(),
link.sourcePort().name(),
link.destinationNode().name(),
link.destinationPort().name())
command = command.replace("%d", description)
command = command.replace("{link_description}", description)
if not sys.platform.startswith("win"):
command = shlex.split(command)
@@ -155,7 +166,7 @@ class PacketCapture:
QtWidgets.QMessageBox.critical(self.parent(), "Packet Capture Analyzer", "No packet capture analyzer program configured")
return
try:
subprocess.Popen(command)
subprocess.Popen(command, env=os.environ)
except OSError as e:
QtWidgets.QMessageBox.critical(self.parent(), "Packet Capture Analyzer", "Can't start packet capture analyzer program {}".format(str(e)))
return
@@ -183,14 +194,14 @@ class PacketCapture:
QtWidgets.QMessageBox.critical(self.parent(), "Packet capture", "Can't create packet capture file {}: {}".format(capture_file_path, str(e)))
return
if link in self._tail_process and self._tail_process[link].poll() is None:
if link in self._tail_process:
try:
self._tail_process[link].kill()
except (PermissionError, OSError):
# Sometimes we have condition on windows where the process is in the process to quit
pass
del self._tail_process[link]
if link in self._capture_reader_process and self._capture_reader_process[link].poll() is None:
if link in self._capture_reader_process:
try:
self._capture_reader_process[link].kill()
except (PermissionError, OSError):
@@ -199,13 +210,19 @@ class PacketCapture:
# PCAP capture file path
command = command.replace("%c", '"' + capture_file_path + '"')
command = command.replace("{pcap_file}", '"' + capture_file_path + '"')
# Add description
# Add project name
command = command.replace("%P", link.project().name())
command = command.replace("{project}", link.project().name().replace('"', '\\"'))
# Add link description
description = "{} {} to {} {}".format(link.sourceNode().name(),
link.sourcePort().name(),
link.destinationNode().name(),
link.destinationPort().name())
command = command.replace("%d", description)
command = command.replace("{link_description}", description)
if "|" in command:
# live traffic capture (using tail)

View File

@@ -301,7 +301,6 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
self.uiImagesPathLineEdit.setText(local_server["images_path"])
self.uiConfigsPathLineEdit.setText(local_server["configs_path"])
self.uiAppliancesPathLineEdit.setText(local_server["appliances_path"])
self.uiStatsCheckBox.setChecked(settings["send_stats"])
self.uiOverlayNotificationsCheckBox.setChecked(settings["overlay_notifications"])
self.uiCrashReportCheckBox.setChecked(local_server["report_errors"])
self.uiCheckForUpdateCheckBox.setChecked(settings["check_for_update"])
@@ -411,7 +410,6 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
"vnc_console_command": self.uiVNCConsoleCommandLineEdit.text(),
"spice_console_command": self.uiSPICEConsoleCommandLineEdit.text(),
"delay_console_all": self.uiDelayConsoleAllSpinBox.value(),
"send_stats": self.uiStatsCheckBox.isChecked(),
"multi_profiles": self.uiMultiProfilesCheckBox.isChecked(),
"direct_file_upload": self.uiDirectFileUpload.isChecked()
}

View File

@@ -475,9 +475,9 @@ class Project(QtCore.QObject):
"variables": self._variables,
"supplier": self._supplier
}
self.put("", self._projectUpdatedCallback, body=body)
self.put("", self.projectUpdatedCallback, body=body)
def _projectUpdatedCallback(self, result, error=False, **kwargs):
def projectUpdatedCallback(self, result, error=False, **kwargs):
if error:
self.project_creation_error_signal.emit(result["message"])
return
@@ -654,7 +654,7 @@ class Project(QtCore.QObject):
@qslot
def _websocket_error(self, error):
if self._notification_stream:
log.error(self._notification_stream.errorString())
log.error("Websocket project notification stream error: {}".format(self._notification_stream.errorString()))
self._notification_stream = None
self._startListenNotifications()
@@ -712,17 +712,20 @@ class Project(QtCore.QObject):
drawing = Topology.instance().getDrawingFromUuid(result["event"]["drawing_id"])
if drawing is not None:
drawing.delete(skip_controller=True)
# project.closed and project.updated notifications have been moved to the controller
# because they are not project specific, keeping it there for backward compatibility
# when connected to an older controller version
elif result["action"] == "project.closed":
Topology.instance().setProject(None)
elif result["action"] == "project.updated":
self._projectUpdatedCallback(result["event"])
self.projectUpdatedCallback(result["event"])
elif result["action"] == "snapshot.restored":
Topology.instance().restoreSnapshot(result["event"]["project_id"])
elif result["action"] == "log.error":
log.error(result["event"]["message"])
elif result["action"] == "log.warning":
log.warning(result["event"]["message"])
elif result["action"] == "log.info":
log.info(result["event"]["message"], extra={"show": True})
elif result["action"] == "log.error" and result["event"].get("message"):
log.error(result["event"].get("message"))
elif result["action"] == "log.warning" and result["event"].get("message"):
log.warning(result["event"].get("message"))
elif result["action"] == "log.info" and result["event"].get("message"):
log.info(result["event"].get("message"), extra={"show": True})
elif result["action"] == "ping":
pass

View File

@@ -195,54 +195,6 @@ if hasattr(sys, '_called_from_test'):
QtCore.pyqtSignal = FakeQtSignal
class StatsQtWidgetsQWizard(QtWidgets.QWizard):
"""
Send stats from all the QWizard
"""
def __init__(self, *args):
super().__init__(*args)
from ..utils.analytics import AnalyticsClient
name = self.__class__.__name__
name = re.sub(r"([A-Z])", r" \1", name).strip()
AnalyticsClient.instance().sendScreenView(name)
QtWidgets.QWizard = StatsQtWidgetsQWizard
class StatsQtWidgetsQMainWindow(QtWidgets.QMainWindow):
"""
Send stats from all the QMainWindow
"""
def __init__(self, *args):
super().__init__(*args)
from ..utils.analytics import AnalyticsClient
name = self.__class__.__name__
name = re.sub(r"([A-Z])", r" \1", name).strip()
AnalyticsClient.instance().sendScreenView(name)
QtWidgets.QMainWindow = StatsQtWidgetsQMainWindow
class StatsQtWidgetsQDialog(QtWidgets.QDialog):
"""
Send stats from all the QWizard
"""
def __init__(self, *args):
super().__init__(*args)
from ..utils.analytics import AnalyticsClient
name = self.__class__.__name__
name = re.sub(r"([A-Z])", r" \1", name).strip()
AnalyticsClient.instance().sendScreenView(name)
QtWidgets.QDialog = StatsQtWidgetsQDialog
def qpartial(func, *args, **kwargs):
"""
A functools partial that you can use on qobject. If the targeted qobject is

View File

@@ -21,11 +21,13 @@ import copy
import os
import collections.abc
import jsonschema
from gns3.utils.get_resource import get_resource
import logging
log = logging.getLogger(__name__)
class ApplianceError(Exception):
pass
@@ -38,6 +40,7 @@ class Appliance(collections.abc.Mapping):
:params path: Path of the appliance file on disk or file content
"""
self._registry = registry
self._registry_version = None
if os.path.isabs(path):
try:
@@ -58,16 +61,25 @@ class Appliance(collections.abc.Mapping):
:param appliance: Sanity check on the appliance configuration
"""
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"] > 7:
raise ApplianceError("Please update GNS3 in order to install this appliance")
raise ApplianceError("Invalid appliance configuration please report the issue on https://github.com/GNS3/gns3-registry/issues")
with open(get_resource(os.path.join("schemas", "appliance.json"))) as f:
self._registry_version = self._appliance["registry_version"]
if self._registry_version > 8:
# we only support registry version 8 and below
raise ApplianceError("Registry version {} is not supported in this version of GNS3".format(self._registry_version))
if self._registry_version == 8:
# registry version 8 has a different schema with support for multiple settings sets
appliance_file = "appliance_v8.json"
else:
appliance_file = "appliance.json"
with open(get_resource("schemas/{}".format(appliance_file))) as f:
schema = json.load(f)
v = jsonschema.Draft4Validator(schema)
try:
v.validate(self._appliance)
except jsonschema.ValidationError as e:
except jsonschema.ValidationError:
error = jsonschema.exceptions.best_match(v.iter_errors(self._appliance)).message
raise ApplianceError("Invalid appliance file: {}".format(error))
@@ -82,10 +94,11 @@ class Appliance(collections.abc.Mapping):
def _resolve_version(self):
"""
Replace image field in versions by their the complete information from images
Replace image field in versions by the complete information from images
"""
if "versions" not in self._appliance:
log.debug("No versions found in appliance")
return
for version in self._appliance["versions"]:
@@ -96,7 +109,7 @@ class Appliance(collections.abc.Mapping):
for file in self._appliance["images"]:
file = copy.copy(file)
if "idlepc" in version:
if self._registry_version < 8 and "idlepc" in version:
file["idlepc"] = version["idlepc"]
if "/" in filename:
@@ -127,7 +140,7 @@ class Appliance(collections.abc.Mapping):
def search_images_for_version(self, version_name):
"""
Search on disk the images required by this version.
And keep only the require images in the images fields. Add to the images
And keep only the required images in the images fields. Add to the images
their disk type and path.
:param version_name: Version name
@@ -142,10 +155,18 @@ class Appliance(collections.abc.Mapping):
for image_type, image in version["images"].items():
image["type"] = image_type
img = self._registry.search_image_file(self.emulator(), image["filename"], image.get("md5sum"), image.get("filesize"))
checksum = image.get("md5sum")
if checksum is None and self._registry_version >= 8:
# registry version >= 8 has the checksum and checksum_type fields
checksum_type = image.get("checksum_type", "md5") # md5 is the default and only supported type
if checksum_type != "md5":
raise ApplianceError("Checksum type {} is not supported".format(checksum_type))
checksum = image.pop("checksum")
img = self._registry.search_image_file(self.template_type(), image["filename"], checksum, image.get("filesize"))
if img is None:
if "md5sum" in image:
raise ApplianceError("File {} with checksum {} not found for {}".format(image["filename"], image["md5sum"], appliance["name"]))
if checksum:
raise ApplianceError("File {} with checksum {} not found for {}".format(image["filename"], checksum, appliance["name"]))
else:
raise ApplianceError("File {} not found for {}".format(image["filename"], appliance["name"]))
@@ -186,9 +207,51 @@ class Appliance(collections.abc.Mapping):
except ApplianceError:
return False
def emulator(self):
if "qemu" in self._appliance:
return "qemu"
if "iou" in self._appliance:
return "iou"
return "dynamips"
def template_type(self):
if self._registry_version >= 8:
template_type = None
for settings in self._appliance["settings"]:
if settings["template_type"] and not template_type:
template_type = settings["template_type"]
elif settings["template_type"] and template_type != settings["template_type"]:
# we are currently not supporting multiple different template types in the same appliance
raise ApplianceError("Multiple different template types found in appliance")
if not template_type:
raise ApplianceError("No template type found in appliance {}".format(self._appliance["name"]))
return template_type
else:
if "qemu" in self._appliance:
return "qemu"
if "iou" in self._appliance:
return "iou"
if "dynamips" in self._appliance:
return "dynamips"
if "docker" in self._appliance:
return "docker"
return None
def template_properties(self):
"""
Get template properties
"""
if self._registry_version >= 8:
# find the default settings if any
for settings in self._appliance["settings"]:
if settings.get("default", False):
return settings["template_properties"]
# otherwise take the first settings we find
for settings in self._appliance["settings"]:
if settings["template_type"]:
return settings["template_properties"]
else:
if "qemu" in self._appliance:
return self._appliance["qemu"]
if "iou" in self._appliance:
return self._appliance["iou"]
if "dynamips" in self._appliance:
return self._appliance["dynamips"]
if "docker" in self._appliance:
return self._appliance["docker"]
return None

View File

@@ -24,6 +24,7 @@ from ..qt import QtCore, QtWidgets, QtNetwork
from ..controller import Controller
from .config import Config, ConfigException
import logging
log = logging.getLogger(__name__)
@@ -33,7 +34,7 @@ class ApplianceToTemplate:
Appliance installation.
"""
def new_template(self, appliance_config, server, controller_symbols=None, parent=None):
def new_template(self, appliance_config, server, appliance_version=None, controller_symbols=None, parent=None):
"""
Creates a new template from an appliance.
@@ -42,6 +43,7 @@ class ApplianceToTemplate:
"""
self._parent = parent
self._registry_version = appliance_config["registry_version"]
new_template = {
"compute_id": server,
"name": appliance_config["name"]
@@ -73,45 +75,101 @@ class ApplianceToTemplate:
elif appliance_config["category"] == "firewall":
new_template["symbol"] = ":/symbols/firewall.svg"
if "qemu" in appliance_config:
new_template["template_type"] = "qemu"
self._add_qemu_config(new_template, appliance_config)
elif "iou" in appliance_config:
new_template["template_type"] = "iou"
self._add_iou_config(new_template, appliance_config)
elif "dynamips" in appliance_config:
new_template["template_type"] = "dynamips"
self._add_dynamips_config(new_template, appliance_config)
elif "docker" in appliance_config:
new_template["template_type"] = "docker"
self._add_docker_config(new_template, appliance_config)
if self._registry_version >= 8:
if appliance_version:
for version in appliance_config["versions"]:
if appliance_version and version["name"] == appliance_version:
# inject "usage", "category" and "symbol" specified at the version
# level into the template properties
usage = version.get("usage")
if usage:
new_template["usage"] = usage
new_template["symbol"] = version.get("symbol", new_template["symbol"])
new_template["category"] = version.get("category", new_template["category"])
settings = self._get_settings(appliance_config, version.get("settings"))
template_type = settings["template_type"]
if template_type == "qemu":
self._add_qemu_config(new_template, settings["template_properties"], appliance_config)
elif template_type == "iou":
self._add_iou_config(new_template, settings["template_properties"], appliance_config)
elif template_type == "dynamips":
self._add_dynamips_config(new_template, settings["template_properties"], appliance_config)
else:
# docker appliances have no version
settings = self._get_settings(appliance_config)
if settings["template_type"] == "docker":
self._add_docker_config(new_template, settings["template_properties"], appliance_config)
else:
raise ConfigException("{} no configuration found for known emulators".format(new_template["name"]))
if "qemu" in appliance_config:
self._add_qemu_config(new_template, appliance_config["qemu"], appliance_config)
elif "iou" in appliance_config:
self._add_iou_config(new_template, appliance_config["iou"], appliance_config)
elif "dynamips" in appliance_config:
self._add_dynamips_config(new_template, appliance_config["dynamips"], appliance_config)
elif "docker" in appliance_config:
self._add_docker_config(new_template, appliance_config["docker"], appliance_config)
else:
raise ConfigException("{} no configuration found for known emulators".format(new_template["name"]))
return new_template
def _add_qemu_config(self, new_config, appliance_config):
def _get_settings(self, appliance_config, settings_name=None):
new_config.update(appliance_config["qemu"])
default_settings = None
# first look for default settings, if any ('default' = true, first set that has it)
for settings in appliance_config["settings"]:
if settings.get("default", False):
default_settings = settings
break
# then look for specific settings set if a name is provided
if settings_name:
for settings in appliance_config["settings"]:
if settings.get("name") == settings_name:
if settings.get("inherit_default_properties", True) and \
default_settings and default_settings["template_type"] == settings["template_type"]:
default_settings["template_properties"].update(settings["template_properties"])
return default_settings
return settings
raise ConfigException("Settings '{}' cannot be found in the appliance file", settings_name)
elif default_settings:
return default_settings
if not appliance_config.get("settings"):
raise ConfigException("No settings found in the appliance file")
# if no default settings are specified, use the first available settings set
return appliance_config["settings"][0]
def _add_qemu_config(self, new_config, template_properties, appliance_config):
new_config["template_type"] = "qemu"
new_config.update(template_properties)
# the following properties are not valid for a template
new_config.pop("kvm", None)
new_config.pop("path", None)
new_config.pop("arch", None)
new_config.pop("kvm", None) # To check KVM setting against the server capabilities
new_config.pop("path", None) # Qemu binary selected in previous step
new_config.pop("arch", None) # Used for selecting the Qemu binary
options = appliance_config["qemu"].get("options", "")
if appliance_config["qemu"].get("kvm", "allow") == "disable" and "-machine accel=tcg" not in options:
options = template_properties.get("options", "")
if template_properties.get("kvm", "allow") == "disable" and "-machine accel=tcg" not in options:
options += " -machine accel=tcg"
new_config["options"] = options.strip()
options = options.strip()
if options:
new_config["options"] = options
for image in appliance_config["images"]:
if image.get("path"):
new_config[image["type"]] = self._relative_image_path("QEMU", image["path"])
if "path" in appliance_config["qemu"]:
new_config["qemu_path"] = appliance_config["qemu"]["path"]
if "path" in template_properties:
new_config["qemu_path"] = template_properties["path"]
else:
new_config["qemu_path"] = "qemu-system-{}".format(appliance_config["qemu"]["arch"])
if self._registry_version >= 8:
# the "arch" field was replaced by the "platform" field in registry version 8
new_config["qemu_path"] = "qemu-system-{}".format(template_properties["platform"])
else:
new_config["qemu_path"] = "qemu-system-{}".format(template_properties["arch"])
if "first_port_name" in appliance_config:
new_config["first_port_name"] = appliance_config["first_port_name"]
@@ -128,24 +186,28 @@ class ApplianceToTemplate:
if "linked_clone" in appliance_config:
new_config["linked_clone"] = appliance_config["linked_clone"]
def _add_docker_config(self, new_config, appliance_config):
def _add_docker_config(self, new_config, template_properties, appliance_config):
new_config.update(appliance_config["docker"])
new_config["template_type"] = "docker"
new_config.update(template_properties)
if "custom_adapters" in appliance_config:
new_config["custom_adapters"] = appliance_config["custom_adapters"]
def _add_dynamips_config(self, new_config, appliance_config):
def _add_dynamips_config(self, new_config, template_properties, appliance_config):
new_config.update(appliance_config["dynamips"])
new_config["template_type"] = "dynamips"
new_config.update(template_properties)
for image in appliance_config["images"]:
new_config[image["type"]] = self._relative_image_path("IOS", image["path"])
new_config["idlepc"] = image.get("idlepc", "")
if self._registry_version < 8:
new_config["idlepc"] = image.get("idlepc", "")
def _add_iou_config(self, new_config, appliance_config):
def _add_iou_config(self, new_config, template_properties, appliance_config):
new_config.update(appliance_config["iou"])
new_config["template_type"] = "iou"
new_config.update(template_properties)
for image in appliance_config["images"]:
if "path" not in image:
raise ConfigException("Disk image is missing")
@@ -197,7 +259,8 @@ class ApplianceToTemplate:
url = "https://raw.githubusercontent.com/GNS3/gns3-registry/master/symbols/{}".format(symbol_id)
try:
self._downloadApplianceSymbol(url, path)
if not self._downloadApplianceSymbol(url, path):
return None
controller.clearStaticCache()
if controller.isRemote():
controller.uploadSymbol(symbol_id, path)
@@ -230,5 +293,7 @@ class ApplianceToTemplate:
log.debug("Error while saving appliance symbol to '{}': {}".format(path, e))
raise
log.debug("Appliance symbol downloaded and saved to '{}'".format(path))
return True
else:
log.warning("Error when downloading appliance symbol from '{}': {}".format(url, reply.errorString()))
log.error("Error when downloading appliance symbol from '{}': {}".format(url, reply.errorString()))
return False

View File

@@ -71,6 +71,11 @@
"format": "uri",
"title": "Website of the vendor"
},
"vendor_logo_url": {
"type": "string",
"format": "uri",
"title": "Link to the vendor logo (used by the GNS3 marketplace)"
},
"documentation_url": {
"type": "string",
"format": "uri",
@@ -86,7 +91,7 @@
"title": "An optional product url on vendor website"
},
"registry_version": {
"enum": [1, 2, 3, 4, 5, 6],
"enum": [1, 2, 3, 4, 5, 6, 7],
"title": "Version of the registry compatible with this appliance"
},
"status": {
@@ -282,6 +287,7 @@
"i82559er",
"i82562",
"i82801",
"igb",
"ne2k_pci",
"pcnet",
"rocker",
@@ -354,6 +360,10 @@
"type": "boolean",
"title": "Enable the Trusted Platform Module (TPM)"
},
"uefi": {
"type": "boolean",
"title": "Enable the UEFI boot mode"
},
"on_close": {
"title": "Action to execute on the VM is closed",
"enum": ["power_off", "shutdown_signal", "save_vm_state"]
@@ -396,7 +406,6 @@
"md5sum": {
"type": "string",
"title": "md5sum of the file",
"type": "string",
"pattern": "^[a-f0-9]{32}$"
},
"filesize": {

View File

@@ -0,0 +1,907 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"title": "JSON schema validating a GNS3 appliance",
"definitions": {
"categories": {
"enum": [
"router",
"multilayer_switch",
"firewall",
"guest"
]
},
"docker_properties": {
"type": "object",
"title": "Docker template properties",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"title": "Name of the template"
},
"category": {
"$ref": "#/definitions/categories",
"title": "Category of the template"
},
"default_name_format": {
"type": "string",
"title": "Default name format"
},
"usage": {
"type": "string",
"title": "How to use the template"
},
"symbol": {
"type": "string",
"title": "Symbol of the template"
},
"image": {
"type": "string",
"title": "Docker image"
},
"adapters": {
"type": "integer",
"title": "Number of ethernet adapters"
},
"start_command": {
"type": "string",
"title": "Command executed when the container start. Empty will use the default"
},
"environment": {
"type": "string",
"title": "One KEY=VAR environment by line"
},
"console_type": {
"enum": [
"telnet",
"vnc",
"http",
"https",
"none"
],
"title": "Type of console"
},
"console_http_port": {
"title": "Internal port in the container of the HTTP server",
"type": "integer"
},
"console_http_path": {
"title": "Path of the web interface",
"type": "string"
},
"console_resolution": {
"title": "Console resolution for VNC, for example 1024x768",
"type": "string",
"pattern": "^[0-9]+x[0-9]+$"
},
"extra_hosts": {
"title": "Docker extra hosts (added to /etc/hosts)",
"type": "string"
},
"extra_volumes": {
"title": "Additional directories to make persistent",
"type": "array"
}
},
"required": [
"image"
]
},
"iou_properties": {
"type": "object",
"title": "IOU template properties",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"title": "Name of the template"
},
"category": {
"$ref": "#/definitions/categories",
"title": "Category of the template"
},
"default_name_format": {
"type": "string",
"title": "Default name format"
},
"usage": {
"type": "string",
"title": "How to use the template"
},
"symbol": {
"type": "string",
"title": "Symbol of the template"
},
"ethernet_adapters": {
"type": "integer",
"title": "Number of ethernet adapters"
},
"serial_adapters": {
"type": "integer",
"title": "Number of serial adapters"
},
"ram": {
"type": "integer",
"title": "Host RAM"
},
"nvram": {
"type": "integer",
"title": "Host NVRAM"
},
"startup_config": {
"type": "string",
"title": "Config loaded at startup"
}
}
},
"dynamips_slot": {
"enum": [
"C2600-MB-2FE",
"C2600-MB-1E",
"PA-A1",
"PA-8E",
"C1700-MB-1FE",
"PA-8T",
"PA-2FE-TX",
"PA-FE-TX",
"PA-GE",
"C2600-MB-2E",
"C7200-IO-FE",
"NM-4T",
"C2600-MB-1FE",
"C7200-IO-2FE",
"PA-POS-OC3",
"PA-4T+",
"C1700-MB-WIC1",
"NM-16ESW",
"C7200-IO-GE-E",
"NM-4E",
"GT96100-FE",
"NM-1FE-TX",
"Leopard-2FE",
"NM-1E",
"PA-4E",
""
]
},
"dynamips_wic": {
"enum": [
"WIC-1ENET",
"WIC-1T",
"WIC-2T",
""
]
},
"dynamips_properties": {
"type": "object",
"title": "Dynamips template properties",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"title": "Name of the template"
},
"category": {
"$ref": "#/definitions/categories",
"title": "Category of the template"
},
"default_name_format": {
"type": "string",
"title": "Default name format"
},
"usage": {
"type": "string",
"title": "How to use the template"
},
"symbol": {
"type": "string",
"title": "Symbol of the template"
},
"chassis": {
"title": "Chassis type",
"enum": [
"1720",
"1721",
"1750",
"1751",
"1760",
"2610",
"2620",
"2610XM",
"2620XM",
"2650XM",
"2621",
"2611XM",
"2621XM",
"2651XM",
"3620",
"3640",
"3660",
""
]
},
"platform": {
"title": "Platform type",
"enum": [
"c1700",
"c2600",
"c2691",
"c3725",
"c3745",
"c3600",
"c7200"
]
},
"ram": {
"title": "Amount of ram",
"type": "integer",
"minimum": 1
},
"nvram": {
"title": "Amount of nvram",
"type": "integer",
"minimum": 1
},
"idlepc": {
"type": "string",
"pattern": "^0x[0-9a-f]{8}"
},
"startup_config": {
"type": "string",
"title": "Config loaded at startup"
},
"wic0": {
"$ref": "#/definitions/dynamips_wic"
},
"wic1": {
"$ref": "#/definitions/dynamips_wic"
},
"wic2": {
"$ref": "#/definitions/dynamips_wic"
},
"slot0": {
"$ref": "#/definitions/dynamips_slot"
},
"slot1": {
"$ref": "#/definitions/dynamips_slot"
},
"slot2": {
"$ref": "#/definitions/dynamips_slot"
},
"slot3": {
"$ref": "#/definitions/dynamips_slot"
},
"slot4": {
"$ref": "#/definitions/dynamips_slot"
},
"slot5": {
"$ref": "#/definitions/dynamips_slot"
},
"slot6": {
"$ref": "#/definitions/dynamips_slot"
},
"midplane": {
"enum": [
"std",
"vxr"
]
},
"npe": {
"enum": [
"npe-100",
"npe-150",
"npe-175",
"npe-200",
"npe-225",
"npe-300",
"npe-400",
"npe-g2"
]
}
},
"required": [
"platform"
]
},
"qemu_disk_interfaces": {
"enum": [
"ide",
"scsi",
"sd",
"mtd",
"floppy",
"pflash",
"virtio",
"sata"
]
},
"qemu_properties": {
"type": "object",
"title": "Qemu template properties",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"title": "Name of the template"
},
"category": {
"$ref": "#/definitions/categories",
"title": "Category of the template"
},
"default_name_format": {
"type": "string",
"title": "Default name format"
},
"usage": {
"type": "string",
"title": "How to use the template"
},
"symbol": {
"type": "string",
"title": "Symbol of the template"
},
"adapter_type": {
"enum": [
"e1000",
"i82550",
"i82551",
"i82557a",
"i82557b",
"i82557c",
"i82558a",
"i82558b",
"i82559a",
"i82559b",
"i82559c",
"i82559er",
"i82562",
"i82801",
"igb",
"ne2k_pci",
"pcnet",
"rtl8139",
"virtio",
"virtio-net-pci",
"vmxnet3"
],
"title": "Type of network adapter"
},
"adapters": {
"type": "integer",
"title": "Number of adapters"
},
"custom_adapters": {
"type": "array",
"title": "Custom adapters",
"items": {
"type": "object",
"properties": {
"adapter_number": {
"title": "Adapter number",
"type": "integer"
},
"port_name": {
"title": "Custom port name",
"type": "string",
"minimum": 1
},
"adapter_type": {
"title": "Custom adapter type",
"type": "string",
"enum": [
"e1000",
"i82550",
"i82551",
"i82557a",
"i82557b",
"i82557c",
"i82558a",
"i82558b",
"i82559a",
"i82559b",
"i82559c",
"i82559er",
"i82562",
"i82801",
"igb",
"ne2k_pci",
"pcnet",
"rtl8139",
"virtio",
"virtio-net-pci",
"vmxnet3"
]
},
"mac_address": {
"title": "Custom MAC address",
"type": "string",
"minimum": 1,
"pattern": "^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$"
}
},
"required": [
"adapter_number"
]
}
},
"first_port_name": {
"type": "string",
"title": "Optional name of the first networking port example: eth0"
},
"port_name_format": {
"type": "string",
"title": "Optional formating of the networking port example: eth{0}"
},
"port_segment_size": {
"type": "integer",
"title": "Optional port segment size. A port segment is a block of port. For example Ethernet0/0 Ethernet0/1 is the module 0 with a port segment size of 2"
},
"linked_clone": {
"type": "boolean",
"title": "False if you don't want to use a single image for all nodes"
},
"ram": {
"type": "integer",
"title": "Ram allocated to the appliance (MB)"
},
"cpus": {
"type": "integer",
"title": "Number of Virtual CPU"
},
"hda_disk_interface": {
"$ref": "#/definitions/qemu_disk_interfaces",
"title": "Disk interface for the installed hda_disk_image"
},
"hdb_disk_interface": {
"$ref": "#/definitions/qemu_disk_interfaces",
"title": "Disk interface for the installed hdb_disk_image"
},
"hdc_disk_interface": {
"$ref": "#/definitions/qemu_disk_interfaces",
"title": "Disk interface for the installed hdc_disk_image"
},
"hdd_disk_interface": {
"$ref": "#/definitions/qemu_disk_interfaces",
"title": "Disk interface for the installed hdd_disk_image"
},
"platform": {
"enum": [
"aarch64",
"alpha",
"arm",
"cris",
"i386",
"lm32",
"m68k",
"microblaze",
"microblazeel",
"mips",
"mips64",
"mips64el",
"mipsel",
"moxie",
"or32",
"ppc",
"ppc64",
"ppcemb",
"s390x",
"sh4",
"sh4eb",
"sparc",
"sparc64",
"tricore",
"unicore32",
"x86_64",
"xtensa",
"xtensaeb"
],
"title": "Platform to emulate"
},
"console_type": {
"enum": [
"telnet",
"vnc",
"spice",
"spice+agent",
"none"
],
"title": "Type of console connection for the administration of the appliance"
},
"boot_priority": {
"enum": [
"d",
"c",
"dc",
"cd",
"n",
"nc",
"nd",
"cn",
"dn"
],
"title": "Optional define the disk boot priory. Refer to -boot option in qemu manual for more details."
},
"kernel_command_line": {
"type": "string",
"title": "Command line parameters send to the kernel"
},
"options": {
"type": "string",
"title": "Optional additional qemu command line options"
},
"cpu_throttling": {
"type": "number",
"minimum": 0,
"maximum": 100,
"title": "Throttle the CPU"
},
"tpm": {
"type": "boolean",
"title": "Enable the Trusted Platform Module (TPM)"
},
"uefi": {
"type": "boolean",
"title": "Enable the UEFI boot mode"
},
"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",
"very high",
"high",
"normal",
"low",
"very low",
"null"
]
}
}
}
},
"properties": {
"appliance_id": {
"title": "Appliance ID",
"type": "string",
"minLength": 36,
"maxLength": 36,
"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
},
"name": {
"type": "string",
"title": "Appliance name"
},
"category": {
"$ref": "#/definitions/categories",
"title": "Category of the appliance"
},
"description": {
"type": "string",
"title": "Description of the appliance. Could be a marketing description"
},
"vendor_name": {
"type": "string",
"title": "Name of the vendor"
},
"vendor_url": {
"type": "string",
"format": "uri",
"title": "Website of the vendor"
},
"vendor_logo_url": {
"type": "string",
"format": "uri",
"title": "Link to the vendor logo (used by the GNS3 marketplace)"
},
"documentation_url": {
"type": "string",
"format": "uri",
"title": "An optional documentation for using the appliance on vendor website"
},
"product_name": {
"type": "string",
"title": "Product name"
},
"product_url": {
"type": "string",
"format": "uri",
"title": "An optional product url on vendor website"
},
"registry_version": {
"enum": [
8
],
"title": "Version of the registry compatible with this appliance (version >=8 introduced breaking changes)"
},
"status": {
"enum": [
"stable",
"experimental",
"broken"
],
"title": "Document if the appliance is working or not"
},
"availability": {
"enum": [
"free",
"with-registration",
"free-to-try",
"service-contract"
],
"title": "About image availability: can be downloaded directly; download requires a free registration; paid but a trial version (time or feature limited) is available; not available publicly"
},
"maintainer": {
"type": "string",
"title": "Maintainer name"
},
"maintainer_email": {
"type": "string",
"format": "email",
"title": "Maintainer email"
},
"installation_instructions": {
"type": "string",
"title": "Optional installation instructions"
},
"usage": {
"type": "string",
"title": "How to use the appliance"
},
"default_username": {
"type": "string",
"title": "Default username for the appliance"
},
"default_password": {
"type": "string",
"title": "Default password for the appliance"
},
"symbol": {
"type": "string",
"title": "An optional symbol for the appliance"
},
"settings": {
"type": "array",
"title": "Settings for running the appliance",
"items": {
"type": "object",
"title": "Emulator settings",
"properties": {
"name": {
"type": "string",
"title": "Name of the settings set"
},
"default": {
"type": "boolean",
"title": "Whether these are the default settings"
},
"inherit_default_properties": {
"type": "boolean",
"title": "Whether the default properties should be used",
"default": "true"
},
"template_type": {
"enum": [
"docker",
"iou",
"dynamips",
"qemu"
],
"title": "Type of emulator properties"
},
"template_properties": {
"type": "object",
"title": "Properties for the template",
"oneOf": [
{
"$ref": "#/definitions/qemu_properties"
},
{
"$ref": "#/definitions/dynamips_properties"
},
{
"$ref": "#/definitions/iou_properties"
},
{
"$ref": "#/definitions/docker_properties"
}
]
}
},
"required": [
"template_type",
"template_properties"
]
}
}
},
"images": {
"type": "array",
"title": "Images for this appliance",
"items": {
"type": "object",
"title": "An image file",
"properties": {
"filename": {
"type": "string",
"title": "Filename"
},
"version": {
"type": "string",
"title": "Version of the image file"
},
"md5sum": {
"type": "string",
"title": "MD5 cheksum of the image file",
"pattern": "^[a-f0-9]{32}$"
},
"checksum": {
"type": "string",
"title": "checksum of the image file"
},
"checksum_type": {
"title": "checksum type of the image file",
"enum": [
"md5"
]
},
"filesize": {
"type": "integer",
"title": "File size in bytes of the image file"
},
"download_url": {
"type": "string",
"format": "uri",
"title": "Download URL where you can download the image file from a browser"
},
"direct_download_url": {
"type": "string",
"format": "uri",
"title": "Optional. Non authenticated URL to the image file where you can download the image directly"
},
"compression": {
"enum": [
"bzip2",
"gzip",
"lzma",
"xz",
"rar",
"zip",
"7z"
],
"title": "Optional, compression type of direct download URL image."
},
"compression_target": {
"type": "string",
"title": "Optional, file name of the image file inside the compressed file."
}
},
"anyOf": [
{
"required": [
"filename",
"version",
"md5sum",
"filesize"
]
},
{
"required": [
"filename",
"version",
"checksum",
"filesize"
]
}
]
}
},
"versions": {
"type": "array",
"title": "Versions of the appliance",
"items": {
"type": "object",
"title": "A version of the appliance",
"properties": {
"name": {
"type": "string",
"title": "Name of the version"
},
"settings": {
"type": "string",
"title": "Template settings to use to run the version"
},
"category": {
"$ref": "#/definitions/categories",
"title": "Category of the version"
},
"installation_instructions": {
"type": "string",
"title": "Optional installation instructions for the version"
},
"usage": {
"type": "string",
"title": "Optional instructions about using the version"
},
"default_username": {
"type": "string",
"title": "Default username for the version"
},
"default_password": {
"type": "string",
"title": "Default password for the version"
},
"symbol": {
"type": "string",
"title": "An optional symbol for the version"
},
"images": {
"type": "object",
"title": "Images used for this version",
"properties": {
"kernel_image": {
"type": "string",
"title": "Kernel image (Qemu only)"
},
"initrd": {
"type": "string",
"title": "Initrd disk image (Qemu only)"
},
"image": {
"type": "string",
"title": "OS image (IOU and Dynamips only)"
},
"bios_image": {
"type": "string",
"title": "Bios image (Qemu only)"
},
"hda_disk_image": {
"type": "string",
"title": "Hda disk image (Qemu only)"
},
"hdb_disk_image": {
"type": "string",
"title": "Hdc disk image (Qemu only)"
},
"hdc_disk_image": {
"type": "string",
"title": "Hdd disk image (Qemu only)"
},
"hdd_disk_image": {
"type": "string",
"title": "Hdd disk image (Qemu only)"
},
"cdrom_image": {
"type": "string",
"title": "cdrom image (Qemu only)"
}
}
}
},
"required": [
"name"
]
}
},
"required": [
"appliance_id",
"name",
"category",
"description",
"vendor_name",
"vendor_url",
"product_name",
"registry_version",
"status",
"maintainer",
"maintainer_email",
"settings"
]
}

View File

@@ -55,28 +55,29 @@ if sys.platform.startswith("win"):
# windows 32-bit
program_files_x86 = program_files = os.environ["PROGRAMFILES"]
PRECONFIGURED_TELNET_CONSOLE_COMMANDS = {'Putty (normal standalone version)': 'putty_standalone.exe -telnet %h %p -loghost "%d"',
'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),
'SuperPutty': r'SuperPutty.exe -telnet "%h -P %p -wt \"%d\""',
'SecureCRT': r'"{}\VanDyke Software\SecureCRT\SecureCRT.exe" /N "%d" /T /TELNET %h %p'.format(program_files),
'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)}
PRECONFIGURED_TELNET_CONSOLE_COMMANDS = {'Putty (normal standalone version)': 'putty_standalone.exe -telnet {host} {port} -loghost "{name}"',
'KiTTY': r'kitty -title "{name}" telnet://{host} {port}',
'MobaXterm': r'"{}\Mobatek\MobaXterm Personal Edition\MobaXterm.exe" -newtab "telnet {{host}} {{port}}"'.format(program_files_x86),
'Royal TS V3': r'{}\code4ward.net\Royal TS V3\RTS3App.exe /connectadhoc:{{host}} /adhoctype:terminal /p:IsTelnetConnection="true" /p:ConnectionType="telnet;Telnet Connection" /p:Port="{{port}}" /p:Name="{{name}}"'.format(program_files),
'Royal TS V5': r'"{}\Royal TS V5\RoyalTS.exe" /protocol:terminal /using:adhoc /uri:"{{host}}" /property:Port="{{port}}" /property:IsTelnetConnection="true" /property:Name="{{name}}"'.format(program_files_x86),
'SuperPutty': r'SuperPutty.exe -telnet "{host} -P {port} -wt \"{name}\""',
'SecureCRT': r'"{}\VanDyke Software\SecureCRT\SecureCRT.exe" /N "{{name}}" /T /TELNET {{host}} {{port}}'.format(program_files),
'SecureCRT (personal profile)': r'"{}\AppData\Local\VanDyke Software\SecureCRT\SecureCRT.exe" /T /N "{{name}}" /TELNET {{host}} {{port}}'.format(userprofile),
'TeraTerm Pro': r'"{}\teraterm\ttermpro.exe" /W="{{name}}" /M="ttstart.macro" /T=1 {{host}} {{port}}'.format(program_files_x86),
'Telnet': 'telnet {host} {port}',
'Windows Terminal': 'wt.exe -w 1 new-tab --suppressApplicationTitle --title {name} telnet {host} {port}',
'Xshell 4': r'"{}\NetSarang\Xshell 4\xshell.exe" -url telnet://{{host}}:{{port}}'.format(program_files_x86),
'Xshell 5': r'"{}\NetSarang\Xshell 5\xshell.exe" -url telnet://{{host}}:{{port}} -newtab {{name}}'.format(program_files_x86),
'ZOC 6': r'"{}\ZOC6\zoc.exe" "/TELNET:{{host}}:{{port}}" /TABBED "/TITLE:{{name}}"'.format(program_files_x86)}
# default on Windows
if shutil.which("Solar-PuTTY.exe"):
# Solar-Putty is the default if it is installed.
PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Solar-Putty (included with GNS3)"] = 'Solar-PuTTY.exe --telnet --hostname %h --port %p --name "%d"'
PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Solar-Putty (included with GNS3)"] = 'Solar-PuTTY.exe --telnet --hostname {host} --port {port} --name "{name}"'
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Solar-Putty (included with GNS3)"]
DEFAULT_DELAY_CONSOLE_ALL = 1500
else:
PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Solar-Putty (included with GNS3 downloaded from gns3.com)"] = 'Solar-PuTTY.exe --telnet --hostname %h --port %p --name "%d"'
PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Solar-Putty (included with GNS3 downloaded from gns3.com)"] = 'Solar-PuTTY.exe --telnet --hostname {host} --port {port} --name "{name}"'
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Putty (normal standalone version)"]
elif sys.platform.startswith("darwin"):
@@ -86,7 +87,7 @@ elif sys.platform.startswith("darwin"):
r""" -e 'set posix_path to do shell script "echo \"$PATH\""'"""
r""" -e 'tell application "Terminal"'"""
r""" -e 'activate'"""
r""" -e 'do script "echo -n -e \"\\033]0;%d\\007\"; clear; PATH=" & quoted form of posix_path & " telnet %h %p ; exit"'"""
r""" -e 'do script "echo -n -e \"\\033]0;{name}\\007\"; clear; PATH=" & quoted form of posix_path & " telnet {host} {port} ; exit"'"""
r""" -e 'end tell'""",
'Terminal tabbed (experimental)': r"""osascript"""
r""" -e 'set posix_path to do shell script "echo \"$PATH\""'"""
@@ -102,7 +103,7 @@ elif sys.platform.startswith("darwin"):
r""" -e 'repeat while the busy of window 1 = true'"""
r""" -e 'delay 0.01'"""
r""" -e 'end repeat'"""
r""" -e 'do script "echo -n -e \"\\033]0;%d\\007\"; clear; PATH=" & quoted form of posix_path & " telnet %h %p ; exit" in window 1'"""
r""" -e 'do script "echo -n -e \"\\033]0;{name}\\007\"; clear; PATH=" & quoted form of posix_path & " telnet {host} {port} ; exit" in window 1'"""
r""" -e 'end tell'""",
'iTerm2 2.x': r"""osascript"""
r""" -e 'set posix_path to do shell script "echo \"$PATH\""'"""
@@ -117,7 +118,7 @@ elif sys.platform.startswith("darwin"):
r""" -e ' set s to (make new session at the end of sessions)'"""
r""" -e ' tell s'"""
r""" -e ' exec command "sh"'"""
r""" -e ' write text "PATH=" & quoted form of posix_path & " exec telnet %h %p"'"""
r""" -e ' write text "PATH=" & quoted form of posix_path & " exec telnet {host} {port}"'"""
r""" -e ' end tell'"""
r""" -e 'end tell'"""
r""" -e 'end tell'""",
@@ -134,33 +135,33 @@ elif sys.platform.startswith("darwin"):
r""" -e ' create tab with default profile command "sh"'"""
r""" -e ' set s to current session'"""
r""" -e ' tell s'"""
r""" -e ' set name to "%d"'"""
r""" -e ' write text "PATH=" & quoted form of posix_path & " exec telnet %h %p"'"""
r""" -e ' set name to "{name}"'"""
r""" -e ' write text "PATH=" & quoted form of posix_path & " exec telnet {host} {port}"'"""
r""" -e ' end tell'"""
r""" -e 'end tell'"""
r""" -e 'end tell'""",
'Royal TSX': "open 'rtsx://telnet%3A%2F%2F%h:%p'",
'SecureCRT': '/Applications/SecureCRT.app/Contents/MacOS/SecureCRT /N "%d" /T /TELNET %h %p',
'ZOC 6': '/Applications/zoc6.app/Contents/MacOS/zoc6 "/TELNET:%h:%p" /TABBED "/TITLE:%d"',
'ZOC 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"'
'Royal TSX': "open 'rtsx://telnet%3A%2F%2F{host}:{port}'",
'SecureCRT': '/Applications/SecureCRT.app/Contents/MacOS/SecureCRT /N "{name}" /T /TELNET {host} {port}',
'ZOC 6': '/Applications/zoc6.app/Contents/MacOS/zoc6 "/TELNET:{host}:{port}" /TABBED "/TITLE:{name}"',
'ZOC 7': '/Applications/zoc7.app/Contents/MacOS/zoc7 "/TELNET:{host}:{port}" /TABBED "/TITLE:{name}"',
'ZOC 8': '/Applications/zoc8.app/Contents/MacOS/zoc8 "/TELNET:{host}:{port}" /TABBED "/TITLE:{name}"'
}
# default Mac OS X Telnet console command
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Terminal"]
else:
PRECONFIGURED_TELNET_CONSOLE_COMMANDS = {'Xterm': 'xterm -T "%d" -e "telnet %h %p"',
'Putty': 'putty -telnet %h %p -title "%d" -sl 2500 -fg SALMON1 -bg BLACK',
'Gnome Terminal': 'gnome-terminal -t "%d" -e "telnet %h %p"',
'Xfce4 Terminal': 'xfce4-terminal --tab -T "%d" -e "telnet %h %p"',
'ROXTerm': 'roxterm -n "%d" --tab -e "telnet %h %p"',
'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"',
'terminator': 'terminator -e "telnet %h %p" -T "%d"',
'urxvt': 'urxvt -title %d -e telnet %h %p',
'kitty': 'kitty -T %d telnet %h %p'}
PRECONFIGURED_TELNET_CONSOLE_COMMANDS = {'Xterm': 'xterm -T "{name}" -e "telnet {host} {port}"',
'Putty': 'putty -telnet {host} {port} -title "{name}" -sl 2500 -fg SALMON1 -bg BLACK',
'Gnome Terminal': 'gnome-terminal --tab -t "{name}" -- telnet {host} {port}',
'Xfce4 Terminal': 'xfce4-terminal --tab -T "{name}" -e "telnet {host} {port}"',
'ROXTerm': 'roxterm -n "{name}" --tab -e "telnet {host} {port}"',
'KDE Konsole': 'konsole --new-tab -p tabtitle="{name}" -e "telnet {host} {port}"',
'SecureCRT': 'SecureCRT /T /N "{name}" /TELNET {host} {port}',
'Mate Terminal': 'mate-terminal --tab -e "telnet {host} {port}" -t "{name}"',
'terminator': 'terminator -e "telnet {host} {port}" -T "{name}"',
'urxvt': 'urxvt -title {name} -e telnet {host} {port}',
'kitty': 'kitty -T {name} telnet {host} {port}'}
# default Telnet console command on other systems
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Xterm"]
@@ -168,14 +169,17 @@ else:
if sys.platform.startswith("linux"):
distro_name = distro.name()
if distro_name == "Debian" or distro_name == "Ubuntu" or distro_name == "LinuxMint":
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Gnome Terminal"]
if shutil.which("mate-terminal"):
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Mate Terminal"]
else:
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Gnome Terminal"]
# Pre-configured VNC console commands on various OSes
if sys.platform.startswith("win"):
# Windows
PRECONFIGURED_VNC_CONSOLE_COMMANDS = {
'TightVNC (included with GNS3)': 'tvnviewer.exe %h:%p',
'UltraVNC': r'"{}\uvnc bvba\UltraVNC\vncviewer.exe" %h:%p'.format(program_files)
'TightVNC (included with GNS3)': 'tvnviewer.exe {host}:{port}',
'UltraVNC': r'"{}\uvnc bvba\UltraVNC\vncviewer.exe" {{host}}:{{port}}'.format(program_files)
}
# default Windows VNC console command
@@ -187,11 +191,11 @@ elif sys.platform.startswith("darwin"):
'OSX builtin screen sharing': "osascript"
" -e 'tell application \"Screen Sharing\"'"
" -e ' display dialog \"WARNING OSX VNC support is limited if you have trouble connecting to a device please use an alternative client like Chicken of the VNC.\" buttons {\"OK\"} default button 1 with icon caution with title \"GNS3\"'"
" -e ' open location \"vnc://%h:%p\"'"
" -e ' open location \"vnc://{host}:{port}\"'"
" -e 'end tell'",
'Chicken of the VNC': "/Applications/Chicken.app/Contents/MacOS/Chicken %h:%p",
'Chicken of the VNC < 2.2': r"/Applications/Chicken\ of\ the\ VNC.app/Contents/MacOS/Chicken\ of\ the\ VNC %h:%p",
'Royal TSX': "open 'rtsx://vnc%3A%2F%2F%h:%p'",
'Chicken of the VNC': "/Applications/Chicken.app/Contents/MacOS/Chicken {host}:{port}",
'Chicken of the VNC < 2.2': r"/Applications/Chicken\ of\ the\ VNC.app/Contents/MacOS/Chicken\ of\ the\ VNC {host}:{port}",
'Royal TSX': "open 'rtsx://vnc%3A%2F%2F{host}:{port}'",
}
# default Mac OS X VNC console command
@@ -199,10 +203,10 @@ elif sys.platform.startswith("darwin"):
else:
PRECONFIGURED_VNC_CONSOLE_COMMANDS = {
'TightVNC': 'vncviewer %h:%p',
'Vinagre': 'vinagre %h::%p',
'gvncviewer': 'gvncviewer %h:%P',
'Remote Viewer': 'remote-viewer vnc://%h:%p'
'TightVNC': 'vncviewer {host}:{port}',
'Vinagre': 'vinagre {host}::{port}',
'gvncviewer': 'gvncviewer {host}:{display}',
'Remote Viewer': 'remote-viewer vnc://{host}:{port}'
}
# default VNC console command on other systems
@@ -212,7 +216,7 @@ else:
if sys.platform.startswith("win"):
# Windows
PRECONFIGURED_SPICE_CONSOLE_COMMANDS = {
'Remote Viewer': r'"{}\VirtViewer v11.0-256\bin\remote-viewer.exe" spice://%h:%p'.format(program_files),
'Remote Viewer': r'"{}\VirtViewer v11.0-256\bin\remote-viewer.exe" spice://{{host}}:{{port}}'.format(program_files),
}
# default Windows SPICE console command
@@ -221,7 +225,7 @@ if sys.platform.startswith("win"):
elif sys.platform.startswith("darwin"):
# Mac OS X
PRECONFIGURED_SPICE_CONSOLE_COMMANDS = {
'Remote Viewer': '/Applications/RemoteViewer.app/Contents/MacOS/RemoteViewer spice://%h:%p',
'Remote Viewer': '/Applications/RemoteViewer.app/Contents/MacOS/RemoteViewer spice://{host}:{port}',
}
# default Mac OS X SPICE console command
@@ -229,7 +233,7 @@ elif sys.platform.startswith("darwin"):
else:
PRECONFIGURED_SPICE_CONSOLE_COMMANDS = {
'Remote Viewer': 'remote-viewer spice://%h:%p',
'Remote Viewer': 'remote-viewer spice://{host}:{port}',
}
# default SPICE console command on other systems
@@ -240,29 +244,28 @@ WIRESHARK_NORMAL_CAPTURE = "Wireshark Traditional Capture"
WIRESHARK_LIVE_TRAFFIC_CAPTURE = "Wireshark Live Traffic Capture"
if sys.platform.startswith("win"):
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: r"{}\Wireshark\wireshark.exe %c".format(program_files),
WIRESHARK_LIVE_TRAFFIC_CAPTURE: r'tail.exe -f -c +0b %c | "{}\Wireshark\wireshark.exe" -o "gui.window_title:%d" -k -i -'.format(program_files)}
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: r'{}\Wireshark\wireshark.exe {{pcap_file}} --capture-comment "{{project}} {{link_description}}"'.format(program_files),
WIRESHARK_LIVE_TRAFFIC_CAPTURE: r'tail.exe -f -c +0b {{pcap_file}} | "{}\Wireshark\wireshark.exe" --capture-comment "{{project}} {{link_description}}" -o "gui.window_title:{{link_description}}" -k -i -'.format(program_files)}
elif sys.platform.startswith("darwin"):
# Mac OS X
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: "/usr/bin/open -a /Applications/Wireshark.app %c",
"Wireshark V1.X Live Traffic Capture": 'tail -f -c +0 %c | /Applications/Wireshark.app/Contents/Resources/bin/wireshark -o "gui.window_title:%d" -k -i -',
WIRESHARK_LIVE_TRAFFIC_CAPTURE: 'tail -f -c +0 %c | /Applications/Wireshark.app/Contents/MacOS/Wireshark -o "gui.window_title:%d" -k -i -'}
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: '/usr/bin/open -a /Applications/Wireshark.app {pcap_file} --capture-comment {project} {link_description}"',
WIRESHARK_LIVE_TRAFFIC_CAPTURE: 'tail -f -c +0 {pcap_file} | /Applications/Wireshark.app/Contents/MacOS/Wireshark --capture-comment "{project} {link_description}" -o "gui.window_title:{link_description}" -k -i -'}
elif sys.platform.startswith("freebsd"):
# FreeBSD
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: "wireshark %c",
WIRESHARK_LIVE_TRAFFIC_CAPTURE: 'gtail -f -c +0b %c | wireshark -o "gui.window_title:%d" -k -i -'}
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: 'wireshark {pcap_file} --capture-comment "{project} {link_description}"',
WIRESHARK_LIVE_TRAFFIC_CAPTURE: 'gtail -f -c +0b {pcap_file} | wireshark --capture-comment "{project} {link_description}" -o "gui.window_title:{link_description}" -k -i -'}
else:
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: "wireshark %c",
WIRESHARK_LIVE_TRAFFIC_CAPTURE: 'tail -f -c +0b %c | wireshark -o "gui.window_title:%d" -k -i -'}
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: 'wireshark {pcap_file} --capture-comment "{project} {link_description}"',
WIRESHARK_LIVE_TRAFFIC_CAPTURE: 'tail -f -c +0b {pcap_file} | wireshark --capture-comment "{project} {link_description}" -o "gui.window_title:{link_description}" -k -i -'}
DEFAULT_PACKET_CAPTURE_READER_COMMAND = PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS[WIRESHARK_LIVE_TRAFFIC_CAPTURE]
DEFAULT_PACKET_CAPTURE_ANALYZER_COMMAND = ""
if sys.platform.startswith("win"):
# Windows 64-bit
DEFAULT_PACKET_CAPTURE_ANALYZER_COMMAND = r'"{}\SolarWinds\ResponseTimeViewer\ResponseTimeViewer.exe" %c'.format(program_files_x86)
DEFAULT_PACKET_CAPTURE_ANALYZER_COMMAND = r'"{}\SolarWinds\ResponseTimeViewer\ResponseTimeViewer.exe" {{pcap_file}}'.format(program_files_x86)
STYLES = ["Charcoal", "Classic", "Legacy"]
@@ -284,7 +287,6 @@ GENERAL_SETTINGS = {
"check_for_update": True,
"overlay_notifications": True,
"experimental_features": False,
"send_stats": True,
"stats_visitor_id": str(uuid.uuid4()), # An anonymous id for stats
"last_check_for_update": 0,
"telnet_console_command": DEFAULT_TELNET_CONSOLE_COMMAND,

View File

@@ -54,14 +54,24 @@ def spiceConsole(node, port, command):
command = command.replace("%h", host)
command = command.replace("%p", str(port))
command = command.replace("%d", name.replace('"', '\\"'))
command = command.replace("%P", node.project().name().replace('"', '\\"'))
command = command.replace("%i", node.project().id())
command = command.replace("%n", str(node.id()))
command = command.replace("%c", Controller.instance().httpClient().fullUrl())
command = command.replace("{host}", host)
command = command.replace("{port}", str(port))
command = command.replace("{name}", name.replace('"', '\\"'))
command = command.replace("{project}", node.project().name())
command = command.replace("{project_id}", node.project().id())
command = command.replace("{node_id}", str(node.id()))
command = command.replace("{url}", Controller.instance().httpClient().fullUrl())
try:
log.debug('starting SPICE program "{}"'.format(command))
if sys.platform.startswith("win"):
# use the string on Windows
subprocess.Popen(command)
subprocess.Popen(command, env=os.environ)
else:
# use arguments on other platforms
args = shlex.split(command)

View File

@@ -25,6 +25,8 @@ import os
import sys
import shlex
import subprocess
import psutil
from .main_window import MainWindow
from .controller import Controller
@@ -34,6 +36,39 @@ log = logging.getLogger(__name__)
console_mutex = QtCore.QMutex()
def gnome_terminal_env():
uid = os.getuid()
# get list of processes of current user
procs = [p.info for p in psutil.process_iter(
attrs=['name', 'pid', 'ppid', 'create_time', 'uids']
) if p.info['uids'].real == uid]
# get pid of gnome-terminal-server process
gnome_terminal_server_pid = [p['pid'] for p in procs if p['name'] == "gnome-terminal-server"]
if not gnome_terminal_server_pid:
return {}
gnome_terminal_server_pid = gnome_terminal_server_pid[0]
# get subprocesses of gnome-terminal-server
gnome_terminal_server_children = [p for p in procs if p['ppid'] == gnome_terminal_server_pid]
gnome_terminal_server_children.sort(key=lambda p: p['create_time'], reverse=True)
# return the gnome-terminal environment variables of the first subprocess named telnet
for proc in gnome_terminal_server_children:
if proc['name'] == "telnet":
try:
env = psutil.Process(proc['pid']).environ()
if 'GNOME_TERMINAL_SERVICE' in env and \
'GNOME_TERMINAL_SCREEN' in env:
return {'GNOME_TERMINAL_SERVICE': env['GNOME_TERMINAL_SERVICE'],
'GNOME_TERMINAL_SCREEN': env['GNOME_TERMINAL_SCREEN']}
except psutil.Error:
pass
return {}
class ConsoleThread(QtCore.QThread):
consoleError = QtCore.pyqtSignal(str)
@@ -52,7 +87,7 @@ class ConsoleThread(QtCore.QThread):
if sys.platform.startswith("win"):
# use the string on Windows
subprocess.call(command)
subprocess.Popen(command, env=os.environ)
else:
# use arguments on other platforms
try:
@@ -60,7 +95,14 @@ class ConsoleThread(QtCore.QThread):
except ValueError:
self.consoleError.emit("Syntax error in command: '{}'".format(command))
return
subprocess.call(args, env=os.environ)
env = os.environ.copy()
# special case to force gnome-terminal to correctly use tabs on Linux
if sys.platform.startswith("linux") and "gnome-terminal" in args[0] and "--tab" in command:
# inject gnome-terminal environment variables
if "GNOME_TERMINAL_SERVICE" not in env or "GNOME_TERMINAL_SCREEN" not in env:
env.update(gnome_terminal_env())
subprocess.Popen(args, env=env)
def run(self):
@@ -71,10 +113,19 @@ class ConsoleThread(QtCore.QThread):
command = self._command.replace("%h", host)
command = command.replace("%p", str(port))
command = command.replace("%d", self._name.replace('"', '\\"'))
command = command.replace("%P", self._node.project().name().replace('"', '\\"'))
command = command.replace("%i", self._node.project().id())
command = command.replace("%n", str(self._node.id()))
command = command.replace("%c", Controller.instance().httpClient().fullUrl())
command = command.replace("{host}", host)
command = command.replace("{port}", str(port))
command = command.replace("{name}", self._name.replace('"', '\\"'))
command = command.replace("{project}", self._node.project().name().replace('"', '\\"'))
command = command.replace("{project_id}", self._node.project().id())
command = command.replace("{node_id}", str(self._node.id()))
command = command.replace("{url}", Controller.instance().httpClient().fullUrl())
# If the console use an apple script we lock to avoid multiple console
# to interact at the same time
if sys.platform.startswith("darwin") and "osascript" in command:

View File

@@ -209,11 +209,11 @@ class Topology(QtCore.QObject):
project.setId(project_settings["project_id"])
self.setProject(project)
project.load()
self._main_window.uiStatusBar.showMessage("Project loaded", 2000)
self._main_window.uiStatusBar.showMessage("Project loaded", 5000)
else:
self.setProject(project)
project.create()
self._main_window.uiStatusBar.showMessage("Project created", 2000)
self._main_window.uiStatusBar.showMessage("Project created", 5000)
return project
def restoreSnapshot(self, project_id):
@@ -225,7 +225,7 @@ class Topology(QtCore.QObject):
project = self._project
self.setProject(project, snapshot=True)
project.load()
self._main_window.uiStatusBar.showMessage("Snapshot restored", 2000)
self._main_window.uiStatusBar.showMessage("Snapshot restored", 5000)
def loadProject(self, path):
"""
@@ -241,7 +241,7 @@ class Topology(QtCore.QObject):
from .project import Project
self.setProject(Project())
self._project.load(path)
self._main_window.uiStatusBar.showMessage("Project loaded {}".format(path), 2000)
self._main_window.uiStatusBar.showMessage("Project loaded {}".format(path), 5000)
return True
def editReadme(self):

View File

@@ -415,3 +415,22 @@ class TopologySummaryView(QtWidgets.QTreeWidget):
for link in self._topology.links():
if link.suspended():
link.toggleSuspend()
def keyPressEvent(self, event):
"""
Handles key press events
"""
from .main_window import MainWindow
view = MainWindow.instance().uiGraphicsView
# only deleting a link or node is supported for now
if event.key() == QtCore.Qt.Key_Delete:
current_item = self.currentItem()
if isinstance(current_item, TopologyNodeItem):
current_item.node().delete()
else:
link = current_item.data(0, QtCore.Qt.UserRole)
for item in view.scene().items():
if isinstance(item, LinkItem) and item.link() == link:
item.delete()
super().keyPressEvent(event)

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>900</width>
<height>601</height>
<width>726</width>
<height>428</height>
</rect>
</property>
<property name="sizePolicy">
@@ -219,6 +219,27 @@
</item>
</layout>
</widget>
<widget class="QWizardPage" name="uiInstructionsPage">
<property name="title">
<string>Installation instructions</string>
</property>
<property name="subTitle">
<string>Please read the following instructions in order to install your new appliance.</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QTextEdit" name="uiInstructionsTextEdit">
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="uiFilesWizardPage">
<property name="title">
<string>Required files</string>
@@ -232,12 +253,12 @@
<property name="indentation">
<number>20</number>
</property>
<attribute name="headerDefaultSectionSize">
<number>120</number>
</attribute>
<attribute name="headerMinimumSectionSize">
<number>20</number>
</attribute>
<attribute name="headerDefaultSectionSize">
<number>120</number>
</attribute>
<column>
<property name="text">
<string>Appliance version and files</string>

View File

@@ -2,16 +2,19 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/appliance_wizard.ui'
#
# Created by: PyQt5 UI code generator 5.9
# Created by: PyQt5 UI code generator 5.15.9
#
# 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_ApplianceWizard(object):
def setupUi(self, ApplianceWizard):
ApplianceWizard.setObjectName("ApplianceWizard")
ApplianceWizard.resize(900, 601)
ApplianceWizard.resize(726, 428)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@@ -112,6 +115,14 @@ class Ui_ApplianceWizard(object):
self.uiQemuListComboBox.setObjectName("uiQemuListComboBox")
self.horizontalLayout_2.addWidget(self.uiQemuListComboBox)
ApplianceWizard.addPage(self.uiQemuWizardPage)
self.uiInstructionsPage = QtWidgets.QWizardPage()
self.uiInstructionsPage.setObjectName("uiInstructionsPage")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.uiInstructionsPage)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.uiInstructionsTextEdit = QtWidgets.QTextEdit(self.uiInstructionsPage)
self.uiInstructionsTextEdit.setObjectName("uiInstructionsTextEdit")
self.verticalLayout_3.addWidget(self.uiInstructionsTextEdit)
ApplianceWizard.addPage(self.uiInstructionsPage)
self.uiFilesWizardPage = QtWidgets.QWizardPage()
self.uiFilesWizardPage.setObjectName("uiFilesWizardPage")
self.verticalLayout = QtWidgets.QVBoxLayout(self.uiFilesWizardPage)
@@ -174,6 +185,13 @@ class Ui_ApplianceWizard(object):
self.uiQemuWizardPage.setTitle(_translate("ApplianceWizard", "Qemu settings"))
self.uiQemuWizardPage.setSubTitle(_translate("ApplianceWizard", "Please choose the qemu binary that will be used to run this appliance."))
self.uiQemuListLabel.setText(_translate("ApplianceWizard", "Qemu binary:"))
self.uiInstructionsPage.setTitle(_translate("ApplianceWizard", "Installation instructions"))
self.uiInstructionsPage.setSubTitle(_translate("ApplianceWizard", "Please read the following instructions in order to install your new appliance."))
self.uiInstructionsTextEdit.setHtml(_translate("ApplianceWizard", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'Ubuntu\'; font-size:11pt; font-weight:400; font-style:normal;\">\n"
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p></body></html>"))
self.uiFilesWizardPage.setTitle(_translate("ApplianceWizard", "Required files"))
self.uiFilesWizardPage.setSubTitle(_translate("ApplianceWizard", "The following files are required to install the appliance"))
self.uiApplianceVersionTreeWidget.headerItem().setText(0, _translate("ApplianceWizard", "Appliance version and files"))
@@ -191,5 +209,4 @@ class Ui_ApplianceWizard(object):
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'Ubuntu\'; font-size:11pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">The default username/password is admin/admin. A default configuration is present.</p></body></html>"))
from . import resources_rc

View File

@@ -9,16 +9,10 @@
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>147</height>
<width>460</width>
<height>142</height>
</rect>
</property>
<property name="maximumSize">
<size>
<width>500</width>
<height>147</height>
</size>
</property>
<property name="windowTitle">
<string>Packet capture</string>
</property>

View File

@@ -2,19 +2,20 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/capture_dialog.ui'
#
# Created: Mon May 30 21:49:29 2016
# by: PyQt5 UI code generator 5.2.1
# 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
class Ui_CaptureDialog(object):
def setupUi(self, CaptureDialog):
CaptureDialog.setObjectName("CaptureDialog")
CaptureDialog.setWindowModality(QtCore.Qt.WindowModal)
CaptureDialog.resize(500, 147)
CaptureDialog.setMaximumSize(QtCore.QSize(500, 147))
CaptureDialog.resize(460, 142)
CaptureDialog.setModal(False)
self.gridLayout = QtWidgets.QGridLayout(CaptureDialog)
self.gridLayout.setObjectName("gridLayout")
@@ -73,5 +74,4 @@ class Ui_CaptureDialog(object):
self.uiLinkTypeLabel.setText(_translate("CaptureDialog", "Link type:"))
self.uiFileNameLabel.setText(_translate("CaptureDialog", "File name:"))
self.uiStartCommandCheckBox.setText(_translate("CaptureDialog", "Start the capture visualization program"))
from . import resources_rc

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>965</width>
<height>547</height>
<width>576</width>
<height>346</height>
</rect>
</property>
<property name="sizePolicy">
@@ -70,7 +70,7 @@
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Or customize the command in the next input field. &lt;br/&gt;The following variables are replaced by GNS3: &lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%h: console IP or hostname&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%p: console port&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%P: VNC display&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%s: path of the serial connection&lt;/li&gt;&lt;/ul&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%d: title of the console&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%i: Project UUID&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%c: server URL (&lt;span style=&quot; font-style:italic;&quot;&gt;http://user:password@server:port&lt;/span&gt;)&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Or customize the command in the next input field. &lt;br/&gt;The following variables are replaced by GNS3: &lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%h or {host}: console IP or hostname&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%p or {port}: console port&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%D or {display}: VNC display&lt;/li&gt;&lt;/ul&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%d or {name}: title of the console&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%P or {project}: project name&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%i or {project_id}: project UUID&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%c or {url}: server URL (&lt;span style=&quot; font-style:italic;&quot;&gt;http://user:password@server:port&lt;/span&gt;)&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>

View File

@@ -2,16 +2,19 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/console_command_dialog.ui'
#
# Created by: PyQt5 UI code generator 5.5.1
# 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
class Ui_uiConsoleCommandDialog(object):
def setupUi(self, uiConsoleCommandDialog):
uiConsoleCommandDialog.setObjectName("uiConsoleCommandDialog")
uiConsoleCommandDialog.resize(965, 547)
uiConsoleCommandDialog.resize(576, 346)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@@ -57,8 +60,8 @@ class Ui_uiConsoleCommandDialog(object):
self.verticalLayout.addWidget(self.uiButtonBox)
self.retranslateUi(uiConsoleCommandDialog)
self.uiButtonBox.accepted.connect(uiConsoleCommandDialog.accept)
self.uiButtonBox.rejected.connect(uiConsoleCommandDialog.reject)
self.uiButtonBox.accepted.connect(uiConsoleCommandDialog.accept) # type: ignore
self.uiButtonBox.rejected.connect(uiConsoleCommandDialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(uiConsoleCommandDialog)
def retranslateUi(self, uiConsoleCommandDialog):
@@ -67,5 +70,4 @@ class Ui_uiConsoleCommandDialog(object):
self.label_2.setText(_translate("uiConsoleCommandDialog", "Choose a predefined command:"))
self.uiRemovePushButton.setText(_translate("uiConsoleCommandDialog", "Remove"))
self.uiSavePushButton.setText(_translate("uiConsoleCommandDialog", "Save"))
self.label.setText(_translate("uiConsoleCommandDialog", "<html><head/><body><p>Or customize the command in the next input field. <br/>The following variables are replaced by GNS3: </p><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%h: console IP or hostname</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%p: console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%P: VNC display</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%s: path of the serial connection</li></ul><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d: title of the console</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i: Project UUID</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%c: server URL (<span style=\" font-style:italic;\">http://user:password@server:port</span>)</li></ul></body></html>"))
self.label.setText(_translate("uiConsoleCommandDialog", "<html><head/><body><p>Or customize the command in the next input field. <br/>The following variables are replaced by GNS3: </p><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%h or {host}: console IP or hostname</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%p or {port}: console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%D or {display}: VNC display</li></ul><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d or {name}: title of the console</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%P or {project}: project name</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i or {project_id}: project UUID</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%c or {url}: server URL (<span style=\" font-style:italic;\">http://user:password@server:port</span>)</li></ul></body></html>"))

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>941</width>
<height>910</height>
<width>512</width>
<height>652</height>
</rect>
</property>
<property name="windowTitle">
@@ -223,7 +223,16 @@
<string>Binary images</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="margin">
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item>
@@ -373,7 +382,16 @@
<string>Console applications</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="margin">
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item>
@@ -400,7 +418,7 @@
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Command line replacements:&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%h = console IP or hostname&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%p = console port&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%d = title of the console&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%i = project UUID&lt;/li&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%n = node UUID&lt;/li&gt;&lt;/ul&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%c = server URL&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Command line replacements:&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%h or {host} = console IP or hostname&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%p or {port} = console port&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%d or {name} = title of the console (node name)&lt;/li&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%P or {project} = project&lt;/li&gt;&lt;/ul&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%i or {project_id} = project UUID&lt;/li&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%n or {node_id} = node UUID&lt;/li&gt;&lt;/ul&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%c or {url} = server URL&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="readOnly">
<bool>true</bool>
@@ -474,7 +492,16 @@
<string>VNC</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_6">
<property name="margin">
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item>
@@ -501,7 +528,7 @@
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Command line replacements:&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%h = console IP or hostname&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%p = console port&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%P = VNC display&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%d = title of the console&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%i = project UUID&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%n = node UUID&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Command line replacements:&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%h or {host} = console IP or hostname&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%p or {port} = console port&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%P or {display} = VNC display&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%d or {name} = node name&lt;/li&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%T or {project} = project name&lt;/li&gt;&lt;/ul&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%i or {project_id} = project UUID&lt;/li&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%n or {node_id} = node UUID&lt;/li&gt;&lt;/ul&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%c or {url} = server URL&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="readOnly">
<bool>true</bool>
@@ -570,7 +597,7 @@
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Command line replacements:&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%h = console IP or hostname&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%p = console port&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%d = title of the console&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%i = project UUID&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%n = node UUID&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Command line replacements:&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%h or {host} = console IP or hostname&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%p or {port} = console port&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%d or {name} = node name&lt;/li&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%P or {project} = project name&lt;/li&gt;&lt;/ul&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%i or {project_id} = project UUID&lt;/li&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%n or {node_id} = node UUID&lt;/li&gt;&lt;/ul&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%c or {url} = server URL&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="readOnly">
<bool>true</bool>
@@ -943,7 +970,16 @@
<string>Miscellaneous</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="margin">
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item>
@@ -966,16 +1002,6 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="uiStatsCheckBox">
<property name="text">
<string>Send anonymous usage statistics</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="uiOverlayNotificationsCheckBox">
<property name="text">
@@ -1098,7 +1124,6 @@
<tabstop>uiDefaultNoteColorPushButton</tabstop>
<tabstop>uiCheckForUpdateCheckBox</tabstop>
<tabstop>uiCrashReportCheckBox</tabstop>
<tabstop>uiStatsCheckBox</tabstop>
<tabstop>uiOverlayNotificationsCheckBox</tabstop>
<tabstop>uiExperimentalFeaturesCheckBox</tabstop>
<tabstop>uiHdpiCheckBox</tabstop>

View File

@@ -2,9 +2,10 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/general_preferences_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_GeneralPreferencesPageWidget(object):
def setupUi(self, GeneralPreferencesPageWidget):
GeneralPreferencesPageWidget.setObjectName("GeneralPreferencesPageWidget")
GeneralPreferencesPageWidget.resize(941, 910)
GeneralPreferencesPageWidget.resize(512, 652)
self.verticalLayout = QtWidgets.QVBoxLayout(GeneralPreferencesPageWidget)
self.verticalLayout.setObjectName("verticalLayout")
self.uiMiscTabWidget = QtWidgets.QTabWidget(GeneralPreferencesPageWidget)
@@ -446,10 +447,6 @@ class Ui_GeneralPreferencesPageWidget(object):
self.uiCrashReportCheckBox.setChecked(True)
self.uiCrashReportCheckBox.setObjectName("uiCrashReportCheckBox")
self.verticalLayout_2.addWidget(self.uiCrashReportCheckBox)
self.uiStatsCheckBox = QtWidgets.QCheckBox(self.uiMiscTab)
self.uiStatsCheckBox.setChecked(True)
self.uiStatsCheckBox.setObjectName("uiStatsCheckBox")
self.verticalLayout_2.addWidget(self.uiStatsCheckBox)
self.uiOverlayNotificationsCheckBox = QtWidgets.QCheckBox(self.uiMiscTab)
self.uiOverlayNotificationsCheckBox.setObjectName("uiOverlayNotificationsCheckBox")
self.verticalLayout_2.addWidget(self.uiOverlayNotificationsCheckBox)
@@ -520,8 +517,7 @@ class Ui_GeneralPreferencesPageWidget(object):
GeneralPreferencesPageWidget.setTabOrder(self.uiDefaultNoteFontPushButton, self.uiDefaultNoteColorPushButton)
GeneralPreferencesPageWidget.setTabOrder(self.uiDefaultNoteColorPushButton, self.uiCheckForUpdateCheckBox)
GeneralPreferencesPageWidget.setTabOrder(self.uiCheckForUpdateCheckBox, self.uiCrashReportCheckBox)
GeneralPreferencesPageWidget.setTabOrder(self.uiCrashReportCheckBox, self.uiStatsCheckBox)
GeneralPreferencesPageWidget.setTabOrder(self.uiStatsCheckBox, self.uiOverlayNotificationsCheckBox)
GeneralPreferencesPageWidget.setTabOrder(self.uiCrashReportCheckBox, self.uiOverlayNotificationsCheckBox)
GeneralPreferencesPageWidget.setTabOrder(self.uiOverlayNotificationsCheckBox, self.uiExperimentalFeaturesCheckBox)
GeneralPreferencesPageWidget.setTabOrder(self.uiExperimentalFeaturesCheckBox, self.uiHdpiCheckBox)
GeneralPreferencesPageWidget.setTabOrder(self.uiHdpiCheckBox, self.uiMultiProfilesCheckBox)
@@ -562,7 +558,7 @@ class Ui_GeneralPreferencesPageWidget(object):
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.uiImagesTab), _translate("GeneralPreferencesPageWidget", "Binary images"))
self.uiTelnetConsoleSettingsGroupBox.setTitle(_translate("GeneralPreferencesPageWidget", "Console settings"))
self.uiTelnetConsoleCommandLabel.setText(_translate("GeneralPreferencesPageWidget", "Console application command for Telnet:"))
self.uiTelnetConsoleCommandLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%h = console IP or hostname</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%p = console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d = title of the console</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i = project UUID</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%n = node UUID</li></ul><li style=\" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%c = server URL</li></ul></body></html>"))
self.uiTelnetConsoleCommandLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%h or {host} = console IP or hostname</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%p or {port} = console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d or {name} = title of the console (node name)</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%P or {project} = project</li></ul><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i or {project_id} = project UUID</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%n or {node_id} = node UUID</li></ul><li style=\" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%c or {url} = server URL</li></ul></body></html>"))
self.uiTelnetConsolePreconfiguredCommandPushButton.setText(_translate("GeneralPreferencesPageWidget", "&Edit"))
self.uiConsoleMiscGroupBox.setTitle(_translate("GeneralPreferencesPageWidget", "Miscellaneous"))
self.uiDelayConsoleAllSpinBox.setSuffix(_translate("GeneralPreferencesPageWidget", " ms"))
@@ -570,12 +566,12 @@ class Ui_GeneralPreferencesPageWidget(object):
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.uiConsoleTab), _translate("GeneralPreferencesPageWidget", "Console applications"))
self.uiVNCConsoleSettingsGroupBox.setTitle(_translate("GeneralPreferencesPageWidget", "Settings for VNC connections"))
self.uiVNCConsoleCommandLabel.setText(_translate("GeneralPreferencesPageWidget", "Console application command for VNC:"))
self.uiVNCConsoleCommandLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%h = console IP or hostname</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%p = console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%P = VNC display</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d = title of the console</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i = project UUID</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%n = node UUID</li></ul></body></html>"))
self.uiVNCConsoleCommandLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%h or {host} = console IP or hostname</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%p or {port} = console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%P or {display} = VNC display</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d or {name} = node name</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%T or {project} = project name</li></ul><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i or {project_id} = project UUID</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%n or {node_id} = node UUID</li></ul><li style=\" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%c or {url} = server URL</li></ul></body></html>"))
self.uiVNCConsolePreconfiguredCommandPushButton.setText(_translate("GeneralPreferencesPageWidget", "&Edit"))
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.uiVNCTab), _translate("GeneralPreferencesPageWidget", "VNC"))
self.uiSPICEConsoleSettingsGroupBox.setTitle(_translate("GeneralPreferencesPageWidget", "Settings for SPICE connections"))
self.uiSPICEConsoleCommandLabel.setText(_translate("GeneralPreferencesPageWidget", "Console application command for SPICE:"))
self.uiSPICEConsoleCommandLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%h = console IP or hostname</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%p = console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d = title of the console</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i = project UUID</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%n = node UUID</li></ul><p><br/></p></body></html>"))
self.uiSPICEConsoleCommandLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%h or {host} = console IP or hostname</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%p or {port} = console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d or {name} = node name</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%P or {project} = project name</li></ul><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i or {project_id} = project UUID</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%n or {node_id} = node UUID</li></ul><li style=\" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%c or {url} = server URL</li></ul></body></html>"))
self.uiSPICEConsolePreconfiguredCommandPushButton.setText(_translate("GeneralPreferencesPageWidget", "&Edit"))
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.uiSPICETab), _translate("GeneralPreferencesPageWidget", "SPICE"))
self.uiSceneWidthLabel.setText(_translate("GeneralPreferencesPageWidget", "Default width:"))
@@ -601,7 +597,6 @@ class Ui_GeneralPreferencesPageWidget(object):
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.uiSceneTab), _translate("GeneralPreferencesPageWidget", "Topology view"))
self.uiCheckForUpdateCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Automatically check for update"))
self.uiCrashReportCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Send anonymous crash reports"))
self.uiStatsCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Send anonymous usage statistics"))
self.uiOverlayNotificationsCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Display error, warning and info in an overlay popup"))
self.uiExperimentalFeaturesCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Enable experimental features (dangerous, restart required)"))
self.uiHdpiCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Enable HDPI mode (this may crash on Linux, restart required)"))

View File

@@ -138,6 +138,7 @@ background-none;
<addaction name="uiShowPortNamesAction"/>
<addaction name="uiLockAllAction"/>
<addaction name="separator"/>
<addaction name="uiResetGUIStateAction"/>
<addaction name="uiResetDocksAction"/>
<addaction name="uiDocksMenu"/>
</widget>
@@ -1300,6 +1301,11 @@ background-none;
<string>Reset all console connections</string>
</property>
</action>
<action name="uiResetGUIStateAction">
<property name="text">
<string>Reset GUI state</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@@ -2,9 +2,10 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/main_window.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
# Created by: PyQt5 UI code generator 5.15.9
#
# 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
@@ -14,7 +15,7 @@ class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.setWindowModality(QtCore.Qt.NonModal)
MainWindow.resize(986, 716)
MainWindow.resize(986, 719)
MainWindow.setContextMenuPolicy(QtCore.Qt.PreventContextMenu)
MainWindow.setStyleSheet("#toolBar_Devices QToolButton {\n"
"width: 50px;\n"
@@ -450,11 +451,10 @@ class Ui_MainWindow(object):
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.uiResetGUIStateAction = QtWidgets.QAction(MainWindow)
self.uiResetGUIStateAction.setObjectName("uiResetGUIStateAction")
self.uiEditMenu.addAction(self.uiSelectAllAction)
self.uiEditMenu.addAction(self.uiSelectNoneAction)
self.uiEditMenu.addSeparator()
@@ -495,6 +495,7 @@ class Ui_MainWindow(object):
self.uiViewMenu.addAction(self.uiShowPortNamesAction)
self.uiViewMenu.addAction(self.uiLockAllAction)
self.uiViewMenu.addSeparator()
self.uiViewMenu.addAction(self.uiResetGUIStateAction)
self.uiViewMenu.addAction(self.uiResetDocksAction)
self.uiViewMenu.addAction(self.uiDocksMenu.menuAction())
self.uiControlMenu.addAction(self.uiStartAllAction)
@@ -554,7 +555,7 @@ class Ui_MainWindow(object):
self.uiAnnotationToolBar.addAction(self.uiScreenshotAction)
self.retranslateUi(MainWindow)
self.uiQuitAction.triggered.connect(MainWindow.close)
self.uiQuitAction.triggered.connect(MainWindow.close) # type: ignore
QtCore.QMetaObject.connectSlotsByName(MainWindow)
MainWindow.setTabOrder(self.uiGraphicsView, self.uiNodesView)
MainWindow.setTabOrder(self.uiNodesView, self.uiConsoleTextEdit)
@@ -721,9 +722,8 @@ class Ui_MainWindow(object):
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"))
self.uiResetGUIStateAction.setText(_translate("MainWindow", "Reset GUI state"))
from ..compute_summary_view import ComputeSummaryView
from ..console_view import ConsoleView
from ..graphics_view import GraphicsView

View File

@@ -79,7 +79,7 @@
<item row="3" column="0" colspan="2">
<widget class="QLineEdit" name="uiCaptureReaderCommandLineEdit">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Command line replacements:&lt;/p&gt;&lt;p&gt;%c = capture file (PCAP format)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Command line replacements:&lt;/p&gt;&lt;p&gt;%c or {pcap_file} = capture file (PCAP format)&lt;/p&gt;&lt;p&gt;%P or {project} = project name&lt;/p&gt;&lt;p&gt;%d or {link_description} = link description&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>

View File

@@ -2,12 +2,15 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/packet_capture_preferences_page.ui'
#
# Created by: PyQt5 UI code generator 5.9
# 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
class Ui_PacketCapturePreferencesPageWidget(object):
def setupUi(self, PacketCapturePreferencesPageWidget):
PacketCapturePreferencesPageWidget.setObjectName("PacketCapturePreferencesPageWidget")
@@ -64,6 +67,11 @@ class Ui_PacketCapturePreferencesPageWidget(object):
self.retranslateUi(PacketCapturePreferencesPageWidget)
QtCore.QMetaObject.connectSlotsByName(PacketCapturePreferencesPageWidget)
PacketCapturePreferencesPageWidget.setTabOrder(self.uiPreconfiguredCaptureReaderCommandComboBox, self.uiPreconfiguredCaptureReaderCommandPushButton)
PacketCapturePreferencesPageWidget.setTabOrder(self.uiPreconfiguredCaptureReaderCommandPushButton, self.uiCaptureReaderCommandLineEdit)
PacketCapturePreferencesPageWidget.setTabOrder(self.uiCaptureReaderCommandLineEdit, self.uiAutoStartCheckBox)
PacketCapturePreferencesPageWidget.setTabOrder(self.uiAutoStartCheckBox, self.uiCaptureAnalyzerCommandLineEdit)
PacketCapturePreferencesPageWidget.setTabOrder(self.uiCaptureAnalyzerCommandLineEdit, self.uiRestoreDefaultsPushButton)
def retranslateUi(self, PacketCapturePreferencesPageWidget):
_translate = QtCore.QCoreApplication.translate
@@ -74,6 +82,5 @@ class Ui_PacketCapturePreferencesPageWidget(object):
self.uiCaptureReaderCommandLabel.setText(_translate("PacketCapturePreferencesPageWidget", "Packet capture reader command:"))
self.uiAutoStartCheckBox.setText(_translate("PacketCapturePreferencesPageWidget", "Automatically start the packet capture application"))
self.uiCaptureAnalyzerCommandLabel.setText(_translate("PacketCapturePreferencesPageWidget", "Packet capture analyzer command:"))
self.uiCaptureReaderCommandLineEdit.setToolTip(_translate("PacketCapturePreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><p>%c = capture file (PCAP format)</p></body></html>"))
self.uiCaptureReaderCommandLineEdit.setToolTip(_translate("PacketCapturePreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><p>%c or {pcap_file} = capture file (PCAP format)</p><p>%P or {project} = project name</p><p>%d or {link_description} = link description</p></body></html>"))
self.uiRestoreDefaultsPushButton.setText(_translate("PacketCapturePreferencesPageWidget", "Restore defaults"))

View File

@@ -2,6 +2,14 @@
<ui version="4.0">
<class>StyleEditorDialog</class>
<widget class="QDialog" name="StyleEditorDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>288</width>
<height>358</height>
</rect>
</property>
<property name="windowTitle">
<string>Style editor</string>
</property>
@@ -76,14 +84,14 @@
<item row="3" column="1">
<widget class="QComboBox" name="uiBorderStyleComboBox"/>
</item>
<item row="4" column="0">
<item row="7" column="0">
<widget class="QLabel" name="uiRotationLabel">
<property name="text">
<string>Rotation:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="7" column="1">
<widget class="QSpinBox" name="uiRotationSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@@ -106,6 +114,63 @@ editing (notes only) with ALT and '+' (or P) / ALT and '-' (or M)</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="uiCornerRadiusLabel">
<property name="text">
<string>Corner radius:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="uiCornerRadiusSpinBox">
<property name="suffix">
<string>°</string>
</property>
<property name="maximum">
<number>100</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="uiWidthLabel">
<property name="text">
<string>Width:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="uiWidthSpinBox">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="uiHeightLabel">
<property name="text">
<string>Height:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="uiHeightSpinBox">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@@ -2,7 +2,7 @@
# 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.15.2
# Created by: PyQt5 UI code generator 5.15.6
#
# 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.
@@ -14,6 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_StyleEditorDialog(object):
def setupUi(self, StyleEditorDialog):
StyleEditorDialog.setObjectName("StyleEditorDialog")
StyleEditorDialog.resize(288, 358)
StyleEditorDialog.setModal(True)
self.verticalLayout = QtWidgets.QVBoxLayout(StyleEditorDialog)
self.verticalLayout.setObjectName("verticalLayout")
@@ -52,7 +53,7 @@ class Ui_StyleEditorDialog(object):
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.uiBorderStyleComboBox)
self.uiRotationLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox)
self.uiRotationLabel.setObjectName("uiRotationLabel")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.uiRotationLabel)
self.formLayout.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.uiRotationLabel)
self.uiRotationSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -62,7 +63,30 @@ class Ui_StyleEditorDialog(object):
self.uiRotationSpinBox.setMinimum(-360)
self.uiRotationSpinBox.setMaximum(360)
self.uiRotationSpinBox.setObjectName("uiRotationSpinBox")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.uiRotationSpinBox)
self.formLayout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.uiRotationSpinBox)
self.uiCornerRadiusLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox)
self.uiCornerRadiusLabel.setObjectName("uiCornerRadiusLabel")
self.formLayout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.uiCornerRadiusLabel)
self.uiCornerRadiusSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox)
self.uiCornerRadiusSpinBox.setMaximum(100)
self.uiCornerRadiusSpinBox.setObjectName("uiCornerRadiusSpinBox")
self.formLayout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.uiCornerRadiusSpinBox)
self.uiWidthLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox)
self.uiWidthLabel.setObjectName("uiWidthLabel")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.uiWidthLabel)
self.uiWidthSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox)
self.uiWidthSpinBox.setMinimum(10)
self.uiWidthSpinBox.setMaximum(1000)
self.uiWidthSpinBox.setObjectName("uiWidthSpinBox")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.uiWidthSpinBox)
self.uiHeightLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox)
self.uiHeightLabel.setObjectName("uiHeightLabel")
self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.uiHeightLabel)
self.uiHeightSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox)
self.uiHeightSpinBox.setMinimum(10)
self.uiHeightSpinBox.setMaximum(1000)
self.uiHeightSpinBox.setObjectName("uiHeightSpinBox")
self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.uiHeightSpinBox)
self.verticalLayout.addWidget(self.uiStyleSettingsGroupBox)
self.uiButtonBox = QtWidgets.QDialogButtonBox(StyleEditorDialog)
self.uiButtonBox.setOrientation(QtCore.Qt.Horizontal)
@@ -73,8 +97,8 @@ class Ui_StyleEditorDialog(object):
self.verticalLayout.addItem(spacerItem)
self.retranslateUi(StyleEditorDialog)
self.uiButtonBox.accepted.connect(StyleEditorDialog.accept)
self.uiButtonBox.rejected.connect(StyleEditorDialog.reject)
self.uiButtonBox.accepted.connect(StyleEditorDialog.accept) # type: ignore
self.uiButtonBox.rejected.connect(StyleEditorDialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(StyleEditorDialog)
def retranslateUi(self, StyleEditorDialog):
@@ -90,4 +114,10 @@ class Ui_StyleEditorDialog(object):
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)"))
self.uiRotationSpinBox.setSuffix(_translate("StyleEditorDialog", "°"))
self.uiCornerRadiusLabel.setText(_translate("StyleEditorDialog", "Corner radius:"))
self.uiCornerRadiusSpinBox.setSuffix(_translate("StyleEditorDialog", "°"))
self.uiWidthLabel.setText(_translate("StyleEditorDialog", "Width:"))
self.uiWidthSpinBox.setSuffix(_translate("StyleEditorDialog", " px"))
self.uiHeightLabel.setText(_translate("StyleEditorDialog", "Height:"))
self.uiHeightSpinBox.setSuffix(_translate("StyleEditorDialog", " px"))
from . import resources_rc

View File

@@ -24,7 +24,6 @@ import re
from gns3.utils import parse_version
from gns3 import version
from gns3.qt import QtNetwork, QtCore, QtWidgets, QtGui, qslot
from gns3.local_config import LocalConfig

View File

@@ -1,134 +0,0 @@
# -*- coding: utf-8 -*-
#
# 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/>.
import platform
import sys
from datetime import datetime
from urllib.parse import quote
from ..version import __version__
from ..qt import QtCore, QtNetwork, QtWidgets
from ..local_config import LocalConfig
from ..settings import GENERAL_SETTINGS
import logging
log = logging.getLogger(__name__)
class AnalyticsClient(QtCore.QObject):
"""
Google analytics client to send events.
"""
_property_id = "UA-55817127-3"
def __init__(self):
super().__init__()
self._visitor_id = None
self._manager = QtNetwork.QNetworkAccessManager(self)
def finished(network_reply):
try:
error = network_reply.error()
except TypeError:
# For unknow reason sometimes error is transform to a signal
# we receive few crash report about that, but we are not able
# to reproduce. We suspect the problem happen when the
# application is closing.
#
# https://github.com/GNS3/gns3-gui/issues/2011
return
if error != QtNetwork.QNetworkReply.NoError:
log.debug("Error when pushing to Google Analytics %s", network_reply.errorString())
self._manager.finished.connect(finished)
#
# We need to build a user agent for Universal Analytics in order to
# let analytics guess the OS
# this could break by analytics at anytime :(
if sys.platform.startswith("darwin"):
self._user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X {release}) AppleWebKit/537.36 (KHTML, like Gecko) GNS3/{version}".format(release=platform.mac_ver()[0].replace(".", "_"), version=__version__)
elif sys.platform.startswith("win"):
self._user_agent = "Mozilla/5.0 (Windows NT {release}) AppleWebKit/537.36 (KHTML, like Gecko) Safari/537.36 GNS3/{version}".format(release=platform.release(), version=__version__)
else:
self._user_agent = "Mozilla/5.0 (X11; Linux {arch}) AppleWebKit/537.36 (KHTML, like Gecko) Safari/537.36 GNS3/{version}".format(arch=platform.machine(), version=__version__)
self._rate_limit = {}
def sendScreenView(self, screen, session_start=None):
"""
:params session_start: True session start, None during session, False session stop
"""
if session_start is not False and screen in self._rate_limit:
if self._rate_limit[screen] + 60 * 1 > datetime.utcnow().timestamp():
log.debug("Ignore call %s to Google Analytics because of rate limiting", screen)
return
self._rate_limit[screen] = datetime.utcnow().timestamp()
settings = LocalConfig.instance().loadSectionSettings("MainWindow", GENERAL_SETTINGS)
if settings["send_stats"] is False:
log.debug("Stats is turn off ignore call %s", screen)
return
body = "v=1" # Version
body += "&tid={}".format(self._property_id) # Tracking ID / Property ID
body += "&cid={}".format(settings["stats_visitor_id"]) # Anonymous Client ID
body += "&aip=1" # Anonymize IP
body += "&t=screenview" # Screenview hit type
body += "&an=GNS3" # App name
body += "&av={}".format(quote(__version__)) # App version.
body += "&ua={}".format(quote(self._user_agent)) # User agent
body += "&cd={}".format(quote(screen)) # Category
body += "&ds=gns3-gui" # Data source
if session_start is True:
body += "&sc=start" # Session start
elif session_start is False:
body += "&sc=end" # Session end
screen = QtWidgets.QApplication.desktop().screenGeometry()
body += "&sr={}x{}".format(screen.width(), screen.height()) # Screen resolution
locale = QtCore.QLocale.system().name().lower()
if locale:
body += "&ul={}".format(locale) # User language
# TODO: HTTPS when possible because it's broken for the moment with Qt on OSX:
# https://bugreports.qt.io/browse/QTBUG-45487
if sys.platform.startswith("darwin"):
url = QtCore.QUrl('http://www.google-analytics.com/collect')
else:
url = QtCore.QUrl('https://www.google-analytics.com/collect')
request_qt = QtNetwork.QNetworkRequest(url)
request_qt.setRawHeader(b"Content-Type", b"application/x-www-form-urlencoded")
request_qt.setRawHeader(b"User-Agent", self._user_agent.encode())
self._manager.post(request_qt, body.encode())
log.debug("Send stats to Google Analytics: %s", body)
@staticmethod
def instance():
"""
Singleton to return only on instance of AnalyticsClient.
:returns: instance of AnalyticsClient
"""
if not hasattr(AnalyticsClient, '_instance') or AnalyticsClient._instance is None:
AnalyticsClient._instance = AnalyticsClient()
return AnalyticsClient._instance

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env python
#
# Copyright (C) 2023 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/>.
# This script is intended to be built as a small executable for macOS to set the correct permissions on uBridge
import os
import shutil
import sys
def authorize_ubridge():
path = shutil.which("ubridge", path=os.path.dirname(sys.executable))
if path is None:
raise SystemExit("Could not find ubridge executable at {}".format(path))
try:
shutil.chown(path, "root", "admin")
os.chmod(path, 0o4750)
except OSError as e:
raise SystemExit("Could not authorize {}: {}".format(path, str(e)))
if __name__ == '__main__':
authorize_ubridge()

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 GNS3 Technologies Inc.
# Copyright (C) 2023 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
@@ -15,50 +15,35 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import os
import tempfile
import pkg_resources
import atexit
import logging
import os
import sys
try:
import importlib_resources
except ImportError:
from importlib import resources as importlib_resources
from contextlib import ExitStack
resource_manager = ExitStack()
atexit.register(resource_manager.close)
log = logging.getLogger(__name__)
try:
egg_cache_dir = tempfile.mkdtemp()
pkg_resources.set_extraction_path(egg_cache_dir)
except ValueError:
# If the path is already set the module throw an error
pass
@atexit.register
def clean_egg_cache():
try:
import shutil
shutil.rmtree(egg_cache_dir, ignore_errors=True)
except Exception:
# We don't care if we can not cleanup
pass
def get_resource(resource_name):
"""
Return a resource in current directory or in frozen package
"""
resource_path = None
if hasattr(sys, "frozen"):
resource_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable), resource_name))
if sys.platform.startswith("darwin") and not os.path.exists(resource_path):
resource_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable), "lib", resource_name))
elif not hasattr(sys, "frozen"):
if pkg_resources.resource_exists("gns3", resource_name):
try:
resource_path = pkg_resources.resource_filename("gns3", resource_name)
except pkg_resources.ExtractionError as e:
log.fatal(e)
sys.stderr.write(e)
sys.exit(1)
resource_path = os.path.normpath(resource_path)
else:
resource_path = os.path.dirname(os.path.realpath(__file__))
resource_path = os.path.join(resource_path, "..", "..", "resources", resource_name)
else:
ref = importlib_resources.files("gns3") / resource_name
path = resource_manager.enter_context(importlib_resources.as_file(ref))
if os.path.exists(path):
resource_path = os.path.normpath(path)
return resource_path

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python
#
# Copyright (C) 2023 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
import shutil
import logging
log = logging.getLogger(__name__)
def macos_ubridge_setuid():
# AuthorizationExecuteWithPrivileges() has been deprecated since macOS 10.7 but it still works
# and much simpler than using SMJobBless() which requires a separate helper tool
import ctypes
import ctypes.util
from ctypes import byref
authorize_ubridge = shutil.which("authorize_ubridge", path=os.path.dirname(sys.executable))
if authorize_ubridge is None:
raise OSError("Could not find the authorize_ubridge executable")
# https://developer.apple.com/documentation/security
sec = ctypes.cdll.LoadLibrary(ctypes.util.find_library("Security"))
try:
sec.AuthorizationCreate
except AttributeError:
raise OSError("macOS security library does not support AuthorizationCreate")
kAuthorizationFlagDefaults = 0
auth = ctypes.c_void_p()
r_auth = byref(auth)
err = sec.AuthorizationCreate(None, None, kAuthorizationFlagDefaults, r_auth)
if err:
raise OSError("Could not create authorization: {}".format(err))
exe = [authorize_ubridge]
log.info("Executing '{}' with privileges".format(exe))
args = (ctypes.c_char_p * len(exe))()
for i, arg in enumerate(exe[1:]):
args[i] = arg.encode('utf8')
io = ctypes.c_void_p()
err = sec.AuthorizationExecuteWithPrivileges(auth, exe[0].encode('utf8'), 0, args, byref(io))
if err:
raise OSError("Could not authorize uBridge: {}".format(err))
else:
log.info("Successfully authorized uBridge")

View File

@@ -15,9 +15,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import shlex
import subprocess
import sys
from gns3.qt import QtWidgets
from gns3.utils.progress_dialog import ProgressDialog

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.38"
__version_info__ = (2, 2, 38, 0)
__version__ = "2.2.47"
__version_info__ = (2, 2, 47, 0)
if "dev" in __version__:
try:

View File

@@ -48,16 +48,27 @@ def vncConsole(node, port, command):
# replace the place-holders by the actual values
command = command.replace("%h", host)
command = command.replace("%p", str(port))
command = command.replace("%P", str(port - 5900))
command = command.replace("%D", str(port - 5900))
command = command.replace("%d", name.replace('"', '\\"'))
command = command.replace("%P", node.project().name().replace('"', '\\"'))
command = command.replace("%i", node.project().id())
command = command.replace("%n", str(node.id()))
command = command.replace("%c", Controller.instance().httpClient().fullUrl())
command = command.replace("{host}", host)
command = command.replace("{port}", str(port))
command = command.replace("{display}", str(port - 5900))
command = command.replace("{name}", name.replace('"', '\\"'))
command = command.replace("{project}", node.project().name().replace('"', '\\"'))
command = command.replace("{project_id}", node.project().id())
command = command.replace("{node_id}", str(node.id()))
command = command.replace("{url}", Controller.instance().httpClient().fullUrl())
try:
log.debug('starting VNC program "{}"'.format(command))
if sys.platform.startswith("win"):
# use the string on Windows
subprocess.Popen(command)
subprocess.Popen(command, env=os.environ)
else:
# use arguments on other platforms
args = shlex.split(command)

View File

@@ -1,3 +1,5 @@
-rrequirements.txt
PyQt5==5.15.7
PyQt5-Qt5==5.15.2
PyQt5-sip==12.12.2
PyQt5==5.15.9

View File

@@ -1,7 +1,6 @@
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
jsonschema>=4.22.0,<4.23
sentry-sdk==2.1.1,<2.2
psutil==5.9.8
distro>=1.9.0
truststore>=0.9.1; python_version >= '3.10'
importlib-resources>=1.3; python_version < '3.9'

207
resources/symbols/nat.svg Normal file
View File

@@ -0,0 +1,207 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.1"
width="4.5009999cm"
height="2.0009999cm"
viewBox="0.2 0.149 4.7 2.151"
id="svg2"
sodipodi:docname="nat.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
id="namedview30"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="cm"
showgrid="false"
inkscape:lockguides="true"
inkscape:zoom="5.7444245"
inkscape:cx="75.725602"
inkscape:cy="42.040765"
inkscape:window-width="1658"
inkscape:window-height="1016"
inkscape:window-x="70"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<metadata
id="metadata57">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
<dc:creator>
<cc:Agent>
<dc:title>Jeremy Grossmann</dc:title>
</cc:Agent>
</dc:creator>
<dc:publisher>
<cc:Agent>
<dc:title>GNS-3</dc:title>
</cc:Agent>
</dc:publisher>
<dc:description>Created for the GNS-3 project (www.gns3.net)</dc:description>
<cc:license
rdf:resource="http://creativecommons.org/licenses/GPL/2.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/GPL/2.0/">
<cc:permits
rdf:resource="http://web.resource.org/cc/Reproduction" />
<cc:permits
rdf:resource="http://web.resource.org/cc/Distribution" />
<cc:requires
rdf:resource="http://web.resource.org/cc/Notice" />
<cc:permits
rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
<cc:requires
rdf:resource="http://web.resource.org/cc/ShareAlike" />
<cc:requires
rdf:resource="http://web.resource.org/cc/SourceCode" />
</cc:License>
</rdf:RDF>
</metadata>
<defs
id="defs55" />
<path
d="m 3.7599791,0.61531938 -10e-4,-0.021 -0.003,-0.021 -0.007,-0.02 -0.009,-0.02 -0.011,-0.021 -0.013,-0.02 -0.017,-0.02 -0.018,-0.019 -0.021,-0.019 -0.023,-0.019 -0.024,-0.017 -0.028,-0.017 -0.03,-0.017 -0.031,-0.017 -0.033,-0.015 -0.035,-0.015 -0.037,-0.014 -0.038,-0.013 -0.04,-0.012 -0.041,-0.011 -0.043,-0.011 -0.045,-0.01 -0.044,-0.008 -0.046,-0.008 -0.048,-0.007 -0.047,-0.006 -0.048,-0.004 -0.049,-0.004 -0.05,-0.003 -0.049,-0.001 -0.049,-0.001 0,0 -0.05,0.001 -0.049,0.001 -0.05,0.003 -0.048,0.004 -0.049,0.004 -0.047,0.006 -0.047,0.007 -0.046,0.008 -0.045,0.008 -0.043,0.01 -0.043,0.011 -0.042,0.011 -0.04,0.012 -0.039,0.013 -0.036,0.014 -0.035,0.015 -0.033,0.015 -0.032,0.017 -0.029,0.017 -0.027,0.017 -0.026,0.017 -0.023,0.019 -0.02,0.019 -0.019,0.019 -0.016,0.02 -0.014,0.02 -0.01,0.021 -0.009,0.02 -0.006,0.02 -0.005,0.021 -0.001,0.021 0,0 0.001,0.02 0.005,0.022 0.006,0.02 0.009,0.021 0.01,0.02 0.014,0.019 0.016,0.02 0.019,0.02 0.02,0.019 0.023,0.018 0.026,0.018 0.027,0.017 0.029,0.017 0.032,0.016 0.033,0.016 0.035,0.014 0.036,0.014 0.039,0.013 0.04,0.013 0.042,0.011 0.043,0.01 0.043,0.011 0.045,0.008 0.046,0.007 0.047,0.007 0.047,0.006 0.049,0.005 0.048,0.003 0.05,0.003 0.049,10e-4 0.05,0.001 0,0 0.049,-0.001 0.049,-10e-4 0.05,-0.003 0.049,-0.003 0.048,-0.005 0.047,-0.006 0.048,-0.007 0.046,-0.007 0.044,-0.008 0.045,-0.011 0.043,-0.01 0.041,-0.011 0.04,-0.013 0.038,-0.013 0.037,-0.014 0.035,-0.014 0.033,-0.016 0.031,-0.016 0.03,-0.017 0.028,-0.017 0.024,-0.018 0.023,-0.018 0.021,-0.019 0.018,-0.02 0.017,-0.02 0.013,-0.019 0.011,-0.02 0.009,-0.021 0.007,-0.02 0.003,-0.022 10e-4,-0.02"
id="path4"
style="fill:#ffffff;stroke:none" />
<path
d="m 2.2259791,0.83031938 -10e-4,-0.021 -0.003,-0.02 -0.004,-0.021 -0.007,-0.02 -0.009,-0.021 -0.01,-0.02 -0.012,-0.02 -0.015,-0.018 -0.015,-0.019 -0.018,-0.019 -0.019,-0.018 -0.021,-0.017 -0.022,-0.017 -0.024,-0.016 -0.026,-0.015 -0.026,-0.015 -0.029,-0.014 -0.029,-0.013 -0.031,-0.013 -0.032,-0.011 -0.032,-0.011 -0.034,-0.009 -0.035,-0.009 -0.035,-0.008 -0.036,-0.006 -0.036,-0.006 -0.037,-0.005 -0.038,-0.004 -0.037,-0.002 -0.039,-0.002 -0.038,0 0,0 -0.037,0 -0.038,0.002 -0.038,0.002 -0.038,0.004 -0.037,0.005 -0.036,0.006 -0.036,0.006 -0.035,0.008 -0.035,0.009 -0.033,0.009 -0.033,0.011 -0.032,0.011 -0.03,0.013 -0.03,0.013 -0.028,0.014 -0.027,0.015 -0.025,0.015 -0.024,0.016 -0.023,0.017 -0.021,0.017 -0.018,0.018 -0.019,0.019 -0.015,0.019 -0.015,0.018 -0.012,0.02 -0.011,0.02 -0.008,0.021 -0.007,0.02 -0.005,0.021 -0.002,0.02 -0.001,0.021 0,0 0.001,0.021 0.002,0.021 0.005,0.02 0.007,0.021 0.008,0.02 0.011,0.02 0.012,0.02 0.015,0.019 0.015,0.019 0.019,0.018 0.018,0.018 0.021,0.018 0.023,0.016 0.024,0.016 0.025,0.016 0.027,0.014 0.028,0.015 0.03,0.012 0.03,0.013 0.032,0.012 0.033,0.01 0.033,0.01 0.035,0.008 0.035,0.008 0.036,0.007 0.036,0.005 0.037,0.005 0.038,0.004 0.038,0.002 0.038,0.002 0.037,0 0,0 0.038,0 0.039,-0.002 0.037,-0.002 0.038,-0.004 0.037,-0.005 0.036,-0.005 0.036,-0.007 0.035,-0.008 0.035,-0.008 0.034,-0.01 0.032,-0.01 0.032,-0.012 0.031,-0.013 0.029,-0.012 0.029,-0.015 0.026,-0.014 0.026,-0.016 0.024,-0.016 0.022,-0.016 0.021,-0.018 0.019,-0.018 0.018,-0.018 0.015,-0.019 0.015,-0.019 0.012,-0.02 0.01,-0.02 0.009,-0.02 0.007,-0.021 0.004,-0.02 0.003,-0.021 10e-4,-0.021"
id="path6"
style="fill:#ffffff;stroke:none" />
<path
d="m 1.2809791,1.2493194 -10e-4,-0.017 -0.001,-0.017 -0.004,-0.017 -0.005,-0.017 -0.005,-0.016 -0.007,-0.016 -0.009,-0.016 -0.009,-0.017 -0.011,-0.015 -0.012,-0.015 -0.012,-0.014 -0.014,-0.015 -0.015,-0.014 -0.017,-0.012 -0.017,-0.013 -0.018,-0.012 -0.019,-0.011 -0.02,-0.011 -0.02,-0.01 -0.022,-0.01 -0.022,-0.008 -0.023,-0.008 -0.023,-0.007 -0.024,-0.006 -0.024,-0.006 -0.025,-0.004 -0.024,-0.005 -0.026,-0.002 -0.025,-0.002 -0.025,-0.002 -0.026,0 0,0 -0.026,0 -0.026,0.002 -0.025,0.002 -0.025,0.002 -0.024,0.005 -0.026,0.004 -0.024,0.006 -0.023,0.006 -0.023,0.007 -0.023,0.008 -0.022,0.008 -0.022,0.01 -0.021,0.01 -0.019,0.011 -0.019,0.011 -0.018,0.012 -0.017,0.013 -0.017,0.012 -0.015,0.014 -0.014,0.015 -0.013,0.014 -0.011,0.015 -0.011,0.015 -0.01,0.017 -0.008,0.016 -0.007,0.016 -0.006,0.016 -0.004,0.017 -0.004,0.017 -0.002,0.017 0,0.017 0,0 0,0.016 0.002,0.018 0.004,0.016 0.004,0.017 0.006,0.017 0.007,0.016 0.008,0.016 0.01,0.015 0.011,0.016 0.011,0.015 0.013,0.015 0.014,0.014 0.015,0.014 0.017,0.013 0.017,0.012 0.018,0.012 0.019,0.012 0.019,0.01 0.021,0.01 0.022,0.01 0.022,0.008 0.023,0.008 0.023,0.007 0.023,0.007 0.024,0.005 0.026,0.005 0.024,0.004 0.025,0.003 0.025,10e-4 0.026,0.002 0.026,0.001 0,0 0.026,-0.001 0.025,-0.002 0.025,-10e-4 0.026,-0.003 0.024,-0.004 0.025,-0.005 0.024,-0.005 0.024,-0.007 0.023,-0.007 0.023,-0.008 0.022,-0.008 0.022,-0.01 0.02,-0.01 0.02,-0.01 0.019,-0.012 0.018,-0.012 0.017,-0.012 0.017,-0.013 0.015,-0.014 0.014,-0.014 0.012,-0.015 0.012,-0.015 0.011,-0.016 0.009,-0.015 0.009,-0.016 0.007,-0.016 0.005,-0.017 0.005,-0.017 0.004,-0.016 0.001,-0.018 10e-4,-0.016"
id="path8"
style="fill:#ffffff;stroke:none" />
<path
d="m 2.0969791,1.5713194 -0.002,-0.018 -0.003,-0.018 -0.004,-0.019 -0.007,-0.018 -0.009,-0.017 -0.01,-0.018 -0.013,-0.017 -0.015,-0.018 -0.016,-0.016 -0.017,-0.016 -0.019,-0.016 -0.021,-0.016 -0.024,-0.015 -0.024,-0.014 -0.026,-0.013 -0.027,-0.013 -0.028,-0.012 -0.031,-0.012 -0.03,-0.011 -0.032,-0.01 -0.034,-0.009 -0.035,-0.009 -0.034,-0.008 -0.036,-0.007 -0.036,-0.005 -0.038,-0.006 -0.037,-0.004 -0.038,-0.003 -0.038,-0.002 -0.039,-0.002 -0.038,0 0,0 -0.038,0 -0.039,0.002 -0.039,0.002 -0.038,0.003 -0.037,0.004 -0.037,0.006 -0.037,0.005 -0.035,0.007 -0.035,0.008 -0.034,0.009 -0.034,0.009 -0.032,0.01 -0.031,0.011 -0.03,0.012 -0.029,0.012 -0.027,0.013 -0.026,0.013 -0.024,0.014 -0.023,0.015 -0.021,0.016 -0.019,0.016 -0.018,0.016 -0.017,0.016 -0.014,0.018 -0.012,0.017 -0.01,0.018 -0.01,0.017 -0.006,0.018 -0.005,0.019 -0.003,0.018 -0.001,0.018 0,0 0.001,0.019 0.003,0.018 0.005,0.018 0.006,0.018 0.01,0.019 0.01,0.017 0.012,0.017 0.014,0.018 0.017,0.016 0.018,0.016 0.019,0.016 0.021,0.015 0.023,0.015 0.024,0.015 0.026,0.014 0.027,0.012 0.029,0.013 0.03,0.011 0.031,0.011 0.032,0.01 0.034,0.01 0.034,0.008 0.035,0.008 0.035,0.006 0.037,0.007 0.037,0.004 0.037,0.005 0.038,0.003 0.039,0.003 0.039,10e-4 0.038,0 0,0 0.038,0 0.039,-10e-4 0.038,-0.003 0.038,-0.003 0.037,-0.005 0.038,-0.004 0.036,-0.007 0.036,-0.006 0.034,-0.008 0.035,-0.008 0.034,-0.01 0.032,-0.01 0.03,-0.011 0.031,-0.011 0.028,-0.013 0.027,-0.012 0.026,-0.014 0.024,-0.015 0.024,-0.015 0.021,-0.015 0.019,-0.016 0.017,-0.016 0.016,-0.016 0.015,-0.018 0.013,-0.017 0.01,-0.017 0.009,-0.019 0.007,-0.018 0.004,-0.018 0.003,-0.018 0.002,-0.019"
id="path10"
style="fill:#ffffff;stroke:none" />
<path
d="m 3.9229791,1.7583194 -0.002,-0.021 -0.004,-0.022 -0.007,-0.022 -0.011,-0.021 -0.012,-0.021 -0.016,-0.021 -0.019,-0.021 -0.021,-0.02 -0.024,-0.02 -0.027,-0.02 -0.029,-0.018 -0.032,-0.019 -0.033,-0.017 -0.037,-0.017 -0.039,-0.016 -0.04,-0.016 -0.043,-0.014 -0.045,-0.014 -0.046,-0.013 -0.048,-0.012 -0.05,-0.01 -0.051,-0.011 -0.052,-0.009 -0.054,-0.008 -0.054,-0.007 -0.056,-0.006 -0.055,-0.005 -0.057,-0.004 -0.057,-0.002 -0.058,-0.002 -0.057,0 0,0 -0.057,0 -0.058,0.002 -0.057,0.002 -0.057,0.004 -0.056,0.005 -0.055,0.006 -0.054,0.007 -0.054,0.008 -0.052,0.009 -0.051,0.011 -0.05,0.01 -0.048,0.012 -0.047,0.013 -0.044,0.014 -0.043,0.014 -0.04,0.016 -0.039,0.016 -0.037,0.017 -0.034,0.017 -0.031,0.019 -0.029,0.018 -0.027,0.02 -0.024,0.02 -0.021,0.02 -0.018,0.021 -0.017,0.021 -0.012,0.021 -0.011,0.021 -0.007,0.022 -0.005,0.022 -10e-4,0.021 0,0 10e-4,0.023 0.005,0.021 0.007,0.022 0.011,0.021 0.012,0.022 0.017,0.02 0.018,0.021 0.021,0.02 0.024,0.02 0.027,0.02 0.029,0.018 0.031,0.018 0.034,0.018 0.037,0.017 0.039,0.016 0.04,0.016 0.043,0.015 0.044,0.013 0.047,0.013 0.048,0.012 0.05,0.011 0.051,0.01 0.052,0.009 0.054,0.009 0.054,0.007 0.055,0.005 0.056,0.005 0.057,0.004 0.057,0.003 0.058,0.002 0.057,0 0,0 0.057,0 0.058,-0.002 0.057,-0.003 0.057,-0.004 0.055,-0.005 0.056,-0.005 0.054,-0.007 0.054,-0.009 0.052,-0.009 0.051,-0.01 0.05,-0.011 0.048,-0.012 0.046,-0.013 0.045,-0.013 0.043,-0.015 0.04,-0.016 0.039,-0.016 0.037,-0.017 0.033,-0.018 0.032,-0.018 0.029,-0.018 0.027,-0.02 0.024,-0.02 0.021,-0.02 0.019,-0.021 0.016,-0.02 0.012,-0.022 0.011,-0.021 0.007,-0.022 0.004,-0.021 0.002,-0.023"
id="path12"
style="fill:#ffffff;stroke:none" />
<path
d="m 4.5529791,0.76631938 0,-0.016 -0.003,-0.016 -0.004,-0.017 -0.008,-0.016 -0.007,-0.016 -0.011,-0.016 -0.012,-0.015 -0.013,-0.015 -0.016,-0.015 -0.017,-0.015 -0.019,-0.013 -0.02,-0.014 -0.022,-0.013 -0.023,-0.013 -0.025,-0.012 -0.026,-0.011 -0.027,-0.012 -0.029,-0.01 -0.03,-0.01 -0.031,-0.009 -0.031,-0.008 -0.033,-0.008 -0.034,-0.007 -0.034,-0.006 -0.035,-0.005 -0.035,-0.005 -0.036,-0.003 -0.037,-0.003 -0.036,-0.002 -0.037,-0.001 -0.037,0 0,0 -0.037,0 -0.036,0.001 -0.038,0.002 -0.035,0.003 -0.036,0.003 -0.036,0.005 -0.034,0.005 -0.035,0.006 -0.034,0.007 -0.032,0.008 -0.032,0.008 -0.031,0.009 -0.03,0.01 -0.028,0.01 -0.028,0.012 -0.026,0.011 -0.025,0.012 -0.023,0.013 -0.022,0.013 -0.02,0.014 -0.019,0.013 -0.016,0.015 -0.016,0.015 -0.014,0.015 -0.012,0.015 -0.01,0.016 -0.009,0.016 -0.006,0.016 -0.005,0.017 -0.002,0.016 -10e-4,0.016 0,0 10e-4,0.016 0.002,0.016 0.005,0.017 0.006,0.016 0.009,0.016 0.01,0.016 0.012,0.016 0.014,0.014 0.016,0.016 0.016,0.014 0.019,0.014 0.02,0.013 0.022,0.014 0.023,0.013 0.025,0.012 0.026,0.011 0.028,0.011 0.028,0.011 0.03,0.009 0.031,0.009 0.032,0.009 0.032,0.007 0.034,0.007 0.035,0.006 0.034,0.006 0.036,0.004 0.036,0.003 0.035,0.003 0.038,0.003 0.036,0 0.037,10e-4 0,0 0.037,-10e-4 0.037,0 0.036,-0.003 0.037,-0.003 0.036,-0.003 0.035,-0.004 0.035,-0.006 0.034,-0.006 0.034,-0.007 0.033,-0.007 0.031,-0.009 0.031,-0.009 0.03,-0.009 0.029,-0.011 0.027,-0.011 0.026,-0.011 0.025,-0.012 0.023,-0.013 0.022,-0.014 0.02,-0.013 0.019,-0.014 0.017,-0.014 0.016,-0.016 0.013,-0.014 0.012,-0.016 0.011,-0.016 0.007,-0.016 0.008,-0.016 0.004,-0.017 0.003,-0.016 0,-0.016"
id="path14"
style="fill:#ffffff;stroke:none" />
<path
d="m 4.7599791,1.1813194 -10e-4,-0.016 -0.002,-0.016 -0.005,-0.017 -0.007,-0.016 -0.008,-0.016 -0.01,-0.016 -0.012,-0.016 -0.013,-0.014 -0.015,-0.015 -0.018,-0.015 -0.019,-0.014 -0.019,-0.013 -0.022,-0.013 -0.023,-0.014 -0.024,-0.011 -0.027,-0.012 -0.027,-0.011 -0.028,-0.01 -0.03,-0.01 -0.03,-0.009 -0.032,-0.008 -0.033,-0.008 -0.033,-0.007 -0.033,-0.005 -0.035,-0.006 -0.035,-0.004 -0.036,-0.004 -0.036,-0.003 -0.036,-0.002 -0.037,-0.001 -0.036,-0.001 0,0 -0.038,0.001 -0.036,0.001 -0.036,0.002 -0.036,0.003 -0.036,0.004 -0.035,0.004 -0.035,0.006 -0.033,0.005 -0.034,0.007 -0.032,0.008 -0.032,0.008 -0.03,0.009 -0.03,0.01 -0.028,0.01 -0.027,0.011 -0.027,0.012 -0.024,0.011 -0.022,0.014 -0.022,0.013 -0.021,0.013 -0.018,0.014 -0.018,0.015 -0.015,0.015 -0.013,0.014 -0.012,0.016 -0.01,0.016 -0.008,0.016 -0.007,0.016 -0.005,0.017 -0.002,0.016 -10e-4,0.016 0,0 10e-4,0.016 0.002,0.016 0.005,0.016 0.007,0.016 0.008,0.017 0.01,0.016 0.012,0.015 0.013,0.015 0.015,0.015 0.018,0.015 0.018,0.013 0.021,0.014 0.022,0.013 0.022,0.013 0.024,0.012 0.027,0.011 0.027,0.012 0.028,0.01 0.03,0.01 0.03,0.009 0.032,0.008 0.032,0.007 0.034,0.007 0.033,0.006 0.035,0.005 0.035,0.005 0.036,0.004 0.036,0.003 0.036,0.002 0.036,0.001 0.038,0 0,0 0.036,0 0.037,-0.001 0.036,-0.002 0.036,-0.003 0.036,-0.004 0.035,-0.005 0.035,-0.005 0.033,-0.006 0.033,-0.007 0.033,-0.007 0.032,-0.008 0.03,-0.009 0.03,-0.01 0.028,-0.01 0.027,-0.012 0.027,-0.011 0.024,-0.012 0.023,-0.013 0.022,-0.013 0.019,-0.014 0.019,-0.013 0.018,-0.015 0.015,-0.015 0.013,-0.015 0.012,-0.015 0.01,-0.016 0.008,-0.017 0.007,-0.016 0.005,-0.016 0.002,-0.016 10e-4,-0.016"
id="path16"
style="fill:#ffffff;stroke:none" />
<path
d="m 4.6189791,1.5243194 -10e-4,-0.027 -0.002,-0.027 -0.005,-0.026 -0.006,-0.027 -0.009,-0.026 -0.01,-0.026 -0.012,-0.025 -0.014,-0.025 -0.014,-0.025 -0.017,-0.023 -0.018,-0.024 -0.021,-0.022 -0.021,-0.022 -0.023,-0.02 -0.024,-0.021 -0.027,-0.018 -0.027,-0.018 -0.027,-0.018 -0.029,-0.015 -0.031,-0.015 -0.032,-0.013 -0.031,-0.013 -0.033,-0.011 -0.034,-0.01 -0.035,-0.009 -0.035,-0.007 -0.036,-0.006 -0.035,-0.005 -0.036,-0.003 -0.036,-0.002 -0.036,-0.001 0,0 -0.037,0.001 -0.036,0.002 -0.036,0.003 -0.036,0.005 -0.035,0.006 -0.035,0.007 -0.035,0.009 -0.034,0.01 -0.033,0.011 -0.032,0.013 -0.031,0.013 -0.031,0.015 -0.029,0.015 -0.028,0.018 -0.027,0.018 -0.026,0.018 -0.024,0.021 -0.023,0.02 -0.022,0.022 -0.019,0.022 -0.019,0.024 -0.017,0.023 -0.015,0.025 -0.014,0.025 -0.011,0.025 -0.01,0.026 -0.008,0.026 -0.007,0.027 -0.005,0.026 -0.002,0.027 -10e-4,0.027 0,0 10e-4,0.027 0.002,0.027 0.005,0.026 0.007,0.026 0.008,0.026 0.01,0.026 0.011,0.026 0.014,0.025 0.015,0.024 0.017,0.024 0.019,0.023 0.019,0.022 0.022,0.022 0.023,0.021 0.024,0.02 0.026,0.019 0.027,0.018 0.028,0.017 0.029,0.016 0.031,0.015 0.031,0.013 0.032,0.013 0.033,0.011 0.034,0.01 0.035,0.008 0.035,0.008 0.035,0.006 0.036,0.005 0.036,0.003 0.036,0.002 0.037,10e-4 0,0 0.036,-10e-4 0.036,-0.002 0.036,-0.003 0.035,-0.005 0.036,-0.006 0.035,-0.008 0.035,-0.008 0.034,-0.01 0.033,-0.011 0.031,-0.013 0.032,-0.013 0.031,-0.015 0.029,-0.016 0.027,-0.017 0.027,-0.018 0.027,-0.019 0.024,-0.02 0.023,-0.021 0.021,-0.022 0.021,-0.022 0.018,-0.023 0.017,-0.024 0.014,-0.024 0.014,-0.025 0.012,-0.026 0.01,-0.026 0.009,-0.026 0.006,-0.026 0.005,-0.026 0.002,-0.027 10e-4,-0.027"
id="path18"
style="fill:#ffffff;stroke:none" />
<path
d="m 2.7919791,0.61131938 0.965,-0.091 -0.013,-0.021 -0.014,-0.019 -0.017,-0.02 -0.019,-0.02 -0.022,-0.019 -0.024,-0.018 -0.026,-0.018 -0.028,-0.017 -0.031,-0.017 -0.032,-0.016 -0.034,-0.015 -0.036,-0.014 -0.038,-0.014 -0.039,-0.013 -0.042,-0.012 -0.042,-0.011 -0.043,-0.011 -0.045,-0.009 -0.046,-0.009 -0.047,-0.007 -0.048,-0.007 -0.048,-0.005 -0.049,-0.004 -0.049,-0.004 -0.049,-0.002 -0.051,-0.001 -0.05,0 -0.05,0 -0.049,0.003 -0.05,0.002 -0.049,0.004 -0.049,0.006 -0.047,0.006 -0.048,0.007 -0.046,0.008 -0.045,0.009 -0.044,0.01 -0.043,0.011 -0.041,0.012 -0.04,0.013 -0.038,0.013 -0.036,0.014 -0.036,0.016 -0.032,0.015 -0.032,0.017 -0.029,0.017 -0.026,0.018 -0.024,0.018 -0.024,0.019 -0.02,0.018 -0.018,0.02 -0.015,0.02 -0.012,0.021 -0.011,0.02 0.971,0.077"
id="path22"
style="fill:#ffffff;stroke:none" />
<path
d="m 3.7569791,0.51931938 -0.013,-0.021 -0.014,-0.019 -0.017,-0.02 -0.02,-0.019 -0.022,-0.019 -0.024,-0.018 -0.026,-0.018 -0.029,-0.018 -0.03,-0.016 -0.032,-0.016 -0.035,-0.016 -0.035,-0.014 -0.038,-0.014 -0.04,-0.012 -0.04,-0.013 -0.042,-0.011 -0.044,-0.01 -0.045,-0.01 -0.046,-0.008 -0.046,-0.008 -0.048,-0.006 -0.048,-0.005 -0.049,-0.004 -0.05,-0.004 -0.05,-0.002 -0.049,-0.001 -0.051,0 -0.049,0 -0.049,0.003 -0.051,0.002 -0.048,0.004 -0.049,0.006 -0.047,0.006 -0.048,0.007 -0.046,0.008 -0.045,0.009 -0.043,0.01 -0.043,0.011 -0.042,0.012 -0.039,0.013 -0.038,0.013 -0.036,0.014 -0.036,0.015 -0.033,0.015 -0.031,0.017 -0.028,0.018 -0.028,0.017 -0.024,0.018 -0.022,0.019 -0.021,0.019 -0.018,0.019 -0.016,0.02 -0.012,0.021 -0.011,0.02"
id="path24"
style="fill:none;stroke:#6c8f93;stroke-width:0.06067566;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
<path
d="m 1.4819791,0.82631938 0.486,-0.317 -0.029,-0.013 -0.032,-0.012 -0.032,-0.012 -0.033,-0.01 -0.034,-0.01 -0.035,-0.008 -0.036,-0.008 -0.036,-0.006 -0.037,-0.006 -0.037,-0.005 -0.038,-0.003 -0.038,-0.003 -0.039,-0.001 -0.038,0 -0.038,0 -0.038,0.002 -0.038,0.003 -0.038,0.004 -0.037,0.005 -0.036,0.006 -0.037,0.007 -0.035,0.008 -0.034,0.009 -0.034,0.009 -0.033,0.011 -0.031,0.013 -0.031,0.012 -0.029,0.013 -0.028,0.015 -0.027,0.015 -0.026,0.015 -0.024,0.016 -0.022,0.018 -0.021,0.017 -0.018,0.018 -0.018,0.02 -0.016,0.018 -0.014,0.02 -0.012,0.02 -0.01,0.02 -0.008,0.02 -0.006,0.022 -0.005,0.02 -0.003,0.021 0,0.021 0.001,0.021 0.003,0.021 0.005,0.021 0.74600003,-0.067"
id="path26"
style="fill:#ffffff;stroke:none" />
<path
d="m 1.9659791,0.50831938 -0.03,-0.014 -0.031,-0.011 -0.033,-0.012 -0.033,-0.01 -0.035,-0.01 -0.034,-0.008 -0.036,-0.008 -0.036,-0.006 -0.037,-0.005 -0.037,-0.005 -0.038,-0.004 -0.038,-0.002 -0.039,-0.001 -0.038,0 -0.038,0 -0.038,0.003 -0.038,0.002 -0.038,0.004 -0.037,0.005 -0.036,0.007 -0.036,0.006 -0.035,0.009 -0.034,0.008 -0.034,0.011 -0.033,0.011 -0.032,0.011 -0.03,0.013 -0.029,0.013 -0.028,0.015 -0.027,0.015 -0.025,0.015 -0.024,0.016 -0.022,0.018 -0.02,0.017 -0.02,0.019 -0.017,0.018 -0.016,0.019 -0.013,0.02 -0.012,0.02 -0.01,0.02 -0.008,0.021 -0.006,0.02 -0.005,0.021 -0.003,0.02 0,0.022 0.002,0.021 0.002,0.02 0.006,0.021"
id="path28"
style="fill:none;stroke:#6c8f93;stroke-width:0.06067566;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
<path
d="m 1.3459791,1.5683194 -0.78100003,-0.012 0,0.019 0.001,0.019 0.003,0.019 0.006,0.018 0.008,0.019 0.009,0.018 0.012,0.018 0.014,0.018 0.015,0.017 0.017,0.017 0.019,0.017 0.02,0.016 0.023,0.015 0.023,0.015 0.026,0.015 0.027,0.014 0.027,0.012 0.03,0.013 0.031,0.012 0.032,0.01 0.034,0.011 0.033,0.009 0.035,0.008 0.036,0.008 0.037,0.007 0.038,0.006 0.037,0.005 0.039,0.004 0.039,0.003 0.039,0.003 0.04,10e-4 0.039,0 0.039,-10e-4 0.039,-0.002 0.039,-0.002 0.039,-0.003 0.039,-0.005 0.037,-0.006 0.037,-0.005 0.037,-0.008 0.035,-0.008 0.035,-0.009 0.033,-0.009 0.033,-0.01 0.033,-0.011 0.03,-0.013 -0.513,-0.282"
id="path30"
style="fill:#ffffff;stroke:none" />
<path
d="m 0.56497907,1.5563194 -0.001,0.019 0.002,0.019 0.003,0.018 0.006,0.019 0.008,0.018 0.01,0.019 0.011,0.018 0.014,0.018 0.015,0.017 0.017,0.017 0.018,0.016 0.02,0.016 0.023,0.016 0.024,0.015 0.024,0.014 0.026,0.014 0.029,0.013 0.029,0.013 0.032,0.011 0.032,0.011 0.032,0.01 0.034,0.009 0.035,0.009 0.037,0.008 0.036,0.006 0.037,0.007 0.038,0.005 0.038,0.004 0.039,0.003 0.039,0.002 0.039,0.002 0.04,0 0.039,0 0.039,-0.003 0.039,-0.002 0.038,-0.003 0.038,-0.005 0.038,-0.004 0.038,-0.007 0.036,-0.007 0.036,-0.008 0.034,-0.009 0.034,-0.009 0.034,-0.01 0.031,-0.011 0.03,-0.012"
id="path32"
style="fill:none;stroke:#6c8f93;stroke-width:0.06067566;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
<path
d="m 3.8359791,0.75931938 0.689,0.104 0.011,-0.016 0.01,-0.015 0.007,-0.016 0.006,-0.016 0.003,-0.016 0.002,-0.017 0,-0.016 -0.002,-0.017 -0.003,-0.016 -0.006,-0.016 -0.007,-0.016 -0.01,-0.016 -0.01,-0.016 -0.013,-0.015 -0.015,-0.015 -0.016,-0.015 -0.018,-0.014 -0.02,-0.014 -0.022,-0.014 -0.021,-0.012 -0.025,-0.013 -0.025,-0.012 -0.027,-0.011 -0.029,-0.011 -0.028,-0.01 -0.03,-0.009 -0.032,-0.009 -0.032,-0.008 -0.034,-0.007 -0.033,-0.006 -0.036,-0.006 -0.034,-0.005 -0.037,-0.004 -0.036,-0.003 -0.037,-0.003 -0.037,-0.001 -0.036,-0.001 -0.037,0 -0.037,0.001 -0.037,0.002 -0.036,0.002 -0.036,0.003 -0.036,0.004 -0.036,0.005 0.238,0.306"
id="path34"
style="fill:#ffffff;stroke:none" />
<path
d="m 4.4569791,0.92331938 0.067,-0.059 0.011,-0.016 0.009,-0.015 0.009,-0.016 0.005,-0.017 0.004,-0.016 0.002,-0.016 0.001,-0.016 -0.003,-0.017 -0.003,-0.016 -0.006,-0.017 -0.007,-0.015 -0.009,-0.016 -0.011,-0.016 -0.012,-0.015 -0.016,-0.015 -0.016,-0.015 -0.017,-0.014 -0.02,-0.014 -0.021,-0.014 -0.023,-0.013 -0.023,-0.012 -0.026,-0.012 -0.027,-0.011 -0.028,-0.011 -0.029,-0.011 -0.03,-0.009 -0.032,-0.008 -0.031,-0.008 -0.033,-0.008 -0.034,-0.006 -0.035,-0.006 -0.035,-0.005 -0.036,-0.004 -0.036,-0.003 -0.037,-0.002 -0.037,-0.002 -0.037,-0.001 -0.037,0 -0.037,0.001 -0.037,0.002 -0.036,0.002 -0.036,0.003 -0.036,0.004 -0.035,0.005"
id="path36"
style="fill:none;stroke:#6c8f93;stroke-width:0.06067566;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
<path
d="m 3.9329791,1.1853194 0.775,0.132 0.016,-0.017 0.013,-0.017 0.012,-0.018 0.008,-0.018 0.008,-0.018 0.004,-0.019 10e-4,-0.018 0,-0.018 -0.003,-0.019 -0.004,-0.019 -0.008,-0.018 -0.009,-0.018 -0.012,-0.017 -0.014,-0.018 -0.016,-0.017 -0.018,-0.017 -0.02,-0.016 -0.023,-0.016 -0.024,-0.015 -0.686,0.20100002"
id="path38"
style="fill:#ffffff;stroke:none" />
<path
d="m 4.6121701,1.38473 0.098479,-0.059128 0.014482,-0.018039 0.013517,-0.018039 0.01062,-0.018039 0.00869,-0.018039 0.00772,-0.018039 0.00386,-0.018039 0.00193,-0.019041 0,-0.018039 -0.00386,-0.020044 -0.00386,-0.018039 -0.00676,-0.018039 -0.00965,-0.018039 -0.011586,-0.018039 -0.013517,-0.018039 -0.015448,-0.017037 -0.018344,-0.017037 -0.021241,-0.016035 -0.020275,-0.016035 -0.024137,-0.0160349 -0.1544773,-0.0591286"
id="path40"
style="fill:none;stroke:#6c8f93;stroke-width:0.0596842;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
<path
d="m 3.9009791,1.5363194 -0.245,0.503 0.036,0.008 0.036,0.007 0.037,0.006 0.038,0.004 0.038,0.003 0.037,0.002 0.037,0 0.038,-0.002 0.037,-0.002 0.038,-0.004 0.036,-0.005 0.038,-0.007 0.035,-0.008 0.036,-0.009 0.034,-0.011 0.034,-0.012 0.033,-0.013 0.032,-0.014 0.031,-0.016 0.03,-0.016 0.028,-0.018 0.027,-0.019 0.027,-0.02 0.025,-0.02 0.022,-0.022 0.022,-0.021 0.02,-0.024 0.018,-0.024 0.016,-0.024 0.015,-0.025 0.014,-0.026 0.01,-0.025 0.01,-0.027 0.008,-0.027 0.006,-0.026 0.003,-0.028 0.002,-0.027 0,-0.027 -0.002,-0.027 -0.004,-0.027 -0.006,-0.026 -0.008,-0.028 -0.009,-0.025 -0.71,0.149"
id="path42"
style="fill:#ffffff;stroke:none" />
<path
d="m 3.6589791,2.0403194 0.036,0.008 0.036,0.007 0.037,0.006 0.037,0.004 0.037,0.002 0.038,0.002 0.038,10e-4 0.037,-0.003 0.038,-0.002 0.037,-0.004 0.037,-0.005 0.036,-0.007 0.036,-0.009 0.036,-0.009 0.034,-0.011 0.033,-0.011 0.034,-0.014 0.032,-0.014 0.031,-0.015 0.029,-0.018 0.029,-0.017 0.027,-0.019 0.026,-0.019 0.025,-0.021 0.023,-0.022 0.021,-0.022 0.019,-0.024 0.019,-0.023 0.016,-0.024 0.015,-0.025 0.014,-0.026 0.011,-0.026 0.008,-0.026 0.008,-0.027 0.006,-0.027 0.003,-0.027 0.002,-0.027 0,-0.027 -0.002,-0.027 -0.005,-0.027 -0.005,-0.028 -0.008,-0.026 -0.01,-0.026"
id="path44"
style="fill:none;stroke:#6c8f93;stroke-width:0.06067566;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
<path
d="m 0.80297907,1.2493194 -0.037,-0.34200002 -0.028,0.002 -0.026,0.002 -0.027,0.004 -0.026,0.004 -0.026,0.005 -0.026,0.006 -0.024,0.007 -0.025,0.008 -0.024,0.008 -0.023,0.009 -0.022,0.01 -0.021,0.01 -0.021,0.012 -0.02,0.012 -0.019,0.013 -0.017,0.013 -0.017,0.014 -0.015,0.014 -0.015,0.015 -0.013,0.015 -0.011,0.016 -0.011,0.016 -0.01,0.016 -0.008,0.017 -0.006,0.017 -0.006,0.016 -0.004,0.018 -0.003,0.018 -0.001,0.017 0,0.017 0.001,0.018 0.003,0.017 0.004,0.017 0.006,0.017 0.007,0.017 0.008,0.017 0.009,0.016 0.011,0.016 0.012,0.016 0.013,0.015 0.016,0.015 0.015,0.014 0.017,0.014 0.018,0.013 0.018,0.013 0.02,0.011 0.021,0.011 0.021,0.012 0.022,0.009 0.024,0.009 0.023,0.009 0.243,-0.305"
id="path46"
style="fill:#ffffff;stroke:none" />
<path
d="m 0.76697907,0.90731938 -0.027,0.002 -0.027,0.002 -0.026,0.003 -0.026,0.005 -0.025,0.004 -0.026,0.006 -0.025,0.007 -0.023,0.007 -0.023,0.009 -0.023,0.009 -0.023,0.009 -0.021,0.01 -0.02,0.011 -0.02,0.012 -0.018,0.012 -0.018,0.013 -0.017,0.013 -0.016,0.015 -0.015,0.014 -0.012,0.014 -0.012,0.016 -0.012,0.015 -0.009,0.016 -0.009,0.016 -0.007,0.017 -0.006,0.017 -0.005,0.017 -0.003,0.017 -0.002,0.017 0,0.017 0,0.017 0.003,0.017 0.003,0.018 0.004,0.016 0.007,0.017 0.006,0.016 0.009,0.017 0.009,0.016 0.012,0.015 0.012,0.016 0.014,0.014 0.014,0.014 0.016,0.015 0.016,0.013 0.018,0.013 0.019,0.012 0.019,0.011 0.021,0.012 0.021,0.01 0.022,0.009 0.023,0.009 0.024,0.009"
id="path48"
style="fill:none;stroke:#6c8f93;stroke-width:0.06067566;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
<path
d="m 2.7759791,1.7874896 -1.109,0.040159 0.008,0.020594 0.009,0.019565 0.013,0.020594 0.015,0.020595 0.018,0.019565 0.021,0.018535 0.023,0.018535 0.025,0.018535 0.029,0.018535 0.031,0.017505 0.032,0.015446 0.036,0.016475 0.037,0.015446 0.039,0.015446 0.042,0.013386 0.043,0.013386 0.045,0.011327 0.047,0.012357 0.048,0.010297 0.049,0.00927 0.051,0.00927 0.052,0.00824 0.053,0.00618 0.053,0.00618 0.055,0.00515 0.055,0.00412 0.056,0.00206 0.055,0.00206 0.057,0.00103 0.055,0 0.057,-0.00103 0.055,-0.00309 0.055,-0.00309 0.055,-0.00515 0.054,-0.00515 0.053,-0.00618 0.052,-0.00721 0.052,-0.00824 0.05,-0.010297 0.049,-0.00927 0.047,-0.011327 0.046,-0.011327 0.044,-0.013386 0.042,-0.014416 0.04,-0.013386 0.039,-0.015446 0.036,-0.015446 0.034,-0.016476 0.031,-0.016476 0.03,-0.017505 0.027,-0.019565 0.025,-0.017505 0.022,-0.019565 0.018,-0.018535 -1.056,-0.1307748"
id="path50"
style="fill:#ffffff;stroke:none" />
<path
d="m 1.6669791,1.8393194 0.008,0.019 0.01,0.02 0.012,0.02 0.016,0.019 0.017,0.019 0.021,0.018 0.023,0.018 0.025,0.019 0.03,0.017 0.03,0.017 0.032,0.015 0.036,0.016 0.037,0.015 0.039,0.015 0.042,0.013 0.044,0.013 0.043,0.011 0.048,0.012 0.047,0.01 0.049,0.009 0.051,0.009 0.052,0.008 0.053,0.006 0.053,0.005 0.055,0.006 0.054,0.003 0.056,0.003 0.056,0.002 0.056,10e-4 0.056,0 0.056,-10e-4 0.055,-0.003 0.055,-0.003 0.055,-0.005 0.054,-0.005 0.054,-0.006 0.051,-0.007 0.052,-0.008 0.05,-0.009 0.048,-0.01 0.048,-0.011 0.046,-0.011 0.043,-0.013 0.043,-0.012 0.04,-0.015 0.039,-0.014 0.036,-0.016 0.034,-0.015 0.032,-0.017 0.029,-0.016 0.027,-0.018 0.024,-0.018 0.022,-0.019 0.02,-0.018"
id="path52"
style="fill:none;stroke:#6c8f93;stroke-width:0.06067566;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
<rect
width="2.7800133"
height="0.61515152"
rx="0"
ry="0.28983101"
x="1.2692652"
y="0.80605406"
transform="matrix(0.999604,0.02812984,-0.02812984,0.999604,0,0)"
id="rect1892"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none" />
<path
d="m 2.5029776,1.9073766 0.023,-0.00895 0.020913,-0.00976 0.022119,-0.011947 0.021191,-0.0142 0.021284,-0.015682 0.02031,-0.017195 0.020402,-0.018676 0.020541,-0.020897 0.018455,-0.021702 0.018546,-0.023184 0.018685,-0.025405 0.016552,-0.02547 0.016691,-0.027691 0.014651,-0.029236 0.015717,-0.029204 0.013676,-0.03075 0.012702,-0.032263 0.012748,-0.033004 0.010665,-0.033809 0.010665,-0.033809 0.00863,-0.035354 0.00756,-0.035386 0.00653,-0.036158 0.00658,-0.0369 0.00445,-0.036963 0.00333,-0.036256 0.00236,-0.037768 0.00226,-0.036288 -9.319e-4,-0.036384 2.273e-4,-0.037833 -0.00205,-0.035676 -0.00413,-0.03648 L 2.883076,0.9720976 2.877836,0.9363258 2.871426,0.902003 2.863906,0.8683889 2.855226,0.8362236 2.845476,0.8040261 2.834621,0.7725371 2.82256,0.7432375 2.810453,0.7146786 2.797187,0.6875686 2.782854,0.6604265 2.768383,0.635506 2.751686,0.6120024 2.733784,0.590685 2.716948,0.569403 2.69886,0.5510511 2.679613,0.5341483 2.660319,0.517986 2.640887,0.5040454 2.620296,0.4915538 2.598453,0.4819918 2.577631,0.4732028"
id="path24-7"
style="fill:none;stroke:#6c8f93;stroke-width:0.107496;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
<path
id="path2057-3"
d="M 1.5298421,1.2403194 H 2.3548856 V 1.303797 L 2.577141,1.1602384 2.3548856,1.0313279 v 0.064457 H 1.5298421 v 0.1445348"
style="fill:#6c8f93;fill-opacity:1;stroke:none;stroke-width:5.35746e-05;stroke-opacity:0.994961" />
<path
id="path2057-3-9"
d="M 3.1225773,1.2403194 H 3.9476208 V 1.303797 L 4.1698762,1.1602384 3.9476208,1.0313279 v 0.064457 H 3.1225773 v 0.1445348"
style="fill:#6c8f93;fill-opacity:1;stroke:none;stroke-width:5.35746e-05;stroke-opacity:0.994961" />
<path
id="path2057-3-5"
d="m 1.5278616,0.86827348 h 0.8319275 v 0.063478 L 2.5838989,0.78819244 2.3597891,0.65928186 v 0.064457 H 1.5278616 v 0.14453482"
style="fill:#6c8f93;fill-opacity:1;stroke:none;stroke-width:5.37977e-05;stroke-opacity:0.994961" />
<path
id="path2057-3-6"
d="m 1.527029,1.6225499 h 0.8217599 v 0.063478 L 2.5701597,1.5424689 2.3487889,1.4135585 v 0.064457 H 1.5270291 v 0.1445347"
style="fill:#6c8f93;fill-opacity:1;stroke:none;stroke-width:5.34679e-05;stroke-opacity:0.994961" />
</svg>

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -19,9 +19,9 @@ import sys
from setuptools import setup, find_packages
from setuptools.command.test import test as TestCommand
# we only support Python 3 version >= 3.4
if len(sys.argv) >= 2 and sys.argv[1] == "install" and sys.version_info < (3, 4):
raise SystemExit("Python 3.4 or higher is required")
# we only support Python 3 version >= 3.7
if len(sys.argv) >= 2 and sys.argv[1] == "install" and sys.version_info < (3, 7):
raise SystemExit("Python 3.7 or higher is required")
class PyTest(TestCommand):
@@ -79,7 +79,7 @@ setup(
include_package_data=True,
package_data={"gns3": ["configs/*.txt", "schemas/*.json"]},
platforms="any",
python_requires=">=3.4",
python_requires='>=3.7',
setup_requires=["setuptools>=17.1"],
classifiers=[
"Development Status :: 5 - Production/Stable",
@@ -92,15 +92,12 @@ setup(
"Operating System :: MacOS :: MacOS X",
"Operating System :: Microsoft :: Windows",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: Implementation :: CPython",
],
)

View File

@@ -75,6 +75,7 @@ def controller():
Controller._instance = None
c = Controller.instance()
c._http_client = MagicMock()
c._http_client.fullUrl.return_value = "http://localhost:3080"
return c
@@ -172,7 +173,7 @@ def local_server_config():
return LocalServerConfig.instance()
@pytest.yield_fixture(autouse=True)
@pytest.fixture(autouse=True)
def run_around_tests(local_config, main_window):
"""
This setup a temporay environnement around tests

View File

@@ -61,6 +61,7 @@ def test_toSvg_negative_y(project, controller):
def test_fromSvg(project, controller):
line = LineItem(project=project)
line._main_window.uiSnapToGridAction.isChecked = lambda: False
line.setPos(50, 84)
line.fromSvg('<svg height="150" width="250"><line x1="0" y1="0" x2="250" y2="150" stroke-width="5" stroke="#0000ff" stroke-dasharray="5, 25, 25" /></svg>')
assert line.line().x1() == 0
@@ -75,6 +76,7 @@ def test_fromSvg(project, controller):
def test_fromSvg_top_direction(project, controller):
line = LineItem(project=project)
line._main_window.uiSnapToGridAction.isChecked = lambda: False
line.setPos(50, 84)
line.fromSvg('<svg height="150" width="250"><line x1="0" y1="150" x2="250" y2="0" stroke-width="5" stroke="#0000ff" stroke-dasharray="5, 25, 25" /></svg>')
assert line.line().x1() == 0

View File

@@ -0,0 +1,50 @@
{
"appliance_id": "1c784362-8aaf-4312-b0f5-4b138cf2e25b",
"name": "Arista vEOS",
"category": "router",
"description": "Arista EOS® is the core of Arista cloud networking solutions for next-generation data centers and cloud networks. Cloud architectures built with Arista EOS scale to tens of thousands of compute and storage nodes with management and provisioning capabilities that work at scale. Through its programmability, EOS enables a set of software applications that deliver workflow automation, high availability, unprecedented network visibility and analytics and rapid integration with a wide range of third-party applications for virtualization, management, automation and orchestration services.\n\nArista Extensible Operating System (EOS) is a fully programmable and highly modular, Linux-based network operation system, using familiar industry standard CLI and runs a single binary software image across the Arista switching family. Architected for resiliency and programmability, EOS has a unique multi-process state sharing architecture that separates state information and packet forwarding from protocol processing and application logic.",
"vendor_name": "Arista",
"vendor_url": "http://www.arista.com/",
"documentation_url": "http://www.arista.com/docs/Manuals/ConfigGuide.pdf",
"product_name": "Arista vEOS",
"product_url": "https://eos.arista.com/",
"registry_version": 8,
"status": "stable",
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"settings": [
{
"template_type": "qemu",
"template_properties": {
"adapter_type": "e1000",
"adapters": 8,
"ram": 2048,
"platform": "x86_64",
"console_type": "telnet"
}
}
],
"images": [
{
"filename": "Aboot-veos-serial-2.1.0.iso",
"version": "2.1.0",
"md5sum": "2687534f2ff11b998dec0511066457c0",
"download_url": "https://www.arista.com/en/support/software-download"
},
{
"filename": "vEOS-lab-4.13.8M.vmdk",
"version": "4.13.8M",
"md5sum": "a47145b9e6e7a24171c0850f8755535e",
"download_url": "https://www.arista.com/en/support/software-download"
}
],
"versions": [
{
"name": "4.13.8M",
"images": {
"hda_disk_image": "Aboot-veos-serial-2.1.0.iso",
"hdb_disk_image": "vEOS-lab-4.13.8M.vmdk"
}
}
]
}

View File

@@ -1,4 +1,5 @@
{
"appliance_id": "1c784362-8aaf-4312-b0f5-4b138cf2e25b",
"name": "Arista vEOS",
"category": "router",
"description": "Arista EOS® is the core of Arista cloud networking solutions for next-generation data centers and cloud networks. Cloud architectures built with Arista EOS scale to tens of thousands of compute and storage nodes with management and provisioning capabilities that work at scale. Through its programmability, EOS enables a set of software applications that deliver workflow automation, high availability, unprecedented network visibility and analytics and rapid integration with a wide range of third-party applications for virtualization, management, automation and orchestration services.\n\nArista Extensible Operating System (EOS) is a fully programmable and highly modular, Linux-based network operation system, using familiar industry standard CLI and runs a single binary software image across the Arista switching family. Architected for resiliency and programmability, EOS has a unique multi-process state sharing architecture that separates state information and packet forwarding from protocol processing and application logic.",
@@ -11,7 +12,6 @@
"status": "stable",
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"qemu": {
"adapter_type": "e1000",
"adapters": 8,
@@ -19,7 +19,6 @@
"arch": "x86_64",
"console_type": "telnet"
},
"images": [
{
"filename": "Aboot-veos-serial-2.1.0.iso",
@@ -34,7 +33,6 @@
"download_url": "https://www.arista.com/en/support/software-download"
}
],
"versions": [
{
"name": "4.13.8M",

View File

@@ -11,7 +11,6 @@
"status": "stable",
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"qemu": {
"adapter_type": "e1000",
"adapters": 1,
@@ -19,7 +18,6 @@
"arch": "i386",
"console_type": "telnet"
},
"images": [
{
"filename": "linux-microcore-3.4.1.img",
@@ -30,7 +28,6 @@
"direct_download_url": "http://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/linux-microcore-3.4.1.img"
}
],
"versions": [
{
"name": "3.4.1",

View File

@@ -0,0 +1,51 @@
{
"appliance_id": "96dac9ff-581c-4262-a9f0-5c68890d049e",
"category": "router",
"status": "experimental",
"maintainer": "GNS3 Team",
"name": "Cisco 3745",
"vendor_name": "Cisco",
"product_name": "3745",
"vendor_url": "http://www.cisco.com",
"description": "Cisco 3745 Multiservice Access Router",
"registry_version": 8,
"maintainer_email": "developers@gns3.net",
"documentation_url": "http://www.cisco.com/c/en/us/support/routers/3745-multiservice-access-router/model.html",
"settings": [
{
"template_type": "dynamips",
"template_properties": {
"chassis": "",
"platform": "c3745",
"ram": 256,
"nvram": 256,
"startup_config": "ios_base_startup-config.txt",
"slot0": "GT96100-FE",
"slot1": "NM-1FE-TX",
"slot2": "NM-4T",
"slot3": "",
"slot4": "",
"wic0": "WIC-1T",
"wic1": "WIC-1T",
"wic2": "WIC-1T",
"idlepc": "0x60aa1da0"
}
}
],
"versions": [
{
"name": "124-25d",
"images": {
"image": "c3745-adventerprisek9-mz.124-25d.image"
}
}
],
"images": [
{
"filesize": 82053028,
"md5sum": "ddbaf74274822b50fa9670e10c75b08f",
"version": "124-25d",
"filename": "c3745-adventerprisek9-mz.124-25d.image"
}
]
}

View File

@@ -1,4 +1,5 @@
{
"appliance_id": "96dac9ff-581c-4262-a9f0-5c68890d049e",
"category": "router",
"status": "experimental",
"maintainer": "GNS3 Team",
@@ -10,7 +11,6 @@
"registry_version": 2,
"maintainer_email": "developers@gns3.net",
"documentation_url": "http://www.cisco.com/c/en/us/support/routers/3745-multiservice-access-router/model.html",
"dynamips": {
"chassis": "",
"platform": "c3745",
@@ -26,7 +26,6 @@
"wic1": "WIC-1T",
"wic2": "WIC-1T"
},
"versions": [
{
"images": {

View File

@@ -0,0 +1,41 @@
{
"appliance_id": "a8f5935d-7229-4b32-8a01-24ef41758f2f",
"category": "router",
"status": "experimental",
"maintainer": "GNS3 Team",
"name": "Cisco IOU L3",
"vendor_name": "Cisco",
"product_name": "Cisco IOU L3",
"vendor_url": "http://www.cisco.com",
"description": "Cisco IOS on UNIX Layer 3 image.",
"registry_version": 8,
"maintainer_email": "developers@gns3.net",
"settings": [
{
"template_type": "iou",
"template_properties": {
"ethernet_adapters": 2,
"serial_adapters": 2,
"nvram": 128,
"ram": 256,
"startup_config": "iou_l3_base_startup-config.txt"
}
}
],
"versions": [
{
"images": {
"image": "i86bi-linux-l3-adventerprisek9-15.4.1T.bin"
},
"name": "15.4.1T"
}
],
"images": [
{
"filesize": 152677848,
"md5sum": "5d41402abc4b2a76b9719d911017c592",
"version": "15.4.1T",
"filename": "i86bi-linux-l3-adventerprisek9-15.4.1T.bin"
}
]
}

View File

@@ -1,4 +1,5 @@
{
"appliance_id": "a8f5935d-7229-4b32-8a01-24ef41758f2f",
"category": "router",
"status": "experimental",
"maintainer": "GNS3 Team",
@@ -9,7 +10,6 @@
"description": "Cisco IOS on UNIX Layer 3 image.",
"registry_version": 2,
"maintainer_email": "developers@gns3.net",
"iou": {
"ethernet_adapters": 2,
"serial_adapters": 2,
@@ -17,7 +17,6 @@
"ram": 256,
"startup_config": "iou_l3_base_startup-config.txt"
},
"versions": [
{
"images": {

View File

@@ -0,0 +1,115 @@
{
"appliance_id": "1cfdf900-7c30-4cb7-8f03-3f61d2581633",
"name": "Empty VM",
"category": "guest",
"description": "A empty VM with empty hard disks 8G, 30G, 100G & 200G.",
"vendor_name": "GNS3",
"vendor_url": "https://gns3.com",
"documentation_url": "",
"product_name": "QEMU",
"product_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
"registry_version": 8,
"status": "experimental",
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"usage": "Default at first boot the VM will start from the cdrom.",
"settings": [
{
"default": true,
"template_type": "qemu",
"template_properties": {
"adapter_type": "e1000",
"adapters": 1,
"ram": 1024,
"hda_disk_interface": "sata",
"platform": "x86_64",
"console_type": "vnc",
"boot_priority": "d"
}
},
{
"name": "i386 settings",
"template_type": "qemu",
"template_properties": {
"platform": "i386",
"adapters": 8
}
},
{
"name": "ARM settings",
"inherit_default_properties": false,
"template_type": "qemu",
"template_properties": {
"platform": "arm",
"ram": 512
}
}
],
"images": [
{
"filename": "empty8G.qcow2",
"version": "8G",
"md5sum": "f1d2c25b6990f99bd05b433ab603bdb4",
"filesize": 197120,
"download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty8G.qcow2/download"
},
{
"filename": "empty30G.qcow2",
"version": "30G",
"checksum": "3411a599e822f2ac6be560a26405821a",
"filesize": 197120,
"download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty30G.qcow2/download"
},
{
"filename": "empty100G.qcow2",
"version": "100G",
"checksum": "d08fdec95fffbda3f04e9a00db49295df73ae4a507396e442ba9e4ad5c14ce5a",
"checksum_type": "sha256",
"filesize": 198656,
"download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty100G.qcow2/download"
},
{
"filename": "empty200G.qcow2",
"version": "200G",
"md5sum": "d1686d2f25695dee32eab9a6f4652c7c",
"filesize": 200192,
"download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty200G.qcow2/download"
}
],
"versions": [
{
"name": "8G",
"images": {
"hda_disk_image": "empty8G.qcow2"
}
},
{
"name": "30G",
"settings": "i386 settings",
"images": {
"hda_disk_image": "empty30G.qcow2"
}
},
{
"name": "100G",
"settings": "ARM settings",
"images": {
"hda_disk_image": "empty100G.qcow2"
}
},
{
"name": "200G",
"settings": "ARM settings",
"usage": "This is how to use this version",
"symbol": "ethernet_switch",
"category": "switch",
"images": {
"hda_disk_image": "empty200G.qcow2"
}
}
]
}

View File

@@ -3,8 +3,6 @@
"vendor_name": "Juniper",
"product_name": "vSRX",
"name": "vSRX",
"description": "The vSRX delivers core firewall, networking, advanced security, and automated lifecycle management capabilities for enterprises and service providers. The industry\u2019s fastest virtual security platform, the vSRX offers firewall speeds up to 17 Gbps using only two virtual CPUs, providing scalable, secure protection across private, public, and hybrid clouds.",
"maintainer_email": "developers@gns3.net",
"documentation_url": "http://www.juniper.net/techpubs/",
@@ -13,7 +11,6 @@
"status": "experimental",
"vendor_url": "https://www.juniper.net",
"registry_version": 1,
"qemu": {
"console_type": "telnet",
"ram": 2000,
@@ -23,8 +20,6 @@
"options": "-smp 2",
"kvm": "require"
},
"images": [
{
"version": "12.1X47.4-domestic",
@@ -34,7 +29,6 @@
"download_url": "https://www.juniper.net/us/en/dm/free-vsrx-trial/"
}
],
"versions": [
{
"name": "12.1X47.4-domestic",
@@ -43,4 +37,4 @@
}
}
]
}
}

View File

@@ -12,7 +12,6 @@
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"usage": "Just start the appliance",
"qemu": {
"adapter_type": "e1000",
"adapters": 1,
@@ -21,7 +20,6 @@
"console_type": "telnet",
"kvm": "allow"
},
"images": [
{
"filename": "linux-microcore-3.4.1.img",
@@ -40,7 +38,6 @@
"direct_download_url": "http://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/linux-microcore-4.0.2-clean.img"
}
],
"versions": [
{
"name": "3.4.1",

View File

@@ -0,0 +1,24 @@
{
"appliance_id": "40c84186-752f-4a71-b6fb-961ba36e8cf7",
"name": "Open vSwitch",
"category": "multilayer_switch",
"description": "Open vSwitch is a production quality, multilayer virtual switch licensed under the open source Apache 2.0 license. It is designed to enable massive network automation through programmatic extension, while still supporting standard management interfaces and protocols (e.g. NetFlow, sFlow, IPFIX, RSPAN, CLI, LACP, 802.1ag). In addition, it is designed to support distribution across multiple physical servers similar to VMware's vNetwork distributed vswitch or Cisco's Nexus 1000V.",
"vendor_name": "Open vSwitch",
"vendor_url": "http://openvswitch.org/",
"documentation_url": "http://openvswitch.org/support/",
"product_name": "Open vSwitch",
"registry_version": 8,
"status": "stable",
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"usage": "By default all interfaces are connected to the br0",
"settings": [
{
"template_type": "docker",
"template_properties": {
"adapters": 16,
"image": "gns3/openvswitch:latest"
}
}
]
}

View File

@@ -1,4 +1,5 @@
{
"appliance_id": "40c84186-752f-4a71-b6fb-961ba36e8cf7",
"name": "Open vSwitch",
"category": "multilayer_switch",
"description": "Open vSwitch is a production quality, multilayer virtual switch licensed under the open source Apache 2.0 license. It is designed to enable massive network automation through programmatic extension, while still supporting standard management interfaces and protocols (e.g. NetFlow, sFlow, IPFIX, RSPAN, CLI, LACP, 802.1ag). In addition, it is designed to support distribution across multiple physical servers similar to VMware's vNetwork distributed vswitch or Cisco's Nexus 1000V.",

View File

@@ -202,6 +202,62 @@ def test_create_new_version():
os.remove(wrong_appliance_file)
def test_emulator():
assert Appliance(registry, os.path.abspath("tests/registry/appliances/microcore-linux.gns3a")).emulator() == "qemu"
assert Appliance(registry, os.path.abspath("tests/registry/appliances/cisco-iou-l3.gns3a")).emulator() == "iou"
def test_template_type():
assert Appliance(registry, os.path.abspath("tests/registry/appliances/microcore-linux.gns3a")).template_type() == "qemu"
assert Appliance(registry, os.path.abspath("tests/registry/appliances/cisco-iou-l3.gns3a")).template_type() == "iou"
def test_checksum_in_appliance_format_v8(registry, images_dir):
path_empty_8g = os.path.join(images_dir, "QEMU", "empty8G.qcow2")
with open(path_empty_8g, 'w+') as f:
f.write("hello")
appliance = Appliance(registry, os.path.abspath("tests/registry/appliances/empty-vm-v8.gns3a"))
appliance._appliance['versions'][0]['images']['hda_disk_image']['filesize'] = 5
appliance._appliance['versions'][0]['images']['hda_disk_image']['md5sum'] = "5d41402abc4b2a76b9719d911017c592"
detected = appliance.search_images_for_version("8G")
assert detected["images"][0]["md5sum"] == "5d41402abc4b2a76b9719d911017c592"
assert detected["images"][0]["filesize"] == 5
path_empty_30g = os.path.join(images_dir, "QEMU", "empty30G.qcow2")
os.rename(path_empty_8g, path_empty_30g)
appliance._appliance['versions'][1]['images']['hda_disk_image']['filesize'] = 5
appliance._appliance['versions'][1]['images']['hda_disk_image']['checksum'] = "5d41402abc4b2a76b9719d911017c592"
detected = appliance.search_images_for_version("30G")
assert detected["images"][0]["md5sum"] == "5d41402abc4b2a76b9719d911017c592"
assert detected["images"][0]["filesize"] == 5
with pytest.raises(ApplianceError):
appliance.search_images_for_version("100G")
def test_multiple_different_template_types_found():
appliance_path = "tests/registry/appliances/empty-vm-v8.gns3a"
wrong_appliance_fp, wrong_appliance_file = tempfile.mkstemp()
with open(appliance_path, encoding='utf-8') as f:
appliance = json.loads(f.read())
appliance["settings"][0]["template_type"] = "dynamips"
os.write(wrong_appliance_fp, json.dumps(appliance).encode())
os.close(wrong_appliance_fp)
with pytest.raises(ApplianceError):
Appliance(registry, wrong_appliance_file).template_type()
def test_appliance_without_settings():
appliance_path = "tests/registry/appliances/empty-vm-v8.gns3a"
wrong_appliance_fp, wrong_appliance_file = tempfile.mkstemp()
with open(appliance_path, encoding='utf-8') as f:
appliance = json.loads(f.read())
del appliance["settings"]
os.write(wrong_appliance_fp, json.dumps(appliance).encode())
os.close(wrong_appliance_fp)
with pytest.raises(ApplianceError):
Appliance(registry, wrong_appliance_file).template_type()

View File

@@ -64,8 +64,15 @@ def empty_config(tmpdir, images_dir, symbols_dir, local_server_config):
return Config(path)
def test_add_appliance_iou(iou_l3):
with open("tests/registry/appliances/cisco-iou-l3.gns3a", encoding="utf-8") as f:
@pytest.mark.parametrize(
"appliance_file",
[
"cisco-iou-l3.gns3a",
"cisco-iou-l3-v8.gns3a"
]
)
def test_add_appliance_iou(iou_l3, appliance_file):
with open("tests/registry/appliances/{}".format(appliance_file), encoding="utf-8") as f:
config = json.load(f)
config["images"] = [
{
@@ -74,7 +81,7 @@ def test_add_appliance_iou(iou_l3):
"path": iou_l3
}
]
new_template = ApplianceToTemplate().new_template(config, "local")
new_template = ApplianceToTemplate().new_template(config, "local", "15.4.1T")
assert new_template == {
"category": "router",
"template_type": "iou",
@@ -91,8 +98,15 @@ def test_add_appliance_iou(iou_l3):
}
def test_add_appliance_docker():
with open("tests/registry/appliances/openvswitch.gns3a", encoding="utf-8") as f:
@pytest.mark.parametrize(
"appliance_file",
[
"openvswitch.gns3a",
"openvswitch-v8.gns3a"
]
)
def test_add_appliance_docker(appliance_file):
with open("tests/registry/appliances/{}".format(appliance_file), encoding="utf-8") as f:
config = json.load(f)
new_template = ApplianceToTemplate().new_template(config, "local")
@@ -108,8 +122,15 @@ def test_add_appliance_docker():
}
def test_add_appliance_dynamips(cisco_3745):
with open("tests/registry/appliances/cisco-3745.gns3a", encoding="utf-8") as f:
@pytest.mark.parametrize(
"appliance_file",
[
"cisco-3745.gns3a",
"cisco-3745-v8.gns3a"
]
)
def test_add_appliance_dynamips(cisco_3745, appliance_file):
with open("tests/registry/appliances/{}".format(appliance_file), encoding="utf-8") as f:
config = json.load(f)
config["images"] = [
{
@@ -120,7 +141,7 @@ def test_add_appliance_dynamips(cisco_3745):
}
]
new_template = ApplianceToTemplate().new_template(config, "local")
new_template = ApplianceToTemplate().new_template(config, "local", "124-25d")
assert new_template == {
"template_type": "dynamips",
"category": "router",
@@ -166,7 +187,6 @@ def test_add_appliance_guest(linux_microcore_img):
"symbol": ":/symbols/qemu_guest.svg",
"hda_disk_image": "linux-microcore-3.4.1.img",
"name": "Micro Core Linux",
"options": "",
"qemu_path": "qemu-system-i386",
"usage": "Just start the appliance",
"ram": 32,
@@ -238,8 +258,15 @@ def test_add_appliance_with_boot_priority(linux_microcore_img):
assert new_template["boot_priority"] == "dc"
def test_add_appliance_router_two_disk(images_dir):
with open("tests/registry/appliances/arista-veos.gns3a", encoding="utf-8") as f:
@pytest.mark.parametrize(
"appliance_file",
[
"arista-veos.gns3a",
"arista-veos-v8.gns3a"
]
)
def test_add_appliance_router_two_disk(images_dir, appliance_file):
with open("tests/registry/appliances/{}".format(appliance_file), encoding="utf-8") as f:
config = json.load(f)
config["images"] = [
@@ -255,8 +282,8 @@ def test_add_appliance_router_two_disk(images_dir):
}
]
new_template = ApplianceToTemplate().new_template(config, "local")
assert new_template == {
new_template = ApplianceToTemplate().new_template(config, "local", "4.13.8M")
expected_result = {
"template_type": "qemu",
"adapter_type": "e1000",
"adapters": 8,
@@ -265,12 +292,76 @@ def test_add_appliance_router_two_disk(images_dir):
"hda_disk_image": "a",
"hdb_disk_image": "b",
"name": "Arista vEOS",
"options": "",
"qemu_path": "qemu-system-x86_64",
"ram": 2048,
"console_type": "telnet",
"compute_id": "local"
}
if "v8" in appliance_file:
expected_result["platform"] = "x86_64" # platform was added in v8
assert new_template == expected_result
def test_add_appliance_v8_default_properties_inheritance(images_dir):
with open("tests/registry/appliances/empty-vm-v8.gns3a", encoding="utf-8") as f:
config = json.load(f)
# check that default properties are used
new_template = ApplianceToTemplate().new_template(config, "local", "8G")
expected_result = {
"name": "Empty VM",
"template_type": "qemu",
"symbol": ":/symbols/qemu_guest.svg",
"category": "guest",
"adapter_type": "e1000",
"adapters": 1,
"ram": 1024,
"qemu_path": "qemu-system-x86_64",
"hda_disk_interface": "sata",
"platform": "x86_64",
"console_type": "vnc",
"boot_priority": "d",
"compute_id": "local",
"usage": "Default at first boot the VM will start from the cdrom."
}
assert new_template == expected_result
# check that specific properties are used along with default properties
new_template = ApplianceToTemplate().new_template(config, "local", "30G")
expected_result.update(
{
"adapters": 8,
"qemu_path": "qemu-system-i386",
"platform": "i386",
}
)
assert new_template == expected_result
# check that specific properties are used along without default properties
new_template = ApplianceToTemplate().new_template(config, "local", "100G")
expected_result = {
"name": "Empty VM",
"template_type": "qemu",
"symbol": ":/symbols/qemu_guest.svg",
"category": "guest",
"ram": 512,
"qemu_path": "qemu-system-arm",
"platform": "arm",
"compute_id": "local",
"usage": "Default at first boot the VM will start from the cdrom."
}
assert new_template == expected_result
# check that specific properties are used with "usage", "symbol" and "category" defined at the version level
new_template = ApplianceToTemplate().new_template(config, "local", "200G")
expected_result.update(
{
"usage": "This is how to use this version",
"symbol": "ethernet_switch",
"category": "switch"
}
)
assert new_template == expected_result
def test_add_appliance_path_relative_to_images_dir(tmpdir, linux_microcore_img):

View File

@@ -47,7 +47,7 @@ def http_client(http_request, network_manager):
return HTTPClient({"protocol": "http", "host": "127.0.0.1", "port": "3080"}, network_manager=network_manager)
@pytest.yield_fixture(autouse=True)
@pytest.fixture(autouse=True)
def http_request():
mock = unittest.mock.Mock()

View File

@@ -34,7 +34,7 @@ def images_dir(tmpdir):
return path
@pytest.yield_fixture
@pytest.fixture
def image_manager(tmpdir, images_dir):
ImageManager._instance = None
settings = LOCAL_SERVER_SETTINGS

View File

@@ -16,7 +16,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import json
import pytest
import logging
import subprocess
@@ -32,7 +31,7 @@ def local_server_path(tmpdir):
return str(tmpdir / "gns3server")
@pytest.yield_fixture
@pytest.fixture
def local_server(local_server_path, tmpdir):
with open(str(tmpdir / "test.cfg"), "w+") as f:
f.write("""
@@ -87,7 +86,7 @@ def test_startLocalServer(tmpdir, local_server, local_server_path):
'--debug',
'--log=' + str(tmpdir / "gns3_server.log"),
'--pid=' + str(tmpdir / "gns3_server.pid")
], stderr=unittest.mock.ANY)
], stderr=unittest.mock.ANY, env=unittest.mock.ANY)
def test_killAlreadyRunningServer(local_server):

View File

@@ -17,28 +17,30 @@
import os
import shlex
import pytest
from unittest.mock import patch
from gns3.spice_console import spiceConsole
def test_spice_console_on_linux_and_mac(vpcs_device):
with patch('subprocess.Popen') as popen, \
patch('sys.platform', new="linux"):
vpcs_device.settings()["console_host"] = "localhost"
spiceConsole(vpcs_device, '2525', 'command %h %p')
popen.assert_called_once_with(shlex.split('command localhost 2525'), env=os.environ)
popen.assert_called_with(shlex.split('command localhost 2525'), env=os.environ)
spiceConsole(vpcs_device, '2525', 'command {host} {port}')
popen.assert_called_with(shlex.split('command localhost 2525'), env=os.environ)
def test_spice_console_on_windows(vpcs_device):
with patch('subprocess.Popen') as popen, \
with patch('subprocess.Popen') as p, \
patch('sys.platform', new="win"):
vpcs_device.settings()["console_host"] = "localhost"
spiceConsole(vpcs_device, '2525', 'command %h %p')
popen.assert_called_once_with('command localhost 2525')
p.assert_called_with('command localhost 2525', env=os.environ)
spiceConsole(vpcs_device, '2525', 'command {host} {port}')
p.assert_called_with('command localhost 2525', env=os.environ)
# def test_spice_console_on_linux_with_popen_issues():
# with patch('subprocess.Popen', side_effect=OSError("Dummy")), \
@@ -49,9 +51,11 @@ def test_spice_console_on_windows(vpcs_device):
def test_spice_console_with_ipv6_support(vpcs_device):
with patch('subprocess.Popen') as popen, \
patch('sys.platform', new="linux"):
vpcs_device.settings()["console_host"] = "::1"
spiceConsole(vpcs_device, '2525', 'command %h %p')
popen.assert_called_once_with(shlex.split('command [::1] 2525'), env=os.environ)
popen.assert_called_with(shlex.split('command [::1] 2525'), env=os.environ)
spiceConsole(vpcs_device, '2525', 'command {host} {port}')
popen.assert_called_with(shlex.split('command [::1] 2525'), env=os.environ)

View File

@@ -26,14 +26,14 @@ from gns3.update_manager import UpdateManager
from gns3 import version
@pytest.yield_fixture
@pytest.fixture
def frozen():
sys.frozen = True
yield
delattr(sys, 'frozen')
@pytest.yield_fixture
@pytest.fixture
def devVersion():
old_version_info = version.__version_info__
old_version = version.__version__
@@ -44,7 +44,7 @@ def devVersion():
version.__version__ = old_version
@pytest.yield_fixture
@pytest.fixture
def stableVersion():
old_version_info = version.__version_info__
old_version = version.__version__

View File

@@ -17,31 +17,36 @@
import os
import shlex
import pytest
from unittest.mock import patch
from gns3.vnc_console import vncConsole
def test_vnc_console_on_linux_and_mac(vpcs_device):
with patch('subprocess.Popen') as popen, \
with patch('subprocess.Popen') as p, \
patch('sys.platform', new="linux"):
vpcs_device.settings()["console_host"] = "localhost"
vncConsole(vpcs_device, 6000, 'command %h %p %P')
popen.assert_called_once_with(shlex.split('command localhost 6000 100'), env=os.environ)
vncConsole(vpcs_device, 6000, 'command %h %p %D')
p.assert_called_with(shlex.split('command localhost 6000 100'), env=os.environ)
vncConsole(vpcs_device, 6000, 'command {host} {port} {display}')
p.assert_called_with(shlex.split('command localhost 6000 100'), env=os.environ)
def test_vnc_console_on_windows(vpcs_device):
with patch('subprocess.Popen') as popen, \
with patch('subprocess.Popen') as p, \
patch('sys.platform', new="win"):
vpcs_device.settings()["console_host"] = "localhost"
vncConsole(vpcs_device, 6000, 'command %h %p %P')
popen.assert_called_once_with('command localhost 6000 100')
vncConsole(vpcs_device, 6000, 'command %h %p %D')
p.assert_called_with('command localhost 6000 100', env=os.environ)
vncConsole(vpcs_device, 6000, 'command {host} {port} {display}')
p.assert_called_with('command localhost 6000 100', env=os.environ)
# def test_vnc_console_on_linux_with_popen_issues():
# with patch('subprocess.Popen', side_effect=OSError("Dummy")), \
# def test_vnc_console_on_linux_with_popen_issues(vpcs_device):
# with patch('subprocess.Popen', side_effect=subprocess.SubprocessError()), \
# patch('sys.platform', new="linux"):
# vpcs_device.settings()["console_host"] = "localhost"
#
# with pytest.raises(OSError):
# vncConsole('localhost', 6000, 'command %h %p %P')
# with pytest.raises(subprocess.SubprocessError):
# vncConsole(vpcs_device, 6000, 'command %h %p %P')

View File

@@ -1,4 +1,4 @@
-rrequirements.txt
PyQt5==5.15.7 # pyup: ignore
pywin32==305 # pyup: ignore
PyQt5==5.15.10 # pyup: ignore
pywin32==306 # pyup: ignore