Compare commits

...

85 Commits

Author SHA1 Message Date
ziajka
2744e669b4 Release v2.1.7 2018-06-12 11:12:49 +02:00
grossmj
6ab2d63bdc Do not try to update link if it is being deleted. Fixes #2483. 2018-06-06 21:00:08 +07:00
grossmj
0de6bfe7e1 Fix can't add SVG image to project. Fixes #2502 2018-06-06 18:26:37 +07:00
grossmj
f144103bca Remove unwanted trailing characters and other white spaces when reading .md5sum files. Fixes #2498. 2018-06-04 23:59:53 +07:00
grossmj
c0b26aff48 Update interface sequence number check. Fixes #2491. 2018-06-04 22:31:19 +07:00
ziajka
9601e4e6f2 Logo should not have context menu, Fixes: #2507 2018-05-24 13:21:13 +02:00
ziajka
88708c2a8d Update logo position only when changes, Fixes: #2506 2018-05-24 13:15:32 +02:00
ziajka
8eff12194d Development on v2.1.7dev1 2018-05-22 14:14:46 +02:00
ziajka
b0520b2bd4 Release v2.1.6 2018-05-22 14:11:59 +02:00
ziajka
17d2c023bf Fix redraw logo on Windows 2018-05-22 13:16:48 +02:00
ziajka
ce9fdea0a0 Merge pull request #2492 from GNS3/extra-hosts
Extra hosts for Docker, global variables for project and supplier logo support, Fixes: #2482
2018-05-15 09:23:42 +02:00
ziajka
24d7dacb4e Variables fix on ProjectWelcomeDialog 2018-05-10 10:46:57 +02:00
ziajka
bb36765407 Remove project_created_signal 2018-05-09 15:24:41 +02:00
ziajka
250db92ce0 Ask for global variables when project is loaded 2018-05-09 11:54:13 +02:00
ziajka
d59ec39505 Add/Edit global variables of project 2018-05-08 18:31:26 +02:00
ziajka
5e9ae04dc1 Rename tabs at Edit Project 2018-05-08 17:05:25 +02:00
ziajka
ddb0fccda3 Global variables tab on Edit project 2018-05-08 17:03:04 +02:00
ziajka
9b22a52f14 Support of supplier logo and url 2018-05-08 16:22:01 +02:00
grossmj
948878bfdd Add missing crowdfunder name in About dialog. 2018-05-08 21:52:37 +08:00
ziajka
7340abbaa9 Project variables and supplier 2018-05-08 13:00:32 +02:00
grossmj
4ea0528bf2 No timeout when duplicating a project. 2018-04-28 17:09:08 +07:00
grossmj
49005e6add No timeout when restoring snapshot. 2018-04-28 16:41:54 +07:00
ziajka
5484c039b5 Fix tests 2018-04-27 14:47:09 +02:00
ziajka
daaf71b6d2 Add advanced settings for docker and param, Ref. #2482 2018-04-27 14:28:14 +02:00
grossmj
450f0e006b Merge remote-tracking branch 'origin/2.1' into 2.1 2018-04-23 15:40:18 +07:00
grossmj
a6a967fbde Replace "not supported" by "none" in topology summary view. 2018-04-23 15:39:58 +07:00
ziajka
1a6293709e Development on v2.1.6 2018-04-18 11:41:43 +02:00
ziajka
2ed53225e0 Release v2.1.5 2018-04-18 11:28:52 +02:00
grossmj
b8798fbda5 Disable TraceNG for version 2.1.5 2018-04-18 17:19:44 +08:00
grossmj
368de32faa Fix Qemu binary list locks when a version is deleted. Fixes #2474. 2018-04-18 15:44:33 +08:00
grossmj
98d01cbfa0 Fix invalid answer from the PyPi server. Fixes #2473. 2018-04-18 15:10:31 +08:00
grossmj
ad62bb7832 Fix wrong wizard page name. 2018-04-16 17:16:20 +08:00
grossmj
637061663a Add default destination setting for traceng + some checks. Ref #2450. 2018-04-16 15:03:02 +08:00
grossmj
c137198985 Grid size support for projects. Fixes #2469. 2018-04-13 16:56:37 +08:00
grossmj
946efb61de Remove 'include INSTALL' from MANIFEST. Fixes #2470. 2018-04-13 14:17:03 +08:00
grossmj
4c610acfa4 Fix traceng tests. 2018-03-30 12:10:57 +07:00
grossmj
37f74824f1 Merge branch 'traceng' into 2.1 2018-03-29 15:19:29 +07:00
grossmj
5ccf8c414d Sync 2018-03-29 15:19:18 +07:00
grossmj
913f0d5e4a Check for valid IP address and prevent to run on non-Windows platforms. 2018-03-29 13:26:43 +07:00
grossmj
061bac0cc6 Support for source and destination for traceNG. 2018-03-27 16:58:49 +07:00
ziajka
ec59cd87bd Back to development on v2.1.5dev1 2018-03-15 08:46:06 +01:00
ziajka
05d9ee8499 Re-release v2.1.4 due to travis issue 2018-03-14 15:28:15 +01:00
grossmj
a72ece5c18 Custom icons and small fixes for TraceNG integration. 2018-03-14 16:56:39 +07:00
grossmj
63baa2eff0 Base support for TraceNG. 2018-03-12 17:57:13 +07: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
96 changed files with 87019 additions and 84345 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,62 @@
# Change Log
## 2.1.7 12/06/2018
* Do not try to update link if it is being deleted. Fixes #2483.
* Fix can't add SVG image to project. Fixes #2502
* Remove unwanted trailing characters and other white spaces when reading .md5sum files. Fixes #2498.
* Update interface sequence number check. Fixes #2491.
* Logo should not have context menu, Fixes: #2507
* Update logo position only when changes, Fixes: #2506
## 2.1.6 22/05/2018
* Ask for global variables when project is loaded
* Add/Edit global variables of project
* Rename tabs at Edit Project
* Global variables tab on Edit project
* Support of supplier logo and url
* Add missing crowdfunder name in About dialog.
* Project variables and supplier
* No timeout when duplicating a project.
* No timeout when restoring snapshot.
* Add advanced settings for docker and ExtraHosts param, Ref. #2482
* Replace "not supported" by "none" in topology summary view.
## 2.1.5 18/04/2018
* Fix Qemu binary list locks when a version is deleted. Fixes #2474.
* Fix invalid answer from the PyPi server. Fixes #2473.
* Fix wrong wizard page name.
* Grid size support for projects. Fixes #2469.
* Remove 'include INSTALL' from MANIFEST. Fixes #2470.
* Check for valid IP address and prevent to run on non-Windows platforms.
## 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

@@ -1,6 +1,5 @@
include README.rst
include AUTHORS
include INSTALL
include LICENSE
include MANIFEST.in
include requirements.txt

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://806956d411fc4cdf8cda8f27b08079a8:a1d1a6aaa57f4c46a7db02d89f5fab95@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

