Compare commits

...

42 Commits

Author SHA1 Message Date
ziajka
05d9ee8499 Re-release v2.1.4 due to travis issue 2018-03-14 15:28:15 +01:00
ziajka
b91fd4a0c2 Development on v2.1.5dev1 2018-03-12 09:25:41 +01:00
ziajka
718217e332 Release v2.1.4 2018-03-12 09:17:16 +01:00
ziajka
c202c5e4be Move connect to update settings into one place 2018-03-09 13:31:55 +01:00
ziajka
71830dd69f Merge pull request #2449 from GNS3/update-nodes
Update node on server on any change, Fixes: #2429
2018-03-09 12:55:48 +01:00
ziajka
37a7fdfa68 Update node on server on any change, Fixes: #2429 2018-03-09 12:54:29 +01:00
grossmj
0efe006cad Mark IOU layer 1 keepalive messages feature as non-functional. Fixes #2431. 2018-03-05 16:44:42 +07:00
ziajka
4a663a5910 Fix typo 2018-02-27 16:08:33 +01:00
ziajka
a559bd4ae4 Images refresh when added via settings, Fixes:#2423 2018-02-27 16:07:06 +01:00
ziajka
5ebb3011d3 Merge pull request #2433 from GNS3/show-if-labels-on-new-project
Show labels on the new project, Fixes: #2308
2018-02-19 13:05:22 +01:00
ziajka
81300fd40e Adjust tests 2018-02-19 12:55:52 +01:00
ziajka
d4dda2a285 Emit project_loaded_signal after project creation 2018-02-19 12:54:36 +01:00
ziajka
5a4342d4b8 Add option Show interface labels on new project, Ref. #2308 2018-02-16 14:32:07 +01:00
ziajka
94fc5e6c4f Improve finding pyuic3.exe on Windows 2018-02-16 14:30:49 +01:00
grossmj
a3e81fbf2e Use debug for error downloading file messages. Fixes #2398. 2018-02-07 16:12:50 +08:00
grossmj
514eb97eac Merge remote-tracking branch 'origin/2.1' into 2.1 2018-02-06 15:38:38 +08:00
grossmj
7637039cb2 Refresh buttons in the cloud node to query the server for available interfaces. Fixes #2416. 2018-02-06 15:36:27 +08:00
Jeremy Grossmann
ac989b191b Merge pull request #2410 from GNS3/new-appliance-symbol-from-controller
Appliance import looks for symbols on server, Fixes. #2405
2018-02-02 10:24:32 +01:00
Dominik Ziajka
c971cef31b Handle Certifacte Error, Ref. gns3-server#1262 2018-02-02 10:02:18 +01:00
Dominik Ziajka
c1af2df780 Backward compatibility for tests, Ref. #2405? 2018-02-02 08:47:56 +01:00
grossmj
eaaa141be9 Use UTF-8 for IOURC file migration. 2018-02-02 15:41:42 +08:00
ziajka
226169cdc6 Look for symbols on controller, Ref. #2405 2018-02-01 17:42:02 +01:00
grossmj
42a4c89f20 Display an error message if Telnet console program cannot be executed. 2018-01-29 18:59:28 +07:00
grossmj
1482b0e804 Back to development on v2.1.4dev1 2018-01-21 15:57:41 +07:00
grossmj
8ebe3435c4 Re-release v2.1.3 to fix idna packaging issue. 2018-01-21 15:16:25 +07:00
ziajka
a1cd34d7c4 Back to development on v2.1.4dev1 2018-01-19 08:18:19 +01:00
ziajka
1e4a44135c Re-release v2.1.3 2018-01-19 08:11:46 +01:00
ziajka
a407f1ec90 Update to python3.6 in tests - running xvfb 2018-01-19 08:05:07 +01:00
ziajka
faab113384 Update to python3.6 in tests 2018-01-19 08:01:11 +01:00
ziajka
c158b7fc46 Use Ubuntu 17.10 for TCI tests 2018-01-19 07:55:13 +01:00
ziajka
16de9e830f Use Ubuntu 16.04 for TCI tests 2018-01-19 07:44:37 +01:00
ziajka
25c625c0bb Development on v2.1.4dev1 2018-01-19 07:17:22 +01:00
ziajka
bf42d1a355 Release v2.1.3 2018-01-19 07:15:24 +01:00
grossmj
1c0f3493ee Fix more client/server version tests. 2018-01-18 16:14:09 +08:00
grossmj
c3c1f87c5e Change messages when there are different client and server versions. Fixes #2391. 2018-01-18 15:58:21 +08:00
grossmj
6b80914385 Bump version number to 2.1.3dev1 2018-01-18 15:32:06 +08:00
grossmj
a114d9ace7 Fix "Transport selection via DSN is deprecated" message. Sync is configured with HTTPTransport. 2018-01-15 16:56:16 +07:00
grossmj
4dca4d057a Refresh CPU/RAM info every 1 second. Ref #2262. 2018-01-15 14:42:01 +07:00
grossmj
17af21e29a Only check for AVG on Windows 2018-01-14 13:40:31 +07:00
grossmj
7fbce0266d Improve the search for VBoxManage. 2018-01-11 16:33:15 +07:00
grossmj
d5cdbdbf90 Allow telnet console to node with name containing double quotes. Fixes #2371. 2018-01-10 22:16:35 +07:00
ziajka
e5a790f4b2 Development v2.1.3dev1 2018-01-08 14:21:28 +01:00
35 changed files with 520 additions and 192 deletions

View File

@@ -11,6 +11,8 @@ before_deploy:
- sudo pip install urllib3[secure]
deploy:
provider: pypi
edge:
branch: v1.8.45
user: noplay
password:
secure: FofcqlJjgqf2jaDaXpLHeigVoexbrOz3WwnDuiJpwJxeFUlPY8s2cQs/Bm+dzxzZaOaGiVE0A83v/Xa10yD5tflThHt4sqYJK3iQCinA7wgeAlDimB4xrWUNplfNJZ/Eod5Ssa++E02W+3i29PxpXY//mjCY7qDxaoxul1gnFJY=

View File

@@ -1,5 +1,30 @@
# Change Log
## 2.1.4 12/03/2018
* Update node on server on any change, Fixes: #2429
* Mark IOU layer 1 keepalive messages feature as non-functional. Fixes #2431.
* Images refresh when added via settings, Fixes:#2423
* Emit project_loaded_signal after project creation
* Add option Show interface labels on new project, Ref. #2308
* Improve finding pyuic3.exe on Windows
* Use debug for error downloading file messages. Fixes #2398.
* Refresh buttons in the cloud node to query the server for available interfaces. Fixes #2416.
* Handle Certifacte Error, Ref. gns3-server#1262
* Backward compatibility for tests, Ref. #2405?
* Use UTF-8 for IOURC file migration.
* Look for symbols on controller, Ref. #2405
* Display an error message if Telnet console program cannot be executed.
## 2.1.3 19/01/2018
* Change messages when there are different client and server versions. Fixes #2391.
* Fix "Transport selection via DSN is deprecated" message. Sync is configured with HTTPTransport.
* Refresh CPU/RAM info every 1 second. Ref #2262.
* Only check for AVG on Windows
* Improve the search for VBoxManage.
* Allow telnet console to node with name containing double quotes. Fixes #2371.
## 2.1.2 08/01/2018
* Update VMware promotion in setup wizard.

View File

