95 lines
4.7 KiB
JavaScript
95 lines
4.7 KiB
JavaScript
"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();
|
|
})();
|