Files
2026-10-06 21:08:12 +03:00

396 lines
5.5 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Server-side Trips report page for .wifitrip import/export.
"""
from html import escape
from reports.template import (
ReportContext,
page_begin,
page_end,
)
def _stat_card(label, value):
return f"""
<div class="stat-card">
<div class="stat-label">
{escape(str(label))}
</div>
<div class="stat-value">
{escape(str(value))}
</div>
</div>
"""
def _message(message):
if not message:
return ""
message_type = escape(
str(message.get("type", "info"))
)
message_text = escape(
str(message.get("text", ""))
)
return f"""
<section class="card">
<div class="info {message_type}">
{message_text}
</div>
</section>
"""
def _import_result(result):
if not result:
return ""
html = """
<section class="card">
<h2>Import result</h2>
<div class="stats-grid">
"""
html += _stat_card(
"Sessions in file",
result.get("sessions", 0),
)
html += _stat_card(
"Imported",
result.get("imported", 0),
)
html += _stat_card(
"Duplicates",
result.get("duplicates", 0),
)
html += _stat_card(
"Failed",
result.get("failed", 0),
)
html += """
</div>
"""
failed_sessions = result.get(
"failed_sessions",
[],
)
if failed_sessions:
html += """
<h3>Failed sessions</h3>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Session hash</th>
<th>Error</th>
</tr>
</thead>
<tbody>
"""
for item in failed_sessions:
html += f"""
<tr>
<td>
<code>
{escape(str(item.get("session_hash", "")))}
</code>
</td>
<td>
{escape(str(item.get("error", "")))}
</td>
</tr>
"""
html += """
</tbody>
</table>
</div>
"""
html += """
</section>
"""
return html
def _validation_result(validation):
if not validation:
return ""
if not validation.get("valid"):
return f"""
<section class="card">
<h2>Package validation</h2>
<div class="info error">
{escape(
str(
validation.get(
"error",
"Package is invalid."
)
)
)}
</div>
</section>
"""
return f"""
<section class="card">
<h2>Package validation</h2>
<div class="info success">
Package is valid and contains
{escape(str(validation.get("sessions", 0)))}
session(s).
</div>
<div class="stats-grid">
{_stat_card(
"Sessions",
validation.get("sessions", 0)
)}
{_stat_card(
"Access points",
validation.get("access_points", 0)
)}
{_stat_card(
"GPS points",
validation.get("gps_points", 0)
)}
{_stat_card(
"Observations",
validation.get("observations", 0)
)}
{_stat_card(
"Handshakes",
validation.get("handshakes", 0)
)}
{_stat_card(
"Handshake Cracked",
validation.get("credentials", 0)
)}
</div>
</section>
"""
def render_trips_page(
conn,
context=None,
message=None,
import_result=None,
validation=None,
):
if context is None:
context = ReportContext(
mode="server",
page="trips",
)
session_count = conn.execute(
"""
SELECT COUNT(*) AS count
FROM capture_sessions
"""
).fetchone()["count"]
html = page_begin(
"Trips",
(
"Export and import capture sessions "
"as .wifitrip packages."
),
"trips",
context,
)
html += _message(
message
)
html += """
<section class="card">
<h2>Export capture sessions</h2>
<p>
Export all capture sessions into one
<code>.wifitrip</code> package.
</p>
<p>
The package contains logical session data:
GPS track, access-point observations,
handshakes and credentials. Physical capture
files are not included.
</p>
<div class="stats-grid">
"""
html += _stat_card(
"Sessions available",
session_count,
)
html += """
</div>
<form
method="get"
action="/reports/trips/export"
>
<button type="submit" class="button">
Export all sessions
</button>
</form>
</section>
"""
html += """
<section class="card">
<h2>Import capture sessions</h2>
<p>
Import one <code>.wifitrip</code> package.
The complete package is validated before
any database changes are made.
</p>
<form
method="post"
action="/reports/trips/import"
enctype="multipart/form-data"
>
<label for="wifitrip_file">
.wifitrip file
</label>
<div class="wifitrip-file-row">
<input
type="file"
id="wifitrip_file"
name="wifitrip_file"
accept=".wifitrip,application/zip"
required
>
<label
for="wifitrip_file"
class="button"
>
Browse
</label>
<span
class="wifitrip-file-name"
id="wifitrip-file-name"
>
Файл не выбран
</span>
</div>
<div style="margin-top: 1rem;">
<button type="submit" class="button">
Import package
</button>
</div>
</form>
<script>
const wifitripFileInput = document.getElementById(
"wifitrip_file"
);
const wifitripFileName = document.getElementById(
"wifitrip-file-name"
);
if (wifitripFileInput && wifitripFileName) {
wifitripFileInput.addEventListener(
"change",
function () {
if (this.files.length > 0) {
wifitripFileName.textContent =
this.files[0].name;
} else {
wifitripFileName.textContent =
"Файл не выбран";
}
}
);
}
</script>
</section>
"""
html += _validation_result(
validation
)
html += _import_result(
import_result
)
html += page_end()
return html