Files
wifi-gps-mapper-public/reports/dashboard.py
T
2026-10-06 21:08:12 +03:00

856 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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"""
<table>
<tr>
<td><b>Database size</b></td>
<td>{data["database_size"]}</td>
</tr>
<tr>
<td><b>Captures size</b></td>
<td>{data["captures_size"]}</td>
</tr>
<tr>
<td><b>Capture files</b></td>
<td>{data["capture_files"]}</td>
</tr>
<tr>
<td><b>First access point</b></td>
<td>{format_datetime(data["first_seen"])}</td>
</tr>
<tr>
<td><b>Last access point</b></td>
<td>{format_datetime(data["last_seen"])}</td>
</tr>
</table>
"""
return card(
"ℹ️ Database Information",
html,
)
# ============================================================
# Top Vendors
# ============================================================
def vendors_block(conn):
rows = top_vendors(
conn,
limit=15
)
html = """
<table>
<tr>
<th>Vendor</th>
<th>Devices</th>
</tr>
"""
for vendor, total in rows:
if not vendor:
vendor = "-"
html += f"""
<tr>
<td>{vendor}</td>
<td>{total}</td>
</tr>
"""
html += """
</table>
"""
return card(
"🏭 Top Vendors",
html,
)
# ============================================================
# Top ESSID
# ============================================================
def essid_block(conn):
rows = top_essid(
conn,
limit=20
)
html = """
<table>
<tr>
<th>ESSID</th>
<th>Count</th>
</tr>
"""
for essid, total in rows:
if not essid:
essid = "&lt;hidden&gt;"
html += f"""
<tr>
<td>{essid}</td>
<td>{total}</td>
</tr>
"""
html += """
</table>
"""
return card(
"📶 Most Common ESSID",
html,
)
# ============================================================
# Channels
# ============================================================
def channels_block(conn):
rows = channel_statistics(
conn
)
html = """
<table>
<tr>
<th>Channel</th>
<th>Access Points</th>
</tr>
"""
for channel, total in rows:
html += f"""
<tr>
<td>{channel}</td>
<td>{total}</td>
</tr>
"""
html += """
</table>
"""
return card(
"📡 Channel Statistics",
html,
)
# ============================================================
# Encryption
# ============================================================
def encryption_block(conn):
rows = encryption_statistics(
conn
)
html = """
<table>
<tr>
<th>Encryption</th>
<th>Networks</th>
</tr>
"""
for encryption, total in rows:
if not encryption:
encryption = "-"
html += f"""
<tr>
<td>{encryption}</td>
<td>{total}</td>
</tr>
"""
html += """
</table>
"""
return card(
"🔐 Encryption",
html,
)
# ============================================================
# Session Card
# ============================================================
def session_card(
session,
context=None,
):
session_url = context.session_url(
session["id"]
)
return f"""
<div class="card">
<h3>
🚗 Session {format_datetime(session.get("trip_start"))}
</h3>
<table>
<tr>
<td><b>Trip start</b></td>
<td>{format_datetime(session.get("trip_start"))}</td>
</tr>
<tr>
<td><b>Trip end</b></td>
<td>{format_datetime(session.get("trip_end"))}</td>
</tr>
<tr>
<td><b>Duration</b></td>
<td>{session.get("duration","-")}</td>
</tr>
<tr>
<td><b>Imported</b></td>
<td>{format_datetime(session.get("start_time"))}</td>
</tr>
<tr>
<td><b>Session folder</b></td>
<td>{format_session_folder(session.get("capture_file"))}</td>
</tr>
<tr>
<td><b>Access Points</b></td>
<td>{session.get("access_points","-")}</td>
</tr>
<tr>
<td><b>Coordinates Restored</b></td>
<td>{session.get("coordinates_restored","-")}</td>
</tr>
<tr>
<td><b>AP without Coordinates</b></td>
<td>{session.get("ap_without_coordinates","-")}</td>
</tr>
<tr>
<td><b>Handshake AP</b></td>
<td>{session.get("handshakes","-")}</td>
</tr>
<tr>
<td><b>Handshake Records</b></td>
<td>{session.get("handshake_records","-")}</td>
</tr>
<tr>
<td><b>PMKID AP</b></td>
<td>{session.get("pmkid","-")}</td>
</tr>
<tr>
<td><b>Cracked AP</b></td>
<td>{session.get("cracked","-")}</td>
</tr>
<tr>
<td><b>Directory size</b></td>
<td>{session.get("directory_size","-")}</td>
</tr>
</table>
<p>
<a
class="button"
draggable="false"
href="{session_url}">
📄 Open Session Report
</a>
</p>
</div>
"""
# ============================================================
# Recent Sessions
# ============================================================
def sessions_block(
conn,
context=None,
):
sessions = recent_sessions(
conn,
limit=RECENT_SESSIONS_LIMIT,
)
if not sessions:
return card(
"🚗 Recent Capture Sessions",
"""
<p>
Capture sessions not found.
</p>
"""
)
cards = []
for session in sessions:
cards.append(
session_card(
session,
context,
)
)
capture_sessions_url = (
context.capture_sessions_url()
)
html = """
<div class="report-grid">
"""
html += "\n".join(cards)
html += f"""
</div>
<p class="report-action">
<a
class="button"
draggable="false"
href="{capture_sessions_url}">
Show all sessions
</a>
</p>
"""
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 += """
<div class="report-grid">
"""
html += vendors_block(
conn
)
html += essid_block(
conn
)
html += """
</div>
"""
# --------------------------------------------------------
# Channels / Encryption
# --------------------------------------------------------
html += """
<div class="report-grid">
"""
html += channels_block(
conn
)
html += encryption_block(
conn
)
html += """
</div>
"""
# --------------------------------------------------------
# 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:
<b>{len(sessions)}</b>
"""
)
cards = []
for session in sessions:
cards.append(
session_card(
session,
context,
)
)
html += """
<div class="report-grid">
"""
html += "\n".join(cards)
html += """
</div>
"""
html += page_end()
return html