Merge branch '2.1' into 2.2

# Conflicts:
#	gns3/modules/docker/pages/docker_vm_preferences_page.py
#	gns3/modules/docker/ui/docker_vm_configuration_page.ui
#	gns3/modules/docker/ui/docker_vm_configuration_page_ui.py
#	gns3/modules/qemu/pages/qemu_vm_configuration_page.py
#	gns3/modules/virtualbox/pages/virtualbox_vm_configuration_page.py
#	gns3/modules/vmware/pages/vmware_vm_configuration_page.py
#	gns3/version.py
This commit is contained in:
grossmj
2018-06-04 22:45:24 +07:00
26 changed files with 5043 additions and 4463 deletions

View File

@@ -1,5 +1,19 @@
# Change Log
## 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.

View File

@@ -51,7 +51,7 @@ class CrashReport:
Report crash to a third party service
"""
DSN = "sync+https://a89695dcde7b43b6ad0ce7190aefab8b:7786022ee4d04ef8aa4022dea63bcfd1@sentry.io/38506"
DSN = "sync+https://22de49ca89514046acf5c2d4759e8b06:62e5d41b53ad46e09398c62bc281f588@sentry.io/38506"
if hasattr(sys, "frozen"):
cacert = get_resource("cacert.pem")
if cacert is not None and os.path.isfile(cacert):

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
@@ -38,6 +38,67 @@ class EditProjectDialog(QtWidgets.QDialog, Ui_EditProjectDialog):
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):
"""
Called when the dialog is closed.
@@ -53,5 +114,6 @@ class EditProjectDialog(QtWidgets.QDialog, Ui_EditProjectDialog):
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

@@ -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

@@ -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__)
@@ -296,6 +297,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.
@@ -416,12 +421,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):
@@ -441,7 +450,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():

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

@@ -138,6 +138,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_auto_start": DOCKER_CONTAINER_SETTINGS["console_auto_start"],
"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

@@ -98,6 +98,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"])
@@ -165,6 +166,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

@@ -90,6 +90,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

@@ -40,5 +40,6 @@ DOCKER_CONTAINER_SETTINGS = {
"console_auto_start": False,
"console_resolution": "1024x768",
"console_http_port": 80,
"console_http_path": "/"
"console_http_path": "/",
"extra_hosts": ""
}

View File

@@ -25,11 +25,10 @@ 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.dialogs.custom_adapters_configuration_dialog import CustomAdaptersConfigurationDialog
from gns3.ports.port_name_factory import StandardPortNameFactory
from gns3.dialogs.custom_adapters_configuration_dialog import CustomAdaptersConfigurationDialog
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
@@ -369,17 +368,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
else:
first_port_name = self.uiFirstPortNameLineEdit.text().strip()
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}")
return
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",
"The format must contain {1}, {segment0} or {segment1} if the segment size is not 0")
return
adapters = self.uiAdaptersSpinBox.value()
default_adapter = self.uiAdapterTypesComboBox.currentData()
@@ -606,21 +595,21 @@ 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()
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())

View File

@@ -93,17 +93,7 @@ class VirtualBoxVMConfigurationPage(QtWidgets.QWidget, Ui_virtualBoxVMConfigPage
else:
first_port_name = self.uiFirstPortNameLineEdit.text().strip()
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}")
return
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",
"The format must contain {1}, {segment0} or {segment1} if the segment size is not 0")
return
adapters = self.uiAdaptersSpinBox.value()
default_adapter = self.uiAdapterTypesComboBox.currentText()
@@ -244,21 +234,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["console_type"] = self.uiConsoleTypeComboBox.currentText().lower()
settings["console_auto_start"] = self.uiConsoleAutoStartCheckBox.isChecked()

View File

@@ -93,17 +93,7 @@ class VMwareVMConfigurationPage(QtWidgets.QWidget, Ui_VMwareVMConfigPageWidget):
else:
first_port_name = self.uiFirstPortNameLineEdit.text().strip()
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}")
return
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",
"The format must contain {1}, {segment0} or {segment1} if the segment size is not 0")
return
adapters = self.uiAdaptersSpinBox.value()
default_adapter = self.uiAdapterTypesComboBox.currentText()
@@ -243,21 +233,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

@@ -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
@@ -72,6 +73,8 @@ class Project(QtCore.QObject):
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
@@ -208,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
@@ -417,7 +446,9 @@ class Project(QtCore.QObject):
"snap_to_grid": self._snap_to_grid,
"show_grid": self._show_grid,
"grid_size": self._grid_size,
"show_interface_labels": self._show_interface_labels
"show_interface_labels": self._show_interface_labels,
"variables": self._variables,
"supplier": self._supplier
}
self.put("", self._projectUpdatedCallback, body=body)
@@ -437,6 +468,7 @@ class Project(QtCore.QObject):
self._closed = False
self._closing = False
self._startListenNotifications()
self.project_updated_signal.emit()
self.project_loaded_signal.emit()
@@ -457,6 +489,9 @@ 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

View File

@@ -174,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):

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

@@ -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,6 +135,7 @@ 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
@@ -168,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

@@ -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

