{% extends "paxalia/base.html" %}
{% load static i18n %}
{% block page_title %}{% trans "Broken Links" %}{% endblock %}
{% block page_subtitle %}{% trans "Investigate real 404s, crawler probes, and the clients reaching missing paths" %}{% endblock %}
{% block extra_css %}
{% endblock %}
{% block filter_bar %}
{% include 'paxalia/includes/filter_bar.html' %}
{% endblock %}
{% block dashboard_content %}
{% trans "All 404s" %}{{ total_404s }}
{% trans "Real / normal" %}{{ human_404s }}
{% trans "Bot / scanner" %}{{ bot_404s }}
{% trans "Unique missing paths" %}{{ unique_missing_paths }}
{% trans "Broken pages to fix" %}
{% trans "Normal 404 requests are separated from automation. Hits, clients, referrers, and first/last sightings help distinguish a genuine broken link from background scanning." %}
{{ date_range_label }}
{% if top_broken_paths %}
{% trans "Path" %}
{% trans "Hits" %}
{% trans "IPs" %}
{% trans "Referrers" %}
{% trans "First seen" %}
{% trans "Last seen" %}
{% if can_manage_bot_paths %}
{% trans "Rule" %}
{% endif %}
{% for row in top_broken_paths %}
{{ row.path }}
{{ row.count }}
{{ row.unique_ips }}
{{ row.unique_referrers }}
{{ row.first_seen|date:"Y-m-d H:i" }}
{{ row.last_seen|date:"Y-m-d H:i" }}
{% if can_manage_bot_paths %}
{% if row.path != "/" %}
{% else %}{% trans "Root path" %}{% endif %}
{% endif %}
{% endfor %}
{% else %}
{% trans "No normal 404 paths were recorded in this date range." %}
{% trans "Each client is grouped with the missing paths it requested, making repeated broken-link attempts easier to investigate." %}
{{ human_clients|length }}
{% if human_clients %}
{% for row in human_clients %}
{{ row.ip_hash }}
{{ row.hits }} {% trans "404s" %} · {{ row.unique_paths }} {% trans "missing paths" %}
{% trans "First" %} {{ row.first_seen|date:"Y-m-d H:i" }} · {% trans "Last" %} {{ row.last_seen|date:"Y-m-d H:i" }}
{% trans "Missing path" %}
{% trans "Hits" %}
{% trans "Last seen" %}
{% for path in row.paths %}
{{ path.path }}
{{ path.count }}
{{ path.last_seen|date:"Y-m-d H:i" }}
{% endfor %}
{% endfor %}
{% else %}
{% trans "No identifiable client IP/hash was recorded for normal 404s." %}
{% endif %}
{% trans "Referrers" %}
{% trans "A referrer indicates another page or external source actively points at the missing URL." %}
{% if top_referrers %}
{% trans "Referrer" %}
{% trans "Hits" %}
{% trans "Missing paths" %}
{% trans "Last seen" %}
{% for row in top_referrers %}
{{ row.referrer }}
{{ row.count }}
{{ row.unique_paths }}
{{ row.last_seen|date:"Y-m-d H:i" }}
{% endfor %}
{% else %}
{% trans "No referred normal 404s were recorded." %}
{% endif %}
{% trans "Scanner / crawler 404s" %}
{% trans "Automation is kept separate from the work queue. A path rule makes future classification deterministic; the one-click cleanup removes matching historical normal-page records in the current filtered scope." %}
{% if scanner_paths %}
{% trans "Path" %}
{% trans "Category" %}
{% trans "Hits" %}
{% trans "IPs" %}
{% trans "First seen" %}
{% trans "Last seen" %}
{% if can_manage_bot_paths %}
{% trans "Rule" %}
{% endif %}
{% for row in scanner_paths %}
{{ row.path }}
{{ row.bot_category|default:"Unclassified" }}
{{ row.count }}
{{ row.unique_ips }}
{{ row.first_seen|date:"Y-m-d H:i" }}
{{ row.last_seen|date:"Y-m-d H:i" }}
{% if can_manage_bot_paths %}
{% if row.path != "/" %}{% endif %}
{% endif %}
{% endfor %}
{% else %}
{% trans "No bot/scanner 404s were recorded in this date range." %}
{% endif %}
{% trans "Automation clients" %}
{% trans "See which IP/hash is responsible for repeated automated 404 probes and which paths it targeted." %}
{% if bot_clients %}
{% for row in bot_clients %}
{{ row.ip_hash }}
{{ row.hits }} {% trans "404s" %} · {{ row.unique_paths }} {% trans "paths" %}
{% trans "First" %} {{ row.first_seen|date:"Y-m-d H:i" }} · {% trans "Last" %} {{ row.last_seen|date:"Y-m-d H:i" }}
{% trans "Target" %}
{% trans "Hits" %}
{% for path in row.paths %}
{{ path.path }}
{{ path.count }}
{% endfor %}
{% endfor %}
{% else %}
{% trans "No identifiable bot/scanner client IPs were recorded." %}