2889 lines
83 KiB
HTML
2889 lines
83 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
<meta
|
|
name="viewport"
|
|
content="width=device-width, initial-scale=1.0"
|
|
>
|
|
|
|
<title>WiFi GPS Mapper</title>
|
|
|
|
<link
|
|
rel="stylesheet"
|
|
href="{{ url_for('static', filename='style.css') }}"
|
|
>
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<header class="app-header">
|
|
|
|
<div class="header-inner">
|
|
|
|
<div>
|
|
|
|
<h1>
|
|
WiFi GPS Mapper
|
|
</h1>
|
|
|
|
<div class="subtitle">
|
|
Wireless network scanner
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="header-actions">
|
|
|
|
<a
|
|
class="header-link"
|
|
href="{{ url_for('reports_dashboard') }}"
|
|
>
|
|
REPORTS
|
|
</a>
|
|
|
|
|
|
<div class="header-scanner-controls">
|
|
|
|
<button
|
|
type="submit"
|
|
form="interface-form"
|
|
class="header-button start"
|
|
{% if status.running or gps_test.running %}disabled{% endif %}
|
|
>
|
|
START
|
|
</button>
|
|
|
|
|
|
<form
|
|
method="post"
|
|
action="{{ url_for('scanner_stop') }}"
|
|
>
|
|
|
|
<button
|
|
type="submit"
|
|
class="header-button stop"
|
|
{% if not status.running %}disabled{% endif %}
|
|
>
|
|
STOP
|
|
</button>
|
|
|
|
</form>
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
id="header-scanner-status"
|
|
class="header-status {{ status.health|lower }}"
|
|
>
|
|
<span
|
|
class="status-indicator {{ status.health|lower }}"
|
|
></span>
|
|
|
|
<span class="status-text">
|
|
{% if status.health == "ACTIVE" %}
|
|
SCANNER ACTIVE
|
|
{% elif status.health == "WARNING" %}
|
|
WARNING
|
|
{% elif status.health == "STOPPED" %}
|
|
SCANNER STOPPED
|
|
{% else %}
|
|
SCANNER READY
|
|
{% endif %}
|
|
</span>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</header>
|
|
|
|
|
|
<main>
|
|
|
|
|
|
<section class="panel scanner-panel">
|
|
|
|
<div class="panel-heading">
|
|
|
|
<div>
|
|
|
|
<div class="panel-kicker">
|
|
CONTROL
|
|
</div>
|
|
|
|
<h2>
|
|
Scanner
|
|
</h2>
|
|
|
|
</div>
|
|
|
|
<div class="scanner-state">
|
|
|
|
{% if status.running %}
|
|
|
|
<strong class="running">
|
|
RUNNING
|
|
</strong>
|
|
|
|
<span>
|
|
PID {{ status.pid }}
|
|
</span>
|
|
|
|
{% else %}
|
|
|
|
<strong class="stopped">
|
|
STOPPED
|
|
</strong>
|
|
|
|
{% endif %}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="scanner-controls">
|
|
|
|
<form
|
|
id="interface-form"
|
|
class="interface-form"
|
|
method="post"
|
|
action="{{ url_for('scanner_start') }}"
|
|
>
|
|
|
|
<div class="field">
|
|
|
|
<label for="interface">
|
|
WiFi interface
|
|
</label>
|
|
|
|
<select
|
|
id="interface"
|
|
name="interface"
|
|
required
|
|
{% if status.running or gps_test.running %}disabled{% endif %}
|
|
>
|
|
|
|
<option value="">
|
|
Select interface
|
|
</option>
|
|
|
|
{% for interface in interfaces %}
|
|
|
|
<option value="{{ interface }}">
|
|
{{ interface }}
|
|
</option>
|
|
|
|
{% endfor %}
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
|
|
{% if interface_error %}
|
|
|
|
<div class="error">
|
|
{{ interface_error }}
|
|
</div>
|
|
|
|
{% endif %}
|
|
|
|
|
|
<button
|
|
type="submit"
|
|
class="button start"
|
|
{% if status.running or gps_test.running %}disabled{% endif %}
|
|
>
|
|
START SCAN
|
|
</button>
|
|
|
|
</form>
|
|
|
|
|
|
<form
|
|
method="post"
|
|
action="{{ url_for('scanner_stop') }}"
|
|
>
|
|
|
|
<button
|
|
type="submit"
|
|
class="button stop"
|
|
{% if not status.running %}disabled{% endif %}
|
|
>
|
|
STOP SCAN
|
|
</button>
|
|
|
|
</form>
|
|
|
|
</div>
|
|
|
|
</section>
|
|
|
|
|
|
<section class="panel configuration-panel">
|
|
|
|
<div class="panel-heading">
|
|
|
|
<div>
|
|
|
|
<div class="panel-kicker">
|
|
SYSTEM
|
|
</div>
|
|
|
|
<h2>
|
|
Configuration
|
|
</h2>
|
|
|
|
</div>
|
|
|
|
<span class="panel-hint">
|
|
config.yaml
|
|
</span>
|
|
|
|
</div>
|
|
|
|
|
|
{% if config._error %}
|
|
|
|
<div class="error">
|
|
{{ config._error }}
|
|
</div>
|
|
|
|
{% else %}
|
|
|
|
<form
|
|
id="configuration-form"
|
|
class="configuration-form"
|
|
>
|
|
|
|
<div class="config-grid">
|
|
|
|
<div class="config-column config-column-wifi">
|
|
|
|
<div class="field field-wide wifi-configuration">
|
|
|
|
<div class="wifi-title">
|
|
WiFi
|
|
</div>
|
|
|
|
|
|
<div class="wifi-interface-status">
|
|
|
|
<span>
|
|
Interface:
|
|
</span>
|
|
|
|
<strong id="wifi-diagnostics-interface">
|
|
not selected
|
|
</strong>
|
|
|
|
<span
|
|
id="wifi-diagnostics-status"
|
|
>
|
|
Select interface to detect channels
|
|
</span>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="wifi-band">
|
|
|
|
<div class="wifi-band-header">
|
|
|
|
<label class="wifi-band-toggle">
|
|
|
|
<input
|
|
id="wifi-enable-2-4ghz"
|
|
type="checkbox"
|
|
{% if config.wifi.enable_2_4ghz %}
|
|
checked
|
|
{% endif %}
|
|
>
|
|
|
|
<span>
|
|
2.4 GHz
|
|
</span>
|
|
|
|
</label>
|
|
|
|
|
|
<div class="wifi-band-actions">
|
|
|
|
<button
|
|
type="button"
|
|
class="channel-action"
|
|
data-band="2.4"
|
|
>
|
|
Select all
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
class="channel-action"
|
|
data-band="2.4"
|
|
data-action="clear"
|
|
>
|
|
Clear all
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
id="wifi-channels-2-4"
|
|
class="wifi-channel-grid"
|
|
></div>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="wifi-band">
|
|
|
|
<div class="wifi-band-header">
|
|
|
|
<label class="wifi-band-toggle">
|
|
|
|
<input
|
|
id="wifi-enable-5ghz"
|
|
type="checkbox"
|
|
{% if config.wifi.enable_5ghz %}
|
|
checked
|
|
{% endif %}
|
|
>
|
|
|
|
<span>
|
|
5 GHz
|
|
</span>
|
|
|
|
</label>
|
|
|
|
|
|
<div class="wifi-band-actions">
|
|
|
|
<button
|
|
type="button"
|
|
class="channel-action"
|
|
data-band="5"
|
|
>
|
|
Select all
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
class="channel-action"
|
|
data-band="5"
|
|
data-action="clear"
|
|
>
|
|
Clear all
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
id="wifi-channels-5"
|
|
class="wifi-channel-grid"
|
|
></div>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="wifi-band">
|
|
|
|
<div class="wifi-band-header">
|
|
|
|
<label class="wifi-band-toggle">
|
|
|
|
<input
|
|
id="wifi-enable-6ghz"
|
|
type="checkbox"
|
|
{% if config.wifi.enable_6ghz %}
|
|
checked
|
|
{% endif %}
|
|
>
|
|
|
|
<span>
|
|
6 GHz
|
|
</span>
|
|
|
|
</label>
|
|
|
|
|
|
<div class="wifi-band-actions">
|
|
|
|
<button
|
|
type="button"
|
|
class="channel-action"
|
|
data-band="6"
|
|
>
|
|
Select all
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
class="channel-action"
|
|
data-band="6"
|
|
data-action="clear"
|
|
>
|
|
Clear all
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
id="wifi-channels-6"
|
|
class="wifi-channel-grid"
|
|
></div>
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
id="wifi-channel-error"
|
|
class="error"
|
|
hidden
|
|
></div>
|
|
|
|
|
|
<input
|
|
id="wifi-channels"
|
|
type="hidden"
|
|
value=""
|
|
>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="config-column config-column-main">
|
|
|
|
<div class="field">
|
|
|
|
<label for="gps-source">
|
|
GPS source
|
|
</label>
|
|
|
|
<select id="gps-source">
|
|
|
|
<option
|
|
value="gpsrelay"
|
|
{% if config.gps.source == "gpsrelay" %}
|
|
selected
|
|
{% endif %}
|
|
>
|
|
GPSRelay
|
|
</option>
|
|
|
|
<option
|
|
value="gnss"
|
|
{% if config.gps.source == "gnss" %}
|
|
selected
|
|
{% endif %}
|
|
>
|
|
GNSS
|
|
</option>
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
id="gpsrelay-settings"
|
|
{% if config.gps.source != "gpsrelay" %}
|
|
hidden
|
|
{% endif %}
|
|
>
|
|
|
|
<div class="field">
|
|
|
|
<label for="gps-host">
|
|
GPS host
|
|
</label>
|
|
|
|
<input
|
|
id="gps-host"
|
|
type="text"
|
|
value="{{ config.gps.gpsrelay.host }}"
|
|
inputmode="decimal"
|
|
placeholder="xxx.xxx.xxx.xxx"
|
|
maxlength="15"
|
|
pattern="^(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}$"
|
|
title="Введите IPv4-адрес в формате xxx.xxx.xxx.xxx, например 192.168.1.10"
|
|
required
|
|
>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="field">
|
|
|
|
<label for="gps-port">
|
|
GPS port
|
|
</label>
|
|
|
|
<input
|
|
id="gps-port"
|
|
type="number"
|
|
value="{{ config.gps.gpsrelay.port }}"
|
|
min="1"
|
|
max="65535"
|
|
required
|
|
>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
id="gnss-settings"
|
|
{% if config.gps.source != "gnss" %}
|
|
hidden
|
|
{% endif %}
|
|
>
|
|
|
|
<div class="field">
|
|
|
|
<label for="gnss-handler">
|
|
GNSS handler
|
|
</label>
|
|
|
|
<select id="gnss-handler">
|
|
|
|
<option
|
|
value="direct"
|
|
{% if config.gps.gnss.handler == "direct" %}
|
|
selected
|
|
{% endif %}
|
|
>
|
|
Direct serial
|
|
</option>
|
|
|
|
<option
|
|
value="gpsd"
|
|
{% if config.gps.gnss.handler == "gpsd" %}
|
|
selected
|
|
{% endif %}
|
|
>
|
|
gpsd
|
|
</option>
|
|
|
|
<option
|
|
value="modem"
|
|
{% if config.gps.gnss.handler == "modem" %}
|
|
selected
|
|
{% endif %}
|
|
>
|
|
Modem
|
|
</option>
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
class="field"
|
|
id="gnss-direct-device-field"
|
|
{% if config.gps.gnss.handler != "direct" %}
|
|
hidden
|
|
{% endif %}
|
|
>
|
|
|
|
<label for="gnss-direct-device">
|
|
GNSS device
|
|
</label>
|
|
|
|
<input
|
|
id="gnss-direct-device"
|
|
type="text"
|
|
value="{{ config.gps.gnss.direct.device }}"
|
|
{% if config.gps.gnss.handler == "direct" %}
|
|
required
|
|
{% endif %}
|
|
>
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
class="field"
|
|
id="gnss-modem-device-field"
|
|
{% if config.gps.gnss.handler != "modem" %}
|
|
hidden
|
|
{% endif %}
|
|
>
|
|
|
|
<label for="gnss-modem-device">
|
|
GNSS modem device
|
|
</label>
|
|
|
|
<input
|
|
id="gnss-modem-device"
|
|
type="text"
|
|
value="{{ config.gps.gnss.modem.device }}"
|
|
{% if config.gps.gnss.handler == "modem" %}
|
|
required
|
|
{% endif %}
|
|
>
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
class="field"
|
|
id="gnss-at-device-field"
|
|
{% if config.gps.gnss.handler != "modem" %}
|
|
hidden
|
|
{% endif %}
|
|
>
|
|
|
|
<label for="gnss-at-device">
|
|
GNSS AT device
|
|
</label>
|
|
|
|
<input
|
|
id="gnss-at-device"
|
|
type="text"
|
|
value="{{ config.gps.gnss.modem.at_device }}"
|
|
{% if config.gps.gnss.handler == "modem" %}
|
|
required
|
|
{% endif %}
|
|
>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="field config-info">
|
|
|
|
<label>
|
|
Database
|
|
</label>
|
|
|
|
<div class="config-info-value">
|
|
{{ config.database.file }}
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="field config-info">
|
|
|
|
<label>
|
|
Vendor database
|
|
</label>
|
|
|
|
<div class="config-info-value">
|
|
{{ config.vendor.database }}
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="field config-info">
|
|
|
|
<label>
|
|
Captures directory
|
|
</label>
|
|
|
|
<div class="config-info-value">
|
|
{{ config.captures.directory }}
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="config-column config-column-extra">
|
|
|
|
<div class="field">
|
|
|
|
<label for="wifi-extra-args">
|
|
Extra args
|
|
</label>
|
|
|
|
<input
|
|
id="wifi-extra-args"
|
|
type="text"
|
|
value="{{ config.wifi.extra_args }}"
|
|
>
|
|
|
|
<div class="config-help">
|
|
Дополнительные параметры запуска hcxdumptool.
|
|
Если параметры не нужны, оставьте поле пустым.
|
|
Полную справку по параметрам используемой версии смотрите командой:
|
|
./bin/hcxdumptool --help
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="field">
|
|
|
|
<label for="timezone">
|
|
Timezone
|
|
</label>
|
|
|
|
<input
|
|
id="timezone"
|
|
type="text"
|
|
value="{{ config.timezone }}"
|
|
placeholder="Europe/Moscow"
|
|
required
|
|
>
|
|
|
|
<div class="config-help">
|
|
IANA часовой пояс для отображения времени в UI и отчетах.
|
|
В базе данных и обменных файлах время всегда хранится в UTC.
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="field">
|
|
|
|
<label for="logging-level">
|
|
Logging level
|
|
</label>
|
|
|
|
<select id="logging-level">
|
|
|
|
<option
|
|
value="DEBUG"
|
|
{% if config.logging.level == "DEBUG" %}
|
|
selected
|
|
{% endif %}
|
|
>
|
|
DEBUG
|
|
</option>
|
|
|
|
<option
|
|
value="INFO"
|
|
{% if config.logging.level == "INFO" %}
|
|
selected
|
|
{% endif %}
|
|
>
|
|
INFO
|
|
</option>
|
|
|
|
<option
|
|
value="WARNING"
|
|
{% if config.logging.level == "WARNING" %}
|
|
selected
|
|
{% endif %}
|
|
>
|
|
WARNING
|
|
</option>
|
|
|
|
<option
|
|
value="ERROR"
|
|
{% if config.logging.level == "ERROR" %}
|
|
selected
|
|
{% endif %}
|
|
>
|
|
ERROR
|
|
</option>
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="field">
|
|
|
|
<label for="status-interval">
|
|
Status interval
|
|
</label>
|
|
|
|
<input
|
|
id="status-interval"
|
|
type="number"
|
|
value="{{ config.status.interval }}"
|
|
min="1"
|
|
required
|
|
>
|
|
|
|
<div class="config-help">
|
|
Интервал вывода контрольной информации о работе программы, секунд.
|
|
Каждые N секунд отображаются длительность текущей сессии,
|
|
количество полученных GPS сообщений, последние координаты
|
|
и текущая статистика захвата.
|
|
Сохранение GPS-потока в gps_track.nmea от этого параметра не зависит.
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="configuration-actions">
|
|
|
|
<button
|
|
type="submit"
|
|
class="button start"
|
|
>
|
|
SAVE CONFIGURATION
|
|
</button>
|
|
|
|
<div
|
|
id="configuration-save-status"
|
|
hidden
|
|
></div>
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
{% endif %}
|
|
|
|
</section>
|
|
|
|
|
|
<section class="panel gps-diagnostics-panel">
|
|
|
|
<div class="panel-heading">
|
|
|
|
<div>
|
|
|
|
<div class="panel-kicker">
|
|
GPS
|
|
</div>
|
|
|
|
<h2>
|
|
GPS Diagnostics
|
|
</h2>
|
|
|
|
</div>
|
|
|
|
<span
|
|
id="gps-test-status"
|
|
class="panel-hint"
|
|
>
|
|
GPS READY
|
|
</span>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="gps-diagnostics">
|
|
|
|
<div class="gps-diagnostics-state">
|
|
|
|
<span
|
|
id="gps-test-indicator"
|
|
class="gps-state-indicator ready"
|
|
>
|
|
●
|
|
</span>
|
|
|
|
<div>
|
|
|
|
<strong
|
|
id="gps-test-state"
|
|
>
|
|
Готов к тесту
|
|
</strong>
|
|
|
|
<span
|
|
id="gps-test-message"
|
|
>
|
|
GPS test is ready.
|
|
</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="gps-diagnostics-grid">
|
|
|
|
<div class="gps-diagnostic">
|
|
|
|
<span>
|
|
Source
|
|
</span>
|
|
|
|
<strong id="gps-test-source">
|
|
{{ config.gps.source|upper }}
|
|
</strong>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="gps-diagnostic">
|
|
|
|
<span>
|
|
Handler
|
|
</span>
|
|
|
|
<strong id="gps-test-handler">
|
|
{% if config.gps.source == "gnss" %}
|
|
{{ config.gps.gnss.handler }}
|
|
{% else %}
|
|
GPSRelay
|
|
{% endif %}
|
|
</strong>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="gps-diagnostic">
|
|
|
|
<span>
|
|
NMEA
|
|
</span>
|
|
|
|
<strong id="gps-test-nmea">
|
|
{{ gps_test.nmea_count }}
|
|
</strong>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="gps-diagnostic">
|
|
|
|
<span>
|
|
FIX
|
|
</span>
|
|
|
|
<strong id="gps-test-fix">
|
|
{{ gps_test.fix_count }}
|
|
</strong>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="gps-diagnostic">
|
|
|
|
<span>
|
|
Latitude
|
|
</span>
|
|
|
|
<strong id="gps-test-latitude">
|
|
{% if gps_test.latitude is not none %}
|
|
{{ "%.6f"|format(gps_test.latitude) }}
|
|
{% else %}
|
|
—
|
|
{% endif %}
|
|
</strong>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="gps-diagnostic">
|
|
|
|
<span>
|
|
Longitude
|
|
</span>
|
|
|
|
<strong id="gps-test-longitude">
|
|
{% if gps_test.longitude is not none %}
|
|
{{ "%.6f"|format(gps_test.longitude) }}
|
|
{% else %}
|
|
—
|
|
{% endif %}
|
|
</strong>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="gps-diagnostics-actions">
|
|
|
|
<button
|
|
id="gps-test-button"
|
|
type="button"
|
|
class="button start"
|
|
{% if status.running %}disabled{% endif %}
|
|
>
|
|
TEST GPS
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</section>
|
|
|
|
|
|
<section class="panel">
|
|
|
|
<div class="panel-heading">
|
|
|
|
<div>
|
|
|
|
<div class="panel-kicker">
|
|
RUNTIME
|
|
</div>
|
|
|
|
<h2>
|
|
Scanner Output
|
|
</h2>
|
|
|
|
</div>
|
|
|
|
<span class="panel-hint">
|
|
Live output
|
|
</span>
|
|
|
|
</div>
|
|
|
|
|
|
<pre id="scanner-output">{% for line in output %}{{ line }}
|
|
{% endfor %}</pre>
|
|
|
|
</section>
|
|
|
|
|
|
</main>
|
|
|
|
|
|
<script>
|
|
|
|
let scannerWasRunning = false;
|
|
|
|
|
|
function updateScannerHeader(
|
|
health
|
|
)
|
|
{
|
|
const status =
|
|
document.getElementById(
|
|
"header-scanner-status"
|
|
);
|
|
|
|
const indicator =
|
|
status.querySelector(
|
|
".status-indicator"
|
|
);
|
|
|
|
const text =
|
|
status.querySelector(
|
|
".status-text"
|
|
);
|
|
|
|
|
|
status.className =
|
|
"header-status " +
|
|
health.toLowerCase();
|
|
|
|
indicator.className =
|
|
"status-indicator " +
|
|
health.toLowerCase();
|
|
|
|
|
|
if (health === "ACTIVE")
|
|
{
|
|
text.textContent =
|
|
"SCANNER ACTIVE";
|
|
}
|
|
else if (health === "WARNING")
|
|
{
|
|
text.textContent =
|
|
"WARNING";
|
|
}
|
|
else if (health === "STOPPING")
|
|
{
|
|
text.textContent =
|
|
"SCANNER STOPPING";
|
|
}
|
|
else if (health === "STOPPED")
|
|
{
|
|
text.textContent =
|
|
"SCANNER STOPPED";
|
|
}
|
|
else
|
|
{
|
|
text.textContent =
|
|
"SCANNER READY";
|
|
}
|
|
}
|
|
|
|
function updateGpsDiagnostics(
|
|
gpsTest,
|
|
scannerRunning
|
|
)
|
|
{
|
|
|
|
const status =
|
|
document.getElementById(
|
|
"gps-test-status"
|
|
);
|
|
|
|
const state =
|
|
document.getElementById(
|
|
"gps-test-state"
|
|
);
|
|
|
|
const indicator =
|
|
document.getElementById(
|
|
"gps-test-indicator"
|
|
);
|
|
|
|
const message =
|
|
document.getElementById(
|
|
"gps-test-message"
|
|
);
|
|
|
|
const nmea =
|
|
document.getElementById(
|
|
"gps-test-nmea"
|
|
);
|
|
|
|
const fix =
|
|
document.getElementById(
|
|
"gps-test-fix"
|
|
);
|
|
|
|
const latitude =
|
|
document.getElementById(
|
|
"gps-test-latitude"
|
|
);
|
|
|
|
const longitude =
|
|
document.getElementById(
|
|
"gps-test-longitude"
|
|
);
|
|
|
|
const button =
|
|
document.getElementById(
|
|
"gps-test-button"
|
|
);
|
|
|
|
if (!gpsTest)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
if (button)
|
|
{
|
|
button.textContent =
|
|
gpsTest.running
|
|
? "STOP GPS TEST"
|
|
: "TEST GPS";
|
|
|
|
button.disabled =
|
|
!gpsTest.running
|
|
&&
|
|
scannerRunning;
|
|
}
|
|
|
|
nmea.textContent =
|
|
gpsTest.nmea_count;
|
|
|
|
fix.textContent =
|
|
gpsTest.fix_count;
|
|
|
|
|
|
latitude.textContent =
|
|
gpsTest.latitude !== null
|
|
? Number(gpsTest.latitude).toFixed(6)
|
|
: "—";
|
|
|
|
longitude.textContent =
|
|
gpsTest.longitude !== null
|
|
? Number(gpsTest.longitude).toFixed(6)
|
|
: "—";
|
|
|
|
|
|
if (gpsTest.state === "STARTING")
|
|
{
|
|
status.textContent =
|
|
"GPS CONNECTING";
|
|
|
|
indicator.className =
|
|
"gps-state-indicator connecting";
|
|
|
|
indicator.textContent =
|
|
"●";
|
|
|
|
state.textContent =
|
|
"Подключение к GPS…";
|
|
|
|
message.textContent =
|
|
"Connecting to GPS source.";
|
|
}
|
|
else if (
|
|
gpsTest.state === "CONNECTED"
|
|
)
|
|
{
|
|
status.textContent =
|
|
"GPS CONNECTED";
|
|
|
|
indicator.className =
|
|
"gps-state-indicator connecting";
|
|
|
|
indicator.textContent =
|
|
"●";
|
|
|
|
state.textContent =
|
|
"Подключение к GPS…";
|
|
|
|
message.textContent =
|
|
"GPS source connected. Waiting for NMEA.";
|
|
}
|
|
else if (
|
|
gpsTest.state === "NMEA"
|
|
)
|
|
{
|
|
status.textContent =
|
|
"WAITING FOR FIX";
|
|
|
|
indicator.className =
|
|
"gps-state-indicator nmea";
|
|
|
|
indicator.textContent =
|
|
"●";
|
|
|
|
state.textContent =
|
|
"NMEA получен, FIX ещё нет";
|
|
|
|
message.textContent =
|
|
"NMEA data received, but no valid coordinates yet.";
|
|
}
|
|
else if (
|
|
gpsTest.state === "FIX"
|
|
)
|
|
{
|
|
status.textContent =
|
|
"GPS READY";
|
|
|
|
indicator.className =
|
|
"gps-state-indicator fix";
|
|
|
|
indicator.textContent =
|
|
"●";
|
|
|
|
state.textContent =
|
|
"GPS готов — FIX получен";
|
|
|
|
message.textContent =
|
|
"Valid GPS coordinates received.";
|
|
}
|
|
else if (
|
|
gpsTest.state === "ERROR"
|
|
)
|
|
{
|
|
status.textContent =
|
|
"GPS ERROR";
|
|
|
|
indicator.className =
|
|
"gps-state-indicator error";
|
|
|
|
indicator.textContent =
|
|
"●";
|
|
|
|
state.textContent =
|
|
"Ошибка подключения / источника";
|
|
|
|
message.textContent =
|
|
gpsTest.error ||
|
|
"Unable to connect to GPS source.";
|
|
}
|
|
else if (
|
|
gpsTest.state === "STOPPED"
|
|
)
|
|
{
|
|
status.textContent =
|
|
"GPS STOPPED";
|
|
|
|
indicator.className =
|
|
"gps-state-indicator stopped";
|
|
|
|
indicator.textContent =
|
|
"●";
|
|
|
|
state.textContent =
|
|
"Тест остановлен";
|
|
|
|
message.textContent =
|
|
"GPS test stopped.";
|
|
}
|
|
else
|
|
{
|
|
status.textContent =
|
|
"GPS READY";
|
|
|
|
indicator.className =
|
|
"gps-state-indicator ready";
|
|
|
|
indicator.textContent =
|
|
"●";
|
|
|
|
state.textContent =
|
|
"Готов к тесту";
|
|
|
|
message.textContent =
|
|
"GPS test is ready.";
|
|
}
|
|
|
|
}
|
|
|
|
async function updateStatus()
|
|
{
|
|
try
|
|
{
|
|
const response =
|
|
await fetch(
|
|
"{{ url_for('api_status') }}"
|
|
);
|
|
|
|
const data =
|
|
await response.json();
|
|
|
|
updateGpsDiagnostics(
|
|
data.gps_test,
|
|
data.status.running
|
|
);
|
|
|
|
updateScannerHeader(
|
|
data.status.health
|
|
);
|
|
|
|
const scannerState =
|
|
document.querySelector(
|
|
".scanner-state"
|
|
);
|
|
|
|
if (data.status.running)
|
|
{
|
|
scannerState.innerHTML =
|
|
"<strong class=\"running\">" +
|
|
"RUNNING" +
|
|
"</strong>" +
|
|
"<span>PID " +
|
|
data.status.pid +
|
|
"</span>";
|
|
}
|
|
else
|
|
{
|
|
scannerState.innerHTML =
|
|
"<strong class=\"stopped\">" +
|
|
"STOPPED" +
|
|
"</strong>";
|
|
}
|
|
|
|
|
|
const output =
|
|
document.getElementById(
|
|
"scanner-output"
|
|
);
|
|
|
|
|
|
const scannerStopping =
|
|
data.status.health === "STOPPING";
|
|
|
|
const interfaceSelect =
|
|
document.getElementById(
|
|
"interface"
|
|
);
|
|
|
|
const startButtons =
|
|
document.querySelectorAll(
|
|
'button.start[form="interface-form"], ' +
|
|
'#interface-form button.start'
|
|
);
|
|
|
|
const stopButtons =
|
|
document.querySelectorAll(
|
|
'form[action="{{ url_for("scanner_stop") }}"] button.stop'
|
|
);
|
|
|
|
interfaceSelect.disabled =
|
|
data.status.running ||
|
|
data.gps_test.running;
|
|
|
|
startButtons.forEach(
|
|
function(button)
|
|
{
|
|
button.disabled =
|
|
data.status.running ||
|
|
data.gps_test.running;
|
|
}
|
|
);
|
|
|
|
stopButtons.forEach(
|
|
function(button)
|
|
{
|
|
button.disabled =
|
|
!data.status.running ||
|
|
scannerStopping;
|
|
}
|
|
);
|
|
|
|
output.textContent =
|
|
data.output.join("\n");
|
|
|
|
if (data.status.running)
|
|
{
|
|
scannerWasRunning = true;
|
|
|
|
output.scrollTop =
|
|
output.scrollHeight;
|
|
}
|
|
else if (scannerWasRunning)
|
|
{
|
|
scannerWasRunning = false;
|
|
}
|
|
|
|
}
|
|
catch (error)
|
|
{
|
|
console.error(error);
|
|
}
|
|
}
|
|
|
|
|
|
setInterval(
|
|
updateStatus,
|
|
1000
|
|
);
|
|
|
|
const WIFI_BANDS = [
|
|
"2.4",
|
|
"5",
|
|
"6"
|
|
];
|
|
|
|
|
|
const wifiChannelState = {
|
|
"2.4": new Set(),
|
|
"5": new Set(),
|
|
"6": new Set()
|
|
};
|
|
|
|
|
|
function getWifiChannelSuffix(band)
|
|
{
|
|
if (band === "2.4")
|
|
{
|
|
return "a";
|
|
}
|
|
|
|
if (band === "5")
|
|
{
|
|
return "b";
|
|
}
|
|
|
|
return "c";
|
|
}
|
|
|
|
|
|
function getWifiChannelContainer(band)
|
|
{
|
|
return document.getElementById(
|
|
band === "2.4"
|
|
? "wifi-channels-2-4"
|
|
: `wifi-channels-${band}`
|
|
);
|
|
}
|
|
|
|
|
|
function renderWifiChannels(
|
|
band,
|
|
availableChannels
|
|
)
|
|
{
|
|
const container =
|
|
getWifiChannelContainer(band);
|
|
|
|
if (!container)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
container.innerHTML = "";
|
|
|
|
|
|
const channels = [
|
|
...new Set(
|
|
availableChannels.map(
|
|
item => item.channel
|
|
)
|
|
)
|
|
].sort(
|
|
(a, b) => a - b
|
|
);
|
|
|
|
|
|
const available = new Set(
|
|
availableChannels
|
|
.filter(
|
|
item => item.available === true
|
|
)
|
|
.map(
|
|
item => item.channel
|
|
)
|
|
);
|
|
|
|
|
|
for (const channel of channels)
|
|
{
|
|
const availableNow =
|
|
available.has(channel);
|
|
|
|
|
|
const suffix =
|
|
getWifiChannelSuffix(band);
|
|
|
|
|
|
const value =
|
|
`${channel}${suffix}`;
|
|
|
|
|
|
if (!availableNow)
|
|
{
|
|
wifiChannelState[band].delete(
|
|
value
|
|
);
|
|
}
|
|
|
|
|
|
const label =
|
|
document.createElement("label");
|
|
|
|
|
|
label.className =
|
|
"wifi-channel";
|
|
|
|
|
|
if (!availableNow)
|
|
{
|
|
label.classList.add(
|
|
"unavailable"
|
|
);
|
|
}
|
|
|
|
|
|
const checkbox =
|
|
document.createElement("input");
|
|
|
|
|
|
checkbox.type = "checkbox";
|
|
|
|
|
|
checkbox.value = value;
|
|
|
|
|
|
checkbox.dataset.band = band;
|
|
|
|
|
|
checkbox.disabled =
|
|
!availableNow;
|
|
|
|
|
|
checkbox.checked =
|
|
availableNow &&
|
|
wifiChannelState[band].has(
|
|
value
|
|
);
|
|
|
|
|
|
checkbox.addEventListener(
|
|
"change",
|
|
function()
|
|
{
|
|
const toggle =
|
|
document.getElementById(
|
|
band === "2.4"
|
|
? "wifi-enable-2-4ghz"
|
|
: band === "5"
|
|
? "wifi-enable-5ghz"
|
|
: "wifi-enable-6ghz"
|
|
);
|
|
|
|
if (this.checked)
|
|
{
|
|
wifiChannelState[
|
|
band
|
|
].add(
|
|
value
|
|
);
|
|
|
|
if (
|
|
toggle &&
|
|
!toggle.disabled
|
|
)
|
|
{
|
|
toggle.checked =
|
|
true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
wifiChannelState[
|
|
band
|
|
].delete(
|
|
value
|
|
);
|
|
|
|
if (
|
|
toggle &&
|
|
wifiChannelState[
|
|
band
|
|
].size === 0
|
|
)
|
|
{
|
|
toggle.checked =
|
|
false;
|
|
}
|
|
}
|
|
|
|
updateWifiChannelsValue();
|
|
}
|
|
);
|
|
|
|
const text =
|
|
document.createElement("span");
|
|
|
|
|
|
text.textContent =
|
|
availableNow
|
|
? channel
|
|
: `${channel} (недоступен)`;
|
|
|
|
|
|
label.appendChild(
|
|
checkbox
|
|
);
|
|
|
|
|
|
label.appendChild(
|
|
text
|
|
);
|
|
|
|
|
|
container.appendChild(
|
|
label
|
|
);
|
|
}
|
|
|
|
|
|
updateWifiBandAvailability(
|
|
band,
|
|
availableChannels.some(
|
|
item => item.available === true
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
function updateWifiBandAvailability(
|
|
band,
|
|
available
|
|
)
|
|
{
|
|
const buttons =
|
|
document.querySelectorAll(
|
|
`.channel-action[data-band="${band}"]`
|
|
);
|
|
|
|
|
|
buttons.forEach(
|
|
function(button)
|
|
{
|
|
button.disabled =
|
|
!available;
|
|
}
|
|
);
|
|
|
|
|
|
const container =
|
|
getWifiChannelContainer(
|
|
band
|
|
);
|
|
|
|
|
|
if (container)
|
|
{
|
|
container.classList.toggle(
|
|
"band-unavailable",
|
|
!available
|
|
);
|
|
}
|
|
|
|
|
|
const toggle =
|
|
document.getElementById(
|
|
band === "2.4"
|
|
? "wifi-enable-2-4ghz"
|
|
: band === "5"
|
|
? "wifi-enable-5ghz"
|
|
: "wifi-enable-6ghz"
|
|
);
|
|
|
|
|
|
if (!toggle)
|
|
{
|
|
return;
|
|
}
|
|
|
|
toggle.disabled =
|
|
!available;
|
|
|
|
if (!available)
|
|
{
|
|
toggle.checked =
|
|
false;
|
|
|
|
setWifiBandSelection(
|
|
band,
|
|
false
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
if (wifiChannelState[band].size > 0)
|
|
{
|
|
toggle.checked =
|
|
true;
|
|
}
|
|
}
|
|
|
|
|
|
function updateWifiChannelsValue()
|
|
{
|
|
const values = [];
|
|
|
|
|
|
[
|
|
"2.4",
|
|
"5",
|
|
"6"
|
|
].forEach(
|
|
function(band)
|
|
{
|
|
for (
|
|
const value
|
|
of wifiChannelState[band]
|
|
)
|
|
{
|
|
values.push(value);
|
|
}
|
|
}
|
|
);
|
|
|
|
|
|
const order = {
|
|
"a": 0,
|
|
"b": 1,
|
|
"c": 2
|
|
};
|
|
|
|
|
|
values.sort(
|
|
function(a, b)
|
|
{
|
|
const channelA =
|
|
parseInt(a, 10);
|
|
|
|
const channelB =
|
|
parseInt(b, 10);
|
|
|
|
|
|
if (
|
|
channelA !==
|
|
channelB
|
|
)
|
|
{
|
|
return (
|
|
channelA -
|
|
channelB
|
|
);
|
|
}
|
|
|
|
|
|
return (
|
|
order[
|
|
a.slice(-1)
|
|
] -
|
|
order[
|
|
b.slice(-1)
|
|
]
|
|
);
|
|
}
|
|
);
|
|
|
|
|
|
document.getElementById(
|
|
"wifi-channels"
|
|
).value =
|
|
values.join(",");
|
|
}
|
|
|
|
|
|
function restoreWifiChannelState(
|
|
configuredChannels
|
|
)
|
|
{
|
|
[
|
|
"2.4",
|
|
"5",
|
|
"6"
|
|
].forEach(
|
|
function(band)
|
|
{
|
|
wifiChannelState[band].clear();
|
|
}
|
|
);
|
|
|
|
|
|
const configuredValues = [
|
|
configuredChannels["2.4"] || "",
|
|
configuredChannels["5"] || "",
|
|
configuredChannels["6"] || ""
|
|
];
|
|
|
|
|
|
for (
|
|
const value
|
|
of configuredValues
|
|
.join(",")
|
|
.split(",")
|
|
.map(
|
|
item => item.trim()
|
|
)
|
|
.filter(Boolean)
|
|
)
|
|
{
|
|
if (value.endsWith("a"))
|
|
{
|
|
wifiChannelState[
|
|
"2.4"
|
|
].add(value);
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
if (value.endsWith("b"))
|
|
{
|
|
wifiChannelState[
|
|
"5"
|
|
].add(value);
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
if (value.endsWith("c"))
|
|
{
|
|
wifiChannelState[
|
|
"6"
|
|
].add(value);
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
const channel =
|
|
parseInt(value, 10);
|
|
|
|
|
|
if (
|
|
channel >= 1 &&
|
|
channel <= 13
|
|
)
|
|
{
|
|
wifiChannelState[
|
|
"2.4"
|
|
].add(
|
|
`${channel}a`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
async function detectWifiChannels(
|
|
interfaceName
|
|
)
|
|
{
|
|
const status =
|
|
document.getElementById(
|
|
"wifi-diagnostics-status"
|
|
);
|
|
|
|
|
|
const interfaceLabel =
|
|
document.getElementById(
|
|
"wifi-diagnostics-interface"
|
|
);
|
|
|
|
|
|
const error =
|
|
document.getElementById(
|
|
"wifi-channel-error"
|
|
);
|
|
|
|
|
|
interfaceLabel.textContent =
|
|
interfaceName || "not selected";
|
|
|
|
|
|
error.hidden = true;
|
|
|
|
|
|
if (!interfaceName)
|
|
{
|
|
status.textContent =
|
|
"Select interface to detect channels";
|
|
|
|
|
|
[
|
|
"2.4",
|
|
"5",
|
|
"6"
|
|
].forEach(
|
|
function(band)
|
|
{
|
|
renderWifiChannels(
|
|
band,
|
|
[]
|
|
);
|
|
}
|
|
);
|
|
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
try
|
|
{
|
|
const response =
|
|
await fetch(
|
|
"{{ url_for('api_wifi_channels') }}"
|
|
+
|
|
"?interface="
|
|
+
|
|
encodeURIComponent(
|
|
interfaceName
|
|
)
|
|
);
|
|
|
|
|
|
const data =
|
|
await response.json();
|
|
|
|
|
|
if (!response.ok)
|
|
{
|
|
throw new Error(
|
|
data.error
|
|
||
|
|
"Channel detection failed."
|
|
);
|
|
}
|
|
|
|
|
|
renderWifiChannels(
|
|
"2.4",
|
|
data["2.4"] || []
|
|
);
|
|
|
|
|
|
renderWifiChannels(
|
|
"5",
|
|
data["5"] || []
|
|
);
|
|
|
|
|
|
renderWifiChannels(
|
|
"6",
|
|
data["6"] || []
|
|
);
|
|
|
|
|
|
const count =
|
|
(data["2.4"] || []).filter(
|
|
item => item.available === true
|
|
).length
|
|
+
|
|
(data["5"] || []).filter(
|
|
item => item.available === true
|
|
).length
|
|
+
|
|
(data["6"] || []).filter(
|
|
item => item.available === true
|
|
).length;
|
|
|
|
|
|
status.textContent =
|
|
`${count} available channels detected`;
|
|
|
|
|
|
if (data.error)
|
|
{
|
|
throw new Error(
|
|
data.error
|
|
);
|
|
}
|
|
|
|
|
|
updateWifiChannelsValue();
|
|
}
|
|
catch (errorValue)
|
|
{
|
|
status.textContent =
|
|
"Channel detection failed.";
|
|
|
|
|
|
error.textContent =
|
|
errorValue.message;
|
|
|
|
|
|
error.hidden = false;
|
|
}
|
|
}
|
|
|
|
|
|
function setWifiBandSelection(
|
|
band,
|
|
selected
|
|
)
|
|
{
|
|
const container =
|
|
getWifiChannelContainer(
|
|
band
|
|
);
|
|
|
|
if (!container)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
const toggle =
|
|
document.getElementById(
|
|
band === "2.4"
|
|
? "wifi-enable-2-4ghz"
|
|
: band === "5"
|
|
? "wifi-enable-5ghz"
|
|
: "wifi-enable-6ghz"
|
|
);
|
|
|
|
|
|
if (
|
|
toggle &&
|
|
!toggle.disabled
|
|
)
|
|
{
|
|
toggle.checked =
|
|
selected;
|
|
}
|
|
|
|
|
|
container
|
|
.querySelectorAll(
|
|
"input[type='checkbox']"
|
|
)
|
|
.forEach(
|
|
function(checkbox)
|
|
{
|
|
if (
|
|
checkbox.disabled
|
|
)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
checkbox.checked =
|
|
selected;
|
|
|
|
|
|
if (selected)
|
|
{
|
|
wifiChannelState[
|
|
band
|
|
].add(
|
|
checkbox.value
|
|
);
|
|
}
|
|
else
|
|
{
|
|
wifiChannelState[
|
|
band
|
|
].delete(
|
|
checkbox.value
|
|
);
|
|
}
|
|
}
|
|
);
|
|
|
|
|
|
updateWifiChannelsValue();
|
|
}
|
|
|
|
document
|
|
.querySelectorAll(
|
|
".channel-action"
|
|
)
|
|
.forEach(
|
|
function(button)
|
|
{
|
|
button.addEventListener(
|
|
"click",
|
|
function()
|
|
{
|
|
const band =
|
|
this.dataset.band;
|
|
|
|
|
|
const clear =
|
|
this.dataset.action
|
|
===
|
|
"clear";
|
|
|
|
|
|
setWifiBandSelection(
|
|
band,
|
|
!clear
|
|
);
|
|
}
|
|
);
|
|
}
|
|
);
|
|
|
|
|
|
const interfaceSelect =
|
|
document.getElementById(
|
|
"interface"
|
|
);
|
|
|
|
|
|
if (interfaceSelect)
|
|
{
|
|
interfaceSelect.addEventListener(
|
|
"change",
|
|
function()
|
|
{
|
|
detectWifiChannels(
|
|
this.value
|
|
);
|
|
}
|
|
);
|
|
}
|
|
|
|
const scannerStartForm =
|
|
document.querySelector(
|
|
".interface-form"
|
|
);
|
|
|
|
if (scannerStartForm)
|
|
{
|
|
scannerStartForm.addEventListener(
|
|
"submit",
|
|
function(event)
|
|
{
|
|
const hasSelectedChannel =
|
|
WIFI_BANDS.some(
|
|
function(band)
|
|
{
|
|
return (
|
|
wifiChannelState[
|
|
band
|
|
].size > 0
|
|
);
|
|
}
|
|
);
|
|
|
|
if (hasSelectedChannel)
|
|
{
|
|
return;
|
|
}
|
|
|
|
event.preventDefault();
|
|
|
|
let error =
|
|
document.querySelector(
|
|
".interface-form .error"
|
|
);
|
|
|
|
if (!error)
|
|
{
|
|
error =
|
|
document.createElement(
|
|
"div"
|
|
);
|
|
|
|
error.className =
|
|
"error";
|
|
|
|
scannerStartForm
|
|
.querySelector(
|
|
".field"
|
|
)
|
|
.after(error);
|
|
}
|
|
|
|
error.textContent =
|
|
"Select at least one available WiFi channel before starting the scan.";
|
|
|
|
error.hidden =
|
|
false;
|
|
}
|
|
);
|
|
}
|
|
|
|
[
|
|
[
|
|
"2.4",
|
|
"wifi-enable-2-4ghz",
|
|
{{ config.wifi.enable_2_4ghz | tojson }}
|
|
],
|
|
[
|
|
"5",
|
|
"wifi-enable-5ghz",
|
|
{{ config.wifi.enable_5ghz | tojson }}
|
|
],
|
|
[
|
|
"6",
|
|
"wifi-enable-6ghz",
|
|
{{ config.wifi.enable_6ghz | tojson }}
|
|
]
|
|
].forEach(
|
|
function(entry)
|
|
{
|
|
const band =
|
|
entry[0];
|
|
|
|
const toggle =
|
|
document.getElementById(
|
|
entry[1]
|
|
);
|
|
|
|
if (!toggle)
|
|
{
|
|
return;
|
|
}
|
|
|
|
toggle.checked =
|
|
entry[2] === true ||
|
|
wifiChannelState[band].size > 0;
|
|
|
|
toggle.addEventListener(
|
|
"change",
|
|
function()
|
|
{
|
|
if (!this.checked)
|
|
{
|
|
setWifiBandSelection(
|
|
band,
|
|
false
|
|
);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
);
|
|
|
|
|
|
restoreWifiChannelState(
|
|
{
|
|
"2.4":
|
|
{{ config.wifi.channels_2_4ghz | tojson }},
|
|
|
|
"5":
|
|
{{ config.wifi.channels_5ghz | tojson }},
|
|
|
|
"6":
|
|
{{ config.wifi.channels_6ghz | tojson }}
|
|
}
|
|
);
|
|
|
|
if (interfaceSelect)
|
|
{
|
|
detectWifiChannels(
|
|
interfaceSelect.value
|
|
);
|
|
}
|
|
|
|
const configurationForm =
|
|
document.getElementById(
|
|
"configuration-form"
|
|
);
|
|
|
|
|
|
const gpsSource =
|
|
document.getElementById(
|
|
"gps-source"
|
|
);
|
|
|
|
const gpsrelaySettings =
|
|
document.getElementById(
|
|
"gpsrelay-settings"
|
|
);
|
|
|
|
const gnssSettings =
|
|
document.getElementById(
|
|
"gnss-settings"
|
|
);
|
|
|
|
const gnssDirectDeviceField =
|
|
document.getElementById(
|
|
"gnss-direct-device-field"
|
|
);
|
|
|
|
const gnssDirectDevice =
|
|
document.getElementById(
|
|
"gnss-direct-device"
|
|
);
|
|
|
|
const gnssModemDeviceField =
|
|
document.getElementById(
|
|
"gnss-modem-device-field"
|
|
);
|
|
|
|
const gnssModemDevice =
|
|
document.getElementById(
|
|
"gnss-modem-device"
|
|
);
|
|
|
|
const gnssAtDeviceField =
|
|
document.getElementById(
|
|
"gnss-at-device-field"
|
|
);
|
|
|
|
const gnssAtDevice =
|
|
document.getElementById(
|
|
"gnss-at-device"
|
|
);
|
|
|
|
const gnssHandler =
|
|
document.getElementById(
|
|
"gnss-handler"
|
|
);
|
|
|
|
|
|
if (gpsSource)
|
|
{
|
|
|
|
gpsSource.addEventListener(
|
|
"change",
|
|
function()
|
|
{
|
|
|
|
const isGpsRelay =
|
|
gpsSource.value === "gpsrelay";
|
|
|
|
if (gpsrelaySettings)
|
|
{
|
|
gpsrelaySettings.hidden =
|
|
!isGpsRelay;
|
|
}
|
|
|
|
if (gnssSettings)
|
|
{
|
|
gnssSettings.hidden =
|
|
isGpsRelay;
|
|
}
|
|
|
|
}
|
|
);
|
|
|
|
}
|
|
|
|
|
|
function updateGnssHandlerSettings()
|
|
{
|
|
|
|
if (!gnssHandler)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const isDirect =
|
|
gnssHandler.value === "direct";
|
|
|
|
const isModem =
|
|
gnssHandler.value === "modem";
|
|
|
|
if (gnssDirectDeviceField)
|
|
{
|
|
gnssDirectDeviceField.hidden =
|
|
!isDirect;
|
|
}
|
|
|
|
if (gnssModemDeviceField)
|
|
{
|
|
gnssModemDeviceField.hidden =
|
|
!isModem;
|
|
}
|
|
|
|
if (gnssAtDeviceField)
|
|
{
|
|
gnssAtDeviceField.hidden =
|
|
!isModem;
|
|
}
|
|
|
|
if (gnssDirectDevice)
|
|
{
|
|
gnssDirectDevice.required =
|
|
isDirect;
|
|
}
|
|
|
|
if (gnssModemDevice)
|
|
{
|
|
gnssModemDevice.required =
|
|
isModem;
|
|
}
|
|
|
|
if (gnssAtDevice)
|
|
{
|
|
gnssAtDevice.required =
|
|
isModem;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
if (gnssHandler)
|
|
{
|
|
|
|
gnssHandler.addEventListener(
|
|
"change",
|
|
updateGnssHandlerSettings
|
|
);
|
|
|
|
updateGnssHandlerSettings();
|
|
|
|
}
|
|
|
|
|
|
if (configurationForm)
|
|
{
|
|
|
|
configurationForm.addEventListener(
|
|
"submit",
|
|
async function(event)
|
|
{
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
const configuration = {
|
|
|
|
gps: {
|
|
|
|
source:
|
|
document.getElementById(
|
|
"gps-source"
|
|
).value,
|
|
|
|
gpsrelay: {
|
|
|
|
host:
|
|
document.getElementById(
|
|
"gps-host"
|
|
).value.trim(),
|
|
|
|
port:
|
|
Number(
|
|
document.getElementById(
|
|
"gps-port"
|
|
).value
|
|
)
|
|
|
|
},
|
|
|
|
gnss: {
|
|
|
|
handler:
|
|
document.getElementById(
|
|
"gnss-handler"
|
|
).value,
|
|
|
|
direct: {
|
|
|
|
device:
|
|
document.getElementById(
|
|
"gnss-direct-device"
|
|
).value.trim()
|
|
|
|
},
|
|
|
|
gpsd: {},
|
|
|
|
modem: {
|
|
|
|
device:
|
|
document.getElementById(
|
|
"gnss-modem-device"
|
|
).value.trim(),
|
|
|
|
at_device:
|
|
document.getElementById(
|
|
"gnss-at-device"
|
|
).value.trim()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
timezone:
|
|
document.getElementById(
|
|
"timezone"
|
|
).value.trim(),
|
|
|
|
|
|
database: {
|
|
|
|
file:
|
|
{{ config.database.file | tojson }}
|
|
|
|
},
|
|
|
|
|
|
vendor: {
|
|
|
|
database:
|
|
{{ config.vendor.database | tojson }},
|
|
|
|
sources: {
|
|
|
|
oui:
|
|
{{ config.vendor.sources.oui | tojson }},
|
|
|
|
mam:
|
|
{{ config.vendor.sources.mam | tojson }},
|
|
|
|
oui36:
|
|
{{ config.vendor.sources.oui36 | tojson }}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
wifi: {
|
|
|
|
enable_2_4ghz:
|
|
document.getElementById(
|
|
"wifi-enable-2-4ghz"
|
|
).checked,
|
|
|
|
channels_2_4ghz:
|
|
Array.from(
|
|
wifiChannelState["2.4"]
|
|
).join(","),
|
|
|
|
enable_5ghz:
|
|
document.getElementById(
|
|
"wifi-enable-5ghz"
|
|
).checked,
|
|
|
|
channels_5ghz:
|
|
Array.from(
|
|
wifiChannelState["5"]
|
|
).join(","),
|
|
|
|
enable_6ghz:
|
|
document.getElementById(
|
|
"wifi-enable-6ghz"
|
|
).checked,
|
|
|
|
channels_6ghz:
|
|
Array.from(
|
|
wifiChannelState["6"]
|
|
).join(","),
|
|
|
|
filtermode:
|
|
Number(
|
|
{{ config.wifi.filtermode | tojson }}
|
|
),
|
|
|
|
extra_args:
|
|
document.getElementById(
|
|
"wifi-extra-args"
|
|
).value.trim()
|
|
|
|
},
|
|
|
|
|
|
captures: {
|
|
|
|
directory:
|
|
{{ config.captures.directory | tojson }}
|
|
|
|
},
|
|
|
|
|
|
reports: {
|
|
|
|
recent_sessions_limit:
|
|
Number(
|
|
{{ config.reports.recent_sessions_limit | tojson }}
|
|
)
|
|
|
|
},
|
|
|
|
|
|
logging: {
|
|
|
|
level:
|
|
document.getElementById(
|
|
"logging-level"
|
|
).value
|
|
|
|
},
|
|
|
|
|
|
status: {
|
|
|
|
interval:
|
|
Number(
|
|
document.getElementById(
|
|
"status-interval"
|
|
).value
|
|
)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
const saveStatus =
|
|
document.getElementById(
|
|
"configuration-save-status"
|
|
);
|
|
|
|
const saveButton =
|
|
configurationForm.querySelector(
|
|
'button[type="submit"]'
|
|
);
|
|
|
|
|
|
try
|
|
{
|
|
if (saveStatus)
|
|
{
|
|
saveStatus.hidden = true;
|
|
}
|
|
|
|
|
|
if (saveButton)
|
|
{
|
|
saveButton.disabled = true;
|
|
saveButton.textContent = "SAVING...";
|
|
}
|
|
|
|
|
|
const response =
|
|
await fetch(
|
|
"{{ url_for('config_save') }}",
|
|
{
|
|
method: "POST",
|
|
|
|
headers: {
|
|
"Content-Type":
|
|
"application/json"
|
|
},
|
|
|
|
body:
|
|
JSON.stringify(
|
|
configuration
|
|
)
|
|
}
|
|
);
|
|
|
|
|
|
if (response.ok)
|
|
{
|
|
const result =
|
|
await response.json();
|
|
|
|
if (saveStatus)
|
|
{
|
|
saveStatus.textContent =
|
|
"✓ CONFIGURATION SAVED";
|
|
|
|
saveStatus.hidden = false;
|
|
}
|
|
|
|
setTimeout(
|
|
function()
|
|
{
|
|
window.location.reload();
|
|
},
|
|
1500
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
let errorMessage =
|
|
"Configuration save failed.";
|
|
|
|
|
|
try
|
|
{
|
|
const result =
|
|
await response.json();
|
|
|
|
if (result.error)
|
|
{
|
|
errorMessage =
|
|
result.error;
|
|
}
|
|
}
|
|
catch (error)
|
|
{
|
|
// Keep default error message.
|
|
}
|
|
|
|
|
|
if (saveStatus)
|
|
{
|
|
saveStatus.textContent =
|
|
"✗ " + errorMessage;
|
|
|
|
saveStatus.hidden = false;
|
|
}
|
|
|
|
|
|
if (saveButton)
|
|
{
|
|
saveButton.disabled = false;
|
|
saveButton.textContent =
|
|
"SAVE CONFIGURATION";
|
|
}
|
|
}
|
|
catch (error)
|
|
{
|
|
console.error(
|
|
"Configuration save error:",
|
|
error
|
|
);
|
|
|
|
|
|
if (saveStatus)
|
|
{
|
|
saveStatus.textContent =
|
|
"✗ " + error.message;
|
|
|
|
saveStatus.hidden = false;
|
|
}
|
|
|
|
|
|
if (saveButton)
|
|
{
|
|
saveButton.disabled = false;
|
|
saveButton.textContent =
|
|
"SAVE CONFIGURATION";
|
|
}
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
|
|
const gpsTestButton =
|
|
document.getElementById(
|
|
"gps-test-button"
|
|
);
|
|
|
|
|
|
if (gpsTestButton)
|
|
{
|
|
|
|
gpsTestButton.addEventListener(
|
|
"click",
|
|
async function()
|
|
{
|
|
|
|
if (gpsTestButton.disabled)
|
|
{
|
|
return;
|
|
}
|
|
|
|
gpsTestButton.disabled = true;
|
|
|
|
try
|
|
{
|
|
|
|
const gpsTest =
|
|
await fetch(
|
|
"{{ url_for('api_gps_test') }}"
|
|
);
|
|
|
|
const gpsTestData =
|
|
await gpsTest.json();
|
|
|
|
const endpoint =
|
|
gpsTestData.running
|
|
? "{{ url_for('gps_test_stop') }}"
|
|
: "{{ url_for('gps_test_start') }}";
|
|
|
|
|
|
const response =
|
|
await fetch(
|
|
endpoint,
|
|
{
|
|
method: "POST"
|
|
}
|
|
);
|
|
|
|
const data =
|
|
await response.json();
|
|
|
|
|
|
if (!response.ok)
|
|
{
|
|
throw new Error(
|
|
data.error
|
|
||
|
|
"GPS test request failed."
|
|
);
|
|
}
|
|
|
|
updateStatus();
|
|
|
|
}
|
|
catch (error)
|
|
{
|
|
|
|
console.error(
|
|
"GPS test error:",
|
|
error
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
);
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html>
|