2026-08-14 18:02:25 +02:00
{% extends "base.html" %}
{% block title %}Gestion des données — Admin GMAO{% endblock %}
{% block content %}
< div class = "container-fluid" >
< div class = "d-flex justify-content-between align-items-center mb-4" >
< h1 > < i class = "bi bi-database-exclamation text-danger" > < / i > Gestion des données< / h1 >
< a href = "{{ url_for('admin.index') }}" class = "btn btn-outline-secondary" > < i class = "bi bi-arrow-left" > < / i > Retour admin< / a >
< / div >
< div class = "alert alert-danger" >
< i class = "bi bi-exclamation-triangle-fill" > < / i >
< strong > Attention :< / strong > Ces actions sont irréversibles. Les équipements et interventions sont supprimés logiquement (masqués). Les mails sont supprimés définitivement.
< / div >
<!-- Statistiques -->
< div class = "row g-3 mb-4" >
< div class = "col-md-2" >
< div class = "card text-center border-warning" >
< div class = "card-body py-2" >
< div class = "display-6 text-warning" > {{ stats.equipments }}< / div >
< small class = "text-muted" > Équipements< / small >
< / div >
< / div >
< / div >
< div class = "col-md-2" >
< div class = "card text-center border-primary" >
< div class = "card-body py-2" >
< div class = "display-6 text-primary" > {{ stats.interventions }}< / div >
< small class = "text-muted" > Interventions< / small >
< / div >
< / div >
< / div >
< div class = "col-md-2" >
< div class = "card text-center border-info" >
< div class = "card-body py-2" >
< div class = "display-6 text-info" > {{ stats.interventions_planifiees }}< / div >
< small class = "text-muted" > Planifiées< / small >
< / div >
< / div >
< / div >
< div class = "col-md-2" >
< div class = "card text-center border-success" >
< div class = "card-body py-2" >
< div class = "display-6 text-success" > {{ stats.interventions_terminees }}< / div >
< small class = "text-muted" > Terminées< / small >
< / div >
< / div >
< / div >
< div class = "col-md-2" >
< div class = "card text-center border-secondary" >
< div class = "card-body py-2" >
< div class = "display-6 text-secondary" > {{ stats.outlook_mails }}< / div >
< small class = "text-muted" > Mails Outlook< / small >
< / div >
< / div >
< / div >
< div class = "col-md-2" >
< div class = "card text-center border-dark" >
< div class = "card-body py-2" >
< div class = "display-6 text-dark" > {{ stats.outlook_interpretations }}< / div >
< small class = "text-muted" > Interp. Outlook< / small >
< / div >
< / div >
< / div >
< div class = "col-md-2" >
< div class = "card text-center border-warning" >
< div class = "card-body py-2" >
< div class = "display-6 text-warning" > {{ stats.ent_messages }}< / div >
< small class = "text-muted" > Messages ENT< / small >
< / div >
< / div >
< / div >
< div class = "col-md-2" >
< div class = "card text-center border-info" >
< div class = "card-body py-2" >
< div class = "display-6 text-info" > {{ stats.ent_interpretations }}< / div >
< small class = "text-muted" > Interp. ENT< / small >
< / div >
< / div >
< / div >
< / div >
<!-- Suppressions en masse -->
< div class = "row g-3 mb-4" >
< div class = "col-md-3" >
< div class = "card border-danger h-100" >
< div class = "card-header bg-danger text-white" >
< i class = "bi bi-trash" > < / i > Supprimer tous les équipements
< / div >
< div class = "card-body" >
< p class = "text-muted small" > {{ stats.equipments }} équipement(s). Suppression logique.< / p >
< form method = "POST" action = "{{ url_for('admin.delete_all_equipments') }}" onsubmit = "return confirm('CONFIRMATION : Supprimer tous les équipements ?')" >
< input type = "hidden" name = "csrf_token" value = "{{ csrf_token() }}" >
< div class = "mb-2" >
< label class = "form-label small" > Tapez < strong > SUPPRIMER< / strong > :< / label >
< input type = "text" name = "confirm_text" class = "form-control form-control-sm" placeholder = "SUPPRIMER" required >
< / div >
< button type = "submit" class = "btn btn-danger btn-sm w-100" > < i class = "bi bi-trash" > < / i > Supprimer équipements< / button >
< / form >
< / div >
< / div >
< / div >
< div class = "col-md-3" >
< div class = "card border-danger h-100" >
< div class = "card-header bg-danger text-white" >
< i class = "bi bi-trash" > < / i > Supprimer toutes les interventions
< / div >
< div class = "card-body" >
< p class = "text-muted small" > {{ stats.interventions }} intervention(s). Suppression logique.< / p >
< form method = "POST" action = "{{ url_for('admin.delete_all_interventions') }}" onsubmit = "return confirm('CONFIRMATION : Supprimer toutes les interventions ?')" >
< input type = "hidden" name = "csrf_token" value = "{{ csrf_token() }}" >
< div class = "mb-2" >
< label class = "form-label small" > Tapez < strong > SUPPRIMER< / strong > :< / label >
< input type = "text" name = "confirm_text" class = "form-control form-control-sm" placeholder = "SUPPRIMER" required >
< / div >
< button type = "submit" class = "btn btn-danger btn-sm w-100" > < i class = "bi bi-trash" > < / i > Supprimer interventions< / button >
< / form >
< / div >
< / div >
< / div >
< div class = "col-md-3" >
< div class = "card border-danger h-100" >
< div class = "card-header bg-danger text-white" >
< i class = "bi bi-trash" > < / i > Supprimer tous les mails Outlook
< / div >
< div class = "card-body" >
< p class = "text-muted small" > {{ stats.outlook_mails }} mail(s) + {{ stats.outlook_interpretations }} interp. Suppression définitive.< / p >
< form method = "POST" action = "{{ url_for('admin.delete_all_mails') }}" onsubmit = "return confirm('CONFIRMATION : Supprimer tous les mails ET interprétations ?')" >
< input type = "hidden" name = "csrf_token" value = "{{ csrf_token() }}" >
< div class = "mb-2" >
< label class = "form-label small" > Tapez < strong > SUPPRIMER< / strong > :< / label >
< input type = "text" name = "confirm_text" class = "form-control form-control-sm" placeholder = "SUPPRIMER" required >
< / div >
< button type = "submit" class = "btn btn-danger btn-sm w-100" > < i class = "bi bi-trash" > < / i > Supprimer mails Outlook< / button >
< / form >
< / div >
< / div >
< / div >
< div class = "col-md-3" >
< div class = "card border-danger h-100" >
< div class = "card-header bg-danger text-white" >
< i class = "bi bi-trash" > < / i > Supprimer tous les messages ENT
< / div >
< div class = "card-body" >
< p class = "text-muted small" > {{ stats.ent_messages }} message(s) + {{ stats.ent_interpretations }} interp. Suppression définitive.< / p >
< form method = "POST" action = "{{ url_for('admin.delete_all_ent_messages') }}" onsubmit = "return confirm('CONFIRMATION : Supprimer tous les messages ENT ET interprétations ?')" >
< input type = "hidden" name = "csrf_token" value = "{{ csrf_token() }}" >
< div class = "mb-2" >
< label class = "form-label small" > Tapez < strong > SUPPRIMER< / strong > :< / label >
< input type = "text" name = "confirm_text" class = "form-control form-control-sm" placeholder = "SUPPRIMER" required >
< / div >
< button type = "submit" class = "btn btn-danger btn-sm w-100" > < i class = "bi bi-trash" > < / i > Supprimer messages ENT< / button >
< / form >
< / div >
< / div >
< / div >
< / div >
<!-- Liste des interventions -->
< div class = "card mb-3" >
< div class = "card-header d-flex justify-content-between align-items-center" >
< span > < i class = "bi bi-wrench" > < / i > Interventions en base ({{ stats.interventions }})< / span >
< / div >
{% if interventions.items %}
< div class = "table-responsive" >
< table class = "table table-sm table-hover mb-0" >
< thead >
< tr >
< th > #< / th >
< th > Titre< / th >
< th > Type< / th >
< th > Statut< / th >
< th > Équipement< / th >
< th > Date< / th >
< th class = "text-end" > Actions< / th >
< / tr >
< / thead >
< tbody >
{% for interv in interventions.items %}
< tr >
< td > #{{ interv.id }}< / td >
< td > {{ interv.title[:40] }}{% if interv.title|length > 40 %}…{% endif %}< / td >
< td >
{% if interv.type == 'curatif' %}< span class = "badge bg-danger" > Curatif< / span >
{% else %}< span class = "badge bg-info" > Préventif< / span > {% endif %}
< / td >
< td > < span class = "badge {{ interv.status|status_badge }}" > {{ interv.status|format_status }}< / span > < / td >
< td > {{ interv.equipment.name[:25] if interv.equipment else '—' }}< / td >
< td > {{ interv.scheduled_date.strftime('%d/%m/%Y') if interv.scheduled_date else '—' }}< / td >
< td class = "text-end" >
< form method = "POST" action = "{{ url_for('admin.delete_single_intervention', interv_id=interv.id) }}" style = "display:inline;" >
< input type = "hidden" name = "csrf_token" value = "{{ csrf_token() }}" >
< button type = "submit" class = "btn btn-sm btn-outline-danger" onclick = "return confirm('Supprimer cette intervention ?')" title = "Supprimer" > < i class = "bi bi-trash" > < / i > < / button >
< / form >
< / td >
< / tr >
{% endfor %}
< / tbody >
< / table >
< / div >
< div class = "card-footer" >
{% if interventions.has_prev %}
< a href = "{{ url_for('admin.data_management', interventions_page=interventions.prev_num) }}" class = "btn btn-sm btn-outline-secondary" > < i class = "bi bi-chevron-left" > < / i > Précédent< / a >
{% endif %}
< span class = "mx-2" > Page {{ interventions.page }} / {{ interventions.pages }}< / span >
{% if interventions.has_next %}
< a href = "{{ url_for('admin.data_management', interventions_page=interventions.next_num) }}" class = "btn btn-sm btn-outline-secondary" > Suivant < i class = "bi bi-chevron-right" > < / i > < / a >
{% endif %}
< / div >
{% else %}
< div class = "card-body text-center text-muted py-4" >
< i class = "bi bi-clock display-4" > < / i >
< p class = "mt-2" > Aucune intervention.< / p >
< / div >
{% endif %}
< / div >
<!-- Liste des équipements -->
< div class = "card mb-3" >
< div class = "card-header d-flex justify-content-between align-items-center" >
< span > < i class = "bi bi-pc-display" > < / i > Équipements en base ({{ stats.equipments }})< / span >
< / div >
{% if equipments.items %}
< div class = "table-responsive" >
< table class = "table table-sm table-hover mb-0" >
< thead >
< tr >
< th > Équipement< / th >
< th > Salle< / th >
< th > Statut< / th >
< th class = "text-end" > Actions< / th >
< / tr >
< / thead >
< tbody >
{% for eq in equipments.items %}
< tr >
< td > < a href = "{{ url_for('equipments.detail', id=eq.id) }}" > {{ eq.name[:30] }}{% if eq.name|length > 30 %}…{% endif %}< / a > < / td >
< td > {{ eq.room.name if eq.room else '—' }}< / td >
< td > {{ eq.status or '—' }}< / td >
< td class = "text-end" >
< form method = "POST" action = "{{ url_for('admin.delete_single_equipment', equip_id=eq.id) }}" style = "display:inline;" >
< input type = "hidden" name = "csrf_token" value = "{{ csrf_token() }}" >
< button type = "submit" class = "btn btn-sm btn-outline-danger" onclick = "return confirm('Supprimer cet équipement ?')" title = "Supprimer" > < i class = "bi bi-trash" > < / i > < / button >
< / form >
< / td >
< / tr >
{% endfor %}
< / tbody >
< / table >
< / div >
< div class = "card-footer" >
{% if equipments.has_prev %}
< a href = "{{ url_for('admin.data_management', equipments_page=equipments.prev_num) }}" class = "btn btn-sm btn-outline-secondary" > < i class = "bi bi-chevron-left" > < / i > Précédent< / a >
{% endif %}
< span class = "mx-2" > Page {{ equipments.page }} / {{ equipments.pages }}< / span >
{% if equipments.has_next %}
< a href = "{{ url_for('admin.data_management', equipments_page=equipments.next_num) }}" class = "btn btn-sm btn-outline-secondary" > Suivant < i class = "bi bi-chevron-right" > < / i > < / a >
{% endif %}
< / div >
{% else %}
< div class = "card-body text-center text-muted py-4" >
< i class = "bi bi-gear-fill display-4" > < / i >
< p class = "mt-2" > Aucun équipement.< / p >
< / div >
{% endif %}
< / div >
<!-- Liste des mails Outlook -->
< div class = "card" >
< div class = "card-header d-flex justify-content-between align-items-center" >
< span > < i class = "bi bi-envelope" > < / i > Mails Outlook en base ({{ stats.outlook_mails }})< / span >
2026-08-16 15:32:55 +02:00
< a class = "btn btn-sm {{ 'btn-primary' if missing_only else 'btn-outline-primary' }}" href = "{{ url_for('admin.data_management', non_interpretes='0' if missing_only else '1') }}" >
< i class = "bi bi-funnel" > < / i > {{ 'Afficher tous' if missing_only else 'Non interprétés uniquement' }}
< / a >
2026-08-14 18:02:25 +02:00
< / div >
{% if mails.items %}
< div class = "table-responsive" >
< table class = "table table-sm table-hover mb-0" >
< thead >
< tr >
< th > Expéditeur< / th >
< th > Sujet< / th >
< th > Date< / th >
< th > Interprété< / th >
< th > Actions< / th >
< / tr >
< / thead >
< tbody >
{% for mail in mails.items %}
< tr >
< td >
{{ mail.from_name or '—' }}
{% if mail.from_email %}< br > < small class = "text-muted" > {{ mail.from_email }}< / small > {% endif %}
< / td >
< td > {{ mail.subject[:60] if mail.subject else '—' }}< / td >
2026-08-15 00:47:55 +02:00
< td > {{ mail.received_at|datetime_fmt if mail.received_at else '—' }}< / td >
2026-08-14 18:02:25 +02:00
< td >
{% if outlook_interp_map and mail.id in outlook_interp_map %}
{% set ointerp = outlook_interp_map[mail.id] %}
{% if ointerp.status == 'pending' %}
< span class = "badge bg-warning text-dark" > En attente< / span >
{% elif ointerp.status == 'processed' %}
< span class = "badge bg-success" > Traitée< / span >
{% elif ointerp.status == 'rejected' %}
< span class = "badge bg-danger" > Rejetée< / span >
{% else %}
< span class = "badge bg-info" > {{ ointerp.status }}< / span >
{% endif %}
{% if ointerp.analysis_type %}
< br > < small class = "text-muted" > {{ ointerp.analysis_type }}< / small >
{% endif %}
{% else %}
< span class = "badge bg-secondary" > Non interprété< / span >
2026-08-16 15:32:55 +02:00
< br > < small class = "text-danger" > {% if mail.from_email and mail.from_email in excluded_list %}Expéditeur exclu{% else %}Aucune interprétation créée (échec ou traitement à venir){% endif %}< / small >
2026-08-14 18:02:25 +02:00
{% endif %}
< / td >
< td >
< div class = "btn-group btn-group-sm" >
{% if not (outlook_interp_map and mail.id in outlook_interp_map) %}
< form method = "POST" action = "{{ url_for('admin.interpret_outlook_mail', mail_id=mail.id) }}" style = "display:inline;" onsubmit = "return confirm('Interpréter ce mail maintenant ?')" >
< input type = "hidden" name = "csrf_token" value = "{{ csrf_token() }}" >
< input type = "hidden" name = "page" value = "{{ mails.page }}" >
< button type = "submit" class = "btn btn-sm btn-outline-primary" title = "Interpréter maintenant" > < i class = "bi bi-robot" > < / i > < / button >
< / form >
{% endif %}
{% if mail.from_email %}
< form method = "POST" action = "{{ url_for('admin.ignore_sender') }}" style = "display:inline;" onsubmit = "return confirm('Ignorer cet expéditeur ? Il ne sera plus interprété par le watchdog.')" >
< input type = "hidden" name = "csrf_token" value = "{{ csrf_token() }}" >
< input type = "hidden" name = "sender" value = "{{ mail.from_email }}" >
< input type = "hidden" name = "page" value = "{{ mails.page }}" >
< button type = "submit" class = "btn btn-sm btn-outline-warning" title = "Ignorer cet expéditeur" > < i class = "bi bi-person-x" > < / i > < / button >
< / form >
{% endif %}
< form method = "POST" action = "{{ url_for('admin.delete_single_mail', mail_id=mail.id) }}" style = "display:inline;" onsubmit = "return confirm('Supprimer ce mail ?')" >
< input type = "hidden" name = "csrf_token" value = "{{ csrf_token() }}" >
< input type = "hidden" name = "page" value = "{{ mails.page }}" >
< button type = "submit" class = "btn btn-sm btn-outline-danger" title = "Supprimer" > < i class = "bi bi-trash" > < / i > < / button >
< / form >
< / div >
< / td >
< / tr >
{% endfor %}
< / tbody >
< / table >
< / div >
< div class = "card-footer" >
{% if mails.has_prev %}
< a href = "{{ url_for('admin.data_management', page=mails.prev_num) }}" class = "btn btn-sm btn-outline-secondary" > < i class = "bi bi-chevron-left" > < / i > Précédent< / a >
{% endif %}
< span class = "mx-2" > Page {{ mails.page }} / {{ mails.pages }}< / span >
{% if mails.has_next %}
< a href = "{{ url_for('admin.data_management', page=mails.next_num) }}" class = "btn btn-sm btn-outline-secondary" > Suivant < i class = "bi bi-chevron-right" > < / i > < / a >
{% endif %}
< / div >
{% else %}
< div class = "card-body text-center text-muted py-4" >
< i class = "bi bi-inbox display-4" > < / i >
< p class = "mt-2" > Aucun mail en base.< / p >
< / div >
{% endif %}
< / div >
<!-- Liste des messages ENT -->
< div class = "card mt-3" >
< div class = "card-header d-flex justify-content-between align-items-center" >
< span > < i class = "bi bi-broadcast" > < / i > Messages ENT en base ({{ stats.ent_messages }})< / span >
2026-08-16 15:32:55 +02:00
< a class = "btn btn-sm {{ 'btn-primary' if missing_only else 'btn-outline-primary' }}" href = "{{ url_for('admin.data_management', non_interpretes='0' if missing_only else '1') }}" >
< i class = "bi bi-funnel" > < / i > {{ 'Afficher tous' if missing_only else 'Non interprétés uniquement' }}
< / a >
2026-08-14 18:02:25 +02:00
< / div >
{% if ent_messages.items %}
< div class = "table-responsive" >
< table class = "table table-sm table-hover mb-0" >
< thead >
< tr >
< th > Expéditeur< / th >
< th > Sujet< / th >
< th > Date< / th >
< th > Interprété< / th >
< th > Actions< / th >
< / tr >
< / thead >
< tbody >
{% for msg in ent_messages.items %}
< tr >
< td >
{{ msg.sender_name or '—' }}
{% if msg.original_sender %}< br > < small class = "text-muted" > Transféré par: {{ msg.original_sender }}< / small > {% endif %}
< / td >
< td > {{ msg.subject[:60] if msg.subject else '—' }}< / td >
2026-08-15 00:47:55 +02:00
< td > {{ msg.date|datetime_fmt if msg.date else '—' }}< / td >
2026-08-14 18:02:25 +02:00
< td >
{% if ent_interp_map and msg.id in ent_interp_map %}
{% set interp = ent_interp_map[msg.id] %}
{% if interp.status == 'pending' %}
< span class = "badge bg-warning text-dark" > En attente< / span >
{% elif interp.status == 'processed' %}
< span class = "badge bg-success" > Traitée< / span >
{% elif interp.status == 'rejected' %}
< span class = "badge bg-danger" > Rejetée< / span >
{% else %}
< span class = "badge bg-info" > {{ interp.status }}< / span >
{% endif %}
{% if interp.analysis_type %}
< br > < small class = "text-muted" > {{ interp.analysis_type }}< / small >
{% endif %}
{% else %}
< span class = "badge bg-secondary" > Non interprété< / span >
2026-08-16 15:32:55 +02:00
< br > < small class = "text-danger" > {% if msg.sender_name and msg.sender_name in excluded_list %}Expéditeur exclu{% else %}Aucune interprétation créée (échec ou traitement à venir){% endif %}< / small >
2026-08-14 18:02:25 +02:00
{% endif %}
< / td >
< td >
< div class = "btn-group btn-group-sm" >
{% if not (ent_interp_map and msg.id in ent_interp_map) %}
< form method = "POST" action = "{{ url_for('admin.interpret_ent_message', msg_id=msg.id) }}" style = "display:inline;" onsubmit = "return confirm('Interpréter ce message maintenant ?')" >
< input type = "hidden" name = "csrf_token" value = "{{ csrf_token() }}" >
< input type = "hidden" name = "page" value = "{{ ent_messages.page }}" >
< button type = "submit" class = "btn btn-sm btn-outline-primary" title = "Interpréter maintenant" > < i class = "bi bi-robot" > < / i > < / button >
< / form >
{% endif %}
{% if msg.sender_name %}
< form method = "POST" action = "{{ url_for('admin.ignore_sender') }}" style = "display:inline;" onsubmit = "return confirm('Ignorer cet expéditeur ? Il ne sera plus interprété par le watchdog.')" >
< input type = "hidden" name = "csrf_token" value = "{{ csrf_token() }}" >
< input type = "hidden" name = "sender" value = "{{ msg.sender_name }}" >
< input type = "hidden" name = "page" value = "{{ ent_messages.page }}" >
< button type = "submit" class = "btn btn-sm btn-outline-warning" title = "Ignorer cet expéditeur" > < i class = "bi bi-person-x" > < / i > < / button >
< / form >
{% endif %}
< form method = "POST" action = "{{ url_for('admin.delete_single_ent_message', msg_id=msg.id) }}" style = "display:inline;" onsubmit = "return confirm('Supprimer ce message ENT ?')" >
< input type = "hidden" name = "csrf_token" value = "{{ csrf_token() }}" >
< input type = "hidden" name = "page" value = "{{ ent_messages.page }}" >
< button type = "submit" class = "btn btn-sm btn-outline-danger" title = "Supprimer" > < i class = "bi bi-trash" > < / i > < / button >
< / form >
< / div >
< / td >
< / tr >
{% endfor %}
< / tbody >
< / table >
< / div >
< div class = "card-footer" >
{% if ent_messages.has_prev %}
< a href = "{{ url_for('admin.data_management', page=ent_messages.prev_num) }}" class = "btn btn-sm btn-outline-secondary" > < i class = "bi bi-chevron-left" > < / i > Précédent< / a >
{% endif %}
< span class = "mx-2" > Page {{ ent_messages.page }} / {{ ent_messages.pages }}< / span >
{% if ent_messages.has_next %}
< a href = "{{ url_for('admin.data_management', page=ent_messages.next_num) }}" class = "btn btn-sm btn-outline-secondary" > Suivant < i class = "bi bi-chevron-right" > < / i > < / a >
{% endif %}
< / div >
{% else %}
< div class = "card-body text-center text-muted py-4" >
< i class = "bi bi-broadcast display-4" > < / i >
< p class = "mt-2" > Aucun message ENT en base.< / p >
< / div >
{% endif %}
< / div >
< / div >
2026-08-16 15:32:55 +02:00
{% endblock %}