Compare commits

...

22 Commits

Author SHA1 Message Date
Jeremy Grossmann
5f34eb18d1 Merge pull request #3829 from GNS3/release/v2.2.59
Release v2.2.59
2026-05-08 20:06:01 +08:00
grossmj
3a822c02b2 Merge branch 'master' into 2.2 2026-05-08 19:49:31 +08:00
grossmj
84967d4c87 Release v2.2.59 2026-05-08 19:06:04 +08:00
grossmj
6981870554 Update requirements.txt & python_requires 2026-05-08 17:01:14 +08:00
grossmj
7c06038072 Remove psutil version check 2026-05-08 16:41:46 +08:00
grossmj
86be15d474 Fix remaining PyQt6 compatibility issues. Fixes #3822 2026-05-08 13:00:37 +08:00
grossmj
2c64f83d05 Add --title to remote-viewer console commands. Fixes #3783 2026-05-08 12:54:06 +08:00
grossmj
6cc024ed91 Fix deleting drawings. Ref #3810 2026-04-13 12:03:49 +08:00
Jeremy Grossmann
607343292b Merge pull request #3821 from GNS3/release/v2.2.58.1
Release v2.2.58.1
2026-04-12 21:18:16 +08:00
grossmj
03f74d77e4 Development on 2.2.59.dev2 2026-04-12 21:17:21 +08:00
grossmj
1cf86ed4b5 Release v2.2.58.1 2026-04-12 20:59:21 +08:00
Jeremy Grossmann
c4570098a4 Merge pull request #3820 from GNS3/fix/v2.2.58
Fix callback issues in v2.2.58
2026-04-12 20:55:27 +08:00
grossmj
45fe2189f9 Fix tests 2026-04-12 20:53:28 +08:00
grossmj
852ebddf3b Fix callback issues in v2.2.58 2026-04-12 20:49:20 +08:00
grossmj
7baf6af346 Merge branch 'master' into 2.2 2026-04-12 20:35:12 +08:00
grossmj
030c6a4d6d Development on 2.2.59.dev1 2026-04-11 18:05:54 +08:00
Jeremy Grossmann
39759e2da0 Merge pull request #3816 from GNS3/release/v2.2.58
Release v2.2.58
2026-04-11 18:02:02 +08:00
grossmj
9447f21de8 Fix tests 2026-04-11 17:59:48 +08:00
Jeremy Grossmann
0fe25f61b3 Merge branch 'master' into release/v2.2.58 2026-04-11 17:43:12 +08:00
grossmj
96aa00f33b Fix running tests 2026-04-11 17:37:04 +08:00
Jeremy Grossmann
e5e9eb02bc Merge pull request #3805 from GNS3/release/v2.2.57
Release v2.2.57
2026-03-24 10:10:28 +08:00
grossmj
898a03f676 Fix QWebSocket error signal. Fixes #3804 2026-03-24 10:07:44 +08:00
17 changed files with 41 additions and 30 deletions

View File

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

View File

@@ -1,5 +1,16 @@
# Change Log
## 2.2.59 08/05/2026
* Remove psutil version check
* Fix remaining PyQt6 compatibility issues. Fixes #3822
* Add --title to remote-viewer console commands. Fixes #3783
* Fix deleting drawings. Ref #3810
## 2.2.58.1 12/04/2026
* Fix callback issues in found in v2.2.58
## 2.2.58 10/04/2026
* Update snapshot date & time format

View File

@@ -3,7 +3,7 @@ FROM ubuntu:latest
MAINTAINER GNS3 Team
RUN apt-get update
RUN apt-get install -y --force-yes python3 python3-pyqt5 python3-pip python3-pyqt5.qtsvg python3-pyqt5.qtwebsockets python3-dev xvfb
RUN apt-get install -y --force-yes python3 python3-pyqt6 python3-pip python3-pyqt6.qtsvg python3-pyqt6.qtwebsockets python3-dev xvfb
RUN apt-get clean
ADD dev-requirements.txt /dev-requirements.txt

View File

@@ -217,7 +217,7 @@ class ComputeManager(QtCore.QObject):
if compute_id in self._computes:
del self._computes[compute_id]
self._controller.delete("/computes/{compute_id}".format(compute_id=compute_id))
self._controller.delete("/computes/{compute_id}".format(compute_id=compute_id), None)
self.deleted_signal.emit(compute_id)
def updateList(self, computes):

View File

@@ -247,7 +247,7 @@ class Controller(QtCore.QObject):
def createHTTPQuery(self, method, path, *args, **kwargs):
"""
Forward the query to the HTTP client or controller depending of the path
Forward the query to the HTTP client or controller depending on the path
"""
if self._http_client:

View File

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

