Compare commits

...

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

Further , more detail information can be found at

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-09 20:48:41 +00:00
grossmj
6619c6af97 Add PyQt5==5.12.3 for macOS build 2021-08-07 20:09:41 +09:30
grossmj
60e04c7248 Development on 2.2.24dev1 2021-08-05 21:16:10 +09:30
grossmj
724858f977 Release v2.2.23 2021-08-05 15:58:54 +09:30
Jeremy Grossmann
5a2e05a4fd Merge pull request #3212 from GNS3/handle_no-kvm_deprecated
Handle -no-kvm param deprecated in Qemu >= v5.2
2021-07-27 17:37:45 +09:30
grossmj
010888e3ca Handle -no-kvm param deprecated in Qemu >= v5.2 2021-07-27 16:34:51 +09:30
grossmj
3226921536 Support for invisible links. Fixes #2461 2021-07-27 15:30:58 +09:30
grossmj
022e918301 Add kitty console application command line. Fixes #3203 2021-07-25 16:12:35 +09:30
grossmj
846b19a9e7 Add Windows Terminal profile as an option for Console Applications. Fixes #3193 2021-06-14 13:10:01 +09:30
grossmj
45f5c6e010 Development on 2.2.23dev1 2021-06-10 16:20:10 +09:30
grossmj
963bbb7b89 Release v2.2.22 2021-06-10 15:41:58 +09:30
grossmj
016ad7a775 Fix exception shown when GNS3 is started with empty config. Fixes #3188 2021-06-10 12:28:25 +09:30
grossmj
e8c82566c6 Add ZOC8 console terminal for macOS command line 2021-06-07 19:41:09 +09:30
grossmj
1ed6fceade Fix tests. Ref https://github.com/GNS3/gns3-gui/issues/2461 2021-06-07 14:26:20 +09:30
grossmj
d945fd8b7b Minor changes to style editor dialog. 2021-06-07 14:10:32 +09:30
grossmj
fd6c7eccd0 Link style support. Fixes https://github.com/GNS3/gns3-gui/issues/2461 2021-06-07 14:09:58 +09:30
grossmj
7a1afe2aec Upgrade dependencies 2021-06-07 13:46:06 +09:30
grossmj
6debe56d8e Fix charcoal theme. Ref #3137 2021-06-06 21:45:16 +09:30
grossmj
a4c7d41c26 Fix issue when showing menu to select port. Fixes #3169 2021-05-20 22:15:21 -07:00
grossmj
ea9243dcd9 Merge remote-tracking branch 'origin/2.2' into 2.2 2021-05-20 15:00:49 +09:30
grossmj
e9d8337bd6 Revert "Downgrade to PyQt5 5.12.1. Fixes https://github.com/GNS3/gns3-gui/issues/3169"
This reverts commit ece4d51213.
2021-05-20 14:59:44 +09:30
Jeremy Grossmann
3c92e463f8 Update setup.py
Fixes https://github.com/GNS3/gns3-server/issues/1897
2021-05-16 17:45:11 +09:30
grossmj
3d07db5c5f Development on 2.2.22dev1 2021-05-10 23:44:40 +09:30
grossmj
20cc309ac8 Release v2.2.21 2021-05-10 22:42:47 +09:30
grossmj
262a2839c5 Fix issue with empty project variable name. Fixes #3162 2021-05-10 17:55:06 +09:30
grossmj
ece4d51213 Downgrade to PyQt5 5.12.1. Fixes https://github.com/GNS3/gns3-gui/issues/3169 2021-05-10 17:03:22 +09:30
grossmj
0ef39ba129 Development on 2.2.21dev1 2021-04-09 13:50:21 +09:30
grossmj
f90267b4f0 Release v2.2.20 2021-04-09 12:14:38 +09:30
grossmj
8f16706a22 Merge branch 'master' into 2.2 2021-04-09 12:05:15 +09:30
grossmj
2d3ee3abf9 Fix project does not load anymore. Fixes #3140 2021-04-07 16:47:09 +09:30
grossmj
b8b209fa55 Fix errors while connecting to server 2021-04-07 15:51:52 +09:30
grossmj
18129e3d29 Do not connect to server while waiting for user to accept/reject SSL certificate. Fixes #3144 2021-04-07 12:09:38 +09:30
grossmj
7a2b9c024f Fix invalid server version check request. Fixes #3144 2021-04-07 12:03:37 +09:30
grossmj
4923a6dc17 Revert to PyQt5 v5.12.3 because of SSL not working
Probably the SSL DLLs weren't properly found and included by cx_Freeze
2021-04-06 23:01:07 +09:30
grossmj
73dfc047aa Set PyQt5 version to 5.15.2 on Windows 2021-04-06 22:12:34 +09:30
grossmj
fe0a70c4be Upgrade dependencies 2021-04-06 13:57:00 +09:30
Tomas Hrnciar
67014965be Explicitly require setuptools, utils/get_resource.py imports pkg_resources 2021-03-31 11:53:28 +02:00
Jeremy Grossmann
f14cb43404 Merge pull request #3153 from VidVidex/master
Add terminator as a predefined custom console option
2021-03-26 14:53:00 +10:30
Vid
f8517ee5ac Add terminator as a predefined custom console option 2021-03-24 20:28:19 +01:00
grossmj
7dc607b4c5 Development on 2.2.20dev1 2021-03-05 16:48:09 +10:30
grossmj
882fa76550 Release v2.2.19 2021-03-05 14:51:03 +10:30
grossmj
1490a1ad8f Development on 2.2.19dev1 2021-02-16 20:44:58 +10:30
grossmj
aab0c99cc6 Release v2.2.18 2021-02-16 19:09:46 +10:30
grossmj
a6a987d74c Fix bug with SSL connection on projet websocket stream. 2021-02-16 17:42:18 +10:30
grossmj
9c58b18c20 Merge remote-tracking branch 'origin/2.2' into 2.2 2021-02-16 16:42:45 +10:30
grossmj
8bc499c68f Bump version to 2.2.18dev2 2021-02-16 16:35:26 +10:30
Jeremy Grossmann
bd5eb288b7 Merge pull request #3130 from GNS3/ssl-support
SSL support.
2021-02-16 16:16:32 +10:30
grossmj
465a289568 SSL support. 2021-02-16 16:08:27 +10:30
grossmj
d240ba3056 Merge remote-tracking branch 'origin/2.2' into 2.2 2021-01-26 23:11:11 +10:30
grossmj
3cedfd3649 Remove the useless file "zoom-in (copy).svg". Fixes #3114 2021-01-26 23:10:34 +10:30
Jeremy Grossmann
276d7abdd9 Merge pull request #3104 from b-ehlers/QemuConfig
Add Qemu config disk
2020-12-14 14:23:09 +10:30
grossmj
927e38bd6d Development on 2.2.18dev1 2020-12-04 18:10:11 +10:30
grossmj
376cc29995 Release v2.2.17 2020-12-04 16:26:56 +10:30
grossmj
1f8ebeb084 Merge branch 'master' into 2.2 2020-12-04 16:21:42 +10:30
grossmj
0212755c78 Remove "-nographic" option by default for Qemu VM. Fixes #3094 2020-12-02 18:44:18 +10:30
Jeremy Grossmann
2f7d75eae9 Fix app cannot start on macOS Big Sur. Ref #3037 2020-11-30 20:02:52 +10:30
Jeremy Grossmann
fc1c060922 Merge pull request #3097 from SpikefishSolutions/master
Add yes/no prompts to gui for global project level buttons start/stop/reload/suspend to prevent bad day.
2020-11-21 18:32:45 +10:30
John
0ea72ce782 one more spacing update 2020-11-20 21:33:44 -05:00
John
3de2d2eda2 spacing updates 2020-11-20 21:32:26 -05:00
John
c08262f8af Correct stop/start/reload/suspend button names 2020-11-20 21:26:08 -05:00
John
9ae70bf2fe Add yes/no prompts to all major buttons 2020-11-20 21:11:15 -05:00
John
fa6d250602 oops.. need to build after commit. 2020-11-20 20:27:43 -05:00
John
0668840a2b i don't get it. 2020-11-20 20:26:19 -05:00
John
8b25d1b06c can't fix indent? 2020-11-20 20:25:06 -05:00
John
58c3ba0755 update indent 2020-11-20 20:23:46 -05:00
John
5a91c9aaf8 Create a message box for stopping all devives instead of blindly making someone's day terrible. 2020-11-20 18:28:30 -05:00
grossmj
0fc3f4ef16 Development on 2.2.17dev1 2020-11-05 16:59:58 +10:30
grossmj
f0e5cd2ba2 Release v2.2.16 2020-11-05 15:38:19 +10:30
grossmj
f59ef6378a Fix broken security link (replaced by email). Fixes #3085 2020-11-05 15:00:04 +10:30
grossmj
61ef08d1b7 Fix packets capture stops after some time. Fixes #3067 2020-11-05 14:21:22 +10:30
grossmj
e812c000fd Option to allocate or not the vCPUs and RAM settings for the GNS3 VM. Fixes https://github.com/GNS3/gns3-gui/issues/3069 2020-11-05 11:13:57 +10:30
Bernhard Ehlers
d3d9e1e8ae Use HDD disk image as startup QEMU config disk 2020-10-19 03:45:27 +02:00
Bernhard Ehlers
05f8df345a Fix HDD configuration layout
(cherry picked from commit 4f631669e5)
2020-10-16 10:22:32 +02:00
grossmj
4b0cc11cab Development on 2.2.16dev1 2020-10-07 16:30:03 +10:30
grossmj
b5285cd142 Release v2.2.15 2020-10-07 15:29:52 +10:30
grossmj
69482343ba Fix custom symbol not sent to remote controller when installing appliance 2020-10-07 15:09:08 +10:30
grossmj
d4639c2e61 Development on 2.2.15dev1 2020-09-15 06:49:11 +09:30
grossmj
b85ade9dd7 Release v2.2.14 2020-09-15 05:52:48 +09:30
grossmj
e191cb8aa3 Fix tests. Ref #3002 2020-09-14 00:10:11 +09:30
grossmj
e6bc75ce26 Improvements to add a new version of an appliance from wizard. Fixes #3002. 2020-09-14 00:04:58 +09:30
grossmj
bc1df346f2 Development on 2.2.14dev1 2020-09-05 04:26:16 +09:30
grossmj
27c35321f0 Release v2.2.13 2020-09-04 23:13:28 +09:30
Bernhard Ehlers
3e212fc629 Edit only text mode config files
(cherry picked from commit 880ac5e8c3)
2020-08-18 02:27:31 +02:00
Bernhard Ehlers
25e41dc0f1 Hide config import/export when configFiles attribute is empty
(cherry picked from commit fd7b915e96)
2020-08-17 13:09:59 +02:00
grossmj
c58c7774c4 Qemu disk interfaces must be set to "none" by default. Ref #3035
(cherry picked from commit 5fbb6cbf61)
2020-08-17 12:49:21 +09:30
grossmj
bd2bc8265c Do not allow image to be configured on Qemu VM secondary slave disk if create config disk option is enabled.
(cherry picked from commit 04f9a1cf8c)
2020-08-15 16:05:43 +09:30
grossmj
f2209a2780 Add explicit option to automatically create or not the config disk. Off by default.
(cherry picked from commit af79471afd)
2020-08-14 17:57:24 +09:30
grossmj
7b99ba325b Development on 2.2.13dev1 2020-08-07 21:12:46 +09:30
grossmj
74763287fb Release v2.2.12 2020-08-07 19:27:32 +09:30
grossmj
737ff42d64 Merge branch 'master' into 2.2 2020-08-07 19:04:08 +09:30
Jeremy Grossmann
5656bd2d48 Downgrade psutil to version 5.6.7 2020-07-29 17:36:14 +09:30
Jeremy Grossmann
058c069394 Fix log shows the GUI command line without spaces between its arguments. Fixes #3026 2020-07-27 18:27:23 +09:30
grossmj
926ec48d00 Upgrade to psutil version 5.7.2 2020-07-21 15:49:58 +09:30
grossmj
410e5353b2 Use server host is console host is equal to "0:0:0:0:0:0:0:0" 2020-07-17 21:13:27 +09:30
grossmj
bfb90406ed Remove VMware promotion. 2020-07-17 21:12:59 +09:30
grossmj
439cdce287 Development on 2.2.12dev1 2020-07-09 21:37:03 +09:30
grossmj
4e50c2a4b1 Release v2.2.11 2020-07-09 20:37:10 +09:30
grossmj
94c636ae61 Merge branch 'master' into 2.2 2020-07-09 20:23:31 +09:30
grossmj
f53b9a266e Try to fix "Recent project" selection not working. Ref #3007 2020-07-09 15:37:47 +09:30
grossmj
2476448032 Fix debug entries shown twice in console window and double error messages with remote GNS3VM. Fixes #3010 #3011
Note that the debug level is broken (has been for a long time apparently).
2020-07-07 21:06:56 +09:30
grossmj
a34cd742e3 Merge branch '2.2' 2020-07-06 22:20:05 +09:30
grossmj
39698196ac Fix deprecation warning. Ref #3009 2020-07-06 22:08:10 +09:30
grossmj
61432ced4f Fix tests on macOS. Ref #3009 2020-07-06 22:03:29 +09:30
grossmj
2ddd13c445 Add Snyk badges. 2020-06-27 18:27:04 +09:30
grossmj
af6c4c5b3e Merge branch 'master' into 2.2 2020-06-26 21:30:37 +09:30
grossmj
d4012294bf Run tests inside container 2020-06-26 20:50:48 +09:30
grossmj
4b04b0e855 Use xvfb to run tests 2020-06-26 20:41:08 +09:30
grossmj
1bec5019bf Install PyQt5 using pip 2020-06-26 19:42:42 +09:30
grossmj
ec6b876baa Explicitly install sip 2020-06-26 19:38:10 +09:30
grossmj
7cee0d01ab Install PyQt5 for tests and add GitHub Actions badge 2020-06-26 19:34:02 +09:30
Jeremy Grossmann
dd3314d06b Set up GitHub Actions for running tests 2020-06-26 19:27:22 +09:30
grossmj
9c58b26265 Remove tox, Travis CI and pep8.sh script
Update dependencies
2020-06-26 19:21:51 +09:30
Jeremy Grossmann
83c26f47da Merge pull request #3005 from GNS3/whitesource/configure
Configure WhiteSource for GitHub.com
2020-06-26 09:04:39 +08:00
whitesource-for-github-com[bot]
8ed2f55600 Add .whitesource configuration file 2020-06-26 01:01:54 +00:00
Jeremy Grossmann
b435317904 Merge pull request #3003 from GNS3/snyk-fix-8bf3e4840df4587cc42afb85b857d470
[Snyk] Security upgrade psutil from 5.6.6 to 5.6.7
2020-06-25 11:41:01 +08:00
snyk-bot
acb8aa8ca2 fix: requirements.txt to reduce vulnerabilities
The following vulnerabilities are fixed by pinning transitive dependencies:
- https://snyk.io/vuln/SNYK-PYTHON-PSUTIL-483082
2020-06-24 13:10:33 +00:00
grossmj
c55d6b8a6f Fix sentry SDK is configured twice. 2020-06-24 12:40:06 +09:30
grossmj
a4039a254e Development on 2.2.11dev1 2020-06-18 19:06:00 +09:30
grossmj
85ed4b3026 Release v2.2.10 2020-06-18 12:29:33 +09:30
grossmj
5207a99692 New fix for multi-device selection/deselection not working as expected with right click. Fixes #2986 2020-06-09 13:50:31 +09:30
grossmj
d69527995d Revert "Fix Multi-device selection/deselection not working as expected with right click. Fixes #2986"
This reverts commit ddeb95cb0a.
2020-06-09 13:19:20 +09:30
grossmj
4b000ba2f7 Optimize snap-to-grid code for drawing items. Fixes #2997 2020-06-09 11:47:05 +09:30
grossmj
1b302b77a0 Move jsonschema 2.6.0 requirement in build repository.
https://github.com/GNS3/gns3-server/issues/1751
https://github.com/GNS3/gns3-gui/issues/2849

This is to avoid the following error:

