Compare commits

...

65 Commits

Author SHA1 Message Date
Julien Duponchelle
31e82bb410 2.0.3 2017-06-13 10:33:13 +02:00
Julien Duponchelle
cab3baf2c6 Cleanup 2017-06-12 16:04:11 +02:00
Julien Duponchelle
574da9c80a Display error when we can't export files
Fix #2097, #2098
2017-06-09 15:06:33 +02:00
Julien Duponchelle
8321883199 Fix auth header not sent is some conditions
Fix #2099
2017-06-09 14:12:38 +02:00
Julien Duponchelle
98e4aefa65 If we have auth issue at server startup continue to get better error 2017-06-09 11:37:54 +02:00
Julien Duponchelle
67d816baa3 Do not override IOU configuration file when you change the image
Fix #2091
2017-06-08 16:20:04 +02:00
Julien Duponchelle
13a8bd4500 Fix some PNG loading issues on Windows
Fix #2085
2017-06-08 14:59:47 +02:00
Julien Duponchelle
802b80b764 Handle label with missing elements
Fix #2096
2017-06-07 19:02:38 +02:00
Julien Duponchelle
59292ff6cb Support floating value for font size
Fix #2092
2017-06-07 16:34:05 +02:00
Julien Duponchelle
7810d19f4d Handle partial json in a response
Fix #2093
2017-06-07 14:49:31 +02:00
ziajka
0788ce569f Extended 'Thanks to' section - tab selection on first element 2017-06-06 16:14:53 +02:00
ziajka
4b0379892d Extended section 2017-06-06 15:55:15 +02:00
Julien Duponchelle
8f8994e7df 2.0.3dev1 2017-05-30 09:10:54 +02:00
Julien Duponchelle
4e172fc7e3 2.0.2 2017-05-30 09:02:02 +02:00
Julien Duponchelle
8ed8a2c115 Show a default symbol in case of corrupted file
Fix https://github.com/GNS3/gns3-server/issues/1043
2017-05-24 12:26:18 +02:00
Julien Duponchelle
073665a75d When another gui is already running exit instead of proper close to avoid any issue
Fix #2059
2017-05-23 18:00:02 +02:00
Julien Duponchelle
4ccc67aa46 Fix duplicate on remote server use wrong location
Fix https://github.com/GNS3/gns3-server/issues/1040
2017-05-23 17:25:29 +02:00
Julien Duponchelle
6e2632e91f Display the location of settings when we disallow opening due to old release 2017-05-23 15:54:35 +02:00
Julien Duponchelle
38ddcde902 Improve search for dynamips in development on OSX 2017-05-23 14:24:54 +02:00
Julien Duponchelle
436563afcb Merge pull request #2079 from GNS3/pyup-update-pytest-3.0.7-to-3.1.0
Update pytest to 3.1.0
2017-05-23 08:29:55 +02:00
pyup-bot
7eaab3e38b Update pytest from 3.0.7 to 3.1.0 2017-05-23 02:25:48 +02:00
Julien Duponchelle
0927a2a8c9 Fix error display when loading a .png custom symbol 2017-05-22 13:38:04 +02:00
Julien Duponchelle
87e6159ff6 Fix a crash in the progress dialog
Fix #2064
2017-05-22 09:56:31 +02:00
Julien Duponchelle
effdcf5e24 Fix a race condition when exporting a closed project
Fix #2078
2017-05-22 09:54:17 +02:00
Julien Duponchelle
021cdd2e65 Fix RuntimeError: wrapped C/C++ object of type NodeItem has been deleted
Fix #2070
2017-05-19 17:39:34 +02:00
Julien Duponchelle
363c4a9966 2.0.2dev1 2017-05-16 09:15:18 +02:00
Julien Duponchelle
7082c75511 2.0.1 2017-05-16 08:48:56 +02:00
grossmj
16c4a837d7 Improve inline help. Fixes #1999.
Add a warning about wifi interfaces in the cloud. Fixes #1902.
2017-05-14 22:18:35 +02:00
grossmj
fd42ac410c Copy remote directory path into clipboard in "Show in FileManager". Fixes #1966. 2017-05-12 16:49:26 +08:00
Jeremy Grossmann
6efc177804 Merge pull request #2054 from GNS3/export_thread
Do not run import / export of project in seperate thread
2017-05-12 10:23:25 +08:00
Julien Duponchelle
6f9e6c9b92 Fix display of error in progress dialog when we don't have thread 2017-05-11 17:48:38 +02:00
Jeremy Grossmann
0411c68150 Merge pull request #2057 from GNS3/lost_slot_and_port
Fix lost slot and port in dynamips settings
2017-05-11 23:37:39 +08:00
Julien Duponchelle
4246e731e5 Fix lost slot and port in dynamips settings
When you reopen a project you no longer have the
wic and slot, until you move the node and retrieve an
update. The settings is just lost in the GUI but is fine
on server.

Fix #2053
2017-05-11 09:49:47 +02:00
Julien Duponchelle
50cca71279 Do not run import / export of project in seperate thread
This trigger warning because you need to do the HTTP request
to the API from the main thread.

Fix #2008
2017-05-10 18:13:45 +02:00
Julien Duponchelle
c78ef8f348 Assert when running an HTTP query outside the main thread
Ref #2008
2017-05-10 17:57:02 +02:00
Julien Duponchelle
c03a5a9e0a Proper error when you try to load the pid file as config file
Fix #2044
2017-05-09 17:27:38 +02:00
Julien Duponchelle
c93b7836d8 Log malformed svg text item
Fix #2045
2017-05-09 17:14:02 +02:00
Julien Duponchelle
690b22cc24 Fix a race condition when right click and delete a node at the same time
Fix #2043
2017-05-09 17:03:23 +02:00
Julien Duponchelle
3560251816 Fix a race condition when snapshoting a closed project
Fix #2046
2017-05-09 16:37:42 +02:00
Jeremy Grossmann
90e861289f Merge pull request #2016 from GNS3/missing_xattr
Catch missing function listxattr on some linux host
2017-05-08 21:04:47 +07:00
Jeremy Grossmann
6e144d6122 Update doctor_dialog.py 2017-05-08 21:04:01 +07:00
grossmj
2f168193d1 Catch remaining missing function listxattr on some Linux host. 2017-05-08 21:01:27 +07:00
Jeremy Grossmann
0fe5559564 Merge pull request #2007 from GNS3/fix_project_close
Fix project closing when we have multiple client connected
2017-05-08 20:42:35 +07:00
Julien Duponchelle
37f0744d7c Fix a race condition when creating node and closing project
Fix #2018
2017-05-05 19:04:06 +02:00
Julien Duponchelle
000f4a4790 Fix Bug with python before 3.4.3
Fix #2035
2017-05-05 18:58:40 +02:00
Julien Duponchelle
a09b7d6738 Fix error if you put a path in a .gns3a file for qemu
Fix #2038
2017-05-05 18:31:13 +02:00
Julien Duponchelle
a1fa8f9ec2 Fix AttributeError: 'NoneType' object has no attribute '_refreshVisibleWidgets'
Fix #2034
2017-05-05 18:29:36 +02:00
Jeremy Grossmann
cd6b0b793e Merge pull request #2009 from GNS3/dissalow_gns3_on_remote
Disallow opening a .gns3 on a remote server
2017-05-05 20:53:29 +07:00
Jeremy Grossmann
6453932421 Update main_window.py 2017-05-05 20:52:03 +07:00
Julien Duponchelle
37a23d9682 Do not crash if the logging code raise an exception
Fix #2017
2017-05-04 12:18:26 +02:00
Julien Duponchelle
e18e10c701 Fix some crash in dynamips device preference page
Fix #2029
2017-05-04 12:02:49 +02:00
Julien Duponchelle
a9240e2e46 Fix warning when loading IOU images on Windows
Fix #2012
2017-05-04 10:43:13 +02:00
Julien Duponchelle
4df0c33013 Do not crash if you don't have configure a packet capture program on Windows
Fix #2026
2017-05-04 10:38:35 +02:00
Julien Duponchelle
4096316ceb Ignore error when we can't kill the packet capture
The user probalby manually kill it.

Fix #2013
2017-05-04 10:30:41 +02:00
Julien Duponchelle
e09292c647 Fix AttributeError: 'NoneType' object has no attribute 'wasCanceled'
Fix #2023
2017-05-04 10:27:45 +02:00
Julien Duponchelle
31c37161fa Fix RuntimeError: wrapped C/C++ object of type QComboBox has been deleted
Fix #2027
2017-05-04 10:25:46 +02:00
Julien Duponchelle
a047cd7f4c Fix RuntimeError: wrapped C/C++ object of type QTreeWidgetItem has been deleted
Fix #2028
2017-05-04 10:13:33 +02:00
Julien Duponchelle
87cde665a8 Fix detection of https when use for the local server
Ref #995
2017-05-03 17:23:03 +02:00
Julien Duponchelle
6361c94bbe Silent the _COMPIZ_TOOLKIT_ACTION warning
This a Qt bug.

Fix #2020
2017-05-03 15:54:55 +02:00
Julien Duponchelle
8a0aeff0bb Cacth TypeError: native Qt signal is not callable
Fix #2011
2017-05-03 10:58:38 +02:00
Julien Duponchelle
508f8b3ad5 Fix AttributeError: 'C7200' object has no attribute 'warning_signal'
Fix #2014
2017-05-03 10:51:12 +02:00
Julien Duponchelle
bfe942c029 Catch missing function listxattr on some linux host
Fix #2010
2017-05-03 10:47:22 +02:00
Julien Duponchelle
b3a86594ff 2.0.1dev1 2017-05-03 10:19:25 +02:00
Julien Duponchelle
a948fd07b1 Disallow opening a .gns3 on a remote server
This prevent opening a local .gns3 on a remote server.
Because this is not working you need to import the
project on the server using portable project.

