Files
usbcheck.it/public/assets/js/fakecheck.js
2025-11-24 02:12:49 +01:00

871 lines
36 KiB
JavaScript
Raw 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.js
document.addEventListener("DOMContentLoaded", () => {
const baseUrl = window.fakecheckBaseUrl || "";
const locale = window.fakecheckLocale || "en";
// Neu: API-Basis-URL (prod/staging) über Config oder Fallback anhand Host
const apiBaseUrl = window.fakecheckApiBaseUrl || detectApiBaseUrl();
function detectApiBaseUrl() {
const host = window.location.hostname || "";
// einfache Heuristik: staging-Domain → staging-API
if (host === "staging.usbcheck.it" || host.endsWith(".staging.usbcheck.it")) {
return "https://api.staging.usbcheck.it";
}
// default: production
return "https://api.usbcheck.it";
}
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");
// Falls du einen Login-Indikator hast, kannst du ihn global setzen,
// z. B. window.fakecheckIsLoggedIn = true/false
if (window.fakecheckIsLoggedIn) {
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 = `<strong>${prefix}</strong>${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;
}
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 ------------------------------------------------------
if (btnPickDir) {
btnPickDir.addEventListener("click", async () => {
try {
const handle = await tester.pickDirectory();
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.name === "AbortError") {
logLine("Verzeichnisauswahl abgebrochen.", "warn");
} else {
logLine("Fehler bei Verzeichnisauswahl: " + err.message, "error");
}
}
});
}
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.name === "AbortError") {
setStatus("Test wurde abgebrochen.");
setOverallStatus("warn", "Test abgebrochen.");
logLine("Test wurde vom Benutzer abgebrochen.", "warn");
} else {
setStatus("Fehler: " + err.message);
setOverallStatus("bad", "Fehler im Browser-Test.");
logLine("Fehler im Test: " + err.message, "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) {
// Absoluter Pfad dein Script liegt oberhalb des public-Roots:
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) {
// 401 => nicht eingeloggt; 500 => Fehler etc.
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.message, "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
? '<ul class="list-disc list-inside mt-1">' +
issues.map(i => '<li>' + i + '</li>').join("") +
"</ul>"
: '<span class="text-emerald-600 text-[11px]">Keine besonderen Auffälligkeiten.</span>';
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
? '<ul class="list-disc list-inside mt-1 text-[11px]">' +
notes.map(n => "<li>" + n + "</li>").join("") +
"</ul>"
: "";
resultBox.innerHTML = `
<div class="mb-3">
<span class="inline-flex items-center rounded-full px-3 py-1 text-[11px] font-semibold
${rating === "ok" ? "bg-emerald-100 text-emerald-800" : ""}
${rating === "needs_review" ? "bg-amber-100 text-amber-800" : ""}
${rating === "suspicious" ? "bg-red-100 text-red-800" : ""}
${rating === "invalid" ? "bg-slate-100 text-slate-700" : ""}
">
Bewertung: ${ratingLabel}
</span>
<p class="mt-1 text-xs text-slate-600 dark:text-slate-300">${ratingDesc}</p>
</div>
<div class="border border-slate-200 dark:border-slate-700 rounded-xl p-3 mb-3">
<h3 class="text-xs font-semibold mb-1 text-slate-800 dark:text-slate-100">Eingabedaten</h3>
<dl class="text-[11px] space-y-1 text-slate-600 dark:text-slate-300">
<div><dt class="font-medium">Hersteller (Angabe):</dt><dd>${input.manufacturer || '<span class="text-slate-400">keine Angabe</span>'}</dd></div>
<div><dt class="font-medium">VID / PID:</dt><dd>${(input.vid || "") + " / " + (input.pid || "")}</dd></div>
<div><dt class="font-medium">Vendor aus VID:</dt><dd>${vendorLine}</dd></div>
</dl>
</div>
<div class="border border-slate-200 dark:border-slate-700 rounded-xl p-3 mb-3">
<h3 class="text-xs font-semibold mb-1 text-slate-800 dark:text-slate-100">Seriennummer-Analyse</h3>
<dl class="text-[11px] space-y-1 text-slate-600 dark:text-slate-300">
<div><dt class="font-medium">Seriennummer:</dt><dd><code class="text-[10px] bg-slate-100 dark:bg-slate-800 px-1.5 py-0.5 rounded">${serialInfo.serial || ""}</code></dd></div>
<div><dt class="font-medium">Länge:</dt><dd>${serialInfo.length || 0} Zeichen</dd></div>
<div><dt class="font-medium">Kategorie:</dt><dd>${serialInfo.category || "-"}</dd></div>
<div><dt class="font-medium">Score:</dt><dd>${typeof serialInfo.score === "number" ? serialInfo.score : "-"} / 100</dd></div>
<div><dt class="font-medium">Auffälligkeiten:</dt><dd>${issuesList}</dd></div>
</dl>
</div>
<div class="border border-slate-200 dark:border-slate-700 rounded-xl p-3">
<h3 class="text-xs font-semibold mb-1 text-slate-800 dark:text-slate-100">Hersteller-Konsistenz</h3>
${notesList}
<p class="mt-2 text-[10px] text-slate-500">
Diese Einschätzung basiert auf Heuristiken und kann keine Echtheit garantieren.
</p>
</div>
`;
}
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();
});