// /public/assets/js/fakecheck/fakecheck.browser.js (function () { // Core / Namespace muss geladen sein if (!window.usbcheck) return; const { cfg, log: logLine, setStatus, setModeLabel, setProgress, setOverallStatus, formatBytes, formatMbps, formatDuration, t, tFmt } = window.usbcheck; const root = document.getElementById("fc-root"); if (!root) { // Seite ohne Fakecheck-Tool → nichts tun return; } // --- DOM-Helper --------------------------------------------------------- const $ = (sel) => document.querySelector(sel); const $$ = (sel) => Array.from(document.querySelectorAll(sel)); const fsapiWarning = $("#fc-fsapi-warning"); const selectedPathText = $("#fc-selected-path-label"); const saveHint = $("#fc-save-hint"); const saveError = $("#fc-save-error"); const resMode = $("#fc-res-mode"); const resDuration = $("#fc-res-duration"); const resWriteSpeed = $("#fc-res-write-speed"); const resReadSpeed = $("#fc-res-read-speed"); const resWritten = $("#fc-res-written-bytes"); const resVerified = $("#fc-res-verified-bytes"); const btnPickDir = $("#fc-btn-pick-directory"); const btnClearSel = $("#fc-btn-clear-selection"); const btnStart = $("#fc-btn-start-tests"); const btnCancel = $("#fc-btn-cancel-tests"); const modeTiles = $$("#fc-mode-grid .fc-mode-tile"); // Login-Indikator if (cfg.loggedIn && saveHint) { saveHint.style.display = "block"; } // --- Test-Engine (Browser) --------------------------------------------- class UsbBrowserTester { constructor() { this.rootHandle = null; this.abortController = null; this.capacityBytes = null; this.capacityProbeFile = "usbcheck_capacity_probe.bin"; } hasFsApiSupport() { return "showDirectoryPicker" in window; } async pickDirectory() { if (!this.hasFsApiSupport()) { throw new Error(t("fake_ui.error_fsapi_not_supported", "File System Access API wird von diesem Browser nicht unterstützt.")); } const handle = await window.showDirectoryPicker(); this.rootHandle = handle; return handle; } async clearSelection() { this.rootHandle = null; this.capacityBytes = null; } // Freispeicher-Ermittlung async ensureCapacity(abortSignal) { if (this.capacityBytes != null) return this.capacityBytes; if (!this.rootHandle) { throw new Error(t("fake_ui.error_no_directory_selected", "Kein Verzeichnis ausgewählt.")); } const dirHandle = this.rootHandle; const PROBE_FILENAME = this.capacityProbeFile; const CHUNK_SIZE = 1024 * 1024; // 1 MiB const MAX_BYTES = 128 * 1024 * 1024; // max. 128 MiB testen logLine(t("fake_ui.log_capacity_probe_start", "Ermittle verfügbaren Speicherplatz im gewählten Verzeichnis..."), "info"); let bytesWritten = 0; let writable = null; try { const fileHandle = await dirHandle.getFileHandle(PROBE_FILENAME, { create: true }); writable = await fileHandle.createWritable(); const chunk = new Uint8Array(CHUNK_SIZE); chunk.fill(0x5a); while (bytesWritten + CHUNK_SIZE <= MAX_BYTES) { if (abortSignal && abortSignal.aborted) { await writable.abort(); throw new DOMException("Abgebrochen", "AbortError"); } await writable.write(chunk); bytesWritten += CHUNK_SIZE; } await writable.close(); } catch (err) { if (writable) { try { await writable.close(); } catch (e) {} } if (err && err.name === "AbortError") { throw err; } // sonst: „Out of space“ → okay, wir nehmen bytesWritten als Limit } finally { try { await dirHandle.removeEntry(PROBE_FILENAME); } catch (e) { // egal } } if (!bytesWritten) { throw new Error(t("fake_ui.error_no_space_detected", "Es konnte kein freier Speicher im gewählten Verzeichnis reserviert werden.")); } this.capacityBytes = bytesWritten; logLine( tFmt( "fake_ui.log_capacity_probe_result", "Ermittelter für Tests nutzbarer Speicher: {size} (Schreibprobe).", { size: formatBytes(bytesWritten) } ), "info" ); return this.capacityBytes; } planSizeBytes({ defaultMB, minMB, maxFraction }) { const MiB = 1024 * 1024; const cap = this.capacityBytes || (64 * MiB); const minBytes = (minMB || 1) * MiB; const defaultBytes = (defaultMB || 8) * MiB; const maxBytesByCap = Math.max(minBytes, cap * (maxFraction || 0.25)); let target = Math.min(defaultBytes, maxBytesByCap); const CHUNK_SIZE = MiB; target = Math.floor(target / CHUNK_SIZE) * CHUNK_SIZE; if (target < CHUNK_SIZE) target = CHUNK_SIZE; return target; } // Quick-Check async runQuickCheck(report, progressCb, abortSignal) { const TEST_FILENAME = "usbcheck_quick_test.bin"; const CHUNK_SIZE = 1024 * 1024; const dirHandle = this.rootHandle; if (!dirHandle) throw new Error(t("fake_ui.error_no_directory_selected", "Kein Verzeichnis ausgewählt.")); const totalBytes = this.planSizeBytes({ defaultMB: 8, minMB: 1, maxFraction: 0.25 }); logLine( tFmt( "fake_ui.log_quick_prepare", "Quick-Check: Vorbereitung... (Testgröße: {size})", { size: formatBytes(totalBytes) } ), "info" ); const fileHandle = await dirHandle.getFileHandle(TEST_FILENAME, { create: true }); const writable = await fileHandle.createWritable(); let writtenBytes = 0; const writeStart = performance.now(); while (writtenBytes < totalBytes) { if (abortSignal.aborted) { await writable.abort(); throw new DOMException("Abgebrochen", "AbortError"); } const remaining = totalBytes - writtenBytes; const chunkLen = Math.min(CHUNK_SIZE, remaining); const chunk = new Uint8Array(chunkLen); for (let i = 0; i < chunkLen; i++) { chunk[i] = (i + writtenBytes) % 251; } await writable.write(chunk); writtenBytes += chunkLen; progressCb((writtenBytes / totalBytes) * 100 * 0.5); } await writable.close(); const writeEnd = performance.now(); logLine(t("fake_ui.log_quick_verify_start", "Quick-Check: Schreiben abgeschlossen. Verifiziere Daten..."), "info"); const file = await fileHandle.getFile(); const readStart = performance.now(); const reader = file.stream().getReader(); let offset = 0; let verifiedBytes = 0; while (true) { if (abortSignal.aborted) { reader.cancel(); throw new DOMException("Abgebrochen", "AbortError"); } const { done, value } = await reader.read(); if (done) break; const chunk = value; for (let i = 0; i < chunk.length; i++) { const expected = (offset + i) % 251; if (chunk[i] !== expected) { logLine( tFmt( "fake_ui.log_quick_data_error", "Quick-Check: Datenfehler bei Byte {byte}", { byte: (offset + i).toString() } ), "error" ); throw new Error( tFmt( "fake_ui.error_quick_data_error", "Datenfehler im Quick-Check bei Byte {byte}", { byte: (offset + i).toString() } ) ); } } offset += chunk.length; verifiedBytes += chunk.length; progressCb(50 + (verifiedBytes / totalBytes) * 100 * 0.5); } const readEnd = performance.now(); const writeSeconds = (writeEnd - writeStart) / 1000; const readSeconds = (readEnd - readStart) / 1000; report.quick = { mode: "quick", test_file: TEST_FILENAME, size_bytes: totalBytes, write_bytes: writtenBytes, read_bytes: verifiedBytes, write_duration_s: writeSeconds, read_duration_s: readSeconds, write_mbit_s: writeSeconds ? (writtenBytes * 8 / (writeSeconds * 1e6)) : null, read_mbit_s: readSeconds ? (verifiedBytes * 8 / (readSeconds * 1e6)) : null, ok: true }; try { await dirHandle.removeEntry(TEST_FILENAME); } catch (e) { logLine(t("fake_ui.log_quick_delete_warn", "Quick-Check: Konnte Testdatei nicht löschen (nicht kritisch)."), "warn"); } logLine(t("fake_ui.log_quick_success", "Quick-Check: Erfolgreich abgeschlossen."), "info"); } // Benchmark async runBenchmark(report, progressCb, abortSignal) { const TEST_FILENAME = "usbcheck_benchmark.bin"; const CHUNK_SIZE = 1024 * 1024; const dirHandle = this.rootHandle; if (!dirHandle) throw new Error(t("fake_ui.error_no_directory_selected", "Kein Verzeichnis ausgewählt.")); const totalBytes = this.planSizeBytes({ defaultMB: 32, minMB: 4, maxFraction: 0.5 }); logLine( tFmt( "fake_ui.log_bench_start", "Benchmark: Start – schreibe Testdatei ({size})...", { size: formatBytes(totalBytes) } ), "info" ); const fileHandle = await dirHandle.getFileHandle(TEST_FILENAME, { create: true }); const writable = await fileHandle.createWritable(); let writtenBytes = 0; const writeStart = performance.now(); while (writtenBytes < totalBytes) { if (abortSignal.aborted) { await writable.abort(); throw new DOMException("Abgebrochen", "AbortError"); } const remaining = totalBytes - writtenBytes; const chunkLen = Math.min(CHUNK_SIZE, remaining); const chunk = new Uint8Array(chunkLen); for (let i = 0; i < chunkLen; i++) { chunk[i] = 0xaa; } await writable.write(chunk); writtenBytes += chunkLen; progressCb((writtenBytes / totalBytes) * 100 * 0.4); } await writable.close(); const writeEnd = performance.now(); logLine(t("fake_ui.log_bench_read_start", "Benchmark: Lesen & Timing..."), "info"); const file = await fileHandle.getFile(); const readStart = performance.now(); const reader = file.stream().getReader(); let readBytes = 0; while (true) { if (abortSignal.aborted) { reader.cancel(); throw new DOMException("Abgebrochen", "AbortError"); } const { done, value } = await reader.read(); if (done) break; readBytes += value.length; progressCb(40 + (readBytes / totalBytes) * 100 * 0.6); } const readEnd = performance.now(); const writeSeconds = (writeEnd - writeStart) / 1000; const readSeconds = (readEnd - readStart) / 1000; report.benchmark = { mode: "benchmark", test_file: TEST_FILENAME, size_bytes: totalBytes, write_bytes: writtenBytes, read_bytes: readBytes, write_duration_s: writeSeconds, read_duration_s: readSeconds, write_mbit_s: writeSeconds ? (writtenBytes * 8 / (writeSeconds * 1e6)) : null, read_mbit_s: readSeconds ? (readBytes * 8 / (readSeconds * 1e6)) : null, ok: true }; try { await dirHandle.removeEntry(TEST_FILENAME); } catch (e) { logLine(t("fake_ui.log_bench_delete_warn", "Benchmark: Konnte Testdatei nicht löschen (nicht kritisch)."), "warn"); } logLine(t("fake_ui.log_bench_success", "Benchmark: Erfolgreich abgeschlossen."), "info"); } // Write/Verify async runWriteVerify(report, progressCb, abortSignal) { const BASE_FILENAME = "usbcheck_block_"; const BLOCKS = 4; const CHUNK_SIZE = 1024 * 1024; const dirHandle = this.rootHandle; if (!dirHandle) throw new Error(t("fake_ui.error_no_directory_selected", "Kein Verzeichnis ausgewählt.")); const totalBytesPlanned = this.planSizeBytes({ defaultMB: 128, minMB: 8, maxFraction: 0.75 }); const blockBytes = Math.max( CHUNK_SIZE, Math.floor(totalBytesPlanned / BLOCKS / CHUNK_SIZE) * CHUNK_SIZE ); const totalBytes = blockBytes * BLOCKS; logLine( tFmt( "fake_ui.log_wv_start", "Write/Verify: Start – {blocks} Blöcke à {size} (gesamt {total})...", { blocks: BLOCKS.toString(), size: formatBytes(blockBytes), total: formatBytes(totalBytes) } ), "info" ); let writtenBytes = 0; let verifiedBytes = 0; const globalStart = performance.now(); const writeDetails = []; const readDetails = []; for (let b = 0; b < BLOCKS; b++) { const filename = `${BASE_FILENAME}${String(b + 1).padStart(2, "0")}.bin`; logLine( tFmt( "fake_ui.log_wv_block_start", "Write/Verify: Block {num}/{blocks} – {file}", { num: (b + 1).toString(), blocks: BLOCKS.toString(), file: filename } ), "info" ); const fileHandle = await dirHandle.getFileHandle(filename, { create: true }); const writable = await fileHandle.createWritable(); const blockBytesTotal = blockBytes; let blockWritten = 0; const blockWriteStart = performance.now(); while (blockWritten < blockBytesTotal) { if (abortSignal.aborted) { await writable.abort(); throw new DOMException("Abgebrochen", "AbortError"); } const remaining = blockBytesTotal - blockWritten; const chunkLen = Math.min(CHUNK_SIZE, remaining); const chunk = new Uint8Array(chunkLen); for (let i = 0; i < chunkLen; i++) { chunk[i] = (i + blockWritten + b * 13) % 251; } await writable.write(chunk); blockWritten += chunkLen; writtenBytes += chunkLen; const progress = (writtenBytes + verifiedBytes) / (totalBytes * 2) * 100; progressCb(progress); } await writable.close(); const blockWriteEnd = performance.now(); writeDetails.push({ block: b + 1, bytes: blockBytesTotal, duration_s: (blockWriteEnd - blockWriteStart) / 1000 }); const file = await fileHandle.getFile(); const reader = file.stream().getReader(); let blockOffset = 0; const blockReadStart = performance.now(); while (true) { if (abortSignal.aborted) { reader.cancel(); throw new DOMException("Abgebrochen", "AbortError"); } const { done, value } = await reader.read(); if (done) break; const chunk = value; for (let i = 0; i < chunk.length; i++) { const expected = (i + blockOffset + b * 13) % 251; if (chunk[i] !== expected) { logLine( tFmt( "fake_ui.log_wv_data_error", "Write/Verify: Datenfehler in Block {num} bei Byte {byte}", { num: (b + 1).toString(), byte: (blockOffset + i).toString() } ), "error" ); throw new Error( tFmt( "fake_ui.error_wv_data_error", "Datenfehler in Block {num} bei Byte {byte}", { num: (b + 1).toString(), byte: (blockOffset + i).toString() } ) ); } } blockOffset += chunk.length; verifiedBytes += chunk.length; const progress = (writtenBytes + verifiedBytes) / (totalBytes * 2) * 100; progressCb(progress); } const blockReadEnd = performance.now(); readDetails.push({ block: b + 1, bytes: blockBytesTotal, duration_s: (blockReadEnd - blockReadStart) / 1000 }); try { await dirHandle.removeEntry(filename); } catch (e) { logLine( tFmt( "fake_ui.log_wv_delete_warn", "Write/Verify: Konnte Blockdatei {file} nicht löschen (nicht kritisch).", { file: filename } ), "warn" ); } } const globalEnd = performance.now(); const totalDuration = (globalEnd - globalStart) / 1000; const sumWrite = writeDetails.reduce((acc, d) => acc + d.bytes, 0); const sumRead = readDetails.reduce((acc, d) => acc + d.bytes, 0); const writeSec = writeDetails.reduce((acc, d) => acc + d.duration_s, 0); const readSec = readDetails.reduce((acc, d) => acc + d.duration_s, 0); report.writeverify = { mode: "writeverify", blocks: BLOCKS, block_size_bytes: blockBytes, total_bytes: totalBytes, written_bytes: writtenBytes, verified_bytes: verifiedBytes, write_duration_s: writeSec, read_duration_s: readSec, write_mbit_s: writeSec ? (sumWrite * 8 / (writeSec * 1e6)) : null, read_mbit_s: readSec ? (sumRead * 8 / (readSec * 1e6)) : null, ok: true }; report.writeverify_total_duration_s = totalDuration; logLine(t("fake_ui.log_wv_success", "Write/Verify: Alle Blöcke erfolgreich verifiziert."), "info"); } async run(mode, updateProgressCb, abortSignal) { if (!this.rootHandle) { throw new Error(t("fake_ui.error_no_directory_selected", "Kein Verzeichnis ausgewählt.")); } await this.ensureCapacity(abortSignal); const report = { meta: { base_url: cfg.baseUrl || "", locale: cfg.locale || "en", user_agent: navigator.userAgent, started_at: new Date().toISOString() }, tool: "usbcheck_browser", tool_version: "0.1.0", mode_requested: mode, quick: null, benchmark: null, writeverify: null, total_duration_s: null }; const t0 = performance.now(); if (mode === "quick") { await this.runQuickCheck(report, updateProgressCb, abortSignal); } else if (mode === "benchmark") { await this.runBenchmark(report, updateProgressCb, abortSignal); } else if (mode === "writeverify") { await this.runWriteVerify(report, updateProgressCb, abortSignal); } else if (mode === "all") { const modes = ["quick", "benchmark", "writeverify"]; for (let i = 0; i < modes.length; i++) { const subMode = modes[i]; logLine( tFmt( "fake_ui.log_all_subtest_start", "All-Inclusive: Starte Teiltest \"{mode}\" ({num}/{total})...", { mode: subMode, num: (i + 1).toString(), total: modes.length.toString() } ), "info" ); const base = (i / modes.length) * 100; const span = (1 / modes.length) * 100; const mapProgress = (p) => { const mapped = base + (p / 100) * span; updateProgressCb(mapped); }; if (subMode === "quick") { await this.runQuickCheck(report, mapProgress, abortSignal); } else if (subMode === "benchmark") { await this.runBenchmark(report, mapProgress, abortSignal); } else if (subMode === "writeverify") { await this.runWriteVerify(report, mapProgress, abortSignal); } } } else { throw new Error(tFmt("fake_ui.error_unknown_mode", "Unbekannter Modus: {mode}", { mode })); } const t1 = performance.now(); report.total_duration_s = (t1 - t0) / 1000; report.meta.ended_at = new Date().toISOString(); return report; } } const tester = new UsbBrowserTester(); let currentMode = null; let isRunning = false; if (!tester.hasFsApiSupport()) { if (fsapiWarning) fsapiWarning.style.display = "block"; logLine( t("fake_ui.log_fsapi_partial", "Dein Browser unterstützt die File System Access API nicht voll. Einige Funktionen sind deaktiviert."), "warn" ); } function updateStartButtonState() { const hasDir = !!tester.rootHandle; const hasMode = !!currentMode; if (btnStart) btnStart.disabled = !(hasDir && hasMode && !isRunning); if (btnCancel) btnCancel.disabled = !isRunning; } // --- Event-Handler ------------------------------------------------------ let isPickingDir = false; if (btnPickDir) { btnPickDir.addEventListener("click", () => { if (isPickingDir) return; isPickingDir = true; if (!("showDirectoryPicker" in window)) { logLine(t("fake_ui.error_fsapi_not_supported", "File System Access API wird von diesem Browser nicht unterstützt."), "error"); if (fsapiWarning) fsapiWarning.style.display = "block"; isPickingDir = false; return; } window.showDirectoryPicker() .then((handle) => { tester.rootHandle = handle; tester.capacityBytes = null; if (selectedPathText) { selectedPathText.textContent = tFmt( "fake_ui.selected_path_label", 'USB-Ordner ausgewählt (Name: "{name}").', { name: (handle.name || "Unbekannt") } ); } if (btnClearSel) btnClearSel.disabled = false; setStatus(t("fake_ui.status_dir_selected", "USB-Verzeichnis ausgewählt. Wähle jetzt einen Testmodus.")); logLine( tFmt( "fake_ui.log_dir_selected", "Verzeichnis ausgewählt: {name}", { name: (handle.name || "[ohne Namen]") } ), "info" ); updateStartButtonState(); }) .catch((err) => { if (err && err.name === "AbortError") { logLine(t("fake_ui.log_pick_abort", "Verzeichnisauswahl abgebrochen."), "warn"); } else if (err) { logLine( tFmt( "fake_ui.log_pick_error", "Fehler bei Verzeichnisauswahl: {msg}", { msg: err.message || "unbekannt" } ), "error" ); } else { logLine(t("fake_ui.log_pick_error_unknown", "Unbekannter Fehler bei Verzeichnisauswahl."), "error"); } }) .finally(() => { isPickingDir = false; }); }); } if (btnClearSel) { btnClearSel.addEventListener("click", async () => { await tester.clearSelection(); if (selectedPathText) { selectedPathText.textContent = t("fake_ui.selected_path_none", "Noch kein Verzeichnis gewählt."); } btnClearSel.disabled = true; setStatus(t("fake_ui.status_ready", "Bereit. Wähle zuerst deinen USB-Stick aus.")); logLine(t("fake_ui.log_dir_reset", "Verzeichnisauswahl zurückgesetzt."), "info"); updateStartButtonState(); }); } modeTiles.forEach((tile) => { tile.addEventListener("click", () => { if (tile.classList.contains("disabled")) return; if (isRunning) return; modeTiles.forEach((tEl) => tEl.classList.remove("selected")); tile.classList.add("selected"); currentMode = tile.getAttribute("data-mode"); const titleEl = tile.querySelector("h4"); const label = titleEl ? titleEl.textContent : currentMode; setModeLabel(label || ""); setStatus( tFmt( "fake_ui.status_mode_selected", 'Modus "{mode}" ausgewählt. Du kannst den Test jetzt starten.', { mode: label || currentMode || "" } ) ); logLine( tFmt( "fake_ui.log_mode_selected", "Modus gewählt: {mode}", { mode: currentMode || "" } ), "info" ); updateStartButtonState(); }); }); if (btnStart) { btnStart.addEventListener("click", async () => { if (!currentMode || !tester.rootHandle || isRunning) return; isRunning = true; updateStartButtonState(); if (btnCancel) btnCancel.disabled = false; if (saveError) saveError.style.display = "none"; setProgress(0); logLine( tFmt( "fake_ui.log_test_start", "Starte Tests im Modus: {mode}", { mode: (currentMode || "").toUpperCase() } ), "info" ); setStatus(t("fake_ui.status_running", "Test läuft... bitte USB-Stick nicht entfernen.")); setOverallStatus("warn", t("fake_ui.overall_running", "Test läuft...")); const abortController = new AbortController(); tester.abortController = abortController; const updateProgressCb = (percent) => setProgress(percent); let report = null; try { report = await tester.run(currentMode, updateProgressCb, abortController.signal); setProgress(100); setStatus(t("fake_ui.status_done", "Test abgeschlossen.")); setOverallStatus("ok", t("fake_ui.overall_done", "Browser-Test erfolgreich abgeschlossen.")); applyReportToDashboard(report); console.log("USB Browser Test Report (fakecheck):", report); await saveReportToBackend(report); } catch (err) { if (err && err.name === "AbortError") { setStatus(t("fake_ui.status_aborted", "Test wurde abgebrochen.")); setOverallStatus("warn", t("fake_ui.overall_aborted", "Test abgebrochen.")); logLine(t("fake_ui.log_test_aborted", "Test wurde vom Benutzer abgebrochen."), "warn"); } else if (err) { setStatus( tFmt( "fake_ui.status_error", "Fehler: {msg}", { msg: err.message || "unbekannt" } ) ); setOverallStatus("bad", t("fake_ui.overall_error", "Fehler im Browser-Test.")); logLine( tFmt( "fake_ui.log_test_error", "Fehler im Test: {msg}", { msg: err.message || "unbekannt" } ), "error" ); } else { setStatus(t("fake_ui.status_error_unknown", "Unbekannter Fehler im Test.")); setOverallStatus("bad", t("fake_ui.overall_error", "Fehler im Browser-Test.")); logLine(t("fake_ui.log_test_error_unknown", "Unbekannter Fehler im Test."), "error"); } } finally { isRunning = false; tester.abortController = null; updateStartButtonState(); if (btnCancel) btnCancel.disabled = true; } }); } if (btnCancel) { btnCancel.addEventListener("click", () => { if (!isRunning || !tester.abortController) return; tester.abortController.abort(); }); } // --- Dashboard-Füllung -------------------------------------------------- function applyReportToDashboard(report) { const modeLabelMap = { quick: t("fake_ui.mode_quick", "Quick-Check"), benchmark: t("fake_ui.mode_benchmark", "Benchmark"), writeverify: t("fake_ui.mode_writeverify", "Write & Verify"), all: t("fake_ui.mode_all", "All-Inclusive (alle Browser-Tests)") }; if (resMode) { resMode.textContent = modeLabelMap[report.mode_requested] || report.mode_requested || "–"; } if (resDuration) { resDuration.textContent = formatDuration(report.total_duration_s); } let aggWriteBytes = 0; let aggReadBytes = 0; let writeSec = 0; let readSec = 0; if (report.quick) { aggWriteBytes += report.quick.write_bytes || 0; aggReadBytes += report.quick.read_bytes || 0; writeSec += report.quick.write_duration_s || 0; readSec += report.quick.read_duration_s || 0; } if (report.benchmark) { aggWriteBytes += report.benchmark.write_bytes || 0; aggReadBytes += report.benchmark.read_bytes || 0; writeSec += report.benchmark.write_duration_s || 0; readSec += report.benchmark.read_duration_s || 0; } if (report.writeverify) { aggWriteBytes += report.writeverify.written_bytes || 0; aggReadBytes += report.writeverify.verified_bytes || 0; writeSec += report.writeverify.write_duration_s || 0; readSec += report.writeverify.read_duration_s || 0; } if (resWritten) { resWritten.textContent = aggWriteBytes ? formatBytes(aggWriteBytes) : "–"; } if (resVerified) { resVerified.textContent = aggReadBytes ? formatBytes(aggReadBytes) : "–"; } const avgWriteMbps = (aggWriteBytes && writeSec) ? formatMbps(aggWriteBytes, writeSec) : "–"; const avgReadMbps = (aggReadBytes && readSec) ? formatMbps(aggReadBytes, readSec) : "–"; if (resWriteSpeed) resWriteSpeed.textContent = avgWriteMbps; if (resReadSpeed) resReadSpeed.textContent = avgReadMbps; } // --- Backend-Speicherung ------------------------------------------------ async function saveReportToBackend(report) { // apiBase kommt aus fakecheck.core.js (detectApiBase) const apiBase = (cfg && cfg.apiBase) ? cfg.apiBase : ""; const base = apiBase.replace(/\/+$/, ""); // Fallback: falls apiBase aus irgendeinem Grund leer ist, lokal auf /api routen const url = base ? (base + "/browser.quick.test") : "/api/browser.quick.test"; try { const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(report), credentials: "include" }); if (!response.ok) { const msg = "HTTP " + response.status; logLine( tFmt( "fake_ui.log_backend_save_error_status", "Backend: Konnte Ergebnis nicht speichern ({status}).", { status: msg } ), "warn" ); if (response.status >= 500 && saveError) { saveError.style.display = "block"; } return; } const data = await response.json().catch(() => null); if (!data || data.ok !== true) { logLine( tFmt( "fake_ui.log_backend_save_error_payload", "Backend: Testergebnis wurde nicht bestätigt{suffix}.", { suffix: data && data.error ? ` (${data.error})` : "" } ), "warn" ); if (saveError) saveError.style.display = "block"; return; } logLine( tFmt( "fake_ui.log_backend_save_ok", "Backend: Testergebnis gespeichert{suffix}", { suffix: (data && data.id ? ` (ID: ${data.id})` : "") } ), "info" ); } catch (err) { if (saveError) saveError.style.display = "block"; logLine( tFmt( "fake_ui.log_backend_save_error", "Fehler beim Speichern im Backend: {msg}", { msg: err ? err.message || "unbekannt" : "unbekannt" } ), "error" ); } } // --- Initialzustand ----------------------------------------------------- setStatus(t("fake_ui.status_ready", "Bereit. Wähle zuerst deinen USB-Stick aus.")); setModeLabel(t("fake_ui.status_mode_none", "Kein Modus selektiert")); setOverallStatus("ok", t("fake_ui.overall_initial", "Noch kein Test durchgeführt.")); logLine( t("fake_ui.log_loaded", "USB-Browser-Test (fakecheck) geladen. Warte auf Verzeichnisauswahl und Modus."), "info" ); updateStartButtonState(); })();