#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Dashboard HTML report. Главная страница отчета. Содержит только обзорную информацию. Без карты. Без полного списка точек доступа. Вся детальная информация находится на страницах отдельных поездок. """ from pathlib import Path from .config import ( RECENT_SESSIONS_LIMIT, ) from .utils import ( format_datetime, ) from .template import ( page_begin, page_end, page_header, card, stat, stats_grid, ) from .statistics import ( database_statistics, top_vendors, top_essid, channel_statistics, encryption_statistics, ) from .sessions import ( recent_sessions, all_sessions, ) # ============================================================ # Helpers # ============================================================ def format_session_folder(capture_file): if not capture_file: return "-" folder = Path(capture_file).parent.name try: from datetime import datetime, timezone dt = datetime.strptime( folder, "%Y%m%d_%H%M%S" ).replace( tzinfo=timezone.utc ) return format_datetime( dt.isoformat() ) except ValueError: return folder # ============================================================ # Database statistics # ============================================================ def statistics_block(conn): data = database_statistics(conn) items = [ stat( data["access_points"], "Access Points" ), stat( data["access_points_without_coordinates"], "AP without Coordinates" ), stat( data["sessions"], "Capture Sessions" ), stat( data["vendors"], "Vendors" ), stat( data["handshakes"], "Handshakes" ), stat( data["credentials"], "Handshake Cracked" ), stat( data["pmkid"], "PMKID" ), stat( data["cracked"], "Cracked AP" ), stat( data["database_size"], "Database" ), ] return card( "📊 Database Overview", stats_grid(items) ) # ============================================================ # Information # ============================================================ def information_block(conn): data = database_statistics(conn) html = f"""
Database size {data["database_size"]}
Captures size {data["captures_size"]}
Capture files {data["capture_files"]}
First access point {format_datetime(data["first_seen"])}
Last access point {format_datetime(data["last_seen"])}
""" return card( "ℹ️ Database Information", html, ) # ============================================================ # Top Vendors # ============================================================ def vendors_block(conn): rows = top_vendors( conn, limit=15 ) html = """ """ for vendor, total in rows: if not vendor: vendor = "-" html += f""" """ html += """
Vendor Devices
{vendor} {total}
""" return card( "🏭 Top Vendors", html, ) # ============================================================ # Top ESSID # ============================================================ def essid_block(conn): rows = top_essid( conn, limit=20 ) html = """ """ for essid, total in rows: if not essid: essid = "<hidden>" html += f""" """ html += """
ESSID Count
{essid} {total}
""" return card( "📶 Most Common ESSID", html, ) # ============================================================ # Channels # ============================================================ def channels_block(conn): rows = channel_statistics( conn ) html = """ """ for channel, total in rows: html += f""" """ html += """
Channel Access Points
{channel} {total}
""" return card( "📡 Channel Statistics", html, ) # ============================================================ # Encryption # ============================================================ def encryption_block(conn): rows = encryption_statistics( conn ) html = """ """ for encryption, total in rows: if not encryption: encryption = "-" html += f""" """ html += """
Encryption Networks
{encryption} {total}
""" return card( "🔐 Encryption", html, ) # ============================================================ # Session Card # ============================================================ def session_card( session, context=None, ): session_url = context.session_url( session["id"] ) return f"""

🚗 Session {format_datetime(session.get("trip_start"))}

Trip start {format_datetime(session.get("trip_start"))}
Trip end {format_datetime(session.get("trip_end"))}
Duration {session.get("duration","-")}
Imported {format_datetime(session.get("start_time"))}
Session folder {format_session_folder(session.get("capture_file"))}
Access Points {session.get("access_points","-")}
Coordinates Restored {session.get("coordinates_restored","-")}
AP without Coordinates {session.get("ap_without_coordinates","-")}
Handshake AP {session.get("handshakes","-")}
Handshake Records {session.get("handshake_records","-")}
PMKID AP {session.get("pmkid","-")}
Cracked AP {session.get("cracked","-")}
Directory size {session.get("directory_size","-")}

📄 Open Session Report

""" # ============================================================ # Recent Sessions # ============================================================ def sessions_block( conn, context=None, ): sessions = recent_sessions( conn, limit=RECENT_SESSIONS_LIMIT, ) if not sessions: return card( "🚗 Recent Capture Sessions", """

Capture sessions not found.

""" ) cards = [] for session in sessions: cards.append( session_card( session, context, ) ) capture_sessions_url = ( context.capture_sessions_url() ) html = """
""" html += "\n".join(cards) html += f"""

Show all sessions

""" return card( "🚗 Recent Capture Sessions", html, ) # ============================================================ # Dashboard # ============================================================ def generate_dashboard( conn, context=None, ): html = page_begin( title="WiFi GPS Mapper", description=( "Database overview" ), active="dashboard", context=context, ) # -------------------------------------------------------- # Database statistics # -------------------------------------------------------- html += statistics_block( conn ) # -------------------------------------------------------- # Information # -------------------------------------------------------- html += information_block( conn ) # -------------------------------------------------------- # Vendors / ESSID # -------------------------------------------------------- html += """
""" html += vendors_block( conn ) html += essid_block( conn ) html += """
""" # -------------------------------------------------------- # Channels / Encryption # -------------------------------------------------------- html += """
""" html += channels_block( conn ) html += encryption_block( conn ) html += """
""" # -------------------------------------------------------- # Sessions # -------------------------------------------------------- html += sessions_block( conn, context, ) # -------------------------------------------------------- # Footer # -------------------------------------------------------- html += page_end() return html # ============================================================ # All Capture Sessions # ============================================================ def generate_capture_sessions( conn, context=None, ): sessions = all_sessions( conn ) html = page_begin( title="Capture Sessions", description="All capture sessions", active="capture_sessions", context=context, ) html += page_header( "🚗 Capture Sessions", f""" Total sessions: {len(sessions)} """ ) cards = [] for session in sessions: cards.append( session_card( session, context, ) ) html += """
""" html += "\n".join(cards) html += """
""" html += page_end() return html