Fix https://github.com/GNS3/gns3-server/issues/984
2017-05-02 09:27:40 +02:00
Julien Duponchelle
072f714e21 Fix project closing when we have multiple client connected
In all case when we close the main window we let the server
manage if he need to close or not the project.

Fix https://github.com/GNS3/gns3-server/issues/991
2017-05-02 09:07:14 +02:00
60 changed files with 410 additions and 279 deletions

View File

@@ -1,5 +1,62 @@
# Change Log
## 2.0.3 13/06/2017
* Display error when we can't export files
* Fix auth header not sent is some conditions
* If we have auth issue at server startup continue to get better error
* Do not override IOU configuration file when you change the image
* Fix some PNG loading issues on Windows
* Handle label with missing elements
* Support floating value for font size
* Handle partial json in a response
* Add Dominik as a new team member
## 2.0.2 30/05/2017
* Show a default symbol in case of corrupted file
* When another gui is already running exit instead of proper close to avoid any issue
* Fix duplicate on remote server use wrong location
* Display the location of settings when we disallow opening due to old release
* Improve search for dynamips in development on OSX
* Fix error display when loading a .png custom symbol
* Fix a crash in the progress dialog
* Fix a race condition when exporting a closed project
* Fix RuntimeError: wrapped C/C++ object of type NodeItem has been deleted
## 2.0.1 16/05/2017
* Improve inline help. Fixes #1999. Add a warning about wifi interfaces in the cloud. Fixes #1902.
* Copy remote directory path into clipboard in "Show in FileManager". Fixes #1966.
* Fix display of error in progress dialog when we don't have thread
* Fix lost slot and port in dynamips settings
* Do not run import / export of project in seperate thread
* Assert when running an HTTP query outside the main thread
* Proper error when you try to load the pid file as config file
* Log malformed svg text item
* Fix a race condition when right click and delete a node at the same time
* Fix a race condition when snapshoting a closed project
* Update doctor_dialog.py
* Catch remaining missing function listxattr on some Linux host.
* Fix a race condition when creating node and closing project
* Fix error if you put a path in a .gns3a file for qemu
* Fix AttributeError: 'NoneType' object has no attribute '_refreshVisibleWidgets'
* Do not crash if the logging code raise an exception
* Fix some crash in dynamips device preference page
* Fix warning when loading IOU images on Windows
* Do not crash if you don't have configure a packet capture program on Windows
* Ignore error when we can't kill the packet capture
* Fix AttributeError: 'NoneType' object has no attribute 'wasCanceled'
* Fix RuntimeError: wrapped C/C++ object of type QComboBox has been deleted
* Fix RuntimeError: wrapped C/C++ object of type QTreeWidgetItem has been deleted
* Fix detection of https when use for the local server
* Silent the _COMPIZ_TOOLKIT_ACTION warning
* Cacth TypeError: native Qt signal is not callable
* Fix AttributeError: 'C7200' object has no attribute 'warning_signal'
* Catch missing function listxattr on some linux host
* Disallow opening a .gns3 on a remote server
* Fix project closing when we have multiple client connected
## 2.0.0 02/05/2017
* Clarify that we don't override vmware custom adapters

View File

@@ -1,6 +1,6 @@
-rrequirements.txt
pep8==1.7.0
pytest==3.0.7
pytest==3.1.0
pytest-pythonpath==0.7.1 # useful for running tests outside tox
pytest-timeout==1.2.0

View File

@@ -51,7 +51,7 @@ class CrashReport:
Report crash to a third party service
"""
DSN = "sync+https://53d2bc7500394badbaaf8c64f30276d5:6e38be5966be4b55aa46d80b0d6ede79@sentry.io/38506"
DSN = "sync+https://cf90c68e9c81469499e1417714cf2e79:a8ac6a66779a433497c8e9cb32c2a668@sentry.io/38506"
if hasattr(sys, "frozen"):
cacert = get_resource("cacert.pem")
if cacert is not None and os.path.isfile(cacert):

View File

@@ -138,14 +138,18 @@ class DoctorDialog(QtWidgets.QDialog, Ui_DoctorDialog):
request_setuid = False
if sys.platform.startswith("linux"):
if "security.capability" in os.listxattr(path):
caps = os.getxattr(path, "security.capability")
# test the 2nd byte and check if the 13th bit (CAP_NET_RAW) is set
if not struct.unpack("<IIIII", caps)[1] & 1 << 13:
return(2, "Ubridge require CAP_NET_RAW. Run sudo setcap cap_net_admin,cap_net_raw=ep {path}".format(path=path))
else:
# capabilities not supported
request_setuid = True
try:
if "security.capability" in os.listxattr(path):
caps = os.getxattr(path, "security.capability")
# test the 2nd byte and check if the 13th bit (CAP_NET_RAW) is set
if not struct.unpack("<IIIII", caps)[1] & 1 << 13:
return (2, "Ubridge requires CAP_NET_RAW. Run sudo setcap cap_net_admin,cap_net_raw=ep {path}".format(path=path))
else:
# capabilities not supported
request_setuid = True
except AttributeError:
# Due to a Python bug, os.listxattr could be missing: https://github.com/GNS3/gns3-gui/issues/2010
return (1, "Could not determine if CAP_NET_RAW capability is set for uBridge (Python bug)".format(path=path))
if sys.platform.startswith("darwin") or request_setuid:
if os.stat(path).st_uid != 0 or not os.stat(path).st_mode & stat.S_ISUID:
@@ -164,11 +168,15 @@ class DoctorDialog(QtWidgets.QDialog, Ui_DoctorDialog):
if not os.path.exists(path):
return (2, "Dynamips path {path} doesn't exists".format(path=path))
if sys.platform.startswith("linux") and "security.capability" in os.listxattr(path):
caps = os.getxattr(path, "security.capability")
# test the 2nd byte and check if the 13th bit (CAP_NET_RAW) is set
if not struct.unpack("<IIIII", caps)[1] & 1 << 13:
return (2, "Dynamips require CAP_NET_RAW. Run sudo setcap cap_net_raw,cap_net_admin+eip {path}".format(path=path))
try:
if sys.platform.startswith("linux") and "security.capability" in os.listxattr(path):
caps = os.getxattr(path, "security.capability")
# test the 2nd byte and check if the 13th bit (CAP_NET_RAW) is set
if not struct.unpack("<IIIII", caps)[1] & 1 << 13:
return (2, "Dynamips requires CAP_NET_RAW. Run sudo setcap cap_net_raw,cap_net_admin+eip {path}".format(path=path))
except AttributeError:
# Due to a Python bug, os.listxattr could be missing: https://github.com/GNS3/gns3-gui/issues/2010
return (1, "Could not determine if CAP_NET_RAW capability is set for Dynamips (Python bug)".format(path=path))
return (0, None)
def checkGNS3InstalledTwice(self):

View File

@@ -137,9 +137,17 @@ class NodePropertiesDialog(QtWidgets.QDialog, Ui_NodePropertiesDialog):
if page != self.uiEmptyPageWidget:
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).setEnabled(True)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Reset).setEnabled(True)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Help).setEnabled(True)
else:
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).setEnabled(False)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Reset).setEnabled(False)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Help).setEnabled(False)
# hide the contextual help button if there is no help text
if page.whatsThis():
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Help).show()
else:
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Help).hide()
def on_uiButtonBox_clicked(self, button):
"""
@@ -153,6 +161,8 @@ class NodePropertiesDialog(QtWidgets.QDialog, Ui_NodePropertiesDialog):
self.applySettings()
elif button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Reset):
self.resetSettings()
elif button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Help):
self.showHelp()
elif button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Cancel):
QtWidgets.QDialog.reject(self)
else:
@@ -215,6 +225,14 @@ class NodePropertiesDialog(QtWidgets.QDialog, Ui_NodePropertiesDialog):
child = item.child(index)
child.setSettings(child.node().settings().copy())
def showHelp(self):
"""
Show contextual help for the current page.
"""
page = self.uiConfigStackedWidget.currentWidget()
if page != self.uiEmptyPageWidget and page.whatsThis():
QtWidgets.QMessageBox.information(self, "{} help".format(page.windowTitle()), page.whatsThis().strip())
class ConfigurationPageItem(QtWidgets.QTreeWidgetItem):

View File

@@ -83,7 +83,8 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
def _projectsTreeWidgetDoubleClickedSlot(self, item, column):
self.done(True)
def _deleteProjectSlot(self):
@qslot
def _deleteProjectSlot(self, *args):
if len(self.uiProjectsTreeWidget.selectedItems()) == 0:
QtWidgets.QMessageBox.critical(self, "Delete project", "No project selected")
return
@@ -131,7 +132,15 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
new_project_name)
name = name.strip()
if reply and len(name) > 0:
Controller.instance().post("/projects/{project_id}/duplicate".format(project_id=project_id), self._duplicateCallback, body={"name": name})
if Controller.instance().isRemote():
Controller.instance().post("/projects/{project_id}/duplicate".format(project_id=project_id),
self._duplicateCallback,
body={"name": name})
else:
project_location = os.path.join(Topology.instance().projectsDirPath(), name)
Controller.instance().post("/projects/{project_id}/duplicate".format(project_id=project_id),
self._duplicateCallback,
body={"name": name, "path": project_location})
def _duplicateCallback(self, result, error=False, **kwargs):
if error:

