mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-06-06 02:32:04 +03:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9dd4020666 | ||
|
|
83d0957d50 | ||
|
|
1767a441ab | ||
|
|
41b7c2c33e | ||
|
|
1cebcabff3 | ||
|
|
53cc823859 | ||
|
|
dd6329a1f3 | ||
|
|
68633c4732 | ||
|
|
161421abcf | ||
|
|
9466b2a1fb | ||
|
|
889d41636d | ||
|
|
878cfb2fe5 | ||
|
|
141767e0d9 | ||
|
|
a5976a61ac | ||
|
|
3edde1274b | ||
|
|
e17e7fc033 | ||
|
|
bfe11d7976 | ||
|
|
2b98d51ff7 | ||
|
|
5efb3019f4 |
16
CHANGELOG
16
CHANGELOG
@@ -1,5 +1,21 @@
|
||||
# Change Log
|
||||
|
||||
## 3.0.2 03/01/2025
|
||||
|
||||
* Add button to create templates based on images that are not used by any yet.
|
||||
* Add "prune" images button in image management dialog.
|
||||
* Use the controller image endpoint to install appliances
|
||||
* Drop Python 3.8
|
||||
* Add image info tooltip in image management dialog.
|
||||
* Upgrade dependencies
|
||||
* Apply grid color via css property
|
||||
|
||||
## 3.0.1 27/12/2024
|
||||
|
||||
* Fix issue when image is already on the local server. Fixes https://github.com/GNS3/gns3-gui/issues/3678
|
||||
* Fix image uploading when image name differs with the image name recorded in the appliance. Fixes https://github.com/GNS3/gns3-gui/issues/3682
|
||||
* Fix Linux Mint default terminal configuration
|
||||
|
||||
## 3.0.0 20/12/2024
|
||||
|
||||
* Change title of QMessageBox
|
||||
|
||||
@@ -237,7 +237,7 @@ class Controller(QtCore.QObject):
|
||||
|
||||
def request(self, method, path, *args, **kwargs):
|
||||
"""
|
||||
Forward the query to the HTTP client or controller depending of the path
|
||||
Forward the query to the HTTP client or controller depending on the path
|
||||
"""
|
||||
|
||||
if self._http_client:
|
||||
|
||||
@@ -50,7 +50,7 @@ class CrashReport:
|
||||
Report crash to a third party service
|
||||
"""
|
||||
|
||||
DSN = "https://77c0749c7bf450b586f2495c285b10d2@o19455.ingest.us.sentry.io/38506"
|
||||
DSN = "https://142c803f12d32e781a654ef31138c684@o19455.ingest.us.sentry.io/38506"
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@@ -174,7 +174,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
|
||||
elif self.page(page_id) == self.uiFilesWizardPage:
|
||||
if Controller.instance().isRemote() or self._compute_id != "local":
|
||||
self._registry.getRemoteImageList(self._appliance.template_type(), self._compute_id)
|
||||
self._registry.getRemoteImageList()
|
||||
else:
|
||||
self.images_changed_signal.emit()
|
||||
|
||||
@@ -212,7 +212,7 @@ Usage: {}
|
||||
log.error("Error while uploading image '{}': {}".format(image_path, result["message"]))
|
||||
else:
|
||||
log.info("Image '{}' has been successfully uploaded".format(image_path))
|
||||
self._registry.getRemoteImageList(self._appliance.template_type(), self._compute_id)
|
||||
self._registry.getRemoteImageList()
|
||||
|
||||
def _showApplianceInfoSlot(self):
|
||||
"""
|
||||
@@ -329,7 +329,7 @@ Usage: {}
|
||||
image_widget.setForeground(2, QtGui.QBrush(QtGui.QColor("red")))
|
||||
else:
|
||||
image_widget.setForeground(2, QtGui.QBrush(QtGui.QColor("green")))
|
||||
image_widget.setToolTip(2, image["path"])
|
||||
image_widget.setToolTip(0, f'{image["status"]} with path: {image["path"]}')
|
||||
|
||||
# Associated data stored are col 0: version, col 1: image
|
||||
image_widget.setData(0, QtCore.Qt.UserRole, version)
|
||||
@@ -383,11 +383,13 @@ Usage: {}
|
||||
|
||||
for version in self._appliance["versions"]:
|
||||
for image in version["images"].values():
|
||||
img = self._registry.search_image_file(self._appliance.template_type(),
|
||||
image["filename"],
|
||||
image.get("md5sum"),
|
||||
image.get("filesize"),
|
||||
strict_md5_check=not self.allowCustomFiles.isChecked())
|
||||
img = self._registry.search_image_file(
|
||||
self._appliance.template_type(),
|
||||
image["filename"],
|
||||
image.get("md5sum"),
|
||||
image.get("filesize"),
|
||||
strict_md5_check=not self.allowCustomFiles.isChecked()
|
||||
)
|
||||
if img:
|
||||
if img.location == "local":
|
||||
image["status"] = "Found locally"
|
||||
@@ -551,21 +553,6 @@ Usage: {}
|
||||
TemplateManager.instance().createTemplate(Template(new_template), callback=self._templateCreatedCallback)
|
||||
return False
|
||||
|
||||
#worker = WaitForLambdaWorker(lambda: self._create_template(appliance_configuration, self._compute_id), allowed_exceptions=[ConfigException, OSError])
|
||||
#progress_dialog = ProgressDialog(worker, "Add template", "Installing a new template...", None, busy=True, parent=self)
|
||||
#progress_dialog.show()
|
||||
#if progress_dialog.exec_():
|
||||
# QtWidgets.QMessageBox.information(self.parent(), "Add template", "{} template has been installed!".format(appliance_configuration["name"]))
|
||||
# return True
|
||||
#return False
|
||||
|
||||
# worker = WaitForLambdaWorker(lambda: config.save(), allowed_exceptions=[ConfigException, OSError])
|
||||
# progress_dialog = ProgressDialog(worker, "Add appliance", "Install the appliance...", None, busy=True, parent=self)
|
||||
# progress_dialog.show()
|
||||
# if progress_dialog.exec_():
|
||||
# QtWidgets.QMessageBox.information(self.parent(), "Add appliance", "{} installed!".format(appliance_configuration["name"]))
|
||||
# return True
|
||||
|
||||
def _templateCreatedCallback(self, result, error=False, **kwargs):
|
||||
|
||||
if error is True:
|
||||
@@ -590,7 +577,7 @@ Usage: {}
|
||||
if image["location"] == "local":
|
||||
if not Controller.instance().isRemote() and self._compute_id == "local" and image["path"].startswith(ImageManager.instance().getDirectory()):
|
||||
log.debug("{} is already on the local server".format(image["path"]))
|
||||
return
|
||||
return True
|
||||
image = Image(self._appliance.template_type(), image["path"], filename=image["filename"])
|
||||
image_upload_manager = ImageUploadManager(image, Controller.instance(), self.parent())
|
||||
if not image_upload_manager.upload():
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
import os
|
||||
import pathlib
|
||||
from gns3.http_client_error import HttpClientError, HttpClientCancelledRequestError
|
||||
from ..qt import QtCore, QtWidgets, qslot, sip_is_deleted
|
||||
from ..qt import QtCore, QtGui, QtWidgets, qslot, sip_is_deleted
|
||||
from ..ui.image_dialog_ui import Ui_ImageDialog
|
||||
from ..utils import human_size
|
||||
from ..controller import Controller
|
||||
@@ -41,6 +41,8 @@ class ImageDialog(QtWidgets.QDialog, Ui_ImageDialog):
|
||||
self.setupUi(self)
|
||||
self.uiUploadImagePushButton.clicked.connect(self._uploadImageSlot)
|
||||
self.uiDeleteImagePushButton.clicked.connect(self._deleteImageSlot)
|
||||
self.uiInstallAllPushButton.clicked.connect(self._installAllSlot)
|
||||
self.uiPruneImagesPushButton.clicked.connect(self._pruneImagesSlot)
|
||||
self.uiRefreshImagesPushButton.clicked.connect(Controller.instance().refreshImageList)
|
||||
Controller.instance().image_list_updated_signal.connect(self._updateImageListSlot)
|
||||
self._updateImageListSlot()
|
||||
@@ -136,6 +138,64 @@ class ImageDialog(QtWidgets.QDialog, Ui_ImageDialog):
|
||||
|
||||
Controller.instance().refreshImageList()
|
||||
|
||||
def _installAllSlot(self, *args):
|
||||
|
||||
reply = QtWidgets.QMessageBox.warning(
|
||||
self,
|
||||
"Install appliance(s)",
|
||||
"This will attempt to automatically create templates based on image checksums.\nContinue?",
|
||||
QtWidgets.QMessageBox.Yes,
|
||||
QtWidgets.QMessageBox.No
|
||||
)
|
||||
|
||||
if reply == QtWidgets.QMessageBox.No:
|
||||
return
|
||||
|
||||
Controller.instance().post(
|
||||
f"/images/install",
|
||||
progress_text=f"Installing appliances",
|
||||
timeout=None,
|
||||
wait=True
|
||||
)
|
||||
|
||||
@qslot
|
||||
def _pruneImagesSlot(self, *args):
|
||||
|
||||
reply = QtWidgets.QMessageBox.warning(
|
||||
self,
|
||||
"Prune image(s)",
|
||||
"Delete all images not used by a template?\nThis cannot be reverted.",
|
||||
QtWidgets.QMessageBox.Yes,
|
||||
QtWidgets.QMessageBox.No
|
||||
)
|
||||
|
||||
if reply == QtWidgets.QMessageBox.No:
|
||||
return
|
||||
|
||||
error_msgs = ""
|
||||
try:
|
||||
Controller.instance().delete(
|
||||
f"/images/prune",
|
||||
progress_text=f"Pruning images",
|
||||
timeout=None,
|
||||
wait=True
|
||||
)
|
||||
except HttpClientCancelledRequestError:
|
||||
return
|
||||
except HttpClientError as e:
|
||||
error_msgs += f"{e}\n"
|
||||
|
||||
if error_msgs:
|
||||
error_dialog = QtWidgets.QMessageBox(self)
|
||||
error_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
|
||||
error_dialog.setWindowTitle("Image pruning")
|
||||
error_dialog.setText(f"Error while deleting images on the controller")
|
||||
error_dialog.setDetailedText(error_msgs)
|
||||
error_dialog.setIcon(QtWidgets.QMessageBox.Critical)
|
||||
error_dialog.show()
|
||||
|
||||
Controller.instance().refreshImageList()
|
||||
|
||||
@qslot
|
||||
def _updateImageListSlot(self, *args):
|
||||
|
||||
@@ -146,6 +206,7 @@ class ImageDialog(QtWidgets.QDialog, Ui_ImageDialog):
|
||||
for image in Controller.instance().images():
|
||||
item = QtWidgets.QTreeWidgetItem([image["filename"], image["image_type"], human_size(image["image_size"])])
|
||||
item.setData(0, QtCore.Qt.UserRole, image["filename"])
|
||||
item.setToolTip(0, f'{image["filename"]} {image["checksum"]}')
|
||||
items.append(item)
|
||||
|
||||
self.uiImagesTreeWidget.addTopLevelItems(items)
|
||||
@@ -159,6 +220,31 @@ class ImageDialog(QtWidgets.QDialog, Ui_ImageDialog):
|
||||
self.uiImagesTreeWidget.sortItems(0, QtCore.Qt.AscendingOrder)
|
||||
self.uiImagesTreeWidget.setUpdatesEnabled(True)
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
"""
|
||||
Handles all context menu events.
|
||||
|
||||
:param event: QContextMenuEvent instance
|
||||
"""
|
||||
|
||||
items = self.uiImagesTreeWidget.selectedItems()
|
||||
if items:
|
||||
menu = QtWidgets.QMenu()
|
||||
copy = QtWidgets.QAction("&Copy image information to clipboard", menu)
|
||||
copy.triggered.connect(self._copyToClipboardSlot)
|
||||
menu.addAction(copy)
|
||||
menu.exec_(event.globalPos())
|
||||
|
||||
def _copyToClipboardSlot(self):
|
||||
"""
|
||||
Copies the selected image tooltip to the clipboard.
|
||||
"""
|
||||
|
||||
items = self.uiImagesTreeWidget.selectedItems()
|
||||
if items:
|
||||
QtWidgets.QApplication.clipboard().setText(items[0].toolTip(0))
|
||||
log.info(f"'{items[0].toolTip(0)}' copied to clipboard")
|
||||
|
||||
def keyPressEvent(self, e):
|
||||
"""
|
||||
Event handler in order to properly handle escape.
|
||||
@@ -166,3 +252,5 @@ class ImageDialog(QtWidgets.QDialog, Ui_ImageDialog):
|
||||
|
||||
if e.key() == QtCore.Qt.Key_Escape:
|
||||
self.close()
|
||||
elif e.matches(QtGui.QKeySequence.Copy):
|
||||
self._copyToClipboardSlot()
|
||||
|
||||
@@ -74,6 +74,10 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
:param parent: parent widget
|
||||
"""
|
||||
|
||||
# Class-level constants for default colors
|
||||
DEFAULT_DRAWING_GRID_COLOR = QtGui.QColor(208, 208, 208) # #D0D0D0
|
||||
DEFAULT_NODE_GRID_COLOR = QtGui.QColor(190, 190, 190) # #BEBEBE
|
||||
|
||||
def __init__(self, parent):
|
||||
|
||||
# Our parent is the central widget which parent is the main window.
|
||||
@@ -92,6 +96,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
self._dragging = False
|
||||
self._grid_size = 75
|
||||
self._drawing_grid_size = 25
|
||||
self._drawing_grid_color = self.DEFAULT_DRAWING_GRID_COLOR
|
||||
self._node_grid_color = self.DEFAULT_NODE_GRID_COLOR
|
||||
self._last_mouse_position = None
|
||||
self._topology = Topology.instance()
|
||||
|
||||
@@ -1685,11 +1691,39 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
self._topology.addDrawing(item)
|
||||
return item
|
||||
|
||||
@QtCore.Property(QtGui.QColor)
|
||||
def drawingGridColor(self):
|
||||
"""Returns the drawing grid color"""
|
||||
return self._drawing_grid_color
|
||||
|
||||
@drawingGridColor.setter
|
||||
def drawingGridColor(self, color):
|
||||
"""Sets the drawing grid color"""
|
||||
self._drawing_grid_color = color
|
||||
self.viewport().update()
|
||||
|
||||
@QtCore.Property(QtGui.QColor)
|
||||
def nodeGridColor(self):
|
||||
"""Returns the node grid color"""
|
||||
return self._node_grid_color
|
||||
|
||||
@nodeGridColor.setter
|
||||
def nodeGridColor(self, color):
|
||||
"""Sets the node grid color"""
|
||||
self._node_grid_color = color
|
||||
self.viewport().update()
|
||||
|
||||
def resetGridColors(self):
|
||||
"""Reset grid colors to defaults"""
|
||||
self._drawing_grid_color = self.DEFAULT_DRAWING_GRID_COLOR
|
||||
self._node_grid_color = self.DEFAULT_NODE_GRID_COLOR
|
||||
self.viewport().update()
|
||||
|
||||
def drawBackground(self, painter, rect):
|
||||
super().drawBackground(painter, rect)
|
||||
if self._main_window.uiShowGridAction.isChecked():
|
||||
grids = [(self.drawingGridSize(), QtGui.QColor(208, 208, 208)),
|
||||
(self.nodeGridSize(), QtGui.QColor(190, 190, 190))]
|
||||
grids = [(self.drawingGridSize(), self._drawing_grid_color),
|
||||
(self.nodeGridSize(), self._node_grid_color)]
|
||||
painter.save()
|
||||
for (grid, colour) in grids:
|
||||
if not grid:
|
||||
|
||||
@@ -63,7 +63,7 @@ class ImageUploadManager(object):
|
||||
except HttpClientError as e:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self._parent,
|
||||
"Image upload",
|
||||
"Image upload to controller",
|
||||
f"Could not upload image {self._image.filename}: {e}"
|
||||
)
|
||||
return False
|
||||
|
||||
@@ -189,9 +189,9 @@ def main():
|
||||
# catch exceptions to write them in a file
|
||||
sys.excepthook = exceptionHook
|
||||
|
||||
# we only support Python 3 version >= 3.8
|
||||
if sys.version_info < (3, 8):
|
||||
raise SystemExit("Python 3.8 or higher is required")
|
||||
# we only support Python 3 version >= 3.9
|
||||
if sys.version_info < (3, 9):
|
||||
raise SystemExit("Python 3.9 or higher is required")
|
||||
|
||||
if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.5.0"):
|
||||
raise SystemExit("Requirement is PyQt5 version 5.5.0 or higher, got version {}".format(QtCore.QT_VERSION_STR))
|
||||
|
||||
@@ -232,7 +232,7 @@ class NodesView(QtWidgets.QTreeWidget):
|
||||
msgbox.setText(f"Do you want to delete template '{template.name()}'?\n\n"
|
||||
f"Deleting templates and images is irreversible!\n\n")
|
||||
msgbox.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
|
||||
delete_all_button = QtWidgets.QPushButton(f"&Delete the template and orphaned images", msgbox)
|
||||
delete_all_button = QtWidgets.QPushButton(f"&Delete the template and its image(s)", msgbox)
|
||||
msgbox.addButton(delete_all_button, QtWidgets.QMessageBox.YesRole)
|
||||
delete_template_only_button = QtWidgets.QPushButton(f"&Only delete the template", msgbox)
|
||||
msgbox.addButton(delete_template_only_button, QtWidgets.QMessageBox.NoRole)
|
||||
|
||||
@@ -112,12 +112,11 @@ class LogQMessageBox(QtWidgets.QMessageBox):
|
||||
Return a logger in the context of the caller
|
||||
in order to have the correct information in the log
|
||||
"""
|
||||
if sys.version_info < (3, 5):
|
||||
return logging.getLogger('qt')
|
||||
|
||||
try:
|
||||
caller = inspect.stack()[2]
|
||||
location = "{}:{}".format(os.path.basename(caller.filename), caller.lineno)
|
||||
except:
|
||||
except Exception:
|
||||
# If anything go wrong during the format return the standard logger
|
||||
# for unknonw reason sometimes we don't have the caller info
|
||||
return logging.getLogger('qt')
|
||||
|
||||
@@ -156,7 +156,7 @@ class ApplianceToTemplate:
|
||||
|
||||
for image in appliance_config["images"]:
|
||||
if image.get("path"):
|
||||
new_config[image["type"]] = self._relative_image_path("QEMU", image["path"])
|
||||
new_config[image["type"]] = image["filename"]
|
||||
|
||||
if "arch" in appliance_config:
|
||||
new_config["platform"] = appliance_config["arch"]
|
||||
@@ -188,38 +188,21 @@ class ApplianceToTemplate:
|
||||
|
||||
new_config["template_type"] = "dynamips"
|
||||
new_config.update(template_properties)
|
||||
|
||||
for image in appliance_config["images"]:
|
||||
new_config[image["type"]] = self._relative_image_path("IOS", image["path"])
|
||||
new_config[image["type"]] = image["filename"]
|
||||
if self._registry_version < 8:
|
||||
new_config["idlepc"] = image.get("idlepc", "")
|
||||
if "image" not in new_config:
|
||||
raise ConfigException("Disk image is missing")
|
||||
|
||||
def _add_iou_config(self, new_config, template_properties, appliance_config):
|
||||
|
||||
new_config["template_type"] = "iou"
|
||||
new_config.update(template_properties)
|
||||
for image in appliance_config["images"]:
|
||||
if "path" not in image:
|
||||
raise ConfigException("Disk image is missing")
|
||||
new_config[image["type"]] = self._relative_image_path("IOU", image["path"])
|
||||
new_config["path"] = new_config["image"]
|
||||
|
||||
def _relative_image_path(self, image_dir_type, path):
|
||||
"""
|
||||
|
||||
:param image_dir_type: Type of image directory
|
||||
:param filename: Filename at the end of the process
|
||||
:param path: Full path to the file
|
||||
:returns: Path relative to image directory.
|
||||
Copy the image to the directory if not already in the directory
|
||||
"""
|
||||
|
||||
images_dir = os.path.join(Config().images_dir, image_dir_type)
|
||||
path = os.path.abspath(path)
|
||||
if os.path.commonprefix([images_dir, path]) == images_dir:
|
||||
return path.replace(images_dir, '').strip('/\\')
|
||||
|
||||
return os.path.basename(path)
|
||||
new_config["path"] = image["filename"]
|
||||
if "path" not in new_config:
|
||||
raise ConfigException("Disk image is missing")
|
||||
|
||||
def _set_symbol(self, symbol_id, controller_symbols):
|
||||
"""
|
||||
|
||||
@@ -47,23 +47,25 @@ class Registry(QtCore.QObject):
|
||||
|
||||
:param image_directory: Folder we need to add
|
||||
"""
|
||||
|
||||
self._images_dirs.append(image_directory)
|
||||
|
||||
def getRemoteImageList(self, emulator, compute_id):
|
||||
self._emulator = emulator
|
||||
Controller.instance().getCompute("/{}/images".format(emulator), compute_id, self._getRemoteListCallback, progress_text="Listing remote images...")
|
||||
def getRemoteImageList(self):
|
||||
|
||||
Controller.instance().get("/images", self._getRemoteListCallback, progress_text="Listing remote images...")
|
||||
|
||||
def _getRemoteListCallback(self, result, error=False, **kwargs):
|
||||
|
||||
if error:
|
||||
if "message" in result:
|
||||
log.error("Error while getting the list of remote images: {}".format(result["message"]))
|
||||
return
|
||||
self._remote_images = []
|
||||
for res in result:
|
||||
image = Image(self._emulator, res["path"])
|
||||
image = Image(res["image_type"], res["path"])
|
||||
image.location = "remote"
|
||||
image.md5sum = res.get("md5sum")
|
||||
image.filesize = res.get("filesize")
|
||||
image.md5sum = res.get("checksum")
|
||||
image.filesize = res.get("image_size")
|
||||
self._remote_images.append(image)
|
||||
self.image_list_changed_signal.emit()
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ else:
|
||||
|
||||
if sys.platform.startswith("linux"):
|
||||
distro_name = distro.name()
|
||||
if distro_name == "Debian" or distro_name == "Ubuntu" or distro_name == "LinuxMint":
|
||||
if distro_name == "Debian" or distro_name == "Ubuntu" or distro_name == "Linux Mint":
|
||||
if shutil.which("mate-terminal"):
|
||||
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Mate Terminal"]
|
||||
else:
|
||||
|
||||
@@ -47,6 +47,10 @@ class Style:
|
||||
Sets the legacy GUI style.
|
||||
"""
|
||||
|
||||
graphics_view = self._mw.uiGraphicsView
|
||||
if hasattr(graphics_view, 'resetGridColors'):
|
||||
graphics_view.resetGridColors()
|
||||
|
||||
self._mw.setStyleSheet("")
|
||||
self._mw.uiNewProjectAction.setIcon(QtGui.QIcon(":/icons/new-project.svg"))
|
||||
self._mw.uiOpenProjectAction.setIcon(QtGui.QIcon(":/icons/open.svg"))
|
||||
@@ -100,6 +104,10 @@ class Style:
|
||||
Sets the classic GUI style.
|
||||
"""
|
||||
|
||||
graphics_view = self._mw.uiGraphicsView
|
||||
if hasattr(graphics_view, 'resetGridColors'):
|
||||
graphics_view.resetGridColors()
|
||||
|
||||
self._mw.setStyleSheet("")
|
||||
self._mw.uiNewProjectAction.setIcon(self._getStyleIcon(":/classic_icons/new-project.svg", ":/classic_icons/new-project-hover.svg"))
|
||||
self._mw.uiOpenProjectAction.setIcon(self._getStyleIcon(":/classic_icons/open.svg", ":/classic_icons/open-hover.svg"))
|
||||
@@ -157,6 +165,10 @@ class Style:
|
||||
Sets the charcoal GUI style.
|
||||
"""
|
||||
|
||||
graphics_view = self._mw.uiGraphicsView
|
||||
if hasattr(graphics_view, 'resetGridColors'):
|
||||
graphics_view.resetGridColors()
|
||||
|
||||
style_file = QtCore.QFile(":/styles/charcoal.css")
|
||||
style_file.open(QtCore.QFile.ReadOnly)
|
||||
style = QtCore.QTextStream(style_file).readAll()
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>732</width>
|
||||
<height>329</height>
|
||||
<width>849</width>
|
||||
<height>328</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -26,7 +26,7 @@
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
@@ -90,6 +90,20 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiInstallAllPushButton">
|
||||
<property name="text">
|
||||
<string>&Install all</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiPruneImagesPushButton">
|
||||
<property name="text">
|
||||
<string>&Prune</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiRefreshImagesPushButton">
|
||||
<property name="text">
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/image_dialog.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
|
||||
@@ -14,13 +15,13 @@ class Ui_ImageDialog(object):
|
||||
def setupUi(self, ImageDialog):
|
||||
ImageDialog.setObjectName("ImageDialog")
|
||||
ImageDialog.setWindowModality(QtCore.Qt.ApplicationModal)
|
||||
ImageDialog.resize(732, 329)
|
||||
ImageDialog.resize(849, 328)
|
||||
ImageDialog.setModal(True)
|
||||
self.gridLayout = QtWidgets.QGridLayout(ImageDialog)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.uiImagesTreeWidget = QtWidgets.QTreeWidget(ImageDialog)
|
||||
self.uiImagesTreeWidget.setAlternatingRowColors(True)
|
||||
self.uiImagesTreeWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
self.uiImagesTreeWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
|
||||
self.uiImagesTreeWidget.setObjectName("uiImagesTreeWidget")
|
||||
self.uiImagesTreeWidget.header().setSortIndicatorShown(True)
|
||||
self.gridLayout.addWidget(self.uiImagesTreeWidget, 0, 0, 1, 1)
|
||||
@@ -38,6 +39,12 @@ class Ui_ImageDialog(object):
|
||||
self.horizontalLayout.addWidget(self.uiInstallApplianceCheckBox)
|
||||
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout.addItem(spacerItem)
|
||||
self.uiInstallAllPushButton = QtWidgets.QPushButton(ImageDialog)
|
||||
self.uiInstallAllPushButton.setObjectName("uiInstallAllPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiInstallAllPushButton)
|
||||
self.uiPruneImagesPushButton = QtWidgets.QPushButton(ImageDialog)
|
||||
self.uiPruneImagesPushButton.setObjectName("uiPruneImagesPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiPruneImagesPushButton)
|
||||
self.uiRefreshImagesPushButton = QtWidgets.QPushButton(ImageDialog)
|
||||
self.uiRefreshImagesPushButton.setObjectName("uiRefreshImagesPushButton")
|
||||
self.horizontalLayout.addWidget(self.uiRefreshImagesPushButton)
|
||||
@@ -49,8 +56,8 @@ class Ui_ImageDialog(object):
|
||||
self.gridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 1)
|
||||
|
||||
self.retranslateUi(ImageDialog)
|
||||
self.uiButtonBox.accepted.connect(ImageDialog.accept)
|
||||
self.uiButtonBox.rejected.connect(ImageDialog.reject)
|
||||
self.uiButtonBox.accepted.connect(ImageDialog.accept) # type: ignore
|
||||
self.uiButtonBox.rejected.connect(ImageDialog.reject) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(ImageDialog)
|
||||
|
||||
def retranslateUi(self, ImageDialog):
|
||||
@@ -63,4 +70,6 @@ class Ui_ImageDialog(object):
|
||||
self.uiUploadImagePushButton.setText(_translate("ImageDialog", "&Upload"))
|
||||
self.uiDeleteImagePushButton.setText(_translate("ImageDialog", "&Delete"))
|
||||
self.uiInstallApplianceCheckBox.setText(_translate("ImageDialog", "&Install appliance(s) after upload"))
|
||||
self.uiInstallAllPushButton.setText(_translate("ImageDialog", "&Install all"))
|
||||
self.uiPruneImagesPushButton.setText(_translate("ImageDialog", "&Prune"))
|
||||
self.uiRefreshImagesPushButton.setText(_translate("ImageDialog", "&Refresh"))
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
# or negative for a release candidate or beta (after the base version
|
||||
# number has been incremented)
|
||||
|
||||
__version__ = "3.0.0"
|
||||
__version_info__ = (3, 0, 0, 0)
|
||||
__version__ = "3.0.2"
|
||||
__version_info__ = (3, 0, 2, 0)
|
||||
|
||||
if "dev" in __version__:
|
||||
try:
|
||||
|
||||
@@ -10,7 +10,7 @@ authors = [
|
||||
{ name = "Jeremy Grossmann", email = "developers@gns3.com" }
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
requires-python = ">=3.9"
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: X11 Applications :: Qt",
|
||||
@@ -22,7 +22,6 @@ classifiers = [
|
||||
"Operating System :: MacOS :: MacOS X",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
jsonschema>=4.23,<4.24
|
||||
sentry-sdk>=2.17,<2.18 # optional dependency
|
||||
psutil>=6.1.0
|
||||
sentry-sdk>=2.19.2,<2.20 # optional dependency
|
||||
psutil>=6.1.1
|
||||
distro>=1.9.0
|
||||
truststore>=0.10.0; python_version >= '3.10'
|
||||
importlib-resources>=1.3; python_version < '3.9'
|
||||
|
||||
Reference in New Issue
Block a user