diff --git a/public/calibration.html b/public/calibration.html
index 319491a..3fa5157 100644
--- a/public/calibration.html
+++ b/public/calibration.html
@@ -229,7 +229,12 @@
-
+
+
+
+
@@ -371,18 +376,36 @@
}
function updateCalibInfo(meta) {
+ const sel = document.getElementById('cam-select-calib');
+
if (!meta) {
document.getElementById('info-timestamp').textContent = '(keine Session vorhanden)';
document.getElementById('info-created').textContent = '–';
document.getElementById('info-images').textContent = '–';
+ // Selector leeren
+ sel.innerHTML = '';
return;
}
document.getElementById('info-timestamp').textContent = meta.timestamp ?? '–';
document.getElementById('info-created').textContent = formatDate(meta.createdAt);
- const imgTxt = meta.imageCount != null
- ? `${meta.imageCount} Bilder total. ${(meta.cameras ?? []).length} Kamera(s) verwendet.`
+ const cameras = meta.cameras ?? [];
+ const imgTxt = meta.imageCount != null
+ ? `${meta.imageCount} Bilder total. ${cameras.length} Kamera(s) verwendet.`
: '–';
document.getElementById('info-images').textContent = imgTxt;
+
+ // Kamera-Selector aktualisieren
+ const prev = sel.value;
+ sel.innerHTML = '';
+ for (const cam of cameras) {
+ const opt = document.createElement('option');
+ opt.value = cam;
+ opt.textContent = cam;
+ if (cam === prev) opt.selected = true;
+ sel.appendChild(opt);
+ }
+ // Falls nur eine Kamera vorhanden – automatisch vorwählen
+ if (cameras.length === 1) sel.value = cameras[0];
}
// Beim Laden aktuelle Session holen
@@ -427,6 +450,66 @@
logC(`Fehler: ${err}`);
}
});
+
+ // "Kalibrierung berechnen" – SSE-Stream lesen
+ document.getElementById('btn-compute-calib').addEventListener('click', async () => {
+ const camera = document.getElementById('cam-select-calib').value;
+ if (!camera) { logC('⚠ Bitte zuerst eine Kamera auswählen.'); return; }
+
+ logC(`Starte Kalibrierung für ${camera} …`);
+ const btn = document.getElementById('btn-compute-calib');
+ btn.disabled = true;
+
+ try {
+ const response = await fetch('/api/calibration/compute', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ camera }),
+ });
+
+ if (!response.ok) {
+ const err = await response.json().catch(() => ({ error: response.statusText }));
+ logC(`Fehler: ${err.error}`);
+ return;
+ }
+
+ // SSE-Stream zeilenweise verarbeiten
+ const reader = response.body.getReader();
+ const decoder = new TextDecoder();
+ let buffer = '';
+
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ buffer += decoder.decode(value, { stream: true });
+
+ // Vollständige SSE-Ereignisse (getrennt durch \n\n) extrahieren
+ const parts = buffer.split('\n\n');
+ buffer = parts.pop(); // letztes unvollständiges Fragment behalten
+
+ for (const part of parts) {
+ for (const line of part.split('\n')) {
+ if (!line.startsWith('data: ')) continue;
+ try {
+ const evt = JSON.parse(line.slice(6));
+ if (evt.type === 'log') {
+ if (evt.text !== '') logC(evt.text);
+ } else if (evt.type === 'done') {
+ logC(evt.exitCode === 0
+ ? '✅ Kalibrierung abgeschlossen.'
+ : `❌ Script beendet mit Exit-Code ${evt.exitCode}`);
+ }
+ } catch { /* ungültiges JSON überspringen */ }
+ }
+ }
+ }
+ } catch (err) {
+ logC(`Fehler: ${err}`);
+ } finally {
+ btn.disabled = false;
+ }
+ });