from __future__ import annotations from datetime import datetime from html import escape from pathlib import Path from typing import Dict, List from jinja2 import Environment, FileSystemLoader, select_autoescape from utils.models import HostResult def fmt_ports(ports: List[int]) -> str: if not ports: return "none" return ", ".join(str(p) for p in sorted(set(int(x) for x in ports))) def badge(text: str, bg: str, fg: str = "#ffffff") -> str: return ( f'{escape(text)}' ) def pill_state(state: str) -> str: s = (state or "").lower() if s == "open": return badge("open", "#16a34a") if s in ("open|filtered",): return badge("open|filtered", "#0ea5e9") if s == "filtered": return badge("filtered", "#f59e0b", "#111111") if s == "closed": return badge("closed", "#ef4444") return badge(s, "#6b7280") def pill_proto(proto: str) -> str: return badge((proto or "").lower(), "#334155") def _env(templates_dir: Path) -> Environment: env = Environment( loader=FileSystemLoader(str(templates_dir)), autoescape=select_autoescape(["html", "xml"]), trim_blocks=True, lstrip_blocks=True, ) env.globals.update(badge=badge, pill_state=pill_state, pill_proto=pill_proto, fmt_ports=fmt_ports) return env def render_html_report_jinja( reports: Dict[str, Dict[str, List[int]]], host_results: List[HostResult], templates_dir: Path, template_name: str = "report.html.j2", title: str = "Port Compliance Report", only_issues: bool = False, ) -> str: 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) # 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(): 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"), total_hosts=total_hosts, ok_hosts=ok_hosts, hosts_with_issues=hosts_with_issues, reports=reports, host_results_by_ip=by_ip, only_issues=only_issues, ) return html def write_html_report_jinja( reports: Dict[str, Dict[str, List[int]]], host_results: List[HostResult], out_path: Path, templates_dir: Path = Path("templates"), template_name: str = "report.html.j2", title: str = "Port Compliance Report", only_issues: bool = False, ) -> Path: html = render_html_report_jinja( reports, host_results, templates_dir, template_name, title, only_issues ) out_path.write_text(html, encoding="utf-8") return out_path