```
ERROR: Double requirement given: jsonschema==2.6.0 (from -r gns3-gui\win-requirements.txt (line 4)) (already in jsonschema==3.2.0 (from -r gns3-gui\requirements.txt (line 1)), name='jsonschema')
```
2020-06-07 13:07:16 +09:30
grossmj
a5b5c404ec Only use jsonschema 2.6.0 on Windows and macOS.
https://github.com/GNS3/gns3-server/issues/1751
https://github.com/GNS3/gns3-gui/issues/2849
2020-06-07 12:55:22 +09:30
grossmj
6b97b0c6cd Disable default integrations for sentry sdk. 2020-06-06 15:37:17 +09:30
grossmj
115dd43eee Development on 2.2.10dev1 2020-06-04 21:06:30 +09:30
grossmj
2530bf97a8 Release v2.2.9 2020-06-04 18:39:27 +09:30
grossmj
9892fd0654 Fix issue editing README.txt on Windows. 2020-06-04 18:12:50 +09:30
grossmj
c71ee73da8 Merge branch 'master' into 2.2 2020-06-04 12:22:11 +09:30
Jeremy Grossmann
0643fd516d Merge pull request #2993 from GNS3/replicate-network-connection-state
Support to activate/deactive network connection state replication in Qemu
2020-06-04 10:49:41 +08:00
grossmj
a25680f2ce Fix GUI doesn't detect another GUI on macOS. Fixes #2994 2020-06-03 20:38:55 +09:30
grossmj
58bd5be920 Support to activate/deactive network connection state replication in Qemu. 2020-06-02 18:45:22 +09:30
Jeremy Grossmann
d95633ba2c Merge pull request #2989 from GNS3/reset-mac-addresses
Option to reset all MAC addresses when exporting or duplicating a project.
2020-05-27 10:53:22 +08:00
grossmj
dfea6d1723 Option to reset or not all MAC addresses when exporting or duplicating a project. 2020-05-27 12:14:47 +09:30
grossmj
ddeb95cb0a Fix Multi-device selection/deselection not working as expected with right click. Fixes #2986 2020-05-26 16:11:53 +09:30
grossmj
5f7ff0d70d Generate MainWindow Ui file. 2020-05-26 13:01:04 +09:30
Jeremy Grossmann
a00e039cec Merge pull request #2875 from fatoms/master
Proposed fix for "Edit readme" is missing in GNS3 GUI. #2854
2020-05-26 11:30:19 +08:00
Dominic
a24e9adef1 Merge branch 'Edit_Readme' 2020-05-21 20:03:03 +02:00
Dominic Harford
ee5f8e8edd Resolve conflict with GNS3 repo 2020-05-21 19:08:29 +02:00
grossmj
f5470130f5 Fix issues with crash reporting & bump version to 2.2.9dev2. Ref https://github.com/GNS3/gns3-server/issues/1758 2020-05-21 18:19:19 +09:30
grossmj
1ff405885e Merge branch 'master' into 2.2 2020-05-20 17:25:37 +09:30
Dominic
9fb42ead9f Revert "Updated GUI pyqt files from Tab Order 'fixes' in "Tab Order in Preferences and Project Dialog #2872""
This reverts commit 0d2f91709c.
2020-05-19 18:31:02 +02:00
grossmj
2ea1946c0f Replace Raven by Sentry SDK. Fixes https://github.com/GNS3/gns3-server/issues/1758 2020-05-19 15:48:53 +09:30
grossmj
963e054918 Fix online help menu URL. Fixes #2984 2020-05-08 12:42:06 +09:30
grossmj
0f5f6ab645 Require setuptools>=17.1 in setup.py. Ref https://github.com/GNS3/gns3-server/issues/1751
This is to support environmental markers.
https://github.com/pypa/setuptools/blob/master/CHANGES.rst#171
2020-05-08 12:34:58 +09:30
grossmj
8a905b5c39 Development on 2.2.9dev1 2020-05-07 23:10:20 +09:30
grossmj
e917193f06 Merge branch '2.2' 2020-05-07 23:09:04 +09:30
grossmj
16846ce49c Release v2.2.8 2020-05-07 18:10:57 +09:30
grossmj
624a670ae7 Make sure "port" is defined. 2020-05-07 17:51:32 +09:30
grossmj
406326ccd8 Merge remote-tracking branch 'origin/master' 2020-05-06 11:57:25 +09:30
grossmj
24bc15fb73 Default port set to 80 for server running in the GNS3 VM. Fixes #1737 2020-05-05 12:40:50 +09:30
grossmj
348d8b9438 Make the Web UI the default page. Ref https://github.com/GNS3/gns3-server/issues/1737 2020-04-30 17:27:06 +09:30
grossmj
6787982408 Fix "export portable project forgets contents of README". Fixes #1724 2020-04-30 16:43:00 +09:30
Jeremy Grossmann
c2384917fa Update README. Ref https://github.com/GNS3/gns3-server/issues/1719 2020-04-29 15:01:45 +09:30
grossmj
b80178d0cf Activate unified title and toolbar on MacOS. Fixes #2968 2020-04-29 13:08:51 +09:30
grossmj
e6084ed834 Confirmation dialog for "console connect to all nodes". Fixes #2971 2020-04-28 15:04:39 +09:30
grossmj
ba924cd0d9 Add "Resume all suspended links". Fixes #2858 2020-04-28 14:00:26 +09:30
grossmj
0c3d43346f Revert "Change default path for SecureCRT. Fixes #2896"
This reverts commit 0c4367d77e.
2020-04-28 13:19:38 +09:30
grossmj
fcf6ef3027 Remove @property from ConfigurationDialog(). Fixes #2819 #2965 2020-04-28 11:57:54 +09:30
grossmj
e0f87e573d Use Environmental Markers to force jsonschema version. Fixes https://github.com/GNS3/gns3-gui/issues/2849
Version 3.2.0 with Python >= 3.8
Version 2.6.0 with Python < 3.8
2020-04-27 12:54:17 +09:30
grossmj
3ec068f0cb Use Environmental Markers to force jsonschema version 2.6.0 on Windows/macOS. Ref https://github.com/GNS3/gns3-gui/issues/2849 2020-04-27 12:43:07 +09:30
grossmj
37f1fcf6f7 Remove preferences dialog geometry restoration. Fixes #2807 2020-04-27 11:55:01 +09:30
grossmj
c51dd1605d Merge branch '2.3'
# Conflicts:
#	gns3/version.py
2020-04-13 11:57:04 +09:30
grossmj
4ebf3b4e1c Development on 2.2.8dev1 2020-04-08 01:26:42 +09:30
grossmj
b1ec9d535c Release v2.2.7 2020-04-08 00:03:13 +09:30
grossmj
7fc9087cf0 Fix unable to configure custom adapters for Qemu VMs. Fixes #2961 2020-04-07 15:47:53 +09:30
Bernhard Ehlers
5dc2c77806 QEMU config disk - enable QEMU config import/export
(cherry picked from commit d01f15c4df)
2020-04-06 13:42:00 +02:00
grossmj
4972d460d2 Fix tests. 2020-04-06 21:09:47 +09:30
grossmj
c388836be7 Fix VNC console template doesn't extract %i (Project UUID). Fixes #2960 2020-04-06 18:34:37 +09:30
grossmj
18ae4a6ce9 Fix contextual menu issues. Ref #2955 2020-03-30 21:37:52 -07:00
grossmj
3020e1fc9f Downgrade to PyQt 5.12.3 Ref #2955 #2952 2020-03-29 18:18:53 +10:30
grossmj
fe2f8424db Downgrade to PyQt 5.13.2 Ref #2955 #2952 2020-03-29 14:46:34 +10:30
grossmj
a744f65199 Merge branch 'master' into 2.3
# Conflicts:
#	gns3/version.py
2020-03-28 13:44:08 +10:30
grossmj
d27578f0fc Release v2.2.6 2020-03-26 12:37:59 +10:30
grossmj
b01c11f19b Prevent locked drawings to be deleted. Fixes https://github.com/GNS3/gns3-gui/issues/2948 2020-03-16 16:30:09 +10:30
grossmj
fb269da4d3 Fix issues with empty project variables. Fixes https://github.com/GNS3/gns3-gui/issues/2941 2020-03-14 17:22:44 +10:30
grossmj
ab15f96bb5 Upgrade psutil to version 5.6.6 due to CVE-2019-18874
https://github.com/advisories/GHSA-qfc5-mcwq-26q8
2020-03-14 15:47:12 +10:30
grossmj
5bb8b8e8bd Use existing README.txt if existing when exporting portable project. Fixes https://github.com/GNS3/gns3-server/issues/1724 2020-03-10 17:32:13 +10:30
grossmj
3f9632fae0 Allow creation of a diskless Qemu VMs. Fixes #2939 2020-03-10 17:04:07 +10:30
grossmj
b5f8195abb Re-enable "create new version" in appliance wizard. Fixes #2837 2020-03-03 13:11:01 +08:00
grossmj
73a293bd17 Fix unable to load project from project library. Fixes #2932 2020-03-03 09:34:45 +08:00
grossmj
0a1dfb99e9 Merge remote-tracking branch 'origin/2.2' into 2.2 2020-02-19 14:13:16 +08:00
grossmj
d352919264 Fix some permission denied errors when loading remote project. Ref #2871 Fixes #2901 2020-02-19 14:13:03 +08:00
Jeremy Grossmann
65f2a1e461 Merge pull request #2931 from inthought/2.2
Add 'Royal TS V5' to predefined console list
2020-02-18 13:29:46 +10:30
Travis Abram
71f289721b Add 'Royal TS V5' to predefined console list 2020-02-16 20:46:40 -08:00
grossmj
c28089d400 Disallow invalid grid sized. Fixes #2908 2020-02-10 16:59:17 +08:00
grossmj
64f009bf71 Check if hostname is blank. Fixes #2924 2020-01-25 18:21:02 +08:00
Jeremy Grossmann
edb2fd7fd9 Merge pull request #2925 from GNS3/qemu-changes
GUI changes to support recent versions of Qemu
2020-01-25 16:04:54 +07:00
grossmj
62e7ad8c8a Add nvme disk interface and fix scsi disk interface for Qemu VMs. 2020-01-25 16:22:34 +08:00
grossmj
caeb5d71c3 Add latest Qemu nic models. 2020-01-24 19:05:46 +08:00
grossmj
cfe96b2311 Upgrade Qt version to 5.14.1. Ref #2778 #2903 2020-01-24 17:47:01 +08:00
grossmj
8955b9ee29 Upgrade to PyQt 5.14.1. Ref #2778 2020-01-22 17:53:04 +08:00
grossmj
e727abf27a Change version to 2.3.0dev1 on 2.3 branch 2020-01-16 18:06:51 +08:00
grossmj
f209bf7644 Development on 2.2.6dev1 2020-01-10 00:32:10 +08:00
grossmj
5860dedc32 Release v2.2.5 2020-01-09 23:52:40 +08:00
grossmj
9e2df17a4e Add gns3-gui.xml and update Linux icons paths & permissions. Ref #2919 2020-01-09 23:49:44 +08:00
grossmj
a95761437a Development on 2.2.5dev1 2020-01-09 05:17:01 +08:00
grossmj
626510865f Update paths to icons for Linux 2020-01-09 04:19:21 +08:00
grossmj
2e248aa340 Release v2.2.4 2020-01-09 00:45:09 +08:00
grossmj
e306f73f01 Fix "Console to all nodes" doesn't open cloud objects with console configured. Fixes #2902 2020-01-08 00:35:57 +08:00
grossmj
0c4367d77e Change default path for SecureCRT. Fixes #2896 2019-12-26 06:13:23 +08:00
grossmj
6dda0ff787 Add icons in setup.py Ref #2898 2019-12-26 06:04:40 +08:00
grossmj
d7d4b84309 Add remote viewer as a VNC console for Linux. Fixes #2913 2019-12-26 04:13:02 +08:00
grossmj
7b57983699 Development on 2.2.4dev1 2019-11-12 16:43:21 +08:00
grossmj
bb89fe2275 Release v2.2.3 2019-11-12 15:29:54 +08:00
grossmj
7eb2a923b2 Fix issue when binding on 0.0.0.0. Fixes #2892 2019-11-11 17:49:09 +08:00
grossmj
58052e3cce Allow double click on cloud with configured console to open session. Fixes #2894 2019-11-11 14:31:35 +08:00
grossmj
e431104f6b Officially support Python 3.8. Ref https://github.com/GNS3/gns3-gui/issues/2895 2019-11-11 12:56:11 +08:00
grossmj
a4e9d6b8ce Set psutil to version 5.6.3 in requirements.txt 2019-11-08 10:44:17 +08:00
grossmj
f58f5c7b95 Developement version on 2.2.3dev1 2019-11-04 19:45:18 +08:00
grossmj
37faa39309 Release v2.2.2 2019-11-04 18:33:29 +08:00
grossmj
4d64598ed2 Fix KeyError: 'spice+agent'. Fixes #2890 2019-11-03 15:19:18 +08:00
grossmj
5132c4e172 Fix wrong log.error() call when exporting file. 2019-11-02 23:19:04 +08:00
grossmj
3c3fdd9ffd Revert "Explicitly cleanup the cache directory."
This reverts commit 8095fef228.
2019-11-02 15:47:24 +08:00
grossmj
efa50571c6 Fix "UnboundLocalError: local variable 'pywintypes' referenced before assignment" 2019-11-02 15:45:16 +08:00
grossmj
ca9b10fcca Fix version string. 2019-11-02 15:27:28 +08:00
grossmj
8660161b10 Fix GUI uses only telnet console. Fixes #2885 2019-11-02 02:23:13 +08:00
grossmj
50ebfb9c06 Fix missing sys module in sudo.py Fixes #2886 2019-11-02 02:06:33 +08:00
grossmj
26df59d6b6 Development on 2.2.2dev1 2019-11-01 18:43:36 +08:00
grossmj
b903e2ad73 Release v2.2.1 2019-11-01 17:53:20 +08:00
grossmj
b2fe7eb643 Check if console_type is None. 2019-11-01 17:47:26 +08:00
grossmj
8095fef228 Explicitly cleanup the cache directory. 2019-11-01 17:46:53 +08:00
grossmj
b8da5440f5 Get Windows interface from registry if cannot load win32com module. 2019-11-01 17:44:47 +08:00
grossmj
6cea094e4e Ignore OSError returned by psutil when bringing console to front. 2019-11-01 17:44:06 +08:00
grossmj
9ac46c9d50 Catch error if NPF or NPCAP service cannot be detected. Ref https://github.com/GNS3/gns3-server/issues/1670 2019-10-30 17:59:19 +08:00
Dominic
c8a8663ff0 Restore editReadme attribute which was removed in Change 'New export project wizard' ( ID c2472bcb22 ) 2019-10-18 17:11:28 +02:00
Dominic
d27e5c1795 Revert "Remove unused edit readme action. Fixes #2816"
This reverts commit 7cd0187f33.
2019-10-18 16:46:48 +02:00
Dominic
0d2f91709c Updated GUI pyqt files from Tab Order 'fixes' in "Tab Order in Preferences and Project Dialog #2872" 2019-10-18 16:45:39 +02:00
grossmj
6dc44d5108 Better handling for reading synchronous JSON response from server. Ref #2874 2019-10-17 12:45:40 +08:00
grossmj
9c6be0341b Fix tests. 2019-10-17 12:34:34 +08:00
grossmj
011a49e998 Fix JSONDecodeError when getting server version. Fixes #2874 2019-10-17 12:31:15 +08:00
grossmj
e18c2df5f5 Fix FileNotFoundError exceptions when launching SPICE or VNC clients. 2019-10-17 12:19:55 +08:00
grossmj
1794b8389f Fix UnboundLocalError local variable 'win32serviceutil' referenced before assignment 2019-10-17 12:12:46 +08:00
grossmj
0379c370eb Merge remote-tracking branch 'remotes/origin/master' into 2.2 2019-10-15 23:36:49 +08:00
Jeremy Grossmann
e03550a89b Merge pull request #2872 from fatoms/Tab_Order
Tab Order in Preferences and Project Dialog
2019-10-15 22:35:53 +07:00
Dominic
c6ea775e81 'Fix' tab order in preferences dialog so it follows the layout 2019-10-12 22:06:30 +02:00
Dominic
38233ba5e9 'Fix' tab order in edit project dialog so it follows the layout 2019-10-12 22:05:52 +02:00
grossmj
89c1272bc1 Use compatible shlex_quote to handle case where Windows needs double quotes around file names, not single quotes. Ref https://github.com/GNS3/gns3-gui/issues/2866 2019-10-09 17:02:30 +08:00
grossmj
8bbb46c599 Use 0.0.0.0 by default for server host. Fixes https://github.com/GNS3/gns3-server/issues/1663 2019-10-09 16:35:42 +08:00
grossmj
74fca3d736 Set default host to "localhost". Fixes https://github.com/GNS3/gns3-server/issues/1663 2019-10-08 18:28:11 +08:00
grossmj
7aeed7aa59 Catch IndexError when configuring port names. Fixes #2865 2019-10-08 16:15:36 +08:00
grossmj
aa15ace887 Bump version to 2.2.1dev1 2019-10-08 16:05:37 +08:00
grossmj
2d0a7b5f58 Release v2.2.0 2019-09-30 16:24:26 +08:00
grossmj
20a09b56c1 Merge branch 'master' into 2.2
# Conflicts:
#	gns3/version.py
2019-09-24 14:07:38 +08:00
grossmj
1938cdabae Bump version to 2.2.0dev18 2019-09-24 14:02:36 +08:00
grossmj
8d1bff782c Release v2.2.0rc5 2019-09-09 15:06:14 +07:00
grossmj
4e3eee2383 Adjust size for setup dialog and remove question about running the wizard again. Ref #2846 2019-09-07 23:46:02 +07:00
grossmj
da8aa0d2fd Release v2.2.0rc4 2019-08-30 15:23:32 +07:00
grossmj
5b4481c43a Fix issue when asking to run the setup wizard again. Ref #2846 2019-08-29 15:59:40 +07:00
grossmj
593cb8c1fd Remove warning about VirtualBox not supporting nested virtualization. Ref https://github.com/GNS3/gns3-server/issues/1610 2019-08-27 17:28:44 +07:00
grossmj
210cf63fe2 Ask user if they want to see the wizard again. Ref #2846 2019-08-27 16:15:50 +07:00
grossmj
3b178013c0 Bump version to 2.2.0dev17 2019-08-23 18:20:44 +07:00
grossmj
6e44d6b919 Merge branch 'master' into 2.2
# Conflicts:
#	gns3/version.py
2019-08-20 17:35:13 +07:00
grossmj
6b520b8036 Bump version to 2.2.0dev16 2019-08-20 17:33:48 +07:00
grossmj
803782b9d8 Release v2.2.0rc3 2019-08-11 19:14:56 -07:00
grossmj
d3d6ca3f2e Revert to jsonschema 2.6.0 due to packaging problem. 2019-08-11 19:11:41 -07:00
grossmj
f545c793f8 Release v2.2.0rc2 2019-08-10 12:04:19 -05:00
grossmj
47d6a4fef6 Release v2.2.0rc1 2019-08-10 11:42:13 -05:00
grossmj
8862b608cf Bump jsonschema to version 3.0.2 2019-08-09 13:47:58 -05:00
grossmj
76832ab83f Fix "Unable to change Remote Main Server IP". Fixes #2823 2019-08-02 17:23:31 -02:30
grossmj
fed245fd34 Fix "AttributeError: 'QGraphicsTextItem' object has no attribute 'locked'". Fixes #2814 2019-07-30 15:50:40 -02:30
grossmj
3e0f1affd0 Merge branch '2.2' 2019-07-12 12:07:33 +02:00
grossmj
2110c2805e Development on 2.2.0dev15 2019-07-11 17:34:56 +02:00
grossmj
46cfdd8314 Release v2.2.0b4 2019-07-11 16:58:35 +02:00
grossmj
f8f648c2b6 Fix issue preventing to open the QFileDialog in the correct directory. 2019-07-11 16:50:59 +02:00
grossmj
7cd0187f33 Remove unused edit readme action. Fixes #2816 2019-07-10 12:10:17 +02:00
grossmj
4d8f362f11 Remove deprecated Qemu parameter to run legacy ASA VMs. Fixes #2827 2019-07-10 11:33:04 +02:00
grossmj
469eaa4737 Upload images on remote controller. Fixes #2828 2019-07-10 11:23:29 +02:00
grossmj
c921224b30 Preferences dialog: send API request only if connected to controller 2019-07-05 18:07:48 +02:00
grossmj
61487b2e2f Fix AttributeError: 'QGraphicsTextItem' object has no attribute 'locked'. Fixes #2814 2019-06-24 15:29:07 +02:00
grossmj
9affca495e Fix KeyError: 'chassis' when converting old IOS templates. Fixes #2813 2019-06-24 15:14:20 +02:00
grossmj
9d8886a640 Development on 2.2.0dev14 2019-06-15 16:38:06 +02:00
grossmj
98cfec1b77 Release v2.2.0b3 2019-06-15 15:39:32 +02:00
grossmj
aed174953e Merge 2.1 into 2.2 branch. 2019-06-15 15:26:20 +02:00
grossmj
f0feea8262 Fix template migration issues from GUI to controller. Fixes https://github.com/GNS3/gns3-gui/issues/2803 2019-06-15 12:52:50 +02:00
grossmj
e2aeaf0a78 Development on 2.1.22dev1 2019-06-14 19:42:18 +02:00
grossmj
b92bb94875 Release v2.1.21 2019-06-14 10:47:08 +02:00
grossmj
c56db59353 Increase timeout from 2 to 5 seconds for synchronous check. Ref #2805 2019-06-10 22:20:40 +02:00
grossmj
a87c4e21d7 %guest-cid% variable implementation for Qemu VMs. Fixes https://github.com/GNS3/gns3-gui/issues/2804 2019-06-04 18:00:44 +02:00
grossmj
ed99a989d7 Fix "General Preferences dialog displays misleading information". Fixes #2801 2019-06-04 17:05:01 +02:00
grossmj
f9a4c9399a Increase timeout from 2 to 5 seconds for synchronous check. Ref #2805 2019-05-31 09:17:34 +02:00
grossmj
efb5c8ca9a Development on 2.2.0dev13 2019-05-29 17:52:38 +07:00
grossmj
0946dff3a0 Release v2.2.0b2 2019-05-29 17:16:59 +07:00
grossmj
d7d96b10e5 Merging 2.1 into 2.2 branch 2019-05-29 16:50:36 +07:00
grossmj
0c0b2d5cb3 Development on 2.1.21dev1 2019-05-29 16:37:43 +07:00
grossmj
450fbc9af3 Release v2.1.20 2019-05-29 15:44:25 +07:00
grossmj
469ee8fab8 Fix KeyError: 'endpoint' issue. Fixes #2802 2019-05-28 23:17:55 +07:00
grossmj
6ccfcaf76e Development on 2.1.20dev1 2019-05-28 16:33:43 +07:00
grossmj
520e857874 Release v2.1.19 2019-05-28 15:23:35 +07:00
grossmj
012c7b4241 Fix wrong aligment of symbols in saved/exported projects. Fixes #2800 2019-05-27 16:33:51 +07:00
grossmj
1d71cd5bf0 Replace urllib.request by Qt implementation for local server synchronous check. Fixes #2793 2019-05-27 16:03:55 +07:00
grossmj
17d1a7f4ed Support snapshots for portable projects. Fixes https://github.com/GNS3/gns3-gui/issues/2792 2019-05-27 15:35:47 +07:00
grossmj
0cd5c08c6b Fix event notification problem for projects and how snapshots are restored. 2019-05-27 15:24:36 +07:00
grossmj
20ac503fe9 Do not close the nodes dock widget when creating project. 2019-05-26 15:20:53 +07:00
grossmj
5f737c2c7c Fix no scan for images on remote controller. Fixes #2799 2019-05-26 15:12:22 +07:00
grossmj
eb1a37be36 Remove problematic tests. 2019-05-25 17:49:33 +07:00
grossmj
07c64b5432 Use QNetworkAccessManager to download custom appliance symbols. 2019-05-25 16:25:02 +07:00
grossmj
ce981d1c49 Experimental auto upgrade should not be available for "frozen" app. Fixes #2797 2019-05-25 15:17:23 +07:00
grossmj
32a9f2556e Use QNetworkAccessManager for synchronous local server check. Ref #2793
Remove unused code.
2019-05-25 15:04:37 +07:00
grossmj
7f08675121 Merging 2.2 into master 2019-05-24 15:27:07 +07:00
grossmj
1dc3c13df2 Don't allow link labels to be moved for locked nodes. Fixes #2794 2019-05-23 15:10:40 +07:00
grossmj
6a6e86b325 Merge 2.1 into 2.2 branch 2019-05-23 14:51:53 +07:00
grossmj
d96277882a Set grid's minimum to 5. Fixes #2795 2019-05-23 14:41:53 +07:00
grossmj
ecec917752 Development on 2.1.19dev1 2019-05-23 14:37:37 +07:00
grossmj
ea9c1a8ee1 Release v2.1.18 2019-05-22 16:13:28 +07:00
grossmj
cfbb09fb57 Fix error in HTTPConnection.request for Python3.6. Fixes #2793 2019-05-22 16:05:34 +07:00
grossmj
dc8aa1fb92 Catch more OSError/PermissionError when checking md5 on remote images. Fixes #2582 2019-05-22 15:23:56 +07:00
grossmj
786cc8aa65 Fix exception when grid size is 0. Fixes #2790 2019-05-22 15:13:16 +07:00
grossmj
4a353e08e3 Catch PermissionError when scanning local image directories. Fixes #2791 2019-05-22 14:55:07 +07:00
grossmj
1371921586 Development on 2.2.0dev12 2019-05-21 19:16:19 +07:00
grossmj
cd8696a714 Release v2.2.0b1 2019-05-21 15:26:54 +07:00
grossmj
17799719d6 Merge branch '2.1' into 2.2 2019-05-21 15:16:19 +07:00
grossmj
2a59013604 Revert "Make sure the latest PyQt5 version 5.12.x is used on Windows." Ref #2778 2019-05-20 12:01:16 +07:00
grossmj
1c46299dd9 Change behavior when an IOU license is verified. Fixes https://github.com/GNS3/gns3-server/issues/1555 2019-05-20 10:51:25 +07:00
grossmj
628d7cb909 Fix cannot load new profile. Fixes #2784 2019-05-19 16:33:33 +07:00
grossmj
b23c92c0fb Fix Docker extra volumes support 2019-05-19 14:26:03 +07:00
Jeremy Grossmann
49ce5a9f38 Merge pull request #2775 from kazkansouh/2.2-docker-volumes
Custom persistent docker volumes
2019-05-18 20:17:20 +07:00
Jeremy Grossmann
4575ea9f6d Merge pull request #2789 from GNS3/update-dockerfile
Update Dockerfile to Ubuntu 18.04
2019-05-18 20:10:31 +07:00
grossmj
fd6a00df6a Update Dockerfile to Ubuntu 18.04 2019-05-18 20:03:04 +07:00
grossmj
58ab4b424a Fix remote packet capture when controller is also remote. Fixes #2785 2019-05-18 17:33:34 +07:00
grossmj
1ea1abf582 Set console type to "none" by default for Ethernet switches and add a warning if trying to use "telnet". Fixes https://github.com/GNS3/gns3-gui/issues/2776 2019-05-18 14:28:20 +07:00
grossmj
e8caab74f4 Bump version to 2.2.0dev11 2019-05-18 14:11:07 +07:00
grossmj
9fce393fd1 Add tooltip for symbol theme support in general preferences. Fixes #2770 2019-05-18 14:01:08 +07:00
grossmj
827c11ae97 Merge remote-tracking branch 'origin/2.2' into 2.2 2019-05-18 13:45:57 +07:00
grossmj
eb370d5672 Merge 2.1 branch into 2.2 2019-05-18 13:45:39 +07:00
grossmj
7732aaf9a5 Release v2.1.17 2019-05-17 15:10:28 +07:00
Karim Kanso
63161eb760 Minor ui tweak to align extra hosts text edit look and feel with extra volume text edit. 2019-04-22 13:03:23 +01:00
Karim Kanso
5dba814d1b Support for persistent docker volumes to be configured via ui (requires corresponding commit on gns3-server) 2019-04-22 11:09:28 +01:00
Jeremy Grossmann
aecdc71f3a Merge pull request #2772 from GNS3/pyup-update-pytest-4.4.0-to-4.4.1
Update pytest to 4.4.1
2019-04-16 20:04:42 +07:00
pyup-bot
3209c1d0e6 Update pytest from 4.4.0 to 4.4.1 2019-04-16 03:33:58 +02:00
grossmj
2b3fb53ef2 Release v2.2.0a5 2019-04-15 17:05:20 +07:00
grossmj
cbbbece0e5 Revert "Drop old Qemu support (Windows and macOS) and legacy ASA support." Ref https://github.com/GNS3/gns3-server/issues/1579
This reverts commit 3e47267e35.
2019-04-15 15:56:01 +07:00
grossmj
56d742b19f Merge 2.1 into 2.2 2019-04-15 15:54:08 +07:00
grossmj
1f566a31cf Development on 2.1.17dev1 2019-04-15 12:41:41 +07:00
grossmj
10d75e15da Release v2.1.16 2019-04-15 12:00:18 +07:00
grossmj
17def7e00a Do not make NPF or NPCAP service mandatory to start the local server on Windows. 2019-04-15 10:33:27 +07:00
grossmj
106afd0987 Do not try to upload a local image that is already installed on the local server. 2019-04-15 00:29:43 +07:00
grossmj
bba9c5e1d8 Back to the major.minor version for config files. Ref https://github.com/GNS3/gns3-gui/issues/2756 2019-04-14 21:31:41 +07:00
grossmj
ae8e8013d4 Some adjustments with compute WebSocket handling. Ref https://github.com/GNS3/gns3-server/issues/1564 2019-04-14 16:48:12 +07:00
grossmj
3a5f1d60f9 Fix AttributeError: 'GraphicsView' object has no attribute '_import_config_dir'. Fixes #2768 2019-04-13 18:45:16 +07:00
grossmj
3f6eb61382 Do not try to lock a SvgIconItem. Fixes #2766 2019-04-13 18:40:54 +07:00
grossmj
32bfff381d Merge 2.1 into 2.2 2019-04-13 18:11:41 +07:00
grossmj
f68a8ea829 Fix OverflowError error with progress dialog. Fixes #2767 2019-04-13 17:38:43 +07:00
grossmj
50066b2f12 More fixes for stuck progress window. Fixes #2765 2019-04-13 17:10:24 +07:00
grossmj
21a99d4376 Fix adding multiple devices - stuck progress window. Fixes #2765 2019-04-13 17:04:23 +07:00
grossmj
f97d3041b8 Make sure the latest PyQt5 version 5.12.x is used on Windows. 2019-04-13 15:43:23 +07:00
grossmj
31d6a065b0 Show a warning when a config export is not supported. Ref #2762 2019-04-11 15:32:22 +07:00
grossmj
20bf63dbbf Prevent locked nodes to be deleted. Fixes https://github.com/GNS3/gns3-gui/issues/2764 2019-04-10 15:43:52 +07:00
grossmj
1c3e0ef640 Fix default telnet console command. 2019-04-09 21:18:55 +07:00
grossmj
5b58d3ab6d Bump version to 2.2.0dev10 2019-04-09 19:20:21 +07:00
grossmj
554c9205f3 Add PuTTY 0.71 and mark GNS3 PuTTY as deprecated. Fixes #2758 2019-04-09 19:19:56 +07:00
grossmj
543a8e7c33 Fix bug with IOS platform detection. Fixes #2760 2019-04-09 16:24:07 +07:00
grossmj
69ef35c674 Development on 2.2.0dev9 2019-04-05 22:01:35 +08:00
grossmj
45102a07b6 Release v2.2.0a4 2019-04-05 19:10:04 +08:00
grossmj
f0b8b22e8a Use the full version number for path to config files. Ref https://github.com/GNS3/gns3-gui/issues/2756 2019-04-05 18:44:31 +08:00
grossmj
d94f5a2d8c Fix error message when shutting down GUI without a started server. 2019-04-01 21:08:17 +07:00
grossmj
a768661c05 Fix remote packet capture and make sure packet capture is stopped when deleting an NIO. Fixes https://github.com/GNS3/gns3-gui/issues/2753 2019-04-01 19:47:32 +07:00
grossmj
4657b005b6 Restore migrate old settings. 2019-04-01 16:20:26 +07:00
grossmj
e71da830b0 Merge remote-tracking branch 'origin/2.2' into 2.2 2019-04-01 15:53:59 +07:00
grossmj
ebf2563200 Store config files in version specific location 2019-04-01 15:53:39 +07:00
Jeremy Grossmann
e8eaa00244 Merge pull request #2755 from GNS3/pyup-update-pytest-4.3.1-to-4.4.0
Update pytest to 4.4.0
2019-04-01 12:00:43 +07:00
pyup-bot
d750e7a427 Update pytest from 4.3.1 to 4.4.0 2019-04-01 06:53:29 +02:00
grossmj
bfc8adc904 Fix error messages on closing GNS3 application. Fixes https://github.com/GNS3/gns3-gui/issues/2750 2019-03-30 17:20:15 +07:00
grossmj
4de38ea590 Fix bug when list of files for an appliance is not displayed. 2019-03-30 15:44:30 +07:00
ziajka
cc0c6d0a7a Update 'local' to 'bundled' in server & gui, Fixes: #1561 2019-03-27 11:56:32 +01:00
grossmj
d1d0810233 Development on 2.2.0dev8 2019-03-25 23:44:19 +08:00
grossmj
ee3c758bb7 Release v2.2.0a3 2019-03-25 19:35:22 +08:00
grossmj
8f077456b1 Development on 2.1.16dev1 2019-03-21 13:56:11 +08:00
grossmj
a29f3e35c0 Release v2.1.15 2019-03-21 11:41:44 +08:00
grossmj
b12cb5c939 Fix bug when changing symbol. Fixes #2740 2019-03-20 15:15:29 +08:00
grossmj
ba646f5efa Fix image upload tests, second try. 2019-03-18 17:34:26 +07:00
grossmj
edafc29cdc Fix image upload tests. 2019-03-18 17:19:23 +07:00
grossmj
5aa67d18c0 Fix issue when images are not uploaded from appliance wizard. Ref https://github.com/GNS3/gns3-gui/issues/2738 2019-03-18 15:33:37 +07:00
grossmj
8067aaadd4 Development on 2.2.0dev7 2019-03-14 23:27:11 +07:00
Jeremy Grossmann
a9a2a541c0 Update gns3-feature-request.md 2019-02-27 15:46:57 +07:00
Jeremy Grossmann
8998c07e0e Update gns3-bug-report.md 2019-02-27 15:46:28 +07:00
Jeremy Grossmann
ba01a89af1 Update gns3-feature-request.md 2019-02-27 15:43:07 +07:00
Jeremy Grossmann
eae07d62ad Update gns3-bug-report.md 2019-02-27 15:42:21 +07:00
Jeremy Grossmann
23903cf0c9 Delete feature_request.md 2019-02-27 15:36:33 +07:00
Jeremy Grossmann
4d908fd855 Delete bug_report.md 2019-02-27 15:36:18 +07:00
Jeremy Grossmann
bb0e67be4f Update issue templates 2019-02-27 15:32:18 +07:00
Jeremy Grossmann
0410c446fc Merge pull request #2466 from dhalperi/fix-typo
Fix a minor typo in the setup wizard
2018-04-05 13:19:38 +07:00
Daniel Halperin
18486e4772 Fix a minor typo in the setup wizard
Eveything -> Everything
2018-04-04 22:53:09 -07:00
215 changed files with 123154 additions and 123912 deletions

View File

@@ -0,0 +1,34 @@
---
name: GNS3 bug report
about: Create a report to help us fix a bug
title: 'Short description of the bug'
labels: Bug
assignees: ''
---
**Before you start**
Please open an issue only if you suspect there is a bug or any problem with GNS3. Go to https://gns3.com/community for any other questions or for requesting help with GNS3.
You may also post this issue directly on the GNS3 server repository if you know the bug comes from the server: https://github.com/GNS3/gns3-server/issues/new
**Describe the bug**
Please provide a clear and detailed description of what the bug is.
**GNS3 version and operating system (please complete the following information):**
- OS: [e.g. Windows, Linux or macOS]
- GNS3 version [e.g. 2.1.14]
- Any use of the GNS3 VM or remote server (ESXi, bare metal etc.)
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Screenshots or videos**
If applicable, add screenshots (e.g. of the topology and/or error message) or links to videos to help explain the problem. This will help us a lot to quickly find the bug and fix it.
**Additional context**
Add any other context about the problem here.

View File

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

View File

@@ -0,0 +1,25 @@
---
name: GNS3 feature request
about: Suggest an idea for GNS3
title: 'Short description of the feature request'
labels: Enhancement
assignees: ''
---
**Before you start**
Please check if a similar feature request has already been submitted.
You may also post this issue directly on the GNS3 server repository if you know the feature request only applies to the server: https://github.com/GNS3/gns3-server/issues/new
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen. If applicable, please provide screenshots
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,16 @@
name: Add new issues to GNS3 project
on:
issues:
types:
- opened
jobs:
add-to-project:
name: Add issue to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@v0.4.0
with:
project-url: https://github.com/orgs/GNS3/projects/3
github-token: ${{ secrets.ADD_NEW_ISSUES_TO_PROJECT }}

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

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

19
.github/workflows/testing.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: testing
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build and run Docker image
run: |
docker build -t gns3-gui-test .
docker run gns3-gui-test

1
.gitignore vendored
View File

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

View File

@@ -1,2 +0,0 @@
branch:
2.2

View File

@@ -1,21 +0,0 @@
sudo: required
services:
- docker
notifications:
email: false
script:
- docker build -t gns3-gui-test .
- docker run gns3-gui-test
before_deploy:
- sudo pip install twine
- sudo pip install urllib3[secure]
deploy:
provider: pypi
edge:
branch: v1.8.45
user: noplay
password:
secure: FofcqlJjgqf2jaDaXpLHeigVoexbrOz3WwnDuiJpwJxeFUlPY8s2cQs/Bm+dzxzZaOaGiVE0A83v/Xa10yD5tflThHt4sqYJK3iQCinA7wgeAlDimB4xrWUNplfNJZ/Eod5Ssa++E02W+3i29PxpXY//mjCY7qDxaoxul1gnFJY=
on:
tags: true
repo: GNS3/gns3-gui

14
.whitesource Normal file
View File

@@ -0,0 +1,14 @@
{
"scanSettings": {
"configMode": "AUTO",
"configExternalURL": "",
"projectToken" : "",
"baseBranches": ["master", "2.2", "3.0"]
},
"checkRunSettings": {
"vulnerableCheckRunConclusionLevel": "failure"
},
"issueSettings": {
"minSeverityLevel": "LOW"
}
}

524
CHANGELOG
View File

