0.31.9
This commit is contained in:
1 parent
180a0e4877
commit
d23fa5e154
39 files changed
+1521
-376
No files matched your search
@@ -0,0 +1,249 @@
|
||||
"use strict";
|
||||
// Credential management is separated from generic Settings to keep UI modules bounded.
|
||||
// Only non-secret metadata is retained in client state.
|
||||
(() => {
|
||||
let certificateApiState = null, certificateApiLoading = false;
|
||||
let certificateApiEditingKey = null, certificateApiDetailsKey = null;
|
||||
function certificateApiCredentials() {
|
||||
const values = certificateApiState?.credentials;
|
||||
if (Array.isArray(values)) return values;
|
||||
if (certificateApiState?.configured && certificateApiState?.key_id) return [{ key_id: certificateApiState.key_id, comment: "", created_at: certificateApiState.created_at, last_used_at: certificateApiState.last_used_at }];
|
||||
return [];
|
||||
}
|
||||
function clearCertificateApiSecret() {
|
||||
$("certificate-api-secret").textContent = "";
|
||||
$("certificate-api-secret-wrap").hidden = true;
|
||||
}
|
||||
function showCertificateApiSecret(token) {
|
||||
$("certificate-api-secret").textContent = token;
|
||||
$("certificate-api-secret-wrap").hidden = !token;
|
||||
}
|
||||
function resetCertificateApiForm() {
|
||||
certificateApiEditingKey = null;
|
||||
$("certificate-api-key-id").value = "";
|
||||
$("certificate-api-key-id").readOnly = false;
|
||||
$("certificate-api-comment").value = "";
|
||||
$("certificate-api-certificate-ids").value = "";
|
||||
$("certificate-api-allow-deploy").checked = false;
|
||||
$("certificate-api-token").value = "";
|
||||
$("certificate-api-token-field").hidden = false;
|
||||
$("certificate-api-edit-note").hidden = true;
|
||||
$("certificate-api-cancel-edit").hidden = true;
|
||||
const submit = $("certificate-api-save");
|
||||
submit.dataset.i18n = "certificateApiRotate";
|
||||
submit.textContent = t("certificateApiRotate");
|
||||
certificateApiExample();
|
||||
}
|
||||
function beginCertificateApiEdit(credential) {
|
||||
certificateApiEditingKey = credential.key_id;
|
||||
certificateApiDetailsKey = credential.key_id;
|
||||
$("certificate-api-key-id").value = credential.key_id;
|
||||
$("certificate-api-key-id").readOnly = true;
|
||||
$("certificate-api-comment").value = credential.comment || "";
|
||||
$("certificate-api-certificate-ids").value = (credential.certificate_ids || []).join("\n");
|
||||
$("certificate-api-allow-deploy").checked = !!credential.allow_deploy;
|
||||
$("certificate-api-token").value = "";
|
||||
$("certificate-api-token-field").hidden = true;
|
||||
$("certificate-api-edit-note").hidden = false;
|
||||
$("certificate-api-cancel-edit").hidden = false;
|
||||
const submit = $("certificate-api-save");
|
||||
submit.dataset.i18n = "certificateApiSave";
|
||||
submit.textContent = t("certificateApiSave");
|
||||
certificateApiExample();
|
||||
$("certificate-api-form").scrollIntoView?.({ block: "start", behavior: "smooth" });
|
||||
$("certificate-api-comment").focus?.();
|
||||
}
|
||||
function setCertificateApiCredential(credential) {
|
||||
if (!certificateApiState) return;
|
||||
const entries = certificateApiCredentials();
|
||||
certificateApiState.credentials = entries.some(item => item.key_id === credential.key_id)
|
||||
? entries.map(item => item.key_id === credential.key_id ? credential : item)
|
||||
: [...entries, credential];
|
||||
certificateApiState.configured = certificateApiState.credentials.length > 0;
|
||||
}
|
||||
function certificateApiExample() {
|
||||
const key = $("certificate-api-key-id").value.trim() || certificateApiCredentials()[0]?.key_id || "certbot-main";
|
||||
const enteredIds = $("certificate-api-certificate-ids").value.split(/[\n,]+/).map(value => value.trim()).filter(Boolean);
|
||||
const selected = enteredIds[0] || certificateApiCredentials()[0]?.certificate_ids?.find(value => value !== "*") || "CERTIFICATE_ID";
|
||||
const reload = `curl --fail --show-error --request POST \
|
||||
--header 'Authorization: Bearer $RUSTPROXY_CERT_TOKEN' \
|
||||
--header 'X-RustProxy-Certificate-Key: ${key}' \
|
||||
${location.origin}/api/certificates/${selected}/reload`;
|
||||
if (!$("certificate-api-allow-deploy").checked) {
|
||||
$("certificate-api-curl").textContent = reload;
|
||||
return;
|
||||
}
|
||||
$("certificate-api-curl").textContent = `${reload}
|
||||
|
||||
# Optional higher-privilege deploy for the same allowlisted certificate IDs:
|
||||
curl --fail --show-error --request POST \
|
||||
--header 'Authorization: Bearer $RUSTPROXY_CERT_TOKEN' \
|
||||
--header 'X-RustProxy-Certificate-Key: ${key}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data @deploy.json \
|
||||
${location.origin}/api/certificates/deploy`;
|
||||
}
|
||||
function certificateApiDetailNode(credential) {
|
||||
const detail = el("div", undefined, "certificate-api-details");
|
||||
const item = (label, value) => {
|
||||
const line = el("div", undefined, "certificate-api-detail-line");
|
||||
line.append(el("span", `${label}:`, "fine"), el("strong", String(value), "mono"));
|
||||
detail.append(line);
|
||||
};
|
||||
item(t("certificateApiKeyId"), credential.key_id);
|
||||
item(t("certificateApiComment"), credential.comment || "-");
|
||||
item(t("certificateApiCreatedAt"), formatDateTime(Number(credential.created_at) * 1000));
|
||||
item(t("certificateApiLastUsed"), credential.last_used_at ? formatDateTime(Number(credential.last_used_at) * 1000) : t("certificateApiNeverUsed"));
|
||||
item(t("certificateApiPermissions"), credential.allow_deploy ? t("certificateApiScopeDeployReload") : t("certificateApiScopeReloadOnly"));
|
||||
item(t("certificateApiCertificateIds"), (credential.certificate_ids || []).join(", "));
|
||||
item(t("certificateApiTokenLabel"), t("certificateApiTokenHidden"));
|
||||
return detail;
|
||||
}
|
||||
function renderCertificateApi() {
|
||||
const state = certificateApiState, target = $("certificate-api-credentials");
|
||||
if (!state) { $("certificate-api-status").textContent = t("certificateApiUnknown"); if (target) target.replaceChildren(); certificateApiExample(); return; }
|
||||
const credentials = certificateApiCredentials(), max = Number(state.max_credentials) || 32;
|
||||
$("certificate-api-status").textContent = credentials.length
|
||||
? t("certificateApiConfiguredCount").replace("{count}", credentials.length).replace("{max}", max)
|
||||
: t("certificateApiDisabled");
|
||||
$("certificate-api-revoke-all").disabled = !credentials.length;
|
||||
if (target) {
|
||||
target.replaceChildren();
|
||||
for (const credential of credentials) {
|
||||
const container = el("div", undefined, "certificate-api-credential-item");
|
||||
const row = el("div", undefined, "certificate-api-credential-row");
|
||||
const info = el("div", undefined, "certificate-api-credential-info");
|
||||
info.append(el("b", credential.key_id || "-", "mono"));
|
||||
if (credential.comment) info.append(el("span", credential.comment, "fine"));
|
||||
const scope = credential.allow_deploy ? t("certificateApiScopeDeployReload") : t("certificateApiScopeReloadOnly");
|
||||
const ids = Array.isArray(credential.certificate_ids) ? credential.certificate_ids.join(", ") : "-";
|
||||
info.append(el("span", `${scope} · ${t("certificateApiAllowedIds")}: ${ids}`, "fine mono"));
|
||||
const lastUsed = credential.last_used_at ? formatDateTime(Number(credential.last_used_at) * 1000) : t("certificateApiNeverUsed");
|
||||
info.append(el("span", `${t("certificateApiLastUsed")}: ${lastUsed}`, "fine"));
|
||||
const actions = el("div", undefined, "certificate-api-row-actions");
|
||||
const path = `/api/certificate-api/${encodeURIComponent(credential.key_id)}`;
|
||||
const preview = action(t(certificateApiDetailsKey === credential.key_id ? "certificateApiHide" : "certificateApiView"), async () => {
|
||||
if (certificateApiDetailsKey === credential.key_id) { certificateApiDetailsKey = null; renderCertificateApi(); return; }
|
||||
preview.disabled = true;
|
||||
clearCertificateApiSecret();
|
||||
try {
|
||||
const data = await api(path);
|
||||
setCertificateApiCredential(data.credential);
|
||||
certificateApiDetailsKey = credential.key_id;
|
||||
renderCertificateApi();
|
||||
} catch (error) { toast(error.message, true); preview.disabled = false; }
|
||||
});
|
||||
const edit = action(t("certificateApiEdit"), async () => {
|
||||
edit.disabled = true;
|
||||
clearCertificateApiSecret();
|
||||
try {
|
||||
const data = await api(path);
|
||||
setCertificateApiCredential(data.credential);
|
||||
beginCertificateApiEdit(data.credential);
|
||||
renderCertificateApi();
|
||||
} catch (error) { toast(error.message, true); edit.disabled = false; }
|
||||
});
|
||||
const regenerate = action(t("certificateApiRegenerate"), async () => {
|
||||
if (!await appConfirm(t("certificateApiRegenerateConfirm").replace("{key}", credential.key_id), { danger: true })) return;
|
||||
regenerate.disabled = true;
|
||||
clearCertificateApiSecret();
|
||||
try {
|
||||
const data = await api(`${path}/regenerate`, { method: "POST" });
|
||||
setCertificateApiCredential(data.credential);
|
||||
// Never put the bearer token into the persistent credential list/state.
|
||||
showCertificateApiSecret(data.token || "");
|
||||
renderCertificateApi();
|
||||
toast(t("certificateApiRegenerated"));
|
||||
} catch (error) { toast(error.message, true); regenerate.disabled = false; }
|
||||
});
|
||||
const remove = action(t("certificateApiRevoke"), async () => {
|
||||
if (!await appConfirm(t("certificateApiRevokeOneConfirm").replace("{key}", credential.key_id), { danger: true })) return;
|
||||
remove.disabled = true;
|
||||
try {
|
||||
await api(path, { method: "DELETE" });
|
||||
certificateApiState.credentials = certificateApiCredentials().filter(value => value.key_id !== credential.key_id);
|
||||
certificateApiState.configured = certificateApiState.credentials.length > 0;
|
||||
if (certificateApiEditingKey === credential.key_id) resetCertificateApiForm();
|
||||
if (certificateApiDetailsKey === credential.key_id) certificateApiDetailsKey = null;
|
||||
clearCertificateApiSecret(); renderCertificateApi(); toast(t("certificateApiRevoked"));
|
||||
} catch (error) { toast(error.message, true); remove.disabled = false; }
|
||||
});
|
||||
remove.classList.add("danger");
|
||||
actions.append(preview, edit, regenerate, remove);
|
||||
row.append(info, actions);
|
||||
container.append(row);
|
||||
if (certificateApiDetailsKey === credential.key_id) container.append(certificateApiDetailNode(credential));
|
||||
target.append(container);
|
||||
}
|
||||
}
|
||||
certificateApiExample();
|
||||
}
|
||||
async function loadCertificateApi(force = false) {
|
||||
if (!authenticated || certificateApiLoading || (certificateApiState && !force)) return;
|
||||
certificateApiLoading = true;
|
||||
try { certificateApiState = await api("/api/certificate-api"); renderCertificateApi(); }
|
||||
catch (error) { $("certificate-api-status").textContent = error.message; }
|
||||
finally { certificateApiLoading = false; }
|
||||
}
|
||||
$("certificate-api-form").addEventListener("submit", async event => {
|
||||
event.preventDefault();
|
||||
const button = $("certificate-api-save");
|
||||
button.disabled = true;
|
||||
try {
|
||||
const keyId = $("certificate-api-key-id").value.trim();
|
||||
const comment = $("certificate-api-comment").value.trim();
|
||||
const certificateIds = $("certificate-api-certificate-ids").value.split(/[\n,]+/).map(value => value.trim()).filter(Boolean);
|
||||
if (!certificateIds.length) throw fieldError($("certificate-api-certificate-ids"), t("certificateApiCertificateIdsRequired"));
|
||||
const allowDeploy = $("certificate-api-allow-deploy").checked;
|
||||
if (certificateApiEditingKey) {
|
||||
const result = await api(`/api/certificate-api/${encodeURIComponent(certificateApiEditingKey)}`, {
|
||||
method: "PATCH", body: JSON.stringify({ comment, certificate_ids: certificateIds, allow_deploy: allowDeploy })
|
||||
});
|
||||
setCertificateApiCredential(result.credential);
|
||||
resetCertificateApiForm();
|
||||
renderCertificateApi();
|
||||
toast(t("certificateApiSaved"));
|
||||
} else {
|
||||
const token = $("certificate-api-token").value.trim();
|
||||
const result = await api("/api/certificate-api", {
|
||||
method: "PUT", body: JSON.stringify({ key_id: keyId || null, token: token || null, comment, certificate_ids: certificateIds, allow_deploy: allowDeploy })
|
||||
});
|
||||
certificateApiState = { ...result }; delete certificateApiState.token; delete certificateApiState.credential;
|
||||
resetCertificateApiForm();
|
||||
showCertificateApiSecret(result.token || "");
|
||||
renderCertificateApi();
|
||||
toast(t("certificateApiRotated"));
|
||||
}
|
||||
} catch (error) { showFieldError(error, $("certificate-api-form")); toast(error.message, true); }
|
||||
finally { button.disabled = false; }
|
||||
});
|
||||
$("certificate-api-cancel-edit").addEventListener("click", resetCertificateApiForm);
|
||||
$("certificate-api-revoke-all").addEventListener("click", async () => {
|
||||
if (!await appConfirm(t("certificateApiRevokeAllConfirm"), { danger: true })) return;
|
||||
const button = $("certificate-api-revoke-all"); button.disabled = true;
|
||||
try {
|
||||
await api("/api/certificate-api", { method: "DELETE" });
|
||||
certificateApiState = { configured: false, credentials: [], max_credentials: certificateApiState?.max_credentials || 32, scopes: certificateApiState?.scopes || [] };
|
||||
certificateApiDetailsKey = null;
|
||||
resetCertificateApiForm(); clearCertificateApiSecret(); renderCertificateApi(); toast(t("certificateApiRevoked"));
|
||||
} catch (error) { toast(error.message, true); }
|
||||
finally { button.disabled = false; }
|
||||
});
|
||||
$("certificate-api-key-id").addEventListener("input", certificateApiExample);
|
||||
$("certificate-api-certificate-ids").addEventListener("input", certificateApiExample);
|
||||
$("certificate-api-allow-deploy").addEventListener("change", certificateApiExample);
|
||||
window.rustProxyCertificateApi = {
|
||||
render: renderCertificateApi,
|
||||
load: loadCertificateApi,
|
||||
reset() {
|
||||
certificateApiState = null;
|
||||
certificateApiLoading = false;
|
||||
certificateApiDetailsKey = null;
|
||||
resetCertificateApiForm();
|
||||
clearCertificateApiSecret();
|
||||
$("certificate-api-credentials").replaceChildren();
|
||||
}
|
||||
};
|
||||
renderCertificateApi();
|
||||
loadCertificateApi();
|
||||
})();
|
||||
Reference in new issue
Block a user