init commit
This commit is contained in:
95
app/reporting_jinja.py
Normal file
95
app/reporting_jinja.py
Normal file
@@ -0,0 +1,95 @@
|
||||
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'<span style="display:inline-block;padding:2px 6px;border-radius:12px;'
|
||||
f'font-size:12px;line-height:16px;background:{bg};color:{fg};'
|
||||
f'font-family:Segoe UI,Arial,sans-serif">{escape(text)}</span>'
|
||||
)
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user