This commit is contained in:
Mateusz Gruszczyński committed 2026-10-23 13:07:11 +02:00
1 parent 66ff9dfaa2
commit ee54aa6875
30 files changed
+939 -68

No files matched your search

@@ -0,0 +1,94 @@
"use strict";
(() => {
const form = $("certificate-form"), file = $("certificate-file"), source = $("certificate-file-source");
const names = $("certificate-hosts"), button = $("certificate-inspect"), status = $("certificate-inspect-status");
let timer = null, sequence = 0, namesRevision = 0, resetGeneration = 0, applying = false;
let manuallyEdited = !!names.value.trim(), pending = null, lastSuccess = "";
const identity = () => JSON.stringify([source.value, file.value.trim(), authEpoch]);
function validFile() {
const value = file.value.trim();
return source.value === "external"
? value.startsWith("/") && value.length <= 4096 && !/[\x00-\x1f\x7f]/.test(value)
: value.length <= 96 && value !== "." && value !== ".." && /^[A-Za-z0-9_.-]+$/.test(value);
}
function message(key = "", detail = "") {
if (key && !detail) status.dataset.i18n = key;
else delete status.dataset.i18n;
status.textContent = key ? `${t(key)}${detail ? ` ${detail}` : ""}` : "";
status.classList.toggle("status-error", !!detail);
}
function refresh() { button.disabled = !validFile() || !!pending; }
function invalidate() {
clearTimeout(timer); timer = null; sequence++;
pending?.controller.abort(); pending = null; lastSuccess = "";
refresh();
}
function writeNames(value) {
names.value = value; applying = true;
try { names.dispatchEvent(new Event("input", { bubbles: true })); }
finally { applying = false; }
}
function reset({ preserveNames = false } = {}) {
invalidate(); manuallyEdited = preserveNames && !!names.value.trim(); namesRevision++; resetGeneration++;
message(); refresh();
}
async function read(force = false) {
clearTimeout(timer); timer = null;
if (!authenticated || !validFile()) { refresh(); return; }
const id = identity();
if (!force && pending?.id === id) return pending.promise;
if (!force && lastSuccess === id) return;
invalidate();
const current = sequence, epoch = authEpoch, revision = namesRevision, previousNames = names.value;
const certFile = file.value.trim(), controller = new AbortController();
message("certificateInspectReading");
const active = { id, controller, promise: null };
pending = active; refresh();
active.promise = (async () => {
try {
const result = await api("/api/certificates/inspect", {
method: "POST", body: JSON.stringify({ cert_file: certFile }), signal: controller.signal,
});
if (current !== sequence || epoch !== authEpoch || !authenticated || identity() !== id) return;
const hosts = Array.isArray(result.covered_hosts) ? result.covered_hosts.filter(host => typeof host === "string") : [];
lastSuccess = id;
const update = revision === namesRevision && names.value === previousNames && (force || !manuallyEdited);
if (update) { writeNames(hosts.join(", ")); manuallyEdited = false; }
message(!hosts.length ? "certificateInspectNoNames" : update ? "certificateInspectReady" : "certificateInspectKept");
} catch (error) {
if (current === sequence && epoch === authEpoch && authenticated && identity() === id && error.name !== "AbortError") {
message("certificateInspectFailed", error.message);
}
} finally {
if (pending === active) { pending = null; refresh(); }
}
})();
return active.promise;
}
function schedule() {
invalidate();
if (!manuallyEdited && names.value) writeNames("");
message();
if (validFile()) timer = setTimeout(() => { void read(); }, 400);
}
async function beforeSave() {
const generation = resetGeneration;
let id;
do { id = identity(); await read(); }
while (authenticated && generation === resetGeneration && id !== identity());
return generation === resetGeneration;
}
names.addEventListener("input", () => {
if (applying) return;
manuallyEdited = true; namesRevision++;
});
file.addEventListener("input", schedule);
file.addEventListener("change", () => { void read(); });
file.addEventListener("blur", () => { void read(); });
source.addEventListener("change", schedule);
form.addEventListener("reset", () => reset());
button.addEventListener("click", () => { void read(true); });
window.rustProxyCertificateInspection = { reset, schedule, read, beforeSave, refresh };
refresh();
})();