@@ -1,12 +1,12 @@
# Run tests inside a container
FROM ubuntu:yakkety
FROM ubuntu:17.10
MAINTAINER GNS3 Team
#ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update
RUN apt-get install -y --force-yes python3.5 python3-pyqt5 python3-pip python3-pyqt5.qtsvg python3-pyqt5.qtwebsockets python3.5-dev xvfb
RUN apt-get install -y --force-yes python3.6 python3-pyqt5 python3-pip python3-pyqt5.qtsvg python3-pyqt5.qtwebsockets python3.6-dev xvfb
RUN apt-get clean
@@ -19,4 +19,4 @@ ADD . /src
WORKDIR /src
CMD xvfb-run python3.5 -m pytest -vv
CMD xvfb-run python3.6 -m pytest -vv

View File

@@ -55,7 +55,7 @@ class ComputeManager(QtCore.QObject):
def _refreshComputesSlot(self):
if self._refreshingComputes:
return
if self._controller.connected() and datetime.datetime.now().timestamp() - self._last_computes_refresh > 5:
if self._controller.connected() and datetime.datetime.now().timestamp() - self._last_computes_refresh > 1:
self._last_computes_refresh = datetime.datetime.now().timestamp()
self._refreshingComputes = True
self._controller.get("/computes", self._listComputesCallback, showProgress=False, timeout=30)
@@ -84,6 +84,7 @@ class ComputeManager(QtCore.QObject):
Called when we received data from a compute
node.
"""
self._last_computes_refresh = datetime.datetime.now().timestamp()
new_node = False

View File

@@ -237,13 +237,9 @@ class Controller(QtCore.QObject):
if not self._http_client:
return
m = hashlib.md5()
m.update(url.encode())
if ".svg" in url:
extension = ".svg"
else:
extension = ".png"
path = os.path.join(self._cache_directory, m.hexdigest() + extension)
path = self.getStaticCachedPath(url)
if os.path.exists(path):
callback(path)
elif path in self._static_asset_download_queue:
@@ -263,8 +259,7 @@ class Controller(QtCore.QObject):
self.getStatic(fallback, callback)
fallback_used = True
if fallback_used:
log.error("Error while downloading file: {}".format(url))
log.error("Error while downloading file: {}".format(url))
log.debug("Error while downloading file: {}".format(url))
del self._static_asset_download_queue[path]
return
try:
@@ -278,6 +273,21 @@ class Controller(QtCore.QObject):
callback(path)
del self._static_asset_download_queue[path]
def getStaticCachedPath(self, url):
"""
Returns static cached (hashed) path
:param url:
:return:
"""
m = hashlib.md5()
m.update(url.encode())
if ".svg" in url:
extension = ".svg"
else:
extension = ".png"
path = os.path.join(self._cache_directory, m.hexdigest() + extension)
return path
def getSymbolIcon(self, symbol_id, callback, fallback=None):
"""
Get a QIcon for a symbol from the controller
@@ -298,6 +308,9 @@ class Controller(QtCore.QObject):
icon.addFile(path)
callback(icon)
def getSymbols(self, callback):
self.get('/symbols', callback=callback)
def deleteProject(self, project_id, callback=None):
Controller.instance().delete("/projects/{}".format(project_id), qpartial(self._deleteProjectCallback, callback=callback, project_id=project_id))

View File

