#!/usr/bin/env python3
"""
Dashboard template for viewing honeypot statistics.
Customize this template to change the dashboard appearance.
"""
import html
from datetime import datetime
from zoneinfo import ZoneInfo
def _escape(value) -> str:
"""Escape HTML special characters to prevent XSS attacks."""
if value is None:
return ""
return html.escape(str(value))
def format_timestamp(iso_timestamp: str, time_only: bool = False) -> str:
"""Format ISO timestamp for display with timezone conversion
Args:
iso_timestamp: ISO format timestamp string (UTC)
time_only: If True, return only HH:MM:SS, otherwise full datetime
"""
try:
# Parse UTC timestamp
dt = datetime.fromisoformat(iso_timestamp)
if time_only:
return dt.strftime("%H:%M:%S")
return dt.strftime("%Y-%m-%d %H:%M:%S")
except Exception:
# Fallback for old format
return (
iso_timestamp.split("T")[1][:8] if "T" in iso_timestamp else iso_timestamp
)
def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
"""Generate dashboard HTML with access statistics
Args:
stats: Statistics dictionary
dashboard_path: The secret dashboard path for generating API URLs
"""
# Generate IP rows with clickable functionality for dropdown stats
top_ips_rows = (
"\n".join([f"""
| {i+1} |
{_escape(ip)} |
{count} |
|
|
""" for i, (ip, count) in enumerate(stats["top_ips"])])
or '| No data |
'
)
# Generate paths rows (CRITICAL: paths can contain XSS payloads)
top_paths_rows = (
"\n".join(
[
f'| {i+1} | {_escape(path)} | {count} |
'
for i, (path, count) in enumerate(stats["top_paths"])
]
)
or '| No data |
'
)
# Generate User-Agent rows (CRITICAL: user agents can contain XSS payloads)
top_ua_rows = (
"\n".join(
[
f'| {i+1} | {_escape(ua[:80])} | {count} |
'
for i, (ua, count) in enumerate(stats["top_user_agents"])
]
)
or '| No data |
'
)
# Generate suspicious accesses rows with clickable IPs
suspicious_rows = (
"\n".join([f"""
| {_escape(log["ip"])} |
{_escape(log["path"])} |
{_escape(log["user_agent"][:60])} |
{format_timestamp(log["timestamp"], time_only=True)} |
|
|
""" for log in stats["recent_suspicious"][-10:]])
or '| No suspicious activity detected |
'
)
# Generate honeypot triggered IPs rows with clickable IPs
honeypot_rows = (
"\n".join([f"""
| {_escape(ip)} |
{_escape(", ".join(paths))} |
{len(paths)} |
|
|
""" for ip, paths in stats.get("honeypot_triggered_ips", [])])
or '| No honeypot triggers yet |
'
)
# Generate attack types rows with clickable IPs
attack_type_rows = (
"\n".join([f"""
| {_escape(log["ip"])} |
{_escape(log["path"])} |
{_escape(", ".join(log["attack_types"]))} |
{_escape(log["user_agent"][:60])} |
{format_timestamp(log["timestamp"],time_only=True)} |
|
|
""" for log in stats.get("attack_types", [])[-10:]])
or '| No attacks detected |
'
)
# Generate credential attempts rows with clickable IPs
credential_rows = (
"\n".join([f"""
| {_escape(log["ip"])} |
{_escape(log["username"])} |
{_escape(log["password"])} |
{_escape(log["path"])} |
{format_timestamp(log["timestamp"], time_only=True)} |
|
|
""" for log in stats.get("credential_attempts", [])[-20:]])
or '| No credentials captured yet |
'
)
return f"""
Krawl Dashboard
Krawl Dashboard
{stats['total_accesses']}
Total Accesses
{stats['unique_ips']}
Unique IPs
{stats['unique_paths']}
Unique Paths
{stats['suspicious_accesses']}
Suspicious Accesses
{stats.get('honeypot_ips', 0)}
Honeypot Caught
{len(stats.get('credential_attempts', []))}
Credentials Captured
Honeypot Triggers by IP
| IP Address |
Accessed Paths |
Count |
{honeypot_rows}
Recent Suspicious Activity
| IP Address |
Path |
User-Agent |
Time |
{suspicious_rows}
Captured Credentials
| IP Address |
Username |
Password |
Path |
Time |
{credential_rows}
Detected Attack Types
| IP Address |
Path |
Attack Types |
User-Agent |
Time |
{attack_type_rows}
Top IP Addresses
| # |
IP Address |
Access Count |
{top_ips_rows}
Top Paths
| # |
Path |
Access Count |
{top_paths_rows}
Top User-Agents
| # |
User-Agent |
Count |
{top_ua_rows}
"""