import html
import json
from reports.database import query_all, query_one
from reports.template import (
ReportContext,
card,
page_begin,
page_end,
page_header,
)
from reports.utils import (
format_datetime as format_report_datetime,
)
def esc(value):
if value is None:
return "—"
return html.escape(
str(value),
quote=True,
)
def format_datetime(value):
return format_report_datetime(
value,
"%d.%m.%y-%H.%M.%S"
)
def format_popup_datetime(value):
return format_report_datetime(
value,
"%d-%m-%Y-%H-%M-%S"
)
def load_report_interval(conn):
row = query_one(
conn,
"""
SELECT
MIN(start_time) AS first_time,
MAX(
COALESCE(
end_time,
start_time
)
) AS last_time
FROM capture_sessions
""",
)
if not row:
return (
None,
None,
)
return (
row["first_time"],
row["last_time"],
)
def load_access_points(conn):
rows = query_all(
conn,
"""
WITH ranked_observations AS (
SELECT
obs.access_point_id,
obs.latitude,
obs.longitude,
obs.observed_at,
obs.rssi,
obs.channel,
ROW_NUMBER() OVER (
PARTITION BY obs.access_point_id
ORDER BY
obs.observed_at DESC,
obs.id DESC
) AS rn
FROM access_point_observations AS obs
WHERE
obs.latitude IS NOT NULL
AND obs.longitude IS NOT NULL
AND obs.latitude != 0
AND obs.longitude != 0
)
SELECT
ap.id,
ap.bssid,
ap.essid,
ap.vendor,
ap.has_handshake,
ap.has_pmkid,
ap.is_cracked,
obs.latitude,
obs.longitude,
obs.observed_at,
obs.rssi,
obs.channel,
(
SELECT c.password
FROM credentials AS c
WHERE c.access_point_id = ap.id
ORDER BY
c.created_at DESC,
c.id DESC
LIMIT 1
) AS password
FROM access_points AS ap
JOIN ranked_observations AS obs
ON obs.access_point_id = ap.id
AND obs.rn = 1
ORDER BY
obs.observed_at DESC,
ap.bssid
""",
)
return rows
def build_marker_js(observations):
ap_features = []
handshake_features = []
pmkid_features = []
password_features = []
for row in observations:
latitude = row["latitude"]
longitude = row["longitude"]
if (
latitude is None
or longitude is None
):
continue
ssid = (
row["essid"]
if row["essid"] is not None
and str(row["essid"]) != ""
else "—"
)
vendor = (
row["vendor"]
if row["vendor"] is not None
and str(row["vendor"]) != ""
else "—"
)
channel = (
row["channel"]
if row["channel"] is not None
else "—"
)
rssi = (
f"{row['rssi']} dBm"
if row["rssi"] is not None
else "—"
)
password = (
row["password"]
if row["password"] is not None
and str(row["password"]) != ""
else "—"
)
popup = (
f"{esc(ssid)}"
f"
MAC: {esc(row['bssid'])}"
f"
Vendor: {esc(vendor)}"
f"
Channel: {esc(channel)}"
f"
RSSI: {esc(rssi)}"
f"
Password: {esc(password)}"
f"
Date: "
f"{esc(format_popup_datetime(row['observed_at']))}"
f'
'
f"Подробнее"
)
feature = {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
float(longitude),
float(latitude),
],
},
"properties": {
"popup": popup,
},
}
ap_features.append(feature)
if row["has_handshake"]:
handshake_features.append(
feature
)
if row["has_pmkid"]:
pmkid_features.append(
feature
)
if row["is_cracked"] or row["password"]:
password_features.append(
feature
)
return (
"const apFeatures = "
+ json.dumps(
ap_features,
ensure_ascii=False,
)
+ ";\n"
"const handshakeFeatures = "
+ json.dumps(
handshake_features,
ensure_ascii=False,
)
+ ";\n"
"const pmkidFeatures = "
+ json.dumps(
pmkid_features,
ensure_ascii=False,
)
+ ";\n"
"const passwordFeatures = "
+ json.dumps(
password_features,
ensure_ascii=False,
)
+ ";\n"
)
def render_access_points_map(
observations,
context,
):
marker_js = build_marker_js(
observations
)
maplibre_css_url = context.asset_url(
"maplibre/maplibre-gl.css"
)
maplibre_js_url = context.asset_url(
"maplibre/maplibre-gl.js"
)
map_html = """