|
|
@@ -0,0 +1,169 @@
|
|
|
+{% extends 'layout.twig' %}
|
|
|
+
|
|
|
+{% block title %}Search — IRDB{% endblock %}
|
|
|
+
|
|
|
+{% block content %}
|
|
|
+<div class="mx-auto max-w-5xl">
|
|
|
+ <div class="flex items-baseline justify-between gap-4">
|
|
|
+ <h1 class="text-2xl font-semibold tracking-tight">Search</h1>
|
|
|
+ {% if has_query %}
|
|
|
+ <span class="text-sm text-slate-500 dark:text-slate-400">
|
|
|
+ Query: <span class="font-mono text-slate-700 dark:text-slate-200">{{ query }}</span>
|
|
|
+ </span>
|
|
|
+ {% endif %}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <form method="get" action="/app/search" class="mt-4">
|
|
|
+ <label for="search-q" class="sr-only">Search IPs</label>
|
|
|
+ <div class="flex gap-2">
|
|
|
+ <input type="search" id="search-q" name="q" value="{{ query }}"
|
|
|
+ placeholder="Enter an IP, prefix, or CIDR (e.g. 203.0.113 or 10.0.0.0/8)"
|
|
|
+ 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"
|
|
|
+ autofocus>
|
|
|
+ <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>
|
|
|
+ </div>
|
|
|
+ </form>
|
|
|
+
|
|
|
+ {% if not has_query %}
|
|
|
+ <p class="mt-6 text-sm text-slate-500 dark:text-slate-400">
|
|
|
+ Enter an IP address or prefix to search across the IPs list, manual blocks, and allowlist.
|
|
|
+ </p>
|
|
|
+ {% else %}
|
|
|
+ {# ------------------------------- IPs --------------------------------- #}
|
|
|
+ <section class="mt-8">
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">
|
|
|
+ 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>
|
|
|
+ </h2>
|
|
|
+ <a href="/app/ips?q={{ query|url_encode }}" class="text-sm text-indigo-600 hover:underline dark:text-indigo-400">
|
|
|
+ Open in IPs ›
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+ {% if errors.ips is defined %}
|
|
|
+ <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">
|
|
|
+ Failed to load IPs: {{ errors.ips }}
|
|
|
+ </div>
|
|
|
+ {% elseif ips and ips.items|length > 0 %}
|
|
|
+ <div class="mt-3 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900">
|
|
|
+ <table class="w-full text-sm">
|
|
|
+ <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">
|
|
|
+ <tr>
|
|
|
+ <th class="px-4 py-2 font-medium">IP</th>
|
|
|
+ <th class="px-4 py-2 font-medium">Top category</th>
|
|
|
+ <th class="px-4 py-2 text-right font-medium">Max score</th>
|
|
|
+ <th class="px-4 py-2 font-medium">Last report</th>
|
|
|
+ <th class="px-4 py-2 font-medium">Status</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody class="divide-y divide-slate-100 dark:divide-slate-800">
|
|
|
+ {% for item in ips.items %}
|
|
|
+ <tr>
|
|
|
+ <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>
|
|
|
+ <td class="px-4 py-2 font-mono text-slate-600 dark:text-slate-300">{{ item.topCategory|default('—') }}</td>
|
|
|
+ <td class="px-4 py-2 text-right font-mono">{{ item.maxScore|number_format(2) }}</td>
|
|
|
+ <td class="px-4 py-2 text-slate-500 dark:text-slate-400">{{ item.lastReportAt|default('—') }}</td>
|
|
|
+ <td class="px-4 py-2 font-mono text-xs uppercase">{{ item.status }}</td>
|
|
|
+ </tr>
|
|
|
+ {% endfor %}
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ {% if ips.total > ips.items|length %}
|
|
|
+ <p class="mt-2 text-xs text-slate-500 dark:text-slate-400">
|
|
|
+ Showing {{ ips.items|length }} of {{ ips.total }} —
|
|
|
+ <a href="/app/ips?q={{ query|url_encode }}" class="text-indigo-600 hover:underline dark:text-indigo-400">view all</a>.
|
|
|
+ </p>
|
|
|
+ {% endif %}
|
|
|
+ {% else %}
|
|
|
+ <p class="mt-3 text-sm text-slate-500 dark:text-slate-400">No IPs match this query.</p>
|
|
|
+ {% endif %}
|
|
|
+ </section>
|
|
|
+
|
|
|
+ {# --------------------------- Manual blocks --------------------------- #}
|
|
|
+ <section class="mt-8">
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">
|
|
|
+ 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>
|
|
|
+ </h2>
|
|
|
+ <a href="/app/manual-blocks" class="text-sm text-indigo-600 hover:underline dark:text-indigo-400">
|
|
|
+ Open Manual blocks ›
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+ {% if errors.manual_blocks is defined %}
|
|
|
+ <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">
|
|
|
+ Failed to load manual blocks: {{ errors.manual_blocks }}
|
|
|
+ </div>
|
|
|
+ {% elseif manual_blocks|length > 0 %}
|
|
|
+ <div class="mt-3 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900">
|
|
|
+ <table class="w-full text-sm">
|
|
|
+ <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">
|
|
|
+ <tr>
|
|
|
+ <th class="px-4 py-2 font-medium">Kind</th>
|
|
|
+ <th class="px-4 py-2 font-medium">Target</th>
|
|
|
+ <th class="px-4 py-2 font-medium">Reason</th>
|
|
|
+ <th class="px-4 py-2 font-medium">Expires</th>
|
|
|
+ <th class="px-4 py-2 font-medium">Created</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody class="divide-y divide-slate-100 dark:divide-slate-800">
|
|
|
+ {% for item in manual_blocks %}
|
|
|
+ <tr>
|
|
|
+ <td class="px-4 py-2 font-mono text-xs uppercase">{{ item.kind }}</td>
|
|
|
+ <td class="px-4 py-2 font-mono">{{ item.kind == 'ip' ? item.ip : item.cidr }}</td>
|
|
|
+ <td class="px-4 py-2 text-slate-600 dark:text-slate-300">{{ item.reason|default('—') }}</td>
|
|
|
+ <td class="px-4 py-2 text-slate-500 dark:text-slate-400">{{ item.expires_at|default('—') }}</td>
|
|
|
+ <td class="px-4 py-2 text-slate-500 dark:text-slate-400">{{ item.created_at }}</td>
|
|
|
+ </tr>
|
|
|
+ {% endfor %}
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ {% else %}
|
|
|
+ <p class="mt-3 text-sm text-slate-500 dark:text-slate-400">No manual blocks match this query.</p>
|
|
|
+ {% endif %}
|
|
|
+ </section>
|
|
|
+
|
|
|
+ {# ------------------------------ Allowlist --------------------------- #}
|
|
|
+ <section class="mt-8">
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">
|
|
|
+ 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>
|
|
|
+ </h2>
|
|
|
+ <a href="/app/allowlist" class="text-sm text-indigo-600 hover:underline dark:text-indigo-400">
|
|
|
+ Open Allowlist ›
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+ {% if errors.allowlist is defined %}
|
|
|
+ <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">
|
|
|
+ Failed to load allowlist: {{ errors.allowlist }}
|
|
|
+ </div>
|
|
|
+ {% elseif allowlist|length > 0 %}
|
|
|
+ <div class="mt-3 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900">
|
|
|
+ <table class="w-full text-sm">
|
|
|
+ <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">
|
|
|
+ <tr>
|
|
|
+ <th class="px-4 py-2 font-medium">Kind</th>
|
|
|
+ <th class="px-4 py-2 font-medium">Target</th>
|
|
|
+ <th class="px-4 py-2 font-medium">Reason</th>
|
|
|
+ <th class="px-4 py-2 font-medium">Created</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody class="divide-y divide-slate-100 dark:divide-slate-800">
|
|
|
+ {% for item in allowlist %}
|
|
|
+ <tr>
|
|
|
+ <td class="px-4 py-2 font-mono text-xs uppercase">{{ item.kind }}</td>
|
|
|
+ <td class="px-4 py-2 font-mono">{{ item.kind == 'ip' ? item.ip : item.cidr }}</td>
|
|
|
+ <td class="px-4 py-2 text-slate-600 dark:text-slate-300">{{ item.reason|default('—') }}</td>
|
|
|
+ <td class="px-4 py-2 text-slate-500 dark:text-slate-400">{{ item.created_at }}</td>
|
|
|
+ </tr>
|
|
|
+ {% endfor %}
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ {% else %}
|
|
|
+ <p class="mt-3 text-sm text-slate-500 dark:text-slate-400">No allowlist entries match this query.</p>
|
|
|
+ {% endif %}
|
|
|
+ </section>
|
|
|
+ {% endif %}
|
|
|
+</div>
|
|
|
+{% endblock %}
|