@@ -15,7 +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/>.
from ..qt import QtWidgets
from ..qt import QtWidgets, QtCore, qslot, qpartial
from ..topology import Topology
from ..ui.edit_project_dialog_ui import Ui_EditProjectDialog
@@ -36,6 +36,68 @@ class EditProjectDialog(QtWidgets.QDialog, Ui_EditProjectDialog):
self.uiProjectAutoStartCheckBox.setChecked(self._project.autoStart())
self.uiSceneWidthSpinBox.setValue(self._project.sceneWidth())
self.uiSceneHeightSpinBox.setValue(self._project.sceneHeight())
self.uiGridSizeSpinBox.setValue(self._project.gridSize())
self.uiGlobalVariablesGrid.setAlignment(QtCore.Qt.AlignTop)
self.uiNewVarButton = QtWidgets.QPushButton('Add new variable', self)
self.uiNewVarButton.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
self.uiNewVarButton.clicked.connect(self.onAddNewVariable)
self.uiGlobalVariablesGrid.addWidget(self.uiNewVarButton, 0, 3, QtCore.Qt.AlignRight)
self._variables = self.setUpVariables()
self.updateGlobalVariables()
def setUpVariables(self):
new_variable = {"name": "", "value": ""}
variables = self._project.variables()
if variables is not None:
variables.append(new_variable)
else:
variables = [new_variable]
return variables
def updateGlobalVariables(self):
while True:
item = self.uiGlobalVariablesGrid.takeAt(1)
if item is None:
break
elif item.widget():
item.widget().deleteLater()
for i, variable in enumerate(self._variables, start=1):
nameLabel = QtWidgets.QLabel()
nameLabel.setText("Name:")
self.uiGlobalVariablesGrid.addWidget(nameLabel, i, 0)
nameEdit = QtWidgets.QLineEdit()
nameEdit.setText(variable.get("name", ""))
nameEdit.textChanged.connect(qpartial(self.onNameChange, variable))
self.uiGlobalVariablesGrid.addWidget(nameEdit, i, 1)
valueLabel = QtWidgets.QLabel()
valueLabel.setText("Value:")
self.uiGlobalVariablesGrid.addWidget(valueLabel, i, 2)
valueEdit = QtWidgets.QLineEdit()
valueEdit.setText(variable.get("value", ""))
valueEdit.textChanged.connect(qpartial(self.onValueChange, variable))
self.uiGlobalVariablesGrid.addWidget(valueEdit, i, 3)
@qslot
def onAddNewVariable(self, event):
self._variables += [{"name": "", "value": ""}]
self.updateGlobalVariables()
def onNameChange(self, variable, text):
variable["name"] = text
def onValueChange(self, variable, text):
variable["value"] = text
def _cleanVariables(self):
return [v for v in self._variables if v.get("name", "").strip() != ""]
def done(self, result):
"""
@@ -51,5 +113,7 @@ class EditProjectDialog(QtWidgets.QDialog, Ui_EditProjectDialog):
self._project.setAutoStart(self.uiProjectAutoStartCheckBox.isChecked())
self._project.setSceneHeight(self.uiSceneHeightSpinBox.value())
self._project.setSceneWidth(self.uiSceneWidthSpinBox.value())
self._project.setGridSize(self.uiGridSizeSpinBox.value())
self._project.setVariables(self._cleanVariables())
self._project.update()
super().done(result)

View File

@@ -106,6 +106,7 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
Controller.instance().deleteProject(project_id)
def _duplicateProjectSlot(self):
if len(self.uiProjectsTreeWidget.selectedItems()) == 0:
QtWidgets.QMessageBox.critical(self, "Duplicate project", "No project selected")
return
@@ -135,12 +136,16 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
if Controller.instance().isRemote():
Controller.instance().post("/projects/{project_id}/duplicate".format(project_id=project_id),
self._duplicateCallback,
body={"name": name})
body={"name": name},
progressText="Duplicating project '{}'...".format(name),
timeout=None)
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})
body={"name": name, "path": project_location},
progressText="Duplicating project '{}'...".format(name),
timeout=None)
def _duplicateCallback(self, result, error=False, **kwargs):
if error:

View File

@@ -0,0 +1,88 @@
# -*- 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 copy
from gns3.qt import QtWidgets, QtCore, qpartial
from gns3.ui.project_welcome_dialog_ui import Ui_ProjectWelcomeDialog
import logging
log = logging.getLogger(__name__)
class ProjectWelcomeDialog(QtWidgets.QDialog, Ui_ProjectWelcomeDialog):
"""
This dialog shows when project is imported and global variables assigned to the project are missing.
"""
def __init__(self, parent, project):
super().__init__(parent)
self._project = project
self.setupUi(self)
self.uiOkButton.clicked.connect(self._okButtonClickedSlot)
self.gridLayout.setAlignment(QtCore.Qt.AlignTop)
self.label.setOpenExternalLinks(True)
self._variables = self._getVariables(project)
self._loadReadme()
self._addMisingVariablesEdits()
def _getVariables(self, project):
variables = copy.copy(self._project.variables())
if variables is None:
variables = []
return variables
def _addMisingVariablesEdits(self):
missing = [v for v in self._variables if v.get("value", "").strip() == ""]
for i, variable in enumerate(missing, start=0):
nameLabel = QtWidgets.QLabel()
nameLabel.setText(variable.get("name", ""))
self.gridLayout.addWidget(nameLabel, i, 0)
valueEdit = QtWidgets.QLineEdit()
valueEdit.setText(variable.get("value", ""))
valueEdit.textChanged.connect(qpartial(self.onValueChange, variable))
self.gridLayout.addWidget(valueEdit, i, 1)
def _loadReadme(self):
self._project.get("/files/README.txt", self._loadedReadme)
def _loadedReadme(self, result, error=False, raw_body=None, context={}, **kwargs):
if not error:
self.label.setText(raw_body.decode("utf-8"))
def onValueChange(self, variable, text):
variable["value"] = text
def _okButtonClickedSlot(self):
missing = [v for v in self._variables if v.get("value", "").strip() == ""]
if len(missing) > 0:
reply = QtWidgets.QMessageBox.warning(
self, 'Missing values',
'Are you sure you want to continue without providing missing values?',
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
return
self._project.setVariables(self._variables)
self._project.update()
self.accept()

View File

@@ -22,8 +22,6 @@ Dialog to manage the snapshots.
from ..qt import QtCore, QtWidgets
from ..ui.snapshots_dialog_ui import Ui_SnapshotsDialog
from ..controller import Controller
from ..utils.progress_dialog import ProgressDialog
from ..utils.create_snapshot_worker import CreateSnapshotWorker
from datetime import datetime
@@ -87,21 +85,21 @@ 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 and self._project:
snapshot_worker = CreateSnapshotWorker(self._project, snapshot_name)
snapshot_worker.finished.connect(self._createSnapshotsCallback)
Controller.instance().post("/projects/{}/snapshots".format(self._project.id()),
self._createSnapshotsCallback,
{"name": snapshot_name},
progressText="Creation of snapshot '{}' in progress...".format(snapshot_name),
timeout=None)
progress_dialog = ProgressDialog(snapshot_worker, "Snapshot progress", "Creation of snapshot in progress...",
"Cancel", busy=True, parent=self, create_thread=False, cancelable=True)
progress_dialog.show()
progress_dialog.exec_()
def _createSnapshotsCallback(self):
def _createSnapshotsCallback(self, result, error=False, server=None, context={}, **kwargs):
if error:
if result:
log.error(result["message"])
else:
log.error("Cannot create snapshot of project")
return
self._listSnapshots()
def _createSnapshotsErrorCallback(self, message, error):
log.error(message)
def _deleteSnapshotSlot(self):
"""
Slot to delete a snapshot.
@@ -113,6 +111,7 @@ class SnapshotsDialog(QtWidgets.QDialog, Ui_SnapshotsDialog):
Controller.instance().delete("/projects/{}/snapshots/{}".format(self._project.id(), snapshot_id), self._deleteSnapshotsCallback)
def _deleteSnapshotsCallback(self, result, error=False, server=None, context={}, **kwargs):
if error:
if result:
log.error(result["message"])
@@ -135,13 +134,16 @@ class SnapshotsDialog(QtWidgets.QDialog, Ui_SnapshotsDialog):
:param snapshot_id: id of the snapshot
"""
reply = QtWidgets.QMessageBox.question(self, "Snapshots", "This will discard any changes made to your project since the snapshot was taken?", QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Cancel)
reply = QtWidgets.QMessageBox.question(self, "Snapshots", "This will discard any changes made to your project since the snapshot was taken, would you like to proceed?", QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Cancel)
if reply == QtWidgets.QMessageBox.Cancel:
return
Controller.instance().post("/projects/{}/snapshots/{}/restore".format(self._project.id(), snapshot_id), self._restoreSnapshotsCallback, timeout=300)
Controller.instance().post("/projects/{}/snapshots/{}/restore".format(self._project.id(), snapshot_id),
self._restoreSnapshotsCallback, progressText="Restoring snapshot...", timeout=None)
def _restoreSnapshotsCallback(self, result, error=False, server=None, context={}, **kwargs):
if error:
if result:
log.error(result["message"])

View File

@@ -60,6 +60,7 @@ from .items.rectangle_item import RectangleItem
from .items.line_item import LineItem
from .items.ellipse_item import EllipseItem
from .items.image_item import ImageItem
from .items.logo_item import LogoItem
log = logging.getLogger(__name__)
@@ -88,6 +89,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
self._adding_line = False
self._newlink = None
self._dragging = False
self._grid_size = 75
self._last_mouse_position = None
self._topology = Topology.instance()
self._background_warning_msgbox = QtWidgets.QErrorMessage(self)
@@ -127,6 +129,24 @@ class GraphicsView(QtWidgets.QGraphicsView):
factor = zoom / 100.
self.scale(factor, factor)
def setGridSize(self, grid_size):
"""
Sets the grid size.
:param grid_size: integer
"""
self._grid_size = grid_size
def gridSize(self):
"""
Returns the grid size
:returns: integer
"""
return self._grid_size
def setEnabled(self, enabled):
if enabled is False:
@@ -275,6 +295,10 @@ class GraphicsView(QtWidgets.QGraphicsView):
self.scene().addItem(image_item)
self._topology.addDrawing(image_item)
def addLogo(self, logo_path, logo_url):
logo_item = LogoItem(logo_path, logo_url, self._topology.project())
self.scene().addItem(logo_item)
def addLink(self, source_node, source_port, destination_node, destination_port, **link_data):
"""
Creates a Link instance representing a connection between 2 devices.
@@ -395,12 +419,16 @@ class GraphicsView(QtWidgets.QGraphicsView):
"""
is_not_link = True
is_not_logo = True
item = self.itemAt(event.pos())
if item and sip.isdeleted(item):
return
if item and (isinstance(item, LinkItem) or isinstance(item.parentItem(), LinkItem)):
is_not_link = False
if item and (isinstance(item, LogoItem) or isinstance(item.parentItem(), LogoItem)):
is_not_logo = False
else:
for it in self.scene().items():
if isinstance(it, LinkItem):
@@ -420,7 +448,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
item.setSelected(False)
else:
item.setSelected(True)
elif is_not_link and event.button() == QtCore.Qt.RightButton and not self._adding_link:
elif is_not_link and is_not_logo and event.button() == QtCore.Qt.RightButton and not self._adding_link:
if item and not sip.isdeleted(item):
# Prevent right clicking on a selected item from de-selecting all other items
if not item.isSelected():
@@ -1547,21 +1575,21 @@ class GraphicsView(QtWidgets.QGraphicsView):
def drawBackground(self, painter, rect):
super().drawBackground(painter, rect)
if self._main_window.uiShowGridAction.isChecked():
gridSize = 75
grid_size = self.gridSize()
painter.save()
painter.setPen(QtGui.QPen(QtGui.QColor(190, 190, 190)))
left = int(rect.left()) - (int(rect.left()) % gridSize)
top = int(rect.top()) - (int(rect.top()) % gridSize)
left = int(rect.left()) - (int(rect.left()) % grid_size)
top = int(rect.top()) - (int(rect.top()) % grid_size)
x = left
while x < rect.right():
painter.drawLine(x, rect.top(), x, rect.bottom())
x += gridSize
x += grid_size
y = top
while y < rect.bottom():
painter.drawLine(rect.left(), y, rect.right(), y)
y += gridSize
y += grid_size
painter.restore()
def toggleUiDeviceMenu(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

@@ -188,11 +188,11 @@ class DrawingItem:
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemPositionHasChanged and self.isActive() and self._main_window.uiSnapToGridAction.isChecked():
GRID_SIZE = 75
grid_size = self._graphics_view.gridSize()
mid_x = self.boundingRect().width() / 2
tmp_x = (GRID_SIZE * round((self.x() + mid_x) / GRID_SIZE)) - mid_x
tmp_x = (grid_size * round((self.x() + mid_x) / grid_size)) - mid_x
mid_y = self.boundingRect().height() / 2
tmp_y = (GRID_SIZE * round((self.y() + mid_y) / GRID_SIZE)) - mid_y
tmp_y = (grid_size * round((self.y() + mid_y) / grid_size)) - mid_y
if tmp_x != self.x() and tmp_y != self.y():
self.setPos(tmp_x, tmp_y)

136
gns3/items/logo_item.py Normal file
View File

@@ -0,0 +1,136 @@
# -*- 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 urllib.parse
from ..qt import QtCore, QtGui, QtWidgets, QtSvg
from ..qt.qimage_svg_renderer import QImageSvgRenderer
from ..controller import Controller
import logging
log = logging.getLogger(__name__)
class LogoItem(QtSvg.QGraphicsSvgItem):
"""
Margin for the logo
"""
MARGIN = 20
"""
Logo for the scene.
:param logo_path: Path to the logo (remote)
:param logo_url: URL which needs to be open user clicks on the logo
:param project: Current project
"""
def __init__(self, logo_path, logo_url, project):
super().__init__()
self._logo_path = logo_path
self._logo_url = logo_url
self._project = project
# Temporary symbol during loading
renderer = QImageSvgRenderer(":/icons/reload.svg")
renderer.setObjectName("symbol_loading")
self.setSharedRenderer(renderer)
effect = QtWidgets.QGraphicsColorizeEffect()
effect.setColor(QtGui.QColor("black"))
effect.setStrength(0.8)
self.setGraphicsEffect(effect)
self.graphicsEffect().setEnabled(False)
# set graphical settings for this item
self.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable)
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges)
self.setAcceptHoverEvents(True)
from ..main_window import MainWindow
self._main_window = MainWindow.instance()
self._settings = self._main_window.uiGraphicsView.settings()
self.updatePosition()
self._main_window.uiGraphicsView.viewport().installEventFilter(self)
remote_file = urllib.parse.quote('project-files/images/{}'.format(logo_path))
Controller.instance().getStatic(
'/projects/{}/files/{}'.format(project.id(), remote_file),
self.updateImage
)
# make it the last one
self.setZValue(-2)
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.Paint:
self.updatePosition()
return QtWidgets.QWidget.eventFilter(self, source, event)
def updateImage(self, local_path):
renderer = QImageSvgRenderer(local_path)
renderer.setObjectName("project_logo")
self.setSharedRenderer(renderer)
def updatePosition(self):
"""
Updates position to be located in the right bottom corner
"""
logo_rect = self.boundingRect()
width = self._main_window.uiGraphicsView.viewport().width()
height = self._main_window.uiGraphicsView.viewport().height()
rect = self._main_window.uiGraphicsView.mapToScene(QtCore.QRect(0, 0, width, height)).boundingRect()
x = rect.x() + rect.width() - self.MARGIN - logo_rect.width()
y = rect.y() + rect.height() - self.MARGIN - logo_rect.height()
# update only when changes
if [int(self.x()), int(self.y())] != [int(x), int(y)]:
self.setX(x)
self.setY(y)
self.update()
def hoverEnterEvent(self, event):
"""
Handles all hover enter events for this item.
:param event: QGraphicsSceneHoverEvent instance
"""
if self._logo_url is not None:
self.graphicsEffect().setEnabled(True)
def hoverLeaveEvent(self, event):
"""
Handles all hover leave events for this item.
:param event: QGraphicsSceneHoverEvent instance
"""
self.graphicsEffect().setEnabled(False)
def mousePressEvent(self, event):
url = QtCore.QUrl(self._logo_url)
if not QtGui.QDesktopServices.openUrl(url):
QtWidgets.QMessageBox.warning(self, 'Open Url', 'Could not open url')

View File

@@ -41,7 +41,6 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
"""
show_layer = False
GRID_SIZE = 75
def __init__(self, node):
super().__init__()
@@ -108,18 +107,19 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
self.createdSlot(node.id())
def _snapToGrid(self):
grid_size = self._main_window.uiGraphicsView.gridSize()
mid_x = self.boundingRect().width() / 2
x = (self.GRID_SIZE * round((self.x() + mid_x) / self.GRID_SIZE)) - mid_x
x = (grid_size * round((self.x() + mid_x) / grid_size)) - mid_x
mid_y = self.boundingRect().height() / 2
y = (self.GRID_SIZE * round((self.y() + mid_y) / self.GRID_SIZE)) - mid_y
y = (grid_size * round((self.y() + mid_y) / grid_size)) - mid_y
self.setPos(x, y)
def updateNode(self):
"""
Sync change to the node
"""
if self._initialized:
self._node.setGraphics(self)
self._node.setGraphics(self)
@qslot
def setSymbol(self, symbol):
@@ -458,10 +458,11 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
"""
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.isActive() and self._main_window.uiSnapToGridAction.isChecked():
grid_size = self._main_window.uiGraphicsView.gridSize()
mid_x = self.boundingRect().width() / 2
value.setX((self.GRID_SIZE * round((value.x() + mid_x) / self.GRID_SIZE)) - mid_x)
value.setX((grid_size * round((value.x() + mid_x) / grid_size)) - mid_x)
mid_y = self.boundingRect().height() / 2
value.setY((self.GRID_SIZE * round((value.y() + mid_y) / self.GRID_SIZE)) - mid_y)
value.setY((grid_size * round((value.y() + mid_y) / grid_size)) - mid_y)
# dynamically change the renderer when this node item is selected/unselected.
if change == QtWidgets.QGraphicsItem.ItemSelectedChange:

View File

@@ -76,6 +76,7 @@ class Link(QtCore.QObject):
self._destination_label = None
self._link_id = link_id
self._capturing = False
self._deleting = False
self._capture_file_path = None
self._capture_file = None
self._initialized = False
@@ -158,7 +159,7 @@ class Link(QtCore.QObject):
self._updateLabels()
def update(self):
if not self._link_id:
if not self._link_id or self.deleting():
return
body = self._prepareParams()
Controller.instance().put("/projects/{project_id}/links/{link_id}".format(project_id=self._source_node.project().id(), link_id=self._link_id), self.updateLinkCallback, body=body)
@@ -244,6 +245,19 @@ class Link(QtCore.QObject):
def link_id(self):
return self._link_id
def deleting(self):
"""
Is the link being deleted
"""
return self._deleting
def setDeleting(self):
"""
Mark this link as being deleted
"""
self._deleting = True
def capturing(self):
"""
Is a capture running on the link?
@@ -306,8 +320,10 @@ class Link(QtCore.QObject):
if skip_controller:
self._linkDeletedCallback({})
else:
self.setDeleting()
Controller.instance().delete("/projects/{project_id}/links/{link_id}".format(project_id=self.project().id(),
link_id=self._link_id), self._linkDeletedCallback)
link_id=self._link_id),
self._linkDeletedCallback)
def _linkDeletedCallback(self, result, error=False, **kwargs):
"""

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):
"""
@@ -317,7 +319,7 @@ class LocalConfig(QtCore.QObject):
"""
if Controller.instance().connected() and self._settings_retrieved_from_controller:
# We save only non user specific sections
section_to_save_on_controller = ["Builtin", "Docker", "IOU", "Qemu", "VMware", "VPCS", "VirtualBox", "GraphicsView", "Dynamips"]
section_to_save_on_controller = ["Builtin", "Docker", "IOU", "Qemu", "VMware", "VPCS", "TraceNG", "VirtualBox", "GraphicsView", "Dynamips"]
controller_settings = {}
for key, val in self._settings.items():
if key in section_to_save_on_controller:
@@ -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

@@ -145,7 +145,8 @@ def main():
frozen_dirs = [
frozen_dir,
os.path.normpath(os.path.join(frozen_dir, 'dynamips')),
os.path.normpath(os.path.join(frozen_dir, 'vpcs'))
os.path.normpath(os.path.join(frozen_dir, 'vpcs')),
os.path.normpath(os.path.join(frozen_dir, 'traceng'))
]
os.environ["PATH"] = os.pathsep.join(frozen_dirs) + os.pathsep + os.environ.get("PATH", "")

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

@@ -19,9 +19,11 @@ from gns3.modules.builtin import Builtin
from gns3.modules.dynamips import Dynamips
from gns3.modules.iou import IOU
from gns3.modules.vpcs import VPCS
from gns3.modules.traceng import TraceNG
from gns3.modules.virtualbox import VirtualBox
from gns3.modules.qemu import Qemu
from gns3.modules.vmware import VMware
from gns3.modules.docker import Docker
#FIXME: removed TraceNG
MODULES = [Builtin, VPCS, Dynamips, IOU, Qemu, VirtualBox, VMware, Docker]

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

@@ -135,6 +135,6 @@ class DockerVMWizard(VMWizard, Ui_DockerVMWizard):
"name": name,
"environment": self.uiEnvironmentTextEdit.toPlainText(),
"start_command": start_command,
"console_type": self.uiConsoleTypeComboBox.currentText()
"console_type": self.uiConsoleTypeComboBox.currentText(),
}
return settings

View File

@@ -49,7 +49,8 @@ class DockerVM(Node):
"console_type": DOCKER_CONTAINER_SETTINGS["console_type"],
"console_resolution": DOCKER_CONTAINER_SETTINGS["console_resolution"],
"console_http_port": DOCKER_CONTAINER_SETTINGS["console_http_port"],
"console_http_path": DOCKER_CONTAINER_SETTINGS["console_http_path"]}
"console_http_path": DOCKER_CONTAINER_SETTINGS["console_http_path"],
"extra_hosts": DOCKER_CONTAINER_SETTINGS["extra_hosts"]}
self.settings().update(docker_vm_settings)

View File

@@ -67,6 +67,7 @@ class DockerVMConfigurationPage(QtWidgets.QWidget, Ui_dockerVMConfigPageWidget):
self.uiConsoleResolutionComboBox.setCurrentIndex(self.uiConsoleResolutionComboBox.findText(settings["console_resolution"]))
self.uiConsoleHttpPortSpinBox.setValue(settings["console_http_port"])
self.uiHttpConsolePathLineEdit.setText(settings["console_http_path"])
self.uiExtraHostsTextEdit.setText(settings["extra_hosts"])
if not group:
self.uiNameLineEdit.setText(settings["name"])
@@ -128,6 +129,7 @@ class DockerVMConfigurationPage(QtWidgets.QWidget, Ui_dockerVMConfigPageWidget):
settings["console_resolution"] = self.uiConsoleResolutionComboBox.currentText()
settings["console_http_port"] = self.uiConsoleHttpPortSpinBox.value()
settings["console_http_path"] = self.uiHttpConsolePathLineEdit.text()
settings["extra_hosts"] = self.uiExtraHostsTextEdit.toPlainText()
if not group:
adapters = self.uiAdapterSpinBox.value()

View File

@@ -61,7 +61,6 @@ class DockerVMPreferencesPage(QtWidgets.QWidget, Ui_DockerVMPreferencesPageWidge
return section_item
def _refreshInfo(self, docker_image):
self.uiDockerVMInfoTreeWidget.clear()
# fill out the General section
@@ -79,6 +78,9 @@ class DockerVMPreferencesPage(QtWidgets.QWidget, Ui_DockerVMPreferencesPageWidge
if docker_image["environment"]:
QtWidgets.QTreeWidgetItem(section_item, ["Environment:", str(docker_image["environment"])])
if docker_image["extra_hosts"]:
QtWidgets.QTreeWidgetItem(section_item, ["Extra hosts:", str(docker_image["extra_hosts"])])
self.uiDockerVMInfoTreeWidget.expandAll()
self.uiDockerVMInfoTreeWidget.resizeColumnToContents(0)
self.uiDockerVMInfoTreeWidget.resizeColumnToContents(1)

View File

@@ -38,5 +38,6 @@ DOCKER_CONTAINER_SETTINGS = {
"console_type": "telnet",
"console_resolution": "1024x768",
"console_http_port": 80,
"console_http_path": "/"
"console_http_path": "/",
"extra_hosts": ""
}

View File

@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>613</width>
<height>519</height>
<height>524</height>
</rect>
</property>
<property name="windowTitle">
@@ -24,7 +24,16 @@
<string>General settings</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<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 row="0" column="1">
@@ -263,6 +272,45 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="advancedTab">
<attribute name="title">
<string>Advanced</string>
</attribute>
<widget class="QLabel" name="uiExtraHostsLabel">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>152</width>
<height>82</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Extra hosts added to
/etc/hosts file.
(hostname:IP, one per line)</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
<widget class="QTextEdit" name="uiExtraHostsTextEdit">
<property name="geometry">
<rect>
<x>168</x>
<y>10</y>
<width>413</width>
<height>82</height>
</rect>
</property>
</widget>
</widget>
</widget>
</item>
<item>

View File

@@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/docker/ui/docker_vm_configuration_page.ui'
# Form implementation generated from reading ui file '/home/dominik/projects/gns3-gui/gns3/modules/docker/ui/docker_vm_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.8.2
#
# WARNING! All changes made in this file will be lost!
@@ -12,7 +11,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_dockerVMConfigPageWidget(object):
def setupUi(self, dockerVMConfigPageWidget):
dockerVMConfigPageWidget.setObjectName("dockerVMConfigPageWidget")
dockerVMConfigPageWidget.resize(613, 519)
dockerVMConfigPageWidget.resize(613, 524)
self.verticalLayout = QtWidgets.QVBoxLayout(dockerVMConfigPageWidget)
self.verticalLayout.setObjectName("verticalLayout")
self.uiTabWidget = QtWidgets.QTabWidget(dockerVMConfigPageWidget)
@@ -122,6 +121,21 @@ class Ui_dockerVMConfigPageWidget(object):
self.uiHttpConsolePathLineEdit.setObjectName("uiHttpConsolePathLineEdit")
self.gridLayout.addWidget(self.uiHttpConsolePathLineEdit, 9, 1, 1, 1)
self.uiTabWidget.addTab(self.tab, "")
self.advancedTab = QtWidgets.QWidget()
self.advancedTab.setObjectName("advancedTab")
self.uiExtraHostsLabel = QtWidgets.QLabel(self.advancedTab)
self.uiExtraHostsLabel.setGeometry(QtCore.QRect(10, 10, 152, 82))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiExtraHostsLabel.sizePolicy().hasHeightForWidth())
self.uiExtraHostsLabel.setSizePolicy(sizePolicy)
self.uiExtraHostsLabel.setWordWrap(True)
self.uiExtraHostsLabel.setObjectName("uiExtraHostsLabel")
self.uiExtraHostsTextEdit = QtWidgets.QTextEdit(self.advancedTab)
self.uiExtraHostsTextEdit.setGeometry(QtCore.QRect(168, 10, 413, 82))
self.uiExtraHostsTextEdit.setObjectName("uiExtraHostsTextEdit")
self.uiTabWidget.addTab(self.advancedTab, "")
self.verticalLayout.addWidget(self.uiTabWidget)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
@@ -164,4 +178,8 @@ class Ui_dockerVMConfigPageWidget(object):
self.uiConsoleResolutionLabel.setText(_translate("dockerVMConfigPageWidget", "VNC console resolution:"))
self.label_2.setText(_translate("dockerVMConfigPageWidget", "HTTP path:"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.tab), _translate("dockerVMConfigPageWidget", "General settings"))
self.uiExtraHostsLabel.setText(_translate("dockerVMConfigPageWidget", "Extra hosts added to \n"
"/etc/hosts file.\n"
"(hostname:IP, one per line)"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.advancedTab), _translate("dockerVMConfigPageWidget", "Advanced"))

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

@@ -25,9 +25,9 @@ import re
from collections import OrderedDict
from gns3.modules.qemu.dialogs.qemu_image_wizard import QemuImageWizard
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.ports.port_name_factory import StandardPortNameFactory
from gns3.node import Node
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
@@ -281,8 +281,12 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
if index != -1:
self.uiQemuListComboBox.setCurrentIndex(index)
else:
QtWidgets.QMessageBox.critical(self, "Qemu", "Could not find {} in the Qemu binaries list".format(qemu_path))
self.uiQemuListComboBox.clear()
index = self.uiQemuListComboBox.findData("{path}".format(path=os.path.basename(qemu_path)), flags=QtCore.Qt.MatchEndsWith)
self.uiQemuListComboBox.setCurrentIndex(index)
if index == -1:
QtWidgets.QMessageBox.warning(self, "Qemu","Could not find '{}' in the Qemu binaries list, please select a new binary".format(qemu_path))
else:
QtWidgets.QMessageBox.warning(self, "Qemu","Could not find '{}' in the Qemu binaries list, an alternative path has been selected".format(qemu_path))
def _cpuThrottlingChangedSlot(self, state):
"""
@@ -322,11 +326,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
QtWidgets.QMessageBox.warning(self, "Qemu", "Server {} is not running, cannot retrieve the QEMU binaries list".format(settings["server"]))
else:
callback = qpartial(self._getQemuBinariesFromServerCallback, qemu_path=settings["qemu_path"])
try:
Qemu.instance().getQemuBinariesFromServer(self._compute_id, callback)
except ModuleError as e:
QtWidgets.QMessageBox.critical(self, "Qemu", "Error while getting the QEMU binaries list: {}".format(e))
self.uiQemuListComboBox.clear()
Qemu.instance().getQemuBinariesFromServer(self._compute_id, callback)
if not group:
# set the device name
@@ -497,25 +497,29 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
symbol_path = self.uiSymbolLineEdit.text()
settings["symbol"] = symbol_path
settings["category"] = self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex())
port_name_format = self.uiPortNameFormatLineEdit.text()
if '{0}' not in port_name_format and '{port0}' not in port_name_format and '{port1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}, {port0} or {port1}")
else:
settings["port_name_format"] = self.uiPortNameFormatLineEdit.text()
port_segment_size = self.uiPortSegmentSizeSpinBox.value()
if port_segment_size and '{1}' not in port_name_format and '{segment0}' not in port_name_format and '{segment1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "If the segment size is not 0, the format must contain {1}, {segment0} or {segment1}")
else:
settings["port_segment_size"] = port_segment_size
first_port_name = self.uiFirstPortNameLineEdit.text().strip()
settings["first_port_name"] = self.uiFirstPortNameLineEdit.text().strip()
try:
StandardPortNameFactory(self.uiAdaptersSpinBox.value(), first_port_name, port_name_format, port_segment_size)
except (ValueError, KeyError):
QtWidgets.QMessageBox.critical(self, "Invalid format", "Invalid port name format")
raise ConfigurationError()
if self.uiQemuListComboBox.count():
settings["port_name_format"] = self.uiPortNameFormatLineEdit.text()
settings["port_segment_size"] = port_segment_size
settings["first_port_name"] = first_port_name
if self.uiQemuListComboBox.currentIndex() != -1:
qemu_path = self.uiQemuListComboBox.itemData(self.uiQemuListComboBox.currentIndex())
settings["qemu_path"] = qemu_path
else:
QtWidgets.QMessageBox.critical(self, "Qemu binary", "Please select a Qemu binary")
if node:
raise ConfigurationError()
settings["boot_priority"] = self.uiBootPriorityComboBox.itemData(self.uiBootPriorityComboBox.currentIndex())
settings["console_type"] = self.uiConsoleTypeComboBox.currentText().lower()

