feat: ajouter les tags de session et les parametres par defaut de groupe
Tags : une session peut porter plusieurs tags libres (Session.tags), saisis comme une liste separee par des virgules dans l'editeur (normalises : espaces retires, doublons insensibles a la casse elimines), avec autocompletion sur les tags deja utilises. Affiches dans l'arbre des sessions, et filtrables via un champ dedie au-dessus de l'arbre (masque les sessions ne correspondant pas, sans toucher aux dossiers/terminaux locaux/hosts OpenSSH). Parametres de groupe : choix architectural deliberement plus simple qu'une resolution dynamique a la connexion. app/groups.py (GroupSettingsStore, meme pattern que les autres stores) associe un dossier (Session.group) a des valeurs par defaut (utilisateur, port, authentification, cle, bastion, keepalive). Ces valeurs ne s'appliquent qu'au moment de la creation d'une session, via un bouton explicite "↓ Heriter du groupe" dans SessionDialog - jamais de resolution automatique/retroactive. Ce choix evite d'introduire des valeurs sentinelles "herite vs. explicite" sur chaque champ (port=22 est a la fois une valeur par defaut ET une vraie valeur possible, par exemple) dans tout le reste du code (ConnectionController, SSHConnection...), pour une fonctionnalite que la priorisation du projet place parmi les secondaires. Une session reste ensuite un ensemble de valeurs normales, librement modifiables - "surcharger une valeur heritee" est donc trivialement satisfait. Clic droit sur un dossier dans le panneau lateral : "Nouvelle session dans ce dossier..." (pre-remplit le champ Dossier) et "Parametres du groupe..." (GroupSettingsDialog). tests/test_tags_and_groups.py : persistance des tags, CRUD GroupSettingsStore, normalisation de la saisie des tags, heritage applique/absent, filtrage dans l'arbre. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
"""Boite de dialogue de creation/edition d'une session SSH."""
|
||||
from __future__ import annotations
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog, QFormLayout, QLineEdit, QSpinBox, QComboBox, QDialogButtonBox,
|
||||
QHBoxLayout, QPushButton, QTextEdit, QFileDialog, QWidget, QVBoxLayout,
|
||||
QCheckBox, QMessageBox, QGroupBox, QStackedWidget,
|
||||
QCheckBox, QMessageBox, QGroupBox, QStackedWidget, QCompleter,
|
||||
)
|
||||
|
||||
from app.session_manager import (
|
||||
@@ -13,6 +14,7 @@ from app.session_manager import (
|
||||
)
|
||||
from app.key_manager import KeyManager
|
||||
from app.openssh.config import load_openssh_hosts
|
||||
from app.groups import GroupSettingsStore
|
||||
|
||||
|
||||
class SessionDialog(QDialog):
|
||||
@@ -22,11 +24,12 @@ class SessionDialog(QDialog):
|
||||
def __init__(
|
||||
self, key_manager: KeyManager, session: Session | None = None,
|
||||
parent=None, connect_label: str = "", title: str = "",
|
||||
session_store: SessionStore | None = None,
|
||||
session_store: SessionStore | None = None, group_store: GroupSettingsStore | None = None,
|
||||
):
|
||||
super().__init__(parent)
|
||||
self.key_manager = key_manager
|
||||
self.session_store = session_store
|
||||
self.group_store = group_store
|
||||
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"))
|
||||
@@ -38,6 +41,25 @@ class SessionDialog(QDialog):
|
||||
self.name_edit = QLineEdit(self.session.name)
|
||||
self.group_edit = QLineEdit(self.session.group)
|
||||
self.group_edit.setPlaceholderText("ex: Production/Web")
|
||||
self.inherit_group_btn = QPushButton("↓ Heriter du groupe")
|
||||
self.inherit_group_btn.setToolTip(
|
||||
"Pre-remplit ce formulaire avec les valeurs par defaut enregistrees pour ce dossier"
|
||||
" (Parametres du groupe..., dans le panneau des sessions)."
|
||||
)
|
||||
self.inherit_group_btn.clicked.connect(self._apply_group_defaults)
|
||||
group_row = QWidget()
|
||||
group_layout = QHBoxLayout(group_row)
|
||||
group_layout.setContentsMargins(0, 0, 0, 0)
|
||||
group_layout.addWidget(self.group_edit, 1)
|
||||
group_layout.addWidget(self.inherit_group_btn)
|
||||
|
||||
self.tags_edit = QLineEdit(", ".join(self.session.tags))
|
||||
self.tags_edit.setPlaceholderText("ex: prod, docker, client-x (separes par des virgules)")
|
||||
if self.session_store is not None:
|
||||
completer = QCompleter(self.session_store.tags(), self.tags_edit)
|
||||
completer.setCaseSensitivity(Qt.CaseInsensitive)
|
||||
self.tags_edit.setCompleter(completer)
|
||||
|
||||
self.host_edit = QLineEdit(self.session.host)
|
||||
self.port_spin = QSpinBox()
|
||||
self.port_spin.setRange(1, 65535)
|
||||
@@ -70,7 +92,8 @@ class SessionDialog(QDialog):
|
||||
self.notes_edit.setFixedHeight(60)
|
||||
|
||||
form.addRow("Nom :", self.name_edit)
|
||||
form.addRow("Dossier :", self.group_edit)
|
||||
form.addRow("Dossier :", group_row)
|
||||
form.addRow("Tags :", self.tags_edit)
|
||||
form.addRow("Hote :", self.host_edit)
|
||||
form.addRow("Port :", self.port_spin)
|
||||
form.addRow("Utilisateur :", self.user_edit)
|
||||
@@ -125,6 +148,55 @@ class SessionDialog(QDialog):
|
||||
self.key_combo.addItem(path, path)
|
||||
self.key_combo.setCurrentIndex(self.key_combo.count() - 1)
|
||||
|
||||
# ---------- heritage des parametres du groupe ----------
|
||||
def _apply_group_defaults(self) -> None:
|
||||
group_path = self.group_edit.text().strip()
|
||||
if not group_path:
|
||||
QMessageBox.information(self, "Heriter du groupe", "Indiquez d'abord un dossier.")
|
||||
return
|
||||
settings = self.group_store.get(group_path) if self.group_store else None
|
||||
if settings is None:
|
||||
QMessageBox.information(
|
||||
self, "Heriter du groupe",
|
||||
f"Aucun parametre par defaut enregistre pour « {group_path} ».\n\n"
|
||||
"Definissez-en depuis le panneau des sessions : clic droit sur le dossier "
|
||||
"-> Parametres du groupe...",
|
||||
)
|
||||
return
|
||||
if settings.username:
|
||||
self.user_edit.setText(settings.username)
|
||||
if settings.port:
|
||||
self.port_spin.setValue(settings.port)
|
||||
auth_idx = self.auth_combo.findData(settings.auth_type)
|
||||
if auth_idx >= 0:
|
||||
self.auth_combo.setCurrentIndex(auth_idx)
|
||||
if settings.key_path:
|
||||
existing = self.key_combo.findData(settings.key_path)
|
||||
if existing >= 0:
|
||||
self.key_combo.setCurrentIndex(existing)
|
||||
else:
|
||||
self.key_combo.addItem(settings.key_path, settings.key_path)
|
||||
self.key_combo.setCurrentIndex(self.key_combo.count() - 1)
|
||||
if settings.bastion.mode != BASTION_NONE:
|
||||
mode_idx = self.bastion_mode_combo.findData(settings.bastion.mode)
|
||||
if mode_idx >= 0:
|
||||
self.bastion_mode_combo.setCurrentIndex(mode_idx)
|
||||
if settings.bastion.mode == BASTION_SESSION:
|
||||
found = self.bastion_session_combo.findData(settings.bastion.session_id)
|
||||
if found >= 0:
|
||||
self.bastion_session_combo.setCurrentIndex(found)
|
||||
elif settings.bastion.mode == BASTION_OPENSSH:
|
||||
found = self.bastion_openssh_combo.findData(settings.bastion.openssh_alias)
|
||||
if found >= 0:
|
||||
self.bastion_openssh_combo.setCurrentIndex(found)
|
||||
elif settings.bastion.mode == BASTION_MANUAL:
|
||||
self.bastion_host_edit.setText(settings.bastion.host)
|
||||
self.bastion_port_spin.setValue(settings.bastion.port or 22)
|
||||
self.bastion_user_edit.setText(settings.bastion.username)
|
||||
self.keepalive_check.setChecked(settings.keepalive_enabled)
|
||||
self.keepalive_interval_spin.setValue(settings.keepalive_interval or 30)
|
||||
self.keepalive_count_spin.setValue(settings.keepalive_count_max or 3)
|
||||
|
||||
# ---------- Bastion / Jump Host ----------
|
||||
def _build_bastion_group(self) -> QGroupBox:
|
||||
"""Un seul saut pour l'instant (voir BastionConfig) : Aucun / une session
|
||||
@@ -359,6 +431,14 @@ class SessionDialog(QDialog):
|
||||
self.session.auth_type = self.auth_combo.currentData()
|
||||
self.session.key_path = self.key_combo.currentData() or ""
|
||||
self.session.notes = self.notes_edit.toPlainText()
|
||||
seen = set()
|
||||
tags = []
|
||||
for raw in self.tags_edit.text().split(","):
|
||||
tag = raw.strip()
|
||||
if tag and tag.lower() not in seen:
|
||||
seen.add(tag.lower())
|
||||
tags.append(tag)
|
||||
self.session.tags = tags
|
||||
self.session.bastion = self._result_bastion()
|
||||
self.session.keepalive_enabled = self.keepalive_check.isChecked()
|
||||
self.session.keepalive_interval = self.keepalive_interval_spin.value()
|
||||
|
||||
Reference in New Issue
Block a user