451 lines
No EOL
25 KiB
HTML
451 lines
No EOL
25 KiB
HTML
{% 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>
|
|
</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>
|
|
<td>{{ mail.received_at|datetime_fmt if mail.received_at else '—' }}</td>
|
|
<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>
|
|
{% 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>
|
|
</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>
|
|
<td>{{ msg.date|datetime_fmt if msg.date else '—' }}</td>
|
|
<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>
|
|
{% 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>
|
|
{% endblock %} |