@@ -51,7 +51,7 @@ class CrashReport:
Report crash to a third party service
"""
DSN = "sync+https://5e56cf6924c94ad594e040a66735b112:3103c4e6e6564d68ab7623e9911b4db2@sentry.io/38506"
DSN = "sync+https://b892f3e4a56e4443ad16cfe3d4c6d602:ca3ad57e613441ab95ea15a45e9e5435@sentry.io/38506"
if hasattr(sys, "frozen"):
cacert = get_resource("cacert.pem")
if cacert is not None and os.path.isfile(cacert):

View File

@@ -34,6 +34,9 @@ from ..controller import Controller
from ..local_config import LocalConfig
from ..image_upload_manager import ImageUploadManager
import logging
log = logging.getLogger(__name__)
class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
images_changed_signal = QtCore.Signal()
@@ -41,6 +44,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
def __init__(self, parent, path):
super().__init__(parent)
self.setupUi(self)
self.images_changed_signal.connect(self._refreshVersions)
self.versions_changed_signal.connect(self._versionRefreshedSlot)
@@ -86,6 +90,10 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
self.uiServerWizardPage.isComplete = self._uiServerWizardPage_isComplete
# symbols loaded from controller
self._symbols = []
def initializePage(self, page_id):
"""
Initialize Wizard pages.
@@ -110,6 +118,8 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
type = "dynamips"
if self.page(page_id) == self.uiInfoWizardPage:
Controller.instance().getSymbols(self._getSymbolsCallback)
self.uiInfoWizardPage.setTitle(self._appliance["product_name"])
self.uiDescriptionLabel.setText(self._appliance["description"])
@@ -316,6 +326,12 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
self.uiApplianceVersionTreeWidget.resizeColumnToContents(1)
self._refreshing = False
def _getSymbolsCallback(self, result, error=False, **kwargs):
if error:
log.warning("Cannot load symbols from controller")
else:
self._symbols = result
def _refreshDialogWorker(self):
"""
Scan local directory in order to found the images on disk
@@ -482,7 +498,10 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if "qemu" in appliance_configuration:
appliance_configuration["qemu"]["path"] = self.uiQemuListComboBox.currentData()
worker = WaitForLambdaWorker(lambda: config.add_appliance(appliance_configuration, self._compute_id), allowed_exceptions=[ConfigException, OSError])
worker = WaitForLambdaWorker(
lambda: config.add_appliance(appliance_configuration, self._compute_id, self._symbols),
allowed_exceptions=[ConfigException, OSError])
progress_dialog = ProgressDialog(worker, "Add appliance", "Install the appliance...", None, busy=True, parent=self)
progress_dialog.show()
if not progress_dialog.exec_():

View File

@@ -95,13 +95,14 @@ class DoctorDialog(QtWidgets.QDialog, Ui_DoctorDialog):
def checkAVGInstalled(self):
"""Checking if AVG software is not installed"""
for proc in psutil.process_iter():
try:
psinfo = proc.as_dict(["exe"])
if psinfo["exe"] and "AVG\\" in psinfo["exe"]:
return (2, "AVG has known issues with GNS3, even after you disable it. You must whitelist dynamips.exe in the AVG preferences.")
except psutil.NoSuchProcess:
pass
if sys.platform.startswith("win32"):
for proc in psutil.process_iter():
try:
psinfo = proc.as_dict(["exe"])
if psinfo["exe"] and "AVG\\" in psinfo["exe"]:
return (2, "AVG has known issues with GNS3, even after you disable it. You must whitelist dynamips.exe in the AVG preferences.")
except psutil.NoSuchProcess:
pass
return (0, None)
def checkFreeRam(self):

View File

@@ -391,21 +391,22 @@ class HTTPClient(QtCore.QObject):
return
if params["version"].split("-")[0] != __version__.split("-")[0]:
msg = "Client version {} differs with server version {}".format(__version__, params["version"])
log.error(msg)
msg = "Client version {} is not the same as server version {}".format(__version__, params["version"])
# Stable release
if __version_info__[3] == 0:
log.error(msg)
for request, callback in self._query_waiting_connections:
if callback is not None:
callback({"message": msg}, error=True, server=server)
return
# We don't allow different major version to interact even with dev build
elif parse_version(__version__)[:2] != parse_version(params["version"])[:2]:
log.error(msg)
for request, callback in self._query_waiting_connections:
if callback is not None:
callback({"message": msg}, error=True, server=server)
return
log.warning("Use a different client and server version can create bugs. Use it at your own risk.")
log.warning("{}\nUsing different versions may result in unexpected problems. Please use at your own risk.".format(msg))
self._connected = True
self._retry = 0

View File

@@ -118,8 +118,7 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
"""
Sync change to the node
"""
if self._initialized:
self._node.setGraphics(self)
self._node.setGraphics(self)
@qslot
def setSymbol(self, symbol):

View File

@@ -265,11 +265,13 @@ class LocalConfig(QtCore.QObject):
if "version" not in self._settings or parse_version(self._settings["version"]) < parse_version("2.0.0"):
if "IOU" in self._settings and "iourc_path" in self._settings["IOU"] and "iourc_content" not in self._settings["IOU"]:
try:
with open(self._settings["IOU"]["iourc_path"], "r") as f:
with open(self._settings["IOU"]["iourc_path"], "r", encoding="utf-8") as f:
self._settings["IOU"]["iourc_content"] = f.read().replace("\r\n", "\n")
del self._settings["IOU"]["iourc_path"]
except OSError as e:
log.warn("Can't import IOU licence {}: {}".format(self._settings["IOU"]["iourc_path"], str(e)))
log.warning("Can't import IOU licence {}: {}".format(self._settings["IOU"]["iourc_path"], str(e)))
except UnicodeDecodeError as e:
log.warning("Non ascii characters in iourc file {}, please remove them: {}".format(self._settings["IOU"]["iourc_path"], str(e)))
def _readConfig(self, config_path):
"""
@@ -475,6 +477,21 @@ class LocalConfig(QtCore.QObject):
settings["direct_file_upload"] = value
self.saveSectionSettings("MainWindow", settings)
def showInterfaceLabelsOnNewProject(self):
"""
:returns: Boolean. True if show_interface_labels_on_new_project is enabled
"""
from gns3.settings import GRAPHICS_VIEW_SETTINGS
return self.loadSectionSettings("GraphicsView", GRAPHICS_VIEW_SETTINGS) \
.get("show_interface_labels_on_new_project", False)
def setShowInterfaceLabelsOnNewProject(self, value):
from gns3.settings import GRAPHICS_VIEW_SETTINGS
settings = self.loadSectionSettings("GraphicsView", GRAPHICS_VIEW_SETTINGS)
settings["show_interface_labels_on_new_project"] = value
self.saveSectionSettings("GraphicsView", settings)
@staticmethod
def instance():
"""

View File

@@ -54,6 +54,7 @@ from .dialogs.new_appliance_dialog import NewApplianceDialog
from .dialogs.notif_dialog import NotifDialog, NotifDialogHandler
from .status_bar import StatusBarHandler
from .registry.appliance import ApplianceError
from .appliance_manager import ApplianceManager
log = logging.getLogger(__name__)
@@ -69,6 +70,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
# signal to tell the view if the user is adding a link or not
adding_link_signal = QtCore.pyqtSignal(bool)
# Signal of settings updates
settings_updated_signal = QtCore.Signal()
def __init__(self, parent=None, open_file=None):
"""
:param open_file: Open this file instead of asking for a new project
@@ -110,6 +114,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._local_config_timer.timeout.connect(local_config.checkConfigChanged)
self._local_config_timer.start(1000) # milliseconds
self._analytics_client = AnalyticsClient()
self._appliance_manager = ApplianceManager()
# restore the geometry and state of the main window.
self.restoreGeometry(QtCore.QByteArray().fromBase64(self._settings["geometry"].encode()))
@@ -271,6 +276,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
# connect the signal to the view
self.adding_link_signal.connect(self.uiGraphicsView.addingLinkSlot)
# connect to the signal when settings change
self.settings_updated_signal.connect(self.settingsChangedSlot)
def _loadSettings(self):
"""
Loads the settings from the persistent settings file.
@@ -303,6 +311,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._settings.update(new_settings)
# save the settings
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
self.settings_updated_signal.emit()
def _openWebInterfaceActionSlot(self):
if Controller.instance().connected():
@@ -491,6 +500,17 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._project_dialog = None
self._refreshVisibleWidgets()
@qslot
def settingsChangedSlot(self, *args):
"""
Called when settings are updated
"""
# It covers case when project is not set
# and we need to refresh appliance manager
project = Topology.instance().project()
if project is None:
self._appliance_manager.instance().refresh()
def _refreshVisibleWidgets(self):
"""
Refresh widgets that should be visible or not
@@ -611,7 +631,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
for item in self.uiGraphicsView.scene().items():
if isinstance(item, LinkItem):
item.adjust()
def _updateZoomSettings(self, zoom=None):
"""
Updates zoom settings

View File

@@ -58,18 +58,19 @@ class Cloud(Node):
if "interfaces" in result:
self._interfaces = result["interfaces"].copy()
def update(self, new_settings):
def update(self, new_settings, force=False):
"""
Updates the settings for this cloud.
:param new_settings: settings dictionary
:param force: force this node to update
"""
params = {}
for name, value in new_settings.items():
if name in self._settings and self._settings[name] != value:
params[name] = value
if params:
if params or force:
self._update(params)
def _updateCallback(self, result):

View File

@@ -50,6 +50,7 @@ class CloudConfigurationPage(QtWidgets.QWidget, Ui_cloudConfigPageWidget):
self.uiEthernetWarningPushButton.clicked.connect(self._EthernetWarningSlot)
self.uiAddEthernetPushButton.clicked.connect(self._EthernetAddSlot)
self.uiAddAllEthernetPushButton.clicked.connect(self._EthernetAddAllSlot)
self.uiRefreshEthernetPushButton.clicked.connect(self._EthernetRefreshSlot)
self.uiDeleteEthernetPushButton.clicked.connect(self._EthernetDeleteSlot)
# connect TAP slots
@@ -57,6 +58,7 @@ class CloudConfigurationPage(QtWidgets.QWidget, Ui_cloudConfigPageWidget):
self.uiTAPListWidget.itemSelectionChanged.connect(self._TAPChangedSlot)
self.uiAddTAPPushButton.clicked.connect(self._TAPAddSlot)
self.uiAddAllTAPPushButton.clicked.connect(self._TAPAddAllSlot)
self.uiRefreshTAPPushButton.clicked.connect(self._TAPRefreshSlot)
self.uiDeleteTAPPushButton.clicked.connect(self._TAPDeleteSlot)
# connect UDP slots
@@ -74,6 +76,16 @@ class CloudConfigurationPage(QtWidgets.QWidget, Ui_cloudConfigPageWidget):
icon = QtGui.QIcon(':/icons/dialog-warning.svg')
self.uiEthernetWarningPushButton.setIcon(icon)
def _refreshInterfaces(self):
"""
Refresh the network interfaces.
"""
if self._node:
self._interfaces = self._node.interfaces()
self._loadNetworkInterfaces(self._interfaces)
self._node.updated_signal.disconnect(self._refreshInterfaces)
def _EthernetChangedSlot(self):
"""
Enables the use of the delete button.
@@ -121,6 +133,15 @@ class CloudConfigurationPage(QtWidgets.QWidget, Ui_cloudConfigPageWidget):
interface = self.uiEthernetComboBox.itemText(index)
self._EthernetAddSlot(interface)
def _EthernetRefreshSlot(self):
"""
Refresh all Ethernet interfaces.
"""
if self._node:
self._node.update({}, force=True)
self._node.updated_signal.connect(self._refreshInterfaces)
def _EthernetDeleteSlot(self):
"""
Deletes the selected Ethernet interface.
@@ -199,6 +220,15 @@ class CloudConfigurationPage(QtWidgets.QWidget, Ui_cloudConfigPageWidget):
interface = self.uiTAPComboBox.itemText(index)
self._TAPAddSlot(interface)
def _TAPRefreshSlot(self):
"""
Refresh all TAP interfaces.
"""
if self._node:
self._node.update({}, force=True)
self._node.updated_signal.connect(self._refreshInterfaces)
def _TAPDeleteSlot(self):
"""
Deletes a TAP interface.

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>821</width>
<height>363</height>
<width>1000</width>
<height>378</height>
</rect>
</property>
<property name="windowTitle">
@@ -57,7 +57,7 @@
</property>
</widget>
</item>
<item row="0" column="4">
<item row="0" column="5">
<widget class="QPushButton" name="uiDeleteEthernetPushButton">
<property name="enabled">
<bool>false</bool>
@@ -67,7 +67,7 @@
</property>
</widget>
</item>
<item row="1" column="0" colspan="5">
<item row="1" column="0" colspan="6">
<widget class="QListWidget" name="uiEthernetListWidget">
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
@@ -91,6 +91,13 @@
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QPushButton" name="uiRefreshEthernetPushButton">
<property name="text">
<string>&amp;Refresh</string>
</property>
</widget>
</item>
</layout>
<zorder>uiEthernetListWidget</zorder>
<zorder>uiEthernetComboBox</zorder>
@@ -99,13 +106,14 @@
<zorder>uiAddAllEthernetPushButton</zorder>
<zorder>uiShowSpecialInterfacesCheckBox</zorder>
<zorder>uiEthernetWarningPushButton</zorder>
<zorder>uiRefreshEthernetPushButton</zorder>
</widget>
<widget class="QWidget" name="TAPTab">
<attribute name="title">
<string>TAP interfaces</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="4">
<item row="1" column="5">
<widget class="QPushButton" name="uiDeleteTAPPushButton">
<property name="enabled">
<bool>false</bool>
@@ -115,7 +123,7 @@
</property>
</widget>
</item>
<item row="2" column="0" colspan="5">
<item row="2" column="0" colspan="6">
<widget class="QListWidget" name="uiTAPListWidget">
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
@@ -142,7 +150,7 @@
</property>
</widget>
</item>
<item row="0" column="1" colspan="4">
<item row="0" column="1" colspan="5">
<widget class="QComboBox" name="uiTAPComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@@ -165,6 +173,13 @@
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QPushButton" name="uiRefreshTAPPushButton">
<property name="text">
<string>&amp;Refresh</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="UDPTab">

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/builtin/ui/cloud_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.9
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
@@ -11,7 +11,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_cloudConfigPageWidget(object):
def setupUi(self, cloudConfigPageWidget):
cloudConfigPageWidget.setObjectName("cloudConfigPageWidget")
cloudConfigPageWidget.resize(821, 363)
cloudConfigPageWidget.resize(1000, 378)
self.verticalLayout = QtWidgets.QVBoxLayout(cloudConfigPageWidget)
self.verticalLayout.setObjectName("verticalLayout")
self.uiTabWidget = QtWidgets.QTabWidget(cloudConfigPageWidget)
@@ -39,11 +39,11 @@ class Ui_cloudConfigPageWidget(object):
self.uiDeleteEthernetPushButton = QtWidgets.QPushButton(self.EthernetTab)
self.uiDeleteEthernetPushButton.setEnabled(False)
self.uiDeleteEthernetPushButton.setObjectName("uiDeleteEthernetPushButton")
self.gridLayout_3.addWidget(self.uiDeleteEthernetPushButton, 0, 4, 1, 1)
self.gridLayout_3.addWidget(self.uiDeleteEthernetPushButton, 0, 5, 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, 5)
self.gridLayout_3.addWidget(self.uiEthernetListWidget, 1, 0, 1, 6)
self.uiEthernetWarningPushButton = QtWidgets.QPushButton(self.EthernetTab)
self.uiEthernetWarningPushButton.setText("")
self.uiEthernetWarningPushButton.setObjectName("uiEthernetWarningPushButton")
@@ -51,6 +51,9 @@ class Ui_cloudConfigPageWidget(object):
self.uiShowSpecialInterfacesCheckBox = QtWidgets.QCheckBox(self.EthernetTab)
self.uiShowSpecialInterfacesCheckBox.setObjectName("uiShowSpecialInterfacesCheckBox")
self.gridLayout_3.addWidget(self.uiShowSpecialInterfacesCheckBox, 2, 0, 1, 2)
self.uiRefreshEthernetPushButton = QtWidgets.QPushButton(self.EthernetTab)
self.uiRefreshEthernetPushButton.setObjectName("uiRefreshEthernetPushButton")
self.gridLayout_3.addWidget(self.uiRefreshEthernetPushButton, 0, 4, 1, 1)
self.uiEthernetListWidget.raise_()
self.uiEthernetComboBox.raise_()
self.uiAddEthernetPushButton.raise_()
@@ -58,6 +61,7 @@ class Ui_cloudConfigPageWidget(object):
self.uiAddAllEthernetPushButton.raise_()
self.uiShowSpecialInterfacesCheckBox.raise_()
self.uiEthernetWarningPushButton.raise_()
self.uiRefreshEthernetPushButton.raise_()
self.uiTabWidget.addTab(self.EthernetTab, "")
self.TAPTab = QtWidgets.QWidget()
self.TAPTab.setObjectName("TAPTab")
@@ -66,11 +70,11 @@ class Ui_cloudConfigPageWidget(object):
self.uiDeleteTAPPushButton = QtWidgets.QPushButton(self.TAPTab)
self.uiDeleteTAPPushButton.setEnabled(False)
self.uiDeleteTAPPushButton.setObjectName("uiDeleteTAPPushButton")
self.gridLayout_2.addWidget(self.uiDeleteTAPPushButton, 1, 4, 1, 1)
self.gridLayout_2.addWidget(self.uiDeleteTAPPushButton, 1, 5, 1, 1)
self.uiTAPListWidget = QtWidgets.QListWidget(self.TAPTab)
self.uiTAPListWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.uiTAPListWidget.setObjectName("uiTAPListWidget")
self.gridLayout_2.addWidget(self.uiTAPListWidget, 2, 0, 1, 5)
self.gridLayout_2.addWidget(self.uiTAPListWidget, 2, 0, 1, 6)
self.uiTAPLineEdit = QtWidgets.QLineEdit(self.TAPTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -91,10 +95,13 @@ class Ui_cloudConfigPageWidget(object):
self.uiTAPComboBox.setInsertPolicy(QtWidgets.QComboBox.InsertAlphabetically)
self.uiTAPComboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
self.uiTAPComboBox.setObjectName("uiTAPComboBox")
self.gridLayout_2.addWidget(self.uiTAPComboBox, 0, 1, 1, 4)
self.gridLayout_2.addWidget(self.uiTAPComboBox, 0, 1, 1, 5)
self.uiAddAllTAPPushButton = QtWidgets.QPushButton(self.TAPTab)
self.uiAddAllTAPPushButton.setObjectName("uiAddAllTAPPushButton")
self.gridLayout_2.addWidget(self.uiAddAllTAPPushButton, 1, 3, 1, 1)
self.uiRefreshTAPPushButton = QtWidgets.QPushButton(self.TAPTab)
self.uiRefreshTAPPushButton.setObjectName("uiRefreshTAPPushButton")
self.gridLayout_2.addWidget(self.uiRefreshTAPPushButton, 1, 4, 1, 1)
self.uiTabWidget.addTab(self.TAPTab, "")
self.UDPTab = QtWidgets.QWidget()
self.UDPTab.setObjectName("UDPTab")
@@ -241,11 +248,13 @@ class Ui_cloudConfigPageWidget(object):
self.uiDeleteEthernetPushButton.setText(_translate("cloudConfigPageWidget", "&Delete"))
self.uiEthernetListWidget.setSortingEnabled(True)
self.uiShowSpecialInterfacesCheckBox.setText(_translate("cloudConfigPageWidget", "&Show special Ethernet interfaces"))
self.uiRefreshEthernetPushButton.setText(_translate("cloudConfigPageWidget", "&Refresh"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.EthernetTab), _translate("cloudConfigPageWidget", "Ethernet interfaces"))
self.uiDeleteTAPPushButton.setText(_translate("cloudConfigPageWidget", "&Delete"))
self.uiTAPListWidget.setSortingEnabled(True)
self.uiAddTAPPushButton.setText(_translate("cloudConfigPageWidget", "&Add"))
self.uiAddAllTAPPushButton.setText(_translate("cloudConfigPageWidget", "&Add all"))
self.uiRefreshTAPPushButton.setText(_translate("cloudConfigPageWidget", "&Refresh"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.TAPTab), _translate("cloudConfigPageWidget", "TAP interfaces"))
self.uiUDPTunnelSettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "UDP tunnel settings"))
self.uiRemoteHostLineEdit.setText(_translate("cloudConfigPageWidget", "127.0.0.1"))

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>569</width>
<height>503</height>
<width>767</width>
<height>685</height>
</rect>
</property>
<property name="windowTitle">
@@ -181,7 +181,7 @@
<bool>true</bool>
</property>
<property name="text">
<string>Enable layer 1 keepalive messages (testing only)</string>
<string>Enable layer 1 keepalive messages (non-functional)</string>
</property>
<property name="checked">
<bool>false</bool>

