Compare commits

...

79 Commits

Author SHA1 Message Date
grossmj
342ca95bd2 Release v2.2.53 2025-01-21 11:52:12 +10:00
grossmj
187ef561fd Update file browser filters for all files and IOU images 2025-01-07 11:32:17 +07:00
grossmj
97070718fa Upgrade dependencies 2024-12-30 10:49:40 +07:00
grossmj
9466b2a1fb Merge branch 'master' into 2.2 2024-12-28 18:03:26 +07:00
grossmj
3edde1274b Fix Linux Mint default terminal configuration 2024-12-25 22:14:25 +07:00
Jeremy Grossmann
e17e7fc033 Merge pull request #3672 from ob7/enable-css-grid-colors
Add CSS Grid Color Customization Support
2024-12-02 19:56:29 +10:00
ob7
bfe11d7976 apply grid color via css property 2024-12-02 00:13:01 -09:00
Jeremy Grossmann
2b98d51ff7 Merge pull request #3668 from GNS3/release/v2.2.52
release/v2.2.52
2024-12-02 11:35:42 +10:00
grossmj
f6fb0100e2 Development on 2.2.53.dev1 2024-12-02 11:34:00 +10:00
grossmj
804b871cd6 Release v2.2.52 2024-12-02 11:14:23 +10:00
grossmj
c604ff70c7 Change title of QMessageBox 2024-12-02 10:55:49 +10:00
grossmj
63a7f36cfe Add iol extension filter. Ref #3664 2024-11-25 10:59:00 +10:00
grossmj
731fee1217 Remove maximum 64GB RAM limitation for QEMU VMs. Fixes #3658 2024-11-18 14:56:00 +10:00
Jeremy Grossmann
714dae44f3 Merge pull request #3659 from GNS3/feature/bring-to-front-linux
Bring to front support for consoles on Linux.
2024-11-10 18:26:21 +10:00
grossmj
c67dc19ec8 Bring to front support for consoles on Linux. 2024-11-10 18:23:01 +10:00
grossmj
1c5f6b362b Relax setuptools requirement to allow for easier Debian packaging on Ubuntu Focal & Jammy 2024-11-08 12:46:56 +10:00
grossmj
683400c204 Development on 2.2.52.dev1 2024-11-07 23:13:31 +10:00
Jeremy Grossmann
5efb3019f4 Merge pull request #3656 from GNS3/release/v2.2.51
release/v2.2.51
2024-11-07 23:13:11 +10:00
grossmj
23e0520cd2 Release v2.2.51 2024-11-07 15:08:27 +10:00
grossmj
1199de27df Merge branch 'master' into 2.2 2024-10-28 17:32:19 +10:00
grossmj
b3140f9d8e Python 3.13 support 2024-10-26 18:38:07 +10:00
grossmj
1b50cdc341 Upgrade dependencies 2024-10-26 18:22:40 +10:00
Jeremy Grossmann
bd15734c30 Merge pull request #3649 from ob7/add-link-shortcut-key
Add Keyboard Shortcut (Ctrl+L) for Add Link Tool
2024-10-24 21:23:15 +10:00
ob7
24fff8972a Add keyboard shortcut for Add Link 2024-10-23 17:13:17 -08:00
grossmj
9614a1253c Development on 2.2.51.dev1 2024-10-21 13:24:14 +10:00
Jeremy Grossmann
a3a0be863e Merge pull request #3644 from GNS3/2.2
Release v2.2.50
2024-10-21 13:22:00 +10:00
grossmj
9b5713df03 Release v2.2.50 2024-10-21 12:14:18 +10:00
grossmj
6df7dc4730 Replace AppVeyor testing with GH Actions 2024-10-19 16:07:54 +10:00
grossmj
e84ef8bf13 Fix issue when pid file contains invalid data 2024-10-16 16:28:46 +10:00
grossmj
f7703e3fa2 Add comment to indicate sentry-sdk is optional. Ref https://github.com/GNS3/gns3-server/issues/2423 2024-10-14 17:46:53 +10:00
grossmj
de8b9bc8f1 Fix f-string syntax error. Fixes #3639 2024-09-28 11:20:39 +07:00
grossmj
f21a530729 Improve information provided when uploading invalid appliance image. Fixes #3637 2024-09-26 16:14:02 +07:00
grossmj
be2b5eecf6 Use "experimental features" option to force listening for HTTP notification streams. Ref #3579 2024-09-24 12:25:26 +07:00
Jeremy Grossmann
2d4f6e6ecf Merge pull request #3635 from GNS3/revert-3633-backport-aux-console-support
Revert "Backport auxiliary console support for Qemu, Docker and Dynamips nodes"
2024-09-23 13:15:32 +07:00
Jeremy Grossmann
c152de84de Revert "Backport auxiliary console support for Qemu, Docker and Dynamips nodes" 2024-09-23 13:11:23 +07:00
Jeremy Grossmann
d34e7b377c Merge pull request #3633 from GNS3/backport-aux-console-support
Backport auxiliary console support for Qemu, Docker and Dynamips nodes
2024-09-22 21:44:25 +07:00
grossmj
74c55241cf Backport auxiliary console support for Qemu, Docker and Dynamips nodes 2024-09-22 18:29:05 +07:00
grossmj
6edb9c9303 Fix to allow packet capture on more than 6 links. Fixes #3594 2024-09-21 16:40:22 +07:00
grossmj
5d14dd9ab8 Merge branch 'master' into 2.2 2024-09-18 17:14:33 +07:00
Jeremy Grossmann
0c7cae2222 Merge pull request #3632 from GNS3/docker-mac-address
Support for custom MAC addresses in Docker containers
2024-09-18 04:05:46 -06:00
grossmj
9b882924c0 Support for configuring MAC address in Docker containers 2024-09-18 16:30:22 +07:00
Jeremy Grossmann
fccfc01f2e Merge pull request #3630 from griffi-gh/patch-1
Add KRDC to pre-configured VNC console commands
2024-09-12 12:08:38 -06:00
griffi-gh
46872c664d Add KRDC to pre-configured VNC console commands 2024-09-12 17:37:16 +02:00
grossmj
89166b9d35 Development on 2.2.50.dev1 2024-08-06 20:33:55 +02:00
Jeremy Grossmann
d0421e8b1f Merge pull request #3616 from GNS3/2.2
Release v2.2.49
2024-08-06 20:32:46 +02:00
grossmj
b35ce7303d Release v2.2.49 2024-08-06 12:55:15 +02:00
grossmj
47432568e6 Upgrade development packages 2024-08-03 12:32:43 +02:00
grossmj
42ea34ca6c Upgrade jsonschema and sentry-sdk packages 2024-08-03 11:57:15 +02:00
grossmj
9f3598d36d Upgrade to PyQt5 v5.15.11 2024-08-03 11:55:40 +02:00
grossmj
8df248a1e6 Add shortcuts info dialog 2024-07-27 16:23:23 +02:00
grossmj
7196aeb3cf Merge branch 'master' into 2.2 2024-07-27 15:46:14 +02:00
Jeremy Grossmann
38657f4112 Merge pull request #3612 from braza2004/master
Control menu Shortcut Keys
2024-07-27 15:43:53 +02:00
Bilal
84017fa0f1 modified Control menu shortcuts further 2024-07-24 07:05:38 +00:00
Bilal
3aa5a5369f modified Control menu shortcuts 2024-07-24 07:02:14 +00:00
athaarnaqvi
0ed791b946 Added Key Shortcuts 2024-07-23 16:59:55 +05:00
Jeremy Grossmann
133732b7ae Merge pull request #3608 from braza2004/FitInViewKeyShortcut
Fit In View Shortcut Key
2024-07-22 23:27:11 +02:00
grossmj
2ed48def9f Add Fit In View Shortcut in Ui file instead 2024-07-22 23:25:05 +02:00
grossmj
279a91d402 Fix tests. Fixes #3605 2024-07-22 18:58:32 +02:00
Bilal_Raza
4906678d13 feature/FitInViewShortcut 2024-07-22 16:53:58 +05:00
grossmj
a5ff9318f0 Development on 2.2.49.dev1 2024-07-13 16:32:13 +02:00
Jeremy Grossmann
faa802d59c Merge pull request #3597 from GNS3/release/v2.2.48.1
Release v2.2.48.1
2024-07-13 14:43:39 +02:00
grossmj
01b5b8bfa8 Release v2.2.48.1 2024-07-12 18:26:17 +02:00
grossmj
b22c5c8442 Development on 2.2.49.dev1 2024-07-09 00:32:45 +02:00
Jeremy Grossmann
141e7d8307 Merge pull request #3592 from GNS3/2.2
Release v2.2.48
2024-07-09 00:31:15 +02:00
grossmj
1e80354e4e Release v2.2.48 2024-07-08 18:44:09 +02:00
grossmj
d340b1f50a Use "experimental features" to allow bypassing hostname validation. Ref #3524 2024-07-08 14:19:42 +02:00
grossmj
d7bb195610 Update appliance_v8.json. Ref https://github.com/GNS3/gns3-registry/pull/897 2024-07-07 17:47:24 +02:00
Jeremy Grossmann
d963dd4746 Merge pull request #3591 from GNS3/feature/keep-compute-ids
Option to keep the compute IDs unchanged when exporting a project
2024-07-06 17:12:42 +02:00
grossmj
60addccd95 Option to keep the compute IDs unchanged when exporting a project 2024-07-06 17:08:16 +02:00
grossmj
80b654ba53 Upgrade sentry-sdk and psutil packages 2024-07-03 18:52:49 +02:00
grossmj
b67a8c87a7 Switch to PyQt5 5.15.10 for macOS build 2024-06-10 17:20:52 +02:00
grossmj
53ece94a08 Development on 2.2.48.dev1 2024-05-23 12:19:15 +07:00
Jeremy Grossmann
a6c7e0be59 Merge pull request #3586 from GNS3/update-ga-workflows
Update GitHub Action workflows
2024-05-17 12:21:05 +07:00
grossmj
fcea25dcbb Update GitHub Action workflows 2024-05-17 12:14:47 +07:00
Jeremy Grossmann
f29c065164 Merge pull request #3585 from GNS3/release/v2.2.47
Release v2.2.47
2024-05-15 17:27:30 +07:00
grossmj
c6b5494ce6 Use system Python 2024-05-15 17:19:54 +07:00
grossmj
02b14f6aea Call pip as a Python module 2024-05-15 17:12:42 +07:00
grossmj
e2cc378aee Use ubuntu:latest for running tests inside Docker container 2024-05-15 17:10:04 +07:00
Jeremy Grossmann
854e1fded6 Merge pull request #3568 from GNS3/release/v2.2.46
Release v2.2.46
2024-02-26 17:50:19 +08:00
54 changed files with 662 additions and 401 deletions