View File

@@ -19,16 +19,8 @@
Dialog to manage the snapshots.
"""
import shutil
import re
import time
import os
from ..qt import QtCore, QtWidgets
from ..utils.progress_dialog import ProgressDialog
from ..utils.process_files_worker import ProcessFilesWorker
from ..ui.snapshots_dialog_ui import Ui_SnapshotsDialog
from ..node import Node
from ..controller import Controller
from datetime import datetime
@@ -92,7 +84,7 @@ class SnapshotsDialog(QtWidgets.QDialog, Ui_SnapshotsDialog):
"""
snapshot_name, ok = QtWidgets.QInputDialog.getText(self, "Snapshot", "Snapshot name:", QtWidgets.QLineEdit.Normal, "Unnamed")
if ok and snapshot_name:
if ok and snapshot_name and self._project:
Controller.instance().post("/projects/{}/snapshots".format(self._project.id()), self._createSnapshotsCallback, {"name": snapshot_name})
def _createSnapshotsCallback(self, result, error=False, server=None, context={}, **kwargs):

View File

@@ -550,7 +550,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
if not self._adding_link:
if isinstance(item, NodeItem) and item.node().initialized():
item.setSelected(True)
if item.node().status() == Node.stopped:
if item.node().status() == Node.stopped or item.node().isAlwaysOn():
self.configureSlot()
return
else:
@@ -924,12 +924,14 @@ class GraphicsView(QtWidgets.QGraphicsView):
break
if os.path.exists(node_dir):
log.debug("Open %s in file manage")
log.debug("Open %s in file manager")
if QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(node_dir)) is False:
QtWidgets.QMessageBox.critical(self, "Show in file manager", "Failed to open {}".format(node_dir))
break
else:
QtWidgets.QMessageBox.information(self, "Show in file manager", "The device directory is located in {} on {}".format(node_dir, node.compute().name()))
reply = QtWidgets.QMessageBox.information(self, "Show in file manager", "The device directory is located in {} on {}\n\nCopy path to clipboard?".format(node_dir, node.compute().name()), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
QtWidgets.QApplication.clipboard().setText(node_dir)
break
def consoleToNode(self, node, aux=False):
@@ -1431,7 +1433,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
if not node_module:
raise ModuleError("Could not find any module for {}".format(node_class))
if self._topology.project() is None:
return
node = node_module.instantiateNode(node_class, self.allocateCompute(node_data, instance), self._topology.project())
# If no server is available a ValueError is raised
except (ModuleError, ValueError) as e:

View File

@@ -274,6 +274,9 @@ class HTTPClient(QtCore.QObject):
:returns: QNetworkReply
"""
if "dev" in __version__:
assert QtCore.QThread.currentThread() == self.thread(), "HTTP request not started from the main thread"
# Shutdown in progress do not execute the query
if self._shutdown:
return
@@ -636,7 +639,10 @@ class HTTPClient(QtCore.QObject):
content_type = response.header(QtNetwork.QNetworkRequest.ContentTypeHeader)
log.debug(body)
if body and len(body.strip(" \n\t")) > 0 and content_type == "application/json":
params = json.loads(body)
try:
params = json.loads(body)
except ValueError: # Partial JSON
raise HttpBadRequest(body)
else:
params = {}
if callback is not None:

View File

@@ -142,8 +142,9 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
def symbol(self):
return self._symbol
def _symbolLoadedCallback(self, path):
renderer = QImageSvgRenderer(path)
@qslot
def _symbolLoadedCallback(self, path, *args):
renderer = QImageSvgRenderer(path, fallback=":/icons/cancel.svg")
renderer.setObjectName(path)
self.setSharedRenderer(renderer)
if self._node.settings().get("symbol") != self._symbol:
@@ -373,8 +374,8 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
if self._node_label.toPlainText() != label_data["text"]:
self._node_label.setPlainText(label_data["text"])
self._node_label.setStyle(label_data["style"])
self._node_label.setRotation(label_data["rotation"])
self._node_label.setStyle(label_data.get("style", ""))
self._node_label.setRotation(label_data.get("rotation", 0))
if label_data["x"] is None:
self._centerLabel()
self.updateNode()

View File

@@ -201,7 +201,7 @@ class NoteItem(QtWidgets.QGraphicsTextItem):
val = val.strip()
if key == "font-size":
font.setPointSize(int(val))
font.setPointSizeF(float(val))
elif key == "font-family":
font.setFamily(val)
elif key == "font-style" and val == "italic":
@@ -252,7 +252,7 @@ class NoteItem(QtWidgets.QGraphicsTextItem):
style = ""
style += "font-family: {};".format(self.font().family())
style += "font-size: {};".format(self.font().pointSize())
style += "font-size: {};".format(self.font().pointSizeF())
if self.font().italic():
style += "font-style: italic;"

View File

@@ -20,7 +20,7 @@ Base class for shape items (Rectangle, ellipse etc.).
"""
import xml.etree.ElementTree as ET
from ..qt import QtCore, QtGui, QtWidgets, QtSvg
from ..qt import QtCore, QtGui, QtWidgets
from .drawing_item import DrawingItem
from .utils import colorFromSvg
@@ -35,12 +35,11 @@ class ShapeItem(DrawingItem):
QtCore.Qt.SolidLine: "",
QtCore.Qt.NoPen: None,
QtCore.Qt.DashLine: "25, 25",
QtCore.Qt.DotLine: "5, 25",
QtCore.Qt.DashDotLine: "5, 25, 25",
QtCore.Qt.DotLine: "5, 25",
QtCore.Qt.DashDotLine: "5, 25, 25",
QtCore.Qt.DashDotDotLine: "25, 25, 5, 25, 5"
}
"""
Base class to draw shapes on the scene.
"""
@@ -192,10 +191,10 @@ class ShapeItem(DrawingItem):
element.set("fill-opacity", str(self.brush().color().alphaF()))
dasharray = self.QT_DASH_TO_SVG[pen.style()]
if dasharray is None: # No border to the element
if dasharray is None: # No border to the element
return element
elif dasharray == "":
pass # Solid line
pass # Solid line
else:
element.set("stroke-dasharray", dasharray)
element.set("stroke-width", str(pen.width()))
@@ -239,7 +238,7 @@ class ShapeItem(DrawingItem):
stroke = svg[0].get("stroke-dasharray")
if stroke:
for (qt_stroke, svg_stroke) in self.QT_DASH_TO_SVG.items():
if svg_stroke == stroke:
if svg_stroke == stroke:
pen.setStyle(qt_stroke)
self.setPen(pen)

View File