View File

@@ -2,8 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/iou/ui/iou_device_configuration_page.ui'
#
# Created: Thu Jan 5 14:49:45 2017
# 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!
@@ -12,7 +11,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_iouDeviceConfigPageWidget(object):
def setupUi(self, iouDeviceConfigPageWidget):
iouDeviceConfigPageWidget.setObjectName("iouDeviceConfigPageWidget")
iouDeviceConfigPageWidget.resize(569, 503)
iouDeviceConfigPageWidget.resize(767, 685)
self.verticalLayout = QtWidgets.QVBoxLayout(iouDeviceConfigPageWidget)
self.verticalLayout.setObjectName("verticalLayout")
self.uiTabWidget = QtWidgets.QTabWidget(iouDeviceConfigPageWidget)
@@ -209,7 +208,7 @@ class Ui_iouDeviceConfigPageWidget(object):
self.uiPrivateConfigToolButton.setText(_translate("iouDeviceConfigPageWidget", "&Browse..."))
self.uiDefaultNameFormatLabel.setText(_translate("iouDeviceConfigPageWidget", "Default name format:"))
self.uiOtherSettingsGroupBox.setTitle(_translate("iouDeviceConfigPageWidget", "Other settings"))
self.uiL1KeepalivesCheckBox.setText(_translate("iouDeviceConfigPageWidget", "Enable layer 1 keepalive messages (testing only)"))
self.uiL1KeepalivesCheckBox.setText(_translate("iouDeviceConfigPageWidget", "Enable layer 1 keepalive messages (non-functional)"))
self.uiDefaultValuesCheckBox.setText(_translate("iouDeviceConfigPageWidget", "Use default IOU values for memories"))
self.uiRamLabel.setText(_translate("iouDeviceConfigPageWidget", "RAM size:"))
self.uiRamSpinBox.setSuffix(_translate("iouDeviceConfigPageWidget", " MB"))

