Update public source
This commit is contained in:
@@ -0,0 +1,395 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user