This commit is contained in:
Mateusz Gruszczyński committed 2026-10-05 12:22:41 +02:00
1 parent e1a59f6e56
commit d168ee2afa
49 files changed
+1679 -751

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@
Read README.md, ROADMAP.md and docs/TEST_REPORT.md before making changes.
The project name is RustProxy; the Cargo package/binary remains `rust-proxy` for
compatibility. Current source version: 0.25.6.
compatibility. Current source version: 0.25.7.
## Delivery
+9
View File
@@ -4,6 +4,15 @@
> test-only changes and packaging implementation notes were moved to
> [`docs/CHANGELOG_TECHNICAL_ARCHIVE.md`](docs/CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.25.7
- Add one-click vhost enable/disable actions and make the vhost editor Enabled control clearly stand out with an explicit state indicator.
- Add search, sorting and selectable columns to the main Backends diagnostics table; visible columns and sort order are saved in the existing account-scoped administration preferences.
- Theme dashboard scrollbars for dark and light modes.
- Add ordinary/Docker Prometheus metrics for backend retry attempts/successes and upstream-client generation, generation request count, generation age and rotations.
- Add Grafana panels for ordinary and Docker backend client lifecycle, retries and rotations.
- Keep SQLite schema 8 and the existing configuration format; no migration is required from 0.25.6.
## 0.25.6
- Show upstream Hyper client generation, request budget progress, generation age and rotation count in the live **Backends** diagnostics table, sourced from the same `/api/events` status snapshots as the existing backend metrics.
Generated
+1 -1
View File
@@ -1688,7 +1688,7 @@ dependencies = [
[[package]]
name = "rust-proxy"
version = "0.25.6"
version = "0.25.7"
dependencies = [
"anyhow",
"arc-swap",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "rust-proxy"
version = "0.25.6"
version = "0.25.7"
edition = "2021"
rust-version = "1.88"
description = "RustProxy - independent reverse proxy with local administration"
+12 -9
View File
@@ -1,9 +1,9 @@
# RustProxy 0.25.6
# RustProxy 0.25.7
Independent Rust reverse proxy and static Web Server with a dashboard, SQLite configuration,
streaming HTTP/TLS/WebSocket forwarding, raw TCP/UDP stream hosts, IP-bound vhosts and backend pools.
**0.25.6 release source.**
**0.25.7 release source.**
## Layer-4 streams
@@ -11,13 +11,16 @@ Raw TCP/UDP forwarding has its own **Streams** page in the primary navigation, a
Named HTTP/HTTPS listener bind addresses use the same chip-style editor as vhost hostnames, including multi-address listeners.
## What changed in 0.25.6
## What changed in 0.25.7
- Show backend Hyper client generation, generation request count, generation age and total rotations directly in the live **Backends** diagnostics table. The table consumes the same status snapshots delivered by `/api/events`, so these fields refresh alongside Active/Requests/Errors/Latency/Traffic without a second polling path.
- Keep the 0.25.5 bounded backend-client behavior: each backend client generation is recycled after 25,000 requests or one hour by default, with a 128 KiB upstream HTTP/1 adaptive read-buffer ceiling. All three limits remain configurable per vhost/backend and recycling does not reload the vhost or purge cache.
- Keep SQLite schema 8 and the existing configuration format; upgrading from 0.25.5 requires no data migration.
- Add a one-click enable/disable action to every vhost card while keeping the existing revision/CSRF protected save path.
- Make the main **Backends** diagnostics table searchable and sortable, with visible columns and sort order saved in the existing account-scoped administration preferences. These view preferences do not modify proxy configuration.
- Style dashboard scrollbars with the active dark/light theme.
- Export the previously missing backend retry and upstream-client lifecycle metrics to Prometheus for ordinary and Docker-discovered backends, and add matching Grafana lifecycle/retry panels.
- Make the vhost editor's **Enabled** control visually distinct, with an explicit state badge and traffic-impact hint.
- Keep SQLite schema 8 and the existing configuration format; upgrading from 0.25.6 requires no data migration.
The detailed 0.25.5 implementation audit remains available as historical baseline evidence. Current 0.25.6 verification status is maintained in [docs/TEST_REPORT.md](docs/TEST_REPORT.md).
The detailed 0.25.5 implementation audit remains available as historical baseline evidence. Current 0.25.7 verification status is maintained in [docs/TEST_REPORT.md](docs/TEST_REPORT.md).
## Documentation
@@ -94,7 +97,7 @@ CARGO_BUILD_JOBS=1 CARGO_PROFILE_RELEASE_LTO=false \
CARGO_PROFILE_RELEASE_CODEGEN_UNITS=8 cargo build --release --locked
```
The 0.25.6 source archive preserves the resolved `Cargo.lock` supplied with the source tree and
The 0.25.7 source archive preserves the resolved `Cargo.lock` supplied with the source tree and
updates only this package's own version entry. This packaging environment has no Cargo, so the
dependency graph was not re-resolved here. On the compiler-enabled deployment host, run the
locked check/test/release gates below and retain the verified lockfile. `dev.sh` can generate a
@@ -333,7 +336,7 @@ python3 scripts/release_smoke.py
# Runs those checks first, then the Rust suite when Cargo is available:
bash dev.sh test
# Source-only package, with no Git checkout required:
python3 make_zip.py /tmp/RustProxy-0.25.6.zip
python3 make_zip.py /tmp/RustProxy-0.25.7.zip
```
## New management and test-origin guide
+12
View File
@@ -1,5 +1,17 @@
# RustProxy roadmap
## 0.25.7 - Vhost operations and backend observability
Current direction: 0.25.6 -> 0.25.7
- [x] Add a fast vhost enable/disable action to the vhost cards using the existing revisioned save path.
- [x] Add backend-table search, sorting and account-scoped persisted column visibility preferences to the main dashboard.
- [x] Style scrollbars for dark and light dashboard themes.
- [x] Export retry and upstream-client lifecycle metrics for ordinary and Docker backends.
- [x] Add Grafana panels for backend client generation, age, retry activity and rotations.
- [x] Make the vhost editor Enabled control visually distinct from ordinary settings.
- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on a Rust release host.
## 0.25.6 - Backend memory diagnostics visibility
Current direction: 0.25.5 -> 0.25.6
+48 -47
View File
@@ -1,19 +1,19 @@
61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
95ac5b696f463eb9b491564a2658f9e9db56d0beb629eea47c0ec0dc81e361e5 AGENTS.md
d9515bebfa7df5c0862e4fb1667017dc6d81c5e82e7383c55d9220a10e152372 CHANGELOG.md
22fb853ffef79b5f72f2b1c2818acae81b89511fdd5e2509b41c64883694a06a Cargo.lock
0a3790234bfadfcb178e26629be392d164ca0c86a7268ca9b12dd2713643819f Cargo.toml
160a7e43d9d963bfa93d0331e56fe039d39e2a4312199634bf3449611207eeb0 AGENTS.md
bc6f9cf316f77dbd4b4b7f79f02a1a77df86ddd97eb7e9f044403e9e425e75ff CHANGELOG.md
d07ad8d8f03ea00e9902b9b07eca1d91f3c8b683e169496c2fbc3ee7af908260 Cargo.lock
99b597e30f1aa5989dd553680f8de1f69663a04ea4f80663f19200f4d23d80d2 Cargo.toml
d6d22ba63f4bc3d16a13012ecaabff8a156be57c824d2f20f66976502fb27d1f Dockerfile
a160ca8048f170a45da6abe57f886ee6e5416057ff158f5cbb12259b78b6adc8 README.md
3930c03cc27a1c441d976733b10783b6e36d037d691f75e5e7b569998ad8cd10 ROADMAP.md
45e033e8c9f53d919235463f77e3a3585b00f8bfaf196050e34b160d84def69d README.md
0fa8f954b10cc8c4a9452b821ece5a5841c5cf8c1581e27df091b5ce7e73300e ROADMAP.md
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
928ba6a504bc431ae100c6e587861e5088b28822d2cb19da5d437b39d26c864a build.rs
a28b771e57632b7268a2e43bbf0447df564de522d1cacdc7cdf9aec766f653e7 debian/README.md
e322e6b0412b42f906e1a554ddcf2427d2f71d6fb0fa7e999ecc590131662154 debian/build-package.sh
f505c829f5256f5bf14320eed034c049b4e4dd568ae2918fd8ea238051759793 debian/README.md
33e38a974560d7f832b467a9223664e1fc26627259656927cbbcd7053441303c debian/build-package.sh
0763a4bd8b429f7922b23a7276ff694f3a751b83fb127f6ad7ec654e5e507ecd debian/build-repository.sh
d6d6d3a1a1f350e8930974cb06b3abf712a0144e25589ff578ee589ee0685dd9 debian/changelog
037ac5c953ddbb32bcd4f295f0fd259d0f64e003fa9830c9d4fe50afc552958c debian/changelog
c90457b19b1b2f7998d3db59a656d215eb0fab23769197405960457b66d95cf7 debian/control
dff2ecb9c14623416981f444bc6df1c4e2bf55d330260f36a312d8d42245400f debian/rules
c537d54fd92a9b97c5f6e6ec63bc1276cda324513e2946b341b2193f63b62e6a debian/rustproxy.environment
@@ -24,7 +24,7 @@ cb0db03a2c1b06fa04a11f7690755a0ca90b17f19ed8fdb228810f505c48c78a debian/rustpro
e8e18df40bcd69d6aa404282679186d9b8256ac63a5b4e80d7f081e8a0095a2e debian/source/format
385ac7970fa94f043d0bcf04a91184e6e2c807587b85e55517ed064bb4bddba6 deploy/README.md
7b7fdf383bbaea4f37b5c120e98a24e9a075fba56ae7838a31a5152c7edcde02 deploy/rustproxy.service
18fb60f5f784389223ed7199e665d7033d51095fab2a3b13382401eec7ff5e7f dev.sh
e7d57772dab81cfcb9546a213d7cfad91a8aa381a08e13d25b2c64f0ae046d94 dev.sh
5f51efe621f45512a466fa7f2656867c6502a135ca040f4bfeec06a6919b39b6 docker-agent/README.md
7fee7d51a7deb67e73e53a0bdff3aa4630aea9bab9b23a0f9b8001b2169892b1 docker-agent/certificate.go
5e4f565c3c9de4dba897af2e801ea646a83395e72eae4717a6d502dff2fcec10 docker-agent/docker-agent.env.example
@@ -33,14 +33,14 @@ bf687a851374d8f1fa59d050f3f76532f7c12f16a6daa7d3c36102c2d9a8ec9c docker-agent/d
9d77c90767fa929ee8338734d72ce83284d3d965673270f62cb2b1613d587a33 docker-agent/install.sh
b68234472bec5cedd67c7cfe1de7d8806a965dfd4101f944aa6320132f18c14e docker-agent/main.go
3d4051a1f805d145df3877476e4b87773e74be8e123045065955a5133afdc68c docker-agent/main_test.go
7fd09ce19c86cf778eda55fc7b5943d70f650c656ffe0fc54f8e15a932a76578 docker-agent/model.go
9df430e38907d04ec62b47f7ed4d0bc06f43199103737050938b8a79bf86c398 docker-agent/model.go
e52b31d0abf65ae3034f6cb367cca113633014ce8d1d799ad4dd5624d2c7b8bb docker-agent/network.go
634790f35ff4627df2a78cec2ed371f6e9ec9df00afb386075595fc7f3ed72eb docker-agent/rustproxy-docker-agent.service
5220d669171ec3ad65f50e0bc8de5178dadaa4904def8e1a4f61002aa9e9a55b docker-agent/server.go
802a30b793a6ae6a5046489612a23659f33d25e41d0794551381db202daac904 docs/ACME_PROVIDERS.md
ec15163f5f51c9212003b7edd1fc380321311c2496fbaa9b45c838281b9af475 docs/ACME_RFC2136.md
d699f2261a0d7854e49e9b8bae44fa37008a7d2f6340a73cdc37e840722ea211 docs/API.md
8e2b592bfb631ace744a3e0c75e1a4f1d94164b6bce3fd6faf05a3d954edfd9a docs/API_ACCESS.md
7a30b06c88701d1b689e0d402e716400e9e8c4a0cb350e8f8fe4210e165d29c0 docs/API_ACCESS.md
0f7f159ad5b4b5277f80c19f3c47f4e7823ea439ef02e5913e1391f1b34d129f docs/ARCHITECTURE.md
cd2597465af031cdb47db802f8ad6d3af34ef9e79f679df906a7b84220ea3260 docs/CACHE.md
9cb338ed07ac1f92ad491e2099a7e3af63e66262417b1fef24d98c6a791d13fa docs/CACHE_DIAGNOSTICS.md
@@ -50,17 +50,17 @@ db5534427ff0f004940fd6960906d3eaa75347a110e6322827c51c5d63f20adb docs/CONTAINER
7db65a96f696aff1d7f74e2041cfc6ecdf437b1412ece90cee26da1ceff4cf7e docs/DOCKER_DISCOVERY.md
9a882f07945b207d032936feb843f29467e4654f8255fa0104d988a54987f982 docs/ERROR_LOGGING.md
df6ecd4a50796640243676ad4b3a87789f6e5d20cbebea6255e92fbfff1c7131 docs/HTTP3.md
4c27c3d7a7fca41fb325fcec5d86a883174eb4a3d11d5d6ac68912f9e8a17775 docs/INSTALL.md
8cd4109e62ec7106c64e77bb426933d5517b1e04b52574a9bdb21bd6e1884495 docs/INSTALL.md
3b5c31c805e5aee0dc2100b81246a9b631b5abb996faa598ae94f87ba39305f3 docs/INTEGRATIONS_040.md
b640a87349e125d1cce1b93caed06aeee09ef5acd0824d0ca1472757b696d477 docs/LIVE_LOGS.md
7c35c4cdbe7bbb81d94f3bf6aeb22b8a866af3e7f0d62bbedc299e4d6f264e0d docs/MANAGEMENT.md
87608e2bd72a9fbeb611371064748ce33b4f21d7bc807af93c486fa53c5da244 docs/MIGRATION.md
397afd77e47be6b29b3acf3ee027e52a5ded5733789a998f2eb14073666984f3 docs/MIGRATION.md
5f090a190000c6badab2a74e5d4968e70c34de43b59c1452f5b05503b5c5c18a docs/MOCK_AUTH.md
1f85fac0e06e432ef6f744f0b5f9c1b7a27a1e9e1ad30dc1a218eab96b1caffe docs/MOCK_SERVER.md
e7516646dfb107d6240370f4e7460b1189f2eb21f398740381d5d102847bbfe6 docs/NOT_FOUND_HOSTS.md
3da047768f32064c5ef0ad7a4c73c4b14930fe7317c54f5650f426af9f484593 docs/NPM_MIGRATION.md
712cb1cbf84515384b081fd2929e86e40af7c10f65bc69919b78b7b3e2fd12b5 docs/PHP_FPM.md
d3e3bff7e447356127814a9704706ec11eb94068eb6dc744eb614923dfe742b1 docs/PROMETHEUS.md
d27aebd13c71900df076a53e8ea77e3646565d1872085a59c4158de2df385e63 docs/PROMETHEUS.md
0832608a28022bec27b82a6c4abfbfeac7c4bdbe84572e8b3a430cee8d76efa2 docs/README.md
6e46a137b9c88dac8cb9e3fdca877430092392de9913d746278dbff8652460c2 docs/REDIRECTS.md
0dfd0d431c5aedc67abac1ce5de040ffa790ec9578fd7d6f91a987c333d73358 docs/REFERENCES.md
@@ -71,7 +71,7 @@ a699fd663bfbf001525f22f0fbacdf03a58c5e825232d8d144e51cbb8b1008b2 docs/SECURITY.
620b69e566d27af04f0604f4ca3f88cf95cdfea7fbc6ae9dd92446d2b2b5d9be docs/SECURITY_AUDIT.md
a2be253670072567459010b79f64df6093ccf9614aa249bdaa98917a83caeb31 docs/SETTINGS.md
3611defd206cde107e75cac006159856d12cd8f15df7ae2eb9ec3592ed2b4abd docs/STREAMS.md
71dbb8a13872ac9c1c5af5c63ffa44571e6d31bf4ae1ee6803c3373d1d8b1171 docs/TEST_REPORT.md
93e822855eea1d4c8d76f8e1fbbb3e3a143e6aab24ccd650b3b641921310e124 docs/TEST_REPORT.md
cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md
8ee6187a5460ca45643b7fd789e27bffd264449e3a92ff70e9359edee011bbbb docs/TRANSFORM.md
a1e9c6d38d21067d1ce7115ca3395ae8ff1f6a4c40fb0ebf00a6940ef79df439 docs/TRANSPORT.md
@@ -113,14 +113,14 @@ b26833cb4251f9390a44192ecc827ff7f052fd19016a5ccf7498c94503ab4ce3 examples/mock-
5a8f3a8a054abc89c6e78896b9fe2f903212199a24dc3d2d2bcbd0696a31bcde examples/transport.json
d242de9614fbb52933b4accd32f4715ca94107abe4a3b1d7e088362b07a6ba60 examples/vhosts-balanced.json
af2bdb1343a8141069e94ccd2c7f92574ad848437e2f9fdb5c7576a9a18ef4a5 examples/vhosts-private.json
05f196d7f6d52083b6eaad3b432305fbd70c4cef541f85355a275d4804dc879d grafana/README.md
dfe6b4a94349af6091e25a2b2635bbdce9143aa452c90efbcc10de1081a4ed15 grafana/rustproxy-overview.json
93164163bb24981267fb67942c1f3a1b92534fa69c09ff0e5b3fd96c083ddf17 grafana/README.md
2ff0048e598edacfd569f24bdc8b577b5c1cf49c8f20322acea4e981ce1849cb grafana/rustproxy-overview.json
30763fc61c38992a44bc24ad6932b88fd3b322724fce799db1f17f9a747eb59a install-debian13.sh
858b39fa6886b71d20690bf369e76580922364b3cc7f0cf6ba9b040df9cd76e3 install.sh
29cada0936762f26495064d1524a668262ed729b21f20aebddc8b6e5cfea363b langs/README.md
70c1c370d906fa958293f9e4b5df7751f49104be115a40bdc0d5d4c479b5ca70 langs/en_US.json
c469574e18e8abc8ec66806d03ab53499371e6c7ca3aa318aebc7b171ab8efb4 langs/pl_pl.json
b650fd27159eab69c0fd4b27c541019a5ba48fcac120ad90a1195af2bcad1b23 make_zip.py
b1b2d9b5ba3037989f9e0ddfc75acd5b8aff6f27f1b33dc985425dc96add9057 langs/en_US.json
ca41421c7a40f066319b806b52f7004ce1736cfe6ab959b81ab773d3bb50d921 langs/pl_pl.json
268f53cc66c33e93217f4e8e143d9e74b979c095c9a53513b7d0337ca502818f make_zip.py
e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql
20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql
5c62da30fa9db594a4d656c77ae676d3c388072b6a85f91170ec7d911d7cde43 migrations/0004_admin_preferences.sql
@@ -139,11 +139,11 @@ edb6ee1445b01cd6f3a74a3a607f39fb107d411b49df028d2e0f4679e093de31 scripts/acme_p
e69813fb1d3a4cdcfb7243964cc323fc21a603087bf2e84188fa42b9d3cee4b5 scripts/acme_rfc2136.py
7e524285bfa2e3dbde0391257660635b039f23b0b3807e9510df663f757a5ad8 scripts/admin_audit.py
20e83f8b6347b819d1d1ea3de5c691c36f0816954a4210b0d3f39557fefe1931 scripts/backup.py
d0ca0435b4c3ab5bf62ec80b30f9b3c7d5e618cc39e62fa8c524c47570471d20 scripts/check_source.py
0c73dbd46f19b519ad6dde9e97a0b11841c84473f9285c6d03b2138e95817f86 scripts/check_source.py
187ee92c8029cdd59e89b4447cf4d170e856017a2091ceb85ae7c22dd1a79c72 scripts/deploy.py
381592aa62cc720b2f4aa559f885013027c5ac63d66c4562e688b753f854e818 scripts/deploy_core.py
fa03e74191206c0629024405b75cf0bec25d73477fd54e60571f8f793a63b7b7 scripts/deploy_core.py
f014472588e889488976970dbaa95a8745b6907516aa81a7cca21d0f81eaaad3 scripts/deploy_runtime.py
90bd9eee430f67f75f96b2d25f98cc4efb9f3ae065484d32f975dcf19e6ea9bc scripts/docs_check.py
33f5ada91e81dd509738145a1256e7282e7c0ead1c937459da70ad5b59d739a7 scripts/docs_check.py
6facb2d65bfab234f09855097d12da8f72669a38ebceb4b1cbd03ab42b8d2469 scripts/langs_check.py
2e35b7c78887fd56f83ca9017caa168630963a9f47da71dfc53b1a8f9ad436c2 scripts/memory_contracts.py
2cc658faaa610b21b48942c59b955525539ad4503fd60f2a49c7e626a22faaff scripts/migrate_npm.py
@@ -153,24 +153,24 @@ d1418f8e574a7390fd45a2aa879bdf5720b773162d65e295ab0350506aa5220a scripts/mock_a
2d3ff92300b09ce9cecbbaaa9309b4d33160fa4b2291c9952b784e8cfb599ab5 scripts/mock_server.py
2d4c859aae5ba357a6d30024c8487ae8f59f01b192711f13f9c2f8fee0f48b50 scripts/os-acme-client/README.md
c73b814a42bb4ff703441dbd177ee625055eeb115f9b5bb0c2150aede573a159 scripts/os-acme-client/opnsense-rustproxy-cert-deploy.sh
2e57cd5e40162f019e670e1526dade23892139a069c5db8fe04d20385a7aa315 scripts/preflight.py
bcf91eaaf6709709f21936a3040a048bbadc434db569823659731780c9b5dcd2 scripts/release_smoke.py
93eb03e1d26fd6dae3dc798b7284aa939c0c4a57270af3851a44eb2f0c0a7bf0 scripts/preflight.py
96a4ed125d21ebc38c280dccd13db437e942d4e3656de9f5decc6a96c469080b scripts/release_smoke.py
45be151cd85fc2ef2f1fdc3003ace63381af262e73af8a685ab48cb697cc0de1 scripts/routeros-oci-build/.env.routeros.example
3b48cff8506e5a6ae7d3876f47721f5de7d63e2e02264a78cf5b6ddd52d9fbc6 scripts/routeros-oci-build/Dockerfile.routeros
7c7a3e51f43b43e1c962a4b0bb591f99c1f06977bf77f23719f7f867dfe7eb8e scripts/routeros-oci-build/Dockerfile.routeros.dockerignore
68c076ef4537a047a7c390c534dbadd90e00cf495931c6d739f1c28b6b4dc7df scripts/routeros-oci-build/README.md
d53b2ae7315ac143bd17d6327611a39e7c23666408bb95e39da2ca8517322ccb scripts/routeros-oci-build/build-routeros-oci.sh
3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a scripts/routeros-oci-build/docker-entrypoint.sh
4aca6fa11cd0b88b3b0d1527d2cf6132c94ce1405e69ff057ca1c9e48a5479ac scripts/rust_module_contracts.py
d36a5f5532f7d458b4e8249acf0fdf733b3c9531e9a590ea8d1b1dddec8fa7ef scripts/rust_module_contracts.py
ecfe0bb21a7549225a19b0846bb6de8c99cbfbbf5a706d33cf84db2c7146abed scripts/rust_source.py
1c83054336b3508d7ad72cd259f039394682642439877fe69639c0b76be09154 scripts/security_contracts.py
295b6f2625b1875950ea3eecccd142f49be327afdd00c92641286e6de530ec51 scripts/security_dependency_contracts.py
a65010f828194885ffc7f0f733937957f28e6049cd978bec1380a2b8bfbd0113 scripts/source_module_contracts.py
cdde3a77767dd6f9694fa4b6a9c322dabe1b461585bc2cb12ebed8a9afcc1a6c scripts/swagger_assets.py
a7a66a0f81216ce576e2b2a73e0d1eeeab0491f799b25105236767b40f6de88b scripts/swagger_assets.py
102b23de19a88a8e60de392fb83aefd3dfb33e90cc97ed1690d9f3d4a33d8866 scripts/test_compression.py
f6630becaaae2ca443817b2e16bb8b4940d5b06ab5eaa19d2db720e6344fa282 scripts/upgrade.py
2e81ff452d94b053f3c242edca26b6a5d04cde2b12982da5b757d9120035fe62 scripts/upgrade.py
43db35e7264b0345592172f2d1f6a6e4919fc4cbde2a9ff787acd7fff1d031d6 scripts/web_assets.py
0cdbedc07475d5c11f53361db7f8da4f2310594c704fcc214904f7a02ba083f4 scripts/web_server_contracts.py
769b6d92bde5457564937e03549b4f479315032540195b784ee19f8245f1ff94 scripts/web_server_contracts.py
b8ce3e8fd08865015ce312fae9c0457612af18d6ef4513a6281b9e3212f8d458 scripts/web_service_ui_test.mjs
d8e2b51c39252dbb4305512fa9b1ef508ec610968aed316b10514fd160298762 src/acl.rs
4e3be1ea1be0a81529c7c9021fb9bb77c24e5044acd54094972745443ad2c26a src/acl/tests.rs
@@ -182,28 +182,29 @@ b9e57db4bd80ef37564dfc533a96897d8a494671d53acea9fb4bf28d23ba370d src/acme/provi
7acbaf34e7b953b753ab074893e47b5024f0cd756dc4c7a1074067dd936c2e8e src/acme/tests.rs
55ef7ceb44c25c7a8ddcbfd5e0d4b647526f9bdcde901ee08db5314e4d055b39 src/admin/access.rs
85f00f8157e83a152cc531f4cfecd0b1472ce269be9c8eb1ac17ad0693748b42 src/admin/certificates.rs
95d05aa0cef117161d4cd688f154b19aa8a99768273ae1f6a5566a919f97630d src/admin/documentation.rs
1de4c7c116eeab2452ccccbaba3f2b7b63c6eb1c1e146ab870d19c5a5ca16f80 src/admin/documentation.rs
ea4b58f7e46189e0b16606f197ee89f7587b569764470a6b92d37daef67e3afe src/admin/documentation/documents.rs
67b901485d5858d5b5bf78a6260035db32241531d99481754c3533fdf404facb src/admin/documentation/tests.rs
beb8be241e4f1a8cea7b6c4e02b42f0359fb1fff10ab5ce2c70ccd0e63a554b0 src/admin/logging_api.rs
468b6062cb806ef489bc107f69d4725dbf9c08d7e18d15b4482ae0f309c15d2f src/admin/mod.rs
20edad7e3c31deea6ab40bf9bf8e44639118f90319dcd95935fa8620d12cf1a7 src/admin/openapi.json
6187fcb99a16c0b457e798d7f285bf687f3c164e8719e3d80a570a2e19d501c7 src/admin/openapi.json
a53309daf118381031122ca70764f7a2f92adc44af3fc91df5f946e6856f76ae src/admin/operations.rs
b4fa393b780b2676b5d72e0f90cd7290292f424b8062d4b5983e722d6ca72c32 src/admin/public.rs
ea95e3d74729d652ce467c626a73e433060a195afa31dba5da1fc7ca935678ea src/admin/router.rs
2672f363659bda3402b12cfaae0878d62750238f965635c7e16d430642aef312 src/admin/session.rs
9e2981f9c96140bd6d9ca54e0d03b51b0783360e534a13e3b5cd1bec8c9440fd src/admin/session.rs
f7938b202919a49fd2ff86ecdf09efadae12e0ffc0a4246a72a76218db11aa88 src/admin/status.rs
cadd7dca29b9aad8c77c7bc23dd249ea37a15adfa4d636a8ef00273a91ce35dc src/admin/tail.rs
9bed1f93a8ef7bea4478b26bc825aacc8825ca70cf26c993fe78ac784aab2526 src/admin/tests.rs
5df98246c46135a176b4b8b705c92fd328719e82eb7342f088256abb976d5ae7 src/admin/tests/part_01.rs
b8866db46f8be4f3f5bcceb5d84d32fe482f65d534b65cc75b4b3e33dba576c0 src/admin/tests/part_02.rs
b31d594499d7f8164c14b66dd77971b9985cefd99bd4d3686f9ea26e11e5a59c src/admin/tests/part_03.rs
02046813070ae7311612592fefec5432582b74b9a9b897c06ee46f363e7fa434 src/admin/tests/part_04.rs
252456477d13c0652a488731c11173649c4816cf67f151cd881f138f1cac2edc src/admin/tests/part_04.rs
5f0062abc42a3b81c79088dc0824ab4c81a22c6ea765e77d2527d07d13bffd58 src/admin/tests/part_05.rs
23c55f23deeec844a9abc2289440c9e0583aaeb72416fc5f3632a43a864c0efd src/admin/web/acme.js
78e7d54d6536cd600dcbe66f0fc06ec109e121dfee1e57f3ca6e61cc6ae04a6a src/admin/web/app/config-history.js
22252759b3bbdacf887616d6c06113cc3f690884ffe958e7cfbd28bfb2d6805f src/admin/web/app/core.js
bf987a7652ebe054d02188fce32de3d797254b4740ea818985d5b76013fa3111 src/admin/web/app/core.js
19abc332a1009bcb087fe4ebb58d458739b27480ab50715b03d07fd408302942 src/admin/web/app/runtime.js
6dd5e2de7873c812e331546eee00380ec8529b70b9e7321d6ea14642eba80673 src/admin/web/app/settings.js
342b5e6401c2d23d680ff86dcc937b29f5673cd42e578c6a57127a6ab7609f2a src/admin/web/app/settings.js
d0be71555de13f732fd6ee44438a0110eb08c91e78bbc1aa36a1a84182780210 src/admin/web/cache-panel.js
50d1ebcbaf4d0b2968da23bb943fc3f67131c5d32df3f61d23ff37aad9da5b81 src/admin/web/demo.html
3b84a7a08f9f68e2c78fed4523b594c31a251addc5480a69c5f6949908230539 src/admin/web/docker.js
@@ -218,7 +219,7 @@ f7d0955ab18c15e04369204e789f341d2e832f927c1ff20a3b2b9e35e7898f00 src/admin/web/
e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/error_504.html
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
dee3b113907f77138cdf919efdfb097e45b889cff3893bfc6f02ed998643bbae src/admin/web/host404.js
fa46ced03d335eb1b1e855f51fcbbcdeb52b7bfb955ce78f0e56322978bc895d src/admin/web/index.html
f3a946a33a2006a1e175c9c9a3621c51f46ad055d95c19324e5aa21c9711ef13 src/admin/web/index.html
24db88842707f764064657712d3660c6ac129fbfb47d188c0903925cf8244ce5 src/admin/web/integrations.js
cdab0c11a28deebdf401c0453fbc76b675e9e643722106fb20e812bb9648ccde src/admin/web/login-keys.json
a253d202dc9a147f2add34c64e0f35c38fdc7f667f9908933ce5f959b1a92a2e src/admin/web/login.html
@@ -227,12 +228,12 @@ e41619e5cb0ba01a3d287748f28eb38bbe7641f9bedade252d286c18e619c400 src/admin/web/
22318f39f7bb1ae98e65737d3c2ad9e63065b8e94ad59bed560c0e80d3a46949 src/admin/web/redirects.js
668f5162fa9f8bbe2fefce4115644a6469e01aa7defc9c7f760c28afd8e2afc7 src/admin/web/settings.js
1a30affd6a717095a1763998a5850a79ae0f86b7b694806f2aba2b880735dd1a src/admin/web/streams.js
702848ca4331fc68a53aa7df0622c0ca66f8cbb01abe598418754931dbee4360 src/admin/web/style.css
59eda800b0eadf4ca452bc9c9b1b669394973d96716afbc92ef8e3122b54fcc5 src/admin/web/style.css
47e98065281c04b4c185d53df4ed0cb12fabc716e52143e6ae3c3c25db62126a src/admin/web/traffic-panel.js
d818580e61bbb70da1f758b9231c0667639cd947d875a55adc21dcf9ed1319c2 src/admin/web/transform-panel.js
ac79a0f5471f110850bf5ceaaee9a3903bb92fb2edc10e969d8fda81f66571c0 src/admin/web/vhosts/core.js
c6fa43f90c7f3783b1dee7413a4d3333cef9f9453e74e3d5c85e7051369465bc src/admin/web/vhosts/editor.js
1457a049cd5d266995462bd9cad1b63530c469ecd4b8b80cc9d1e1b6ab4485ed src/admin/web/vhosts/render.js
d4938e0bf8cf41f79e9b49cf9b5cf2c8340668e792b73d5350cb1872e7ef1edf src/admin/web/vhosts/editor.js
c815bcf24a75608669232fe5ff447d0b755de586093addca3758aae739f9febc src/admin/web/vhosts/render.js
0de35a0671f3ac8fed62260d63ae43d04efce4947428ebdb9254cff0295295f9 src/admin/web/web-server.js
98c00d99822f9be54fc58afb6d4b577901712effd2c5eddce014312681864625 src/auth/files.rs
b2f948bcc3dbd24952c7b1b02d41d06757f49f0c38a78caf51f8f8a687d8480d src/auth/files/tests.rs
@@ -329,17 +330,17 @@ e40171e72327bc4350ba95cedacde2f3c9148c26a5a232d9f790f4449f20a685 src/geoip/test
68bc11160bc5275a904e48af8698b37433096fb6d592364d8281d59bb9e2b3f4 src/http3/tests.rs
3dab8fac75263120e48ee869ad55a3e2ccc2a19fc9f6d6a6fe055657ec55b1e3 src/lib.rs
6946917d57fb589bdba639fef2cec1d256fac0c8bfde92adffef2b31c3b3e08c src/logging.rs
4ef787e9c180f0ac6ba5781711becd0b08d7517520fc03e21248e442d8fe2901 src/logging/format.rs
1bff8b45d825ec29b44cda4b98b4c9d2e3ad14ff69252e5eb469ecce6e8514a9 src/logging/format.rs
147825d3c6dbb4c794c18897703741aa6b60bbfa3dbae0e30b6f770e2a8672ba src/logging/model.rs
348db1112a6067b06f4620ae75d2fc57d275852fef995b52649a6112b8c6589c src/logging/runtime.rs
6a07c1eeb6914b863ba7581444486029ca0c7465d2427f251bd1143cbba49448 src/logging/tests.rs
3836ed14b6710e183f048ee4d7807a35366c27a27338057fe4a2811129b63302 src/main.rs
46732ed6d9cf2a33031624f941b8318d2676fb1b6bb5142e96820301f9fa1ac4 src/main/admin_runtime.rs
f31da9bff5455db1c0c793116f2cee209022555b9f3d804ef9f1662a1ae05e83 src/main/bootstrap.rs
b3d9ff6e92d3e6c8d54c7476200cacbc69d378e873e10d53ce306ac7132187ea src/main/bootstrap.rs
5a98d0e249338e73aa57ae2e352dc16db2f79c2d042c1544ce1c2022da76a31c src/main/proxy_runtime.rs
4dd7c1084a6214c0f2513ebf86fe8ef144470695557772bfe25979edd64491ab src/main/shutdown.rs
0a6578a0f4023b9f8d73043bf942dc5e096ef9afb8c0d2f131c14f65d08ac01d src/main/shutdown/tests.rs
42784b7f4ff85f942d4dd3cee13a2182c20a0683f0d572eae137305398e7c928 src/main/tasks.rs
c601a96213abeff45dc879ee5a1e107ff60c1f1d657c505f770a81e7e7e4fb25 src/main/tasks.rs
49d7e104585c8791ac9a203dddd95802784b68c0ba65d6f90f3d57154b4c7871 src/metrics.rs
201ab702722d6d21585fe8bb49939150d61bfac7ed8dc198b98c2cc6098ffb5f src/metrics/global.rs
49b11df65b4fb0b5b727f9f867436584680b45e3f4fbd53f74bc0cab8b9a88ec src/metrics/vhost.rs
@@ -354,7 +355,7 @@ ff1c502ed1ce8d5de9ba7685253a6b70793ca9824b75fba58bae8aa6c4c11b84 src/prometheus
d3c6b08088df0003144544936a55e2a3661aac85292821b594f00adfd19a2951 src/prometheus/model.rs
064b2be7fed2f6acf526b1cce070c1bb1b943d36f08fa50bbfcbdf3cf1c9318c src/prometheus/tests.rs
028e1dd9f2ec2afaf699a6b21c2d4b0d96e76f11f5e4ed009c233dadf0f3be51 src/prometheus/traffic.rs
cbfb888199cc64c080230a729a00d57c0c56381d50ba45f3ae368e13d761d81f src/prometheus/vhosts_backends.rs
e03f0d6b3f35dae6b98c394837aa0d17e4d73a8306767b4c607868bc00c30286 src/prometheus/vhosts_backends.rs
6fc307286ab24b259237d34c6207dd325b375e1e896f824c66c9805a97bb981f src/proxy/body.rs
16b9d4db3b20b2ab14c47ac14fcd18d3d8ca77c3ad9a1f37866ebfa60f379862 src/proxy/body/tests.rs
00e50a914511754266b4eae2626244587174bfa71fff91509c530bcde7f0d397 src/proxy/cache_helpers.rs
@@ -446,7 +447,7 @@ f0479524d66bfee7a6eed5eea03601630ba82064fb944902deb9166f90fe17cf src/vhost/vali
9c1c8d6eedecb95eadbfb23c3caea562f6f7a995606ef988f25b58c2d03bcdd5 src/web_server/config/model.rs
6191f4d541476c4a3ae296e7dd28bdb9f8a123fc509b47619f6f44519c44d278 src/web_server/config/validation.rs
86c744216d7132d8294a6e90ba9be5fd871646006313d1c62506d40c81db9b12 src/web_server/fastcgi.rs
a7039c5e1c9ec46807f26f50effd89fa1c7cb1d0f92e6b8c8f7bc348aa1e7979 src/web_server/fastcgi/protocol.rs
a490ec8fc283efe9c91e8e8ffd00926133444f51a9d2390aab63c5db234e85be src/web_server/fastcgi/protocol.rs
134c4998f6a0aa27e121af1ae3355b7e335488b2ae867ac0a934c58b4eb77191 src/web_server/fastcgi/response.rs
8186363315bd998919c6c783e6f7501528f257a5c0da77c5d60f423e8670db5e src/web_server/filesystem.rs
0aafd7f37c4fe784b047646e4ce5ddcb5fe821231a41ed0a9f519529385288a8 src/web_server/htaccess.rs
@@ -456,14 +457,14 @@ a7039c5e1c9ec46807f26f50effd89fa1c7cb1d0f92e6b8c8f7bc348aa1e7979 src/web_server
80f946570c14c8915fd21bfdb1a84dfb6a14c633ab6060acb828fab4c7c21daa src/web_server/mod.rs
67ab8bef567452f123c4b9b46a942b39c93f1f765c128edca7ee808ff1c5e304 src/web_server/request.rs
c5cb561b0f276e8082f904b9446fa5885f6b3c0608f25ea2ef425061e9f7f6b4 src/web_server/response.rs
622dcb6005b6226709ee407279b72acd41dce7278f49d3f39cc9ae11423d7748 src/web_server/response/file.rs
9988a784cb025c63c230389da138deab40fae2716aaa85c4dfeec909bdc1adf1 src/web_server/response/file.rs
9aa68251a5f549f4ce3b1bda88d4abb9efc629caf444989381b4117c9488e37b src/web_server/response/headers.rs
2268a6aeed2f565f9791af33e1cd21c56c228c8873b031725b39a6c57184f2d1 src/web_server/routing.rs
400e1c8aa2fe857d96036a64499fbc3de44c1e3a53811c5b25101fb843359792 src/web_server/security.rs
3e0b4e92600a7d5abcec92efed532b3a01e0781cb4f2681daac6d6ae13b0a6e9 src/web_server/serve.rs
cf86c4692080723bbdf8f4105e1abfae33d7fcf020d2ad9e13ebae3943bd0cbe src/web_server/tests.rs
c62752fe2fa6b74ddc9d94d36f8d191376b1575b15d21fc434c5a5da4c932692 src/web_server/tests/part_01.rs
743789497958281203b136057d4bc711b6d1d0c167d2fc1ebbfc5c1d6507a618 src/web_server/tests/part_02.rs
5ac754a25cc0009015d5937522224195a695abda9e1f4e121d824b22f729dbff src/web_server/tests/part_02.rs
e54f44f6ca1ed2c41e620de9195d32c4f8bb6936c3881745316a07160f87b4fa src/web_server/tests/part_03.rs
f00743af2cf4a5952caef7a1dcf68e7817b5599a252a9973e1c22045517c0b0e tests/fixtures/argon2id.json
1725c10331745dc04a07675784d385260da7b14567b8dba3cbae5e891eb5dadf tests/http2.rs
+1 -1
View File
@@ -1,6 +1,6 @@
# Debian packaging
This directory builds the `rustproxy` binary package for RustProxy 0.25.6.
This directory builds the `rustproxy` binary package for RustProxy 0.25.7.
The package installs:
- `/usr/bin/rust-proxy`
+1 -1
View File
@@ -21,6 +21,6 @@ cargo fetch --locked
dpkg-buildpackage -us -uc -b
mkdir -p dist
find .. -maxdepth 1 -type f \( -name 'rustproxy_0.25.6_*.deb' -o -name 'rustproxy_0.25.6_*.changes' -o -name 'rustproxy_0.25.6_*.buildinfo' \) \
find .. -maxdepth 1 -type f \( -name 'rustproxy_0.25.7_*.deb' -o -name 'rustproxy_0.25.7_*.changes' -o -name 'rustproxy_0.25.7_*.buildinfo' \) \
-exec cp -f {} dist/ \;
echo "Packages copied to $ROOT/dist"
+8
View File
@@ -1,3 +1,11 @@
rustproxy (0.25.7) stable; urgency=medium
* Add fast vhost enable/disable controls and improve the vhost Enabled editor control.
* Add backend-table search, sorting and selectable columns plus themed scrollbars.
* Export missing backend retry/client-lifecycle Prometheus metrics and extend Grafana.
-- RustProxy Maintainers <noreply@example.invalid> Mon, 05 Oct 2026 11:42:00 +0200
rustproxy (0.25.6) stable; urgency=medium
* Show upstream client generation, generation requests/age and rotations in the live Backends diagnostics table.
+2 -2
View File
@@ -18,7 +18,7 @@ if [[ $# -gt 0 ]]; then shift; fi
case "$mode" in
help|-h|--help)
cat <<'HELP'
RustProxy 0.25.6
RustProxy 0.25.7
./dev.sh Build and start proxy + local panel
./dev.sh run [serve flags] Same, with extra server options
./dev.sh admin setup-token Generate/print a one-time setup capability before first login
@@ -139,7 +139,7 @@ if [[ "$mode" == "test" ]]; then
printf 'Checking all Rust targets before tests...\n' >&2
cargo check --locked --all-targets
fi
printf 'Building RustProxy 0.25.6...\n' >&2
printf 'Building RustProxy 0.25.7...\n' >&2
cargo build --locked
BIN="$CARGO_TARGET_DIR/debug/rust-proxy"
case "$mode" in
+1 -1
View File
@@ -1,6 +1,6 @@
package main
const version = "0.25.6"
const version = "0.25.7"
type config struct {
Listen string
+1 -1
View File
@@ -1,4 +1,4 @@
# Administration API access - 0.25.6
# Administration API access - 0.25.7
## Sign-in boundary
+1 -1
View File
@@ -1,4 +1,4 @@
# Installing and updating RustProxy 0.25.6
# Installing and updating RustProxy 0.25.7
The scripts prepare a Linux/systemd installation. This source archive is **not a
verified production build**: Rust/Cargo, real CA operations, live systemd startup
+4
View File
@@ -1,5 +1,9 @@
# Deployment and configuration migration history
## 0.25.6 -> 0.25.7
There is no SQLite schema or saved-configuration migration. Version 0.25.7 reuses the existing `admin_preferences` document to persist backend-table visible columns and sort order per administrator account; search text remains transient. It also adds vhost enable/disable UI actions, themed scrollbars and additional Prometheus/Grafana observability. Existing vhost `enabled` values and backend configuration remain unchanged until an administrator explicitly uses the new action.
## 0.25.5 -> 0.25.6
There is no SQLite schema or saved-configuration migration. Version 0.25.6 adds the already-exposed upstream client generation/rotation diagnostics to the live Backends table and keeps the 0.25.5 backend client recycling defaults unchanged.
+15 -3
View File
@@ -56,7 +56,7 @@ Only `GET` on the exact configured path without a query string is accepted. A
wrong path is `404`, wrong method `405`, disallowed peer `403`, invalid/missing
required bearer `401`, and exporter concurrency exhaustion `429`.
## Coverage audit in 0.19.1
## Coverage audit in 0.25.7
The exporter represents all scalar fields currently sampled by `diagnostics::View` when available. The per-interface `network_interfaces` list added in 0.18.6 is intentionally dashboard-only so kernel-created interface names cannot create an uncontrolled Prometheus label set. RustProxy also exports the bounded operational data already available from traffic metrics, cache, vhosts, backend pools, streams, logging, GeoIP and ACME renewal observations.
@@ -72,8 +72,8 @@ The exporter represents all scalar fields currently sampled by `diagnostics::Vie
| Traffic analytics | independent live collector (`include_traffic`): capture-state gauges, per-vhost request/error/body-byte/duration totals; approximate bounded top countries; fixed User-Agent families; fixed HTTP method families; status classes; cache outcomes; serving source (`web_server`, `reverse_proxy`, `local_response`, `unresolved`); and currently configured route IDs |
| Built-in Web Server | configured Web Service count/info, PHP and directory-listing flags, vhost/route source metadata, plus actual request traffic through the shared vhost counters and Traffic serving-source metrics |
| Redirect/404 hosts | desired enable state, domain/rule counts and local-response status |
| Backends | pool readiness/policy info, bounded backend status, health/drain/backup, active work and configured request-concurrency/queue limits, requests/errors/bytes, weight, separate active/passive failure state, active/passive successes and health-failure totals, unconditional HTTP-5xx/timeout/connect/I/O/slow-response observation counters, DNS/TLS/header-timeout/read-timeout/send-timeout diagnostics, capacity rejection/queue-timeout counters, plus `passive_http_5xx_total`, `passive_timeouts_total`, `passive_connect_errors_total`, `passive_io_errors_total` and `passive_slow_total` aliases, health transitions, last status, current/EWMA latency, last-probe/failure/ejection/recovery timestamps, traffic rate, and retry attempt/success totals for ordinary vhosts |
| Docker vhosts | separate `rust_proxy_docker_vhost_*` and `rust_proxy_docker_backend_*` families: configured agents/assignments/domains, vhost traffic/status/duration plus backend readiness, health, requests/errors/bytes, latency/EWMA, separate active/passive failure state, unconditional observation counters and matching passive-health aliases, transitions and probe results |
| Backends | pool readiness/policy info, bounded backend status, health/drain/backup, active work and configured request-concurrency/queue limits, requests/errors/bytes, weight, separate active/passive failure state, active/passive successes and health-failure totals, unconditional HTTP-5xx/timeout/connect/I/O/slow-response observation counters, DNS/TLS/header-timeout/read-timeout/send-timeout diagnostics, capacity rejection/queue-timeout counters, retry attempt/success totals, upstream-client generation/current-generation requests/generation age/rotation totals, plus `passive_http_5xx_total`, `passive_timeouts_total`, `passive_connect_errors_total`, `passive_io_errors_total` and `passive_slow_total` aliases, health transitions, last status, current/EWMA latency, last-probe/failure/ejection/recovery timestamps and traffic rate for ordinary vhosts |
| Docker vhosts | separate `rust_proxy_docker_vhost_*` and `rust_proxy_docker_backend_*` families: configured agents/assignments/domains, vhost traffic/status/duration plus backend readiness, health, requests/errors/bytes, latency/EWMA, separate active/passive failure state, unconditional observation counters and matching passive-health aliases, retry attempt/success totals, upstream-client generation/current-generation requests/generation age/rotation totals, transitions and probe results |
| Streams | desired enable state, active/session/reject/error counters, bytes/packets and max connections |
| Logging | structured sink queue capacity, sent/error/drop counters plus the global live-log contention drop counter |
| TLS/ACME | configured certificate count, auto-renew flag, bounded issuer metadata, latest ACME job state, renewal check/expiry timestamps, failure count and status |
@@ -157,6 +157,18 @@ rust_proxy_docker_backend_up
rust_proxy_docker_backend_ewma_latency_seconds
```
Backend client lifecycle and retry diagnostics:
```promql
rust_proxy_backend_upstream_client_generation_requests
rust_proxy_backend_upstream_client_generation_age_seconds
rate(rust_proxy_backend_upstream_client_rotations_total[5m])
rate(rust_proxy_backend_retry_attempts_total[5m])
rate(rust_proxy_backend_retry_successes_total[5m])
```
The same fields are exported for Docker-discovered backends under the `rust_proxy_docker_backend_*` prefix.
Certificate expiry window:
```promql
+24 -78
View File
@@ -1,78 +1,27 @@
# Verification report - RustProxy 0.25.6
# Verification report - RustProxy 0.25.7
## Scope
RustProxy 0.25.6 is a patch release that keeps the bounded upstream-client memory controls from 0.25.5 and makes their runtime state visible in the main Backends diagnostics table. Each row now shows client generation, requests in the current generation, generation age and total rotations. Those values come from the same backend-pool status object delivered through `/api/events` and `/api/status`, so the dashboard refreshes them with the existing live diagnostics path. The defaults remain 25,000 requests, 3,600 seconds and a 131,072-byte upstream HTTP/1 adaptive read-buffer ceiling. Recycling remains local to the backend client and does not reload the vhost or purge cache. SQLite remains schema 8 and no saved-configuration migration is introduced.
RustProxy 0.25.7 is a patch release focused on vhost operations and backend observability. Vhost cards now expose a direct revision-protected enable/disable action, the main Backends diagnostics table supports account-scoped persisted sort/column visibility preferences plus transient search, and the vhost editor gives the Enabled setting a distinct state treatment. Dashboard scrollbars follow the active theme. Prometheus now exports backend retry attempt/success counters and upstream-client generation, generation-request, generation-age and rotation metrics for both ordinary and Docker-discovered backends; the bundled Grafana dashboard adds matching lifecycle/retry panels. SQLite remains schema 8 and no saved-configuration migration is introduced.
## Source contracts reviewed
- Revision History now uses the shared dashboard dialog rule with square corners; the dedicated history dialog retains only layout dimensions/overflow and no longer duplicates color, background, border, shadow or backdrop appearance declarations. Calendar/diff sub-panels no longer add local rounded corners.
- Dashboard widths up to 850 px now hide the main section list behind an accessible `aria-expanded` Menu control. Opening shows a two-column section grid; selecting a section, pressing Escape or clicking outside closes it. Desktop navigation keeps the existing sidebar behavior.
- The authenticated top bar exposes an About dialog with author Mateusz Griszczynski, an Open Source notice and the canonical Git repository link; the documentation action is labeled Documentation. The About dialog uses the same square-corner modal styling and adds no new server route.
- Cloudflare client-IP mode still requires the direct transport peer to match the built-in Cloudflare edge CIDRs. After a successful verified `CF-Connecting-IP` override, one valid two-letter `CF-IPCountry` value is normalized and preferred for country policy/logging/Traffic; missing, duplicate, malformed and `XX` values fall back to local GeoIP of the verified effective client IP.
- Prometheus `include_traffic` defaults on and exports bounded per-vhost Traffic totals, country top values, fixed User-Agent families, fixed method families, status classes, cache outcomes, serving source and configured route IDs. Raw effective client IPs, raw request paths and full User-Agent strings remain excluded from exporter labels.
- Traffic serving source is recorded as one fixed value (`web_server`, `reverse_proxy`, `local_response`, `unresolved`) and the exporter adds configured Web Service inventory plus vhost/route source metadata. This makes pure Web Server traffic explicit while preserving the existing shared vhost request/status/byte/duration counters.
- The bundled Grafana dashboard adds a Traffic vhost variable and panels for Web Server request rate, queue drops, countries, User-Agent families, source rates, methods, status classes, cache outcomes, capture-state gauges and configured-route traffic.
- Vhost and location `method_policy` values validate and normalize HTTP methods; host and selected-location policies are cumulative and deny with 405 before upstream work. Allow-list denial includes `Allow`.
- Custom locations can select an `acl_profile_id` and add inline IP ACL plus country access policy. Profile and inline rules are compiled with the route and checked before authentication/backend selection; a location cannot weaken a vhost denial.
- Every observed exchange receives a fresh 64-hex SHA-256 `request_id`, overwrites inbound `X-Request-ID` before forwarding, and reuses the same value for diagnostics/logging. Custom header rules continue to protect `X-Request-ID`.
- SQLite schema 8 stores revision/time/actor/document in bounded `configuration_history`; successful CAS writes record history transactionally and retain the newest 512 revisions. The session-protected `/api/config/history` API supports metadata listing and one-revision detail with the previous retained document.
- `POST /api/config/history/restore` accepts an older retained revision plus the currently observed `expected_revision`. It rejects stale or non-older targets, reloads the retained document and passes it through the same startup-lock, certificate-removal, compile, cache-invalidation, persistence and hot-publish path as a normal configuration update.
- Restore persistence uses the existing storage CAS and appends the restored document as the next monotonic revision. The same transaction records `config.restore` with `source_revision=<old> revision=<new>`; the selected historical row is never overwritten.
- The Manual config history UI groups retained revisions by month/day, lists revisions for a selected day and renders a bounded recursive path-level before/after diff using text-only DOM operations. An older selected revision exposes a confirmation-gated restore action; success refreshes the active envelope and history.
- `Config.web_services` is a separate persisted collection; vhost/location `source` defaults to Reverse Proxy for old documents and Web Server stores only `web_service_id`.
- Full configuration compilation validates Web Service IDs, absolute/existing document roots, index names, duplicate IDs and every active Web Service reference. A pure static vhost does not require a backend pool; mixed Web Server and Reverse Proxy locations do.
- Web Server dispatch occurs after normal frontend routing, ACL and Basic/Tinyauth authorization and before backend selection/DNS/cache/upstream work. Static responses continue through the existing custom error-page, response-header/security, compression and transport pipeline.
- Static files use streamed Tokio file bodies with explicit lengths rather than whole-file buffering.
- Files support metadata-based ETag/Last-Modified, If-None-Match/If-Modified-Since, one Range/If-Range and 304/206/416 behavior.
- Directory requests redirect to a slash path, then search the optional ordered index list (which may be empty), then optionally render an HTML-escaped and URL-encoded RustProxy-styled listing without exposing filesystem paths.
- Root directory listing opens a fresh directory file description per request rather than a `dup` sharing the persistent root descriptor offset; a regression test requests the same listing twice. Nested directories independently apply index lookup first and listing fallback only when no index exists. Generated listings send `Cache-Control: no-store`.
- A directory with no matching index and directory listing disabled returns the built-in generic 403 page (or the configured vhost/global 403 replacement in the outer response pass), not a Web Server implementation-detail string. HEAD keeps an empty body.
- Web Services support up to 64 normalized MIME overrides, a validated fallback MIME header and an optional validated default `Cache-Control` header. Static files remain backed by the filesystem/OS page cache; existing vhost/location response-header rules run later and can override the source-level cache policy.
- Web Services additionally support ordered `ext:`/`mime:` Cache-Control rules, validated fixed response headers, ordered `try_files`, regex internal/redirect rewrites, precompressed `.br`/`.gz` sidecars and attachment extensions. Protected framing/transport headers cannot be overridden by the source header map.
- Optional `.htaccess` compatibility compiles a bounded root/subdirectory set at Web Service Apply time rather than parsing per request. It supports common RewriteRule/RewriteCond front controllers, DirectoryIndex/Indexes, AddType, Header, Expires, PHP AddHandler and simple Require all granted/denied. Unsupported access/handler scopes fail closed, `.ht*` control files are never served, and PHP AddHandler requires PHP-FPM.
- Web Service presets are UI-only generators: they populate ordinary configuration fields and do not persist a hidden CMS mode. ID/name/document root and PHP-FPM endpoint/tuning remain operator-owned. Optional hardening is persisted explicitly and defaults off for compatibility.
- `block_common_attacks` rejects TRACE/CONNECT/TRACK, sensitive dot/control/backup paths and a bounded set of high-confidence exploit probes; it also suppresses FastCGI `HTTP_PROXY` derivation from the inbound `Proxy` header. It does not claim generic WAF/SQLi/XSS filtering.
- `strip_unnecessary_headers` removes common source/PHP fingerprint headers after Web Service and `.htaccess` response handling while leaving the later vhost/location response-header pipeline authoritative.
- Optional PHP handling executes configured extensions through a bounded FastCGI connector over Unix socket or loopback TCP. PHP concurrency uses a fixed semaphore without an unbounded wait queue, request/response bodies use bounded streaming, and connection failure cannot fall back to serving PHP source. Advanced configuration adds Authorization forwarding control, a streaming request-body ceiling, bounded CGI response headers/stderr, trusted custom FastCGI parameters and validated `PHP_VALUE` overrides that cannot replace core CGI/HTTP fields or `PHP_ADMIN_VALUE`.
- Web Service symlink policy defaults to `deny`/`O_NOFOLLOW`; Linux-only `inside_root` verifies opened descriptors through `/proc/self/fd` against the canonical root. PHP-FPM is intentionally limited to `deny` because the external worker reopens `SCRIPT_FILENAME`.
- Routing rejects malformed/ambiguous encoded paths before filesystem lookup. Unix filesystem lookup walks from an opened root descriptor; `deny` uses `openat` + `O_NOFOLLOW`, while Linux-only `inside_root` verifies every opened descriptor against the canonical root. PHP-FPM requires `deny`. The secure Web Server compile path remains Unix-only.
- No second URL router, custom-error subsystem or object cache was introduced. Reverse Proxy remains the default source for existing configuration.
- Dashboard CRUD, source selectors, English/Polish strings and OpenAPI schemas expose the new model.
- Web Service index files are optional (0..16). An empty list skips index lookup, allowing directory-listing-only services without synthetic index names.
- Generated directory listings use a self-contained RustProxy panel-style layout (matching palette/typography, responsive light/dark treatment and RustProxy footer) while preserving HTML escaping, URL encoding and filesystem-path secrecy.
- Pure Web Server vhosts no longer present backend diagnostics as though an origin pool existed; their Backends summary identifies `Web Server`, while mixed static/proxy vhosts retain backend diagnostics.
- Status payloads expose fixed-cardinality per-vhost metrics independently of backend pools. The Vhosts dashboard uses those counters for Web Server-only request/active/error values, EWMA latency and byte-rate traffic while retaining backend health details for reverse-proxy sources.
- Logging settings are split into Outputs, JSON format and Error log sub-tabs inside one card, matching the Cache subsection layout. `route`, `cache_effective_enabled` and `transform_effective_enabled` are accepted by the same backend validator that saves custom JSON mappings, and the regression test validates then renders those fields.
- Structured logging exposes `user_agent` and `country`. User-Agent capture defaults on; country capture defaults off. Country resolution is skipped unless capture or an existing country ACL/rate-limit needs it; verified Cloudflare mode prefers trusted `CF-IPCountry`, otherwise local GeoIP is used.
- The authenticated Traffic workspace uses a bounded 2048-sample non-blocking queue, at most 64 vhost buckets and 32-entry approximate top tables for countries, User-Agents, effective client IPs, paths, routes, methods, status classes and cache outcomes. It never writes per-request analytics to SQLite. The Traffic UI is split into Overview, Clients, Requests and Delivery sub-tabs. Countries/methods/status/cache use donut summaries; User-Agent, effective client IP, path, route and request/response byte volume use bounded horizontal bar charts from the same in-memory TOP state.
- Sustained-traffic memory maintenance calls `malloc_trim(0)` every 60 seconds on Linux/glibc to release already-free allocator pages and trims HOT objects only when above the configured waterline and idle past the configured age. Backend Hyper clients additionally recycle their connection-pool generation after 25,000 requests or 3,600 seconds by default; both triggers are configurable/disableable per vhost, and HTTP/1 upstream adaptive read buffers default to a 128 KiB ceiling. Recycling does not publish a new vhost snapshot or purge cache.
- The live Backends table renders `upstream_client_generation`, `upstream_client_generation_requests`, `upstream_client_generation_age_seconds` and `upstream_client_rotations_total` in the same row as Active/Requests/Errors/Latency/Traffic. `app/core.js` feeds the table from the existing `/api/events` `snapshot.status` payload, so the new fields do not introduce another polling loop or control-plane query.
- Cache compatibility currently includes vhost configuration, so a cache-affecting vhost save can purge cache during snapshot publication; replacing the compiled snapshot also retires its upstream clients after outstanding references drain. These mechanisms explain why a manual vhost save can visibly reduce RSS without implying that reloads are required for steady-state operation.
- The Web Server list now uses the same vhost card/detail-cell visual structure as the Vhosts list. Reverse Proxy vhost creation reports a dedicated “at least one backend” validation error instead of the generic paths/ACL/headers JSON error.
- Web Service cards use only the shared `el(...)` DOM helper; a regression contract rejects the previous undefined `text(...)` call that aborted list rendering. Authenticated-shell startup now verifies `/api/auth/session` first and never reveals the legacy embedded sign-in form for non-authentication JavaScript/API failures, preventing F5 from falsely presenting the user as logged out.
- The Web Server UI module performs a catch-up render when it registers after `app.js`; this closes the race where a very fast `/api/config` response could finish before `web-server.js` existed and leave the Web Service list blank until a later render.
- The Web Service editor now builds its configuration tabs with the same `vh-tabs` + `quiet` button pattern and active/ARIA state handling as the Vhost editor; the source contract also checks this shared modal-tab pattern and invalid-field navigation switches to the owning tab.
- Static dispatch clones only `Method`, `HeaderMap` and `Uri` before awaiting the filesystem source; it does not hold `&Request<Body>` across an await, keeping the Axum 0.8 fallback handler future `Send`. This addresses the reported `Handler<_, _>` / E0277 failure at `.fallback(forward)`.
- `/docs` is an authenticated documentation surface with a Markdown Reader, searchable embedded docs bundle and a separate API/OpenAPI tab. Reader Markdown files are direct `include_str!` compiler inputs and no longer depend on a generated `OUT_DIR/docs-content.json`, preventing clean/incremental builds from failing on a missing documentation artifact.
- Documentation cleanup keeps README and the active ROADMAP current-only; operator release history, developer-level changelog history and completed roadmap milestones are separated into CHANGELOG/audit/archive documents.
Backend-table visible columns and sort order are presentation-only fields in the existing account-scoped `admin_preferences` document; search text is not persisted and none of these view settings alter RustProxy configuration. The quick vhost action changes only the selected vhost `enabled` flag through the same `saveSection("vhosts", ...)` revision/CSRF path as other vhost edits. Existing backend client recycling defaults remain unchanged.
## Implemented automated coverage
Rust unit tests in `src/web_server/tests.rs` cover GET/HEAD/index/slash redirect, MIME/length, validators, valid/invalid range handling, If-Range, escaped/repeated directory listings, per-directory index-before-listing fallback, generic 403/HEAD behavior, MIME overrides/default MIME, cache rules, source headers, `try_files`, rewrite redirect/internal behavior, `.htaccess` WordPress-style front-controller routing, nested DirectoryIndex/MIME/Header/Expires behavior, strict-mode rejection and PHP-source protection, hardening probe/method blocking and fingerprint-header stripping, precompressed representations/ranges, download headers, PHP FastCGI fixture execution/failure-source-disclosure behavior, request-body 413 enforcement, custom FastCGI/PHP_VALUE validation and reserved-parameter rejection, direct traversal rejection and Unix/Linux symlink policy. Upstream tests cover request-budget client generation recycling and disabled recycling; balancing tests cover the new keepalive defaults and validation bounds. `src/traffic/tests.rs` includes bounded-table, effective-client-IP view and fixed Prometheus User-Agent/method family coverage. Configuration tests cover pure static vhosts without a pool, mixed Web Server/Reverse Proxy routing, reuse of one Web Service by multiple vhosts, unknown service references and backward-compatible default source behavior. Vhost tests cover method-policy normalization/allow/block behavior, database tests cover retained configuration revision documents, actor metadata and previous-revision lookup, and admin tests cover restoring revision 1 as revision 3 while preserving the previous document and emitting `config.restore`. ACL tests include encoded traversal/separator/malformed-encoding cases.
Rust unit tests in `src/web_server/tests.rs` cover GET/HEAD/index/slash redirect, MIME/length, validators, valid/invalid range handling, If-Range, escaped/repeated directory listings, per-directory index-before-listing fallback, generic 403/HEAD behavior, MIME overrides/default MIME, cache rules, source headers, `try_files`, rewrite redirect/internal behavior, `.htaccess` WordPress-style front-controller routing, nested DirectoryIndex/MIME/Header/Expires behavior, strict-mode rejection and PHP-source protection, hardening probe/method blocking and fingerprint-header stripping, precompressed representations/ranges, download headers, PHP FastCGI fixture execution/failure-source-disclosure behavior, request-body 413 enforcement, custom FastCGI/PHP_VALUE validation and reserved-parameter rejection, direct traversal rejection and Unix/Linux symlink policy. Upstream tests cover request-budget client generation recycling and disabled recycling; balancing tests cover the new keepalive defaults and validation bounds. `src/traffic/tests.rs` includes bounded-table, effective-client-IP view and fixed Prometheus User-Agent/method family coverage. Configuration tests cover pure static vhosts without a pool, mixed Web Server/Reverse Proxy routing, reuse of one Web Service by multiple vhosts, unknown service references and backward-compatible default source behavior. Vhost tests cover method-policy normalization/allow/block behavior, database tests cover retained configuration revision documents, actor metadata and previous-revision lookup, and admin tests cover account-scoped/CSRF-protected view preferences including legacy preference defaults, backend-table columns/sort persistence and invalid-column rejection, plus restoring revision 1 as revision 3 while preserving the previous document and emitting `config.restore`. ACL tests include encoded traversal/separator/malformed-encoding cases.
Rust-independent release checks additionally validate UI/JSON/language/source contracts and reproducible packaging.
During the prior 0.25.4 compiler gate, an external build exposed one module-privacy error in the history split (`CONFIG_HISTORY_CAPACITY` was private to `db::history`) and one unused `country_resolved` assignment in the location GeoIP branch. Both were corrected before the 0.25.5 baseline: the capacity is exposed to its parent module with `pub(super)`, referenced through `history::CONFIG_HISTORY_CAPACITY`, and the dead assignment is removed. This workspace still has no Rust/Cargo toolchain, so compiler confirmation of the 0.25.6 tree remains a release-host gate.
During the prior 0.25.4 compiler gate, an external build exposed one module-privacy error in the history split (`CONFIG_HISTORY_CAPACITY` was private to `db::history`) and one unused `country_resolved` assignment in the location GeoIP branch. Both were corrected before the 0.25.5 baseline: the capacity is exposed to its parent module with `pub(super)`, referenced through `history::CONFIG_HISTORY_CAPACITY`, and the dead assignment is removed. This workspace still has no Rust/Cargo toolchain, so compiler confirmation of the 0.25.7 tree remains a release-host gate.
### Modular source-tree verification
- The new upstream client implementation is split into `src/upstream/client.rs` with test bodies in `src/upstream/client_tests.rs`; both stay below the 500-line module limit, and `src/upstream.rs` remains below that limit after moving client construction out.
- `python3 scripts/source_module_contracts.py`: PASS. Dashboard JavaScript, Docker Agent Go implementation and operational deploy/NPM-migration Python modules remain within their source-size contracts.
- `python3 scripts/rust_module_contracts.py` is BLOCKED by a pre-existing source-tree issue in the supplied 0.25.5 baseline archive: `src/admin/documentation.rs` is 554 lines while the contract limit is 500. The same failure reproduces on the unmodified 0.25.5 baseline archive.
- `python3 scripts/docs_check.py` is also BLOCKED by a pre-existing supplied-archive issue: the Reader does not embed `docs/ACME_PROVIDERS.md`. This also reproduces on the unmodified input archive.
- Rust unit-test bodies added for this change are isolated in a test include file; the implementation module contains only the guarded include declaration.
- Cargo parsing/type checking remains a release-host requirement because this workspace has no Rust toolchain.
- `src/admin/documentation.rs` was split so the embedded Reader document catalog lives in `src/admin/documentation/documents.rs`; both files now stay below the 500-line Rust module limit without changing Reader behavior.
- The Rust module contract now recognizes the existing `*_tests.rs` naming convention, and the documentation contract validates embedded documents independently of `include_str!` line wrapping.
- `python3 scripts/rust_module_contracts.py`: PASS.
- `python3 scripts/source_module_contracts.py`: PASS. Dashboard JavaScript, Docker Agent Go implementation and operational Python modules remain within their source-size contracts.
- `python3 scripts/docs_check.py`: PASS. The documentation index, authenticated Reader/API split and local Markdown links are consistent.
- Cargo parsing/type checking remains a release-host requirement because this workspace has no Rust/Cargo toolchain.
## Verification status in this workspace
@@ -80,26 +29,23 @@ During the prior 0.25.4 compiler gate, an external build exposed one module-priv
|---|---|
| `python3 scripts/langs_check.py` | PASS |
| `node --check src/admin/web/vhosts/render.js` | PASS |
| Backend live-table source contract | PASS: four client-generation diagnostics are rendered from backend member status fields |
| `python3 scripts/memory_contracts.py` | PASS, including upstream generation-recycle/buffer-cap source contracts |
| `node --check src/admin/web/vhosts/editor.js` | PASS |
| 0.25.7 vhost/dashboard source contracts | PASS: quick enable/disable, backend search/sort/column controls, account-scoped backend table preferences, themed scrollbars and distinct Enabled editor control are present |
| 0.25.7 Prometheus/Grafana source contracts | PASS: retry/client-lifecycle families exist for ordinary and Docker backends and matching dashboard queries are present |
| `python3 scripts/memory_contracts.py` | PASS |
| `python3 scripts/web_server_contracts.py` | PASS |
| `python3 scripts/security_contracts.py` | PASS |
| `python3 scripts/security_dependency_contracts.py` | PASS |
| `python3 scripts/source_module_contracts.py` | PASS |
| `node --check src/admin/web/vhosts/editor.js` | PASS |
| `python3 scripts/rust_module_contracts.py` | PASS |
| `python3 scripts/docs_check.py` | PASS |
| `node scripts/web_service_ui_test.mjs` | PASS |
| OpenAPI/language JSON parse | PASS |
| OpenAPI/language/Grafana JSON parse | PASS |
| `go test ./...` in `docker-agent` | PASS |
| SQL migration/backup smoke (`release_smoke.sql_and_backup_smoke`) | PASS |
| Stopped-source upgrade smoke (`release_smoke.upgrade_smoke`) | PASS |
| `python3 scripts/rust_module_contracts.py` | BLOCKED by pre-existing `src/admin/documentation.rs` 554-line baseline failure |
| `python3 scripts/docs_check.py` | BLOCKED by pre-existing missing `docs/ACME_PROVIDERS.md` Reader embed |
| `python3 scripts/check_source.py` | BLOCKED because it invokes the same pre-existing Rust module-size gate |
| `python3 scripts/release_smoke.py` | BLOCKED because it invokes the same pre-existing Rust module-size gate; its SQL/upgrade smoke functions pass when run directly |
| `./dev.sh test` | BLOCKED at the same pre-existing Rust module-size gate before reaching Cargo |
| `cargo fmt --all` / `./dev.sh fmt` | BLOCKED: Rust/Cargo toolchain unavailable in this workspace |
| `cargo check --locked --all-targets` / `./dev.sh check` | BLOCKED: Rust/Cargo toolchain unavailable in this workspace |
| `cargo test --locked --all-targets` | BLOCKED: Rust/Cargo toolchain unavailable in this workspace |
| Live sustained HTTPS/HTTP/1.1 RSS acceptance | release-host acceptance gate; verify RSS plateau and `upstream_client_rotations_total` under representative traffic |
| `python3 scripts/check_source.py` | PASS; explicitly reports Rust parsing/types/runtime not verified |
| `python3 scripts/release_smoke.py` | PASS, including SQLite/backup/upgrade, Debian/Grafana and reproducible source-archive contracts |
| `./dev.sh test` | Static/security/UI/smoke gates PASS; exits 127 when it reaches Rust because Cargo is unavailable |
| `./dev.sh check` | BLOCKED: Rust/Cargo toolchain unavailable in this workspace |
| `cargo fmt --all`, `cargo check --locked --all-targets`, `cargo test --locked --all-targets` | BLOCKED: Rust/Cargo toolchain unavailable in this workspace |
The new source-level memory bounds and UI/config contracts pass the available targeted checks. Compiler-enabled and sustained-traffic runtime acceptance remain required before production release; two unrelated static gates are already failing in the supplied 0.25.5 baseline as described above.
All Rust-independent release gates available in this workspace pass. Compiler formatting, parsing, type checking, linking and Rust unit/integration tests still require a Rust-enabled release host before treating the archive as compiler-verified production output.
+4 -4
View File
@@ -2,13 +2,13 @@
`rustproxy-overview.json` is a provisioning/import dashboard for the dedicated
RustProxy Prometheus exporter. It covers global traffic, status classes, vhost
latency, backend health/latency, a dedicated Docker-vhost section, hot/cold/transient
cache memory, Linux process and cgroup diagnostics, HTTP/2/HTTP/3/TLS/WebSocket
latency, backend health/latency, upstream-client generation/retry/rotation diagnostics,
a dedicated Docker-vhost section, hot/cold/transient cache memory, Linux process and cgroup diagnostics, HTTP/2/HTTP/3/TLS/WebSocket
counters, Layer-4 streams, structured-log sink health, configuration inventory and bounded Traffic analytics.
The **Docker vhosts** row uses only the dedicated `rust_proxy_docker_*` metric
families and includes configured/enabled counts, request/error rates, p95 exchange
latency, backend health/EWMA latency/active work, traffic and probe age. Dashboard
latency, backend health/EWMA latency/active work, traffic, probe age and upstream-client lifecycle/retry panels. Dashboard
variables **Docker agent** and **Docker service** filter only this section, which keeps
multi-agent installations readable without changing the ordinary vhost panels.
@@ -19,7 +19,7 @@ classes, cache outcomes, capture-state gauges and configured-route traffic. Raw
are intentionally absent from Prometheus labels.
Import the JSON in Grafana and select a Prometheus datasource when prompted. The
dashboard expects RustProxy 0.25.6 metric names and a scrape interval around 15s.
dashboard expects RustProxy 0.25.7 metric names and a scrape interval around 15s.
Collectors can be disabled in RustProxy settings; panels for disabled collectors
will simply show no series.
+406 -70
View File
@@ -670,6 +670,174 @@
}
]
},
{
"id": 83,
"type": "timeseries",
"title": "Backend client generation",
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 0,
"y": 35
},
"fieldConfig": {
"defaults": {
"unit": "short",
"custom": {
"drawStyle": "line",
"lineInterpolation": "linear",
"lineWidth": 1,
"fillOpacity": 10,
"showPoints": "never",
"spanNulls": false,
"axisPlacement": "auto"
}
},
"overrides": []
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom",
"calcs": []
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"refId": "A",
"expr": "rust_proxy_backend_upstream_client_generation",
"legendFormat": "{{vhost}} / {{backend}} generation",
"range": true
},
{
"refId": "B",
"expr": "rust_proxy_backend_upstream_client_generation_requests",
"legendFormat": "{{vhost}} / {{backend}} requests",
"range": true
}
],
"description": "Current reusable upstream-client generation and the requests served by that generation."
},
{
"id": 84,
"type": "timeseries",
"title": "Backend client generation age",
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 8,
"y": 35
},
"fieldConfig": {
"defaults": {
"unit": "s",
"custom": {
"drawStyle": "line",
"lineInterpolation": "linear",
"lineWidth": 1,
"fillOpacity": 10,
"showPoints": "never",
"spanNulls": false,
"axisPlacement": "auto"
}
},
"overrides": []
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom",
"calcs": []
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"refId": "A",
"expr": "rust_proxy_backend_upstream_client_generation_age_seconds",
"legendFormat": "{{vhost}} / {{backend}}",
"range": true
}
],
"description": "Age of the currently active reusable upstream-client generation."
},
{
"id": 85,
"type": "timeseries",
"title": "Backend retries and rotations",
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 16,
"y": 35
},
"fieldConfig": {
"defaults": {
"unit": "ops",
"custom": {
"drawStyle": "line",
"lineInterpolation": "linear",
"lineWidth": 1,
"fillOpacity": 10,
"showPoints": "never",
"spanNulls": false,
"axisPlacement": "auto"
}
},
"overrides": []
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom",
"calcs": []
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"refId": "A",
"expr": "sum by (vhost,backend) (rate(rust_proxy_backend_retry_attempts_total[$__rate_interval]))",
"legendFormat": "{{vhost}} / {{backend}} retry attempts",
"range": true
},
{
"refId": "B",
"expr": "sum by (vhost,backend) (rate(rust_proxy_backend_retry_successes_total[$__rate_interval]))",
"legendFormat": "{{vhost}} / {{backend}} retry successes",
"range": true
},
{
"refId": "C",
"expr": "sum by (vhost,backend) (rate(rust_proxy_backend_upstream_client_rotations_total[$__rate_interval]))",
"legendFormat": "{{vhost}} / {{backend}} client rotations",
"range": true
}
],
"description": "Backend retry attempts/successes and reusable upstream-client rotations per second."
},
{
"id": 58,
"type": "row",
@@ -679,7 +847,7 @@
"h": 1,
"w": 24,
"x": 0,
"y": 35
"y": 42
},
"panels": []
},
@@ -695,7 +863,7 @@
"h": 5,
"w": 6,
"x": 0,
"y": 36
"y": 43
},
"fieldConfig": {
"defaults": {
@@ -739,7 +907,7 @@
"h": 5,
"w": 6,
"x": 6,
"y": 36
"y": 43
},
"fieldConfig": {
"defaults": {
@@ -783,7 +951,7 @@
"h": 5,
"w": 6,
"x": 12,
"y": 36
"y": 43
},
"fieldConfig": {
"defaults": {
@@ -827,7 +995,7 @@
"h": 5,
"w": 6,
"x": 18,
"y": 36
"y": 43
},
"fieldConfig": {
"defaults": {
@@ -871,7 +1039,7 @@
"h": 7,
"w": 8,
"x": 0,
"y": 41
"y": 48
},
"fieldConfig": {
"defaults": {
@@ -920,7 +1088,7 @@
"h": 7,
"w": 8,
"x": 8,
"y": 41
"y": 48
},
"fieldConfig": {
"defaults": {
@@ -975,7 +1143,7 @@
"h": 7,
"w": 8,
"x": 16,
"y": 41
"y": 48
},
"fieldConfig": {
"defaults": {
@@ -1024,7 +1192,7 @@
"h": 7,
"w": 8,
"x": 0,
"y": 48
"y": 55
},
"fieldConfig": {
"defaults": {
@@ -1073,7 +1241,7 @@
"h": 7,
"w": 8,
"x": 8,
"y": 48
"y": 55
},
"fieldConfig": {
"defaults": {
@@ -1122,7 +1290,7 @@
"h": 7,
"w": 8,
"x": 16,
"y": 48
"y": 55
},
"fieldConfig": {
"defaults": {
@@ -1171,7 +1339,7 @@
"h": 7,
"w": 8,
"x": 0,
"y": 55
"y": 62
},
"fieldConfig": {
"defaults": {
@@ -1220,7 +1388,7 @@
"h": 7,
"w": 8,
"x": 8,
"y": 55
"y": 62
},
"fieldConfig": {
"defaults": {
@@ -1275,7 +1443,7 @@
"h": 7,
"w": 8,
"x": 16,
"y": 55
"y": 62
},
"fieldConfig": {
"defaults": {
@@ -1312,6 +1480,174 @@
}
]
},
{
"id": 86,
"type": "timeseries",
"title": "Docker client generation",
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 0,
"y": 69
},
"fieldConfig": {
"defaults": {
"unit": "short",
"custom": {
"drawStyle": "line",
"lineInterpolation": "linear",
"lineWidth": 1,
"fillOpacity": 10,
"showPoints": "never",
"spanNulls": false,
"axisPlacement": "auto"
}
},
"overrides": []
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom",
"calcs": []
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"refId": "A",
"expr": "rust_proxy_docker_backend_upstream_client_generation{agent=~\"$docker_agent\",service=~\"$docker_service\"}",
"legendFormat": "{{agent}} / {{service}} / {{backend}} generation",
"range": true
},
{
"refId": "B",
"expr": "rust_proxy_docker_backend_upstream_client_generation_requests{agent=~\"$docker_agent\",service=~\"$docker_service\"}",
"legendFormat": "{{agent}} / {{service}} / {{backend}} requests",
"range": true
}
],
"description": "Current reusable upstream-client generation and requests for Docker-discovered backends."
},
{
"id": 87,
"type": "timeseries",
"title": "Docker client generation age",
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 8,
"y": 69
},
"fieldConfig": {
"defaults": {
"unit": "s",
"custom": {
"drawStyle": "line",
"lineInterpolation": "linear",
"lineWidth": 1,
"fillOpacity": 10,
"showPoints": "never",
"spanNulls": false,
"axisPlacement": "auto"
}
},
"overrides": []
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom",
"calcs": []
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"refId": "A",
"expr": "rust_proxy_docker_backend_upstream_client_generation_age_seconds{agent=~\"$docker_agent\",service=~\"$docker_service\"}",
"legendFormat": "{{agent}} / {{service}} / {{backend}}",
"range": true
}
],
"description": "Age of the active reusable upstream-client generation for Docker-discovered backends."
},
{
"id": 88,
"type": "timeseries",
"title": "Docker retries and rotations",
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 16,
"y": 69
},
"fieldConfig": {
"defaults": {
"unit": "ops",
"custom": {
"drawStyle": "line",
"lineInterpolation": "linear",
"lineWidth": 1,
"fillOpacity": 10,
"showPoints": "never",
"spanNulls": false,
"axisPlacement": "auto"
}
},
"overrides": []
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom",
"calcs": []
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"refId": "A",
"expr": "sum by (agent,service,backend) (rate(rust_proxy_docker_backend_retry_attempts_total{agent=~\"$docker_agent\",service=~\"$docker_service\"}[$__rate_interval]))",
"legendFormat": "{{agent}} / {{service}} / {{backend}} retry attempts",
"range": true
},
{
"refId": "B",
"expr": "sum by (agent,service,backend) (rate(rust_proxy_docker_backend_retry_successes_total{agent=~\"$docker_agent\",service=~\"$docker_service\"}[$__rate_interval]))",
"legendFormat": "{{agent}} / {{service}} / {{backend}} retry successes",
"range": true
},
{
"refId": "C",
"expr": "sum by (agent,service,backend) (rate(rust_proxy_docker_backend_upstream_client_rotations_total{agent=~\"$docker_agent\",service=~\"$docker_service\"}[$__rate_interval]))",
"legendFormat": "{{agent}} / {{service}} / {{backend}} client rotations",
"range": true
}
],
"description": "Retry attempts/successes and reusable upstream-client rotations for Docker-discovered backends."
},
{
"id": 16,
"type": "row",
@@ -1321,7 +1657,7 @@
"h": 1,
"w": 24,
"x": 0,
"y": 62
"y": 76
},
"panels": []
},
@@ -1337,7 +1673,7 @@
"h": 5,
"w": 6,
"x": 0,
"y": 63
"y": 77
},
"fieldConfig": {
"defaults": {
@@ -1381,7 +1717,7 @@
"h": 5,
"w": 6,
"x": 6,
"y": 63
"y": 77
},
"fieldConfig": {
"defaults": {
@@ -1425,7 +1761,7 @@
"h": 5,
"w": 6,
"x": 12,
"y": 63
"y": 77
},
"fieldConfig": {
"defaults": {
@@ -1469,7 +1805,7 @@
"h": 5,
"w": 6,
"x": 18,
"y": 63
"y": 77
},
"fieldConfig": {
"defaults": {
@@ -1513,7 +1849,7 @@
"h": 7,
"w": 12,
"x": 0,
"y": 68
"y": 82
},
"fieldConfig": {
"defaults": {
@@ -1580,7 +1916,7 @@
"h": 7,
"w": 12,
"x": 12,
"y": 68
"y": 82
},
"fieldConfig": {
"defaults": {
@@ -1647,7 +1983,7 @@
"h": 7,
"w": 12,
"x": 0,
"y": 75
"y": 89
},
"fieldConfig": {
"defaults": {
@@ -1720,7 +2056,7 @@
"h": 7,
"w": 12,
"x": 12,
"y": 75
"y": 89
},
"fieldConfig": {
"defaults": {
@@ -1766,7 +2102,7 @@
"h": 1,
"w": 24,
"x": 0,
"y": 82
"y": 96
},
"panels": []
},
@@ -1782,7 +2118,7 @@
"h": 8,
"w": 12,
"x": 0,
"y": 83
"y": 97
},
"fieldConfig": {
"defaults": {
@@ -1861,7 +2197,7 @@
"h": 8,
"w": 12,
"x": 12,
"y": 83
"y": 97
},
"fieldConfig": {
"defaults": {
@@ -1916,7 +2252,7 @@
"h": 7,
"w": 8,
"x": 0,
"y": 91
"y": 105
},
"fieldConfig": {
"defaults": {
@@ -1971,7 +2307,7 @@
"h": 7,
"w": 8,
"x": 8,
"y": 91
"y": 105
},
"fieldConfig": {
"defaults": {
@@ -2032,7 +2368,7 @@
"h": 7,
"w": 8,
"x": 16,
"y": 91
"y": 105
},
"fieldConfig": {
"defaults": {
@@ -2087,7 +2423,7 @@
"h": 7,
"w": 12,
"x": 0,
"y": 98
"y": 112
},
"fieldConfig": {
"defaults": {
@@ -2142,7 +2478,7 @@
"h": 7,
"w": 12,
"x": 12,
"y": 98
"y": 112
},
"fieldConfig": {
"defaults": {
@@ -2194,7 +2530,7 @@
"h": 1,
"w": 24,
"x": 0,
"y": 105
"y": 119
},
"panels": []
},
@@ -2210,7 +2546,7 @@
"h": 7,
"w": 8,
"x": 0,
"y": 106
"y": 120
},
"fieldConfig": {
"defaults": {
@@ -2271,7 +2607,7 @@
"h": 7,
"w": 8,
"x": 8,
"y": 106
"y": 120
},
"fieldConfig": {
"defaults": {
@@ -2326,7 +2662,7 @@
"h": 7,
"w": 8,
"x": 16,
"y": 106
"y": 120
},
"fieldConfig": {
"defaults": {
@@ -2381,7 +2717,7 @@
"h": 7,
"w": 12,
"x": 0,
"y": 113
"y": 127
},
"fieldConfig": {
"defaults": {
@@ -2448,7 +2784,7 @@
"h": 7,
"w": 12,
"x": 12,
"y": 113
"y": 127
},
"fieldConfig": {
"defaults": {
@@ -2512,7 +2848,7 @@
"h": 1,
"w": 24,
"x": 0,
"y": 120
"y": 134
},
"panels": []
},
@@ -2528,7 +2864,7 @@
"h": 7,
"w": 12,
"x": 0,
"y": 121
"y": 135
},
"fieldConfig": {
"defaults": {
@@ -2589,7 +2925,7 @@
"h": 7,
"w": 12,
"x": 12,
"y": 121
"y": 135
},
"fieldConfig": {
"defaults": {
@@ -2644,7 +2980,7 @@
"h": 7,
"w": 12,
"x": 0,
"y": 128
"y": 142
},
"fieldConfig": {
"defaults": {
@@ -2705,7 +3041,7 @@
"h": 7,
"w": 12,
"x": 12,
"y": 128
"y": 142
},
"fieldConfig": {
"defaults": {
@@ -2763,7 +3099,7 @@
"h": 1,
"w": 24,
"x": 0,
"y": 135
"y": 149
},
"panels": []
},
@@ -2779,7 +3115,7 @@
"h": 7,
"w": 12,
"x": 0,
"y": 136
"y": 150
},
"fieldConfig": {
"defaults": {
@@ -2864,7 +3200,7 @@
"h": 7,
"w": 12,
"x": 12,
"y": 136
"y": 150
},
"fieldConfig": {
"defaults": {
@@ -2910,7 +3246,7 @@
"h": 1,
"w": 24,
"x": 0,
"y": 143
"y": 157
},
"panels": []
},
@@ -2952,7 +3288,7 @@
"h": 7,
"w": 12,
"x": 0,
"y": 144
"y": 158
},
"description": "Seconds until the observed certificate notAfter timestamp.",
"targets": [
@@ -3002,7 +3338,7 @@
"h": 7,
"w": 6,
"x": 12,
"y": 144
"y": 158
},
"targets": [
{
@@ -3025,7 +3361,7 @@
"h": 7,
"w": 6,
"x": 18,
"y": 144
"y": 158
},
"fieldConfig": {
"defaults": {
@@ -3063,7 +3399,7 @@
"h": 1,
"w": 24,
"x": 0,
"y": 151
"y": 165
},
"id": 51,
"panels": [],
@@ -3133,7 +3469,7 @@
"h": 7,
"w": 8,
"x": 0,
"y": 152
"y": 166
},
"id": 52,
"options": {
@@ -3229,7 +3565,7 @@
"h": 7,
"w": 8,
"x": 8,
"y": 152
"y": 166
},
"id": 53,
"options": {
@@ -3336,7 +3672,7 @@
"h": 7,
"w": 8,
"x": 16,
"y": 152
"y": 166
},
"id": 54,
"options": {
@@ -3375,7 +3711,7 @@
"h": 1,
"w": 24,
"x": 0,
"y": 159
"y": 173
},
"id": 55,
"panels": [],
@@ -3445,7 +3781,7 @@
"h": 7,
"w": 12,
"x": 0,
"y": 160
"y": 174
},
"id": 56,
"options": {
@@ -3552,7 +3888,7 @@
"h": 7,
"w": 12,
"x": 12,
"y": 160
"y": 174
},
"id": 57,
"options": {
@@ -3605,7 +3941,7 @@
"h": 1,
"w": 24,
"x": 0,
"y": 167
"y": 181
},
"panels": []
},
@@ -3621,7 +3957,7 @@
"h": 7,
"w": 6,
"x": 0,
"y": 168
"y": 182
},
"fieldConfig": {
"defaults": {
@@ -3665,7 +4001,7 @@
"h": 7,
"w": 6,
"x": 6,
"y": 168
"y": 182
},
"fieldConfig": {
"defaults": {
@@ -3709,7 +4045,7 @@
"h": 7,
"w": 6,
"x": 12,
"y": 168
"y": 182
},
"fieldConfig": {
"defaults": {
@@ -3754,7 +4090,7 @@
"h": 7,
"w": 6,
"x": 18,
"y": 168
"y": 182
},
"fieldConfig": {
"defaults": {
@@ -3799,7 +4135,7 @@
"h": 7,
"w": 12,
"x": 0,
"y": 175
"y": 189
},
"fieldConfig": {
"defaults": {
@@ -3848,7 +4184,7 @@
"h": 7,
"w": 12,
"x": 12,
"y": 175
"y": 189
},
"fieldConfig": {
"defaults": {
@@ -3897,7 +4233,7 @@
"h": 7,
"w": 12,
"x": 0,
"y": 182
"y": 196
},
"fieldConfig": {
"defaults": {
@@ -3946,7 +4282,7 @@
"h": 7,
"w": 12,
"x": 12,
"y": 182
"y": 196
},
"fieldConfig": {
"defaults": {
@@ -3995,7 +4331,7 @@
"h": 5,
"w": 12,
"x": 0,
"y": 189
"y": 203
},
"fieldConfig": {
"defaults": {
@@ -4050,7 +4386,7 @@
"h": 5,
"w": 12,
"x": 12,
"y": 189
"y": 203
},
"fieldConfig": {
"defaults": {
@@ -4193,9 +4529,9 @@
},
"timepicker": {},
"timezone": "browser",
"title": "RustProxy 0.25.6 - Operations",
"title": "RustProxy 0.25.7 - Operations",
"uid": "rustproxy-0170",
"version": 4,
"version": 5,
"weekStart": "",
"description": "RustProxy 0.25.6 operations dashboard including bounded Traffic analytics, country and User-Agent-family visibility, and built-in Web Server traffic."
"description": "RustProxy 0.25.7 operations dashboard including backend client lifecycle/retry diagnostics, bounded Traffic analytics, country and User-Agent-family visibility, and built-in Web Server traffic."
}
+12 -1
View File
@@ -1962,6 +1962,17 @@
"vhPoolRecycleRequests": "Recycle upstream pool after requests (0 = off)",
"vhPoolRecycleSeconds": "Maximum upstream pool age (seconds, 0 = off)",
"vhHttp1MaxBuffer": "HTTP/1 upstream max read buffer (bytes)",
"vhPoolRecycleHelp": "Recycling replaces only the Hyper client generation for each backend; active exchanges continue normally. Defaults bound long-lived pool/TLS buffer retention without reloading the vhost or cache. Request recycling accepts 0 or 1000..10000000; age accepts 0 or 60..604800."
"vhPoolRecycleHelp": "Recycling replaces only the Hyper client generation for each backend; active exchanges continue normally. Defaults bound long-lived pool/TLS buffer retention without reloading the vhost or cache. Request recycling accepts 0 or 1000..10000000; age accepts 0 or 60..604800.",
"backendSearch": "Search backends",
"backendSearchPlaceholder": "Search vhost, backend or state",
"backendSortBy": "Sort by",
"backendSortAscending": "Ascending sort",
"backendSortDescending": "Descending sort",
"backendColumns": "Columns",
"backendSearchEmpty": "No backends match the current search.",
"vhQuickEnable": "Enable",
"vhQuickDisable": "Disable",
"vhEnableHint": "Controls whether this vhost accepts traffic.",
"backendColumnsRequired": "At least one backend table column must remain visible."
}
}
+12 -1
View File
@@ -1962,6 +1962,17 @@
"vhPoolRecycleRequests": "Odśwież pulę upstream po liczbie żądań (0 = wył.)",
"vhPoolRecycleSeconds": "Maks. wiek puli upstream (sekundy, 0 = wył.)",
"vhHttp1MaxBuffer": "Maks. bufor odczytu HTTP/1 upstream (bajty)",
"vhPoolRecycleHelp": "Odświeżanie podmienia tylko generację klienta Hyper danego backendu; aktywne wymiany kończą się normalnie. Domyślne limity ograniczają retencję pamięci puli/TLS bez przeładowania vhosta ani cache. Limit żądań: 0 lub 1000..10000000; limit czasu: 0 lub 60..604800."
"vhPoolRecycleHelp": "Odświeżanie podmienia tylko generację klienta Hyper danego backendu; aktywne wymiany kończą się normalnie. Domyślne limity ograniczają retencję pamięci puli/TLS bez przeładowania vhosta ani cache. Limit żądań: 0 lub 1000..10000000; limit czasu: 0 lub 60..604800.",
"backendSearch": "Szukaj backendów",
"backendSearchPlaceholder": "Szukaj vhosta, backendu lub stanu",
"backendSortBy": "Sortuj według",
"backendSortAscending": "Sortowanie rosnące",
"backendSortDescending": "Sortowanie malejące",
"backendColumns": "Kolumny",
"backendSearchEmpty": "Brak backendów pasujących do wyszukiwania.",
"vhQuickEnable": "Włącz",
"vhQuickDisable": "Wyłącz",
"vhEnableHint": "Określa, czy ten vhost przyjmuje ruch.",
"backendColumnsRequired": "Co najmniej jedna kolumna tabeli backendów musi pozostać widoczna."
}
}
+1 -1
View File
@@ -88,7 +88,7 @@ def add_file(archive: zipfile.ZipFile, rel: Path, data: bytes, timestamp: tuple[
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('output', nargs='?', type=Path, default=ROOT.parent / 'RustProxy-0.25.6.zip')
parser.add_argument('output', nargs='?', type=Path, default=ROOT.parent / 'RustProxy-0.25.7.zip')
output = parser.parse_args().output.resolve()
paths = files()
checksum = ROOT / 'SHA256SUMS'
+25 -16
View File
@@ -24,7 +24,7 @@ def read_docker_agent_source() -> str:
preflight(root)
manifest = tomllib.loads((root / "Cargo.toml").read_text())
assert manifest["package"]["version"] == "0.25.6"
assert manifest["package"]["version"] == "0.25.7"
assert manifest["dependencies"]["quinn"]["version"] == "0.11.12"
h3dep = manifest["dependencies"]["h3"]
assert h3dep["package"] == "hpx-h3" and h3dep["version"] == "=2.5.19" and "quinn" in h3dep["features"]
@@ -32,14 +32,14 @@ assert "h3-quinn" not in manifest["dependencies"]
lock_path = root / "Cargo.lock"
if lock_path.exists():
lock = lock_path.read_text()
assert 'name = "rust-proxy"\nversion = "0.25.6"' in lock
assert 'name = "rust-proxy"\nversion = "0.25.7"' in lock
assert 'name = "systemstat"' in lock and 'name = "metrics-process"' in lock and 'name = "sysinfo"' in lock, "Cargo.lock must be resolver-generated after adding cross-platform statistics libraries"
for release_doc, marker in {
"README.md": "# RustProxy 0.25.6",
"CHANGELOG.md": "## 0.25.6",
"ROADMAP.md": "## 0.25.6",
"AGENTS.md": "Current source version: 0.25.6.",
"docs/TEST_REPORT.md": "# Verification report - RustProxy 0.25.6",
"README.md": "# RustProxy 0.25.7",
"CHANGELOG.md": "## 0.25.7",
"ROADMAP.md": "## 0.25.7",
"AGENTS.md": "Current source version: 0.25.7.",
"docs/TEST_REPORT.md": "# Verification report - RustProxy 0.25.7",
}.items():
assert marker in (root / release_doc).read_text(), f"stale release identity in {release_doc}"
assert tomllib.loads((root / "rust-toolchain.toml").read_text())["toolchain"]["channel"] == "stable"
@@ -183,6 +183,15 @@ assert '"force-ssl", "vhForceSsl"' in vhosts_js and 'force_ssl: yes("force-ssl")
assert 'vhForceSslNeedsHttp' in vhosts_js and 'tlsPorts.has(443)' in vhosts_js
assert '"forward-client-ip", "vhForwardClientIp"' in vhosts_js and 'forward_client_ip: yes("forward-client-ip")' in vhosts_js
assert 'id="vhost-search"' in html and '$("vhost-search").addEventListener("input", render)' in vhosts_js
for marker in ['id="pool-search"', 'id="pool-sort"', 'data-pool-column-toggle="vhost"', 'data-pool-column="generationAge"']:
assert marker in html, marker
for marker in ['toggleVhostEnabled', 'vhQuickEnable', 'vhQuickDisable', 'backend_table_columns', 'applyPoolTableView', 'saveSection("vhosts", next.vhosts || [])', 'vh-enable-control']:
assert marker in vhosts_js, marker
assert 'rustproxy-backend-table-v1' not in vhosts_js and 'poolTablePreferenceKey' not in vhosts_js
assert 'window.rustProxyPreferences.update({' in vhosts_js and 'backend_table_sort' in vhosts_js and 'backend_table_descending' in vhosts_js
style_ui = (root / 'src/admin/web/style.css').read_text()
for marker in ['--scrollbar-thumb', '*::-webkit-scrollbar-thumb', '.backend-table-tools', '.vh-enable-control', '.vhost-quick-toggle']:
assert marker in style_ui, marker
assert 'id="admin-session-days"' in html and 'min="7" max="30"' in html
assert 'member-local-risk' in vhosts_js and 'vhLocalListenerRiskConfirm' in vhosts_js
assert 'accept_local_listener_risk: yes("member-local-risk", row)' in vhosts_js
@@ -231,7 +240,7 @@ assert 'Opt-in health decisions based on ordinary user traffic. Defaults to disa
assert '"force_ssl"' in openapi and '"forward_client_ip"' in openapi
assert '"backend_header"' in openapi and '"backend_timing_header"' in openapi
assert '"StreamMetrics"' in openapi and '"streams"' in openapi
for marker in ['"version": "0.25.6"', '"/api/traffic"', '"capture_user_agent"', '"capture_country"', '"hot_auto_trim_enabled"', '"hot_auto_trim_after_seconds"', '"hot_auto_trim_target_percent"', '"/api/config/{section}"', '"ConfigSectionUpdate"', '"cold_on_idle"', '"cold_on_age"', '"cold_max_hot_seconds"', '"cold_on_lru"', '"cold_on_shutdown"', '"cold_gc_interval_seconds"', '"cold_gc_max_delete"', '"collapse_enabled"', '"collapse_wait_timeout_ms"', '"collapse_timeout_fallback"', '"stale_if_error_timeout"', '"stale_if_error_connection"', '"stale_if_error_dns"', '"stale_if_error_tls"', '"stale_if_error_statuses"', '"proactive_refresh_enabled"', '"proactive_refresh_before_expiry_seconds"', '"proactive_refresh_percent"', '"proactive_refresh_max_inflight"', '"TransformSettings"', '"TransformOverrides"', '"/api/cache/cold/cleanup"', '"/api/cache/invalidate"', '"/api/cache/bans"', '"/api/cache/bans/{id}"', '"CacheInvalidationRequest"', '"CacheInvalidationResponse"', '"ColdCleanupJob"', '"match_type"', '"regex_insensitive"', '"rewrite"', '"response"', '"covered_hosts"', '"default": true']:
for marker in ['"version": "0.25.7"', '"/api/traffic"', '"capture_user_agent"', '"capture_country"', '"hot_auto_trim_enabled"', '"hot_auto_trim_after_seconds"', '"hot_auto_trim_target_percent"', '"/api/config/{section}"', '"ConfigSectionUpdate"', '"cold_on_idle"', '"cold_on_age"', '"cold_max_hot_seconds"', '"cold_on_lru"', '"cold_on_shutdown"', '"cold_gc_interval_seconds"', '"cold_gc_max_delete"', '"collapse_enabled"', '"collapse_wait_timeout_ms"', '"collapse_timeout_fallback"', '"stale_if_error_timeout"', '"stale_if_error_connection"', '"stale_if_error_dns"', '"stale_if_error_tls"', '"stale_if_error_statuses"', '"proactive_refresh_enabled"', '"proactive_refresh_before_expiry_seconds"', '"proactive_refresh_percent"', '"proactive_refresh_max_inflight"', '"TransformSettings"', '"TransformOverrides"', '"/api/cache/cold/cleanup"', '"/api/cache/invalidate"', '"/api/cache/bans"', '"/api/cache/bans/{id}"', '"CacheInvalidationRequest"', '"CacheInvalidationResponse"', '"ColdCleanupJob"', '"match_type"', '"regex_insensitive"', '"rewrite"', '"response"', '"covered_hosts"', '"default": true']:
assert marker in openapi, marker
for marker in ['"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
assert marker in openapi, marker
@@ -655,7 +664,7 @@ diagnostics_source = rust_source(root, 'src/diagnostics.rs') + rust_source(root,
for marker in ['process_rss_anon_bytes', 'process_rss_file_bytes', 'process_rss_shmem_bytes', 'process_data_bytes', 'process_swap_bytes', 'RssAnon', 'RssFile', 'RssShmem', 'VmData', 'VmSwap']:
assert marker in diagnostics_source, marker
prometheus_source = rust_source(root, 'src/prometheus.rs')
for marker in ['allow_trusted_network_without_token', 'include_traffic', 'include_docker_vhosts', 'include_streams', 'include_logging', 'rust_proxy_docker_vhost_{name}', 'rust_proxy_docker_backend_{name}', 'rust_proxy_geoip_loaded', 'not_after_timestamp_seconds', 'ewma_latency_seconds', 'passive_failures', 'active_health_failures_total', 'passive_health_failures_total', 'passive_http_5xx_total', 'passive_timeouts_total', 'passive_connect_errors_total', 'passive_io_errors_total', 'passive_slow_total', 'http_5xx_total', 'timeouts_total', 'connect_errors_total', 'io_errors_total', 'slow_responses_total', 'rust_proxy_log_sink_queue_dropped_total', 'rust_proxy_not_found_hosts_configured', 'rust_proxy_traffic_country_requests', 'rust_proxy_traffic_user_agent_family_requests_total', 'rust_proxy_traffic_source_requests_total', 'rust_proxy_traffic_country_capture_enabled', 'rust_proxy_traffic_user_agent_capture_enabled', 'rust_proxy_traffic_route_requests', 'rust_proxy_web_services_configured', 'rust_proxy_web_service_info', 'rust_proxy_vhost_source_info']:
for marker in ['allow_trusted_network_without_token', 'include_traffic', 'include_docker_vhosts', 'include_streams', 'include_logging', 'rust_proxy_docker_vhost_{name}', 'rust_proxy_docker_backend_{name}', 'rust_proxy_geoip_loaded', 'not_after_timestamp_seconds', 'ewma_latency_seconds', 'passive_failures', 'active_health_failures_total', 'passive_health_failures_total', 'passive_http_5xx_total', 'passive_timeouts_total', 'passive_connect_errors_total', 'passive_io_errors_total', 'passive_slow_total', 'http_5xx_total', 'timeouts_total', 'connect_errors_total', 'io_errors_total', 'slow_responses_total', 'rust_proxy_log_sink_queue_dropped_total', 'rust_proxy_not_found_hosts_configured', 'rust_proxy_traffic_country_requests', 'rust_proxy_traffic_user_agent_family_requests_total', 'rust_proxy_traffic_source_requests_total', 'rust_proxy_traffic_country_capture_enabled', 'rust_proxy_traffic_user_agent_capture_enabled', 'rust_proxy_traffic_route_requests', 'rust_proxy_web_services_configured', 'rust_proxy_web_service_info', 'rust_proxy_vhost_source_info', 'retry_attempts_total', 'retry_successes_total', 'upstream_client_generation', 'upstream_client_generation_requests', 'upstream_client_generation_age_seconds', 'upstream_client_rotations_total']:
assert marker in prometheus_source, marker
assert 'if docker_targets.contains_key(&pool.id)' in prometheus_source
assert 'if docker_targets.contains_key(id)' in prometheus_source
@@ -680,7 +689,7 @@ for marker in ['locationMatchRegex', 'headersVisualBuilder', 'headersAdvancedJso
for marker in ['lastApiLatencyMs', 'cacheMemorySamples', 'cacheTransientSamples', 'cacheColdStorageSamples', 'cacheHitSamples', 'processCpuSamples', 'processRssSamples', 'networkRxSamples', 'responseLatencySamples', 'client_ip']:
assert marker in app_js, marker
logging_source = rust_source(root, 'src/logging.rs')
assert 'pub client_ip: Option<String>' in logging_source and '"client_ip" => entry.client_ip' in logging_source
assert 'pub client_ip: Option<String>' in logging_source and '"client_ip" => entry' in logging_source and '.client_ip' in logging_source
for marker in ['check_rustproxy_api', 'response time', 'backend']:
assert marker in (root / 'nagios/README.md').read_text(), marker
stream_source = rust_source(root, "src/stream.rs")
@@ -720,7 +729,7 @@ for marker in ['dpkg-scanpackages', 'apt-ftparchive', 'Packages.gz', 'RUSTPROXY_
grafana = json.loads((root / 'grafana/rustproxy-overview.json').read_text())
assert grafana['uid'] == 'rustproxy-0170' and grafana['schemaVersion'] >= 39
expressions = '\n'.join(target.get('expr','') for panel in grafana['panels'] for target in panel.get('targets',[]))
for marker in ['rust_proxy_process_rss_bytes', 'rust_proxy_cache_transient_bytes', 'rust_proxy_backend_latency_seconds', 'rust_proxy_stream_active', 'rust_proxy_log_sink_queue_dropped_total', 'rust_proxy_traffic_source_requests_total', 'rust_proxy_traffic_country_requests', 'rust_proxy_traffic_user_agent_family_requests_total', 'rust_proxy_traffic_country_capture_enabled', 'rust_proxy_traffic_user_agent_capture_enabled', 'rust_proxy_traffic_route_requests']:
for marker in ['rust_proxy_process_rss_bytes', 'rust_proxy_cache_transient_bytes', 'rust_proxy_backend_latency_seconds', 'rust_proxy_stream_active', 'rust_proxy_log_sink_queue_dropped_total', 'rust_proxy_traffic_source_requests_total', 'rust_proxy_traffic_country_requests', 'rust_proxy_traffic_user_agent_family_requests_total', 'rust_proxy_traffic_country_capture_enabled', 'rust_proxy_traffic_user_agent_capture_enabled', 'rust_proxy_traffic_route_requests', 'rust_proxy_backend_upstream_client_generation_requests', 'rust_proxy_backend_upstream_client_generation_age_seconds', 'rust_proxy_backend_retry_attempts_total', 'rust_proxy_backend_upstream_client_rotations_total', 'rust_proxy_docker_backend_upstream_client_generation_requests', 'rust_proxy_docker_backend_retry_attempts_total']:
assert marker in expressions, marker
assert any(variable.get('name') == 'traffic_vhost' for variable in grafana['templating']['list'])
assert any(panel.get('title') == 'Traffic analytics and Web Server' for panel in grafana['panels'])
@@ -882,7 +891,7 @@ assert 'class="app-loading"' in html and 'document.documentElement.classList.rem
assert 'id="mobile-menu-toggle"' in html and 'id="main-navigation"' in html and 'aria-controls="main-navigation"' in html
assert '.sidebar.mobile-nav-open .main-navigation' in style_source and 'display: none;' in style_source and 'grid-template-columns: repeat(2, minmax(0, 1fr))' in style_source
assert 'function setMobileMenu(open)' in app_js and 'setPanel(button.dataset.panel); setMobileMenu(false);' in app_js and 'event.key === "Escape"' in app_js
assert 'id="about-open"' in html and 'id="about-dialog"' in html and 'Mateusz Griszczynski' in html
assert 'id="about-open"' in html and 'id="about-dialog"' in html and 'Mateusz Gruszczynski' in html
assert 'https://git.linuxiarz.pl/gru/RustProxy' in html and 'data-i18n="documentation" id="api-docs"' in html
assert '$("about-open").addEventListener("click"' in app_js and '$("about-close").addEventListener("click"' in app_js
assert '.about-dialog {' in style_source and '.about-meta {' in style_source and '.top-actions {\n width: 100%;\n flex-wrap: wrap;' in style_source
@@ -908,7 +917,7 @@ assert '.check>span {' in style_source and 'transform: translateY(-1px)' in styl
assert '.docker-alias-field>span:first-child {' in style_source and 'margin-bottom: 0' in style_source and '.docker-alias-editor {' in style_source and 'min-height: 38px' in style_source and 'height: 30px' in style_source
assert 'field.append(box, el("small", t("dockerAliasesHelp")' not in docker_ui
assert 'readOnly: !!editing' not in vhosts_js and 'vhIdRenameConfirm' in vhosts_js and 'v.id === value.id && v.id !== editing' in vhosts_js
assert 'enforce_local_page_no_cache' in proxy_source and 'LocalGeneratedResponse' in proxy_source and 'get::<crate::pages::PageServed>().is_none()' in proxy_source
assert 'enforce_local_page_no_cache' in proxy_source and 'LocalGeneratedResponse' in proxy_source and 'get::<crate::pages::PageServed>()' in proxy_source and '.is_none()' in proxy_source
assert 'locally_generated_pages_remove_cache_metadata' in proxy_source and '"x-cache-reason"' in proxy_source and '"cache-status"' in proxy_source
assert 'local_page_method_rejection_is_still_marked_non_cacheable' in pages_rust and 'response.extensions_mut().insert(PageServed);' in pages_rust
assert "rel.as_posix() == 'docker-agent/docker-agent'" in (root / 'make_zip.py').read_text()
@@ -921,7 +930,7 @@ assert '<div class="edition">INDEPENDENT BY DESIGN <span>' not in html, "release
assert '<div class="edition">INDEPENDENT BY DESIGN</div>' in html
# tls_verify=false must not feed legacy appliance certificates back through webpki.
upstream_source = rust_source(root, "src/upstream.rs")
assert "BackendCertificateVerificationDisabled { supported_schemes }" in upstream_source
assert "BackendCertificateVerificationDisabled" in upstream_source and "supported_schemes" in upstream_source
disabled_verifier = upstream_source.split("impl ServerCertVerifier for BackendCertificateVerificationDisabled", 1)[1].split("pub fn client(", 1)[0]
assert disabled_verifier.count("Ok(HandshakeSignatureValid::assertion())") == 2
assert "WebPkiServerVerifier" not in disabled_verifier
@@ -1194,7 +1203,7 @@ assert '"error_log" => {' not in admin_0241 and 'request("/api/config/error_log"
assert '"error_log"' in openapi and 'logging, error_log, forwarding' not in openapi
assert 'logging_section_persists_error_log_settings_atomically' in admin_0241 and 'error_log_is_not_a_standalone_configuration_section' in admin_0241
assert 'location.pathname === "/logging"' in settings_js_0241
assert '"RuntimeInformation"' in openapi and '"version": "0.25.6"' in openapi
assert '"RuntimeInformation"' in openapi and '"version": "0.25.7"' in openapi
# 0.24.1 compiler and hostname-cardinality regressions.
tls_0241 = rust_source(root, "src/tls.rs")
@@ -1239,5 +1248,5 @@ for schema_name in ["RedirectHost", "NotFoundHost"]:
node = openapi_0241["components"]["schemas"][schema_name]["properties"]["frontend"]["properties"]["hosts"]
assert "maxItems" not in node, schema_name
print("PASS: 0.25.6 manifest/package, shared system-statistics/runtime metadata, Settings logging, retained H2, Debian/Grafana/Prometheus/404-host/custom-location/path/cache/dashboard/Nagios UI/source contracts, stopped legacy-renewal removal, session/admin/transport contracts, deterministic package source and embedded SQLite schema-v8 migrations.")
print("PASS: 0.25.7 manifest/package, shared system-statistics/runtime metadata, Settings logging, retained H2, Debian/Grafana/Prometheus/404-host/custom-location/path/cache/dashboard/Nagios UI/source contracts, stopped legacy-renewal removal, session/admin/transport contracts, deterministic package source and embedded SQLite schema-v8 migrations.")
print("NOT VERIFIED: Rust parsing, types, compilation, linking or runtime behavior. Run ./dev.sh test.")
+1 -1
View File
@@ -97,7 +97,7 @@ def digest(path: Path) -> str:
def fetch_https(url: str, max_bytes: int) -> bytes:
if urllib.parse.urlparse(url).scheme != 'https':
raise ValueError('GeoIP downloads require HTTPS.')
request = urllib.request.Request(url, headers={'User-Agent': 'RustProxy-installer/0.25.6'})
request = urllib.request.Request(url, headers={'User-Agent': 'RustProxy-installer/0.25.7'})
with urllib.request.urlopen(request, timeout=30) as response:
if urllib.parse.urlparse(response.geturl()).scheme != 'https':
raise ValueError('GeoIP download redirected away from HTTPS.')
+9 -6
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Rust-independent documentation/Reader consistency checks for the 0.25.6 release."""
"""Rust-independent documentation/Reader consistency checks for the 0.25.7 release."""
from pathlib import Path
import re
from rust_source import rust_source
@@ -43,25 +43,28 @@ for marker in ["docs-tab-reader", "docs-reader-list", "docs-reader-content", "do
assert marker in reader_html, marker
for marker in ["renderMarkdown", "selectDocument", 'get("/docs/content.json")', "docsByPath", "docsCategoryHistorical"]:
assert marker in reader_js, marker
documentation_rs = (ROOT / "src/admin/documentation.rs").read_text(encoding="utf-8")
documentation_rs = rust_source(ROOT, "src/admin/documentation.rs") + (ROOT / "src/admin/documentation/documents.rs").read_text(encoding="utf-8")
assert "READER_DOCUMENTS" in documentation_rs and "reader_bundle" in documentation_rs
assert 'include_str!(concat!(env!("CARGO_MANIFEST_DIR")' in documentation_rs
assert 'docs-content.json' not in documentation_rs and 'documentation_bundle' not in build
for document in sorted(DOCS.glob("*.md")):
marker = f'/{document.relative_to(ROOT).as_posix()}")))'
marker = f'/{document.relative_to(ROOT).as_posix()}"'
assert marker in documentation_rs, f"Reader does not embed {document.relative_to(ROOT)}"
assert '"/docs/content.json"' in admin
readme = (ROOT / "README.md").read_text(encoding="utf-8")
roadmap = (ROOT / "ROADMAP.md").read_text(encoding="utf-8")
changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
assert readme.startswith("# RustProxy 0.25.6")
assert readme.startswith("# RustProxy 0.25.7")
assert "docs/WEB_SERVER.md" in readme and "docs/README.md" in readme
assert readme.count("## What changed in ") == 1, "README duplicates historical release notes; use CHANGELOG/archive"
assert "## Release history" in readme and "docs/ROADMAP_HISTORY.md" in readme
assert roadmap.startswith("# RustProxy roadmap") and "## 0.25.6" in roadmap and "## 0.25.5" in roadmap and "## 0.25.4" in roadmap and "## 0.25.3" in roadmap and "## 0.25.2" in roadmap and "## 0.25.1" in roadmap and "## 0.25.0" in roadmap
assert roadmap.startswith("# RustProxy roadmap") and "## 0.25.7" in roadmap and "## 0.25.5" in roadmap and "## 0.25.4" in roadmap and "## 0.25.3" in roadmap and "## 0.25.2" in roadmap and "## 0.25.1" in roadmap and "## 0.25.0" in roadmap
assert "## 0.24." not in roadmap and "## Web Server follow-up" in roadmap and "docs/ROADMAP_HISTORY.md" in roadmap
assert changelog.startswith("# Changelog\n") and "## 0.25.6" in changelog[:1000] and "## 0.25.5" in changelog[:1600] and "## 0.25.4" in changelog[:1800] and "## 0.25.3" in changelog[:3000]
assert changelog.startswith("# Changelog\n")
for release in ["0.25.7", "0.25.6", "0.25.5", "0.25.4", "0.25.3"]:
assert f"## {release}" in changelog, release
assert changelog.index("## 0.25.7") < changelog.index("## 0.25.6") < changelog.index("## 0.25.5")
assert "CHANGELOG_TECHNICAL_ARCHIVE.md" in changelog
for developer_only in ["Clean the remaining compiler warnings", "Update package/source/UI/OpenAPI/release identity", "source regressions and update release identity"]:
assert developer_only not in changelog, f"developer-only CHANGELOG detail: {developer_only}"
+2 -2
View File
@@ -75,10 +75,10 @@ def check(root: Path, repair_known: bool = False) -> list[str]:
raise ValueError(f"Conflicting Rust modules: {flat.relative_to(root)} and {module.relative_to(root)}")
for path in ["src/auth/mod.rs", "src/auth/store.rs", "src/backup/snapshot.rs", "src/upstream.rs", "src/tls.rs", "src/proxy/websocket.rs", "src/cache/mod.rs", "src/cache/model.rs", "src/cache/lifecycle.rs", "src/cache/cold.rs", "src/cache/dashboard.rs", "src/cache/tickets.rs", "src/cache/lookup.rs", "src/cache/policy.rs", "src/cache/fill.rs", "src/cache/tests.rs", "src/settings.rs", "src/compression.rs", "src/diagnostics.rs", "src/traffic.rs", "src/system_stats/mod.rs", "src/system_stats/linux.rs", "src/vhost.rs", "src/web_server/mod.rs", "src/web_server/config.rs", "src/web_server/filesystem.rs", "src/web_server/security.rs", "src/web_server/response.rs", "src/web_server/request.rs", "src/web_server/routing.rs", "src/web_server/fastcgi.rs", "src/web_server/htaccess.rs", "src/web_server/serve.rs", "src/web_server/tests.rs", "src/balance.rs", "src/security.rs", "src/admin/web/vhosts/core.js", "src/admin/web/vhosts/editor.js", "src/admin/web/vhosts/render.js", "src/admin/web/web-server.js", "src/admin/web/streams.js", "src/admin/web/host404.js", "src/admin/web/app/core.js", "src/admin/web/app/config-history.js", "src/admin/web/app/runtime.js", "src/admin/web/app/settings.js", "src/admin/web/management.js", "src/pages.rs", "scripts/mock_server.py", "scripts/mock_auth.py", "src/forwarding.rs", "src/tinyauth.rs", "src/http3.rs", "src/stream.rs", "src/admin/web/integrations.js", "src/admin/web/traffic-panel.js", "src/prometheus.rs", "src/admin/web/settings.js", "src/admin/web/docker.js", "src/docker.rs", "docker-agent/main.go", "docker-agent/model.go", "docker-agent/docker.go", "docker-agent/network.go", "docker-agent/certificate.go", "docker-agent/server.go", "docker-agent/main_test.go", "docker-agent/rustproxy-docker-agent.service", "docker-agent/install.sh", "docs/DOCKER_DISCOVERY.md", "docs/WEB_SERVER.md", "docs/PHP_FPM.md", "docs/README.md", "docs/TODO.md", "docs/V0.25.5_AUDIT.md", "docs/V0.25.4_AUDIT.md", "docs/V0.25.3_AUDIT.md", "docs/V0.25.2_AUDIT.md", "docs/CHANGELOG_TECHNICAL_ARCHIVE.md", "docs/ROADMAP_HISTORY.md", "scripts/docs_check.py", "scripts/web_server_contracts.py", "scripts/rust_source.py", "scripts/rust_module_contracts.py", "scripts/source_module_contracts.py", "scripts/acme_provider.py", "src/basic_auth.rs", "scripts/deploy.py", "scripts/deploy_core.py", "scripts/deploy_runtime.py", "scripts/migrate_npm.py", "scripts/migrate_npm_core.py", "scripts/migrate_npm_api.py", "docs/NPM_MIGRATION.md", "docs/STREAMS.md", "install.sh", "install-debian13.sh", "update.sh", "build.rs", "scripts/swagger_assets.py", "src/admin/documentation.rs", "src/admin/openapi.json", "langs/en_US.json", "langs/pl_pl.json", "Dockerfile", ".dockerignore", "docs/CONTAINER.md", "docs/PROMETHEUS.md", "docs/NOT_FOUND_HOSTS.md", "debian/control", "debian/rules", "debian/rustproxy.service", "debian/rustproxy.logrotate", "grafana/rustproxy-overview.json"]:
if not (root / path).is_file():
raise ValueError(f"Incomplete RustProxy 0.25.6 source tree: missing {path}")
raise ValueError(f"Incomplete RustProxy 0.25.7 source tree: missing {path}")
text = (root / "src/lib.rs").read_text(encoding="utf-8")
if '#[path = "auth/mod.rs"]' not in text:
raise ValueError("src/lib.rs is not the 0.25.6 entry point; extract the complete package, not individual files.")
raise ValueError("src/lib.rs is not the 0.25.7 entry point; extract the complete package, not individual files.")
return messages
+17 -8
View File
@@ -123,7 +123,7 @@ def upgrade_smoke(tmp: Path) -> None:
assert backup.is_dir()
assert (backup / "src/obsolete.rs").is_file()
assert not (target / "src/obsolete.rs").exists()
assert 'version = "0.25.6"' in (target / "Cargo.toml").read_text(encoding="utf-8")
assert 'version = "0.25.7"' in (target / "Cargo.toml").read_text(encoding="utf-8")
assert (target / "data/keep.txt").read_text(encoding="utf-8") == "preserve\n"
assert (target / ".env").read_text(encoding="utf-8") == "RUST_PROXY_DEMO=0\n"
assert (target / "langs/site.json").is_file()
@@ -134,7 +134,7 @@ def upgrade_smoke(tmp: Path) -> None:
assert (target / "docker-agent/certificate.go").is_file()
assert (target / "docker-agent/server.go").is_file()
manifest = json.loads((backup / "upgrade.json").read_text(encoding="utf-8"))
assert manifest["to_version"] == "0.25.6"
assert manifest["to_version"] == "0.25.7"
assert manifest["removed_obsolete_runtime_state"] is True
assert not legacy.exists()
assert not (backup / "legacy-data").exists()
@@ -392,6 +392,15 @@ def ui_contract_smoke() -> None:
assert "vhForceSslNeedsTls" in vhosts and "vhForceSslNeedsHttp" in vhosts and "vhForceSslPortConflict" in vhosts
assert "tlsPorts.has(443)" in vhosts
assert '$("vhost-search").addEventListener("input", render)' in vhosts and "vhSearchEmpty" in vhosts
for marker in ['toggleVhostEnabled', 'vhQuickEnable', 'vhQuickDisable', 'backend_table_columns', 'applyPoolTableView', 'vh-enable-control']:
assert marker in vhosts, marker
assert 'rustproxy-backend-table-v1' not in vhosts and 'poolTablePreferenceKey' not in vhosts
assert 'window.rustProxyPreferences.update({' in vhosts and 'backend_table_sort' in vhosts and 'backend_table_descending' in vhosts
for marker in ['id="pool-search"', 'id="pool-sort"', 'data-pool-column-toggle="vhost"']:
assert marker in html, marker
style = (ROOT / "src/admin/web/style.css").read_text(encoding="utf-8")
for marker in ['--scrollbar-thumb', '*::-webkit-scrollbar-thumb', '.backend-table-tools', '.vhost-quick-toggle']:
assert marker in style, marker
assert "pool-vhost-disabled" in vhosts
for marker in ["locationMatchRegex", "headersVisualBuilder", "headersAdvancedJson", "openLocationHelp", "cloudflare-client-ip", "timing-headers"]:
assert marker in vhosts, marker
@@ -545,7 +554,7 @@ def source_contract_smoke() -> None:
assert marker in admin, marker
anonymous_block = admin.split("fn anonymous_allowed", 1)[1].split("const CERTIFICATE_API_KEY_HEADER", 1)[0]
assert '"/api/status"' not in anonymous_block
assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.25.6"' in openapi_0241
assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.25.7"' in openapi_0241
assert 'id="panel-logging"' not in html
for marker in ['id="settings-tab-logging"', 'id="settings-page-logging"', 'id="logging-form"', 'id="error-log-form"']:
assert marker in html, marker
@@ -885,7 +894,7 @@ def source_contract_smoke() -> None:
assert "RedirectResponse" in proxy and "snapshot.redirect_on" in proxy
assert "#[serde(default)]\n pub tls_verify: bool" in config
upstream = rust_source(ROOT, "src/upstream.rs")
assert "BackendCertificateVerificationDisabled { supported_schemes }" in upstream
assert "BackendCertificateVerificationDisabled" in upstream and "supported_schemes" in upstream
disabled_verifier = upstream.split("impl ServerCertVerifier for BackendCertificateVerificationDisabled", 1)[1].split("pub fn client(", 1)[0]
assert disabled_verifier.count("Ok(HandshakeSignatureValid::assertion())") == 2
assert "WebPkiServerVerifier" not in disabled_verifier
@@ -952,7 +961,7 @@ def source_contract_smoke() -> None:
assert 'function setMobileMenu(open)' in app_source and 'setPanel(button.dataset.panel); setMobileMenu(false);' in app_source
assert 'id="mobile-menu-toggle"' in html and 'id="main-navigation"' in html
assert '.sidebar.mobile-nav-open .main-navigation' in style_source
assert 'id="about-open"' in html and 'id="about-dialog"' in html and 'Mateusz Griszczynski' in html
assert 'id="about-open"' in html and 'id="about-dialog"' in html and 'Mateusz Gruszczynski' in html
assert 'https://git.linuxiarz.pl/gru/RustProxy' in html and 'data-i18n="documentation" id="api-docs"' in html
assert '$("about-open").addEventListener("click"' in app_source and '.about-dialog {' in style_source
assert 'id="config-history-restore"' in html
@@ -1002,7 +1011,7 @@ def source_contract_smoke() -> None:
assert '.managed-update' in main and "flush_hot_to_cold_for_update" in main
openapi = (ROOT / "src/admin/openapi.json").read_text(encoding="utf-8")
assert '"/api/config/history/restore"' in openapi
for marker in ['"version": "0.25.6"', '"memory_preset"', '"cold_preset"', '"memory_budget_bytes"', '"max_inflight"', '"preset"', '"session_days"', '"session_lifetime_seconds"', '"accept_local_listener_risk"', '"backend_header"', '"backend_timing_header"', '"cold_on_age"', '"match_type"', '"regex_insensitive"', '"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
for marker in ['"version": "0.25.7"', '"memory_preset"', '"cold_preset"', '"memory_budget_bytes"', '"max_inflight"', '"preset"', '"session_days"', '"session_lifetime_seconds"', '"accept_local_listener_risk"', '"backend_header"', '"backend_timing_header"', '"cold_on_age"', '"match_type"', '"regex_insensitive"', '"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
assert marker in openapi, marker
assert '"software_update"' in openapi and '"update_available"' in openapi
for marker in ['"/api/certificates/deploy"', '"CertificateDeploy"', '"CertificateDeployResult"', '"/api/certificates/reload"']:
@@ -1104,7 +1113,7 @@ def debian_grafana_contract_smoke() -> None:
assert "rustproxy.linuxiarz.pl" in repo and "dpkg-scanpackages" in repo and "gzip -9 -n" in repo
assert dashboard.get("uid") == "rustproxy-0170"
queries = json.dumps(dashboard)
for marker in ["rust_proxy_process_rss_bytes", "rust_proxy_cache_transient_bytes", "rust_proxy_backend_ewma_latency_seconds", "rust_proxy_certificate_renewal_not_after_timestamp_seconds", "rust_proxy_not_found_hosts_configured", "rust_proxy_http1_requests_total"]:
for marker in ["rust_proxy_process_rss_bytes", "rust_proxy_cache_transient_bytes", "rust_proxy_backend_ewma_latency_seconds", "rust_proxy_certificate_renewal_not_after_timestamp_seconds", "rust_proxy_not_found_hosts_configured", "rust_proxy_http1_requests_total", "rust_proxy_backend_upstream_client_generation_requests", "rust_proxy_backend_upstream_client_generation_age_seconds", "rust_proxy_backend_retry_attempts_total", "rust_proxy_backend_upstream_client_rotations_total", "rust_proxy_docker_backend_upstream_client_generation_requests", "rust_proxy_docker_backend_retry_attempts_total"]:
assert marker in queries, marker
@@ -1177,7 +1186,7 @@ def main() -> None:
source_contract_smoke()
container_contract_smoke()
debian_grafana_contract_smoke()
print("PASS: 0.25.6 SQLite/upgrade, local-response 404 hosts, Prometheus/Grafana diagnostics, Debian packaging, cache/dashboard/logging/Nagios, retained admin/H3/stream/TLS contracts and reproducible source archives.")
print("PASS: 0.25.7 SQLite/upgrade, local-response 404 hosts, Prometheus/Grafana diagnostics, Debian packaging, cache/dashboard/logging/Nagios, retained admin/H3/stream/TLS contracts and reproducible source archives.")
if __name__ == "__main__":
+1 -1
View File
@@ -20,7 +20,7 @@ MAX_LINES = 500
def is_test_source(path: Path) -> bool:
relative = path.relative_to(SRC)
return path.name == "tests.rs" or "tests" in relative.parts
return path.name == "tests.rs" or path.stem.endswith("_tests") or "tests" in relative.parts
include_re = re.compile(r'include!\(\s*"([^"]+\.rs)"\s*\)')
+1 -1
View File
@@ -85,7 +85,7 @@ def download(destination: Path) -> None:
"""TLS verification uses Python's default trust store; never executes a shell."""
started = time.monotonic()
opener = build_opener(HttpsRedirects())
request = Request(DOWNLOAD_URL, headers={"User-Agent": "RustProxy-build/0.25.6"})
request = Request(DOWNLOAD_URL, headers={"User-Agent": "RustProxy-build/0.25.7"})
with opener.open(request, timeout=30) as response, destination.open("wb") as output:
length = response.headers.get("Content-Length")
if length and int(length) > MAX_ARCHIVE:
+3 -3
View File
@@ -46,7 +46,7 @@ def upgrade(source: Path, target: Path, *, stopped: bool, data_dir: Path | None
for name in MANAGED:
if (target / name).is_symlink():
raise ValueError(f"Managed target {name} is a symlink; refusing to replace it.")
backup = target / ".upgrade-backup" / ("before-0.25.6-" + uuid.uuid4().hex[:12])
backup = target / ".upgrade-backup" / ("before-0.25.7-" + uuid.uuid4().hex[:12])
backup.mkdir(parents=True, mode=0o700)
installed: list[str] = []
moved: list[str] = []
@@ -84,7 +84,7 @@ def upgrade(source: Path, target: Path, *, stopped: bool, data_dir: Path | None
remove_legacy = legacy.exists()
if remove_legacy and (legacy.is_symlink() or not legacy.is_file()):
raise ValueError("Obsolete renewal observation sidecar must be a regular file.")
(backup / "upgrade.json").write_text(json.dumps({"from_directory":str(target), "to_version":"0.25.6", "managed_paths":MANAGED, "removed_obsolete_runtime_state": remove_legacy}, indent=2)+"\n", encoding="utf-8")
(backup / "upgrade.json").write_text(json.dumps({"from_directory":str(target), "to_version":"0.25.7", "managed_paths":MANAGED, "removed_obsolete_runtime_state": remove_legacy}, indent=2)+"\n", encoding="utf-8")
if remove_legacy:
legacy.unlink()
except BaseException:
@@ -115,7 +115,7 @@ def main() -> None:
backup = upgrade(Path(__file__).resolve().parents[1], args.target, stopped=args.stopped, data_dir=data_dir)
except (OSError, ValueError) as error:
parser.exit(2, f"Upgrade refused or rolled back: {error}\n")
print(f"Installed RustProxy 0.25.6. Previous managed source files: {backup}")
print(f"Installed RustProxy 0.25.7. Previous managed source files: {backup}")
print("Preserved data, .env, .git, target and other unmanaged files. No database migration was run.")
print(f"Next: cd {args.target}; ./dev.sh test; ./dev.sh")
+1 -1
View File
@@ -96,7 +96,7 @@ need(web,
'HeaderValue::from_static("no-store")',
"builtin_error_response(StatusCode::FORBIDDEN, &method)",
"empty_index_can_go_directly_to_directory_listing",
"RustProxy <b>0.25.6</b>",
"RustProxy <b>0.25.7</b>",
"traversal_and_symlink_escape_are_not_served",
"try_files_rewrites_cache_rules_headers_and_downloads_work",
"precompressed_sidecars_keep_mime_validators_and_range_semantics",
+2 -450
View File
@@ -11,456 +11,8 @@ use std::sync::OnceLock;
// Keep Reader input independent from build-script output. `include_str!` makes every
// document a direct compiler input, so a clean or partially removed target directory
// cannot leave the crate pointing at a missing OUT_DIR artifact.
static READER_DOCUMENTS: &[(&str, &str, &str)] = &[
(
"README.md",
"project",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md")),
),
(
"ROADMAP.md",
"project",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/ROADMAP.md")),
),
(
"CHANGELOG.md",
"project",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/CHANGELOG.md")),
),
(
"docs/ACME_PROVIDERS.md",
"guides",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/ACME_PROVIDERS.md"
)),
),
(
"docs/ACME_RFC2136.md",
"historical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/ACME_RFC2136.md")),
),
(
"docs/API.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/API.md")),
),
(
"docs/API_ACCESS.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/API_ACCESS.md")),
),
(
"docs/ARCHITECTURE.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/ARCHITECTURE.md")),
),
(
"docs/CACHE.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/CACHE.md")),
),
(
"docs/CACHE_DIAGNOSTICS.md",
"technical",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/CACHE_DIAGNOSTICS.md"
)),
),
(
"docs/CHANGELOG_TECHNICAL_ARCHIVE.md",
"historical",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/CHANGELOG_TECHNICAL_ARCHIVE.md"
)),
),
(
"docs/CONTAINER.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/CONTAINER.md")),
),
(
"docs/CUSTOM_LOCATIONS.md",
"guides",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/CUSTOM_LOCATIONS.md"
)),
),
(
"docs/DOCKER_DISCOVERY.md",
"guides",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/DOCKER_DISCOVERY.md"
)),
),
(
"docs/ERROR_LOGGING.md",
"technical",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/ERROR_LOGGING.md"
)),
),
(
"docs/HTTP3.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/HTTP3.md")),
),
(
"docs/INSTALL.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/INSTALL.md")),
),
(
"docs/INTEGRATIONS_040.md",
"historical",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/INTEGRATIONS_040.md"
)),
),
(
"docs/LIVE_LOGS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/LIVE_LOGS.md")),
),
(
"docs/MANAGEMENT.md",
"historical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/MANAGEMENT.md")),
),
(
"docs/MIGRATION.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/MIGRATION.md")),
),
(
"docs/MOCK_AUTH.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/MOCK_AUTH.md")),
),
(
"docs/MOCK_SERVER.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/MOCK_SERVER.md")),
),
(
"docs/NOT_FOUND_HOSTS.md",
"guides",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/NOT_FOUND_HOSTS.md"
)),
),
(
"docs/NPM_MIGRATION.md",
"guides",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/NPM_MIGRATION.md"
)),
),
(
"docs/PHP_FPM.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/PHP_FPM.md")),
),
(
"docs/PROMETHEUS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/PROMETHEUS.md")),
),
(
"docs/README.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/README.md")),
),
(
"docs/REDIRECTS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/REDIRECTS.md")),
),
(
"docs/REFERENCES.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/REFERENCES.md")),
),
(
"docs/RENEWAL.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/RENEWAL.md")),
),
(
"docs/ROADMAP_HISTORY.md",
"historical",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/ROADMAP_HISTORY.md"
)),
),
(
"docs/SECRET_STORE_BACKUP.md",
"guides",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/SECRET_STORE_BACKUP.md"
)),
),
(
"docs/SECURITY.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/SECURITY.md")),
),
(
"docs/SECURITY_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/SECURITY_AUDIT.md"
)),
),
(
"docs/SETTINGS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/SETTINGS.md")),
),
(
"docs/STREAMS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/STREAMS.md")),
),
(
"docs/TEST_REPORT.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/TEST_REPORT.md")),
),
(
"docs/TODO.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/TODO.md")),
),
(
"docs/TRANSFORM.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/TRANSFORM.md")),
),
(
"docs/TRANSPORT.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/TRANSPORT.md")),
),
(
"docs/V0.21.0_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.21.0_AUDIT.md"
)),
),
(
"docs/V0.22.0_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.22.0_AUDIT.md"
)),
),
(
"docs/V0.22.1_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.22.1_AUDIT.md"
)),
),
(
"docs/V0.22.2_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.22.2_AUDIT.md"
)),
),
(
"docs/V0.22.3_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.22.3_AUDIT.md"
)),
),
(
"docs/V0.23.0_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.23.0_AUDIT.md"
)),
),
(
"docs/V0.23.1_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.23.1_AUDIT.md"
)),
),
(
"docs/V0.23.2_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.23.2_AUDIT.md"
)),
),
(
"docs/V0.23.3_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.23.3_AUDIT.md"
)),
),
(
"docs/V0.24.0_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.0_AUDIT.md"
)),
),
(
"docs/V0.24.1_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.1_AUDIT.md"
)),
),
(
"docs/V0.24.3_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.3_AUDIT.md"
)),
),
(
"docs/V0.24.4_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.4_AUDIT.md"
)),
),
(
"docs/V0.24.5_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.5_AUDIT.md"
)),
),
(
"docs/V0.24.6_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.6_AUDIT.md"
)),
),
(
"docs/V0.24.7_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.7_AUDIT.md"
)),
),
(
"docs/V0.24.8_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.8_AUDIT.md"
)),
),
(
"docs/V0.25.0_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.0_AUDIT.md"
)),
),
(
"docs/V0.25.1_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.1_AUDIT.md"
)),
),
(
"docs/V0.25.5_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.5_AUDIT.md"
)),
),
(
"docs/V0.25.4_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.4_AUDIT.md"
)),
),
(
"docs/V0.25.3_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.3_AUDIT.md"
)),
),
(
"docs/V0.25.2_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.2_AUDIT.md"
)),
),
(
"docs/V0.25.0_SECURITY_PREPROD_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.0_SECURITY_PREPROD_AUDIT.md"
)),
),
(
"docs/VHOSTS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/VHOSTS.md")),
),
(
"docs/VHOST_ACCESS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/VHOST_ACCESS.md")),
),
(
"docs/WEB_SERVER.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/WEB_SERVER.md")),
),
];
mod documents;
use documents::READER_DOCUMENTS;
pub(super) async fn index() -> Html<&'static str> {
Html(include_str!("web/docs.html"))
+452
View File
@@ -0,0 +1,452 @@
//! Embedded Markdown documents for the authenticated Reader.
pub(super) static READER_DOCUMENTS: &[(&str, &str, &str)] = &[
(
"README.md",
"project",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md")),
),
(
"ROADMAP.md",
"project",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/ROADMAP.md")),
),
(
"CHANGELOG.md",
"project",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/CHANGELOG.md")),
),
(
"docs/ACME_PROVIDERS.md",
"guides",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/ACME_PROVIDERS.md"
)),
),
(
"docs/ACME_RFC2136.md",
"historical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/ACME_RFC2136.md")),
),
(
"docs/API.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/API.md")),
),
(
"docs/API_ACCESS.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/API_ACCESS.md")),
),
(
"docs/ARCHITECTURE.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/ARCHITECTURE.md")),
),
(
"docs/CACHE.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/CACHE.md")),
),
(
"docs/CACHE_DIAGNOSTICS.md",
"technical",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/CACHE_DIAGNOSTICS.md"
)),
),
(
"docs/CHANGELOG_TECHNICAL_ARCHIVE.md",
"historical",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/CHANGELOG_TECHNICAL_ARCHIVE.md"
)),
),
(
"docs/CONTAINER.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/CONTAINER.md")),
),
(
"docs/CUSTOM_LOCATIONS.md",
"guides",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/CUSTOM_LOCATIONS.md"
)),
),
(
"docs/DOCKER_DISCOVERY.md",
"guides",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/DOCKER_DISCOVERY.md"
)),
),
(
"docs/ERROR_LOGGING.md",
"technical",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/ERROR_LOGGING.md"
)),
),
(
"docs/HTTP3.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/HTTP3.md")),
),
(
"docs/INSTALL.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/INSTALL.md")),
),
(
"docs/INTEGRATIONS_040.md",
"historical",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/INTEGRATIONS_040.md"
)),
),
(
"docs/LIVE_LOGS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/LIVE_LOGS.md")),
),
(
"docs/MANAGEMENT.md",
"historical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/MANAGEMENT.md")),
),
(
"docs/MIGRATION.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/MIGRATION.md")),
),
(
"docs/MOCK_AUTH.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/MOCK_AUTH.md")),
),
(
"docs/MOCK_SERVER.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/MOCK_SERVER.md")),
),
(
"docs/NOT_FOUND_HOSTS.md",
"guides",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/NOT_FOUND_HOSTS.md"
)),
),
(
"docs/NPM_MIGRATION.md",
"guides",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/NPM_MIGRATION.md"
)),
),
(
"docs/PHP_FPM.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/PHP_FPM.md")),
),
(
"docs/PROMETHEUS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/PROMETHEUS.md")),
),
(
"docs/README.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/README.md")),
),
(
"docs/REDIRECTS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/REDIRECTS.md")),
),
(
"docs/REFERENCES.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/REFERENCES.md")),
),
(
"docs/RENEWAL.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/RENEWAL.md")),
),
(
"docs/ROADMAP_HISTORY.md",
"historical",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/ROADMAP_HISTORY.md"
)),
),
(
"docs/SECRET_STORE_BACKUP.md",
"guides",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/SECRET_STORE_BACKUP.md"
)),
),
(
"docs/SECURITY.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/SECURITY.md")),
),
(
"docs/SECURITY_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/SECURITY_AUDIT.md"
)),
),
(
"docs/SETTINGS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/SETTINGS.md")),
),
(
"docs/STREAMS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/STREAMS.md")),
),
(
"docs/TEST_REPORT.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/TEST_REPORT.md")),
),
(
"docs/TODO.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/TODO.md")),
),
(
"docs/TRANSFORM.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/TRANSFORM.md")),
),
(
"docs/TRANSPORT.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/TRANSPORT.md")),
),
(
"docs/V0.21.0_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.21.0_AUDIT.md"
)),
),
(
"docs/V0.22.0_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.22.0_AUDIT.md"
)),
),
(
"docs/V0.22.1_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.22.1_AUDIT.md"
)),
),
(
"docs/V0.22.2_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.22.2_AUDIT.md"
)),
),
(
"docs/V0.22.3_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.22.3_AUDIT.md"
)),
),
(
"docs/V0.23.0_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.23.0_AUDIT.md"
)),
),
(
"docs/V0.23.1_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.23.1_AUDIT.md"
)),
),
(
"docs/V0.23.2_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.23.2_AUDIT.md"
)),
),
(
"docs/V0.23.3_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.23.3_AUDIT.md"
)),
),
(
"docs/V0.24.0_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.0_AUDIT.md"
)),
),
(
"docs/V0.24.1_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.1_AUDIT.md"
)),
),
(
"docs/V0.24.3_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.3_AUDIT.md"
)),
),
(
"docs/V0.24.4_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.4_AUDIT.md"
)),
),
(
"docs/V0.24.5_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.5_AUDIT.md"
)),
),
(
"docs/V0.24.6_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.6_AUDIT.md"
)),
),
(
"docs/V0.24.7_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.7_AUDIT.md"
)),
),
(
"docs/V0.24.8_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.8_AUDIT.md"
)),
),
(
"docs/V0.25.0_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.0_AUDIT.md"
)),
),
(
"docs/V0.25.1_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.1_AUDIT.md"
)),
),
(
"docs/V0.25.5_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.5_AUDIT.md"
)),
),
(
"docs/V0.25.4_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.4_AUDIT.md"
)),
),
(
"docs/V0.25.3_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.3_AUDIT.md"
)),
),
(
"docs/V0.25.2_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.2_AUDIT.md"
)),
),
(
"docs/V0.25.0_SECURITY_PREPROD_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.0_SECURITY_PREPROD_AUDIT.md"
)),
),
(
"docs/VHOSTS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/VHOSTS.md")),
),
(
"docs/VHOST_ACCESS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/VHOST_ACCESS.md")),
),
(
"docs/WEB_SERVER.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/WEB_SERVER.md")),
),
];
+70 -2
View File
@@ -2,7 +2,7 @@
"openapi": "3.0.3",
"info": {
"title": "RustProxy Administration API",
"version": "0.25.6",
"version": "0.25.7",
"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."
},
"servers": [
@@ -2917,6 +2917,74 @@
],
"default": "dark",
"description": "Account-scoped administration interface theme. Existing accounts and omitted values default to the current dark theme."
},
"backend_table_columns": {
"type": "array",
"minItems": 1,
"maxItems": 14,
"uniqueItems": true,
"items": {
"type": "string",
"enum": [
"vhost",
"backend",
"status",
"active",
"requests",
"errors",
"latency",
"traffic",
"generation",
"generationRequests",
"generationAge",
"rotations",
"lastFailure",
"lastRecovery"
]
},
"default": [
"vhost",
"backend",
"status",
"active",
"requests",
"errors",
"latency",
"traffic",
"generation",
"generationRequests",
"generationAge",
"rotations",
"lastFailure",
"lastRecovery"
],
"description": "Account-scoped Backends diagnostics columns shown in the dashboard."
},
"backend_table_sort": {
"type": "string",
"enum": [
"vhost",
"backend",
"status",
"active",
"requests",
"errors",
"latency",
"traffic",
"generation",
"generationRequests",
"generationAge",
"rotations",
"lastFailure",
"lastRecovery"
],
"default": "vhost",
"description": "Account-scoped sort key for the Backends diagnostics table."
},
"backend_table_descending": {
"type": "boolean",
"default": false,
"description": "Account-scoped sort direction for the Backends diagnostics table."
}
},
"required": [
@@ -5093,7 +5161,7 @@
}
}
},
"description": "Sets an HttpOnly SameSite=Strict session cookie. Browser sessions use the configured 7\u201330 day absolute lifetime. Use the panel sign-in page. No bearer token or URL token is accepted.",
"description": "Sets an HttpOnly SameSite=Strict session cookie. Browser sessions use the configured 7–30 day absolute lifetime. Use the panel sign-in page. No bearer token or URL token is accepted.",
"security": [],
"parameters": [
{
+57
View File
@@ -267,10 +267,35 @@ const LIVE_LOG_COLUMNS: &[&str] = &[
"websocket",
"upstream_error",
];
const BACKEND_TABLE_COLUMNS: &[&str] = &[
"vhost",
"backend",
"status",
"active",
"requests",
"errors",
"latency",
"traffic",
"generation",
"generationRequests",
"generationAge",
"rotations",
"lastFailure",
"lastRecovery",
];
fn default_admin_theme() -> String {
"dark".to_owned()
}
fn default_backend_table_columns() -> Vec<String> {
BACKEND_TABLE_COLUMNS
.iter()
.map(|value| (*value).to_owned())
.collect()
}
fn default_backend_table_sort() -> String {
"vhost".to_owned()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
@@ -278,6 +303,12 @@ struct AdminPreferences {
live_log_columns: Vec<String>,
#[serde(default = "default_admin_theme")]
theme: String,
#[serde(default = "default_backend_table_columns")]
backend_table_columns: Vec<String>,
#[serde(default = "default_backend_table_sort")]
backend_table_sort: String,
#[serde(default)]
backend_table_descending: bool,
}
impl Default for AdminPreferences {
@@ -288,6 +319,9 @@ impl Default for AdminPreferences {
.map(|value| (*value).to_owned())
.collect(),
theme: default_admin_theme(),
backend_table_columns: default_backend_table_columns(),
backend_table_sort: default_backend_table_sort(),
backend_table_descending: false,
}
}
}
@@ -316,6 +350,29 @@ fn validate_admin_preferences(preferences: &AdminPreferences) -> Result<(), ApiE
"invalid administration theme preference".into(),
));
}
if preferences.backend_table_columns.is_empty()
|| preferences.backend_table_columns.len() > BACKEND_TABLE_COLUMNS.len()
{
return Err(ApiError(
StatusCode::UNPROCESSABLE_ENTITY,
"at least one backend table column must be visible".into(),
));
}
let mut seen = std::collections::HashSet::new();
for column in &preferences.backend_table_columns {
if !BACKEND_TABLE_COLUMNS.contains(&column.as_str()) || !seen.insert(column.as_str()) {
return Err(ApiError(
StatusCode::UNPROCESSABLE_ENTITY,
"invalid backend table column preference".into(),
));
}
}
if !BACKEND_TABLE_COLUMNS.contains(&preferences.backend_table_sort.as_str()) {
return Err(ApiError(
StatusCode::UNPROCESSABLE_ENTITY,
"invalid backend table sort preference".into(),
));
}
Ok(())
}
+35 -1
View File
@@ -20,6 +20,12 @@
LIVE_LOG_COLUMNS.len()
);
assert_eq!(initial["theme"], "dark");
assert_eq!(
initial["backend_table_columns"].as_array().unwrap().len(),
BACKEND_TABLE_COLUMNS.len()
);
assert_eq!(initial["backend_table_sort"], "vhost");
assert_eq!(initial["backend_table_descending"], false);
assert!(store
.save_admin_preferences("owner", r#"{"live_log_columns":["time","status"]}"#)
@@ -39,10 +45,19 @@
let legacy = json_body(response).await;
assert_eq!(legacy["live_log_columns"], json!(["time", "status"]));
assert_eq!(legacy["theme"], "dark");
assert_eq!(
legacy["backend_table_columns"].as_array().unwrap().len(),
BACKEND_TABLE_COLUMNS.len()
);
assert_eq!(legacy["backend_table_sort"], "vhost");
assert_eq!(legacy["backend_table_descending"], false);
let selected = json!({
"live_log_columns":["time","request","status","upstream_error"],
"theme":"light"
"theme":"light",
"backend_table_columns":["vhost","backend","status","requests","latency"],
"backend_table_sort":"requests",
"backend_table_descending":true
});
let response = router
.clone()
@@ -101,6 +116,7 @@
assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);
let response = router
.clone()
.oneshot(
request("/api/preferences")
.method("PUT")
@@ -117,6 +133,24 @@
.await
.unwrap();
assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);
let response = router
.oneshot(
request("/api/preferences")
.method("PUT")
.header("cookie", &cookie)
.header("origin", "http://127.0.0.1:9090")
.header("x-rustproxy-request", "1")
.header("x-csrf-token", &csrf)
.header("content-type", "application/json")
.body(Body::from(
r#"{"live_log_columns":["time"],"backend_table_columns":["unknown"]}"#,
))
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);
store.close().await;
}
+1 -1
View File
@@ -353,7 +353,7 @@ function lock(notify = true) {
if (navigating) { document.documentElement.classList.add("app-loading"); location.replace(destination); }
}
async function activate(info) { pendingAuthDestination = null; authMessage(""); $("api-docs").hidden = false; $("logs-open").hidden = false; authEpoch++; sessionInfo = info; csrf = info.csrf_token; authenticated = true; const preferences = await loadAdminPreferences(); window.rustProxyLogs?.applyPreferences(preferences); await loadConfig(); $("password").value = ""; $("password-confirm").value = ""; $("setup-token").value = ""; $("login").hidden = true; $("workspace").hidden = false; $("logout").hidden = false; renderSession(); await refresh(true); startLive(); document.documentElement.classList.remove("app-loading"); }
async function activate(info) { pendingAuthDestination = null; authMessage(""); $("api-docs").hidden = false; $("logs-open").hidden = false; authEpoch++; sessionInfo = info; csrf = info.csrf_token; authenticated = true; const preferences = await loadAdminPreferences(); window.rustProxyLogs?.applyPreferences(preferences); window.rustProxyVhosts?.applyPreferences(preferences); await loadConfig(); $("password").value = ""; $("password-confirm").value = ""; $("setup-token").value = ""; $("login").hidden = true; $("workspace").hidden = false; $("logout").hidden = false; renderSession(); await refresh(true); startLive(); document.documentElement.classList.remove("app-loading"); }
async function loadAudit() { if (!authenticated) return; const epoch = authEpoch; try { const data = await api("/api/audit"); if (!authenticated || epoch !== authEpoch) return; const rows = data.entries.map(item => { const tr = el("tr"); for (const value of [new Date(item.time * 1000).toLocaleString(locale()), item.actor, item.action, item.detail]) { const td = el("td", value); td.title = value; tr.append(td); } return tr; }); $("audit-rows").replaceChildren(...rows); $("empty-audit").hidden = rows.length > 0; } catch (e) { toast(e.message, true); } }
let tunnelRefreshTimer = null;
function tunnelMessage(value = "", error = false) { const node = $("tunnels-status"); if (!node) return; node.textContent = value; node.classList.toggle("error", !!error); }
+1 -1
View File
@@ -6,7 +6,7 @@ function renderSettings() {
if (!settingsDirty.has("response-form")) {
const server = config.server_header || {}, compression = config.compression || {}, diagnostics = config.diagnostic_headers || {};
$("expose-request-id").checked = !!diagnostics.request_id;
$("server-mode").value = server.mode || "preserve"; $("server-value").value = server.value || "RustProxy/v0.25.6";
$("server-mode").value = server.mode || "preserve"; $("server-value").value = server.value || "RustProxy/v0.25.7";
for (const name of ["gzip", "br", "zstd", "deflate"]) $(`compress-${name}`).checked = !!compression[name];
$("compress-min").value = compression.min_bytes ?? 1024; syncServerMode();
}
+89 -20
View File
@@ -523,9 +523,75 @@
</article>
</div>
<article class="card pool-card" id="backend-diagnostics-card">
<div class="card-heading">
<h2 data-i18n="vhHealthTitle">Backends</h2><button class="quiet" data-i18n="vhManage"
id="pool-open" type="button">Manage vhosts</button>
<div class="card-heading backend-card-heading">
<h2 data-i18n="vhHealthTitle">Backends</h2>
<div class="backend-table-tools">
<label class="sr-only" data-i18n="backendSearch" for="pool-search">Search
backends</label>
<input autocomplete="off" data-i18n-aria="backendSearch"
data-i18n-placeholder="backendSearchPlaceholder" id="pool-search" type="search" />
<label class="backend-sort-control"><span data-i18n="backendSortBy">Sort
by</span><select data-i18n-aria="backendSortBy" id="pool-sort">
<option data-i18n="vhVhost" value="vhost">Vhost</option>
<option data-i18n="backend" value="backend">Backend</option>
<option data-i18n="status" value="status">Status</option>
<option data-i18n="vhActive" value="active">Active</option>
<option data-i18n="vhRequests" value="requests">Requests</option>
<option data-i18n="vhErrors" value="errors">Errors</option>
<option data-i18n="vhLatency" value="latency">Latency</option>
<option data-i18n="vhTraffic" value="traffic">Traffic/s</option>
<option data-i18n="vhClientGeneration" value="generation">Client gen.</option>
<option data-i18n="vhGenerationRequests" value="generationRequests">Gen.
requests</option>
<option data-i18n="vhGenerationAge" value="generationAge">Gen. age</option>
<option data-i18n="vhClientRotations" value="rotations">Rotations</option>
<option data-i18n="vhLastFailure" value="lastFailure">Last failure</option>
<option data-i18n="vhLastRecovery" value="lastRecovery">Last recovery</option>
</select></label>
<button class="quiet backend-sort-direction" data-i18n-aria="backendSortAscending"
id="pool-sort-direction" type="button">A-Z</button>
<details class="backend-column-picker">
<summary class="quiet" data-i18n="backendColumns">Columns</summary>
<div class="backend-column-options">
<label class="check"><input checked data-pool-column-toggle="vhost"
type="checkbox" /><span data-i18n="vhVhost">Vhost</span></label>
<label class="check"><input checked data-pool-column-toggle="backend"
type="checkbox" /><span data-i18n="backend">Backend</span></label>
<label class="check"><input checked data-pool-column-toggle="status"
type="checkbox" /><span data-i18n="status">Status</span></label>
<label class="check"><input checked data-pool-column-toggle="active"
type="checkbox" /><span data-i18n="vhActive">Active</span></label>
<label class="check"><input checked data-pool-column-toggle="requests"
type="checkbox" /><span data-i18n="vhRequests">Requests</span></label>
<label class="check"><input checked data-pool-column-toggle="errors"
type="checkbox" /><span data-i18n="vhErrors">Errors</span></label>
<label class="check"><input checked data-pool-column-toggle="latency"
type="checkbox" /><span data-i18n="vhLatency">Latency</span></label>
<label class="check"><input checked data-pool-column-toggle="traffic"
type="checkbox" /><span data-i18n="vhTraffic">Traffic/s</span></label>
<label class="check"><input checked data-pool-column-toggle="generation"
type="checkbox" /><span data-i18n="vhClientGeneration">Client
gen.</span></label>
<label class="check"><input checked data-pool-column-toggle="generationRequests"
type="checkbox" /><span data-i18n="vhGenerationRequests">Gen.
requests</span></label>
<label class="check"><input checked data-pool-column-toggle="generationAge"
type="checkbox" /><span data-i18n="vhGenerationAge">Gen.
age</span></label>
<label class="check"><input checked data-pool-column-toggle="rotations"
type="checkbox" /><span
data-i18n="vhClientRotations">Rotations</span></label>
<label class="check"><input checked data-pool-column-toggle="lastFailure"
type="checkbox" /><span data-i18n="vhLastFailure">Last
failure</span></label>
<label class="check"><input checked data-pool-column-toggle="lastRecovery"
type="checkbox" /><span data-i18n="vhLastRecovery">Last
recovery</span></label>
</div>
</details>
<button class="quiet" data-i18n="vhManage" id="pool-open" type="button">Manage
vhosts</button>
</div>
</div>
<p class="fine" data-i18n="vhHealthHint">Live state: leases cover the entire exchange; traffic
is an EWMA of payload bytes.</p>
@@ -533,20 +599,23 @@
<table class="pool-table">
<thead>
<tr>
<th data-i18n="vhVhost">Vhost</th>
<th data-i18n="backend">Backend</th>
<th data-i18n="status">Status</th>
<th data-i18n="vhActive">Active</th>
<th data-i18n="vhRequests">Requests</th>
<th data-i18n="vhErrors">Errors</th>
<th data-i18n="vhLatency">Latency</th>
<th data-i18n="vhTraffic">Traffic/s</th>
<th data-i18n="vhClientGeneration">Client gen.</th>
<th data-i18n="vhGenerationRequests">Gen. requests</th>
<th data-i18n="vhGenerationAge">Gen. age</th>
<th data-i18n="vhClientRotations">Rotations</th>
<th data-i18n="vhLastFailure">Last failure</th>
<th data-i18n="vhLastRecovery">Last recovery</th>
<th data-i18n="vhVhost" data-pool-column="vhost">Vhost</th>
<th data-i18n="backend" data-pool-column="backend">Backend</th>
<th data-i18n="status" data-pool-column="status">Status</th>
<th data-i18n="vhActive" data-pool-column="active">Active</th>
<th data-i18n="vhRequests" data-pool-column="requests">Requests</th>
<th data-i18n="vhErrors" data-pool-column="errors">Errors</th>
<th data-i18n="vhLatency" data-pool-column="latency">Latency</th>
<th data-i18n="vhTraffic" data-pool-column="traffic">Traffic/s</th>
<th data-i18n="vhClientGeneration" data-pool-column="generation">Client gen.
</th>
<th data-i18n="vhGenerationRequests" data-pool-column="generationRequests">Gen.
requests</th>
<th data-i18n="vhGenerationAge" data-pool-column="generationAge">Gen. age</th>
<th data-i18n="vhClientRotations" data-pool-column="rotations">Rotations</th>
<th data-i18n="vhLastFailure" data-pool-column="lastFailure">Last failure</th>
<th data-i18n="vhLastRecovery" data-pool-column="lastRecovery">Last recovery
</th>
</tr>
</thead>
<tbody id="pool-rows"></tbody>
@@ -1598,7 +1667,7 @@
<option data-i18n="serverCustom" value="custom">Replace / add custom value</option>
</select>
<label data-i18n="serverValue" for="server-value">Custom Server value</label><input
id="server-value" maxlength="256" placeholder="RustProxy/v0.25.6"
id="server-value" maxlength="256" placeholder="RustProxy/v0.25.7"
spellcheck="false" />
<h3 class="subheading" data-i18n="diagnosticHeaders">Diagnostic headers</h3>
<div class="switch-grid">
@@ -2386,7 +2455,7 @@
</section>
</div>
</section>
<footer class="page-footer"><span>RustProxy <b>0.25.6</b></span><span id="footer-status">API - /
<footer class="page-footer"><span>RustProxy <b>0.25.7</b></span><span id="footer-status">API - /
dropped: 0</span></footer>
</div>
</main>
@@ -3097,7 +3166,7 @@ max_execution_time = 30</code></pre>
<article class="card about-card">
<div class="about-product">
<span aria-hidden="true" class="brand-mark">r<span>/</span>p</span>
<div><span class="small-label">RustProxy</span><strong>0.25.6</strong></div>
<div><span class="small-label">RustProxy</span><strong>0.25.7</strong></div>
</div>
<dl class="about-meta">
<div>
+196 -2
View File
@@ -50,6 +50,9 @@
--shadow-mid: #0006;
--shadow-strong: #0009;
--neutral-soft: rgba(127, 127, 127, .05);
--scrollbar-track: #151a16;
--scrollbar-thumb: #4f5d50;
--scrollbar-thumb-hover: #6b7b69;
--mono: ui-monospace, SFMono-Regular, Consolas, monospace;
--sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif
}
@@ -97,11 +100,39 @@ html[data-theme="light"] {
--transform-chart: #7d4fa3;
--shadow-mid: rgb(31 39 33 / .16);
--shadow-strong: rgb(31 39 33 / .26);
--neutral-soft: rgba(70, 78, 72, .05)
--neutral-soft: rgba(70, 78, 72, .05);
--scrollbar-track: #e7ece4;
--scrollbar-thumb: #a9b5a6;
--scrollbar-thumb-hover: #879684
}
* {
box-sizing: border-box
box-sizing: border-box;
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
scrollbar-width: thin
}
*::-webkit-scrollbar {
width: 10px;
height: 10px
}
*::-webkit-scrollbar-track {
background: var(--scrollbar-track)
}
*::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb);
border: 2px solid var(--scrollbar-track);
border-radius: 999px
}
*::-webkit-scrollbar-thumb:hover {
background: var(--scrollbar-thumb-hover)
}
*::-webkit-scrollbar-corner {
background: var(--scrollbar-track)
}
html.app-loading {
@@ -2463,6 +2494,63 @@ td {
flex: 0 0 auto
}
.vh-enable-control {
display: grid !important;
grid-template-columns: auto minmax(0, 1fr) auto;
grid-template-rows: auto auto;
align-items: center;
gap: 2px 10px;
margin-top: 5px !important;
padding: 11px 12px;
border: 1px solid var(--pill-live-line);
background: var(--panel2);
box-shadow: inset 3px 0 0 var(--green)
}
.vh-enable-control>input {
grid-column: 1;
grid-row: 1/3;
width: 18px;
height: 18px;
accent-color: var(--green)
}
.vh-enable-control>span {
grid-column: 2;
grid-row: 1;
color: var(--text);
font-size: 12px;
font-weight: 700
}
.vh-enable-hint {
grid-column: 2;
grid-row: 2;
color: var(--muted);
font-size: 10px
}
.vh-enable-state {
grid-column: 3;
grid-row: 1/3;
padding: 3px 7px;
border: 1px solid var(--pill-live-line);
color: var(--green);
font: 600 10px var(--mono);
text-transform: uppercase;
letter-spacing: .04em
}
.vh-enable-control.is-disabled {
border-color: var(--danger-line-strong);
box-shadow: inset 3px 0 0 var(--red)
}
.vh-enable-control.is-disabled .vh-enable-state {
border-color: var(--danger-line);
color: var(--red)
}
.vh-wide {
grid-column: 1/-1;
min-width: 0
@@ -2524,6 +2612,78 @@ td {
margin-bottom: 12px
}
.backend-card-heading {
align-items: flex-start;
gap: 10px
}
.backend-table-tools {
display: flex;
align-items: center;
justify-content: flex-end;
flex-wrap: wrap;
gap: 7px;
margin-left: auto
}
#pool-search {
width: min(250px, 30vw);
min-width: 170px
}
.backend-sort-control {
display: flex;
align-items: center;
gap: 6px;
color: var(--muted);
font-size: 11px
}
.backend-sort-control select {
width: auto;
min-width: 120px
}
.backend-sort-direction {
min-width: 48px
}
.backend-column-picker {
position: relative
}
.backend-column-picker>summary {
list-style: none;
cursor: pointer;
user-select: none
}
.backend-column-picker>summary::-webkit-details-marker {
display: none
}
.backend-column-options {
position: absolute;
z-index: 20;
top: calc(100% + 6px);
right: 0;
display: grid;
grid-template-columns: repeat(2, minmax(145px, 1fr));
gap: 1px 12px;
min-width: 330px;
max-height: 330px;
overflow: auto;
padding: 10px 12px 12px;
border: 1px solid var(--line);
background: var(--panel);
box-shadow: 0 12px 28px var(--shadow-strong)
}
.backend-column-options .check {
margin-top: 4px !important;
font-size: 11px
}
.pool-card .fine {
margin: 0 0 8px
}
@@ -4861,6 +5021,16 @@ dialog .window-heading {
margin-top: 8px
}
.vhost-quick-toggle.is-enable {
border-color: var(--pill-live-line);
color: var(--green)
}
.vhost-quick-toggle.is-disable {
border-color: var(--danger-line);
color: var(--red)
}
@media(max-width:980px) {
.logging-grid {
grid-template-columns: 1fr
@@ -4896,6 +5066,30 @@ dialog .window-heading {
min-width: 0
}
.backend-card-heading {
align-items: stretch;
flex-direction: column
}
.backend-table-tools {
width: 100%;
justify-content: flex-start;
margin-left: 0
}
#pool-search {
flex: 1 1 100%;
width: 100%;
min-width: 0
}
.backend-column-options {
right: auto;
left: 0;
grid-template-columns: 1fr;
min-width: min(290px, 82vw)
}
.vhost-card-head .entity-actions {
width: 100%;
justify-content: flex-start
+5 -1
View File
@@ -38,7 +38,11 @@
editing = value.id || null; revision = envelope?.revision ?? null; dirty = false; initialized = true;
container.replaceChildren(); $("vhost-editing").textContent = t(editing ? "vhEditLabel" : "vhNewLabel");
let g = group("vhEditor"); field(g, "id", "vhId", value.id, "text", { required: true, maxLength: 64, pattern: "[A-Za-z0-9_-]{1,64}" }); if (editing) note(g, "vhIdRenameHelp");
readHosts = hostTagEditor(g, value.frontend?.hosts || []); field(g, "enabled", "vhEnabled", value.enabled !== false, "checkbox");
readHosts = hostTagEditor(g, value.frontend?.hosts || []);
const enabledControl = field(g, "enabled", "vhEnabled", value.enabled !== false, "checkbox"), enabledLabel = enabledControl.closest("label"), enabledState = el("strong", "", "vh-enable-state");
if (enabledLabel) { enabledLabel.classList.add("vh-enable-control", "vh-wide"); enabledLabel.append(text("small", "vhEnableHint", "vh-enable-hint"), enabledState); }
const syncEnabledState = () => { const key = enabledControl.checked ? "vhEnabled" : "vhDisabled"; enabledLabel?.classList.toggle("is-disabled", !enabledControl.checked); enabledLabel?.classList.toggle("is-enabled", enabledControl.checked); enabledState.dataset.i18n = key; enabledState.textContent = t(key); };
enabledControl.addEventListener("change", syncEnabledState); syncEnabledState();
const mainSource = field(g, "source-type", "sourceType", value.source?.type || "reverse_proxy", "select", { values: [["reverse_proxy", "sourceReverseProxy"], ["web_server", "sourceWebServer"]] });
const mainWebService = field(g, "source-web-service", "webService", value.source?.web_service_id || "", "select"); fillWebServices(mainWebService, value.source?.web_service_id || "");
const syncMainSource = () => { const web = mainSource.value === "web_server"; mainWebService.disabled = !web; mainWebService.required = web; }; mainSource.addEventListener("change", syncMainSource); syncMainSource();
+93 -4
View File
@@ -1,3 +1,87 @@
const poolColumns = ["vhost", "backend", "status", "active", "requests", "errors", "latency", "traffic", "generation", "generationRequests", "generationAge", "rotations", "lastFailure", "lastRecovery"];
const defaultPoolTablePrefs = () => ({ sort: "vhost", descending: false, columns: [...poolColumns] });
let poolTablePrefs = defaultPoolTablePrefs(), poolPreferenceSaveRunning = false, poolPreferenceSaveQueued = false;
function normalizePoolTablePrefs(preferences) {
const selected = Array.isArray(preferences?.backend_table_columns) ? preferences.backend_table_columns.filter(key => poolColumns.includes(key)) : [];
return {
sort: poolColumns.includes(preferences?.backend_table_sort) ? preferences.backend_table_sort : "vhost",
descending: preferences?.backend_table_descending === true,
columns: selected.length ? selected : [...poolColumns],
};
}
function applyPoolPreferences(preferences) {
poolTablePrefs = normalizePoolTablePrefs(preferences);
const sort = $("pool-sort"); if (sort) sort.value = poolTablePrefs.sort;
syncPoolColumns(); syncPoolSortDirection(); applyPoolTableView();
}
async function savePoolTablePrefs() {
if (!authenticated) return;
poolPreferenceSaveQueued = true;
if (poolPreferenceSaveRunning) return;
poolPreferenceSaveRunning = true;
try {
while (poolPreferenceSaveQueued && authenticated) {
poolPreferenceSaveQueued = false;
const saved = await window.rustProxyPreferences.update({
backend_table_columns: poolColumns.filter(key => poolTablePrefs.columns.includes(key)),
backend_table_sort: poolTablePrefs.sort,
backend_table_descending: poolTablePrefs.descending,
});
if (!authenticated) return;
if (!poolPreferenceSaveQueued) poolTablePrefs = normalizePoolTablePrefs(saved);
}
syncPoolColumns(); syncPoolSortDirection();
} catch (error) { if (error.status !== 401) toast(error.message, true); }
finally { poolPreferenceSaveRunning = false; }
}
function syncPoolColumns() {
const visible = new Set(poolTablePrefs.columns);
document.querySelectorAll("[data-pool-column]").forEach(node => { node.hidden = !visible.has(node.dataset.poolColumn); });
document.querySelectorAll("[data-pool-column-toggle]").forEach(input => { input.checked = visible.has(input.dataset.poolColumnToggle); });
}
function syncPoolSortDirection() {
const control = $("pool-sort-direction"); if (!control) return;
control.textContent = poolTablePrefs.descending ? "Z-A" : "A-Z";
control.setAttribute("aria-label", t(poolTablePrefs.descending ? "backendSortDescending" : "backendSortAscending"));
}
function applyPoolTableView() {
const body = $("pool-rows"); if (!body) return;
const query = ($("pool-search")?.value || "").trim().toLowerCase();
const key = $("pool-sort")?.value || poolTablePrefs.sort;
const rows = [...body.querySelectorAll("tr")];
rows.sort((left, right) => {
const a = left.poolSort?.[key] ?? "", b = right.poolSort?.[key] ?? "";
const result = typeof a === "number" && typeof b === "number" ? a - b : String(a).localeCompare(String(b), locale(), { numeric: true, sensitivity: "base" });
return poolTablePrefs.descending ? -result : result;
});
body.replaceChildren(...rows);
let visible = 0;
for (const row of rows) { const match = !query || (row.poolSearchValue || "").includes(query); row.hidden = !match; if (match) visible++; }
syncPoolColumns(); syncPoolSortDirection();
const empty = $("pool-empty");
if (empty) { empty.textContent = query && rows.length && !visible ? t("backendSearchEmpty") : t("vhHealthEmpty"); empty.hidden = visible > 0 || (!rows.length && !document.querySelector("#backend-diagnostics-card:not([hidden])")); }
}
function initPoolTableTools() {
const sort = $("pool-sort"), direction = $("pool-sort-direction"), search = $("pool-search");
if (sort) { sort.value = poolTablePrefs.sort; sort.addEventListener("change", () => { poolTablePrefs.sort = sort.value; void savePoolTablePrefs(); applyPoolTableView(); }); }
direction?.addEventListener("click", () => { poolTablePrefs.descending = !poolTablePrefs.descending; void savePoolTablePrefs(); applyPoolTableView(); });
search?.addEventListener("input", applyPoolTableView);
document.querySelectorAll("[data-pool-column-toggle]").forEach(input => input.addEventListener("change", () => {
const visible = new Set(poolTablePrefs.columns);
if (input.checked) visible.add(input.dataset.poolColumnToggle); else visible.delete(input.dataset.poolColumnToggle);
if (!visible.size) { input.checked = true; toast(t("backendColumnsRequired"), true); return; }
poolTablePrefs.columns = poolColumns.filter(key => visible.has(key)); void savePoolTablePrefs(); syncPoolColumns();
}));
syncPoolColumns(); syncPoolSortDirection();
}
async function toggleVhostEnabled(vhost) {
if (!envelope || busy) return;
const next = structuredClone(envelope.config), target = (next.vhosts || []).find(item => item.id === vhost.id);
if (!target) return;
target.enabled = vhost.enabled === false;
if (await saveSection("vhosts", next.vhosts || [])) render();
}
function render() {
if (!envelope) return;
if (!initialized) load(defaults(), [], false);
@@ -33,7 +117,9 @@
const browseTls = configuredEndpoints.some(endpoint => endpoint.binding?.tls === true);
const openButton = button("vhOpenBrowser", () => { if (browseHost) window.open(`${browseTls ? "https" : "http"}://${browseHost}/`, "_blank", "noopener,noreferrer"); });
openButton.disabled = !browseHost || v.enabled === false;
actions.append(openButton, button("edit", async () => { if (await allowDiscard()) load(structuredClone(v)); }), button("remove", async () => {
const quickToggle = button(v.enabled === false ? "vhQuickEnable" : "vhQuickDisable", () => toggleVhostEnabled(v), `quiet vhost-quick-toggle ${v.enabled === false ? "is-enable" : "is-disable"}`);
quickToggle.setAttribute("aria-pressed", String(v.enabled !== false));
actions.append(quickToggle, openButton, button("edit", async () => { if (await allowDiscard()) load(structuredClone(v)); }), button("remove", async () => {
if (busy || !(await appConfirm(t("vhDelete"), { danger: true }))) return;
const next = structuredClone(envelope.config); next.vhosts = (next.vhosts || []).filter(item => item.id !== v.id);
if (await saveSection("vhosts", next.vhosts || [])) { if (editing === v.id) load(); render(); }
@@ -147,7 +233,9 @@
const generationAgeText = generationAgeRaw == null || !Number.isFinite(generationAge) ? "-" : generationAge >= 3600 ? `${Math.floor(generationAge / 3600)}h ${Math.floor((generationAge % 3600) / 60)}m` : generationAge >= 60 ? `${Math.floor(generationAge / 60)}m ${Math.floor(generationAge % 60)}s` : `${Math.floor(generationAge)}s`;
const row = el("tr"); if (vhostDisabled) row.classList.add("pool-vhost-disabled"); const poolDomain = status.backend_pool_domains?.[pool.id]; const poolLabel = poolDomain ? `${pool.id} (${poolDomain})` : pool.id; const cells = [poolLabel, `${member.id}${member.backup ? " / " + t("vhBackup") : ""}`, label, activeLimit, member.requests, member.errors,
member.ewma_latency_ms > 0 ? `${member.ewma_latency_ms.toFixed(1)} ms` : "-", `${rateBytes(memberTraffic)}/s`, member.upstream_client_generation ?? "-", member.upstream_client_generation_requests ?? "-", generationAgeText, member.upstream_client_rotations_total ?? "-", lastFailure, lastRecovery];
cells.forEach((value, index) => { const cell = el("td", String(value ?? "-")); cell.title = index === 2 ? (vhostDisabled ? t("vhDisabled") : `${label} · ${healthDetail}`) : index === 12 ? (member.last_failure_detail || lastFailure) : String(value ?? "-"); if (index === 2 && !vhostDisabled) cell.className = member.status === "down" ? "status-error" : member.status === "up" ? "status-good" : ""; if (index === 12 && member.last_failure_at_ms) cell.classList.add("backend-last-failure"); row.append(cell); }); rows.push(row);
const sortValues = { vhost: poolLabel.toLowerCase(), backend: String(member.id || "").toLowerCase(), status: label.toLowerCase(), active: Number(member.active) || 0, requests: Number(member.requests) || 0, errors: Number(member.errors) || 0, latency: Number(member.ewma_latency_ms) || 0, traffic: memberTraffic, generation: Number(member.upstream_client_generation) || 0, generationRequests: Number(member.upstream_client_generation_requests) || 0, generationAge: Number.isFinite(generationAge) ? generationAge : -1, rotations: Number(member.upstream_client_rotations_total) || 0, lastFailure: Number(member.last_failure_at_ms) || 0, lastRecovery: Number(member.last_recovered_ms) || 0 };
row.poolSort = sortValues; row.poolSearchValue = [poolLabel, member.id, label, member.status, member.last_failure_class, member.last_failure_detail].filter(Boolean).join(" ").toLowerCase();
cells.forEach((value, index) => { const cell = el("td", String(value ?? "-")); cell.dataset.poolColumn = poolColumns[index]; cell.title = index === 2 ? (vhostDisabled ? t("vhDisabled") : `${label} · ${healthDetail}`) : index === 12 ? (member.last_failure_detail || lastFailure) : String(value ?? "-"); if (index === 2 && !vhostDisabled) cell.className = member.status === "down" ? "status-error" : member.status === "up" ? "status-good" : ""; if (index === 12 && member.last_failure_at_ms) cell.classList.add("backend-last-failure"); row.append(cell); }); rows.push(row);
if (!vhostDisabled && member.status === "up") up++; active += Number(member.active) || 0; requests += Number(member.requests) || 0; errors += Number(member.errors) || 0; traffic += memberTraffic;
const latency = Number(member.ewma_latency_ms || member.latency_ms) || 0; if (latency > 0) { const weight = Math.max(1, Number(member.requests) || 1); weightedLatency += latency * weight; latencyWeight += weight; }
}
@@ -190,9 +278,9 @@
vhost.source?.type !== "web_server" || (vhost.routes || []).some(route => !route.response && (route.source?.type || "reverse_proxy") === "reverse_proxy")
);
$("pool-rows").replaceChildren(...rows);
$("pool-empty").hidden = rows.length > 0 || !hasConfiguredProxySource;
const backendDiagnosticsCard = $("backend-diagnostics-card");
if (backendDiagnosticsCard) backendDiagnosticsCard.hidden = !hasConfiguredProxySource;
applyPoolTableView();
document.querySelectorAll("[data-health-for]").forEach(node => node.textContent = summaries.get(node.dataset.healthFor) || t("vhHealthEmpty"));
const fill = (attr, format) => document.querySelectorAll(`[data-${attr.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`)}]`).forEach(node => { const data = aggregate.get(node.dataset[attr]); node.textContent = data ? format(data) : "-"; });
fill("vhostHealth", d => d.disabled ? t("vhDisabled") : d.staticOnly ? t("vhUp") : d.healthMode === "off" ? t("vhHealthDisabled") : `${d.up}/${d.total} ${t("vhUp")}`);
@@ -242,6 +330,7 @@
load(saved ? structuredClone(saved) : defaults());
});
$("pool-open").addEventListener("click", () => setPanel("vhosts"));
initPoolTableTools();
$("location-help-close")?.addEventListener("click", () => $("location-help-dialog")?.close());
window.rustProxyVhosts = { render, renderStatus, reset() { dialog.close(); draftEpoch++; stagedCertificates = []; uploading = false; editing = null; revision = null; dirty = false; initialized = false; chartHistory.clear(); backendTrafficHistory.clear(); vhostTrafficHistory.clear(); container.replaceChildren(); $("vhost-search").value = ""; $("vhost-list").replaceChildren(); $("pool-rows").replaceChildren(); } };
window.rustProxyVhosts = { render, renderStatus, applyPreferences: applyPoolPreferences, reset() { dialog.close(); draftEpoch++; stagedCertificates = []; uploading = false; editing = null; revision = null; dirty = false; initialized = false; poolTablePrefs = defaultPoolTablePrefs(); poolPreferenceSaveRunning = false; poolPreferenceSaveQueued = false; chartHistory.clear(); backendTrafficHistory.clear(); vhostTrafficHistory.clear(); container.replaceChildren(); $("vhost-search").value = ""; if ($("pool-search")) $("pool-search").value = ""; $("vhost-list").replaceChildren(); $("pool-rows").replaceChildren(); const sort = $("pool-sort"); if (sort) sort.value = poolTablePrefs.sort; applyPoolTableView(); } };
if (envelope) render(); if (latestStatus) renderStatus(latestStatus);
-1
View File
@@ -29,7 +29,6 @@ impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for RedactingStderr {
}
}
}
fn redact_regex(pattern: &'static str) -> &'static regex::Regex {
static AUTH: OnceLock<regex::Regex> = OnceLock::new();
static QUERY: OnceLock<regex::Regex> = OnceLock::new();
+1 -1
View File
@@ -41,7 +41,7 @@ use zeroize::Zeroizing;
#[command(
name = "rust-proxy",
version,
about = "RustProxy - independent Rust reverse proxy and static Web Server 0.25.6"
about = "RustProxy - independent Rust reverse proxy and static Web Server 0.25.7"
)]
struct Cli {
#[arg(
+1 -1
View File
@@ -239,7 +239,7 @@ async fn live_logs_command(dir: PathBuf, addr: SocketAddr) -> Result<()> {
}
});
let interactive = io::stdin().is_terminal() && io::stdout().is_terminal();
println!("RustProxy live logs 0.25.6 - all vhosts. Type 'help' for interactive filters; Ctrl-C or 'quit' exits.");
println!("RustProxy live logs 0.25.7 - all vhosts. Type 'help' for interactive filters; Ctrl-C or 'quit' exits.");
if interactive { print!("> "); io::stdout().flush()?; }
let mut filters = LiveLogFilters::default();
let mut history = std::collections::BTreeMap::<u64, serde_json::Value>::new();
+36
View File
@@ -102,6 +102,8 @@ fn render_backends(
("io_errors_total", "passive_io_errors_total", "counter"),
("slow_responses_total", "slow_responses_total", "counter"),
("slow_responses_total", "passive_slow_total", "counter"),
("retry_attempts_total", "retry_attempts_total", "counter"),
("retry_successes_total", "retry_successes_total", "counter"),
(
"capacity_rejections_total",
"capacity_rejections_total",
@@ -117,6 +119,17 @@ fn render_backends(
),
("read_timeouts_total", "read_timeouts_total", "counter"),
("send_timeouts_total", "send_timeouts_total", "counter"),
("upstream_client_generation", "upstream_client_generation", "gauge"),
(
"upstream_client_generation_requests",
"upstream_client_generation_requests",
"gauge",
),
(
"upstream_client_rotations_total",
"upstream_client_rotations_total",
"counter",
),
(
"max_concurrent_requests",
"max_concurrent_requests",
@@ -151,6 +164,11 @@ fn render_backends(
1000.0,
),
("traffic_bytes_per_second", "traffic_bytes_per_second", 1.0),
(
"upstream_client_generation_age_seconds",
"upstream_client_generation_age_seconds",
1.0,
),
] {
if let Some(n) = member[field].as_f64() {
f.add(
@@ -379,6 +397,8 @@ fn render_docker(
("io_errors_total", "passive_io_errors_total", "counter"),
("slow_responses_total", "slow_responses_total", "counter"),
("slow_responses_total", "passive_slow_total", "counter"),
("retry_attempts_total", "retry_attempts_total", "counter"),
("retry_successes_total", "retry_successes_total", "counter"),
(
"capacity_rejections_total",
"capacity_rejections_total",
@@ -394,6 +414,17 @@ fn render_docker(
),
("read_timeouts_total", "read_timeouts_total", "counter"),
("send_timeouts_total", "send_timeouts_total", "counter"),
("upstream_client_generation", "upstream_client_generation", "gauge"),
(
"upstream_client_generation_requests",
"upstream_client_generation_requests",
"gauge",
),
(
"upstream_client_rotations_total",
"upstream_client_rotations_total",
"counter",
),
(
"max_concurrent_requests",
"max_concurrent_requests",
@@ -428,6 +459,11 @@ fn render_docker(
1000.0,
),
("traffic_bytes_per_second", "traffic_bytes_per_second", 1.0),
(
"upstream_client_generation_age_seconds",
"upstream_client_generation_age_seconds",
1.0,
),
] {
if let Some(n) = member[field].as_f64() {
f.add(
+1 -1
View File
@@ -156,7 +156,7 @@ pub(super) fn fastcgi_params(
}
params.insert("HTTPS".into(), if context.tls { "on" } else { "off" }.into());
params.insert("GATEWAY_INTERFACE".into(), "CGI/1.1".into());
params.insert("SERVER_SOFTWARE".into(), "RustProxy/0.25.6".into());
params.insert("SERVER_SOFTWARE".into(), "RustProxy/0.25.7".into());
params.insert("REDIRECT_STATUS".into(), "200".into());
if let Some(value) = headers.get(CONTENT_TYPE).and_then(|value| value.to_str().ok()) {
params.insert("CONTENT_TYPE".into(), value.into());
+1 -1
View File
@@ -180,7 +180,7 @@ pub(super) fn directory_listing(path: &str, entries: &[DirectoryEntry]) -> Vec<u
"<a class=\"{class}\" href=\"{href}\"><span class=\"type\">{type_label}</span><span class=\"name\">{label}{slash}</span><span class=\"kind\">{kind}</span></a>"
));
}
html.push_str(r#"</div></section><footer class="page-footer"><span>RustProxy <b>0.25.6</b></span><span>Web Server · Directory listing</span></footer></main></body></html>"#);
html.push_str(r#"</div></section><footer class="page-footer"><span>RustProxy <b>0.25.7</b></span><span>Web Server · Directory listing</span></footer></main></body></html>"#);
html.into_bytes()
}
+1 -1
View File
@@ -202,7 +202,7 @@ async fn empty_index_can_go_directly_to_directory_listing() {
let body = std::str::from_utf8(&body).unwrap();
assert!(body.contains("file.txt"));
assert!(body.contains("DIRECTORY LISTING"));
assert!(body.contains("RustProxy <b>0.25.6</b>"));
assert!(body.contains("RustProxy <b>0.25.7</b>"));
}
#[tokio::test]