v0.31.3
This commit is contained in:
1 parent
0facfe6f5c
commit
95c55da2ee
39 files changed
+715
-151
No files matched your search
@@ -25,7 +25,7 @@ function panelFromLocation() { if (typeof location === "undefined") return "over
|
||||
let envelope = null, busy = false, connected = false, currentPanel = panelFromLocation(), previous = null;
|
||||
let samples = [], protocolSamples = { h1: [], h2: [], h3: [] }, websocketProtocolSamples = { h1: [], h2: [], h3: [] }, transformSamples = [], toastTimer, editingBackend = null, editingRoute = null;
|
||||
let liveEvents = null, liveConnected = false, liveFallback = false, liveInterval = null, authEpoch = 0, pendingAuthDestination = null;
|
||||
let lastApiLatencyMs = null;
|
||||
let lastApiLatencyMs = null, sessionCheckPromise = null;
|
||||
let adminAuthChannel = null; try { adminAuthChannel = new BroadcastChannel("rustproxy-admin-session"); adminAuthChannel.addEventListener("message", event => { if (event.data === "locked" && authenticated) lock(false); }); } catch { }
|
||||
// Language content is discovered by build.rs from langs/*.json, never compiled
|
||||
// into a two-language switch here. Locale metadata also drives Intl and RTL.
|
||||
@@ -388,7 +388,7 @@ async function activate(info) {
|
||||
document.documentElement.classList.remove("app-startup-failed");
|
||||
rustProxyRefresh.start();
|
||||
const preferences = await loadAdminPreferences();
|
||||
if (!authenticated || epoch !== authEpoch) return;
|
||||
if (!authenticated || epoch !== authEpoch) return false;
|
||||
window.rustProxyLogs?.applyPreferences(preferences);
|
||||
window.rustProxyFileLogs?.applyPreferences(preferences);
|
||||
window.rustProxyVhosts?.applyPreferences(preferences);
|
||||
@@ -399,17 +399,18 @@ async function activate(info) {
|
||||
for (let attempt = 0; attempt < 3 && authenticated && epoch === authEpoch; attempt++) {
|
||||
try { await loadConfig(); break; }
|
||||
catch (error) {
|
||||
if (!authenticated || epoch !== authEpoch || error?.status === 401) return;
|
||||
if (attempt === 2) { showStartupFailure(error); return; }
|
||||
if (!authenticated || epoch !== authEpoch || error?.status === 401) return false;
|
||||
if (attempt === 2) { showStartupFailure(error); return false; }
|
||||
await new Promise(resolve => setTimeout(resolve, (attempt + 1) * 400));
|
||||
}
|
||||
}
|
||||
if (!authenticated || epoch !== authEpoch) return;
|
||||
if (!authenticated || epoch !== authEpoch) return false;
|
||||
// A single full snapshot seeds non-live diagnostics; subsequent updates
|
||||
// continue over SSE rather than polling full status on a timer.
|
||||
void refresh(true).catch(error => {
|
||||
if (authenticated && epoch === authEpoch && error?.status !== 401) toast(error.message, true);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
async function loadAudit() { if (!authenticated) return; const epoch = authEpoch; try { const data = await api("/api/audit"); if (!authenticated || epoch !== authEpoch) return; const rows = data.entries.map(item => { const tr = el("tr"); for (const value of [formatDateTime(item.time * 1000), item.actor, item.action, item.detail]) { const td = el("td", value); td.title = value; tr.append(td); } return tr; }); $("audit-rows").replaceChildren(...rows); $("empty-audit").hidden = rows.length > 0; } catch (e) { toast(e.message, true); } }
|
||||
function tunnelMessage(value = "", error = false) { const node = $("tunnels-status"); if (!node) return; node.textContent = value; node.classList.toggle("error", !!error); }
|
||||
@@ -475,26 +476,3 @@ $("tunnels-dialog").addEventListener("close", stopTunnelRefresh);
|
||||
$("config-editor").addEventListener("input", () => setSettingsDirty("config-editor"));
|
||||
$("save-json").addEventListener("click", async event => { const editor = $("config-editor"), page = editor.closest("[data-settings-page]"); if (!envelope || busy) return; try { await withFormSubmitState(page, event.currentTarget, async () => { let config; try { config = JSON.parse(editor.value); } catch { throw fieldError(editor, t("vhJsonInvalid")); } if (await save(config)) setSettingsDirty("config-editor", false); }); } catch (e) { toast(e.message, true); } });
|
||||
$("export-config").addEventListener("click", () => { const url = URL.createObjectURL(new Blob([JSON.stringify(envelope.config, null, 2) + "\n"], { type: "application/json" })); const a = el("a"); a.href = url; a.download = `RustProxy-config-r${envelope.revision}.json`; a.click(); setTimeout(() => URL.revokeObjectURL(url), 1000); });
|
||||
async function revalidateSession() {
|
||||
const epoch = authEpoch;
|
||||
try {
|
||||
const info = await api("/api/auth/session");
|
||||
clearInvalidOriginRecovery();
|
||||
if (epoch !== authEpoch) return;
|
||||
if (!authenticated) { await activate(info); return; }
|
||||
sessionInfo = info; csrf = info.csrf_token; renderSession(); window.rustProxyRefresh?.sync();
|
||||
} catch (error) { if (recoverInvalidOrigin(error)) return; if (error.status !== 401) toast(error.message, true); }
|
||||
}
|
||||
window.addEventListener("pageshow", event => { if (event.persisted) void revalidateSession(); });
|
||||
function showStartupFailure(error) {
|
||||
// Never expose a blank page or an incomplete workspace after startup fails.
|
||||
// The authenticated shell keeps its own retry link; 401 still redirects to login.
|
||||
$("login").hidden = true;
|
||||
$("workspace").hidden = true;
|
||||
$("logout").hidden = !authenticated;
|
||||
document.documentElement.classList.add("app-loading", "app-startup-failed");
|
||||
$("app-startup-title").textContent = languagePacks.size ? t("startupFailed") : "Could not load RustProxy";
|
||||
const detail = $("app-startup-detail");
|
||||
detail.textContent = error?.message || String(error);
|
||||
detail.hidden = false;
|
||||
}
|
||||
Reference in new issue
Block a user