From 7a04cc166db74add702179e1ab35b776a96c7b88 Mon Sep 17 00:00:00 2001 From: grigo Date: Tue, 23 Jun 2026 14:54:48 +0300 Subject: [PATCH] fix proxy --- server/static/index.html | 134 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 124 insertions(+), 10 deletions(-) diff --git a/server/static/index.html b/server/static/index.html index 24744f1..49ac2b2 100644 --- a/server/static/index.html +++ b/server/static/index.html @@ -226,6 +226,12 @@ color: #d7f7ff; font-size: 0.72rem; white-space: pre-wrap; } #apiDebugChart { width: 100%; height: 140px; display: block; margin-top: 8px; background: #0a0a14; border-radius: 4px; } + .api-debug-legend { + display: flex; gap: 10px; flex-wrap: wrap; align-items: center; + margin-top: 5px; color: #aaa; font-size: 0.7rem; + } + .api-debug-legend span { display: inline-flex; align-items: center; gap: 4px; } + .api-debug-swatch { width: 16px; height: 3px; border-radius: 2px; display: inline-block; } #apiDebugSummary { margin-top: 8px; padding: 8px; background: #0a0a14; border: 1px solid #333; border-radius: 4px; font-size: 0.75rem; line-height: 1.45; @@ -304,6 +310,11 @@
Готово
“Расчёт линии” вызывает profile + LOS + link budget и строит график земли/поверхности.
+
+ земля DEM + поверхность/препятствия + прямая TX→RX +
Нет расчёта.
Выберите запрос для проверки RF API.
@@ -536,6 +547,12 @@ let apiDebugTxMarker = null; let apiDebugRxMarker = null; let apiDebugLineLayer = null; + let apiDebugCursorDist = null; + let apiDebugCursorMarker = null; + let apiDebugGroundProfile = null; + let apiDebugSurfaceProfile = null; + let apiDebugLosLine = null; + let apiDebugBaseSummaryHtml = 'Нет расчёта.'; let mapRulerMode = 'pick'; let mapRulerPtA = null; let mapRulerPtB = null; @@ -2184,6 +2201,80 @@ } } + function clearApiDebugCursorMarker() { + if (apiDebugCursorMarker) { + map.removeLayer(apiDebugCursorMarker); + apiDebugCursorMarker = null; + } + } + + function clearApiDebugCursor() { + apiDebugCursorDist = null; + clearApiDebugCursorMarker(); + apiDebugSetStatus('RF расчёт линии готов'); + apiDebugSetSummary(apiDebugBaseSummaryHtml); + drawApiDebugChart(); + } + + function drawApiDebugChart() { + const series = []; + if (apiDebugGroundProfile) { + series.push({ + color: '#4fc3f7', + profile: apiDebugGroundProfile, + label: 'земля DEM' + }); + } + if (apiDebugSurfaceProfile) { + series.push({ + color: '#00ff88', + profile: apiDebugSurfaceProfile, + label: 'поверхность', + losLine: apiDebugLosLine, + cursor: apiDebugCursorDist + }); + } + renderElevationCanvas( + document.getElementById('apiDebugChart'), + series, + 'done', + 'Запустите расчёт линии' + ); + } + + function setApiDebugChartCursor(distM) { + if (!apiDebugSurfaceProfile || !apiDebugGroundProfile) return; + const tx = apiDebugLatLon('tx'); + const rx = apiDebugLatLon('rx'); + if (!tx || !rx) return; + const total = apiDebugSurfaceProfile.total_m || lineLengthM(tx, rx); + apiDebugCursorDist = Math.max(0, Math.min(distM, total)); + const pt = latLonAtLineDist(tx, rx, apiDebugCursorDist); + clearApiDebugCursorMarker(); + apiDebugCursorMarker = L.circleMarker([pt.lat, pt.lon], { + radius: 8, + color: '#fff', + weight: 2, + fillColor: '#ff9800', + fillOpacity: 0.95, + interactive: false + }).addTo(map); + const ground = elevationAtDist(apiDebugGroundProfile, apiDebugCursorDist); + const surface = elevationAtDist(apiDebugSurfaceProfile, apiDebugCursorDist); + const delta = ground != null && surface != null ? surface - ground : null; + apiDebugSetStatus( + `${apiDebugCursorDist.toFixed(0)} m · земля ${fmtNum(ground, 1, ' m')} · поверхность ${fmtNum(surface, 1, ' m')}` + ); + apiDebugSetSummary( + `${apiDebugBaseSummaryHtml}
` + + `Точка на графике: ${fmtNum(apiDebugCursorDist, 0, ' m')} от TX
` + + `Координаты: ${fmtNum(pt.lat, 6)}, ${fmtNum(pt.lon, 6)}
` + + `Земля DEM: ${fmtNum(ground, 1, ' m')} · поверхность: ${fmtNum(surface, 1, ' m')}` + + (delta != null && Math.abs(delta) > 0.05 ? ` · препятствие +${fmtNum(delta, 1, ' m')}` : '') + ); + drawApiDebugChart(); + } + function setApiDebugPoint(which, lat, lon) { const prefix = which === 'tx' ? 'Tx' : 'Rx'; document.getElementById(`apiDebug${prefix}Lat`).value = Number(lat).toFixed(6); @@ -2227,6 +2318,13 @@ if (el) el.value = ''; }); refreshApiDebugOverlay(); + clearApiDebugCursorMarker(); + apiDebugGroundProfile = null; + apiDebugSurfaceProfile = null; + apiDebugLosLine = null; + apiDebugCursorDist = null; + apiDebugBaseSummaryHtml = 'Нет расчёта.'; + drawApiDebugChart(); setApiDebugPickMode(null); apiDebugSetStatus('Точки очищены'); apiDebugSetSummary('Выберите TX и RX на карте или из линейки.'); @@ -2454,16 +2552,14 @@ const last = profile.samples[profile.samples.length - 1] || {}; const elevA = Number(first.ground_m ?? first.surface_m ?? 0) + f.txHeight; const elevB = Number(last.ground_m ?? last.surface_m ?? 0) + f.rxHeight; - renderElevationCanvas( - document.getElementById('apiDebugChart'), - [ - { color: '#4fc3f7', profile: ground, label: 'земля' }, - { color: '#00ff88', profile: surface, label: 'поверхность', losLine: { elevA, elevB } } - ], - 'done', - 'RF profile пустой' - ); - apiDebugSetSummary(formatApiDebugLineSummary(profile, los, budget)); + apiDebugGroundProfile = ground; + apiDebugSurfaceProfile = surface; + apiDebugLosLine = { elevA, elevB }; + apiDebugCursorDist = null; + clearApiDebugCursorMarker(); + drawApiDebugChart(); + apiDebugBaseSummaryHtml = formatApiDebugLineSummary(profile, los, budget); + apiDebugSetSummary(apiDebugBaseSummaryHtml); } function fmtBool(v) { @@ -3553,6 +3649,24 @@ }); })(); + (function bindApiDebugChartProbe() { + const canvas = document.getElementById('apiDebugChart'); + if (!canvas) return; + canvas.addEventListener('mousemove', e => { + if (!apiDebugSurfaceProfile) return; + const layout = canvas._elevLayout; + if (!layout || layout.plotW <= 0) return; + const rect = canvas.getBoundingClientRect(); + const x = e.clientX - rect.left; + const dist = ((x - layout.margin.l) / layout.plotW) * layout.maxDist; + setApiDebugChartCursor(dist); + }); + canvas.addEventListener('mouseleave', () => { + if (!apiDebugSurfaceProfile) return; + clearApiDebugCursor(); + }); + })(); + async function refreshPairedStatus() { try { const res = await fetch('/api/paired-tracks/active', { cache: 'no-store' });