templates and data classes are done
This commit is contained in:
@@ -54,121 +54,132 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# ===== Host Sections ===== #}
|
||||
{% for ip_key, r in reports|dictsort %}
|
||||
{% set has_issues = r.unexpected_tcp or r.missing_tcp or r.unexpected_udp or r.missing_udp %}
|
||||
{% set hr = host_results_by_ip.get(ip_key) %}
|
||||
{% set header_title = ip_key ~ ((' (' ~ hr.host ~ ')') if hr and hr.host else '') %}
|
||||
{# ===== Host Sections (Issues first, then Expected) ===== #}
|
||||
|
||||
{# A small macro reused for delta lines #}
|
||||
{% macro delta_row(label, ports) -%}
|
||||
<tr>
|
||||
<td colspan="5" style="padding:4px 10px 6px 10px;font-size:13px">
|
||||
<strong style="color:#e5e7eb">{{ label }}:</strong>
|
||||
<span style="color:#cbd5e1">{{ fmt_ports(ports) }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
{%- endmacro %}
|
||||
|
||||
{# ---------- 1) Issues (already sorted by IP in Python) ---------- #}
|
||||
{% for hr in issues %}
|
||||
{% set ip_key = hr.ip %}
|
||||
{% set r = hr %} {# keep "r" alias so the rest of the block looks familiar #}
|
||||
{% set has_issues = true %}
|
||||
{% set host_row = host_results_by_ip.get(ip_key) %}
|
||||
{% set header_title = ip_key ~ ((' (' ~ host_row.host ~ ')') if host_row and host_row.host else '') %}
|
||||
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0;border-collapse:separate;border-spacing:0">
|
||||
<tr>
|
||||
<td colspan="5" style="padding:12px 10px;background:#1e293b;color:#f1f5f9;font-weight:600;font-size:14px;border-radius:8px;border:1px solid #334155">
|
||||
|
||||
{{ header_title }}
|
||||
{% if has_issues %}
|
||||
{{ badge('ISSUES', '#ef4444') }}
|
||||
{% else %}
|
||||
{{ badge('OK', '#16a34a') }}
|
||||
{% endif %}
|
||||
{{ header_title }} {{ badge('ISSUES', '#ef4444') }}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% if has_issues %}
|
||||
<tr>
|
||||
<td colspan="5" style="padding:10px 10px 6px 10px;font-size:13px">
|
||||
{{ badge('ISSUES', '#ef4444') }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="5" style="padding:10px 10px 6px 10px;font-size:13px">
|
||||
{{ badge('ISSUES', '#ef4444') }}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% macro delta_row(label, ports) -%}
|
||||
<tr>
|
||||
<td colspan="5" style="padding:4px 10px 6px 10px;font-size:13px">
|
||||
<strong style="color:#e5e7eb">{{ label }}:</strong>
|
||||
<span style="color:#cbd5e1">{{ fmt_ports(ports) }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
{%- endmacro %}
|
||||
{{ delta_row('Unexpected TCP open ports', r.unexpected_tcp) }}
|
||||
{{ delta_row('Expected TCP ports not seen', r.missing_tcp) }}
|
||||
{{ delta_row('Unexpected UDP open ports', r.unexpected_udp) }}
|
||||
{{ delta_row('Expected UDP ports not seen', r.missing_udp) }}
|
||||
|
||||
{{ delta_row('Unexpected TCP open ports', r.unexpected_tcp) }}
|
||||
{{ delta_row('Expected TCP ports not seen', r.missing_tcp) }}
|
||||
{{ delta_row('Unexpected UDP open ports', r.unexpected_udp) }}
|
||||
{{ delta_row('Expected UDP ports not seen', r.missing_udp) }}
|
||||
<tr>
|
||||
<td colspan="5" style="padding:8px 10px 6px 10px;font-size:13px">
|
||||
<div style="font-weight:600;margin:8px 0;color:#e5e7eb">Discovered Ports</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Protocol</td>
|
||||
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Port</td>
|
||||
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">State</td>
|
||||
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Service</td>
|
||||
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Expectation</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="5" style="padding:8px 10px 6px 10px;font-size:13px">
|
||||
<div style="font-weight:600;margin:8px 0;color:#e5e7eb">Discovered Ports</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Protocol</td>
|
||||
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Port</td>
|
||||
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">State</td>
|
||||
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Service</td>
|
||||
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Expectation</td>
|
||||
</tr>
|
||||
{% if host_row and host_row.ports %}
|
||||
{% set ns = namespace(visible_rows=0) %}
|
||||
|
||||
{% if hr and hr.ports %}
|
||||
{# Track visible rows after filtering (e.g., skip closed UDP) #}
|
||||
{% set ns = namespace(visible_rows=0) %}
|
||||
{% for p in host_row.ports %}
|
||||
{% set proto = (p.protocol|string|lower) %}
|
||||
{% set state = (p.state|string|lower) %}
|
||||
{% set skip_row = (proto == 'udp' and ('closed' in state)) %}
|
||||
|
||||
{% for p in hr.ports %}
|
||||
{# --- Normalize helpers --- #}
|
||||
{% set proto = (p.protocol|string|lower) %}
|
||||
{% set state = (p.state|string|lower) %}
|
||||
{% set is_tcp = (proto == 'tcp') %}
|
||||
{% set is_udp = (proto == 'udp') %}
|
||||
{% set is_open = ('open' in state) %}
|
||||
{% set is_issue = (is_open and (
|
||||
(is_tcp and (p.port in (r.unexpected_tcp or []))) or
|
||||
(is_udp and (p.port in (r.unexpected_udp or [])))
|
||||
)) %}
|
||||
{% set expectation_badge = (
|
||||
badge('Issue', '#ef4444') if is_issue
|
||||
else (badge('Expected', '#16a34a') if is_open else '<span style="color:#9ca3af">—</span>')
|
||||
) %}
|
||||
|
||||
{# --- Skip rule: hide UDP rows that are "closed" (substring match) --- #}
|
||||
{% set skip_row = (proto == 'udp' and ('closed' in state)) %}
|
||||
|
||||
{# --- Expectation labeling --- #}
|
||||
{% set is_tcp = (proto == 'tcp') %}
|
||||
{% set is_udp = (proto == 'udp') %}
|
||||
{% set is_open = ('open' in state) %}
|
||||
{% set is_issue = (is_open and (
|
||||
(is_tcp and (p.port in (r.unexpected_tcp or []))) or
|
||||
(is_udp and (p.port in (r.unexpected_udp or [])))
|
||||
)) %}
|
||||
{% set expectation_badge = (
|
||||
badge('Issue', '#ef4444') if is_issue
|
||||
else (badge('Expected', '#16a34a') if is_open else '<span style="color:#9ca3af">—</span>')
|
||||
) %}
|
||||
|
||||
{% if not skip_row %}
|
||||
{% set ns.visible_rows = ns.visible_rows + 1 %}
|
||||
<tr>
|
||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020">{{ pill_proto(p.protocol) }}</td>
|
||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#e5e7eb">{{ p.port }}</td>
|
||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020">{{ pill_state(p.state) }}</td>
|
||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#cbd5e1">{{ p.service or '-' }}</td>
|
||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020">{{ expectation_badge | safe }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if ns.visible_rows == 0 %}
|
||||
{% if not skip_row %}
|
||||
{% set ns.visible_rows = ns.visible_rows + 1 %}
|
||||
<tr>
|
||||
<td colspan="5" style="padding:8px 10px;border:1px solid #1f2937;background:#0b1020;color:#9ca3af">
|
||||
No per-port details to display after filtering closed UDP results.
|
||||
</td>
|
||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020">{{ pill_proto(p.protocol) }}</td>
|
||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#e5e7eb">{{ p.port }}</td>
|
||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020">{{ pill_state(p.state) }}</td>
|
||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#cbd5e1">{{ p.service or '-' }}</td>
|
||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020">{{ expectation_badge | safe }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% endfor %}
|
||||
|
||||
{% if ns.visible_rows == 0 %}
|
||||
<tr>
|
||||
<td colspan="5" style="padding:8px 10px;border:1px solid #1f2937;background:#0b1020;color:#9ca3af">
|
||||
No per-port details available for this host.
|
||||
No per-port details to display after filtering closed UDP results.
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{# Host has no issues #}
|
||||
<tr>
|
||||
<td colspan="5" style="padding:10px 10px 8px 10px;font-size:13px">
|
||||
{{ badge('OK', '#16a34a') }} <span style="color:#cbd5e1">Matches expected ports.</span>
|
||||
<td colspan="5" style="padding:8px 10px;border:1px solid #1f2937;background:#0b1020;color:#9ca3af">
|
||||
No per-port details available for this host.
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
{% endfor %}
|
||||
|
||||
{# ---------- 2) Expected / OK hosts (only if not only_issues) ---------- #}
|
||||
{% if not only_issues %}
|
||||
{% for hr in expected %}
|
||||
{% set ip_key = hr.ip %}
|
||||
{% set r = hr %}
|
||||
{% set has_issues = false %}
|
||||
{% set host_row = host_results_by_ip.get(ip_key) %}
|
||||
{% set header_title = ip_key ~ ((' (' ~ host_row.host ~ ')') if host_row and host_row.host else '') %}
|
||||
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0;border-collapse:separate;border-spacing:0">
|
||||
<tr>
|
||||
<td colspan="5" style="padding:12px 10px;background:#1e293b;color:#f1f5f9;font-weight:600;font-size:14px;border-radius:8px;border:1px solid #334155">
|
||||
{{ header_title }} {{ badge('OK', '#16a34a') }}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="5" style="padding:10px 10px 8px 10px;font-size:13px">
|
||||
{{ badge('OK', '#16a34a') }} <span style="color:#cbd5e1">Matches expected ports.</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div style="margin-top:18px;font-size:11px;color:#9ca3af">
|
||||
Report generated by mass-scan-v2 • {{ generated }}
|
||||
</div>
|
||||
|
||||
@@ -38,65 +38,63 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Host sections #}
|
||||
{% for ip_key, r in reports|dictsort %}
|
||||
{% set has_issues = r.unexpected_tcp or r.missing_tcp or r.unexpected_udp or r.missing_udp %}
|
||||
{% set hr = host_results_by_ip.get(ip_key) %}
|
||||
{% set header_title = ip_key ~ ((' (' ~ hr.host ~ ')') if hr and hr.host else '') %}
|
||||
{# ===== Host Sections (LIGHT theme) ===== #}
|
||||
|
||||
{# Reusable delta row #}
|
||||
{% macro delta_row(label, ports) -%}
|
||||
<tr>
|
||||
<td colspan="5" style="padding:4px 10px 6px 10px;font-size:13px">
|
||||
<strong>{{ label }}:</strong> {{ fmt_ports(ports) }}
|
||||
</td>
|
||||
</tr>
|
||||
{%- endmacro %}
|
||||
|
||||
{# ---------- 1) Issues first (already IP-sorted in Python) ---------- #}
|
||||
{% for hr in issues %}
|
||||
{% set ip_key = hr.ip %}
|
||||
{% set r = hr %}
|
||||
{% set host_row = host_results_by_ip.get(ip_key) %}
|
||||
{% set header_title = ip_key ~ ((' (' ~ host_row.host ~ ')') if host_row and host_row.host else '') %}
|
||||
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0">
|
||||
<tr>
|
||||
<td colspan="4" style="padding:12px 10px;background:#0f172a;color:#e2e8f0;font-weight:600;font-size:14px;border-radius:8px">
|
||||
{{ header_title }} {% if has_issues %} {{ badge('ISSUES', '#ef4444') }} {% else %} {{ badge('OK', '#16a34a') }} {% endif %}
|
||||
<td colspan="5" style="padding:12px 10px;background:#0f172a;color:#e2e8f0;font-weight:600;font-size:14px;border-radius:8px">
|
||||
{{ header_title }} {{ badge('ISSUES', '#ef4444') }}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% if has_issues %}
|
||||
<tr>
|
||||
<td colspan="4" style="padding:10px 10px 6px 10px;font-size:13px">
|
||||
{{ badge('ISSUES', '#ef4444') }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="5" style="padding:10px 10px 6px 10px;font-size:13px">
|
||||
{{ badge('ISSUES', '#ef4444') }}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% macro delta_row(label, ports) -%}
|
||||
<tr>
|
||||
<td colspan="4" style="padding:4px 10px 6px 10px;font-size:13px">
|
||||
<strong>{{ label }}:</strong> {{ fmt_ports(ports) }}
|
||||
</td>
|
||||
</tr>
|
||||
{%- endmacro %}
|
||||
{{ delta_row('Unexpected TCP open ports', r.unexpected_tcp) }}
|
||||
{{ delta_row('Expected TCP ports not seen', r.missing_tcp) }}
|
||||
{{ delta_row('Unexpected UDP open ports', r.unexpected_udp) }}
|
||||
{{ delta_row('Expected UDP ports not seen', r.missing_udp) }}
|
||||
|
||||
{{ delta_row('Unexpected TCP open ports', r.unexpected_tcp) }}
|
||||
{{ delta_row('Expected TCP ports not seen', r.missing_tcp) }}
|
||||
{{ delta_row('Unexpected UDP open ports', r.unexpected_udp) }}
|
||||
{{ delta_row('Expected UDP ports not seen', r.missing_udp) }}
|
||||
<tr>
|
||||
<td colspan="5" style="padding:8px 10px 6px 10px;font-size:13px">
|
||||
<div style="font-weight:600;margin:8px 0">Discovered Ports</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">Protocol</td>
|
||||
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">Port</td>
|
||||
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">State</td>
|
||||
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">Service</td>
|
||||
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">Expectation</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="4" style="padding:8px 10px 6px 10px;font-size:13px">
|
||||
<div style="font-weight:600;margin:8px 0">Discovered Ports</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">Protocol</td>
|
||||
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">Port</td>
|
||||
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">State</td>
|
||||
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">Service</td>
|
||||
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">Expectation</td>
|
||||
</tr>
|
||||
|
||||
{% if hr and hr.ports %}
|
||||
{# Track whether we rendered any visible rows after filtering #}
|
||||
{% if host_row and host_row.ports %}
|
||||
{% set ns = namespace(visible_rows=0) %}
|
||||
|
||||
{% for p in hr.ports %}
|
||||
{# Normalize helpers #}
|
||||
{% for p in host_row.ports %}
|
||||
{% set proto = (p.protocol|string|lower) %}
|
||||
{% set state = (p.state|string|lower) %}
|
||||
|
||||
{# Skip rule: hide UDP rows that are 'closed' #}
|
||||
{% set skip_row = (proto == 'udp' and ('closed' in state)) %}
|
||||
|
||||
{# Compute expectation labeling (only matters for displayed rows) #}
|
||||
{% set is_tcp = (proto == 'tcp') %}
|
||||
{% set is_udp = (proto == 'udp') %}
|
||||
{% set is_open = ('open' in state) %}
|
||||
@@ -121,7 +119,6 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{# If everything was filtered out, show a friendly note #}
|
||||
{% if ns.visible_rows == 0 %}
|
||||
<tr>
|
||||
<td colspan="5" style="padding:8px 10px;border:1px solid #e5e7eb;color:#64748b">
|
||||
@@ -136,18 +133,31 @@
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{# Host has no issues #}
|
||||
<tr>
|
||||
<td colspan="4" style="padding:10px 10px 8px 10px;font-size:13px">
|
||||
{{ badge('OK', '#16a34a') }} Matches expected ports.
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
{% endfor %}
|
||||
|
||||
{# ---------- 2) Expected / OK (only if not only_issues) ---------- #}
|
||||
{% if not only_issues %}
|
||||
{% for hr in expected %}
|
||||
{% set ip_key = hr.ip %}
|
||||
{% set host_row = host_results_by_ip.get(ip_key) %}
|
||||
{% set header_title = ip_key ~ ((' (' ~ host_row.host ~ ')') if host_row and host_row.host else '') %}
|
||||
|
||||
<div style="margin-top:18px;font-size:11px;color:#94a3b8">
|
||||
Report generated by mass-scan-v2 • {{ generated }}
|
||||
</div>
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0">
|
||||
<tr>
|
||||
<td colspan="5" style="padding:12px 10px;background:#0f172a;color:#e2e8f0;font-weight:600;font-size:14px;border-radius:8px">
|
||||
{{ header_title }} {{ badge('OK', '#16a34a') }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="5" style="padding:10px 10px 8px 10px;font-size:13px">
|
||||
{{ badge('OK', '#16a34a') }} Matches expected ports.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<div style="margin-top:18px;font-size:11px;color:#94a3b8">
|
||||
Report generated by mass-scan-v2 • {{ generated }}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user