View File

@@ -23,12 +23,10 @@ import os
import sys
import shutil
from gns3.qt import QtWidgets
from gns3.local_server_config import LocalServerConfig
from gns3.local_config import LocalConfig
from ..module import Module
from ..module_error import ModuleError
from .virtualbox_vm import VirtualBoxVM
from .settings import VBOX_SETTINGS
from .settings import VBOX_VM_SETTINGS
@@ -78,7 +76,8 @@ class VirtualBox(Module):
vboxmanage_path_osx = "/Applications/VirtualBox.app/Contents/MacOS/VBoxManage"
if os.path.exists(vboxmanage_path_osx):
vboxmanage_path = vboxmanage_path_osx
else:
if vboxmanage_path is None:
vboxmanage_path = shutil.which("vboxmanage")
if vboxmanage_path is None:

View File

@@ -100,15 +100,11 @@ class Node(BaseNode):
for key in data:
if key not in self._settings or self._settings[key] != data[key]:
changed = True
if not changed:
return
# If it's the initialization we don't resend it
# to the server
if self._settings["x"] is not None:
self._update(data)
else:
self._settings.update(data)
self._update(data)
def setSymbol(self, symbol):
self._settings["symbol"] = symbol
@@ -227,10 +223,9 @@ class Node(BaseNode):
Update the node on the controller
"""
if self.initialized():
log.debug("{} is updating settings: {}".format(self.name(), params))
body = self._prepareBody(params)
self.controllerHttpPut("/nodes/{node_id}".format(node_id=self._node_id), self.updateNodeCallback, body=body, timeout=timeout, showProgress=False)
log.debug("{} is updating settings: {}".format(self.name(), params))
body = self._prepareBody(params)
self.controllerHttpPut("/nodes/{node_id}".format(node_id=self._node_id), self.updateNodeCallback, body=body, timeout=timeout, showProgress=False)
def updateNodeCallback(self, result, error=False, **kwargs):
"""
@@ -355,7 +350,6 @@ class Node(BaseNode):
return False
result = self._parseResponse(result)
self._created = True
self._createCallback(result)
if self._loading:

View File

@@ -131,7 +131,6 @@ class NodesView(QtWidgets.QTreeWidget):
item.setData(1, QtCore.Qt.UserRole, "appliance_template")
item.setSizeHint(0, QtCore.QSize(32, 32))
Controller.instance().getSymbolIcon(appliance.get("symbol"), qpartial(self._setItemIcon, item), fallback=":/symbols/" + appliance["category"] + ".svg")
self.sortByColumn(0, QtCore.Qt.AscendingOrder)
def _setItemIcon(self, item, icon):

View File

@@ -318,6 +318,7 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
self.uiSceneHeightSpinBox.setValue(settings["scene_height"])
self.uiRectangleSelectedItemCheckBox.setChecked(settings["draw_rectangle_selected_item"])
self.uiDrawLinkStatusPointsCheckBox.setChecked(settings["draw_link_status_points"])
self.uiShowInterfaceLabelsOnNewProject.setChecked(settings["show_interface_labels_on_new_project"])
qt_font = QtGui.QFont()
if qt_font.fromString(settings["default_label_font"]):
@@ -380,6 +381,7 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
"scene_height": self.uiSceneHeightSpinBox.value(),
"draw_rectangle_selected_item": self.uiRectangleSelectedItemCheckBox.isChecked(),
"draw_link_status_points": self.uiDrawLinkStatusPointsCheckBox.isChecked(),
"show_interface_labels_on_new_project": self.uiShowInterfaceLabelsOnNewProject.isChecked(),
"default_label_font": self.uiDefaultLabelStylePlainTextEdit.font().toString(),
"default_label_color": self._default_label_color.name()}
MainWindow.instance().uiGraphicsView.setSettings(new_graphics_view_settings)

View File

