2026-08-21 13:08:30 +02:00
{% extends "base.html" %}
{% block title %}Audit des templates — Administration{% endblock %}
{% block content %}
< div class = "container-fluid py-3" >
< div class = "d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-2 mb-3" >
< div >
< h1 class = "h3 mb-1" > < i class = "bi bi-files" > < / i > Audit des templates< / h1 >
< p class = "text-muted mb-0" > Repérez les copies identiques ou divergentes et marquez celles qui ne doivent plus être utilisées.< / p >
< / div >
< a href = "{{ url_for('admin.data_management') }}" class = "btn btn-outline-secondary btn-sm" > < i class = "bi bi-arrow-left" > < / i > Gestion des données< / a >
< / div >
< div class = "alert alert-info py-2" >
< i class = "bi bi-info-circle" > < / i > Le marquage < strong > obsolète< / strong > est documentaire : aucun fichier n’ est supprimé et le fonctionnement du site n’ est pas modifié.
« Utilisé probablement » reflète l’ ordre des chargeurs Jinja ; vérifiez les routes avant toute suppression manuelle.
< / div >
2026-08-21 13:26:32 +02:00
< div class = "card border-0 shadow-sm mb-3" >
< div class = "card-header d-flex justify-content-between align-items-center" >
< strong > < i class = "bi bi-diagram-3" > < / i > Architecture cible des templates< / strong >
< span class = "badge bg-primary" > Convention recommandée< / span >
< / div >
< div class = "card-body" >
< p class = "small text-muted" > Le domaine métier est propriétaire de ses écrans. Le dossier global reste réservé au socle partagé ; une seule copie canonique doit être conservée par écran.< / p >
< div class = "row g-2" >
{% for node in template_architecture %}
< div class = "col-md-6 col-xl-3" >
< div class = "border rounded h-100 p-2 {% if 'transition' in node.label|lower %}bg-warning-subtle{% else %}bg-light{% endif %}" >
< code class = "d-block text-break" > {{ node.path }}< / code >
< strong class = "small" > {{ node.label }}< / strong >
< p class = "small text-muted mb-1" > {{ node.description }}< / p >
< div class = "small" > {% for child in node.children %}< span class = "badge text-bg-secondary me-1 mb-1" > {{ child }}< / span > {% endfor %}< / div >
< / div >
< / div >
{% endfor %}
< / div >
< / div >
< / div >
2026-08-21 13:08:30 +02:00
< div class = "row g-2 mb-3" >
{% for label, value, color in [('Templates', stats.total, 'primary'), ('Groupes doublons', stats.duplicate_groups, 'warning'), ('Identiques', stats.identical_groups, 'success'), ('Divergents', stats.divergent_groups, 'danger'), ('Marqués obsolètes', stats.obsolete, 'secondary')] %}
< div class = "col-6 col-md" > < div class = "card border-0 shadow-sm h-100" > < div class = "card-body py-2" > < div class = "small text-muted" > {{ label }}< / div > < div class = "h4 text-{{ color }} mb-0" > {{ value }}< / div > < / div > < / div > < / div >
{% endfor %}
< / div >
< div class = "btn-group btn-group-sm mb-3" role = "group" >
< a class = "btn {{ 'btn-primary' if selected_filter == 'duplicates' else 'btn-outline-primary' }}" href = "{{ url_for('admin.template_debug', filter='duplicates') }}" > Doublons< / a >
< a class = "btn {{ 'btn-primary' if selected_filter == 'obsolete' else 'btn-outline-primary' }}" href = "{{ url_for('admin.template_debug', filter='obsolete') }}" > Obsolètes< / a >
< a class = "btn {{ 'btn-primary' if selected_filter == 'all' else 'btn-outline-primary' }}" href = "{{ url_for('admin.template_debug', filter='all') }}" > Tous< / a >
< / div >
{% if groups %}
{% for logical_key, templates in groups|dictsort %}
< div class = "card border-0 shadow-sm mb-3" >
< div class = "card-header d-flex flex-wrap justify-content-between gap-2 align-items-center" >
2026-08-21 13:26:32 +02:00
< div >
< code > {{ logical_key }}< / code >
< span class = "text-muted small ms-2" > Cible : < code > {{ templates[0].target_path }}< / code > < / span >
< / div >
2026-08-21 13:08:30 +02:00
{% if templates[0].duplicate_count > 1 %}
< span class = "badge bg-{{ 'success' if templates[0].comparison == 'identique' else 'danger' }}" > {{ templates[0].duplicate_count }} copies — {{ templates[0].comparison }}< / span >
{% else %}< span class = "badge bg-secondary" > unique< / span > {% endif %}
< / div >
< div class = "table-responsive" >
< table class = "table table-sm align-middle mb-0" >
2026-08-21 13:26:32 +02:00
< thead > < tr > < th > Fichier< / th > < th > Architecture cible< / th > < th > Source< / th > < th > État estimé< / th > < th > Empreinte< / th > < th > Actions< / th > < th style = "min-width:300px" > Décision< / th > < / tr > < / thead >
2026-08-21 13:08:30 +02:00
< tbody >
{% for template in templates %}
< tr class = "{% if template.mark and template.mark.status == 'obsolete' %}table-secondary{% endif %}" >
< td > < code class = "text-break" > {{ template.path }}< / code > < / td >
2026-08-21 13:26:32 +02:00
< td > < code class = "text-break small" > {{ template.target_path }}< / code > < / td >
2026-08-21 13:08:30 +02:00
< td > < span class = "badge bg-{{ 'primary' if template.source == 'global' else 'info' }}" > {{ template.source }}< / span > < / td >
< td >
{% if template.mark and template.mark.status == 'obsolete' %}< span class = "badge bg-secondary" > Obsolète< / span >
{% elif template.probable_active %}< span class = "badge bg-success" > Utilisé probablement< / span >
{% else %}< span class = "badge bg-warning text-dark" > Masqué probablement< / span > {% endif %}
< / td >
< td > < code title = "{{ template.hash }}" > {{ template.hash[:10] }}< / code > < / td >
2026-08-21 13:26:32 +02:00
< td > < a class = "btn btn-outline-primary btn-sm" href = "{{ url_for('admin.template_debug_preview', path=template.path) }}" target = "_blank" rel = "noopener" > < i class = "bi bi-eye" > < / i > Prévisualiser< / a > < / td >
2026-08-21 13:08:30 +02:00
< td >
< form method = "post" action = "{{ url_for('admin.template_debug_mark') }}" class = "d-flex gap-1" >
< input type = "hidden" name = "path" value = "{{ template.path }}" >
< input type = "hidden" name = "return_filter" value = "{{ selected_filter }}" >
{% if template.mark and template.mark.status == 'obsolete' %}
< span class = "small text-muted flex-grow-1" > {{ template.mark.notes or 'Aucune note' }}< / span >
< button class = "btn btn-outline-success btn-sm" name = "action" value = "restore" > < i class = "bi bi-arrow-counterclockwise" > < / i > Réactiver< / button >
{% else %}
< input class = "form-control form-control-sm" name = "notes" placeholder = "Raison / remplacement conseillé" >
< button class = "btn btn-outline-danger btn-sm text-nowrap" name = "action" value = "obsolete" > < i class = "bi bi-archive" > < / i > Obsolète< / button >
{% endif %}
< / form >
< / td >
< / tr >
{% endfor %}
< / tbody >
< / table >
< / div >
< / div >
{% endfor %}
{% else %}
< div class = "alert alert-secondary" > Aucun template ne correspond à ce filtre.< / div >
{% endif %}
< / div >
{% endblock %}