diff --git a/AGENTS.md b/AGENTS.md index 6aaa0f3..aca5a61 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # RustProxy source and documentation maintenance -Current source version: **0.34.10**. The Cargo package and binary are named `rust-proxy`. +Current source version: **0.34.11**. 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 d7254e6..4c8d7f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # 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.10. +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.11. + +## 0.34.11 — adaptive Vhost request-rate scaling + +- **Vhosts and Docker Vhosts:** both live **Lifetime request rate** sparklines now use one adaptive Y-axis calculation. The scale follows the 85th percentile of the last 24 displayed rate samples, with 25% headroom and a minimum of 1 req/s, instead of using the maximum across the entire session history. This prevents one or a few exceptional spikes from flattening normal traffic. It expands for sustained higher rates and shrinks as traffic returns to normal. +- **Peak visibility:** readings above the dynamic range are clipped only in the SVG plot; the numeric peak still shows the real maximum of the retained browser-side history. Hovering the rate value and the chart's accessible description expose the current Y range and clipping notice. Rate sampling, Docker agent protocol, stored counters, API and other Overview chart scaling are unchanged. ## 0.34.10 — path permission helpers and automatic certificate SNI diff --git a/Cargo.lock b/Cargo.lock index 439a1d1..0223f73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1925,7 +1925,7 @@ dependencies = [ [[package]] name = "rust-proxy" -version = "0.34.10" +version = "0.34.11" dependencies = [ "anyhow", "arc-swap", diff --git a/Cargo.toml b/Cargo.toml index e81674d..4c7501a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-proxy" -version = "0.34.10" +version = "0.34.11" edition = "2021" rust-version = "1.99" description = "RustProxy - independent reverse proxy with local administration" diff --git a/README.md b/README.md index 89db7cc..241cc73 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# RustProxy 0.34.10 +# RustProxy 0.34.11 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,6 +16,10 @@ 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.11 — adaptive live request-rate charts + +- **Vhosts and Docker Vhosts:** the lifetime request-rate sparkline now rescales with recent traffic. Isolated extreme peaks no longer flatten ordinary request activity; sustained traffic growth expands the scale and it shrinks again as traffic drops. Samples above the visible range are capped at the top of the plot, while the text legend retains the real recorded maximum. Hover the value to see the current scale range. Existing statistics collection, request counters and metrics APIs are unchanged. See [Vhosts](docs/VHOSTS.md) and [Docker discovery](docs/DOCKER_DISCOVERY.md). + ## 0.34.10 — filesystem permissions and certificate names - **Certificates → Existing files:** permission commands are generated from the entered external certificate/key paths and displayed **below both path fields**. The command block keeps the previous panel styling, with account selection and guidance under **Options and help**. Commands grant directory traversal and read access to the resolved files for the `rustproxy` service account. After the public certificate can be read, its DNS SAN names automatically fill the legacy SNI field; manual edits are preserved, and **Read names from certificate** retries after permissions are corrected. See [Certificates](docs/ACME_PROVIDERS.md). @@ -119,4 +123,4 @@ ownership, cascade rules, duplicate checks and browser regression commands. ## Status of this source archive -Version **0.34.10** is provided as a **source-only release candidate**. It retains the experimental PQC implementation introduced in 0.34.9, whose dependencies were provisionally locked using published crate metadata. Native build and protocol acceptance remain required. Run `cargo metadata --locked`, `cargo check --locked --all-targets`, `cargo test --locked --all-targets` and protocol compatibility checks on your target system before production deployment. See [Installation](docs/INSTALL.md) and [Roadmap](ROADMAP.md). +Version **0.34.11** is provided as a **source-only release candidate**. It retains the experimental PQC implementation introduced in 0.34.9, whose dependencies were provisionally locked using published crate metadata. Native build and protocol acceptance remain required. Run `cargo metadata --locked`, `cargo check --locked --all-targets`, `cargo test --locked --all-targets` and protocol compatibility checks on your target system before production deployment. See [Installation](docs/INSTALL.md) and [Roadmap](ROADMAP.md). diff --git a/SHA256SUMS b/SHA256SUMS index 4a89e19..0f02766 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -1,12 +1,12 @@ 61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore 4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example 9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore -7f4a44314648305efc26a4b3335cfcdeafc401df69c19e7fdcf2c4382a28d16b AGENTS.md -f3169e64988b073686380b48e2d0f7a3858caaafba3f54a4bc35ba97c22b83fb CHANGELOG.md -8ed9d763e18ae167df3b738c5c467e3a581c3b14c151a1dce7404dffcb7094fc Cargo.lock -d1b10459313cf8c33e80d38da05e1ad6b57df6f4dbe9d0e1f18aacf66955822e Cargo.toml +8216a946a208da74532066574db3beb83b27e9d3d277237d02592fc162002bd8 AGENTS.md +eba2f6f89d0d4364892378f80f1184987b7b10bb97dff978cc8137659088cd3f CHANGELOG.md +4a8484f9a1e20f473ec502ce276b4086b6584946ed5e453696ceb33c7fe652b5 Cargo.lock +69b21d2305427bfa9bb589d2789244de1dcbc480c9d5199fb7cc8ce9467685f4 Cargo.toml 25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile -59bc6f91c6f766799e85e35fb44499c8cb0a4d9880bdcc08402ba6a68a703945 README.md +fc884d9b37e69879ec50fcbbe70724e0847f4a161a139788e2149ab03f39a1f3 README.md ae6d14ada6fde7d0b8514840b821678418cf5b1816c18434e3b7fb2ae27a0b21 ROADMAP.md da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml ac07b3bdd46c787989e93f55ebd6ea3d7f5b150ef36f2422e2b58df3f6b7543a build.rs @@ -37,21 +37,21 @@ f24767c2f9d4db4db5005b700d03323dd20f0a1d180ae939be4fe54a126aaf32 docs/CONFIGURA 80bcc666736b10f6202386d0342e8d6feeba858d77aea310bf3ec6de01c2bfb3 docs/CONTAINER.md dc670d912a6a946160c2f8528c9bb710ec38ebe406dcb7dcaf18fb2653f667b6 docs/CUSTOM_LOCATIONS.md 302a9db0a5a4e5f6c49684ef0ea9ad9f1796fdbcb5e28bb5a98c75fffd4374f4 docs/DASHBOARD.md -3e2f7670d136911c1b7bfe8571c60590932f74a60bcac308add4ccb0446bf970 docs/DOCKER_DISCOVERY.md +11a08bf2110bbeabc924eb049a30177a216af5607ccdaf8be9178e751f78a2c4 docs/DOCKER_DISCOVERY.md c891ee5272e2e47e1a8f97133a413e2330844103d543fb5cbcc4d0724d59a1a5 docs/INSTALL.md 37001cbf8ed248ca05ec40f12dd9876d33f6e0c97dcb21eb2e6f53c3d97a60bb docs/INTEGRATIONS.md 694cb7b15c5bbdc94cecd15dc516c05dcb3467849322cdd125b872d36dee4699 docs/LOGS.md 888cdc8c56e10b8a7615012e192e085bf2899d0ec9b3ddfb5731e7de6ab5bcde docs/MIGRATION.md -140b95ad778261455f269f2144a1c61c2315bd3b85300fb613822eacddf3cfc2 docs/MONITORING.md +27ba3ed337963d9cd05ba382666eed9c414f58c52e5897b4c1c45603f412b456 docs/MONITORING.md d5894c07c68ec4230987767198434abee13b0e525842fea21bcbe752fa82742b docs/PHP_FPM.md -61606429f533b23cb991332459f4c88799cd2e3d0344701a65c1b04422e1b008 docs/README.md +005ce62922a1d9bf08ff7cf39c0da7440c5d39f115b5f6b41b5c4464bab1ee0a docs/README.md 490a6dbe870f2b1fd4ec7816768dde6c86fa7957fc9de2d2f075e123c9222222 docs/RESPONSE_DELIVERY.md e2b36152c13d65621a367c69dd400118dae1e4426a4d2b2139d2f63fa30442d6 docs/SECURITY.md bbef60ff1771da9f48af812987766e1e479408873ae2f079b13f1d5c62ecc6ff docs/STREAMS.md -374fc461eb9700cad532addbf2a440d69162d0e9237b6c4f9ee494a3df0e67d4 docs/TLS_POST_QUANTUM.md +a44bdb81483ba02ccb33303044302d963849e6489b7987a9e7be9079f97f3eab docs/TLS_POST_QUANTUM.md 43a2c6230f95f81bf4efd1575a4f7cdc8b02244df38e19b1ec9c3cd736d3afe6 docs/TRANSPORT.md 8542c4a5c9d5dfe474459099546ae590b4c4d3f363f0bba9721c6107643c2d3a docs/TROUBLESHOOTING.md -25b32d0d906333969df9e6a0a8c1b399cd34ad177b601219ba8ade8e164d3887 docs/VHOSTS.md +e14c02f2c95daf5779d1aa5300751162b63fdd968832a1f0c2f191fa00ace6dc docs/VHOSTS.md 25fd98fd7257bb371472ac32cb8d508f5ee3d785a6feb01fc45cc80a3d3afc54 docs/WEB_SERVER.md 5502e8ce44276213803b66c58e3a4d804a1354f5ed5d83f47ee805e8c44ab088 examples/acl-headers.json 2431dd88a2d9ad2cf3cd4b8fcdfa602563ab1013c612d8917fddbe7febfb2dc3 examples/cache-dashboard.json @@ -77,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 -3bd38496b4ca22c75f041e544af1fad0768ec551b6a493578fe18cdb3ca986da langs/en_US.json -312c803a799d9b8d8f2c3ab8541f8add81e7cd5790c1ac7275c90e4cb09eb1e9 langs/pl_pl.json +1b2ab86636cf47938b0b55de9ac41227c643f23693cb5f7ff82a71fe73f8a36c langs/en_US.json +08fac446a02ebd027a5de519b090178fc35ef1d4dd03bee7455452d0a7cc979c langs/pl_pl.json a99ad040be10fa028351e290b9363236b3513a1cadbdc9fb9e0260d517b9b0ae make_zip.py e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql 20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql @@ -148,7 +148,7 @@ d58a2ed83319176de4e66790ed292447c9497ae031464ec55cfe27174acb639a src/admin/file 282352eda7957477e7da44f16b90984b26a4a67a8755833bc0a6b34eaf93e3af src/admin/file_logs_query.rs f66ff718f82a326f80808b0a5088bd05a48625be802e5ba305fff3c344b59948 src/admin/logging_api.rs 1ba0520c6348246cbb35b60e596e99dc600137c0696de86508cf06587bd86563 src/admin/mod.rs -a6f8687a9655032da676bdec97c50f5dbe9759fe4871ad65de7a7d3e464dd3ec src/admin/openapi.json +9e3eb61074ba72fbd1d7c8a38fb1cc4a1683fe797a4a3ea5a96f19a649b9a526 src/admin/openapi.json 7cfd6593ac9bcea3d1b9e61bbcb7ed98d65ed3190f54f94c1e104541f4fbb4f5 src/admin/operations.rs 257d93a29d0bda96462bcf695aba8d0ec0f9412c379e066ec46ae8ee034e3a53 src/admin/panel_assets.rs f5127855c2994d052678865df36d703b74f4a0afc633daf7718b10da18d26e37 src/admin/panel_content.rs @@ -170,7 +170,7 @@ c18c369124368f58524652565102817ed69fd25b222b0c5e737ef7632b45dcfb src/admin/web/ 86abf50f39862d821cd2e43ca425671aa85289272915132c5a9a5dd1fc4182e2 src/admin/web/api-tokens.js c0d08c5b8fc13dec9bf53fd842d224b9df3874fc3bdf31413e3ab182a8cec2aa src/admin/web/app/backend-dashboard.js aed3aea4eef0011c6b51d39736782e7d1ec52662e422f40e0c7b9ddfc19d0378 src/admin/web/app/certificate-inspection.js -a085ee336e6669229f3ad2f74536f19bd088412ca79bcaff30dff293f1e1baf9 src/admin/web/app/chart-scales.js +7e00a5a127a79887f3b450bc86f43ad00d87b391bb7c8a84fdf0ca7bbd89d2bb src/admin/web/app/chart-scales.js d6c5448db12de34a7bb4924411b44d09c7144bc3e659bd3c8a7c0ebf1938da1c src/admin/web/app/config-history.js c8e1d31a7bf77d9e4f4be979bf6a7474369aae3e1e46e6b30d1a76677fa3d762 src/admin/web/app/core.js 622d34e1616fee9a480d208192fc5a84d62d84ef4233d6a7d95e2bfb8cef415b src/admin/web/app/dashboard-inline-layout.js @@ -204,7 +204,7 @@ f2a2b0476cc55d8dadb4fd52c2c589d64785ad400ccc6cac5a2d10cfca729928 src/admin/web/ 560a8e047182fbcee08aa4f8d999400a8a5ddca0b6a4640efd46f6b5c34fde91 src/admin/web/dashboard-defaults.json 14213011cfb6ac334c8711b76bbdde97588a7b7ba4eb2592a1e387f877d6cbe0 src/admin/web/dashboard-widgets.json 50d1ebcbaf4d0b2968da23bb943fc3f67131c5d32df3f61d23ff37aad9da5b81 src/admin/web/demo.html -a2f02dee2c5472c82079c128f847741f1276d80e36fd31e17e1fee7f86ee44a7 src/admin/web/docker.js +701ff696630a61451c24fb94c668a6d61a28309867cd330d18d7955f7a031173 src/admin/web/docker.js 1d5f8fbf81394a6a8c3c2eff78b48408ef7790c0613905c2c7f584f1548862c6 src/admin/web/docs.css aeffd403fa1823df7d9fa4c48280b4033b2d15e4cb9f66608caff89290de0a52 src/admin/web/docs.html 24b6d5676c2e0cd3862851a395c753dfd7e6fb022f7c69267c1e02b728f1d06f src/admin/web/docs.js @@ -244,7 +244,7 @@ d88d89c8071d2458aef278498a5a7863fb45c374fca801b0b3a9de402c455b4c src/admin/web/ 8338ce13b5e3e3f421437631124cb691aeb5b9e17409d256b4b9bcac457b3c59 src/admin/web/vhosts/health.js b3239e67901f1796d7a974ade0796f2e3590ff8064628a5629b78c7f6ce2f32e src/admin/web/vhosts/protocol.js 8c3da49973c62333559b0e3588c02beb0b425c025eac5cfa82a1bb968f70d9dd src/admin/web/vhosts/rates.js -69c8848159dcc7c4d93f49de3be75553cbecd0731d64302e2b644ae61196e704 src/admin/web/vhosts/render.js +510562ac8aa0a2558743172156a6797fb10d91ccedb7d619208821ac5b6b6afc src/admin/web/vhosts/render.js 4dfdeb7c73c2775eacaca65fd8ef1dc80b275421b15522d319d338f5b96288cc src/admin/web/web-server-profiles.js a9466c7b291a1f436c0531709458b22a453ec2251f554ed09ea8065df02411a8 src/admin/web/web-server.js 2d40c2cb32d7365df0c9cd6c8cb6f0483c7a799786c648f4836a0d9740c6c5ef src/auth/files/mod.rs @@ -525,6 +525,7 @@ f826d8816bdb9edab966e10991e45335625ef7d7376169cf6fd1e0e5d01bf868 src/web_server b791762b4ab7a43c3be19df84e523faf3dfb4df055f66d02178f93edb60d2204 src/web_server/serve.rs df888adf893d0b6b99348d19fa07bd9c2b8fc0ce7f411d06be7411afb4311fe0 src/web_server/tests.rs f00743af2cf4a5952caef7a1dcf68e7817b5599a252a9973e1c22045517c0b0e tests/fixtures/argon2id.json +0eb7bf4cf8a5007a760725630b57612d9bbd7aaf96b85f24d636277f3f77bd4d tests/web/vhost_live_rate.test.cjs eca68a07c2b1e45fdb8f512294f5a432db3ae5a6aec8fb200b47f155cd9a31d6 tools/README.md 7c30c280d0b0a8bf855eeffab583ca34dc161255f7ef6124148aa979c17c26d3 tools/dev/memory_load.py d1418f8e574a7390fd45a2aa879bdf5720b773162d65e295ab0350506aa5220a tools/dev/mock_auth.py diff --git a/docs/DOCKER_DISCOVERY.md b/docs/DOCKER_DISCOVERY.md index 0497e4f..f101dd7 100644 --- a/docs/DOCKER_DISCOVERY.md +++ b/docs/DOCKER_DISCOVERY.md @@ -95,6 +95,8 @@ Every published vhost on an enabled Docker agent has a **Traffic statistics** di Counters come from RustProxy's generated Vhost metrics, not the Docker agent or just the upstream backend: they include requests handled at the proxy frontend, even those rejected before reaching a backend. The request-rate graph is sampled in the browser from cumulative counters **since the panel session started observing this host**; its history is not stored on disk. Traffic/s is the observed delta of ingress plus egress bytes. Missing metrics show `-`. Live updates follow the administration status stream/polling settings; expanding the statistics does not prevent Docker inventory refreshes. +The Docker request-rate chart uses the **same adaptive Y range as ordinary Vhosts**. It tracks the recent typical request rate instead of retaining the largest ever plotted value as its scale. Exceptional peaks above that range are visually capped at the top; the numeric **max/peak** label retains the actual sampled maximum. Hover it for the plotted scale and clipping indication. No agent update or configuration change is needed. See [Vhosts](VHOSTS.md) for the exact scaling policy. + ## Troubleshooting - **WAITING:** first poll not yet observed; refresh and inspect the agent endpoint. diff --git a/docs/MONITORING.md b/docs/MONITORING.md index ccc9f5d..4462a2b 100644 --- a/docs/MONITORING.md +++ b/docs/MONITORING.md @@ -14,6 +14,8 @@ RustProxy has operational views in the authenticated panel and an optional stand | **Docker / Streams** | Agent status, dynamic Vhost health, TCP/UDP sessions and traffic | An agent can be waiting for first observation; Streams are non-HTTP. | | **Logs** | Live requests, stored JSONL/error/protocol records | A log sink can be disabled while live memory view remains available. | +Vhost and Docker Vhost live request-rate charts use a **recent-traffic adaptive Y scale** so isolated high spikes do not flatten ordinary request activity. Values above the chart range touch the top edge, while the peak text remains the genuine largest retained sample; hover the legend to inspect the plotted range. This visualization does not change lifetime request counters or Prometheus/JSON metrics. See [Vhosts](VHOSTS.md) and [Docker discovery](DOCKER_DISCOVERY.md). + 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. diff --git a/docs/README.md b/docs/README.md index 91ce811..2fdb112 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ # RustProxy documentation -Practical guides for **RustProxy 0.34.10**. 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.11**. 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 @@ -22,6 +22,8 @@ Practical guides for **RustProxy 0.34.10**. This index and the listed pages are - [Experimental TLS post-quantum key exchange](TLS_POST_QUANTUM.md) — opt-in X25519MLKEM768, compatibility, rollback and validation. - [TCP/UDP Streams](STREAMS.md) — non-HTTP forwarding and its security boundaries. +The Vhosts and Docker discovery guides include the shared adaptive request-rate chart scale introduced in 0.34.11, including how high peaks are displayed without hiding ordinary traffic. + ## Integrations and operations - [Certificates and automatic renewal](ACME_PROVIDERS.md) — PEM, automatic SNI names, path permission commands, HTTP-01, DNS-01, Certbot and external certificates. diff --git a/docs/TLS_POST_QUANTUM.md b/docs/TLS_POST_QUANTUM.md index 7dada40..e2a062e 100644 --- a/docs/TLS_POST_QUANTUM.md +++ b/docs/TLS_POST_QUANTUM.md @@ -1,6 +1,6 @@ # Experimental post-quantum TLS key exchange (PQC) -**Version:** introduced in RustProxy 0.34.9; included in the 0.34.10 source candidate. **Default: off.** This is an experimental opt-in feature and not a production compatibility guarantee. +**Version:** introduced in RustProxy 0.34.9; included in the 0.34.11 source candidate. **Default: off.** This is an experimental opt-in feature and not a production compatibility guarantee. ## What it does diff --git a/docs/VHOSTS.md b/docs/VHOSTS.md index 5df8d9b..83b8f7a 100644 --- a/docs/VHOSTS.md +++ b/docs/VHOSTS.md @@ -13,6 +13,12 @@ A **Vhost** publishes one or more hostnames on named HTTP/HTTPS listeners. Its s A Vhost may bind several frontends. Hostnames and selected listeners must match: the existence of a certificate or an upstream does not create DNS entries or a listening socket. Existing Vhosts retain their bindings when new listeners are created. +## Live request-rate graph + +Each Vhost card has a **Lifetime request rate** chart showing sampled requests per second since the current browser session began observing that Vhost. The Y-axis automatically follows recent traffic (85th percentile of up to 24 newest displayed samples, with 25% headroom). One anomalous surge no longer compresses normal traffic into a flat line. After sustained changes in request load, the range adjusts up or down automatically. + +A peak above the current scale touches the top edge of the sparkline, **not** its exact proportional height; the numerical **peak** still shows the maximum recorded in retained session history. Hover the rate/peak legend to see the plotted 0-to-maximum range and whether peaks are clipped. This is a display-only change: HTTP counters, sampling intervals, status endpoints and persisted traffic metrics are unchanged. Reloading or signing out resets the browser-side chart history. Docker Vhosts use the same scale; see [Docker discovery](DOCKER_DISCOVERY.md). + ## Block request Content-Type values (ordinary Vhosts) Go to **Vhosts → Edit → Security → Request Content-Type**. Enable **Block selected Content-Type values**, enter one MIME pattern per line, then **Save**. The policy is off by default and is available in the regular Vhost editor, not in the Docker vhost editor. diff --git a/langs/en_US.json b/langs/en_US.json index d61036d..05ca735 100644 --- a/langs/en_US.json +++ b/langs/en_US.json @@ -1149,6 +1149,8 @@ "vhLifetimeChart": "Lifetime request rate", "vhReqPerSecond": "req/s", "vhPeak": "peak", + "vhChartAdaptiveScale": "Adaptive Y scale", + "vhChartClippedPeak": "Peaks above the chart range are clipped", "listenerAddresses": "Bind addresses", "listenerAddressesInvalid": "Provide 1–16 unique listener addresses.", "listenerAddressesExplicit": "When a listener uses multiple addresses, select explicit IPs; 0.0.0.0 and :: are not allowed.", diff --git a/langs/pl_pl.json b/langs/pl_pl.json index b623c8b..a1a3bd9 100644 --- a/langs/pl_pl.json +++ b/langs/pl_pl.json @@ -1149,6 +1149,8 @@ "vhLifetimeChart": "Przebieg ruchu od otwarcia panelu", "vhReqPerSecond": "żąd./s", "vhPeak": "max", + "vhChartAdaptiveScale": "Adaptacyjna skala Y", + "vhChartClippedPeak": "Piki poza zakresem wykresu są ucinane", "listenerAddresses": "Adresy nasłuchu", "listenerAddressesInvalid": "Podaj 1–16 unikalnych adresów nasłuchu.", "listenerAddressesExplicit": "Przy wielu adresach wybierz konkretne IP; 0.0.0.0 i :: nie są dozwolone.", diff --git a/src/admin/openapi.json b/src/admin/openapi.json index d780907..29c5ffa 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.10", + "version": "0.34.11", "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/app/chart-scales.js b/src/admin/web/app/chart-scales.js index 152c8c5..7c2be1d 100644 --- a/src/admin/web/app/chart-scales.js +++ b/src/admin/web/app/chart-scales.js @@ -1,5 +1,5 @@ -// Stable plot ranges: percentages are absolute 0-100; unbounded metrics have -// unit-appropriate zero-based floors, expanding only for real higher readings. +// Overview charts retain stable, expanding ranges (and fixed 0-100% axes). +// Vhost request-rate sparklines use a separate, adaptive plotting policy. window.rustProxyChartScale = (() => { const floors = { rate: 100, count: 10, ms: 100, bytes: 64 * 1024 * 1024, "bytes/s": 64 * 1024 }; const ceilings = new Map(); @@ -17,8 +17,28 @@ window.rustProxyChartScale = (() => { ceilings.set(key, result); return result; } + // Vhost live charts show the entire session history, but their Y range + // follows the recent typical rate. A one-off surge must not flatten the + // subsequent request activity; the real (unclipped) peak stays in the legend. + // The recent quantile also prevents a range that only grows over time. + function liveRate(values) { + const samples = (Array.isArray(values) ? values : []).map(value => + typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0); + const maximum = Math.max(0, ...samples); + const recent = samples.slice(-24).sort((a, b) => a - b); + // Interpolation would allow a single extreme point to dominate small + // histories. Nearest lower rank excludes that isolated maximum. + const typical = recent.length ? recent[Math.floor((recent.length - 1) * 0.85)] : 0; + const scale = Math.max(1, typical * 1.25); + const points = samples.map((value, index) => { + const x = index * 320 / Math.max(1, samples.length - 1); + const y = 50 - Math.min(value / scale, 1) * 44; + return `${x},${y}`; + }).join(" "); + return { points, peak: maximum, scale, clipped: maximum > scale }; + } function reset(prefix) { for (const key of ceilings.keys()) if (key.startsWith(prefix)) ceilings.delete(key); } - return { peak, reset }; + return { peak, liveRate, reset }; })(); diff --git a/src/admin/web/docker.js b/src/admin/web/docker.js index aff1a42..03cf82c 100644 --- a/src/admin/web/docker.js +++ b/src/admin/web/docker.js @@ -278,12 +278,18 @@ view.fields.vhErrors.textContent = metrics ? fmt(metrics.errors) : "-"; view.fields.vhLatency.textContent = Number(metrics?.ewma_latency_ms) > 0 ? `${fmt(metrics.ewma_latency_ms, 1)} ms` : "-"; view.fields.vhTraffic.textContent = !history || history.paused ? "-" : `${rateBytes(history.traffic)}/s`; - if (!history) { view.chartValue.textContent = "-"; view.line.setAttribute("points", ""); continue; } + if (!history) { + view.chartValue.textContent = "-"; view.chartValue.removeAttribute("title"); + view.line.setAttribute("points", ""); view.line.parentNode?.setAttribute("aria-label", t("vhLifetimeChart")); + continue; + } const data = history.pendingCount ? [...history.points, history.pendingSum / history.pendingCount] : history.points; - const peak = Math.max(0, ...data), scale = Math.max(1, peak); - const points = data.map((value, index) => `${index * 320 / Math.max(1, data.length - 1)},${50 - value / scale * 44}`).join(" "); - view.line.setAttribute("points", points); - view.chartValue.textContent = `${history.paused ? "-" : history.rate.toFixed(1)} ${t("vhReqPerSecond")} · ${t("vhPeak")} ${peak.toFixed(1)}`; + const plot = window.rustProxyChartScale.liveRate(data); + view.line.setAttribute("points", plot.points); + const scaleHint = `${t("vhChartAdaptiveScale")}: 0–${plot.scale.toFixed(1)} ${t("vhReqPerSecond")}${plot.clipped ? ` · ${t("vhChartClippedPeak")}` : ""}`; + view.line.parentNode?.setAttribute("aria-label", `${t("vhLifetimeChart")} · ${scaleHint}`); + view.chartValue.textContent = `${history.paused ? "-" : history.rate.toFixed(1)} ${t("vhReqPerSecond")} · ${t("vhPeak")} ${plot.peak.toFixed(1)}`; + view.chartValue.title = scaleHint; } } function selectHealthMode(value) { const control = el("select", undefined, "docker-health-mode"); for (const [name, key] of [["http", "dockerHealthHttp"], ["tcp", "dockerHealthTcp"], ["off", "dockerHealthOff"]]) { const option = el("option", t(key)); option.value = name; control.append(option); } control.value = value; return control; } diff --git a/src/admin/web/vhosts/render.js b/src/admin/web/vhosts/render.js index 2a3cb15..949ad57 100644 --- a/src/admin/web/vhosts/render.js +++ b/src/admin/web/vhosts/render.js @@ -536,8 +536,17 @@ function renderStatus(status) { } if (history.points.length >= 120) { const compacted = []; for (let i = 0; i < history.points.length; i += 2) compacted.push((history.points[i] + history.points[i + 1]) / 2); history.points = compacted; history.bucketSize *= 2; } chartHistory.set(id, history); - const displayPoints = history.pendingCount ? [...history.points, history.pendingSum / history.pendingCount] : history.points; const actualPeak = Math.max(0, ...displayPoints), scalePeak = Math.max(1, actualPeak); const points = displayPoints.map((value, index) => `${index * 320 / Math.max(1, displayPoints.length - 1)},${50 - value / scalePeak * 44}`).join(" "); - const svg = document.querySelector(`[data-chart-for="${CSS.escape(id)}"]`); svg?.querySelector("polyline")?.setAttribute("points", points); const label = document.querySelector(`[data-chart-value-for="${CSS.escape(id)}"]`); if (label) label.textContent = `${pausedRate ? "-" : rate.toFixed(1)} ${t("vhReqPerSecond")} · ${t("vhPeak")} ${actualPeak.toFixed(1)}`; + const displayPoints = history.pendingCount ? [...history.points, history.pendingSum / history.pendingCount] : history.points; + const plot = window.rustProxyChartScale.liveRate(displayPoints); + const svg = document.querySelector(`[data-chart-for="${CSS.escape(id)}"]`); + svg?.querySelector("polyline")?.setAttribute("points", plot.points); + const scaleHint = `${t("vhChartAdaptiveScale")}: 0–${plot.scale.toFixed(1)} ${t("vhReqPerSecond")}${plot.clipped ? ` · ${t("vhChartClippedPeak")}` : ""}`; + svg?.setAttribute("aria-label", `${t("vhLifetimeChart")} · ${scaleHint}`); + const label = document.querySelector(`[data-chart-value-for="${CSS.escape(id)}"]`); + if (label) { + label.textContent = `${pausedRate ? "-" : rate.toFixed(1)} ${t("vhReqPerSecond")} · ${t("vhPeak")} ${plot.peak.toFixed(1)}`; + label.title = scaleHint; + } } const editingVhost = editing ? configuredVhosts.get(editing) : null; const editingHasProxy = editingVhost && (editingVhost.source?.type !== "web_server" || (editingVhost.routes || []).some(route => !route.response && (route.source?.type || "reverse_proxy") === "reverse_proxy"));