@@ -20,110 +20,127 @@
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<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" 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="8" 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="0" column="1">
<widget class="QLineEdit" name="uiProjectNameLineEdit"/>
</item>
<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="3" column="0">
<widget class="QLabel" name="uiGridSizeLabel">
<property name="text">
<string>Grid size:</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="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="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>
<item row="9" 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="6" 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="3" column="1">
<widget class="QSpinBox" name="uiGridSizeSpinBox"/>
</item>
</layout>
</widget>
<resources/>

View File

@@ -1,8 +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 by: PyQt5 UI code generator 5.5.1
# Created by: PyQt5 UI code generator 5.8.2
#
# WARNING! All changes made in this file will be lost!
@@ -16,52 +16,65 @@ class Ui_EditProjectDialog(object):
EditProjectDialog.setModal(True)
self.gridLayout = QtWidgets.QGridLayout(EditProjectDialog)
self.gridLayout.setObjectName("gridLayout")
self.uiSceneWidthLabel = QtWidgets.QLabel(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.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)
self.uiProjectAutoStartCheckBox = QtWidgets.QCheckBox(EditProjectDialog)
self.uiProjectAutoStartCheckBox.setObjectName("uiProjectAutoStartCheckBox")
self.gridLayout.addWidget(self.uiProjectAutoStartCheckBox, 7, 0, 1, 3)
self.uiProjectAutoCloseCheckBox = QtWidgets.QCheckBox(EditProjectDialog)
self.uiGeneralGrid.addWidget(self.uiSceneWidthLabel, 2, 0, 1, 1)
self.uiProjectAutoCloseCheckBox = QtWidgets.QCheckBox(self.uiGeneralTab)
self.uiProjectAutoCloseCheckBox.setObjectName("uiProjectAutoCloseCheckBox")
self.gridLayout.addWidget(self.uiProjectAutoCloseCheckBox, 8, 0, 1, 3)
self.uiProjectNameLineEdit = QtWidgets.QLineEdit(EditProjectDialog)
self.uiProjectNameLineEdit.setObjectName("uiProjectNameLineEdit")
self.gridLayout.addWidget(self.uiProjectNameLineEdit, 0, 1, 1, 1)
self.uiProjectNameLabel = QtWidgets.QLabel(EditProjectDialog)
self.uiGeneralGrid.addWidget(self.uiProjectAutoCloseCheckBox, 9, 0, 1, 3)
self.uiProjectNameLabel = QtWidgets.QLabel(self.uiGeneralTab)
self.uiProjectNameLabel.setObjectName("uiProjectNameLabel")
self.gridLayout.addWidget(self.uiProjectNameLabel, 0, 0, 1, 1)
self.uiGridSizeLabel = QtWidgets.QLabel(EditProjectDialog)
self.uiGridSizeLabel.setObjectName("uiGridSizeLabel")
self.gridLayout.addWidget(self.uiGridSizeLabel, 3, 0, 1, 1)
self.uiSceneWidthSpinBox = QtWidgets.QSpinBox(EditProjectDialog)
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.uiSceneHeightSpinBox = QtWidgets.QSpinBox(EditProjectDialog)
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.gridLayout.addWidget(self.uiSceneHeightSpinBox, 2, 1, 1, 1)
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.gridLayout.addItem(spacerItem, 10, 0, 1, 1)
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, 9, 0, 1, 3)
self.uiProjectAutoOpenCheckBox = QtWidgets.QCheckBox(EditProjectDialog)
self.uiProjectAutoOpenCheckBox.setObjectName("uiProjectAutoOpenCheckBox")
self.gridLayout.addWidget(self.uiProjectAutoOpenCheckBox, 6, 0, 1, 3)
self.uiGridSizeSpinBox = QtWidgets.QSpinBox(EditProjectDialog)
self.uiGridSizeSpinBox.setObjectName("uiGridSizeSpinBox")
self.gridLayout.addWidget(self.uiGridSizeSpinBox, 3, 1, 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)
@@ -70,12 +83,14 @@ class Ui_EditProjectDialog(object):
_translate = QtCore.QCoreApplication.translate
EditProjectDialog.setWindowTitle(_translate("EditProjectDialog", "Edit project"))
self.uiSceneWidthLabel.setText(_translate("EditProjectDialog", "Scene width:"))
self.uiSceneHeightLabel.setText(_translate("EditProjectDialog", "Scene height:"))
self.uiProjectAutoStartCheckBox.setText(_translate("EditProjectDialog", "Start all nodes when this project is opened"))
self.uiProjectAutoCloseCheckBox.setText(_translate("EditProjectDialog", "Leave this project running in the background when closing GNS3"))
self.uiProjectNameLabel.setText(_translate("EditProjectDialog", "Project Name:"))
self.uiGridSizeLabel.setText(_translate("EditProjectDialog", "Grid size:"))
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.tabWidget.setTabText(self.tabWidget.indexOf(self.uiGeneralTab), _translate("EditProjectDialog", "General"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.uiGlobalVariablesTab), _translate("EditProjectDialog", "Global variables"))

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

View File

@@ -146,6 +146,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

@@ -128,8 +128,6 @@ def test_project_delete_on_created_project(controller):
def test_project_destroy(controller):
project = Project()
project.setId(str(uuid4()))
project.destroy()
@@ -140,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'}