mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-05-28 22:40:30 +03:00
Compare commits
365 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e224a9ac29 | ||
|
|
5b87f36cfd | ||
|
|
a9354da184 | ||
|
|
91d7ed187b | ||
|
|
0f8724edc4 | ||
|
|
4e7f480af4 | ||
|
|
5ce4863baa | ||
|
|
bb5bfee256 | ||
|
|
0a74872ea2 | ||
|
|
20ed599e65 | ||
|
|
2722b2854b | ||
|
|
6b32d8bad6 | ||
|
|
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 | ||
|
|
98cc82e6fd | ||
|
|
4b795112b4 | ||
|
|
0e186afaf1 | ||
|
|
b218f7fdf8 | ||
|
|
f1cb6d66f3 | ||
|
|
29758f1b4f | ||
|
|
445dcf3e3b | ||
|
|
f623f28509 | ||
|
|
9d02d57162 | ||
|
|
df03f50e3d | ||
|
|
3b72a66ca5 | ||
|
|
f8aee44442 | ||
|
|
6dd4d1700e | ||
|
|
9b674669db | ||
|
|
53073d458f | ||
|
|
957d89d450 | ||
|
|
db02cbdb2f | ||
|
|
0c9f70152f | ||
|
|
9016975958 | ||
|
|
bf295060fd | ||
|
|
c22ec9f8bd | ||
|
|
bb49cadc6a | ||
|
|
131a49160c | ||
|
|
225b829eae | ||
|
|
e5ef6180b1 | ||
|
|
6e7947eea3 | ||
|
|
3922d370a8 | ||
|
|
833b9d00c9 | ||
|
|
377b8dfcaf | ||
|
|
e68937475f | ||
|
|
6f418f0853 | ||
|
|
8e59927ada | ||
|
|
1012686053 | ||
|
|
672bd850ad | ||
|
|
5db5e1f9fe | ||
|
|
ca94c71bf2 | ||
|
|
76264c55ce | ||
|
|
fd243c42a8 | ||
|
|
a6521ef9e4 | ||
|
|
9fa833762c | ||
|
|
ca0c6468b5 | ||
|
|
15f6945a94 | ||
|
|
645deb8c79 | ||
|
|
428f12a2b3 | ||
|
|
9ad5760ee6 | ||
|
|
82fc4fb3c9 | ||
|
|
df42147d92 | ||
|
|
da5520aa90 | ||
|
|
491c66a315 | ||
|
|
e5c81da700 | ||
|
|
65fad1b4f4 | ||
|
|
34661908d9 | ||
|
|
aee5ffa17f | ||
|
|
e9e8be42b5 | ||
|
|
ae0d928383 | ||
|
|
8db3c1be42 | ||
|
|
f50da3ebd7 | ||
|
|
75b52fc9a4 | ||
|
|
1952da5876 | ||
|
|
1f620026d4 | ||
|
|
1d293618e5 | ||
|
|
2622549ce6 | ||
|
|
900bd1c0b4 | ||
|
|
0b3dbb2843 | ||
|
|
ef4f6b2b27 | ||
|
|
e9806345ca | ||
|
|
ee23e32c75 | ||
|
|
fbeacdcb2a | ||
|
|
b3937c7b94 | ||
|
|
181bf3f360 | ||
|
|
f2711732db | ||
|
|
148ac4b072 | ||
|
|
65eeb79b26 | ||
|
|
537304ce08 | ||
|
|
f22df5f016 | ||
|
|
8dfc8b7714 | ||
|
|
8c6fa9433f | ||
|
|
63837578c5 | ||
|
|
b719703dbe | ||
|
|
084d14c17e | ||
|
|
8c0fca1dd7 | ||
|
|
863d05c923 | ||
|
|
3ebaac8a2c | ||
|
|
16878c9dfa | ||
|
|
45da18bb7c | ||
|
|
7a6d06ea0c | ||
|
|
d371042647 | ||
|
|
0321c11c34 | ||
|
|
522df41a57 | ||
|
|
afccdf5b9e | ||
|
|
b2cd24b511 | ||
|
|
6d131a05f1 | ||
|
|
35e6156c6c | ||
|
|
96d8de4da8 |
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 }}
|
||||
93
.github/workflows/codeql.yml
vendored
Normal file
93
.github/workflows/codeql.yml
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
schedule:
|
||||
- cron: '17 22 * * 6'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
# required to fetch internal or private CodeQL packs
|
||||
packages: read
|
||||
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: python
|
||||
build-mode: none
|
||||
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
|
||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# If the analyze step fails for one of the languages you are analyzing with
|
||||
# "We were unable to automatically build your code", modify the matrix above
|
||||
# to set the build mode to "manual" for that language. Then modify this step
|
||||
# to build your code.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
- if: matrix.build-mode == 'manual'
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||
'languages you are analyzing, replace this with the commands to build' \
|
||||
'your code, for example:'
|
||||
echo ' make bootstrap'
|
||||
echo ' make release'
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
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 .
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -63,3 +63,4 @@ __pycache__
|
||||
|
||||
# Virtualenv
|
||||
env
|
||||
venv
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"scanSettings": {
|
||||
"configMode": "AUTO",
|
||||
"configExternalURL": "",
|
||||
"projectToken" : ""
|
||||
"projectToken" : "",
|
||||
"baseBranches": ["master", "2.2", "3.0"]
|
||||
},
|
||||
"checkRunSettings": {
|
||||
"vulnerableCheckRunConclusionLevel": "failure"
|
||||
|
||||
211
CHANGELOG
211
CHANGELOG
@@ -1,5 +1,216 @@
|
||||
# Change Log
|
||||
|
||||
## 2.2.55 19/11/2025
|
||||
|
||||
* Fix SyntaxWarning: invalid escape sequence. Fixes #3760
|
||||
* Support for Python 3.14
|
||||
* Clicking the "console connect to all nodes" opens all consoles in name order with case-insensitively
|
||||
|
||||
## 2.2.54 21/04/2025
|
||||
|
||||
* Replace "Docker hub" by "Docker repository" because it is possible to use different repositories
|
||||
* Upgrade dependencies
|
||||
* Fix bring console in front when clicking on "Open all consoles". Fixes #3706
|
||||
* Add -F arg to wmctrl. Ref #3706
|
||||
|
||||
## 2.2.53 21/01/2025
|
||||
|
||||
* Update file browser filters for all files and IOU images
|
||||
* Upgrade dependencies
|
||||
* Fix Linux Mint default terminal configuration
|
||||
|
||||
## 2.2.52 02/12/2024
|
||||
|
||||
* Add iol extension filter. Ref #3664
|
||||
* Remove maximum 64GB RAM limitation for QEMU VMs. Fixes #3658
|
||||
* Bring to front support for consoles on Linux.
|
||||
* Relax setuptools requirement to allow for easier Debian packaging on Ubuntu Focal & Jammy
|
||||
|
||||
## 2.2.51 07/11/2024
|
||||
|
||||
* Python 3.13 support
|
||||
* Upgrade dependencies
|
||||
* Add keyboard shortcut for Add Link
|
||||
|
||||
## 2.2.50 21/10/2024
|
||||
|
||||
* Fix issue when pid file contains invalid data
|
||||
* Add comment to indicate sentry-sdk is optional. Ref https://github.com/GNS3/gns3-server/issues/2423
|
||||
* Improve information provided when uploading invalid appliance image. Fixes #3637
|
||||
* Use "experimental features" option to force listening for HTTP notification streams. Ref #3579
|
||||
* Fix to allow packet capture on more than 6 links. Fixes #3594
|
||||
* Support for configuring MAC address in Docker containers
|
||||
* Add KRDC to pre-configured VNC console commands
|
||||
|
||||
## 2.2.49 06/08/2024
|
||||
|
||||
* Upgrade jsonschema and sentry-sdk packages
|
||||
* Upgrade to PyQt5 v5.15.11
|
||||
* Add shortcuts info dialog
|
||||
* Added Key Shortcuts
|
||||
|
||||
## 2.2.48.1 12/07/2024
|
||||
|
||||
* No changes
|
||||
|
||||
## 2.2.48 08/07/2024
|
||||
|
||||
* Use "experimental features" to allow bypassing hostname validation. Ref #3524
|
||||
* Update appliance_v8.json. Ref https://github.com/GNS3/gns3-registry/pull/897
|
||||
* Option to keep the compute IDs unchanged when exporting a project
|
||||
* Upgrade sentry-sdk and psutil packages
|
||||
* Switch to PyQt5 5.15.10 for macOS build
|
||||
|
||||
## 2.2.47 15/05/2024
|
||||
|
||||
* Remove maximum size for capture dialog. Ref #3576
|
||||
* Change sentry-sdk version
|
||||
* Upgrade aiohttp, sentry-sdk and truststore
|
||||
* Upgrade jsonschema and aiohttp
|
||||
* Drop Python 3.7
|
||||
* Remove dev requirements for Python 3.6
|
||||
* Add NAT symbols
|
||||
* Only show log message if event has "message"
|
||||
|
||||
## 2.2.46 26/02/2024
|
||||
|
||||
* Add GNS3 console command "env" to show what environment variables are used. Ref https://github.com/GNS3/gns3-server/issues/2306
|
||||
* Add CTRL+C shortcut to copy status bar message. Ref #3561
|
||||
* Key modifier (ALT) to ignore snap to grid. Fixes #3538
|
||||
* Increase timeout to 5s for status bar messages. The coordinates message has no timeout and can be reset when clicking on the scene. Ref #3561
|
||||
* Add reset GUI state feature. Ref #3549
|
||||
* Fix for hiding Windows terminal. Ref #3290
|
||||
* Drop support for Python 3.6
|
||||
* Upgrade sentry-sdk, psutil and distro dependencies
|
||||
|
||||
## 2.2.45 12/01/2024
|
||||
|
||||
* Add missing console_type values in appliance_v8.json. Ref https://github.com/GNS3/gns3-registry/issues/849
|
||||
* Handle moved project notifications on controller stream
|
||||
* Add debug for PATH env variable
|
||||
* Add custom executable paths on Windows
|
||||
* Add --suppressApplicationTitle for Windows terminal. Fixes https://github.com/GNS3/gns3-gui/issues/3544
|
||||
* Upgrade sentry-sdk and aiohttp
|
||||
|
||||
## 2.2.44.1 07/11/2023
|
||||
|
||||
* No changes
|
||||
|
||||
## 2.2.44 06/11/2023
|
||||
|
||||
* Fix timeout issue when creating Qemu disk image. Fixes https://github.com/GNS3/gns3-server/issues/2313
|
||||
* Refactor command variables support
|
||||
* Add vendor_logo_url in appliance schemas. Ref https://github.com/GNS3/gns3-registry/pull/825
|
||||
* Add Qemu IGB network device
|
||||
* Add the ability to edit width and height in the style edit dialog.
|
||||
|
||||
## 2.2.43 19/09/2023
|
||||
|
||||
* Add KiTTY to preconfigured telnet consoles. Fixes #3507
|
||||
* Fix generic icon in Wayland. Ref #3501
|
||||
* Support for appliance format version 8.
|
||||
* Use importlib instead of pkg_resources
|
||||
* Upgrade to PyQt 5.15.9 and pywin32
|
||||
* Add support for appliance version 8 format
|
||||
|
||||
## 2.2.42 09/08/2023
|
||||
|
||||
* Use the system's certificate store for SSL connections
|
||||
* Give a node some time to start before opening the console (for console auto start). Fixes #3474
|
||||
* Use Mate Terminal by default if installed on Debian, Ubuntu and Linux Mint.
|
||||
* Support for gnome-terminal tabs to be opened in the same window.
|
||||
* Remove import urllib3 and let sentry_sdk import and patch it. Fixes https://github.com/GNS3/gns3-gui/issues/3498
|
||||
* Add import sys in sudo.py
|
||||
* Rounded Rectangle support
|
||||
|
||||
## 2.2.41 12/07/2023
|
||||
|
||||
* Use alternative method to set the correct permissions for uBridge on macOS
|
||||
* Remove sending stats to GA
|
||||
* Catch urllib3 exceptions when sending crash report. Ref https://github.com/GNS3/gns3-gui/issues/3483
|
||||
* Backport UEFI boot mode support for Qemu VMs
|
||||
* Add debug for dropEvent. Ref https://github.com/GNS3/gns3-server/issues/2242
|
||||
|
||||
## 2.2.40.1 10/06/2023
|
||||
|
||||
* No changes
|
||||
|
||||
## 2.2.40 06/06/2023
|
||||
|
||||
* Change log messages for Websocket errors
|
||||
* Do not proceed if an appliance symbol cannot be downloaded. Ref #3466
|
||||
* Delete a node or link from topology summary view using Delete key. Ref #3445
|
||||
* Fix "Start the capture visualization program" checkbox works only one (first) time for a given link. Fixes #3442
|
||||
* Let the selected link style applied when editing a link. Fixes #3460
|
||||
* Fix hovered color shown in style editing dialog. Fixes #3460
|
||||
|
||||
## 2.2.39 08/05/2023
|
||||
|
||||
* Fix nodes are not snapped to the grid at the moment of creation
|
||||
* Upgrade distro and aiohttp dependencies
|
||||
|
||||
## 2.2.38 28/02/2023
|
||||
|
||||
* Add long description content type in setup.py
|
||||
* Automatically add new issues to GNS3 project
|
||||
* Development 2.2.38.dev1
|
||||
|
||||
## 2.2.37 25/01/2023
|
||||
|
||||
* Upgrade to PyQt5 v5.15.7
|
||||
* Changed Windows Terminal telnet console profile from OS X to windows ref: issue #3193
|
||||
|
||||
## 2.2.36 04/01/2023
|
||||
|
||||
* Add Trusted Platform Module (TPM) support for Qemu VMs
|
||||
* Add "on_close" setting to appliance schema. Fixes https://github.com/GNS3/gns3-server/issues/2148
|
||||
* Add default 'ide' disk interface when manually creating Qemu VM template. Fixes #3360
|
||||
* Fix zoom factor is multiplied when loading projects. Fixes #3408
|
||||
* Remove deprecated PuTTY option in preferences. Ref https://github.com/GNS3/gns3-gui/discussions/3415
|
||||
|
||||
|
||||
## 2.2.35.1 10/11/2022
|
||||
|
||||
* Re-release Web-Ui v2.2.35
|
||||
|
||||
## 2.2.35 08/11/2022
|
||||
|
||||
* Fix "variables": [] in project file leads to unlimited increase of empty name/value pairs in GUI. Fixes #3397
|
||||
* Make version PEP 440 compliant
|
||||
* Support for Python 3.11
|
||||
* Upgrade PyQt to 5.15.7 and pywin32 to v305
|
||||
* Allow for more dependency versions at patch level
|
||||
* Replace deprecated distro.linux_distribution() call
|
||||
* Add a fix for the CVE-2007-4559
|
||||
|
||||
## 2.2.34 28/08/2022
|
||||
|
||||
* Upgrade dev dependencies
|
||||
* Implement new option (Delete All) to contextual menu in "Console" dock. Fixes #3325
|
||||
* Fix 2560x1440 resolution for Docker container
|
||||
|
||||
## 2.2.33.1 21/06/2022
|
||||
|
||||
* Match GNS3 server version
|
||||
|
||||
## 2.2.33 20/06/2022
|
||||
|
||||
* Upgrade sentry-sdk and psutil
|
||||
* Check that node names for Qemu and Docker are valid
|
||||
* Backport reset all console connections. Fixes #2072
|
||||
* Add more video resolutions to Docker containers using VNC. Fixes #3329
|
||||
* Add python_requires=">=3.4" in setup.py. Fixes #3326
|
||||
* Only allow post release corrective versions of GUI and server to interact
|
||||
* Allow minor versions of GUI and server to interact
|
||||
* Update VirtViewer path. Fixes #3334
|
||||
|
||||
## 2.2.32 27/04/2022
|
||||
|
||||
* Use public DSNs for Sentry
|
||||
* Fix exception when doubleclick on NAT node. Fixes #3312
|
||||
* Fix "Apply" button in the "Preferences" dialog stays gray when templates/nodes are opened by double-click. Fixes #3307
|
||||
* Add 'reset docks' in the view menu. Ref #3317
|
||||
|
||||
## 2.2.31 26/02/2022
|
||||
|
||||
* Install setuptools v59.6.0 when using Python 3.6
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
include README.rst
|
||||
include README.md
|
||||
include AUTHORS
|
||||
include LICENSE
|
||||
include MANIFEST.in
|
||||
|
||||
62
README.md
Normal file
62
README.md
Normal file
@@ -0,0 +1,62 @@
|
||||
GNS3-gui
|
||||
========
|
||||
|
||||
[](https://github.com/GNS3/gns3-gui/actions?query=workflow%3Atesting)
|
||||
|
||||
[](https://pypi.python.org/pypi/gns3-gui)
|
||||
|
||||
[](https://snyk.io/test/github/GNS3/gns3-gui)
|
||||
|
||||
GNS3 GUI repository.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Please see <https://docs.gns3.com/>
|
||||
|
||||
Software dependencies
|
||||
---------------------
|
||||
|
||||
PyQt5 which is either part of the Linux distribution or installable from
|
||||
PyPi. The other Python dependencies are automatically installed during
|
||||
the GNS3 GUI installation and are listed
|
||||
[here](https://github.com/GNS3/gns3-gui/blob/master/requirements.txt)
|
||||
|
||||
For connecting to nodes using Telnet, a Telnet client is required. On
|
||||
Linux that's a terminal emulator like xterm, gnome-terminal, konsole
|
||||
plus the telnet program. For connecting to nodes with a GUI, a VNC
|
||||
client is required, optionally a SPICE client can be used for Qemu
|
||||
nodes.
|
||||
|
||||
For using packet captures within GNS3, Wireshark should be installed.
|
||||
It's recommended, but if you don't need that functionality you can go
|
||||
without it.
|
||||
|
||||
Development
|
||||
-----------
|
||||
|
||||
If you want to update the interface, modify the .ui files using QT
|
||||
tools. And:
|
||||
|
||||
``` {.bash}
|
||||
cd scripts
|
||||
python build_pyqt.py
|
||||
```
|
||||
|
||||
### Debug
|
||||
|
||||
If you want to see the full logs in the internal shell you can type:
|
||||
|
||||
``` {.bash}
|
||||
debug 2
|
||||
```
|
||||
|
||||
Or start the app with --debug flag.
|
||||
|
||||
Due to the fact PyQT intercept you can use a web debugger for inspecting
|
||||
stuff: <https://github.com/Kozea/wdb>
|
||||
|
||||
Security issues
|
||||
---------------
|
||||
|
||||
Please contact us at <security@gns3.net>
|
||||
60
README.rst
60
README.rst
@@ -1,60 +0,0 @@
|
||||
GNS3-gui
|
||||
========
|
||||
|
||||
.. image:: https://github.com/GNS3/gns3-gui/workflows/testing/badge.svg
|
||||
:target: https://github.com/GNS3/gns3-gui/actions?query=workflow%3Atesting
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/gns3-gui.svg
|
||||
:target: https://pypi.python.org/pypi/gns3-gui
|
||||
|
||||
.. image:: https://snyk.io/test/github/GNS3/gns3-gui/badge.svg
|
||||
:target: https://snyk.io/test/github/GNS3/gns3-gui
|
||||
|
||||
|
||||
GNS3 GUI repository.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Please see https://docs.gns3.com/
|
||||
|
||||
Software dependencies
|
||||
---------------------
|
||||
|
||||
PyQt5 which is either part of the Linux distribution or installable from PyPi. The other Python dependencies are automatically installed during the GNS3 GUI installation and are listed `here <https://github.com/GNS3/gns3-gui/blob/master/requirements.txt>`_
|
||||
|
||||
For connecting to nodes using Telnet, a Telnet client is required. On Linux that's a terminal emulator like xterm, gnome-terminal, konsole plus the telnet program. For connecting to nodes with a GUI, a VNC client is required, optionally a SPICE client can be used for Qemu nodes.
|
||||
|
||||
For using packet captures within GNS3, Wireshark should be installed. It's recommended, but if you don't need that functionality you can go without it.
|
||||
|
||||
Development
|
||||
-------------
|
||||
|
||||
If you want to update the interface, modify the .ui files using QT tools. And:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
cd scripts
|
||||
python build_pyqt.py
|
||||
|
||||
Debug
|
||||
"""""
|
||||
|
||||
If you want to see the full logs in the internal shell you can type:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
debug 2
|
||||
|
||||
|
||||
Or start the app with --debug flag.
|
||||
|
||||
Due to the fact PyQT intercept you can use a web debugger for inspecting stuff:
|
||||
https://github.com/Kozea/wdb
|
||||
|
||||
Security issues
|
||||
----------------
|
||||
|
||||
Please contact us at security@gns3.net
|
||||
|
||||
|
||||
5
SECURITY.md
Normal file
5
SECURITY.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please use GitHub's report a vulnerability feature. More information can be found in https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability
|
||||
19
appveyor.yml
19
appveyor.yml
@@ -1,19 +0,0 @@
|
||||
version: '{build}-{branch}'
|
||||
|
||||
image: Visual Studio 2017
|
||||
|
||||
platform: x64
|
||||
|
||||
environment:
|
||||
PYTHON: "C:\\Python36-x64"
|
||||
DISTUTILS_USE_SDK: "1"
|
||||
|
||||
install:
|
||||
- cinst nmap
|
||||
- "%PYTHON%\\python.exe -m pip install -r dev-requirements.txt"
|
||||
- "%PYTHON%\\python.exe -m pip install -r win-requirements.txt"
|
||||
|
||||
build: off
|
||||
|
||||
test_script:
|
||||
- "%PYTHON%\\python.exe -m pytest -v"
|
||||
@@ -1,5 +1,4 @@
|
||||
-rrequirements.txt
|
||||
|
||||
pytest==6.2.4
|
||||
flake8==3.9.2
|
||||
pytest-timeout==1.4.2
|
||||
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.
|
||||
|
||||
@@ -22,7 +22,7 @@ import inspect
|
||||
import datetime
|
||||
import platform
|
||||
|
||||
from .qt import QtCore
|
||||
from .qt import QtCore, QtWidgets
|
||||
from .topology import Topology
|
||||
from .version import __version__
|
||||
from .console_cmd import ConsoleCmd
|
||||
@@ -109,6 +109,29 @@ class ConsoleView(PyCutExt, ConsoleCmd):
|
||||
self.stdout = sys.stdout
|
||||
self._topology = Topology.instance()
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
"""
|
||||
Handles all context menu events.
|
||||
|
||||
:param event: QContextMenuEvent instance
|
||||
"""
|
||||
|
||||
menu = self.createStandardContextMenu()
|
||||
delete_all_action = QtWidgets.QAction("Delete All", menu)
|
||||
delete_all_action.triggered.connect(self._deleteAllActionSlot)
|
||||
menu.addAction(delete_all_action)
|
||||
menu.exec_(event.globalPos());
|
||||
|
||||
def _deleteAllActionSlot(self):
|
||||
"""
|
||||
Delete all action slot
|
||||
"""
|
||||
|
||||
self.clear()
|
||||
self.write(self.prompt)
|
||||
self.lines = []
|
||||
self._clearLine()
|
||||
|
||||
def _writeMessageSlot(self, message, level):
|
||||
"""
|
||||
Write a message in the console.
|
||||
|
||||
@@ -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://3af06592e0804039befaae43066b8fb8:73e4b6fd709e4622a60c86ac5ece632d@o19455.ingest.sentry.io/38506"
|
||||
DSN = "https://4787643437b9cd21a738c97fe5595c2b@o19455.ingest.us.sentry.io/38506"
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
@@ -64,29 +63,23 @@ 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(),
|
||||
"os:release": platform.release(),
|
||||
"os:win_32": " ".join(platform.win32_ver()),
|
||||
"os:mac": "{} {}".format(platform.mac_ver()[0], platform.mac_ver()[2]),
|
||||
"os:linux": " ".join(distro.linux_distribution()),
|
||||
"os:linux": distro.name(pretty=True),
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -46,19 +46,11 @@ class EditProjectDialog(QtWidgets.QDialog, Ui_EditProjectDialog):
|
||||
self.uiNewVarButton.clicked.connect(self.onAddNewVariable)
|
||||
self.uiGlobalVariablesGrid.addWidget(self.uiNewVarButton, 0, 3, QtCore.Qt.AlignRight)
|
||||
|
||||
self._variables = self.setUpVariables()
|
||||
self._variables = self._project.variables()
|
||||
if not self._variables:
|
||||
self._variables = [{"name": "", "value": ""}]
|
||||
self.updateGlobalVariables()
|
||||
|
||||
def setUpVariables(self):
|
||||
new_variable = {"name": "", "value": ""}
|
||||
variables = self._project.variables()
|
||||
|
||||
if variables is not None:
|
||||
variables.append(new_variable)
|
||||
else:
|
||||
variables = [new_variable]
|
||||
return variables
|
||||
|
||||
def updateGlobalVariables(self):
|
||||
while True:
|
||||
item = self.uiGlobalVariablesGrid.takeAt(1)
|
||||
|
||||
@@ -131,6 +131,7 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
|
||||
QtWidgets.QLineEdit: "textChanged",
|
||||
QtWidgets.QPlainTextEdit: "textChanged",
|
||||
# QtWidgets.QTreeWidget: "itemChanged",
|
||||
QtWidgets.QTreeWidget: "itemDoubleClicked",
|
||||
QtWidgets.QComboBox: "currentIndexChanged",
|
||||
QtWidgets.QSpinBox: "valueChanged",
|
||||
QtWidgets.QAbstractButton: "pressed"
|
||||
|
||||
@@ -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)
|
||||
@@ -121,8 +127,6 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
def setZoom(self, zoom):
|
||||
"""
|
||||
Sets zoom of the Graphics View
|
||||
:param zoom:
|
||||
:return:
|
||||
"""
|
||||
if zoom:
|
||||
factor = zoom / 100.
|
||||
@@ -193,6 +197,9 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
# clear all objects on the scene
|
||||
self.scene().clear()
|
||||
|
||||
# reset zoom / scale
|
||||
self.resetTransform()
|
||||
|
||||
|
||||
def _loadSettings(self):
|
||||
"""
|
||||
@@ -437,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
|
||||
@@ -594,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):
|
||||
"""
|
||||
@@ -639,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():
|
||||
@@ -659,11 +668,11 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
if not self._adding_link:
|
||||
if isinstance(item, NodeItem) and item.node().initialized():
|
||||
item.setSelected(True)
|
||||
if item.node().status() == Node.stopped or item.node().consoleType() == "none":
|
||||
if item.node().status() == Node.stopped or item.node().consoleType() == "none" or item.node().consoleType() is None:
|
||||
self.configureSlot()
|
||||
return
|
||||
else:
|
||||
if sys.platform.startswith("win") and item.node().bringToFront():
|
||||
if item.node().bringToFront():
|
||||
return
|
||||
self.consoleFromItems(self.scene().selectedItems())
|
||||
return
|
||||
@@ -712,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()
|
||||
@@ -856,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)
|
||||
@@ -1028,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
|
||||
@@ -1127,7 +1137,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
|
||||
delay = self._main_window.settings()["delay_console_all"]
|
||||
counter = 0
|
||||
for name in sorted(nodes.keys()):
|
||||
for name in sorted(nodes.keys(), key=str.casefold):
|
||||
node = nodes[name]
|
||||
callback = qpartial(self.consoleToNode, node)
|
||||
self._main_window.run_later(counter, callback)
|
||||
@@ -1206,7 +1216,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
|
||||
delay = self._main_window.settings()["delay_console_all"]
|
||||
counter = 0
|
||||
for name in sorted(nodes.keys()):
|
||||
for name in sorted(nodes.keys(), key=str.casefold):
|
||||
node = nodes[name]
|
||||
callback = qpartial(self.consoleToNode, node, aux=True)
|
||||
self._main_window.run_later(counter, callback)
|
||||
@@ -1245,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
|
||||
@@ -1292,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)
|
||||
@@ -1655,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:
|
||||
|
||||
@@ -404,17 +404,12 @@ class HTTPClient(QtCore.QObject):
|
||||
self._query_waiting_connections = []
|
||||
return
|
||||
|
||||
if params["version"].split("-")[0] != __version__.split("-")[0]:
|
||||
if params["version"].split("+")[0] != __version__.split("+")[0]:
|
||||
msg = "Client version {} is not the same as server (controller) version {}".format(__version__, params["version"])
|
||||
# Stable release
|
||||
if __version_info__[3] == 0:
|
||||
log.error(msg)
|
||||
for request, callback in self._query_waiting_connections:
|
||||
if callback is not None:
|
||||
callback({"message": msg}, error=True, server=server)
|
||||
return
|
||||
# We don't allow different major version to interact even with dev build
|
||||
elif parse_version(__version__)[:2] != parse_version(params["version"])[:2]:
|
||||
# We don't allow different versions to interact even with dev build
|
||||
# (excepting post release corrections e.g 2.2.32.1, occassionally done when fixing a packaging problem)
|
||||
# TODO: we should probably follow this standard starting with v3.0: https://semver.org/
|
||||
if parse_version(__version__)[:3] != parse_version(params["version"])[:3]:
|
||||
log.error(msg)
|
||||
for request, callback in self._query_waiting_connections:
|
||||
if callback is not None:
|
||||
|
||||
@@ -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
|
||||
|
||||
41
gns3/main.py
41
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))
|
||||
@@ -220,10 +219,17 @@ def main():
|
||||
if not options.debug:
|
||||
try:
|
||||
# hide the console
|
||||
# win32console.AllocConsole()
|
||||
console_window = win32console.GetConsoleWindow()
|
||||
win32gui.ShowWindow(console_window, win32con.SW_HIDE)
|
||||
parent_window = win32gui.GetParent(console_window)
|
||||
if not parent_window and console_window:
|
||||
win32gui.ShowWindow(console_window, win32con.SW_HIDE)
|
||||
elif parent_window:
|
||||
win32gui.ShowWindow(parent_window, win32con.SW_HIDE)
|
||||
else:
|
||||
log.warning("Could not get the console window")
|
||||
except win32console.error as e:
|
||||
print("warning: could not allocate console: {}".format(e))
|
||||
log.warning("Could not allocate console: {}".format(e))
|
||||
|
||||
local_config = LocalConfig.instance()
|
||||
|
||||
@@ -259,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,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.uiShowGridAction.triggered.connect(self._showGridActionSlot)
|
||||
self.uiSnapToGridAction.triggered.connect(self._snapToGridActionSlot)
|
||||
self.uiLockAllAction.triggered.connect(self._lockActionSlot)
|
||||
self.uiResetGUIStateAction.triggered.connect(self._resetGUIState)
|
||||
self.uiResetDocksAction.triggered.connect(self._resetDocksSlot)
|
||||
|
||||
# tool menu connections
|
||||
self.uiWebUIAction.triggered.connect(self._openWebInterfaceActionSlot)
|
||||
@@ -245,6 +246,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.uiReloadAllAction.triggered.connect(self._reloadAllActionSlot)
|
||||
self.uiAuxConsoleAllAction.triggered.connect(self._auxConsoleAllActionSlot)
|
||||
self.uiConsoleAllAction.triggered.connect(self._consoleAllActionSlot)
|
||||
self.uiResetConsoleAllAction.triggered.connect(self._consoleResetAllActionSlot)
|
||||
|
||||
# device menu is contextual and is build on-the-fly
|
||||
self.uiDeviceMenu.aboutToShow.connect(self._deviceMenuActionSlot)
|
||||
@@ -266,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)
|
||||
@@ -366,12 +369,27 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
item.updateNode()
|
||||
item.update()
|
||||
|
||||
def analyticsClient(self):
|
||||
def _resetGUIState(self):
|
||||
"""
|
||||
Return the analytics client
|
||||
Reset the GUI state.
|
||||
"""
|
||||
|
||||
return self._analytics_client
|
||||
self._save_gui_state_geometry = False
|
||||
self.close()
|
||||
if hasattr(sys, "frozen"):
|
||||
QtCore.QProcess.startDetached(os.path.abspath(sys.executable), sys.argv)
|
||||
else:
|
||||
QtWidgets.QMessageBox.information(self, "GUI state","The GUI state has been reset, please restart the application")
|
||||
|
||||
def _resetDocksSlot(self):
|
||||
"""
|
||||
Reset the dock widgets.
|
||||
"""
|
||||
|
||||
self.uiTopologySummaryDockWidget.setFloating(False)
|
||||
self.uiComputeSummaryDockWidget.setFloating(False)
|
||||
self.uiConsoleDockWidget.setFloating(False)
|
||||
self.uiNodesDockWidget.setFloating(False)
|
||||
|
||||
def _newProjectActionSlot(self):
|
||||
"""
|
||||
@@ -410,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)
|
||||
@@ -429,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)
|
||||
@@ -859,6 +877,15 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
if project is not None:
|
||||
project.reload_all_nodes()
|
||||
|
||||
def _consoleResetAllActionSlot(self):
|
||||
"""
|
||||
Slot called when reset all console connections.
|
||||
"""
|
||||
|
||||
project = Topology.instance().project()
|
||||
if project is not None:
|
||||
project.reset_console_all_nodes()
|
||||
|
||||
def _deviceMenuActionSlot(self):
|
||||
"""
|
||||
Slot to contextually show the device menu.
|
||||
@@ -893,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:
|
||||
@@ -953,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.
|
||||
@@ -1101,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)
|
||||
|
||||
@@ -1123,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()
|
||||
@@ -1139,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()
|
||||
@@ -1213,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
|
||||
@@ -1408,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
|
||||
|
||||
@@ -116,6 +120,18 @@ class DockerVM(Node):
|
||||
from .pages.docker_vm_configuration_page import DockerVMConfigurationPage
|
||||
return DockerVMConfigurationPage
|
||||
|
||||
@staticmethod
|
||||
def validateHostname(hostname):
|
||||
"""
|
||||
Checks if the hostname is valid.
|
||||
|
||||
:param hostname: hostname to check
|
||||
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
return DockerVM.isValidRfc1123Hostname(hostname)
|
||||
|
||||
@staticmethod
|
||||
def defaultSymbol():
|
||||
"""
|
||||
|
||||
@@ -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,20 +176,35 @@
|
||||
</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">
|
||||
<string>2560x1440</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>1920x1080</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>1680x1050</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>1440x900</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>1366x768</string>
|
||||
@@ -212,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>
|
||||
@@ -229,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:
|
||||
@@ -253,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,16 +2,19 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/docker/ui/docker_vm_configuration_page.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.11.3
|
||||
# Created by: PyQt5 UI code generator 5.15.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_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)
|
||||
@@ -64,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")
|
||||
@@ -87,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("")
|
||||
@@ -100,35 +109,38 @@ class Ui_dockerVMConfigPageWidget(object):
|
||||
self.uiConsoleResolutionComboBox.addItem("")
|
||||
self.uiConsoleResolutionComboBox.addItem("")
|
||||
self.uiConsoleResolutionComboBox.addItem("")
|
||||
self.gridLayout.addWidget(self.uiConsoleResolutionComboBox, 8, 1, 1, 1)
|
||||
self.uiConsoleResolutionComboBox.addItem("")
|
||||
self.uiConsoleResolutionComboBox.addItem("")
|
||||
self.uiConsoleResolutionComboBox.addItem("")
|
||||
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")
|
||||
@@ -180,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:"))
|
||||
@@ -190,13 +203,16 @@ class Ui_dockerVMConfigPageWidget(object):
|
||||
self.uiConsoleTypeComboBox.setItemText(4, _translate("dockerVMConfigPageWidget", "none"))
|
||||
self.uiConsoleAutoStartCheckBox.setText(_translate("dockerVMConfigPageWidget", "Auto start console"))
|
||||
self.uiConsoleResolutionLabel.setText(_translate("dockerVMConfigPageWidget", "VNC console resolution:"))
|
||||
self.uiConsoleResolutionComboBox.setItemText(0, _translate("dockerVMConfigPageWidget", "1920x1080"))
|
||||
self.uiConsoleResolutionComboBox.setItemText(1, _translate("dockerVMConfigPageWidget", "1366x768"))
|
||||
self.uiConsoleResolutionComboBox.setItemText(2, _translate("dockerVMConfigPageWidget", "1280x1024"))
|
||||
self.uiConsoleResolutionComboBox.setItemText(3, _translate("dockerVMConfigPageWidget", "1280x800"))
|
||||
self.uiConsoleResolutionComboBox.setItemText(4, _translate("dockerVMConfigPageWidget", "1024x768"))
|
||||
self.uiConsoleResolutionComboBox.setItemText(5, _translate("dockerVMConfigPageWidget", "800x600"))
|
||||
self.uiConsoleResolutionComboBox.setItemText(6, _translate("dockerVMConfigPageWidget", "640x480"))
|
||||
self.uiConsoleResolutionComboBox.setItemText(0, _translate("dockerVMConfigPageWidget", "2560x1440"))
|
||||
self.uiConsoleResolutionComboBox.setItemText(1, _translate("dockerVMConfigPageWidget", "1920x1080"))
|
||||
self.uiConsoleResolutionComboBox.setItemText(2, _translate("dockerVMConfigPageWidget", "1680x1050"))
|
||||
self.uiConsoleResolutionComboBox.setItemText(3, _translate("dockerVMConfigPageWidget", "1440x900"))
|
||||
self.uiConsoleResolutionComboBox.setItemText(4, _translate("dockerVMConfigPageWidget", "1366x768"))
|
||||
self.uiConsoleResolutionComboBox.setItemText(5, _translate("dockerVMConfigPageWidget", "1280x1024"))
|
||||
self.uiConsoleResolutionComboBox.setItemText(6, _translate("dockerVMConfigPageWidget", "1280x800"))
|
||||
self.uiConsoleResolutionComboBox.setItemText(7, _translate("dockerVMConfigPageWidget", "1024x768"))
|
||||
self.uiConsoleResolutionComboBox.setItemText(8, _translate("dockerVMConfigPageWidget", "800x600"))
|
||||
self.uiConsoleResolutionComboBox.setItemText(9, _translate("dockerVMConfigPageWidget", "640x480"))
|
||||
self.label.setText(_translate("dockerVMConfigPageWidget", "HTTP port in the container:"))
|
||||
self.label_2.setText(_translate("dockerVMConfigPageWidget", "HTTP path:"))
|
||||
self.uiEnvironmentLabel.setText(_translate("dockerVMConfigPageWidget", "Environment variables:\n"
|
||||
@@ -217,4 +233,3 @@ class Ui_dockerVMConfigPageWidget(object):
|
||||
self.uiExtraVolumeTextEdit.setPlaceholderText(_translate("dockerVMConfigPageWidget", "e.g. /etc/sysctl.d"))
|
||||
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.tab_2), _translate("dockerVMConfigPageWidget", "Advanced"))
|
||||
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.tab_3), _translate("dockerVMConfigPageWidget", "Usage"))
|
||||
|
||||
|
||||
@@ -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:"))
|
||||
|
||||
@@ -310,8 +310,8 @@ class Router(Node):
|
||||
|
||||
# IOS names must start with a letter, end with a letter or digit, and
|
||||
# have as interior characters only letters, digits, and hyphens.
|
||||
# They must be 63 characters or fewer.
|
||||
if re.search(r"""^[\-\w]+$""", hostname) and len(hostname) <= 63:
|
||||
# They must be 63 characters or fewer (ARPANET rules).
|
||||
if re.search(r"""^(?!-|[0-9])[a-zA-Z0-9-]{1,63}(?<!-)$""", hostname):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -131,8 +131,8 @@ class IOUDevice(Node):
|
||||
|
||||
# IOS names must start with a letter, end with a letter or digit, and
|
||||
# have as interior characters only letters, digits, and hyphens.
|
||||
# They must be 63 characters or fewer.
|
||||
if re.search(r"""^[\-\w]+$""", hostname) and len(hostname) <= 63:
|
||||
# They must be 63 characters or fewer (ARPANET rules).
|
||||
if re.search(r"""^(?!-|[0-9])[a-zA-Z0-9-]{1,63}(?<!-)$""", hostname):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -157,6 +157,7 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
|
||||
|
||||
if self.uiHdaDiskImageLineEdit.text().strip():
|
||||
settings["hda_disk_image"] = self.uiHdaDiskImageLineEdit.text().strip()
|
||||
settings["hda_disk_interface"] = "ide"
|
||||
|
||||
if self.uiLegacyASACheckBox.isChecked():
|
||||
# special settings for legacy ASA VM
|
||||
|
||||
@@ -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"),
|
||||
@@ -578,6 +579,8 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
self.uiProcessPriorityComboBox.setCurrentIndex(index)
|
||||
self.uiQemuOptionsLineEdit.setText(settings["options"])
|
||||
self.uiUsageTextEdit.setPlainText(settings["usage"])
|
||||
self.uiTPMCheckBox.setChecked(settings["tpm"])
|
||||
self.uiUEFICheckBox.setChecked(settings["uefi"])
|
||||
|
||||
def saveSettings(self, settings, node=None, group=False):
|
||||
"""
|
||||
@@ -692,4 +695,6 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
settings["process_priority"] = self.uiProcessPriorityComboBox.currentText().lower()
|
||||
settings["options"] = self.uiQemuOptionsLineEdit.text()
|
||||
settings["usage"] = self.uiUsageTextEdit.toPlainText()
|
||||
settings["tpm"] = self.uiTPMCheckBox.isChecked()
|
||||
settings["uefi"] = self.uiUEFICheckBox.isChecked()
|
||||
return settings
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
QEMU VM implementation.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from gns3.node import Node
|
||||
from .settings import QEMU_VM_SETTINGS
|
||||
|
||||
@@ -72,6 +74,8 @@ class QemuVM(Node):
|
||||
"mac_address": QEMU_VM_SETTINGS["mac_address"],
|
||||
"legacy_networking": QEMU_VM_SETTINGS["legacy_networking"],
|
||||
"replicate_network_connection_state": QEMU_VM_SETTINGS["replicate_network_connection_state"],
|
||||
"tpm": QEMU_VM_SETTINGS["tpm"],
|
||||
"uefi": QEMU_VM_SETTINGS["uefi"],
|
||||
"create_config_disk": QEMU_VM_SETTINGS["create_config_disk"],
|
||||
"platform": QEMU_VM_SETTINGS["platform"],
|
||||
"on_close": QEMU_VM_SETTINGS["on_close"],
|
||||
@@ -163,6 +167,18 @@ class QemuVM(Node):
|
||||
from .pages.qemu_vm_configuration_page import QemuVMConfigurationPage
|
||||
return QemuVMConfigurationPage
|
||||
|
||||
@staticmethod
|
||||
def validateHostname(hostname):
|
||||
"""
|
||||
Checks if the hostname is valid.
|
||||
|
||||
:param hostname: hostname to check
|
||||
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
return QemuVM.isValidRfc1123Hostname(hostname)
|
||||
|
||||
@staticmethod
|
||||
def defaultSymbol():
|
||||
"""
|
||||
|
||||
@@ -57,6 +57,8 @@ QEMU_VM_SETTINGS = {
|
||||
"mac_address": "",
|
||||
"legacy_networking": False,
|
||||
"replicate_network_connection_state": True,
|
||||
"tpm": False,
|
||||
"uefi": False,
|
||||
"create_config_disk": False,
|
||||
"on_close": "power_off",
|
||||
"platform": "",
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>941</width>
|
||||
<height>939</height>
|
||||
<width>478</width>
|
||||
<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="1" column="0" colspan="2">
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiBaseVMCheckBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
@@ -900,10 +896,33 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiTPMCheckBox">
|
||||
<property name="text">
|
||||
<string>Enable Trusted Platform Module (TPM)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiQemuOptionsLabel">
|
||||
<property name="text">
|
||||
<string>Options:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiUEFICheckBox">
|
||||
<property name="text">
|
||||
<string>Enable UEFI boot mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<zorder>uiQemuOptionsLineEdit</zorder>
|
||||
<zorder>uiQemuOptionsLabel</zorder>
|
||||
<zorder>uiBaseVMCheckBox</zorder>
|
||||
<zorder>uiTPMCheckBox</zorder>
|
||||
<zorder>uiUEFICheckBox</zorder>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/qemu/ui/qemu_vm_configuration_page.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.13.2
|
||||
# Created by: PyQt5 UI code generator 5.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_QemuVMConfigPageWidget(object):
|
||||
def setupUi(self, QemuVMConfigPageWidget):
|
||||
QemuVMConfigPageWidget.setObjectName("QemuVMConfigPageWidget")
|
||||
QemuVMConfigPageWidget.resize(941, 939)
|
||||
QemuVMConfigPageWidget.resize(478, 579)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(QemuVMConfigPageWidget)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiQemutabWidget = QtWidgets.QTabWidget(QemuVMConfigPageWidget)
|
||||
@@ -58,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)
|
||||
@@ -427,19 +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, 1, 0, 1, 2)
|
||||
self.gridLayout_3.addWidget(self.uiBaseVMCheckBox, 3, 0, 1, 2)
|
||||
self.uiTPMCheckBox = QtWidgets.QCheckBox(self.groupBox)
|
||||
self.uiTPMCheckBox.setObjectName("uiTPMCheckBox")
|
||||
self.gridLayout_3.addWidget(self.uiTPMCheckBox, 1, 0, 1, 2)
|
||||
self.uiQemuOptionsLabel = QtWidgets.QLabel(self.groupBox)
|
||||
self.uiQemuOptionsLabel.setObjectName("uiQemuOptionsLabel")
|
||||
self.gridLayout_3.addWidget(self.uiQemuOptionsLabel, 0, 0, 1, 1)
|
||||
self.uiUEFICheckBox = QtWidgets.QCheckBox(self.groupBox)
|
||||
self.uiUEFICheckBox.setObjectName("uiUEFICheckBox")
|
||||
self.gridLayout_3.addWidget(self.uiUEFICheckBox, 2, 0, 1, 2)
|
||||
self.uiQemuOptionsLineEdit.raise_()
|
||||
self.uiQemuOptionsLabel.raise_()
|
||||
self.uiBaseVMCheckBox.raise_()
|
||||
self.uiTPMCheckBox.raise_()
|
||||
self.uiUEFICheckBox.raise_()
|
||||
self.verticalLayout_2.addWidget(self.groupBox)
|
||||
spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.verticalLayout_2.addItem(spacerItem4)
|
||||
@@ -544,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"
|
||||
@@ -556,5 +565,8 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
"</ul>\n"
|
||||
"</body></html>"))
|
||||
self.uiBaseVMCheckBox.setText(_translate("QemuVMConfigPageWidget", "Use as a linked base VM"))
|
||||
self.uiTPMCheckBox.setText(_translate("QemuVMConfigPageWidget", "Enable Trusted Platform Module (TPM)"))
|
||||
self.uiQemuOptionsLabel.setText(_translate("QemuVMConfigPageWidget", "Options:"))
|
||||
self.uiUEFICheckBox.setText(_translate("QemuVMConfigPageWidget", "Enable UEFI boot mode"))
|
||||
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiAdvancedSettingsTab), _translate("QemuVMConfigPageWidget", "Advanced"))
|
||||
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiUsageTab), _translate("QemuVMConfigPageWidget", "Usage"))
|
||||
|
||||
@@ -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:
|
||||
|
||||
78
gns3/node.py
78
gns3/node.py
@@ -16,7 +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
|
||||
@@ -650,10 +654,11 @@ class Node(BaseNode):
|
||||
|
||||
if not console_type:
|
||||
console_type = self.consoleType()
|
||||
if console_type == "vnc":
|
||||
return general_settings["vnc_console_command"]
|
||||
elif console_type.startswith("spice"):
|
||||
return general_settings["spice_console_command"]
|
||||
if console_type:
|
||||
if console_type == "vnc":
|
||||
return general_settings["vnc_console_command"]
|
||||
elif console_type.startswith("spice"):
|
||||
return general_settings["spice_console_command"]
|
||||
return general_settings["telnet_console_command"]
|
||||
|
||||
def consoleType(self):
|
||||
@@ -687,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):
|
||||
"""
|
||||
@@ -719,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)
|
||||
@@ -737,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):
|
||||
@@ -863,6 +884,33 @@ class Node(BaseNode):
|
||||
if error and "message" in result:
|
||||
log.error("Error while import config: {}".format(result["message"]))
|
||||
|
||||
@staticmethod
|
||||
def isValidRfc1123Hostname(hostname):
|
||||
"""
|
||||
Validate a hostname according to RFC 1123
|
||||
|
||||
Each element of the hostname must be from 1 to 63 characters long
|
||||
and the entire hostname, including the dots, can be at most 253
|
||||
characters long. Valid characters for hostnames are ASCII
|
||||
letters from a to z, the digits from 0 to 9, and the hyphen (-).
|
||||
A hostname may not start with a hyphen.
|
||||
"""
|
||||
|
||||
if hostname[-1] == ".":
|
||||
hostname = hostname[:-1] # strip exactly one dot from the right, if present
|
||||
|
||||
if len(hostname) > 253:
|
||||
return False
|
||||
|
||||
labels = hostname.split(".")
|
||||
|
||||
# the TLD must be not all-numeric
|
||||
if re.match(r"[0-9]+$", labels[-1]):
|
||||
return False
|
||||
|
||||
allowed = re.compile(r"(?!-)[a-zA-Z0-9-]{1,63}(?<!-)$")
|
||||
return all(allowed.match(label) for label in labels)
|
||||
|
||||
@staticmethod
|
||||
def onCloseOptions():
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -365,6 +365,15 @@ class Project(QtCore.QObject):
|
||||
|
||||
Controller.instance().post("/projects/{project_id}/nodes/reload".format(project_id=self._id), None, body={}, timeout=None)
|
||||
|
||||
def reset_console_all_nodes(self):
|
||||
"""Reset console for all nodes belonging to this project"""
|
||||
|
||||
# Don't do anything if the project doesn't exist on the server
|
||||
if self._id is None:
|
||||
return
|
||||
|
||||
Controller.instance().post("/projects/{project_id}/nodes/console/reset".format(project_id=self._id), None, body={}, timeout=None)
|
||||
|
||||
def get(self, path, callback, **kwargs):
|
||||
"""
|
||||
HTTP GET on the remote server
|
||||
@@ -466,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
|
||||
@@ -618,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):
|
||||
"""
|
||||
@@ -645,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()
|
||||
|
||||
@@ -703,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"] > 6:
|
||||
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",
|
||||
@@ -350,6 +356,18 @@
|
||||
"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",
|
||||
@@ -388,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"
|
||||
]
|
||||
}
|
||||
129
gns3/settings.py
129
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"',
|
||||
'Putty (custom deprecated version)': 'putty.exe -telnet %h %p -wt "%d" -gns3 5 -skin 4',
|
||||
'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',
|
||||
'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,49 +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',
|
||||
'Windows Terminal': 'wt.exe -w 1 new-tab --title %d 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
|
||||
@@ -188,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
|
||||
@@ -200,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
|
||||
@@ -213,7 +217,7 @@ else:
|
||||
if sys.platform.startswith("win"):
|
||||
# Windows
|
||||
PRECONFIGURED_SPICE_CONSOLE_COMMANDS = {
|
||||
'Remote Viewer': r'"{}\VirtViewer v7.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
|
||||
@@ -222,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
|
||||
@@ -230,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
|
||||
@@ -241,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"]
|
||||
|
||||
@@ -285,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"))
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>986</width>
|
||||
<height>716</height>
|
||||
<height>719</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
@@ -38,7 +38,16 @@ background-none;
|
||||
</property>
|
||||
<widget class="QWidget" name="uiCentralWidget">
|
||||
<layout class="QGridLayout">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
@@ -62,7 +71,7 @@ background-none;
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>986</width>
|
||||
<height>42</height>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="uiEditMenu">
|
||||
@@ -103,6 +112,7 @@ background-none;
|
||||
<addaction name="uiAcademyAction"/>
|
||||
<addaction name="uiDoctorAction"/>
|
||||
<addaction name="uiExportDebugInformationAction"/>
|
||||
<addaction name="uiShortcutsAction"/>
|
||||
<addaction name="uiAboutQtAction"/>
|
||||
<addaction name="uiAboutAction"/>
|
||||
</widget>
|
||||
@@ -129,6 +139,8 @@ background-none;
|
||||
<addaction name="uiShowPortNamesAction"/>
|
||||
<addaction name="uiLockAllAction"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="uiResetGUIStateAction"/>
|
||||
<addaction name="uiResetDocksAction"/>
|
||||
<addaction name="uiDocksMenu"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="uiControlMenu">
|
||||
@@ -141,6 +153,7 @@ background-none;
|
||||
<addaction name="uiReloadAllAction"/>
|
||||
<addaction name="uiAuxConsoleAllAction"/>
|
||||
<addaction name="uiConsoleAllAction"/>
|
||||
<addaction name="uiResetConsoleAllAction"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="uiAnnotateMenu">
|
||||
<property name="title">
|
||||
@@ -228,7 +241,16 @@ background-none;
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@@ -372,7 +394,16 @@ background-none;
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@@ -438,7 +469,16 @@ background-none;
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QGridLayout">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
@@ -482,7 +522,16 @@ background-none;
|
||||
</attribute>
|
||||
<widget class="QWidget" name="dockWidgetContents">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
@@ -596,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">
|
||||
@@ -615,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">
|
||||
@@ -749,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">
|
||||
@@ -1094,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">
|
||||
@@ -1243,6 +1307,26 @@ background-none;
|
||||
<string>New template</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="uiResetDocksAction">
|
||||
<property name="text">
|
||||
<string>Reset docks</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="uiResetConsoleAllAction">
|
||||
<property name="text">
|
||||
<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.13.2
|
||||
# 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"
|
||||
@@ -48,7 +49,7 @@ class Ui_MainWindow(object):
|
||||
self.gridlayout.addWidget(self.uiGraphicsView, 0, 0, 1, 1)
|
||||
MainWindow.setCentralWidget(self.uiCentralWidget)
|
||||
self.uiMenuBar = QtWidgets.QMenuBar(MainWindow)
|
||||
self.uiMenuBar.setGeometry(QtCore.QRect(0, 0, 986, 42))
|
||||
self.uiMenuBar.setGeometry(QtCore.QRect(0, 0, 986, 22))
|
||||
self.uiMenuBar.setObjectName("uiMenuBar")
|
||||
self.uiEditMenu = QtWidgets.QMenu(self.uiMenuBar)
|
||||
self.uiEditMenu.setObjectName("uiEditMenu")
|
||||
@@ -448,6 +449,14 @@ class Ui_MainWindow(object):
|
||||
self.uiNewTemplateAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiNewTemplateAction.setIcon(icon)
|
||||
self.uiNewTemplateAction.setObjectName("uiNewTemplateAction")
|
||||
self.uiResetDocksAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiResetDocksAction.setObjectName("uiResetDocksAction")
|
||||
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()
|
||||
@@ -472,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)
|
||||
@@ -488,6 +498,8 @@ 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)
|
||||
self.uiControlMenu.addAction(self.uiSuspendAllAction)
|
||||
@@ -495,6 +507,7 @@ class Ui_MainWindow(object):
|
||||
self.uiControlMenu.addAction(self.uiReloadAllAction)
|
||||
self.uiControlMenu.addAction(self.uiAuxConsoleAllAction)
|
||||
self.uiControlMenu.addAction(self.uiConsoleAllAction)
|
||||
self.uiControlMenu.addAction(self.uiResetConsoleAllAction)
|
||||
self.uiAnnotateMenu.addAction(self.uiAddNoteAction)
|
||||
self.uiAnnotateMenu.addAction(self.uiInsertImageAction)
|
||||
self.uiAnnotateMenu.addAction(self.uiDrawRectangleAction)
|
||||
@@ -545,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)
|
||||
@@ -594,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"))
|
||||
@@ -625,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"))
|
||||
@@ -691,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"))
|
||||
@@ -711,6 +729,10 @@ class Ui_MainWindow(object):
|
||||
self.uiLockAllAction.setToolTip(_translate("MainWindow", "Lock or unlock all items"))
|
||||
self.uiWebUIAction.setText(_translate("MainWindow", "Web UI - beta"))
|
||||
self.uiNewTemplateAction.setText(_translate("MainWindow", "New template"))
|
||||
self.uiResetDocksAction.setText(_translate("MainWindow", "Reset docks"))
|
||||
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
|
||||
@@ -239,4 +238,22 @@ class UpdateManager(QtCore.QObject):
|
||||
for member in members:
|
||||
# Path separator is always / even on windows
|
||||
member.name = member.name.split("/", 1)[1]
|
||||
tar.extractall(path=self._package_directory, members=members)
|
||||
def is_within_directory(directory, target):
|
||||
|
||||
abs_directory = os.path.abspath(directory)
|
||||
abs_target = os.path.abspath(target)
|
||||
|
||||
prefix = os.path.commonprefix([abs_directory, abs_target])
|
||||
|
||||
return prefix == abs_directory
|
||||
|
||||
def safe_extract(tar, path=".", members=None, *, numeric_owner=False):
|
||||
|
||||
for member in tar.getmembers():
|
||||
member_path = os.path.join(path, member.name)
|
||||
if not is_within_directory(path, member_path):
|
||||
raise Exception("Attempted Path Traversal in Tar File")
|
||||
|
||||
tar.extractall(path, members, numeric_owner=numeric_owner)
|
||||
|
||||
safe_extract(tar, path=self._package_directory, members=members)
|
||||
|
||||
@@ -63,7 +63,7 @@ def parse_version(version):
|
||||
"""
|
||||
|
||||
release_type_found = False
|
||||
version_infos = re.split('(\.|[a-z]+)', version)
|
||||
version_infos = re.split('(\\.|[a-z]+)', version)
|
||||
version = []
|
||||
for info in version_infos:
|
||||
if info == '.' or len(info) == 0:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user