mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-06-06 18:52:07 +03:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31e82bb410 | ||
|
|
cab3baf2c6 | ||
|
|
574da9c80a | ||
|
|
8321883199 | ||
|
|
98e4aefa65 | ||
|
|
67d816baa3 | ||
|
|
13a8bd4500 | ||
|
|
802b80b764 | ||
|
|
59292ff6cb | ||
|
|
7810d19f4d | ||
|
|
0788ce569f | ||
|
|
4b0379892d | ||
|
|
8f8994e7df | ||
|
|
4e172fc7e3 | ||
|
|
8ed8a2c115 | ||
|
|
073665a75d | ||
|
|
4ccc67aa46 | ||
|
|
6e2632e91f | ||
|
|
38ddcde902 | ||
|
|
436563afcb | ||
|
|
7eaab3e38b | ||
|
|
0927a2a8c9 | ||
|
|
87e6159ff6 | ||
|
|
effdcf5e24 | ||
|
|
021cdd2e65 | ||
|
|
363c4a9966 | ||
|
|
7082c75511 | ||
|
|
16c4a837d7 | ||
|
|
fd42ac410c | ||
|
|
6efc177804 | ||
|
|
6f9e6c9b92 | ||
|
|
0411c68150 | ||
|
|
4246e731e5 | ||
|
|
50cca71279 | ||
|
|
c78ef8f348 | ||
|
|
c03a5a9e0a | ||
|
|
c93b7836d8 | ||
|
|
690b22cc24 | ||
|
|
3560251816 | ||
|
|
90e861289f | ||
|
|
6e144d6122 | ||
|
|
2f168193d1 | ||
|
|
0fe5559564 | ||
|
|
37f0744d7c | ||
|
|
000f4a4790 | ||
|
|
a09b7d6738 | ||
|
|
a1fa8f9ec2 | ||
|
|
cd6b0b793e | ||
|
|
6453932421 | ||
|
|
37a23d9682 | ||
|
|
e18e10c701 | ||
|
|
a9240e2e46 | ||
|
|
4df0c33013 | ||
|
|
4096316ceb | ||
|
|
e09292c647 | ||
|
|
31c37161fa | ||
|
|
a047cd7f4c | ||
|
|
87cde665a8 | ||
|
|
6361c94bbe | ||
|
|
8a0aeff0bb | ||
|
|
508f8b3ad5 | ||
|
|
bfe942c029 | ||
|
|
b3a86594ff | ||
|
|
a948fd07b1 | ||
|
|
072f714e21 |
57
CHANGELOG
57
CHANGELOG
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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><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></string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0" colspan="3">
|
||||
<widget class="QGroupBox" name="uiGeneralGroupBox">
|
||||
|
||||
@@ -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:"))
|
||||
|
||||
|
||||
@@ -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><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></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>&Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<item row="0" column="3">
|
||||
<widget class="QPushButton" name="uiAddAllEthernetPushButton">
|
||||
<property name="text">
|
||||
<string>&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>&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">
|
||||
|
||||
@@ -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 "real world" (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"))
|
||||
|
||||
@@ -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><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></string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="uiGeneralGroupBox">
|
||||
|
||||
@@ -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"))
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
12
gns3/node.py
12
gns3/node.py
@@ -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
|
||||
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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", "")
|
||||
|
||||
@@ -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_()
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ p, li { white-space: pre-wrap; }
|
||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'.SF NS Text'; font-size:13pt; font-weight:400; font-style:normal;">
|
||||
</style></head><body style=" font-family:'Noto Sans'; font-size:9pt; font-weight:400; font-style:normal;">
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
@@ -3656,7 +3656,8 @@ Paul Sivie
|
||||
BlessLarryAgbemor
|
||||
Richard Miller
|
||||
Francesco Colista
|
||||
Stephen C. Moore</string>
|
||||
Stephen C. Moore
|
||||
Dominik Ziajka</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user