v0.3
This commit is contained in:
1 parent
28e39f7ce4
commit
b57beb83c7
34 files changed
+1037
-314
No files matched your search
@@ -22,7 +22,7 @@ function clearInvalidOriginRecovery() {
|
||||
}
|
||||
const PANEL_PATHS = { overview: "/overview", vhosts: "/vhosts", "web-server": "/web-server", "docker-vhosts": "/docker-vhosts", redirects: "/redirects", notfound: "/404-hosts", streams: "/streams", "acl-profiles": "/acl-profiles", tls: "/tls", traffic: "/traffic", cache: "/cache", transform: "/transform", filelogs: "/logs", settings: "/settings" };
|
||||
function panelFromLocation() { if (typeof location === "undefined") return "overview"; if (location.pathname === "/configuration" || location.pathname === "/logging") return "settings"; const found = Object.entries(PANEL_PATHS).find(([, path]) => path === location.pathname); return found?.[0] || "overview"; }
|
||||
let envelope = null, busy = false, connected = false, currentPanel = panelFromLocation(), previous = null;
|
||||
let envelope = null, configStorage = 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, sessionCheckPromise = null;
|
||||
@@ -57,7 +57,7 @@ function translate() {
|
||||
window.rustProxyDrafts?.refresh();
|
||||
window.rustProxyPreferences?.refreshAppearanceUi?.();
|
||||
$("connection-label").textContent = t(connected ? "connected" : "disconnected");
|
||||
if (envelope) renderConfig(); if (latestStatus) { renderFooter(latestStatus); renderDiagnostics(latestStatus, false); window.rustProxyVhosts?.renderStatus(latestStatus); window.rustProxyStreams?.renderStatus(latestStatus); window.rustProxyStreamDashboard?.render(latestStatus); window.rustProxyBackendDashboard?.render(latestStatus, null, false); window.rustProxyExtraOverview?.render(latestStatus, false); window.rustProxyWideOverview?.render(latestStatus, false); window.rustProxyWebOverview?.render(latestStatus, false); } if (lastLogs) renderLogs(lastLogs);
|
||||
if (envelope) renderConfig(); renderConfigStorage(); if (latestStatus) { renderFooter(latestStatus); renderDiagnostics(latestStatus, false); window.rustProxyVhosts?.renderStatus(latestStatus); window.rustProxyStreams?.renderStatus(latestStatus); window.rustProxyStreamDashboard?.render(latestStatus); window.rustProxyBackendDashboard?.render(latestStatus, null, false); window.rustProxyExtraOverview?.render(latestStatus, false); window.rustProxyWideOverview?.render(latestStatus, false); window.rustProxyWebOverview?.render(latestStatus, false); } if (lastLogs) renderLogs(lastLogs);
|
||||
syncSettingsDirtyIndicators();
|
||||
}
|
||||
function hideToast() { const node = $("toast"); if (typeof node.hidePopover === "function" && node.matches(":popover-open")) node.hidePopover(); node.hidden = true; }
|
||||
@@ -335,7 +335,28 @@ async function refreshCertificateInventory() {
|
||||
if (e.status !== 401 && authenticated && epoch === authEpoch) certificateInventory = new Map();
|
||||
}
|
||||
}
|
||||
async function loadConfig() { const epoch = authEpoch; const value = await api("/api/config"); if (!authenticated || epoch !== authEpoch) return; envelope = value; await refreshCertificateInventory(); if (!authenticated || epoch !== authEpoch) return; renderConfig(); }
|
||||
function renderConfigStorage() {
|
||||
const mode = $("config-storage-mode"), location = $("config-storage-location");
|
||||
const description = $("config-storage-description"), history = $("config-storage-history");
|
||||
if (!configStorage) {
|
||||
mode.textContent = t("configStorageLoading");
|
||||
location.textContent = ""; description.textContent = ""; history.textContent = "";
|
||||
return;
|
||||
}
|
||||
if (configStorage.error) {
|
||||
mode.textContent = t("configStorageUnavailable");
|
||||
location.textContent = ""; description.textContent = ""; history.textContent = "";
|
||||
return;
|
||||
}
|
||||
const file = configStorage.mode === "file";
|
||||
mode.textContent = t(file ? "configStorageFile" : "configStorageSqlite");
|
||||
const source = file ? configStorage.config_path : configStorage.database_path;
|
||||
location.textContent = source ? `${t("configStoragePath")}: ${source}` : "";
|
||||
description.textContent = t(file ? "configStorageFileHelp" : "configStorageSqliteHelp");
|
||||
history.textContent = file && configStorage.database_path
|
||||
? `${t("configStorageHistory")}: ${configStorage.database_path}` : "";
|
||||
}
|
||||
async function loadConfig() { const epoch = authEpoch; const value = await api("/api/config"); if (!authenticated || epoch !== authEpoch) return; envelope = value; configStorage = value.storage || { error: true }; renderConfigStorage(); await refreshCertificateInventory(); if (!authenticated || epoch !== authEpoch) return; renderConfig(); }
|
||||
function rulesText(acl) { return (acl?.rules || []).map(r => `${r.action} ${r.network}`).join("\n"); }
|
||||
function normalizeAclRuleText(value) {
|
||||
return String(value ?? "").normalize("NFKC")
|
||||
@@ -376,7 +397,7 @@ function lock(notify = true) {
|
||||
window.rustProxyInlineLayout?.cancel?.();
|
||||
window.rustProxyRefresh?.stop();
|
||||
const destination = pendingAuthDestination || authReturnDestination(), navigating = typeof location !== "undefined" && (location.protocol === "http:" || location.protocol === "https:");
|
||||
pendingAuthDestination = destination; if (notify) adminAuthChannel?.postMessage("locked"); $("api-docs").hidden = true; $("logs-menu").hidden = true; resetAdminPreferences(); window.rustProxyCertificates?.reset(); window.rustProxySettings?.reset(); window.rustProxyTrafficPanel?.reset(); window.rustProxyCachePanel?.reset(); window.rustProxyTransformPanel?.reset(); window.rustProxyAcme?.reset(); window.rustProxyIntegrations?.reset(); window.rustProxyLogs?.reset(); window.rustProxyFileLogs?.reset(); window.rustProxyDrafts?.reset(); $("logs-open").hidden = true; $("file-logs-open").hidden = true; window.rustProxyAclProfiles?.reset(); window.rustProxyVhosts?.reset(); window.rustProxyWebServer?.reset(); window.rustProxyDocker?.reset(); window.rustProxyRedirects?.reset(); window.rustProxyNotFound?.reset(); window.rustProxyStreams?.reset(); window.rustProxyStreamDashboard?.reset(); window.rustProxyBackendDashboard?.reset(); window.rustProxyExtraOverview?.reset(); window.rustProxyWideOverview?.reset(); window.rustProxyWebOverview?.reset(); authEpoch++; stopLive(); csrf = ""; authenticated = false; sessionInfo = null; envelope = null; certificateInventory = new Map(); previous = null; samples = []; protocolSamples = { h1: [], h2: [], h3: [] }; websocketProtocolSamples = { h1: [], h2: [], h3: [] }; transformSamples = []; systemSamples = []; connectionOpenSamples = []; connectionExchangeSamples = []; networkInterfaceSamples.clear(); lastLogs = null; latestStatus = null; clearSettingsDirty(); connected = false; busy = false; $("workspace").hidden = true; $("login").hidden = navigating; $("logout").hidden = true; $("password").value = ""; $("password-confirm").value = ""; $("setup-token").value = ""; $("password-form").reset(); $("config-editor").value = ""; $("logs").replaceChildren(); $("audit-rows").replaceChildren(); authMessage("");
|
||||
pendingAuthDestination = destination; if (notify) adminAuthChannel?.postMessage("locked"); $("api-docs").hidden = true; $("logs-menu").hidden = true; resetAdminPreferences(); window.rustProxyCertificates?.reset(); window.rustProxySettings?.reset(); window.rustProxyTrafficPanel?.reset(); window.rustProxyCachePanel?.reset(); window.rustProxyTransformPanel?.reset(); window.rustProxyAcme?.reset(); window.rustProxyIntegrations?.reset(); window.rustProxyLogs?.reset(); window.rustProxyFileLogs?.reset(); window.rustProxyDrafts?.reset(); $("logs-open").hidden = true; $("file-logs-open").hidden = true; window.rustProxyAclProfiles?.reset(); window.rustProxyVhosts?.reset(); window.rustProxyWebServer?.reset(); window.rustProxyDocker?.reset(); window.rustProxyRedirects?.reset(); window.rustProxyNotFound?.reset(); window.rustProxyStreams?.reset(); window.rustProxyStreamDashboard?.reset(); window.rustProxyBackendDashboard?.reset(); window.rustProxyExtraOverview?.reset(); window.rustProxyWideOverview?.reset(); window.rustProxyWebOverview?.reset(); authEpoch++; stopLive(); csrf = ""; authenticated = false; sessionInfo = null; envelope = null; configStorage = null; certificateInventory = new Map(); previous = null; samples = []; protocolSamples = { h1: [], h2: [], h3: [] }; websocketProtocolSamples = { h1: [], h2: [], h3: [] }; transformSamples = []; systemSamples = []; connectionOpenSamples = []; connectionExchangeSamples = []; networkInterfaceSamples.clear(); lastLogs = null; latestStatus = null; clearSettingsDirty(); connected = false; busy = false; $("workspace").hidden = true; $("login").hidden = navigating; $("logout").hidden = true; $("password").value = ""; $("password-confirm").value = ""; $("setup-token").value = ""; $("password-form").reset(); $("config-editor").value = ""; $("logs").replaceChildren(); $("audit-rows").replaceChildren(); authMessage("");
|
||||
for (const id of ["certificate-list", "host-policy-list"]) { $(id)?.replaceChildren(); }
|
||||
$("workspace").querySelectorAll("form").forEach(form => form.reset());
|
||||
$("workspace").querySelectorAll("input:not([type=checkbox]):not([type=radio]),textarea").forEach(input => { input.value = ""; });
|
||||
|
||||
+60
-24
@@ -2769,14 +2769,23 @@
|
||||
id="prom-path" maxlength="128" required="" value="/metrics" /></label></div>
|
||||
<label><span data-i18n="prometheusCidrs"></span><textarea id="prom-cidrs" required=""
|
||||
rows="3" spellcheck="false"></textarea></label>
|
||||
<div class="card-heading"><h3 data-i18n="promTokenTitle">Prometheus bearer token</h3></div>
|
||||
<p class="fine" data-i18n="promTokenHelp">Generate a token directly in the panel. Only its SHA-256 hash is retained; rotating invalidates the previous token immediately.</p>
|
||||
<div class="card-heading">
|
||||
<h3 data-i18n="promTokenTitle">Prometheus bearer token</h3>
|
||||
</div>
|
||||
<p class="fine" data-i18n="promTokenHelp">Generate a token directly in the panel. Only
|
||||
its SHA-256 hash is retained; rotating invalidates the previous token immediately.
|
||||
</p>
|
||||
<p class="fine" id="prom-token-status" role="status"></p>
|
||||
<div class="form-actions">
|
||||
<button class="outline" id="prom-token-generate" type="button" data-i18n="promTokenGenerate">Generate / rotate token</button>
|
||||
<button class="quiet danger" id="prom-token-revoke" type="button" data-i18n="promTokenRevoke">Revoke token</button>
|
||||
<button class="outline" id="prom-token-generate" type="button"
|
||||
data-i18n="promTokenGenerate">Generate / rotate token</button>
|
||||
<button class="quiet danger" id="prom-token-revoke" type="button"
|
||||
data-i18n="promTokenRevoke">Revoke token</button>
|
||||
</div>
|
||||
<div hidden id="prom-token-secret-wrap"><label data-i18n="apiTokenCopyNow">Copy the
|
||||
token now — it will not be displayed again</label>
|
||||
<pre class="metrics-example" id="prom-token-secret"></pre>
|
||||
</div>
|
||||
<div hidden id="prom-token-secret-wrap"><label data-i18n="apiTokenCopyNow">Copy the token now — it will not be displayed again</label><pre class="metrics-example" id="prom-token-secret"></pre></div>
|
||||
<label class="check"><input id="prom-trusted-no-token" type="checkbox" /><span
|
||||
data-i18n="prometheusTrustedNoToken"></span></label>
|
||||
<p class="fine" data-i18n="prometheusTrustedNoTokenHelp"></p>
|
||||
@@ -2869,9 +2878,14 @@
|
||||
type="submit">Validate, save and apply</button></div>
|
||||
</form>
|
||||
<article class="card edit-form" id="api-tokens-shortcut">
|
||||
<div class="card-heading"><h2 data-i18n="generalApiTokensTitle">API tokens (RO/RW)</h2></div>
|
||||
<p class="fine" data-i18n="apiTokensMovedHelp">API token management has moved to Settings → Security.</p>
|
||||
<div class="form-actions"><a class="outline" id="api-tokens-security-link" href="/settings?tab=security#api-tokens-card" data-i18n="apiTokensOpenSecurity">Open API tokens in Security</a></div>
|
||||
<div class="card-heading">
|
||||
<h2 data-i18n="generalApiTokensTitle">API tokens (RO/RW)</h2>
|
||||
</div>
|
||||
<p class="fine" data-i18n="apiTokensMovedHelp">API token management has moved to
|
||||
Settings → Security.</p>
|
||||
<div class="form-actions"><a class="outline" id="api-tokens-security-link"
|
||||
href="/settings?tab=security#api-tokens-card"
|
||||
data-i18n="apiTokensOpenSecurity">Open API tokens in Security</a></div>
|
||||
</article>
|
||||
</div>
|
||||
<div aria-labelledby="settings-tab-system-info" class="settings-page"
|
||||
@@ -3039,7 +3053,7 @@
|
||||
role="tabpanel">
|
||||
<article class="card edit-form">
|
||||
<div class="card-heading">
|
||||
<div><span class="small-label">DESIRED STATE / SQLITE</span>
|
||||
<div><span class="small-label" data-i18n="configStorageLabel">CONFIGURATION SOURCE</span>
|
||||
<h2 data-i18n="manualConfig">Manual config</h2>
|
||||
</div>
|
||||
<div class="manual-config-tools"><button class="quiet" data-i18n="configHistory"
|
||||
@@ -3047,6 +3061,12 @@
|
||||
class="outline" data-i18n="export" id="export-config" type="button">Export
|
||||
JSON</button></div>
|
||||
</div>
|
||||
<div class="manual-config-source" role="status" aria-live="polite">
|
||||
<strong id="config-storage-mode" data-i18n="configStorageLoading">Checking storage...</strong>
|
||||
<p id="config-storage-location" class="manual-config-path"></p>
|
||||
<p id="config-storage-description" class="fine"></p>
|
||||
<p id="config-storage-history" class="fine"></p>
|
||||
</div>
|
||||
<p class="fine" data-i18n="configHelp">A valid save creates one new revision. Invalid or
|
||||
stale changes leave the running configuration untouched.</p>
|
||||
<label class="sr-only" for="config-editor">JSON</label><textarea id="config-editor"
|
||||
@@ -3055,8 +3075,6 @@
|
||||
id="save-json">Validate, save and apply</button><span class="fine"
|
||||
data-i18n="noRestart">Listener changes require restart; other settings apply
|
||||
live.</span></div>
|
||||
<p class="fine" data-i18n="roadmapHelp">Release details and planned features are
|
||||
documented in ROADMAP.md.</p>
|
||||
</article>
|
||||
</div>
|
||||
<section aria-labelledby="settings-tab-security" class="settings-page"
|
||||
@@ -3071,19 +3089,37 @@
|
||||
type="button">Administration audit</button>
|
||||
</div>
|
||||
<div class="security-stack">
|
||||
<article class="card edit-form" id="api-tokens-card" tabindex="-1">
|
||||
<div class="card-heading"><h2 data-i18n="generalApiTokensTitle">API tokens (RO/RW)</h2></div>
|
||||
<p class="fine" data-i18n="generalApiTokensHelp">Generate independent API tokens with read-only or read-write access, restricted to your client IP/CIDR. Existing browser login remains available. Non-loopback requests require HTTPS.</p>
|
||||
<form id="api-tokens-form">
|
||||
<label><span data-i18n="apiTokenComment">Description</span><input id="api-token-comment" maxlength="128" placeholder="external-automation" /></label>
|
||||
<label><span data-i18n="apiTokenPermission">Permissions</span><select id="api-token-permission"><option value="ro" data-i18n="apiTokenReadOnly">Read only (RO)</option><option value="rw" data-i18n="apiTokenReadWrite">Read and write (RW)</option></select></label>
|
||||
<label><span data-i18n="apiTokenIpAllow">Allowed source IP/CIDR (one per line)</span><textarea id="api-token-cidrs" rows="3" required placeholder="192.0.2.10/32"></textarea></label>
|
||||
<div class="form-actions"><button class="primary" type="submit" data-i18n="apiTokenCreate">Generate API token</button></div>
|
||||
</form>
|
||||
<div id="api-token-secret-wrap" hidden><label data-i18n="apiTokenCopyNow">Copy the token now — it will not be displayed again</label><pre id="api-token-secret" class="metrics-example"></pre></div>
|
||||
<div id="api-token-list" class="certificate-api-credentials"></div>
|
||||
<pre class="metrics-example">curl -H 'Authorization: Bearer $RUSTPROXY_API_TOKEN' https://panel.example.com/api/config</pre>
|
||||
</article>
|
||||
<article class="card edit-form" id="api-tokens-card" tabindex="-1">
|
||||
<div class="card-heading">
|
||||
<h2 data-i18n="generalApiTokensTitle">API tokens (RO/RW)</h2>
|
||||
</div>
|
||||
<p class="fine" data-i18n="generalApiTokensHelp">Generate independent API tokens
|
||||
with read-only or read-write access, restricted to your client IP/CIDR. Existing
|
||||
browser login remains available. Non-loopback requests require HTTPS.</p>
|
||||
<form id="api-tokens-form">
|
||||
<label><span data-i18n="apiTokenComment">Description</span><input
|
||||
id="api-token-comment" maxlength="128"
|
||||
placeholder="external-automation" /></label>
|
||||
<label><span data-i18n="apiTokenPermission">Permissions</span><select
|
||||
id="api-token-permission">
|
||||
<option value="ro" data-i18n="apiTokenReadOnly">Read only (RO)</option>
|
||||
<option value="rw" data-i18n="apiTokenReadWrite">Read and write (RW)
|
||||
</option>
|
||||
</select></label>
|
||||
<label><span data-i18n="apiTokenIpAllow">Allowed source IP/CIDR (one per
|
||||
line)</span><textarea id="api-token-cidrs" rows="3" required
|
||||
placeholder="192.0.2.10/32"></textarea></label>
|
||||
<div class="form-actions"><button class="primary" type="submit"
|
||||
data-i18n="apiTokenCreate">Generate API token</button></div>
|
||||
</form>
|
||||
<div id="api-token-secret-wrap" hidden><label data-i18n="apiTokenCopyNow">Copy the
|
||||
token now — it will not be displayed again</label>
|
||||
<pre id="api-token-secret" class="metrics-example"></pre>
|
||||
</div>
|
||||
<div id="api-token-list" class="certificate-api-credentials"></div>
|
||||
<pre
|
||||
class="metrics-example">curl -H 'Authorization: Bearer $RUSTPROXY_API_TOKEN' https://panel.example.com/api/config</pre>
|
||||
</article>
|
||||
<article class="card security-account-card">
|
||||
<div class="card-heading">
|
||||
<div><span class="small-label" data-i18n="currentSession">CURRENT SESSION</span>
|
||||
|
||||
@@ -1033,6 +1033,23 @@ button.is-saving {
|
||||
margin-top: 20px
|
||||
}
|
||||
|
||||
.manual-config-source {
|
||||
padding: 12px 16px;
|
||||
margin-top: 14px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius, 8px);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.manual-config-source p {
|
||||
margin: 6px 0 0;
|
||||
}
|
||||
|
||||
.manual-config-path {
|
||||
font: 12px/1.6 var(--mono);
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.page-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
Reference in new issue
Block a user