refactor(templates): extract sections to includes; feat(css): add reusable components; fix(tables): lock column widths & stop snippet reflow

- Move large sections into partials:
  - forms → templates/_include_forms.html
  - scripts → templates/_include_scripts.html (if applicable)
- Add Tailwind component classes in assets/input.css:
  - .badge + variants (.badge-ok, .badge-warn, .badge-danger, .badge-muted, .badge-info, .badge-success, .badge-success-solid)
  - .chip
  - .card
- Override .border-gray-800 to fixed color (no opacity var)
- Stabilize table layouts:
  - Use table-fixed + <colgroup> with percentage widths
  - Forms cols: 10% / 10% / 15% / 45% / 25%
  - Scripts cols: 10% / 20% / 45% / 25%
  - Remove inner fixed-width wrapper from snippet cells; use w-full + wrapping to prevent column jitter
- Update templates to use new badge/chip classes
This commit is contained in:
2025-08-22 12:55:46 -05:00
parent dbd7cb31c7
commit 9cc2f8183c
7 changed files with 521 additions and 468 deletions

View File

@@ -1,5 +1,4 @@
{% extends "base.html" %}
{% from "_macros_ssl_tls.html" import ssl_tls_card %}
{% block title %}Scan Results{% endblock %}
{% block content %}
@@ -41,69 +40,11 @@
</section>
<!-- Enrichment -->
<section id="enrichment" class="bg-card border border-gray-800 rounded-xl p-4">
<h2 class="text-lg font-semibold mb-3">Enrichment</h2>
{% if enrichment.whois %}
<h3 class="text-base font-semibold mt-2 mb-2">WHOIS</h3>
<div class="overflow-x-auto">
<table class="min-w-full text-sm">
<thead class="text-gray-400 border-b border-gray-800">
<tr>
<th class="text-left py-2 pr-4">Field</th>
<th class="text-left py-2 pr-4">Value</th>
</tr>
</thead>
<tbody>
{% for k, v in enrichment.whois.items() %}
<tr class="border-b border-gray-900">
<td class="py-2 pr-4 whitespace-nowrap">{{ k.replace('_', ' ').title() }}</td>
<td class="py-2 pr-4 break-all">{{ v }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% if enrichment.raw_whois %}
<h3 class="text-base font-semibold mt-4 mb-2">Raw WHOIS</h3>
<pre class="bg-[#0b0f14] border border-gray-800 rounded-lg p-3 overflow-x-auto text-sm">{{ enrichment.raw_whois }}</pre>
{% endif %}
{% if enrichment.geoip %}
<h3 class="text-base font-semibold mt-4 mb-2">GeoIP</h3>
{% for ip, info in enrichment.geoip.items() %}
<details class="border border-gray-800 rounded-lg mb-2">
<summary class="px-3 py-2 cursor-pointer hover:bg-gray-900/50">{{ ip }}</summary>
<div class="px-3 pb-3 overflow-x-auto">
<table class="min-w-full text-sm">
<tbody>
{% for key, val in info.items() %}
<tr class="border-b border-gray-900">
<td class="py-2 pr-4 whitespace-nowrap text-gray-400">{{ key.replace('_', ' ').title() }}</td>
<td class="py-2 pr-4 break-all">{{ val }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</details>
{% endfor %}
{% endif %}
{% if not enrichment.whois and not enrichment.raw_whois and not enrichment.geoip and not enrichment.bec_words %}
<p class="text-sm text-gray-500">No enrichment data available.</p>
{% endif %}
<p class="mt-2"><a href="#top-jump-list" class="text-sm text-gray-400 hover:text-blue-400">Back to top</a></p>
</section>
{% include "partials/result_enrichment.html" %}
<!-- TLS / SSL / CERTS -->
<section id="ssl" class="bg-card border border-gray-800 rounded-xl p-4">
<h2 class="text-lg font-semibold mb-3">TLS / Certs</h2>
{% from "partials/result_ssl_tls.html" import ssl_tls_card %}
{{ ssl_tls_card(enrichment.ssl_tls) }}
</section>
<!-- Redirects -->
<section id="redirects" class="bg-card border border-gray-800 rounded-xl p-4">
@@ -136,221 +77,11 @@
</section>
<!-- Forms -->
<section id="forms" class="bg-card border border-gray-800 rounded-xl p-4">
<h2 class="text-lg font-semibold mb-3">Forms</h2>
{% include "partials/result_forms.html" %}
{% if forms and forms|length > 0 %}
<div class="overflow-x-auto">
<table class="min-w-full text-sm">
<thead class="text-gray-400 border-b border-gray-800">
<tr>
<th class="text-left py-2 pr-4">Action</th>
<th class="text-left py-2 pr-4">Method</th>
<th class="text-left py-2 pr-4">Inputs</th>
<th class="text-left py-2 pr-4">Matches (Rules)</th>
<th class="text-left py-2 pr-4">Form Snippet</th>
</tr>
</thead>
<tbody>
{% for f in forms %}
<tr class="border-b border-gray-900 align-top">
<!-- Action -->
<td class="py-2 pr-4 break-all">
{% if f.action %}
{{ f.action[:80] }}{% if f.action|length > 80 %}…{% endif %}
{% else %}
<span class="text-gray-500">(no action)</span>
{% endif %}
</td>
<!-- Method -->
<td class="py-2 pr-4 whitespace-nowrap">{{ (f.method or 'get')|upper }}</td>
<!-- Inputs -->
<td class="py-2 pr-4">
{% if f.inputs and f.inputs|length > 0 %}
<div class="flex flex-wrap gap-1">
{% for inp in f.inputs %}
<span class="rounded-full bg-gray-800 border border-gray-700 text-gray-300 px-2 py-0.5 text-xs"
title="{{ (inp.name or '') ~ ' : ' ~ (inp.type or 'text') }}">
{{ inp.name or '(unnamed)' }}<small class="text-gray-400"> : {{ (inp.type or 'text') }}</small>
</span>
{% endfor %}
</div>
{% else %}
<span class="text-gray-500">None</span>
{% endif %}
</td>
<!-- Matches (Rules) -->
<td class="py-2 pr-4">
{% if f.rules and f.rules|length > 0 %}
<ul class="space-y-1">
{% for r in f.rules %}
<li title="{{ r.description or '' }}">
{{ r.name }}
{% if r.severity %}
{% set sev = r.severity|lower %}
<span class="ml-2 rounded-full px-2 py-0.5 text-xs border
{% if sev == 'high' %} bg-red-600/20 text-red-300 border-red-700
{% elif sev == 'medium' %} bg-yellow-600/20 text-yellow-300 border-yellow-700
{% else %} bg-blue-600/20 text-blue-300 border-blue-700 {% endif %}">
{{ r.severity|title }}
</span>
{% endif %}
{% if r.tags %}
{% for t in r.tags %}
<span class="ml-1 rounded-full bg-gray-800 border border-gray-700 text-gray-300 px-2 py-0.5 text-xs" title="Tag: {{ t }}">{{ t }}</span>
{% endfor %}
{% endif %}
{% if r.description %}
<small class="text-gray-400"> — {{ r.description }}</small>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<span class="text-gray-500">N/A</span>
{% endif %}
</td>
<!-- Form Snippet -->
<td class="py-2 pr-4">
{% if f.content_snippet %}
<details>
<summary class="cursor-pointer text-blue-300 hover:underline">
View snippet ({{ f.content_snippet|length }} chars)
</summary>
<pre class="bg-[#0b0f14] border border-gray-800 rounded-lg p-3 overflow-x-auto text-xs mt-1">{{ f.content_snippet }}</pre>
</details>
{% else %}
<span class="text-gray-500">N/A</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-sm text-gray-500">No form issues detected.</p>
{% endif %}
<p class="mt-2"><a href="#top-jump-list" class="text-sm text-gray-400 hover:text-blue-400">Back to top</a></p>
</section>
<!-- Suspicious Scripts -->
<section id="scripts" class="bg-card border border-gray-800 rounded-xl p-4">
<h2 class="text-lg font-semibold mb-3">Suspicious Scripts</h2>
{% if suspicious_scripts %}
<div class="overflow-x-auto">
<table class="min-w-full text-sm">
<thead class="text-gray-400 border-b border-gray-800">
<tr>
<th class="text-left py-2 pr-4">Type</th>
<th class="text-left py-2 pr-4">Source URL</th>
<th class="text-left py-2 pr-4">Matches (Rules &amp; Heuristics)</th>
<th class="text-left py-2 pr-4">Content Snippet</th>
</tr>
</thead>
<tbody>
{% for s in suspicious_scripts %}
<tr class="border-b border-gray-900 align-top">
<td class="py-2 pr-4 whitespace-nowrap">{{ s.type or 'unknown' }}</td>
<td class="py-2 pr-4 break-all">
{% if s.src %}
<a href="{{ s.src }}" target="_blank" rel="noopener" class="hover:text-blue-400">
{{ s.src[:100] }}{% if s.src|length > 100 %}…{% endif %}
</a>
{% else %}
<span class="text-gray-500">N/A</span>
{% endif %}
</td>
<!-- Matches (Rules & Heuristics) -->
<td class="py-2 pr-4" data-role="matches-cell">
{% set has_rules = s.rules and s.rules|length > 0 %}
{% set has_heur = s.heuristics and s.heuristics|length > 0 %}
{% if has_rules %}
<div class="mb-1"><strong>Rules</strong></div>
<ul class="space-y-1">
{% for r in s.rules %}
<li title="{{ r.description or '' }}">
{{ r.name }}
{% if r.severity %}
{% set sev = r.severity|lower %}
<span class="ml-2 rounded-full px-2 py-0.5 text-xs border
{% if sev == 'high' %} bg-red-600/20 text-red-300 border-red-700
{% elif sev == 'medium' %} bg-yellow-600/20 text-yellow-300 border-yellow-700
{% else %} bg-blue-600/20 text-blue-300 border-blue-700 {% endif %}">
{{ r.severity|title }}
</span>
{% endif %}
{% if r.tags %}
{% for t in r.tags %}
<span class="ml-1 rounded-full bg-gray-800 border border-gray-700 text-gray-300 px-2 py-0.5 text-xs" title="Tag: {{ t }}">{{ t }}</span>
{% endfor %}
{% endif %}
{% if r.description %}
<small class="text-gray-400"> — {{ r.description }}</small>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
{% if has_heur %}
<div class="mt-2 mb-1"><strong>Heuristics</strong></div>
<ul class="list-disc list-inside space-y-1">
{% for h in s.heuristics %}
<li>{{ h }}</li>
{% endfor %}
</ul>
{% endif %}
{% if not has_rules and not has_heur %}
<span class="text-gray-500">N/A</span>
{% endif %}
</td>
<!-- Content Snippet / Analyze external script -->
<td class="py-2 pr-4" data-role="snippet-cell">
{% if s.content_snippet %}
<details>
<summary class="cursor-pointer text-blue-300 hover:underline">
View snippet ({{ s.content_snippet|length }} chars)
</summary>
<pre class="bg-[#0b0f14] border border-gray-800 rounded-lg p-3 overflow-x-auto text-xs mt-1">{{ s.content_snippet }}</pre>
</details>
{% else %}
{% if s.type == 'external' and s.src %}
<button
type="button"
class="btn-analyze-snippet inline-flex items-center gap-2 rounded-lg px-3 py-1.5 bg-blue-600 hover:bg-blue-500 text-white text-xs"
data-url="{{ s.src }}"
data-job="{{ uuid }}">
Analyze external script
</button>
{% else %}
<span class="text-gray-500">N/A</span>
{% endif %}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-sm text-gray-500">No suspicious scripts detected.</p>
{% endif %}
<p class="mt-2"><a href="#top-jump-list" class="text-sm text-gray-400 hover:text-blue-400">Back to top</a></p>
</section>
{% include "partials/result_scripts.html" %}
<!-- Screenshot -->
<section id="screenshot" class="bg-card border border-gray-800 rounded-xl p-4">