Client SSH/SFTP personnel pour Windows
Reunit en un seul outil les fonctions utiles de MobaXterm, Termius et Bitvise, sans compte ni service tiers, avec toutes les donnees en local. - Terminaux multi-onglets : sessions SSH et terminaux Windows locaux (PowerShell / cmd) via la pseudo-console native ConPTY - Gestionnaire de fichiers SFTP facon WinSCP : double panneau, colonnes triables, glisser-deposer, renommage, permissions, file de transferts. Ouvrable sur une connexion en cours sans creer de favori - Gestion des cles SSH : generation Ed25519/RSA, import, export - Gestion du fichier known_hosts, avec remplacement guide d'une cle d'hote modifiee (cas frequent des serveurs en DHCP) - Trois themes (clair, bleu nuit, noir), commutables a chaud - Authentification automatique reprenant le comportement de la commande ssh (agent SSH et cles ~/.ssh), mot de passe demande seulement en secours Aucun mot de passe n'est stocke sur le disque. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
0
app/dialogs/__init__.py
Normal file
0
app/dialogs/__init__.py
Normal file
42
app/dialogs/connect_prompt.py
Normal file
42
app/dialogs/connect_prompt.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""Dialogues utilises au moment de la connexion : saisie de secret et confirmation de cle d'hote."""
|
||||
from __future__ import annotations
|
||||
|
||||
from PySide6.QtWidgets import QInputDialog, QLineEdit, QMessageBox, QWidget
|
||||
|
||||
|
||||
def ask_secret(parent: QWidget, title: str, label: str) -> tuple[str, bool]:
|
||||
text, ok = QInputDialog.getText(parent, title, label, QLineEdit.Password)
|
||||
return text, ok
|
||||
|
||||
|
||||
def confirm_host_key(parent: QWidget, hostname: str, key_type: str, fingerprint: str) -> bool:
|
||||
msg = (
|
||||
f"L'empreinte de la cle du serveur '{hostname}' n'est pas connue.\n\n"
|
||||
f"Type : {key_type}\n"
|
||||
f"Empreinte : {fingerprint}\n\n"
|
||||
"Verifiez cette empreinte aupres de l'administrateur du serveur si vous n'etes pas certain.\n"
|
||||
"Faire confiance a cette cle et se connecter ?"
|
||||
)
|
||||
answer = QMessageBox.question(
|
||||
parent, "Nouvelle cle d'hote detectee", msg,
|
||||
QMessageBox.Yes | QMessageBox.No, QMessageBox.No,
|
||||
)
|
||||
return answer == QMessageBox.Yes
|
||||
|
||||
|
||||
def confirm_host_key_change(parent: QWidget, hostname: str, key_type: str, old_fingerprint: str, new_fingerprint: str) -> bool:
|
||||
msg = (
|
||||
f"La cle presentee par le serveur '{hostname}' ne correspond plus a celle enregistree.\n\n"
|
||||
f"Ancienne empreinte : {old_fingerprint}\n"
|
||||
f"Nouvelle empreinte : {new_fingerprint}\n\n"
|
||||
"C'est generalement normal si ce serveur a une adresse IP attribuee par DHCP et "
|
||||
"qu'elle a ete reutilisee par une autre machine, ou si le serveur a ete reinstalle. "
|
||||
"Mais cela peut aussi indiquer une interception de la connexion (attaque de type "
|
||||
"'man-in-the-middle') : verifiez la nouvelle empreinte si vous avez un doute.\n\n"
|
||||
"Remplacer l'ancienne cle enregistree et se connecter ?"
|
||||
)
|
||||
answer = QMessageBox.warning(
|
||||
parent, "Cle d'hote modifiee", msg,
|
||||
QMessageBox.Yes | QMessageBox.No, QMessageBox.No,
|
||||
)
|
||||
return answer == QMessageBox.Yes
|
||||
198
app/dialogs/key_dialog.py
Normal file
198
app/dialogs/key_dialog.py
Normal file
@@ -0,0 +1,198 @@
|
||||
"""Gestionnaire de cles SSH : generation, import, export."""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QHBoxLayout, QListWidget, QListWidgetItem, QPushButton,
|
||||
QMessageBox, QFileDialog, QFormLayout, QLineEdit, QComboBox, QSpinBox,
|
||||
QDialogButtonBox, QApplication, QLabel,
|
||||
)
|
||||
|
||||
from app.key_manager import KeyManager, KEY_TYPE_ED25519, KEY_TYPE_RSA
|
||||
|
||||
|
||||
class GenerateKeyDialog(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Generer une nouvelle cle SSH")
|
||||
layout = QVBoxLayout(self)
|
||||
form = QFormLayout()
|
||||
|
||||
self.name_edit = QLineEdit()
|
||||
self.type_combo = QComboBox()
|
||||
self.type_combo.addItem("Ed25519 (recommande)", KEY_TYPE_ED25519)
|
||||
self.type_combo.addItem("RSA", KEY_TYPE_RSA)
|
||||
self.bits_spin = QSpinBox()
|
||||
self.bits_spin.setRange(2048, 8192)
|
||||
self.bits_spin.setSingleStep(1024)
|
||||
self.bits_spin.setValue(4096)
|
||||
self.passphrase_edit = QLineEdit()
|
||||
self.passphrase_edit.setEchoMode(QLineEdit.Password)
|
||||
self.comment_edit = QLineEdit()
|
||||
|
||||
form.addRow("Nom :", self.name_edit)
|
||||
form.addRow("Type :", self.type_combo)
|
||||
form.addRow("Taille (RSA) :", self.bits_spin)
|
||||
form.addRow("Passphrase (optionnel) :", self.passphrase_edit)
|
||||
form.addRow("Commentaire :", self.comment_edit)
|
||||
layout.addLayout(form)
|
||||
|
||||
self.type_combo.currentIndexChanged.connect(
|
||||
lambda: self.bits_spin.setEnabled(self.type_combo.currentData() == KEY_TYPE_RSA)
|
||||
)
|
||||
self.bits_spin.setEnabled(False)
|
||||
|
||||
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
buttons.accepted.connect(self.accept)
|
||||
buttons.rejected.connect(self.reject)
|
||||
layout.addWidget(buttons)
|
||||
|
||||
def values(self) -> dict:
|
||||
return dict(
|
||||
name=self.name_edit.text().strip() or "cle_ssh",
|
||||
key_type=self.type_combo.currentData(),
|
||||
rsa_bits=self.bits_spin.value(),
|
||||
passphrase=self.passphrase_edit.text(),
|
||||
comment=self.comment_edit.text().strip(),
|
||||
)
|
||||
|
||||
|
||||
class KeyManagerDialog(QDialog):
|
||||
def __init__(self, key_manager: KeyManager, parent=None):
|
||||
super().__init__(parent)
|
||||
self.key_manager = key_manager
|
||||
self.setWindowTitle("Gestionnaire de cles SSH")
|
||||
self.setMinimumSize(560, 360)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
self.list = QListWidget()
|
||||
layout.addWidget(self.list)
|
||||
|
||||
self.info_label = QLabel("")
|
||||
self.info_label.setWordWrap(True)
|
||||
layout.addWidget(self.info_label)
|
||||
|
||||
btn_row = QHBoxLayout()
|
||||
self.generate_btn = QPushButton("Generer...")
|
||||
self.import_btn = QPushButton("Importer...")
|
||||
self.export_pub_btn = QPushButton("Exporter cle publique...")
|
||||
self.export_priv_btn = QPushButton("Exporter cle privee...")
|
||||
self.copy_pub_btn = QPushButton("Copier cle publique")
|
||||
self.remove_btn = QPushButton("Supprimer")
|
||||
for b in (self.generate_btn, self.import_btn, self.export_pub_btn,
|
||||
self.export_priv_btn, self.copy_pub_btn, self.remove_btn):
|
||||
btn_row.addWidget(b)
|
||||
layout.addLayout(btn_row)
|
||||
|
||||
close_row = QHBoxLayout()
|
||||
close_row.addStretch()
|
||||
close_btn = QPushButton("Fermer")
|
||||
close_btn.clicked.connect(self.accept)
|
||||
close_row.addWidget(close_btn)
|
||||
layout.addLayout(close_row)
|
||||
|
||||
self.generate_btn.clicked.connect(self.generate_key)
|
||||
self.import_btn.clicked.connect(self.import_key)
|
||||
self.export_pub_btn.clicked.connect(self.export_public)
|
||||
self.export_priv_btn.clicked.connect(self.export_private)
|
||||
self.copy_pub_btn.clicked.connect(self.copy_public)
|
||||
self.remove_btn.clicked.connect(self.remove_key)
|
||||
self.list.currentItemChanged.connect(self._update_info)
|
||||
|
||||
self.refresh()
|
||||
|
||||
def refresh(self) -> None:
|
||||
self.list.clear()
|
||||
for entry in self.key_manager.keys:
|
||||
item = QListWidgetItem(f"{entry.name} [{entry.key_type}]")
|
||||
item.setData(Qt.UserRole, entry.id)
|
||||
self.list.addItem(item)
|
||||
self.info_label.setText("")
|
||||
|
||||
def _current_entry(self):
|
||||
item = self.list.currentItem()
|
||||
if not item:
|
||||
return None
|
||||
key_id = item.data(Qt.UserRole)
|
||||
return next((k for k in self.key_manager.keys if k.id == key_id), None)
|
||||
|
||||
def _update_info(self) -> None:
|
||||
entry = self._current_entry()
|
||||
if not entry:
|
||||
self.info_label.setText("")
|
||||
return
|
||||
self.info_label.setText(
|
||||
f"Chemin : {entry.private_path}\nEmpreinte : {entry.fingerprint}\n\n{entry.public_key_text}"
|
||||
)
|
||||
|
||||
def generate_key(self) -> None:
|
||||
dlg = GenerateKeyDialog(self)
|
||||
if dlg.exec() != QDialog.Accepted:
|
||||
return
|
||||
vals = dlg.values()
|
||||
try:
|
||||
self.key_manager.generate_key(**vals)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
QMessageBox.critical(self, "Erreur", str(exc))
|
||||
return
|
||||
self.refresh()
|
||||
|
||||
def import_key(self) -> None:
|
||||
priv_path, _ = QFileDialog.getOpenFileName(self, "Selectionner la cle privee a importer")
|
||||
if not priv_path:
|
||||
return
|
||||
name = Path(priv_path).stem
|
||||
try:
|
||||
self.key_manager.import_key(name=name, private_key_path=priv_path)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
QMessageBox.critical(self, "Erreur", str(exc))
|
||||
return
|
||||
self.refresh()
|
||||
|
||||
def export_public(self) -> None:
|
||||
entry = self._current_entry()
|
||||
if not entry:
|
||||
return
|
||||
dest, _ = QFileDialog.getSaveFileName(self, "Exporter la cle publique", f"{entry.name}.pub")
|
||||
if dest:
|
||||
self.key_manager.export_public_key(entry, dest)
|
||||
QMessageBox.information(self, "Export", "Cle publique exportee.")
|
||||
|
||||
def export_private(self) -> None:
|
||||
entry = self._current_entry()
|
||||
if not entry:
|
||||
return
|
||||
if QMessageBox.warning(
|
||||
self, "Attention",
|
||||
"La cle privee est sensible : quiconque y a acces peut se connecter a vos serveurs. Continuer ?",
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
) != QMessageBox.Yes:
|
||||
return
|
||||
dest, _ = QFileDialog.getSaveFileName(self, "Exporter la cle privee", entry.name)
|
||||
if dest:
|
||||
self.key_manager.export_private_key(entry, dest)
|
||||
QMessageBox.information(self, "Export", "Cle privee exportee.")
|
||||
|
||||
def copy_public(self) -> None:
|
||||
entry = self._current_entry()
|
||||
if not entry:
|
||||
return
|
||||
QApplication.clipboard().setText(entry.public_key_text)
|
||||
QMessageBox.information(self, "Copie", "Cle publique copiee dans le presse-papiers.")
|
||||
|
||||
def remove_key(self) -> None:
|
||||
entry = self._current_entry()
|
||||
if not entry:
|
||||
return
|
||||
delete_files = QMessageBox.question(
|
||||
self, "Supprimer",
|
||||
"Retirer aussi les fichiers de cle du disque (irreversible) ?\n"
|
||||
"Non = seulement retirer de la liste.",
|
||||
QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel,
|
||||
)
|
||||
if delete_files == QMessageBox.Cancel:
|
||||
return
|
||||
self.key_manager.remove(entry.id, delete_files=(delete_files == QMessageBox.Yes))
|
||||
self.refresh()
|
||||
95
app/dialogs/known_hosts_dialog.py
Normal file
95
app/dialogs/known_hosts_dialog.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""Gestionnaire des hotes SSH connus (fichier known_hosts local)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QHBoxLayout, QTreeWidget, QTreeWidgetItem, QPushButton,
|
||||
QMessageBox, QLabel, QAbstractItemView,
|
||||
)
|
||||
|
||||
from app.known_hosts_manager import KnownHostsStore
|
||||
|
||||
|
||||
class KnownHostsDialog(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.store = KnownHostsStore()
|
||||
self.setWindowTitle("Gestionnaire des hotes connus")
|
||||
self.setMinimumSize(560, 380)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
info = QLabel(
|
||||
"Cles des serveurs approuvees lors d'une premiere connexion. Si un serveur change "
|
||||
"d'adresse (ex. reattribution DHCP), l'application vous proposera automatiquement de "
|
||||
"remplacer son entree au moment de la connexion. Vous pouvez aussi nettoyer manuellement ici."
|
||||
)
|
||||
info.setWordWrap(True)
|
||||
layout.addWidget(info)
|
||||
|
||||
self.tree = QTreeWidget()
|
||||
self.tree.setHeaderLabels(["Hote", "Type de cle", "Empreinte"])
|
||||
self.tree.setRootIsDecorated(False)
|
||||
self.tree.setSelectionMode(QAbstractItemView.ExtendedSelection)
|
||||
self.tree.setColumnWidth(0, 200)
|
||||
self.tree.setColumnWidth(1, 110)
|
||||
layout.addWidget(self.tree)
|
||||
|
||||
self.count_label = QLabel("")
|
||||
self.count_label.setStyleSheet("color: #93a4c2;")
|
||||
layout.addWidget(self.count_label)
|
||||
|
||||
btn_row = QHBoxLayout()
|
||||
self.refresh_btn = QPushButton("Actualiser")
|
||||
self.remove_btn = QPushButton("Supprimer la selection")
|
||||
self.clear_btn = QPushButton("Tout effacer")
|
||||
btn_row.addWidget(self.refresh_btn)
|
||||
btn_row.addWidget(self.remove_btn)
|
||||
btn_row.addWidget(self.clear_btn)
|
||||
btn_row.addStretch()
|
||||
close_btn = QPushButton("Fermer")
|
||||
close_btn.clicked.connect(self.accept)
|
||||
btn_row.addWidget(close_btn)
|
||||
layout.addLayout(btn_row)
|
||||
|
||||
self.refresh_btn.clicked.connect(self.refresh)
|
||||
self.remove_btn.clicked.connect(self.remove_selected)
|
||||
self.clear_btn.clicked.connect(self.clear_all)
|
||||
|
||||
self.refresh()
|
||||
|
||||
def refresh(self) -> None:
|
||||
self.tree.clear()
|
||||
entries = self.store.list_entries()
|
||||
for entry in entries:
|
||||
item = QTreeWidgetItem([entry.hostname, entry.key_type, entry.fingerprint])
|
||||
item.setData(0, Qt.UserRole, entry.hostname)
|
||||
self.tree.addTopLevelItem(item)
|
||||
self.count_label.setText(f"{len(entries)} hote(s) enregistre(s).")
|
||||
self.remove_btn.setEnabled(bool(entries))
|
||||
self.clear_btn.setEnabled(bool(entries))
|
||||
|
||||
def remove_selected(self) -> None:
|
||||
items = self.tree.selectedItems()
|
||||
if not items:
|
||||
return
|
||||
hostnames = {item.data(0, Qt.UserRole) for item in items}
|
||||
if QMessageBox.question(
|
||||
self, "Supprimer",
|
||||
f"Retirer {len(hostnames)} hote(s) du fichier known_hosts ?\n"
|
||||
"Une confirmation d'empreinte sera redemandee a la prochaine connexion.",
|
||||
) != QMessageBox.Yes:
|
||||
return
|
||||
for hostname in hostnames:
|
||||
self.store.remove_host(hostname)
|
||||
self.refresh()
|
||||
|
||||
def clear_all(self) -> None:
|
||||
if QMessageBox.question(
|
||||
self, "Tout effacer",
|
||||
"Vider entierement le fichier known_hosts ?\n"
|
||||
"Toutes les connexions redemanderont une confirmation d'empreinte.",
|
||||
) != QMessageBox.Yes:
|
||||
return
|
||||
self.store.clear_all()
|
||||
self.refresh()
|
||||
151
app/dialogs/session_dialog.py
Normal file
151
app/dialogs/session_dialog.py
Normal file
@@ -0,0 +1,151 @@
|
||||
"""Boite de dialogue de creation/edition d'une session SSH."""
|
||||
from __future__ import annotations
|
||||
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog, QFormLayout, QLineEdit, QSpinBox, QComboBox, QDialogButtonBox,
|
||||
QHBoxLayout, QPushButton, QTextEdit, QFileDialog, QWidget, QVBoxLayout,
|
||||
QCheckBox, QMessageBox,
|
||||
)
|
||||
|
||||
from app.session_manager import Session, AUTH_AUTO, AUTH_KEY, AUTH_PASSWORD, AUTH_AGENT
|
||||
from app.key_manager import KeyManager
|
||||
|
||||
|
||||
class SessionDialog(QDialog):
|
||||
"""Formulaire de session. En mode `connect_label`, il sert de connexion rapide :
|
||||
on saisit les infos et on se connecte directement, l'enregistrement etant optionnel."""
|
||||
|
||||
def __init__(self, key_manager: KeyManager, session: Session | None = None,
|
||||
parent=None, connect_label: str = "", title: str = ""):
|
||||
super().__init__(parent)
|
||||
self.key_manager = key_manager
|
||||
self.session = session or Session()
|
||||
self.quick_mode = bool(connect_label)
|
||||
self.setWindowTitle(title or ("Nouvelle session" if session is None else "Modifier la session"))
|
||||
self.setMinimumWidth(420)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
form = QFormLayout()
|
||||
|
||||
self.name_edit = QLineEdit(self.session.name)
|
||||
self.group_edit = QLineEdit(self.session.group)
|
||||
self.group_edit.setPlaceholderText("ex: Production/Web")
|
||||
self.host_edit = QLineEdit(self.session.host)
|
||||
self.port_spin = QSpinBox()
|
||||
self.port_spin.setRange(1, 65535)
|
||||
self.port_spin.setValue(self.session.port or 22)
|
||||
self.user_edit = QLineEdit(self.session.username)
|
||||
|
||||
self.auth_combo = QComboBox()
|
||||
self.auth_combo.addItem("Automatique (comme la commande ssh)", AUTH_AUTO)
|
||||
self.auth_combo.addItem("Cle SSH choisie", AUTH_KEY)
|
||||
self.auth_combo.addItem("Mot de passe (saisi a la connexion)", AUTH_PASSWORD)
|
||||
self.auth_combo.addItem("Agent SSH", AUTH_AGENT)
|
||||
self.auth_combo.setToolTip(
|
||||
"Automatique : utilise l'agent SSH et vos cles ~/.ssh comme le fait la commande ssh,\n"
|
||||
"et demande un mot de passe seulement si aucune cle ne convient."
|
||||
)
|
||||
idx = self.auth_combo.findData(self.session.auth_type)
|
||||
self.auth_combo.setCurrentIndex(idx if idx >= 0 else 0)
|
||||
|
||||
self.key_combo = QComboBox()
|
||||
self._reload_keys()
|
||||
self.browse_key_btn = QPushButton("Parcourir...")
|
||||
|
||||
key_row = QWidget()
|
||||
key_layout = QHBoxLayout(key_row)
|
||||
key_layout.setContentsMargins(0, 0, 0, 0)
|
||||
key_layout.addWidget(self.key_combo, 1)
|
||||
key_layout.addWidget(self.browse_key_btn)
|
||||
|
||||
self.notes_edit = QTextEdit(self.session.notes)
|
||||
self.notes_edit.setFixedHeight(60)
|
||||
|
||||
form.addRow("Nom :", self.name_edit)
|
||||
form.addRow("Dossier :", self.group_edit)
|
||||
form.addRow("Hote :", self.host_edit)
|
||||
form.addRow("Port :", self.port_spin)
|
||||
form.addRow("Utilisateur :", self.user_edit)
|
||||
form.addRow("Authentification :", self.auth_combo)
|
||||
form.addRow("Cle privee :", key_row)
|
||||
form.addRow("Notes :", self.notes_edit)
|
||||
|
||||
layout.addLayout(form)
|
||||
|
||||
self.save_check = QCheckBox("Enregistrer dans mes sessions (optionnel)")
|
||||
if self.quick_mode:
|
||||
# decoche par defaut : les serveurs de test changent d'IP (DHCP), les
|
||||
# enregistrer systematiquement encombre la liste pour rien
|
||||
self.save_check.setChecked(False)
|
||||
layout.addWidget(self.save_check)
|
||||
|
||||
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
if self.quick_mode:
|
||||
buttons.button(QDialogButtonBox.Ok).setText(connect_label)
|
||||
buttons.accepted.connect(self._on_accept)
|
||||
buttons.rejected.connect(self.reject)
|
||||
layout.addWidget(buttons)
|
||||
|
||||
self.auth_combo.currentIndexChanged.connect(self._update_key_row)
|
||||
self.browse_key_btn.clicked.connect(self._browse_key)
|
||||
self._update_key_row()
|
||||
|
||||
if self.session.key_path:
|
||||
existing = self.key_combo.findData(self.session.key_path)
|
||||
if existing >= 0:
|
||||
self.key_combo.setCurrentIndex(existing)
|
||||
else:
|
||||
self.key_combo.addItem(self.session.key_path, self.session.key_path)
|
||||
self.key_combo.setCurrentIndex(self.key_combo.count() - 1)
|
||||
|
||||
def _reload_keys(self) -> None:
|
||||
self.key_combo.clear()
|
||||
for entry in self.key_manager.keys:
|
||||
self.key_combo.addItem(f"{entry.name} ({entry.key_type})", entry.private_path)
|
||||
|
||||
def _update_key_row(self) -> None:
|
||||
is_key_auth = self.auth_combo.currentData() == AUTH_KEY
|
||||
self.key_combo.setEnabled(is_key_auth)
|
||||
self.browse_key_btn.setEnabled(is_key_auth)
|
||||
|
||||
def _browse_key(self) -> None:
|
||||
path, _ = QFileDialog.getOpenFileName(self, "Choisir une cle privee")
|
||||
if path:
|
||||
self.key_combo.addItem(path, path)
|
||||
self.key_combo.setCurrentIndex(self.key_combo.count() - 1)
|
||||
|
||||
def should_save(self) -> bool:
|
||||
"""En connexion rapide, indique si la session doit aussi etre enregistree."""
|
||||
return self.save_check.isChecked() if self.quick_mode else True
|
||||
|
||||
def _on_accept(self) -> None:
|
||||
if not self.host_edit.text().strip():
|
||||
QMessageBox.warning(self, "Hote manquant", "Indiquez l'adresse du serveur (nom d'hote ou IP).")
|
||||
self.host_edit.setFocus()
|
||||
return
|
||||
if not self.user_edit.text().strip():
|
||||
QMessageBox.warning(self, "Utilisateur manquant", "Indiquez le nom d'utilisateur.")
|
||||
self.user_edit.setFocus()
|
||||
return
|
||||
if self.auth_combo.currentData() == AUTH_KEY and not self.key_combo.currentData():
|
||||
QMessageBox.warning(
|
||||
self, "Cle manquante",
|
||||
"Aucune cle privee n'est selectionnee.\n\n"
|
||||
"Choisissez une cle (bouton « Parcourir... »), ou passez l'authentification "
|
||||
"sur « Automatique » — qui utilisera l'agent SSH et vos cles ~/.ssh comme le "
|
||||
"fait la commande ssh.",
|
||||
)
|
||||
self.auth_combo.setFocus()
|
||||
return
|
||||
self.session.name = self.name_edit.text().strip() or self.host_edit.text().strip()
|
||||
self.session.group = self.group_edit.text().strip()
|
||||
self.session.host = self.host_edit.text().strip()
|
||||
self.session.port = self.port_spin.value()
|
||||
self.session.username = self.user_edit.text().strip()
|
||||
self.session.auth_type = self.auth_combo.currentData()
|
||||
self.session.key_path = self.key_combo.currentData() or ""
|
||||
self.session.notes = self.notes_edit.toPlainText()
|
||||
self.accept()
|
||||
|
||||
def result_session(self) -> Session:
|
||||
return self.session
|
||||
Reference in New Issue
Block a user