@@ -26,6 +26,10 @@ from .drawing_item import DrawingItem
from .utils import colorFromSvg
import logging
log = logging.getLogger(__name__)
class TextItem(QtWidgets.QGraphicsTextItem, DrawingItem):
"""
Text item for the QGraphicsView.
@@ -45,7 +49,10 @@ class TextItem(QtWidgets.QGraphicsTextItem, DrawingItem):
self.setFont(qt_font)
if svg:
svg = self.fromSvg(svg)
try:
svg = self.fromSvg(svg)
except ET.ParseError as e:
log.warning(str(e))
if self._id is None:
self.create()
@@ -114,7 +121,7 @@ class TextItem(QtWidgets.QGraphicsTextItem, DrawingItem):
text = ET.SubElement(svg, "text")
text.set("font-family", self.font().family())
text.set("font-size", str(self.font().pointSize()))
text.set("font-size", str(self.font().pointSizeF()))
if self.font().italic():
text.set("font-style", "italic")
if self.font().bold():
@@ -150,7 +157,7 @@ class TextItem(QtWidgets.QGraphicsTextItem, DrawingItem):
color.setAlphaF(float(opacity))
self.setDefaultTextColor(color)
font.setPointSize(int(text.get("font-size", self.font().pointSize())))
font.setPointSizeF(float(text.get("font-size", self.font().pointSizeF())))
font.setFamily(text.get("font-family", self.font().family()))
if text.get("font-style") == "italic":
font.setItalic(True)

View File

@@ -210,7 +210,7 @@ class LocalConfig(QtCore.QObject):
if "version" in self._settings:
if parse_version(self._settings["version"])[:2] > parse_version(__version__)[:2]:
QtWidgets.QApplication(sys.argv) # We need to create an application because settings are loaded before Qt init
QtWidgets.QMessageBox.critical(None, "Version error", "Your settings are for version {} of GNS3. You cannot use a previous version of GNS3 without risking losing data.".format(self._settings["version"]))
QtWidgets.QMessageBox.critical(None, "Version error", "Your settings are for version {} of GNS3. You cannot use a previous version of GNS3 without risking losing data. If you want to reset delete the settings in {}".format(self._settings["version"], self.configDirectory()))
# Exit immediately not clean but we want to avoid any side effect that could corrupt the file
sys.exit(1)

View File

@@ -92,7 +92,6 @@ class LocalServer(QtCore.QObject):
self._settings = {}
self.localServerSettings()
self._port = self._settings.get("port", 3080)
if not self._settings.get("auto_start", True):
if self._settings.get("host") is None:
self._http_client = HTTPClient(self._settings)
@@ -173,6 +172,10 @@ class LocalServer(QtCore.QObject):
else:
# capabilities not supported
request_setuid = True
except AttributeError:
# Due to a Python bug, os.listxattr could be missing: https://github.com/GNS3/gns3-gui/issues/2010
log.warning("Could not determine if CAP_NET_RAW capability is set for uBridge (Python bug)")
return True
except OSError as e:
QtWidgets.QMessageBox.critical(self.parent(), "uBridge", "Can't set CAP_NET_RAW capability to uBridge {}: {}".format(path, str(e)))
return False
@@ -511,10 +514,14 @@ class LocalServer(QtCore.QObject):
:returns: boolean
"""
status, json_data = getSynchronous(self._settings["host"], self._port, "version",
status, json_data = getSynchronous(self._settings["protocol"], self._settings["host"], self._port, "version",
timeout=2, user=self._settings["user"], password=self._settings["password"])
if json_data is None or status != 200:
if status == 401: # Auth issue that need to be solved later
return True
elif json_data is None:
return False
elif status != 200:
return False
else:
version = json_data.get("version", None)

View File

@@ -105,7 +105,11 @@ def init_logger(level, logfile, quiet=False):
name, lno = name.split(":")
lno = int(lno)
name = name.replace("gns3.", "")
return log_factory(name, level, fn, lno, msg, args, exc_info, func=func, sinfo=sinfo, **kwargs)
try:
return log_factory(name, level, fn, lno, msg, args, exc_info, func=func, sinfo=sinfo, **kwargs)
except Exception as e: # To avoid recursion we just print the message if something is wrong when logging
print(msg)
return
logging.setLogRecordFactory(factory)
try:

View File

@@ -418,7 +418,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
# Portable GNS3 project
Topology.instance().importProject(path)
elif path.endswith(".net"):
QtWidgets.QMessageBox.critical(self, "Open project", "Importing legacy project is not supported in 2.0.\nYou need to open it with GNS3 1.x in order to convert it or manually run the gns3 converter.")
QtWidgets.QMessageBox.critical(self, "Open project", "Importing legacy project is not supported in 2.0.\nYou must open it using GNS3 1.x in order to convert it or manually run the gns3 converter.")
return
elif path.endswith(".gns3appliance") or path.endswith(".gns3a"):
@@ -431,7 +431,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._appliance_wizard.show()
self._appliance_wizard.exec_()
elif path.endswith(".gns3"):
Topology.instance().loadProject(path)
if Controller.instance().isRemote():
QtWidgets.QMessageBox.critical(self, "Open project", "Cannot open a .gns3 file on a remote server, please use a portable project (.gns3p) instead")
return
else:
Topology.instance().loadProject(path)
else:
try:
extension = path.split('.')[1]
@@ -439,7 +443,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
except IndexError:
QtWidgets.QMessageBox.critical(self, "File open", "Missing file extension for {}".format(path))
def _projectChangedSlot(self):
@qslot
def _projectChangedSlot(self, *args):
"""
Called when a project finish to load
"""
@@ -942,21 +947,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
log.debug("Close the Main Window")
self._analytics_client.sendScreenView("Main Window", session_start=False)
project = Topology.instance().project()
if not project:
self._finish_application_closing(close_windows=False)
event.accept()
self.uiConsoleTextEdit.closeIO()
elif project.closed() or not project.autoClose():
log.debug("Project is closed killing server and closing main windows")
self._finish_application_closing(close_windows=False)
event.accept()
self.uiConsoleTextEdit.closeIO()
else:
log.debug("Project is not closed asking for project closing")
project.project_closed_signal.connect(self._finish_application_closing)
project.close(local_server_shutdown=True)
event.ignore()
self._finish_application_closing(close_windows=False)
event.accept()
self.uiConsoleTextEdit.closeIO()
def _finish_application_closing(self, close_windows=True):
"""
@@ -1004,7 +997,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
reply = QtWidgets.QMessageBox.warning(self, "GNS3", "Another GNS3 GUI is already running. Continue?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
self.close()
sys.exit(1)
return
if not sys.platform.startswith("win") and os.geteuid() == 0:

View File

@@ -19,13 +19,12 @@
Configuration page for clouds.
"""
from gns3.qt import QtGui, QtCore, QtWidgets
from gns3.qt import QtCore, QtWidgets
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.controller import Controller
from gns3.node import Node
from ..ui.cloud_configuration_page_ui import Ui_cloudConfigPageWidget
from ..cloud import Cloud
class CloudConfigurationPage(QtWidgets.QWidget, Ui_cloudConfigPageWidget):
@@ -48,6 +47,7 @@ class CloudConfigurationPage(QtWidgets.QWidget, Ui_cloudConfigPageWidget):
# connect Ethernet slots
self.uiEthernetListWidget.itemSelectionChanged.connect(self._EthernetChangedSlot)
self.uiEthernetWarningPushButton.clicked.connect(self._EthernetWarningSlot)
self.uiAddEthernetPushButton.clicked.connect(self._EthernetAddSlot)
self.uiAddAllEthernetPushButton.clicked.connect(self._EthernetAddAllSlot)
self.uiDeleteEthernetPushButton.clicked.connect(self._EthernetDeleteSlot)
@@ -79,6 +79,13 @@ class CloudConfigurationPage(QtWidgets.QWidget, Ui_cloudConfigPageWidget):
else:
self.uiDeleteEthernetPushButton.setEnabled(False)
def _EthernetWarningSlot(self):
"""
Shows a warning about Wifi Ethernet interfaces.
"""
QtWidgets.QMessageBox.warning(self, "Ethernet interfaces", "Wifi interfaces may not work properly. It is recommended to use wired Ethernet or Loopback interfaces only.")
def _EthernetAddSlot(self, interface=None):
"""
Adds a new Ethernet interface.

View File

@@ -19,7 +19,7 @@
Configuration page for Ethernet hubs.
"""
from gns3.qt import QtGui, QtWidgets
from gns3.qt import QtWidgets
from gns3.dialogs.node_properties_dialog import ConfigurationError
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.node import Node

View File

@@ -19,7 +19,7 @@
Configuration page for Ethernet switches.
"""
from gns3.qt import QtGui, QtCore, QtWidgets
from gns3.qt import QtCore, QtWidgets
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.node import Node

View File

@@ -6,13 +6,16 @@
<rect>
<x>0</x>
<y>0</y>
<width>459</width>
<height>430</height>
<width>540</width>
<height>553</height>
</rect>
</property>
<property name="windowTitle">
<string>ATM Switch</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This is a simple ATM switch. Only IOS c7200 routers with at least a configured PA-A1 adapter can connect to it.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" colspan="3">
<widget class="QGroupBox" name="uiGeneralGroupBox">

View File

@@ -1,16 +1,14 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/builtin/ui/atm_switch_configuration_page.ui'
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/builtin/ui/atm_switch_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.6
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_atmSwitchConfigPageWidget(object):
def setupUi(self, atmSwitchConfigPageWidget):
atmSwitchConfigPageWidget.setObjectName("atmSwitchConfigPageWidget")
atmSwitchConfigPageWidget.resize(459, 430)
@@ -170,6 +168,7 @@ class Ui_atmSwitchConfigPageWidget(object):
def retranslateUi(self, atmSwitchConfigPageWidget):
_translate = QtCore.QCoreApplication.translate
atmSwitchConfigPageWidget.setWindowTitle(_translate("atmSwitchConfigPageWidget", "ATM Switch"))
atmSwitchConfigPageWidget.setWhatsThis(_translate("atmSwitchConfigPageWidget", "<html><head/><body><p>This is a simple ATM switch. Only IOS c7200 routers with at least a configured PA-A1 adapter can connect to it.</p></body></html>"))
self.uiGeneralGroupBox.setTitle(_translate("atmSwitchConfigPageWidget", "General"))
self.uiNameLabel.setText(_translate("atmSwitchConfigPageWidget", "Name:"))
self.uiVPICheckBox.setText(_translate("atmSwitchConfigPageWidget", "Use VPI only (VP tunnel)"))
@@ -186,3 +185,4 @@ class Ui_atmSwitchConfigPageWidget(object):
self.uiDestinationPortLabel.setText(_translate("atmSwitchConfigPageWidget", "Port:"))
self.uiDestinationVPILabel.setText(_translate("atmSwitchConfigPageWidget", "VPI:"))
self.uiDestinationVCILabel.setText(_translate("atmSwitchConfigPageWidget", "VCI:"))

View File

@@ -6,13 +6,16 @@
<rect>
<x>0</x>
<y>0</y>
<width>758</width>
<height>299</height>
<width>821</width>
<height>363</height>
</rect>
</property>
<property name="windowTitle">
<string>Cloud configuration</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;A cloud node allows you to connect your project to the &amp;quot;real world&amp;quot; (a network or host) using either an Ethernet interface, a TAP interface (Linux only) or even an UDP tunnel. &lt;span style=&quot; font-weight:600;&quot;&gt;Please be aware that Wifi interfaces may not work properly.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="uiTabWidget">
@@ -40,21 +43,21 @@
</property>
</widget>
</item>
<item row="0" column="1">
<item row="0" column="2">
<widget class="QPushButton" name="uiAddEthernetPushButton">
<property name="text">
<string>&amp;Add</string>
</property>
</widget>
</item>
<item row="0" column="2">
<item row="0" column="3">
<widget class="QPushButton" name="uiAddAllEthernetPushButton">
<property name="text">
<string>&amp;Add all</string>
</property>
</widget>
</item>
<item row="0" column="3">
<item row="0" column="4">
<widget class="QPushButton" name="uiDeleteEthernetPushButton">
<property name="enabled">
<bool>false</bool>
@@ -64,7 +67,7 @@
</property>
</widget>
</item>
<item row="1" column="0" colspan="4">
<item row="1" column="0" colspan="5">
<widget class="QListWidget" name="uiEthernetListWidget">
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
@@ -74,7 +77,17 @@
</property>
</widget>
</item>
<item row="2" column="0">
<item row="0" column="1">
<widget class="QPushButton" name="uiEthernetWarningPushButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="dialog-warning"/>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="uiShowSpecialInterfacesCheckBox">
<property name="text">
<string>&amp;Show special Ethernet interfaces</string>
@@ -88,6 +101,7 @@
<zorder>uiDeleteEthernetPushButton</zorder>
<zorder>uiAddAllEthernetPushButton</zorder>
<zorder>uiShowSpecialInterfacesCheckBox</zorder>
<zorder>uiEthernetWarningPushButton</zorder>
</widget>
<widget class="QWidget" name="TAPTab">
<attribute name="title">

View File

@@ -2,8 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/builtin/ui/cloud_configuration_page.ui'
#
# Created: Fri Jun 10 16:26:54 2016
# by: PyQt5 UI code generator 5.2.1
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
@@ -33,21 +32,34 @@ class Ui_cloudConfigPageWidget(object):
self.gridLayout_3.addWidget(self.uiEthernetComboBox, 0, 0, 1, 1)
self.uiAddEthernetPushButton = QtWidgets.QPushButton(self.EthernetTab)
self.uiAddEthernetPushButton.setObjectName("uiAddEthernetPushButton")
self.gridLayout_3.addWidget(self.uiAddEthernetPushButton, 0, 1, 1, 1)
self.gridLayout_3.addWidget(self.uiAddEthernetPushButton, 0, 2, 1, 1)
self.uiAddAllEthernetPushButton = QtWidgets.QPushButton(self.EthernetTab)
self.uiAddAllEthernetPushButton.setObjectName("uiAddAllEthernetPushButton")
self.gridLayout_3.addWidget(self.uiAddAllEthernetPushButton, 0, 2, 1, 1)
self.gridLayout_3.addWidget(self.uiAddAllEthernetPushButton, 0, 3, 1, 1)
self.uiDeleteEthernetPushButton = QtWidgets.QPushButton(self.EthernetTab)
self.uiDeleteEthernetPushButton.setEnabled(False)
self.uiDeleteEthernetPushButton.setObjectName("uiDeleteEthernetPushButton")
self.gridLayout_3.addWidget(self.uiDeleteEthernetPushButton, 0, 3, 1, 1)
self.gridLayout_3.addWidget(self.uiDeleteEthernetPushButton, 0, 4, 1, 1)
self.uiEthernetListWidget = QtWidgets.QListWidget(self.EthernetTab)
self.uiEthernetListWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.uiEthernetListWidget.setObjectName("uiEthernetListWidget")
self.gridLayout_3.addWidget(self.uiEthernetListWidget, 1, 0, 1, 4)
self.gridLayout_3.addWidget(self.uiEthernetListWidget, 1, 0, 1, 5)
self.uiEthernetWarningPushButton = QtWidgets.QPushButton(self.EthernetTab)
self.uiEthernetWarningPushButton.setText("")
icon = QtGui.QIcon.fromTheme("dialog-warning")
self.uiEthernetWarningPushButton.setIcon(icon)
self.uiEthernetWarningPushButton.setObjectName("uiEthernetWarningPushButton")
self.gridLayout_3.addWidget(self.uiEthernetWarningPushButton, 0, 1, 1, 1)
self.uiShowSpecialInterfacesCheckBox = QtWidgets.QCheckBox(self.EthernetTab)
self.uiShowSpecialInterfacesCheckBox.setObjectName("uiShowSpecialInterfacesCheckBox")
self.gridLayout_3.addWidget(self.uiShowSpecialInterfacesCheckBox, 2, 0, 1, 1)
self.gridLayout_3.addWidget(self.uiShowSpecialInterfacesCheckBox, 2, 0, 1, 2)
self.uiEthernetListWidget.raise_()
self.uiEthernetComboBox.raise_()
self.uiAddEthernetPushButton.raise_()
self.uiDeleteEthernetPushButton.raise_()
self.uiAddAllEthernetPushButton.raise_()
self.uiShowSpecialInterfacesCheckBox.raise_()
self.uiEthernetWarningPushButton.raise_()
self.uiTabWidget.addTab(self.EthernetTab, "")
self.TAPTab = QtWidgets.QWidget()
self.TAPTab.setObjectName("TAPTab")
@@ -225,6 +237,7 @@ class Ui_cloudConfigPageWidget(object):
def retranslateUi(self, cloudConfigPageWidget):
_translate = QtCore.QCoreApplication.translate
cloudConfigPageWidget.setWindowTitle(_translate("cloudConfigPageWidget", "Cloud configuration"))
cloudConfigPageWidget.setWhatsThis(_translate("cloudConfigPageWidget", "<html><head/><body><p>A cloud node allows you to connect your project to the &quot;real world&quot; (a network or host) using either an Ethernet interface, a TAP interface (Linux only) or even an UDP tunnel. <span style=\" font-weight:600;\">Please be aware that Wifi interfaces may not work properly.</span></p></body></html>"))
self.uiAddEthernetPushButton.setText(_translate("cloudConfigPageWidget", "&Add"))
self.uiAddAllEthernetPushButton.setText(_translate("cloudConfigPageWidget", "&Add all"))
self.uiDeleteEthernetPushButton.setText(_translate("cloudConfigPageWidget", "&Delete"))

View File

@@ -7,12 +7,15 @@
<x>0</x>
<y>0</y>
<width>499</width>
<height>405</height>
<height>414</height>
</rect>
</property>
<property name="windowTitle">
<string>Frame Relay Switch</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This is a simple Frame Relay switch. Only serial links can be connected to it. &lt;span style=&quot; font-weight:600;&quot;&gt;Note that only the Frame-Relay LMI AINSI type is supported.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="uiGeneralGroupBox">

View File

@@ -1,19 +1,17 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/frame_relay_switch_configuration_page.ui'
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/builtin/ui/frame_relay_switch_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.4.2
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_frameRelaySwitchConfigPageWidget(object):
def setupUi(self, frameRelaySwitchConfigPageWidget):
frameRelaySwitchConfigPageWidget.setObjectName("frameRelaySwitchConfigPageWidget")
frameRelaySwitchConfigPageWidget.resize(499, 405)
frameRelaySwitchConfigPageWidget.resize(499, 414)
self.gridLayout_2 = QtWidgets.QGridLayout(frameRelaySwitchConfigPageWidget)
self.gridLayout_2.setObjectName("gridLayout_2")
self.uiGeneralGroupBox = QtWidgets.QGroupBox(frameRelaySwitchConfigPageWidget)
@@ -136,6 +134,7 @@ class Ui_frameRelaySwitchConfigPageWidget(object):
def retranslateUi(self, frameRelaySwitchConfigPageWidget):
_translate = QtCore.QCoreApplication.translate
frameRelaySwitchConfigPageWidget.setWindowTitle(_translate("frameRelaySwitchConfigPageWidget", "Frame Relay Switch"))
frameRelaySwitchConfigPageWidget.setWhatsThis(_translate("frameRelaySwitchConfigPageWidget", "<html><head/><body><p>This is a simple Frame Relay switch. Only serial links can be connected to it. <span style=\" font-weight:600;\">Note that only the Frame-Relay LMI AINSI type is supported.</span></p></body></html>"))
self.uiGeneralGroupBox.setTitle(_translate("frameRelaySwitchConfigPageWidget", "General"))
self.uiNameLabel.setText(_translate("frameRelaySwitchConfigPageWidget", "Name:"))
self.uiFrameRelayMappingGroupBox.setTitle(_translate("frameRelaySwitchConfigPageWidget", "Mapping"))
@@ -149,3 +148,4 @@ class Ui_frameRelaySwitchConfigPageWidget(object):
self.uiDestinationDLCILabel.setText(_translate("frameRelaySwitchConfigPageWidget", "DLCI:"))
self.uiAddPushButton.setText(_translate("frameRelaySwitchConfigPageWidget", "&Add"))
self.uiDeletePushButton.setText(_translate("frameRelaySwitchConfigPageWidget", "&Delete"))

View File

@@ -19,7 +19,7 @@
Configuration page for Docker images.
"""
from gns3.qt import QtWidgets, QtGui
from gns3.qt import QtWidgets
from ..ui.docker_vm_configuration_page_ui import Ui_dockerVMConfigPageWidget
from ....dialogs.file_editor_dialog import FileEditorDialog
@@ -27,9 +27,10 @@ from ....dialogs.node_properties_dialog import ConfigurationError
from ....dialogs.symbol_selection_dialog import SymbolSelectionDialog
class DockerVMConfigurationPage(
QtWidgets.QWidget, Ui_dockerVMConfigPageWidget):
"""QWidget configuration page for Docker images."""
class DockerVMConfigurationPage(QtWidgets.QWidget, Ui_dockerVMConfigPageWidget):
"""
QWidget configuration page for Docker images
"""
def __init__(self):

View File

@@ -24,9 +24,7 @@ import re
from gns3.node import Node
from gns3.utils.normalize_filename import normalize_filename
from gns3.image_manager import ImageManager
from ..settings import PLATFORMS_DEFAULT_RAM
from ..adapters import ADAPTER_MATRIX
from ..wics import WIC_MATRIX
@@ -460,8 +458,6 @@ class Router(Node):
new_settings = {}
if startup_config in contents:
new_settings["startup_config"] = os.path.join(directory, startup_config)
else:
self.warning_signal.emit(self.id(), "no startup-config file could be found, expected file name: {}".format(startup_config))
if private_config in contents:
new_settings["private_config"] = os.path.join(directory, private_config)

View File

@@ -54,6 +54,8 @@ class DynamipsPreferencesPage(QtWidgets.QWidget, Ui_DynamipsPreferencesPageWidge
file_filter = "Executable (*.exe);;All files (*.*)"
dynamips_path = shutil.which("dynamips")
if sys.platform.startswith("darwin") and dynamips_path is None:
dynamips_path = "/Applications/GNS3.app/Contents/Resources/dynamips"
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select Dynamips", dynamips_path, file_filter)
if not path:
return

View File

@@ -598,7 +598,7 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
if node:
settings["slot" + str(slot_number)] = node.settings().get("slot" + str(slot_number))
if settings["slot" + str(slot_number)] and settings.get("slot" + str(slot_number)) != module:
if settings.get("slot" + str(slot_number)) and settings.get("slot" + str(slot_number)) != module:
if node:
self._checkForLinkConnectedToAdapter(slot_number, settings, node)
settings["slot" + str(slot_number)] = module

View File

@@ -21,7 +21,7 @@ Configuration page for IOU devices.
import os
from gns3.qt import QtGui, QtWidgets
from gns3.qt import QtWidgets
from gns3.local_server import LocalServer
from gns3.dialogs.node_properties_dialog import ConfigurationError
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
@@ -83,16 +83,17 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
self.uiIOUImageLineEdit.clear()
self.uiIOUImageLineEdit.setText(path)
if "l2" in path:
# set the default L2 base startup-config
default_base_config = get_default_base_config(self._base_iou_l2_config_template)
if default_base_config:
self.uiStartupConfigLineEdit.setText(default_base_config)
else:
# set the default L3 base startup-config
default_base_config = get_default_base_config(self._base_iou_l3_config_template)
if default_base_config:
self.uiStartupConfigLineEdit.setText(default_base_config)
if len(self.uiStartupConfigLineEdit.text().strip()) == 0:
if "l2" in path:
# set the default L2 base startup-config
default_base_config = get_default_base_config(self._base_iou_l2_config_template)
if default_base_config:
self.uiStartupConfigLineEdit.setText(default_base_config)
else:
# set the default L3 base startup-config
default_base_config = get_default_base_config(self._base_iou_l3_config_template)
if default_base_config:
self.uiStartupConfigLineEdit.setText(default_base_config)
def _startupConfigBrowserSlot(self):
"""

View File

@@ -237,7 +237,7 @@ class IOUDevicePreferencesPage(QtWidgets.QWidget, Ui_IOUDevicePreferencesPageWid
path, _ = QtWidgets.QFileDialog.getOpenFileName(parent,
"Select an IOU image",
cls._default_images_dir,
"All files (*)",
"All file (*);;IOU image (*.bin *.image)",
"IOU image (*.bin *.image)")
if not path:

View File

@@ -26,7 +26,7 @@ from collections import OrderedDict
from gns3.modules.qemu.dialogs.qemu_image_wizard import QemuImageWizard
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.node import Node
from gns3.qt import QtGui, QtCore, QtWidgets, qpartial
from gns3.qt import QtCore, QtWidgets, qpartial
from gns3.modules.module_error import ModuleError
from gns3.dialogs.node_properties_dialog import ConfigurationError
from gns3.image_manager import ImageManager

View File

@@ -19,7 +19,7 @@
Configuration page for VirtualBox VMs.
"""
from gns3.qt import QtGui, QtWidgets
from gns3.qt import QtWidgets
from gns3.dialogs.node_properties_dialog import ConfigurationError
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.node import Node

View File

@@ -19,7 +19,7 @@
Configuration page for VMware VMs.
"""
from gns3.qt import QtGui, QtWidgets
from gns3.qt import QtWidgets
from gns3.dialogs.node_properties_dialog import ConfigurationError
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.node import Node

View File

@@ -20,7 +20,7 @@ Configuration page for VPCS nodes
"""
import os
from gns3.qt import QtGui, QtWidgets
from gns3.qt import QtWidgets
from gns3.local_server import LocalServer
from gns3.node import Node

View File

@@ -66,8 +66,12 @@ class Node(BaseNode):
def _exportFileCallback(self, result, error=False, raw_body=None, context={}, **kwargs):
if not error:
with open(context["path"], "wb+") as f:
f.write(raw_body)
try:
with open(context["path"], "wb+") as f:
f.write(raw_body)
except OSError as e:
log.erro("Can't write %s: %s", context["path"], str(e))
def creator(self):
return self._creator
@@ -338,9 +342,7 @@ class Node(BaseNode):
if "properties" in result:
for name, value in result["properties"].items():
if name.startswith("slot") or name.startswith("wic"):
pass
elif name in self._settings and self._settings[name] != value:
if name in self._settings and self._settings[name] != value:
log.debug("{} setting up and updating {} from '{}' to '{}'".format(self.name(), name, self._settings[name], value))
self._settings[name] = value

View File

@@ -135,10 +135,12 @@ class NodesView(QtWidgets.QTreeWidget):
# We can not edit stuff like EthernetSwitch
# or without config template like VPCS
if not "builtin" in node and hasattr(module, "vmConfigurationPage"):
if "builtin" not in node and hasattr(module, "vmConfigurationPage"):
for vm_key, vm in module.instance().VMs().items():
if vm["name"] == node["name"]:
break
if vm is None:
return
menu = QtWidgets.QMenu()
configuration = QtWidgets.QAction("Configure Template", menu)
configuration.setIcon(QtGui.QIcon(":/icons/configuration.svg"))

View File

@@ -47,7 +47,10 @@ class PacketCapture:
Kill all running captures (for example when change project)
"""
for process in list(self._tail_process.values()):
process.kill()
try:
process.kill()
except OSError:
pass
self._tail_process = {}
self._capture_reader_process = {}
@@ -209,6 +212,9 @@ class PacketCapture:
# normal traffic capture
if not sys.platform.startswith("win"):
command = shlex.split(command)
if len(command) == 0:
QtWidgets.QMessageBox.critical(self.parent(), "Packet capture", "No packet capture program configured")
return
try:
self._capture_reader_process[link] = subprocess.Popen(command)
except OSError as e:

View File

@@ -181,7 +181,7 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
except OSError as e:
QtWidgets.QMessageBox.critical(self, "Import configuration file", "Could not load configuration file {}: {}".format(os.path.basename(path), e))
return
except ValueError as e:
except (ValueError, TypeError) as e:
QtWidgets.QMessageBox.critical(self, "Import configuration file", "Invalid file: {}".format(e))
return

View File

@@ -93,6 +93,7 @@ class GNS3VMPreferencesPage(QtWidgets.QWidget, Ui_GNS3VMPreferencesPageWidget):
self.uiHeadlessCheckBox.setChecked(self._settings["headless"])
Controller.instance().get("/gns3vm/engines", self._listEnginesCallback)
@qslot
def _listEnginesCallback(self, result, error=False, ignore_error=False, **kwargs):
if error:
if "message" in result:

View File

@@ -108,7 +108,8 @@ class Progress(QtCore.QObject):
for query in self._queries.copy().values():
query["response"].abort()
def _rejectSlot(self):
@qslot
def _rejectSlot(self, *args):
if self._progress_dialog is not None and not sip.isdeleted(self._progress_dialog) or self._progress_dialog.wasCanceled():
self._progress_dialog.deleteLater()
self._progress_dialog = None
@@ -124,7 +125,7 @@ class Progress(QtCore.QObject):
self.show_signal.emit()
@qslot
def _showSlot(self):
def _showSlot(self, *args):
if self._show_lock:
return
self._show_lock = True

View File

@@ -259,6 +259,9 @@ def qslot(func):
# Log qt error to Python log
def myQtMsgHandler(msg_type, msg_log_context, msg_string):
if "_COMPIZ_TOOLKIT_ACTION" in msg_string:
# Qt < 5.6 issue: https://github.com/GNS3/gns3-gui/issues/2020
return
log.error(msg_string)

View File

@@ -16,21 +16,27 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import xml.etree.ElementTree as ET
from . import QtCore
from . import QtSvg
from . import QtGui
import logging
log = logging.getLogger(__name__)
class QImageSvgRenderer(QtSvg.QSvgRenderer):
"""
Renderer pixmap and svg to SVG item
:param path_or_data: Svg element of path to a SVG
:param fallback: Image to display if the image is not working
"""
def __init__(self, path_or_data=None):
def __init__(self, path_or_data=None, fallback=None):
super().__init__()
self._fallback = fallback
self._svg = """<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{width}" height="{height}"></svg>"""
self.load(path_or_data)
@@ -43,21 +49,38 @@ class QImageSvgRenderer(QtSvg.QSvgRenderer):
except ValueError:
pass # On windows we can get an error because the path is too long (it's the svg data)
res = super().load(path_or_data)
# If we can't render a SVG we load and base64 the image to create a SVG
if self.isValid():
return res
try:
# We load the SVG with ElementTree before
# because Qt when failing loading send noise to logs
# and their is no way to prevent that
if not path_or_data.startswith(":") and os.path.exists(path_or_data):
ET.parse(path_or_data)
res = super().load(path_or_data)
# If we can't render a SVG we load and base64 the image to create a SVG
if self.isValid():
return res
except ET.ParseError:
pass
image = QtGui.QImage(path_or_data)
data = QtCore.QByteArray()
buf = QtCore.QBuffer(data)
image.save(buf, 'PNG')
self._svg = """<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{width}" height="{height}">
<image width="{width}" height="{height}" xlink:href="data:image/png;base64,{data}"/>
</svg>""".format(data=bytes(data.toBase64()).decode(),
width=image.rect().width(),
height=image.rect().height())
return super().load(self._svg.encode())
if image.rect().width() > 0:
self._svg = """<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{width}" height="{height}">
<image width="{width}" height="{height}" xlink:href="data:image/png;base64,{data}"/>
</svg>""".format(data=bytes(data.toBase64()).decode(),
width=image.rect().width(),
height=image.rect().height())
res = super().load(self._svg.encode())
elif self._fallback:
log.error("Invalid or corrupted image file")
res = super().load(self._fallback)
else:
self._svg = """<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
</svg>"""
res = super().load(self._svg.encode())
return res
def svg(self):
"""

View File

@@ -18,7 +18,6 @@
import json
import sys
import os
import urllib
@@ -227,7 +226,8 @@ class Config:
new_config["options"] = options.strip()
for image in appliance_config["images"]:
new_config[image["type"]] = self._relative_image_path("QEMU", image["path"])
if image.get("path"):
new_config[image["type"]] = self._relative_image_path("QEMU", image["path"])
new_config.setdefault("hda_disk_image", "")
new_config.setdefault("hdb_disk_image", "")

View File

@@ -20,15 +20,12 @@ Contains this entire topology: nodes and links.
"""
import os
import json
import xml.etree.ElementTree as ET
from .local_server import LocalServer
from .node import Node
from .qt import QtCore, QtWidgets
from .utils.process_files_worker import ProcessFilesWorker
from .utils.progress_dialog import ProgressDialog
from .utils.export_project_worker import ExportProjectWorker
from .utils.import_project_worker import ImportProjectWorker
@@ -237,7 +234,7 @@ It is your responsability to check if you have the right to distribute the image
self.editReadme()
export_worker = ExportProjectWorker(self._project, path, include_images)
progress_dialog = ProgressDialog(export_worker, "Exporting project", "Exporting portable project files...", "Cancel", parent=self._main_window)
progress_dialog = ProgressDialog(export_worker, "Exporting project", "Exporting portable project files...", "Cancel", parent=self._main_window, create_thread=False)
progress_dialog.show()
progress_dialog.exec_()
@@ -252,7 +249,7 @@ It is your responsability to check if you have the right to distribute the image
name=dialog.getProjectSettings()["project_name"],
path=dialog.getProjectSettings().get("project_files_dir"))
import_worker.imported.connect(self._projectImportedSlot)
progress_dialog = ProgressDialog(import_worker, "Importing project", "Importing portable project files...", "Cancel", parent=self._main_window)
progress_dialog = ProgressDialog(import_worker, "Importing project", "Importing portable project files...", "Cancel", parent=self._main_window, create_thread=False)
progress_dialog.show()
progress_dialog.exec_()

View File

@@ -87,7 +87,7 @@ p, li { white-space: pre-wrap; }
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'.SF NS Text'; font-size:13pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Noto Sans'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:600;&quot;&gt;Developers&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-size:11pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:11pt;&quot;&gt;Jeremy Grossmann&lt;/span&gt;&lt;/p&gt;
@@ -3656,7 +3656,8 @@ Paul Sivie
BlessLarryAgbemor
Richard Miller
Francesco Colista
Stephen C. Moore</string>
Stephen C. Moore
Dominik Ziajka</string>
</property>
</widget>
</item>

View File

@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/moores/dev/gns3-gui/gns3/ui/about_dialog.ui'
# Form implementation generated from reading ui file '/home/dominik/projects/gns3-gui/gns3/ui/about_dialog.ui'
#
# Created by: PyQt5 UI code generator 5.7
# Created by: PyQt5 UI code generator 5.8.2
#
# WARNING! All changes made in this file will be lost!
@@ -20,7 +20,6 @@ class Ui_AboutDialog(object):
self.tab = QtWidgets.QWidget()
self.tab.setObjectName("tab")
self.gridLayout = QtWidgets.QGridLayout(self.tab)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setObjectName("gridLayout")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
@@ -40,7 +39,6 @@ class Ui_AboutDialog(object):
self.tab_4 = QtWidgets.QWidget()
self.tab_4.setObjectName("tab_4")
self.verticalLayout = QtWidgets.QVBoxLayout(self.tab_4)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setObjectName("verticalLayout")
self.uiTeamTextEdit = QtWidgets.QTextEdit(self.tab_4)
self.uiTeamTextEdit.setReadOnly(True)
@@ -51,7 +49,6 @@ class Ui_AboutDialog(object):
self.tab_2 = QtWidgets.QWidget()
self.tab_2.setObjectName("tab_2")
self.vboxlayout = QtWidgets.QVBoxLayout(self.tab_2)
self.vboxlayout.setContentsMargins(0, 0, 0, 0)
self.vboxlayout.setObjectName("vboxlayout")
self.uiThanksPlainTextEdit = QtWidgets.QPlainTextEdit(self.tab_2)
self.uiThanksPlainTextEdit.setReadOnly(True)
@@ -61,7 +58,6 @@ class Ui_AboutDialog(object):
self.tab_3 = QtWidgets.QWidget()
self.tab_3.setObjectName("tab_3")
self.vboxlayout1 = QtWidgets.QVBoxLayout(self.tab_3)
self.vboxlayout1.setContentsMargins(0, 0, 0, 0)
self.vboxlayout1.setObjectName("vboxlayout1")
self.uiLicensePlainTextEdit = QtWidgets.QPlainTextEdit(self.tab_3)
self.uiLicensePlainTextEdit.setReadOnly(True)
@@ -97,7 +93,7 @@ class Ui_AboutDialog(object):
self.uiTeamTextEdit.setHtml(_translate("AboutDialog", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'.SF NS Text\'; font-size:13pt; font-weight:400; font-style:normal;\">\n"
"</style></head><body style=\" font-family:\'Noto Sans\'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:\'Ubuntu\'; font-size:11pt; font-weight:600;\">Developers</span></p>\n"
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:\'Ubuntu\'; font-size:11pt;\"><br /></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:\'Ubuntu\'; font-size:11pt;\">Jeremy Grossmann</span></p>\n"
@@ -3648,7 +3644,8 @@ class Ui_AboutDialog(object):
"BlessLarryAgbemor\n"
"Richard Miller\n"
"Francesco Colista\n"
"Stephen C. Moore"))
"Stephen C. Moore\n"
"Dominik Ziajka"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("AboutDialog", "&Thanks to"))
self.uiLicensePlainTextEdit.setPlainText(_translate("AboutDialog", " GNU GENERAL PUBLIC LICENSE\n"
" Version 3, 29 June 2007\n"

View File

@@ -155,7 +155,7 @@ to display the configuration page.</string>
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset</set>
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok|QDialogButtonBox::Reset</set>
</property>
</widget>
</item>

View File

@@ -1,16 +1,14 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/ui/node_properties_dialog.ui'
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/node_properties_dialog.ui'
#
# Created by: PyQt5 UI code generator 5.4.2
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_NodePropertiesDialog(object):
def setupUi(self, NodePropertiesDialog):
NodePropertiesDialog.setObjectName("NodePropertiesDialog")
NodePropertiesDialog.resize(689, 454)
@@ -70,7 +68,7 @@ class Ui_NodePropertiesDialog(object):
self.gridlayout.addWidget(self.splitter, 0, 0, 1, 1)
self.uiButtonBox = QtWidgets.QDialogButtonBox(NodePropertiesDialog)
self.uiButtonBox.setOrientation(QtCore.Qt.Horizontal)
self.uiButtonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply | QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Reset)
self.uiButtonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Help|QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Reset)
self.uiButtonBox.setObjectName("uiButtonBox")
self.gridlayout.addWidget(self.uiButtonBox, 1, 0, 1, 1)
@@ -84,6 +82,6 @@ class Ui_NodePropertiesDialog(object):
self.uiNodesTreeWidget.headerItem().setText(0, _translate("NodePropertiesDialog", "Nodes"))
self.uiTitleLabel.setText(_translate("NodePropertiesDialog", "Node Configuration"))
self.textLabel.setText(_translate("NodePropertiesDialog", "Please select a node in the list \n"
"to display the configuration page."))
"to display the configuration page."))
from . import resources_rc