@@ -60,7 +60,9 @@ class Project(QtCore.QObject):
self._auto_open = False
self._auto_close = False
graphic_settings = LocalConfig.instance().loadSectionSettings(self.__class__.__name__, GRAPHICS_VIEW_SETTINGS)
config = LocalConfig.instance()
graphic_settings = LocalConfig.instance().loadSectionSettings("GraphicsView", GRAPHICS_VIEW_SETTINGS)
self._scene_width = graphic_settings["scene_width"]
self._scene_height = graphic_settings["scene_height"]
self._zoom = graphic_settings.get("zoom", None)
@@ -68,6 +70,7 @@ class Project(QtCore.QObject):
self._snap_to_grid = graphic_settings.get("snap_to_grid", False)
self._show_grid = graphic_settings.get("show_grid", False)
self._show_interface_labels = graphic_settings.get("show_interface_labels", False)
self._show_interface_labels_on_new_project = config.showInterfaceLabelsOnNewProject()
self._name = "untitled"
self._filename = None
@@ -373,7 +376,8 @@ class Project(QtCore.QObject):
"""
body = {
"name": self._name,
"path": self.filesDir()
"path": self.filesDir(),
"show_interface_labels": self._show_interface_labels_on_new_project
}
Controller.instance().post("/projects", self._projectCreatedCallback, body=body)
@@ -413,6 +417,7 @@ class Project(QtCore.QObject):
self._closing = False
self._startListenNotifications()
self.project_updated_signal.emit()
self.project_loaded_signal.emit()
def _parseResponse(self, result):
"""

View File

