Commit Graph

154 Commits

Author SHA1 Message Date
d32a04c9ca fix(pct): CC = FK contacts ET champs legacy responsable_email/referent_email
Cause: les FK servers.responsable_domaine_contact_id / referent_technique_contact_id
n'etaient pas remplies dans la DB SANEF actuelle, mais les emails sont presents
dans les champs legacy texte servers.responsable_email / referent_email.

Solution: _fetch_pct_cc_emails() cherche maintenant en 2 etapes:
1. Via FK -> contacts.email (si modele moderne renseigne)
2. Via champs legacy texte sur servers (responsable_email/referent_email)

Dedoublonne par email lowercase. Garde le nom (legacy: responsable_nom/referent_nom,
moderne: contacts.name).
2026-05-07 21:50:51 +02:00
00998e9320 feat(pct): bouton Prevenance PCT + preview avant envoi + CC responsables/referents
- Service mail_service.py: send_html_mail via SMTP standard (host/port/user/pass/from/use_tls
  depuis Settings > SMTP). Gere SSL_465 et STARTTLS_587. Mode dry_run pour preview.
- Settings: nouvelle section 'smtp' avec smtp_host/port/user/pass/from/use_tls/pct_recipient
  (a configurer pour O365 SMTP submission)
- Router planning_import.py:
  * _build_pct_email(): construit subject + HTML pro/colore (header bleu degrade SANEF,
    cards avec border-left bleu/orange, tableau serveurs, footer)
  * Subject: 'Intervention sur <app>' si app uniforme, sinon liste des serveurs
  * Plage horaire = 20 min × N serveurs (formattee Hh MM)
  * 'Moyen d'exploitation prevu : Rollback en cas de probleme' ajoute en bas
  * _fetch_pct_cc_emails(): query distinct contacts depuis responsable_domaine_contact_id
    + referent_technique_contact_id + server_additional_referents
  * Endpoint POST /patching/import/pct-prevenance/preview retourne {subject, html, to, cc,
    smtp_configured, row_count} sans envoyer
  * Endpoint POST /patching/import/pct-prevenance/send envoie reellement, audit log,
    update pct_mail_sent_at sur les rows
- Template patching_import.html:
  * Bouton 'Prevenance PCT' (violet) a cote des autres actions
  * Modal preview avec iframe sandboxe pour le rendu HTML mail
  * Affiche destinataires, CC, objet, count serveurs
  * Warning rouge si SMTP non configure (envoi desactive, preview seulement)
  * 2 boutons: Annuler / Envoyer (avec confirmation)
2026-05-07 21:44:02 +02:00
e665fd94e7 fix(snapshots): exclure uniquement le compte technique 'admin' local (pas le role admin)
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.
2026-05-07 21:06:13 +02:00
cefddd2ea0 feat(snapshots): dropdown Intervenant alimente depuis users actifs (hors admin) au pageload
- 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.
2026-05-07 21:04:00 +02:00
c918edb093 feat(snapshots): nouveau format <user>_YYYY-MM-DD_HH-MM_avant_patch + filtre PatchCenter only
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
2026-05-07 20:58:54 +02:00
77e884d620 feat(snapshots): filtre format PatchCenter strict + UX feedback + dates fr
- 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)
2026-05-07 20:38:11 +02:00
3cdff5db08 feat(planning_import): skip les rows Windows a l'import (seuls Linux geres)
- 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
2026-05-07 20:23:38 +02:00
c63b3a9119 feat(snapshots): page de gestion snapshots VM (listing + filtre auteur/age + suppression)
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.
2026-05-07 20:13:29 +02:00
e448d8885b feat(planning_import): canonicalisation env + domaine a l'import (Production/production/PROD -> Production)
- Tables ENV_CANONICAL et DOMAIN_CANONICAL: lookup case+accent insensible
- _canonicalize_env / _canonicalize_domain : retournent la forme canonique connue,
  sinon valeur d'origine inchangee
- Applique dans l'INSERT row au moment de l'import
- Lookup en 3 passes:
  1. lowercase exact
  2. lowercase + accents stripped
  3. lowercase + accents + espaces normalises (ex 'Flux  Libre' -> 'flux libre')

