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
374 changed files with 101449 additions and 120984 deletions

View File

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

View File

@@ -1,93 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
schedule:
- cron: '17 22 * * 6'
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: python
build-mode: none
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

View File

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

1
.gitignore vendored
View File

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

View File

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

289
CHANGELOG
View File

@@ -1,248 +1,53 @@
# Change Log
## 2.2.59 08/05/2026
## 3.0.0a1 04/08/2022
* Remove psutil version check
* Fix remaining PyQt6 compatibility issues. Fixes #3822
* Add --title to remote-viewer console commands. Fixes #3783
* Fix deleting drawings. Ref #3810
## 2.2.58.1 12/04/2026
* Fix callback issues in found in v2.2.58
## 2.2.58 10/04/2026
* Update snapshot date & time format
* Fix bug when HTTP DELETE requests are silently dropped. Fixes #3810
* Show if a linked base VM or not in device's properties
* Add HTTP method to response timeout slot. Ref #3810
* Add default link style section in preferences
* Update the default style colors based on the interface style
* Support bring to front for vnc and spice consoles on Linux. Fixes #3783
* Update appliance schemas
* Fix bug when adding multiple nodes at the same time. Fixes #3807
* Fix QWebSocket error signal. Fixes #3804
## 2.2.57 23/03/2026
* Fix errors from controller are not reported
* Deactivate 'use default IOU values' by default and update RAM/NVRAM values
* Fix QMenu parents
* Fix UltraVNC preconfigured command
* Dark style
* Fix bug when dragging scene
* Upgrade pytest and jsonschema dependencies
* Upgrade sentry-sdk and psutil dependencies
* Fix mouse manipulation bug
* Add Multi VNC tab handling in Superputty
* Fixing tab name in Superputty + Adding Superputty VNC support
* settings.py: add OpenBSD packet capture command
## 2.2.56.1 28/01/2026
* Fix line style support for links
* Fix cannot add IOS in preferences
* Fix cannot add IOU in preferences
* Upgrade dependencies
* Drop Python 3.8 support
* Display a warning if a SVG image format isn't supported
* Fix error in profile selection window after PyQt6 migration
## 2.2.56 21/01/2026
* Fixing tab name in MobaXterm
* PyQt6 migration
* Add XDG Config Home support
## 2.2.55 19/11/2025
* Fix SyntaxWarning: invalid escape sequence. Fixes #3760
* Support for Python 3.14
* Clicking the "console connect to all nodes" opens all consoles in name order with case-insensitively
## 2.2.54 21/04/2025
* Replace "Docker hub" by "Docker repository" because it is possible to use different repositories
* Upgrade dependencies
* Fix bring console in front when clicking on "Open all consoles". Fixes #3706
* Add -F arg to wmctrl. Ref #3706
## 2.2.53 21/01/2025
* Update file browser filters for all files and IOU images
* Upgrade dependencies
* Fix Linux Mint default terminal configuration
## 2.2.52 02/12/2024
* Add iol extension filter. Ref #3664
* Remove maximum 64GB RAM limitation for QEMU VMs. Fixes #3658
* Bring to front support for consoles on Linux.
* Relax setuptools requirement to allow for easier Debian packaging on Ubuntu Focal & Jammy
## 2.2.51 07/11/2024
* Python 3.13 support
* Upgrade dependencies
* Add keyboard shortcut for Add Link
## 2.2.50 21/10/2024
* Fix issue when pid file contains invalid data
* Add comment to indicate sentry-sdk is optional. Ref https://github.com/GNS3/gns3-server/issues/2423
* Improve information provided when uploading invalid appliance image. Fixes #3637
* Use "experimental features" option to force listening for HTTP notification streams. Ref #3579
* Fix to allow packet capture on more than 6 links. Fixes #3594
* Support for configuring MAC address in Docker containers
* Add KRDC to pre-configured VNC console commands
## 2.2.49 06/08/2024
* Upgrade jsonschema and sentry-sdk packages
* Upgrade to PyQt5 v5.15.11
* Add shortcuts info dialog
* Added Key Shortcuts
## 2.2.48.1 12/07/2024
* No changes
## 2.2.48 08/07/2024
* Use "experimental features" to allow bypassing hostname validation. Ref #3524
* Update appliance_v8.json. Ref https://github.com/GNS3/gns3-registry/pull/897
* Option to keep the compute IDs unchanged when exporting a project
* Upgrade sentry-sdk and psutil packages
* Switch to PyQt5 5.15.10 for macOS build
## 2.2.47 15/05/2024
* Remove maximum size for capture dialog. Ref #3576
* Change sentry-sdk version
* Upgrade aiohttp, sentry-sdk and truststore
* Upgrade jsonschema and aiohttp
* Drop Python 3.7
* Remove dev requirements for Python 3.6
* Add NAT symbols
* Only show log message if event has "message"
## 2.2.46 26/02/2024
* Add GNS3 console command "env" to show what environment variables are used. Ref https://github.com/GNS3/gns3-server/issues/2306
* Add CTRL+C shortcut to copy status bar message. Ref #3561
* Key modifier (ALT) to ignore snap to grid. Fixes #3538
* Increase timeout to 5s for status bar messages. The coordinates message has no timeout and can be reset when clicking on the scene. Ref #3561
* Add reset GUI state feature. Ref #3549
* Fix for hiding Windows terminal. Ref #3290
* Drop support for Python 3.6
* Upgrade sentry-sdk, psutil and distro dependencies
## 2.2.45 12/01/2024
* Add missing console_type values in appliance_v8.json. Ref https://github.com/GNS3/gns3-registry/issues/849
* Handle moved project notifications on controller stream
* Add debug for PATH env variable
* Add custom executable paths on Windows
* Add --suppressApplicationTitle for Windows terminal. Fixes https://github.com/GNS3/gns3-gui/issues/3544
* Upgrade sentry-sdk and aiohttp
## 2.2.44.1 07/11/2023
* No changes
## 2.2.44 06/11/2023
* Fix timeout issue when creating Qemu disk image. Fixes https://github.com/GNS3/gns3-server/issues/2313
* Refactor command variables support
* Add vendor_logo_url in appliance schemas. Ref https://github.com/GNS3/gns3-registry/pull/825
* Add Qemu IGB network device
* Add the ability to edit width and height in the style edit dialog.
## 2.2.43 19/09/2023
* Add KiTTY to preconfigured telnet consoles. Fixes #3507
* Fix generic icon in Wayland. Ref #3501
* Support for appliance format version 8.
* Use importlib instead of pkg_resources
* Upgrade to PyQt 5.15.9 and pywin32
* Add support for appliance version 8 format
## 2.2.42 09/08/2023
* Use the system's certificate store for SSL connections
* Give a node some time to start before opening the console (for console auto start). Fixes #3474
* Use Mate Terminal by default if installed on Debian, Ubuntu and Linux Mint.
* Support for gnome-terminal tabs to be opened in the same window.
* Remove import urllib3 and let sentry_sdk import and patch it. Fixes https://github.com/GNS3/gns3-gui/issues/3498
* Add import sys in sudo.py
* Rounded Rectangle support
## 2.2.41 12/07/2023
* Use alternative method to set the correct permissions for uBridge on macOS
* Remove sending stats to GA
* Catch urllib3 exceptions when sending crash report. Ref https://github.com/GNS3/gns3-gui/issues/3483
* Backport UEFI boot mode support for Qemu VMs
* Add debug for dropEvent. Ref https://github.com/GNS3/gns3-server/issues/2242
## 2.2.40.1 10/06/2023
* No changes
## 2.2.40 06/06/2023
* Change log messages for Websocket errors
* Do not proceed if an appliance symbol cannot be downloaded. Ref #3466
* Delete a node or link from topology summary view using Delete key. Ref #3445
* Fix "Start the capture visualization program" checkbox works only one (first) time for a given link. Fixes #3442
* Let the selected link style applied when editing a link. Fixes #3460
* Fix hovered color shown in style editing dialog. Fixes #3460
## 2.2.39 08/05/2023
* Fix nodes are not snapped to the grid at the moment of creation
* Upgrade distro and aiohttp dependencies
## 2.2.38 28/02/2023
* Add long description content type in setup.py
* Automatically add new issues to GNS3 project
* Development 2.2.38.dev1
## 2.2.37 25/01/2023
* Upgrade to PyQt5 v5.15.7
* Changed Windows Terminal telnet console profile from OS X to windows ref: issue #3193
## 2.2.36 04/01/2023
* Add Trusted Platform Module (TPM) support for Qemu VMs
* Add "on_close" setting to appliance schema. Fixes https://github.com/GNS3/gns3-server/issues/2148
* Add default 'ide' disk interface when manually creating Qemu VM template. Fixes #3360
* Fix zoom factor is multiplied when loading projects. Fixes #3408
* Remove deprecated PuTTY option in preferences. Ref https://github.com/GNS3/gns3-gui/discussions/3415
## 2.2.35.1 10/11/2022
* Re-release Web-Ui v2.2.35
## 2.2.35 08/11/2022
* Fix "variables": [] in project file leads to unlimited increase of empty name/value pairs in GUI. Fixes #3397
* Make version PEP 440 compliant
* Support for Python 3.11
* Upgrade PyQt to 5.15.7 and pywin32 to v305
* Allow for more dependency versions at patch level
* Replace deprecated distro.linux_distribution() call
* Add a fix for the CVE-2007-4559
## 2.2.34 28/08/2022
* Upgrade dev dependencies
* Implement new option (Delete All) to contextual menu in "Console" dock. Fixes #3325
* Fix 2560x1440 resolution for Docker container
* 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

View File

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

View File

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

View File