@@ -1,5 +1,529 @@
# 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
* Automatically add new issues to GNS3 project
* Development 2.2.38.dev1
## 2.2.37 25/01/2023
* Upgrade to PyQt5 v5.15.7
* Changed Windows Terminal telnet console profile from OS X to windows ref: issue #3193
## 2.2.36 04/01/2023
* Add Trusted Platform Module (TPM) support for Qemu VMs
* Add "on_close" setting to appliance schema. Fixes https://github.com/GNS3/gns3-server/issues/2148
* Add default 'ide' disk interface when manually creating Qemu VM template. Fixes #3360
* Fix zoom factor is multiplied when loading projects. Fixes #3408
* Remove deprecated PuTTY option in preferences. Ref https://github.com/GNS3/gns3-gui/discussions/3415
## 2.2.35.1 10/11/2022
* Re-release Web-Ui v2.2.35
## 2.2.35 08/11/2022
* Fix "variables": [] in project file leads to unlimited increase of empty name/value pairs in GUI. Fixes #3397
* Make version PEP 440 compliant
* Support for Python 3.11
* Upgrade PyQt to 5.15.7 and pywin32 to v305
* Allow for more dependency versions at patch level
* Replace deprecated distro.linux_distribution() call
* Add a fix for the CVE-2007-4559
## 2.2.34 28/08/2022
* Upgrade dev dependencies
* Implement new option (Delete All) to contextual menu in "Console" dock. Fixes #3325
* Fix 2560x1440 resolution for Docker container
## 2.2.33.1 21/06/2022
* Match GNS3 server version
## 2.2.33 20/06/2022
* Upgrade sentry-sdk and psutil
* Check that node names for Qemu and Docker are valid
* Backport reset all console connections. Fixes #2072
* Add more video resolutions to Docker containers using VNC. Fixes #3329
* Add python_requires=">=3.4" in setup.py. Fixes #3326
* Only allow post release corrective versions of GUI and server to interact
* Allow minor versions of GUI and server to interact
* Update VirtViewer path. Fixes #3334
## 2.2.32 27/04/2022
* Use public DSNs for Sentry
* Fix exception when doubleclick on NAT node. Fixes #3312
* Fix "Apply" button in the "Preferences" dialog stays gray when templates/nodes are opened by double-click. Fixes #3307
* Add 'reset docks' in the view menu. Ref #3317
## 2.2.31 26/02/2022
* Install setuptools v59.6.0 when using Python 3.6
## 2.2.30 25/02/2022
* Set setuptools to v60.6.0
* Upgrade to pywin32 v303. Ref #3290
* Fix int() call. Ref #3283
* Fix QPoint() as unexpected type 'float'. Fixes #3283
* Fix painter.drawRect() has unexpected type 'float'. Fixes #3282
* Fix SpinBox.setValue() requires integer. Fixes #3281
## 2.2.29 08/01/2022
* Clear cache when opening symbol selection dialog. Fixes #3256
* Fix @ in username issue with HTTP authentication. Fixes #3275
* Use '//' operator instead of int()
* Fix create drawing item calls since mapToScene() returns a QPointF https://doc.qt.io/qt-5/qgraphicsview.html#mapToScene-4
* Fixed QPoint called with floats
## 2.2.28 15/12/2021
* Fixed drawLine called with float arguments
* Fixed dead VIX API link
## 2.2.27 12/11/2021
* Fix symbols in "Symbol selection" dialog are not placed in alphabetical order. Fixes #3245
* Fix links duplicates in topology summary. Fixes #3251
* chore : use --no-cache-dir flag to pip in dockerfiles to save space
## 2.2.26 08/10/2021
* Upgrade embedded Python to version 3.7 in Windows package
* Upgrade Visual C++ Redistributable for Visual Studio 2019 in Windows package
* Fix SSL support in Windows package
* Open "template configuration" dialog with double click on template name in "Preferences". Fixes #3239
* Only show "virtio" network adapter when legacy node is enabled. Fixes https://github.com/GNS3/gns3-gui/issues/1969
* Double-click on a template opens "template configuration" dialog. Fixes #3236
* Fix "Custom symbols" can't be unfolded after using "Filter" field. Fixes #3231
## 2.2.25 14/09/2021
* Fix menu disabled for modal dialogs on macOS. Fixes #3007
* Change method to display the recent files menu. Fixes #3007
* Fix bug when using empty port names for custom adapters. Fixes #3228
* Upgrade Qt to version 5.15.4 on macOS
* Fix mouse zoom-in/out step value is two times bigger than keyboard one. Fixes #3226
* Upgrade to Qt 5.15.4 on Windows. Ref #3210
* Fix issue with custom adapters at the node level. Fixes #3223
* Explicitly require setuptools, utils/get_resource.py imports pkg_resources
## 2.2.24 25/08/2021
* Fix incorrect Qemu binary selected when importing template. Fixes https://github.com/GNS3/gns3-gui/issues/3216
* Early support for Python3.10
* Bump pywin32 from 300 to 301
* Add PyQt5==5.12.3 for macOS build
## 2.2.23 05/08/2021
* Handle -no-kvm param deprecated in Qemu >= v5.2
* Support for invisible links. Fixes #2461
* Add kitty console application command line. Fixes #3203
* Add Windows Terminal profile as an option for Console Applications. Fixes #3193
## 2.2.22 10/06/2021
* Fix exception shown when GNS3 is started with empty config. Fixes #3188
* Add ZOC8 console terminal for macOS command line
* Link style support. Fixes https://github.com/GNS3/gns3-gui/issues/2461
* Fix charcoal theme. Ref #3137
* Fix issue when showing menu to select port. Fixes #3169
## 2.2.21 10/05/2021
* Fix issue with empty project variable name. Fixes #3162
* Downgrade to PyQt5 5.12.1. Fixes https://github.com/GNS3/gns3-gui/issues/3169
## 2.2.20 09/04/2021
* Fix project does not load anymore. Fixes #3140
* Do not connect to server while waiting for user to accept/reject SSL certificate. Fixes #3144
* Fix invalid server version check request. Fixes #3144
* Upgrade dependencies
* Add terminator as a predefined custom console option
## 2.2.19 05/03/2021
* No changes
## 2.2.18 16/02/2021
* SSL support.
* Remove the useless file "zoom-in (copy).svg". Fixes #3114
* Use HDD disk image as startup QEMU config disk
* Edit only text mode config files
* Hide config import/export when configFiles attribute is empty
* Qemu disk interfaces must be set to "none" by default. Ref #3035
* Do not allow image to be configured on Qemu VM secondary slave disk if create config disk option is enabled.
* Add explicit option to automatically create or not the config disk. Off by default.
* QEMU config disk support
## 2.2.17 04/12/2020
* Remove "-nographic" option by default for Qemu VM. Fixes #3094
* Fix app cannot start on macOS Big Sur. Ref #3037
* Require confirmation before stopping all devices.
## 2.2.16 05/11/2020
* Fix packets capture stops after some time. Fixes #3067
* Option to allocate or not the vCPUs and RAM settings for the GNS3 VM. Fixes https://github.com/GNS3/gns3-gui/issues/3069
## 2.2.15 07/10/2020
* Fix custom symbol not sent to remote controller when installing appliance
## 2.2.14 14/09/2020
* Improvements to add a new version of an appliance from wizard. Fixes #3002.
## 2.2.13 04/09/2020
* No changes
## 2.2.12 07/08/2020
* Downgrade psutil to version 5.6.7
* Fix log shows the GUI command line without spaces between its arguments. Fixes #3026
* Use server host is console host is equal to "0:0:0:0:0:0:0:0"
* Remove VMware promotion.
## 2.2.11 09/07/2020
* Try to fix "Recent project" selection not working. Ref #3007
* Fix debug entries shown twice in console window and double error messages with remote GNS3VM. Fixes #3010
* Fix deprecation warning. Ref #3009
* Fix tests on macOS. Ref #3009
* Fix sentry SDK is configured twice.
## 2.2.10 18/06/2020
* New fix for multi-device selection/deselection not working as expected with right click. Fixes #2986
* Optimize snap-to-grid code for drawing items. Fixes #2997
* Move jsonschema 2.6.0 requirement in build repository.
* Only use jsonschema 2.6.0 on Windows and macOS.
* Disable default integrations for sentry sdk.
## 2.2.9 04/06/2020
* Fix GUI doesn't detect another GUI on macOS. Fixes #2994
* Support to activate/deactive network connection state replication in Qemu.
* Option to reset or not all MAC addresses when exporting or duplicating a project.
* Fix Multi-device selection/deselection not working as expected with right click. Fixes #2986
* Replace Raven by Sentry SDK. Fixes https://github.com/GNS3/gns3-server/issues/1758
* Fix online help menu URL. Fixes #2984
* Require setuptools>=17.1 in setup.py. Ref https://github.com/GNS3/gns3-server/issues/1751 This is to support environmental markers. https://github.com/pypa/setuptools/blob/master/CHANGES.rst#171
* Update README. Ref https://github.com/GNS3/gns3-server/issues/1719
* Restore editReadme attribute which was removed in Change 'New export project wizard'
* Updated GUI pyqt files from Tab Order 'fixes' in "Tab Order in Preferences and Project Dialog #2872"
## 2.2.8 07/05/2020
* Default port set to 80 for server running in the GNS3 VM. Fixes #1737
* Make the Web UI the default page. Ref https://github.com/GNS3/gns3-server/issues/1737
* Fix "export portable project forgets contents of README". Fixes #1724
* Activate unified title and toolbar on MacOS. Fixes #2968
* Confirmation dialog for "console connect to all nodes". Fixes #2971
* Add "Resume all suspended links". Fixes #2858
* Revert "Change default path for SecureCRT. Fixes #2896"
* Remove @property from ConfigurationDialog(). Fixes #2819 #2965
* Use Environmental Markers to force jsonschema version. Fixes https://github.com/GNS3/gns3-gui/issues/2849 Version 3.2.0 with Python >= 3.8 Version 2.6.0 with Python < 3.8
* Use Environmental Markers to force jsonschema version 2.6.0 on Windows/macOS. Ref https://github.com/GNS3/gns3-gui/issues/2849
* Remove preferences dialog geometry restoration. Fixes #2807
* Fix unable to configure custom adapters for Qemu VMs. Fixes #2961
## 2.2.7 07/04/2020
* Fix VNC console template doesn't extract %i (Project UUID). Fixes #2960
* Fix contextual menu issues. Ref #2955
## 2.2.6 26/03/2020
* Prevent locked drawings to be deleted. Fixes https://github.com/GNS3/gns3-gui/issues/2948
* Fix issues with empty project variables. Fixes https://github.com/GNS3/gns3-gui/issues/2941
* Upgrade psutil to version 5.6.6 due to CVE-2019-18874 https://github.com/advisories/GHSA-qfc5-mcwq-26q8
* Use existing README.txt if existing when exporting portable project. Fixes https://github.com/GNS3/gns3-server/issues/1724
* Allow creation of a diskless Qemu VMs. Fixes #2939
* Re-enable "create new version" in appliance wizard. Fixes #2837
* Fix unable to load project from project library. Fixes #2932
* Fix some permission denied errors when loading remote project. Ref #2871 Fixes #2901
* Add 'Royal TS V5' to predefined console list
* Disallow invalid grid sized. Fixes #2908
* Check if hostname is blank. Fixes #2924
* Add nvme disk interface and fix scsi disk interface for Qemu VMs.
* Add latest Qemu nic models.
* Upgrade Qt version to 5.14.1. Ref #2778 #2903
## 2.2.5 09/01/2020
* Add gns3-gui.xml and update Linux icons paths & permissions. Ref #2919
## 2.2.4 08/01/2020
* Fix "Console to all nodes" doesn't open cloud objects with console configured. Fixes #2902
* Change default path for SecureCRT. Fixes #2896
* Add icons in setup.py Ref #2898
* Add remote viewer as a VNC console for Linux. Fixes #2913
## 2.2.3 12/11/2019
* Fix issue when binding on 0.0.0.0. Fixes #2892
* Allow double click on cloud with configured console to open session. Fixes #2894
* Officially support Python 3.8. Ref https://github.com/GNS3/gns3-gui/issues/2895
* Set psutil to version 5.6.3 in requirements.txt
## 2.2.2 04/11/2019
* Fix KeyError: 'spice+agent'. Fixes #2890
* Fix wrong log.error() call when exporting file.
* Revert "Explicitly cleanup the cache directory."
* Fix "UnboundLocalError: local variable 'pywintypes' referenced before assignment"
* Fix GUI uses only telnet console. Fixes #2885
* Fix missing sys module in sudo.py Fixes #2886
## 2.2.1 01/11/2019
* Check if console_type is None.
* Explicitly cleanup the cache directory.
* Get Windows interface from registry if cannot load win32com module.
* Ignore OSError returned by psutil when bringing console to front.
* Catch error if NPF or NPCAP service cannot be detected. Ref https://github.com/GNS3/gns3-server/issues/1670
* Better handling for reading synchronous JSON response from server. Ref #2874
* Fix JSONDecodeError when getting server version. Fixes #2874
* Fix FileNotFoundError exceptions when launching SPICE or VNC clients.
* Fix UnboundLocalError local variable 'win32serviceutil' referenced before assignment
* 'Fix' tab order in preferences dialog so it follows the layout
* 'Fix' tab order in edit project dialog so it follows the layout
* Use compatible shlex_quote to handle case where Windows needs double quotes around file names, not single quotes. Ref https://github.com/GNS3/gns3-gui/issues/2866
* Use 0.0.0.0 by default for server host. Fixes https://github.com/GNS3/gns3-server/issues/1663
* Catch IndexError when configuring port names. Fixes #2865
## 2.2.0 30/09/2019
* No changes
## 2.2.0rc5 09/09/2019
* Adjust size for setup dialog and remove question about running the wizard again. Ref #2846
## 2.2.0rc4 30/08/2019
* Fix issue when asking to run the setup wizard again. Ref #2846
* Remove warning about VirtualBox not supporting nested virtualization. Ref https://github.com/GNS3/gns3-server/issues/1610
* Ask user if they want to see the wizard again. Ref #2846
## 2.2.0rc3 12/08/2019
* Revert to jsonschema 2.6.0 due to packaging problem.
## 2.2.0rc2 10/08/2019
* Bump jsonschema to version 3.0.2
* Fix "Unable to change Remote Main Server IP". Fixes #2823
* Fix "AttributeError: 'QGraphicsTextItem' object has no attribute 'locked'". Fixes #2814
* Fix a minor typo in the setup wizard
## 2.2.0b4 11/07/2019
* Fix issue preventing to open the QFileDialog in the correct directory.
* Remove unused edit readme action. Fixes #2816
* Remove deprecated Qemu parameter to run legacy ASA VMs. Fixes #2827
* Upload images on remote controller. Fixes #2828
* Preferences dialog: send API request only if connected to controller
* Fix AttributeError: 'QGraphicsTextItem' object has no attribute 'locked'. Fixes #2814
* Fix KeyError: 'chassis' when converting old IOS templates. Fixes #2813
## 2.2.0b3 15/06/2019
* Fix template migration issues from GUI to controller. Fixes https://github.com/GNS3/gns3-gui/issues/2803
* %guest-cid% variable implementation for Qemu VMs. Fixes https://github.com/GNS3/gns3-gui/issues/2804
* Increase timeout from 2 to 5 seconds for synchronous check. Ref #2805
## 2.2.0b2 29/05/2019
* Fix KeyError: 'endpoint' issue. Fixes #2802
* Fix wrong aligment of symbols in saved/exported projects. Fixes #2800
* Replace urllib.request by Qt implementation for local server synchronous check. Fixes #2793
* Support snapshots for portable projects. Fixes https://github.com/GNS3/gns3-gui/issues/2792
* Fix event notification problem for projects and how snapshots are restored.
* Do not close the nodes dock widget when creating project.
* Fix no scan for images on remote controller. Fixes #2799
* Use QNetworkAccessManager to download custom appliance symbols.
* Experimental auto upgrade should not be available for "frozen" app. Fixes #2797
* Don't allow link labels to be moved for locked nodes. Fixes #2794
* Catch more OSError/PermissionError when checking md5 on remote images. Fixes #2582
* Fix exception when grid size is 0. Fixes #2790
* Catch PermissionError when scanning local image directories. Fixes #2791
## 2.1.20 29/05/2019
* Fix KeyError: 'endpoint' issue. Fixes #2802
## 2.1.19 28/05/2019
* Fix wrong aligment of symbols in saved/exported projects. Fixes #2800
* Replace urllib.request by Qt implementation for local server synchronous check. Fixes #2793
* Set grid's minimum to 5. Fixes #2795
## 2.1.18 22/05/2019
* Fix error in HTTPConnection.request for Python3.6. Fixes #2793
* Catch more OSError/PermissionError when checking md5 on remote images. Fixes #2582
* Fix exception when grid size is 0. Fixes #2790
* Catch PermissionError when scanning local image directories. Fixes #2791
* Revert "Make sure the latest PyQt5 version 5.12.x is used on Windows." Ref #2778
## 2.2.0b1 21/05/2019
* Change behavior when an IOU license is verified. Fixes https://github.com/GNS3/gns3-server/issues/1555
* Fix cannot load new profile. Fixes #2784
* Fix remote packet capture when controller is also remote. Fixes #2785
* Set console type to "none" by default for Ethernet switches and add a warning if trying to use "telnet". Fixes https://github.com/GNS3/gns3-gui/issues/2776
* Add tooltip for symbol theme support in general preferences. Fixes #2770
* Support for persistent docker volumes
## 2.1.17 17/05/2019
* No changes.
## 2.2.0a5 15/04/2019
* Revert "Drop old Qemu support (Windows and macOS) and legacy ASA support." Ref https://github.com/GNS3/gns3-server/issues/1579
* Do not make NPF or NPCAP service mandatory to start the local server on Windows.
* Do not try to upload a local image that is already installed on the local server.
* Back to the major.minor version for config files. Ref https://github.com/GNS3/gns3-gui/issues/2756
* Some adjustments with compute WebSocket handling. Ref https://github.com/GNS3/gns3-server/issues/1564
* Fix AttributeError: 'GraphicsView' object has no attribute '_import_config_dir'. Fixes #2768
* Do not try to lock a SvgIconItem. Fixes #2766
* Prevent locked nodes to be deleted. Fixes https://github.com/GNS3/gns3-gui/issues/2764
* Add PuTTY 0.71 and mark GNS3 PuTTY as deprecated. Fixes #2758
* Fix bug with IOS platform detection. Fixes #2760
## 2.1.16 15/04/2019
* Do not make NPF or NPCAP service mandatory to start the local server on Windows.
* Fix OverflowError error with progress dialog. Fixes #2767
* More fixes for stuck progress window. Fixes #2765
* Fix adding multiple devices - stuck progress window. Fixes #2765
* Make sure the latest PyQt5 version 5.12.x is used on Windows.
* Show a warning when a config export is not supported. Ref #2762
## 2.1.15 21/03/2019
* No changes on the GUI.
## 2.2.0a4 05/04/2019
* Use the full version number for path to config files. Ref https://github.com/GNS3/gns3-gui/issues/2756
* Fix error message when shutting down GUI without a started server.
* Fix remote packet capture and make sure packet capture is stopped when deleting an NIO. Fixes https://github.com/GNS3/gns3-gui/issues/2753
* Store config files in version specific location
* Update pytest from 4.3.1 to 4.4.0
* Fix error messages on closing GNS3 application. Fixes https://github.com/GNS3/gns3-gui/issues/2750
* Fix bug when list of files for an appliance is not displayed.
* Update 'local' to 'bundled' in server & gui, Fixes: #1561
## 2.2.0a3 25/03/2019
* Fix bug when changing symbol. Fixes #2740
* Fix issue when images are not uploaded from appliance wizard. Ref https://github.com/GNS3/gns3-gui/issues/2738
## 2.2.0a2 14/03/2019
* Try to handle stacked widget layout differently. Ref #2605

View File

@@ -1,22 +1,16 @@
# Run tests inside a container
FROM ubuntu:17.10
FROM ubuntu:18.04
MAINTAINER GNS3 Team
#ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update
RUN apt-get install -y --force-yes python3.6 python3-pyqt5 python3-pip python3-pyqt5.qtsvg python3-pyqt5.qtwebsockets python3.6-dev xvfb
RUN apt-get clean
ADD dev-requirements.txt /dev-requirements.txt
ADD requirements.txt /requirements.txt
RUN pip3 install -r /dev-requirements.txt
RUN pip3 install --no-cache-dir -r /dev-requirements.txt
ADD . /src
WORKDIR /src
CMD xvfb-run python3.6 -m pytest -vv

View File

@@ -1,9 +1,8 @@
include README.rst
include README.md
include AUTHORS
include LICENSE
include MANIFEST.in
include requirements.txt
include tox.ini
recursive-include tests *
recursive-include gns3 *
recursive-include resources *

62
README.md Normal file
View File

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

View File

@@ -1,47 +0,0 @@
GNS3-gui
========
.. image:: https://travis-ci.org/GNS3/gns3-gui.svg?branch=master
:target: https://travis-ci.org/GNS3/gns3-gui
.. image:: https://img.shields.io/pypi/v/gns3-gui.svg
:target: https://pypi.python.org/pypi/gns3-gui
GNS3 GUI repository.
Installation
------------
Please see https://docs.gns3.com/
Development
-------------
If you want to update the interface, modify the .ui files using QT tools. And:
.. code:: bash
cd scripts
python build_pyqt.py
Debug
"""""
If you want to see the full logs in the internal shell you can type:
.. code:: bash
debug 2
Or start the app with --debug flag.
Due to the fact PyQT intercept you can use a web debugger for inspecting stuff:
https://github.com/Kozea/wdb
Security issues
----------------
Please contact us using contact informations available here:
http://docs.gns3.com/1ON9JBXSeR7Nt2-Qum2o3ZX0GU86BZwlmNSUgvmqNWGY/index.html

5
SECURITY.md Normal file
View File

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

View File

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

View File

@@ -1,6 +1,5 @@
-rrequirements.txt
pep8==1.7.0
pytest==4.3.1
pytest-pythonpath==0.7.3 # useful for running tests outside tox
pytest-timeout==1.3.3
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.
@@ -223,17 +232,10 @@ class ConsoleCmd(cmd.Cmd):
level = int(args[0])
if level == 0:
print("Deactivating debugging")
for handler in root.handlers:
if isinstance(handler, logging.StreamHandler):
root.removeHandler(handler)
root.setLevel(logging.INFO)
else:
root.addHandler(logging.StreamHandler(sys.stdout))
if level == 1:
print("Activating debugging")
else:
print("Activating full debugging")
root.setLevel(logging.DEBUG)
print("Activating debugging")
root.setLevel(logging.DEBUG)
from .main_window import MainWindow
MainWindow.instance().setSettings({"debug_level": level})
else:

View File

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

View File

@@ -46,6 +46,7 @@ class Controller(QtCore.QObject):
super().__init__()
self._connected = False
self._connecting = False
self._notification_stream = None
self._version = None
self._cache_directory = tempfile.TemporaryDirectory(suffix="-gns3")
self._http_client = None
@@ -53,8 +54,7 @@ class Controller(QtCore.QObject):
self._error_dialog = None
self._display_error = True
self._projects = []
self._controller_websocket = QtWebSockets.QWebSocket()
self._project_websocket = QtWebSockets.QWebSocket()
self._websocket = QtWebSockets.QWebSocket()
# If we do multiple call in order to download the same symbol we queue them
self._static_asset_download_queue = {}
@@ -130,7 +130,8 @@ class Controller(QtCore.QObject):
self._connected = False
self._connecting = True
self.get('/version', self._versionGetSlot)
status, json_data = self.httpClient().getSynchronous('GET', '/version', timeout=60)
self._versionGetSlot(json_data, status is None or status >= 300)
def _httpClientDisconnectedSlot(self):
if self._connected:
@@ -148,11 +149,14 @@ class Controller(QtCore.QObject):
if self._first_error:
self._connecting = False
self.connection_failed_signal.emit()
if "message" in result and self._display_error:
if self._display_error:
self._error_dialog = QtWidgets.QMessageBox(self.parent())
self._error_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
self._error_dialog.setWindowTitle("Connection to server")
self._error_dialog.setText("Error when connecting to the GNS3 server:\n{}".format(result["message"]))
if result and "message" in result:
self._error_dialog.setText("Error when connecting to the GNS3 server:\n{}".format(result["message"]))
else:
self._error_dialog.setText("Cannot connect to the GNS3 server")
self._error_dialog.setIcon(QtWidgets.QMessageBox.Critical)
self._error_dialog.show()
# Try to connect again in 5 seconds
@@ -164,6 +168,7 @@ class Controller(QtCore.QObject):
self._error_dialog.reject()
self._error_dialog = None
self._version = result.get("version")
self._http_client.connection_connected_signal.emit()
def _httpClientConnectedSlot(self):
@@ -246,12 +251,6 @@ class Controller(QtCore.QObject):
if self._http_client:
return self._http_client.createHTTPQuery(method, path, *args, **kwargs)
def getSynchronous(self, endpoint, timeout=2):
return self._http_client.getSynchronous(endpoint, timeout)
def connectProjectWebSocket(self, path, *args):
return self._http_client.connectWebSocket(self._project_websocket, path)
@staticmethod
def instance():
"""
@@ -426,9 +425,10 @@ class Controller(QtCore.QObject):
ignoreErrors=True)
else:
self._notification_stream = self._http_client.connectWebSocket(self._controller_websocket, "/notifications/ws")
self._notification_stream = self._http_client.connectWebSocket(self._websocket, "/notifications/ws")
self._notification_stream.textMessageReceived.connect(self._websocket_event_received)
self._notification_stream.error.connect(self._websocket_error)
self._notification_stream.sslErrors.connect(self._sslErrorsSlot)
def stopListenNotifications(self):
if self._notification_stream:
@@ -449,10 +449,15 @@ class Controller(QtCore.QObject):
@qslot
def _websocket_error(self, error):
if self._notification_stream:
log.error(self._notification_stream.errorString())
log.error("Websocket controller notification stream error: {}".format(self._notification_stream.errorString()))
self._notification_stream = None
self._startListenNotifications()
@qslot
def _sslErrorsSlot(self, ssl_errors):
self._http_client.handleSslError(self._notification_stream, ssl_errors)
@qslot
def _websocket_event_received(self, event):
try:
@@ -474,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,22 +15,20 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
try:
import sentry_sdk
from sentry_sdk.integrations.logging import LoggingIntegration
SENTRY_SDK_AVAILABLE = True
except ImportError:
# Sentry SDK is not installed with deb package in order to simplify packaging
SENTRY_SDK_AVAILABLE = False
import sys
import psutil
import os
import platform
import struct
import distro
try:
import raven
from raven.transport.http import HTTPTransport
RAVEN_AVAILABLE = True
except ImportError:
# raven is not installed with deb package in order to simplify packaging
RAVEN_AVAILABLE = False
from .utils.get_resource import get_resource
from .version import __version__, __version_info__
import logging
@@ -52,66 +50,61 @@ class CrashReport:
Report crash to a third party service
"""
DSN = "https://cc555ea871c3443b8ef31f6bf2bb0d7a:5b6b0f2388bd4838a5c9187aae03fcad@sentry.io/38506"
if hasattr(sys, "frozen"):
cacert = get_resource("cacert.pem")
if cacert is not None and os.path.isfile(cacert):
DSN += "?ca_certs={}".format(cacert)
else:
log.warning("The SSL certificate bundle file '{}' could not be found".format(cacert))
DSN = "https://235ecbc961abe34327a4a397d8ce427a@o19455.ingest.us.sentry.io/38506"
_instance = None
def __init__(self):
# We don't want sentry making noise if an error is catched when you don't have internet
# We don't want sentry making noise if an error is caught when we don't have internet
sentry_errors = logging.getLogger('sentry.errors')
sentry_errors.disabled = True
sentry_uncaught = logging.getLogger('sentry.errors.uncaught')
sentry_uncaught.disabled = True
self._sentry_initialized = False
def captureException(self, exception, value, tb):
from .local_server import LocalServer
from .local_config import LocalConfig
from .controller import Controller
from .compute_manager import ComputeManager
local_server = LocalServer.instance().localServerSettings()
if local_server["report_errors"]:
if not RAVEN_AVAILABLE:
if SENTRY_SDK_AVAILABLE:
# Don't send log records as events.
sentry_logging = LoggingIntegration(level=logging.INFO, event_level=None)
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
if os.path.exists(LocalConfig.instance().runAsRootPath()):
log.warning("User has run application as root. Crash reports are disabled.")
sys.exit(1)
return
if os.path.exists(".git"):
log.warning("A .git directory exist crash report is turn off for developers. Instant exit")
sys.exit(1)
return
if hasattr(exception, "fingerprint"):
client = raven.Client(CrashReport.DSN, release=__version__, fingerprint=['{{ default }}', exception.fingerprint], transport=HTTPTransport)
else:
client = raven.Client(CrashReport.DSN, release=__version__, transport=HTTPTransport)
context = {
tags = {
"os:name": platform.system(),
"os:release": platform.release(),
"os:win_32": " ".join(platform.win32_ver()),
"os:mac": "{} {}".format(platform.mac_ver()[0], platform.mac_ver()[2]),
"os:linux": " ".join(distro.linux_distribution()),
"os:linux": distro.name(pretty=True),
}
self._add_qt_information(tags)
with sentry_sdk.configure_scope() as scope:
for key, value in tags.items():
scope.set_tag(key, value)
extra_context = {
"python:version": "{}.{}.{}".format(sys.version_info[0],
sys.version_info[1],
sys.version_info[2]),
"python:bit": struct.calcsize("P") * 8,
"python:encoding": sys.getdefaultencoding(),
"python:frozen": "{}".format(hasattr(sys, "frozen")),
"python:frozen": "{}".format(hasattr(sys, "frozen"))
}
# extra controller and compute information
extra_context = {"controller:version": Controller.instance().version(),
"controller:host": Controller.instance().host(),
"controller:connected": Controller.instance().connected()}
from .controller import Controller
from .compute_manager import ComputeManager
extra_context["controller:version"] = Controller.instance().version()
extra_context["controller:host"] = Controller.instance().host()
extra_context["controller:connected"] = Controller.instance().connected()
for index, compute in enumerate(ComputeManager.instance().computes()):
extra_context["compute{}:id".format(index)] = compute.id()
extra_context["compute{}:name".format(index)] = compute.name(),
@@ -120,27 +113,48 @@ class CrashReport:
extra_context["compute{}:platform".format(index)] = compute.capabilities().get("platform")
extra_context["compute{}:version".format(index)] = compute.capabilities().get("version")
context = self._add_qt_information(context)
client.tags_context(context)
client.extra_context(extra_context)
try:
report = client.captureException((exception, value, tb))
except Exception as e:
log.error("Can't send crash report to Sentry: {}".format(e))
return
log.debug("Crash report sent with event ID: {}".format(client.get_ident(report)))
with sentry_sdk.configure_scope() as scope:
for key, value in extra_context.items():
scope.set_extra(key, value)
def captureException(self, exception, value, tb):
from .local_server import LocalServer
from .local_config import LocalConfig
local_server = LocalServer.instance().localServerSettings()
if local_server["report_errors"]:
if not SENTRY_SDK_AVAILABLE:
log.warning("Cannot capture exception: Sentry SDK is not available")
return
if os.path.exists(LocalConfig.instance().runAsRootPath()):
log.warning("User is running application as root. Crash reports disabled.")
return
if not hasattr(sys, "frozen") and os.path.exists(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".git")):
log.warning(".git directory detected, crash reporting is turned off for developers.")
return
try:
error = (exception, value, tb)
sentry_sdk.capture_exception(error=error)
log.info("Crash report sent with event ID: {}".format(sentry_sdk.last_event_id()))
except Exception as e:
log.warning("Can't send crash report to Sentry: {}".format(e))
def _add_qt_information(self, tags):
def _add_qt_information(self, context):
try:
from .qt import QtCore
from .qt import sip
except ImportError:
return context
context["psutil:version"] = psutil.__version__
context["pyqt:version"] = QtCore.PYQT_VERSION_STR
context["qt:version"] = QtCore.QT_VERSION_STR
context["sip:version"] = sip.SIP_VERSION_STR
return context
return tags
tags["pyqt:version"] = QtCore.PYQT_VERSION_STR
tags["qt:version"] = QtCore.QT_VERSION_STR
tags["sip:version"] = sip.SIP_VERSION_STR
return tags
@classmethod
def instance(cls):

View File

@@ -36,6 +36,7 @@ from ..compute_manager import ComputeManager
from ..controller import Controller
from ..local_config import LocalConfig
from ..image_upload_manager import ImageUploadManager
from ..image_manager import ImageManager
import logging
log = logging.getLogger(__name__)
@@ -71,11 +72,14 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
self.uiCreateVersionPushButton.clicked.connect(self._createVersionPushButtonClickedSlot)
self.allowCustomFiles.clicked.connect(self._allowCustomFilesChangedSlot)
#FIXME: deactivate the create version feature (confusing and maybe not necessary, TBD)
self.uiCreateVersionPushButton.hide()
# directories where to search for images
images_directories = list()
for emulator in ("QEMU", "IOU", "DYNAMIPS"):
emulator_images_dir = ImageManager.instance().getDirectoryForType(emulator)
if os.path.exists(emulator_images_dir):
images_directories.append(emulator_images_dir)
images_directories.append(os.path.dirname(self._path))
download_directory = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DownloadLocation)
if download_directory != "" and download_directory != os.path.dirname(self._path):
@@ -90,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)
@@ -140,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")
@@ -169,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():
@@ -190,25 +187,56 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
QtWidgets.QMessageBox.warning(self, "GNS3 VM", "The GNS3 VM is not available, please configure the GNS3 VM before adding a new appliance.")
elif self.page(page_id) == self.uiFilesWizardPage:
self._registry.getRemoteImageList(self._appliance.emulator(), self._compute_id)
if Controller.instance().isRemote() or self._compute_id != "local":
self._registry.getRemoteImageList(self._appliance.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:
@@ -221,8 +249,15 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
def _uiServerWizardPage_isComplete(self):
return self.uiRemoteRadioButton.isEnabled() or self.uiVMRadioButton.isEnabled() or self.uiLocalRadioButton.isEnabled()
def _imageUploadedCallback(self, result, error=False, **kwargs):
self._registry.getRemoteImageList(self._appliance.emulator(), self._compute_id)
def _imageUploadedCallback(self, result, error=False, context=None, **kwargs):
if context is None:
context = {}
image_path = context.get("image_path", "unknown")
if error:
log.error("Error while uploading image '{}': {}".format(image_path, result["message"]))
else:
log.info("Image '{}' has been successfully uploaded".format(image_path))
self._registry.getRemoteImageList(self._appliance.template_type(), self._compute_id)
def _showApplianceInfoSlot(self):
"""
@@ -339,6 +374,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
image_widget.setForeground(2, QtGui.QBrush(QtGui.QColor("red")))
else:
image_widget.setForeground(2, QtGui.QBrush(QtGui.QColor("green")))
image_widget.setToolTip(2, image["path"])
# Associated data stored are col 0: version, col 1: image
image_widget.setData(0, QtCore.Qt.UserRole, version)
@@ -392,15 +428,20 @@ 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"),
strict_md5_check=not self.allowCustomFiles.isChecked())
if img:
image["status"] = "Found"
if img.location == "local":
image["status"] = "Found locally"
else:
compute = ComputeManager.instance().getCompute(self._compute_id)
image["status"] = "Found on {}".format(compute.name())
image["md5sum"] = img.md5sum
image["filesize"] = img.filesize
image["path"] = img.path
else:
image["status"] = "Missing"
self._refreshing = False
@@ -455,8 +496,24 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
Allow user to create a new version of an appliance
"""
new_version, ok = QtWidgets.QInputDialog.getText(self, "Creating a new version", "Creating a new version allows to import unknown files to use with this appliance.\nPlease share your experience on the GNS3 community if this version works.\n\nVersion name:", QtWidgets.QLineEdit.Normal)
current = self.uiApplianceVersionTreeWidget.currentItem()
if current is None:
QtWidgets.QMessageBox.critical(self.parent(), "Base version", "Please select a base version")
return
base_version = current.data(0, QtCore.Qt.UserRole)
new_version_name, ok = QtWidgets.QInputDialog.getText(self, "Creating a new version", "Create a new version for this appliance.\nPlease share your experience on the GNS3 community if this version works.\n\nVersion name:", QtWidgets.QLineEdit.Normal, base_version.get("name"))
if ok:
new_version = {"name": new_version_name}
new_version["images"] = {}
for disk_type in base_version["images"]:
base_filename = base_version["images"][disk_type]["filename"]
filename, ok = QtWidgets.QInputDialog.getText(self, "Image", "Disk image filename for {}".format(disk_type), QtWidgets.QLineEdit.Normal, base_filename)
if not ok:
filename = base_filename
new_version["images"][disk_type] = {"filename": filename, "version": new_version_name}
try:
self._appliance.create_new_version(new_version)
except ApplianceError as e:
@@ -483,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",
@@ -495,9 +552,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "Can't access to the image file {}: {}.".format(path, str(e)))
return
image_upload_manger = ImageUploadManager(
image, Controller.instance(), self._compute_id,
self._imageUploadedCallback, LocalConfig.instance().directFileUpload())
image_upload_manger = ImageUploadManager(image, Controller.instance(), self._compute_id, self._imageUploadedCallback, LocalConfig.instance().directFileUpload())
image_upload_manger.upload()
def _getQemuBinariesFromServerCallback(self, result, error=False, **kwargs):
@@ -520,7 +575,11 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if self.uiQemuListComboBox.count() == 1:
self.next()
else:
i = self.uiQemuListComboBox.findText(self._appliance["qemu"]["arch"], QtCore.Qt.MatchContains)
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)
@@ -533,8 +592,8 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if version is None:
appliance_configuration = self._appliance.copy()
if not "docker" 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:
@@ -551,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)
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
@@ -583,33 +647,48 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
self._template_created = True
self.done(True)
def _uploadImages(self, version):
def _uploadImages(self, name, version):
"""
Upload an image the compute.
"""
appliance_configuration = self._appliance.search_images_for_version(version)
try:
appliance_configuration = self._appliance.search_images_for_version(version)
except ApplianceError as e:
QtWidgets.QMessageBox.critical(self, "Appliance","Cannot install {} version {}: {}".format(name, version, e))
return
for image in appliance_configuration["images"]:
if image["location"] == "local":
image = Image(self._appliance.emulator(), image["path"], filename=image["filename"])
image_upload_manger = ImageUploadManager(
image, Controller.instance(), self._compute_id,
self._applianceImageUploadedCallback, LocalConfig.instance().directFileUpload())
image_upload_manger.upload()
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.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
def _applianceImageUploadedCallback(self, result, error=False, **kwargs):
self._image_uploading_count -= 1
def _applianceImageUploadedCallback(self, result, error=False, context=None, **kwargs):
if context is None:
context = {}
image_path = context.get("image_path", "unknown")
if error:
log.error("Error while uploading image '{}': {}".format(image_path, result["message"]))
else:
log.info("Image '{}' has been successfully uploaded".format(image_path))
self._image_uploading_count -= 1
def nextId(self):
if self.currentPage() == self.uiServerWizardPage:
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):
@@ -638,7 +717,8 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
return False
self._uploadImages(version["name"])
self._uploadImages(appliance["name"], version["name"])
elif self.currentPage() == self.uiUsageWizardPage:
# validate the usage page
@@ -676,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

@@ -49,7 +49,6 @@ class ConfigurationDialog(QtWidgets.QDialog, Ui_configurationDialog):
self._settings = settings
self._configuration_page = configuration_page
@property
def settings(self):
return self._settings

View File

@@ -44,6 +44,9 @@ class ConsoleCommandDialog(QtWidgets.QDialog, Ui_uiConsoleCommandDialog):
"""
super().__init__(parent)
self.setupUi(self)
if console_type == "spice+agent":
# special case for spice+agent, use the spice console type
console_type = "spice"
self._console_type = console_type
self._current = current
@@ -63,7 +66,7 @@ class ConsoleCommandDialog(QtWidgets.QDialog, Ui_uiConsoleCommandDialog):
elif self._console_type == "vnc":
self._consoles = copy.copy(PRECONFIGURED_VNC_CONSOLE_COMMANDS)
self._consoles.update(self._settings[self._console_type])
elif self._console_type.startswith("spice"):
elif self._console_type == "spice":
self._consoles = copy.copy(PRECONFIGURED_SPICE_CONSOLE_COMMANDS)
self._consoles.update(self._settings[self._console_type])
@@ -121,8 +124,8 @@ class ConsoleCommandDialog(QtWidgets.QDialog, Ui_uiConsoleCommandDialog):
dialog = ConsoleCommandDialog(parent, console_type=console_type, current=current)
dialog.show()
if dialog.exec_():
return (True, dialog.uiCommandPlainTextEdit.toPlainText().replace("\n", " "))
return (False, None)
return True, dialog.uiCommandPlainTextEdit.toPlainText().replace("\n", " ")
return False, None
if __name__ == '__main__':