View File

@@ -43,7 +43,16 @@ class AnalyticsClient(QtCore.QObject):
self._manager = QtNetwork.QNetworkAccessManager(self)
def finished(network_reply):
error = network_reply.error()
try:
error = network_reply.error()
except TypeError:
# For unknow reason sometimes error is transform to a signal
# we receive few crash report about that, but we are not able
# to reproduce. We suspect the problem happen when the
# application is closing.
#
# https://github.com/GNS3/gns3-gui/issues/2011
return
if error != QtNetwork.QNetworkReply.NoError:
log.debug("Error when pushing to Google Analytics %s", network_reply.errorString())

View File

@@ -15,12 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import zipfile
import shutil
from ..qt import QtCore
from ..local_server import LocalServer
class ExportProjectWorker(QtCore.QObject):
@@ -40,12 +35,11 @@ class ExportProjectWorker(QtCore.QObject):
self._path = path
def run(self):
vm_server = None
self._project.get("/export?include_images={}".format(self._include_images),
self._exportReceived,
downloadProgressCallback=self._downloadFileProgress,
timeout=None)
if self._project:
self._project.get("/export?include_images={}".format(self._include_images),
self._exportReceived,
downloadProgressCallback=self._downloadFileProgress,
timeout=None)
def _exportReceived(self, content, error=False, server=None, context={}, **kwargs):
if error:

