diff --git a/CHANGELOG b/CHANGELOG index 0433bd57..b522a79b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,20 @@ # Change Log +## 2.1.8 14/06/2018 + +* Add error information when cannot access/read IOS/IOU config file. Ref #2501 +* Fallback when using process name to bring console to front. +* Use process name to bring console to front. Fixes #2514. + +## 2.1.7 12/06/2018 + +* Do not try to update link if it is being deleted. Fixes #2483. +* Fix can't add SVG image to project. Fixes #2502 +* Remove unwanted trailing characters and other white spaces when reading .md5sum files. Fixes #2498. +* Update interface sequence number check. Fixes #2491. +* Logo should not have context menu, Fixes: #2507 +* Update logo position only when changes, Fixes: #2506 + ## 2.1.6 22/05/2018 * Ask for global variables when project is loaded diff --git a/gns3/compute_summary_view.py b/gns3/compute_summary_view.py index 9b1e4551..f406e462 100644 --- a/gns3/compute_summary_view.py +++ b/gns3/compute_summary_view.py @@ -61,7 +61,9 @@ class ComputeItem(QtWidgets.QTreeWidgetItem): text = "{} CPU {}%, RAM {}%".format(text, self._compute.cpuUsagePercent(), self._compute.memoryUsagePercent()) self.setText(0, text) - self.setToolTip(0, text + " on " + self._compute.capabilities().get("platform", "")) + self.setToolTip(0, "{} version {} running on {}".format(self._compute.name(), + self._compute.capabilities().get("version", "n/a"), + self._compute.capabilities().get("platform", ""))) if self._compute.connected(): self._status = "connected" diff --git a/gns3/crash_report.py b/gns3/crash_report.py index 153b91e2..ff973aa6 100644 --- a/gns3/crash_report.py +++ b/gns3/crash_report.py @@ -51,7 +51,7 @@ class CrashReport: Report crash to a third party service """ - DSN = "sync+https://22de49ca89514046acf5c2d4759e8b06:62e5d41b53ad46e09398c62bc281f588@sentry.io/38506" + DSN = "sync+https://08a47076d6cf4eebb455e8e79ef1e78d:ee721159025d4e2ea044e119a8789ece@sentry.io/38506" if hasattr(sys, "frozen"): cacert = get_resource("cacert.pem") if cacert is not None and os.path.isfile(cacert): diff --git a/gns3/dialogs/appliance_wizard.py b/gns3/dialogs/appliance_wizard.py index 39f98e5e..ef1d48b7 100644 --- a/gns3/dialogs/appliance_wizard.py +++ b/gns3/dialogs/appliance_wizard.py @@ -96,13 +96,18 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard): self.uiLocalRadioButton.setText("Install the appliance on the main server") else: if not path.endswith('.builtin.gns3a'): - destination = Config().appliances_dir + destination = None try: - os.makedirs(destination, exist_ok=True) - destination = os.path.join(destination, os.path.basename(path)) - shutil.copy(path, destination) + destination = Config().appliances_dir except OSError as e: - QtWidgets.QMessageBox.warning(self.parent(), "Cannot copy '{}' to '{}'".format(path, destination), str(e)) + QtWidgets.QMessageBox.critical(self.parent(), "Add appliance", "Invalid appliance file: {}".format(e)) + if destination: + try: + os.makedirs(destination, exist_ok=True) + destination = os.path.join(destination, os.path.basename(path)) + shutil.copy(path, destination) + except OSError as e: + QtWidgets.QMessageBox.warning(self.parent(), "Cannot copy {} to {}".format(path, destination), str(e)) self.uiServerWizardPage.isComplete = self._uiServerWizardPage_isComplete @@ -434,17 +439,20 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard): return image = Image(self._appliance.emulator(), path, filename=disk["filename"]) - disallow_custom_images = not self.allowCustomFiles.isChecked() - try: - if disallow_custom_images and ("md5sum" in disk and image.md5sum != disk["md5sum"]): - QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "This is not the correct file. The MD5 checksum is {} and it should be {}.".format(image.md5sum, disk["md5sum"])) - return + if "md5sum" in disk and image.md5sum != disk["md5sum"]: + reply = QtWidgets.QMessageBox.question(self, "Add appliance", + "This is not the correct file. The MD5 sum is {} and should be {}.\nDo you want to accept it at your own risks?".format(image.md5sum, disk["md5sum"]), + QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.No: + return except OSError as e: - QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "Cannot access to the image file '{}': {}.".format(path, str(e))) + QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "Can't access to the image file {}: {}.".format(path, str(e))) return - image_upload_manger = ImageUploadManager(image, Controller.instance(), self._compute_id, self._imageUploadedCallback, LocalConfig.instance().directFileUpload()) + image_upload_manger = ImageUploadManager( + image, Controller.instance(), self._compute_id, + self._imageUploadedCallback, LocalConfig.instance().directFileUpload()) image_upload_manger.upload() def _getQemuBinariesFromServerCallback(self, result, error=False, **kwargs): diff --git a/gns3/items/drawing_item.py b/gns3/items/drawing_item.py index 3fd109c1..2f3f2490 100644 --- a/gns3/items/drawing_item.py +++ b/gns3/items/drawing_item.py @@ -43,6 +43,7 @@ class DrawingItem: def __init__(self, project=None, pos=None, drawing_id=None, svg=None, z=0, rotation=0, **kws): self._id = drawing_id + self._deleting = False if self._id is None: self._id = str(uuid.uuid4()) self.setFlags(QtWidgets.QGraphicsItem.ItemIsMovable | QtWidgets.QGraphicsItem.ItemIsFocusable | QtWidgets.QGraphicsItem.ItemIsSelectable | QtWidgets.QGraphicsItem.ItemSendsGeometryChanges) @@ -81,13 +82,13 @@ class DrawingItem: """ if error: - log.error("Error while setting up drawing: {}".format(result["message"])) + log.error("Error while creating drawing: {}".format(result["message"])) return False self._id = result["drawing_id"] self.updateDrawingCallback(result) def updateDrawing(self): - if self._id: + if self._id and not self.deleting(): self._project.put("/drawings/" + self._id, self.updateDrawingCallback, body=self.__json__(), showProgress=False) @qslot @@ -101,7 +102,7 @@ class DrawingItem: """ if error: - log.error("Error while setting up drawing: {}".format(result["message"])) + log.error("Error while updating drawing: {}".format(result["message"])) return False self.setPos(QtCore.QPoint(result["x"], result["y"])) self.setZValue(result["z"]) @@ -171,6 +172,20 @@ class DrawingItem: else: self.setFlag(self.ItemIsMovable, True) + def deleting(self): + """ + Is the link being deleted + """ + + return self._deleting + + def setDeleting(self): + """ + Mark this drawing as being deleted + """ + + self._deleting = True + def delete(self, skip_controller=False): """ Deletes this drawing. @@ -178,6 +193,7 @@ class DrawingItem: :param skip_controller: Do not replicate change on the controller (usefull when it's already deleted on controller) """ + self.setDeleting() self.scene().removeItem(self) from ..topology import Topology Topology.instance().removeDrawing(self) diff --git a/gns3/modules/dynamips/pages/ios_router_configuration_page.py b/gns3/modules/dynamips/pages/ios_router_configuration_page.py index 11de6f37..e7078c80 100644 --- a/gns3/modules/dynamips/pages/ios_router_configuration_page.py +++ b/gns3/modules/dynamips/pages/ios_router_configuration_page.py @@ -31,6 +31,9 @@ from gns3.node import Node from ..ui.ios_router_configuration_page_ui import Ui_iosRouterConfigPageWidget from ..settings import CHASSIS, ADAPTER_MATRIX, WIC_MATRIX +import logging +log = logging.getLogger(__name__) + class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget): @@ -520,7 +523,7 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget if self._configFileValid(startup_config): settings["startup_config"] = startup_config else: - QtWidgets.QMessageBox.critical(self, "Startup-config", "Cannot read the startup-config file") + QtWidgets.QMessageBox.critical(self, "Startup-config", "Cannot access or read the startup-config file") private_config = self.uiPrivateConfigLineEdit.text().strip() if not private_config: @@ -529,7 +532,7 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget if self._configFileValid(private_config): settings["private_config"] = private_config else: - QtWidgets.QMessageBox.critical(self, "Private-config", "Cannot read the private-config file") + QtWidgets.QMessageBox.critical(self, "Private-config", "Cannot access or read the private-config file") symbol_path = self.uiSymbolLineEdit.text() settings["symbol"] = symbol_path @@ -645,6 +648,13 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget """ Return true if it's a valid configuration file """ + if not os.path.isabs(path): path = os.path.join(LocalServer.instance().localServerSettings()["configs_path"], path) - return os.access(path, os.R_OK) + result = os.access(path, os.R_OK) + if not result: + if not os.path.exists(path): + log.error("Cannot access config file '{}'".format(path)) + else: + log.error("Cannot read config file '{}'".format(path)) + return result diff --git a/gns3/modules/iou/pages/iou_device_configuration_page.py b/gns3/modules/iou/pages/iou_device_configuration_page.py index c212505a..30bdd5eb 100644 --- a/gns3/modules/iou/pages/iou_device_configuration_page.py +++ b/gns3/modules/iou/pages/iou_device_configuration_page.py @@ -30,6 +30,9 @@ from gns3.controller import Controller from gns3.utils.get_resource import get_resource from ..ui.iou_device_configuration_page_ui import Ui_iouDeviceConfigPageWidget +import logging +log = logging.getLogger(__name__) + class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget): @@ -269,7 +272,7 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget if self._configFileValid(startup_config): settings["startup_config"] = startup_config else: - QtWidgets.QMessageBox.critical(self, "Startup-config", "Cannot read the startup-config file") + QtWidgets.QMessageBox.critical(self, "Startup-config", "Cannot access or read the startup-config file") # save the private-config private_config = self.uiPrivateConfigLineEdit.text().strip() @@ -279,7 +282,7 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget if self._configFileValid(private_config): settings["private_config"] = private_config else: - QtWidgets.QMessageBox.critical(self, "Private-config", "Cannot read the private-config file") + QtWidgets.QMessageBox.critical(self, "Private-config", "Cannot access or read the private-config file") settings["symbol"] = self.uiSymbolLineEdit.text() settings["category"] = self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex()) @@ -319,4 +322,10 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget if not os.path.isabs(path): path = os.path.join(LocalServer.instance().localServerSettings()["configs_path"], path) - return os.access(path, os.R_OK) + result = os.access(path, os.R_OK) + if not result: + if not os.path.exists(path): + log.error("Cannot access config file '{}'".format(path)) + else: + log.error("Cannot read config file '{}'".format(path)) + return result diff --git a/gns3/node.py b/gns3/node.py index 8a7c3880..c7968bcf 100644 --- a/gns3/node.py +++ b/gns3/node.py @@ -21,7 +21,7 @@ import pathlib from gns3.controller import Controller from gns3.ports.ethernet_port import EthernetPort from gns3.ports.serial_port import SerialPort -from gns3.utils.bring_to_front import bring_window_to_front_from_title +from gns3.utils.bring_to_front import bring_window_to_front_from_process_name, bring_window_to_front_from_title from gns3.utils.normalize_filename import normalize_filename from gns3.qt import QtGui, QtCore @@ -695,6 +695,14 @@ class Node(BaseNode): """ if self.status() == Node.started: + console_command = self.consoleCommand() + if console_command: + process_name = console_command.split()[0] + if bring_window_to_front_from_process_name(process_name, self.name()): + return True + else: + log.debug("Could not find process name '' and window title '{}' to bring it to front".format(process_name, self.name())) + if bring_window_to_front_from_title(self.name()): return True else: diff --git a/gns3/registry/config.py b/gns3/registry/config.py index 6931c95b..600d6a7f 100644 --- a/gns3/registry/config.py +++ b/gns3/registry/config.py @@ -266,8 +266,8 @@ class Config: else: new_config["qemu_path"] = "qemu-system-{}".format(appliance_config["qemu"]["arch"]) - if "boot_priority" in appliance_config: - new_config["boot_priority"] = appliance_config["boot_priority"] + if "boot_priority" in appliance_config["qemu"]: + new_config["boot_priority"] = appliance_config["qemu"]["boot_priority"] if "first_port_name" in appliance_config: new_config["first_port_name"] = appliance_config["first_port_name"] diff --git a/gns3/settings.py b/gns3/settings.py index ac42d131..6d35fd2c 100644 --- a/gns3/settings.py +++ b/gns3/settings.py @@ -72,11 +72,15 @@ if sys.platform.startswith("win"): elif sys.platform.startswith("darwin"): # Mac OS X PRECONFIGURED_TELNET_CONSOLE_COMMANDS = { - 'Terminal': r"""osascript -e 'tell application "Terminal"'""" + 'Terminal': r"""osascript""" + r""" -e 'set posix_path to do shell script "echo \"$PATH\""'""" + r""" -e 'tell application "Terminal"'""" r""" -e 'activate'""" - r""" -e 'do script "echo -n -e \"\\033]0;%d\\007\"; clear; PATH=\"" & (system attribute "PATH") & "\" telnet %h %p ; exit"'""" + r""" -e 'do script "echo -n -e \"\\033]0;%d\\007\"; clear; PATH=" & quoted form of posix_path & " telnet %h %p ; exit"'""" r""" -e 'end tell'""", - 'Terminal tabbed (experimental)': r"""osascript -e 'tell application "Terminal"'""" + 'Terminal tabbed (experimental)': r"""osascript""" + r""" -e 'set posix_path to do shell script "echo \"$PATH\""'""" + r""" -e 'tell application "Terminal"'""" r""" -e 'activate'""" r""" -e 'tell application "System Events" to tell process "Terminal" to keystroke "t" using command down'""" r""" -e 'if (the (count of the window) = 0) then'""" @@ -88,9 +92,11 @@ elif sys.platform.startswith("darwin"): r""" -e 'repeat while the busy of window 1 = true'""" r""" -e 'delay 0.01'""" r""" -e 'end repeat'""" - r""" -e 'do script "echo -n -e \"\\033]0;%d\\007\"; clear; PATH=\"" & (system attribute "PATH") & "\" telnet %h %p ; exit" in window 1'""" + r""" -e 'do script "echo -n -e \"\\033]0;%d\\007\"; clear; PATH=" & quoted form of posix_path & " telnet %h %p ; exit" in window 1'""" r""" -e 'end tell'""", - 'iTerm2 2.x': r"""osascript -e 'tell application "iTerm"'""" + 'iTerm2 2.x': r"""osascript""" + r""" -e 'set posix_path to do shell script "echo \"$PATH\""'""" + r""" -e 'tell application "iTerm"'""" r""" -e 'activate'""" r""" -e 'if (count of terminals) = 0 then'""" r""" -e ' set t to (make new terminal)'""" @@ -100,12 +106,14 @@ elif sys.platform.startswith("darwin"): r""" -e 'tell t'""" r""" -e ' set s to (make new session at the end of sessions)'""" r""" -e ' tell s'""" - r""" -e ' exec command "sh -c \"PATH=\\\"" & (system attribute "PATH") & "\\\" telnet %h %p"'""" - + r""" -e ' exec command "sh"'""" + r""" -e ' write text "PATH=" & quoted form of posix_path & " exec telnet %h %p"'""" r""" -e ' end tell'""" r""" -e 'end tell'""" r""" -e 'end tell'""", - 'iTerm2 3.x': r"""osascript -e 'tell application "iTerm"'""" + 'iTerm2 3.x': r"""osascript""" + r""" -e 'set posix_path to do shell script "echo \"$PATH\""'""" + r""" -e 'tell application "iTerm"'""" r""" -e 'activate'""" r""" -e 'if (count of windows) = 0 then'""" r""" -e ' set t to (create window with default profile)'""" @@ -117,7 +125,7 @@ elif sys.platform.startswith("darwin"): r""" -e ' set s to current session'""" r""" -e ' tell s'""" r""" -e ' set name to "%d"'""" - r""" -e ' write text "PATH=\"" & (system attribute "PATH") & "\" exec telnet %h %p"'""" + r""" -e ' write text "PATH=" & quoted form of posix_path & " exec telnet %h %p"'""" r""" -e ' end tell'""" r""" -e 'end tell'""" r""" -e 'end tell'""", diff --git a/gns3/utils/bring_to_front.py b/gns3/utils/bring_to_front.py index ba87beb7..9437260c 100644 --- a/gns3/utils/bring_to_front.py +++ b/gns3/utils/bring_to_front.py @@ -62,10 +62,13 @@ def bring_window_to_front_from_process_name(process_name, title=None): for hwnd in get_windows_from_pid(proc.pid): if title is None: set_foreground_window(hwnd) + return True elif title in win32gui.GetWindowText(hwnd): set_foreground_window(hwnd) + return True except psutil.Error: continue + return False def bring_window_to_front_from_pid(pid): diff --git a/tests/registry/test_config.py b/tests/registry/test_config.py index 4131e6c2..bacc0eab 100644 --- a/tests/registry/test_config.py +++ b/tests/registry/test_config.py @@ -344,7 +344,7 @@ def test_add_appliance_with_boot_priority(empty_config, linux_microcore_img): "path": linux_microcore_img } ] - config["boot_priority"] = "dc" + config["qemu"]["boot_priority"] = "dc" empty_config.add_appliance(config, "local") assert empty_config._config["Qemu"]["vms"][0]["boot_priority"] == "dc"