View File

@@ -580,7 +580,7 @@ class HTTPClient(QtCore.QObject):
:param body: params to send (dictionary)
:param callback: callback method to call when the server replies
:param context: Pass a context to the response callback
:param downloadProgressCallback: Callback called when received something, it can be an incomplete response
:param downloadProgressCallback: callback called when received something, it can be an incomplete response
:param showProgress: Display progress to the user
:param networkManager: The network manager to use. If None use default
:param progressText: Text display to user in progress dialog. None for auto generated
@@ -606,7 +606,7 @@ class HTTPClient(QtCore.QObject):
request = self._addAuth(request)
request.setRawHeader(b"User-Agent", "GNS3 QT Client v{version}".format(version=__version__).encode())
# By default QT doesn't support GET with body even if it's in the RFC that's why we need to use sendCustomRequest
# By default, QT doesn't support GET with body even if it's in the RFC that's why we need to use sendCustomRequest
body = self._addBodyToRequest(body, request)
if not networkManager:
@@ -616,12 +616,13 @@ class HTTPClient(QtCore.QObject):
response = networkManager.sendCustomRequest(request, method.encode(), body)
except SystemError as e:
log.error("Can't send query: {}".format(str(e)))
return
return None
if context:
context = copy.copy(context)
else:
context = {"query_id": str(uuid.uuid4())}
context = dict()
context["query_id"] = str(uuid.uuid4())
response.finished.connect(qpartial(self._processResponse, response, server, callback, context, body, ignoreErrors))
response.errorOccurred.connect(qpartial(self._processError, response, server, callback, context, body, ignoreErrors))

View File