View File

@@ -0,0 +1,249 @@
# -*- 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/>.
"""
TraceNG module implementation.
"""
import os
import copy
import shutil
from gns3.local_config import LocalConfig
from gns3.local_server_config import LocalServerConfig
from ..module import Module
from .traceng_node import TraceNGNode
from .settings import TRACENG_SETTINGS
from .settings import TRACENG_NODES_SETTINGS
import logging
log = logging.getLogger(__name__)
class TraceNG(Module):
"""
TraceNG module.
"""
def __init__(self):
super().__init__()
self._settings = {}
self._nodes = []
self._traceng_nodes = {}
self._working_dir = ""
self._loadSettings()
def configChangedSlot(self):
# load the settings
self._loadSettings()
def _loadSettings(self):
"""
Loads the settings from the persistent settings file.
"""
self._settings = LocalConfig.instance().loadSectionSettings(self.__class__.__name__, TRACENG_SETTINGS)
if not os.path.exists(self._settings["traceng_path"]):
traceng_path = shutil.which("traceng")
if traceng_path:
self._settings["traceng_path"] = os.path.abspath(traceng_path)
else:
self._settings["traceng_path"] = ""
self._loadTraceNGNodes()
def _saveSettings(self):
"""
Saves the settings to the persistent settings file.
"""
# save the settings
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
server_settings = {}
if self._settings["traceng_path"]:
# save some settings to the server config file
server_settings["traceng_path"] = os.path.normpath(self._settings["traceng_path"])
config = LocalServerConfig.instance()
config.saveSettings(self.__class__.__name__, server_settings)
def _loadTraceNGNodes(self):
"""
Load the TraceNG nodes from the persistent settings file.
"""
self._traceng_nodes = {}
settings = LocalConfig.instance().settings()
if "nodes" in settings.get(self.__class__.__name__, {}):
for node in settings[self.__class__.__name__]["nodes"]:
name = node.get("name")
server = node.get("server")
key = "{server}:{name}".format(server=server, name=name)
if key in self._traceng_nodes or not name or not server:
continue
node_settings = TRACENG_NODES_SETTINGS.copy()
node_settings.update(node)
self._traceng_nodes[key] = node_settings
def _saveTraceNGNodes(self):
"""
Saves the TraceNG nodes to the persistent settings file.
"""
self._settings["nodes"] = list(self._traceng_nodes.values())
self._saveSettings()
def addNode(self, node):
"""
Adds a node to this module.
:param node: Node instance
"""
self._nodes.append(node)
def removeNode(self, node):
"""
Removes a node from this module.
:param node: Node instance
"""
if node in self._nodes:
self._nodes.remove(node)
def settings(self):
"""
Returns the module settings
:returns: module settings (dictionary)
"""
return self._settings
def setSettings(self, settings):
"""
Sets the module settings
:param settings: module settings (dictionary)
"""
self._settings.update(settings)
self._saveSettings()
def instantiateNode(self, node_class, server, project):
"""
Instantiate a new node.
:param node_class: Node object
:param server: HTTPClient instance
:param project: Project instance
"""
# create an instance of the node class
return node_class(self, server, project)
def reset(self):
"""
Resets the module.
"""
self._nodes.clear()
@staticmethod
def getNodeType(name, platform=None):
if name == "traceng":
return TraceNGNode
return None
@staticmethod
def vmConfigurationPage():
from .pages.traceng_node_configuration_page import TraceNGNodeConfigurationPage
return TraceNGNodeConfigurationPage
def VMs(self):
"""
Returns list of TraceNG nodes
"""
return self._traceng_nodes
def setVMs(self, new_traceng_nodes):
"""
Sets TraceNG list
:param new_traceng_vms: TraceNG node list
"""
self._traceng_nodes = new_traceng_nodes.copy()
self._saveTraceNGNodes()
@staticmethod
def classes():
"""
Returns all the node classes supported by this module.
:returns: list of classes
"""
return [TraceNGNode]
def nodes(self):
"""
Returns all the node data necessary to represent a node
in the nodes view and create a node on the scene.
"""
nodes = []
# Add a default TraceNG not linked to a specific server
nodes.append(
{
"class": TraceNGNode.__name__,
"name": "TraceNG",
"categories": [TraceNGNode.end_devices],
"symbol": TraceNGNode.defaultSymbol(),
"builtin": True
}
)
return nodes
@staticmethod
def preferencePages():
"""
:returns: QWidget object list
"""
from .pages.traceng_preferences_page import TraceNGPreferencesPage
from .pages.traceng_node_preferences_page import TraceNGNodePreferencesPage
return [TraceNGPreferencesPage, TraceNGNodePreferencesPage]
@staticmethod
def instance():
"""
Singleton to return only on instance of TraceNG module.
:returns: instance of TraceNG
"""
if not hasattr(TraceNG, "_instance"):
TraceNG._instance = TraceNG()
return TraceNG._instance

View File

View File

@@ -0,0 +1,86 @@
# -*- 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/>.
"""
Wizard for TraceNG nodes.
"""
import sys
import ipaddress
from gns3.qt import QtGui, QtWidgets
from gns3.node import Node
from gns3.dialogs.vm_wizard import VMWizard
from ..ui.traceng_node_wizard_ui import Ui_TraceNGNodeWizard
class TraceNGNodeWizard(VMWizard, Ui_TraceNGNodeWizard):
"""
Wizard to create a TraceNG node template.
:param parent: parent widget
"""
def __init__(self, traceng_nodes, parent):
super().__init__(traceng_nodes, parent)
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/icons/traceng.png"))
self.uiNameWizardPage.registerField("name*", self.uiNameLineEdit)
# TraceNG is only supported on a local server
self.uiRemoteRadioButton.setEnabled(False)
self.uiVMRadioButton.setEnabled(False)
def validateCurrentPage(self):
"""
Validates the server.
"""
if super().validateCurrentPage() is False:
return False
if self.currentPage() == self.uiNameWizardPage:
if not sys.platform.startswith("win"):
QtWidgets.QMessageBox.critical(self, "TraceNG", "TraceNG can only run on Windows with a local server")
return False
ip_address = self.uiIPAddressLineEdit.text()
if ip_address:
try:
ipaddress.IPv4Address(ip_address)
except ipaddress.AddressValueError:
QtWidgets.QMessageBox.critical(self, "IP address", "Invalid IP address format")
return False
return True
def getSettings(self):
"""
Returns the settings set in this Wizard.
:return: settings dict
"""
settings = {"name": self.uiNameLineEdit.text(),
"ip_address": self.uiIPAddressLineEdit.text(),
"symbol": ":/symbols/traceng.svg",
"category": Node.end_devices,
"server": self._compute_id}
return settings

View File

View File

@@ -0,0 +1,155 @@
# -*- 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/>.
"""
Configuration page for TraceNG nodes
"""
import ipaddress
from gns3.qt import QtWidgets
from gns3.local_server import LocalServer
from gns3.node import Node
from gns3.controller import Controller
from ..ui.traceng_node_configuration_page_ui import Ui_TraceNGNodeConfigPageWidget
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.dialogs.node_properties_dialog import ConfigurationError
class TraceNGNodeConfigurationPage(QtWidgets.QWidget, Ui_TraceNGNodeConfigPageWidget):
"""
QWidget configuration page for TraceNG nodes.
"""
def __init__(self):
super().__init__()
self.setupUi(self)
self.uiSymbolToolButton.clicked.connect(self._symbolBrowserSlot)
self._default_configs_dir = LocalServer.instance().localServerSettings()["configs_path"]
if Controller.instance().isRemote():
self.uiScriptFileToolButton.hide()
# add the categories
for name, category in Node.defaultCategories().items():
self.uiCategoryComboBox.addItem(name, category)
def _symbolBrowserSlot(self):
"""
Slot to open the symbol browser and select a new symbol.
"""
symbol_path = self.uiSymbolLineEdit.text()
dialog = SymbolSelectionDialog(self, symbol=symbol_path)
dialog.show()
if dialog.exec_():
new_symbol_path = dialog.getSymbol()
self.uiSymbolLineEdit.setText(new_symbol_path)
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(new_symbol_path))
def loadSettings(self, settings, node=None, group=False):
"""
Loads the TraceNG node settings.
:param settings: the settings (dictionary)
:param node: Node instance
:param group: indicates the settings apply to a group of routers
"""
if not group:
self.uiNameLineEdit.setText(settings["name"])
self.uiIPAddressLineEdit.setText(settings["ip_address"])
self.uiDefaultDestinationLineEdit.setText(settings["default_destination"])
else:
self.uiIPAddressLabel.hide()
self.uiIPAddressLineEdit.hide()
self.uiDefaultDestinationLabel.hide()
self.uiDefaultDestinationLineEdit.hide()
self.uiNameLabel.hide()
self.uiNameLineEdit.hide()
if not node:
# these are template settings
# rename the label from "Name" to "Template name"
self.uiNameLabel.setText("Template name:")
# load the default name format
self.uiDefaultNameFormatLineEdit.setText(settings["default_name_format"])
# load the symbol
self.uiSymbolLineEdit.setText(settings["symbol"])
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(settings["symbol"]))
# load the category
index = self.uiCategoryComboBox.findData(settings["category"])
if index != -1:
self.uiCategoryComboBox.setCurrentIndex(index)
else:
self.uiDefaultNameFormatLabel.hide()
self.uiDefaultNameFormatLineEdit.hide()
self.uiSymbolLabel.hide()
self.uiSymbolLineEdit.hide()
self.uiSymbolToolButton.hide()
self.uiCategoryComboBox.hide()
self.uiCategoryLabel.hide()
self.uiCategoryComboBox.hide()
def saveSettings(self, settings, node=None, group=False):
"""
Saves the TraceNG node settings.
:param settings: the settings (dictionary)
:param node: Node instance
:param group: indicates the settings apply to a group of routers
"""
# these settings cannot be shared by nodes and updated
# in the node properties dialog.
if not group:
# set the node name
name = self.uiNameLineEdit.text()
if not name:
QtWidgets.QMessageBox.critical(self, "Name", "TraceNG node name cannot be empty!")
else:
settings["name"] = name
ip_address = self.uiIPAddressLineEdit.text().strip()
if ip_address:
try:
ipaddress.IPv4Address(ip_address)
settings["ip_address"] = ip_address
except ipaddress.AddressValueError:
QtWidgets.QMessageBox.critical(self, "IP address", "Invalid IP address format")
if node:
raise ConfigurationError()
settings["default_destination"] = self.uiDefaultDestinationLineEdit.text().strip()
if not node:
default_name_format = self.uiDefaultNameFormatLineEdit.text().strip()
if '{0}' not in default_name_format and '{id}' not in default_name_format:
QtWidgets.QMessageBox.critical(self, "Default name format", "The default name format must contain at least {0} or {id}")
else:
settings["default_name_format"] = default_name_format
symbol_path = self.uiSymbolLineEdit.text()
settings["symbol"] = symbol_path
settings["category"] = self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex())
return settings

View File