View File

@@ -10,7 +10,7 @@ jobs:
name: Add issue to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@v0.4.0
- uses: actions/add-to-project@v1.0.1
with:
project-url: https://github.com/orgs/GNS3/projects/3
github-token: ${{ secrets.ADD_NEW_ISSUES_TO_PROJECT }}

View File

@@ -15,62 +15,79 @@ on:
push:
branches: [ "master" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master" ]
schedule:
- cron: '27 6 * * 2'
- cron: '17 22 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
include:
- language: python
build-mode: none
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

View File

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

View File

@@ -1,5 +1,53 @@
# Change Log
## 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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
-rrequirements.txt
pytest==7.2.0
flake8==5.0.4
pytest-timeout==2.1.0
pytest==8.3.2
pytest-timeout==2.3.1

View File

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

View File

@@ -50,7 +50,7 @@ class CrashReport:
Report crash to a third party service
"""
DSN = "https://235ecbc961abe34327a4a397d8ce427a@o19455.ingest.us.sentry.io/38506"
DSN = "https://3ab14deb02a197dfb74396582e088adf@o19455.ingest.us.sentry.io/38506"
_instance = None
def __init__(self):

View File

@@ -543,9 +543,19 @@ Usage: {}
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:

View File

@@ -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_()

View File

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

View File

@@ -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)
@@ -666,7 +672,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
self.configureSlot()
return
else:
if sys.platform.startswith("win") and item.node().bringToFront():
if item.node().bringToFront():
return
self.consoleFromItems(self.scene().selectedItems())
return
@@ -860,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)
@@ -1032,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
@@ -1249,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
@@ -1296,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)
@@ -1659,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:

View File

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

View File

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

View File

@@ -268,6 +268,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.uiExportDebugInformationAction.triggered.connect(self._exportDebugInformationSlot)
self.uiDoctorAction.triggered.connect(self._doctorSlot)
self.uiAcademyAction.triggered.connect(self._academyActionSlot)
self.uiShortcutsAction.triggered.connect(self._shortcutsActionSlot)
# browsers tool bar connections
self.uiBrowseRoutersAction.triggered.connect(self._browseRoutersActionSlot)
@@ -427,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)
@@ -446,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)
@@ -919,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:
@@ -979,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.
@@ -1439,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)

View File

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

View File

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

View File

@@ -35,6 +35,7 @@ DOCKER_CONTAINER_SETTINGS = {
"name": "",
"image": "",
"adapters": 1,
"mac_address": "",
"custom_adapters": [],
"environment": "",
"console_type": "telnet",

View File

@@ -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>&amp;Configure custom adapters</string>
</property>
</widget>
</item>
<item row="7" column="0">
<item row="8" column="0">
<widget class="QLabel" name="uiConsoleTypeLabel">
<property name="text">
<string>Console type:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<item row="8" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetNoConstraint</enum>
@@ -166,14 +176,14 @@
</item>
</layout>
</item>
<item row="8" column="0">
<item row="9" column="0">
<widget class="QLabel" name="uiConsoleResolutionLabel">
<property name="text">
<string>VNC console resolution:</string>
</property>
</widget>
</item>
<item row="8" column="1">
<item row="9" column="1">
<widget class="QComboBox" name="uiConsoleResolutionComboBox">
<item>
<property name="text">
@@ -227,14 +237,14 @@
</item>
</widget>
</item>
<item row="9" column="0">
<item row="10" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>HTTP port in the container:</string>
</property>
</widget>
</item>
<item row="9" column="1">
<item row="10" column="1">
<widget class="QSpinBox" name="uiConsoleHttpPortSpinBox">
<property name="minimum">
<number>1</number>
@@ -244,17 +254,17 @@
</property>
</widget>
</item>
<item row="10" column="0">
<item row="11" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>HTTP path:</string>
</property>
</widget>
</item>
<item row="10" column="1">
<item row="11" column="1">
<widget class="QLineEdit" name="uiHttpConsolePathLineEdit"/>
</item>
<item row="11" column="0">
<item row="12" column="0">
<widget class="QLabel" name="uiEnvironmentLabel">
<property name="text">
<string>Environment variables:
@@ -268,17 +278,17 @@
</property>
</widget>
</item>
<item row="11" column="1">
<item row="12" column="1">
<widget class="QTextEdit" name="uiEnvironmentTextEdit"/>
</item>
<item row="12" column="0">
<item row="13" column="0">
<widget class="QLabel" name="uiNetworkConfigLabel">
<property name="text">
<string>Network configuration</string>
</property>
</widget>
</item>
<item row="12" column="1">
<item row="13" column="1">
<widget class="QPushButton" name="uiNetworkConfigEditButton">
<property name="text">
<string>Edit</string>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/docker/ui/docker_vm_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.15.7
# Created by: PyQt5 UI code generator 5.15.6
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
@@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_dockerVMConfigPageWidget(object):
def setupUi(self, dockerVMConfigPageWidget):
dockerVMConfigPageWidget.setObjectName("dockerVMConfigPageWidget")
dockerVMConfigPageWidget.resize(938, 872)
dockerVMConfigPageWidget.resize(504, 560)
self.verticalLayout = QtWidgets.QVBoxLayout(dockerVMConfigPageWidget)
self.verticalLayout.setObjectName("verticalLayout")
self.uiTabWidget = QtWidgets.QTabWidget(dockerVMConfigPageWidget)
@@ -67,15 +67,21 @@ class Ui_dockerVMConfigPageWidget(object):
self.uiAdapterSpinBox.setMinimum(1)
self.uiAdapterSpinBox.setObjectName("uiAdapterSpinBox")
self.gridLayout.addWidget(self.uiAdapterSpinBox, 5, 1, 1, 1)
self.uiMacAddrLabel = QtWidgets.QLabel(self.tab)
self.uiMacAddrLabel.setObjectName("uiMacAddrLabel")
self.gridLayout.addWidget(self.uiMacAddrLabel, 6, 0, 1, 1)
self.uiMacAddrLineEdit = QtWidgets.QLineEdit(self.tab)
self.uiMacAddrLineEdit.setObjectName("uiMacAddrLineEdit")
self.gridLayout.addWidget(self.uiMacAddrLineEdit, 6, 1, 1, 1)
self.uiCustomAdaptersLabel = QtWidgets.QLabel(self.tab)
self.uiCustomAdaptersLabel.setObjectName("uiCustomAdaptersLabel")
self.gridLayout.addWidget(self.uiCustomAdaptersLabel, 6, 0, 1, 1)
self.gridLayout.addWidget(self.uiCustomAdaptersLabel, 7, 0, 1, 1)
self.uiCustomAdaptersConfigurationPushButton = QtWidgets.QPushButton(self.tab)
self.uiCustomAdaptersConfigurationPushButton.setObjectName("uiCustomAdaptersConfigurationPushButton")
self.gridLayout.addWidget(self.uiCustomAdaptersConfigurationPushButton, 6, 1, 1, 1)
self.gridLayout.addWidget(self.uiCustomAdaptersConfigurationPushButton, 7, 1, 1, 1)
self.uiConsoleTypeLabel = QtWidgets.QLabel(self.tab)
self.uiConsoleTypeLabel.setObjectName("uiConsoleTypeLabel")
self.gridLayout.addWidget(self.uiConsoleTypeLabel, 7, 0, 1, 1)
self.gridLayout.addWidget(self.uiConsoleTypeLabel, 8, 0, 1, 1)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint)
self.horizontalLayout.setObjectName("horizontalLayout")
@@ -90,10 +96,10 @@ class Ui_dockerVMConfigPageWidget(object):
self.uiConsoleAutoStartCheckBox = QtWidgets.QCheckBox(self.tab)
self.uiConsoleAutoStartCheckBox.setObjectName("uiConsoleAutoStartCheckBox")
self.horizontalLayout.addWidget(self.uiConsoleAutoStartCheckBox)
self.gridLayout.addLayout(self.horizontalLayout, 7, 1, 1, 1)
self.gridLayout.addLayout(self.horizontalLayout, 8, 1, 1, 1)
self.uiConsoleResolutionLabel = QtWidgets.QLabel(self.tab)
self.uiConsoleResolutionLabel.setObjectName("uiConsoleResolutionLabel")
self.gridLayout.addWidget(self.uiConsoleResolutionLabel, 8, 0, 1, 1)
self.gridLayout.addWidget(self.uiConsoleResolutionLabel, 9, 0, 1, 1)
self.uiConsoleResolutionComboBox = QtWidgets.QComboBox(self.tab)
self.uiConsoleResolutionComboBox.setObjectName("uiConsoleResolutionComboBox")
self.uiConsoleResolutionComboBox.addItem("")
@@ -106,35 +112,35 @@ class Ui_dockerVMConfigPageWidget(object):
self.uiConsoleResolutionComboBox.addItem("")
self.uiConsoleResolutionComboBox.addItem("")
self.uiConsoleResolutionComboBox.addItem("")
self.gridLayout.addWidget(self.uiConsoleResolutionComboBox, 8, 1, 1, 1)
self.gridLayout.addWidget(self.uiConsoleResolutionComboBox, 9, 1, 1, 1)
self.label = QtWidgets.QLabel(self.tab)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 9, 0, 1, 1)
self.gridLayout.addWidget(self.label, 10, 0, 1, 1)
self.uiConsoleHttpPortSpinBox = QtWidgets.QSpinBox(self.tab)
self.uiConsoleHttpPortSpinBox.setMinimum(1)
self.uiConsoleHttpPortSpinBox.setMaximum(65535)
self.uiConsoleHttpPortSpinBox.setObjectName("uiConsoleHttpPortSpinBox")
self.gridLayout.addWidget(self.uiConsoleHttpPortSpinBox, 9, 1, 1, 1)
self.gridLayout.addWidget(self.uiConsoleHttpPortSpinBox, 10, 1, 1, 1)
self.label_2 = QtWidgets.QLabel(self.tab)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 10, 0, 1, 1)
self.gridLayout.addWidget(self.label_2, 11, 0, 1, 1)
self.uiHttpConsolePathLineEdit = QtWidgets.QLineEdit(self.tab)
self.uiHttpConsolePathLineEdit.setObjectName("uiHttpConsolePathLineEdit")
self.gridLayout.addWidget(self.uiHttpConsolePathLineEdit, 10, 1, 1, 1)
self.gridLayout.addWidget(self.uiHttpConsolePathLineEdit, 11, 1, 1, 1)
self.uiEnvironmentLabel = QtWidgets.QLabel(self.tab)
self.uiEnvironmentLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
self.uiEnvironmentLabel.setWordWrap(False)
self.uiEnvironmentLabel.setObjectName("uiEnvironmentLabel")
self.gridLayout.addWidget(self.uiEnvironmentLabel, 11, 0, 1, 1)
self.gridLayout.addWidget(self.uiEnvironmentLabel, 12, 0, 1, 1)
self.uiEnvironmentTextEdit = QtWidgets.QTextEdit(self.tab)
self.uiEnvironmentTextEdit.setObjectName("uiEnvironmentTextEdit")
self.gridLayout.addWidget(self.uiEnvironmentTextEdit, 11, 1, 1, 1)
self.gridLayout.addWidget(self.uiEnvironmentTextEdit, 12, 1, 1, 1)
self.uiNetworkConfigLabel = QtWidgets.QLabel(self.tab)
self.uiNetworkConfigLabel.setObjectName("uiNetworkConfigLabel")
self.gridLayout.addWidget(self.uiNetworkConfigLabel, 12, 0, 1, 1)
self.gridLayout.addWidget(self.uiNetworkConfigLabel, 13, 0, 1, 1)
self.uiNetworkConfigEditButton = QtWidgets.QPushButton(self.tab)
self.uiNetworkConfigEditButton.setObjectName("uiNetworkConfigEditButton")
self.gridLayout.addWidget(self.uiNetworkConfigEditButton, 12, 1, 1, 1)
self.gridLayout.addWidget(self.uiNetworkConfigEditButton, 13, 1, 1, 1)
self.uiTabWidget.addTab(self.tab, "")
self.tab_2 = QtWidgets.QWidget()
self.tab_2.setObjectName("tab_2")
@@ -186,6 +192,7 @@ class Ui_dockerVMConfigPageWidget(object):
self.uiSymbolToolButton.setText(_translate("dockerVMConfigPageWidget", "&Browse..."))
self.uiCMDLabel.setText(_translate("dockerVMConfigPageWidget", "Start command:"))
self.uiAdapterLabel.setText(_translate("dockerVMConfigPageWidget", "Adapters:"))
self.uiMacAddrLabel.setText(_translate("dockerVMConfigPageWidget", "Base MAC:"))
self.uiCustomAdaptersLabel.setText(_translate("dockerVMConfigPageWidget", "Custom adapters:"))
self.uiCustomAdaptersConfigurationPushButton.setText(_translate("dockerVMConfigPageWidget", "&Configure custom adapters"))
self.uiConsoleTypeLabel.setText(_translate("dockerVMConfigPageWidget", "Console type:"))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/qemu/ui/qemu_vm_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.15.9
# 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.
@@ -59,7 +59,8 @@ class Ui_QemuVMConfigPageWidget(object):
self.gridLayout_4.addWidget(self.uiRamLabel, 4, 0, 1, 1)
self.uiRamSpinBox = QtWidgets.QSpinBox(self.uiGeneralSettingsTab)
self.uiRamSpinBox.setMinimum(32)
self.uiRamSpinBox.setMaximum(65535)
self.uiRamSpinBox.setMaximum(2147483647)
self.uiRamSpinBox.setSingleStep(1024)
self.uiRamSpinBox.setProperty("value", 256)
self.uiRamSpinBox.setObjectName("uiRamSpinBox")
self.gridLayout_4.addWidget(self.uiRamSpinBox, 4, 1, 1, 1)

View File

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

View File

@@ -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"]))

View File

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

View File

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

View File

@@ -16,8 +16,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
import pathlib
import re
import shutil
import subprocess
from gns3.controller import Controller
from gns3.ports.ethernet_port import EthernetPort
@@ -740,18 +743,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, "-a", 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):

View File

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

View File

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

View File

@@ -627,21 +627,24 @@ class Project(QtCore.QObject):
return
# Qt websocket before Qt 5.6 doesn't support auth
if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.6.0") or parse_version(QtCore.PYQT_VERSION_STR) < parse_version("5.6.0"):
if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.6.0") or parse_version(QtCore.PYQT_VERSION_STR) < parse_version("5.6.0") or LocalConfig.instance().experimental():
path = "/projects/{project_id}/notifications".format(project_id=self._id)
self._notification_stream = Controller.instance().createHTTPQuery("GET", path, self._endListenNotificationCallback,
downloadProgressCallback=self._event_received,
networkManager=self._notification_network_manager,
timeout=None,
showProgress=False,
ignoreErrors=True)
downloadProgressCallback=self._event_received,
networkManager=self._notification_network_manager,
timeout=None,
showProgress=False,
ignoreErrors=True)
url = Controller.instance().getHttpClient().url() + path
log.info("Listening for project notifications on '{}'".format(url))
else:
path = "/projects/{project_id}/notifications/ws".format(project_id=self._id)
self._notification_stream = Controller.instance().httpClient().connectWebSocket(self._websocket, path)
self._notification_stream.textMessageReceived.connect(self._websocket_event_received)
self._notification_stream.error.connect(self._websocket_error)
self._notification_stream.sslErrors.connect(self._sslErrorsSlot)
path = "/projects/{project_id}/notifications/ws".format(project_id=self._id)
self._notification_stream = Controller.instance().httpClient().connectWebSocket(self._websocket, path)
self._notification_stream.textMessageReceived.connect(self._websocket_event_received)
self._notification_stream.error.connect(self._websocket_error)
self._notification_stream.sslErrors.connect(self._sslErrorsSlot)
log.info("Listening for project notifications on '{}'".format(self._notification_stream.requestUrl().toString()))
def _endListenNotificationCallback(self, result, error=False, **kwargs):
"""

View File

@@ -7,6 +7,7 @@
"enum": [
"router",
"multilayer_switch",
"switch",
"firewall",
"guest"
]
@@ -720,174 +721,174 @@
"template_properties"
]
}
}
},
"images": {
"type": "array",
"title": "Images for this appliance",
"items": {
"type": "object",
"title": "An image file",
"properties": {
"filename": {
"type": "string",
"title": "Filename"
},
"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."
}
},
"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)"
"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": [
"name"
]
}
}
},
"required": [

View File

@@ -168,7 +168,7 @@ else:
if sys.platform.startswith("linux"):
distro_name = distro.name()
if distro_name == "Debian" or distro_name == "Ubuntu" or distro_name == "LinuxMint":
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:
@@ -206,7 +206,8 @@ else:
'TightVNC': 'vncviewer {host}:{port}',
'Vinagre': 'vinagre {host}::{port}',
'gvncviewer': 'gvncviewer {host}:{display}',
'Remote Viewer': 'remote-viewer vnc://{host}:{port}'
'Remote Viewer': 'remote-viewer vnc://{host}:{port}',
'KRDC': 'krdc vnc://{host}:{port}'
}
# default VNC console command on other systems
@@ -287,7 +288,6 @@ GENERAL_SETTINGS = {
"check_for_update": True,
"overlay_notifications": True,
"experimental_features": False,
"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,

View File

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

View File

@@ -26,6 +26,7 @@ import sys
import shlex
import subprocess
import psutil
import shutil
from .main_window import MainWindow
from .controller import Controller
@@ -102,7 +103,16 @@ class ConsoleThread(QtCore.QThread):
# inject gnome-terminal environment variables
if "GNOME_TERMINAL_SERVICE" not in env or "GNOME_TERMINAL_SCREEN" not in env:
env.update(gnome_terminal_env())
subprocess.Popen(args, env=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, "-a", self._name], env=os.environ)
except OSError as e:
self.consoleError.emit("Count not focus on terminal window: '{}'".format(e))
def run(self):

