From 9cc2f8183c8fb6d92d496c806a1232f5864dd473 Mon Sep 17 00:00:00 2001 From: Phillip Tarrant Date: Fri, 22 Aug 2025 12:55:46 -0500 Subject: [PATCH] refactor(templates): extract sections to includes; feat(css): add reusable components; fix(tables): lock column widths & stop snippet reflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 + 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 --- app/templates/_macros_ssl_tls.html | 195 ------------ app/templates/partials/result_enrichment.html | 58 ++++ app/templates/partials/result_forms.html | 113 +++++++ app/templates/partials/result_scripts.html | 120 ++++++++ app/templates/partials/result_ssl_tls.html | 200 +++++++++++++ app/templates/result.html | 277 +----------------- assets/input.css | 26 ++ 7 files changed, 521 insertions(+), 468 deletions(-) delete mode 100644 app/templates/_macros_ssl_tls.html create mode 100644 app/templates/partials/result_enrichment.html create mode 100644 app/templates/partials/result_forms.html create mode 100644 app/templates/partials/result_scripts.html create mode 100644 app/templates/partials/result_ssl_tls.html diff --git a/app/templates/_macros_ssl_tls.html b/app/templates/_macros_ssl_tls.html deleted file mode 100644 index 12689ce..0000000 --- a/app/templates/_macros_ssl_tls.html +++ /dev/null @@ -1,195 +0,0 @@ -{# templates/_macros_ssl_tls.html #} -{% macro ssl_tls_card(ssl_tls) %} -
- - {# -------- 1) Error branch -------- #} - {% if ssl_tls is none or (ssl_tls.error is defined) or ('error' in ssl_tls) %} - Error -

SSL/TLS enrichment failed or is unavailable.

- {% if ssl_tls and ssl_tls.error %} -
{{ ssl_tls.error }}
- {% endif %} - - {# -------- 2) Skipped branch -------- #} - {% elif ssl_tls.skipped %} - Skipped - {% if ssl_tls.reason %}{{ ssl_tls.reason }}{% endif %} - -
- Raw TLS JSON -
{{ ssl_tls|tojson(indent=2) }}
-
- - {# -------- 3) Normal branch (render probe + crt.sh) -------- #} - {% else %} - - {# ===================== LIVE PROBE ===================== #} - {% set probe = ssl_tls.probe if ssl_tls else None %} -
-
-

Live TLS Probe

- {% if probe %} - Host: - {{ probe.hostname }}:{{ probe.port }} - {% endif %} -
- - {% if not probe %} -

No probe data.

- {% else %} -
- - - - - - - - - - - {% set versions = ['TLS1.0','TLS1.1','TLS1.2','TLS1.3'] %} - {% for v in versions %} - {% set r = probe.results_by_version.get(v) if probe.results_by_version else None %} - - - - - - - {% endfor %} - -
VersionStatusSelected CipherLatency
{{ v }} - {% if r and r.supported %} - Supported - {% else %} - Not Supported - {% endif %} - - {% if r and r.selected_cipher %} - {{ r.selected_cipher }} - {% elif r and r.error %} - ({{ r.error }}) - {% else %} - - {% endif %} - - {% if r and r.handshake_seconds is not none %} - {{ '%.0f' % (r.handshake_seconds*1000) }} ms - {% else %} - - {% endif %} -
-
- -
- {% if probe.weak_protocols and probe.weak_protocols|length > 0 %} - Weak Protocols - {% for wp in probe.weak_protocols %} - {{ wp }} - {% endfor %} - {% endif %} - {% if probe.weak_ciphers and probe.weak_ciphers|length > 0 %} - Weak Ciphers - {% for wc in probe.weak_ciphers %} - {{ wc }} - {% endfor %} - {% endif %} -
- - {% if probe.errors and probe.errors|length > 0 %} -
- Probe Notes -
    - {% for e in probe.errors %} -
  • {{ e }}
  • - {% endfor %} -
-
- {% endif %} - {% endif %} -
- -
- - {# ===================== CRT.SH ===================== #} - {% set crtsh = ssl_tls.crtsh if ssl_tls else None %} -
-
-

Certificate Transparency (crt.sh)

- {% if crtsh %} - Parsed: - {{ crtsh.hostname or 'n/a' }} - {% if crtsh.root_domain %} - • Root: - {{ crtsh.root_domain }} - {% if crtsh.is_root_domain %} - Root - {% else %} - Subdomain - {% endif %} - {% endif %} - {% endif %} -
- - {% if not crtsh %} -

No CT data.

- {% else %} -
-
-

Host Certificates

- {% set host_certs = crtsh.crtsh.host_certs if 'crtsh' in crtsh and crtsh.crtsh else None %} - {% if host_certs and host_certs|length > 0 %} -
    - {% for c in host_certs[:10] %} -
  • - {{ c.get('issuer_name','issuer n/a') }} - - {{ c.get('name_value','(name n/a)') }} - • not_before: {{ c.get('not_before','?') }} -
  • - {% endfor %} -
- {% if host_certs|length > 10 %} -
(+ {{ host_certs|length - 10 }} more)
- {% endif %} - {% else %} -

No active host certs found.

- {% endif %} -
- -
-

Wildcard on Root

- {% set wc = crtsh.crtsh.wildcard_root_certs if 'crtsh' in crtsh and crtsh.crtsh else None %} - {% if wc and wc|length > 0 %} -
    - {% for c in wc[:10] %} -
  • - {{ c.get('issuer_name','issuer n/a') }} - - {{ c.get('name_value','(name n/a)') }} - • not_before: {{ c.get('not_before','?') }} -
  • - {% endfor %} -
- {% if wc|length > 10 %} -
(+ {{ wc|length - 10 }} more)
- {% endif %} - {% else %} -

No wildcard/root certs found.

- {% endif %} -
-
- {% endif %} -
- - -
- Raw TLS JSON -
{{ ssl_tls|tojson(indent=2) }}
-
- {% endif %} - -

Back to top

-
-{% endmacro %} diff --git a/app/templates/partials/result_enrichment.html b/app/templates/partials/result_enrichment.html new file mode 100644 index 0000000..81be05e --- /dev/null +++ b/app/templates/partials/result_enrichment.html @@ -0,0 +1,58 @@ + +
+

Enrichment

+ + {% if enrichment.whois %} +

WHOIS

+
+ + + + + + + + + {% for k, v in enrichment.whois.items() %} + + + + + {% endfor %} + +
FieldValue
{{ k.replace('_', ' ').title() }}{{ v }}
+
+ {% endif %} + + {% if enrichment.raw_whois %} +

Raw WHOIS

+
{{ enrichment.raw_whois }}
+ {% endif %} + + {% if enrichment.geoip %} +

GeoIP

+ {% for ip, info in enrichment.geoip.items() %} +
+ {{ ip }} +
+ + + {% for key, val in info.items() %} + + + + + {% endfor %} + +
{{ key.replace('_', ' ').title() }}{{ val }}
+
+
+ {% endfor %} + {% endif %} + + {% if not enrichment.whois and not enrichment.raw_whois and not enrichment.geoip and not enrichment.bec_words %} +

No enrichment data available.

+ {% endif %} + +

Back to top

+
\ No newline at end of file diff --git a/app/templates/partials/result_forms.html b/app/templates/partials/result_forms.html new file mode 100644 index 0000000..ca53ad8 --- /dev/null +++ b/app/templates/partials/result_forms.html @@ -0,0 +1,113 @@ + +
+

Forms

+ + {% if forms and forms|length > 0 %} +
+ + + + + + + + + + + + + + + + + + + {% for f in forms %} + + + + + + + + + + + + + + + + + {% endfor %} + +
ActionMethodInputsMatches (Rules)Form Snippet
+ {% if f.action %} + {{ f.action[:80] }}{% if f.action|length > 80 %}…{% endif %} + {% else %} + (no action) + {% endif %} + {{ (f.method or 'get')|upper }} + {% if f.inputs and f.inputs|length > 0 %} +
+ {% for inp in f.inputs %} + + {{ inp.name or '(unnamed)' }} : {{ (inp.type or 'text') }} + + {% endfor %} +
+ {% else %} + None + {% endif %} +
+ {% if f.rules and f.rules|length > 0 %} +
    + {% for r in f.rules %} +
  • + {{ r.name }} + {% if r.severity %} + {% set sev = r.severity|lower %} + + {{ r.severity|title }} + + {% endif %} + {% if r.tags %} + {% for t in r.tags %} + {{ t }} + {% endfor %} + {% endif %} + {% if r.description %} + — {{ r.description }} + {% endif %} +
  • + {% endfor %} +
+ {% else %} + N/A + {% endif %} +
+ {% if f.content_snippet %} +
+ + View snippet ({{ f.content_snippet|length }} chars) + +
{{ f.content_snippet }}
+
+ {% else %} + N/A + {% endif %} +
+
+ + {% else %} +

No form issues detected.

+ {% endif %} + +

Back to top

+
\ No newline at end of file diff --git a/app/templates/partials/result_scripts.html b/app/templates/partials/result_scripts.html new file mode 100644 index 0000000..b7f8934 --- /dev/null +++ b/app/templates/partials/result_scripts.html @@ -0,0 +1,120 @@ + +
+

Suspicious Scripts

+ + {% if suspicious_scripts %} +
+ + + + + + + + + + + + + + + + + {% for s in suspicious_scripts %} + + + + + + + + + + + + {% endfor %} + +
TypeSource URLMatches (Rules & Heuristics)Content Snippet
{{ s.type or 'unknown' }} + {% if s.src %} + + {{ s.src[:100] }}{% if s.src|length > 100 %}…{% endif %} + + {% else %} + N/A + {% endif %} + + {% set has_rules = s.rules and s.rules|length > 0 %} + {% set has_heur = s.heuristics and s.heuristics|length > 0 %} + + {% if has_rules %} +
Rules
+
    + {% for r in s.rules %} +
  • + {{ r.name }} + {% if r.severity %} + {% set sev = r.severity|lower %} + + {{ r.severity|title }} + + {% endif %} + {% if r.tags %} + {% for t in r.tags %} + {{ t }} + {% endfor %} + {% endif %} + {% if r.description %} + — {{ r.description }} + {% endif %} +
  • + {% endfor %} +
+ {% endif %} + + {% if has_heur %} +
Heuristics
+
    + {% for h in s.heuristics %} +
  • {{ h }}
  • + {% endfor %} +
+ {% endif %} + + {% if not has_rules and not has_heur %} + N/A + {% endif %} +
+ {% if s.content_snippet %} +
+ + View snippet ({{ s.content_snippet|length }} chars) + +
{{ s.content_snippet }}
+
+ {% else %} + {% if s.type == 'external' and s.src %} + + {% else %} + N/A + {% endif %} + {% endif %} +
+
+ + {% else %} +

No suspicious scripts detected.

+ {% endif %} + +

Back to top

+
\ No newline at end of file diff --git a/app/templates/partials/result_ssl_tls.html b/app/templates/partials/result_ssl_tls.html new file mode 100644 index 0000000..f48c892 --- /dev/null +++ b/app/templates/partials/result_ssl_tls.html @@ -0,0 +1,200 @@ + +{# templates/result_ssl_tls.html #} +{% macro ssl_tls_card(ssl_tls) %} +
+

TLS / Certs

+
+ + {# -------- 1) Error branch -------- #} + {% if ssl_tls is none or (ssl_tls.error is defined) or ('error' in ssl_tls) %} + Error +

SSL/TLS enrichment failed or is unavailable.

+ {% if ssl_tls and ssl_tls.error %} +
{{ ssl_tls.error }}
+ {% endif %} + + {# -------- 2) Skipped branch -------- #} + {% elif ssl_tls.skipped %} + Skipped + {% if ssl_tls.reason %}{{ ssl_tls.reason }}{% endif %} + +
+ Raw TLS JSON +
{{ ssl_tls|tojson(indent=2) }}
+
+ + {# -------- 3) Normal branch (render probe + crt.sh) -------- #} + {% else %} + + {# ===================== LIVE PROBE ===================== #} + {% set probe = ssl_tls.probe if ssl_tls else None %} +
+
+

Live TLS Probe

+ {% if probe %} + Host: + {{ probe.hostname }}:{{ probe.port }} + {% endif %} +
+ + {% if not probe %} +

No probe data.

+ {% else %} +
+ + + + + + + + + + + {% set versions = ['TLS1.0','TLS1.1','TLS1.2','TLS1.3'] %} + {% for v in versions %} + {% set r = probe.results_by_version.get(v) if probe.results_by_version else None %} + + + + + + + {% endfor %} + +
VersionStatusSelected CipherLatency
{{ v }} + {% if r and r.supported %} + Supported + {% else %} + Not Supported + {% endif %} + + {% if r and r.selected_cipher %} + {{ r.selected_cipher }} + {% elif r and r.error %} + ({{ r.error }}) + {% else %} + + {% endif %} + + {% if r and r.handshake_seconds is not none %} + {{ '%.0f' % (r.handshake_seconds*1000) }} ms + {% else %} + + {% endif %} +
+
+ +
+ {% if probe.weak_protocols and probe.weak_protocols|length > 0 %} + Weak Protocols + {% for wp in probe.weak_protocols %} + {{ wp }} + {% endfor %} + {% endif %} + {% if probe.weak_ciphers and probe.weak_ciphers|length > 0 %} + Weak Ciphers + {% for wc in probe.weak_ciphers %} + {{ wc }} + {% endfor %} + {% endif %} +
+ + {% if probe.errors and probe.errors|length > 0 %} +
+ Probe Notes +
    + {% for e in probe.errors %} +
  • {{ e }}
  • + {% endfor %} +
+
+ {% endif %} + {% endif %} +
+ +
+ + {# ===================== CRT.SH ===================== #} + {% set crtsh = ssl_tls.crtsh if ssl_tls else None %} +
+
+

Certificate Transparency (crt.sh)

+ {% if crtsh %} + Parsed: + {{ crtsh.hostname or 'n/a' }} + {% if crtsh.root_domain %} + • Root: + {{ crtsh.root_domain }} + {% if crtsh.is_root_domain %} + Root + {% else %} + Subdomain + {% endif %} + {% endif %} + {% endif %} +
+ + {% if not crtsh %} +

No CT data.

+ {% else %} +
+
+

Host Certificates

+ {% set host_certs = crtsh.crtsh.host_certs if 'crtsh' in crtsh and crtsh.crtsh else None %} + {% if host_certs and host_certs|length > 0 %} +
    + {% for c in host_certs[:10] %} +
  • + {{ c.get('issuer_name','issuer n/a') }} + + {{ c.get('name_value','(name n/a)') }} + • not_before: {{ c.get('not_before','?') }} +
  • + {% endfor %} +
+ {% if host_certs|length > 10 %} +
(+ {{ host_certs|length - 10 }} more)
+ {% endif %} + {% else %} +

No active host certs found.

+ {% endif %} +
+ +
+

Wildcard on Root

+ {% set wc = crtsh.crtsh.wildcard_root_certs if 'crtsh' in crtsh and crtsh.crtsh else None %} + {% if wc and wc|length > 0 %} +
    + {% for c in wc[:10] %} +
  • + {{ c.get('issuer_name','issuer n/a') }} + + {{ c.get('name_value','(name n/a)') }} + • not_before: {{ c.get('not_before','?') }} +
  • + {% endfor %} +
+ {% if wc|length > 10 %} +
(+ {{ wc|length - 10 }} more)
+ {% endif %} + {% else %} +

No wildcard/root certs found.

+ {% endif %} +
+
+ {% endif %} +
+ + +
+ Raw TLS JSON +
{{ ssl_tls|tojson(indent=2) }}
+
+ {% endif %} + +

Back to top

+
+
+{% endmacro %} + diff --git a/app/templates/result.html b/app/templates/result.html index d063f08..6842fdb 100644 --- a/app/templates/result.html +++ b/app/templates/result.html @@ -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 @@ -
-

Enrichment

- - {% if enrichment.whois %} -

WHOIS

-
- - - - - - - - - {% for k, v in enrichment.whois.items() %} - - - - - {% endfor %} - -
FieldValue
{{ k.replace('_', ' ').title() }}{{ v }}
-
- {% endif %} - - {% if enrichment.raw_whois %} -

Raw WHOIS

-
{{ enrichment.raw_whois }}
- {% endif %} - - {% if enrichment.geoip %} -

GeoIP

- {% for ip, info in enrichment.geoip.items() %} -
- {{ ip }} -
- - - {% for key, val in info.items() %} - - - - - {% endfor %} - -
{{ key.replace('_', ' ').title() }}{{ val }}
-
-
- {% endfor %} - {% endif %} - - {% if not enrichment.whois and not enrichment.raw_whois and not enrichment.geoip and not enrichment.bec_words %} -

No enrichment data available.

- {% endif %} - -

Back to top

-
+ {% include "partials/result_enrichment.html" %} -
-

TLS / Certs

+ {% from "partials/result_ssl_tls.html" import ssl_tls_card %} {{ ssl_tls_card(enrichment.ssl_tls) }} -
@@ -136,221 +77,11 @@
-
-

Forms

+ {% include "partials/result_forms.html" %} - {% if forms and forms|length > 0 %} -
- - - - - - - - - - - - {% for f in forms %} - - - - - - - - - - - - - - - - - {% endfor %} - -
ActionMethodInputsMatches (Rules)Form Snippet
- {% if f.action %} - {{ f.action[:80] }}{% if f.action|length > 80 %}…{% endif %} - {% else %} - (no action) - {% endif %} - {{ (f.method or 'get')|upper }} - {% if f.inputs and f.inputs|length > 0 %} -
- {% for inp in f.inputs %} - - {{ inp.name or '(unnamed)' }} : {{ (inp.type or 'text') }} - - {% endfor %} -
- {% else %} - None - {% endif %} -
- {% if f.rules and f.rules|length > 0 %} -
    - {% for r in f.rules %} -
  • - {{ r.name }} - {% if r.severity %} - {% set sev = r.severity|lower %} - - {{ r.severity|title }} - - {% endif %} - {% if r.tags %} - {% for t in r.tags %} - {{ t }} - {% endfor %} - {% endif %} - {% if r.description %} - — {{ r.description }} - {% endif %} -
  • - {% endfor %} -
- {% else %} - N/A - {% endif %} -
- {% if f.content_snippet %} -
- - View snippet ({{ f.content_snippet|length }} chars) - -
{{ f.content_snippet }}
-
- {% else %} - N/A - {% endif %} -
-
- {% else %} -

No form issues detected.

- {% endif %} - -

Back to top

-
-
-

Suspicious Scripts

- - {% if suspicious_scripts %} -
- - - - - - - - - - - {% for s in suspicious_scripts %} - - - - - - - - - - - - {% endfor %} - -
TypeSource URLMatches (Rules & Heuristics)Content Snippet
{{ s.type or 'unknown' }} - {% if s.src %} - - {{ s.src[:100] }}{% if s.src|length > 100 %}…{% endif %} - - {% else %} - N/A - {% endif %} - - {% set has_rules = s.rules and s.rules|length > 0 %} - {% set has_heur = s.heuristics and s.heuristics|length > 0 %} - - {% if has_rules %} -
Rules
-
    - {% for r in s.rules %} -
  • - {{ r.name }} - {% if r.severity %} - {% set sev = r.severity|lower %} - - {{ r.severity|title }} - - {% endif %} - {% if r.tags %} - {% for t in r.tags %} - {{ t }} - {% endfor %} - {% endif %} - {% if r.description %} - — {{ r.description }} - {% endif %} -
  • - {% endfor %} -
- {% endif %} - - {% if has_heur %} -
Heuristics
-
    - {% for h in s.heuristics %} -
  • {{ h }}
  • - {% endfor %} -
- {% endif %} - - {% if not has_rules and not has_heur %} - N/A - {% endif %} -
- {% if s.content_snippet %} -
- - View snippet ({{ s.content_snippet|length }} chars) - -
{{ s.content_snippet }}
-
- {% else %} - {% if s.type == 'external' and s.src %} - - {% else %} - N/A - {% endif %} - {% endif %} -
-
- - {% else %} -

No suspicious scripts detected.

- {% endif %} - -

Back to top

-
+ {% include "partials/result_scripts.html" %}
diff --git a/assets/input.css b/assets/input.css index e84ecb0..e39657d 100644 --- a/assets/input.css +++ b/assets/input.css @@ -1,6 +1,32 @@ @tailwind base; @tailwind components; + +/* ---- Reusable components ---- */ +@layer components { + /* Base badge + variants (compose in markup as: class="badge badge-ok") */ + .badge { @apply inline-flex items-center rounded-full px-2 py-0.5 text-xs border; } + .badge-ok { @apply bg-green-600/20 text-green-300 border-green-700; } + .badge-warn { @apply bg-yellow-600/20 text-yellow-300 border-yellow-700; } + .badge-danger { @apply bg-red-600/20 text-red-300 border-red-700; } + .badge-muted { @apply bg-gray-700/30 text-gray-300 border-gray-700; } + .badge-info { @apply bg-blue-600/20 text-blue-300 border-blue-700; } + .badge-success { @apply bg-green-600/20 text-green-300 border-green-700; } + .badge-success-solid { @apply bg-green-600 text-white border-green-600; } + + + /* Chips (tags/pills) */ + .chip { @apply rounded-full bg-gray-800 border border-gray-700 text-gray-300 px-2 py-0.5 text-xs; } + + /* Card container */ + .card { @apply bg-card border border-gray-800 rounded-xl p-4; } +} + @tailwind utilities; +/* Your earlier override to remove opacity var from gray-800 borders */ +@layer utilities { + .border-gray-800 { border-color: #1f2937; } /* rgb(31,41,55) */ +} + /* Optional tiny custom classes */ .card-shadow { box-shadow: 0 1px 2px rgba(0,0,0,.2); }