Files
mass-scan2/app/templates/report_dark.html.j2
2025-10-21 21:00:48 -05:00

178 lines
7.5 KiB
Django/Jinja

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{ title }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
{# Dark mode variant of compliance report. Filename: report_dark.html.j2 #}
</head>
<body style="margin:0;padding:16px;background:#0b1020;color:#e5e7eb">
<div style="max-width:860px;margin:0 auto;font-family:Segoe UI,Arial,sans-serif">
{# ===== Title Card ===== #}
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 12px 0">
<tr>
<td style="padding:14px 16px;background:#1e293b;border:1px solid #334155;border-radius:10px;color:#f1f5f9">
<div style="font-size:20px;font-weight:700;margin-bottom:4px">{{ title }}</div>
<div style="font-size:12px;color:#94a3b8">Generated: {{ generated }}</div>
</td>
</tr>
</table>
{# ===== Summary Bar ===== #}
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 16px 0">
<tr>
<td style="
padding:12px 16px;
border:1px solid #334155;
border-radius:8px;
background:#1e293b;
color:#f1f5f9;
font-size:14px;
">
Total hosts:
<strong style="color:#f8fafc">{{ total_hosts }}</strong>&nbsp;&nbsp;
Matching expected:
<strong style="color:#4ade80">{{ ok_hosts }}</strong>&nbsp;&nbsp;
With issues:
<strong style="color:#f87171">{{ hosts_with_issues|length }}</strong>
</td>
</tr>
</table>
{% if not only_issues and ok_hosts == total_hosts and total_hosts > 0 %}
<div style="margin:6px 0 12px 0;font-size:12px;color:#9ca3af">
All hosts matched expected ports.
</div>
{% endif %}
{% if only_issues and hosts_with_issues|length == 0 %}
<div style="margin:8px 0;color:#9ca3af">
No hosts with issues found. ✅
</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 '') %}
<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 %}
</td>
</tr>
{% if has_issues %}
<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) }}
<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 hr and hr.ports %}
{# Track visible rows after filtering (e.g., skip closed UDP) #}
{% set ns = namespace(visible_rows=0) %}
{% for p in hr.ports %}
{# --- Normalize helpers --- #}
{% set proto = (p.protocol|string|lower) %}
{% set state = (p.state|string|lower) %}
{# --- 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 %}
<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>
</tr>
{% endif %}
{% else %}
<tr>
<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 %}
{% else %}
{# Host has no issues #}
<tr>
<td colspan="5" style="padding:10px 10px 8px 10px;font-size:13px">
{{ badge('OK', '#16a34a') }} &nbsp; <span style="color:#cbd5e1">Matches expected ports.</span>
</td>
</tr>
{% endif %}
</table>
{% endfor %}
<div style="margin-top:18px;font-size:11px;color:#9ca3af">
Report generated by mass-scan-v2 • {{ generated }}
</div>
</div>
</body>
</html>