diff --git a/web/app.js b/web/app.js
index 7d809c9..6a04acb 100644
--- a/web/app.js
+++ b/web/app.js
@@ -38,8 +38,10 @@ const gpuToggle = qs('gpuToggle');
const signalList = qs('signalList');
const eventList = qs('eventList');
+const recordingList = qs('recordingList');
const signalCountBadge = qs('signalCountBadge');
const eventCountBadge = qs('eventCountBadge');
+const recordingCountBadge = qs('recordingCountBadge');
const healthBuffer = qs('healthBuffer');
const healthDropped = qs('healthDropped');
@@ -112,6 +114,8 @@ let lastEventEndMs = 0;
let selectedEventId = null;
let timelineRects = [];
let liveSignalRects = [];
+let recordings = [];
+let recordingsFetchInFlight = false;
const GAIN_MAX = 60;
const timelineWindowMs = 5 * 60 * 1000;
@@ -761,6 +765,26 @@ function renderLists() {
`).join('');
}
+
+ if (recordingList && recordingCountBadge) {
+ recordingCountBadge.textContent = `${recordings.length}`;
+ if (recordings.length === 0) {
+ recordingList.innerHTML = '
No recordings yet.
';
+ } else {
+ recordingList.innerHTML = recordings.slice(0, 50).map((rec) => `
+
+ `).join('');
+ }
+ }
}
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) {
if (!ev) return;
selectedEventId = ev.id;
@@ -1127,6 +1167,16 @@ eventList.addEventListener('click', (ev) => {
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) => {
if (ev.target && ['INPUT', 'SELECT', 'TEXTAREA'].includes(ev.target.tagName)) return;
if (ev.key === ' ') {
@@ -1159,8 +1209,10 @@ loadConfig();
loadStats();
loadGPU();
fetchEvents(true);
+fetchRecordings();
connect();
requestAnimationFrame(renderLoop);
setInterval(loadStats, 1000);
setInterval(loadGPU, 1000);
setInterval(() => fetchEvents(false), 2000);
+setInterval(fetchRecordings, 5000);
diff --git a/web/index.html b/web/index.html
index d7a0e8d..ad08357 100644
--- a/web/index.html
+++ b/web/index.html
@@ -104,6 +104,7 @@
+
@@ -198,6 +199,11 @@
Render rate-
+
+