Compare commits

...

126 Commits

Author SHA1 Message Date
grossmj
f3b3d7565d Release v3.0.0a1 2022-08-04 11:38:17 +02:00
Jeremy Grossmann
1d0a173689 Merge pull request #3358 from GNS3/use-themed-symbols
Let the controller allocate symbols
2022-07-25 20:45:40 +02:00
grossmj
b2363434a2 Set default symbol theme to "Affinity-square-blue" 2022-07-25 20:38:26 +02:00
grossmj
ca3e6f0472 Fix creating a custom Ethernet switch template 2022-07-25 17:24:28 +02:00
grossmj
f1b19f4633 Update decorative symbols (for Wizards etc.) 2022-07-25 17:13:23 +02:00
grossmj
ed57ac3de5 Use generic symbol names 2022-07-25 15:12:29 +02:00
grossmj
6b35992e5a Set raw image param when uploading an image from the appliance wizard 2022-07-22 12:40:32 +02:00
grossmj
5f97d7891f Fix incorrect param in getCompute() 2022-07-22 12:06:27 +02:00
Jeremy Grossmann
6a46c26a37 Merge pull request #3355 from GNS3/enhancement/2076
Remove valid hostname checks for Dynamips, IOU, Qemu and Docker nodes
2022-07-17 11:56:29 +02:00
grossmj
d43411dafd Checks for valid hostname on server side for Dynamips, IOU, Qemu and Docker nodes 2022-07-17 11:51:29 +02:00
grossmj
0427383457 Upgrade dependencies 2022-07-15 11:32:18 +02:00
grossmj
991387f483 Fix incorrect call to QProgress.setValue() with float 2022-07-12 00:50:50 +02:00
Jeremy Grossmann
8ff966dd54 Merge pull request #3350 from GNS3/import-project
Project import
2022-07-05 23:08:47 +02:00
grossmj
271850cfec Remove "portable" from project import/export references 2022-07-05 23:07:10 +02:00
grossmj
f09c67dd3d Reactivate project importation 2022-07-05 23:01:44 +02:00
grossmj
aea8e01d13 Merge branch '2.2' into 3.0
# Conflicts:
#	gns3/http_client.py
#	gns3/modules/docker/ui/docker_vm_configuration_page_ui.py
#	gns3/version.py
2022-06-21 16:33:42 +02:00
Jeremy Grossmann
5615141ed7 Merge pull request #3318 from GNS3/remove-qemu-binaries-requirement
Remove Qemu binary requirement
2022-06-06 14:50:52 +08:00
Jeremy Grossmann
c06491b112 Merge pull request #3336 from GNS3/project-export-zstd
zstandard compression support for project export
2022-06-03 11:32:18 +07:00
grossmj
b01c03855e Support compression levels 2022-06-01 20:26:12 +07:00
grossmj
01e101beac Add zstandard compression 2022-06-01 15:35:01 +07:00
grossmj
18eba37b85 Upgrade dependencies 2022-05-26 19:21:37 +07:00
grossmj
3b7fb3329f Merge branch '2.2' into 3.0
# Conflicts:
#	gns3/ui/main_window.ui
#	gns3/ui/main_window_ui.py
#	gns3/version.py
#	requirements.txt
2022-04-27 22:28:16 +07:00
grossmj
5b901fa115 Remove Qemu binary requirement 2022-04-19 18:21:39 +07:00
Jeremy Grossmann
25bd1aa974 Merge pull request #3315 from GNS3/qemu-disk-images
New implementation to create/resize Qemu disk images
2022-04-14 17:23:43 +07:00
grossmj
b0c374a043 Use controller API to list images 2022-04-13 18:13:18 +07:00
grossmj
548663c964 Use new API endpoints to create/resize Qemu disk images. 2022-04-07 16:23:14 +08:00
grossmj
567cd6485f Fix QFileDialog calls 2022-03-30 18:40:50 +08:00
Jeremy Grossmann
d69f00a370 Merge pull request #3306 from GNS3/image-management
Image management dialog
2022-03-20 19:56:47 +10:00
grossmj
1c6eb5eb4d Image management dialog 2022-03-20 19:28:11 +10:00
grossmj
d3c3ae5143 Use README.md in setup.py 2022-03-16 11:22:28 +10:00
grossmj
f2b2e6f618 Drop Python 3.6 support and require Python >= 3.7 2022-03-14 15:13:27 +10:30
grossmj
a617d5aedf Convert README to markdown 2022-03-13 15:44:26 +10:30
grossmj
9e33cd24bb Merge branch '2.2' into 3.0
# Conflicts:
#	gns3/version.py
#	requirements.txt
2022-03-13 14:50:44 +10:30
grossmj
6f767d7455 Upgrade dependencies 2022-03-12 16:23:17 +10:30
Jeremy Grossmann
f3ba40de43 Merge pull request #3270 from GNS3/server-settings-refactoring
Consolidation of "Server" settings and simplified wizard
2022-01-16 21:13:26 +10:00
grossmj
7e60e4021b Merge branch '3.0' into server-settings-refactoring
# Conflicts:
#	gns3/dialogs/setup_wizard.py
#	tests/test_link.py
2022-01-16 21:36:38 +10:30
grossmj
557c47d995 Merge branch 'master' into 3.0
# Conflicts:
#	gns3/version.py
2022-01-11 23:14:01 +10:30
grossmj
ce8f9206d9 Merge branch '2.2' into 3.0
# Conflicts:
#	gns3/http_client.py
2021-12-25 11:21:10 +10:30
grossmj
1ac14e0f6d Improvements when connecting and updating computes 2021-12-24 13:08:10 +10:30
grossmj
e0651e349c Use current directory when searching for images. Fixes #3198 2021-12-19 18:54:07 +10:30
grossmj
15889a0ac5 Merge branch '2.2' into 3.0
# Conflicts:
#	gns3/version.py
2021-12-16 12:35:51 +10:30
grossmj
f38ab34ea0 Fix tests 2021-12-12 17:26:52 +10:30
grossmj
087172d024 Refactor server settings and wizard 2021-12-12 16:35:09 +10:30
grossmj
a46b08bea1 Disable local server and GNS3 VM preferences 2021-12-09 18:52:01 +10:30
grossmj
a6c3e2a4bb Fix project readme edit/refresh 2021-12-09 18:50:36 +10:30
Jeremy Grossmann
f89ff86808 Merge pull request #3266 from GNS3/http-client-refactoring
HTTP client refactoring
2021-12-09 16:31:04 +10:30
grossmj
50cdb2432d Fix tests 2021-12-08 20:03:23 +10:30
grossmj
04ea58395a Image uploading to controller and project export 2021-12-07 23:37:59 +10:30
grossmj
6331f54b88 HTTP client refactoring completed 2021-12-03 18:14:37 +10:30
grossmj
a91683f6ff Merge branch '2.2' into 3.0
# Conflicts:
#	gns3/version.py
2021-12-02 14:28:57 +10:30
grossmj
62b7d29e4c Start HTTP client refactoring 2021-12-02 14:22:46 +10:30
grossmj
926aec9089 Upgrade dependencies 2021-11-24 17:48:58 +10:30
grossmj
586b640967 Handle empty compute_id in preferences. Ref #3265 2021-11-24 17:41:40 +10:30
grossmj
52322eb982 Remove direct upload to compute 2021-11-16 16:44:28 +10:30
grossmj
340ec2d543 Send JWT token in query string when connecting to websocket. Ref https://github.com/GNS3/gns3-server/pull/1992 2021-11-01 16:52:43 +10:30
grossmj
2d3c7ac0c2 Remove traceng code 2021-10-22 16:11:12 +10:30
grossmj
3cd15e5f1a Option to delete orphaned image files from disk when template is removed. Fixes #3249 2021-10-21 15:59:29 +10:30
grossmj
1a2d3d65c1 Remove Qemu legacy networking code 2021-10-20 15:59:05 +10:30
grossmj
c93a543ccb Update appliance files 2021-10-17 15:09:49 +10:30
grossmj
579fb6ceb8 Merge branch 'master' into 3.0
# Conflicts:
#	gns3/version.py
2021-10-09 12:13:24 +10:30
grossmj
391ac73f1a Merge branch '2.2' into 3.0 2021-10-07 21:01:46 +10:30
grossmj
eedec3f999 Upgrade dependencies and test using Python 3.10 2021-10-07 14:59:54 +10:30
grossmj
c103e2deba Upgrade dependencies 2021-09-23 10:39:27 +09:30
grossmj
48e7ef07cf Isolate and unisolate support. Fixes https://github.com/GNS3/gns3-gui/issues/3190 2021-09-15 18:48:02 +09:30
grossmj
c6f8198974 Merge branch '2.2' into 3.0
# Conflicts:
#	gns3/version.py
2021-09-15 16:58:57 +09:30
grossmj
c4561e81eb Support authentication using JWT tokens 2021-09-11 19:53:40 +09:30
grossmj
b3603ea364 Merge branch '2.2' into 3.0
# Conflicts:
#	gns3/node.py
#	gns3/version.py
#	win-requirements.txt
2021-09-09 16:27:25 +09:30
grossmj
dd44582347 Merge branch 'master' into 3.0
# Conflicts:
#	dev-requirements.txt
#	gns3/version.py
#	win-requirements.txt
2021-08-29 19:15:35 +09:30
grossmj
270301d9b5 Bump version to 3.0.0dev3 2021-08-12 16:05:20 +09:30
grossmj
4614543edf Upgrade dependencies 2021-08-10 23:08:14 +09:30
grossmj
80b7ece5a8 Merge branch '2.2' into 3.0
# Conflicts:
#	gns3/version.py
#	mac-requirements.txt
2021-08-10 15:28:01 +09:30
grossmj
54511b2c45 Upgrade dependencies. 2021-06-12 15:30:38 +09:30
grossmj
ed36917cf3 Fix tests 2021-06-12 15:09:18 +09:30
grossmj
7545c600bc Merge branch '2.2' into 3.0
# Conflicts:
#	gns3/items/link_item.py
#	gns3/main_window.py
#	gns3/ui/resources_rc.py
#	gns3/version.py
#	win-requirements.txt
2021-06-12 14:25:47 +09:30
grossmj
4dd7db5a86 Merge branch '2.2' into 3.0
# Conflicts:
#	gns3/version.py
#	win-requirements.txt
2021-05-15 20:12:52 +09:30
grossmj
6d2ffc4614 Revert "Rename __json__() to asdict()"
This reverts commit bc14f15a
2021-04-17 23:38:08 +09:30
grossmj
bc14f15a61 Rename __json__() to asdict() 2021-04-17 23:34:28 +09:30
grossmj
2c7de627f7 Merge branch 'master' into 3.0
# Conflicts:
#	gns3/version.py
#	requirements.txt
#	win-requirements.txt
2021-04-10 12:31:08 +09:30
grossmj
76d9fcf60e Fix merge 2021-02-16 20:58:00 +10:30
grossmj
c3049ea843 Merge branch '2.2' into 3.0
# Conflicts:
#	gns3/modules/qemu/pages/qemu_vm_configuration_page.py
#	gns3/modules/qemu/qemu_vm.py
#	gns3/modules/qemu/ui/qemu_vm_configuration_page.ui
#	gns3/modules/qemu/ui/qemu_vm_configuration_page_ui.py
#	gns3/version.py
2021-02-16 20:55:41 +10:30
grossmj
d6432c2e88 Merge branch '2.2' into 3.0
# Conflicts:
#	gns3/version.py
2020-12-07 17:56:09 +10:30
grossmj
89b9e6c332 Providing the path to create a project is now deprecated. 2020-11-13 15:17:55 +10:30
grossmj
54b5d8f347 Upgrade packages to latest versions 2020-11-08 21:35:55 +10:30
grossmj
237ef785ad Fix tests 2020-11-05 18:26:15 +10:30
grossmj
0ddd91af43 Merge branch '2.2' into 3.0
# Conflicts:
#	gns3/link.py
#	gns3/version.py
2020-11-05 17:05:58 +10:30
grossmj
7f5db61722 Client to use version 3 of the API. 2020-11-02 18:15:43 +10:30
grossmj
fcc5c4c114 Merge remote-tracking branch 'origin/3.0' into 3.0 2020-10-19 18:28:30 +10:30
grossmj
68f3dc763d Fix some API calls. 2020-10-19 18:28:17 +10:30
Jeremy Grossmann
786306304b Merge pull request #3075 from b-ehlers/fix_hdd_layout
Fix HDD configuration layout
2020-10-16 19:21:41 +10:30
Bernhard Ehlers
4f631669e5 Fix HDD configuration layout 2020-10-16 10:22:32 +02:00
grossmj
0b8fb93752 Merge branch '2.3' into 3.0
# Conflicts:
#	gns3/version.py
2020-10-12 21:21:51 +10:30
grossmj
422f6004b1 Bump version to 3.0.0dev1 2020-10-12 17:48:32 +10:30
grossmj
717d683b44 Merge branch '2.2' into 3.0 2020-10-12 17:47:26 +10:30
grossmj
932f737ed9 Create branch 3.0 2020-10-06 11:59:24 +10:30
grossmj
64a0ee37de Change Qemu disk descriptions. Fixes #3035 2020-08-18 18:07:01 +09:30
Jeremy Grossmann
ec8d214c08 Merge pull request #3040 from b-ehlers/edit_config_text
Edit only text mode config files
2020-08-18 08:44:56 +08:00
Bernhard Ehlers
880ac5e8c3 Edit only text mode config files 2020-08-18 02:27:31 +02:00
Jeremy Grossmann
feb40a6250 Merge pull request #3039 from b-ehlers/hide_empty_config
Hide config import/export when configFiles attribute is empty
2020-08-17 19:36:34 +08:00
Bernhard Ehlers
fd7b915e96 Hide config import/export when configFiles attribute is empty 2020-08-17 13:09:59 +02:00
grossmj
5fbb6cbf61 Qemu disk interfaces must be set to "none" by default. Ref #3035 2020-08-17 12:49:21 +09:30
Jeremy Grossmann
fda948cc5b Merge pull request #3024 from GNS3/qemu-config-disk
QEMU config disk - enable QEMU config import/export
2020-08-15 16:40:13 +08:00
Jeremy Grossmann
a9b18f1771 Merge branch '2.3' into qemu-config-disk 2020-08-15 16:36:58 +08:00
grossmj
04f9a1cf8c Do not allow image to be configured on Qemu VM secondary slave disk if create config disk option is enabled. 2020-08-15 16:05:43 +09:30
grossmj
af79471afd Add explicit option to automatically create or not the config disk. Off by default. 2020-08-14 17:57:24 +09:30
grossmj
6067786783 Bump version to 2.3.0dev2 2020-08-13 01:49:25 +09:30
grossmj
090fc63bb6 Merge branch '2.2' into 2.3
# Conflicts:
#	gns3/version.py
2020-08-13 01:48:49 +09:30
Jeremy Grossmann
029a1df7f7 Merge pull request #3029 from GNS3/aux-console-refactoring
Auxiliary console support
2020-08-12 15:59:04 +08:00
Jeremy Grossmann
b47aa95b3e Downgrade psutil to version 5.6.7 2020-07-29 17:36:46 +09:30
grossmj
ffb364591f Auxiliary console support for Qemu. Ref #2873
Improvements for auxiliary console support for Docker and Dynamips.
2020-07-29 16:23:51 +09:30
grossmj
6a9440c978 Support to reset all console connections. Ref https://github.com/GNS3/gns3-server/issues/1619 2020-07-26 18:27:18 +09:30
grossmj
758054cfd3 Support to reset links. Fixes https://github.com/GNS3/gns3-server/issues/1620 2020-07-24 21:48:49 +09:30
grossmj
39e6a6e2ab Fix bug when recent files cannot be seen in the new project dialog. 2020-07-24 18:49:32 +09:30
grossmj
7a74685c0a Wait for the controller to be online before allowing actions like creating or opening a project. Fixes #2907 2020-07-22 17:32:01 +09:30
grossmj
f48eff2344 Upgrade to psutil version 5.7.2 2020-07-21 15:49:06 +09:30
grossmj
b8a583d3f6 Show progress dialog immediately when connecting to server. Ref #2907 2020-07-20 19:14:54 +09:30
Bernhard Ehlers
d01f15c4df QEMU config disk - enable QEMU config import/export 2020-07-19 17:56:24 +09:30
grossmj
3cbad22a04 Fix tests 2020-07-19 14:27:09 +09:30
grossmj
c61a99e78d Add total RAM, CPUs and disk size to servers summary as well as disk usage in percent. Fixes https://github.com/GNS3/gns3-server/issues/1532 2020-07-19 14:16:07 +09:30
Jeremy Grossmann
e318610983 Merge pull request #3023 from GNS3/docker-resource-constraints
Resource constraints for Docker VMs.
2020-07-18 19:39:08 +08:00
grossmj
ab7cc29fa2 Resource constraints for Docker VMs. 2020-07-18 21:03:55 +09:30
grossmj
2788019e17 Do not show project readme when loading a snapshot. 2020-07-18 17:41:05 +09:30
grossmj
819bb1c58e Wait for readme to be updated before exporting the project. 2020-07-17 16:36:35 +09:30
grossmj
723f806a52 Support for "usage" for "Cloud" nodes. Fixes https://github.com/GNS3/gns3-gui/issues/2887
Allow "usage" for all builtin nodes (not exposed in Ui).
2020-07-15 19:15:51 +09:30
grossmj
a5093e06d1 Markdown support in project Readme. Fixes #2550 #2289
Allow project README to be edited from "File->Edit project". Fixes #2829
2020-07-14 22:40:05 +09:30
grossmj
f33c01ac58 Bump PyQt version to 5.15.0 LTS. Ref #3020 2020-07-13 14:07:38 +09:30
grossmj
8c382e5b7d Bump version to 2.3.0dev1 2020-07-11 11:41:51 +09:30
201 changed files with 118272 additions and 135304 deletions

View File

@@ -1,5 +1,54 @@
# Change Log
## 3.0.0a1 04/08/2022
* Set default symbol theme to "Affinity-square-blue"
* Fix creating a custom Ethernet switch template
* Update decorative symbols (for Wizards etc.)
* Use generic symbol names
* Set raw image param when uploading an image from the appliance wizard
* Checks for valid hostname on server side for Dynamips, IOU, Qemu and Docker nodes
* Support compression levels
* Add zstandard compression
* Remove Qemu binary requirement
* Use controller API to list images
* Use new API endpoints to create/resize Qemu disk images.
* Image management dialog
* Drop Python 3.6 support and require Python >= 3.7
* Improvements when connecting and updating computes
* Use current directory when searching for images. Fixes #3198
* Refactor server settings and wizard
* Disable local server and GNS3 VM preferences
* Image uploading to controller and project export
* HTTP client refactoring
* Handle empty compute_id in preferences. Ref #3265
* Remove direct upload to compute
* Send JWT token in query string when connecting to websocket. Ref https://github.com/GNS3/gns3-server/pull/1992
* Remove traceng code
* Option to delete orphaned image files from disk when template is removed. Fixes #3249
* Remove Qemu legacy networking code
* Isolate and unisolate support. Fixes https://github.com/GNS3/gns3-gui/issues/3190
* Support authentication using JWT tokens
* Providing the path to create a project is now deprecated.
* Client to use version 3 of the API.
* Change Qemu disk descriptions. Fixes #3035
* 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.
* Auxiliary console support for Qemu. Ref #2873 Improvements for auxiliary console support for Docker and Dynamips.
* Support to reset all console connections. Ref https://github.com/GNS3/gns3-server/issues/1619
* Support to reset links. Fixes https://github.com/GNS3/gns3-server/issues/1620
* Fix bug when recent files cannot be seen in the new project dialog.
* Wait for the controller to be online before allowing actions like creating or opening a project. Fixes #2907
* Show progress dialog immediately when connecting to server. Ref #2907
* QEMU config disk - enable QEMU config import/export
* Add total RAM, CPUs and disk size to servers summary as well as disk usage in percent. Fixes https://github.com/GNS3/gns3-server/issues/1532
* Resource constraints for Docker VMs.
* Support for "usage" for "Cloud" nodes. Fixes https://github.com/GNS3/gns3-gui/issues/2887 Allow "usage" for all builtin nodes (not exposed in Ui).
* Markdown support in project Readme. Fixes #2550 #2289 Allow project README to be edited from "File->Edit project". Fixes #2829
## 2.2.33.1 21/06/2022
* Match GNS3 server version

View File