View File

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

View File

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

View File

@@ -46,19 +46,11 @@ class EditProjectDialog(QtWidgets.QDialog, Ui_EditProjectDialog):
self.uiNewVarButton.clicked.connect(self.onAddNewVariable)
self.uiGlobalVariablesGrid.addWidget(self.uiNewVarButton, 0, 3, QtCore.Qt.AlignRight)
self._variables = self.setUpVariables()
self._variables = self._project.variables()
if not self._variables:
self._variables = [{"name": "", "value": ""}]
self.updateGlobalVariables()
def setUpVariables(self):
new_variable = {"name": "", "value": ""}
variables = self._project.variables()
if variables is not None:
variables.append(new_variable)
else:
variables = [new_variable]
return variables
def updateGlobalVariables(self):
while True:
item = self.uiGlobalVariablesGrid.takeAt(1)
@@ -98,7 +90,7 @@ class EditProjectDialog(QtWidgets.QDialog, Ui_EditProjectDialog):
variable["value"] = text
def _cleanVariables(self):
return [v for v in self._variables if v.get("name", "").strip() != ""]
return [v for v in self._variables if v.get("name").strip() != ""]
def done(self, result):
"""
@@ -108,14 +100,19 @@ class EditProjectDialog(QtWidgets.QDialog, Ui_EditProjectDialog):
"""
if result:
self._project.setName(self.uiProjectNameLineEdit.text())
self._project.setAutoOpen(self.uiProjectAutoOpenCheckBox.isChecked())
self._project.setAutoClose(not self.uiProjectAutoCloseCheckBox.isChecked())
self._project.setAutoStart(self.uiProjectAutoStartCheckBox.isChecked())
self._project.setSceneHeight(self.uiSceneHeightSpinBox.value())
self._project.setSceneWidth(self.uiSceneWidthSpinBox.value())
self._project.setNodeGridSize(self.uiNodeGridSizeSpinBox.value())
self._project.setDrawingGridSize(self.uiDrawingGridSizeSpinBox.value())
self._project.setVariables(self._cleanVariables())
self._project.update()
node_grid_size = self.uiNodeGridSizeSpinBox.value()
drawing_grid_size = self.uiDrawingGridSizeSpinBox.value()
if node_grid_size % drawing_grid_size != 0:
QtWidgets.QMessageBox.critical(self, "Grid sizes", "Invalid grid sizes which will create overlapping lines")
else:
self._project.setNodeGridSize(node_grid_size)
self._project.setDrawingGridSize(drawing_grid_size)
self._project.setName(self.uiProjectNameLineEdit.text())
self._project.setAutoOpen(self.uiProjectAutoOpenCheckBox.isChecked())
self._project.setAutoClose(not self.uiProjectAutoCloseCheckBox.isChecked())
self._project.setAutoStart(self.uiProjectAutoStartCheckBox.isChecked())
self._project.setSceneHeight(self.uiSceneHeightSpinBox.value())
self._project.setSceneWidth(self.uiSceneWidthSpinBox.value())
self._project.setVariables(self._cleanVariables())
self._project.update()
super().done(result)

View File

@@ -152,18 +152,18 @@ class NewTemplateWizard(QtWidgets.QWizard, Ui_NewTemplateWizard):
"""
self.uiAppliancesTreeWidget.clear()
parent_routers = QtWidgets.QTreeWidgetItem(self.uiAppliancesTreeWidget)
parent_routers.setText(0, "Routers")
parent_routers.setFlags(parent_routers.flags() & ~QtCore.Qt.ItemIsSelectable)
parent_switches = QtWidgets.QTreeWidgetItem(self.uiAppliancesTreeWidget)
parent_switches.setText(0, "Switches")
parent_switches.setFlags(parent_switches.flags() & ~QtCore.Qt.ItemIsSelectable)
parent_guests = QtWidgets.QTreeWidgetItem(self.uiAppliancesTreeWidget)
parent_guests.setText(0, "Guests")
parent_guests.setFlags(parent_guests.flags() & ~QtCore.Qt.ItemIsSelectable)
parent_firewalls = QtWidgets.QTreeWidgetItem(self.uiAppliancesTreeWidget)
parent_firewalls.setText(0, "Firewalls")
parent_firewalls.setFlags(parent_guests.flags() & ~QtCore.Qt.ItemIsSelectable)
parent_switches = QtWidgets.QTreeWidgetItem(self.uiAppliancesTreeWidget)
parent_switches.setText(0, "Switches")
parent_switches.setFlags(parent_guests.flags() & ~QtCore.Qt.ItemIsSelectable)
parent_routers = QtWidgets.QTreeWidgetItem(self.uiAppliancesTreeWidget)
parent_routers.setText(0, "Routers")
parent_routers.setFlags(parent_guests.flags() & ~QtCore.Qt.ItemIsSelectable)
parent_firewalls.setFlags(parent_firewalls.flags() & ~QtCore.Qt.ItemIsSelectable)
self.uiAppliancesTreeWidget.expandAll()
for appliance in ApplianceManager.instance().appliances():
@@ -268,7 +268,7 @@ class NewTemplateWizard(QtWidgets.QWizard, Ui_NewTemplateWizard):
super().done(result)
if result:
ApplianceManager.instance().appliances_changed_signal.disconnect(self._appliancesChangedSlot)
#ApplianceManager.instance().appliances_changed_signal.disconnect(self._appliancesChangedSlot)
from gns3.main_window import MainWindow
if self.currentPage() == self.uiApplianceFromServerWizardPage:
items = self.uiAppliancesTreeWidget.selectedItems()

View File

@@ -128,9 +128,10 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
# Class name, changed signal
widget_to_watch = {
#QtWidgets.QLineEdit: "textChanged",
QtWidgets.QLineEdit: "textChanged",
QtWidgets.QPlainTextEdit: "textChanged",
# QtWidgets.QTreeWidget: "itemChanged",
QtWidgets.QTreeWidget: "itemDoubleClicked",
QtWidgets.QComboBox: "currentIndexChanged",
QtWidgets.QSpinBox: "valueChanged",
QtWidgets.QAbstractButton: "pressed"

View File

@@ -22,6 +22,7 @@ import shutil
from gns3.qt import QtWidgets
from gns3.local_config import LocalConfig
from gns3.ui.profile_select_dialog_ui import Ui_ProfileSelectDialog
from gns3.version import __version_info__
import logging
log = logging.getLogger(__name__)
@@ -39,8 +40,8 @@ class ProfileSelectDialog(QtWidgets.QDialog, Ui_ProfileSelectDialog):
self._main.hide()
parent = self._main
super().__init__(parent)
self.setupUi(self)
self.setupUi(self)
self.uiNewPushButton.clicked.connect(self._newPushButtonSlot)
self.uiDeletePushButton.clicked.connect(self._deletePushButtonSlot)
@@ -48,12 +49,13 @@ class ProfileSelectDialog(QtWidgets.QDialog, Ui_ProfileSelectDialog):
screen = QtWidgets.QApplication.desktop().screenGeometry()
self.move(screen.center() - self.rect().center())
version = "{}.{}".format(__version_info__[0], __version_info__[1])
if sys.platform.startswith("win"):
appdata = os.path.expandvars("%APPDATA%")
path = os.path.join(appdata, "GNS3")
path = os.path.join(appdata, "GNS3", version)
else:
home = os.path.expanduser("~")
path = os.path.join(home, ".config", "GNS3")
path = os.path.join(home, ".config", "GNS3", version)
self.profiles_path = os.path.join(path, "profiles")
self.uiShowAtStartupCheckBox.setChecked(LocalConfig.instance().multiProfiles())
@@ -65,9 +67,9 @@ class ProfileSelectDialog(QtWidgets.QDialog, Ui_ProfileSelectDialog):
try:
if os.path.exists(self.profiles_path):
for profil in sorted(os.listdir(self.profiles_path)):
if not profil.startswith("."):
self.uiProfileSelectComboBox.addItem(profil)
for profile in sorted(os.listdir(self.profiles_path)):
if not profile.startswith("."):
self.uiProfileSelectComboBox.addItem(profile)
except OSError:
pass
@@ -79,7 +81,7 @@ class ProfileSelectDialog(QtWidgets.QDialog, Ui_ProfileSelectDialog):
super().accept()
def _newPushButtonSlot(self):
profile, ok = QtWidgets.QInputDialog.getText(self.parent(), "New profile", "Profile name:")
profile, ok = QtWidgets.QInputDialog.getText(self, "New profile", "Profile name:")
if ok:
self.uiProfileSelectComboBox.addItem(profile)
self.uiProfileSelectComboBox.setCurrentText(profile)
@@ -88,13 +90,13 @@ class ProfileSelectDialog(QtWidgets.QDialog, Ui_ProfileSelectDialog):
def _deletePushButtonSlot(self):
profile = self.uiProfileSelectComboBox.currentText()
if profile == "default":
QtWidgets.QMessageBox.critical(self.parentWidget(), "Delete profile", "You can't delete the default profile")
QtWidgets.QMessageBox.critical(self, "Delete profile", "The default profile cannot be deleted")
else:
try:
shutil.rmtree(os.path.join(self.profiles_path, profile))
self._refresh()
except (OSError, PermissionError) as e:
QtWidgets.QMessageBox.critical(self.parentWidget(), "Delete profile", str(e))
QtWidgets.QMessageBox.critical(self, "Cannot delete profile", str(e))
if __name__ == '__main__':

View File

@@ -53,7 +53,7 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
if show_open_options:
self.uiOpenProjectPushButton.clicked.connect(self._openProjectActionSlot)
self.uiRecentProjectsPushButton.clicked.connect(self._showRecentProjectsSlot)
self._addRecentFilesMenu()
else:
self.uiOpenProjectGroupBox.hide()
self.uiProjectTabWidget.removeTab(1)
@@ -135,17 +135,20 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
new_project_name)
name = name.strip()
if reply and len(name) > 0:
reset_mac_addresses = self.uiResetMacAddressesCheckBox.isChecked()
if Controller.instance().isRemote():
Controller.instance().post("/projects/{project_id}/duplicate".format(project_id=project_id),
self._duplicateCallback,
body={"name": name},
body={"name": name, "reset_mac_addresses": reset_mac_addresses},
progressText="Duplicating project '{}'...".format(name),
timeout=None)
else:
project_location = os.path.join(Topology.instance().projectsDirPath(), name)
Controller.instance().post("/projects/{project_id}/duplicate".format(project_id=project_id),
self._duplicateCallback,
body={"name": name, "path": project_location},
body={"name": name, "path": project_location, "reset_mac_addresses": reset_mac_addresses},
progressText="Duplicating project '{}'...".format(name),
timeout=None)
@@ -228,20 +231,20 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
self._main_window.openProjectActionSlot()
self.reject()
def _showRecentProjectsSlot(self):
def _addRecentFilesMenu(self):
"""
lot to show all the recent projects in a menu.
Add recent projects in a menu.
"""
menu = QtWidgets.QMenu()
menu.triggered.connect(self._menuTriggeredSlot)
menu = QtWidgets.QMenu(parent=self)
if Controller.instance().isRemote():
for action in self._main_window.recent_project_actions:
menu.addAction(action)
else:
for action in self._main_window.recent_file_actions:
menu.addAction(action)
menu.exec_(QtGui.QCursor.pos())
menu.triggered.connect(self._menuTriggeredSlot)
self.uiRecentProjectsPushButton.setMenu(menu)
def _overwriteProjectCallback(self, result, error=False, **kwargs):
if error:

View File

@@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import os
import datetime
from gns3.qt import QtCore, QtWidgets
@@ -54,9 +55,19 @@ class ExportProjectWizard(QtWidgets.QWizard, Ui_ExportProjectWizard):
self.uiCompressionComboBox.setCurrentIndex(1)
self.helpRequested.connect(self._showHelpSlot)
self.uiPathBrowserToolButton.clicked.connect(self._pathBrowserSlot)
self._loadReadme()
readme_text = "Project: '{}' created on {}\nAuthor: John Doe <john.doe@example.com>\n\nNo project description was given".format(self._project.name(), datetime.date.today())
self.uiReadmeTextEdit.setPlainText(readme_text)
def _loadReadme(self):
self._project.get("/files/README.txt", self._loadedReadme)
def _loadedReadme(self, result, error=False, raw_body=None, context={}, **kwargs):
if not error:
self.uiReadmeTextEdit.setPlainText(raw_body.decode("utf-8", errors="replace"))
else:
readme_text = "Project: '{}' created on {}\nAuthor: John Doe <john.doe@example.com>\n\nNo project description was given".format(self._project.name(), datetime.date.today())
self.uiReadmeTextEdit.setPlainText(readme_text)
def _pathBrowserSlot(self):
@@ -119,8 +130,16 @@ class ExportProjectWizard(QtWidgets.QWizard, Ui_ExportProjectWizard):
include_images = "yes"
else:
include_images = "no"
if self.uiIncludeSnapshotsCheckBox.isChecked():
include_snapshots = "yes"
else:
include_snapshots = "no"
if self.uiResetMacAddressesCheckBox.isChecked():
reset_mac_addresses = "yes"
else:
reset_mac_addresses = "no"
compression = self.uiCompressionComboBox.currentData()
export_worker = ExportProjectWorker(self._project, self._path, include_images, compression)
export_worker = ExportProjectWorker(self._project, self._path, include_images, include_snapshots, reset_mac_addresses, compression)
progress_dialog = ProgressDialog(export_worker, "Exporting project", "Exporting portable project files...", "Cancel", parent=self, create_thread=False)
progress_dialog.show()
progress_dialog.exec_()

View File

@@ -37,9 +37,7 @@ class ProjectWelcomeDialog(QtWidgets.QDialog, Ui_ProjectWelcomeDialog):
self.uiOkButton.clicked.connect(self._okButtonClickedSlot)
self.gridLayout.setAlignment(QtCore.Qt.AlignTop)
self.label.setOpenExternalLinks(True)
self._variables = self._getVariables(project)
self._loadReadme()
self._addMisingVariablesEdits()
@@ -50,10 +48,11 @@ class ProjectWelcomeDialog(QtWidgets.QDialog, Ui_ProjectWelcomeDialog):
return variables
def _addMisingVariablesEdits(self):
missing = [v for v in self._variables if v.get("value", "").strip() == ""]
#TODO: refactor this to use a QListWidget
missing = [v for v in self._variables if v.get("name") and v.get("value", "").strip() == ""]
for i, variable in enumerate(missing, start=0):
nameLabel = QtWidgets.QLabel()
nameLabel.setText(variable.get("name", ""))
nameLabel.setText(variable.get("name") + ":")
self.gridLayout.addWidget(nameLabel, i, 0)
valueEdit = QtWidgets.QLineEdit()
@@ -72,13 +71,13 @@ class ProjectWelcomeDialog(QtWidgets.QDialog, Ui_ProjectWelcomeDialog):
variable["value"] = text
def _okButtonClickedSlot(self):
missing = [v for v in self._variables if v.get("value", "").strip() == ""]
missing = [v for v in self._variables if v.get("name") and v.get("value", "").strip() == ""]
if len(missing) > 0:
reply = QtWidgets.QMessageBox.warning(
self, 'Missing values',
'Are you sure you want to continue without providing missing values?',
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
reply = QtWidgets.QMessageBox.warning(self,
"Missing values",
"Are you sure you want to continue without providing missing values?",
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
return

View File

@@ -43,15 +43,18 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
super().__init__(parent)
self.setupUi(self)
self.adjustSize()
self._gns3_vm_settings = {
"enable": True,
"headless": False,
"when_exit": "stop",
"engine": "vmware",
"allocate_vcpus_ram": True,
"vcpus": 1,
"ram": 2048,
"vmname": "GNS3 VM"
"vmname": "GNS3 VM",
"port": 80
}
self.setWizardStyle(QtWidgets.QWizard.ModernStyle)
@@ -65,7 +68,6 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
self.uiRefreshPushButton.clicked.connect(self._refreshVMListSlot)
self.uiVmwareRadioButton.clicked.connect(self._listVMwareVMsSlot)
self.uiVirtualBoxRadioButton.clicked.connect(self._listVirtualBoxVMsSlot)
self.uiVMwareBannerButton.clicked.connect(self._VMwareBannerButtonClickedSlot)
settings = parent.settings()
self.uiShowCheckBox.setChecked(settings["hide_setup_wizard"])
@@ -83,14 +85,14 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
if address.protocol() in [QtNetwork.QAbstractSocket.IPv4Protocol, QtNetwork.QAbstractSocket.IPv6Protocol]:
address_string = address.toString()
if address_string.startswith("169.254") or address_string.startswith("fe80"):
# ignore link-local addresses
# ignore link-local addresses, could not use https://doc.qt.io/qt-5/qhostaddress.html#isLinkLocal
# because it was introduced in Qt 5.11
continue
self.uiLocalServerHostComboBox.addItem(address_string, address_string)
if sys.platform.startswith("darwin"):
self.uiVMwareBannerButton.setIcon(QtGui.QIcon(":/images/vmware_fusion_banner.png"))
else:
self.uiVMwareBannerButton.setIcon(QtGui.QIcon(":/images/vmware_workstation_banner.png"))
self.uiLocalServerHostComboBox.addItem("localhost", "localhost") # local host
self.uiLocalServerHostComboBox.addItem("::", "::") # all IPv6 addresses
self.uiLocalServerHostComboBox.addItem("0.0.0.0", "0.0.0.0") # all IPv4 addresses
if sys.platform.startswith("linux"):
self.uiLocalRadioButton.setChecked(True)
@@ -114,13 +116,6 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
self.uiLocalServerPathLineEdit.setText(path)
def _VMwareBannerButtonClickedSlot(self):
if sys.platform.startswith("darwin"):
url = "http://send.onenetworkdirect.net/z/621395/CD225091/"
else:
url = "http://send.onenetworkdirect.net/z/616207/CD225091/"
QtGui.QDesktopServices.openUrl(QtCore.QUrl(url))
def _listVMwareVMsSlot(self):
"""
Slot to refresh the VMware VMs list.
@@ -132,7 +127,7 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
from gns3.modules import VMware
settings = VMware.instance().settings()
if not os.path.exists(settings["vmrun_path"]):
QtWidgets.QMessageBox.critical(self, "VMware", "VMware vmrun tool could not be found, VMware or the VIX API (required for VMware player) is probably not installed. You can download it from https://www.vmware.com/support/developer/vix-api/. After installation you need to restart GNS3.")
QtWidgets.QMessageBox.critical(self, "VMware", "VMware vmrun tool could not be found, VMware or the VIX API (required for VMware player) is probably not installed. You can download it from https://customerconnect.vmware.com/downloads/details?downloadGroup=PLAYER-1400-VIX1170&productId=687. After installation you need to restart GNS3.")
return
self._refreshVMListSlot()
@@ -141,7 +136,6 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
Slot to refresh the VirtualBox VMs list.
"""
QtWidgets.QMessageBox.warning(self, "GNS3 VM on VirtualBox", "VirtualBox doesn't support nested virtualization, this means running Qemu based VM could be very slow")
download_url = "https://github.com/GNS3/gns3-gui/releases/download/v{version}/GNS3.VM.VirtualBox.{version}.zip".format(version=__version__)
self.uiGNS3VMDownloadLinkUrlLabel.setText('If you don\'t have the GNS3 Virtual Machine you can <a href="{download_url}">download it here</a>.<br>And import the VM in the virtualization software and hit refresh.'.format(download_url=download_url))
self.uiVmwareRadioButton.setChecked(False)
@@ -414,11 +408,8 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
settings["hide_setup_wizard"] = True
else:
local_server_settings = LocalServer.instance().localServerSettings()
if local_server_settings["host"] is None:
local_server_settings["host"] = DEFAULT_LOCAL_SERVER_HOST
LocalServer.instance().updateLocalServerSettings(local_server_settings)
settings["hide_setup_wizard"] = self.uiShowCheckBox.isChecked()
LocalServer.instance().updateLocalServerSettings(local_server_settings)
settings["hide_setup_wizard"] = not self.uiShowCheckBox.isChecked()
self.parentWidget().setSettings(settings)
super().done(result)

View File

@@ -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()))
self.uiRotationSpinBox.setValue(first_item.rotation())
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

@@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2019 Pekka Helenius
# Copyright (C) 2014 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Style editor to edit Link items.
"""
from ..qt import QtCore, QtWidgets, QtGui
from ..ui.style_editor_dialog_ui import Ui_StyleEditorDialog
class StyleEditorDialogLink(QtWidgets.QDialog, Ui_StyleEditorDialog):
"""
Style editor dialog.
:param parent: parent widget
:param link: selected link
"""
def __init__(self, link, parent):
super().__init__(parent)
self.setupUi(self)
self._link = link
self._link_style = {}
self.uiBorderColorPushButton.clicked.connect(self._setBorderColorSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self._applyPreferencesSlot)
self.uiBorderStyleComboBox.addItem("Solid", QtCore.Qt.SolidLine)
self.uiBorderStyleComboBox.addItem("Dash", QtCore.Qt.DashLine)
self.uiBorderStyleComboBox.addItem("Dot", QtCore.Qt.DotLine)
self.uiBorderStyleComboBox.addItem("Dash Dot", QtCore.Qt.DashDotLine)
self.uiBorderStyleComboBox.addItem("Dash Dot Dot", QtCore.Qt.DashDotDotLine)
self.uiBorderStyleComboBox.addItem("Invisible", QtCore.Qt.NoPen)
self.uiColorLabel.hide()
self.uiColorPushButton.hide()
self._color = None
self.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(),
self._border_color.green(),
self._border_color.blue(),
self._border_color.alpha()))
self.uiBorderWidthSpinBox.setValue(pen.width())
index = self.uiBorderStyleComboBox.findData(pen.style())
if index != -1:
self.uiBorderStyleComboBox.setCurrentIndex(index)
self.adjustSize()
def _setBorderColorSlot(self):
"""
Slot to select the border color.
"""
color = QtWidgets.QColorDialog.getColor(self._border_color, self, "Select Color", QtWidgets.QColorDialog.ShowAlphaChannel)
if color.isValid():
self._border_color = color
self.uiBorderColorPushButton.setStyleSheet("background-color: rgba({}, {}, {}, {});".format(self._border_color.red(),
self._border_color.green(),
self._border_color.blue(),
self._border_color.alpha()))
def _applyPreferencesSlot(self):
"""
Applies the new style settings.
"""
border_style = QtCore.Qt.PenStyle(self.uiBorderStyleComboBox.itemData(self.uiBorderStyleComboBox.currentIndex()))
pen = QtGui.QPen(self._border_color, self.uiBorderWidthSpinBox.value(), border_style, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)
self._link.setPen(pen)
new_link_style = {}
new_link_style["color"] = self._border_color.name()
new_link_style["width"] = self.uiBorderWidthSpinBox.value()
new_link_style["type"] = border_style
# Store values
self._link.setLinkStyle(new_link_style)
self._link.setHovered(False) # allow to see the new style
def done(self, result):
"""
Called when the dialog is closed.
:param result: boolean (accepted or rejected)
"""
if result:
self._applyPreferencesSlot()
super().done(result)

View File

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

View File

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

View File