View File

@@ -15,34 +15,40 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import ssl
import http
import json
import base64
import urllib.request
import logging
log = logging.getLogger(__name__)
def getSynchronous(host, port, endpoint, timeout=2, user=None, password=None):
def getSynchronous(protocol, host, port, endpoint, timeout=2, user=None, password=None):
"""
:returns: Tuple (Status code, json of anwser). Status 0 is a non HTTP error
"""
try:
url = "http://{host}:{port}/v2/{endpoint}".format(host=host, port=port, endpoint=endpoint)
url = "{protocol}://{host}:{port}/v2/{endpoint}".format(protocol=protocol, host=host, port=port, endpoint=endpoint)
request = urllib.request.Request(url)
if user is not None and len(user) > 0:
log.debug("Synchronous get {} with user '{}'".format(url, user))
auth_handler = urllib.request.HTTPBasicAuthHandler()
auth_handler.add_password(realm="GNS3 server",
uri=url,
user=user,
passwd=password)
opener = urllib.request.build_opener(auth_handler)
urllib.request.install_opener(opener)
base64string = base64.encodebytes('{}:{}'.format(user, password).encode()).replace(b'\n', b'')
request.add_header("Authorization", "Basic {}".format(base64string.decode()))
else:
log.debug("Synchronous get {} (no authentication)".format(url))
response = urllib.request.urlopen(url, timeout=timeout)
if sys.version_info >= (3, 5):
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
response = urllib.request.urlopen(request, timeout=timeout, context=ctx)
else:
response = urllib.request.urlopen(request, timeout=timeout)
content_type = response.getheader("CONTENT-TYPE")
if response.status == 200:
if content_type == "application/json":

