4104 lines
330 KiB
HTML
4104 lines
330 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<html class="app-loading" data-theme="dark" lang="en">
|
||
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta content="width=device-width, initial-scale=1" name="viewport" />
|
||
<title>RustProxy / Control room</title>
|
||
<link href="/favicon.svg" rel="icon" />
|
||
<link href="/assets/style___RUSTPROXY_ASSET_ID__.css" rel="stylesheet" />
|
||
<script defer="" src="/assets/app___RUSTPROXY_ASSET_ID__.js"></script>
|
||
</head>
|
||
|
||
<body>
|
||
<div class="app-startup" id="app-startup" role="status" aria-live="polite">
|
||
<div class="app-startup-card">
|
||
<div class="app-startup-brand" aria-hidden="true">r<span>/</span>p</div>
|
||
<div class="app-startup-spinner" aria-hidden="true"></div>
|
||
<h2 id="app-startup-title" data-i18n="startupLoading">Loading RustProxy…</h2>
|
||
<p class="app-startup-message" data-i18n="startupPreparing">Connecting to the administration panel.</p>
|
||
<div class="app-startup-recovery">
|
||
<p data-i18n="startupSlow">The panel is taking longer than expected. Check your connection and try
|
||
again.</p>
|
||
<p id="app-startup-detail" class="app-startup-detail" hidden></p>
|
||
<a class="outline" id="app-startup-reload" href="" data-i18n="startupRetry">Reload panel</a>
|
||
<button class="outline" type="button" id="app-session-retry" data-i18n="sessionRetry" hidden>Retry
|
||
connection</button>
|
||
</div>
|
||
<noscript>JavaScript is required to use the administration panel.</noscript>
|
||
</div>
|
||
</div>
|
||
<div class="shell">
|
||
<aside class="sidebar">
|
||
<a class="brand" href="/" aria-label="RustProxy"><span
|
||
class="brand-mark">r<span>/</span>p</span><span>RustProxy</span></a>
|
||
<button aria-controls="main-navigation" aria-expanded="false" class="mobile-menu-toggle"
|
||
data-i18n-aria="mainMenu" id="mobile-menu-toggle" type="button"><span aria-hidden="true"
|
||
class="mobile-menu-icon"></span><span data-i18n="mainMenu">Menu</span></button>
|
||
<div class="edition">INDEPENDENT BY DESIGN</div>
|
||
<nav aria-label="Main navigation" class="main-navigation" id="main-navigation">
|
||
<button class="nav active" data-panel="overview"><span>01</span><b
|
||
data-i18n="overview">Overview</b></button>
|
||
<button class="nav" data-panel="vhosts"><span>02</span><b data-i18n="vhosts">Vhosts</b></button>
|
||
<button class="nav" data-panel="web-server"><span>03</span><b data-i18n="webServer">Web
|
||
Server</b></button>
|
||
<button class="nav" data-panel="docker-vhosts"><span>04</span><b data-i18n="docker-vhosts">Docker
|
||
vhosts</b></button>
|
||
<button class="nav" data-panel="redirects"><span>05</span><b
|
||
data-i18n="redirects">Redirects</b></button>
|
||
<button class="nav" data-panel="notfound"><span>06</span><b data-i18n="notfound">404 hosts</b></button>
|
||
<button class="nav" data-panel="streams"><span>07</span><b data-i18n="streams">Streams</b></button>
|
||
<button class="nav" data-panel="acl-profiles"><span>08</span><b data-i18n="aclProfiles">ACL
|
||
profiles</b></button>
|
||
<button class="nav" data-panel="tls"><span>09</span><b data-i18n="tls">TLS</b></button>
|
||
<button class="nav" data-panel="traffic"><span>10</span><b data-i18n="traffic">Traffic</b></button>
|
||
<button class="nav" data-panel="cache"><span>11</span><b data-i18n="cache">Cache</b></button>
|
||
<button class="nav" data-panel="transform"><span>12</span><b
|
||
data-i18n="transform">Transform</b></button>
|
||
<button class="nav" data-panel="settings"><span>13</span><b data-i18n="settings">Settings</b></button>
|
||
</nav>
|
||
<div class="sidebar-bottom"><span class="small-label" data-i18n="runtime">RUNTIME</span>
|
||
<p>Rust / Tokio / Hyper<br />Axum / Tower / SQLite</p>
|
||
<div class="local-tag"><i></i><span data-i18n="controlPlane">CONTROL PLANE</span></div>
|
||
</div>
|
||
</aside>
|
||
<main>
|
||
<header class="topbar"><span class="breadcrumb">CONTROL ROOM <span>/</span> <b
|
||
id="page-label">Overview</b></span>
|
||
<div class="top-actions"><a class="quiet" href="/docs" data-i18n="documentation" id="api-docs"
|
||
hidden="">Documentation</a>
|
||
<details class="top-log-menu" hidden="" id="logs-menu">
|
||
<summary class="quiet" data-i18n="logs">Logs</summary>
|
||
<div class="top-log-menu-items"><button class="quiet" data-i18n="storedLogs" hidden=""
|
||
id="file-logs-open" type="button">Stored logs</button><button class="quiet"
|
||
data-i18n="liveLogs" hidden="" id="logs-open" type="button">Live access logs</button>
|
||
</div>
|
||
</details><button class="quiet" data-i18n="about" id="about-open"
|
||
type="button">About</button><button class="quiet" data-i18n="logout" hidden=""
|
||
id="logout">Lock</button>
|
||
</div>
|
||
</header>
|
||
<section class="login-section" hidden="" id="login">
|
||
<div class="eyebrow" data-i18n="firstSlice">THE FIRST WORKING SLICE</div>
|
||
<h1 data-i18n="loginTitle">Your traffic.<br />Your control.</h1>
|
||
<form class="login-card" id="login-form">
|
||
<div class="card-heading">
|
||
<h2 id="auth-title">Sign in</h2><span class="pill" id="auth-badge">LOCAL</span>
|
||
</div>
|
||
<div hidden="" id="setup-fields">
|
||
<p class="fine" data-i18n="setupHelp">Create the first administrator using a one-time local
|
||
capability.</p><code class="command">rust-proxy --data-dir ./data admin setup-token</code>
|
||
<p class="fine" data-i18n="setupCommandExample">Standard service installation example:</p>
|
||
<code class="command">rust-proxy --data-dir /var/lib/rustproxy admin setup-token</code>
|
||
<label data-i18n="setupToken" for="setup-token">One-time setup token</label><input
|
||
autocomplete="off" id="setup-token" maxlength="64" minlength="64" spellcheck="false"
|
||
type="password" />
|
||
</div>
|
||
<label data-i18n="username" for="username">Username</label><input autocapitalize="none"
|
||
autocomplete="username" id="username" maxlength="64" minlength="3"
|
||
pattern="[A-Za-z0-9_.-]{3,64}" required="" spellcheck="false" />
|
||
<label data-i18n="password" for="password">Password</label><input autocomplete="current-password"
|
||
id="password" maxlength="128" required="" type="password" />
|
||
<div hidden="" id="confirm-field"><label data-i18n="confirmPassword" for="password-confirm">Confirm
|
||
password</label><input autocomplete="new-password" id="password-confirm" maxlength="128"
|
||
type="password" /></div>
|
||
<button class="primary" id="login-submit" type="submit">Sign in</button>
|
||
<p class="fine" data-i18n="sessionNote">Passwords use Argon2id; the session cookie is HttpOnly.
|
||
Use HTTPS before binding administration beyond loopback.</p>
|
||
</form>
|
||
<p class="fine" id="auth-message" role="alert" aria-live="polite"></p>
|
||
<div class="login-footer">HTTP/2 + TLS + WS <span>/</span> STREAMING <span>/</span> HOT RELOAD</div>
|
||
</section>
|
||
<div hidden="" id="workspace">
|
||
<section class="page-intro">
|
||
<div class="page-intro-copy">
|
||
<div class="page-intro-meta-row">
|
||
<div class="eyebrow"><i class="signal" id="signal"></i><span id="connection-label">CONNECTED
|
||
TO
|
||
LOCAL CORE</span></div>
|
||
<div class="overview-samples-toolbar" id="overview-samples-toolbar" hidden role="group"
|
||
aria-label="Overview chart samples" data-i18n-aria="overviewSamplesTitle">
|
||
<label class="function-live-chart-samples" for="overview-live-samples"><span
|
||
data-i18n="liveChartSamples">Samples</span>
|
||
<select id="overview-live-samples" aria-describedby="overview-samples-state">
|
||
<option value="60">60</option>
|
||
<option value="120">120</option>
|
||
<option value="300">300</option>
|
||
<option value="600">600</option>
|
||
</select>
|
||
</label>
|
||
<button class="quiet" id="overview-samples-save" type="button"
|
||
data-i18n="overviewSamplesRemember">Remember choice</button>
|
||
<small class="fine" id="overview-samples-state" role="status" aria-live="polite"
|
||
data-i18n="overviewSamplesSaved">Saved default</small>
|
||
</div>
|
||
</div>
|
||
<div class="page-intro-title-row">
|
||
<h1 id="page-title">Traffic, in focus.</h1>
|
||
<button class="quiet" data-i18n="refreshNow" id="overview-refresh" type="button"
|
||
hidden>Refresh now</button>
|
||
<button class="quiet" data-i18n="overviewLayoutEditorOpen" id="overview-layout-open"
|
||
type="button" hidden>Dashboard presets</button>
|
||
<button class="outline" id="overview-layout-unlock" type="button" hidden
|
||
data-i18n="overviewLayoutUnlock" aria-pressed="false">Unlock layout</button>
|
||
<div class="overview-inline-actions" id="overview-inline-actions" hidden>
|
||
<button class="primary" id="overview-inline-save" type="button"
|
||
data-i18n="overviewLayoutInlineSave">Save and lock</button>
|
||
<button class="quiet" id="overview-inline-cancel" type="button"
|
||
data-i18n="overviewLayoutInlineCancel">Cancel changes</button>
|
||
</div>
|
||
</div>
|
||
<p class="lead" id="page-description"></p>
|
||
</div>
|
||
</section>
|
||
<section class="panel" id="panel-overview">
|
||
<p class="fine overview-inline-hint" id="overview-inline-hint" role="status" aria-live="polite"
|
||
hidden data-i18n="overviewLayoutInlineHint">Layout unlocked: move cards freely, resize with − /
|
||
+,
|
||
hide with × and save or cancel.</p>
|
||
<details class="overview-inline-hidden" id="overview-inline-hidden" hidden>
|
||
<summary><span data-i18n="overviewLayoutInlineHidden">Hidden blocks</span> <span
|
||
id="overview-inline-hidden-count"></span></summary>
|
||
<div class="overview-inline-hidden-list" id="overview-inline-hidden-list"></div>
|
||
</details>
|
||
<div class="stats-grid dashboard-overview-grid" id="dashboard-overview-grid">
|
||
<!-- RP_METRICS_MAIN --><!-- RP_CHARTS_MAIN --><!-- RP_METRICS_WEB --><!-- RP_CHARTS_WEB -->
|
||
<article class="card" data-dashboard-widget="cache_diagnostics">
|
||
<div class="mini-heading">
|
||
<h3 data-i18n="cacheOverview">RAM cache</h3><span class="pill" id="cache-state">-</span>
|
||
</div>
|
||
<dl class="mini-metrics">
|
||
<div>
|
||
<dt>HIT / MISS / BYPASS</dt>
|
||
<dd id="cache-counters">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="cacheObjects">Objects / hot memory</dt>
|
||
<dd id="cache-objects">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="cacheMemoryBreakdown">Hot / transient cache RAM</dt>
|
||
<dd id="cache-memory-breakdown">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="cacheTransientBreakdown">Fill / cold queue / cold I/O</dt>
|
||
<dd id="cache-transient-breakdown">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="cacheColdStorageUsage">COLD storage / budget</dt>
|
||
<dd id="cache-cold-storage">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="cacheEvictions">Evicted / expired</dt>
|
||
<dd id="cache-evictions">-</dd>
|
||
</div>
|
||
</dl>
|
||
<div class="mini-live-chart-stack">
|
||
<div class="mini-live-chart">
|
||
<div class="mini-live-chart-head"><span data-i18n="cacheOutcomeHistory">HIT / MISS /
|
||
BYPASS activity</span><b id="cache-outcome-chart-value">-</b></div>
|
||
<svg preserveAspectRatio="none" aria-label="HIT MISS BYPASS activity"
|
||
data-i18n-aria="cacheOutcomeHistory" role="img" viewbox="0 0 300 60">
|
||
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||
<polyline class="diagnostic-line cache-hit-line" id="cache-hit-sparkline"
|
||
points="0,55 300,55"></polyline>
|
||
<polyline class="diagnostic-line cache-miss-line" id="cache-miss-sparkline"
|
||
points="0,55 300,55"></polyline>
|
||
<polyline class="diagnostic-line cache-bypass-line" id="cache-bypass-sparkline"
|
||
points="0,55 300,55"></polyline>
|
||
</svg>
|
||
<div class="mini-chart-legend" aria-hidden="true"><span
|
||
class="cache-hit-key">HIT</span><span
|
||
class="cache-miss-key">MISS</span><span
|
||
class="cache-bypass-key">BYPASS</span></div>
|
||
</div>
|
||
<div class="mini-live-chart">
|
||
<div class="mini-live-chart-head"><span data-i18n="cacheHotMemoryHistory">HOT RAM
|
||
usage / limit</span><b id="cache-chart-value">-</b></div>
|
||
<svg preserveAspectRatio="none" aria-label="HOT cache RAM usage history"
|
||
data-i18n-aria="cacheHotMemoryHistory" role="img" viewbox="0 0 300 60">
|
||
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||
<polyline class="diagnostic-line cache-hot-line" id="cache-memory-sparkline"
|
||
points="0,55 300,55"></polyline>
|
||
</svg>
|
||
</div>
|
||
<div class="mini-live-chart">
|
||
<div class="mini-live-chart-head"><span
|
||
data-i18n="cacheTransientMemoryHistory">Transient
|
||
RAM usage / limit</span><b id="cache-transient-chart-value">-</b></div>
|
||
<svg preserveAspectRatio="none" aria-label="Transient cache RAM usage history"
|
||
data-i18n-aria="cacheTransientMemoryHistory" role="img" viewbox="0 0 300 60">
|
||
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||
<polyline class="diagnostic-line cache-transient-total-line"
|
||
id="cache-transient-sparkline" points="0,55 300,55"></polyline>
|
||
<polyline class="diagnostic-line cache-fill-line" id="cache-fill-sparkline"
|
||
points="0,55 300,55"></polyline>
|
||
<polyline class="diagnostic-line cache-queue-line" id="cache-queue-sparkline"
|
||
points="0,55 300,55"></polyline>
|
||
<polyline class="diagnostic-line cache-io-line" id="cache-io-sparkline"
|
||
points="0,55 300,55"></polyline>
|
||
</svg>
|
||
<div class="mini-chart-legend" aria-hidden="true"><span
|
||
class="cache-transient-total-key"
|
||
data-i18n="cacheTransientShort">TOTAL</span><span class="cache-fill-key"
|
||
data-i18n="cacheFillShort">fill</span><span class="cache-queue-key"
|
||
data-i18n="cacheQueueShort">cold queue</span><span class="cache-io-key"
|
||
data-i18n="cacheIoShort">cold I/O</span></div>
|
||
</div>
|
||
<div class="mini-live-chart">
|
||
<div class="mini-live-chart-head"><span data-i18n="cacheColdStorageHistory">COLD
|
||
storage
|
||
usage / limit</span><b id="cache-cold-chart-value">-</b></div>
|
||
<svg preserveAspectRatio="none" aria-label="COLD storage usage history"
|
||
data-i18n-aria="cacheColdStorageHistory" role="img" viewbox="0 0 300 60">
|
||
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||
<polyline class="diagnostic-line cache-cold-line" id="cache-cold-sparkline"
|
||
points="0,55 300,55"></polyline>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
<article class="card" data-dashboard-widget="process_diagnostics">
|
||
<div class="mini-heading">
|
||
<h3 data-i18n="processDiagnostics">Process</h3>
|
||
<div class="mini-heading-actions">
|
||
<details class="metric-info">
|
||
<summary data-i18n-aria="memoryAccountingInfo"
|
||
aria-label="Memory accounting information">i</summary>
|
||
<div class="metric-info-popover">
|
||
<p class="fine" data-i18n="memoryAccountingHint">RSS is the real process
|
||
footprint. HOT/transient cache values are RustProxy-accounted
|
||
allocations; transform memory is a conservative active reservation.
|
||
Non-cache RSS is RSS minus accounted cache and includes transforms,
|
||
connections, TLS, logging, Tokio/runtime state, allocator overhead and
|
||
other buffers, so these rows are not all additive.</p>
|
||
</div>
|
||
</details>
|
||
<span class="meta" id="system-sample-age">-</span>
|
||
</div>
|
||
</div>
|
||
<dl class="mini-metrics">
|
||
<div>
|
||
<dt data-i18n="platform">Platform</dt>
|
||
<dd id="process-platform">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="processCpu">CPU (100% = 1 core)</dt>
|
||
<dd id="process-cpu">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="fileDescriptors">File descriptors / limit</dt>
|
||
<dd id="process-fds">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="processThreads">OS threads</dt>
|
||
<dd id="process-threads">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="processVirtualMemory">Virtual memory / limit</dt>
|
||
<dd id="process-virtual-memory">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="processStartTime">Process start</dt>
|
||
<dd id="process-start-time">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="uptime">Uptime</dt>
|
||
<dd id="process-uptime">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="processRssTotal">RSS total</dt>
|
||
<dd id="process-rss-total">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="memoryCacheHot">HOT cache RAM / limit</dt>
|
||
<dd id="memory-cache-hot">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="memoryCacheTransient">Cache transient RAM / limit</dt>
|
||
<dd id="memory-cache-transient">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="memoryTransform">Transform reserved RAM / budget</dt>
|
||
<dd id="memory-transform">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="memoryTransformInflight">Active transforms / limit</dt>
|
||
<dd id="memory-transform-inflight">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="memoryNonCacheRss">Non-cache process RSS</dt>
|
||
<dd id="memory-non-cache-rss">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="processRssAnon">Anonymous RSS</dt>
|
||
<dd id="process-rss-anon">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="processRssFile">File-backed / shared RSS</dt>
|
||
<dd id="process-rss-file">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="processDataSwap">Data address space / swap</dt>
|
||
<dd id="process-data-swap">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="containerMemory">cgroup RAM / limit</dt>
|
||
<dd id="cgroup-memory">-</dd>
|
||
</div>
|
||
</dl>
|
||
<div class="mini-live-chart-stack">
|
||
<div class="mini-live-chart">
|
||
<div class="mini-live-chart-head"><span data-i18n="processCpuHistory">Process CPU
|
||
history</span><b id="process-cpu-chart-value">-</b></div>
|
||
<svg preserveAspectRatio="none" aria-label="Process CPU history"
|
||
data-i18n-aria="processCpuHistory" role="img" viewbox="0 0 300 60">
|
||
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||
<polyline class="diagnostic-line" id="process-cpu-sparkline"
|
||
points="0,55 300,55"></polyline>
|
||
</svg>
|
||
</div>
|
||
<div class="mini-live-chart">
|
||
<div class="mini-live-chart-head"><span data-i18n="processRssHistory">Process RSS
|
||
history</span><b id="process-rss-chart-value">-</b></div>
|
||
<svg preserveAspectRatio="none" aria-label="Process RSS history"
|
||
data-i18n-aria="processRssHistory" role="img" viewbox="0 0 300 60">
|
||
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||
<polyline class="diagnostic-line process-rss-line" id="process-rss-sparkline"
|
||
points="0,55 300,55"></polyline>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
<article class="card" data-dashboard-widget="network_diagnostics">
|
||
<div class="mini-heading">
|
||
<h3 data-i18n="networkDiagnostics">Network and responses</h3><span class="meta"
|
||
data-i18n="hostScope">OS scope</span>
|
||
</div>
|
||
<dl class="mini-metrics">
|
||
<div>
|
||
<dt data-i18n="networkRate">RX / TX per second</dt>
|
||
<dd id="system-network">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="meanLatency">Mean exchange duration</dt>
|
||
<dd id="mean-latency">-</dd>
|
||
</div>
|
||
<div>
|
||
<dt>gzip / br / zstd / deflate</dt>
|
||
<dd id="compression-counters">-</dd>
|
||
</div>
|
||
</dl>
|
||
<div class="mini-live-chart-stack">
|
||
<div class="mini-live-chart">
|
||
<div class="mini-live-chart-head"><span data-i18n="networkHistory">RX / TX
|
||
history</span><b id="network-chart-value">-</b></div>
|
||
<svg preserveAspectRatio="none" aria-label="Network throughput history"
|
||
data-i18n-aria="networkHistory" role="img" viewbox="0 0 300 60">
|
||
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||
<polyline class="diagnostic-line network-rx-line" id="network-rx-sparkline"
|
||
points="0,55 300,55"></polyline>
|
||
<polyline class="diagnostic-line network-tx-line" id="network-tx-sparkline"
|
||
points="0,55 300,55"></polyline>
|
||
</svg>
|
||
</div>
|
||
<div class="mini-live-chart">
|
||
<div class="mini-live-chart-head"><span data-i18n="networkInterfacesHistory">Traffic
|
||
by interface</span><b id="network-interface-chart-value">-</b></div>
|
||
<div class="network-interface-charts" id="network-interface-charts"></div>
|
||
</div>
|
||
<div class="mini-live-chart">
|
||
<div class="mini-live-chart-head"><span data-i18n="responseLatencyHistory">Response
|
||
time history</span><span class="mini-live-chart-actions">
|
||
<details class="metric-info">
|
||
<summary data-i18n-aria="responseLatencyInfo"
|
||
aria-label="Response time information">i</summary>
|
||
<div class="metric-info-popover">
|
||
<p class="fine" data-i18n="responseLatencyHint">The value is the
|
||
cumulative mean duration of completed HTTP exchanges since
|
||
process start. The chart shows successive samples of that mean;
|
||
it is not a percentile or the latency of one request.</p>
|
||
</div>
|
||
</details><b id="latency-chart-value">-</b>
|
||
</span></div>
|
||
<svg preserveAspectRatio="none" aria-label="Mean response latency history"
|
||
data-i18n-aria="responseLatencyHistory" role="img" viewbox="0 0 300 60">
|
||
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||
<polyline class="diagnostic-line" id="latency-sparkline" points="0,55 300,55">
|
||
</polyline>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
<article class="card pool-card" id="backend-diagnostics-card"
|
||
data-dashboard-widget="backend_table">
|
||
<div class="card-heading backend-card-heading">
|
||
<h2 data-i18n="vhHealthTitle">Backends</h2>
|
||
<div class="backend-table-tools">
|
||
<label class="sr-only" data-i18n="backendSearch" for="pool-search">Search
|
||
backends</label>
|
||
<input autocomplete="off" data-i18n-aria="backendSearch"
|
||
data-i18n-placeholder="backendSearchPlaceholder" id="pool-search"
|
||
type="search" />
|
||
<label class="backend-sort-control"><span data-i18n="backendSortBy">Sort
|
||
by</span><select data-i18n-aria="backendSortBy" id="pool-sort">
|
||
<option data-i18n="vhVhost" value="vhost">Vhost</option>
|
||
<option data-i18n="backend" value="backend">Backend</option>
|
||
<option data-i18n="status" value="status">Status</option>
|
||
<option data-i18n="vhActive" value="active">Active</option>
|
||
<option data-i18n="vhRequests" value="requests">Requests</option>
|
||
<option data-i18n="vhErrors" value="errors">Errors</option>
|
||
<option data-i18n="vhLatency" value="latency">Latency</option>
|
||
<option data-i18n="vhTraffic" value="traffic">Traffic/s</option>
|
||
<option data-i18n="vhClientGeneration" value="generation">Client gen.
|
||
</option>
|
||
<option data-i18n="vhGenerationRequests" value="generationRequests">Gen.
|
||
requests</option>
|
||
<option data-i18n="vhGenerationAge" value="generationAge">Gen. age</option>
|
||
<option data-i18n="vhClientRotations" value="rotations">Rotations</option>
|
||
<option data-i18n="vhLastFailure" value="lastFailure">Last failure</option>
|
||
<option data-i18n="vhLastRecovery" value="lastRecovery">Last recovery
|
||
</option>
|
||
</select></label>
|
||
<button class="quiet backend-sort-direction" data-i18n-aria="backendSortAscending"
|
||
id="pool-sort-direction" type="button">↑</button>
|
||
<details class="backend-column-picker">
|
||
<summary class="quiet" data-i18n="backendColumns">Columns</summary>
|
||
<div class="backend-column-options">
|
||
<label class="check"><input checked data-pool-column-toggle="vhost"
|
||
type="checkbox" /><span data-i18n="vhVhost">Vhost</span></label>
|
||
<label class="check"><input checked data-pool-column-toggle="backend"
|
||
type="checkbox" /><span data-i18n="backend">Backend</span></label>
|
||
<label class="check"><input checked data-pool-column-toggle="status"
|
||
type="checkbox" /><span data-i18n="status">Status</span></label>
|
||
<label class="check"><input checked data-pool-column-toggle="active"
|
||
type="checkbox" /><span data-i18n="vhActive">Active</span></label>
|
||
<label class="check"><input checked data-pool-column-toggle="requests"
|
||
type="checkbox" /><span
|
||
data-i18n="vhRequests">Requests</span></label>
|
||
<label class="check"><input checked data-pool-column-toggle="errors"
|
||
type="checkbox" /><span data-i18n="vhErrors">Errors</span></label>
|
||
<label class="check"><input checked data-pool-column-toggle="latency"
|
||
type="checkbox" /><span data-i18n="vhLatency">Latency</span></label>
|
||
<label class="check"><input checked data-pool-column-toggle="traffic"
|
||
type="checkbox" /><span
|
||
data-i18n="vhTraffic">Traffic/s</span></label>
|
||
<label class="check"><input checked data-pool-column-toggle="generation"
|
||
type="checkbox" /><span data-i18n="vhClientGeneration">Client
|
||
gen.</span></label>
|
||
<label class="check"><input checked
|
||
data-pool-column-toggle="generationRequests" type="checkbox" /><span
|
||
data-i18n="vhGenerationRequests">Gen.
|
||
requests</span></label>
|
||
<label class="check"><input checked data-pool-column-toggle="generationAge"
|
||
type="checkbox" /><span data-i18n="vhGenerationAge">Gen.
|
||
age</span></label>
|
||
<label class="check"><input checked data-pool-column-toggle="rotations"
|
||
type="checkbox" /><span
|
||
data-i18n="vhClientRotations">Rotations</span></label>
|
||
<label class="check"><input checked data-pool-column-toggle="lastFailure"
|
||
type="checkbox" /><span data-i18n="vhLastFailure">Last
|
||
failure</span></label>
|
||
<label class="check"><input checked data-pool-column-toggle="lastRecovery"
|
||
type="checkbox" /><span data-i18n="vhLastRecovery">Last
|
||
recovery</span></label>
|
||
</div>
|
||
</details>
|
||
<button class="quiet" data-i18n="vhManage" id="pool-open" type="button">Manage
|
||
vhosts</button>
|
||
</div>
|
||
</div>
|
||
<div class="table-scroll">
|
||
<table class="pool-table">
|
||
<thead>
|
||
<tr>
|
||
<th data-i18n="vhVhost" data-pool-column="vhost">Vhost</th>
|
||
<th data-i18n="backend" data-pool-column="backend">Backend</th>
|
||
<th data-i18n="status" data-pool-column="status">Status</th>
|
||
<th data-i18n="vhActive" data-pool-column="active">Active</th>
|
||
<th data-i18n="vhRequests" data-pool-column="requests">Requests</th>
|
||
<th data-i18n="vhErrors" data-pool-column="errors">Errors</th>
|
||
<th data-i18n="vhLatency" data-pool-column="latency">Latency</th>
|
||
<th data-i18n="vhTraffic" data-pool-column="traffic">Traffic/s</th>
|
||
<th data-i18n="vhClientGeneration" data-pool-column="generation">Client gen.
|
||
</th>
|
||
<th data-i18n="vhGenerationRequests" data-pool-column="generationRequests">
|
||
Gen.
|
||
requests</th>
|
||
<th data-i18n="vhGenerationAge" data-pool-column="generationAge">Gen. age
|
||
</th>
|
||
<th data-i18n="vhClientRotations" data-pool-column="rotations">Rotations
|
||
</th>
|
||
<th data-i18n="vhLastFailure" data-pool-column="lastFailure">Last failure
|
||
</th>
|
||
<th data-i18n="vhLastRecovery" data-pool-column="lastRecovery">Last recovery
|
||
</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="pool-rows"></tbody>
|
||
</table>
|
||
</div>
|
||
<p class="fine" data-i18n="vhHealthEmpty" id="pool-empty">No backend diagnostics yet.</p>
|
||
</article>
|
||
</div>
|
||
</section>
|
||
<section class="panel" hidden="" id="panel-vhosts">
|
||
<div class="vhost-layout">
|
||
<article class="card">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="vhList">Virtual hosts</h2>
|
||
<div class="vhost-list-tools">
|
||
<label class="sr-only" data-i18n="vhSearch" for="vhost-search">Search vhosts</label>
|
||
<input autocomplete="off" data-i18n-aria="vhSearch"
|
||
data-i18n-placeholder="vhSearchPlaceholder" id="vhost-search" type="search" />
|
||
<label class="backend-sort-control"><span data-i18n="backendSortBy">Sort
|
||
by</span><select data-i18n-aria="backendSortBy" id="vhost-sort">
|
||
<option data-i18n="vhVhost" value="id">Vhost</option>
|
||
<option data-i18n="vhHosts" value="hostname">Hosts</option>
|
||
<option data-i18n="status" value="status">Status</option>
|
||
<option data-i18n="vhListeners" value="listeners">Listeners</option>
|
||
<option data-i18n="vhCertificate" value="certificate">Certificate</option>
|
||
<option data-i18n="vhBackendPool" value="backends">Backends</option>
|
||
</select></label>
|
||
<button class="quiet backend-sort-direction" data-i18n-aria="backendSortAscending"
|
||
id="vhost-sort-direction" type="button">↑</button>
|
||
<button class="outline" data-i18n="vhNew" id="vhost-new" type="button">New
|
||
vhost</button>
|
||
</div>
|
||
</div>
|
||
<div class="entity-list" id="vhost-list"></div>
|
||
</article>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="panel" hidden="" id="panel-web-server">
|
||
<p class="notice fine" data-i18n="webServerIntro">Create reusable local Web Services here, then
|
||
attach them to a vhost or Custom location.</p>
|
||
<div class="vhost-layout">
|
||
<article class="card">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="webServiceList">Web Services</h2>
|
||
<div class="vhost-list-tools">
|
||
<label class="sr-only" data-i18n="webServiceSearch" for="web-service-search">Search
|
||
Web Services</label>
|
||
<input autocomplete="off" data-i18n-aria="webServiceSearch"
|
||
data-i18n-placeholder="webServiceSearchPlaceholder" id="web-service-search"
|
||
type="search" />
|
||
<button class="outline" data-i18n="phpProfileToolbar"
|
||
id="web-service-php-profiles-open" type="button">PHP-FPM profiles</button>
|
||
<button class="outline" data-i18n="webServiceNew" id="web-service-new"
|
||
type="button">New Web Service</button>
|
||
</div>
|
||
</div>
|
||
<div class="entity-list" id="web-service-list"></div>
|
||
</article>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="panel" hidden="" id="panel-docker-vhosts">
|
||
<p class="notice fine" data-i18n="dockerVhostsIntro">Published TCP ports discovered by configured
|
||
Docker agents. Map names and aliases, select the Docker host IP, and inspect/tune live backend
|
||
health for every published service.</p>
|
||
<article class="card docker-toolbar">
|
||
<div><span class="small-label" data-i18n="dockerDiscovery">DOCKER DISCOVERY</span>
|
||
<h2 data-i18n="dockerVhosts">Docker vhosts</h2>
|
||
</div>
|
||
<div class="toolbar-actions"><label class="sr-only" data-i18n="dockerSearch"
|
||
for="docker-search">Search Docker vhosts</label><input autocomplete="off"
|
||
data-i18n-aria="dockerSearch" data-i18n-placeholder="dockerSearchPlaceholder"
|
||
id="docker-search" type="search" /><label class="backend-sort-control"><span
|
||
data-i18n="backendSortBy">Sort by</span><select data-i18n-aria="backendSortBy"
|
||
id="docker-vhost-sort">
|
||
<option data-i18n="dockerServices" value="service">Service</option>
|
||
<option data-i18n="dockerHostname" value="hostname">Hostname</option>
|
||
<option data-i18n="status" value="status">Status</option>
|
||
<option data-i18n="dockerBackendIp" value="backend">Backend host IP</option>
|
||
<option data-i18n="dockerBackendScheme" value="scheme">Backend protocol</option>
|
||
<option data-i18n="dockerPublish" value="published">Published</option>
|
||
</select></label><button class="quiet backend-sort-direction"
|
||
data-i18n-aria="backendSortAscending" id="docker-vhost-sort-direction"
|
||
type="button">↑</button><label class="check compact-check docker-live-switch"
|
||
title="Refresh this view from RustProxy memory every 5 seconds"><input
|
||
id="docker-live-refresh" type="checkbox" /><span data-i18n="dockerLiveView">Live
|
||
view</span></label><button class="outline" data-i18n="dockerRefresh"
|
||
id="docker-refresh" type="button">Refresh agents</button><button class="quiet"
|
||
data-i18n="dockerAgentsSettings" id="docker-settings-open" type="button">Docker agents
|
||
settings</button><button class="quiet" data-i18n="dockerShowHidden"
|
||
id="docker-show-hidden" type="button">Show hidden</button></div>
|
||
</article>
|
||
<div class="docker-agent-observations" id="docker-discovery-list"></div>
|
||
</section>
|
||
|
||
<section class="panel" hidden="" id="panel-redirects">
|
||
<p class="notice fine" data-i18n="redirectsPageIntro">Redirect hosts answer locally without opening
|
||
an upstream connection. Rules can match scheme and path, then redirect to a URL, another host or
|
||
a path.</p>
|
||
<div class="vhost-layout redirect-layout">
|
||
<article class="card">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="redirectList">Redirect hosts</h2>
|
||
<div class="vhost-list-tools">
|
||
<label class="sr-only" data-i18n="redirectSearch" for="redirect-search">Search
|
||
redirects</label>
|
||
<input autocomplete="off" data-i18n-aria="redirectSearch"
|
||
data-i18n-placeholder="redirectSearchPlaceholder" id="redirect-search"
|
||
type="search" />
|
||
<label class="backend-sort-control"><span data-i18n="backendSortBy">Sort
|
||
by</span><select data-i18n-aria="backendSortBy" id="redirect-sort">
|
||
<option data-i18n="redirectId" value="id">Redirect ID</option>
|
||
<option data-i18n="redirectName" value="name">Display name</option>
|
||
<option data-i18n="redirectHosts" value="hostname">Source hostnames</option>
|
||
<option data-i18n="status" value="status">Status</option>
|
||
<option data-i18n="redirectTarget" value="target">Target</option>
|
||
<option data-i18n="redirectRules" value="rules">Rules</option>
|
||
<option data-i18n="redirectListeners" value="listeners">Listeners</option>
|
||
<option data-i18n="vhCertificate" value="certificate">Certificate</option>
|
||
</select></label>
|
||
<button class="quiet backend-sort-direction" data-i18n-aria="backendSortAscending"
|
||
id="redirect-sort-direction" type="button">↑</button>
|
||
<button class="outline" data-i18n="redirectNew" id="redirect-new" type="button">New
|
||
redirect</button>
|
||
</div>
|
||
</div>
|
||
<div class="entity-list" id="redirect-list"></div>
|
||
</article>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="panel" hidden="" id="panel-notfound">
|
||
<p class="notice fine" data-i18n="notfoundPageIntro">404 hosts reserve selected domains and answer
|
||
locally without contacting a backend.</p>
|
||
<div class="vhost-layout redirect-layout">
|
||
<article class="card">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="notfoundList">404 hosts</h2>
|
||
<div class="vhost-list-tools">
|
||
<label class="sr-only" data-i18n="notfoundSearch" for="notfound-search">Search 404
|
||
hosts</label>
|
||
<input autocomplete="off" data-i18n-aria="notfoundSearch"
|
||
data-i18n-placeholder="notfoundSearchPlaceholder" id="notfound-search"
|
||
type="search" />
|
||
<label class="backend-sort-control"><span data-i18n="backendSortBy">Sort
|
||
by</span><select data-i18n-aria="backendSortBy" id="notfound-sort">
|
||
<option data-i18n="notfoundId" value="id">Host ID</option>
|
||
<option data-i18n="notfoundName" value="name">Display name</option>
|
||
<option data-i18n="notfoundHosts" value="hostname">Hostnames</option>
|
||
<option data-i18n="notfoundStatus" value="status">HTTP status</option>
|
||
<option data-i18n="notfoundMode" value="mode">Response type</option>
|
||
<option data-i18n="redirectListeners" value="listeners">Listeners</option>
|
||
<option data-i18n="vhCertificate" value="certificate">Certificate</option>
|
||
</select></label>
|
||
<button class="quiet backend-sort-direction" data-i18n-aria="backendSortAscending"
|
||
id="notfound-sort-direction" type="button">↑</button>
|
||
<button class="outline" data-i18n="notfoundNew" id="notfound-new" type="button">New
|
||
404 host</button>
|
||
</div>
|
||
</div>
|
||
<div class="entity-list" id="notfound-list"></div>
|
||
</article>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="panel" hidden="" id="panel-streams">
|
||
<p class="notice fine" data-i18n="streamsPageIntro">TCP/UDP streams are independent Layer-4 proxies.
|
||
Each stream owns a dedicated listener and upstream and bypasses the HTTP vhost, ACL and cache
|
||
stack.</p>
|
||
<div class="vhost-layout stream-layout">
|
||
<article class="card">
|
||
<div class="card-heading">
|
||
<div><span class="small-label" data-i18n="streamLayer4">LAYER 4 / TCP + UDP</span>
|
||
<h2 data-i18n="streamList">Streams</h2>
|
||
</div>
|
||
<div class="vhost-list-tools">
|
||
<label class="sr-only" data-i18n="streamSearch" for="stream-search">Search
|
||
streams</label>
|
||
<input autocomplete="off" data-i18n-aria="streamSearch"
|
||
data-i18n-placeholder="streamSearchPlaceholder" id="stream-search"
|
||
type="search" />
|
||
<label class="backend-sort-control"><span data-i18n="backendSortBy">Sort
|
||
by</span><select data-i18n-aria="backendSortBy" id="stream-sort">
|
||
<option data-i18n="streamName" value="name">Display name</option>
|
||
<option data-i18n="streamId" value="id">Stream ID</option>
|
||
<option data-i18n="streamProtocol" value="protocol">Protocol</option>
|
||
<option data-i18n="streamListen" value="listen">Listen address</option>
|
||
<option data-i18n="streamUpstream" value="upstream">Upstream address
|
||
</option>
|
||
<option data-i18n="status" value="status">Status</option>
|
||
<option data-i18n="streamMaxConnections" value="maxConnections">Max
|
||
connections</option>
|
||
<option data-i18n="streamIdleShort" value="idleTimeout">Idle timeout
|
||
</option>
|
||
</select></label>
|
||
<button class="quiet backend-sort-direction" data-i18n-aria="backendSortAscending"
|
||
id="stream-sort-direction" type="button">↑</button>
|
||
<button class="outline" data-i18n="streamNew" id="stream-new" type="button">New
|
||
stream</button>
|
||
</div>
|
||
</div>
|
||
<div class="stream-page-actions"><button class="quiet" data-i18n="streamAddListener"
|
||
id="stream-listener-add" type="button">Add TCP/UDP listener</button><span
|
||
class="fine" data-i18n="streamDedicatedListenerHelp">A stream listener is created
|
||
together with the stream and is separate from HTTP/HTTPS listeners.</span></div>
|
||
<p class="pending-note" hidden="" id="stream-restart-banner"></p>
|
||
<p class="fine" id="stream-active-summary"></p>
|
||
<div class="entity-list" id="stream-list"></div>
|
||
</article>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="panel" hidden="" id="panel-acl-profiles">
|
||
<p class="notice fine" data-i18n="aclProfilesIntro">Reusable access profiles can be attached to
|
||
multiple vhosts. Per-vhost ACL and country rules remain active and are enforced together with
|
||
the profile.</p>
|
||
<div class="acl-profile-layout">
|
||
<article class="card">
|
||
<div class="card-heading">
|
||
<div><span class="small-label" data-i18n="aclProfilesReusable">REUSABLE POLICY</span>
|
||
<h2 data-i18n="aclProfiles">ACL profiles</h2>
|
||
</div><button class="outline" data-i18n="aclProfileNew" id="acl-profile-new"
|
||
type="button">New profile</button>
|
||
</div>
|
||
<div class="entity-list" id="acl-profile-list"></div>
|
||
</article>
|
||
<form class="card edit-form" id="acl-profile-form">
|
||
<div class="card-heading">
|
||
<div><span class="small-label" id="acl-profile-editing"></span>
|
||
<h2 data-i18n="aclProfileEditor">Profile configuration</h2>
|
||
</div>
|
||
</div>
|
||
<div class="field-grid">
|
||
<label><span data-i18n="aclProfileId">Profile ID</span><input id="acl-profile-id"
|
||
maxlength="64" pattern="[A-Za-z0-9_-]{1,64}" required /></label>
|
||
<label><span data-i18n="aclProfileName">Display name</span><input id="acl-profile-name"
|
||
maxlength="96" required /></label>
|
||
</div>
|
||
<section class="acl-policy-builder" aria-labelledby="acl-policy-mode-label">
|
||
<div class="acl-policy-step-head">
|
||
<div><span class="acl-policy-step">1</span>
|
||
<div><strong id="acl-policy-mode-label" data-i18n="aclProfilePolicyMode">Choose
|
||
the base policy</strong>
|
||
<p class="fine" data-i18n="aclProfilePolicyModeHelp">Start with a simple
|
||
allow-list or block-list. Use custom mode only when you need mixed
|
||
allow and deny rules.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="acl-policy-modes" id="acl-profile-mode-options">
|
||
<label class="acl-policy-mode-card"><input checked name="acl-profile-mode"
|
||
type="radio" value="allow_exceptions" /><span><b
|
||
data-i18n="aclProfileModeAllowExceptions">Block by default</b><small
|
||
data-i18n="aclProfileModeAllowExceptionsHelp">Allow selected countries
|
||
OR selected IP/CIDR ranges.</small></span></label>
|
||
<label class="acl-policy-mode-card"><input name="acl-profile-mode" type="radio"
|
||
value="block_exceptions" /><span><b
|
||
data-i18n="aclProfileModeBlockExceptions">Allow by default</b><small
|
||
data-i18n="aclProfileModeBlockExceptionsHelp">Block selected countries
|
||
OR selected IP/CIDR ranges.</small></span></label>
|
||
<label class="acl-policy-mode-card acl-policy-mode-custom"><input
|
||
name="acl-profile-mode" type="radio" value="custom" /><span><b
|
||
data-i18n="aclProfileModeCustom">Custom</b><small
|
||
data-i18n="aclProfileModeCustomHelp">Control default, country mode and
|
||
every IP action manually.</small></span></label>
|
||
</div>
|
||
<div class="acl-policy-custom-controls" id="acl-profile-custom-controls" hidden>
|
||
<label><span data-i18n="aclDefault">Default action</span><select
|
||
id="acl-profile-default">
|
||
<option data-i18n="deny" value="deny">Deny</option>
|
||
<option data-i18n="allow" value="allow">Allow</option>
|
||
</select></label>
|
||
<label><span data-i18n="vhCountryMode">Country access</span><select
|
||
id="acl-profile-country-mode">
|
||
<option data-i18n="vhOff" value="off">Off</option>
|
||
<option data-i18n="vhCountryBlock" value="block">Block listed countries
|
||
</option>
|
||
<option data-i18n="vhCountryAllow" value="allow">Allow only listed countries
|
||
</option>
|
||
</select></label>
|
||
</div>
|
||
</section>
|
||
<section class="acl-policy-builder">
|
||
<div class="acl-policy-step-head">
|
||
<div><span class="acl-policy-step">2</span>
|
||
<div><strong data-i18n="aclProfileCountryExceptions">Country exceptions</strong>
|
||
<p class="fine" id="acl-profile-country-help"></p>
|
||
</div>
|
||
</div><label class="acl-policy-switch"><input id="acl-profile-country-enabled"
|
||
type="checkbox" /><span data-i18n="aclProfileUseCountries">Use
|
||
countries</span></label>
|
||
</div>
|
||
<label class="acl-policy-country-list" id="acl-profile-country-field"><span
|
||
id="acl-profile-country-label"
|
||
data-i18n="vhCountryList">Countries</span><textarea id="acl-profile-countries"
|
||
rows="4" maxlength="1024" spellcheck="false"
|
||
placeholder="CN PL"></textarea></label>
|
||
</section>
|
||
<section class="acl-policy-builder">
|
||
<div class="acl-policy-step-head">
|
||
<div><span class="acl-policy-step">3</span>
|
||
<div><strong data-i18n="aclProfileIpExceptions">IP / CIDR exceptions</strong>
|
||
<p class="fine" id="acl-profile-ip-help"></p>
|
||
</div>
|
||
</div><button class="quiet" data-i18n="aclProfileAddIpException"
|
||
id="acl-profile-rule-add" type="button">Add
|
||
IP exception</button>
|
||
</div>
|
||
<div class="acl-profile-rule-rows" id="acl-profile-rule-rows"></div>
|
||
<details class="acl-profile-bulk" id="acl-profile-bulk">
|
||
<summary data-i18n="aclProfileBulkRules">Paste / import rules</summary>
|
||
<label><span data-i18n="aclProfileRules">IP rules — one “allow|deny CIDR” per
|
||
line</span><textarea id="acl-profile-rules" rows="6" spellcheck="false"
|
||
placeholder="allow 10.0.0.0/8 allow 203.0.113.5/32"></textarea></label>
|
||
<p class="fine" data-i18n="aclProfileBulkHelp">Whitespace and invisible Unicode
|
||
separators are normalized during import.</p>
|
||
<button class="quiet" data-i18n="aclProfileImportRules"
|
||
id="acl-profile-import-rules" type="button">Import rules</button>
|
||
</details>
|
||
</section>
|
||
<section class="acl-policy-preview" aria-live="polite">
|
||
<div class="acl-policy-preview-head"><span data-i18n="aclProfilePreview">Effective
|
||
policy</span><b id="acl-profile-preview-default"></b></div>
|
||
<div class="acl-policy-preview-flow" id="acl-profile-preview-flow"></div>
|
||
<p class="fine" id="acl-profile-preview-note"></p>
|
||
</section>
|
||
<p class="fine" data-i18n="aclProfileCombineHelp">Country and IP exceptions use OR inside
|
||
this profile. Explicit deny rules and blocked countries win; attached profile, vhost and
|
||
location policies must still all pass.</p>
|
||
<div class="form-actions"><button class="primary" data-i18n="apply" type="submit">Validate,
|
||
save and apply</button><button class="quiet" data-i18n="clear"
|
||
id="acl-profile-reset" type="button">Clear</button></div>
|
||
</form>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="panel" hidden="" id="panel-tls">
|
||
<div class="section-heading certificate-workspace-heading">
|
||
<div>
|
||
<h2 data-i18n="certificateLibrary"></h2>
|
||
<p class="fine" data-i18n="certificateWorkspaceHelp"></p>
|
||
</div>
|
||
<button class="primary" data-i18n="addCertificate" id="certificate-new" type="button"></button>
|
||
</div>
|
||
<nav aria-label="Certificates" class="settings-tabs certificate-tabs" id="certificate-tabs"
|
||
role="tablist">
|
||
<button aria-controls="certificate-page-installed" aria-selected="true"
|
||
data-certificate-tab="installed" data-i18n="certificateTabInstalled"
|
||
id="certificate-tab-installed" role="tab" tabindex="0" type="button"></button>
|
||
<button aria-controls="certificate-page-renewal" aria-selected="false"
|
||
data-certificate-tab="renewal" data-i18n="certificateTabRenewal"
|
||
id="certificate-tab-renewal" role="tab" tabindex="-1" type="button"></button>
|
||
<button aria-controls="certificate-page-generate" aria-selected="false"
|
||
data-certificate-tab="generate" data-i18n="certificateTabGenerate"
|
||
id="certificate-tab-generate" role="tab" tabindex="-1" type="button"></button>
|
||
</nav>
|
||
<article class="card certificate-operation-card certificate-runtime-card" id="acme-runtime-card">
|
||
<div class="certificate-operation-heading">
|
||
<div>
|
||
<span class="small-label" data-i18n="certificateRuntimeTitle">ACME runtime</span>
|
||
<strong data-i18n="acme.runtime_checking" id="acme-runtime-message"
|
||
role="status">Checking local Python environment…</strong>
|
||
</div>
|
||
<span class="pill" data-i18n="certificateRuntimePreparing"
|
||
id="acme-runtime-state">Preparing</span>
|
||
</div>
|
||
<p class="fine" hidden="" id="acme-runtime-path"></p>
|
||
<p class="fine" id="acme-runtime-components"></p>
|
||
</article>
|
||
<div class="certificate-pages">
|
||
<div aria-labelledby="certificate-tab-installed" class="certificate-page"
|
||
data-certificate-page="installed" id="certificate-page-installed" role="tabpanel">
|
||
<div class="section-heading certificate-subheading">
|
||
<div>
|
||
<h3 data-i18n="certificateInstalledTitle"></h3>
|
||
<p class="fine" data-i18n="certificateInstalledHelp"></p>
|
||
</div>
|
||
<button class="outline" data-i18n="reloadCertificates" id="reload-certificates"
|
||
type="button"></button>
|
||
</div>
|
||
<p class="fine certificate-safety" data-i18n="certificateSafety"></p>
|
||
<article class="card certificate-inventory-card">
|
||
<div id="certificate-list"></div>
|
||
<div class="certificate-library-footer">
|
||
<p class="fine" data-i18n="certificateHelp"></p>
|
||
<code class="command" data-i18n="certificateHint"></code>
|
||
</div>
|
||
</article>
|
||
</div>
|
||
<div aria-labelledby="certificate-tab-renewal" class="certificate-page"
|
||
data-certificate-page="renewal" id="certificate-page-renewal" role="tabpanel" hidden="">
|
||
<div class="certificate-section-intro">
|
||
<h3 data-i18n="renewalTitle"></h3>
|
||
<p class="fine" data-i18n="renewalHelp"></p>
|
||
</div>
|
||
<article class="card certificate-operation-card" id="acme-renewal-status-card">
|
||
<div class="certificate-operation-heading">
|
||
<div>
|
||
<span class="small-label" data-i18n="certificateOperationStatus"></span>
|
||
<strong id="acme-renewal-job" role="status"></strong>
|
||
</div>
|
||
<span class="pill" id="acme-renewal-state"></span>
|
||
</div>
|
||
<p class="certificate-log-path fine" hidden="" id="acme-renewal-log"></p>
|
||
</article>
|
||
<article class="card certificate-renewal-card">
|
||
<div id="renewal-list"></div>
|
||
</article>
|
||
</div>
|
||
<div aria-labelledby="certificate-tab-generate" class="certificate-page"
|
||
data-certificate-page="generate" id="certificate-page-generate" role="tabpanel" hidden="">
|
||
<div class="certificate-section-intro">
|
||
<h3 data-i18n="certificateGenerateTitle"></h3>
|
||
<p class="fine" data-i18n="certificateGenerateHelp"></p>
|
||
</div>
|
||
<article class="card certificate-operation-card" id="acme-issue-status-card">
|
||
<div class="certificate-operation-heading">
|
||
<div>
|
||
<span class="small-label" data-i18n="certificateOperationStatus"></span>
|
||
<strong data-i18n="acmeIdle" id="acme-job" role="status"></strong>
|
||
</div>
|
||
<span class="pill" id="acme-issue-state"></span>
|
||
</div>
|
||
<p class="certificate-log-path fine" hidden="" id="acme-log-path"></p>
|
||
<button class="outline" data-i18n="acmeImport" hidden="" id="acme-import"
|
||
type="button"></button>
|
||
</article>
|
||
<article class="card certificate-generator-card">
|
||
<label class="provider-picker"><span data-i18n="certificateProvider"></span><select
|
||
id="certificate-provider">
|
||
<option data-i18n="providerHttp01" value="http01"></option>
|
||
<option data-i18n="providerCloudflare" value="cloudflare"></option>
|
||
<option data-i18n="providerOvh" value="ovh"></option>
|
||
<option data-i18n="providerRfc2136" value="rfc2136"></option>
|
||
<option data-i18n="providerImport" value="import"></option>
|
||
<option data-i18n="providerExisting" value="existing"></option>
|
||
</select></label>
|
||
<p class="fine provider-hint" id="certificate-provider-hint"></p>
|
||
<div id="certificate-provider-body">
|
||
<article class="card acme-card certificate-editor-card"
|
||
id="certificate-acme-editor">
|
||
<form id="acme-form">
|
||
<div class="vh-grid">
|
||
<label><span data-i18n="certificateId">Certificate ID</span><input
|
||
autocomplete="off" id="acme-name" maxlength="64"
|
||
pattern="[A-Za-z0-9_-]{1,64}" required="" /></label>
|
||
<label><span data-i18n="acmeEmail">ACME contact email</span><input
|
||
autocomplete="off" id="acme-email" maxlength="254" required=""
|
||
type="email" /></label>
|
||
<label class="vh-wide"><span data-i18n="acmeDomains">DNS
|
||
names</span><input autocomplete="off" id="acme-domains"
|
||
placeholder="example.org, *.example.org" required="" /></label>
|
||
<label><span data-i18n="acmeEnvironment">CA environment</span><select
|
||
id="acme-environment">
|
||
<option data-i18n="acmeStaging" value="staging">Staging</option>
|
||
<option data-i18n="acmeProduction" selected value="production">
|
||
Production</option>
|
||
</select></label>
|
||
<label data-acme-provider="rfc2136"><span
|
||
data-i18n="acmeDnsServer"></span><input autocomplete="off"
|
||
id="acme-dns" maxlength="64" placeholder="10.0.0.53"
|
||
required="" /></label>
|
||
<label data-acme-provider="rfc2136"><span
|
||
data-i18n="acmeDnsPort"></span><input id="acme-port" max="65535"
|
||
min="1" required="" type="number" value="53" /></label>
|
||
<label data-acme-provider="rfc2136"><span
|
||
data-i18n="acmeKeyName"></span><input autocomplete="off"
|
||
id="acme-key-name" maxlength="253"
|
||
placeholder="rustproxy.example.org." required="" /></label>
|
||
<label data-acme-provider="rfc2136"><span
|
||
data-i18n="acmeSecret"></span><input autocomplete="new-password"
|
||
id="acme-secret" maxlength="1024" required="" spellcheck="false"
|
||
type="password" /></label>
|
||
<label data-acme-provider="rfc2136"><span
|
||
data-i18n="acmeAlgorithm"></span><select id="acme-algorithm">
|
||
<option>HMAC-SHA256</option>
|
||
<option>HMAC-SHA384</option>
|
||
<option>HMAC-SHA512</option>
|
||
</select></label>
|
||
<label data-acme-provider="dns"><span
|
||
data-i18n="acmePropagation"></span><input id="acme-propagation"
|
||
max="900" min="10" required="" type="number"
|
||
value="60" /></label>
|
||
<label class="check" data-acme-provider="rfc2136"><input
|
||
id="acme-sign-query" type="checkbox" /><span
|
||
data-i18n="acmeSignQuery"></span></label>
|
||
<label class="check vh-wide"><input id="acme-tos" required=""
|
||
type="checkbox" /><span data-i18n="acmeTos"></span></label>
|
||
<label class="check vh-wide"><input id="acme-confirm-production"
|
||
type="checkbox" /><span
|
||
data-i18n="acmeConfirmProduction"></span></label>
|
||
<label class="vh-wide" data-acme-provider="cloudflare"><span
|
||
data-i18n="acmeCfToken"></span><input
|
||
autocomplete="new-password" id="acme-cf-token" maxlength="4096"
|
||
required="" type="password" /></label>
|
||
<label data-acme-provider="ovh"><span
|
||
data-i18n="acmeOvhEndpoint"></span><select
|
||
id="acme-ovh-endpoint">
|
||
<option value="ovh-eu">ovh-eu</option>
|
||
<option value="ovh-ca">ovh-ca</option>
|
||
</select></label>
|
||
<label data-acme-provider="ovh"><span
|
||
data-i18n="acmeOvhApplicationKey"></span><input
|
||
autocomplete="new-password" id="acme-ovh-application-key"
|
||
maxlength="4096" required="" type="password" /></label>
|
||
<label data-acme-provider="ovh"><span
|
||
data-i18n="acmeOvhApplicationSecret"></span><input
|
||
autocomplete="new-password" id="acme-ovh-application-secret"
|
||
maxlength="4096" required="" type="password" /></label>
|
||
<label data-acme-provider="ovh"><span
|
||
data-i18n="acmeOvhConsumerKey"></span><input
|
||
autocomplete="new-password" id="acme-ovh-consumer-key"
|
||
maxlength="4096" required="" type="password" /></label>
|
||
</div>
|
||
<div class="form-actions"><button class="primary" data-i18n="acmeIssue"
|
||
id="acme-submit" type="submit"></button>
|
||
<button class="quiet" data-i18n="cacheRefresh" id="acme-refresh"
|
||
type="button"></button>
|
||
</div>
|
||
</form>
|
||
<p class="fine" data-i18n="acmeRenewalHint"></p>
|
||
</article>
|
||
<form class="card edit-form certificate-editor-card" id="certificate-upload-form">
|
||
<h3 data-i18n="certImport"></h3>
|
||
<p class="fine" data-i18n="certLibraryHint"></p>
|
||
<label data-i18n="certificateId" for="cert-import-name"></label><input
|
||
id="cert-import-name" maxlength="64" pattern="[A-Za-z0-9_-]{1,64}"
|
||
required="" />
|
||
<label data-i18n="certCheckDomains" for="cert-import-domains"></label><input
|
||
id="cert-import-domains" placeholder="app.example.com" required="" />
|
||
<label data-i18n="vhCertificateFile" for="cert-import-pem"></label><input
|
||
accept=".pem,.crt,.cer" id="cert-import-pem" required="" type="file" />
|
||
<label data-i18n="vhPrivateKeyFile" for="cert-import-key"></label><input
|
||
accept=".key,.pem" id="cert-import-key" required="" type="file" />
|
||
<label class="check"><input id="cert-import-default" type="checkbox" /><span
|
||
data-i18n="defaultCertificate"></span></label>
|
||
<button class="primary" data-i18n="certImportSave" type="submit"></button>
|
||
<p class="fine" data-i18n="certAcmeLater"></p>
|
||
</form>
|
||
<details class="card certificate-editor-card" id="certificate-advanced" open="">
|
||
<summary data-i18n="certAdvanced"></summary>
|
||
<form class="edit-form" id="certificate-form">
|
||
<h3 data-i18n="addCertificate"></h3>
|
||
<label data-i18n="certificateId" for="certificate-id"></label><input
|
||
id="certificate-id" maxlength="64" pattern="[A-Za-z0-9_.-]+"
|
||
placeholder="app" required="" />
|
||
<label data-i18n="certLegacyNames" for="certificate-hosts"></label><input
|
||
id="certificate-hosts" placeholder="app.localhost" />
|
||
<label data-i18n="certificateFileSource"
|
||
for="certificate-file-source"></label><select
|
||
id="certificate-file-source">
|
||
<option data-i18n="certificateSourceManaged" value="managed"></option>
|
||
<option data-i18n="certificateSourceExternal" value="external"></option>
|
||
</select>
|
||
<p class="fine" id="certificate-file-source-help"
|
||
data-i18n="certificateSourceManagedHelp"></p>
|
||
<pre class="metrics-example" data-i18n="certificateExternalAclExample"
|
||
hidden id="certificate-external-acl-example"></pre>
|
||
<label data-i18n="certificateFile" for="certificate-file"></label><input
|
||
id="certificate-file" maxlength="96" pattern="[A-Za-z0-9_.-]+"
|
||
placeholder="app.pem" required="" />
|
||
<label data-i18n="privateKeyFile" for="certificate-key"></label><input
|
||
id="certificate-key" maxlength="96" pattern="[A-Za-z0-9_.-]+"
|
||
placeholder="app.key" required="" />
|
||
<label class="check"><input id="certificate-default" type="checkbox" /><span
|
||
data-i18n="defaultCertificate"></span></label>
|
||
<div class="form-actions"><button class="primary"
|
||
data-i18n="saveCertificate" type="submit"></button>
|
||
<button class="quiet" data-i18n="clear" id="reset-certificate"
|
||
type="button"></button>
|
||
</div>
|
||
</form>
|
||
</details>
|
||
</div>
|
||
</article>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section class="panel" hidden="" id="panel-traffic">
|
||
<div class="cache-toolbar card"><label><span data-i18n="logVhost">Vhost</span><select
|
||
id="traffic-scope">
|
||
<option data-i18n="logAllVhosts" value="*">All vhosts</option>
|
||
</select></label><button class="quiet" data-i18n="cacheRefresh" id="traffic-refresh"
|
||
type="button">Refresh</button>
|
||
<button class="quiet" data-i18n="settings" id="traffic-settings" type="button">Settings</button>
|
||
<div class="cache-toolbar-status"><span class="fine" id="traffic-panel-state"
|
||
role="status"></span>
|
||
<label class="check cache-live-toggle"><input checked="" id="traffic-live"
|
||
type="checkbox" />
|
||
<span data-i18n="refreshViewLive">Auto in this view</span></label>
|
||
</div>
|
||
</div>
|
||
<nav aria-label="Traffic analytics" class="settings-tabs sub-tabs traffic-tabs" id="traffic-tabs"
|
||
role="tablist">
|
||
<button aria-selected="true" class="active" data-i18n="trafficTabOverview"
|
||
data-traffic-tab="overview" role="tab" type="button">Overview</button>
|
||
<button aria-selected="false" data-i18n="trafficTabClients" data-traffic-tab="clients"
|
||
role="tab" type="button">Clients</button>
|
||
<button aria-selected="false" data-i18n="trafficTabRequests" data-traffic-tab="requests"
|
||
role="tab" type="button">Requests</button>
|
||
<button aria-selected="false" data-i18n="trafficTabDelivery" data-traffic-tab="delivery"
|
||
role="tab" type="button">Delivery</button>
|
||
</nav>
|
||
<div data-traffic-page="overview">
|
||
<div class="stats-grid cache-stats">
|
||
<article class="stat"><span data-i18n="requests">Requests</span><strong
|
||
id="traffic-requests">-</strong><small data-i18n="trafficSinceStart">Since process
|
||
start</small></article>
|
||
<article class="stat"><span data-i18n="errors">Errors</span><strong
|
||
id="traffic-errors">-</strong><small id="traffic-error-ratio">-</small></article>
|
||
<article class="stat"><span data-i18n="trafficResponseBytes">Response bytes</span><strong
|
||
id="traffic-response-bytes">-</strong><small id="traffic-request-bytes">-</small>
|
||
</article>
|
||
<article class="stat"><span data-i18n="trafficMeanLatency">Mean latency</span><strong
|
||
id="traffic-latency">-</strong><small id="traffic-dropped">-</small></article>
|
||
</div>
|
||
<article class="card">
|
||
<div class="card-heading">
|
||
<div><span class="small-label" data-i18n="trafficBoundedLabel">BOUNDED / ASYNC</span>
|
||
<h2 data-i18n="trafficDimensionsTitle">Traffic dimensions</h2>
|
||
</div>
|
||
</div>
|
||
<p class="fine" data-i18n="trafficApproxHelp">Top lists are approximate and bounded so
|
||
high-cardinality traffic cannot grow proxy memory without limit.</p>
|
||
<p class="fine" id="traffic-capture-status"></p>
|
||
</article>
|
||
<div class="traffic-chart-grid traffic-overview-charts">
|
||
<article class="card traffic-chart-card">
|
||
<h3 class="subheading" data-i18n="trafficMethods">Methods</h3>
|
||
<div class="traffic-donut-layout">
|
||
<div class="traffic-donut" id="traffic-chart-methods" role="img"><span
|
||
id="traffic-chart-methods-total">-</span></div>
|
||
<ol class="traffic-chart-legend" id="traffic-chart-methods-legend"></ol>
|
||
</div>
|
||
</article>
|
||
<article class="card traffic-chart-card">
|
||
<h3 class="subheading" data-i18n="trafficStatusClasses">Status classes</h3>
|
||
<div class="traffic-donut-layout">
|
||
<div class="traffic-donut" id="traffic-chart-statuses" role="img"><span
|
||
id="traffic-chart-statuses-total">-</span></div>
|
||
<ol class="traffic-chart-legend" id="traffic-chart-statuses-legend"></ol>
|
||
</div>
|
||
</article>
|
||
<article class="card traffic-chart-card">
|
||
<h3 class="subheading" data-i18n="trafficCacheOutcomes">Cache outcomes</h3>
|
||
<div class="traffic-donut-layout">
|
||
<div class="traffic-donut" id="traffic-chart-cache" role="img"><span
|
||
id="traffic-chart-cache-total">-</span></div>
|
||
<ol class="traffic-chart-legend" id="traffic-chart-cache-legend"></ol>
|
||
</div>
|
||
</article>
|
||
</div>
|
||
</div>
|
||
<div data-traffic-page="clients" hidden>
|
||
<div class="traffic-chart-grid traffic-clients-grid">
|
||
<article class="card traffic-chart-card">
|
||
<h3 class="subheading" data-i18n="trafficTopCountries">Top countries</h3>
|
||
<div class="traffic-donut-layout">
|
||
<div class="traffic-donut" id="traffic-chart-countries" role="img"><span
|
||
id="traffic-chart-countries-total">-</span></div>
|
||
<ol class="traffic-chart-legend" id="traffic-chart-countries-legend"></ol>
|
||
</div>
|
||
<p class="empty" id="traffic-chart-countries-empty" data-i18n="trafficNoCountryData">
|
||
Country collection is disabled or no GeoIP data is available.</p>
|
||
</article>
|
||
<article class="card traffic-chart-card traffic-wide-chart">
|
||
<h3 class="subheading" data-i18n="trafficTopUserAgents">Top User-Agents</h3>
|
||
<div class="traffic-bar-chart" id="traffic-chart-user-agents" role="img"></div>
|
||
<p class="empty" id="traffic-chart-user-agents-empty"
|
||
data-i18n="trafficNoUserAgentData">No User-Agent data collected.</p>
|
||
</article>
|
||
<article class="card traffic-chart-card traffic-wide-chart">
|
||
<h3 class="subheading" data-i18n="trafficTopClientIps">Top client IPs</h3>
|
||
<div class="traffic-bar-chart" id="traffic-chart-client-ips" role="img"></div>
|
||
<p class="empty" id="traffic-chart-client-ips-empty" data-i18n="trafficNoData">No
|
||
traffic data yet.</p>
|
||
</article>
|
||
</div>
|
||
</div>
|
||
<div data-traffic-page="requests" hidden>
|
||
<div class="traffic-chart-grid traffic-request-grid">
|
||
<article class="card traffic-chart-card traffic-wide-chart">
|
||
<h3 class="subheading" data-i18n="trafficTopPaths">Top paths</h3>
|
||
<div class="traffic-bar-chart" id="traffic-chart-paths" role="img"></div>
|
||
<p class="empty" id="traffic-chart-paths-empty" data-i18n="trafficNoData">No traffic
|
||
data yet.</p>
|
||
</article>
|
||
<article class="card traffic-chart-card traffic-wide-chart">
|
||
<h3 class="subheading" data-i18n="trafficTopRoutes">Top routes</h3>
|
||
<div class="traffic-bar-chart" id="traffic-chart-routes" role="img"></div>
|
||
<p class="empty" id="traffic-chart-routes-empty" data-i18n="trafficNoData">No traffic
|
||
data yet.</p>
|
||
</article>
|
||
<article class="card traffic-chart-card">
|
||
<h3 class="subheading" data-i18n="trafficMethods">Methods</h3>
|
||
<div class="traffic-donut-layout">
|
||
<div class="traffic-donut traffic-donut-mirror" id="traffic-chart-methods-detail"
|
||
role="img"><span id="traffic-chart-methods-detail-total">-</span></div>
|
||
<ol class="traffic-chart-legend" id="traffic-chart-methods-detail-legend"></ol>
|
||
</div>
|
||
</article>
|
||
</div>
|
||
</div>
|
||
<div data-traffic-page="delivery" hidden>
|
||
<div class="traffic-chart-grid traffic-delivery-grid">
|
||
<article class="card traffic-chart-card">
|
||
<h3 class="subheading" data-i18n="trafficStatusClasses">Status classes</h3>
|
||
<div class="traffic-donut-layout">
|
||
<div class="traffic-donut traffic-donut-mirror" id="traffic-chart-statuses-detail"
|
||
role="img"><span id="traffic-chart-statuses-detail-total">-</span></div>
|
||
<ol class="traffic-chart-legend" id="traffic-chart-statuses-detail-legend"></ol>
|
||
</div>
|
||
</article>
|
||
<article class="card traffic-chart-card">
|
||
<h3 class="subheading" data-i18n="trafficCacheOutcomes">Cache outcomes</h3>
|
||
<div class="traffic-donut-layout">
|
||
<div class="traffic-donut traffic-donut-mirror" id="traffic-chart-cache-detail"
|
||
role="img"><span id="traffic-chart-cache-detail-total">-</span></div>
|
||
<ol class="traffic-chart-legend" id="traffic-chart-cache-detail-legend"></ol>
|
||
</div>
|
||
</article>
|
||
<article class="card traffic-chart-card traffic-wide-chart">
|
||
<h3 class="subheading" data-i18n="trafficVolume">Traffic volume</h3>
|
||
<div class="traffic-bar-chart" id="traffic-chart-volume" role="img"></div>
|
||
</article>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section class="panel" hidden="" id="panel-cache">
|
||
<div class="cache-toolbar card"><label><span data-i18n="logVhost">Vhost</span><select
|
||
id="cache-scope">
|
||
<option data-i18n="logAllVhosts" value="*">All vhosts</option>
|
||
</select></label><button class="quiet" data-i18n="cacheRefresh" id="cache-refresh"
|
||
type="button">Refresh</button><button class="quiet" data-i18n="settings"
|
||
id="cache-panel-settings" type="button">Settings</button><button class="quiet"
|
||
data-i18n="cacheColdCleanup" id="cache-panel-cleanup" type="button">Clean cold
|
||
cache</button><button class="outline" data-i18n="cachePurgeScope" id="cache-panel-purge"
|
||
type="button">Purge selected scope</button><button class="outline"
|
||
data-i18n="cacheInvalidationOpen" id="cache-invalidation-open" type="button">Purge /
|
||
Ban</button><button class="outline" data-i18n="cacheBanListOpen" id="cache-bans-open"
|
||
type="button">Active BANs</button>
|
||
<div class="cache-toolbar-status"><span class="fine" id="cache-panel-state"
|
||
role="status"></span><label class="check cache-live-toggle"><input checked=""
|
||
id="cache-live" type="checkbox" /><span data-i18n="refreshViewLive">Auto in this
|
||
view</span></label></div>
|
||
<p class="operation-message cache-toolbar-message" id="cache-toolbar-operation-status"
|
||
role="status" aria-live="polite"></p>
|
||
</div>
|
||
<details class="card function-live-chart" id="cache-live-chart">
|
||
<summary class="function-live-chart-summary">
|
||
<span class="function-live-chart-heading"><strong data-i18n="cacheLiveChartTitle">Live cache
|
||
activity</strong><small data-i18n="cacheLiveChartHelp">HIT / MISS / BYPASS per
|
||
second for the selected scope</small></span>
|
||
<b id="cache-live-chart-value">-</b>
|
||
</summary>
|
||
<div class="function-live-chart-body">
|
||
<svg aria-label="Live cache activity" data-i18n-aria="cacheLiveChartTitle"
|
||
id="cache-live-activity-chart" role="img" viewBox="0 0 900 180"
|
||
preserveAspectRatio="none">
|
||
<path class="chart-grid" d="M0 50H900 M0 90H900 M0 130H900"></path>
|
||
<polyline class="function-live-line cache-hit-line" id="cache-live-hit-line"
|
||
points="0,170 900,170"></polyline>
|
||
<polyline class="function-live-line cache-miss-line" id="cache-live-miss-line"
|
||
points="0,170 900,170"></polyline>
|
||
<polyline class="function-live-line cache-bypass-line" id="cache-live-bypass-line"
|
||
points="0,170 900,170"></polyline>
|
||
</svg>
|
||
<div class="function-live-chart-footer">
|
||
<label class="function-live-chart-samples"><span
|
||
data-i18n="liveChartSamples">Samples</span><select id="cache-live-samples">
|
||
<option value="60">60 · ~3 min</option>
|
||
<option value="120">120 · ~6 min</option>
|
||
<option value="300">300 · ~15 min</option>
|
||
<option value="600">600 · ~30 min</option>
|
||
</select></label>
|
||
<div class="function-live-chart-legend">
|
||
<span class="cache-hit-key"><i></i>HIT <b id="cache-live-hit-rate">0.0/s</b></span>
|
||
<span class="cache-miss-key"><i></i>MISS <b
|
||
id="cache-live-miss-rate">0.0/s</b></span>
|
||
<span class="cache-bypass-key"><i></i>BYPASS <b
|
||
id="cache-live-bypass-rate">0.0/s</b></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
<div class="stats-grid cache-stats">
|
||
<article class="stat"><span data-i18n="cacheHotObjects" id="cache-hot-objects-label">Hot
|
||
objects</span><strong id="cp-entries">-</strong><small
|
||
id="cp-capacity">-</small><progress aria-labelledby="cache-hot-objects-label"
|
||
id="cp-hot-entries-meter" max="100" value="0"></progress></article>
|
||
<article class="stat"><span data-i18n="cacheHotMemory" id="cache-memory-label">Hot
|
||
memory</span><strong id="cp-bytes">-</strong><small
|
||
id="cp-memory-detail">-</small><progress aria-labelledby="cache-memory-label"
|
||
id="cp-memory" max="100" value="0"></progress>
|
||
</article>
|
||
<article class="stat"><span data-i18n="cacheTransientMemory">Transient cache RAM</span><strong
|
||
id="cp-transient">-</strong><small id="cp-transient-detail">-</small></article>
|
||
<article class="stat"><span data-i18n="cacheColdObjects" id="cache-cold-objects-label">Cold
|
||
objects</span><strong id="cp-cold-entries">-</strong><small
|
||
id="cp-cold-capacity">-</small><progress aria-labelledby="cache-cold-objects-label"
|
||
id="cp-cold-entries-meter" max="100" value="0"></progress></article>
|
||
<article class="stat"><span data-i18n="cacheColdDisk" id="cache-cold-disk-label">Cold
|
||
disk</span><strong id="cp-cold-bytes">-</strong><small
|
||
id="cp-cold-disk-detail">-</small><progress aria-labelledby="cache-cold-disk-label"
|
||
id="cp-cold-disk" max="100" value="0"></progress>
|
||
</article>
|
||
<article class="stat"><span data-i18n="cacheHitRatio">Hit ratio</span><strong
|
||
id="cp-ratio">-</strong><small id="cp-requests">-</small></article>
|
||
<article class="stat"><span>BYPASS</span><strong id="cp-bypass">-</strong><small
|
||
id="cp-fills">-</small></article>
|
||
<article class="stat"><span data-i18n="cacheSaved">Body bytes served from RAM</span><strong
|
||
id="cp-saved">-</strong><small data-i18n="cacheCounterScope">Domain counters since
|
||
config revision</small></article>
|
||
<article class="stat"><span data-i18n="cacheTierMoves">Tier moves</span><strong
|
||
id="cp-tier-moves">-</strong><small id="cp-tier-errors">-</small></article>
|
||
<article class="stat"><span data-i18n="cacheEvictions">Evicted / expired</span><strong
|
||
id="cp-expired">-</strong><small id="cp-rejected">-</small></article>
|
||
</div>
|
||
<article class="card">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="cacheDomains">Domains and effectiveness</h2>
|
||
<label class="cache-domain-filter"><span data-i18n="cacheDomainFilter">Show</span><select
|
||
id="cache-domain-filter">
|
||
<option data-i18n="cacheDomainFilterAll" value="all">All domains</option>
|
||
<option data-i18n="cacheDomainFilterEnabled" value="enabled">Cache enabled</option>
|
||
<option data-i18n="cacheDomainFilterDisabled" value="disabled">Cache disabled
|
||
</option>
|
||
</select></label>
|
||
</div>
|
||
<p class="fine" data-i18n="cacheScopeHint">Limits, fills and evictions are shared. Domain
|
||
counters reset on configuration publication; purging objects does not reset counters.</p>
|
||
<div class="table-scroll" id="cache-domain-scroll">
|
||
<table class="cache-table">
|
||
<thead>
|
||
<tr>
|
||
<th data-i18n="vhVhost">Vhost</th>
|
||
<th data-i18n="cacheDomain">Domain</th>
|
||
<th data-i18n="cacheHotObjects">Hot</th>
|
||
<th data-i18n="cacheColdObjects">Cold</th>
|
||
<th data-i18n="cacheHotMemory">Hot memory</th>
|
||
<th data-i18n="cacheColdDisk">Cold disk</th>
|
||
<th>HIT / MISS / BYPASS</th>
|
||
<th data-i18n="cacheHitRatio">Hit ratio</th>
|
||
<th data-i18n="cacheReasons">Decision counters</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="cache-domain-rows"></tbody>
|
||
</table>
|
||
</div>
|
||
<p class="empty" data-i18n="cacheNoDomains" id="cache-domains-empty">No cache decisions yet.
|
||
Send traffic to a configured host.</p>
|
||
</article>
|
||
<article class="card">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="cacheObjectList">Stored objects</h2>
|
||
<div class="entity-actions"><button class="quiet" data-i18n="cachePrevious" id="cache-prev"
|
||
type="button">Previous</button><span class="fine" id="cache-page"></span><button
|
||
class="quiet" data-i18n="cacheNext" id="cache-next" type="button">Next</button>
|
||
</div>
|
||
</div>
|
||
<p class="fine" data-i18n="cacheObjectHint">Most recently used first. Targets, cache keys and
|
||
Cache-Tag values are visible to authenticated administrators; bodies and request headers are
|
||
not exposed.</p>
|
||
<p class="fine" id="cache-object-summary"></p>
|
||
<div class="table-scroll" id="cache-object-scroll">
|
||
<table class="cache-table">
|
||
<thead>
|
||
<tr>
|
||
<th data-i18n="vhVhost">Vhost</th>
|
||
<th data-i18n="cacheDomain">Domain</th>
|
||
<th data-i18n="cacheTarget">Target</th>
|
||
<th data-i18n="cacheKeyTags">Cache key / tags</th>
|
||
<th data-i18n="cacheTier">Tier</th>
|
||
<th data-i18n="cacheBodySize">Body</th>
|
||
<th data-i18n="cacheStorage">Memory / disk</th>
|
||
<th data-i18n="cacheOverhead">Overhead</th>
|
||
<th>HIT</th>
|
||
<th data-i18n="cacheAge">Age</th>
|
||
<th data-i18n="cacheResidentAgeValue">Time in RustProxy</th>
|
||
<th data-i18n="cacheTtlRemaining">Remaining TTL</th>
|
||
<th data-i18n="actions">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="cache-object-rows"></tbody>
|
||
</table>
|
||
</div>
|
||
<p class="empty" data-i18n="cacheNoObjects" id="cache-objects-empty">No fresh objects in this
|
||
scope. Inspect the decision counters above.</p>
|
||
</article>
|
||
</section>
|
||
<section class="panel" hidden="" id="panel-transform">
|
||
<div class="cache-toolbar card"><label><span data-i18n="logVhost">Vhost</span><select
|
||
id="transform-scope">
|
||
<option data-i18n="logAllVhosts" value="*">All vhosts</option>
|
||
</select></label><button class="quiet" data-i18n="cacheRefresh" id="transform-refresh"
|
||
type="button">Refresh</button><button class="quiet" data-i18n="settings"
|
||
id="transform-panel-settings" type="button">Settings</button>
|
||
<div class="cache-toolbar-status"><span class="fine" id="transform-panel-state"
|
||
role="status"></span><label class="check cache-live-toggle"><input checked=""
|
||
id="transform-live" type="checkbox" /><span data-i18n="refreshViewLive">Auto in this
|
||
view</span></label></div>
|
||
</div>
|
||
<details class="card function-live-chart" id="transform-live-chart">
|
||
<summary class="function-live-chart-summary">
|
||
<span class="function-live-chart-heading"><strong data-i18n="transformLiveChartTitle">Live
|
||
transformation activity</strong><small data-i18n="transformLiveChartHelp">Applied /
|
||
cache HIT / skipped / errors per second for the selected scope</small></span>
|
||
<b id="transform-live-chart-value">-</b>
|
||
</summary>
|
||
<div class="function-live-chart-body">
|
||
<svg aria-label="Live transformation activity" data-i18n-aria="transformLiveChartTitle"
|
||
id="transform-live-activity-chart" role="img" viewBox="0 0 900 180"
|
||
preserveAspectRatio="none">
|
||
<path class="chart-grid" d="M0 50H900 M0 90H900 M0 130H900"></path>
|
||
<polyline class="function-live-line transform-applied-line"
|
||
id="transform-live-applied-line" points="0,170 900,170"></polyline>
|
||
<polyline class="function-live-line transform-cache-line" id="transform-live-cache-line"
|
||
points="0,170 900,170"></polyline>
|
||
<polyline class="function-live-line transform-skipped-line"
|
||
id="transform-live-skipped-line" points="0,170 900,170"></polyline>
|
||
<polyline class="function-live-line transform-error-line" id="transform-live-error-line"
|
||
points="0,170 900,170"></polyline>
|
||
</svg>
|
||
<div class="function-live-chart-footer">
|
||
<label class="function-live-chart-samples"><span
|
||
data-i18n="liveChartSamples">Samples</span><select id="transform-live-samples">
|
||
<option value="60">60 · ~3 min</option>
|
||
<option value="120">120 · ~6 min</option>
|
||
<option value="300">300 · ~15 min</option>
|
||
<option value="600">600 · ~30 min</option>
|
||
</select></label>
|
||
<div class="function-live-chart-legend">
|
||
<span class="transform-applied-key"><i></i><span
|
||
data-i18n="transformChartApplied">Applied</span> <b
|
||
id="transform-live-applied-rate">0.0/s</b></span>
|
||
<span class="transform-cache-key"><i></i><span
|
||
data-i18n="transformChartCacheHits">Cache HIT</span> <b
|
||
id="transform-live-cache-rate">0.0/s</b></span>
|
||
<span class="transform-skipped-key"><i></i><span
|
||
data-i18n="transformChartSkipped">Skipped</span> <b
|
||
id="transform-live-skipped-rate">0.0/s</b></span>
|
||
<span class="transform-error-key"><i></i><span data-i18n="errors">Errors</span> <b
|
||
id="transform-live-error-rate">0.0/s</b></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
<div class="stats-grid cache-stats transform-stats">
|
||
<article class="stat"><span data-i18n="transformApplied">Transformed responses</span><strong
|
||
id="tp-applied">-</strong><small id="tp-examined">-</small></article>
|
||
<article class="stat"><span data-i18n="transformCacheHits">Transformed cache HITs</span><strong
|
||
id="tp-cache-hits">-</strong><small data-i18n="transformCacheHitsHelp">Served without
|
||
running the transformer again</small></article>
|
||
<article class="stat"><span data-i18n="transformSaved">Bytes saved before
|
||
compression</span><strong id="tp-saved">-</strong><small id="tp-byte-flow">-</small>
|
||
</article>
|
||
<article class="stat"><span data-i18n="transformReduction">Average reduction</span><strong
|
||
id="tp-reduction">-</strong><small data-i18n="transformReductionHelp">For responses that
|
||
changed representation</small></article>
|
||
<article class="stat"><span data-i18n="transformSkipped">Skipped / fail-open</span><strong
|
||
id="tp-skipped">-</strong><small id="tp-skip-top">-</small></article>
|
||
<article class="stat"><span data-i18n="transformErrors">Body/transport errors</span><strong
|
||
id="tp-errors">-</strong><small id="tp-duration">-</small></article>
|
||
<article class="stat"><span data-i18n="transformMemoryUsage">Transform RAM
|
||
reservation</span><strong id="tp-memory">-</strong><small
|
||
id="tp-memory-detail">-</small></article>
|
||
<article class="stat"><span data-i18n="transformConcurrencyUsage">Active transform
|
||
slots</span><strong id="tp-inflight">-</strong><small id="tp-inflight-detail">-</small>
|
||
</article>
|
||
</div>
|
||
<div class="diagnostics-grid transform-diagnostics">
|
||
<article class="card">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="transformByType">Transformations by type</h2>
|
||
</div>
|
||
<div class="table-scroll">
|
||
<table class="cache-table">
|
||
<thead>
|
||
<tr>
|
||
<th data-i18n="transformType">Type</th>
|
||
<th data-i18n="transformApplied">Applied</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="transform-type-rows"></tbody>
|
||
</table>
|
||
</div>
|
||
</article>
|
||
<article class="card">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="transformSkipReasons">Skip / fail-open reasons</h2>
|
||
</div>
|
||
<div class="cache-reasons transform-reason-list" id="transform-reason-list"></div>
|
||
<p class="empty" data-i18n="transformNoSkips" id="transform-reasons-empty">No skipped
|
||
transformations in this scope.</p>
|
||
</article>
|
||
</div>
|
||
<article class="card">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="transformVhosts">Vhost effectiveness</h2>
|
||
</div>
|
||
<p class="fine" data-i18n="transformScopeHint">Counters are in-memory runtime diagnostics. A
|
||
transformed cache HIT does not execute the transformer again.</p>
|
||
<div class="table-scroll">
|
||
<table class="cache-table">
|
||
<thead>
|
||
<tr>
|
||
<th data-i18n="vhVhost">Vhost</th>
|
||
<th data-i18n="transformApplied">Applied</th>
|
||
<th data-i18n="transformCacheHits">Cache HITs</th>
|
||
<th data-i18n="transformSkipped">Skipped</th>
|
||
<th data-i18n="errors">Errors</th>
|
||
<th data-i18n="transformSaved">Saved</th>
|
||
<th data-i18n="transformReduction">Reduction</th>
|
||
<th data-i18n="transformAvgTime">Avg time</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="transform-vhost-rows"></tbody>
|
||
</table>
|
||
</div>
|
||
<p class="empty" data-i18n="transformNoData" id="transform-vhosts-empty">No transform activity
|
||
yet.</p>
|
||
</article>
|
||
</section>
|
||
<section class="panel" hidden="" id="panel-filelogs">
|
||
<article class="card file-logs-workspace">
|
||
<div class="card-heading">
|
||
<div><span class="small-label" data-i18n="fileLogsMode">LOCAL / READ-ONLY / BOUNDED</span>
|
||
<h2 data-i18n="fileLogsTitle">Stored log explorer</h2>
|
||
</div>
|
||
<div class="file-logs-heading-actions">
|
||
<details class="file-log-search-help">
|
||
<summary class="quiet" data-i18n="fileLogsSearchHelp">Search syntax</summary>
|
||
<div class="file-log-search-help-popover">
|
||
<strong data-i18n="fileLogsSearchHelpTitle">Kibana-style field search</strong>
|
||
<p class="fine" data-i18n="fileLogsSearchHelpText">Use field:value clauses.
|
||
Clauses are joined with AND; prefix a clause with ! to exclude it. Quotes
|
||
keep spaces together.</p>
|
||
<code>req: GET AND !country: PL</code>
|
||
<code>status:>=500 AND path:/api/*</code>
|
||
<code>error:"connection reset"</code>
|
||
</div>
|
||
</details>
|
||
<label class="file-log-consolidate" title="Consolidate vhost log rotations"><input
|
||
checked id="file-log-consolidate-vhosts" type="checkbox" /><span
|
||
data-i18n="fileLogsConsolidateShort">Merge rotations</span></label>
|
||
<button class="outline" data-i18n="refresh" id="file-logs-refresh"
|
||
type="button">Refresh</button>
|
||
</div>
|
||
</div>
|
||
<p class="fine" data-i18n="fileLogsHelp">Searches bounded RustProxy access/error logs and parses
|
||
structured fields without exposing filesystem paths.</p>
|
||
<form class="file-logs-filter" id="file-logs-filter">
|
||
<label class="file-log-source"><span data-i18n="fileLogsSource">Source</span><select
|
||
id="file-log-source">
|
||
<option data-i18n="fileLogsAllSources" value="all">All configured files</option>
|
||
</select></label>
|
||
<label class="file-log-search"><span data-i18n="search">Search</span><input id="file-log-q"
|
||
maxlength="512" data-i18n-placeholder="fileLogsSearchPlaceholder"
|
||
placeholder="req: GET AND !country: Poland" type="search" /></label>
|
||
<label class="file-log-limit"><span data-i18n="fileLogsPageSize">Page size</span><select
|
||
id="file-log-limit">
|
||
<option>50</option>
|
||
<option selected>100</option>
|
||
<option>250</option>
|
||
<option>500</option>
|
||
</select></label>
|
||
</form>
|
||
<div class="file-log-tabs" role="tablist" aria-label="Stored log views">
|
||
<button class="file-log-tab active" id="file-log-tab-entries" type="button" role="tab"
|
||
aria-controls="file-log-view-entries" aria-selected="true"
|
||
data-i18n="fileLogsEntriesTab">Log entries</button>
|
||
<button class="file-log-tab" id="file-log-tab-counts" type="button" role="tab"
|
||
aria-controls="file-log-view-counts" aria-selected="false"
|
||
data-i18n="fileLogsCountsTab">Repeated values</button>
|
||
</div>
|
||
<section class="file-log-advanced-parser" id="file-log-view-counts" role="tabpanel"
|
||
aria-labelledby="file-log-tab-counts" hidden>
|
||
<h3 data-i18n="fileLogsAdvancedParser">Advanced parser · repeated values</h3>
|
||
<form class="file-log-aggregate-form" id="file-log-aggregate-form">
|
||
<label><span data-i18n="fileLogsAggregateField">Field to count</span><input
|
||
id="file-log-aggregate-field" list="file-log-field-suggestions" maxlength="128"
|
||
placeholder="user_agent" /></label>
|
||
<datalist id="file-log-field-suggestions"></datalist>
|
||
<label><span data-i18n="fileLogsAggregateMin">Minimum repetitions</span><input
|
||
id="file-log-aggregate-min" type="number" min="2" max="20000"
|
||
value="2" /></label>
|
||
<label><span data-i18n="fileLogsAggregateLimit">Top values</span><select
|
||
id="file-log-aggregate-limit">
|
||
<option>20</option>
|
||
<option selected>50</option>
|
||
<option>100</option>
|
||
<option>200</option>
|
||
</select></label>
|
||
<button class="outline" data-i18n="fileLogsCountRepeats" type="submit">Count
|
||
repeats</button>
|
||
</form>
|
||
<p class="fine" data-i18n="fileLogsAggregateHelp">Counts matching values across the bounded
|
||
scanned log set, not only the current page. Use fields such as user_agent, client_ip,
|
||
host, path or any parsed nested field.</p>
|
||
<div class="table-scroll file-log-aggregate-table">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th data-i18n="fileLogsAggregateValue">Value</th>
|
||
<th data-i18n="fileLogsAggregateCount">Count</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="file-log-aggregate-rows"></tbody>
|
||
</table>
|
||
</div>
|
||
<p class="empty" data-i18n="fileLogsAggregateEmpty" id="file-log-aggregate-empty">No
|
||
repeated values for the selected field.</p>
|
||
<p class="fine" id="file-log-aggregate-state" role="status"></p>
|
||
</section>
|
||
<div id="file-log-view-entries" role="tabpanel" aria-labelledby="file-log-tab-entries">
|
||
<div class="file-logs-table-toolbar">
|
||
<p class="fine" id="file-log-summary" role="status"></p>
|
||
<details class="log-column-picker file-log-column-picker">
|
||
<summary data-i18n="logColumns">Columns</summary>
|
||
<div class="log-column-options" id="file-log-column-options"></div>
|
||
</details>
|
||
</div>
|
||
<div class="table-scroll file-logs-table">
|
||
<table>
|
||
<thead id="file-logs-table-head">
|
||
<tr></tr>
|
||
</thead>
|
||
<tbody id="file-log-rows"></tbody>
|
||
</table>
|
||
</div>
|
||
<p class="empty" data-i18n="fileLogsEmpty" id="file-logs-empty">No records match the current
|
||
search.</p>
|
||
<div class="file-logs-pager"><button class="quiet" data-i18n="previous" disabled
|
||
id="file-logs-prev" type="button">Previous</button><button class="quiet"
|
||
data-i18n="next" disabled id="file-logs-next" type="button">Next</button></div>
|
||
</div>
|
||
</article>
|
||
</section>
|
||
<section class="panel" hidden="" id="panel-settings">
|
||
<div class="settings-shortcuts">
|
||
<div><b data-i18n="security">Security</b>
|
||
<p class="fine" data-i18n="settingsSecurityHint">Accounts and ACLs are global; SSL and
|
||
response policies also have per-vhost controls.</p>
|
||
</div><button class="outline" data-i18n="security" id="settings-security-open"
|
||
type="button">Security</button><button class="quiet" data-i18n="vhosts"
|
||
id="settings-vhost-open" type="button">Vhosts</button>
|
||
</div>
|
||
<div class="settings-filter-row">
|
||
<label class="settings-filter"><span class="small-label" data-i18n="settingsSearch">Find
|
||
settings</span><input autocomplete="off"
|
||
data-i18n-placeholder="settingsSearchPlaceholder" id="settings-search"
|
||
placeholder="Search settings..." type="search" /></label>
|
||
<span class="fine" id="settings-search-status" role="status"></span>
|
||
</div>
|
||
<nav aria-label="Settings" class="settings-tabs" id="settings-tabs" role="tablist"><button
|
||
aria-controls="settings-page-appearance" aria-selected="false" data-i18n="appearance"
|
||
data-settings-tab="appearance" id="settings-tab-appearance" role="tab" tabindex="-1"
|
||
type="button"></button><button aria-controls="settings-page-listeners" aria-selected="false"
|
||
data-i18n="listeners" data-settings-tab="listeners" id="settings-tab-listeners" role="tab"
|
||
tabindex="-1" type="button"></button><button aria-controls="settings-page-connections"
|
||
aria-selected="false" data-i18n="httpConnections" data-settings-tab="connections"
|
||
id="settings-tab-connections" role="tab" tabindex="-1" type="button"></button><button
|
||
aria-controls="settings-page-docker-agents" aria-selected="false" data-i18n="dockerAgents"
|
||
data-settings-tab="docker-agents" id="settings-tab-docker-agents" role="tab" tabindex="-1"
|
||
type="button"></button><button aria-controls="settings-page-policies" aria-selected="false"
|
||
data-i18n="policyRulesTitle" data-settings-tab="policies" id="settings-tab-policies"
|
||
role="tab" tabindex="-1" type="button">Rules & headers</button><button
|
||
aria-controls="settings-page-cache" aria-selected="false" data-i18n="cacheSettings"
|
||
data-settings-tab="cache" id="settings-tab-cache" role="tab" tabindex="-1"
|
||
type="button"></button><button aria-controls="settings-page-transform" aria-selected="false"
|
||
data-i18n="transformSettings" data-settings-tab="transform" id="settings-tab-transform"
|
||
role="tab" tabindex="-1" type="button"></button><button
|
||
aria-controls="settings-page-forwarding" aria-selected="false"
|
||
data-i18n="forwardingSettings" data-settings-tab="forwarding" id="settings-tab-forwarding"
|
||
role="tab" tabindex="-1" type="button"></button><button aria-controls="settings-page-pages"
|
||
aria-selected="false" data-i18n="globalPages" data-settings-tab="pages"
|
||
id="settings-tab-pages" role="tab" tabindex="-1" type="button"></button><button
|
||
aria-controls="settings-page-logging" aria-selected="false" data-i18n="logging"
|
||
data-settings-tab="logging" id="settings-tab-logging" role="tab" tabindex="-1"
|
||
type="button"></button><button aria-controls="settings-page-geoip" aria-selected="false"
|
||
data-i18n="geoipUpdatesTitle" data-settings-tab="geoip" id="settings-tab-geoip" role="tab"
|
||
tabindex="-1" type="button"></button><button aria-controls="settings-page-prometheus"
|
||
aria-selected="false" data-i18n="prometheusTitle" data-settings-tab="prometheus"
|
||
id="settings-tab-prometheus" role="tab" tabindex="-1" type="button"></button><button
|
||
aria-controls="settings-page-administration" aria-selected="false"
|
||
data-i18n="administration" data-settings-tab="administration"
|
||
id="settings-tab-administration" role="tab" tabindex="-1" type="button"></button><button
|
||
aria-controls="settings-page-system-info" aria-selected="false"
|
||
data-i18n="systemInformation" data-settings-tab="system-info" id="settings-tab-system-info"
|
||
role="tab" tabindex="-1" type="button"></button><button
|
||
aria-controls="settings-page-certificate-api" aria-selected="false"
|
||
data-i18n="certificateApiTitle" data-settings-tab="certificate-api"
|
||
id="settings-tab-certificate-api" role="tab" tabindex="-1" type="button"></button><button
|
||
aria-controls="settings-page-tls-policy" aria-selected="false" data-i18n="tlsPolicy"
|
||
data-settings-tab="tls-policy" id="settings-tab-tls-policy" role="tab" tabindex="-1"
|
||
type="button"></button><button aria-controls="settings-page-manual-config"
|
||
aria-selected="false" data-i18n="manualConfig" data-settings-tab="manual-config"
|
||
id="settings-tab-manual-config" role="tab" tabindex="-1" type="button"></button><button
|
||
aria-controls="settings-page-security" aria-selected="false" data-i18n="security"
|
||
data-settings-tab="security" id="settings-tab-security" role="tab" tabindex="-1"
|
||
type="button"></button></nav>
|
||
<div class="settings-pages" id="settings-pages">
|
||
<div aria-labelledby="settings-tab-appearance" class="settings-page"
|
||
data-settings-page="appearance" hidden="" id="settings-page-appearance" role="tabpanel">
|
||
<div class="card edit-form appearance-settings-card">
|
||
<div class="card-heading">
|
||
<div><span class="small-label" data-i18n="appearanceEyebrow">INTERFACE</span>
|
||
<h2 data-i18n="appearance">Appearance</h2>
|
||
</div>
|
||
</div>
|
||
<p class="fine" data-i18n="appearanceHelp">These preferences are saved for your
|
||
administrator account and follow you between sessions.</p>
|
||
<section class="appearance-section">
|
||
<div class="appearance-section-heading">
|
||
<div><span class="small-label"
|
||
data-i18n="appearanceInterfaceSection">INTERFACE</span>
|
||
<h3 data-i18n="appearanceInterfaceTitle">Interface</h3>
|
||
</div><button class="quiet appearance-section-reset"
|
||
data-appearance-reset="interface" data-i18n="resetSection"
|
||
type="button">Reset section</button>
|
||
</div>
|
||
<div class="appearance-settings-grid">
|
||
<label><span data-i18n="theme">Theme</span><select id="ui-theme">
|
||
<option data-i18n="themeDark" value="dark">Dark</option>
|
||
<option data-i18n="themeUltraDark" value="ultra_dark">Ultra dark ·
|
||
near-black</option>
|
||
<option data-i18n="themeLight" value="light">Light</option>
|
||
<option data-i18n="themeCream" value="cream">Warm cream</option>
|
||
</select></label>
|
||
<label><span data-i18n="language">Language</span><select aria-label="Language"
|
||
data-i18n-aria="language" id="language"></select></label>
|
||
<label class="appearance-scale-control"><span><span
|
||
data-i18n="interfaceScale">Interface scale</span>
|
||
<output id="ui-scale-value" for="ui-scale">100%</output></span>
|
||
<input id="ui-scale" max="125" min="70" step="5" type="range" value="100" />
|
||
<small class="fine" data-i18n="interfaceScaleHelp">Scale the entire
|
||
administration interface, including dialogs and overlays, without
|
||
changing browser zoom.</small></label>
|
||
<label><span data-i18n="timeFormat">Time format</span><select
|
||
id="ui-time-format">
|
||
<option data-i18n="timeFormat24" value="24h">24-hour</option>
|
||
<option data-i18n="timeFormat12" value="12h">12-hour</option>
|
||
</select></label>
|
||
<label><span data-i18n="buttonStyle">Button style</span><select
|
||
id="ui-button-style">
|
||
<option data-i18n="buttonStyleNormal" value="normal">Normal</option>
|
||
<option data-i18n="buttonStyleCompact" value="compact">Compact</option>
|
||
</select><small class="fine" data-i18n="buttonStyleHelp">Normal uses the
|
||
refreshed controls; Compact restores the smaller source-style buttons
|
||
across all actions and dialog tabs.</small></label>
|
||
<label><span data-i18n="cornerStyle">Corner rounding</span><select
|
||
id="ui-corner-style">
|
||
<option data-i18n="cornerStyleDefault" value="default">Original
|
||
(theme-independent)</option>
|
||
<option data-i18n="cornerStyleSquare" value="square">Square</option>
|
||
<option data-i18n="cornerStyleSubtle" value="subtle">Subtle</option>
|
||
<option data-i18n="cornerStyleRounded" value="rounded">Rounded</option>
|
||
<option data-i18n="cornerStyleExtra" value="extra">Extra rounded
|
||
</option>
|
||
</select><small class="fine" data-i18n="cornerStyleHelp">Controls corners
|
||
throughout the interface independently of the theme. Original keeps the
|
||
previous geometry.</small></label>
|
||
</div>
|
||
</section>
|
||
<section class="appearance-section">
|
||
<div class="appearance-section-heading">
|
||
<div><span class="small-label" data-i18n="appearanceChartsSection">CHARTS</span>
|
||
<h3 data-i18n="chartStyle">Chart style</h3>
|
||
</div><button class="quiet appearance-section-reset"
|
||
data-appearance-reset="charts" data-i18n="resetSection" type="button">Reset
|
||
section</button>
|
||
</div>
|
||
<div class="appearance-settings-grid appearance-chart-settings">
|
||
<label><span data-i18n="chartStyle">Chart style</span><select
|
||
id="ui-chart-style">
|
||
<option data-i18n="chartStyleDefault" value="default">Default</option>
|
||
<option data-i18n="chartStyleSoft" value="soft">Soft</option>
|
||
<option data-i18n="chartStyleTechnical" value="technical">Technical
|
||
</option>
|
||
<option data-i18n="chartStyleHighContrast" value="high_contrast">High
|
||
contrast</option>
|
||
<option data-i18n="chartStyleNeon" value="neon">Neon</option>
|
||
<option data-i18n="chartStyleMonochrome" value="monochrome">Monochrome
|
||
</option>
|
||
<option data-i18n="chartStyleWarm" value="warm">Warm</option>
|
||
<option data-i18n="chartStyleCool" value="cool">Cool</option>
|
||
</select></label>
|
||
<div class="appearance-chart-preview" aria-hidden="true">
|
||
<svg viewBox="0 0 300 74" preserveAspectRatio="none">
|
||
<path class="chart-grid" d="M0 18H300 M0 37H300 M0 56H300"></path>
|
||
<polyline class="appearance-chart-primary"
|
||
points="0,58 48,42 92,50 138,25 182,35 228,16 300,29"></polyline>
|
||
<polyline class="appearance-chart-secondary"
|
||
points="0,63 48,56 92,38 138,45 182,27 228,37 300,19"></polyline>
|
||
</svg>
|
||
<span data-i18n="chartStylePreview">Preview</span>
|
||
</div>
|
||
</div>
|
||
<div class="chart-style-palette" id="chart-style-palette"
|
||
aria-label="Chart style palettes" data-i18n-aria="chartStylePalettes"></div>
|
||
</section>
|
||
<section class="appearance-section">
|
||
<div class="appearance-section-heading">
|
||
<div><span class="small-label" data-i18n="appearanceDataSection">TABLES /
|
||
LOGS</span>
|
||
<h3 data-i18n="tableDensity">Tables and logs density</h3>
|
||
</div><button class="quiet appearance-section-reset"
|
||
data-appearance-reset="tables" data-i18n="resetSection" type="button">Reset
|
||
section</button>
|
||
</div>
|
||
<div class="appearance-settings-grid">
|
||
<label><span data-i18n="tableDensity">Tables and logs density</span><select
|
||
id="ui-table-density">
|
||
<option data-i18n="densityNormal" value="normal">Normal</option>
|
||
<option data-i18n="densityCompact" value="compact">Compact</option>
|
||
<option data-i18n="densityUltra" value="ultra">Ultra compact</option>
|
||
</select><small class="fine" data-i18n="tableDensityHelp">Changes row height
|
||
and log spacing independently from interface scale.</small></label>
|
||
</div>
|
||
</section>
|
||
<section class="appearance-section">
|
||
<div class="appearance-section-heading">
|
||
<div><span class="small-label"
|
||
data-i18n="appearanceDashboardSection">DASHBOARD</span>
|
||
<h3 data-i18n="dashboardLayout">Dashboard layout</h3>
|
||
</div><button class="quiet appearance-section-reset"
|
||
data-appearance-reset="dashboard" data-i18n="resetSection"
|
||
type="button">Reset section</button>
|
||
</div>
|
||
<div class="appearance-settings-grid dashboard-appearance-grid">
|
||
<label class="dashboard-density-setting"><span
|
||
data-i18n="dashboardDensity">Dashboard density</span><select
|
||
id="ui-dashboard-density">
|
||
<option data-i18n="densityNormal" value="normal">Normal</option>
|
||
<option data-i18n="densityCompact" value="compact">Compact</option>
|
||
<option data-i18n="densityUltra" value="ultra">Ultra compact</option>
|
||
</select><small class="fine" data-i18n="dashboardDensityHelp">Controls
|
||
Overview telemetry card size without changing the rest of the
|
||
interface.</small></label>
|
||
<div class="dashboard-widget-settings">
|
||
<div class="dashboard-widget-settings-head">
|
||
<div class="dashboard-widget-settings-intro">
|
||
<span data-i18n="dashboardWidgets">Overview cards</span>
|
||
<small class="fine" data-i18n="dashboardWidgetsHelp">Show, hide and
|
||
reorder all Overview blocks, including charts, diagnostics and
|
||
the backend table.</small>
|
||
</div>
|
||
<div class="dashboard-widget-settings-controls">
|
||
<button class="outline" type="button"
|
||
id="overview-layout-open-settings"
|
||
data-i18n="overviewLayoutEditorOpenSettings">Dashboard
|
||
presets</button>
|
||
<button class="quiet" id="dashboard-widget-expand" type="button"
|
||
aria-expanded="false" aria-controls="dashboard-widget-list"
|
||
data-i18n="dashboardWidgetsShowAll">Show all</button>
|
||
<button class="quiet dashboard-widget-scroll-button"
|
||
id="dashboard-widget-scroll-prev" type="button"
|
||
data-i18n-aria="dashboardWidgetsScrollBack"
|
||
aria-label="Scroll cards left">←</button>
|
||
<button class="quiet dashboard-widget-scroll-button"
|
||
id="dashboard-widget-scroll-next" type="button"
|
||
data-i18n-aria="dashboardWidgetsScrollNext"
|
||
aria-label="Scroll cards right">→</button>
|
||
</div>
|
||
</div>
|
||
<div class="dashboard-widget-list" id="dashboard-widget-list" role="group"
|
||
data-i18n-aria="dashboardWidgets" aria-label="Overview cards"
|
||
tabindex="0"></div>
|
||
<small class="fine dashboard-widget-scroll-hint"
|
||
data-i18n="dashboardWidgetsScrollHelp">Scroll horizontally to browse
|
||
cards, or expand the list.</small>
|
||
<small class="fine" data-i18n="streamWidgetSettingsHelp">Stream cards are
|
||
opt-in and only change Dashboard presentation, not Stream routing or
|
||
monitoring.</small>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section class="appearance-section" id="refresh-preferences">
|
||
<div class="appearance-section-heading">
|
||
<div>
|
||
<div class="small-label" data-i18n="refreshSection">REFRESH</div>
|
||
<h3 data-i18n="refreshTitle">Data refresh</h3>
|
||
</div><button class="quiet appearance-section-reset" id="refresh-reset"
|
||
type="button" data-i18n="resetSection">Reset section</button>
|
||
</div>
|
||
<p class="fine" data-i18n="refreshHelp">Per-account refresh intervals. Off pauses
|
||
all automatic sources; manual refresh remains available.</p>
|
||
<div class="appearance-settings-grid">
|
||
<label class="check"><input type="checkbox" checked id="refresh-live-enabled"
|
||
data-refresh-preference="live_enabled" />
|
||
<span data-i18n="refreshLiveEnabled">Automatic refresh
|
||
enabled</span></label>
|
||
<label><span data-i18n="refreshOverview">Live overview / SSE interval</span>
|
||
<select id="refresh-overview-seconds"
|
||
data-refresh-preference="overview_seconds">
|
||
<option value="1">1 s</option>
|
||
<option value="2">2 s</option>
|
||
<option selected value="3">3 s</option>
|
||
<option value="5">5 s</option>
|
||
<option value="10">10 s</option>
|
||
</select></label>
|
||
<label><span data-i18n="refreshTraffic">Traffic interval</span>
|
||
<select id="refresh-traffic-seconds"
|
||
data-refresh-preference="traffic_seconds">
|
||
<option value="0" data-i18n="refreshManual">Manual</option>
|
||
<option value="3">3 s</option>
|
||
<option selected value="5">5 s</option>
|
||
<option value="10">10 s</option>
|
||
<option value="30">30 s</option>
|
||
</select></label>
|
||
<label><span data-i18n="refreshCache">Cache interval</span>
|
||
<select id="refresh-cache-seconds" data-refresh-preference="cache_seconds">
|
||
<option value="0" data-i18n="refreshManual">Manual</option>
|
||
<option value="3">3 s</option>
|
||
<option selected value="5">5 s</option>
|
||
<option value="10">10 s</option>
|
||
<option value="30">30 s</option>
|
||
</select></label>
|
||
<label><span data-i18n="refreshSystemInfo">System information interval</span>
|
||
<select id="refresh-system-info-seconds"
|
||
data-refresh-preference="system_info_seconds">
|
||
<option value="0" data-i18n="refreshManual">Manual</option>
|
||
<option value="15">15 s</option>
|
||
<option value="30">30 s</option>
|
||
<option selected value="60">60 s</option>
|
||
<option value="300">300 s</option>
|
||
</select></label>
|
||
</div>
|
||
</section>
|
||
<div class="appearance-actions"><button class="outline" data-i18n="appearanceReset"
|
||
id="appearance-reset" type="button">Reset appearance</button></div>
|
||
</div>
|
||
</div>
|
||
<div aria-labelledby="settings-tab-listeners" class="settings-page"
|
||
data-settings-page="listeners" hidden="" id="settings-page-listeners" role="tabpanel">
|
||
<form class="card edit-form" id="listeners-form">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="listeners">Public listeners</h2><span class="pill"
|
||
data-i18n="restartLabel">RESTART</span>
|
||
</div>
|
||
<p class="fine" data-i18n="listenersHelp">Saved in SQLite. Changes take effect on the
|
||
next process start; the administration listener is configured separately.</p>
|
||
<div class="named-listeners" id="named-listener-rows"></div><button class="quiet"
|
||
data-i18n="listenerAdd" id="add-named-listener" type="button">Add listener</button>
|
||
<p class="fine" data-i18n="listenerScopeHint"></p>
|
||
<p class="fine" data-i18n="lowPortsHelp">Privileged ports may need CAP_NET_BIND_SERVICE.
|
||
Do not run the whole application as root.</p>
|
||
<p class="pending-note" hidden="" id="listener-pending"></p>
|
||
<div class="listener-active-summary" id="listener-active" role="status"></div>
|
||
<div class="form-actions"><button class="primary" data-i18n="saveListeners"
|
||
type="submit">Save listeners</button></div>
|
||
</form>
|
||
</div>
|
||
<div aria-labelledby="settings-tab-connections" class="settings-page"
|
||
data-settings-page="connections" hidden="" id="settings-page-connections" role="tabpanel">
|
||
<div class="card http-connections-card">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="httpConnections">HTTP connections</h2>
|
||
</div>
|
||
<p class="fine" data-i18n="httpConnectionsHelp">Connection lifecycle and transport
|
||
limits for HTTP/2 and HTTP/3.</p>
|
||
<nav aria-label="HTTP connection settings" class="settings-tabs sub-tabs"
|
||
id="http-settings-tabs" role="tablist">
|
||
<button aria-selected="true" data-http-settings-tab="http2" data-i18n="http2Tab"
|
||
type="button">HTTP/2</button>
|
||
<button aria-selected="false" data-http-settings-tab="http3" data-i18n="http3Tab"
|
||
tabindex="-1" type="button">HTTP/3</button>
|
||
</nav>
|
||
<div data-http-settings-page="http2">
|
||
<form class="edit-form embedded-settings-form" id="http2-form">
|
||
<div class="card-heading">
|
||
<h3 data-i18n="http2Settings">HTTP/2 connections</h3><span class="pill"
|
||
data-i18n="restartLabel">RESTART</span>
|
||
</div>
|
||
<p class="fine" data-i18n="http2SettingsHelp">HTTP/2 enablement is controlled
|
||
per listener. Tune idle, maximum lifetime and graceful GOAWAY drain limits
|
||
here.</p>
|
||
<p class="pending-note" hidden="" id="http2-listeners-dirty"
|
||
data-i18n="http2ListenersDirty">Save pending listener changes before editing
|
||
HTTP/2 settings.</p>
|
||
<div class="named-listeners http2-listener-settings" id="http2-listener-rows">
|
||
</div>
|
||
<p class="fine" hidden="" id="http2-no-listeners"
|
||
data-i18n="http2NoEnabledListeners">No listener has HTTP/2 enabled. Enable
|
||
HTTP/2 or h2c in Settings > Listeners first.</p>
|
||
<div class="form-actions"><button class="primary" data-i18n="http2Save"
|
||
id="http2-save" type="submit">Save HTTP/2 settings</button></div>
|
||
</form>
|
||
</div>
|
||
<div data-http-settings-page="http3" hidden>
|
||
<form class="edit-form embedded-settings-form" id="http3-form">
|
||
<div class="card-heading">
|
||
<h3 data-i18n="http3Settings">HTTP/3 / QUIC</h3><span class="pill"
|
||
data-i18n="restartLabel">RESTART</span>
|
||
</div>
|
||
<p class="fine" data-i18n="http3SettingsHelp">HTTP/3 enablement is controlled in
|
||
Listeners. Tune advertisement, connection and stream limits here.</p>
|
||
<p class="pending-note" hidden="" id="http3-listeners-dirty"
|
||
data-i18n="http3ListenersDirty">Save pending listener changes before editing
|
||
HTTP/3 settings.</p>
|
||
<div class="named-listeners http3-listener-settings" id="http3-listener-rows">
|
||
</div>
|
||
<p class="fine" hidden="" id="http3-no-listeners"
|
||
data-i18n="http3NoTlsListeners">No TLS listeners are configured. Add and
|
||
save a TLS listener first.</p>
|
||
<div class="form-actions"><button class="primary" data-i18n="http3Save"
|
||
id="http3-save" type="submit">Save HTTP/3 settings</button></div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div aria-labelledby="settings-tab-docker-agents" class="settings-page"
|
||
data-settings-page="docker-agents" hidden="" id="settings-page-docker-agents"
|
||
role="tabpanel">
|
||
<form class="card edit-form" id="docker-agents-form">
|
||
<div class="card-heading">
|
||
<div><span class="small-label" data-i18n="dockerDesiredState">DESIRED STATE /
|
||
SQLITE</span>
|
||
<h2 data-i18n="dockerAgents">Docker agents</h2>
|
||
</div><span class="pill live" data-i18n="hotApply">LIVE APPLY</span>
|
||
</div>
|
||
<p class="fine" data-i18n="dockerAgentsHelp">Configure one agent per Docker host. HTTPS
|
||
with a persistent self-signed certificate is the default; pin its SHA-256
|
||
fingerprint in RustProxy. Each agent has its own base domain and optional default
|
||
backend IP.</p>
|
||
<div class="docker-agent-settings-list" id="docker-agent-settings-list"></div>
|
||
<button class="quiet" data-i18n="dockerAgentAdd" id="docker-agent-add" type="button">Add
|
||
Docker agent</button>
|
||
<div class="form-actions"><button class="primary" data-i18n="dockerAgentsSave"
|
||
type="submit">Save and connect agents</button></div>
|
||
</form>
|
||
</div>
|
||
<section aria-labelledby="settings-tab-policies" class="settings-page"
|
||
data-settings-page="policies" hidden="" id="settings-page-policies" role="tabpanel">
|
||
<div class="card edit-form">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="policyRulesTitle">Rules & headers</h2><span class="pill live"
|
||
data-i18n="hotApply">LIVE APPLY</span>
|
||
</div>
|
||
<nav aria-label="Rules and headers" class="settings-tabs sub-tabs"
|
||
id="policy-settings-tabs" role="tablist">
|
||
<button aria-selected="true" data-policy-settings-tab="user-agent"
|
||
data-i18n="globalUaBlockingTitle" type="button">Global User-Agent
|
||
blocklist</button>
|
||
<button aria-selected="false" data-policy-settings-tab="headers"
|
||
data-i18n="customHeaders" type="button">Global custom headers</button>
|
||
<button aria-selected="false" data-policy-settings-tab="response"
|
||
data-i18n="responseSettings" type="button">Client response</button>
|
||
</nav>
|
||
</div>
|
||
<div data-policy-settings-page="user-agent">
|
||
<article class="card security-policy-card">
|
||
<div class="card-heading">
|
||
<div><span class="small-label" data-i18n="userAgentBlocking">USER-AGENT
|
||
BLOCKING</span>
|
||
<h2 data-i18n="globalUaBlockingTitle">Global User-Agent blocklist</h2>
|
||
<p class="fine" data-i18n="globalUaBlockingHelp">Blocks matching User-Agent
|
||
values on public proxy vhosts before an upstream is opened. Per-vhost
|
||
lists are configured in Vhosts → Security.</p>
|
||
</div>
|
||
</div>
|
||
<form class="edit-form security-inline-form" id="global-ua-block-form">
|
||
<label class="check"><input id="global-ua-block-enabled" type="checkbox" /><span
|
||
data-i18n="enabled">Enabled</span></label>
|
||
<label><span data-i18n="uaBlockPatterns">Blocked patterns, one per
|
||
line</span><textarea id="global-ua-block-patterns" rows="6"
|
||
maxlength="32768" spellcheck="false"
|
||
placeholder="BadBot curl/* *scanner*"></textarea></label>
|
||
<p class="fine" data-i18n="uaBlockPatternHelp">Matching is case-insensitive.
|
||
Plain text matches a substring; * is a wildcard. Requests without User-Agent
|
||
are not blocked by this rule.</p>
|
||
<div class="form-actions"><button class="primary" data-i18n="apply"
|
||
type="submit">Validate, save and apply</button></div>
|
||
</form>
|
||
</article>
|
||
</div>
|
||
<div data-policy-settings-page="headers" hidden="">
|
||
<form class="card edit-form" id="headers-form">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="customHeaders">Global custom headers</h2><span class="pill live"
|
||
data-i18n="hotApply">LIVE APPLY</span>
|
||
</div>
|
||
<p class="fine" data-i18n="globalHeadersHelp">Request rules go to the backend;
|
||
response
|
||
rules go to the client. Route rules run after global rules. Server policy takes
|
||
final precedence.</p>
|
||
<h3 class="subheading" data-i18n="responseHeaders">Response to client</h3>
|
||
<div class="header-rules" id="response-header-rules"></div><button class="quiet"
|
||
data-i18n="addHeader" id="add-response-header" type="button">Add header
|
||
rule</button>
|
||
<h3 class="subheading" data-i18n="requestHeaders">Request to backend</h3>
|
||
<div class="header-rules" id="request-header-rules"></div><button class="quiet"
|
||
data-i18n="addHeader" id="add-request-header" type="button">Add header
|
||
rule</button>
|
||
<p class="fine" data-i18n="headersHelp">Operations: set, append, remove. Transport,
|
||
body
|
||
encoding and client-identity headers are protected.</p>
|
||
<div class="form-actions"><button class="primary" data-i18n="saveHeaders"
|
||
type="submit">Apply header rules</button></div>
|
||
</form>
|
||
</div>
|
||
<div data-policy-settings-page="response" hidden="">
|
||
<form class="card edit-form" id="response-form">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="responseSettings">Client response</h2><span class="pill live"
|
||
data-i18n="hotApply">LIVE APPLY</span>
|
||
</div>
|
||
<label data-i18n="serverHeader" for="server-mode">Server header</label><select
|
||
id="server-mode">
|
||
<option data-i18n="serverPreserve" value="preserve">Preserve origin value
|
||
</option>
|
||
<option data-i18n="serverHide" value="hide">Hide completely</option>
|
||
<option data-i18n="serverCustom" selected value="custom">Replace / add custom
|
||
value
|
||
</option>
|
||
</select>
|
||
<label data-i18n="serverValue" for="server-value">Custom Server value</label><input
|
||
id="server-value" maxlength="256" placeholder="RustProxy" value="RustProxy"
|
||
spellcheck="false" />
|
||
<h3 class="subheading" data-i18n="diagnosticHeaders">Diagnostic headers</h3>
|
||
<div class="switch-grid">
|
||
<label class="check"><input id="expose-request-id"
|
||
type="checkbox" /><span>X-Request-ID</span></label>
|
||
</div>
|
||
<h3 class="subheading" data-i18n="compression">Streaming compression</h3>
|
||
<div class="switch-grid"><label class="check"><input id="compress-gzip"
|
||
type="checkbox" /><span>gzip</span></label><label class="check"><input
|
||
id="compress-br" type="checkbox" /><span>Brotli
|
||
(br)</span></label><label class="check"><input id="compress-zstd"
|
||
type="checkbox" /><span>Zstandard
|
||
(zstd)</span></label><label class="check"><input id="compress-deflate"
|
||
type="checkbox" /><span>deflate</span></label></div>
|
||
<label data-i18n="compressionMin" for="compress-min">Minimum response size
|
||
(bytes)</label><input id="compress-min" max="1048576" min="32" required=""
|
||
type="number" />
|
||
<p class="fine" data-i18n="compressionHelp">Negotiates Accept-Encoding using client
|
||
q-values and zstd → br → gzip → deflate as the server preference for equal
|
||
quality.
|
||
Cookies, Authorization, Set-Cookie, private and no-store responses may be
|
||
compressed. Existing Content-Encoding, ranges, SSE, upgrades and no-transform
|
||
are
|
||
passed through.</p>
|
||
<div class="form-actions"><button class="primary" data-i18n="saveResponse"
|
||
type="submit">Apply response settings</button></div>
|
||
</form>
|
||
</div>
|
||
</section>
|
||
<div aria-labelledby="settings-tab-cache" class="settings-page" data-settings-page="cache"
|
||
hidden="" id="settings-page-cache" role="tabpanel">
|
||
<form class="card edit-form" id="cache-form">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="cacheSettings">Cache</h2><span class="pill live"
|
||
data-i18n="hotApply">LIVE APPLY</span>
|
||
</div>
|
||
<nav aria-label="Cache settings" class="settings-tabs sub-tabs" id="cache-settings-tabs"
|
||
role="tablist">
|
||
<button aria-selected="true" data-cache-settings-tab="general"
|
||
data-i18n="cacheTabGeneral" type="button">General</button>
|
||
<button aria-selected="false" data-cache-settings-tab="resilience"
|
||
data-i18n="cacheTabResilience" type="button">Resilience</button>
|
||
<button aria-selected="false" data-cache-settings-tab="cold"
|
||
data-i18n="cacheTabCold" type="button">Cold cache</button>
|
||
<button aria-selected="false" data-cache-settings-tab="diagnostics"
|
||
data-i18n="cacheTabDiagnostics" type="button">Static & diagnostics</button>
|
||
</nav>
|
||
<div data-cache-settings-page="general">
|
||
<label class="check"><input id="cache-enabled" type="checkbox" /><span
|
||
data-i18n="enableCache">Enable shared cache</span></label>
|
||
<p class="fine" data-i18n="cacheSafety">Only complete public-safe GET responses.
|
||
Authorization, cookies and private/no-store responses still bypass the cache.
|
||
One byte range can be served from a complete cached object;
|
||
multipart/unsupported ranges pass through.</p>
|
||
<div class="card soft-card cache-memory-sizing">
|
||
<div class="card-heading">
|
||
<h3 data-i18n="cacheMemorySizingTitle">Memory sizing</h3>
|
||
<span class="pill" id="cache-memory-preset-selected">-</span>
|
||
<span class="pill" id="cache-memory-detected">-</span>
|
||
</div>
|
||
<p class="fine" data-i18n="cacheMemorySizingHelp">Presets use the effective
|
||
memory available to RustProxy (host RAM capped by cgroup when present). They
|
||
tune HOT, transient RAM, object size, entry count and the demotion queue;
|
||
the COLD disk budget is left unchanged.</p>
|
||
<div class="form-actions">
|
||
<button class="outline" data-cache-memory-preset="conservative"
|
||
data-i18n="cachePresetConservative" type="button">Conservative</button>
|
||
<button class="outline" data-cache-memory-preset="recommended"
|
||
data-i18n="cachePresetRecommended" type="button">Recommended</button>
|
||
<button class="outline" data-cache-memory-preset="performance"
|
||
data-i18n="cachePresetPerformance" type="button">Performance</button>
|
||
</div>
|
||
<p class="fine" id="cache-memory-preset-summary">-</p>
|
||
</div>
|
||
<p class="fine" id="cache-memory-warning" hidden></p>
|
||
<div class="field-grid">
|
||
<div><label data-i18n="cacheMemory" for="cache-memory">HOT allocated budget
|
||
(MiB)</label><input id="cache-memory" min="0.0625" required=""
|
||
step="0.0625" type="number" /></div>
|
||
<div><label data-i18n="cacheTransientLimit"
|
||
for="cache-transient-memory">Transient RAM budget (MiB)</label><input
|
||
id="cache-transient-memory" min="0.0625" required="" step="0.0625"
|
||
type="number" /></div>
|
||
<div><label data-i18n="cacheObject" for="cache-object">Maximum object
|
||
(KiB)</label><input id="cache-object" min="1" required=""
|
||
type="number" /></div>
|
||
<div><label data-i18n="cacheEntries" for="cache-entries">Maximum
|
||
entries</label><input id="cache-entries" max="16384" min="1" required=""
|
||
type="number" /></div>
|
||
<div><label data-i18n="cacheEvictionLimit" for="cache-eviction-limit">LRU
|
||
removals per admission</label><input id="cache-eviction-limit"
|
||
max="4096" min="1" required="" type="number" /></div>
|
||
<div><label data-i18n="cacheAutoTrimAfter" for="cache-auto-trim-after">Idle age
|
||
before HOT trim (seconds)</label><input id="cache-auto-trim-after"
|
||
max="86400" min="5" required="" type="number" /></div>
|
||
<div><label data-i18n="cacheAutoTrimTarget" for="cache-auto-trim-target">HOT
|
||
trim target (%)</label><input id="cache-auto-trim-target" max="95"
|
||
min="10" required="" type="number" /></div>
|
||
<div><label data-i18n="cacheMaxTtl" for="cache-ttl">Maximum TTL
|
||
(seconds)</label><input id="cache-ttl" max="604800" min="1" required=""
|
||
type="number" /></div>
|
||
</div>
|
||
<label class="check"><input id="cache-auto-trim-enabled" type="checkbox" /><span
|
||
data-i18n="cacheAutoTrimEnabled">Automatically release idle HOT cache
|
||
memory</span></label>
|
||
<p class="fine" data-i18n="cacheAutoTrimHelp">When HOT memory is above the target,
|
||
only entries idle for the configured age are removed in bounded passes. Active
|
||
objects are not purged.</p>
|
||
<label data-i18n="cacheDefaultTtl" for="cache-default-ttl">Fallback TTL for public
|
||
responses (0 = off)</label><input id="cache-default-ttl" max="604800" min="0"
|
||
required="" type="number" />
|
||
<label class="check"><input id="cache-query" type="checkbox" /><span
|
||
data-i18n="bypassQuery">Bypass URLs with a query string</span></label>
|
||
<label data-i18n="cachePrefixes" for="cache-prefixes">Bypass path prefixes, one per
|
||
line</label><textarea id="cache-prefixes" placeholder="/api/private" rows="3"
|
||
spellcheck="false"></textarea>
|
||
</div>
|
||
<div data-cache-settings-page="resilience" hidden>
|
||
<h3 class="subheading" data-i18n="cacheCollapseTitle">Request collapsing</h3>
|
||
<label class="check"><input id="cache-collapse-enabled" type="checkbox" /><span
|
||
data-i18n="cacheCollapseEnable">Collapse concurrent fills for one cache
|
||
key</span></label>
|
||
<div class="field-grid">
|
||
<div><label data-i18n="cacheCollapseTimeout"
|
||
for="cache-collapse-timeout">Follower wait timeout (ms)</label><input
|
||
id="cache-collapse-timeout" max="60000" min="10" required=""
|
||
type="number" /></div>
|
||
<div><label data-i18n="cacheCollapseFallback"
|
||
for="cache-collapse-fallback">Timeout fallback</label><select
|
||
id="cache-collapse-fallback">
|
||
<option data-i18n="cacheCollapseFallbackOrigin" value="origin">Origin
|
||
</option>
|
||
<option data-i18n="cacheCollapseFallbackStale"
|
||
value="stale_if_available">Stale if available</option>
|
||
</select></div>
|
||
</div>
|
||
<h3 class="subheading" data-i18n="cacheStaleErrorTitle">Stale-if-error policy</h3>
|
||
<div class="switch-grid">
|
||
<label class="check"><input id="cache-sie-timeout" type="checkbox" /><span
|
||
data-i18n="cacheSieTimeout">Timeout</span></label>
|
||
<label class="check"><input id="cache-sie-connection" type="checkbox" /><span
|
||
data-i18n="cacheSieConnection">Connection failure</span></label>
|
||
<label class="check"><input id="cache-sie-dns" type="checkbox" /><span
|
||
data-i18n="cacheSieDns">DNS failure</span></label>
|
||
<label class="check"><input id="cache-sie-tls" type="checkbox" /><span
|
||
data-i18n="cacheSieTls">TLS failure</span></label>
|
||
</div>
|
||
<label data-i18n="cacheSieStatuses" for="cache-sie-statuses">HTTP 5xx statuses
|
||
eligible for stale fallback</label><input id="cache-sie-statuses"
|
||
maxlength="127" placeholder="500, 502, 503, 504" required="" />
|
||
<h3 class="subheading" data-i18n="cacheProactiveTitle">Proactive refresh</h3>
|
||
<label class="check"><input id="cache-proactive-enabled" type="checkbox" /><span
|
||
data-i18n="cacheProactiveEnable">Refresh sampled cached objects before
|
||
expiry</span></label>
|
||
<div class="field-grid">
|
||
<div><label data-i18n="cacheProactiveBefore"
|
||
for="cache-proactive-before">Refresh before expiry
|
||
(seconds)</label><input id="cache-proactive-before" max="604800" min="1"
|
||
required="" type="number" /></div>
|
||
<div><label data-i18n="cacheProactivePercent"
|
||
for="cache-proactive-percent">Sampled objects (%)</label><input
|
||
id="cache-proactive-percent" max="100" min="0" required=""
|
||
type="number" /></div>
|
||
<div><label data-i18n="cacheProactiveInflight"
|
||
for="cache-proactive-inflight">Maximum concurrent
|
||
refreshes</label><input id="cache-proactive-inflight" max="64" min="1"
|
||
required="" type="number" /></div>
|
||
</div>
|
||
<p class="fine" data-i18n="cacheProactiveHelp">Uses validator revalidation,
|
||
deterministic sampling and a global concurrency cap to avoid refresh storms.</p>
|
||
</div>
|
||
<div data-cache-settings-page="cold" hidden>
|
||
<div class="card soft-card cache-memory-sizing">
|
||
<div class="card-heading">
|
||
<h3 data-i18n="cacheColdPresetTitle">COLD policy preset</h3>
|
||
<span class="pill" id="cache-cold-preset-selected">-</span>
|
||
</div>
|
||
<p class="fine" data-i18n="cacheColdPresetHelp">The recommended policy enables
|
||
idle/LRU/shutdown demotion, uses a queue sized from transient RAM and
|
||
conservative GC timings. Disk path, disk budget and object limit are left
|
||
unchanged.</p>
|
||
<div class="form-actions">
|
||
<button class="outline" data-cache-cold-preset="recommended"
|
||
data-i18n="cachePresetRecommended" type="button">Recommended</button>
|
||
</div>
|
||
</div>
|
||
<label class="check"><input id="cache-cold-enabled" type="checkbox" /><span
|
||
data-i18n="cacheColdEnable">Enable persistent disk tier</span></label>
|
||
<p class="fine" data-i18n="cacheColdHelp">Idle and LRU hot objects are demoted to
|
||
disk. A cold hit is read asynchronously and promoted back to RAM. Metadata is
|
||
readable JSON; bodies are stored as raw .body files.</p>
|
||
<label class="check"><input id="cache-cold-on-idle" type="checkbox" /><span
|
||
data-i18n="cacheColdOnIdle">Move old/idle objects to cold
|
||
storage</span></label>
|
||
<label class="check"><input id="cache-cold-on-age" type="checkbox" /><span
|
||
data-i18n="cacheColdOnAge">Move objects after their maximum hot-tier
|
||
age</span></label>
|
||
<label class="check"><input id="cache-cold-on-lru" type="checkbox" /><span
|
||
data-i18n="cacheColdOnLru">Move hot LRU evictions to cold
|
||
storage</span></label>
|
||
<label class="check"><input id="cache-cold-on-shutdown" type="checkbox" /><span
|
||
data-i18n="cacheColdOnShutdown">Flush hot cache to cold storage during
|
||
graceful shutdown</span></label>
|
||
<div class="field-grid">
|
||
<div><label data-i18n="cacheColdPath" for="cache-cold-path">Cold path (inside
|
||
data directory)</label><input id="cache-cold-path" maxlength="256"
|
||
placeholder="cache/cold" required="" /></div>
|
||
<div><label data-i18n="cacheColdBytes" for="cache-cold-bytes">Cold disk budget
|
||
(MiB)</label><input id="cache-cold-bytes" max="1048576" min="0.0625"
|
||
required="" step="0.0625" type="number" /></div>
|
||
<div><label data-i18n="cacheColdEntries" for="cache-cold-entries">Cold object
|
||
limit</label><input id="cache-cold-entries" max="1000000" min="1"
|
||
required="" type="number" /></div>
|
||
<div><label data-i18n="cacheColdAfter" for="cache-cold-after">Demote after idle
|
||
seconds</label><input id="cache-cold-after" max="604800" min="5"
|
||
required="" type="number" /></div>
|
||
<div><label data-i18n="cacheColdMaxHot" for="cache-cold-max-hot">Maximum
|
||
hot-tier age (seconds)</label><input id="cache-cold-max-hot"
|
||
max="2592000" min="5" required="" type="number" /></div>
|
||
<div><label data-i18n="cacheColdGcInterval" for="cache-cold-gc-interval">GC
|
||
interval (seconds)</label><input id="cache-cold-gc-interval" max="86400"
|
||
min="5" required="" type="number" /></div>
|
||
<div><label data-i18n="cacheColdGcMaxDelete"
|
||
for="cache-cold-gc-max-delete">Maximum deletes per GC pass</label><input
|
||
id="cache-cold-gc-max-delete" max="10000" min="1" required=""
|
||
type="number" /></div>
|
||
<div><label data-i18n="cacheColdQueueLimit" for="cache-cold-queue-bytes">COLD
|
||
queue RAM budget (MiB)</label><input id="cache-cold-queue-bytes"
|
||
min="0.0625" required="" step="0.0625" type="number" /></div>
|
||
</div>
|
||
<p class="fine" data-i18n="cacheColdPolicyHelp">Idle age measures time since the
|
||
last access. Maximum hot-tier age measures total RAM residency even for
|
||
frequently used objects. LRU demotion still applies when the RAM budget needs
|
||
space.</p>
|
||
</div>
|
||
<div data-cache-settings-page="diagnostics" hidden>
|
||
<h3 class="subheading" data-i18n="cacheStaticTitle">Static asset cache boost</h3>
|
||
<label class="check"><input id="cache-static-cookie" type="checkbox" /><span
|
||
data-i18n="cacheStaticCookie">Ignore request cookies for static
|
||
files</span></label>
|
||
<label data-i18n="cacheStaticExtensions" for="cache-static-extensions">Global static
|
||
extensions</label><textarea id="cache-static-extensions" rows="5"
|
||
spellcheck="false"></textarea>
|
||
<p class="fine" data-i18n="cacheStaticHelp">Cookie bypass is limited to these file
|
||
extensions. Response privacy rules still apply.</p>
|
||
<div class="switch-grid"><label class="check"><input id="cache-status-header"
|
||
type="checkbox" /><span>X-Cache: HIT / MISS /
|
||
BYPASS</span></label><label class="check"><input id="cache-hits-header"
|
||
type="checkbox" /><span>X-Cache-Hits</span></label><label
|
||
class="check"><input id="cache-rfc-header"
|
||
type="checkbox" /><span>Cache-Status (RFC 9211)</span></label><label
|
||
class="check"><input id="cache-resident-age-header"
|
||
type="checkbox" /><span>X-Cache-Resident-Age</span></label></div>
|
||
<label class="check"><input id="cache-only-hits" type="checkbox" /><span
|
||
data-i18n="cacheOnlyHits">Expose cache diagnostics only on HIT (hide
|
||
MISS/BYPASS)</span></label>
|
||
<div class="cache-via-setting">
|
||
<label class="check"><input id="cache-via-enabled" type="checkbox" /><span
|
||
data-i18n="cacheViaEnable">Add Via response header</span></label>
|
||
<label><span data-i18n="cacheViaValue">Via header value</span><input
|
||
id="cache-via-value" maxlength="256" placeholder="RustProxy" required=""
|
||
type="text" /></label>
|
||
</div>
|
||
<p class="fine" data-i18n="cacheViaHelp">Defaults to Via: RustProxy. Changing this
|
||
response-only header does not purge cached objects.</p>
|
||
<div class="cache-maintenance-help">
|
||
<h3 class="subheading" data-i18n="cacheHttpInvalidationTitle">HTTP BAN / PURGE
|
||
IP allowlist</h3>
|
||
<details class="metric-info cache-maintenance-info">
|
||
<summary aria-label="curl examples for HTTP BAN and PURGE"
|
||
data-i18n-aria="cacheHttpInvalidationCurlLabel">i</summary>
|
||
<div class="metric-info-popover" role="note">
|
||
<p class="fine" data-i18n="cacheHttpInvalidationCurlIntro">Run curl from
|
||
an allowlisted IP against a public vhost:</p>
|
||
<code>curl -i -X PURGE 'https://site.example/assets/app_abcdef0123456789.js'</code>
|
||
<code>curl -i -X BAN 'https://site.example/assets/'</code>
|
||
<code>curl -i -X BAN 'https://site.example/' -H 'X-RustProxy-Confirm-Wide: 1'</code>
|
||
<p class="fine" data-i18n="cacheHttpInvalidationCurlNote">PURGE removes
|
||
an exact URL; BAN invalidates a path prefix for 1 hour. No admin
|
||
cookie is needed, but your IP must be allowed. A reverse proxy must
|
||
forward these methods and trusted client IP information.</p>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
<label><span data-i18n="cacheHttpInvalidationAddresses">Allowed client IP addresses
|
||
or CIDRs (one per line)</span>
|
||
<textarea id="cache-http-invalidation-cidrs" rows="4" spellcheck="false"
|
||
placeholder="192.0.2.10 2001:db8:10::/64"></textarea></label>
|
||
<p class="fine" data-i18n="cacheHttpInvalidationHelp">Empty = disabled. PURGE
|
||
/path?query removes exact matching entries; BAN /path lazily blocks cached URLs
|
||
starting with the prefix (TTL 1 hour, optional X-RustProxy-Ban-Ttl). BAN /
|
||
requires X-RustProxy-Confirm-Wide: 1. Only the matched vhost and Host are
|
||
affected. Trusted reverse proxies must sanitize forwarding headers.</p>
|
||
<p class="fine" data-i18n="cacheAgeHelp">Age is sent on cache hits unless an
|
||
explicit response-header rule removes or replaces it. Such Age rules are
|
||
wire-only and do not change cache freshness.</p>
|
||
</div>
|
||
<div class="form-actions"><button class="primary" data-i18n="saveCache"
|
||
type="submit">Apply cache policy</button><button class="outline"
|
||
data-i18n="purgeCache" id="purge-cache" type="button">Purge all cache</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div aria-labelledby="settings-tab-transform" class="settings-page"
|
||
data-settings-page="transform" hidden="" id="settings-page-transform" role="tabpanel">
|
||
<form class="card edit-form" id="transform-form">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="transformSettings">Static response transformation</h2><span
|
||
class="pill live" data-i18n="hotApply">LIVE APPLY</span>
|
||
</div>
|
||
<label class="check"><input id="transform-enabled" type="checkbox" /><span
|
||
data-i18n="transformEnable">Enable transformation engine globally</span></label>
|
||
<p class="fine" data-i18n="transformHelp">Transformation happens after upstream decoding
|
||
and before cache storage and client compression. Each vhost must opt in separately.
|
||
</p>
|
||
<div class="card soft-card cache-memory-sizing">
|
||
<div class="card-heading">
|
||
<h3 data-i18n="transformResourcePresetTitle">Resource preset</h3>
|
||
<span class="pill" id="transform-preset-selected">-</span>
|
||
</div>
|
||
<p class="fine" data-i18n="transformResourcePresetHelp">Presets tune the temporary
|
||
memory ceiling, maximum transformable response size and global concurrent
|
||
transformation limit. Content types and extension allow-lists are left
|
||
unchanged.</p>
|
||
<div class="form-actions">
|
||
<button class="outline" data-i18n="transformPresetConservative"
|
||
data-transform-preset="conservative" type="button">RAM saver</button>
|
||
<button class="outline" data-i18n="transformPresetRecommended"
|
||
data-transform-preset="recommended" type="button">Recommended</button>
|
||
<button class="outline" data-i18n="transformPresetPerformance"
|
||
data-transform-preset="performance" type="button">Performance</button>
|
||
<button class="outline" data-i18n="transformPresetUber"
|
||
data-transform-preset="uber" type="button">Uber performance</button>
|
||
</div>
|
||
<p class="fine" id="transform-preset-summary">-</p>
|
||
</div>
|
||
<p class="fine" id="transform-memory-warning" hidden></p>
|
||
<div class="field-grid">
|
||
<div><label data-i18n="transformMemoryBudget"
|
||
for="transform-memory-budget">Temporary
|
||
transform memory budget (MiB)</label><input id="transform-memory-budget"
|
||
max="2048" min="1" required="" step="1" type="number" /></div>
|
||
<div><label data-i18n="transformMaxBody" for="transform-max-body">Maximum
|
||
transformable
|
||
response (KiB)</label><input id="transform-max-body" min="1" max="32768"
|
||
required="" type="number" /></div>
|
||
<div><label data-i18n="transformMaxInflight" for="transform-max-inflight">Maximum
|
||
concurrent transformations</label><input id="transform-max-inflight" min="1"
|
||
max="1024" required="" type="number" /></div>
|
||
</div>
|
||
<div class="switch-grid">
|
||
<label class="check"><input id="transform-javascript" type="checkbox" /><span
|
||
data-i18n="transformJavascript">JavaScript minification</span></label>
|
||
<label class="check"><input id="transform-css" type="checkbox" /><span
|
||
data-i18n="transformCss">CSS minification</span></label>
|
||
<label class="check"><input id="transform-html" type="checkbox" /><span
|
||
data-i18n="transformHtml">HTML minification</span></label>
|
||
<label class="check"><input id="transform-svg" type="checkbox" /><span
|
||
data-i18n="transformSvg">SVG optimization</span></label>
|
||
<label class="check"><input id="transform-json" type="checkbox" /><span
|
||
data-i18n="transformJson">JSON compaction</span></label>
|
||
</div>
|
||
<label data-i18n="transformExtensions" for="transform-extensions">Allowed file
|
||
extensions, one per line</label>
|
||
<textarea id="transform-extensions" rows="6" spellcheck="false"></textarea>
|
||
<p class="fine" data-i18n="transformSafety">Cache-Control: no-transform is always
|
||
respected. Transformed representations receive a new ETag and are cached before
|
||
gzip, Brotli or zstd encoding.</p>
|
||
<div class="form-actions"><button class="primary" data-i18n="saveTransform"
|
||
type="submit">Apply transformation policy</button></div>
|
||
</form>
|
||
</div>
|
||
<div aria-labelledby="settings-tab-forwarding" class="settings-page"
|
||
data-settings-page="forwarding" hidden="" id="settings-page-forwarding" role="tabpanel">
|
||
<form class="card edit-form" id="forwarding-form">
|
||
<h2 data-i18n="forwardingSettings">Client identity / forwarding</h2>
|
||
<p class="fine" data-i18n="forwardingHelp"></p><label data-i18n="trustedProxies"
|
||
for="trusted-proxies"></label><textarea id="trusted-proxies"
|
||
placeholder="10.0.0.5/32" rows="3" spellcheck="false"></textarea><label
|
||
class="check"><input id="trusted-proto" type="checkbox" /><span
|
||
data-i18n="trustForwardedProto"></span></label>
|
||
<p class="fine" data-i18n="forwardingCanonical"></p><button class="primary"
|
||
data-i18n="apply" type="submit"></button>
|
||
</form>
|
||
</div>
|
||
<div aria-labelledby="settings-tab-pages" class="settings-page" data-settings-page="pages"
|
||
hidden="" id="settings-page-pages" role="tabpanel">
|
||
<form class="card edit-form" id="pages-form">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="globalPages">Default responses</h2>
|
||
</div>
|
||
<p class="fine" data-i18n="pagesHelp">Choose a page for direct-IP access, unknown hosts
|
||
and errors. Custom errors keep their status unless explicitly overridden.</p>
|
||
<div id="global-pages-fields"></div>
|
||
<div class="form-actions"><button class="primary" data-i18n="apply"
|
||
type="submit">Validate, save and apply</button></div>
|
||
</form>
|
||
</div>
|
||
<div aria-labelledby="settings-tab-logging" class="settings-page" data-settings-page="logging"
|
||
hidden="" id="settings-page-logging" role="tabpanel">
|
||
<div class="card edit-form" id="logging-settings-card">
|
||
<div class="card-heading">
|
||
<div><span class="small-label" data-i18n="loggingMode">JSON / ASYNC / BOUNDED</span>
|
||
<h2 data-i18n="loggingTitle">Structured access logging</h2>
|
||
</div><span class="pill live" data-i18n="hotApply">LIVE APPLY</span>
|
||
</div>
|
||
<nav aria-label="Logging settings" class="settings-tabs sub-tabs"
|
||
id="logging-settings-tabs" role="tablist">
|
||
<button aria-selected="true" data-i18n="loggingTabOutputs"
|
||
data-logging-settings-tab="outputs" type="button">Outputs</button>
|
||
<button aria-selected="false" data-i18n="loggingTabFormat"
|
||
data-logging-settings-tab="format" type="button">JSON format</button>
|
||
<button aria-selected="false" data-i18n="loggingTabErrors"
|
||
data-logging-settings-tab="errors" type="button">Error log</button>
|
||
</nav>
|
||
<form class="logging-settings-subform" id="logging-form">
|
||
<div data-logging-settings-page="outputs">
|
||
<p class="fine" data-i18n="loggingHelp">Live log viewing stays in memory.
|
||
External sinks
|
||
run on a bounded background queue and never block proxy request I/O.</p>
|
||
<article class="card soft-card file-logs-launcher">
|
||
<div>
|
||
<h3 class="subheading" data-i18n="fileLogsTitle">Stored log explorer
|
||
</h3>
|
||
<p class="fine" data-i18n="fileLogsSettingsHelp">Browse configured
|
||
access/error files with server-side filters. File logs can be off
|
||
while Live Logs continues to use its memory buffer.</p>
|
||
</div>
|
||
<button class="outline" data-i18n="openLogs" id="settings-file-logs-open"
|
||
type="button">Open Logs</button>
|
||
</article>
|
||
<article class="card soft-card">
|
||
<h3 class="subheading" data-i18n="loggingCaptureTitle">Captured request
|
||
dimensions</h3>
|
||
<label class="check"><input id="log-capture-user-agent"
|
||
type="checkbox" /><span data-i18n="loggingCaptureUserAgent">Capture
|
||
User-Agent</span></label>
|
||
<label class="check"><input id="log-capture-country" type="checkbox" /><span
|
||
data-i18n="loggingCaptureCountry">Capture country from
|
||
GeoIP</span></label>
|
||
<p class="fine" data-i18n="loggingCaptureCountryHelp">Country lookup is
|
||
optional and disabled by default. Enable it only when country
|
||
logs/statistics are needed; country ACL/rate-limit rules still resolve
|
||
GeoIP when required.</p>
|
||
</article>
|
||
<div class="logging-grid">
|
||
<article class="card">
|
||
<h3 class="subheading" data-i18n="loggingFile">JSON file</h3>
|
||
<label class="check"><input id="log-file-enabled"
|
||
type="checkbox" /><span
|
||
data-i18n="enabled">Enabled</span></label>
|
||
<label class="check"><input id="log-file-per-vhost"
|
||
type="checkbox" /><span data-i18n="loggingPerVhost">Separate
|
||
file per vhost</span></label>
|
||
<label><span data-i18n="loggingPath">File path</span><input
|
||
id="log-file-path" maxlength="1024"
|
||
placeholder="/var/log/rustproxy/access.jsonl" /></label>
|
||
<label><span data-i18n="loggingDirectory">Per-vhost
|
||
directory</span><input id="log-file-directory" maxlength="1024"
|
||
placeholder="/var/log/rustproxy/vhosts" /></label>
|
||
<p class="fine" data-i18n="loggingPerVhostHelp">When enabled, each vhost
|
||
writes
|
||
to its
|
||
own JSONL file. Unmatched traffic uses _unmatched.jsonl.</p>
|
||
</article>
|
||
<article class="card">
|
||
<h3 class="subheading">Logstash</h3>
|
||
<label class="check"><input id="log-logstash-enabled"
|
||
type="checkbox" /><span
|
||
data-i18n="enabled">Enabled</span></label>
|
||
<label><span data-i18n="loggingAddress">TCP address</span><input
|
||
id="log-logstash-address" maxlength="128"
|
||
placeholder="127.0.0.1:5044" /></label>
|
||
</article>
|
||
<article class="card">
|
||
<h3 class="subheading">Loki</h3>
|
||
<label class="check"><input id="log-loki-enabled"
|
||
type="checkbox" /><span
|
||
data-i18n="enabled">Enabled</span></label>
|
||
<label><span data-i18n="loggingPushUrl">Push URL</span><input
|
||
id="log-loki-url" maxlength="2048"
|
||
placeholder="http://127.0.0.1:3100/loki/api/v1/push" /></label>
|
||
<label><span data-i18n="loggingTokenEnv">Bearer token environment
|
||
variable</span><input id="log-loki-token" maxlength="128"
|
||
placeholder="LOKI_TOKEN" /></label>
|
||
<label><span data-i18n="loggingLabels">Labels (JSON
|
||
object)</span><textarea id="log-loki-labels" rows="4"
|
||
spellcheck="false"></textarea></label>
|
||
</article>
|
||
</div>
|
||
<p class="fine" id="logging-status" role="status"></p>
|
||
</div>
|
||
<div data-logging-settings-page="format" hidden>
|
||
<h3 class="subheading" data-i18n="loggingFormat">Configurable JSON format</h3>
|
||
<p class="fine" data-i18n="loggingFormatHelp">Map output names to source fields.
|
||
Leave
|
||
mapping empty for the complete standard entry.</p>
|
||
<div class="logging-format-actions"><label><span
|
||
data-i18n="loggingPreset">Format
|
||
preset</span><select id="log-format-preset">
|
||
<option data-i18n="loggingPresetCustom" value="custom">Custom
|
||
</option>
|
||
<option data-i18n="loggingPresetStandard" value="standard">RustProxy
|
||
standard
|
||
</option>
|
||
<option data-i18n="loggingPresetNginx" value="nginx">Nginx-style
|
||
JSON
|
||
</option>
|
||
<option data-i18n="loggingPresetEcs" value="ecs">ECS-style</option>
|
||
<option data-i18n="loggingPresetMinimal" value="minimal">Minimal
|
||
</option>
|
||
</select></label>
|
||
<div class="logging-builder-action"><span class="small-label"
|
||
data-i18n="loggingBuilder">Visual field builder</span><button
|
||
class="outline" data-i18n="loggingBuilderOpen" id="log-format-open"
|
||
type="button">Configure fields</button></div>
|
||
</div>
|
||
<details>
|
||
<summary data-i18n="loggingAdvancedJson">Advanced JSON mapping</summary>
|
||
<div class="field-grid"><label><span data-i18n="loggingFields">Field mapping
|
||
(JSON
|
||
object)</span><textarea id="log-format-fields" rows="7"
|
||
spellcheck="false"></textarea></label>
|
||
<label><span data-i18n="loggingStatic">Static fields (JSON
|
||
object)</span><textarea id="log-format-static" rows="7"
|
||
spellcheck="false"></textarea></label>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
<div class="form-actions"><button class="primary" data-i18n="apply"
|
||
type="submit">Validate, save
|
||
and apply</button></div>
|
||
</form>
|
||
<form class="logging-settings-subform" data-logging-settings-page="errors" hidden
|
||
id="error-log-form">
|
||
<h3 class="subheading" data-i18n="errorLogTitle">Error file</h3>
|
||
<label class="check"><input id="error-log-enabled" type="checkbox" /><span
|
||
data-i18n="enabled">Enabled</span></label>
|
||
<p class="fine" data-i18n="errorLogRoutingHelp">Warnings, errors and worker panics
|
||
go to this file when enabled, or to the console/systemd journal when disabled.
|
||
File failures fall back to the console.</p>
|
||
<div class="field-grid">
|
||
<label><span data-i18n="errorLogPath">Error log path</span><input
|
||
id="error-log-path" maxlength="1024"
|
||
placeholder="/var/log/rustproxy/error.log" /></label>
|
||
<label><span data-i18n="errorLogFormat">Format</span><select
|
||
id="error-log-format">
|
||
<option value="JSON">JSON</option>
|
||
<option value="RAW">RAW</option>
|
||
</select></label>
|
||
</div>
|
||
<p class="fine" id="error-log-status" role="status"></p>
|
||
<div class="form-actions"><button class="primary" data-i18n="apply"
|
||
type="submit">Validate, save and apply</button></div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<div aria-labelledby="settings-tab-geoip" class="settings-page" data-settings-page="geoip"
|
||
hidden="" id="settings-page-geoip" role="tabpanel">
|
||
<form class="card edit-form" id="geoip-form">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="geoipUpdatesTitle"></h2><span class="pill">AUTO</span>
|
||
</div>
|
||
<p class="fine" data-i18n="geoipUpdatesHelp"></p>
|
||
<label class="check"><input id="geoip-enabled" type="checkbox" /><span
|
||
data-i18n="geoipUpdatesEnable"></span></label>
|
||
<div class="field-grid">
|
||
<label><span data-i18n="geoipFormat"></span><select id="geoip-format">
|
||
<option data-i18n="geoipFormatCsvPair" value="csv_pair"></option>
|
||
<option data-i18n="geoipFormatMmdb" value="mmdb"></option>
|
||
</select></label>
|
||
<label><span data-i18n="geoipMaxFileMiB"></span><input id="geoip-max-mib" min="1"
|
||
max="256" required="" type="number" /></label>
|
||
<label><span data-i18n="geoipIntervalHours"></span><input id="geoip-interval-hours"
|
||
min="0.0166667" max="744" step="any" required="" type="number" /></label>
|
||
</div>
|
||
<div class="field-grid" id="geoip-csv-fields">
|
||
<label><span data-i18n="geoipIpv4Url"></span><input id="geoip-ipv4-url"
|
||
maxlength="2048" required="" type="url" /></label>
|
||
<label><span data-i18n="geoipIpv4ChecksumUrl"></span><input
|
||
id="geoip-ipv4-checksum-url" maxlength="2048" type="url" /></label>
|
||
<label><span data-i18n="geoipIpv6Url"></span><input id="geoip-ipv6-url"
|
||
maxlength="2048" required="" type="url" /></label>
|
||
<label><span data-i18n="geoipIpv6ChecksumUrl"></span><input
|
||
id="geoip-ipv6-checksum-url" maxlength="2048" type="url" /></label>
|
||
</div>
|
||
<div class="field-grid" id="geoip-mmdb-fields" hidden="">
|
||
<label><span data-i18n="geoipMmdbUrl"></span><input id="geoip-mmdb-url"
|
||
maxlength="2048" type="url" /></label>
|
||
<label><span data-i18n="geoipMmdbChecksumUrl"></span><input
|
||
id="geoip-mmdb-checksum-url" maxlength="2048" type="url" /></label>
|
||
</div>
|
||
<p class="fine" data-i18n="geoipSourceHelp"></p>
|
||
<p class="fine" id="geoip-status" role="status"></p>
|
||
<div class="form-actions"><button class="primary" data-i18n="apply"
|
||
type="submit"></button><button class="outline" data-i18n="geoipUpdateNow"
|
||
id="geoip-update-now" type="button"></button></div>
|
||
<p class="fine" data-i18n="geoipUpdateNowHelp"></p>
|
||
</form>
|
||
</div>
|
||
<div aria-labelledby="settings-tab-prometheus" class="settings-page"
|
||
data-settings-page="prometheus" hidden="" id="settings-page-prometheus" role="tabpanel">
|
||
<form class="card edit-form" id="prometheus-form">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="prometheusTitle"></h2><span class="pill"
|
||
data-i18n="restartLabel"></span>
|
||
</div>
|
||
<p class="fine" data-i18n="prometheusHelp"></p><label class="check"><input
|
||
id="prom-enabled" type="checkbox" /><span
|
||
data-i18n="prometheusEnable"></span></label>
|
||
<div class="field-grid"><label><span data-i18n="prometheusListen"></span><input
|
||
id="prom-listen" maxlength="64" placeholder="127.0.0.1:9191"
|
||
required="" /></label><label><span data-i18n="prometheusPath"></span><input
|
||
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>
|
||
<label><span data-i18n="prometheusTokenEnv"></span><input autocomplete="off"
|
||
id="prom-token-env" maxlength="128"
|
||
placeholder="RUST_PROXY_METRICS_TOKEN" /></label>
|
||
<p class="fine" data-i18n="prometheusTokenHelp"></p>
|
||
<label class="check"><input id="prom-trusted-no-token" type="checkbox" /><span
|
||
data-i18n="prometheusTrustedNoToken"></span></label>
|
||
<p class="fine" data-i18n="prometheusTrustedNoTokenHelp"></p>
|
||
<h3 class="subheading" data-i18n="prometheusCollectors"></h3>
|
||
<div class="switch-grid">
|
||
<label class="check"><input checked="" id="prom-system" type="checkbox" /><span
|
||
data-i18n="systemDiagnostics"></span></label>
|
||
<label class="check"><input checked="" id="prom-vhosts" type="checkbox" /><span
|
||
data-i18n="vhosts"></span></label>
|
||
<label class="check"><input checked="" id="prom-traffic" type="checkbox" /><span
|
||
data-i18n="traffic"></span></label>
|
||
<label class="check"><input checked="" id="prom-backends" type="checkbox" /><span
|
||
data-i18n="vhBackendPool"></span></label>
|
||
<label class="check"><input checked="" id="prom-docker-vhosts"
|
||
type="checkbox" /><span data-i18n="docker-vhosts"></span></label>
|
||
<label class="check"><input checked="" id="prom-domains" type="checkbox" /><span
|
||
data-i18n="cacheDomains"></span></label>
|
||
<label class="check"><input checked="" id="prom-streams" type="checkbox" /><span
|
||
data-i18n="streams"></span></label>
|
||
<label class="check"><input checked="" id="prom-logging" type="checkbox" /><span
|
||
data-i18n="logging"></span></label>
|
||
</div>
|
||
<p class="fine" id="prom-status" role="status"></p>
|
||
<pre class="metrics-example" id="prom-scrape-example"></pre>
|
||
<div class="form-actions"><button class="primary" data-i18n="apply"
|
||
type="submit"></button><button class="quiet" data-i18n="prometheusPreview"
|
||
id="prom-preview" type="button"></button></div>
|
||
<pre class="metrics-preview" hidden="" id="prom-preview-output"></pre>
|
||
</form>
|
||
</div>
|
||
<div aria-labelledby="settings-tab-administration" class="settings-page"
|
||
data-settings-page="administration" hidden="" id="settings-page-administration"
|
||
role="tabpanel">
|
||
<form class="card edit-form" id="administration-form">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="administration">Administration panel</h2><span class="pill"
|
||
data-i18n="restartLabel">RESTART</span>
|
||
</div>
|
||
<p class="fine" data-i18n="administrationHelp">HTTPS allows the administration listener
|
||
to bind beyond loopback. Listener and certificate changes take effect after restart.
|
||
</p>
|
||
<label><span data-i18n="administrationListen">Listen address</span><input
|
||
id="admin-listen" maxlength="64" placeholder="127.0.0.1:9090"
|
||
required="" /></label>
|
||
<p class="fine" id="admin-listen-lock-note" hidden
|
||
data-i18n="administrationStartupLocked">This address is fixed by the process
|
||
startup/systemd configuration and cannot be changed here.</p>
|
||
<label class="check"><input id="admin-tls" type="checkbox" /><span
|
||
data-i18n="administrationHttps">Enable HTTPS for administration</span></label>
|
||
<label><span data-i18n="administrationCertificate">Certificate</span><select
|
||
id="admin-certificate"></select></label>
|
||
<label><span data-i18n="administrationPublicOrigin">Public panel HTTPS origin
|
||
(optional)</span><input id="admin-public-origin" maxlength="255"
|
||
placeholder="https://panel.example.com" type="url" spellcheck="false"
|
||
autocomplete="off" /></label>
|
||
<p class="fine" data-i18n="administrationPublicOriginHelp">Pins the browser-facing HTTPS
|
||
scheme, hostname and optional port for Origin/CSRF checks behind a trusted reverse
|
||
proxy; it is not only a DNS allowlist. The hostname is automatically allowed.
|
||
Restart required.</p>
|
||
<details id="admin-extra-hosts-details">
|
||
<summary data-i18n="administrationHostsAdvanced">Advanced: additional allowed DNS
|
||
hostnames</summary>
|
||
<label><span data-i18n="administrationHosts">Additional allowed DNS
|
||
hostnames</span><textarea id="admin-hosts" rows="4" spellcheck="false"
|
||
placeholder="other-admin.example.com"></textarea></label>
|
||
<p class="fine" data-i18n="administrationHostsHelp">Only extra hostnames belong
|
||
here. The hostname from the public panel URL is automatically included; without
|
||
a public URL this remains an independent strict allowlist.</p>
|
||
</details>
|
||
<label><span data-i18n="administrationSessionDays">Administrator session lifetime
|
||
(days)</span><input id="admin-session-days" min="7" max="30" step="1"
|
||
type="number" required="" /></label>
|
||
<p class="fine" data-i18n="administrationSessionDaysHelp">Browser sessions use an
|
||
absolute 7–30 day lifetime.</p>
|
||
<h3 class="subheading" data-i18n="panelCompressionTitle">Panel content compression</h3>
|
||
<div class="switch-grid">
|
||
<label class="check"><input id="admin-compression-zstd" type="checkbox"
|
||
checked /><span>Zstd</span></label>
|
||
<label class="check"><input id="admin-compression-br" type="checkbox"
|
||
checked /><span>Brotli (br)</span></label>
|
||
<label class="check"><input id="admin-compression-gzip" type="checkbox"
|
||
checked /><span>gzip</span></label>
|
||
</div>
|
||
<p class="fine" data-i18n="panelCompressionHelp">The browser negotiates the best
|
||
supported encoding (equal quality: Zstd → Brotli → gzip). Disable all to send
|
||
uncompressed panel assets. Applies immediately; a reverse proxy may forward or
|
||
re-encode the response.</p>
|
||
<p class="fine" id="admin-status" role="status"></p>
|
||
<div class="form-actions"><button class="primary" data-i18n="apply"
|
||
type="submit">Validate, save and apply</button></div>
|
||
</form>
|
||
</div>
|
||
<div aria-labelledby="settings-tab-system-info" class="settings-page"
|
||
data-settings-page="system-info" hidden="" id="settings-page-system-info" role="tabpanel">
|
||
<article class="card edit-form runtime-information-card">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="systemInformation">System information</h2>
|
||
<div class="system-info-heading-actions"><span class="pill">API</span><button
|
||
class="quiet" data-i18n="refreshNow" id="system-info-refresh"
|
||
type="button">Refresh now</button></div>
|
||
</div>
|
||
<p class="fine" data-i18n="systemInformationHelp">Read-only information from the
|
||
authenticated system-info API. Unavailable metrics are not estimated.</p>
|
||
<p class="fine" id="system-info-refresh-note" role="status"></p>
|
||
<h3 data-i18n="systemHostHeading">Operating system and hardware</h3>
|
||
<div class="runtime-information-grid">
|
||
<div><span class="small-label" data-i18n="runtimePlatform">Platform</span><code
|
||
id="runtime-platform">-</code></div>
|
||
<div><span class="small-label" data-i18n="systemOsVersion">Operating
|
||
system</span><code id="runtime-operating-system">-</code></div>
|
||
<div><span class="small-label" data-i18n="systemKernel">Kernel</span><code
|
||
id="runtime-kernel-version">-</code></div>
|
||
<div><span class="small-label"
|
||
data-i18n="systemArchitecture">Architecture</span><code
|
||
id="runtime-architecture">-</code></div>
|
||
<div><span class="small-label" data-i18n="systemLogicalCpus">Logical
|
||
CPUs</span><code id="runtime-logical-cpus">-</code></div>
|
||
<div><span class="small-label" data-i18n="systemCpuLoad">CPU usage</span><code
|
||
id="runtime-cpu-usage">-</code></div>
|
||
<div><span class="small-label" data-i18n="systemMemory">RAM used / total</span><code
|
||
id="runtime-system-memory">-</code></div>
|
||
<div><span class="small-label" data-i18n="systemUptime">System uptime</span><code
|
||
id="runtime-system-uptime">-</code></div>
|
||
</div>
|
||
<h3 data-i18n="systemProcessHeading">RustProxy process</h3>
|
||
<div class="runtime-information-grid">
|
||
<div><span class="small-label" data-i18n="systemAppVersion">RustProxy
|
||
version</span><code id="runtime-version">-</code></div>
|
||
<div><span class="small-label" data-i18n="systemProcessUptime">Process
|
||
uptime</span><code id="runtime-process-uptime">-</code></div>
|
||
<div><span class="small-label" data-i18n="systemProcessMemory">Process RAM
|
||
(RSS)</span><code id="runtime-process-memory">-</code></div>
|
||
<div><span class="small-label" data-i18n="systemProcessThreads">Process
|
||
threads</span><code id="runtime-process-threads">-</code></div>
|
||
<div><span class="small-label" data-i18n="systemOpenFds">Open file
|
||
descriptors</span><code id="runtime-open-fds">-</code></div>
|
||
<div><span class="small-label" data-i18n="statisticsSource">Statistics
|
||
source</span><code id="runtime-statistics-source">-</code></div>
|
||
</div>
|
||
</article>
|
||
<article class="card edit-form runtime-information-card">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="systemCertificateHeading">ACME / Python environment</h2>
|
||
</div>
|
||
<p class="fine" data-i18n="systemCertificateHelp">Versions refer to the interpreter and
|
||
tools actually selected for certificate management, not an arbitrary Python
|
||
installation.</p>
|
||
<div class="runtime-information-grid">
|
||
<div><span class="small-label" data-i18n="runtimeAcmeMode">ACME mode</span><code
|
||
id="runtime-acme-mode">-</code></div>
|
||
<div><span class="small-label" data-i18n="systemAcmeStatus">ACME status</span><code
|
||
id="runtime-acme-status">-</code></div>
|
||
<div><span class="small-label" data-i18n="runtimePython">Python</span><code
|
||
id="runtime-python">-</code></div>
|
||
<div><span class="small-label" data-i18n="runtimePip">pip</span><code
|
||
id="runtime-pip">-</code></div>
|
||
<div><span class="small-label" data-i18n="runtimeCertbot">Certbot</span><code
|
||
id="runtime-certbot">-</code></div>
|
||
<div><span class="small-label" data-i18n="certificateRuntimePath">Managed
|
||
environment</span><code id="runtime-acme-directory">-</code></div>
|
||
</div>
|
||
</article>
|
||
<article class="card edit-form runtime-information-card">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="systemPathsHeading">Data locations</h2>
|
||
</div>
|
||
<div class="runtime-information-grid">
|
||
<div><span class="small-label" data-i18n="dataDirectory">Data directory</span><code
|
||
id="runtime-data-directory">-</code></div>
|
||
<div><span class="small-label" data-i18n="databaseLocation">Database</span><code
|
||
id="runtime-database-path">-</code></div>
|
||
</div>
|
||
</article>
|
||
</div>
|
||
<div aria-labelledby="settings-tab-certificate-api" class="settings-page"
|
||
data-settings-page="certificate-api" hidden="" id="settings-page-certificate-api"
|
||
role="tabpanel">
|
||
<form class="card edit-form" id="certificate-api-form">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="certificateApiTitle">Certificate API</h2><span
|
||
class="pill live">SCOPED TOKENS</span>
|
||
</div>
|
||
<p class="fine" data-i18n="certificateApiHelp">Separate Key ID + Bearer Token
|
||
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
|
||
id="certificate-api-comment" maxlength="256"
|
||
placeholder="Certbot on edge-01" /></label>
|
||
<label><span data-i18n="certificateApiCertificateIds">Allowed certificate
|
||
IDs</span><textarea id="certificate-api-certificate-ids" maxlength="2048"
|
||
placeholder="shared-cert" required rows="3"></textarea></label>
|
||
<p class="fine" data-i18n="certificateApiCertificateIdsHelp">List one or more
|
||
certificate IDs separated by commas or new lines. The token can act only on these
|
||
IDs.</p>
|
||
<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 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"
|
||
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">
|
||
<label data-i18n="certificateApiOneTimeToken">Copy this token now</label>
|
||
<pre class="metrics-example" id="certificate-api-secret"></pre>
|
||
</div>
|
||
<div class="certificate-api-credentials" id="certificate-api-credentials"></div>
|
||
<p class="fine" data-i18n="certificateApiCurlHelp">External automation sends the Key ID
|
||
header and Bearer token over HTTPS (or loopback HTTP). Reload tokens use the
|
||
targeted
|
||
/api/certificates/{id}/reload endpoint; token access cannot trigger global reload.
|
||
</p>
|
||
<pre class="metrics-example" id="certificate-api-curl"></pre>
|
||
</form>
|
||
</div>
|
||
<div aria-labelledby="settings-tab-tls-policy" class="settings-page"
|
||
data-settings-page="tls-policy" hidden="" id="settings-page-tls-policy" role="tabpanel">
|
||
<form class="card edit-form" id="tls-policy-form">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="tlsPolicy">Global TLS policy</h2><span class="pill live"
|
||
data-i18n="hotApply">LIVE APPLY</span>
|
||
</div>
|
||
<p class="fine" data-i18n="tlsPolicyHelp">Global defaults for TLS frontends. Per-vhost
|
||
overrides can narrow versions/ciphers. HTTP/3 requires TLS 1.3.</p>
|
||
<div class="switch-grid"><label class="check"><input id="tls-policy-12"
|
||
type="checkbox" /><span>TLS 1.2</span></label><label class="check"><input
|
||
id="tls-policy-13" type="checkbox" /><span>TLS 1.3</span></label></div>
|
||
<label><span data-i18n="tlsCiphers">Cipher suites (one per line; empty = rustls
|
||
defaults)</span><textarea id="tls-policy-ciphers" rows="8"
|
||
spellcheck="false"></textarea></label>
|
||
<p class="fine tls-policy-effective" id="tls-policy-effective"></p>
|
||
<p class="fine" id="tls-policy-available"></p>
|
||
<div class="form-actions"><button class="primary" data-i18n="apply"
|
||
type="submit">Validate, save and apply</button></div>
|
||
</form>
|
||
</div>
|
||
<div aria-labelledby="settings-tab-manual-config" class="settings-page"
|
||
data-settings-page="manual-config" hidden="" id="settings-page-manual-config"
|
||
role="tabpanel">
|
||
<article class="card edit-form">
|
||
<div class="card-heading">
|
||
<div><span class="small-label">DESIRED STATE / SQLITE</span>
|
||
<h2 data-i18n="manualConfig">Manual config</h2>
|
||
</div>
|
||
<div class="manual-config-tools"><button class="quiet" data-i18n="configHistory"
|
||
id="config-history-open" type="button">Revision history</button><button
|
||
class="outline" data-i18n="export" id="export-config" type="button">Export
|
||
JSON</button></div>
|
||
</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"
|
||
rows="24" spellcheck="false"></textarea>
|
||
<div class="form-actions"><button class="primary" data-i18n="apply"
|
||
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"
|
||
data-settings-page="security" hidden="" id="settings-page-security" role="tabpanel">
|
||
<div class="security-page-heading">
|
||
<div><span class="small-label" data-i18n="security">Security</span>
|
||
<h2 data-i18n="securityTitle">Access stays deliberate.</h2>
|
||
<p class="fine" data-i18n="securityDescription">Sessions, credentials and host
|
||
access policies in one place.</p>
|
||
</div>
|
||
<button class="outline" data-i18n="securityAuditOpen" id="security-audit-open"
|
||
type="button">Administration audit</button>
|
||
</div>
|
||
<div class="security-stack">
|
||
<article class="card security-account-card">
|
||
<div class="card-heading">
|
||
<div><span class="small-label" data-i18n="currentSession">CURRENT SESSION</span>
|
||
<h2 class="session-user" id="session-user"></h2>
|
||
</div>
|
||
<button class="outline" data-i18n="revokeAll" id="revoke-sessions"
|
||
type="button">Sign out all sessions</button>
|
||
</div>
|
||
<p class="fine" id="session-expiry"></p>
|
||
<p class="fine security-note" data-i18n="recoveryHelp">Lost access? Run rust-proxy
|
||
admin reset-password locally. Existing sessions will be revoked.</p>
|
||
<details class="security-editor" id="security-password-editor">
|
||
<summary><span data-i18n="changePassword">Change password</span><small
|
||
class="fine" data-i18n="securityPasswordHint">Requires the current
|
||
password and signs out all sessions.</small></summary>
|
||
<form class="edit-form security-inline-form" id="password-form">
|
||
<label data-i18n="currentPassword" for="current-password">Current
|
||
password</label><input autocomplete="current-password"
|
||
id="current-password" maxlength="128" required="" type="password" />
|
||
<label data-i18n="newPassword" for="new-password">New password (12-128 UTF-8
|
||
bytes)</label><input autocomplete="new-password" id="new-password"
|
||
maxlength="128" minlength="12" required="" type="password" />
|
||
<label data-i18n="confirmPassword" for="new-password-confirm">Confirm
|
||
password</label><input autocomplete="new-password"
|
||
id="new-password-confirm" maxlength="128" minlength="12" required=""
|
||
type="password" />
|
||
<div class="form-actions"><button class="primary" data-i18n="savePassword"
|
||
type="submit">Change and sign out</button></div>
|
||
</form>
|
||
</details>
|
||
</article>
|
||
<article class="card security-policy-card">
|
||
<div class="card-heading">
|
||
<div><span class="small-label" data-i18n="securityAccessControl">ACCESS
|
||
CONTROL</span>
|
||
<h2 data-i18n="hostPolicies">Host access policies</h2>
|
||
<p class="fine" data-i18n="securityPoliciesHint">Keep the policy list
|
||
visible; open the editor only when adding or changing a rule set.</p>
|
||
</div>
|
||
<span class="pill" id="security-policy-count">0</span>
|
||
</div>
|
||
<div id="host-policy-list"></div>
|
||
<details class="security-editor" id="security-policy-editor">
|
||
<summary><span data-i18n="editHostPolicy">Add / replace host policy</span><small
|
||
class="fine" data-i18n="securityPolicyEditorHint">Hostname scope,
|
||
default action and CIDR rules.</small></summary>
|
||
<form class="edit-form security-inline-form" id="host-policy-form">
|
||
<label data-i18n="hostnames" for="policy-hosts">Hostnames and aliases,
|
||
comma-separated</label><input id="policy-hosts"
|
||
placeholder="app.localhost" required="" />
|
||
<label data-i18n="aclDefault" for="policy-default">Default
|
||
decision</label><select id="policy-default">
|
||
<option data-i18n="deny" value="deny">Deny</option>
|
||
<option data-i18n="allow" value="allow">Allow</option>
|
||
</select>
|
||
<label data-i18n="aclRules" for="policy-rules">Rules: allow/deny followed by
|
||
CIDR, one per line</label><textarea id="policy-rules"
|
||
placeholder="allow 127.0.0.1/32" rows="4" spellcheck="false"></textarea>
|
||
<p class="fine" data-i18n="aclHelp">Deny wins. Route allow never overrides
|
||
host deny. The socket client IP is used.</p>
|
||
<div class="form-actions"><button class="primary" data-i18n="savePolicy"
|
||
type="submit">Apply host policy</button></div>
|
||
</form>
|
||
</details>
|
||
</article>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</section>
|
||
<footer class="page-footer"><span>RustProxy <b>__RUSTPROXY_VERSION__</b></span><span
|
||
id="footer-status">API - /
|
||
dropped: 0</span></footer>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
<dialog aria-labelledby="cache-invalidation-title" class="vhost-dialog cache-operation-dialog"
|
||
id="cache-invalidation-dialog">
|
||
<article class="card">
|
||
<div class="card-heading cache-dialog-heading">
|
||
<div><span class="small-label">CACHE</span>
|
||
<h2 data-i18n="cacheInvalidationTitle" id="cache-invalidation-title">Cache purge / ban</h2>
|
||
</div><button class="quiet" data-i18n="close" id="cache-invalidation-close" type="button">Close</button>
|
||
</div>
|
||
<form class="edit-form" id="cache-invalidation-form">
|
||
<div class="field-grid">
|
||
<div><label data-i18n="cacheInvalidationOperation"
|
||
for="cache-invalidation-operation">Operation</label><select
|
||
id="cache-invalidation-operation">
|
||
<option value="purge">PURGE</option>
|
||
<option value="ban">BAN</option>
|
||
</select></div>
|
||
<div><label data-i18n="cacheInvalidationSelector"
|
||
for="cache-invalidation-selector">Selector</label><select id="cache-invalidation-selector">
|
||
<option value="exact">Exact URL</option>
|
||
<option value="prefix">URL prefix</option>
|
||
<option value="tag">Cache-Tag</option>
|
||
<option value="regex">Regex</option>
|
||
<option value="cache_key">Cache key</option>
|
||
</select></div>
|
||
<div><label data-i18n="cacheInvalidationValue" for="cache-invalidation-value">Value</label><input
|
||
id="cache-invalidation-value" maxlength="8192"
|
||
placeholder="/assets/app___RUSTPROXY_ASSET_ID__.js" required="" />
|
||
</div>
|
||
<div><label data-i18n="cacheInvalidationHost" for="cache-invalidation-host">Host
|
||
(optional)</label><input id="cache-invalidation-host" maxlength="253"
|
||
placeholder="example.com" /></div>
|
||
<div><label data-i18n="cacheBanTtl" for="cache-ban-ttl">BAN TTL (seconds)</label><input
|
||
id="cache-ban-ttl" min="1" max="604800" type="number" value="3600" disabled="" /></div>
|
||
</div>
|
||
<p class="fine" data-i18n="cacheInvalidationHelp">The selected vhost limits the operation. Exact and
|
||
prefix values include the query string. Regex and root-prefix / operations require an additional
|
||
confirmation.</p>
|
||
<p class="operation-message" id="cache-invalidation-status" role="alert" aria-live="polite"></p>
|
||
<div class="form-actions"><button class="primary" data-i18n="cacheRunInvalidation" type="submit">Run
|
||
operation</button></div>
|
||
</form>
|
||
</article>
|
||
</dialog>
|
||
<dialog aria-labelledby="cache-bans-title" class="vhost-dialog cache-operation-dialog" id="cache-bans-dialog">
|
||
<article class="card">
|
||
<div class="card-heading cache-dialog-heading">
|
||
<div><span class="small-label">CACHE</span>
|
||
<h2 data-i18n="cacheActiveBans" id="cache-bans-title">Active BAN rules</h2>
|
||
</div><button class="quiet" data-i18n="close" id="cache-bans-close" type="button">Close</button>
|
||
</div>
|
||
<p class="operation-message" id="cache-bans-status" role="alert" aria-live="polite"></p>
|
||
<div class="table-scroll">
|
||
<table class="cache-table">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th data-i18n="cacheInvalidationSelector">Selector</th>
|
||
<th data-i18n="cacheInvalidationValue">Value</th>
|
||
<th data-i18n="cacheInvalidationHost">Host</th>
|
||
<th data-i18n="vhVhost">Vhost</th>
|
||
<th data-i18n="cacheBanExpires">Expires</th>
|
||
<th data-i18n="actions">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="cache-ban-rows"></tbody>
|
||
</table>
|
||
</div>
|
||
<p class="empty" data-i18n="cacheNoBans" id="cache-bans-empty">No active BAN rules.</p>
|
||
</article>
|
||
</dialog>
|
||
<dialog aria-labelledby="cache-cleanup-title" class="vhost-dialog" id="cache-cleanup-dialog">
|
||
<form class="card edit-form" id="cache-cleanup-form">
|
||
<div class="card-heading">
|
||
<div><span class="small-label" data-i18n="cacheColdTitle">Persistent cold cache</span>
|
||
<h2 id="cache-cleanup-title" data-i18n="cacheColdCleanup">Clean cold cache</h2>
|
||
</div>
|
||
<button class="quiet" data-i18n="close" id="cache-cleanup-close" type="button">Close</button>
|
||
</div>
|
||
<p class="fine" data-i18n="cacheColdCleanupHelp">Runs asynchronously and uses the same eviction mechanism as
|
||
automatic cold-cache GC.</p>
|
||
<div class="field-grid">
|
||
<div><label data-i18n="cacheColdUnusedSeconds" for="cache-cleanup-unused">Unused for at least (seconds,
|
||
optional)</label><input id="cache-cleanup-unused" min="1" max="31536000" type="number" /></div>
|
||
<div><label data-i18n="cacheColdOldestCount" for="cache-cleanup-oldest">Remove up to oldest
|
||
objects</label><input id="cache-cleanup-oldest" min="1" max="10000" type="number" value="100" />
|
||
</div>
|
||
</div>
|
||
<label class="check"><input id="cache-cleanup-hits-zero" type="checkbox" /><span
|
||
data-i18n="cacheColdHitsZero">Only objects with hits = 0</span></label>
|
||
<p class="fine" id="cache-cleanup-status" role="status"></p>
|
||
<div class="form-actions"><button class="primary" data-i18n="cacheColdCleanupStart" type="submit">Start
|
||
cleanup</button><button class="quiet" data-i18n="cancelAction" id="cache-cleanup-cancel"
|
||
type="button">Cancel</button></div>
|
||
</form>
|
||
</dialog>
|
||
<dialog aria-labelledby="overview-presets-title" aria-modal="true" class="audit-dialog overview-presets-dialog"
|
||
id="overview-presets-dialog">
|
||
<div class="window-heading">
|
||
<h2 id="overview-presets-title" data-i18n="dashboardPresetsTitle">Dashboard presets</h2>
|
||
<button type="button" class="outline" id="overview-presets-close" data-i18n="close">Close</button>
|
||
</div>
|
||
<div class="overview-presets-content">
|
||
<p class="fine" data-i18n="dashboardPresetsHelp">Save named arrangements and restore them from any browser.
|
||
Use Unlock layout on Overview to move, resize or hide cards before saving a preset.</p>
|
||
<div class="overview-presets-create">
|
||
<label for="overview-presets-name" data-i18n="dashboardPresetsName">Preset name</label>
|
||
<div class="overview-presets-create-controls">
|
||
<input id="overview-presets-name" type="text" maxlength="64" autocomplete="off"
|
||
data-i18n-placeholder="dashboardPresetsNamePlaceholder" placeholder="e.g. Monitoring" />
|
||
<button type="button" class="primary" id="overview-presets-save"
|
||
data-i18n="dashboardPresetsSave">Save current view</button>
|
||
</div>
|
||
</div>
|
||
<div class="overview-presets-list" id="overview-presets-list" role="list"></div>
|
||
<p class="fine" data-i18n="dashboardPresetsRenameHint">To rename a preset, type its new name above
|
||
and choose Rename next to the saved preset.</p>
|
||
</div>
|
||
<div class="overview-layout-footer">
|
||
<p id="overview-presets-message" role="status" aria-live="polite"></p>
|
||
<button class="outline" id="overview-presets-factory" type="button"
|
||
data-i18n="dashboardPresetsFactory">Restore factory layout</button>
|
||
</div>
|
||
</dialog>
|
||
<dialog aria-labelledby="tunnels-dialog-title" class="audit-dialog" id="tunnels-dialog">
|
||
<div class="window-heading">
|
||
<div><span class="small-label">WEBSOCKET / CONNECT</span>
|
||
<h2 data-i18n="tunnelsTitle" id="tunnels-dialog-title">Active WebSocket / tunnels</h2>
|
||
</div>
|
||
<div class="window-tools"><button class="quiet" data-i18n="tunnelsReload" id="tunnels-reload"
|
||
type="button">Reload</button><button class="outline" data-i18n="close" id="tunnels-close"
|
||
type="button">Close</button></div>
|
||
</div>
|
||
<article class="card audit-dialog-card">
|
||
<div class="field-grid tunnel-controls">
|
||
<div><label data-i18n="tunnelKillReason" for="tunnel-kill-reason">Kill reason</label><input
|
||
id="tunnel-kill-reason" maxlength="256" data-i18n-placeholder="tunnelKillReasonPlaceholder"
|
||
placeholder="Administrative close" /></div>
|
||
</div>
|
||
<p class="operation-message" id="tunnels-status" role="alert" aria-live="polite"></p>
|
||
<div class="table-scroll">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th data-i18n="tunnelId">ID</th>
|
||
<th data-i18n="tunnelClient">Client</th>
|
||
<th data-i18n="tunnelHost">Host</th>
|
||
<th data-i18n="tunnelUpstream">Upstream</th>
|
||
<th data-i18n="tunnelProtocol">Protocol</th>
|
||
<th data-i18n="tunnelDuration">Duration</th>
|
||
<th data-i18n="tunnelRx">RX</th>
|
||
<th data-i18n="tunnelTx">TX</th>
|
||
<th data-i18n="actions">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tunnels-rows"></tbody>
|
||
</table>
|
||
</div>
|
||
<p class="empty" data-i18n="tunnelsEmpty" id="tunnels-empty">No active tunnels.</p>
|
||
</article>
|
||
</dialog>
|
||
<dialog aria-labelledby="config-history-title" class="config-history-dialog" id="config-history-dialog">
|
||
<div class="window-heading">
|
||
<div><span class="small-label" data-i18n="configHistoryRetention">SQLITE / LAST 512 REVISIONS</span>
|
||
<h2 data-i18n="configHistory" id="config-history-title">Revision history</h2>
|
||
</div>
|
||
<div class="window-tools"><button class="quiet" data-i18n="reload" id="config-history-reload"
|
||
type="button">Reload</button><button class="outline" data-i18n="close" id="config-history-close"
|
||
type="button">Close</button></div>
|
||
</div>
|
||
<div class="config-history-layout">
|
||
<section class="card config-history-browser">
|
||
<p class="fine" data-i18n="configHistoryHelp">Select a marked day to inspect the saved configuration
|
||
revisions and their changes.</p>
|
||
<p class="fine" id="config-history-summary" role="status"></p>
|
||
<div class="config-history-months" id="config-history-calendar"></div>
|
||
</section>
|
||
<aside class="card config-history-detail">
|
||
<div class="config-history-detail-head">
|
||
<div><span class="small-label" data-i18n="configHistoryChanges">CHANGES</span>
|
||
<h3 id="config-history-detail-title">-</h3>
|
||
</div>
|
||
<div class="config-history-detail-actions">
|
||
<span class="fine" id="config-history-detail-meta"></span>
|
||
<button class="outline" data-i18n="configHistoryRestoreAction" id="config-history-restore"
|
||
type="button" hidden disabled>Restore revision</button>
|
||
<button class="quiet" data-i18n="configHistoryFullCompare" id="config-history-compare-open"
|
||
type="button" disabled>Full JSON comparison</button>
|
||
</div>
|
||
</div>
|
||
<div class="config-history-revisions" id="config-history-revisions"></div>
|
||
<div class="config-history-diff" id="config-history-diff">
|
||
<p class="empty" data-i18n="configHistorySelectDay">Select a day.</p>
|
||
</div>
|
||
</aside>
|
||
</div>
|
||
</dialog>
|
||
<dialog aria-labelledby="config-json-compare-title" class="config-history-json-dialog"
|
||
id="config-history-json-dialog">
|
||
<div class="window-heading">
|
||
<h2 data-i18n="configHistoryFullCompare" id="config-json-compare-title">Full JSON comparison</h2>
|
||
<div class="window-tools">
|
||
<button class="outline" data-i18n="close" id="config-history-json-close" type="button">Close</button>
|
||
</div>
|
||
</div>
|
||
<div class="config-history-json-columns">
|
||
<section>
|
||
<h3 data-i18n="configHistoryBefore">Before</h3>
|
||
<pre id="config-history-json-before"></pre>
|
||
</section>
|
||
<section>
|
||
<h3 data-i18n="configHistoryAfter">After</h3>
|
||
<pre id="config-history-json-after"></pre>
|
||
</section>
|
||
</div>
|
||
</dialog>
|
||
<dialog aria-labelledby="audit-dialog-title" class="audit-dialog" id="audit-dialog">
|
||
<div class="window-heading">
|
||
<div><span class="small-label" data-i18n="auditRetention">SQLITE / LAST 100 / RETAINED 10,000</span>
|
||
<h2 data-i18n="audit" id="audit-dialog-title">Administration audit</h2>
|
||
</div>
|
||
<div class="window-tools"><button class="quiet" data-i18n="reloadAudit" id="reload-audit"
|
||
type="button">Reload</button><button class="outline" data-i18n="close" id="audit-dialog-close"
|
||
type="button">Close</button></div>
|
||
</div>
|
||
<article class="card audit-dialog-card">
|
||
<p class="fine" data-i18n="securityAuditHint">Recent administration changes are loaded only when this window
|
||
is opened.</p>
|
||
<div class="table-scroll">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th data-i18n="time">Time</th>
|
||
<th data-i18n="actor">Actor</th>
|
||
<th data-i18n="action">Action</th>
|
||
<th data-i18n="detail">Detail</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="audit-rows"></tbody>
|
||
</table>
|
||
</div>
|
||
<p class="empty" data-i18n="noAudit" id="empty-audit">No audit entries.</p>
|
||
</article>
|
||
</dialog>
|
||
<dialog aria-labelledby="log-window-title" class="logs-dialog" id="logs-dialog">
|
||
<div class="window-heading">
|
||
<h2 data-i18n="liveLogs" id="log-window-title">Live access logs</h2><span class="fine"
|
||
id="log-count"></span>
|
||
<div class="window-tools"><button class="quiet" data-i18n="logPause" id="logs-pause"
|
||
type="button">Pause</button><button class="quiet" data-i18n="logPin" id="logs-pin"
|
||
type="button">Dock</button><button class="outline" data-i18n="close" id="logs-close"
|
||
type="button">Close</button></div>
|
||
</div>
|
||
<article class="card logs-card">
|
||
<div class="card-heading">
|
||
<div><span class="small-label" data-i18n="activity">ACTIVITY</span>
|
||
<h2 data-i18n="accessLog">Access log</h2>
|
||
</div>
|
||
<div class="log-tools"><select aria-label="Vhost" data-i18n-aria="logVhost" id="log-vhost">
|
||
<option data-i18n="logAllVhosts" value="">All vhosts</option>
|
||
</select><select aria-label="Method" id="log-method">
|
||
<option data-i18n="logAllMethods" value="">All methods</option>
|
||
<option>GET</option>
|
||
<option>POST</option>
|
||
<option>HEAD</option>
|
||
<option>PUT</option>
|
||
<option>PATCH</option>
|
||
<option>DELETE</option>
|
||
<option>OPTIONS</option>
|
||
</select><select aria-label="HTTP status" id="log-status">
|
||
<option data-i18n="allResults" value="">All results</option>
|
||
<option value="2">2xx</option>
|
||
<option value="3">3xx</option>
|
||
<option value="4">4xx</option>
|
||
<option value="5">5xx</option>
|
||
</select><input aria-label="Filter host or path" data-i18n-aria="filterLogs"
|
||
data-i18n-placeholder="filterLogs" id="log-filter" placeholder="Filter host or path"
|
||
type="search" /><select aria-label="Filter result" data-i18n-aria="filterResult"
|
||
id="log-outcome">
|
||
<option data-i18n="allResults" value="all">All results</option>
|
||
<option value="HIT">HIT</option>
|
||
<option value="MISS">MISS</option>
|
||
<option value="BYPASS">BYPASS</option>
|
||
<option data-i18n="errors" value="errors">Errors</option>
|
||
</select>
|
||
<details class="log-column-picker">
|
||
<summary data-i18n="logColumns">Columns</summary>
|
||
<div class="log-column-options" id="log-column-options"></div>
|
||
</details><button class="quiet" data-i18n="logExpandBuffer" id="logs-load-history"
|
||
type="button">Increase
|
||
buffer</button><button class="quiet" data-i18n="export" id="export-logs" type="button">Export
|
||
JSON</button>
|
||
</div>
|
||
</div>
|
||
<div class="table-scroll">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th data-i18n="time" data-log-column="time">Time</th>
|
||
<th data-i18n="request" data-log-column="request">Request</th>
|
||
<th data-log-column="host">Host</th>
|
||
<th data-i18n="sourceIp" data-log-column="source_ip">Source IP</th>
|
||
<th data-i18n="vhVhost" data-log-column="vhost">Vhost</th>
|
||
<th data-i18n="backend" data-log-column="backend">Backend</th>
|
||
<th data-i18n="status" data-log-column="status">Status</th>
|
||
<th data-i18n="duration" data-log-column="duration">Duration</th>
|
||
<th data-log-column="cache">Cache</th>
|
||
<th data-i18n="outcome" data-log-column="outcome">Outcome</th>
|
||
<th data-i18n="logTransport" data-log-column="transport">Transport</th>
|
||
<th data-i18n="logWebSocket" data-log-column="websocket">WebSocket</th>
|
||
<th data-i18n="logUpstreamError" data-log-column="upstream_error">Upstream error</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="logs"></tbody>
|
||
</table>
|
||
</div>
|
||
<p class="empty" data-i18n="noTraffic" id="empty-logs">No traffic yet. Open the proxy to make your first
|
||
request.</p>
|
||
</article>
|
||
<p class="fine log-footnote" data-i18n="logBufferHint">Live view starts with the latest 256 completed exchanges.
|
||
Increase buffer to keep accumulating live entries up to 4096 until the log window is closed.</p>
|
||
</dialog>
|
||
<dialog aria-labelledby="web-service-editor-title" class="vhost-dialog" id="web-service-dialog">
|
||
<form class="card vh-form" id="web-service-form" novalidate="">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="webServiceEditor" id="web-service-editor-title">Web Service configuration</h2>
|
||
<span class="pill" id="web-service-editing">NEW</span>
|
||
<button class="quiet" data-i18n="close" id="web-service-close" type="button">Close</button>
|
||
</div>
|
||
<nav aria-label="Web Service configuration" class="vh-tabs" id="web-service-tabs"></nav>
|
||
<div class="web-service-pages">
|
||
<div class="vh-grid web-service-page" data-web-service-page="general">
|
||
<fieldset class="vh-group vh-wide">
|
||
<legend data-i18n="webServicePresetTitle">Configuration preset</legend>
|
||
<div class="vh-grid">
|
||
<label><span data-i18n="webServicePreset">Preset</span><select id="web-service-preset">
|
||
<option data-i18n="webServicePresetCustom" value="custom">Custom</option>
|
||
<option data-i18n="webServicePresetStatic" value="static">Static site</option>
|
||
<option data-i18n="webServicePresetSpa" value="spa">SPA</option>
|
||
<option data-i18n="webServicePresetPhpGeneric" value="php_generic">PHP generic
|
||
</option>
|
||
<option value="wordpress">WordPress</option>
|
||
<option value="drupal">Drupal</option>
|
||
<option value="laravel">Laravel</option>
|
||
<option value="symfony">Symfony</option>
|
||
<option value="joomla">Joomla</option>
|
||
<option value="prestashop">PrestaShop</option>
|
||
<option data-i18n="webServicePresetDownloads" value="downloads">Downloads / file
|
||
repository</option>
|
||
</select></label>
|
||
<div class="vh-field"><span class="small-label" data-i18n="webServicePresetAction">Preset
|
||
action</span><button class="outline" data-i18n="webServicePresetApply"
|
||
id="web-service-preset-apply" type="button">Apply preset</button></div>
|
||
<label class="check"><input id="web-service-hardening-attacks" type="checkbox" /><span
|
||
data-i18n="webServiceHardeningAttacks">Block common attack probes</span></label>
|
||
<label class="check"><input id="web-service-hardening-headers" type="checkbox" /><span
|
||
data-i18n="webServiceHardeningHeaders">Strip unnecessary fingerprint
|
||
headers</span></label>
|
||
<p class="fine vh-wide" data-i18n="webServicePresetHelp" id="web-service-preset-summary">
|
||
Presets fill normal Web Service settings and remain fully editable. Document root,
|
||
service ID/name and PHP-FPM endpoint are not replaced.</p>
|
||
</div>
|
||
</fieldset>
|
||
<label><span data-i18n="webServiceId">Service ID</span><input id="web-service-id" maxlength="64"
|
||
pattern="[A-Za-z0-9_-]{1,64}" required="" /></label>
|
||
<label><span data-i18n="webServiceName">Name</span><input id="web-service-name" maxlength="96"
|
||
required="" /></label>
|
||
<label class="vh-wide"><span data-i18n="webServiceRoot">Document root</span><input
|
||
id="web-service-root" maxlength="4096" placeholder="/var/www/main" required="" /></label>
|
||
<label class="vh-wide"><span data-i18n="webServiceIndex">Index files</span><textarea
|
||
id="web-service-index" maxlength="4096" rows="5" spellcheck="false"></textarea><small
|
||
class="fine" data-i18n="webServiceIndexHelp">Optional ordered index list. index.php may be
|
||
used when PHP-FPM is enabled.</small></label>
|
||
<label class="check"><input id="web-service-listing" type="checkbox" /><span
|
||
data-i18n="webServiceDirectoryListing">Directory listing</span></label>
|
||
</div>
|
||
<div class="vh-grid web-service-page" data-web-service-page="routing" hidden>
|
||
<label class="vh-wide"><span data-i18n="webServiceTryFiles">try_files</span><textarea
|
||
id="web-service-try-files" maxlength="8192" rows="7" spellcheck="false"
|
||
placeholder="$uri $uri/ /index.html"></textarea><small class="fine"
|
||
data-i18n="webServiceTryFilesHelp">One candidate per line. Supports $uri, $query_string and
|
||
a final =404/other error status.</small></label>
|
||
<label class="vh-wide"><span data-i18n="webServiceRewrites">Rewrite rules</span><textarea
|
||
id="web-service-rewrites" maxlength="32768" rows="8" spellcheck="false"
|
||
placeholder="^/old/(.*)$ => 301:/new/$1 ^/app$ => /index.html"></textarea><small
|
||
class="fine" data-i18n="webServiceRewritesHelp">One regex rule per line. Use pattern =>
|
||
/target for an internal rewrite or pattern => 301:/target for a redirect.</small></label>
|
||
</div>
|
||
<div class="vh-grid web-service-page" data-web-service-page="htaccess" hidden>
|
||
<label class="check"><input id="web-service-htaccess-enabled" type="checkbox" /><span
|
||
data-i18n="webServiceHtaccessEnabled">Enable .htaccess compatibility</span></label>
|
||
<label><span data-i18n="webServiceHtaccessFilename">Configuration file</span><input
|
||
id="web-service-htaccess-filename" maxlength="64" value=".htaccess" /></label>
|
||
<label class="check"><input id="web-service-htaccess-recursive" type="checkbox" checked /><span
|
||
data-i18n="webServiceHtaccessRecursive">Scan subdirectories</span></label>
|
||
<label class="check"><input id="web-service-htaccess-strict" type="checkbox" /><span
|
||
data-i18n="webServiceHtaccessStrict">Strict unsupported-directive mode</span></label>
|
||
<div class="vh-wide"><button class="outline" id="web-service-htaccess-help"
|
||
data-i18n="webServiceHtaccessDocs" type="button">Compatibility documentation</button></div>
|
||
<p class="fine vh-wide" data-i18n="webServiceHtaccessHelp">RustProxy compiles a bounded
|
||
Apache-compatible subset when the Web Service is applied. Files are not parsed on every request;
|
||
apply the Web Service again after editing .htaccess on disk.</p>
|
||
</div>
|
||
<div class="vh-grid web-service-page" data-web-service-page="mime" hidden>
|
||
<label class="vh-wide"><span data-i18n="webServiceMimeOverrides">MIME overrides</span><textarea
|
||
id="web-service-mime-types" maxlength="16384" rows="8" spellcheck="false"
|
||
placeholder="wasm = application/wasm webp = image/webp"></textarea><small class="fine"
|
||
data-i18n="webServiceMimeHelp">Optional extension-to-Content-Type overrides, one per line.
|
||
Built-in mappings are used for all other known extensions.</small></label>
|
||
<label><span data-i18n="webServiceDefaultMime">Default MIME type</span><input
|
||
id="web-service-default-mime" maxlength="256" value="application/octet-stream"
|
||
required="" /></label>
|
||
</div>
|
||
<div class="vh-grid web-service-page" data-web-service-page="cache" hidden>
|
||
<label><span data-i18n="webServiceCacheControl">Default Cache-Control</span><input
|
||
id="web-service-cache-control" maxlength="1024"
|
||
placeholder="public, max-age=3600" /></label>
|
||
<label class="vh-wide"><span data-i18n="webServiceCacheRules">Cache-Control rules</span><textarea
|
||
id="web-service-cache-rules" maxlength="32768" rows="7" spellcheck="false"
|
||
placeholder="ext:html = no-cache mime:image/ = public, max-age=604800"></textarea><small
|
||
class="fine" data-i18n="webServiceCacheRulesHelp">Specific rules are evaluated in order
|
||
before the default. Match ext:css or mime:image/.</small></label>
|
||
<label class="vh-wide"><span data-i18n="webServiceResponseHeaders">Response headers</span><textarea
|
||
id="web-service-response-headers" maxlength="32768" rows="7" spellcheck="false"
|
||
placeholder="X-Content-Type-Options = nosniff"></textarea><small class="fine"
|
||
data-i18n="webServiceResponseHeadersHelp">Fixed headers for this service. Vhost/location
|
||
response-header rules are applied later and can override them.</small></label>
|
||
<p class="fine vh-wide" data-i18n="webServiceCacheHelp">With vhost cache enabled, eligible Web
|
||
Server responses also use the RustProxy object cache. Private, cookie-bearing and no-store
|
||
responses are never stored.</p>
|
||
</div>
|
||
<div class="vh-grid web-service-page" data-web-service-page="delivery" hidden>
|
||
<label class="vh-wide"><span data-i18n="webServiceSendfile">Linux sendfile (zero-copy)</span>
|
||
<select id="web-service-sendfile">
|
||
<option value="off" data-i18n="notEnabled">Disabled</option>
|
||
<option value="auto" data-i18n="webServiceSendfileAuto">Auto (eligible plain HTTP/1.1)</option>
|
||
</select><small class="fine" data-i18n="webServiceSendfileHelp">Opt-in for static GET files at least 64 KiB on Linux HTTP/1.1 without TLS, cache, compression or transform. Other requests use safe streaming; switching this setting never clears the cache.</small></label>
|
||
<label><span data-i18n="webServiceCompression">Dynamic compression</span><select
|
||
id="web-service-compression">
|
||
<option value="" data-i18n="inherit">Inherit</option>
|
||
<option value="on" data-i18n="enabled">Enabled</option>
|
||
<option value="off" data-i18n="notEnabled">Disabled</option>
|
||
</select><small class="fine" data-i18n="webServiceCompressionHelp">Inherits the vhost/global
|
||
policy by default. Codecs can be filtered per service; globally disabled codecs stay
|
||
unavailable.</small></label>
|
||
<div class="vh-wide vh-grid" id="web-service-compression-options">
|
||
<label><span data-i18n="compressionZstd">Zstandard (zstd)</span><select
|
||
id="web-service-compression-zstd">
|
||
<option value="" data-i18n="inherit">Inherit</option>
|
||
<option value="on" data-i18n="enabled">Enabled</option>
|
||
<option value="off" data-i18n="notEnabled">Disabled</option>
|
||
</select></label>
|
||
<label><span data-i18n="compressionBr">Brotli (br)</span><select
|
||
id="web-service-compression-br">
|
||
<option value="" data-i18n="inherit">Inherit</option>
|
||
<option value="on" data-i18n="enabled">Enabled</option>
|
||
<option value="off" data-i18n="notEnabled">Disabled</option>
|
||
</select></label>
|
||
<label><span data-i18n="compressionGzip">gzip</span><select id="web-service-compression-gzip">
|
||
<option value="" data-i18n="inherit">Inherit</option>
|
||
<option value="on" data-i18n="enabled">Enabled</option>
|
||
<option value="off" data-i18n="notEnabled">Disabled</option>
|
||
</select></label>
|
||
<label><span data-i18n="compressionDeflate">deflate</span><select
|
||
id="web-service-compression-deflate">
|
||
<option value="" data-i18n="inherit">Inherit</option>
|
||
<option value="on" data-i18n="enabled">Enabled</option>
|
||
<option value="off" data-i18n="notEnabled">Disabled</option>
|
||
</select></label>
|
||
</div>
|
||
<label class="check"><input id="web-service-precompressed" type="checkbox" /><span
|
||
data-i18n="webServicePrecompressed">Serve precompressed sidecars</span></label>
|
||
<label class="check"><input id="web-service-precompressed-br" type="checkbox" /><span
|
||
data-i18n="webServicePrecompressedBr">Brotli (.br)</span></label>
|
||
<label class="check"><input id="web-service-precompressed-gzip" type="checkbox" /><span
|
||
data-i18n="webServicePrecompressedGzip">Gzip (.gz)</span></label>
|
||
<label class="vh-wide"><span data-i18n="webServiceDownloadExtensions">Download
|
||
extensions</span><textarea id="web-service-download-extensions" maxlength="4096" rows="5"
|
||
spellcheck="false" placeholder="zip pdf tar"></textarea><small class="fine"
|
||
data-i18n="webServiceDownloadExtensionsHelp">Files with these extensions receive
|
||
Content-Disposition: attachment.</small></label>
|
||
<p class="fine vh-wide" data-i18n="webServiceRangeHelp">Static and precompressed representations
|
||
keep validators, single-range requests, 206/416 and streaming delivery.</p>
|
||
</div>
|
||
<div class="vh-grid web-service-page" data-web-service-page="php" hidden>
|
||
<label class="vh-wide"><span data-i18n="phpProfileSelect">PHP-FPM configuration source</span>
|
||
<select id="web-service-php-profile">
|
||
<option value="" data-i18n="phpProfileLocal">Local configuration</option>
|
||
</select>
|
||
<small class="fine" data-i18n="phpProfileHelp">Select a shared profile or edit PHP-FPM locally.
|
||
Profile changes apply to all attached Web Services.</small>
|
||
</label>
|
||
<div class="vh-wide"><button type="button" class="outline" id="web-service-php-profiles"
|
||
data-i18n="phpProfileManage">Manage PHP-FPM profiles</button></div>
|
||
<div class="vh-wide php-switch-row"><label class="check php-switch-label"><input
|
||
id="web-service-php-enabled" type="checkbox" /><span
|
||
data-i18n="webServicePhpEnabled">Enable PHP-FPM / FastCGI</span></label>
|
||
<details class="metric-info php-field-info">
|
||
<summary data-i18n-aria="phpHintEnabled" aria-label="About enabling PHP-FPM">i</summary>
|
||
<div class="metric-info-popover">
|
||
<p class="fine" data-i18n="phpHintEnabled">Enables execution of configured PHP
|
||
extensions via the PHP-FPM socket. The pool must already be running; RustProxy does
|
||
not start PHP. Other files remain static.</p>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
<label class="vh-wide"><span data-i18n="webServicePhpEndpoint">PHP-FPM endpoint</span><input
|
||
id="web-service-php-endpoint" maxlength="512"
|
||
placeholder="unix:/run/php/php-fpm.sock" /><small class="fine"
|
||
data-i18n="webServicePhpEndpointHelp">Unix: unix:/run/php/php-fpm.sock. TCP:
|
||
tcp:127.0.0.1:9000 or tcp:[::1]:9000. TCP is intentionally limited to loopback
|
||
addresses.</small></label>
|
||
<label><span data-i18n="webServicePhpExtensions">PHP extensions</span><input
|
||
id="web-service-php-extensions" maxlength="512" placeholder="php, phtml" /><small
|
||
class="fine" data-i18n="phpHintExtensions">Comma-separated extensions without dots (e.g.
|
||
php, phtml). Only listed types are executed as PHP.</small></label>
|
||
<div class="vh-wide php-switch-row"><label class="check php-switch-label"><input
|
||
id="web-service-php-pass-authorization" type="checkbox" /><span
|
||
data-i18n="webServicePhpPassAuthorization">Forward Authorization header</span></label>
|
||
<details class="metric-info php-field-info">
|
||
<summary data-i18n-aria="phpHintPassAuthorization"
|
||
aria-label="Authorization forwarding information">i</summary>
|
||
<div class="metric-info-popover">
|
||
<p class="fine" data-i18n="phpHintPassAuthorization">Passes the incoming Authorization
|
||
header to PHP as HTTP_AUTHORIZATION. Enable for API/Bearer/Basic authentication;
|
||
disable if the application does not need it.</p>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
<div class="vh-wide php-switch-row"><label class="check php-switch-label"><input
|
||
id="web-service-php-path-info" type="checkbox" /><span
|
||
data-i18n="webServicePhpPathInfo">Enable PHP PATH_INFO splitting</span></label>
|
||
<details class="metric-info php-field-info">
|
||
<summary data-i18n-aria="phpHintPathInfo" aria-label="PHP PATH_INFO information">i</summary>
|
||
<div class="metric-info-popover">
|
||
<p class="fine" data-i18n="phpHintPathInfo">Allows /static.php/skins/file.css to run the
|
||
existing static.php script with PATH_INFO=/skins/file.css. Needed by Roundcube
|
||
static.php routes; usually not required for WordPress. Off by default.</p>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
<label><span data-i18n="webServicePhpConnectTimeout">Connect timeout (ms)</span><input
|
||
id="web-service-php-connect-timeout" min="50" max="60000" step="1" type="number" /><small
|
||
class="fine" data-i18n="phpHintConnectTimeout">How long to wait for the FPM socket
|
||
connection; e.g. 1000 = 1 s (50–60000).</small></label>
|
||
<label><span data-i18n="webServicePhpRequestTimeout">Total FastCGI request timeout (ms)</span><input
|
||
id="web-service-php-request-timeout" min="100" max="3600000" step="1" type="number" /><small
|
||
class="fine" data-i18n="phpHintRequestTimeout">Time for the complete FastCGI exchange; e.g.
|
||
30000 = 30 s (100–3600000).</small></label>
|
||
<label><span data-i18n="webServicePhpConcurrency">Max concurrent PHP requests</span><input
|
||
id="web-service-php-concurrency" min="1" max="4096" step="1" type="number" /><small
|
||
class="fine" data-i18n="phpHintConcurrency">Max simultaneous PHP requests for this service;
|
||
e.g. 64. Exhaustion returns 503 (1–4096).</small></label>
|
||
<label><span data-i18n="webServicePhpBodyLimit">PHP request body limit (bytes, 0 =
|
||
unlimited)</span><input id="web-service-php-body-limit" min="0" max="8589934592" step="1"
|
||
type="number" /><small class="fine" data-i18n="phpHintBodyLimit">Bytes, not MB: 67108864 =
|
||
64 MiB; 0 = no proxy-side limit. PHP post_max_size is separate.</small></label>
|
||
<label><span data-i18n="webServicePhpHeaderLimit">Max FastCGI response header bytes</span><input
|
||
id="web-service-php-header-limit" min="4096" max="1048576" step="1" type="number" /><small
|
||
class="fine" data-i18n="phpHintHeaderLimit">Max PHP response-header size in bytes; default
|
||
65536 = 64 KiB (4096–1048576).</small></label>
|
||
<label><span data-i18n="webServicePhpStderrLimit">Max PHP-FPM stderr log bytes</span><input
|
||
id="web-service-php-stderr-limit" min="0" max="65536" step="1" type="number" /><small
|
||
class="fine" data-i18n="phpHintStderrLimit">Max FastCGI STDERR bytes kept per request; e.g.
|
||
4096. 0 disables capture.</small></label>
|
||
<label class="vh-wide"><span data-i18n="webServicePhpCustomParams">Custom FastCGI
|
||
parameters</span><textarea id="web-service-php-custom-params" maxlength="65536" rows="6"
|
||
spellcheck="false" placeholder="APP_ENV = production APP_DEBUG = 0"></textarea><small
|
||
class="fine" data-i18n="webServicePhpCustomParamsHelp">One NAME = value entry per line.
|
||
Static trusted values only; core CGI, HTTP_* and PHP_* control names are
|
||
reserved. Example: APP_ENV = production; no variable interpolation.</small></label>
|
||
<label class="vh-wide"><span data-i18n="webServicePhpValues">PHP INI overrides
|
||
(PHP_VALUE)</span><textarea id="web-service-php-values" maxlength="65536" rows="6"
|
||
spellcheck="false"
|
||
placeholder="memory_limit = 256M upload_max_filesize = 64M post_max_size = 64M"></textarea><small
|
||
class="fine" data-i18n="webServicePhpValuesHelp">One php.ini directive = value per line.
|
||
Examples: memory_limit = 256M, upload_max_filesize = 64M, post_max_size = 64M,
|
||
max_execution_time = 30. Use PHP_ADMIN_VALUE for operator-only settings.</small></label>
|
||
<label class="vh-wide"><span data-i18n="phpProfileAdminValues">PHP administrator INI overrides
|
||
(PHP_ADMIN_VALUE)</span><textarea id="web-service-php-admin-values" maxlength="65536"
|
||
rows="6" spellcheck="false"
|
||
placeholder="open_basedir = /var/www/webmail/:/tmp/ display_errors = Off"></textarea><small
|
||
class="fine" data-i18n="phpHintAdminValues">Admin-only directives, e.g. open_basedir =
|
||
/var/www/webmail/:/tmp/. For isolation use a dedicated PHP-FPM pool/socket; dynamic INI
|
||
overrides can persist across requests sharing a worker.</small></label>
|
||
<div class="vh-wide form-actions"><button class="outline" data-i18n="webServicePhpDocsButton"
|
||
id="web-service-php-help" type="button">FastCGI syntax and safety</button></div>
|
||
<p class="fine vh-wide" data-i18n="webServicePhpHelp">RustProxy speaks FastCGI directly to a
|
||
long-running PHP-FPM pool. PHP is not spawned per request and PHP source is never served when
|
||
PHP handling is selected.</p>
|
||
</div>
|
||
<div class="vh-grid web-service-page" data-web-service-page="security" hidden>
|
||
<label><span data-i18n="webServiceSymlinkPolicy">Symlink policy</span><select
|
||
id="web-service-symlink-policy">
|
||
<option data-i18n="webServiceSymlinkDeny" value="deny">Deny</option>
|
||
<option data-i18n="webServiceSymlinkInsideRoot" value="inside_root">Allow only inside
|
||
document root (Linux)</option>
|
||
</select></label>
|
||
<div class="vh-wide">
|
||
<span class="small-label" data-i18n="webServiceHardeningTitle">Request/response hardening</span>
|
||
<p class="fine" data-i18n="webServiceHardeningSecurityHelp">Hardening switches are shown beside
|
||
the preset selector on General. They remain independent settings after a preset is applied.
|
||
</p>
|
||
</div>
|
||
<p class="fine vh-wide" data-i18n="webServiceSafety">Document root is a strict filesystem boundary.
|
||
The default denies symlinks. Linux can optionally follow only targets that resolve inside the
|
||
root; PHP-FPM currently requires the deny policy.</p>
|
||
</div>
|
||
</div>
|
||
<div class="form-actions">
|
||
<button class="primary" data-i18n="apply" type="submit">Validate, save and apply</button>
|
||
<button class="quiet" data-i18n="vhReset" id="web-service-reset" type="button">Discard changes</button>
|
||
</div>
|
||
</form>
|
||
</dialog>
|
||
<dialog aria-labelledby="php-profiles-title" class="vhost-dialog location-help-dialog" id="php-fpm-profiles-dialog">
|
||
<div class="window-heading">
|
||
<h2 id="php-profiles-title" data-i18n="phpProfileTitle">Shared PHP-FPM profiles</h2>
|
||
<button type="button" class="outline" id="php-fpm-profiles-close" data-i18n="close">Close</button>
|
||
</div>
|
||
<div class="card location-help-content">
|
||
<p class="fine" data-i18n="phpProfileDescription">Expand a profile to edit it. Changes are saved immediately
|
||
and shared by every attached Web Service.</p>
|
||
<div id="php-fpm-profiles-list"></div>
|
||
<button type="button" class="outline" id="php-fpm-profile-add" data-i18n="phpProfileAdd">Add
|
||
profile</button>
|
||
<hr class="php-profile-help-divider" />
|
||
<button type="button" class="outline" id="php-fpm-profiles-help" data-i18n="webServicePhpDocsButton">FastCGI
|
||
syntax and safety</button>
|
||
</div>
|
||
</dialog>
|
||
<dialog aria-labelledby="htaccess-help-title" class="vhost-dialog location-help-dialog" id="htaccess-help-dialog">
|
||
<div class="window-heading">
|
||
<h2 data-i18n="webServiceHtaccessDocsTitle" id="htaccess-help-title">.htaccess compatibility</h2>
|
||
<button class="outline" data-i18n="close" id="htaccess-help-close" type="button">Close</button>
|
||
</div>
|
||
<article class="card location-help-content">
|
||
<p data-i18n="webServiceHtaccessDocsIntro">This is a migration-oriented compatibility layer, not a complete
|
||
Apache runtime. Supported directives are compiled into RustProxy Web Server rules.</p>
|
||
<h3 data-i18n="webServiceHtaccessDocsSupportedTitle">Supported subset</h3>
|
||
<p><code>RewriteEngine</code>, <code>RewriteBase</code>, <code>RewriteCond</code>, <code>RewriteRule</code>,
|
||
<code>DirectoryIndex</code>, <code>Options +/-Indexes</code>, <code>AddType</code>,
|
||
<code>AddHandler</code> (PHP extensions), <code>Header set/add/unset</code>, <code>ExpiresActive</code>,
|
||
<code>ExpiresDefault</code>, <code>ExpiresByType</code>, <code>Require all granted/denied</code>.
|
||
</p>
|
||
<h3 data-i18n="webServiceHtaccessDocsRewriteTitle">Rewrite compatibility</h3>
|
||
<p data-i18n="webServiceHtaccessDocsRewrite">Rewrite conditions support REQUEST_FILENAME file tests (-f, -d,
|
||
-s), REQUEST_URI, REQUEST_METHOD, QUERY_STRING, HTTP_HOST, HTTPS, DOCUMENT_ROOT, THE_REQUEST and HTTP
|
||
headers. Flags: L, END, R, R=301/302/307/308, QSA, NC, F, OR. PT/NE are accepted as no-ops. E= is
|
||
accepted as a compatibility no-op because request HTTP headers already reach PHP-FPM.</p>
|
||
<pre><code>RewriteEngine On
|
||
RewriteCond %{REQUEST_FILENAME} !-f
|
||
RewriteCond %{REQUEST_FILENAME} !-d
|
||
RewriteRule . /index.php [L]</code></pre>
|
||
<h3 data-i18n="webServiceHtaccessDocsPhpTitle">PHP-FPM</h3>
|
||
<p data-i18n="webServiceHtaccessDocsPhp">.htaccess may add PHP extensions with AddHandler, but the PHP-FPM
|
||
endpoint, timeouts and concurrency remain controlled by the Web Service PHP-FPM tab. Apache SetHandler
|
||
proxy socket syntax is intentionally not accepted.</p>
|
||
<h3 data-i18n="webServiceHtaccessDocsReloadTitle">Loading and limits</h3>
|
||
<p data-i18n="webServiceHtaccessDocsReload">Files are read and compiled on Validate, save and apply.
|
||
RustProxy does not parse .htaccess per request. Re-apply after changing a file. The scan is bounded to
|
||
128 files, depth 16, 128 KiB per file and 1 MiB total.</p>
|
||
<h3 data-i18n="webServiceHtaccessDocsUnsupportedTitle">Not Apache-complete</h3>
|
||
<p data-i18n="webServiceHtaccessDocsUnsupported">ErrorDocument, SetEnvIf, Redirect/RedirectMatch and
|
||
FallbackResource are not implemented in this compatibility layer. General Files/FilesMatch, unsupported
|
||
authentication/authorization directives, non-PHP handlers and SetHandler are rejected even when Strict
|
||
mode is off because silently ignoring them could weaken access control or expose source.
|
||
.htaccess/.htpasswd files are always blocked from HTTP delivery. Other unsupported non-security
|
||
directives are logged and ignored unless Strict mode is enabled.</p>
|
||
<p class="fine" data-i18n="webServiceHtaccessDocsSecurity">Vhost/location ACL, authentication, error pages
|
||
and response rules still remain authoritative. .htaccess cannot escape the document root, select an
|
||
arbitrary FastCGI socket or override protected framing headers.</p>
|
||
</article>
|
||
</dialog>
|
||
<dialog aria-labelledby="php-help-title" class="vhost-dialog location-help-dialog" id="php-help-dialog">
|
||
<div class="window-heading">
|
||
<h2 data-i18n="webServicePhpDocsTitle" id="php-help-title">PHP-FPM / FastCGI configuration</h2>
|
||
<button class="outline" data-i18n="close" id="php-help-close" type="button">Close</button>
|
||
</div>
|
||
<article class="card location-help-content php-reference">
|
||
<p data-i18n="phpDocsScope">The same fields and syntax apply to a local Web Service PHP configuration and a
|
||
reusable PHP-FPM profile. A selected profile overrides local PHP fields for all attached services.
|
||
RustProxy connects to an already running PHP-FPM pool.</p>
|
||
<h3 data-i18n="phpDocsSwitchesTitle">Enabling PHP and PATH_INFO</h3>
|
||
<p data-i18n="phpHintEnabled">Enables execution of configured PHP extensions via the PHP-FPM socket. The
|
||
pool must already be running; RustProxy does not start PHP. Other files remain static.</p>
|
||
<p data-i18n="phpHintPathInfo">Allows /static.php/skins/file.css to run the existing static.php script with
|
||
PATH_INFO=/skins/file.css. Needed by Roundcube static.php routes; usually not required for WordPress.
|
||
Off by default.</p>
|
||
<p data-i18n="phpHintPassAuthorization">Passes the incoming Authorization header to PHP as
|
||
HTTP_AUTHORIZATION. Enable for API/Bearer/Basic authentication; disable if the application does not need
|
||
it.</p>
|
||
<h3 data-i18n="webServicePhpDocsEndpointTitle">Endpoint syntax</h3>
|
||
<pre><code>unix:/run/php/php_wordpress.sock
|
||
tcp:127.0.0.1:9000
|
||
tcp:[::1]:9000</code></pre>
|
||
<p data-i18n="webServicePhpDocsEndpoint">Use unix: for the full local socket path or tcp: plus a literal
|
||
loopback IP and port. Hostnames and non-loopback TCP are rejected. The RustProxy user must be allowed to
|
||
connect to the socket. Separate pools when PHP INI security settings differ.</p>
|
||
<h3 data-i18n="phpDocsLimitsTitle">Execution and size limits</h3>
|
||
<p data-i18n="phpDocsLimitsText">PHP extensions: comma-separated names such as php, phtml (no leading dots).
|
||
Connect timeout: 1000 ms = 1 second. Full FastCGI request timeout: 30000 ms = 30 seconds. Concurrent
|
||
requests: 64 (returns 503 when exhausted). Request body: 67108864 bytes = 64 MiB; 0 means unlimited by
|
||
RustProxy. Response headers: 65536 bytes. STDERR: 4096 bytes; 0 disables capture. All numeric fields
|
||
require integers; PHP INI limits are separate.</p>
|
||
<h3 data-i18n="webServicePhpDocsCustomTitle">Custom FastCGI parameters</h3>
|
||
<pre><code>APP_ENV = production
|
||
APP_DEBUG = 0
|
||
TENANT = main</code></pre>
|
||
<p data-i18n="webServicePhpDocsCustom">Put these in Custom FastCGI parameters only. One NAME = value per
|
||
line; names start with A-Z and then contain A-Z, 0-9 or underscore (max 64 characters). Values are
|
||
static, up to 8192 bytes each; no placeholders are expanded. Do not use SCRIPT_FILENAME, HTTP_* or
|
||
PHP_VALUE/PHP_ADMIN_VALUE here.</p>
|
||
<h3 data-i18n="webServicePhpDocsValuesTitle">PHP_VALUE — regular php.ini overrides</h3>
|
||
<pre><code>memory_limit = 256M
|
||
upload_max_filesize = 64M
|
||
post_max_size = 64M
|
||
max_execution_time = 30
|
||
error_reporting = E_ALL
|
||
log_errors = On
|
||
display_errors = Off
|
||
date.timezone = Europe/Warsaw</code></pre>
|
||
<p data-i18n="phpDocsValuesText">Enter these in PHP INI overrides (PHP_VALUE), not in FastCGI parameters.
|
||
Use one directive = value per line (php.ini notation, no [section] headers or PHP tags). Exact support
|
||
depends on which directives PHP-FPM permits to change. Common sizes use M/G, booleans On/Off and times
|
||
seconds. Do not duplicate a directive in PHP_ADMIN_VALUE.</p>
|
||
<h3 data-i18n="phpDocsAdminTitle">PHP_ADMIN_VALUE — administrator-only overrides</h3>
|
||
<pre><code>open_basedir = /var/www/webmail/:/tmp/:/usr/lib/php/
|
||
display_errors = Off
|
||
log_errors = On</code></pre>
|
||
<p data-i18n="phpDocsAdminText">Put these in PHP administrator INI overrides (PHP_ADMIN_VALUE). open_basedir
|
||
lists allowed directories separated by ':' on Unix. Set it to paths of THAT application; do not reuse
|
||
Roundcube restrictions in a WordPress pool. For security-sensitive values, prefer php_admin_value[...]
|
||
in the configuration of a dedicated PHP-FPM pool. Shared FPM workers can retain request-supplied INI
|
||
settings.</p>
|
||
<h3 data-i18n="phpDocsOtherTitle">Storage, validation and troubleshooting</h3>
|
||
<p data-i18n="phpDocsOtherText">Each INI name may contain letters, digits, '.', '_' and '-' (up to 64
|
||
bytes); a value must be nonempty (up to 8192 bytes). Max 64 custom FastCGI entries and 64 INI overrides
|
||
per map, with a total 128 KiB budget. No control characters or multi-line values. Values are saved in
|
||
normal configuration: never place secrets there unless that storage is acceptable. A syntax-valid
|
||
directive can still be rejected/ignored by PHP itself.</p>
|
||
<p class="fine" data-i18n="phpDocsPoolWarning">Critical: per-service PHP_VALUE/PHP_ADMIN_VALUE do NOT
|
||
isolate applications when they share one PHP-FPM worker pool. Use different Unix sockets and restart the
|
||
pools after moving WordPress and Roundcube apart. PATH_INFO does not fix open_basedir isolation.</p>
|
||
</article>
|
||
</dialog>
|
||
<dialog aria-labelledby="vhost-editor-title" class="vhost-dialog" id="vhost-dialog">
|
||
<form class="card vh-form" id="vhost-form" novalidate="">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="vhEditor" id="vhost-editor-title">Vhost configuration</h2><span class="pill"
|
||
id="vhost-editing">NEW</span><button class="quiet" data-i18n="close" id="vhost-close"
|
||
type="button">Close</button>
|
||
</div>
|
||
<div class="vh-live-summary" id="vhost-health-live"></div>
|
||
<nav aria-label="Vhost configuration" class="vh-tabs" id="vhost-tabs"></nav>
|
||
<div id="vhost-fields"></div>
|
||
<div class="form-actions"><button class="primary" data-i18n="apply" id="vhost-save" type="submit">Validate,
|
||
save and apply</button><button class="quiet" data-i18n="vhReset" id="vhost-reset"
|
||
type="button">Discard changes</button></div>
|
||
<p class="fine" data-i18n="vhSafetyHint">Private IP binding is not authentication. Keep firewall/ACL rules.
|
||
HSTS preload is opt-in; affinity cookies are not login sessions.</p>
|
||
</form>
|
||
</dialog>
|
||
<dialog aria-labelledby="location-help-title" class="vhost-dialog location-help-dialog" id="location-help-dialog">
|
||
<div class="window-heading">
|
||
<h2 id="location-help-title"></h2>
|
||
<button class="outline" data-i18n="close" id="location-help-close" type="button">Close</button>
|
||
</div>
|
||
<article class="card location-help-content" id="location-help-body"></article>
|
||
</dialog>
|
||
|
||
<dialog aria-labelledby="redirect-editor-title" class="vhost-dialog" id="redirect-dialog">
|
||
<form class="card vh-form" id="redirect-form" novalidate="">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="redirectEditor" id="redirect-editor-title">Redirect configuration</h2><span class="pill"
|
||
id="redirect-editing">NEW</span><button class="quiet" data-i18n="close" id="redirect-close"
|
||
type="button">Close</button>
|
||
</div>
|
||
<div class="vh-grid">
|
||
<label><span data-i18n="redirectId">Redirect ID</span><input id="redirect-id" maxlength="64"
|
||
pattern="[A-Za-z0-9_-]{1,64}" required="" /></label>
|
||
<label><span data-i18n="redirectName">Display name</span><input id="redirect-name"
|
||
maxlength="96" /></label>
|
||
<label class="check"><input id="redirect-enabled" type="checkbox" /><span
|
||
data-i18n="enabled">Enabled</span></label>
|
||
<div class="vh-wide" id="redirect-hosts-editor"></div>
|
||
<label><span data-i18n="vhCertificate">Certificate</span><select
|
||
id="redirect-certificate"></select></label>
|
||
</div>
|
||
<section class="vh-group">
|
||
<div class="card-heading">
|
||
<div><span class="small-label" data-i18n="redirectFrontend">FRONTEND</span>
|
||
<h3 data-i18n="redirectListeners">Listeners</h3>
|
||
</div>
|
||
</div>
|
||
<div class="redirect-listeners" id="redirect-listeners"></div>
|
||
<div class="protocol-toggle-row"><label class="check"><input id="redirect-http2" type="checkbox" /><span
|
||
data-i18n="vhHttp2Enabled">Enable HTTP/2 for this host</span></label><label
|
||
class="check"><input id="redirect-http3" type="checkbox" /><span
|
||
data-i18n="vhHttp3Enabled">Enable HTTP/3 for this host</span></label></div>
|
||
</section>
|
||
<section class="vh-group">
|
||
<div class="card-heading">
|
||
<div><span class="small-label" data-i18n="redirectBehavior">BEHAVIOR</span>
|
||
<h3 data-i18n="redirectRules">Rules</h3>
|
||
</div><button class="quiet" data-i18n="redirectAddRule" id="redirect-rule-add" type="button">Add
|
||
rule</button>
|
||
</div>
|
||
<p class="fine" data-i18n="redirectRulesHelp">Longest matching path wins. 307/308 preserve the request
|
||
method; 301/302/303 may change it in clients.</p>
|
||
<div class="redirect-rules" id="redirect-rules"></div>
|
||
</section>
|
||
<div class="form-actions"><button class="primary" data-i18n="apply" type="submit">Validate, save and
|
||
apply</button><button class="quiet" data-i18n="vhReset" id="redirect-reset" type="button">Discard
|
||
changes</button></div>
|
||
<p class="fine" data-i18n="redirectSafetyHint">Redirect hosts do not contact a backend. Global host ACLs
|
||
still apply. Avoid self-referential rules that can create browser redirect loops.</p>
|
||
</form>
|
||
</dialog>
|
||
|
||
<dialog aria-labelledby="notfound-editor-title" class="vhost-dialog" id="notfound-dialog">
|
||
<form class="card vh-form" id="notfound-form" novalidate="">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="notfoundEditor" id="notfound-editor-title">404 host configuration</h2><span class="pill"
|
||
id="notfound-editing">NEW</span><button class="quiet" data-i18n="close" id="notfound-close"
|
||
type="button">Close</button>
|
||
</div>
|
||
<div class="vh-grid">
|
||
<label><span data-i18n="notfoundId">Host ID</span><input id="notfound-id" maxlength="64"
|
||
pattern="[A-Za-z0-9_-]{1,64}" required="" /></label>
|
||
<label><span data-i18n="notfoundName">Display name</span><input id="notfound-name"
|
||
maxlength="96" /></label>
|
||
<label class="check"><input id="notfound-enabled" type="checkbox" /><span
|
||
data-i18n="enabled">Enabled</span></label>
|
||
<div class="vh-wide" id="notfound-hosts-editor"></div>
|
||
<label><span data-i18n="vhCertificate">Certificate</span><select
|
||
id="notfound-certificate"></select></label>
|
||
</div>
|
||
<section class="vh-group">
|
||
<div class="card-heading">
|
||
<div><span class="small-label" data-i18n="notfoundFrontend">FRONTEND</span>
|
||
<h3 data-i18n="redirectListeners">Listeners</h3>
|
||
</div>
|
||
</div>
|
||
<div id="notfound-listeners"></div>
|
||
<div class="protocol-toggle-row"><label class="check"><input id="notfound-http2" type="checkbox" /><span
|
||
data-i18n="vhHttp2Enabled">Enable HTTP/2 for this host</span></label><label
|
||
class="check"><input id="notfound-http3" type="checkbox" /><span
|
||
data-i18n="vhHttp3Enabled">Enable HTTP/3 for this host</span></label></div>
|
||
</section>
|
||
<section class="vh-group">
|
||
<div class="card-heading">
|
||
<div><span class="small-label" data-i18n="notfoundBehavior">RESPONSE</span>
|
||
<h3 data-i18n="notfoundResponse">Local response</h3>
|
||
</div>
|
||
</div>
|
||
<div class="vh-grid">
|
||
<label><span data-i18n="notfoundMode">Response type</span><select id="notfound-mode">
|
||
<option data-i18n="notfoundModeWelcome" value="welcome">RustProxy placeholder</option>
|
||
<option data-i18n="notfoundModeBuiltin" value="builtin">Application-style status page
|
||
</option>
|
||
<option data-i18n="notfoundModeEmpty" value="empty">Status only / empty</option>
|
||
<option data-i18n="notfoundModeHtml" value="html">Custom HTML</option>
|
||
</select></label>
|
||
<label><span data-i18n="notfoundStatus">HTTP status</span><select id="notfound-status"
|
||
required></select></label>
|
||
<label class="vh-wide" id="notfound-html-field"><span data-i18n="notfoundHtml">Custom
|
||
HTML</span><textarea id="notfound-html" maxlength="32768" rows="12"
|
||
spellcheck="false"></textarea></label>
|
||
</div>
|
||
<p class="fine" data-i18n="notfoundResponseHelp">The host never opens an upstream connection. The
|
||
built-in placeholder is no-store and intentionally omits optional RustProxy diagnostic/server
|
||
headers.</p>
|
||
</section>
|
||
<div class="form-actions"><button class="primary" data-i18n="apply" type="submit">Validate, save and
|
||
apply</button><button class="quiet" data-i18n="vhReset" id="notfound-reset" type="button">Discard
|
||
changes</button></div>
|
||
</form>
|
||
</dialog>
|
||
|
||
<dialog aria-labelledby="stream-editor-title" class="vhost-dialog" id="stream-dialog">
|
||
<form class="card vh-form" id="stream-form" novalidate="">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="streamEditor" id="stream-editor-title">Stream configuration</h2><span class="pill"
|
||
id="stream-editing">NEW</span><button class="quiet" data-i18n="close" id="stream-close"
|
||
type="button">Close</button>
|
||
</div>
|
||
<div class="vh-live-summary" id="stream-live-summary"></div>
|
||
<nav aria-label="Stream configuration" class="vh-tabs" id="stream-tabs"></nav>
|
||
<div id="stream-fields"></div>
|
||
<div class="form-actions"><button class="primary" data-i18n="apply" id="stream-save" type="submit">Validate,
|
||
save and apply</button><button class="quiet" data-i18n="vhReset" id="stream-reset"
|
||
type="button">Discard changes</button></div>
|
||
<p class="fine" data-i18n="streamRestartHelp">Changing a stream listener requires a RustProxy restart.</p>
|
||
</form>
|
||
</dialog>
|
||
<dialog aria-labelledby="about-title" class="vhost-dialog about-dialog" id="about-dialog">
|
||
<div class="window-heading">
|
||
<h2 data-i18n="aboutTitle" id="about-title">About RustProxy</h2>
|
||
<button class="outline" data-i18n="close" id="about-close" type="button">Close</button>
|
||
</div>
|
||
<article class="card about-card">
|
||
<div class="about-product">
|
||
<span aria-hidden="true" class="brand-mark">r<span>/</span>p</span>
|
||
<div><span class="small-label">RustProxy</span><strong>__RUSTPROXY_VERSION__</strong></div>
|
||
</div>
|
||
<dl class="about-meta">
|
||
<div>
|
||
<dt data-i18n="aboutAuthor">Author</dt>
|
||
<dd>Mateusz Gruszczynski @linuxiarz.pl</dd>
|
||
</div>
|
||
<div>
|
||
<dt data-i18n="aboutLicense">License</dt>
|
||
<dd data-i18n="aboutLicenseValue">Open Source</dd>
|
||
</div>
|
||
</dl>
|
||
<p data-i18n="aboutOpenSourceText">RustProxy is an open-source project. Its source code is publicly
|
||
available for inspection, development and community contribution.</p>
|
||
<a class="outline about-repository" href="https://git.linuxiarz.pl/gru/RustProxy" rel="noopener noreferrer"
|
||
target="_blank"><span data-i18n="aboutRepository">Official source
|
||
repository</span><span>git.linuxiarz.pl/gru/RustProxy</span></a>
|
||
<a class="outline about-repository" href="https://rustproxy.linuxiarz.pl/" rel="noopener noreferrer"
|
||
target="_blank"><span data-i18n="aboutPackagesRepository">Official package
|
||
repository</span><span>rustproxy.linuxiarz.pl</span></a>
|
||
</article>
|
||
</dialog>
|
||
<div aria-live="polite" class="toast" hidden="" id="toast" popover="manual" role="status"></div>
|
||
<dialog aria-labelledby="logging-format-title" class="vhost-dialog logging-format-dialog"
|
||
id="logging-format-dialog">
|
||
<div class="card vh-form">
|
||
<div class="card-heading">
|
||
<h2 data-i18n="loggingBuilder" id="logging-format-title">Visual field builder</h2><span class="pill"
|
||
data-i18n="loggingFormat">CONFIGURABLE JSON</span><button class="quiet" data-i18n="close"
|
||
id="logging-format-close" type="button">Close</button>
|
||
</div>
|
||
<p class="vh-live-summary" data-i18n="loggingBuilderHelp">Select source fields and optionally rename their
|
||
JSON keys. Changes are written to the advanced JSON mapping when applied.</p>
|
||
<div class="logging-builder-layout">
|
||
<section>
|
||
<h3 class="subheading" data-i18n="loggingFields">Fields</h3>
|
||
<div class="table-scroll">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th data-i18n="enabled">Enabled</th>
|
||
<th data-i18n="loggingSourceField">Source field</th>
|
||
<th data-i18n="loggingOutputField">Output JSON key</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="logging-field-rows"></tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
<section class="logging-preview">
|
||
<h3 data-i18n="loggingPreview">Preview</h3>
|
||
<pre class="metrics-example" id="logging-format-preview"></pre>
|
||
</section>
|
||
</div>
|
||
<div class="form-actions"><button class="primary" data-i18n="apply" id="logging-format-apply"
|
||
type="button">Apply</button><button class="quiet" data-i18n="close" id="logging-format-cancel"
|
||
type="button">Close</button></div>
|
||
</div>
|
||
</dialog>
|
||
</body>
|
||
|
||
</html> |