From 7cfc3a6da1cfa8d07a29d5ebd18a41980f6d92dd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?=
Date: Wed, 21 Oct 2026 09:38:21 +0200
Subject: [PATCH] v0.34.5
---
AGENTS.md | 2 +-
CHANGELOG.md | 17 +-
Cargo.lock | 2 +-
Cargo.toml | 2 +-
README.md | 12 +-
SHA256SUMS | 63 +--
docs/ACCESS.md | 2 +
docs/CACHE.md | 4 +
docs/CONFIGURATION.md | 2 +-
docs/DOCKER_DISCOVERY.md | 2 +-
docs/MONITORING.md | 2 +
docs/README.md | 2 +-
docs/RESPONSE_DELIVERY.md | 2 +-
docs/STREAMS.md | 2 +
docs/VHOSTS.md | 8 +
docs/WEB_SERVER.md | 6 +-
langs/en_US.json | 27 +-
langs/pl_pl.json | 27 +-
scripts/audit_css_usage.py | 166 +++++++
src/admin/openapi.json | 2 +-
src/admin/web/docker.js | 6 +-
src/admin/web/docs.css | 43 +-
src/admin/web/docs.js | 5 +
src/admin/web/index.html | 461 ++++++++++--------
src/admin/web/settings.js | 9 -
src/admin/web/streams.js | 47 +-
src/admin/web/style.css | 865 +++++++++++++--------------------
src/admin/web/vhosts/core.js | 30 +-
src/admin/web/vhosts/editor.js | 163 ++++---
src/admin/web/vhosts/rates.js | 4 +-
src/tls/tests.rs | 5 +-
tests/css_usage_audit.cjs | 46 ++
tests/ux_0345.cjs | 70 +++
tests/ux_0345_followup.cjs | 87 ++++
tests/vhost_ui_layout.cjs | 59 +++
35 files changed, 1377 insertions(+), 875 deletions(-)
create mode 100755 scripts/audit_css_usage.py
create mode 100644 tests/css_usage_audit.cjs
create mode 100644 tests/ux_0345.cjs
create mode 100644 tests/ux_0345_followup.cjs
create mode 100644 tests/vhost_ui_layout.cjs
diff --git a/AGENTS.md b/AGENTS.md
index 3082a54..8e672b2 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,6 +1,6 @@
# RustProxy source and documentation maintenance
-Current source version: **0.34.4**. The Cargo package and binary are named `rust-proxy`.
+Current source version: **0.34.5**. The Cargo package and binary are named `rust-proxy`.
Before editing, read [README.md](README.md), [ROADMAP.md](ROADMAP.md), the relevant [documentation guide](docs/README.md) and the actual code and tests. Historical release experiments are not current specifications. Use the bundled `src/admin/openapi.json` for API routes and payloads; the source implementation is authoritative for runtime behavior.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f5716e3..01ee3bb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,20 @@
# Changelog
-Release history focused on **features, settings, default behavior, compatibility and fixes**. Changes to internal code structure and preliminary performance experiments are intentionally omitted. Historical descriptions reflect their respective releases; consult the current documentation for the settings available in 0.34.4.
+Release history focused on **features, settings, default behavior, compatibility and fixes**. Changes to internal code structure and preliminary performance experiments are intentionally omitted. Historical descriptions reflect their respective releases; consult the current documentation for the settings available in 0.34.5.
+
+## 0.34.5 — Administration UX improvements (source candidate)
+
+- **Web Server → Delivery:** separated static-file delivery, dynamic compression/codecs and precompressed `.br`/`.gz` sidecar controls into individually labelled settings groups. Existing options, defaults and configuration keys remain unchanged.
+- **Web Server → PHP-FPM:** grouped profile and endpoint selection, request behavior, timeouts/capacity, and FastCGI/php.ini overrides. Disabled inputs, selectors and textareas now share a clear inactive appearance when PHP is off or controlled by a shared profile.
+- **Streams → Backends:** split backend routing/capacity from health checks. Probe timing and UDP payload fields appear only when a health-check mode is selected; hidden controls retain their values and cannot block form validation.
+- **Redirects and 404 hosts:** separated identity/domains from frontend listener and certificate selection, with more consistent checkbox alignment and spacing.
+- **Vhost → Transform:** the file-extension override checkbox now sits directly above its textarea, separated from format selectors. The three similar Vhost cache overrides (static extensions, bypass prefixes and unsafe force targets) are stacked consistently; disabled overrides no longer appear editable. Existing saved field names and policy behavior remain unchanged.
+- **Vhost access:** relocated the inline IP/CIDR ACL editor to **Security → Traffic policy**, alongside reusable ACL profiles. **Authentication** now contains Basic Auth and Tinyauth alone. An inline ACL remains an independent, cumulative restriction (it cannot be bypassed by a profile allow).
+- **Web Server presets:** the two hardening checkboxes appear together below preset selection/action rather than beside them. **Settings → Appearance → Data refresh** shows the master refresh checkbox on its own line, with four equally sized refresh selectors in a balanced two-column grid (one column on mobile).
+- **Settings → System information:** removed the dedicated RustProxy process block from this screen while preserving **Statistics source** with the host details. Status/metrics API fields and backend monitoring are unchanged.
+- **Corner-style consistency:** additional Vhost dependent-field groups, cache/legend and metric-info controls, selected admin cards and the self-contained authenticated documentation page follow the saved Square/Subtle/Rounded/Extra corner preference. Circular status indicators/spinners remain circular by design; Original retains its legacy styling.
+- Updated English and Polish labels, documentation and UI regression checks. This is a UI-only release; it does not change backend execution or configuration semantics. Native Rust builds and target-system acceptance remain required.
## 0.34.4 — Multi-profile ACLs, certificate override and mobile Docker details (source candidate)
@@ -14,6 +27,8 @@ Release history focused on **features, settings, default behavior, compatibility
- Docker agent details now wrap certificate SHA-256 fingerprints on narrow screens.
- Ordinary Vhosts and Docker publications may attach up to eight reusable ACL profiles. Legacy `acl_profile_id` remains loadable; new assignments use `acl_profile_ids`. Among selected profiles, an explicit IP deny or blocked country always denies, explicit IP/country allows are unioned, and unmatched defaults must all permit access. Single-profile behavior is unchanged. Inline Vhost/location/global controls are still additional (AND) restrictions, not bypassed. Docker assignments override inherited agent profiles when they select their own profiles.
- `allow_certificate_mismatch` permits a deliberate, confirmed exception per named certificate assignment on an ordinary Vhost or Docker publication. TLS identity/key checks remain intact. Only explicitly selected hostname/certificate pairs skip RustProxy DNS SAN binding verification. Client hostname validation still fails/warns for a mismatched certificate; do not use as a permanent replacement for a matching certificate.
+- Refined Vhost editor layout: separate **SSL certificates** (selection and explicit mismatch consent) from **SSL settings** (TLS policy), and split Traffic policy into ACL, method, IP/country/User-Agent rate limits. Vhost security now separates HSTS, response headers and User-Agent blocking; Custom location access, balancing controls and WebSocket settings have their own groups. Checkbox/button alignment was adjusted in toolbars, dialogs and configuration cards; ordinary UI surface corners follow the selected dashboard rounding style where previously fixed. Backend settings and API semantics are unchanged.
+- Docker publication editor shows the certificate/domain mismatch consent immediately below **Backend host IP**, with a small vertical gap.
- The source release needs native Rust compilation and target-system acceptance tests before production use.
## 0.34.3 — QUIC socket buffering, live HTTP/3 settings and cache outcome mix (source candidate)
diff --git a/Cargo.lock b/Cargo.lock
index 76715ba..355fd0c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1881,7 +1881,7 @@ dependencies = [
[[package]]
name = "rust-proxy"
-version = "0.34.4"
+version = "0.34.5"
dependencies = [
"anyhow",
"arc-swap",
diff --git a/Cargo.toml b/Cargo.toml
index c848c26..d36a5ae 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "rust-proxy"
-version = "0.34.4"
+version = "0.34.5"
edition = "2021"
rust-version = "1.99"
description = "RustProxy - independent reverse proxy with local administration"
diff --git a/README.md b/README.md
index b5bf1a4..e29623b 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# RustProxy 0.34.4
+# RustProxy 0.34.5
RustProxy combines a reverse proxy, static/PHP web server, certificate management and traffic monitoring in one service with a browser administration panel. It supports several domains and backends without requiring a separate proxy configuration language.
@@ -16,11 +16,19 @@ RustProxy combines a reverse proxy, static/PHP web server, certificate managemen
Features and default settings vary by component; configure only the capabilities you need. The [documentation index](docs/README.md) covers each workflow, including prerequisites, security boundaries, troubleshooting and API access.
+## 0.34.5
+
+- Administration UX refinements: clearer grouping for Web Server delivery and PHP-FPM controls, Stream backend and health-check settings, and Redirect/404 host identity versus frontend certificate options.
+- Stream backend probe settings are hidden while health checks are off, and disabled Web Server fields now have a consistent muted appearance. Both UI languages are supported.
+- UX follow-up: Vhost transform/cache overrides now place checkboxes directly above their textareas; inline Vhost IP rules live with ACL profiles under Security. Web Server preset hardening controls and refresh interval grids have clearer alignment, and System information keeps the statistics source without the separate process block.
+- More controls (including the authenticated documentation/Swagger page) follow the saved corner style. Configuration keys, API payloads and proxy behavior are unchanged.
+
## 0.34.4
- Docker vhosts: SHA-256 certificate fingerprints wrap correctly in mobile agent details.
- Ordinary and Docker vhosts support up to eight ACL profiles with deterministic explicit deny priority and merged explicit allows, while keeping standalone ACL layers restrictive. Existing singular ACL configuration remains supported.
- Certificate-to-domain mismatch can be explicitly accepted per ordinary or Docker vhost (with a warning/confirmation); normal validation stays enforced without this override, and TLS clients may still reject the certificate.
+- Editor layout: SSL certificates and TLS settings are separate; Vhost traffic/security, WebSockets, balancing and location access options are grouped by purpose. Docker certificate overrides appear under the backend IP choice, and common controls respect the dashboard corner style.
## 0.34.3
@@ -74,4 +82,4 @@ The authenticated **`/docs`** page includes the guides and a separate interactiv
## Status of this source archive
-Version **0.34.4** is provided as a **source release candidate**. Configuration, ACL and TLS changes require native compilation and integration testing on the target systems before a production deployment is declared. See [Installation](docs/INSTALL.md) and [Roadmap](ROADMAP.md).
+Version **0.34.5** is provided as a **source release candidate**. Configuration, ACL and TLS changes require native compilation and integration testing on the target systems before a production deployment is declared. See [Installation](docs/INSTALL.md) and [Roadmap](ROADMAP.md).
diff --git a/SHA256SUMS b/SHA256SUMS
index 31a6121..a800e8a 100644
--- a/SHA256SUMS
+++ b/SHA256SUMS
@@ -1,12 +1,12 @@
61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
-ad2dc4d62a754442b775ee92f725ee39c68795ff75052cb75d797a95420b7fa2 AGENTS.md
-296249d11082fa03f00d2075755892ada315b190dc88b9dc0e02cc35b716a387 CHANGELOG.md
-9076d0c433202e776b22c667d3ba694f5110a182bbea65931026fc8fe7bc7a13 Cargo.lock
-9709ce361cf2855ff2aa9ad099764b276e81e87f04feb045160b10f575b04f0c Cargo.toml
+c85d98468358b1b6acfae1025db0de42858f4b80418a5e8d6072e9c059f684d2 AGENTS.md
+0370b6470b855ad901c74a8afe1461987ba64fc020f5c71f441f41d5431b334c CHANGELOG.md
+32a94804157a1bdf05335f2eb9fb1fa3c3507511997ca3aef51235884a1300ec Cargo.lock
+f716baecb16304e87bc0627a0d70c5a4a3d1a7e8847987f5a60f1ae1b77f4008 Cargo.toml
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
-f9f9466a7f3dbe89e6d538bfd6a23a3b9e53fae41db34947672d5b3854ede03d README.md
+b2435a90dd36e4a531b23711794bae7fee3c877edf84ee78064e199ac87fccbd README.md
ae6d14ada6fde7d0b8514840b821678418cf5b1816c18434e3b7fb2ae27a0b21 ROADMAP.md
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs
@@ -27,31 +27,32 @@ b7d0466f70b541a705d234fc8a88b99b6197fdd5e682d3a288a78c76764358cf docker-agent/m
e52b31d0abf65ae3034f6cb367cca113633014ce8d1d799ad4dd5624d2c7b8bb docker-agent/network.go
634790f35ff4627df2a78cec2ed371f6e9ec9df00afb386075595fc7f3ed72eb docker-agent/rustproxy-docker-agent.service
5220d669171ec3ad65f50e0bc8de5178dadaa4904def8e1a4f61002aa9e9a55b docker-agent/server.go
-d363e19e27e3d044939a87a58b08a83e9e7999291fdd0f9da59c028386b442e4 docs/ACCESS.md
+30214fb5082c9de0ef90e1f4271825b5bf4c9898dd396ef797daf0a7abb37834 docs/ACCESS.md
64dacde6ce787302b6586f780eae7ef3e30bf81f8072c0b38020ec7a10dc3db0 docs/ACME_PROVIDERS.md
9268ab7fe86683dacd64bafbd9e0edc8956ce9b9ef6277cc6ada61c3fcb84746 docs/API.md
2362ab34020d4e0dcd70e280e9539ef7764b63212856ee08a3a55906c48f6421 docs/API_ACCESS.md
728b15b9825f547f34c4121219784798dd046fbbdd72ece8906d0cd77e8efc64 docs/BACKUP.md
-7e6a7b9421541c94181a712d17f6166a328e7e28c5a1d629e7d335c0ae5aa1de docs/CACHE.md
-e6d9feabc7a4fcb9a56a2028accbab19ca44479ee67deeefd0ab37f29c45cf4a docs/CONFIGURATION.md
+0305f4e464cae51d50ed6a171571515505aab3bc3aacdcfaa632f252460cd4a8 docs/CACHE.md
+c81c6ee84e6af6507926144f8caf1e94daf497a8a0159449a3808088a11ea045 docs/CONFIGURATION.md
80bcc666736b10f6202386d0342e8d6feeba858d77aea310bf3ec6de01c2bfb3 docs/CONTAINER.md
+ddaebc028e42f85652bf0c1fa0d88eb30346e543c66ebba64f22c0e54cd87678 docs/CSS_AUDIT.md
dc670d912a6a946160c2f8528c9bb710ec38ebe406dcb7dcaf18fb2653f667b6 docs/CUSTOM_LOCATIONS.md
9fb2d0bcac70d1883dd16557622851a1f0527b3d3569a4b5177f5e6dedd3c74d docs/DASHBOARD.md
-c0b7f122de42c2db9fc1791d1aa2dd5322a9ad2efcf6c9cbda93aabcc7caf31d docs/DOCKER_DISCOVERY.md
+8a97377f04a12c28ca219accd921e439e67130fed0d87b51495c0e15a626cbc0 docs/DOCKER_DISCOVERY.md
dbae1a739874ac7b61e276c1ad6fd7c3465acc95afa4db3751224c8426ad6569 docs/INSTALL.md
37001cbf8ed248ca05ec40f12dd9876d33f6e0c97dcb21eb2e6f53c3d97a60bb docs/INTEGRATIONS.md
8c5007550ebae8408cbb72a75b2af9894578a1b9acda6bd34bc5f533916ddf72 docs/LOGS.md
888cdc8c56e10b8a7615012e192e085bf2899d0ec9b3ddfb5731e7de6ab5bcde docs/MIGRATION.md
-e4ffd659be1e6c445b582a76643a46408699ef1e929d3a4c6e741d8146ded031 docs/MONITORING.md
+140b95ad778261455f269f2144a1c61c2315bd3b85300fb613822eacddf3cfc2 docs/MONITORING.md
d5894c07c68ec4230987767198434abee13b0e525842fea21bcbe752fa82742b docs/PHP_FPM.md
-14039f58802eddd023bd3ff91aae63cd45aa37685e040d77c271d0bc8dc43aa0 docs/README.md
-7c47aed9c178e8c707b98d1b20ec985c1170f193de0c7b10b682e76da576e764 docs/RESPONSE_DELIVERY.md
+932cdc186b86bb87df63f243fb3238b93ed3ecd77fe0bdc960d6059039eb9909 docs/README.md
+490a6dbe870f2b1fd4ec7816768dde6c86fa7957fc9de2d2f075e123c9222222 docs/RESPONSE_DELIVERY.md
701023a4cdcac145dcb2bc5bb709afe79599a5d70cbda6fdfe19ab39ad703bfc docs/SECURITY.md
-519f9c45fe31ede5afdfb8c4e4ef05f17540843ac93fe8b05ff6c4fbea7bcf68 docs/STREAMS.md
+bbef60ff1771da9f48af812987766e1e479408873ae2f079b13f1d5c62ecc6ff docs/STREAMS.md
5bedc7d8b9fcaa80cacea225b1f6750101a7ac1b34eb9ce58310ec60329e380d docs/TRANSPORT.md
8542c4a5c9d5dfe474459099546ae590b4c4d3f363f0bba9721c6107643c2d3a docs/TROUBLESHOOTING.md
-7c8d40210a2f70258c261e05eeb41955d4f65fcafb3159be8cc127d4cf0b5a81 docs/VHOSTS.md
-f80fe921f0e93f1833ade5740b102c548210519b3b792d386762a57b92ea54cc docs/WEB_SERVER.md
+2fb3da550ff021456fdd5efb869869288cce1f027dafe72be07903fad6a6d1fe docs/VHOSTS.md
+a47b394fbe87b312c89a59b17a58310595ebdaa1f55cfa603bd33e28653ed90f docs/WEB_SERVER.md
5502e8ce44276213803b66c58e3a4d804a1354f5ed5d83f47ee805e8c44ab088 examples/acl-headers.json
2431dd88a2d9ad2cf3cd4b8fcdfa602563ab1013c612d8917fddbe7febfb2dc3 examples/cache-dashboard.json
e31ac05a99615e4ea6c4c9a8b2e090968583fe3959f838a600b5740cb170f8c5 examples/config.json
@@ -76,8 +77,8 @@ cb01fdef286f38b442dd9252c23c4f8e44eb72e9ecdd99adb926e8e9f6318bb1 integrations/r
96563a83e87cfb33e238c2a970ce36b1a1b51e91318d09b123c3755002ba2646 integrations/routeros-oci-build/build-routeros-oci.sh
3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a integrations/routeros-oci-build/docker-entrypoint.sh
13189fa546c3488dddd8efc80c47f6da5241e5fc28b79cbfe795414ea05c7a4f langs/README.md
-62288f4a3a0f2fccee695560f15fdc0fc0f86d2feb4f567e4d3bcf47f05af999 langs/en_US.json
-17c4009784455aaaa8d62d3dd0f7ba70631af2364b54684bacc53b76acb07e31 langs/pl_pl.json
+973795e8af5240a73df8e1c36325f2c023b3d2c40accf1d1894b8a180f93832e langs/en_US.json
+8c9ad51f6624d6ce6f3d0dca7b69fe3e6899c704fb326ee26e8b10d9861be6a5 langs/pl_pl.json
a99ad040be10fa028351e290b9363236b3513a1cadbdc9fb9e0260d517b9b0ae make_zip.py
e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql
20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql
@@ -97,6 +98,7 @@ ee50f38c037765fab742e888d4b9fc4d874c31c785c6064d4eca3d2c4d24d944 nagios/check_r
6e036f736a2b40feb78eec17bf34871771524190f283591d1c89e48e6d2271e6 nagios/check_rustproxy_queues
7458b2d74495e6fb1c1edeea4d5a793705476431dadb60627187111f91441e13 rust-toolchain.toml
bd313cacfc1b450a90368b30ae39ab48b877c8bed3c2db8bbd4e4a1f40865c9e scripts/README.md
+f550ffab868c5241ff5d43d9ae569e19f6842b324b80ecefdc7034642125ed9b scripts/audit_css_usage.py
31953ddf3fda1c9b47fb5de7f85b91a23782b4ccc9555b8423a965659a0a8786 scripts/build/swagger_assets.py
e4d15aa6aa864d04b9b30aca33826b833632af02ca643f8b23e219a5ea1298dd scripts/deploy/deploy.py
4853dbb3385ede933dcf66431eaca3bcfdd091c0f265dd8b34609505d29f0b58 scripts/deploy/deploy_core.py
@@ -142,7 +144,7 @@ c6b552cd5acd9e743c28b1a5564420c214c619fe891ab0561b7d4499aae8d6c0 src/admin/file
c720a7cad6a15c57b1f2ae7b3e5fbf30a97c64afb9137915f37b23f4e576bdc5 src/admin/file_logs_query.rs
4e15827a896c30e8aeba4677bc138695cce61c9feb847de0f3de9d66f542ff7a src/admin/logging_api.rs
8d1cf7855e2b95798819af33a47a3209cf2438a6108c814cd291fe2ca89fa006 src/admin/mod.rs
-ef072f5af05e712750128f791a4ae45c0d741e3144751ec4c0848b4992e839de src/admin/openapi.json
+10a6da3e3d0f0e4ac0c4504a985b52f014c2901235bcaaf9d99d3b244e1cbac4 src/admin/openapi.json
d9120f7dc61d27621c5ac27ea49815dc7945c44d5b99d208da503818c198f649 src/admin/operations.rs
30895e05c5d5d0a26be29f667e0f6d72f16a96a5c8a98e09cd11d8ef2d7b4763 src/admin/panel_assets.rs
75bbedd54fdabb2b489642a642ee7c6533f69cdacb85e251763a56ba11174d5c src/admin/panel_content.rs
@@ -186,10 +188,10 @@ f1a764691f3e2cdb7c6c43e37dae78249561c017a1b74e973a378cb780b72835 src/admin/web/
560a8e047182fbcee08aa4f8d999400a8a5ddca0b6a4640efd46f6b5c34fde91 src/admin/web/dashboard-defaults.json
14213011cfb6ac334c8711b76bbdde97588a7b7ba4eb2592a1e387f877d6cbe0 src/admin/web/dashboard-widgets.json
50d1ebcbaf4d0b2968da23bb943fc3f67131c5d32df3f61d23ff37aad9da5b81 src/admin/web/demo.html
-0c010eb6d463b34f13f98a647ee763ea087112042c1486f577ba6e7025ebf342 src/admin/web/docker.js
-d4aef376cb44b2a13174dca1cb9383f64c04b0b93279f4f0b4805e76002548a1 src/admin/web/docs.css
+9c43c583513ca6707847e11e25f056b32b41758f9804425f5eb80bcdc66bb0df src/admin/web/docker.js
+1d5f8fbf81394a6a8c3c2eff78b48408ef7790c0613905c2c7f584f1548862c6 src/admin/web/docs.css
aeffd403fa1823df7d9fa4c48280b4033b2d15e4cb9f66608caff89290de0a52 src/admin/web/docs.html
-6adb291fcc98d516fd9745d9673db16e408854a32cbf41335ae956bda11d46de src/admin/web/docs.js
+24b6d5676c2e0cd3862851a395c753dfd7e6fb022f7c69267c1e02b728f1d06f src/admin/web/docs.js
c52c430b9a6e584012e6014709e1e16ae7e775b2a5aedff8eb767fa867c1cbdd src/admin/web/drafts.js
007c24189ad7b497d4da024fc52e6c51393f91ee135f525a356e1f14882b7632 src/admin/web/error_400.html
232b766f278ae4424705af351910677fcd7ac9a20bb8320d9a12b763c91219a2 src/admin/web/error_401.html
@@ -206,7 +208,7 @@ e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
7cbdef9192a9dee1f8eac42b85f52b3190f7f6efae50004aa7ad116a910e4036 src/admin/web/file-logs.js
b14e7b3578322f309627f6fe965cc16abcf85da3437c9070be6caecae1908743 src/admin/web/host404.js
-a6d073e94d7fff31b5d590759a5b390558da95e7e37353bf308dbbf302228cc9 src/admin/web/index.html
+289ba997f960b4bb9321c32521c9064bf11f093b2f563b94112f86132cf9a225 src/admin/web/index.html
7915a06cebb3dfa2c51833c326586920691ba4431b1f984429b039333f9c2a93 src/admin/web/integrations.js
7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json
cbd41678eadd994dc5252628ee8ef9551fa9f7d8694ca5abd206240d9a96ad30 src/admin/web/login.html
@@ -216,17 +218,18 @@ c2e5f554810b8e34fb47361fb90a01073a2e332ee26d82e030956fad3ec1f83e src/admin/web/
a8afc6e70950bc70b98725eb049d73cff35dc3a3ee659229a15a8e08d3eb4f12 src/admin/web/partials/overview-metrics.html
3928afce2ed6d5f2d3e29475dd29e4c3bd3c6df180ad5119beaff0c7a58e3b3b src/admin/web/prometheus-token.js
90d9a0836662135b79fb6f183c4f3eff60f8a64392d6c5795ccf1d8ef6862263 src/admin/web/redirects.js
-17784875e475d6df42964d7ecfe50ca82b74bad0e942342bc17537e727224796 src/admin/web/settings.js
+b52db71e9efac0ea0a14fe3c332a6debf28cf762765327be007bf44849704db4 src/admin/web/settings.js
8eadbada619b15087783dcfa0e889a81f88e78034d084c5ab616f7912b835940 src/admin/web/status_200.html
-c13cffe18a616fe5faa5ee945ab0d94d8d392ae8f32474c62fe41010e063ddb1 src/admin/web/streams.js
-2a3c6382a3a65625654c5117f205b062a50e6c6a2b7e0bbaea7b009d786db63c src/admin/web/style.css
+02dc0b1d86046c5f6515e8ca7f4891dd8373a436e479dc575cedaf2f8ef0c80b src/admin/web/streams.js
+7776fa7c002a079f781748097538c6983031623fff714d0ad58c55a196e29c22 src/admin/web/style.css
d290c048450655b3eeae7c6ec54a82d645d1965513694ab29fdf7eef796a1abd src/admin/web/traffic-panel.js
dfb4ded2fefb90fc1e2d33370cdef10b040a85cb43235ea9dbc1d73bef823a79 src/admin/web/transform-panel.js
-fde265c3cdc902f81b6400193d9679e738539f64dee29bb857801b13cbeed8fd src/admin/web/vhosts/core.js
-7a124b972a43b19184cf060ff85244fc9f7c4e0ef9b1bf28dbcb7c9b6dff391f src/admin/web/vhosts/editor.js
+2e323f3fbcd60d606bbeae88dfd024e243727b069c82d9017cfa47acd130bcb0 src/admin/web/unused.css
+ad2a127f83145792d5c658b9e9a3fae42e26bd5ec5339599feed34711133720e src/admin/web/vhosts/core.js
+4f87802573f2e5abebcade4928ab460b0de78b991a96a391485cb36336d61812 src/admin/web/vhosts/editor.js
8338ce13b5e3e3f421437631124cb691aeb5b9e17409d256b4b9bcac457b3c59 src/admin/web/vhosts/health.js
b3239e67901f1796d7a974ade0796f2e3590ff8064628a5629b78c7f6ce2f32e src/admin/web/vhosts/protocol.js
-2170cfddf5c611672de34b0525d36ade8fcbe459cc21269281d6b615d8601b93 src/admin/web/vhosts/rates.js
+8c3da49973c62333559b0e3588c02beb0b425c025eac5cfa82a1bb968f70d9dd src/admin/web/vhosts/rates.js
69c8848159dcc7c4d93f49de3be75553cbecd0731d64302e2b644ae61196e704 src/admin/web/vhosts/render.js
4dfdeb7c73c2775eacaca65fd8ef1dc80b275421b15522d319d338f5b96288cc src/admin/web/web-server-profiles.js
fd476445a88eb40270e706814eee50cbfbba4069a26dd843c095367e55a147a1 src/admin/web/web-server.js
@@ -505,11 +508,15 @@ dc3a18c14c3cc28519103e860b0f42a59d163db15df3f68b76778cdfa6786e46 src/web_server
cf729dc5d00132736521887e77ab0ab5280ede6840e1fb225736ccab09748f22 tests/acl_picker_corner_styles.cjs
2467a6c2295d3fb2ad84cdb4a85176372953034fef2f4d95dc3bf58f019efe21 tests/acl_picker_dom.cjs
8bfee0027ce317f3427ecdf9193549d4a52db58a7b2b99db7cdeebc458d7d4c0 tests/cache_outcomes.cjs
+09d856ae357dfc5b717b806b2fd2f8a6dd67aab76c2bebeb49d13219beb68f9d tests/css_usage_audit.cjs
f00743af2cf4a5952caef7a1dcf68e7817b5599a252a9973e1c22045517c0b0e tests/fixtures/argon2id.json
1725c10331745dc04a07675784d385260da7b14567b8dba3cbae5e891eb5dadf tests/http2.rs
347a4ff9e028a1e999ad129590c355b5734cf928dd81e5099aea0c8aca8d5908 tests/http3.rs
eecf680f001f2110dde4eceaccf2f922859a16c946a644f8854e8de4d3771f7c tests/policies.rs
7a28cfdb43a3cecf90136cf8898c060c382af557ce4ff168a6672391a0e5c26b tests/transport_config.rs
+38a1a0594c4aece0bb31332dffcd37c4080410c75b571f124bd906b12d7c1409 tests/ux_0345.cjs
+ba40e1109d9689c9bf844f228a4632f4c64b1004f4ceccc3b16ee373f4d351d1 tests/ux_0345_followup.cjs
+3421dc1baeb2a9882907b09e816213b375767089c79fce710248c61b9c3e1ae2 tests/vhost_ui_layout.cjs
eca68a07c2b1e45fdb8f512294f5a432db3ae5a6aec8fb200b47f155cd9a31d6 tools/README.md
358c1e94dd2b4a756430e5335747f0b437345b1713670a9972c5bcf78b837ccb tools/audit/admin_audit.py
7c30c280d0b0a8bf855eeffab583ca34dc161255f7ef6124148aa979c17c26d3 tools/dev/memory_load.py
diff --git a/docs/ACCESS.md b/docs/ACCESS.md
index 2476d16..dd6bcae 100644
--- a/docs/ACCESS.md
+++ b/docs/ACCESS.md
@@ -13,6 +13,8 @@ Access control can be configured globally, on a Vhost and on selected Custom loc
Default deny plus explicit allow exceptions is usually safer for private admin applications. Apply rules to a staging client before enforcing them on a live business-critical service.
+In the **Vhost → Security** editor, Traffic policy groups reusable ACL profiles and the separate **inline Vhost IP/CIDR access list** together, followed by HTTP methods, per-IP rates, country policy, country-rate and User-Agent-rate controls. The inline list is not a second version of an assigned profile: both layers remain independently enforced; an allow in one does not cancel a deny in the other. The **Vhost → Authentication** tab is for Basic Auth and Tinyauth only. HSTS, security headers and User-Agent blocking are separate Security groups. **Custom locations → Access** separates its ACL profile, HTTP method, country and inline IP/CIDR fields. These are only layout changes; restriction scopes still combine as described above.
+
## HTTP methods and rate limits
Method policies allow, block or leave methods unrestricted. A denied method returns `405`, while typical ACL/country denials return `403`. Per-client rate limits can configure request count, window and burst with optional country overrides; exceeding the limit returns `429` with `Retry-After`. Limits are application controls, not a replacement for upstream DDoS protection.
diff --git a/docs/CACHE.md b/docs/CACHE.md
index c52612c..ae9f5a9 100644
--- a/docs/CACHE.md
+++ b/docs/CACHE.md
@@ -10,6 +10,10 @@ RustProxy provides an optional shared HTTP response cache with a bounded in-memo
4. Optionally enable HIT-only diagnostics. Send repeated identical requests using `curl`, then inspect **Cache → Domains / Stored objects** for `MISS → HIT`.
5. Check TTL, revalidation and invalidation after updating the origin's content or configuration.
+## Vhost editor options
+
+In **Vhosts → Cache**, overrides for static extensions, bypass path prefixes and unsafe force targets each place the enable/override checkbox immediately above the associated multiline list. The fields are disabled while their individual override is off; their saved values and cache policy are unchanged. Never enable unsafe forced caching for personalized responses.
+
## Stored object rules
- Reuse is scoped by frontend scheme, Host, exact client URL, Vhost/content policy and eligible `Vary` dimensions. HTTP and HTTPS objects do not cross-share.
diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index 46b634f..85f94b6 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -68,7 +68,7 @@ Response transformations are optional and apply only to configured eligible MIME
## Administration preferences
-Appearance settings, Dashboard Presets and Overview cards belong to the administrator profile. Up to 16 named presets can store card visibility, order, widths, density and sample count. Changing the dashboard does not restart proxy listeners or rewrite routing rules. Source sections under `config` have a revision guard to prevent accidental overwrite by a stale editor.
+Appearance settings, Dashboard Presets and Overview cards belong to the administrator profile. **Settings → Appearance → Data refresh** keeps the automatic-refresh toggle separate from four intervals arranged in two equal columns (one on narrow screens). The saved corner style (Square, Subtle, Rounded or Extra) applies to controls and surfaces in the main panel and the authenticated Reader/OpenAPI page. **Original** keeps the legacy appearance. Up to 16 named presets can store card visibility, order, widths, density and sample count. Changing the dashboard does not restart proxy listeners or rewrite routing rules. Source sections under `config` have a revision guard to prevent accidental overwrite by a stale editor.
## Sample files and safe changes
diff --git a/docs/DOCKER_DISCOVERY.md b/docs/DOCKER_DISCOVERY.md
index 8eb58ed..a1ae457 100644
--- a/docs/DOCKER_DISCOVERY.md
+++ b/docs/DOCKER_DISCOVERY.md
@@ -79,7 +79,7 @@ In **Docker vhosts**, select the service and set:
| **Cache / compression / transforms** | Optional; obey the global limits and privacy policy of the application. |
| **Health / ACL / timeouts** | Select up to eight reusable ACL profiles per app or inherit the agent's defaults. A searchable multi-select previews the profile ID, default action, IP rules and country filters; selected profiles appear as removable badges. Optional inline ACL remains independently enforced. Explicit denies and country blocks beat merged allows. Test allowed and blocked clients. |
-To deliberately assign a domain not covered by the agent's **named frontend certificate**, enable **Accept certificate/domain mismatch** in the top-right TLS section of the per-service editor and confirm the risk. This records `allow_certificate_mismatch: true` on that publication only. Without consent, the server rejects the incompatible mapping. The client browser may still reject the TLS handshake; a matching SAN certificate is strongly preferred. The agent's own outbound TLS fingerprint validation is unaffected.
+To deliberately assign a domain not covered by the agent's **named frontend certificate**, enable **Accept certificate/domain mismatch** immediately below **Backend host IP** in the per-service editor and confirm the risk. This records `allow_certificate_mismatch: true` on that publication only. Without consent, the server rejects the incompatible mapping. The client browser may still reject the TLS handshake; a matching SAN certificate is strongly preferred. The agent's own outbound TLS fingerprint validation is unaffected.
Use **Publish** and test the resulting hostname. Edits to an already published service use its own **Apply changes** action; unpublishing does not stop its Docker container. Hidden rows in the page are only a browser view preference, not a security policy.
diff --git a/docs/MONITORING.md b/docs/MONITORING.md
index f53124e..ccc9f5d 100644
--- a/docs/MONITORING.md
+++ b/docs/MONITORING.md
@@ -16,6 +16,8 @@ RustProxy has operational views in the authenticated panel and an optional stand
Overview allows account-specific card layout and history controls; see [Dashboard](DASHBOARD.md). GeoIP/country analytics depend on the installed/update database and whether country capture is enabled.
+**Settings → System information** shows the operating-system/hardware snapshot and **Statistics source**. The old dedicated process-information block is no longer displayed there; this is a panel presentation change only. Process CPU/RAM and related measurements continue to be available in the Overview and authenticated metrics APIs where supported. **Settings → Appearance → Data refresh** stores the per-account refresh intervals; switching off automatic refresh does not disable manual refresh.
+
## Prometheus exporter
The exporter is **disabled by default** and uses a separate HTTP listener, normally `127.0.0.1:9191`, at `/metrics`. It is not protected by the admin browser cookie; it has its **own** client-network and bearer-token policy. In **Settings → Prometheus** enable the endpoint and configure:
diff --git a/docs/README.md b/docs/README.md
index 8ae172d..39940a2 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,6 +1,6 @@
# RustProxy documentation
-Practical guides for **RustProxy 0.34.4**. This index and the listed pages are also available in the authenticated panel at **`/docs → Reader`**. The adjacent **API** tab provides the embedded OpenAPI reference. Paths below are links to actual files in the source archive.
+Practical guides for **RustProxy 0.34.5**. This index and the listed pages are also available in the authenticated panel at **`/docs → Reader`**. The adjacent **API** tab provides the embedded OpenAPI reference. Paths below are links to actual files in the source archive.
## Getting started
diff --git a/docs/RESPONSE_DELIVERY.md b/docs/RESPONSE_DELIVERY.md
index 0126857..2d5d68f 100644
--- a/docs/RESPONSE_DELIVERY.md
+++ b/docs/RESPONSE_DELIVERY.md
@@ -20,7 +20,7 @@ The Web Server can additionally use precompressed `.br`/`.gz` file sidecars when
## Static response transformations
-Under **Settings → Transform**, the engine is disabled by default. Even when enabled globally, **each Vhost must opt in**. The default globally enabled transform types, once the engine is turned on, are JavaScript and CSS. HTML, SVG and JSON transformations are individually optional. A Vhost may override the allowed types and extensions within global restrictions.
+Under **Settings → Transform**, the engine is disabled by default. Even when enabled globally, **each Vhost must opt in**. The default globally enabled transform types, once the engine is turned on, are JavaScript and CSS. HTML, SVG and JSON transformations are individually optional. A Vhost may override the allowed types and extensions within global restrictions. In **Vhosts → Transform**, the optional custom extension list sits immediately below its override checkbox rather than next to the transform-format selectors. The list remains read-only until its override is enabled.
The available resource controls include:
diff --git a/docs/STREAMS.md b/docs/STREAMS.md
index 4497d95..4018802 100644
--- a/docs/STREAMS.md
+++ b/docs/STREAMS.md
@@ -12,6 +12,8 @@ The **Streams** workspace forwards non-HTTP TCP or UDP traffic through dedicated
The source model supports a legacy single-upstream configuration or a backend list (up to the validated per-host limit), with pool-based load balancing and health observations. Do not assume HTTP health-check response codes can be applied to a UDP-only application: use the probe options actually supported by the Stream type.
+The backend editor groups connection/capacity settings separately from active health-check options. When **Health checks** is Off, probe tuning fields are hidden; selecting TCP connect/UDP probe reveals them without erasing their values. Hidden fields are excluded from browser validation.
+
## Behavior and boundaries
- **TCP** forwards a bidirectional byte stream. The proxy does not interpret application messages or terminate application TLS.
diff --git a/docs/VHOSTS.md b/docs/VHOSTS.md
index 4438cbb..3f1c59a 100644
--- a/docs/VHOSTS.md
+++ b/docs/VHOSTS.md
@@ -44,6 +44,8 @@ Backend pools support scheduling/weighting, standby members, draining and option
- `backup` members act as fallbacks; `drain` prevents new normal selection while letting existing work complete within normal lifetimes. Explicitly pinned backends need careful policy review.
- Concurrency and queue limits protect overloaded origins. Retries are restricted; do not assume non-idempotent requests are automatically replayed.
+The **Redirects** and **404 hosts** editors distinguish host identity/domains from frontend listeners and certificate selection. The configuration options have not changed.
+
## Hostnames, redirects and errors
- `force_ssl` redirects matching plaintext traffic to HTTPS using `308`, preserving path/query; configure both HTTP and HTTPS frontends.
@@ -56,6 +58,12 @@ Backend pools support scheduling/weighting, standby members, draining and option
Keep Vhost IDs stable for monitoring/history unless renaming is intentional. Renaming may reset the runtime identity and invalidate incompatible cache objects. Confirm the deployed scheme and Host, HTTPS trust, method/access restrictions, health status and logs after saving. For per-path behavior see [Custom locations](CUSTOM_LOCATIONS.md); for policies see [Access](ACCESS.md); for caching see [Cache](CACHE.md).
+## Editor layout
+
+Within the Vhost editor, **SSL certificates** contains the named certificate selection and the explicit certificate/domain mismatch exception. The next **SSL settings** section contains the optional override of the global TLS 1.2/1.3 and cipher policy. Turning off the TLS override retains the inherited global policy. These two sections are on the same **SSL** tab.
+
+The **Security** tab groups Traffic policy by reusable ACL profiles, HTTP methods, per-IP limits, country access, country-specific limits and User-Agent limits. It also separates HSTS, other security response headers and per-Vhost User-Agent blocking. Within **Custom locations → Access**, profile, HTTP method, country and inline IP/CIDR rules are likewise separated. **Details** separates base Vhost properties from WebSocket options; **Backends** separates balancing strategy, retry behavior and keepalive/connection reuse. The visual grouping does not change saved configuration keys or access precedence.
+
## Several ACL profiles and intentional TLS mismatch
Assign up to eight ACL profiles using the Vhost security tab. Explicit deny/blocked country wins, matching explicit allows combine across those profiles, and unmatched defaults must all allow. The legacy single `acl_profile_id` field still works. Inline, location and global policies are independently enforced. See [Access](ACCESS.md) for precedence and safe examples.
diff --git a/docs/WEB_SERVER.md b/docs/WEB_SERVER.md
index cb720ec..ad8de50 100644
--- a/docs/WEB_SERVER.md
+++ b/docs/WEB_SERVER.md
@@ -34,12 +34,16 @@ In **Web Server → Edit service → Delivery**, `sendfile` supports `off` (defa
## Rewrites and application presets
-The editor supports common website presets to fill suggested fields; review their document root, `try_files`, MIME and PHP values before saving. Presets do not install WordPress, Roundcube or other applications. For a single-page app, a fallback similar to `$uri`, `$uri/`, `/index.html` serves the SPA entry for routes without an existing file. For PHP frameworks, use the appropriate front controller; see [PHP-FPM](PHP_FPM.md).
+The editor supports common website presets to fill suggested fields; review their document root, `try_files`, MIME and PHP values before saving. The two independent hardening checkboxes (block common attack probes and remove fingerprinting headers) are directly below the preset selection and Apply action; the chosen values remain editable. Presets do not install WordPress, Roundcube or other applications. For a single-page app, a fallback similar to `$uri`, `$uri/`, `/index.html` serves the SPA entry for routes without an existing file. For PHP frameworks, use the appropriate front controller; see [PHP-FPM](PHP_FPM.md).
## `.htaccess` compatibility
An optional bounded Apache-style `.htaccess` compatibility layer supports a **subset** of rules useful for migrations, not arbitrary Apache modules or dynamic directive execution. It can scan configured paths and enforce stricter handling of unsupported expressions. Prefer explicit `try_files`/rewrite/header controls when possible and test migrated sites. A real Apache configuration cannot be pasted unchanged into RustProxy.
+## Admin editor layout
+
+In **Web Server → Edit → Delivery**, static file delivery, dynamic compression and precompressed assets have separate sections. In **PHP-FPM**, profile/endpoint, request options, resource limits and FastCGI/php.ini overrides are grouped. Disabled local controls indicate that PHP is off or a shared profile owns those settings; selection and saved values behave as before.
+
## PHP and error responses
A Web Service may enable PHP via a shared PHP-FPM profile or its own configuration. PHP is executed by the FPM service, not RustProxy, and must not be exposed as a raw Internet port. Use dedicated pools for different applications. See [PHP-FPM](PHP_FPM.md).
diff --git a/langs/en_US.json b/langs/en_US.json
index 640472a..92ca181 100644
--- a/langs/en_US.json
+++ b/langs/en_US.json
@@ -6,6 +6,16 @@
"direction": "ltr"
},
"translations": {
+ "webServiceStaticDeliverySection": "Static file delivery",
+ "webServiceDynamicCompressionSection": "Dynamic compression",
+ "webServiceSidecarsSection": "Precompressed assets",
+ "webServicePhpSourceSection": "PHP-FPM source and endpoint",
+ "webServicePhpRequestSection": "Request handling",
+ "webServicePhpLimitsSection": "Timeouts and capacity",
+ "webServicePhpOverridesSection": "FastCGI and php.ini overrides",
+ "hostIdentitySection": "Host and domains",
+ "streamBackendConnectionSection": "Backend and capacity",
+ "streamBackendHealthSection": "Health checks",
"fileLogsSortAscending": "Sort ascending",
"fileLogsSortDescending": "Sort descending",
"policyRulesTitle": "Rules & headers",
@@ -586,7 +596,7 @@
"certDuplicate": "This certificate ID exists. Use a new ID for safe rotation.",
"certManage": "Manage certificate library",
"certSelectHint": "Select an imported certificate. Uploads and rotation are in the Certificates tab. Assignments are validated against vhost names.",
- "vhTinyauth": "Authentication and IP access",
+ "vhTinyauth": "Authentication",
"vhTabAuth": "Authentication",
"taEnable": "Enable Tinyauth protection",
"taUrl": "Tinyauth URL / verification endpoint",
@@ -874,6 +884,20 @@
"advanced": "Advanced",
"docsLicenses": "Swagger UI licenses",
"vhTrafficPolicy": "Traffic policy",
+ "vhGeneralDetailsSection": "General settings",
+ "vhWebsocketOptionsSection": "WebSocket options",
+ "vhBalancerAlgorithmSection": "Load balancing strategy",
+ "vhBalancerRetrySection": "Retry policy",
+ "vhBalancerKeepaliveSection": "Keepalive and connection reuse",
+ "vhSecurityHstsSection": "HSTS",
+ "vhSecurityHeadersSection": "Security headers",
+ "vhUaBlockSection": "User-Agent blocking",
+ "vhLocationIpAclSection": "Local IP/CIDR rules",
+ "vhSslSettings": "SSL settings",
+ "vhTrafficMethodsSection": "HTTP methods",
+ "vhTrafficRateSection": "Per-client IP rate limiting",
+ "vhTrafficCountrySection": "Country access",
+ "vhTrafficCountryRatesSection": "Country-specific rate limits",
"vhHttp3Enabled": "Enable HTTP/3 for this vhost",
"vhCloudflareClientIp": "Use verified Cloudflare client IP",
"vhTimingHeaders": "Expose Server-Timing diagnostics",
@@ -1071,6 +1095,7 @@
"vhAclProfile": "ACL profiles",
"vhAclProfileNone": "No reusable profile",
"vhAclProfileHelp": "Select up to eight reusable ACL profiles. Hover or open the selector to preview their default action, IP rules and country restrictions. Inline Vhost ACLs and other restrictions still apply separately.",
+ "vhInlineAclHelp": "Inline IP/CIDR rules apply independently of reusable ACL profiles and can further restrict access. An allow in one layer does not override a deny in another.",
"multiAclWarning": "Multiple ACL profiles: explicit IP deny and blocked country take priority over allows in every selected profile. Explicit allows are combined (OR); if no rule matches, all profile defaults must allow. Vhost inline ACL, location ACL and global host ACL still apply separately (AND). Avoid mixing allow-all policies with denies: it is easy to expose or accidentally block access.",
"vhCertificateMismatchForce": "Force an incompatible certificate (explicit risk acceptance)",
"vhCertificateMismatchWarning": "This allows RustProxy to publish this name with a certificate that does not cover it. Browsers and TLS clients will still reject or warn about the hostname mismatch. Use only when necessary; obtain a matching certificate whenever possible.",
diff --git a/langs/pl_pl.json b/langs/pl_pl.json
index dacd515..45f4825 100644
--- a/langs/pl_pl.json
+++ b/langs/pl_pl.json
@@ -6,6 +6,16 @@
"direction": "ltr"
},
"translations": {
+ "webServiceStaticDeliverySection": "Dostarczanie plików statycznych",
+ "webServiceDynamicCompressionSection": "Kompresja dynamiczna",
+ "webServiceSidecarsSection": "Gotowe pliki skompresowane",
+ "webServicePhpSourceSection": "Źródło konfiguracji i połączenie PHP-FPM",
+ "webServicePhpRequestSection": "Obsługa żądań",
+ "webServicePhpLimitsSection": "Limity czasu i obciążenia",
+ "webServicePhpOverridesSection": "Parametry FastCGI i ustawienia php.ini",
+ "hostIdentitySection": "Host i domeny",
+ "streamBackendConnectionSection": "Backend i obciążenie",
+ "streamBackendHealthSection": "Kontrola dostępności",
"fileLogsSortAscending": "Sortuj rosnąco",
"fileLogsSortDescending": "Sortuj malejąco",
"policyRulesTitle": "Reguły i nagłówki",
@@ -586,7 +596,7 @@
"certDuplicate": "Ten identyfikator certyfikatu istnieje. Do bezpiecznej rotacji użyj nowego.",
"certManage": "Zarządzaj biblioteką certyfikatów",
"certSelectHint": "Wybierz zaimportowany certyfikat. Import i rotacja są w zakładce Certyfikaty. Przypisania są sprawdzane względem nazw vhosta.",
- "vhTinyauth": "Uwierzytelnianie i dostęp IP",
+ "vhTinyauth": "Uwierzytelnianie",
"vhTabAuth": "Uwierzytelnianie",
"taEnable": "Włącz ochronę Tinyauth",
"taUrl": "URL Tinyauth / punkt weryfikacji",
@@ -874,6 +884,20 @@
"advanced": "Zaawansowane",
"docsLicenses": "Licencje Swagger UI",
"vhTrafficPolicy": "Polityka ruchu",
+ "vhGeneralDetailsSection": "Ustawienia podstawowe",
+ "vhWebsocketOptionsSection": "Opcje WebSocket",
+ "vhBalancerAlgorithmSection": "Strategia równoważenia ruchu",
+ "vhBalancerRetrySection": "Ponawianie żądań",
+ "vhBalancerKeepaliveSection": "Keepalive i ponowne użycie połączeń",
+ "vhSecurityHstsSection": "HSTS",
+ "vhSecurityHeadersSection": "Nagłówki bezpieczeństwa",
+ "vhUaBlockSection": "Blokowanie User-Agent",
+ "vhLocationIpAclSection": "Lokalne reguły IP/CIDR",
+ "vhSslSettings": "Ustawienia SSL",
+ "vhTrafficMethodsSection": "Metody HTTP",
+ "vhTrafficRateSection": "Limity zapytań dla adresów IP",
+ "vhTrafficCountrySection": "Dostęp według kraju",
+ "vhTrafficCountryRatesSection": "Limity zapytań dla krajów",
"vhHttp3Enabled": "Włącz HTTP/3 dla tego vhosta",
"vhCloudflareClientIp": "Używaj zweryfikowanego IP klienta z Cloudflare",
"vhTimingHeaders": "Udostępniaj diagnostykę Server-Timing",
@@ -1071,6 +1095,7 @@
"vhAclProfile": "Profile ACL",
"vhAclProfileNone": "Bez wspólnego profilu",
"vhAclProfileHelp": "Wybierz maksymalnie 8 profili ACL. Rozwiń listę, by zobaczyć domyślną akcję, reguły IP i ograniczenia krajów. Lokalne ACL Vhosta i pozostałe ograniczenia nadal działają niezależnie.",
+ "vhInlineAclHelp": "Lokalne reguły IP/CIDR są niezależne od profili ACL i również ograniczają dostęp. Zezwolenie w jednym miejscu nie znosi blokady w drugim.",
"multiAclWarning": "Wiele profili ACL: jawne blokady IP i zablokowane kraje mają pierwszeństwo przed zezwoleniami we wszystkich profilach. Jawne zezwolenia są łączone (LUB); gdy żadna reguła nie pasuje, wszystkie wartości domyślne muszą zezwalać. Lokalne ACL vhosta, lokalizacje i globalne ACL pozostają dodatkowymi ograniczeniami (ORAZ). Nie zaleca się łączenia reguł „zezwól wszystkim” i „blokuj” — łatwo przypadkowo otworzyć albo zablokować dostęp.",
"vhCertificateMismatchForce": "Wymuś niedopasowany certyfikat (świadoma akceptacja ryzyka)",
"vhCertificateMismatchWarning": "RustProxy pozwoli przypisać domenę do certyfikatu, który jej nie obejmuje. Przeglądarki i klienci TLS nadal mogą odrzucić połączenie z powodu niezgodności nazwy. Zalecany jest prawidłowy certyfikat.",
diff --git a/scripts/audit_css_usage.py b/scripts/audit_css_usage.py
new file mode 100755
index 0000000..95cb60a
--- /dev/null
+++ b/scripts/audit_css_usage.py
@@ -0,0 +1,166 @@
+#!/usr/bin/env python3
+"""Conservative CSS reference audit for the RustProxy administration UI.
+
+Requires: pip install tinycss2 cssselect2 (audit-only tooling, not runtime deps).
+Output is an inventory, NOT permission to remove dynamically generated styles.
+Selectors are considered dead only when a required class/ID token is absent
+from all application templates and scripts; :not() is not treated as a requirement.
+The Swagger UI bundle emits its own markup, so docs.css is reviewed separately.
+"""
+from __future__ import annotations
+
+import argparse
+from collections import Counter
+import json
+from pathlib import Path
+import re
+
+try:
+ import tinycss2
+ from cssselect2 import parser as css_parser
+except ImportError as exc:
+ raise SystemExit("Install audit dependencies: python3 -m pip install tinycss2 cssselect2") from exc
+
+ROOT = Path(__file__).resolve().parents[1]
+WEB = ROOT / 'src/admin/web'
+STYLES = ('style.css', 'docs.css')
+SOURCE_SUFFIXES = {'.js', '.html', '.json', '.rs'}
+UNSUPPORTED_PSEUDO_ELEMENTS = re.compile(r'::(?:-webkit-[\w-]+|-moz-[\w-]+|backdrop)\b')
+WORD_PATTERN = re.compile(r'(? set[str]:
+ paths = list(WEB.rglob('*.js')) + list(WEB.rglob('*.html'))
+ if scope == 'panel':
+ paths += list(WEB.rglob('*.json')) + list((ROOT / 'src').rglob('*.rs'))
+ else:
+ paths = [WEB / 'docs.html', WEB / 'docs.js']
+ words: set[str] = set()
+ for path in paths:
+ words.update(WORD_PATTERN.findall(path.read_text(encoding='utf-8', errors='replace')))
+ return words
+
+
+def selector_parts(tokens: list) -> list[str]:
+ parts: list[list] = [[]]
+ for tok in tokens:
+ if tok.type == 'literal' and tok.value == ',':
+ parts.append([])
+ else:
+ parts[-1].append(tok)
+ return [tinycss2.serialize(part).strip() for part in parts if tinycss2.serialize(part).strip()]
+
+
+def rules(stylesheet: str):
+ def walk(items):
+ for item in items:
+ if item.type == 'qualified-rule':
+ yield item.source_line, selector_parts(item.prelude)
+ elif item.type == 'at-rule' and item.lower_at_keyword in ('media', 'container', 'supports', 'layer') and item.content:
+ yield from walk(tinycss2.parse_rule_list(item.content, skip_whitespace=True, skip_comments=True))
+ return list(walk(tinycss2.parse_stylesheet(stylesheet, skip_whitespace=True, skip_comments=True)))
+
+
+def token_refs(node) -> list[tuple[str, str]]:
+ if isinstance(node, css_parser.ClassSelector):
+ return [('class', node.class_name)]
+ if isinstance(node, css_parser.IDSelector):
+ return [('id', node.ident)]
+ if isinstance(node, css_parser.AttributeSelector) and node.operator in ('=', '~=') and node.value:
+ if node.name in ('class', 'id'):
+ return [(node.name, node.value)]
+ return []
+ out = []
+ for field in ('parsed_tree', 'left', 'right', 'simple_selectors', 'selector', 'selector_list'):
+ value = getattr(node, field, None)
+ for child in (value if isinstance(value, (tuple, list)) else [value]):
+ if child is not None:
+ out.extend(token_refs(child))
+ return out
+
+
+def possibly_matches(node, words: set[str]) -> bool:
+ # Deliberately conservative: class/id absence proves a positive selector
+ # impossible but does NOT make a selector in :not() impossible.
+ if isinstance(node, css_parser.ClassSelector):
+ return node.class_name in words
+ if isinstance(node, css_parser.IDSelector):
+ return node.ident in words
+ if isinstance(node, css_parser.AttributeSelector):
+ if node.name in ('class', 'id') and node.operator in ('=', '~=') and node.value:
+ return node.value in words
+ return True
+ if isinstance(node, css_parser.NegationSelector):
+ return True
+ if isinstance(node, (css_parser.MatchesAnySelector, css_parser.SpecificityAdjustmentSelector, css_parser.RelationalSelector)):
+ return any(possibly_matches(child, words) for child in node.selector_list)
+ if isinstance(node, (css_parser.CombinedSelector, css_parser.CompoundSelector)):
+ children = ([node.left, node.right] if isinstance(node, css_parser.CombinedSelector)
+ else node.simple_selectors)
+ return all(possibly_matches(child, words) for child in children)
+ if isinstance(node, css_parser.Selector):
+ return possibly_matches(node.parsed_tree, words)
+ if isinstance(node, css_parser.RelativeSelector):
+ return possibly_matches(node.selector, words)
+ # Pseudo-classes, type selectors, vendor syntax and other unknowns: KEEP.
+ return True
+
+
+def audit_file(path: Path, words: set[str], scope: str) -> dict:
+ raw = path.read_text(encoding='utf-8')
+ entries = []
+ all_classes: set[str] = set()
+ unknown = []
+ for line, selectors in rules(raw):
+ parts = []
+ for selector in selectors:
+ text = UNSUPPORTED_PSEUDO_ELEMENTS.sub('', selector)
+ try:
+ parsed = list(css_parser.parse(text))
+ references = set().union(*(set(token_refs(item)) for item in parsed))
+ all_classes.update(word for typ, word in references if typ == 'class')
+ dead = bool(parsed) and not any(possibly_matches(item, words) for item in parsed)
+ # docs.css targets classes emitted by the third-party Swagger UI bundle.
+ external = scope == 'docs' and '.swagger-ui' in selector
+ if external:
+ dead = False
+ missing = sorted({f'{typ}:{word}' for typ, word in references if word not in words})
+ parts.append({'selector': selector, 'dead': dead, 'missing_tokens': missing,
+ 'external': external})
+ except css_parser.SelectorError as exc:
+ parts.append({'selector': selector, 'dead': False, 'missing_tokens': [], 'unparsed': str(exc)})
+ unknown.append({'line': line, 'selector': selector, 'error': str(exc)})
+ entries.append({'line': line, 'parts': parts})
+ dead_parts = [p for e in entries for p in e['parts'] if p['dead']]
+ kept_parts = [p for e in entries for p in e['parts'] if not p['dead']]
+ return {
+ 'file': str(path.relative_to(ROOT)), 'bytes_before': len(raw.encode('utf-8')),
+ 'total_rules': len(entries), 'total_selectors': len(dead_parts) + len(kept_parts),
+ 'referenced_classes': sorted(all_classes & words),
+ 'unreferenced_classes': sorted(all_classes - words),
+ 'unused_selector_count': len(dead_parts), 'active_selector_count': len(kept_parts),
+ 'unparsed': unknown, 'entries': entries,
+ }
+
+
+def main():
+ cli = argparse.ArgumentParser(description=__doc__)
+ cli.add_argument('--json', type=Path, help='Write complete selector audit JSON to this file')
+ args = cli.parse_args()
+ results = [audit_file(WEB / filename, source_words('docs' if filename == 'docs.css' else 'panel'),
+ 'docs' if filename == 'docs.css' else 'panel') for filename in STYLES]
+ for r in results:
+ print(f"{r['file']}: {r['total_rules']} rules, {r['total_selectors']} selector branches, "
+ f"{len(r['referenced_classes'])} referenced classes, {len(r['unreferenced_classes'])} unreferenced classes, "
+ f"{r['unused_selector_count']} provably dead selector branches, {len(r['unparsed'])} unparsed")
+ print(' Unreferenced:', ', '.join(r['unreferenced_classes']) or '(none)')
+ for e in r['entries']:
+ for p in e['parts']:
+ if p['dead']:
+ print(f" {e['line']}: {p['selector'][:170]} ({', '.join(p['missing_tokens'])})")
+ if args.json:
+ args.json.write_text(json.dumps(results, indent=2, ensure_ascii=False) + '\n', encoding='utf-8')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/src/admin/openapi.json b/src/admin/openapi.json
index b5680e2..814616c 100644
--- a/src/admin/openapi.json
+++ b/src/admin/openapi.json
@@ -2,7 +2,7 @@
"openapi": "3.0.3",
"info": {
"title": "RustProxy Administration API",
- "version": "0.34.4",
+ "version": "0.34.5",
"description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust. The API also permits explicitly scoped GeneralApiBearer tokens on a narrow route allowlist, with RO/RW and source CIDR checks; API-token management and Prometheus-token management are browser-session-only."
},
"servers": [
diff --git a/src/admin/web/docker.js b/src/admin/web/docker.js
index 5258cb8..9ae96be 100644
--- a/src/admin/web/docker.js
+++ b/src/admin/web/docker.js
@@ -342,6 +342,8 @@
mismatchLabel.title = t(certMismatch.disabled ? "dockerCertMismatchUnavailable" : "dockerCertMismatchWarning");
const certificateBar = el("div", undefined, "docker-service-certificate-bar");
certificateBar.append(mismatchLabel);
+ const backendIpSettings = el("div", undefined, "docker-backend-ip-settings");
+ backendIpSettings.append(labeled("dockerBackendIp", target), certificateBar);
const h2Enabled = input("", "checkbox", "docker-http2-enabled"); h2Enabled.checked = assignment?.http2_enabled !== false;
// New publications default to H3 off; legacy assignments keep their saved behavior.
@@ -353,8 +355,8 @@
h1.append(el("strong", "HTTP/1.1"), el("small", t("dockerHttp1Always"), "fine"));
protocolOptions.append(h1, checkboxField("dockerHttp2Enabled", h2Enabled), checkboxField("dockerHttp3Enabled", h3Enabled));
protocolBox.append(protocolSummary, protocolOptions);
- route.append(labeled("dockerHostname", host, t("dockerHostnameHelp").replace("{domain}", agent.base_domain)), aliases.node, labeled("dockerBackendIp", target), labeled("dockerBackendScheme", scheme), switches);
- editor.append(certificateBar, route, protocolBox, aclEditor(assignment, agent), timeoutEditor(assignment), healthEditor(assignment), passiveHealthEditor(assignment));
+ route.append(labeled("dockerHostname", host, t("dockerHostnameHelp").replace("{domain}", agent.base_domain)), aliases.node, backendIpSettings, labeled("dockerBackendScheme", scheme), switches);
+ editor.append(route, protocolBox, aclEditor(assignment, agent), timeoutEditor(assignment), healthEditor(assignment), passiveHealthEditor(assignment));
const actions = el("div", undefined, "docker-service-actions"), apply = el("button", t("dockerApplyService"), "outline docker-apply"); apply.type = "button"; apply.disabled = !publish.checked; apply.addEventListener("click", async () => { apply.disabled = true; try { await saveAssignment(agent.id, row, true); } catch (error) { toast(error.message, true); } finally { if (row.isConnected) apply.disabled = !publish.checked; } }); actions.append(apply); editor.append(actions); row.append(editor);
function updateHint() { const targetLabel = target.selectedOptions?.[0]?.textContent || t("dockerBackendIpAuto"), aliasCount = aliases.count(); editorHint.textContent = `${host.value.trim() || technical} → ${scheme.value.toUpperCase()} · ${targetLabel} · H1${h2Enabled.checked ? "/H2" : ""}${h3Enabled.checked ? "/H3" : ""}${aliasCount ? ` · +${aliasCount} ${t("dockerAliasesShort")}` : ""}`; } updateHint();
for (const control of [host, aliases.input, target, scheme]) { control.addEventListener("input", updateHint); control.addEventListener("change", updateHint); }
diff --git a/src/admin/web/docs.css b/src/admin/web/docs.css
index 307698d..5217607 100644
--- a/src/admin/web/docs.css
+++ b/src/admin/web/docs.css
@@ -89,7 +89,7 @@ main {
.swagger-ui input,
.swagger-ui textarea,
.swagger-ui .dialog-ux .modal-ux {
- border-radius: 0
+ border-radius: var(--rp-docs-radius-surface, 0)
}
.swagger-ui .opblock .opblock-summary {
@@ -343,4 +343,43 @@ main {
.markdown-reader {
padding: 18px
}
-}
\ No newline at end of file
+}
+/* Authenticated documentation uses the same stored corner preference as the
+ main panel, while retaining its original flat shape for "Original". */
+html[data-corner-style=square] {
+ --rp-docs-radius-surface: 0px;
+ --rp-docs-radius-control: 0px;
+}
+html[data-corner-style=subtle] {
+ --rp-docs-radius-surface: 5px;
+ --rp-docs-radius-control: 4px;
+}
+html[data-corner-style=rounded] {
+ --rp-docs-radius-surface: 12px;
+ --rp-docs-radius-control: 8px;
+}
+html[data-corner-style=extra] {
+ --rp-docs-radius-surface: 18px;
+ --rp-docs-radius-control: 12px;
+}
+
+.docs-session,
+.docs-api-controls,
+.reader-sidebar,
+.markdown-reader,
+.docs-confirm,
+#docs-error {
+ border-radius: var(--rp-docs-radius-surface, 0);
+}
+
+.docs-bar button,
+.docs-confirm-actions button,
+.docs-tabs button,
+.reader-sidebar input,
+.reader-list button,
+.swagger-ui .btn,
+.swagger-ui select,
+.swagger-ui input,
+.swagger-ui textarea {
+ border-radius: var(--rp-docs-radius-control, 0);
+}
diff --git a/src/admin/web/docs.js b/src/admin/web/docs.js
index dfb9903..7e1b1a2 100644
--- a/src/admin/web/docs.js
+++ b/src/admin/web/docs.js
@@ -1,6 +1,11 @@
"use strict";
(() => {
const element = id => document.getElementById(id);
+ const corners = new Set(["default", "square", "subtle", "rounded", "extra"]);
+ try {
+ const savedCorner = localStorage.getItem("rp-corner-style");
+ document.documentElement.dataset.cornerStyle = corners.has(savedCorner) ? savedCorner : "default";
+ } catch { document.documentElement.dataset.cornerStyle = "default"; }
let session = null, spec = null, ui = null, translations = {}, fallback = {}, epoch = 0;
let documents = [], docsByPath = new Map(), currentDocument = null, activeTab = "reader";
let channel = null; try { channel = new BroadcastChannel("rustproxy-admin-session"); } catch { }
diff --git a/src/admin/web/index.html b/src/admin/web/index.html
index 6f8866a..64d2dd1 100644
--- a/src/admin/web/index.html
+++ b/src/admin/web/index.html
@@ -1922,7 +1922,7 @@
Per-account refresh intervals. Off pauses
all automatic sources; manual refresh remains available.
-
+
System uptime-
-
-
RustProxy process
-
-
RustProxy
- version-
-
Process
- uptime-
-
Process RAM
- (RSS)-
-
Process
- threads-
-
Open file
- descriptors-
Statistics
source-
@@ -3550,11 +3537,13 @@
Preset
action
-
-
+
+
+
+
Presets fill normal Web Service settings and remain fully editable. Document root,
service ID/name and PHP-FPM endpoint are not replaced.
@@ -3629,167 +3618,235 @@
responses are never stored.
-
-
-
-
-
-
-
-
-
-
-
-
-
Static and precompressed representations
- keep validators, single-range requests, 206/416 and streaming delivery.
+
+
Static file delivery
+
+
+
+
Static and precompressed
+ representations
+ keep validators, single-range requests, 206/416 and streaming delivery.
+
+
+
+
Dynamic compression
+
+
+
+
+
+
+
+
+
+
+
+
Precompressed assets
+
+
+
+
+
+
-
-
-
-
- i
-
-
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.
+
+
PHP-FPM source and endpoint
+
+
+
+
+
+ i
+
+
+
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.
+
+
-
-
-
-
-
-
- i
-
-
Passes the incoming Authorization
- header to PHP as HTTP_AUTHORIZATION. Enable for API/Bearer/Basic authentication;
- disable if the application does not need it.
+
+
+
+
+
+
Request handling
+
+
+
+ i
+
+
Passes the incoming
+ Authorization
+ header to PHP as HTTP_AUTHORIZATION. Enable for API/Bearer/Basic
+ authentication;
+ disable if the application does not need it.
+
+
-
-
-
-
- i
-
-
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.
+
+
+ i
+
+
+
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.
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
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.
+
+
+
+
Timeouts and capacity
+
+
+
+
+
+
+
+
+
+
+
FastCGI and php.ini overrides
+
+
+
+
+
+
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.