@@ -0,0 +1,189 @@
# -*- 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/>.
"""
Configuration page for TraceNG node preferences.
"""
import copy
from gns3.qt import QtCore, QtWidgets, qpartial
from gns3.main_window import MainWindow
from gns3.dialogs.configuration_dialog import ConfigurationDialog
from gns3.compute_manager import ComputeManager
from gns3.controller import Controller
from .. import TraceNG
from ..settings import TRACENG_NODES_SETTINGS
from ..ui.traceng_node_preferences_page_ui import Ui_TraceNGNodePageWidget
from ..pages.traceng_node_configuration_page import TraceNGNodeConfigurationPage
from ..dialogs.traceng_node_wizard import TraceNGNodeWizard
class TraceNGNodePreferencesPage(QtWidgets.QWidget, Ui_TraceNGNodePageWidget):
"""
QWidget preference page for TraceNG node preferences.
"""
def __init__(self):
super().__init__()
self.setupUi(self)
self._main_window = MainWindow.instance()
self._traceng_nodes = {}
self._items = []
self.uiNewTraceNGPushButton.clicked.connect(self._newTraceNGSlot)
self.uiEditTraceNGPushButton.clicked.connect(self._editTraceNGSlot)
self.uiDeleteTraceNGPushButton.clicked.connect(self._deleteTraceNGSlot)
self.uiTraceNGTreeWidget.itemSelectionChanged.connect(self._tracengChangedSlot)
def _createSectionItem(self, name):
section_item = QtWidgets.QTreeWidgetItem(self.uiTraceNGInfoTreeWidget)
section_item.setText(0, name)
font = section_item.font(0)
font.setBold(True)
section_item.setFont(0, font)
return section_item
def _refreshInfo(self, traceng_node):
self.uiTraceNGInfoTreeWidget.clear()
# fill out the General section
section_item = self._createSectionItem("General")
QtWidgets.QTreeWidgetItem(section_item, ["Template name:", traceng_node["name"]])
QtWidgets.QTreeWidgetItem(section_item, ["IP address:", traceng_node["ip_address"]])
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", traceng_node["default_name_format"]])
try:
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(traceng_node["server"]).name()])
except KeyError:
pass
self.uiTraceNGInfoTreeWidget.expandAll()
self.uiTraceNGInfoTreeWidget.resizeColumnToContents(0)
self.uiTraceNGInfoTreeWidget.resizeColumnToContents(1)
self.uiTraceNGTreeWidget.setMaximumWidth(self.uiTraceNGTreeWidget.sizeHintForColumn(0) + 20)
def _tracengChangedSlot(self):
"""
Loads a selected TraceNG node template from the tree widget.
"""
selection = self.uiTraceNGTreeWidget.selectedItems()
self.uiDeleteTraceNGPushButton.setEnabled(len(selection) != 0)
single_selected = len(selection) == 1
self.uiEditTraceNGPushButton.setEnabled(single_selected)
if single_selected:
key = selection[0].data(0, QtCore.Qt.UserRole)
traceng_node = self._traceng_nodes[key]
self._refreshInfo(traceng_node)
else:
self.uiTraceNGInfoTreeWidget.clear()
def _newTraceNGSlot(self):
"""
Creates a new TraceNG node template.
"""
wizard = TraceNGNodeWizard(self._traceng_nodes, parent=self)
wizard.show()
if wizard.exec_():
new_traceng_node_settings = wizard.getSettings()
key = "{server}:{name}".format(server=new_traceng_node_settings["server"], name=new_traceng_node_settings["name"])
self._traceng_nodes[key] = TRACENG_NODES_SETTINGS.copy()
self._traceng_nodes[key].update(new_traceng_node_settings)
item = QtWidgets.QTreeWidgetItem(self.uiTraceNGTreeWidget)
item.setText(0, self._traceng_nodes[key]["name"])
Controller.instance().getSymbolIcon(self._traceng_nodes[key]["symbol"], qpartial(self._setItemIcon, item))
item.setData(0, QtCore.Qt.UserRole, key)
self._items.append(item)
self.uiTraceNGTreeWidget.setCurrentItem(item)
def _editTraceNGSlot(self):
"""
Edits a TraceNG node template.
"""
item = self.uiTraceNGTreeWidget.currentItem()
if item:
key = item.data(0, QtCore.Qt.UserRole)
traceng_node = self._traceng_nodes[key]
dialog = ConfigurationDialog(traceng_node["name"], traceng_node, TraceNGNodeConfigurationPage(), parent=self)
dialog.show()
if dialog.exec_():
# update the icon
Controller.instance().getSymbolIcon(traceng_node["symbol"], qpartial(self._setItemIcon, item))
if traceng_node["name"] != item.text(0):
new_key = "{server}:{name}".format(server=traceng_node["server"], name=traceng_node["name"])
if new_key in self._traceng_nodes:
QtWidgets.QMessageBox.critical(self, "TraceNG node", "TraceNG node name {} already exists for server {}".format(traceng_node["name"],
traceng_node["server"]))
traceng_node["name"] = item.text(0)
return
self._traceng_nodes[new_key] = self._traceng_nodes[key]
del self._traceng_nodes[key]
item.setText(0, traceng_node["name"])
item.setData(0, QtCore.Qt.UserRole, new_key)
self._refreshInfo(traceng_node)
def _deleteTraceNGSlot(self):
"""
Deletes a TraceNG node template.
"""
for item in self.uiTraceNGTreeWidget.selectedItems():
if item:
key = item.data(0, QtCore.Qt.UserRole)
del self._traceng_nodes[key]
self.uiTraceNGTreeWidget.takeTopLevelItem(self.uiTraceNGTreeWidget.indexOfTopLevelItem(item))
def loadPreferences(self):
"""
Loads the TraceNG node preferences.
"""
traceng_module = TraceNG.instance()
self._traceng_nodes = copy.deepcopy(traceng_module.VMs())
self._items.clear()
for key, node in self._traceng_nodes.items():
item = QtWidgets.QTreeWidgetItem(self.uiTraceNGTreeWidget)
item.setText(0, node["name"])
Controller.instance().getSymbolIcon(node["symbol"], qpartial(self._setItemIcon, item))
item.setData(0, QtCore.Qt.UserRole, key)
self._items.append(item)
if self._items:
self.uiTraceNGTreeWidget.setCurrentItem(self._items[0])
self.uiTraceNGTreeWidget.sortByColumn(0, QtCore.Qt.AscendingOrder)
self.uiTraceNGTreeWidget.setMaximumWidth(self.uiTraceNGTreeWidget.sizeHintForColumn(0) + 20)
def _setItemIcon(self, item, icon):
item.setIcon(0, icon)
self.uiTraceNGTreeWidget.setMaximumWidth(self.uiTraceNGTreeWidget.sizeHintForColumn(0) + 20)
def savePreferences(self):
"""
Saves the TraceNG node preferences.
"""
TraceNG.instance().setVMs(self._traceng_nodes)

View File

@@ -0,0 +1,127 @@
# -*- 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/>.
"""
Configuration page for TraceNG preferences.
"""
import os
import sys
import shutil
from gns3.qt import QtWidgets
from .. import TraceNG
from ..ui.traceng_preferences_page_ui import Ui_TraceNGPreferencesPageWidget
from ..settings import TRACENG_SETTINGS
class TraceNGPreferencesPage(QtWidgets.QWidget, Ui_TraceNGPreferencesPageWidget):
"""
QWidget preference page for TraceNG
"""
def __init__(self):
super().__init__()
self.setupUi(self)
# connect signals
self.uiRestoreDefaultsPushButton.clicked.connect(self._restoreDefaultsSlot)
self.uiTraceNGPathToolButton.clicked.connect(self._tracengPathBrowserSlot)
def _tracengPathBrowserSlot(self):
"""
Slot to open a file browser and select traceng
"""
filter = ""
if sys.platform.startswith("win"):
filter = "Executable (*.exe);;All files (*.*)"
traceng_path = shutil.which("traceng")
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select TraceNG", traceng_path, filter)
if not path:
return
if self._checkTraceNGPath(path):
self.uiTraceNGPathLineEdit.setText(os.path.normpath(path))
def _checkTraceNGPath(self, path):
"""
Checks that the TraceNG path is valid.
:param path: TraceNG path
:returns: boolean
"""
if not os.path.exists(path):
QtWidgets.QMessageBox.critical(self, "TraceNG", '"{}" does not exist'.format(path))
return False
if not os.access(path, os.X_OK):
QtWidgets.QMessageBox.critical(self, "TraceNG", "{} is not an executable".format(os.path.basename(path)))
return False
return True
def _restoreDefaultsSlot(self):
"""
Slot to populate the page widgets with the default settings.
"""
self._populateWidgets(TRACENG_SETTINGS)
def _useLocalServerSlot(self, state):
"""
Slot to enable or not local server settings.
"""
if state:
self.uiTraceNGPathLineEdit.setEnabled(True)
self.uiTraceNGPathToolButton.setEnabled(True)
else:
self.uiTraceNGPathLineEdit.setEnabled(False)
self.uiTraceNGPathToolButton.setEnabled(False)
def _populateWidgets(self, settings):
"""
Populates the widgets with the settings.
:param settings: TraceNG settings
"""
self.uiTraceNGPathLineEdit.setText(settings["traceng_path"])
def loadPreferences(self):
"""
Loads TraceNG preferences.
"""
traceng_settings = TraceNG.instance().settings()
self._populateWidgets(traceng_settings)
def savePreferences(self):
"""
Saves TraceNG preferences.
"""
traceng_path = self.uiTraceNGPathLineEdit.text().strip()
if traceng_path and not self._checkTraceNGPath(traceng_path):
return
new_settings = {"traceng_path": traceng_path}
TraceNG.instance().setSettings(new_settings)

View File

@@ -0,0 +1,36 @@
# -*- 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/>.
"""
Default TraceNG settings.
"""
from gns3.node import Node
TRACENG_SETTINGS = {
"traceng_path": "",
}
TRACENG_NODES_SETTINGS = {
"name": "",
"ip_address": "",
"default_destination": "",
"default_name_format": "TraceNG{0}",
"console_type": "none",
"symbol": ":/symbols/traceng.svg",
"category": Node.end_devices,
}

View File

