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>
94 lines
3.3 KiB
Python
94 lines
3.3 KiB
Python
"""Valeurs par defaut d'un groupe (dossier de sessions) : username, port, cle
|
|
SSH, bastion, keepalive.
|
|
|
|
Choix delibere : l'heritage s'applique au moment de la creation d'une session
|
|
(pre-remplissage explicite dans l'editeur), pas par une resolution dynamique a
|
|
chaque connexion. Modifier les parametres d'un groupe ne change donc jamais
|
|
retroactivement les sessions deja creees - elles restent des valeurs normales,
|
|
librement modifiables, sans indirection a suivre dans tout le reste du code
|
|
(ConnectionController, SSHConnection, resolve_bastion_target...)."""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field, asdict
|
|
from pathlib import Path
|
|
|
|
from app.paths import DATA_DIR
|
|
from app.storage.json_store import load_json_document, save_json_document
|
|
from app.session_manager import BastionConfig, AUTH_AUTO
|
|
|
|
GROUPS_FILE = DATA_DIR / "groups.json"
|
|
SCHEMA_VERSION = 1
|
|
|
|
|
|
@dataclass
|
|
class GroupSettings:
|
|
path: str = "" # ex: "Production/Web", correspond a Session.group
|
|
username: str = ""
|
|
port: int = 22
|
|
auth_type: str = AUTH_AUTO
|
|
key_path: str = ""
|
|
bastion: BastionConfig = field(default_factory=BastionConfig)
|
|
keepalive_enabled: bool = True
|
|
keepalive_interval: int = 30
|
|
keepalive_count_max: int = 3
|
|
|
|
def to_dict(self) -> dict:
|
|
return asdict(self)
|
|
|
|
@staticmethod
|
|
def from_dict(data: dict) -> "GroupSettings":
|
|
g = GroupSettings()
|
|
for k in GroupSettings.__dataclass_fields__:
|
|
if k not in data:
|
|
continue
|
|
v = data[k]
|
|
if k == "bastion" and isinstance(v, dict):
|
|
v = BastionConfig.from_dict(v)
|
|
setattr(g, k, v)
|
|
return g
|
|
|
|
|
|
class GroupSettingsStore:
|
|
"""Charge/sauvegarde les parametres par defaut de chaque groupe depuis
|
|
data/groups.json. Un groupe sans entree ici n'a simplement aucune valeur
|
|
par defaut (comportement actuel, inchange)."""
|
|
|
|
def __init__(self, path: Path = GROUPS_FILE):
|
|
self.path = path
|
|
self.groups: list[GroupSettings] = []
|
|
self.load_warning: str | None = None
|
|
self.load()
|
|
|
|
def load(self) -> None:
|
|
was_missing = not self.path.exists()
|
|
document, warning = load_json_document(
|
|
self.path,
|
|
default={"schema_version": SCHEMA_VERSION, "groups": []},
|
|
current_version=SCHEMA_VERSION,
|
|
)
|
|
self.load_warning = warning
|
|
self.groups = [GroupSettings.from_dict(g) for g in document.get("groups", [])]
|
|
if was_missing:
|
|
self.save()
|
|
|
|
def save(self) -> None:
|
|
payload = {"schema_version": SCHEMA_VERSION, "groups": [g.to_dict() for g in self.groups]}
|
|
save_json_document(self.path, payload)
|
|
|
|
def get(self, group_path: str) -> GroupSettings | None:
|
|
return next((g for g in self.groups if g.path == group_path), None)
|
|
|
|
def set(self, settings: GroupSettings) -> None:
|
|
"""Cree ou remplace les parametres du groupe (identifie par son path)."""
|
|
for i, g in enumerate(self.groups):
|
|
if g.path == settings.path:
|
|
self.groups[i] = settings
|
|
self.save()
|
|
return
|
|
self.groups.append(settings)
|
|
self.save()
|
|
|
|
def delete(self, group_path: str) -> None:
|
|
self.groups = [g for g in self.groups if g.path != group_path]
|
|
self.save()
|