Files
RustProxy/src/admin/web/index.html
T
2026-10-12 14:57:37 +02:00

3858 lines
322 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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" />
<!-- One ordered JS payload: no race between module fetches and session/config API. -->
<script defer="" src="/assets/app___RUSTPROXY_ASSET_ID__.js"></script>
</head>
<body>
<!-- Visible even when the dashboard JavaScript fails to initialize. -->
<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="eyebrow"><i class="signal" id="signal"></i><span id="connection-label">CONNECTED TO
LOCAL CORE</span></div>
<div class="page-intro-title-row">
<h1 id="page-title">Traffic, in focus.</h1>
<button class="quiet overview-refresh" data-i18n="refreshNow" id="overview-refresh" type="button" hidden>Refresh now</button>
</div>
<p class="lead" id="page-description"></p>
</div>
</section>
<section class="panel" id="panel-overview">
<div class="stats-grid">
<article class="stat" data-dashboard-widget="requests"><span
data-i18n="requests">Requests</span><strong id="metric-requests">0</strong><small
id="request-rate">0 req/s</small></article>
<article class="stat" data-dashboard-widget="active"><span data-i18n="activeExchanges">Active
exchanges</span><strong id="metric-active">0</strong><small
data-i18n="bodyLifetime">Full body lifetime</small>
</article>
<article class="stat" data-dashboard-widget="bandwidth"><span data-i18n="bandwidth">Payload
transferred</span><strong id="metric-bytes">0 B</strong><small class="payload-split"
id="metric-bytes-split"><span>↓ <b id="metric-bytes-down">0 B</b></span><span>↑ <b
id="metric-bytes-up">0 B</b></span></small>
</article>
<article class="stat" data-dashboard-widget="errors"><span
data-i18n="errors">Errors</span><strong id="metric-errors">0</strong><small
data-i18n="errorDescription">5xx / body /
cancelled</small></article>
<article class="stat" data-dashboard-widget="cache"><span data-i18n="cacheHitRatio">Cache hit
ratio</span><strong id="metric-cache-ratio">-</strong><small
id="cache-summary">-</small></article>
<article class="stat" data-dashboard-widget="memory"><span data-i18n="processMemory">Process
memory</span><strong id="metric-rss">-</strong><small id="process-summary">-</small>
</article>
<article class="stat stat-with-action" data-dashboard-widget="websocket"><span
data-i18n="wsActive">WebSocket
tunnels</span><button class="quiet stat-corner-action" data-i18n="tunnelsOpen"
id="tunnels-open" type="button">Active tunnels</button><strong
id="metric-ws">0</strong><small
aria-label="Active WebSocket tunnels by frontend protocol"><span data-i18n="wsH1">H1
Upgrade</span> <span id="metric-ws-h1">0</span> · <span data-i18n="wsH2Rfc8441">H2
RFC 8441</span> <span id="metric-ws-h2">0</span> · <span data-i18n="wsH3Rfc9220">H3
RFC 9220</span> <span id="metric-ws-h3">0</span><br><span
data-i18n="wsEstablished">Established</span>: <span
id="metric-ws-total">0</span></small></article>
<article class="stat" data-dashboard-widget="http2"><span data-i18n="h2Requests">HTTP/2
requests</span><strong id="metric-h2">0</strong><small><span
data-i18n="h2ActiveConnections">Active H2
connections</span>: <span id="metric-h2-active">0</span></small></article>
<article class="stat" data-dashboard-widget="http3"><span data-i18n="h3Requests">HTTP/3
requests</span><strong id="metric-h3">0</strong></article>
<article class="stat" data-dashboard-widget="tls"><span data-i18n="tlsFailures">TLS handshake
errors</span><strong id="metric-tls-errors">0</strong></article>
<article class="stat stat-chart" data-dashboard-widget="http_protocol"><span
data-i18n="httpProtocolMix">HTTP protocol
mix</span><strong id="metric-http-proto-total">0.0/s</strong><svg
aria-label="HTTP protocol activity" data-i18n-aria="httpProtocolMix"
id="http-protocol-chart" role="img" viewBox="0 0 300 60">
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
<polyline class="diagnostic-line proto-h1-line" id="http-protocol-h1"
points="0,55 300,55"></polyline>
<polyline class="diagnostic-line proto-h2-line" id="http-protocol-h2"
points="0,55 300,55"></polyline>
<polyline class="diagnostic-line proto-h3-line" id="http-protocol-h3"
points="0,55 300,55"></polyline>
</svg><small class="stat-chart-legend"><span class="legend-h1"><i></i>H1 <b
id="metric-http-proto-h1">0.0/s</b></span><span class="legend-h2"><i></i>H2 <b
id="metric-http-proto-h2">0.0/s</b></span><span class="legend-h3"><i></i>H3 <b
id="metric-http-proto-h3">0.0/s</b></span></small></article>
<article class="stat stat-chart" data-dashboard-widget="websocket_protocol"><span
data-i18n="wsProtocolMix">WebSocket protocol
mix</span><strong id="metric-ws-proto-total">0</strong><svg
aria-label="WebSocket protocol activity" data-i18n-aria="wsProtocolMix"
id="ws-protocol-chart" role="img" viewBox="0 0 300 60">
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
<polyline class="diagnostic-line proto-h1-line" id="ws-protocol-h1"
points="0,55 300,55"></polyline>
<polyline class="diagnostic-line proto-h2-line" id="ws-protocol-h2"
points="0,55 300,55"></polyline>
<polyline class="diagnostic-line proto-h3-line" id="ws-protocol-h3"
points="0,55 300,55"></polyline>
</svg><small class="stat-chart-legend"><span class="legend-h1"><i></i>H1 <b
id="metric-ws-proto-h1">0</b></span><span class="legend-h2"><i></i>H2 <b
id="metric-ws-proto-h2">0</b></span><span class="legend-h3"><i></i>H3 <b
id="metric-ws-proto-h3">0</b></span></small></article>
</div>
<div class="overview-grid">
<article class="card activity-card">
<div class="card-heading">
<div><span class="small-label" data-i18n="liveTraffic">LIVE TRAFFIC</span>
<h2 data-i18n="requestsPerSecond">Requests per second</h2>
</div><span class="pill live" data-i18n="live">LIVE</span>
</div><svg aria-label="Recent request rate" id="traffic-chart" role="img"
viewbox="0 0 600 130">
<path class="chart-grid" d="M0 25H600 M0 65H600 M0 105H600"></path>
<polyline class="traffic-line traffic-line-total" id="sparkline" points="0,120 600,120">
</polyline>
<polyline class="traffic-line traffic-line-h1" id="sparkline-h1" points="0,120 600,120">
</polyline>
<polyline class="traffic-line traffic-line-h2" id="sparkline-h2" points="0,120 600,120">
</polyline>
<polyline class="traffic-line traffic-line-h3" id="sparkline-h3" points="0,120 600,120">
</polyline>
<polyline class="traffic-line traffic-line-transform" id="sparkline-transform"
points="0,120 600,120">
</polyline>
</svg>
<div class="traffic-protocol-legend"
aria-label="Request rate by HTTP protocol and transformed response rate">
<span class="legend-total"><i></i>Total <b id="rate-total">0.0/s</b></span>
<span class="legend-h1"><i></i>HTTP/1.1 <b id="rate-h1">0.0/s</b></span>
<span class="legend-h2"><i></i>HTTP/2 <b id="rate-h2">0.0/s</b></span>
<span class="legend-h3"><i></i>HTTP/3 <b id="rate-h3">0.0/s</b></span>
<span class="legend-transform"><i></i><span
data-i18n="liveTransformed">Transformed</span> <b
id="rate-transform">0.0/s</b></span>
</div>
<div class="chart-footer"><span data-i18n="lastMinute">Recent samples / resets on
reload</span><span id="chart-peak">peak 0 req/s</span></div>
</article>
<article class="card system-card">
<div class="card-heading">
<div><span class="small-label" data-i18n="systemLoad">SYSTEM LOAD</span>
<h2><span id="system-cpu">-</span> <small>CPU</small></h2>
</div><span class="meta" id="system-source">-</span>
</div><svg aria-label="System CPU history" data-i18n-aria="systemChart" id="system-chart"
role="img" viewbox="0 0 300 100">
<path class="chart-grid" d="M0 25H300 M0 50H300 M0 75H300"></path>
<polyline id="system-sparkline" points="0,95 300,95"></polyline>
</svg>
<div class="memory-line"><span data-i18n="systemMemory">System RAM</span><b
id="system-memory">-</b></div><progress aria-label="System RAM"
data-i18n-aria="systemMemory" id="memory-meter" max="100" value="0"></progress>
<div class="memory-line"><span data-i18n="systemSwap">System swap</span><b
id="system-swap">-</b></div>
<div class="chart-footer"><span data-i18n="loadAverage">Load 1 / 5 / 15 min</span><span
id="system-load">-</span></div>
<div class="chart-footer"><span data-i18n="systemUptime">System uptime</span><span
id="system-uptime">-</span></div>
</article>
<article class="card connection-card">
<div class="card-heading">
<div><span class="small-label" data-i18n="connectionActivity">CONNECTION ACTIVITY</span>
<h2><span id="connection-current">0</span> <small data-i18n="connections">Open
connections</small></h2>
</div><span class="pill live" data-i18n="live">LIVE</span>
</div>
<svg aria-label="Connection activity history" data-i18n-aria="connectionActivity"
id="connection-chart" role="img" viewbox="0 0 300 100">
<path class="chart-grid" d="M0 25H300 M0 50H300 M0 75H300"></path>
<polyline class="connection-open-line" id="connection-open-sparkline"
points="0,95 300,95"></polyline>
<polyline class="connection-exchange-line" id="connection-exchange-sparkline"
points="0,95 300,95"></polyline>
</svg>
<div class="connection-legend"><span class="connection-open-key"><i></i><span
data-i18n="connections">Open connections</span> <b
id="connection-open-value">0</b></span><span
class="connection-exchange-key"><i></i><span data-i18n="activeExchanges">Active
exchanges</span> <b id="connection-exchange-value">0</b></span></div>
<div class="chart-footer"><span><span data-i18n="backends">Backends</span>: <b
id="connection-backends">0</b></span></div>
</article>
</div>
<div class="diagnostics-grid">
<article class="card">
<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 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 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 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 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">
<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 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 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">
<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 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 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>
</div>
<article class="card pool-card" id="backend-diagnostics-card">
<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>
</section>
<section class="panel" hidden="" id="panel-vhosts">
<p class="notice fine" data-i18n="vhIntro">Each vhost owns its bound IPs, paths and backends.
Unlisted addresses cannot reach it. Listener changes require restart.</p>
<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&#10;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&#10;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 45H900 M0 90H900 M0 135H900"></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="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 45H900 M0 90H900 M0 135H900"></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:&gt;=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="themeLight" value="light">Light</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>
</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">
<label><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"><span
data-i18n="dashboardWidgets">Overview cards</span><small class="fine"
data-i18n="dashboardWidgetsHelp">Show, hide and reorder the telemetry
cards at the top of Overview.</small>
<div class="dashboard-widget-list" id="dashboard-widget-list"></div>
</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 selected value="2">2 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 &gt; 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&#10;curl/*&#10;*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&#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="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&#10;$uri/&#10;/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&#10;^/app$ => /index.html"></textarea><small
class="fine" data-i18n="webServiceRewritesHelp">One regex rule per line. Use pattern =&gt;
/target for an internal rewrite or pattern =&gt; 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&#10;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&#10;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><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&#10;pdf&#10;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&#10;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&#10;upload_max_filesize = 64M&#10;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/&#10;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>