This commit is contained in:
Mateusz Gruszczyński committed 2026-10-12 14:57:37 +02:00
1 parent 180a0e4877
commit d23fa5e154
39 files changed
+1521 -376

No files matched your search

+249
View File
@@ -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();
})();
+3 -2
View File
@@ -2867,6 +2867,7 @@
credentials are reload-only by default and restricted to explicit certificate IDs.
Deploy is optional; tokens cannot read configuration or use other administration APIs.</p>
<p class="fine" id="certificate-api-status" role="status"></p>
<p class="fine" data-i18n="certificateApiEditNote" id="certificate-api-edit-note" hidden>Edit the settings below without changing the bearer token. Key ID cannot be changed.</p>
<label><span data-i18n="certificateApiKeyId">Key ID</span><input
id="certificate-api-key-id" maxlength="64" placeholder="certbot-main" /></label>
<label><span data-i18n="certificateApiComment">Comment</span><input
@@ -2878,14 +2879,14 @@
<label class="check"><input id="certificate-api-allow-deploy" type="checkbox" /><span
data-i18n="certificateApiAllowDeploy">Also allow deploy</span></label>
<p class="fine" data-i18n="certificateApiAllowDeployHelp">Leave disabled for Certbot renewal hooks. Reload-only is the recommended least-privilege mode.</p>
<label><span data-i18n="certificateApiToken">Bearer token (optional custom
<label id="certificate-api-token-field"><span data-i18n="certificateApiToken">Bearer token (optional custom
value)</span><input autocomplete="off" id="certificate-api-token"
maxlength="128" minlength="32"
placeholder="Leave empty to generate a strong token" type="password" /></label>
<p class="fine" data-i18n="certificateApiTokenHelp">Each cleartext token is shown only
after generation/rotation. RustProxy stores only its hash.</p>
<div class="form-actions"><button class="primary" data-i18n="certificateApiRotate"
type="submit">Generate / rotate token</button><button class="outline danger"
id="certificate-api-save" type="submit">Generate / rotate token</button><button class="outline" data-i18n="certificateApiCancelEdit" id="certificate-api-cancel-edit" hidden type="button">Cancel edit</button><button class="outline danger"
data-i18n="certificateApiRevokeAll" id="certificate-api-revoke-all"
type="button">Revoke all tokens</button></div>
<div hidden id="certificate-api-secret-wrap">
+2 -95
View File
@@ -114,78 +114,6 @@
button.addEventListener("keydown", event => { const index = httpTabs.indexOf(button); let next; if (event.key === "ArrowRight" || event.key === "ArrowDown") next = (index + 1) % httpTabs.length; else if (event.key === "ArrowLeft" || event.key === "ArrowUp") next = (index + httpTabs.length - 1) % httpTabs.length; else if (event.key === "Home") next = 0; else if (event.key === "End") next = httpTabs.length - 1; else return; event.preventDefault(); selectHttpTab(httpTabs[next].dataset.httpSettingsTab, true); });
}
selectHttpTab("http2");
let certificateApiState = null, certificateApiLoading = false;
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 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 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 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 remove = action(t("certificateApiRevoke"), async () => {
if (!await appConfirm(t("certificateApiRevokeOneConfirm").replace("{key}", credential.key_id), { danger: true })) return;
remove.disabled = true;
try {
await api(`/api/certificate-api/${encodeURIComponent(credential.key_id)}`, { method: "DELETE" });
certificateApiState.credentials = certificateApiCredentials().filter(value => value.key_id !== credential.key_id);
certificateApiState.configured = certificateApiState.credentials.length > 0;
renderCertificateApi(); toast(t("certificateApiRevoked"));
} catch (error) { toast(error.message, true); remove.disabled = false; }
});
remove.classList.add("danger");
row.append(info, remove); target.append(row);
}
}
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; }
}
const defaults = { enabled: false, listen: "127.0.0.1:9191", path: "/metrics", allowed_cidrs: ["127.0.0.0/8", "::1/128"], bearer_token_env: null, allow_trusted_network_without_token: false, include_system: true, include_vhosts: true, include_traffic: true, include_backends: true, include_docker_vhosts: true, include_cache_domains: true, include_streams: true, include_logging: true };
const geoipDefaults = {
enabled: true, format: "csv_pair",
@@ -303,7 +231,7 @@ curl --fail --show-error --request POST \
const effective = latestStatus?.tls?.effective_cipher_suites || [];
$("tls-policy-effective").textContent = effective.length ? `${t("tlsEffectiveCiphers")}: ${effective.join(", ")}` : "";
$("tls-policy-available").textContent = available.length ? `${t("tlsAvailableCiphers")}: ${available.join(", ")}` : "";
renderCertificateApi(); loadCertificateApi();
window.rustProxyCertificateApi?.render(); window.rustProxyCertificateApi?.load();
}
$("geoip-format").addEventListener("change", syncGeoipFormat); $("geoip-enabled").addEventListener("change", syncGeoipFormat);
bindSettingsForm("geoip-form", next => {
@@ -343,27 +271,6 @@ curl --fail --show-error --request POST \
if (!$("tls-policy-12").checked && !$("tls-policy-13").checked) throw fieldError($("tls-policy-12"), t("tlsPolicyVersionRequired"));
next.tls ||= {}; next.tls.policy = { tls12: $("tls-policy-12").checked, tls13: $("tls-policy-13").checked, cipher_suites: $("tls-policy-ciphers").value.split(/[\n,]+/).map(v => v.trim()).filter(Boolean) };
}, "tls");
$("certificate-api-form").addEventListener("submit", async event => {
event.preventDefault(); const button = event.submitter || $("certificate-api-form").querySelector("button[type=submit]"); button.disabled = true;
try {
const keyId = $("certificate-api-key-id").value.trim(), token = $("certificate-api-token").value.trim(), 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;
const payload = await api("/api/certificate-api", { method: "PUT", body: JSON.stringify({ key_id: keyId || null, token: token || null, comment, certificate_ids: certificateIds, allow_deploy: allowDeploy }) });
const clearToken = payload.token || ""; certificateApiState = { ...payload }; delete certificateApiState.token; delete certificateApiState.credential;
$("certificate-api-key-id").value = ""; $("certificate-api-comment").value = ""; $("certificate-api-certificate-ids").value = ""; $("certificate-api-allow-deploy").checked = false; $("certificate-api-token").value = ""; $("certificate-api-secret").textContent = clearToken; $("certificate-api-secret-wrap").hidden = !clearToken; renderCertificateApi(); toast(t("certificateApiRotated"));
} catch (error) { toast(error.message, true); } finally { button.disabled = false; }
});
$("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 || [] }; $("certificate-api-secret-wrap").hidden = true; $("certificate-api-secret").textContent = ""; 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);
$("admin-tls").addEventListener("change", () => { $("admin-certificate").disabled = !$("admin-tls").checked; });
$("prometheus-form").addEventListener("input", example);
$("prom-preview").addEventListener("click", async () => {
@@ -373,6 +280,6 @@ curl --fail --show-error --request POST \
const text = await response.text(); if (epoch !== authEpoch || !authenticated) return; $("prom-preview-output").textContent = text.slice(0, 262144); $("prom-preview-output").hidden = false;
} catch (error) { if (epoch === authEpoch) toast(error.message, true); } finally { button.disabled = false; }
});
window.rustProxySettings = { select, selectHttpTab, syncLocation, render, refreshSearch: applySettingsSearch, current: () => active, reset() { if (search) search.value = ""; applySettingsSearch(); select("listeners", false, false); selectPolicyTab("user-agent"); selectCacheTab("general"); selectLoggingTab("outputs"); selectHttpTab("http2"); certificateApiState = null; certificateApiLoading = false; $("certificate-api-key-id").value = ""; $("certificate-api-comment").value = ""; $("certificate-api-certificate-ids").value = ""; $("certificate-api-allow-deploy").checked = false; $("certificate-api-token").value = ""; $("certificate-api-secret").textContent = ""; $("certificate-api-credentials").replaceChildren(); $("certificate-api-secret-wrap").hidden = true; $("prom-preview-output").textContent = ""; $("prom-preview-output").hidden = true; } };
window.rustProxySettings = { select, selectHttpTab, syncLocation, render, refreshSearch: applySettingsSearch, current: () => active, reset() { if (search) search.value = ""; applySettingsSearch(); select("listeners", false, false); selectPolicyTab("user-agent"); selectCacheTab("general"); selectLoggingTab("outputs"); selectHttpTab("http2"); window.rustProxyCertificateApi?.reset(); $("prom-preview-output").textContent = ""; $("prom-preview-output").hidden = true; } };
select(active); render();
})();
+34 -3
View File
@@ -8081,15 +8081,46 @@ body[data-density="compact"] .page-footer {
margin-top: 10px;
}
#certificate-api-token-field[hidden],
#certificate-api-edit-note[hidden],
#certificate-api-cancel-edit[hidden] {
display: none;
}
.certificate-api-credential-item {
border: 1px solid var(--line);
border-radius: 4px;
background: var(--panel2);
}
.certificate-api-row-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
gap: 4px;
}
.certificate-api-details {
display: grid;
gap: 7px;
padding: 10px 12px;
border-top: 1px solid var(--line);
}
.certificate-api-detail-line {
display: grid;
gap: 4px;
grid-template-columns: minmax(120px, 1fr) minmax(0, 2fr);
overflow-wrap: anywhere;
}
.certificate-api-credential-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 8px 9px;
border: 1px solid var(--line);
border-radius: 4px;
background: var(--panel2);
}
.certificate-api-credential-info {