View File

@@ -16,10 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pathlib
import zipfile
import uuid
import os
import sys
from ..controller import Controller
@@ -69,4 +66,3 @@ class ImportProjectWorker(QtCore.QObject):
def cancel(self):
pass

View File

@@ -37,11 +37,12 @@ class ProgressDialog(QtWidgets.QProgressDialog):
:param cancel_button_text: text for the cancel button
:param busy: if True, the progress bar in "sliding mode"
:param delay: Countdown in seconds before starting the worker
:param create_thread: Start the worker in a dedicated thread
to show unknown progress.
:param parent: parent widget
"""
def __init__(self, worker, title, label_text, cancel_button_text, busy=False, parent=None, delay=0):
def __init__(self, worker, title, label_text, cancel_button_text, busy=False, parent=None, delay=0, create_thread=True):
assert QtCore.QThread.currentThread() == QtWidgets.QApplication.instance().thread()
@@ -59,14 +60,18 @@ class ProgressDialog(QtWidgets.QProgressDialog):
self.canceled.connect(self._canceledSlot)
self.destroyed.connect(self._cleanup)
self._thread = QtCore.QThread()
self._worker = worker
self._worker.setObjectName(worker.__class__.__name__)
self._worker.moveToThread(self._thread)
if create_thread:
self._thread = QtCore.QThread()
self._worker.moveToThread(self._thread)
else:
self._thread = None
self._worker.finished.connect(self.accept)
self._worker.updated.connect(self._updateProgressSlot)
self._worker.error.connect(self._error)
self._thread.started.connect(self._worker.run)
if self._thread:
self._thread.started.connect(self._worker.run)
self._countdownTimer = None
if delay == 0:
@@ -99,6 +104,8 @@ class ProgressDialog(QtWidgets.QProgressDialog):
if self._thread:
self._thread.start()
log.debug("{} thread started".format(self._worker.objectName()))
elif self._worker:
self._worker.run()
@qslot
def _canceledSlot(self):
@@ -110,7 +117,8 @@ class ProgressDialog(QtWidgets.QProgressDialog):
@qslot
def accept(self):
log.debug("{} thread finished".format(self._worker.objectName()))
if self._worker:
log.debug("{} thread finished".format(self._worker.objectName()))
self._cleanup()
super().accept()
@@ -138,6 +146,7 @@ class ProgressDialog(QtWidgets.QProgressDialog):
thread.wait()
log.debug("{} thread destroyed".format(self._worker.objectName()))
thread.deleteLater()
self._worker = None
@qslot
def _updateProgressSlot(self, value):
@@ -159,8 +168,6 @@ class ProgressDialog(QtWidgets.QProgressDialog):
:param message: message
"""
if not self._thread:
return
if stop:
log.critical("{} thread stopping with an error: {}".format(self._worker.objectName(), message))
QtWidgets.QMessageBox.critical(self.parentWidget(), "Error", "{}".format(message))

