Files
LoraMapTester/server/static/index.html
T
2026-06-24 07:59:47 +03:00

256 lines
14 KiB
HTML
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.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>LoraTester</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<link rel="stylesheet" href="/static/app.css" />
</head>
<body>
<header>
<h1>LoraTester</h1>
<span class="muted" id="status">загрузка…</span>
<span class="muted" id="pollStatus" title="Автообновление">⟳ 1 с</span>
<span class="legend"><span class="legend-tx">● TX</span> &nbsp; <span class="legend-rx">● RX</span></span>
<span id="gpsDistance" class="muted">GPS: —</span>
<input type="text" id="webDeviceId" placeholder="ник в чате" style="padding:6px;border-radius:4px;border:none;max-width:160px" />
</header>
<main>
<div id="mapWrap">
<div id="mapCenterBar">
<button type="button" id="btnMapTx">TX</button>
<button type="button" id="btnMapRx">RX</button>
<button type="button" id="btnMapBoth">Оба</button>
<button type="button" id="btnMapRuler">Линейка</button>
<button type="button" id="btnApiDebug">API</button>
</div>
<div id="apiDebugPanel">
<div id="apiDebugHead">
<span>RF API · расчёты</span>
<button type="button" id="btnApiDebugClose" aria-label="Закрыть">×</button>
</div>
<label>API base URL
<input type="text" id="apiDebugBase" />
</label>
<div class="api-debug-help">По умолчанию запросы идут через серверный proxy <b>/rf-api</b>, не напрямую из браузера на порт 5603.</div>
<div class="api-debug-grid">
<label>TX lat<input type="number" id="apiDebugTxLat" step="0.000001" /></label>
<label>TX lon<input type="number" id="apiDebugTxLon" step="0.000001" /></label>
<label>RX lat<input type="number" id="apiDebugRxLat" step="0.000001" /></label>
<label>RX lon<input type="number" id="apiDebugRxLon" step="0.000001" /></label>
<label>TX height, m<input type="number" id="apiDebugTxH" step="1" value="30" /></label>
<label>RX height, m<input type="number" id="apiDebugRxH" step="1" value="2" /></label>
<label>Frequency, MHz<input type="number" id="apiDebugFreq" step="1" value="433" /></label>
<label>Samples<input type="number" id="apiDebugSamples" min="2" max="2048" value="128" /></label>
<label>Модель budget
<select id="apiDebugModel">
<option value="manual" selected>manual</option>
<option value="itm">itm</option>
<option value="p452">p452</option>
</select>
</label>
<label class="api-debug-check"><input type="checkbox" id="apiDebugIncludeCanopy" />canopy в профиле</label>
</div>
<div class="api-debug-section">
<div class="api-debug-section-title">1. Выбор точек</div>
<div class="api-debug-help">Для линии нужны две точки: TX и RX. Самый простой способ — нажать “Два клика TX→RX”, затем кликнуть на карте первую и вторую точку.</div>
<div class="api-debug-actions">
<button type="button" id="btnApiPickTx">Клик: TX</button>
<button type="button" id="btnApiPickRx">Клик: RX</button>
<button type="button" id="btnApiPickLink">Два клика TX→RX</button>
<button type="button" id="btnApiDebugUseRuler">A/B линейки</button>
<button type="button" id="btnApiDebugUseMap">TX/RX устройств</button>
<button type="button" id="btnApiClearPoints">Очистить точки</button>
</div>
</div>
<div class="api-debug-section">
<div class="api-debug-section-title">2. Расчёт</div>
<div class="api-debug-actions">
<button type="button" id="btnApiDebugLine">Расчёт линии</button>
<button type="button" data-api-demo="elevation">Высота TX</button>
<button type="button" data-api-demo="profile">Только профиль</button>
<button type="button" data-api-demo="los">Только LOS/Френель</button>
<button type="button" data-api-demo="budget">Только link budget</button>
<button type="button" data-api-demo="landcover">Landcover path</button>
<button type="button" data-api-demo="beam">Сектор антенны</button>
</div>
</div>
<div class="api-debug-section">
<div class="api-debug-section-title">3. Async jobs (Celery)</div>
<div class="api-debug-help">Viewshed и coverage ставятся в очередь; UI опрашивает <code>/api/v1/jobs/{id}</code> до статуса done/error.</div>
<div class="api-debug-actions">
<button type="button" data-api-demo="viewshed">Viewshed</button>
<button type="button" data-api-demo="coverage">Coverage</button>
</div>
</div>
<div class="api-debug-section">
<div class="api-debug-section-title">4. Диагностика API</div>
<div class="api-debug-actions">
<button type="button" data-api-demo="health">Health</button>
<button type="button" data-api-demo="data">Data status</button>
<button type="button" data-api-demo="openapi">OpenAPI paths</button>
</div>
</div>
<div id="apiDebugStatus">Готово</div>
<div class="api-debug-mini">“Расчёт линии” — profile + LOS + link budget (manual/ITM/P.452). Landcover — если растр есть. Coverage/viewshed — async через Celery.</div>
<canvas id="apiDebugChart" width="800" height="140"></canvas>
<div class="api-debug-legend">
<span><i class="api-debug-swatch" style="background:#00ff88"></i> поверхность/препятствия</span>
<span><i class="api-debug-swatch" style="background:#ff9800"></i> луч TX→RX</span>
<span><i class="api-debug-swatch" style="background:#ffd54f"></i> границы 60% зоны Френеля</span>
<span><i class="api-debug-swatch" style="background:#ba68c8"></i> границы 100% зоны Френеля</span>
<span><i class="api-debug-swatch" style="background:#ff1744"></i> нарушения зоны Френеля</span>
<label><input type="checkbox" id="apiDebugShowFresnel" checked /> зона Френеля</label>
</div>
<div id="apiDebugSummary">Нет расчёта.</div>
<pre id="apiDebugOutput">Выберите запрос для проверки RF API.</pre>
</div>
<div id="mapRulerPanel">
<div id="mapRulerHead">
<span>Линейка высот <span id="mapRulerStatus"></span></span>
<button type="button" id="btnMapRulerClose" aria-label="Закрыть">×</button>
</div>
<div id="mapRulerTools">
<button type="button" id="btnRulerPick" class="active">Точки</button>
<button type="button" id="btnRulerAutoTxRx">TX→RX</button>
<button type="button" id="btnRulerClear">Сброс</button>
</div>
<div id="mapRulerHint">Клик на карте — точка A, затем точка B</div>
<div id="mapRulerPointsRow">
<label><input type="checkbox" id="mapRulerPointsAuto" checked /> Авто</label>
<input type="range" id="mapRulerPointsSlider" min="20" max="500" value="100" disabled />
<span id="mapRulerPointsLabel">100 точек</span>
</div>
<canvas id="mapRulerCanvas" width="800" height="120"></canvas>
</div>
<div id="map"></div>
</div>
<aside>
<div class="panel" style="flex:1;display:flex;flex-direction:column">
<h2>Чат</h2>
<div id="chatLog"></div>
<form id="chatForm">
<input type="text" id="chatInput" placeholder="Сообщение…" autocomplete="off" />
<button type="submit"></button>
</form>
</div>
<div class="panel">
<h2>Устройства</h2>
<ul id="deviceList"></ul>
</div>
<div class="panel">
<h2>Статистика</h2>
<div id="stats">Выберите устройство</div>
<div id="history" class="muted" style="margin-top:8px"></div>
</div>
<div class="panel" id="controlPanel">
<h2>Управление</h2>
<label class="muted">Целевое устройство</label>
<select id="cmdTargetSelect"><option value=""></option></select>
<div id="cmdCurrentValues" class="muted" style="margin-top:4px;font-size:0.75rem"></div>
<div id="cmdDraftHint" class="muted" style="display:none;margin-top:4px;font-size:0.7rem;color:#ffc107">Черновик — значения с устройства не подставляются</div>
<div class="cmd-row" style="margin-top:6px">
<label class="muted" style="flex:1">FQ MHz<input type="number" id="cmdFq" step="0.001" placeholder="433" /></label>
<label class="muted" style="flex:1">PW dBm<input type="number" id="cmdPw" min="-9" max="22" placeholder="22" /></label>
</div>
<div class="cmd-row">
<label class="muted" style="flex:1">SF<input type="number" id="cmdSf" min="5" max="12" placeholder="7" /></label>
<label class="muted" style="flex:1">PL<input type="number" id="cmdPl" min="1" max="64" placeholder="8" /></label>
</div>
<label class="muted">BW kHz</label>
<select id="cmdBw">
<option value=""></option>
<option>7.81</option><option>10.42</option><option>15.63</option><option>20.83</option>
<option>31.25</option><option>41.67</option><option>62.5</option>
<option selected>125</option><option>250</option><option>500</option>
</select>
<label class="muted">CR</label>
<select id="cmdCr">
<option value=""></option><option>4/5</option><option>4/6</option><option>4/7</option><option>4/8</option>
</select>
<label class="muted">TM ms</label>
<input type="number" id="cmdTm" min="0" max="60000" placeholder="0" />
<label class="muted">Роль</label>
<select id="cmdRole"><option value=""></option><option value="TX">TX</option><option value="RX">RX</option></select>
<button type="button" id="btnCmdApply" style="width:100%;margin-top:6px;padding:6px;background:#e94560;color:#fff;border:none;border-radius:4px;font-weight:600;cursor:pointer">Применить</button>
<button type="button" id="btnPairedStart" style="width:100%;margin-top:8px;padding:6px;background:#00ff88;color:#111;border:none;border-radius:4px;font-weight:600;cursor:pointer">Старт трека (оба)</button>
<div id="pairedStatus"></div>
</div>
<div class="panel">
<h2>Треки</h2>
<div class="track-mode">
<button type="button" id="btnModeSingle" class="active">Один трек</button>
<button type="button" id="btnModeDual">Сравнение TX+RX</button>
</div>
<div id="trackPanelSingle">
<div class="track-row">
<label>Трек</label>
<select id="trackSingleSelect"><option value=""></option></select>
</div>
</div>
<div id="trackPanelDual" style="display:none">
<div class="track-row">
<label class="legend-tx">Трек TX</label>
<select id="trackTxSelect"><option value=""></option></select>
</div>
<div class="track-row">
<label class="legend-rx">Трек RX</label>
<select id="trackRxSelect"><option value=""></option></select>
</div>
</div>
<div class="track-actions">
<button type="button" id="btnShowTracks">Показать на карте</button>
<button type="button" id="btnHideTracks" disabled>Скрыть треки</button>
</div>
<div id="trackInfo" class="muted" style="margin-top:6px">Выберите трек</div>
<div id="timelineStatsPanel">
<h3 style="margin:0 0 6px;font-size:0.9rem">Статистика по времени</h3>
<div id="timelineNote"></div>
<div id="distanceNow">Расстояние GPS: —</div>
<div id="timelineStats"></div>
</div>
</div>
</aside>
<div id="trackTimeline">
<div class="timeline-bar">
<div class="timeline-bar-header">
<span class="timeline-bar-title">Время теста</span>
<button type="button" id="btnPlay" class="muted" style="padding:4px 10px;border:none;border-radius:4px;cursor:pointer;background:#0a0a14;color:#eee">▶ Play</button>
</div>
<div class="timeline-labels">
<span id="timeStart"></span>
<span id="timeCurrent"></span>
<span id="timeEnd"></span>
</div>
<input type="range" id="timeSlider" min="0" max="100" value="0" step="0.1" />
<div id="elevationPanel">
<div id="elevationPanelTitle">
<span>Линейка высот <span id="elevationStatus" class="muted"></span></span>
<span class="elev-legend"><span class="legend-tx">TX</span> <span class="legend-rx">RX</span></span>
</div>
<canvas id="elevationCanvas" width="800" height="130"></canvas>
</div>
<div id="qualityVizPanel">
<div class="quality-viz-box">
<div class="quality-viz-title">RX Quality vs расстояние TX↔RX · колёсико — шаг шкалы</div>
<canvas id="qualityDistCanvas" width="800" height="140"></canvas>
</div>
</div>
</div>
</div>
</main>
<div id="mapModal">
<div id="mapModalHeader">
<span>Детали</span>
<button type="button" id="mapModalClose" aria-label="Закрыть">×</button>
</div>
<div id="mapModalBody"></div>
</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="/static/radio-ui.js"></script>
<script src="/static/quality-viz.js"></script>
<script src="/static/app.js"></script>
</body>
</html>