@@ -0,0 +1,172 @@
# -*- 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/>.
"""
TraceNG node implementation.
"""
from gns3.node import Node
from gns3.qt import QtWidgets
import logging
log = logging.getLogger(__name__)
class TraceNGNode(Node):
"""
TraceNG node.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
URL_PREFIX = "traceng"
def __init__(self, module, server, project):
super().__init__(module, server, project)
traceng_settings = {"console_host": None,
"console": None,
"console_type": "none",
"ip_address": "",
"default_destination": ""}
self._last_destination = ""
self.settings().update(traceng_settings)
def update(self, new_settings):
"""
Updates the settings for this TraceNG node.
:param new_settings: settings dictionary
"""
params = {}
for name, value in new_settings.items():
if name in self._settings and self._settings[name] != value:
params[name] = value
if params:
self._update(params)
def start(self):
"""
Starts this node instance.
"""
if self.isStarted():
log.debug("{} is already running".format(self.name()))
return
if self._last_destination:
destination = self._last_destination
else:
destination = self.settings()["default_destination"]
destination, ok = QtWidgets.QInputDialog.getText(self.parent(), "TraceNG", "Destination host or IP address:", text=destination)
if ok:
if not destination:
QtWidgets.QMessageBox.critical(self, "TraceNG", "Please provide a host or IP address to trace")
return
ip_address = self.settings()["ip_address"]
if destination == ip_address:
QtWidgets.QMessageBox.critical(self, "TraceNG", "Destination cannot be the same as this node IP address ({})".format(ip_address))
return
self._last_destination = destination
params = {"destination": destination}
log.debug("{} is starting".format(self.name()))
self.controllerHttpPost("/nodes/{node_id}/start".format(node_id=self._node_id), self._startCallback, body=params, timeout=None, progressText="{} is starting".format(self.name()))
def info(self):
"""
Returns information about this TraceNG node.
:returns: formatted string
"""
if self.status() == Node.started:
state = "started"
else:
state = "stopped"
info = """Node {name} is {state}
Local node ID is {id}
Server's VPCS node ID is {node_id}
TraceNG's server runs on {host}, console is on port {console}
""".format(name=self.name(),
id=self.id(),
node_id=self._node_id,
state=state,
host=self.compute().name(),
console=self._settings["console"])
port_info = ""
for port in self._ports:
if port.isFree():
port_info += " {port_name} is empty\n".format(port_name=port.name())
else:
port_info += " {port_name} {port_description}\n".format(port_name=port.name(),
port_description=port.description())
return info + port_info
def console(self):
"""
Returns the console port for this TraceNG node.
:returns: port (integer)
"""
return self._settings["console"]
def configPage(self):
"""
Returns the configuration page widget to be used by the node properties dialog.
:returns: QWidget object
"""
from .pages.traceng_node_configuration_page import TraceNGNodeConfigurationPage
return TraceNGNodeConfigurationPage
@staticmethod
def defaultSymbol():
"""
Returns the default symbol path for this node.
:returns: symbol path (or resource).
"""
return ":/symbols/traceng.svg"
@staticmethod
def symbolName():
return "TraceNG"
@staticmethod
def categories():
"""
Returns the node categories the node is part of (used by the node panel).
:returns: list of node categories
"""
return [Node.end_devices]
def __str__(self):
return "TraceNG node"

View File

View File

@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TraceNGNodeConfigPageWidget</class>
<widget class="QWidget" name="TraceNGNodeConfigPageWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>846</width>
<height>340</height>
</rect>
</property>
<property name="windowTitle">
<string>TraceNG node configuration</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="uiNameLabel">
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiIPAddressLabel">
<property name="text">
<string>IP address:</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="uiDefaultDestinationLabel">
<property name="text">
<string>Default destination:</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="3">
<widget class="QLabel" name="uiDefaultNameFormatLabel">
<property name="text">
<string>Default name format:</string>
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="QLineEdit" name="uiDefaultNameFormatLineEdit"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="uiSymbolLabel">
<property name="text">
<string>Symbol:</string>
</property>
</widget>
</item>
<item row="4" column="3">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLineEdit" name="uiSymbolLineEdit"/>
</item>
<item>
<widget class="QToolButton" name="uiSymbolToolButton">
<property name="text">
<string>&amp;Browse...</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum>
</property>
</widget>
</item>
</layout>
</item>
<item row="5" column="0" colspan="2">
<widget class="QLabel" name="uiCategoryLabel">
<property name="text">
<string>Category:</string>
</property>
</widget>
</item>
<item row="5" column="3">
<widget class="QComboBox" name="uiCategoryComboBox"/>
</item>
<item row="6" column="1" colspan="3">
<spacer name="spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>263</width>
<height>212</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="3">
<widget class="QLineEdit" name="uiDefaultDestinationLineEdit"/>
</item>
<item row="1" column="3">
<widget class="QLineEdit" name="uiIPAddressLineEdit"/>
</item>
<item row="0" column="3">
<widget class="QLineEdit" name="uiNameLineEdit"/>
</item>
</layout>
<zorder>uiNameLabel</zorder>
<zorder>uiNameLineEdit</zorder>
<zorder>uiDefaultNameFormatLabel</zorder>
<zorder>uiDefaultNameFormatLineEdit</zorder>
<zorder>uiSymbolLabel</zorder>
<zorder>uiCategoryLabel</zorder>
<zorder>uiCategoryComboBox</zorder>
<zorder>uiIPAddressLabel</zorder>
<zorder>uiIPAddressLineEdit</zorder>
<zorder>uiDefaultDestinationLabel</zorder>
<zorder>uiDefaultDestinationLineEdit</zorder>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/traceng/ui/traceng_node_configuration_page.ui'
#
# 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_TraceNGNodeConfigPageWidget(object):
def setupUi(self, TraceNGNodeConfigPageWidget):
TraceNGNodeConfigPageWidget.setObjectName("TraceNGNodeConfigPageWidget")
TraceNGNodeConfigPageWidget.resize(846, 340)
self.gridLayout = QtWidgets.QGridLayout(TraceNGNodeConfigPageWidget)
self.gridLayout.setObjectName("gridLayout")
self.uiNameLabel = QtWidgets.QLabel(TraceNGNodeConfigPageWidget)
self.uiNameLabel.setObjectName("uiNameLabel")
self.gridLayout.addWidget(self.uiNameLabel, 0, 0, 1, 1)
self.uiIPAddressLabel = QtWidgets.QLabel(TraceNGNodeConfigPageWidget)
self.uiIPAddressLabel.setObjectName("uiIPAddressLabel")
self.gridLayout.addWidget(self.uiIPAddressLabel, 1, 0, 1, 1)
self.uiDefaultDestinationLabel = QtWidgets.QLabel(TraceNGNodeConfigPageWidget)
self.uiDefaultDestinationLabel.setObjectName("uiDefaultDestinationLabel")
self.gridLayout.addWidget(self.uiDefaultDestinationLabel, 2, 0, 1, 2)
self.uiDefaultNameFormatLabel = QtWidgets.QLabel(TraceNGNodeConfigPageWidget)
self.uiDefaultNameFormatLabel.setObjectName("uiDefaultNameFormatLabel")
self.gridLayout.addWidget(self.uiDefaultNameFormatLabel, 3, 0, 1, 3)
self.uiDefaultNameFormatLineEdit = QtWidgets.QLineEdit(TraceNGNodeConfigPageWidget)
self.uiDefaultNameFormatLineEdit.setObjectName("uiDefaultNameFormatLineEdit")
self.gridLayout.addWidget(self.uiDefaultNameFormatLineEdit, 3, 3, 1, 1)
self.uiSymbolLabel = QtWidgets.QLabel(TraceNGNodeConfigPageWidget)
self.uiSymbolLabel.setObjectName("uiSymbolLabel")
self.gridLayout.addWidget(self.uiSymbolLabel, 4, 0, 1, 1)
self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
self.uiSymbolLineEdit = QtWidgets.QLineEdit(TraceNGNodeConfigPageWidget)
self.uiSymbolLineEdit.setObjectName("uiSymbolLineEdit")
self.horizontalLayout_7.addWidget(self.uiSymbolLineEdit)
self.uiSymbolToolButton = QtWidgets.QToolButton(TraceNGNodeConfigPageWidget)
self.uiSymbolToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiSymbolToolButton.setObjectName("uiSymbolToolButton")
self.horizontalLayout_7.addWidget(self.uiSymbolToolButton)
self.gridLayout.addLayout(self.horizontalLayout_7, 4, 3, 1, 1)
self.uiCategoryLabel = QtWidgets.QLabel(TraceNGNodeConfigPageWidget)
self.uiCategoryLabel.setObjectName("uiCategoryLabel")
self.gridLayout.addWidget(self.uiCategoryLabel, 5, 0, 1, 2)
self.uiCategoryComboBox = QtWidgets.QComboBox(TraceNGNodeConfigPageWidget)
self.uiCategoryComboBox.setObjectName("uiCategoryComboBox")
self.gridLayout.addWidget(self.uiCategoryComboBox, 5, 3, 1, 1)
spacerItem = QtWidgets.QSpacerItem(263, 212, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem, 6, 1, 1, 3)
self.uiDefaultDestinationLineEdit = QtWidgets.QLineEdit(TraceNGNodeConfigPageWidget)
self.uiDefaultDestinationLineEdit.setObjectName("uiDefaultDestinationLineEdit")
self.gridLayout.addWidget(self.uiDefaultDestinationLineEdit, 2, 3, 1, 1)
self.uiIPAddressLineEdit = QtWidgets.QLineEdit(TraceNGNodeConfigPageWidget)
self.uiIPAddressLineEdit.setObjectName("uiIPAddressLineEdit")
self.gridLayout.addWidget(self.uiIPAddressLineEdit, 1, 3, 1, 1)
self.uiNameLineEdit = QtWidgets.QLineEdit(TraceNGNodeConfigPageWidget)
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 3, 1, 1)
self.uiNameLabel.raise_()
self.uiNameLineEdit.raise_()
self.uiDefaultNameFormatLabel.raise_()
self.uiDefaultNameFormatLineEdit.raise_()
self.uiSymbolLabel.raise_()
self.uiCategoryLabel.raise_()
self.uiCategoryComboBox.raise_()
self.uiIPAddressLabel.raise_()
self.uiIPAddressLineEdit.raise_()
self.uiDefaultDestinationLabel.raise_()
self.uiDefaultDestinationLineEdit.raise_()
self.retranslateUi(TraceNGNodeConfigPageWidget)
QtCore.QMetaObject.connectSlotsByName(TraceNGNodeConfigPageWidget)
def retranslateUi(self, TraceNGNodeConfigPageWidget):
_translate = QtCore.QCoreApplication.translate
TraceNGNodeConfigPageWidget.setWindowTitle(_translate("TraceNGNodeConfigPageWidget", "TraceNG node configuration"))
self.uiNameLabel.setText(_translate("TraceNGNodeConfigPageWidget", "Name:"))
self.uiIPAddressLabel.setText(_translate("TraceNGNodeConfigPageWidget", "IP address:"))
self.uiDefaultDestinationLabel.setText(_translate("TraceNGNodeConfigPageWidget", "Default destination:"))
self.uiDefaultNameFormatLabel.setText(_translate("TraceNGNodeConfigPageWidget", "Default name format:"))
self.uiSymbolLabel.setText(_translate("TraceNGNodeConfigPageWidget", "Symbol:"))
self.uiSymbolToolButton.setText(_translate("TraceNGNodeConfigPageWidget", "&Browse..."))
self.uiCategoryLabel.setText(_translate("TraceNGNodeConfigPageWidget", "Category:"))

View File

@@ -0,0 +1,160 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TraceNGNodePageWidget</class>
<widget class="QWidget" name="TraceNGNodePageWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>546</width>
<height>455</height>
</rect>
</property>
<property name="windowTitle">
<string>TraceNG nodes</string>
</property>
<property name="accessibleName">
<string>TraceNG node templates</string>
</property>
<property name="accessibleDescription">
<string/>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QTreeWidget" name="uiTraceNGTreeWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>160</width>
<height>16777215</height>
</size>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTreeWidget" name="uiTraceNGInfoTreeWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="indentation">
<number>10</number>
</property>
<property name="allColumnsShowFocus">
<bool>true</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>1</string>
</property>
</column>
<column>
<property name="text">
<string>2</string>
</property>
</column>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QPushButton" name="uiNewTraceNGPushButton">
<property name="text">
<string>&amp;New</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="uiEditTraceNGPushButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Edit</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="uiDeleteTraceNGPushButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Delete</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>uiNewTraceNGPushButton</tabstop>
<tabstop>uiDeleteTraceNGPushButton</tabstop>
</tabstops>
<resources/>
<connections/>
<designerdata>
<property name="gridDeltaX">
<number>10</number>
</property>
<property name="gridDeltaY">
<number>10</number>
</property>
<property name="gridSnapX">
<bool>true</bool>
</property>
<property name="gridSnapY">
<bool>true</bool>
</property>
<property name="gridVisible">
<bool>true</bool>
</property>
</designerdata>
</ui>

View File

@@ -0,0 +1,83 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/traceng/ui/traceng_node_preferences_page.ui'
#
# 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_TraceNGNodePageWidget(object):
def setupUi(self, TraceNGNodePageWidget):
TraceNGNodePageWidget.setObjectName("TraceNGNodePageWidget")
TraceNGNodePageWidget.resize(546, 455)
TraceNGNodePageWidget.setAccessibleDescription("")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(TraceNGNodePageWidget)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.splitter = QtWidgets.QSplitter(TraceNGNodePageWidget)
self.splitter.setOrientation(QtCore.Qt.Horizontal)
self.splitter.setObjectName("splitter")
self.uiTraceNGTreeWidget = QtWidgets.QTreeWidget(self.splitter)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiTraceNGTreeWidget.sizePolicy().hasHeightForWidth())
self.uiTraceNGTreeWidget.setSizePolicy(sizePolicy)
self.uiTraceNGTreeWidget.setMaximumSize(QtCore.QSize(160, 16777215))
font = QtGui.QFont()
font.setPointSize(11)
font.setBold(True)
font.setWeight(75)
self.uiTraceNGTreeWidget.setFont(font)
self.uiTraceNGTreeWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.uiTraceNGTreeWidget.setIconSize(QtCore.QSize(32, 32))
self.uiTraceNGTreeWidget.setRootIsDecorated(False)
self.uiTraceNGTreeWidget.setObjectName("uiTraceNGTreeWidget")
self.uiTraceNGTreeWidget.headerItem().setText(0, "1")
self.uiTraceNGTreeWidget.header().setVisible(False)
self.layoutWidget = QtWidgets.QWidget(self.splitter)
self.layoutWidget.setObjectName("layoutWidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget)
self.verticalLayout.setObjectName("verticalLayout")
self.uiTraceNGInfoTreeWidget = QtWidgets.QTreeWidget(self.layoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiTraceNGInfoTreeWidget.sizePolicy().hasHeightForWidth())
self.uiTraceNGInfoTreeWidget.setSizePolicy(sizePolicy)
self.uiTraceNGInfoTreeWidget.setIndentation(10)
self.uiTraceNGInfoTreeWidget.setAllColumnsShowFocus(True)
self.uiTraceNGInfoTreeWidget.setObjectName("uiTraceNGInfoTreeWidget")
self.uiTraceNGInfoTreeWidget.header().setVisible(False)
self.verticalLayout.addWidget(self.uiTraceNGInfoTreeWidget)
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
self.uiNewTraceNGPushButton = QtWidgets.QPushButton(self.layoutWidget)
self.uiNewTraceNGPushButton.setObjectName("uiNewTraceNGPushButton")
self.horizontalLayout_5.addWidget(self.uiNewTraceNGPushButton)
self.uiEditTraceNGPushButton = QtWidgets.QPushButton(self.layoutWidget)
self.uiEditTraceNGPushButton.setEnabled(False)
self.uiEditTraceNGPushButton.setObjectName("uiEditTraceNGPushButton")
self.horizontalLayout_5.addWidget(self.uiEditTraceNGPushButton)
self.uiDeleteTraceNGPushButton = QtWidgets.QPushButton(self.layoutWidget)
self.uiDeleteTraceNGPushButton.setEnabled(False)
self.uiDeleteTraceNGPushButton.setObjectName("uiDeleteTraceNGPushButton")
self.horizontalLayout_5.addWidget(self.uiDeleteTraceNGPushButton)
self.verticalLayout.addLayout(self.horizontalLayout_5)
self.verticalLayout_2.addWidget(self.splitter)
self.retranslateUi(TraceNGNodePageWidget)
QtCore.QMetaObject.connectSlotsByName(TraceNGNodePageWidget)
TraceNGNodePageWidget.setTabOrder(self.uiNewTraceNGPushButton, self.uiDeleteTraceNGPushButton)
def retranslateUi(self, TraceNGNodePageWidget):
_translate = QtCore.QCoreApplication.translate
TraceNGNodePageWidget.setWindowTitle(_translate("TraceNGNodePageWidget", "TraceNG nodes"))
TraceNGNodePageWidget.setAccessibleName(_translate("TraceNGNodePageWidget", "TraceNG node templates"))
self.uiTraceNGInfoTreeWidget.headerItem().setText(0, _translate("TraceNGNodePageWidget", "1"))
self.uiTraceNGInfoTreeWidget.headerItem().setText(1, _translate("TraceNGNodePageWidget", "2"))
self.uiNewTraceNGPushButton.setText(_translate("TraceNGNodePageWidget", "&New"))
self.uiEditTraceNGPushButton.setText(_translate("TraceNGNodePageWidget", "&Edit"))
self.uiDeleteTraceNGPushButton.setText(_translate("TraceNGNodePageWidget", "&Delete"))

View File

@@ -0,0 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TraceNGNodeWizard</class>
<widget class="QWizard" name="TraceNGNodeWizard">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>706</width>
<height>452</height>
</rect>
</property>
<property name="windowTitle">
<string>New TraceNG node template</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<widget class="QWizardPage" name="uiServerWizardPage">
<property name="title">
<string>Server</string>
</property>
<property name="subTitle">
<string>Please choose a server type to run your new TraceNG node.</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QGroupBox" name="uiServerTypeGroupBox">
<property name="title">
<string>Server type</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QRadioButton" name="uiRemoteRadioButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Run the TraceNG node on a remote computer</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="uiVMRadioButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Run the TraceNG node on the GNS3 VM</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="uiLocalRadioButton">
<property name="text">
<string>Run the TraceNG node on your local computer</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="uiRemoteServersGroupBox">
<property name="title">
<string>Remote server</string>
</property>
<layout class="QGridLayout" name="gridLayout_7">
<item row="0" column="0">
<widget class="QLabel" name="uiRemoteServersLabel">
<property name="text">
<string>Run on:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="uiRemoteServersComboBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="uiNameWizardPage">
<property name="title">
<string>Name and IP address</string>
</property>
<property name="subTitle">
<string>Please choose a descriptive name and IP address for the new TraceNG node.</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="uiNameLabel">
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="uiNameLineEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiIPAddressLabel">
<property name="text">
<string>IP address:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="uiIPAddressLineEdit">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<tabstops>
<tabstop>uiNameLineEdit</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/traceng/ui/traceng_node_wizard.ui'
#
# 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_TraceNGNodeWizard(object):
def setupUi(self, TraceNGNodeWizard):
TraceNGNodeWizard.setObjectName("TraceNGNodeWizard")
TraceNGNodeWizard.resize(706, 452)
TraceNGNodeWizard.setModal(True)
self.uiServerWizardPage = QtWidgets.QWizardPage()
self.uiServerWizardPage.setObjectName("uiServerWizardPage")
self.gridLayout_2 = QtWidgets.QGridLayout(self.uiServerWizardPage)
self.gridLayout_2.setObjectName("gridLayout_2")
self.uiServerTypeGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
self.uiServerTypeGroupBox.setObjectName("uiServerTypeGroupBox")
self.verticalLayout = QtWidgets.QVBoxLayout(self.uiServerTypeGroupBox)
self.verticalLayout.setObjectName("verticalLayout")
self.uiRemoteRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiRemoteRadioButton.setEnabled(False)
self.uiRemoteRadioButton.setChecked(False)
self.uiRemoteRadioButton.setObjectName("uiRemoteRadioButton")
self.verticalLayout.addWidget(self.uiRemoteRadioButton)
self.uiVMRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiVMRadioButton.setEnabled(False)
self.uiVMRadioButton.setObjectName("uiVMRadioButton")
self.verticalLayout.addWidget(self.uiVMRadioButton)
self.uiLocalRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiLocalRadioButton.setChecked(True)
self.uiLocalRadioButton.setObjectName("uiLocalRadioButton")
self.verticalLayout.addWidget(self.uiLocalRadioButton)
self.gridLayout_2.addWidget(self.uiServerTypeGroupBox, 0, 0, 1, 1)
self.uiRemoteServersGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
self.uiRemoteServersGroupBox.setObjectName("uiRemoteServersGroupBox")
self.gridLayout_7 = QtWidgets.QGridLayout(self.uiRemoteServersGroupBox)
self.gridLayout_7.setObjectName("gridLayout_7")
self.uiRemoteServersLabel = QtWidgets.QLabel(self.uiRemoteServersGroupBox)
self.uiRemoteServersLabel.setObjectName("uiRemoteServersLabel")
self.gridLayout_7.addWidget(self.uiRemoteServersLabel, 0, 0, 1, 1)
self.uiRemoteServersComboBox = QtWidgets.QComboBox(self.uiRemoteServersGroupBox)
self.uiRemoteServersComboBox.setEnabled(False)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiRemoteServersComboBox.sizePolicy().hasHeightForWidth())
self.uiRemoteServersComboBox.setSizePolicy(sizePolicy)
self.uiRemoteServersComboBox.setObjectName("uiRemoteServersComboBox")
self.gridLayout_7.addWidget(self.uiRemoteServersComboBox, 0, 1, 1, 1)
self.gridLayout_2.addWidget(self.uiRemoteServersGroupBox, 1, 0, 1, 1)
TraceNGNodeWizard.addPage(self.uiServerWizardPage)
self.uiNameWizardPage = QtWidgets.QWizardPage()
self.uiNameWizardPage.setObjectName("uiNameWizardPage")
self.gridLayout = QtWidgets.QGridLayout(self.uiNameWizardPage)
self.gridLayout.setObjectName("gridLayout")
self.uiNameLabel = QtWidgets.QLabel(self.uiNameWizardPage)
self.uiNameLabel.setObjectName("uiNameLabel")
self.gridLayout.addWidget(self.uiNameLabel, 0, 0, 1, 1)
self.uiNameLineEdit = QtWidgets.QLineEdit(self.uiNameWizardPage)
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
self.uiIPAddressLabel = QtWidgets.QLabel(self.uiNameWizardPage)
self.uiIPAddressLabel.setObjectName("uiIPAddressLabel")
self.gridLayout.addWidget(self.uiIPAddressLabel, 1, 0, 1, 1)
self.uiIPAddressLineEdit = QtWidgets.QLineEdit(self.uiNameWizardPage)
self.uiIPAddressLineEdit.setText("")
self.uiIPAddressLineEdit.setObjectName("uiIPAddressLineEdit")
self.gridLayout.addWidget(self.uiIPAddressLineEdit, 1, 1, 1, 1)
TraceNGNodeWizard.addPage(self.uiNameWizardPage)
self.retranslateUi(TraceNGNodeWizard)
QtCore.QMetaObject.connectSlotsByName(TraceNGNodeWizard)
def retranslateUi(self, TraceNGNodeWizard):
_translate = QtCore.QCoreApplication.translate
TraceNGNodeWizard.setWindowTitle(_translate("TraceNGNodeWizard", "New TraceNG node template"))
self.uiServerWizardPage.setTitle(_translate("TraceNGNodeWizard", "Server"))
self.uiServerWizardPage.setSubTitle(_translate("TraceNGNodeWizard", "Please choose a server type to run your new TraceNG node."))
self.uiServerTypeGroupBox.setTitle(_translate("TraceNGNodeWizard", "Server type"))
self.uiRemoteRadioButton.setText(_translate("TraceNGNodeWizard", "Run the TraceNG node on a remote computer"))
self.uiVMRadioButton.setText(_translate("TraceNGNodeWizard", "Run the TraceNG node on the GNS3 VM"))
self.uiLocalRadioButton.setText(_translate("TraceNGNodeWizard", "Run the TraceNG node on your local computer"))
self.uiRemoteServersGroupBox.setTitle(_translate("TraceNGNodeWizard", "Remote server"))
self.uiRemoteServersLabel.setText(_translate("TraceNGNodeWizard", "Run on:"))
self.uiNameWizardPage.setTitle(_translate("TraceNGNodeWizard", "Name and IP address"))
self.uiNameWizardPage.setSubTitle(_translate("TraceNGNodeWizard", "Please choose a descriptive name and IP address for the new TraceNG node."))
self.uiNameLabel.setText(_translate("TraceNGNodeWizard", "Name:"))
self.uiIPAddressLabel.setText(_translate("TraceNGNodeWizard", "IP address:"))

View File

@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TraceNGPreferencesPageWidget</class>
<widget class="QWidget" name="TraceNGPreferencesPageWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>623</width>
<height>280</height>
</rect>
</property>
<property name="windowTitle">
<string>TraceNG</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTabWidget" name="uiTabWidget">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="uiGeneralSettingsTabWidget">
<attribute name="title">
<string>Local settings</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="margin">
<number>10</number>
</property>
<item>
<widget class="QLabel" name="uiTraceNGPathLabel">
<property name="text">
<string>Path to TraceNG executable:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLineEdit" name="uiTraceNGPathLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="uiTraceNGPathToolButton">
<property name="text">
<string>&amp;Browse...</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>138</width>
<height>17</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="uiRestoreDefaultsPushButton">
<property name="text">
<string>Restore defaults</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
<designerdata>
<property name="gridDeltaX">
<number>10</number>
</property>
<property name="gridDeltaY">
<number>10</number>
</property>
<property name="gridSnapX">
<bool>true</bool>
</property>
<property name="gridSnapY">
<bool>true</bool>
</property>
<property name="gridVisible">
<bool>true</bool>
</property>
</designerdata>
</ui>

View File

@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/traceng/ui/traceng_preferences_page.ui'
#
# 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_TraceNGPreferencesPageWidget(object):
def setupUi(self, TraceNGPreferencesPageWidget):
TraceNGPreferencesPageWidget.setObjectName("TraceNGPreferencesPageWidget")
TraceNGPreferencesPageWidget.resize(623, 280)
self.verticalLayout_2 = QtWidgets.QVBoxLayout(TraceNGPreferencesPageWidget)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.uiTabWidget = QtWidgets.QTabWidget(TraceNGPreferencesPageWidget)
self.uiTabWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.uiTabWidget.setObjectName("uiTabWidget")
self.uiGeneralSettingsTabWidget = QtWidgets.QWidget()
self.uiGeneralSettingsTabWidget.setObjectName("uiGeneralSettingsTabWidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.uiGeneralSettingsTabWidget)
self.verticalLayout.setContentsMargins(10, 10, 10, 10)
self.verticalLayout.setObjectName("verticalLayout")
self.uiTraceNGPathLabel = QtWidgets.QLabel(self.uiGeneralSettingsTabWidget)
self.uiTraceNGPathLabel.setObjectName("uiTraceNGPathLabel")
self.verticalLayout.addWidget(self.uiTraceNGPathLabel)
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
self.uiTraceNGPathLineEdit = QtWidgets.QLineEdit(self.uiGeneralSettingsTabWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiTraceNGPathLineEdit.sizePolicy().hasHeightForWidth())
self.uiTraceNGPathLineEdit.setSizePolicy(sizePolicy)
self.uiTraceNGPathLineEdit.setObjectName("uiTraceNGPathLineEdit")
self.horizontalLayout_5.addWidget(self.uiTraceNGPathLineEdit)
self.uiTraceNGPathToolButton = QtWidgets.QToolButton(self.uiGeneralSettingsTabWidget)
self.uiTraceNGPathToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiTraceNGPathToolButton.setObjectName("uiTraceNGPathToolButton")
self.horizontalLayout_5.addWidget(self.uiTraceNGPathToolButton)
self.verticalLayout.addLayout(self.horizontalLayout_5)
spacerItem = QtWidgets.QSpacerItem(20, 5, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.uiTabWidget.addTab(self.uiGeneralSettingsTabWidget, "")
self.verticalLayout_2.addWidget(self.uiTabWidget)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
spacerItem1 = QtWidgets.QSpacerItem(138, 17, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem1)
self.uiRestoreDefaultsPushButton = QtWidgets.QPushButton(TraceNGPreferencesPageWidget)
self.uiRestoreDefaultsPushButton.setObjectName("uiRestoreDefaultsPushButton")
self.horizontalLayout_2.addWidget(self.uiRestoreDefaultsPushButton)
self.verticalLayout_2.addLayout(self.horizontalLayout_2)
self.retranslateUi(TraceNGPreferencesPageWidget)
self.uiTabWidget.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(TraceNGPreferencesPageWidget)
def retranslateUi(self, TraceNGPreferencesPageWidget):
_translate = QtCore.QCoreApplication.translate
TraceNGPreferencesPageWidget.setWindowTitle(_translate("TraceNGPreferencesPageWidget", "TraceNG"))
self.uiTraceNGPathLabel.setText(_translate("TraceNGPreferencesPageWidget", "Path to TraceNG executable:"))
self.uiTraceNGPathToolButton.setText(_translate("TraceNGPreferencesPageWidget", "&Browse..."))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiGeneralSettingsTabWidget), _translate("TraceNGPreferencesPageWidget", "Local settings"))
self.uiRestoreDefaultsPushButton.setText(_translate("TraceNGPreferencesPageWidget", "Restore defaults"))

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

@@ -22,6 +22,7 @@ Configuration page for VirtualBox VMs.
from gns3.qt import QtWidgets
from gns3.dialogs.node_properties_dialog import ConfigurationError
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.ports.port_name_factory import StandardPortNameFactory
from gns3.node import Node
from ..ui.virtualbox_vm_configuration_page_ui import Ui_virtualBoxVMConfigPageWidget
@@ -178,21 +179,21 @@ class VirtualBoxVMConfigurationPage(QtWidgets.QWidget, Ui_virtualBoxVMConfigPage
symbol_path = self.uiSymbolLineEdit.text()
settings["symbol"] = symbol_path
settings["category"] = self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex())
port_name_format = self.uiPortNameFormatLineEdit.text()
if '{0}' not in port_name_format and '{port0}' not in port_name_format and '{port1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}, {port0} or {port1}")
else:
settings["port_name_format"] = self.uiPortNameFormatLineEdit.text()
port_segment_size = self.uiPortSegmentSizeSpinBox.value()
if port_segment_size and '{1}' not in port_name_format and '{segment0}' not in port_name_format and '{segment1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "If the segment size is not 0, the format must contain {1}, {segment0} or {segment1}")
else:
settings["port_segment_size"] = port_segment_size
first_port_name = self.uiFirstPortNameLineEdit.text().strip()
settings["first_port_name"] = self.uiFirstPortNameLineEdit.text().strip()
try:
StandardPortNameFactory(self.uiAdaptersSpinBox.value(), first_port_name, port_name_format, port_segment_size)
except (ValueError, KeyError):
QtWidgets.QMessageBox.critical(self, "Invalid format", "Invalid port name format")
raise ConfigurationError()
settings["port_name_format"] = self.uiPortNameFormatLineEdit.text()
settings["port_segment_size"] = port_segment_size
settings["first_port_name"] = first_port_name
settings["ram"] = self.uiVMRamSpinBox.value()
settings["adapter_type"] = self.uiAdapterTypesComboBox.currentText()

View File

@@ -50,7 +50,7 @@ class VMwareVMWizard(VMWizard, Ui_VMwareVMWizard):
if super().validateCurrentPage() is False:
return False
if self.currentPage() == self.uiVirtualBoxWizardPage:
if self.currentPage() == self.uiVMwareWizardPage:
if not self.uiVMListComboBox.count():
QtWidgets.QMessageBox.critical(self, "VMware VMs", "There is no VMware VM available!")
return False
@@ -59,7 +59,7 @@ class VMwareVMWizard(VMWizard, Ui_VMwareVMWizard):
def initializePage(self, page_id):
super().initializePage(page_id)
if self.page(page_id) == self.uiVirtualBoxWizardPage:
if self.page(page_id) == self.uiVMwareWizardPage:
self.uiVMListComboBox.clear()
Controller.instance().getCompute("/vmware/vms", self._compute_id, self._getVMwareVMsFromServerCallback, progressText="Listing VMware VMs...")

View File

@@ -22,6 +22,7 @@ Configuration page for VMware VMs.
from gns3.qt import QtWidgets
from gns3.dialogs.node_properties_dialog import ConfigurationError
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.ports.port_name_factory import StandardPortNameFactory
from gns3.node import Node
from ..ui.vmware_vm_configuration_page_ui import Ui_VMwareVMConfigPageWidget
@@ -175,21 +176,21 @@ class VMwareVMConfigurationPage(QtWidgets.QWidget, Ui_VMwareVMConfigPageWidget):
symbol_path = self.uiSymbolLineEdit.text()
settings["symbol"] = symbol_path
settings["category"] = self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex())
port_name_format = self.uiPortNameFormatLineEdit.text()
if '{0}' not in port_name_format and '{port0}' not in port_name_format and '{port1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}, {port0} or {port1}")
else:
settings["port_name_format"] = self.uiPortNameFormatLineEdit.text()
port_segment_size = self.uiPortSegmentSizeSpinBox.value()
if port_segment_size and '{1}' not in port_name_format and '{segment0}' not in port_name_format and '{segment1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "If the segment size is not 0, the format must contain {1}, {segment0} or {segment1}")
else:
settings["port_segment_size"] = port_segment_size
first_port_name = self.uiFirstPortNameLineEdit.text().strip()
settings["first_port_name"] = self.uiFirstPortNameLineEdit.text().strip()
try:
StandardPortNameFactory(self.uiAdaptersSpinBox.value(), first_port_name, port_name_format, port_segment_size)
except (ValueError, KeyError):
QtWidgets.QMessageBox.critical(self, "Invalid format", "Invalid port name format")
raise ConfigurationError()
settings["port_name_format"] = self.uiPortNameFormatLineEdit.text()
settings["port_segment_size"] = port_segment_size
settings["first_port_name"] = first_port_name
settings["adapter_type"] = self.uiAdapterTypesComboBox.currentText()
settings["use_any_adapter"] = self.uiUseAnyAdapterCheckBox.isChecked()

View File

@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>598</width>
<width>755</width>
<height>453</height>
</rect>
</property>
@@ -81,7 +81,7 @@
</item>
</layout>
</widget>
<widget class="QWizardPage" name="uiVirtualBoxWizardPage">
<widget class="QWizardPage" name="uiVMwareWizardPage">
<property name="title">
<string>VMware Virtual Machine</string>
</property>

View File

@@ -2,8 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/vmware/ui/vmware_vm_wizard.ui'
#
# Created: Tue Sep 20 17:45:46 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!
@@ -12,7 +11,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_VMwareVMWizard(object):
def setupUi(self, VMwareVMWizard):
VMwareVMWizard.setObjectName("VMwareVMWizard")
VMwareVMWizard.resize(598, 453)
VMwareVMWizard.resize(755, 453)
VMwareVMWizard.setModal(True)
self.uiServerWizardPage = QtWidgets.QWizardPage()
self.uiServerWizardPage.setObjectName("uiServerWizardPage")
@@ -48,14 +47,14 @@ class Ui_VMwareVMWizard(object):
self.gridLayout_8.addWidget(self.uiRemoteServersLabel, 0, 0, 1, 1)
self.gridLayout_2.addWidget(self.uiRemoteServersGroupBox, 1, 0, 1, 1)
VMwareVMWizard.addPage(self.uiServerWizardPage)
self.uiVirtualBoxWizardPage = QtWidgets.QWizardPage()
self.uiVirtualBoxWizardPage.setObjectName("uiVirtualBoxWizardPage")
self.gridLayout = QtWidgets.QGridLayout(self.uiVirtualBoxWizardPage)
self.uiVMwareWizardPage = QtWidgets.QWizardPage()
self.uiVMwareWizardPage.setObjectName("uiVMwareWizardPage")
self.gridLayout = QtWidgets.QGridLayout(self.uiVMwareWizardPage)
self.gridLayout.setObjectName("gridLayout")
self.uiVMListLabel = QtWidgets.QLabel(self.uiVirtualBoxWizardPage)
self.uiVMListLabel = QtWidgets.QLabel(self.uiVMwareWizardPage)
self.uiVMListLabel.setObjectName("uiVMListLabel")
self.gridLayout.addWidget(self.uiVMListLabel, 0, 0, 1, 1)
self.uiVMListComboBox = QtWidgets.QComboBox(self.uiVirtualBoxWizardPage)
self.uiVMListComboBox = QtWidgets.QComboBox(self.uiVMwareWizardPage)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@@ -63,11 +62,11 @@ class Ui_VMwareVMWizard(object):
self.uiVMListComboBox.setSizePolicy(sizePolicy)
self.uiVMListComboBox.setObjectName("uiVMListComboBox")
self.gridLayout.addWidget(self.uiVMListComboBox, 0, 1, 1, 1)
self.uiBaseVMCheckBox = QtWidgets.QCheckBox(self.uiVirtualBoxWizardPage)
self.uiBaseVMCheckBox = QtWidgets.QCheckBox(self.uiVMwareWizardPage)
self.uiBaseVMCheckBox.setEnabled(True)
self.uiBaseVMCheckBox.setObjectName("uiBaseVMCheckBox")
self.gridLayout.addWidget(self.uiBaseVMCheckBox, 1, 0, 1, 2)
VMwareVMWizard.addPage(self.uiVirtualBoxWizardPage)
VMwareVMWizard.addPage(self.uiVMwareWizardPage)
self.retranslateUi(VMwareVMWizard)
QtCore.QMetaObject.connectSlotsByName(VMwareVMWizard)
@@ -82,8 +81,8 @@ class Ui_VMwareVMWizard(object):
self.uiLocalRadioButton.setText(_translate("VMwareVMWizard", "Run this VMware VM on my local computer"))
self.uiRemoteServersGroupBox.setTitle(_translate("VMwareVMWizard", "Remote servers"))
self.uiRemoteServersLabel.setText(_translate("VMwareVMWizard", "Run on server:"))
self.uiVirtualBoxWizardPage.setTitle(_translate("VMwareVMWizard", "VMware Virtual Machine"))
self.uiVirtualBoxWizardPage.setSubTitle(_translate("VMwareVMWizard", "Please choose a VMware virtual machine from the list."))
self.uiVMwareWizardPage.setTitle(_translate("VMwareVMWizard", "VMware Virtual Machine"))
self.uiVMwareWizardPage.setSubTitle(_translate("VMwareVMWizard", "Please choose a VMware virtual machine from the list."))
self.uiVMListLabel.setText(_translate("VMwareVMWizard", "VM list:"))
self.uiBaseVMCheckBox.setText(_translate("VMwareVMWizard", "Use as a linked base VM (experimental)"))

View File

@@ -77,10 +77,10 @@ class VPCS(Module):
# save the settings
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
server_settings = copy.copy(self._settings)
if server_settings["vpcs_path"]:
server_settings = {}
if self._settings["vpcs_path"]:
# save some settings to the server config file
server_settings["vpcs_path"] = os.path.normpath(server_settings["vpcs_path"])
server_settings["vpcs_path"] = os.path.normpath(self._settings["vpcs_path"])
config = LocalServerConfig.instance()
config.saveSettings(self.__class__.__name__, server_settings)

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):
"""
@@ -263,6 +258,7 @@ class Node(BaseNode):
node_id=self._node_id),
self._duplicateCallback,
body=body,
progressText="Duplicating node {}...".format(self.name()),
timeout=None)
def _duplicateCallback(self, result, error=False, **kwargs):
@@ -355,7 +351,6 @@ class Node(BaseNode):
return False
result = self._parseResponse(result)
self._created = True
self._createCallback(result)
if self._loading:
@@ -386,6 +381,8 @@ class Node(BaseNode):
"""
if not skip_controller:
for link in self.links():
link.setDeleting()
self.controllerHttpDelete("/nodes/{node_id}".format(node_id=self._node_id), self._deleteCallback)
else:
self.deleted_signal.emit()

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

@@ -316,8 +316,10 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
self.uiSceneWidthSpinBox.setValue(settings["scene_width"])
self.uiSceneHeightSpinBox.setValue(settings["scene_height"])
self.uiGridSizeSpinBox.setValue(settings["grid_size"])
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"]):
@@ -378,8 +380,10 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
new_graphics_view_settings = {"scene_width": self.uiSceneWidthSpinBox.value(),
"scene_height": self.uiSceneHeightSpinBox.value(),
"grid_size": self.uiGridSizeSpinBox.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

@@ -0,0 +1,61 @@
#!/usr/bin/env python
#
# 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 logging
log = logging.getLogger(__name__)
class StandardPortNameFactory:
"""
Generate default port names.
"""
def __new__(cls, ethernet_adapters, first_port_name, port_name_format, port_segment_size):
ports = []
adapter_number = interface_number = segment_number = 0
for adapter_number in range(adapter_number, ethernet_adapters + adapter_number):
if first_port_name and adapter_number == 0:
port_name = first_port_name
else:
port_name = port_name_format.format(interface_number,
segment_number,
adapter=adapter_number,
**cls._generate_replacement(interface_number, segment_number))
interface_number += 1
if port_segment_size:
if interface_number % port_segment_size == 0:
segment_number += 1
interface_number = 0
else:
segment_number += 1
ports.append(port_name)
return ports
@staticmethod
def _generate_replacement(interface_number, segment_number):
"""
This will generate replacement string for
{port0} => {port9}
{segment0} => {segment9}
"""
replacements = {}
for i in range(0, 9):
replacements["port" + str(i)] = interface_number + i
replacements["segment" + str(i)] = segment_number + i
return replacements

View File

@@ -49,6 +49,7 @@ class Project(QtCore.QObject):
# Called when project is fully loaded
project_loaded_signal = QtCore.Signal()
def __init__(self):
self._id = None
@@ -60,14 +61,20 @@ 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)
self._show_layers = graphic_settings.get("show_layers", False)
self._snap_to_grid = graphic_settings.get("snap_to_grid", False)
self._show_grid = graphic_settings.get("show_grid", False)
self._grid_size = graphic_settings.get("grid_size", 75)
self._show_interface_labels = graphic_settings.get("show_interface_labels", False)
self._show_interface_labels_on_new_project = config.showInterfaceLabelsOnNewProject()
self._variables = None
self._supplier = None
self._name = "untitled"
self._filename = None
@@ -176,6 +183,21 @@ class Project(QtCore.QObject):
"""
return self._show_grid
def setGridSize(self, grid_size):
"""
Sets the grid size
"""
self._grid_size = grid_size
def gridSize(self):
"""
Returns the grid size
:return: integer
"""
return self._grid_size
def setShowInterfaceLabels(self, show_interface_labels):
"""
Sets show interface labels mode
@@ -189,6 +211,32 @@ class Project(QtCore.QObject):
"""
return self._show_interface_labels
def setVariables(self, variables):
"""
Sets variables of project
"""
self._variables = variables
def variables(self):
"""
Returns variables assigned to the project
:return: boolean
"""
return self._variables
def setSupplier(self, supplier):
"""
Sets supplier of project
"""
self._supplier = supplier
def supplier(self):
"""
Returns supplier
:return: boolean
"""
return self._supplier
def setName(self, name):
"""
Set project name
@@ -265,7 +313,11 @@ class Project(QtCore.QObject):
"""
Duplicate a project
"""
Controller.instance().post("/projects/{project_id}/duplicate".format(project_id=self._id), qpartial(self._duplicateCallback, callback), body={"name": name, "path": path}, timeout=None)
Controller.instance().post("/projects/{project_id}/duplicate".format(project_id=self._id),
qpartial(self._duplicateCallback, callback),
body={"name": name, "path": path},
progressText="Duplicating project '{}'...".format(name),
timeout=None)
def _duplicateCallback(self, callback, result, error=False, **kwargs):
if error:
@@ -373,7 +425,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)
@@ -392,7 +445,10 @@ class Project(QtCore.QObject):
"show_layers": self._show_layers,
"snap_to_grid": self._snap_to_grid,
"show_grid": self._show_grid,
"show_interface_labels": self._show_interface_labels
"grid_size": self._grid_size,
"show_interface_labels": self._show_interface_labels,
"variables": self._variables,
"supplier": self._supplier
}
self.put("", self._projectUpdatedCallback, body=body)
@@ -412,7 +468,9 @@ class Project(QtCore.QObject):
self._closed = False
self._closing = False
self._startListenNotifications()
self.project_updated_signal.emit()
self.project_loaded_signal.emit()
def _parseResponse(self, result):
"""
@@ -431,6 +489,12 @@ class Project(QtCore.QObject):
self._show_layers = result.get("show_layers", False)
self._snap_to_grid = result.get("snap_to_grid", False)
self._show_grid = result.get("show_grid", False)
self._variables = result.get("variables", None)
self._supplier = result.get("supplier", None)
grid_size = result.get("grid_size", None)
if grid_size:
self._grid_size = grid_size
self._show_interface_labels = result.get("show_interface_labels", False)
def load(self, path=None):