@@ -106,6 +106,11 @@ class DrawingItem:
"""
if error:
if "doesn't exist" in result.get("message", ""):
log.warning("Drawing not found on server, recreating: {}".format(self._id))
self._id = None
self.create()
return True
log.error("Error while updating drawing: {}".format(result["message"]))
return False
self.setPos(QtCore.QPointF(result["x"], result["y"]))
@@ -214,7 +219,7 @@ class DrawingItem:
"""
Deletes this drawing.
:param skip_controller: Do not replicate change on the controller (usefull when it's already deleted on controller)
:param skip_controller: Do not replicate change on the controller (useful when it's already deleted on controller)
"""
self.setDeleting()
@@ -222,7 +227,7 @@ class DrawingItem:
from ..topology import Topology
Topology.instance().removeDrawing(self)
if self._id and not skip_controller:
self._project.delete("/drawings/" + self._id, None, body=self.__json__())
self._project.delete("/drawings/" + self._id, None)
def itemChange(self, change, value):

View File

@@ -132,7 +132,7 @@ class TextItem(QtWidgets.QGraphicsTextItem, DrawingItem):
text.set("text-decoration", "underline")
text.set("fill", "#" + hex(self.defaultTextColor().rgba())[4:])
text.set("fill-opacity", str(self.defaultTextColor().alphaF()))
text.text = self.toPlainText()
text.text = self.toPlainText() or " "
svg = ET.tostring(svg, encoding="utf-8").decode("utf-8")
return svg

View File

@@ -36,7 +36,6 @@ import time
import locale
import argparse
import signal
import psutil
try:
from gns3.qt import QtCore, QtWidgets
@@ -191,9 +190,6 @@ def main():
if parse_version(QtCore.QT_VERSION_STR) < parse_version("6.3.1"):
raise SystemExit("Requirement is PyQt6 version 6.3.1 or higher, got version {}".format(QtCore.QT_VERSION_STR))
if parse_version(psutil.__version__) < parse_version("2.2.1"):
raise SystemExit("Requirement is psutil version 2.2.1 or higher, got version {}".format(psutil.__version__))
# check for the correct locale
# (UNIX/Linux only)
locale_check()

View File

@@ -208,7 +208,7 @@ else:
'TightVNC': 'vncviewer {host}:{port}',
'Vinagre': 'vinagre {host}::{port}',
'gvncviewer': 'gvncviewer {host}:{display}',
'Remote Viewer': 'remote-viewer vnc://{host}:{port}',
'Remote Viewer': 'remote-viewer --title "({name})" vnc://{host}:{port}',
'KRDC': 'krdc vnc://{host}:{port}'
}
@@ -219,7 +219,7 @@ else:
if sys.platform.startswith("win"):
# Windows
PRECONFIGURED_SPICE_CONSOLE_COMMANDS = {
'Remote Viewer': r'"{}\VirtViewer v11.0-256\bin\remote-viewer.exe" spice://{{host}}:{{port}}'.format(program_files),
'Remote Viewer': r'"{}\VirtViewer v11.0-256\bin\remote-viewer.exe" --title "({{name}})" spice://{{host}}:{{port}}'.format(program_files),
}
# default Windows SPICE console command
@@ -228,7 +228,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://{host}:{port}',
'Remote Viewer': '/Applications/RemoteViewer.app/Contents/MacOS/RemoteViewer --title "({name})" spice://{host}:{port}',
}
# default Mac OS X SPICE console command
@@ -236,7 +236,7 @@ elif sys.platform.startswith("darwin"):
else:
PRECONFIGURED_SPICE_CONSOLE_COMMANDS = {
'Remote Viewer': 'remote-viewer spice://{host}:{port}',
'Remote Viewer': 'remote-viewer --title "({name})" spice://{host}:{port}',
}
# default SPICE console command on other systems

View File

@@ -81,7 +81,7 @@ class TemplateManager(QtCore.QObject):
if template_id in self._templates and not self._templates[template_id].builtin():
template = self._templates[template_id]
log.debug("Delete template '{}' (ID={})".format(template.name(), template_id))
self._controller.delete("/templates/{template_id}".format(template_id=template_id))
self._controller.delete("/templates/{template_id}".format(template_id=template_id), None)
def deleteTemplateCallback(self, result, error=False, **kwargs):
"""

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.58"
__version_info__ = (2, 2, 58, 0)
__version__ = "2.2.59"
__version_info__ = (2, 2, 59, 0)
if "dev" in __version__:
try:

View File

@@ -1,6 +1,6 @@
jsonschema==4.25.1; python_version == '3.9' # version 4.25.1 is the last to support Python 3.9
jsonschema>=4.26.0,<4.27; python_version >= '3.10'
sentry-sdk>=2.52.0,<3 # optional dependency
sentry-sdk>=2.59.0,<3 # optional dependency
psutil>=7.2.2
distro>=1.9.0
truststore>=0.10.4; python_version >= '3.10'

View File

@@ -79,7 +79,7 @@ setup(
include_package_data=True,
package_data={"gns3": ["configs/*.txt", "schemas/*.json"]},
platforms="any",
python_requires=">=3.8",
python_requires=">=3.9",
setup_requires=["setuptools>=45.2"],
classifiers=[
"Development Status :: 5 - Production/Stable",

View File

@@ -66,7 +66,6 @@ def test_get_connected(http_client, http_request, network_manager, response):
http_client.createHTTPQuery("GET", "/test", callback)
http_request.assert_called_with(QtCore.QUrl("http://127.0.0.1:3080/v2/test"))
http_request.setRawHeader.assert_any_call(b"Content-Type", b"application/json")
http_request.setRawHeader.assert_any_call(b"User-Agent", "GNS3 QT Client v{version}".format(version=__version__).encode())
assert network_manager.sendCustomRequest.called
args, kwargs = network_manager.sendCustomRequest.call_args
@@ -80,7 +79,7 @@ def test_get_connected(http_client, http_request, network_manager, response):
def test_paramsToQueryString(http_client):
assert http_client._paramsToQueryString({}) == ""
assert http_client._paramsToQueryString(None) == ""
res = http_client._paramsToQueryString({"a": 1, "b": 2})
assert res == "?a=1&b=2" or res == "?b=2&a=1"
res = http_client._paramsToQueryString({"a": 1, "b": 2, "c": None})
@@ -96,7 +95,6 @@ def test_get_connected_auth(http_client, http_request, network_manager, response
http_client.createHTTPQuery("GET", "/test", callback)
http_request.assert_called_with(QtCore.QUrl("http://gns3@127.0.0.1:3080/v2/test"))
http_request.setRawHeader.assert_any_call(b"Content-Type", b"application/json")
http_request.setRawHeader.assert_any_call(b"Authorization", b"Basic Z25zMzozc25n")
http_request.setRawHeader.assert_any_call(b"User-Agent", "GNS3 QT Client v{version}".format(version=__version__).encode())
assert network_manager.sendCustomRequest.called

View File

@@ -65,7 +65,7 @@ def link(devices, controller, project):
{"node_id": devices[0].node_id(), "adapter_number": 0, "port_number": 0},
{"node_id": devices[1].node_id(), "adapter_number": 0, "port_number": 0}
],
"link_style": {},
"link_style": {'color': '#000000', 'width': 2, 'type': 1},
"filters": {},
}
@@ -90,7 +90,7 @@ def test_create_link(devices, project, controller):
{"node_id": devices[0].node_id(), "adapter_number": 0, "port_number": 0},
{"node_id": devices[1].node_id(), "adapter_number": 0, "port_number": 0},
],
"link_style": {},
"link_style": {'color': '#000000', 'width': 2, 'type': 1},
"filters": {},
}