diff --git a/partials/structure/app_config.php b/partials/structure/app_config.php index 94905b9..b6db9de 100644 --- a/partials/structure/app_config.php +++ b/partials/structure/app_config.php @@ -6,9 +6,11 @@ $host = $_SERVER['HTTP_HOST'] ?? app_primary_domain(); $requestUri = $_SERVER['REQUEST_URI'] ?? '/'; $usbConfig = [ - 'lang' => $lang ?? 'en', - 'assetsBase'=> '/assets', - 'env' => $GLOBALS['ENV'] ?? 'prod', + 'lang' => $lang ?? 'en', + 'assetsBase' => '/assets', + // NEU: Versionierung für JS/CSS aus PHP-Config + 'assetVersion'=> defined('ASSET_VERSION') ? ASSET_VERSION : null, + 'env' => $GLOBALS['ENV'] ?? 'prod', 'domains' => [ 'primaryDomain' => app_primary_domain(), @@ -18,9 +20,9 @@ $usbConfig = [ ], 'fakecheck' => [ - 'baseUrl' => $GLOBALS['usb_base_url'] ?? '', - 'apiBaseUrl'=> $GLOBALS['usb_api_base'] ?? 'https://api.usbcheck.it', - 'locale' => $lang ?? 'en', + 'baseUrl' => $GLOBALS['usb_base_url'] ?? '', + 'apiBaseUrl' => $GLOBALS['usb_api_base'] ?? 'https://api.usbcheck.it', + 'locale' => $lang ?? 'en', ], ]; ?> diff --git a/public/assets/js/fakecheck.js b/public/assets/js/fakecheck.js index 9c9f8e5..11ba591 100644 --- a/public/assets/js/fakecheck.js +++ b/public/assets/js/fakecheck.js @@ -1,895 +1,37 @@ // /public/assets/js/fakecheck.js - -document.addEventListener("DOMContentLoaded", () => { - // Zentrale Config aus PHP (app_config.php o.ä.) - const cfg = window.usbConfig || {}; - const baseUrl = cfg.baseUrl || ""; - const locale = (cfg.lang || "en").toLowerCase(); - - // Neu: API-Basis-URL (prod/staging) – über Config oder Fallback anhand Host - function detectApiBaseUrl() { - // Fallback, falls in usbConfig nichts hinterlegt ist - const host = window.location.hostname || ""; - if (host === "staging.usbcheck.it" || host.endsWith(".staging.usbcheck.it")) { - return "https://api.staging.usbcheck.it"; - } - return "https://api.usbcheck.it"; - } - - const apiBaseUrl = cfg.apiBaseUrl || detectApiBaseUrl(); - - const root = document.getElementById("fc-root"); - if (!root) { - // Auf anderen Seiten eingebunden? Dann einfach nichts tun. - return; - } - - // --- DOM-Helper --------------------------------------------------------- - const $ = (sel) => document.querySelector(sel); - const $$ = (sel) => Array.from(document.querySelectorAll(sel)); - - const logEl = $("#fc-log"); - const statusTextEl = $("#fc-status-text"); - const statusModeEl = $("#fc-status-mode"); - const progressInner = $("#fc-progress-inner"); - const overallPill = $("#fc-overall-status-pill"); - const overallStatus = $("#fc-overall-status-text"); - 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 aus usbConfig - if (cfg.isLoggedIn && saveHint) { - saveHint.style.display = "block"; - } - - function logLine(message, level = "info") { - if (!logEl) return; - const line = document.createElement("div"); - line.className = "fc-log-line"; - const prefix = - level === "error" ? "[ERROR] " : - level === "warn" ? "[WARN] " : - "[INFO] "; - line.innerHTML = `${prefix}${message}`; - logEl.appendChild(line); - logEl.scrollTop = logEl.scrollHeight; - } - - function setStatus(text) { - if (statusTextEl) statusTextEl.textContent = text; - } - - function setModeLabel(text) { - if (statusModeEl) statusModeEl.textContent = text; - } - - function setProgress(percent) { - if (!progressInner) return; - const v = Math.min(100, Math.max(0, percent)); - progressInner.style.width = v + "%"; - } - - function setOverallStatus(state, text) { - if (!overallPill || !overallStatus) return; - overallPill.classList.remove("fc-pill-ok", "fc-pill-warn", "fc-pill-bad"); - if (state === "ok") overallPill.classList.add("fc-pill-ok"); - else if (state === "warn") overallPill.classList.add("fc-pill-warn"); - else overallPill.classList.add("fc-pill-bad"); - overallStatus.textContent = text; - } - - function formatBytes(bytes) { - if (bytes == null) return "–"; - const units = ["B", "KB", "MB", "GB", "TB"]; - let u = 0; - let v = bytes; - while (v >= 1024 && u < units.length - 1) { - v /= 1024; - u++; - } - return v.toFixed(1) + " " + units[u]; - } - - function formatMbps(bytes, seconds) { - if (!seconds || seconds <= 0) return "–"; - const bits = bytes * 8; - const mbits = bits / (1000 * 1000); - return mbits.toFixed(1) + " Mbit/s"; - } - - function formatDuration(seconds) { - if (!seconds) return "–"; - const s = Math.round(seconds); - if (s < 60) return s + " s"; - const m = Math.floor(s / 60); - const r = s % 60; - if (m < 60) return `${m} min ${r}s`; - const h = Math.floor(m / 60); - const rm = m % 60; - return `${h} h ${rm} min`; - } - - // --- Test-Engine (Browser) --------------------------------------------- - - class UsbBrowserTester { - constructor() { - this.rootHandle = null; - this.abortController = null; - } - - hasFsApiSupport() { - return "showDirectoryPicker" in window; - } - - // Kann theoretisch bleiben, wird aktuell aber NICHT benutzt, - // damit showDirectoryPicker direkt im Click-Handler liegt. - async pickDirectory() { - if (!this.hasFsApiSupport()) { - throw new Error("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; - } - - async runQuickCheck(report, progressCb, abortSignal) { - const TEST_FILENAME = "usbcheck_quick_test.bin"; - const TEST_SIZE_MB = 8; - const CHUNK_SIZE = 1024 * 1024; - - const dirHandle = this.rootHandle; - if (!dirHandle) throw new Error("Kein Verzeichnis ausgewählt."); - - logLine("Quick-Check: Vorbereitung...", "info"); - const fileHandle = await dirHandle.getFileHandle(TEST_FILENAME, { create: true }); - const writable = await fileHandle.createWritable(); - - const totalBytes = TEST_SIZE_MB * 1024 * 1024; - 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("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(`Quick-Check: Datenfehler bei Byte ${offset + i}`, "error"); - throw new Error(`Datenfehler im Quick-Check bei Byte ${offset + i}`); - } - } - 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 - }; - - logLine("Quick-Check: Erfolgreich abgeschlossen.", "info"); - } - - async runBenchmark(report, progressCb, abortSignal) { - const TEST_FILENAME = "usbcheck_benchmark.bin"; - const TEST_SIZE_MB = 32; - const CHUNK_SIZE = 1024 * 1024; - - const dirHandle = this.rootHandle; - if (!dirHandle) throw new Error("Kein Verzeichnis ausgewählt."); - - logLine("Benchmark: Start – schreibe Testdatei...", "info"); - const fileHandle = await dirHandle.getFileHandle(TEST_FILENAME, { create: true }); - const writable = await fileHandle.createWritable(); - - const totalBytes = TEST_SIZE_MB * 1024 * 1024; - 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("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 - }; - - logLine("Benchmark: Erfolgreich abgeschlossen.", "info"); - } - - async runWriteVerify(report, progressCb, abortSignal) { - const BASE_FILENAME = "usbcheck_block_"; - const BLOCKS = 4; - const BLOCK_SIZE_MB = 32; - const CHUNK_SIZE = 1024 * 1024; - - const dirHandle = this.rootHandle; - if (!dirHandle) throw new Error("Kein Verzeichnis ausgewählt."); - - logLine("Write/Verify: Start – mehrere Blöcke werden getestet...", "info"); - - const totalBytes = BLOCKS * BLOCK_SIZE_MB * 1024 * 1024; - 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(`Write/Verify: Block ${b + 1}/${BLOCKS} – ${filename}`, "info"); - - const fileHandle = await dirHandle.getFileHandle(filename, { create: true }); - const writable = await fileHandle.createWritable(); - - const blockBytes = BLOCK_SIZE_MB * 1024 * 1024; - let blockWritten = 0; - const blockWriteStart = performance.now(); - - while (blockWritten < blockBytes) { - if (abortSignal.aborted) { - await writable.abort(); - throw new DOMException("Abgebrochen", "AbortError"); - } - const remaining = blockBytes - 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: blockBytes, - 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(`Write/Verify: Datenfehler in Block ${b + 1} bei Byte ${blockOffset + i}`, "error"); - throw new Error(`Datenfehler in Block ${b + 1} bei Byte ${blockOffset + i}`); - } - } - 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: blockBytes, - duration_s: (blockReadEnd - blockReadStart) / 1000 - }); - } - - 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_mb: BLOCK_SIZE_MB, - 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("Write/Verify: Alle Blöcke erfolgreich verifiziert.", "info"); - } - - async run(mode, updateProgressCb, abortSignal) { - if (!this.rootHandle) { - throw new Error("Kein USB-Verzeichnis ausgewählt."); - } - - const report = { - meta: { - base_url: baseUrl, - locale: locale, - 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(`All-Inclusive: Starte Teiltest "${subMode}" (${i + 1}/${modes.length})...`, "info"); - await this.run( - subMode, - (p) => { - const base = (i / modes.length) * 100; - const span = (1 / modes.length) * 100; - updateProgressCb(base + (p / 100) * span); - }, - abortSignal - ); - } - } else { - throw new Error("Unbekannter Modus: " + 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( - "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 ------------------------------------------------------ - - // WICHTIG: showDirectoryPicker direkt im Click-Handler (ohne async/await), - // damit "User activation is required" nicht mehr triggert. - let isPickingDir = false; - - if (btnPickDir) { - btnPickDir.addEventListener("click", () => { - if (isPickingDir) return; // Doppelklick-Schutz - isPickingDir = true; - - if (!("showDirectoryPicker" in window)) { - logLine("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; - if (selectedPathText) { - selectedPathText.textContent = - 'USB-Ordner ausgewählt (Name: "' + (handle.name || "Unbekannt") + '").'; - } - if (btnClearSel) btnClearSel.disabled = false; - setStatus("USB-Verzeichnis ausgewählt. Wähle jetzt einen Testmodus."); - logLine("Verzeichnis ausgewählt: " + (handle.name || "[ohne Namen]")); - updateStartButtonState(); - }) - .catch((err) => { - if (err && err.name === "AbortError") { - logLine("Verzeichnisauswahl abgebrochen.", "warn"); - } else if (err) { - logLine("Fehler bei Verzeichnisauswahl: " + err.message, "error"); - } else { - logLine("Unbekannter Fehler bei Verzeichnisauswahl.", "error"); - } - }) - .finally(() => { - isPickingDir = false; - }); +// Loader für alle Fakecheck-Module + +(function () { + const cfg = window.usbConfig || {}; + const assetsBase = cfg.assetsBase || "/assets"; + + // Version aus PHP-Config (ASSET_VERSION) → app_config.php + const assetVersion = cfg.assetVersion; + const versionQuery = assetVersion + ? ("?v=" + encodeURIComponent(assetVersion)) + : ""; + + // Basis-Pfad für die Teil-Skripte + const base = assetsBase.replace(/\/+$/, "") + "/js/fakecheck/"; + + const scripts = [ + base + "fakecheck.core.js" + versionQuery, + base + "fakecheck.browser.js" + versionQuery, + base + "fakecheck.serial.js" + versionQuery + ]; + + function loadScript(src) { + return new Promise((resolve, reject) => { + const s = document.createElement("script"); + s.src = src; + s.async = false; // Reihenfolge sicherstellen + s.onload = resolve; + s.onerror = () => reject(new Error("Konnte " + src + " nicht laden")); + document.head.appendChild(s); }); } - if (btnClearSel) { - btnClearSel.addEventListener("click", async () => { - await tester.clearSelection(); - if (selectedPathText) { - selectedPathText.textContent = "Noch kein Verzeichnis gewählt."; - } - btnClearSel.disabled = true; - setStatus("Bereit. Wähle zuerst deinen USB-Stick aus."); - logLine("Verzeichnisauswahl zurückgesetzt.", "info"); - updateStartButtonState(); - }); - } - - modeTiles.forEach((tile) => { - tile.addEventListener("click", () => { - if (tile.classList.contains("disabled")) return; - if (isRunning) return; - modeTiles.forEach((t) => t.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(`Modus "${label}" ausgewählt. Du kannst den Test jetzt starten.`); - logLine("Modus gewählt: " + 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("Starte Tests im Modus: " + currentMode.toUpperCase(), "info"); - setStatus("Test läuft... bitte USB-Stick nicht entfernen."); - setOverallStatus("warn", "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("Test abgeschlossen."); - setOverallStatus("ok", "Browser-Test erfolgreich abgeschlossen."); - applyReportToDashboard(report); - - // Debug / Support: - console.log("USB Browser Test Report (fakecheck):", report); - - // Ergebnis speichern: Backend entscheidet, ob der User eingeloggt ist - await saveReportToBackend(report); - } catch (err) { - if (err && err.name === "AbortError") { - setStatus("Test wurde abgebrochen."); - setOverallStatus("warn", "Test abgebrochen."); - logLine("Test wurde vom Benutzer abgebrochen.", "warn"); - } else if (err) { - setStatus("Fehler: " + err.message); - setOverallStatus("bad", "Fehler im Browser-Test."); - logLine("Fehler im Test: " + err.message, "error"); - } else { - setStatus("Unbekannter Fehler im Test."); - setOverallStatus("bad", "Fehler im Browser-Test."); - logLine("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: "Quick-Check", - benchmark: "Benchmark", - writeverify: "Write & Verify", - 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) { - const url = "/api/result/browser-quick-test.php"; - 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("Backend: Konnte Ergebnis nicht speichern (" + msg + ").", "warn"); - if (response.status >= 500 && saveError) { - saveError.style.display = "block"; - } - return; - } - - const data = await response.json().catch(() => null); - logLine( - "Backend: Testergebnis gespeichert" + - (data && data.id ? ` (ID: ${data.id})` : ""), - "info" - ); - } catch (err) { - if (saveError) saveError.style.display = "block"; - logLine("Fehler beim Speichern im Backend: " + (err ? err.message : "unbekannt"), "error"); - } - } - - // --- Seriennummer-Quickcheck (API-gestützt) ----------------------------- - - function initSerialCheckWidget() { - const rootSc = document.getElementById("serialcheck-root"); - if (!rootSc) return; // Serialcheck-Partial nicht eingebunden → nichts tun - - const form = rootSc.querySelector("#serialcheck-form"); - const errorBox = rootSc.querySelector("#serialcheck-error"); - const resultBox = rootSc.querySelector("#serialcheck-result"); - - const manufacturerInput = rootSc.querySelector("#sc-manufacturer"); - const vidInput = rootSc.querySelector("#sc-vid"); - const pidInput = rootSc.querySelector("#sc-pid"); - const serialInput = rootSc.querySelector("#sc-serial"); - - function showScError(msg) { - if (!errorBox) return; - errorBox.textContent = msg || "Es ist ein Fehler aufgetreten."; - errorBox.classList.remove("hidden"); - if (resultBox) resultBox.classList.add("hidden"); - } - - function clearScError() { - if (!errorBox) return; - errorBox.classList.add("hidden"); - errorBox.textContent = ""; - } - - function renderScResult(data) { - if (!resultBox) return; - clearScError(); - resultBox.classList.remove("hidden"); - - const rating = data.rating || "unknown"; - let ratingLabel = ""; - let ratingDesc = ""; - - if (rating === "ok") { - ratingLabel = "Plausibel"; - ratingDesc = "Keine deutlichen Auffälligkeiten erkannt."; - } else if (rating === "needs_review") { - ratingLabel = "Überprüfen empfohlen"; - ratingDesc = "Leichte Auffälligkeiten. In Kombination mit einem technischen Test ergibt sich ein klareres Bild."; - } else if (rating === "suspicious") { - ratingLabel = "Auffällig / Verdächtig"; - ratingDesc = "Deutliche Auffälligkeiten erkannt. Ein Kapazitäts-/Geschwindigkeitstest ist dringend empfohlen."; - } else if (rating === "invalid") { - ratingLabel = "Ungültig"; - ratingDesc = "Die Seriennummer konnte nicht sinnvoll bewertet werden."; - } else { - ratingLabel = "Unklar"; - ratingDesc = "Bewertung nicht eindeutig möglich."; - } - - const input = data.input || {}; - const vendorInfo = data.vendor_detected || {}; - const serialInfo = data.serial_analysis || {}; - const consistency = data.consistency || {}; - const issues = serialInfo.issues || []; - const notes = consistency.notes || []; - - const issuesList = issues.length - ? '" - : 'Keine besonderen Auffälligkeiten.'; - - const vendorLine = vendorInfo.found - ? (vendorInfo.vendor + " (VID " + vendorInfo.vid + ")") - : (vendorInfo.vid ? ("Unbekannter Hersteller für VID " + vendorInfo.vid) : "Keine Vendor-ID angegeben"); - - const notesList = notes.length - ? '" - : ""; - - resultBox.innerHTML = ` -
- - Bewertung: ${ratingLabel} - -

${ratingDesc}

-
- -
-

Eingabedaten

-
-
Hersteller (Angabe):
${input.manufacturer || 'keine Angabe'}
-
VID / PID:
${(input.vid || "–") + " / " + (input.pid || "–")}
-
Vendor aus VID:
${vendorLine}
-
-
- -
-

Seriennummer-Analyse

-
-
Seriennummer:
${serialInfo.serial || ""}
-
Länge:
${serialInfo.length || 0} Zeichen
-
Kategorie:
${serialInfo.category || "-"}
-
Score:
${typeof serialInfo.score === "number" ? serialInfo.score : "-"} / 100
-
Auffälligkeiten:
${issuesList}
-
-
- -
-

Hersteller-Konsistenz

- ${notesList} -

- Diese Einschätzung basiert auf Heuristiken und kann keine Echtheit garantieren. -

-
- `; - } - - if (!form) return; - - form.addEventListener("submit", (e) => { - e.preventDefault(); - clearScError(); - if (resultBox) resultBox.classList.add("hidden"); - - const payload = { - manufacturer: manufacturerInput ? manufacturerInput.value.trim() : "", - vid: vidInput ? vidInput.value.trim() : "", - pid: pidInput ? pidInput.value.trim() : "", - serial: serialInput ? serialInput.value.trim() : "" - }; - - if (!payload.serial) { - showScError("Bitte gib eine Seriennummer ein."); - return; - } - - fetch(apiBaseUrl.replace(/\/+$/, "") + "/quickcheck", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload) - }) - .then(res => { - if (!res.ok) { - throw new Error("Server returned status " + res.status); - } - return res.json(); - }) - .then(data => { - if (!data || !data.success) { - throw new Error((data && data.error) || "Unerwartete Antwort vom Server."); - } - renderScResult(data); - }) - .catch(err => { - showScError("Fehler bei der Prüfung: " + err.message); - }); - }); - } - - // --- Initialzustand ----------------------------------------------------- - - setStatus("Bereit. Wähle zuerst deinen USB-Stick aus."); - setModeLabel("Kein Modus selektiert"); - setOverallStatus("ok", "Noch kein Test durchgeführt."); - logLine("USB-Browser-Test (fakecheck) geladen. Warte auf Verzeichnisauswahl und Modus."); - updateStartButtonState(); - - // Serialcheck nur initialisieren, wenn das Partial vorhanden ist - initSerialCheckWidget(); -}); + scripts + .reduce((p, src) => p.then(() => loadScript(src)), Promise.resolve()) + .catch(err => console.error("Fakecheck Loader Fehler:", err)); +})(); diff --git a/public/assets/js/fakecheck/fakecheck.browser.js b/public/assets/js/fakecheck/fakecheck.browser.js new file mode 100644 index 0000000..fde29a5 --- /dev/null +++ b/public/assets/js/fakecheck/fakecheck.browser.js @@ -0,0 +1,660 @@ +// /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 + } = 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; + } + + hasFsApiSupport() { + return "showDirectoryPicker" in window; + } + + // Wird aktuell nicht mehr verwendet (Picker ist direkt im Click-Handler), + // kann aber als Fallback/Refactoring-Hook bleiben. + async pickDirectory() { + if (!this.hasFsApiSupport()) { + throw new Error("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; + } + + async runQuickCheck(report, progressCb, abortSignal) { + const TEST_FILENAME = "usbcheck_quick_test.bin"; + const TEST_SIZE_MB = 8; + const CHUNK_SIZE = 1024 * 1024; + + const dirHandle = this.rootHandle; + if (!dirHandle) throw new Error("Kein Verzeichnis ausgewählt."); + + logLine("Quick-Check: Vorbereitung...", "info"); + const fileHandle = await dirHandle.getFileHandle(TEST_FILENAME, { create: true }); + const writable = await fileHandle.createWritable(); + + const totalBytes = TEST_SIZE_MB * 1024 * 1024; + 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("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(`Quick-Check: Datenfehler bei Byte ${offset + i}`, "error"); + throw new Error(`Datenfehler im Quick-Check bei Byte ${offset + i}`); + } + } + 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 + }; + + logLine("Quick-Check: Erfolgreich abgeschlossen.", "info"); + } + + async runBenchmark(report, progressCb, abortSignal) { + const TEST_FILENAME = "usbcheck_benchmark.bin"; + const TEST_SIZE_MB = 32; + const CHUNK_SIZE = 1024 * 1024; + + const dirHandle = this.rootHandle; + if (!dirHandle) throw new Error("Kein Verzeichnis ausgewählt."); + + logLine("Benchmark: Start – schreibe Testdatei...", "info"); + const fileHandle = await dirHandle.getFileHandle(TEST_FILENAME, { create: true }); + const writable = await fileHandle.createWritable(); + + const totalBytes = TEST_SIZE_MB * 1024 * 1024; + 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("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 + }; + + logLine("Benchmark: Erfolgreich abgeschlossen.", "info"); + } + + async runWriteVerify(report, progressCb, abortSignal) { + const BASE_FILENAME = "usbcheck_block_"; + const BLOCKS = 4; + const BLOCK_SIZE_MB = 32; + const CHUNK_SIZE = 1024 * 1024; + + const dirHandle = this.rootHandle; + if (!dirHandle) throw new Error("Kein Verzeichnis ausgewählt."); + + logLine("Write/Verify: Start – mehrere Blöcke werden getestet...", "info"); + + const totalBytes = BLOCKS * BLOCK_SIZE_MB * 1024 * 1024; + 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(`Write/Verify: Block ${b + 1}/${BLOCKS} – ${filename}`, "info"); + + const fileHandle = await dirHandle.getFileHandle(filename, { create: true }); + const writable = await fileHandle.createWritable(); + + const blockBytes = BLOCK_SIZE_MB * 1024 * 1024; + let blockWritten = 0; + const blockWriteStart = performance.now(); + + while (blockWritten < blockBytes) { + if (abortSignal.aborted) { + await writable.abort(); + throw new DOMException("Abgebrochen", "AbortError"); + } + const remaining = blockBytes - 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: blockBytes, + 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(`Write/Verify: Datenfehler in Block ${b + 1} bei Byte ${blockOffset + i}`, "error"); + throw new Error(`Datenfehler in Block ${b + 1} bei Byte ${blockOffset + i}`); + } + } + 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: blockBytes, + duration_s: (blockReadEnd - blockReadStart) / 1000 + }); + } + + 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_mb: BLOCK_SIZE_MB, + 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("Write/Verify: Alle Blöcke erfolgreich verifiziert.", "info"); + } + + async run(mode, updateProgressCb, abortSignal) { + if (!this.rootHandle) { + throw new Error("Kein USB-Verzeichnis ausgewählt."); + } + + 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") { + // Alle drei Tests nacheinander im selben Report + const modes = ["quick", "benchmark", "writeverify"]; + for (let i = 0; i < modes.length; i++) { + const subMode = modes[i]; + logLine(`All-Inclusive: Starte Teiltest "${subMode}" (${i + 1}/${modes.length})...`, "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("Unbekannter Modus: " + 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( + "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 ------------------------------------------------------ + + // Wichtig: showDirectoryPicker direkt im Click-Handler per Promise, + // kein async/await direkt im Listener → vermeidet "User activation is required". + let isPickingDir = false; + + if (btnPickDir) { + btnPickDir.addEventListener("click", () => { + if (isPickingDir) return; + isPickingDir = true; + + if (!("showDirectoryPicker" in window)) { + logLine("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; + if (selectedPathText) { + selectedPathText.textContent = + 'USB-Ordner ausgewählt (Name: "' + (handle.name || "Unbekannt") + '").'; + } + if (btnClearSel) btnClearSel.disabled = false; + setStatus("USB-Verzeichnis ausgewählt. Wähle jetzt einen Testmodus."); + logLine("Verzeichnis ausgewählt: " + (handle.name || "[ohne Namen]")); + updateStartButtonState(); + }) + .catch((err) => { + if (err && err.name === "AbortError") { + logLine("Verzeichnisauswahl abgebrochen.", "warn"); + } else if (err) { + logLine("Fehler bei Verzeichnisauswahl: " + err.message, "error"); + } else { + logLine("Unbekannter Fehler bei Verzeichnisauswahl.", "error"); + } + }) + .finally(() => { + isPickingDir = false; + }); + }); + } + + if (btnClearSel) { + btnClearSel.addEventListener("click", async () => { + await tester.clearSelection(); + if (selectedPathText) { + selectedPathText.textContent = "Noch kein Verzeichnis gewählt."; + } + btnClearSel.disabled = true; + setStatus("Bereit. Wähle zuerst deinen USB-Stick aus."); + logLine("Verzeichnisauswahl zurückgesetzt.", "info"); + updateStartButtonState(); + }); + } + + modeTiles.forEach((tile) => { + tile.addEventListener("click", () => { + if (tile.classList.contains("disabled")) return; + if (isRunning) return; + modeTiles.forEach((t) => t.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(`Modus "${label}" ausgewählt. Du kannst den Test jetzt starten.`); + logLine("Modus gewählt: " + 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("Starte Tests im Modus: " + currentMode.toUpperCase(), "info"); + setStatus("Test läuft... bitte USB-Stick nicht entfernen."); + setOverallStatus("warn", "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("Test abgeschlossen."); + setOverallStatus("ok", "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("Test wurde abgebrochen."); + setOverallStatus("warn", "Test abgebrochen."); + logLine("Test wurde vom Benutzer abgebrochen.", "warn"); + } else if (err) { + setStatus("Fehler: " + err.message); + setOverallStatus("bad", "Fehler im Browser-Test."); + logLine("Fehler im Test: " + err.message, "error"); + } else { + setStatus("Unbekannter Fehler im Test."); + setOverallStatus("bad", "Fehler im Browser-Test."); + logLine("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: "Quick-Check", + benchmark: "Benchmark", + writeverify: "Write & Verify", + 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) { + const url = "/api/result/browser-quick-test.php"; + 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("Backend: Konnte Ergebnis nicht speichern (" + msg + ").", "warn"); + if (response.status >= 500 && saveError) { + saveError.style.display = "block"; + } + return; + } + + const data = await response.json().catch(() => null); + logLine( + "Backend: Testergebnis gespeichert" + + (data && data.id ? ` (ID: ${data.id})` : ""), + "info" + ); + } catch (err) { + if (saveError) saveError.style.display = "block"; + logLine("Fehler beim Speichern im Backend: " + (err ? err.message : "unbekannt"), "error"); + } + } + + // --- Initialzustand ----------------------------------------------------- + + setStatus("Bereit. Wähle zuerst deinen USB-Stick aus."); + setModeLabel("Kein Modus selektiert"); + setOverallStatus("ok", "Noch kein Test durchgeführt."); + logLine("USB-Browser-Test (fakecheck) geladen. Warte auf Verzeichnisauswahl und Modus."); + updateStartButtonState(); +})(); diff --git a/public/assets/js/fakecheck/fakecheck.core.js b/public/assets/js/fakecheck/fakecheck.core.js new file mode 100644 index 0000000..1088e2e --- /dev/null +++ b/public/assets/js/fakecheck/fakecheck.core.js @@ -0,0 +1,100 @@ +// /public/assets/js/fakecheck/fakecheck.core.js + +(function() { + + // Namespace + window.usbcheck = window.usbcheck || {}; + + const cfg = window.usbConfig || {}; + + // URL-Detection + function detectApiBaseUrl() { + const host = window.location.hostname || ""; + if (host === "staging.usbcheck.it" || host.endsWith(".staging.usbcheck.it")) { + return "https://api.staging.usbcheck.it"; + } + return "https://api.usbcheck.it"; + } + + // Config bereitstellen + usbcheck.cfg = { + baseUrl: cfg.baseUrl || "", + locale: (cfg.lang || "en").toLowerCase(), + apiBase: cfg.apiBaseUrl || detectApiBaseUrl(), + loggedIn: !!cfg.isLoggedIn + }; + + + // ---------- Hilfsfunktionen ---------- + usbcheck.log = function logLine(message, level = "info") { + const logEl = document.querySelector("#fc-log"); + if (!logEl) return; + const line = document.createElement("div"); + line.className = "fc-log-line"; + const prefix = + level === "error" ? "[ERROR] " : + level === "warn" ? "[WARN] " : + "[INFO] "; + line.innerHTML = `${prefix}${message}`; + logEl.appendChild(line); + logEl.scrollTop = logEl.scrollHeight; + }; + + usbcheck.setStatus = function(msg) { + const el = document.querySelector("#fc-status-text"); + if (el) el.textContent = msg; + }; + + usbcheck.setModeLabel = function(msg) { + const el = document.querySelector("#fc-status-mode"); + if (el) el.textContent = msg; + }; + + usbcheck.setProgress = function(percent) { + const el = document.querySelector("#fc-progress-inner"); + if (!el) return; + const v = Math.min(100, Math.max(0, percent)); + el.style.width = v + "%"; + }; + + usbcheck.setOverallStatus = function(state, text) { + const pill = document.querySelector("#fc-overall-status-pill"); + const label = document.querySelector("#fc-overall-status-text"); + if (!pill || !label) return; + + pill.classList.remove("fc-pill-ok","fc-pill-warn","fc-pill-bad"); + + if (state === "ok") pill.classList.add("fc-pill-ok"); + else if (state === "warn") pill.classList.add("fc-pill-warn"); + else pill.classList.add("fc-pill-bad"); + + label.textContent = text; + }; + + usbcheck.formatBytes = function(bytes) { + if (bytes == null) return "–"; + const units = ["B", "KB", "MB", "GB", "TB"]; + let u = 0, v = bytes; + while (v >= 1024 && u < units.length - 1) { v /= 1024; u++; } + return v.toFixed(1) + " " + units[u]; + }; + + usbcheck.formatMbps = function(bytes, seconds) { + if (!seconds || seconds <= 0) return "–"; + const mbits = (bytes * 8) / 1e6; + return mbits.toFixed(1) + " Mbit/s"; + }; + + usbcheck.formatDuration = function(seconds) { + if (!seconds) return "–"; + const s = Math.round(seconds); + if (s < 60) return s + " s"; + const m = Math.floor(s / 60); + const r = s % 60; + if (m < 60) return `${m} min ${r}s`; + const h = Math.floor(m / 60); + const rm = m % 60; + return `${h} h ${rm} min`; + }; + +})(); diff --git a/public/assets/js/fakecheck/fakecheck.serial.js b/public/assets/js/fakecheck/fakecheck.serial.js new file mode 100644 index 0000000..850427a --- /dev/null +++ b/public/assets/js/fakecheck/fakecheck.serial.js @@ -0,0 +1,25 @@ +// /public/assets/js/fakecheck.js + +(function() { + const base = "/assets/js/fakecheck/"; + + const scripts = [ + base + "core.js?v=1", + base + "fakecheck.browser.js?v=1", + base + "fakecheck.serial.js?v=1" + ]; + + function loadScript(src) { + return new Promise((resolve, reject) => { + const s = document.createElement("script"); + s.src = src; + s.async = false; // Reihenfolge sicherstellen + s.onload = resolve; + s.onerror = () => reject(new Error("Konnte " + src + " nicht laden")); + document.head.appendChild(s); + }); + } + + scripts.reduce((p, src) => p.then(() => loadScript(src)), Promise.resolve()) + .catch(err => console.error("Fakecheck Loader Fehler:", err)); +})();