@@ -49,7 +49,7 @@ from .compute_manager import ComputeManager
from .utils.get_icon import get_icon
# link items
from .items.link_item import LinkItem
from .items.link_item import LinkItem, SvgIconItem
from .items.ethernet_link_item import EthernetLinkItem
from .items.serial_link_item import SerialLinkItem
@@ -121,8 +121,6 @@ class GraphicsView(QtWidgets.QGraphicsView):
def setZoom(self, zoom):
"""
Sets zoom of the Graphics View
:param zoom:
:return:
"""
if zoom:
factor = zoom / 100.
@@ -193,6 +191,9 @@ class GraphicsView(QtWidgets.QGraphicsView):
# clear all objects on the scene
self.scene().clear()
# reset zoom / scale
self.resetTransform()
def _loadSettings(self):
"""
@@ -326,6 +327,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
# connect the signals that let the graphics view knows about events such as
# a new link creation or deletion.
if self._topology.addLink(link):
source_node.addLink(link)
destination_node.addLink(link)
link.add_link_signal.connect(self.addLinkSlot)
link.delete_link_signal.connect(self.deleteLinkSlot)
@@ -392,7 +395,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
# link addition code
if not self._newlink:
source_item = item
source_port = source_item.connectToPort()
source_port = source_item.connectToPort(event.globalPos())
if not source_port:
return
@@ -409,7 +412,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
source_item = self._newlink.sourceItem()
source_port = self._newlink.sourcePort()
destination_item = item
destination_port = destination_item.connectToPort()
destination_port = destination_item.connectToPort(event.globalPos())
if not destination_port:
return
@@ -435,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
@@ -460,28 +465,18 @@ class GraphicsView(QtWidgets.QGraphicsView):
else:
item.setSelected(True)
elif is_not_link and is_not_logo and event.button() == QtCore.Qt.RightButton and not self._adding_link:
if item and not sip.isdeleted(item):
# Prevent right clicking on a selected item from de-selecting all other items
if not item.isSelected():
if not event.modifiers() & QtCore.Qt.ControlModifier:
for it in self.scene().items():
it.setSelected(False)
item.setSelected(True)
self._showDeviceContextualMenu(QtGui.QCursor.pos())
else:
self._showDeviceContextualMenu(QtGui.QCursor.pos())
# when more than one item is selected display the contextual menu even if mouse is not above an item
elif len(self.scene().selectedItems()) > 1:
self._showDeviceContextualMenu(QtGui.QCursor.pos())
pass #TODO: remove this without creating a bug...
elif is_not_link and self._adding_link and event.button() == QtCore.Qt.RightButton:
# send a escape key to the main window to cancel the link addition
key = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Escape, QtCore.Qt.NoModifier)
QtWidgets.QApplication.sendEvent(self._main_window, key)
elif item and isinstance(item, NodeItem) and self._adding_link and event.button() == QtCore.Qt.LeftButton:
self._userNodeLinking(event, item)
#context_event = QtGui.QContextMenuEvent(QtGui.QContextMenuEvent.Mouse, event.pos())
#QtWidgets.QApplication.sendEvent(self, context_event)
elif event.button() == QtCore.Qt.LeftButton and self._adding_note:
pos = self.mapToScene(event.pos())
note = self.createDrawingItem("text", pos.x(), pos.y(), 2)
note = self.createDrawingItem("text", int(pos.x()), int(pos.y()), 2)
pos_x = note.pos().x()
pos_y = note.pos().y() - (note.boundingRect().height() / 2)
note.setPos(pos_x, pos_y)
@@ -491,19 +486,19 @@ class GraphicsView(QtWidgets.QGraphicsView):
self._adding_note = False
elif event.button() == QtCore.Qt.LeftButton and self._adding_rectangle:
pos = self.mapToScene(event.pos())
self.createDrawingItem("rect", pos.x(), pos.y(), 1)
self.createDrawingItem("rect", int(pos.x()), int(pos.y()), 1)
self._main_window.uiDrawRectangleAction.setChecked(False)
self.setCursor(QtCore.Qt.ArrowCursor)
self._adding_rectangle = False
elif event.button() == QtCore.Qt.LeftButton and self._adding_ellipse:
pos = self.mapToScene(event.pos())
self.createDrawingItem("ellipse", pos.x(), pos.y(), 1)
self.createDrawingItem("ellipse", int(pos.x()), int(pos.y()), 1)
self._main_window.uiDrawEllipseAction.setChecked(False)
self.setCursor(QtCore.Qt.ArrowCursor)
self._adding_ellipse = False
elif event.button() == QtCore.Qt.LeftButton and self._adding_line:
pos = self.mapToScene(event.pos())
self.createDrawingItem("line", pos.x(), pos.y(), 1)
self.createDrawingItem("line", int(pos.x()), int(pos.y()), 1)
self._main_window.uiDrawLineAction.setChecked(False)
self.setCursor(QtCore.Qt.ArrowCursor)
self._adding_line = False
@@ -512,6 +507,46 @@ class GraphicsView(QtWidgets.QGraphicsView):
self.toggleUiDeviceMenu()
def contextMenuEvent(self, event):
"""
Handles all context menu events.
:param event: QContextMenuEvent instance
"""
is_not_link = True
is_not_logo = True
item = self.itemAt(event.pos())
if item and sip.isdeleted(item):
return
if item and (isinstance(item, LinkItem) or isinstance(item.parentItem(), LinkItem)):
is_not_link = False
if item and (isinstance(item, LogoItem) or isinstance(item.parentItem(), LogoItem)):
is_not_logo = False
else:
for it in self.scene().items():
if isinstance(it, LinkItem):
it.setHovered(False)
if is_not_link and is_not_logo and not self._adding_link:
if item and not sip.isdeleted(item):
# Prevent right clicking on a selected item from de-selecting all other items
if not item.isSelected():
if not event.modifiers() & QtCore.Qt.ControlModifier:
for it in self.scene().items():
it.setSelected(False)
item.setSelected(True)
self._showDeviceContextualMenu(event.globalPos())
# when more than one item is selected display the contextual menu even if mouse is not above an item
elif len(self.scene().selectedItems()) > 1:
self._showDeviceContextualMenu(event.globalPos())
#elif item and isinstance(item, NodeItem) and self._adding_link:
# self._userNodeLinking(event, item)
else:
super().contextMenuEvent(event)
def mouseReleaseEvent(self, event):
"""
Handles all mouse release events.
@@ -547,7 +582,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
delta = event.angleDelta()
if delta is not None and delta.x() == 0:
# CTRL is pressed then use the mouse wheel to zoom in or out.
self.scaleView(pow(2.0, delta.y() / 240.0))
self.scaleView(pow(2.0, (delta.y()/2) / 240.0))
self._topology.project().setZoom(round(self.transform().m11() * 100))
self._topology.project().update()
else:
@@ -562,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):
"""
@@ -607,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():
@@ -627,7 +662,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
if not self._adding_link:
if isinstance(item, NodeItem) and item.node().initialized():
item.setSelected(True)
if item.node().status() == Node.stopped or item.node().isAlwaysOn():
if item.node().status() == Node.stopped or item.node().consoleType() == "none" or item.node().consoleType() is None:
self.configureSlot()
return
else:
@@ -680,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()
@@ -691,8 +727,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
integer, ok = QtWidgets.QInputDialog.getInt(self, "Nodes", "Number of nodes:", 2, 1, 100, 1)
if ok:
for node_number in range(integer):
x = event.pos().x() - (150 / 2) + (node_number % max_nodes_per_line) * offset
y = event.pos().y() - (70 / 2) + (node_number // max_nodes_per_line) * offset
x = event.pos().x() - (150 // 2) + (node_number % max_nodes_per_line) * offset
y = event.pos().y() - (70 // 2) + (node_number // max_nodes_per_line) * offset
if self.createNodeFromTemplateId(template_id, QtCore.QPoint(x, y)) is False:
event.ignore()
break
@@ -724,6 +760,11 @@ class GraphicsView(QtWidgets.QGraphicsView):
self.populateDeviceContextualMenu(menu)
menu.exec_(pos)
menu.clear()
# Make sure to deselect all items.
# This is to prevent a bug on Windows
# see https://github.com/GNS3/gns3-gui/issues/2986
for it in self.scene().items():
it.setSelected(False)
def populateDeviceContextualMenu(self, menu):
"""
@@ -826,19 +867,19 @@ class GraphicsView(QtWidgets.QGraphicsView):
bring_to_front_action.triggered.connect(self.bringToFrontSlot)
menu.addAction(bring_to_front_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "configFiles"), items)):
if True in list(map(lambda item: isinstance(item, NodeItem) and bool(item.node().configFiles()), items)):
import_config_action = QtWidgets.QAction("Import config", menu)
import_config_action.setIcon(get_icon("import.svg"))
import_config_action.triggered.connect(self.importConfigActionSlot)
menu.addAction(import_config_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "configFiles"), items)):
if True in list(map(lambda item: isinstance(item, NodeItem) and bool(item.node().configFiles()), items)):
export_config_action = QtWidgets.QAction("Export config", menu)
export_config_action.setIcon(get_icon("export.svg"))
export_config_action.triggered.connect(self.exportConfigActionSlot)
menu.addAction(export_config_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "configFiles"), items)):
if True in list(map(lambda item: isinstance(item, NodeItem) and bool(item.node().configTextFiles()), items)):
export_config_action = QtWidgets.QAction("Edit config", menu)
export_config_action.setIcon(get_icon("edit.svg"))
export_config_action.triggered.connect(self.editConfigActionSlot)
@@ -988,6 +1029,9 @@ class GraphicsView(QtWidgets.QGraphicsView):
if isinstance(item, NodeItem) and item.node().initialized():
new_hostname, ok = QtWidgets.QInputDialog.getText(self, "Change hostname", "Hostname:", QtWidgets.QLineEdit.Normal, item.node().name())
if ok:
if not new_hostname.strip():
QtWidgets.QMessageBox.critical(self, "Change hostname", "Hostname cannot be blank")
continue
if hasattr(item.node(), "validateHostname"):
if not item.node().validateHostname(new_hostname):
QtWidgets.QMessageBox.critical(self, "Change hostname", "Invalid name detected for this node: {}".format(new_hostname))
@@ -1054,8 +1098,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
# TightVNC has lack support of IPv6 host at this moment
if "vncviewer" in node.consoleCommand() and ":" in node.consoleHost():
QtWidgets.QMessageBox.warning(
self, "TightVNC", "TightVNC (vncviewer) may not start because of lack of IPv6 support.")
QtWidgets.QMessageBox.warning(self, "TightVNC", "TightVNC (vncviewer) may not start because of lack of IPv6 support.")
try:
node.openConsole(aux=aux)
@@ -1096,11 +1139,21 @@ class GraphicsView(QtWidgets.QGraphicsView):
def consoleFromAllItems(self):
"""
Console from all scene items, except builtin devices.
Console from all scene items with console type different than "none"
"""
items = [item for item in self.scene().items()
if not (isinstance(item, NodeItem) and isinstance(item.node().module(), Builtin))]
if isinstance(item, NodeItem) and item.node().consoleType() != "none"]
nb_items = len(items)
if nb_items > 10:
proceed = QtWidgets.QMessageBox.question(self,
"Console to all nodes",
"You are about to open console windows to {} nodes. Are you sure?".format(nb_items),
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if proceed == QtWidgets.QMessageBox.No:
return
self.consoleFromItems(items)
def consoleActionSlot(self):
@@ -1116,24 +1169,20 @@ class GraphicsView(QtWidgets.QGraphicsView):
Allow user to use a custom console for this VM
"""
current_cmd = None
console_type = "telnet"
for item in self.scene().selectedItems():
if isinstance(item, NodeItem) and item.node().console() is not None and item.node().initialized() and item.node().status() == Node.started:
if isinstance(item, NodeItem) and item.node().console() is not None and item.node().initialized():
if item.node().consoleType() not in ("telnet", "serial", "vnc", "spice", "spice+agent"):
continue
current_cmd = item.node().consoleCommand()
console_type = item.node().consoleType()
(ok, cmd) = ConsoleCommandDialog.getCommand(self, console_type=console_type, current=current_cmd)
if ok:
for item in self.scene().selectedItems():
if isinstance(item, NodeItem) and item.node().console() is not None and item.node().initialized() and item.node().status() == Node.started:
node = item.node()
if node.consoleType() not in ("telnet", "serial", "vnc", "spice", "spice+agent"):
continue
(ok, cmd) = ConsoleCommandDialog.getCommand(self, console_type=console_type, current=current_cmd)
if ok:
try:
node.openConsole(command=cmd)
if item.node().status() != Node.started:
QtWidgets.QMessageBox.warning(self, "Console", "This node must be started before a console can be opened")
continue
item.node().openConsole(command=cmd)
except (OSError, ValueError) as e:
QtWidgets.QMessageBox.critical(self, "Console", "Cannot start console application: {}".format(e))
@@ -1183,7 +1232,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
items = []
for item in self.scene().selectedItems():
if isinstance(item, NodeItem) and hasattr(item.node(), "configFiles") and item.node().initialized():
if isinstance(item, NodeItem) and item.node().configFiles() and item.node().initialized():
items.append(item)
if not items:
@@ -1199,12 +1248,12 @@ class GraphicsView(QtWidgets.QGraphicsView):
path, _ = QtWidgets.QFileDialog.getOpenFileName(self,
"Import {}".format(os.path.basename(config_file)),
self._import_config_dir,
self._import_config_directory,
"All files (*.*);;Config files (*.cfg)",
"Config files (*.cfg)")
if not path:
continue
self._import_config_dir = os.path.dirname(path)
self._import_config_directory = os.path.dirname(path)
item.node().importFile(config_file, path)
def editConfigActionSlot(self):
@@ -1214,17 +1263,17 @@ class GraphicsView(QtWidgets.QGraphicsView):
items = []
for item in self.scene().selectedItems():
if isinstance(item, NodeItem) and hasattr(item.node(), "configFiles") and item.node().initialized():
if isinstance(item, NodeItem) and item.node().configTextFiles() and item.node().initialized():
items.append(item)
if not items:
return
for item in items:
if len(item.node().configFiles()) == 1:
config_file = item.node().configFiles()[0]
if len(item.node().configTextFiles()) == 1:
config_file = item.node().configTextFiles()[0]
else:
config_file, ok = QtWidgets.QInputDialog.getItem(self, "Edit file", "File to edit?", item.node().configFiles(), 0, False)
config_file, ok = QtWidgets.QInputDialog.getItem(self, "Edit file", "File to edit?", item.node().configTextFiles(), 0, False)
if not ok:
continue
dialog = FileEditorDialog(item.node(), config_file, parent=self)
@@ -1239,7 +1288,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
items = []
for item in self.scene().selectedItems():
if isinstance(item, NodeItem) and hasattr(item.node(), "configFiles") and item.node().initialized():
if isinstance(item, NodeItem) and item.node().configFiles() and item.node().initialized():
items.append(item)
if not items:
@@ -1366,7 +1415,15 @@ class GraphicsView(QtWidgets.QGraphicsView):
type = "rect"
else:
type = "image"
self.createDrawingItem(type, item.pos().x() + 20, item.pos().y() + 20, item.zValue(), rotation=item.rotation(), svg=item.toSvg())
self.createDrawingItem(
type,
int(item.pos().x()) + 20,
int(item.pos().y()) + 20,
item.zValue(),
rotation=item.rotation(),
svg=item.toSvg()
)
elif isinstance(item, NodeItem):
item.node().duplicate(item.pos().x() + 20, item.pos().y() + 20, item.zValue())
@@ -1485,7 +1542,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
"""
for item in self.scene().selectedItems():
if not isinstance(item, LinkItem) and not isinstance(item, LabelItem):
if not isinstance(item, LinkItem) and not isinstance(item, LabelItem) and not isinstance(item, SvgIconItem):
if item.locked() is True:
item.setLocked(False)
else:
@@ -1503,7 +1560,14 @@ class GraphicsView(QtWidgets.QGraphicsView):
selected_nodes = []
for item in self.scene().selectedItems():
if isinstance(item, NodeItem):
selected_nodes.append(item.node())
node = item.node()
if node.locked():
QtWidgets.QMessageBox.critical(self, "Delete", "Cannot delete node '{}' because it is locked".format(node.name()))
return
selected_nodes.append(node)
if isinstance(item, DrawingItem) and item.locked():
QtWidgets.QMessageBox.critical(self, "Delete", "Cannot delete drawing because it is locked")
return
if selected_nodes:
if len(selected_nodes) > 1:
question = "Do you want to permanently delete these {} nodes?".format(len(selected_nodes))
@@ -1598,10 +1662,12 @@ class GraphicsView(QtWidgets.QGraphicsView):
def drawBackground(self, painter, rect):
super().drawBackground(painter, rect)
if self._main_window.uiShowGridAction.isChecked():
grids = [(self.drawingGridSize(),QtGui.QColor(208, 208, 208)),
(self.nodeGridSize(),QtGui.QColor(190, 190, 190))]
grids = [(self.drawingGridSize(), QtGui.QColor(208, 208, 208)),
(self.nodeGridSize(), QtGui.QColor(190, 190, 190))]
painter.save()
for (grid,colour) in grids:
for (grid, colour) in grids:
if not grid:
continue
painter.setPen(QtGui.QPen(colour))
left = int(rect.left()) - (int(rect.left()) % grid)
@@ -1609,11 +1675,11 @@ class GraphicsView(QtWidgets.QGraphicsView):
x = left
while x < rect.right():
painter.drawLine(x, rect.top(), x, rect.bottom())
painter.drawLine(x, int(rect.top()), x, int(rect.bottom()))
x += grid
y = top
while y < rect.bottom():
painter.drawLine(rect.left(), y, rect.right(), y)
painter.drawLine(int(rect.left()), y, int(rect.right()), y)
y += grid
painter.restore()

View File

@@ -18,18 +18,15 @@
from .qt import sip
import json
import copy
import http
import uuid
import pathlib
import base64
import datetime
import ipaddress
import urllib.request
import urllib.parse
from .version import __version__, __version_info__
from .qt import QtCore, QtNetwork, qpartial, sip_is_deleted
from .qt import QtCore, QtNetwork, QtWidgets, qpartial, sip_is_deleted
from .utils import parse_version
import logging
@@ -79,6 +76,18 @@ class HTTPClient(QtCore.QObject):
self._shutdown = False # Shutdown in progress
self._accept_insecure_certificate = settings.get("accept_insecure_certificate", None)
# Add custom CA
# ssl_config = QtNetwork.QSslConfiguration.defaultConfiguration()
# if ssl_config.addCaCertificates("/path/to/rootCA.crt"):
# log.debug("CA certificate added")
# QtNetwork.QSslConfiguration.setDefaultConfiguration(ssl_config)
if self._protocol == "https":
if not QtNetwork.QSslSocket.supportsSsl():
log.error("SSL is not supported")
else:
log.debug(f"SSL is supported, version: {QtNetwork.QSslSocket().sslLibraryBuildVersionString()}")
# In order to detect computer hibernation we detect the date of the last
# query and disconnect if time is too long between two query
self._last_query_timestamp = None
@@ -93,6 +102,12 @@ class HTTPClient(QtCore.QObject):
# List of query waiting for the connection
self._query_waiting_connections = []
# To catch SSL errors
self._network_manager.sslErrors.connect(self._sslErrorsSlot)
# Store SSL error exceptions
self._ssl_exceptions = {}
def setMaxTimeDifferenceBetweenQueries(self, value):
self._max_time_difference_between_queries = value
@@ -207,16 +222,16 @@ class HTTPClient(QtCore.QObject):
Called when a query upload progress
"""
if not sip_is_deleted(HTTPClient._progress_callback):
HTTPClient._progress_callback.progress_signal.emit(query_id, str(sent), str(total))
HTTPClient._progress_callback.progress_signal.emit(query_id, str(abs(sent)), str(abs(total)))
def _notify_progress_download(self, query_id, sent, total):
"""
Called when a query download progress
"""
if not sip_is_deleted(HTTPClient._progress_callback):
# abs() for maxium because sometimes the system send negative
# abs() for maximum because sometimes the system send negative
# values
HTTPClient._progress_callback.progress_signal.emit(query_id, str(sent), str(abs(total)))
HTTPClient._progress_callback.progress_signal.emit(query_id, str(abs(sent)), str(abs(total)))
@classmethod
def setProgressCallback(cls, progress_callback):
@@ -389,17 +404,12 @@ class HTTPClient(QtCore.QObject):
self._query_waiting_connections = []
return
if params["version"].split("-")[0] != __version__.split("-")[0]:
if params["version"].split("+")[0] != __version__.split("+")[0]:
msg = "Client version {} is not the same as server (controller) version {}".format(__version__, params["version"])
# Stable release
if __version_info__[3] == 0:
log.error(msg)
for request, callback in self._query_waiting_connections:
if callback is not None:
callback({"message": msg}, error=True, server=server)
return
# We don't allow different major version to interact even with dev build
elif parse_version(__version__)[:2] != parse_version(params["version"])[:2]:
# We don't allow different versions to interact even with dev build
# (excepting post release corrections e.g 2.2.32.1, occassionally done when fixing a packaging problem)
# TODO: we should probably follow this standard starting with v3.0: https://semver.org/
if parse_version(__version__)[:3] != parse_version(params["version"])[:3]:
log.error(msg)
for request, callback in self._query_waiting_connections:
if callback is not None:
@@ -470,7 +480,16 @@ class HTTPClient(QtCore.QObject):
"""
host = self._getHostForQuery()
request = websocket.request()
request.setUrl(QtCore.QUrl("ws://{host}:{port}{prefix}{path}".format(host=host, port=self._port, path=path, prefix=prefix)))
ws_protocol = "ws"
if self._protocol == "https":
ws_protocol = "wss"
ws_url = "{protocol}://{host}:{port}{prefix}{path}".format(protocol=ws_protocol,
host=host,
port=self._port,
path=path,
prefix=prefix)
log.debug("Connecting to WebSocket endpoint: {}".format(ws_url))
request.setUrl(QtCore.QUrl(ws_url))
self._addAuth(request)
websocket.open(request)
return websocket
@@ -530,14 +549,13 @@ class HTTPClient(QtCore.QObject):
query_string = self._paramsToQueryString(params)
log.debug("{method} {protocol}://{host}:{port}{prefix}{path} {body}{query_string}".format(method=method, protocol=self._protocol, host=host, port=self._port, path=path, body=body, prefix=prefix, query_string=query_string))
url = QtCore.QUrl("{protocol}://{host}:{port}{prefix}{path}{query_string}".format(protocol=self._protocol, host=host, port=self._port, path=path, prefix=prefix, query_string=query_string))
if self._user:
url = QtCore.QUrl("{protocol}://{user}@{host}:{port}{prefix}{path}{query_string}".format(protocol=self._protocol, user=self._user, host=host, port=self._port, path=path, prefix=prefix, query_string=query_string))
else:
url = QtCore.QUrl("{protocol}://{host}:{port}{prefix}{path}{query_string}".format(protocol=self._protocol, host=host, port=self._port, path=path, prefix=prefix, query_string=query_string))
url.setUserName(self._user)
request = self._request(url)
request = self._addAuth(request)
request.setRawHeader(b"User-Agent", "GNS3 QT Client v{version}".format(version=__version__).encode())
# By default QT doesn't support GET with body even if it's in the RFC that's why we need to use sendCustomRequest
@@ -725,47 +743,103 @@ class HTTPClient(QtCore.QObject):
e = HttpBadRequest(body)
raise e
def getSynchronous(self, endpoint, timeout=2):
def getSynchronous(self, method, endpoint, prefix="/v2", timeout=5):
"""
Synchronous check if a server is running
:returns: Tuple (Status code, json of anwser). Status 0 is a non HTTP error
:returns: Tuple (Status code, json of answer). Status 0 is a non HTTP error
"""
try:
url = "{protocol}://{host}:{port}/v2/{endpoint}".format(protocol=self._protocol, host=self._host, port=self._port, endpoint=endpoint)
if self._user is not None and len(self._user) > 0:
log.debug("Synchronous get {} with user '{}'".format(url, self._user))
auth_handler = urllib.request.HTTPBasicAuthHandler()
auth_handler.add_password(realm="GNS3 server",
uri=url,
user=self._user,
passwd=self._password)
opener = urllib.request.build_opener(auth_handler)
urllib.request.install_opener(opener)
else:
log.debug("Synchronous get {} (no authentication)".format(url))
response = urllib.request.urlopen(url, timeout=timeout)
content_type = response.getheader("CONTENT-TYPE")
if response.status == 200:
if content_type == "application/json":
content = response.read()
host = self._getHostForQuery()
log.debug("{method} {protocol}://{host}:{port}{prefix}{endpoint}".format(method=method, protocol=self._protocol, host=host, port=self._port, prefix=prefix, endpoint=endpoint))
url = QtCore.QUrl("{protocol}://{host}:{port}{prefix}{endpoint}".format(protocol=self._protocol, host=host, port=self._port, prefix=prefix, endpoint=endpoint))
if self._user:
url.setUserName(self._user)
request = self._request(url)
request = self._addAuth(request)
request.setRawHeader(b"User-Agent", "GNS3 QT Client v{version}".format(version=__version__).encode())
try:
response = self._network_manager.sendCustomRequest(request, method.encode())
except SystemError as e:
log.error("Can't send query: {}".format(str(e)))
return
loop = QtCore.QEventLoop()
response.finished.connect(loop.quit)
if timeout is not None:
QtCore.QTimer.singleShot(timeout * 1000, qpartial(self._timeoutSlot, response, timeout))
if not loop.isRunning():
loop.exec_()
status = response.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)
if response.error() != QtNetwork.QNetworkReply.NoError:
log.debug("Error while connecting to local server {}".format(response.errorString()))
else:
content_type = response.header(QtNetwork.QNetworkRequest.ContentTypeHeader)
if status == 200 and content_type == "application/json":
content = bytes(response.readAll())
try:
json_data = json.loads(content.decode("utf-8"))
return response.status, json_data
else:
return response.status, None
except http.client.InvalidURL as e:
log.warning("Invalid local server url: {}".format(e))
return 0, None
except urllib.error.URLError:
# Connection refused. It's a normal behavior if server is not started
return 0, None
except urllib.error.HTTPError as e:
log.debug("Error during get on {}:{}: {}".format(self.host(), self.port(), e))
return e.code, None
except (OSError, http.client.BadStatusLine, ValueError) as e:
log.debug("Error during get on {}:{}: {}".format(self.host(), self.port(), e))
return 0, None
except (UnicodeEncodeError, ValueError) as e:
log.warning("Could not read JSON data returned from {}: {}".format(url, e))
else:
return status, json_data
return status, None
def _sslErrorsSlot(self, response, ssl_errors):
self.handleSslError(response, ssl_errors)
def handleSslError(self, response, ssl_errors):
if self._accept_insecure_certificate:
response.ignoreSslErrors()
return
url = response.request().url()
host_port_key = f"{url.host()}:{url.port()}"
# get the certificate digest
ssl_config = response.sslConfiguration()
peer_cert = ssl_config.peerCertificate()
digest = peer_cert.digest()
if host_port_key in self._ssl_exceptions:
if self._ssl_exceptions[host_port_key] == digest:
response.ignoreSslErrors()
return
from gns3.main_window import MainWindow
main_window = MainWindow.instance()
msgbox = QtWidgets.QMessageBox(main_window)
msgbox.setWindowTitle("SSL error detected")
msgbox.setText(f"This server could not prove that it is {url.host()}:{url.port()}. Please carefully examine the certificate to make sure the server can be trusted.")
msgbox.setInformativeText(f"{ssl_errors[0].errorString()}")
msgbox.setDetailedText(peer_cert.toText())
msgbox.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
connect_button = QtWidgets.QPushButton(f"&Connect to {url.host()}:{url.port()}", msgbox)
msgbox.addButton(connect_button, QtWidgets.QMessageBox.YesRole)
abort_button = QtWidgets.QPushButton("&Abort", msgbox)
msgbox.addButton(abort_button, QtWidgets.QMessageBox.RejectRole)
msgbox.setDefaultButton(abort_button)
msgbox.setIcon(QtWidgets.QMessageBox.Critical)
msgbox.exec_()
if msgbox.clickedButton() == connect_button:
self._ssl_exceptions[host_port_key] = digest
response.ignoreSslErrors()
else:
for error in ssl_errors:
log.error(f"SSL error detected: {error.errorString()}")
main_window.close()
@classmethod
def fromUrl(cls, url, network_manager=None, base_settings=None):

View File

@@ -176,7 +176,7 @@ class ImageManager:
if node_type == 'DYNAMIPS':
return os.path.join(self.getDirectory(), 'IOS')
else:
return os.path.join(self.getDirectory(), node_type)
return os.path.join(self.getDirectory(), node_type.upper())
@staticmethod
def instance():

View File

@@ -15,6 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import pathlib
import urllib.parse
@@ -37,6 +38,9 @@ class ImageUploadManager(object):
self._controller = controller
def upload(self):
if not os.path.exists(self._image.path):
log.error("Image '{}' could not be found".format(self._image.path))
return
if self._directFileUpload:
# first obtain endpoint and know when target request
self._controller.getEndpoint(self._getComputePath(), self._compute_id, self._onLoadEndpointCallback, showProgress=False)
@@ -71,19 +75,16 @@ class ImageUploadManager(object):
self._callback(result, error, **kwargs)
def _fileUploadToCompute(self, endpoint):
log.info("Uploading file to compute: {}".format(endpoint))
log.debug("Uploading image '{}' to compute".format(self._image.path))
parse_results = urllib.parse.urlparse(endpoint)
network_manager = self._controller.getHttpClient().getNetworkManager()
client = HTTPClient.fromUrl(endpoint, network_manager=network_manager)
# We don't retry connection as in case of fail we try direct file upload
client.setMaxRetryConnection(0)
client.createHTTPQuery(
'POST', parse_results.path, self._checkIfSuccessfulCallback, body=pathlib.Path(self._image.path),
progressText="Uploading {}".format(self._image.filename), timeout=None, prefix="")
client.createHTTPQuery('POST', parse_results.path, self._checkIfSuccessfulCallback, body=pathlib.Path(self._image.path),
context={"image_path": self._image.path}, progressText="Uploading {}".format(self._image.filename), timeout=None, prefix="")
def _fileUploadToController(self):
log.info("Uploading file to controller: {}".format(self._getComputePath()))
self._controller.postCompute(
self._getComputePath(), self._compute_id, self._callback, body=pathlib.Path(self._image.path),
progressText="Uploading {}".format(self._image.filename), timeout=None)
log.debug("Uploading image '{}' to controller".format(self._image.path))
self._controller.postCompute(self._getComputePath(), self._compute_id, self._callback, body=pathlib.Path(self._image.path),
context={"image_path": self._image.path}, progressText="Uploading {}".format(self._image.filename), timeout=None)

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,
@@ -212,14 +225,14 @@ class DrawingItem:
self._project.delete("/drawings/" + self._id, None, body=self.__json__())
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemPositionHasChanged 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
tmp_x = (grid_size * round((self.x() + mid_x) / grid_size)) - mid_x
value.setX((grid_size * round((value.x() + mid_x) / grid_size)) - mid_x)
mid_y = self.boundingRect().height() / 2
tmp_y = (grid_size * round((self.y() + mid_y) / grid_size)) - mid_y
if tmp_x != self.x() and tmp_y != self.y():
self.setPos(tmp_x, tmp_y)
value.setY((grid_size * round((value.y()+mid_y)/grid_size)) - mid_y)
if change == QtWidgets.QGraphicsItem.ItemSelectedChange:
if not value:
@@ -247,7 +260,7 @@ class DrawingItem:
center = self.mapFromItem(self, brect.width() / 2.0, brect.height() / 2.0)
painter.setBrush(QtCore.Qt.red)
painter.setPen(QtCore.Qt.red)
painter.drawRect((brect.width() / 2.0) - 10, (brect.height() / 2.0) - 10, 20, 20)
painter.drawRect(QtCore.QRectF((brect.width() / 2.0) - 10, (brect.height() / 2.0) - 10, 20, 20))
painter.setPen(QtCore.Qt.black)
zval = str(int(self.zValue()))
painter.drawText(QtCore.QPointF(center.x() - 4, center.y() + 4), zval)

View File

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

View File

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

View File

@@ -25,6 +25,7 @@ from ..qt import QtCore, QtGui, QtWidgets, QtSvg, qslot, sip_is_deleted
from ..packet_capture import PacketCapture
from ..dialogs.filter_dialog import FilterDialog
from ..dialogs.style_editor_dialog_link import StyleEditorDialogLink
from ..utils.get_icon import get_icon
@@ -137,6 +138,21 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
def _suspendActionSlot(self, *args):
self._link.toggleSuspend()
@qslot
def _styleActionSlot(self, *args):
style_dialog = StyleEditorDialogLink(self, self._main_window)
style_dialog.show()
style_dialog.exec_()
def setLinkStyle(self, link_style):
self._link._link_style["color"] = link_style["color"]
self._link._link_style["width"] = link_style["width"]
self._link._link_style["type"] = link_style["type"]
# This refers to functions in link.py!
self._link.setLinkStyle(link_style)
self._link.update()
def delete(self):
"""
Delete this link
@@ -266,6 +282,12 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
resume_action.triggered.connect(self._suspendActionSlot)
menu.addAction(resume_action)
# style
style_action = QtWidgets.QAction("Style", menu)
style_action.setIcon(get_icon("node_conception.svg"))
style_action.triggered.connect(self._styleActionSlot)
menu.addAction(style_action)
# delete
delete_action = QtWidgets.QAction("Delete", menu)
delete_action.setIcon(get_icon('delete.svg'))
@@ -280,23 +302,31 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
:param: QGraphicsSceneMouseEvent instance
"""
if event.button() == QtCore.Qt.RightButton:
if self._adding_flag:
# send a escape key to the main window to cancel the link addition
from ..main_window import MainWindow
key = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Escape, QtCore.Qt.NoModifier)
QtWidgets.QApplication.sendEvent(MainWindow.instance(), key)
return
if event.button() == QtCore.Qt.RightButton and self._adding_flag:
# send a escape key to the main window to cancel the link addition
from ..main_window import MainWindow
key = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Escape, QtCore.Qt.NoModifier)
QtWidgets.QApplication.sendEvent(MainWindow.instance(), key)
return
else:
super().mousePressEvent(event)
if not sip_is_deleted(self):
# create the contextual menu
self.setAcceptHoverEvents(False)
menu = QtWidgets.QMenu()
self.populateLinkContextualMenu(menu)
menu.exec_(QtGui.QCursor.pos())
self.setAcceptHoverEvents(True)
self._hovered = False
self.adjust()
def contextMenuEvent(self, event):
"""
Handles all context menu events.
:param event: QContextMenuEvent instance
"""
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.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.
@@ -103,21 +104,13 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
from ..main_window import MainWindow
self._main_window = MainWindow.instance()
if self._main_window.uiSnapToGridAction.isChecked():
self._snapToGrid()
self._settings = self._main_window.uiGraphicsView.settings()
if node.initialized():
self.createdSlot(node.id())
def _snapToGrid(self):
grid_size = self._main_window.uiGraphicsView.nodeGridSize()
mid_x = self.boundingRect().width() / 2
x = (grid_size * round((self.x() + mid_x) / grid_size)) - mid_x
mid_y = self.boundingRect().height() / 2
y = (grid_size * round((self.y() + mid_y) / grid_size)) - mid_y
self.setPos(x, y)
if self._main_window.uiSnapToGridAction.isChecked():
self.setPos(QtCore.QPointF(self._node.x() + 0.1, self._node.y()))
def updateNode(self):
"""
@@ -399,7 +392,7 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
else:
self._node_label.setPos(label_data["x"], label_data["y"])
def connectToPort(self, unavailable_ports=[]):
def connectToPort(self, pos, unavailable_ports=[]):
"""
Shows a contextual menu for the user to choose port or auto-select one.
@@ -447,7 +440,10 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
menu.addAction(QtGui.QIcon(':/icons/led_green.svg'), port_object.name())
menu.triggered.connect(self.selectedPortSlot)
menu.exec_(QtGui.QCursor.pos())
# add some delay before showing the menu
# https://github.com/GNS3/gns3-gui/issues/3169
QtCore.QThread.msleep(100)
menu.exec_(pos)
return self._selected_port
def selectedPortSlot(self, action):
@@ -474,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)
@@ -515,7 +512,7 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
center = self.mapFromItem(self, brect.width() / 2.0, brect.height() / 2.0)
painter.setBrush(QtCore.Qt.red)
painter.setPen(QtCore.Qt.red)
painter.drawRect((brect.width() / 2.0) - 10, (brect.height() / 2.0) - 10, 20, 20)
painter.drawRect(QtCore.QRectF((brect.width() / 2.0) - 10, (brect.height() / 2.0) - 10, 20, 20))
painter.setPen(QtCore.Qt.black)
if self.show_layer:
text = str(int(self.zValue())) # Z value
@@ -536,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

