| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- {% extends 'layout.twig' %}
- {% block title %}Search — IRDB{% endblock %}
- {% block content %}
- {% import 'partials/sort.twig' as sort %}
- <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" data-sortable-table="search-ips">
- <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>
- {{ sort.th('IP', 'ip') }}
- {{ sort.th('Top category', 'top_category') }}
- {{ sort.th('Max score', 'max_score', 'number', 'right') }}
- {{ sort.th('Last report', 'last_report', 'date') }}
- {{ sort.th('Status', 'status') }}
- </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" data-sort-value="{{ item.ip }}"><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" data-sort-value="{{ item.topCategory|default('') }}">{{ item.topCategory|default('—') }}</td>
- <td class="px-4 py-2 text-right font-mono" data-sort-value="{{ item.maxScore }}">{{ item.maxScore|number_format(2) }}</td>
- <td class="px-4 py-2 text-slate-500 dark:text-slate-400" data-sort-value="{{ item.lastReportAt|default('') }}">{% if item.lastReportAt %}<time class="irdb-dt" datetime="{{ item.lastReportAt }}">{{ item.lastReportAt }}</time>{% else %}—{% endif %}</td>
- <td class="px-4 py-2 font-mono text-xs uppercase" data-sort-value="{{ item.status }}">{{ 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" data-sortable-table="search-manual-blocks">
- <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>
- {{ sort.th('Kind', 'kind') }}
- {{ sort.th('Target', 'target') }}
- {{ sort.th('Reason', 'reason') }}
- {{ sort.th('Expires', 'expires', 'date') }}
- {{ sort.th('Created', 'created', 'date') }}
- </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" data-sort-value="{{ item.kind }}">{{ item.kind }}</td>
- <td class="px-4 py-2 font-mono" data-sort-value="{{ item.kind == 'ip' ? item.ip : item.cidr }}">{{ item.kind == 'ip' ? item.ip : item.cidr }}</td>
- <td class="px-4 py-2 text-slate-600 dark:text-slate-300" data-sort-value="{{ item.reason|default('') }}">{{ item.reason|default('—') }}</td>
- <td class="px-4 py-2 text-slate-500 dark:text-slate-400" data-sort-value="{{ item.expires_at|default('') }}">{% if item.expires_at %}<time class="irdb-dt" datetime="{{ item.expires_at }}">{{ item.expires_at }}</time>{% else %}—{% endif %}</td>
- <td class="px-4 py-2 text-slate-500 dark:text-slate-400" data-sort-value="{{ item.created_at|default('') }}">{% if item.created_at %}<time class="irdb-dt" datetime="{{ item.created_at }}">{{ item.created_at }}</time>{% endif %}</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" data-sortable-table="search-allowlist">
- <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>
- {{ sort.th('Kind', 'kind') }}
- {{ sort.th('Target', 'target') }}
- {{ sort.th('Reason', 'reason') }}
- {{ sort.th('Created', 'created', 'date') }}
- </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" data-sort-value="{{ item.kind }}">{{ item.kind }}</td>
- <td class="px-4 py-2 font-mono" data-sort-value="{{ item.kind == 'ip' ? item.ip : item.cidr }}">{{ item.kind == 'ip' ? item.ip : item.cidr }}</td>
- <td class="px-4 py-2 text-slate-600 dark:text-slate-300" data-sort-value="{{ item.reason|default('') }}">{{ item.reason|default('—') }}</td>
- <td class="px-4 py-2 text-slate-500 dark:text-slate-400" data-sort-value="{{ item.created_at|default('') }}">{% if item.created_at %}<time class="irdb-dt" datetime="{{ item.created_at }}">{{ item.created_at }}</time>{% endif %}</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 %}
|