From 785a4639afae9102a7a8affd3f875f622512c914 Mon Sep 17 00:00:00 2001 From: Alpinux Date: Wed, 6 May 2026 13:53:11 +0200 Subject: [PATCH] =?UTF-8?q?v1.8.0=20=E2=80=94=20Fusion=20pages=20Bannis=20?= =?UTF-8?q?et=20Erreurs=20en=20un=20seul=20onglet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La page /errors/ intègre désormais deux onglets (Erreurs 404 et Bannissements) activés via hash URL (#errors / #banned). Le lien "Bannis" disparaît de la nav, la route /errors/banned/ redirige vers /errors/#banned. Co-Authored-By: Claude Sonnet 4.6 --- app/CHANGELOG.md | 10 + app/VERSION | 2 +- app/app.py | 42 ++- app/static/app.css | 10 + app/templates/base.html | 4 +- app/templates/errors_404.html | 469 ++++++++++++++++++++-------------- 6 files changed, 319 insertions(+), 218 deletions(-) diff --git a/app/CHANGELOG.md b/app/CHANGELOG.md index 9802e56..3ff652d 100644 --- a/app/CHANGELOG.md +++ b/app/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog — Alpinux Static +## [1.8.0] — 2026-05-06 + +### Modifié +- Fusion des pages **Erreurs 404** et **Bannis** en une seule page avec deux onglets (URL `/errors/`) +- L'onglet actif est mémorisé dans le hash d'URL (`#errors` / `#banned`) +- Suppression du lien « Bannis » dans la navigation (accessible via l'onglet de la page Erreurs) +- Route `/errors/banned/` redirige vers `/errors/#banned` + +--- + ## [1.7.0] — 2026-05-06 ### Ajouté diff --git a/app/VERSION b/app/VERSION index bd8bf88..27f9cd3 100644 --- a/app/VERSION +++ b/app/VERSION @@ -1 +1 @@ -1.7.0 +1.8.0 diff --git a/app/app.py b/app/app.py index 8b16403..9f2c608 100644 --- a/app/app.py +++ b/app/app.py @@ -1023,6 +1023,8 @@ def errors_404(): redir = _require_admin() if redir: return redir + + # ── Onglet Erreurs 404 ── data = _parse_404s() filtered = {} for path, info in data.items(): @@ -1031,11 +1033,28 @@ def errors_404(): filtered[path] = entry entries = sorted(filtered.items(), key=lambda x: x[1]["count"], reverse=True) ignored = _load_ignored_ips() + + # ── Onglet Bannissements ── + banned_ips, banned_nets = _get_banned_ips() + all_banned = sorted(banned_ips) + sorted(str(n) for n in banned_nets) + by_asn: dict = {} + for b_entry in all_banned: + rep_ip = b_entry.split("/")[0] + info = _lookup_ip_asn(rep_ip) + key = info.get("asn") or "?" + if key not in by_asn: + by_asn[key] = {"asn": info.get("asn", ""), "name": info.get("name", ""), + "country": info.get("country", ""), "entries": []} + by_asn[key]["entries"].append(b_entry) + banned_groups = sorted(by_asn.values(), key=lambda g: len(g["entries"]), reverse=True) + return render_template("errors_404.html", entries=entries, ignored_ips=sorted(ignored), total=sum(v["count"] for v in filtered.values()), log_configured=bool(STATS_LOG_FILE), + banned_groups=banned_groups, + banned_total=len(all_banned), ) @@ -1140,28 +1159,7 @@ def errors_ban(): @app.route("/errors/banned/") def errors_banned(): - redir = _require_admin() - if redir: - return redir - banned_ips, banned_nets = _get_banned_ips() - all_entries = sorted(banned_ips) + sorted(str(n) for n in banned_nets) - - by_asn: dict = {} - for entry in all_entries: - rep_ip = entry.split("/")[0] - info = _lookup_ip_asn(rep_ip) - key = info.get("asn") or "?" - if key not in by_asn: - by_asn[key] = { - "asn": info.get("asn", ""), - "name": info.get("name", ""), - "country": info.get("country", ""), - "entries": [], - } - by_asn[key]["entries"].append(entry) - - groups = sorted(by_asn.values(), key=lambda g: len(g["entries"]), reverse=True) - return render_template("banned.html", groups=groups, total=len(all_entries)) + return redirect(url_for('errors_404') + '#banned') @app.route("/errors/unban", methods=["POST"]) diff --git a/app/static/app.css b/app/static/app.css index b84898f..d539c81 100644 --- a/app/static/app.css +++ b/app/static/app.css @@ -391,3 +391,13 @@ footer { background: var(--blue-dark); color: rgba(255,255,255,.6); margin-top: .btn-unban { font-size: .75rem; padding: .2rem .5rem; background: #f0fdf4; color: #15803d; border: 1px solid #86efac; border-radius: 5px; cursor: pointer; } .btn-unban:hover:not(:disabled) { filter: brightness(.92); } .btn-unban-as { background: #fefce8; color: #854d0e; border-color: #fde68a; } + +/* ── Section tabs (Erreurs / Bannis) ───────────────────────────── */ +.err-header { display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem; flex-wrap: wrap; } +.err-header h2 { margin: 0; } +.sec-tabs { display: flex; gap: .25rem; } +.sec-tab { background: none; border: 1px solid var(--border); border-radius: 6px; padding: .35rem .9rem; font-size: .88rem; cursor: pointer; color: var(--muted); transition: background .15s, color .15s; } +.sec-tab:hover { background: var(--blue-light); color: var(--blue); } +.sec-tab--active { background: var(--blue); color: #fff; border-color: var(--blue); font-weight: 600; } +.tab-badge { display: inline-flex; align-items: center; justify-content: center; background: rgba(255,255,255,.25); border-radius: 9px; font-size: .72rem; font-weight: 700; min-width: 1.2rem; padding: 0 .3rem; margin-left: .3rem; vertical-align: middle; } +.sec-tab:not(.sec-tab--active) .tab-badge { background: #e5e7eb; color: #6b7280; } diff --git a/app/templates/base.html b/app/templates/base.html index 348b273..2756893 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -25,9 +25,7 @@ {% if request.endpoint in ('stats', 'stats_report') %}class="active"{% endif %}>Statistiques Erreurs - Bannis - Corbeille{% if trash_count %}{{ trash_count }}{% endif %}