View File

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

View File

@@ -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>&amp;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>&amp;Reset MAC addresses</string>
<string>&amp;Keep the original compute IDs</string>
</property>
</widget>
</item>

View File

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

View File

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

View File

@@ -598,7 +598,7 @@ class Ui_GeneralPreferencesPageWidget(object):
self.uiCheckForUpdateCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Automatically check for update"))
self.uiCrashReportCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Send anonymous crash reports"))
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"))

View File

@@ -112,6 +112,7 @@ background-none;
<addaction name="uiAcademyAction"/>
<addaction name="uiDoctorAction"/>
<addaction name="uiExportDebugInformationAction"/>
<addaction name="uiShortcutsAction"/>
<addaction name="uiAboutQtAction"/>
<addaction name="uiAboutAction"/>
</widget>
@@ -644,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">
@@ -663,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">
@@ -797,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">
@@ -1142,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">
@@ -1306,6 +1322,11 @@ background-none;
<string>Reset GUI state</string>
</property>
</action>
<action name="uiShortcutsAction">
<property name="text">
<string>&amp;Shortcuts</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/main_window.ui'
#
# Created by: PyQt5 UI code generator 5.15.9
# Created by: PyQt5 UI code generator 5.15.10
#
# 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.
@@ -455,6 +455,8 @@ class Ui_MainWindow(object):
self.uiResetConsoleAllAction.setObjectName("uiResetConsoleAllAction")
self.uiResetGUIStateAction = QtWidgets.QAction(MainWindow)
self.uiResetGUIStateAction.setObjectName("uiResetGUIStateAction")
self.uiShortcutsAction = QtWidgets.QAction(MainWindow)
self.uiShortcutsAction.setObjectName("uiShortcutsAction")
self.uiEditMenu.addAction(self.uiSelectAllAction)
self.uiEditMenu.addAction(self.uiSelectNoneAction)
self.uiEditMenu.addSeparator()
@@ -479,6 +481,7 @@ class Ui_MainWindow(object):
self.uiHelpMenu.addAction(self.uiAcademyAction)
self.uiHelpMenu.addAction(self.uiDoctorAction)
self.uiHelpMenu.addAction(self.uiExportDebugInformationAction)
self.uiHelpMenu.addAction(self.uiShortcutsAction)
self.uiHelpMenu.addAction(self.uiAboutQtAction)
self.uiHelpMenu.addAction(self.uiAboutAction)
self.uiViewMenu.addAction(self.uiActionFullscreen)
@@ -604,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"))
@@ -635,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"))
@@ -701,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"))
@@ -724,6 +732,7 @@ class Ui_MainWindow(object):
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

