Просмотр исходного кода

Add recordings list to UI

master
Jan Svabenik 3 дней назад
Родитель
Сommit
cbb0fe7dfb
2 измененных файлов: 58 добавлений и 0 удалений
  1. +52
    -0
      web/app.js
  2. +6
    -0
      web/index.html

+ 52
- 0
web/app.js Просмотреть файл

@@ -38,8 +38,10 @@ const gpuToggle = qs('gpuToggle');


const signalList = qs('signalList'); const signalList = qs('signalList');
const eventList = qs('eventList'); const eventList = qs('eventList');
const recordingList = qs('recordingList');
const signalCountBadge = qs('signalCountBadge'); const signalCountBadge = qs('signalCountBadge');
const eventCountBadge = qs('eventCountBadge'); const eventCountBadge = qs('eventCountBadge');
const recordingCountBadge = qs('recordingCountBadge');


const healthBuffer = qs('healthBuffer'); const healthBuffer = qs('healthBuffer');
const healthDropped = qs('healthDropped'); const healthDropped = qs('healthDropped');
@@ -112,6 +114,8 @@ let lastEventEndMs = 0;
let selectedEventId = null; let selectedEventId = null;
let timelineRects = []; let timelineRects = [];
let liveSignalRects = []; let liveSignalRects = [];
let recordings = [];
let recordingsFetchInFlight = false;


const GAIN_MAX = 60; const GAIN_MAX = 60;
const timelineWindowMs = 5 * 60 * 1000; const timelineWindowMs = 5 * 60 * 1000;
@@ -761,6 +765,26 @@ function renderLists() {
</button> </button>
`).join(''); `).join('');
} }

if (recordingList && recordingCountBadge) {
recordingCountBadge.textContent = `${recordings.length}`;
if (recordings.length === 0) {
recordingList.innerHTML = '<div class="empty-state">No recordings yet.</div>';
} else {
recordingList.innerHTML = recordings.slice(0, 50).map((rec) => `
<button class="list-item recording-item" type="button" data-id="${rec.id}">
<div class="item-top">
<span class="item-title">${new Date(rec.start).toLocaleString()}</span>
<span class="item-badge">${fmtMHz(rec.center_hz || 0, 6)}</span>
</div>
<div class="item-bottom">
<span class="item-meta">${rec.id}</span>
<span class="item-meta">recording</span>
</div>
</button>
`).join('');
}
}
} }


function normalizeEvent(ev) { function normalizeEvent(ev) {
@@ -811,6 +835,22 @@ async function fetchEvents(initial) {
} }
} }


async function fetchRecordings() {
if (recordingsFetchInFlight || !recordingList) return;
recordingsFetchInFlight = true;
try {
const res = await fetch('/api/recordings');
if (!res.ok) return;
const data = await res.json();
if (Array.isArray(data)) {
recordings = data;
renderLists();
}
} finally {
recordingsFetchInFlight = false;
}
}

function openDrawer(ev) { function openDrawer(ev) {
if (!ev) return; if (!ev) return;
selectedEventId = ev.id; selectedEventId = ev.id;
@@ -1127,6 +1167,16 @@ eventList.addEventListener('click', (ev) => {
openDrawer(eventsById.get(id)); openDrawer(eventsById.get(id));
}); });


if (recordingList) {
recordingList.addEventListener('click', async (ev) => {
const target = ev.target.closest('.recording-item');
if (!target) return;
const id = target.dataset.id;
const audio = new Audio(`/api/recordings/${id}/audio`);
audio.play();
});
}

window.addEventListener('keydown', (ev) => { window.addEventListener('keydown', (ev) => {
if (ev.target && ['INPUT', 'SELECT', 'TEXTAREA'].includes(ev.target.tagName)) return; if (ev.target && ['INPUT', 'SELECT', 'TEXTAREA'].includes(ev.target.tagName)) return;
if (ev.key === ' ') { if (ev.key === ' ') {
@@ -1159,8 +1209,10 @@ loadConfig();
loadStats(); loadStats();
loadGPU(); loadGPU();
fetchEvents(true); fetchEvents(true);
fetchRecordings();
connect(); connect();
requestAnimationFrame(renderLoop); requestAnimationFrame(renderLoop);
setInterval(loadStats, 1000); setInterval(loadStats, 1000);
setInterval(loadGPU, 1000); setInterval(loadGPU, 1000);
setInterval(() => fetchEvents(false), 2000); setInterval(() => fetchEvents(false), 2000);
setInterval(fetchRecordings, 5000);

+ 6
- 0
web/index.html Просмотреть файл

@@ -104,6 +104,7 @@
<button class="rail-tab" data-tab="signals" type="button">Signals</button> <button class="rail-tab" data-tab="signals" type="button">Signals</button>
<button class="rail-tab" data-tab="events" type="button">Events</button> <button class="rail-tab" data-tab="events" type="button">Events</button>
<button class="rail-tab" data-tab="health" type="button">Health</button> <button class="rail-tab" data-tab="health" type="button">Health</button>
<button class="rail-tab" data-tab="recordings" type="button">Recordings</button>
</div> </div>


<div class="rail-body"> <div class="rail-body">
@@ -198,6 +199,11 @@
<div class="health-card health-card--wide"><span class="health-lbl">Render rate</span><span class="health-val" id="healthFps">-</span></div> <div class="health-card health-card--wide"><span class="health-lbl">Render rate</span><span class="health-val" id="healthFps">-</span></div>
</div> </div>
</section> </section>

<section class="tab-panel" data-panel="recordings">
<div class="list-head"><span class="grp-title">Recordings</span><span class="count-pill" id="recordingCountBadge">0</span></div>
<div class="event-list" id="recordingList"><div class="empty-state">No recordings yet.</div></div>
</section>
</div> </div>
</aside> </aside>




Загрузка…
Отмена
Сохранить