@@ -50,10 +50,16 @@ class SerialLinkItem(LinkItem):
LinkItem.adjust(self)
if self._hovered:
self.setPen(QtGui.QPen(QtCore.Qt.red, self._pen_width + 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
else:
self.setPen(QtGui.QPen(QtCore.Qt.darkRed, self._pen_width, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
try:
if self._hovered:
self.setPen(QtGui.QPen(QtCore.Qt.red, self._link._link_style["width"] + 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
else:
self.setPen(QtGui.QPen(QtGui.QColor(self._link._link_style["color"]), self._link._link_style["width"], self._link._link_style["type"], QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
except:
if self._hovered:
self.setPen(QtGui.QPen(QtCore.Qt.red, self._pen_width + 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
else:
self.setPen(QtGui.QPen(QtCore.Qt.darkRed, self._pen_width, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
# get source to destination angle
vector_angle = math.atan2(self.dy, self.dx)
@@ -136,11 +142,12 @@ class SerialLinkItem(LinkItem):
self._source_port.setLabel(source_port_label)
if self._draw_port_labels:
source_port_label.setFlag(source_port_label.ItemIsMovable, not self._source_item.locked())
source_port_label.show()
else:
source_port_label.hide()
if self._settings["draw_link_status_points"]:
if self._settings["draw_link_status_points"] and self.pen().style() != QtCore.Qt.NoPen:
painter.drawPoint(self.source_point)
# destination point color
@@ -167,11 +174,12 @@ class SerialLinkItem(LinkItem):
self._destination_port.setLabel(destination_port_label)
if self._draw_port_labels:
destination_port_label.setFlag(destination_port_label.ItemIsMovable, not self._destination_item.locked())
destination_port_label.show()
else:
destination_port_label.hide()
if self._settings["draw_link_status_points"]:
if self._settings["draw_link_status_points"] and self.pen().style() != QtCore.Qt.NoPen:
painter.drawPoint(self.destination_point)
self._drawSymbol()

View File

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

@@ -19,7 +19,6 @@
Manages and stores everything needed for a connection between 2 devices.
"""
import os
import re
from .qt import sip
import uuid
@@ -79,6 +78,8 @@ class Link(QtCore.QObject):
self._deleting = False
self._capture_file_path = None
self._capture_file = None
self._response_stream = None
self._capture_compute_id = None
self._initialized = False
self._filters = {}
self._suspend = False
@@ -89,9 +90,7 @@ class Link(QtCore.QObject):
self._creator = False
self._nodes = []
self._source_node.addLink(self)
self._destination_node.addLink(self)
self._link_style = {}
body = self._prepareParams()
if self._link_id:
@@ -103,30 +102,38 @@ class Link(QtCore.QObject):
Controller.instance().post("/projects/{project_id}/links".format(project_id=source_node.project().id()), self._linkCreatedCallback, body=body)
def _parseResponse(self, result):
self._capturing = result.get("capturing", False)
# If the controller is remote the capture path should be rewrite to something local
self._capturing = result.get("capturing", False)
if self._capturing:
if Controller.instance().isRemote():
if self._capture_file_path is None and result.get("capture_file_path", None) is not None:
self._capture_compute_id = result.get("capture_compute_id", None)
self._capture_file_path = result.get("capture_file_path", None)
if Controller.instance().isRemote() or (self._capture_compute_id and self._capture_compute_id != "local"):
# We need to stream the pcap file content if the controller or compute is remote
if Controller.instance().isRemote() or self._capture_file_path is None:
self._capture_file = QtCore.QTemporaryFile()
self._capture_file.open(QtCore.QFile.WriteOnly)
self._capture_file.setAutoRemove(True)
self._capture_file_path = self._capture_file.fileName()
Controller.instance().get("/projects/{project_id}/links/{link_id}/pcap".format(project_id=self.project().id(),link_id=self._link_id),
None,
showProgress=False,
downloadProgressCallback=self._downloadPcapProgress,
ignoreErrors=True, # If something is wrong avoid disconnect us from server
timeout=None)
else:
self._capture_file_path = result["capture_file_path"]
else:
self._capture_file = QtCore.QFile(self._capture_file_path)
self._capture_file.open(QtCore.QFile.WriteOnly)
self._response_stream = Controller.instance().get("/projects/{project_id}/links/{link_id}/pcap".format(project_id=self.project().id(), link_id=self._link_id),
None,
showProgress=False,
downloadProgressCallback=self._downloadPcapProgress,
ignoreErrors=True, # If something is wrong avoid disconnect us from server
timeout=None)
log.debug("Has successfully started capturing packets on link {} to '{}'".format(self._link_id, self._capture_file_path))
else:
self._response_stream = None
if "nodes" in result:
self._nodes = result["nodes"]
self._updateLabels()
if "filters" in result:
self._filters = result["filters"]
if "link_style" in result:
self._link_style = result["link_style"]
if "suspend" in result:
self._suspend = result["suspend"]
self.updated_link_signal.emit(self._id)
@@ -209,6 +216,7 @@ class Link(QtCore.QObject):
}
],
"filters": self._filters,
"link_style": self._link_style,
"suspend": self._suspend
}
if self._source_port.label():
@@ -351,9 +359,8 @@ class Link(QtCore.QObject):
def _startCaptureCallback(self, result, error=False, **kwargs):
if error:
log.error("Error while starting capture on link: {}".format(result["message"]))
log.error("Error while starting capture on link {}: {}".format(self._link_id, result["message"]))
return
self._parseResponse(result)
def _downloadPcapProgress(self, content, server=None, context={}, **kwargs):
"""
@@ -366,25 +373,27 @@ class Link(QtCore.QObject):
self._capture_file.flush()
def stopCapture(self):
if Controller.instance().isRemote():
if Controller.instance().isRemote() or (self._capture_compute_id and self._capture_compute_id != "local"):
if self._capture_file:
self._capture_file.close()
self._capture_file = None
if self._capture_file_path and os.path.exists(self._capture_file_path):
try:
os.remove(self._capture_file_path)
except OSError as e:
log.error("Cannot remove file {}: {}".format(self._capture_file_path, e))
# if self._capture_file_path and os.path.exists(self._capture_file_path):
# try:
# os.remove(self._capture_file_path)
# except OSError as e:
# log.error("Cannot remove file {}: {}".format(self._capture_file_path, e))
self._capture_file_path = None
Controller.instance().post("/projects/{project_id}/links/{link_id}/stop_capture".format(project_id=self.project().id(),
link_id=self._link_id),
self._stopCaptureCallback)
def _stopCaptureCallback(self, result, error=False, **kwargs):
if error:
log.error("Error while stopping capture on link: {}".format(result["message"]))
log.error("Error while stopping capture on link {}: {}".format(self._link_id, result["message"]))
return
self._parseResponse(result)
log.debug("Has successfully stopped capturing packets on link {}".format(self._link_id))
def get(self, path, callback, **kwargs):
"""
@@ -462,3 +471,9 @@ class Link(QtCore.QObject):
:params filters: List of filters
"""
self._filters = filters
def setLinkStyle(self, link_style):
"""
:params _link_style: Set link style attributes
"""
self._link_style = link_style

View File

@@ -24,8 +24,10 @@ import copy
import psutil
from .qt import QtCore, QtWidgets
from .version import __version__
from .version import __version__, __version_info__
from .utils import parse_version
from .local_server_config import LocalServerConfig
from .settings import LOCAL_SERVER_SETTINGS
import logging
log = logging.getLogger(__name__)
@@ -87,8 +89,25 @@ class LocalConfig(QtCore.QObject):
try:
# create the config file if it doesn't exist
os.makedirs(os.path.dirname(self._config_file), exist_ok=True)
with open(self._config_file, "w", encoding="utf-8") as f:
json.dump({"version": __version__, "type": "settings"}, f)
if sys.platform.startswith("win"):
old_config_path = os.path.join(os.path.expandvars("%APPDATA%"), "GNS3", filename)
else:
old_config_path = os.path.join(os.path.expanduser("~"), ".config", "GNS3", filename)
# TODO: migrate versioned config file from a previous version of GNS3 (for instance 2.2 -> 2.3) + support profiles
if os.path.exists(old_config_path):
# migrate post version 2.2.0 configuration file
shutil.copyfile(old_config_path, self._config_file)
# reset the local server path and ubridge path
settings = LocalServerConfig.instance().loadSettings("Server", LOCAL_SERVER_SETTINGS)
settings["path"] = ""
settings["ubridge_path"] = ""
LocalServerConfig.instance().saveSettings("Server", settings)
else:
# create a new config
with open(self._config_file, "w", encoding="utf-8") as f:
json.dump({"version": __version__, "type": "settings"}, f)
except OSError as e:
log.error("Could not create the config file {}: {}".format(self._config_file, e))
@@ -114,17 +133,18 @@ class LocalConfig(QtCore.QObject):
self._config_file = None
self._resetLoadConfig()
def configDirectory(self):
"""
Get the configuration directory
"""
version = "{}.{}".format(__version_info__[0], __version_info__[1])
if sys.platform.startswith("win"):
appdata = os.path.expandvars("%APPDATA%")
path = os.path.join(appdata, "GNS3")
path = os.path.join(appdata, "GNS3", version)
else:
home = os.path.expanduser("~")
path = os.path.join(home, ".config", "GNS3")
path = os.path.join(home, ".config", "GNS3", version)
if self._profile is not None:
path = os.path.join(path, "profiles", self._profile)
@@ -146,8 +166,9 @@ class LocalConfig(QtCore.QObject):
# In < 1.4 on Mac the config was in a gns3.net directory
# We have move to same location as Linux
if sys.platform.startswith("darwin"):
version = "{}.{}".format(__version_info__[0], __version_info__[1])
old_path = os.path.join(os.path.expanduser("~"), ".config", "gns3.net")
new_path = os.path.join(os.path.expanduser("~"), ".config", "GNS3")
new_path = os.path.join(os.path.expanduser("~"), ".config", "GNS3", version)
if os.path.exists(old_path) and not os.path.exists(new_path):
try:
shutil.copytree(old_path, new_path)
@@ -156,7 +177,7 @@ class LocalConfig(QtCore.QObject):
def _migrateOldConfig(self):
"""
Migrate pre 1.4 config
Migrate config from a previous version.
"""
# Display an error if settings come from a more recent version of GNS3
@@ -165,7 +186,7 @@ class LocalConfig(QtCore.QObject):
if "version" in self._settings:
if parse_version(self._settings["version"])[:2] > parse_version(__version__)[:2]:
app = QtWidgets.QApplication(sys.argv) # We need to create an application because settings are loaded before Qt init
error_message = "Your settings are for version {} of GNS3. You cannot use a previous version of GNS3 without risking losing data. If you want to reset delete the settings in {}".format(self._settings["version"], self.configDirectory())
error_message = "Settings are for version {} of GNS3. It is not possible to use a previous version of GNS3 without risking losing data. Delete the settings in '{}' to start GNS3".format(self._settings["version"], self.configDirectory())
QtWidgets.QMessageBox.critical(False, "Version error", error_message)
# Exit immediately not clean but we want to avoid any side effect that could corrupt the file
QtCore.QTimer.singleShot(0, app.quit)
@@ -174,21 +195,21 @@ class LocalConfig(QtCore.QObject):
if "version" not in self._settings or parse_version(self._settings["version"]) < parse_version("1.4.0alpha1"):
servers = self._settings.get("Servers", {})
servers = self._settings.get("Servers", {})
if "LocalServer" in self._settings:
if "LocalServer" in self._settings:
servers["local_server"] = copy.copy(self._settings["LocalServer"])
# We migrate the server binary for OSX due to the change from py2app to CX freeze
# We migrate the server binary for OSX due to the change from py2app to CX freeze
if servers["local_server"]["path"] == "/Applications/GNS3.app/Contents/Resources/server/Contents/MacOS/gns3server":
servers["local_server"]["path"] = "gns3server"
if "RemoteServers" in self._settings:
if "RemoteServers" in self._settings:
servers["remote_servers"] = copy.copy(self._settings["RemoteServers"])
self._settings["Servers"] = servers
self._settings["Servers"] = servers
if "GUI" in self._settings:
if "GUI" in self._settings:
main_window = self._settings.get("MainWindow", {})
main_window["hide_getting_started_dialog"] = self._settings["GUI"].get("hide_getting_started_dialog", False)
self._settings["MainWindow"] = main_window
@@ -201,7 +222,7 @@ class LocalConfig(QtCore.QObject):
if self._settings["MainWindow"].get("telnet_console_command") not in PRECONFIGURED_TELNET_CONSOLE_COMMANDS.values():
self._settings["MainWindow"]["telnet_console_command"] = DEFAULT_TELNET_CONSOLE_COMMAND
# Migrate 1.X to 2.0
# Migrate 1.X to 2.0
if "version" not in self._settings or parse_version(self._settings["version"]) < parse_version("2.0.0"):
if "Qemu" in self._settings:
# The internet VM is replaced by the nat Node
@@ -212,7 +233,7 @@ class LocalConfig(QtCore.QObject):
vms.append(vm)
self._settings["Qemu"]["vms"] = vms
# Starting with 2.0.0dev5 IOU licence is stored in the settings
# Starting with 2.0.0dev5 IOU licence is stored in the settings
if "version" not in self._settings or parse_version(self._settings["version"]) < parse_version("2.0.0"):
if "IOU" in self._settings and "iourc_path" in self._settings["IOU"] and "iourc_content" not in self._settings["IOU"]:
try:
@@ -467,7 +488,7 @@ class LocalConfig(QtCore.QObject):
if pid != my_pid:
try:
process = psutil.Process(pid=pid)
ps_name = process.name()
ps_name = process.name().lower()
except (OSError, psutil.NoSuchProcess, psutil.AccessDenied):
pass
else:

View File

@@ -31,12 +31,11 @@ import subprocess
from gns3.qt import QtWidgets, QtCore, qslot
from gns3.settings import LOCAL_SERVER_SETTINGS
from gns3.settings import LOCAL_SERVER_SETTINGS, DEFAULT_LOCAL_SERVER_HOST
from gns3.local_config import LocalConfig
from gns3.local_server_config import LocalServerConfig
from gns3.utils.wait_for_connection_worker import WaitForConnectionWorker
from gns3.utils.progress_dialog import ProgressDialog
from gns3.utils.http import getSynchronous
from gns3.utils.sudo import sudo
from gns3.http_client import HTTPClient
from gns3.controller import Controller
@@ -131,6 +130,7 @@ class LocalServer(QtCore.QObject):
import win32serviceutil
except ImportError as e:
log.error("Could not check if the {} service is running: {}".format(service_name, e))
return
try:
if win32serviceutil.QueryServiceStatus(service_name, None)[1] != win32service.SERVICE_RUNNING:
@@ -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
@@ -246,6 +250,9 @@ class LocalServer(QtCore.QObject):
"""
Update the local server settings. Keep the key not in new_settings
"""
if "host" in new_settings and new_settings["host"] is None:
new_settings["host"] = DEFAULT_LOCAL_SERVER_HOST
old_settings = copy.copy(self._settings)
if not self._settings:
self._settings = new_settings
@@ -369,13 +376,15 @@ class LocalServer(QtCore.QObject):
self._checkUbridgePermissions()
if sys.platform.startswith('win'):
if not self._checkWindowsService("npf") and not self._checkWindowsService("npcap"):
QtWidgets.QMessageBox.critical(self.parent(), "Error", "The NPF or NPCAP service is not installed, please install Winpcap or Npcap and reboot.")
return False
if sys.platform.startswith("win"):
import pywintypes
try:
if not self._checkWindowsService("npf") and not self._checkWindowsService("npcap"):
log.warning("The NPF or NPCAP service is not installed, please install Winpcap or Npcap and reboot.")
except pywintypes.error as e:
log.warning("Could not check if the NPF or Npcap service is running: {}".format(e.strerror))
self._port = self._settings["port"]
# check the local server path
local_server_path = self.localServerPath()
if not local_server_path:
@@ -473,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
@@ -516,9 +529,7 @@ class LocalServer(QtCore.QObject):
:returns: boolean
"""
status, json_data = getSynchronous(self._settings["protocol"], self._settings["host"], self._port, "version",
timeout=2, user=self._settings["user"], password=self._settings["password"])
status, json_data = HTTPClient(self._settings).getSynchronous("GET", "/version")
if status == 401: # Auth issue that need to be solved later
return True
elif json_data is None:

View File

@@ -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():
"""
@@ -118,6 +108,9 @@ def main():
# an extra argument starting with -psn_. We filter it
if sys.platform.startswith("darwin"):
sys.argv = [a for a in sys.argv if not a.startswith("-psn_")]
if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.15.2"):
# Fixes issue on macOS Big Sur: https://github.com/GNS3/gns3-gui/issues/3037
os.environ["QT_MAC_WANTS_LAYER"] = "1"
parser = argparse.ArgumentParser()
parser.add_argument("project", help="load a GNS3 project (.gns3)", metavar="path", nargs="?")
@@ -132,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
@@ -142,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'))
]
@@ -151,6 +152,7 @@ def main():
if options.project:
os.chdir(frozen_dir)
def exceptionHook(exception, value, tb):
if exception == KeyboardInterrupt:
@@ -182,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))
@@ -217,10 +219,17 @@ def main():
if not options.debug:
try:
# hide the console
# win32console.AllocConsole()
console_window = win32console.GetConsoleWindow()
win32gui.ShowWindow(console_window, win32con.SW_HIDE)
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:
print("warning: could not allocate console: {}".format(e))
log.warning("Could not allocate console: {}".format(e))
local_config = LocalConfig.instance()
@@ -255,8 +264,8 @@ def main():
current_year = datetime.date.today().year
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.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

@@ -42,14 +42,13 @@ from .dialogs.edit_project_dialog import EditProjectDialog
from .dialogs.setup_wizard import SetupWizard
from .settings import GENERAL_SETTINGS
from .items.node_item import NodeItem
from .items.link_item import LinkItem
from .items.link_item import LinkItem, SvgIconItem
from .items.shape_item import ShapeItem
from .items.label_item import LabelItem
from .topology import Topology
from .http_client import HTTPClient
from .progress import Progress
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
@@ -84,6 +83,24 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._settings = {}
self.setupUi(self)
self.setUnifiedTitleAndToolBarOnMac(True)
# These widgets will be disabled when no project is loaded
self.disableWhenNoProjectWidgets = [
self.uiGraphicsView,
self.uiAnnotateMenu,
self.uiAnnotationToolBar,
self.uiControlToolBar,
self.uiControlMenu,
self.uiSaveProjectAsAction,
self.uiExportProjectAction,
self.uiScreenshotAction,
self.uiSnapshotAction,
self.uiEditProjectAction,
self.uiDeleteProjectAction,
self.uiImportExportConfigsAction,
self.uiLockAllAction
]
self._notif_dialog = NotifDialog(self)
# Setup logger
@@ -115,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()))
@@ -177,27 +194,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.setWindowTitle("[*] GNS3")
# This widgets will be disable when you have no project loaded
self.disableWhenNoProjectWidgets = [
self.uiGraphicsView,
self.uiAnnotateMenu,
self.uiAnnotationToolBar,
self.uiControlToolBar,
self.uiControlMenu,
self.uiSaveProjectAsAction,
self.uiExportProjectAction,
self.uiScreenshotAction,
self.uiSnapshotAction,
self.uiEditProjectAction,
self.uiDeleteProjectAction,
self.uiImportExportConfigsAction
]
# This widgets are not enabled if it's a remote controller (no access to the local file system)
self.disableWhenRemoteContollerWidgets = [
# self.uiImportExportConfigsAction
]
# load initial stuff once the event loop isn't busy
self.run_later(0, self.startupLoading)
@@ -237,9 +233,10 @@ 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
self.uiWebInterfaceAction.triggered.connect(self._openLightWebInterfaceActionSlot)
self.uiWebUIAction.triggered.connect(self._openWebInterfaceActionSlot)
# control menu connections
@@ -249,6 +246,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.uiReloadAllAction.triggered.connect(self._reloadAllActionSlot)
self.uiAuxConsoleAllAction.triggered.connect(self._auxConsoleAllActionSlot)
self.uiConsoleAllAction.triggered.connect(self._consoleAllActionSlot)
self.uiResetConsoleAllAction.triggered.connect(self._consoleResetAllActionSlot)
# device menu is contextual and is build on-the-fly
self.uiDeviceMenu.aboutToShow.connect(self._deviceMenuActionSlot)
@@ -322,14 +320,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
self.settings_updated_signal.emit()
def _openLightWebInterfaceActionSlot(self):
if Controller.instance().connected():
QtGui.QDesktopServices.openUrl(QtCore.QUrl(Controller.instance().httpClient().fullUrl()))
def _openWebInterfaceActionSlot(self):
if Controller.instance().connected():
base_url = Controller.instance().httpClient().fullUrl()
webui_url = "{}/static/web-ui/local".format(base_url)
webui_url = "{}/static/web-ui/bundled".format(base_url)
QtGui.QDesktopServices.openUrl(QtCore.QUrl(webui_url))
def _showGridActionSlot(self):
@@ -363,22 +357,38 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
:return: None
"""
for item in self.uiGraphicsView.items():
if not isinstance(item, LinkItem) and not isinstance(item, LabelItem):
if self.uiLockAllAction.isChecked() and not item.locked():
item.setLocked(True)
elif not self.uiLockAllAction.isChecked() and item.locked():
item.setLocked(False)
if item.parentItem() is None:
item.updateNode()
item.update()
if self.uiGraphicsView.isEnabled():
for item in self.uiGraphicsView.items():
if not isinstance(item, LinkItem) and not isinstance(item, LabelItem) and not isinstance(item, SvgIconItem):
if self.uiLockAllAction.isChecked() and not item.locked():
item.setLocked(True)
elif not self.uiLockAllAction.isChecked() and item.locked():
item.setLocked(False)
if item.parentItem() is None:
item.updateNode()
item.update()
def analyticsClient(self):
def _resetGUIState(self):
"""
Return the analytics client
Reset the GUI state.
"""
return self._analytics_client
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.
"""
self.uiTopologySummaryDockWidget.setFloating(False)
self.uiComputeSummaryDockWidget.setFloating(False)
self.uiConsoleDockWidget.setFloating(False)
self.uiNodesDockWidget.setFloating(False)
def _newProjectActionSlot(self):
"""
@@ -392,9 +402,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._project_dialog = ProjectDialog(self)
self._project_dialog.show()
create_new_project = self._project_dialog.exec_()
# Close the device dock so it repopulates. Done in case switching between cloud and local.
self.uiNodesDockWidget.setVisible(False)
self.uiNodesDockWidget.setWindowTitle("")
if create_new_project:
Topology.instance().createLoadProject(self._project_dialog.getProjectSettings())
@@ -436,7 +443,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._newProjectActionSlot()
else:
directory = self._project_dir
if self._project_dir and not os.path.exists(self._project_dir):
if self._project_dir is None or not os.path.exists(self._project_dir):
directory = Topology.instance().projectsDirPath()
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open project", directory,
"All files (*.*);;GNS3 Project (*.gns3);;GNS3 Portable Project (*.gns3project *.gns3p);;NET files (*.net)",
@@ -545,8 +552,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""
Refresh widgets that should be visible or not
"""
for widget in self.disableWhenRemoteContollerWidgets:
widget.setVisible(not Controller.instance().isRemote())
# No projects
if Topology.instance().project() is None:
@@ -815,6 +820,13 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""
Slot called when starting all the nodes.
"""
reply = QtWidgets.QMessageBox.question(self, "Confirm Start All", "Are you sure you want to start all devices?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
return
project = Topology.instance().project()
if project is not None:
project.start_all_nodes()
@@ -824,6 +836,12 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
Slot called when suspending all the nodes.
"""
reply = QtWidgets.QMessageBox.question(self, "Confirm Suspend All", "Are you sure you want to suspend all devices?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
return
project = Topology.instance().project()
if project is not None:
project.suspend_all_nodes()
@@ -833,6 +851,12 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
Slot called when stopping all the nodes.
"""
reply = QtWidgets.QMessageBox.question(self, "Confirm Stop All", "Are you sure you want to stop all devices?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
return
project = Topology.instance().project()
if project is not None:
project.stop_all_nodes()
@@ -842,10 +866,25 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
Slot called when reloading all the nodes.
"""
reply = QtWidgets.QMessageBox.question(self, "Confirm Reload All", "Are you sure you want to reload all devices?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
return
project = Topology.instance().project()
if project is not None:
project.reload_all_nodes()
def _consoleResetAllActionSlot(self):
"""
Slot called when reset all console connections.
"""
project = Topology.instance().project()
if project is not None:
project.reset_console_all_nodes()
def _deviceMenuActionSlot(self):
"""
Slot to contextually show the device menu.
@@ -916,7 +955,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
Slot to launch a browser pointing to the documentation page.
"""
QtGui.QDesktopServices.openUrl(QtCore.QUrl("https://gns3.com/support/docs"))
QtGui.QDesktopServices.openUrl(QtCore.QUrl("https://docs.gns3.com/"))
def _checkForUpdateActionSlot(self, silent=False):
"""
@@ -1060,11 +1099,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
with Progress.instance().context(min_duration=0):
dialog = PreferencesDialog(self)
dialog.restoreGeometry(QtCore.QByteArray().fromBase64(self._settings["preferences_dialog_geometry"].encode()))
#dialog.restoreGeometry(QtCore.QByteArray().fromBase64(self._settings["preferences_dialog_geometry"].encode()))
dialog.show()
dialog.exec_()
self._settings["preferences_dialog_geometry"] = bytes(dialog.saveGeometry().toBase64()).decode()
self.setSettings(self._settings)
#self._settings["preferences_dialog_geometry"] = bytes(dialog.saveGeometry().toBase64()).decode()
#self.setSettings(self._settings)
def _editReadmeActionSlot(self):
"""
@@ -1088,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)
@@ -1110,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()
@@ -1126,10 +1167,15 @@ 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()
server = LocalServer.instance()
server.stopLocalServer(wait=True)
@@ -1189,8 +1235,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
# restore debug level
if self._settings["debug_level"]:
print("Activating debugging (use command 'debug 0' to deactivate)")
root = logging.getLogger()
root.addHandler(logging.StreamHandler(sys.stdout))
root.setLevel(logging.DEBUG)
# restore the style
self._setStyle(self._settings.get("style"))
@@ -1198,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

@@ -38,7 +38,7 @@ class EthernetSwitch(Node):
# this is an always-on node
self.setStatus(Node.started)
self._always_on = True
self.settings().update({"ports_mapping": [], "console_type": "telnet"})
self.settings().update({"ports_mapping": [], "console_type": "none"})
def info(self):
"""

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

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

View File

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

View File

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

View File

@@ -59,7 +59,7 @@ ETHERNET_SWITCH_SETTINGS = {
"default_name_format": "Switch{0}",
"symbol": ":/symbols/ethernet_switch.svg",
"category": Node.switches,
"console_type": "telnet",
"console_type": "none",
"ports_mapping": [],
"node_type": "ethernet_switch"
}

View File

@@ -51,7 +51,8 @@ class DockerVM(Node):
"console_resolution": DOCKER_CONTAINER_SETTINGS["console_resolution"],
"console_http_port": DOCKER_CONTAINER_SETTINGS["console_http_port"],
"console_http_path": DOCKER_CONTAINER_SETTINGS["console_http_path"],
"extra_hosts": DOCKER_CONTAINER_SETTINGS["extra_hosts"]}
"extra_hosts": DOCKER_CONTAINER_SETTINGS["extra_hosts"],
"extra_volumes": DOCKER_CONTAINER_SETTINGS["extra_volumes"]}
self.settings().update(docker_vm_settings)
@@ -115,6 +116,18 @@ class DockerVM(Node):
from .pages.docker_vm_configuration_page import DockerVMConfigurationPage
return DockerVMConfigurationPage
@staticmethod
def validateHostname(hostname):
"""
Checks if the hostname is valid.
:param hostname: hostname to check
:returns: boolean
"""
return DockerVM.isValidRfc1123Hostname(hostname)
@staticmethod
def defaultSymbol():
"""

View File

@@ -103,7 +103,8 @@ class DockerVMConfigurationPage(QtWidgets.QWidget, Ui_dockerVMConfigPageWidget):
self.uiConsoleResolutionComboBox.setCurrentIndex(self.uiConsoleResolutionComboBox.findText(settings["console_resolution"]))
self.uiConsoleHttpPortSpinBox.setValue(settings["console_http_port"])
self.uiHttpConsolePathLineEdit.setText(settings["console_http_path"])
self.uiExtraHostsTextEdit.setText(settings["extra_hosts"])
self.uiExtraHostsTextEdit.setPlainText(settings["extra_hosts"])
self.uiExtraVolumeTextEdit.setPlainText("\n".join(settings["extra_volumes"]))
if not group:
self.uiNameLineEdit.setText(settings["name"])
@@ -175,6 +176,8 @@ class DockerVMConfigurationPage(QtWidgets.QWidget, Ui_dockerVMConfigPageWidget):
settings["console_http_port"] = self.uiConsoleHttpPortSpinBox.value()
settings["console_http_path"] = self.uiHttpConsolePathLineEdit.text()
settings["extra_hosts"] = self.uiExtraHostsTextEdit.toPlainText()
# only tidy input here, validation is performed server side
settings["extra_volumes"] = [ y for x in self.uiExtraVolumeTextEdit.toPlainText().split("\n") for y in [ x.strip() ] if y ]
if not group:
adapters = self.uiAdapterSpinBox.value()

View File

@@ -52,6 +52,7 @@ class DockerVMPreferencesPage(QtWidgets.QWidget, Ui_DockerVMPreferencesPageWidge
self.uiEditDockerVMPushButton.clicked.connect(self._dockerImageEditSlot)
self.uiDeleteDockerVMPushButton.clicked.connect(self._dockerImageDeleteSlot)
self.uiDockerVMsTreeWidget.itemSelectionChanged.connect(self._dockerImageChangedSlot)
self.uiDockerVMsTreeWidget.itemDoubleClicked.connect(self._dockerImageEditSlot)
def _createSectionItem(self, name):
"""
@@ -95,6 +96,9 @@ class DockerVMPreferencesPage(QtWidgets.QWidget, Ui_DockerVMPreferencesPageWidge
if docker_container["extra_hosts"]:
QtWidgets.QTreeWidgetItem(section_item, ["Extra hosts:", str(docker_container["extra_hosts"])])
if docker_container["extra_volumes"]:
QtWidgets.QTreeWidgetItem(section_item, ["Extra volumes:", "\n".join(docker_container["extra_volumes"])])
self.uiDockerVMInfoTreeWidget.expandAll()
self.uiDockerVMInfoTreeWidget.resizeColumnToContents(0)
self.uiDockerVMInfoTreeWidget.resizeColumnToContents(1)

View File

@@ -43,5 +43,6 @@ DOCKER_CONTAINER_SETTINGS = {
"console_http_port": 80,
"console_http_path": "/",
"extra_hosts": "",
"extra_volumes": [],
"node_type": "docker"
}

View File

@@ -175,11 +175,26 @@
</item>
<item row="8" column="1">
<widget class="QComboBox" name="uiConsoleResolutionComboBox">
<item>
<property name="text">
<string>2560x1440</string>
</property>
</item>
<item>
<property name="text">
<string>1920x1080</string>
</property>
</item>
<item>
<property name="text">
<string>1680x1050</string>
</property>
</item>
<item>
<property name="text">
<string>1440x900</string>
</property>
</item>
<item>
<property name="text">
<string>1366x768</string>
@@ -297,9 +312,34 @@ one per line)</string>
</widget>
</item>
<item row="0" column="1">
<widget class="QTextEdit" name="uiExtraHostsTextEdit"/>
<widget class="QPlainTextEdit" name="uiExtraHostsTextEdit">
<property name="placeholderText">
<string>e.g. router:192.168.0.1</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiExtraVolumeLabel">
<property name="text">
<string>Additional directories to
make persistent that are
not included in the image
VOLUMES config. One
directory per line.</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPlainTextEdit" name="uiExtraVolumeTextEdit">
<property name="plainText">
<string/>
</property>
<property name="placeholderText">
<string>e.g. /etc/sysctl.d</string>
</property>
</widget>
</item>
<item row="2" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>

View File

@@ -2,12 +2,15 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/docker/ui/docker_vm_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING! All changes made in this file will be lost!
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_dockerVMConfigPageWidget(object):
def setupUi(self, dockerVMConfigPageWidget):
dockerVMConfigPageWidget.setObjectName("dockerVMConfigPageWidget")
@@ -100,6 +103,9 @@ class Ui_dockerVMConfigPageWidget(object):
self.uiConsoleResolutionComboBox.addItem("")
self.uiConsoleResolutionComboBox.addItem("")
self.uiConsoleResolutionComboBox.addItem("")
self.uiConsoleResolutionComboBox.addItem("")
self.uiConsoleResolutionComboBox.addItem("")
self.uiConsoleResolutionComboBox.addItem("")
self.gridLayout.addWidget(self.uiConsoleResolutionComboBox, 8, 1, 1, 1)
self.label = QtWidgets.QLabel(self.tab)
self.label.setObjectName("label")
@@ -143,11 +149,18 @@ class Ui_dockerVMConfigPageWidget(object):
self.uiExtraHostsLabel.setWordWrap(True)
self.uiExtraHostsLabel.setObjectName("uiExtraHostsLabel")
self.gridLayout_2.addWidget(self.uiExtraHostsLabel, 0, 0, 1, 1)
self.uiExtraHostsTextEdit = QtWidgets.QTextEdit(self.tab_2)
self.uiExtraHostsTextEdit = QtWidgets.QPlainTextEdit(self.tab_2)
self.uiExtraHostsTextEdit.setObjectName("uiExtraHostsTextEdit")
self.gridLayout_2.addWidget(self.uiExtraHostsTextEdit, 0, 1, 1, 1)
self.uiExtraVolumeLabel = QtWidgets.QLabel(self.tab_2)
self.uiExtraVolumeLabel.setObjectName("uiExtraVolumeLabel")
self.gridLayout_2.addWidget(self.uiExtraVolumeLabel, 1, 0, 1, 1)
self.uiExtraVolumeTextEdit = QtWidgets.QPlainTextEdit(self.tab_2)
self.uiExtraVolumeTextEdit.setPlainText("")
self.uiExtraVolumeTextEdit.setObjectName("uiExtraVolumeTextEdit")
self.gridLayout_2.addWidget(self.uiExtraVolumeTextEdit, 1, 1, 1, 1)
spacerItem = QtWidgets.QSpacerItem(20, 388, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_2.addItem(spacerItem, 1, 1, 1, 1)
self.gridLayout_2.addItem(spacerItem, 2, 1, 1, 1)
self.uiTabWidget.addTab(self.tab_2, "")
self.tab_3 = QtWidgets.QWidget()
self.tab_3.setObjectName("tab_3")
@@ -183,13 +196,16 @@ class Ui_dockerVMConfigPageWidget(object):
self.uiConsoleTypeComboBox.setItemText(4, _translate("dockerVMConfigPageWidget", "none"))
self.uiConsoleAutoStartCheckBox.setText(_translate("dockerVMConfigPageWidget", "Auto start console"))
self.uiConsoleResolutionLabel.setText(_translate("dockerVMConfigPageWidget", "VNC console resolution:"))
self.uiConsoleResolutionComboBox.setItemText(0, _translate("dockerVMConfigPageWidget", "1920x1080"))
self.uiConsoleResolutionComboBox.setItemText(1, _translate("dockerVMConfigPageWidget", "1366x768"))
self.uiConsoleResolutionComboBox.setItemText(2, _translate("dockerVMConfigPageWidget", "1280x1024"))
self.uiConsoleResolutionComboBox.setItemText(3, _translate("dockerVMConfigPageWidget", "1280x800"))
self.uiConsoleResolutionComboBox.setItemText(4, _translate("dockerVMConfigPageWidget", "1024x768"))
self.uiConsoleResolutionComboBox.setItemText(5, _translate("dockerVMConfigPageWidget", "800x600"))
self.uiConsoleResolutionComboBox.setItemText(6, _translate("dockerVMConfigPageWidget", "640x480"))
self.uiConsoleResolutionComboBox.setItemText(0, _translate("dockerVMConfigPageWidget", "2560x1440"))
self.uiConsoleResolutionComboBox.setItemText(1, _translate("dockerVMConfigPageWidget", "1920x1080"))
self.uiConsoleResolutionComboBox.setItemText(2, _translate("dockerVMConfigPageWidget", "1680x1050"))
self.uiConsoleResolutionComboBox.setItemText(3, _translate("dockerVMConfigPageWidget", "1440x900"))
self.uiConsoleResolutionComboBox.setItemText(4, _translate("dockerVMConfigPageWidget", "1366x768"))
self.uiConsoleResolutionComboBox.setItemText(5, _translate("dockerVMConfigPageWidget", "1280x1024"))
self.uiConsoleResolutionComboBox.setItemText(6, _translate("dockerVMConfigPageWidget", "1280x800"))
self.uiConsoleResolutionComboBox.setItemText(7, _translate("dockerVMConfigPageWidget", "1024x768"))
self.uiConsoleResolutionComboBox.setItemText(8, _translate("dockerVMConfigPageWidget", "800x600"))
self.uiConsoleResolutionComboBox.setItemText(9, _translate("dockerVMConfigPageWidget", "640x480"))
self.label.setText(_translate("dockerVMConfigPageWidget", "HTTP port in the container:"))
self.label_2.setText(_translate("dockerVMConfigPageWidget", "HTTP path:"))
self.uiEnvironmentLabel.setText(_translate("dockerVMConfigPageWidget", "Environment variables:\n"
@@ -201,6 +217,12 @@ class Ui_dockerVMConfigPageWidget(object):
"to the /etc/hosts file.\n"
"(hostname:IP\n"
"one per line)"))
self.uiExtraHostsTextEdit.setPlaceholderText(_translate("dockerVMConfigPageWidget", "e.g. router:192.168.0.1"))
self.uiExtraVolumeLabel.setText(_translate("dockerVMConfigPageWidget", "Additional directories to\n"
"make persistent that are\n"
"not included in the image\n"
"VOLUMES config. One\n"
"directory per line."))
self.uiExtraVolumeTextEdit.setPlaceholderText(_translate("dockerVMConfigPageWidget", "e.g. /etc/sysctl.d"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.tab_2), _translate("dockerVMConfigPageWidget", "Advanced"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.tab_3), _translate("dockerVMConfigPageWidget", "Usage"))

View File

@@ -130,7 +130,7 @@ class Dynamips(Module):
for router in self._settings.get("routers"):
router_settings = IOS_ROUTER_SETTINGS.copy()
router_settings.update(router)
if not router_settings.get("chassis"):
if router_settings.get("chassis"):
del router_settings["chassis"]
templates.append(Template(router_settings))
TemplateManager.instance().updateList(templates)

View File

@@ -123,7 +123,7 @@ class IOSRouterWizard(VMWithImagesWizard, Ui_IOSRouterWizard):
# try to guess the platform
image = os.path.basename(self.uiIOSImageLineEdit.text())
match = re.match(r"^(c[0-9]+)p?\\-\w+", image.lower())
match = re.match(r"^(c[0-9]+)p?-\w+", image.lower())
if not match:
QtWidgets.QMessageBox.warning(self, "IOS image", "Could not detect the platform, make sure this is a valid IOS image!")
return

View File

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

View File

@@ -111,7 +111,7 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
# try to guess the platform
image = os.path.basename(path)
match = re.match(r"^(c[0-9]+)\\-\w+", image)
match = re.match(r"^(c[0-9]+)p?-\w+", image)
if not match:
QtWidgets.QMessageBox.warning(self, "IOS image", "Could not detect the platform, make sure this is a valid IOS image!")
return

View File

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

View File

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

View File

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

@@ -59,6 +59,7 @@ class IOUDevicePreferencesPage(QtWidgets.QWidget, Ui_IOUDevicePreferencesPageWid
self.uiEditIOUDevicePushButton.clicked.connect(self._iouDeviceEditSlot)
self.uiDeleteIOUDevicePushButton.clicked.connect(self._iouDeviceDeleteSlot)
self.uiIOUDevicesTreeWidget.itemSelectionChanged.connect(self._iouDeviceChangedSlot)
self.uiIOUDevicesTreeWidget.itemDoubleClicked.connect(self._iouDeviceEditSlot)
def _createSectionItem(self, name):
"""
@@ -210,7 +211,7 @@ class IOUDevicePreferencesPage(QtWidgets.QWidget, Ui_IOUDevicePreferencesPageWid
del self._iou_devices[key]
item.setText(0, iou_device["name"])
item.setData(0, QtCore.Qt.UserRole, new_key)
self._refreshInfo(dialog.settings)
self._refreshInfo(dialog.settings())
def _iouDeviceDeleteSlot(self):
"""

View File

@@ -98,7 +98,10 @@ class IOUPreferencesPage(QtWidgets.QWidget, Ui_IOUPreferencesPageWidget):
:param settings: IOU settings
"""
self.IOULicenceTextEdit.setPlainText(settings["iourc_content"])
if settings["iourc_content"]:
self.IOULicenceTextEdit.blockSignals(True)
self.IOULicenceTextEdit.setPlainText(settings["iourc_content"])
self.IOULicenceTextEdit.blockSignals(False)
self.uiLicensecheckBox.setChecked(settings["license_check"])
def loadPreferences(self):
@@ -106,7 +109,10 @@ class IOUPreferencesPage(QtWidgets.QWidget, Ui_IOUPreferencesPageWidget):
Loads IOU preferences.
"""
Controller.instance().get("/iou_license", self._getSettingsCallback)
if Controller.instance().connected():
Controller.instance().get("/iou_license", self._getSettingsCallback)
else:
log.error("Cannot load the IOU license in the preferences dialog: not connected to the controller")
@qslot
def _getSettingsCallback(self, result, error=False, **kwargs):
@@ -115,7 +121,7 @@ class IOUPreferencesPage(QtWidgets.QWidget, Ui_IOUPreferencesPageWidget):
return
if error:
if "message" in result:
log.error("Error while getting settings : {}".format(result["message"]))
log.error("Error while getting the IOU license information: {}".format(result["message"]))
return
self._old_settings = copy.copy(result)
self._populateWidgets(result)

View File

@@ -14,6 +14,13 @@
<string>IOS on UNIX</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>IOU licence (iourc file):</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>

View File

@@ -1,21 +1,22 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/iou/ui/iou_preferences_page.ui'
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/iou/ui/iou_preferences_page.ui'
#
# Created by: PyQt5 UI code generator 5.7.1
# Created by: PyQt5 UI code generator 5.9
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_IOUPreferencesPageWidget(object):
def setupUi(self, IOUPreferencesPageWidget):
IOUPreferencesPageWidget.setObjectName("IOUPreferencesPageWidget")
IOUPreferencesPageWidget.resize(490, 532)
self.vboxlayout = QtWidgets.QVBoxLayout(IOUPreferencesPageWidget)
self.vboxlayout.setObjectName("vboxlayout")
self.label = QtWidgets.QLabel(IOUPreferencesPageWidget)
self.label.setObjectName("label")
self.vboxlayout.addWidget(self.label)
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
self.IOULicenceTextEdit = QtWidgets.QPlainTextEdit(IOUPreferencesPageWidget)
@@ -47,7 +48,9 @@ class Ui_IOUPreferencesPageWidget(object):
def retranslateUi(self, IOUPreferencesPageWidget):
_translate = QtCore.QCoreApplication.translate
IOUPreferencesPageWidget.setWindowTitle(_translate("IOUPreferencesPageWidget", "IOS on UNIX"))
self.label.setText(_translate("IOUPreferencesPageWidget", "IOU licence (iourc file):"))
self.IOULicenceTextEdit.setToolTip(_translate("IOUPreferencesPageWidget", "A license is required to run IOU. Copy & paste the content of your iourc file here or use the browse button to select a file. The license will be pushed to remote servers."))
self.uiIOURCPathToolButton.setText(_translate("IOUPreferencesPageWidget", "&Browse..."))
self.uiLicensecheckBox.setText(_translate("IOUPreferencesPageWidget", "Check for a valid IOU license key"))
self.uiRestoreDefaultsPushButton.setText(_translate("IOUPreferencesPageWidget", "Restore defaults"))

View File

@@ -152,9 +152,14 @@ class Module(QtCore.QObject):
:param directory: destination directory path
"""
node_names_cannot_export = []
for node in self._nodes:
if hasattr(node, "initialized") and node.initialized():
node.exportConfigsToDirectory(directory)
if not node.exportConfigsToDirectory(directory):
node_names_cannot_export.append(node.name())
if node_names_cannot_export:
log.warning("Config export is not supported by the following nodes: {}".format(" ".join(node_names_cannot_export)))
def importConfigs(self, directory):
"""

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

@@ -47,10 +47,13 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
# Mandatory fields
self.uiNameWizardPage.registerField("vm_name*", self.uiNameLineEdit)
self.uiDiskWizardPage.registerField("hda_disk_image*", self.uiHdaDiskImageLineEdit)
self.uiInitrdKernelImageWizardPage.registerField("initrd*", self.uiInitrdImageLineEdit)
self.uiInitrdKernelImageWizardPage.registerField("kernel_image*", self.uiKernelImageLineEdit)
# Fill image combo boxes
self.addImageSelector(self.uiHdaDiskExistingImageRadioButton, self.uiHdaDiskImageListComboBox, self.uiHdaDiskImageLineEdit, self.uiHdaDiskImageToolButton, QemuVMConfigurationPage.getDiskImage, create_image_wizard=QemuImageWizard, create_button=self.uiHdaDiskImageCreateToolButton, image_suffix="-hda")
self.addImageSelector(self.uiLinuxExistingImageRadioButton, self.uiInitrdImageListComboBox, self.uiInitrdImageLineEdit, self.uiInitrdImageToolButton, QemuVMConfigurationPage.getDiskImage)
self.addImageSelector(self.uiLinuxExistingImageRadioButton, self.uiKernelImageListComboBox, self.uiKernelImageLineEdit, self.uiKernelImageToolButton, QemuVMConfigurationPage.getDiskImage)
def validateCurrentPage(self):
"""
@@ -61,7 +64,11 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
return False
if self.currentPage() == self.uiNameWizardPage:
self.uiRamSpinBox.setValue(512)
if self.uiLegacyASACheckBox.isChecked():
QtWidgets.QMessageBox.warning(self, "Legacy ASA VM", "Running ASA (with initrd/kernel) is not recommended and will not work on Windows 10, please use ASAv instead")
self.uiRamSpinBox.setValue(1024)
else:
self.uiRamSpinBox.setValue(256)
if self.currentPage() == self.uiBinaryMemoryWizardPage:
if not self.uiQemuListComboBox.count():
@@ -81,7 +88,7 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
if self.uiLocalRadioButton.isChecked() and not ComputeManager.instance().localPlatform().startswith("linux"):
QtWidgets.QMessageBox.warning(self, "QEMU on Windows or Mac", "The recommended way to run QEMU on Windows and OSX is to use the GNS3 VM")
if self.page(page_id) == self.uiDiskWizardPage:
if self.page(page_id) in [self.uiDiskWizardPage, self.uiInitrdKernelImageWizardPage]:
self.loadImagesList("/qemu/images")
elif self.page(page_id) == self.uiBinaryMemoryWizardPage:
try:
@@ -109,7 +116,9 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
is_64bit = sys.maxsize > 2 ** 32
if ComputeManager.instance().localPlatform().startswith("win") and self.uiLocalRadioButton.isChecked():
if is_64bit:
if self.uiLegacyASACheckBox.isChecked():
search_string = r"qemu-0.13.0\qemu-system-i386w.exe"
elif is_64bit:
# default is qemu-system-x86_64w.exe on Windows 64-bit with a remote server
search_string = "x86_64w.exe"
else:
@@ -143,9 +152,46 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
"qemu_path": qemu_path,
"compute_id": self._compute_id,
"category": Node.end_devices,
"hda_disk_image": self.uiHdaDiskImageLineEdit.text(),
"console_type": console_type,
"options": ""
"console_type": console_type
}
if self.uiHdaDiskImageLineEdit.text().strip():
settings["hda_disk_image"] = self.uiHdaDiskImageLineEdit.text().strip()
settings["hda_disk_interface"] = "ide"
if self.uiLegacyASACheckBox.isChecked():
# special settings for legacy ASA VM
settings["adapters"] = 4
settings["initrd"] = self.uiInitrdImageLineEdit.text()
settings["kernel_image"] = self.uiKernelImageLineEdit.text()
settings["kernel_command_line"] = "ide_generic.probe_mask=0x01 ide_core.chs=0.0:980,16,32 auto nousb console=ttyS0,9600 bigphysarea=65536 ide1=noprobe no-hlt -net nic"
settings["options"] = "-machine accel=tcg -icount auto"
if not sys.platform.startswith("darwin"):
settings["cpu_throttling"] = 80 # limit to 80% CPU usage
settings["process_priority"] = "low"
settings["symbol"] = ":/symbols/asa.svg"
settings["category"] = Node.security_devices
if "options" not in settings:
settings["options"] = ""
if self._compute_id == "local" and (sys.platform.startswith("win") and qemu_path.endswith(r"qemu-0.11.0\qemu.exe")) or \
(sys.platform.startswith("darwin") and "GNS3.app" in qemu_path):
settings["options"] += " -vga none -vnc none"
settings["legacy_networking"] = True
settings["options"] = settings["options"].strip()
return settings
def nextId(self):
"""
Wizard rules!
"""
current_id = self.currentId()
if self.page(current_id) == self.uiDiskWizardPage:
if self.uiLegacyASACheckBox.isChecked():
return self.uiDiskWizardPage.nextId()
return -1
elif self.page(current_id) == self.uiInitrdKernelImageWizardPage:
return -1
return QtWidgets.QWizard.nextId(self)

View File

@@ -78,7 +78,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self.uiHdcDiskImageResizeToolButton.clicked.connect(self._hdcDiskImageResizeSlot)
self.uiHddDiskImageResizeToolButton.clicked.connect(self._hddDiskImageResizeSlot)
disk_interfaces = ["ide", "sata", "scsi", "sd", "mtd", "floppy", "pflash", "virtio", "none"]
disk_interfaces = ["ide", "sata", "nvme", "scsi", "sd", "mtd", "floppy", "pflash", "virtio", "none"]
self.uiHdaDiskInterfaceComboBox.addItems(disk_interfaces)
self.uiHdbDiskInterfaceComboBox.addItems(disk_interfaces)
self.uiHdcDiskInterfaceComboBox.addItems(disk_interfaces)
@@ -90,6 +90,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self.uiActivateCPUThrottlingCheckBox.stateChanged.connect(self._cpuThrottlingChangedSlot)
self.uiLegacyNetworkingCheckBox.stateChanged.connect(self._legacyNetworkingChangedSlot)
self.uiCustomAdaptersConfigurationPushButton.clicked.connect(self._customAdaptersConfigurationSlot)
self.uiCreateConfigDiskCheckBox.stateChanged.connect(self._createConfigDiskChangedSlot)
# add the categories
for name, category in Node.defaultCategories().items():
@@ -99,8 +100,14 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
for name, option_name in Node.onCloseOptions().items():
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, 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"),
("e1000-82544gc", "Intel 82544GC Gigabit Ethernet"),
("e1000-82545em", "Intel 82545EM Gigabit Ethernet"),
("e1000e", "Intel PCIe Gigabit Ethernet"),
("i82550", "Intel i82550 Ethernet"),
("i82551", "Intel i82551 Ethernet"),
("i82557a", "Intel i82557A Ethernet"),
@@ -114,8 +121,10 @@ 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"),
("rtl8139", "Realtek 8139 Ethernet"),
("virtio", "Legacy paravirtualized Network I/O"),
("virtio-net-pci", "Paravirtualized Network I/O"),
@@ -147,6 +156,9 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
for device_name, device_description in self._qemu_network_devices.items():
if legacy_networking and device_name not in self._legacy_devices:
continue
# special case for virtio legacy networking
if not legacy_networking and device_name == "virtio":
continue
self.uiAdapterTypesComboBox.addItem("{} ({})".format(device_description, device_name), device_name)
@staticmethod
@@ -360,6 +372,19 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
else:
self._refreshQemuNetworkDevices()
def _createConfigDiskChangedSlot(self, state):
"""
Slot to allow or not HDD disk to be configured based on the state of the config disk option.
"""
_translate = QtCore.QCoreApplication.translate
if state:
self.uiHddDiskImageLabel.setText(_translate("QemuVMConfigPageWidget", "Startup-cfg:"))
else:
self.uiHddDiskImageLabel.setText(_translate("QemuVMConfigPageWidget", "Disk image:"))
self.uiHddDiskImageCreateToolButton.setEnabled(not state)
self.uiHddDiskImageResizeToolButton.setEnabled(not state)
def _customAdaptersConfigurationSlot(self):
"""
Slot to open the custom adapters configuration dialog
@@ -391,11 +416,21 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
try:
ports = StandardPortNameFactory(adapters, first_port_name, port_name_format, port_segment_size)
except (ValueError, KeyError):
except (IndexError, ValueError, KeyError):
QtWidgets.QMessageBox.critical(self, "Invalid format", "Invalid port name format")
return
dialog = CustomAdaptersConfigurationDialog(ports, self._custom_adapters, default_adapter, self._qemu_network_devices, base_mac_address, parent=self)
if self.uiLegacyNetworkingCheckBox.isChecked():
network_devices = {}
for nic, desc in self._qemu_network_devices.items():
if nic in self._legacy_devices:
network_devices[nic] = desc
else:
network_devices = self._qemu_network_devices.copy()
# special case for virtio legacy networking
network_devices.pop("virtio")
dialog = CustomAdaptersConfigurationDialog(ports, self._custom_adapters, default_adapter, network_devices, base_mac_address, parent=self)
dialog.show()
dialog.exec_()
@@ -439,6 +474,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self.uiHdbDiskInterfaceComboBox.setCurrentIndex(self.uiHdbDiskInterfaceComboBox.findText(settings["hdb_disk_interface"]))
self.uiHdcDiskInterfaceComboBox.setCurrentIndex(self.uiHdcDiskInterfaceComboBox.findText(settings["hdc_disk_interface"]))
self.uiHddDiskInterfaceComboBox.setCurrentIndex(self.uiHddDiskInterfaceComboBox.findText(settings["hdd_disk_interface"]))
self.uiCreateConfigDiskCheckBox.setChecked(settings["create_config_disk"])
self.uiCdromImageLineEdit.setText(settings["cdrom_image"])
self.uiBiosImageLineEdit.setText(settings["bios_image"])
self.uiInitrdLineEdit.setText(settings["initrd"])
@@ -511,6 +547,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self._custom_adapters = settings["custom_adapters"].copy()
self.uiLegacyNetworkingCheckBox.setChecked(settings["legacy_networking"])
self.uiReplicateNetworkConnectionStateCheckBox.setChecked(settings["replicate_network_connection_state"])
# load the MAC address setting
self.uiMacAddrLineEdit.setInputMask("HH:HH:HH:HH:HH:HH;_")
@@ -542,6 +579,8 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self.uiProcessPriorityComboBox.setCurrentIndex(index)
self.uiQemuOptionsLineEdit.setText(settings["options"])
self.uiUsageTextEdit.setPlainText(settings["usage"])
self.uiTPMCheckBox.setChecked(settings["tpm"])
self.uiUEFICheckBox.setChecked(settings["uefi"])
def saveSettings(self, settings, node=None, group=False):
"""
@@ -573,6 +612,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
settings["hdb_disk_interface"] = self.uiHdbDiskInterfaceComboBox.currentText()
settings["hdc_disk_interface"] = self.uiHdcDiskInterfaceComboBox.currentText()
settings["hdd_disk_interface"] = self.uiHddDiskInterfaceComboBox.currentText()
settings["create_config_disk"] = self.uiCreateConfigDiskCheckBox.isChecked()
settings["cdrom_image"] = self.uiCdromImageLineEdit.text().strip()
settings["bios_image"] = self.uiBiosImageLineEdit.text().strip()
settings["initrd"] = self.uiInitrdLineEdit.text().strip()
@@ -642,6 +682,8 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
raise ConfigurationError()
settings["adapters"] = adapters
settings["legacy_networking"] = self.uiLegacyNetworkingCheckBox.isChecked()
settings["replicate_network_connection_state"] = self.uiReplicateNetworkConnectionStateCheckBox.isChecked()
settings["custom_adapters"] = self._custom_adapters.copy()
settings["on_close"] = self.uiOnCloseComboBox.itemData(self.uiOnCloseComboBox.currentIndex())
settings["cpus"] = self.uiCPUSpinBox.value()
@@ -653,4 +695,6 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
settings["process_priority"] = self.uiProcessPriorityComboBox.currentText().lower()
settings["options"] = self.uiQemuOptionsLineEdit.text()
settings["usage"] = self.uiUsageTextEdit.toPlainText()
settings["tpm"] = self.uiTPMCheckBox.isChecked()
settings["uefi"] = self.uiUEFICheckBox.isChecked()
return settings

View File

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

View File

@@ -19,6 +19,8 @@
QEMU VM implementation.
"""
import re
from gns3.node import Node
from .settings import QEMU_VM_SETTINGS
@@ -71,6 +73,10 @@ class QemuVM(Node):
"adapter_type": QEMU_VM_SETTINGS["adapter_type"],
"mac_address": QEMU_VM_SETTINGS["mac_address"],
"legacy_networking": QEMU_VM_SETTINGS["legacy_networking"],
"replicate_network_connection_state": QEMU_VM_SETTINGS["replicate_network_connection_state"],
"tpm": QEMU_VM_SETTINGS["tpm"],
"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"],
"cpu_throttling": QEMU_VM_SETTINGS["cpu_throttling"],
@@ -133,6 +139,24 @@ class QemuVM(Node):
usage = "\n" + self._settings.get("usage")
return info + port_info + usage
def configFiles(self):
"""
Name of the configuration files
"""
if self._settings.get("create_config_disk"):
return ["config.zip"]
return None
def configTextFiles(self):
"""
Name of the configuration files, which are plain text files
:returns: List of configuration files, False if no files
"""
return None
def configPage(self):
"""
Returns the configuration page widget to be used by the node properties dialog.
@@ -143,6 +167,18 @@ class QemuVM(Node):
from .pages.qemu_vm_configuration_page import QemuVMConfigurationPage
return QemuVMConfigurationPage
@staticmethod
def validateHostname(hostname):
"""
Checks if the hostname is valid.
:param hostname: hostname to check
:returns: boolean
"""
return QemuVM.isValidRfc1123Hostname(hostname)
@staticmethod
def defaultSymbol():
"""

View File

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

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>941</width>
<height>877</height>
<width>478</width>
<height>579</height>
</rect>
</property>
<property name="windowTitle">
@@ -399,14 +399,21 @@
<string>HDD (Secondary Slave)</string>
</property>
<layout class="QGridLayout" name="gridLayout_9">
<item row="0" column="0">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="uiCreateConfigDiskCheckBox">
<property name="text">
<string>Automatically create a config disk on HDD</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiHddDiskImageLabel">
<property name="text">
<string>Disk image:</string>
</property>
</widget>
</item>
<item row="0" column="1" rowspan="2">
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QLineEdit" name="uiHddDiskImageLineEdit"/>
@@ -437,7 +444,7 @@
</item>
</layout>
</item>
<item row="1" column="0" rowspan="2">
<item row="2" column="0">
<widget class="QLabel" name="uiHddDiskInterfaceLabel">
<property name="text">
<string>Disk interface:</string>
@@ -470,7 +477,16 @@
<string>CD/DVD</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="margin">
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item>
@@ -526,6 +542,23 @@
<string>Network</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_5">
<item row="3" column="0">
<widget class="QLabel" name="uiPortSegmentSizeLabel">
<property name="text">
<string>Segment size:</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<widget class="QSpinBox" name="uiPortSegmentSizeSpinBox">
<property name="maximum">
<number>128</number>
</property>
<property name="singleStep">
<number>4</number>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="uiAdaptersLabel">
<property name="text">
@@ -560,13 +593,6 @@
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="uiPortSegmentSizeLabel">
<property name="text">
<string>Segment size:</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="uiMacAddrLabel">
<property name="text">
@@ -598,14 +624,14 @@
</property>
</widget>
</item>
<item row="7" column="0" colspan="3">
<item row="8" column="0" colspan="3">
<widget class="QCheckBox" name="uiLegacyNetworkingCheckBox">
<property name="text">
<string>Use the legacy networking mode</string>
</property>
</widget>
</item>
<item row="8" column="2">
<item row="9" column="2">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -628,16 +654,6 @@
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<widget class="QSpinBox" name="uiPortSegmentSizeSpinBox">
<property name="maximum">
<number>128</number>
</property>
<property name="singleStep">
<number>4</number>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QSpinBox" name="uiAdaptersSpinBox">
<property name="sizePolicy">
@@ -654,6 +670,13 @@
</property>
</widget>
</item>
<item row="7" column="0" colspan="3">
<widget class="QCheckBox" name="uiReplicateNetworkConnectionStateCheckBox">
<property name="text">
<string>Replicate network connection states in Qemu</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="uiAdvancedSettingsTab">
@@ -844,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">
@@ -860,12 +876,14 @@
&lt;li&gt;%vm-id% =VM ID&lt;/li&gt;
&lt;li&gt;%project-id% = project ID&lt;/li&gt;
&lt;li&gt;%project-path% = project path&lt;/li&gt;
&lt;li&gt;%console-port% = console port number&lt;/li&gt;
&lt;li&gt;%guest-cid% = unique ID from 3 to 65535&lt;/li&gt;
&lt;/ul&gt;
&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="uiBaseVMCheckBox">
<property name="enabled">
<bool>true</bool>
@@ -875,10 +893,33 @@
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="uiTPMCheckBox">
<property name="text">
<string>Enable Trusted Platform Module (TPM)</string>
</property>
</widget>
</item>
<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,16 +2,19 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/qemu/ui/qemu_vm_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
# Created by: PyQt5 UI code generator 5.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_QemuVMConfigPageWidget(object):
def setupUi(self, QemuVMConfigPageWidget):
QemuVMConfigPageWidget.setObjectName("QemuVMConfigPageWidget")
QemuVMConfigPageWidget.resize(941, 877)
QemuVMConfigPageWidget.resize(478, 579)
self.verticalLayout = QtWidgets.QVBoxLayout(QemuVMConfigPageWidget)
self.verticalLayout.setObjectName("verticalLayout")
self.uiQemutabWidget = QtWidgets.QTabWidget(QemuVMConfigPageWidget)
@@ -209,9 +212,12 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiHddGroupBox.setObjectName("uiHddGroupBox")
self.gridLayout_9 = QtWidgets.QGridLayout(self.uiHddGroupBox)
self.gridLayout_9.setObjectName("gridLayout_9")
self.uiCreateConfigDiskCheckBox = QtWidgets.QCheckBox(self.uiHddGroupBox)
self.uiCreateConfigDiskCheckBox.setObjectName("uiCreateConfigDiskCheckBox")
self.gridLayout_9.addWidget(self.uiCreateConfigDiskCheckBox, 0, 0, 1, 2)
self.uiHddDiskImageLabel = QtWidgets.QLabel(self.uiHddGroupBox)
self.uiHddDiskImageLabel.setObjectName("uiHddDiskImageLabel")
self.gridLayout_9.addWidget(self.uiHddDiskImageLabel, 0, 0, 1, 1)
self.gridLayout_9.addWidget(self.uiHddDiskImageLabel, 1, 0, 1, 1)
self.horizontalLayout_10 = QtWidgets.QHBoxLayout()
self.horizontalLayout_10.setObjectName("horizontalLayout_10")
self.uiHddDiskImageLineEdit = QtWidgets.QLineEdit(self.uiHddGroupBox)
@@ -227,10 +233,10 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiHddDiskImageResizeToolButton = QtWidgets.QToolButton(self.uiHddGroupBox)
self.uiHddDiskImageResizeToolButton.setObjectName("uiHddDiskImageResizeToolButton")
self.horizontalLayout_10.addWidget(self.uiHddDiskImageResizeToolButton)
self.gridLayout_9.addLayout(self.horizontalLayout_10, 0, 1, 2, 1)
self.gridLayout_9.addLayout(self.horizontalLayout_10, 1, 1, 1, 1)
self.uiHddDiskInterfaceLabel = QtWidgets.QLabel(self.uiHddGroupBox)
self.uiHddDiskInterfaceLabel.setObjectName("uiHddDiskInterfaceLabel")
self.gridLayout_9.addWidget(self.uiHddDiskInterfaceLabel, 1, 0, 2, 1)
self.gridLayout_9.addWidget(self.uiHddDiskInterfaceLabel, 2, 0, 1, 1)
self.uiHddDiskInterfaceComboBox = QtWidgets.QComboBox(self.uiHddGroupBox)
self.uiHddDiskInterfaceComboBox.setObjectName("uiHddDiskInterfaceComboBox")
self.gridLayout_9.addWidget(self.uiHddDiskInterfaceComboBox, 2, 1, 1, 1)
@@ -268,6 +274,14 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiNetworkTab.setObjectName("uiNetworkTab")
self.gridLayout_5 = QtWidgets.QGridLayout(self.uiNetworkTab)
self.gridLayout_5.setObjectName("gridLayout_5")
self.uiPortSegmentSizeLabel = QtWidgets.QLabel(self.uiNetworkTab)
self.uiPortSegmentSizeLabel.setObjectName("uiPortSegmentSizeLabel")
self.gridLayout_5.addWidget(self.uiPortSegmentSizeLabel, 3, 0, 1, 1)
self.uiPortSegmentSizeSpinBox = QtWidgets.QSpinBox(self.uiNetworkTab)
self.uiPortSegmentSizeSpinBox.setMaximum(128)
self.uiPortSegmentSizeSpinBox.setSingleStep(4)
self.uiPortSegmentSizeSpinBox.setObjectName("uiPortSegmentSizeSpinBox")
self.gridLayout_5.addWidget(self.uiPortSegmentSizeSpinBox, 3, 1, 1, 2)
self.uiAdaptersLabel = QtWidgets.QLabel(self.uiNetworkTab)
self.uiAdaptersLabel.setObjectName("uiAdaptersLabel")
self.gridLayout_5.addWidget(self.uiAdaptersLabel, 0, 0, 1, 1)
@@ -284,9 +298,6 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiPortNameFormatLineEdit.setText("")
self.uiPortNameFormatLineEdit.setObjectName("uiPortNameFormatLineEdit")
self.gridLayout_5.addWidget(self.uiPortNameFormatLineEdit, 2, 1, 1, 2)
self.uiPortSegmentSizeLabel = QtWidgets.QLabel(self.uiNetworkTab)
self.uiPortSegmentSizeLabel.setObjectName("uiPortSegmentSizeLabel")
self.gridLayout_5.addWidget(self.uiPortSegmentSizeLabel, 3, 0, 1, 1)
self.uiMacAddrLabel = QtWidgets.QLabel(self.uiNetworkTab)
self.uiMacAddrLabel.setObjectName("uiMacAddrLabel")
self.gridLayout_5.addWidget(self.uiMacAddrLabel, 4, 0, 1, 1)
@@ -304,9 +315,9 @@ class Ui_QemuVMConfigPageWidget(object):
self.gridLayout_5.addWidget(self.uiCustomAdaptersConfigurationPushButton, 6, 1, 1, 2)
self.uiLegacyNetworkingCheckBox = QtWidgets.QCheckBox(self.uiNetworkTab)
self.uiLegacyNetworkingCheckBox.setObjectName("uiLegacyNetworkingCheckBox")
self.gridLayout_5.addWidget(self.uiLegacyNetworkingCheckBox, 7, 0, 1, 3)
self.gridLayout_5.addWidget(self.uiLegacyNetworkingCheckBox, 8, 0, 1, 3)
spacerItem3 = QtWidgets.QSpacerItem(20, 261, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_5.addItem(spacerItem3, 8, 2, 1, 1)
self.gridLayout_5.addItem(spacerItem3, 9, 2, 1, 1)
self.uiAdapterTypesComboBox = QtWidgets.QComboBox(self.uiNetworkTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -315,11 +326,6 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiAdapterTypesComboBox.setSizePolicy(sizePolicy)
self.uiAdapterTypesComboBox.setObjectName("uiAdapterTypesComboBox")
self.gridLayout_5.addWidget(self.uiAdapterTypesComboBox, 5, 1, 1, 2)
self.uiPortSegmentSizeSpinBox = QtWidgets.QSpinBox(self.uiNetworkTab)
self.uiPortSegmentSizeSpinBox.setMaximum(128)
self.uiPortSegmentSizeSpinBox.setSingleStep(4)
self.uiPortSegmentSizeSpinBox.setObjectName("uiPortSegmentSizeSpinBox")
self.gridLayout_5.addWidget(self.uiPortSegmentSizeSpinBox, 3, 1, 1, 2)
self.uiAdaptersSpinBox = QtWidgets.QSpinBox(self.uiNetworkTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -330,6 +336,9 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiAdaptersSpinBox.setMaximum(275)
self.uiAdaptersSpinBox.setObjectName("uiAdaptersSpinBox")
self.gridLayout_5.addWidget(self.uiAdaptersSpinBox, 0, 1, 1, 2)
self.uiReplicateNetworkConnectionStateCheckBox = QtWidgets.QCheckBox(self.uiNetworkTab)
self.uiReplicateNetworkConnectionStateCheckBox.setObjectName("uiReplicateNetworkConnectionStateCheckBox")
self.gridLayout_5.addWidget(self.uiReplicateNetworkConnectionStateCheckBox, 7, 0, 1, 3)
self.uiQemutabWidget.addTab(self.uiNetworkTab, "")
self.uiAdvancedSettingsTab = QtWidgets.QWidget()
self.uiAdvancedSettingsTab.setObjectName("uiAdvancedSettingsTab")
@@ -419,19 +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, 1, 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)
@@ -492,6 +509,7 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiHdcDiskImageResizeToolButton.setText(_translate("QemuVMConfigPageWidget", "Resize..."))
self.uiHdcDiskInterfaceLabel.setText(_translate("QemuVMConfigPageWidget", "Disk interface:"))
self.uiHddGroupBox.setTitle(_translate("QemuVMConfigPageWidget", "HDD (Secondary Slave)"))
self.uiCreateConfigDiskCheckBox.setText(_translate("QemuVMConfigPageWidget", "Automatically create a config disk on HDD"))
self.uiHddDiskImageLabel.setText(_translate("QemuVMConfigPageWidget", "Disk image:"))
self.uiHddDiskImageToolButton.setText(_translate("QemuVMConfigPageWidget", "&Browse..."))
self.uiHddDiskImageCreateToolButton.setText(_translate("QemuVMConfigPageWidget", "Create..."))
@@ -502,16 +520,17 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiCdromImageLabel.setText(_translate("QemuVMConfigPageWidget", "Image:"))
self.uiCdromImageToolButton.setText(_translate("QemuVMConfigPageWidget", "&Browse..."))
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiCdromTab), _translate("QemuVMConfigPageWidget", "CD/DVD"))
self.uiPortSegmentSizeLabel.setText(_translate("QemuVMConfigPageWidget", "Segment size:"))
self.uiAdaptersLabel.setText(_translate("QemuVMConfigPageWidget", "Adapters:"))
self.uiFirstPortNameLabel.setText(_translate("QemuVMConfigPageWidget", "First port name:"))
self.uiPortNameFormatLabel.setToolTip(_translate("QemuVMConfigPageWidget", "<html><head/><body><p>{0} - the port number, from 0 to the number of adapters-1.</p><p>{1} - the segment number, from 0 to the number of segments-1.</p><p>{port0} - named alias for {0}.</p><p>{port1} - the port number, from 1 to the number of adapters.</p><p>{segment0} - named alias for {1}.</p><p>{segment1} - the segment number, from 1 to the number of segments.</p></body></html>"))
self.uiPortNameFormatLabel.setText(_translate("QemuVMConfigPageWidget", "Name format:"))
self.uiPortSegmentSizeLabel.setText(_translate("QemuVMConfigPageWidget", "Segment size:"))
self.uiMacAddrLabel.setText(_translate("QemuVMConfigPageWidget", "Base MAC:"))
self.uiAdapterTypesLabel.setText(_translate("QemuVMConfigPageWidget", "Type:"))
self.uiCustomAdaptersLabel.setText(_translate("QemuVMConfigPageWidget", "Custom adapters:"))
self.uiCustomAdaptersConfigurationPushButton.setText(_translate("QemuVMConfigPageWidget", "&Configure custom adapters"))
self.uiLegacyNetworkingCheckBox.setText(_translate("QemuVMConfigPageWidget", "Use the legacy networking mode"))
self.uiReplicateNetworkConnectionStateCheckBox.setText(_translate("QemuVMConfigPageWidget", "Replicate network connection states in Qemu"))
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiNetworkTab), _translate("QemuVMConfigPageWidget", "Network"))
self.uiLinuxBootGroupBox.setTitle(_translate("QemuVMConfigPageWidget", "Linux boot specific settings"))
self.uiKernelCommandLineLabel.setText(_translate("QemuVMConfigPageWidget", "Kernel command line:"))
@@ -534,16 +553,19 @@ 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"
"<li>%vm-id% =VM ID</li>\n"
"<li>%project-id% = project ID</li>\n"
"<li>%project-path% = project path</li>\n"
"<li>%console-port% = console port number</li>\n"
"<li>%guest-cid% = unique ID from 3 to 65535</li>\n"
"</ul>\n"
"</body></html>"))
self.uiBaseVMCheckBox.setText(_translate("QemuVMConfigPageWidget", "Use as a linked base VM"))
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

@@ -106,6 +106,13 @@
<item row="0" column="1">
<widget class="QLineEdit" name="uiNameLineEdit"/>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="uiLegacyASACheckBox">
<property name="text">
<string>This is a legacy ASA VM</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="uiBinaryMemoryWizardPage">
@@ -305,6 +312,114 @@
</item>
</layout>
</widget>
<widget class="QWizardPage" name="uiInitrdKernelImageWizardPage">
<property name="title">
<string>Linux boot specific settings</string>
</property>
<property name="subTitle">
<string>Please choose a initrd and a kernel image.</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QRadioButton" name="uiLinuxExistingImageRadioButton">
<property name="text">
<string>Existing image</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="uiNewImageRadioButton_4">
<property name="text">
<string>New Image</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QFormLayout" name="formLayout_2">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<widget class="QComboBox" name="uiInitrdImageListComboBox"/>
</item>
<item>
<widget class="QLineEdit" name="uiInitrdImageLineEdit"/>
</item>
<item>
<widget class="QToolButton" name="uiInitrdImageToolButton">
<property name="text">
<string>&amp;Browse...</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiKernelImageLabel">
<property name="text">
<string>Kernel image (vmlinuz):</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_14">
<item>
<widget class="QComboBox" name="uiKernelImageListComboBox"/>
</item>
<item>
<widget class="QLineEdit" name="uiKernelImageLineEdit"/>
</item>
<item>
<widget class="QToolButton" name="uiKernelImageToolButton">
<property name="text">
<string>&amp;Browse...</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QLabel" name="uiInitrdLabel">
<property name="text">
<string>Initial RAM disk (initrd):</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<tabstops>
<tabstop>uiNameLineEdit</tabstop>

View File

@@ -60,6 +60,9 @@ class Ui_QemuVMWizard(object):
self.uiNameLineEdit = QtWidgets.QLineEdit(self.uiNameWizardPage)
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
self.uiLegacyASACheckBox = QtWidgets.QCheckBox(self.uiNameWizardPage)
self.uiLegacyASACheckBox.setObjectName("uiLegacyASACheckBox")
self.gridLayout.addWidget(self.uiLegacyASACheckBox, 1, 0, 1, 2)
QemuVMWizard.addPage(self.uiNameWizardPage)
self.uiBinaryMemoryWizardPage = QtWidgets.QWizardPage()
self.uiBinaryMemoryWizardPage.setObjectName("uiBinaryMemoryWizardPage")
@@ -146,6 +149,60 @@ class Ui_QemuVMWizard(object):
self.horizontalLayout_8.addWidget(self.uiHdaDiskImageCreateToolButton)
self.gridLayout_3.addLayout(self.horizontalLayout_8, 1, 0, 1, 1)
QemuVMWizard.addPage(self.uiDiskWizardPage)
self.uiInitrdKernelImageWizardPage = QtWidgets.QWizardPage()
self.uiInitrdKernelImageWizardPage.setObjectName("uiInitrdKernelImageWizardPage")
self.gridLayout_4 = QtWidgets.QGridLayout(self.uiInitrdKernelImageWizardPage)
self.gridLayout_4.setObjectName("gridLayout_4")
self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
self.uiLinuxExistingImageRadioButton = QtWidgets.QRadioButton(self.uiInitrdKernelImageWizardPage)
self.uiLinuxExistingImageRadioButton.setChecked(True)
self.uiLinuxExistingImageRadioButton.setObjectName("uiLinuxExistingImageRadioButton")
self.horizontalLayout_7.addWidget(self.uiLinuxExistingImageRadioButton)
self.uiNewImageRadioButton_4 = QtWidgets.QRadioButton(self.uiInitrdKernelImageWizardPage)
self.uiNewImageRadioButton_4.setChecked(False)
self.uiNewImageRadioButton_4.setObjectName("uiNewImageRadioButton_4")
self.horizontalLayout_7.addWidget(self.uiNewImageRadioButton_4)
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_7.addItem(spacerItem2)
self.gridLayout_4.addLayout(self.horizontalLayout_7, 0, 0, 1, 1)
self.formLayout_2 = QtWidgets.QFormLayout()
self.formLayout_2.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
self.formLayout_2.setObjectName("formLayout_2")
self.horizontalLayout_11 = QtWidgets.QHBoxLayout()
self.horizontalLayout_11.setObjectName("horizontalLayout_11")
self.uiInitrdImageListComboBox = QtWidgets.QComboBox(self.uiInitrdKernelImageWizardPage)
self.uiInitrdImageListComboBox.setObjectName("uiInitrdImageListComboBox")
self.horizontalLayout_11.addWidget(self.uiInitrdImageListComboBox)
self.uiInitrdImageLineEdit = QtWidgets.QLineEdit(self.uiInitrdKernelImageWizardPage)
self.uiInitrdImageLineEdit.setObjectName("uiInitrdImageLineEdit")
self.horizontalLayout_11.addWidget(self.uiInitrdImageLineEdit)
self.uiInitrdImageToolButton = QtWidgets.QToolButton(self.uiInitrdKernelImageWizardPage)
self.uiInitrdImageToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiInitrdImageToolButton.setObjectName("uiInitrdImageToolButton")
self.horizontalLayout_11.addWidget(self.uiInitrdImageToolButton)
self.formLayout_2.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_11)
self.uiKernelImageLabel = QtWidgets.QLabel(self.uiInitrdKernelImageWizardPage)
self.uiKernelImageLabel.setObjectName("uiKernelImageLabel")
self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.uiKernelImageLabel)
self.horizontalLayout_14 = QtWidgets.QHBoxLayout()
self.horizontalLayout_14.setObjectName("horizontalLayout_14")
self.uiKernelImageListComboBox = QtWidgets.QComboBox(self.uiInitrdKernelImageWizardPage)
self.uiKernelImageListComboBox.setObjectName("uiKernelImageListComboBox")
self.horizontalLayout_14.addWidget(self.uiKernelImageListComboBox)
self.uiKernelImageLineEdit = QtWidgets.QLineEdit(self.uiInitrdKernelImageWizardPage)
self.uiKernelImageLineEdit.setObjectName("uiKernelImageLineEdit")
self.horizontalLayout_14.addWidget(self.uiKernelImageLineEdit)
self.uiKernelImageToolButton = QtWidgets.QToolButton(self.uiInitrdKernelImageWizardPage)
self.uiKernelImageToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiKernelImageToolButton.setObjectName("uiKernelImageToolButton")
self.horizontalLayout_14.addWidget(self.uiKernelImageToolButton)
self.formLayout_2.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_14)
self.uiInitrdLabel = QtWidgets.QLabel(self.uiInitrdKernelImageWizardPage)
self.uiInitrdLabel.setObjectName("uiInitrdLabel")
self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.uiInitrdLabel)
self.gridLayout_4.addLayout(self.formLayout_2, 1, 0, 1, 1)
QemuVMWizard.addPage(self.uiInitrdKernelImageWizardPage)
self.retranslateUi(QemuVMWizard)
QtCore.QMetaObject.connectSlotsByName(QemuVMWizard)
@@ -168,6 +225,7 @@ class Ui_QemuVMWizard(object):
self.uiNameWizardPage.setTitle(_translate("QemuVMWizard", "QEMU VM name"))
self.uiNameWizardPage.setSubTitle(_translate("QemuVMWizard", "Please choose a descriptive name for your new QEMU virtual machine."))
self.uiNameLabel.setText(_translate("QemuVMWizard", "Name:"))
self.uiLegacyASACheckBox.setText(_translate("QemuVMWizard", "This is a legacy ASA VM"))
self.uiBinaryMemoryWizardPage.setTitle(_translate("QemuVMWizard", "QEMU binary and memory"))
self.uiBinaryMemoryWizardPage.setSubTitle(_translate("QemuVMWizard", "Please check the Qemu binary is correctly set and the virtual machine has enough memory to work."))
self.uiQemuListLabel.setText(_translate("QemuVMWizard", "Qemu binary:"))
@@ -188,4 +246,12 @@ class Ui_QemuVMWizard(object):
self.uiHdaDiskImageLabel.setText(_translate("QemuVMWizard", "Disk image (hda):"))
self.uiHdaDiskImageToolButton.setText(_translate("QemuVMWizard", "&Browse..."))
self.uiHdaDiskImageCreateToolButton.setText(_translate("QemuVMWizard", "&Create"))
self.uiInitrdKernelImageWizardPage.setTitle(_translate("QemuVMWizard", "Linux boot specific settings"))
self.uiInitrdKernelImageWizardPage.setSubTitle(_translate("QemuVMWizard", "Please choose a initrd and a kernel image."))
self.uiLinuxExistingImageRadioButton.setText(_translate("QemuVMWizard", "Existing image"))
self.uiNewImageRadioButton_4.setText(_translate("QemuVMWizard", "New Image"))
self.uiInitrdImageToolButton.setText(_translate("QemuVMWizard", "&Browse..."))
self.uiKernelImageLabel.setText(_translate("QemuVMWizard", "Kernel image (vmlinuz):"))
self.uiKernelImageToolButton.setText(_translate("QemuVMWizard", "&Browse..."))
self.uiInitrdLabel.setText(_translate("QemuVMWizard", "Initial RAM disk (initrd):"))

View File

@@ -72,7 +72,7 @@ class TraceNGNode(Node):
self._last_destination = destination
params = {"destination": destination}
log.debug("{} is starting".format(self.name()))
self.controllerHttpPost("/nodes/{node_id}/start".format(node_id=self._node_id), self._startCallback, body=params, timeout=None, progressText="{} is starting".format(self.name()))
self.controllerHttpPost("/nodes/{node_id}/start".format(node_id=self._node_id), self._startCallback, body=params, timeout=None, showProgress=False)
def info(self):
"""

View File

@@ -99,7 +99,7 @@ class VirtualBoxVMConfigurationPage(QtWidgets.QWidget, Ui_virtualBoxVMConfigPage
try:
ports = StandardPortNameFactory(adapters, first_port_name, port_name_format, port_segment_size)
except (ValueError, KeyError):
except (IndexError, ValueError, KeyError):
QtWidgets.QMessageBox.critical(self, "Invalid format", "Invalid port name format")
return

View File

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

View File

@@ -99,7 +99,7 @@ class VMwareVMConfigurationPage(QtWidgets.QWidget, Ui_VMwareVMConfigPageWidget):
try:
ports = StandardPortNameFactory(adapters, first_port_name, port_name_format, port_segment_size)
except (ValueError, KeyError):
except (IndexError, ValueError, KeyError):
QtWidgets.QMessageBox.critical(self, "Invalid format", "Invalid port name format")
return

View File

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

View File

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

View File

@@ -17,6 +17,7 @@
import os
import pathlib
import re
from gns3.controller import Controller
from gns3.ports.ethernet_port import EthernetPort
@@ -199,6 +200,26 @@ class Node(BaseNode):
return self._always_on
def configFiles(self):
"""
Name of the configuration files
This method should be overridden in derived classes
:returns: List of configuration files, False if no files
"""
return None
def configTextFiles(self):
"""
Name of the configuration files, which are plain text files
:returns: List of configuration files, False if no files
"""
return self.configFiles()
def get(self, path, *args, **kwargs):
"""
GET on current server / project
@@ -223,7 +244,7 @@ class Node(BaseNode):
return
log.debug("{} is starting".format(self.name()))
self.post("/start", self._startCallback, timeout=None, progressText="{} is starting".format(self.name()))
self.post("/start", self._startCallback, timeout=None, showProgress=False)
def _startCallback(self, result, error=False, **kwargs):
"""
@@ -249,7 +270,7 @@ class Node(BaseNode):
return
log.debug("{} is stopping".format(self.name()))
self.post("/stop", self._stopCallback, timeout=None, progressText="{} is stopping".format(self.name()))
self.post("/stop", self._stopCallback, timeout=None, showProgress=False)
def _stopCallback(self, result, error=False, **kwargs):
"""
@@ -279,7 +300,7 @@ class Node(BaseNode):
return
log.debug("{} is being suspended".format(self.name()))
self.post("/suspend", self._suspendCallback, timeout=None, progressText="{} is suspending".format(self.name()))
self.post("/suspend", self._suspendCallback, timeout=None, showProgress=False)
def _suspendCallback(self, result, error=False, **kwargs):
"""
@@ -301,7 +322,7 @@ class Node(BaseNode):
"""
log.debug("{} is being reloaded".format(self.name()))
self.post("/reload", self._reloadCallback, timeout=None, progressText="{} is reloading".format(self.name()))
self.post("/reload", self._reloadCallback, timeout=None, showProgress=False)
def _reloadCallback(self, result, error=False, **kwargs):
"""
@@ -455,7 +476,7 @@ class Node(BaseNode):
if not skip_controller:
for link in self.links():
link.setDeleting()
self.controllerHttpDelete("/nodes/{node_id}".format(node_id=self._node_id), self._deleteCallback)
self.controllerHttpDelete("/nodes/{node_id}".format(node_id=self._node_id), self._deleteCallback, showProgress=False)
else:
self.deleted_signal.emit()
self._module.removeNode(self)
@@ -485,7 +506,7 @@ class Node(BaseNode):
"y": int(y),
"z": int(z)}
self.post("/duplicate", self._duplicateCallback, body=params, timeout=None, progressText="{} is being duplicated".format(self.name()))
self.post("/duplicate", self._duplicateCallback, body=params, timeout=None, showProgress=False)
def _duplicateCallback(self, result, error=False, **kwargs):
"""
@@ -538,7 +559,7 @@ class Node(BaseNode):
del result["properties"]
# Update common element of all nodes
for key in ["x", "y", "z", "locked", "symbol", "label", "console_host", "console", "console_type", "console_auto_start", "custom_adapters"]:
for key in ["x", "y", "z", "locked", "symbol", "label", "console_host", "console", "console_type", "console_auto_start", "custom_adapters", "first_port_name", "port_name_format", "port_segment_size"]:
if key in result:
self._settings[key] = result[key]
@@ -628,11 +649,12 @@ class Node(BaseNode):
from .main_window import MainWindow
general_settings = MainWindow.instance().settings()
if console_type != "telnet":
if not console_type:
console_type = self.consoleType()
if console_type:
if console_type == "vnc":
return general_settings["vnc_console_command"]
if console_type.startswith("spice"):
elif console_type.startswith("spice"):
return general_settings["spice_console_command"]
return general_settings["telnet_console_command"]
@@ -641,7 +663,7 @@ class Node(BaseNode):
Get the console type (serial, telnet or VNC)
"""
console_type = "telnet"
console_type = "none"
if "console_type" in self.settings():
return self.settings()["console_type"]
return console_type
@@ -654,7 +676,7 @@ class Node(BaseNode):
"""
host = self.settings()["console_host"]
if host is None or host == "::" or host == "0.0.0.0":
if host is None or host == "::" or host == "0.0.0.0" or host == "0:0:0:0:0:0:0:0":
host = Controller.instance().host()
return host
@@ -667,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):
"""
@@ -704,10 +727,10 @@ class Node(BaseNode):
nodeTelnetConsole(self, console_port, command)
elif console_type == "vnc":
from .vnc_console import vncConsole
vncConsole(self.consoleHost(), console_port, command)
vncConsole(self, console_port, command)
elif console_type.startswith("spice"):
from .spice_console import spiceConsole
spiceConsole(self.consoleHost(), console_port, command)
spiceConsole(self, console_port, command)
elif console_type == "http" or console_type == "https":
QtGui.QDesktopServices.openUrl(QtCore.QUrl("{console_type}://{host}:{port}{path}".format(console_type=console_type, host=self.consoleHost(), port=console_port, path=self.consoleHttpPath())))
@@ -765,7 +788,7 @@ class Node(BaseNode):
with open(context["path"], "wb+") as f:
f.write(raw_body)
except OSError as e:
log.erro("Can't write %s: %s", context["path"], str(e))
log.error("Cannot export file '{}': {}".format(context["path"], e))
def exportConfigsToDirectory(self, directory):
"""
@@ -774,13 +797,14 @@ class Node(BaseNode):
:param directory: destination directory path
"""
if not hasattr(self, "configFiles"):
return
if not self.configFiles():
return False
for file in self.configFiles():
self.get("/files/{file}".format(file=file),
self._exportConfigsToDirectoryCallback,
context={"directory": directory, "file": file},
raw=True)
return True
def _exportConfigsToDirectoryCallback(self, result, error=False, raw_body=None, context={}, **kwargs):
"""
@@ -812,7 +836,7 @@ class Node(BaseNode):
:param directory: source directory path
"""
if not hasattr(self, "configFiles"):
if not self.configFiles():
return
try:
@@ -842,6 +866,33 @@ class Node(BaseNode):
if error and "message" in result:
log.error("Error while import config: {}".format(result["message"]))
@staticmethod
def isValidRfc1123Hostname(hostname):
"""
Validate a hostname according to RFC 1123
Each element of the hostname must be from 1 to 63 characters long
and the entire hostname, including the dots, can be at most 253
characters long. Valid characters for hostnames are ASCII
letters from a to z, the digits from 0 to 9, and the hyphen (-).
A hostname may not start with a hyphen.
"""
if hostname[-1] == ".":
hostname = hostname[:-1] # strip exactly one dot from the right, if present
if len(hostname) > 253:
return False
labels = hostname.split(".")
# the TLD must be not all-numeric
if re.match(r"[0-9]+$", labels[-1]):
return False
allowed = re.compile(r"(?!-)[a-zA-Z0-9-]{1,63}(?<!-)$")
return all(allowed.match(label) for label in labels)
@staticmethod
def onCloseOptions():
"""

View File

@@ -142,19 +142,30 @@ class NodesView(QtWidgets.QTreeWidget):
# when we're in docked mode, outside main window
return self.window().parent()
def mousePressEvent(self, event):
def contextMenuEvent(self, event):
"""
Handles all mouse press events.
Handles all context menu events.
:param: QMouseEvent instance
:param event: QContextMenuEvent instance
"""
# Check that an item has been selected and right click
if event.button() == QtCore.Qt.RightButton:
self._showContextualMenu()
event.accept()
return
super().mousePressEvent(event)
self._showContextualMenu(event.globalPos())
def mouseDoubleClickEvent(self, event):
"""
Handles all mouse double click events.
:param event: QMouseEvent instance
"""
item = self.itemAt(event.pos())
if item:
template = TemplateManager.instance().getTemplate(item.data(0, QtCore.Qt.UserRole))
if template:
configuration_page = TEMPLATE_TYPE_TO_CONFIGURATION_PAGE.get(template.template_type())
if not template.builtin() and configuration_page:
self._configurationSlot(template, configuration_page)
super().mouseDoubleClickEvent(event)
def mouseMoveEvent(self, event):
"""
@@ -181,7 +192,7 @@ class NodesView(QtWidgets.QTreeWidget):
drag.exec_(QtCore.Qt.CopyAction)
event.accept()
def _showContextualMenu(self):
def _showContextualMenu(self, pos):
menu = QtWidgets.QMenu()
refresh_action = QtWidgets.QAction("Refresh templates", menu)
@@ -207,16 +218,16 @@ class NodesView(QtWidgets.QTreeWidget):
delete_action.triggered.connect(qpartial(self._deleteSlot, template))
menu.addAction(delete_action)
menu.exec_(QtGui.QCursor.pos())
menu.exec_(pos)
def _configurationSlot(self, template, configuration_page, source):
def _configurationSlot(self, template, configuration_page, source=None):
dialog = ConfigurationDialog(template.name(), template.settings(), configuration_page(), parent=self)
dialog.show()
if dialog.exec_():
TemplateManager.instance().updateTemplate(template)
def _deleteSlot(self, template, source):
def _deleteSlot(self, template, source=None):
reply = QtWidgets.QMessageBox.question(self, "Template", "Delete {} template?".format(template.name()),
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)

View File

@@ -38,7 +38,7 @@ class PacketCapture:
def __init__(self):
self._tail_process = {}
self._capture_reader_process = {}
# Auto start the capture program for th link
# Auto start the capture program for this link
self._autostart = {}
Topology.instance().project_changed_signal.connect(self.killAllCapture)
@@ -47,6 +47,7 @@ class PacketCapture:
"""
Kill all running captures (for example when change project)
"""
for process in list(self._tail_process.values()):
try:
process.kill()
@@ -89,12 +90,11 @@ class PacketCapture:
def _updatedLinkSlot(self, link_id):
link = self.topology().getLink(link_id)
if link:
if link.capturing():
if self._autostart.get(link) and link not in self._tail_process:
log.debug("Starting packet capture reader for link {}".format(link.link_id()))
self.startPacketCaptureReader(link)
log.debug("Has successfully started capturing packets on {} to {}".format(link.id(), link.capture_file_path()))
else:
self.stopPacketCaptureReader(link)
@@ -107,20 +107,25 @@ class PacketCapture:
"""
link.stopCapture()
log.debug("Has successfully stopped capturing packets on {}".format(link.id()))
def startPacketCaptureReader(self, link):
"""
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:
self._tail_process[link].kill()
if link in self._tail_process:
log.debug("Stopping packet capture reader for link {}".format(link.link_id()))
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)

Some files were not shown because too many files have changed in this diff Show More