View File

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

View File

@@ -23,8 +23,8 @@
# or negative for a release candidate or beta (after the base version
# number has been incremented)
__version__ = "2.2.47"
__version_info__ = (2, 2, 47, 0)
__version__ = "2.2.53"
__version_info__ = (2, 2, 53, 0)
if "dev" in __version__:
try:

View File

@@ -1,5 +1,3 @@
-rrequirements.txt
PyQt5-Qt5==5.15.2
PyQt5-sip==12.12.2
PyQt5==5.15.9
PyQt5==5.15.11

View File

@@ -1,6 +1,6 @@
jsonschema>=4.22.0,<4.23
sentry-sdk==2.1.1,<2.2
psutil==5.9.8
jsonschema>=4.23,<4.24
sentry-sdk>=2.19.2,<2.20 # optional dependency
psutil>=6.1.1
distro>=1.9.0
truststore>=0.9.1; python_version >= '3.10'
truststore>=0.10.0; python_version >= '3.10'
importlib-resources>=1.3; python_version < '3.9'

View File

@@ -19,9 +19,9 @@ import sys
from setuptools import setup, find_packages
from setuptools.command.test import test as TestCommand
# we only support Python 3 version >= 3.7
if len(sys.argv) >= 2 and sys.argv[1] == "install" and sys.version_info < (3, 7):
raise SystemExit("Python 3.7 or higher is required")
# we only support Python 3 version >= 3.8
if len(sys.argv) >= 2 and sys.argv[1] == "install" and sys.version_info < (3, 8):
raise SystemExit("Python 3.8 or higher is required")
class PyTest(TestCommand):
@@ -79,8 +79,8 @@ setup(
include_package_data=True,
package_data={"gns3": ["configs/*.txt", "schemas/*.json"]},
platforms="any",
python_requires='>=3.7',
setup_requires=["setuptools>=17.1"],
python_requires=">=3.8",
setup_requires=["setuptools>=45.2"],
classifiers=[
"Development Status :: 5 - Production/Stable",
"Environment :: X11 Applications :: Qt",
@@ -98,6 +98,7 @@ setup(
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython",
],
)

View File

@@ -65,8 +65,8 @@
{
"filename": "empty100G.qcow2",
"version": "100G",
"checksum": "d08fdec95fffbda3f04e9a00db49295df73ae4a507396e442ba9e4ad5c14ce5a",
"checksum_type": "sha256",
"checksum": "1e6409a4523ada212dea2ebc50e50a65",
"checksum_type": "md5",
"filesize": 198656,
"download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty100G.qcow2/download"

View File

@@ -1,4 +1,4 @@
-rrequirements.txt
PyQt5==5.15.10 # pyup: ignore
PyQt5==5.15.11 # pyup: ignore
pywin32==306 # pyup: ignore