Skip to content

Commit

Permalink
Merge pull request #22328 from ccordoba12/show-connection-logs
Browse files Browse the repository at this point in the history
PR: Add a widget to show connection logs to `ConnectionDialog` (Remote client)
  • Loading branch information
ccordoba12 authored Aug 6, 2024
2 parents f80b878 + 29dcbca commit 622a1ae
Show file tree
Hide file tree
Showing 14 changed files with 343 additions and 76 deletions.
18 changes: 14 additions & 4 deletions spyder/api/widgets/dialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
from qtpy.QtGui import QIcon
from qtpy.QtWidgets import QDialogButtonBox

# Local imports
from spyder.utils.stylesheet import AppStyle


class SpyderDialogButtonBox(QDialogButtonBox):
"""
QDialogButtonBox widget for Spyder that doesn't display icons on its
standard buttons.
"""
"""QDialogButtonBox widget for Spyder."""

def __init__(self, buttons=None, orientation=Qt.Horizontal, parent=None):
if buttons:
Expand All @@ -28,6 +28,7 @@ def __init__(self, buttons=None, orientation=Qt.Horizontal, parent=None):
else:
super().__init__(parent=parent)

# Don't display icons on standard buttons. This is a problem on Linux
button_constants = [
QDialogButtonBox.Ok,
QDialogButtonBox.Open,
Expand All @@ -53,3 +54,12 @@ def __init__(self, buttons=None, orientation=Qt.Horizontal, parent=None):
button = self.button(constant)
if button is not None:
button.setIcon(QIcon())

# Set a reasonable spacing between buttons. This is a problem on Mac
self.layout().setSpacing(2 * AppStyle.MarginSize)

# Use the Windows buttons layout to have a uniform layout in all
# platforms. We selected that layout because Windows is our most
# popular platform.
# Solution found in https://stackoverflow.com/a/35907926/438386
self.setStyleSheet("* {button-layout: 0}")
28 changes: 16 additions & 12 deletions spyder/config/appearance.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@
import os
import sys

from qdarkstyle.dark.palette import DarkPalette
from qdarkstyle.light.palette import LightPalette

from spyder.config.base import running_under_pytest
from spyder.config.fonts import MEDIUM, MONOSPACE
from spyder.plugins.help.utils.sphinxify import CSS_PATH


WIN = os.name == 'nt'
LINUX = sys.platform.startswith('linux')

Expand Down Expand Up @@ -74,15 +78,15 @@
# ---- IDLE ----
'idle/name': "IDLE",
# Name Color Bold Italic
'idle/background': "#ffffff",
'idle/background': LightPalette.COLOR_BACKGROUND_1,
'idle/currentline': "#f2e6f3",
'idle/currentcell': "#feefff",
'idle/occurrence': "#C2E3FA",
'idle/ctrlclick': "#0000ff",
'idle/sideareas': "#efefef",
'idle/matched_p': "#99ff99",
'idle/unmatched_p': "#ff9999",
'idle/normal': ('#000000', False, False),
'idle/normal': (LightPalette.COLOR_TEXT_1, False, False),
'idle/keyword': ('#ff7700', True, False),
'idle/magic': ('#ff7700', True, False),
'idle/builtin': ('#900090', False, False),
Expand Down Expand Up @@ -114,15 +118,15 @@
# ---- Pydev ----
'pydev/name': "Pydev",
# Name Color Bold Italic
'pydev/background': "#ffffff",
'pydev/background': LightPalette.COLOR_BACKGROUND_1,
'pydev/currentline': "#e8f2fe",
'pydev/currentcell': "#eff8fe",
'pydev/occurrence': "#C2E3FA",
'pydev/ctrlclick': "#0000ff",
'pydev/sideareas': "#efefef",
'pydev/matched_p': "#99ff99",
'pydev/unmatched_p': "#ff99992",
'pydev/normal': ('#000000', False, False),
'pydev/normal': (LightPalette.COLOR_TEXT_1, False, False),
'pydev/keyword': ('#0000ff', False, False),
'pydev/magic': ('#0000ff', False, False),
'pydev/builtin': ('#900090', False, False),
Expand All @@ -134,15 +138,15 @@
# ---- Scintilla ----
'scintilla/name': "Scintilla",
# Name Color Bold Italic
'scintilla/background': "#ffffff",
'scintilla/background': LightPalette.COLOR_BACKGROUND_1,
'scintilla/currentline': "#e1f0d1",
'scintilla/currentcell': "#edfcdc",
'scintilla/occurrence': "#C2E3FA",
'scintilla/ctrlclick': "#0000ff",
'scintilla/sideareas': "#efefef",
'scintilla/matched_p': "#99ff99",
'scintilla/unmatched_p': "#ff9999",
'scintilla/normal': ('#000000', False, False),
'scintilla/normal': (LightPalette.COLOR_TEXT_1, False, False),
'scintilla/keyword': ('#00007f', True, False),
'scintilla/magic': ('#00007f', True, False),
'scintilla/builtin': ('#000000', False, False),
Expand All @@ -154,15 +158,15 @@
# ---- Spyder ----
'spyder/name': "Spyder",
# Name Color Bold Italic
'spyder/background': "#ffffff",
'spyder/background': LightPalette.COLOR_BACKGROUND_1,
'spyder/currentline': "#f7ecf8",
'spyder/currentcell': "#fdfdde",
'spyder/occurrence': "#C2E3FA",
'spyder/ctrlclick': "#0000ff",
'spyder/sideareas': "#efefef",
'spyder/matched_p': "#99ff99",
'spyder/unmatched_p': "#ff9999",
'spyder/normal': ('#000000', False, False),
'spyder/normal': (LightPalette.COLOR_TEXT_1, False, False),
'spyder/keyword': ('#0000ff', False, False),
'spyder/magic': ('#0000ff', False, False),
'spyder/builtin': ('#900090', False, False),
Expand All @@ -182,7 +186,7 @@
'spyder/dark/sideareas': "#222b35",
'spyder/dark/matched_p': "#0bbe0b",
'spyder/dark/unmatched_p': "#ff4340",
'spyder/dark/normal': ('#ffffff', False, False),
'spyder/dark/normal': (DarkPalette.COLOR_TEXT_1, False, False),
'spyder/dark/keyword': ('#c670e0', False, False),
'spyder/dark/magic': ('#c670e0', False, False),
'spyder/dark/builtin': ('#fab16c', False, False),
Expand Down Expand Up @@ -274,15 +278,15 @@
# ---- minimal (Eclipse color theme) ----
'minimal/name': "Minimal",
# Name Color Bold Italic
'minimal/background': "#ffffff",
'minimal/background': LightPalette.COLOR_BACKGROUND_1,
'minimal/currentline': "#aaccff",
'minimal/currentcell': "#E7F1FF",
'minimal/occurrence': "#C2E3FA",
'minimal/ctrlclick': "#05314d",
'minimal/sideareas': "#aaccff",
'minimal/matched_p': "#000000",
'minimal/unmatched_p': "#efefff",
'minimal/normal': ('#000000', False, False),
'minimal/normal': (LightPalette.COLOR_TEXT_1, False, False),
'minimal/keyword': ('#5c8198', False, False),
'minimal/magic': ('#5c8198', False, False),
'minimal/builtin': ('#000066', False, False),
Expand Down Expand Up @@ -314,7 +318,7 @@
# ---- Notepad++ (Eclipse color theme) ----
'notepad++/name': "Notepad++",
# Name Color Bold Italic
'notepad++/background': "#ffffff",
'notepad++/background': LightPalette.COLOR_BACKGROUND_1,
'notepad++/currentline': "#eeeeee",
'notepad++/currentcell': "#D9D9D9",
'notepad++/occurrence': "#C2E3FA",
Expand Down
2 changes: 1 addition & 1 deletion spyder/config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -672,4 +672,4 @@
# or if you want to *rename* options, then you need to do a MAJOR update in
# version, e.g. from 3.0.0 to 4.0.0
# 3. You don't need to touch this value if you're just adding a new option
CONF_VERSION = '84.0.0'
CONF_VERSION = '84.1.0'
2 changes: 1 addition & 1 deletion spyder/plugins/findinfiles/widgets/main_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
MAX_PATH_HISTORY, SearchInComboBox)
from spyder.plugins.findinfiles.widgets.search_thread import SearchThread
from spyder.utils.misc import regexp_error_msg
from spyder.utils.palette import SpyderPalette, SpyderPalette
from spyder.utils.palette import SpyderPalette
from spyder.utils.stylesheet import AppStyle
from spyder.widgets.comboboxes import PatternComboBox
from spyder.widgets.helperwidgets import PaneEmptyWidget
Expand Down
2 changes: 1 addition & 1 deletion spyder/plugins/findinfiles/widgets/tests/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
SearchInComboBoxItems
)
from spyder.plugins.findinfiles.widgets.search_thread import SearchThread
from spyder.utils.palette import SpyderPalette, SpyderPalette
from spyder.utils.palette import SpyderPalette
from spyder.utils.stylesheet import APP_STYLESHEET


