generated from Grigo/AndroidTemplate
fix
This commit is contained in:
@@ -379,7 +379,7 @@ def health():
|
|||||||
return {
|
return {
|
||||||
"ok": status["db_ok"],
|
"ok": status["db_ok"],
|
||||||
"ts": time.time(),
|
"ts": time.time(),
|
||||||
"api_build": "2026-06-16c",
|
"api_build": "2026-06-16d",
|
||||||
**status,
|
**status,
|
||||||
**elevation_status(),
|
**elevation_status(),
|
||||||
}
|
}
|
||||||
|
|||||||
+69
-28
@@ -361,7 +361,7 @@
|
|||||||
{ position: 'topright', collapsed: true }
|
{ position: 'topright', collapsed: true }
|
||||||
).addTo(map);
|
).addTo(map);
|
||||||
|
|
||||||
const API_BUILD = '2026-06-16c';
|
const API_BUILD = '2026-06-16d';
|
||||||
|
|
||||||
const markers = {};
|
const markers = {};
|
||||||
let selectedId = null;
|
let selectedId = null;
|
||||||
@@ -560,10 +560,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function snapAtCursor(track, telemetryRows, cursor, roleFallback) {
|
function snapAtCursor(track, telemetryRows, cursor, roleFallback) {
|
||||||
|
const pos = trackPointAt(track, cursor);
|
||||||
|
if (pos?.meta && String(pos.meta).length > 2) {
|
||||||
|
const ts = timelineUseProgress ? null : cursor.t;
|
||||||
|
return snapFromTrackPoint(pos, ts, roleFallback || track?.role);
|
||||||
|
}
|
||||||
if (timelineUseProgress) {
|
if (timelineUseProgress) {
|
||||||
const pos = positionAtProgress(track?.points, cursor.progress);
|
return snapFromTrackPoint(pos, null, roleFallback || track?.role);
|
||||||
if (!pos) return null;
|
|
||||||
return { meta: pos.meta, role: roleFallback, rssi: pos.rssi, ts: cursor.progress };
|
|
||||||
}
|
}
|
||||||
return snapAtTime(track, telemetryRows, cursor.t, roleFallback);
|
return snapAtTime(track, telemetryRows, cursor.t, roleFallback);
|
||||||
}
|
}
|
||||||
@@ -592,21 +595,42 @@
|
|||||||
return { ...track, points };
|
return { ...track, points };
|
||||||
}
|
}
|
||||||
|
|
||||||
function snapAtTime(track, telemetryRows, t, roleFallback) {
|
function normalizeTelemetry(rows) {
|
||||||
const tel = telemetryAtTime(telemetryRows, t);
|
if (!rows?.length) return [];
|
||||||
if (tel) return tel;
|
return rows
|
||||||
const pos = positionAt(track?.points, t);
|
.map(r => ({ ...r, ts: Number(r.ts) }))
|
||||||
|
.sort((a, b) => a.ts - b.ts);
|
||||||
|
}
|
||||||
|
|
||||||
|
function trackPointAt(track, cursor) {
|
||||||
|
if (!track?.points?.length) return null;
|
||||||
|
return timelineUseProgress
|
||||||
|
? positionAtProgress(track.points, cursor.progress)
|
||||||
|
: positionAt(track.points, cursor.t);
|
||||||
|
}
|
||||||
|
|
||||||
|
function snapFromTrackPoint(pos, t, roleFallback) {
|
||||||
if (!pos) return null;
|
if (!pos) return null;
|
||||||
return {
|
return {
|
||||||
meta: pos.meta,
|
meta: pos.meta,
|
||||||
role: roleFallback || track?.role,
|
role: roleFallback,
|
||||||
rssi: pos.rssi,
|
rssi: pos.rssi,
|
||||||
ts: t,
|
ts: t,
|
||||||
lat: pos.lat,
|
lat: pos.lat,
|
||||||
lon: pos.lon
|
lon: pos.lon,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function snapAtTime(track, telemetryRows, t, roleFallback) {
|
||||||
|
const pos = positionAt(track?.points, t);
|
||||||
|
if (pos?.meta && String(pos.meta).length > 2) {
|
||||||
|
return snapFromTrackPoint(pos, t, roleFallback || track?.role);
|
||||||
|
}
|
||||||
|
const tel = telemetryAtTime(telemetryRows, t);
|
||||||
|
if (tel) return tel;
|
||||||
|
return snapFromTrackPoint(pos, t, roleFallback || track?.role);
|
||||||
|
}
|
||||||
|
|
||||||
function rxQualityFromMeta(meta) {
|
function rxQualityFromMeta(meta) {
|
||||||
if (!meta) return null;
|
if (!meta) return null;
|
||||||
const snap = RadioUI.parseRadioSnapshot(meta);
|
const snap = RadioUI.parseRadioSnapshot(meta);
|
||||||
@@ -1847,18 +1871,17 @@
|
|||||||
|
|
||||||
function telemetryAtTime(rows, t) {
|
function telemetryAtTime(rows, t) {
|
||||||
if (!rows?.length) return null;
|
if (!rows?.length) return null;
|
||||||
const first = rows[0];
|
const tNum = Number(t);
|
||||||
const last = rows[rows.length - 1];
|
let best = null;
|
||||||
if (t <= first.ts) return first;
|
let bestD = Infinity;
|
||||||
if (t >= last.ts) return last;
|
for (const r of rows) {
|
||||||
for (let i = 0; i < rows.length - 1; i++) {
|
const d = Math.abs(Number(r.ts) - tNum);
|
||||||
const a = rows[i];
|
if (d < bestD) {
|
||||||
const b = rows[i + 1];
|
best = r;
|
||||||
if (t >= a.ts && t <= b.ts) {
|
bestD = d;
|
||||||
return t - a.ts <= b.ts - t ? a : b;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return last;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
function telemetryFromTrackPoint(track, t, roleFallback) {
|
function telemetryFromTrackPoint(track, t, roleFallback) {
|
||||||
@@ -1890,9 +1913,21 @@
|
|||||||
return bestD === 0 ? best : null;
|
return bestD === 0 ? best : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function trackMetaDiversity(points) {
|
||||||
|
const packets = new Set();
|
||||||
|
let withMeta = 0;
|
||||||
|
for (const p of points || []) {
|
||||||
|
if (!p.meta || String(p.meta).length < 3) continue;
|
||||||
|
withMeta++;
|
||||||
|
const snap = RadioUI.parseRadioSnapshot(p.meta);
|
||||||
|
if (snap.packet != null) packets.add(snap.packet);
|
||||||
|
}
|
||||||
|
return { withMeta, uniquePackets: packets.size, total: points?.length || 0 };
|
||||||
|
}
|
||||||
|
|
||||||
function pairedTelemetryAtTime(txTrack, rxTrack, telemetryTx, telemetryRx, t) {
|
function pairedTelemetryAtTime(txTrack, rxTrack, telemetryTx, telemetryRx, t) {
|
||||||
let txTel = telemetryAtTime(telemetryTx, t) || telemetryFromTrackPoint(txTrack, t, 'TX');
|
let txTel = snapAtTime(txTrack, telemetryTx, t, 'TX');
|
||||||
let rxTel = telemetryAtTime(telemetryRx, t) || telemetryFromTrackPoint(rxTrack, t, 'RX');
|
let rxTel = snapAtTime(rxTrack, telemetryRx, t, 'RX');
|
||||||
const txSnap = txTel ? telemetryToSnap(txTel) : null;
|
const txSnap = txTel ? telemetryToSnap(txTel) : null;
|
||||||
const rxSnap = rxTel ? telemetryToSnap(rxTel) : null;
|
const rxSnap = rxTel ? telemetryToSnap(rxTel) : null;
|
||||||
if (txSnap?.packet != null) {
|
if (txSnap?.packet != null) {
|
||||||
@@ -2211,6 +2246,12 @@
|
|||||||
noteText =
|
noteText =
|
||||||
'Треки не пересекаются по времени — шкала на полном диапазоне; вне записи позиция удерживается на краю.';
|
'Треки не пересекаются по времени — шкала на полном диапазоне; вне записи позиция удерживается на краю.';
|
||||||
}
|
}
|
||||||
|
const txDiv = trackMetaDiversity(loadedTxTrack.points);
|
||||||
|
const rxDiv = trackMetaDiversity(loadedRxTrack.points);
|
||||||
|
if (txDiv.uniquePackets <= 1 && rxDiv.uniquePackets <= 1
|
||||||
|
&& (txDiv.withMeta > 1 || rxDiv.withMeta > 1)) {
|
||||||
|
noteText += ' Радио-статистика в точках трека не менялась при записи.';
|
||||||
|
}
|
||||||
applyTimelineRange(range, noteText);
|
applyTimelineRange(range, noteText);
|
||||||
}
|
}
|
||||||
setTimelineVisible(true);
|
setTimelineVisible(true);
|
||||||
@@ -2227,7 +2268,7 @@
|
|||||||
`/api/telemetry?device_id=${encodeURIComponent(loadedSingleTrack.device_id)}&since=${range.min}&until=${range.max}&limit=500`,
|
`/api/telemetry?device_id=${encodeURIComponent(loadedSingleTrack.device_id)}&since=${range.min}&until=${range.max}&limit=500`,
|
||||||
{ cache: 'no-store' }
|
{ cache: 'no-store' }
|
||||||
);
|
);
|
||||||
if (res.ok) telemetrySingle = await res.json();
|
if (res.ok) telemetrySingle = normalizeTelemetry(await res.json());
|
||||||
}
|
}
|
||||||
updateTimelineAtSingle(timelineCursor());
|
updateTimelineAtSingle(timelineCursor());
|
||||||
return;
|
return;
|
||||||
@@ -2240,8 +2281,8 @@
|
|||||||
fetch(`/api/telemetry?device_id=${encodeURIComponent(loadedTxTrack.device_id)}&since=${range.min}&until=${range.max}&limit=500`, { cache: 'no-store' }),
|
fetch(`/api/telemetry?device_id=${encodeURIComponent(loadedTxTrack.device_id)}&since=${range.min}&until=${range.max}&limit=500`, { cache: 'no-store' }),
|
||||||
fetch(`/api/telemetry?device_id=${encodeURIComponent(loadedRxTrack.device_id)}&since=${range.min}&until=${range.max}&limit=500`, { cache: 'no-store' })
|
fetch(`/api/telemetry?device_id=${encodeURIComponent(loadedRxTrack.device_id)}&since=${range.min}&until=${range.max}&limit=500`, { cache: 'no-store' })
|
||||||
]);
|
]);
|
||||||
if (telTx.ok) telemetryTx = await telTx.json();
|
if (telTx.ok) telemetryTx = normalizeTelemetry(await telTx.json());
|
||||||
if (telRx.ok) telemetryRx = await telRx.json();
|
if (telRx.ok) telemetryRx = normalizeTelemetry(await telRx.json());
|
||||||
}
|
}
|
||||||
updateTimelineAt(timelineCursor());
|
updateTimelineAt(timelineCursor());
|
||||||
}
|
}
|
||||||
@@ -2326,7 +2367,7 @@
|
|||||||
`/api/telemetry?device_id=${encodeURIComponent(loadedSingleTrack.device_id)}&since=${range.min}&until=${range.max}&limit=500`,
|
`/api/telemetry?device_id=${encodeURIComponent(loadedSingleTrack.device_id)}&since=${range.min}&until=${range.max}&limit=500`,
|
||||||
{ cache: 'no-store' }
|
{ cache: 'no-store' }
|
||||||
);
|
);
|
||||||
if (telRes.ok) telemetrySingle = await telRes.json();
|
if (telRes.ok) telemetrySingle = normalizeTelemetry(await telRes.json());
|
||||||
updateTimelineAtSingle(timelineCursor());
|
updateTimelineAtSingle(timelineCursor());
|
||||||
}
|
}
|
||||||
document.getElementById('trackInfo').textContent =
|
document.getElementById('trackInfo').textContent =
|
||||||
@@ -2387,8 +2428,8 @@
|
|||||||
fetch(`/api/telemetry?device_id=${encodeURIComponent(loadedTxTrack.device_id)}&since=${range.min}&until=${range.max}&limit=500`, { cache: 'no-store' }),
|
fetch(`/api/telemetry?device_id=${encodeURIComponent(loadedTxTrack.device_id)}&since=${range.min}&until=${range.max}&limit=500`, { cache: 'no-store' }),
|
||||||
fetch(`/api/telemetry?device_id=${encodeURIComponent(loadedRxTrack.device_id)}&since=${range.min}&until=${range.max}&limit=500`, { cache: 'no-store' })
|
fetch(`/api/telemetry?device_id=${encodeURIComponent(loadedRxTrack.device_id)}&since=${range.min}&until=${range.max}&limit=500`, { cache: 'no-store' })
|
||||||
]);
|
]);
|
||||||
if (telTx.ok) telemetryTx = await telTx.json();
|
if (telTx.ok) telemetryTx = normalizeTelemetry(await telTx.json());
|
||||||
if (telRx.ok) telemetryRx = await telRx.json();
|
if (telRx.ok) telemetryRx = normalizeTelemetry(await telRx.json());
|
||||||
updateTimelineAt(timelineCursor());
|
updateTimelineAt(timelineCursor());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,8 +116,10 @@
|
|||||||
|
|
||||||
function renderCompareGrid(txSnap, rxSnap, txId, rxId, changedTx, changedRx, staticOpen) {
|
function renderCompareGrid(txSnap, rxSnap, txId, rxId, changedTx, changedRx, staticOpen) {
|
||||||
let html = '<div class="radio-compare-grid">';
|
let html = '<div class="radio-compare-grid">';
|
||||||
html += `<div class="radio-compare-head"><span class="legend-tx">TX</span> ${escapeHtml(txId || '—')}`;
|
html += '<div class="radio-compare-head">';
|
||||||
html += `<span class="legend-rx">RX</span> ${escapeHtml(rxId || '—')}</div>`;
|
html += `<span><span class="legend-tx">TX</span> ${escapeHtml(txId || '—')}</span>`;
|
||||||
|
html += `<span><span class="legend-rx">RX</span> ${escapeHtml(rxId || '—')}</span>`;
|
||||||
|
html += '</div>';
|
||||||
for (const row of DYNAMIC_ROWS) {
|
for (const row of DYNAMIC_ROWS) {
|
||||||
const txCls = changedTx && changedTx.has(row.key) ? ' changed' : '';
|
const txCls = changedTx && changedTx.has(row.key) ? ' changed' : '';
|
||||||
const rxCls = changedRx && changedRx.has(row.key) ? ' changed' : '';
|
const rxCls = changedRx && changedRx.has(row.key) ? ' changed' : '';
|
||||||
|
|||||||
Reference in New Issue
Block a user