@@ -1,62 +1,46 @@
GNS3-gui
========
# GNS3-gui
[![image](https://github.com/GNS3/gns3-gui/workflows/testing/badge.svg)](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)](https://pypi.python.org/pypi/gns3-gui)
## Installation
[![image](https://snyk.io/test/github/GNS3/gns3-gui/badge.svg)](https://snyk.io/test/github/GNS3/gns3-gui)
Please see the documentation on our [website](https://docs.gns3.com)
GNS3 GUI repository.
## Software dependencies
Installation
------------
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>)
Please see <https://docs.gns3.com/>
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.
Software dependencies
---------------------
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.
PyQt6 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)
## Development
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.
If you want to update the interface, modify the .ui files using QT tools. And:
For using packet captures within GNS3, Wireshark should be installed.
It's recommended, but if you don't need that functionality you can go
without it.
Development
-----------
If you want to update the interface, modify the .ui files using QT
tools. And:
``` {.bash}
cd scripts
```shell
cd scripts
python build_pyqt.py
```
### Debug
### Debugging
If you want to see the full logs in the internal shell you can type:
``` {.bash}
```shell
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>
Due to the fact PyQT intercept you can use a web debugger for inspecting stuff:
https://github.com/Kozea/wdb
## Security issues
Please contact us at security@gns3.net
Security issues
---------------
Please contact us at <security@gns3.net>

View File

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

19
appveyor.yml Normal file
View File

@@ -0,0 +1,19 @@
version: '{build}-{branch}'
image: Visual Studio 2017
platform: x64
environment:
PYTHON: "C:\\Python36-x64"
DISTUTILS_USE_SDK: "1"
install:
- cinst nmap
- "%PYTHON%\\python.exe -m pip install -r dev-requirements.txt"
- "%PYTHON%\\python.exe -m pip install -r win-requirements.txt"
build: off
test_script:
- "%PYTHON%\\python.exe -m pytest -v"

View File

@@ -1,5 +1,5 @@
-rrequirements.txt
-r requirements.txt
pytest==8.4.2; python_version == '3.9' # version 8.4.2 is the last one supporting Python 3.9
pytest==9.0.2; python_version >= '3.10'
pytest-timeout==2.4.0
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

@@ -27,15 +27,24 @@ log = logging.getLogger(__name__)
class Application(QtWidgets.QApplication):
file_open_signal = QtCore.Signal(str)
file_open_signal = QtCore.pyqtSignal(str)
def __init__(self, argv):
def __init__(self, argv, hdpi=True):
self.setStyle(QtWidgets.QStyleFactory.create("Fusion"))
super().__init__(argv)
# both Qt and PyQt must be version >= 5.6 in order to enable high DPI scaling
if parse_version(QtCore.QT_VERSION_STR) >= parse_version("5.6") and parse_version(QtCore.PYQT_VERSION_STR) >= parse_version("5.6"):
# only available starting Qt version 5.6
if hdpi:
if sys.platform.startswith("linux"):
log.warning("HDPI mode is enabled. HDPI support on Linux is not fully stable and GNS3 may crash depending of your version of Linux. To disabled HDPI mode please edit ~/.config/GNS3/gns3_gui.conf and set 'hdpi' to 'false'")
self.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
self.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps)
else:
log.info("HDPI mode is disabled")
self.setAttribute(QtCore.Qt.AA_DisableHighDpiScaling)
# this is tell Wayland what is the name of the desktop file (gns3.desktop)
self.setDesktopFileName("gns3")
super().__init__(argv)
# this info is necessary for QSettings
self.setOrganizationName("GNS3")
@@ -48,7 +57,7 @@ class Application(QtWidgets.QApplication):
self.open_file_at_startup = None
def event(self, event):
# When you double click on a file, you receive an event
# When you double click file you receive an event
# and not the file as command line parameter
if sys.platform.startswith("darwin"):
if isinstance(event, QtGui.QFileOpenEvent):

View File

@@ -251,7 +251,7 @@ class BaseNode(QtCore.QObject):
return self._ports
def controllerHttpPost(self, path, callback, body=None, context=None, **kwargs):
def controllerHttpPost(self, path, callback, body={}, context={}, **kwargs):
"""
POST on current server / project
@@ -263,7 +263,7 @@ class BaseNode(QtCore.QObject):
self._project.post(path, callback, body=body, context=context, **kwargs)
def controllerHttpPut(self, path, callback, body=None, context=None, **kwargs):
def controllerHttpPut(self, path, callback, body={}, context={}, **kwargs):
"""
PUT on current server / project
@@ -275,7 +275,7 @@ class BaseNode(QtCore.QObject):
self._project.put(path, callback, body=body, context=context, **kwargs)
def controllerHttpGet(self, path, callback, context=None, **kwargs):
def controllerHttpGet(self, path, callback, context={}, **kwargs):
"""
Get on current server / project
@@ -287,7 +287,7 @@ class BaseNode(QtCore.QObject):
self._project.get(path, callback, context=context, **kwargs)
def controllerHttpDelete(self, path, callback, context=None, **kwargs):
def controllerHttpDelete(self, path, callback, context={}, **kwargs):
"""
Delete on current server / project

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:
@@ -82,7 +97,7 @@ class ComputeItem(QtWidgets.QTreeWidgetItem):
self._status = "stopped"
self.setToolTip(0, "{} is stopped or cannot be reached".format(self._compute.name()))
self.setIcon(0, QtGui.QIcon(':/icons/led_red.svg'))
self._parent.sortItems(0, QtCore.Qt.SortOrder.AscendingOrder)
self._parent.sortItems(0, QtCore.Qt.AscendingOrder)
# add nodes belonging to this compute
self.takeChildren()
@@ -98,7 +113,7 @@ class ComputeItem(QtWidgets.QTreeWidgetItem):
else:
item.setIcon(0, QtGui.QIcon(':/icons/led_red.svg'))
self.addChild(item)
self.sortChildren(0, QtCore.Qt.SortOrder.AscendingOrder)
self.sortChildren(0, QtCore.Qt.AscendingOrder)
class ComputeSummaryView(QtWidgets.QTreeWidget):
@@ -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

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

View File

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

View File

@@ -21,13 +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 gns3.local_config import LocalConfig
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__)
@@ -42,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):
@@ -50,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):
@@ -73,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):
"""
@@ -104,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):
@@ -125,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
@@ -132,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.WindowModality.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.Icon.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:
@@ -182,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):
"""
@@ -227,31 +234,13 @@ class Controller(QtCore.QObject):
return compute_id
return compute_id
def getEndpoint(self, path, compute_id, *args, **kwargs):
def request(self, method, path, *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):
"""
Forward the query to the HTTP client or controller depending on the path
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():
@@ -284,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
@@ -302,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
@@ -366,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):
@@ -409,35 +404,39 @@ 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") or LocalConfig.instance().experimental():
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)
url = self._http_client.url() + '/notifications'
log.info("Listening for controller notifications on '{}'".format(url))
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().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)
if parse_version(QtCore.QT_VERSION_STR) < parse_version("6.5.0"):
self._notification_stream.error.connect(self._websocket_error)
else:
self._notification_stream.errorOccurred.connect(self._websocket_error)
self._notification_stream.error.connect(self._websocket_error)
self._notification_stream.sslErrors.connect(self._sslErrorsSlot)
log.info("Listening for controller notifications on '{}'".format(self._notification_stream.requestUrl().toString()))
def stopListenNotifications(self):
if self._notification_stream:
@@ -445,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):
"""
@@ -458,9 +456,8 @@ class Controller(QtCore.QObject):
@qslot
def _websocket_error(self, error):
if self._notification_stream:
log.error("Websocket controller notification stream error: {}".format(self._notification_stream.errorString()))
self._notification_stream = None
self._startListenNotifications()
log.error("Websocket notification stream error: {}".format(self._notification_stream.errorString()))
self.stopListenNotifications()
@qslot
def _sslErrorsSlot(self, ssl_errors):
@@ -488,21 +485,11 @@ class Controller(QtCore.QObject):
elif result["action"] == "compute.created" or result["action"] == "compute.updated":
from .compute_manager import ComputeManager
ComputeManager.instance().computeDataReceivedCallback(result["event"])
elif result["action"] == "project.closed":
from .topology import Topology
project = Topology.instance().project()
if project and project.id() == result["event"]["project_id"]:
Topology.instance().setProject(None)
elif result["action"] == "project.updated":
from .topology import Topology
project = Topology.instance().project()
if project and project.id() == result["event"]["project_id"]:
project.projectUpdatedCallback(result["event"])
elif result["action"] == "log.error" and result["event"].get("message"):
log.error(result["event"].get("message"))
elif result["action"] == "log.warning" and result["event"].get("message"):
log.warning(result["event"].get("message"))
elif result["action"] == "log.info" and result["event"].get("message"):
log.info(result["event"].get("message"), extra={"show": True})
elif result["action"] == "log.error":
log.error(result["event"]["message"])
elif result["action"] == "log.warning":
log.warning(result["event"]["message"])
elif result["action"] == "log.info":
log.info(result["event"]["message"], extra={"show": True})
elif result["action"] == "ping":
pass

View File

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

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,13 +75,19 @@ 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):
images_directories.append(emulator_images_dir)
images_directories.append(os.path.dirname(self._path))
download_directory = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.StandardLocation.DownloadLocation)
download_directory = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DownloadLocation)
if download_directory != "" and download_directory != os.path.dirname(self._path):
images_directories.append(download_directory)
@@ -94,17 +100,12 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
self.setWindowTitle("Install {} appliance".format(self._appliance["name"]))
# add a custom button to show appliance information
if self._appliance["registry_version"] < 8:
# FIXME: show appliance info for v8
self.setButtonText(QtWidgets.QWizard.WizardButton.CustomButton1, "&Appliance info")
self.setOption(QtWidgets.QWizard.WizardOption.HaveCustomButton1, True)
self.customButtonClicked.connect(self._showApplianceInfoSlot)
self.setButtonText(QtWidgets.QWizard.CustomButton1, "&Appliance info")
self.setOption(QtWidgets.QWizard.HaveCustomButton1, True)
self.customButtonClicked.connect(self._showApplianceInfoSlot)
# customize the server selection
self.uiRemoteRadioButton.toggled.connect(self._remoteServerToggledSlot)
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")
@@ -138,20 +139,19 @@ 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.WizardPixmap.LogoPixmap, QtGui.QPixmap(symbol))
self.page(page_id).setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(symbol))
if self.page(page_id) == self.uiServerWizardPage:
Controller.instance().getSymbols(self._getSymbolsCallback)
template_type = self._appliance.template_type()
if not template_type:
raise ApplianceError("No template type found for appliance {}".format(self._appliance["name"]))
is_mac = ComputeManager.instance().localPlatform().startswith("darwin")
is_win = ComputeManager.instance().localPlatform().startswith("win")
self.uiRemoteServersComboBox.clear()
if len(ComputeManager.instance().remoteComputes()) == 0:
self.uiRemoteRadioButton.setEnabled(False)
@@ -160,94 +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 template_type == "qemu":
# disallow usage of the local server because Qemu has issues on OSX and Windows
if not LocalConfig.instance().experimental():
self.uiLocalRadioButton.setEnabled(False)
elif template_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.template_type(), self._compute_id)
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.template_properties().get('kvm', 'require') == 'require':
self._server_check = False
Qemu.instance().getQemuCapabilitiesFromServer(self._compute_id, qpartial(self._qemuServerCapabilitiesCallback))
else:
self._server_check = True
if self._appliance["registry_version"] >= 8:
qemu_platform = self._appliance.template_properties()["platform"]
else:
qemu_platform = self._appliance.template_properties()["arch"]
Qemu.instance().getQemuBinariesFromServer(self._compute_id, qpartial(self._getQemuBinariesFromServerCallback), [qemu_platform])
elif self.page(page_id) == self.uiInstructionsPage:
installation_instructions = self._appliance.get("installation_instructions", "No installation instructions available")
self.uiInstructionsTextEdit.setText(installation_instructions.strip())
elif self.page(page_id) == self.uiUsageWizardPage:
# TODO: allow taking these info fields at the version level in v8
category = self._appliance["category"].replace("_", " ")
usage = self._appliance.get("usage", "No usage information available")
if self._appliance["registry_version"] >= 8:
default_username = self._appliance.get("default_username")
default_password = self._appliance.get("default_password")
if default_username and default_password:
usage += "\n\nDefault username: {}\nDefault password: {}".format(default_username, default_password)
usage_info = """
The template will be available in the {} category.
Usage: {}
""".format(category, usage)
self.uiUsageTextEdit.setText(usage_info.strip())
def _qemuServerCapabilitiesCallback(self, result, error=None, *args, **kwargs):
"""
Check if the server supports KVM or not
"""
if self._appliance["registry_version"] >= 8:
qemu_platform = self._appliance.template_properties()["platform"]
else:
qemu_platform = self._appliance.template_properties()["arch"]
if error is None and "kvm" in result and qemu_platform in result["kvm"]:
self._server_check = True
else:
if error:
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
self.uiUsageTextEdit.setText("The template will be available in the {} category.\n\n{}".format(self._appliance["category"].replace("_", " "), self._appliance.get("usage", "")))
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:
@@ -257,7 +190,7 @@ Usage: {}
log.error("Error while uploading image '{}': {}".format(image_path, result["message"]))
else:
log.info("Image '{}' has been successfully uploaded".format(image_path))
self._registry.getRemoteImageList(self._appliance.template_type(), self._compute_id)
self._registry.getRemoteImageList(self._appliance.emulator(), self._compute_id)
def _showApplianceInfoSlot(self):
"""
@@ -327,10 +260,10 @@ Usage: {}
msgbox = QtWidgets.QMessageBox(self)
msgbox.setWindowTitle("Appliance information")
msgbox.setStyleSheet("QLabel{min-width: 600px;}") # TODO: resize details box QTextEdit{min-height: 500px;}
msgbox.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding)
msgbox.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
msgbox.setText(text_info)
msgbox.setDetailedText(self._appliance["description"])
msgbox.exec()
msgbox.exec_()
@qslot
def _refreshVersions(self, *args):
@@ -368,7 +301,7 @@ Usage: {}
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")))
@@ -377,9 +310,9 @@ Usage: {}
image_widget.setToolTip(2, image["path"])
# Associated data stored are col 0: version, col 1: image
image_widget.setData(0, QtCore.Qt.ItemDataRole.UserRole, version)
image_widget.setData(1, QtCore.Qt.ItemDataRole.UserRole, image)
image_widget.setData(2, QtCore.Qt.ItemDataRole.UserRole, self._appliance)
image_widget.setData(0, QtCore.Qt.UserRole, version)
image_widget.setData(1, QtCore.Qt.UserRole, image)
image_widget.setData(2, QtCore.Qt.UserRole, self._appliance)
top.addChild(image_widget)
font = top.font(0)
@@ -393,10 +326,10 @@ Usage: {}
expand = False
top.setForeground(2, QtGui.QBrush(QtGui.QColor("green")))
top.setData(1, QtCore.Qt.ItemDataRole.DisplayRole, human_filesize(size))
top.setData(2, QtCore.Qt.ItemDataRole.DisplayRole, status)
top.setData(0, QtCore.Qt.ItemDataRole.UserRole, version)
top.setData(2, QtCore.Qt.ItemDataRole.UserRole, self._appliance)
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)
self.uiApplianceVersionTreeWidget.addTopLevelItem(top)
if expand:
top.setExpanded(True)
@@ -428,7 +361,7 @@ Usage: {}
for version in self._appliance["versions"]:
for image in version["images"].values():
img = self._registry.search_image_file(self._appliance.template_type(),
img = self._registry.search_image_file(self._appliance.emulator(),
image["filename"],
image.get("md5sum"),
image.get("filesize"),
@@ -459,7 +392,7 @@ Usage: {}
if current is None or sip.isdeleted(current):
return
image = current.data(1, QtCore.Qt.ItemDataRole.UserRole)
image = current.data(1, QtCore.Qt.UserRole)
if image is not None:
if "direct_download_url" in image or "download_url" in image:
self.uiDownloadPushButton.show()
@@ -480,7 +413,7 @@ Usage: {}
if current is None or sip.isdeleted(current):
return
data = current.data(1, QtCore.Qt.ItemDataRole.UserRole)
data = current.data(1, QtCore.Qt.UserRole)
if data is not None:
if "direct_download_url" in data:
QtGui.QDesktopServices.openUrl(QtCore.QUrl(data["direct_download_url"]))
@@ -500,16 +433,16 @@ Usage: {}
if current is None:
QtWidgets.QMessageBox.critical(self.parent(), "Base version", "Please select a base version")
return
base_version = current.data(0, QtCore.Qt.ItemDataRole.UserRole)
base_version = current.data(0, QtCore.Qt.UserRole)
new_version_name, ok = QtWidgets.QInputDialog.getText(self, "Creating a new version", "Create a new version for this appliance.\nPlease share your experience on the GNS3 community if this version works.\n\nVersion name:", QtWidgets.QLineEdit.EchoMode.Normal, base_version.get("name"))
new_version_name, ok = QtWidgets.QInputDialog.getText(self, "Creating a new version", "Create a new version for this appliance.\nPlease share your experience on the GNS3 community if this version works.\n\nVersion name:", QtWidgets.QLineEdit.Normal, base_version.get("name"))
if ok:
new_version = {"name": new_version_name}
new_version["images"] = {}
for disk_type in base_version["images"]:
base_filename = base_version["images"][disk_type]["filename"]
filename, ok = QtWidgets.QInputDialog.getText(self, "Image", "Disk image filename for {}".format(disk_type), QtWidgets.QLineEdit.EchoMode.Normal, base_filename)
filename, ok = QtWidgets.QInputDialog.getText(self, "Image", "Disk image filename for {}".format(disk_type), QtWidgets.QLineEdit.Normal, base_filename)
if not ok:
filename = base_filename
new_version["images"][disk_type] = {"filename": filename, "version": new_version_name}
@@ -534,65 +467,27 @@ Usage: {}
current = self.uiApplianceVersionTreeWidget.currentItem()
if not current:
return
disk = current.data(1, QtCore.Qt.ItemDataRole.UserRole)
disk = current.data(1, QtCore.Qt.UserRole)
path, _ = QtWidgets.QFileDialog.getOpenFileName()
if len(path) == 0:
return
image = Image(self._appliance.template_type(), path, filename=disk["filename"])
image = Image(self._appliance.emulator(), path, filename=disk["filename"])
try:
if "md5sum" in disk and image.md5sum != disk["md5sum"]:
reply = QtWidgets.QMessageBox.question(
self,
"Add appliance",
"This is not the correct file.\n\n"
"MD5 checksum\n"
f"actual:\t{image.md5sum}\n"
f"expected:\t{disk['md5sum']}\n\n"
"File size\n"
f"actual:\t{image.filesize} bytes\n"
f"expected:\t{disk['filesize']} bytes\n\n"
"Do you want to accept it at your own risks?",
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No
)
if reply == QtWidgets.QMessageBox.StandardButton.No:
reply = QtWidgets.QMessageBox.question(self, "Add appliance",
"This is not the correct file. The MD5 sum is {} and should be {}.\nDo you want to accept it at your own risks?".format(image.md5sum, disk["md5sum"]),
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
return
except OSError as e:
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:
if self._appliance["registry_version"] >= 8:
qemu_platform = self._appliance.template_properties()["platform"]
else:
qemu_platform = self._appliance.template_properties()["arch"]
i = self.uiQemuListComboBox.findData(qemu_platform, flags=QtCore.Qt.MatchFlag.MatchEndsWith)
if i != -1:
self.uiQemuListComboBox.setCurrentIndex(i)
def _install(self, version):
"""
Install the appliance in GNS3
@@ -602,8 +497,8 @@ Usage: {}
if version is None:
appliance_configuration = self._appliance.copy()
if self._appliance.template_type() != "docker":
# only Docker do not have versions
if "docker" not in appliance_configuration:
# only Docker do not have version
return False
else:
try:
@@ -615,27 +510,19 @@ Usage: {}
template_manager = TemplateManager().instance()
while len(appliance_configuration["name"]) == 0 or not template_manager.is_name_available(appliance_configuration["name"]):
QtWidgets.QMessageBox.warning(self.parent(), "Add template", "The name \"{}\" is already used by another template".format(appliance_configuration["name"]))
appliance_configuration["name"], ok = QtWidgets.QInputDialog.getText(self.parent(), "Add template", "New name:", QtWidgets.QLineEdit.EchoMode.Normal, appliance_configuration["name"])
appliance_configuration["name"], ok = QtWidgets.QInputDialog.getText(self.parent(), "Add template", "New name:", QtWidgets.QLineEdit.Normal, appliance_configuration["name"])
if not ok:
return False
appliance_configuration["name"] = appliance_configuration["name"].strip()
if self._appliance["registry_version"] >= 8:
if "settings" in appliance_configuration:
for settings in appliance_configuration["settings"]:
if settings["template_type"] == "qemu":
settings["template_properties"]["path"] = self.uiQemuListComboBox.currentData()
elif "qemu" in appliance_configuration:
appliance_configuration["qemu"]["path"] = self.uiQemuListComboBox.currentData()
new_template = ApplianceToTemplate().new_template(appliance_configuration, self._compute_id, version, self._symbols, parent=self)
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
#worker = WaitForLambdaWorker(lambda: self._create_template(appliance_configuration, self._compute_id), allowed_exceptions=[ConfigException, OSError])
#progress_dialog = ProgressDialog(worker, "Add template", "Installing a new template...", None, busy=True, parent=self)
#progress_dialog.show()
#if progress_dialog.exec():
#if progress_dialog.exec_():
# QtWidgets.QMessageBox.information(self.parent(), "Add template", "{} template has been installed!".format(appliance_configuration["name"]))
# return True
#return False
@@ -643,7 +530,7 @@ Usage: {}
# worker = WaitForLambdaWorker(lambda: config.save(), allowed_exceptions=[ConfigException, OSError])
# progress_dialog = ProgressDialog(worker, "Add appliance", "Install the appliance...", None, busy=True, parent=self)
# progress_dialog.show()
# if progress_dialog.exec():
# if progress_dialog.exec_():
# QtWidgets.QMessageBox.information(self.parent(), "Add appliance", "{} installed!".format(appliance_configuration["name"]))
# return True
@@ -666,38 +553,22 @@ Usage: {}
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.template_type(), image["path"], filename=image["filename"])
image_upload_manager = ImageUploadManager(image, Controller.instance(), self._compute_id, self._applianceImageUploadedCallback, LocalConfig.instance().directFileUpload())
image_upload_manager.upload()
self._image_uploading_count += 1
def _applianceImageUploadedCallback(self, result, error=False, 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 = Image(self._appliance.emulator(), image["path"], filename=image["filename"])
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 self._appliance.template_type() == "docker":
if "docker" in self._appliance:
# skip Qemu binary selection and files pages if this is a Docker appliance
return super().nextId() + 3
elif self._appliance.template_type() != "qemu":
# skip the Qemu binary selection page if not a Qemu appliance
return super().nextId() + 1
if self.currentPage() == self.uiQemuWizardPage:
if not self._appliance.get("installation_instructions"):
# skip the installation instructions page if there are no instructions
return super().nextId() + 1
return super().nextId()
@@ -714,21 +585,21 @@ Usage: {}
current = self.uiApplianceVersionTreeWidget.currentItem()
if current is None or sip.isdeleted(current):
return False
version = current.data(0, QtCore.Qt.ItemDataRole.UserRole)
version = current.data(0, QtCore.Qt.UserRole)
if version is None:
return False
appliance = current.data(2, QtCore.Qt.ItemDataRole.UserRole)
appliance = current.data(2, QtCore.Qt.UserRole)
try:
self._appliance.search_images_for_version(version["name"])
except ApplianceError as e:
QtWidgets.QMessageBox.critical(self, "Appliance", "Cannot install {} version {}: {}".format(appliance["name"], version["name"], e))
return False
reply = QtWidgets.QMessageBox.question(self, "Appliance", "Would you like to install {} version {}?".format(appliance["name"], version["name"]),
QtWidgets.QMessageBox.StandardButton.Yes, QtWidgets.QMessageBox.StandardButton.No)
if reply == QtWidgets.QMessageBox.StandardButton.No:
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
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
@@ -740,7 +611,7 @@ Usage: {}
return False
current = self.uiApplianceVersionTreeWidget.currentItem()
if current:
version = current.data(0, QtCore.Qt.ItemDataRole.UserRole)
version = current.data(0, QtCore.Qt.UserRole)
return self._install(version["name"])
else:
return self._install(None)
@@ -753,39 +624,17 @@ Usage: {}
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")):
if "qemu" in self._appliance:
reply = QtWidgets.QMessageBox.question(self, "Appliance", "Qemu on Windows and macOS is not supported by the GNS3 team. Do you want to continue?", QtWidgets.QMessageBox.StandardButton.Yes, QtWidgets.QMessageBox.StandardButton.No)
if reply == QtWidgets.QMessageBox.StandardButton.No:
reply = QtWidgets.QMessageBox.question(self, "Appliance", "Qemu on Windows and macOS is not supported by the GNS3 team. Do you want to continue?", QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
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):
"""
@@ -821,8 +670,8 @@ Usage: {}
reply = QtWidgets.QMessageBox.question(self, "Custom files",
"This option allows files with different MD5 checksums. This feature is only for advanced users and can lead "
"to unexpected problems. Do you want to proceed?",
QtWidgets.QMessageBox.StandardButton.Yes, QtWidgets.QMessageBox.StandardButton.No)
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.StandardButton.No:
if reply == QtWidgets.QMessageBox.No:
self.allowCustomFiles.setChecked(False)
return False

View File

@@ -24,15 +24,15 @@ log = logging.getLogger(__name__)
class CaptureDialog(QtWidgets.QDialog, Ui_CaptureDialog):
"""
This dialog allow to configure the packet capture
This dialog allow configure the packet capture
"""
def __init__(self, parent, file_name, auto_start, ethernet_link=True):
super().__init__(parent)
self.setupUi(self)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).clicked.connect(self._okButtonClickedSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Cancel).clicked.connect(self.reject)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(self._okButtonClickedSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Cancel).clicked.connect(self.reject)
if ethernet_link:
self.uiDataLinkTypeComboBox.addItem("Ethernet", "DLT_EN10MB")
@@ -70,6 +70,6 @@ if __name__ == '__main__':
main = QtWidgets.QMainWindow()
dialog = CaptureDialog(main, "test.pcap")
dialog.show()
exit_code = app.exec()
exit_code = app.exec_()
print(dialog.dataLink())
print(dialog.fileName())

View File

@@ -59,7 +59,7 @@ class ConfigurationDialog(QtWidgets.QDialog, Ui_configurationDialog):
:param button: button that was clicked (QAbstractButton)
"""
if button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Cancel):
if button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Cancel):
QtWidgets.QDialog.reject(self)
else:
try:

View File

@@ -93,7 +93,7 @@ class ConsoleCommandDialog(QtWidgets.QDialog, Ui_uiConsoleCommandDialog):
"""
Save a custom command to the list
"""
name, ok = QtWidgets.QInputDialog.getText(self, "Add a command", "Command name:", QtWidgets.QLineEdit.EchoMode.Normal)
name, ok = QtWidgets.QInputDialog.getText(self, "Add a command", "Command name:", QtWidgets.QLineEdit.Normal)
command = self.uiCommandPlainTextEdit.toPlainText().strip()
if ok and len(command) > 0:
if command not in self._consoles.values():
@@ -123,7 +123,7 @@ class ConsoleCommandDialog(QtWidgets.QDialog, Ui_uiConsoleCommandDialog):
def getCommand(parent, console_type="telnet", current=None):
dialog = ConsoleCommandDialog(parent, console_type=console_type, current=current)
dialog.show()
if dialog.exec():
if dialog.exec_():
return True, dialog.uiCommandPlainTextEdit.toPlainText().replace("\n", " ")
return False, None

View File

@@ -67,7 +67,7 @@ class CustomAdaptersConfigurationDialog(QtWidgets.QDialog, Ui_CustomAdaptersConf
self._custom_adapters = custom_adapters
self._base_mac_address = base_mac_address
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Reset).clicked.connect(self._resetSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Reset).clicked.connect(self._resetSlot)
if self._default_adapter_type and self._adapter_types:
self.uiAdaptersTreeWidget.setColumnCount(3)
@@ -115,10 +115,10 @@ class CustomAdaptersConfigurationDialog(QtWidgets.QDialog, Ui_CustomAdaptersConf
adapter_number = 0
for port_name in self._ports:
item = TreeWidgetItem(self.uiAdaptersTreeWidget)
item.setFlags(item.flags() | QtCore.Qt.ItemFlag.ItemIsEditable)
item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
item.setText(0, "Adapter {}".format(adapter_number))
item.setData(0, QtCore.Qt.ItemDataRole.UserRole, adapter_number)
item.setData(1, QtCore.Qt.ItemDataRole.UserRole, port_name)
item.setData(0, QtCore.Qt.UserRole, adapter_number)
item.setData(1, QtCore.Qt.UserRole, port_name)
custom_adapter = self._getCustomAdapterSettings(adapter_number)
item.setText(1, custom_adapter.get("port_name", port_name))
@@ -131,7 +131,7 @@ class CustomAdaptersConfigurationDialog(QtWidgets.QDialog, Ui_CustomAdaptersConf
index = 0
for adapter_type, adapter_description in self._adapter_types.items():
combobox.addItem("{}".format(adapter_type))
combobox.setItemData(index, adapter_description, QtCore.Qt.ItemDataRole.ToolTipRole)
combobox.setItemData(index, adapter_description, QtCore.Qt.ToolTipRole)
index += 1
adapter_type_index = combobox.findText(custom_adapter.get("adapter_type", self._default_adapter_type))
combobox.setCurrentIndex(adapter_type_index)
@@ -147,7 +147,7 @@ class CustomAdaptersConfigurationDialog(QtWidgets.QDialog, Ui_CustomAdaptersConf
adapter_number += 1
self.uiAdaptersTreeWidget.setItemDelegateForColumn(0, NoEditDelegate(self))
self.uiAdaptersTreeWidget.sortByColumn(0, QtCore.Qt.SortOrder.AscendingOrder)
self.uiAdaptersTreeWidget.sortByColumn(0, QtCore.Qt.AscendingOrder)
self.uiAdaptersTreeWidget.setSortingEnabled(True)
for column in range(self.uiAdaptersTreeWidget.columnCount()):
@@ -166,9 +166,9 @@ class CustomAdaptersConfigurationDialog(QtWidgets.QDialog, Ui_CustomAdaptersConf
custom_adapter_settings = {}
item = self.uiAdaptersTreeWidget.topLevelItem(row)
port_name = item.text(1)
adapter_number = item.data(0, QtCore.Qt.ItemDataRole.UserRole)
adapter_number = item.data(0, QtCore.Qt.UserRole)
custom_adapter_settings["adapter_number"] = adapter_number
original_port_name = item.data(1, QtCore.Qt.ItemDataRole.UserRole)
original_port_name = item.data(1, QtCore.Qt.UserRole)
if not port_name:
QtWidgets.QMessageBox.critical(self, "Port name", "Port name cannot be empty for adapter {}".format(adapter_number))
return False

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
@@ -223,4 +179,4 @@ if __name__ == '__main__':
main = QtWidgets.QMainWindow()
dialog = DoctorDialog(main, console=True)
# dialog.show()
#exit_code = app.exec()
#exit_code = app.exec_()

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()
@@ -121,4 +90,4 @@ if __name__ == '__main__':
main = QtWidgets.QMainWindow()
dialog = EditComputeDialog(main)
dialog.show()
exit_code = app.exec()
exit_code = app.exec_()

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
@@ -39,18 +41,55 @@ class EditProjectDialog(QtWidgets.QDialog, Ui_EditProjectDialog):
self.uiNodeGridSizeSpinBox.setValue(self._project.nodeGridSize())
self.uiDrawingGridSizeSpinBox.setValue(self._project.drawingGridSize())
self.uiGlobalVariablesGrid.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
self.uiGlobalVariablesGrid.setAlignment(QtCore.Qt.AlignTop)
self.uiNewVarButton = QtWidgets.QPushButton('Add new variable', self)
self.uiNewVarButton.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed)
self.uiNewVarButton.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
self.uiNewVarButton.clicked.connect(self.onAddNewVariable)
self.uiGlobalVariablesGrid.addWidget(self.uiNewVarButton, 0, 3, QtCore.Qt.AlignmentFlag.AlignRight)
self.uiGlobalVariablesGrid.addWidget(self.uiNewVarButton, 0, 3, QtCore.Qt.AlignRight)
self._variables = self._project.variables()
if not self._variables:
self._variables = [{"name": "", "value": ""}]
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()
if variables is not None:
variables.append(new_variable)
else:
variables = [new_variable]
return variables
def updateGlobalVariables(self):
while True:
item = self.uiGlobalVariablesGrid.takeAt(1)
@@ -115,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

@@ -48,8 +48,8 @@ class FileEditorDialog(QtWidgets.QDialog, Ui_FileEditorDialog):
self.setWindowTitle(target.name() + " " + os.path.basename(path))
self.uiRefreshButton.pressed.connect(self._refreshSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save).clicked.connect(self._okButtonClickedSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Cancel).clicked.connect(self.reject)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Save).clicked.connect(self._okButtonClickedSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Cancel).clicked.connect(self.reject)
self._refreshSlot()
@@ -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

@@ -38,9 +38,9 @@ class FilterDialog(QtWidgets.QDialog, Ui_FilterDialog):
self._link.listAvailableFilters(self._listAvailableFiltersCallback)
self._initialized = False
self._filter_items = {}
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).clicked.connect(self._applyPreferencesSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Help).clicked.connect(self._helpSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Reset).clicked.connect(self._resetSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self._applyPreferencesSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Help).clicked.connect(self._helpSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Reset).clicked.connect(self._resetSlot)
def _listAvailableFiltersCallback(self, result, error=False, *args, **kwargs):
if error:
@@ -91,7 +91,7 @@ class FilterDialog(QtWidgets.QDialog, Ui_FilterDialog):
spinBox.setMinimum(param["minimum"])
spinBox.setMaximum(param["maximum"])
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(spinBox.sizePolicy().hasHeightForWidth())
@@ -112,7 +112,7 @@ class FilterDialog(QtWidgets.QDialog, Ui_FilterDialog):
textEdit = QtWidgets.QTextEdit()
textEdit.setAcceptRichText(False)
filter["textEdits"].append(textEdit)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
textEdit.setMinimumWidth(300)
@@ -128,7 +128,7 @@ class FilterDialog(QtWidgets.QDialog, Ui_FilterDialog):
line += 1
spacerItem = QtWidgets.QSpacerItem(1, 1, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
spacerItem = QtWidgets.QSpacerItem(1, 1, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
gridLayout.addItem(spacerItem, line, 0, 1, 1)
vlayout.addLayout(gridLayout)
tab.setLayout(vlayout)

View File

@@ -33,8 +33,8 @@ class IdlePCDialog(QtWidgets.QDialog, Ui_IdlePCDialog):
super().__init__(parent)
self.setupUi(self)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).clicked.connect(self._applySlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Help).clicked.connect(self._helpSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self._applySlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Help).clicked.connect(self._helpSlot)
self._router = router
self._idlepcs = idlepcs

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

@@ -18,9 +18,9 @@
import sys
import tempfile
import json
import sip
import os
from ..qt import sip
from gns3.qt import QtCore, QtWidgets, qpartial
from gns3.controller import Controller
from gns3.appliance_manager import ApplianceManager
@@ -41,16 +41,16 @@ class NewTemplateWizard(QtWidgets.QWizard, Ui_NewTemplateWizard):
super().__init__(parent)
self.setupUi(self)
self.setWizardStyle(QtWidgets.QWizard.WizardStyle.ModernStyle)
self.setWizardStyle(QtWidgets.QWizard.ModernStyle)
if sys.platform.startswith("darwin"):
# we want to see the cancel button on OSX
self.setOptions(QtWidgets.QWizard.WizardOption.NoDefaultButton)
self.setOptions(QtWidgets.QWizard.NoDefaultButton)
# add a custom button to show appliance information
self.setButtonText(QtWidgets.QWizard.WizardButton.CustomButton1, "&Update from online registry")
self.setOption(QtWidgets.QWizard.WizardOption.HaveCustomButton1, True)
self.setButtonText(QtWidgets.QWizard.CustomButton1, "&Update from online registry")
self.setOption(QtWidgets.QWizard.HaveCustomButton1, True)
self.customButtonClicked.connect(self._downloadAppliancesSlot)
self.button(QtWidgets.QWizard.WizardButton.CustomButton1).hide()
self.button(QtWidgets.QWizard.CustomButton1).hide()
self.uiFilterLineEdit.textChanged.connect(self._filterTextChangedSlot)
ApplianceManager.instance().appliances_changed_signal.connect(self._appliancesChangedSlot)
@@ -154,16 +154,16 @@ class NewTemplateWizard(QtWidgets.QWizard, Ui_NewTemplateWizard):
self.uiAppliancesTreeWidget.clear()
parent_routers = QtWidgets.QTreeWidgetItem(self.uiAppliancesTreeWidget)
parent_routers.setText(0, "Routers")
parent_routers.setFlags(parent_routers.flags() & ~QtCore.Qt.ItemFlag.ItemIsSelectable)
parent_routers.setFlags(parent_routers.flags() & ~QtCore.Qt.ItemIsSelectable)
parent_switches = QtWidgets.QTreeWidgetItem(self.uiAppliancesTreeWidget)
parent_switches.setText(0, "Switches")
parent_switches.setFlags(parent_switches.flags() & ~QtCore.Qt.ItemFlag.ItemIsSelectable)
parent_switches.setFlags(parent_switches.flags() & ~QtCore.Qt.ItemIsSelectable)
parent_guests = QtWidgets.QTreeWidgetItem(self.uiAppliancesTreeWidget)
parent_guests.setText(0, "Guests")
parent_guests.setFlags(parent_guests.flags() & ~QtCore.Qt.ItemFlag.ItemIsSelectable)
parent_guests.setFlags(parent_guests.flags() & ~QtCore.Qt.ItemIsSelectable)
parent_firewalls = QtWidgets.QTreeWidgetItem(self.uiAppliancesTreeWidget)
parent_firewalls.setText(0, "Firewalls")
parent_firewalls.setFlags(parent_firewalls.flags() & ~QtCore.Qt.ItemFlag.ItemIsSelectable)
parent_firewalls.setFlags(parent_firewalls.flags() & ~QtCore.Qt.ItemIsSelectable)
self.uiAppliancesTreeWidget.expandAll()
for appliance in ApplianceManager.instance().appliances():
@@ -200,14 +200,14 @@ class NewTemplateWizard(QtWidgets.QWizard, Ui_NewTemplateWizard):
item.setText(1, "N/A")
item.setText(2, appliance["vendor_name"])
item.setData(0, QtCore.Qt.ItemDataRole.UserRole, appliance)
item.setData(0, QtCore.Qt.UserRole, appliance)
#item.setSizeHint(0, QtCore.QSize(32, 32))
item.setToolTip(0, self._get_tooltip_text(appliance))
Controller.instance().getSymbolIcon(appliance.get("symbol"), qpartial(self._setItemIcon, item),
fallback=":/symbols/" + appliance["category"] + ".svg")
self.uiAppliancesTreeWidget.sortByColumn(0, QtCore.Qt.SortOrder.AscendingOrder)
self.uiAppliancesTreeWidget.sortByColumn(0, QtCore.Qt.AscendingOrder)
self.uiAppliancesTreeWidget.resizeColumnToContents(0)
if not appliance_filter:
self.uiAppliancesTreeWidget.collapseAll()
@@ -221,19 +221,19 @@ class NewTemplateWizard(QtWidgets.QWizard, Ui_NewTemplateWizard):
super().initializePage(page_id)
if self.page(page_id) == self.uiApplianceFromServerWizardPage:
self.button(QtWidgets.QWizard.WizardButton.CustomButton1).show()
self.setButtonText(QtWidgets.QWizard.WizardButton.FinishButton, "&Install")
self.button(QtWidgets.QWizard.CustomButton1).show()
self.setButtonText(QtWidgets.QWizard.FinishButton, "&Install")
self._get_appliances_from_server()
else:
self.button(QtWidgets.QWizard.WizardButton.CustomButton1).hide()
self.button(QtWidgets.QWizard.CustomButton1).hide()
def cleanupPage(self, page_id):
"""
Restore button default settings on the first page.
"""
self.button(QtWidgets.QWizard.WizardButton.CustomButton1).hide()
self.setButtonText(QtWidgets.QWizard.WizardButton.FinishButton, "&Finish")
self.button(QtWidgets.QWizard.CustomButton1).hide()
self.setButtonText(QtWidgets.QWizard.FinishButton, "&Finish")
super().cleanupPage(page_id)
def validateCurrentPage(self):
@@ -274,7 +274,7 @@ class NewTemplateWizard(QtWidgets.QWizard, Ui_NewTemplateWizard):
items = self.uiAppliancesTreeWidget.selectedItems()
for item in items:
f = tempfile.NamedTemporaryFile(mode="w+", suffix=".builtin.gns3a", delete=False)
json.dump(item.data(0, QtCore.Qt.ItemDataRole.UserRole), f)
json.dump(item.data(0, QtCore.Qt.UserRole), f)
f.close()
MainWindow.instance().loadPath(f.name)
try:

View File

@@ -40,8 +40,8 @@ class NodePropertiesDialog(QtWidgets.QDialog, Ui_NodePropertiesDialog):
self._node_items = node_items
self._parent_items = {}
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).setEnabled(False)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Reset).setEnabled(False)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).setEnabled(False)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Reset).setEnabled(False)
self.previousItem = None
self.previousPage = None
@@ -84,7 +84,7 @@ class NodePropertiesDialog(QtWidgets.QDialog, Ui_NodePropertiesDialog):
ConfigurationPageItem(self._parent_items[parent], node_item)
# sort the tree
self.uiNodesTreeWidget.sortByColumn(0, QtCore.Qt.SortOrder.AscendingOrder)
self.uiNodesTreeWidget.sortByColumn(0, QtCore.Qt.AscendingOrder)
if len(self._node_items) == 1:
parent = " {} group".format(str(node_item.node()))
@@ -135,19 +135,19 @@ class NodePropertiesDialog(QtWidgets.QDialog, Ui_NodePropertiesDialog):
self.uiConfigStackedWidget.setCurrentWidget(page)
if page != self.uiEmptyPageWidget:
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).setEnabled(True)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Reset).setEnabled(True)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Help).setEnabled(True)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).setEnabled(True)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Reset).setEnabled(True)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Help).setEnabled(True)
else:
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).setEnabled(False)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Reset).setEnabled(False)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Help).setEnabled(False)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).setEnabled(False)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Reset).setEnabled(False)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Help).setEnabled(False)
# hide the contextual help button if there is no help text
if page.whatsThis():
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Help).show()
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Help).show()
else:
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Help).hide()
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Help).hide()
def on_uiButtonBox_clicked(self, button):
"""
@@ -157,13 +157,13 @@ class NodePropertiesDialog(QtWidgets.QDialog, Ui_NodePropertiesDialog):
"""
try:
if button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply):
if button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply):
self.applySettings()
elif button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Reset):
elif button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Reset):
self.resetSettings()
elif button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Help):
elif button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Help):
self.showHelp()
elif button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Cancel):
elif button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Cancel):
QtWidgets.QDialog.reject(self)
else:
self.applySettings()

View File

@@ -54,12 +54,12 @@ class NotifDialog(QtWidgets.QWidget):
super().__init__(parent)
self._notifs = []
self.setWindowFlags(QtCore.Qt.WindowType.FramelessWindowHint |
QtCore.Qt.WindowType.WindowDoesNotAcceptFocus |
QtCore.Qt.WindowType.SubWindow)
# QtCore.Qt.WindowType.Tool)
# QtCore.Qt.WindowType.WindowStaysOnTopHint)
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_ShowWithoutActivating) # | QtCore.Qt.WidgetAttribute.WA_TranslucentBackground)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint |
QtCore.Qt.WindowDoesNotAcceptFocus |
QtCore.Qt.SubWindow)
# QtCore.Qt.Tool)
# QtCore.Qt.WindowStaysOnTopHint)
self.setAttribute(QtCore.Qt.WA_ShowWithoutActivating) # | QtCore.Qt.WA_TranslucentBackground)
self._layout = QtWidgets.QVBoxLayout()
@@ -70,7 +70,7 @@ class NotifDialog(QtWidgets.QWidget):
for i in range(0, MAX_ELEMENTS):
l = QtWidgets.QLabel()
l.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
l.setAlignment(QtCore.Qt.AlignTop)
l.setWordWrap(True)
l.hide()
self._layout.addWidget(l)
@@ -187,4 +187,4 @@ if __name__ == '__main__':
main.setMinimumWidth(600)
main.setMinimumHeight(600)
main.show()
exit_code = app.exec()
exit_code = app.exec_()

View File

@@ -19,9 +19,9 @@
Dialog to load module and built-in preference pages.
"""
from ..qt import QtGui, QtCore, QtWidgets
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
@@ -49,9 +49,8 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
# We adapt the max size to the screen resolution
# We need to manually do that otherwise on small screen the windows
# could be bigger than the screen instead of displaying scrollbars
geometry = QtGui.QGuiApplication.primaryScreen().geometry()
height = geometry.height() - 100
width = geometry.width() - 100
height = QtWidgets.QDesktopWidget().screenGeometry().height() - 100
width = QtWidgets.QDesktopWidget().screenGeometry().width() - 100
# 980 is the default width
if self.width() > width:
@@ -61,7 +60,7 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
self.resize(self.width(), height)
self.uiTreeWidget.currentItemChanged.connect(self._showPreferencesPageSlot)
self._applyButton = self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply)
self._applyButton = self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply)
self._applyButton.clicked.connect(self._applyPreferences)
self._applyButton.setEnabled(False)
self._applyButton.setStyleSheet("QPushButton:disabled {color: gray}")
@@ -74,7 +73,7 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
# set the maximum width based on the content of column 0
self.uiTreeWidget.setMaximumWidth(self.uiTreeWidget.sizeHintForColumn(0) + 10)
# Something has changed?
# Something has change?
self._modified_pages = set()
def _loadPreferencePages(self):
@@ -85,8 +84,8 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
# load built-in preference pages
pages = [
GeneralPreferencesPage,
ServerPreferencesPage,
GNS3VMPreferencesPage,
ControllerPreferencesPage,
#GNS3VMPreferencesPage,
PacketCapturePreferencesPage,
]
@@ -96,7 +95,7 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
name = preferences_page.windowTitle()
item = QtWidgets.QTreeWidgetItem(self.uiTreeWidget)
item.setText(0, name)
item.setData(0, QtCore.Qt.ItemDataRole.UserRole, preferences_page)
item.setData(0, QtCore.Qt.UserRole, preferences_page)
self.uiStackedWidget.addWidget(preferences_page)
self._items.append(item)
self._watchForChanges(preferences_page)
@@ -112,7 +111,7 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
name = preferences_page.windowTitle()
item = QtWidgets.QTreeWidgetItem(parent)
item.setText(0, name)
item.setData(0, QtCore.Qt.ItemDataRole.UserRole, preferences_page)
item.setData(0, QtCore.Qt.UserRole, preferences_page)
self.uiStackedWidget.addWidget(preferences_page)
self._items.append(item)
if cls is preference_pages[0]:
@@ -177,7 +176,7 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
if current is None:
current = previous
preferences_page = current.data(0, QtCore.Qt.ItemDataRole.UserRole)
preferences_page = current.data(0, QtCore.Qt.UserRole)
accessible_name = preferences_page.accessibleName()
if accessible_name:
self.uiTitleLabel.setText(accessible_name)
@@ -193,9 +192,9 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
for index in range(0, self.uiStackedWidget.count()):
page = self.uiStackedWidget.widget(index)
if self.uiStackedWidget.currentIndex() == index:
page.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding)
page.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
else:
page.setSizePolicy(QtWidgets.QSizePolicy.Policy.Ignored, QtWidgets.QSizePolicy.Policy.Ignored)
page.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored)
def _applyPreferences(self):
"""
@@ -224,9 +223,9 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
reply = QtWidgets.QMessageBox.warning(self,
"Preferences",
"You have unsaved preferences in {}.\n\nContinue without saving?".format(pages_title),
QtWidgets.QMessageBox.StandardButton.Yes,
QtWidgets.QMessageBox.StandardButton.No)
if reply == QtWidgets.QMessageBox.StandardButton.No:
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
return
QtWidgets.QDialog.reject(self)

View File

@@ -19,7 +19,7 @@ import os
import sys
import shutil
from gns3.qt import QtWidgets, QtGui
from gns3.qt import QtWidgets
from gns3.local_config import LocalConfig
from gns3.ui.profile_select_dialog_ui import Ui_ProfileSelectDialog
from gns3.version import __version_info__
@@ -46,7 +46,7 @@ class ProfileSelectDialog(QtWidgets.QDialog, Ui_ProfileSelectDialog):
self.uiDeletePushButton.clicked.connect(self._deletePushButtonSlot)
# Center on screen
screen = QtGui.QGuiApplication.primaryScreen().geometry()
screen = QtWidgets.QApplication.desktop().screenGeometry()
self.move(screen.center() - self.rect().center())
version = "{}.{}".format(__version_info__[0], __version_info__[1])
@@ -54,14 +54,8 @@ class ProfileSelectDialog(QtWidgets.QDialog, Ui_ProfileSelectDialog):
appdata = os.path.expandvars("%APPDATA%")
path = os.path.join(appdata, "GNS3", version)
else:
xgd_config_var = "$XDG_CONFIG_HOME"
xdg_config_res = os.path.expandvars(xgd_config_var)
if xdg_config_res != xgd_config_var:
path = os.path.join(xdg_config_res, "GNS3", version)
else:
home = os.path.expanduser("~")
path = os.path.join(home, ".config", "GNS3", version)
home = os.path.expanduser("~")
path = os.path.join(home, ".config", "GNS3", version)
self.profiles_path = os.path.join(path, "profiles")
self.uiShowAtStartupCheckBox.setChecked(LocalConfig.instance().multiProfiles())
@@ -109,4 +103,4 @@ if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
dialog = ProfileSelectDialog()
dialog.show()
exit_code = app.exec()
exit_code = app.exec_()

View File

@@ -93,15 +93,15 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
for project in self.uiProjectsTreeWidget.selectedItems():
if sip_is_deleted(project):
continue
project_id = project.data(0, QtCore.Qt.ItemDataRole.UserRole)
project_name = project.data(1, QtCore.Qt.ItemDataRole.UserRole)
project_id = project.data(0, QtCore.Qt.UserRole)
project_name = project.data(1, QtCore.Qt.UserRole)
reply = QtWidgets.QMessageBox.warning(self,
"Delete project",
'Delete project "{}"?\nThis cannot be reverted.'.format(project_name),
QtWidgets.QMessageBox.StandardButton.Yes,
QtWidgets.QMessageBox.StandardButton.No)
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
projects_to_delete.add(project_id)
for project_id in projects_to_delete:
@@ -118,8 +118,8 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
return
for project in self.uiProjectsTreeWidget.selectedItems():
project_id = project.data(0, QtCore.Qt.ItemDataRole.UserRole)
project_name = project.data(1, QtCore.Qt.ItemDataRole.UserRole)
project_id = project.data(0, QtCore.Qt.UserRole)
project_name = project.data(1, QtCore.Qt.UserRole)
new_project_name = project_name + "-1"
existing_project_name = [p["name"] for p in Controller.instance().projects()]
@@ -131,7 +131,7 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
name, reply = QtWidgets.QInputDialog.getText(self,
"Duplicate project",
'Duplicate project "{}"?.'.format(project_name),
QtWidgets.QLineEdit.EchoMode.Normal,
QtWidgets.QLineEdit.Normal,
new_project_name)
name = name.strip()
if reply and len(name) > 0:
@@ -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:
@@ -167,9 +173,9 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
for project in Controller.instance().projects():
path = os.path.join(project["path"], project["filename"])
item = QtWidgets.QTreeWidgetItem([project["name"], project["status"], path])
item.setData(0, QtCore.Qt.ItemDataRole.UserRole, project["project_id"])
item.setData(1, QtCore.Qt.ItemDataRole.UserRole, project["name"])
item.setData(2, QtCore.Qt.ItemDataRole.UserRole, path)
item.setData(0, QtCore.Qt.UserRole, project["project_id"])
item.setData(1, QtCore.Qt.UserRole, project["name"])
item.setData(2, QtCore.Qt.UserRole, path)
items.append(item)
self.uiProjectsTreeWidget.addTopLevelItems(items)
@@ -180,7 +186,7 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
self.uiProjectsTreeWidget.resizeColumnToContents(0)
self.uiProjectsTreeWidget.resizeColumnToContents(1)
self.uiProjectsTreeWidget.resizeColumnToContents(2)
self.uiProjectsTreeWidget.sortItems(0, QtCore.Qt.SortOrder.AscendingOrder)
self.uiProjectsTreeWidget.sortItems(0, QtCore.Qt.AscendingOrder)
self.uiProjectsTreeWidget.setUpdatesEnabled(True)
def keyPressEvent(self, e):
@@ -188,7 +194,7 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
Event handler in order to properly handle escape.
"""
if e.key() == QtCore.Qt.Key.Key_Escape:
if e.key() == QtCore.Qt.Key_Escape:
self.close()
def _projectNameSlot(self, text):
@@ -289,10 +295,10 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
reply = QtWidgets.QMessageBox.warning(self,
"New project",
'Project "{}" already exists in location "{}", overwrite it?'.format(existing_project["name"], existing_project["path"]),
QtWidgets.QMessageBox.StandardButton.Yes,
QtWidgets.QMessageBox.StandardButton.No)
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
if reply == QtWidgets.QMessageBox.Yes:
Controller.instance().deleteProject(existing_project["project_id"], self._overwriteProjectCallback)
# In all cases we cancel the new project and if project success to delete
@@ -313,6 +319,6 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
QtWidgets.QMessageBox.critical(self, "Open project", "No project selected")
return
self._project_settings["project_id"] = current.data(0, QtCore.Qt.ItemDataRole.UserRole)
self._project_settings["project_name"] = current.data(1, QtCore.Qt.ItemDataRole.UserRole)
self._project_settings["project_id"] = current.data(0, QtCore.Qt.UserRole)
self._project_settings["project_name"] = current.data(1, QtCore.Qt.UserRole)
super().done(result)

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)
@@ -41,48 +42,78 @@ class ExportProjectWizard(QtWidgets.QWizard, Ui_ExportProjectWizard):
self._project = project
self._path = None
self.setWizardStyle(QtWidgets.QWizard.WizardStyle.ModernStyle)
self.setWizardStyle(QtWidgets.QWizard.ModernStyle)
if sys.platform.startswith("darwin"):
# we want to see the cancel button on OSX
self.setOptions(QtWidgets.QWizard.WizardOption.NoDefaultButton)
self.setOptions(QtWidgets.QWizard.NoDefaultButton)
self.uiCompressionComboBox.addItem("None", "none")
self.uiCompressionComboBox.addItem("Zip compression (deflate)", "zip")
self.uiCompressionComboBox.addItem("Bzip2 compression", "bzip2")
self.uiCompressionComboBox.addItem("Lzma compression", "lzma")
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):
directory = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.StandardLocation.DocumentsLocation)
directory = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DocumentsLocation)
if len(directory) == 0:
directory = LocalServer.instance().localServerSettings()["projects_path"]
path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export portable project", directory,
"GNS3 Portable Project (*.gns3project *.gns3p)",
"GNS3 Portable Project (*.gns3project *.gns3p)")
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,19 +181,63 @@ class ExportProjectWizard(QtWidgets.QWizard, Ui_ExportProjectWizard):
"""
if result:
include_images = include_snapshots = reset_mac_addresses = keep_compute_ids = "no"
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:
include_images = "no"
if self.uiIncludeSnapshotsCheckBox.isChecked():
include_snapshots = "yes"
else:
include_snapshots = "no"
if self.uiResetMacAddressesCheckBox.isChecked():
reset_mac_addresses = "yes"
if self.uiKeepComputeIdsCheckBox.isChecked():
keep_compute_ids = "yes"
else:
reset_mac_addresses = "no"
compression = self.uiCompressionComboBox.currentData()
export_worker = ExportProjectWorker(self._project, self._path, include_images, include_snapshots, reset_mac_addresses, keep_compute_ids, 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

@@ -35,7 +35,7 @@ class ProjectWelcomeDialog(QtWidgets.QDialog, Ui_ProjectWelcomeDialog):
self._project = project
self.setupUi(self)
self.uiOkButton.clicked.connect(self._okButtonClickedSlot)
self.gridLayout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
self.gridLayout.setAlignment(QtCore.Qt.AlignTop)
self.label.setOpenExternalLinks(True)
self._variables = self._getVariables(project)
self._loadReadme()
@@ -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
@@ -76,9 +76,9 @@ class ProjectWelcomeDialog(QtWidgets.QDialog, Ui_ProjectWelcomeDialog):
reply = QtWidgets.QMessageBox.warning(self,
"Missing values",
"Are you sure you want to continue without providing missing values?",
QtWidgets.QMessageBox.StandardButton.Yes,
QtWidgets.QMessageBox.StandardButton.No)
if reply == QtWidgets.QMessageBox.StandardButton.No:
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
return
self._project.setVariables(self._variables)

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,44 +43,21 @@ 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.WizardStyle.ModernStyle)
self.setWizardStyle(QtWidgets.QWizard.ModernStyle)
if sys.platform.startswith("darwin"):
# we want to see the cancel button on OSX
self.setOptions(QtWidgets.QWizard.WizardOption.NoDefaultButton)
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():
if address.protocol() in [QtNetwork.QAbstractSocket.NetworkLayerProtocol.IPv4Protocol, QtNetwork.QAbstractSocket.NetworkLayerProtocol.IPv6Protocol]:
if address.protocol() in [QtNetwork.QAbstractSocket.IPv4Protocol, QtNetwork.QAbstractSocket.IPv6Protocol]:
address_string = address.toString()
if address_string.startswith("169.254") or address_string.startswith("fe80"):
# ignore link-local addresses, could not use https://doc.qt.io/qt-5/qhostaddress.html#isLinkLocal
@@ -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.
@@ -156,18 +101,11 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
:returns: current QWidget
"""
pane = dialog.uiTreeWidget.findItems(name, QtCore.Qt.MatchFlag.MatchFixedString)[0]
pane = dialog.uiTreeWidget.findItems(name, QtCore.Qt.MatchFixedString)[0]
child_pane = pane.child(0)
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

@@ -67,8 +67,8 @@ class SnapshotsDialog(QtWidgets.QDialog, Ui_SnapshotsDialog):
for snapshot in result:
item = QtWidgets.QListWidgetItem(self.uiSnapshotsList)
item.setText("{} on {}".format(snapshot["name"], datetime.fromtimestamp(snapshot["created_at"]).strftime("%Y-%m-%d at %H:%M:%S")))
item.setData(QtCore.Qt.ItemDataRole.UserRole, snapshot["snapshot_id"])
item.setText("{} on {}".format(snapshot["name"], datetime.fromtimestamp(snapshot["created_at"]).strftime("%d/%m/%y at %H:%M:%S")))
item.setData(QtCore.Qt.UserRole, snapshot["snapshot_id"])
if self.uiSnapshotsList.count():
self.uiSnapshotsList.setCurrentRow(0)
@@ -83,13 +83,16 @@ class SnapshotsDialog(QtWidgets.QDialog, Ui_SnapshotsDialog):
Slot to create a snapshot.
"""
snapshot_name, ok = QtWidgets.QInputDialog.getText(self, "Snapshot", "Snapshot name:", QtWidgets.QLineEdit.EchoMode.Normal, "Unnamed")
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:
@@ -107,7 +110,7 @@ class SnapshotsDialog(QtWidgets.QDialog, Ui_SnapshotsDialog):
item = self.uiSnapshotsList.currentItem()
if item:
snapshot_id = item.data(QtCore.Qt.ItemDataRole.UserRole)
snapshot_id = item.data(QtCore.Qt.UserRole)
Controller.instance().delete("/projects/{}/snapshots/{}".format(self._project.id(), snapshot_id), self._deleteSnapshotsCallback)
def _deleteSnapshotsCallback(self, result, error=False, server=None, context={}, **kwargs):
@@ -125,7 +128,7 @@ class SnapshotsDialog(QtWidgets.QDialog, Ui_SnapshotsDialog):
item = self.uiSnapshotsList.currentItem()
if item:
snapshot_id = item.data(QtCore.Qt.ItemDataRole.UserRole)
snapshot_id = item.data(QtCore.Qt.UserRole)
self._restoreSnapshot(snapshot_id)
def _restoreSnapshot(self, snapshot_id):
@@ -135,12 +138,17 @@ class SnapshotsDialog(QtWidgets.QDialog, Ui_SnapshotsDialog):
:param snapshot_id: id of the snapshot
"""
reply = QtWidgets.QMessageBox.question(self, "Snapshots", "This will discard any changes made to your project since the snapshot was taken, would you like to proceed?", QtWidgets.QMessageBox.StandardButton.Ok, QtWidgets.QMessageBox.StandardButton.Cancel)
if reply == QtWidgets.QMessageBox.StandardButton.Cancel:
reply = QtWidgets.QMessageBox.question(self, "Snapshots", "This will discard any changes made to your project since the snapshot was taken, would you like to proceed?", QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Cancel)
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):
@@ -155,5 +163,5 @@ class SnapshotsDialog(QtWidgets.QDialog, Ui_SnapshotsDialog):
Slot to restore a snapshot when it is double clicked.
"""
snapshot_id = item.data(QtCore.Qt.ItemDataRole.UserRole)
snapshot_id = item.data(QtCore.Qt.UserRole)
self._restoreSnapshot(snapshot_id)

View File

@@ -22,7 +22,6 @@ Style editor to edit Shape items.
from ..qt import QtCore, QtWidgets, QtGui
from ..ui.style_editor_dialog_ui import Ui_StyleEditorDialog
from ..items.shape_item import ShapeItem
from ..items.rectangle_item import RectangleItem
class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog):
@@ -42,14 +41,14 @@ class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog):
self._items = items
self.uiColorPushButton.clicked.connect(self._setColorSlot)
self.uiBorderColorPushButton.clicked.connect(self._setBorderColorSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).clicked.connect(self._applyPreferencesSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self._applyPreferencesSlot)
self.uiBorderStyleComboBox.addItem("Solid", QtCore.Qt.PenStyle.SolidLine)
self.uiBorderStyleComboBox.addItem("Dash", QtCore.Qt.PenStyle.DashLine)
self.uiBorderStyleComboBox.addItem("Dot", QtCore.Qt.PenStyle.DotLine)
self.uiBorderStyleComboBox.addItem("Dash Dot", QtCore.Qt.PenStyle.DashDotLine)
self.uiBorderStyleComboBox.addItem("Dash Dot Dot", QtCore.Qt.PenStyle.DashDotDotLine)
self.uiBorderStyleComboBox.addItem("No border", QtCore.Qt.PenStyle.NoPen)
self.uiBorderStyleComboBox.addItem("Solid", QtCore.Qt.SolidLine)
self.uiBorderStyleComboBox.addItem("Dash", QtCore.Qt.DashLine)
self.uiBorderStyleComboBox.addItem("Dot", QtCore.Qt.DotLine)
self.uiBorderStyleComboBox.addItem("Dash Dot", QtCore.Qt.DashDotLine)
self.uiBorderStyleComboBox.addItem("Dash Dot Dot", QtCore.Qt.DashDotDotLine)
self.uiBorderStyleComboBox.addItem("No border", QtCore.Qt.NoPen)
# use the first item in the list as the model
first_item = items[0]
@@ -71,27 +70,8 @@ class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog):
self._border_color.green(),
self._border_color.blue(),
self._border_color.alpha()))
if isinstance(first_item, RectangleItem):
# use the horizontal corner radius first and then the vertical one if it's not set
# maybe we allow configuring them separately in the future
corner_radius = first_item.horizontalCornerRadius()
if not corner_radius:
corner_radius = first_item.verticalCornerRadius()
self.uiCornerRadiusSpinBox.setValue(corner_radius)
else:
self.uiCornerRadiusLabel.hide()
self.uiCornerRadiusSpinBox.hide()
self.uiRotationSpinBox.setValue(int(first_item.rotation()))
self.uiBorderWidthSpinBox.setValue(pen.width())
if isinstance(first_item, ShapeItem):
rect = first_item.rect()
self.uiWidthSpinBox.setValue(int(rect.width()))
self.uiHeightSpinBox.setValue(int(rect.height()))
else:
self.uiWidthSpinBox.hide()
self.uiWidthLabel.hide()
self.uiHeightSpinBox.hide()
self.uiHeightLabel.hide()
index = self.uiBorderStyleComboBox.findData(pen.style())
if index != -1:
self.uiBorderStyleComboBox.setCurrentIndex(index)
@@ -101,7 +81,7 @@ class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog):
Slot to select the filling color.
"""
color = QtWidgets.QColorDialog.getColor(self._color, self, "Select Color", QtWidgets.QColorDialog.ColorDialogOption.ShowAlphaChannel)
color = QtWidgets.QColorDialog.getColor(self._color, self, "Select Color", QtWidgets.QColorDialog.ShowAlphaChannel)
if color.isValid():
self._color = color
self.uiColorPushButton.setStyleSheet("background-color: rgba({}, {}, {}, {});".format(self._color.red(),
@@ -114,7 +94,7 @@ class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog):
Slot to select the border color.
"""
color = QtWidgets.QColorDialog.getColor(self._border_color, self, "Select Color", QtWidgets.QColorDialog.ColorDialogOption.ShowAlphaChannel)
color = QtWidgets.QColorDialog.getColor(self._border_color, self, "Select Color", QtWidgets.QColorDialog.ShowAlphaChannel)
if color.isValid():
self._border_color = color
self.uiBorderColorPushButton.setStyleSheet("background-color: rgba({}, {}, {}, {});".format(self._border_color.red(),
@@ -128,7 +108,7 @@ class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog):
"""
border_style = QtCore.Qt.PenStyle(self.uiBorderStyleComboBox.itemData(self.uiBorderStyleComboBox.currentIndex()))
pen = QtGui.QPen(self._border_color, self.uiBorderWidthSpinBox.value(), border_style, QtCore.Qt.PenCapStyle.RoundCap, QtCore.Qt.PenJoinStyle.RoundJoin)
pen = QtGui.QPen(self._border_color, self.uiBorderWidthSpinBox.value(), border_style, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)
if self._color:
brush = QtGui.QBrush(self._color)
else:
@@ -136,18 +116,10 @@ class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog):
for item in self._items:
item.setPen(pen)
# on multi-selection it's possible to select many type of items
# on multiselection it's possible to select many type of items
# but brush can be applied only on ShapeItem,
if brush and isinstance(item, ShapeItem):
item.setBrush(brush)
if isinstance(item, RectangleItem):
corner_radius = self.uiCornerRadiusSpinBox.value()
# use the corner radius for both horizontal (rx) and vertical (ry)
# maybe we support setting them separately in the future
item.setHorizontalCornerRadius(corner_radius)
item.setVerticalCornerRadius(corner_radius)
if isinstance(item, ShapeItem):
item.setWidthAndHeight(self.uiWidthSpinBox.value(), self.uiHeightSpinBox.value())
item.setRotation(self.uiRotationSpinBox.value())
def done(self, result):

View File

@@ -41,41 +41,30 @@ class StyleEditorDialogLink(QtWidgets.QDialog, Ui_StyleEditorDialog):
self._link = link
self._link_style = {}
self.uiBorderColorLabel.setText("Link color")
self.uiBorderWidthLabel.setText("Link width")
self.uiBorderStyleLabel.setText("Link style")
self.uiBorderColorPushButton.clicked.connect(self._setBorderColorSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).clicked.connect(self._applyPreferencesSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self._applyPreferencesSlot)
self.uiBorderStyleComboBox.addItem("Solid", QtCore.Qt.PenStyle.SolidLine)
self.uiBorderStyleComboBox.addItem("Dash", QtCore.Qt.PenStyle.DashLine)
self.uiBorderStyleComboBox.addItem("Dot", QtCore.Qt.PenStyle.DotLine)
self.uiBorderStyleComboBox.addItem("Dash Dot", QtCore.Qt.PenStyle.DashDotLine)
self.uiBorderStyleComboBox.addItem("Dash Dot Dot", QtCore.Qt.PenStyle.DashDotDotLine)
self.uiBorderStyleComboBox.addItem("Invisible", QtCore.Qt.PenStyle.NoPen)
self.uiBorderStyleComboBox.addItem("Solid", QtCore.Qt.SolidLine)
self.uiBorderStyleComboBox.addItem("Dash", QtCore.Qt.DashLine)
self.uiBorderStyleComboBox.addItem("Dot", QtCore.Qt.DotLine)
self.uiBorderStyleComboBox.addItem("Dash Dot", QtCore.Qt.DashDotLine)
self.uiBorderStyleComboBox.addItem("Dash Dot Dot", QtCore.Qt.DashDotDotLine)
self.uiBorderStyleComboBox.addItem("Invisible", QtCore.Qt.NoPen)
self.uiColorLabel.hide()
self.uiColorPushButton.hide()
self._color = None
self.uiCornerRadiusLabel.hide()
self.uiCornerRadiusSpinBox.hide()
self.uiRotationLabel.hide()
self.uiRotationSpinBox.hide()
link.setHovered(False) # make sure we use the right style
pen = link.pen()
link.setHovered(True)
self._border_color = pen.color()
self.uiBorderColorPushButton.setStyleSheet("background-color: rgba({}, {}, {}, {});".format(
self._border_color.red(),
self._border_color.green(),
self._border_color.blue(),
self._border_color.alpha())
)
self.uiBorderColorPushButton.setStyleSheet("background-color: rgba({}, {}, {}, {});".format(self._border_color.red(),
self._border_color.green(),
self._border_color.blue(),
self._border_color.alpha()))
self.uiBorderWidthSpinBox.setValue(pen.width())
index = self.uiBorderStyleComboBox.findData(pen.style())
if index != -1:
@@ -88,7 +77,7 @@ class StyleEditorDialogLink(QtWidgets.QDialog, Ui_StyleEditorDialog):
Slot to select the border color.
"""
color = QtWidgets.QColorDialog.getColor(self._border_color, self, "Select Color", QtWidgets.QColorDialog.ColorDialogOption.ShowAlphaChannel)
color = QtWidgets.QColorDialog.getColor(self._border_color, self, "Select Color", QtWidgets.QColorDialog.ShowAlphaChannel)
if color.isValid():
self._border_color = color
self.uiBorderColorPushButton.setStyleSheet("background-color: rgba({}, {}, {}, {});".format(self._border_color.red(),
@@ -102,19 +91,17 @@ class StyleEditorDialogLink(QtWidgets.QDialog, Ui_StyleEditorDialog):
"""
border_style = QtCore.Qt.PenStyle(self.uiBorderStyleComboBox.itemData(self.uiBorderStyleComboBox.currentIndex()))
pen = QtGui.QPen(self._border_color, self.uiBorderWidthSpinBox.value(), border_style, QtCore.Qt.PenCapStyle.RoundCap, QtCore.Qt.PenJoinStyle.RoundJoin)
pen = QtGui.QPen(self._border_color, self.uiBorderWidthSpinBox.value(), border_style, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)
self._link.setPen(pen)
new_link_style = {
"color": self._border_color.name(),
"width": self.uiBorderWidthSpinBox.value(),
"type": border_style.value,
}
new_link_style = {}
new_link_style["color"] = self._border_color.name()
new_link_style["width"] = self.uiBorderWidthSpinBox.value()
new_link_style["type"] = border_style
# Store values
self._link.setLinkStyle(new_link_style)
self._link.setHovered(False) # allow to see the new style
def done(self, result):
"""

View File

@@ -50,16 +50,16 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
self.setupUi(self)
self._items = items
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).clicked.connect(self._applyPreferencesSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self._applyPreferencesSlot)
self.uiSymbolToolButton.clicked.connect(self._symbolBrowserSlot)
self.uiCustomSymbolRadioButton.toggled.connect(self._customSymbolToggledSlot)
self.uiBuiltInSymbolRadioButton.toggled.connect(self._builtInSymbolToggledSlot)
self.uiSearchLineEdit.textChanged.connect(self._searchTextChangedSlot)
if not SymbolSelectionDialog._symbols_dir:
SymbolSelectionDialog._symbols_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.StandardLocation.PicturesLocation)
SymbolSelectionDialog._symbols_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.PicturesLocation)
if not self._items:
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).hide()
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).hide()
self.uiBuiltInSymbolRadioButton.setChecked(True)
self.uiSymbolTreeWidget.setFocus()
@@ -85,14 +85,14 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
font = parent.font(0)
font.setBold(True)
parent.setFont(0, font)
parent.setFlags(parent.flags() & ~QtCore.Qt.ItemFlag.ItemIsSelectable)
parent.setFlags(parent.flags() & ~QtCore.Qt.ItemIsSelectable)
self._parents[theme] = parent
else:
parent = self._parents[theme]
name = os.path.splitext(symbol.filename())[0]
item = QtWidgets.QTreeWidgetItem(parent)
item.setData(0, QtCore.Qt.ItemDataRole.UserRole, symbol)
item.setData(0, QtCore.Qt.UserRole, symbol)
item.setToolTip(0, symbol.id())
self._symbol_items.append(item)
item.setText(0, name)
@@ -101,7 +101,7 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
if sip_is_deleted(item):
return
svg_renderer = QImageSvgRenderer(path)
image = QtGui.QImage(64, 64, QtGui.QImage.Format.Format_ARGB32)
image = QtGui.QImage(64, 64, QtGui.QImage.Format_ARGB32)
# Set the ARGB to 0 to prevent rendering artifacts
image.fill(0x00000000)
svg_renderer.render(QtGui.QPainter(image))
@@ -111,7 +111,7 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
Controller.instance().getStatic(symbol.url(), qpartial(render, item))
for parent in self._parents.values():
parent.sortChildren(0, QtCore.Qt.SortOrder.AscendingOrder)
parent.sortChildren(0, QtCore.Qt.AscendingOrder)
self.adjustSize()
def _searchTextChangedSlot(self, text):
@@ -123,7 +123,7 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
"""
text = self.uiSearchLineEdit.text()
for item in self._symbol_items:
# if not item.data(0, QtCore.Qt.ItemDataRole.UserRole).builtin():
# if not item.data(0, QtCore.Qt.UserRole).builtin():
# item.setHidden(True)
# else:
if not text.strip() or text.strip().lower() in item.text(0).lower():
@@ -176,7 +176,7 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
if self.uiSymbolTreeWidget.isEnabled():
current = self.uiSymbolTreeWidget.currentItem()
if current and current.parent():
return current.data(0, QtCore.Qt.ItemDataRole.UserRole).id()
return current.data(0, QtCore.Qt.UserRole).id()
else:
return os.path.basename(self.uiSymbolLineEdit.text())
return None
@@ -184,14 +184,21 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
def _symbolBrowserSlot(self):
# supported image file formats
file_formats = "Image files (*.svg *.bmp *.jpeg *.jpg *.pbm *.pgm *.png *.ppm *.xbm *.xpm *.gif);;All files (*)"
file_formats = "Image files (*.svg *.bmp *.jpeg *.jpg *.pbm *.pgm *.png *.ppm *.xbm *.xpm *.gif);;All files (*.*)"
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Image", SymbolSelectionDialog._symbols_dir, file_formats)
if not path:
return
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

@@ -39,7 +39,7 @@ class TextEditorDialog(QtWidgets.QDialog, Ui_TextEditorDialog):
self._items = items
self.uiFontPushButton.clicked.connect(self._setFontSlot)
self.uiColorPushButton.clicked.connect(self._setColorSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).clicked.connect(self._applyPreferencesSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self._applyPreferencesSlot)
# use the first item in the list as the model
first_item = items[0]
@@ -49,7 +49,7 @@ class TextEditorDialog(QtWidgets.QDialog, Ui_TextEditorDialog):
self.uiPlainTextEdit.setFont(first_item.font())
if not first_item.editable():
self.uiPlainTextEdit.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.NoTextInteraction)
self.uiPlainTextEdit.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
if len(self._items) == 1:
self.uiApplyColorToAllItemsCheckBox.setChecked(True)
@@ -77,7 +77,7 @@ class TextEditorDialog(QtWidgets.QDialog, Ui_TextEditorDialog):
"""
selected_font, ok = QtWidgets.QFontDialog.getFont(self.uiPlainTextEdit.font(), self,
options=QtWidgets.QFontDialog.FontDialogOption.DontUseNativeDialog)
options=QtWidgets.QFontDialog.DontUseNativeDialog)
if ok:
self.uiPlainTextEdit.setFont(selected_font)
@@ -87,7 +87,7 @@ class TextEditorDialog(QtWidgets.QDialog, Ui_TextEditorDialog):
Slot to select the color.
"""
color = QtWidgets.QColorDialog.getColor(self._color, self, None, QtWidgets.QColorDialog.ColorDialogOption.ShowAlphaChannel)
color = QtWidgets.QColorDialog.getColor(self._color, self, None, QtWidgets.QColorDialog.ShowAlphaChannel)
if color.isValid():
self._setColor(color)

View File

@@ -89,7 +89,7 @@ class VMWithImagesWizard(VMWizard):
def _imageCreateSlot(self, line_edit, create_image_wizard, image_suffix):
create_dialog = create_image_wizard(self, self.getSettings()["compute_id"], self.uiNameLineEdit.text() + image_suffix)
if QtWidgets.QDialog.DialogCode.Accepted == create_dialog.exec():
if QtWidgets.QDialog.Accepted == create_dialog.exec_():
line_edit.setText(create_dialog.uiLocationLineEdit.text())
def _imageBrowserSlot(self, line_edit, image_selector):
@@ -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

@@ -39,10 +39,10 @@ class VMWizard(QtWidgets.QWizard):
self._devices = devices
self._local_server_disable = False
self.setWizardStyle(QtWidgets.QWizard.WizardStyle.ModernStyle)
self.setWizardStyle(QtWidgets.QWizard.ModernStyle)
if sys.platform.startswith("darwin"):
# we want to see the cancel button on OSX
self.setOptions(QtWidgets.QWizard.WizardOption.NoDefaultButton)
self.setOptions(QtWidgets.QWizard.NoDefaultButton)
self.uiRemoteRadioButton.toggled.connect(self._remoteServerToggledSlot)
if hasattr(self, "uiVMRadioButton"):

View File

@@ -74,10 +74,6 @@ class GraphicsView(QtWidgets.QGraphicsView):
:param parent: parent widget
"""
# Class-level constants for default colors
DEFAULT_DRAWING_GRID_COLOR = QtGui.QColor(208, 208, 208) # #D0D0D0
DEFAULT_NODE_GRID_COLOR = QtGui.QColor(190, 190, 190) # #BEBEBE
def __init__(self, parent):
# Our parent is the central widget which parent is the main window.
@@ -96,12 +92,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
self._dragging = False
self._grid_size = 75
self._drawing_grid_size = 25
self._drawing_grid_color = self.DEFAULT_DRAWING_GRID_COLOR
self._node_grid_color = self.DEFAULT_NODE_GRID_COLOR
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)
@@ -111,14 +103,14 @@ class GraphicsView(QtWidgets.QGraphicsView):
self.setSceneSize(width, height)
# set the custom flags for this view
self.setDragMode(QtWidgets.QGraphicsView.DragMode.RubberBandDrag)
self.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing)
self.setTransformationAnchor(QtWidgets.QGraphicsView.ViewportAnchor.AnchorUnderMouse)
self.setResizeAnchor(QtWidgets.QGraphicsView.ViewportAnchor.AnchorViewCenter)
self.setDragMode(QtWidgets.QGraphicsView.RubberBandDrag)
self.setRenderHint(QtGui.QPainter.Antialiasing)
self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorViewCenter)
# default directories for QFileDialog
self._import_config_directory = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.StandardLocation.DocumentsLocation)
self._export_config_directory = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.StandardLocation.DocumentsLocation)
self._import_config_directory = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DocumentsLocation)
self._export_config_directory = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DocumentsLocation)
self._local_addresses = ['0.0.0.0', '127.0.0.1', 'localhost', '::1', '0:0:0:0:0:0:0:1', '::', QtNetwork.QHostInfo.localHostName()]
def setSceneSize(self, width, height):
@@ -127,6 +119,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
def setZoom(self, zoom):
"""
Sets zoom of the Graphics View
:param zoom:
:return:
"""
if zoom:
factor = zoom / 100.
@@ -197,9 +191,6 @@ class GraphicsView(QtWidgets.QGraphicsView):
# clear all objects on the scene
self.scene().clear()
# reset zoom / scale
self.resetTransform()
def _loadSettings(self):
"""
@@ -237,12 +228,12 @@ class GraphicsView(QtWidgets.QGraphicsView):
"""
if enabled:
self.setCursor(QtCore.Qt.CursorShape.CrossCursor)
self.setCursor(QtCore.Qt.CrossCursor)
else:
if self._newlink and self._newlink in self.scene().items():
self.scene().removeItem(self._newlink)
self._newlink = None
self.setCursor(QtCore.Qt.CursorShape.ArrowCursor)
self.setCursor(QtCore.Qt.ArrowCursor)
self._adding_link = enabled
def addNote(self, state):
@@ -254,10 +245,10 @@ class GraphicsView(QtWidgets.QGraphicsView):
if state:
self._adding_note = True
self.setCursor(QtCore.Qt.CursorShape.IBeamCursor)
self.setCursor(QtCore.Qt.IBeamCursor)
else:
self._adding_note = False
self.setCursor(QtCore.Qt.CursorShape.ArrowCursor)
self.setCursor(QtCore.Qt.ArrowCursor)
def addRectangle(self, state):
"""
@@ -268,10 +259,10 @@ class GraphicsView(QtWidgets.QGraphicsView):
if state:
self._adding_rectangle = True
self.setCursor(QtCore.Qt.CursorShape.PointingHandCursor)
self.setCursor(QtCore.Qt.PointingHandCursor)
else:
self._adding_rectangle = False
self.setCursor(QtCore.Qt.CursorShape.ArrowCursor)
self.setCursor(QtCore.Qt.ArrowCursor)
def addEllipse(self, state):
"""
@@ -282,10 +273,10 @@ class GraphicsView(QtWidgets.QGraphicsView):
if state:
self._adding_ellipse = True
self.setCursor(QtCore.Qt.CursorShape.PointingHandCursor)
self.setCursor(QtCore.Qt.PointingHandCursor)
else:
self._adding_ellipse = False
self.setCursor(QtCore.Qt.CursorShape.ArrowCursor)
self.setCursor(QtCore.Qt.ArrowCursor)
def addLine(self, state):
"""
@@ -296,10 +287,10 @@ class GraphicsView(QtWidgets.QGraphicsView):
if state:
self._adding_line = True
self.setCursor(QtCore.Qt.CursorShape.PointingHandCursor)
self.setCursor(QtCore.Qt.PointingHandCursor)
else:
self._adding_line = False
self.setCursor(QtCore.Qt.CursorShape.ArrowCursor)
self.setCursor(QtCore.Qt.ArrowCursor)
def addImage(self, image_path):
"""
@@ -401,7 +392,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
# link addition code
if not self._newlink:
source_item = item
source_port = source_item.connectToPort(event.globalPosition().toPoint())
source_port = source_item.connectToPort(event.globalPos())
if not source_port:
return
@@ -410,15 +401,15 @@ class GraphicsView(QtWidgets.QGraphicsView):
return
if source_port.linkType() == "Serial":
self._newlink = SerialLinkItem(source_item, source_port, self.mapToScene(event.position().toPoint()), None, adding_flag=True)
self._newlink = SerialLinkItem(source_item, source_port, self.mapToScene(event.pos()), None, adding_flag=True)
else:
self._newlink = EthernetLinkItem(source_item, source_port, self.mapToScene(event.position().toPoint()), None, adding_flag=True)
self._newlink = EthernetLinkItem(source_item, source_port, self.mapToScene(event.pos()), None, adding_flag=True)
self.scene().addItem(self._newlink)
else:
source_item = self._newlink.sourceItem()
source_port = self._newlink.sourcePort()
destination_item = item
destination_port = destination_item.connectToPort(event.globalPosition().toPoint())
destination_port = destination_item.connectToPort(event.globalPos())
if not destination_port:
return
@@ -441,11 +432,9 @@ class GraphicsView(QtWidgets.QGraphicsView):
is_not_link = True
is_not_logo = True
item = self.itemAt(event.position().toPoint())
item = self.itemAt(event.pos())
if item and sip.isdeleted(item):
return
elif not item:
self._main_window.uiStatusBar.clearMessage() # reset the status bar message when clicking on the scene
if item and (isinstance(item, LinkItem) or isinstance(item.parentItem(), LinkItem)):
is_not_link = False
@@ -456,57 +445,57 @@ class GraphicsView(QtWidgets.QGraphicsView):
if isinstance(it, LinkItem):
it.setHovered(False)
if (event.buttons() == QtCore.Qt.MouseButton.LeftButton and event.modifiers() == QtCore.Qt.KeyboardModifier.ShiftModifier) or event.buttons() == QtCore.Qt.MouseButton.MiddleButton:
if (event.buttons() == QtCore.Qt.LeftButton and event.modifiers() == QtCore.Qt.ShiftModifier) or event.buttons() == QtCore.Qt.MidButton:
# checks to see if either the middle mouse is pressed
# or a combination of left mouse button and SHIT key are pressed to start dragging the view
self._last_mouse_position = self.mapFromGlobal(event.globalPosition())
self._last_mouse_position = self.mapFromGlobal(event.globalPos())
self._dragging = True
self.setCursor(QtCore.Qt.CursorShape.ClosedHandCursor)
self.setCursor(QtCore.Qt.ClosedHandCursor)
return
if is_not_link and item and event.modifiers() == QtCore.Qt.KeyboardModifier.ControlModifier and event.button() == QtCore.Qt.MouseButton.LeftButton and item and not self._adding_link:
if is_not_link and item and event.modifiers() == QtCore.Qt.ControlModifier and event.button() == QtCore.Qt.LeftButton and item and not self._adding_link:
# manual selection using CTRL
if item.isSelected():
item.setSelected(False)
else:
item.setSelected(True)
elif is_not_link and is_not_logo and event.button() == QtCore.Qt.MouseButton.RightButton and not self._adding_link:
elif is_not_link and is_not_logo and event.button() == QtCore.Qt.RightButton and not self._adding_link:
pass #TODO: remove this without creating a bug...
elif is_not_link and self._adding_link and event.button() == QtCore.Qt.MouseButton.RightButton:
elif is_not_link and self._adding_link and event.button() == QtCore.Qt.RightButton:
# send a escape key to the main window to cancel the link addition
key = QtGui.QKeyEvent(QtCore.QEvent.Type.KeyPress, QtCore.Qt.Key.Key_Escape, QtCore.Qt.KeyboardModifier.NoModifier)
key = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Escape, QtCore.Qt.NoModifier)
QtWidgets.QApplication.sendEvent(self._main_window, key)
elif item and isinstance(item, NodeItem) and self._adding_link and event.button() == QtCore.Qt.MouseButton.LeftButton:
elif item and isinstance(item, NodeItem) and self._adding_link and event.button() == QtCore.Qt.LeftButton:
self._userNodeLinking(event, item)
#context_event = QtGui.QContextMenuEvent(QtGui.QContextMenuEvent.Reason.Mouse, event.pos())
#context_event = QtGui.QContextMenuEvent(QtGui.QContextMenuEvent.Mouse, event.pos())
#QtWidgets.QApplication.sendEvent(self, context_event)
elif event.button() == QtCore.Qt.MouseButton.LeftButton and self._adding_note:
pos = self.mapToScene(event.position().toPoint())
elif event.button() == QtCore.Qt.LeftButton and self._adding_note:
pos = self.mapToScene(event.pos())
note = self.createDrawingItem("text", int(pos.x()), int(pos.y()), 2)
pos_x = note.pos().x()
pos_y = note.pos().y() - (note.boundingRect().height() / 2)
note.setPos(pos_x, pos_y)
note.editText()
self._main_window.uiAddNoteAction.setChecked(False)
self.setCursor(QtCore.Qt.CursorShape.ArrowCursor)
self.setCursor(QtCore.Qt.ArrowCursor)
self._adding_note = False
elif event.button() == QtCore.Qt.MouseButton.LeftButton and self._adding_rectangle:
pos = self.mapToScene(event.position().toPoint())
elif event.button() == QtCore.Qt.LeftButton and self._adding_rectangle:
pos = self.mapToScene(event.pos())
self.createDrawingItem("rect", int(pos.x()), int(pos.y()), 1)
self._main_window.uiDrawRectangleAction.setChecked(False)
self.setCursor(QtCore.Qt.CursorShape.ArrowCursor)
self.setCursor(QtCore.Qt.ArrowCursor)
self._adding_rectangle = False
elif event.button() == QtCore.Qt.MouseButton.LeftButton and self._adding_ellipse:
pos = self.mapToScene(event.position().toPoint())
elif event.button() == QtCore.Qt.LeftButton and self._adding_ellipse:
pos = self.mapToScene(event.pos())
self.createDrawingItem("ellipse", int(pos.x()), int(pos.y()), 1)
self._main_window.uiDrawEllipseAction.setChecked(False)
self.setCursor(QtCore.Qt.CursorShape.ArrowCursor)
self.setCursor(QtCore.Qt.ArrowCursor)
self._adding_ellipse = False
elif event.button() == QtCore.Qt.MouseButton.LeftButton and self._adding_line:
pos = self.mapToScene(event.position().toPoint())
elif event.button() == QtCore.Qt.LeftButton and self._adding_line:
pos = self.mapToScene(event.pos())
self.createDrawingItem("line", int(pos.x()), int(pos.y()), 1)
self._main_window.uiDrawLineAction.setChecked(False)
self.setCursor(QtCore.Qt.CursorShape.ArrowCursor)
self.setCursor(QtCore.Qt.ArrowCursor)
self._adding_line = False
else:
super().mousePressEvent(event)
@@ -540,7 +529,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
if item and not sip.isdeleted(item):
# Prevent right clicking on a selected item from de-selecting all other items
if not item.isSelected():
if not (event.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier):
if not event.modifiers() & QtCore.Qt.ControlModifier:
for it in self.scene().items():
it.setSelected(False)
item.setSelected(True)
@@ -566,12 +555,12 @@ class GraphicsView(QtWidgets.QGraphicsView):
# If the left mouse button is not still pressed TOGETHER with the SHIFT key and neither is the middle button
# this means the user is no longer trying to drag the view
if self._dragging and not (event.buttons() == QtCore.Qt.MouseButton.LeftButton and event.modifiers() == QtCore.Qt.KeyboardModifier.ShiftModifier) and not (event.buttons() & QtCore.Qt.MouseButton.MiddleButton):
if self._dragging and not (event.buttons() == QtCore.Qt.LeftButton and event.modifiers() == QtCore.Qt.ShiftModifier) and not event.buttons() & QtCore.Qt.MidButton:
self._dragging = False
self.setCursor(QtCore.Qt.CursorShape.ArrowCursor)
self.setCursor(QtCore.Qt.ArrowCursor)
else:
item = self.itemAt(event.position().toPoint())
if item is not None and not (event.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier):
item = self.itemAt(event.pos())
if item is not None and not event.modifiers() & QtCore.Qt.ControlModifier:
item.setSelected(True)
super().mouseReleaseEvent(event)
@@ -584,7 +573,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
:param: QWheelEvent instance
"""
if event.modifiers() == QtCore.Qt.KeyboardModifier.ControlModifier:
if event.modifiers() == QtCore.Qt.ControlModifier:
delta = event.angleDelta()
if delta is not None and delta.x() == 0:
# CTRL is pressed then use the mouse wheel to zoom in or out.
@@ -603,7 +592,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
if factor < 0.10 or factor > 10:
return
self.scale(scale_factor, scale_factor)
self._main_window.uiStatusBar.showMessage("Zoom: {}%".format(round(self.transform().m11() * 100)), 5000)
self._main_window.uiStatusBar.showMessage("Zoom: {}%".format(round(self.transform().m11() * 100)), 2000)
def keyPressEvent(self, event):
"""
@@ -612,7 +601,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
:param event: QKeyEvent
"""
if event.key() == QtCore.Qt.Key.Key_Delete:
if event.key() == QtCore.Qt.Key_Delete:
# check if we are editing an LabelItem instance, then send the delete key event to it
for item in self.scene().selectedItems():
if (isinstance(item, LabelItem) or isinstance(item, TextItem)) and item.hasFocus():
@@ -632,23 +621,23 @@ class GraphicsView(QtWidgets.QGraphicsView):
# This if statement event checks to see if the user is dragging the scene
# if so it sets the value of the scene scroll bars based on the change between
# the previous and current mouse position
mapped_global_pos = self.mapFromGlobal(event.globalPosition())
mapped_global_pos = self.mapFromGlobal(event.globalPos())
hBar = self.horizontalScrollBar()
vBar = self.verticalScrollBar()
delta = mapped_global_pos - self._last_mouse_position
hBar.setValue(int(hBar.value() + (delta.x() if QtWidgets.QApplication.isRightToLeft() else -delta.x())))
vBar.setValue(int(vBar.value() - delta.y()))
hBar.setValue(hBar.value() + (delta.x() if QtWidgets.QApplication.isRightToLeft() else -delta.x()))
vBar.setValue(vBar.value() - delta.y())
self._last_mouse_position = mapped_global_pos
if self._adding_link and self._newlink and self._newlink in self.scene().items():
# update the mouse position when the user is adding a link.
self._newlink.setMousePoint(self.mapToScene(event.position().toPoint()))
self._newlink.setMousePoint(self.mapToScene(event.pos()))
event.ignore()
else:
item = self.itemAt(event.position().toPoint())
item = self.itemAt(event.pos())
if item:
# show item coords in the status bar
coords = "X: {} Y: {} Z: {}".format(item.x(), item.y(), item.zValue())
self._main_window.uiStatusBar.showMessage(coords)
self._main_window.uiStatusBar.showMessage(coords, 2000)
# force the children to redraw because of a problem with QGraphicsEffect
for item in self.scene().selectedItems():
@@ -663,7 +652,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
:param event: QMouseEvent instance
"""
item = self.itemAt(event.position().toPoint())
item = self.itemAt(event.pos())
if not self._adding_link:
if isinstance(item, NodeItem) and item.node().initialized():
@@ -672,7 +661,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
self.configureSlot()
return
else:
if item.node().bringToFront():
if sys.platform.startswith("win") and item.node().bringToFront():
return
self.consoleFromItems(self.scene().selectedItems())
return
@@ -697,7 +686,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
node_properties = NodePropertiesDialog(items, self._main_window)
node_properties.setModal(True)
node_properties.show()
node_properties.exec()
node_properties.exec_()
def dragMoveEvent(self, event):
"""
@@ -721,25 +710,24 @@ class GraphicsView(QtWidgets.QGraphicsView):
:param event: QDropEvent instance
"""
log.debug("Drop event received with mime data: {}".format(event.mimeData().formats()))
# check if what has been dropped is handled by this view
if event.mimeData().hasFormat("application/x-gns3-template"):
template_id = event.mimeData().data("application/x-gns3-template").data().decode()
event.setDropAction(QtCore.Qt.DropAction.CopyAction)
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
if event.modifiers() == QtCore.Qt.KeyboardModifier.ShiftModifier:
if event.keyboardModifiers() == QtCore.Qt.ShiftModifier:
max_nodes_per_line = 10 # max number of nodes on a single line
offset = 100 # spacing between elements
integer, ok = QtWidgets.QInputDialog.getInt(self, "Nodes", "Number of nodes:", 2, 1, 100, 1)
if ok:
for node_number in range(integer):
x = event.position().x() - (150 // 2) + (node_number % max_nodes_per_line) * offset
y = event.position().y() - (70 // 2) + (node_number // max_nodes_per_line) * offset
if self.createNodeFromTemplateId(template_id, QtCore.QPointF(x, y)) is False:
x = event.pos().x() - (150 // 2) + (node_number % max_nodes_per_line) * offset
y = event.pos().y() - (70 // 2) + (node_number // max_nodes_per_line) * offset
if self.createNodeFromTemplateId(template_id, QtCore.QPoint(x, y)) is False:
event.ignore()
break
else:
if self.createNodeFromTemplateId(template_id, event.position()) is False:
if self.createNodeFromTemplateId(template_id, event.pos()) is False:
event.ignore()
elif event.mimeData().hasFormat("text/uri-list") and event.mimeData().hasUrls():
# This should not arrive but we received bug report with it...
@@ -762,9 +750,9 @@ class GraphicsView(QtWidgets.QGraphicsView):
:param pos: position where to display the menu
"""
menu = QtWidgets.QMenu(parent=self)
menu = QtWidgets.QMenu()
self.populateDeviceContextualMenu(menu)
menu.exec(pos)
menu.exec_(pos)
menu.clear()
# Make sure to deselect all items.
# This is to prevent a bug on Windows
@@ -785,145 +773,157 @@ class GraphicsView(QtWidgets.QGraphicsView):
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "configPage"), items)):
# Action: Configure node
configure_action = QtGui.QAction("Configure", menu)
configure_action = QtWidgets.QAction("Configure", menu)
configure_action.setIcon(get_icon("configuration.svg"))
configure_action.triggered.connect(self.configureActionSlot)
menu.addAction(configure_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and item.node().console() is not None, items)):
console_action = QtGui.QAction("Console", menu)
console_action = QtWidgets.QAction("Console", menu)
console_action.setIcon(get_icon("console.svg"))
console_action.triggered.connect(self.consoleActionSlot)
menu.addAction(console_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "auxConsole"), items)):
aux_console_action = QtGui.QAction("Auxiliary console", menu)
aux_console_action = QtWidgets.QAction("Auxiliary console", menu)
aux_console_action.setIcon(get_icon("aux-console.svg"))
aux_console_action.triggered.connect(self.auxConsoleActionSlot)
menu.addAction(aux_console_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and not item.node().isAlwaysOn(), items)):
start_action = QtGui.QAction("Start", menu)
start_action = QtWidgets.QAction("Start", menu)
start_action.setIcon(get_icon("start.svg"))
start_action.triggered.connect(self.startActionSlot)
menu.addAction(start_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and not item.node().isAlwaysOn(), items)):
suspend_action = QtGui.QAction("Suspend", menu)
suspend_action = QtWidgets.QAction("Suspend", menu)
suspend_action.setIcon(get_icon("pause.svg"))
suspend_action.triggered.connect(self.suspendActionSlot)
menu.addAction(suspend_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and not item.node().isAlwaysOn(), items)):
stop_action = QtGui.QAction("Stop", menu)
stop_action = QtWidgets.QAction("Stop", menu)
stop_action.setIcon(get_icon("stop.svg"))
stop_action.triggered.connect(self.stopActionSlot)
menu.addAction(stop_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and not item.node().isAlwaysOn(), items)):
reload_action = QtGui.QAction("Reload", menu)
reload_action = QtWidgets.QAction("Reload", menu)
reload_action.setIcon(get_icon("reload.svg"))
reload_action.triggered.connect(self.reloadActionSlot)
menu.addAction(reload_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and item.node().console() is not None, items)):
console_edit_action = QtGui.QAction("Custom console", menu)
console_edit_action = QtWidgets.QAction("Custom console", menu)
console_edit_action.setIcon(get_icon("console_edit.svg"))
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 = QtGui.QAction("Change hostname", menu)
change_hostname_action = QtWidgets.QAction("Change hostname", menu)
change_hostname_action.setIcon(get_icon("show-hostname.svg"))
change_hostname_action.triggered.connect(self.changeHostnameActionSlot)
menu.addAction(change_hostname_action)
if True in list(map(lambda item: isinstance(item, NodeItem), items)):
# Action: Change symbol
change_symbol_action = QtGui.QAction("Change symbol", menu)
change_symbol_action = QtWidgets.QAction("Change symbol", menu)
change_symbol_action.setIcon(get_icon("node_conception.svg"))
change_symbol_action.triggered.connect(self.changeSymbolActionSlot)
menu.addAction(change_symbol_action)
if True in list(map(lambda item: isinstance(item, DrawingItem) or isinstance(item, NodeItem), items)):
duplicate_action = QtGui.QAction("Duplicate", menu)
duplicate_action = QtWidgets.QAction("Duplicate", menu)
duplicate_action.setIcon(get_icon("duplicate.svg"))
duplicate_action.triggered.connect(self.duplicateActionSlot)
menu.addAction(duplicate_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "info"), items)):
# Action: Show node information
show_node_info_action = QtGui.QAction("Show node information", menu)
show_node_info_action = QtWidgets.QAction("Show node information", menu)
show_node_info_action.setIcon(get_icon("help.svg"))
show_node_info_action.triggered.connect(self.showNodeInfoSlot)
menu.addAction(show_node_info_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "nodeDir"), items)):
# Action: Show in file manager
show_in_file_manager_action = QtGui.QAction("Show in file manager", menu)
show_in_file_manager_action = QtWidgets.QAction("Show in file manager", menu)
show_in_file_manager_action.setIcon(get_icon("open.svg"))
show_in_file_manager_action.triggered.connect(self.showInFileManagerSlot)
menu.addAction(show_in_file_manager_action)
if not sys.platform.startswith("darwin") and True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "bringToFront"), items)):
# Action: bring console or window to front (Windows and Linux only)
bring_to_front_action = QtGui.QAction("Bring to front", menu)
if sys.platform.startswith("win") and True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "bringToFront"), items)):
# Action: bring console or window to front (Windows only)
bring_to_front_action = QtWidgets.QAction("Bring to front", menu)
bring_to_front_action.setIcon(get_icon("front.svg"))
bring_to_front_action.triggered.connect(self.bringToFrontSlot)
menu.addAction(bring_to_front_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and bool(item.node().configFiles()), items)):
import_config_action = QtGui.QAction("Import config", menu)
import_config_action = QtWidgets.QAction("Import config", menu)
import_config_action.setIcon(get_icon("import.svg"))
import_config_action.triggered.connect(self.importConfigActionSlot)
menu.addAction(import_config_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and bool(item.node().configFiles()), items)):
export_config_action = QtGui.QAction("Export config", menu)
export_config_action = QtWidgets.QAction("Export config", menu)
export_config_action.setIcon(get_icon("export.svg"))
export_config_action.triggered.connect(self.exportConfigActionSlot)
menu.addAction(export_config_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and bool(item.node().configTextFiles()), items)):
export_config_action = QtGui.QAction("Edit config", menu)
export_config_action = QtWidgets.QAction("Edit config", menu)
export_config_action.setIcon(get_icon("edit.svg"))
export_config_action.triggered.connect(self.editConfigActionSlot)
menu.addAction(export_config_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "idlepc"), items)):
idlepc_action = QtGui.QAction("Idle-PC", menu)
idlepc_action = QtWidgets.QAction("Idle-PC", menu)
idlepc_action.setIcon(get_icon("calculate.svg"))
idlepc_action.triggered.connect(self.idlepcActionSlot)
menu.addAction(idlepc_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "idlepc"), items)):
auto_idlepc_action = QtGui.QAction("Auto Idle-PC", menu)
auto_idlepc_action = QtWidgets.QAction("Auto Idle-PC", menu)
auto_idlepc_action.setIcon(get_icon("calculate.svg"))
auto_idlepc_action.triggered.connect(self.autoIdlepcActionSlot)
menu.addAction(auto_idlepc_action)
if True in list(map(lambda item: isinstance(item, LabelItem), items)):
text_edit_action = QtGui.QAction("Text edit", menu)
text_edit_action = QtWidgets.QAction("Text edit", menu)
text_edit_action.setIcon(get_icon("show-hostname.svg"))
text_edit_action.triggered.connect(self.textEditActionSlot)
menu.addAction(text_edit_action)
if True in list(map(lambda item: isinstance(item, TextItem), items)):
text_edit_action = QtGui.QAction("Text edit", menu)
text_edit_action = QtWidgets.QAction("Text edit", menu)
text_edit_action.setIcon(get_icon("edit.svg"))
text_edit_action.triggered.connect(self.textEditActionSlot)
menu.addAction(text_edit_action)
if True in list(map(lambda item: isinstance(item, ShapeItem) or isinstance(item, LineItem), items)):
style_action = QtGui.QAction("Style", menu)
style_action = QtWidgets.QAction("Style", menu)
style_action.setIcon(get_icon("node_conception.svg"))
style_action.triggered.connect(self.styleActionSlot)
menu.addAction(style_action)
if True in list(map(lambda item: isinstance(item, LabelItem), items)) and False in list(map(lambda item: item.parentItem() is None, items)):
# action only for port labels
reset_label_position_action = QtGui.QAction("Reset position", menu)
reset_label_position_action = QtWidgets.QAction("Reset position", menu)
reset_label_position_action.setIcon(get_icon("reset.svg"))
reset_label_position_action.triggered.connect(self.resetLabelPositionActionSlot)
menu.addAction(reset_label_position_action)
@@ -932,41 +932,41 @@ class GraphicsView(QtWidgets.QGraphicsView):
if True in list(map(lambda item: item.parentItem() is None, items)):
if len(items) > 1:
horizontal_align_action = QtGui.QAction("Align horizontally", menu)
horizontal_align_action = QtWidgets.QAction("Align horizontally", menu)
horizontal_align_action.setIcon(get_icon("horizontally.svg"))
horizontal_align_action.triggered.connect(self.horizontalAlignmentSlot)
menu.addAction(horizontal_align_action)
vertical_align_action = QtGui.QAction("Align vertically", menu)
vertical_align_action = QtWidgets.QAction("Align vertically", menu)
vertical_align_action.setIcon(get_icon("vertically.svg"))
vertical_align_action.triggered.connect(self.verticalAlignmentSlot)
menu.addAction(vertical_align_action)
raise_layer_action = QtGui.QAction("Raise one layer", menu)
raise_layer_action = QtWidgets.QAction("Raise one layer", menu)
raise_layer_action.setIcon(get_icon("raise_z_value.svg"))
raise_layer_action.triggered.connect(self.raiseLayerActionSlot)
menu.addAction(raise_layer_action)
lower_layer_action = QtGui.QAction("Lower one layer", menu)
lower_layer_action = QtWidgets.QAction("Lower one layer", menu)
lower_layer_action.setIcon(get_icon("lower_z_value.svg"))
lower_layer_action.triggered.connect(self.lowerLayerActionSlot)
menu.addAction(lower_layer_action)
if len(items) > 1:
lock_action = QtGui.QAction("Lock or unlock items", menu)
lock_action = QtWidgets.QAction("Lock or unlock items", menu)
lock_action.setIcon(get_icon("lock.svg"))
else:
item = items[0]
if item.flags() & QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable:
lock_action = QtGui.QAction("Lock item", menu)
if item.flags() & QtWidgets.QGraphicsItem.ItemIsMovable:
lock_action = QtWidgets.QAction("Lock item", menu)
lock_action.setIcon(get_icon("lock.svg"))
else:
lock_action = QtGui.QAction("Unlock item", menu)
lock_action = QtWidgets.QAction("Unlock item", menu)
lock_action.setIcon(get_icon("unlock.svg"))
lock_action.triggered.connect(self.lockActionSlot)
menu.addAction(lock_action)
delete_action = QtGui.QAction("Delete", menu)
delete_action = QtWidgets.QAction("Delete", menu)
delete_action.setIcon(get_icon("delete.svg"))
delete_action.triggered.connect(self.deleteActionSlot)
menu.addAction(delete_action)
@@ -1011,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
@@ -1033,15 +1053,11 @@ class GraphicsView(QtWidgets.QGraphicsView):
for item in self.scene().selectedItems():
if isinstance(item, NodeItem) and item.node().initialized():
new_hostname, ok = QtWidgets.QInputDialog.getText(self, "Change hostname", "Hostname:", QtWidgets.QLineEdit.EchoMode.Normal, item.node().name())
new_hostname, ok = QtWidgets.QInputDialog.getText(self, "Change hostname", "Hostname:", QtWidgets.QLineEdit.Normal, item.node().name())
if ok:
if not new_hostname.strip():
QtWidgets.QMessageBox.critical(self, "Change hostname", "Hostname cannot be blank")
continue
if hasattr(item.node(), "validateHostname") and not LocalConfig.instance().experimental():
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):
@@ -1057,7 +1073,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
if items:
dialog = SymbolSelectionDialog(self, items)
dialog.show()
dialog.exec()
dialog.exec_()
def showInFileManagerSlot(self):
"""
@@ -1074,13 +1090,13 @@ 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
else:
reply = QtWidgets.QMessageBox.information(self, "Show in file manager", "The device directory is located in {} on {}\n\nCopy path to clipboard?".format(node_dir, node.compute().name()), QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No)
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
reply = QtWidgets.QMessageBox.information(self, "Show in file manager", "The device directory is located in {} on {}\n\nCopy path to clipboard?".format(node_dir, node.compute().name()), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
QtWidgets.QApplication.clipboard().setText(node_dir)
break
@@ -1137,7 +1153,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
delay = self._main_window.settings()["delay_console_all"]
counter = 0
for name in sorted(nodes.keys(), key=str.casefold):
for name in sorted(nodes.keys()):
node = nodes[name]
callback = qpartial(self.consoleToNode, node)
self._main_window.run_later(counter, callback)
@@ -1155,10 +1171,10 @@ class GraphicsView(QtWidgets.QGraphicsView):
proceed = QtWidgets.QMessageBox.question(self,
"Console to all nodes",
"You are about to open console windows to {} nodes. Are you sure?".format(nb_items),
QtWidgets.QMessageBox.StandardButton.Yes,
QtWidgets.QMessageBox.StandardButton.No)
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if proceed == QtWidgets.QMessageBox.StandardButton.No:
if proceed == QtWidgets.QMessageBox.No:
return
self.consoleFromItems(items)
@@ -1216,7 +1232,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
delay = self._main_window.settings()["delay_console_all"]
counter = 0
for name in sorted(nodes.keys(), key=str.casefold):
for name in sorted(nodes.keys()):
node = nodes[name]
callback = qpartial(self.consoleToNode, node, aux=True)
self._main_window.run_later(counter, callback)
@@ -1255,7 +1271,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
path, _ = QtWidgets.QFileDialog.getOpenFileName(self,
"Import {}".format(os.path.basename(config_file)),
self._import_config_directory,
"All files (*);;Config files (*.cfg)",
"All files (*.*);;Config files (*.cfg)",
"Config files (*.cfg)")
if not path:
continue
@@ -1284,7 +1300,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
continue
dialog = FileEditorDialog(item.node(), config_file, parent=self)
dialog.show()
dialog.exec()
dialog.exec_()
def exportConfigActionSlot(self):
"""
@@ -1302,7 +1318,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
for item in items:
for config_file in item.node().configFiles():
path, ok = QtWidgets.QFileDialog.getSaveFileName(self, "Export file", os.path.join(self._export_config_directory, item.node().name() + "_" + os.path.basename(config_file)), "All files (*);;Config files (*.cfg)")
path, ok = QtWidgets.QFileDialog.getSaveFileName(self, "Export file", os.path.join(self._export_config_directory, item.node().name() + "_" + os.path.basename(config_file)), "All files (*.*);;Config files (*.cfg)")
if not path:
continue
self._export_config_directory = os.path.dirname(path)
@@ -1322,7 +1338,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
if isinstance(item, NodeItem):
dialog = NodeInfoDialog(item.node(), parent=self)
dialog.show()
dialog.exec()
dialog.exec_()
def bringToFrontSlot(self):
"""
@@ -1363,7 +1379,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
if idlepcs and idlepcs[0] != "0x0":
dialog = IdlePCDialog(router, idlepcs, parent=self)
dialog.show()
dialog.exec()
dialog.exec_()
else:
QtWidgets.QMessageBox.critical(self, "Idle-PC", "Sorry no Idle-PC values could be computed, please check again with Cisco IOS in a different state")
@@ -1446,7 +1462,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
if items:
style_dialog = StyleEditorDialog(self._main_window, items)
style_dialog.show()
style_dialog.exec()
style_dialog.exec_()
def textEditActionSlot(self):
"""
@@ -1461,7 +1477,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
if items:
text_edit_dialog = TextEditorDialog(self._main_window, items)
text_edit_dialog.show()
text_edit_dialog.exec()
text_edit_dialog.exec_()
def resetLabelPositionActionSlot(self):
"""
@@ -1519,7 +1535,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
for item in self.scene().selectedItems():
if item.parentItem() is None:
if not (item.flags() & QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable):
if not (item.flags() & QtWidgets.QGraphicsItem.ItemIsMovable):
log.error("Cannot move object to a upper layer because it is locked")
continue
item.setZValue(item.zValue() + 1)
@@ -1534,7 +1550,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
for item in self.scene().selectedItems():
if item.parentItem() is None:
if not (item.flags() & QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable):
if not (item.flags() & QtWidgets.QGraphicsItem.ItemIsMovable):
log.error("Cannot move object to a lower layer because it is locked")
continue
item.setZValue(item.zValue() - 1)
@@ -1580,8 +1596,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
else:
question = "Do you want to permanently delete {}?".format(selected_nodes[0].name())
reply = QtWidgets.QMessageBox.question(self, "Delete", question,
QtWidgets.QMessageBox.StandardButton.Yes, QtWidgets.QMessageBox.StandardButton.No)
if reply == QtWidgets.QMessageBox.StandardButton.No:
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
return
for item in self.scene().selectedItems():
if isinstance(item, NodeItem):
@@ -1617,7 +1633,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
"""
Ask the server to create a node using this template
"""
pos = self.mapToScene(pos.toPoint())
pos = self.mapToScene(pos)
return TemplateManager().instance().createNodeFromTemplateId(self._topology.project(), template_id, pos.x(), pos.y())
def createNodeItem(self, node, symbol, x, y):
@@ -1648,15 +1664,15 @@ class GraphicsView(QtWidgets.QGraphicsView):
def createDrawingItem(self, type, x, y, z, locked=False, rotation=0, svg=None, drawing_id=None):
if type == "ellipse":
item = EllipseItem(pos=QtCore.QPointF(x, y), z=z, locked=locked, rotation=rotation, project=self._topology.project(), drawing_id=drawing_id, svg=svg)
item = EllipseItem(pos=QtCore.QPoint(x, y), z=z, locked=locked, rotation=rotation, project=self._topology.project(), drawing_id=drawing_id, svg=svg)
elif type == "rect":
item = RectangleItem(pos=QtCore.QPointF(x, y), z=z, locked=locked, rotation=rotation, project=self._topology.project(), drawing_id=drawing_id, svg=svg)
item = RectangleItem(pos=QtCore.QPoint(x, y), z=z, locked=locked, rotation=rotation, project=self._topology.project(), drawing_id=drawing_id, svg=svg)
elif type == "line":
item = LineItem(pos=QtCore.QPointF(x, y), dst=QtCore.QPoint(200, 0), z=z, locked=locked, rotation=rotation, project=self._topology.project(), drawing_id=drawing_id, svg=svg)
item = LineItem(pos=QtCore.QPoint(x, y), dst=QtCore.QPoint(200, 0), z=z, locked=locked, rotation=rotation, project=self._topology.project(), drawing_id=drawing_id, svg=svg)
elif type == "image":
item = ImageItem(pos=QtCore.QPointF(x, y), z=z, rotation=rotation, locked=locked, project=self._topology.project(), drawing_id=drawing_id, svg=svg)
item = ImageItem(pos=QtCore.QPoint(x, y), z=z, rotation=rotation, locked=locked, project=self._topology.project(), drawing_id=drawing_id, svg=svg)
elif type == "text":
item = TextItem(pos=QtCore.QPointF(x, y), z=z, rotation=rotation, locked=locked, project=self._topology.project(), drawing_id=drawing_id, svg=svg)
item = TextItem(pos=QtCore.QPoint(x, y), z=z, rotation=rotation, locked=locked, project=self._topology.project(), drawing_id=drawing_id, svg=svg)
if drawing_id is None:
item.create()
@@ -1665,39 +1681,11 @@ class GraphicsView(QtWidgets.QGraphicsView):
self._topology.addDrawing(item)
return item
@QtCore.Property(QtGui.QColor)
def drawingGridColor(self):
"""Returns the drawing grid color"""
return self._drawing_grid_color
@drawingGridColor.setter
def drawingGridColor(self, color):
"""Sets the drawing grid color"""
self._drawing_grid_color = color
self.viewport().update()
@QtCore.Property(QtGui.QColor)
def nodeGridColor(self):
"""Returns the node grid color"""
return self._node_grid_color
@nodeGridColor.setter
def nodeGridColor(self, color):
"""Sets the node grid color"""
self._node_grid_color = color
self.viewport().update()
def resetGridColors(self):
"""Reset grid colors to defaults"""
self._drawing_grid_color = self.DEFAULT_DRAWING_GRID_COLOR
self._node_grid_color = self.DEFAULT_NODE_GRID_COLOR
self.viewport().update()
def drawBackground(self, painter, rect):
super().drawBackground(painter, rect)
if self._main_window.uiShowGridAction.isChecked():
grids = [(self.drawingGridSize(), self._drawing_grid_color),
(self.nodeGridSize(), self._node_grid_color)]
grids = [(self.drawingGridSize(), QtGui.QColor(208, 208, 208)),
(self.nodeGridSize(), QtGui.QColor(190, 190, 190))]
painter.save()
for (grid, colour) in grids:
if not grid:

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))
@@ -94,9 +93,9 @@ class ImageManager:
reply = QtWidgets.QMessageBox.question(parent,
'Image',
'Would you like to copy {} to the default images directory'.format(source_filename),
QtWidgets.QMessageBox.StandardButton.Yes,
QtWidgets.QMessageBox.StandardButton.No)
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
try:
os.makedirs(destination_directory, exist_ok=True)
except OSError as e:
@@ -106,7 +105,7 @@ class ImageManager:
worker = FileCopyWorker(source_path, destination_path)
progress_dialog = ProgressDialog(worker, 'Image', 'Copying {}'.format(source_filename), 'Cancel', busy=True, parent=parent)
progress_dialog.show()
progress_dialog.exec()
progress_dialog.exec_()
errors = progress_dialog.errors()
if errors:
QtWidgets.QMessageBox.critical(parent, 'Image', '{}'.format(''.join(errors)))
@@ -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

@@ -27,12 +27,12 @@ log = logging.getLogger(__name__)
class DrawingItem:
# Map QT stroke to SVG style
QT_DASH_TO_SVG = {
QtCore.Qt.PenStyle.SolidLine: "",
QtCore.Qt.PenStyle.NoPen: None,
QtCore.Qt.PenStyle.DashLine: "25, 25",
QtCore.Qt.PenStyle.DotLine: "5, 25",
QtCore.Qt.PenStyle.DashDotLine: "5, 25, 25",
QtCore.Qt.PenStyle.DashDotDotLine: "25, 25, 5, 25, 5"
QtCore.Qt.SolidLine: "",
QtCore.Qt.NoPen: None,
QtCore.Qt.DashLine: "25, 25",
QtCore.Qt.DotLine: "5, 25",
QtCore.Qt.DashDotLine: "5, 25, 25",
QtCore.Qt.DashDotDotLine: "25, 25, 5, 25, 5"
}
show_layer = False
@@ -44,11 +44,10 @@ class DrawingItem:
def __init__(self, project=None, pos=None, drawing_id=None, svg=None, z=0, locked=False, rotation=0, **kws):
self._id = drawing_id
self._deleting = False
self._allow_snap_to_grid = True
self._locked = locked
if self._id is None:
self._id = str(uuid.uuid4())
self.setFlags(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable | QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsFocusable | QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsSelectable | QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges)
self.setFlags(QtWidgets.QGraphicsItem.ItemIsMovable | QtWidgets.QGraphicsItem.ItemIsFocusable | QtWidgets.QGraphicsItem.ItemIsSelectable | QtWidgets.QGraphicsItem.ItemSendsGeometryChanges)
from ..main_window import MainWindow
self._graphics_view = MainWindow.instance().uiGraphicsView
@@ -57,7 +56,7 @@ class DrawingItem:
self._project = project
# Store a hash of the SVG to avoid him
# to be sent if he doesn't change
# to be send if he doesn't change
self._hash_svg = None
if pos:
@@ -93,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):
@@ -106,14 +105,9 @@ class DrawingItem:
"""
if error:
if "doesn't exist" in result.get("message", ""):
log.warning("Drawing not found on server, recreating: {}".format(self._id))
self._id = None
self.create()
return True
log.error("Error while updating drawing: {}".format(result["message"]))
return False
self.setPos(QtCore.QPointF(result["x"], result["y"]))
self.setPos(QtCore.QPoint(result["x"], result["y"]))
self.setZValue(result["z"])
self.setLocked(result["locked"])
self.setRotation(result["rotation"])
@@ -129,21 +123,18 @@ class DrawingItem:
"""
key = event.key()
modifiers = event.modifiers()
if key in (QtCore.Qt.Key.Key_P, QtCore.Qt.Key.Key_Plus, QtCore.Qt.Key.Key_Equal) and modifiers & QtCore.Qt.KeyboardModifier.AltModifier \
or key == QtCore.Qt.Key.Key_Plus and modifiers & QtCore.Qt.KeyboardModifier.AltModifier and modifiers & QtCore.Qt.KeyboardModifier.KeypadModifier:
if key in (QtCore.Qt.Key_P, QtCore.Qt.Key_Plus, QtCore.Qt.Key_Equal) and modifiers & QtCore.Qt.AltModifier \
or key == QtCore.Qt.Key_Plus and modifiers & QtCore.Qt.AltModifier and modifiers & QtCore.Qt.KeypadModifier:
if self.rotation() == 0:
self.setRotation(359)
else:
self.setRotation(self.rotation() - 1)
return True
elif key in (QtCore.Qt.Key.Key_M, QtCore.Qt.Key.Key_Minus) and modifiers & QtCore.Qt.KeyboardModifier.AltModifier \
or key == QtCore.Qt.Key.Key_Minus and modifiers & QtCore.Qt.KeyboardModifier.AltModifier and modifiers & QtCore.Qt.KeyboardModifier.KeypadModifier:
elif key in (QtCore.Qt.Key_M, QtCore.Qt.Key_Minus) and modifiers & QtCore.Qt.AltModifier \
or key == QtCore.Qt.Key_Minus and modifiers & QtCore.Qt.AltModifier and modifiers & QtCore.Qt.KeypadModifier:
if self.rotation() < 360.0:
self.setRotation(self.rotation() + 1)
return True
elif modifiers & QtCore.Qt.KeyboardModifier.AltModifier:
self._allow_snap_to_grid = False
return True
return False
def keyPressEvent(self, event):
@@ -156,15 +147,6 @@ class DrawingItem:
if not self.handleKeyPressEvent(event):
QtWidgets.QGraphicsItem.keyPressEvent(self, event)
def keyReleaseEvent(self, event):
"""
Handles all key release events
:param event: QKeyEvent
"""
self._allow_snap_to_grid = True
def __json__(self):
data = {
"drawing_id": self._id,
@@ -196,9 +178,9 @@ class DrawingItem:
"""
if locked is True:
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, False)
self.setFlag(self.ItemIsMovable, False)
else:
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, True)
self.setFlag(self.ItemIsMovable, True)
self._locked = locked
def deleting(self):
@@ -219,7 +201,7 @@ class DrawingItem:
"""
Deletes this drawing.
:param skip_controller: Do not replicate change on the controller (useful when it's already deleted on controller)
:param skip_controller: Do not replicate change on the controller (usefull when it's already deleted on controller)
"""
self.setDeleting()
@@ -227,19 +209,18 @@ class DrawingItem:
from ..topology import Topology
Topology.instance().removeDrawing(self)
if self._id and not skip_controller:
self._project.delete("/drawings/" + self._id, None)
self._project.delete("/drawings/" + self._id, None, body=self.__json__())
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.GraphicsItemChange.ItemPositionChange and self._main_window.uiSnapToGridAction.isChecked() \
and self._allow_snap_to_grid:
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.isActive() and self._main_window.uiSnapToGridAction.isChecked():
grid_size = self._graphics_view.drawingGridSize()
mid_x = self.boundingRect().width() / 2
value.setX((grid_size * round((value.x() + mid_x) / grid_size)) - mid_x)
mid_y = self.boundingRect().height() / 2
value.setY((grid_size * round((value.y()+mid_y)/grid_size)) - mid_y)
if change == QtWidgets.QGraphicsItem.GraphicsItemChange.ItemSelectedChange:
if change == QtWidgets.QGraphicsItem.ItemSelectedChange:
if not value:
self.updateDrawing()
return QtWidgets.QGraphicsItem.itemChange(self, change, value)
@@ -263,10 +244,10 @@ class DrawingItem:
return
center = self.mapFromItem(self, brect.width() / 2.0, brect.height() / 2.0)
painter.setBrush(QtCore.Qt.GlobalColor.red)
painter.setPen(QtCore.Qt.GlobalColor.red)
painter.setBrush(QtCore.Qt.red)
painter.setPen(QtCore.Qt.red)
painter.drawRect(QtCore.QRectF((brect.width() / 2.0) - 10, (brect.height() / 2.0) - 10, 20, 20))
painter.setPen(QtCore.Qt.GlobalColor.black)
painter.setPen(QtCore.Qt.black)
zval = str(int(self.zValue()))
painter.drawText(QtCore.QPointF(center.x() - 4, center.y() + 4), zval)
@@ -305,9 +286,9 @@ class DrawingItem:
pen.setColor(colorFromSvg(svg.get("stroke")))
# Map SVG stroke style (border of the element to the Qt version)
if not svg.get("stroke"):
pen.setStyle(QtCore.Qt.PenStyle.NoPen)
pen.setStyle(QtCore.Qt.NoPen)
else:
pen.setStyle(QtCore.Qt.PenStyle.SolidLine)
pen.setStyle(QtCore.Qt.SolidLine)
stroke = svg.get("stroke-dasharray")
if stroke:
for (qt_stroke, svg_stroke) in self.QT_DASH_TO_SVG.items():

View File

@@ -54,14 +54,14 @@ class EthernetLinkItem(LinkItem):
try:
if self._hovered:
self.setPen(QtGui.QPen(QtCore.Qt.GlobalColor.red, self._link._link_style["width"] + 1, QtCore.Qt.PenStyle.SolidLine, QtCore.Qt.PenCapStyle.RoundCap, QtCore.Qt.PenJoinStyle.RoundJoin))
self.setPen(QtGui.QPen(QtCore.Qt.red, self._link._link_style["width"] + 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
else:
self.setPen(QtGui.QPen(QtGui.QColor(self._link._link_style["color"]), self._link._link_style["width"], QtCore.Qt.PenStyle(self._link._link_style["type"]), QtCore.Qt.PenCapStyle.RoundCap, QtCore.Qt.PenJoinStyle.RoundJoin))
self.setPen(QtGui.QPen(QtGui.QColor(self._link._link_style["color"]), self._link._link_style["width"], self._link._link_style["type"], QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
except:
if self._hovered:
self.setPen(QtGui.QPen(QtCore.Qt.GlobalColor.red, self._pen_width + 1, QtCore.Qt.PenStyle.SolidLine, QtCore.Qt.PenCapStyle.RoundCap, QtCore.Qt.PenJoinStyle.RoundJoin))
self.setPen(QtGui.QPen(QtCore.Qt.red, self._pen_width + 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
else:
self.setPen(QtGui.QPen(QtGui.QColor("#000000"), self._pen_width, QtCore.Qt.PenStyle.SolidLine, QtCore.Qt.PenCapStyle.RoundCap, QtCore.Qt.PenJoinStyle.RoundJoin))
self.setPen(QtGui.QPen(QtGui.QColor("#000000"), self._pen_width, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
# draw a line between nodes
path = QtGui.QPainterPath(self.source)
@@ -121,17 +121,17 @@ class EthernetLinkItem(LinkItem):
if self._link.suspended() or self._source_port.status() == Port.suspended:
# link or port is suspended
color = QtCore.Qt.GlobalColor.yellow
shape = QtCore.Qt.PenCapStyle.RoundCap
color = QtCore.Qt.yellow
shape = QtCore.Qt.RoundCap
elif self._source_port.status() == Port.started:
# port is active
color = QtCore.Qt.GlobalColor.green
shape = QtCore.Qt.PenCapStyle.RoundCap
color = QtCore.Qt.green
shape = QtCore.Qt.RoundCap
else:
color = QtCore.Qt.GlobalColor.red
shape = QtCore.Qt.PenCapStyle.SquareCap
color = QtCore.Qt.red
shape = QtCore.Qt.SquareCap
painter.setPen(QtGui.QPen(color, self._point_size, QtCore.Qt.PenStyle.SolidLine, shape, QtCore.Qt.PenJoinStyle.MiterJoin))
painter.setPen(QtGui.QPen(color, self._point_size, QtCore.Qt.SolidLine, shape, QtCore.Qt.MiterJoin))
point1 = QtCore.QPointF(self.source + self.edge_offset) + QtCore.QPointF((self.dx * self._source_collision_offset) / self.length, (self.dy * self._source_collision_offset) / self.length)
# avoid any collision of the status point with the source node
@@ -154,27 +154,27 @@ class EthernetLinkItem(LinkItem):
self._source_port.setLabel(source_port_label)
if self._draw_port_labels:
source_port_label.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, not self._source_item.locked())
source_port_label.setFlag(source_port_label.ItemIsMovable, not self._source_item.locked())
source_port_label.show()
else:
source_port_label.hide()
if self._settings["draw_link_status_points"] and self.pen().style() != QtCore.Qt.PenStyle.NoPen:
if self._settings["draw_link_status_points"] and self.pen().style() != QtCore.Qt.NoPen:
painter.drawPoint(point1)
if self._link.suspended() or self._destination_port.status() == Port.suspended:
# link or port is suspended
color = QtCore.Qt.GlobalColor.yellow
shape = QtCore.Qt.PenCapStyle.RoundCap
color = QtCore.Qt.yellow
shape = QtCore.Qt.RoundCap
elif self._destination_port.status() == Port.started:
# port is active
color = QtCore.Qt.GlobalColor.green
shape = QtCore.Qt.PenCapStyle.RoundCap
color = QtCore.Qt.green
shape = QtCore.Qt.RoundCap
else:
color = QtCore.Qt.GlobalColor.red
shape = QtCore.Qt.PenCapStyle.SquareCap
color = QtCore.Qt.red
shape = QtCore.Qt.SquareCap
painter.setPen(QtGui.QPen(color, self._point_size, QtCore.Qt.PenStyle.SolidLine, shape, QtCore.Qt.PenJoinStyle.MiterJoin))
painter.setPen(QtGui.QPen(color, self._point_size, QtCore.Qt.SolidLine, shape, QtCore.Qt.MiterJoin))
point2 = QtCore.QPointF(self.destination - self.edge_offset) - QtCore.QPointF((self.dx * self._destination_collision_offset) / self.length, (self.dy * self._destination_collision_offset) / self.length)
# avoid any collision of the status point with the destination node
@@ -197,12 +197,12 @@ class EthernetLinkItem(LinkItem):
self._destination_port.setLabel(destination_port_label)
if self._draw_port_labels:
destination_port_label.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, not self._destination_item.locked())
destination_port_label.setFlag(destination_port_label.ItemIsMovable, not self._destination_item.locked())
destination_port_label.show()
else:
destination_port_label.hide()
if self._settings["draw_link_status_points"] and self.pen().style() != QtCore.Qt.PenStyle.NoPen:
if self._settings["draw_link_status_points"] and self.pen().style() != QtCore.Qt.NoPen:
painter.drawPoint(point2)
self._drawSymbol()

View File

@@ -19,12 +19,12 @@
Graphical representation of an image on the QGraphicsScene.
"""
from ..qt import QtSvgWidgets
from ..qt import QtSvg
from ..qt.qimage_svg_renderer import QImageSvgRenderer
from .drawing_item import DrawingItem
class ImageItem(QtSvgWidgets.QGraphicsSvgItem, DrawingItem):
class ImageItem(QtSvg.QGraphicsSvgItem, DrawingItem):
"""
Class to insert an image on the scene.

View File

@@ -42,7 +42,7 @@ class LabelItem(QtWidgets.QGraphicsTextItem):
qt_font.fromString(view_settings["default_label_font"])
self.setDefaultTextColor(QtGui.QColor(view_settings["default_label_color"]))
self.setFont(qt_font)
self.setFlags(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable | QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsSelectable)
self.setFlags(QtWidgets.QGraphicsItem.ItemIsMovable | QtWidgets.QGraphicsItem.ItemIsSelectable)
self.setZValue(2)
self._editable = True
@@ -91,12 +91,12 @@ class LabelItem(QtWidgets.QGraphicsTextItem):
key = event.key()
modifiers = event.modifiers()
if key in (QtCore.Qt.Key.Key_P, QtCore.Qt.Key.Key_Plus, QtCore.Qt.Key.Key_Equal) and modifiers & QtCore.Qt.KeyboardModifier.AltModifier \
or key == QtCore.Qt.Key.Key_Plus and modifiers & QtCore.Qt.KeyboardModifier.AltModifier and modifiers & QtCore.Qt.KeyboardModifier.KeypadModifier:
if key in (QtCore.Qt.Key_P, QtCore.Qt.Key_Plus, QtCore.Qt.Key_Equal) and modifiers & QtCore.Qt.AltModifier \
or key == QtCore.Qt.Key_Plus and modifiers & QtCore.Qt.AltModifier and modifiers & QtCore.Qt.KeypadModifier:
if self.rotation() > -360.0:
self.setRotation(self.rotation() - 1)
elif key in (QtCore.Qt.Key.Key_M, QtCore.Qt.Key.Key_Minus) and modifiers & QtCore.Qt.KeyboardModifier.AltModifier \
or key == QtCore.Qt.Key.Key_Minus and modifiers & QtCore.Qt.KeyboardModifier.AltModifier and modifiers & QtCore.Qt.KeyboardModifier.KeypadModifier:
elif key in (QtCore.Qt.Key_M, QtCore.Qt.Key_Minus) and modifiers & QtCore.Qt.AltModifier \
or key == QtCore.Qt.Key_Minus and modifiers & QtCore.Qt.AltModifier and modifiers & QtCore.Qt.KeypadModifier:
if self.rotation() < 360.0:
self.setRotation(self.rotation() + 1)
else:
@@ -107,11 +107,11 @@ class LabelItem(QtWidgets.QGraphicsTextItem):
Edit mode for this note.
"""
self.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.TextEditorInteraction)
self.setTextInteractionFlags(QtCore.Qt.TextEditorInteraction)
self.setSelected(True)
self.setFocus()
cursor = self.textCursor()
cursor.select(QtGui.QTextCursor.SelectionType.Document)
cursor.select(QtGui.QTextCursor.Document)
self.setTextCursor(cursor)
def mouseDoubleClickEvent(self, event):
@@ -131,12 +131,12 @@ class LabelItem(QtWidgets.QGraphicsTextItem):
:param event: QFocusEvent instance
"""
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsFocusable, False)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable, False)
cursor = self.textCursor()
if cursor.hasSelection():
cursor.clearSelection()
self.setTextCursor(cursor)
self.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.NoTextInteraction)
self.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
if not self.toPlainText():
# delete the note if empty
self.delete()
@@ -163,10 +163,10 @@ class LabelItem(QtWidgets.QGraphicsTextItem):
return
center = self.mapFromItem(self, brect.width() / 2.0, brect.height() / 2.0)
painter.setBrush(QtCore.Qt.GlobalColor.red)
painter.setPen(QtCore.Qt.GlobalColor.red)
painter.setBrush(QtCore.Qt.red)
painter.setPen(QtCore.Qt.red)
painter.drawRect(QtCore.QRectF((brect.width() / 2.0) - 10, (brect.height() / 2.0) - 10, 20, 20))
painter.setPen(QtCore.Qt.GlobalColor.black)
painter.setPen(QtCore.Qt.black)
zval = str(int(self.zValue()))
painter.drawText(QtCore.QPointF(center.x(), center.y()), zval)
@@ -213,7 +213,7 @@ class LabelItem(QtWidgets.QGraphicsTextItem):
:param change: GraphicsItemChange type
:param value: value of the change
"""
if change == QtWidgets.QGraphicsItem.GraphicsItemChange.ItemSelectedChange:
if change == QtWidgets.QGraphicsItem.ItemSelectedChange:
if value == 0:
self.item_unselected_signal.emit()
return super().itemChange(change, value)

View File

@@ -44,7 +44,7 @@ class LineItem(QtWidgets.QGraphicsLineItem, DrawingItem):
0,
dst.x(),
dst.y())
pen = QtGui.QPen(QtCore.Qt.GlobalColor.black, 2, QtCore.Qt.PenStyle.SolidLine, QtCore.Qt.PenCapStyle.RoundCap, QtCore.Qt.PenJoinStyle.RoundJoin)
pen = QtGui.QPen(QtCore.Qt.black, 2, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)
self.setPen(pen)
else:
self.fromSvg(svg)
@@ -124,19 +124,19 @@ class LineItem(QtWidgets.QGraphicsLineItem, DrawingItem):
if self._isHorizontalLine():
if event.pos().x() > (self.line().x2() - self._border):
self._graphics_view.setCursor(QtCore.Qt.CursorShape.SizeHorCursor)
self._graphics_view.setCursor(QtCore.Qt.SizeHorCursor)
elif event.pos().x() < self._border:
self._graphics_view.setCursor(QtCore.Qt.CursorShape.SizeHorCursor)
self._graphics_view.setCursor(QtCore.Qt.SizeHorCursor)
else:
self._graphics_view.setCursor(QtCore.Qt.CursorShape.SizeAllCursor)
self._graphics_view.setCursor(QtCore.Qt.SizeAllCursor)
# Vertical line
else:
if event.pos().y() > (self.line().y2() - self._border):
self._graphics_view.setCursor(QtCore.Qt.CursorShape.SizeVerCursor)
self._graphics_view.setCursor(QtCore.Qt.SizeVerCursor)
elif event.pos().y() < self._border:
self._graphics_view.setCursor(QtCore.Qt.CursorShape.SizeVerCursor)
self._graphics_view.setCursor(QtCore.Qt.SizeVerCursor)
else:
self._graphics_view.setCursor(QtCore.Qt.CursorShape.SizeAllCursor)
self._graphics_view.setCursor(QtCore.Qt.SizeAllCursor)
def mouseMoveEvent(self, event):
"""
@@ -177,17 +177,17 @@ class LineItem(QtWidgets.QGraphicsLineItem, DrawingItem):
self.update()
if self._isHorizontalLine():
if event.pos().x() > (self.line().x2() - self._border):
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, False)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, False)
self._edge = "right"
elif event.pos().x() < (self.line().x1() + self._border):
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, False)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, False)
self._edge = "left"
else:
if event.pos().y() > (self.line().y2() - self._border):
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, False)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, False)
self._edge = "top"
elif event.pos().y() < (self.line().y1() + self._border):
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, False)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, False)
self._edge = "bottom"
super().mousePressEvent(event)
@@ -199,7 +199,7 @@ class LineItem(QtWidgets.QGraphicsLineItem, DrawingItem):
"""
self.update()
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable)
self._edge = None
super().mouseReleaseEvent(event)
@@ -213,4 +213,4 @@ class LineItem(QtWidgets.QGraphicsLineItem, DrawingItem):
# locked objects don't need cursors
if not self.locked():
self._graphics_view.setCursor(QtCore.Qt.CursorShape.ArrowCursor)
self._graphics_view.setCursor(QtCore.Qt.ArrowCursor)

View File

@@ -21,7 +21,7 @@ Link items are graphical representation of a link on the QGraphicsScene
"""
import math
from ..qt import QtCore, QtGui, QtWidgets, QtSvgWidgets, qslot, sip_is_deleted
from ..qt import QtCore, QtGui, QtWidgets, QtSvg, qslot, sip_is_deleted
from ..packet_capture import PacketCapture
from ..dialogs.filter_dialog import FilterDialog
@@ -29,11 +29,11 @@ from ..dialogs.style_editor_dialog_link import StyleEditorDialogLink
from ..utils.get_icon import get_icon
class SvgIconItem(QtSvgWidgets.QGraphicsSvgItem):
class SvgIconItem(QtSvg.QGraphicsSvgItem):
def __init__(self, symbol, parent):
QtSvgWidgets.QGraphicsSvgItem.__init__(self, symbol, parent)
QtSvg.QGraphicsSvgItem.__init__(self, symbol, parent)
def mousePressEvent(self, event):
@@ -56,7 +56,7 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
"""
_draw_port_labels = False
delete_link_item_signal = QtCore.Signal(str)
delete_link_item_signal = QtCore.pyqtSignal(str)
def __init__(self, source_item, source_port, destination_item, destination_port, link=None, adding_flag=False):
@@ -102,7 +102,7 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
self._link = link
self._link.updated_link_signal.connect(self._drawSymbol)
self._link.delete_link_signal.connect(self._linkDeletedSlot)
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsFocusable)
self.setFlag(self.ItemIsFocusable)
source_item.addLink(self)
destination_item.addLink(self)
self.setCustomToolTip()
@@ -132,7 +132,7 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
def _filterActionSlot(self, *args):
dialog = FilterDialog(self._main_window, self._link)
dialog.show()
dialog.exec()
dialog.exec_()
@qslot
def _suspendActionSlot(self, *args):
@@ -142,7 +142,7 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
def _styleActionSlot(self, *args):
style_dialog = StyleEditorDialogLink(self, self._main_window)
style_dialog.show()
style_dialog.exec()
style_dialog.exec_()
def setLinkStyle(self, link_style):
self._link._link_style["color"] = link_style["color"]
@@ -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.
@@ -239,57 +245,63 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
if not self._link.capturing():
# start capture
start_capture_action = QtGui.QAction("Start capture", menu)
start_capture_action = QtWidgets.QAction("Start capture", menu)
start_capture_action.setIcon(get_icon('capture-start.svg'))
start_capture_action.triggered.connect(self._startCaptureActionSlot)
menu.addAction(start_capture_action)
if self._link.capturing():
# stop capture
stop_capture_action = QtGui.QAction("Stop capture", menu)
stop_capture_action = QtWidgets.QAction("Stop capture", menu)
stop_capture_action.setIcon(get_icon('capture-stop.svg'))
stop_capture_action.triggered.connect(self._stopCaptureActionSlot)
menu.addAction(stop_capture_action)
# start wireshark
start_wireshark_action = QtGui.QAction("Start Wireshark", menu)
start_wireshark_action = QtWidgets.QAction("Start Wireshark", menu)
start_wireshark_action.setIcon(QtGui.QIcon(":/icons/wireshark.png"))
start_wireshark_action.triggered.connect(self._startWiresharkActionSlot)
menu.addAction(start_wireshark_action)
if PacketCapture.instance().packetAnalyzerAvailable():
analyze_action = QtGui.QAction("Analyze capture", menu)
analyze_action = QtWidgets.QAction("Analyze capture", menu)
analyze_action.setIcon(QtGui.QIcon(':/icons/rtv.png'))
analyze_action.triggered.connect(self._analyzeCaptureActionSlot)
menu.addAction(analyze_action)
if self._link.suspended() is False:
# Edit filters
filter_action = QtGui.QAction("Packet filters", menu)
filter_action = QtWidgets.QAction("Packet filters", menu)
filter_action.setIcon(get_icon('filter.svg'))
filter_action.triggered.connect(self._filterActionSlot)
menu.addAction(filter_action)
# Suspend link
suspend_action = QtGui.QAction("Suspend", menu)
suspend_action = QtWidgets.QAction("Suspend", menu)
suspend_action.setIcon(get_icon('pause.svg'))
suspend_action.triggered.connect(self._suspendActionSlot)
menu.addAction(suspend_action)
else:
# Resume link
resume_action = QtGui.QAction("Resume", menu)
resume_action = QtWidgets.QAction("Resume", menu)
resume_action.setIcon(get_icon('start.svg'))
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 = QtGui.QAction("Style", menu)
style_action = QtWidgets.QAction("Style", menu)
style_action.setIcon(get_icon("node_conception.svg"))
style_action.triggered.connect(self._styleActionSlot)
menu.addAction(style_action)
# delete
delete_action = QtGui.QAction("Delete", menu)
delete_action = QtWidgets.QAction("Delete", menu)
delete_action.setIcon(get_icon('delete.svg'))
delete_action.triggered.connect(self._deleteActionSlot)
menu.addAction(delete_action)
@@ -302,10 +314,10 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
:param: QGraphicsSceneMouseEvent instance
"""
if event.button() == QtCore.Qt.MouseButton.RightButton and self._adding_flag:
if event.button() == QtCore.Qt.RightButton and self._adding_flag:
# send a escape key to the main window to cancel the link addition
from ..main_window import MainWindow
key = QtGui.QKeyEvent(QtCore.QEvent.Type.KeyPress, QtCore.Qt.Key.Key_Escape, QtCore.Qt.KeyboardModifier.NoModifier)
key = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Escape, QtCore.Qt.NoModifier)
QtWidgets.QApplication.sendEvent(MainWindow.instance(), key)
return
else:
@@ -320,13 +332,13 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
if not sip_is_deleted(self):
# create the contextual menu
self.setHovered(True)
self.setAcceptHoverEvents(False)
menu = QtWidgets.QMenu(parent=self.scene().parent())
menu = QtWidgets.QMenu()
self.populateLinkContextualMenu(menu)
menu.exec(QtGui.QCursor.pos())
menu.exec_(QtGui.QCursor.pos())
self.setAcceptHoverEvents(True)
self.setHovered(False)
self._hovered = False
self.adjust()
def keyPressEvent(self, event):
"""
@@ -336,10 +348,18 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
"""
# On pressing backspace or delete key, the selected link gets deleted
if event.key() == QtCore.Qt.Key.Key_Delete or event.key() == QtCore.Qt.Key.Key_Backspace:
if event.key() == QtCore.Qt.Key_Delete or event.key() == QtCore.Qt.Key_Backspace:
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

@@ -18,7 +18,7 @@
import urllib.parse
from ..qt import QtCore, QtGui, QtWidgets, QtSvgWidgets
from ..qt import QtCore, QtGui, QtWidgets, QtSvg
from ..qt.qimage_svg_renderer import QImageSvgRenderer
from ..controller import Controller
@@ -27,7 +27,7 @@ import logging
log = logging.getLogger(__name__)
class LogoItem(QtSvgWidgets.QGraphicsSvgItem):
class LogoItem(QtSvg.QGraphicsSvgItem):
"""
Margin for the logo
"""
@@ -60,8 +60,8 @@ class LogoItem(QtSvgWidgets.QGraphicsSvgItem):
self.graphicsEffect().setEnabled(False)
# set graphical settings for this item
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsFocusable)
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable)
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges)
self.setAcceptHoverEvents(True)
from ..main_window import MainWindow
@@ -83,7 +83,7 @@ class LogoItem(QtSvgWidgets.QGraphicsSvgItem):
self.setZValue(-2)
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.Type.Paint:
if event.type() == QtCore.QEvent.Paint:
self.updatePosition()
return QtWidgets.QWidget.eventFilter(self, source, event)

View File

@@ -21,7 +21,7 @@ Graphical representation of a node on the QGraphicsScene.
from ..qt import sip
from ..qt import QtCore, QtGui, QtWidgets, QtSvgWidgets, qslot
from ..qt import QtCore, QtGui, QtWidgets, QtSvg, qslot
from ..qt.qimage_svg_renderer import QImageSvgRenderer
from .label_item import LabelItem
from ..symbol import Symbol
@@ -32,7 +32,7 @@ import logging
log = logging.getLogger(__name__)
class NodeItem(QtSvgWidgets.QGraphicsSvgItem):
class NodeItem(QtSvg.QGraphicsSvgItem):
"""
Node for the scene.
@@ -51,7 +51,6 @@ class NodeItem(QtSvgWidgets.QGraphicsSvgItem):
self._links = []
self._symbol = None
self._locked = False
self._allow_snap_to_grid = True
# says if the attached node has been initialized
# by the server.
@@ -60,7 +59,7 @@ class NodeItem(QtSvgWidgets.QGraphicsSvgItem):
# node label
self._node_label = None
self.setPos(QtCore.QPointF(self._node.x(), self._node.y()))
self.setPos(QtCore.QPoint(self._node.x(), self._node.y()))
# Temporary symbol during loading
renderer = QImageSvgRenderer(":/icons/reload.svg")
@@ -74,10 +73,10 @@ class NodeItem(QtSvgWidgets.QGraphicsSvgItem):
self.graphicsEffect().setEnabled(False)
# set graphical settings for this node
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable)
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsSelectable)
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsFocusable)
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable)
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges)
self.setAcceptHoverEvents(True)
# update z value and locked state
@@ -109,9 +108,6 @@ class NodeItem(QtSvgWidgets.QGraphicsSvgItem):
if node.initialized():
self.createdSlot(node.id())
if self._main_window.uiSnapToGridAction.isChecked():
self.setPos(QtCore.QPointF(self._node.x() + 0.1, self._node.y()))
def updateNode(self):
"""
Sync change to the node
@@ -223,7 +219,7 @@ class NodeItem(QtSvgWidgets.QGraphicsSvgItem):
:param base_node_id: base node identifier (integer)
"""
self.setPos(QtCore.QPointF(self._node.x(), self._node.y()))
self.setPos(QtCore.QPoint(self._node.x(), self._node.y()))
self.setSymbol(self._node.symbol())
self.update()
@@ -384,7 +380,7 @@ class NodeItem(QtSvgWidgets.QGraphicsSvgItem):
self._node_label.setRotation(label_data.get("rotation", 0))
if self._node.locked():
self._node_label.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, False)
self._node_label.setFlag(self.ItemIsMovable, False)
if label_data["x"] is None:
self._centerLabel()
@@ -402,7 +398,7 @@ class NodeItem(QtSvgWidgets.QGraphicsSvgItem):
"""
self._selected_port = None
menu = QtWidgets.QMenu(parent=self.scene().parent())
menu = QtWidgets.QMenu()
ports = self._node.ports()
if not ports:
QtWidgets.QMessageBox.critical(self.scene().parent(), "Link", "No port available, please configure this device")
@@ -443,7 +439,7 @@ class NodeItem(QtSvgWidgets.QGraphicsSvgItem):
# add some delay before showing the menu
# https://github.com/GNS3/gns3-gui/issues/3169
QtCore.QThread.msleep(100)
menu.exec(pos)
menu.exec_(pos)
return self._selected_port
def selectedPortSlot(self, action):
@@ -470,8 +466,7 @@ class NodeItem(QtSvgWidgets.QGraphicsSvgItem):
:param value: value of the change
"""
if change == QtWidgets.QGraphicsItem.GraphicsItemChange.ItemPositionChange and self._main_window.uiSnapToGridAction.isChecked() \
and self._allow_snap_to_grid:
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.isActive() and self._main_window.uiSnapToGridAction.isChecked():
grid_size = self._main_window.uiGraphicsView.nodeGridSize()
mid_x = self.boundingRect().width() / 2
value.setX((grid_size * round((value.x() + mid_x) / grid_size)) - mid_x)
@@ -479,7 +474,7 @@ class NodeItem(QtSvgWidgets.QGraphicsSvgItem):
value.setY((grid_size * round((value.y() + mid_y) / grid_size)) - mid_y)
# dynamically change the renderer when this node item is selected/unselected.
if change == QtWidgets.QGraphicsItem.GraphicsItemChange.ItemSelectedChange:
if change == QtWidgets.QGraphicsItem.ItemSelectedChange:
if value:
self.graphicsEffect().setEnabled(True)
else:
@@ -487,7 +482,7 @@ class NodeItem(QtSvgWidgets.QGraphicsSvgItem):
self.updateNode()
# adjust link item positions when this node is moving or has changed.
if change == QtWidgets.QGraphicsItem.GraphicsItemChange.ItemPositionChange or change == QtWidgets.QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged:
if change == QtWidgets.QGraphicsItem.ItemPositionChange or change == QtWidgets.QGraphicsItem.ItemPositionHasChanged:
for link in self._links:
link.adjust()
@@ -504,16 +499,16 @@ class NodeItem(QtSvgWidgets.QGraphicsSvgItem):
# don't show the selection rectangle
if not self._settings["draw_rectangle_selected_item"]:
option.state = QtWidgets.QStyle.StateFlag.State_None
option.state = QtWidgets.QStyle.State_None
super().paint(painter, option, widget)
if not self._initialized or self.show_layer:
brect = self.boundingRect()
center = self.mapFromItem(self, brect.width() / 2.0, brect.height() / 2.0)
painter.setBrush(QtCore.Qt.GlobalColor.red)
painter.setPen(QtCore.Qt.GlobalColor.red)
painter.setBrush(QtCore.Qt.red)
painter.setPen(QtCore.Qt.red)
painter.drawRect(QtCore.QRectF((brect.width() / 2.0) - 10, (brect.height() / 2.0) - 10, 20, 20))
painter.setPen(QtCore.Qt.GlobalColor.black)
painter.setPen(QtCore.Qt.black)
if self.show_layer:
text = str(int(self.zValue())) # Z value
elif self._last_error:
@@ -533,27 +528,6 @@ class NodeItem(QtSvgWidgets.QGraphicsSvgItem):
for link in self._links:
link.adjust()
def keyPressEvent(self, event):
"""
Handles all key press events
:param event: QKeyEvent
"""
if event.modifiers() & QtCore.Qt.KeyboardModifier.AltModifier:
self._allow_snap_to_grid = False
else:
super().keyPressEvent(event)
def keyReleaseEvent(self, event):
"""
Handles all key release events
:param event: QKeyEvent
"""
self._allow_snap_to_grid = True
def locked(self):
return self._locked
@@ -566,13 +540,13 @@ class NodeItem(QtSvgWidgets.QGraphicsSvgItem):
"""
if locked is True:
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, False)
self.setFlag(self.ItemIsMovable, False)
if self._node_label:
self._node_label.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, False)
self._node_label.setFlag(self.ItemIsMovable, False)
else:
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, True)
self.setFlag(self.ItemIsMovable, True)
if self._node_label:
self._node_label.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, True)
self._node_label.setFlag(self.ItemIsMovable, True)
for link in self._links:
link.adjust()
self._locked = locked

View File

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

View File

@@ -52,14 +52,14 @@ class SerialLinkItem(LinkItem):
try:
if self._hovered:
self.setPen(QtGui.QPen(QtCore.Qt.GlobalColor.red, self._link._link_style["width"] + 1, QtCore.Qt.PenStyle.SolidLine, QtCore.Qt.PenCapStyle.RoundCap, QtCore.Qt.PenJoinStyle.RoundJoin))
self.setPen(QtGui.QPen(QtCore.Qt.red, self._link._link_style["width"] + 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
else:
self.setPen(QtGui.QPen(QtGui.QColor(self._link._link_style["color"]), self._link._link_style["width"], QtCore.Qt.PenStyle(self._link._link_style["type"]), QtCore.Qt.PenCapStyle.RoundCap, QtCore.Qt.PenJoinStyle.RoundJoin))
self.setPen(QtGui.QPen(QtGui.QColor(self._link._link_style["color"]), self._link._link_style["width"], self._link._link_style["type"], QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
except:
if self._hovered:
self.setPen(QtGui.QPen(QtCore.Qt.GlobalColor.red, self._pen_width + 1, QtCore.Qt.PenStyle.SolidLine, QtCore.Qt.PenCapStyle.RoundCap, QtCore.Qt.PenJoinStyle.RoundJoin))
self.setPen(QtGui.QPen(QtCore.Qt.red, self._pen_width + 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
else:
self.setPen(QtGui.QPen(QtCore.Qt.GlobalColor.darkRed, self._pen_width, QtCore.Qt.PenStyle.SolidLine, QtCore.Qt.PenCapStyle.RoundCap, QtCore.Qt.PenJoinStyle.RoundJoin))
self.setPen(QtGui.QPen(QtCore.Qt.darkRed, self._pen_width, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
# get source to destination angle
vector_angle = math.atan2(self.dy, self.dx)
@@ -122,17 +122,17 @@ class SerialLinkItem(LinkItem):
# source point color
if self._link.suspended() or self._source_port.status() == Port.suspended:
# link or port is suspended
shape = QtCore.Qt.PenCapStyle.RoundCap
color = QtCore.Qt.GlobalColor.yellow
shape = QtCore.Qt.RoundCap
color = QtCore.Qt.yellow
elif self._source_port.status() == Port.started:
# port is active
shape = QtCore.Qt.PenCapStyle.RoundCap
color = QtCore.Qt.GlobalColor.green
shape = QtCore.Qt.RoundCap
color = QtCore.Qt.green
else:
shape = QtCore.Qt.PenCapStyle.SquareCap
color = QtCore.Qt.GlobalColor.red
shape = QtCore.Qt.SquareCap
color = QtCore.Qt.red
painter.setPen(QtGui.QPen(color, self._point_size, QtCore.Qt.PenStyle.SolidLine, shape, QtCore.Qt.PenJoinStyle.MiterJoin))
painter.setPen(QtGui.QPen(color, self._point_size, QtCore.Qt.SolidLine, shape, QtCore.Qt.MiterJoin))
source_port_label = self._source_port.label()
if source_port_label is None:
@@ -142,28 +142,28 @@ class SerialLinkItem(LinkItem):
self._source_port.setLabel(source_port_label)
if self._draw_port_labels:
source_port_label.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, not self._source_item.locked())
source_port_label.setFlag(source_port_label.ItemIsMovable, not self._source_item.locked())
source_port_label.show()
else:
source_port_label.hide()
if self._settings["draw_link_status_points"] and self.pen().style() != QtCore.Qt.PenStyle.NoPen:
if self._settings["draw_link_status_points"] and self.pen().style() != QtCore.Qt.NoPen:
painter.drawPoint(self.source_point)
# destination point color
if self._link.suspended() or self._destination_port.status() == Port.suspended:
# link or port is suspended
color = QtCore.Qt.GlobalColor.yellow
shape = QtCore.Qt.PenCapStyle.RoundCap
color = QtCore.Qt.yellow
shape = QtCore.Qt.RoundCap
elif self._destination_port.status() == Port.started:
# port is active
color = QtCore.Qt.GlobalColor.green
shape = QtCore.Qt.PenCapStyle.RoundCap
color = QtCore.Qt.green
shape = QtCore.Qt.RoundCap
else:
color = QtCore.Qt.GlobalColor.red
shape = QtCore.Qt.PenCapStyle.SquareCap
color = QtCore.Qt.red
shape = QtCore.Qt.SquareCap
painter.setPen(QtGui.QPen(color, self._point_size, QtCore.Qt.PenStyle.SolidLine, shape, QtCore.Qt.PenJoinStyle.MiterJoin))
painter.setPen(QtGui.QPen(color, self._point_size, QtCore.Qt.SolidLine, shape, QtCore.Qt.MiterJoin))
destination_port_label = self._destination_port.label()
@@ -174,12 +174,12 @@ class SerialLinkItem(LinkItem):
self._destination_port.setLabel(destination_port_label)
if self._draw_port_labels:
destination_port_label.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, not self._destination_item.locked())
destination_port_label.setFlag(destination_port_label.ItemIsMovable, not self._destination_item.locked())
destination_port_label.show()
else:
destination_port_label.hide()
if self._settings["draw_link_status_points"] and self.pen().style() != QtCore.Qt.PenStyle.NoPen:
if self._settings["draw_link_status_points"] and self.pen().style() != QtCore.Qt.NoPen:
painter.drawPoint(self.destination_point)
self._drawSymbol()

View File

@@ -44,7 +44,7 @@ class ShapeItem(DrawingItem):
if svg is None:
self.setRect(0, 0, width, height)
pen = QtGui.QPen(QtCore.Qt.GlobalColor.black, 2, QtCore.Qt.PenStyle.SolidLine, QtCore.Qt.PenCapStyle.RoundCap, QtCore.Qt.PenJoinStyle.RoundJoin)
pen = QtGui.QPen(QtCore.Qt.black, 2, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)
self.setPen(pen)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255, 255)) # default color is white and not transparent
self.setBrush(brush)
@@ -61,21 +61,21 @@ class ShapeItem(DrawingItem):
"""
self.update()
self._originally_movable = bool(self.flags() & QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable)
self._originally_movable = self.flags() & QtWidgets.QGraphicsItem.ItemIsMovable
if event.pos().x() > (self.rect().right() - self._border):
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, False)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, False)
self._edge = "right"
elif event.pos().x() < (self.rect().left() + self._border):
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, False)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, False)
self._edge = "left"
elif event.pos().y() < (self.rect().top() + self._border):
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, False)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, False)
self._edge = "top"
elif event.pos().y() > (self.rect().bottom() - self._border):
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, False)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, False)
self._edge = "bottom"
QtWidgets.QGraphicsItem.mousePressEvent(self, event)
@@ -87,7 +87,7 @@ class ShapeItem(DrawingItem):
"""
self.update()
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, self._originally_movable)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, self._originally_movable)
self._edge = None
QtWidgets.QGraphicsItem.mouseReleaseEvent(self, event)
@@ -150,15 +150,15 @@ class ShapeItem(DrawingItem):
# locked objects don't need cursors
if not self.locked():
if event.pos().x() > (self.rect().right() - self._border):
self._graphics_view.setCursor(QtCore.Qt.CursorShape.SizeHorCursor)
self._graphics_view.setCursor(QtCore.Qt.SizeHorCursor)
elif event.pos().x() < (self.rect().left() + self._border):
self._graphics_view.setCursor(QtCore.Qt.CursorShape.SizeHorCursor)
self._graphics_view.setCursor(QtCore.Qt.SizeHorCursor)
elif event.pos().y() < (self.rect().top() + self._border):
self._graphics_view.setCursor(QtCore.Qt.CursorShape.SizeVerCursor)
self._graphics_view.setCursor(QtCore.Qt.SizeVerCursor)
elif event.pos().y() > (self.rect().bottom() - self._border):
self._graphics_view.setCursor(QtCore.Qt.CursorShape.SizeVerCursor)
self._graphics_view.setCursor(QtCore.Qt.SizeVerCursor)
else:
self._graphics_view.setCursor(QtCore.Qt.CursorShape.SizeAllCursor)
self._graphics_view.setCursor(QtCore.Qt.SizeAllCursor)
def hoverLeaveEvent(self, event):
"""
@@ -169,14 +169,11 @@ class ShapeItem(DrawingItem):
# locked objects don't need cursors
if not self.locked():
self._graphics_view.setCursor(QtCore.Qt.CursorShape.ArrowCursor)
def setWidthAndHeight(self, width, height):
self.setRect(0, 0, width, height)
self._graphics_view.setCursor(QtCore.Qt.ArrowCursor)
def fromSvg(self, svg):
"""
Import element information from SVG
Import element informations from an SVG
"""
svg = ET.fromstring(svg)
width = float(svg.get("width", self.rect().width()))
@@ -184,7 +181,7 @@ class ShapeItem(DrawingItem):
self.setRect(0, 0, width, height)
pen = QtGui.QPen()
brush = QtGui.QBrush(QtCore.Qt.BrushStyle.SolidPattern)
brush = QtGui.QBrush(QtCore.Qt.SolidPattern)
if len(svg):
pen = self._penFromSVGElement(svg[0])

View File

@@ -62,11 +62,11 @@ class TextItem(QtWidgets.QGraphicsTextItem, DrawingItem):
Edit mode for this note.
"""
self.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.TextEditorInteraction)
self.setTextInteractionFlags(QtCore.Qt.TextEditorInteraction)
self.setSelected(True)
self.setFocus()
cursor = self.textCursor()
cursor.select(QtGui.QTextCursor.SelectionType.Document)
cursor.select(QtGui.QTextCursor.Document)
self.setTextCursor(cursor)
def mouseDoubleClickEvent(self, event):
@@ -85,12 +85,12 @@ class TextItem(QtWidgets.QGraphicsTextItem, DrawingItem):
:param event: QFocusEvent instance
"""
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsFocusable, False)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable, False)
cursor = self.textCursor()
if cursor.hasSelection():
cursor.clearSelection()
self.setTextCursor(cursor)
self.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.NoTextInteraction)
self.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
if not self.toPlainText():
# delete the note if empty
self.delete()
@@ -132,7 +132,7 @@ class TextItem(QtWidgets.QGraphicsTextItem, DrawingItem):
text.set("text-decoration", "underline")
text.set("fill", "#" + hex(self.defaultTextColor().rgba())[4:])
text.set("fill-opacity", str(self.defaultTextColor().alphaF()))
text.text = self.toPlainText() or " "
text.text = self.toPlainText()
svg = ET.tostring(svg, encoding="utf-8").decode("utf-8")
return svg

View File

@@ -23,7 +23,7 @@ import re
from .qt import sip
import uuid
from .qt import QtCore, QtNetwork
from .qt import QtCore
from .controller import Controller
@@ -78,27 +78,19 @@ class Link(QtCore.QObject):
self._deleting = False
self._capture_file_path = None
self._capture_file = None
self._network_manager = None
self._response_stream = None
self._capture_compute_id = None
self._initialized = False
self._filters = {}
self._suspend = False
self._nodes = []
# Boolean if True we are creating the first instance of this node
# if false the node already exist in the topology
# use to avoid erasing information when reloading
self._creator = False
# Add the default link style from the topology view settings
from .main_window import MainWindow
topology_view_settings = MainWindow.instance().uiGraphicsView.settings()
self._link_style = {
"color": topology_view_settings.get("default_link_color", "#000000"),
"width": topology_view_settings.get("default_link_width", 2),
"type": topology_view_settings.get("default_link_type", 1)
}
self._nodes = []
self._link_style = {}
body = self._prepareParams()
if self._link_id:
@@ -119,21 +111,19 @@ class Link(QtCore.QObject):
# We need to stream the pcap file content if the controller or compute is remote
if Controller.instance().isRemote() or self._capture_file_path is None:
self._capture_file = QtCore.QTemporaryFile()
self._capture_file.open(QtCore.QIODeviceBase.OpenModeFlag.WriteOnly)
self._capture_file.open(QtCore.QFile.WriteOnly)
self._capture_file.setAutoRemove(True)
self._capture_file_path = self._capture_file.fileName()
else:
self._capture_file = QtCore.QFile(self._capture_file_path)
self._capture_file.open(QtCore.QIODeviceBase.OpenModeFlag.WriteOnly)
if self._network_manager is None:
self._network_manager = QtNetwork.QNetworkAccessManager(self)
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,
networkManager=self._network_manager)
self._capture_file.open(QtCore.QFile.WriteOnly)
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
@@ -359,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)
@@ -395,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__)
@@ -93,22 +91,17 @@ class LocalConfig(QtCore.QObject):
if sys.platform.startswith("win"):
old_config_path = os.path.join(os.path.expandvars("%APPDATA%"), "GNS3", filename)
else:
xgd_config_var = "$XDG_CONFIG_HOME"
xdg_config_res = os.path.expandvars(xgd_config_var)
if xdg_config_res != xgd_config_var:
old_config_path = os.path.join(xdg_config_res, "GNS3", filename)
else:
old_config_path = os.path.join(os.path.expanduser("~"), ".config", "GNS3", filename)
old_config_path = os.path.join(os.path.expanduser("~"), ".config", "GNS3", filename)
# TODO: migrate versioned config file from a previous version of GNS3 (for instance 2.2 -> 2.3) + support profiles
if os.path.exists(old_config_path):
# migrate post version 2.2.0 configuration file
shutil.copyfile(old_config_path, self._config_file)
# reset the local server path and ubridge path
settings = LocalServerConfig.instance().loadSettings("Server", LOCAL_SERVER_SETTINGS)
settings["path"] = ""
settings["ubridge_path"] = ""
LocalServerConfig.instance().saveSettings("Server", settings)
# 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:
@@ -148,13 +141,8 @@ class LocalConfig(QtCore.QObject):
appdata = os.path.expandvars("%APPDATA%")
path = os.path.join(appdata, "GNS3", version)
else:
xgd_config_var = "$XDG_CONFIG_HOME"
xdg_config_res = os.path.expandvars(xgd_config_var)
if xdg_config_res != xgd_config_var:
path = os.path.join(xdg_config_res, "GNS3", version)
else:
home = os.path.expanduser("~")
path = os.path.join(home, ".config", "GNS3", version)
home = os.path.expanduser("~")
path = os.path.join(home, ".config", "GNS3", version)
if self._profile is not None:
path = os.path.join(path, "profiles", self._profile)
@@ -200,7 +188,7 @@ class LocalConfig(QtCore.QObject):
QtWidgets.QMessageBox.critical(False, "Version error", error_message)
# Exit immediately not clean but we want to avoid any side effect that could corrupt the file
QtCore.QTimer.singleShot(0, app.quit)
app.exec()
app.exec_()
sys.exit(1)
if "version" not in self._settings or parse_version(self._settings["version"]) < parse_version("1.4.0alpha1"):
@@ -404,6 +392,14 @@ class LocalConfig(QtCore.QObject):
from gns3.settings import GENERAL_SETTINGS
return self.loadSectionSettings("MainWindow", GENERAL_SETTINGS)["experimental_features"]
def hdpi(self):
"""
:returns: Boolean. True if hdpi is allowed
"""
from gns3.settings import GENERAL_SETTINGS
return self.loadSectionSettings("MainWindow", GENERAL_SETTINGS)["hdpi"]
def multiProfiles(self):
"""
:returns: Boolean. True if multi_profiles is enabled
@@ -418,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
@@ -485,7 +467,7 @@ class LocalConfig(QtCore.QObject):
if os.path.exists(pid_path):
try:
with open(pid_path, encoding="utf-8") as f:
with open(pid_path) as f:
pid = int(f.read())
if pid != my_pid:
try:
@@ -500,17 +482,9 @@ class LocalConfig(QtCore.QObject):
return False
else:
return True
except OSError as e:
except (OSError, ValueError) as e:
log.critical("Can't read pid file %s: %s", pid_path, str(e))
return False
except ValueError as e:
log.warning("Invalid data in pid file %s: %s", pid_path, str(e))
try:
# try removing the file since it contains invalid data
os.remove(pid_path)
except OSError:
log.critical("Can't remove pid file %s", pid_path)
return False
try:
with open(pid_path, 'w+') as f:

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
@@ -51,9 +48,9 @@ class StopLocalServerWorker(QtCore.QObject):
the server
"""
# signals to update the progress dialog.
error = QtCore.Signal(str, bool)
finished = QtCore.Signal()
updated = QtCore.Signal(int)
error = QtCore.pyqtSignal(str, bool)
finished = QtCore.pyqtSignal()
updated = QtCore.pyqtSignal(int)
def __init__(self, local_server_process):
super().__init__()
@@ -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.
@@ -171,9 +140,9 @@ class LocalServer(QtCore.QObject):
self.parent(),
"uBridge",
"uBridge requires CAP_NET_RAW capability to interact with network interfaces. Set the capability to uBridge? All users on the system will be able to read packet from the network interfaces.",
QtWidgets.QMessageBox.StandardButton.Yes,
QtWidgets.QMessageBox.StandardButton.No)
if proceed == QtWidgets.QMessageBox.StandardButton.Yes:
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if proceed == QtWidgets.QMessageBox.Yes:
sudo(["setcap", "cap_net_admin,cap_net_raw=ep", path])
except AttributeError:
# Due to a Python bug, os.listxattr could be missing: https://github.com/GNS3/gns3-gui/issues/2010
@@ -190,25 +159,15 @@ class LocalServer(QtCore.QObject):
self.parent(),
"uBridge",
"uBridge requires root permissions to interact with network interfaces. Set root permissions to uBridge? All admin users on the system will be able to read packet from the network interfaces.",
QtWidgets.QMessageBox.StandardButton.Yes,
QtWidgets.QMessageBox.StandardButton.No)
if proceed == QtWidgets.QMessageBox.StandardButton.Yes:
from gns3.utils.macos_ubridge_setuid import macos_ubridge_setuid
if sys.platform.startswith("darwin") and hasattr(sys, "frozen"):
macos_ubridge_setuid()
else:
sudo(["chown", "root:admin", path], ["chmod", "4750", path])
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if proceed == QtWidgets.QMessageBox.Yes:
sudo(["chown", "root:admin", path], ["chmod", "4750", path])
except OSError as e:
QtWidgets.QMessageBox.critical(self.parent(), "uBridge", "Can't set root permissions to uBridge {}: {}".format(path, str(e)))
return False
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.
@@ -216,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:
@@ -252,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:
@@ -279,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
@@ -325,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
@@ -358,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():
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):
@@ -375,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()
@@ -476,21 +409,17 @@ 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:
if sys.platform.startswith("win"):
# use the string on Windows
self._local_server_process = subprocess.Popen(
command,
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP,
stderr=subprocess.PIPE,
env=os.environ)
self._local_server_process = subprocess.Popen(command, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, stderr=subprocess.PIPE)
else:
# use arguments on other platforms
args = shlex.split(command)
self._local_server_process = subprocess.Popen(args, stderr=subprocess.PIPE, env=os.environ)
self._local_server_process = subprocess.Popen(args, stderr=subprocess.PIPE)
except (OSError, subprocess.SubprocessError) as e:
log.warning('Could not start local server "{}": {}'.format(command, e))
return False
@@ -529,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):
"""
@@ -552,15 +470,16 @@ 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()
progress_dialog.exec_()
if self._local_server_process.returncode is None:
self._killLocalServer()
self._server_started_by_me = False
@@ -581,12 +500,12 @@ 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?",
QtWidgets.QMessageBox.StandardButton.Yes,
QtWidgets.QMessageBox.StandardButton.No)
"Local controller",
"The local controller cannot be stopped, would you like to kill it?",
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if proceed == QtWidgets.QMessageBox.StandardButton.Yes:
if proceed == QtWidgets.QMessageBox.Yes:
self._local_server_process.kill()
@staticmethod

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

@@ -30,12 +30,23 @@ try:
except Exception as e:
print("Fail update installation: {}".format(str(e)))
# WARNING
# Due to buggy user machines we choose to put this as the first loading modules
# otherwise the egg cache is initialized in his standard location and
# if is not writetable the application crash. It's the user fault
# because one day the user as used sudo to run an egg and break his
# filesystem permissions, but it's a common mistake.
from gns3.utils.get_resource import get_resource
import datetime
import traceback
import time
import locale
import argparse
import signal
import psutil
try:
from gns3.qt import QtCore, QtWidgets
@@ -49,12 +60,12 @@ from gns3.local_config import LocalConfig
from gns3.application import Application
from gns3.utils import parse_version
from gns3.dialogs.profile_select import ProfileSelectDialog
from gns3.version import __version__
import logging
log = logging.getLogger(__name__)
from gns3.version import __version__
def locale_check():
"""
@@ -124,13 +135,6 @@ def main():
if options.project:
options.project = os.path.abspath(options.project)
try:
import truststore
truststore.inject_into_ssl()
log.info("Using system certificate store for SSL connections")
except ImportError:
pass
if hasattr(sys, "frozen"):
# We add to the path where the OS search executable our binary location starting by GNS3
# packaged binary
@@ -141,9 +145,7 @@ def main():
frozen_dirs = [
frozen_dir,
os.path.normpath(os.path.join(frozen_dir, 'dynamips')),
os.path.normpath(os.path.join(frozen_dir, 'ubridge')),
os.path.normpath(os.path.join(frozen_dir, 'vpcs')),
os.path.normpath(os.path.join(frozen_dir, 'traceng'))
]
os.environ["PATH"] = os.pathsep.join(frozen_dirs) + os.pathsep + os.environ.get("PATH", "")
@@ -151,7 +153,6 @@ def main():
if options.project:
os.chdir(frozen_dir)
def exceptionHook(exception, value, tb):
if exception == KeyboardInterrupt:
@@ -183,12 +184,15 @@ def main():
# catch exceptions to write them in a file
sys.excepthook = exceptionHook
# we only support Python 3 version >= 3.9
if sys.version_info < (3, 9):
raise SystemExit("Python 3.9 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("6.3.1"):
raise SystemExit("Requirement is PyQt6 version 6.3.1 or higher, got version {}".format(QtCore.QT_VERSION_STR))
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))
if parse_version(psutil.__version__) < parse_version("2.2.1"):
raise SystemExit("Requirement is psutil version 2.2.1 or higher, got version {}".format(psutil.__version__))
# check for the correct locale
# (UNIX/Linux only)
@@ -202,7 +206,7 @@ def main():
# always use the INI format on Windows and OSX (because we don't like the registry and plist files)
if sys.platform.startswith('win') or sys.platform.startswith('darwin'):
QtCore.QSettings.setDefaultFormat(QtCore.QSettings.Format.IniFormat)
QtCore.QSettings.setDefaultFormat(QtCore.QSettings.IniFormat)
if sys.platform.startswith('win') and hasattr(sys, "frozen"):
try:
@@ -215,27 +219,20 @@ def main():
if not options.debug:
try:
# hide the console
# win32console.AllocConsole()
console_window = win32console.GetConsoleWindow()
parent_window = win32gui.GetParent(console_window)
if not parent_window and console_window:
win32gui.ShowWindow(console_window, win32con.SW_HIDE)
elif parent_window:
win32gui.ShowWindow(parent_window, win32con.SW_HIDE)
else:
log.warning("Could not get the console window")
win32gui.ShowWindow(console_window, win32con.SW_HIDE)
except win32console.error as e:
log.warning("Could not allocate console: {}".format(e))
print("warning: could not allocate console: {}".format(e))
local_config = LocalConfig.instance()
global app
app = Application(sys.argv)
app = Application(sys.argv, hdpi=local_config.hdpi())
if local_config.multiProfiles() and not options.profile:
profile_select = ProfileSelectDialog()
profile_select.show()
if profile_select.exec():
if profile_select.exec_():
options.profile = profile_select.profile()
else:
sys.exit(0)
@@ -261,7 +258,6 @@ def main():
log.info("GNS3 GUI version {}".format(__version__))
log.info("Copyright (c) 2007-{} GNS3 Technologies Inc.".format(current_year))
log.info("Application started with {}".format(" ".join(sys.argv)))
log.debug("PATH={}".format(os.environ["PATH"]))
# update the exception file path to have it in the same directory as the settings file.
exception_file_path = os.path.join(LocalConfig.instance().configDirectory(), exception_file_path)
@@ -273,7 +269,7 @@ def main():
error_message = "GNS3.app must be moved to the '/Applications' folder before it can be used"
QtWidgets.QMessageBox.critical(False, "Loading error", error_message)
QtCore.QTimer.singleShot(0, app.quit)
app.exec()
app.exec_()
sys.exit(1)
global mainwindow
@@ -296,7 +292,7 @@ def main():
mainwindow.show()
exit_code = app.exec()
exit_code = app.exec_()
signal.signal(signal.SIGINT, orig_sigint)
signal.signal(signal.SIGTERM, orig_sigterm)

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
@@ -49,6 +50,7 @@ from .topology import Topology
from .http_client import HTTPClient
from .progress import Progress
from .update_manager import UpdateManager
from .utils.analytics import AnalyticsClient
from .dialogs.appliance_wizard import ApplianceWizard
from .dialogs.new_template_wizard import NewTemplateWizard
from .dialogs.notif_dialog import NotifDialog, NotifDialogHandler
@@ -69,7 +71,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""
# signal to tell the view if the user is adding a link or not
adding_link_signal = QtCore.Signal(bool)
adding_link_signal = QtCore.pyqtSignal(bool)
# Signal of settings updates
settings_updated_signal = QtCore.Signal()
@@ -85,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,
@@ -99,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))
@@ -116,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()
@@ -132,11 +150,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._local_config_timer = QtCore.QTimer(self)
self._local_config_timer.timeout.connect(local_config.checkConfigChanged)
self._local_config_timer.start(1000) # milliseconds
self._analytics_client = AnalyticsClient()
self._template_manager = TemplateManager().instance()
self._appliance_manager = ApplianceManager().instance()
# restore the geometry and state of the main window.
self._save_gui_state_geometry = True
self.restoreGeometry(QtCore.QByteArray().fromBase64(self._settings["geometry"].encode()))
self.restoreState(QtCore.QByteArray().fromBase64(self._settings["state"].encode()))
@@ -154,28 +172,28 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.uiNodesDockWidget.setVisible(False)
# default directories for QFileDialog
self._import_configs_from_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.StandardLocation.DocumentsLocation)
self._export_configs_to_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.StandardLocation.DocumentsLocation)
self._screenshots_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.StandardLocation.PicturesLocation)
self._pictures_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.StandardLocation.PicturesLocation)
self._appliance_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.StandardLocation.DownloadLocation)
self._portable_project_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.StandardLocation.DownloadLocation)
self._import_configs_from_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DocumentsLocation)
self._export_configs_to_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DocumentsLocation)
self._screenshots_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.PicturesLocation)
self._pictures_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.PicturesLocation)
self._appliance_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DownloadLocation)
self._portable_project_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DownloadLocation)
self._project_dir = None
# add recent file actions to the File menu
for i in range(0, self._maxrecent_files):
action = QtGui.QAction(self.uiFileMenu)
action = QtWidgets.QAction(self.uiFileMenu)
action.setVisible(False)
action.triggered.connect(self.openRecentFileSlot)
self.recent_file_actions.append(action)
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):
action = QtGui.QAction(self.uiFileMenu)
action = QtWidgets.QAction(self.uiFileMenu)
action.setVisible(False)
action.triggered.connect(self.openRecentProjectSlot)
self.recent_project_actions.append(action)
@@ -194,12 +212,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.setWindowTitle("[*] GNS3")
# detect if the SVG module is correctly installed
supported_image_formats = [fmt.data().decode('utf-8') for fmt in QtGui.QImageReader().supportedImageFormats()]
log.debug("Supported image formats: %s", ", ".join(supported_image_formats))
if "svg" not in supported_image_formats:
log.warning("SVG image format is not supported, is the Qt SVG module installed?")
# load initial stuff once the event loop isn't busy
self.run_later(0, self.startupLoading)
@@ -213,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)
@@ -237,9 +250,9 @@ 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.uiResetGUIStateAction.triggered.connect(self._resetGUIState)
self.uiResetDocksAction.triggered.connect(self._resetDocksSlot)
# tool menu connections
@@ -274,7 +287,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.uiExportDebugInformationAction.triggered.connect(self._exportDebugInformationSlot)
self.uiDoctorAction.triggered.connect(self._doctorSlot)
self.uiAcademyAction.triggered.connect(self._academyActionSlot)
self.uiShortcutsAction.triggered.connect(self._shortcutsActionSlot)
# browsers tool bar connections
self.uiBrowseRoutersAction.triggered.connect(self._browseRoutersActionSlot)
@@ -321,11 +333,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
style = new_settings.get("style")
if style and new_settings["style"] != self._settings["style"]:
self._setStyle(style)
QtWidgets.QMessageBox.information(
self,
"Interface style",
"Please restart the application to fully apply the {} style.".format(style)
)
self._settings.update(new_settings)
# save the settings
@@ -335,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):
@@ -380,18 +387,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
item.updateNode()
item.update()
def _resetGUIState(self):
"""
Reset the GUI state.
"""
self._save_gui_state_geometry = False
self.close()
if hasattr(sys, "frozen"):
QtCore.QProcess.startDetached(os.path.abspath(sys.executable), sys.argv)
else:
QtWidgets.QMessageBox.information(self, "GUI state","The GUI state has been reset, please restart the application")
def _resetDocksSlot(self):
"""
Reset the dock widgets.
@@ -402,6 +397,13 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.uiConsoleDockWidget.setFloating(False)
self.uiNodesDockWidget.setFloating(False)
def analyticsClient(self):
"""
Return the analytics client
"""
return self._analytics_client
def _newProjectActionSlot(self):
"""
Slot called to create a new project.
@@ -413,7 +415,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._project_dialog = ProjectDialog(self)
self._project_dialog.show()
create_new_project = self._project_dialog.exec()
create_new_project = self._project_dialog.exec_()
if create_new_project:
Topology.instance().createLoadProject(self._project_dialog.getProjectSettings())
@@ -427,7 +429,16 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
dialog = NewTemplateWizard(self)
dialog.show()
dialog.exec()
dialog.exec_()
def _imageManagementActionSlot(self):
"""
Called when user wants to manage images
"""
dialog = ImageDialog(self)
dialog.show()
dialog.exec_()
@qslot
def openApplianceActionSlot(self, *args):
@@ -439,7 +450,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
if not os.path.exists(self._appliance_dir):
directory = Topology.instance().projectsDirPath()
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Import appliance", directory,
"All files (*);;GNS3 Appliance (*.gns3appliance *.gns3a)",
"All files (*.*);;GNS3 Appliance (*.gns3appliance *.gns3a)",
"GNS3 Appliance (*.gns3appliance *.gns3a)")
if path:
self.loadPath(path)
@@ -458,7 +469,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
if self._project_dir is None or not os.path.exists(self._project_dir):
directory = Topology.instance().projectsDirPath()
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open project", directory,
"All files (*);;GNS3 Project (*.gns3);;GNS3 Portable Project (*.gns3project *.gns3p);;NET files (*.net)",
"All files (*.*);;GNS3 Project (*.gns3);;GNS3 Portable Project (*.gns3project *.gns3p);;NET files (*.net)",
"GNS3 Project (*.gns3)")
if path:
self.loadPath(path)
@@ -522,10 +533,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
QtWidgets.QMessageBox.critical(self, "Appliance", "Error while importing appliance {}: {}".format(path, str(e)))
return
self._appliance_wizard.show()
self._appliance_wizard.exec()
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)
@@ -565,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:
@@ -599,7 +613,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
Exports all configs to a directory.
"""
path = QtWidgets.QFileDialog.getExistingDirectory(self, "Export directory", self._export_configs_to_dir, QtWidgets.QFileDialog.Option.ShowDirsOnly)
path = QtWidgets.QFileDialog.getExistingDirectory(self, "Export directory", self._export_configs_to_dir, QtWidgets.QFileDialog.ShowDirsOnly)
if path:
self._export_configs_to_dir = os.path.dirname(path)
for module in MODULES:
@@ -612,7 +626,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
Imports all configs from a directory.
"""
path = QtWidgets.QFileDialog.getExistingDirectory(self, "Import directory", self._import_configs_from_dir, QtWidgets.QFileDialog.Option.ShowDirsOnly)
path = QtWidgets.QFileDialog.getExistingDirectory(self, "Import directory", self._import_configs_from_dir, QtWidgets.QFileDialog.ShowDirsOnly)
if path:
self._import_configs_from_dir = os.path.dirname(path)
for module in MODULES:
@@ -630,12 +644,12 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
scene = self.uiGraphicsView.scene()
scene.clearSelection()
source = scene.itemsBoundingRect().adjusted(-20.0, -20.0, 20.0, 20.0)
image = QtGui.QImage(source.size().toSize(), QtGui.QImage.Format.Format_RGB32)
image.fill(QtCore.Qt.GlobalColor.white)
image = QtGui.QImage(source.size().toSize(), QtGui.QImage.Format_RGB32)
image.fill(QtCore.Qt.white)
painter = QtGui.QPainter(image)
painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing, True)
painter.setRenderHint(QtGui.QPainter.RenderHint.TextAntialiasing, True)
painter.setRenderHint(QtGui.QPainter.RenderHint.SmoothPixmapTransform, True)
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
painter.setRenderHint(QtGui.QPainter.TextAntialiasing, True)
painter.setRenderHint(QtGui.QPainter.SmoothPixmapTransform, True)
scene.render(painter, source=source)
painter.end()
# TODO: quality option
@@ -725,7 +739,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
dialog = SnapshotsDialog(self, project)
dialog.show()
dialog.exec()
dialog.exec_()
def _selectAllActionSlot(self):
"""
@@ -750,12 +764,12 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
Slot to switch to full screen.
"""
if not self.windowState() & QtCore.Qt.WindowState.WindowFullScreen:
if not self.windowState() & QtCore.Qt.WindowFullScreen:
# switch to full screen
self.setWindowState(self.windowState() | QtCore.Qt.WindowState.WindowFullScreen)
self.setWindowState(self.windowState() | QtCore.Qt.WindowFullScreen)
else:
# switch back to normal
self.setWindowState(self.windowState() & ~QtCore.Qt.WindowState.WindowFullScreen)
self.setWindowState(self.windowState() & ~QtCore.Qt.WindowFullScreen)
def _zoomInActionSlot(self):
"""
@@ -791,7 +805,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
view = self.uiGraphicsView
bounding_rect = view.scene().itemsBoundingRect().adjusted(-20.0, -20.0, 20.0, 20.0)
view.ensureVisible(bounding_rect)
view.fitInView(bounding_rect, QtCore.Qt.AspectRatioMode.KeepAspectRatio)
view.fitInView(bounding_rect, QtCore.Qt.KeepAspectRatio)
def _showLayersActionSlot(self):
"""
@@ -834,9 +848,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""
reply = QtWidgets.QMessageBox.question(self, "Confirm Start All", "Are you sure you want to start all devices?",
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No)
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.StandardButton.No:
if reply == QtWidgets.QMessageBox.No:
return
project = Topology.instance().project()
@@ -849,9 +863,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""
reply = QtWidgets.QMessageBox.question(self, "Confirm Suspend All", "Are you sure you want to suspend all devices?",
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No)
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.StandardButton.No:
if reply == QtWidgets.QMessageBox.No:
return
project = Topology.instance().project()
@@ -864,9 +878,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""
reply = QtWidgets.QMessageBox.question(self, "Confirm Stop All", "Are you sure you want to stop all devices?",
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No)
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.StandardButton.No:
if reply == QtWidgets.QMessageBox.No:
return
project = Topology.instance().project()
@@ -879,9 +893,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""
reply = QtWidgets.QMessageBox.question(self, "Confirm Reload All", "Are you sure you want to reload all devices?",
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No)
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.StandardButton.No:
if reply == QtWidgets.QMessageBox.No:
return
project = Topology.instance().project()
@@ -931,7 +945,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
Slot called when inserting an image on the scene.
"""
# supported image file formats
file_formats = "Image files (*.svg *.bmp *.jpeg *.jpg *.gif *.pbm *.pgm *.png *.ppm *.xbm *.xpm);;All files (*)"
file_formats = "Image files (*.svg *.bmp *.jpeg *.jpg *.gif *.pbm *.pgm *.png *.ppm *.xbm *.xpm);;All files (*.*)"
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Image", self._pictures_dir, file_formats)
if not path:
@@ -984,21 +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()
def _shortcutsActionSlot(self):
shortcuts_text = ""
for action in self.findChildren(QtGui.QAction):
shortcut = action.shortcut().toString()
if shortcut:
shortcuts_text += f"{action.toolTip()}: {shortcut}\n"
QtWidgets.QMessageBox.information(self, "Shortcuts", shortcuts_text)
setup_wizard = SetupWizard(self)
setup_wizard.show()
setup_wizard.exec_()
def _aboutQtActionSlot(self):
"""
@@ -1014,7 +1016,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
dialog = AboutDialog(self)
dialog.show()
dialog.exec()
dialog.exec_()
def _exportDebugInformationSlot(self):
"""
@@ -1023,7 +1025,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
dialog = ExportDebugDialog(self, Topology.instance().project())
dialog.show()
dialog.exec()
dialog.exec_()
def _doctorSlot(self):
"""
@@ -1032,7 +1034,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
dialog = DoctorDialog(self)
dialog.show()
dialog.exec()
dialog.exec_()
def _academyActionSlot(self):
"""
@@ -1122,7 +1124,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
dialog = PreferencesDialog(self)
#dialog.restoreGeometry(QtCore.QByteArray().fromBase64(self._settings["preferences_dialog_geometry"].encode()))
dialog.show()
dialog.exec()
dialog.exec_()
#self._settings["preferences_dialog_geometry"] = bytes(dialog.saveGeometry().toBase64()).decode()
#self.setSettings(self._settings)
@@ -1132,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)
@@ -1145,13 +1153,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
key = event.key()
# if the user is adding a link and press Escape, then cancel the link addition.
if self.uiAddLinkAction.isChecked() and key == QtCore.Qt.Key.Key_Escape:
if self.uiAddLinkAction.isChecked() and key == QtCore.Qt.Key_Escape:
self.uiAddLinkAction.setChecked(False)
self._addLinkActionSlot()
elif key == QtCore.Qt.Key.Key_C and (event.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier):
status_bar_message = self.uiStatusBar.currentMessage()
if status_bar_message:
QtWidgets.QApplication.clipboard().setText(status_bar_message)
else:
super().keyPressEvent(event)
@@ -1164,8 +1168,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
if Topology.instance().project():
reply = QtWidgets.QMessageBox.question(self, "Confirm Exit", "Are you sure you want to exit GNS3?",
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No)
if reply == QtWidgets.QMessageBox.StandardButton.No:
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
event.ignore()
return
@@ -1174,6 +1178,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
progress.setCancelButtonText("Force quit")
log.debug("Close the Main Window")
self._analytics_client.sendScreenView("Main Window", session_start=False)
self._finish_application_closing(close_windows=False)
event.accept()
self.uiConsoleTextEdit.closeIO()
@@ -1188,12 +1194,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
log.debug("_finish_application_closing")
if self._save_gui_state_geometry:
self._settings["geometry"] = bytes(self.saveGeometry().toBase64()).decode()
self._settings["state"] = bytes(self.saveState().toBase64()).decode()
else:
self._settings["geometry"] = ""
self._settings["state"] = ""
self._settings["geometry"] = bytes(self.saveGeometry().toBase64()).decode()
self._settings["state"] = bytes(self.saveState().toBase64()).decode()
self.setSettings(self._settings)
Controller.instance().stopListenNotifications()
@@ -1227,8 +1229,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
if not LocalConfig.instance().isMainGui():
reply = QtWidgets.QMessageBox.warning(self, "GNS3", "Another GNS3 GUI is already running. Continue?",
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No)
if reply == QtWidgets.QMessageBox.StandardButton.No:
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
sys.exit(1)
return
@@ -1266,19 +1268,18 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
Controller.instance().connected_signal.connect(self._controllerConnectedSlot)
Controller.instance().project_list_updated_signal.connect(self.updateRecentProjectActions)
self._analytics_client.sendScreenView("Main Window")
self.uiGraphicsView.setEnabled(False)
# show the setup wizard
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)
@@ -1431,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
@@ -1446,21 +1455,21 @@ 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,
"All files (*);;GNS3 Portable Project (*.gns3project *.gns3p)",
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open project", directory,
"All files (*.*);;GNS3 Portable Project (*.gns3project *.gns3p)",
"GNS3 Portable Project (*.gns3project *.gns3p)")
if path:
Topology.instance().importProject(path)
@@ -1471,7 +1480,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
return
dialog = EditProjectDialog(self)
dialog.show()
dialog.exec()
dialog.exec_()
def _deleteProjectActionSlot(self):
if Topology.instance().project() is None:
@@ -1480,8 +1489,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self,
"GNS3",
"The project will be deleted from disk. All files will be removed including the project subdirectories. Continue?",
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No)
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
Topology.instance().deleteProject()
def _setStyle(self, style_name):
@@ -1496,8 +1505,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
style.setCharcoalStyle()
elif style_name == "Classic":
style.setClassicStyle()
elif style_name == "Dark":
style.setDarkStyle()
else:
style.setLegacyStyle()

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

@@ -37,7 +37,7 @@ class CloudWizard(VMWizard, Ui_CloudNodeWizard):
super().__init__(cloud_nodes, parent)
self.setPixmap(QtWidgets.QWizard.WizardPixmap.LogoPixmap, QtGui.QPixmap(":/symbols/cloud.svg"))
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/symbols/cloud.svg"))
self.uiNameWizardPage.registerField("name*", self.uiNameLineEdit)
def getSettings(self):
@@ -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

@@ -38,7 +38,7 @@ class EthernetHubWizard(VMWizard, Ui_EthernetHubWizard):
super().__init__(ethernet_hubs, parent)
self.setPixmap(QtWidgets.QWizard.WizardPixmap.LogoPixmap, QtGui.QPixmap(":/symbols/hub.svg"))
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/symbols/hub.svg"))
self.uiNameWizardPage.registerField("name*", self.uiNameLineEdit)
def getSettings(self):
@@ -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

@@ -38,7 +38,7 @@ class EthernetSwitchWizard(VMWizard, Ui_EthernetSwitchWizard):
super().__init__(ethernet_switches, parent)
self.setPixmap(QtWidgets.QWizard.WizardPixmap.LogoPixmap, QtGui.QPixmap(":/symbols/ethernet_switch.svg"))
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/symbols/ethernet_switch.svg"))
self.uiNameWizardPage.registerField("name*", self.uiNameLineEdit)
def getSettings(self):
@@ -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

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

View File

@@ -43,7 +43,7 @@ class ATMSwitchConfigurationPage(QtWidgets.QWidget, Ui_atmSwitchConfigPageWidget
self.uiMappingTreeWidget.itemSelectionChanged.connect(self._mappingSelectionChangedSlot)
# enable sorting
self.uiMappingTreeWidget.sortByColumn(0, QtCore.Qt.SortOrder.AscendingOrder)
self.uiMappingTreeWidget.sortByColumn(0, QtCore.Qt.AscendingOrder)
self.uiMappingTreeWidget.setSortingEnabled(True)
def _mappingSelectedSlot(self, item, column):
@@ -61,11 +61,11 @@ class ATMSwitchConfigurationPage(QtWidgets.QWidget, Ui_atmSwitchConfigPageWidget
match_destination_mapping = mapping.search(destination)
if match_source_mapping and match_destination_mapping:
self.uiVPICheckBox.setCheckState(QtCore.Qt.CheckState.Unchecked)
self.uiVPICheckBox.setCheckState(QtCore.Qt.Unchecked)
(source_port, source_vpi, source_vci) = match_source_mapping.group(1, 2, 3)
(destination_port, destination_vpi, destination_vci) = match_destination_mapping.group(1, 2, 3)
else:
self.uiVPICheckBox.setCheckState(QtCore.Qt.CheckState.Checked)
self.uiVPICheckBox.setCheckState(QtCore.Qt.Checked)
(source_port, source_vpi) = source.split(':')
(destination_port, destination_vpi) = destination.split(':')
source_vci = destination_vci = 0
@@ -103,7 +103,7 @@ class ATMSwitchConfigurationPage(QtWidgets.QWidget, Ui_atmSwitchConfigPageWidget
destination_vpi = self.uiDestinationVPISpinBox.value()
destination_vci = self.uiDestinationVCISpinBox.value()
if self.uiVPICheckBox.checkState() == QtCore.Qt.CheckState.Unchecked:
if self.uiVPICheckBox.checkState() == QtCore.Qt.Unchecked:
source = "{port}:{vpi}:{vci}".format(port=source_port,
vpi=source_vpi,
vci=source_vci)

View File

@@ -364,7 +364,7 @@ class CloudConfigurationPage(QtWidgets.QWidget, Ui_cloudConfigPageWidget):
if interface["type"] == "ethernet":
if not state and interface["special"]:
continue
if self.uiEthernetListWidget.findItems(interface["name"], QtCore.Qt.MatchFlag.MatchFixedString):
if self.uiEthernetListWidget.findItems(interface["name"], QtCore.Qt.MatchFixedString):
continue
self.uiEthernetComboBox.addItem(interface["name"])
index += 1
@@ -377,7 +377,7 @@ class CloudConfigurationPage(QtWidgets.QWidget, Ui_cloudConfigPageWidget):
symbol_path = self.uiSymbolLineEdit.text()
dialog = SymbolSelectionDialog(self, symbol=symbol_path)
dialog.show()
if dialog.exec():
if dialog.exec_():
new_symbol_path = dialog.getSymbol()
self.uiSymbolLineEdit.setText(new_symbol_path)
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(new_symbol_path))
@@ -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
@@ -107,7 +111,7 @@ class CloudPreferencesPage(QtWidgets.QWidget, Ui_CloudPreferencesPageWidget):
self.uiEditCloudNodePushButton.setEnabled(single_selected)
if single_selected:
key = selection[0].data(0, QtCore.Qt.ItemDataRole.UserRole)
key = selection[0].data(0, QtCore.Qt.UserRole)
cloud_node = self._cloud_nodes[key]
self._refreshInfo(cloud_node)
else:
@@ -120,7 +124,7 @@ class CloudPreferencesPage(QtWidgets.QWidget, Ui_CloudPreferencesPageWidget):
wizard = CloudWizard(self._cloud_nodes, parent=self)
wizard.show()
if wizard.exec():
if wizard.exec_():
new_cloud_settings = wizard.getSettings()
key = "{server}:{name}".format(server=new_cloud_settings["compute_id"], name=new_cloud_settings["name"])
self._cloud_nodes[key] = CLOUD_SETTINGS.copy()
@@ -130,7 +134,7 @@ class CloudPreferencesPage(QtWidgets.QWidget, Ui_CloudPreferencesPageWidget):
item.setText(0, self._cloud_nodes[key]["name"])
Controller.instance().getSymbolIcon(self._cloud_nodes[key]["symbol"], qpartial(self._setItemIcon, item))
item.setData(0, QtCore.Qt.ItemDataRole.UserRole, key)
item.setData(0, QtCore.Qt.UserRole, key)
self._items.append(item)
self.uiCloudNodesTreeWidget.setCurrentItem(item)
@@ -141,11 +145,11 @@ class CloudPreferencesPage(QtWidgets.QWidget, Ui_CloudPreferencesPageWidget):
item = self.uiCloudNodesTreeWidget.currentItem()
if item:
key = item.data(0, QtCore.Qt.ItemDataRole.UserRole)
key = item.data(0, QtCore.Qt.UserRole)
cloud_node = self._cloud_nodes[key]
dialog = ConfigurationDialog(cloud_node["name"], cloud_node, CloudConfigurationPage(), parent=self)
dialog.show()
if dialog.exec():
if dialog.exec_():
# update the icon
Controller.instance().getSymbolIcon(cloud_node["symbol"], qpartial(self._setItemIcon, item))
if cloud_node["name"] != item.text(0):
@@ -158,7 +162,7 @@ class CloudPreferencesPage(QtWidgets.QWidget, Ui_CloudPreferencesPageWidget):
self._cloud_nodes[new_key] = self._cloud_nodes[key]
del self._cloud_nodes[key]
item.setText(0, cloud_node["name"])
item.setData(0, QtCore.Qt.ItemDataRole.UserRole, new_key)
item.setData(0, QtCore.Qt.UserRole, new_key)
self._refreshInfo(cloud_node)
def _deleteCloudNodeSlot(self):
@@ -168,7 +172,7 @@ class CloudPreferencesPage(QtWidgets.QWidget, Ui_CloudPreferencesPageWidget):
for item in self.uiCloudNodesTreeWidget.selectedItems():
if item:
key = item.data(0, QtCore.Qt.ItemDataRole.UserRole)
key = item.data(0, QtCore.Qt.UserRole)
del self._cloud_nodes[key]
self.uiCloudNodesTreeWidget.takeTopLevelItem(self.uiCloudNodesTreeWidget.indexOfTopLevelItem(item))
@@ -192,12 +196,12 @@ class CloudPreferencesPage(QtWidgets.QWidget, Ui_CloudPreferencesPageWidget):
item = QtWidgets.QTreeWidgetItem(self.uiCloudNodesTreeWidget)
item.setText(0, cloud_node["name"])
Controller.instance().getSymbolIcon(cloud_node["symbol"], qpartial(self._setItemIcon, item))
item.setData(0, QtCore.Qt.ItemDataRole.UserRole, key)
item.setData(0, QtCore.Qt.UserRole, key)
self._items.append(item)
if self._items:
self.uiCloudNodesTreeWidget.setCurrentItem(self._items[0])
self.uiCloudNodesTreeWidget.sortByColumn(0, QtCore.Qt.SortOrder.AscendingOrder)
self.uiCloudNodesTreeWidget.sortByColumn(0, QtCore.Qt.AscendingOrder)
self.uiCloudNodesTreeWidget.setMaximumWidth(self.uiCloudNodesTreeWidget.sizeHintForColumn(0) + 20)
def _setItemIcon(self, item, icon):

View File

@@ -52,7 +52,7 @@ class EthernetHubConfigurationPage(QtWidgets.QWidget, Ui_ethernetHubConfigPageWi
symbol_path = self.uiSymbolLineEdit.text()
dialog = SymbolSelectionDialog(self, symbol=symbol_path)
dialog.show()
if dialog.exec():
if dialog.exec_():
new_symbol_path = dialog.getSymbol()
self.uiSymbolLineEdit.setText(new_symbol_path)
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(new_symbol_path))

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"]))])
@@ -104,7 +108,7 @@ class EthernetHubPreferencesPage(QtWidgets.QWidget, Ui_EthernetHubPreferencesPag
self.uiEditEthernetHubPushButton.setEnabled(single_selected)
if single_selected:
key = selection[0].data(0, QtCore.Qt.ItemDataRole.UserRole)
key = selection[0].data(0, QtCore.Qt.UserRole)
ethernet_hub = self._ethernet_hubs[key]
self._refreshInfo(ethernet_hub)
else:
@@ -117,7 +121,7 @@ class EthernetHubPreferencesPage(QtWidgets.QWidget, Ui_EthernetHubPreferencesPag
wizard = EthernetHubWizard(self._ethernet_hubs, parent=self)
wizard.show()
if wizard.exec():
if wizard.exec_():
new_ethernet_hub_settings = wizard.getSettings()
key = "{server}:{name}".format(server=new_ethernet_hub_settings["compute_id"], name=new_ethernet_hub_settings["name"])
self._ethernet_hubs[key] = ETHERNET_HUB_SETTINGS.copy()
@@ -126,7 +130,7 @@ class EthernetHubPreferencesPage(QtWidgets.QWidget, Ui_EthernetHubPreferencesPag
item = QtWidgets.QTreeWidgetItem(self.uiEthernetHubsTreeWidget)
item.setText(0, self._ethernet_hubs[key]["name"])
Controller.instance().getSymbolIcon(self._ethernet_hubs[key]["symbol"], qpartial(self._setItemIcon, item))
item.setData(0, QtCore.Qt.ItemDataRole.UserRole, key)
item.setData(0, QtCore.Qt.UserRole, key)
self._items.append(item)
self.uiEthernetHubsTreeWidget.setCurrentItem(item)
@@ -137,11 +141,11 @@ class EthernetHubPreferencesPage(QtWidgets.QWidget, Ui_EthernetHubPreferencesPag
item = self.uiEthernetHubsTreeWidget.currentItem()
if item:
key = item.data(0, QtCore.Qt.ItemDataRole.UserRole)
key = item.data(0, QtCore.Qt.UserRole)
ethernet_hub = self._ethernet_hubs[key]
dialog = ConfigurationDialog(ethernet_hub["name"], ethernet_hub, EthernetHubConfigurationPage(), parent=self)
dialog.show()
if dialog.exec():
if dialog.exec_():
# update the icon
Controller.instance().getSymbolIcon(ethernet_hub["symbol"], qpartial(self._setItemIcon, item))
if ethernet_hub["name"] != item.text(0):
@@ -154,7 +158,7 @@ class EthernetHubPreferencesPage(QtWidgets.QWidget, Ui_EthernetHubPreferencesPag
self._ethernet_hubs[new_key] = self._ethernet_hubs[key]
del self._ethernet_hubs[key]
item.setText(0, ethernet_hub["name"])
item.setData(0, QtCore.Qt.ItemDataRole.UserRole, new_key)
item.setData(0, QtCore.Qt.UserRole, new_key)
self._refreshInfo(ethernet_hub)
def _deleteEthernetHubSlot(self):
@@ -164,7 +168,7 @@ class EthernetHubPreferencesPage(QtWidgets.QWidget, Ui_EthernetHubPreferencesPag
for item in self.uiEthernetHubsTreeWidget.selectedItems():
if item:
key = item.data(0, QtCore.Qt.ItemDataRole.UserRole)
key = item.data(0, QtCore.Qt.UserRole)
del self._ethernet_hubs[key]
self.uiEthernetHubsTreeWidget.takeTopLevelItem(self.uiEthernetHubsTreeWidget.indexOfTopLevelItem(item))
@@ -188,12 +192,12 @@ class EthernetHubPreferencesPage(QtWidgets.QWidget, Ui_EthernetHubPreferencesPag
item = QtWidgets.QTreeWidgetItem(self.uiEthernetHubsTreeWidget)
item.setText(0, ethernet_hub["name"])
Controller.instance().getSymbolIcon(ethernet_hub["symbol"], qpartial(self._setItemIcon, item))
item.setData(0, QtCore.Qt.ItemDataRole.UserRole, key)
item.setData(0, QtCore.Qt.UserRole, key)
self._items.append(item)
if self._items:
self.uiEthernetHubsTreeWidget.setCurrentItem(self._items[0])
self.uiEthernetHubsTreeWidget.sortByColumn(0, QtCore.Qt.SortOrder.AscendingOrder)
self.uiEthernetHubsTreeWidget.sortByColumn(0, QtCore.Qt.AscendingOrder)
self.uiEthernetHubsTreeWidget.setMaximumWidth(self.uiEthernetHubsTreeWidget.sizeHintForColumn(0) + 20)
def _setItemIcon(self, item, icon):

View File

@@ -51,7 +51,7 @@ class EthernetSwitchConfigurationPage(QtWidgets.QWidget, Ui_ethernetSwitchConfig
self.uiPortTypeComboBox.currentIndexChanged.connect(self._typeSelectionChangedSlot)
# enable sorting
self.uiPortsTreeWidget.sortByColumn(0, QtCore.Qt.SortOrder.AscendingOrder)
self.uiPortsTreeWidget.sortByColumn(0, QtCore.Qt.AscendingOrder)
self.uiPortsTreeWidget.setSortingEnabled(True)
self.uiSymbolToolButton.clicked.connect(self._symbolBrowserSlot)
@@ -64,7 +64,7 @@ class EthernetSwitchConfigurationPage(QtWidgets.QWidget, Ui_ethernetSwitchConfig
symbol_path = self.uiSymbolLineEdit.text()
dialog = SymbolSelectionDialog(self, symbol=symbol_path)
dialog.show()
if dialog.exec():
if dialog.exec_():
new_symbol_path = dialog.getSymbol()
self.uiSymbolLineEdit.setText(new_symbol_path)
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(new_symbol_path))
@@ -131,7 +131,7 @@ class EthernetSwitchConfigurationPage(QtWidgets.QWidget, Ui_ethernetSwitchConfig
if port in self._ports:
# update a given entry in the tree widget
item = self.uiPortsTreeWidget.findItems(str(port), QtCore.Qt.MatchFlag.MatchFixedString)[0]
item = self.uiPortsTreeWidget.findItems(str(port), QtCore.Qt.MatchFixedString)[0]
item.setText(1, str(vlan))
item.setText(2, port_type)
item.setText(3, port_ethertype)

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
@@ -109,7 +113,7 @@ class EthernetSwitchPreferencesPage(QtWidgets.QWidget, Ui_EthernetSwitchPreferen
self.uiEditEthernetSwitchPushButton.setEnabled(single_selected)
if single_selected:
key = selection[0].data(0, QtCore.Qt.ItemDataRole.UserRole)
key = selection[0].data(0, QtCore.Qt.UserRole)
ethernet_switch = self._ethernet_switches[key]
self._refreshInfo(ethernet_switch)
else:
@@ -122,7 +126,7 @@ class EthernetSwitchPreferencesPage(QtWidgets.QWidget, Ui_EthernetSwitchPreferen
wizard = EthernetSwitchWizard(self._ethernet_switches, parent=self)
wizard.show()
if wizard.exec():
if wizard.exec_():
new_ethernet_switch_settings = wizard.getSettings()
key = "{server}:{name}".format(server=new_ethernet_switch_settings["compute_id"], name=new_ethernet_switch_settings["name"])
self._ethernet_switches[key] = ETHERNET_SWITCH_SETTINGS.copy()
@@ -132,7 +136,7 @@ class EthernetSwitchPreferencesPage(QtWidgets.QWidget, Ui_EthernetSwitchPreferen
item.setText(0, self._ethernet_switches[key]["name"])
Controller.instance().getSymbolIcon(self._ethernet_switches[key]["symbol"], qpartial(self._setItemIcon, item))
item.setData(0, QtCore.Qt.ItemDataRole.UserRole, key)
item.setData(0, QtCore.Qt.UserRole, key)
self._items.append(item)
self.uiEthernetSwitchesTreeWidget.setCurrentItem(item)
@@ -143,11 +147,11 @@ class EthernetSwitchPreferencesPage(QtWidgets.QWidget, Ui_EthernetSwitchPreferen
item = self.uiEthernetSwitchesTreeWidget.currentItem()
if item:
key = item.data(0, QtCore.Qt.ItemDataRole.UserRole)
key = item.data(0, QtCore.Qt.UserRole)
ethernet_switch = self._ethernet_switches[key]
dialog = ConfigurationDialog(ethernet_switch["name"], ethernet_switch, EthernetSwitchConfigurationPage(), parent=self)
dialog.show()
if dialog.exec():
if dialog.exec_():
# update the icon
Controller.instance().getSymbolIcon(ethernet_switch["symbol"], qpartial(self._setItemIcon, item))
if ethernet_switch["name"] != item.text(0):
@@ -160,7 +164,7 @@ class EthernetSwitchPreferencesPage(QtWidgets.QWidget, Ui_EthernetSwitchPreferen
self._ethernet_switches[new_key] = self._ethernet_switches[key]
del self._ethernet_switches[key]
item.setText(0, ethernet_switch["name"])
item.setData(0, QtCore.Qt.ItemDataRole.UserRole, new_key)
item.setData(0, QtCore.Qt.UserRole, new_key)
self._refreshInfo(ethernet_switch)
def _deleteEthernetSwitchSlot(self):
@@ -169,7 +173,7 @@ class EthernetSwitchPreferencesPage(QtWidgets.QWidget, Ui_EthernetSwitchPreferen
"""
for item in self.uiEthernetSwitchesTreeWidget.selectedItems():
if item:
key = item.data(0, QtCore.Qt.ItemDataRole.UserRole)
key = item.data(0, QtCore.Qt.UserRole)
del self._ethernet_switches[key]
self.uiEthernetSwitchesTreeWidget.takeTopLevelItem(self.uiEthernetSwitchesTreeWidget.indexOfTopLevelItem(item))
@@ -193,12 +197,12 @@ class EthernetSwitchPreferencesPage(QtWidgets.QWidget, Ui_EthernetSwitchPreferen
item = QtWidgets.QTreeWidgetItem(self.uiEthernetSwitchesTreeWidget)
item.setText(0, ethernet_switch["name"])
Controller.instance().getSymbolIcon(ethernet_switch["symbol"], qpartial(self._setItemIcon, item))
item.setData(0, QtCore.Qt.ItemDataRole.UserRole, key)
item.setData(0, QtCore.Qt.UserRole, key)
self._items.append(item)
if self._items:
self.uiEthernetSwitchesTreeWidget.setCurrentItem(self._items[0])
self.uiEthernetSwitchesTreeWidget.sortByColumn(0, QtCore.Qt.SortOrder.AscendingOrder)
self.uiEthernetSwitchesTreeWidget.sortByColumn(0, QtCore.Qt.AscendingOrder)
self.uiEthernetSwitchesTreeWidget.setMaximumWidth(self.uiEthernetSwitchesTreeWidget.sizeHintForColumn(0) + 20)
def _setItemIcon(self, item, icon):

View File

@@ -42,7 +42,7 @@ class FrameRelaySwitchConfigurationPage(QtWidgets.QWidget, Ui_frameRelaySwitchCo
self.uiMappingTreeWidget.itemSelectionChanged.connect(self._mappingSelectionChangedSlot)
# enable sorting
self.uiMappingTreeWidget.sortByColumn(0, QtCore.Qt.SortOrder.AscendingOrder)
self.uiMappingTreeWidget.sortByColumn(0, QtCore.Qt.AscendingOrder)
self.uiMappingTreeWidget.setSortingEnabled(True)
def _mappingSelectedSlot(self, item, column):

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

@@ -1,40 +1,39 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/builtin/ui/atm_switch_configuration_page.ui'
#
# Created by: PyQt6 UI code generator 6.10.1
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_atmSwitchConfigPageWidget(object):
def setupUi(self, atmSwitchConfigPageWidget):
atmSwitchConfigPageWidget.setObjectName("atmSwitchConfigPageWidget")
atmSwitchConfigPageWidget.resize(540, 553)
atmSwitchConfigPageWidget.resize(459, 430)
self.gridLayout_2 = QtWidgets.QGridLayout(atmSwitchConfigPageWidget)
self.gridLayout_2.setObjectName("gridLayout_2")
self.uiGeneralGroupBox = QtWidgets.QGroupBox(parent=atmSwitchConfigPageWidget)
self.uiGeneralGroupBox = QtWidgets.QGroupBox(atmSwitchConfigPageWidget)
self.uiGeneralGroupBox.setObjectName("uiGeneralGroupBox")
self.gridLayout = QtWidgets.QGridLayout(self.uiGeneralGroupBox)
self.gridLayout.setObjectName("gridLayout")
self.uiNameLabel = QtWidgets.QLabel(parent=self.uiGeneralGroupBox)
self.uiNameLabel = QtWidgets.QLabel(self.uiGeneralGroupBox)
self.uiNameLabel.setObjectName("uiNameLabel")
self.gridLayout.addWidget(self.uiNameLabel, 0, 0, 1, 1)
self.uiNameLineEdit = QtWidgets.QLineEdit(parent=self.uiGeneralGroupBox)
self.uiNameLineEdit = QtWidgets.QLineEdit(self.uiGeneralGroupBox)
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
self.uiVPICheckBox = QtWidgets.QCheckBox(parent=self.uiGeneralGroupBox)
self.uiVPICheckBox = QtWidgets.QCheckBox(self.uiGeneralGroupBox)
self.uiVPICheckBox.setObjectName("uiVPICheckBox")
self.gridLayout.addWidget(self.uiVPICheckBox, 1, 0, 1, 2)
self.gridLayout_2.addWidget(self.uiGeneralGroupBox, 0, 0, 1, 3)
self.uiMappingGroupBox = QtWidgets.QGroupBox(parent=atmSwitchConfigPageWidget)
self.uiMappingGroupBox = QtWidgets.QGroupBox(atmSwitchConfigPageWidget)
self.uiMappingGroupBox.setObjectName("uiMappingGroupBox")
self.vboxlayout = QtWidgets.QVBoxLayout(self.uiMappingGroupBox)
self.vboxlayout.setObjectName("vboxlayout")
self.uiMappingTreeWidget = QtWidgets.QTreeWidget(parent=self.uiMappingGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding)
self.uiMappingTreeWidget = QtWidgets.QTreeWidget(self.uiMappingGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiMappingTreeWidget.sizePolicy().hasHeightForWidth())
@@ -43,17 +42,17 @@ class Ui_atmSwitchConfigPageWidget(object):
self.uiMappingTreeWidget.setObjectName("uiMappingTreeWidget")
self.vboxlayout.addWidget(self.uiMappingTreeWidget)
self.gridLayout_2.addWidget(self.uiMappingGroupBox, 0, 3, 3, 1)
self.uiAddPushButton = QtWidgets.QPushButton(parent=atmSwitchConfigPageWidget)
self.uiAddPushButton = QtWidgets.QPushButton(atmSwitchConfigPageWidget)
self.uiAddPushButton.setObjectName("uiAddPushButton")
self.gridLayout_2.addWidget(self.uiAddPushButton, 3, 0, 1, 1)
self.uiDeletePushButton = QtWidgets.QPushButton(parent=atmSwitchConfigPageWidget)
self.uiDeletePushButton = QtWidgets.QPushButton(atmSwitchConfigPageWidget)
self.uiDeletePushButton.setEnabled(False)
self.uiDeletePushButton.setObjectName("uiDeletePushButton")
self.gridLayout_2.addWidget(self.uiDeletePushButton, 3, 1, 1, 1)
spacerItem = QtWidgets.QSpacerItem(213, 31, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
spacerItem = QtWidgets.QSpacerItem(213, 31, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_2.addItem(spacerItem, 4, 2, 1, 2)
self.uiSourceGroupBox = QtWidgets.QGroupBox(parent=atmSwitchConfigPageWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred)
self.uiSourceGroupBox = QtWidgets.QGroupBox(atmSwitchConfigPageWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiSourceGroupBox.sizePolicy().hasHeightForWidth())
@@ -61,11 +60,11 @@ class Ui_atmSwitchConfigPageWidget(object):
self.uiSourceGroupBox.setObjectName("uiSourceGroupBox")
self.gridlayout = QtWidgets.QGridLayout(self.uiSourceGroupBox)
self.gridlayout.setObjectName("gridlayout")
self.uiSourcePortLabel = QtWidgets.QLabel(parent=self.uiSourceGroupBox)
self.uiSourcePortLabel = QtWidgets.QLabel(self.uiSourceGroupBox)
self.uiSourcePortLabel.setObjectName("uiSourcePortLabel")
self.gridlayout.addWidget(self.uiSourcePortLabel, 0, 0, 1, 1)
self.uiSourcePortSpinBox = QtWidgets.QSpinBox(parent=self.uiSourceGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed)
self.uiSourcePortSpinBox = QtWidgets.QSpinBox(self.uiSourceGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiSourcePortSpinBox.sizePolicy().hasHeightForWidth())
@@ -75,12 +74,12 @@ class Ui_atmSwitchConfigPageWidget(object):
self.uiSourcePortSpinBox.setProperty("value", 1)
self.uiSourcePortSpinBox.setObjectName("uiSourcePortSpinBox")
self.gridlayout.addWidget(self.uiSourcePortSpinBox, 0, 1, 1, 1)
self.uiSourceVPILabel = QtWidgets.QLabel(parent=self.uiSourceGroupBox)
self.uiSourceVPILabel = QtWidgets.QLabel(self.uiSourceGroupBox)
self.uiSourceVPILabel.setObjectName("uiSourceVPILabel")
self.gridlayout.addWidget(self.uiSourceVPILabel, 1, 0, 1, 1)
self.uiSourceVPISpinBox = QtWidgets.QSpinBox(parent=self.uiSourceGroupBox)
self.uiSourceVPISpinBox = QtWidgets.QSpinBox(self.uiSourceGroupBox)
self.uiSourceVPISpinBox.setEnabled(True)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiSourceVPISpinBox.sizePolicy().hasHeightForWidth())
@@ -89,11 +88,11 @@ class Ui_atmSwitchConfigPageWidget(object):
self.uiSourceVPISpinBox.setProperty("value", 0)
self.uiSourceVPISpinBox.setObjectName("uiSourceVPISpinBox")
self.gridlayout.addWidget(self.uiSourceVPISpinBox, 1, 1, 1, 1)
self.uiSourceVCILabel = QtWidgets.QLabel(parent=self.uiSourceGroupBox)
self.uiSourceVCILabel = QtWidgets.QLabel(self.uiSourceGroupBox)
self.uiSourceVCILabel.setObjectName("uiSourceVCILabel")
self.gridlayout.addWidget(self.uiSourceVCILabel, 2, 0, 1, 1)
self.uiSourceVCISpinBox = QtWidgets.QSpinBox(parent=self.uiSourceGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed)
self.uiSourceVCISpinBox = QtWidgets.QSpinBox(self.uiSourceGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiSourceVCISpinBox.sizePolicy().hasHeightForWidth())
@@ -103,8 +102,8 @@ class Ui_atmSwitchConfigPageWidget(object):
self.uiSourceVCISpinBox.setObjectName("uiSourceVCISpinBox")
self.gridlayout.addWidget(self.uiSourceVCISpinBox, 2, 1, 1, 1)
self.gridLayout_2.addWidget(self.uiSourceGroupBox, 1, 0, 1, 3)
self.uiDestinationGroupBox = QtWidgets.QGroupBox(parent=atmSwitchConfigPageWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred)
self.uiDestinationGroupBox = QtWidgets.QGroupBox(atmSwitchConfigPageWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiDestinationGroupBox.sizePolicy().hasHeightForWidth())
@@ -112,11 +111,11 @@ class Ui_atmSwitchConfigPageWidget(object):
self.uiDestinationGroupBox.setObjectName("uiDestinationGroupBox")
self.gridlayout1 = QtWidgets.QGridLayout(self.uiDestinationGroupBox)
self.gridlayout1.setObjectName("gridlayout1")
self.uiDestinationPortLabel = QtWidgets.QLabel(parent=self.uiDestinationGroupBox)
self.uiDestinationPortLabel = QtWidgets.QLabel(self.uiDestinationGroupBox)
self.uiDestinationPortLabel.setObjectName("uiDestinationPortLabel")
self.gridlayout1.addWidget(self.uiDestinationPortLabel, 0, 0, 1, 1)
self.uiDestinationPortSpinBox = QtWidgets.QSpinBox(parent=self.uiDestinationGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed)
self.uiDestinationPortSpinBox = QtWidgets.QSpinBox(self.uiDestinationGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiDestinationPortSpinBox.sizePolicy().hasHeightForWidth())
@@ -126,12 +125,12 @@ class Ui_atmSwitchConfigPageWidget(object):
self.uiDestinationPortSpinBox.setProperty("value", 10)
self.uiDestinationPortSpinBox.setObjectName("uiDestinationPortSpinBox")
self.gridlayout1.addWidget(self.uiDestinationPortSpinBox, 0, 1, 1, 1)
self.uiDestinationVPILabel = QtWidgets.QLabel(parent=self.uiDestinationGroupBox)
self.uiDestinationVPILabel = QtWidgets.QLabel(self.uiDestinationGroupBox)
self.uiDestinationVPILabel.setObjectName("uiDestinationVPILabel")
self.gridlayout1.addWidget(self.uiDestinationVPILabel, 1, 0, 1, 1)
self.uiDestinationVPISpinBox = QtWidgets.QSpinBox(parent=self.uiDestinationGroupBox)
self.uiDestinationVPISpinBox = QtWidgets.QSpinBox(self.uiDestinationGroupBox)
self.uiDestinationVPISpinBox.setEnabled(True)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiDestinationVPISpinBox.sizePolicy().hasHeightForWidth())
@@ -140,11 +139,11 @@ class Ui_atmSwitchConfigPageWidget(object):
self.uiDestinationVPISpinBox.setProperty("value", 0)
self.uiDestinationVPISpinBox.setObjectName("uiDestinationVPISpinBox")
self.gridlayout1.addWidget(self.uiDestinationVPISpinBox, 1, 1, 1, 1)
self.uiDestinationVCILabel = QtWidgets.QLabel(parent=self.uiDestinationGroupBox)
self.uiDestinationVCILabel = QtWidgets.QLabel(self.uiDestinationGroupBox)
self.uiDestinationVCILabel.setObjectName("uiDestinationVCILabel")
self.gridlayout1.addWidget(self.uiDestinationVCILabel, 2, 0, 1, 1)
self.uiDestinationVCISpinBox = QtWidgets.QSpinBox(parent=self.uiDestinationGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed)
self.uiDestinationVCISpinBox = QtWidgets.QSpinBox(self.uiDestinationGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiDestinationVCISpinBox.sizePolicy().hasHeightForWidth())
@@ -186,3 +185,4 @@ class Ui_atmSwitchConfigPageWidget(object):
self.uiDestinationPortLabel.setText(_translate("atmSwitchConfigPageWidget", "Port:"))
self.uiDestinationVPILabel.setText(_translate("atmSwitchConfigPageWidget", "VPI:"))
self.uiDestinationVCILabel.setText(_translate("atmSwitchConfigPageWidget", "VCI:"))

View File

@@ -1,13 +1,12 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/builtin/ui/builtin_preferences_page.ui'
#
# Created by: PyQt6 UI code generator 6.10.1
# Created by: PyQt5 UI code generator 5.9.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_BuiltinPreferencesPageWidget(object):
def setupUi(self, BuiltinPreferencesPageWidget):
@@ -15,28 +14,28 @@ class Ui_BuiltinPreferencesPageWidget(object):
BuiltinPreferencesPageWidget.resize(456, 385)
self.verticalLayout = QtWidgets.QVBoxLayout(BuiltinPreferencesPageWidget)
self.verticalLayout.setObjectName("verticalLayout")
self.uiTabWidget = QtWidgets.QTabWidget(parent=BuiltinPreferencesPageWidget)
self.uiTabWidget.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
self.uiTabWidget = QtWidgets.QTabWidget(BuiltinPreferencesPageWidget)
self.uiTabWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.uiTabWidget.setObjectName("uiTabWidget")
self.uiServerSettingsTabWidget = QtWidgets.QWidget()
self.uiServerSettingsTabWidget.setObjectName("uiServerSettingsTabWidget")
self.gridLayout = QtWidgets.QGridLayout(self.uiServerSettingsTabWidget)
self.gridLayout.setObjectName("gridLayout")
self.label = QtWidgets.QLabel(parent=self.uiServerSettingsTabWidget)
self.label = QtWidgets.QLabel(self.uiServerSettingsTabWidget)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
self.uiNATInterfaceComboBox = QtWidgets.QComboBox(parent=self.uiServerSettingsTabWidget)
self.uiNATInterfaceComboBox = QtWidgets.QComboBox(self.uiServerSettingsTabWidget)
self.uiNATInterfaceComboBox.setObjectName("uiNATInterfaceComboBox")
self.gridLayout.addWidget(self.uiNATInterfaceComboBox, 1, 0, 1, 1)
spacerItem = QtWidgets.QSpacerItem(20, 5, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
spacerItem = QtWidgets.QSpacerItem(20, 5, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem, 2, 0, 1, 1)
self.uiTabWidget.addTab(self.uiServerSettingsTabWidget, "")
self.verticalLayout.addWidget(self.uiTabWidget)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
spacerItem1 = QtWidgets.QSpacerItem(254, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
spacerItem1 = QtWidgets.QSpacerItem(254, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem1)
self.uiRestoreDefaultsPushButton = QtWidgets.QPushButton(parent=BuiltinPreferencesPageWidget)
self.uiRestoreDefaultsPushButton = QtWidgets.QPushButton(BuiltinPreferencesPageWidget)
self.uiRestoreDefaultsPushButton.setObjectName("uiRestoreDefaultsPushButton")
self.horizontalLayout_2.addWidget(self.uiRestoreDefaultsPushButton)
self.verticalLayout.addLayout(self.horizontalLayout_2)
@@ -51,3 +50,4 @@ class Ui_BuiltinPreferencesPageWidget(object):
self.label.setText(_translate("BuiltinPreferencesPageWidget", "Default NAT interface:"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiServerSettingsTabWidget), _translate("BuiltinPreferencesPageWidget", "Local settings"))
self.uiRestoreDefaultsPushButton.setText(_translate("BuiltinPreferencesPageWidget", "Restore defaults"))

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

@@ -1,58 +1,60 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/builtin/ui/cloud_configuration_page.ui'
#
# Created by: PyQt6 UI code generator 6.10.1
# Created by: PyQt5 UI code generator 5.15.0
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# 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 PyQt6 import QtCore, QtGui, QtWidgets
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(parent=cloudConfigPageWidget)
self.uiTabWidget = QtWidgets.QTabWidget(cloudConfigPageWidget)
self.uiTabWidget.setObjectName("uiTabWidget")
self.EthernetTab = QtWidgets.QWidget()
self.EthernetTab.setObjectName("EthernetTab")
self.gridLayout_3 = QtWidgets.QGridLayout(self.EthernetTab)
self.gridLayout_3.setObjectName("gridLayout_3")
self.uiEthernetComboBox = QtWidgets.QComboBox(parent=self.EthernetTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed)
self.uiEthernetComboBox = QtWidgets.QComboBox(self.EthernetTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiEthernetComboBox.sizePolicy().hasHeightForWidth())
self.uiEthernetComboBox.setSizePolicy(sizePolicy)
self.uiEthernetComboBox.setInsertPolicy(QtWidgets.QComboBox.InsertPolicy.InsertAlphabetically)
self.uiEthernetComboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.SizeAdjustPolicy.AdjustToContents)
self.uiEthernetComboBox.setInsertPolicy(QtWidgets.QComboBox.InsertAlphabetically)
self.uiEthernetComboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
self.uiEthernetComboBox.setObjectName("uiEthernetComboBox")
self.gridLayout_3.addWidget(self.uiEthernetComboBox, 0, 0, 1, 1)
self.uiAddEthernetPushButton = QtWidgets.QPushButton(parent=self.EthernetTab)
self.uiAddEthernetPushButton = QtWidgets.QPushButton(self.EthernetTab)
self.uiAddEthernetPushButton.setObjectName("uiAddEthernetPushButton")
self.gridLayout_3.addWidget(self.uiAddEthernetPushButton, 0, 2, 1, 1)
self.uiAddAllEthernetPushButton = QtWidgets.QPushButton(parent=self.EthernetTab)
self.uiAddAllEthernetPushButton = QtWidgets.QPushButton(self.EthernetTab)
self.uiAddAllEthernetPushButton.setObjectName("uiAddAllEthernetPushButton")
self.gridLayout_3.addWidget(self.uiAddAllEthernetPushButton, 0, 3, 1, 1)
self.uiDeleteEthernetPushButton = QtWidgets.QPushButton(parent=self.EthernetTab)
self.uiDeleteEthernetPushButton = QtWidgets.QPushButton(self.EthernetTab)
self.uiDeleteEthernetPushButton.setEnabled(False)
self.uiDeleteEthernetPushButton.setObjectName("uiDeleteEthernetPushButton")
self.gridLayout_3.addWidget(self.uiDeleteEthernetPushButton, 0, 5, 1, 1)
self.uiEthernetListWidget = QtWidgets.QListWidget(parent=self.EthernetTab)
self.uiEthernetListWidget.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)
self.uiEthernetListWidget = QtWidgets.QListWidget(self.EthernetTab)
self.uiEthernetListWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.uiEthernetListWidget.setObjectName("uiEthernetListWidget")
self.gridLayout_3.addWidget(self.uiEthernetListWidget, 1, 0, 1, 6)
self.uiEthernetWarningPushButton = QtWidgets.QPushButton(parent=self.EthernetTab)
self.uiEthernetWarningPushButton = QtWidgets.QPushButton(self.EthernetTab)
self.uiEthernetWarningPushButton.setText("")
self.uiEthernetWarningPushButton.setObjectName("uiEthernetWarningPushButton")
self.gridLayout_3.addWidget(self.uiEthernetWarningPushButton, 0, 1, 1, 1)
self.uiShowSpecialInterfacesCheckBox = QtWidgets.QCheckBox(parent=self.EthernetTab)
self.uiShowSpecialInterfacesCheckBox = QtWidgets.QCheckBox(self.EthernetTab)
self.uiShowSpecialInterfacesCheckBox.setObjectName("uiShowSpecialInterfacesCheckBox")
self.gridLayout_3.addWidget(self.uiShowSpecialInterfacesCheckBox, 2, 0, 1, 2)
self.uiRefreshEthernetPushButton = QtWidgets.QPushButton(parent=self.EthernetTab)
self.uiRefreshEthernetPushButton = QtWidgets.QPushButton(self.EthernetTab)
self.uiRefreshEthernetPushButton.setObjectName("uiRefreshEthernetPushButton")
self.gridLayout_3.addWidget(self.uiRefreshEthernetPushButton, 0, 4, 1, 1)
self.uiEthernetListWidget.raise_()
@@ -68,39 +70,39 @@ class Ui_cloudConfigPageWidget(object):
self.TAPTab.setObjectName("TAPTab")
self.gridLayout_2 = QtWidgets.QGridLayout(self.TAPTab)
self.gridLayout_2.setObjectName("gridLayout_2")
self.uiDeleteTAPPushButton = QtWidgets.QPushButton(parent=self.TAPTab)
self.uiDeleteTAPPushButton = QtWidgets.QPushButton(self.TAPTab)
self.uiDeleteTAPPushButton.setEnabled(False)
self.uiDeleteTAPPushButton.setObjectName("uiDeleteTAPPushButton")
self.gridLayout_2.addWidget(self.uiDeleteTAPPushButton, 1, 5, 1, 1)
self.uiTAPListWidget = QtWidgets.QListWidget(parent=self.TAPTab)
self.uiTAPListWidget.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)
self.uiTAPListWidget = QtWidgets.QListWidget(self.TAPTab)
self.uiTAPListWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.uiTAPListWidget.setObjectName("uiTAPListWidget")
self.gridLayout_2.addWidget(self.uiTAPListWidget, 2, 0, 1, 6)
self.uiTAPLineEdit = QtWidgets.QLineEdit(parent=self.TAPTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed)
self.uiTAPLineEdit = QtWidgets.QLineEdit(self.TAPTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiTAPLineEdit.sizePolicy().hasHeightForWidth())
self.uiTAPLineEdit.setSizePolicy(sizePolicy)
self.uiTAPLineEdit.setObjectName("uiTAPLineEdit")
self.gridLayout_2.addWidget(self.uiTAPLineEdit, 1, 1, 1, 1)
self.uiAddTAPPushButton = QtWidgets.QPushButton(parent=self.TAPTab)
self.uiAddTAPPushButton = QtWidgets.QPushButton(self.TAPTab)
self.uiAddTAPPushButton.setObjectName("uiAddTAPPushButton")
self.gridLayout_2.addWidget(self.uiAddTAPPushButton, 1, 2, 1, 1)
self.uiTAPComboBox = QtWidgets.QComboBox(parent=self.TAPTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed)
self.uiTAPComboBox = QtWidgets.QComboBox(self.TAPTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiTAPComboBox.sizePolicy().hasHeightForWidth())
self.uiTAPComboBox.setSizePolicy(sizePolicy)
self.uiTAPComboBox.setInsertPolicy(QtWidgets.QComboBox.InsertPolicy.InsertAlphabetically)
self.uiTAPComboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.SizeAdjustPolicy.AdjustToContents)
self.uiTAPComboBox.setInsertPolicy(QtWidgets.QComboBox.InsertAlphabetically)
self.uiTAPComboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
self.uiTAPComboBox.setObjectName("uiTAPComboBox")
self.gridLayout_2.addWidget(self.uiTAPComboBox, 0, 1, 1, 5)
self.uiAddAllTAPPushButton = QtWidgets.QPushButton(parent=self.TAPTab)
self.uiAddAllTAPPushButton = QtWidgets.QPushButton(self.TAPTab)
self.uiAddAllTAPPushButton.setObjectName("uiAddAllTAPPushButton")
self.gridLayout_2.addWidget(self.uiAddAllTAPPushButton, 1, 3, 1, 1)
self.uiRefreshTAPPushButton = QtWidgets.QPushButton(parent=self.TAPTab)
self.uiRefreshTAPPushButton = QtWidgets.QPushButton(self.TAPTab)
self.uiRefreshTAPPushButton.setObjectName("uiRefreshTAPPushButton")
self.gridLayout_2.addWidget(self.uiRefreshTAPPushButton, 1, 4, 1, 1)
self.uiTabWidget.addTab(self.TAPTab, "")
@@ -108,8 +110,8 @@ class Ui_cloudConfigPageWidget(object):
self.UDPTab.setObjectName("UDPTab")
self.gridLayout_5 = QtWidgets.QGridLayout(self.UDPTab)
self.gridLayout_5.setObjectName("gridLayout_5")
self.uiUDPTunnelSettingsGroupBox = QtWidgets.QGroupBox(parent=self.UDPTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Preferred)
self.uiUDPTunnelSettingsGroupBox = QtWidgets.QGroupBox(self.UDPTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiUDPTunnelSettingsGroupBox.sizePolicy().hasHeightForWidth())
@@ -117,8 +119,8 @@ class Ui_cloudConfigPageWidget(object):
self.uiUDPTunnelSettingsGroupBox.setObjectName("uiUDPTunnelSettingsGroupBox")
self.gridLayout_4 = QtWidgets.QGridLayout(self.uiUDPTunnelSettingsGroupBox)
self.gridLayout_4.setObjectName("gridLayout_4")
self.uiRemoteHostLineEdit = QtWidgets.QLineEdit(parent=self.uiUDPTunnelSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed)
self.uiRemoteHostLineEdit = QtWidgets.QLineEdit(self.uiUDPTunnelSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiRemoteHostLineEdit.sizePolicy().hasHeightForWidth())
@@ -126,23 +128,23 @@ class Ui_cloudConfigPageWidget(object):
self.uiRemoteHostLineEdit.setMinimumSize(QtCore.QSize(80, 0))
self.uiRemoteHostLineEdit.setObjectName("uiRemoteHostLineEdit")
self.gridLayout_4.addWidget(self.uiRemoteHostLineEdit, 2, 1, 1, 1)
self.uiRemotePortLabel = QtWidgets.QLabel(parent=self.uiUDPTunnelSettingsGroupBox)
self.uiRemotePortLabel = QtWidgets.QLabel(self.uiUDPTunnelSettingsGroupBox)
self.uiRemotePortLabel.setObjectName("uiRemotePortLabel")
self.gridLayout_4.addWidget(self.uiRemotePortLabel, 3, 0, 1, 1)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.uiAddUDPPushButton = QtWidgets.QPushButton(parent=self.uiUDPTunnelSettingsGroupBox)
self.uiAddUDPPushButton = QtWidgets.QPushButton(self.uiUDPTunnelSettingsGroupBox)
self.uiAddUDPPushButton.setObjectName("uiAddUDPPushButton")
self.horizontalLayout.addWidget(self.uiAddUDPPushButton)
self.uiDeleteUDPPushButton = QtWidgets.QPushButton(parent=self.uiUDPTunnelSettingsGroupBox)
self.uiDeleteUDPPushButton = QtWidgets.QPushButton(self.uiUDPTunnelSettingsGroupBox)
self.uiDeleteUDPPushButton.setEnabled(False)
self.uiDeleteUDPPushButton.setObjectName("uiDeleteUDPPushButton")
self.horizontalLayout.addWidget(self.uiDeleteUDPPushButton)
spacerItem = QtWidgets.QSpacerItem(50, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
spacerItem = QtWidgets.QSpacerItem(50, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.gridLayout_4.addLayout(self.horizontalLayout, 4, 0, 1, 2)
self.uiRemotePortSpinBox = QtWidgets.QSpinBox(parent=self.uiUDPTunnelSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed)
self.uiRemotePortSpinBox = QtWidgets.QSpinBox(self.uiUDPTunnelSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiRemotePortSpinBox.sizePolicy().hasHeightForWidth())
@@ -151,19 +153,19 @@ class Ui_cloudConfigPageWidget(object):
self.uiRemotePortSpinBox.setProperty("value", 20000)
self.uiRemotePortSpinBox.setObjectName("uiRemotePortSpinBox")
self.gridLayout_4.addWidget(self.uiRemotePortSpinBox, 3, 1, 1, 1)
self.uiUDPNameLineEdit = QtWidgets.QLineEdit(parent=self.uiUDPTunnelSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed)
self.uiUDPNameLineEdit = QtWidgets.QLineEdit(self.uiUDPTunnelSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiUDPNameLineEdit.sizePolicy().hasHeightForWidth())
self.uiUDPNameLineEdit.setSizePolicy(sizePolicy)
self.uiUDPNameLineEdit.setObjectName("uiUDPNameLineEdit")
self.gridLayout_4.addWidget(self.uiUDPNameLineEdit, 0, 1, 1, 1)
self.uiRemoteHostLabel = QtWidgets.QLabel(parent=self.uiUDPTunnelSettingsGroupBox)
self.uiRemoteHostLabel = QtWidgets.QLabel(self.uiUDPTunnelSettingsGroupBox)
self.uiRemoteHostLabel.setObjectName("uiRemoteHostLabel")
self.gridLayout_4.addWidget(self.uiRemoteHostLabel, 2, 0, 1, 1)
self.uiLocalPortSpinBox = QtWidgets.QSpinBox(parent=self.uiUDPTunnelSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed)
self.uiLocalPortSpinBox = QtWidgets.QSpinBox(self.uiUDPTunnelSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiLocalPortSpinBox.sizePolicy().hasHeightForWidth())
@@ -172,17 +174,17 @@ class Ui_cloudConfigPageWidget(object):
self.uiLocalPortSpinBox.setProperty("value", 30000)
self.uiLocalPortSpinBox.setObjectName("uiLocalPortSpinBox")
self.gridLayout_4.addWidget(self.uiLocalPortSpinBox, 1, 1, 1, 1)
self.uiLocalPortLabel = QtWidgets.QLabel(parent=self.uiUDPTunnelSettingsGroupBox)
self.uiLocalPortLabel = QtWidgets.QLabel(self.uiUDPTunnelSettingsGroupBox)
self.uiLocalPortLabel.setObjectName("uiLocalPortLabel")
self.gridLayout_4.addWidget(self.uiLocalPortLabel, 1, 0, 1, 1)
self.uiUDPNameLabel = QtWidgets.QLabel(parent=self.uiUDPTunnelSettingsGroupBox)
self.uiUDPNameLabel = QtWidgets.QLabel(self.uiUDPTunnelSettingsGroupBox)
self.uiUDPNameLabel.setObjectName("uiUDPNameLabel")
self.gridLayout_4.addWidget(self.uiUDPNameLabel, 0, 0, 1, 1)
spacerItem1 = QtWidgets.QSpacerItem(20, 10, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
spacerItem1 = QtWidgets.QSpacerItem(20, 10, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_4.addItem(spacerItem1, 5, 0, 1, 1)
self.gridLayout_5.addWidget(self.uiUDPTunnelSettingsGroupBox, 0, 0, 1, 1)
self.uiUDPTunnelsGroupBox = QtWidgets.QGroupBox(parent=self.UDPTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred)
self.uiUDPTunnelsGroupBox = QtWidgets.QGroupBox(self.UDPTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiUDPTunnelsGroupBox.sizePolicy().hasHeightForWidth())
@@ -190,8 +192,8 @@ class Ui_cloudConfigPageWidget(object):
self.uiUDPTunnelsGroupBox.setObjectName("uiUDPTunnelsGroupBox")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.uiUDPTunnelsGroupBox)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.uiUDPTreeWidget = QtWidgets.QTreeWidget(parent=self.uiUDPTunnelsGroupBox)
self.uiUDPTreeWidget.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)
self.uiUDPTreeWidget = QtWidgets.QTreeWidget(self.uiUDPTunnelsGroupBox)
self.uiUDPTreeWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.uiUDPTreeWidget.setObjectName("uiUDPTreeWidget")
self.verticalLayout_2.addWidget(self.uiUDPTreeWidget)
self.gridLayout_5.addWidget(self.uiUDPTunnelsGroupBox, 0, 1, 1, 1)
@@ -200,64 +202,64 @@ class Ui_cloudConfigPageWidget(object):
self.MiscTab.setObjectName("MiscTab")
self.gridLayout = QtWidgets.QGridLayout(self.MiscTab)
self.gridLayout.setObjectName("gridLayout")
self.uiConsolePortSpinBox = QtWidgets.QSpinBox(parent=self.MiscTab)
self.uiConsolePortSpinBox = QtWidgets.QSpinBox(self.MiscTab)
self.uiConsolePortSpinBox.setMinimum(1)
self.uiConsolePortSpinBox.setMaximum(65535)
self.uiConsolePortSpinBox.setProperty("value", 23)
self.uiConsolePortSpinBox.setObjectName("uiConsolePortSpinBox")
self.gridLayout.addWidget(self.uiConsolePortSpinBox, 3, 2, 1, 1)
self.uiNameLabel = QtWidgets.QLabel(parent=self.MiscTab)
self.uiNameLabel = QtWidgets.QLabel(self.MiscTab)
self.uiNameLabel.setObjectName("uiNameLabel")
self.gridLayout.addWidget(self.uiNameLabel, 0, 0, 1, 1)
self.uiNameLineEdit = QtWidgets.QLineEdit(parent=self.MiscTab)
self.uiNameLineEdit = QtWidgets.QLineEdit(self.MiscTab)
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 2, 1, 1)
self.uiConsoleHostLabel = QtWidgets.QLabel(parent=self.MiscTab)
self.uiConsoleHostLabel = QtWidgets.QLabel(self.MiscTab)
self.uiConsoleHostLabel.setObjectName("uiConsoleHostLabel")
self.gridLayout.addWidget(self.uiConsoleHostLabel, 2, 0, 1, 1)
self.uiConsoleHostLineEdit = QtWidgets.QLineEdit(parent=self.MiscTab)
self.uiConsoleHostLineEdit = QtWidgets.QLineEdit(self.MiscTab)
self.uiConsoleHostLineEdit.setObjectName("uiConsoleHostLineEdit")
self.gridLayout.addWidget(self.uiConsoleHostLineEdit, 2, 2, 1, 1)
self.labeluiConsolePortLabel = QtWidgets.QLabel(parent=self.MiscTab)
self.labeluiConsolePortLabel = QtWidgets.QLabel(self.MiscTab)
self.labeluiConsolePortLabel.setObjectName("labeluiConsolePortLabel")
self.gridLayout.addWidget(self.labeluiConsolePortLabel, 3, 0, 1, 1)
self.uiConsoleHttpPathLabel = QtWidgets.QLabel(parent=self.MiscTab)
self.uiConsoleHttpPathLabel = QtWidgets.QLabel(self.MiscTab)
self.uiConsoleHttpPathLabel.setObjectName("uiConsoleHttpPathLabel")
self.gridLayout.addWidget(self.uiConsoleHttpPathLabel, 5, 0, 1, 1)
self.uiConsoleHttpPathLineEdit = QtWidgets.QLineEdit(parent=self.MiscTab)
self.uiConsoleHttpPathLineEdit = QtWidgets.QLineEdit(self.MiscTab)
self.uiConsoleHttpPathLineEdit.setObjectName("uiConsoleHttpPathLineEdit")
self.gridLayout.addWidget(self.uiConsoleHttpPathLineEdit, 5, 2, 1, 1)
self.uiDefaultNameFormatLabel = QtWidgets.QLabel(parent=self.MiscTab)
self.uiDefaultNameFormatLabel = QtWidgets.QLabel(self.MiscTab)
self.uiDefaultNameFormatLabel.setObjectName("uiDefaultNameFormatLabel")
self.gridLayout.addWidget(self.uiDefaultNameFormatLabel, 6, 0, 1, 1)
self.uiDefaultNameFormatLineEdit = QtWidgets.QLineEdit(parent=self.MiscTab)
self.uiDefaultNameFormatLineEdit = QtWidgets.QLineEdit(self.MiscTab)
self.uiDefaultNameFormatLineEdit.setObjectName("uiDefaultNameFormatLineEdit")
self.gridLayout.addWidget(self.uiDefaultNameFormatLineEdit, 6, 2, 1, 1)
self.uiSymbolLabel = QtWidgets.QLabel(parent=self.MiscTab)
self.uiSymbolLabel = QtWidgets.QLabel(self.MiscTab)
self.uiSymbolLabel.setObjectName("uiSymbolLabel")
self.gridLayout.addWidget(self.uiSymbolLabel, 7, 0, 1, 1)
spacerItem2 = QtWidgets.QSpacerItem(20, 399, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
spacerItem2 = QtWidgets.QSpacerItem(20, 399, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem2, 9, 0, 1, 3)
self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
self.uiSymbolLineEdit = QtWidgets.QLineEdit(parent=self.MiscTab)
self.uiSymbolLineEdit = QtWidgets.QLineEdit(self.MiscTab)
self.uiSymbolLineEdit.setObjectName("uiSymbolLineEdit")
self.horizontalLayout_7.addWidget(self.uiSymbolLineEdit)
self.uiSymbolToolButton = QtWidgets.QToolButton(parent=self.MiscTab)
self.uiSymbolToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonStyle.ToolButtonTextOnly)
self.uiSymbolToolButton = QtWidgets.QToolButton(self.MiscTab)
self.uiSymbolToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiSymbolToolButton.setObjectName("uiSymbolToolButton")
self.horizontalLayout_7.addWidget(self.uiSymbolToolButton)
self.gridLayout.addLayout(self.horizontalLayout_7, 7, 2, 1, 1)
self.uiCategoryLabel = QtWidgets.QLabel(parent=self.MiscTab)
self.uiCategoryLabel = QtWidgets.QLabel(self.MiscTab)
self.uiCategoryLabel.setObjectName("uiCategoryLabel")
self.gridLayout.addWidget(self.uiCategoryLabel, 8, 0, 1, 1)
self.uiCategoryComboBox = QtWidgets.QComboBox(parent=self.MiscTab)
self.uiCategoryComboBox = QtWidgets.QComboBox(self.MiscTab)
self.uiCategoryComboBox.setObjectName("uiCategoryComboBox")
self.gridLayout.addWidget(self.uiCategoryComboBox, 8, 2, 1, 1)
self.uiConsoleTypeLabel = QtWidgets.QLabel(parent=self.MiscTab)
self.uiConsoleTypeLabel = QtWidgets.QLabel(self.MiscTab)
self.uiConsoleTypeLabel.setObjectName("uiConsoleTypeLabel")
self.gridLayout.addWidget(self.uiConsoleTypeLabel, 1, 0, 1, 1)
self.uiConsoleTypeComboBox = QtWidgets.QComboBox(parent=self.MiscTab)
self.uiConsoleTypeComboBox = QtWidgets.QComboBox(self.MiscTab)
self.uiConsoleTypeComboBox.setObjectName("uiConsoleTypeComboBox")
self.uiConsoleTypeComboBox.addItem("")
self.uiConsoleTypeComboBox.addItem("")
@@ -282,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)
@@ -336,3 +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

@@ -38,6 +38,13 @@
<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>

View File

@@ -1,13 +1,12 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/builtin/ui/cloud_preferences_page.ui'
#
# Created by: PyQt6 UI code generator 6.10.1
# Created by: PyQt5 UI code generator 5.9
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_CloudPreferencesPageWidget(object):
def setupUi(self, CloudPreferencesPageWidget):
@@ -16,29 +15,34 @@ class Ui_CloudPreferencesPageWidget(object):
CloudPreferencesPageWidget.setAccessibleDescription("")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(CloudPreferencesPageWidget)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.splitter = QtWidgets.QSplitter(parent=CloudPreferencesPageWidget)
self.splitter.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.splitter = QtWidgets.QSplitter(CloudPreferencesPageWidget)
self.splitter.setOrientation(QtCore.Qt.Horizontal)
self.splitter.setObjectName("splitter")
self.uiCloudNodesTreeWidget = QtWidgets.QTreeWidget(parent=self.splitter)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Expanding)
self.uiCloudNodesTreeWidget = QtWidgets.QTreeWidget(self.splitter)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiCloudNodesTreeWidget.sizePolicy().hasHeightForWidth())
self.uiCloudNodesTreeWidget.setSizePolicy(sizePolicy)
self.uiCloudNodesTreeWidget.setMaximumSize(QtCore.QSize(160, 16777215))
self.uiCloudNodesTreeWidget.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)
font = QtGui.QFont()
font.setPointSize(11)
font.setBold(True)
font.setWeight(75)
self.uiCloudNodesTreeWidget.setFont(font)
self.uiCloudNodesTreeWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.uiCloudNodesTreeWidget.setIconSize(QtCore.QSize(32, 32))
self.uiCloudNodesTreeWidget.setRootIsDecorated(False)
self.uiCloudNodesTreeWidget.setObjectName("uiCloudNodesTreeWidget")
self.uiCloudNodesTreeWidget.headerItem().setText(0, "1")
self.uiCloudNodesTreeWidget.header().setVisible(False)
self.layoutWidget = QtWidgets.QWidget(parent=self.splitter)
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.uiCloudNodeInfoTreeWidget = QtWidgets.QTreeWidget(parent=self.layoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Expanding)
self.uiCloudNodeInfoTreeWidget = QtWidgets.QTreeWidget(self.layoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiCloudNodeInfoTreeWidget.sizePolicy().hasHeightForWidth())
@@ -50,14 +54,14 @@ class Ui_CloudPreferencesPageWidget(object):
self.verticalLayout.addWidget(self.uiCloudNodeInfoTreeWidget)
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
self.uiNewCloudNodePushButton = QtWidgets.QPushButton(parent=self.layoutWidget)
self.uiNewCloudNodePushButton = QtWidgets.QPushButton(self.layoutWidget)
self.uiNewCloudNodePushButton.setObjectName("uiNewCloudNodePushButton")
self.horizontalLayout_5.addWidget(self.uiNewCloudNodePushButton)
self.uiEditCloudNodePushButton = QtWidgets.QPushButton(parent=self.layoutWidget)
self.uiEditCloudNodePushButton = QtWidgets.QPushButton(self.layoutWidget)
self.uiEditCloudNodePushButton.setEnabled(False)
self.uiEditCloudNodePushButton.setObjectName("uiEditCloudNodePushButton")
self.horizontalLayout_5.addWidget(self.uiEditCloudNodePushButton)
self.uiDeleteCloudNodePushButton = QtWidgets.QPushButton(parent=self.layoutWidget)
self.uiDeleteCloudNodePushButton = QtWidgets.QPushButton(self.layoutWidget)
self.uiDeleteCloudNodePushButton.setEnabled(False)
self.uiDeleteCloudNodePushButton.setObjectName("uiDeleteCloudNodePushButton")
self.horizontalLayout_5.addWidget(self.uiDeleteCloudNodePushButton)
@@ -77,3 +81,4 @@ class Ui_CloudPreferencesPageWidget(object):
self.uiNewCloudNodePushButton.setText(_translate("CloudPreferencesPageWidget", "&New"))
self.uiEditCloudNodePushButton.setText(_translate("CloudPreferencesPageWidget", "&Edit"))
self.uiDeleteCloudNodePushButton.setText(_translate("CloudPreferencesPageWidget", "&Delete"))

View File

@@ -1,13 +1,12 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/builtin/ui/cloud_wizard.ui'
#
# Created by: PyQt6 UI code generator 6.10.1
# Created by: PyQt5 UI code generator 5.9
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_CloudNodeWizard(object):
def setupUi(self, CloudNodeWizard):
@@ -18,31 +17,31 @@ class Ui_CloudNodeWizard(object):
self.uiServerWizardPage.setObjectName("uiServerWizardPage")
self.gridLayout_2 = QtWidgets.QGridLayout(self.uiServerWizardPage)
self.gridLayout_2.setObjectName("gridLayout_2")
self.uiServerTypeGroupBox = QtWidgets.QGroupBox(parent=self.uiServerWizardPage)
self.uiServerTypeGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
self.uiServerTypeGroupBox.setObjectName("uiServerTypeGroupBox")
self.verticalLayout = QtWidgets.QVBoxLayout(self.uiServerTypeGroupBox)
self.verticalLayout.setObjectName("verticalLayout")
self.uiRemoteRadioButton = QtWidgets.QRadioButton(parent=self.uiServerTypeGroupBox)
self.uiRemoteRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiRemoteRadioButton.setChecked(True)
self.uiRemoteRadioButton.setObjectName("uiRemoteRadioButton")
self.verticalLayout.addWidget(self.uiRemoteRadioButton)
self.uiVMRadioButton = QtWidgets.QRadioButton(parent=self.uiServerTypeGroupBox)
self.uiVMRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiVMRadioButton.setObjectName("uiVMRadioButton")
self.verticalLayout.addWidget(self.uiVMRadioButton)
self.uiLocalRadioButton = QtWidgets.QRadioButton(parent=self.uiServerTypeGroupBox)
self.uiLocalRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiLocalRadioButton.setObjectName("uiLocalRadioButton")
self.verticalLayout.addWidget(self.uiLocalRadioButton)
self.gridLayout_2.addWidget(self.uiServerTypeGroupBox, 0, 0, 1, 1)
self.uiRemoteServersGroupBox = QtWidgets.QGroupBox(parent=self.uiServerWizardPage)
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(parent=self.uiRemoteServersGroupBox)
self.uiRemoteServersLabel = QtWidgets.QLabel(self.uiRemoteServersGroupBox)
self.uiRemoteServersLabel.setObjectName("uiRemoteServersLabel")
self.gridLayout_7.addWidget(self.uiRemoteServersLabel, 0, 0, 1, 1)
self.uiRemoteServersComboBox = QtWidgets.QComboBox(parent=self.uiRemoteServersGroupBox)
self.uiRemoteServersComboBox = QtWidgets.QComboBox(self.uiRemoteServersGroupBox)
self.uiRemoteServersComboBox.setEnabled(False)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiRemoteServersComboBox.sizePolicy().hasHeightForWidth())
@@ -55,10 +54,10 @@ class Ui_CloudNodeWizard(object):
self.uiNameWizardPage.setObjectName("uiNameWizardPage")
self.gridLayout = QtWidgets.QGridLayout(self.uiNameWizardPage)
self.gridLayout.setObjectName("gridLayout")
self.uiNameLabel = QtWidgets.QLabel(parent=self.uiNameWizardPage)
self.uiNameLabel = QtWidgets.QLabel(self.uiNameWizardPage)
self.uiNameLabel.setObjectName("uiNameLabel")
self.gridLayout.addWidget(self.uiNameLabel, 0, 0, 1, 1)
self.uiNameLineEdit = QtWidgets.QLineEdit(parent=self.uiNameWizardPage)
self.uiNameLineEdit = QtWidgets.QLineEdit(self.uiNameWizardPage)
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
CloudNodeWizard.addPage(self.uiNameWizardPage)
@@ -80,3 +79,4 @@ class Ui_CloudNodeWizard(object):
self.uiNameWizardPage.setTitle(_translate("CloudNodeWizard", "Name"))
self.uiNameWizardPage.setSubTitle(_translate("CloudNodeWizard", "Please choose a descriptive name for the new cloud node."))
self.uiNameLabel.setText(_translate("CloudNodeWizard", "Name:"))

View File

@@ -1,13 +1,12 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/builtin/ui/ethernet_hub_configuration_page.ui'
#
# Created by: PyQt6 UI code generator 6.10.1
# Created by: PyQt5 UI code generator 5.11.3
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_ethernetHubConfigPageWidget(object):
def setupUi(self, ethernetHubConfigPageWidget):
@@ -15,8 +14,8 @@ class Ui_ethernetHubConfigPageWidget(object):
ethernetHubConfigPageWidget.resize(591, 352)
self.verticalLayout = QtWidgets.QVBoxLayout(ethernetHubConfigPageWidget)
self.verticalLayout.setObjectName("verticalLayout")
self.uiSettingsGroupBox = QtWidgets.QGroupBox(parent=ethernetHubConfigPageWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred)
self.uiSettingsGroupBox = QtWidgets.QGroupBox(ethernetHubConfigPageWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiSettingsGroupBox.sizePolicy().hasHeightForWidth())
@@ -24,39 +23,39 @@ class Ui_ethernetHubConfigPageWidget(object):
self.uiSettingsGroupBox.setObjectName("uiSettingsGroupBox")
self.gridLayout = QtWidgets.QGridLayout(self.uiSettingsGroupBox)
self.gridLayout.setObjectName("gridLayout")
self.uiNameLabel = QtWidgets.QLabel(parent=self.uiSettingsGroupBox)
self.uiNameLabel = QtWidgets.QLabel(self.uiSettingsGroupBox)
self.uiNameLabel.setObjectName("uiNameLabel")
self.gridLayout.addWidget(self.uiNameLabel, 0, 0, 1, 1)
self.uiDefaultNameFormatLabel = QtWidgets.QLabel(parent=self.uiSettingsGroupBox)
self.uiDefaultNameFormatLabel = QtWidgets.QLabel(self.uiSettingsGroupBox)
self.uiDefaultNameFormatLabel.setObjectName("uiDefaultNameFormatLabel")
self.gridLayout.addWidget(self.uiDefaultNameFormatLabel, 1, 0, 1, 2)
self.uiDefaultNameFormatLineEdit = QtWidgets.QLineEdit(parent=self.uiSettingsGroupBox)
self.uiDefaultNameFormatLineEdit = QtWidgets.QLineEdit(self.uiSettingsGroupBox)
self.uiDefaultNameFormatLineEdit.setObjectName("uiDefaultNameFormatLineEdit")
self.gridLayout.addWidget(self.uiDefaultNameFormatLineEdit, 1, 2, 1, 1)
self.uiSymbolLabel = QtWidgets.QLabel(parent=self.uiSettingsGroupBox)
self.uiSymbolLabel = QtWidgets.QLabel(self.uiSettingsGroupBox)
self.uiSymbolLabel.setObjectName("uiSymbolLabel")
self.gridLayout.addWidget(self.uiSymbolLabel, 2, 0, 1, 2)
self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
self.uiSymbolLineEdit = QtWidgets.QLineEdit(parent=self.uiSettingsGroupBox)
self.uiSymbolLineEdit = QtWidgets.QLineEdit(self.uiSettingsGroupBox)
self.uiSymbolLineEdit.setObjectName("uiSymbolLineEdit")
self.horizontalLayout_7.addWidget(self.uiSymbolLineEdit)
self.uiSymbolToolButton = QtWidgets.QToolButton(parent=self.uiSettingsGroupBox)
self.uiSymbolToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonStyle.ToolButtonTextOnly)
self.uiSymbolToolButton = QtWidgets.QToolButton(self.uiSettingsGroupBox)
self.uiSymbolToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiSymbolToolButton.setObjectName("uiSymbolToolButton")
self.horizontalLayout_7.addWidget(self.uiSymbolToolButton)
self.gridLayout.addLayout(self.horizontalLayout_7, 2, 2, 1, 1)
self.uiCategoryLabel = QtWidgets.QLabel(parent=self.uiSettingsGroupBox)
self.uiCategoryLabel = QtWidgets.QLabel(self.uiSettingsGroupBox)
self.uiCategoryLabel.setObjectName("uiCategoryLabel")
self.gridLayout.addWidget(self.uiCategoryLabel, 3, 0, 1, 2)
self.uiCategoryComboBox = QtWidgets.QComboBox(parent=self.uiSettingsGroupBox)
self.uiCategoryComboBox = QtWidgets.QComboBox(self.uiSettingsGroupBox)
self.uiCategoryComboBox.setObjectName("uiCategoryComboBox")
self.gridLayout.addWidget(self.uiCategoryComboBox, 3, 2, 1, 1)
self.uiPortsLabel = QtWidgets.QLabel(parent=self.uiSettingsGroupBox)
self.uiPortsLabel = QtWidgets.QLabel(self.uiSettingsGroupBox)
self.uiPortsLabel.setObjectName("uiPortsLabel")
self.gridLayout.addWidget(self.uiPortsLabel, 4, 0, 1, 1)
self.uiPortsSpinBox = QtWidgets.QSpinBox(parent=self.uiSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed)
self.uiPortsSpinBox = QtWidgets.QSpinBox(self.uiSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiPortsSpinBox.sizePolicy().hasHeightForWidth())
@@ -66,9 +65,9 @@ class Ui_ethernetHubConfigPageWidget(object):
self.uiPortsSpinBox.setProperty("value", 1)
self.uiPortsSpinBox.setObjectName("uiPortsSpinBox")
self.gridLayout.addWidget(self.uiPortsSpinBox, 4, 2, 1, 1)
spacerItem = QtWidgets.QSpacerItem(20, 71, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
spacerItem = QtWidgets.QSpacerItem(20, 71, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem, 5, 2, 1, 1)
self.uiNameLineEdit = QtWidgets.QLineEdit(parent=self.uiSettingsGroupBox)
self.uiNameLineEdit = QtWidgets.QLineEdit(self.uiSettingsGroupBox)
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 2, 1, 1)
self.verticalLayout.addWidget(self.uiSettingsGroupBox)
@@ -86,3 +85,4 @@ class Ui_ethernetHubConfigPageWidget(object):
self.uiSymbolToolButton.setText(_translate("ethernetHubConfigPageWidget", "&Browse..."))
self.uiCategoryLabel.setText(_translate("ethernetHubConfigPageWidget", "Category:"))
self.uiPortsLabel.setText(_translate("ethernetHubConfigPageWidget", "Number of ports:"))

View File

@@ -38,6 +38,13 @@
<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>

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