Backfill SQL one-shot pour les rows existantes (backfill_canonicalize_env_domain_20260507.sql):
- env: Production/Pré-Prod/Recette/Test/Test 1/Test 2/Développement/Qualif
- domaine: Flux Libre/Péage/Infrastructure/Trafic/DMZ/LAN/BI/EMV/Gestion
- Idempotent
2026-05-07 19:48:21 +02:00
f539c604d6 fix(planning_import): RETURNING id au lieu de lastval() (FK violation patch_planning_import_rows_import_id_fkey)
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.
2026-05-07 19:37:44 +02:00
90444c0c56 feat(patching): particularites par serveur (notes wiki SANEF) + skip_first_reboot + reboot_delay cluster
Migration migrate_patching_notes_20260507.sql:
- servers.skip_first_reboot boolean (TPV1: vptraatpf1/2 a true)
- servers.patching_notes text (markdown — meme operateur)
- server_clusters.reboot_delay_min_minutes int default 0
- Backfill patching_notes pour 22 cas particuliers du wiki SANEF:
  ASM Oracle (~50 hosts kernel*), TPV1, HAproxy FL, Covoiturage,
  SI Patrimoine (vrpatbsip1 avant vrpataels1, kibana, certs),
  Talend, Scoop (Debian apt-mark hold + CentOS containers Docker),
  DATI (pm2/tomcat post-reboot), COMMVAULT (mode maintenance),
  Masterparc (kmeihm pm2), Splunk (RPM special),
  Site institutionnel (HAproxy backend rotation, no *node*),
  Centreon (1 par 1 + check centengine), Sextan (10min reboot delay),
  OCTAN, PAIPOR (site maintenance), Gaspar, Postgres, Oracle OEM, SMTP relay
- Cluster Sextan cree (10 min entre reboots) + 10 serveurs Sextan rattaches

UI iexec:
- Banner cumule en haut: '⚠ Particularites pour N serveur(s)' si au moins 1 note
- Badges sur la cellule asset_name: ⚠ note (modal markdown au clic),
  ⏭ skip 1st reboot, ⏱ Xmin (cluster reboot delay)
- Modal patching_notes avec rendu pre/markdown, fermeture Escape

UI fiche serveur (server_detail.html):
- Ligne 'Skip 1er reboot' dans bloc Patching
- Bandeau orange particularites avec contenu patching_notes si renseigne