View File

@@ -60,6 +60,14 @@ class QImageSvgRenderer(QtSvg.QSvgRenderer):
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():
if not path_or_data.startswith(":") and path_exists:
try:
with open(path_or_data, "rb") as f:
self._svg = f.read().decode()
except UnicodeError as e:
log.error("Could not decode '{}' content: {}".format(path_or_data, e))
except OSError as e:
log.error("Could not read '{}': {}".format(path_or_data, e))
return res
except ET.ParseError:
pass

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":
@@ -167,6 +174,7 @@ class Config:
new_config["console_type"] = appliance_config["docker"].get("console_type", "telnet")
new_config["console_http_port"] = appliance_config["docker"].get("console_http_port", 80)
new_config["console_http_path"] = appliance_config["docker"].get("console_http_path", "/")
new_config["extra_hosts"] = appliance_config["docker"].get("extra_hosts", "")
self._config["Docker"]["containers"].append(new_config)
def _add_dynamips_config(self, new_config, appliance_config):
@@ -276,9 +284,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 +297,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

@@ -97,7 +97,7 @@ class Image:
if os.path.exists(self.path + ".md5sum"):
with open(self.path + ".md5sum", encoding="utf-8") as f:
self._md5sum = f.read()
self._md5sum = f.read().strip()
return self._md5sum
if not os.path.isfile(self.path):

