185 lines
8.2 KiB
JavaScript
185 lines
8.2 KiB
JavaScript
// /public/assets/js/fakecheck/fakecheck.serial.js
|
||
|
||
(function () {
|
||
if (!window.usbcheck) return;
|
||
|
||
const {
|
||
cfg,
|
||
t,
|
||
tFmt,
|
||
log: logLine
|
||
} = window.usbcheck;
|
||
|
||
const rootSc = document.getElementById("serialcheck-root");
|
||
if (!rootSc) return; // Partial nicht eingebunden → nichts tun
|
||
|
||
// Elemente
|
||
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");
|
||
|
||
// API-Basis:
|
||
// - cfg.apiBase kommt aus fakecheck.core.js (z.B. https://api.usbcheck.it)
|
||
// - wir hängen /v1/quickcheck dran
|
||
// - Fallback: /api/v1/quickcheck auf demselben Host
|
||
const apiBaseRaw = (cfg && cfg.apiBase) ? cfg.apiBase : "";
|
||
const apiBase = apiBaseRaw.replace(/\/+$/, "");
|
||
const apiUrl = apiBase ? (apiBase + "/v1/quickcheck") : "/api/v1/quickcheck";
|
||
|
||
// -------------------------------------------------------
|
||
// Fehleranzeige
|
||
// -------------------------------------------------------
|
||
function showScError(msgKey, fallback, vars = {}) {
|
||
if (!errorBox) return;
|
||
const msg = tFmt(msgKey, fallback, vars);
|
||
errorBox.textContent = msg;
|
||
errorBox.classList.remove("hidden");
|
||
if (resultBox) resultBox.classList.add("hidden");
|
||
}
|
||
|
||
function clearScError() {
|
||
if (!errorBox) return;
|
||
errorBox.classList.add("hidden");
|
||
errorBox.textContent = "";
|
||
}
|
||
|
||
// -------------------------------------------------------
|
||
// Ergebnis rendern
|
||
// -------------------------------------------------------
|
||
function renderScResult(data) {
|
||
if (!resultBox) return;
|
||
|
||
clearScError();
|
||
resultBox.classList.remove("hidden");
|
||
|
||
const rating = data.rating || "unknown";
|
||
const input = data.input || {};
|
||
const vendorInfo = data.vendor_detected || {};
|
||
const serialInfo = data.serial_analysis || {};
|
||
const consistency = data.consistency || {};
|
||
|
||
// Rating → Label + Beschreibung
|
||
const ratingLabel = t(`serial.rating.${rating}.label`, rating);
|
||
const ratingDesc = t(`serial.rating.${rating}.desc`, "");
|
||
|
||
// Auffälligkeiten
|
||
const issues = serialInfo.issues || [];
|
||
const issuesHtml = 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]">${t("serial.issues_none", "Keine besonderen Auffälligkeiten.")}</span>`;
|
||
|
||
// Vendor
|
||
const vendorLine = vendorInfo.found
|
||
? tFmt("serial.vendor_detected", "{vendor} (VID {vid})", {
|
||
vendor: vendorInfo.vendor,
|
||
vid: vendorInfo.vid
|
||
})
|
||
: vendorInfo.vid
|
||
? tFmt("serial.vendor_unknown", "Unbekannter Hersteller für VID {vid}", { vid: vendorInfo.vid })
|
||
: t("serial.vendor_none", "Keine Vendor-ID angegeben");
|
||
|
||
// Konsistenz-Notizen
|
||
const notes = consistency.notes || [];
|
||
const notesHtml = notes.length
|
||
? '<ul class="list-disc list-inside mt-1 text-[11px]">' +
|
||
notes.map(n => `<li>${n}</li>`).join("") +
|
||
'</ul>'
|
||
: "";
|
||
|
||
// HTML einsetzen
|
||
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" : ""}
|
||
">
|
||
${t("serial.rating_label", "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">${t("serial.heading_input", "Eingabedaten")}</h3>
|
||
<dl class="text-[11px] space-y-1 text-slate-600 dark:text-slate-300">
|
||
<div><dt class="font-medium">${t("serial.input.manufacturer", "Hersteller (Angabe):")}</dt><dd>${input.manufacturer || '<span class="text-slate-400">' + t("serial.none", "keine Angabe") + '</span>'}</dd></div>
|
||
<div><dt class="font-medium">${t("serial.input.vidpid", "VID/PID:")}</dt><dd>${(input.vid || "–") + " / " + (input.pid || "–")}</dd></div>
|
||
<div><dt class="font-medium">${t("serial.input.vendor_detected", "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">${t("serial.heading_analysis", "Seriennummer-Analyse")}</h3>
|
||
<dl class="text-[11px] space-y-1 text-slate-600 dark:text-slate-300">
|
||
<div><dt class="font-medium">${t("serial.analysis.serial", "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">${t("serial.analysis.length", "Länge:")}</dt><dd>${serialInfo.length || 0} ${t("serial.chars", "Zeichen")}</dd></div>
|
||
<div><dt class="font-medium">${t("serial.analysis.category", "Kategorie:")}</dt><dd>${serialInfo.category || "-"}</dd></div>
|
||
<div><dt class="font-medium">${t("serial.analysis.score", "Score:")}</dt><dd>${typeof serialInfo.score === "number" ? serialInfo.score : "-"} / 100</dd></div>
|
||
<div><dt class="font-medium">${t("serial.analysis.issues", "Auffälligkeiten:")}</dt><dd>${issuesHtml}</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">${t("serial.heading_consistency", "Hersteller-Konsistenz")}</h3>
|
||
${notesHtml}
|
||
<p class="mt-2 text-[10px] text-slate-500">
|
||
${t("serial.disclaimer", "Diese Einschätzung basiert auf Heuristiken und kann keine Echtheit garantieren.")}
|
||
</p>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// -------------------------------------------------------
|
||
// Submit-Handler
|
||
// -------------------------------------------------------
|
||
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("serial.error.no_serial", "Bitte gib eine Seriennummer ein.");
|
||
return;
|
||
}
|
||
|
||
fetch(apiUrl, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(payload)
|
||
})
|
||
.then(res => {
|
||
if (!res.ok) {
|
||
throw new Error("HTTP " + res.status);
|
||
}
|
||
return res.json();
|
||
})
|
||
.then(data => {
|
||
if (!data || !data.success) {
|
||
throw new Error((data && data.error) || t("serial.error.unknown", "Unerwartete Antwort vom Server."));
|
||
}
|
||
renderScResult(data);
|
||
})
|
||
.catch(err => {
|
||
showScError("serial.error.api", "Fehler bei der Prüfung: {msg}", { msg: err.message });
|
||
});
|
||
});
|
||
|
||
})();
|