View File

@@ -17,7 +17,7 @@
# __version__ is a human-readable version number.
__version__ = "2.0.0"
__version__ = "2.0.3"
# If it's a git checkout try to add the commit
if "dev" in __version__:
@@ -37,4 +37,4 @@ or negative for a release candidate or beta (after the base version
number has been incremented)
"""
__version_info__ = (2, 0, 0, 0)
__version_info__ = (2, 0, 3, 0)

View File

@@ -1,66 +0,0 @@
#!/bin/bash
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set -e
SIP=4.16.8
PYQT=5.4.2
echo "Install sip $SIP and PyQT $PYQT"
mkdir -p build
cd build
# install SIP
if [ ! -d "sip-${SIP}" ]
then
wget --quiet --output-document=sip.tar.gz "http://downloads.sourceforge.net/project/pyqt/sip/sip-${SIP}/sip-${SIP}.tar.gz"
tar -xf sip.tar.gz
cd "sip-${SIP}"
echo "sip configure"
python -B configure.py
echo "sip make"
make
cd ..
fi
cd "sip-${SIP}"
echo "sip make install"
sudo make install
cd ..
echo "Install PyQT"
# install PyQt
if [ ! -d "PyQt-x11-gpl-${PYQT}" ]
then
wget --quiet --output-document=pyqt.tar.gz "http://downloads.sourceforge.net/project/pyqt/PyQt5/PyQt-${PYQT}/PyQt-gpl-${PYQT}.tar.gz"
tar -xf pyqt.tar.gz
cd "PyQt-gpl-${PYQT}"
python -B configure.py --confirm-license
make
cd ..
fi
cd PyQt-x11-gpl-${PYQT}
sudo make install
python -c 'import PyQt5' # Check if it's ok

View File

@@ -25,7 +25,7 @@ def test_dump():
note = NoteItem()
note.setPlainText("Test")
font = QtGui.QFont()
font.setPointSize(55)
font.setPointSizeF(55.8)
font.setFamily("Verdana")
font.setBold(True)
font.setItalic(True)
@@ -39,7 +39,7 @@ def test_dump():
"x": 0,
"y": 0,
"rotation": 0,
"style": "font-family: Verdana;font-size: 55;font-style: italic;font-weight: bold;text-decoration: line-through;fill: #ff0000;fill-opacity: 1.0;"
"style": "font-family: Verdana;font-size: 55.8;font-style: italic;font-weight: bold;text-decoration: line-through;fill: #ff0000;fill-opacity: 1.0;"
}
@@ -47,7 +47,7 @@ def test_setStyle():
note = NoteItem()
note.setPlainText("Test")
font = QtGui.QFont()
font.setPointSize(55)
font.setPointSizeF(55.8)
font.setFamily("Verdana")
font.setBold(True)
font.setItalic(True)
@@ -59,7 +59,7 @@ def test_setStyle():
style = note.dump()["style"]
note2 = NoteItem()
note2.setStyle(style)
assert note2.font().pointSize() == 55
assert note2.font().pointSizeF() == 55.8
assert note2.font().family() == "Verdana"
assert note2.font().italic()
assert note2.font().bold()

View File

@@ -31,7 +31,7 @@ def test_toSvg(project, controller):
text = svg[0]
assert text.get("font-family") == "TypeWriter"
assert text.get("font-size") == "10"
assert text.get("font-size") == "10.0"
assert text.get("fill") == "#000000"
assert text.get("fill-opacity") == "1.0"
assert text.text == "Hello"
@@ -40,7 +40,7 @@ def test_toSvg(project, controller):
def test_fromSvg(project, controller):
text = TextItem(project=project)
font = QtGui.QFont()
font.setPointSize(55)
font.setPointSizeF(55.8)
font.setFamily("Verdana")
font.setBold(True)
font.setItalic(True)
@@ -53,7 +53,7 @@ def test_fromSvg(project, controller):
text2.fromSvg(text.toSvg())
assert text2.toPlainText() == "Hello"
assert hex(text2.defaultTextColor().rgba()) == "0xffff0000"
assert text2.font().pointSize() == 55
assert text2.font().pointSizeF() == 55.8
assert text2.font().family() == "Verdana"
assert text2.font().italic()
assert text2.font().bold()

View File

@@ -29,3 +29,13 @@ def test_render_svg():
def test_render_png():
renderer = QImageSvgRenderer('resources/images/gns3_icon_256x256.png')
assert renderer.isValid()
def test_render_text_svg():
renderer = QImageSvgRenderer('<svg></svg>')
assert renderer.isValid()
def test_render_text_broken_svg():
renderer = QImageSvgRenderer('<svg></svg')
assert renderer.isValid() is False