Skip to content

Commit d49c77c

Browse files
committed
Remote Client: Add optional fields for config file based connection
1 parent ed3079f commit d49c77c

File tree

3 files changed

+100
-18
lines changed

3 files changed

+100
-18
lines changed

spyder/plugins/remoteclient/api/manager.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,11 @@ async def __create_new_connection(self) -> bool:
621621
self._ssh_connection = await asyncssh.connect(
622622
**connect_kwargs, client_factory=self.client_factory
623623
)
624-
except (OSError, asyncssh.Error) as e:
624+
except (
625+
OSError,
626+
asyncssh.Error,
627+
asyncssh.public_key.KeyImportError,
628+
) as e:
625629
self.logger.error(f"Failed to open ssh connection: {e}")
626630
self.__emit_connection_status(
627631
ConnectionStatus.Error,

spyder/plugins/remoteclient/plugin.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -213,19 +213,39 @@ def load_conf(self, config_id):
213213
if not options:
214214
return {}
215215

216-
if options["client_keys"]:
216+
if options["config"]:
217+
# Only `host` and list with path to config file as `config`
218+
# are required
219+
options["config"] = [options["config"]]
220+
221+
# Optional configuration vslues mapping (`port`, `username`,
222+
# `client_keys`, `passphrase` and `password`)
223+
if options["port"] == 0:
224+
# If 0 is set (`From file` option) ignore value
225+
options.pop("port")
226+
227+
if not options["username"]:
228+
options.pop("username")
229+
230+
if not options["client_keys"]:
231+
options.pop("client_keys")
232+
else:
233+
options["client_keys"] = [options["client_keys"]]
234+
235+
passphrase = self.get_conf(f"{config_id}/passphrase", secure=True)
236+
if passphrase:
237+
options["passphrase"] = passphrase
238+
239+
password = self.get_conf(f"{config_id}/password", secure=True)
240+
if password:
241+
options["password"] = password
242+
elif options["client_keys"]:
217243
passphrase = self.get_conf(f"{config_id}/passphrase", secure=True)
218244
options["client_keys"] = [options["client_keys"]]
219245

220246
# Passphrase is optional
221247
if passphrase:
222248
options["passphrase"] = passphrase
223-
elif options["config"]:
224-
# Should have only `host` and list with path to config file as
225-
# `config`
226-
options.pop("port")
227-
options.pop("username")
228-
options.pop("client_keys")
229249
else:
230250
# Password is mandatory in this case
231251
password = self.get_conf(f"{config_id}/password", secure=True)

spyder/plugins/remoteclient/widgets/connectiondialog.py

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class ValidationReasons(TypedDict):
6565
class BaseConnectionPage(SpyderConfigPage, SpyderFontsMixin):
6666
"""Base class to create connection pages."""
6767

68-
MIN_HEIGHT = 450
68+
MIN_HEIGHT = 700
6969
NEW_CONNECTION = False
7070
CONF_SECTION = "remoteclient"
7171

@@ -186,8 +186,6 @@ def create_connection_info_widget(self):
186186
intro_layout.addWidget(intro_tip)
187187

188188
# Authentication methods
189-
# TODO: The config file method is not implemented yet, so we need to
190-
# disable it for now.
191189
methods = (
192190
(_('Password'), AuthenticationMethod.Password),
193191
(_('Key file'), AuthenticationMethod.KeyFile),
@@ -423,6 +421,14 @@ def _create_configfile_subpage(self):
423421
status_icon=ima.icon("error"),
424422
)
425423

424+
configfile = self.create_browsefile(
425+
text=_("Configuration file *"),
426+
option=f"{self.host_id}/configfile",
427+
tip=_("File with the OpenSSH client configuration to use"),
428+
alignment=Qt.Vertical,
429+
status_icon=ima.icon("error"),
430+
)
431+
426432
host = self.create_lineedit(
427433
text=_("Host *"),
428434
option=f"{self.host_id}/{AuthenticationMethod.ConfigFile}/address",
@@ -434,14 +440,55 @@ def _create_configfile_subpage(self):
434440
status_icon=ima.icon("error"),
435441
)
436442

437-
configfile = self.create_browsefile(
438-
text=_("Configuration file *"),
439-
option=f"{self.host_id}/configfile",
440-
tip=_("File with the OpenSSH client configuration to use"),
443+
port = self.create_spinbox(
444+
prefix=_("Port"),
445+
suffix="",
446+
option=f"{self.host_id}/{AuthenticationMethod.ConfigFile}/port",
447+
min_=0,
448+
max_=65535,
449+
tip=_("Introduce a the port to use for your connection."
450+
"Set <b>From file</b> (<b>0</b> value) to use the port "
451+
"configuration from the configuration file"),
452+
)
453+
port.spinbox.setSpecialValueText(_("From file"))
454+
port.spinbox.setStyleSheet("margin-left: 5px")
455+
456+
username = self.create_lineedit(
457+
text=_("Username"),
458+
option=f"{self.host_id}/{AuthenticationMethod.ConfigFile}/username",
459+
status_icon=ima.icon("error"),
460+
)
461+
462+
password = self.create_lineedit(
463+
text=_("Password"),
464+
option=f"{self.host_id}/password",
465+
tip=(
466+
_("Your password will be saved securely by Spyder")
467+
if self.NEW_CONNECTION
468+
else _("Your password is saved securely by Spyder")
469+
),
470+
status_icon=ima.icon("error"),
471+
password=True
472+
)
473+
474+
keyfile = self.create_browsefile(
475+
text=_("Key file"),
476+
option=f"{self.host_id}/keyfile",
441477
alignment=Qt.Vertical,
442478
status_icon=ima.icon("error"),
443479
)
444480

481+
passphrase = self.create_lineedit(
482+
text=_("Passphrase"),
483+
option=f"{self.host_id}/passphrase",
484+
tip=(
485+
_("Your passphrase will be saved securely by Spyder")
486+
if self.NEW_CONNECTION
487+
else _("Your passphrase is saved securely by Spyder")
488+
),
489+
password=True
490+
)
491+
445492
validation_label = MessageLabel(self)
446493

447494
# Add widgets to their required dicts
@@ -460,9 +507,19 @@ def _create_configfile_subpage(self):
460507
configfile_layout.setContentsMargins(0, 0, 0, 0)
461508
configfile_layout.addWidget(name)
462509
configfile_layout.addSpacing(5 * AppStyle.MarginSize)
510+
configfile_layout.addWidget(configfile)
511+
configfile_layout.addSpacing(5 * AppStyle.MarginSize)
463512
configfile_layout.addWidget(host)
464513
configfile_layout.addSpacing(5 * AppStyle.MarginSize)
465-
configfile_layout.addWidget(configfile)
514+
configfile_layout.addWidget(port)
515+
configfile_layout.addSpacing(5 * AppStyle.MarginSize)
516+
configfile_layout.addWidget(username)
517+
configfile_layout.addSpacing(5 * AppStyle.MarginSize)
518+
configfile_layout.addWidget(password)
519+
configfile_layout.addSpacing(5 * AppStyle.MarginSize)
520+
configfile_layout.addWidget(keyfile)
521+
configfile_layout.addSpacing(5 * AppStyle.MarginSize)
522+
configfile_layout.addWidget(passphrase)
466523
configfile_layout.addSpacing(7 * AppStyle.MarginSize)
467524
configfile_layout.addWidget(validation_label)
468525
configfile_layout.addStretch()
@@ -636,14 +693,13 @@ def save_server_info(self):
636693
),
637694
port=self.get_option(
638695
f"{self.host_id}/{self.auth_method()}/port",
639-
default=""
640696
),
641697
username=self.get_option(
642698
f"{self.host_id}/{self.auth_method()}/username",
643699
default=""
644700
),
645701
client_keys=self.get_option(f"{self.host_id}/keyfile", default=""),
646-
config=[self.get_option(f"{self.host_id}/configfile")],
702+
config=self.get_option(f"{self.host_id}/configfile"),
647703
)
648704

649705
servers = self.get_option("servers", default={})
@@ -671,6 +727,8 @@ def remove_config_options(self):
671727
"keyfile",
672728
"configfile_login/name",
673729
"configfile_login/address",
730+
"configfile_login/port",
731+
"configfile_login/username",
674732
"configfile",
675733
]
676734
for option in options:

0 commit comments

Comments
 (0)