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

+54 -5
View File
@@ -7,6 +7,8 @@ function verifyAdminModules() {
["rustProxyTrafficPanel", "refresh"],
["rustProxyPreferences", "load"],
["rustProxyRefresh", "start"],
["rustProxyPermissions", "render"],
["rustProxyCertificateInspection", "read"],
];
const missing = required.find(([module, method]) => typeof window[module]?.[method] !== "function");
if (missing) throw new Error(`Panel resources incomplete (${missing[0]}). Reload to retry.`);
@@ -34,6 +36,12 @@ else document.addEventListener("DOMContentLoaded", startAdminPanel, { once: true
function certificateUsesExternalFiles(cert) {
return String(cert?.cert_file || "").startsWith("/") && String(cert?.key_file || "").startsWith("/");
}
function syncCertificatePermissions() {
window.rustProxyPermissions?.render($("certificate-external-acl-example"), {
kind: "certificate", paths: [$("certificate-file").value, $("certificate-key").value],
enabled: $("certificate-file-source").value === "external",
});
}
function syncCertificateFileSource() {
const external = $("certificate-file-source").value === "external";
const cert = $("certificate-file"), key = $("certificate-key");
@@ -47,9 +55,14 @@ function syncCertificateFileSource() {
const helpKey = external ? "certificateSourceExternalHelp" : "certificateSourceManagedHelp";
$("certificate-file-source-help").dataset.i18n = helpKey;
$("certificate-file-source-help").textContent = t(helpKey);
$("certificate-external-acl-example").hidden = !external;
syncCertificatePermissions();
window.rustProxyCertificateInspection?.refresh();
}
$("certificate-file-source").addEventListener("change", syncCertificateFileSource);
for (const id of ["certificate-file", "certificate-key"]) {
$(id).addEventListener("input", syncCertificatePermissions);
$(id).addEventListener("change", syncCertificatePermissions);
}
function renderCertificates() {
if (!envelope) return;
const settings = envelope.config.tls || {};
@@ -148,7 +161,15 @@ 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-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; }));
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";
$("certificate-file").value = cert.cert_file; $("certificate-key").value = cert.key_file;
$("certificate-default").checked = settings.default_certificate === cert.id;
window.rustProxyCertificateInspection?.reset({ preserveNames: true });
syncCertificateFileSource(); window.rustProxyCertificateInspection?.schedule();
}));
}
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);
@@ -157,8 +178,38 @@ function renderCertificates() {
});
$("certificate-list").replaceChildren(...entries);
if (!entries.length) $("certificate-list").append(el("p", t("emptyCertificates"), "empty"));
syncCertificatePermissions();
}
$("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); } });
$("certificate-form").addEventListener("submit", async event => {
event.preventDefault(); const form = event.currentTarget;
if (!envelope || busy) return;
try {
await withFormSubmitState(form, event.submitter, async () => {
const epoch = authEpoch;
if (await window.rustProxyCertificateInspection?.beforeSave() === false) return;
if (!authenticated || epoch !== authEpoch || !envelope || busy) return;
const next = structuredClone(envelope.config);
next.tls ||= {}; 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], 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 {
@@ -331,8 +382,6 @@ function renderNetworkInterfaceCharts(interfaces, appendSample) {
}
}
// Cumulative process counters, not rate-sampler deltas. Never invent OTHER
// or force a 100% mix if independently read totals are inconsistent.
function cacheOutcomeMix(totals) {
if (!Array.isArray(totals) || totals.length !== 4 ||
totals.some(value => !Number.isSafeInteger(value) || value < 0)) return null;