Pas encore implemente cote logique d'execution (Phase 2):
- skip_first_reboot logic dans le step reboot
- enforcement reboot_delay_min_minutes entre membres cluster
- Pour l'instant: notes affichees en mode 'memo operateur' uniquement
2026-05-07 11:41:05 +02:00
29f6153370 feat(pct): workflow prevenance PCT (auto-detection + gate confirmation + suffixe Teams)
- 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.
2026-05-07 08:19:19 +02:00
060af01db9 feat(teams): fan-out multi-recipient + flag is_database_server + multi-referents
- 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.
2026-05-06 10:33:12 +02:00
edec1f7db5 feat(teams): mode SharePoint sync (calque .exe Sanef Patch Manager) + rules-based routing
- 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
2026-05-06 09:57:42 +02:00
5a9e2f78d1 fix(servers/edit + patching): dropdown 'Solution applicative' affiche TOUTES les applications (filtre 'WHERE itop_id IS NOT NULL' retire) - les apps locales sans correspondance iTop apparaissent maintenant 2026-05-05 15:58:41 +02:00
c19309fec2 fix(servers/edit): retire le push iTop sur changement application (etait bloquant 10s+ si iTop injoignable) - update local seulement, push iTop dedie via endpoint async a faire si besoin 2026-05-05 15:50:13 +02:00
e8f369817e fix(servers): bulk + edit synchronisent aussi les colonnes text legacy s.environnement et s.domaine (sinon liste affiche valeur obsolete) + script SQL re-sync des serveurs deja desyncs 2026-05-05 15:29:34 +02:00
1b82440813 fix(servers): bulk + edit comparaisons domain/env/zone case-insensitive (BD mixte RECETTE/Recette/recette) + fallback bulk env_code si serveur sans domain_env_id + log INFO/WARNING + retour msg=bulk_<n_updated_reel> 2026-05-05 15:19:03 +02:00
0ed564a8ed feat(check satellite): cascade LAN+DMZ avec fallback automatique + migration servers.satellite_url + override BDD prioritaire 2026-05-05 14:34:47 +02:00
a7874aec11 feat(settings/clusters M2): UI CRUD server_clusters - groupes + ordre redemarrage + strategie sequential/parallel + panneau detail serveurs rattaches 2026-05-05 14:05:59 +02:00
075706178e feat(settings/teams M1+M4): UI CRUD canaux Teams + service teams_service.py (Adaptive Card via Incoming Webhook) + bouton Test webhook 2026-05-05 13:58:38 +02:00
ff95424e03 feat(patching/iexec B3.6): bouton 3e Reboot manuel (double confirmation, jamais auto) + 3f Wait reconnexion (poll TCP/22 + SSH uptime, timeout 10min) - shutdown -r +1 avec audit log 2026-05-05 12:06:50 +02:00
19d88f2d53 feat(patching/iexec): detection auto deps problematiques + bouton retry sans paquets KO (multilib, requires, conflicts) - extra_excludes via SSE query param 2026-05-05 11:32:44 +02:00
8cf78dfef3 feat(patching/iexec): terminal live SSE pour dry-run et patch reel - generator yum_stream_lines + endpoint /yum-stream + EventSource cote client + log audit en fin de stream 2026-05-04 17:02:28 +02:00
e29ecff949 feat(patching/iexec B3.4+B3.5): pre-capture services+ports + post-compare avant/apres avec rapport diff (scripts wiki SANEF, push base64) - workflow 3a/3b/3c/3d sequentiel 2026-05-04 16:52:15 +02:00
6c92c71d17 feat(patching/iexec B3): step 3a dry-run (yum update --assumeno) + step 3b real patch (yum update -y) avec excludes effectifs depuis v_servers, validation anti-injection sur excludes, log audit, double confirmation pour patch reel 2026-05-04 16:40:46 +02:00
9996757e4b feat(snapshot): branche prod/hprod via prefixe hostname (vp/sp/lp = prod/metier ; reste = hprod/gestion) + fix matching DR vpsiaavcs1 (etait vpsicavcs1) + tolerance par name 2026-05-04 16:15:46 +02:00
a68d9494f1 fix(patching/iexec snapshot): toujours chercher la VM dans vCenter par hostname (s.vcenter_vm_name peut etre faux en base) 2026-05-04 16:12:50 +02:00
a6b98568f1 feat(patching/iexec B2): branchement snapshot vCenter - bouton Step 2 lance snapshot pour rows verdict OK, nom intervenant_YYYY-MM-DD_avant_patch, log audit dans patch_planning_row_log 2026-05-04 15:50:11 +02:00
11bbda5a27 fix(patching/iexec): r.os manquant dans SELECT de iexec_check (filtre Windows cassait) 2026-05-04 15:27:13 +02:00
8f98492c77 fix(patching/iexec): vue s appelle v_servers (pas v_servers_patching) pour effective_excludes 2026-05-04 15:22:42 +02:00
4e02319516 fix(patching/import): import Query manquant pour iexec_page (NameError au demarrage) 2026-05-04 15:15:47 +02:00
eb2e0dc8ba feat(patching/iexec B1): page wizard step 1 - checks DNS+SSH+Satellite (LAN vpdsiasat2 / DMZ vpdsiasat1 selon domaine), Linux uniquement (Windows skip), sudo -n partout 2026-05-04 15:14:06 +02:00
a5f3a25198 feat(patching/import): actions Reporter/Ajouter au patching + log + colonne Etat (etape A) + placeholder /patching/iexec affichant excludes effectifs (etape B a venir) 2026-05-04 14:57:49 +02:00
630297f98e feat(patching/import): stockage date/heure typés (DATE+TIME) + jour_text fallback texte libre + tri colonne Date par date+heure combinés 2026-05-04 13:57:24 +02:00
488b5a980b feat(patching/import): ajout colonnes Resp Domaine DTS, Referent technique, Mode operatoire, Impacts, BDD - support nouveau format S07+ + Date au lieu de Jour 2026-05-04 13:12:09 +02:00
557015325b feat(patching): import planning xlsx (etape 1) - tables patch_planning_imports + rows, page upload + selecteur semaine + tableau 2026-05-04 12:57:35 +02:00
767b33095a feat(qualys/agents): bloque troubleshooting pour Windows (route + bouton 'Non gere' dans table) 2026-04-28 01:48:32 +02:00
cdcb85917d feat(qualys/agents): audit en background thread + page d'attente auto-refresh (fix ERR_CONNECTION_RESET sur audits longs) 2026-04-27 23:25:50 +02:00
03229d4d08 feat(qualys/agents): bouton Check + page audit cible Qualys agent (status + version + logs agent/systeme via SSH) 2026-04-27 23:09:05 +02:00
cc550c2d84 fix(qualys/duplicates): scan async (background thread) + bandeau scan en cours - evite 503 HAProxy 2026-04-25 10:31:07 +00:00
3d043af194 feat(qualys): page doublons + suppression API Qualys 1-clic 2026-04-25 10:17:40 +00:00
0ab4f2d8fa fix(qualys/dashboard): vire flag in-memory + safety net thread + flex layout 6 KPI 2026-04-25 00:13:22 +00:00
34cca6f77b fix(qualys/dashboard): user est un dict, utiliser user.get(sub) au lieu de .username 2026-04-25 00:09:15 +00:00
9a7f446637 fix(qualys/dashboard): insert pending row dans la route avant spawn thread (no race) 2026-04-25 00:07:22 +00:00
daf87891a7 feat(qualys/dashboard): is_running base sur DB (multi-worker safe) + bouton Annuler 2026-04-25 00:05:49 +00:00
8f8e8c4d8f feat(qualys): dashboard vulnerabilites avec KPI + historique 2026-04-24 23:49:46 +00:00
b06aedfc3b fix(qualys): force vuln cache refresh on bulk resync redirect 2026-04-24 22:51:11 +00:00
392c8f4fe5 fix(qualys/search): KPI vuln_map est dict total/severityN, pas int 2026-04-24 22:34:16 +00:00
3c00f05263 feat(qualys/agents): colonne Version OS dans table sans-agent 2026-04-24 22:30:39 +00:00