dashboard.twig 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. {% extends 'layout.twig' %}
  2. {% block title %}Dashboard — IRDB{% endblock %}
  3. {% block content %}
  4. <div class="mx-auto max-w-6xl">
  5. <div class="flex items-center justify-between">
  6. <div>
  7. <h1 class="text-2xl font-semibold tracking-tight">Dashboard</h1>
  8. <p class="mt-1 text-sm text-slate-500 dark:text-slate-400">
  9. Last 24 hours, refreshed every 30 s.
  10. {% if stats %}<span class="font-mono">reference policy: {{ stats.referencePolicy }}</span>{% endif %}
  11. </p>
  12. </div>
  13. </div>
  14. {% if not api_reachable %}
  15. <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">
  16. API unreachable; counters cannot be loaded right now.
  17. </div>
  18. {% endif %}
  19. {% if stats %}
  20. <section class="mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
  21. {% set cards = [
  22. { label: 'Active blocks', value: stats.activeBlocks, hint: 'IPs with score > 0 + manual single IPs' },
  23. { label: 'Manual blocks', value: stats.manualBlocksCount, hint: 'across IPs and subnets' },
  24. { label: 'Allowlist entries', value: stats.allowlistCount, hint: 'IPs and subnets' },
  25. { label: 'Reports (24h)', value: stats.reports24h, hint: 'all categories' },
  26. ] %}
  27. {% for card in cards %}
  28. <div class="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
  29. <div class="text-xs uppercase tracking-wider text-slate-500 dark:text-slate-400">{{ card.label }}</div>
  30. <div class="mt-2 font-mono text-3xl font-semibold">{{ card.value }}</div>
  31. <div class="mt-1 text-xs text-slate-400">{{ card.hint }}</div>
  32. </div>
  33. {% endfor %}
  34. </section>
  35. <section class="mt-6 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
  36. <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">Reports per hour</h2>
  37. <div class="mt-3 h-64">
  38. <canvas id="reports-chart"
  39. data-buckets="{{ stats.reportsByHour|json_encode|e('html_attr') }}">
  40. </canvas>
  41. </div>
  42. </section>
  43. <section class="mt-6 grid grid-cols-1 gap-4 lg:grid-cols-2">
  44. <div class="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
  45. <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">Top reporters (24h)</h2>
  46. {% if stats.topReporters|length > 0 %}
  47. <table class="mt-3 w-full text-sm">
  48. <thead class="text-left text-xs uppercase tracking-wider text-slate-400">
  49. <tr><th class="pb-2 font-medium">Reporter</th><th class="pb-2 text-right font-medium">Reports</th></tr>
  50. </thead>
  51. <tbody class="divide-y divide-slate-100 dark:divide-slate-800">
  52. {% for r in stats.topReporters %}
  53. <tr><td class="py-1.5 font-mono">{{ r.name }}</td><td class="py-1.5 text-right font-mono">{{ r.count }}</td></tr>
  54. {% endfor %}
  55. </tbody>
  56. </table>
  57. {% else %}
  58. <p class="mt-2 text-sm text-slate-400">No reports in the last 24 hours.</p>
  59. {% endif %}
  60. </div>
  61. <div class="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
  62. <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">Top categories (24h)</h2>
  63. {% if stats.topCategories|length > 0 %}
  64. <table class="mt-3 w-full text-sm">
  65. <thead class="text-left text-xs uppercase tracking-wider text-slate-400">
  66. <tr><th class="pb-2 font-medium">Category</th><th class="pb-2 text-right font-medium">Reports</th></tr>
  67. </thead>
  68. <tbody class="divide-y divide-slate-100 dark:divide-slate-800">
  69. {% for c in stats.topCategories %}
  70. <tr><td class="py-1.5 font-mono">{{ c.slug }}</td><td class="py-1.5 text-right font-mono">{{ c.count }}</td></tr>
  71. {% endfor %}
  72. </tbody>
  73. </table>
  74. {% else %}
  75. <p class="mt-2 text-sm text-slate-400">No reports in the last 24 hours.</p>
  76. {% endif %}
  77. </div>
  78. </section>
  79. <section class="mt-6 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
  80. <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">Jobs status</h2>
  81. {% if stats.jobsStatus|length > 0 %}
  82. <ul class="mt-3 space-y-1 text-sm">
  83. {% for job in stats.jobsStatus %}
  84. <li class="flex items-center justify-between">
  85. <span class="font-mono">{{ job.name }}</span>
  86. <span class="flex items-center gap-2">
  87. <span class="rounded px-2 py-0.5 text-xs uppercase
  88. {% if job.status == 'success' %}bg-emerald-100 text-emerald-800 dark:bg-emerald-900 dark:text-emerald-100
  89. {% elseif job.status == 'failure' %}bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100
  90. {% else %}bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300{% endif %}">
  91. {{ job.status }}
  92. </span>
  93. {% if job.overdue %}
  94. <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>
  95. {% endif %}
  96. <span class="text-xs text-slate-500 dark:text-slate-400">
  97. {% if job.last_finished_at %}<time class="irdb-dt" datetime="{{ job.last_finished_at }}">{{ job.last_finished_at }}</time>{% else %}never{% endif %}
  98. </span>
  99. </span>
  100. </li>
  101. {% endfor %}
  102. </ul>
  103. {% else %}
  104. <p class="mt-2 text-sm text-slate-400">No job runs recorded yet.</p>
  105. {% endif %}
  106. </section>
  107. {% endif %}
  108. </div>
  109. {% endblock %}