Files
usbcheck.it/public/assets/js/fakecheck/fakecheck.browser.js
2025-11-30 02:51:15 +01:00

971 lines
38 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// /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(/\/+$/, "");
// v1-Endpunkt
const url = base ? (base + "/v1/browser.quick.test") : "/api/v1/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();
})();