mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-05-28 22:40:30 +03:00
Compare commits
260 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcf62fc507 | ||
|
|
c811c270ec | ||
|
|
dbc519e0af | ||
|
|
bcbf8be182 | ||
|
|
43df10520f | ||
|
|
55b37716a3 | ||
|
|
e08253e362 | ||
|
|
b9a59183a1 | ||
|
|
342ca95bd2 | ||
|
|
187ef561fd | ||
|
|
97070718fa | ||
|
|
9466b2a1fb | ||
|
|
3edde1274b | ||
|
|
e17e7fc033 | ||
|
|
bfe11d7976 | ||
|
|
2b98d51ff7 | ||
|
|
f6fb0100e2 | ||
|
|
804b871cd6 | ||
|
|
c604ff70c7 | ||
|
|
63a7f36cfe | ||
|
|
731fee1217 | ||
|
|
714dae44f3 | ||
|
|
c67dc19ec8 | ||
|
|
1c5f6b362b | ||
|
|
683400c204 | ||
|
|
5efb3019f4 | ||
|
|
23e0520cd2 | ||
|
|
1199de27df | ||
|
|
b3140f9d8e | ||
|
|
1b50cdc341 | ||
|
|
bd15734c30 | ||
|
|
24fff8972a | ||
|
|
9614a1253c | ||
|
|
a3a0be863e | ||
|
|
9b5713df03 | ||
|
|
6df7dc4730 | ||
|
|
e84ef8bf13 | ||
|
|
f7703e3fa2 | ||
|
|
de8b9bc8f1 | ||
|
|
f21a530729 | ||
|
|
be2b5eecf6 | ||
|
|
2d4f6e6ecf | ||
|
|
c152de84de | ||
|
|
d34e7b377c | ||
|
|
74c55241cf | ||
|
|
6edb9c9303 | ||
|
|
5d14dd9ab8 | ||
|
|
0c7cae2222 | ||
|
|
9b882924c0 | ||
|
|
fccfc01f2e | ||
|
|
46872c664d | ||
|
|
89166b9d35 | ||
|
|
d0421e8b1f | ||
|
|
b35ce7303d | ||
|
|
47432568e6 | ||
|
|
42ea34ca6c | ||
|
|
9f3598d36d | ||
|
|
8df248a1e6 | ||
|
|
7196aeb3cf | ||
|
|
38657f4112 | ||
|
|
84017fa0f1 | ||
|
|
3aa5a5369f | ||
|
|
0ed791b946 | ||
|
|
133732b7ae | ||
|
|
2ed48def9f | ||
|
|
279a91d402 | ||
|
|
4906678d13 | ||
|
|
a5ff9318f0 | ||
|
|
faa802d59c | ||
|
|
01b5b8bfa8 | ||
|
|
b22c5c8442 | ||
|
|
141e7d8307 | ||
|
|
1e80354e4e | ||
|
|
d340b1f50a | ||
|
|
d7bb195610 | ||
|
|
d963dd4746 | ||
|
|
60addccd95 | ||
|
|
80b654ba53 | ||
|
|
b67a8c87a7 | ||
|
|
53ece94a08 | ||
|
|
a6c7e0be59 | ||
|
|
fcea25dcbb | ||
|
|
f29c065164 | ||
|
|
c6b5494ce6 | ||
|
|
02b14f6aea | ||
|
|
e2cc378aee | ||
|
|
812aedebe3 | ||
|
|
2f0d2063cf | ||
|
|
ce0515f0ae | ||
|
|
bc10c69a2d | ||
|
|
3707758388 | ||
|
|
8aaefac91b | ||
|
|
19157ab49d | ||
|
|
38b98cd883 | ||
|
|
e9419924c5 | ||
|
|
cf3c5c09fa | ||
|
|
9e89cf5ad5 | ||
|
|
131ef09b55 | ||
|
|
2df18ee04e | ||
|
|
854e1fded6 | ||
|
|
31711a9d4e | ||
|
|
fd2e236927 | ||
|
|
21409a899d | ||
|
|
25be9e7ec7 | ||
|
|
1260c2bc2d | ||
|
|
1441e38876 | ||
|
|
3c8cff20b7 | ||
|
|
f831d71c3f | ||
|
|
f71b6dcda1 | ||
|
|
62289e7be3 | ||
|
|
7447e9b7d4 | ||
|
|
c5961f400e | ||
|
|
6ca61905b2 | ||
|
|
130e91da76 | ||
|
|
bbc5b3e4ac | ||
|
|
2a72ad5e0b | ||
|
|
88ed9407b9 | ||
|
|
18be274fed | ||
|
|
fefda50378 | ||
|
|
3df374e784 | ||
|
|
04fb449b44 | ||
|
|
fc54b76ee1 | ||
|
|
59b284e18b | ||
|
|
1d4492c911 | ||
|
|
9d8b6a172e | ||
|
|
8c3ef7a968 | ||
|
|
947733aada | ||
|
|
a199fef03b | ||
|
|
29b851207b | ||
|
|
ca5557e579 | ||
|
|
7331ae29ef | ||
|
|
3a7e06e14b | ||
|
|
0a81af8248 | ||
|
|
a882956ec9 | ||
|
|
9f4361d66f | ||
|
|
d10c3c7308 | ||
|
|
0cd7d7e4c2 | ||
|
|
8f3f72ff54 | ||
|
|
7fbc0befa1 | ||
|
|
9d9668442e | ||
|
|
932083be88 | ||
|
|
82e7c151c4 | ||
|
|
7e5c363bc3 | ||
|
|
15d029a7fb | ||
|
|
9087ba8f5a | ||
|
|
3d89d6e6cc | ||
|
|
91bae81300 | ||
|
|
7de5bf6bd5 | ||
|
|
9de238619a | ||
|
|
ed88466d63 | ||
|
|
478b793b04 | ||
|
|
841c29e6f6 | ||
|
|
f9d96051f5 | ||
|
|
607e201674 | ||
|
|
18950ca64f | ||
|
|
c0b5f39c4c | ||
|
|
3e717999ca | ||
|
|
800d14363d | ||
|
|
2dd9d61c57 | ||
|
|
10afb5a8de | ||
|
|
3413afe952 | ||
|
|
7222da9512 | ||
|
|
dbe8df5a37 | ||
|
|
a9890265b9 | ||
|
|
97b777ceea | ||
|
|
c06e534935 | ||
|
|
025276f8a7 | ||
|
|
6777961d29 | ||
|
|
7f6cace0d5 | ||
|
|
1a739c0c37 | ||
|
|
6d855045ef | ||
|
|
fef734bbbe | ||
|
|
b079443735 | ||
|
|
4a32ae9736 | ||
|
|
9793d00131 | ||
|
|
2b7840279a | ||
|
|
9243083321 | ||
|
|
3a8b3e5c4a | ||
|
|
e2168a3c81 | ||
|
|
01deb01e6a | ||
|
|
1133ee6e1b | ||
|
|
7512ffec64 | ||
|
|
3527e5551c | ||
|
|
72960f8f2b | ||
|
|
8abb502c72 | ||
|
|
08c729e83a | ||
|
|
aac004bd2f | ||
|
|
70677d8f18 | ||
|
|
fba1ff4208 | ||
|
|
e4edbefc23 | ||
|
|
d93f9afe74 | ||
|
|
162d197e36 | ||
|
|
5c21dd8a2f | ||
|
|
aa9b9d3b0b | ||
|
|
eae9eec15b | ||
|
|
a3bf832721 | ||
|
|
67890d74d9 | ||
|
|
ab4325f951 | ||
|
|
2a947b9cc5 | ||
|
|
601c082288 | ||
|
|
7701d57bd0 | ||
|
|
f0b4148a20 | ||
|
|
2fdcbafbc1 | ||
|
|
5d82cea935 | ||
|
|
b0e3e93c41 | ||
|
|
535f53737d | ||
|
|
354f3eecec | ||
|
|
35a6a5c8c7 | ||
|
|
23cba0a28d | ||
|
|
9c3d7bc95a | ||
|
|
cf2802b15a | ||
|
|
b162c55078 | ||
|
|
bb42b0ed0b | ||
|
|
e108b5194d | ||
|
|
e9ef8735be | ||
|
|
e8e90bb16a | ||
|
|
49f77930f4 | ||
|
|
a58451a552 | ||
|
|
0a43b9e6e9 | ||
|
|
4f32619ed8 | ||
|
|
20740748c1 | ||
|
|
8a5ab6b374 | ||
|
|
e11ce27f7b | ||
|
|
4b7cf4e553 | ||
|
|
b72358461c | ||
|
|
2987bcf91a | ||
|
|
5e97bc0f86 | ||
|
|
e3a3de5df7 | ||
|
|
e1693ce113 | ||
|
|
522091d219 | ||
|
|
1446748934 | ||
|
|
3f0ce380e8 | ||
|
|
bd71383354 | ||
|
|
9649895378 | ||
|
|
8579ffa20a | ||
|
|
3206743329 | ||
|
|
29c87b6e96 | ||
|
|
93b2721d6a | ||
|
|
1ff369683f | ||
|
|
7c56a2467c | ||
|
|
59ef34c17d | ||
|
|
d1fae54049 | ||
|
|
49bd61f769 | ||
|
|
9a4faddd10 | ||
|
|
7654681a94 | ||
|
|
9bfecde957 | ||
|
|
705cbf8bb9 | ||
|
|
ab6e0ce496 | ||
|
|
8042c9eb6f | ||
|
|
ad3c8a09db | ||
|
|
ea4a7f201e | ||
|
|
28c82b8718 | ||
|
|
6a4dd59e81 | ||
|
|
7418c190a8 | ||
|
|
737e32f5c3 | ||
|
|
cfc09d2c14 | ||
|
|
f6ab5cae16 | ||
|
|
39ec7eb8ea | ||
|
|
64c579d43c | ||
|
|
181bf3f360 |
16
.github/workflows/add-new-issues-to-project.yml
vendored
Normal file
16
.github/workflows/add-new-issues-to-project.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Add new issues to GNS3 project
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
|
||||
jobs:
|
||||
add-to-project:
|
||||
name: Add issue to project
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/add-to-project@v1.0.1
|
||||
with:
|
||||
project-url: https://github.com/orgs/GNS3/projects/3
|
||||
github-token: ${{ secrets.ADD_NEW_ISSUES_TO_PROJECT }}
|
||||
73
.github/workflows/codeql.yml
vendored
73
.github/workflows/codeql.yml
vendored
@@ -15,62 +15,79 @@ on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ "master" ]
|
||||
schedule:
|
||||
- cron: '27 6 * * 2'
|
||||
- cron: '17 22 * * 6'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
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
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'python' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Use only 'java' to analyze code written in Java, Kotlin or both
|
||||
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
|
||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||
|
||||
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@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
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.
|
||||
|
||||
# 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
|
||||
# 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
|
||||
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# 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 the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
- 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@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
2
.github/workflows/testing.yml
vendored
2
.github/workflows/testing.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build and run Docker image
|
||||
run: |
|
||||
docker build -t gns3-gui-test .
|
||||
|
||||
149
CHANGELOG
149
CHANGELOG
@@ -1,5 +1,154 @@
|
||||
# Change Log
|
||||
|
||||
## 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
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# Run tests inside a container
|
||||
FROM ubuntu:18.04
|
||||
FROM ubuntu:latest
|
||||
MAINTAINER GNS3 Team
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y --force-yes python3.6 python3-pyqt5 python3-pip python3-pyqt5.qtsvg python3-pyqt5.qtwebsockets python3.6-dev xvfb
|
||||
RUN apt-get install -y --force-yes python3 python3-pyqt5 python3-pip python3-pyqt5.qtsvg python3-pyqt5.qtwebsockets python3-dev xvfb
|
||||
RUN apt-get clean
|
||||
|
||||
ADD dev-requirements.txt /dev-requirements.txt
|
||||
ADD requirements.txt /requirements.txt
|
||||
RUN pip3 install --no-cache-dir -r /dev-requirements.txt
|
||||
RUN python3 -m pip install --break-system-packages --no-cache-dir -r /dev-requirements.txt
|
||||
|
||||
ADD . /src
|
||||
WORKDIR /src
|
||||
|
||||
CMD xvfb-run python3.6 -m pytest -vv
|
||||
CMD xvfb-run python3 -m pytest -vv
|
||||
|
||||
20
appveyor.yml
20
appveyor.yml
@@ -1,20 +0,0 @@
|
||||
version: '{build}-{branch}'
|
||||
|
||||
image: Visual Studio 2022
|
||||
|
||||
platform: x64
|
||||
|
||||
environment:
|
||||
PYTHON: "C:\\Python37-x64"
|
||||
DISTUTILS_USE_SDK: "1"
|
||||
|
||||
install:
|
||||
- cinst nmap
|
||||
- "%PYTHON%\\python.exe -m pip install -U pip setuptools" # upgrade pip & setuptools first
|
||||
- "%PYTHON%\\python.exe -m pip install -r dev-requirements.txt"
|
||||
- "%PYTHON%\\python.exe -m pip install -r win-requirements.txt"
|
||||
|
||||
build: off
|
||||
|
||||
test_script:
|
||||
- "%PYTHON%\\python.exe -m pytest -v"
|
||||
@@ -1,6 +1,4 @@
|
||||
-rrequirements.txt
|
||||
|
||||
pytest==7.2.0; python_version >= '3.7'
|
||||
pytest==7.0.1; python_version < '3.7' # v7.0.1 is the last version to support Python 3.6
|
||||
flake8==5.0.4
|
||||
pytest-timeout==2.1.0
|
||||
pytest==8.3.2
|
||||
pytest-timeout==2.3.1
|
||||
|
||||
@@ -46,6 +46,9 @@ class Application(QtWidgets.QApplication):
|
||||
|
||||
super().__init__(argv)
|
||||
|
||||
# this is tell Wayland what is the name of the desktop file (gns3.desktop)
|
||||
self.setDesktopFileName("gns3")
|
||||
|
||||
# this info is necessary for QSettings
|
||||
self.setOrganizationName("GNS3")
|
||||
self.setOrganizationDomain("gns3.net")
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
Handles commands typed in the GNS3 console.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import cmd
|
||||
import struct
|
||||
@@ -34,6 +35,14 @@ log = logging.getLogger(__name__)
|
||||
|
||||
class ConsoleCmd(cmd.Cmd):
|
||||
|
||||
def do_env(self, args):
|
||||
"""
|
||||
Show the environment variables used by GNS3.
|
||||
"""
|
||||
|
||||
for key, val in os.environ.items():
|
||||
print("{}={}".format(key, val))
|
||||
|
||||
def do_version(self, args):
|
||||
"""
|
||||
Show the version of GNS3 and its dependencies.
|
||||
|
||||
@@ -25,6 +25,8 @@ from .qt import QtCore, QtNetwork, QtGui, QtWidgets, QtWebSockets, qpartial, qsl
|
||||
from .symbol import Symbol
|
||||
from .local_server_config import LocalServerConfig
|
||||
from .settings import LOCAL_SERVER_SETTINGS
|
||||
|
||||
from gns3.local_config import LocalConfig
|
||||
from gns3.utils import parse_version
|
||||
|
||||
import logging
|
||||
@@ -416,19 +418,23 @@ class Controller(QtCore.QObject):
|
||||
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"):
|
||||
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))
|
||||
|
||||
else:
|
||||
self._notification_stream = self._http_client.connectWebSocket(self._websocket, "/notifications/ws")
|
||||
self._notification_stream.textMessageReceived.connect(self._websocket_event_received)
|
||||
self._notification_stream.error.connect(self._websocket_error)
|
||||
self._notification_stream.sslErrors.connect(self._sslErrorsSlot)
|
||||
log.info("Listening for controller notifications on '{}'".format(self._notification_stream.requestUrl().toString()))
|
||||
|
||||
def stopListenNotifications(self):
|
||||
if self._notification_stream:
|
||||
@@ -449,7 +455,7 @@ class Controller(QtCore.QObject):
|
||||
@qslot
|
||||
def _websocket_error(self, error):
|
||||
if self._notification_stream:
|
||||
log.error("Websocket notification stream error: {}".format(self._notification_stream.errorString()))
|
||||
log.error("Websocket controller notification stream error: {}".format(self._notification_stream.errorString()))
|
||||
self._notification_stream = None
|
||||
self._startListenNotifications()
|
||||
|
||||
@@ -479,11 +485,21 @@ class Controller(QtCore.QObject):
|
||||
elif result["action"] == "compute.created" or result["action"] == "compute.updated":
|
||||
from .compute_manager import ComputeManager
|
||||
ComputeManager.instance().computeDataReceivedCallback(result["event"])
|
||||
elif result["action"] == "log.error":
|
||||
log.error(result["event"]["message"])
|
||||
elif result["action"] == "log.warning":
|
||||
log.warning(result["event"]["message"])
|
||||
elif result["action"] == "log.info":
|
||||
log.info(result["event"]["message"], extra={"show": True})
|
||||
elif result["action"] == "project.closed":
|
||||
from .topology import Topology
|
||||
project = Topology.instance().project()
|
||||
if project and project.id() == result["event"]["project_id"]:
|
||||
Topology.instance().setProject(None)
|
||||
elif result["action"] == "project.updated":
|
||||
from .topology import Topology
|
||||
project = Topology.instance().project()
|
||||
if project and project.id() == result["event"]["project_id"]:
|
||||
project.projectUpdatedCallback(result["event"])
|
||||
elif result["action"] == "log.error" and result["event"].get("message"):
|
||||
log.error(result["event"].get("message"))
|
||||
elif result["action"] == "log.warning" and result["event"].get("message"):
|
||||
log.warning(result["event"].get("message"))
|
||||
elif result["action"] == "log.info" and result["event"].get("message"):
|
||||
log.info(result["event"].get("message"), extra={"show": True})
|
||||
elif result["action"] == "ping":
|
||||
pass
|
||||
|
||||
@@ -15,12 +15,6 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import platform
|
||||
import struct
|
||||
import distro
|
||||
|
||||
try:
|
||||
import sentry_sdk
|
||||
from sentry_sdk.integrations.logging import LoggingIntegration
|
||||
@@ -29,7 +23,12 @@ except ImportError:
|
||||
# Sentry SDK is not installed with deb package in order to simplify packaging
|
||||
SENTRY_SDK_AVAILABLE = False
|
||||
|
||||
from .utils.get_resource import get_resource
|
||||
import sys
|
||||
import os
|
||||
import platform
|
||||
import struct
|
||||
import distro
|
||||
|
||||
from .version import __version__, __version_info__
|
||||
|
||||
import logging
|
||||
@@ -51,7 +50,7 @@ class CrashReport:
|
||||
Report crash to a third party service
|
||||
"""
|
||||
|
||||
DSN = "https://433337b5780f4fe8abec8ee5e85c5897@o19455.ingest.sentry.io/38506"
|
||||
DSN = "https://62d45083e8fee6a3f5c28d4710ef2cb6@o19455.ingest.us.sentry.io/38506"
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
@@ -64,22 +63,16 @@ class CrashReport:
|
||||
self._sentry_initialized = False
|
||||
|
||||
if SENTRY_SDK_AVAILABLE:
|
||||
cacert = None
|
||||
if hasattr(sys, "frozen"):
|
||||
cacert_resource = get_resource("cacert.pem")
|
||||
if cacert_resource is not None and os.path.isfile(cacert_resource):
|
||||
cacert = cacert_resource
|
||||
else:
|
||||
log.error("The SSL certificate bundle file '{}' could not be found".format(cacert_resource))
|
||||
|
||||
# Don't send log records as events.
|
||||
sentry_logging = LoggingIntegration(level=logging.INFO, event_level=None)
|
||||
|
||||
sentry_sdk.init(dsn=CrashReport.DSN,
|
||||
release=__version__,
|
||||
ca_certs=cacert,
|
||||
default_integrations=False,
|
||||
integrations=[sentry_logging])
|
||||
try:
|
||||
sentry_sdk.init(dsn=CrashReport.DSN,
|
||||
release=__version__,
|
||||
default_integrations=False,
|
||||
integrations=[sentry_logging])
|
||||
except Exception as e:
|
||||
log.error("Crash report could not be sent: {}".format(e))
|
||||
return
|
||||
|
||||
tags = {
|
||||
"os:name": platform.system(),
|
||||
|
||||
@@ -94,9 +94,11 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
self.setWindowTitle("Install {} appliance".format(self._appliance["name"]))
|
||||
|
||||
# add a custom button to show appliance information
|
||||
self.setButtonText(QtWidgets.QWizard.CustomButton1, "&Appliance info")
|
||||
self.setOption(QtWidgets.QWizard.HaveCustomButton1, True)
|
||||
self.customButtonClicked.connect(self._showApplianceInfoSlot)
|
||||
if self._appliance["registry_version"] < 8:
|
||||
# FIXME: show appliance info for v8
|
||||
self.setButtonText(QtWidgets.QWizard.CustomButton1, "&Appliance info")
|
||||
self.setOption(QtWidgets.QWizard.HaveCustomButton1, True)
|
||||
self.customButtonClicked.connect(self._showApplianceInfoSlot)
|
||||
|
||||
# customize the server selection
|
||||
self.uiRemoteRadioButton.toggled.connect(self._remoteServerToggledSlot)
|
||||
@@ -144,18 +146,9 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
if self.page(page_id) == self.uiServerWizardPage:
|
||||
|
||||
Controller.instance().getSymbols(self._getSymbolsCallback)
|
||||
|
||||
if "qemu" in self._appliance:
|
||||
emulator_type = "qemu"
|
||||
elif "iou" in self._appliance:
|
||||
emulator_type = "iou"
|
||||
elif "docker" in self._appliance:
|
||||
emulator_type = "docker"
|
||||
elif "dynamips" in self._appliance:
|
||||
emulator_type = "dynamips"
|
||||
else:
|
||||
QtWidgets.QMessageBox.warning(self, "Appliance", "Could not determine the emulator type")
|
||||
|
||||
template_type = self._appliance.template_type()
|
||||
if not template_type:
|
||||
raise ApplianceError("No template type found for appliance {}".format(self._appliance["name"]))
|
||||
is_mac = ComputeManager.instance().localPlatform().startswith("darwin")
|
||||
is_win = ComputeManager.instance().localPlatform().startswith("win")
|
||||
|
||||
@@ -173,11 +166,11 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
if ComputeManager.instance().localPlatform() is None:
|
||||
self.uiLocalRadioButton.setEnabled(False)
|
||||
elif is_mac or is_win:
|
||||
if emulator_type == "qemu":
|
||||
if template_type == "qemu":
|
||||
# disallow usage of the local server because Qemu has issues on OSX and Windows
|
||||
if not LocalConfig.instance().experimental():
|
||||
self.uiLocalRadioButton.setEnabled(False)
|
||||
elif emulator_type != "dynamips":
|
||||
elif template_type != "dynamips":
|
||||
self.uiLocalRadioButton.setEnabled(False)
|
||||
|
||||
if ComputeManager.instance().vmCompute():
|
||||
@@ -195,27 +188,55 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
|
||||
elif self.page(page_id) == self.uiFilesWizardPage:
|
||||
if Controller.instance().isRemote() or self._compute_id != "local":
|
||||
self._registry.getRemoteImageList(self._appliance.emulator(), self._compute_id)
|
||||
self._registry.getRemoteImageList(self._appliance.template_type(), self._compute_id)
|
||||
else:
|
||||
self.images_changed_signal.emit()
|
||||
|
||||
elif self.page(page_id) == self.uiQemuWizardPage:
|
||||
if self._appliance['qemu'].get('kvm', 'require') == 'require':
|
||||
if self._appliance.template_properties().get('kvm', 'require') == 'require':
|
||||
self._server_check = False
|
||||
Qemu.instance().getQemuCapabilitiesFromServer(self._compute_id, qpartial(self._qemuServerCapabilitiesCallback))
|
||||
else:
|
||||
self._server_check = True
|
||||
Qemu.instance().getQemuBinariesFromServer(self._compute_id, qpartial(self._getQemuBinariesFromServerCallback), [self._appliance["qemu"]["arch"]])
|
||||
if self._appliance["registry_version"] >= 8:
|
||||
qemu_platform = self._appliance.template_properties()["platform"]
|
||||
else:
|
||||
qemu_platform = self._appliance.template_properties()["arch"]
|
||||
Qemu.instance().getQemuBinariesFromServer(self._compute_id, qpartial(self._getQemuBinariesFromServerCallback), [qemu_platform])
|
||||
|
||||
elif self.page(page_id) == self.uiInstructionsPage:
|
||||
|
||||
installation_instructions = self._appliance.get("installation_instructions", "No installation instructions available")
|
||||
self.uiInstructionsTextEdit.setText(installation_instructions.strip())
|
||||
|
||||
elif self.page(page_id) == self.uiUsageWizardPage:
|
||||
self.uiUsageTextEdit.setText("The template will be available in the {} category.\n\n{}".format(self._appliance["category"].replace("_", " "), self._appliance.get("usage", "")))
|
||||
# TODO: allow taking these info fields at the version level in v8
|
||||
category = self._appliance["category"].replace("_", " ")
|
||||
usage = self._appliance.get("usage", "No usage information available")
|
||||
if self._appliance["registry_version"] >= 8:
|
||||
default_username = self._appliance.get("default_username")
|
||||
default_password = self._appliance.get("default_password")
|
||||
if default_username and default_password:
|
||||
usage += "\n\nDefault username: {}\nDefault password: {}".format(default_username, default_password)
|
||||
|
||||
usage_info = """
|
||||
The template will be available in the {} category.
|
||||
|
||||
Usage: {}
|
||||
""".format(category, usage)
|
||||
|
||||
self.uiUsageTextEdit.setText(usage_info.strip())
|
||||
|
||||
def _qemuServerCapabilitiesCallback(self, result, error=None, *args, **kwargs):
|
||||
"""
|
||||
Check if the server supports KVM or not
|
||||
"""
|
||||
|
||||
if error is None and "kvm" in result and self._appliance["qemu"]["arch"] in result["kvm"]:
|
||||
if self._appliance["registry_version"] >= 8:
|
||||
qemu_platform = self._appliance.template_properties()["platform"]
|
||||
else:
|
||||
qemu_platform = self._appliance.template_properties()["arch"]
|
||||
if error is None and "kvm" in result and qemu_platform in result["kvm"]:
|
||||
self._server_check = True
|
||||
else:
|
||||
if error:
|
||||
@@ -236,7 +257,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
log.error("Error while uploading image '{}': {}".format(image_path, result["message"]))
|
||||
else:
|
||||
log.info("Image '{}' has been successfully uploaded".format(image_path))
|
||||
self._registry.getRemoteImageList(self._appliance.emulator(), self._compute_id)
|
||||
self._registry.getRemoteImageList(self._appliance.template_type(), self._compute_id)
|
||||
|
||||
def _showApplianceInfoSlot(self):
|
||||
"""
|
||||
@@ -407,7 +428,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
|
||||
for version in self._appliance["versions"]:
|
||||
for image in version["images"].values():
|
||||
img = self._registry.search_image_file(self._appliance.emulator(),
|
||||
img = self._registry.search_image_file(self._appliance.template_type(),
|
||||
image["filename"],
|
||||
image.get("md5sum"),
|
||||
image.get("filesize"),
|
||||
@@ -519,12 +540,22 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
if len(path) == 0:
|
||||
return
|
||||
|
||||
image = Image(self._appliance.emulator(), path, filename=disk["filename"])
|
||||
image = Image(self._appliance.template_type(), path, filename=disk["filename"])
|
||||
try:
|
||||
if "md5sum" in disk and image.md5sum != disk["md5sum"]:
|
||||
reply = QtWidgets.QMessageBox.question(self, "Add appliance",
|
||||
"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)
|
||||
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.Yes | QtWidgets.QMessageBox.No
|
||||
)
|
||||
if reply == QtWidgets.QMessageBox.No:
|
||||
return
|
||||
except OSError as e:
|
||||
@@ -554,7 +585,11 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
if self.uiQemuListComboBox.count() == 1:
|
||||
self.next()
|
||||
else:
|
||||
i = self.uiQemuListComboBox.findData(self._appliance["qemu"]["arch"], flags=QtCore.Qt.MatchEndsWith)
|
||||
if self._appliance["registry_version"] >= 8:
|
||||
qemu_platform = self._appliance.template_properties()["platform"]
|
||||
else:
|
||||
qemu_platform = self._appliance.template_properties()["arch"]
|
||||
i = self.uiQemuListComboBox.findData(qemu_platform, flags=QtCore.Qt.MatchEndsWith)
|
||||
if i != -1:
|
||||
self.uiQemuListComboBox.setCurrentIndex(i)
|
||||
|
||||
@@ -567,8 +602,8 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
|
||||
if version is None:
|
||||
appliance_configuration = self._appliance.copy()
|
||||
if "docker" not in appliance_configuration:
|
||||
# only Docker do not have version
|
||||
if self._appliance.template_type() != "docker":
|
||||
# only Docker do not have versions
|
||||
return False
|
||||
else:
|
||||
try:
|
||||
@@ -585,10 +620,15 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
return False
|
||||
appliance_configuration["name"] = appliance_configuration["name"].strip()
|
||||
|
||||
if "qemu" in appliance_configuration:
|
||||
if self._appliance["registry_version"] >= 8:
|
||||
if "settings" in appliance_configuration:
|
||||
for settings in appliance_configuration["settings"]:
|
||||
if settings["template_type"] == "qemu":
|
||||
settings["template_properties"]["path"] = self.uiQemuListComboBox.currentData()
|
||||
elif "qemu" in appliance_configuration:
|
||||
appliance_configuration["qemu"]["path"] = self.uiQemuListComboBox.currentData()
|
||||
|
||||
new_template = ApplianceToTemplate().new_template(appliance_configuration, self._compute_id, self._symbols, parent=self)
|
||||
new_template = ApplianceToTemplate().new_template(appliance_configuration, self._compute_id, version, self._symbols, parent=self)
|
||||
TemplateManager.instance().createTemplate(Template(new_template), callback=self._templateCreatedCallback)
|
||||
return False
|
||||
|
||||
@@ -632,7 +672,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
if not Controller.instance().isRemote() and self._compute_id == "local" and image["path"].startswith(ImageManager.instance().getDirectory()):
|
||||
log.debug("{} is already on the local server".format(image["path"]))
|
||||
return
|
||||
image = Image(self._appliance.emulator(), image["path"], filename=image["filename"])
|
||||
image = Image(self._appliance.template_type(), image["path"], filename=image["filename"])
|
||||
image_upload_manager = ImageUploadManager(image, Controller.instance(), self._compute_id, self._applianceImageUploadedCallback, LocalConfig.instance().directFileUpload())
|
||||
image_upload_manager.upload()
|
||||
self._image_uploading_count += 1
|
||||
@@ -649,12 +689,16 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
|
||||
def nextId(self):
|
||||
if self.currentPage() == self.uiServerWizardPage:
|
||||
if "docker" in self._appliance:
|
||||
if self._appliance.template_type() == "docker":
|
||||
# skip Qemu binary selection and files pages if this is a Docker appliance
|
||||
return super().nextId() + 2
|
||||
elif "qemu" not in self._appliance:
|
||||
return super().nextId() + 3
|
||||
elif self._appliance.template_type() != "qemu":
|
||||
# skip the Qemu binary selection page if not a Qemu appliance
|
||||
return super().nextId() + 1
|
||||
if self.currentPage() == self.uiQemuWizardPage:
|
||||
if not self._appliance.get("installation_instructions"):
|
||||
# skip the installation instructions page if there are no instructions
|
||||
return super().nextId() + 1
|
||||
return super().nextId()
|
||||
|
||||
def validateCurrentPage(self):
|
||||
@@ -722,7 +766,6 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
|
||||
elif self.currentPage() == self.uiQemuWizardPage:
|
||||
# validate the Qemu
|
||||
|
||||
if self._server_check is False:
|
||||
QtWidgets.QMessageBox.critical(self, "Checking for KVM support", "Please wait for the server to reply...")
|
||||
return False
|
||||
|
||||
@@ -126,20 +126,18 @@ class ExportProjectWizard(QtWidgets.QWizard, Ui_ExportProjectWizard):
|
||||
"""
|
||||
|
||||
if result:
|
||||
include_images = include_snapshots = reset_mac_addresses = keep_compute_ids = "no"
|
||||
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"
|
||||
else:
|
||||
reset_mac_addresses = "no"
|
||||
if self.uiKeepComputeIdsCheckBox.isChecked():
|
||||
keep_compute_ids = "yes"
|
||||
|
||||
compression = self.uiCompressionComboBox.currentData()
|
||||
export_worker = ExportProjectWorker(self._project, self._path, include_images, include_snapshots, reset_mac_addresses, compression)
|
||||
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_()
|
||||
|
||||
@@ -22,6 +22,7 @@ Style editor to edit Shape items.
|
||||
from ..qt import QtCore, QtWidgets, QtGui
|
||||
from ..ui.style_editor_dialog_ui import Ui_StyleEditorDialog
|
||||
from ..items.shape_item import ShapeItem
|
||||
from ..items.rectangle_item import RectangleItem
|
||||
|
||||
|
||||
class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog):
|
||||
@@ -70,8 +71,27 @@ class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog):
|
||||
self._border_color.green(),
|
||||
self._border_color.blue(),
|
||||
self._border_color.alpha()))
|
||||
if isinstance(first_item, RectangleItem):
|
||||
# use the horizontal corner radius first and then the vertical one if it's not set
|
||||
# maybe we allow configuring them separately in the future
|
||||
corner_radius = first_item.horizontalCornerRadius()
|
||||
if not corner_radius:
|
||||
corner_radius = first_item.verticalCornerRadius()
|
||||
self.uiCornerRadiusSpinBox.setValue(corner_radius)
|
||||
else:
|
||||
self.uiCornerRadiusLabel.hide()
|
||||
self.uiCornerRadiusSpinBox.hide()
|
||||
self.uiRotationSpinBox.setValue(int(first_item.rotation()))
|
||||
self.uiBorderWidthSpinBox.setValue(pen.width())
|
||||
if isinstance(first_item, ShapeItem):
|
||||
rect = first_item.rect()
|
||||
self.uiWidthSpinBox.setValue(int(rect.width()))
|
||||
self.uiHeightSpinBox.setValue(int(rect.height()))
|
||||
else:
|
||||
self.uiWidthSpinBox.hide()
|
||||
self.uiWidthLabel.hide()
|
||||
self.uiHeightSpinBox.hide()
|
||||
self.uiHeightLabel.hide()
|
||||
index = self.uiBorderStyleComboBox.findData(pen.style())
|
||||
if index != -1:
|
||||
self.uiBorderStyleComboBox.setCurrentIndex(index)
|
||||
@@ -116,10 +136,18 @@ class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog):
|
||||
|
||||
for item in self._items:
|
||||
item.setPen(pen)
|
||||
# on multiselection it's possible to select many type of items
|
||||
# on multi-selection it's possible to select many type of items
|
||||
# but brush can be applied only on ShapeItem,
|
||||
if brush and isinstance(item, ShapeItem):
|
||||
item.setBrush(brush)
|
||||
if isinstance(item, RectangleItem):
|
||||
corner_radius = self.uiCornerRadiusSpinBox.value()
|
||||
# use the corner radius for both horizontal (rx) and vertical (ry)
|
||||
# maybe we support setting them separately in the future
|
||||
item.setHorizontalCornerRadius(corner_radius)
|
||||
item.setVerticalCornerRadius(corner_radius)
|
||||
if isinstance(item, ShapeItem):
|
||||
item.setWidthAndHeight(self.uiWidthSpinBox.value(), self.uiHeightSpinBox.value())
|
||||
item.setRotation(self.uiRotationSpinBox.value())
|
||||
|
||||
def done(self, result):
|
||||
|
||||
@@ -54,11 +54,15 @@ class StyleEditorDialogLink(QtWidgets.QDialog, Ui_StyleEditorDialog):
|
||||
self.uiColorLabel.hide()
|
||||
self.uiColorPushButton.hide()
|
||||
self._color = None
|
||||
|
||||
|
||||
self.uiCornerRadiusLabel.hide()
|
||||
self.uiCornerRadiusSpinBox.hide()
|
||||
self.uiRotationLabel.hide()
|
||||
self.uiRotationSpinBox.hide()
|
||||
|
||||
link.setHovered(False) # make sure we use the right style
|
||||
pen = link.pen()
|
||||
link.setHovered(True)
|
||||
|
||||
self._border_color = pen.color()
|
||||
self.uiBorderColorPushButton.setStyleSheet("background-color: rgba({}, {}, {}, {});".format(self._border_color.red(),
|
||||
@@ -102,6 +106,7 @@ class StyleEditorDialogLink(QtWidgets.QDialog, Ui_StyleEditorDialog):
|
||||
|
||||
# Store values
|
||||
self._link.setLinkStyle(new_link_style)
|
||||
self._link.setHovered(False) # allow to see the new style
|
||||
|
||||
def done(self, result):
|
||||
"""
|
||||
|
||||
@@ -184,7 +184,7 @@ 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
|
||||
|
||||
@@ -74,6 +74,10 @@ 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.
|
||||
@@ -92,6 +96,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)
|
||||
@@ -438,6 +444,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
item = self.itemAt(event.pos())
|
||||
if item and sip.isdeleted(item):
|
||||
return
|
||||
elif not item:
|
||||
self._main_window.uiStatusBar.clearMessage() # reset the status bar message when clicking on the scene
|
||||
|
||||
if item and (isinstance(item, LinkItem) or isinstance(item.parentItem(), LinkItem)):
|
||||
is_not_link = False
|
||||
@@ -595,7 +603,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
if factor < 0.10 or factor > 10:
|
||||
return
|
||||
self.scale(scale_factor, scale_factor)
|
||||
self._main_window.uiStatusBar.showMessage("Zoom: {}%".format(round(self.transform().m11() * 100)), 2000)
|
||||
self._main_window.uiStatusBar.showMessage("Zoom: {}%".format(round(self.transform().m11() * 100)), 5000)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
"""
|
||||
@@ -640,7 +648,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
if item:
|
||||
# show item coords in the status bar
|
||||
coords = "X: {} Y: {} Z: {}".format(item.x(), item.y(), item.zValue())
|
||||
self._main_window.uiStatusBar.showMessage(coords, 2000)
|
||||
self._main_window.uiStatusBar.showMessage(coords)
|
||||
|
||||
# force the children to redraw because of a problem with QGraphicsEffect
|
||||
for item in self.scene().selectedItems():
|
||||
@@ -664,7 +672,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
self.configureSlot()
|
||||
return
|
||||
else:
|
||||
if sys.platform.startswith("win") and item.node().bringToFront():
|
||||
if item.node().bringToFront():
|
||||
return
|
||||
self.consoleFromItems(self.scene().selectedItems())
|
||||
return
|
||||
@@ -713,6 +721,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
:param event: QDropEvent instance
|
||||
"""
|
||||
|
||||
log.debug("Drop event received with mime data: {}".format(event.mimeData().formats()))
|
||||
# check if what has been dropped is handled by this view
|
||||
if event.mimeData().hasFormat("application/x-gns3-template"):
|
||||
template_id = event.mimeData().data("application/x-gns3-template").data().decode()
|
||||
@@ -857,8 +866,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
show_in_file_manager_action.triggered.connect(self.showInFileManagerSlot)
|
||||
menu.addAction(show_in_file_manager_action)
|
||||
|
||||
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)
|
||||
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 = QtWidgets.QAction("Bring to front", menu)
|
||||
bring_to_front_action.setIcon(get_icon("front.svg"))
|
||||
bring_to_front_action.triggered.connect(self.bringToFrontSlot)
|
||||
@@ -1029,7 +1038,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
if not new_hostname.strip():
|
||||
QtWidgets.QMessageBox.critical(self, "Change hostname", "Hostname cannot be blank")
|
||||
continue
|
||||
if hasattr(item.node(), "validateHostname"):
|
||||
if 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
|
||||
@@ -1246,7 +1255,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
|
||||
@@ -1293,7 +1302,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)
|
||||
@@ -1656,11 +1665,39 @@ 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(), QtGui.QColor(208, 208, 208)),
|
||||
(self.nodeGridSize(), QtGui.QColor(190, 190, 190))]
|
||||
grids = [(self.drawingGridSize(), self._drawing_grid_color),
|
||||
(self.nodeGridSize(), self._node_grid_color)]
|
||||
painter.save()
|
||||
for (grid, colour) in grids:
|
||||
if not grid:
|
||||
|
||||
@@ -44,6 +44,7 @@ class DrawingItem:
|
||||
def __init__(self, project=None, pos=None, drawing_id=None, svg=None, z=0, locked=False, rotation=0, **kws):
|
||||
self._id = drawing_id
|
||||
self._deleting = False
|
||||
self._allow_snap_to_grid = True
|
||||
self._locked = locked
|
||||
if self._id is None:
|
||||
self._id = str(uuid.uuid4())
|
||||
@@ -135,6 +136,9 @@ class DrawingItem:
|
||||
if self.rotation() < 360.0:
|
||||
self.setRotation(self.rotation() + 1)
|
||||
return True
|
||||
elif modifiers & QtCore.Qt.AltModifier:
|
||||
self._allow_snap_to_grid = False
|
||||
return True
|
||||
return False
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
@@ -147,6 +151,15 @@ class DrawingItem:
|
||||
if not self.handleKeyPressEvent(event):
|
||||
QtWidgets.QGraphicsItem.keyPressEvent(self, event)
|
||||
|
||||
def keyReleaseEvent(self, event):
|
||||
"""
|
||||
Handles all key release events
|
||||
|
||||
:param event: QKeyEvent
|
||||
"""
|
||||
|
||||
self._allow_snap_to_grid = True
|
||||
|
||||
def __json__(self):
|
||||
data = {
|
||||
"drawing_id": self._id,
|
||||
@@ -213,7 +226,8 @@ class DrawingItem:
|
||||
|
||||
def itemChange(self, change, value):
|
||||
|
||||
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.isActive() and self._main_window.uiSnapToGridAction.isChecked():
|
||||
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self._main_window.uiSnapToGridAction.isChecked() \
|
||||
and self._allow_snap_to_grid:
|
||||
grid_size = self._graphics_view.drawingGridSize()
|
||||
mid_x = self.boundingRect().width() / 2
|
||||
value.setX((grid_size * round((value.x() + mid_x) / grid_size)) - mid_x)
|
||||
|
||||
@@ -320,13 +320,13 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
|
||||
|
||||
if not sip_is_deleted(self):
|
||||
# create the contextual menu
|
||||
self.setHovered(True)
|
||||
self.setAcceptHoverEvents(False)
|
||||
menu = QtWidgets.QMenu()
|
||||
self.populateLinkContextualMenu(menu)
|
||||
menu.exec_(QtGui.QCursor.pos())
|
||||
self.setAcceptHoverEvents(True)
|
||||
self._hovered = False
|
||||
self.adjust()
|
||||
self.setHovered(False)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
"""
|
||||
|
||||
@@ -51,6 +51,7 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
|
||||
self._links = []
|
||||
self._symbol = None
|
||||
self._locked = False
|
||||
self._allow_snap_to_grid = True
|
||||
|
||||
# says if the attached node has been initialized
|
||||
# by the server.
|
||||
@@ -108,6 +109,9 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
|
||||
if node.initialized():
|
||||
self.createdSlot(node.id())
|
||||
|
||||
if self._main_window.uiSnapToGridAction.isChecked():
|
||||
self.setPos(QtCore.QPointF(self._node.x() + 0.1, self._node.y()))
|
||||
|
||||
def updateNode(self):
|
||||
"""
|
||||
Sync change to the node
|
||||
@@ -466,7 +470,8 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
|
||||
:param value: value of the change
|
||||
"""
|
||||
|
||||
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.isActive() and self._main_window.uiSnapToGridAction.isChecked():
|
||||
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self._main_window.uiSnapToGridAction.isChecked() \
|
||||
and self._allow_snap_to_grid:
|
||||
grid_size = self._main_window.uiGraphicsView.nodeGridSize()
|
||||
mid_x = self.boundingRect().width() / 2
|
||||
value.setX((grid_size * round((value.x() + mid_x) / grid_size)) - mid_x)
|
||||
@@ -528,6 +533,27 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
|
||||
for link in self._links:
|
||||
link.adjust()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
"""
|
||||
Handles all key press events
|
||||
|
||||
:param event: QKeyEvent
|
||||
"""
|
||||
|
||||
if event.modifiers() & QtCore.Qt.AltModifier:
|
||||
self._allow_snap_to_grid = False
|
||||
else:
|
||||
super().keyPressEvent(event)
|
||||
|
||||
def keyReleaseEvent(self, event):
|
||||
"""
|
||||
Handles all key release events
|
||||
|
||||
:param event: QKeyEvent
|
||||
"""
|
||||
|
||||
self._allow_snap_to_grid = True
|
||||
|
||||
def locked(self):
|
||||
|
||||
return self._locked
|
||||
|
||||
@@ -32,8 +32,22 @@ class RectangleItem(QtWidgets.QGraphicsRectItem, ShapeItem):
|
||||
"""
|
||||
|
||||
def __init__(self, width=200, height=100, **kws):
|
||||
self._rx = 0
|
||||
self._ry = 0
|
||||
super().__init__(width=width, height=height, **kws)
|
||||
|
||||
def setHorizontalCornerRadius(self, radius: int):
|
||||
self._rx = radius
|
||||
|
||||
def horizontalCornerRadius(self):
|
||||
return self._rx
|
||||
|
||||
def setVerticalCornerRadius(self, radius: int):
|
||||
self._ry = radius
|
||||
|
||||
def verticalCornerRadius(self):
|
||||
return self._ry
|
||||
|
||||
def paint(self, painter, option, widget=None):
|
||||
"""
|
||||
Paints the contents of an item in local coordinates.
|
||||
@@ -43,7 +57,9 @@ class RectangleItem(QtWidgets.QGraphicsRectItem, ShapeItem):
|
||||
:param widget: QWidget instance
|
||||
"""
|
||||
|
||||
super().paint(painter, option, widget)
|
||||
painter.setPen(self.pen())
|
||||
painter.setBrush(self.brush())
|
||||
painter.drawRoundedRect(self.rect(), self._rx, self._ry)
|
||||
self.drawLayerInfo(painter)
|
||||
|
||||
def toSvg(self):
|
||||
@@ -57,7 +73,27 @@ class RectangleItem(QtWidgets.QGraphicsRectItem, ShapeItem):
|
||||
rect = ET.SubElement(svg, "rect")
|
||||
rect.set("width", str(int(self.rect().width())))
|
||||
rect.set("height", str(int(self.rect().height())))
|
||||
if self._rx:
|
||||
rect.set("rx", str(self._rx))
|
||||
if self._ry:
|
||||
rect.set("ry", str(self._ry))
|
||||
|
||||
rect = self._styleSvg(rect)
|
||||
|
||||
return ET.tostring(svg, encoding="utf-8").decode("utf-8")
|
||||
|
||||
def fromSvg(self, svg):
|
||||
svg_elem = ET.fromstring(svg)
|
||||
if len(svg_elem):
|
||||
# handle horizontal corner radius and vertical corner radius (specific to rectangles)
|
||||
rx = svg_elem[0].get("rx")
|
||||
ry = svg_elem[0].get("ry")
|
||||
if rx:
|
||||
self._rx = int(rx)
|
||||
elif ry:
|
||||
self._rx = int(ry) # defaults to ry if it is specified
|
||||
if ry:
|
||||
self._ry = int(ry)
|
||||
elif rx:
|
||||
self._ry = int(rx) # defaults to rx if it is specified
|
||||
super().fromSvg(svg)
|
||||
|
||||
@@ -171,9 +171,12 @@ class ShapeItem(DrawingItem):
|
||||
if not self.locked():
|
||||
self._graphics_view.setCursor(QtCore.Qt.ArrowCursor)
|
||||
|
||||
def setWidthAndHeight(self, width, height):
|
||||
self.setRect(0, 0, width, height)
|
||||
|
||||
def fromSvg(self, svg):
|
||||
"""
|
||||
Import element informations from an SVG
|
||||
Import element information from SVG
|
||||
"""
|
||||
svg = ET.fromstring(svg)
|
||||
width = float(svg.get("width", self.rect().width()))
|
||||
|
||||
@@ -23,7 +23,7 @@ import re
|
||||
from .qt import sip
|
||||
import uuid
|
||||
|
||||
from .qt import QtCore
|
||||
from .qt import QtCore, QtNetwork
|
||||
from .controller import Controller
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ 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
|
||||
@@ -117,12 +118,15 @@ class Link(QtCore.QObject):
|
||||
else:
|
||||
self._capture_file = QtCore.QFile(self._capture_file_path)
|
||||
self._capture_file.open(QtCore.QFile.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)
|
||||
timeout=None,
|
||||
networkManager=self._network_manager)
|
||||
log.debug("Has successfully started capturing packets on link {} to '{}'".format(self._link_id, self._capture_file_path))
|
||||
else:
|
||||
self._response_stream = None
|
||||
|
||||
@@ -483,7 +483,7 @@ class LocalConfig(QtCore.QObject):
|
||||
|
||||
if os.path.exists(pid_path):
|
||||
try:
|
||||
with open(pid_path) as f:
|
||||
with open(pid_path, encoding="utf-8") as f:
|
||||
pid = int(f.read())
|
||||
if pid != my_pid:
|
||||
try:
|
||||
@@ -498,9 +498,17 @@ class LocalConfig(QtCore.QObject):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
except (OSError, ValueError) as e:
|
||||
except OSError 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:
|
||||
|
||||
@@ -193,7 +193,11 @@ class LocalServer(QtCore.QObject):
|
||||
QtWidgets.QMessageBox.Yes,
|
||||
QtWidgets.QMessageBox.No)
|
||||
if proceed == QtWidgets.QMessageBox.Yes:
|
||||
sudo(["chown", "root:admin", path], ["chmod", "4750", path])
|
||||
from gns3.utils.macos_ubridge_setuid import macos_ubridge_setuid
|
||||
if sys.platform.startswith("darwin") and hasattr(sys, "frozen"):
|
||||
macos_ubridge_setuid()
|
||||
else:
|
||||
sudo(["chown", "root:admin", path], ["chmod", "4750", path])
|
||||
except OSError as e:
|
||||
QtWidgets.QMessageBox.critical(self.parent(), "uBridge", "Can't set root permissions to uBridge {}: {}".format(path, str(e)))
|
||||
return False
|
||||
@@ -478,11 +482,15 @@ class LocalServer(QtCore.QObject):
|
||||
try:
|
||||
if sys.platform.startswith("win"):
|
||||
# use the string on Windows
|
||||
self._local_server_process = subprocess.Popen(command, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, stderr=subprocess.PIPE)
|
||||
self._local_server_process = subprocess.Popen(
|
||||
command,
|
||||
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP,
|
||||
stderr=subprocess.PIPE,
|
||||
env=os.environ)
|
||||
else:
|
||||
# use arguments on other platforms
|
||||
args = shlex.split(command)
|
||||
self._local_server_process = subprocess.Popen(args, stderr=subprocess.PIPE)
|
||||
self._local_server_process = subprocess.Popen(args, stderr=subprocess.PIPE, env=os.environ)
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
log.warning('Could not start local server "{}": {}'.format(command, e))
|
||||
return False
|
||||
|
||||
35
gns3/main.py
35
gns3/main.py
@@ -30,16 +30,6 @@ try:
|
||||
except Exception as e:
|
||||
print("Fail update installation: {}".format(str(e)))
|
||||
|
||||
|
||||
# WARNING
|
||||
# Due to buggy user machines we choose to put this as the first loading modules
|
||||
# otherwise the egg cache is initialized in his standard location and
|
||||
# if is not writetable the application crash. It's the user fault
|
||||
# because one day the user as used sudo to run an egg and break his
|
||||
# filesystem permissions, but it's a common mistake.
|
||||
from gns3.utils.get_resource import get_resource
|
||||
|
||||
|
||||
import datetime
|
||||
import traceback
|
||||
import time
|
||||
@@ -60,12 +50,12 @@ from gns3.local_config import LocalConfig
|
||||
from gns3.application import Application
|
||||
from gns3.utils import parse_version
|
||||
from gns3.dialogs.profile_select import ProfileSelectDialog
|
||||
from gns3.version import __version__
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
from gns3.version import __version__
|
||||
|
||||
|
||||
def locale_check():
|
||||
"""
|
||||
@@ -135,6 +125,13 @@ def main():
|
||||
if options.project:
|
||||
options.project = os.path.abspath(options.project)
|
||||
|
||||
try:
|
||||
import truststore
|
||||
truststore.inject_into_ssl()
|
||||
log.info("Using system certificate store for SSL connections")
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if hasattr(sys, "frozen"):
|
||||
# We add to the path where the OS search executable our binary location starting by GNS3
|
||||
# packaged binary
|
||||
@@ -145,6 +142,7 @@ def main():
|
||||
frozen_dirs = [
|
||||
frozen_dir,
|
||||
os.path.normpath(os.path.join(frozen_dir, 'dynamips')),
|
||||
os.path.normpath(os.path.join(frozen_dir, 'ubridge')),
|
||||
os.path.normpath(os.path.join(frozen_dir, 'vpcs')),
|
||||
os.path.normpath(os.path.join(frozen_dir, 'traceng'))
|
||||
]
|
||||
@@ -154,6 +152,7 @@ def main():
|
||||
if options.project:
|
||||
os.chdir(frozen_dir)
|
||||
|
||||
|
||||
def exceptionHook(exception, value, tb):
|
||||
|
||||
if exception == KeyboardInterrupt:
|
||||
@@ -185,9 +184,9 @@ def main():
|
||||
# catch exceptions to write them in a file
|
||||
sys.excepthook = exceptionHook
|
||||
|
||||
# we only support Python 3 version >= 3.4
|
||||
if sys.version_info < (3, 4):
|
||||
raise SystemExit("Python 3.4 or higher is required")
|
||||
# we only support Python 3 version >= 3.8
|
||||
if sys.version_info < (3, 8):
|
||||
raise SystemExit("Python 3.8 or higher is required")
|
||||
|
||||
if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.5.0"):
|
||||
raise SystemExit("Requirement is PyQt5 version 5.5.0 or higher, got version {}".format(QtCore.QT_VERSION_STR))
|
||||
@@ -222,8 +221,11 @@ def main():
|
||||
# hide the console
|
||||
# win32console.AllocConsole()
|
||||
console_window = win32console.GetConsoleWindow()
|
||||
if console_window:
|
||||
parent_window = win32gui.GetParent(console_window)
|
||||
if not parent_window and console_window:
|
||||
win32gui.ShowWindow(console_window, win32con.SW_HIDE)
|
||||
elif parent_window:
|
||||
win32gui.ShowWindow(parent_window, win32con.SW_HIDE)
|
||||
else:
|
||||
log.warning("Could not get the console window")
|
||||
except win32console.error as e:
|
||||
@@ -263,6 +265,7 @@ def main():
|
||||
log.info("GNS3 GUI version {}".format(__version__))
|
||||
log.info("Copyright (c) 2007-{} GNS3 Technologies Inc.".format(current_year))
|
||||
log.info("Application started with {}".format(" ".join(sys.argv)))
|
||||
log.debug("PATH={}".format(os.environ["PATH"]))
|
||||
|
||||
# update the exception file path to have it in the same directory as the settings file.
|
||||
exception_file_path = os.path.join(LocalConfig.instance().configDirectory(), exception_file_path)
|
||||
|
||||
@@ -49,7 +49,6 @@ from .topology import Topology
|
||||
from .http_client import HTTPClient
|
||||
from .progress import Progress
|
||||
from .update_manager import UpdateManager
|
||||
from .utils.analytics import AnalyticsClient
|
||||
from .dialogs.appliance_wizard import ApplianceWizard
|
||||
from .dialogs.new_template_wizard import NewTemplateWizard
|
||||
from .dialogs.notif_dialog import NotifDialog, NotifDialogHandler
|
||||
@@ -133,11 +132,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self._local_config_timer = QtCore.QTimer(self)
|
||||
self._local_config_timer.timeout.connect(local_config.checkConfigChanged)
|
||||
self._local_config_timer.start(1000) # milliseconds
|
||||
self._analytics_client = AnalyticsClient()
|
||||
self._template_manager = TemplateManager().instance()
|
||||
self._appliance_manager = ApplianceManager().instance()
|
||||
|
||||
# restore the geometry and state of the main window.
|
||||
self._save_gui_state_geometry = True
|
||||
self.restoreGeometry(QtCore.QByteArray().fromBase64(self._settings["geometry"].encode()))
|
||||
self.restoreState(QtCore.QByteArray().fromBase64(self._settings["state"].encode()))
|
||||
|
||||
@@ -234,6 +233,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.uiShowGridAction.triggered.connect(self._showGridActionSlot)
|
||||
self.uiSnapToGridAction.triggered.connect(self._snapToGridActionSlot)
|
||||
self.uiLockAllAction.triggered.connect(self._lockActionSlot)
|
||||
self.uiResetGUIStateAction.triggered.connect(self._resetGUIState)
|
||||
self.uiResetDocksAction.triggered.connect(self._resetDocksSlot)
|
||||
|
||||
# tool menu connections
|
||||
@@ -268,6 +268,7 @@ 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)
|
||||
@@ -368,6 +369,18 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
item.updateNode()
|
||||
item.update()
|
||||
|
||||
def _resetGUIState(self):
|
||||
"""
|
||||
Reset the GUI state.
|
||||
"""
|
||||
|
||||
self._save_gui_state_geometry = False
|
||||
self.close()
|
||||
if hasattr(sys, "frozen"):
|
||||
QtCore.QProcess.startDetached(os.path.abspath(sys.executable), sys.argv)
|
||||
else:
|
||||
QtWidgets.QMessageBox.information(self, "GUI state","The GUI state has been reset, please restart the application")
|
||||
|
||||
def _resetDocksSlot(self):
|
||||
"""
|
||||
Reset the dock widgets.
|
||||
@@ -378,13 +391,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.uiConsoleDockWidget.setFloating(False)
|
||||
self.uiNodesDockWidget.setFloating(False)
|
||||
|
||||
def analyticsClient(self):
|
||||
"""
|
||||
Return the analytics client
|
||||
"""
|
||||
|
||||
return self._analytics_client
|
||||
|
||||
def _newProjectActionSlot(self):
|
||||
"""
|
||||
Slot called to create a new project.
|
||||
@@ -422,7 +428,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)
|
||||
@@ -441,7 +447,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)
|
||||
@@ -914,7 +920,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:
|
||||
@@ -974,6 +980,15 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
# start and connect to the local server if needed
|
||||
LocalServer.instance().localServerAutoStartIfRequired()
|
||||
|
||||
def _shortcutsActionSlot(self):
|
||||
|
||||
shortcuts_text = ""
|
||||
for action in self.findChildren(QtWidgets.QAction):
|
||||
shortcut = action.shortcut().toString()
|
||||
if shortcut:
|
||||
shortcuts_text += f"{action.toolTip()}: {shortcut}\n"
|
||||
QtWidgets.QMessageBox.information(self, "Shortcuts", shortcuts_text)
|
||||
|
||||
def _aboutQtActionSlot(self):
|
||||
"""
|
||||
Slot to display the Qt About dialog.
|
||||
@@ -1122,6 +1137,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
if self.uiAddLinkAction.isChecked() and key == QtCore.Qt.Key_Escape:
|
||||
self.uiAddLinkAction.setChecked(False)
|
||||
self._addLinkActionSlot()
|
||||
elif key == QtCore.Qt.Key_C and event.modifiers() & QtCore.Qt.ControlModifier:
|
||||
status_bar_message = self.uiStatusBar.currentMessage()
|
||||
if status_bar_message:
|
||||
QtWidgets.QApplication.clipboard().setText(status_bar_message)
|
||||
else:
|
||||
super().keyPressEvent(event)
|
||||
|
||||
@@ -1144,8 +1163,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
progress.setCancelButtonText("Force quit")
|
||||
|
||||
log.debug("Close the Main Window")
|
||||
self._analytics_client.sendScreenView("Main Window", session_start=False)
|
||||
|
||||
self._finish_application_closing(close_windows=False)
|
||||
event.accept()
|
||||
self.uiConsoleTextEdit.closeIO()
|
||||
@@ -1160,8 +1177,12 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
|
||||
log.debug("_finish_application_closing")
|
||||
|
||||
self._settings["geometry"] = bytes(self.saveGeometry().toBase64()).decode()
|
||||
self._settings["state"] = bytes(self.saveState().toBase64()).decode()
|
||||
if self._save_gui_state_geometry:
|
||||
self._settings["geometry"] = bytes(self.saveGeometry().toBase64()).decode()
|
||||
self._settings["state"] = bytes(self.saveState().toBase64()).decode()
|
||||
else:
|
||||
self._settings["geometry"] = ""
|
||||
self._settings["state"] = ""
|
||||
self.setSettings(self._settings)
|
||||
|
||||
Controller.instance().stopListenNotifications()
|
||||
@@ -1234,7 +1255,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
Controller.instance().connected_signal.connect(self._controllerConnectedSlot)
|
||||
Controller.instance().project_list_updated_signal.connect(self.updateRecentProjectActions)
|
||||
|
||||
self._analytics_client.sendScreenView("Main Window")
|
||||
self.uiGraphicsView.setEnabled(False)
|
||||
|
||||
# show the setup wizard
|
||||
@@ -1429,7 +1449,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
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)",
|
||||
"All files (*);;GNS3 Portable Project (*.gns3project *.gns3p)",
|
||||
"GNS3 Portable Project (*.gns3project *.gns3p)")
|
||||
if path:
|
||||
Topology.instance().importProject(path)
|
||||
|
||||
@@ -73,7 +73,7 @@ class Nat(Node):
|
||||
:returns: symbol path (or resource).
|
||||
"""
|
||||
|
||||
return ":/symbols/cloud.svg"
|
||||
return ":/symbols/nat.svg"
|
||||
|
||||
@staticmethod
|
||||
def categories():
|
||||
|
||||
@@ -42,6 +42,7 @@ class DockerVM(Node):
|
||||
docker_vm_settings = {"image": "",
|
||||
"usage": "",
|
||||
"adapters": DOCKER_CONTAINER_SETTINGS["adapters"],
|
||||
"mac_address": DOCKER_CONTAINER_SETTINGS["mac_address"],
|
||||
"custom_adapters": DOCKER_CONTAINER_SETTINGS["custom_adapters"],
|
||||
"start_command": DOCKER_CONTAINER_SETTINGS["start_command"],
|
||||
"environment": DOCKER_CONTAINER_SETTINGS["environment"],
|
||||
@@ -88,6 +89,9 @@ class DockerVM(Node):
|
||||
port_name=port.name(),
|
||||
port_description=port.description())
|
||||
|
||||
if port.macAddress():
|
||||
port_info += " MAC address is {mac_address}\n".format(mac_address=port.macAddress())
|
||||
|
||||
usage = "\n" + self._settings.get("usage")
|
||||
return info + port_info + usage
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
Configuration page for Docker images.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from gns3.qt import QtWidgets
|
||||
from gns3.node import Node
|
||||
from gns3.dialogs.custom_adapters_configuration_dialog import CustomAdaptersConfigurationDialog
|
||||
@@ -69,15 +71,25 @@ class DockerVMConfigurationPage(QtWidgets.QWidget, Ui_dockerVMConfigPageWidget):
|
||||
|
||||
if self._node:
|
||||
adapters = self._settings["adapters"]
|
||||
base_mac_address = self._settings["mac_address"]
|
||||
else:
|
||||
adapters = self.uiAdapterSpinBox.value()
|
||||
mac = self.uiMacAddrLineEdit.text()
|
||||
if mac != ":::::":
|
||||
if not re.search(r"""^([0-9a-fA-F]{2}[:]){5}[0-9a-fA-F]{2}$""", mac):
|
||||
QtWidgets.QMessageBox.critical(self, "MAC address", "Invalid MAC address (format required: hh:hh:hh:hh:hh:hh)")
|
||||
return
|
||||
else:
|
||||
base_mac_address = mac
|
||||
else:
|
||||
base_mac_address = ""
|
||||
|
||||
ports = []
|
||||
for adapter_number in range(0, adapters):
|
||||
port_name = "eth{}".format(adapter_number)
|
||||
ports.append(port_name)
|
||||
|
||||
dialog = CustomAdaptersConfigurationDialog(ports, self._custom_adapters, parent=self)
|
||||
dialog = CustomAdaptersConfigurationDialog(ports, self._custom_adapters, "TAP", {"TAP": "Default"}, base_mac_address, parent=self)
|
||||
dialog.show()
|
||||
dialog.exec_()
|
||||
|
||||
@@ -150,6 +162,13 @@ class DockerVMConfigurationPage(QtWidgets.QWidget, Ui_dockerVMConfigPageWidget):
|
||||
self.uiSymbolLineEdit.hide()
|
||||
self.uiSymbolToolButton.hide()
|
||||
|
||||
# load the MAC address setting
|
||||
self.uiMacAddrLineEdit.setInputMask("HH:HH:HH:HH:HH:HH;_")
|
||||
if settings["mac_address"]:
|
||||
self.uiMacAddrLineEdit.setText(settings["mac_address"])
|
||||
else:
|
||||
self.uiMacAddrLineEdit.clear()
|
||||
|
||||
self.uiUsageTextEdit.setPlainText(settings["usage"])
|
||||
|
||||
def _networkConfigEditSlot(self):
|
||||
@@ -199,6 +218,18 @@ class DockerVMConfigurationPage(QtWidgets.QWidget, Ui_dockerVMConfigPageWidget):
|
||||
else:
|
||||
settings["name"] = name
|
||||
|
||||
# check and save the MAC address
|
||||
mac = self.uiMacAddrLineEdit.text()
|
||||
if mac != ":::::":
|
||||
if not re.search(r"""^([0-9a-fA-F]{2}[:]){5}[0-9a-fA-F]{2}$""", mac):
|
||||
QtWidgets.QMessageBox.critical(self, "MAC address", "Invalid MAC address (format required: hh:hh:hh:hh:hh:hh)")
|
||||
if node:
|
||||
raise ConfigurationError()
|
||||
else:
|
||||
settings["mac_address"] = mac
|
||||
else:
|
||||
settings["mac_address"] = None
|
||||
|
||||
if not node:
|
||||
# these are template settings
|
||||
settings["category"] = self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex())
|
||||
|
||||
@@ -35,6 +35,7 @@ DOCKER_CONTAINER_SETTINGS = {
|
||||
"name": "",
|
||||
"image": "",
|
||||
"adapters": 1,
|
||||
"mac_address": "",
|
||||
"custom_adapters": [],
|
||||
"environment": "",
|
||||
"console_type": "telnet",
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>938</width>
|
||||
<height>872</height>
|
||||
<width>504</width>
|
||||
<height>560</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -103,27 +103,37 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="uiMacAddrLabel">
|
||||
<property name="text">
|
||||
<string>Base MAC:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="uiMacAddrLineEdit"/>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="uiCustomAdaptersLabel">
|
||||
<property name="text">
|
||||
<string>Custom adapters:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<item row="7" column="1">
|
||||
<widget class="QPushButton" name="uiCustomAdaptersConfigurationPushButton">
|
||||
<property name="text">
|
||||
<string>&Configure custom adapters</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="uiConsoleTypeLabel">
|
||||
<property name="text">
|
||||
<string>Console type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<item row="8" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetNoConstraint</enum>
|
||||
@@ -166,14 +176,14 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="uiConsoleResolutionLabel">
|
||||
<property name="text">
|
||||
<string>VNC console resolution:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<item row="9" column="1">
|
||||
<widget class="QComboBox" name="uiConsoleResolutionComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
@@ -227,14 +237,14 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>HTTP port in the container:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<item row="10" column="1">
|
||||
<widget class="QSpinBox" name="uiConsoleHttpPortSpinBox">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
@@ -244,17 +254,17 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<item row="11" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>HTTP path:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<item row="11" column="1">
|
||||
<widget class="QLineEdit" name="uiHttpConsolePathLineEdit"/>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<item row="12" column="0">
|
||||
<widget class="QLabel" name="uiEnvironmentLabel">
|
||||
<property name="text">
|
||||
<string>Environment variables:
|
||||
@@ -268,17 +278,17 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="1">
|
||||
<item row="12" column="1">
|
||||
<widget class="QTextEdit" name="uiEnvironmentTextEdit"/>
|
||||
</item>
|
||||
<item row="12" column="0">
|
||||
<item row="13" column="0">
|
||||
<widget class="QLabel" name="uiNetworkConfigLabel">
|
||||
<property name="text">
|
||||
<string>Network configuration</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="1">
|
||||
<item row="13" column="1">
|
||||
<widget class="QPushButton" name="uiNetworkConfigEditButton">
|
||||
<property name="text">
|
||||
<string>Edit</string>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/docker/ui/docker_vm_configuration_page.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.7
|
||||
# Created by: PyQt5 UI code generator 5.15.6
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
@@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
class Ui_dockerVMConfigPageWidget(object):
|
||||
def setupUi(self, dockerVMConfigPageWidget):
|
||||
dockerVMConfigPageWidget.setObjectName("dockerVMConfigPageWidget")
|
||||
dockerVMConfigPageWidget.resize(938, 872)
|
||||
dockerVMConfigPageWidget.resize(504, 560)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(dockerVMConfigPageWidget)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiTabWidget = QtWidgets.QTabWidget(dockerVMConfigPageWidget)
|
||||
@@ -67,15 +67,21 @@ class Ui_dockerVMConfigPageWidget(object):
|
||||
self.uiAdapterSpinBox.setMinimum(1)
|
||||
self.uiAdapterSpinBox.setObjectName("uiAdapterSpinBox")
|
||||
self.gridLayout.addWidget(self.uiAdapterSpinBox, 5, 1, 1, 1)
|
||||
self.uiMacAddrLabel = QtWidgets.QLabel(self.tab)
|
||||
self.uiMacAddrLabel.setObjectName("uiMacAddrLabel")
|
||||
self.gridLayout.addWidget(self.uiMacAddrLabel, 6, 0, 1, 1)
|
||||
self.uiMacAddrLineEdit = QtWidgets.QLineEdit(self.tab)
|
||||
self.uiMacAddrLineEdit.setObjectName("uiMacAddrLineEdit")
|
||||
self.gridLayout.addWidget(self.uiMacAddrLineEdit, 6, 1, 1, 1)
|
||||
self.uiCustomAdaptersLabel = QtWidgets.QLabel(self.tab)
|
||||
self.uiCustomAdaptersLabel.setObjectName("uiCustomAdaptersLabel")
|
||||
self.gridLayout.addWidget(self.uiCustomAdaptersLabel, 6, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiCustomAdaptersLabel, 7, 0, 1, 1)
|
||||
self.uiCustomAdaptersConfigurationPushButton = QtWidgets.QPushButton(self.tab)
|
||||
self.uiCustomAdaptersConfigurationPushButton.setObjectName("uiCustomAdaptersConfigurationPushButton")
|
||||
self.gridLayout.addWidget(self.uiCustomAdaptersConfigurationPushButton, 6, 1, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiCustomAdaptersConfigurationPushButton, 7, 1, 1, 1)
|
||||
self.uiConsoleTypeLabel = QtWidgets.QLabel(self.tab)
|
||||
self.uiConsoleTypeLabel.setObjectName("uiConsoleTypeLabel")
|
||||
self.gridLayout.addWidget(self.uiConsoleTypeLabel, 7, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiConsoleTypeLabel, 8, 0, 1, 1)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint)
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
@@ -90,10 +96,10 @@ class Ui_dockerVMConfigPageWidget(object):
|
||||
self.uiConsoleAutoStartCheckBox = QtWidgets.QCheckBox(self.tab)
|
||||
self.uiConsoleAutoStartCheckBox.setObjectName("uiConsoleAutoStartCheckBox")
|
||||
self.horizontalLayout.addWidget(self.uiConsoleAutoStartCheckBox)
|
||||
self.gridLayout.addLayout(self.horizontalLayout, 7, 1, 1, 1)
|
||||
self.gridLayout.addLayout(self.horizontalLayout, 8, 1, 1, 1)
|
||||
self.uiConsoleResolutionLabel = QtWidgets.QLabel(self.tab)
|
||||
self.uiConsoleResolutionLabel.setObjectName("uiConsoleResolutionLabel")
|
||||
self.gridLayout.addWidget(self.uiConsoleResolutionLabel, 8, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiConsoleResolutionLabel, 9, 0, 1, 1)
|
||||
self.uiConsoleResolutionComboBox = QtWidgets.QComboBox(self.tab)
|
||||
self.uiConsoleResolutionComboBox.setObjectName("uiConsoleResolutionComboBox")
|
||||
self.uiConsoleResolutionComboBox.addItem("")
|
||||
@@ -106,35 +112,35 @@ class Ui_dockerVMConfigPageWidget(object):
|
||||
self.uiConsoleResolutionComboBox.addItem("")
|
||||
self.uiConsoleResolutionComboBox.addItem("")
|
||||
self.uiConsoleResolutionComboBox.addItem("")
|
||||
self.gridLayout.addWidget(self.uiConsoleResolutionComboBox, 8, 1, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiConsoleResolutionComboBox, 9, 1, 1, 1)
|
||||
self.label = QtWidgets.QLabel(self.tab)
|
||||
self.label.setObjectName("label")
|
||||
self.gridLayout.addWidget(self.label, 9, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.label, 10, 0, 1, 1)
|
||||
self.uiConsoleHttpPortSpinBox = QtWidgets.QSpinBox(self.tab)
|
||||
self.uiConsoleHttpPortSpinBox.setMinimum(1)
|
||||
self.uiConsoleHttpPortSpinBox.setMaximum(65535)
|
||||
self.uiConsoleHttpPortSpinBox.setObjectName("uiConsoleHttpPortSpinBox")
|
||||
self.gridLayout.addWidget(self.uiConsoleHttpPortSpinBox, 9, 1, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiConsoleHttpPortSpinBox, 10, 1, 1, 1)
|
||||
self.label_2 = QtWidgets.QLabel(self.tab)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.gridLayout.addWidget(self.label_2, 10, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.label_2, 11, 0, 1, 1)
|
||||
self.uiHttpConsolePathLineEdit = QtWidgets.QLineEdit(self.tab)
|
||||
self.uiHttpConsolePathLineEdit.setObjectName("uiHttpConsolePathLineEdit")
|
||||
self.gridLayout.addWidget(self.uiHttpConsolePathLineEdit, 10, 1, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiHttpConsolePathLineEdit, 11, 1, 1, 1)
|
||||
self.uiEnvironmentLabel = QtWidgets.QLabel(self.tab)
|
||||
self.uiEnvironmentLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
|
||||
self.uiEnvironmentLabel.setWordWrap(False)
|
||||
self.uiEnvironmentLabel.setObjectName("uiEnvironmentLabel")
|
||||
self.gridLayout.addWidget(self.uiEnvironmentLabel, 11, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiEnvironmentLabel, 12, 0, 1, 1)
|
||||
self.uiEnvironmentTextEdit = QtWidgets.QTextEdit(self.tab)
|
||||
self.uiEnvironmentTextEdit.setObjectName("uiEnvironmentTextEdit")
|
||||
self.gridLayout.addWidget(self.uiEnvironmentTextEdit, 11, 1, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiEnvironmentTextEdit, 12, 1, 1, 1)
|
||||
self.uiNetworkConfigLabel = QtWidgets.QLabel(self.tab)
|
||||
self.uiNetworkConfigLabel.setObjectName("uiNetworkConfigLabel")
|
||||
self.gridLayout.addWidget(self.uiNetworkConfigLabel, 12, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiNetworkConfigLabel, 13, 0, 1, 1)
|
||||
self.uiNetworkConfigEditButton = QtWidgets.QPushButton(self.tab)
|
||||
self.uiNetworkConfigEditButton.setObjectName("uiNetworkConfigEditButton")
|
||||
self.gridLayout.addWidget(self.uiNetworkConfigEditButton, 12, 1, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiNetworkConfigEditButton, 13, 1, 1, 1)
|
||||
self.uiTabWidget.addTab(self.tab, "")
|
||||
self.tab_2 = QtWidgets.QWidget()
|
||||
self.tab_2.setObjectName("tab_2")
|
||||
@@ -186,6 +192,7 @@ class Ui_dockerVMConfigPageWidget(object):
|
||||
self.uiSymbolToolButton.setText(_translate("dockerVMConfigPageWidget", "&Browse..."))
|
||||
self.uiCMDLabel.setText(_translate("dockerVMConfigPageWidget", "Start command:"))
|
||||
self.uiAdapterLabel.setText(_translate("dockerVMConfigPageWidget", "Adapters:"))
|
||||
self.uiMacAddrLabel.setText(_translate("dockerVMConfigPageWidget", "Base MAC:"))
|
||||
self.uiCustomAdaptersLabel.setText(_translate("dockerVMConfigPageWidget", "Custom adapters:"))
|
||||
self.uiCustomAdaptersConfigurationPushButton.setText(_translate("dockerVMConfigPageWidget", "&Configure custom adapters"))
|
||||
self.uiConsoleTypeLabel.setText(_translate("dockerVMConfigPageWidget", "Console type:"))
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
<string>Docker Virtual Machine</string>
|
||||
</property>
|
||||
<property name="subTitle">
|
||||
<string>Please choose a Docker virtual machine from the list or provide an image name on Docker hub.</string>
|
||||
<string>Please choose a Docker virtual machine from the list or provide an image name on a Docker repository.</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
|
||||
@@ -205,7 +205,7 @@ class Ui_DockerVMWizard(object):
|
||||
self.uiRemoteServersGroupBox.setTitle(_translate("DockerVMWizard", "Remote server"))
|
||||
self.uiRemoteServersLabel.setText(_translate("DockerVMWizard", "Run on:"))
|
||||
self.uiImageWizardPage.setTitle(_translate("DockerVMWizard", "Docker Virtual Machine"))
|
||||
self.uiImageWizardPage.setSubTitle(_translate("DockerVMWizard", "Please choose a Docker virtual machine from the list or provide an image name on Docker hub."))
|
||||
self.uiImageWizardPage.setSubTitle(_translate("DockerVMWizard", "Please choose a Docker virtual machine from the list or provide an image name on a Docker repository."))
|
||||
self.uiExistingImageRadioButton.setText(_translate("DockerVMWizard", "Existing image"))
|
||||
self.uiNewImageRadioButton.setText(_translate("DockerVMWizard", "New image"))
|
||||
self.uiImageListLabel.setText(_translate("DockerVMWizard", "Image list:"))
|
||||
|
||||
@@ -51,7 +51,7 @@ class DynamipsPreferencesPage(QtWidgets.QWidget, Ui_DynamipsPreferencesPageWidge
|
||||
|
||||
file_filter = ""
|
||||
if sys.platform.startswith("win"):
|
||||
file_filter = "Executable (*.exe);;All files (*.*)"
|
||||
file_filter = "Executable (*.exe);;All files (*)"
|
||||
|
||||
dynamips_path = shutil.which("dynamips")
|
||||
if sys.platform.startswith("darwin") and dynamips_path is None:
|
||||
|
||||
@@ -24,6 +24,7 @@ import re
|
||||
|
||||
from gns3.qt import QtCore, QtGui, QtWidgets
|
||||
from gns3.local_server import LocalServer
|
||||
from gns3.local_config import LocalConfig
|
||||
from gns3.dialogs.node_properties_dialog import ConfigurationError
|
||||
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
|
||||
from gns3.controller import Controller
|
||||
@@ -488,7 +489,7 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
|
||||
name = self.uiNameLineEdit.text()
|
||||
if not name:
|
||||
QtWidgets.QMessageBox.critical(self, "Name", "IOS router name cannot be empty!")
|
||||
elif node and not node.validateHostname(name):
|
||||
elif node and not node.validateHostname(name) and not LocalConfig.instance().experimental():
|
||||
QtWidgets.QMessageBox.critical(self, "Name", "Invalid name detected for IOS router: {}".format(name))
|
||||
else:
|
||||
settings["name"] = name
|
||||
|
||||
@@ -229,7 +229,7 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(parent,
|
||||
"Select an IOS image",
|
||||
cls._default_images_dir,
|
||||
"All files (*.*);;IOS image (*.bin *.image)",
|
||||
"All files (*);;IOS image (*.bin *.image)",
|
||||
"IOS image (*.bin *.image)")
|
||||
|
||||
if not path:
|
||||
|
||||
@@ -23,6 +23,7 @@ import os
|
||||
|
||||
from gns3.qt import QtWidgets
|
||||
from gns3.local_server import LocalServer
|
||||
from gns3.local_config import LocalConfig
|
||||
from gns3.dialogs.node_properties_dialog import ConfigurationError
|
||||
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
|
||||
from gns3.node import Node
|
||||
@@ -57,6 +58,7 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
|
||||
self.uiPrivateConfigToolButton.hide()
|
||||
|
||||
# location of the base config templates
|
||||
# FIXME: this does not work
|
||||
self._base_iou_l2_config_template = get_resource(os.path.join("configs", "iou_l2_base_startup-config.txt"))
|
||||
self._base_iou_l3_config_template = get_resource(os.path.join("configs", "iou_l3_base_startup-config.txt"))
|
||||
self._default_configs_dir = LocalServer.instance().localServerSettings()["configs_path"]
|
||||
@@ -244,7 +246,7 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
|
||||
name = self.uiNameLineEdit.text()
|
||||
if not name:
|
||||
QtWidgets.QMessageBox.critical(self, "Name", "IOU device name cannot be empty!")
|
||||
elif node and not node.validateHostname(name):
|
||||
elif node and not node.validateHostname(name) and not LocalConfig.instance().experimental():
|
||||
QtWidgets.QMessageBox.critical(self, "Name", "Invalid name detected for IOU device: {}".format(name))
|
||||
else:
|
||||
settings["name"] = name
|
||||
|
||||
@@ -291,8 +291,8 @@ class IOUDevicePreferencesPage(QtWidgets.QWidget, Ui_IOUDevicePreferencesPageWid
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(parent,
|
||||
"Select an IOU image",
|
||||
cls._default_images_dir,
|
||||
"All file (*);;IOU image (*.bin *.image)",
|
||||
"IOU image (*.bin *.image)")
|
||||
"All file (*);;IOU image (x86_64* i86bi* *.bin *.image *.iol)",
|
||||
"IOU image (x86_64* i86bi* *.bin *.image *.iol)")
|
||||
|
||||
if not path:
|
||||
return
|
||||
|
||||
@@ -121,7 +121,7 @@ class Qemu(Module):
|
||||
:param options: Options for the image creation
|
||||
"""
|
||||
|
||||
Controller.instance().postCompute("/qemu/img", compute_id, callback, body=options)
|
||||
Controller.instance().postCompute("/qemu/img", compute_id, callback, timeout=None, body=options)
|
||||
|
||||
def updateDiskImage(self, compute_id, callback, options):
|
||||
"""
|
||||
|
||||
@@ -101,7 +101,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
self.uiOnCloseComboBox.addItem(name, option_name)
|
||||
|
||||
# Supported NIC models: e1000, e1000-82544gc, e1000-82545em, e1000e, i82550, i82551, i82557a, i82557b, i82557c, i82558a
|
||||
# i82558b, i82559a, i82559b, i82559c, i82559er, i82562, i82801, ne2k_pci, pcnet, rocker, rtl8139, virtio-net-pci, vmxnet3
|
||||
# i82558b, i82559a, i82559b, i82559c, i82559er, i82562, i82801, igb, ne2k_pci, pcnet, rocker, rtl8139, virtio-net-pci, vmxnet3
|
||||
# This list can be retrieved using "qemu-system-x86_64 -nic model=?" or "qemu-system-x86_64 -device help"
|
||||
self._legacy_devices = ("e1000", "i82551", "i82557b", "i82559er", "ne2k_pci", "pcnet", "rtl8139", "virtio")
|
||||
self._qemu_network_devices = OrderedDict([("e1000", "Intel Gigabit Ethernet"),
|
||||
@@ -121,6 +121,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
("i82559er", "Intel i82559ER Ethernet"),
|
||||
("i82562", "Intel i82562 Ethernet"),
|
||||
("i82801", "Intel i82801 Ethernet"),
|
||||
("igb", "Intel 82576 Gigabit Ethernet"),
|
||||
("ne2k_pci", "NE2000 Ethernet"),
|
||||
("pcnet", "AMD PCNet Ethernet"),
|
||||
("rocker", "Rocker L2 switch device"),
|
||||
@@ -579,6 +580,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
self.uiQemuOptionsLineEdit.setText(settings["options"])
|
||||
self.uiUsageTextEdit.setPlainText(settings["usage"])
|
||||
self.uiTPMCheckBox.setChecked(settings["tpm"])
|
||||
self.uiUEFICheckBox.setChecked(settings["uefi"])
|
||||
|
||||
def saveSettings(self, settings, node=None, group=False):
|
||||
"""
|
||||
@@ -694,4 +696,5 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
settings["options"] = self.uiQemuOptionsLineEdit.text()
|
||||
settings["usage"] = self.uiUsageTextEdit.toPlainText()
|
||||
settings["tpm"] = self.uiTPMCheckBox.isChecked()
|
||||
settings["uefi"] = self.uiUEFICheckBox.isChecked()
|
||||
return settings
|
||||
|
||||
@@ -75,6 +75,7 @@ class QemuVM(Node):
|
||||
"legacy_networking": QEMU_VM_SETTINGS["legacy_networking"],
|
||||
"replicate_network_connection_state": QEMU_VM_SETTINGS["replicate_network_connection_state"],
|
||||
"tpm": QEMU_VM_SETTINGS["tpm"],
|
||||
"uefi": QEMU_VM_SETTINGS["uefi"],
|
||||
"create_config_disk": QEMU_VM_SETTINGS["create_config_disk"],
|
||||
"platform": QEMU_VM_SETTINGS["platform"],
|
||||
"on_close": QEMU_VM_SETTINGS["on_close"],
|
||||
|
||||
@@ -58,6 +58,7 @@ QEMU_VM_SETTINGS = {
|
||||
"legacy_networking": False,
|
||||
"replicate_network_connection_state": True,
|
||||
"tpm": False,
|
||||
"uefi": False,
|
||||
"create_config_disk": False,
|
||||
"on_close": "power_off",
|
||||
"platform": "",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>478</width>
|
||||
<height>550</height>
|
||||
<height>579</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -94,7 +94,10 @@
|
||||
<number>32</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
<number>2147483647</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>1024</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>256</number>
|
||||
@@ -867,13 +870,6 @@
|
||||
<string>Additional settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiQemuOptionsLabel">
|
||||
<property name="text">
|
||||
<string>Options:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="uiQemuOptionsLineEdit">
|
||||
<property name="toolTip">
|
||||
@@ -890,7 +886,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiBaseVMCheckBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
@@ -907,11 +903,26 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiQemuOptionsLabel">
|
||||
<property name="text">
|
||||
<string>Options:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiUEFICheckBox">
|
||||
<property name="text">
|
||||
<string>Enable UEFI boot mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<zorder>uiQemuOptionsLineEdit</zorder>
|
||||
<zorder>uiQemuOptionsLabel</zorder>
|
||||
<zorder>uiBaseVMCheckBox</zorder>
|
||||
<zorder>uiTPMCheckBox</zorder>
|
||||
<zorder>uiUEFICheckBox</zorder>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
@@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
class Ui_QemuVMConfigPageWidget(object):
|
||||
def setupUi(self, QemuVMConfigPageWidget):
|
||||
QemuVMConfigPageWidget.setObjectName("QemuVMConfigPageWidget")
|
||||
QemuVMConfigPageWidget.resize(478, 550)
|
||||
QemuVMConfigPageWidget.resize(478, 579)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(QemuVMConfigPageWidget)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiQemutabWidget = QtWidgets.QTabWidget(QemuVMConfigPageWidget)
|
||||
@@ -59,7 +59,8 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
self.gridLayout_4.addWidget(self.uiRamLabel, 4, 0, 1, 1)
|
||||
self.uiRamSpinBox = QtWidgets.QSpinBox(self.uiGeneralSettingsTab)
|
||||
self.uiRamSpinBox.setMinimum(32)
|
||||
self.uiRamSpinBox.setMaximum(65535)
|
||||
self.uiRamSpinBox.setMaximum(2147483647)
|
||||
self.uiRamSpinBox.setSingleStep(1024)
|
||||
self.uiRamSpinBox.setProperty("value", 256)
|
||||
self.uiRamSpinBox.setObjectName("uiRamSpinBox")
|
||||
self.gridLayout_4.addWidget(self.uiRamSpinBox, 4, 1, 1, 1)
|
||||
@@ -428,23 +429,27 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
self.groupBox.setObjectName("groupBox")
|
||||
self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox)
|
||||
self.gridLayout_3.setObjectName("gridLayout_3")
|
||||
self.uiQemuOptionsLabel = QtWidgets.QLabel(self.groupBox)
|
||||
self.uiQemuOptionsLabel.setObjectName("uiQemuOptionsLabel")
|
||||
self.gridLayout_3.addWidget(self.uiQemuOptionsLabel, 0, 0, 1, 1)
|
||||
self.uiQemuOptionsLineEdit = QtWidgets.QLineEdit(self.groupBox)
|
||||
self.uiQemuOptionsLineEdit.setObjectName("uiQemuOptionsLineEdit")
|
||||
self.gridLayout_3.addWidget(self.uiQemuOptionsLineEdit, 0, 1, 1, 1)
|
||||
self.uiBaseVMCheckBox = QtWidgets.QCheckBox(self.groupBox)
|
||||
self.uiBaseVMCheckBox.setEnabled(True)
|
||||
self.uiBaseVMCheckBox.setObjectName("uiBaseVMCheckBox")
|
||||
self.gridLayout_3.addWidget(self.uiBaseVMCheckBox, 2, 0, 1, 2)
|
||||
self.gridLayout_3.addWidget(self.uiBaseVMCheckBox, 3, 0, 1, 2)
|
||||
self.uiTPMCheckBox = QtWidgets.QCheckBox(self.groupBox)
|
||||
self.uiTPMCheckBox.setObjectName("uiTPMCheckBox")
|
||||
self.gridLayout_3.addWidget(self.uiTPMCheckBox, 1, 0, 1, 2)
|
||||
self.uiQemuOptionsLabel = QtWidgets.QLabel(self.groupBox)
|
||||
self.uiQemuOptionsLabel.setObjectName("uiQemuOptionsLabel")
|
||||
self.gridLayout_3.addWidget(self.uiQemuOptionsLabel, 0, 0, 1, 1)
|
||||
self.uiUEFICheckBox = QtWidgets.QCheckBox(self.groupBox)
|
||||
self.uiUEFICheckBox.setObjectName("uiUEFICheckBox")
|
||||
self.gridLayout_3.addWidget(self.uiUEFICheckBox, 2, 0, 1, 2)
|
||||
self.uiQemuOptionsLineEdit.raise_()
|
||||
self.uiQemuOptionsLabel.raise_()
|
||||
self.uiBaseVMCheckBox.raise_()
|
||||
self.uiTPMCheckBox.raise_()
|
||||
self.uiUEFICheckBox.raise_()
|
||||
self.verticalLayout_2.addWidget(self.groupBox)
|
||||
spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.verticalLayout_2.addItem(spacerItem4)
|
||||
@@ -549,7 +554,6 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
self.uiProcessPriorityComboBox.setItemText(4, _translate("QemuVMConfigPageWidget", "Low"))
|
||||
self.uiProcessPriorityComboBox.setItemText(5, _translate("QemuVMConfigPageWidget", "Very low"))
|
||||
self.groupBox.setTitle(_translate("QemuVMConfigPageWidget", "Additional settings"))
|
||||
self.uiQemuOptionsLabel.setText(_translate("QemuVMConfigPageWidget", "Options:"))
|
||||
self.uiQemuOptionsLineEdit.setToolTip(_translate("QemuVMConfigPageWidget", "<html><head/><body><p>Variable replacements:</p>\n"
|
||||
"<ul>\n"
|
||||
"<li>%vm-name% =VM name</li>\n"
|
||||
@@ -562,5 +566,7 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
"</body></html>"))
|
||||
self.uiBaseVMCheckBox.setText(_translate("QemuVMConfigPageWidget", "Use as a linked base VM"))
|
||||
self.uiTPMCheckBox.setText(_translate("QemuVMConfigPageWidget", "Enable Trusted Platform Module (TPM)"))
|
||||
self.uiQemuOptionsLabel.setText(_translate("QemuVMConfigPageWidget", "Options:"))
|
||||
self.uiUEFICheckBox.setText(_translate("QemuVMConfigPageWidget", "Enable UEFI boot mode"))
|
||||
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiAdvancedSettingsTab), _translate("QemuVMConfigPageWidget", "Advanced"))
|
||||
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiUsageTab), _translate("QemuVMConfigPageWidget", "Usage"))
|
||||
|
||||
@@ -51,7 +51,7 @@ class TraceNGPreferencesPage(QtWidgets.QWidget, Ui_TraceNGPreferencesPageWidget)
|
||||
|
||||
filter = ""
|
||||
if sys.platform.startswith("win"):
|
||||
filter = "Executable (*.exe);;All files (*.*)"
|
||||
filter = "Executable (*.exe);;All files (*)"
|
||||
traceng_path = shutil.which("traceng")
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select TraceNG", traceng_path, filter)
|
||||
if not path:
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"""
|
||||
VirtualBox VM implementation.
|
||||
"""
|
||||
import sys
|
||||
|
||||
from gns3.node import Node
|
||||
from gns3.utils.bring_to_front import bring_window_to_front_from_process_name
|
||||
@@ -100,7 +101,7 @@ class VirtualBoxVM(Node):
|
||||
Bring the VM window to front.
|
||||
"""
|
||||
|
||||
if self.status() == Node.started:
|
||||
if self.status() == Node.started and sys.platform.startswith("win"):
|
||||
# try 2 different window title formats
|
||||
bring_window_to_front_from_process_name("VirtualBox.exe", title="{} [".format(self._settings["vmname"]))
|
||||
bring_window_to_front_from_process_name("VirtualBox.exe", title="{} (".format(self._settings["vmname"]))
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
VMware VM implementation.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from gns3.qt import QtCore
|
||||
from gns3.node import Node
|
||||
from gns3.utils.bring_to_front import bring_window_to_front_from_process_name
|
||||
@@ -124,7 +126,7 @@ class VMwareVM(Node):
|
||||
Bring the VM window to front.
|
||||
"""
|
||||
|
||||
if self.status() == Node.started:
|
||||
if self.status() == Node.started and sys.platform.startswith("win"):
|
||||
try:
|
||||
vmx_pairs = self.module().parseVMwareFile(self.settings()["vmx_path"])
|
||||
except OSError as e:
|
||||
|
||||
@@ -51,7 +51,7 @@ class VPCSPreferencesPage(QtWidgets.QWidget, Ui_VPCSPreferencesPageWidget):
|
||||
|
||||
filter = ""
|
||||
if sys.platform.startswith("win"):
|
||||
filter = "Executable (*.exe);;All files (*.*)"
|
||||
filter = "Executable (*.exe);;All files (*)"
|
||||
vpcs_path = shutil.which("vpcs")
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select VPCS", vpcs_path, filter)
|
||||
if not path:
|
||||
|
||||
41
gns3/node.py
41
gns3/node.py
@@ -16,8 +16,11 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
from gns3.controller import Controller
|
||||
from gns3.ports.ethernet_port import EthernetPort
|
||||
@@ -689,7 +692,8 @@ class Node(BaseNode):
|
||||
return
|
||||
super().setStatus(status)
|
||||
if status == self.started and "console_auto_start" in self.settings() and self.settings()["console_auto_start"]:
|
||||
self.openConsole()
|
||||
# give the node some time to start before opening the console
|
||||
QtCore.QTimer.singleShot(1000, self.openConsole)
|
||||
|
||||
def openConsole(self, command=None, aux=False):
|
||||
"""
|
||||
@@ -721,6 +725,9 @@ class Node(BaseNode):
|
||||
if "console_type" in self.settings():
|
||||
console_type = self.consoleType()
|
||||
|
||||
if aux is False and self.bringToFront() is True:
|
||||
return
|
||||
|
||||
if console_type == "telnet":
|
||||
from .telnet_console import nodeTelnetConsole
|
||||
nodeTelnetConsole(self, console_port, command)
|
||||
@@ -739,18 +746,30 @@ class Node(BaseNode):
|
||||
"""
|
||||
|
||||
if self.status() == Node.started:
|
||||
console_command = self.consoleCommand()
|
||||
if console_command:
|
||||
process_name = console_command.split()[0]
|
||||
if bring_window_to_front_from_process_name(process_name, self.name()):
|
||||
if sys.platform.startswith("linux"):
|
||||
wmctrl_path = shutil.which("wmctrl")
|
||||
if wmctrl_path:
|
||||
try:
|
||||
# use wmctrl to raise the window based on the node name (this doesn't work well with window having multiple tabs)
|
||||
subprocess.run([wmctrl_path, "-Fa", self.name()], check=True, env=os.environ)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
log.debug("Could not find window title '{}' to bring it to front".format(self.name()))
|
||||
except OSError as e:
|
||||
log.warning("Count not focus on terminal window: '{}'".format(e))
|
||||
elif sys.platform.startswith("win"):
|
||||
console_command = self.consoleCommand()
|
||||
if console_command:
|
||||
process_name = console_command.split()[0]
|
||||
if bring_window_to_front_from_process_name(process_name, self.name()):
|
||||
return True
|
||||
else:
|
||||
log.debug("Could not find process name '' and window title '{}' to bring it to front".format(process_name, self.name()))
|
||||
|
||||
if bring_window_to_front_from_title(self.name()):
|
||||
return True
|
||||
else:
|
||||
log.debug("Could not find process name '' and window title '{}' to bring it to front".format(process_name, self.name()))
|
||||
|
||||
if bring_window_to_front_from_title(self.name()):
|
||||
return True
|
||||
else:
|
||||
log.debug("Could not find window title '{}' to bring it to front".format(self.name()))
|
||||
log.debug("Could not find window title '{}' to bring it to front".format(self.name()))
|
||||
return False
|
||||
|
||||
def importFile(self, path, source_path):
|
||||
|
||||
@@ -112,15 +112,20 @@ class PacketCapture:
|
||||
"""
|
||||
Starts the packet capture reader.
|
||||
"""
|
||||
|
||||
self._startPacketCommand(link, self.settings()["packet_capture_reader_command"])
|
||||
|
||||
def stopPacketCaptureReader(self, link):
|
||||
"""
|
||||
Stop the packet capture reader
|
||||
"""
|
||||
if link in self._tail_process and self._tail_process[link].poll() is None:
|
||||
|
||||
if link in self._tail_process:
|
||||
log.debug("Stopping packet capture reader for link {}".format(link.link_id()))
|
||||
self._tail_process[link].kill()
|
||||
try:
|
||||
self._tail_process[link].kill()
|
||||
except (PermissionError, OSError):
|
||||
pass
|
||||
del self._tail_process[link]
|
||||
|
||||
def startPacketCaptureAnalyzer(self, link):
|
||||
@@ -141,13 +146,19 @@ class PacketCapture:
|
||||
|
||||
# PCAP capture file path
|
||||
command = command.replace("%c", '"' + capture_file_path + '"')
|
||||
command = command.replace("{pcap_file}", '"' + capture_file_path + '"')
|
||||
|
||||
# Add description
|
||||
# Add project name
|
||||
command = command.replace("%P", link.project().name())
|
||||
command = command.replace("{project}", link.project().name().replace('"', '\\"'))
|
||||
|
||||
# Add link description
|
||||
description = "{}[{}]->{}[{}]".format(link.sourceNode().name(),
|
||||
link.sourcePort().name(),
|
||||
link.destinationNode().name(),
|
||||
link.destinationPort().name())
|
||||
command = command.replace("%d", description)
|
||||
command = command.replace("{link_description}", description)
|
||||
|
||||
if not sys.platform.startswith("win"):
|
||||
command = shlex.split(command)
|
||||
@@ -155,7 +166,7 @@ class PacketCapture:
|
||||
QtWidgets.QMessageBox.critical(self.parent(), "Packet Capture Analyzer", "No packet capture analyzer program configured")
|
||||
return
|
||||
try:
|
||||
subprocess.Popen(command)
|
||||
subprocess.Popen(command, env=os.environ)
|
||||
except OSError as e:
|
||||
QtWidgets.QMessageBox.critical(self.parent(), "Packet Capture Analyzer", "Can't start packet capture analyzer program {}".format(str(e)))
|
||||
return
|
||||
@@ -183,14 +194,14 @@ class PacketCapture:
|
||||
QtWidgets.QMessageBox.critical(self.parent(), "Packet capture", "Can't create packet capture file {}: {}".format(capture_file_path, str(e)))
|
||||
return
|
||||
|
||||
if link in self._tail_process and self._tail_process[link].poll() is None:
|
||||
if link in self._tail_process:
|
||||
try:
|
||||
self._tail_process[link].kill()
|
||||
except (PermissionError, OSError):
|
||||
# Sometimes we have condition on windows where the process is in the process to quit
|
||||
pass
|
||||
del self._tail_process[link]
|
||||
if link in self._capture_reader_process and self._capture_reader_process[link].poll() is None:
|
||||
if link in self._capture_reader_process:
|
||||
try:
|
||||
self._capture_reader_process[link].kill()
|
||||
except (PermissionError, OSError):
|
||||
@@ -199,13 +210,19 @@ class PacketCapture:
|
||||
|
||||
# PCAP capture file path
|
||||
command = command.replace("%c", '"' + capture_file_path + '"')
|
||||
command = command.replace("{pcap_file}", '"' + capture_file_path + '"')
|
||||
|
||||
# Add description
|
||||
# Add project name
|
||||
command = command.replace("%P", link.project().name())
|
||||
command = command.replace("{project}", link.project().name().replace('"', '\\"'))
|
||||
|
||||
# Add link description
|
||||
description = "{} {} to {} {}".format(link.sourceNode().name(),
|
||||
link.sourcePort().name(),
|
||||
link.destinationNode().name(),
|
||||
link.destinationPort().name())
|
||||
command = command.replace("%d", description)
|
||||
command = command.replace("{link_description}", description)
|
||||
|
||||
if "|" in command:
|
||||
# live traffic capture (using tail)
|
||||
|
||||
@@ -195,7 +195,7 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
|
||||
configuration_file_path = LocalConfig.instance().configFilePath()
|
||||
directory = os.path.dirname(configuration_file_path)
|
||||
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Import configuration file", directory, "Configuration file (*.ini *.conf);;All files (*.*)")
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Import configuration file", directory, "Configuration file (*.ini *.conf);;All files (*)")
|
||||
if not path:
|
||||
return
|
||||
|
||||
@@ -240,7 +240,7 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
|
||||
configuration_file_path = LocalConfig.instance().configFilePath()
|
||||
directory = os.path.dirname(configuration_file_path)
|
||||
|
||||
path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export configuration file", directory, "Configuration file (*.ini *.conf);;All files (*.*)")
|
||||
path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export configuration file", directory, "Configuration file (*.ini *.conf);;All files (*)")
|
||||
if not path:
|
||||
return
|
||||
|
||||
@@ -301,7 +301,6 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
|
||||
self.uiImagesPathLineEdit.setText(local_server["images_path"])
|
||||
self.uiConfigsPathLineEdit.setText(local_server["configs_path"])
|
||||
self.uiAppliancesPathLineEdit.setText(local_server["appliances_path"])
|
||||
self.uiStatsCheckBox.setChecked(settings["send_stats"])
|
||||
self.uiOverlayNotificationsCheckBox.setChecked(settings["overlay_notifications"])
|
||||
self.uiCrashReportCheckBox.setChecked(local_server["report_errors"])
|
||||
self.uiCheckForUpdateCheckBox.setChecked(settings["check_for_update"])
|
||||
@@ -411,7 +410,6 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
|
||||
"vnc_console_command": self.uiVNCConsoleCommandLineEdit.text(),
|
||||
"spice_console_command": self.uiSPICEConsoleCommandLineEdit.text(),
|
||||
"delay_console_all": self.uiDelayConsoleAllSpinBox.value(),
|
||||
"send_stats": self.uiStatsCheckBox.isChecked(),
|
||||
"multi_profiles": self.uiMultiProfilesCheckBox.isChecked(),
|
||||
"direct_file_upload": self.uiDirectFileUpload.isChecked()
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ class ServerPreferencesPage(QtWidgets.QWidget, Ui_ServerPreferencesPageWidget):
|
||||
|
||||
filter = ""
|
||||
if sys.platform.startswith("win"):
|
||||
filter = "Executable (*.exe);;All files (*.*)"
|
||||
filter = "Executable (*.exe);;All files (*)"
|
||||
server_path = shutil.which("gns3server")
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select the local server", server_path, filter)
|
||||
if not path:
|
||||
@@ -121,7 +121,7 @@ class ServerPreferencesPage(QtWidgets.QWidget, Ui_ServerPreferencesPageWidget):
|
||||
|
||||
filter = ""
|
||||
if sys.platform.startswith("win"):
|
||||
filter = "Executable (*.exe);;All files (*.*)"
|
||||
filter = "Executable (*.exe);;All files (*)"
|
||||
|
||||
ubridge_path = shutil.which("ubridge")
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select ubridge executable", ubridge_path, filter)
|
||||
|
||||
@@ -475,9 +475,9 @@ class Project(QtCore.QObject):
|
||||
"variables": self._variables,
|
||||
"supplier": self._supplier
|
||||
}
|
||||
self.put("", self._projectUpdatedCallback, body=body)
|
||||
self.put("", self.projectUpdatedCallback, body=body)
|
||||
|
||||
def _projectUpdatedCallback(self, result, error=False, **kwargs):
|
||||
def projectUpdatedCallback(self, result, error=False, **kwargs):
|
||||
if error:
|
||||
self.project_creation_error_signal.emit(result["message"])
|
||||
return
|
||||
@@ -627,21 +627,24 @@ class Project(QtCore.QObject):
|
||||
return
|
||||
|
||||
# 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"):
|
||||
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():
|
||||
path = "/projects/{project_id}/notifications".format(project_id=self._id)
|
||||
self._notification_stream = Controller.instance().createHTTPQuery("GET", path, self._endListenNotificationCallback,
|
||||
downloadProgressCallback=self._event_received,
|
||||
networkManager=self._notification_network_manager,
|
||||
timeout=None,
|
||||
showProgress=False,
|
||||
ignoreErrors=True)
|
||||
downloadProgressCallback=self._event_received,
|
||||
networkManager=self._notification_network_manager,
|
||||
timeout=None,
|
||||
showProgress=False,
|
||||
ignoreErrors=True)
|
||||
url = Controller.instance().getHttpClient().url() + path
|
||||
log.info("Listening for project notifications on '{}'".format(url))
|
||||
|
||||
else:
|
||||
path = "/projects/{project_id}/notifications/ws".format(project_id=self._id)
|
||||
self._notification_stream = Controller.instance().httpClient().connectWebSocket(self._websocket, path)
|
||||
self._notification_stream.textMessageReceived.connect(self._websocket_event_received)
|
||||
self._notification_stream.error.connect(self._websocket_error)
|
||||
self._notification_stream.sslErrors.connect(self._sslErrorsSlot)
|
||||
path = "/projects/{project_id}/notifications/ws".format(project_id=self._id)
|
||||
self._notification_stream = Controller.instance().httpClient().connectWebSocket(self._websocket, path)
|
||||
self._notification_stream.textMessageReceived.connect(self._websocket_event_received)
|
||||
self._notification_stream.error.connect(self._websocket_error)
|
||||
self._notification_stream.sslErrors.connect(self._sslErrorsSlot)
|
||||
log.info("Listening for project notifications on '{}'".format(self._notification_stream.requestUrl().toString()))
|
||||
|
||||
def _endListenNotificationCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
@@ -654,7 +657,7 @@ class Project(QtCore.QObject):
|
||||
@qslot
|
||||
def _websocket_error(self, error):
|
||||
if self._notification_stream:
|
||||
log.error(self._notification_stream.errorString())
|
||||
log.error("Websocket project notification stream error: {}".format(self._notification_stream.errorString()))
|
||||
self._notification_stream = None
|
||||
self._startListenNotifications()
|
||||
|
||||
@@ -712,17 +715,20 @@ class Project(QtCore.QObject):
|
||||
drawing = Topology.instance().getDrawingFromUuid(result["event"]["drawing_id"])
|
||||
if drawing is not None:
|
||||
drawing.delete(skip_controller=True)
|
||||
# project.closed and project.updated notifications have been moved to the controller
|
||||
# because they are not project specific, keeping it there for backward compatibility
|
||||
# when connected to an older controller version
|
||||
elif result["action"] == "project.closed":
|
||||
Topology.instance().setProject(None)
|
||||
elif result["action"] == "project.updated":
|
||||
self._projectUpdatedCallback(result["event"])
|
||||
self.projectUpdatedCallback(result["event"])
|
||||
elif result["action"] == "snapshot.restored":
|
||||
Topology.instance().restoreSnapshot(result["event"]["project_id"])
|
||||
elif result["action"] == "log.error":
|
||||
log.error(result["event"]["message"])
|
||||
elif result["action"] == "log.warning":
|
||||
log.warning(result["event"]["message"])
|
||||
elif result["action"] == "log.info":
|
||||
log.info(result["event"]["message"], extra={"show": True})
|
||||
elif result["action"] == "log.error" and result["event"].get("message"):
|
||||
log.error(result["event"].get("message"))
|
||||
elif result["action"] == "log.warning" and result["event"].get("message"):
|
||||
log.warning(result["event"].get("message"))
|
||||
elif result["action"] == "log.info" and result["event"].get("message"):
|
||||
log.info(result["event"].get("message"), extra={"show": True})
|
||||
elif result["action"] == "ping":
|
||||
pass
|
||||
|
||||
@@ -195,54 +195,6 @@ if hasattr(sys, '_called_from_test'):
|
||||
QtCore.pyqtSignal = FakeQtSignal
|
||||
|
||||
|
||||
class StatsQtWidgetsQWizard(QtWidgets.QWizard):
|
||||
"""
|
||||
Send stats from all the QWizard
|
||||
"""
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
|
||||
from ..utils.analytics import AnalyticsClient
|
||||
name = self.__class__.__name__
|
||||
name = re.sub(r"([A-Z])", r" \1", name).strip()
|
||||
AnalyticsClient.instance().sendScreenView(name)
|
||||
|
||||
QtWidgets.QWizard = StatsQtWidgetsQWizard
|
||||
|
||||
|
||||
class StatsQtWidgetsQMainWindow(QtWidgets.QMainWindow):
|
||||
"""
|
||||
Send stats from all the QMainWindow
|
||||
"""
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
|
||||
from ..utils.analytics import AnalyticsClient
|
||||
name = self.__class__.__name__
|
||||
name = re.sub(r"([A-Z])", r" \1", name).strip()
|
||||
AnalyticsClient.instance().sendScreenView(name)
|
||||
|
||||
QtWidgets.QMainWindow = StatsQtWidgetsQMainWindow
|
||||
|
||||
|
||||
class StatsQtWidgetsQDialog(QtWidgets.QDialog):
|
||||
"""
|
||||
Send stats from all the QWizard
|
||||
"""
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
|
||||
from ..utils.analytics import AnalyticsClient
|
||||
name = self.__class__.__name__
|
||||
name = re.sub(r"([A-Z])", r" \1", name).strip()
|
||||
AnalyticsClient.instance().sendScreenView(name)
|
||||
|
||||
QtWidgets.QDialog = StatsQtWidgetsQDialog
|
||||
|
||||
|
||||
def qpartial(func, *args, **kwargs):
|
||||
"""
|
||||
A functools partial that you can use on qobject. If the targeted qobject is
|
||||
|
||||
@@ -21,11 +21,13 @@ import copy
|
||||
import os
|
||||
import collections.abc
|
||||
import jsonschema
|
||||
|
||||
|
||||
from gns3.utils.get_resource import get_resource
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ApplianceError(Exception):
|
||||
pass
|
||||
|
||||
@@ -38,6 +40,7 @@ class Appliance(collections.abc.Mapping):
|
||||
:params path: Path of the appliance file on disk or file content
|
||||
"""
|
||||
self._registry = registry
|
||||
self._registry_version = None
|
||||
|
||||
if os.path.isabs(path):
|
||||
try:
|
||||
@@ -58,16 +61,25 @@ class Appliance(collections.abc.Mapping):
|
||||
:param appliance: Sanity check on the appliance configuration
|
||||
"""
|
||||
if "registry_version" not in self._appliance:
|
||||
raise ApplianceError("Invalid appliance configuration please report the issue on https://github.com/GNS3/gns3-registry")
|
||||
if self._appliance["registry_version"] > 7:
|
||||
raise ApplianceError("Please update GNS3 in order to install this appliance")
|
||||
raise ApplianceError("Invalid appliance configuration please report the issue on https://github.com/GNS3/gns3-registry/issues")
|
||||
|
||||
with open(get_resource(os.path.join("schemas", "appliance.json"))) as f:
|
||||
self._registry_version = self._appliance["registry_version"]
|
||||
if self._registry_version > 8:
|
||||
# we only support registry version 8 and below
|
||||
raise ApplianceError("Registry version {} is not supported in this version of GNS3".format(self._registry_version))
|
||||
|
||||
if self._registry_version == 8:
|
||||
# registry version 8 has a different schema with support for multiple settings sets
|
||||
appliance_file = "appliance_v8.json"
|
||||
else:
|
||||
appliance_file = "appliance.json"
|
||||
|
||||
with open(get_resource("schemas/{}".format(appliance_file))) as f:
|
||||
schema = json.load(f)
|
||||
v = jsonschema.Draft4Validator(schema)
|
||||
try:
|
||||
v.validate(self._appliance)
|
||||
except jsonschema.ValidationError as e:
|
||||
except jsonschema.ValidationError:
|
||||
error = jsonschema.exceptions.best_match(v.iter_errors(self._appliance)).message
|
||||
raise ApplianceError("Invalid appliance file: {}".format(error))
|
||||
|
||||
@@ -82,10 +94,11 @@ class Appliance(collections.abc.Mapping):
|
||||
|
||||
def _resolve_version(self):
|
||||
"""
|
||||
Replace image field in versions by their the complete information from images
|
||||
Replace image field in versions by the complete information from images
|
||||
"""
|
||||
|
||||
if "versions" not in self._appliance:
|
||||
log.debug("No versions found in appliance")
|
||||
return
|
||||
|
||||
for version in self._appliance["versions"]:
|
||||
@@ -96,7 +109,7 @@ class Appliance(collections.abc.Mapping):
|
||||
for file in self._appliance["images"]:
|
||||
file = copy.copy(file)
|
||||
|
||||
if "idlepc" in version:
|
||||
if self._registry_version < 8 and "idlepc" in version:
|
||||
file["idlepc"] = version["idlepc"]
|
||||
|
||||
if "/" in filename:
|
||||
@@ -127,7 +140,7 @@ class Appliance(collections.abc.Mapping):
|
||||
def search_images_for_version(self, version_name):
|
||||
"""
|
||||
Search on disk the images required by this version.
|
||||
And keep only the require images in the images fields. Add to the images
|
||||
And keep only the required images in the images fields. Add to the images
|
||||
their disk type and path.
|
||||
|
||||
:param version_name: Version name
|
||||
@@ -142,10 +155,18 @@ class Appliance(collections.abc.Mapping):
|
||||
for image_type, image in version["images"].items():
|
||||
image["type"] = image_type
|
||||
|
||||
img = self._registry.search_image_file(self.emulator(), image["filename"], image.get("md5sum"), image.get("filesize"))
|
||||
checksum = image.get("md5sum")
|
||||
if checksum is None and self._registry_version >= 8:
|
||||
# registry version >= 8 has the checksum and checksum_type fields
|
||||
checksum_type = image.get("checksum_type", "md5") # md5 is the default and only supported type
|
||||
if checksum_type != "md5":
|
||||
raise ApplianceError("Checksum type {} is not supported".format(checksum_type))
|
||||
checksum = image.pop("checksum")
|
||||
|
||||
img = self._registry.search_image_file(self.template_type(), image["filename"], checksum, image.get("filesize"))
|
||||
if img is None:
|
||||
if "md5sum" in image:
|
||||
raise ApplianceError("File {} with checksum {} not found for {}".format(image["filename"], image["md5sum"], appliance["name"]))
|
||||
if checksum:
|
||||
raise ApplianceError("File {} with checksum {} not found for {}".format(image["filename"], checksum, appliance["name"]))
|
||||
else:
|
||||
raise ApplianceError("File {} not found for {}".format(image["filename"], appliance["name"]))
|
||||
|
||||
@@ -186,9 +207,51 @@ class Appliance(collections.abc.Mapping):
|
||||
except ApplianceError:
|
||||
return False
|
||||
|
||||
def emulator(self):
|
||||
if "qemu" in self._appliance:
|
||||
return "qemu"
|
||||
if "iou" in self._appliance:
|
||||
return "iou"
|
||||
return "dynamips"
|
||||
def template_type(self):
|
||||
|
||||
if self._registry_version >= 8:
|
||||
template_type = None
|
||||
for settings in self._appliance["settings"]:
|
||||
if settings["template_type"] and not template_type:
|
||||
template_type = settings["template_type"]
|
||||
elif settings["template_type"] and template_type != settings["template_type"]:
|
||||
# we are currently not supporting multiple different template types in the same appliance
|
||||
raise ApplianceError("Multiple different template types found in appliance")
|
||||
if not template_type:
|
||||
raise ApplianceError("No template type found in appliance {}".format(self._appliance["name"]))
|
||||
return template_type
|
||||
else:
|
||||
if "qemu" in self._appliance:
|
||||
return "qemu"
|
||||
if "iou" in self._appliance:
|
||||
return "iou"
|
||||
if "dynamips" in self._appliance:
|
||||
return "dynamips"
|
||||
if "docker" in self._appliance:
|
||||
return "docker"
|
||||
return None
|
||||
|
||||
def template_properties(self):
|
||||
"""
|
||||
Get template properties
|
||||
"""
|
||||
|
||||
if self._registry_version >= 8:
|
||||
# find the default settings if any
|
||||
for settings in self._appliance["settings"]:
|
||||
if settings.get("default", False):
|
||||
return settings["template_properties"]
|
||||
# otherwise take the first settings we find
|
||||
for settings in self._appliance["settings"]:
|
||||
if settings["template_type"]:
|
||||
return settings["template_properties"]
|
||||
else:
|
||||
if "qemu" in self._appliance:
|
||||
return self._appliance["qemu"]
|
||||
if "iou" in self._appliance:
|
||||
return self._appliance["iou"]
|
||||
if "dynamips" in self._appliance:
|
||||
return self._appliance["dynamips"]
|
||||
if "docker" in self._appliance:
|
||||
return self._appliance["docker"]
|
||||
return None
|
||||
|
||||
@@ -24,6 +24,7 @@ from ..qt import QtCore, QtWidgets, QtNetwork
|
||||
from ..controller import Controller
|
||||
from .config import Config, ConfigException
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -33,7 +34,7 @@ class ApplianceToTemplate:
|
||||
Appliance installation.
|
||||
"""
|
||||
|
||||
def new_template(self, appliance_config, server, controller_symbols=None, parent=None):
|
||||
def new_template(self, appliance_config, server, appliance_version=None, controller_symbols=None, parent=None):
|
||||
"""
|
||||
Creates a new template from an appliance.
|
||||
|
||||
@@ -42,6 +43,7 @@ class ApplianceToTemplate:
|
||||
"""
|
||||
|
||||
self._parent = parent
|
||||
self._registry_version = appliance_config["registry_version"]
|
||||
new_template = {
|
||||
"compute_id": server,
|
||||
"name": appliance_config["name"]
|
||||
@@ -73,45 +75,101 @@ class ApplianceToTemplate:
|
||||
elif appliance_config["category"] == "firewall":
|
||||
new_template["symbol"] = ":/symbols/firewall.svg"
|
||||
|
||||
if "qemu" in appliance_config:
|
||||
new_template["template_type"] = "qemu"
|
||||
self._add_qemu_config(new_template, appliance_config)
|
||||
elif "iou" in appliance_config:
|
||||
new_template["template_type"] = "iou"
|
||||
self._add_iou_config(new_template, appliance_config)
|
||||
elif "dynamips" in appliance_config:
|
||||
new_template["template_type"] = "dynamips"
|
||||
self._add_dynamips_config(new_template, appliance_config)
|
||||
elif "docker" in appliance_config:
|
||||
new_template["template_type"] = "docker"
|
||||
self._add_docker_config(new_template, appliance_config)
|
||||
if self._registry_version >= 8:
|
||||
if appliance_version:
|
||||
for version in appliance_config["versions"]:
|
||||
if appliance_version and version["name"] == appliance_version:
|
||||
# inject "usage", "category" and "symbol" specified at the version
|
||||
# level into the template properties
|
||||
usage = version.get("usage")
|
||||
if usage:
|
||||
new_template["usage"] = usage
|
||||
new_template["symbol"] = version.get("symbol", new_template["symbol"])
|
||||
new_template["category"] = version.get("category", new_template["category"])
|
||||
settings = self._get_settings(appliance_config, version.get("settings"))
|
||||
template_type = settings["template_type"]
|
||||
if template_type == "qemu":
|
||||
self._add_qemu_config(new_template, settings["template_properties"], appliance_config)
|
||||
elif template_type == "iou":
|
||||
self._add_iou_config(new_template, settings["template_properties"], appliance_config)
|
||||
elif template_type == "dynamips":
|
||||
self._add_dynamips_config(new_template, settings["template_properties"], appliance_config)
|
||||
else:
|
||||
# docker appliances have no version
|
||||
settings = self._get_settings(appliance_config)
|
||||
if settings["template_type"] == "docker":
|
||||
self._add_docker_config(new_template, settings["template_properties"], appliance_config)
|
||||
else:
|
||||
raise ConfigException("{} no configuration found for known emulators".format(new_template["name"]))
|
||||
if "qemu" in appliance_config:
|
||||
self._add_qemu_config(new_template, appliance_config["qemu"], appliance_config)
|
||||
elif "iou" in appliance_config:
|
||||
self._add_iou_config(new_template, appliance_config["iou"], appliance_config)
|
||||
elif "dynamips" in appliance_config:
|
||||
self._add_dynamips_config(new_template, appliance_config["dynamips"], appliance_config)
|
||||
elif "docker" in appliance_config:
|
||||
self._add_docker_config(new_template, appliance_config["docker"], appliance_config)
|
||||
else:
|
||||
raise ConfigException("{} no configuration found for known emulators".format(new_template["name"]))
|
||||
|
||||
return new_template
|
||||
|
||||
def _add_qemu_config(self, new_config, appliance_config):
|
||||
def _get_settings(self, appliance_config, settings_name=None):
|
||||
|
||||
new_config.update(appliance_config["qemu"])
|
||||
default_settings = None
|
||||
# first look for default settings, if any ('default' = true, first set that has it)
|
||||
for settings in appliance_config["settings"]:
|
||||
if settings.get("default", False):
|
||||
default_settings = settings
|
||||
break
|
||||
|
||||
# then look for specific settings set if a name is provided
|
||||
if settings_name:
|
||||
for settings in appliance_config["settings"]:
|
||||
if settings.get("name") == settings_name:
|
||||
if settings.get("inherit_default_properties", True) and \
|
||||
default_settings and default_settings["template_type"] == settings["template_type"]:
|
||||
default_settings["template_properties"].update(settings["template_properties"])
|
||||
return default_settings
|
||||
return settings
|
||||
raise ConfigException("Settings '{}' cannot be found in the appliance file", settings_name)
|
||||
elif default_settings:
|
||||
return default_settings
|
||||
|
||||
if not appliance_config.get("settings"):
|
||||
raise ConfigException("No settings found in the appliance file")
|
||||
|
||||
# if no default settings are specified, use the first available settings set
|
||||
return appliance_config["settings"][0]
|
||||
|
||||
def _add_qemu_config(self, new_config, template_properties, appliance_config):
|
||||
|
||||
new_config["template_type"] = "qemu"
|
||||
new_config.update(template_properties)
|
||||
|
||||
# the following properties are not valid for a template
|
||||
new_config.pop("kvm", None)
|
||||
new_config.pop("path", None)
|
||||
new_config.pop("arch", None)
|
||||
new_config.pop("kvm", None) # To check KVM setting against the server capabilities
|
||||
new_config.pop("path", None) # Qemu binary selected in previous step
|
||||
new_config.pop("arch", None) # Used for selecting the Qemu binary
|
||||
|
||||
options = appliance_config["qemu"].get("options", "")
|
||||
if appliance_config["qemu"].get("kvm", "allow") == "disable" and "-machine accel=tcg" not in options:
|
||||
options = template_properties.get("options", "")
|
||||
if template_properties.get("kvm", "allow") == "disable" and "-machine accel=tcg" not in options:
|
||||
options += " -machine accel=tcg"
|
||||
new_config["options"] = options.strip()
|
||||
options = options.strip()
|
||||
if options:
|
||||
new_config["options"] = options
|
||||
|
||||
for image in appliance_config["images"]:
|
||||
if image.get("path"):
|
||||
new_config[image["type"]] = self._relative_image_path("QEMU", image["path"])
|
||||
|
||||
if "path" in appliance_config["qemu"]:
|
||||
new_config["qemu_path"] = appliance_config["qemu"]["path"]
|
||||
if "path" in template_properties:
|
||||
new_config["qemu_path"] = template_properties["path"]
|
||||
else:
|
||||
new_config["qemu_path"] = "qemu-system-{}".format(appliance_config["qemu"]["arch"])
|
||||
if self._registry_version >= 8:
|
||||
# the "arch" field was replaced by the "platform" field in registry version 8
|
||||
new_config["qemu_path"] = "qemu-system-{}".format(template_properties["platform"])
|
||||
else:
|
||||
new_config["qemu_path"] = "qemu-system-{}".format(template_properties["arch"])
|
||||
|
||||
if "first_port_name" in appliance_config:
|
||||
new_config["first_port_name"] = appliance_config["first_port_name"]
|
||||
@@ -128,24 +186,28 @@ class ApplianceToTemplate:
|
||||
if "linked_clone" in appliance_config:
|
||||
new_config["linked_clone"] = appliance_config["linked_clone"]
|
||||
|
||||
def _add_docker_config(self, new_config, appliance_config):
|
||||
def _add_docker_config(self, new_config, template_properties, appliance_config):
|
||||
|
||||
new_config.update(appliance_config["docker"])
|
||||
new_config["template_type"] = "docker"
|
||||
new_config.update(template_properties)
|
||||
|
||||
if "custom_adapters" in appliance_config:
|
||||
new_config["custom_adapters"] = appliance_config["custom_adapters"]
|
||||
|
||||
def _add_dynamips_config(self, new_config, appliance_config):
|
||||
def _add_dynamips_config(self, new_config, template_properties, appliance_config):
|
||||
|
||||
new_config.update(appliance_config["dynamips"])
|
||||
new_config["template_type"] = "dynamips"
|
||||
new_config.update(template_properties)
|
||||
|
||||
for image in appliance_config["images"]:
|
||||
new_config[image["type"]] = self._relative_image_path("IOS", image["path"])
|
||||
new_config["idlepc"] = image.get("idlepc", "")
|
||||
if self._registry_version < 8:
|
||||
new_config["idlepc"] = image.get("idlepc", "")
|
||||
|
||||
def _add_iou_config(self, new_config, appliance_config):
|
||||
def _add_iou_config(self, new_config, template_properties, appliance_config):
|
||||
|
||||
new_config.update(appliance_config["iou"])
|
||||
new_config["template_type"] = "iou"
|
||||
new_config.update(template_properties)
|
||||
for image in appliance_config["images"]:
|
||||
if "path" not in image:
|
||||
raise ConfigException("Disk image is missing")
|
||||
@@ -197,7 +259,8 @@ class ApplianceToTemplate:
|
||||
|
||||
url = "https://raw.githubusercontent.com/GNS3/gns3-registry/master/symbols/{}".format(symbol_id)
|
||||
try:
|
||||
self._downloadApplianceSymbol(url, path)
|
||||
if not self._downloadApplianceSymbol(url, path):
|
||||
return None
|
||||
controller.clearStaticCache()
|
||||
if controller.isRemote():
|
||||
controller.uploadSymbol(symbol_id, path)
|
||||
@@ -230,5 +293,7 @@ class ApplianceToTemplate:
|
||||
log.debug("Error while saving appliance symbol to '{}': {}".format(path, e))
|
||||
raise
|
||||
log.debug("Appliance symbol downloaded and saved to '{}'".format(path))
|
||||
return True
|
||||
else:
|
||||
log.warning("Error when downloading appliance symbol from '{}': {}".format(url, reply.errorString()))
|
||||
log.error("Error when downloading appliance symbol from '{}': {}".format(url, reply.errorString()))
|
||||
return False
|
||||
|
||||
@@ -71,6 +71,11 @@
|
||||
"format": "uri",
|
||||
"title": "Website of the vendor"
|
||||
},
|
||||
"vendor_logo_url": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"title": "Link to the vendor logo (used by the GNS3 marketplace)"
|
||||
},
|
||||
"documentation_url": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
@@ -86,7 +91,7 @@
|
||||
"title": "An optional product url on vendor website"
|
||||
},
|
||||
"registry_version": {
|
||||
"enum": [1, 2, 3, 4, 5, 6],
|
||||
"enum": [1, 2, 3, 4, 5, 6, 7],
|
||||
"title": "Version of the registry compatible with this appliance"
|
||||
},
|
||||
"status": {
|
||||
@@ -141,7 +146,7 @@
|
||||
},
|
||||
"image": {
|
||||
"type": "string",
|
||||
"title": "Docker image in the Docker Hub"
|
||||
"title": "Docker image on the Docker repository"
|
||||
},
|
||||
"start_command": {
|
||||
"type": "string",
|
||||
@@ -282,6 +287,7 @@
|
||||
"i82559er",
|
||||
"i82562",
|
||||
"i82801",
|
||||
"igb",
|
||||
"ne2k_pci",
|
||||
"pcnet",
|
||||
"rocker",
|
||||
@@ -354,6 +360,10 @@
|
||||
"type": "boolean",
|
||||
"title": "Enable the Trusted Platform Module (TPM)"
|
||||
},
|
||||
"uefi": {
|
||||
"type": "boolean",
|
||||
"title": "Enable the UEFI boot mode"
|
||||
},
|
||||
"on_close": {
|
||||
"title": "Action to execute on the VM is closed",
|
||||
"enum": ["power_off", "shutdown_signal", "save_vm_state"]
|
||||
@@ -396,7 +406,6 @@
|
||||
"md5sum": {
|
||||
"type": "string",
|
||||
"title": "md5sum of the file",
|
||||
"type": "string",
|
||||
"pattern": "^[a-f0-9]{32}$"
|
||||
},
|
||||
"filesize": {
|
||||
|
||||
908
gns3/schemas/appliance_v8.json
Normal file
908
gns3/schemas/appliance_v8.json
Normal file
@@ -0,0 +1,908 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"type": "object",
|
||||
"title": "JSON schema validating a GNS3 appliance",
|
||||
"definitions": {
|
||||
"categories": {
|
||||
"enum": [
|
||||
"router",
|
||||
"multilayer_switch",
|
||||
"switch",
|
||||
"firewall",
|
||||
"guest"
|
||||
]
|
||||
},
|
||||
"docker_properties": {
|
||||
"type": "object",
|
||||
"title": "Docker template properties",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "Name of the template"
|
||||
},
|
||||
"category": {
|
||||
"$ref": "#/definitions/categories",
|
||||
"title": "Category of the template"
|
||||
},
|
||||
"default_name_format": {
|
||||
"type": "string",
|
||||
"title": "Default name format"
|
||||
},
|
||||
"usage": {
|
||||
"type": "string",
|
||||
"title": "How to use the template"
|
||||
},
|
||||
"symbol": {
|
||||
"type": "string",
|
||||
"title": "Symbol of the template"
|
||||
},
|
||||
"image": {
|
||||
"type": "string",
|
||||
"title": "Docker image"
|
||||
},
|
||||
"adapters": {
|
||||
"type": "integer",
|
||||
"title": "Number of ethernet adapters"
|
||||
},
|
||||
"start_command": {
|
||||
"type": "string",
|
||||
"title": "Command executed when the container start. Empty will use the default"
|
||||
},
|
||||
"environment": {
|
||||
"type": "string",
|
||||
"title": "One KEY=VAR environment by line"
|
||||
},
|
||||
"console_type": {
|
||||
"enum": [
|
||||
"telnet",
|
||||
"vnc",
|
||||
"http",
|
||||
"https",
|
||||
"none"
|
||||
],
|
||||
"title": "Type of console"
|
||||
},
|
||||
"console_http_port": {
|
||||
"title": "Internal port in the container of the HTTP server",
|
||||
"type": "integer"
|
||||
},
|
||||
"console_http_path": {
|
||||
"title": "Path of the web interface",
|
||||
"type": "string"
|
||||
},
|
||||
"console_resolution": {
|
||||
"title": "Console resolution for VNC, for example 1024x768",
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+x[0-9]+$"
|
||||
},
|
||||
"extra_hosts": {
|
||||
"title": "Docker extra hosts (added to /etc/hosts)",
|
||||
"type": "string"
|
||||
},
|
||||
"extra_volumes": {
|
||||
"title": "Additional directories to make persistent",
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"image"
|
||||
]
|
||||
},
|
||||
"iou_properties": {
|
||||
"type": "object",
|
||||
"title": "IOU template properties",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "Name of the template"
|
||||
},
|
||||
"category": {
|
||||
"$ref": "#/definitions/categories",
|
||||
"title": "Category of the template"
|
||||
},
|
||||
"default_name_format": {
|
||||
"type": "string",
|
||||
"title": "Default name format"
|
||||
},
|
||||
"usage": {
|
||||
"type": "string",
|
||||
"title": "How to use the template"
|
||||
},
|
||||
"symbol": {
|
||||
"type": "string",
|
||||
"title": "Symbol of the template"
|
||||
},
|
||||
"ethernet_adapters": {
|
||||
"type": "integer",
|
||||
"title": "Number of ethernet adapters"
|
||||
},
|
||||
"serial_adapters": {
|
||||
"type": "integer",
|
||||
"title": "Number of serial adapters"
|
||||
},
|
||||
"ram": {
|
||||
"type": "integer",
|
||||
"title": "Host RAM"
|
||||
},
|
||||
"nvram": {
|
||||
"type": "integer",
|
||||
"title": "Host NVRAM"
|
||||
},
|
||||
"startup_config": {
|
||||
"type": "string",
|
||||
"title": "Config loaded at startup"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dynamips_slot": {
|
||||
"enum": [
|
||||
"C2600-MB-2FE",
|
||||
"C2600-MB-1E",
|
||||
"PA-A1",
|
||||
"PA-8E",
|
||||
"C1700-MB-1FE",
|
||||
"PA-8T",
|
||||
"PA-2FE-TX",
|
||||
"PA-FE-TX",
|
||||
"PA-GE",
|
||||
"C2600-MB-2E",
|
||||
"C7200-IO-FE",
|
||||
"NM-4T",
|
||||
"C2600-MB-1FE",
|
||||
"C7200-IO-2FE",
|
||||
"PA-POS-OC3",
|
||||
"PA-4T+",
|
||||
"C1700-MB-WIC1",
|
||||
"NM-16ESW",
|
||||
"C7200-IO-GE-E",
|
||||
"NM-4E",
|
||||
"GT96100-FE",
|
||||
"NM-1FE-TX",
|
||||
"Leopard-2FE",
|
||||
"NM-1E",
|
||||
"PA-4E",
|
||||
""
|
||||
]
|
||||
},
|
||||
"dynamips_wic": {
|
||||
"enum": [
|
||||
"WIC-1ENET",
|
||||
"WIC-1T",
|
||||
"WIC-2T",
|
||||
""
|
||||
]
|
||||
},
|
||||
"dynamips_properties": {
|
||||
"type": "object",
|
||||
"title": "Dynamips template properties",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "Name of the template"
|
||||
},
|
||||
"category": {
|
||||
"$ref": "#/definitions/categories",
|
||||
"title": "Category of the template"
|
||||
},
|
||||
"default_name_format": {
|
||||
"type": "string",
|
||||
"title": "Default name format"
|
||||
},
|
||||
"usage": {
|
||||
"type": "string",
|
||||
"title": "How to use the template"
|
||||
},
|
||||
"symbol": {
|
||||
"type": "string",
|
||||
"title": "Symbol of the template"
|
||||
},
|
||||
"chassis": {
|
||||
"title": "Chassis type",
|
||||
"enum": [
|
||||
"1720",
|
||||
"1721",
|
||||
"1750",
|
||||
"1751",
|
||||
"1760",
|
||||
"2610",
|
||||
"2620",
|
||||
"2610XM",
|
||||
"2620XM",
|
||||
"2650XM",
|
||||
"2621",
|
||||
"2611XM",
|
||||
"2621XM",
|
||||
"2651XM",
|
||||
"3620",
|
||||
"3640",
|
||||
"3660",
|
||||
""
|
||||
]
|
||||
},
|
||||
"platform": {
|
||||
"title": "Platform type",
|
||||
"enum": [
|
||||
"c1700",
|
||||
"c2600",
|
||||
"c2691",
|
||||
"c3725",
|
||||
"c3745",
|
||||
"c3600",
|
||||
"c7200"
|
||||
]
|
||||
},
|
||||
"ram": {
|
||||
"title": "Amount of ram",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"nvram": {
|
||||
"title": "Amount of nvram",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"idlepc": {
|
||||
"type": "string",
|
||||
"pattern": "^0x[0-9a-f]{8}"
|
||||
},
|
||||
"startup_config": {
|
||||
"type": "string",
|
||||
"title": "Config loaded at startup"
|
||||
},
|
||||
"wic0": {
|
||||
"$ref": "#/definitions/dynamips_wic"
|
||||
},
|
||||
"wic1": {
|
||||
"$ref": "#/definitions/dynamips_wic"
|
||||
},
|
||||
"wic2": {
|
||||
"$ref": "#/definitions/dynamips_wic"
|
||||
},
|
||||
"slot0": {
|
||||
"$ref": "#/definitions/dynamips_slot"
|
||||
},
|
||||
"slot1": {
|
||||
"$ref": "#/definitions/dynamips_slot"
|
||||
},
|
||||
"slot2": {
|
||||
"$ref": "#/definitions/dynamips_slot"
|
||||
},
|
||||
"slot3": {
|
||||
"$ref": "#/definitions/dynamips_slot"
|
||||
},
|
||||
"slot4": {
|
||||
"$ref": "#/definitions/dynamips_slot"
|
||||
},
|
||||
"slot5": {
|
||||
"$ref": "#/definitions/dynamips_slot"
|
||||
},
|
||||
"slot6": {
|
||||
"$ref": "#/definitions/dynamips_slot"
|
||||
},
|
||||
"midplane": {
|
||||
"enum": [
|
||||
"std",
|
||||
"vxr"
|
||||
]
|
||||
},
|
||||
"npe": {
|
||||
"enum": [
|
||||
"npe-100",
|
||||
"npe-150",
|
||||
"npe-175",
|
||||
"npe-200",
|
||||
"npe-225",
|
||||
"npe-300",
|
||||
"npe-400",
|
||||
"npe-g2"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"platform"
|
||||
]
|
||||
},
|
||||
"qemu_disk_interfaces": {
|
||||
"enum": [
|
||||
"ide",
|
||||
"scsi",
|
||||
"sd",
|
||||
"mtd",
|
||||
"floppy",
|
||||
"pflash",
|
||||
"virtio",
|
||||
"sata"
|
||||
]
|
||||
},
|
||||
"qemu_properties": {
|
||||
"type": "object",
|
||||
"title": "Qemu template properties",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "Name of the template"
|
||||
},
|
||||
"category": {
|
||||
"$ref": "#/definitions/categories",
|
||||
"title": "Category of the template"
|
||||
},
|
||||
"default_name_format": {
|
||||
"type": "string",
|
||||
"title": "Default name format"
|
||||
},
|
||||
"usage": {
|
||||
"type": "string",
|
||||
"title": "How to use the template"
|
||||
},
|
||||
"symbol": {
|
||||
"type": "string",
|
||||
"title": "Symbol of the template"
|
||||
},
|
||||
"adapter_type": {
|
||||
"enum": [
|
||||
"e1000",
|
||||
"i82550",
|
||||
"i82551",
|
||||
"i82557a",
|
||||
"i82557b",
|
||||
"i82557c",
|
||||
"i82558a",
|
||||
"i82558b",
|
||||
"i82559a",
|
||||
"i82559b",
|
||||
"i82559c",
|
||||
"i82559er",
|
||||
"i82562",
|
||||
"i82801",
|
||||
"igb",
|
||||
"ne2k_pci",
|
||||
"pcnet",
|
||||
"rtl8139",
|
||||
"virtio",
|
||||
"virtio-net-pci",
|
||||
"vmxnet3"
|
||||
],
|
||||
"title": "Type of network adapter"
|
||||
},
|
||||
"adapters": {
|
||||
"type": "integer",
|
||||
"title": "Number of adapters"
|
||||
},
|
||||
"custom_adapters": {
|
||||
"type": "array",
|
||||
"title": "Custom adapters",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"adapter_number": {
|
||||
"title": "Adapter number",
|
||||
"type": "integer"
|
||||
},
|
||||
"port_name": {
|
||||
"title": "Custom port name",
|
||||
"type": "string",
|
||||
"minimum": 1
|
||||
},
|
||||
"adapter_type": {
|
||||
"title": "Custom adapter type",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"e1000",
|
||||
"i82550",
|
||||
"i82551",
|
||||
"i82557a",
|
||||
"i82557b",
|
||||
"i82557c",
|
||||
"i82558a",
|
||||
"i82558b",
|
||||
"i82559a",
|
||||
"i82559b",
|
||||
"i82559c",
|
||||
"i82559er",
|
||||
"i82562",
|
||||
"i82801",
|
||||
"igb",
|
||||
"ne2k_pci",
|
||||
"pcnet",
|
||||
"rtl8139",
|
||||
"virtio",
|
||||
"virtio-net-pci",
|
||||
"vmxnet3"
|
||||
]
|
||||
},
|
||||
"mac_address": {
|
||||
"title": "Custom MAC address",
|
||||
"type": "string",
|
||||
"minimum": 1,
|
||||
"pattern": "^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"adapter_number"
|
||||
]
|
||||
}
|
||||
},
|
||||
"first_port_name": {
|
||||
"type": "string",
|
||||
"title": "Optional name of the first networking port example: eth0"
|
||||
},
|
||||
"port_name_format": {
|
||||
"type": "string",
|
||||
"title": "Optional formating of the networking port example: eth{0}"
|
||||
},
|
||||
"port_segment_size": {
|
||||
"type": "integer",
|
||||
"title": "Optional port segment size. A port segment is a block of port. For example Ethernet0/0 Ethernet0/1 is the module 0 with a port segment size of 2"
|
||||
},
|
||||
"linked_clone": {
|
||||
"type": "boolean",
|
||||
"title": "False if you don't want to use a single image for all nodes"
|
||||
},
|
||||
"ram": {
|
||||
"type": "integer",
|
||||
"title": "Ram allocated to the appliance (MB)"
|
||||
},
|
||||
"cpus": {
|
||||
"type": "integer",
|
||||
"title": "Number of Virtual CPU"
|
||||
},
|
||||
"hda_disk_interface": {
|
||||
"$ref": "#/definitions/qemu_disk_interfaces",
|
||||
"title": "Disk interface for the installed hda_disk_image"
|
||||
},
|
||||
"hdb_disk_interface": {
|
||||
"$ref": "#/definitions/qemu_disk_interfaces",
|
||||
"title": "Disk interface for the installed hdb_disk_image"
|
||||
},
|
||||
"hdc_disk_interface": {
|
||||
"$ref": "#/definitions/qemu_disk_interfaces",
|
||||
"title": "Disk interface for the installed hdc_disk_image"
|
||||
},
|
||||
"hdd_disk_interface": {
|
||||
"$ref": "#/definitions/qemu_disk_interfaces",
|
||||
"title": "Disk interface for the installed hdd_disk_image"
|
||||
},
|
||||
"platform": {
|
||||
"enum": [
|
||||
"aarch64",
|
||||
"alpha",
|
||||
"arm",
|
||||
"cris",
|
||||
"i386",
|
||||
"lm32",
|
||||
"m68k",
|
||||
"microblaze",
|
||||
"microblazeel",
|
||||
"mips",
|
||||
"mips64",
|
||||
"mips64el",
|
||||
"mipsel",
|
||||
"moxie",
|
||||
"or32",
|
||||
"ppc",
|
||||
"ppc64",
|
||||
"ppcemb",
|
||||
"s390x",
|
||||
"sh4",
|
||||
"sh4eb",
|
||||
"sparc",
|
||||
"sparc64",
|
||||
"tricore",
|
||||
"unicore32",
|
||||
"x86_64",
|
||||
"xtensa",
|
||||
"xtensaeb"
|
||||
],
|
||||
"title": "Platform to emulate"
|
||||
},
|
||||
"console_type": {
|
||||
"enum": [
|
||||
"telnet",
|
||||
"vnc",
|
||||
"spice",
|
||||
"spice+agent",
|
||||
"none"
|
||||
],
|
||||
"title": "Type of console connection for the administration of the appliance"
|
||||
},
|
||||
"boot_priority": {
|
||||
"enum": [
|
||||
"d",
|
||||
"c",
|
||||
"dc",
|
||||
"cd",
|
||||
"n",
|
||||
"nc",
|
||||
"nd",
|
||||
"cn",
|
||||
"dn"
|
||||
],
|
||||
"title": "Optional define the disk boot priory. Refer to -boot option in qemu manual for more details."
|
||||
},
|
||||
"kernel_command_line": {
|
||||
"type": "string",
|
||||
"title": "Command line parameters send to the kernel"
|
||||
},
|
||||
"options": {
|
||||
"type": "string",
|
||||
"title": "Optional additional qemu command line options"
|
||||
},
|
||||
"cpu_throttling": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 100,
|
||||
"title": "Throttle the CPU"
|
||||
},
|
||||
"tpm": {
|
||||
"type": "boolean",
|
||||
"title": "Enable the Trusted Platform Module (TPM)"
|
||||
},
|
||||
"uefi": {
|
||||
"type": "boolean",
|
||||
"title": "Enable the UEFI boot mode"
|
||||
},
|
||||
"on_close": {
|
||||
"title": "Action to execute on the VM is closed",
|
||||
"enum": [
|
||||
"power_off",
|
||||
"shutdown_signal",
|
||||
"save_vm_state"
|
||||
]
|
||||
},
|
||||
"process_priority": {
|
||||
"title": "Process priority for QEMU",
|
||||
"enum": [
|
||||
"realtime",
|
||||
"very high",
|
||||
"high",
|
||||
"normal",
|
||||
"low",
|
||||
"very low",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"appliance_id": {
|
||||
"title": "Appliance ID",
|
||||
"type": "string",
|
||||
"minLength": 36,
|
||||
"maxLength": 36,
|
||||
"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "Appliance name"
|
||||
},
|
||||
"category": {
|
||||
"$ref": "#/definitions/categories",
|
||||
"title": "Category of the appliance"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"title": "Description of the appliance. Could be a marketing description"
|
||||
},
|
||||
"vendor_name": {
|
||||
"type": "string",
|
||||
"title": "Name of the vendor"
|
||||
},
|
||||
"vendor_url": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"title": "Website of the vendor"
|
||||
},
|
||||
"vendor_logo_url": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"title": "Link to the vendor logo (used by the GNS3 marketplace)"
|
||||
},
|
||||
"documentation_url": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"title": "An optional documentation for using the appliance on vendor website"
|
||||
},
|
||||
"product_name": {
|
||||
"type": "string",
|
||||
"title": "Product name"
|
||||
},
|
||||
"product_url": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"title": "An optional product url on vendor website"
|
||||
},
|
||||
"registry_version": {
|
||||
"enum": [
|
||||
8
|
||||
],
|
||||
"title": "Version of the registry compatible with this appliance (version >=8 introduced breaking changes)"
|
||||
},
|
||||
"status": {
|
||||
"enum": [
|
||||
"stable",
|
||||
"experimental",
|
||||
"broken"
|
||||
],
|
||||
"title": "Document if the appliance is working or not"
|
||||
},
|
||||
"availability": {
|
||||
"enum": [
|
||||
"free",
|
||||
"with-registration",
|
||||
"free-to-try",
|
||||
"service-contract"
|
||||
],
|
||||
"title": "About image availability: can be downloaded directly; download requires a free registration; paid but a trial version (time or feature limited) is available; not available publicly"
|
||||
},
|
||||
"maintainer": {
|
||||
"type": "string",
|
||||
"title": "Maintainer name"
|
||||
},
|
||||
"maintainer_email": {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"title": "Maintainer email"
|
||||
},
|
||||
"installation_instructions": {
|
||||
"type": "string",
|
||||
"title": "Optional installation instructions"
|
||||
},
|
||||
"usage": {
|
||||
"type": "string",
|
||||
"title": "How to use the appliance"
|
||||
},
|
||||
"default_username": {
|
||||
"type": "string",
|
||||
"title": "Default username for the appliance"
|
||||
},
|
||||
"default_password": {
|
||||
"type": "string",
|
||||
"title": "Default password for the appliance"
|
||||
},
|
||||
"symbol": {
|
||||
"type": "string",
|
||||
"title": "An optional symbol for the appliance"
|
||||
},
|
||||
"settings": {
|
||||
"type": "array",
|
||||
"title": "Settings for running the appliance",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"title": "Emulator settings",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "Name of the settings set"
|
||||
},
|
||||
"default": {
|
||||
"type": "boolean",
|
||||
"title": "Whether these are the default settings"
|
||||
},
|
||||
"inherit_default_properties": {
|
||||
"type": "boolean",
|
||||
"title": "Whether the default properties should be used",
|
||||
"default": "true"
|
||||
},
|
||||
"template_type": {
|
||||
"enum": [
|
||||
"docker",
|
||||
"iou",
|
||||
"dynamips",
|
||||
"qemu"
|
||||
],
|
||||
"title": "Type of emulator properties"
|
||||
},
|
||||
"template_properties": {
|
||||
"type": "object",
|
||||
"title": "Properties for the template",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/qemu_properties"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/dynamips_properties"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/iou_properties"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/docker_properties"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"template_type",
|
||||
"template_properties"
|
||||
]
|
||||
}
|
||||
},
|
||||
"images": {
|
||||
"type": "array",
|
||||
"title": "Images for this appliance",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"title": "An image file",
|
||||
"properties": {
|
||||
"filename": {
|
||||
"type": "string",
|
||||
"title": "Filename"
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"title": "Version of the image file"
|
||||
},
|
||||
"md5sum": {
|
||||
"type": "string",
|
||||
"title": "MD5 cheksum of the image file",
|
||||
"pattern": "^[a-f0-9]{32}$"
|
||||
},
|
||||
"checksum": {
|
||||
"type": "string",
|
||||
"title": "checksum of the image file"
|
||||
},
|
||||
"checksum_type": {
|
||||
"title": "checksum type of the image file",
|
||||
"enum": [
|
||||
"md5"
|
||||
]
|
||||
},
|
||||
"filesize": {
|
||||
"type": "integer",
|
||||
"title": "File size in bytes of the image file"
|
||||
},
|
||||
"download_url": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"title": "Download URL where you can download the image file from a browser"
|
||||
},
|
||||
"direct_download_url": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"title": "Optional. Non authenticated URL to the image file where you can download the image directly"
|
||||
},
|
||||
"compression": {
|
||||
"enum": [
|
||||
"bzip2",
|
||||
"gzip",
|
||||
"lzma",
|
||||
"xz",
|
||||
"rar",
|
||||
"zip",
|
||||
"7z"
|
||||
],
|
||||
"title": "Optional, compression type of direct download URL image."
|
||||
},
|
||||
"compression_target": {
|
||||
"type": "string",
|
||||
"title": "Optional, file name of the image file inside the compressed file."
|
||||
}
|
||||
},
|
||||
"anyOf": [
|
||||
{
|
||||
"required": [
|
||||
"filename",
|
||||
"version",
|
||||
"md5sum",
|
||||
"filesize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"filename",
|
||||
"version",
|
||||
"checksum",
|
||||
"filesize"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"versions": {
|
||||
"type": "array",
|
||||
"title": "Versions of the appliance",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"title": "A version of the appliance",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "Name of the version"
|
||||
},
|
||||
"settings": {
|
||||
"type": "string",
|
||||
"title": "Template settings to use to run the version"
|
||||
},
|
||||
"category": {
|
||||
"$ref": "#/definitions/categories",
|
||||
"title": "Category of the version"
|
||||
},
|
||||
"installation_instructions": {
|
||||
"type": "string",
|
||||
"title": "Optional installation instructions for the version"
|
||||
},
|
||||
"usage": {
|
||||
"type": "string",
|
||||
"title": "Optional instructions about using the version"
|
||||
},
|
||||
"default_username": {
|
||||
"type": "string",
|
||||
"title": "Default username for the version"
|
||||
},
|
||||
"default_password": {
|
||||
"type": "string",
|
||||
"title": "Default password for the version"
|
||||
},
|
||||
"symbol": {
|
||||
"type": "string",
|
||||
"title": "An optional symbol for the version"
|
||||
},
|
||||
"images": {
|
||||
"type": "object",
|
||||
"title": "Images used for this version",
|
||||
"properties": {
|
||||
"kernel_image": {
|
||||
"type": "string",
|
||||
"title": "Kernel image (Qemu only)"
|
||||
},
|
||||
"initrd": {
|
||||
"type": "string",
|
||||
"title": "Initrd disk image (Qemu only)"
|
||||
},
|
||||
"image": {
|
||||
"type": "string",
|
||||
"title": "OS image (IOU and Dynamips only)"
|
||||
},
|
||||
"bios_image": {
|
||||
"type": "string",
|
||||
"title": "Bios image (Qemu only)"
|
||||
},
|
||||
"hda_disk_image": {
|
||||
"type": "string",
|
||||
"title": "Hda disk image (Qemu only)"
|
||||
},
|
||||
"hdb_disk_image": {
|
||||
"type": "string",
|
||||
"title": "Hdc disk image (Qemu only)"
|
||||
},
|
||||
"hdc_disk_image": {
|
||||
"type": "string",
|
||||
"title": "Hdd disk image (Qemu only)"
|
||||
},
|
||||
"hdd_disk_image": {
|
||||
"type": "string",
|
||||
"title": "Hdd disk image (Qemu only)"
|
||||
},
|
||||
"cdrom_image": {
|
||||
"type": "string",
|
||||
"title": "cdrom image (Qemu only)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"appliance_id",
|
||||
"name",
|
||||
"category",
|
||||
"description",
|
||||
"vendor_name",
|
||||
"vendor_url",
|
||||
"product_name",
|
||||
"registry_version",
|
||||
"status",
|
||||
"maintainer",
|
||||
"maintainer_email",
|
||||
"settings"
|
||||
]
|
||||
}
|
||||
128
gns3/settings.py
128
gns3/settings.py
@@ -55,28 +55,29 @@ if sys.platform.startswith("win"):
|
||||
# windows 32-bit
|
||||
program_files_x86 = program_files = os.environ["PROGRAMFILES"]
|
||||
|
||||
PRECONFIGURED_TELNET_CONSOLE_COMMANDS = {'Putty (normal standalone version)': 'putty_standalone.exe -telnet %h %p -loghost "%d"',
|
||||
'MobaXterm': r'"{}\Mobatek\MobaXterm Personal Edition\MobaXterm.exe" -newtab "telnet %h %p"'.format(program_files_x86),
|
||||
'Royal TS V3': r'{}\code4ward.net\Royal TS V3\RTS3App.exe /connectadhoc:%h /adhoctype:terminal /p:IsTelnetConnection="true" /p:ConnectionType="telnet;Telnet Connection" /p:Port="%p" /p:Name="%d"'.format(program_files),
|
||||
'Royal TS V5': r'"{}\Royal TS V5\RoyalTS.exe" /protocol:terminal /using:adhoc /uri:"%h" /property:Port="%p" /property:IsTelnetConnection="true" /property:Name="%d"'.format(program_files_x86),
|
||||
'SuperPutty': r'SuperPutty.exe -telnet "%h -P %p -wt \"%d\""',
|
||||
'SecureCRT': r'"{}\VanDyke Software\SecureCRT\SecureCRT.exe" /N "%d" /T /TELNET %h %p'.format(program_files),
|
||||
'SecureCRT (personal profile)': r'"{}\AppData\Local\VanDyke Software\SecureCRT\SecureCRT.exe" /T /N "%d" /TELNET %h %p'.format(userprofile),
|
||||
'TeraTerm Pro': r'"{}\teraterm\ttermpro.exe" /W="%d" /M="ttstart.macro" /T=1 %h %p'.format(program_files_x86),
|
||||
'Telnet': 'telnet %h %p',
|
||||
'Windows Terminal': 'wt.exe -w 1 new-tab --title %d telnet %h %p',
|
||||
'Xshell 4': r'"{}\NetSarang\Xshell 4\xshell.exe" -url telnet://%h:%p'.format(program_files_x86),
|
||||
'Xshell 5': r'"{}\NetSarang\Xshell 5\xshell.exe" -url telnet://%h:%p -newtab %d'.format(program_files_x86),
|
||||
'ZOC 6': r'"{}\ZOC6\zoc.exe" "/TELNET:%h:%p" /TABBED "/TITLE:%d"'.format(program_files_x86)}
|
||||
PRECONFIGURED_TELNET_CONSOLE_COMMANDS = {'Putty (normal standalone version)': 'putty_standalone.exe -telnet {host} {port} -loghost "{name}"',
|
||||
'KiTTY': r'kitty -title "{name}" telnet://{host} {port}',
|
||||
'MobaXterm': r'"{}\Mobatek\MobaXterm Personal Edition\MobaXterm.exe" -newtab "telnet {{host}} {{port}}"'.format(program_files_x86),
|
||||
'Royal TS V3': r'{}\code4ward.net\Royal TS V3\RTS3App.exe /connectadhoc:{{host}} /adhoctype:terminal /p:IsTelnetConnection="true" /p:ConnectionType="telnet;Telnet Connection" /p:Port="{{port}}" /p:Name="{{name}}"'.format(program_files),
|
||||
'Royal TS V5': r'"{}\Royal TS V5\RoyalTS.exe" /protocol:terminal /using:adhoc /uri:"{{host}}" /property:Port="{{port}}" /property:IsTelnetConnection="true" /property:Name="{{name}}"'.format(program_files_x86),
|
||||
'SuperPutty': r'SuperPutty.exe -telnet "{host} -P {port} -wt \"{name}\""',
|
||||
'SecureCRT': r'"{}\VanDyke Software\SecureCRT\SecureCRT.exe" /N "{{name}}" /T /TELNET {{host}} {{port}}'.format(program_files),
|
||||
'SecureCRT (personal profile)': r'"{}\AppData\Local\VanDyke Software\SecureCRT\SecureCRT.exe" /T /N "{{name}}" /TELNET {{host}} {{port}}'.format(userprofile),
|
||||
'TeraTerm Pro': r'"{}\teraterm\ttermpro.exe" /W="{{name}}" /M="ttstart.macro" /T=1 {{host}} {{port}}'.format(program_files_x86),
|
||||
'Telnet': 'telnet {host} {port}',
|
||||
'Windows Terminal': 'wt.exe -w 1 new-tab --suppressApplicationTitle --title {name} telnet {host} {port}',
|
||||
'Xshell 4': r'"{}\NetSarang\Xshell 4\xshell.exe" -url telnet://{{host}}:{{port}}'.format(program_files_x86),
|
||||
'Xshell 5': r'"{}\NetSarang\Xshell 5\xshell.exe" -url telnet://{{host}}:{{port}} -newtab {{name}}'.format(program_files_x86),
|
||||
'ZOC 6': r'"{}\ZOC6\zoc.exe" "/TELNET:{{host}}:{{port}}" /TABBED "/TITLE:{{name}}"'.format(program_files_x86)}
|
||||
|
||||
# default on Windows
|
||||
if shutil.which("Solar-PuTTY.exe"):
|
||||
# Solar-Putty is the default if it is installed.
|
||||
PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Solar-Putty (included with GNS3)"] = 'Solar-PuTTY.exe --telnet --hostname %h --port %p --name "%d"'
|
||||
PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Solar-Putty (included with GNS3)"] = 'Solar-PuTTY.exe --telnet --hostname {host} --port {port} --name "{name}"'
|
||||
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Solar-Putty (included with GNS3)"]
|
||||
DEFAULT_DELAY_CONSOLE_ALL = 1500
|
||||
else:
|
||||
PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Solar-Putty (included with GNS3 downloaded from gns3.com)"] = 'Solar-PuTTY.exe --telnet --hostname %h --port %p --name "%d"'
|
||||
PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Solar-Putty (included with GNS3 downloaded from gns3.com)"] = 'Solar-PuTTY.exe --telnet --hostname {host} --port {port} --name "{name}"'
|
||||
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Putty (normal standalone version)"]
|
||||
|
||||
elif sys.platform.startswith("darwin"):
|
||||
@@ -86,7 +87,7 @@ elif sys.platform.startswith("darwin"):
|
||||
r""" -e 'set posix_path to do shell script "echo \"$PATH\""'"""
|
||||
r""" -e 'tell application "Terminal"'"""
|
||||
r""" -e 'activate'"""
|
||||
r""" -e 'do script "echo -n -e \"\\033]0;%d\\007\"; clear; PATH=" & quoted form of posix_path & " telnet %h %p ; exit"'"""
|
||||
r""" -e 'do script "echo -n -e \"\\033]0;{name}\\007\"; clear; PATH=" & quoted form of posix_path & " telnet {host} {port} ; exit"'"""
|
||||
r""" -e 'end tell'""",
|
||||
'Terminal tabbed (experimental)': r"""osascript"""
|
||||
r""" -e 'set posix_path to do shell script "echo \"$PATH\""'"""
|
||||
@@ -102,7 +103,7 @@ elif sys.platform.startswith("darwin"):
|
||||
r""" -e 'repeat while the busy of window 1 = true'"""
|
||||
r""" -e 'delay 0.01'"""
|
||||
r""" -e 'end repeat'"""
|
||||
r""" -e 'do script "echo -n -e \"\\033]0;%d\\007\"; clear; PATH=" & quoted form of posix_path & " telnet %h %p ; exit" in window 1'"""
|
||||
r""" -e 'do script "echo -n -e \"\\033]0;{name}\\007\"; clear; PATH=" & quoted form of posix_path & " telnet {host} {port} ; exit" in window 1'"""
|
||||
r""" -e 'end tell'""",
|
||||
'iTerm2 2.x': r"""osascript"""
|
||||
r""" -e 'set posix_path to do shell script "echo \"$PATH\""'"""
|
||||
@@ -117,7 +118,7 @@ elif sys.platform.startswith("darwin"):
|
||||
r""" -e ' set s to (make new session at the end of sessions)'"""
|
||||
r""" -e ' tell s'"""
|
||||
r""" -e ' exec command "sh"'"""
|
||||
r""" -e ' write text "PATH=" & quoted form of posix_path & " exec telnet %h %p"'"""
|
||||
r""" -e ' write text "PATH=" & quoted form of posix_path & " exec telnet {host} {port}"'"""
|
||||
r""" -e ' end tell'"""
|
||||
r""" -e 'end tell'"""
|
||||
r""" -e 'end tell'""",
|
||||
@@ -134,48 +135,51 @@ elif sys.platform.startswith("darwin"):
|
||||
r""" -e ' create tab with default profile command "sh"'"""
|
||||
r""" -e ' set s to current session'"""
|
||||
r""" -e ' tell s'"""
|
||||
r""" -e ' set name to "%d"'"""
|
||||
r""" -e ' write text "PATH=" & quoted form of posix_path & " exec telnet %h %p"'"""
|
||||
r""" -e ' set name to "{name}"'"""
|
||||
r""" -e ' write text "PATH=" & quoted form of posix_path & " exec telnet {host} {port}"'"""
|
||||
r""" -e ' end tell'"""
|
||||
r""" -e 'end tell'"""
|
||||
r""" -e 'end tell'""",
|
||||
'Royal TSX': "open 'rtsx://telnet%3A%2F%2F%h:%p'",
|
||||
'SecureCRT': '/Applications/SecureCRT.app/Contents/MacOS/SecureCRT /N "%d" /T /TELNET %h %p',
|
||||
'ZOC 6': '/Applications/zoc6.app/Contents/MacOS/zoc6 "/TELNET:%h:%p" /TABBED "/TITLE:%d"',
|
||||
'ZOC 7': '/Applications/zoc7.app/Contents/MacOS/zoc7 "/TELNET:%h:%p" /TABBED "/TITLE:%d"',
|
||||
'ZOC 8': '/Applications/zoc8.app/Contents/MacOS/zoc8 "/TELNET:%h:%p" /TABBED "/TITLE:%d"'
|
||||
'Royal TSX': "open 'rtsx://telnet%3A%2F%2F{host}:{port}'",
|
||||
'SecureCRT': '/Applications/SecureCRT.app/Contents/MacOS/SecureCRT /N "{name}" /T /TELNET {host} {port}',
|
||||
'ZOC 6': '/Applications/zoc6.app/Contents/MacOS/zoc6 "/TELNET:{host}:{port}" /TABBED "/TITLE:{name}"',
|
||||
'ZOC 7': '/Applications/zoc7.app/Contents/MacOS/zoc7 "/TELNET:{host}:{port}" /TABBED "/TITLE:{name}"',
|
||||
'ZOC 8': '/Applications/zoc8.app/Contents/MacOS/zoc8 "/TELNET:{host}:{port}" /TABBED "/TITLE:{name}"'
|
||||
}
|
||||
|
||||
# default Mac OS X Telnet console command
|
||||
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Terminal"]
|
||||
|
||||
else:
|
||||
PRECONFIGURED_TELNET_CONSOLE_COMMANDS = {'Xterm': 'xterm -T "%d" -e "telnet %h %p"',
|
||||
'Putty': 'putty -telnet %h %p -title "%d" -sl 2500 -fg SALMON1 -bg BLACK',
|
||||
'Gnome Terminal': 'gnome-terminal -t "%d" -e "telnet %h %p"',
|
||||
'Xfce4 Terminal': 'xfce4-terminal --tab -T "%d" -e "telnet %h %p"',
|
||||
'ROXTerm': 'roxterm -n "%d" --tab -e "telnet %h %p"',
|
||||
'KDE Konsole': 'konsole --new-tab -p tabtitle="%d" -e "telnet %h %p"',
|
||||
'SecureCRT': 'SecureCRT /T /N "%d" /TELNET %h %p',
|
||||
'Mate Terminal': 'mate-terminal --tab -e "telnet %h %p" -t "%d"',
|
||||
'terminator': 'terminator -e "telnet %h %p" -T "%d"',
|
||||
'urxvt': 'urxvt -title %d -e telnet %h %p',
|
||||
'kitty': 'kitty -T %d telnet %h %p'}
|
||||
PRECONFIGURED_TELNET_CONSOLE_COMMANDS = {'Xterm': 'xterm -T "{name}" -e "telnet {host} {port}"',
|
||||
'Putty': 'putty -telnet {host} {port} -title "{name}" -sl 2500 -fg SALMON1 -bg BLACK',
|
||||
'Gnome Terminal': 'gnome-terminal --tab -t "{name}" -- telnet {host} {port}',
|
||||
'Xfce4 Terminal': 'xfce4-terminal --tab -T "{name}" -e "telnet {host} {port}"',
|
||||
'ROXTerm': 'roxterm -n "{name}" --tab -e "telnet {host} {port}"',
|
||||
'KDE Konsole': 'konsole --new-tab -p tabtitle="{name}" -e "telnet {host} {port}"',
|
||||
'SecureCRT': 'SecureCRT /T /N "{name}" /TELNET {host} {port}',
|
||||
'Mate Terminal': 'mate-terminal --tab -e "telnet {host} {port}" -t "{name}"',
|
||||
'terminator': 'terminator -e "telnet {host} {port}" -T "{name}"',
|
||||
'urxvt': 'urxvt -title {name} -e telnet {host} {port}',
|
||||
'kitty': 'kitty -T {name} telnet {host} {port}'}
|
||||
|
||||
# default Telnet console command on other systems
|
||||
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Xterm"]
|
||||
|
||||
if sys.platform.startswith("linux"):
|
||||
distro_name = distro.name()
|
||||
if distro_name == "Debian" or distro_name == "Ubuntu" or distro_name == "LinuxMint":
|
||||
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Gnome Terminal"]
|
||||
if distro_name == "Debian" or distro_name == "Ubuntu" or distro_name == "Linux Mint":
|
||||
if shutil.which("mate-terminal"):
|
||||
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Mate Terminal"]
|
||||
else:
|
||||
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Gnome Terminal"]
|
||||
|
||||
# Pre-configured VNC console commands on various OSes
|
||||
if sys.platform.startswith("win"):
|
||||
# Windows
|
||||
PRECONFIGURED_VNC_CONSOLE_COMMANDS = {
|
||||
'TightVNC (included with GNS3)': 'tvnviewer.exe %h:%p',
|
||||
'UltraVNC': r'"{}\uvnc bvba\UltraVNC\vncviewer.exe" %h:%p'.format(program_files)
|
||||
'TightVNC (included with GNS3)': 'tvnviewer.exe {host}:{port}',
|
||||
'UltraVNC': r'"{}\uvnc bvba\UltraVNC\vncviewer.exe" {{host}}:{{port}}'.format(program_files)
|
||||
}
|
||||
|
||||
# default Windows VNC console command
|
||||
@@ -187,11 +191,11 @@ elif sys.platform.startswith("darwin"):
|
||||
'OSX builtin screen sharing': "osascript"
|
||||
" -e 'tell application \"Screen Sharing\"'"
|
||||
" -e ' display dialog \"WARNING OSX VNC support is limited if you have trouble connecting to a device please use an alternative client like Chicken of the VNC.\" buttons {\"OK\"} default button 1 with icon caution with title \"GNS3\"'"
|
||||
" -e ' open location \"vnc://%h:%p\"'"
|
||||
" -e ' open location \"vnc://{host}:{port}\"'"
|
||||
" -e 'end tell'",
|
||||
'Chicken of the VNC': "/Applications/Chicken.app/Contents/MacOS/Chicken %h:%p",
|
||||
'Chicken of the VNC < 2.2': r"/Applications/Chicken\ of\ the\ VNC.app/Contents/MacOS/Chicken\ of\ the\ VNC %h:%p",
|
||||
'Royal TSX': "open 'rtsx://vnc%3A%2F%2F%h:%p'",
|
||||
'Chicken of the VNC': "/Applications/Chicken.app/Contents/MacOS/Chicken {host}:{port}",
|
||||
'Chicken of the VNC < 2.2': r"/Applications/Chicken\ of\ the\ VNC.app/Contents/MacOS/Chicken\ of\ the\ VNC {host}:{port}",
|
||||
'Royal TSX': "open 'rtsx://vnc%3A%2F%2F{host}:{port}'",
|
||||
}
|
||||
|
||||
# default Mac OS X VNC console command
|
||||
@@ -199,10 +203,11 @@ elif sys.platform.startswith("darwin"):
|
||||
|
||||
else:
|
||||
PRECONFIGURED_VNC_CONSOLE_COMMANDS = {
|
||||
'TightVNC': 'vncviewer %h:%p',
|
||||
'Vinagre': 'vinagre %h::%p',
|
||||
'gvncviewer': 'gvncviewer %h:%P',
|
||||
'Remote Viewer': 'remote-viewer vnc://%h:%p'
|
||||
'TightVNC': 'vncviewer {host}:{port}',
|
||||
'Vinagre': 'vinagre {host}::{port}',
|
||||
'gvncviewer': 'gvncviewer {host}:{display}',
|
||||
'Remote Viewer': 'remote-viewer vnc://{host}:{port}',
|
||||
'KRDC': 'krdc vnc://{host}:{port}'
|
||||
}
|
||||
|
||||
# default VNC console command on other systems
|
||||
@@ -212,7 +217,7 @@ else:
|
||||
if sys.platform.startswith("win"):
|
||||
# Windows
|
||||
PRECONFIGURED_SPICE_CONSOLE_COMMANDS = {
|
||||
'Remote Viewer': r'"{}\VirtViewer v11.0-256\bin\remote-viewer.exe" spice://%h:%p'.format(program_files),
|
||||
'Remote Viewer': r'"{}\VirtViewer v11.0-256\bin\remote-viewer.exe" spice://{{host}}:{{port}}'.format(program_files),
|
||||
}
|
||||
|
||||
# default Windows SPICE console command
|
||||
@@ -221,7 +226,7 @@ if sys.platform.startswith("win"):
|
||||
elif sys.platform.startswith("darwin"):
|
||||
# Mac OS X
|
||||
PRECONFIGURED_SPICE_CONSOLE_COMMANDS = {
|
||||
'Remote Viewer': '/Applications/RemoteViewer.app/Contents/MacOS/RemoteViewer spice://%h:%p',
|
||||
'Remote Viewer': '/Applications/RemoteViewer.app/Contents/MacOS/RemoteViewer spice://{host}:{port}',
|
||||
}
|
||||
|
||||
# default Mac OS X SPICE console command
|
||||
@@ -229,7 +234,7 @@ elif sys.platform.startswith("darwin"):
|
||||
|
||||
else:
|
||||
PRECONFIGURED_SPICE_CONSOLE_COMMANDS = {
|
||||
'Remote Viewer': 'remote-viewer spice://%h:%p',
|
||||
'Remote Viewer': 'remote-viewer spice://{host}:{port}',
|
||||
}
|
||||
|
||||
# default SPICE console command on other systems
|
||||
@@ -240,29 +245,28 @@ WIRESHARK_NORMAL_CAPTURE = "Wireshark Traditional Capture"
|
||||
WIRESHARK_LIVE_TRAFFIC_CAPTURE = "Wireshark Live Traffic Capture"
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: r"{}\Wireshark\wireshark.exe %c".format(program_files),
|
||||
WIRESHARK_LIVE_TRAFFIC_CAPTURE: r'tail.exe -f -c +0b %c | "{}\Wireshark\wireshark.exe" -o "gui.window_title:%d" -k -i -'.format(program_files)}
|
||||
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: r'{}\Wireshark\wireshark.exe {{pcap_file}} --capture-comment "{{project}} {{link_description}}"'.format(program_files),
|
||||
WIRESHARK_LIVE_TRAFFIC_CAPTURE: r'tail.exe -f -c +0b {{pcap_file}} | "{}\Wireshark\wireshark.exe" --capture-comment "{{project}} {{link_description}}" -o "gui.window_title:{{link_description}}" -k -i -'.format(program_files)}
|
||||
|
||||
elif sys.platform.startswith("darwin"):
|
||||
# Mac OS X
|
||||
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: "/usr/bin/open -a /Applications/Wireshark.app %c",
|
||||
"Wireshark V1.X Live Traffic Capture": 'tail -f -c +0 %c | /Applications/Wireshark.app/Contents/Resources/bin/wireshark -o "gui.window_title:%d" -k -i -',
|
||||
WIRESHARK_LIVE_TRAFFIC_CAPTURE: 'tail -f -c +0 %c | /Applications/Wireshark.app/Contents/MacOS/Wireshark -o "gui.window_title:%d" -k -i -'}
|
||||
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: '/usr/bin/open -a /Applications/Wireshark.app {pcap_file} --capture-comment {project} {link_description}"',
|
||||
WIRESHARK_LIVE_TRAFFIC_CAPTURE: 'tail -f -c +0 {pcap_file} | /Applications/Wireshark.app/Contents/MacOS/Wireshark --capture-comment "{project} {link_description}" -o "gui.window_title:{link_description}" -k -i -'}
|
||||
|
||||
elif sys.platform.startswith("freebsd"):
|
||||
# FreeBSD
|
||||
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: "wireshark %c",
|
||||
WIRESHARK_LIVE_TRAFFIC_CAPTURE: 'gtail -f -c +0b %c | wireshark -o "gui.window_title:%d" -k -i -'}
|
||||
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: 'wireshark {pcap_file} --capture-comment "{project} {link_description}"',
|
||||
WIRESHARK_LIVE_TRAFFIC_CAPTURE: 'gtail -f -c +0b {pcap_file} | wireshark --capture-comment "{project} {link_description}" -o "gui.window_title:{link_description}" -k -i -'}
|
||||
else:
|
||||
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: "wireshark %c",
|
||||
WIRESHARK_LIVE_TRAFFIC_CAPTURE: 'tail -f -c +0b %c | wireshark -o "gui.window_title:%d" -k -i -'}
|
||||
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: 'wireshark {pcap_file} --capture-comment "{project} {link_description}"',
|
||||
WIRESHARK_LIVE_TRAFFIC_CAPTURE: 'tail -f -c +0b {pcap_file} | wireshark --capture-comment "{project} {link_description}" -o "gui.window_title:{link_description}" -k -i -'}
|
||||
|
||||
DEFAULT_PACKET_CAPTURE_READER_COMMAND = PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS[WIRESHARK_LIVE_TRAFFIC_CAPTURE]
|
||||
|
||||
DEFAULT_PACKET_CAPTURE_ANALYZER_COMMAND = ""
|
||||
if sys.platform.startswith("win"):
|
||||
# Windows 64-bit
|
||||
DEFAULT_PACKET_CAPTURE_ANALYZER_COMMAND = r'"{}\SolarWinds\ResponseTimeViewer\ResponseTimeViewer.exe" %c'.format(program_files_x86)
|
||||
DEFAULT_PACKET_CAPTURE_ANALYZER_COMMAND = r'"{}\SolarWinds\ResponseTimeViewer\ResponseTimeViewer.exe" {{pcap_file}}'.format(program_files_x86)
|
||||
|
||||
STYLES = ["Charcoal", "Classic", "Legacy"]
|
||||
|
||||
@@ -284,8 +288,6 @@ GENERAL_SETTINGS = {
|
||||
"check_for_update": True,
|
||||
"overlay_notifications": True,
|
||||
"experimental_features": False,
|
||||
"send_stats": True,
|
||||
"stats_visitor_id": str(uuid.uuid4()), # An anonymous id for stats
|
||||
"last_check_for_update": 0,
|
||||
"telnet_console_command": DEFAULT_TELNET_CONSOLE_COMMAND,
|
||||
"vnc_console_command": DEFAULT_VNC_CONSOLE_COMMAND,
|
||||
|
||||
@@ -54,14 +54,24 @@ def spiceConsole(node, port, command):
|
||||
command = command.replace("%h", host)
|
||||
command = command.replace("%p", str(port))
|
||||
command = command.replace("%d", name.replace('"', '\\"'))
|
||||
command = command.replace("%P", node.project().name().replace('"', '\\"'))
|
||||
command = command.replace("%i", node.project().id())
|
||||
command = command.replace("%n", str(node.id()))
|
||||
command = command.replace("%c", Controller.instance().httpClient().fullUrl())
|
||||
|
||||
command = command.replace("{host}", host)
|
||||
command = command.replace("{port}", str(port))
|
||||
command = command.replace("{name}", name.replace('"', '\\"'))
|
||||
command = command.replace("{project}", node.project().name())
|
||||
command = command.replace("{project_id}", node.project().id())
|
||||
command = command.replace("{node_id}", str(node.id()))
|
||||
command = command.replace("{url}", Controller.instance().httpClient().fullUrl())
|
||||
|
||||
try:
|
||||
log.debug('starting SPICE program "{}"'.format(command))
|
||||
if sys.platform.startswith("win"):
|
||||
# use the string on Windows
|
||||
subprocess.Popen(command)
|
||||
subprocess.Popen(command, env=os.environ)
|
||||
else:
|
||||
# use arguments on other platforms
|
||||
args = shlex.split(command)
|
||||
|
||||
@@ -47,6 +47,10 @@ class Style:
|
||||
Sets the legacy GUI style.
|
||||
"""
|
||||
|
||||
graphics_view = self._mw.uiGraphicsView
|
||||
if hasattr(graphics_view, 'resetGridColors'):
|
||||
graphics_view.resetGridColors()
|
||||
|
||||
self._mw.setStyleSheet("")
|
||||
self._mw.uiNewProjectAction.setIcon(QtGui.QIcon(":/icons/new-project.svg"))
|
||||
self._mw.uiOpenProjectAction.setIcon(QtGui.QIcon(":/icons/open.svg"))
|
||||
@@ -99,6 +103,10 @@ class Style:
|
||||
Sets the classic GUI style.
|
||||
"""
|
||||
|
||||
graphics_view = self._mw.uiGraphicsView
|
||||
if hasattr(graphics_view, 'resetGridColors'):
|
||||
graphics_view.resetGridColors()
|
||||
|
||||
self._mw.setStyleSheet("")
|
||||
self._mw.uiNewProjectAction.setIcon(self._getStyleIcon(":/classic_icons/new-project.svg", ":/classic_icons/new-project-hover.svg"))
|
||||
self._mw.uiOpenProjectAction.setIcon(self._getStyleIcon(":/classic_icons/open.svg", ":/classic_icons/open-hover.svg"))
|
||||
@@ -155,6 +163,10 @@ class Style:
|
||||
Sets the charcoal GUI style.
|
||||
"""
|
||||
|
||||
graphics_view = self._mw.uiGraphicsView
|
||||
if hasattr(graphics_view, 'resetGridColors'):
|
||||
graphics_view.resetGridColors()
|
||||
|
||||
style_file = QtCore.QFile(":/styles/charcoal.css")
|
||||
style_file.open(QtCore.QFile.ReadOnly)
|
||||
style = QtCore.QTextStream(style_file).readAll()
|
||||
|
||||
@@ -25,6 +25,9 @@ import os
|
||||
import sys
|
||||
import shlex
|
||||
import subprocess
|
||||
import psutil
|
||||
import shutil
|
||||
|
||||
from .main_window import MainWindow
|
||||
from .controller import Controller
|
||||
|
||||
@@ -34,6 +37,39 @@ log = logging.getLogger(__name__)
|
||||
console_mutex = QtCore.QMutex()
|
||||
|
||||
|
||||
def gnome_terminal_env():
|
||||
|
||||
uid = os.getuid()
|
||||
|
||||
# get list of processes of current user
|
||||
procs = [p.info for p in psutil.process_iter(
|
||||
attrs=['name', 'pid', 'ppid', 'create_time', 'uids']
|
||||
) if p.info['uids'].real == uid]
|
||||
|
||||
# get pid of gnome-terminal-server process
|
||||
gnome_terminal_server_pid = [p['pid'] for p in procs if p['name'] == "gnome-terminal-server"]
|
||||
if not gnome_terminal_server_pid:
|
||||
return {}
|
||||
gnome_terminal_server_pid = gnome_terminal_server_pid[0]
|
||||
|
||||
# get subprocesses of gnome-terminal-server
|
||||
gnome_terminal_server_children = [p for p in procs if p['ppid'] == gnome_terminal_server_pid]
|
||||
gnome_terminal_server_children.sort(key=lambda p: p['create_time'], reverse=True)
|
||||
|
||||
# return the gnome-terminal environment variables of the first subprocess named telnet
|
||||
for proc in gnome_terminal_server_children:
|
||||
if proc['name'] == "telnet":
|
||||
try:
|
||||
env = psutil.Process(proc['pid']).environ()
|
||||
if 'GNOME_TERMINAL_SERVICE' in env and \
|
||||
'GNOME_TERMINAL_SCREEN' in env:
|
||||
return {'GNOME_TERMINAL_SERVICE': env['GNOME_TERMINAL_SERVICE'],
|
||||
'GNOME_TERMINAL_SCREEN': env['GNOME_TERMINAL_SCREEN']}
|
||||
except psutil.Error:
|
||||
pass
|
||||
return {}
|
||||
|
||||
|
||||
class ConsoleThread(QtCore.QThread):
|
||||
|
||||
consoleError = QtCore.pyqtSignal(str)
|
||||
@@ -52,7 +88,7 @@ class ConsoleThread(QtCore.QThread):
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
# use the string on Windows
|
||||
subprocess.call(command)
|
||||
subprocess.Popen(command, env=os.environ)
|
||||
else:
|
||||
# use arguments on other platforms
|
||||
try:
|
||||
@@ -60,7 +96,23 @@ class ConsoleThread(QtCore.QThread):
|
||||
except ValueError:
|
||||
self.consoleError.emit("Syntax error in command: '{}'".format(command))
|
||||
return
|
||||
subprocess.call(args, env=os.environ)
|
||||
|
||||
env = os.environ.copy()
|
||||
# special case to force gnome-terminal to correctly use tabs on Linux
|
||||
if sys.platform.startswith("linux") and "gnome-terminal" in args[0] and "--tab" in command:
|
||||
# inject gnome-terminal environment variables
|
||||
if "GNOME_TERMINAL_SERVICE" not in env or "GNOME_TERMINAL_SCREEN" not in env:
|
||||
env.update(gnome_terminal_env())
|
||||
proc = subprocess.Popen(args, env=env)
|
||||
if sys.platform.startswith("linux"):
|
||||
wmctrl_path = shutil.which("wmctrl")
|
||||
if wmctrl_path:
|
||||
proc.wait() # wait for the terminal to open
|
||||
try:
|
||||
# use wmctrl to raise the window based on the node name
|
||||
subprocess.run([wmctrl_path, "-Fa", self._name], env=os.environ)
|
||||
except OSError as e:
|
||||
self.consoleError.emit("Count not focus on terminal window: '{}'".format(e))
|
||||
|
||||
def run(self):
|
||||
|
||||
@@ -71,10 +123,19 @@ class ConsoleThread(QtCore.QThread):
|
||||
command = self._command.replace("%h", host)
|
||||
command = command.replace("%p", str(port))
|
||||
command = command.replace("%d", self._name.replace('"', '\\"'))
|
||||
command = command.replace("%P", self._node.project().name().replace('"', '\\"'))
|
||||
command = command.replace("%i", self._node.project().id())
|
||||
command = command.replace("%n", str(self._node.id()))
|
||||
command = command.replace("%c", Controller.instance().httpClient().fullUrl())
|
||||
|
||||
command = command.replace("{host}", host)
|
||||
command = command.replace("{port}", str(port))
|
||||
command = command.replace("{name}", self._name.replace('"', '\\"'))
|
||||
command = command.replace("{project}", self._node.project().name().replace('"', '\\"'))
|
||||
command = command.replace("{project_id}", self._node.project().id())
|
||||
command = command.replace("{node_id}", str(self._node.id()))
|
||||
command = command.replace("{url}", Controller.instance().httpClient().fullUrl())
|
||||
|
||||
# If the console use an apple script we lock to avoid multiple console
|
||||
# to interact at the same time
|
||||
if sys.platform.startswith("darwin") and "osascript" in command:
|
||||
|
||||
@@ -209,11 +209,11 @@ class Topology(QtCore.QObject):
|
||||
project.setId(project_settings["project_id"])
|
||||
self.setProject(project)
|
||||
project.load()
|
||||
self._main_window.uiStatusBar.showMessage("Project loaded", 2000)
|
||||
self._main_window.uiStatusBar.showMessage("Project loaded", 5000)
|
||||
else:
|
||||
self.setProject(project)
|
||||
project.create()
|
||||
self._main_window.uiStatusBar.showMessage("Project created", 2000)
|
||||
self._main_window.uiStatusBar.showMessage("Project created", 5000)
|
||||
return project
|
||||
|
||||
def restoreSnapshot(self, project_id):
|
||||
@@ -225,7 +225,7 @@ class Topology(QtCore.QObject):
|
||||
project = self._project
|
||||
self.setProject(project, snapshot=True)
|
||||
project.load()
|
||||
self._main_window.uiStatusBar.showMessage("Snapshot restored", 2000)
|
||||
self._main_window.uiStatusBar.showMessage("Snapshot restored", 5000)
|
||||
|
||||
def loadProject(self, path):
|
||||
"""
|
||||
@@ -241,7 +241,7 @@ class Topology(QtCore.QObject):
|
||||
from .project import Project
|
||||
self.setProject(Project())
|
||||
self._project.load(path)
|
||||
self._main_window.uiStatusBar.showMessage("Project loaded {}".format(path), 2000)
|
||||
self._main_window.uiStatusBar.showMessage("Project loaded {}".format(path), 5000)
|
||||
return True
|
||||
|
||||
def editReadme(self):
|
||||
@@ -255,7 +255,7 @@ class Topology(QtCore.QObject):
|
||||
if self._project:
|
||||
self._project.project_creation_error_signal.disconnect(self._projectCreationErrorSlot)
|
||||
self.setProject(None)
|
||||
QtWidgets.QMessageBox.critical(self._main_window, "New project", message)
|
||||
QtWidgets.QMessageBox.critical(self._main_window, "Project", message)
|
||||
|
||||
def exportProject(self):
|
||||
if self._project is None:
|
||||
|
||||
@@ -415,3 +415,22 @@ class TopologySummaryView(QtWidgets.QTreeWidget):
|
||||
for link in self._topology.links():
|
||||
if link.suspended():
|
||||
link.toggleSuspend()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
"""
|
||||
Handles key press events
|
||||
"""
|
||||
|
||||
from .main_window import MainWindow
|
||||
view = MainWindow.instance().uiGraphicsView
|
||||
# only deleting a link or node is supported for now
|
||||
if event.key() == QtCore.Qt.Key_Delete:
|
||||
current_item = self.currentItem()
|
||||
if isinstance(current_item, TopologyNodeItem):
|
||||
current_item.node().delete()
|
||||
else:
|
||||
link = current_item.data(0, QtCore.Qt.UserRole)
|
||||
for item in view.scene().items():
|
||||
if isinstance(item, LinkItem) and item.link() == link:
|
||||
item.delete()
|
||||
super().keyPressEvent(event)
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>900</width>
|
||||
<height>601</height>
|
||||
<width>726</width>
|
||||
<height>428</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@@ -219,6 +219,27 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWizardPage" name="uiInstructionsPage">
|
||||
<property name="title">
|
||||
<string>Installation instructions</string>
|
||||
</property>
|
||||
<property name="subTitle">
|
||||
<string>Please read the following instructions in order to install your new appliance.</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QTextEdit" name="uiInstructionsTextEdit">
|
||||
<property name="html">
|
||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWizardPage" name="uiFilesWizardPage">
|
||||
<property name="title">
|
||||
<string>Required files</string>
|
||||
@@ -232,12 +253,12 @@
|
||||
<property name="indentation">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<attribute name="headerDefaultSectionSize">
|
||||
<number>120</number>
|
||||
</attribute>
|
||||
<attribute name="headerMinimumSectionSize">
|
||||
<number>20</number>
|
||||
</attribute>
|
||||
<attribute name="headerDefaultSectionSize">
|
||||
<number>120</number>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Appliance version and files</string>
|
||||
|
||||
@@ -2,16 +2,19 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/appliance_wizard.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.9
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_ApplianceWizard(object):
|
||||
def setupUi(self, ApplianceWizard):
|
||||
ApplianceWizard.setObjectName("ApplianceWizard")
|
||||
ApplianceWizard.resize(900, 601)
|
||||
ApplianceWizard.resize(726, 428)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@@ -112,6 +115,14 @@ class Ui_ApplianceWizard(object):
|
||||
self.uiQemuListComboBox.setObjectName("uiQemuListComboBox")
|
||||
self.horizontalLayout_2.addWidget(self.uiQemuListComboBox)
|
||||
ApplianceWizard.addPage(self.uiQemuWizardPage)
|
||||
self.uiInstructionsPage = QtWidgets.QWizardPage()
|
||||
self.uiInstructionsPage.setObjectName("uiInstructionsPage")
|
||||
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.uiInstructionsPage)
|
||||
self.verticalLayout_3.setObjectName("verticalLayout_3")
|
||||
self.uiInstructionsTextEdit = QtWidgets.QTextEdit(self.uiInstructionsPage)
|
||||
self.uiInstructionsTextEdit.setObjectName("uiInstructionsTextEdit")
|
||||
self.verticalLayout_3.addWidget(self.uiInstructionsTextEdit)
|
||||
ApplianceWizard.addPage(self.uiInstructionsPage)
|
||||
self.uiFilesWizardPage = QtWidgets.QWizardPage()
|
||||
self.uiFilesWizardPage.setObjectName("uiFilesWizardPage")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.uiFilesWizardPage)
|
||||
@@ -174,6 +185,13 @@ class Ui_ApplianceWizard(object):
|
||||
self.uiQemuWizardPage.setTitle(_translate("ApplianceWizard", "Qemu settings"))
|
||||
self.uiQemuWizardPage.setSubTitle(_translate("ApplianceWizard", "Please choose the qemu binary that will be used to run this appliance."))
|
||||
self.uiQemuListLabel.setText(_translate("ApplianceWizard", "Qemu binary:"))
|
||||
self.uiInstructionsPage.setTitle(_translate("ApplianceWizard", "Installation instructions"))
|
||||
self.uiInstructionsPage.setSubTitle(_translate("ApplianceWizard", "Please read the following instructions in order to install your new appliance."))
|
||||
self.uiInstructionsTextEdit.setHtml(_translate("ApplianceWizard", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
|
||||
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
|
||||
"p, li { white-space: pre-wrap; }\n"
|
||||
"</style></head><body style=\" font-family:\'Ubuntu\'; font-size:11pt; font-weight:400; font-style:normal;\">\n"
|
||||
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p></body></html>"))
|
||||
self.uiFilesWizardPage.setTitle(_translate("ApplianceWizard", "Required files"))
|
||||
self.uiFilesWizardPage.setSubTitle(_translate("ApplianceWizard", "The following files are required to install the appliance"))
|
||||
self.uiApplianceVersionTreeWidget.headerItem().setText(0, _translate("ApplianceWizard", "Appliance version and files"))
|
||||
@@ -191,5 +209,4 @@ class Ui_ApplianceWizard(object):
|
||||
"p, li { white-space: pre-wrap; }\n"
|
||||
"</style></head><body style=\" font-family:\'Ubuntu\'; font-size:11pt; font-weight:400; font-style:normal;\">\n"
|
||||
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">The default username/password is admin/admin. A default configuration is present.</p></body></html>"))
|
||||
|
||||
from . import resources_rc
|
||||
|
||||
@@ -9,16 +9,10 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>500</width>
|
||||
<height>147</height>
|
||||
<width>460</width>
|
||||
<height>142</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
<height>147</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Packet capture</string>
|
||||
</property>
|
||||
|
||||
@@ -2,19 +2,20 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/capture_dialog.ui'
|
||||
#
|
||||
# Created: Mon May 30 21:49:29 2016
|
||||
# by: PyQt5 UI code generator 5.2.1
|
||||
# Created by: PyQt5 UI code generator 5.15.6
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_CaptureDialog(object):
|
||||
def setupUi(self, CaptureDialog):
|
||||
CaptureDialog.setObjectName("CaptureDialog")
|
||||
CaptureDialog.setWindowModality(QtCore.Qt.WindowModal)
|
||||
CaptureDialog.resize(500, 147)
|
||||
CaptureDialog.setMaximumSize(QtCore.QSize(500, 147))
|
||||
CaptureDialog.resize(460, 142)
|
||||
CaptureDialog.setModal(False)
|
||||
self.gridLayout = QtWidgets.QGridLayout(CaptureDialog)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
@@ -73,5 +74,4 @@ class Ui_CaptureDialog(object):
|
||||
self.uiLinkTypeLabel.setText(_translate("CaptureDialog", "Link type:"))
|
||||
self.uiFileNameLabel.setText(_translate("CaptureDialog", "File name:"))
|
||||
self.uiStartCommandCheckBox.setText(_translate("CaptureDialog", "Start the capture visualization program"))
|
||||
|
||||
from . import resources_rc
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>965</width>
|
||||
<height>547</height>
|
||||
<width>576</width>
|
||||
<height>346</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@@ -70,7 +70,7 @@
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Or customize the command in the next input field. <br/>The following variables are replaced by GNS3: </p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%h: console IP or hostname</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%p: console port</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%P: VNC display</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%s: path of the serial connection</li></ul><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%d: title of the console</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%i: Project UUID</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%c: server URL (<span style=" font-style:italic;">http://user:password@server:port</span>)</li></ul></body></html></string>
|
||||
<string><html><head/><body><p>Or customize the command in the next input field. <br/>The following variables are replaced by GNS3: </p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%h or {host}: console IP or hostname</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%p or {port}: console port</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%D or {display}: VNC display</li></ul><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%d or {name}: title of the console</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%P or {project}: project name</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%i or {project_id}: project UUID</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%c or {url}: server URL (<span style=" font-style:italic;">http://user:password@server:port</span>)</li></ul></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
|
||||
@@ -2,16 +2,19 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/console_command_dialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.5.1
|
||||
# Created by: PyQt5 UI code generator 5.15.6
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_uiConsoleCommandDialog(object):
|
||||
def setupUi(self, uiConsoleCommandDialog):
|
||||
uiConsoleCommandDialog.setObjectName("uiConsoleCommandDialog")
|
||||
uiConsoleCommandDialog.resize(965, 547)
|
||||
uiConsoleCommandDialog.resize(576, 346)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@@ -57,8 +60,8 @@ class Ui_uiConsoleCommandDialog(object):
|
||||
self.verticalLayout.addWidget(self.uiButtonBox)
|
||||
|
||||
self.retranslateUi(uiConsoleCommandDialog)
|
||||
self.uiButtonBox.accepted.connect(uiConsoleCommandDialog.accept)
|
||||
self.uiButtonBox.rejected.connect(uiConsoleCommandDialog.reject)
|
||||
self.uiButtonBox.accepted.connect(uiConsoleCommandDialog.accept) # type: ignore
|
||||
self.uiButtonBox.rejected.connect(uiConsoleCommandDialog.reject) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(uiConsoleCommandDialog)
|
||||
|
||||
def retranslateUi(self, uiConsoleCommandDialog):
|
||||
@@ -67,5 +70,4 @@ class Ui_uiConsoleCommandDialog(object):
|
||||
self.label_2.setText(_translate("uiConsoleCommandDialog", "Choose a predefined command:"))
|
||||
self.uiRemovePushButton.setText(_translate("uiConsoleCommandDialog", "Remove"))
|
||||
self.uiSavePushButton.setText(_translate("uiConsoleCommandDialog", "Save"))
|
||||
self.label.setText(_translate("uiConsoleCommandDialog", "<html><head/><body><p>Or customize the command in the next input field. <br/>The following variables are replaced by GNS3: </p><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%h: console IP or hostname</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%p: console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%P: VNC display</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%s: path of the serial connection</li></ul><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d: title of the console</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i: Project UUID</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%c: server URL (<span style=\" font-style:italic;\">http://user:password@server:port</span>)</li></ul></body></html>"))
|
||||
|
||||
self.label.setText(_translate("uiConsoleCommandDialog", "<html><head/><body><p>Or customize the command in the next input field. <br/>The following variables are replaced by GNS3: </p><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%h or {host}: console IP or hostname</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%p or {port}: console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%D or {display}: VNC display</li></ul><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d or {name}: title of the console</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%P or {project}: project name</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i or {project_id}: project UUID</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%c or {url}: server URL (<span style=\" font-style:italic;\">http://user:password@server:port</span>)</li></ul></body></html>"))
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>900</width>
|
||||
<height>600</height>
|
||||
<width>602</width>
|
||||
<height>367</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -27,19 +27,6 @@
|
||||
<string>Please select the location, whether to include base images or not and the compression type.</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiPathLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Path:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
@@ -54,6 +41,26 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiResetMacAddressesCheckBox">
|
||||
<property name="text">
|
||||
<string>&Reset MAC addresses</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiPathLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Path:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiCompressionLabel">
|
||||
<property name="text">
|
||||
@@ -61,6 +68,19 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="2">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>247</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="2">
|
||||
<widget class="QComboBox" name="uiCompressionComboBox"/>
|
||||
</item>
|
||||
@@ -78,23 +98,10 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="2">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>247</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiResetMacAddressesCheckBox">
|
||||
<item row="6" column="0" colspan="3">
|
||||
<widget class="QCheckBox" name="uiKeepComputeIdsCheckBox">
|
||||
<property name="text">
|
||||
<string>&Reset MAC addresses</string>
|
||||
<string>&Keep the original compute IDs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/export_project_wizard.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.13.2
|
||||
# Created by: PyQt5 UI code generator 5.15.6
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@@ -14,20 +15,12 @@ class Ui_ExportProjectWizard(object):
|
||||
def setupUi(self, ExportProjectWizard):
|
||||
ExportProjectWizard.setObjectName("ExportProjectWizard")
|
||||
ExportProjectWizard.setWindowModality(QtCore.Qt.ApplicationModal)
|
||||
ExportProjectWizard.resize(900, 600)
|
||||
ExportProjectWizard.resize(602, 367)
|
||||
ExportProjectWizard.setOptions(QtWidgets.QWizard.HaveHelpButton)
|
||||
self.uiExportOptionsWizardPage = QtWidgets.QWizardPage()
|
||||
self.uiExportOptionsWizardPage.setObjectName("uiExportOptionsWizardPage")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self.uiExportOptionsWizardPage)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.uiPathLabel = QtWidgets.QLabel(self.uiExportOptionsWizardPage)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiPathLabel.sizePolicy().hasHeightForWidth())
|
||||
self.uiPathLabel.setSizePolicy(sizePolicy)
|
||||
self.uiPathLabel.setObjectName("uiPathLabel")
|
||||
self.gridLayout.addWidget(self.uiPathLabel, 0, 0, 1, 1)
|
||||
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
|
||||
self.uiPathLineEdit = QtWidgets.QLineEdit(self.uiExportOptionsWizardPage)
|
||||
@@ -37,9 +30,22 @@ class Ui_ExportProjectWizard(object):
|
||||
self.uiPathBrowserToolButton.setObjectName("uiPathBrowserToolButton")
|
||||
self.horizontalLayout_3.addWidget(self.uiPathBrowserToolButton)
|
||||
self.gridLayout.addLayout(self.horizontalLayout_3, 0, 1, 1, 2)
|
||||
self.uiResetMacAddressesCheckBox = QtWidgets.QCheckBox(self.uiExportOptionsWizardPage)
|
||||
self.uiResetMacAddressesCheckBox.setObjectName("uiResetMacAddressesCheckBox")
|
||||
self.gridLayout.addWidget(self.uiResetMacAddressesCheckBox, 5, 0, 1, 2)
|
||||
self.uiPathLabel = QtWidgets.QLabel(self.uiExportOptionsWizardPage)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiPathLabel.sizePolicy().hasHeightForWidth())
|
||||
self.uiPathLabel.setSizePolicy(sizePolicy)
|
||||
self.uiPathLabel.setObjectName("uiPathLabel")
|
||||
self.gridLayout.addWidget(self.uiPathLabel, 0, 0, 1, 1)
|
||||
self.uiCompressionLabel = QtWidgets.QLabel(self.uiExportOptionsWizardPage)
|
||||
self.uiCompressionLabel.setObjectName("uiCompressionLabel")
|
||||
self.gridLayout.addWidget(self.uiCompressionLabel, 1, 0, 1, 1)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 247, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout.addItem(spacerItem, 7, 2, 1, 1)
|
||||
self.uiCompressionComboBox = QtWidgets.QComboBox(self.uiExportOptionsWizardPage)
|
||||
self.uiCompressionComboBox.setObjectName("uiCompressionComboBox")
|
||||
self.gridLayout.addWidget(self.uiCompressionComboBox, 1, 1, 1, 2)
|
||||
@@ -49,11 +55,9 @@ class Ui_ExportProjectWizard(object):
|
||||
self.uiIncludeSnapshotsCheckBox = QtWidgets.QCheckBox(self.uiExportOptionsWizardPage)
|
||||
self.uiIncludeSnapshotsCheckBox.setObjectName("uiIncludeSnapshotsCheckBox")
|
||||
self.gridLayout.addWidget(self.uiIncludeSnapshotsCheckBox, 4, 0, 1, 2)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 247, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout.addItem(spacerItem, 6, 2, 1, 1)
|
||||
self.uiResetMacAddressesCheckBox = QtWidgets.QCheckBox(self.uiExportOptionsWizardPage)
|
||||
self.uiResetMacAddressesCheckBox.setObjectName("uiResetMacAddressesCheckBox")
|
||||
self.gridLayout.addWidget(self.uiResetMacAddressesCheckBox, 5, 0, 1, 2)
|
||||
self.uiKeepComputeIdsCheckBox = QtWidgets.QCheckBox(self.uiExportOptionsWizardPage)
|
||||
self.uiKeepComputeIdsCheckBox.setObjectName("uiKeepComputeIdsCheckBox")
|
||||
self.gridLayout.addWidget(self.uiKeepComputeIdsCheckBox, 6, 0, 1, 3)
|
||||
ExportProjectWizard.addPage(self.uiExportOptionsWizardPage)
|
||||
self.uiProjectReadmeWizardPage = QtWidgets.QWizardPage()
|
||||
self.uiProjectReadmeWizardPage.setObjectName("uiProjectReadmeWizardPage")
|
||||
@@ -72,12 +76,13 @@ class Ui_ExportProjectWizard(object):
|
||||
ExportProjectWizard.setWindowTitle(_translate("ExportProjectWizard", "Export project"))
|
||||
self.uiExportOptionsWizardPage.setTitle(_translate("ExportProjectWizard", "Export project"))
|
||||
self.uiExportOptionsWizardPage.setSubTitle(_translate("ExportProjectWizard", "Please select the location, whether to include base images or not and the compression type."))
|
||||
self.uiPathLabel.setText(_translate("ExportProjectWizard", "Path:"))
|
||||
self.uiPathBrowserToolButton.setText(_translate("ExportProjectWizard", "Browse..."))
|
||||
self.uiResetMacAddressesCheckBox.setText(_translate("ExportProjectWizard", "&Reset MAC addresses"))
|
||||
self.uiPathLabel.setText(_translate("ExportProjectWizard", "Path:"))
|
||||
self.uiCompressionLabel.setText(_translate("ExportProjectWizard", "Compression:"))
|
||||
self.uiIncludeImagesCheckBox.setText(_translate("ExportProjectWizard", "&Include base images"))
|
||||
self.uiIncludeSnapshotsCheckBox.setText(_translate("ExportProjectWizard", "&Include snapshots"))
|
||||
self.uiResetMacAddressesCheckBox.setText(_translate("ExportProjectWizard", "&Reset MAC addresses"))
|
||||
self.uiKeepComputeIdsCheckBox.setText(_translate("ExportProjectWizard", "&Keep the original compute IDs"))
|
||||
self.uiProjectReadmeWizardPage.setTitle(_translate("ExportProjectWizard", "Readme file"))
|
||||
self.uiProjectReadmeWizardPage.setSubTitle(_translate("ExportProjectWizard", "Write a summary of the project."))
|
||||
self.uiReadmeTextEdit.setHtml(_translate("ExportProjectWizard", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>941</width>
|
||||
<height>910</height>
|
||||
<width>512</width>
|
||||
<height>652</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -223,7 +223,16 @@
|
||||
<string>Binary images</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
@@ -373,7 +382,16 @@
|
||||
<string>Console applications</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
@@ -400,7 +418,7 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Command line replacements:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%h = console IP or hostname</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%p = console port</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%d = title of the console</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%i = project UUID</li><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%n = node UUID</li></ul><li style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%c = server URL</li></ul></body></html></string>
|
||||
<string><html><head/><body><p>Command line replacements:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%h or {host} = console IP or hostname</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%p or {port} = console port</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%d or {name} = title of the console (node name)</li><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%P or {project} = project</li></ul><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%i or {project_id} = project UUID</li><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%n or {node_id} = node UUID</li></ul><li style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%c or {url} = server URL</li></ul></body></html></string>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
@@ -474,7 +492,16 @@
|
||||
<string>VNC</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
@@ -501,7 +528,7 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Command line replacements:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%h = console IP or hostname</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%p = console port</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%P = VNC display</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%d = title of the console</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%i = project UUID</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%n = node UUID</li></ul></body></html></string>
|
||||
<string><html><head/><body><p>Command line replacements:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%h or {host} = console IP or hostname</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%p or {port} = console port</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%P or {display} = VNC display</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%d or {name} = node name</li><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%T or {project} = project name</li></ul><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%i or {project_id} = project UUID</li><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%n or {node_id} = node UUID</li></ul><li style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%c or {url} = server URL</li></ul></body></html></string>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
@@ -570,7 +597,7 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Command line replacements:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%h = console IP or hostname</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%p = console port</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%d = title of the console</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%i = project UUID</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%n = node UUID</li></ul><p><br/></p></body></html></string>
|
||||
<string><html><head/><body><p>Command line replacements:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%h or {host} = console IP or hostname</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%p or {port} = console port</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%d or {name} = node name</li><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%P or {project} = project name</li></ul><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%i or {project_id} = project UUID</li><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%n or {node_id} = node UUID</li></ul><li style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%c or {url} = server URL</li></ul></body></html></string>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
@@ -943,7 +970,16 @@
|
||||
<string>Miscellaneous</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
@@ -966,16 +1002,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="uiStatsCheckBox">
|
||||
<property name="text">
|
||||
<string>Send anonymous usage statistics</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="uiOverlayNotificationsCheckBox">
|
||||
<property name="text">
|
||||
@@ -986,7 +1012,7 @@
|
||||
<item>
|
||||
<widget class="QCheckBox" name="uiExperimentalFeaturesCheckBox">
|
||||
<property name="text">
|
||||
<string>Enable experimental features (dangerous, restart required)</string>
|
||||
<string>Enable experimental features</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -1098,7 +1124,6 @@
|
||||
<tabstop>uiDefaultNoteColorPushButton</tabstop>
|
||||
<tabstop>uiCheckForUpdateCheckBox</tabstop>
|
||||
<tabstop>uiCrashReportCheckBox</tabstop>
|
||||
<tabstop>uiStatsCheckBox</tabstop>
|
||||
<tabstop>uiOverlayNotificationsCheckBox</tabstop>
|
||||
<tabstop>uiExperimentalFeaturesCheckBox</tabstop>
|
||||
<tabstop>uiHdpiCheckBox</tabstop>
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/general_preferences_page.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.13.2
|
||||
# Created by: PyQt5 UI code generator 5.15.6
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@@ -13,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
class Ui_GeneralPreferencesPageWidget(object):
|
||||
def setupUi(self, GeneralPreferencesPageWidget):
|
||||
GeneralPreferencesPageWidget.setObjectName("GeneralPreferencesPageWidget")
|
||||
GeneralPreferencesPageWidget.resize(941, 910)
|
||||
GeneralPreferencesPageWidget.resize(512, 652)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(GeneralPreferencesPageWidget)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiMiscTabWidget = QtWidgets.QTabWidget(GeneralPreferencesPageWidget)
|
||||
@@ -446,10 +447,6 @@ class Ui_GeneralPreferencesPageWidget(object):
|
||||
self.uiCrashReportCheckBox.setChecked(True)
|
||||
self.uiCrashReportCheckBox.setObjectName("uiCrashReportCheckBox")
|
||||
self.verticalLayout_2.addWidget(self.uiCrashReportCheckBox)
|
||||
self.uiStatsCheckBox = QtWidgets.QCheckBox(self.uiMiscTab)
|
||||
self.uiStatsCheckBox.setChecked(True)
|
||||
self.uiStatsCheckBox.setObjectName("uiStatsCheckBox")
|
||||
self.verticalLayout_2.addWidget(self.uiStatsCheckBox)
|
||||
self.uiOverlayNotificationsCheckBox = QtWidgets.QCheckBox(self.uiMiscTab)
|
||||
self.uiOverlayNotificationsCheckBox.setObjectName("uiOverlayNotificationsCheckBox")
|
||||
self.verticalLayout_2.addWidget(self.uiOverlayNotificationsCheckBox)
|
||||
@@ -520,8 +517,7 @@ class Ui_GeneralPreferencesPageWidget(object):
|
||||
GeneralPreferencesPageWidget.setTabOrder(self.uiDefaultNoteFontPushButton, self.uiDefaultNoteColorPushButton)
|
||||
GeneralPreferencesPageWidget.setTabOrder(self.uiDefaultNoteColorPushButton, self.uiCheckForUpdateCheckBox)
|
||||
GeneralPreferencesPageWidget.setTabOrder(self.uiCheckForUpdateCheckBox, self.uiCrashReportCheckBox)
|
||||
GeneralPreferencesPageWidget.setTabOrder(self.uiCrashReportCheckBox, self.uiStatsCheckBox)
|
||||
GeneralPreferencesPageWidget.setTabOrder(self.uiStatsCheckBox, self.uiOverlayNotificationsCheckBox)
|
||||
GeneralPreferencesPageWidget.setTabOrder(self.uiCrashReportCheckBox, self.uiOverlayNotificationsCheckBox)
|
||||
GeneralPreferencesPageWidget.setTabOrder(self.uiOverlayNotificationsCheckBox, self.uiExperimentalFeaturesCheckBox)
|
||||
GeneralPreferencesPageWidget.setTabOrder(self.uiExperimentalFeaturesCheckBox, self.uiHdpiCheckBox)
|
||||
GeneralPreferencesPageWidget.setTabOrder(self.uiHdpiCheckBox, self.uiMultiProfilesCheckBox)
|
||||
@@ -562,7 +558,7 @@ class Ui_GeneralPreferencesPageWidget(object):
|
||||
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.uiImagesTab), _translate("GeneralPreferencesPageWidget", "Binary images"))
|
||||
self.uiTelnetConsoleSettingsGroupBox.setTitle(_translate("GeneralPreferencesPageWidget", "Console settings"))
|
||||
self.uiTelnetConsoleCommandLabel.setText(_translate("GeneralPreferencesPageWidget", "Console application command for Telnet:"))
|
||||
self.uiTelnetConsoleCommandLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%h = console IP or hostname</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%p = console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d = title of the console</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i = project UUID</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%n = node UUID</li></ul><li style=\" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%c = server URL</li></ul></body></html>"))
|
||||
self.uiTelnetConsoleCommandLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%h or {host} = console IP or hostname</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%p or {port} = console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d or {name} = title of the console (node name)</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%P or {project} = project</li></ul><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i or {project_id} = project UUID</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%n or {node_id} = node UUID</li></ul><li style=\" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%c or {url} = server URL</li></ul></body></html>"))
|
||||
self.uiTelnetConsolePreconfiguredCommandPushButton.setText(_translate("GeneralPreferencesPageWidget", "&Edit"))
|
||||
self.uiConsoleMiscGroupBox.setTitle(_translate("GeneralPreferencesPageWidget", "Miscellaneous"))
|
||||
self.uiDelayConsoleAllSpinBox.setSuffix(_translate("GeneralPreferencesPageWidget", " ms"))
|
||||
@@ -570,12 +566,12 @@ class Ui_GeneralPreferencesPageWidget(object):
|
||||
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.uiConsoleTab), _translate("GeneralPreferencesPageWidget", "Console applications"))
|
||||
self.uiVNCConsoleSettingsGroupBox.setTitle(_translate("GeneralPreferencesPageWidget", "Settings for VNC connections"))
|
||||
self.uiVNCConsoleCommandLabel.setText(_translate("GeneralPreferencesPageWidget", "Console application command for VNC:"))
|
||||
self.uiVNCConsoleCommandLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%h = console IP or hostname</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%p = console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%P = VNC display</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d = title of the console</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i = project UUID</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%n = node UUID</li></ul></body></html>"))
|
||||
self.uiVNCConsoleCommandLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%h or {host} = console IP or hostname</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%p or {port} = console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%P or {display} = VNC display</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d or {name} = node name</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%T or {project} = project name</li></ul><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i or {project_id} = project UUID</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%n or {node_id} = node UUID</li></ul><li style=\" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%c or {url} = server URL</li></ul></body></html>"))
|
||||
self.uiVNCConsolePreconfiguredCommandPushButton.setText(_translate("GeneralPreferencesPageWidget", "&Edit"))
|
||||
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.uiVNCTab), _translate("GeneralPreferencesPageWidget", "VNC"))
|
||||
self.uiSPICEConsoleSettingsGroupBox.setTitle(_translate("GeneralPreferencesPageWidget", "Settings for SPICE connections"))
|
||||
self.uiSPICEConsoleCommandLabel.setText(_translate("GeneralPreferencesPageWidget", "Console application command for SPICE:"))
|
||||
self.uiSPICEConsoleCommandLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%h = console IP or hostname</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%p = console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d = title of the console</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i = project UUID</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%n = node UUID</li></ul><p><br/></p></body></html>"))
|
||||
self.uiSPICEConsoleCommandLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%h or {host} = console IP or hostname</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%p or {port} = console port</li><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%d or {name} = node name</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%P or {project} = project name</li></ul><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%i or {project_id} = project UUID</li><ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%n or {node_id} = node UUID</li></ul><li style=\" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%c or {url} = server URL</li></ul></body></html>"))
|
||||
self.uiSPICEConsolePreconfiguredCommandPushButton.setText(_translate("GeneralPreferencesPageWidget", "&Edit"))
|
||||
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.uiSPICETab), _translate("GeneralPreferencesPageWidget", "SPICE"))
|
||||
self.uiSceneWidthLabel.setText(_translate("GeneralPreferencesPageWidget", "Default width:"))
|
||||
@@ -601,9 +597,8 @@ class Ui_GeneralPreferencesPageWidget(object):
|
||||
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.uiSceneTab), _translate("GeneralPreferencesPageWidget", "Topology view"))
|
||||
self.uiCheckForUpdateCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Automatically check for update"))
|
||||
self.uiCrashReportCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Send anonymous crash reports"))
|
||||
self.uiStatsCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Send anonymous usage statistics"))
|
||||
self.uiOverlayNotificationsCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Display error, warning and info in an overlay popup"))
|
||||
self.uiExperimentalFeaturesCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Enable experimental features (dangerous, restart required)"))
|
||||
self.uiExperimentalFeaturesCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Enable experimental features"))
|
||||
self.uiHdpiCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Enable HDPI mode (this may crash on Linux, restart required)"))
|
||||
self.uiMultiProfilesCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Request for profile settings at application startup"))
|
||||
self.uiDirectFileUpload.setToolTip(_translate("GeneralPreferencesPageWidget", "Experimental, requires computes visibility from GUI network"))
|
||||
|
||||
@@ -112,6 +112,7 @@ background-none;
|
||||
<addaction name="uiAcademyAction"/>
|
||||
<addaction name="uiDoctorAction"/>
|
||||
<addaction name="uiExportDebugInformationAction"/>
|
||||
<addaction name="uiShortcutsAction"/>
|
||||
<addaction name="uiAboutQtAction"/>
|
||||
<addaction name="uiAboutAction"/>
|
||||
</widget>
|
||||
@@ -138,6 +139,7 @@ background-none;
|
||||
<addaction name="uiShowPortNamesAction"/>
|
||||
<addaction name="uiLockAllAction"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="uiResetGUIStateAction"/>
|
||||
<addaction name="uiResetDocksAction"/>
|
||||
<addaction name="uiDocksMenu"/>
|
||||
</widget>
|
||||
@@ -643,6 +645,9 @@ background-none;
|
||||
<property name="statusTip">
|
||||
<string>Start/Resume all devices</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+B</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="uiStopAllAction">
|
||||
<property name="enabled">
|
||||
@@ -662,6 +667,9 @@ background-none;
|
||||
<property name="statusTip">
|
||||
<string>Stop all devices</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+E</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="uiConsoleAllAction">
|
||||
<property name="enabled">
|
||||
@@ -796,6 +804,9 @@ background-none;
|
||||
<property name="statusTip">
|
||||
<string>Suspend all devices</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+J</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="uiAddNoteAction">
|
||||
<property name="checkable">
|
||||
@@ -1141,11 +1152,17 @@ background-none;
|
||||
<property name="statusTip">
|
||||
<string>Add a link</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+L</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="uiFitInViewAction">
|
||||
<property name="text">
|
||||
<string>Fit in view</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+1</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="uiActionFullscreen">
|
||||
<property name="text">
|
||||
@@ -1300,6 +1317,16 @@ background-none;
|
||||
<string>Reset all console connections</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="uiResetGUIStateAction">
|
||||
<property name="text">
|
||||
<string>Reset GUI state</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="uiShortcutsAction">
|
||||
<property name="text">
|
||||
<string>&Shortcuts</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/main_window.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.14.1
|
||||
# Created by: PyQt5 UI code generator 5.15.10
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@@ -14,7 +15,7 @@ class Ui_MainWindow(object):
|
||||
def setupUi(self, MainWindow):
|
||||
MainWindow.setObjectName("MainWindow")
|
||||
MainWindow.setWindowModality(QtCore.Qt.NonModal)
|
||||
MainWindow.resize(986, 716)
|
||||
MainWindow.resize(986, 719)
|
||||
MainWindow.setContextMenuPolicy(QtCore.Qt.PreventContextMenu)
|
||||
MainWindow.setStyleSheet("#toolBar_Devices QToolButton {\n"
|
||||
"width: 50px;\n"
|
||||
@@ -450,11 +451,12 @@ class Ui_MainWindow(object):
|
||||
self.uiNewTemplateAction.setObjectName("uiNewTemplateAction")
|
||||
self.uiResetDocksAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiResetDocksAction.setObjectName("uiResetDocksAction")
|
||||
self.uiEditReadmeAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiEditReadmeAction.setIcon(icon31)
|
||||
self.uiEditReadmeAction.setObjectName("uiEditReadmeAction")
|
||||
self.uiResetConsoleAllAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiResetConsoleAllAction.setObjectName("uiResetConsoleAllAction")
|
||||
self.uiResetGUIStateAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiResetGUIStateAction.setObjectName("uiResetGUIStateAction")
|
||||
self.uiShortcutsAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiShortcutsAction.setObjectName("uiShortcutsAction")
|
||||
self.uiEditMenu.addAction(self.uiSelectAllAction)
|
||||
self.uiEditMenu.addAction(self.uiSelectNoneAction)
|
||||
self.uiEditMenu.addSeparator()
|
||||
@@ -479,6 +481,7 @@ class Ui_MainWindow(object):
|
||||
self.uiHelpMenu.addAction(self.uiAcademyAction)
|
||||
self.uiHelpMenu.addAction(self.uiDoctorAction)
|
||||
self.uiHelpMenu.addAction(self.uiExportDebugInformationAction)
|
||||
self.uiHelpMenu.addAction(self.uiShortcutsAction)
|
||||
self.uiHelpMenu.addAction(self.uiAboutQtAction)
|
||||
self.uiHelpMenu.addAction(self.uiAboutAction)
|
||||
self.uiViewMenu.addAction(self.uiActionFullscreen)
|
||||
@@ -495,6 +498,7 @@ class Ui_MainWindow(object):
|
||||
self.uiViewMenu.addAction(self.uiShowPortNamesAction)
|
||||
self.uiViewMenu.addAction(self.uiLockAllAction)
|
||||
self.uiViewMenu.addSeparator()
|
||||
self.uiViewMenu.addAction(self.uiResetGUIStateAction)
|
||||
self.uiViewMenu.addAction(self.uiResetDocksAction)
|
||||
self.uiViewMenu.addAction(self.uiDocksMenu.menuAction())
|
||||
self.uiControlMenu.addAction(self.uiStartAllAction)
|
||||
@@ -554,7 +558,7 @@ class Ui_MainWindow(object):
|
||||
self.uiAnnotationToolBar.addAction(self.uiScreenshotAction)
|
||||
|
||||
self.retranslateUi(MainWindow)
|
||||
self.uiQuitAction.triggered.connect(MainWindow.close)
|
||||
self.uiQuitAction.triggered.connect(MainWindow.close) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
||||
MainWindow.setTabOrder(self.uiGraphicsView, self.uiNodesView)
|
||||
MainWindow.setTabOrder(self.uiNodesView, self.uiConsoleTextEdit)
|
||||
@@ -603,9 +607,11 @@ class Ui_MainWindow(object):
|
||||
self.uiStartAllAction.setText(_translate("MainWindow", "Start/Resume all nodes"))
|
||||
self.uiStartAllAction.setToolTip(_translate("MainWindow", "Start/Resume all nodes"))
|
||||
self.uiStartAllAction.setStatusTip(_translate("MainWindow", "Start/Resume all devices"))
|
||||
self.uiStartAllAction.setShortcut(_translate("MainWindow", "Ctrl+B"))
|
||||
self.uiStopAllAction.setText(_translate("MainWindow", "Stop all nodes"))
|
||||
self.uiStopAllAction.setToolTip(_translate("MainWindow", "Stop all nodes"))
|
||||
self.uiStopAllAction.setStatusTip(_translate("MainWindow", "Stop all devices"))
|
||||
self.uiStopAllAction.setShortcut(_translate("MainWindow", "Ctrl+E"))
|
||||
self.uiConsoleAllAction.setText(_translate("MainWindow", "Console connect to all nodes"))
|
||||
self.uiConsoleAllAction.setToolTip(_translate("MainWindow", "Console connect to all nodes"))
|
||||
self.uiConsoleAllAction.setStatusTip(_translate("MainWindow", "Console to all devices"))
|
||||
@@ -634,6 +640,7 @@ class Ui_MainWindow(object):
|
||||
self.uiSuspendAllAction.setText(_translate("MainWindow", "Suspend all nodes"))
|
||||
self.uiSuspendAllAction.setToolTip(_translate("MainWindow", "Suspend all nodes"))
|
||||
self.uiSuspendAllAction.setStatusTip(_translate("MainWindow", "Suspend all devices"))
|
||||
self.uiSuspendAllAction.setShortcut(_translate("MainWindow", "Ctrl+J"))
|
||||
self.uiAddNoteAction.setText(_translate("MainWindow", "Add note"))
|
||||
self.uiAddNoteAction.setToolTip(_translate("MainWindow", "Add a note"))
|
||||
self.uiAddNoteAction.setStatusTip(_translate("MainWindow", "Add a note"))
|
||||
@@ -700,7 +707,9 @@ class Ui_MainWindow(object):
|
||||
self.uiAddLinkAction.setText(_translate("MainWindow", "Add a link"))
|
||||
self.uiAddLinkAction.setToolTip(_translate("MainWindow", "Add a link"))
|
||||
self.uiAddLinkAction.setStatusTip(_translate("MainWindow", "Add a link"))
|
||||
self.uiAddLinkAction.setShortcut(_translate("MainWindow", "Ctrl+L"))
|
||||
self.uiFitInViewAction.setText(_translate("MainWindow", "Fit in view"))
|
||||
self.uiFitInViewAction.setShortcut(_translate("MainWindow", "Ctrl+1"))
|
||||
self.uiActionFullscreen.setText(_translate("MainWindow", "Fullscreen"))
|
||||
self.uiActionFullscreen.setShortcut(_translate("MainWindow", "Ctrl+F"))
|
||||
self.uiSetupWizard.setText(_translate("MainWindow", "&Setup Wizard"))
|
||||
@@ -721,9 +730,9 @@ class Ui_MainWindow(object):
|
||||
self.uiWebUIAction.setText(_translate("MainWindow", "Web UI - beta"))
|
||||
self.uiNewTemplateAction.setText(_translate("MainWindow", "New template"))
|
||||
self.uiResetDocksAction.setText(_translate("MainWindow", "Reset docks"))
|
||||
self.uiEditReadmeAction.setText(_translate("MainWindow", "Edit readme"))
|
||||
self.uiEditReadmeAction.setToolTip(_translate("MainWindow", "Edit readme"))
|
||||
self.uiResetConsoleAllAction.setText(_translate("MainWindow", "Reset all console connections"))
|
||||
self.uiResetGUIStateAction.setText(_translate("MainWindow", "Reset GUI state"))
|
||||
self.uiShortcutsAction.setText(_translate("MainWindow", "&Shortcuts"))
|
||||
from ..compute_summary_view import ComputeSummaryView
|
||||
from ..console_view import ConsoleView
|
||||
from ..graphics_view import GraphicsView
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QLineEdit" name="uiCaptureReaderCommandLineEdit">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Command line replacements:</p><p>%c = capture file (PCAP format)</p></body></html></string>
|
||||
<string><html><head/><body><p>Command line replacements:</p><p>%c or {pcap_file} = capture file (PCAP format)</p><p>%P or {project} = project name</p><p>%d or {link_description} = link description</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/packet_capture_preferences_page.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.9
|
||||
# Created by: PyQt5 UI code generator 5.15.6
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_PacketCapturePreferencesPageWidget(object):
|
||||
def setupUi(self, PacketCapturePreferencesPageWidget):
|
||||
PacketCapturePreferencesPageWidget.setObjectName("PacketCapturePreferencesPageWidget")
|
||||
@@ -64,6 +67,11 @@ class Ui_PacketCapturePreferencesPageWidget(object):
|
||||
|
||||
self.retranslateUi(PacketCapturePreferencesPageWidget)
|
||||
QtCore.QMetaObject.connectSlotsByName(PacketCapturePreferencesPageWidget)
|
||||
PacketCapturePreferencesPageWidget.setTabOrder(self.uiPreconfiguredCaptureReaderCommandComboBox, self.uiPreconfiguredCaptureReaderCommandPushButton)
|
||||
PacketCapturePreferencesPageWidget.setTabOrder(self.uiPreconfiguredCaptureReaderCommandPushButton, self.uiCaptureReaderCommandLineEdit)
|
||||
PacketCapturePreferencesPageWidget.setTabOrder(self.uiCaptureReaderCommandLineEdit, self.uiAutoStartCheckBox)
|
||||
PacketCapturePreferencesPageWidget.setTabOrder(self.uiAutoStartCheckBox, self.uiCaptureAnalyzerCommandLineEdit)
|
||||
PacketCapturePreferencesPageWidget.setTabOrder(self.uiCaptureAnalyzerCommandLineEdit, self.uiRestoreDefaultsPushButton)
|
||||
|
||||
def retranslateUi(self, PacketCapturePreferencesPageWidget):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
@@ -74,6 +82,5 @@ class Ui_PacketCapturePreferencesPageWidget(object):
|
||||
self.uiCaptureReaderCommandLabel.setText(_translate("PacketCapturePreferencesPageWidget", "Packet capture reader command:"))
|
||||
self.uiAutoStartCheckBox.setText(_translate("PacketCapturePreferencesPageWidget", "Automatically start the packet capture application"))
|
||||
self.uiCaptureAnalyzerCommandLabel.setText(_translate("PacketCapturePreferencesPageWidget", "Packet capture analyzer command:"))
|
||||
self.uiCaptureReaderCommandLineEdit.setToolTip(_translate("PacketCapturePreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><p>%c = capture file (PCAP format)</p></body></html>"))
|
||||
self.uiCaptureReaderCommandLineEdit.setToolTip(_translate("PacketCapturePreferencesPageWidget", "<html><head/><body><p>Command line replacements:</p><p>%c or {pcap_file} = capture file (PCAP format)</p><p>%P or {project} = project name</p><p>%d or {link_description} = link description</p></body></html>"))
|
||||
self.uiRestoreDefaultsPushButton.setText(_translate("PacketCapturePreferencesPageWidget", "Restore defaults"))
|
||||
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
<ui version="4.0">
|
||||
<class>StyleEditorDialog</class>
|
||||
<widget class="QDialog" name="StyleEditorDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>288</width>
|
||||
<height>358</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Style editor</string>
|
||||
</property>
|
||||
@@ -76,14 +84,14 @@
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="uiBorderStyleComboBox"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="uiRotationLabel">
|
||||
<property name="text">
|
||||
<string>Rotation:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<item row="7" column="1">
|
||||
<widget class="QSpinBox" name="uiRotationSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
@@ -106,6 +114,63 @@ editing (notes only) with ALT and '+' (or P) / ALT and '-' (or M)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="uiCornerRadiusLabel">
|
||||
<property name="text">
|
||||
<string>Corner radius:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QSpinBox" name="uiCornerRadiusSpinBox">
|
||||
<property name="suffix">
|
||||
<string>°</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="uiWidthLabel">
|
||||
<property name="text">
|
||||
<string>Width:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QSpinBox" name="uiWidthSpinBox">
|
||||
<property name="suffix">
|
||||
<string> px</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="uiHeightLabel">
|
||||
<property name="text">
|
||||
<string>Height:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QSpinBox" name="uiHeightSpinBox">
|
||||
<property name="suffix">
|
||||
<string> px</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/style_editor_dialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.2
|
||||
# Created by: PyQt5 UI code generator 5.15.6
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
@@ -14,6 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
class Ui_StyleEditorDialog(object):
|
||||
def setupUi(self, StyleEditorDialog):
|
||||
StyleEditorDialog.setObjectName("StyleEditorDialog")
|
||||
StyleEditorDialog.resize(288, 358)
|
||||
StyleEditorDialog.setModal(True)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(StyleEditorDialog)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
@@ -52,7 +53,7 @@ class Ui_StyleEditorDialog(object):
|
||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.uiBorderStyleComboBox)
|
||||
self.uiRotationLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox)
|
||||
self.uiRotationLabel.setObjectName("uiRotationLabel")
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.uiRotationLabel)
|
||||
self.formLayout.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.uiRotationLabel)
|
||||
self.uiRotationSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
@@ -62,7 +63,30 @@ class Ui_StyleEditorDialog(object):
|
||||
self.uiRotationSpinBox.setMinimum(-360)
|
||||
self.uiRotationSpinBox.setMaximum(360)
|
||||
self.uiRotationSpinBox.setObjectName("uiRotationSpinBox")
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.uiRotationSpinBox)
|
||||
self.formLayout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.uiRotationSpinBox)
|
||||
self.uiCornerRadiusLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox)
|
||||
self.uiCornerRadiusLabel.setObjectName("uiCornerRadiusLabel")
|
||||
self.formLayout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.uiCornerRadiusLabel)
|
||||
self.uiCornerRadiusSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox)
|
||||
self.uiCornerRadiusSpinBox.setMaximum(100)
|
||||
self.uiCornerRadiusSpinBox.setObjectName("uiCornerRadiusSpinBox")
|
||||
self.formLayout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.uiCornerRadiusSpinBox)
|
||||
self.uiWidthLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox)
|
||||
self.uiWidthLabel.setObjectName("uiWidthLabel")
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.uiWidthLabel)
|
||||
self.uiWidthSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox)
|
||||
self.uiWidthSpinBox.setMinimum(10)
|
||||
self.uiWidthSpinBox.setMaximum(1000)
|
||||
self.uiWidthSpinBox.setObjectName("uiWidthSpinBox")
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.uiWidthSpinBox)
|
||||
self.uiHeightLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox)
|
||||
self.uiHeightLabel.setObjectName("uiHeightLabel")
|
||||
self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.uiHeightLabel)
|
||||
self.uiHeightSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox)
|
||||
self.uiHeightSpinBox.setMinimum(10)
|
||||
self.uiHeightSpinBox.setMaximum(1000)
|
||||
self.uiHeightSpinBox.setObjectName("uiHeightSpinBox")
|
||||
self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.uiHeightSpinBox)
|
||||
self.verticalLayout.addWidget(self.uiStyleSettingsGroupBox)
|
||||
self.uiButtonBox = QtWidgets.QDialogButtonBox(StyleEditorDialog)
|
||||
self.uiButtonBox.setOrientation(QtCore.Qt.Horizontal)
|
||||
@@ -73,8 +97,8 @@ class Ui_StyleEditorDialog(object):
|
||||
self.verticalLayout.addItem(spacerItem)
|
||||
|
||||
self.retranslateUi(StyleEditorDialog)
|
||||
self.uiButtonBox.accepted.connect(StyleEditorDialog.accept)
|
||||
self.uiButtonBox.rejected.connect(StyleEditorDialog.reject)
|
||||
self.uiButtonBox.accepted.connect(StyleEditorDialog.accept) # type: ignore
|
||||
self.uiButtonBox.rejected.connect(StyleEditorDialog.reject) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(StyleEditorDialog)
|
||||
|
||||
def retranslateUi(self, StyleEditorDialog):
|
||||
@@ -90,4 +114,10 @@ class Ui_StyleEditorDialog(object):
|
||||
self.uiRotationSpinBox.setToolTip(_translate("StyleEditorDialog", "Rotation can be ajusted on the scene for a selected item while\n"
|
||||
"editing (notes only) with ALT and \'+\' (or P) / ALT and \'-\' (or M)"))
|
||||
self.uiRotationSpinBox.setSuffix(_translate("StyleEditorDialog", "°"))
|
||||
self.uiCornerRadiusLabel.setText(_translate("StyleEditorDialog", "Corner radius:"))
|
||||
self.uiCornerRadiusSpinBox.setSuffix(_translate("StyleEditorDialog", "°"))
|
||||
self.uiWidthLabel.setText(_translate("StyleEditorDialog", "Width:"))
|
||||
self.uiWidthSpinBox.setSuffix(_translate("StyleEditorDialog", " px"))
|
||||
self.uiHeightLabel.setText(_translate("StyleEditorDialog", "Height:"))
|
||||
self.uiHeightSpinBox.setSuffix(_translate("StyleEditorDialog", " px"))
|
||||
from . import resources_rc
|
||||
|
||||
@@ -24,7 +24,6 @@ import re
|
||||
|
||||
|
||||
from gns3.utils import parse_version
|
||||
|
||||
from gns3 import version
|
||||
from gns3.qt import QtNetwork, QtCore, QtWidgets, QtGui, qslot
|
||||
from gns3.local_config import LocalConfig
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2014 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import platform
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
from urllib.parse import quote
|
||||
from ..version import __version__
|
||||
from ..qt import QtCore, QtNetwork, QtWidgets
|
||||
from ..local_config import LocalConfig
|
||||
from ..settings import GENERAL_SETTINGS
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AnalyticsClient(QtCore.QObject):
|
||||
"""
|
||||
Google analytics client to send events.
|
||||
"""
|
||||
|
||||
_property_id = "UA-55817127-3"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._visitor_id = None
|
||||
self._manager = QtNetwork.QNetworkAccessManager(self)
|
||||
|
||||
def finished(network_reply):
|
||||
try:
|
||||
error = network_reply.error()
|
||||
except TypeError:
|
||||
# For unknow reason sometimes error is transform to a signal
|
||||
# we receive few crash report about that, but we are not able
|
||||
# to reproduce. We suspect the problem happen when the
|
||||
# application is closing.
|
||||
#
|
||||
# https://github.com/GNS3/gns3-gui/issues/2011
|
||||
return
|
||||
if error != QtNetwork.QNetworkReply.NoError:
|
||||
log.debug("Error when pushing to Google Analytics %s", network_reply.errorString())
|
||||
|
||||
self._manager.finished.connect(finished)
|
||||
|
||||
#
|
||||
# We need to build a user agent for Universal Analytics in order to
|
||||
# let analytics guess the OS
|
||||
# this could break by analytics at anytime :(
|
||||
if sys.platform.startswith("darwin"):
|
||||
self._user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X {release}) AppleWebKit/537.36 (KHTML, like Gecko) GNS3/{version}".format(release=platform.mac_ver()[0].replace(".", "_"), version=__version__)
|
||||
elif sys.platform.startswith("win"):
|
||||
self._user_agent = "Mozilla/5.0 (Windows NT {release}) AppleWebKit/537.36 (KHTML, like Gecko) Safari/537.36 GNS3/{version}".format(release=platform.release(), version=__version__)
|
||||
else:
|
||||
self._user_agent = "Mozilla/5.0 (X11; Linux {arch}) AppleWebKit/537.36 (KHTML, like Gecko) Safari/537.36 GNS3/{version}".format(arch=platform.machine(), version=__version__)
|
||||
self._rate_limit = {}
|
||||
|
||||
def sendScreenView(self, screen, session_start=None):
|
||||
"""
|
||||
:params session_start: True session start, None during session, False session stop
|
||||
"""
|
||||
|
||||
if session_start is not False and screen in self._rate_limit:
|
||||
if self._rate_limit[screen] + 60 * 1 > datetime.utcnow().timestamp():
|
||||
log.debug("Ignore call %s to Google Analytics because of rate limiting", screen)
|
||||
return
|
||||
|
||||
self._rate_limit[screen] = datetime.utcnow().timestamp()
|
||||
|
||||
settings = LocalConfig.instance().loadSectionSettings("MainWindow", GENERAL_SETTINGS)
|
||||
if settings["send_stats"] is False:
|
||||
log.debug("Stats is turn off ignore call %s", screen)
|
||||
return
|
||||
|
||||
body = "v=1" # Version
|
||||
body += "&tid={}".format(self._property_id) # Tracking ID / Property ID
|
||||
body += "&cid={}".format(settings["stats_visitor_id"]) # Anonymous Client ID
|
||||
body += "&aip=1" # Anonymize IP
|
||||
body += "&t=screenview" # Screenview hit type
|
||||
body += "&an=GNS3" # App name
|
||||
body += "&av={}".format(quote(__version__)) # App version.
|
||||
body += "&ua={}".format(quote(self._user_agent)) # User agent
|
||||
body += "&cd={}".format(quote(screen)) # Category
|
||||
body += "&ds=gns3-gui" # Data source
|
||||
if session_start is True:
|
||||
body += "&sc=start" # Session start
|
||||
elif session_start is False:
|
||||
body += "&sc=end" # Session end
|
||||
|
||||
screen = QtWidgets.QApplication.desktop().screenGeometry()
|
||||
body += "&sr={}x{}".format(screen.width(), screen.height()) # Screen resolution
|
||||
|
||||
locale = QtCore.QLocale.system().name().lower()
|
||||
if locale:
|
||||
body += "&ul={}".format(locale) # User language
|
||||
|
||||
# TODO: HTTPS when possible because it's broken for the moment with Qt on OSX:
|
||||
# https://bugreports.qt.io/browse/QTBUG-45487
|
||||
if sys.platform.startswith("darwin"):
|
||||
url = QtCore.QUrl('http://www.google-analytics.com/collect')
|
||||
else:
|
||||
url = QtCore.QUrl('https://www.google-analytics.com/collect')
|
||||
request_qt = QtNetwork.QNetworkRequest(url)
|
||||
request_qt.setRawHeader(b"Content-Type", b"application/x-www-form-urlencoded")
|
||||
request_qt.setRawHeader(b"User-Agent", self._user_agent.encode())
|
||||
self._manager.post(request_qt, body.encode())
|
||||
|
||||
log.debug("Send stats to Google Analytics: %s", body)
|
||||
|
||||
@staticmethod
|
||||
def instance():
|
||||
"""
|
||||
Singleton to return only on instance of AnalyticsClient.
|
||||
:returns: instance of AnalyticsClient
|
||||
"""
|
||||
|
||||
if not hasattr(AnalyticsClient, '_instance') or AnalyticsClient._instance is None:
|
||||
AnalyticsClient._instance = AnalyticsClient()
|
||||
return AnalyticsClient._instance
|
||||
38
gns3/utils/authorize_ubridge.py
Normal file
38
gns3/utils/authorize_ubridge.py
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2023 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# This script is intended to be built as a small executable for macOS to set the correct permissions on uBridge
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
|
||||
def authorize_ubridge():
|
||||
|
||||
path = shutil.which("ubridge", path=os.path.dirname(sys.executable))
|
||||
if path is None:
|
||||
raise SystemExit("Could not find ubridge executable at {}".format(path))
|
||||
try:
|
||||
shutil.chown(path, "root", "admin")
|
||||
os.chmod(path, 0o4750)
|
||||
except OSError as e:
|
||||
raise SystemExit("Could not authorize {}: {}".format(path, str(e)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
authorize_ubridge()
|
||||
@@ -28,21 +28,24 @@ class ExportProjectWorker(QtCore.QObject):
|
||||
finished = QtCore.pyqtSignal()
|
||||
updated = QtCore.pyqtSignal(int)
|
||||
|
||||
def __init__(self, project, path, include_images, include_snapshots, reset_mac_addresses, compression):
|
||||
def __init__(self, project, path, include_images, include_snapshots, reset_mac_addresses, keep_compute_ids, compression):
|
||||
super().__init__()
|
||||
self._project = project
|
||||
self._path = path
|
||||
self._include_images = include_images
|
||||
self._include_snapshots = include_snapshots
|
||||
self._reset_mac_addresses = reset_mac_addresses
|
||||
self._path = path
|
||||
self._keep_compute_ids = keep_compute_ids
|
||||
self._compression = compression
|
||||
|
||||
def run(self):
|
||||
if self._project:
|
||||
self._project.get("/export?include_images={}&include_snapshots={}&reset_mac_addresses={}&compression={}".format(self._include_images, self._include_snapshots, self._reset_mac_addresses, self._compression),
|
||||
self._exportReceived,
|
||||
downloadProgressCallback=self._downloadFileProgress,
|
||||
timeout=None)
|
||||
self._project.get(
|
||||
"/export?include_images={}&include_snapshots={}&reset_mac_addresses={}&keep_compute_ids={}&compression={}".format(self._include_images, self._include_snapshots, self._reset_mac_addresses, self._keep_compute_ids, self._compression),
|
||||
self._exportReceived,
|
||||
downloadProgressCallback=self._downloadFileProgress,
|
||||
timeout=None
|
||||
)
|
||||
|
||||
def _exportReceived(self, content, error=False, server=None, context={}, **kwargs):
|
||||
if error:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2014 GNS3 Technologies Inc.
|
||||
# Copyright (C) 2023 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -15,50 +15,35 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import tempfile
|
||||
import pkg_resources
|
||||
import atexit
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
import importlib_resources
|
||||
except ImportError:
|
||||
from importlib import resources as importlib_resources
|
||||
|
||||
|
||||
from contextlib import ExitStack
|
||||
resource_manager = ExitStack()
|
||||
atexit.register(resource_manager.close)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
egg_cache_dir = tempfile.mkdtemp()
|
||||
pkg_resources.set_extraction_path(egg_cache_dir)
|
||||
except ValueError:
|
||||
# If the path is already set the module throw an error
|
||||
pass
|
||||
|
||||
|
||||
@atexit.register
|
||||
def clean_egg_cache():
|
||||
try:
|
||||
import shutil
|
||||
shutil.rmtree(egg_cache_dir, ignore_errors=True)
|
||||
except Exception:
|
||||
# We don't care if we can not cleanup
|
||||
pass
|
||||
|
||||
|
||||
def get_resource(resource_name):
|
||||
"""
|
||||
Return a resource in current directory or in frozen package
|
||||
"""
|
||||
|
||||
resource_path = None
|
||||
if hasattr(sys, "frozen"):
|
||||
resource_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable), resource_name))
|
||||
if sys.platform.startswith("darwin") and not os.path.exists(resource_path):
|
||||
resource_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable), "lib", resource_name))
|
||||
elif not hasattr(sys, "frozen"):
|
||||
if pkg_resources.resource_exists("gns3", resource_name):
|
||||
try:
|
||||
resource_path = pkg_resources.resource_filename("gns3", resource_name)
|
||||
except pkg_resources.ExtractionError as e:
|
||||
log.fatal(e)
|
||||
sys.stderr.write(e)
|
||||
sys.exit(1)
|
||||
resource_path = os.path.normpath(resource_path)
|
||||
else:
|
||||
resource_path = os.path.dirname(os.path.realpath(__file__))
|
||||
resource_path = os.path.join(resource_path, "..", "..", "resources", resource_name)
|
||||
else:
|
||||
ref = importlib_resources.files("gns3") / resource_name
|
||||
path = resource_manager.enter_context(importlib_resources.as_file(ref))
|
||||
if os.path.exists(path):
|
||||
resource_path = os.path.normpath(path)
|
||||
return resource_path
|
||||
|
||||
64
gns3/utils/macos_ubridge_setuid.py
Normal file
64
gns3/utils/macos_ubridge_setuid.py
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2023 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def macos_ubridge_setuid():
|
||||
|
||||
# AuthorizationExecuteWithPrivileges() has been deprecated since macOS 10.7 but it still works
|
||||
# and much simpler than using SMJobBless() which requires a separate helper tool
|
||||
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
from ctypes import byref
|
||||
|
||||
authorize_ubridge = shutil.which("authorize_ubridge", path=os.path.dirname(sys.executable))
|
||||
if authorize_ubridge is None:
|
||||
raise OSError("Could not find the authorize_ubridge executable")
|
||||
|
||||
# https://developer.apple.com/documentation/security
|
||||
sec = ctypes.cdll.LoadLibrary(ctypes.util.find_library("Security"))
|
||||
|
||||
try:
|
||||
sec.AuthorizationCreate
|
||||
except AttributeError:
|
||||
raise OSError("macOS security library does not support AuthorizationCreate")
|
||||
|
||||
kAuthorizationFlagDefaults = 0
|
||||
auth = ctypes.c_void_p()
|
||||
r_auth = byref(auth)
|
||||
err = sec.AuthorizationCreate(None, None, kAuthorizationFlagDefaults, r_auth)
|
||||
if err:
|
||||
raise OSError("Could not create authorization: {}".format(err))
|
||||
|
||||
exe = [authorize_ubridge]
|
||||
log.info("Executing '{}' with privileges".format(exe))
|
||||
args = (ctypes.c_char_p * len(exe))()
|
||||
for i, arg in enumerate(exe[1:]):
|
||||
args[i] = arg.encode('utf8')
|
||||
io = ctypes.c_void_p()
|
||||
err = sec.AuthorizationExecuteWithPrivileges(auth, exe[0].encode('utf8'), 0, args, byref(io))
|
||||
if err:
|
||||
raise OSError("Could not authorize uBridge: {}".format(err))
|
||||
else:
|
||||
log.info("Successfully authorized uBridge")
|
||||
@@ -15,9 +15,9 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from gns3.qt import QtWidgets
|
||||
from gns3.utils.progress_dialog import ProgressDialog
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
# or negative for a release candidate or beta (after the base version
|
||||
# number has been incremented)
|
||||
|
||||
__version__ = "2.2.37"
|
||||
__version_info__ = (2, 2, 37, 0)
|
||||
__version__ = "2.2.54"
|
||||
__version_info__ = (2, 2, 54, 0)
|
||||
|
||||
if "dev" in __version__:
|
||||
try:
|
||||
|
||||
@@ -48,16 +48,27 @@ def vncConsole(node, port, command):
|
||||
# replace the place-holders by the actual values
|
||||
command = command.replace("%h", host)
|
||||
command = command.replace("%p", str(port))
|
||||
command = command.replace("%P", str(port - 5900))
|
||||
command = command.replace("%D", str(port - 5900))
|
||||
command = command.replace("%d", name.replace('"', '\\"'))
|
||||
command = command.replace("%P", node.project().name().replace('"', '\\"'))
|
||||
command = command.replace("%i", node.project().id())
|
||||
command = command.replace("%n", str(node.id()))
|
||||
command = command.replace("%c", Controller.instance().httpClient().fullUrl())
|
||||
|
||||
command = command.replace("{host}", host)
|
||||
command = command.replace("{port}", str(port))
|
||||
command = command.replace("{display}", str(port - 5900))
|
||||
command = command.replace("{name}", name.replace('"', '\\"'))
|
||||
command = command.replace("{project}", node.project().name().replace('"', '\\"'))
|
||||
command = command.replace("{project_id}", node.project().id())
|
||||
command = command.replace("{node_id}", str(node.id()))
|
||||
command = command.replace("{url}", Controller.instance().httpClient().fullUrl())
|
||||
|
||||
try:
|
||||
log.debug('starting VNC program "{}"'.format(command))
|
||||
if sys.platform.startswith("win"):
|
||||
# use the string on Windows
|
||||
subprocess.Popen(command)
|
||||
subprocess.Popen(command, env=os.environ)
|
||||
else:
|
||||
# use arguments on other platforms
|
||||
args = shlex.split(command)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
-rrequirements.txt
|
||||
|
||||
PyQt5==5.15.7
|
||||
PyQt5==5.15.11
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
jsonschema>=4.17.3,<4.18; python_version >= '3.7'
|
||||
jsonschema==3.2.0; python_version < '3.7' # v3.2.0 is the last version to support Python 3.6
|
||||
sentry-sdk==1.12.1,<1.13
|
||||
psutil==5.9.4
|
||||
distro>=1.7.0
|
||||
setuptools>=60.8.1; python_version >= '3.7'
|
||||
setuptools==59.6.0; python_version < '3.7' # v59.6.0 is the last version to support Python 3.6
|
||||
jsonschema>=4.23,<4.24
|
||||
sentry-sdk>=2.26.1,<2.27 # optional dependency
|
||||
psutil>=7.0.0
|
||||
distro>=1.9.0
|
||||
truststore>=0.10.0; python_version >= '3.10'
|
||||
importlib-resources>=1.3; python_version < '3.9'
|
||||
|
||||
207
resources/symbols/nat.svg
Normal file
207
resources/symbols/nat.svg
Normal file
@@ -0,0 +1,207 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
width="4.5009999cm"
|
||||
height="2.0009999cm"
|
||||
viewBox="0.2 0.149 4.7 2.151"
|
||||
id="svg2"
|
||||
sodipodi:docname="nat.svg"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<sodipodi:namedview
|
||||
id="namedview30"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="cm"
|
||||
showgrid="false"
|
||||
inkscape:lockguides="true"
|
||||
inkscape:zoom="5.7444245"
|
||||
inkscape:cx="75.725602"
|
||||
inkscape:cy="42.040765"
|
||||
inkscape:window-width="1658"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-x="70"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<metadata
|
||||
id="metadata57">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>Jeremy Grossmann</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:publisher>
|
||||
<cc:Agent>
|
||||
<dc:title>GNS-3</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:publisher>
|
||||
<dc:description>Created for the GNS-3 project (www.gns3.net)</dc:description>
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/GPL/2.0/" />
|
||||
</cc:Work>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/GPL/2.0/">
|
||||
<cc:permits
|
||||
rdf:resource="http://web.resource.org/cc/Reproduction" />
|
||||
<cc:permits
|
||||
rdf:resource="http://web.resource.org/cc/Distribution" />
|
||||
<cc:requires
|
||||
rdf:resource="http://web.resource.org/cc/Notice" />
|
||||
<cc:permits
|
||||
rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
|
||||
<cc:requires
|
||||
rdf:resource="http://web.resource.org/cc/ShareAlike" />
|
||||
<cc:requires
|
||||
rdf:resource="http://web.resource.org/cc/SourceCode" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs55" />
|
||||
<path
|
||||
d="m 3.7599791,0.61531938 -10e-4,-0.021 -0.003,-0.021 -0.007,-0.02 -0.009,-0.02 -0.011,-0.021 -0.013,-0.02 -0.017,-0.02 -0.018,-0.019 -0.021,-0.019 -0.023,-0.019 -0.024,-0.017 -0.028,-0.017 -0.03,-0.017 -0.031,-0.017 -0.033,-0.015 -0.035,-0.015 -0.037,-0.014 -0.038,-0.013 -0.04,-0.012 -0.041,-0.011 -0.043,-0.011 -0.045,-0.01 -0.044,-0.008 -0.046,-0.008 -0.048,-0.007 -0.047,-0.006 -0.048,-0.004 -0.049,-0.004 -0.05,-0.003 -0.049,-0.001 -0.049,-0.001 0,0 -0.05,0.001 -0.049,0.001 -0.05,0.003 -0.048,0.004 -0.049,0.004 -0.047,0.006 -0.047,0.007 -0.046,0.008 -0.045,0.008 -0.043,0.01 -0.043,0.011 -0.042,0.011 -0.04,0.012 -0.039,0.013 -0.036,0.014 -0.035,0.015 -0.033,0.015 -0.032,0.017 -0.029,0.017 -0.027,0.017 -0.026,0.017 -0.023,0.019 -0.02,0.019 -0.019,0.019 -0.016,0.02 -0.014,0.02 -0.01,0.021 -0.009,0.02 -0.006,0.02 -0.005,0.021 -0.001,0.021 0,0 0.001,0.02 0.005,0.022 0.006,0.02 0.009,0.021 0.01,0.02 0.014,0.019 0.016,0.02 0.019,0.02 0.02,0.019 0.023,0.018 0.026,0.018 0.027,0.017 0.029,0.017 0.032,0.016 0.033,0.016 0.035,0.014 0.036,0.014 0.039,0.013 0.04,0.013 0.042,0.011 0.043,0.01 0.043,0.011 0.045,0.008 0.046,0.007 0.047,0.007 0.047,0.006 0.049,0.005 0.048,0.003 0.05,0.003 0.049,10e-4 0.05,0.001 0,0 0.049,-0.001 0.049,-10e-4 0.05,-0.003 0.049,-0.003 0.048,-0.005 0.047,-0.006 0.048,-0.007 0.046,-0.007 0.044,-0.008 0.045,-0.011 0.043,-0.01 0.041,-0.011 0.04,-0.013 0.038,-0.013 0.037,-0.014 0.035,-0.014 0.033,-0.016 0.031,-0.016 0.03,-0.017 0.028,-0.017 0.024,-0.018 0.023,-0.018 0.021,-0.019 0.018,-0.02 0.017,-0.02 0.013,-0.019 0.011,-0.02 0.009,-0.021 0.007,-0.02 0.003,-0.022 10e-4,-0.02"
|
||||
id="path4"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<path
|
||||
d="m 2.2259791,0.83031938 -10e-4,-0.021 -0.003,-0.02 -0.004,-0.021 -0.007,-0.02 -0.009,-0.021 -0.01,-0.02 -0.012,-0.02 -0.015,-0.018 -0.015,-0.019 -0.018,-0.019 -0.019,-0.018 -0.021,-0.017 -0.022,-0.017 -0.024,-0.016 -0.026,-0.015 -0.026,-0.015 -0.029,-0.014 -0.029,-0.013 -0.031,-0.013 -0.032,-0.011 -0.032,-0.011 -0.034,-0.009 -0.035,-0.009 -0.035,-0.008 -0.036,-0.006 -0.036,-0.006 -0.037,-0.005 -0.038,-0.004 -0.037,-0.002 -0.039,-0.002 -0.038,0 0,0 -0.037,0 -0.038,0.002 -0.038,0.002 -0.038,0.004 -0.037,0.005 -0.036,0.006 -0.036,0.006 -0.035,0.008 -0.035,0.009 -0.033,0.009 -0.033,0.011 -0.032,0.011 -0.03,0.013 -0.03,0.013 -0.028,0.014 -0.027,0.015 -0.025,0.015 -0.024,0.016 -0.023,0.017 -0.021,0.017 -0.018,0.018 -0.019,0.019 -0.015,0.019 -0.015,0.018 -0.012,0.02 -0.011,0.02 -0.008,0.021 -0.007,0.02 -0.005,0.021 -0.002,0.02 -0.001,0.021 0,0 0.001,0.021 0.002,0.021 0.005,0.02 0.007,0.021 0.008,0.02 0.011,0.02 0.012,0.02 0.015,0.019 0.015,0.019 0.019,0.018 0.018,0.018 0.021,0.018 0.023,0.016 0.024,0.016 0.025,0.016 0.027,0.014 0.028,0.015 0.03,0.012 0.03,0.013 0.032,0.012 0.033,0.01 0.033,0.01 0.035,0.008 0.035,0.008 0.036,0.007 0.036,0.005 0.037,0.005 0.038,0.004 0.038,0.002 0.038,0.002 0.037,0 0,0 0.038,0 0.039,-0.002 0.037,-0.002 0.038,-0.004 0.037,-0.005 0.036,-0.005 0.036,-0.007 0.035,-0.008 0.035,-0.008 0.034,-0.01 0.032,-0.01 0.032,-0.012 0.031,-0.013 0.029,-0.012 0.029,-0.015 0.026,-0.014 0.026,-0.016 0.024,-0.016 0.022,-0.016 0.021,-0.018 0.019,-0.018 0.018,-0.018 0.015,-0.019 0.015,-0.019 0.012,-0.02 0.01,-0.02 0.009,-0.02 0.007,-0.021 0.004,-0.02 0.003,-0.021 10e-4,-0.021"
|
||||
id="path6"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<path
|
||||
d="m 1.2809791,1.2493194 -10e-4,-0.017 -0.001,-0.017 -0.004,-0.017 -0.005,-0.017 -0.005,-0.016 -0.007,-0.016 -0.009,-0.016 -0.009,-0.017 -0.011,-0.015 -0.012,-0.015 -0.012,-0.014 -0.014,-0.015 -0.015,-0.014 -0.017,-0.012 -0.017,-0.013 -0.018,-0.012 -0.019,-0.011 -0.02,-0.011 -0.02,-0.01 -0.022,-0.01 -0.022,-0.008 -0.023,-0.008 -0.023,-0.007 -0.024,-0.006 -0.024,-0.006 -0.025,-0.004 -0.024,-0.005 -0.026,-0.002 -0.025,-0.002 -0.025,-0.002 -0.026,0 0,0 -0.026,0 -0.026,0.002 -0.025,0.002 -0.025,0.002 -0.024,0.005 -0.026,0.004 -0.024,0.006 -0.023,0.006 -0.023,0.007 -0.023,0.008 -0.022,0.008 -0.022,0.01 -0.021,0.01 -0.019,0.011 -0.019,0.011 -0.018,0.012 -0.017,0.013 -0.017,0.012 -0.015,0.014 -0.014,0.015 -0.013,0.014 -0.011,0.015 -0.011,0.015 -0.01,0.017 -0.008,0.016 -0.007,0.016 -0.006,0.016 -0.004,0.017 -0.004,0.017 -0.002,0.017 0,0.017 0,0 0,0.016 0.002,0.018 0.004,0.016 0.004,0.017 0.006,0.017 0.007,0.016 0.008,0.016 0.01,0.015 0.011,0.016 0.011,0.015 0.013,0.015 0.014,0.014 0.015,0.014 0.017,0.013 0.017,0.012 0.018,0.012 0.019,0.012 0.019,0.01 0.021,0.01 0.022,0.01 0.022,0.008 0.023,0.008 0.023,0.007 0.023,0.007 0.024,0.005 0.026,0.005 0.024,0.004 0.025,0.003 0.025,10e-4 0.026,0.002 0.026,0.001 0,0 0.026,-0.001 0.025,-0.002 0.025,-10e-4 0.026,-0.003 0.024,-0.004 0.025,-0.005 0.024,-0.005 0.024,-0.007 0.023,-0.007 0.023,-0.008 0.022,-0.008 0.022,-0.01 0.02,-0.01 0.02,-0.01 0.019,-0.012 0.018,-0.012 0.017,-0.012 0.017,-0.013 0.015,-0.014 0.014,-0.014 0.012,-0.015 0.012,-0.015 0.011,-0.016 0.009,-0.015 0.009,-0.016 0.007,-0.016 0.005,-0.017 0.005,-0.017 0.004,-0.016 0.001,-0.018 10e-4,-0.016"
|
||||
id="path8"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<path
|
||||
d="m 2.0969791,1.5713194 -0.002,-0.018 -0.003,-0.018 -0.004,-0.019 -0.007,-0.018 -0.009,-0.017 -0.01,-0.018 -0.013,-0.017 -0.015,-0.018 -0.016,-0.016 -0.017,-0.016 -0.019,-0.016 -0.021,-0.016 -0.024,-0.015 -0.024,-0.014 -0.026,-0.013 -0.027,-0.013 -0.028,-0.012 -0.031,-0.012 -0.03,-0.011 -0.032,-0.01 -0.034,-0.009 -0.035,-0.009 -0.034,-0.008 -0.036,-0.007 -0.036,-0.005 -0.038,-0.006 -0.037,-0.004 -0.038,-0.003 -0.038,-0.002 -0.039,-0.002 -0.038,0 0,0 -0.038,0 -0.039,0.002 -0.039,0.002 -0.038,0.003 -0.037,0.004 -0.037,0.006 -0.037,0.005 -0.035,0.007 -0.035,0.008 -0.034,0.009 -0.034,0.009 -0.032,0.01 -0.031,0.011 -0.03,0.012 -0.029,0.012 -0.027,0.013 -0.026,0.013 -0.024,0.014 -0.023,0.015 -0.021,0.016 -0.019,0.016 -0.018,0.016 -0.017,0.016 -0.014,0.018 -0.012,0.017 -0.01,0.018 -0.01,0.017 -0.006,0.018 -0.005,0.019 -0.003,0.018 -0.001,0.018 0,0 0.001,0.019 0.003,0.018 0.005,0.018 0.006,0.018 0.01,0.019 0.01,0.017 0.012,0.017 0.014,0.018 0.017,0.016 0.018,0.016 0.019,0.016 0.021,0.015 0.023,0.015 0.024,0.015 0.026,0.014 0.027,0.012 0.029,0.013 0.03,0.011 0.031,0.011 0.032,0.01 0.034,0.01 0.034,0.008 0.035,0.008 0.035,0.006 0.037,0.007 0.037,0.004 0.037,0.005 0.038,0.003 0.039,0.003 0.039,10e-4 0.038,0 0,0 0.038,0 0.039,-10e-4 0.038,-0.003 0.038,-0.003 0.037,-0.005 0.038,-0.004 0.036,-0.007 0.036,-0.006 0.034,-0.008 0.035,-0.008 0.034,-0.01 0.032,-0.01 0.03,-0.011 0.031,-0.011 0.028,-0.013 0.027,-0.012 0.026,-0.014 0.024,-0.015 0.024,-0.015 0.021,-0.015 0.019,-0.016 0.017,-0.016 0.016,-0.016 0.015,-0.018 0.013,-0.017 0.01,-0.017 0.009,-0.019 0.007,-0.018 0.004,-0.018 0.003,-0.018 0.002,-0.019"
|
||||
id="path10"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<path
|
||||
d="m 3.9229791,1.7583194 -0.002,-0.021 -0.004,-0.022 -0.007,-0.022 -0.011,-0.021 -0.012,-0.021 -0.016,-0.021 -0.019,-0.021 -0.021,-0.02 -0.024,-0.02 -0.027,-0.02 -0.029,-0.018 -0.032,-0.019 -0.033,-0.017 -0.037,-0.017 -0.039,-0.016 -0.04,-0.016 -0.043,-0.014 -0.045,-0.014 -0.046,-0.013 -0.048,-0.012 -0.05,-0.01 -0.051,-0.011 -0.052,-0.009 -0.054,-0.008 -0.054,-0.007 -0.056,-0.006 -0.055,-0.005 -0.057,-0.004 -0.057,-0.002 -0.058,-0.002 -0.057,0 0,0 -0.057,0 -0.058,0.002 -0.057,0.002 -0.057,0.004 -0.056,0.005 -0.055,0.006 -0.054,0.007 -0.054,0.008 -0.052,0.009 -0.051,0.011 -0.05,0.01 -0.048,0.012 -0.047,0.013 -0.044,0.014 -0.043,0.014 -0.04,0.016 -0.039,0.016 -0.037,0.017 -0.034,0.017 -0.031,0.019 -0.029,0.018 -0.027,0.02 -0.024,0.02 -0.021,0.02 -0.018,0.021 -0.017,0.021 -0.012,0.021 -0.011,0.021 -0.007,0.022 -0.005,0.022 -10e-4,0.021 0,0 10e-4,0.023 0.005,0.021 0.007,0.022 0.011,0.021 0.012,0.022 0.017,0.02 0.018,0.021 0.021,0.02 0.024,0.02 0.027,0.02 0.029,0.018 0.031,0.018 0.034,0.018 0.037,0.017 0.039,0.016 0.04,0.016 0.043,0.015 0.044,0.013 0.047,0.013 0.048,0.012 0.05,0.011 0.051,0.01 0.052,0.009 0.054,0.009 0.054,0.007 0.055,0.005 0.056,0.005 0.057,0.004 0.057,0.003 0.058,0.002 0.057,0 0,0 0.057,0 0.058,-0.002 0.057,-0.003 0.057,-0.004 0.055,-0.005 0.056,-0.005 0.054,-0.007 0.054,-0.009 0.052,-0.009 0.051,-0.01 0.05,-0.011 0.048,-0.012 0.046,-0.013 0.045,-0.013 0.043,-0.015 0.04,-0.016 0.039,-0.016 0.037,-0.017 0.033,-0.018 0.032,-0.018 0.029,-0.018 0.027,-0.02 0.024,-0.02 0.021,-0.02 0.019,-0.021 0.016,-0.02 0.012,-0.022 0.011,-0.021 0.007,-0.022 0.004,-0.021 0.002,-0.023"
|
||||
id="path12"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<path
|
||||
d="m 4.5529791,0.76631938 0,-0.016 -0.003,-0.016 -0.004,-0.017 -0.008,-0.016 -0.007,-0.016 -0.011,-0.016 -0.012,-0.015 -0.013,-0.015 -0.016,-0.015 -0.017,-0.015 -0.019,-0.013 -0.02,-0.014 -0.022,-0.013 -0.023,-0.013 -0.025,-0.012 -0.026,-0.011 -0.027,-0.012 -0.029,-0.01 -0.03,-0.01 -0.031,-0.009 -0.031,-0.008 -0.033,-0.008 -0.034,-0.007 -0.034,-0.006 -0.035,-0.005 -0.035,-0.005 -0.036,-0.003 -0.037,-0.003 -0.036,-0.002 -0.037,-0.001 -0.037,0 0,0 -0.037,0 -0.036,0.001 -0.038,0.002 -0.035,0.003 -0.036,0.003 -0.036,0.005 -0.034,0.005 -0.035,0.006 -0.034,0.007 -0.032,0.008 -0.032,0.008 -0.031,0.009 -0.03,0.01 -0.028,0.01 -0.028,0.012 -0.026,0.011 -0.025,0.012 -0.023,0.013 -0.022,0.013 -0.02,0.014 -0.019,0.013 -0.016,0.015 -0.016,0.015 -0.014,0.015 -0.012,0.015 -0.01,0.016 -0.009,0.016 -0.006,0.016 -0.005,0.017 -0.002,0.016 -10e-4,0.016 0,0 10e-4,0.016 0.002,0.016 0.005,0.017 0.006,0.016 0.009,0.016 0.01,0.016 0.012,0.016 0.014,0.014 0.016,0.016 0.016,0.014 0.019,0.014 0.02,0.013 0.022,0.014 0.023,0.013 0.025,0.012 0.026,0.011 0.028,0.011 0.028,0.011 0.03,0.009 0.031,0.009 0.032,0.009 0.032,0.007 0.034,0.007 0.035,0.006 0.034,0.006 0.036,0.004 0.036,0.003 0.035,0.003 0.038,0.003 0.036,0 0.037,10e-4 0,0 0.037,-10e-4 0.037,0 0.036,-0.003 0.037,-0.003 0.036,-0.003 0.035,-0.004 0.035,-0.006 0.034,-0.006 0.034,-0.007 0.033,-0.007 0.031,-0.009 0.031,-0.009 0.03,-0.009 0.029,-0.011 0.027,-0.011 0.026,-0.011 0.025,-0.012 0.023,-0.013 0.022,-0.014 0.02,-0.013 0.019,-0.014 0.017,-0.014 0.016,-0.016 0.013,-0.014 0.012,-0.016 0.011,-0.016 0.007,-0.016 0.008,-0.016 0.004,-0.017 0.003,-0.016 0,-0.016"
|
||||
id="path14"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<path
|
||||
d="m 4.7599791,1.1813194 -10e-4,-0.016 -0.002,-0.016 -0.005,-0.017 -0.007,-0.016 -0.008,-0.016 -0.01,-0.016 -0.012,-0.016 -0.013,-0.014 -0.015,-0.015 -0.018,-0.015 -0.019,-0.014 -0.019,-0.013 -0.022,-0.013 -0.023,-0.014 -0.024,-0.011 -0.027,-0.012 -0.027,-0.011 -0.028,-0.01 -0.03,-0.01 -0.03,-0.009 -0.032,-0.008 -0.033,-0.008 -0.033,-0.007 -0.033,-0.005 -0.035,-0.006 -0.035,-0.004 -0.036,-0.004 -0.036,-0.003 -0.036,-0.002 -0.037,-0.001 -0.036,-0.001 0,0 -0.038,0.001 -0.036,0.001 -0.036,0.002 -0.036,0.003 -0.036,0.004 -0.035,0.004 -0.035,0.006 -0.033,0.005 -0.034,0.007 -0.032,0.008 -0.032,0.008 -0.03,0.009 -0.03,0.01 -0.028,0.01 -0.027,0.011 -0.027,0.012 -0.024,0.011 -0.022,0.014 -0.022,0.013 -0.021,0.013 -0.018,0.014 -0.018,0.015 -0.015,0.015 -0.013,0.014 -0.012,0.016 -0.01,0.016 -0.008,0.016 -0.007,0.016 -0.005,0.017 -0.002,0.016 -10e-4,0.016 0,0 10e-4,0.016 0.002,0.016 0.005,0.016 0.007,0.016 0.008,0.017 0.01,0.016 0.012,0.015 0.013,0.015 0.015,0.015 0.018,0.015 0.018,0.013 0.021,0.014 0.022,0.013 0.022,0.013 0.024,0.012 0.027,0.011 0.027,0.012 0.028,0.01 0.03,0.01 0.03,0.009 0.032,0.008 0.032,0.007 0.034,0.007 0.033,0.006 0.035,0.005 0.035,0.005 0.036,0.004 0.036,0.003 0.036,0.002 0.036,0.001 0.038,0 0,0 0.036,0 0.037,-0.001 0.036,-0.002 0.036,-0.003 0.036,-0.004 0.035,-0.005 0.035,-0.005 0.033,-0.006 0.033,-0.007 0.033,-0.007 0.032,-0.008 0.03,-0.009 0.03,-0.01 0.028,-0.01 0.027,-0.012 0.027,-0.011 0.024,-0.012 0.023,-0.013 0.022,-0.013 0.019,-0.014 0.019,-0.013 0.018,-0.015 0.015,-0.015 0.013,-0.015 0.012,-0.015 0.01,-0.016 0.008,-0.017 0.007,-0.016 0.005,-0.016 0.002,-0.016 10e-4,-0.016"
|
||||
id="path16"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<path
|
||||
d="m 4.6189791,1.5243194 -10e-4,-0.027 -0.002,-0.027 -0.005,-0.026 -0.006,-0.027 -0.009,-0.026 -0.01,-0.026 -0.012,-0.025 -0.014,-0.025 -0.014,-0.025 -0.017,-0.023 -0.018,-0.024 -0.021,-0.022 -0.021,-0.022 -0.023,-0.02 -0.024,-0.021 -0.027,-0.018 -0.027,-0.018 -0.027,-0.018 -0.029,-0.015 -0.031,-0.015 -0.032,-0.013 -0.031,-0.013 -0.033,-0.011 -0.034,-0.01 -0.035,-0.009 -0.035,-0.007 -0.036,-0.006 -0.035,-0.005 -0.036,-0.003 -0.036,-0.002 -0.036,-0.001 0,0 -0.037,0.001 -0.036,0.002 -0.036,0.003 -0.036,0.005 -0.035,0.006 -0.035,0.007 -0.035,0.009 -0.034,0.01 -0.033,0.011 -0.032,0.013 -0.031,0.013 -0.031,0.015 -0.029,0.015 -0.028,0.018 -0.027,0.018 -0.026,0.018 -0.024,0.021 -0.023,0.02 -0.022,0.022 -0.019,0.022 -0.019,0.024 -0.017,0.023 -0.015,0.025 -0.014,0.025 -0.011,0.025 -0.01,0.026 -0.008,0.026 -0.007,0.027 -0.005,0.026 -0.002,0.027 -10e-4,0.027 0,0 10e-4,0.027 0.002,0.027 0.005,0.026 0.007,0.026 0.008,0.026 0.01,0.026 0.011,0.026 0.014,0.025 0.015,0.024 0.017,0.024 0.019,0.023 0.019,0.022 0.022,0.022 0.023,0.021 0.024,0.02 0.026,0.019 0.027,0.018 0.028,0.017 0.029,0.016 0.031,0.015 0.031,0.013 0.032,0.013 0.033,0.011 0.034,0.01 0.035,0.008 0.035,0.008 0.035,0.006 0.036,0.005 0.036,0.003 0.036,0.002 0.037,10e-4 0,0 0.036,-10e-4 0.036,-0.002 0.036,-0.003 0.035,-0.005 0.036,-0.006 0.035,-0.008 0.035,-0.008 0.034,-0.01 0.033,-0.011 0.031,-0.013 0.032,-0.013 0.031,-0.015 0.029,-0.016 0.027,-0.017 0.027,-0.018 0.027,-0.019 0.024,-0.02 0.023,-0.021 0.021,-0.022 0.021,-0.022 0.018,-0.023 0.017,-0.024 0.014,-0.024 0.014,-0.025 0.012,-0.026 0.01,-0.026 0.009,-0.026 0.006,-0.026 0.005,-0.026 0.002,-0.027 10e-4,-0.027"
|
||||
id="path18"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<path
|
||||
d="m 2.7919791,0.61131938 0.965,-0.091 -0.013,-0.021 -0.014,-0.019 -0.017,-0.02 -0.019,-0.02 -0.022,-0.019 -0.024,-0.018 -0.026,-0.018 -0.028,-0.017 -0.031,-0.017 -0.032,-0.016 -0.034,-0.015 -0.036,-0.014 -0.038,-0.014 -0.039,-0.013 -0.042,-0.012 -0.042,-0.011 -0.043,-0.011 -0.045,-0.009 -0.046,-0.009 -0.047,-0.007 -0.048,-0.007 -0.048,-0.005 -0.049,-0.004 -0.049,-0.004 -0.049,-0.002 -0.051,-0.001 -0.05,0 -0.05,0 -0.049,0.003 -0.05,0.002 -0.049,0.004 -0.049,0.006 -0.047,0.006 -0.048,0.007 -0.046,0.008 -0.045,0.009 -0.044,0.01 -0.043,0.011 -0.041,0.012 -0.04,0.013 -0.038,0.013 -0.036,0.014 -0.036,0.016 -0.032,0.015 -0.032,0.017 -0.029,0.017 -0.026,0.018 -0.024,0.018 -0.024,0.019 -0.02,0.018 -0.018,0.02 -0.015,0.02 -0.012,0.021 -0.011,0.02 0.971,0.077"
|
||||
id="path22"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<path
|
||||
d="m 3.7569791,0.51931938 -0.013,-0.021 -0.014,-0.019 -0.017,-0.02 -0.02,-0.019 -0.022,-0.019 -0.024,-0.018 -0.026,-0.018 -0.029,-0.018 -0.03,-0.016 -0.032,-0.016 -0.035,-0.016 -0.035,-0.014 -0.038,-0.014 -0.04,-0.012 -0.04,-0.013 -0.042,-0.011 -0.044,-0.01 -0.045,-0.01 -0.046,-0.008 -0.046,-0.008 -0.048,-0.006 -0.048,-0.005 -0.049,-0.004 -0.05,-0.004 -0.05,-0.002 -0.049,-0.001 -0.051,0 -0.049,0 -0.049,0.003 -0.051,0.002 -0.048,0.004 -0.049,0.006 -0.047,0.006 -0.048,0.007 -0.046,0.008 -0.045,0.009 -0.043,0.01 -0.043,0.011 -0.042,0.012 -0.039,0.013 -0.038,0.013 -0.036,0.014 -0.036,0.015 -0.033,0.015 -0.031,0.017 -0.028,0.018 -0.028,0.017 -0.024,0.018 -0.022,0.019 -0.021,0.019 -0.018,0.019 -0.016,0.02 -0.012,0.021 -0.011,0.02"
|
||||
id="path24"
|
||||
style="fill:none;stroke:#6c8f93;stroke-width:0.06067566;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||
<path
|
||||
d="m 1.4819791,0.82631938 0.486,-0.317 -0.029,-0.013 -0.032,-0.012 -0.032,-0.012 -0.033,-0.01 -0.034,-0.01 -0.035,-0.008 -0.036,-0.008 -0.036,-0.006 -0.037,-0.006 -0.037,-0.005 -0.038,-0.003 -0.038,-0.003 -0.039,-0.001 -0.038,0 -0.038,0 -0.038,0.002 -0.038,0.003 -0.038,0.004 -0.037,0.005 -0.036,0.006 -0.037,0.007 -0.035,0.008 -0.034,0.009 -0.034,0.009 -0.033,0.011 -0.031,0.013 -0.031,0.012 -0.029,0.013 -0.028,0.015 -0.027,0.015 -0.026,0.015 -0.024,0.016 -0.022,0.018 -0.021,0.017 -0.018,0.018 -0.018,0.02 -0.016,0.018 -0.014,0.02 -0.012,0.02 -0.01,0.02 -0.008,0.02 -0.006,0.022 -0.005,0.02 -0.003,0.021 0,0.021 0.001,0.021 0.003,0.021 0.005,0.021 0.74600003,-0.067"
|
||||
id="path26"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<path
|
||||
d="m 1.9659791,0.50831938 -0.03,-0.014 -0.031,-0.011 -0.033,-0.012 -0.033,-0.01 -0.035,-0.01 -0.034,-0.008 -0.036,-0.008 -0.036,-0.006 -0.037,-0.005 -0.037,-0.005 -0.038,-0.004 -0.038,-0.002 -0.039,-0.001 -0.038,0 -0.038,0 -0.038,0.003 -0.038,0.002 -0.038,0.004 -0.037,0.005 -0.036,0.007 -0.036,0.006 -0.035,0.009 -0.034,0.008 -0.034,0.011 -0.033,0.011 -0.032,0.011 -0.03,0.013 -0.029,0.013 -0.028,0.015 -0.027,0.015 -0.025,0.015 -0.024,0.016 -0.022,0.018 -0.02,0.017 -0.02,0.019 -0.017,0.018 -0.016,0.019 -0.013,0.02 -0.012,0.02 -0.01,0.02 -0.008,0.021 -0.006,0.02 -0.005,0.021 -0.003,0.02 0,0.022 0.002,0.021 0.002,0.02 0.006,0.021"
|
||||
id="path28"
|
||||
style="fill:none;stroke:#6c8f93;stroke-width:0.06067566;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||
<path
|
||||
d="m 1.3459791,1.5683194 -0.78100003,-0.012 0,0.019 0.001,0.019 0.003,0.019 0.006,0.018 0.008,0.019 0.009,0.018 0.012,0.018 0.014,0.018 0.015,0.017 0.017,0.017 0.019,0.017 0.02,0.016 0.023,0.015 0.023,0.015 0.026,0.015 0.027,0.014 0.027,0.012 0.03,0.013 0.031,0.012 0.032,0.01 0.034,0.011 0.033,0.009 0.035,0.008 0.036,0.008 0.037,0.007 0.038,0.006 0.037,0.005 0.039,0.004 0.039,0.003 0.039,0.003 0.04,10e-4 0.039,0 0.039,-10e-4 0.039,-0.002 0.039,-0.002 0.039,-0.003 0.039,-0.005 0.037,-0.006 0.037,-0.005 0.037,-0.008 0.035,-0.008 0.035,-0.009 0.033,-0.009 0.033,-0.01 0.033,-0.011 0.03,-0.013 -0.513,-0.282"
|
||||
id="path30"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<path
|
||||
d="m 0.56497907,1.5563194 -0.001,0.019 0.002,0.019 0.003,0.018 0.006,0.019 0.008,0.018 0.01,0.019 0.011,0.018 0.014,0.018 0.015,0.017 0.017,0.017 0.018,0.016 0.02,0.016 0.023,0.016 0.024,0.015 0.024,0.014 0.026,0.014 0.029,0.013 0.029,0.013 0.032,0.011 0.032,0.011 0.032,0.01 0.034,0.009 0.035,0.009 0.037,0.008 0.036,0.006 0.037,0.007 0.038,0.005 0.038,0.004 0.039,0.003 0.039,0.002 0.039,0.002 0.04,0 0.039,0 0.039,-0.003 0.039,-0.002 0.038,-0.003 0.038,-0.005 0.038,-0.004 0.038,-0.007 0.036,-0.007 0.036,-0.008 0.034,-0.009 0.034,-0.009 0.034,-0.01 0.031,-0.011 0.03,-0.012"
|
||||
id="path32"
|
||||
style="fill:none;stroke:#6c8f93;stroke-width:0.06067566;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||
<path
|
||||
d="m 3.8359791,0.75931938 0.689,0.104 0.011,-0.016 0.01,-0.015 0.007,-0.016 0.006,-0.016 0.003,-0.016 0.002,-0.017 0,-0.016 -0.002,-0.017 -0.003,-0.016 -0.006,-0.016 -0.007,-0.016 -0.01,-0.016 -0.01,-0.016 -0.013,-0.015 -0.015,-0.015 -0.016,-0.015 -0.018,-0.014 -0.02,-0.014 -0.022,-0.014 -0.021,-0.012 -0.025,-0.013 -0.025,-0.012 -0.027,-0.011 -0.029,-0.011 -0.028,-0.01 -0.03,-0.009 -0.032,-0.009 -0.032,-0.008 -0.034,-0.007 -0.033,-0.006 -0.036,-0.006 -0.034,-0.005 -0.037,-0.004 -0.036,-0.003 -0.037,-0.003 -0.037,-0.001 -0.036,-0.001 -0.037,0 -0.037,0.001 -0.037,0.002 -0.036,0.002 -0.036,0.003 -0.036,0.004 -0.036,0.005 0.238,0.306"
|
||||
id="path34"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<path
|
||||
d="m 4.4569791,0.92331938 0.067,-0.059 0.011,-0.016 0.009,-0.015 0.009,-0.016 0.005,-0.017 0.004,-0.016 0.002,-0.016 0.001,-0.016 -0.003,-0.017 -0.003,-0.016 -0.006,-0.017 -0.007,-0.015 -0.009,-0.016 -0.011,-0.016 -0.012,-0.015 -0.016,-0.015 -0.016,-0.015 -0.017,-0.014 -0.02,-0.014 -0.021,-0.014 -0.023,-0.013 -0.023,-0.012 -0.026,-0.012 -0.027,-0.011 -0.028,-0.011 -0.029,-0.011 -0.03,-0.009 -0.032,-0.008 -0.031,-0.008 -0.033,-0.008 -0.034,-0.006 -0.035,-0.006 -0.035,-0.005 -0.036,-0.004 -0.036,-0.003 -0.037,-0.002 -0.037,-0.002 -0.037,-0.001 -0.037,0 -0.037,0.001 -0.037,0.002 -0.036,0.002 -0.036,0.003 -0.036,0.004 -0.035,0.005"
|
||||
id="path36"
|
||||
style="fill:none;stroke:#6c8f93;stroke-width:0.06067566;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||
<path
|
||||
d="m 3.9329791,1.1853194 0.775,0.132 0.016,-0.017 0.013,-0.017 0.012,-0.018 0.008,-0.018 0.008,-0.018 0.004,-0.019 10e-4,-0.018 0,-0.018 -0.003,-0.019 -0.004,-0.019 -0.008,-0.018 -0.009,-0.018 -0.012,-0.017 -0.014,-0.018 -0.016,-0.017 -0.018,-0.017 -0.02,-0.016 -0.023,-0.016 -0.024,-0.015 -0.686,0.20100002"
|
||||
id="path38"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<path
|
||||
d="m 4.6121701,1.38473 0.098479,-0.059128 0.014482,-0.018039 0.013517,-0.018039 0.01062,-0.018039 0.00869,-0.018039 0.00772,-0.018039 0.00386,-0.018039 0.00193,-0.019041 0,-0.018039 -0.00386,-0.020044 -0.00386,-0.018039 -0.00676,-0.018039 -0.00965,-0.018039 -0.011586,-0.018039 -0.013517,-0.018039 -0.015448,-0.017037 -0.018344,-0.017037 -0.021241,-0.016035 -0.020275,-0.016035 -0.024137,-0.0160349 -0.1544773,-0.0591286"
|
||||
id="path40"
|
||||
style="fill:none;stroke:#6c8f93;stroke-width:0.0596842;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||
<path
|
||||
d="m 3.9009791,1.5363194 -0.245,0.503 0.036,0.008 0.036,0.007 0.037,0.006 0.038,0.004 0.038,0.003 0.037,0.002 0.037,0 0.038,-0.002 0.037,-0.002 0.038,-0.004 0.036,-0.005 0.038,-0.007 0.035,-0.008 0.036,-0.009 0.034,-0.011 0.034,-0.012 0.033,-0.013 0.032,-0.014 0.031,-0.016 0.03,-0.016 0.028,-0.018 0.027,-0.019 0.027,-0.02 0.025,-0.02 0.022,-0.022 0.022,-0.021 0.02,-0.024 0.018,-0.024 0.016,-0.024 0.015,-0.025 0.014,-0.026 0.01,-0.025 0.01,-0.027 0.008,-0.027 0.006,-0.026 0.003,-0.028 0.002,-0.027 0,-0.027 -0.002,-0.027 -0.004,-0.027 -0.006,-0.026 -0.008,-0.028 -0.009,-0.025 -0.71,0.149"
|
||||
id="path42"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<path
|
||||
d="m 3.6589791,2.0403194 0.036,0.008 0.036,0.007 0.037,0.006 0.037,0.004 0.037,0.002 0.038,0.002 0.038,10e-4 0.037,-0.003 0.038,-0.002 0.037,-0.004 0.037,-0.005 0.036,-0.007 0.036,-0.009 0.036,-0.009 0.034,-0.011 0.033,-0.011 0.034,-0.014 0.032,-0.014 0.031,-0.015 0.029,-0.018 0.029,-0.017 0.027,-0.019 0.026,-0.019 0.025,-0.021 0.023,-0.022 0.021,-0.022 0.019,-0.024 0.019,-0.023 0.016,-0.024 0.015,-0.025 0.014,-0.026 0.011,-0.026 0.008,-0.026 0.008,-0.027 0.006,-0.027 0.003,-0.027 0.002,-0.027 0,-0.027 -0.002,-0.027 -0.005,-0.027 -0.005,-0.028 -0.008,-0.026 -0.01,-0.026"
|
||||
id="path44"
|
||||
style="fill:none;stroke:#6c8f93;stroke-width:0.06067566;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||
<path
|
||||
d="m 0.80297907,1.2493194 -0.037,-0.34200002 -0.028,0.002 -0.026,0.002 -0.027,0.004 -0.026,0.004 -0.026,0.005 -0.026,0.006 -0.024,0.007 -0.025,0.008 -0.024,0.008 -0.023,0.009 -0.022,0.01 -0.021,0.01 -0.021,0.012 -0.02,0.012 -0.019,0.013 -0.017,0.013 -0.017,0.014 -0.015,0.014 -0.015,0.015 -0.013,0.015 -0.011,0.016 -0.011,0.016 -0.01,0.016 -0.008,0.017 -0.006,0.017 -0.006,0.016 -0.004,0.018 -0.003,0.018 -0.001,0.017 0,0.017 0.001,0.018 0.003,0.017 0.004,0.017 0.006,0.017 0.007,0.017 0.008,0.017 0.009,0.016 0.011,0.016 0.012,0.016 0.013,0.015 0.016,0.015 0.015,0.014 0.017,0.014 0.018,0.013 0.018,0.013 0.02,0.011 0.021,0.011 0.021,0.012 0.022,0.009 0.024,0.009 0.023,0.009 0.243,-0.305"
|
||||
id="path46"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<path
|
||||
d="m 0.76697907,0.90731938 -0.027,0.002 -0.027,0.002 -0.026,0.003 -0.026,0.005 -0.025,0.004 -0.026,0.006 -0.025,0.007 -0.023,0.007 -0.023,0.009 -0.023,0.009 -0.023,0.009 -0.021,0.01 -0.02,0.011 -0.02,0.012 -0.018,0.012 -0.018,0.013 -0.017,0.013 -0.016,0.015 -0.015,0.014 -0.012,0.014 -0.012,0.016 -0.012,0.015 -0.009,0.016 -0.009,0.016 -0.007,0.017 -0.006,0.017 -0.005,0.017 -0.003,0.017 -0.002,0.017 0,0.017 0,0.017 0.003,0.017 0.003,0.018 0.004,0.016 0.007,0.017 0.006,0.016 0.009,0.017 0.009,0.016 0.012,0.015 0.012,0.016 0.014,0.014 0.014,0.014 0.016,0.015 0.016,0.013 0.018,0.013 0.019,0.012 0.019,0.011 0.021,0.012 0.021,0.01 0.022,0.009 0.023,0.009 0.024,0.009"
|
||||
id="path48"
|
||||
style="fill:none;stroke:#6c8f93;stroke-width:0.06067566;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||
<path
|
||||
d="m 2.7759791,1.7874896 -1.109,0.040159 0.008,0.020594 0.009,0.019565 0.013,0.020594 0.015,0.020595 0.018,0.019565 0.021,0.018535 0.023,0.018535 0.025,0.018535 0.029,0.018535 0.031,0.017505 0.032,0.015446 0.036,0.016475 0.037,0.015446 0.039,0.015446 0.042,0.013386 0.043,0.013386 0.045,0.011327 0.047,0.012357 0.048,0.010297 0.049,0.00927 0.051,0.00927 0.052,0.00824 0.053,0.00618 0.053,0.00618 0.055,0.00515 0.055,0.00412 0.056,0.00206 0.055,0.00206 0.057,0.00103 0.055,0 0.057,-0.00103 0.055,-0.00309 0.055,-0.00309 0.055,-0.00515 0.054,-0.00515 0.053,-0.00618 0.052,-0.00721 0.052,-0.00824 0.05,-0.010297 0.049,-0.00927 0.047,-0.011327 0.046,-0.011327 0.044,-0.013386 0.042,-0.014416 0.04,-0.013386 0.039,-0.015446 0.036,-0.015446 0.034,-0.016476 0.031,-0.016476 0.03,-0.017505 0.027,-0.019565 0.025,-0.017505 0.022,-0.019565 0.018,-0.018535 -1.056,-0.1307748"
|
||||
id="path50"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<path
|
||||
d="m 1.6669791,1.8393194 0.008,0.019 0.01,0.02 0.012,0.02 0.016,0.019 0.017,0.019 0.021,0.018 0.023,0.018 0.025,0.019 0.03,0.017 0.03,0.017 0.032,0.015 0.036,0.016 0.037,0.015 0.039,0.015 0.042,0.013 0.044,0.013 0.043,0.011 0.048,0.012 0.047,0.01 0.049,0.009 0.051,0.009 0.052,0.008 0.053,0.006 0.053,0.005 0.055,0.006 0.054,0.003 0.056,0.003 0.056,0.002 0.056,10e-4 0.056,0 0.056,-10e-4 0.055,-0.003 0.055,-0.003 0.055,-0.005 0.054,-0.005 0.054,-0.006 0.051,-0.007 0.052,-0.008 0.05,-0.009 0.048,-0.01 0.048,-0.011 0.046,-0.011 0.043,-0.013 0.043,-0.012 0.04,-0.015 0.039,-0.014 0.036,-0.016 0.034,-0.015 0.032,-0.017 0.029,-0.016 0.027,-0.018 0.024,-0.018 0.022,-0.019 0.02,-0.018"
|
||||
id="path52"
|
||||
style="fill:none;stroke:#6c8f93;stroke-width:0.06067566;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||
<rect
|
||||
width="2.7800133"
|
||||
height="0.61515152"
|
||||
rx="0"
|
||||
ry="0.28983101"
|
||||
x="1.2692652"
|
||||
y="0.80605406"
|
||||
transform="matrix(0.999604,0.02812984,-0.02812984,0.999604,0,0)"
|
||||
id="rect1892"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none" />
|
||||
<path
|
||||
d="m 2.5029776,1.9073766 0.023,-0.00895 0.020913,-0.00976 0.022119,-0.011947 0.021191,-0.0142 0.021284,-0.015682 0.02031,-0.017195 0.020402,-0.018676 0.020541,-0.020897 0.018455,-0.021702 0.018546,-0.023184 0.018685,-0.025405 0.016552,-0.02547 0.016691,-0.027691 0.014651,-0.029236 0.015717,-0.029204 0.013676,-0.03075 0.012702,-0.032263 0.012748,-0.033004 0.010665,-0.033809 0.010665,-0.033809 0.00863,-0.035354 0.00756,-0.035386 0.00653,-0.036158 0.00658,-0.0369 0.00445,-0.036963 0.00333,-0.036256 0.00236,-0.037768 0.00226,-0.036288 -9.319e-4,-0.036384 2.273e-4,-0.037833 -0.00205,-0.035676 -0.00413,-0.03648 L 2.883076,0.9720976 2.877836,0.9363258 2.871426,0.902003 2.863906,0.8683889 2.855226,0.8362236 2.845476,0.8040261 2.834621,0.7725371 2.82256,0.7432375 2.810453,0.7146786 2.797187,0.6875686 2.782854,0.6604265 2.768383,0.635506 2.751686,0.6120024 2.733784,0.590685 2.716948,0.569403 2.69886,0.5510511 2.679613,0.5341483 2.660319,0.517986 2.640887,0.5040454 2.620296,0.4915538 2.598453,0.4819918 2.577631,0.4732028"
|
||||
id="path24-7"
|
||||
style="fill:none;stroke:#6c8f93;stroke-width:0.107496;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||
<path
|
||||
id="path2057-3"
|
||||
d="M 1.5298421,1.2403194 H 2.3548856 V 1.303797 L 2.577141,1.1602384 2.3548856,1.0313279 v 0.064457 H 1.5298421 v 0.1445348"
|
||||
style="fill:#6c8f93;fill-opacity:1;stroke:none;stroke-width:5.35746e-05;stroke-opacity:0.994961" />
|
||||
<path
|
||||
id="path2057-3-9"
|
||||
d="M 3.1225773,1.2403194 H 3.9476208 V 1.303797 L 4.1698762,1.1602384 3.9476208,1.0313279 v 0.064457 H 3.1225773 v 0.1445348"
|
||||
style="fill:#6c8f93;fill-opacity:1;stroke:none;stroke-width:5.35746e-05;stroke-opacity:0.994961" />
|
||||
<path
|
||||
id="path2057-3-5"
|
||||
d="m 1.5278616,0.86827348 h 0.8319275 v 0.063478 L 2.5838989,0.78819244 2.3597891,0.65928186 v 0.064457 H 1.5278616 v 0.14453482"
|
||||
style="fill:#6c8f93;fill-opacity:1;stroke:none;stroke-width:5.37977e-05;stroke-opacity:0.994961" />
|
||||
<path
|
||||
id="path2057-3-6"
|
||||
d="m 1.527029,1.6225499 h 0.8217599 v 0.063478 L 2.5701597,1.5424689 2.3487889,1.4135585 v 0.064457 H 1.5270291 v 0.1445347"
|
||||
style="fill:#6c8f93;fill-opacity:1;stroke:none;stroke-width:5.34679e-05;stroke-opacity:0.994961" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 30 KiB |
19
setup.py
19
setup.py
@@ -19,9 +19,9 @@ import sys
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
# we only support Python 3 version >= 3.4
|
||||
if len(sys.argv) >= 2 and sys.argv[1] == "install" and sys.version_info < (3, 4):
|
||||
raise SystemExit("Python 3.4 or higher is required")
|
||||
# we only support Python 3 version >= 3.8
|
||||
if len(sys.argv) >= 2 and sys.argv[1] == "install" and sys.version_info < (3, 8):
|
||||
raise SystemExit("Python 3.8 or higher is required")
|
||||
|
||||
|
||||
class PyTest(TestCommand):
|
||||
@@ -67,6 +67,7 @@ setup(
|
||||
author_email="package-maintainer@gns3.net",
|
||||
description="GNS3 graphical interface for the GNS3 server.",
|
||||
long_description=open("README.md", "r").read(),
|
||||
long_description_content_type="text/markdown",
|
||||
install_requires=open("requirements.txt", "r").read().splitlines(),
|
||||
entry_points={
|
||||
"gui_scripts": [
|
||||
@@ -78,8 +79,8 @@ setup(
|
||||
include_package_data=True,
|
||||
package_data={"gns3": ["configs/*.txt", "schemas/*.json"]},
|
||||
platforms="any",
|
||||
python_requires=">=3.4",
|
||||
setup_requires=["setuptools>=17.1"],
|
||||
python_requires=">=3.8",
|
||||
setup_requires=["setuptools>=45.2"],
|
||||
classifiers=[
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: X11 Applications :: Qt",
|
||||
@@ -91,15 +92,13 @@ setup(
|
||||
"Operating System :: MacOS :: MacOS X",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.4",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -75,6 +75,7 @@ def controller():
|
||||
Controller._instance = None
|
||||
c = Controller.instance()
|
||||
c._http_client = MagicMock()
|
||||
c._http_client.fullUrl.return_value = "http://localhost:3080"
|
||||
return c
|
||||
|
||||
|
||||
@@ -172,7 +173,7 @@ def local_server_config():
|
||||
return LocalServerConfig.instance()
|
||||
|
||||
|
||||
@pytest.yield_fixture(autouse=True)
|
||||
@pytest.fixture(autouse=True)
|
||||
def run_around_tests(local_config, main_window):
|
||||
"""
|
||||
This setup a temporay environnement around tests
|
||||
|
||||
@@ -61,6 +61,7 @@ def test_toSvg_negative_y(project, controller):
|
||||
|
||||
def test_fromSvg(project, controller):
|
||||
line = LineItem(project=project)
|
||||
line._main_window.uiSnapToGridAction.isChecked = lambda: False
|
||||
line.setPos(50, 84)
|
||||
line.fromSvg('<svg height="150" width="250"><line x1="0" y1="0" x2="250" y2="150" stroke-width="5" stroke="#0000ff" stroke-dasharray="5, 25, 25" /></svg>')
|
||||
assert line.line().x1() == 0
|
||||
@@ -75,6 +76,7 @@ def test_fromSvg(project, controller):
|
||||
|
||||
def test_fromSvg_top_direction(project, controller):
|
||||
line = LineItem(project=project)
|
||||
line._main_window.uiSnapToGridAction.isChecked = lambda: False
|
||||
line.setPos(50, 84)
|
||||
line.fromSvg('<svg height="150" width="250"><line x1="0" y1="150" x2="250" y2="0" stroke-width="5" stroke="#0000ff" stroke-dasharray="5, 25, 25" /></svg>')
|
||||
assert line.line().x1() == 0
|
||||
|
||||
50
tests/registry/appliances/arista-veos-v8.gns3a
Normal file
50
tests/registry/appliances/arista-veos-v8.gns3a
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"appliance_id": "1c784362-8aaf-4312-b0f5-4b138cf2e25b",
|
||||
"name": "Arista vEOS",
|
||||
"category": "router",
|
||||
"description": "Arista EOS® is the core of Arista cloud networking solutions for next-generation data centers and cloud networks. Cloud architectures built with Arista EOS scale to tens of thousands of compute and storage nodes with management and provisioning capabilities that work at scale. Through its programmability, EOS enables a set of software applications that deliver workflow automation, high availability, unprecedented network visibility and analytics and rapid integration with a wide range of third-party applications for virtualization, management, automation and orchestration services.\n\nArista Extensible Operating System (EOS) is a fully programmable and highly modular, Linux-based network operation system, using familiar industry standard CLI and runs a single binary software image across the Arista switching family. Architected for resiliency and programmability, EOS has a unique multi-process state sharing architecture that separates state information and packet forwarding from protocol processing and application logic.",
|
||||
"vendor_name": "Arista",
|
||||
"vendor_url": "http://www.arista.com/",
|
||||
"documentation_url": "http://www.arista.com/docs/Manuals/ConfigGuide.pdf",
|
||||
"product_name": "Arista vEOS",
|
||||
"product_url": "https://eos.arista.com/",
|
||||
"registry_version": 8,
|
||||
"status": "stable",
|
||||
"maintainer": "GNS3 Team",
|
||||
"maintainer_email": "developers@gns3.net",
|
||||
"settings": [
|
||||
{
|
||||
"template_type": "qemu",
|
||||
"template_properties": {
|
||||
"adapter_type": "e1000",
|
||||
"adapters": 8,
|
||||
"ram": 2048,
|
||||
"platform": "x86_64",
|
||||
"console_type": "telnet"
|
||||
}
|
||||
}
|
||||
],
|
||||
"images": [
|
||||
{
|
||||
"filename": "Aboot-veos-serial-2.1.0.iso",
|
||||
"version": "2.1.0",
|
||||
"md5sum": "2687534f2ff11b998dec0511066457c0",
|
||||
"download_url": "https://www.arista.com/en/support/software-download"
|
||||
},
|
||||
{
|
||||
"filename": "vEOS-lab-4.13.8M.vmdk",
|
||||
"version": "4.13.8M",
|
||||
"md5sum": "a47145b9e6e7a24171c0850f8755535e",
|
||||
"download_url": "https://www.arista.com/en/support/software-download"
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "4.13.8M",
|
||||
"images": {
|
||||
"hda_disk_image": "Aboot-veos-serial-2.1.0.iso",
|
||||
"hdb_disk_image": "vEOS-lab-4.13.8M.vmdk"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"appliance_id": "1c784362-8aaf-4312-b0f5-4b138cf2e25b",
|
||||
"name": "Arista vEOS",
|
||||
"category": "router",
|
||||
"description": "Arista EOS® is the core of Arista cloud networking solutions for next-generation data centers and cloud networks. Cloud architectures built with Arista EOS scale to tens of thousands of compute and storage nodes with management and provisioning capabilities that work at scale. Through its programmability, EOS enables a set of software applications that deliver workflow automation, high availability, unprecedented network visibility and analytics and rapid integration with a wide range of third-party applications for virtualization, management, automation and orchestration services.\n\nArista Extensible Operating System (EOS) is a fully programmable and highly modular, Linux-based network operation system, using familiar industry standard CLI and runs a single binary software image across the Arista switching family. Architected for resiliency and programmability, EOS has a unique multi-process state sharing architecture that separates state information and packet forwarding from protocol processing and application logic.",
|
||||
@@ -11,7 +12,6 @@
|
||||
"status": "stable",
|
||||
"maintainer": "GNS3 Team",
|
||||
"maintainer_email": "developers@gns3.net",
|
||||
|
||||
"qemu": {
|
||||
"adapter_type": "e1000",
|
||||
"adapters": 8,
|
||||
@@ -19,7 +19,6 @@
|
||||
"arch": "x86_64",
|
||||
"console_type": "telnet"
|
||||
},
|
||||
|
||||
"images": [
|
||||
{
|
||||
"filename": "Aboot-veos-serial-2.1.0.iso",
|
||||
@@ -34,7 +33,6 @@
|
||||
"download_url": "https://www.arista.com/en/support/software-download"
|
||||
}
|
||||
],
|
||||
|
||||
"versions": [
|
||||
{
|
||||
"name": "4.13.8M",
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
"status": "stable",
|
||||
"maintainer": "GNS3 Team",
|
||||
"maintainer_email": "developers@gns3.net",
|
||||
|
||||
"qemu": {
|
||||
"adapter_type": "e1000",
|
||||
"adapters": 1,
|
||||
@@ -19,7 +18,6 @@
|
||||
"arch": "i386",
|
||||
"console_type": "telnet"
|
||||
},
|
||||
|
||||
"images": [
|
||||
{
|
||||
"filename": "linux-microcore-3.4.1.img",
|
||||
@@ -30,7 +28,6 @@
|
||||
"direct_download_url": "http://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/linux-microcore-3.4.1.img"
|
||||
}
|
||||
],
|
||||
|
||||
"versions": [
|
||||
{
|
||||
"name": "3.4.1",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user