mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-06-05 02:02:08 +03:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fefd1097de | ||
|
|
4c35ea7f17 | ||
|
|
200fbc533b | ||
|
|
c94f63e636 | ||
|
|
f53124f09a | ||
|
|
d7a51ed588 | ||
|
|
dbca1b7106 | ||
|
|
15e5cac33b | ||
|
|
342ca95bd2 | ||
|
|
8191a51b2b | ||
|
|
7d112551a8 | ||
|
|
b5e867f2cd | ||
|
|
e5632e565d | ||
|
|
bd785bf6cd | ||
|
|
2ae788a8f5 | ||
|
|
d7c1754323 | ||
|
|
187ef561fd | ||
|
|
ad2ce4cfef | ||
|
|
97070718fa |
22
CHANGELOG
22
CHANGELOG
@@ -1,5 +1,27 @@
|
||||
# Change Log
|
||||
|
||||
## 3.0.4 25/02/2025
|
||||
|
||||
* Upgrade dependencies
|
||||
* Fix auto idle-pc for IOS templates
|
||||
* Add user info and password change for logged-in user. Fixes #3698
|
||||
|
||||
## 3.0.3 22/01/2025
|
||||
|
||||
* Set minimum duration for progress dialog when uploading. Ref https://github.com/GNS3/gns3-gui/issues/3682
|
||||
* Add logs when uploading images to the controller
|
||||
* Option to disable SSL certificate verification for future connections. Fixes https://github.com/GNS3/gns3-gui/issues/3694
|
||||
* Fix packet capture when connected to a controller with SSL. Fixes https://github.com/GNS3/gns3-gui/issues/3696
|
||||
* Update status after importing an image when installing a new appliance. Fixes #3691
|
||||
* Update file browser filters to find IOU images without extension. Fixes #3692
|
||||
* Upgrade dependencies
|
||||
|
||||
## 2.2.53 21/01/2025
|
||||
|
||||
* Update file browser filters for all files and IOU images
|
||||
* Upgrade dependencies
|
||||
* Fix Linux Mint default terminal configuration
|
||||
|
||||
## 3.0.2 03/01/2025
|
||||
|
||||
* Add button to create templates based on images that are not used by any yet.
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
pytest==8.3.2
|
||||
pytest==8.3.4
|
||||
pytest-timeout==2.3.1
|
||||
|
||||
@@ -50,7 +50,7 @@ class CrashReport:
|
||||
Report crash to a third party service
|
||||
"""
|
||||
|
||||
DSN = "https://142c803f12d32e781a654ef31138c684@o19455.ingest.us.sentry.io/38506"
|
||||
DSN = "https://3a078102c42520fc0bd28a3d8aeb1a97@o19455.ingest.us.sentry.io/38506"
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@@ -522,6 +522,12 @@ Usage: {}
|
||||
image_upload_manger = ImageUploadManager(image, Controller.instance(), self.parent())
|
||||
image_upload_manger.upload()
|
||||
|
||||
# refresh the images list
|
||||
if Controller.instance().isRemote() or self._compute_id != "local":
|
||||
self._registry.getRemoteImageList()
|
||||
else:
|
||||
self.images_changed_signal.emit()
|
||||
|
||||
def _install(self, version):
|
||||
"""
|
||||
Install the appliance in GNS3
|
||||
|
||||
@@ -55,13 +55,13 @@ class ImageDialog(QtWidgets.QDialog, Ui_ImageDialog):
|
||||
self,
|
||||
"Select one or more images to upload",
|
||||
QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DownloadLocation),
|
||||
"Images (*.bin *.image *.iol *.qcow2 *.vmdk *.iso);;All files (*.*)"
|
||||
"Images (*.bin *.image *.iol *.qcow2 *.vmdk *.iso x86_64* i86bi*);;All files (*)"
|
||||
)
|
||||
error_msgs = ""
|
||||
for path in files:
|
||||
log.debug("Uploading image '{}' to controller".format(path))
|
||||
image_filename = os.path.basename(path)
|
||||
install_appliances = self.uiInstallApplianceCheckBox.isChecked()
|
||||
log.info("Uploading image '{}' to controller".format(image_filename))
|
||||
try:
|
||||
Controller.instance().post(
|
||||
f"/images/upload/{image_filename}",
|
||||
|
||||
85
gns3/dialogs/password_dialog.py
Normal file
85
gns3/dialogs/password_dialog.py
Normal file
@@ -0,0 +1,85 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2025 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 re
|
||||
from ..qt import QtCore, QtGui, QtWidgets
|
||||
from ..ui.password_dialog_ui import Ui_PasswordDialog
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PasswordDialog(QtWidgets.QDialog, Ui_PasswordDialog):
|
||||
|
||||
"""
|
||||
Password dialog.
|
||||
"""
|
||||
|
||||
def __init__(self, parent):
|
||||
"""
|
||||
:param parent: parent widget.
|
||||
"""
|
||||
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
self._password = None
|
||||
|
||||
self._eye_on_icon = QtGui.QIcon(':/icons/eye-on.svg')
|
||||
self._eye_off_icon = QtGui.QIcon(':/icons/eye-off.svg')
|
||||
for line_edit in [self.uiPasswordLineEdit, self.uiConfirmPasswordLineEdit]:
|
||||
action = line_edit.addAction(self._eye_on_icon, QtWidgets.QLineEdit.TrailingPosition)
|
||||
button = action.associatedWidgets()[-1]
|
||||
button.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
|
||||
button.pressed.connect(self.onPressedSlot)
|
||||
#button.released.connect(self.onReleasedSlot)
|
||||
|
||||
def onPressedSlot(self):
|
||||
|
||||
button = self.sender()
|
||||
line_edit = button.parent()
|
||||
if line_edit.echoMode() == QtWidgets.QLineEdit.Password:
|
||||
button.setIcon(self._eye_off_icon)
|
||||
line_edit.setEchoMode(QtWidgets.QLineEdit.Normal)
|
||||
else:
|
||||
button.setIcon(self._eye_on_icon)
|
||||
line_edit.setEchoMode(QtWidgets.QLineEdit.Password)
|
||||
|
||||
# def onReleasedSlot(self):
|
||||
#
|
||||
# button = self.sender()
|
||||
# button.setIcon(self._eye_on_icon)
|
||||
# button.parent().setEchoMode(QtWidgets.QLineEdit.Password)
|
||||
|
||||
def getPassword(self):
|
||||
|
||||
return self._password
|
||||
|
||||
def done(self, result):
|
||||
|
||||
if result:
|
||||
new_password = self.uiPasswordLineEdit.text()
|
||||
confirm_password = self.uiConfirmPasswordLineEdit.text()
|
||||
if new_password != confirm_password:
|
||||
QtWidgets.QMessageBox.critical(self, "Error", "Passwords do not match.")
|
||||
return
|
||||
pattern = re.compile(r'^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8}$')
|
||||
if not pattern.match(new_password):
|
||||
QtWidgets.QMessageBox.critical(self, "Error", "Password must be at least 8 characters long and contain at least one digit, one lowercase letter and one uppercase letter.")
|
||||
return
|
||||
self._password = new_password
|
||||
super().done(result)
|
||||
@@ -24,6 +24,7 @@ from ..ui.preferences_dialog_ui import Ui_PreferencesDialog
|
||||
from ..pages.controller_preferences_page import ControllerPreferencesPage
|
||||
from ..pages.general_preferences_page import GeneralPreferencesPage
|
||||
from ..pages.packet_capture_preferences_page import PacketCapturePreferencesPage
|
||||
from ..pages.user_preferences_page import UserPreferencesPage
|
||||
from ..pages.gns3_vm_preferences_page import GNS3VMPreferencesPage
|
||||
from ..modules import MODULES
|
||||
|
||||
@@ -85,8 +86,9 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
|
||||
pages = [
|
||||
GeneralPreferencesPage,
|
||||
ControllerPreferencesPage,
|
||||
UserPreferencesPage,
|
||||
#GNS3VMPreferencesPage,
|
||||
PacketCapturePreferencesPage,
|
||||
PacketCapturePreferencesPage
|
||||
]
|
||||
|
||||
for page in pages:
|
||||
|
||||
@@ -83,7 +83,7 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
|
||||
filter = ""
|
||||
if sys.platform.startswith("win"):
|
||||
filter = "Executable (*.exe);;All files (*.*)"
|
||||
filter = "Executable (*.exe);;All files (*)"
|
||||
server_path = shutil.which("gns3server")
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select the local server", server_path, filter)
|
||||
if not path:
|
||||
@@ -204,7 +204,8 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
remote_controller_settings["remote"] = True
|
||||
remote_controller_settings["host"] = self.uiRemoteMainServerHostLineEdit.text()
|
||||
remote_controller_settings["port"] = self.uiRemoteMainServerPortSpinBox.value()
|
||||
remote_controller_settings["protocol"] = "http"
|
||||
remote_controller_settings["protocol"] = self.uiRemoteMainServerProtocolComboBox.currentText().lower()
|
||||
remote_controller_settings["accept_insecure_ssl_certificate"] = False
|
||||
remote_controller_settings["username"] = self.uiRemoteMainServerUserLineEdit.text()
|
||||
remote_controller_settings["password"] = self.uiRemoteMainServerPasswordLineEdit.text()
|
||||
Controller.instance().setSettings(remote_controller_settings)
|
||||
|
||||
@@ -184,7 +184,7 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
|
||||
def _symbolBrowserSlot(self):
|
||||
|
||||
# supported image file formats
|
||||
file_formats = "Image files (*.svg *.bmp *.jpeg *.jpg *.pbm *.pgm *.png *.ppm *.xbm *.xpm *.gif);;All files (*.*)"
|
||||
file_formats = "Image files (*.svg *.bmp *.jpeg *.jpg *.pbm *.pgm *.png *.ppm *.xbm *.xpm *.gif);;All files (*)"
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Image", SymbolSelectionDialog._symbols_dir, file_formats)
|
||||
if not path:
|
||||
return
|
||||
|
||||
@@ -1281,7 +1281,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self,
|
||||
"Import {}".format(os.path.basename(config_file)),
|
||||
self._import_config_directory,
|
||||
"All files (*.*);;Config files (*.cfg)",
|
||||
"All files (*);;Config files (*.cfg)",
|
||||
"Config files (*.cfg)")
|
||||
if not path:
|
||||
continue
|
||||
@@ -1328,7 +1328,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
|
||||
for item in items:
|
||||
for config_file in item.node().configFiles():
|
||||
path, ok = QtWidgets.QFileDialog.getSaveFileName(self, "Export file", os.path.join(self._export_config_directory, item.node().name() + "_" + os.path.basename(config_file)), "All files (*.*);;Config files (*.cfg)")
|
||||
path, ok = QtWidgets.QFileDialog.getSaveFileName(self, "Export file", os.path.join(self._export_config_directory, item.node().name() + "_" + os.path.basename(config_file)), "All files (*);;Config files (*.cfg)")
|
||||
if not path:
|
||||
continue
|
||||
self._export_config_directory = os.path.dirname(path)
|
||||
|
||||
@@ -67,6 +67,7 @@ class QNetworkReplyWatcher(QtCore.QObject):
|
||||
self._progress = QtWidgets.QProgressDialog(progress_text, "Cancel", 0, 0, parent)
|
||||
self._progress.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
|
||||
self._progress.setWindowModality(QtCore.Qt.ApplicationModal)
|
||||
self._progress.setMinimumDuration(0)
|
||||
else:
|
||||
self._progress = None
|
||||
|
||||
@@ -102,11 +103,11 @@ class QNetworkReplyWatcher(QtCore.QObject):
|
||||
reply.finished.connect(loop.quit)
|
||||
|
||||
if self._progress:
|
||||
reply.finished.connect(self._progress.close)
|
||||
if uploading:
|
||||
reply.uploadProgress.connect(self._updateProgress)
|
||||
else:
|
||||
reply.downloadProgress.connect(self._updateProgress)
|
||||
reply.finished.connect(self._progress.close)
|
||||
self._progress.canceled.connect(reply.abort)
|
||||
self._progress.show()
|
||||
|
||||
@@ -158,7 +159,7 @@ class HTTPClient(QtCore.QObject):
|
||||
self._retry_connection = False
|
||||
self._connected = False
|
||||
self._shutdown = False # shutdown in progress
|
||||
self._accept_insecure_certificate = settings.get("accept_insecure_certificate", False)
|
||||
self._accept_insecure_ssl_certificate = settings.get("accept_insecure_ssl_certificate", False)
|
||||
|
||||
# Add custom CA
|
||||
# ssl_config = QtNetwork.QSslConfiguration.defaultConfiguration()
|
||||
@@ -224,12 +225,12 @@ class HTTPClient(QtCore.QObject):
|
||||
|
||||
return self._protocol
|
||||
|
||||
def setAcceptInsecureCertificate(self, certificate: bool) -> None:
|
||||
def setAcceptInsecureCertificate(self, accept_insecure_ssl_certificate: bool) -> None:
|
||||
"""
|
||||
Does the server accept this insecure SSL certificate digest
|
||||
"""
|
||||
|
||||
self._accept_insecure_certificate = certificate
|
||||
self._accept_insecure_ssl_certificate = accept_insecure_ssl_certificate
|
||||
|
||||
def url(self) -> str:
|
||||
"""
|
||||
@@ -251,6 +252,13 @@ class HTTPClient(QtCore.QObject):
|
||||
|
||||
return "{}://{}:{}".format(self.protocol(), host, self.port())
|
||||
|
||||
def getToken(self):
|
||||
"""
|
||||
Return the JWT token
|
||||
"""
|
||||
|
||||
return self._jwt_token
|
||||
|
||||
def shutdown(self) -> None:
|
||||
"""
|
||||
Stop the server and stop to accept queries
|
||||
@@ -545,7 +553,12 @@ class HTTPClient(QtCore.QObject):
|
||||
self._auth_attempted = True
|
||||
content = self._executeHTTPQuery("POST", "/access/users/authenticate", body=body, wait=True)
|
||||
if content:
|
||||
log.info(f"Authenticated with controller {self._host} on port {self._port}")
|
||||
additional_ssl_info = ""
|
||||
if self._protocol == "https":
|
||||
additional_ssl_info = "using SSL"
|
||||
if self._accept_insecure_ssl_certificate:
|
||||
additional_ssl_info += " with insecure certificate"
|
||||
log.info(f"Authenticated with controller {self._host} on port {self._port} {additional_ssl_info}")
|
||||
token = content.get("access_token")
|
||||
if token:
|
||||
self._auth_attempted = False
|
||||
@@ -839,7 +852,7 @@ class HTTPClient(QtCore.QObject):
|
||||
Handle SSL errors
|
||||
"""
|
||||
|
||||
if self._accept_insecure_certificate:
|
||||
if self._accept_insecure_ssl_certificate:
|
||||
reply.ignoreSslErrors()
|
||||
return
|
||||
|
||||
@@ -852,7 +865,6 @@ class HTTPClient(QtCore.QObject):
|
||||
digest = peer_cert.digest()
|
||||
|
||||
if host_port_key in self._ssl_exceptions:
|
||||
|
||||
if self._ssl_exceptions[host_port_key] == digest:
|
||||
reply.ignoreSslErrors()
|
||||
return
|
||||
@@ -865,6 +877,8 @@ class HTTPClient(QtCore.QObject):
|
||||
msgbox.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
|
||||
connect_button = QtWidgets.QPushButton(f"&Connect to {url.host()}:{url.port()}", msgbox)
|
||||
msgbox.addButton(connect_button, QtWidgets.QMessageBox.YesRole)
|
||||
checkbox = QtWidgets.QCheckBox("Accept insecure certificate for future connections", parent=msgbox)
|
||||
msgbox.setCheckBox(checkbox)
|
||||
abort_button = QtWidgets.QPushButton("&Abort", msgbox)
|
||||
msgbox.addButton(abort_button, QtWidgets.QMessageBox.RejectRole)
|
||||
msgbox.setDefaultButton(abort_button)
|
||||
@@ -873,6 +887,12 @@ class HTTPClient(QtCore.QObject):
|
||||
|
||||
if msgbox.clickedButton() == connect_button:
|
||||
self._ssl_exceptions[host_port_key] = digest
|
||||
if checkbox.isChecked():
|
||||
log.warning(f"Accepting insecure SSL certificate for future connections to {host_port_key}")
|
||||
from gns3.controller import Controller
|
||||
self._accept_insecure_ssl_certificate = True
|
||||
controller_settings = {"accept_insecure_ssl_certificate": True}
|
||||
Controller.instance().setSettings(controller_settings)
|
||||
reply.ignoreSslErrors()
|
||||
else:
|
||||
for error in ssl_errors:
|
||||
|
||||
@@ -47,7 +47,7 @@ class ImageUploadManager(object):
|
||||
|
||||
def _fileUploadToController(self) -> bool:
|
||||
|
||||
log.debug("Uploading image '{}' to controller".format(self._image.path))
|
||||
log.info("Uploading image '{}' to controller".format(self._image.filename))
|
||||
try:
|
||||
self._controller.post(
|
||||
f"/images/upload/{self._image.filename}",
|
||||
|
||||
@@ -121,6 +121,8 @@ class Link(QtCore.QObject):
|
||||
|
||||
if self._network_manager is None:
|
||||
self._network_manager = QtNetwork.QNetworkAccessManager(self)
|
||||
self._network_manager.sslErrors.connect(Controller.instance().httpClient().handleSslError)
|
||||
|
||||
self._response_stream = Controller.instance().get(
|
||||
"/projects/{project_id}/links/{link_id}/capture/stream".format(project_id=self.project().id(), link_id=self._link_id),
|
||||
callback=None,
|
||||
|
||||
@@ -456,7 +456,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
if not os.path.exists(self._appliance_dir):
|
||||
directory = Topology.instance().projectsDirPath()
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Import appliance", directory,
|
||||
"All files (*.*);;GNS3 Appliance (*.gns3appliance *.gns3a)",
|
||||
"All files (*);;GNS3 Appliance (*.gns3appliance *.gns3a)",
|
||||
"GNS3 Appliance (*.gns3appliance *.gns3a)")
|
||||
if path:
|
||||
self.loadPath(path)
|
||||
@@ -475,7 +475,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
if self._project_dir is None or not os.path.exists(self._project_dir):
|
||||
directory = Topology.instance().projectsDirPath()
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open project", directory,
|
||||
"All files (*.*);;GNS3 Project (*.gns3);;GNS3 Portable Project (*.gns3project *.gns3p);;NET files (*.net)",
|
||||
"All files (*);;GNS3 Project (*.gns3);;GNS3 Portable Project (*.gns3project *.gns3p);;NET files (*.net)",
|
||||
"GNS3 Project (*.gns3)")
|
||||
if path:
|
||||
self.loadPath(path)
|
||||
@@ -951,7 +951,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
Slot called when inserting an image on the scene.
|
||||
"""
|
||||
# supported image file formats
|
||||
file_formats = "Image files (*.svg *.bmp *.jpeg *.jpg *.gif *.pbm *.pgm *.png *.ppm *.xbm *.xpm);;All files (*.*)"
|
||||
file_formats = "Image files (*.svg *.bmp *.jpeg *.jpg *.gif *.pbm *.pgm *.png *.ppm *.xbm *.xpm);;All files (*)"
|
||||
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Image", self._pictures_dir, file_formats)
|
||||
if not path:
|
||||
@@ -1489,7 +1489,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
if not os.path.exists(directory):
|
||||
directory = Topology.instance().projectsDirPath()
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open project", directory,
|
||||
"All files (*.*);;GNS3 Portable Project (*.gns3project *.gns3p)",
|
||||
"All files (*);;GNS3 Portable Project (*.gns3project *.gns3p)",
|
||||
"GNS3 Portable Project (*.gns3project *.gns3p)")
|
||||
if path:
|
||||
Topology.instance().importProject(path)
|
||||
|
||||
@@ -218,15 +218,20 @@ class IOSRouterWizard(VMWithImagesWizard, Ui_IOSRouterWizard):
|
||||
image = self.uiIOSImageLineEdit.text()
|
||||
platform = self.uiPlatformComboBox.currentText()
|
||||
ram = self.uiRamSpinBox.value()
|
||||
Controller.instance().postCompute("/auto_idlepc",
|
||||
self._compute_id,
|
||||
callback=self._computeAutoIdlepcCallback,
|
||||
timeout=None,
|
||||
body={
|
||||
"image": image,
|
||||
"platform": platform,
|
||||
"ram": ram},
|
||||
wait=True)
|
||||
|
||||
Controller.instance().postCompute(
|
||||
"/dynamips/auto_idlepc",
|
||||
self._compute_id,
|
||||
callback=self._computeAutoIdlepcCallback,
|
||||
body={
|
||||
"image": image,
|
||||
"platform": platform,
|
||||
"ram": ram
|
||||
},
|
||||
timeout=None,
|
||||
progress_text="Finding an Idle-PC value...",
|
||||
wait=True
|
||||
)
|
||||
|
||||
def _etherSwitchSlot(self, state):
|
||||
"""
|
||||
|
||||
@@ -51,7 +51,7 @@ class DynamipsPreferencesPage(QtWidgets.QWidget, Ui_DynamipsPreferencesPageWidge
|
||||
|
||||
file_filter = ""
|
||||
if sys.platform.startswith("win"):
|
||||
file_filter = "Executable (*.exe);;All files (*.*)"
|
||||
file_filter = "Executable (*.exe);;All files (*)"
|
||||
|
||||
dynamips_path = shutil.which("dynamips")
|
||||
if sys.platform.startswith("darwin") and dynamips_path is None:
|
||||
|
||||
@@ -229,7 +229,7 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(parent,
|
||||
"Select an IOS image",
|
||||
cls._default_images_dir,
|
||||
"All files (*.*);;IOS image (*.bin *.image)",
|
||||
"All files (*);;IOS image (*.bin *.image)",
|
||||
"IOS image (*.bin *.image)")
|
||||
|
||||
if not path:
|
||||
|
||||
@@ -299,8 +299,8 @@ class IOUDevicePreferencesPage(QtWidgets.QWidget, Ui_IOUDevicePreferencesPageWid
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(parent,
|
||||
"Select an IOU image",
|
||||
cls._default_images_dir,
|
||||
"All file (*);;IOU image (*.bin *.image *.iol)",
|
||||
"IOU image (*.bin *.image *.iol)")
|
||||
"All file (*);;IOU image (x86_64* i86bi* *.bin *.image *.iol)",
|
||||
"IOU image (x86_64* i86bi* *.bin *.image *.iol)")
|
||||
|
||||
if not path:
|
||||
return
|
||||
|
||||
@@ -51,7 +51,7 @@ class VPCSPreferencesPage(QtWidgets.QWidget, Ui_VPCSPreferencesPageWidget):
|
||||
|
||||
filter = ""
|
||||
if sys.platform.startswith("win"):
|
||||
filter = "Executable (*.exe);;All files (*.*)"
|
||||
filter = "Executable (*.exe);;All files (*)"
|
||||
vpcs_path = shutil.which("vpcs")
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select VPCS", vpcs_path, filter)
|
||||
if not path:
|
||||
|
||||
@@ -109,7 +109,7 @@ class ControllerPreferencesPage(QtWidgets.QWidget, Ui_ControllerPreferencesPageW
|
||||
|
||||
filter = ""
|
||||
if sys.platform.startswith("win"):
|
||||
filter = "Executable (*.exe);;All files (*.*)"
|
||||
filter = "Executable (*.exe);;All files (*)"
|
||||
server_path = shutil.which("gns3server")
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select the local server", server_path, filter)
|
||||
if not path:
|
||||
@@ -124,7 +124,7 @@ class ControllerPreferencesPage(QtWidgets.QWidget, Ui_ControllerPreferencesPageW
|
||||
|
||||
filter = ""
|
||||
if sys.platform.startswith("win"):
|
||||
filter = "Executable (*.exe);;All files (*.*)"
|
||||
filter = "Executable (*.exe);;All files (*)"
|
||||
|
||||
ubridge_path = shutil.which("ubridge")
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select ubridge executable", ubridge_path, filter)
|
||||
@@ -286,6 +286,12 @@ class ControllerPreferencesPage(QtWidgets.QWidget, Ui_ControllerPreferencesPageW
|
||||
"udp_start_port_range": self.uiUDPStartPortSpinBox.value(),
|
||||
"udp_end_port_range": self.uiUDPEndPortSpinBox.value()})
|
||||
|
||||
# reset the accept_insecure_ssl_certificate setting if the host or port has changed
|
||||
current_host_port_key = f"{local_server_settings['host']}:{local_server_settings['port']}"
|
||||
new_host_port_key = f"{new_local_server_settings['host']}:{new_local_server_settings['port']}"
|
||||
if current_host_port_key != new_host_port_key:
|
||||
new_local_server_settings["accept_insecure_ssl_certificate"] = False
|
||||
|
||||
if new_local_server_settings["console_end_port_range"] <= new_local_server_settings["console_start_port_range"]:
|
||||
QtWidgets.QMessageBox.critical(self, "Port range", "Invalid console port range from {} to {}".format(new_local_server_settings["console_start_port_range"],
|
||||
new_local_server_settings["console_end_port_range"]))
|
||||
|
||||
@@ -195,7 +195,7 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
|
||||
configuration_file_path = LocalConfig.instance().configFilePath()
|
||||
directory = os.path.dirname(configuration_file_path)
|
||||
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Import configuration file", directory, "Configuration file (*.ini *.conf);;All files (*.*)")
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Import configuration file", directory, "Configuration file (*.ini *.conf);;All files (*)")
|
||||
if not path:
|
||||
return
|
||||
|
||||
@@ -240,7 +240,7 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
|
||||
configuration_file_path = LocalConfig.instance().configFilePath()
|
||||
directory = os.path.dirname(configuration_file_path)
|
||||
|
||||
path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export configuration file", directory, "Configuration file (*.ini *.conf);;All files (*.*)")
|
||||
path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export configuration file", directory, "Configuration file (*.ini *.conf);;All files (*)")
|
||||
if not path:
|
||||
return
|
||||
|
||||
|
||||
138
gns3/pages/user_preferences_page.py
Normal file
138
gns3/pages/user_preferences_page.py
Normal file
@@ -0,0 +1,138 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2025 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 user preferences.
|
||||
"""
|
||||
|
||||
from gns3.qt import QtWidgets, qslot, sip_is_deleted
|
||||
from ..ui.user_preferences_page_ui import Ui_UserPreferencesPageWidget
|
||||
from gns3.controller import Controller
|
||||
from gns3.dialogs.password_dialog import PasswordDialog
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UserPreferencesPage(QtWidgets.QWidget, Ui_UserPreferencesPageWidget):
|
||||
|
||||
"""
|
||||
QWidget configuration page for user preferences.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
|
||||
super().__init__()
|
||||
self.setupUi(self)
|
||||
|
||||
self.uiChangePasswordPushButton.clicked.connect(self._changePasswordSlot)
|
||||
self.uiCopyAccessTokenPushButton.clicked.connect(self._copyAccessTokenSlot)
|
||||
self.uiChangePasswordPushButton.setEnabled(False)
|
||||
self.uiCopyAccessTokenPushButton.setEnabled(False)
|
||||
Controller.instance().connected_signal.connect(self.loadPreferences)
|
||||
Controller.instance().disconnected_signal.connect(self._disconnectSlot)
|
||||
|
||||
def _populateWidgets(self, result):
|
||||
"""
|
||||
Populates the widgets with the user information.
|
||||
|
||||
:param result: user information
|
||||
"""
|
||||
|
||||
self.uiUsernameLineEdit.setText(result.get("username", "N/A"))
|
||||
self.uiFullNameLineEdit.setText(result.get("full_name", "N/A"))
|
||||
self.uiEmailLineEdit.setText(result.get("email", "N/A"))
|
||||
self.uiChangePasswordPushButton.setEnabled(True)
|
||||
self.uiCopyAccessTokenPushButton.setEnabled(True)
|
||||
|
||||
def _disconnectSlot(self):
|
||||
"""
|
||||
Resets the widgets when the controller is disconnected.
|
||||
"""
|
||||
|
||||
self.uiUsernameLineEdit.clear()
|
||||
self.uiFullNameLineEdit.clear()
|
||||
self.uiEmailLineEdit.clear()
|
||||
self.uiChangePasswordPushButton.setEnabled(False)
|
||||
self.uiCopyAccessTokenPushButton.setEnabled(False)
|
||||
|
||||
def loadPreferences(self):
|
||||
"""
|
||||
Loads the user preferences.
|
||||
"""
|
||||
|
||||
if Controller.instance().connected():
|
||||
Controller.instance().get("/access/users/me", self._getUserInfoCallback)
|
||||
else:
|
||||
log.error("Cannot load the user information in the preferences dialog: not connected to the controller")
|
||||
|
||||
@qslot
|
||||
def _getUserInfoCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
Callback to get the user information.
|
||||
"""
|
||||
|
||||
if sip_is_deleted(self):
|
||||
return
|
||||
if error:
|
||||
if "message" in result:
|
||||
log.error("Error while getting the logged-in user information: {}".format(result["message"]))
|
||||
return
|
||||
self._populateWidgets(result)
|
||||
|
||||
def savePreferences(self):
|
||||
"""
|
||||
Saves the user preferences.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def _changePasswordSlot(self):
|
||||
"""
|
||||
Slot to change the user password.
|
||||
"""
|
||||
|
||||
dialog = PasswordDialog(self)
|
||||
if dialog.exec_():
|
||||
password = dialog.getPassword()
|
||||
new_settings = {"password": password}
|
||||
Controller.instance().put("/access/users/me", self._saveUserSettingsCallback, new_settings)
|
||||
|
||||
def _saveUserSettingsCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
Callback to save the user settings.
|
||||
"""
|
||||
|
||||
if error and "message" in result:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self,
|
||||
"Save user settings",
|
||||
"Error while saving user settings: {}".format(result["message"])
|
||||
)
|
||||
else:
|
||||
QtWidgets.QMessageBox.information(self, "Change password", "New password saved successfully")
|
||||
|
||||
def _copyAccessTokenSlot(self):
|
||||
"""
|
||||
Slot to copy the access token to the clipboard.
|
||||
"""
|
||||
|
||||
if Controller.instance().connected():
|
||||
token = Controller.instance().httpClient().getToken()
|
||||
if token:
|
||||
QtWidgets.QApplication.clipboard().setText(token)
|
||||
log.info("Access token copied to the clipboard")
|
||||
@@ -340,6 +340,7 @@ CONTROLLER_SETTINGS = {
|
||||
"ubridge_path": "ubridge",
|
||||
"host": DEFAULT_CONTROLLER_HOST,
|
||||
"port": DEFAULT_CONTROLLER_PORT,
|
||||
"accept_insecure_ssl_certificate": False,
|
||||
"images_path": DEFAULT_IMAGES_PATH,
|
||||
"projects_path": DEFAULT_PROJECTS_PATH,
|
||||
"appliances_path": DEFAULT_APPLIANCES_PATH,
|
||||
|
||||
121
gns3/ui/password_dialog.ui
Normal file
121
gns3/ui/password_dialog.ui
Normal file
@@ -0,0 +1,121 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>PasswordDialog</class>
|
||||
<widget class="QDialog" name="PasswordDialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::ApplicationModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>377</width>
|
||||
<height>111</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Change password</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="uiConfirmPasswordLineEdit">
|
||||
<property name="maxLength">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiPasswordLabel">
|
||||
<property name="text">
|
||||
<string>New password:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="uiPasswordLineEdit">
|
||||
<property name="maxLength">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiConfirmPasswordLabel">
|
||||
<property name="text">
|
||||
<string>Confirm password:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<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="3" 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>
|
||||
<tabstops>
|
||||
<tabstop>uiPasswordLineEdit</tabstop>
|
||||
<tabstop>uiConfirmPasswordLineEdit</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>uiButtonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>PasswordDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>uiButtonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>PasswordDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
56
gns3/ui/password_dialog_ui.py
Normal file
56
gns3/ui/password_dialog_ui.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/password_dialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.10
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_PasswordDialog(object):
|
||||
def setupUi(self, PasswordDialog):
|
||||
PasswordDialog.setObjectName("PasswordDialog")
|
||||
PasswordDialog.setWindowModality(QtCore.Qt.ApplicationModal)
|
||||
PasswordDialog.resize(377, 111)
|
||||
PasswordDialog.setModal(True)
|
||||
self.gridLayout = QtWidgets.QGridLayout(PasswordDialog)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.uiConfirmPasswordLineEdit = QtWidgets.QLineEdit(PasswordDialog)
|
||||
self.uiConfirmPasswordLineEdit.setMaxLength(100)
|
||||
self.uiConfirmPasswordLineEdit.setEchoMode(QtWidgets.QLineEdit.Password)
|
||||
self.uiConfirmPasswordLineEdit.setObjectName("uiConfirmPasswordLineEdit")
|
||||
self.gridLayout.addWidget(self.uiConfirmPasswordLineEdit, 1, 1, 1, 1)
|
||||
self.uiPasswordLabel = QtWidgets.QLabel(PasswordDialog)
|
||||
self.uiPasswordLabel.setObjectName("uiPasswordLabel")
|
||||
self.gridLayout.addWidget(self.uiPasswordLabel, 0, 0, 1, 1)
|
||||
self.uiPasswordLineEdit = QtWidgets.QLineEdit(PasswordDialog)
|
||||
self.uiPasswordLineEdit.setMaxLength(100)
|
||||
self.uiPasswordLineEdit.setEchoMode(QtWidgets.QLineEdit.Password)
|
||||
self.uiPasswordLineEdit.setObjectName("uiPasswordLineEdit")
|
||||
self.gridLayout.addWidget(self.uiPasswordLineEdit, 0, 1, 1, 1)
|
||||
self.uiConfirmPasswordLabel = QtWidgets.QLabel(PasswordDialog)
|
||||
self.uiConfirmPasswordLabel.setObjectName("uiConfirmPasswordLabel")
|
||||
self.gridLayout.addWidget(self.uiConfirmPasswordLabel, 1, 0, 1, 1)
|
||||
self.uiButtonBox = QtWidgets.QDialogButtonBox(PasswordDialog)
|
||||
self.uiButtonBox.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.uiButtonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
|
||||
self.uiButtonBox.setObjectName("uiButtonBox")
|
||||
self.gridLayout.addWidget(self.uiButtonBox, 2, 0, 1, 2)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout.addItem(spacerItem, 3, 0, 1, 1)
|
||||
|
||||
self.retranslateUi(PasswordDialog)
|
||||
self.uiButtonBox.accepted.connect(PasswordDialog.accept) # type: ignore
|
||||
self.uiButtonBox.rejected.connect(PasswordDialog.reject) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(PasswordDialog)
|
||||
PasswordDialog.setTabOrder(self.uiPasswordLineEdit, self.uiConfirmPasswordLineEdit)
|
||||
|
||||
def retranslateUi(self, PasswordDialog):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
PasswordDialog.setWindowTitle(_translate("PasswordDialog", "Change password"))
|
||||
self.uiPasswordLabel.setText(_translate("PasswordDialog", "New password:"))
|
||||
self.uiConfirmPasswordLabel.setText(_translate("PasswordDialog", "Confirm password:"))
|
||||
185607
gns3/ui/resources_rc.py
185607
gns3/ui/resources_rc.py
File diff suppressed because it is too large
Load Diff
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>603</width>
|
||||
<height>287</height>
|
||||
<width>644</width>
|
||||
<height>290</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -159,27 +159,37 @@
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiRemoteMainServerProtocolLabel">
|
||||
<property name="text">
|
||||
<string>Protocol:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="uiRemoteMainServerProtocolComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>HTTP</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>HTTPS</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Host:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="uiRemoteMainServerPasswordLineEdit">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="uiRemoteMainServerHostLineEdit"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Password:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Port:</string>
|
||||
@@ -187,9 +197,6 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="uiRemoteMainServerUserLineEdit"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="uiRemoteMainServerPortSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
@@ -208,16 +215,30 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="uiRemoteMainServerHostLineEdit"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Username:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="uiRemoteMainServerUserLineEdit"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Password:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="uiRemoteMainServerPasswordLineEdit">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWizardPage" name="uiSummaryWizardPage">
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/setup_wizard.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.14.1
|
||||
# Created by: PyQt5 UI code generator 5.15.6
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@@ -13,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
class Ui_SetupWizard(object):
|
||||
def setupUi(self, SetupWizard):
|
||||
SetupWizard.setObjectName("SetupWizard")
|
||||
SetupWizard.resize(603, 287)
|
||||
SetupWizard.resize(644, 290)
|
||||
SetupWizard.setModal(True)
|
||||
SetupWizard.setWizardStyle(QtWidgets.QWizard.ModernStyle)
|
||||
SetupWizard.setOptions(QtWidgets.QWizard.NoBackButtonOnStartPage)
|
||||
@@ -81,22 +82,23 @@ class Ui_SetupWizard(object):
|
||||
self.uiRemoteControllerWizardPage.setObjectName("uiRemoteControllerWizardPage")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self.uiRemoteControllerWizardPage)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.uiRemoteMainServerProtocolLabel = QtWidgets.QLabel(self.uiRemoteControllerWizardPage)
|
||||
self.uiRemoteMainServerProtocolLabel.setObjectName("uiRemoteMainServerProtocolLabel")
|
||||
self.gridLayout.addWidget(self.uiRemoteMainServerProtocolLabel, 0, 0, 1, 1)
|
||||
self.uiRemoteMainServerProtocolComboBox = QtWidgets.QComboBox(self.uiRemoteControllerWizardPage)
|
||||
self.uiRemoteMainServerProtocolComboBox.setObjectName("uiRemoteMainServerProtocolComboBox")
|
||||
self.uiRemoteMainServerProtocolComboBox.addItem("")
|
||||
self.uiRemoteMainServerProtocolComboBox.addItem("")
|
||||
self.gridLayout.addWidget(self.uiRemoteMainServerProtocolComboBox, 0, 1, 1, 1)
|
||||
self.label_3 = QtWidgets.QLabel(self.uiRemoteControllerWizardPage)
|
||||
self.label_3.setObjectName("label_3")
|
||||
self.gridLayout.addWidget(self.label_3, 0, 0, 1, 1)
|
||||
self.uiRemoteMainServerPasswordLineEdit = QtWidgets.QLineEdit(self.uiRemoteControllerWizardPage)
|
||||
self.uiRemoteMainServerPasswordLineEdit.setEchoMode(QtWidgets.QLineEdit.Password)
|
||||
self.uiRemoteMainServerPasswordLineEdit.setObjectName("uiRemoteMainServerPasswordLineEdit")
|
||||
self.gridLayout.addWidget(self.uiRemoteMainServerPasswordLineEdit, 3, 1, 1, 1)
|
||||
self.label_6 = QtWidgets.QLabel(self.uiRemoteControllerWizardPage)
|
||||
self.label_6.setObjectName("label_6")
|
||||
self.gridLayout.addWidget(self.label_6, 3, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.label_3, 1, 0, 1, 1)
|
||||
self.uiRemoteMainServerHostLineEdit = QtWidgets.QLineEdit(self.uiRemoteControllerWizardPage)
|
||||
self.uiRemoteMainServerHostLineEdit.setObjectName("uiRemoteMainServerHostLineEdit")
|
||||
self.gridLayout.addWidget(self.uiRemoteMainServerHostLineEdit, 1, 1, 1, 1)
|
||||
self.label_4 = QtWidgets.QLabel(self.uiRemoteControllerWizardPage)
|
||||
self.label_4.setObjectName("label_4")
|
||||
self.gridLayout.addWidget(self.label_4, 1, 0, 1, 1)
|
||||
self.uiRemoteMainServerUserLineEdit = QtWidgets.QLineEdit(self.uiRemoteControllerWizardPage)
|
||||
self.uiRemoteMainServerUserLineEdit.setObjectName("uiRemoteMainServerUserLineEdit")
|
||||
self.gridLayout.addWidget(self.uiRemoteMainServerUserLineEdit, 2, 1, 1, 1)
|
||||
self.gridLayout.addWidget(self.label_4, 2, 0, 1, 1)
|
||||
self.uiRemoteMainServerPortSpinBox = QtWidgets.QSpinBox(self.uiRemoteControllerWizardPage)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
@@ -106,13 +108,20 @@ class Ui_SetupWizard(object):
|
||||
self.uiRemoteMainServerPortSpinBox.setMaximum(65535)
|
||||
self.uiRemoteMainServerPortSpinBox.setProperty("value", 3080)
|
||||
self.uiRemoteMainServerPortSpinBox.setObjectName("uiRemoteMainServerPortSpinBox")
|
||||
self.gridLayout.addWidget(self.uiRemoteMainServerPortSpinBox, 1, 1, 1, 1)
|
||||
self.uiRemoteMainServerHostLineEdit = QtWidgets.QLineEdit(self.uiRemoteControllerWizardPage)
|
||||
self.uiRemoteMainServerHostLineEdit.setObjectName("uiRemoteMainServerHostLineEdit")
|
||||
self.gridLayout.addWidget(self.uiRemoteMainServerHostLineEdit, 0, 1, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiRemoteMainServerPortSpinBox, 2, 1, 1, 1)
|
||||
self.label_5 = QtWidgets.QLabel(self.uiRemoteControllerWizardPage)
|
||||
self.label_5.setObjectName("label_5")
|
||||
self.gridLayout.addWidget(self.label_5, 2, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.label_5, 3, 0, 1, 1)
|
||||
self.uiRemoteMainServerUserLineEdit = QtWidgets.QLineEdit(self.uiRemoteControllerWizardPage)
|
||||
self.uiRemoteMainServerUserLineEdit.setObjectName("uiRemoteMainServerUserLineEdit")
|
||||
self.gridLayout.addWidget(self.uiRemoteMainServerUserLineEdit, 3, 1, 1, 1)
|
||||
self.label_6 = QtWidgets.QLabel(self.uiRemoteControllerWizardPage)
|
||||
self.label_6.setObjectName("label_6")
|
||||
self.gridLayout.addWidget(self.label_6, 4, 0, 1, 1)
|
||||
self.uiRemoteMainServerPasswordLineEdit = QtWidgets.QLineEdit(self.uiRemoteControllerWizardPage)
|
||||
self.uiRemoteMainServerPasswordLineEdit.setEchoMode(QtWidgets.QLineEdit.Password)
|
||||
self.uiRemoteMainServerPasswordLineEdit.setObjectName("uiRemoteMainServerPasswordLineEdit")
|
||||
self.gridLayout.addWidget(self.uiRemoteMainServerPasswordLineEdit, 4, 1, 1, 1)
|
||||
SetupWizard.addPage(self.uiRemoteControllerWizardPage)
|
||||
self.uiSummaryWizardPage = QtWidgets.QWizardPage()
|
||||
self.uiSummaryWizardPage.setObjectName("uiSummaryWizardPage")
|
||||
@@ -149,11 +158,14 @@ class Ui_SetupWizard(object):
|
||||
self.uiLocalServerPortLabel.setText(_translate("SetupWizard", "Port:"))
|
||||
self.uiRemoteControllerWizardPage.setTitle(_translate("SetupWizard", "Remote controller"))
|
||||
self.uiRemoteControllerWizardPage.setSubTitle(_translate("SetupWizard", "Please configure the settings to connect to a remote GNS3 controller"))
|
||||
self.uiRemoteMainServerProtocolLabel.setText(_translate("SetupWizard", "Protocol:"))
|
||||
self.uiRemoteMainServerProtocolComboBox.setItemText(0, _translate("SetupWizard", "HTTP"))
|
||||
self.uiRemoteMainServerProtocolComboBox.setItemText(1, _translate("SetupWizard", "HTTPS"))
|
||||
self.label_3.setText(_translate("SetupWizard", "Host:"))
|
||||
self.label_6.setText(_translate("SetupWizard", "Password:"))
|
||||
self.label_4.setText(_translate("SetupWizard", "Port:"))
|
||||
self.uiRemoteMainServerPortSpinBox.setSuffix(_translate("SetupWizard", " TCP"))
|
||||
self.label_5.setText(_translate("SetupWizard", "Username:"))
|
||||
self.label_6.setText(_translate("SetupWizard", "Password:"))
|
||||
self.uiSummaryWizardPage.setTitle(_translate("SetupWizard", "Summary"))
|
||||
self.uiSummaryWizardPage.setSubTitle(_translate("SetupWizard", "The controller has been configured, please see the summary of the settings below"))
|
||||
self.uiSummaryTreeWidget.headerItem().setText(0, _translate("SetupWizard", "1"))
|
||||
|
||||
128
gns3/ui/user_preferences_page.ui
Executable file
128
gns3/ui/user_preferences_page.ui
Executable file
@@ -0,0 +1,128 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>UserPreferencesPageWidget</class>
|
||||
<widget class="QWidget" name="UserPreferencesPageWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>472</width>
|
||||
<height>370</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>User</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="uiSettingsGroupBox">
|
||||
<property name="title">
|
||||
<string>Logged-in user information</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="uiEmailLabel">
|
||||
<property name="text">
|
||||
<string>Email address:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="uiFullNameLabel">
|
||||
<property name="text">
|
||||
<string>Full name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiUsernameLabel">
|
||||
<property name="text">
|
||||
<string>Username:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="uiUsernameLineEdit">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="uiFullNameLineEdit">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="uiEmailLineEdit">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiChangePasswordPushButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Change password</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiCopyAccessTokenPushButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Copy access token</string>
|
||||
</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>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
81
gns3/ui/user_preferences_page_ui.py
Normal file
81
gns3/ui/user_preferences_page_ui.py
Normal file
@@ -0,0 +1,81 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/user_preferences_page.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.10
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_UserPreferencesPageWidget(object):
|
||||
def setupUi(self, UserPreferencesPageWidget):
|
||||
UserPreferencesPageWidget.setObjectName("UserPreferencesPageWidget")
|
||||
UserPreferencesPageWidget.resize(472, 370)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(UserPreferencesPageWidget)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiSettingsGroupBox = QtWidgets.QGroupBox(UserPreferencesPageWidget)
|
||||
self.uiSettingsGroupBox.setObjectName("uiSettingsGroupBox")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self.uiSettingsGroupBox)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.uiEmailLabel = QtWidgets.QLabel(self.uiSettingsGroupBox)
|
||||
self.uiEmailLabel.setObjectName("uiEmailLabel")
|
||||
self.gridLayout.addWidget(self.uiEmailLabel, 3, 0, 1, 1)
|
||||
self.uiFullNameLabel = QtWidgets.QLabel(self.uiSettingsGroupBox)
|
||||
self.uiFullNameLabel.setObjectName("uiFullNameLabel")
|
||||
self.gridLayout.addWidget(self.uiFullNameLabel, 2, 0, 1, 1)
|
||||
self.uiUsernameLabel = QtWidgets.QLabel(self.uiSettingsGroupBox)
|
||||
self.uiUsernameLabel.setObjectName("uiUsernameLabel")
|
||||
self.gridLayout.addWidget(self.uiUsernameLabel, 0, 0, 1, 1)
|
||||
self.uiUsernameLineEdit = QtWidgets.QLineEdit(self.uiSettingsGroupBox)
|
||||
self.uiUsernameLineEdit.setReadOnly(True)
|
||||
self.uiUsernameLineEdit.setObjectName("uiUsernameLineEdit")
|
||||
self.gridLayout.addWidget(self.uiUsernameLineEdit, 0, 1, 1, 1)
|
||||
self.uiFullNameLineEdit = QtWidgets.QLineEdit(self.uiSettingsGroupBox)
|
||||
self.uiFullNameLineEdit.setReadOnly(True)
|
||||
self.uiFullNameLineEdit.setObjectName("uiFullNameLineEdit")
|
||||
self.gridLayout.addWidget(self.uiFullNameLineEdit, 2, 1, 1, 1)
|
||||
self.uiEmailLineEdit = QtWidgets.QLineEdit(self.uiSettingsGroupBox)
|
||||
self.uiEmailLineEdit.setReadOnly(True)
|
||||
self.uiEmailLineEdit.setObjectName("uiEmailLineEdit")
|
||||
self.gridLayout.addWidget(self.uiEmailLineEdit, 3, 1, 1, 1)
|
||||
self.verticalLayout.addWidget(self.uiSettingsGroupBox)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout.addItem(spacerItem)
|
||||
self.uiChangePasswordPushButton = QtWidgets.QPushButton(UserPreferencesPageWidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiChangePasswordPushButton.sizePolicy().hasHeightForWidth())
|
||||
self.uiChangePasswordPushButton.setSizePolicy(sizePolicy)
|
||||
self.uiChangePasswordPushButton.setObjectName("uiChangePasswordPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiChangePasswordPushButton)
|
||||
self.uiCopyAccessTokenPushButton = QtWidgets.QPushButton(UserPreferencesPageWidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiCopyAccessTokenPushButton.sizePolicy().hasHeightForWidth())
|
||||
self.uiCopyAccessTokenPushButton.setSizePolicy(sizePolicy)
|
||||
self.uiCopyAccessTokenPushButton.setObjectName("uiCopyAccessTokenPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiCopyAccessTokenPushButton)
|
||||
self.verticalLayout.addLayout(self.horizontalLayout)
|
||||
spacerItem1 = QtWidgets.QSpacerItem(20, 5, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.verticalLayout.addItem(spacerItem1)
|
||||
|
||||
self.retranslateUi(UserPreferencesPageWidget)
|
||||
QtCore.QMetaObject.connectSlotsByName(UserPreferencesPageWidget)
|
||||
|
||||
def retranslateUi(self, UserPreferencesPageWidget):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
UserPreferencesPageWidget.setWindowTitle(_translate("UserPreferencesPageWidget", "User"))
|
||||
self.uiSettingsGroupBox.setTitle(_translate("UserPreferencesPageWidget", "Logged-in user information"))
|
||||
self.uiEmailLabel.setText(_translate("UserPreferencesPageWidget", "Email address:"))
|
||||
self.uiFullNameLabel.setText(_translate("UserPreferencesPageWidget", "Full name:"))
|
||||
self.uiUsernameLabel.setText(_translate("UserPreferencesPageWidget", "Username:"))
|
||||
self.uiChangePasswordPushButton.setText(_translate("UserPreferencesPageWidget", "&Change password"))
|
||||
self.uiCopyAccessTokenPushButton.setText(_translate("UserPreferencesPageWidget", "&Copy access token"))
|
||||
@@ -23,8 +23,8 @@
|
||||
# or negative for a release candidate or beta (after the base version
|
||||
# number has been incremented)
|
||||
|
||||
__version__ = "3.0.2"
|
||||
__version_info__ = (3, 0, 2, 0)
|
||||
__version__ = "3.0.4"
|
||||
__version_info__ = (3, 0, 4, 0)
|
||||
|
||||
if "dev" in __version__:
|
||||
try:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
jsonschema>=4.23,<4.24
|
||||
sentry-sdk>=2.19.2,<2.20 # optional dependency
|
||||
psutil>=6.1.1
|
||||
sentry-sdk>=2.22,<2.23 # optional dependency
|
||||
psutil>=7.0.0
|
||||
distro>=1.9.0
|
||||
truststore>=0.10.0; python_version >= '3.10'
|
||||
truststore>=0.10.1; python_version >= '3.10'
|
||||
importlib-resources>=1.3; python_version < '3.9'
|
||||
|
||||
43
resources/icons/eye-off.svg
Normal file
43
resources/icons/eye-off.svg
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
enable-background="new -0.709 -32.081 141.732 141.732"
|
||||
height="141.732px"
|
||||
id="Livello_1"
|
||||
version="1.1"
|
||||
viewBox="-0.709 -32.081 141.732 141.732"
|
||||
width="141.732px"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="eye_off2.svg"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs9" /><sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="5.0729548"
|
||||
inkscape:cx="71.161683"
|
||||
inkscape:cy="70.865997"
|
||||
inkscape:window-width="1658"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-x="70"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Livello_1" /><g
|
||||
id="Livello_80"><path
|
||||
d="M89.668,38.786c0-10.773-8.731-19.512-19.51-19.512S50.646,28.01,50.646,38.786c0,10.774,8.732,19.511,19.512,19.511 C80.934,58.297,89.668,49.561,89.668,38.786 M128.352,38.727c-13.315,17.599-34.426,28.972-58.193,28.972 c-23.77,0-44.879-11.373-58.194-28.972C25.279,21.129,46.389,9.756,70.158,9.756C93.927,9.756,115.036,21.129,128.352,38.727 M140.314,38.76C125.666,15.478,99.725,0,70.158,0S14.648,15.478,0,38.76c14.648,23.312,40.591,38.81,70.158,38.81 S125.666,62.072,140.314,38.76"
|
||||
id="path2" /></g><g
|
||||
id="Livello_1_1_" /><path
|
||||
d="m 19.354144,-16.791815 c 2.079678,-2.079624 5.451424,-2.079624 7.5311,0 L 122.74,79.063042 c 2.07952,2.079517 2.07952,5.451476 0,7.530992 -2.07952,2.079517 -5.45148,2.079517 -7.53099,0 L 19.354144,-9.2607185 c -2.079623,-2.0796765 -2.079623,-5.4514205 0,-7.5310965 z"
|
||||
id="path905"
|
||||
style="fill:#ffffff;stroke-width:5.32526" /><path
|
||||
d="m 13.899155,-13.386617 c 2.079678,-2.079624 5.451424,-2.079624 7.5311,0 L 117.28501,82.46824 c 2.07952,2.079517 2.07952,5.451476 0,7.530992 -2.07952,2.079517 -5.45148,2.079517 -7.53099,0 L 13.899155,-5.8555208 c -2.079623,-2.0796767 -2.079623,-5.4514202 0,-7.5310962 z"
|
||||
id="path2-3"
|
||||
style="stroke-width:5.32526" /></svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
1
resources/icons/eye-on.svg
Normal file
1
resources/icons/eye-on.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new -0.709 -32.081 141.732 141.732" height="141.732px" id="Livello_1" version="1.1" viewBox="-0.709 -32.081 141.732 141.732" width="141.732px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="Livello_80"><path d="M89.668,38.786c0-10.773-8.731-19.512-19.51-19.512S50.646,28.01,50.646,38.786c0,10.774,8.732,19.511,19.512,19.511 C80.934,58.297,89.668,49.561,89.668,38.786 M128.352,38.727c-13.315,17.599-34.426,28.972-58.193,28.972 c-23.77,0-44.879-11.373-58.194-28.972C25.279,21.129,46.389,9.756,70.158,9.756C93.927,9.756,115.036,21.129,128.352,38.727 M140.314,38.76C125.666,15.478,99.725,0,70.158,0S14.648,15.478,0,38.76c14.648,23.312,40.591,38.81,70.158,38.81 S125.666,62.072,140.314,38.76"/></g><g id="Livello_1_1_"/></svg>
|
||||
|
After Width: | Height: | Size: 935 B |
@@ -99,6 +99,8 @@
|
||||
<file>icons/camera-photo-hover.svg</file>
|
||||
<file>icons/front.svg</file>
|
||||
<file>icons/dialog-warning.svg</file>
|
||||
<file>icons/eye-on.svg</file>
|
||||
<file>icons/eye-off.svg</file>
|
||||
<file>images/gns3_logo.png</file>
|
||||
<file>images/gns3_icon_128x128.png</file>
|
||||
<file>images/gns3_icon_256x256.png</file>
|
||||
|
||||
Reference in New Issue
Block a user