View File

@@ -131,6 +131,10 @@
"console_http_path": {
"description": "Path of the web interface",
"type": "string"
},
"extra_hosts": {
"description": "Hosts which will be written to /etc/hosts into container" ,
"type": "string"
}
},
"required": [

View File

@@ -281,6 +281,7 @@ NODES_VIEW_SETTINGS = {
GRAPHICS_VIEW_SETTINGS = {
"scene_width": 2000,
"scene_height": 1000,
"grid_size": 75,
"draw_rectangle_selected_item": False,
"draw_link_status_points": True,
"default_label_font": "TypeWriter,10,-1,5,75,0,0,0,0,0",
@@ -289,7 +290,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,6 +30,7 @@ from .utils.progress_dialog import ProgressDialog
from .utils.export_project_worker import ExportProjectWorker
from .utils.import_project_worker import ImportProjectWorker
from .dialogs.file_editor_dialog import FileEditorDialog
from .dialogs.project_welcome_dialog import ProjectWelcomeDialog
from .modules import MODULES
from .modules.module_error import ModuleError
@@ -134,12 +135,16 @@ class Topology(QtCore.QObject):
self.project_changed_signal.emit()
def _projectUpdatedSlot(self):
if not self._project or not self._project.filesDir() or not self._project.filename():
return
self._main_window.setWindowTitle("{name} - GNS3".format(name=self._project.name()))
project_file = os.path.join(self._project.filesDir(), self._project.filename())
self._main_window.uiGraphicsView.setSceneSize(self._project.sceneWidth(), self._project.sceneHeight())
self._main_window.uiGraphicsView.setGridSize(self._project.gridSize())
self._main_window.uiShowGridAction.setChecked(self._project.showGrid())
self._main_window.showGrid(self._project.showGrid())
if os.path.exists(project_file):
self._main_window.updateRecentFileSettings(project_file)
self._main_window.updateRecentFileActions()
@@ -153,6 +158,7 @@ class Topology(QtCore.QObject):
self._main_window.uiShowLayersAction.setChecked(self._project.showLayers())
self._main_window.showLayers(self._project.showLayers())
self._main_window.uiGraphicsView.setGridSize(self._project.gridSize())
self._main_window.uiShowGridAction.setChecked(self._project.showGrid())
self._main_window.showGrid(self._project.showGrid())
@@ -164,12 +170,29 @@ class Topology(QtCore.QObject):
self._main_window.uiGraphicsView.setZoom(self._project.zoom())
supplier = self._project.supplier()
if supplier:
self._main_window.uiGraphicsView.addLogo(
supplier.get('logo', None),
supplier.get('url', None)
)
self._displayProjectWelcomeDialog()
def _displayProjectWelcomeDialog(self):
variables = self.project().variables()
if variables:
missing = [v for v in variables if v.get("value", "").strip() == ""]
if len(missing) > 0:
dialog = ProjectWelcomeDialog(self._main_window, self.project())
dialog.show()
dialog.exec_()
def createLoadProject(self, project_settings):
"""
Create load a project based on settings, not on the .gns3
"""
self.setProject(None)
from .project import Project
project = Project()

View File

@@ -99,7 +99,7 @@ class TopologyNodeItem(QtWidgets.QTreeWidgetItem):
if self._node.consoleType() and self._node.console():
self.setText(1, "{} {}:{}".format(self._node.consoleType(), self._node.consoleHost(), self._node.console()))
else:
self.setText(1, "not supported")
self.setText(1, "none")
self.refreshLinks()
self._parent.invisibleRootItem().sortChildren(0, QtCore.Qt.AscendingOrder)

View File

@@ -9,8 +9,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>407</width>
<height>249</height>
<width>465</width>
<height>272</height>
</rect>
</property>
<property name="windowTitle">
@@ -106,21 +106,21 @@ p, li { white-space: pre-wrap; }
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Noto Sans'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:600;&quot;&gt;Developers&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-size:11pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:11pt;&quot;&gt;Jeremy Grossmann&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:11pt;&quot;&gt;Julien Duponchelle&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-size:11pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:600;&quot;&gt;Contributors&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-size:11pt; font-weight:600;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:11pt;&quot;&gt;James E. Carpenter (IOU support)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:11pt;&quot;&gt;Daniel Lintott (NET file import)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:11pt;&quot;&gt;Marc Weisel (Mac OS X packaging)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:11pt;&quot;&gt;Alexey Eromenko (VirtualBox support)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:11pt;&quot;&gt;Vasil Rangelov (Qemu &amp;amp; GUI improvements)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:11pt;&quot;&gt;Bernhard Ehlers (IOU NVRAM import/export)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-size:11pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Developers&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Jeremy Grossmann&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Julien Duponchelle&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Contributors&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-weight:600;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;James E. Carpenter (IOU support)&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Daniel Lintott (NET file import)&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Marc Weisel (Mac OS X packaging)&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Alexey Eromenko (VirtualBox support)&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Vasil Rangelov (Qemu &amp;amp; GUI improvements)&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Bernhard Ehlers (IOU NVRAM import/export)&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextBrowserInteraction</set>
@@ -143,7 +143,8 @@ p, li { white-space: pre-wrap; }
<bool>true</bool>
</property>
<property name="plainText">
<string>James Borden
<string>Mark Fahy
James Borden
Tenzin Rigdol Oshoe
Brian Jacobson
Chad Hoevenaars

File diff suppressed because it is too large Load Diff

View File

@@ -9,8 +9,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>549</width>
<height>234</height>
<width>955</width>
<height>387</height>
</rect>
</property>
<property name="windowTitle">
@@ -21,99 +21,126 @@
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="uiProjectNameLabel">
<property name="text">
<string>Project Name:</string>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="uiGeneralTab">
<attribute name="title">
<string>General</string>
</attribute>
<layout class="QGridLayout" name="uiGeneralGrid">
<item row="2" column="0">
<widget class="QLabel" name="uiSceneWidthLabel">
<property name="text">
<string>Scene width:</string>
</property>
</widget>
</item>
<item row="9" column="0" colspan="3">
<widget class="QCheckBox" name="uiProjectAutoCloseCheckBox">
<property name="text">
<string>Leave this project running in the background when closing GNS3</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiProjectNameLabel">
<property name="text">
<string>Project Name:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="uiSceneWidthSpinBox">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>500</number>
</property>
<property name="maximum">
<number>1000000</number>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="uiGridSizeSpinBox"/>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="uiSceneHeightSpinBox">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>500</number>
</property>
<property name="maximum">
<number>1000000</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="uiSceneHeightLabel">
<property name="text">
<string>Scene height:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="uiProjectNameLineEdit"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="uiGridSizeLabel">
<property name="text">
<string>Grid size:</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="3">
<widget class="QCheckBox" name="uiProjectAutoOpenCheckBox">
<property name="text">
<string>Open this project in the background when GNS3 server starts</string>
</property>
</widget>
</item>
<item row="8" column="0" colspan="3">
<widget class="QCheckBox" name="uiProjectAutoStartCheckBox">
<property name="text">
<string>Start all nodes when this project is opened</string>
</property>
</widget>
</item>
<item row="10" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="uiGlobalVariablesTab">
<attribute name="title">
<string>Global variables</string>
</attribute>
<layout class="QGridLayout" name="uiGlobalVariablesGrid"/>
</widget>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="uiProjectNameLineEdit"/>
</item>
<item row="5" column="0" colspan="3">
<widget class="QCheckBox" name="uiProjectAutoCloseCheckBox">
<property name="text">
<string>Leave this project running in the background when closing GNS3</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="uiSceneHeightSpinBox">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>500</number>
</property>
<property name="maximum">
<number>1000000</number>
</property>
</widget>
</item>
<item row="3" column="0" colspan="3">
<widget class="QCheckBox" name="uiProjectAutoOpenCheckBox">
<property name="text">
<string>Open this project in the background when GNS3 server starts</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="3">
<widget class="QCheckBox" name="uiProjectAutoStartCheckBox">
<property name="text">
<string>Start all nodes when this project is opened</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="uiSceneWidthSpinBox">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>500</number>
</property>
<property name="maximum">
<number>1000000</number>
</property>
</widget>
</item>
<item row="6" column="0" colspan="3">
<item row="1" column="0">
<widget class="QDialogButtonBox" name="uiButtonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiSceneWidthLabel">
<property name="text">
<string>Scene width:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="uiSceneHeightLabel">
<property name="text">
<string>Scene height:</string>
</property>
</widget>
</item>
<item row="7" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>

View File

@@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/edit_project_dialog.ui'
# Form implementation generated from reading ui file '/home/dominik/projects/gns3-gui/gns3/ui/edit_project_dialog.ui'
#
# Created: Sat Oct 8 14:06:27 2016
# by: PyQt5 UI code generator 5.2.1
# Created by: PyQt5 UI code generator 5.8.2
#
# WARNING! All changes made in this file will be lost!
@@ -13,50 +12,69 @@ class Ui_EditProjectDialog(object):
def setupUi(self, EditProjectDialog):
EditProjectDialog.setObjectName("EditProjectDialog")
EditProjectDialog.setWindowModality(QtCore.Qt.ApplicationModal)
EditProjectDialog.resize(549, 234)
EditProjectDialog.resize(955, 387)
EditProjectDialog.setModal(True)
self.gridLayout = QtWidgets.QGridLayout(EditProjectDialog)
self.gridLayout.setObjectName("gridLayout")
self.uiProjectNameLabel = QtWidgets.QLabel(EditProjectDialog)
self.uiProjectNameLabel.setObjectName("uiProjectNameLabel")
self.gridLayout.addWidget(self.uiProjectNameLabel, 0, 0, 1, 1)
self.uiProjectNameLineEdit = QtWidgets.QLineEdit(EditProjectDialog)
self.uiProjectNameLineEdit.setObjectName("uiProjectNameLineEdit")
self.gridLayout.addWidget(self.uiProjectNameLineEdit, 0, 1, 1, 1)
self.uiProjectAutoCloseCheckBox = QtWidgets.QCheckBox(EditProjectDialog)
self.tabWidget = QtWidgets.QTabWidget(EditProjectDialog)
self.tabWidget.setObjectName("tabWidget")
self.uiGeneralTab = QtWidgets.QWidget()
self.uiGeneralTab.setObjectName("uiGeneralTab")
self.uiGeneralGrid = QtWidgets.QGridLayout(self.uiGeneralTab)
self.uiGeneralGrid.setObjectName("uiGeneralGrid")
self.uiSceneWidthLabel = QtWidgets.QLabel(self.uiGeneralTab)
self.uiSceneWidthLabel.setObjectName("uiSceneWidthLabel")
self.uiGeneralGrid.addWidget(self.uiSceneWidthLabel, 2, 0, 1, 1)
self.uiProjectAutoCloseCheckBox = QtWidgets.QCheckBox(self.uiGeneralTab)
self.uiProjectAutoCloseCheckBox.setObjectName("uiProjectAutoCloseCheckBox")
self.gridLayout.addWidget(self.uiProjectAutoCloseCheckBox, 5, 0, 1, 3)
self.uiSceneHeightSpinBox = QtWidgets.QSpinBox(EditProjectDialog)
self.uiSceneHeightSpinBox.setMinimum(500)
self.uiSceneHeightSpinBox.setMaximum(1000000)
self.uiSceneHeightSpinBox.setObjectName("uiSceneHeightSpinBox")
self.gridLayout.addWidget(self.uiSceneHeightSpinBox, 2, 1, 1, 1)
self.uiProjectAutoOpenCheckBox = QtWidgets.QCheckBox(EditProjectDialog)
self.uiProjectAutoOpenCheckBox.setObjectName("uiProjectAutoOpenCheckBox")
self.gridLayout.addWidget(self.uiProjectAutoOpenCheckBox, 3, 0, 1, 3)
self.uiProjectAutoStartCheckBox = QtWidgets.QCheckBox(EditProjectDialog)
self.uiProjectAutoStartCheckBox.setObjectName("uiProjectAutoStartCheckBox")
self.gridLayout.addWidget(self.uiProjectAutoStartCheckBox, 4, 0, 1, 3)
self.uiSceneWidthSpinBox = QtWidgets.QSpinBox(EditProjectDialog)
self.uiGeneralGrid.addWidget(self.uiProjectAutoCloseCheckBox, 9, 0, 1, 3)
self.uiProjectNameLabel = QtWidgets.QLabel(self.uiGeneralTab)
self.uiProjectNameLabel.setObjectName("uiProjectNameLabel")
self.uiGeneralGrid.addWidget(self.uiProjectNameLabel, 1, 0, 1, 1)
self.uiSceneWidthSpinBox = QtWidgets.QSpinBox(self.uiGeneralTab)
self.uiSceneWidthSpinBox.setMinimum(500)
self.uiSceneWidthSpinBox.setMaximum(1000000)
self.uiSceneWidthSpinBox.setObjectName("uiSceneWidthSpinBox")
self.gridLayout.addWidget(self.uiSceneWidthSpinBox, 1, 1, 1, 1)
self.uiGeneralGrid.addWidget(self.uiSceneWidthSpinBox, 2, 1, 1, 1)
self.uiGridSizeSpinBox = QtWidgets.QSpinBox(self.uiGeneralTab)
self.uiGridSizeSpinBox.setObjectName("uiGridSizeSpinBox")
self.uiGeneralGrid.addWidget(self.uiGridSizeSpinBox, 4, 1, 1, 1)
self.uiSceneHeightSpinBox = QtWidgets.QSpinBox(self.uiGeneralTab)
self.uiSceneHeightSpinBox.setMinimum(500)
self.uiSceneHeightSpinBox.setMaximum(1000000)
self.uiSceneHeightSpinBox.setObjectName("uiSceneHeightSpinBox")
self.uiGeneralGrid.addWidget(self.uiSceneHeightSpinBox, 3, 1, 1, 1)
self.uiSceneHeightLabel = QtWidgets.QLabel(self.uiGeneralTab)
self.uiSceneHeightLabel.setObjectName("uiSceneHeightLabel")
self.uiGeneralGrid.addWidget(self.uiSceneHeightLabel, 3, 0, 1, 1)
self.uiProjectNameLineEdit = QtWidgets.QLineEdit(self.uiGeneralTab)
self.uiProjectNameLineEdit.setObjectName("uiProjectNameLineEdit")
self.uiGeneralGrid.addWidget(self.uiProjectNameLineEdit, 1, 1, 1, 1)
self.uiGridSizeLabel = QtWidgets.QLabel(self.uiGeneralTab)
self.uiGridSizeLabel.setObjectName("uiGridSizeLabel")
self.uiGeneralGrid.addWidget(self.uiGridSizeLabel, 4, 0, 1, 1)
self.uiProjectAutoOpenCheckBox = QtWidgets.QCheckBox(self.uiGeneralTab)
self.uiProjectAutoOpenCheckBox.setObjectName("uiProjectAutoOpenCheckBox")
self.uiGeneralGrid.addWidget(self.uiProjectAutoOpenCheckBox, 7, 0, 1, 3)
self.uiProjectAutoStartCheckBox = QtWidgets.QCheckBox(self.uiGeneralTab)
self.uiProjectAutoStartCheckBox.setObjectName("uiProjectAutoStartCheckBox")
self.uiGeneralGrid.addWidget(self.uiProjectAutoStartCheckBox, 8, 0, 1, 3)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.uiGeneralGrid.addItem(spacerItem, 10, 0, 1, 1)
self.tabWidget.addTab(self.uiGeneralTab, "")
self.uiGlobalVariablesTab = QtWidgets.QWidget()
self.uiGlobalVariablesTab.setObjectName("uiGlobalVariablesTab")
self.uiGlobalVariablesGrid = QtWidgets.QGridLayout(self.uiGlobalVariablesTab)
self.uiGlobalVariablesGrid.setObjectName("uiGlobalVariablesGrid")
self.tabWidget.addTab(self.uiGlobalVariablesTab, "")
self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 1)
self.uiButtonBox = QtWidgets.QDialogButtonBox(EditProjectDialog)
self.uiButtonBox.setOrientation(QtCore.Qt.Horizontal)
self.uiButtonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.uiButtonBox.setObjectName("uiButtonBox")
self.gridLayout.addWidget(self.uiButtonBox, 6, 0, 1, 3)
self.uiSceneWidthLabel = QtWidgets.QLabel(EditProjectDialog)
self.uiSceneWidthLabel.setObjectName("uiSceneWidthLabel")
self.gridLayout.addWidget(self.uiSceneWidthLabel, 1, 0, 1, 1)
self.uiSceneHeightLabel = QtWidgets.QLabel(EditProjectDialog)
self.uiSceneHeightLabel.setObjectName("uiSceneHeightLabel")
self.gridLayout.addWidget(self.uiSceneHeightLabel, 2, 0, 1, 1)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem, 7, 0, 1, 1)
self.gridLayout.addWidget(self.uiButtonBox, 1, 0, 1, 1)
self.retranslateUi(EditProjectDialog)
self.tabWidget.setCurrentIndex(0)
self.uiButtonBox.accepted.connect(EditProjectDialog.accept)
self.uiButtonBox.rejected.connect(EditProjectDialog.reject)
QtCore.QMetaObject.connectSlotsByName(EditProjectDialog)
@@ -64,12 +82,15 @@ class Ui_EditProjectDialog(object):
def retranslateUi(self, EditProjectDialog):
_translate = QtCore.QCoreApplication.translate
EditProjectDialog.setWindowTitle(_translate("EditProjectDialog", "Edit project"))
self.uiProjectNameLabel.setText(_translate("EditProjectDialog", "Project Name:"))
self.uiSceneWidthLabel.setText(_translate("EditProjectDialog", "Scene width:"))
self.uiProjectAutoCloseCheckBox.setText(_translate("EditProjectDialog", "Leave this project running in the background when closing GNS3"))
self.uiProjectNameLabel.setText(_translate("EditProjectDialog", "Project Name:"))
self.uiSceneWidthSpinBox.setSuffix(_translate("EditProjectDialog", " px"))
self.uiSceneHeightSpinBox.setSuffix(_translate("EditProjectDialog", " px"))
self.uiSceneHeightLabel.setText(_translate("EditProjectDialog", "Scene height:"))
self.uiGridSizeLabel.setText(_translate("EditProjectDialog", "Grid size:"))
self.uiProjectAutoOpenCheckBox.setText(_translate("EditProjectDialog", "Open this project in the background when GNS3 server starts"))
self.uiProjectAutoStartCheckBox.setText(_translate("EditProjectDialog", "Start all nodes when this project is opened"))
self.uiSceneWidthSpinBox.setSuffix(_translate("EditProjectDialog", " px"))
self.uiSceneWidthLabel.setText(_translate("EditProjectDialog", "Scene width:"))
self.uiSceneHeightLabel.setText(_translate("EditProjectDialog", "Scene height:"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.uiGeneralTab), _translate("EditProjectDialog", "General"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.uiGlobalVariablesTab), _translate("EditProjectDialog", "Global variables"))

View File

@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>1324</width>
<height>738</height>
<height>741</height>
</rect>
</property>
<property name="windowTitle">
@@ -647,7 +647,24 @@
<property name="margin">
<number>10</number>
</property>
<item row="9" column="0" colspan="2">
<item row="6" 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="11" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QPushButton" name="uiDefaultLabelFontPushButton">
@@ -678,13 +695,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 +702,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 +721,58 @@
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="uiLabelPreviewLabel">
<property name="text">
<string>Default label style:</string>
</property>
</widget>
</item>
<item row="7" 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="10" 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="12" 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,23 +792,33 @@
</property>
</widget>
</item>
<item row="10" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
<item row="8" column="0">
<widget class="QCheckBox" name="uiShowInterfaceLabelsOnNewProject">
<property name="text">
<string>Show interface labels on new project</string>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>5</height>
</size>
</property>
</spacer>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_2">
<widget class="QLabel" name="uiGridSizeLabel">
<property name="text">
<string>If you want to change the size of the current project. Via the project menu you can edit it.</string>
<string>Grid size:</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QSpinBox" name="uiGridSizeSpinBox">
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
<property name="value">
<number>75</number>
</property>
</widget>
</item>

View File

@@ -11,7 +11,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_GeneralPreferencesPageWidget(object):
def setupUi(self, GeneralPreferencesPageWidget):
GeneralPreferencesPageWidget.setObjectName("GeneralPreferencesPageWidget")
GeneralPreferencesPageWidget.resize(1324, 738)
GeneralPreferencesPageWidget.resize(1324, 741)
self.verticalLayout = QtWidgets.QVBoxLayout(GeneralPreferencesPageWidget)
self.verticalLayout.setObjectName("verticalLayout")
self.uiMiscTabWidget = QtWidgets.QTabWidget(GeneralPreferencesPageWidget)
@@ -307,6 +307,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, 6, 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 +324,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, 11, 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, 9, 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.gridLayout_8.addWidget(self.uiDrawLinkStatusPointsCheckBox, 7, 0, 1, 1)
self.uiDefaultLabelStylePlainTextEdit = QtWidgets.QPlainTextEdit(self.uiSceneTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
@@ -344,14 +351,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, 10, 0, 1, 2)
spacerItem7 = QtWidgets.QSpacerItem(20, 5, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_8.addItem(spacerItem7, 12, 0, 1, 1)
self.uiSceneWidthSpinBox = QtWidgets.QSpinBox(self.uiSceneTab)
self.uiSceneWidthSpinBox.setMinimum(500)
self.uiSceneWidthSpinBox.setMaximum(1000000)
@@ -359,11 +361,19 @@ 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, 8, 0, 1, 1)
self.uiGridSizeLabel = QtWidgets.QLabel(self.uiSceneTab)
self.uiGridSizeLabel.setObjectName("uiGridSizeLabel")
self.gridLayout_8.addWidget(self.uiGridSizeLabel, 4, 0, 1, 1)
self.uiGridSizeSpinBox = QtWidgets.QSpinBox(self.uiSceneTab)
self.uiGridSizeSpinBox.setMinimum(10)
self.uiGridSizeSpinBox.setMaximum(100)
self.uiGridSizeSpinBox.setSingleStep(10)
self.uiGridSizeSpinBox.setProperty("value", 75)
self.uiGridSizeSpinBox.setObjectName("uiGridSizeSpinBox")
self.gridLayout_8.addWidget(self.uiGridSizeSpinBox, 5, 0, 1, 2)
self.uiMiscTabWidget.addTab(self.uiSceneTab, "")
self.uiMiscTab = QtWidgets.QWidget()
self.uiMiscTab.setObjectName("uiMiscTab")
@@ -491,17 +501,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.uiGridSizeLabel.setText(_translate("GeneralPreferencesPageWidget", "Grid size:"))
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

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ProjectWelcomeDialog</class>
<widget class="QDialog" name="ProjectWelcomeDialog">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>607</width>
<height>308</height>
</rect>
</property>
<property name="windowTitle">
<string>Welcome</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="rightMargin">
<number>12</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Loading.. Please wait.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>20</number>
</property>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="uiOkButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="../../resources/resources.qrc"/>
</resources>
<connections/>
</ui>

View File

@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/dominik/projects/gns3-gui/gns3/ui/project_welcome_dialog.ui'
#
# Created by: PyQt5 UI code generator 5.8.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_ProjectWelcomeDialog(object):
def setupUi(self, ProjectWelcomeDialog):
ProjectWelcomeDialog.setObjectName("ProjectWelcomeDialog")
ProjectWelcomeDialog.setWindowModality(QtCore.Qt.WindowModal)
ProjectWelcomeDialog.resize(607, 308)
ProjectWelcomeDialog.setModal(True)
self.verticalLayout = QtWidgets.QVBoxLayout(ProjectWelcomeDialog)
self.verticalLayout.setContentsMargins(-1, -1, 12, -1)
self.verticalLayout.setObjectName("verticalLayout")
self.label = QtWidgets.QLabel(ProjectWelcomeDialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
self.label.setSizePolicy(sizePolicy)
self.label.setTextFormat(QtCore.Qt.RichText)
self.label.setScaledContents(False)
self.label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.label.setWordWrap(True)
self.label.setObjectName("label")
self.verticalLayout.addWidget(self.label)
self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setObjectName("gridLayout")
self.verticalLayout.addLayout(self.gridLayout)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setSpacing(20)
self.horizontalLayout.setObjectName("horizontalLayout")
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.uiOkButton = QtWidgets.QDialogButtonBox(ProjectWelcomeDialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiOkButton.sizePolicy().hasHeightForWidth())
self.uiOkButton.setSizePolicy(sizePolicy)
self.uiOkButton.setOrientation(QtCore.Qt.Horizontal)
self.uiOkButton.setStandardButtons(QtWidgets.QDialogButtonBox.Ok)
self.uiOkButton.setObjectName("uiOkButton")
self.horizontalLayout.addWidget(self.uiOkButton)
self.verticalLayout.addLayout(self.horizontalLayout)
self.retranslateUi(ProjectWelcomeDialog)
QtCore.QMetaObject.connectSlotsByName(ProjectWelcomeDialog)
def retranslateUi(self, ProjectWelcomeDialog):
_translate = QtCore.QCoreApplication.translate
ProjectWelcomeDialog.setWindowTitle(_translate("ProjectWelcomeDialog", "Welcome"))
self.label.setText(_translate("ProjectWelcomeDialog", "<html><head/><body><p>Loading.. Please wait.</p></body></html>"))
from . import resources_rc

File diff suppressed because it is too large Load Diff

View File

@@ -73,6 +73,9 @@ class UpdateManager(QtCore.QObject):
request = QtNetwork.QNetworkRequest(QtCore.QUrl(url))
request.setRawHeader(b'User-Agent', b'GNS3 Check For Update')
request.setAttribute(QtNetwork.QNetworkRequest.User, user_attribute)
if parse_version(QtCore.QT_VERSION_STR) >= parse_version("5.6.0") and parse_version(QtCore.PYQT_VERSION_STR) >= parse_version("5.6.0"):
# follow redirects only supported starting with Qt 5.6.0
request.setAttribute(QtNetwork.QNetworkRequest.FollowRedirectsAttribute, True)
reply = self._network_manager.get(request)
reply.finished.connect(finished_slot)
log.debug('Download %s', url)
@@ -90,7 +93,7 @@ class UpdateManager(QtCore.QObject):
self._parent = parent
if hasattr(sys, "frozen") and LocalConfig.instance().experimental():
url = 'https://pypi.python.org/pypi/gns3-gui/json'
url = 'https://pypi.org/pypi/gns3-gui/json'
self._get(url, self._pypiReplySlot)
else:
self._get('http://update.gns3.net', self._gns3UpdateReplySlot)
@@ -130,7 +133,9 @@ class UpdateManager(QtCore.QObject):
body = bytes(network_reply.readAll()).decode("utf-8")
body = json.loads(body)
except (UnicodeEncodeError, ValueError) as e:
log.warning("Invalid answer from the PyPi server")
log.warning("Invalid answer from the PyPi server: {}".format(e))
QtWidgets.QMessageBox.critical(self._parent, "Check For Update", "Invalid answer from PyPi server")
return
last_version = self._getLastMinorVersionFromPyPiReply(body)
if parse_version(last_version) > parse_version(version.__version__):
@@ -154,6 +159,7 @@ class UpdateManager(QtCore.QObject):
If no valid version is found it's return the current.
"""
current_version = parse_version(version.__version__)
for release in sorted(body['releases'].keys(), reverse=True):
release_version = parse_version(release)

View File

@@ -1,66 +0,0 @@
#!/usr/bin/env python
#
# Copyright (C) 2017 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/>.
from ..qt import QtCore
from ..controller import Controller
import logging
log = logging.getLogger(__name__)
class CreateSnapshotWorker(QtCore.QObject):
"""
Export snapshot
"""
# signals to update the progress dialog.
error = QtCore.pyqtSignal(str, bool)
finished = QtCore.pyqtSignal()
updated = QtCore.pyqtSignal(int)
canceled = QtCore.pyqtSignal()
def __init__(self, project, snapshot_name):
super().__init__()
self._project = project
self._snapshot_name = snapshot_name
def run(self):
if self._project:
Controller.instance().post(
"/projects/{}/snapshots".format(self._project.id()),
self._createSnapshotsCallback,
{"name": self._snapshot_name},
timeout=None,
showProgress=False,
eventsHandler=self
)
def _createSnapshotsCallback(self, content, error=False, server=None, context={}, **kwargs):
if error:
if content:
self.error.emit(content["message"], True)
else:
self.error.emit("Cannot create snapshot of project", True)
self.finished.emit()
return
self.finished.emit()
def cancel(self):
log.warning("Snapshot `{}` creation has been canceled by user.".format(self._snapshot_name))
self.canceled.emit()

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.7"
__version_info__ = (2, 1, 7, 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

BIN
resources/icons/traceng.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -61,6 +61,7 @@
<file>icons/virtualbox.png</file>
<file>icons/qemu.svg</file>
<file>icons/rtv.png</file>
<file>icons/traceng.png</file>
<file>icons/image.svg</file>
<file>icons/node_conception.svg</file>
<file>icons/raise_z_value.svg</file>

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

@@ -145,6 +145,7 @@ def test_add_appliance_docker(empty_config, iou_l3):
"adapters": 16,
"start_command": "",
"environment": "",
'extra_hosts': "",
"usage": "By default all interfaces are connected to the br0",
"console_type": "telnet",
"console_http_port": 80,

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"})
@@ -127,8 +128,6 @@ def test_project_delete_on_created_project(controller):
def test_project_destroy(controller):
project = Project()
project.setId(str(uuid4()))
project.destroy()
@@ -139,3 +138,49 @@ def test_project_destroy(controller):
assert args[0] == "DELETE"
assert args[1] == "/projects/{project_id}".format(project_id=project.id())
def test_project_variables():
project = Project()
project.setVariables([{'name': 'TEST'}])
variables = project.variables()
assert variables == [{'name': 'TEST'}]
def test_project_supplier():
project = Project()
project.setSupplier({'logo': 'test.png', 'url': 'http://domain'})
supplier = project.supplier()
assert supplier == {'logo': 'test.png', 'url': 'http://domain'}
def test_project_parse_response():
result = {
'project_id': 'projectid',
'name': 'projectname',
'filename': 'filename.gns3',
'variables': [{'name': 'TEST'}],
'supplier': {'logo': 'test.png', 'url': 'http://domain'}
}
project = Project()
project._parseResponse(result)
assert project.id() == 'projectid'
assert project.name() == 'projectname'
assert project.filename() == 'filename.gns3'
assert project.variables() == [{'name': 'TEST'}]
assert project.supplier() == {'logo': 'test.png', 'url': 'http://domain'}
def test_project_update(controller):
project = Project()
project.setVariables([{'name': 'TEST'}])
project.setSupplier({'logo': 'test.png', 'url': 'http://domain'})
project.update()
mock = controller._http_client.createHTTPQuery
args, kwargs = mock.call_args
body = kwargs['body']
assert body['variables'] == [{'name': 'TEST'}]
assert body['supplier'] == {'logo': 'test.png', 'url': 'http://domain'}