Expand Down
5 changes: 5 additions & 0 deletions spyder/plugins/remoteclient/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@

# ---- Constants
# -----------------------------------------------------------------------------

# Max number of logged messages from the client that will be saved.
MAX_CLIENT_MESSAGES = 1000


class RemoteClientActions:
ManageConnections = "manage connections"

Expand Down
14 changes: 13 additions & 1 deletion spyder/plugins/remoteclient/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import asyncssh

from spyder.api.translations import _
from spyder.config.base import get_debug_level
from spyder.plugins.remoteclient.api.jupyterhub import JupyterAPI
from spyder.plugins.remoteclient.api.protocol import (
ConnectionInfo,
Expand All @@ -33,8 +34,14 @@ def __init__(self, client, *args, **kwargs):
self._client = client
super().__init__(*args, **kwargs)

log_format = "%(message)s — %(asctime)s"
formatter = logging.Formatter(
log_format, datefmt="%H:%M:%S %d/%m/%Y"
)
self.setFormatter(formatter)

def emit(self, record):
self._client._plugin.sig_server_log.emit(
self._client._plugin.sig_client_message_logged.emit(
RemoteClientLog(
id=self._client.config_id,
message=self.format(record),
Expand Down Expand Up @@ -72,9 +79,14 @@ def __init__(self, conf_id, options: SSHClientOptions, _plugin=None):
self._port_forwarder: asyncssh.SSHListener = None
self._server_info = {}

# For logging
self._logger = logging.getLogger(
f"{__name__}.{self.__class__.__name__}({self.config_id})"
)

if not get_debug_level():
self._logger.setLevel(logging.DEBUG)

if self._plugin is not None:
self._logger.addHandler(SpyderRemoteClientLoggerHandler(self))

Expand Down
5 changes: 4 additions & 1 deletion spyder/plugins/remoteclient/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ class RemoteClient(SpyderPluginV2):
CONF_SECTION_SERVERS = "servers"

# ---- Signals
sig_server_log = Signal(dict)
sig_server_stopped = Signal(str)
sig_server_renamed = Signal(str)
sig_client_message_logged = Signal(dict)

sig_connection_established = Signal(str)
sig_connection_lost = Signal(str)
Expand Down Expand Up @@ -109,6 +109,9 @@ def on_initialize(self):
self.sig_connection_status_changed.connect(
container.sig_connection_status_changed
)
self.sig_client_message_logged.connect(
container.sig_client_message_logged
)
self._sig_kernel_started.connect(container.on_kernel_started)

def on_first_registration(self):
Expand Down
43 changes: 35 additions & 8 deletions spyder/plugins/remoteclient/widgets/connectiondialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

# Standard library imports
from __future__ import annotations
from collections.abc import Iterable
import re
from typing import TypedDict
import uuid
Expand Down Expand Up @@ -36,6 +37,7 @@
from spyder.plugins.remoteclient.api.protocol import (
ConnectionInfo,
ConnectionStatus,
RemoteClientLog,
SSHClientOptions,
)
from spyder.plugins.remoteclient.widgets import AuthenticationMethod
Expand Down Expand Up @@ -187,7 +189,7 @@ def validate_page(self):
if not widget.textbox.text():
# Validate that the required fields are not empty
widget.status_action.setVisible(True)
widget.status_action.setToolTip("")
widget.status_action.setToolTip(_("This field is empty"))
reasons["missing_info"] = True
elif widget == self._name_widgets[auth_method]:
# Validate the server name is different from the ones already
Expand Down Expand Up @@ -695,6 +697,13 @@ def update_status(self, info: ConnectionInfo):
self.status = info["status"]
self.status_widget.update_status(info)

def add_log(self, log: RemoteClientLog):
if log["id"] == self.host_id:
self.status_widget.add_log(log)

def add_logs(self, logs: Iterable):
self.status_widget.add_logs(logs)

def has_new_name(self):
"""Check if users changed the connection name."""
current_auth_method = self.auth_method(from_gui=True)
Expand Down Expand Up @@ -724,16 +733,27 @@ class ConnectionDialog(SidebarDialog):
sig_start_server_requested = Signal(str)
sig_stop_server_requested = Signal(str)
sig_server_renamed = Signal(str)
sig_connection_status_changed = Signal(dict)
sig_connections_changed = Signal()

def __init__(self, parent=None):
super().__init__(parent)
self._container = parent

# -- Setup
self._add_saved_connection_pages()
self.sig_connection_status_changed.connect(
self._update_connection_buttons_state
)

# If there's more than one page, give focus to the first server because
# users will probably want to interact with servers here rather than
# create new connections.
if self.number_of_pages() > 1:
# Index 1 is the separator added after the new connection page
self.set_current_index(2)

# -- Signals
if self._container is not None:
self._container.sig_connection_status_changed.connect(
self._update_connection_buttons_state
)

# ---- SidebarDialog API
# -------------------------------------------------------------------------
Expand Down Expand Up @@ -928,14 +948,21 @@ def _add_connection_page(self, host_id: str, new: bool):
self._update_button_save_connection_state
)

# This updates the info shown in the "Connection info" tab of pages
self.sig_connection_status_changed.connect(page.update_status)

if new:
page.save_server_info()

self.add_page(page)

# Add saved logs to the page
if self._container is not None:
page.add_logs(self._container.client_logs.get(host_id, []))

# This updates the info shown in the "Connection info" tab of pages
self._container.sig_connection_status_changed.connect(
page.update_status
)
self._container.sig_client_message_logged.connect(page.add_log)

def _add_saved_connection_pages(self):
"""Add a connection page for each server saved in our config system."""
page = self.get_page(index=0)
Expand Down
Loading

0 comments on commit 622a1ae

Please sign in to comment.