This commit is contained in:
Mateusz Gruszczyński committed 2026-10-06 10:16:16 +02:00
1 parent 32e6875980
commit cf25423dde
68 files changed
+2314 -422

No files matched your search

+21 -6
View File
@@ -2,6 +2,7 @@
// Persistent tab DOM: switching tabs never discards unsaved form values.
(() => {
const buttons = [...document.querySelectorAll("[data-settings-tab]")];
const search = $("settings-search"), searchStatus = $("settings-search-status");
const defaultTab = (typeof location !== "undefined" && location.pathname === "/configuration") ? "manual-config" : "listeners";
const requestedTab = typeof location !== "undefined" && location.pathname === "/logging"
? "logging"
@@ -9,6 +10,17 @@
const tabAliases = { http2: "connections" };
const normalizedRequestedTab = tabAliases[requestedTab] || requestedTab;
let active = buttons.some(button => button.dataset.settingsTab === normalizedRequestedTab) ? normalizedRequestedTab : defaultTab;
function applySettingsSearch() {
const query = String(search?.value || "").trim().toLocaleLowerCase();
const matches = [];
for (const button of buttons) {
const page = document.querySelector(`[data-settings-page="${CSS.escape(button.dataset.settingsTab)}"]`);
const haystack = `${button.textContent || ""} ${page?.textContent || ""}`.toLocaleLowerCase();
const match = !query || haystack.includes(query); button.hidden = !match; if (match) matches.push(button);
}
if (searchStatus) searchStatus.textContent = query ? `${t("settingsSearchResults")}: ${matches.length}` : "";
if (query && !matches.some(button => button.dataset.settingsTab === active) && matches[0]) select(matches[0].dataset.settingsTab, false, false);
}
function syncLocation() {
if (typeof location === "undefined" || !["/settings", "/configuration", "/logging"].includes(location.pathname) || !["http:", "https:"].includes(location.protocol)) return;
const url = new URL(location.href); const alreadyCanonical = url.pathname === "/settings" && url.searchParams.get("tab") === active; if (alreadyCanonical) return;
@@ -16,18 +28,21 @@
history.replaceState({ ...(history.state || {}), panel: "settings", settingsTab: active }, "", `${url.pathname}${url.search}${url.hash}`);
}
function select(name, focus = false, persist = true) {
if (!buttons.some(b => b.dataset.settingsTab === name)) return; active = name;
const target = buttons.find(button => button.dataset.settingsTab === name); if (!target) return;
if (target.hidden && search?.value) { search.value = ""; applySettingsSearch(); }
active = name;
for (const b of buttons) { const chosen = b.dataset.settingsTab === name; b.setAttribute("aria-selected", String(chosen)); b.tabIndex = chosen ? 0 : -1; if (chosen && focus) b.focus(); }
document.querySelectorAll("[data-settings-page]").forEach(p => p.hidden = p.dataset.settingsPage !== name);
if (persist) syncLocation();
}
for (const b of buttons) {
b.addEventListener("click", () => select(b.dataset.settingsTab)); b.addEventListener("keydown", event => {
const index = buttons.indexOf(b); let next;
if (event.key === "ArrowRight" || event.key === "ArrowDown") next = (index + 1) % buttons.length; else if (event.key === "ArrowLeft" || event.key === "ArrowUp") next = (index + buttons.length - 1) % buttons.length; else if (event.key === "Home") next = 0; else if (event.key === "End") next = buttons.length - 1; else return;
event.preventDefault(); select(buttons[next].dataset.settingsTab, true);
const visible = buttons.filter(button => !button.hidden), index = visible.indexOf(b); let next;
if (event.key === "ArrowRight" || event.key === "ArrowDown") next = (index + 1) % visible.length; else if (event.key === "ArrowLeft" || event.key === "ArrowUp") next = (index + visible.length - 1) % visible.length; else if (event.key === "Home") next = 0; else if (event.key === "End") next = visible.length - 1; else return;
if (!visible.length) return; event.preventDefault(); select(visible[next].dataset.settingsTab, true);
});
}
search?.addEventListener("input", applySettingsSearch);
const cacheTabs = [...document.querySelectorAll("[data-cache-settings-tab]")];
function selectCacheTab(name, focus = false) {
if (!cacheTabs.some(button => button.dataset.cacheSettingsTab === name)) return;
@@ -101,7 +116,7 @@ curl --fail --show-error --request POST \
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 lastUsed = credential.last_used_at ? new Date(Number(credential.last_used_at) * 1000).toLocaleString() : t("certificateApiNeverUsed");
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;
@@ -206,6 +221,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, current: () => active, reset() { select("listeners", false, false); selectCacheTab("general"); selectLoggingTab("outputs"); selectHttpTab("http2"); certificateApiState = null; certificateApiLoading = false; $("certificate-api-key-id").value = ""; $("certificate-api-comment").value = ""; $("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); selectCacheTab("general"); selectLoggingTab("outputs"); selectHttpTab("http2"); certificateApiState = null; certificateApiLoading = false; $("certificate-api-key-id").value = ""; $("certificate-api-comment").value = ""; $("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; } };
select(active); render();
})();