This commit is contained in:
Mateusz Gruszczyński committed 2026-10-08 10:36:34 +02:00
1 parent 3b3f0db377
commit d940f9995f
47 files changed
+542 -187

No files matched your search

+24 -3
View File
@@ -20,6 +20,25 @@
}
})();
function certificateUsesExternalFiles(cert) {
return String(cert?.cert_file || "").startsWith("/") && String(cert?.key_file || "").startsWith("/");
}
function syncCertificateFileSource() {
const external = $("certificate-file-source").value === "external";
const cert = $("certificate-file"), key = $("certificate-key");
for (const input of [cert, key]) {
input.maxLength = external ? 4096 : 96;
if (external) input.removeAttribute("pattern");
else input.pattern = "[A-Za-z0-9_.-]+";
}
cert.placeholder = external ? "/etc/letsencrypt/live/example.com/fullchain.pem" : "app.pem";
key.placeholder = external ? "/etc/letsencrypt/live/example.com/privkey.pem" : "app.key";
const helpKey = external ? "certificateSourceExternalHelp" : "certificateSourceManagedHelp";
$("certificate-file-source-help").dataset.i18n = helpKey;
$("certificate-file-source-help").textContent = t(helpKey);
}
$("certificate-file-source").addEventListener("change", syncCertificateFileSource);
function renderCertificates() {
if (!envelope) return;
const settings = envelope.config.tls || {};
@@ -41,6 +60,7 @@ function renderCertificates() {
identity.append(el("span", cert.id, "entity-title"));
const statePills = el("div", undefined, "vhost-policy-pills certificate-state-pills");
if (managed) statePills.append(el("small", `${t("certificateManaged")} · ${cert.issuer.provider} / ${cert.issuer.environment}`, "pill"));
else if (certificateUsesExternalFiles(cert)) statePills.append(el("small", t("certificateExternalFiles"), "pill"));
if (settings.default_certificate === cert.id) statePills.append(el("small", t("defaultLabel"), "pill"));
if (inventory?.expiry_status === "expired") statePills.append(el("small", t("certificateExpired"), "pill status-error"));
else if (inventory?.expiry_status === "warning") statePills.append(el("small", t("certificateExpiresSoon"), "pill status-error"));
@@ -91,7 +111,7 @@ function renderCertificates() {
if (result.administration_restart_required) toast(t("certificateAdminRestartRequired"), true);
} catch (error) { toast(error.message, true); } finally { busy = false; }
}));
buttons.append(action(t("edit"), () => { window.rustProxyCertificates?.open("existing"); $("certificate-advanced").open = true; $("certificate-id").value = cert.id; $("certificate-hosts").value = cert.hosts.join(", "); $("certificate-file").value = cert.cert_file; $("certificate-key").value = cert.key_file; $("certificate-default").checked = settings.default_certificate === cert.id; }));
buttons.append(action(t("edit"), () => { window.rustProxyCertificates?.open("existing"); $("certificate-advanced").open = true; $("certificate-id").value = cert.id; $("certificate-hosts").value = cert.hosts.join(", "); $("certificate-file-source").value = certificateUsesExternalFiles(cert) ? "external" : "managed"; syncCertificateFileSource(); $("certificate-file").value = cert.cert_file; $("certificate-key").value = cert.key_file; $("certificate-default").checked = settings.default_certificate === cert.id; }));
}
const remove = action(t("remove"), async () => { if (inUse) { toast(t("certInUse"), true); return; } if (!(await appConfirm(t("confirmDelete"), { danger: true }))) return; const next = structuredClone(envelope.config); next.tls.certificates = next.tls.certificates.filter(v => v.id !== cert.id); await saveSection("tls", next.tls); });
remove.disabled = inUse; remove.title = inUse ? t("certInUse") : ""; buttons.append(remove);
@@ -101,7 +121,7 @@ function renderCertificates() {
$("certificate-list").replaceChildren(...entries);
if (!entries.length) $("certificate-list").append(el("p", t("emptyCertificates"), "empty"));
}
$("certificate-form").addEventListener("submit", async event => { event.preventDefault(); const form = event.currentTarget; if (!envelope || busy) return; try { await withFormSubmitState(form, event.submitter, async () => { const next = structuredClone(envelope.config); next.tls = next.tls || {}; next.tls.certificates = next.tls.certificates || []; const cert = { id: $("certificate-id").value.trim(), hosts: $("certificate-hosts").value.split(",").map(v => v.trim()).filter(Boolean), cert_file: $("certificate-file").value.trim(), key_file: $("certificate-key").value.trim() }; const index = next.tls.certificates.findIndex(v => v.id === cert.id); if (index >= 0 && next.tls.certificates[index].issuer) throw fieldError($("certificate-id"), t("certManagedReadOnly")); if (index < 0) next.tls.certificates.push(cert); else { const old = next.tls.certificates[index]; const sameFiles = old.cert_file === cert.cert_file && old.key_file === cert.key_file; next.tls.certificates[index] = { ...old, ...cert, covered_hosts: sameFiles ? (old.covered_hosts || []) : [], issuer: sameFiles ? old.issuer : null }; } if ($("certificate-default").checked) next.tls.default_certificate = cert.id; else if (next.tls.default_certificate === cert.id) delete next.tls.default_certificate; if (await saveSection("tls", next.tls)) { form.reset(); window.rustProxyCertificates?.close(); } }); } catch (error) { toast(error.message, true); } });
$("certificate-form").addEventListener("submit", async event => { event.preventDefault(); const form = event.currentTarget; if (!envelope || busy) return; try { await withFormSubmitState(form, event.submitter, async () => { const next = structuredClone(envelope.config); next.tls = next.tls || {}; next.tls.certificates = next.tls.certificates || []; const cert = { id: $("certificate-id").value.trim(), hosts: $("certificate-hosts").value.split(",").map(v => v.trim()).filter(Boolean), cert_file: $("certificate-file").value.trim(), key_file: $("certificate-key").value.trim() }; const index = next.tls.certificates.findIndex(v => v.id === cert.id); if (index >= 0 && next.tls.certificates[index].issuer) throw fieldError($("certificate-id"), t("certManagedReadOnly")); if (index < 0) next.tls.certificates.push(cert); else { const old = next.tls.certificates[index]; const sameFiles = old.cert_file === cert.cert_file && old.key_file === cert.key_file; next.tls.certificates[index] = { ...old, ...cert, covered_hosts: sameFiles ? (old.covered_hosts || []) : [], issuer: sameFiles ? old.issuer : null }; } if ($("certificate-default").checked) next.tls.default_certificate = cert.id; else if (next.tls.default_certificate === cert.id) delete next.tls.default_certificate; if (await saveSection("tls", next.tls)) { form.reset(); syncCertificateFileSource(); window.rustProxyCertificates?.close(); } }); } catch (error) { toast(error.message, true); } });
$("reload-certificates").addEventListener("click", async () => {
if (!envelope || busy) return;
try {
@@ -114,7 +134,8 @@ $("reload-certificates").addEventListener("click", async () => {
} catch (error) { toast(error.message, true); }
finally { busy = false; }
});
$("reset-certificate").addEventListener("click", () => $("certificate-form").reset());
$("reset-certificate").addEventListener("click", () => { $("certificate-form").reset(); $("certificate-file-source").value = "managed"; syncCertificateFileSource(); });
syncCertificateFileSource();
// Dashboard metrics are actual server samples. Null means unavailable, not zero.
let systemSamples = [], connectionOpenSamples = [], connectionExchangeSamples = [], cacheMemorySamples = [], cacheTransientSamples = [], cacheFillSamples = [], cacheColdQueueSamples = [], cacheColdIoSamples = [], cacheColdStorageSamples = [], cacheHitSamples = [], cacheMissSamples = [], cacheBypassSamples = [], lastCacheCounters = null,