| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 |
- {% extends 'layout.twig' %}
- {% block title %}Dashboard — IRDB{% endblock %}
- {% block content %}
- <div class="mx-auto max-w-6xl">
- <div class="flex items-center justify-between">
- <div>
- <h1 class="text-2xl font-semibold tracking-tight">Dashboard</h1>
- <p class="mt-1 text-sm text-slate-500 dark:text-slate-400">
- Last 24 hours, refreshed every 30 s.
- {% if stats %}<span class="font-mono">reference policy: {{ stats.referencePolicy }}</span>{% endif %}
- </p>
- </div>
- </div>
- {% if not api_reachable %}
- <div class="mt-4 rounded-md border border-amber-300 bg-amber-50 px-4 py-2 text-sm text-amber-800 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-300">
- API unreachable; counters cannot be loaded right now.
- </div>
- {% endif %}
- {% if stats %}
- <section class="mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
- {% set cards = [
- { label: 'Active blocks', value: stats.activeBlocks, hint: 'IPs with score > 0 + manual single IPs' },
- { label: 'Manual blocks', value: stats.manualBlocksCount, hint: 'across IPs and subnets' },
- { label: 'Allowlist entries', value: stats.allowlistCount, hint: 'IPs and subnets' },
- { label: 'Reports (24h)', value: stats.reports24h, hint: 'all categories' },
- ] %}
- {% for card in cards %}
- <div class="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
- <div class="text-xs uppercase tracking-wider text-slate-500 dark:text-slate-400">{{ card.label }}</div>
- <div class="mt-2 font-mono text-3xl font-semibold">{{ card.value }}</div>
- <div class="mt-1 text-xs text-slate-400">{{ card.hint }}</div>
- </div>
- {% endfor %}
- </section>
- <section class="mt-6 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
- <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">Reports per hour</h2>
- <div class="mt-3 h-64">
- <canvas id="reports-chart"
- data-buckets="{{ stats.reportsByHour|json_encode|e('html_attr') }}">
- </canvas>
- </div>
- </section>
- <section class="mt-6 grid grid-cols-1 gap-4 lg:grid-cols-2">
- <div class="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
- <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">Top reporters (24h)</h2>
- {% if stats.topReporters|length > 0 %}
- <table class="mt-3 w-full text-sm">
- <thead class="text-left text-xs uppercase tracking-wider text-slate-400">
- <tr><th class="pb-2 font-medium">Reporter</th><th class="pb-2 text-right font-medium">Reports</th></tr>
- </thead>
- <tbody class="divide-y divide-slate-100 dark:divide-slate-800">
- {% for r in stats.topReporters %}
- <tr><td class="py-1.5 font-mono">{{ r.name }}</td><td class="py-1.5 text-right font-mono">{{ r.count }}</td></tr>
- {% endfor %}
- </tbody>
- </table>
- {% else %}
- <p class="mt-2 text-sm text-slate-400">No reports in the last 24 hours.</p>
- {% endif %}
- </div>
- <div class="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
- <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">Top categories (24h)</h2>
- {% if stats.topCategories|length > 0 %}
- <table class="mt-3 w-full text-sm">
- <thead class="text-left text-xs uppercase tracking-wider text-slate-400">
- <tr><th class="pb-2 font-medium">Category</th><th class="pb-2 text-right font-medium">Reports</th></tr>
- </thead>
- <tbody class="divide-y divide-slate-100 dark:divide-slate-800">
- {% for c in stats.topCategories %}
- <tr><td class="py-1.5 font-mono">{{ c.slug }}</td><td class="py-1.5 text-right font-mono">{{ c.count }}</td></tr>
- {% endfor %}
- </tbody>
- </table>
- {% else %}
- <p class="mt-2 text-sm text-slate-400">No reports in the last 24 hours.</p>
- {% endif %}
- </div>
- </section>
- <section class="mt-6 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
- <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">Jobs status</h2>
- {% if stats.jobsStatus|length > 0 %}
- <ul class="mt-3 space-y-1 text-sm">
- {% for job in stats.jobsStatus %}
- <li class="flex items-center justify-between">
- <span class="font-mono">{{ job.name }}</span>
- <span class="flex items-center gap-2">
- <span class="rounded px-2 py-0.5 text-xs uppercase
- {% if job.status == 'success' %}bg-emerald-100 text-emerald-800 dark:bg-emerald-900 dark:text-emerald-100
- {% elseif job.status == 'failure' %}bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100
- {% else %}bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300{% endif %}">
- {{ job.status }}
- </span>
- {% if job.overdue %}
- <span class="rounded bg-amber-100 px-2 py-0.5 text-xs uppercase text-amber-800 dark:bg-amber-900 dark:text-amber-100">overdue</span>
- {% endif %}
- <span class="text-xs text-slate-500 dark:text-slate-400">
- {{ job.last_finished_at|default('never') }}
- </span>
- </span>
- </li>
- {% endfor %}
- </ul>
- {% else %}
- <p class="mt-2 text-sm text-slate-400">No job runs recorded yet.</p>
- {% endif %}
- </section>
- {% endif %}
- </div>
- {% endblock %}
|