Avant: WHERE role <> 'admin' -> excluait TOUS les users avec role admin (notamment
les vrais utilisateurs ayant ce role pour leur travail patching).
Apres: WHERE LOWER(username) <> 'admin' -> exclut uniquement le compte technique
'admin' (seed local), peu importe son role. Tous les autres users actifs apparaissent.
- Router: charge la liste des users (is_active=true AND role <> 'admin')
depuis la table users + passe au contexte
- Template: dropdown rempli en Jinja avec username + (toi) si current user + display_name
si different. Si user connecte non present en BDD (cas rare), ajoute une entree
en bonus
- JS: supprime rebuildIntervenantDropdown (la liste reste figee, plus simple,
predictible). Note conservee pour explication.
Avant: 3 champs (Auteur input + 2 checkboxes 'Mes snapshots' / 'Format PatchCenter') -> trop.
Apres:
- Un seul dropdown 'Intervenant' avec liste dynamique des auteurs detectes dans les
snapshots PatchCenter charges
- Defaut = user connecte (intervenant_default depuis JWT 'sub')
- Ajoute '(toi)' a cote de l'option qui matche le user connecte
- Si le user connecte n'a pas encore de snapshot, son option apparait quand meme en tete
avec le tag '(toi, aucun snap)'
- Le filtre 'PatchCenter uniquement' est implicite (toujours actif) -> les snapshots
SLPM ou manuels n'apparaissent plus du tout
- Les autres users peuvent etre selectionnes pour afficher/supprimer leurs snapshots
(pas de restriction par compte au-dela de l'authentification)
- updateFilterSummary affiche le filtre actif clairement
Probleme initial: nom snap base sur 'intervenant' (champ libre Excel modifiable) -> peu fiable
pour identifier qui a cree le snap. De plus, sans heure dans le nom, collisions si meme
serveur patche 2x dans la journee.
Solution:
- Format snap PatchCenter v2: <user_jwt>_YYYY-MM-DD_HH-MM_avant_patch
user = login JWT (sub) immutable, traçable cote AD
HH-MM ajoute pour eviter collisions
- Service: nouveau regex SNAP_PATCHCENTER_V2_RE (avec heure), v1 conservee pour
les snapshots existants legacy
- Router iexec_snapshot: utilise user.get('sub') au lieu de row.intervenant
- UI:
* Renomme checkbox 'Format gere' -> 'Snapshots PatchCenter uniquement'
* Filtre origin === 'patchcenter' (exclut SLPM .exe par defaut)
* Combine avec 'Mes snapshots' (author = login user) -> seulement TES snapshots
PatchCenter visibles, parfait pour le cleanup post-patching
Probleme: tes 51 snapshots etaient au format SLPM_<auteur>_YYYYMMDD_HHMM (cree par le .exe)
non reconnu par PatchCenter qui n'attendait que le format <auteur>_YYYY-MM-DD_avant_patch.
- Service: nouveau regex SNAP_SLPM_RE + helper _detect_snap_origin retourne (origin, author)
- Champs ajoutes au snapshot: origin ('patchcenter'|'slpm'|None), is_managed_format
- Template:
* Filtre 'Format gere uniquement' (renomme depuis 'PatchCenter uniquement')
* Colonne 'Origine' avec badge: PatchCenter (bleu) / SLPM .exe (gris) / manuel (orange)
* Colonne ajoutee dans header + cellules + colspan ajuste a 9
- Overlay 'busy-overlay' plein ecran (z-index 9999) avec spinner anime
- Bloque toute interaction (clics) pendant recherche/suppression
- setBusy active l'overlay + change le texte du bouton
- clearBusy retire l'overlay
- beforeunload: avertit l'utilisateur s'il tente de quitter la page
pendant une requete en vol (close tab, navigation, refresh)
- Service: regex stricte '<auteur>_YYYY-MM-DD_avant_patch' (avant: laxiste avec suffixe optionnel)
- Champ is_patchcenter_format ajoute aux snapshots, et auteur seulement si format match
- Router: _get_user_intervenant_name lit JWT 'sub' (correctif - etait 'username' qui n'existe pas)
- UI:
* Nouveau filtre 'Format PatchCenter uniquement' (checkbox, default ON)
* Filtre 'Mes snapshots' marche meme si auteur input vide -> on garde uniquement
ceux dont l'auteur est connu (= snapshots PatchCenter)
* Dates: formattees jj/mm/aaaa HH:MM (fmtDateFR via Date object navigateur)
* Cellule auteur 'inconnu' rendue avec balise <i> proprement (bypass escapeHTML)
* Helper setBusy/clearBusy pour feedback unifie '⏳ Recherche en cours…' / '⏳ Suppression en cours…'
(status + texte du bouton change pendant l'action)
- Si les filtres cachent des snapshots, status affiche '⚠ N snapshot(s) cache(s)'
avec hint pour decocher 'Mes snapshots' ou mettre age=0
- Tableau vide differencie 2 cas:
* 0 trouvé total -> 'Verifie credentials vCenter'
* X trouvé mais tous filtrés -> 'Ajuste les filtres ci-dessus' en jaune
- Regex WINDOWS_OS_RE: \bwin(dows)?\b ou 'microsoft' insensible casse
- _is_skipped_os(os) : true si OS Windows
- Skip dans la boucle, comptage skipped_windows
- Append au note de l'import un suffix '[skip-windows: N ligne(s) Windows ignoree(s)]'
pour tracer combien de rows ont ete filtrees
Service snapshot_mgmt_service.py:
- list_snapshots(db, vcenter_filter_id=None): itere les vCenters actifs, walk recursif
des snapshot tree de chaque VM, retourne (vcenter, vm, snap_name, snap_id, vm_moid,
created_at, age_days, author, description, is_current)
- delete_snapshot(db, vcenter_id, vm_moid, snap_id, remove_children=False): supprime
un snapshot par moRef, attend la fin de la task vCenter
- Auteur deduit du prefixe du nom (format PatchCenter '<auteur>_YYYY-MM-DD_<suffixe>')
Router /snapshots:
- GET /snapshots: page principale (filtres + table)
- POST /snapshots/list: AJAX scan vCenters, retourne JSON
- POST /snapshots/delete: AJAX suppression batch, double confirmation cote UI
Template snapshots.html:
- Filtres: vCenter, auteur, 'Mes snapshots uniquement' (preselectionne user courant),
age min en jours (defaut 3)
- Table avec checkboxes, sel-all, badge age (vert <3j, orange 3-7j, rouge >7j)
- Bouton 'Charger/Refresh' (lazy load, eviter scan auto au pageload)
- Bouton 'Supprimer la selection' avec 2 confirmations + liste des snapshots
- Recharge auto apres suppression
Nav: lien '📸 Snapshots VM' ajoute dans le menu Patching.
Avant: une fois les verifs faites, le bouton restait cliquable meme si on etait
passe au step 2 (snap), ce qui pouvait re-lancer les verifs et casser l'etat.
Apres: si snap/dry/pre/patch/reboot/recon/post a ete attemptee, le bouton step 1
est explicitement disabled avec un title explicatif. La couleur du badge reste
'done' ou 'failed' selon le verdict des verifs.
Avant: les boutons (+ Ajouter, Reporter, Annuler, Pré-patching) etaient grises (disabled)
quand aucune row n'etait selectionnee, et le clic ne provoquait rien -> confusion.
Apres:
- Boutons toujours cliquables (HTML disabled retire)
- Atténuation visuelle (opacity-50) quand selection vide pour feedback
- Au clic sans selection: alerte 'Veuillez selectionner au moins un serveur.'
- Btn Pre-patching: si selection mais aucun eligible, alerte indiquant
d'utiliser '+ Ajouter au patching' d'abord
- applyFilters: comparaisons en lowercase pour intervenant et env
- rebuildSelectOptions: dedup case-insensitive (Map<lowercase, canonical>)
garde la 1re forme rencontree comme label affiche
lastval() retourne la derniere valeur de sequence de la session — si un trigger sur
patch_planning_imports bumpe une autre sequence (ex: audit log), lastval() retourne
la mauvaise valeur. Resultat: import_id pointe vers un ID inexistant et les INSERT
sur patch_planning_import_rows echouent en FK violation.
Fix: INSERT ... RETURNING id qui est sans ambiguite, et early-return si NULL.
Probleme: vrexpbtex1 est sur .sanef.groupe (exception a la convention vr*=.sanef-rec.fr).
Le code tentait UN seul target via _resolve (TCP port 22 only) et echouait sans fallback SSH.
Logique de resolution revisee:
1. Convention en 1er: suffixe le plus probable selon prefixe (vr/lr/sr=>.sanef-rec.fr,
vp/lp/sp=>.sanef.groupe, etc.)
2. servers.fqdn (BDD) en 2e position si renseigne et plausible (commence par hostname.)
3. Autres suffixes du referentiel ensuite
Implementation:
- _candidate_targets(hostname) : nouvelle fonction qui retourne la liste ordonnee
des FQDN candidats a essayer SSH
- _connect(target, hostname, errors=...) : accepte une list errors optionnelle ou
les exceptions de chaque tentative (PSMP/cle/password) sont append en clair pour
diagnostic. Retro-compatible (errors=None par defaut)
- _connect_via_psmp(target, errors=...) : meme pattern
- _fqdn_is_consistent : assoupli, ne verifie plus la convention SANEF (qui rejetait
les exceptions legitimes), juste que le FQDN commence par hostname.
- run_all_checks: itere _candidate_targets et essaie _connect sur chaque, accumule
les erreurs de tous les candidats, retourne le 1er qui aboutit
UI:
- check_dns: si target trouve mais client KO, status=warn et liste des candidats tentes
- check_ssh: classification erreur (no route/timeout/refused/permission denied/etc.)
+ liste des candidats tentes + suggestion pour FQDN exception
Aligne les exclusions sur les nouvelles regles SANEF:
- Base globale: 32 patterns precedents + *sdcss-kmod* (anciennement override HAproxy FL).
S applique a TOUS les domaines sauf Flux Libre.
- Domaine Flux Libre: liste reduite a *podman* run* *container* uniquement
(les autres patterns doivent etre patches sur ces serveurs).
- ASM Oracle (~50 hosts hardcodes): base globale + kernel* (kmod-redhat-oracleasm
doit suivre le kernel ASM, ne PAS mettre a jour le kernel hote).
- Reset des overrides serveurs HAproxy FL (vrpeahbst1/vppeahbst1/vipeahbst1)
qui pointaient vers base + sdcss-kmod ; ils heritent maintenant du domaine FL.
- Migration migrate_pct_workflow_20260507.sql: ajoute patch_planning_import_rows
pct_required (boolean default false), pct_confirmed_at (timestamptz),
pct_confirmed_by_user_id (FK users). Backfill depuis servers.pct_required.
- Auto-detection a l'import (planning_import.py): scan referent_technique +
mode_operatoire + impacts + commentaire pour pattern \bPCT\b mot entier
(insensible casse) -> pct_required=true sur la row. Propage egalement vers
servers.pct_required si pas deja true.
- UI iexec: badge orange '⚠ Prév PCT à faire' sur la cellule asset_name si
pct_required=true et pas confirme, badge vert '✅ PCT ok' une fois confirme.
- Gate avant Step 3 (PATCH REEL): scan des serveurs cibles, si certains ont
pct_required && !pct_confirmed -> 2 confirmations successives + appel
POST /patching/iexec/confirm-pct qui marque pct_confirmed_at + user_id.
Ne lance pas le patch si l'operateur annule.
- Endpoint POST /patching/iexec/confirm-pct: marque les rows comme PCT confirmes
(pct_confirmed_at = now(), pct_confirmed_by_user_id = current user).
- Notif Teams: send_notification accepte planning_row_id optionnel ; si la row
a pct_required && pct_confirmed, le message debut/fin est suffixe par
' (Prévenance PCT ok)' pour informer le responsable que l'amont a ete gere.
- Migration v2: ajoute teams_channel_rules.match_is_database_server (NULL=any/TRUE=DB only/FALSE=non-DB only),
servers.is_database_server boolean (default false), table server_additional_referents pour multi-referents
- Service teams_service.py: resolve_channel_for_server -> resolve_channels_for_server (pluriel, retourne LIST)
* msg_type=reboot: 1 seul canal (canal flagge is_reboot_channel)
* server.teams_channel_id override: 1 seul canal
* sinon FAN-OUT: TOUTES les regles actives qui matchent contribuent un canal
* dynamic_to_email calcule selon nature de la regle (responsable / DBA pool / referents)
- send_notification: boucle sur les canaux resultants, ecrit 1 fichier SP par destination
- UI settings.html: nouveau filtre 'Match serveur DB' dans formulaire regle, badge dans tableau
- Compat: resolve_channel_for_server (singulier) conserve comme wrapper qui retourne le 1er canal
Permet le scenario: serveur DB avec responsable=Delcour -> notif a la fois dans conv Delcour
ET dans conv DBA (Nadine+Cedric), via 2 regles qui matchent en parallele.
- Migration: ajoute sp_route/mode/is_reboot_channel/is_dynamic_dm sur teams_channels,
cree table teams_channel_rules (match resp/domain/env/msg_type/hostname pattern)
- Service teams_service.py: format texte plat compatible workflows existants,
write_sharepoint_notification (ecrit fichier .txt dans <sp_base>/<sp_route>/),
resolve_channel_for_server rules-based avec priorite reboot,
send_notification orchestre resolution + envoi
- Settings UI: CRUD canaux etendu (mode SP/webhook + flags reboot/dyn_dm),
CRUD regles avec match conditions, sharepoint_notif_path en secret app,
bouton Test ecrit fichier .txt en mode SP
- Mode is_dynamic_dm: prefixe le contenu par 'TO: <email>' pour permettre
un workflow PA unique qui dispatch dynamiquement aux responsables
- Pas d'OAuth requis: PatchCenter ecrit fichiers, Workflows PA cote SharePoint
(deja en place pour le .exe) declenchent et postent sur Teams
Mode webhook conserve mais inactif tant qu'OAuth Entra ID pas mis en place chez SANEF