@@ -20,7 +20,10 @@
import json
import os
import urllib
import shutil
from ssl import CertificateError
from gns3.controller import Controller
from ..local_config import LocalConfig
from ..local_server_config import LocalServerConfig
from ..settings import LOCAL_SERVER_SETTINGS
@@ -96,14 +99,18 @@ class Config:
return False
return True
def add_appliance(self, appliance_config, server):
def add_appliance(self, appliance_config, server, controller_symbols=None):
"""
Add appliance to the user configuration
:param appliance_config: Dictionary with appliance configuration
:param server
:param controller_symbols: Symbols located on controller
"""
if controller_symbols is None:
controller_symbols = []
new_config = {
"server": server,
"name": appliance_config["name"]
@@ -124,7 +131,7 @@ class Config:
new_config["category"] = 1
if "symbol" in appliance_config:
new_config["symbol"] = self._set_symbol(appliance_config["symbol"])
new_config["symbol"] = self._set_symbol(appliance_config["symbol"], controller_symbols)
if new_config.get("symbol") is None:
if appliance_config["category"] == "guest":
@@ -276,9 +283,9 @@ class Config:
self._config["Qemu"].setdefault("vms", [])
self._config["Qemu"]["vms"].append(new_config)
def _set_symbol(self, symbol):
def _set_symbol(self, symbol, controller_symbols):
"""
Download symbol for the web if need
Check if exists on controller or download symbol from the web if needed
"""
# GNS3 builtin symbol
@@ -289,11 +296,25 @@ class Config:
if os.path.exists(path):
return os.path.basename(path)
is_symbol_on_controller = len([s for s in controller_symbols
if s['symbol_id'] == symbol]) > 0
if is_symbol_on_controller:
cached = Controller.instance().getStaticCachedPath(symbol)
if os.path.exists(cached):
try:
shutil.copy(cached, path)
except IOError as e:
log.warning("Cannot copy cached symbol from `{}` to `{}` due `{}`".format(
cached, path, str(e)
))
return symbol
url = "https://raw.githubusercontent.com/GNS3/gns3-registry/master/symbols/{}".format(symbol)
try:
urllib.request.urlretrieve(url, path)
return os.path.basename(path)
except OSError:
except (OSError, CertificateError):
return None
def _relative_image_path(self, image_dir_type, path):

View File

@@ -289,7 +289,8 @@ GRAPHICS_VIEW_SETTINGS = {
"show_layers": False,
"snap_to_grid": False,
"show_grid": False,
"show_interface_labels": False
"show_interface_labels": False,
"show_interface_labels_on_new_project": False
}
LOCAL_SERVER_SETTINGS = {

View File

@@ -19,7 +19,7 @@
Functions to start external console terminals.
"""
from .qt import QtCore, QtWidgets
from .qt import QtCore
import os
import sys
@@ -58,7 +58,7 @@ class ConsoleThread(QtCore.QThread):
try:
args = shlex.split(command)
except ValueError:
self.consoleError.emit("Syntax error in command: {}".format(command))
self.consoleError.emit("Syntax error in command: '{}'".format(command))
return
subprocess.call(args, env=os.environ)
@@ -70,7 +70,7 @@ class ConsoleThread(QtCore.QThread):
# replace the place-holders by the actual values
command = self._command.replace("%h", host)
command = command.replace("%p", str(port))
command = command.replace("%d", self._name)
command = command.replace("%d", self._name.replace('"', '\\"'))
command = command.replace("%i", self._node.project().id())
command = command.replace("%n", str(self._node.id()))
command = command.replace("%c", Controller.instance().httpClient().fullUrl())
@@ -83,8 +83,7 @@ class ConsoleThread(QtCore.QThread):
try:
self.exec_command(command)
except (OSError, subprocess.SubprocessError) as e:
pass
# log.warning('could not start Telnet console "{}": {}'.format(self._command, e))
self.consoleError.emit("Could not start Telnet console with command '{}': {}".format(command, e))
finally:
log.debug('Telnet console {}:{} closed'.format(host, port))
if sys.platform.startswith("darwin") and "osascript" in command:
@@ -115,4 +114,4 @@ def nodeTelnetConsole(node, port, command=None):
def _consoleErrorSlot(message):
QtWidgets.QMessageBox.critical(MainWindow.instance(), "Error", message)
log.error(message)

View File

@@ -30,7 +30,16 @@
<string>General</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="margin">
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item>
@@ -238,7 +247,16 @@
<string>Binary images</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="margin">
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item>
@@ -372,7 +390,16 @@
<string>Console applications</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="margin">
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item>
@@ -483,7 +510,16 @@
<string>VNC</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_6">
<property name="margin">
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item>
@@ -644,10 +680,36 @@
<string>Topology view</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_8">
<property name="margin">
<property name="leftMargin">
<number>10</number>
</property>
<item row="9" column="0" colspan="2">
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="uiRectangleSelectedItemCheckBox">
<property name="text">
<string>Draw a rectangle when an item is selected</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="uiSceneWidthLabel">
<property name="text">
<string>Default width:</string>
</property>
</widget>
</item>
<item row="10" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QPushButton" name="uiDefaultLabelFontPushButton">
@@ -678,13 +740,6 @@
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QLabel" name="uiSceneWidthLabel">
<property name="text">
<string>Default width:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="uiSceneHeightLabel">
<property name="text">
@@ -692,55 +747,6 @@
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="uiRectangleSelectedItemCheckBox">
<property name="text">
<string>Draw a rectangle when an item is selected</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="uiDrawLinkStatusPointsCheckBox">
<property name="text">
<string>Draw link status points</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="uiLabelPreviewLabel">
<property name="text">
<string>Default label style:</string>
</property>
</widget>
</item>
<item row="8" column="0" colspan="2">
<widget class="QPlainTextEdit" name="uiDefaultLabelStylePlainTextEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>50</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="plainText">
<string>AaBbYyZz</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QSpinBox" name="uiSceneHeightSpinBox">
<property name="suffix">
@@ -760,6 +766,58 @@
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="uiLabelPreviewLabel">
<property name="text">
<string>Default label style:</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="uiDrawLinkStatusPointsCheckBox">
<property name="text">
<string>Draw link status points</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="9" column="0" colspan="2">
<widget class="QPlainTextEdit" name="uiDefaultLabelStylePlainTextEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>50</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="plainText">
<string>AaBbYyZz</string>
</property>
</widget>
</item>
<item row="11" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0" colspan="2">
<widget class="QSpinBox" name="uiSceneWidthSpinBox">
<property name="suffix">
@@ -779,19 +837,6 @@
</property>
</widget>
</item>
<item row="10" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
@@ -799,6 +844,13 @@
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QCheckBox" name="uiShowInterfaceLabelsOnNewProject">
<property name="text">
<string>Show interface labels on new project</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="uiMiscTab">
@@ -806,7 +858,16 @@
<string>Miscellaneous</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="margin">
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item>

View File

@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/general_preferences_page.ui'
# Form implementation generated from reading ui file 'C:\Users\dominik.ziajka\projects\gns3-gui\gns3\ui\general_preferences_page.ui'
#
# Created by: PyQt5 UI code generator 5.5.1
# Created by: PyQt5 UI code generator 5.8
#
# WARNING! All changes made in this file will be lost!
@@ -270,6 +270,7 @@ class Ui_GeneralPreferencesPageWidget(object):
self.uiSPICETab = QtWidgets.QWidget()
self.uiSPICETab.setObjectName("uiSPICETab")
self.gridLayout_2 = QtWidgets.QGridLayout(self.uiSPICETab)
self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
self.gridLayout_2.setObjectName("gridLayout_2")
self.uiSPICEConsoleSettingsGroupBox = QtWidgets.QGroupBox(self.uiSPICETab)
self.uiSPICEConsoleSettingsGroupBox.setObjectName("uiSPICEConsoleSettingsGroupBox")
@@ -307,6 +308,13 @@ class Ui_GeneralPreferencesPageWidget(object):
self.gridLayout_8 = QtWidgets.QGridLayout(self.uiSceneTab)
self.gridLayout_8.setContentsMargins(10, 10, 10, 10)
self.gridLayout_8.setObjectName("gridLayout_8")
self.uiRectangleSelectedItemCheckBox = QtWidgets.QCheckBox(self.uiSceneTab)
self.uiRectangleSelectedItemCheckBox.setChecked(True)
self.uiRectangleSelectedItemCheckBox.setObjectName("uiRectangleSelectedItemCheckBox")
self.gridLayout_8.addWidget(self.uiRectangleSelectedItemCheckBox, 5, 0, 1, 2)
self.uiSceneWidthLabel = QtWidgets.QLabel(self.uiSceneTab)
self.uiSceneWidthLabel.setObjectName("uiSceneWidthLabel")
self.gridLayout_8.addWidget(self.uiSceneWidthLabel, 0, 0, 1, 1)
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
self.uiDefaultLabelFontPushButton = QtWidgets.QPushButton(self.uiSceneTab)
@@ -317,24 +325,24 @@ class Ui_GeneralPreferencesPageWidget(object):
self.horizontalLayout_5.addWidget(self.uiDefaultLabelColorPushButton)
spacerItem6 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_5.addItem(spacerItem6)
self.gridLayout_8.addLayout(self.horizontalLayout_5, 9, 0, 1, 2)
self.uiSceneWidthLabel = QtWidgets.QLabel(self.uiSceneTab)
self.uiSceneWidthLabel.setObjectName("uiSceneWidthLabel")
self.gridLayout_8.addWidget(self.uiSceneWidthLabel, 0, 0, 1, 1)
self.gridLayout_8.addLayout(self.horizontalLayout_5, 10, 0, 1, 2)
self.uiSceneHeightLabel = QtWidgets.QLabel(self.uiSceneTab)
self.uiSceneHeightLabel.setObjectName("uiSceneHeightLabel")
self.gridLayout_8.addWidget(self.uiSceneHeightLabel, 2, 0, 1, 1)
self.uiRectangleSelectedItemCheckBox = QtWidgets.QCheckBox(self.uiSceneTab)
self.uiRectangleSelectedItemCheckBox.setChecked(True)
self.uiRectangleSelectedItemCheckBox.setObjectName("uiRectangleSelectedItemCheckBox")
self.gridLayout_8.addWidget(self.uiRectangleSelectedItemCheckBox, 5, 0, 1, 2)
self.uiSceneHeightSpinBox = QtWidgets.QSpinBox(self.uiSceneTab)
self.uiSceneHeightSpinBox.setMinimum(500)
self.uiSceneHeightSpinBox.setMaximum(1000000)
self.uiSceneHeightSpinBox.setSingleStep(100)
self.uiSceneHeightSpinBox.setProperty("value", 1000)
self.uiSceneHeightSpinBox.setObjectName("uiSceneHeightSpinBox")
self.gridLayout_8.addWidget(self.uiSceneHeightSpinBox, 3, 0, 1, 2)
self.uiLabelPreviewLabel = QtWidgets.QLabel(self.uiSceneTab)
self.uiLabelPreviewLabel.setObjectName("uiLabelPreviewLabel")
self.gridLayout_8.addWidget(self.uiLabelPreviewLabel, 8, 0, 1, 1)
self.uiDrawLinkStatusPointsCheckBox = QtWidgets.QCheckBox(self.uiSceneTab)
self.uiDrawLinkStatusPointsCheckBox.setChecked(True)
self.uiDrawLinkStatusPointsCheckBox.setObjectName("uiDrawLinkStatusPointsCheckBox")
self.gridLayout_8.addWidget(self.uiDrawLinkStatusPointsCheckBox, 6, 0, 1, 1)
self.uiLabelPreviewLabel = QtWidgets.QLabel(self.uiSceneTab)
self.uiLabelPreviewLabel.setObjectName("uiLabelPreviewLabel")
self.gridLayout_8.addWidget(self.uiLabelPreviewLabel, 7, 0, 1, 1)
self.uiDefaultLabelStylePlainTextEdit = QtWidgets.QPlainTextEdit(self.uiSceneTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
@@ -344,14 +352,9 @@ class Ui_GeneralPreferencesPageWidget(object):
self.uiDefaultLabelStylePlainTextEdit.setMaximumSize(QtCore.QSize(16777215, 50))
self.uiDefaultLabelStylePlainTextEdit.setReadOnly(True)
self.uiDefaultLabelStylePlainTextEdit.setObjectName("uiDefaultLabelStylePlainTextEdit")
self.gridLayout_8.addWidget(self.uiDefaultLabelStylePlainTextEdit, 8, 0, 1, 2)
self.uiSceneHeightSpinBox = QtWidgets.QSpinBox(self.uiSceneTab)
self.uiSceneHeightSpinBox.setMinimum(500)
self.uiSceneHeightSpinBox.setMaximum(1000000)
self.uiSceneHeightSpinBox.setSingleStep(100)
self.uiSceneHeightSpinBox.setProperty("value", 1000)
self.uiSceneHeightSpinBox.setObjectName("uiSceneHeightSpinBox")
self.gridLayout_8.addWidget(self.uiSceneHeightSpinBox, 3, 0, 1, 2)
self.gridLayout_8.addWidget(self.uiDefaultLabelStylePlainTextEdit, 9, 0, 1, 2)
spacerItem7 = QtWidgets.QSpacerItem(20, 5, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_8.addItem(spacerItem7, 11, 0, 1, 1)
self.uiSceneWidthSpinBox = QtWidgets.QSpinBox(self.uiSceneTab)
self.uiSceneWidthSpinBox.setMinimum(500)
self.uiSceneWidthSpinBox.setMaximum(1000000)
@@ -359,11 +362,12 @@ class Ui_GeneralPreferencesPageWidget(object):
self.uiSceneWidthSpinBox.setProperty("value", 2000)
self.uiSceneWidthSpinBox.setObjectName("uiSceneWidthSpinBox")
self.gridLayout_8.addWidget(self.uiSceneWidthSpinBox, 1, 0, 1, 2)
spacerItem7 = QtWidgets.QSpacerItem(20, 5, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_8.addItem(spacerItem7, 10, 0, 1, 1)
self.label_2 = QtWidgets.QLabel(self.uiSceneTab)
self.label_2.setObjectName("label_2")
self.gridLayout_8.addWidget(self.label_2, 4, 0, 1, 1)
self.uiShowInterfaceLabelsOnNewProject = QtWidgets.QCheckBox(self.uiSceneTab)
self.uiShowInterfaceLabelsOnNewProject.setObjectName("uiShowInterfaceLabelsOnNewProject")
self.gridLayout_8.addWidget(self.uiShowInterfaceLabelsOnNewProject, 7, 0, 1, 1)
self.uiMiscTabWidget.addTab(self.uiSceneTab, "")
self.uiMiscTab = QtWidgets.QWidget()
self.uiMiscTab.setObjectName("uiMiscTab")
@@ -491,17 +495,18 @@ class Ui_GeneralPreferencesPageWidget(object):
"</body></html>"))
self.uiSPICEConsolePreconfiguredCommandPushButton.setText(_translate("GeneralPreferencesPageWidget", "&Edit"))
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.uiSPICETab), _translate("GeneralPreferencesPageWidget", "SPICE"))
self.uiRectangleSelectedItemCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Draw a rectangle when an item is selected"))
self.uiSceneWidthLabel.setText(_translate("GeneralPreferencesPageWidget", "Default width:"))
self.uiDefaultLabelFontPushButton.setText(_translate("GeneralPreferencesPageWidget", "&Select default font"))
self.uiDefaultLabelColorPushButton.setText(_translate("GeneralPreferencesPageWidget", "&Select default color"))
self.uiSceneWidthLabel.setText(_translate("GeneralPreferencesPageWidget", "Default width:"))
self.uiSceneHeightLabel.setText(_translate("GeneralPreferencesPageWidget", "Default height:"))
self.uiRectangleSelectedItemCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Draw a rectangle when an item is selected"))
self.uiDrawLinkStatusPointsCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Draw link status points"))
self.uiLabelPreviewLabel.setText(_translate("GeneralPreferencesPageWidget", "Default label style:"))
self.uiDefaultLabelStylePlainTextEdit.setPlainText(_translate("GeneralPreferencesPageWidget", "AaBbYyZz"))
self.uiSceneHeightSpinBox.setSuffix(_translate("GeneralPreferencesPageWidget", " pixels"))
self.uiLabelPreviewLabel.setText(_translate("GeneralPreferencesPageWidget", "Default label style:"))
self.uiDrawLinkStatusPointsCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Draw link status points"))
self.uiDefaultLabelStylePlainTextEdit.setPlainText(_translate("GeneralPreferencesPageWidget", "AaBbYyZz"))
self.uiSceneWidthSpinBox.setSuffix(_translate("GeneralPreferencesPageWidget", " pixels"))
self.label_2.setText(_translate("GeneralPreferencesPageWidget", "If you want to change the size of the current project. Via the project menu you can edit it."))
self.uiShowInterfaceLabelsOnNewProject.setText(_translate("GeneralPreferencesPageWidget", "Show interface labels on new project"))
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.uiSceneTab), _translate("GeneralPreferencesPageWidget", "Topology view"))
self.uiCheckForUpdateCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Automatically check for update"))
self.uiCrashReportCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Send anonymous crash reports"))

View File

@@ -15,9 +15,16 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# __version__ is a human-readable version number.
__version__ = "2.1.2"
__version_info__ = (2, 1, 2, 0)
# __version_info__ is a four-tuple for programmatic comparison. The first
# three numbers are the components of the version number. The fourth
# is zero for an official release, positive for a development branch,
# or negative for a release candidate or beta (after the base version
# number has been incremented)
__version__ = "2.1.4"
__version_info__ = (2, 1, 4, 0)
# If it's a git checkout try to add the commit
if "dev" in __version__:

View File

@@ -55,5 +55,5 @@ def vncConsole(host, port, command):
args = shlex.split(command)
subprocess.Popen(args, env=os.environ)
except (OSError, ValueError, subprocess.SubprocessError) as e:
log.warning('could not start VNC program "{}": {}'.format(command, e))
log.error("Could not start VNC program with command '{}': {}".format(command, e))
raise

View File

@@ -22,6 +22,9 @@ if sys.platform.startswith('win'):
PATH = os.path.join(PATH, "bin")
PYUIC = os.path.join(PATH, "pyuic5")
PYRCC = os.path.join(PATH, "pyrcc5")
if not os.path.exists(PYUIC):
PYUIC = "pyuic5.exe"
else:
PYUIC = shutil.which("pyuic5")
PYRCC = shutil.which("pyrcc5")

View File

@@ -306,7 +306,7 @@ def test_callbackConnect_major_version_invalid(http_client):
http_client._query_waiting_connections.append((None, mock))
http_client._callbackConnect(params)
assert http_client._connected is False
mock.assert_called_with({"message": "Client version {} differs with server version 1.2.3".format(__version__)}, error=True, server=None)
mock.assert_called_with({"message": "Client version {} is not the same as server version 1.2.3".format(__version__)}, error=True, server=None)
def test_callbackConnect_minor_version_invalid(http_client):
@@ -323,7 +323,7 @@ def test_callbackConnect_minor_version_invalid(http_client):
if __version_info__[3] == 0:
http_client._callbackConnect(params)
assert http_client._connected is False
mock.assert_called_with({"message": "Client version {} differs with server version {}".format(__version__, new_version)}, error=True, server=None)
mock.assert_called_with({"message": "Client version {} is not the same as server version {}".format(__version__, new_version)}, error=True, server=None)
else:
http_client._callbackConnect(params)
assert http_client._connected is True

43
tests/test_main_window.py Normal file
View File

@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from unittest.mock import MagicMock
@pytest.fixture
def real_main_window():
from gns3.main_window import MainWindow
return MainWindow()
def test_main_window_settings_changed_signal(real_main_window):
settings = real_main_window.settings()
signal = MagicMock()
real_main_window.settings_updated_signal = signal
real_main_window.setSettings(settings)
assert signal.emit.called
def test_main_window_settings_changed_slot(real_main_window):
instance = MagicMock()
manager = MagicMock()
manager.instance.return_value = instance
real_main_window._appliance_manager = manager
real_main_window.settingsChangedSlot()
assert instance.refresh.called

View File

@@ -141,3 +141,39 @@ def test_updatePorts_PortChange(vpcs_device):
])
assert port == vpcs_device._ports[0]
assert port.status() == Port.started
def test_node_setGraphics(vpcs_device):
node = MagicMock(
pos=MagicMock(
return_value=MagicMock(
x=MagicMock(
return_value=10
),
y=MagicMock(
return_value=20
)
)
),
zValue=MagicMock(
return_value=2
),
symbol=MagicMock(
return_value="symbol.svg"
)
)
with patch('gns3.base_node.BaseNode.controllerHttpPut') as mock:
vpcs_device.setGraphics(node)
assert mock.call_count == 1
args, kwargs = mock.call_args
assert args[0] == "/nodes/{node_id}".format(node_id=vpcs_device.node_id())
# second call should not make an update
vpcs_device.setSettingValue('x', 10)
vpcs_device.setSettingValue('y', 20)
vpcs_device.setSettingValue('z', 2)
vpcs_device.setSettingValue('symbol', "symbol.svg")
vpcs_device.setSettingValue('label', node.label().dump())
vpcs_device.setGraphics(node)
assert mock.call_count == 1

View File

@@ -41,7 +41,8 @@ def test_project_create(tmpdir, controller):
assert args[0] == "POST"
assert args[1] == "/projects"
assert kwargs["body"] == {"name": "test",
"path": str(tmpdir)}
"path": str(tmpdir),
"show_interface_labels": False}
args[2]({"project_id": uuid, "name": "test"})