templates and data classes are done
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
# utils/reporting_jinja.py
|
||||
from __future__ import annotations
|
||||
from datetime import datetime
|
||||
from html import escape
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
from typing import Any, Dict, List, Set, Union
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
|
||||
from utils.models import HostResult
|
||||
from utils.models import HostResult, HostReport, GroupedReports # <-- add HostReport, GroupedReports
|
||||
from ipaddress import ip_address
|
||||
|
||||
def fmt_ports(ports: List[int]) -> str:
|
||||
if not ports:
|
||||
@@ -45,8 +47,53 @@ def _env(templates_dir: Path) -> Environment:
|
||||
env.globals.update(badge=badge, pill_state=pill_state, pill_proto=pill_proto, fmt_ports=fmt_ports)
|
||||
return env
|
||||
|
||||
|
||||
# --- NEW: compatibility shim so renderer can accept either shape ---
|
||||
|
||||
ReportsArg = Union[GroupedReports, Dict[str, Dict[str, List[int]]]]
|
||||
|
||||
def _coerce_to_grouped(reports: ReportsArg) -> GroupedReports:
|
||||
"""
|
||||
Accept either:
|
||||
- GroupedReports (new, preferred), or
|
||||
- legacy dict: { ip: {"unexpected_tcp":[], "missing_tcp":[], "unexpected_udp":[], "missing_udp":[] } }
|
||||
and return a GroupedReports instance.
|
||||
"""
|
||||
if isinstance(reports, GroupedReports):
|
||||
return reports
|
||||
|
||||
# Legacy dict -> build HostReport objects
|
||||
by_ip: Dict[str, HostReport] = {}
|
||||
for ip, d in reports.items():
|
||||
hr = HostReport(
|
||||
ip=str(ip),
|
||||
unexpected_tcp=list(d.get("unexpected_tcp", []) or []),
|
||||
missing_tcp=list(d.get("missing_tcp", []) or []),
|
||||
unexpected_udp=list(d.get("unexpected_udp", []) or []),
|
||||
missing_udp=list(d.get("missing_udp", []) or []),
|
||||
)
|
||||
by_ip[ip] = hr
|
||||
|
||||
# Split & sort by IP
|
||||
issues: List[HostReport] = []
|
||||
expected: List[HostReport] = []
|
||||
for hr in by_ip.values():
|
||||
(issues if hr.has_issues() else expected).append(hr)
|
||||
|
||||
def _ip_key(hr: HostReport):
|
||||
try:
|
||||
return ip_address(hr.ip)
|
||||
except ValueError:
|
||||
return hr.ip
|
||||
|
||||
issues.sort(key=_ip_key)
|
||||
expected.sort(key=_ip_key)
|
||||
|
||||
return GroupedReports(issues=issues, expected=expected, by_ip=by_ip)
|
||||
|
||||
|
||||
def render_html_report_jinja(
|
||||
reports: Dict[str, Dict[str, List[int]]],
|
||||
reports: ReportsArg,
|
||||
host_results: List[HostResult],
|
||||
templates_dir: Path,
|
||||
template_name: str = "report.html.j2",
|
||||
@@ -56,30 +103,46 @@ def render_html_report_jinja(
|
||||
env = _env(templates_dir)
|
||||
template = env.get_template(template_name)
|
||||
|
||||
total_hosts = len(reports)
|
||||
hosts_with_issues = [ip for ip, r in reports.items() if any(r.values())]
|
||||
ok_hosts = total_hosts - len(hosts_with_issues)
|
||||
grouped = _coerce_to_grouped(reports)
|
||||
|
||||
# No filtering — we'll show all, but only_issues changes template behavior.
|
||||
by_ip = {hr.address: hr for hr in host_results}
|
||||
for hr in by_ip.values():
|
||||
total_hosts = len(grouped.by_ip)
|
||||
ok_hosts = len(grouped.expected)
|
||||
hosts_with_issues = [hr.ip for hr in grouped.issues]
|
||||
|
||||
# Build a mapping of IP -> HostResult and sort port rows for stable output
|
||||
by_ip_results = {hr.address: hr for hr in host_results}
|
||||
for hr in by_ip_results.values():
|
||||
if hr and hr.ports:
|
||||
hr.ports.sort(key=lambda p: (p.protocol, p.port))
|
||||
|
||||
html = template.render(
|
||||
title=title,
|
||||
generated=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
|
||||
# Summary bar
|
||||
total_hosts=total_hosts,
|
||||
ok_hosts=ok_hosts,
|
||||
hosts_with_issues=hosts_with_issues,
|
||||
|
||||
# New grouped context for simple “issues first, expected later” loops
|
||||
issues=grouped.issues, # list[HostReport]
|
||||
expected=grouped.expected, # list[HostReport]
|
||||
by_ip=grouped.by_ip, # dict[str, HostReport]
|
||||
|
||||
# Legacy context (kept for compatibility if your template still uses it)
|
||||
reports=reports,
|
||||
host_results_by_ip=by_ip,
|
||||
|
||||
# Host scan details
|
||||
host_results_by_ip=by_ip_results,
|
||||
|
||||
# Existing behavior switch
|
||||
only_issues=only_issues,
|
||||
)
|
||||
return html
|
||||
|
||||
|
||||
def write_html_report_jinja(
|
||||
reports: Dict[str, Dict[str, List[int]]],
|
||||
reports: ReportsArg,
|
||||
host_results: List[HostResult],
|
||||
out_path: Path,
|
||||
templates_dir: Path = Path("templates"),
|
||||
|
||||
Reference in New Issue
Block a user