@@ -1,59 +1,45 @@
GNS3-gui
========
# GNS3-gui
.. image:: https://github.com/GNS3/gns3-gui/workflows/testing/badge.svg
:target: https://github.com/GNS3/gns3-gui/actions?query=workflow%3Atesting
[![GitHub Actions tests](https://github.com/GNS3/gns3-gui/workflows/testing/badge.svg)](https://github.com/GNS3/gns3-gui/actions?query=workflow%3Atesting)
[![Latest PyPi version](https://img.shields.io/pypi/v/gns3-gui.svg)](https://pypi.python.org/pypi/gns3-gui)
[![Snyk scanning](https://snyk.io/test/github/GNS3/gns3-gui/badge.svg)](https://snyk.io/test/github/GNS3/gns3-gui)
.. image:: https://img.shields.io/pypi/v/gns3-gui.svg
:target: https://pypi.python.org/pypi/gns3-gui
## Installation
.. image:: https://snyk.io/test/github/GNS3/gns3-gui/badge.svg
:target: https://snyk.io/test/github/GNS3/gns3-gui
Please see the documentation on our [website](https://docs.gns3.com)
## Software dependencies
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>`_
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 in [requirements.txt](https://github.com/GNS3/gns3-gui/blob/3.0/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
-------------
## Development
If you want to update the interface, modify the .ui files using QT tools. And:
.. code:: bash
```shell
cd scripts
python build_pyqt.py
```
cd scripts
python build_pyqt.py
Debug
"""""
### Debugging
If you want to see the full logs in the internal shell you can type:
.. code:: bash
```shell
debug 2
```
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
----------------
## Security issues
Please contact us at security@gns3.net

View File

@@ -1,5 +1,5 @@
-rrequirements.txt
-r requirements.txt
pytest==6.2.4
flake8==3.9.2
pytest-timeout==1.4.2
pytest==6.2.5
flake8==4.0.1
pytest-timeout==2.0.1

View File

@@ -49,7 +49,12 @@ class ApplianceManager(QtCore.QObject):
settings = LocalConfig.instance().loadSectionSettings("MainWindow", GENERAL_SETTINGS)
symbol_theme = settings["symbol_theme"]
if update is True:
self._controller.get("/appliances?update=yes&symbol_theme={}".format(symbol_theme), self._listAppliancesCallback, progressText="Downloading appliances from online registry...")
self._controller.get(
"/appliances?update=yes&symbol_theme={}".format(symbol_theme),
self._listAppliancesCallback,
progress_text="Downloading appliances from online registry...",
wait=True
)
else:
self._controller.get("/appliances?symbol_theme={}".format(symbol_theme), self._listAppliancesCallback)

View File

@@ -37,6 +37,7 @@ class Compute:
self._password = None
self._cpu_usage_percent = None
self._memory_usage_percent = None
self._disk_usage_percent = None
self._capabilities = {"node_types": []}
self._last_error = None
@@ -202,6 +203,24 @@ class Compute:
return self._memory_usage_percent
def setDiskUsagePercent(self, usage):
"""
Sets the compute disk usage.
:returns: disk usage (integer)
"""
self._disk_usage_percent = usage
def diskUsagePercent(self):
"""
Returns the compute disk usage.
:param usage: disk usage (integer)
"""
return self._disk_usage_percent
def capabilities(self):
"""
Returns the compute capabilities

View File

@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from .qt import QtCore
from .qt import QtCore, QtWidgets
from .compute import Compute
from .controller import Controller
@@ -50,11 +50,11 @@ class ComputeManager(QtCore.QObject):
# No need to refresh via an API call if we received fresh data from the notification feed
self._last_computes_refresh = datetime.datetime.now().timestamp()
self._timer = QtCore.QTimer()
self._timer.setInterval(1000)
self._refreshingComputes = False
self._timer.timeout.connect(self._refreshComputesSlot)
self._timer.start()
# self._timer = QtCore.QTimer()
# self._timer.setInterval(1000)
# self._timer.timeout.connect(self._refreshComputesSlot)
# self._timer.start()
def _refreshComputesSlot(self):
"""
@@ -66,7 +66,7 @@ class ComputeManager(QtCore.QObject):
if self._controller.connected() and datetime.datetime.now().timestamp() - self._last_computes_refresh > 1:
self._last_computes_refresh = datetime.datetime.now().timestamp()
self._refreshingComputes = True
self._controller.get("/computes", self._listComputesCallback, showProgress=False, timeout=30)
self._controller.get("/computes", self._listComputesCallback, show_progress=False, timeout=30)
def _controllerConnectedSlot(self):
"""
@@ -75,11 +75,11 @@ class ComputeManager(QtCore.QObject):
if self._controller.connected():
self._refreshingComputes = True
self._controller.get("/computes", self._listComputesCallback, showProgress=False, timeout=30)
self._controller.get("/computes", self._listComputesCallback, show_progress=False, timeout=30)
def _controllerDisconnectedSlot(self):
"""
Called when disconnected from a compute.
Called when disconnected from the controller.
"""
for compute_id in list(self._computes):
@@ -96,8 +96,9 @@ class ComputeManager(QtCore.QObject):
log.error("Error while getting compute list: {}".format(result["message"]))
return
for compute in result:
self.computeDataReceivedCallback(compute)
if result:
for compute in result:
self.computeDataReceivedCallback(compute)
def computeDataReceivedCallback(self, compute):
"""
@@ -113,15 +114,17 @@ class ComputeManager(QtCore.QObject):
self._computes[compute_id] = Compute(compute_id)
self._computes[compute_id].setName(compute["name"])
self._computes[compute_id].setConnected(compute["connected"])
self._computes[compute_id].setProtocol(compute["protocol"])
self._computes[compute_id].setHost(compute["host"])
self._computes[compute_id].setPort(compute["port"])
self._computes[compute_id].setUser(compute["user"])
self._computes[compute_id].setCpuUsagePercent(compute["cpu_usage_percent"])
self._computes[compute_id].setMemoryUsagePercent(compute["memory_usage_percent"])
self._computes[compute_id].setCapabilities(compute["capabilities"])
self._computes[compute_id].setLastError(compute.get("last_error"))
if "connected" in compute:
self._computes[compute_id].setConnected(compute["connected"])
self._computes[compute_id].setCpuUsagePercent(compute["cpu_usage_percent"])
self._computes[compute_id].setMemoryUsagePercent(compute["memory_usage_percent"])
self._computes[compute_id].setDiskUsagePercent(compute["disk_usage_percent"])
self._computes[compute_id].setCapabilities(compute["capabilities"])
self._computes[compute_id].setLastError(compute.get("last_error"))
if new_node:
self.created_signal.emit(compute_id)
@@ -208,6 +211,20 @@ class ComputeManager(QtCore.QObject):
self.created_signal.emit(compute_id)
return self._computes[compute_id]
def connectToCompute(self, compute_id):
"""
Connect to a compute
"""
self._controller.post(f"/computes/{compute_id}/connect", callback=self._computeConnectCallback)
def _computeConnectCallback(self, result, error=False, **kwargs):
if error and "message" in result:
from gns3.main_window import MainWindow
parent = MainWindow.instance()
QtWidgets.QMessageBox.critical(parent, "Remote compute", result.get("message"))
def deleteCompute(self, compute_id):
"""
Deletes a compute by ID
@@ -245,10 +262,16 @@ class ComputeManager(QtCore.QObject):
for compute in computes:
if compute.id() not in self._computes:
log.debug("Create compute %s", compute.id())
self._controller.post("/computes", None, body=compute.__json__())
params = {"connect": True}
self._controller.post("/computes", callback=self._computeCreatedCallback, body=compute.__json__(), params=params)
self._computes[compute.id()] = compute
self.created_signal.emit(compute.id())
def _computeCreatedCallback(self, result, error=False, **kwargs):
if error:
log.error(result.get("message"))
@staticmethod
def reset():
ComputeManager._instance = None

View File

@@ -23,6 +23,8 @@ from .qt import QtGui, QtCore, QtWidgets
from .compute_manager import ComputeManager
from .topology import Topology
from .node import Node
from .utils import human_size
from .utils.get_icon import get_icon
import logging
log = logging.getLogger(__name__)
@@ -43,9 +45,16 @@ class ComputeItem(QtWidgets.QTreeWidgetItem):
self._compute = compute
self._parent = parent
self._status = "unknown"
self._refreshStatusSlot()
def getCompute(self):
return self._compute
def setCompute(self, compute):
self._compute = compute
def _refreshStatusSlot(self):
"""
Changes the icon to show the node status (started, stopped etc.)
@@ -58,14 +67,20 @@ class ComputeItem(QtWidgets.QTreeWidgetItem):
text = self._compute.name()
if self._compute.cpuUsagePercent() is not None:
text = "{} CPU {}%, RAM {}%".format(text, self._compute.cpuUsagePercent(), self._compute.memoryUsagePercent())
text = "{} CPU {}%, RAM {}%, DISK {}%".format(text,
self._compute.cpuUsagePercent(),
self._compute.memoryUsagePercent(),
self._compute.diskUsagePercent())
self.setText(0, text)
if self._compute.connected():
self._status = "connected"
self.setToolTip(0, "Server {} version {} running on {}".format(self._compute.name(),
self._compute.capabilities().get("version", "n/a"),
self._compute.capabilities().get("platform", "")))
self.setToolTip(0, "Server {} v{} running on {} (CPUs={} / RAM={} / DISK={})".format(self._compute.name(),
self._compute.capabilities().get("version", "n/a"),
self._compute.capabilities().get("platform", ""),
self._compute.capabilities().get("cpus", 0),
human_size(self._compute.capabilities().get("memory", 0)),
human_size(self._compute.capabilities().get("disk_size", 0))))
if usage is None or (self._compute.cpuUsagePercent() < 90 and self._compute.memoryUsagePercent() < 90):
self.setIcon(0, QtGui.QIcon(':/icons/led_green.svg'))
else:
@@ -111,13 +126,44 @@ class ComputeSummaryView(QtWidgets.QTreeWidget):
def __init__(self, parent):
super().__init__(parent)
self._computes = {}
self._compute_items = {}
ComputeManager.instance().created_signal.connect(self._computeAddedSlot)
ComputeManager.instance().updated_signal.connect(self._computeUpdatedSlot)
ComputeManager.instance().deleted_signal.connect(self._computeRemovedSlot)
for compute in ComputeManager.instance().computes():
self._computeAddedSlot(compute.id())
def contextMenuEvent(self, event):
"""
Handles all context menu events.
:param event: QContextMenuEvent instance
"""
self._showContextualMenu(event.globalPos())
def _showContextualMenu(self, pos):
item = self.currentItem()
if item:
compute = item.getCompute()
if not compute.connected():
menu = QtWidgets.QMenu()
connect_action = QtWidgets.QAction("Connect to server", menu)
connect_action.setIcon(get_icon("start.svg"))
connect_action.triggered.connect(lambda: ComputeManager.instance().connectToCompute(compute.id()))
menu.addAction(connect_action)
menu.exec_(pos)
def _computeConnectSlot(self, compute_id):
"""
:param compute_id:
:return:
"""
ComputeManager.instance().connectToCompute(compute_id)
def _computeAddedSlot(self, compute_id):
"""
Called when a compute is added to the list of computes
@@ -128,7 +174,7 @@ class ComputeSummaryView(QtWidgets.QTreeWidget):
compute = ComputeManager.instance().getCompute(compute_id)
if ComputeManager.instance().computeIsTheRemoteGNS3VM(compute):
return
self._computes[compute_id] = ComputeItem(self, compute)
self._compute_items[compute_id] = ComputeItem(self, compute)
def _computeUpdatedSlot(self, compute_id):
"""
@@ -137,13 +183,14 @@ class ComputeSummaryView(QtWidgets.QTreeWidget):
:params url: URL of the compute
"""
if compute_id in self._computes:
if compute_id in self._compute_items:
compute = ComputeManager.instance().getCompute(compute_id)
# We hide the remote GNS3 VM
if ComputeManager.instance().computeIsTheRemoteGNS3VM(compute):
self._computeRemovedSlot(compute_id)
else:
self._computes[compute_id]._refreshStatusSlot()
self._compute_items[compute_id].setCompute(compute)
self._compute_items[compute_id]._refreshStatusSlot()
else:
self._computeAddedSlot(compute_id)
@@ -154,6 +201,6 @@ class ComputeSummaryView(QtWidgets.QTreeWidget):
:params url: URL of the compute
"""
if compute_id in self._computes:
self.takeTopLevelItem(self.indexOfTopLevelItem(self._computes[compute_id]))
del self._computes[compute_id]
if compute_id in self._compute_items:
self.takeTopLevelItem(self.indexOfTopLevelItem(self._compute_items[compute_id]))
del self._compute_items[compute_id]

View File

@@ -21,11 +21,12 @@ import tempfile
import json
import pathlib
from .qt import QtCore, QtNetwork, QtGui, QtWidgets, QtWebSockets, qpartial, qslot
from .qt import QtCore, QtGui, QtWebSockets, qpartial, qslot
from .symbol import Symbol
from .local_server_config import LocalServerConfig
from .settings import LOCAL_SERVER_SETTINGS
from .local_config import LocalConfig
from .settings import CONTROLLER_SETTINGS
from gns3.utils import parse_version
from gns3.http_client import HTTPClient
import logging
log = logging.getLogger(__name__)
@@ -40,6 +41,7 @@ class Controller(QtCore.QObject):
disconnected_signal = QtCore.Signal()
connection_failed_signal = QtCore.Signal()
project_list_updated_signal = QtCore.Signal()
image_list_updated_signal = QtCore.Signal()
def __init__(self):
@@ -48,22 +50,54 @@ class Controller(QtCore.QObject):
self._connecting = False
self._notification_stream = None
self._version = None
self._http_client = None
self._cache_directory = tempfile.TemporaryDirectory(suffix="-gns3")
self._http_client = None
self._first_error = True
self._error_dialog = None
self._display_error = True
self._projects = []
self._images = []
self._websocket = QtWebSockets.QWebSocket()
# If we do multiple call in order to download the same symbol we queue them
self._static_asset_download_queue = {}
self._loadSettings()
def settings(self):
"""
Returns the graphics view settings.
:returns: settings dictionary
"""
return self._settings
def _loadSettings(self):
"""
Loads the settings from the persistent settings file.
"""
self._settings = LocalConfig.instance().loadSectionSettings(self.__class__.__name__, CONTROLLER_SETTINGS)
def setSettings(self, new_settings):
"""
Set new controller settings.
:param new_settings: settings dictionary
"""
# save the settings
self._settings.update(new_settings)
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
def host(self):
return self._http_client.host()
def version(self):
return self._version
def isRemote(self):
@@ -71,8 +105,7 @@ class Controller(QtCore.QObject):
:returns Boolean: True if the controller is remote
"""
settings = LocalServerConfig.instance().loadSettings("Server", LOCAL_SERVER_SETTINGS)
return not settings["auto_start"]
return self._settings["remote"]
def connecting(self):
"""
@@ -102,10 +135,8 @@ class Controller(QtCore.QObject):
self._http_client = http_client
if self._http_client:
if self.isRemote():
self._http_client.setMaxTimeDifferenceBetweenQueries(120)
self._http_client.connection_connected_signal.connect(self._httpClientConnectedSlot)
self._http_client.connection_disconnected_signal.connect(self._httpClientDisconnectedSlot)
self._http_client.connected_signal.connect(self._httpClientConnectedSlot)
self._http_client.disconnected_signal.connect(self._httpClientDisconnectedSlot)
self._connectingToServer()
def getHttpClient(self):
@@ -123,6 +154,14 @@ class Controller(QtCore.QObject):
self._display_error = val
self._first_error = True
def connect(self):
"""
Connect to controller
"""
self._http_client = HTTPClient(self._settings)
Controller.instance().setHttpClient(self._http_client)
def _connectingToServer(self):
"""
Connection process as started
@@ -130,46 +169,16 @@ class Controller(QtCore.QObject):
self._connected = False
self._connecting = True
status, json_data = self.httpClient().getSynchronous('GET', '/version', timeout=60)
self._versionGetSlot(json_data, status is None or status >= 300)
self.httpClient().connectToServer()
def _httpClientDisconnectedSlot(self):
if self._connected:
self._connected = False
self.disconnected_signal.emit()
self._connectingToServer()
self.stopListenNotifications()
def _versionGetSlot(self, result, error=False, **kwargs):
"""
Called after the initial version get
"""
if error:
if self._first_error:
self._connecting = False
self.connection_failed_signal.emit()
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")
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
QtCore.QTimer.singleShot(5000, qpartial(self.get, '/version', self._versionGetSlot, showProgress=self._first_error))
self._first_error = False
else:
self._first_error = True
if self._error_dialog:
self._error_dialog.reject()
self._error_dialog = None
self._version = result.get("version")
self._http_client.connection_connected_signal.emit()
def _httpClientConnectedSlot(self):
if not self._connected:
@@ -180,16 +189,16 @@ class Controller(QtCore.QObject):
self._startListenNotifications()
def post(self, *args, **kwargs):
return self.createHTTPQuery("POST", *args, **kwargs)
return self.request("POST", *args, **kwargs)
def get(self, *args, **kwargs):
return self.createHTTPQuery("GET", *args, **kwargs)
return self.request("GET", *args, **kwargs)
def put(self, *args, **kwargs):
return self.createHTTPQuery("PUT", *args, **kwargs)
return self.request("PUT", *args, **kwargs)
def delete(self, *args, **kwargs):
return self.createHTTPQuery("DELETE", *args, **kwargs)
return self.request("DELETE", *args, **kwargs)
def getCompute(self, path, compute_id, *args, **kwargs):
"""
@@ -225,31 +234,13 @@ class Controller(QtCore.QObject):
return compute_id
return compute_id
def getEndpoint(self, path, compute_id, *args, **kwargs):
"""
API post on a specific compute
"""
compute_id = self.__fix_compute_id(compute_id)
path = "/computes/endpoint/{}{}".format(compute_id, path)
return self.get(path, *args, **kwargs)
def putCompute(self, path, compute_id, *args, **kwargs):
"""
API put on a specific compute
"""
compute_id = self.__fix_compute_id(compute_id)
path = "/computes/{}{}".format(compute_id, path)
return self.put(path, *args, **kwargs)
def createHTTPQuery(self, method, path, *args, **kwargs):
def request(self, method, path, *args, **kwargs):
"""
Forward the query to the HTTP client or controller depending of the path
"""
if self._http_client:
return self._http_client.createHTTPQuery(method, path, *args, **kwargs)
return self._http_client.sendRequest(method, path, *args, **kwargs)
@staticmethod
def instance():
@@ -282,9 +273,10 @@ class Controller(QtCore.QObject):
self._static_asset_download_queue[path].append((callback, fallback, ))
else:
self._static_asset_download_queue[path] = [(callback, fallback, )]
self._http_client.createHTTPQuery("GET", url, qpartial(self._getStaticCallback, url, path))
self._http_client.sendRequest("GET", url, qpartial(self._getStaticCallback, url, path), raw=True)
def _getStaticCallback(self, url, path, result, error=False, **kwargs):
def _getStaticCallback(self, url, path, result, error=False, raw_body=None, **kwargs):
if path not in self._static_asset_download_queue:
return
@@ -300,7 +292,7 @@ class Controller(QtCore.QObject):
return
try:
with open(path, "wb+") as f:
f.write(raw_body)
f.write(result)
except OSError as e:
log.error("Can't write to {}: {}".format(path, str(e)))
return
@@ -364,9 +356,14 @@ class Controller(QtCore.QObject):
def uploadSymbol(self, symbol_id, path):
self.post("/symbols/" + symbol_id + "/raw",
qpartial(self._finishSymbolUpload, path),
body=pathlib.Path(path), progressText="Uploading {}".format(symbol_id), timeout=None)
self.post(
"/symbols/" + symbol_id + "/raw",
qpartial(self._finishSymbolUpload, path),
body=pathlib.Path(path),
progress_text="Uploading {}".format(symbol_id),
timeout=None,
wait=True
)
def _finishSymbolUpload(self, path, result, error=False, **kwargs):
@@ -407,23 +404,34 @@ class Controller(QtCore.QObject):
def projects(self):
return self._projects
@qslot
def refreshImageList(self, *args):
self.get("/images", self._imageListCallback)
def _imageListCallback(self, result, error=False, **kwargs):
if not error:
self._images = result
self.image_list_updated_signal.emit()
def images(self):
return self._images
def _startListenNotifications(self):
if not self.connected():
return
# Due to bug in Qt on some version we need a dedicated network manager
self._notification_network_manager = QtNetwork.QNetworkAccessManager()
self._notification_stream = None
# Qt websocket before Qt 5.6 doesn't support auth
if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.6.0") or parse_version(QtCore.PYQT_VERSION_STR) < parse_version("5.6.0"):
self._notification_stream = Controller.instance().createHTTPQuery("GET", "/notifications", self._endListenNotificationCallback,
downloadProgressCallback=self._event_received,
networkManager=self._notification_network_manager,
timeout=None,
showProgress=False,
ignoreErrors=True)
self._notification_stream = Controller.instance().request(
"GET",
"/notifications",
self._endListenNotificationCallback,
download_progress_callback=self._event_received,
timeout=None,
show_progress=False
)
else:
self._notification_stream = self._http_client.connectWebSocket(self._websocket, "/notifications/ws")
self._notification_stream.textMessageReceived.connect(self._websocket_event_received)
@@ -436,7 +444,6 @@ class Controller(QtCore.QObject):
stream = self._notification_stream
self._notification_stream = None
stream.abort()
self._notification_network_manager = None
def _endListenNotificationCallback(self, result, error=False, **kwargs):
"""
@@ -450,8 +457,7 @@ class Controller(QtCore.QObject):
def _websocket_error(self, error):
if self._notification_stream:
log.error("Websocket notification stream error: {}".format(self._notification_stream.errorString()))
self._notification_stream = None
self._startListenNotifications()
self.stopListenNotifications()
@qslot
def _sslErrorsSlot(self, ssl_errors):

View File

@@ -51,7 +51,7 @@ class CrashReport:
Report crash to a third party service
"""
DSN = "https://439b7b27129f4c46971fe7bdc38738de@o19455.ingest.sentry.io/38506"
DSN = "https://c2d4584e0a7b41bb8139956a84967e35@o19455.ingest.sentry.io/38506"
_instance = None
def __init__(self):

View File

@@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
from ..qt import sip
import shutil
@@ -29,7 +30,7 @@ from ..registry.registry import Registry
from ..registry.config import Config
from ..registry.appliance_to_template import ApplianceToTemplate
from ..registry.image import Image
from ..utils import human_filesize
from ..utils import human_size
from ..utils.wait_for_lambda_worker import WaitForLambdaWorker
from ..utils.progress_dialog import ProgressDialog
from ..compute_manager import ComputeManager
@@ -52,7 +53,6 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
self.setupUi(self)
self._refreshing = False
self._server_check = False
self._template_created = False
self._path = path
@@ -75,6 +75,12 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
# directories where to search for images
images_directories = list()
# add the current directory
if hasattr(sys, "frozen"):
images_directories.append(os.path.dirname(os.path.abspath(sys.executable)))
else:
images_directories.append(os.path.abspath(os.curdir))
for emulator in ("QEMU", "IOU", "DYNAMIPS"):
emulator_images_dir = ImageManager.instance().getDirectoryForType(emulator)
if os.path.exists(emulator_images_dir):
@@ -100,9 +106,6 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
# customize the server selection
self.uiRemoteRadioButton.toggled.connect(self._remoteServerToggledSlot)
if hasattr(self, "uiVMRadioButton"):
self.uiVMRadioButton.toggled.connect(self._vmToggledSlot)
self.uiLocalRadioButton.toggled.connect(self._localToggledSlot)
if Controller.instance().isRemote():
self.uiLocalRadioButton.setText("Install the appliance on the main server")
@@ -136,7 +139,12 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
# add symbol
if self._appliance["category"] == "guest":
symbol = ":/symbols/computer.svg"
if self._appliance.emulator() == "docker":
symbol = ":/symbols/docker_guest.svg"
elif self._appliance.emulator() == "qemu":
symbol = ":/symbols/qemu_guest.svg"
else:
symbol = ":/symbols/computer.svg"
else:
symbol = ":/symbols/{}.svg".format(self._appliance["category"])
self.page(page_id).setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(symbol))
@@ -144,21 +152,6 @@ 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")
is_mac = ComputeManager.instance().localPlatform().startswith("darwin")
is_win = ComputeManager.instance().localPlatform().startswith("win")
self.uiRemoteServersComboBox.clear()
if len(ComputeManager.instance().remoteComputes()) == 0:
self.uiRemoteRadioButton.setEnabled(False)
@@ -167,66 +160,27 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
for compute in ComputeManager.instance().remoteComputes():
self.uiRemoteServersComboBox.addItem(compute.name(), compute)
if not ComputeManager.instance().vmCompute():
self.uiVMRadioButton.setEnabled(False)
#if ComputeManager.instance().localPlatform() is None:
# self.uiLocalRadioButton.setEnabled(False)
if ComputeManager.instance().localPlatform() is None:
self.uiLocalRadioButton.setEnabled(False)
elif is_mac or is_win:
if emulator_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":
self.uiLocalRadioButton.setEnabled(False)
if ComputeManager.instance().vmCompute():
self.uiVMRadioButton.setChecked(True)
elif ComputeManager.instance().localCompute() and self.uiLocalRadioButton.isEnabled():
if ComputeManager.instance().localCompute() and self.uiLocalRadioButton.isEnabled():
self.uiLocalRadioButton.setChecked(True)
elif self.uiRemoteRadioButton.isEnabled():
self.uiRemoteRadioButton.setChecked(True)
else:
self.uiRemoteRadioButton.setChecked(False)
if is_mac or is_win:
if not self.uiRemoteRadioButton.isEnabled() and not self.uiVMRadioButton.isEnabled() and not self.uiLocalRadioButton.isEnabled():
QtWidgets.QMessageBox.warning(self, "GNS3 VM", "The GNS3 VM is not available, please configure the GNS3 VM before adding a new appliance.")
elif self.page(page_id) == self.uiFilesWizardPage:
if Controller.instance().isRemote() or self._compute_id != "local":
self._registry.getRemoteImageList(self._appliance.emulator(), self._compute_id)
else:
self.images_changed_signal.emit()
elif self.page(page_id) == self.uiQemuWizardPage:
if self._appliance['qemu'].get('kvm', 'require') == 'require':
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"]])
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", "")))
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"]:
self._server_check = True
else:
if error:
msg = result["message"]
else:
msg = "The selected server does not support KVM. A Linux server or the GNS3 VM running in VMware is required."
QtWidgets.QMessageBox.critical(self, "KVM support", msg)
self._server_check = False
def _uiServerWizardPage_isComplete(self):
return self.uiRemoteRadioButton.isEnabled() or self.uiVMRadioButton.isEnabled() or self.uiLocalRadioButton.isEnabled()
return self.uiRemoteRadioButton.isEnabled() or self.uiLocalRadioButton.isEnabled()
def _imageUploadedCallback(self, result, error=False, context=None, **kwargs):
if context is None:
@@ -347,7 +301,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
size += image.get("filesize", 0)
image_widget = QtWidgets.QTreeWidgetItem([image["filename"],
human_filesize(image.get("filesize", 0)),
human_size(image.get("filesize", 0)),
image["status"]])
if image["status"] == "Missing":
image_widget.setForeground(2, QtGui.QBrush(QtGui.QColor("red")))
@@ -372,7 +326,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
expand = False
top.setForeground(2, QtGui.QBrush(QtGui.QColor("green")))
top.setData(1, QtCore.Qt.DisplayRole, human_filesize(size))
top.setData(1, QtCore.Qt.DisplayRole, human_size(size))
top.setData(2, QtCore.Qt.DisplayRole, status)
top.setData(0, QtCore.Qt.UserRole, version)
top.setData(2, QtCore.Qt.UserRole, self._appliance)
@@ -531,33 +485,9 @@ 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.parent())
image_upload_manger.upload()
def _getQemuBinariesFromServerCallback(self, result, error=False, **kwargs):
"""
Callback for getQemuBinariesFromServer.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
QtWidgets.QMessageBox.critical(self, "Qemu binaries", "{}".format(result["message"]))
else:
self.uiQemuListComboBox.clear()
for qemu in result:
if qemu["version"]:
self.uiQemuListComboBox.addItem("{path} (v{version})".format(path=qemu["path"], version=qemu["version"]), qemu["path"])
else:
self.uiQemuListComboBox.addItem("{path}".format(path=qemu["path"]), qemu["path"])
if self.uiQemuListComboBox.count() == 1:
self.next()
else:
i = self.uiQemuListComboBox.findData(self._appliance["qemu"]["arch"], flags=QtCore.Qt.MatchEndsWith)
if i != -1:
self.uiQemuListComboBox.setCurrentIndex(i)
def _install(self, version):
"""
Install the appliance in GNS3
@@ -585,9 +515,6 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
return False
appliance_configuration["name"] = appliance_configuration["name"].strip()
if "qemu" in appliance_configuration:
appliance_configuration["qemu"]["path"] = self.uiQemuListComboBox.currentData()
new_template = ApplianceToTemplate().new_template(appliance_configuration, self._compute_id, self._symbols, parent=self)
TemplateManager.instance().createTemplate(Template(new_template), callback=self._templateCreatedCallback)
return False
@@ -626,34 +553,22 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
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
return False
for image in appliance_configuration["images"]:
if image["location"] == "local":
if not Controller.instance().isRemote() and self._compute_id == "local" and image["path"].startswith(ImageManager.instance().getDirectory()):
log.debug("{} is already on the local server".format(image["path"]))
return
image = Image(self._appliance.emulator(), image["path"], filename=image["filename"])
image_upload_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, 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
image_upload_manager = ImageUploadManager(image, Controller.instance(), self.parent())
if not image_upload_manager.upload():
return False
return True
def nextId(self):
if self.currentPage() == self.uiServerWizardPage:
if "docker" in self._appliance:
# skip Qemu binary selection and files pages if this is a Docker appliance
return super().nextId() + 2
elif "qemu" not in self._appliance:
# skip the Qemu binary selection page if not a Qemu appliance
return super().nextId() + 1
return super().nextId()
@@ -684,7 +599,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if reply == QtWidgets.QMessageBox.No:
return False
self._uploadImages(appliance["name"], version["name"])
return self._uploadImages(appliance["name"], version["name"])
elif self.currentPage() == self.uiUsageWizardPage:
# validate the usage page
@@ -709,8 +624,6 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
QtWidgets.QMessageBox.critical(self, "Remote server", "There is no remote servers configured in your preferences")
return False
self._compute_id = self.uiRemoteServersComboBox.itemData(self.uiRemoteServersComboBox.currentIndex()).id()
elif hasattr(self, "uiVMRadioButton") and self.uiVMRadioButton.isChecked():
self._compute_id = "vm"
else:
if ComputeManager.instance().localPlatform():
if (ComputeManager.instance().localPlatform().startswith("darwin") or ComputeManager.instance().localPlatform().startswith("win")):
@@ -720,29 +633,8 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
return False
self._compute_id = "local"
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
if self.uiQemuListComboBox.currentIndex() == -1:
QtWidgets.QMessageBox.critical(self, "Qemu binary", "No compatible Qemu binary selected")
return False
return True
@qslot
def _vmToggledSlot(self, checked):
"""
Slot for when the VM radio button is toggled.
:param checked: either the button is checked or not
"""
if checked:
self.uiRemoteServersGroupBox.setEnabled(False)
self.uiRemoteServersGroupBox.hide()
@qslot
def _remoteServerToggledSlot(self, checked):
"""

View File

@@ -19,7 +19,6 @@ import psutil
import platform
import os
import stat
import sys
import struct
from gns3.qt import QtWidgets
@@ -92,19 +91,6 @@ class DoctorDialog(QtWidgets.QDialog, Ui_DoctorDialog):
return (1, "Experimental features are enabled. Turn them off by going to Preferences -> General -> Miscellaneous.")
return (0, None)
def checkAVGInstalled(self):
"""Checking if AVG software is not installed"""
if sys.platform.startswith("win32"):
for proc in psutil.process_iter():
try:
psinfo = proc.as_dict(["exe"])
if psinfo["exe"] and "AVG\\" in psinfo["exe"]:
return (2, "AVG has known issues with GNS3, even after you disable it. You must whitelist dynamips.exe in the AVG preferences.")
except psutil.NoSuchProcess:
pass
return (0, None)
def checkFreeRam(self):
"""Checking for amount of free virtual memory"""
@@ -186,36 +172,6 @@ class DoctorDialog(QtWidgets.QDialog, Ui_DoctorDialog):
pass
return (0, None)
def _checkWindowsService(self, service_name):
import pywintypes
import win32service
import win32serviceutil
try:
if win32serviceutil.QueryServiceStatus(service_name, None)[1] != win32service.SERVICE_RUNNING:
return False
except pywintypes.error as e:
if e.winerror == 1060:
return False
else:
raise
return True
def checkRPFServiceIsRunning(self):
"""Check if the RPF service is running (required to use Ethernet NIOs)"""
if not sys.platform.startswith("win"):
return (0, None)
import pywintypes
try:
if not self._checkWindowsService("npf") and not self._checkWindowsService("npcap"):
return (2, "The NPF or NPCAP service is not installed, please install Winpcap or Npcap and reboot")
except pywintypes.error as e:
return (2, "Could not check if the NPF or Npcap service is running: {}".format(e.strerror))
return (0, None)
if __name__ == '__main__':
import sys

View File

@@ -34,42 +34,15 @@ class EditComputeDialog(QtWidgets.QDialog, Ui_EditComputeDialog):
super().__init__(parent)
self.setupUi(self)
self.uiEnableAuthenticationCheckBox.toggled.connect(self._enableAuthenticationSlot)
self._compute = compute
if self._compute:
self.uiServerNameLineEdit.setText(self._compute.name())
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())
else:
self.uiEnableAuthenticationCheckBox.setChecked(False)
self.uiWarningLabel.setVisible(False)
else:
self.uiEnableAuthenticationCheckBox.setChecked(False)
self.uiWarningLabel.setVisible(False)
self._enableAuthenticationSlot(self.uiEnableAuthenticationCheckBox.isChecked())
def _enableAuthenticationSlot(self, state):
"""
Slot to enable or not the authentication.
"""
if self.uiEnableAuthenticationCheckBox.isChecked():
self.uiServerUserLineEdit.setVisible(True)
self.uiServerPasswordLineEdit.setVisible(True)
self.uiServerUserLabel.setVisible(True)
self.uiServerPasswordLabel.setVisible(True)
else:
self.uiServerUserLineEdit.setVisible(False)
self.uiServerPasswordLineEdit.setVisible(False)
self.uiServerUserLabel.setVisible(False)
self.uiServerPasswordLabel.setVisible(False)
self.uiServerUserLineEdit.setText(self._compute.user())
def compute(self):
return self._compute
@@ -87,16 +60,16 @@ class EditComputeDialog(QtWidgets.QDialog, Ui_EditComputeDialog):
password = self.uiServerPasswordLineEdit.text().strip()
if not re.match(r"^[a-zA-Z0-9\.{}-]+$".format("\u0370-\u1CDF\u2C00-\u30FF\u4E00-\u9FBF"), host):
QtWidgets.QMessageBox.critical(self, "Remote compute", "Invalid remote server hostname {}".format(host))
QtWidgets.QMessageBox.critical(self, "Remote compute", "Invalid compute hostname {}".format(host))
return
if name == "gns3vm":
QtWidgets.QMessageBox.critical(self, "Remote compute", "{} is a reserved name".format(name))
return
if len(name) == 0:
QtWidgets.QMessageBox.critical(self, "Remote compute", "Invalid remote server name {}".format(name))
QtWidgets.QMessageBox.critical(self, "Remote compute", "Invalid compute name {}".format(name))
return
if port is None or port < 1:
QtWidgets.QMessageBox.critical(self, "Remote compute", "Invalid remote server port {}".format(port))
QtWidgets.QMessageBox.critical(self, "Remote compute", "Invalid compute port {}".format(port))
return
if not self._compute:
@@ -105,12 +78,8 @@ class EditComputeDialog(QtWidgets.QDialog, Ui_EditComputeDialog):
self._compute.setProtocol(protocol)
self._compute.setHost(host)
self._compute.setPort(port)
if self.uiEnableAuthenticationCheckBox.isChecked():
self._compute.setUser(user)
self._compute.setPassword(password)
else:
self._compute.setUser(None)
self._compute.setPassword(None)
self._compute.setUser(user)
self._compute.setPassword(password)
super().accept()

View File

@@ -15,7 +15,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from ..qt import QtWidgets, QtCore, qslot, qpartial
from gns3.utils import parse_version
from ..qt import QtGui, QtWidgets, QtCore, qslot, qpartial
from ..topology import Topology
from ..ui.edit_project_dialog_ui import Ui_EditProjectDialog
@@ -46,9 +48,38 @@ class EditProjectDialog(QtWidgets.QDialog, Ui_EditProjectDialog):
self.uiNewVarButton.clicked.connect(self.onAddNewVariable)
self.uiGlobalVariablesGrid.addWidget(self.uiNewVarButton, 0, 3, QtCore.Qt.AlignRight)
self._readme_filename = "README.txt"
self.uiTabWidget.currentChanged.connect(self._previewMarkdownSlot)
self._loadReadme()
self._variables = self.setUpVariables()
self.updateGlobalVariables()
def _loadReadme(self):
self._project.get("/files/{}".format(self._readme_filename), self._loadedReadme, raw=True)
def _loadedReadme(self, result, error=False, context={}, **kwargs):
if not error:
content = result.decode("utf-8", errors="replace")
self.uiReadmeTextEdit.setPlainText(content)
def _previewMarkdownSlot(self, index):
# index 1 is preview tab
if index == 1:
# QTextDocument before Qt version 5.14 doesn't support Markdown
if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.14.0") or parse_version(QtCore.PYQT_VERSION_STR) < parse_version("5.14.0"):
QtWidgets.QMessageBox.critical(self, "Markdown preview", "Markdown preview is only support with Qt version 5.14.0 or above")
return
# show Markdown preview
document = QtGui.QTextDocument()
self.uiReadmePreview.setDocument(document)
document.setMarkdown(self.uiReadmeTextEdit.toPlainText())
def setUpVariables(self):
new_variable = {"name": "", "value": ""}
variables = self._project.variables()
@@ -123,4 +154,12 @@ class EditProjectDialog(QtWidgets.QDialog, Ui_EditProjectDialog):
self._project.setSceneWidth(self.uiSceneWidthSpinBox.value())
self._project.setVariables(self._cleanVariables())
self._project.update()
content = self.uiReadmeTextEdit.toPlainText()
if content:
self._project.post("/files/{}".format(self._readme_filename), self._saveReadmeCallback, body=content)
super().done(result)
def _saveReadmeCallback(self, result, error=False, **kwargs):
if error:
QtWidgets.QMessageBox.critical(self, "Edit project", "Could not created readme file")

View File

@@ -64,9 +64,9 @@ class FileEditorDialog(QtWidgets.QDialog, Ui_FileEditorDialog):
def _refreshSlot(self):
self._target.get("/files/" + self._path, self._getCallback)
def _getCallback(self, result, error=False, raw_body=None, **kwargs):
def _getCallback(self, result, error=False, **kwargs):
if not error:
self.uiFileTextEdit.setText(raw_body.decode("utf-8", errors="ignore"))
self.uiFileTextEdit.setText(result.decode("utf-8", errors="ignore"))
elif result.get("status") == 404:
if self._default:
self.uiFileTextEdit.setText(self._default)

View File

@@ -0,0 +1,168 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2022 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import pathlib
from gns3.http_client_error import HttpClientError, HttpClientCancelledRequestError
from ..qt import QtCore, QtWidgets, qslot, sip_is_deleted
from ..ui.image_dialog_ui import Ui_ImageDialog
from ..utils import human_size
from ..controller import Controller
import logging
log = logging.getLogger(__name__)
class ImageDialog(QtWidgets.QDialog, Ui_ImageDialog):
"""
Image management dialog.
"""
def __init__(self, parent):
"""
:param parent: parent widget.
"""
super().__init__(parent)
self.setupUi(self)
self.uiUploadImagePushButton.clicked.connect(self._uploadImageSlot)
self.uiDeleteImagePushButton.clicked.connect(self._deleteImageSlot)
self.uiRefreshImagesPushButton.clicked.connect(Controller.instance().refreshImageList)
Controller.instance().image_list_updated_signal.connect(self._updateImageListSlot)
self._updateImageListSlot()
Controller.instance().refreshImageList()
@qslot
def _uploadImageSlot(self, *args):
files, _ = QtWidgets.QFileDialog.getOpenFileNames(
self,
"Select one or more images to upload",
".",
"Images (*.bin *.image, *.qcow2, *.vmdk);;All files (*.*)"
)
error_msgs = ""
for path in files:
log.debug("Uploading image '{}' to controller".format(path))
image_filename = os.path.basename(path)
install_appliances = self.uiInstallApplianceCheckBox.isChecked()
try:
Controller.instance().post(
f"/images/upload/{image_filename}",
params={"install_appliances": install_appliances},
body=pathlib.Path(path),
context={"image_path": path},
progress_text="Uploading {}".format(image_filename),
timeout=None,
wait=True
)
except HttpClientCancelledRequestError:
return
except HttpClientError as e:
error_msgs += f"{e}\n"
if error_msgs:
error_dialog = QtWidgets.QMessageBox(self)
error_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
error_dialog.setWindowTitle("Image upload")
error_dialog.setText(f"Error while uploading images to the controller")
error_dialog.setDetailedText(error_msgs)
error_dialog.setIcon(QtWidgets.QMessageBox.Critical)
error_dialog.show()
Controller.instance().refreshImageList()
@qslot
def _deleteImageSlot(self, *args):
if len(self.uiImagesTreeWidget.selectedItems()) == 0:
QtWidgets.QMessageBox.critical(self, "Delete image", "No images selected")
return
reply = QtWidgets.QMessageBox.warning(
self,
"Delete image(s)",
"Delete the selected images?\nThis cannot be reverted.",
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No
)
if reply == QtWidgets.QMessageBox.No:
return
images_to_delete = set()
for image in self.uiImagesTreeWidget.selectedItems():
if sip_is_deleted(image):
continue
image_filename = image.data(0, QtCore.Qt.UserRole)
images_to_delete.add(image_filename)
error_msgs = ""
for image_filename in images_to_delete:
try:
Controller.instance().delete(
f"/images/{image_filename}",
progress_text=f"Deleting {image_filename}",
timeout=None,
wait=True
)
except HttpClientCancelledRequestError:
return
except HttpClientError as e:
error_msgs += f"{e}\n"
if error_msgs:
error_dialog = QtWidgets.QMessageBox(self)
error_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
error_dialog.setWindowTitle("Image deletion")
error_dialog.setText(f"Error while deleting images on the controller")
error_dialog.setDetailedText(error_msgs)
error_dialog.setIcon(QtWidgets.QMessageBox.Critical)
error_dialog.show()
Controller.instance().refreshImageList()
@qslot
def _updateImageListSlot(self, *args):
self.uiImagesTreeWidget.clear()
self.uiDeleteImagePushButton.setEnabled(False)
self.uiImagesTreeWidget.setUpdatesEnabled(False)
items = []
for image in Controller.instance().images():
item = QtWidgets.QTreeWidgetItem([image["filename"], image["image_type"], human_size(image["image_size"])])
item.setData(0, QtCore.Qt.UserRole, image["filename"])
items.append(item)
self.uiImagesTreeWidget.addTopLevelItems(items)
if len(Controller.instance().images()):
self.uiDeleteImagePushButton.setEnabled(True)
self.uiImagesTreeWidget.header().setResizeContentsPrecision(100) # How many rows are checked for the resize for performance reason
self.uiImagesTreeWidget.resizeColumnToContents(0)
self.uiImagesTreeWidget.resizeColumnToContents(1)
self.uiImagesTreeWidget.resizeColumnToContents(2)
self.uiImagesTreeWidget.sortItems(0, QtCore.Qt.AscendingOrder)
self.uiImagesTreeWidget.setUpdatesEnabled(True)
def keyPressEvent(self, e):
"""
Event handler in order to properly handle escape.
"""
if e.key() == QtCore.Qt.Key_Escape:
self.close()

View File

@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2021 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/>.
from ..qt import QtWidgets
from ..ui.login_dialog_ui import Ui_LoginDialog
import logging
log = logging.getLogger(__name__)
class LoginDialog(QtWidgets.QDialog, Ui_LoginDialog):
"""
Login dialog.
"""
def __init__(self, parent):
"""
:param parent: parent widget.
"""
super().__init__(parent)
self.setupUi(self)
self._parent = parent
self._username = None
self._password = None
def getUsername(self):
return self._username
def setUsername(self, username):
self.uiUsernameLineEdit.setText(username)
def getPassword(self):
return self._password
def done(self, result):
if result:
self._username = self.uiUsernameLineEdit.text()
self._password = self.uiPasswordLineEdit.text()
super().done(result)

View File

@@ -21,7 +21,7 @@ Dialog to load module and built-in preference pages.
from ..qt import QtCore, QtWidgets
from ..ui.preferences_dialog_ui import Ui_PreferencesDialog
from ..pages.server_preferences_page import ServerPreferencesPage
from ..pages.controller_preferences_page import ControllerPreferencesPage
from ..pages.general_preferences_page import GeneralPreferencesPage
from ..pages.packet_capture_preferences_page import PacketCapturePreferencesPage
from ..pages.gns3_vm_preferences_page import GNS3VMPreferencesPage
@@ -84,8 +84,8 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
# load built-in preference pages
pages = [
GeneralPreferencesPage,
ServerPreferencesPage,
GNS3VMPreferencesPage,
ControllerPreferencesPage,
#GNS3VMPreferencesPage,
PacketCapturePreferencesPage,
]

View File

@@ -139,18 +139,24 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
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, "reset_mac_addresses": reset_mac_addresses},
progressText="Duplicating project '{}'...".format(name),
timeout=None)
Controller.instance().post(
"/projects/{project_id}/duplicate".format(project_id=project_id),
self._duplicateCallback,
body={"name": name, "reset_mac_addresses": reset_mac_addresses},
progress_text="Duplicating project '{}'...".format(name),
timeout=None,
wait=True
)
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, "reset_mac_addresses": reset_mac_addresses},
progressText="Duplicating project '{}'...".format(name),
timeout=None)
Controller.instance().post(
"/projects/{project_id}/duplicate".format(project_id=project_id),
self._duplicateCallback,
body={"name": name, "path": project_location, "reset_mac_addresses": reset_mac_addresses},
progress_text="Duplicating project '{}'...".format(name),
timeout=None,
wait=True
)
def _duplicateCallback(self, result, error=False, **kwargs):
if error:

View File

@@ -16,14 +16,13 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import os
import datetime
from gns3.qt import QtCore, QtWidgets
from ..local_server import LocalServer
from ..utils.progress_dialog import ProgressDialog
from ..utils.export_project_worker import ExportProjectWorker
from ..ui.export_project_wizard_ui import Ui_ExportProjectWizard
from gns3.qt import QtCore, QtWidgets, QtGui
from gns3.utils import parse_version
from gns3.http_client_error import HttpClientError, HttpClientCancelledRequestError
from gns3.local_server import LocalServer
from gns3.ui.export_project_wizard_ui import Ui_ExportProjectWizard
import logging
log = logging.getLogger(__name__)
@@ -34,6 +33,8 @@ class ExportProjectWizard(QtWidgets.QWizard, Ui_ExportProjectWizard):
Export project wizard.
"""
readme_signal = QtCore.pyqtSignal()
def __init__(self, project, parent):
super().__init__(parent)
@@ -50,24 +51,40 @@ class ExportProjectWizard(QtWidgets.QWizard, Ui_ExportProjectWizard):
self.uiCompressionComboBox.addItem("Zip compression (deflate)", "zip")
self.uiCompressionComboBox.addItem("Bzip2 compression", "bzip2")
self.uiCompressionComboBox.addItem("Lzma compression", "lzma")
self.uiCompressionComboBox.addItem("Zstandard compression", "zstd")
self.uiCompressionComboBox.currentIndexChanged.connect(self._compressionChangedSlot)
# set zip compression by default
self.uiCompressionComboBox.setCurrentIndex(1)
# set zstd compression by default
self.uiCompressionComboBox.setCurrentIndex(4)
self.helpRequested.connect(self._showHelpSlot)
self.uiPathBrowserToolButton.clicked.connect(self._pathBrowserSlot)
# QTextDocument before Qt version 5.14 doesn't support Markdown
if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.14.0") or parse_version(QtCore.PYQT_VERSION_STR) < parse_version("5.14.0"):
self._use_markdown = False
else:
self._use_markdown = True
self._readme_filename = "README.txt"
self.uiTabWidget.currentChanged.connect(self._previewMarkdownSlot)
self._loadReadme()
def _loadReadme(self):
self._project.get("/files/README.txt", self._loadedReadme)
self._project.get("/files/{}".format(self._readme_filename), self._loadedReadme, raw=True)
def _loadedReadme(self, result, error=False, raw_body=None, context={}, **kwargs):
def _loadedReadme(self, result, error=False, context={}, **kwargs):
if not error:
self.uiReadmeTextEdit.setPlainText(raw_body.decode("utf-8", errors="replace"))
content = result.decode("utf-8", errors="replace")
self.uiReadmeTextEdit.setPlainText(content)
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)
if self._use_markdown:
readme_markdown = "# Project {}\n\nCreated on: {}\n\nAuthor: John Doe <john.doe@example.com>\n\n## Description\n\nNo project description was given".format(self._project.name(), datetime.date.today())
self.uiReadmeTextEdit.setPlainText(readme_markdown)
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):
@@ -75,14 +92,28 @@ class ExportProjectWizard(QtWidgets.QWizard, Ui_ExportProjectWizard):
if len(directory) == 0:
directory = LocalServer.instance().localServerSettings()["projects_path"]
path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export portable project", directory,
"GNS3 Portable Project (*.gns3project *.gns3p)",
"GNS3 Portable Project (*.gns3project *.gns3p)")
path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export project", directory,
"GNS3 Project (*.gns3project *.gns3p)",
"GNS3 Project (*.gns3project *.gns3p)")
if path is None or len(path) == 0:
return
self.uiPathLineEdit.setText(path)
def _previewMarkdownSlot(self, index):
# index 1 is preview tab
if index == 1:
if self._use_markdown is False:
QtWidgets.QMessageBox.critical(self, "Markdown preview", "Markdown preview is only support with Qt version 5.14.0 or above")
return
# show Markdown preview
document = QtGui.QTextDocument()
self.uiReadmePreviewEdit.setDocument(document)
document.setMarkdown(self.uiReadmeTextEdit.toPlainText())
def _showHelpSlot(self):
include_image_help = """Including base images means additional images will not be requested to
@@ -110,15 +141,39 @@ class ExportProjectWizard(QtWidgets.QWizard, Ui_ExportProjectWizard):
QtWidgets.QMessageBox.critical(self, "Export project", "Cannot export project to '{}': {}".format(path, e))
return False
self._path = path
elif self.currentPage() == self.uiProjectReadmeWizardPage:
text = self.uiReadmeTextEdit.toPlainText().strip()
if text:
self._project.post("/files/README.txt", self._saveReadmeCallback, body=text)
return True
def _saveReadmeCallback(self, result, error=False, **kwargs):
if error:
QtWidgets.QMessageBox.critical(self, "Export project", "Could not created readme file")
self.readme_signal.emit()
def waitForReadme(self, signal, timeout=10000):
# inspired from https://www.jdreaver.com/posts/2014-07-03-waiting-for-signals-pyside-pyqt.html
loop = QtCore.QEventLoop()
signal.connect(loop.quit)
if timeout is not None:
QtCore.QTimer.singleShot(timeout, loop.quit)
loop.exec_()
def _compressionChangedSlot(self, index):
"""
Set the default compression level.
"""
compression = self.uiCompressionComboBox.itemData(index)
self.uiCompressionLevelSpinBox.setEnabled(True)
if compression == "zip":
self.uiCompressionLevelSpinBox.setValue(6) # ZIP default compression level is 6
elif compression == "bzip2":
self.uiCompressionLevelSpinBox.setValue(9) # BZIP2 default compression level is 9
elif compression == "zstd":
self.uiCompressionLevelSpinBox.setValue(3) # ZSTD default compression level is 3
else:
# compression level is not supported
self.uiCompressionLevelSpinBox.setValue(0)
self.uiCompressionLevelSpinBox.setEnabled(False)
def done(self, result):
"""
@@ -126,6 +181,11 @@ class ExportProjectWizard(QtWidgets.QWizard, Ui_ExportProjectWizard):
"""
if result:
content = self.uiReadmeTextEdit.toPlainText()
if content:
self._project.post("/files/{}".format(self._readme_filename), self._saveReadmeCallback, body=content)
if self.uiIncludeImagesCheckBox.isChecked():
include_images = "yes"
else:
@@ -139,8 +199,45 @@ class ExportProjectWizard(QtWidgets.QWizard, Ui_ExportProjectWizard):
else:
reset_mac_addresses = "no"
compression = self.uiCompressionComboBox.currentData()
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_()
self.waitForReadme(self.readme_signal)
params = {
"include_images": include_images,
"include_snapshots": include_snapshots,
"reset_mac_addresses": reset_mac_addresses,
"compression": compression
}
try:
self._project.get(
"/export",
callback=None,
download_progress_callback=self._downloadFileProgress,
progress_text="Exporting project files...",
params=params,
timeout=None,
wait=True,
raw=True
)
except HttpClientCancelledRequestError:
pass
except HttpClientError as e:
QtWidgets.QMessageBox.critical(
self,
"Project export",
f"Could not export project: {e}"
)
super().done(result)
def _downloadFileProgress(self, content, **kwargs):
"""
Called for each part of the file
"""
try:
with open(self._path, 'ab') as f:
f.write(content)
except OSError as e:
log.error(f"Could not write project file: {e}")
return

View File

@@ -61,11 +61,11 @@ class ProjectWelcomeDialog(QtWidgets.QDialog, Ui_ProjectWelcomeDialog):
self.gridLayout.addWidget(valueEdit, i, 1)
def _loadReadme(self):
self._project.get("/files/README.txt", self._loadedReadme)
self._project.get("/files/README.txt", self._loadedReadme, raw=True)
def _loadedReadme(self, result, error=False, raw_body=None, context={}, **kwargs):
def _loadedReadme(self, result, error=False, context={}, **kwargs):
if not error:
self.label.setText(raw_body.decode("utf-8"))
self.label.setText(result.decode("utf-8"))
def onValueChange(self, variable, text):
variable["value"] = text

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 GNS3 Technologies Inc.
# Copyright (C) 2021 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
@@ -19,14 +19,12 @@ import sys
import os
import shutil
from gns3.qt import QtCore, QtWidgets, QtGui, QtNetwork, qslot
from gns3.qt import QtCore, QtWidgets, QtNetwork
from gns3.controller import Controller
from gns3.local_server import LocalServer
from gns3.utils.interfaces import interfaces
from ..settings import DEFAULT_LOCAL_SERVER_HOST
from ..settings import DEFAULT_CONTROLLER_HOST
from ..ui.setup_wizard_ui import Ui_SetupWizard
from ..version import __version__
import logging
@@ -45,40 +43,17 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
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",
"port": 80
}
self.setWizardStyle(QtWidgets.QWizard.ModernStyle)
if sys.platform.startswith("darwin"):
# we want to see the cancel button on OSX
self.setOptions(QtWidgets.QWizard.NoDefaultButton)
self.uiLocalServerToolButton.clicked.connect(self._localServerBrowserSlot)
self.uiGNS3VMDownloadLinkUrlLabel.setText("")
self.uiRefreshPushButton.clicked.connect(self._refreshVMListSlot)
self.uiVmwareRadioButton.clicked.connect(self._listVMwareVMsSlot)
self.uiVirtualBoxRadioButton.clicked.connect(self._listVirtualBoxVMsSlot)
settings = parent.settings()
self.uiShowCheckBox.setChecked(settings["hide_setup_wizard"])
# by default all radio buttons are unchecked
self.uiVmwareRadioButton.setAutoExclusive(False)
self.uiVirtualBoxRadioButton.setAutoExclusive(False)
self.uiVmwareRadioButton.setChecked(False)
self.uiVirtualBoxRadioButton.setChecked(False)
# Mandatory fields
self.uiLocalServerWizardPage.registerField("path*", self.uiLocalServerPathLineEdit)
self.uiLocalControllerWizardPage.registerField("path*", self.uiLocalServerPathLineEdit)
# load all available addresses
for address in QtNetwork.QNetworkInterface.allAddresses():
@@ -95,11 +70,11 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
self.uiLocalServerHostComboBox.addItem("0.0.0.0", "0.0.0.0") # all IPv4 addresses
if sys.platform.startswith("linux"):
self.uiLocalRadioButton.setChecked(True)
self.uiLocalLabel.setText("Dependencies like Dynamips and Qemu must be manually installed")
Controller.instance().connected_signal.connect(self._refreshLocalServerStatusSlot)
Controller.instance().connection_failed_signal.connect(self._refreshLocalServerStatusSlot)
# only support local controller on Linux
self.uiLocalControllerRadioButton.setChecked(True)
else:
self.uiLocalControllerRadioButton.setEnabled(False)
self.uiRemoteControllerRadioButton.setChecked(True)
def _localServerBrowserSlot(self):
"""
@@ -116,36 +91,6 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
self.uiLocalServerPathLineEdit.setText(path)
def _listVMwareVMsSlot(self):
"""
Slot to refresh the VMware VMs list.
"""
download_url = "https://github.com/GNS3/gns3-gui/releases/download/v{version}/GNS3.VM.VMware.Workstation.{version}.zip".format(version=__version__)
self.uiGNS3VMDownloadLinkUrlLabel.setText('The GNS3 VM can <a href="{download_url}">downloaded here</a>.<br>Import the VM in your virtualization software and hit refresh.'.format(download_url=download_url))
self.uiVirtualBoxRadioButton.setChecked(False)
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://customerconnect.vmware.com/downloads/details?downloadGroup=PLAYER-1400-VIX1170&productId=687. After installation you need to restart GNS3.")
return
self._refreshVMListSlot()
def _listVirtualBoxVMsSlot(self):
"""
Slot to refresh the VirtualBox VMs list.
"""
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)
from gns3.modules import VirtualBox
settings = VirtualBox.instance().settings()
if not os.path.exists(settings["vboxmanage_path"]):
QtWidgets.QMessageBox.critical(self, "VirtualBox", "VBoxManage could not be found, VirtualBox is probably not installed. After installation you need to restart GNS3.")
return
self._refreshVMListSlot()
def _setPreferencesPane(self, dialog, name):
"""
Finds the first child of the QTreeWidgetItem name.
@@ -161,13 +106,6 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
dialog.uiTreeWidget.setCurrentItem(child_pane)
return dialog.uiStackedWidget.currentWidget()
def _getSettingsCallback(self, result, error=False, **kwargs):
if error:
if "message" in result:
log.error("Error while get gettings: {}".format(result["message"]))
return
self._gns3_vm_settings = result
def initializePage(self, page_id):
"""
Initialize Wizard pages.
@@ -176,35 +114,16 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
"""
super().initializePage(page_id)
if self.page(page_id) == self.uiServerWizardPage:
if self.page(page_id) == self.uiControllerWizardPage:
Controller.instance().setDisplayError(False)
Controller.instance().get("/gns3vm", self._getSettingsCallback)
elif self.page(page_id) == self.uiVMWizardPage:
if self._GNS3VMSettings()["engine"] == "vmware":
self.uiVmwareRadioButton.setChecked(True)
self._listVMwareVMsSlot()
elif self._GNS3VMSettings()["engine"] == "virtualbox":
self.uiVirtualBoxRadioButton.setChecked(True)
self._listVirtualBoxVMsSlot()
self.uiCPUSpinBox.setValue(self._GNS3VMSettings()["vcpus"])
self.uiRAMSpinBox.setValue(self._GNS3VMSettings()["ram"])
elif self.page(page_id) == self.uiLocalServerWizardPage:
elif self.page(page_id) == self.uiLocalControllerWizardPage:
local_server_settings = LocalServer.instance().localServerSettings()
self.uiLocalServerPathLineEdit.setText(local_server_settings["path"])
index = self.uiLocalServerHostComboBox.findData(local_server_settings["host"])
if index != -1:
self.uiLocalServerHostComboBox.setCurrentIndex(index)
else:
if self.uiVMRadioButton.isChecked():
# Try to bind with the IP address allocated for VMnet1
for interface in interfaces():
if "vmnet1" in interface["name"].lower():
index = self.uiLocalServerHostComboBox.findText(interface["ip_address"])
break
else:
index = self.uiLocalServerHostComboBox.findText(DEFAULT_LOCAL_SERVER_HOST)
index = self.uiLocalServerHostComboBox.findText(DEFAULT_CONTROLLER_HOST)
if index != -1:
self.uiLocalServerHostComboBox.setCurrentIndex(index)
@@ -213,61 +132,28 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
elif self.page(page_id) == self.uiRemoteControllerWizardPage:
local_server_settings = LocalServer.instance().localServerSettings()
if local_server_settings["host"] is None:
self.uiRemoteMainServerHostLineEdit.setText(DEFAULT_LOCAL_SERVER_HOST)
self.uiRemoteMainServerAuthCheckBox.setChecked(False)
self.uiRemoteMainServerHostLineEdit.setText(DEFAULT_CONTROLLER_HOST)
self.uiRemoteMainServerUserLineEdit.setText("")
self.uiRemoteMainServerPasswordLineEdit.setText("")
else:
self.uiRemoteMainServerHostLineEdit.setText(local_server_settings["host"])
self.uiRemoteMainServerAuthCheckBox.setChecked(local_server_settings["auth"])
self.uiRemoteMainServerUserLineEdit.setText(local_server_settings["user"])
self.uiRemoteMainServerUserLineEdit.setText(local_server_settings["username"])
self.uiRemoteMainServerPasswordLineEdit.setText(local_server_settings["password"])
self.uiRemoteMainServerPortSpinBox.setValue(local_server_settings["port"])
elif self.page(page_id) == self.uiLocalServerStatusWizardPage:
self._refreshLocalServerStatusSlot()
elif self.page(page_id) == self.uiSummaryWizardPage:
self.uiSummaryTreeWidget.clear()
if self.uiLocalRadioButton.isChecked():
if self.uiLocalControllerRadioButton.isChecked():
local_server_settings = LocalServer.instance().localServerSettings()
self._addSummaryEntry("Server type:", "Local")
self._addSummaryEntry("Type:", "Local")
self._addSummaryEntry("Path:", local_server_settings["path"])
self._addSummaryEntry("Host:", local_server_settings["host"])
self._addSummaryEntry("Port:", str(local_server_settings["port"]))
elif self.uiRemoteControllerRadioButton.isChecked():
local_server_settings = LocalServer.instance().localServerSettings()
self._addSummaryEntry("Server type:", "Remote")
self._addSummaryEntry("Type:", "Remote")
self._addSummaryEntry("Host:", local_server_settings["host"])
self._addSummaryEntry("Port:", str(local_server_settings["port"]))
self._addSummaryEntry("User:", local_server_settings["user"])
else:
self._addSummaryEntry("Server type:", "GNS3 Virtual Machine")
self._addSummaryEntry("VM engine:", self._GNS3VMSettings()["engine"].capitalize())
self._addSummaryEntry("VM name:", self._GNS3VMSettings()["vmname"])
self._addSummaryEntry("VM vCPUs:", str(self._GNS3VMSettings()["vcpus"]))
self._addSummaryEntry("VM RAM:", str(self._GNS3VMSettings()["ram"]) + " MB")
@qslot
def _refreshLocalServerStatusSlot(self):
"""
Refresh the local server status page
"""
self.uiLocalServerTextEdit.clear()
if Controller.instance().connected():
self.uiLocalServerTextEdit.setText("Connection to the local GNS3 server has been successful!")
Controller.instance().get("/gns3vm", self._getSettingsCallback)
elif Controller.instance().connecting():
self.uiLocalServerTextEdit.setText("Please wait connection to the GNS3 server...")
else:
local_server_settings = LocalServer.instance().localServerSettings()
self.uiLocalServerTextEdit.setText("Connection to local server failed. Please try one of the following:\n\n- Make sure GNS3 is allowed to run by your firewall.\n- Go back and try to change the server host binding and/or the port\n- Check with a browser if you can connect to {protocol}://{host}:{port}.\n- Try to run {path} in a terminal to see if you have an error.".format(protocol=local_server_settings["protocol"], host=local_server_settings["host"], port=local_server_settings["port"], path=local_server_settings["path"]))
def _GNS3VMSettings(self):
return self._gns3_vm_settings
def _setGNS3VMSettings(self, settings):
Controller.instance().put("/gns3vm", self._saveSettingsCallback, settings, timeout=60 * 5)
self._addSummaryEntry("User:", local_server_settings["username"])
def _saveSettingsCallback(self, result, error=False, **kwargs):
if error:
@@ -289,112 +175,44 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
"""
Controller.instance().setDisplayError(True)
if self.currentPage() == self.uiVMWizardPage:
vmname = self.uiVMListComboBox.currentText()
if vmname:
# save the GNS3 VM settings
vm_settings = self._GNS3VMSettings()
vm_settings["enable"] = True
vm_settings["vmname"] = vmname
if self.currentPage() == self.uiLocalControllerWizardPage:
if self.uiVmwareRadioButton.isChecked():
vm_settings["engine"] = "vmware"
elif self.uiVirtualBoxRadioButton.isChecked():
vm_settings["engine"] = "virtualbox"
local_controller_settings = LocalServer.instance().localServerSettings()
local_controller_settings["auto_start"] = True
local_controller_settings["remote"] = False
local_controller_settings["path"] = self.uiLocalServerPathLineEdit.text().strip()
local_controller_settings["host"] = self.uiLocalServerHostComboBox.itemData(self.uiLocalServerHostComboBox.currentIndex())
local_controller_settings["port"] = self.uiLocalServerPortSpinBox.value()
# set the vCPU count and RAM
vpcus = self.uiCPUSpinBox.value()
ram = self.uiRAMSpinBox.value()
if ram < 1024:
QtWidgets.QMessageBox.warning(self, "GNS3 VM memory", "It is recommended to allocate a minimum of 1024 MB of memory to the GNS3 VM")
vm_settings["vcpus"] = vpcus
vm_settings["ram"] = ram
self._setGNS3VMSettings(vm_settings)
else:
if not self.uiVmwareRadioButton.isChecked() and not self.uiVirtualBoxRadioButton.isChecked():
QtWidgets.QMessageBox.warning(self, "GNS3 VM", "Please select VMware or VirtualBox")
else:
QtWidgets.QMessageBox.warning(self, "GNS3 VM", "Please select a VM. If no VM is listed, check if the GNS3 VM is correctly imported and press refresh.")
if not os.path.isfile(local_controller_settings["path"]):
QtWidgets.QMessageBox.critical(self, "Local server", "Could not find local server {}".format(local_controller_settings["path"]))
return False
elif self.currentPage() == self.uiLocalServerWizardPage:
local_server_settings = LocalServer.instance().localServerSettings()
local_server_settings["auto_start"] = True
local_server_settings["path"] = self.uiLocalServerPathLineEdit.text().strip()
local_server_settings["host"] = self.uiLocalServerHostComboBox.itemData(self.uiLocalServerHostComboBox.currentIndex())
local_server_settings["port"] = self.uiLocalServerPortSpinBox.value()
if not os.path.isfile(local_server_settings["path"]):
QtWidgets.QMessageBox.critical(self, "Local server", "Could not find local server {}".format(local_server_settings["path"]))
return False
if not os.access(local_server_settings["path"], os.X_OK):
QtWidgets.QMessageBox.critical(self, "Local server", "{} is not an executable".format(local_server_settings["path"]))
if not os.access(local_controller_settings["path"], os.X_OK):
QtWidgets.QMessageBox.critical(self, "Local server", "{} is not an executable".format(local_controller_settings["path"]))
return False
LocalServer.instance().updateLocalServerSettings(local_server_settings)
LocalServer.instance().updateLocalServerSettings(local_controller_settings)
# start and connect to the controller if required
if not LocalServer.instance().localServerAutoStartIfRequired():
return False
elif self.currentPage() == self.uiRemoteControllerWizardPage:
local_server_settings = LocalServer.instance().localServerSettings()
local_server_settings["auto_start"] = False
local_server_settings["host"] = self.uiRemoteMainServerHostLineEdit.text()
local_server_settings["port"] = self.uiRemoteMainServerPortSpinBox.value()
local_server_settings["protocol"] = "http"
local_server_settings["user"] = self.uiRemoteMainServerUserLineEdit.text()
local_server_settings["password"] = self.uiRemoteMainServerPasswordLineEdit.text()
local_server_settings["auth"] = self.uiRemoteMainServerAuthCheckBox.isChecked()
LocalServer.instance().updateLocalServerSettings(local_server_settings)
elif self.currentPage() == self.uiSummaryWizardPage:
if self.uiLocalRadioButton.isChecked():
# deactivate the GNS3 VM if using the local server
vm_settings = self._GNS3VMSettings()
vm_settings["enable"] = False
self._setGNS3VMSettings(vm_settings)
elif self.currentPage() == self.uiLocalServerStatusWizardPage:
if not Controller.instance().connected():
return False
remote_controller_settings = Controller.instance().settings()
remote_controller_settings["auto_start"] = False
remote_controller_settings["remote"] = True
remote_controller_settings["host"] = self.uiRemoteMainServerHostLineEdit.text()
remote_controller_settings["port"] = self.uiRemoteMainServerPortSpinBox.value()
remote_controller_settings["protocol"] = "http"
remote_controller_settings["username"] = self.uiRemoteMainServerUserLineEdit.text()
remote_controller_settings["password"] = self.uiRemoteMainServerPasswordLineEdit.text()
Controller.instance().setSettings(remote_controller_settings)
Controller.instance().connect()
return Controller.instance().connected()
return True
def _refreshVMListSlot(self):
"""
Refresh the list of VM available in VMware or VirtualBox.
"""
if self.uiVmwareRadioButton.isChecked():
Controller.instance().get("/gns3vm/engines/vmware/vms", self._getVMsFromServerCallback, progressText="Retrieving VMware VM list from server...")
elif self.uiVirtualBoxRadioButton.isChecked():
Controller.instance().get("/gns3vm/engines/virtualbox/vms", self._getVMsFromServerCallback, progressText="Retrieving VirtualBox VM list from server...")
def _getVMsFromServerCallback(self, result, error=False, **kwargs):
"""
Callback for getVMsFromServer.
:param progress_dialog: QProgressDialog instance
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
QtWidgets.QMessageBox.critical(self, "VM List", "{}".format(result["message"]))
else:
self.uiVMListComboBox.clear()
for vm in result:
self.uiVMListComboBox.addItem(vm["vmname"])
index = self.uiVMListComboBox.findText(self._GNS3VMSettings()["vmname"])
if index != -1:
self.uiVMListComboBox.setCurrentIndex(index)
else:
index = self.uiVMListComboBox.findText("GNS3 VM")
if index != -1:
self.uiVMListComboBox.setCurrentIndex(index)
else:
QtWidgets.QMessageBox.critical(self, "GNS3 VM", "Could not find a VM named 'GNS3 VM', is it imported in VMware or VirtualBox?")
def done(self, result):
"""
This dialog is closed.
@@ -419,20 +237,17 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
"""
current_id = self.currentId()
if self.page(current_id) == self.uiLocalServerStatusWizardPage and not self.uiVMRadioButton.isChecked():
if self.page(current_id) == self.uiLocalControllerWizardPage and self.uiLocalControllerRadioButton.isChecked():
return self._pageId(self.uiSummaryWizardPage)
if self.page(current_id) == self.uiServerWizardPage and self.uiRemoteControllerRadioButton.isChecked():
if self.page(current_id) == self.uiControllerWizardPage and self.uiRemoteControllerRadioButton.isChecked():
return self._pageId(self.uiRemoteControllerWizardPage)
if self.page(current_id) == self.uiVMWizardPage:
return self._pageId(self.uiSummaryWizardPage)
return QtWidgets.QWizard.nextId(self)
def _pageId(self, page):
"""
Return id of the page
"""
for id in self.pageIds():
if self.page(id) == page:
return id

View File

@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
from gns3.qt import QtCore, QtGui, QtWidgets
from gns3.ui.show_readme_dialog_ui import Ui_ShowReadmeDialog
from gns3.utils import parse_version
import logging
log = logging.getLogger(__name__)
class ShowReadmeDialog(QtWidgets.QDialog, Ui_ShowReadmeDialog):
def __init__(self, project, path, content=None, parent=None):
if parent is None:
from gns3.main_window import MainWindow
parent = MainWindow.instance()
super().__init__(parent)
self.setupUi(self)
self._project = project
self._path = path
self.setWindowTitle(project.name() + " " + os.path.basename(path))
self.uiRefreshButton.pressed.connect(self._refreshSlot)
# QTextDocument before Qt version 5.14 doesn't support Markdown
if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.14.0") or parse_version(QtCore.PYQT_VERSION_STR) < parse_version("5.14.0"):
self._markdown = False
else:
self._markdown = True
self._document = QtGui.QTextDocument()
self.uiTextBrowser.setDocument(self._document)
if content:
if self._markdown:
self._document.setMarkdown(content)
else:
self._document.setPlainText(content)
else:
self._refreshSlot()
def _refreshSlot(self):
self._project.get("/files/" + self._path, self._getCallback, raw=True)
def _getCallback(self, result, error=False, **kwargs):
if not error:
content = result.decode("utf-8", errors="ignore")
if self._markdown:
self._document.setMarkdown(content)
else:
self._document.setPlainText(content)

View File

@@ -85,11 +85,14 @@ class SnapshotsDialog(QtWidgets.QDialog, Ui_SnapshotsDialog):
snapshot_name, ok = QtWidgets.QInputDialog.getText(self, "Snapshot", "Snapshot name:", QtWidgets.QLineEdit.Normal, "Unnamed")
if ok and snapshot_name and self._project:
Controller.instance().post("/projects/{}/snapshots".format(self._project.id()),
self._createSnapshotsCallback,
{"name": snapshot_name},
progressText="Creation of snapshot '{}' in progress...".format(snapshot_name),
timeout=None)
Controller.instance().post(
"/projects/{}/snapshots".format(self._project.id()),
self._createSnapshotsCallback,
{"name": snapshot_name},
progress_text="Creation of snapshot '{}' in progress...".format(snapshot_name),
timeout=None,
wait=True
)
def _createSnapshotsCallback(self, result, error=False, server=None, context={}, **kwargs):
if error:
@@ -139,8 +142,13 @@ class SnapshotsDialog(QtWidgets.QDialog, Ui_SnapshotsDialog):
if reply == QtWidgets.QMessageBox.Cancel:
return
Controller.instance().post("/projects/{}/snapshots/{}/restore".format(self._project.id(), snapshot_id),
self._restoreSnapshotsCallback, progressText="Restoring snapshot...", timeout=None)
Controller.instance().post(
"/projects/{}/snapshots/{}/restore".format(self._project.id(), snapshot_id),
self._restoreSnapshotsCallback,
progress_text="Restoring snapshot...",
timeout=None,
wait=True
)
def _restoreSnapshotsCallback(self, result, error=False, server=None, context={}, **kwargs):

View File

@@ -191,7 +191,14 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
SymbolSelectionDialog._symbols_dir = os.path.dirname(path)
symbol_id = os.path.basename(path)
Controller.instance().post("/symbols/" + symbol_id + "/raw", qpartial(self._finishSymbolUpload, path), body=pathlib.Path(path), progressText="Uploading {}".format(symbol_id), timeout=None)
Controller.instance().post(
"/symbols/" + symbol_id + "/raw",
qpartial(self._finishSymbolUpload, path),
body=pathlib.Path(path),
progress_text="Uploading {}".format(symbol_id),
timeout=None,
wait=True
)
def _finishSymbolUpload(self, path, result, error=False, **kwargs):
if error:

View File

@@ -135,14 +135,17 @@ class VMWithImagesWizard(VMWizard):
if create_button:
create_button.show()
def loadImagesList(self, endpoint):
def loadImagesList(self, image_type):
"""
Fill the list box with available Images"
Fill the list box with available images
:param endpoint: server endpoint with the list of Images
:param image_type: image type (qemu, iou or ios)
"""
Controller.instance().getCompute(endpoint, self._compute_id, self._getImagesFromServerCallback)
params = None
if image_type:
params = {"image_type": image_type}
Controller.instance().get("/images", self._getImagesFromServerCallback, params=params)
def _getImagesFromServerCallback(self, result, error=False, **kwargs):
"""
@@ -179,7 +182,7 @@ class VMWithImagesWizard(VMWizard):
if self._widgetOnCurrentPage(combo_box):
combo_box.clear()
for vm in result:
combo_box.addItem(vm["path"], vm)
combo_box.addItem(vm["filename"], vm)
def _widgetOnCurrentPage(self, widget):
"""

View File

@@ -94,8 +94,6 @@ class GraphicsView(QtWidgets.QGraphicsView):
self._drawing_grid_size = 25
self._last_mouse_position = None
self._topology = Topology.instance()
self._background_warning_msgbox = QtWidgets.QErrorMessage(self)
self._background_warning_msgbox.setWindowTitle("Layer position")
# set the scene
scene = QtWidgets.QGraphicsScene(parent=self)
@@ -822,6 +820,18 @@ class GraphicsView(QtWidgets.QGraphicsView):
console_edit_action.triggered.connect(self.customConsoleActionSlot)
menu.addAction(console_edit_action)
if True in list(map(lambda item: isinstance(item, NodeItem), items)):
isolate_action = QtWidgets.QAction("Isolate", menu)
isolate_action.setIcon(get_icon("link-pause.svg"))
isolate_action.triggered.connect(self.isolateActionSlot)
menu.addAction(isolate_action)
if True in list(map(lambda item: isinstance(item, NodeItem), items)):
unisolate_action = QtWidgets.QAction("Un-isolate", menu)
unisolate_action.setIcon(get_icon("link-start.svg"))
unisolate_action.triggered.connect(self.unisolateActionSlot)
menu.addAction(unisolate_action)
if True in list(map(lambda item: isinstance(item, NodeItem), items)):
# Action: Change hostname
change_hostname_action = QtWidgets.QAction("Change hostname", menu)
@@ -1001,6 +1011,26 @@ class GraphicsView(QtWidgets.QGraphicsView):
if isinstance(item, NodeItem) and hasattr(item.node(), "reload") and item.node().initialized():
item.node().reload()
def isolateActionSlot(self):
"""
Slot to receive events from the isolate action in the
contextual menu.
"""
for item in self.scene().selectedItems():
if isinstance(item, NodeItem) and hasattr(item.node(), "isolate") and item.node().initialized():
item.node().isolate()
def unisolateActionSlot(self):
"""
Slot to receive events from the unisolate action in the
contextual menu.
"""
for item in self.scene().selectedItems():
if isinstance(item, NodeItem) and hasattr(item.node(), "unisolate") and item.node().initialized():
item.node().unisolate()
def configureActionSlot(self):
"""
Slot to receive events from the configure action in the
@@ -1028,10 +1058,6 @@ class GraphicsView(QtWidgets.QGraphicsView):
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))
continue
item.node().update({"name": new_hostname})
def changeSymbolActionSlot(self):
@@ -1064,7 +1090,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
break
if os.path.exists(node_dir):
log.debug("Open %s in file manager")
log.debug(f"Open {node_dir} in file manager")
if QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(node_dir)) is False:
QtWidgets.QMessageBox.critical(self, "Show in file manager", "Failed to open {}".format(node_dir))
break

File diff suppressed because it is too large Load Diff

58
gns3/http_client_error.py Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python
#
# Copyright (C) 2021 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/>.
class HttpClientError(Exception):
def __init__(self, message: str):
super().__init__()
self._message = message
def __repr__(self):
return self._message
def __str__(self):
return self._message
class HttpClientNotFoundError(HttpClientError):
def __init__(self, message: str):
super().__init__(message)
class HttpClientCancelledRequestError(HttpClientError):
def __init__(self, message: str):
super().__init__(message)
class HttpClientBadRequestError(HttpClientError):
def __init__(self, message: str):
super().__init__(message)
class HttpClientUnauthorizedError(HttpClientError):
def __init__(self, message: str):
super().__init__(message)
class HttpClientForbiddenError(HttpClientError):
def __init__(self, message: str):
super().__init__(message)
class HttpClientTimeoutError(HttpClientError):
def __init__(self, message: str):
super().__init__(message)

View File

@@ -20,11 +20,10 @@ import copy
import pathlib
from gns3.qt import QtWidgets
from gns3.local_server_config import LocalServerConfig
from gns3.settings import LOCAL_SERVER_SETTINGS
from gns3.controller import Controller
from gns3.utils.file_copy_worker import FileCopyWorker
from gns3.utils.progress_dialog import ProgressDialog
from gns3.http_client_error import HttpClientError, HttpClientCancelledRequestError
from gns3.registry.image import Image
@@ -69,7 +68,7 @@ class ImageManager:
"""
if (server and server != "local") or Controller.instance().isRemote():
return self._uploadImageToRemoteServer(source_path, server, node_type)
return self._uploadImageToRemoteServer(source_path, node_type, parent)
else:
destination_directory = self.getDirectoryForType(node_type)
destination_path = os.path.join(destination_directory, os.path.basename(source_path))
@@ -115,27 +114,45 @@ class ImageManager:
source_path = destination_path
return source_path
def _uploadImageToRemoteServer(self, path, server, node_type):
def _uploadImageToRemoteServer(self, path, node_type, parent):
"""
Upload image to remote server
:param path: File path on computer
:param server: The server where the images should be located
:param node_type: Image node_type
:returns path: Final path
"""
if node_type == 'QEMU':
upload_endpoint = '/qemu/images'
image_type = 'qemu'
elif node_type == 'IOU':
upload_endpoint = '/iou/images'
image_type = 'iou'
elif node_type == 'DYNAMIPS':
upload_endpoint = '/dynamips/images'
image_type = 'ios'
else:
raise Exception('Invalid node type')
filename = self._getRelativeImagePath(path, node_type).replace("\\", "/")
Controller.instance().postCompute('{}/{}'.format(upload_endpoint, filename), server, None, body=pathlib.Path(path), progressText="Uploading {}".format(filename), timeout=None)
try:
Controller.instance().post(
f"/images/upload/{filename}",
callback=None,
params={"image_type": image_type},
body=pathlib.Path(path),
progress_text="Uploading {}".format(filename),
timeout=None,
wait=True
)
except HttpClientCancelledRequestError:
return
except HttpClientError as e:
QtWidgets.QMessageBox.critical(
parent,
"Image upload",
f"Could not upload image {filename}: {e}"
)
return filename
def _getRelativeImagePath(self, path, node_type):
@@ -164,7 +181,7 @@ class ImageManager:
:returns: path to the default images directory
"""
return copy.copy(LocalServerConfig.instance().loadSettings("Server", LOCAL_SERVER_SETTINGS)['images_path'])
return copy.copy(Controller.instance().settings()['images_path'])
def getDirectoryForType(self, node_type):
"""

View File

@@ -17,9 +17,11 @@
import os
import pathlib
import urllib.parse
from gns3.http_client import HTTPClient
from gns3.http_client_error import HttpClientError, HttpClientCancelledRequestError
from gns3.qt import QtWidgets
from gns3.registry.image import Image
from gns3.controller import Controller
import logging
log = logging.getLogger(__name__)
@@ -27,64 +29,43 @@ log = logging.getLogger(__name__)
class ImageUploadManager(object):
"""
Manager over the image upload. Encapsulates file uploads to computes or via controller.
Manager over the image upload
"""
def __init__(self, image, controller, compute_id, callback=None, directFileUpload=False):
self._image = image
self._compute_id = compute_id
self._callback = callback
self._directFileUpload = directFileUpload
self._controller = controller
def __init__(self, image: Image, controller: Controller, parent: QtWidgets.QWidget):
self._image = image
self._controller = controller
self._parent = parent
def upload(self) -> bool:
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)
else:
self._fileUploadToController()
return False
return self._fileUploadToController()
def _getComputePath(self):
return '/{emulator}/images/{filename}'.format(emulator=self._image.emulator, filename=self._image.filename)
def _fileUploadToController(self) -> bool:
def _onLoadEndpointCallback(self, result, error=False, **kwargs):
if error:
if "message" in result:
log.error("Error while getting endpoint: {}".format(result["message"]))
return
# we know where is the endpoint and we trying to post there a file
endpoint = result['endpoint']
self._fileUploadToCompute(endpoint)
def _checkIfSuccessfulCallback(self, result, error=False, **kwargs):
if error:
connection_error = kwargs.get('connection_error', False)
if connection_error:
log.debug("During direct file upload compute is not visible. Fallback to upload via controller.")
# there was an issue with connection, probably we don't have a direct access to compute
# we need to fallback to uploading files via controller
self._fileUploadToController()
else:
if "message" in result:
log.error("Error while direct file upload: {}".format(result["message"]))
return
self._callback(result, error, **kwargs)
def _fileUploadToCompute(self, 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),
context={"image_path": self._image.path}, progressText="Uploading {}".format(self._image.filename), timeout=None, prefix="")
def _fileUploadToController(self):
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)
try:
self._controller.post(
f"/images/upload/{self._image.filename}",
callback=None,
params={"allow_raw_image": True},
body=pathlib.Path(self._image.path),
context={"image_path": self._image.path},
progress_text="Uploading {}".format(self._image.filename),
timeout=None,
wait=True
)
except HttpClientCancelledRequestError:
return False
except HttpClientError as e:
QtWidgets.QMessageBox.critical(
self._parent,
"Image upload",
f"Could not upload image {self._image.filename}: {e}"
)
return False
return True

View File

@@ -92,7 +92,7 @@ class DrawingItem:
def updateDrawing(self):
if self._id and not self.deleting() and self._project:
self._project.put("/drawings/" + self._id, self.updateDrawingCallback, body=self.__json__(), showProgress=False)
self._project.put("/drawings/" + self._id, self.updateDrawingCallback, body=self.__json__(), show_progress=False)
@qslot
def updateDrawingCallback(self, result, error=False, **kwargs):

View File

@@ -159,6 +159,12 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
"""
self._link.deleteLink()
def reset(self):
"""
Reset this link
"""
self._link.resetLink()
def link(self):
"""
Returns the link attached to this link item.
@@ -282,6 +288,12 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
resume_action.triggered.connect(self._suspendActionSlot)
menu.addAction(resume_action)
# reset
reset_action = QtWidgets.QAction("Reset", menu)
reset_action.setIcon(get_icon('reload.svg'))
reset_action.triggered.connect(self._resetActionSlot)
menu.addAction(reset_action)
# style
style_action = QtWidgets.QAction("Style", menu)
style_action.setIcon(get_icon("node_conception.svg"))
@@ -340,6 +352,14 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
self._deleteActionSlot()
return
def _resetActionSlot(self):
"""
Slot to receive events from the reset action in the
contextual menu.
"""
self.reset()
def _deleteActionSlot(self):
"""
Slot to receive events from the delete action in the

View File

@@ -117,12 +117,13 @@ class Link(QtCore.QObject):
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)
self._response_stream = Controller.instance().get(
"/projects/{project_id}/links/{link_id}/capture/stream".format(project_id=self.project().id(), link_id=self._link_id),
callback=None,
show_progress=False,
download_progress_callback=self._downloadPcapProgress,
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
@@ -348,12 +349,35 @@ class Link(QtCore.QObject):
# let the GUI know about this link has been deleted
self.delete_link_signal.emit(self._id)
def resetLink(self):
"""
Resets this link.
"""
log.debug("reset link from {} {} to {} {}".format(self._source_node.name(),
self._source_port.name(),
self._destination_node.name(),
self._destination_port.name()))
Controller.instance().post("/projects/{project_id}/links/{link_id}/reset".format(project_id=self.project().id(),
link_id=self._link_id),
self._linkResetCallback)
def _linkResetCallback(self, result, error=False, **kwargs):
"""
Called after the link is reset.
"""
if error:
log.error("Error while resetting link: {}".format(result["message"]))
return
def startCapture(self, data_link_type, capture_file_name):
data = {
"capture_file_name": capture_file_name,
"data_link_type": data_link_type
}
Controller.instance().post("/projects/{project_id}/links/{link_id}/start_capture".format(project_id=self.project().id(), link_id=self._link_id),
Controller.instance().post("/projects/{project_id}/links/{link_id}/capture/start".format(project_id=self.project().id(), link_id=self._link_id),
self._startCaptureCallback,
body=data)
@@ -384,7 +408,7 @@ class Link(QtCore.QObject):
# 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(),
Controller.instance().post("/projects/{project_id}/links/{link_id}/capture/stop".format(project_id=self.project().id(),
link_id=self._link_id),
self._stopCaptureCallback)

View File

@@ -26,8 +26,6 @@ import psutil
from .qt import QtCore, QtWidgets
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__)
@@ -100,10 +98,10 @@ class LocalConfig(QtCore.QObject):
# 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)
# settings = LocalServerConfig.instance().loadSettings("Controller", CONTROLLER_SETTINGS)
# settings["path"] = ""
# settings["ubridge_path"] = ""
# LocalServerConfig.instance().saveSettings("Controller", settings)
else:
# create a new config
with open(self._config_file, "w", encoding="utf-8") as f:
@@ -416,20 +414,6 @@ class LocalConfig(QtCore.QObject):
settings["multi_profiles"] = value
self.saveSectionSettings("MainWindow", settings)
def directFileUpload(self):
"""
:returns: Boolean. True if direct_file_upload is enabled
"""
from gns3.settings import GENERAL_SETTINGS
return self.loadSectionSettings("MainWindow", GENERAL_SETTINGS)["direct_file_upload"]
def setDirectFileUpload(self, value):
from gns3.settings import GENERAL_SETTINGS
settings = self.loadSectionSettings("MainWindow", GENERAL_SETTINGS)
settings["direct_file_upload"] = value
self.saveSectionSettings("MainWindow", settings)
def showInterfaceLabelsOnNewProject(self):
"""
:returns: Boolean. True if show_interface_labels_on_new_project is enabled

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python
#
# Copyright (C) 2016 GNS3 Technologies Inc.
# Copyright (C) 2021 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
@@ -22,8 +22,6 @@ import stat
import shlex
import socket
import shutil
import random
import string
import struct
import psutil
import signal
@@ -31,13 +29,12 @@ import subprocess
from gns3.qt import QtWidgets, QtCore, qslot
from gns3.settings import LOCAL_SERVER_SETTINGS, DEFAULT_LOCAL_SERVER_HOST
from gns3.settings import DEFAULT_CONTROLLER_HOST
from gns3.local_config import LocalConfig
from gns3.local_server_config import LocalServerConfig
from gns3.http_client import HTTPClient
from gns3.utils.wait_for_connection_worker import WaitForConnectionWorker
from gns3.utils.progress_dialog import ProgressDialog
from gns3.utils.sudo import sudo
from gns3.http_client import HTTPClient
from gns3.controller import Controller
@@ -94,13 +91,6 @@ class LocalServer(QtCore.QObject):
self._settings = {}
self.localServerSettings()
self._port = self._settings.get("port", 3080)
if not self._settings.get("auto_start", True):
if self._settings.get("host") is None:
self._http_client = HTTPClient(self._settings)
Controller.instance().setHttpClient(self._http_client)
else:
self._http_client = None
self._stopping = False
self._timer = QtCore.QTimer()
self._timer.setInterval(5000)
@@ -122,27 +112,6 @@ class LocalServer(QtCore.QObject):
return MainWindow.instance()
return self._parent
def _checkWindowsService(self, service_name):
try:
import pywintypes
import win32service
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:
return False
except pywintypes.error as e:
if e.winerror == 1060: # service is not installed
return False
else:
log.error("Could not check if the {} service is running: {}".format(service_name, e.strerror))
return True
def _checkUbridgePermissions(self):
"""
Checks that uBridge can interact with network interfaces.
@@ -199,12 +168,6 @@ class LocalServer(QtCore.QObject):
return False
return True
def _passwordGenerate(self):
"""
Generate a random password
"""
return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(64))
def localServerSettings(self):
"""
Returns the local server settings.
@@ -212,14 +175,9 @@ class LocalServer(QtCore.QObject):
:returns: local server settings (dict)
"""
settings = LocalServerConfig.instance().loadSettings("Server", LOCAL_SERVER_SETTINGS)
settings = Controller.instance().settings()
self._settings = copy.copy(settings)
# user & password
if settings["auth"] is True and not settings["user"].strip():
settings["user"] = "admin"
settings["password"] = self._passwordGenerate()
# local GNS3 server path
local_server_path = shutil.which(settings["path"].strip())
if local_server_path is None:
@@ -248,14 +206,14 @@ class LocalServer(QtCore.QObject):
"""
if "host" in new_settings and new_settings["host"] is None:
new_settings["host"] = DEFAULT_LOCAL_SERVER_HOST
new_settings["host"] = DEFAULT_CONTROLLER_HOST
old_settings = copy.copy(self._settings)
if not self._settings:
self._settings = new_settings
else:
self._settings.update(new_settings)
self._port = self._settings["port"]
LocalServerConfig.instance().saveSettings("Server", self._settings)
Controller.instance().setSettings(self._settings)
# Settings have changed we need to restart the server
if old_settings != self._settings:
@@ -275,12 +233,6 @@ class LocalServer(QtCore.QObject):
else:
self.stopLocalServer(wait=True)
if self._settings.get("host") is None:
self._http_client = None
else:
self._http_client = HTTPClient(self._settings)
Controller.instance().setHttpClient(self._http_client)
def shouldLocalServerAutoStart(self):
"""
Returns either the local server
@@ -321,29 +273,24 @@ class LocalServer(QtCore.QObject):
Try to start the embedded gns3 server.
"""
if not self.shouldLocalServerAutoStart():
self._http_client = HTTPClient(self._settings)
Controller.instance().setHttpClient(self._http_client)
return
if self.isLocalServerRunning() and self._server_started_by_me:
local_server_already_running = self.isLocalServerRunning()
if local_server_already_running and self._server_started_by_me:
return True
# We check if two gui are not launched at the same time
# to avoid killing the server of the other GUI
if not LocalConfig.isMainGui():
log.info("Not the main GUI, will not auto start the server")
self._http_client = HTTPClient(self._settings)
Controller.instance().setHttpClient(self._http_client)
Controller.instance().connect()
return True
if self.isLocalServerRunning():
if local_server_already_running:
log.debug("A local server already running on this host")
# Try to kill the server. The server can be still running after
# if the server was started by hand
self._killAlreadyRunningServer()
if not self.isLocalServerRunning():
if not local_server_already_running:
if not self.initLocalServer():
QtWidgets.QMessageBox.critical(self.parent(), "Local server", "Could not start the local server process: {}".format(self._settings["path"]))
return False
@@ -354,15 +301,14 @@ class LocalServer(QtCore.QObject):
if self.parent():
worker = WaitForConnectionWorker(self._settings["host"], self._port)
progress_dialog = ProgressDialog(worker,
"Local server",
"Connecting to server {} on port {}...".format(self._settings["host"], self._port),
"Local controller",
"Starting local controller {} on port {}...".format(self._settings["host"], self._port),
"Cancel", busy=True, parent=self.parent())
progress_dialog.show()
if not progress_dialog.exec_():
return False
self._server_started_by_me = True
self._http_client = HTTPClient(self._settings)
Controller.instance().setHttpClient(self._http_client)
Controller.instance().connect()
return True
def initLocalServer(self):
@@ -371,15 +317,6 @@ class LocalServer(QtCore.QObject):
"""
self._checkUbridgePermissions()
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()
@@ -472,7 +409,7 @@ class LocalServer(QtCore.QObject):
pass
except OSError as e:
log.warning("could not delete server log file {}: {}".format(logpath, e))
command += ' --log="{}" --pid="{}"'.format(logpath, self._pid_path())
command += ' --logfile="{}" --pid="{}"'.format(logpath, self._pid_path())
log.debug("Starting local server process with {}".format(command))
try:
@@ -521,19 +458,8 @@ class LocalServer(QtCore.QObject):
:returns: boolean
"""
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:
return False
elif status != 200:
return False
else:
version = json_data.get("version", None)
if version is None:
log.debug("Server is not a GNS3 server")
return False
return True
http_client = HTTPClient(self._settings)
return http_client.checkServerRunning()
def stopLocalServer(self, wait=False):
"""
@@ -544,13 +470,14 @@ class LocalServer(QtCore.QObject):
if self.localServerProcessIsRunning():
self._stopping = True
log.debug("Stopping local server (PID={})".format(self._local_server_process.pid))
log.debug("Stopping local controller (PID={})".format(self._local_server_process.pid))
# local server is running, let's stop it
if self._http_client:
self._http_client.shutdown()
http_client = Controller.instance().httpClient()
if http_client:
http_client.shutdown()
if wait:
worker = StopLocalServerWorker(self._local_server_process)
progress_dialog = ProgressDialog(worker, "Local server", "Waiting for the local server to stop...", None, busy=True, parent=self.parent())
progress_dialog = ProgressDialog(worker, "Local server", "Waiting for the local controller to stop...", None, busy=True, parent=self.parent())
progress_dialog.show()
progress_dialog.exec_()
if self._local_server_process.returncode is None:
@@ -573,8 +500,8 @@ class LocalServer(QtCore.QObject):
self._local_server_process.wait(timeout=60)
except subprocess.TimeoutExpired:
proceed = QtWidgets.QMessageBox.question(self.parent(),
"Local server",
"The Local server cannot be stopped, would you like to kill it?",
"Local controller",
"The local controller cannot be stopped, would you like to kill it?",
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)

View File

@@ -1,163 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
import configparser
import logging
log = logging.getLogger(__name__)
class LocalServerConfig:
"""
Local server configuration.
"""
def __init__(self, config_file=None):
appname = "GNS3"
self._config = configparser.RawConfigParser()
if config_file:
self._config_file = config_file
else:
if sys.platform.startswith("win"):
filename = "gns3_server.ini"
else:
filename = "gns3_server.conf"
from .local_config import LocalConfig
if sys.platform.startswith("win"):
self._config_file = os.path.join(LocalConfig.instance().configDirectory(), filename)
else:
self._config_file = os.path.join(LocalConfig.instance().configDirectory(), filename)
try:
# create the config file if it doesn't exist
open(self._config_file, "a").close()
except OSError as e:
log.error("Could not create the local server configuration {}: {}".format(self._config_file, e))
self.readConfig()
def setConfigFile(self, path):
"""
Change the location of the server config (use for test)
"""
self._config = configparser.RawConfigParser()
self._config_file = path
self.readConfig()
def readConfig(self):
"""
Read the configuration file.
"""
try:
self._config.read(self._config_file, encoding="utf-8")
except (OSError, configparser.Error, UnicodeEncodeError, UnicodeDecodeError) as e:
log.error("Could not read the local server configuration {}: {}".format(self._config_file, e))
def writeConfig(self):
"""
Write the configuration file.
"""
try:
log.debug("Write configuration file %s", self._config_file)
with open(self._config_file, "w", encoding="utf-8") as fp:
self._config.write(fp)
except (OSError, configparser.Error) as e:
log.error("Could not write the local server configuration {}: {}".format(self._config_file, e))
def loadSettings(self, section, default_settings):
"""
Get all the settings from a given section.
:param section: section name
:param default_settings: setting names and default values (dict)
:returns: settings (dict)
"""
if section not in self._config:
self._config[section] = {}
settings = {}
for name, default in default_settings.items():
if isinstance(default, bool):
settings[name] = self._config[section].getboolean(name, default)
elif isinstance(default, int):
settings[name] = self._config[section].getint(name, default)
elif isinstance(default, float):
settings[name] = self._config[section].getfloat(name, default)
else:
settings[name] = self._config[section].get(name, default)
if settings[name] == "None":
settings[name] = None
# sync with the config file
self.saveSettings(section, settings)
return settings
def saveSettings(self, section, settings):
"""
Save all the settings in a given section.
:param section: section name
:param settings: settings to save (dict)
"""
changed = False
if section not in self._config:
self._config[section] = {}
changed = True
for name, value in settings.items():
if name not in self._config[section] or self._config[section][name] != str(value):
self._config[section][name] = str(value)
changed = True
if changed:
self.writeConfig()
def deleteSetting(self, section, name):
"""
Delete a specific setting in a given section.
:param section: section name
:param name: setting name to delete
"""
if section in self._config and name in self._config[section]:
del self._config[section][name]
self.writeConfig()
@staticmethod
def instance():
"""
Singleton to return only on instance of LocalServerConfig.
:returns: instance of Config
"""
if not hasattr(LocalServerConfig, "_instance"):
LocalServerConfig._instance = LocalServerConfig()
return LocalServerConfig._instance

View File

@@ -146,7 +146,6 @@ def main():
frozen_dir,
os.path.normpath(os.path.join(frozen_dir, 'dynamips')),
os.path.normpath(os.path.join(frozen_dir, 'vpcs')),
os.path.normpath(os.path.join(frozen_dir, 'traceng'))
]
os.environ["PATH"] = os.pathsep.join(frozen_dirs) + os.pathsep + os.environ.get("PATH", "")
@@ -185,9 +184,9 @@ def main():
# catch exceptions to write them in a file
sys.excepthook = exceptionHook
# we only support Python 3 version >= 3.4
if sys.version_info < (3, 4):
raise SystemExit("Python 3.4 or higher is required")
# we only support Python 3 version >= 3.7
if sys.version_info < (3, 7):
raise SystemExit("Python 3.7 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))

View File

@@ -39,6 +39,7 @@ from .dialogs.snapshots_dialog import SnapshotsDialog
from .dialogs.export_debug_dialog import ExportDebugDialog
from .dialogs.doctor_dialog import DoctorDialog
from .dialogs.edit_project_dialog import EditProjectDialog
from .dialogs.image_dialog import ImageDialog
from .dialogs.setup_wizard import SetupWizard
from .settings import GENERAL_SETTINGS
from .items.node_item import NodeItem
@@ -86,7 +87,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.setupUi(self)
self.setUnifiedTitleAndToolBarOnMac(True)
# These widgets will be disabled when no project is loaded
# This widgets will be disable when you have no project loaded
self.disableWhenNoProjectWidgets = [
self.uiGraphicsView,
self.uiAnnotateMenu,
@@ -100,9 +101,27 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.uiEditProjectAction,
self.uiDeleteProjectAction,
self.uiImportExportConfigsAction,
self.uiLockAllAction
self.uiLockAllAction,
self.uiShowReadmeAction
]
for widget in self.disableWhenNoProjectWidgets:
widget.setEnabled(False)
self.disableWhenControllerNotConnectedWidgets = [
self.uiNewProjectAction,
self.uiOpenProjectAction,
self.uiImportProjectAction,
self.uiNewTemplateAction,
self.uiImportProjectAction,
self.uiOpenApplianceAction,
self.uiWebUIAction,
self.uiNodesDockWidget
]
for widget in self.disableWhenControllerNotConnectedWidgets:
widget.setEnabled(False)
self._notif_dialog = NotifDialog(self)
# Setup logger
logging.getLogger().addHandler(NotifDialogHandler(self._notif_dialog))
@@ -117,8 +136,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
Controller.instance().setParent(self)
LocalServer.instance().setParent(self)
HTTPClient.setProgressCallback(Progress.instance(self))
self._first_file_load = True
self._open_project_path = None
self._loadSettings()
@@ -172,7 +189,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.uiFileMenu.insertActions(self.uiQuitAction, self.recent_file_actions)
self.recent_file_actions_separator = self.uiFileMenu.insertSeparator(self.uiQuitAction)
self.recent_file_actions_separator.setVisible(False)
self.updateRecentFileActions()
#self.updateRecentFileActions()
# add recent projects to the File menu
for i in range(0, self._maxrecent_files):
@@ -208,6 +225,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.uiOpenProjectAction.triggered.connect(self.openProjectActionSlot)
self.uiOpenApplianceAction.triggered.connect(self.openApplianceActionSlot)
self.uiNewTemplateAction.triggered.connect(self._newTemplateActionSlot)
self.uiImageManagementAction.triggered.connect(self._imageManagementActionSlot)
self.uiSaveProjectAsAction.triggered.connect(self._saveProjectAsActionSlot)
self.uiExportProjectAction.triggered.connect(self._exportProjectActionSlot)
self.uiImportProjectAction.triggered.connect(self._importProjectActionSlot)
@@ -232,6 +250,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.uiResetPortLabelsAction.triggered.connect(self._resetPortLabelsActionSlot)
self.uiShowPortNamesAction.triggered.connect(self._showPortNamesActionSlot)
self.uiShowGridAction.triggered.connect(self._showGridActionSlot)
self.uiShowReadmeAction.triggered.connect(self._showReadmeActionSlot)
self.uiSnapToGridAction.triggered.connect(self._snapToGridActionSlot)
self.uiLockAllAction.triggered.connect(self._lockActionSlot)
self.uiResetDocksAction.triggered.connect(self._resetDocksSlot)
@@ -323,7 +342,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def _openWebInterfaceActionSlot(self):
if Controller.instance().connected():
base_url = Controller.instance().httpClient().fullUrl()
webui_url = "{}/static/web-ui/bundled".format(base_url)
webui_url = f"{base_url}/static/web-ui/bundled"
QtGui.QDesktopServices.openUrl(QtCore.QUrl(webui_url))
def _showGridActionSlot(self):
@@ -412,6 +431,15 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
dialog.show()
dialog.exec_()
def _imageManagementActionSlot(self):
"""
Called when user wants to manage images
"""
dialog = ImageDialog(self)
dialog.show()
dialog.exec_()
@qslot
def openApplianceActionSlot(self, *args):
"""
@@ -508,7 +536,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._appliance_wizard.exec_()
elif path.endswith(".gns3"):
if Controller.instance().isRemote():
QtWidgets.QMessageBox.critical(self, "Open project", "Cannot open a .gns3 file on a remote server, please use a portable project (.gns3p) instead")
QtWidgets.QMessageBox.critical(self, "Open project", "Cannot open a .gns3 file on a remote server, please use a project (.gns3p) instead")
return
else:
Topology.instance().loadProject(path)
@@ -548,6 +576,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
Refresh widgets that should be visible or not
"""
for widget in self.disableWhenControllerNotConnectedWidgets:
widget.setEnabled(Controller.instance().connected())
# No projects
if Topology.instance().project() is None:
for widget in self.disableWhenNoProjectWidgets:
@@ -967,12 +998,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
Slot to open the setup wizard.
"""
with Progress.instance().context(min_duration=0):
setup_wizard = SetupWizard(self)
setup_wizard.show()
res = setup_wizard.exec_()
# start and connect to the local server if needed
LocalServer.instance().localServerAutoStartIfRequired()
setup_wizard = SetupWizard(self)
setup_wizard.show()
setup_wizard.exec_()
def _aboutQtActionSlot(self):
"""
@@ -1106,6 +1134,12 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""
Topology.instance().editReadme()
def _showReadmeActionSlot(self):
"""
Slot to show the README file
"""
Topology.instance().showReadme()
def resizeEvent(self, event):
self._notif_dialog.resize()
super().resizeEvent(event)
@@ -1241,13 +1275,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
if not self._settings["hide_setup_wizard"]:
self._setupWizardActionSlot()
else:
# start and connect to the local server if needed
LocalServer.instance().localServerAutoStartIfRequired()
if self._open_file_at_startup:
self.loadPath(self._open_file_at_startup)
self._open_file_at_startup = None
elif Topology.instance().project() is None:
self._newProjectActionSlot()
if Controller.instance().isRemote():
Controller.instance().connect()
else:
# start and connect to the local server if needed
LocalServer.instance().localServerAutoStartIfRequired()
if self._settings["check_for_update"]:
# automatic check for update every week (604800 seconds)
@@ -1400,9 +1432,17 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.recent_file_actions_separator.setVisible(False)
def _controllerConnectedSlot(self):
self.updateRecentFileActions()
self._refreshVisibleWidgets()
if self._settings["hide_setup_wizard"]:
if self._open_file_at_startup:
self.loadPath(self._open_file_at_startup)
self._open_file_at_startup = None
elif Topology.instance().project() is None:
self._newProjectActionSlot()
def run_later(self, counter, callback):
"""
Run a function after X milliseconds
@@ -1415,20 +1455,20 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def _exportProjectActionSlot(self):
"""
Slot called to export a portable project
Slot called to export a project
"""
Topology.instance().exportProject()
def _importProjectActionSlot(self):
"""
Slot called to import a portable project
Slot called to import a project
"""
directory = self._portable_project_dir
if not os.path.exists(directory):
directory = Topology.instance().projectsDirPath()
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open portable project", directory,
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open project", directory,
"All files (*.*);;GNS3 Portable Project (*.gns3project *.gns3p)",
"GNS3 Portable Project (*.gns3project *.gns3p)")
if path:

View File

@@ -19,12 +19,9 @@ from gns3.modules.builtin import Builtin
from gns3.modules.dynamips import Dynamips
from gns3.modules.iou import IOU
from gns3.modules.vpcs import VPCS
from gns3.modules.traceng import TraceNG
from gns3.modules.virtualbox import VirtualBox
from gns3.modules.qemu import Qemu
from gns3.modules.vmware import VMware
from gns3.modules.docker import Docker
#MODULES = [Builtin, VPCS, Dynamips, IOU, Qemu, VirtualBox, VMware, Docker, TraceNG]
#FIXME: deactivate TraceNG module
MODULES = [Builtin, VPCS, Dynamips, IOU, Qemu, VirtualBox, VMware, Docker]

View File

@@ -19,9 +19,7 @@
Built-in module implementation.
"""
from gns3.qt import QtWidgets
from gns3.local_config import LocalConfig
from gns3.local_server_config import LocalServerConfig
from ..module import Module
from .cloud import Cloud
@@ -53,14 +51,15 @@ class Builtin(Module):
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
server_settings = {}
config = LocalServerConfig.instance()
if self._settings["default_nat_interface"]:
# save some settings to the local server config file
server_settings["default_nat_interface"] = self._settings["default_nat_interface"]
config.saveSettings(self.__class__.__name__, server_settings)
else:
config.deleteSetting(self.__class__.__name__, "default_nat_interface")
# FIXME: handle server side config
# server_settings = {}
# config = LocalServerConfig.instance()
# if self._settings["default_nat_interface"]:
# # save some settings to the local server config file
# server_settings["default_nat_interface"] = self._settings["default_nat_interface"]
# config.saveSettings(self.__class__.__name__, server_settings)
# else:
# config.deleteSetting(self.__class__.__name__, "default_nat_interface")
def _loadSettings(self):
"""

View File

@@ -42,6 +42,7 @@ class Cloud(Node):
self._always_on = True
self._interfaces = {}
self._cloud_settings = {"ports_mapping": [],
"usage": "",
"remote_console_host": CLOUD_SETTINGS["remote_console_host"],
"remote_console_port": CLOUD_SETTINGS["remote_console_port"],
"remote_console_type": CLOUD_SETTINGS["remote_console_type"],
@@ -139,7 +140,8 @@ class Cloud(Node):
port_info += " Port {name} {description}\n".format(name=port.name(),
description=port.description())
return info + port_info
usage = "\n" + self._settings.get("usage")
return info + port_info + usage
def configPage(self):
"""

View File

@@ -48,7 +48,7 @@ class CloudWizard(VMWizard, Ui_CloudNodeWizard):
"""
settings = {"name": self.uiNameLineEdit.text(),
"symbol": ":/symbols/cloud.svg",
"symbol": "cloud",
"compute_id": self._compute_id}
return settings

View File

@@ -54,7 +54,7 @@ class EthernetHubWizard(VMWizard, Ui_EthernetHubWizard):
"name": "Ethernet{}".format(port_number)})
settings = {"name": self.uiNameLineEdit.text(),
"symbol": ":/symbols/hub.svg",
"symbol": "hub",
"category": Node.switches,
"compute_id": self._compute_id,
"ports_mapping": ports}

View File

@@ -54,10 +54,10 @@ class EthernetSwitchWizard(VMWizard, Ui_EthernetSwitchWizard):
"name": "Ethernet{}".format(port_number),
"type": "access",
"vlan": 1,
"ethertype": ""})
"ethertype": "0x8100"})
settings = {"name": self.uiNameLineEdit.text(),
"symbol": ":/symbols/ethernet_switch.svg",
"symbol": "ethernet_switch",
"category": Node.switches,
"compute_id": self._compute_id,
"ports_mapping": ports}

View File

@@ -473,9 +473,13 @@ class CloudConfigurationPage(QtWidgets.QWidget, Ui_cloudConfigPageWidget):
if index != -1:
self.uiCategoryComboBox.setCurrentIndex(index)
Controller.instance().getCompute("/network/interfaces", settings["compute_id"],
self._getInterfacesFromServerCallback,
progressText="Retrieving network interfaces...")
Controller.instance().getCompute(
"/network/interfaces",
settings["compute_id"],
self._getInterfacesFromServerCallback,
progress_text="Retrieving network interfaces...",
wait=True
)
else:
self.uiDefaultNameFormatLabel.hide()
@@ -490,6 +494,7 @@ class CloudConfigurationPage(QtWidgets.QWidget, Ui_cloudConfigPageWidget):
self._interfaces = self._node.interfaces()
self._loadNetworkInterfaces(self._interfaces)
self.uiUsageTextEdit.setPlainText(settings["usage"])
# load the current ports
self._ports = []
self.uiEthernetListWidget.clear()
@@ -560,4 +565,6 @@ class CloudConfigurationPage(QtWidgets.QWidget, Ui_cloudConfigPageWidget):
settings["ports_mapping"] = self._ports
else:
settings["ports_mapping"] = self._ports
settings["usage"] = self.uiUsageTextEdit.toPlainText()
return settings

View File

@@ -87,7 +87,11 @@ class CloudPreferencesPage(QtWidgets.QWidget, Ui_CloudPreferencesPageWidget):
QtWidgets.QTreeWidgetItem(section_item, ["Console type:", cloud_node["remote_console_type"]])
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", cloud_node["default_name_format"]])
try:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(cloud_node["compute_id"]).name()])
compute_id = cloud_node.get("compute_id")
if compute_id:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(compute_id).name()])
else:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", "Dynamically allocated by the controller"])
except KeyError:
pass

View File

@@ -83,7 +83,11 @@ class EthernetHubPreferencesPage(QtWidgets.QWidget, Ui_EthernetHubPreferencesPag
QtWidgets.QTreeWidgetItem(section_item, ["Template ID:", ethernet_hub.get("template_id", "none")])
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", ethernet_hub["default_name_format"]])
try:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(ethernet_hub["compute_id"]).name()])
compute_id = ethernet_hub.get("compute_id")
if compute_id:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(compute_id).name()])
else:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", "Dynamically allocated by the controller"])
except KeyError:
pass
QtWidgets.QTreeWidgetItem(section_item, ["Number of ports:", str(len(ethernet_hub["ports_mapping"]))])

View File

@@ -82,7 +82,11 @@ class EthernetSwitchPreferencesPage(QtWidgets.QWidget, Ui_EthernetSwitchPreferen
QtWidgets.QTreeWidgetItem(section_item, ["Template ID:", ethernet_switch.get("template_id", "none")])
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", ethernet_switch["default_name_format"]])
try:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(ethernet_switch["compute_id"]).name()])
compute_id = ethernet_switch.get("compute_id")
if compute_id:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(compute_id).name()])
else:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", "Dynamically allocated by the controller"])
except KeyError:
pass

View File

@@ -34,12 +34,13 @@ BUILTIN_SETTINGS = {
CLOUD_SETTINGS = {
"name": "",
"usage": "",
"remote_console_host": "127.0.0.1",
"remote_console_port": 23,
"remote_console_type": "none",
"remote_console_http_path": "/",
"default_name_format": "Cloud{0}",
"symbol": ":/symbols/cloud.svg",
"symbol": "cloud",
"category": Node.end_devices,
"ports_mapping": [],
"node_type": "cloud"
@@ -48,7 +49,7 @@ CLOUD_SETTINGS = {
ETHERNET_HUB_SETTINGS = {
"name": "",
"default_name_format": "Hub{0}",
"symbol": ":/symbols/hub.svg",
"symbol": "hub",
"category": Node.switches,
"ports_mapping": [],
"node_type": "ethernet_hub"
@@ -57,7 +58,7 @@ ETHERNET_HUB_SETTINGS = {
ETHERNET_SWITCH_SETTINGS = {
"name": "",
"default_name_format": "Switch{0}",
"symbol": ":/symbols/ethernet_switch.svg",
"symbol": "ethernet_switch",
"category": Node.switches,
"console_type": "none",
"ports_mapping": [],

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>979</width>
<height>564</height>
<width>1034</width>
<height>575</height>
</rect>
</property>
<property name="windowTitle">
@@ -555,6 +555,16 @@
<zorder>uiConsoleTypeLabel</zorder>
<zorder>uiConsoleTypeComboBox</zorder>
</widget>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Usage</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QPlainTextEdit" name="uiUsageTextEdit"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout>

View File

@@ -2,16 +2,19 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/builtin/ui/cloud_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
# Created by: PyQt5 UI code generator 5.15.0
#
# 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_cloudConfigPageWidget(object):
def setupUi(self, cloudConfigPageWidget):
cloudConfigPageWidget.setObjectName("cloudConfigPageWidget")
cloudConfigPageWidget.resize(979, 564)
cloudConfigPageWidget.resize(1034, 575)
self.verticalLayout = QtWidgets.QVBoxLayout(cloudConfigPageWidget)
self.verticalLayout.setObjectName("verticalLayout")
self.uiTabWidget = QtWidgets.QTabWidget(cloudConfigPageWidget)
@@ -281,6 +284,14 @@ class Ui_cloudConfigPageWidget(object):
self.uiConsoleTypeLabel.raise_()
self.uiConsoleTypeComboBox.raise_()
self.uiTabWidget.addTab(self.MiscTab, "")
self.tab = QtWidgets.QWidget()
self.tab.setObjectName("tab")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.tab)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.uiUsageTextEdit = QtWidgets.QPlainTextEdit(self.tab)
self.uiUsageTextEdit.setObjectName("uiUsageTextEdit")
self.verticalLayout_3.addWidget(self.uiUsageTextEdit)
self.uiTabWidget.addTab(self.tab, "")
self.verticalLayout.addWidget(self.uiTabWidget)
self.retranslateUi(cloudConfigPageWidget)
@@ -335,4 +346,4 @@ class Ui_cloudConfigPageWidget(object):
self.uiConsoleTypeComboBox.setItemText(4, _translate("cloudConfigPageWidget", "https"))
self.uiConsoleTypeComboBox.setItemText(5, _translate("cloudConfigPageWidget", "none"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.MiscTab), _translate("cloudConfigPageWidget", "Misc."))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.tab), _translate("cloudConfigPageWidget", "Usage"))

View File

@@ -39,7 +39,7 @@ class DockerVMWizard(VMWizard, Ui_DockerVMWizard):
super().__init__(docker_containers, parent)
self._docker_containers = docker_containers
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/icons/docker.png"))
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/symbols/docker_guest.svg"))
self.uiNewImageRadioButton.setChecked(True)
self._existingImageRadioButtonToggledSlot(False)

View File

@@ -45,14 +45,17 @@ class DockerVM(Node):
"custom_adapters": DOCKER_CONTAINER_SETTINGS["custom_adapters"],
"start_command": DOCKER_CONTAINER_SETTINGS["start_command"],
"environment": DOCKER_CONTAINER_SETTINGS["environment"],
"aux": None,
"console_type": DOCKER_CONTAINER_SETTINGS["console_type"],
"console_auto_start": DOCKER_CONTAINER_SETTINGS["console_auto_start"],
"aux_type": DOCKER_CONTAINER_SETTINGS["aux_type"],
"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_volumes": DOCKER_CONTAINER_SETTINGS["extra_volumes"]}
"extra_volumes": DOCKER_CONTAINER_SETTINGS["extra_volumes"],
"memory": DOCKER_CONTAINER_SETTINGS["memory"],
"cpus": DOCKER_CONTAINER_SETTINGS["cpus"],
}
self.settings().update(docker_vm_settings)
@@ -68,6 +71,7 @@ class DockerVM(Node):
Local ID is {id} and server ID is {node_id}
Docker image is "{image}"
Console is on port {console} and type is {console_type}
Aux console is on port {aux} and type is {aux_type}
""".format(name=self.name(),
id=self.id(),
node_id=self._node_id,
@@ -76,6 +80,8 @@ class DockerVM(Node):
port=self.compute().port(),
console=self._settings["console"],
console_type=self._settings["console_type"],
aux=self._settings["aux"],
aux_type=self._settings["aux_type"],
image=self._settings["image"])
port_info = ""
@@ -116,18 +122,6 @@ 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

@@ -100,11 +100,14 @@ class DockerVMConfigurationPage(QtWidgets.QWidget, Ui_dockerVMConfigPageWidget):
self.uiEnvironmentTextEdit.setText(settings["environment"])
self.uiConsoleTypeComboBox.setCurrentIndex(self.uiConsoleTypeComboBox.findText(settings["console_type"]))
self.uiConsoleAutoStartCheckBox.setChecked(settings["console_auto_start"])
self.uiAuxTypeComboBox.setCurrentIndex(self.uiAuxTypeComboBox.findText(settings["aux_type"]))
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.setPlainText(settings["extra_hosts"])
self.uiExtraVolumeTextEdit.setPlainText("\n".join(settings["extra_volumes"]))
self.uiMaxMemorySpinBox.setValue(settings["memory"])
self.uiMaxCPUsDoubleSpinBox.setValue(settings["cpus"])
if not group:
self.uiNameLineEdit.setText(settings["name"])
@@ -172,12 +175,15 @@ class DockerVMConfigurationPage(QtWidgets.QWidget, Ui_dockerVMConfigPageWidget):
settings["environment"] = self.uiEnvironmentTextEdit.toPlainText()
settings["console_type"] = self.uiConsoleTypeComboBox.currentText()
settings["console_auto_start"] = self.uiConsoleAutoStartCheckBox.isChecked()
settings["aux_type"] = self.uiAuxTypeComboBox.currentText()
settings["console_resolution"] = self.uiConsoleResolutionComboBox.currentText()
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 ]
settings["memory"] = self.uiMaxMemorySpinBox.value()
settings["cpus"] = self.uiMaxCPUsDoubleSpinBox.value()
if not group:
adapters = self.uiAdapterSpinBox.value()

View File

@@ -81,11 +81,16 @@ class DockerVMPreferencesPage(QtWidgets.QWidget, Ui_DockerVMPreferencesPageWidge
QtWidgets.QTreeWidgetItem(section_item, ["Template ID:", docker_container.get("template_id", "none")])
QtWidgets.QTreeWidgetItem(section_item, ["Image name:", docker_container["image"]])
try:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(docker_container["compute_id"]).name()])
compute_id = docker_container.get("compute_id")
if compute_id:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(compute_id).name()])
else:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", "Dynamically allocated by the controller"])
except KeyError:
pass
QtWidgets.QTreeWidgetItem(section_item, ["Console type:", str(docker_container["console_type"])])
QtWidgets.QTreeWidgetItem(section_item, ["Auto start console:", "{}".format(docker_container["console_auto_start"])])
QtWidgets.QTreeWidgetItem(section_item, ["Auxiliary console type:", str(docker_container["aux_type"])])
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", docker_container["default_name_format"]])
QtWidgets.QTreeWidgetItem(section_item, ["Adapters:", str(docker_container["adapters"])])
if docker_container["start_command"]:

View File

@@ -29,7 +29,7 @@ DOCKER_SETTINGS = {
DOCKER_CONTAINER_SETTINGS = {
"default_name_format": "{name}-{0}",
"usage": "",
"symbol": ":/symbols/docker_guest.svg",
"symbol": "docker_guest",
"category": Node.end_devices,
"start_command": "",
"name": "",
@@ -39,10 +39,13 @@ DOCKER_CONTAINER_SETTINGS = {
"environment": "",
"console_type": "telnet",
"console_auto_start": False,
"aux_type": "none",
"console_resolution": "1024x768",
"console_http_port": 80,
"console_http_path": "/",
"extra_hosts": "",
"extra_volumes": [],
"memory": 0,
"cpus": 0,
"node_type": "docker"
}

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>938</width>
<height>872</height>
<width>961</width>
<height>1126</height>
</rect>
</property>
<property name="windowTitle">
@@ -116,14 +116,14 @@
</property>
</widget>
</item>
<item row="7" column="0">
<item row="10" column="0">
<widget class="QLabel" name="uiConsoleTypeLabel">
<property name="text">
<string>Console type:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<item row="10" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetNoConstraint</enum>
@@ -166,14 +166,14 @@
</item>
</layout>
</item>
<item row="8" column="0">
<item row="12" column="0">
<widget class="QLabel" name="uiConsoleResolutionLabel">
<property name="text">
<string>VNC console resolution:</string>
</property>
</widget>
</item>
<item row="8" column="1">
<item row="12" column="1">
<widget class="QComboBox" name="uiConsoleResolutionComboBox">
<item>
<property name="text">
@@ -227,14 +227,14 @@
</item>
</widget>
</item>
<item row="9" column="0">
<item row="13" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>HTTP port in the container:</string>
</property>
</widget>
</item>
<item row="9" column="1">
<item row="13" column="1">
<widget class="QSpinBox" name="uiConsoleHttpPortSpinBox">
<property name="minimum">
<number>1</number>
@@ -244,17 +244,17 @@
</property>
</widget>
</item>
<item row="10" column="0">
<item row="14" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>HTTP path:</string>
</property>
</widget>
</item>
<item row="10" column="1">
<item row="14" column="1">
<widget class="QLineEdit" name="uiHttpConsolePathLineEdit"/>
</item>
<item row="11" column="0">
<item row="15" column="0">
<widget class="QLabel" name="uiEnvironmentLabel">
<property name="text">
<string>Environment variables:
@@ -268,23 +268,81 @@
</property>
</widget>
</item>
<item row="11" column="1">
<item row="15" column="1">
<widget class="QTextEdit" name="uiEnvironmentTextEdit"/>
</item>
<item row="12" column="0">
<item row="16" column="0">
<widget class="QLabel" name="uiNetworkConfigLabel">
<property name="text">
<string>Network configuration</string>
</property>
</widget>
</item>
<item row="12" column="1">
<item row="16" column="1">
<widget class="QPushButton" name="uiNetworkConfigEditButton">
<property name="text">
<string>Edit</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="uiMaxCPUsLabel">
<property name="text">
<string>Maximum CPUs:</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QDoubleSpinBox" name="uiMaxCPUsDoubleSpinBox">
<property name="decimals">
<number>1</number>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="uiMaxMemoryLabel">
<property name="text">
<string>Maximum memory:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QSpinBox" name="uiMaxMemorySpinBox">
<property name="suffix">
<string> MB</string>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="singleStep">
<number>32</number>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="uiAuxTypeLabel">
<property name="text">
<string>Auxiliary console type:</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QComboBox" name="uiAuxTypeComboBox">
<item>
<property name="text">
<string>telnet</string>
</property>
</item>
<item>
<property name="text">
<string>none</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">

View File

@@ -2,9 +2,10 @@
# 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.14.1
# Created by: PyQt5 UI code generator 5.15.0
#
# WARNING! All changes made in this file will be lost!
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -13,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_dockerVMConfigPageWidget(object):
def setupUi(self, dockerVMConfigPageWidget):
dockerVMConfigPageWidget.setObjectName("dockerVMConfigPageWidget")
dockerVMConfigPageWidget.resize(938, 872)
dockerVMConfigPageWidget.resize(961, 1126)
self.verticalLayout = QtWidgets.QVBoxLayout(dockerVMConfigPageWidget)
self.verticalLayout.setObjectName("verticalLayout")
self.uiTabWidget = QtWidgets.QTabWidget(dockerVMConfigPageWidget)
@@ -74,7 +75,7 @@ class Ui_dockerVMConfigPageWidget(object):
self.gridLayout.addWidget(self.uiCustomAdaptersConfigurationPushButton, 6, 1, 1, 1)
self.uiConsoleTypeLabel = QtWidgets.QLabel(self.tab)
self.uiConsoleTypeLabel.setObjectName("uiConsoleTypeLabel")
self.gridLayout.addWidget(self.uiConsoleTypeLabel, 7, 0, 1, 1)
self.gridLayout.addWidget(self.uiConsoleTypeLabel, 10, 0, 1, 1)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint)
self.horizontalLayout.setObjectName("horizontalLayout")
@@ -89,10 +90,10 @@ class Ui_dockerVMConfigPageWidget(object):
self.uiConsoleAutoStartCheckBox = QtWidgets.QCheckBox(self.tab)
self.uiConsoleAutoStartCheckBox.setObjectName("uiConsoleAutoStartCheckBox")
self.horizontalLayout.addWidget(self.uiConsoleAutoStartCheckBox)
self.gridLayout.addLayout(self.horizontalLayout, 7, 1, 1, 1)
self.gridLayout.addLayout(self.horizontalLayout, 10, 1, 1, 1)
self.uiConsoleResolutionLabel = QtWidgets.QLabel(self.tab)
self.uiConsoleResolutionLabel.setObjectName("uiConsoleResolutionLabel")
self.gridLayout.addWidget(self.uiConsoleResolutionLabel, 8, 0, 1, 1)
self.gridLayout.addWidget(self.uiConsoleResolutionLabel, 12, 0, 1, 1)
self.uiConsoleResolutionComboBox = QtWidgets.QComboBox(self.tab)
self.uiConsoleResolutionComboBox.setObjectName("uiConsoleResolutionComboBox")
self.uiConsoleResolutionComboBox.addItem("")
@@ -108,32 +109,56 @@ class Ui_dockerVMConfigPageWidget(object):
self.gridLayout.addWidget(self.uiConsoleResolutionComboBox, 8, 1, 1, 1)
self.label = QtWidgets.QLabel(self.tab)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 9, 0, 1, 1)
self.gridLayout.addWidget(self.label, 13, 0, 1, 1)
self.uiConsoleHttpPortSpinBox = QtWidgets.QSpinBox(self.tab)
self.uiConsoleHttpPortSpinBox.setMinimum(1)
self.uiConsoleHttpPortSpinBox.setMaximum(65535)
self.uiConsoleHttpPortSpinBox.setObjectName("uiConsoleHttpPortSpinBox")
self.gridLayout.addWidget(self.uiConsoleHttpPortSpinBox, 9, 1, 1, 1)
self.gridLayout.addWidget(self.uiConsoleHttpPortSpinBox, 13, 1, 1, 1)
self.label_2 = QtWidgets.QLabel(self.tab)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 10, 0, 1, 1)
self.gridLayout.addWidget(self.label_2, 14, 0, 1, 1)
self.uiHttpConsolePathLineEdit = QtWidgets.QLineEdit(self.tab)
self.uiHttpConsolePathLineEdit.setObjectName("uiHttpConsolePathLineEdit")
self.gridLayout.addWidget(self.uiHttpConsolePathLineEdit, 10, 1, 1, 1)
self.gridLayout.addWidget(self.uiHttpConsolePathLineEdit, 14, 1, 1, 1)
self.uiEnvironmentLabel = QtWidgets.QLabel(self.tab)
self.uiEnvironmentLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
self.uiEnvironmentLabel.setWordWrap(False)
self.uiEnvironmentLabel.setObjectName("uiEnvironmentLabel")
self.gridLayout.addWidget(self.uiEnvironmentLabel, 11, 0, 1, 1)
self.gridLayout.addWidget(self.uiEnvironmentLabel, 15, 0, 1, 1)
self.uiEnvironmentTextEdit = QtWidgets.QTextEdit(self.tab)
self.uiEnvironmentTextEdit.setObjectName("uiEnvironmentTextEdit")
self.gridLayout.addWidget(self.uiEnvironmentTextEdit, 11, 1, 1, 1)
self.gridLayout.addWidget(self.uiEnvironmentTextEdit, 15, 1, 1, 1)
self.uiNetworkConfigLabel = QtWidgets.QLabel(self.tab)
self.uiNetworkConfigLabel.setObjectName("uiNetworkConfigLabel")
self.gridLayout.addWidget(self.uiNetworkConfigLabel, 12, 0, 1, 1)
self.gridLayout.addWidget(self.uiNetworkConfigLabel, 16, 0, 1, 1)
self.uiNetworkConfigEditButton = QtWidgets.QPushButton(self.tab)
self.uiNetworkConfigEditButton.setObjectName("uiNetworkConfigEditButton")
self.gridLayout.addWidget(self.uiNetworkConfigEditButton, 12, 1, 1, 1)
self.gridLayout.addWidget(self.uiNetworkConfigEditButton, 16, 1, 1, 1)
self.uiMaxCPUsLabel = QtWidgets.QLabel(self.tab)
self.uiMaxCPUsLabel.setObjectName("uiMaxCPUsLabel")
self.gridLayout.addWidget(self.uiMaxCPUsLabel, 8, 0, 1, 1)
self.uiMaxCPUsDoubleSpinBox = QtWidgets.QDoubleSpinBox(self.tab)
self.uiMaxCPUsDoubleSpinBox.setDecimals(1)
self.uiMaxCPUsDoubleSpinBox.setSingleStep(0.1)
self.uiMaxCPUsDoubleSpinBox.setObjectName("uiMaxCPUsDoubleSpinBox")
self.gridLayout.addWidget(self.uiMaxCPUsDoubleSpinBox, 8, 1, 1, 1)
self.uiMaxMemoryLabel = QtWidgets.QLabel(self.tab)
self.uiMaxMemoryLabel.setObjectName("uiMaxMemoryLabel")
self.gridLayout.addWidget(self.uiMaxMemoryLabel, 7, 0, 1, 1)
self.uiMaxMemorySpinBox = QtWidgets.QSpinBox(self.tab)
self.uiMaxMemorySpinBox.setMaximum(65535)
self.uiMaxMemorySpinBox.setSingleStep(32)
self.uiMaxMemorySpinBox.setObjectName("uiMaxMemorySpinBox")
self.gridLayout.addWidget(self.uiMaxMemorySpinBox, 7, 1, 1, 1)
self.uiAuxTypeLabel = QtWidgets.QLabel(self.tab)
self.uiAuxTypeLabel.setObjectName("uiAuxTypeLabel")
self.gridLayout.addWidget(self.uiAuxTypeLabel, 11, 0, 1, 1)
self.uiAuxTypeComboBox = QtWidgets.QComboBox(self.tab)
self.uiAuxTypeComboBox.setObjectName("uiAuxTypeComboBox")
self.uiAuxTypeComboBox.addItem("")
self.uiAuxTypeComboBox.addItem("")
self.gridLayout.addWidget(self.uiAuxTypeComboBox, 11, 1, 1, 1)
self.uiTabWidget.addTab(self.tab, "")
self.tab_2 = QtWidgets.QWidget()
self.tab_2.setObjectName("tab_2")
@@ -211,6 +236,12 @@ class Ui_dockerVMConfigPageWidget(object):
"(KEY=VALUE, one per line)"))
self.uiNetworkConfigLabel.setText(_translate("dockerVMConfigPageWidget", "Network configuration"))
self.uiNetworkConfigEditButton.setText(_translate("dockerVMConfigPageWidget", "Edit"))
self.uiMaxCPUsLabel.setText(_translate("dockerVMConfigPageWidget", "Maximum CPUs:"))
self.uiMaxMemoryLabel.setText(_translate("dockerVMConfigPageWidget", "Maximum memory:"))
self.uiMaxMemorySpinBox.setSuffix(_translate("dockerVMConfigPageWidget", " MB"))
self.uiAuxTypeLabel.setText(_translate("dockerVMConfigPageWidget", "Auxiliary console type:"))
self.uiAuxTypeComboBox.setItemText(0, _translate("dockerVMConfigPageWidget", "telnet"))
self.uiAuxTypeComboBox.setItemText(1, _translate("dockerVMConfigPageWidget", "none"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.tab), _translate("dockerVMConfigPageWidget", "General settings"))
self.uiExtraHostsLabel.setText(_translate("dockerVMConfigPageWidget", "Extra hosts added\n"
"to the /etc/hosts file.\n"

View File

@@ -25,7 +25,6 @@ import hashlib
from gns3.local_config import LocalConfig
from gns3.image_manager import ImageManager
from gns3.local_server_config import LocalServerConfig
from gns3.controller import Controller
from gns3.template_manager import TemplateManager
from gns3.template import Template
@@ -145,18 +144,19 @@ class Dynamips(Module):
# save the settings
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
# save some settings to the local server config file
server_settings = {
"allocate_aux_console_ports": self._settings["allocate_aux_console_ports"],
"ghost_ios_support": self._settings["ghost_ios_support"],
"sparse_memory_support": self._settings["sparse_memory_support"],
"mmap_support": self._settings["mmap_support"],
}
if self._settings["dynamips_path"]:
server_settings["dynamips_path"] = os.path.normpath(self._settings["dynamips_path"])
config = LocalServerConfig.instance()
config.saveSettings(self.__class__.__name__, server_settings)
# FIXME: handle server side config
# # save some settings to the local server config file
# server_settings = {
# "allocate_aux_console_ports": self._settings["allocate_aux_console_ports"],
# "ghost_ios_support": self._settings["ghost_ios_support"],
# "sparse_memory_support": self._settings["sparse_memory_support"],
# "mmap_support": self._settings["mmap_support"],
# }
#
# if self._settings["dynamips_path"]:
# server_settings["dynamips_path"] = os.path.normpath(self._settings["dynamips_path"])
# config = LocalServerConfig.instance()
# config.saveSettings(self.__class__.__name__, server_settings)
def updateImageIdlepc(self, image_path, idlepc):
"""

View File

@@ -220,13 +220,13 @@ class IOSRouterWizard(VMWithImagesWizard, Ui_IOSRouterWizard):
ram = self.uiRamSpinBox.value()
Controller.instance().postCompute("/auto_idlepc",
self._compute_id,
self._computeAutoIdlepcCallback,
callback=self._computeAutoIdlepcCallback,
timeout=None,
body={
"image": image,
"platform": platform,
"ram": ram})
self.uiIdlePCFinderPushButton.setEnabled(False)
"ram": ram},
wait=True)
def _etherSwitchSlot(self, state):
"""
@@ -316,7 +316,7 @@ class IOSRouterWizard(VMWithImagesWizard, Ui_IOSRouterWizard):
super().initializePage(page_id)
if self.page(page_id) == self.uiIOSImageWizardPage:
self.loadImagesList("/dynamips/images")
self.loadImagesList("ios")
elif self.page(page_id) == self.uiNameWizardPage:
self._prefillPlatform()
self.uiNameLineEdit.setText(self.uiPlatformComboBox.currentText())

View File

@@ -71,6 +71,7 @@ class Router(Node):
"console_type": "telnet",
"console_auto_start": False,
"aux": None,
"aux_type": "none",
"mac_addr": None,
"system_id": "FTX0945W0MY",
"slot0": None,
@@ -101,11 +102,14 @@ class Router(Node):
"""
log.debug("{} is requesting Idle-PC proposals".format(self.name()))
self.controllerHttpGet("/nodes/{node_id}/dynamips/idlepc_proposals".format(node_id=self._node_id),
callback,
timeout=240,
context={"router": self},
progressText="Computing Idle-PC values, please wait...")
self.controllerHttpGet(
"/nodes/{node_id}/dynamips/idlepc_proposals".format(node_id=self._node_id),
callback,
context={"router": self},
progress_text="Computing Idle-PC values, please wait...",
timeout=240,
wait=True
)
def computeAutoIdlepc(self, callback):
"""
@@ -113,11 +117,14 @@ class Router(Node):
"""
log.debug("{} is requesting Idle-PC proposals".format(self.name()))
self.controllerHttpGet("/nodes/{node_id}/dynamips/auto_idlepc".format(node_id=self._node_id),
callback,
timeout=240,
context={"router": self},
progressText="Computing Idle-PC values, please wait...")
self.controllerHttpGet(
"/nodes/{node_id}/dynamips/auto_idlepc".format(node_id=self._node_id),
callback,
context={"router": self},
progress_text="Computing Idle-PC values, please wait...",
timeout=240,
wait=True
)
def idlepc(self):
"""
@@ -242,7 +249,8 @@ class Router(Node):
Local ID is {id} and server ID is {node_id}
Dynamips ID is {dynamips_id}
Hardware is Dynamips emulated Cisco {platform} {specific_info} with {ram}MB RAM and {nvram}KB NVRAM
Console is on port {console} and type is {console_type}, AUX console is on port {aux}
Console is on port {console} and type is {console_type}
Auxiliary console is on port {aux} and type is {aux_type}
IOS image is "{image_name}"
{idlepc_info}
PCMCIA disks: disk0 is {disk0}MB and disk1 is {disk1}MB
@@ -260,6 +268,7 @@ class Router(Node):
console=self._settings["console"],
console_type=self._settings["console_type"],
aux=self._settings["aux"],
aux_type=self._settings["aux_type"],
image_name=os.path.basename(self._settings["image"]),
idlepc_info=idlepc_info,
disk0=self._settings["disk0"],
@@ -298,23 +307,6 @@ class Router(Node):
from ..pages.ios_router_configuration_page import IOSRouterConfigurationPage
return IOSRouterConfigurationPage
@staticmethod
def validateHostname(hostname):
"""
Checks if the hostname is valid.
:param hostname: hostname to check
:returns: boolean
"""
# 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 (ARPANET rules).
if re.search(r"""^(?!-|[0-9])[a-zA-Z0-9-]{1,63}(?<!-)$""", hostname):
return True
return False
@staticmethod
def defaultSymbol():
"""

View File

@@ -374,6 +374,11 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
self.uiConsoleAutoStartCheckBox.setChecked(settings["console_auto_start"])
# load the auxiliary console type
index = self.uiAuxTypeComboBox.findText(settings["aux_type"])
if index != -1:
self.uiAuxTypeComboBox.setCurrentIndex(index)
# load the memories and disks settings
self.uiRamSpinBox.setValue(settings["ram"])
self.uiNvramSpinBox.setValue(settings["nvram"])
@@ -488,8 +493,6 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
name = self.uiNameLineEdit.text()
if not name:
QtWidgets.QMessageBox.critical(self, "Name", "IOS router name cannot be empty!")
elif node and not node.validateHostname(name):
QtWidgets.QMessageBox.critical(self, "Name", "Invalid name detected for IOS router: {}".format(name))
else:
settings["name"] = name
@@ -579,6 +582,9 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
settings["console_type"] = self.uiConsoleTypeComboBox.currentText().lower()
settings["console_auto_start"] = self.uiConsoleAutoStartCheckBox.isChecked()
# save auxiliary console type
settings["aux_type"] = self.uiAuxTypeComboBox.currentText().lower()
# save the memories and disks settings
settings["ram"] = self.uiRamSpinBox.value()
settings["nvram"] = self.uiNvramSpinBox.value()

View File

@@ -386,7 +386,11 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
QtWidgets.QTreeWidgetItem(section_item, ["Template ID:", ios_router.get("template_id", "none")])
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", ios_router["default_name_format"]])
try:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(ios_router["compute_id"]).name()])
compute_id = ios_router.get("compute_id")
if compute_id:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(compute_id).name()])
else:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", "Dynamically allocated by the controller"])
except KeyError:
pass
QtWidgets.QTreeWidgetItem(section_item, ["Platform:", ios_router["platform"]])
@@ -395,6 +399,7 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
QtWidgets.QTreeWidgetItem(section_item, ["Image:", ios_router["image"]])
QtWidgets.QTreeWidgetItem(section_item, ["Console type:", ios_router["console_type"]])
QtWidgets.QTreeWidgetItem(section_item, ["Auto start console:", "{}".format(ios_router["console_auto_start"])])
QtWidgets.QTreeWidgetItem(section_item, ["Auxiliary console type:", ios_router["aux_type"]])
if ios_router["idlepc"]:
QtWidgets.QTreeWidgetItem(section_item, ["Idle-PC:", ios_router["idlepc"]])
if ios_router["startup_config"]:

View File

@@ -34,12 +34,13 @@ IOS_ROUTER_SETTINGS = {
"default_name_format": "R{0}",
"usage": "",
"image": "",
"symbol": ":/symbols/router.svg",
"symbol": "router",
"category": Node.routers,
"startup_config": "",
"private_config": "",
"console_type": "telnet",
"console_auto_start": False,
"aux_type": "none",
"platform": "",
"idlepc": "",
"idlemax": 500,

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>980</width>
<height>734</height>
<width>964</width>
<height>830</height>
</rect>
</property>
<property name="windowTitle">
@@ -250,7 +250,7 @@
</item>
</layout>
</item>
<item row="12" column="1" colspan="2">
<item row="14" column="1" colspan="2">
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -263,6 +263,27 @@
</property>
</spacer>
</item>
<item row="12" column="2">
<widget class="QComboBox" name="uiAuxTypeComboBox">
<item>
<property name="text">
<string>telnet</string>
</property>
</item>
<item>
<property name="text">
<string>none</string>
</property>
</item>
</widget>
</item>
<item row="12" column="0" colspan="2">
<widget class="QLabel" name="uiAuxTypeLabel">
<property name="text">
<string>Aux console type:</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="uiMemoriesPageWidget">
@@ -270,7 +291,16 @@
<string>Memories and disks</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_5">
<property name="margin">
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item row="0" column="0">
@@ -448,7 +478,16 @@
<string>Slots</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<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>
@@ -649,7 +688,16 @@
<string>Advanced</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>
@@ -828,7 +876,16 @@
<string>Environment</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="margin">
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item>

View File

@@ -2,16 +2,19 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/dynamips/ui/ios_router_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
# Created by: PyQt5 UI code generator 5.15.0
#
# 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_iosRouterConfigPageWidget(object):
def setupUi(self, iosRouterConfigPageWidget):
iosRouterConfigPageWidget.setObjectName("iosRouterConfigPageWidget")
iosRouterConfigPageWidget.resize(980, 734)
iosRouterConfigPageWidget.resize(964, 830)
self.vboxlayout = QtWidgets.QVBoxLayout(iosRouterConfigPageWidget)
self.vboxlayout.setObjectName("vboxlayout")
self.uiTabWidget = QtWidgets.QTabWidget(iosRouterConfigPageWidget)
@@ -143,7 +146,15 @@ class Ui_iosRouterConfigPageWidget(object):
self.horizontalLayout.addWidget(self.uiConsoleAutoStartCheckBox)
self.gridLayout_2.addLayout(self.horizontalLayout, 11, 2, 1, 1)
spacerItem = QtWidgets.QSpacerItem(263, 151, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_2.addItem(spacerItem, 12, 1, 1, 2)
self.gridLayout_2.addItem(spacerItem, 14, 1, 1, 2)
self.uiAuxTypeComboBox = QtWidgets.QComboBox(self.uiGeneralPageWidget)
self.uiAuxTypeComboBox.setObjectName("uiAuxTypeComboBox")
self.uiAuxTypeComboBox.addItem("")
self.uiAuxTypeComboBox.addItem("")
self.gridLayout_2.addWidget(self.uiAuxTypeComboBox, 12, 2, 1, 1)
self.uiAuxTypeLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiAuxTypeLabel.setObjectName("uiAuxTypeLabel")
self.gridLayout_2.addWidget(self.uiAuxTypeLabel, 12, 0, 1, 2)
self.uiTabWidget.addTab(self.uiGeneralPageWidget, "")
self.uiMemoriesPageWidget = QtWidgets.QWidget()
self.uiMemoriesPageWidget.setObjectName("uiMemoriesPageWidget")
@@ -582,6 +593,9 @@ class Ui_iosRouterConfigPageWidget(object):
self.uiConsoleTypeComboBox.setItemText(0, _translate("iosRouterConfigPageWidget", "telnet"))
self.uiConsoleTypeComboBox.setItemText(1, _translate("iosRouterConfigPageWidget", "none"))
self.uiConsoleAutoStartCheckBox.setText(_translate("iosRouterConfigPageWidget", "Auto start console"))
self.uiAuxTypeComboBox.setItemText(0, _translate("iosRouterConfigPageWidget", "telnet"))
self.uiAuxTypeComboBox.setItemText(1, _translate("iosRouterConfigPageWidget", "none"))
self.uiAuxTypeLabel.setText(_translate("iosRouterConfigPageWidget", "Aux console type:"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiGeneralPageWidget), _translate("iosRouterConfigPageWidget", "General"))
self.uiRamLabel.setText(_translate("iosRouterConfigPageWidget", "RAM size:"))
self.uiRamSpinBox.setSuffix(_translate("iosRouterConfigPageWidget", " MiB"))
@@ -639,4 +653,3 @@ class Ui_iosRouterConfigPageWidget(object):
self.uiSensor4SpinBox.setSuffix(_translate("iosRouterConfigPageWidget", " C"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiEnvironmentPageWidget), _translate("iosRouterConfigPageWidget", "Environment"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiUsageTab), _translate("iosRouterConfigPageWidget", "Usage"))

View File

@@ -92,7 +92,7 @@ class IOUDeviceWizard(VMWithImagesWizard, Ui_IOUDeviceWizard):
if self.page(page_id) == self.uiNameWizardPage:
if not self.uiIOUImageToolButton.isEnabled():
QtWidgets.QMessageBox.warning(self, "IOU image", "You have chosen to use a remote server, please provide the path to an IOU image located on this server!")
self.loadImagesList("/iou/images")
self.loadImagesList("iou")
def getSettings(self):
"""

View File

@@ -119,23 +119,6 @@ class IOUDevice(Node):
from .pages.iou_device_configuration_page import iouDeviceConfigurationPage
return iouDeviceConfigurationPage
@staticmethod
def validateHostname(hostname):
"""
Checks if the hostname is valid.
:param hostname: hostname to check
:returns: boolean
"""
# 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 (ARPANET rules).
if re.search(r"""^(?!-|[0-9])[a-zA-Z0-9-]{1,63}(?<!-)$""", hostname):
return True
return False
@staticmethod
def defaultSymbol():
"""

View File

@@ -244,8 +244,6 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
name = self.uiNameLineEdit.text()
if not name:
QtWidgets.QMessageBox.critical(self, "Name", "IOU device name cannot be empty!")
elif node and not node.validateHostname(name):
QtWidgets.QMessageBox.critical(self, "Name", "Invalid name detected for IOU device: {}".format(name))
else:
settings["name"] = name

View File

@@ -88,7 +88,11 @@ class IOUDevicePreferencesPage(QtWidgets.QWidget, Ui_IOUDevicePreferencesPageWid
QtWidgets.QTreeWidgetItem(section_item, ["Template ID:", iou_device.get("template_id", "none")])
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", iou_device["default_name_format"]])
try:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(iou_device["compute_id"]).name()])
compute_id = iou_device.get("compute_id")
if compute_id:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(compute_id).name()])
else:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", "Dynamically allocated by the controller"])
except KeyError:
# Compute doesn't exists
pass

View File

@@ -32,7 +32,7 @@ IOU_DEVICE_SETTINGS = {
"default_name_format": "IOU{0}",
"usage": "",
"path": "",
"symbol": ":/symbols/multilayer_switch.svg",
"symbol": "multilayer_switch",
"category": Node.routers,
"startup_config": "",
"private_config": "",

View File

@@ -20,7 +20,6 @@ QEMU module implementation.
"""
from gns3.local_config import LocalConfig
from gns3.local_server_config import LocalServerConfig
from gns3.controller import Controller
from gns3.template_manager import TemplateManager
from gns3.template import Template
@@ -74,33 +73,11 @@ class Qemu(Module):
# save the settings
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
server_settings = {"enable_hardware_acceleration": self._settings["enable_hardware_acceleration"],
"require_hardware_acceleration": self._settings["require_hardware_acceleration"]}
LocalServerConfig.instance().saveSettings(self.__class__.__name__, server_settings)
def getQemuBinariesFromServer(self, compute_id, callback, archs=None):
"""
Gets the QEMU binaries list from a server.
:param compute_id: server to send the request to
:param callback: callback for the reply from the server
:param archs: A list of architectures. Only binaries matching the specified architectures are returned.
"""
request_body = None
if archs is not None:
request_body = {"archs": archs}
Controller.instance().getCompute("/qemu/binaries", compute_id, callback, body=request_body)
def getQemuImgBinariesFromServer(self, compute_id, callback):
"""
Gets the QEMU-img binaries list from a server.
:param server: server to send the request to
:param callback: callback for the reply from the server
"""
Controller.instance().getCompute("/qemu/img-binaries", compute_id, callback)
# FIXME: handle server side config
# server_settings = {"enable_hardware_acceleration": self._settings["enable_hardware_acceleration"],
# "require_hardware_acceleration": self._settings["require_hardware_acceleration"]}
# LocalServerConfig.instance().saveSettings(self.__class__.__name__, server_settings)
def getQemuCapabilitiesFromServer(self, compute_id, callback):
"""
@@ -112,27 +89,44 @@ class Qemu(Module):
Controller.instance().getCompute("/qemu/capabilities", compute_id, callback)
def createDiskImage(self, compute_id, callback, options):
@staticmethod
def getQemuPlatforms():
"""
Create a disk image on the remote server
Returns all Qemu platforms
:param server: server to send the request to
:param callback: callback for the reply from the server
:param options: Options for the image creation
:return: list of Qemu platforms
"""
Controller.instance().postCompute("/qemu/img", compute_id, callback, body=options)
def updateDiskImage(self, compute_id, callback, options):
"""
Update a disk image on the remote server
:param server: server to send the request to
:param callback: callback for the reply from the server
:param options: Options for the image update
"""
Controller.instance().putCompute("/qemu/img", compute_id, callback, body=options)
return [
"aarch64",
"alpha",
"arm",
"cris",
"i386",
"lm32",
"m68k",
"microblaze",
"microblazeel",
"mips",
"mips64",
"mips64el",
"mipsel",
"moxie",
"or32",
"ppc",
"ppc64",
"ppcemb",
"s390x",
"sh4",
"sh4eb",
"sparc",
"sparc64",
"tricore",
"unicore32",
"x86_64",
"xtensa",
"xtensaeb"
]
@staticmethod
def getNodeClass(node_type, platform=None):

View File

@@ -16,13 +16,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Wizard for QEMU images.
Wizard for QEMU disk images.
"""
import os
from gns3.qt import QtCore, QtGui, QtWidgets, QFileDialog
from .. import Qemu
from gns3.qt import QtCore, QtGui, QtWidgets
from ..ui.qemu_image_wizard_ui import Ui_QemuImageWizard
@@ -32,19 +29,18 @@ class QemuImageWizard(QtWidgets.QWizard, Ui_QemuImageWizard):
Wizard to create a Qemu VM.
:param parent: parent widget
:param server: Server where the image should be created
:param node: node where the image should be created
:param filename: Default filename of image.
:param folder: Default folder for the image. If absent, defaults to Qemu's images folder.
:param size: Default size (in MiB) for the image.
"""
def __init__(self, parent, server, filename="disk", folder=None, size=30000):
def __init__(self, parent, node, filename="disk", size=30000):
super().__init__(parent)
self._server = server
self._node = node
self.setupUi(self)
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/icons/qemu.svg"))
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/symbols/qemu_guest.svg"))
# Initialize "constants"
self._mappings = {
@@ -57,45 +53,19 @@ class QemuImageWizard(QtWidgets.QWizard, Ui_QemuImageWizard):
}
# isComplete() overrides
self.uiSizeAndLocationWizardPage.isComplete = self._uiSizeAndLocationWizardPage_isComplete
self.uiBinaryWizardPage.isComplete = self._uiBinaryWizardPage_isComplete
self.uiNameAndSizeWizardPage.isComplete = self._uiNameAndSizeWizardPage_isComplete
self.uiFormatWizardPage.isComplete = self._uiFormatWizardPage_isComplete
# Signal connections
self.uiFormatRadios.buttonClicked.connect(self._formatChangedSlot)
self.uiLocationLineEdit.textChanged.connect(self._locationChangedSlot)
self.uiLocationBrowseToolButton.clicked.connect(self._browserSlot)
self.uiDiskFilenameLineEdit.textChanged.connect(self._filenameChangedSlot)
# Finish setup
self.page(self.pageIds()[-1]).validatePage = self._createDisk
# Default values
Qemu.instance().getQemuImgBinariesFromServer(self._server,
self._getQemuImgBinariesFromServerCallback)
self.uiLocationLineEdit.setText(filename)
self.uiDiskFilenameLineEdit.setText(filename)
self.uiSizeSpinBox.setValue(size)
self._formatChangedSlot(self.uiFormatQcow2Radio)
def _getQemuImgBinariesFromServerCallback(self, result, error=False, **kwargs):
"""
Callback for getQemuImgBinariesFromServer.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
QtWidgets.QMessageBox.critical(self, "Qemu-img binaries", "{}".format(result["message"]))
else:
self.uiBinaryComboBox.clear()
for qemu in result:
if qemu["version"]:
self.uiBinaryComboBox.addItem(
"{path} (v{version})".format(path=qemu["path"], version=qemu["version"]), qemu["path"]
)
else:
self.uiBinaryComboBox.addItem("{path}".format(path=qemu["path"]), qemu["path"])
self.uiBinaryWizardPage.completeChanged.emit()
def _getCreateDiskServerCallback(self, result, error=False, **kwargs):
"""
:param result: server response
@@ -105,45 +75,34 @@ class QemuImageWizard(QtWidgets.QWizard, Ui_QemuImageWizard):
if error:
QtWidgets.QMessageBox.critical(self, "Create disk", "{}".format(result["message"]))
def _uiSizeAndLocationWizardPage_isComplete(self):
return not "" == self.uiLocationLineEdit.text()
def _uiNameAndSizeWizardPage_isComplete(self):
def _uiBinaryWizardPage_isComplete(self):
return self.uiFormatRadios.checkedButton() is not None and self.uiBinaryComboBox.currentData() is not None
return not "" == self.uiDiskFilenameLineEdit.text()
def _locationChangedSlot(self, new_value):
self.uiSizeAndLocationWizardPage.completeChanged.emit()
def _uiFormatWizardPage_isComplete(self):
def _browserSlot(self):
path, name_filter = QFileDialog.getSaveFileName(
self,
'Image location',
self.uiLocationLineEdit.text(),
'{0} files (*{1});;All files (*)'.format(
self.uiFormatRadios.checkedButton().text(),
self._mappings[self.uiFormatRadios.checkedButton()][1]
),
options=QFileDialog.DontConfirmOverwrite
)
if path:
self.uiLocationLineEdit.setText(path)
return self.uiFormatRadios.checkedButton() is not None
def _filenameChangedSlot(self, new_value):
self.uiNameAndSizeWizardPage.completeChanged.emit()
def _formatChangedSlot(self, new_format):
dir, filename = os.path.split(self.uiLocationLineEdit.text())
filename = self.uiDiskFilenameLineEdit.text().strip()
try:
filename = filename[:filename.rindex('.')] + self._mappings[new_format][1]
except ValueError:
# The file has no extension; Just give it one
filename = filename + self._mappings[new_format][1]
self.uiLocationLineEdit.setText(os.path.join(dir, filename))
self.uiBinaryWizardPage.completeChanged.emit()
self.uiDiskFilenameLineEdit.setText(filename)
self.uiFormatWizardPage.completeChanged.emit()
def _createDisk(self):
selected_format = self.uiFormatRadios.checkedButton()
selected_format = self.uiFormatRadios.checkedButton()
disk_image_filename = self.uiDiskFilenameLineEdit.text().strip()
options = {}
options["path"] = self.uiLocationLineEdit.text()
options["qemu_img"] = self.uiBinaryComboBox.currentData()
options["format"] = self._mappings[selected_format][0]
options["size"] = self.uiSizeSpinBox.value()
@@ -214,11 +173,12 @@ class QemuImageWizard(QtWidgets.QWizard, Ui_QemuImageWizard):
}
options['subformat'] = two + size_mode_mappings[size_mode]
Qemu.instance().createDiskImage(self._server, self._getCreateDiskServerCallback, options)
self._node.createDiskImage(disk_image_filename, options, self._getCreateDiskServerCallback)
return True
def nextId(self):
if self.page(self.currentId()) == self.uiBinaryWizardPage:
if self.page(self.currentId()) == self.uiFormatWizardPage:
current_format = self.uiFormatRadios.checkedButton()
if not current_format:
return self.currentId()

View File

@@ -43,7 +43,7 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
def __init__(self, qemu_vms, parent):
super().__init__(qemu_vms, parent)
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/icons/qemu.svg"))
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/symbols/qemu_guest.svg"))
# Mandatory fields
self.uiNameWizardPage.registerField("vm_name*", self.uiNameLineEdit)
@@ -55,31 +55,6 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
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):
"""
Validates the server.
"""
if super().validateCurrentPage() is False:
return False
if self.currentPage() == self.uiNameWizardPage:
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():
QtWidgets.QMessageBox.critical(self, "QEMU binaries", "Sorry, no QEMU binary has been found. Please make sure QEMU is installed before continuing")
return False
qemu_path = self.uiQemuListComboBox.itemData(self.uiQemuListComboBox.currentIndex())
if sys.platform.startswith("darwin") and "GNS3.app" in qemu_path:
QtWidgets.QMessageBox.warning(self, "Qemu binaries", "This version of qemu is obsolete and provided only for compatibility with old GNS3 versions.\nPlease use Qemu in the GNS3 VM for full Qemu support.")
return True
def initializePage(self, page_id):
super().initializePage(page_id)
@@ -89,53 +64,13 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
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) in [self.uiDiskWizardPage, self.uiInitrdKernelImageWizardPage]:
self.loadImagesList("/qemu/images")
elif self.page(page_id) == self.uiBinaryMemoryWizardPage:
try:
Qemu.instance().getQemuBinariesFromServer(self._compute_id, self._getQemuBinariesFromServerCallback)
except ModuleError as e:
QtWidgets.QMessageBox.critical(self, "Qemu binaries", "Error while getting the QEMU binaries: {}".format(e))
def _getQemuBinariesFromServerCallback(self, result, error=False, **kwargs):
"""
Callback for getQemuBinariesFromServer.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
QtWidgets.QMessageBox.critical(self, "Qemu binaries", "{}".format(result["message"]))
else:
self.uiQemuListComboBox.clear()
for qemu in result:
if qemu["version"]:
self.uiQemuListComboBox.addItem("{path} (v{version})".format(path=qemu["path"], version=qemu["version"]), qemu["path"])
else:
self.uiQemuListComboBox.addItem("{path}".format(path=qemu["path"]), qemu["path"])
is_64bit = sys.maxsize > 2 ** 32
if ComputeManager.instance().localPlatform().startswith("win") and self.uiLocalRadioButton.isChecked():
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:
# default is qemu-system-i386w.exe on Windows 32-bit with a remote server
search_string = "i386w.exe"
elif ComputeManager.instance().localPlatform().startswith("darwin") and hasattr(sys, "frozen") and self.uiLocalRadioButton.isChecked():
search_string = "GNS3.app/Contents/MacOS/qemu/bin/qemu-system-x86_64"
elif is_64bit:
# default is qemu-system-x86_64 on other 64-bit platforms
search_string = "x86_64"
else:
# default is qemu-system-i386 on other platforms
search_string = "i386"
index = self.uiQemuListComboBox.findData(search_string, flags=QtCore.Qt.MatchEndsWith)
self.loadImagesList("qemu")
elif self.page(page_id) == self.uiPlatformMemoryWizardPage:
platforms = Qemu.getQemuPlatforms()
self.uiQemuPlatformComboBox.addItems(platforms)
index = self.uiQemuPlatformComboBox.findText("x86_64", flags=QtCore.Qt.MatchEndsWith)
if index != -1:
self.uiQemuListComboBox.setCurrentIndex(index)
self.uiQemuPlatformComboBox.setCurrentIndex(index)
def getSettings(self):
"""
@@ -145,11 +80,11 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
"""
console_type = self.uiQemuConsoleTypeComboBox.itemText(self.uiQemuConsoleTypeComboBox.currentIndex())
qemu_path = self.uiQemuListComboBox.itemData(self.uiQemuListComboBox.currentIndex())
qemu_platform = self.uiQemuPlatformComboBox.itemText(self.uiQemuPlatformComboBox.currentIndex())
settings = {
"name": self.uiNameLineEdit.text(),
"ram": self.uiRamSpinBox.value(),
"qemu_path": qemu_path,
"platform": qemu_platform,
"compute_id": self._compute_id,
"category": Node.end_devices,
"console_type": console_type
@@ -158,25 +93,8 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
if self.uiHdaDiskImageLineEdit.text().strip():
settings["hda_disk_image"] = self.uiHdaDiskImageLineEdit.text().strip()
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
@@ -188,8 +106,6 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
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

View File

@@ -51,6 +51,8 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self._settings = None
self._custom_adapters = []
self.uiQemuPlatformComboBox.addItems(Qemu.getQemuPlatforms())
self.uiBootPriorityComboBox.addItem("HDD", "c")
self.uiBootPriorityComboBox.addItem("CD/DVD-ROM", "d")
self.uiBootPriorityComboBox.addItem("Network", "n")
@@ -88,7 +90,6 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self.uiInitrdToolButton.clicked.connect(self._initrdBrowserSlot)
self.uiKernelImageToolButton.clicked.connect(self._kernelImageBrowserSlot)
self.uiActivateCPUThrottlingCheckBox.stateChanged.connect(self._cpuThrottlingChangedSlot)
self.uiLegacyNetworkingCheckBox.stateChanged.connect(self._legacyNetworkingChangedSlot)
self.uiCustomAdaptersConfigurationPushButton.clicked.connect(self._customAdaptersConfigurationSlot)
self.uiCreateConfigDiskCheckBox.stateChanged.connect(self._createConfigDiskChangedSlot)
@@ -103,7 +104,6 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
# Supported NIC models: e1000, e1000-82544gc, e1000-82545em, e1000e, i82550, i82551, i82557a, i82557b, i82557c, i82558a
# i82558b, i82559a, i82559b, i82559c, i82559er, i82562, i82801, ne2k_pci, pcnet, rocker, rtl8139, virtio-net-pci, vmxnet3
# This list can be retrieved using "qemu-system-x86_64 -nic model=?" or "qemu-system-x86_64 -device help"
self._legacy_devices = ("e1000", "i82551", "i82557b", "i82559er", "ne2k_pci", "pcnet", "rtl8139", "virtio")
self._qemu_network_devices = OrderedDict([("e1000", "Intel Gigabit Ethernet"),
("e1000-82544gc", "Intel 82544GC Gigabit Ethernet"),
("e1000-82545em", "Intel 82545EM Gigabit Ethernet"),
@@ -125,7 +125,6 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
("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"),
("vmxnet3", "VMWare Paravirtualized Ethernet v3")])
@@ -144,20 +143,14 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self.uiSymbolLineEdit.setText(new_symbol_path)
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(new_symbol_path))
def _refreshQemuNetworkDevices(self, legacy_networking=False):
def _refreshQemuNetworkDevices(self,):
"""
Refreshes the Qemu network device list.
:param legacy_networking: True if legacy networking is enabled.
"""
self.uiAdapterTypesComboBox.clear()
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
@@ -244,24 +237,64 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self.uiCdromImageLineEdit.setText(path)
def _hdaDiskImageCreateSlot(self):
create_dialog = QemuImageWizard(self, self._compute_id, self.uiNameLineEdit.text() + '-hda')
if QtWidgets.QDialog.Accepted == create_dialog.exec_():
self.uiHdaDiskImageLineEdit.setText(create_dialog.uiLocationLineEdit.text())
if self._node:
create_dialog = QemuImageWizard(self, self._node, self.uiNameLineEdit.text() + '-hda')
if QtWidgets.QDialog.Accepted == create_dialog.exec_():
self.uiHdaDiskImageLineEdit.setText(create_dialog.uiDiskFilenameLineEdit.text())
def _hdbDiskImageCreateSlot(self):
create_dialog = QemuImageWizard(self, self._compute_id, self.uiNameLineEdit.text() + '-hdb')
if QtWidgets.QDialog.Accepted == create_dialog.exec_():
self.uiHdbDiskImageLineEdit.setText(create_dialog.uiLocationLineEdit.text())
if self._node:
create_dialog = QemuImageWizard(self, self._node, self.uiNameLineEdit.text() + '-hdb')
if QtWidgets.QDialog.Accepted == create_dialog.exec_():
self.uiHdbDiskImageLineEdit.setText(create_dialog.uiDiskFilenameLineEdit.text())
def _hdcDiskImageCreateSlot(self):
create_dialog = QemuImageWizard(self, self._compute_id, self.uiNameLineEdit.text() + '-hdc')
if QtWidgets.QDialog.Accepted == create_dialog.exec_():
self.uiHdcDiskImageLineEdit.setText(create_dialog.uiLocationLineEdit.text())
if self._node:
create_dialog = QemuImageWizard(self, self._node, self.uiNameLineEdit.text() + '-hdc')
if QtWidgets.QDialog.Accepted == create_dialog.exec_():
self.uiHdcDiskImageLineEdit.setText(create_dialog.uiDiskFilenameLineEdit.text())
def _hddDiskImageCreateSlot(self):
create_dialog = QemuImageWizard(self, self._compute_id, self.uiNameLineEdit.text() + '-hdd')
if QtWidgets.QDialog.Accepted == create_dialog.exec_():
self.uiHddDiskImageLineEdit.setText(create_dialog.uiLocationLineEdit.text())
if self._node:
create_dialog = QemuImageWizard(self, self._node, self.uiNameLineEdit.text() + '-hdd')
if QtWidgets.QDialog.Accepted == create_dialog.exec_():
self.uiHddDiskImageLineEdit.setText(create_dialog.uiDiskFilenameLineEdit.text())
def _hdaDiskImageResizeSlot(self):
disk_image_filename = self.uiHdaDiskImageLineEdit.text().strip()
if disk_image_filename:
size, ok = QtWidgets.QInputDialog.getInt(self, "HDA disk size", "Increase hda disk size in MB:", 10000, 1, 1000000000, 1000)
if ok and self._node:
self._node.resizeDiskImage(disk_image_filename, size, self._resizeDiskImageCallback)
def _hdbDiskImageResizeSlot(self):
disk_image_filename = self.uiHdbDiskImageLineEdit.text()
if disk_image_filename:
size, ok = QtWidgets.QInputDialog.getInt(self, "HDB disk size", "Increase hdb disk size in MB:", 10000, 1, 1000000000, 1000)
if ok and self._node:
self._node.resizeDiskImage(disk_image_filename, size, self._resizeDiskImageCallback)
def _hdcDiskImageResizeSlot(self):
disk_image_filename = self.uiHdcDiskImageLineEdit.text()
if disk_image_filename:
size, ok = QtWidgets.QInputDialog.getInt(self, "HDC disk size", "Increase hdc disk size in MB:", 10000, 1, 1000000000, 1000)
if ok and self._node:
self._node.resizeDiskImage(disk_image_filename, size, self._resizeDiskImageCallback)
def _hddDiskImageResizeSlot(self):
disk_image_filename = self.uiHddDiskImageLineEdit.text()
if disk_image_filename:
size, ok = QtWidgets.QInputDialog.getInt(self, "HDD disk size", "Increase hdd disk size in MB:", 10000, 1, 1000000000, 1000)
if ok and self._node:
self._node.resizeDiskImage(disk_image_filename, size, self._resizeDiskImageCallback)
def _resizeDiskImageCallback(self, result, error=False, **kwargs):
"""
@@ -276,25 +309,6 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
else:
QtWidgets.QMessageBox.information(self, "Disk image", "The disk has been resized")
def _hdaDiskImageResizeSlot(self):
size, ok = QtWidgets.QInputDialog.getInt(self, "HDA disk size", "Increase hda disk size in MB:", 10000, 1, 1000000000, 1000)
if ok and self._node:
self._node.resizeDiskImage("hda", size, self._resizeDiskImageCallback)
def _hdbDiskImageResizeSlot(self):
size, ok = QtWidgets.QInputDialog.getInt(self, "HDB disk size", "Increase hdb disk size in MB:", 10000, 1, 1000000000, 1000)
if ok and self._node:
self._node.resizeDiskImage("hdb", size, self._resizeDiskImageCallback)
def _hdcDiskImageResizeSlot(self):
size, ok = QtWidgets.QInputDialog.getInt(self, "HDC disk size", "Increase hdc disk size in MB:", 10000, 1, 1000000000, 1000)
if ok and self._node:
self._node.resizeDiskImage("hdc", size, self._resizeDiskImageCallback)
def _hddDiskImageResizeSlot(self):
size, ok = QtWidgets.QInputDialog.getInt(self, "HDD disk size", "Increase hdd disk size in MB:", 10000, 1, 1000000000, 1000)
if ok and self._node:
self._node.resizeDiskImage("hdd", size, self._resizeDiskImageCallback)
def _initrdBrowserSlot(self):
"""
@@ -316,40 +330,6 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self.uiKernelImageLineEdit.clear()
self.uiKernelImageLineEdit.setText(path)
def _getQemuBinariesFromServerCallback(self, result, error=False, qemu_path=None, **kwargs):
"""
Callback for getQemuBinariesFromServer.
:param result: server response
:param error: indicates an error (boolean)
"""
if sip_is_deleted(self.uiQemuListComboBox) or sip_is_deleted(self):
return
if error:
QtWidgets.QMessageBox.critical(self, "Qemu binaries", "{}".format(result["message"]))
else:
self.uiQemuListComboBox.clear()
for qemu in result:
if qemu["version"]:
self.uiQemuListComboBox.addItem("{path} (v{version})".format(path=qemu["path"], version=qemu["version"]), qemu["path"])
else:
self.uiQemuListComboBox.addItem("{path}".format(path=qemu["path"]), qemu["path"])
if qemu_path and "/" not in qemu_path and "\\" not in qemu_path:
self.uiQemuListComboBox.addItem("{path}".format(path=qemu_path), qemu_path)
index = self.uiQemuListComboBox.findData("{path}".format(path=qemu_path))
if index != -1:
self.uiQemuListComboBox.setCurrentIndex(index)
else:
index = self.uiQemuListComboBox.findData("{path}".format(path=os.path.basename(qemu_path)), flags=QtCore.Qt.MatchEndsWith)
self.uiQemuListComboBox.setCurrentIndex(index)
if index == -1:
QtWidgets.QMessageBox.warning(self, "Qemu","Could not find '{}' in the Qemu binaries list, please select a new binary".format(qemu_path))
else:
QtWidgets.QMessageBox.warning(self, "Qemu","Could not find '{}' in the Qemu binaries list, an alternative path has been selected".format(qemu_path))
def _cpuThrottlingChangedSlot(self, state):
"""
@@ -361,16 +341,6 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
else:
self.uiCPUThrottlingSpinBox.setEnabled(False)
def _legacyNetworkingChangedSlot(self, state):
"""
Slot to enable or not legacy networking.
"""
if state:
self._refreshQemuNetworkDevices(legacy_networking=True)
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.
@@ -419,16 +389,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
QtWidgets.QMessageBox.critical(self, "Invalid format", "Invalid port name format")
return
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")
network_devices = self._qemu_network_devices.copy()
dialog = CustomAdaptersConfigurationDialog(ports, self._custom_adapters, default_adapter, network_devices, base_mac_address, parent=self)
dialog.show()
dialog.exec_()
@@ -450,12 +411,6 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self._compute_id = settings["compute_id"]
self._node = None
if self._compute_id is None:
QtWidgets.QMessageBox.warning(self, "Qemu", "Server {} is not running, cannot retrieve the QEMU binaries list".format(settings["compute_id"]))
else:
callback = qpartial(self._getQemuBinariesFromServerCallback, qemu_path=settings["qemu_path"])
Qemu.instance().getQemuBinariesFromServer(self._compute_id, callback)
if not group:
# set the device name
self.uiNameLineEdit.setText(settings["name"])
@@ -512,6 +467,11 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self.uiPortSegmentSizeSpinBox.setValue(settings["port_segment_size"])
self.uiFirstPortNameLineEdit.setText(settings["first_port_name"])
self.uiHdaDiskImageCreateToolButton.hide()
self.uiHdbDiskImageCreateToolButton.hide()
self.uiHdcDiskImageCreateToolButton.hide()
self.uiHddDiskImageCreateToolButton.hide()
self.uiHdaDiskImageResizeToolButton.hide()
self.uiHdbDiskImageResizeToolButton.hide()
self.uiHdcDiskImageResizeToolButton.hide()
@@ -532,6 +492,10 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self.uiFirstPortNameLabel.hide()
self.uiFirstPortNameLineEdit.hide()
index = self.uiQemuPlatformComboBox.findText(settings["platform"])
if index != -1:
self.uiQemuPlatformComboBox.setCurrentIndex(index)
index = self.uiBootPriorityComboBox.findData(settings["boot_priority"])
if index != -1:
self.uiBootPriorityComboBox.setCurrentIndex(index)
@@ -540,12 +504,14 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
if index != -1:
self.uiConsoleTypeComboBox.setCurrentIndex(index)
index = self.uiAuxTypeComboBox.findText(settings["aux_type"])
if index != -1:
self.uiAuxTypeComboBox.setCurrentIndex(index)
self.uiConsoleAutoStartCheckBox.setChecked(settings["console_auto_start"])
self.uiKernelCommandLineEdit.setText(settings["kernel_command_line"])
self.uiAdaptersSpinBox.setValue(settings["adapters"])
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
@@ -655,17 +621,11 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
settings["port_segment_size"] = port_segment_size
settings["first_port_name"] = first_port_name
if self.uiQemuListComboBox.currentIndex() != -1:
qemu_path = self.uiQemuListComboBox.itemData(self.uiQemuListComboBox.currentIndex())
settings["qemu_path"] = qemu_path
else:
QtWidgets.QMessageBox.critical(self, "Qemu binary", "Please select a Qemu binary")
if node:
raise ConfigurationError()
settings["platform"] = self.uiQemuPlatformComboBox.itemText(self.uiQemuPlatformComboBox.currentIndex())
settings["boot_priority"] = self.uiBootPriorityComboBox.itemData(self.uiBootPriorityComboBox.currentIndex())
settings["console_type"] = self.uiConsoleTypeComboBox.currentText().lower()
settings["console_auto_start"] = self.uiConsoleAutoStartCheckBox.isChecked()
settings["aux_type"] = self.uiAuxTypeComboBox.currentText().lower()
settings["adapter_type"] = self.uiAdapterTypesComboBox.itemData(self.uiAdapterTypesComboBox.currentIndex())
settings["kernel_command_line"] = self.uiKernelCommandLineEdit.text()
@@ -679,7 +639,6 @@ 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())

View File

@@ -84,18 +84,21 @@ class QemuVMPreferencesPage(QtWidgets.QWidget, Ui_QemuVMPreferencesPageWidget):
if qemu_vm["linked_clone"]:
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", qemu_vm["default_name_format"]])
try:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(qemu_vm["compute_id"]).name()])
compute_id = qemu_vm.get("compute_id")
if compute_id:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(compute_id).name()])
else:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", "Dynamically allocated by the controller"])
except KeyError:
pass
QtWidgets.QTreeWidgetItem(section_item, ["QEMU platform:", os.path.basename(qemu_vm["platform"])])
QtWidgets.QTreeWidgetItem(section_item, ["Console type:", qemu_vm["console_type"]])
QtWidgets.QTreeWidgetItem(section_item, ["Auto start console:", "{}".format(qemu_vm["console_auto_start"])])
QtWidgets.QTreeWidgetItem(section_item, ["Auxiliary console type:", qemu_vm["aux_type"]])
QtWidgets.QTreeWidgetItem(section_item, ["CPUs:", str(qemu_vm["cpus"])])
QtWidgets.QTreeWidgetItem(section_item, ["Memory:", "{} MB".format(qemu_vm["ram"])])
QtWidgets.QTreeWidgetItem(section_item, ["Linked base VM:", "{}".format(qemu_vm["linked_clone"])])
if qemu_vm["qemu_path"]:
QtWidgets.QTreeWidgetItem(section_item, ["QEMU binary:", os.path.basename(qemu_vm["qemu_path"])])
# fill out the Hard disks section
if qemu_vm["hda_disk_image"] or qemu_vm["hdb_disk_image"] or qemu_vm["hdc_disk_image"] or qemu_vm["hdd_disk_image"]:
section_item = self._createSectionItem("Hard disks")

View File

@@ -45,7 +45,6 @@ class QemuVM(Node):
self._linked_clone = True
qemu_vm_settings = {"usage": "",
"qemu_path": "",
"hda_disk_image": "",
"hdb_disk_image": "",
"hdc_disk_image": "",
@@ -68,11 +67,11 @@ class QemuVM(Node):
"cpus": QEMU_VM_SETTINGS["cpus"],
"console_type": QEMU_VM_SETTINGS["console_type"],
"console_auto_start": QEMU_VM_SETTINGS["console_auto_start"],
"aux_type": QEMU_VM_SETTINGS["aux_type"],
"adapters": QEMU_VM_SETTINGS["adapters"],
"custom_adapters": QEMU_VM_SETTINGS["custom_adapters"],
"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"],
"create_config_disk": QEMU_VM_SETTINGS["create_config_disk"],
"platform": QEMU_VM_SETTINGS["platform"],
@@ -90,16 +89,24 @@ class QemuVM(Node):
self.settings().update(qemu_vm_settings)
def resizeDiskImage(self, drive_name, size, callback):
def createDiskImage(self, disk_name, options, callback):
"""
Resize a disk image allocated to the VM.
Create a disk image attached to the VM.
:param callback: callback for the reply from the server
"""
params = {"drive_name": drive_name,
"extend": size}
self.post("/resize_disk", callback, body=params)
self.post(f"/qemu/disk_image/{disk_name}", callback, body=options)
def resizeDiskImage(self, disk_name, size, callback):
"""
Resize a disk image attached to the VM.
:param callback: callback for the reply from the server
"""
params = {"extend": size}
self.put(f"/qemu/disk_image/{disk_name}", callback, body=params)
def info(self):
"""
@@ -113,6 +120,7 @@ class QemuVM(Node):
Local ID is {id} and server ID is {node_id}
Number of processors is {cpus} and amount of memory is {ram}MB
Console is on port {console} and type is {console_type}
Auxiliary console is on port {aux} and type is {aux_type}
""".format(name=self.name(),
id=self.id(),
node_id=self._node_id,
@@ -122,7 +130,9 @@ class QemuVM(Node):
cpus=self._settings["cpus"],
ram=self._settings["ram"],
console=self._settings["console"],
console_type=self._settings["console_type"])
console_type=self._settings["console_type"],
aux=self._settings["aux"],
aux_type=self._settings["aux_type"])
port_info = ""
for port in self._ports:
@@ -155,6 +165,15 @@ class QemuVM(Node):
return None
def auxConsole(self):
"""
Returns the console port for this Docker VM instance.
:returns: port (integer)
"""
return self._settings["aux"]
def configPage(self):
"""
Returns the configuration page widget to be used by the node properties dialog.
@@ -165,18 +184,6 @@ 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

@@ -30,13 +30,12 @@ QEMU_VM_SETTINGS = {
"name": "",
"default_name_format": "{name}-{0}",
"usage": "",
"symbol": ":/symbols/qemu_guest.svg",
"symbol": "qemu_guest",
"category": Node.end_devices,
"port_name_format": "Ethernet{0}",
"port_segment_size": 0,
"first_port_name": "",
"custom_adapters": [],
"qemu_path": "",
"hda_disk_image": "",
"hdb_disk_image": "",
"hdc_disk_image": "",
@@ -50,16 +49,16 @@ QEMU_VM_SETTINGS = {
"boot_priority": "c",
"console_type": "telnet",
"console_auto_start": False,
"aux_type": "none",
"ram": 256,
"cpus": 1,
"adapters": 1,
"adapter_type": "e1000",
"mac_address": "",
"legacy_networking": False,
"replicate_network_connection_state": True,
"create_config_disk": False,
"on_close": "power_off",
"platform": "",
"platform": "x86_64",
"cpu_throttling": 0,
"process_priority": "normal",
"options": "",

View File

@@ -6,12 +6,12 @@
<rect>
<x>0</x>
<y>0</y>
<width>535</width>
<height>369</height>
<width>493</width>
<height>314</height>
</rect>
</property>
<property name="windowTitle">
<string>Qemu image creator</string>
<string>Qemu disk image creator</string>
</property>
<property name="modal">
<bool>true</bool>
@@ -19,39 +19,22 @@
<property name="wizardStyle">
<enum>QWizard::ModernStyle</enum>
</property>
<widget class="QWizardPage" name="uiBinaryWizardPage">
<widget class="QWizardPage" name="uiFormatWizardPage">
<property name="title">
<string>Binary and format</string>
<string>Disk image format</string>
</property>
<property name="subTitle">
<string>Please select a qemu-img binary, and the format for your new image.</string>
<string>Please select the disk image format</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="uiBinaryLabel">
<widget class="QLabel" name="uiFormatLabel">
<property name="text">
<string>Qemu-img binary:</string>
<string>Disk image format:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="uiBinaryComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiFormatLabel">
<property name="text">
<string>Image format:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QRadioButton" name="uiFormatQcow2Radio">
@@ -682,31 +665,20 @@ Free space will be zero filled.</string>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="uiSizeAndLocationWizardPage">
<widget class="QWizardPage" name="uiNameAndSizeWizardPage">
<property name="title">
<string>Size and location</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="uiLocationLabel">
<property name="text">
<string>File location:</string>
<string>Disk filename:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="uiLocationLineEdit"/>
</item>
<item>
<widget class="QToolButton" name="uiLocationBrowseToolButton">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
</layout>
<item row="0" column="2">
<widget class="QLineEdit" name="uiDiskFilenameLineEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiSizeLabel">
@@ -715,34 +687,30 @@ Free space will be zero filled.</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QSpinBox" name="uiSizeSpinBox">
<property name="buttonSymbols">
<enum>QAbstractSpinBox::UpDownArrows</enum>
</property>
<property name="suffix">
<string> MiB</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>2000000</number>
</property>
<property name="singleStep">
<number>1000</number>
</property>
<property name="value">
<number>30000</number>
</property>
<property name="showGroupSeparator" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
<item row="1" column="2">
<widget class="QSpinBox" name="uiSizeSpinBox">
<property name="buttonSymbols">
<enum>QAbstractSpinBox::UpDownArrows</enum>
</property>
<property name="showGroupSeparator" stdset="0">
<bool>true</bool>
</property>
<property name="suffix">
<string> MiB</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>2000000</number>
</property>
<property name="singleStep">
<number>1000</number>
</property>
<property name="value">
<number>30000</number>
</property>
</widget>
</item>
</layout>
</widget>
@@ -783,11 +751,11 @@ Free space will be zero filled.</string>
</connection>
</connections>
<buttongroups>
<buttongroup name="uiQcow2PreallocationRadios"/>
<buttongroup name="uiVmdkAdapterRadios"/>
<buttongroup name="uiFormatRadios"/>
<buttongroup name="uiVdiSizeModeRadios"/>
<buttongroup name="uiVmdkSizeModeRadios"/>
<buttongroup name="uiFormatRadios"/>
<buttongroup name="uiVhdSizeModeRadios"/>
<buttongroup name="uiQcow2PreallocationRadios"/>
</buttongroups>
</ui>

View File

@@ -1,42 +1,32 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/qemu/ui/qemu_image_wizard.ui'
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/qemu/ui/qemu_image_wizard.ui'
#
# Created by: PyQt5 UI code generator 5.4.2
# Created by: PyQt5 UI code generator 5.15.6
#
# WARNING! All changes made in this file will be lost!
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_QemuImageWizard(object):
def setupUi(self, QemuImageWizard):
QemuImageWizard.setObjectName("QemuImageWizard")
QemuImageWizard.resize(535, 369)
QemuImageWizard.resize(493, 314)
QemuImageWizard.setModal(True)
QemuImageWizard.setWizardStyle(QtWidgets.QWizard.ModernStyle)
self.uiBinaryWizardPage = QtWidgets.QWizardPage()
self.uiBinaryWizardPage.setObjectName("uiBinaryWizardPage")
self.gridLayout = QtWidgets.QGridLayout(self.uiBinaryWizardPage)
self.uiFormatWizardPage = QtWidgets.QWizardPage()
self.uiFormatWizardPage.setObjectName("uiFormatWizardPage")
self.gridLayout = QtWidgets.QGridLayout(self.uiFormatWizardPage)
self.gridLayout.setObjectName("gridLayout")
self.uiBinaryLabel = QtWidgets.QLabel(self.uiBinaryWizardPage)
self.uiBinaryLabel.setObjectName("uiBinaryLabel")
self.gridLayout.addWidget(self.uiBinaryLabel, 0, 0, 1, 1)
self.uiBinaryComboBox = QtWidgets.QComboBox(self.uiBinaryWizardPage)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiBinaryComboBox.sizePolicy().hasHeightForWidth())
self.uiBinaryComboBox.setSizePolicy(sizePolicy)
self.uiBinaryComboBox.setObjectName("uiBinaryComboBox")
self.gridLayout.addWidget(self.uiBinaryComboBox, 0, 1, 1, 1)
self.uiFormatLabel = QtWidgets.QLabel(self.uiBinaryWizardPage)
self.uiFormatLabel = QtWidgets.QLabel(self.uiFormatWizardPage)
self.uiFormatLabel.setObjectName("uiFormatLabel")
self.gridLayout.addWidget(self.uiFormatLabel, 1, 0, 1, 1)
self.gridLayout.addWidget(self.uiFormatLabel, 0, 0, 1, 1)
self.verticalLayout_3 = QtWidgets.QVBoxLayout()
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.uiFormatQcow2Radio = QtWidgets.QRadioButton(self.uiBinaryWizardPage)
self.uiFormatQcow2Radio = QtWidgets.QRadioButton(self.uiFormatWizardPage)
self.uiFormatQcow2Radio.setText("Qcow2")
self.uiFormatQcow2Radio.setChecked(True)
self.uiFormatQcow2Radio.setObjectName("uiFormatQcow2Radio")
@@ -44,32 +34,32 @@ class Ui_QemuImageWizard(object):
self.uiFormatRadios.setObjectName("uiFormatRadios")
self.uiFormatRadios.addButton(self.uiFormatQcow2Radio)
self.verticalLayout_3.addWidget(self.uiFormatQcow2Radio)
self.uiFormatQcowRadio = QtWidgets.QRadioButton(self.uiBinaryWizardPage)
self.uiFormatQcowRadio = QtWidgets.QRadioButton(self.uiFormatWizardPage)
self.uiFormatQcowRadio.setText("Qcow")
self.uiFormatQcowRadio.setObjectName("uiFormatQcowRadio")
self.uiFormatRadios.addButton(self.uiFormatQcowRadio)
self.verticalLayout_3.addWidget(self.uiFormatQcowRadio)
self.uiFormatVhdRadio = QtWidgets.QRadioButton(self.uiBinaryWizardPage)
self.uiFormatVhdRadio = QtWidgets.QRadioButton(self.uiFormatWizardPage)
self.uiFormatVhdRadio.setText("VHD")
self.uiFormatVhdRadio.setObjectName("uiFormatVhdRadio")
self.uiFormatRadios.addButton(self.uiFormatVhdRadio)
self.verticalLayout_3.addWidget(self.uiFormatVhdRadio)
self.uiFormatVdiRadio = QtWidgets.QRadioButton(self.uiBinaryWizardPage)
self.uiFormatVdiRadio = QtWidgets.QRadioButton(self.uiFormatWizardPage)
self.uiFormatVdiRadio.setText("VDI")
self.uiFormatVdiRadio.setObjectName("uiFormatVdiRadio")
self.uiFormatRadios.addButton(self.uiFormatVdiRadio)
self.verticalLayout_3.addWidget(self.uiFormatVdiRadio)
self.uiFormatVmdkRadio = QtWidgets.QRadioButton(self.uiBinaryWizardPage)
self.uiFormatVmdkRadio = QtWidgets.QRadioButton(self.uiFormatWizardPage)
self.uiFormatVmdkRadio.setText("VMDK")
self.uiFormatVmdkRadio.setObjectName("uiFormatVmdkRadio")
self.uiFormatRadios.addButton(self.uiFormatVmdkRadio)
self.verticalLayout_3.addWidget(self.uiFormatVmdkRadio)
self.uiFormatRawRadio = QtWidgets.QRadioButton(self.uiBinaryWizardPage)
self.uiFormatRawRadio = QtWidgets.QRadioButton(self.uiFormatWizardPage)
self.uiFormatRawRadio.setObjectName("uiFormatRawRadio")
self.uiFormatRadios.addButton(self.uiFormatRawRadio)
self.verticalLayout_3.addWidget(self.uiFormatRawRadio)
self.gridLayout.addLayout(self.verticalLayout_3, 1, 1, 1, 1)
QemuImageWizard.addPage(self.uiBinaryWizardPage)
self.gridLayout.addLayout(self.verticalLayout_3, 0, 1, 1, 1)
QemuImageWizard.addPage(self.uiFormatWizardPage)
self.uiQcow2OptionsWizardPage = QtWidgets.QWizardPage()
self.uiQcow2OptionsWizardPage.setObjectName("uiQcow2OptionsWizardPage")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.uiQcow2OptionsWizardPage)
@@ -271,59 +261,49 @@ class Ui_QemuImageWizard(object):
self.horizontalLayout_6.addItem(spacerItem5)
self.verticalLayout_6.addWidget(self.uiVmdkMiscGroupBox)
QemuImageWizard.addPage(self.uiVmdkOptionsWizardPage)
self.uiSizeAndLocationWizardPage = QtWidgets.QWizardPage()
self.uiSizeAndLocationWizardPage.setObjectName("uiSizeAndLocationWizardPage")
self.gridLayout_4 = QtWidgets.QGridLayout(self.uiSizeAndLocationWizardPage)
self.uiNameAndSizeWizardPage = QtWidgets.QWizardPage()
self.uiNameAndSizeWizardPage.setObjectName("uiNameAndSizeWizardPage")
self.gridLayout_4 = QtWidgets.QGridLayout(self.uiNameAndSizeWizardPage)
self.gridLayout_4.setObjectName("gridLayout_4")
self.uiLocationLabel = QtWidgets.QLabel(self.uiSizeAndLocationWizardPage)
self.uiLocationLabel = QtWidgets.QLabel(self.uiNameAndSizeWizardPage)
self.uiLocationLabel.setObjectName("uiLocationLabel")
self.gridLayout_4.addWidget(self.uiLocationLabel, 0, 0, 1, 1)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.uiLocationLineEdit = QtWidgets.QLineEdit(self.uiSizeAndLocationWizardPage)
self.uiLocationLineEdit.setObjectName("uiLocationLineEdit")
self.horizontalLayout.addWidget(self.uiLocationLineEdit)
self.uiLocationBrowseToolButton = QtWidgets.QToolButton(self.uiSizeAndLocationWizardPage)
self.uiLocationBrowseToolButton.setObjectName("uiLocationBrowseToolButton")
self.horizontalLayout.addWidget(self.uiLocationBrowseToolButton)
self.gridLayout_4.addLayout(self.horizontalLayout, 0, 1, 1, 1)
self.uiSizeLabel = QtWidgets.QLabel(self.uiSizeAndLocationWizardPage)
self.gridLayout_4.addWidget(self.uiLocationLabel, 0, 0, 1, 2)
self.uiDiskFilenameLineEdit = QtWidgets.QLineEdit(self.uiNameAndSizeWizardPage)
self.uiDiskFilenameLineEdit.setObjectName("uiDiskFilenameLineEdit")
self.gridLayout_4.addWidget(self.uiDiskFilenameLineEdit, 0, 2, 1, 1)
self.uiSizeLabel = QtWidgets.QLabel(self.uiNameAndSizeWizardPage)
self.uiSizeLabel.setObjectName("uiSizeLabel")
self.gridLayout_4.addWidget(self.uiSizeLabel, 1, 0, 1, 1)
self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
self.uiSizeSpinBox = QtWidgets.QSpinBox(self.uiSizeAndLocationWizardPage)
self.uiSizeSpinBox = QtWidgets.QSpinBox(self.uiNameAndSizeWizardPage)
self.uiSizeSpinBox.setButtonSymbols(QtWidgets.QAbstractSpinBox.UpDownArrows)
self.uiSizeSpinBox.setProperty("showGroupSeparator", True)
self.uiSizeSpinBox.setMinimum(0)
self.uiSizeSpinBox.setMaximum(2000000)
self.uiSizeSpinBox.setSingleStep(1000)
self.uiSizeSpinBox.setProperty("value", 30000)
self.uiSizeSpinBox.setProperty("showGroupSeparator", True)
self.uiSizeSpinBox.setObjectName("uiSizeSpinBox")
self.horizontalLayout_7.addWidget(self.uiSizeSpinBox)
self.gridLayout_4.addLayout(self.horizontalLayout_7, 1, 1, 1, 1)
QemuImageWizard.addPage(self.uiSizeAndLocationWizardPage)
self.gridLayout_4.addWidget(self.uiSizeSpinBox, 1, 2, 1, 1)
QemuImageWizard.addPage(self.uiNameAndSizeWizardPage)
self.retranslateUi(QemuImageWizard)
self.uiVmdkStreamOptimizedCheckBox.toggled['bool'].connect(self.uiVmdkFileSizeModeFlatRadio.setDisabled)
self.uiVmdkStreamOptimizedCheckBox.toggled['bool'].connect(self.uiVmdkSplit2gCheckBox.setDisabled)
self.uiVmdkStreamOptimizedCheckBox.toggled['bool'].connect(self.uiVmdkFileSizeModeFlatRadio.setDisabled) # type: ignore
self.uiVmdkStreamOptimizedCheckBox.toggled['bool'].connect(self.uiVmdkSplit2gCheckBox.setDisabled) # type: ignore
QtCore.QMetaObject.connectSlotsByName(QemuImageWizard)
def retranslateUi(self, QemuImageWizard):
_translate = QtCore.QCoreApplication.translate
QemuImageWizard.setWindowTitle(_translate("QemuImageWizard", "Qemu image creator"))
self.uiBinaryWizardPage.setTitle(_translate("QemuImageWizard", "Binary and format"))
self.uiBinaryWizardPage.setSubTitle(_translate("QemuImageWizard", "Please select a qemu-img binary, and the format for your new image."))
self.uiBinaryLabel.setText(_translate("QemuImageWizard", "Qemu-img binary:"))
self.uiFormatLabel.setText(_translate("QemuImageWizard", "Image format:"))
QemuImageWizard.setWindowTitle(_translate("QemuImageWizard", "Qemu disk image creator"))
self.uiFormatWizardPage.setTitle(_translate("QemuImageWizard", "Disk image format"))
self.uiFormatWizardPage.setSubTitle(_translate("QemuImageWizard", "Please select the disk image format"))
self.uiFormatLabel.setText(_translate("QemuImageWizard", "Disk image format:"))
self.uiFormatQcow2Radio.setToolTip(_translate("QemuImageWizard", "Qcow2 is the current Qemu format, supporting many special features."))
self.uiFormatQcowRadio.setToolTip(_translate("QemuImageWizard", "Qcow is a legacy Qemu format that is also supported by VirtualBox."))
self.uiFormatVhdRadio.setToolTip(_translate("QemuImageWizard", "VHD is the format used by Microsoft VirtualPC, and is also supported by Qemu and VirtualBox.\n"
"On Windows 7 and above, it can be mounted on the host PC."))
"On Windows 7 and above, it can be mounted on the host PC."))
self.uiFormatVdiRadio.setToolTip(_translate("QemuImageWizard", "VDI is the native format of VirtualBox"))
self.uiFormatVmdkRadio.setToolTip(_translate("QemuImageWizard", "VMDK is the native format for VMware and is also supported by Qemu and VirtualBox."))
self.uiFormatRawRadio.setToolTip(_translate("QemuImageWizard", "Raw image files represent the actual data on the image, with zero special features.\n"
"It can easily be converted to various other formats by various utilities, making it the most portable format."))
"It can easily be converted to various other formats by various utilities, making it the most portable format."))
self.uiFormatRawRadio.setText(_translate("QemuImageWizard", "Raw"))
self.uiQcow2OptionsWizardPage.setTitle(_translate("QemuImageWizard", "Qcow2 options"))
self.uiSizeOptionsGroupBox.setTitle(_translate("QemuImageWizard", "Size options"))
@@ -331,12 +311,12 @@ class Ui_QemuImageWizard(object):
self.uiQcow2PreallocationOffRadio.setToolTip(_translate("QemuImageWizard", "The file only takes as much space from the host as needed. The VM will still see the full capacity you specify."))
self.uiQcow2PreallocationOffRadio.setText(_translate("QemuImageWizard", "off"))
self.uiQcow2PreallocationMetadataRadio.setToolTip(_translate("QemuImageWizard", "Same as \"off\", but preallocates enough space to hold any potenial metadata for the HDD.\n"
"This improves performance when the image file needs to grow."))
"This improves performance when the image file needs to grow."))
self.uiQcow2PreallocationMetadataRadio.setText(_translate("QemuImageWizard", "metadata"))
self.uiQcow2PreallocationFallocRadio.setToolTip(_translate("QemuImageWizard", "Same as \"full\", but uses C\'s posix_fallocate() if available on the host, instead of zero filling the file."))
self.uiQcow2PreallocationFallocRadio.setText(_translate("QemuImageWizard", "falloc"))
self.uiQcow2PreallocationFullRadio.setToolTip(_translate("QemuImageWizard", "The file will start off at the full size you specify.\n"
"Free space will be zero filled."))
"Free space will be zero filled."))
self.uiQcow2PreallocationFullRadio.setText(_translate("QemuImageWizard", "full"))
self.uiClusterSizeLabel.setText(_translate("QemuImageWizard", "Cluster size:"))
self.uiQcow2ClusterSizeComboBox.setItemText(0, _translate("QemuImageWizard", "<default>"))
@@ -391,8 +371,7 @@ class Ui_QemuImageWizard(object):
self.uiVmdkStreamOptimizedCheckBox.setText(_translate("QemuImageWizard", "Stream optimized"))
self.uiVmdkSplit2gCheckBox.setText(_translate("QemuImageWizard", "Split every 2 GiB"))
self.uiVmdkZeroedGrainCheckBox.setText(_translate("QemuImageWizard", "Zeroed grain"))
self.uiSizeAndLocationWizardPage.setTitle(_translate("QemuImageWizard", "Size and location"))
self.uiLocationLabel.setText(_translate("QemuImageWizard", "File location:"))
self.uiLocationBrowseToolButton.setText(_translate("QemuImageWizard", "Browse"))
self.uiNameAndSizeWizardPage.setTitle(_translate("QemuImageWizard", "Size and location"))
self.uiLocationLabel.setText(_translate("QemuImageWizard", "Disk filename:"))
self.uiSizeLabel.setText(_translate("QemuImageWizard", "Disk size:"))
self.uiSizeSpinBox.setSuffix(_translate("QemuImageWizard", " MiB"))

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>941</width>
<height>939</height>
<width>478</width>
<height>530</height>
</rect>
</property>
<property name="windowTitle">
@@ -119,14 +119,14 @@
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="uiQemuListLabel">
<widget class="QLabel" name="uiQemuPlatformLabel">
<property name="text">
<string>Qemu binary:</string>
<string>Qemu platform:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QComboBox" name="uiQemuListComboBox">
<widget class="QComboBox" name="uiQemuPlatformComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
@@ -202,7 +202,7 @@
</item>
</layout>
</item>
<item row="10" column="1">
<item row="11" column="1">
<spacer name="spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -215,6 +215,42 @@
</property>
</spacer>
</item>
<item row="10" column="0">
<widget class="QLabel" name="uiAuxTypeLabel">
<property name="text">
<string>Auxiliary console type:</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QComboBox" name="uiAuxTypeComboBox">
<item>
<property name="text">
<string>telnet</string>
</property>
</item>
<item>
<property name="text">
<string>vnc</string>
</property>
</item>
<item>
<property name="text">
<string>spice</string>
</property>
</item>
<item>
<property name="text">
<string>spice+agent</string>
</property>
</item>
<item>
<property name="text">
<string>none</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="uiHddTab">
@@ -222,10 +258,67 @@
<string>HDD</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_11">
<item row="2" column="0">
<widget class="QGroupBox" name="uiHdcGroupBox">
<property name="title">
<string>HDC (Disk 2)</string>
</property>
<layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="0">
<widget class="QLabel" name="uiHdcDiskImageLabel">
<property name="text">
<string>Disk image:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QLineEdit" name="uiHdcDiskImageLineEdit"/>
</item>
<item>
<widget class="QToolButton" name="uiHdcDiskImageToolButton">
<property name="text">
<string>&amp;Browse...</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="uiHdcDiskImageCreateToolButton">
<property name="text">
<string>Create...</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="uiHdcDiskImageResizeToolButton">
<property name="text">
<string>Resize...</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiHdcDiskInterfaceLabel">
<property name="text">
<string>Disk interface:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="uiHdcDiskInterfaceComboBox"/>
</item>
</layout>
</widget>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="uiHdaGroupBox">
<property name="title">
<string>HDA (Primary Master)</string>
<string>HDA (Disk 0)</string>
</property>
<layout class="QGridLayout" name="gridLayout_6">
<item row="0" column="0">
@@ -282,7 +375,7 @@
<item row="1" column="0">
<widget class="QGroupBox" name="uiHdbgroupBox">
<property name="title">
<string>HDB (Primary Slave)</string>
<string>HDB (Disk 1)</string>
</property>
<layout class="QGridLayout" name="gridLayout_7">
<item row="0" column="0">
@@ -336,84 +429,37 @@
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="QGroupBox" name="uiHdcGroupBox">
<item row="3" column="0">
<widget class="QGroupBox" name="uiHddGroupBox">
<property name="title">
<string>HDC (Secondary Master)</string>
<string>HDD (Disk 3)</string>
</property>
<layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="0">
<widget class="QLabel" name="uiHdcDiskImageLabel">
<property name="text">
<string>Disk image:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QLineEdit" name="uiHdcDiskImageLineEdit"/>
</item>
<item>
<widget class="QToolButton" name="uiHdcDiskImageToolButton">
<property name="text">
<string>&amp;Browse...</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="uiHdcDiskImageCreateToolButton">
<property name="text">
<string>Create...</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="uiHdcDiskImageResizeToolButton">
<property name="text">
<string>Resize...</string>
</property>
</widget>
</item>
</layout>
<layout class="QGridLayout" name="gridLayout_9">
<item row="1" column="1">
<widget class="QComboBox" name="uiHddDiskInterfaceComboBox"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiHdcDiskInterfaceLabel">
<widget class="QLabel" name="uiHddDiskInterfaceLabel">
<property name="text">
<string>Disk interface:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="uiHdcDiskInterfaceComboBox"/>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<widget class="QGroupBox" name="uiHddGroupBox">
<property name="title">
<string>HDD (Secondary Slave)</string>
</property>
<layout class="QGridLayout" name="gridLayout_9">
<item row="0" column="0" colspan="2">
<item row="2" 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">
<item row="0" column="0">
<widget class="QLabel" name="uiHddDiskImageLabel">
<property name="text">
<string>Disk image:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QLineEdit" name="uiHddDiskImageLineEdit"/>
@@ -444,20 +490,10 @@
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="uiHddDiskInterfaceLabel">
<property name="text">
<string>Disk interface:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="uiHddDiskInterfaceComboBox"/>
</item>
</layout>
</widget>
</item>
<item row="4" column="0">
<item row="5" column="0">
<spacer name="spacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -542,50 +578,6 @@
<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">
<string>Adapters:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiFirstPortNameLabel">
<property name="text">
<string>First port name:</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QLineEdit" name="uiFirstPortNameLineEdit"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="uiPortNameFormatLabel">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;{0} - the port number, from 0 to the number of adapters-1.&lt;/p&gt;&lt;p&gt;{1} - the segment number, from 0 to the number of segments-1.&lt;/p&gt;&lt;p&gt;{port0} - named alias for {0}.&lt;/p&gt;&lt;p&gt;{port1} - the port number, from 1 to the number of adapters.&lt;/p&gt;&lt;p&gt;{segment0} - named alias for {1}.&lt;/p&gt;&lt;p&gt;{segment1} - the segment number, from 1 to the number of segments.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Name format:</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="uiPortNameFormatLineEdit">
<property name="text">
@@ -593,57 +585,6 @@
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="uiMacAddrLabel">
<property name="text">
<string>Base MAC:</string>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="QLineEdit" name="uiMacAddrLineEdit"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="uiAdapterTypesLabel">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="uiCustomAdaptersLabel">
<property name="text">
<string>Custom adapters:</string>
</property>
</widget>
</item>
<item row="6" column="1" colspan="2">
<widget class="QPushButton" name="uiCustomAdaptersConfigurationPushButton">
<property name="text">
<string>&amp;Configure custom adapters</string>
</property>
</widget>
</item>
<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="9" column="2">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>261</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="1" colspan="2">
<widget class="QComboBox" name="uiAdapterTypesComboBox">
<property name="sizePolicy">
@@ -654,6 +595,13 @@
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="uiAdapterTypesLabel">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QSpinBox" name="uiAdaptersSpinBox">
<property name="sizePolicy">
@@ -670,6 +618,36 @@
</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="8" column="2">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>261</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiFirstPortNameLabel">
<property name="text">
<string>First port name:</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QLineEdit" name="uiFirstPortNameLineEdit"/>
</item>
<item row="7" column="0" colspan="3">
<widget class="QCheckBox" name="uiReplicateNetworkConnectionStateCheckBox">
<property name="text">
@@ -677,6 +655,57 @@
</property>
</widget>
</item>
<item row="6" column="1" colspan="2">
<widget class="QPushButton" name="uiCustomAdaptersConfigurationPushButton">
<property name="text">
<string>&amp;Configure custom adapters</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="6" column="0">
<widget class="QLabel" name="uiCustomAdaptersLabel">
<property name="text">
<string>Custom adapters:</string>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="QLineEdit" name="uiMacAddrLineEdit"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="uiAdaptersLabel">
<property name="text">
<string>Adapters:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="uiPortNameFormatLabel">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;{0} - the port number, from 0 to the number of adapters-1.&lt;/p&gt;&lt;p&gt;{1} - the segment number, from 0 to the number of segments-1.&lt;/p&gt;&lt;p&gt;{port0} - named alias for {0}.&lt;/p&gt;&lt;p&gt;{port1} - the port number, from 1 to the number of adapters.&lt;/p&gt;&lt;p&gt;{segment0} - named alias for {1}.&lt;/p&gt;&lt;p&gt;{segment1} - the segment number, from 1 to the number of segments.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Name format:</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="uiMacAddrLabel">
<property name="text">
<string>Base MAC:</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="uiAdvancedSettingsTab">

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/qemu/ui/qemu_vm_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.13.2
# Created by: PyQt5 UI code generator 5.14.1
#
# WARNING! All changes made in this file will be lost!
@@ -13,7 +13,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_QemuVMConfigPageWidget(object):
def setupUi(self, QemuVMConfigPageWidget):
QemuVMConfigPageWidget.setObjectName("QemuVMConfigPageWidget")
QemuVMConfigPageWidget.resize(941, 939)
QemuVMConfigPageWidget.resize(478, 530)
self.verticalLayout = QtWidgets.QVBoxLayout(QemuVMConfigPageWidget)
self.verticalLayout.setObjectName("verticalLayout")
self.uiQemutabWidget = QtWidgets.QTabWidget(QemuVMConfigPageWidget)
@@ -70,17 +70,17 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiCPUSpinBox.setMaximum(255)
self.uiCPUSpinBox.setObjectName("uiCPUSpinBox")
self.gridLayout_4.addWidget(self.uiCPUSpinBox, 5, 1, 1, 1)
self.uiQemuListLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiQemuListLabel.setObjectName("uiQemuListLabel")
self.gridLayout_4.addWidget(self.uiQemuListLabel, 6, 0, 1, 1)
self.uiQemuListComboBox = QtWidgets.QComboBox(self.uiGeneralSettingsTab)
self.uiQemuPlatformLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiQemuPlatformLabel.setObjectName("uiQemuPlatformLabel")
self.gridLayout_4.addWidget(self.uiQemuPlatformLabel, 6, 0, 1, 1)
self.uiQemuPlatformComboBox = QtWidgets.QComboBox(self.uiGeneralSettingsTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiQemuListComboBox.sizePolicy().hasHeightForWidth())
self.uiQemuListComboBox.setSizePolicy(sizePolicy)
self.uiQemuListComboBox.setObjectName("uiQemuListComboBox")
self.gridLayout_4.addWidget(self.uiQemuListComboBox, 6, 1, 1, 1)
sizePolicy.setHeightForWidth(self.uiQemuPlatformComboBox.sizePolicy().hasHeightForWidth())
self.uiQemuPlatformComboBox.setSizePolicy(sizePolicy)
self.uiQemuPlatformComboBox.setObjectName("uiQemuPlatformComboBox")
self.gridLayout_4.addWidget(self.uiQemuPlatformComboBox, 6, 1, 1, 1)
self.uiBootPriorityLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiBootPriorityLabel.setObjectName("uiBootPriorityLabel")
self.gridLayout_4.addWidget(self.uiBootPriorityLabel, 7, 0, 1, 1)
@@ -111,12 +111,53 @@ class Ui_QemuVMConfigPageWidget(object):
self.horizontalLayout_2.addWidget(self.uiConsoleAutoStartCheckBox)
self.gridLayout_4.addLayout(self.horizontalLayout_2, 9, 1, 1, 1)
spacerItem = QtWidgets.QSpacerItem(263, 94, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_4.addItem(spacerItem, 10, 1, 1, 1)
self.gridLayout_4.addItem(spacerItem, 11, 1, 1, 1)
self.uiAuxTypeLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiAuxTypeLabel.setObjectName("uiAuxTypeLabel")
self.gridLayout_4.addWidget(self.uiAuxTypeLabel, 10, 0, 1, 1)
self.uiAuxTypeComboBox = QtWidgets.QComboBox(self.uiGeneralSettingsTab)
self.uiAuxTypeComboBox.setObjectName("uiAuxTypeComboBox")
self.uiAuxTypeComboBox.addItem("")
self.uiAuxTypeComboBox.addItem("")
self.uiAuxTypeComboBox.addItem("")
self.uiAuxTypeComboBox.addItem("")
self.uiAuxTypeComboBox.addItem("")
self.gridLayout_4.addWidget(self.uiAuxTypeComboBox, 10, 1, 1, 1)
self.uiQemutabWidget.addTab(self.uiGeneralSettingsTab, "")
self.uiHddTab = QtWidgets.QWidget()
self.uiHddTab.setObjectName("uiHddTab")
self.gridLayout_11 = QtWidgets.QGridLayout(self.uiHddTab)
self.gridLayout_11.setObjectName("gridLayout_11")
self.uiHdcGroupBox = QtWidgets.QGroupBox(self.uiHddTab)
self.uiHdcGroupBox.setObjectName("uiHdcGroupBox")
self.gridLayout_8 = QtWidgets.QGridLayout(self.uiHdcGroupBox)
self.gridLayout_8.setObjectName("gridLayout_8")
self.uiHdcDiskImageLabel = QtWidgets.QLabel(self.uiHdcGroupBox)
self.uiHdcDiskImageLabel.setObjectName("uiHdcDiskImageLabel")
self.gridLayout_8.addWidget(self.uiHdcDiskImageLabel, 0, 0, 1, 1)
self.horizontalLayout_9 = QtWidgets.QHBoxLayout()
self.horizontalLayout_9.setObjectName("horizontalLayout_9")
self.uiHdcDiskImageLineEdit = QtWidgets.QLineEdit(self.uiHdcGroupBox)
self.uiHdcDiskImageLineEdit.setObjectName("uiHdcDiskImageLineEdit")
self.horizontalLayout_9.addWidget(self.uiHdcDiskImageLineEdit)
self.uiHdcDiskImageToolButton = QtWidgets.QToolButton(self.uiHdcGroupBox)
self.uiHdcDiskImageToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiHdcDiskImageToolButton.setObjectName("uiHdcDiskImageToolButton")
self.horizontalLayout_9.addWidget(self.uiHdcDiskImageToolButton)
self.uiHdcDiskImageCreateToolButton = QtWidgets.QToolButton(self.uiHdcGroupBox)
self.uiHdcDiskImageCreateToolButton.setObjectName("uiHdcDiskImageCreateToolButton")
self.horizontalLayout_9.addWidget(self.uiHdcDiskImageCreateToolButton)
self.uiHdcDiskImageResizeToolButton = QtWidgets.QToolButton(self.uiHdcGroupBox)
self.uiHdcDiskImageResizeToolButton.setObjectName("uiHdcDiskImageResizeToolButton")
self.horizontalLayout_9.addWidget(self.uiHdcDiskImageResizeToolButton)
self.gridLayout_8.addLayout(self.horizontalLayout_9, 0, 1, 1, 1)
self.uiHdcDiskInterfaceLabel = QtWidgets.QLabel(self.uiHdcGroupBox)
self.uiHdcDiskInterfaceLabel.setObjectName("uiHdcDiskInterfaceLabel")
self.gridLayout_8.addWidget(self.uiHdcDiskInterfaceLabel, 1, 0, 1, 1)
self.uiHdcDiskInterfaceComboBox = QtWidgets.QComboBox(self.uiHdcGroupBox)
self.uiHdcDiskInterfaceComboBox.setObjectName("uiHdcDiskInterfaceComboBox")
self.gridLayout_8.addWidget(self.uiHdcDiskInterfaceComboBox, 1, 1, 1, 1)
self.gridLayout_11.addWidget(self.uiHdcGroupBox, 2, 0, 1, 1)
self.uiHdaGroupBox = QtWidgets.QGroupBox(self.uiHddTab)
self.uiHdaGroupBox.setObjectName("uiHdaGroupBox")
self.gridLayout_6 = QtWidgets.QGridLayout(self.uiHdaGroupBox)
@@ -177,46 +218,22 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiHdbDiskInterfaceComboBox.setObjectName("uiHdbDiskInterfaceComboBox")
self.gridLayout_7.addWidget(self.uiHdbDiskInterfaceComboBox, 1, 1, 1, 1)
self.gridLayout_11.addWidget(self.uiHdbgroupBox, 1, 0, 1, 1)
self.uiHdcGroupBox = QtWidgets.QGroupBox(self.uiHddTab)
self.uiHdcGroupBox.setObjectName("uiHdcGroupBox")
self.gridLayout_8 = QtWidgets.QGridLayout(self.uiHdcGroupBox)
self.gridLayout_8.setObjectName("gridLayout_8")
self.uiHdcDiskImageLabel = QtWidgets.QLabel(self.uiHdcGroupBox)
self.uiHdcDiskImageLabel.setObjectName("uiHdcDiskImageLabel")
self.gridLayout_8.addWidget(self.uiHdcDiskImageLabel, 0, 0, 1, 1)
self.horizontalLayout_9 = QtWidgets.QHBoxLayout()
self.horizontalLayout_9.setObjectName("horizontalLayout_9")
self.uiHdcDiskImageLineEdit = QtWidgets.QLineEdit(self.uiHdcGroupBox)
self.uiHdcDiskImageLineEdit.setObjectName("uiHdcDiskImageLineEdit")
self.horizontalLayout_9.addWidget(self.uiHdcDiskImageLineEdit)
self.uiHdcDiskImageToolButton = QtWidgets.QToolButton(self.uiHdcGroupBox)
self.uiHdcDiskImageToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiHdcDiskImageToolButton.setObjectName("uiHdcDiskImageToolButton")
self.horizontalLayout_9.addWidget(self.uiHdcDiskImageToolButton)
self.uiHdcDiskImageCreateToolButton = QtWidgets.QToolButton(self.uiHdcGroupBox)
self.uiHdcDiskImageCreateToolButton.setObjectName("uiHdcDiskImageCreateToolButton")
self.horizontalLayout_9.addWidget(self.uiHdcDiskImageCreateToolButton)
self.uiHdcDiskImageResizeToolButton = QtWidgets.QToolButton(self.uiHdcGroupBox)
self.uiHdcDiskImageResizeToolButton.setObjectName("uiHdcDiskImageResizeToolButton")
self.horizontalLayout_9.addWidget(self.uiHdcDiskImageResizeToolButton)
self.gridLayout_8.addLayout(self.horizontalLayout_9, 0, 1, 1, 1)
self.uiHdcDiskInterfaceLabel = QtWidgets.QLabel(self.uiHdcGroupBox)
self.uiHdcDiskInterfaceLabel.setObjectName("uiHdcDiskInterfaceLabel")
self.gridLayout_8.addWidget(self.uiHdcDiskInterfaceLabel, 1, 0, 1, 1)
self.uiHdcDiskInterfaceComboBox = QtWidgets.QComboBox(self.uiHdcGroupBox)
self.uiHdcDiskInterfaceComboBox.setObjectName("uiHdcDiskInterfaceComboBox")
self.gridLayout_8.addWidget(self.uiHdcDiskInterfaceComboBox, 1, 1, 1, 1)
self.gridLayout_11.addWidget(self.uiHdcGroupBox, 2, 0, 1, 1)
self.uiHddGroupBox = QtWidgets.QGroupBox(self.uiHddTab)
self.uiHddGroupBox.setObjectName("uiHddGroupBox")
self.gridLayout_9 = QtWidgets.QGridLayout(self.uiHddGroupBox)
self.gridLayout_9.setObjectName("gridLayout_9")
self.uiHddDiskInterfaceComboBox = QtWidgets.QComboBox(self.uiHddGroupBox)
self.uiHddDiskInterfaceComboBox.setObjectName("uiHddDiskInterfaceComboBox")
self.gridLayout_9.addWidget(self.uiHddDiskInterfaceComboBox, 1, 1, 1, 1)
self.uiHddDiskInterfaceLabel = QtWidgets.QLabel(self.uiHddGroupBox)
self.uiHddDiskInterfaceLabel.setObjectName("uiHddDiskInterfaceLabel")
self.gridLayout_9.addWidget(self.uiHddDiskInterfaceLabel, 1, 0, 1, 1)
self.uiCreateConfigDiskCheckBox = QtWidgets.QCheckBox(self.uiHddGroupBox)
self.uiCreateConfigDiskCheckBox.setObjectName("uiCreateConfigDiskCheckBox")
self.gridLayout_9.addWidget(self.uiCreateConfigDiskCheckBox, 0, 0, 1, 2)
self.gridLayout_9.addWidget(self.uiCreateConfigDiskCheckBox, 2, 0, 1, 2)
self.uiHddDiskImageLabel = QtWidgets.QLabel(self.uiHddGroupBox)
self.uiHddDiskImageLabel.setObjectName("uiHddDiskImageLabel")
self.gridLayout_9.addWidget(self.uiHddDiskImageLabel, 1, 0, 1, 1)
self.gridLayout_9.addWidget(self.uiHddDiskImageLabel, 0, 0, 1, 1)
self.horizontalLayout_10 = QtWidgets.QHBoxLayout()
self.horizontalLayout_10.setObjectName("horizontalLayout_10")
self.uiHddDiskImageLineEdit = QtWidgets.QLineEdit(self.uiHddGroupBox)
@@ -232,16 +249,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, 1, 1, 1, 1)
self.uiHddDiskInterfaceLabel = QtWidgets.QLabel(self.uiHddGroupBox)
self.uiHddDiskInterfaceLabel.setObjectName("uiHddDiskInterfaceLabel")
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)
self.gridLayout_9.addLayout(self.horizontalLayout_10, 0, 1, 1, 1)
self.gridLayout_11.addWidget(self.uiHddGroupBox, 3, 0, 1, 1)
spacerItem1 = QtWidgets.QSpacerItem(438, 257, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_11.addItem(spacerItem1, 4, 0, 1, 1)
self.gridLayout_11.addItem(spacerItem1, 5, 0, 1, 1)
self.uiQemutabWidget.addTab(self.uiHddTab, "")
self.uiCdromTab = QtWidgets.QWidget()
self.uiCdromTab.setObjectName("uiCdromTab")
@@ -273,50 +284,10 @@ 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)
self.uiFirstPortNameLabel = QtWidgets.QLabel(self.uiNetworkTab)
self.uiFirstPortNameLabel.setObjectName("uiFirstPortNameLabel")
self.gridLayout_5.addWidget(self.uiFirstPortNameLabel, 1, 0, 1, 1)
self.uiFirstPortNameLineEdit = QtWidgets.QLineEdit(self.uiNetworkTab)
self.uiFirstPortNameLineEdit.setObjectName("uiFirstPortNameLineEdit")
self.gridLayout_5.addWidget(self.uiFirstPortNameLineEdit, 1, 1, 1, 2)
self.uiPortNameFormatLabel = QtWidgets.QLabel(self.uiNetworkTab)
self.uiPortNameFormatLabel.setObjectName("uiPortNameFormatLabel")
self.gridLayout_5.addWidget(self.uiPortNameFormatLabel, 2, 0, 1, 1)
self.uiPortNameFormatLineEdit = QtWidgets.QLineEdit(self.uiNetworkTab)
self.uiPortNameFormatLineEdit.setText("")
self.uiPortNameFormatLineEdit.setObjectName("uiPortNameFormatLineEdit")
self.gridLayout_5.addWidget(self.uiPortNameFormatLineEdit, 2, 1, 1, 2)
self.uiMacAddrLabel = QtWidgets.QLabel(self.uiNetworkTab)
self.uiMacAddrLabel.setObjectName("uiMacAddrLabel")
self.gridLayout_5.addWidget(self.uiMacAddrLabel, 4, 0, 1, 1)
self.uiMacAddrLineEdit = QtWidgets.QLineEdit(self.uiNetworkTab)
self.uiMacAddrLineEdit.setObjectName("uiMacAddrLineEdit")
self.gridLayout_5.addWidget(self.uiMacAddrLineEdit, 4, 1, 1, 2)
self.uiAdapterTypesLabel = QtWidgets.QLabel(self.uiNetworkTab)
self.uiAdapterTypesLabel.setObjectName("uiAdapterTypesLabel")
self.gridLayout_5.addWidget(self.uiAdapterTypesLabel, 5, 0, 1, 1)
self.uiCustomAdaptersLabel = QtWidgets.QLabel(self.uiNetworkTab)
self.uiCustomAdaptersLabel.setObjectName("uiCustomAdaptersLabel")
self.gridLayout_5.addWidget(self.uiCustomAdaptersLabel, 6, 0, 1, 1)
self.uiCustomAdaptersConfigurationPushButton = QtWidgets.QPushButton(self.uiNetworkTab)
self.uiCustomAdaptersConfigurationPushButton.setObjectName("uiCustomAdaptersConfigurationPushButton")
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, 8, 0, 1, 3)
spacerItem3 = QtWidgets.QSpacerItem(20, 261, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
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)
@@ -325,6 +296,9 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiAdapterTypesComboBox.setSizePolicy(sizePolicy)
self.uiAdapterTypesComboBox.setObjectName("uiAdapterTypesComboBox")
self.gridLayout_5.addWidget(self.uiAdapterTypesComboBox, 5, 1, 1, 2)
self.uiAdapterTypesLabel = QtWidgets.QLabel(self.uiNetworkTab)
self.uiAdapterTypesLabel.setObjectName("uiAdapterTypesLabel")
self.gridLayout_5.addWidget(self.uiAdapterTypesLabel, 5, 0, 1, 1)
self.uiAdaptersSpinBox = QtWidgets.QSpinBox(self.uiNetworkTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -335,9 +309,43 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiAdaptersSpinBox.setMaximum(275)
self.uiAdaptersSpinBox.setObjectName("uiAdaptersSpinBox")
self.gridLayout_5.addWidget(self.uiAdaptersSpinBox, 0, 1, 1, 2)
self.uiPortSegmentSizeLabel = QtWidgets.QLabel(self.uiNetworkTab)
self.uiPortSegmentSizeLabel.setObjectName("uiPortSegmentSizeLabel")
self.gridLayout_5.addWidget(self.uiPortSegmentSizeLabel, 3, 0, 1, 1)
spacerItem3 = QtWidgets.QSpacerItem(20, 261, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_5.addItem(spacerItem3, 8, 2, 1, 1)
self.uiFirstPortNameLabel = QtWidgets.QLabel(self.uiNetworkTab)
self.uiFirstPortNameLabel.setObjectName("uiFirstPortNameLabel")
self.gridLayout_5.addWidget(self.uiFirstPortNameLabel, 1, 0, 1, 1)
self.uiFirstPortNameLineEdit = QtWidgets.QLineEdit(self.uiNetworkTab)
self.uiFirstPortNameLineEdit.setObjectName("uiFirstPortNameLineEdit")
self.gridLayout_5.addWidget(self.uiFirstPortNameLineEdit, 1, 1, 1, 2)
self.uiReplicateNetworkConnectionStateCheckBox = QtWidgets.QCheckBox(self.uiNetworkTab)
self.uiReplicateNetworkConnectionStateCheckBox.setObjectName("uiReplicateNetworkConnectionStateCheckBox")
self.gridLayout_5.addWidget(self.uiReplicateNetworkConnectionStateCheckBox, 7, 0, 1, 3)
self.uiCustomAdaptersConfigurationPushButton = QtWidgets.QPushButton(self.uiNetworkTab)
self.uiCustomAdaptersConfigurationPushButton.setObjectName("uiCustomAdaptersConfigurationPushButton")
self.gridLayout_5.addWidget(self.uiCustomAdaptersConfigurationPushButton, 6, 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.uiCustomAdaptersLabel = QtWidgets.QLabel(self.uiNetworkTab)
self.uiCustomAdaptersLabel.setObjectName("uiCustomAdaptersLabel")
self.gridLayout_5.addWidget(self.uiCustomAdaptersLabel, 6, 0, 1, 1)
self.uiMacAddrLineEdit = QtWidgets.QLineEdit(self.uiNetworkTab)
self.uiMacAddrLineEdit.setObjectName("uiMacAddrLineEdit")
self.gridLayout_5.addWidget(self.uiMacAddrLineEdit, 4, 1, 1, 2)
self.uiAdaptersLabel = QtWidgets.QLabel(self.uiNetworkTab)
self.uiAdaptersLabel.setObjectName("uiAdaptersLabel")
self.gridLayout_5.addWidget(self.uiAdaptersLabel, 0, 0, 1, 1)
self.uiPortNameFormatLabel = QtWidgets.QLabel(self.uiNetworkTab)
self.uiPortNameFormatLabel.setObjectName("uiPortNameFormatLabel")
self.gridLayout_5.addWidget(self.uiPortNameFormatLabel, 2, 0, 1, 1)
self.uiMacAddrLabel = QtWidgets.QLabel(self.uiNetworkTab)
self.uiMacAddrLabel.setObjectName("uiMacAddrLabel")
self.gridLayout_5.addWidget(self.uiMacAddrLabel, 4, 0, 1, 1)
self.uiQemutabWidget.addTab(self.uiNetworkTab, "")
self.uiAdvancedSettingsTab = QtWidgets.QWidget()
self.uiAdvancedSettingsTab.setObjectName("uiAdvancedSettingsTab")
@@ -470,7 +478,7 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiRamLabel.setText(_translate("QemuVMConfigPageWidget", "RAM:"))
self.uiRamSpinBox.setSuffix(_translate("QemuVMConfigPageWidget", " MB"))
self.uiCPULabel.setText(_translate("QemuVMConfigPageWidget", "vCPUs:"))
self.uiQemuListLabel.setText(_translate("QemuVMConfigPageWidget", "Qemu binary:"))
self.uiQemuPlatformLabel.setText(_translate("QemuVMConfigPageWidget", "Qemu platform:"))
self.uiBootPriorityLabel.setText(_translate("QemuVMConfigPageWidget", "Boot priority:"))
self.uiOnCloseLabel.setText(_translate("QemuVMConfigPageWidget", "On close:"))
self.uiConsoleTypeLabel.setText(_translate("QemuVMConfigPageWidget", "Console type:"))
@@ -480,48 +488,53 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiConsoleTypeComboBox.setItemText(3, _translate("QemuVMConfigPageWidget", "spice+agent"))
self.uiConsoleTypeComboBox.setItemText(4, _translate("QemuVMConfigPageWidget", "none"))
self.uiConsoleAutoStartCheckBox.setText(_translate("QemuVMConfigPageWidget", "Auto start console"))
self.uiAuxTypeLabel.setText(_translate("QemuVMConfigPageWidget", "Auxiliary console type:"))
self.uiAuxTypeComboBox.setItemText(0, _translate("QemuVMConfigPageWidget", "telnet"))
self.uiAuxTypeComboBox.setItemText(1, _translate("QemuVMConfigPageWidget", "vnc"))
self.uiAuxTypeComboBox.setItemText(2, _translate("QemuVMConfigPageWidget", "spice"))
self.uiAuxTypeComboBox.setItemText(3, _translate("QemuVMConfigPageWidget", "spice+agent"))
self.uiAuxTypeComboBox.setItemText(4, _translate("QemuVMConfigPageWidget", "none"))
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiGeneralSettingsTab), _translate("QemuVMConfigPageWidget", "General settings"))
self.uiHdaGroupBox.setTitle(_translate("QemuVMConfigPageWidget", "HDA (Primary Master)"))
self.uiHdaDiskImageLabel.setText(_translate("QemuVMConfigPageWidget", "Disk image:"))
self.uiHdaDiskImageToolButton.setText(_translate("QemuVMConfigPageWidget", "&Browse..."))
self.uiHdaDiskImageCreateToolButton.setText(_translate("QemuVMConfigPageWidget", "Create..."))
self.uiHdaDiskImageResizeToolButton.setText(_translate("QemuVMConfigPageWidget", "Resize..."))
self.uiHdaDiskInterfaceLabel.setText(_translate("QemuVMConfigPageWidget", "Disk interface:"))
self.uiHdbgroupBox.setTitle(_translate("QemuVMConfigPageWidget", "HDB (Primary Slave)"))
self.uiHdbDiskImageLabel.setText(_translate("QemuVMConfigPageWidget", "Disk image:"))
self.uiHdbDiskImageToolButton.setText(_translate("QemuVMConfigPageWidget", "&Browse..."))
self.uiHdbDiskImageCreateToolButton.setText(_translate("QemuVMConfigPageWidget", "Create..."))
self.uiHdbDiskImageResizeToolButton.setText(_translate("QemuVMConfigPageWidget", "Resize..."))
self.uiHdbDiskInterfaceLabel.setText(_translate("QemuVMConfigPageWidget", "Disk interface:"))
self.uiHdcGroupBox.setTitle(_translate("QemuVMConfigPageWidget", "HDC (Secondary Master)"))
self.uiHdcGroupBox.setTitle(_translate("QemuVMConfigPageWidget", "HDC (Disk 2)"))
self.uiHdcDiskImageLabel.setText(_translate("QemuVMConfigPageWidget", "Disk image:"))
self.uiHdcDiskImageToolButton.setText(_translate("QemuVMConfigPageWidget", "&Browse..."))
self.uiHdcDiskImageCreateToolButton.setText(_translate("QemuVMConfigPageWidget", "Create..."))
self.uiHdcDiskImageResizeToolButton.setText(_translate("QemuVMConfigPageWidget", "Resize..."))
self.uiHdcDiskInterfaceLabel.setText(_translate("QemuVMConfigPageWidget", "Disk interface:"))
self.uiHddGroupBox.setTitle(_translate("QemuVMConfigPageWidget", "HDD (Secondary Slave)"))
self.uiHdaGroupBox.setTitle(_translate("QemuVMConfigPageWidget", "HDA (Disk 0)"))
self.uiHdaDiskImageLabel.setText(_translate("QemuVMConfigPageWidget", "Disk image:"))
self.uiHdaDiskImageToolButton.setText(_translate("QemuVMConfigPageWidget", "&Browse..."))
self.uiHdaDiskImageCreateToolButton.setText(_translate("QemuVMConfigPageWidget", "Create..."))
self.uiHdaDiskImageResizeToolButton.setText(_translate("QemuVMConfigPageWidget", "Resize..."))
self.uiHdaDiskInterfaceLabel.setText(_translate("QemuVMConfigPageWidget", "Disk interface:"))
self.uiHdbgroupBox.setTitle(_translate("QemuVMConfigPageWidget", "HDB (Disk 1)"))
self.uiHdbDiskImageLabel.setText(_translate("QemuVMConfigPageWidget", "Disk image:"))
self.uiHdbDiskImageToolButton.setText(_translate("QemuVMConfigPageWidget", "&Browse..."))
self.uiHdbDiskImageCreateToolButton.setText(_translate("QemuVMConfigPageWidget", "Create..."))
self.uiHdbDiskImageResizeToolButton.setText(_translate("QemuVMConfigPageWidget", "Resize..."))
self.uiHdbDiskInterfaceLabel.setText(_translate("QemuVMConfigPageWidget", "Disk interface:"))
self.uiHddGroupBox.setTitle(_translate("QemuVMConfigPageWidget", "HDD (Disk 3)"))
self.uiHddDiskInterfaceLabel.setText(_translate("QemuVMConfigPageWidget", "Disk interface:"))
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..."))
self.uiHddDiskImageResizeToolButton.setText(_translate("QemuVMConfigPageWidget", "Resize..."))
self.uiHddDiskInterfaceLabel.setText(_translate("QemuVMConfigPageWidget", "Disk interface:"))
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiHddTab), _translate("QemuVMConfigPageWidget", "HDD"))
self.uiCdromGroupBox.setTitle(_translate("QemuVMConfigPageWidget", "CD/DVD-ROM"))
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.uiAdapterTypesLabel.setText(_translate("QemuVMConfigPageWidget", "Type:"))
self.uiPortSegmentSizeLabel.setText(_translate("QemuVMConfigPageWidget", "Segment size:"))
self.uiAdaptersLabel.setText(_translate("QemuVMConfigPageWidget", "Adapters:"))
self.uiFirstPortNameLabel.setText(_translate("QemuVMConfigPageWidget", "First port name:"))
self.uiReplicateNetworkConnectionStateCheckBox.setText(_translate("QemuVMConfigPageWidget", "Replicate network connection states in Qemu"))
self.uiCustomAdaptersConfigurationPushButton.setText(_translate("QemuVMConfigPageWidget", "&Configure custom adapters"))
self.uiCustomAdaptersLabel.setText(_translate("QemuVMConfigPageWidget", "Custom adapters:"))
self.uiAdaptersLabel.setText(_translate("QemuVMConfigPageWidget", "Adapters:"))
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.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:"))

View File

@@ -106,32 +106,25 @@
<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">
<widget class="QWizardPage" name="uiPlatformMemoryWizardPage">
<property name="title">
<string>QEMU binary and memory</string>
<string>QEMU platform and memory</string>
</property>
<property name="subTitle">
<string>Please check the Qemu binary is correctly set and the virtual machine has enough memory to work.</string>
<string>Please select the platform and check the virtual machine has enough memory to work.</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="uiQemuListLabel">
<widget class="QLabel" name="uiQemuPlatformLabel">
<property name="text">
<string>Qemu binary:</string>
<string>Platform:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="uiQemuListComboBox">
<widget class="QComboBox" name="uiQemuPlatformComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
@@ -423,7 +416,7 @@
</widget>
<tabstops>
<tabstop>uiNameLineEdit</tabstop>
<tabstop>uiQemuListComboBox</tabstop>
<tabstop>uiQemuPlatformComboBox</tabstop>
<tabstop>uiRamSpinBox</tabstop>
<tabstop>uiHdaDiskImageLineEdit</tabstop>
<tabstop>uiHdaDiskImageToolButton</tabstop>

View File

@@ -2,12 +2,14 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/qemu/ui/qemu_vm_wizard.ui'
#
# Created by: PyQt5 UI code generator 5.9
# Created by: PyQt5 UI code generator 5.14.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_QemuVMWizard(object):
def setupUi(self, QemuVMWizard):
QemuVMWizard.setObjectName("QemuVMWizard")
@@ -60,35 +62,32 @@ 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")
self.gridLayout_2 = QtWidgets.QGridLayout(self.uiBinaryMemoryWizardPage)
self.uiPlatformMemoryWizardPage = QtWidgets.QWizardPage()
self.uiPlatformMemoryWizardPage.setObjectName("uiPlatformMemoryWizardPage")
self.gridLayout_2 = QtWidgets.QGridLayout(self.uiPlatformMemoryWizardPage)
self.gridLayout_2.setObjectName("gridLayout_2")
self.uiQemuListLabel = QtWidgets.QLabel(self.uiBinaryMemoryWizardPage)
self.uiQemuListLabel.setObjectName("uiQemuListLabel")
self.gridLayout_2.addWidget(self.uiQemuListLabel, 0, 0, 1, 1)
self.uiQemuListComboBox = QtWidgets.QComboBox(self.uiBinaryMemoryWizardPage)
self.uiQemuPlatformLabel = QtWidgets.QLabel(self.uiPlatformMemoryWizardPage)
self.uiQemuPlatformLabel.setObjectName("uiQemuPlatformLabel")
self.gridLayout_2.addWidget(self.uiQemuPlatformLabel, 0, 0, 1, 1)
self.uiQemuPlatformComboBox = QtWidgets.QComboBox(self.uiPlatformMemoryWizardPage)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiQemuListComboBox.sizePolicy().hasHeightForWidth())
self.uiQemuListComboBox.setSizePolicy(sizePolicy)
self.uiQemuListComboBox.setObjectName("uiQemuListComboBox")
self.gridLayout_2.addWidget(self.uiQemuListComboBox, 0, 1, 1, 1)
self.uiRamLabel = QtWidgets.QLabel(self.uiBinaryMemoryWizardPage)
sizePolicy.setHeightForWidth(self.uiQemuPlatformComboBox.sizePolicy().hasHeightForWidth())
self.uiQemuPlatformComboBox.setSizePolicy(sizePolicy)
self.uiQemuPlatformComboBox.setObjectName("uiQemuPlatformComboBox")
self.gridLayout_2.addWidget(self.uiQemuPlatformComboBox, 0, 1, 1, 1)
self.uiRamLabel = QtWidgets.QLabel(self.uiPlatformMemoryWizardPage)
self.uiRamLabel.setObjectName("uiRamLabel")
self.gridLayout_2.addWidget(self.uiRamLabel, 1, 0, 1, 1)
self.uiRamSpinBox = QtWidgets.QSpinBox(self.uiBinaryMemoryWizardPage)
self.uiRamSpinBox = QtWidgets.QSpinBox(self.uiPlatformMemoryWizardPage)
self.uiRamSpinBox.setMinimum(32)
self.uiRamSpinBox.setMaximum(65535)
self.uiRamSpinBox.setProperty("value", 256)
self.uiRamSpinBox.setObjectName("uiRamSpinBox")
self.gridLayout_2.addWidget(self.uiRamSpinBox, 1, 1, 1, 1)
QemuVMWizard.addPage(self.uiBinaryMemoryWizardPage)
QemuVMWizard.addPage(self.uiPlatformMemoryWizardPage)
self.uiConsoleTypeWizardPage = QtWidgets.QWizardPage()
self.uiConsoleTypeWizardPage.setObjectName("uiConsoleTypeWizardPage")
self.verticalLayout = QtWidgets.QVBoxLayout(self.uiConsoleTypeWizardPage)
@@ -206,8 +205,8 @@ class Ui_QemuVMWizard(object):
self.retranslateUi(QemuVMWizard)
QtCore.QMetaObject.connectSlotsByName(QemuVMWizard)
QemuVMWizard.setTabOrder(self.uiNameLineEdit, self.uiQemuListComboBox)
QemuVMWizard.setTabOrder(self.uiQemuListComboBox, self.uiRamSpinBox)
QemuVMWizard.setTabOrder(self.uiNameLineEdit, self.uiQemuPlatformComboBox)
QemuVMWizard.setTabOrder(self.uiQemuPlatformComboBox, self.uiRamSpinBox)
QemuVMWizard.setTabOrder(self.uiRamSpinBox, self.uiHdaDiskImageLineEdit)
QemuVMWizard.setTabOrder(self.uiHdaDiskImageLineEdit, self.uiHdaDiskImageToolButton)
@@ -225,10 +224,9 @@ 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:"))
self.uiPlatformMemoryWizardPage.setTitle(_translate("QemuVMWizard", "QEMU platform and memory"))
self.uiPlatformMemoryWizardPage.setSubTitle(_translate("QemuVMWizard", "Please select the platform and check the virtual machine has enough memory to work."))
self.uiQemuPlatformLabel.setText(_translate("QemuVMWizard", "Platform:"))
self.uiRamLabel.setText(_translate("QemuVMWizard", "RAM:"))
self.uiRamSpinBox.setSuffix(_translate("QemuVMWizard", " MB"))
self.uiConsoleTypeWizardPage.setTitle(_translate("QemuVMWizard", "Console type"))
@@ -254,4 +252,3 @@ class Ui_QemuVMWizard(object):
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

@@ -1,140 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
TraceNG module implementation.
"""
import os
import shutil
from gns3.local_config import LocalConfig
from gns3.local_server_config import LocalServerConfig
from ..module import Module
from .traceng_node import TraceNGNode
from .settings import TRACENG_SETTINGS
import logging
log = logging.getLogger(__name__)
class TraceNG(Module):
"""
TraceNG module.
"""
def __init__(self):
super().__init__()
self._working_dir = ""
self._loadSettings()
def _loadSettings(self):
"""
Loads the settings from the persistent settings file.
"""
self._settings = LocalConfig.instance().loadSectionSettings(self.__class__.__name__, TRACENG_SETTINGS)
if not os.path.exists(self._settings["traceng_path"]):
traceng_path = shutil.which("traceng")
if traceng_path:
self._settings["traceng_path"] = os.path.abspath(traceng_path)
else:
self._settings["traceng_path"] = ""
def _saveSettings(self):
"""
Saves the settings to the persistent settings file.
"""
# save the settings
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
server_settings = {}
if self._settings["traceng_path"]:
# save some settings to the server config file
server_settings["traceng_path"] = os.path.normpath(self._settings["traceng_path"])
config = LocalServerConfig.instance()
config.saveSettings(self.__class__.__name__, server_settings)
@staticmethod
def getNodeClass(node_type, platform=None):
"""
Returns the class corresponding to node type.
:param node_type: node type (string)
:param platform: not used
:returns: class or None
"""
if node_type == "traceng":
return TraceNGNode
return None
@staticmethod
def configurationPage():
"""
Returns the configuration page for this module.
:returns: QWidget object
"""
from .pages.traceng_node_configuration_page import TraceNGNodeConfigurationPage
return TraceNGNodeConfigurationPage
@staticmethod
def classes():
"""
Returns all the node classes supported by this module.
:returns: list of classes
"""
return [TraceNGNode]
@staticmethod
def preferencePages():
"""
Returns the preference pages for this module.
:returns: QWidget object list
"""
from .pages.traceng_preferences_page import TraceNGPreferencesPage
from .pages.traceng_node_preferences_page import TraceNGNodePreferencesPage
return [TraceNGPreferencesPage, TraceNGNodePreferencesPage]
@staticmethod
def instance():
"""
Singleton to return only on instance of TraceNG module.
:returns: instance of TraceNG
"""
if not hasattr(TraceNG, "_instance"):
TraceNG._instance = TraceNG()
return TraceNG._instance
def __str__(self):
"""
Returns the module name.
"""
return "traceng"

View File

@@ -1,85 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Wizard for TraceNG nodes.
"""
import sys
import ipaddress
from gns3.qt import QtGui, QtWidgets
from gns3.node import Node
from gns3.dialogs.vm_wizard import VMWizard
from ..ui.traceng_node_wizard_ui import Ui_TraceNGNodeWizard
class TraceNGNodeWizard(VMWizard, Ui_TraceNGNodeWizard):
"""
Wizard to create a TraceNG node.
:param parent: parent widget
"""
def __init__(self, traceng_nodes, parent):
super().__init__(traceng_nodes, parent)
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/icons/traceng.png"))
self.uiNameWizardPage.registerField("name*", self.uiNameLineEdit)
# TraceNG is only supported on a local server
self.uiRemoteRadioButton.setEnabled(False)
self.uiVMRadioButton.setEnabled(False)
def validateCurrentPage(self):
"""
Validates the server.
"""
if super().validateCurrentPage() is False:
return False
if self.currentPage() == self.uiNameWizardPage:
if not sys.platform.startswith("win"):
QtWidgets.QMessageBox.critical(self, "TraceNG", "TraceNG can only run on Windows with a local server")
return False
ip_address = self.uiIPAddressLineEdit.text()
if ip_address:
try:
ipaddress.IPv4Address(ip_address)
except ipaddress.AddressValueError:
QtWidgets.QMessageBox.critical(self, "IP address", "Invalid IP address format")
return False
return True
def getSettings(self):
"""
Returns the settings set in this Wizard.
:return: settings dict
"""
settings = {"name": self.uiNameLineEdit.text(),
"ip_address": self.uiIPAddressLineEdit.text(),
"symbol": ":/symbols/traceng.svg",
"category": Node.end_devices,
"compute_id": self._compute_id}
return settings

View File

@@ -1,155 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Configuration page for TraceNG nodes
"""
import ipaddress
from gns3.qt import QtWidgets
from gns3.local_server import LocalServer
from gns3.node import Node
from gns3.controller import Controller
from ..ui.traceng_node_configuration_page_ui import Ui_TraceNGNodeConfigPageWidget
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.dialogs.node_properties_dialog import ConfigurationError
class TraceNGNodeConfigurationPage(QtWidgets.QWidget, Ui_TraceNGNodeConfigPageWidget):
"""
QWidget configuration page for TraceNG nodes.
"""
def __init__(self):
super().__init__()
self.setupUi(self)
self.uiSymbolToolButton.clicked.connect(self._symbolBrowserSlot)
self._default_configs_dir = LocalServer.instance().localServerSettings()["configs_path"]
if Controller.instance().isRemote():
self.uiScriptFileToolButton.hide()
# add the categories
for name, category in Node.defaultCategories().items():
self.uiCategoryComboBox.addItem(name, category)
def _symbolBrowserSlot(self):
"""
Slot to open the symbol browser and select a new symbol.
"""
symbol_path = self.uiSymbolLineEdit.text()
dialog = SymbolSelectionDialog(self, symbol=symbol_path)
dialog.show()
if dialog.exec_():
new_symbol_path = dialog.getSymbol()
self.uiSymbolLineEdit.setText(new_symbol_path)
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(new_symbol_path))
def loadSettings(self, settings, node=None, group=False):
"""
Loads the TraceNG node settings.
:param settings: the settings (dictionary)
:param node: Node instance
:param group: indicates the settings apply to a group of routers
"""
if not group:
self.uiNameLineEdit.setText(settings["name"])
self.uiIPAddressLineEdit.setText(settings["ip_address"])
self.uiDefaultDestinationLineEdit.setText(settings["default_destination"])
else:
self.uiIPAddressLabel.hide()
self.uiIPAddressLineEdit.hide()
self.uiDefaultDestinationLabel.hide()
self.uiDefaultDestinationLineEdit.hide()
self.uiNameLabel.hide()
self.uiNameLineEdit.hide()
if not node:
# these are template settings
self.uiNameLabel.setText("Template name:")
# load the default name format
self.uiDefaultNameFormatLineEdit.setText(settings["default_name_format"])
# load the symbol
self.uiSymbolLineEdit.setText(settings["symbol"])
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(settings["symbol"]))
# load the category
index = self.uiCategoryComboBox.findData(settings["category"])
if index != -1:
self.uiCategoryComboBox.setCurrentIndex(index)
else:
self.uiDefaultNameFormatLabel.hide()
self.uiDefaultNameFormatLineEdit.hide()
self.uiSymbolLabel.hide()
self.uiSymbolLineEdit.hide()
self.uiSymbolToolButton.hide()
self.uiCategoryComboBox.hide()
self.uiCategoryLabel.hide()
self.uiCategoryComboBox.hide()
def saveSettings(self, settings, node=None, group=False):
"""
Saves the TraceNG node settings.
:param settings: the settings (dictionary)
:param node: Node instance
:param group: indicates the settings apply to a group of routers
"""
# these settings cannot be shared by nodes and updated
# in the node properties dialog.
if not group:
# set the node name
name = self.uiNameLineEdit.text()
if not name:
QtWidgets.QMessageBox.critical(self, "Name", "TraceNG node name cannot be empty!")
else:
settings["name"] = name
ip_address = self.uiIPAddressLineEdit.text().strip()
if ip_address:
try:
ipaddress.IPv4Address(ip_address)
settings["ip_address"] = ip_address
except ipaddress.AddressValueError:
QtWidgets.QMessageBox.critical(self, "IP address", "Invalid IP address format")
if node:
raise ConfigurationError()
settings["default_destination"] = self.uiDefaultDestinationLineEdit.text().strip()
if not node:
default_name_format = self.uiDefaultNameFormatLineEdit.text().strip()
if '{0}' not in default_name_format and '{id}' not in default_name_format:
QtWidgets.QMessageBox.critical(self, "Default name format", "The default name format must contain at least {0} or {id}")
else:
settings["default_name_format"] = default_name_format
symbol_path = self.uiSymbolLineEdit.text()
settings["symbol"] = symbol_path
settings["category"] = self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex())
return settings

View File

@@ -1,213 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Configuration page for TraceNG node preferences.
"""
import copy
from gns3.qt import QtCore, QtWidgets, qpartial
from gns3.main_window import MainWindow
from gns3.dialogs.configuration_dialog import ConfigurationDialog
from gns3.compute_manager import ComputeManager
from gns3.template_manager import TemplateManager
from gns3.controller import Controller
from gns3.template import Template
from ..settings import TRACENG_NODES_SETTINGS
from ..ui.traceng_node_preferences_page_ui import Ui_TraceNGNodePageWidget
from ..pages.traceng_node_configuration_page import TraceNGNodeConfigurationPage
from ..dialogs.traceng_node_wizard import TraceNGNodeWizard
class TraceNGNodePreferencesPage(QtWidgets.QWidget, Ui_TraceNGNodePageWidget):
"""
QWidget preference page for TraceNG node preferences.
"""
def __init__(self):
super().__init__()
self.setupUi(self)
self._main_window = MainWindow.instance()
self._traceng_nodes = {}
self._items = []
self.uiNewTraceNGPushButton.clicked.connect(self._newTraceNGSlot)
self.uiEditTraceNGPushButton.clicked.connect(self._editTraceNGSlot)
self.uiDeleteTraceNGPushButton.clicked.connect(self._deleteTraceNGSlot)
self.uiTraceNGTreeWidget.itemSelectionChanged.connect(self._tracengChangedSlot)
def _createSectionItem(self, name):
"""
Adds a new section to the tree widget.
:param name: section name
"""
section_item = QtWidgets.QTreeWidgetItem(self.uiTraceNGInfoTreeWidget)
section_item.setText(0, name)
font = section_item.font(0)
font.setBold(True)
section_item.setFont(0, font)
return section_item
def _refreshInfo(self, traceng_node):
"""
Refreshes the content of the tree widget.
"""
self.uiTraceNGInfoTreeWidget.clear()
# fill out the General section
section_item = self._createSectionItem("General")
QtWidgets.QTreeWidgetItem(section_item, ["Template name:", traceng_node["name"]])
QtWidgets.QTreeWidgetItem(section_item, ["Template ID:", traceng_node.get("template_id", "none")])
QtWidgets.QTreeWidgetItem(section_item, ["IP address:", traceng_node["ip_address"]])
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", traceng_node["default_name_format"]])
try:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(traceng_node["compute_id"]).name()])
except KeyError:
pass
self.uiTraceNGInfoTreeWidget.expandAll()
self.uiTraceNGInfoTreeWidget.resizeColumnToContents(0)
self.uiTraceNGInfoTreeWidget.resizeColumnToContents(1)
self.uiTraceNGTreeWidget.setMaximumWidth(self.uiTraceNGTreeWidget.sizeHintForColumn(0) + 20)
def _tracengChangedSlot(self):
"""
Loads a selected TraceNG node from the tree widget.
"""
selection = self.uiTraceNGTreeWidget.selectedItems()
self.uiDeleteTraceNGPushButton.setEnabled(len(selection) != 0)
single_selected = len(selection) == 1
self.uiEditTraceNGPushButton.setEnabled(single_selected)
if single_selected:
key = selection[0].data(0, QtCore.Qt.UserRole)
traceng_node = self._traceng_nodes[key]
self._refreshInfo(traceng_node)
else:
self.uiTraceNGInfoTreeWidget.clear()
def _newTraceNGSlot(self):
"""
Creates a new TraceNG node.
"""
wizard = TraceNGNodeWizard(self._traceng_nodes, parent=self)
wizard.show()
if wizard.exec_():
new_traceng_node_settings = wizard.getSettings()
key = "{server}:{name}".format(server=new_traceng_node_settings["compute_id"], name=new_traceng_node_settings["name"])
self._traceng_nodes[key] = TRACENG_NODES_SETTINGS.copy()
self._traceng_nodes[key].update(new_traceng_node_settings)
item = QtWidgets.QTreeWidgetItem(self.uiTraceNGTreeWidget)
item.setText(0, self._traceng_nodes[key]["name"])
Controller.instance().getSymbolIcon(self._traceng_nodes[key]["symbol"], qpartial(self._setItemIcon, item))
item.setData(0, QtCore.Qt.UserRole, key)
self._items.append(item)
self.uiTraceNGTreeWidget.setCurrentItem(item)
def _editTraceNGSlot(self):
"""
Edits a TraceNG node.
"""
item = self.uiTraceNGTreeWidget.currentItem()
if item:
key = item.data(0, QtCore.Qt.UserRole)
traceng_node = self._traceng_nodes[key]
dialog = ConfigurationDialog(traceng_node["name"], traceng_node, TraceNGNodeConfigurationPage(), parent=self)
dialog.show()
if dialog.exec_():
# update the icon
Controller.instance().getSymbolIcon(traceng_node["symbol"], qpartial(self._setItemIcon, item))
if traceng_node["name"] != item.text(0):
new_key = "{server}:{name}".format(server=traceng_node["compute_id"], name=traceng_node["name"])
if new_key in self._traceng_nodes:
QtWidgets.QMessageBox.critical(self, "TraceNG node", "TraceNG node name {} already exists for server {}".format(traceng_node["name"],
traceng_node["compute_id"]))
traceng_node["name"] = item.text(0)
return
self._traceng_nodes[new_key] = self._traceng_nodes[key]
del self._traceng_nodes[key]
item.setText(0, traceng_node["name"])
item.setData(0, QtCore.Qt.UserRole, new_key)
self._refreshInfo(traceng_node)
def _deleteTraceNGSlot(self):
"""
Deletes a TraceNG node.
"""
for item in self.uiTraceNGTreeWidget.selectedItems():
if item:
key = item.data(0, QtCore.Qt.UserRole)
del self._traceng_nodes[key]
self.uiTraceNGTreeWidget.takeTopLevelItem(self.uiTraceNGTreeWidget.indexOfTopLevelItem(item))
def loadPreferences(self):
"""
Loads the TraceNG node preferences.
"""
self._traceng_nodes = {}
templates = TemplateManager.instance().templates()
for template_id, template in templates.items():
if template.template_type() == "traceng" and not template.builtin():
name = template.name()
server = template.compute_id()
#TODO: use template id for the key
key = "{server}:{name}".format(server=server, name=name)
self._traceng_nodes[key] = copy.deepcopy(template.settings())
self._items.clear()
for key, node in self._traceng_nodes.items():
item = QtWidgets.QTreeWidgetItem(self.uiTraceNGTreeWidget)
item.setText(0, node["name"])
Controller.instance().getSymbolIcon(node["symbol"], qpartial(self._setItemIcon, item))
item.setData(0, QtCore.Qt.UserRole, key)
self._items.append(item)
if self._items:
self.uiTraceNGTreeWidget.setCurrentItem(self._items[0])
self.uiTraceNGTreeWidget.sortByColumn(0, QtCore.Qt.AscendingOrder)
self.uiTraceNGTreeWidget.setMaximumWidth(self.uiTraceNGTreeWidget.sizeHintForColumn(0) + 20)
def _setItemIcon(self, item, icon):
item.setIcon(0, icon)
self.uiTraceNGTreeWidget.setMaximumWidth(self.uiTraceNGTreeWidget.sizeHintForColumn(0) + 20)
def savePreferences(self):
"""
Saves the TraceNG node preferences.
"""
templates = []
for template in TemplateManager.instance().templates().values():
if template.template_type() != "traceng":
templates.append(template)
for template_settings in self._traceng_nodes.values():
templates.append(Template(template_settings))
TemplateManager.instance().updateList(templates)

View File

@@ -1,126 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Configuration page for TraceNG preferences.
"""
import os
import sys
import shutil
from gns3.qt import QtWidgets
from .. import TraceNG
from ..ui.traceng_preferences_page_ui import Ui_TraceNGPreferencesPageWidget
from ..settings import TRACENG_SETTINGS
class TraceNGPreferencesPage(QtWidgets.QWidget, Ui_TraceNGPreferencesPageWidget):
"""
QWidget preference page for TraceNG
"""
def __init__(self):
super().__init__()
self.setupUi(self)
# connect signals
self.uiRestoreDefaultsPushButton.clicked.connect(self._restoreDefaultsSlot)
self.uiTraceNGPathToolButton.clicked.connect(self._tracengPathBrowserSlot)
def _tracengPathBrowserSlot(self):
"""
Slot to open a file browser and select traceng
"""
filter = ""
if sys.platform.startswith("win"):
filter = "Executable (*.exe);;All files (*.*)"
traceng_path = shutil.which("traceng")
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select TraceNG", traceng_path, filter)
if not path:
return
if self._checkTraceNGPath(path):
self.uiTraceNGPathLineEdit.setText(os.path.normpath(path))
def _checkTraceNGPath(self, path):
"""
Checks that the TraceNG path is valid.
:param path: TraceNG path
:returns: boolean
"""
if not os.path.exists(path):
QtWidgets.QMessageBox.critical(self, "TraceNG", '"{}" does not exist'.format(path))
return False
if not os.access(path, os.X_OK):
QtWidgets.QMessageBox.critical(self, "TraceNG", "{} is not an executable".format(os.path.basename(path)))
return False
return True
def _restoreDefaultsSlot(self):
"""
Slot to populate the page widgets with the default settings.
"""
self._populateWidgets(TRACENG_SETTINGS)
def _useLocalServerSlot(self, state):
"""
Slot to enable or not local server settings.
"""
if state:
self.uiTraceNGPathLineEdit.setEnabled(True)
self.uiTraceNGPathToolButton.setEnabled(True)
else:
self.uiTraceNGPathLineEdit.setEnabled(False)
self.uiTraceNGPathToolButton.setEnabled(False)
def _populateWidgets(self, settings):
"""
Populates the widgets with the settings.
:param settings: TraceNG settings
"""
self.uiTraceNGPathLineEdit.setText(settings["traceng_path"])
def loadPreferences(self):
"""
Loads TraceNG preferences.
"""
traceng_settings = TraceNG.instance().settings()
self._populateWidgets(traceng_settings)
def savePreferences(self):
"""
Saves TraceNG preferences.
"""
traceng_path = self.uiTraceNGPathLineEdit.text().strip()
if traceng_path and not self._checkTraceNGPath(traceng_path):
return
new_settings = {"traceng_path": traceng_path}
TraceNG.instance().setSettings(new_settings)

View File

@@ -1,37 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Default TraceNG settings.
"""
from gns3.node import Node
TRACENG_SETTINGS = {
"traceng_path": "",
}
TRACENG_NODES_SETTINGS = {
"name": "",
"ip_address": "",
"default_destination": "",
"default_name_format": "TraceNG{0}",
"console_type": "none",
"symbol": ":/symbols/traceng.svg",
"category": Node.end_devices,
"node_type": "traceng"
}

View File

@@ -1,140 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
TraceNG node implementation.
"""
from gns3.node import Node
from gns3.qt import QtWidgets
import logging
log = logging.getLogger(__name__)
class TraceNGNode(Node):
"""
TraceNG node.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
URL_PREFIX = "traceng"
def __init__(self, module, server, project):
super().__init__(module, server, project)
traceng_settings = {"console_type": "none",
"ip_address": "",
"default_destination": ""}
self._last_destination = ""
self.settings().update(traceng_settings)
def start(self):
"""
Starts this node instance.
"""
if self.isStarted():
log.debug("{} is already running".format(self.name()))
return
if self._last_destination:
destination = self._last_destination
else:
destination = self.settings()["default_destination"]
destination, ok = QtWidgets.QInputDialog.getText(self.parent(), "TraceNG", "Destination host or IP address:", text=destination)
if ok:
if not destination:
QtWidgets.QMessageBox.critical(self, "TraceNG", "Please provide a host or IP address to trace")
return
ip_address = self.settings()["ip_address"]
if destination == ip_address:
QtWidgets.QMessageBox.critical(self, "TraceNG", "Destination cannot be the same as this node IP address ({})".format(ip_address))
return
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, showProgress=False)
def info(self):
"""
Returns information about this TraceNG node.
:returns: formatted string
"""
info = """Node {name} is {state}
Running on server {host} with port {port}
Local ID is {id} and server ID is {node_id}
Console is on port {console}
IP address is {ip_address}
""".format(name=self.name(),
id=self.id(),
node_id=self._node_id,
state=self.state(),
host=self.compute().name(),
port=self.compute().port(),
console=self._settings["console"],
ip_address=self._settings["ip_address"])
port_info = ""
for port in self._ports:
if port.isFree():
port_info += " {port_name} is empty\n".format(port_name=port.name())
else:
port_info += " {port_name} {port_description}\n".format(port_name=port.name(),
port_description=port.description())
return info + port_info
def configPage(self):
"""
Returns the configuration page widget to be used by the node properties dialog.
:returns: QWidget object
"""
from .pages.traceng_node_configuration_page import TraceNGNodeConfigurationPage
return TraceNGNodeConfigurationPage
@staticmethod
def defaultSymbol():
"""
Returns the default symbol path for this node.
:returns: symbol path (or resource).
"""
return ":/symbols/traceng.svg"
@staticmethod
def categories():
"""
Returns the node categories the node is part of (used by the node panel).
:returns: list of node categories
"""
return [Node.end_devices]
def __str__(self):
return "TraceNG node"

View File

@@ -1,119 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TraceNGNodeConfigPageWidget</class>
<widget class="QWidget" name="TraceNGNodeConfigPageWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>846</width>
<height>340</height>
</rect>
</property>
<property name="windowTitle">
<string>TraceNG node template configuration</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="uiNameLabel">
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiIPAddressLabel">
<property name="text">
<string>IP address:</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="uiDefaultDestinationLabel">
<property name="text">
<string>Default destination:</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="3">
<widget class="QLabel" name="uiDefaultNameFormatLabel">
<property name="text">
<string>Default name format:</string>
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="QLineEdit" name="uiDefaultNameFormatLineEdit"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="uiSymbolLabel">
<property name="text">
<string>Symbol:</string>
</property>
</widget>
</item>
<item row="4" column="3">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLineEdit" name="uiSymbolLineEdit"/>
</item>
<item>
<widget class="QToolButton" name="uiSymbolToolButton">
<property name="text">
<string>&amp;Browse...</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum>
</property>
</widget>
</item>
</layout>
</item>
<item row="5" column="0" colspan="2">
<widget class="QLabel" name="uiCategoryLabel">
<property name="text">
<string>Category:</string>
</property>
</widget>
</item>
<item row="5" column="3">
<widget class="QComboBox" name="uiCategoryComboBox"/>
</item>
<item row="6" column="1" colspan="3">
<spacer name="spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>263</width>
<height>212</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="3">
<widget class="QLineEdit" name="uiDefaultDestinationLineEdit"/>
</item>
<item row="1" column="3">
<widget class="QLineEdit" name="uiIPAddressLineEdit"/>
</item>
<item row="0" column="3">
<widget class="QLineEdit" name="uiNameLineEdit"/>
</item>
</layout>
<zorder>uiNameLabel</zorder>
<zorder>uiNameLineEdit</zorder>
<zorder>uiDefaultNameFormatLabel</zorder>
<zorder>uiDefaultNameFormatLineEdit</zorder>
<zorder>uiSymbolLabel</zorder>
<zorder>uiCategoryLabel</zorder>
<zorder>uiCategoryComboBox</zorder>
<zorder>uiIPAddressLabel</zorder>
<zorder>uiIPAddressLineEdit</zorder>
<zorder>uiDefaultDestinationLabel</zorder>
<zorder>uiDefaultDestinationLineEdit</zorder>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -1,87 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/traceng/ui/traceng_node_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_TraceNGNodeConfigPageWidget(object):
def setupUi(self, TraceNGNodeConfigPageWidget):
TraceNGNodeConfigPageWidget.setObjectName("TraceNGNodeConfigPageWidget")
TraceNGNodeConfigPageWidget.resize(846, 340)
self.gridLayout = QtWidgets.QGridLayout(TraceNGNodeConfigPageWidget)
self.gridLayout.setObjectName("gridLayout")
self.uiNameLabel = QtWidgets.QLabel(TraceNGNodeConfigPageWidget)
self.uiNameLabel.setObjectName("uiNameLabel")
self.gridLayout.addWidget(self.uiNameLabel, 0, 0, 1, 1)
self.uiIPAddressLabel = QtWidgets.QLabel(TraceNGNodeConfigPageWidget)
self.uiIPAddressLabel.setObjectName("uiIPAddressLabel")
self.gridLayout.addWidget(self.uiIPAddressLabel, 1, 0, 1, 1)
self.uiDefaultDestinationLabel = QtWidgets.QLabel(TraceNGNodeConfigPageWidget)
self.uiDefaultDestinationLabel.setObjectName("uiDefaultDestinationLabel")
self.gridLayout.addWidget(self.uiDefaultDestinationLabel, 2, 0, 1, 2)
self.uiDefaultNameFormatLabel = QtWidgets.QLabel(TraceNGNodeConfigPageWidget)
self.uiDefaultNameFormatLabel.setObjectName("uiDefaultNameFormatLabel")
self.gridLayout.addWidget(self.uiDefaultNameFormatLabel, 3, 0, 1, 3)
self.uiDefaultNameFormatLineEdit = QtWidgets.QLineEdit(TraceNGNodeConfigPageWidget)
self.uiDefaultNameFormatLineEdit.setObjectName("uiDefaultNameFormatLineEdit")
self.gridLayout.addWidget(self.uiDefaultNameFormatLineEdit, 3, 3, 1, 1)
self.uiSymbolLabel = QtWidgets.QLabel(TraceNGNodeConfigPageWidget)
self.uiSymbolLabel.setObjectName("uiSymbolLabel")
self.gridLayout.addWidget(self.uiSymbolLabel, 4, 0, 1, 1)
self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
self.uiSymbolLineEdit = QtWidgets.QLineEdit(TraceNGNodeConfigPageWidget)
self.uiSymbolLineEdit.setObjectName("uiSymbolLineEdit")
self.horizontalLayout_7.addWidget(self.uiSymbolLineEdit)
self.uiSymbolToolButton = QtWidgets.QToolButton(TraceNGNodeConfigPageWidget)
self.uiSymbolToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiSymbolToolButton.setObjectName("uiSymbolToolButton")
self.horizontalLayout_7.addWidget(self.uiSymbolToolButton)
self.gridLayout.addLayout(self.horizontalLayout_7, 4, 3, 1, 1)
self.uiCategoryLabel = QtWidgets.QLabel(TraceNGNodeConfigPageWidget)
self.uiCategoryLabel.setObjectName("uiCategoryLabel")
self.gridLayout.addWidget(self.uiCategoryLabel, 5, 0, 1, 2)
self.uiCategoryComboBox = QtWidgets.QComboBox(TraceNGNodeConfigPageWidget)
self.uiCategoryComboBox.setObjectName("uiCategoryComboBox")
self.gridLayout.addWidget(self.uiCategoryComboBox, 5, 3, 1, 1)
spacerItem = QtWidgets.QSpacerItem(263, 212, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem, 6, 1, 1, 3)
self.uiDefaultDestinationLineEdit = QtWidgets.QLineEdit(TraceNGNodeConfigPageWidget)
self.uiDefaultDestinationLineEdit.setObjectName("uiDefaultDestinationLineEdit")
self.gridLayout.addWidget(self.uiDefaultDestinationLineEdit, 2, 3, 1, 1)
self.uiIPAddressLineEdit = QtWidgets.QLineEdit(TraceNGNodeConfigPageWidget)
self.uiIPAddressLineEdit.setObjectName("uiIPAddressLineEdit")
self.gridLayout.addWidget(self.uiIPAddressLineEdit, 1, 3, 1, 1)
self.uiNameLineEdit = QtWidgets.QLineEdit(TraceNGNodeConfigPageWidget)
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 3, 1, 1)
self.uiNameLabel.raise_()
self.uiNameLineEdit.raise_()
self.uiDefaultNameFormatLabel.raise_()
self.uiDefaultNameFormatLineEdit.raise_()
self.uiSymbolLabel.raise_()
self.uiCategoryLabel.raise_()
self.uiCategoryComboBox.raise_()
self.uiIPAddressLabel.raise_()
self.uiIPAddressLineEdit.raise_()
self.uiDefaultDestinationLabel.raise_()
self.uiDefaultDestinationLineEdit.raise_()
self.retranslateUi(TraceNGNodeConfigPageWidget)
QtCore.QMetaObject.connectSlotsByName(TraceNGNodeConfigPageWidget)
def retranslateUi(self, TraceNGNodeConfigPageWidget):
_translate = QtCore.QCoreApplication.translate
TraceNGNodeConfigPageWidget.setWindowTitle(_translate("TraceNGNodeConfigPageWidget", "TraceNG node template configuration"))
self.uiNameLabel.setText(_translate("TraceNGNodeConfigPageWidget", "Name:"))
self.uiIPAddressLabel.setText(_translate("TraceNGNodeConfigPageWidget", "IP address:"))
self.uiDefaultDestinationLabel.setText(_translate("TraceNGNodeConfigPageWidget", "Default destination:"))
self.uiDefaultNameFormatLabel.setText(_translate("TraceNGNodeConfigPageWidget", "Default name format:"))
self.uiSymbolLabel.setText(_translate("TraceNGNodeConfigPageWidget", "Symbol:"))
self.uiSymbolToolButton.setText(_translate("TraceNGNodeConfigPageWidget", "&Browse..."))
self.uiCategoryLabel.setText(_translate("TraceNGNodeConfigPageWidget", "Category:"))

View File

@@ -1,157 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TraceNGNodePageWidget</class>
<widget class="QWidget" name="TraceNGNodePageWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>546</width>
<height>455</height>
</rect>
</property>
<property name="windowTitle">
<string>TraceNG nodes</string>
</property>
<property name="accessibleName">
<string>TraceNG node templates</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QTreeWidget" name="uiTraceNGTreeWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>160</width>
<height>16777215</height>
</size>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTreeWidget" name="uiTraceNGInfoTreeWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="indentation">
<number>10</number>
</property>
<property name="allColumnsShowFocus">
<bool>true</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>1</string>
</property>
</column>
<column>
<property name="text">
<string>2</string>
</property>
</column>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QPushButton" name="uiNewTraceNGPushButton">
<property name="text">
<string>&amp;New</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="uiEditTraceNGPushButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Edit</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="uiDeleteTraceNGPushButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Delete</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>uiNewTraceNGPushButton</tabstop>
<tabstop>uiDeleteTraceNGPushButton</tabstop>
</tabstops>
<resources/>
<connections/>
<designerdata>
<property name="gridDeltaX">
<number>10</number>
</property>
<property name="gridDeltaY">
<number>10</number>
</property>
<property name="gridSnapX">
<bool>true</bool>
</property>
<property name="gridSnapY">
<bool>true</bool>
</property>
<property name="gridVisible">
<bool>true</bool>
</property>
</designerdata>
</ui>

View File

@@ -1,83 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/traceng/ui/traceng_node_preferences_page.ui'
#
# Created by: PyQt5 UI code generator 5.9
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_TraceNGNodePageWidget(object):
def setupUi(self, TraceNGNodePageWidget):
TraceNGNodePageWidget.setObjectName("TraceNGNodePageWidget")
TraceNGNodePageWidget.resize(546, 455)
self.verticalLayout_2 = QtWidgets.QVBoxLayout(TraceNGNodePageWidget)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.splitter = QtWidgets.QSplitter(TraceNGNodePageWidget)
self.splitter.setOrientation(QtCore.Qt.Horizontal)
self.splitter.setObjectName("splitter")
self.uiTraceNGTreeWidget = QtWidgets.QTreeWidget(self.splitter)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiTraceNGTreeWidget.sizePolicy().hasHeightForWidth())
self.uiTraceNGTreeWidget.setSizePolicy(sizePolicy)
self.uiTraceNGTreeWidget.setMaximumSize(QtCore.QSize(160, 16777215))
font = QtGui.QFont()
font.setPointSize(11)
font.setBold(True)
font.setWeight(75)
self.uiTraceNGTreeWidget.setFont(font)
self.uiTraceNGTreeWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.uiTraceNGTreeWidget.setIconSize(QtCore.QSize(32, 32))
self.uiTraceNGTreeWidget.setRootIsDecorated(False)
self.uiTraceNGTreeWidget.setObjectName("uiTraceNGTreeWidget")
self.uiTraceNGTreeWidget.headerItem().setText(0, "1")
self.uiTraceNGTreeWidget.header().setVisible(False)
self.layoutWidget = QtWidgets.QWidget(self.splitter)
self.layoutWidget.setObjectName("layoutWidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setObjectName("verticalLayout")
self.uiTraceNGInfoTreeWidget = QtWidgets.QTreeWidget(self.layoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiTraceNGInfoTreeWidget.sizePolicy().hasHeightForWidth())
self.uiTraceNGInfoTreeWidget.setSizePolicy(sizePolicy)
self.uiTraceNGInfoTreeWidget.setIndentation(10)
self.uiTraceNGInfoTreeWidget.setAllColumnsShowFocus(True)
self.uiTraceNGInfoTreeWidget.setObjectName("uiTraceNGInfoTreeWidget")
self.uiTraceNGInfoTreeWidget.header().setVisible(False)
self.verticalLayout.addWidget(self.uiTraceNGInfoTreeWidget)
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
self.uiNewTraceNGPushButton = QtWidgets.QPushButton(self.layoutWidget)
self.uiNewTraceNGPushButton.setObjectName("uiNewTraceNGPushButton")
self.horizontalLayout_5.addWidget(self.uiNewTraceNGPushButton)
self.uiEditTraceNGPushButton = QtWidgets.QPushButton(self.layoutWidget)
self.uiEditTraceNGPushButton.setEnabled(False)
self.uiEditTraceNGPushButton.setObjectName("uiEditTraceNGPushButton")
self.horizontalLayout_5.addWidget(self.uiEditTraceNGPushButton)
self.uiDeleteTraceNGPushButton = QtWidgets.QPushButton(self.layoutWidget)
self.uiDeleteTraceNGPushButton.setEnabled(False)
self.uiDeleteTraceNGPushButton.setObjectName("uiDeleteTraceNGPushButton")
self.horizontalLayout_5.addWidget(self.uiDeleteTraceNGPushButton)
self.verticalLayout.addLayout(self.horizontalLayout_5)
self.verticalLayout_2.addWidget(self.splitter)
self.retranslateUi(TraceNGNodePageWidget)
QtCore.QMetaObject.connectSlotsByName(TraceNGNodePageWidget)
TraceNGNodePageWidget.setTabOrder(self.uiNewTraceNGPushButton, self.uiDeleteTraceNGPushButton)
def retranslateUi(self, TraceNGNodePageWidget):
_translate = QtCore.QCoreApplication.translate
TraceNGNodePageWidget.setWindowTitle(_translate("TraceNGNodePageWidget", "TraceNG nodes"))
TraceNGNodePageWidget.setAccessibleName(_translate("TraceNGNodePageWidget", "TraceNG node templates"))
self.uiTraceNGInfoTreeWidget.headerItem().setText(0, _translate("TraceNGNodePageWidget", "1"))
self.uiTraceNGInfoTreeWidget.headerItem().setText(1, _translate("TraceNGNodePageWidget", "2"))
self.uiNewTraceNGPushButton.setText(_translate("TraceNGNodePageWidget", "&New"))
self.uiEditTraceNGPushButton.setText(_translate("TraceNGNodePageWidget", "&Edit"))
self.uiDeleteTraceNGPushButton.setText(_translate("TraceNGNodePageWidget", "&Delete"))

View File

@@ -1,140 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TraceNGNodeWizard</class>
<widget class="QWizard" name="TraceNGNodeWizard">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>706</width>
<height>452</height>
</rect>
</property>
<property name="windowTitle">
<string>New TraceNG node template</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<widget class="QWizardPage" name="uiServerWizardPage">
<property name="title">
<string>Server</string>
</property>
<property name="subTitle">
<string>Please choose a server type to run the TraceNG node.</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QGroupBox" name="uiServerTypeGroupBox">
<property name="title">
<string>Server type</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QRadioButton" name="uiRemoteRadioButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Run the TraceNG node on a remote computer</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="uiVMRadioButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Run the TraceNG node on the GNS3 VM</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="uiLocalRadioButton">
<property name="text">
<string>Run the TraceNG node on your local computer</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="uiRemoteServersGroupBox">
<property name="title">
<string>Remote server</string>
</property>
<layout class="QGridLayout" name="gridLayout_7">
<item row="0" column="0">
<widget class="QLabel" name="uiRemoteServersLabel">
<property name="text">
<string>Run on:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="uiRemoteServersComboBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="uiNameWizardPage">
<property name="title">
<string>Name and IP address</string>
</property>
<property name="subTitle">
<string>Please choose a descriptive name and IP address for the new TraceNG node.</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="uiNameLabel">
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="uiNameLineEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiIPAddressLabel">
<property name="text">
<string>IP address:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="uiIPAddressLineEdit">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<tabstops>
<tabstop>uiNameLineEdit</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -1,93 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/traceng/ui/traceng_node_wizard.ui'
#
# Created by: PyQt5 UI code generator 5.9
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_TraceNGNodeWizard(object):
def setupUi(self, TraceNGNodeWizard):
TraceNGNodeWizard.setObjectName("TraceNGNodeWizard")
TraceNGNodeWizard.resize(706, 452)
TraceNGNodeWizard.setModal(True)
self.uiServerWizardPage = QtWidgets.QWizardPage()
self.uiServerWizardPage.setObjectName("uiServerWizardPage")
self.gridLayout_2 = QtWidgets.QGridLayout(self.uiServerWizardPage)
self.gridLayout_2.setObjectName("gridLayout_2")
self.uiServerTypeGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
self.uiServerTypeGroupBox.setObjectName("uiServerTypeGroupBox")
self.verticalLayout = QtWidgets.QVBoxLayout(self.uiServerTypeGroupBox)
self.verticalLayout.setObjectName("verticalLayout")
self.uiRemoteRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiRemoteRadioButton.setEnabled(False)
self.uiRemoteRadioButton.setChecked(False)
self.uiRemoteRadioButton.setObjectName("uiRemoteRadioButton")
self.verticalLayout.addWidget(self.uiRemoteRadioButton)
self.uiVMRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiVMRadioButton.setEnabled(False)
self.uiVMRadioButton.setObjectName("uiVMRadioButton")
self.verticalLayout.addWidget(self.uiVMRadioButton)
self.uiLocalRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiLocalRadioButton.setChecked(True)
self.uiLocalRadioButton.setObjectName("uiLocalRadioButton")
self.verticalLayout.addWidget(self.uiLocalRadioButton)
self.gridLayout_2.addWidget(self.uiServerTypeGroupBox, 0, 0, 1, 1)
self.uiRemoteServersGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
self.uiRemoteServersGroupBox.setObjectName("uiRemoteServersGroupBox")
self.gridLayout_7 = QtWidgets.QGridLayout(self.uiRemoteServersGroupBox)
self.gridLayout_7.setObjectName("gridLayout_7")
self.uiRemoteServersLabel = QtWidgets.QLabel(self.uiRemoteServersGroupBox)
self.uiRemoteServersLabel.setObjectName("uiRemoteServersLabel")
self.gridLayout_7.addWidget(self.uiRemoteServersLabel, 0, 0, 1, 1)
self.uiRemoteServersComboBox = QtWidgets.QComboBox(self.uiRemoteServersGroupBox)
self.uiRemoteServersComboBox.setEnabled(False)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiRemoteServersComboBox.sizePolicy().hasHeightForWidth())
self.uiRemoteServersComboBox.setSizePolicy(sizePolicy)
self.uiRemoteServersComboBox.setObjectName("uiRemoteServersComboBox")
self.gridLayout_7.addWidget(self.uiRemoteServersComboBox, 0, 1, 1, 1)
self.gridLayout_2.addWidget(self.uiRemoteServersGroupBox, 1, 0, 1, 1)
TraceNGNodeWizard.addPage(self.uiServerWizardPage)
self.uiNameWizardPage = QtWidgets.QWizardPage()
self.uiNameWizardPage.setObjectName("uiNameWizardPage")
self.gridLayout = QtWidgets.QGridLayout(self.uiNameWizardPage)
self.gridLayout.setObjectName("gridLayout")
self.uiNameLabel = QtWidgets.QLabel(self.uiNameWizardPage)
self.uiNameLabel.setObjectName("uiNameLabel")
self.gridLayout.addWidget(self.uiNameLabel, 0, 0, 1, 1)
self.uiNameLineEdit = QtWidgets.QLineEdit(self.uiNameWizardPage)
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
self.uiIPAddressLabel = QtWidgets.QLabel(self.uiNameWizardPage)
self.uiIPAddressLabel.setObjectName("uiIPAddressLabel")
self.gridLayout.addWidget(self.uiIPAddressLabel, 1, 0, 1, 1)
self.uiIPAddressLineEdit = QtWidgets.QLineEdit(self.uiNameWizardPage)
self.uiIPAddressLineEdit.setText("")
self.uiIPAddressLineEdit.setObjectName("uiIPAddressLineEdit")
self.gridLayout.addWidget(self.uiIPAddressLineEdit, 1, 1, 1, 1)
TraceNGNodeWizard.addPage(self.uiNameWizardPage)
self.retranslateUi(TraceNGNodeWizard)
QtCore.QMetaObject.connectSlotsByName(TraceNGNodeWizard)
def retranslateUi(self, TraceNGNodeWizard):
_translate = QtCore.QCoreApplication.translate
TraceNGNodeWizard.setWindowTitle(_translate("TraceNGNodeWizard", "New TraceNG node template"))
self.uiServerWizardPage.setTitle(_translate("TraceNGNodeWizard", "Server"))
self.uiServerWizardPage.setSubTitle(_translate("TraceNGNodeWizard", "Please choose a server type to run the TraceNG node."))
self.uiServerTypeGroupBox.setTitle(_translate("TraceNGNodeWizard", "Server type"))
self.uiRemoteRadioButton.setText(_translate("TraceNGNodeWizard", "Run the TraceNG node on a remote computer"))
self.uiVMRadioButton.setText(_translate("TraceNGNodeWizard", "Run the TraceNG node on the GNS3 VM"))
self.uiLocalRadioButton.setText(_translate("TraceNGNodeWizard", "Run the TraceNG node on your local computer"))
self.uiRemoteServersGroupBox.setTitle(_translate("TraceNGNodeWizard", "Remote server"))
self.uiRemoteServersLabel.setText(_translate("TraceNGNodeWizard", "Run on:"))
self.uiNameWizardPage.setTitle(_translate("TraceNGNodeWizard", "Name and IP address"))
self.uiNameWizardPage.setSubTitle(_translate("TraceNGNodeWizard", "Please choose a descriptive name and IP address for the new TraceNG node."))
self.uiNameLabel.setText(_translate("TraceNGNodeWizard", "Name:"))
self.uiIPAddressLabel.setText(_translate("TraceNGNodeWizard", "IP address:"))

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