index.twig 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. {% extends 'layout.twig' %}
  2. {% block title %}Search — IRDB{% endblock %}
  3. {% block content %}
  4. <div class="mx-auto max-w-5xl">
  5. <div class="flex items-baseline justify-between gap-4">
  6. <h1 class="text-2xl font-semibold tracking-tight">Search</h1>
  7. {% if has_query %}
  8. <span class="text-sm text-slate-500 dark:text-slate-400">
  9. Query: <span class="font-mono text-slate-700 dark:text-slate-200">{{ query }}</span>
  10. </span>
  11. {% endif %}
  12. </div>
  13. <form method="get" action="/app/search" class="mt-4">
  14. <label for="search-q" class="sr-only">Search IPs</label>
  15. <div class="flex gap-2">
  16. <input type="search" id="search-q" name="q" value="{{ query }}"
  17. placeholder="Enter an IP, prefix, or CIDR (e.g. 203.0.113 or 10.0.0.0/8)"
  18. class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm font-mono dark:border-slate-700 dark:bg-slate-900"
  19. autofocus>
  20. <button type="submit" class="rounded-md bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-500">Search</button>
  21. </div>
  22. </form>
  23. {% if not has_query %}
  24. <p class="mt-6 text-sm text-slate-500 dark:text-slate-400">
  25. Enter an IP address or prefix to search across the IPs list, manual blocks, and allowlist.
  26. </p>
  27. {% else %}
  28. {# ------------------------------- IPs --------------------------------- #}
  29. <section class="mt-8">
  30. <div class="flex items-center justify-between">
  31. <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">
  32. IPs <span class="ml-2 rounded bg-slate-100 px-1.5 py-0.5 font-mono text-xs text-slate-700 dark:bg-slate-800 dark:text-slate-300">{{ ips ? ips.total : 0 }}</span>
  33. </h2>
  34. <a href="/app/ips?q={{ query|url_encode }}" class="text-sm text-indigo-600 hover:underline dark:text-indigo-400">
  35. Open in IPs &rsaquo;
  36. </a>
  37. </div>
  38. {% if errors.ips is defined %}
  39. <div class="mt-2 rounded-md border border-red-300 bg-red-50 px-3 py-2 text-sm text-red-800 dark:border-red-800 dark:bg-red-950 dark:text-red-300">
  40. Failed to load IPs: {{ errors.ips }}
  41. </div>
  42. {% elseif ips and ips.items|length > 0 %}
  43. <div class="mt-3 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900">
  44. <table class="w-full text-sm">
  45. <thead class="border-b border-slate-200 bg-slate-50 text-left text-xs uppercase tracking-wider text-slate-500 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-400">
  46. <tr>
  47. <th class="px-4 py-2 font-medium">IP</th>
  48. <th class="px-4 py-2 font-medium">Top category</th>
  49. <th class="px-4 py-2 text-right font-medium">Max score</th>
  50. <th class="px-4 py-2 font-medium">Last report</th>
  51. <th class="px-4 py-2 font-medium">Status</th>
  52. </tr>
  53. </thead>
  54. <tbody class="divide-y divide-slate-100 dark:divide-slate-800">
  55. {% for item in ips.items %}
  56. <tr>
  57. <td class="px-4 py-2"><a href="/app/ips/{{ item.ip|url_encode }}" class="font-mono text-indigo-600 hover:underline dark:text-indigo-400">{{ item.ip }}</a></td>
  58. <td class="px-4 py-2 font-mono text-slate-600 dark:text-slate-300">{{ item.topCategory|default('—') }}</td>
  59. <td class="px-4 py-2 text-right font-mono">{{ item.maxScore|number_format(2) }}</td>
  60. <td class="px-4 py-2 text-slate-500 dark:text-slate-400">{{ item.lastReportAt|default('—') }}</td>
  61. <td class="px-4 py-2 font-mono text-xs uppercase">{{ item.status }}</td>
  62. </tr>
  63. {% endfor %}
  64. </tbody>
  65. </table>
  66. </div>
  67. {% if ips.total > ips.items|length %}
  68. <p class="mt-2 text-xs text-slate-500 dark:text-slate-400">
  69. Showing {{ ips.items|length }} of {{ ips.total }} —
  70. <a href="/app/ips?q={{ query|url_encode }}" class="text-indigo-600 hover:underline dark:text-indigo-400">view all</a>.
  71. </p>
  72. {% endif %}
  73. {% else %}
  74. <p class="mt-3 text-sm text-slate-500 dark:text-slate-400">No IPs match this query.</p>
  75. {% endif %}
  76. </section>
  77. {# --------------------------- Manual blocks --------------------------- #}
  78. <section class="mt-8">
  79. <div class="flex items-center justify-between">
  80. <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">
  81. Manual blocks <span class="ml-2 rounded bg-slate-100 px-1.5 py-0.5 font-mono text-xs text-slate-700 dark:bg-slate-800 dark:text-slate-300">{{ manual_blocks|length }}</span>
  82. </h2>
  83. <a href="/app/manual-blocks" class="text-sm text-indigo-600 hover:underline dark:text-indigo-400">
  84. Open Manual blocks &rsaquo;
  85. </a>
  86. </div>
  87. {% if errors.manual_blocks is defined %}
  88. <div class="mt-2 rounded-md border border-red-300 bg-red-50 px-3 py-2 text-sm text-red-800 dark:border-red-800 dark:bg-red-950 dark:text-red-300">
  89. Failed to load manual blocks: {{ errors.manual_blocks }}
  90. </div>
  91. {% elseif manual_blocks|length > 0 %}
  92. <div class="mt-3 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900">
  93. <table class="w-full text-sm">
  94. <thead class="border-b border-slate-200 bg-slate-50 text-left text-xs uppercase tracking-wider text-slate-500 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-400">
  95. <tr>
  96. <th class="px-4 py-2 font-medium">Kind</th>
  97. <th class="px-4 py-2 font-medium">Target</th>
  98. <th class="px-4 py-2 font-medium">Reason</th>
  99. <th class="px-4 py-2 font-medium">Expires</th>
  100. <th class="px-4 py-2 font-medium">Created</th>
  101. </tr>
  102. </thead>
  103. <tbody class="divide-y divide-slate-100 dark:divide-slate-800">
  104. {% for item in manual_blocks %}
  105. <tr>
  106. <td class="px-4 py-2 font-mono text-xs uppercase">{{ item.kind }}</td>
  107. <td class="px-4 py-2 font-mono">{{ item.kind == 'ip' ? item.ip : item.cidr }}</td>
  108. <td class="px-4 py-2 text-slate-600 dark:text-slate-300">{{ item.reason|default('—') }}</td>
  109. <td class="px-4 py-2 text-slate-500 dark:text-slate-400">{{ item.expires_at|default('—') }}</td>
  110. <td class="px-4 py-2 text-slate-500 dark:text-slate-400">{{ item.created_at }}</td>
  111. </tr>
  112. {% endfor %}
  113. </tbody>
  114. </table>
  115. </div>
  116. {% else %}
  117. <p class="mt-3 text-sm text-slate-500 dark:text-slate-400">No manual blocks match this query.</p>
  118. {% endif %}
  119. </section>
  120. {# ------------------------------ Allowlist --------------------------- #}
  121. <section class="mt-8">
  122. <div class="flex items-center justify-between">
  123. <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">
  124. Allowlist <span class="ml-2 rounded bg-slate-100 px-1.5 py-0.5 font-mono text-xs text-slate-700 dark:bg-slate-800 dark:text-slate-300">{{ allowlist|length }}</span>
  125. </h2>
  126. <a href="/app/allowlist" class="text-sm text-indigo-600 hover:underline dark:text-indigo-400">
  127. Open Allowlist &rsaquo;
  128. </a>
  129. </div>
  130. {% if errors.allowlist is defined %}
  131. <div class="mt-2 rounded-md border border-red-300 bg-red-50 px-3 py-2 text-sm text-red-800 dark:border-red-800 dark:bg-red-950 dark:text-red-300">
  132. Failed to load allowlist: {{ errors.allowlist }}
  133. </div>
  134. {% elseif allowlist|length > 0 %}
  135. <div class="mt-3 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900">
  136. <table class="w-full text-sm">
  137. <thead class="border-b border-slate-200 bg-slate-50 text-left text-xs uppercase tracking-wider text-slate-500 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-400">
  138. <tr>
  139. <th class="px-4 py-2 font-medium">Kind</th>
  140. <th class="px-4 py-2 font-medium">Target</th>
  141. <th class="px-4 py-2 font-medium">Reason</th>
  142. <th class="px-4 py-2 font-medium">Created</th>
  143. </tr>
  144. </thead>
  145. <tbody class="divide-y divide-slate-100 dark:divide-slate-800">
  146. {% for item in allowlist %}
  147. <tr>
  148. <td class="px-4 py-2 font-mono text-xs uppercase">{{ item.kind }}</td>
  149. <td class="px-4 py-2 font-mono">{{ item.kind == 'ip' ? item.ip : item.cidr }}</td>
  150. <td class="px-4 py-2 text-slate-600 dark:text-slate-300">{{ item.reason|default('—') }}</td>
  151. <td class="px-4 py-2 text-slate-500 dark:text-slate-400">{{ item.created_at }}</td>
  152. </tr>
  153. {% endfor %}
  154. </tbody>
  155. </table>
  156. </div>
  157. {% else %}
  158. <p class="mt-3 text-sm text-slate-500 dark:text-slate-400">No allowlist entries match this query.</p>
  159. {% endif %}
  160. </section>
  161. {% endif %}
  162. </div>
  163. {% endblock %}