This commit is contained in:
Mateusz Gruszczyński committed 2026-11-10 11:36:23 +01:00
1 parent e260612ef9
commit 643999f17e
25 files changed
+421 -86

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
# RustProxy source and documentation maintenance # RustProxy source and documentation maintenance
Current source version: **0.36.10**. The Cargo package and binary are named `rust-proxy`. Current source version: **0.36.11**. The Cargo package and binary are named `rust-proxy`.
Before editing, read [README.md](README.md), the relevant [documentation guide](docs/README.md) and the actual code and tests. Historical release experiments are not current specifications. Use the bundled `src/admin/openapi.json` for API routes and payloads; the source implementation is authoritative for runtime behavior. Before editing, read [README.md](README.md), the relevant [documentation guide](docs/README.md) and the actual code and tests. Historical release experiments are not current specifications. Use the bundled `src/admin/openapi.json` for API routes and payloads; the source implementation is authoritative for runtime behavior.
+7
View File
@@ -2,6 +2,13 @@
Release history focused on **features, settings, default behavior, compatibility and fixes**. Changes to internal code structure and preliminary performance experiments are intentionally omitted. Historical descriptions reflect their respective releases; consult the current documentation for the settings available in the latest release. Release history focused on **features, settings, default behavior, compatibility and fixes**. Changes to internal code structure and preliminary performance experiments are intentionally omitted. Historical descriptions reflect their respective releases; consult the current documentation for the settings available in the latest release.
## 0.36.11 — more Backends metrics
- **Backends overview — new columns:** seven more metrics, each sortable and switchable under **Columns**, fill the empty space of wide screens. **HTTP codes** (on by default) shows the 2xx share, any 4xx/5xx shares and a stacked 2xx/3xx/4xx/5xx bar. **Cache** (on by default) shows a HIT-ratio ring and how many bytes were served from cache versus fetched from the backend (ORIGIN); the tooltip lists HIT/MISS/BYPASS counts. **Req/s** shows the current request rate of the backend. **p95 / p99** shows latency percentiles, read from the latency histogram (shown as the bucket's upper bound; above 5 s is shown as ">5 s"). **Probe** shows the last active health-check latency and how long ago it ran. **Error causes** shows up to three most frequent failure counters (5xx, timeout, connect, TLS, DNS, I/O, queue, limit, retry). **Protocol** shows the last observed upstream protocol (H1/H2) and the HTTP/2 share. HTTP codes, Cache and p95 / p99 are counted per vhost, so all backends of one vhost show the same value. The vhosts panel backend table offers the same columns as text.
- **Status API:** each `vhost_metrics` entry in `GET /api/status` (full and `view=live`) adds `status_classes` (1xx–5xx counters), `p95_latency_ms` and `p99_latency_ms` (`null` without traffic). A new top-level `vhost_cache_totals` object (vhost ID → `hits`, `misses`, `bypasses`, `saved_body_bytes`) sums the cache domain statistics per vhost.
- **Preferences API:** `backend_table_columns` and `backend_table_sort` accept `rate`, `statuses`, `p95`, `cache`, `probe`, `causes` and `protocol`. The default column set adds `statuses` and `cache`; the other new columns are opt-in.
- **Upgrade notes:** no configuration or database migration is required. An account that has already saved its own column choice keeps it, so enable the new columns under **Columns** in the Backends card.
## 0.36.10 — Fit to screen, collapsible menu and smoother startup ## 0.36.10 — Fit to screen, collapsible menu and smoother startup
- **Fit to screen (auto-arrange):** a **Fit to screen** switch in the unlocked layout toolbar (account preference `dashboard_auto_fit`, **on by default**) re-arranges a custom Overview for the current screen width, so the layout designed on a wide screen stays tidy on laptops and tablets. Each card keeps its saved width (but at least a readable minimum for its type), cards are placed in saved order into the first free spot from the top, stat tiles that start on one row get one height, a card whose side neighbour ends slightly lower is extended to that edge (only while the dashboard grows by at most two stat tiles), remaining holes are filled by widening a neighbouring card sideways or growing it into free space above or below. When a card is placed, a free strip beside it that is narrower than every card still to be placed is taken by that card at once, and at the end of the grid a chart or panel reaches the last row instead of leaving an empty area. The saved widths, heights and order are never changed; switch it off to see the layout exactly as saved. While a block is dragged the normal flow is shown so the drop placeholder stays accurate. - **Fit to screen (auto-arrange):** a **Fit to screen** switch in the unlocked layout toolbar (account preference `dashboard_auto_fit`, **on by default**) re-arranges a custom Overview for the current screen width, so the layout designed on a wide screen stays tidy on laptops and tablets. Each card keeps its saved width (but at least a readable minimum for its type), cards are placed in saved order into the first free spot from the top, stat tiles that start on one row get one height, a card whose side neighbour ends slightly lower is extended to that edge (only while the dashboard grows by at most two stat tiles), remaining holes are filled by widening a neighbouring card sideways or growing it into free space above or below. When a card is placed, a free strip beside it that is narrower than every card still to be placed is taken by that card at once, and at the end of the grid a chart or panel reaches the last row instead of leaving an empty area. The saved widths, heights and order are never changed; switch it off to see the layout exactly as saved. While a block is dragged the normal flow is shown so the drop placeholder stays accurate.
Generated
+1 -1
View File
@@ -1943,7 +1943,7 @@ dependencies = [
[[package]] [[package]]
name = "rust-proxy" name = "rust-proxy"
version = "0.36.10" version = "0.36.11"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arc-swap", "arc-swap",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "rust-proxy" name = "rust-proxy"
version = "0.36.10" version = "0.36.11"
edition = "2021" edition = "2021"
rust-version = "1.99.1" rust-version = "1.99.1"
description = "RustProxy - independent reverse proxy with local administration" description = "RustProxy - independent reverse proxy with local administration"
+65 -64
View File
@@ -1,10 +1,10 @@
61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore 61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example 4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
e76e688874828e3743d03693b1d2872b9633b4db09909673786caf30f7cfe489 .gitignore e76e688874828e3743d03693b1d2872b9633b4db09909673786caf30f7cfe489 .gitignore
9648830349e8ff28509f8a62a4f62c3cca1721498118cf9fd71e2cb7d95fb4ac AGENTS.md c68dfaf860c4ceaa9de2510330eb05921126ff23191087ca22ca65e5b78ed59a AGENTS.md
11b4e849d5933ca383fc4e3b61a3f28e19deef7ee6d768e5108008e58b8dbc3e CHANGELOG.md 5659076e0d6bf903a9936bf43a716916808b3b0312c4076b07714f84370bdc0b CHANGELOG.md
b7b5cd49c635e709301850d07d24014876e3d4e0942dff320bc1cd621a2aeb55 Cargo.lock 9052c7f968e072cfce8dad7e6e8c72ee526dd1b97a3bdc0aee734ef32e67bfdd Cargo.lock
7a9f5c1cb40414c8cb1e5a3af9c1f1322c69769b76c7c8701d4ee81338168a7c Cargo.toml 3eaef17f8ed43af9ab112eae4c3da46b8a1cd27c3b526c6f684e99923e828938 Cargo.toml
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile 25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0 LICENSE 0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0 LICENSE
82d1dee2fc68158f0ea73259151461e64c8b18d807cd1fecfb1ed99dc30cf5f4 README.md 82d1dee2fc68158f0ea73259151461e64c8b18d807cd1fecfb1ed99dc30cf5f4 README.md
@@ -88,12 +88,12 @@ de6550ab8f48d556d752c252212c38ba65491fd224f972c10fbe552f91ed7520 integrations/o
45be151cd85fc2ef2f1fdc3003ace63381af262e73af8a685ab48cb697cc0de1 integrations/routeros-oci-build/.env.routeros.example 45be151cd85fc2ef2f1fdc3003ace63381af262e73af8a685ab48cb697cc0de1 integrations/routeros-oci-build/.env.routeros.example
11aa102de1089ecc375f20c4bd5420fcfd3b2ef23fe0c8bdf04953fcfaf40ef0 integrations/routeros-oci-build/Dockerfile.routeros 11aa102de1089ecc375f20c4bd5420fcfd3b2ef23fe0c8bdf04953fcfaf40ef0 integrations/routeros-oci-build/Dockerfile.routeros
7c7a3e51f43b43e1c962a4b0bb591f99c1f06977bf77f23719f7f867dfe7eb8e integrations/routeros-oci-build/Dockerfile.routeros.dockerignore 7c7a3e51f43b43e1c962a4b0bb591f99c1f06977bf77f23719f7f867dfe7eb8e integrations/routeros-oci-build/Dockerfile.routeros.dockerignore
6f7a659cd2f13dc0a091049c15800967481fcd6c44984332f6f37bcc758cd5a1 integrations/routeros-oci-build/README.md 6da1af96e4faf09e7d9983c656eeb2bd01b205f0a72cde3ae3494af2fc84a9c3 integrations/routeros-oci-build/README.md
3f092a9ab80221eddd67be4d1a776acbc6e04c1eb6011b588280c28e83b2582f integrations/routeros-oci-build/build-routeros-oci.sh 3f092a9ab80221eddd67be4d1a776acbc6e04c1eb6011b588280c28e83b2582f integrations/routeros-oci-build/build-routeros-oci.sh
3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a integrations/routeros-oci-build/docker-entrypoint.sh 3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a integrations/routeros-oci-build/docker-entrypoint.sh
13189fa546c3488dddd8efc80c47f6da5241e5fc28b79cbfe795414ea05c7a4f langs/README.md 13189fa546c3488dddd8efc80c47f6da5241e5fc28b79cbfe795414ea05c7a4f langs/README.md
ab85a75c125279859f70aa00f5eec44914c8e7a571593c09dd15a400eaddad80 langs/en_US.json 773661d94fbc6fc04a528ebb1bf8e67c77155b21b1d20c7360021d316ab599ee langs/en_US.json
e63c7fa00f90410e3fa0d087dcfb09d639310680011fe3221ac682db8eb84c56 langs/pl_pl.json b8c983393812f1a8086ea7b89d47d8da699cb721fd640cb3e03b684145da8963 langs/pl_pl.json
8ed80f14633970899d56f4ea72684a8d4b1f2843c1c78bfacc4cbb5f7ce9ef4b make_zip.py 8ed80f14633970899d56f4ea72684a8d4b1f2843c1c78bfacc4cbb5f7ce9ef4b make_zip.py
e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql
20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql 20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql
@@ -165,68 +165,69 @@ d58a2ed83319176de4e66790ed292447c9497ae031464ec55cfe27174acb639a src/admin/file
77b8c05645969834cc8a58e249a7b60a57d6c47ad1c5cd92ffa5e4a9b5a8e73c src/admin/file_logs_query.rs 77b8c05645969834cc8a58e249a7b60a57d6c47ad1c5cd92ffa5e4a9b5a8e73c src/admin/file_logs_query.rs
743bb7436f77e04015328a0e7e7d425e43ef8aa8d61467bde157e462c1dcdc2e src/admin/logging_api.rs 743bb7436f77e04015328a0e7e7d425e43ef8aa8d61467bde157e462c1dcdc2e src/admin/logging_api.rs
243f4a9c3fa99781e19c3952354d2632c0ce65e1dafac729567cbe3c177f94cb src/admin/mod.rs 243f4a9c3fa99781e19c3952354d2632c0ce65e1dafac729567cbe3c177f94cb src/admin/mod.rs
5d7d041c7eaaf4badb9e9d8d48d0876742142a670137e66ae1b9b9b252481fc2 src/admin/openapi.json 02088b91cb61389e610b47abfe15c1c124749611967466e9845594fecdb30b87 src/admin/openapi.json
f7a356109786635dd58cba590c71c796dbf36b47f25845190c75ee5c6a9fcb86 src/admin/operations.rs f7a356109786635dd58cba590c71c796dbf36b47f25845190c75ee5c6a9fcb86 src/admin/operations.rs
257d93a29d0bda96462bcf695aba8d0ec0f9412c379e066ec46ae8ee034e3a53 src/admin/panel_assets.rs 257d93a29d0bda96462bcf695aba8d0ec0f9412c379e066ec46ae8ee034e3a53 src/admin/panel_assets.rs
f5127855c2994d052678865df36d703b74f4a0afc633daf7718b10da18d26e37 src/admin/panel_content.rs f5127855c2994d052678865df36d703b74f4a0afc633daf7718b10da18d26e37 src/admin/panel_content.rs
80d7b07d2f0e0494e2a8c53f530b9d1ebc9ba68a98e95059f4553249ccaed65e src/admin/preferences.rs ed2b01ec23a24af877c8a0abd67d63639e8bb49f5b33c0ee844928d972561750 src/admin/preferences.rs
cc3b08a80f44bc141107eef2ec41802b7f1f7b24ddc220f6375f495736bc0cfa src/admin/preferences_columns.rs cc3b08a80f44bc141107eef2ec41802b7f1f7b24ddc220f6375f495736bc0cfa src/admin/preferences_columns.rs
125fa7ea16d126a60f213abf4e5a7adf62c1a830ef96882fa4e0dfc2059f82a0 src/admin/preferences_tests.rs ccf3d947e2c2e0e3e330ba7ece6f1d9e1c97fd59fda15d9bc40b4f5c18af3124 src/admin/preferences_tests.rs
62ecbae5dee00aeeca3eb806e2605372471dfab971cde8f66f11e711df8fcea1 src/admin/public/mod.rs 62ecbae5dee00aeeca3eb806e2605372471dfab971cde8f66f11e711df8fcea1 src/admin/public/mod.rs
7a00e4ab943f74d2b1bdcbfab7f9cd3f584e9f59536b4aca6c4c31f5f5508d12 src/admin/public/tests.rs 7a00e4ab943f74d2b1bdcbfab7f9cd3f584e9f59536b4aca6c4c31f5f5508d12 src/admin/public/tests.rs
6930b4f4b45257772b321b20a9becdf9bc4610274852948ba38c2d0cdf02dfaf src/admin/refresh_preferences.rs 6930b4f4b45257772b321b20a9becdf9bc4610274852948ba38c2d0cdf02dfaf src/admin/refresh_preferences.rs
d151a5a8008349fda5af06c46692ea0c8d1e64059cfe81df412ba8a781d81b94 src/admin/router.rs 66eea4e8fd1c60a64090bd9e97b36903b10b1e2f7089c1a333e057d1d421bd6f src/admin/router.rs
e135431cb078ddb483165581123c64af8f00ea67acb3a395ec3e53c85d0d0d45 src/admin/session.rs e135431cb078ddb483165581123c64af8f00ea67acb3a395ec3e53c85d0d0d45 src/admin/session.rs
c4a3f1ab3d74f153f8bb94a21105feba6e04663e5be6354537f5897fc60cb92c src/admin/status.rs c4a3f1ab3d74f153f8bb94a21105feba6e04663e5be6354537f5897fc60cb92c src/admin/status.rs
6a405b189f40db88ca635ad054fd43801aad8bbd3e7f00f1b0e99bb57d4cb941 src/admin/status_live.rs 6a405b189f40db88ca635ad054fd43801aad8bbd3e7f00f1b0e99bb57d4cb941 src/admin/status_live.rs
8315c856cd50f1072593ecb30c274d42d82f078b5fb93e7c68a8857cd81b0841 src/admin/status_system.rs 8315c856cd50f1072593ecb30c274d42d82f078b5fb93e7c68a8857cd81b0841 src/admin/status_system.rs
cadd7dca29b9aad8c77c7bc23dd249ea37a15adfa4d636a8ef00273a91ce35dc src/admin/tail.rs cadd7dca29b9aad8c77c7bc23dd249ea37a15adfa4d636a8ef00273a91ce35dc src/admin/tail.rs
95ce9f0025a8c6ca578d12231259d5baea38b493969c5160a2742ba31161a309 src/admin/tests.rs 1d83309ba017e086459f761302d2c95220fb5a2b82f2663c531ce66611b0e373 src/admin/tests.rs
c18c369124368f58524652565102817ed69fd25b222b0c5e737ef7632b45dcfb src/admin/web/acl-picker.js c18c369124368f58524652565102817ed69fd25b222b0c5e737ef7632b45dcfb src/admin/web/acl-picker.js
48fcd818c522fe80f2c7b2e218f87de6a7772ffd0a24e93502780b03ec78e335 src/admin/web/acme.js 48fcd818c522fe80f2c7b2e218f87de6a7772ffd0a24e93502780b03ec78e335 src/admin/web/acme.js
86abf50f39862d821cd2e43ca425671aa85289272915132c5a9a5dd1fc4182e2 src/admin/web/api-tokens.js 86abf50f39862d821cd2e43ca425671aa85289272915132c5a9a5dd1fc4182e2 src/admin/web/api-tokens.js
faa9735c2a02746b2954bc4955eb609f94afebb685cd5521d2629640cea2777a src/admin/web/app/backend-dashboard.js faa9735c2a02746b2954bc4955eb609f94afebb685cd5521d2629640cea2777a src/admin/web/app/backend-dashboard.js
aed3aea4eef0011c6b51d39736782e7d1ec52662e422f40e0c7b9ddfc19d0378 src/admin/web/app/certificate-inspection.js aed3aea4eef0011c6b51d39736782e7d1ec52662e422f40e0c7b9ddfc19d0378 src/admin/web/app/certificate-inspection.js
f6b0533ffd6d0e6471519120c440619a7fb4b255854ba3dd23d81cb35cd21afb src/admin/web/app/chart-scales.js f6b0533ffd6d0e6471519120c440619a7fb4b255854ba3dd23d81cb35cd21afb src/admin/web/app/chart-scales.js
e5b39b5592ba81292571033f26934ac0cd42bcdca7395e91094973cecb3eeaf8 src/admin/web/app/config-history.js dedda129e173a347ebca9445c8a313849d1d678c256deabeb18f9977559e021a src/admin/web/app/config-history.js
2bc0b8f29b76abe2ea3d8666b8dd4d7e58f3873ab2503ec056b2c307c7508a77 src/admin/web/app/core.js d20a1cfc53c160e809c34dc83f19be9cb19f075011c4c3f9a9d755230ac2ba18 src/admin/web/app/core.js
1db5fd8f7e1695dd97bbc81a6da276c5a1f171454edf11c5b91c1ea5bf837d42 src/admin/web/app/dashboard-inline-layout.js 073c52c7c7d246703087aa9a8df3c3d8742e651da55c0edd6f2baef26bd241b0 src/admin/web/app/dashboard-inline-layout.js
e87de2bc141cdb7d3ec62cbbe4debebb047e8d5adf6015743a97dfb0cfd268ba src/admin/web/app/dashboard-layout-editor.js b269cd90710d2e86b4c9b685d4d141271ef21fe6256d3b03b8f4018d00c48c7e src/admin/web/app/dashboard-layout-editor.js
716d33249a52548f10153d8e2f5bea962a5f0427525272ddd3a1db636647672c src/admin/web/app/dashboard-presets.js 716d33249a52548f10153d8e2f5bea962a5f0427525272ddd3a1db636647672c src/admin/web/app/dashboard-presets.js
0be1024d9ff657044e04ed71f275cff3b432406d5fdcdeac726daaee54444abd src/admin/web/app/icons.js 99c3e775b3a57d9a783ca9350f48132eecbb0471f10daa6961cb2bcc9de630db src/admin/web/app/icons.js
e5a92fa1cb7e049082f0764b245560c41a9a34bcd8a9536fd872d9169fa5465d src/admin/web/app/logo-animation.js 9fc3165febd83a6661b2827aacd612fbc0f834e99ef1585ec54e03e97fe54a72 src/admin/web/app/logo-animation.js
4ea37d56919772d239817b7afa36d6d6386452f9d7db6a889940fe44c29d7508 src/admin/web/app/memory-breakdown.js 92ed982c3ac4bc4218a24a200d2a33f30fc6705b77b260db2be31b0ce91a7df9 src/admin/web/app/memory-breakdown.js
32aa11f041eafdf16e66adef6f5847a67413785224da2c19ecbf825996622aff src/admin/web/app/overview-extra-metrics.js 32aa11f041eafdf16e66adef6f5847a67413785224da2c19ecbf825996622aff src/admin/web/app/overview-extra-metrics.js
5e62cccf6a9507fe208e9f41bbd08453c3d8c6a6b83c56d93a2bb22550ea6e9a src/admin/web/app/overview-masonry.js 3049a308881f98ab19ffd95b4bb88976702c61f2fc82792b4402f0212fcd7403 src/admin/web/app/overview-masonry.js
959a8f49a251629a501163b4b02841c7ec821dc5a0cca57c8f1ef45464266f17 src/admin/web/app/overview-rows.js bbbbe153ff37aa4cac0a83319e5b56a6442cabd1ef819380ae76e95bf79c7628 src/admin/web/app/overview-rows.js
4d1d4760cdff44771c9d880a7c0da81d7233ef6e3a4868111a522703831a7a54 src/admin/web/app/overview-samples.js 6f10f3239aea505aa01796620981eed873930c129ed5a2fd7eb439e0e0d24558 src/admin/web/app/overview-samples.js
db9012546cae8c58fc2bcbee71c9a95956a13a6db9e6d126c6a3770d45a4e4d2 src/admin/web/app/overview-wide-metrics.js db9012546cae8c58fc2bcbee71c9a95956a13a6db9e6d126c6a3770d45a4e4d2 src/admin/web/app/overview-wide-metrics.js
bfd9fafb3112197e8f875dbf87a9a66c81aef0fe83687601a84a66f5e13f69bc src/admin/web/app/path-permissions.js e799e0406c7ff54cbf71faf704a20265bdf5180a8ed2f26388d0775231365410 src/admin/web/app/path-permissions.js
ea6c65fd2584665a7b6fa48c2c024f7d214d7b5f354193f29a9da8bc0dc4b2e0 src/admin/web/app/preferences.js 66601eb2810b2eb224ddb4fed1ecd9e5f5cb9c51878432e9ea36908ed4a865eb src/admin/web/app/preferences.js
9e2b12d34ab1fb29716645720577ad49b16c03b46c482a03695e8f645d170e79 src/admin/web/app/rate-sampling.js 30ec0a7027215eae63946689af2c042de0cf468a6b1054535694da4dd4214cdb src/admin/web/app/rate-sampling.js
3b8e566d81a5b94ef21c73e7805ae128454f085554de9358040de266bcbdb0e0 src/admin/web/app/refresh.js 3b8e566d81a5b94ef21c73e7805ae128454f085554de9358040de266bcbdb0e0 src/admin/web/app/refresh.js
bebfe4f835d69df1937dd34ebb2bc6da3b701a5e1825a56b89e7199fcc524534 src/admin/web/app/runtime.js 399a52a076872fa031113d475794e029cb7eb3813a3471eb54cbbac5e765ce03 src/admin/web/app/runtime.js
31201586925e6f4792071d7d03fb05dcc4a69a03c02a4e85eb0746f45af68074 src/admin/web/app/session-resume.js 31201586925e6f4792071d7d03fb05dcc4a69a03c02a4e85eb0746f45af68074 src/admin/web/app/session-resume.js
c7a18238ac426de4d1cf84a395ee4dc66410d60a57a3822695dc10adc668d956 src/admin/web/app/settings.js c7a18238ac426de4d1cf84a395ee4dc66410d60a57a3822695dc10adc668d956 src/admin/web/app/settings.js
2281f06990f86acd3ff12fa368e1b73e4c83601e20245f24abbce389dfccb77f src/admin/web/app/sidebar.js
b7ead49224a4ec67181d3f24f9d9634bd6d76756f826a4d5d993300637d1d8f3 src/admin/web/app/stream-dashboard.js b7ead49224a4ec67181d3f24f9d9634bd6d76756f826a4d5d993300637d1d8f3 src/admin/web/app/stream-dashboard.js
7de070cc8cdde45d7e0932259299e341e25b32c9ffbd87e4fcce934d0f090de4 src/admin/web/app/web-overview.js 7de070cc8cdde45d7e0932259299e341e25b32c9ffbd87e4fcce934d0f090de4 src/admin/web/app/web-overview.js
f87af546830f01411dcf93749b4f75ca0f0bb3745a3390eff73ee14fc768cef0 src/admin/web/cache-panel.js 3563508f45f740f0cc0e84c72eb79045c0cb54d6fa06a50326a3a219221e0010 src/admin/web/cache-panel.js
9f323507649910561518b63cf6632e81b3ecc4b09501ae53b635705f34ea6587 src/admin/web/certificate-api.js 99ed8c37e4fee646b9b26c96edbba4d823175dc3354c17c6574a7fc9a3dcbb0e src/admin/web/certificate-api.js
4810137aa4217a86770617d442cf5dc6bec0d9f19e20bb2155e932a9a111cedf src/admin/web/css/00-tokens.css 43d702b676cd4f9dbe42e2ca65d938afe19086e67ab6e24af0b81d6947bee215 src/admin/web/css/00-tokens.css
84584a08df60190f39c3d3d39c4c30fcd53eba6980ea1f237e28f546585bf588 src/admin/web/css/10-base.css 8612407692e9cf6e9d827f27efcd056b8065422a8bae2570ac5b5b1e2eebf575 src/admin/web/css/10-base.css
8a1996d37f138790c12a704deedde9de74f733e7dcbb6be816fd68c281e8e0a9 src/admin/web/css/20-shell.css e711163fb144e38c3c961c20c38be55ebbb2e75898ab0ac1e58762e9ab8ff005 src/admin/web/css/20-shell.css
f6f58db86036ddbe569f6c20f7867519046771b5991bff5cedcd34a6ce7c9b5e src/admin/web/css/30-overview.css f5c40c506bc0ece3396db5d7bebe6fda4af54584d0a772c8495feed9dc9dce6f src/admin/web/css/30-overview.css
ca93e41d1c6001d82f4891cacdd712e896a0355d6db9f76cf62dd2f2225a8674 src/admin/web/css/40-editors.css e5cc169992ba16857f6dea4aef51c26df8f1317e36769caaa235388ec3abb680 src/admin/web/css/40-editors.css
87df062d7478176641364c07de396ca988a37b1a8bcb892b53cc578fe8471850 src/admin/web/css/50-settings.css fb1a22d6fb116ca7ab1bbfeb87c3d3f9dc7cb4386b9c4a570110e0fcbb4b7904 src/admin/web/css/50-settings.css
74419f367c4128824ec2f0901a99f54f68439d5d949d41d9cf3d6bd57568343e src/admin/web/css/60-monitoring.css 31c927777bb844c40305b96b6771b4f3956590fc8346a6b9f08724bad676bce2 src/admin/web/css/60-monitoring.css
0ec5a4b64609bb31373d95fda8011bc42c0748c75a29d28de1841813f0e04f78 src/admin/web/css/70-responsive.css e4f276e7ab5ada8342b0c34a95a0e55f3cd5e446c4620ad1a8e1f07e287793a2 src/admin/web/css/70-responsive.css
8009afdb04d5170687816730cc930a22fead1fe0248835ca7e8de81d6907395d src/admin/web/css/README.md 8009afdb04d5170687816730cc930a22fead1fe0248835ca7e8de81d6907395d src/admin/web/css/README.md
8241bd9489dfed082863948829a44a49bf0a8e9cd63c55809655109e393e0918 src/admin/web/css/manifest.json 8241bd9489dfed082863948829a44a49bf0a8e9cd63c55809655109e393e0918 src/admin/web/css/manifest.json
b369bd8927ee84d05f546c267dd48f849a0857ca4355847302d6ac2381100440 src/admin/web/dashboard-defaults.json b369bd8927ee84d05f546c267dd48f849a0857ca4355847302d6ac2381100440 src/admin/web/dashboard-defaults.json
6f2631ffbd130db1f69f153749c018ed488116061a3462e0eeb38e5496fb1457 src/admin/web/dashboard-widgets.json 6f2631ffbd130db1f69f153749c018ed488116061a3462e0eeb38e5496fb1457 src/admin/web/dashboard-widgets.json
50d1ebcbaf4d0b2968da23bb943fc3f67131c5d32df3f61d23ff37aad9da5b81 src/admin/web/demo.html 50d1ebcbaf4d0b2968da23bb943fc3f67131c5d32df3f61d23ff37aad9da5b81 src/admin/web/demo.html
bd170341634d7fe98172e0714b3cb8ee299a31abdbc9ab1309f5e61816dbcf0d src/admin/web/docker.js bd170341634d7fe98172e0714b3cb8ee299a31abdbc9ab1309f5e61816dbcf0d src/admin/web/docker.js
fec062a5959450b1711948adf3189ceb37edca6fbead2e5bfc03b7ce397c70b1 src/admin/web/docs.css 9d17b9bb74185144472345bd2b034f1ffdbe0f4ef3055e4fe4ef69e32f780e7a src/admin/web/docs.css
aeffd403fa1823df7d9fa4c48280b4033b2d15e4cb9f66608caff89290de0a52 src/admin/web/docs.html aeffd403fa1823df7d9fa4c48280b4033b2d15e4cb9f66608caff89290de0a52 src/admin/web/docs.html
fd8506a91f769b57b62d9d8e389fb67764599354bf702958a913be79f779707b src/admin/web/docs.js fd8506a91f769b57b62d9d8e389fb67764599354bf702958a913be79f779707b src/admin/web/docs.js
8fbc2a837d6f20a7f96c856a91fcba967b40a33494fe27ec876ee1b3b685178f src/admin/web/drafts.js 8fbc2a837d6f20a7f96c856a91fcba967b40a33494fe27ec876ee1b3b685178f src/admin/web/drafts.js
@@ -244,9 +245,9 @@ ff72fc4c6a11907a16234c4b3c7d7f7bcf82eb72868dc3fbf654b04aff806fc8 src/admin/web/
e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/error_504.html e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/error_504.html
003d382cc8dd69ad7237679632c04a9389366bb71dd7dca8ee64ee06614aa9a5 src/admin/web/error_auth_provider.html 003d382cc8dd69ad7237679632c04a9389366bb71dd7dca8ee64ee06614aa9a5 src/admin/web/error_auth_provider.html
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg 0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
7cbdef9192a9dee1f8eac42b85f52b3190f7f6efae50004aa7ad116a910e4036 src/admin/web/file-logs.js 453b2f871a49c6517cbd5d811ec1a28a7ca8f08796543fda6164f2c7c624824c src/admin/web/file-logs.js
882e99059bb2ff110c452eadc0b5779a16af1e34cb1b2536c80cf2056f8c3e8a src/admin/web/host404.js 882e99059bb2ff110c452eadc0b5779a16af1e34cb1b2536c80cf2056f8c3e8a src/admin/web/host404.js
ba4c7aa74400f35e1bee32c3ff86fa8793ae69685a20d81776c762ea681d5c5a src/admin/web/index.html 10ebca279f270da039cbaa0bfea1e2ab42785e32cbeef91e0c3074fed16e5e75 src/admin/web/index.html
5746049029caa135646ebcefb3d419b07eda17b560a9c015c59d27f815df64d8 src/admin/web/integrations.js 5746049029caa135646ebcefb3d419b07eda17b560a9c015c59d27f815df64d8 src/admin/web/integrations.js
7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json 7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json
ac9f667273918e18406df06cb2613c923f50a88897693a03a18f500e22dc6ec2 src/admin/web/login.html ac9f667273918e18406df06cb2613c923f50a88897693a03a18f500e22dc6ec2 src/admin/web/login.html
@@ -261,12 +262,12 @@ f619250d8da61517eb2151f17b640b06057e091640280091e08afd483d67e5ff src/admin/web/
a5191e1272939014a21b8d16588704952e9a705b02bd87fd8f8baca0a64ca919 src/admin/web/streams.js a5191e1272939014a21b8d16588704952e9a705b02bd87fd8f8baca0a64ca919 src/admin/web/streams.js
ae0259cf32f53d69a22a640b1c34562018d4ffb3864494012de3a31c0bb4ac1e src/admin/web/traffic-panel.js ae0259cf32f53d69a22a640b1c34562018d4ffb3864494012de3a31c0bb4ac1e src/admin/web/traffic-panel.js
675ea169d4cc55fda5e291af536338b882bede1eaa6c9d5cc7dfde0779c7c4d1 src/admin/web/transform-panel.js 675ea169d4cc55fda5e291af536338b882bede1eaa6c9d5cc7dfde0779c7c4d1 src/admin/web/transform-panel.js
1657db2c683292411c53e4bc0f67784529b7bc367552901d013e2c88793d613f src/admin/web/vhosts/core.js 8fa4a8d27e87fc6ea0a8f8492e637fe09504cc9a728c27361da2559007e70698 src/admin/web/vhosts/core.js
3f5fc9649801d091708e495634192fecd76482254eb671551427d3ee580eac65 src/admin/web/vhosts/editor.js dc0fc85c64eef983d575f1b220c53c28cf5d4944b77860a57f9f28c6fde2a386 src/admin/web/vhosts/editor.js
3776f72d7051af86ac5fbdd4581040dd88a424c0c6cde789301b032b159094d8 src/admin/web/vhosts/health.js 3776f72d7051af86ac5fbdd4581040dd88a424c0c6cde789301b032b159094d8 src/admin/web/vhosts/health.js
b3239e67901f1796d7a974ade0796f2e3590ff8064628a5629b78c7f6ce2f32e src/admin/web/vhosts/protocol.js b3239e67901f1796d7a974ade0796f2e3590ff8064628a5629b78c7f6ce2f32e src/admin/web/vhosts/protocol.js
8c3da49973c62333559b0e3588c02beb0b425c025eac5cfa82a1bb968f70d9dd src/admin/web/vhosts/rates.js 8c3da49973c62333559b0e3588c02beb0b425c025eac5cfa82a1bb968f70d9dd src/admin/web/vhosts/rates.js
eb9d692cb0437860bed0da512e321e458a3843216773fd486dd1c1f57287cacb src/admin/web/vhosts/render.js 475cfb0343b3dc5e12525de177c98c952d33ba128f2255919e271ae097ee80cf src/admin/web/vhosts/render.js
4dfdeb7c73c2775eacaca65fd8ef1dc80b275421b15522d319d338f5b96288cc src/admin/web/web-server-profiles.js 4dfdeb7c73c2775eacaca65fd8ef1dc80b275421b15522d319d338f5b96288cc src/admin/web/web-server-profiles.js
9e8e99e3cdfc32097d0c63dabb19d1e439b3be2264baa67678b46715b3a5bb4e src/admin/web/web-server.js 9e8e99e3cdfc32097d0c63dabb19d1e439b3be2264baa67678b46715b3a5bb4e src/admin/web/web-server.js
2d40c2cb32d7365df0c9cd6c8cb6f0483c7a799786c648f4836a0d9740c6c5ef src/auth/files/mod.rs 2d40c2cb32d7365df0c9cd6c8cb6f0483c7a799786c648f4836a0d9740c6c5ef src/auth/files/mod.rs
@@ -298,14 +299,14 @@ d789bf40cf9433e2fd16409d112210fe93154588b1ac9bddc9c7f78e1db14426 src/balance/po
a361c341932ffd186c0b21356c91ab1fe8043f5020d40798266d15ae896a114e src/balance/tests.rs a361c341932ffd186c0b21356c91ab1fe8043f5020d40798266d15ae896a114e src/balance/tests.rs
ae06d238b1451328c751a511476ef39c012f3a397393f939ca02f96ecd0119a6 src/basic_auth/mod.rs ae06d238b1451328c751a511476ef39c012f3a397393f939ca02f96ecd0119a6 src/basic_auth/mod.rs
9a8858b7ff461a4d961ba701ca3ed6eaec15c3f7f05eef8e0e30edc63617605a src/basic_auth/tests.rs 9a8858b7ff461a4d961ba701ca3ed6eaec15c3f7f05eef8e0e30edc63617605a src/basic_auth/tests.rs
90f26bcd36720076f874cd0a21230316b7303f68e23839e987cf51e13c23352b src/cache/cache_regression_tests.rs 9a7c0d0e5bcad1c7efdf2a4621346a340abf1b7b82902d2655267da4b8131be7 src/cache/cache_regression_tests.rs
7def03ace07dc38a4806277d75b6ce673d33ddaaf1c4855fa0a3372be1cf9489 src/cache/cold.rs 7def03ace07dc38a4806277d75b6ce673d33ddaaf1c4855fa0a3372be1cf9489 src/cache/cold.rs
b75ff512a1c6344483e00d2c67578ceea6b5472c279da0246f3622daa68d7e16 src/cache/cold/cleanup.rs b75ff512a1c6344483e00d2c67578ceea6b5472c279da0246f3622daa68d7e16 src/cache/cold/cleanup.rs
5fde9e9f77277d557372e4614264512804192bb51f3d2eae785988c17b2e7253 src/cache/cold/cold_tier_tests.rs 5fde9e9f77277d557372e4614264512804192bb51f3d2eae785988c17b2e7253 src/cache/cold/cold_tier_tests.rs
3c3897980c31a69d96f9295689d1ae19f2190da758c69ee7a0541f4171288bb8 src/cache/cold/demotion.rs 3c3897980c31a69d96f9295689d1ae19f2190da758c69ee7a0541f4171288bb8 src/cache/cold/demotion.rs
17b2a157b03f43f49ed05f1f3391aef025a453b748056c67229cb3ceaa53bb7a src/cache/cold/startup.rs 17b2a157b03f43f49ed05f1f3391aef025a453b748056c67229cb3ceaa53bb7a src/cache/cold/startup.rs
83095bc93132f306c537b31ba929669fedac99af3c4f133d0533b5ed7ac4e6ed src/cache/cold/worker.rs 83095bc93132f306c537b31ba929669fedac99af3c4f133d0533b5ed7ac4e6ed src/cache/cold/worker.rs
c6c54984cf3460e6227d40d8d6917d7ca5d809ac364c80effe354c64133c765d src/cache/collapse.rs 7f81a07400ab680a34affa32b63199bb5ca7d4511dd9d58465f68279b32d3232 src/cache/collapse.rs
346fdb40d85627b583a01fa17c65e45c822dc7216198cdeadbb3d07904286fa3 src/cache/dashboard.rs 346fdb40d85627b583a01fa17c65e45c822dc7216198cdeadbb3d07904286fa3 src/cache/dashboard.rs
be3d80cd340edb47755190a668e2f2a2776940665f95ae7110893b706eb94faa src/cache/dashboard_tests.rs be3d80cd340edb47755190a668e2f2a2776940665f95ae7110893b706eb94faa src/cache/dashboard_tests.rs
847c660e6cb7d752f9dac2807c5045a000172c25b0e437cbade06ad7b101dcba src/cache/fill.rs 847c660e6cb7d752f9dac2807c5045a000172c25b0e437cbade06ad7b101dcba src/cache/fill.rs
@@ -315,7 +316,7 @@ bbd9f372648d89b5e14d5b58029582d37f66a89752caf2c14eb1cbf8f24bb7a0 src/cache/inva
6223eaea3f061b111330cc26b28d85b9b239bef30328025f2ef337291e22a445 src/cache/invalidation/invalidate.rs 6223eaea3f061b111330cc26b28d85b9b239bef30328025f2ef337291e22a445 src/cache/invalidation/invalidate.rs
c1ee3d13b373672c53b5c9ebfe5c4b66b89f2f2913c328defd5cf6502f96a654 src/cache/lifecycle.rs c1ee3d13b373672c53b5c9ebfe5c4b66b89f2f2913c328defd5cf6502f96a654 src/cache/lifecycle.rs
bd831807ca4720b3a41a6ea1d9a6c0ed2d935225e2dacbd9313be064f2e4a20e src/cache/lookup.rs bd831807ca4720b3a41a6ea1d9a6c0ed2d935225e2dacbd9313be064f2e4a20e src/cache/lookup.rs
c2e258da25fbf6df7da3de5a80a04d74e85c03ab22c175d7c9a6340b8ea1f1b5 src/cache/lookup/hot.rs dd2d3132d4a8d2f453021d27bd8c804159edd86d286d4c3ddf6b47aadca80a09 src/cache/lookup/hot.rs
4148106b9baf70e41eeba24233b1edbe8dd6f927c11f2c7647e89fbbfb60555e src/cache/lookup/hot_hit_034_tests.rs 4148106b9baf70e41eeba24233b1edbe8dd6f927c11f2c7647e89fbbfb60555e src/cache/lookup/hot_hit_034_tests.rs
e9c6ab0c2be9f96573f826e6a1b9e4c2b8b526f779e72e95a6463ae07e45ab55 src/cache/lookup/insert.rs e9c6ab0c2be9f96573f826e6a1b9e4c2b8b526f779e72e95a6463ae07e45ab55 src/cache/lookup/insert.rs
7f02dc8eeb6123cb22307b76985b606afda2d87b7f845894685cfe4a9aee8b2a src/cache/lookup/tiered.rs 7f02dc8eeb6123cb22307b76985b606afda2d87b7f845894685cfe4a9aee8b2a src/cache/lookup/tiered.rs
@@ -326,28 +327,28 @@ b96e30ef2b12ad7647915457f087a4d11b6141847bc5e971a75c2739c88c3d8c src/cache/mode
f52123b588205ae96c7cded93569e99a1b4db4b361e1f7a91fc703afa6b6f6e5 src/cache/model/entry.rs f52123b588205ae96c7cded93569e99a1b4db4b361e1f7a91fc703afa6b6f6e5 src/cache/model/entry.rs
ff1c3a1a3bc0d26218dcc943ef951c2a212c180bbf6e4bf1c542ffbf12a9f150 src/cache/model/http_invalidation.rs ff1c3a1a3bc0d26218dcc943ef951c2a212c180bbf6e4bf1c542ffbf12a9f150 src/cache/model/http_invalidation.rs
ee9508498d420a726abee1d12ca21fd5117c1908ff53c2ce29f01de4380f26ac src/cache/model/maintenance.rs ee9508498d420a726abee1d12ca21fd5117c1908ff53c2ce29f01de4380f26ac src/cache/model/maintenance.rs
723c6f0580db70b1ef40269b28a4aadc89f024b25be00afe73a971950663d252 src/cache/model/settings.rs 38acaba0131b32d2f2174dd708439df654934fe9106504917954a66d1d7e5e6d src/cache/model/settings.rs
14f3f8ae5252a2dc13fcf9556523ffb945b77ee92a5fb4939cd27e113fc61660 src/cache/model/views.rs 14f3f8ae5252a2dc13fcf9556523ffb945b77ee92a5fb4939cd27e113fc61660 src/cache/model/views.rs
e26dce02df9c043329ca164fed215d271781bce8fb03240f528331bf3df6b9d7 src/cache/policy.rs e26dce02df9c043329ca164fed215d271781bce8fb03240f528331bf3df6b9d7 src/cache/policy.rs
70c255e15f2b8c030988a2263b0e07b5c16e7e26770c72becbcec6da4550fcd9 src/cache/policy/request.rs 70c255e15f2b8c030988a2263b0e07b5c16e7e26770c72becbcec6da4550fcd9 src/cache/policy/request.rs
6ae7a79886dbee537d15830c4647f59f158a2b31425d1c0f06218590c261c968 src/cache/policy/response.rs 5ce9e2b6cc031b4aea533c4b7375417496e998b17e1ab3cd4f6058e8da503a4b src/cache/policy/response.rs
12a6d5d9d6711e956391b4b952e8149658b8bf64cfcc0a4c59c281f2630a6aac src/cache/policy/scope_tests.rs 12a6d5d9d6711e956391b4b952e8149658b8bf64cfcc0a4c59c281f2630a6aac src/cache/policy/scope_tests.rs
c3a4300f7cdb6b2b7c16fbc489d49ba2451331bb4a55a2d9f618c684fd61eecb src/cache/range.rs c3a4300f7cdb6b2b7c16fbc489d49ba2451331bb4a55a2d9f618c684fd61eecb src/cache/range.rs
e1d3f0512d6290b0ac4998f47398a0a7c404db249c5bbdbc4ad4767071013a72 src/cache/stats.rs e1d3f0512d6290b0ac4998f47398a0a7c404db249c5bbdbc4ad4767071013a72 src/cache/stats.rs
8a300f5dad44815401eb23f4f5a157893761852ba4f3005041e0eb3f16892ce3 src/cache/tests.rs 8a300f5dad44815401eb23f4f5a157893761852ba4f3005041e0eb3f16892ce3 src/cache/tests.rs
98af70ada3b14f3ecc94c9c76b028c1b0de1ffd43d9836a0749de763f64d5402 src/cache/tickets.rs ce3d62de66b9dc73c81320478b3090408bbad94e7dbe2885576240fc9270dfed src/cache/tickets.rs
a1132b980833b1b4ce2bba9aa681ff255a0e2d61faaa65a316860da503ea1e27 src/cache/v022_cache_tests.rs a1132b980833b1b4ce2bba9aa681ff255a0e2d61faaa65a316860da503ea1e27 src/cache/v022_cache_tests.rs
4ba0a2e1c21a79cf68bf34cca1f2c2a3d7b20aef38f3b9f510743e60bc1d425f src/cache/varnish_compatible_tests.rs 4ba0a2e1c21a79cf68bf34cca1f2c2a3d7b20aef38f3b9f510743e60bc1d425f src/cache/varnish_compatible_tests.rs
1a2eb6684d9ea4a7e90970d936c75f7c8bbec46a4181fdf273be9bdac8eec847 src/compression/mod.rs 1a2eb6684d9ea4a7e90970d936c75f7c8bbec46a4181fdf273be9bdac8eec847 src/compression/mod.rs
8de82d9188c9c337690c6ec4afdad9fff20651c358e34602a93d8bb8c3d2489a src/compression/tests.rs 8de82d9188c9c337690c6ec4afdad9fff20651c358e34602a93d8bb8c3d2489a src/compression/tests.rs
e695079333ebdc2a6e7b3337cfabef171b3a0a01da0597b77feff70d417f833e src/config.rs e695079333ebdc2a6e7b3337cfabef171b3a0a01da0597b77feff70d417f833e src/config.rs
2c4312dd7410dc231ed204d7d201d716eedb41045fd95df7cbb51d5561540d99 src/config/backend.rs 9f138f86a8d73dfc2d28a2c66d131751c8c85af9c9ccdf22938736973c963678 src/config/backend.rs
6ad644fc4ae821d2c30329ace36652a7ac12d674e71e7dbdcdda3554c983f6f9 src/config/compile.rs 6ad644fc4ae821d2c30329ace36652a7ac12d674e71e7dbdcdda3554c983f6f9 src/config/compile.rs
fbea01fea1213d98aea812c1b78d5db289e09200b98ae514d742235cd3fa70e1 src/config/internal_routing.rs fbea01fea1213d98aea812c1b78d5db289e09200b98ae514d742235cd3fa70e1 src/config/internal_routing.rs
cc75698be2828000e88f2c482bd26f2fa4050e3432bec1b5a581dca17fef4a37 src/config/model.rs cc75698be2828000e88f2c482bd26f2fa4050e3432bec1b5a581dca17fef4a37 src/config/model.rs
75839458ddbc7480ec4492a95eeb67b617ba7d62f7b67e7a767a3453b74dbcbc src/config/route_policy_tests.rs 75839458ddbc7480ec4492a95eeb67b617ba7d62f7b67e7a767a3453b74dbcbc src/config/route_policy_tests.rs
e442c6561723e8dbf73d25912227cebf7aa84ac1523e8bda8e6b4bdad1b674df src/config/routing.rs e442c6561723e8dbf73d25912227cebf7aa84ac1523e8bda8e6b4bdad1b674df src/config/routing.rs
5bc35ebca158aa399a1d2273c21130e1cfaf5c89cb401689ef863d7deca6b801 src/config/snapshot.rs b33c45184e6451e15ce238cc922d2116269a1bf1d72093c2cdc068c4386a35bd src/config/snapshot.rs
477cddaa1e1c0b9b605a2f550971a04cf30287519cba70cfbb2e1860e3a93ff2 src/config/tests.rs 477cddaa1e1c0b9b605a2f550971a04cf30287519cba70cfbb2e1860e3a93ff2 src/config/tests.rs
f35de0ee1516ece6bbfd2b2a7db7d1e0f5048b0ec47cf2a3765dffb799abfd93 src/config/transport_tests.rs f35de0ee1516ece6bbfd2b2a7db7d1e0f5048b0ec47cf2a3765dffb799abfd93 src/config/transport_tests.rs
a09f361d70510d6226f4dd6785e649c3dc01ccafc70a4c8b0dcd098e224129b7 src/db.rs a09f361d70510d6226f4dd6785e649c3dc01ccafc70a4c8b0dcd098e224129b7 src/db.rs
@@ -357,16 +358,16 @@ c290d0cb02dfa5d2c60ecabac1cfb20c8884e3d14cca1944906c6d8e677f1784 src/db/api_tok
2ff744223934685439374572b83fd639bb3eabdbac168bea4a6501a6e9ce0ee0 src/db/history.rs 2ff744223934685439374572b83fd639bb3eabdbac168bea4a6501a6e9ce0ee0 src/db/history.rs
c05a02618d476e4670b4f9930555f199cac776cdf09edb8fcc1a675695b8f99f src/db/tests.rs c05a02618d476e4670b4f9930555f199cac776cdf09edb8fcc1a675695b8f99f src/db/tests.rs
dada314e1709e960eb2e225b4d10c3cd12606c62c4bc2c0220aec1ba133e4ade src/demo.rs dada314e1709e960eb2e225b4d10c3cd12606c62c4bc2c0220aec1ba133e4ade src/demo.rs
9c6b538245b2bcdbef851800ef87d11a6c370de5451488a08d9fab6d67fbbac6 src/diagnostics.rs d2ef07d1565569f520a6a8eb01f4ea9bb673499d038018d52e65fe95e800bb27 src/diagnostics.rs
045593a42a6e4f632e5052c655788f0843faa313fc929265b89509f6dfff91ed src/docker.rs 045593a42a6e4f632e5052c655788f0843faa313fc929265b89509f6dfff91ed src/docker.rs
9a68e0d5bd98541314f1ab5a4c2e344e9837f315bd39ea3988ffab6a0f0425ab src/docker/client.rs 9a68e0d5bd98541314f1ab5a4c2e344e9837f315bd39ea3988ffab6a0f0425ab src/docker/client.rs
8feba6d9eb7fde798cbe8a9e339d6a5346cfef039980fce46727386717635eec src/docker/model.rs 8feba6d9eb7fde798cbe8a9e339d6a5346cfef039980fce46727386717635eec src/docker/model.rs
f095e5e3c564df409874019c647f7cec2156bf5a845b5b84864879fd07cf80ce src/docker/reconcile.rs f095e5e3c564df409874019c647f7cec2156bf5a845b5b84864879fd07cf80ce src/docker/reconcile.rs
2e8d4841665cc1d29e6262a1789bb64ced1cecd0036681cc1ef7b5d5bfe22065 src/docker/tests.rs 2e8d4841665cc1d29e6262a1789bb64ced1cecd0036681cc1ef7b5d5bfe22065 src/docker/tests.rs
0b6831d03841a1dd2d6ea73d8268d0b3fda678c6d9332447e269f1e45c60c0d3 src/forward_auth/mod.rs 34eb55a240928e98bd34e790ac63096b9ca8bf7a3008676dbbd11e1a392b8541 src/forward_auth/mod.rs
6c25951cf69e972c47cdf815f22a5062916b2b768e39d55a26a5fc9e625a2344 src/forward_auth/tests.rs 6c25951cf69e972c47cdf815f22a5062916b2b768e39d55a26a5fc9e625a2344 src/forward_auth/tests.rs
5d32cf9177a33d6fb50452541805b10ae1b0833e709d833f5bd1fc6907193ffb src/forwarding.rs 5d32cf9177a33d6fb50452541805b10ae1b0833e709d833f5bd1fc6907193ffb src/forwarding.rs
b266c0a464bb0c6b581e423d08304a0121f126e8e42ef3a8469cadad42810fdf src/forwarding/peer_formatting.rs 68daa00176a0b47d49ccb3af15791690b38d3e9eea2be36ba0ee5ec7f0fcec42 src/forwarding/peer_formatting.rs
b821e2efd2939cb13d01e402532f33feba6682acb6a7a3223be086c1cd171820 src/forwarding/tests.rs b821e2efd2939cb13d01e402532f33feba6682acb6a7a3223be086c1cd171820 src/forwarding/tests.rs
0541ad50b7bde1fd958fca59d2a4f9a0fde197bcaef68e6bf892ffdeaac46be2 src/geoip.rs 0541ad50b7bde1fd958fca59d2a4f9a0fde197bcaef68e6bf892ffdeaac46be2 src/geoip.rs
e3646c044604bbc169aeba549d973d37b8ac52f88bbab4a0ef74c6ea69681eaa src/geoip/mmdb.rs e3646c044604bbc169aeba549d973d37b8ac52f88bbab4a0ef74c6ea69681eaa src/geoip/mmdb.rs
@@ -408,9 +409,9 @@ c19998732c2d65b464f43d17a58644db5bcf7b4a6dd4c31a65a091c57692a5bb src/oidc/mod.r
c4dbe0f8ded237d45b6a3977bdc7544296e4408996815cae9e28af028419d0b7 src/oidc/tests.rs c4dbe0f8ded237d45b6a3977bdc7544296e4408996815cae9e28af028419d0b7 src/oidc/tests.rs
992f49379e44361d98088d8f8ca1013d62846a54a5a35c089a5e0e1a9488e08d src/oidc/token.rs 992f49379e44361d98088d8f8ca1013d62846a54a5a35c089a5e0e1a9488e08d src/oidc/token.rs
eb95c68c8639ce3ae658eea3334795cdb5e129fb2ba10955f7dffe06d8909b57 src/pages/mod.rs eb95c68c8639ce3ae658eea3334795cdb5e129fb2ba10955f7dffe06d8909b57 src/pages/mod.rs
86008e469c84a7242b7d4947e2116694ababa2cb26120f246d00923a7c019720 src/pages/tests.rs d09dc18863b0508a5e8d54a5305904170f3e8f12857b7a0d5393a11849541e50 src/pages/tests.rs
5650a3f70162abd97021e0a51a92cac2c4dd39004426ef99dff36cbb0741aeac src/prometheus.rs 5650a3f70162abd97021e0a51a92cac2c4dd39004426ef99dff36cbb0741aeac src/prometheus.rs
6c97eb9c593a7802422100e8130921e63b6b9b1b0387b6860af9f49581cd50b3 src/prometheus/core.rs b9d71633703469d1d14e89c4d8a7698f4d354e5111cfe6ed3b2587566d0e2333 src/prometheus/core.rs
2321ee667569803dfe6c92c0e98adba0ac8fcdc6a3eacd5857233f1acae93daf src/prometheus/docker_misc.rs 2321ee667569803dfe6c92c0e98adba0ac8fcdc6a3eacd5857233f1acae93daf src/prometheus/docker_misc.rs
cc909edf740a091f9057912f555dd888b644fa1cb49647fe0e2e1f5dba8ff5af src/prometheus/json.rs cc909edf740a091f9057912f555dd888b644fa1cb49647fe0e2e1f5dba8ff5af src/prometheus/json.rs
f9aab3297fcf647295849f77cbf99197f4dd27efd6817e54c03451e04972430c src/prometheus/model.rs f9aab3297fcf647295849f77cbf99197f4dd27efd6817e54c03451e04972430c src/prometheus/model.rs
@@ -435,7 +436,7 @@ d0d64fd4dd89efd4a9fe089ff4551815cbeaf5b883ddcff803f1b13b5e918dc2 src/proxy/forw
ccc54869ffbcf1edae9ddb96f6dc2191739f8804ee0318b7a6844def6c4693e5 src/proxy/forward/success_prepare.rs ccc54869ffbcf1edae9ddb96f6dc2191739f8804ee0318b7a6844def6c4693e5 src/proxy/forward/success_prepare.rs
c965dba1d2639b338f3f3cfc17f760786a30abab708dfb7920ea3bc94d4d734b src/proxy/forward/upstream.rs c965dba1d2639b338f3f3cfc17f760786a30abab708dfb7920ea3bc94d4d734b src/proxy/forward/upstream.rs
5d72200f3b5308215b7a3d7b1f910932eaa4712f9e2954afdfbe2f252e2a2d3c src/proxy/forward/web_source.rs 5d72200f3b5308215b7a3d7b1f910932eaa4712f9e2954afdfbe2f252e2a2d3c src/proxy/forward/web_source.rs
602879a0f8f475fe63b5e27238f4b371277a6780aa970c6b140c8d7fd3074e37 src/proxy/forward_entry.rs 5f243b21fd719a0c0bc6cdb359b0272c6c11cd9e7c7152d94787cae5476b61bd src/proxy/forward_entry.rs
9b25f5e3ea4443a63ee6f45d12e1957dd316fe8e85325fc00ea416faaab1025a src/proxy/h2_graceful.rs 9b25f5e3ea4443a63ee6f45d12e1957dd316fe8e85325fc00ea416faaab1025a src/proxy/h2_graceful.rs
30057732c35c369740a176f7da285e35a4157571a04328f6c6f1494d4ec8550b src/proxy/headers/authority_tests.rs 30057732c35c369740a176f7da285e35a4157571a04328f6c6f1494d4ec8550b src/proxy/headers/authority_tests.rs
e87437ccf80e50e0159d204dfdd1f8dc0739b6e375be192fce0af749810258cf src/proxy/headers/mod.rs e87437ccf80e50e0159d204dfdd1f8dc0739b6e375be192fce0af749810258cf src/proxy/headers/mod.rs
@@ -451,8 +452,8 @@ c3887e344249ce492862c365896731127b7b6fc51bbfda274d7e7aad3a70be0c src/proxy/web_
5522456cc85948228250feac92308d53b130c4a7af98d24f669aca4dfe6999b6 src/proxy/web_cache_revalidation/tests.rs 5522456cc85948228250feac92308d53b130c4a7af98d24f669aca4dfe6999b6 src/proxy/web_cache_revalidation/tests.rs
3d591201595a3b1e928ded20a8736477a47e5acc3bcbde158f7c17547ce155ba src/proxy/websocket.rs 3d591201595a3b1e928ded20a8736477a47e5acc3bcbde158f7c17547ce155ba src/proxy/websocket.rs
aa58bacc35458ac615680e774a235191f80abf0db978b68095571fce94c2b502 src/proxy/websocket/handshake.rs aa58bacc35458ac615680e774a235191f80abf0db978b68095571fce94c2b502 src/proxy/websocket/handshake.rs
c02d42b5c7eeedbc54c58e98691f7d8e39f8e9be63a48f6d6b4ad6b8ea431b7e src/proxy/websocket/http2_tests.rs 02eae55007334a289c974c0a050ebe9360822574e5982950245a18e1af05a9fc src/proxy/websocket/http2_tests.rs
faedf6cc2bd4c42f342a0aa188b634d6b8d280ca0e121032977eadaecb88e9d7 src/proxy/websocket/http3_tests.rs 15ff0584479e176f44fea61ea9c37c0f22606895fe7cbea8ec87bebd5284c7b7 src/proxy/websocket/http3_tests.rs
7e9c4d4c207badabf90630223964bb2ad641f06d4149843cab751f192b8b4adf src/proxy/websocket/tests.rs 7e9c4d4c207badabf90630223964bb2ad641f06d4149843cab751f192b8b4adf src/proxy/websocket/tests.rs
a44421714aab37f7ceda19e3ec2512c645d2a3ce5dcb3da5bc7a0d87a442d6a8 src/proxy/websocket/tunnel.rs a44421714aab37f7ceda19e3ec2512c645d2a3ce5dcb3da5bc7a0d87a442d6a8 src/proxy/websocket/tunnel.rs
999cbf85cb6e917db529e30ba4a5eed1cc9e95a054da7b3039c730bb80061449 src/proxy/websocket/validation.rs 999cbf85cb6e917db529e30ba4a5eed1cc9e95a054da7b3039c730bb80061449 src/proxy/websocket/validation.rs
@@ -498,12 +499,12 @@ e0676801259fb09a73825d959b89630bfa0b6f1fd1efd3006af29f60537a2fdf src/system_sta
df98becda799b6bf4ff5e2fec7d0e0a486e6b7eca6f9f5ad49ffdafd49a4f498 src/tls.rs df98becda799b6bf4ff5e2fec7d0e0a486e6b7eca6f9f5ad49ffdafd49a4f498 src/tls.rs
2af2164ecdf2b7fd9eef7c6e358dec053dfac9aedaa501dbb75df742d18638b2 src/tls/acme_tests.rs 2af2164ecdf2b7fd9eef7c6e358dec053dfac9aedaa501dbb75df742d18638b2 src/tls/acme_tests.rs
93e0576ac9077e645a72d164bad34cd75fe6032dd0516284d08bddd2fbda4b7c src/tls/certificates.rs 93e0576ac9077e645a72d164bad34cd75fe6032dd0516284d08bddd2fbda4b7c src/tls/certificates.rs
68cc35b3cae43b0321cf667a613310a4b9ec5d8994461d6f3543b1852771bf08 src/tls/external_paths.rs 5703876c4697512f714e9813e56acb6f852c921140b16d135feee3d0dd21e520 src/tls/external_paths.rs
937b95bb8621ab98d7cad3d223e4dee305350fbd0149b7527396d3bdcc28a68b src/tls/external_paths_tests.rs 937b95bb8621ab98d7cad3d223e4dee305350fbd0149b7527396d3bdcc28a68b src/tls/external_paths_tests.rs
1ff95e95d38eedeefcf8a931569d628763d5a328aa5573c73c928f06795037ba src/tls/server_config.rs 1ff95e95d38eedeefcf8a931569d628763d5a328aa5573c73c928f06795037ba src/tls/server_config.rs
94b70c52fdc7bd6e32fba9eed9124d030c692246f6990d61e01b891765275add src/tls/tests.rs 688b929615ebc1d873461237c3de6dfa169984230266a59aad78d93f0eaa2cd6 src/tls/tests.rs
ce51a3280331d67c8406d2c5bc156ac0b492afa58e1f328bcda76db0720a6a9d src/tls/types.rs ce51a3280331d67c8406d2c5bc156ac0b492afa58e1f328bcda76db0720a6a9d src/tls/types.rs
a897c48e3528565fef8d4e32f0c3c078adfb68655c80e1af011f44a8bcfac6f6 src/traffic/mod.rs 58f01b9a6dd1095877cc71445b3ccd651d8b2a1a9c0ad543547840834ec1d91b src/traffic/mod.rs
bd62b4ecdcf831d183c7720766a11703c8612d614e3abf38d2bff28df34710fd src/traffic/tests.rs bd62b4ecdcf831d183c7720766a11703c8612d614e3abf38d2bff28df34710fd src/traffic/tests.rs
bfbbaa3b949ae946fa929bfbae3fd9a25f2ce792abbd0ed370ccf5683250bc4d src/transform/apply.rs bfbbaa3b949ae946fa929bfbae3fd9a25f2ce792abbd0ed370ccf5683250bc4d src/transform/apply.rs
ae30a984fa39d5c45a676effbc880c923078ba8120adff1bff10e108350b31b5 src/transform/compile.rs ae30a984fa39d5c45a676effbc880c923078ba8120adff1bff10e108350b31b5 src/transform/compile.rs
@@ -552,7 +553,7 @@ a484113f2c1fea4e98a437acd8a28be6490d7e612f160a8728ceeaadd363f8ee src/web_server
e9ba6e09ba2c8f8a33410c219d5ea29214cc53e279a43b17c110d24f17a5a71d src/web_server/request.rs e9ba6e09ba2c8f8a33410c219d5ea29214cc53e279a43b17c110d24f17a5a71d src/web_server/request.rs
d98c80663847d6d83073df074ebff65952755415574708abcc153bd8d3adfaa9 src/web_server/response.rs d98c80663847d6d83073df074ebff65952755415574708abcc153bd8d3adfaa9 src/web_server/response.rs
b38a1c9e648e3606a534ebdf74f6315863a326c6de1ca8edc433abe9c1fed7b4 src/web_server/response/file.rs b38a1c9e648e3606a534ebdf74f6315863a326c6de1ca8edc433abe9c1fed7b4 src/web_server/response/file.rs
4733bb1824dd0a12b8804fb8d00791c3f9fda4c09b41503d5d8079d9607df058 src/web_server/response/file_body/mod.rs df87b67c75bbe58d7e077ed4c4cfa105ec680e89dd0c25af7dbd793d418a01ad src/web_server/response/file_body/mod.rs
e5f5fbe702f66bfcbcbc1d1f3cd3d89bdbb929bb86aed94868a2a06a4b9e59e3 src/web_server/response/file_body/tests.rs e5f5fbe702f66bfcbcbc1d1f3cd3d89bdbb929bb86aed94868a2a06a4b9e59e3 src/web_server/response/file_body/tests.rs
7da903c1a69be0ae6b85772b3208c518679efa8ed9d3e98adb694483518156b6 src/web_server/response/headers.rs 7da903c1a69be0ae6b85772b3208c518679efa8ed9d3e98adb694483518156b6 src/web_server/response/headers.rs
2665d4eac9a6430d68224874784cb65d9b9d424353af77735caf044401d08cb4 src/web_server/routing.rs 2665d4eac9a6430d68224874784cb65d9b9d424353af77735caf044401d08cb4 src/web_server/routing.rs
@@ -561,9 +562,9 @@ b791762b4ab7a43c3be19df84e523faf3dfb4df055f66d02178f93edb60d2204 src/web_server
714418c86ab241020a561fcf662a8fe324a6a70cc2fd3a313c56d7939c14b9e8 src/web_server/tests.rs 714418c86ab241020a561fcf662a8fe324a6a70cc2fd3a313c56d7939c14b9e8 src/web_server/tests.rs
f00743af2cf4a5952caef7a1dcf68e7817b5599a252a9973e1c22045517c0b0e tests/fixtures/argon2id.json f00743af2cf4a5952caef7a1dcf68e7817b5599a252a9973e1c22045517c0b0e tests/fixtures/argon2id.json
102b83171a6429b6375d8001d6c5294702385cd58ee55727cb657b1f3566e877 tests/panel_icons_browser.py 102b83171a6429b6375d8001d6c5294702385cd58ee55727cb657b1f3566e877 tests/panel_icons_browser.py
52e61c389a60d147184ee76925cac76e454f8f2e3cb91039bb06e17787ab900f tests/panel_icons_contract.js c4030b7d224553312012b1f25153dd3545e83de7bccff2894c0e8f7e0e84b721 tests/panel_icons_contract.js
01af3008be27fabb486c141c67099af104f2725cc56f4c656aea9741a3c150cd tests/panel_interface_contract.py 01af3008be27fabb486c141c67099af104f2725cc56f4c656aea9741a3c150cd tests/panel_interface_contract.py
626db3f4a7d83c0925a911db5ee6ce4b77048b7c2a9b91cd2d5d7dfa548cddd5 tests/panel_refresh_contract.js 87417f12a236efb7b20b10ebdcd92f2f862a86c7d829e7f8afb4f8a460c30f33 tests/panel_refresh_contract.js
e24a0f84f1bfc46964df865ac387d651f7b384d781cbc8f3a9a5b0b60f3d69ca tests/vhost_auth_selection.js e24a0f84f1bfc46964df865ac387d651f7b384d781cbc8f3a9a5b0b60f3d69ca tests/vhost_auth_selection.js
cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 third_party/swagger-ui-5.33.1/LICENSE cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 third_party/swagger-ui-5.33.1/LICENSE
0d20d1adef18aee3f40dd258172155521ce702ac445cb5f7b7d60ed32dad2fb2 third_party/swagger-ui-5.33.1/NOTICE 0d20d1adef18aee3f40dd258172155521ce702ac445cb5f7b7d60ed32dad2fb2 third_party/swagger-ui-5.33.1/NOTICE
+2 -2
View File
@@ -77,10 +77,10 @@ ENV_FILE=integrations/routeros-oci-build/.env \
bash integrations/routeros-oci-build/build-routeros-oci.sh push bash integrations/routeros-oci-build/build-routeros-oci.sh push
``` ```
For example, with RustProxy version `0.36.10` and the defaults this publishes: For example, with RustProxy version `0.36.11` and the defaults this publishes:
```text ```text
zot.example.net/network/rustproxy:0.36.10 zot.example.net/network/rustproxy:0.36.11
zot.example.net/network/rustproxy:latest zot.example.net/network/rustproxy:latest
zot.example.net/network/rustproxy:routeros zot.example.net/network/rustproxy:routeros
``` ```
+9
View File
@@ -2306,6 +2306,15 @@
"backendEventFailed": "Failed", "backendEventFailed": "Failed",
"backendEventRecovered": "Recovered", "backendEventRecovered": "Recovered",
"backendEventNone": "No failures", "backendEventNone": "No failures",
"backendColRate": "Req/s",
"backendColStatuses": "HTTP codes",
"backendColP95": "p95 / p99",
"backendColCache": "Cache",
"backendColProbe": "Probe",
"backendColCauses": "Error causes",
"backendColProtocol": "Protocol",
"backendVhostScope": "Whole vhost (all backends)",
"backendBucketBound": "histogram bucket upper bound",
"backendAgo": "{value} ago", "backendAgo": "{value} ago",
"backendDetailsToggle": "Show client generation details", "backendDetailsToggle": "Show client generation details",
"processOverviewTitle": "Process", "processOverviewTitle": "Process",
+9
View File
@@ -2306,6 +2306,15 @@
"backendEventFailed": "Awaria", "backendEventFailed": "Awaria",
"backendEventRecovered": "Przywrócony", "backendEventRecovered": "Przywrócony",
"backendEventNone": "Brak awarii", "backendEventNone": "Brak awarii",
"backendColRate": "Żąd./s",
"backendColStatuses": "Kody HTTP",
"backendColP95": "p95 / p99",
"backendColCache": "Cache",
"backendColProbe": "Sonda",
"backendColCauses": "Przyczyny błędów",
"backendColProtocol": "Protokół",
"backendVhostScope": "Cały vhost (wszystkie backendy)",
"backendBucketBound": "górna granica przedziału histogramu",
"backendAgo": "{value} temu", "backendAgo": "{value} temu",
"backendDetailsToggle": "Pokaż szczegóły generacji klienta", "backendDetailsToggle": "Pokaż szczegóły generacji klienta",
"processOverviewTitle": "Proces", "processOverviewTitle": "Proces",
+18 -2
View File
@@ -2,7 +2,7 @@
"openapi": "3.0.3", "openapi": "3.0.3",
"info": { "info": {
"title": "RustProxy Administration API", "title": "RustProxy Administration API",
"version": "0.36.10", "version": "0.36.11",
"description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust. The API also permits explicitly scoped GeneralApiBearer tokens on a narrow route allowlist, with RO/RW and source CIDR checks; API-token management and Prometheus-token management are browser-session-only." "description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust. The API also permits explicitly scoped GeneralApiBearer tokens on a narrow route allowlist, with RO/RW and source CIDR checks; API-token management and Prometheus-token management are browser-session-only."
}, },
"servers": [ "servers": [
@@ -4268,7 +4268,7 @@
"backend_table_columns": { "backend_table_columns": {
"type": "array", "type": "array",
"minItems": 1, "minItems": 1,
"maxItems": 14, "maxItems": 21,
"uniqueItems": true, "uniqueItems": true,
"items": { "items": {
"type": "string", "type": "string",
@@ -4281,6 +4281,13 @@
"errors", "errors",
"latency", "latency",
"traffic", "traffic",
"rate",
"statuses",
"p95",
"cache",
"probe",
"causes",
"protocol",
"generation", "generation",
"generationRequests", "generationRequests",
"generationAge", "generationAge",
@@ -4298,6 +4305,8 @@
"errors", "errors",
"latency", "latency",
"traffic", "traffic",
"statuses",
"cache",
"generation", "generation",
"generationRequests", "generationRequests",
"generationAge", "generationAge",
@@ -4318,6 +4327,13 @@
"errors", "errors",
"latency", "latency",
"traffic", "traffic",
"rate",
"statuses",
"p95",
"cache",
"probe",
"causes",
"protocol",
"generation", "generation",
"generationRequests", "generationRequests",
"generationAge", "generationAge",
+1
View File
@@ -84,6 +84,7 @@ fn default_file_log_columns() -> Vec<String> {
fn default_backend_table_columns() -> Vec<String> { fn default_backend_table_columns() -> Vec<String> {
BACKEND_TABLE_COLUMNS BACKEND_TABLE_COLUMNS
.iter() .iter()
.filter(|value| !BACKEND_TABLE_OPTIONAL_COLUMNS.contains(value))
.map(|value| (*value).to_owned()) .map(|value| (*value).to_owned())
.collect() .collect()
} }
+14
View File
@@ -23,6 +23,13 @@ const BACKEND_TABLE_COLUMNS: &[&str] = &[
"errors", "errors",
"latency", "latency",
"traffic", "traffic",
"rate",
"statuses",
"p95",
"cache",
"probe",
"causes",
"protocol",
"generation", "generation",
"generationRequests", "generationRequests",
"generationAge", "generationAge",
@@ -30,6 +37,13 @@ const BACKEND_TABLE_COLUMNS: &[&str] = &[
"lastFailure", "lastFailure",
"lastRecovery", "lastRecovery",
]; ];
const BACKEND_TABLE_OPTIONAL_COLUMNS: &[&str] = &[
"rate",
"p95",
"probe",
"causes",
"protocol",
];
const VHOST_LIST_SORT_COLUMNS: &[&str] = &[ const VHOST_LIST_SORT_COLUMNS: &[&str] = &[
"id", "id",
"hostname", "hostname",
+1
View File
@@ -174,6 +174,7 @@ fn status_payload(state: &App) -> Value {
.iter() .iter()
.map(|(id, metrics)| (id.clone(), metrics.view())) .map(|(id, metrics)| (id.clone(), metrics.view()))
.collect::<std::collections::BTreeMap<_, _>>()); .collect::<std::collections::BTreeMap<_, _>>());
payload["vhost_cache_totals"] = json!(state.cache.scope_totals());
let mut backend_pool_domains = std::collections::BTreeMap::new(); let mut backend_pool_domains = std::collections::BTreeMap::new();
for agent in &snapshot.envelope.config.docker_agents.agents { for agent in &snapshot.envelope.config.docker_agents.agents {
for assignment in agent for assignment in agent
+1
View File
@@ -61,6 +61,7 @@ fn live_status_payload(state: &App) -> Value {
"backends": snapshot.envelope.config.vhosts.iter().map(|v| v.backends.len()).sum::<usize>(), "backends": snapshot.envelope.config.vhosts.iter().map(|v| v.backends.len()).sum::<usize>(),
"backend_pools": snapshot.pools.iter().map(|p| p.view()).collect::<Vec<_>>(), "backend_pools": snapshot.pools.iter().map(|p| p.view()).collect::<Vec<_>>(),
"vhost_metrics": snapshot.vhost_metrics.iter().map(|(id, metrics)| (id.clone(), metrics.view())).collect::<std::collections::BTreeMap<_, _>>(), "vhost_metrics": snapshot.vhost_metrics.iter().map(|(id, metrics)| (id.clone(), metrics.view())).collect::<std::collections::BTreeMap<_, _>>(),
"vhost_cache_totals": state.cache.scope_totals(),
"streams": {"metrics": stream_metrics}, "streams": {"metrics": stream_metrics},
"logging": {"sinks": state.logs.stats(), "error_file": crate::logging::error_log_stats()} "logging": {"sinks": state.logs.stats(), "error_file": crate::logging::error_log_stats()}
}) })
+13 -3
View File
@@ -2155,8 +2155,18 @@ mod tests {
); );
assert_eq!( assert_eq!(
initial["backend_table_columns"].as_array().unwrap().len(), initial["backend_table_columns"].as_array().unwrap().len(),
BACKEND_TABLE_COLUMNS.len() BACKEND_TABLE_COLUMNS.len() - BACKEND_TABLE_OPTIONAL_COLUMNS.len()
); );
assert!(initial["backend_table_columns"]
.as_array()
.unwrap()
.iter()
.any(|column| column == "cache"));
assert!(!initial["backend_table_columns"]
.as_array()
.unwrap()
.iter()
.any(|column| column == "probe"));
assert_eq!(initial["backend_table_sort"], "vhost"); assert_eq!(initial["backend_table_sort"], "vhost");
assert_eq!(initial["backend_table_descending"], false); assert_eq!(initial["backend_table_descending"], false);
@@ -2193,8 +2203,8 @@ mod tests {
json!(default_file_log_columns()) json!(default_file_log_columns())
); );
assert_eq!( assert_eq!(
legacy["backend_table_columns"].as_array().unwrap().len(), legacy["backend_table_columns"],
BACKEND_TABLE_COLUMNS.len() json!(default_backend_table_columns())
); );
assert_eq!(legacy["backend_table_sort"], "vhost"); assert_eq!(legacy["backend_table_sort"], "vhost");
assert_eq!(legacy["backend_table_descending"], false); assert_eq!(legacy["backend_table_descending"], false);
+32
View File
@@ -2848,3 +2848,35 @@ html[data-table-density=ultra] .mb-bar { height: 16px; }
.bo-traffic-text .bo-out b { color: var(--green); font-weight: 500; } .bo-traffic-text .bo-out b { color: var(--green); font-weight: 500; }
.bo-traffic-text b.is-zero { color: var(--text-subtle); } .bo-traffic-text b.is-zero { color: var(--text-subtle); }
html[data-table-density=ultra] .bo-traffic-text { line-height: 1.25; } html[data-table-density=ultra] .bo-traffic-text { line-height: 1.25; }
.bo-statuses { min-width: 150px; }
.bo-statuses small { display: inline-flex; gap: 6px; }
.bo-statuses small b { font-weight: 500; }
.bo-statuses small .is-warn { color: var(--accent); }
.bo-statuses small .is-bad { color: var(--red); }
.bo-stack { display: flex; gap: 1px; height: 3px; margin-top: 5px; overflow: hidden; background: var(--line); border-radius: var(--rp-progress-radius, 0); }
.bo-stack>i { display: block; flex: 1 1 0; height: 100%; }
.bo-stack>i.is-ok { background: var(--green); opacity: .8; }
.bo-stack>i.is-info { background: var(--blue); }
.bo-stack>i.is-warn { background: var(--accent); }
.bo-stack>i.is-bad { background: var(--red); }
.bo-pair { font-family: var(--mono); font-variant-numeric: tabular-nums; text-align: right; }
.bo-pair small { display: block; margin-top: 1px; color: var(--text-subtle); font-size: .9em; }
.bo-cache { min-width: 200px; }
.bo-cache .bo-chartcell { justify-content: flex-start; }
.bo-cache-hit { display: inline-flex; align-items: center; gap: 6px; min-width: 70px; }
.bo-ring { width: 20px; height: 20px; flex: 0 0 auto; }
.bo-ring circle { fill: none; stroke-width: 3; }
.bo-ring-track { stroke: var(--line); }
.bo-ring-value { stroke: var(--green); stroke-linecap: butt; }
.bo-traffic-text .bo-hit b { color: var(--green); font-weight: 500; }
.bo-traffic-text .bo-origin b { color: var(--text); font-weight: 500; }
.bo-causes { display: flex; flex-wrap: nowrap; align-items: center; gap: 4px; }
.bo-cause { display: inline-flex; align-items: center; gap: 5px; padding: 1px 6px; border: 1px solid var(--danger-line-strong); border-radius: var(--rp-radius-control, 0); color: var(--red); font: 11px var(--mono); }
.bo-cause b { font-weight: 600; }
.bo-cause.is-soft { border-color: var(--field-line); color: var(--muted); }
.bo-causes small { color: var(--text-subtle); font: 11px var(--mono); }
html[data-table-density=ultra] .bo-pair small { display: inline; margin-left: 6px; }
html[data-table-density=ultra] .bo-ring { width: 14px; height: 14px; }
html[data-table-density=ultra] .bo-stack { margin-top: 2px; }
html[data-table-density=compact] .bo-ring { width: 18px; height: 18px; }
html[data-table-density=compact] .bo-stack { margin-top: 3px; }
+21
View File
@@ -663,6 +663,20 @@
<label class="check"><input checked data-pool-column-toggle="traffic" <label class="check"><input checked data-pool-column-toggle="traffic"
type="checkbox" /><span type="checkbox" /><span
data-i18n="vhTraffic">Traffic/s</span></label> data-i18n="vhTraffic">Traffic/s</span></label>
<label class="check"><input data-pool-column-toggle="rate" type="checkbox" /><span
data-i18n="backendColRate">Req/s</span></label>
<label class="check"><input data-pool-column-toggle="statuses" type="checkbox" /><span
data-i18n="backendColStatuses">HTTP codes</span></label>
<label class="check"><input data-pool-column-toggle="p95" type="checkbox" /><span
data-i18n="backendColP95">p95 / p99</span></label>
<label class="check"><input data-pool-column-toggle="cache" type="checkbox" /><span
data-i18n="backendColCache">Cache</span></label>
<label class="check"><input data-pool-column-toggle="probe" type="checkbox" /><span
data-i18n="backendColProbe">Probe</span></label>
<label class="check"><input data-pool-column-toggle="causes" type="checkbox" /><span
data-i18n="backendColCauses">Error causes</span></label>
<label class="check"><input data-pool-column-toggle="protocol" type="checkbox" /><span
data-i18n="backendColProtocol">Protocol</span></label>
<label class="check"><input checked data-pool-column-toggle="generation" <label class="check"><input checked data-pool-column-toggle="generation"
type="checkbox" /><span data-i18n="vhClientGeneration">Client type="checkbox" /><span data-i18n="vhClientGeneration">Client
gen.</span></label> gen.</span></label>
@@ -700,6 +714,13 @@
<th data-i18n="vhErrors" data-pool-column="errors">Errors</th> <th data-i18n="vhErrors" data-pool-column="errors">Errors</th>
<th data-i18n="vhLatency" data-pool-column="latency">Latency</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="vhTraffic" data-pool-column="traffic">Traffic/s</th>
<th data-i18n="backendColRate" data-pool-column="rate">Req/s</th>
<th data-i18n="backendColStatuses" data-pool-column="statuses">HTTP codes</th>
<th data-i18n="backendColP95" data-pool-column="p95">p95 / p99</th>
<th data-i18n="backendColCache" data-pool-column="cache">Cache</th>
<th data-i18n="backendColProbe" data-pool-column="probe">Probe</th>
<th data-i18n="backendColCauses" data-pool-column="causes">Error causes</th>
<th data-i18n="backendColProtocol" data-pool-column="protocol">Protocol</th>
<th data-i18n="vhClientGeneration" data-pool-column="generation">Client gen. <th data-i18n="vhClientGeneration" data-pool-column="generation">Client gen.
</th> </th>
<th data-i18n="vhGenerationRequests" data-pool-column="generationRequests"> <th data-i18n="vhGenerationRequests" data-pool-column="generationRequests">
+1
View File
@@ -2,6 +2,7 @@
let basicEditor = null, locationsReader = () => [], aclControl = null, editing = null, revision = null, dirty = false, initialized = false, readBindings = () => [], readHosts = () => [], countryRatesReader = () => [], uaRatesReader = () => [], pagesEditor = null, stagedCertificates = [], uploading = false, draftEpoch = 0, backendHealthEditorControl = null; let basicEditor = null, locationsReader = () => [], aclControl = null, editing = null, revision = null, dirty = false, initialized = false, readBindings = () => [], readHosts = () => [], countryRatesReader = () => [], uaRatesReader = () => [], pagesEditor = null, stagedCertificates = [], uploading = false, draftEpoch = 0, backendHealthEditorControl = null;
const chartHistory = new Map(); const chartHistory = new Map();
const backendTrafficHistory = new Map(); const backendTrafficHistory = new Map();
const backendRequestHistory = new Map();
const vhostTrafficHistory = new Map(); const vhostTrafficHistory = new Map();
const dialog = $("vhost-dialog"); const dialog = $("vhost-dialog");
const form = $("vhost-form"), container = $("vhost-fields"); const form = $("vhost-form"), container = $("vhost-fields");
+127 -11
View File
@@ -151,9 +151,11 @@ document.addEventListener("scroll", event => {
if (activeVhostBackendPopup && !activeVhostBackendPopup.node.contains(event.target)) positionVhostBackendPopup(); if (activeVhostBackendPopup && !activeVhostBackendPopup.node.contains(event.target)) positionVhostBackendPopup();
}, true); }, true);
const poolColumns = ["vhost", "backend", "status", "active", "requests", "errors", "latency", "traffic", "generation", "generationRequests", "generationAge", "rotations", "lastFailure", "lastRecovery"]; const poolColumns = ["vhost", "backend", "status", "active", "requests", "errors", "latency", "traffic", "rate", "statuses", "p95", "cache", "probe", "causes", "protocol", "generation", "generationRequests", "generationAge", "rotations", "lastFailure", "lastRecovery"];
const poolOptionalColumns = ["rate", "p95", "probe", "causes", "protocol"];
const vhostSortColumns = ["id", "hostname", "status", "listeners", "certificate", "backends"]; const vhostSortColumns = ["id", "hostname", "status", "listeners", "certificate", "backends"];
const defaultPoolTablePrefs = () => ({ sort: "vhost", descending: false, columns: [...poolColumns] }); const defaultPoolColumns = () => poolColumns.filter(key => !poolOptionalColumns.includes(key));
const defaultPoolTablePrefs = () => ({ sort: "vhost", descending: false, columns: defaultPoolColumns() });
const defaultVhostListPrefs = () => ({ sort: "id", descending: false }); const defaultVhostListPrefs = () => ({ sort: "id", descending: false });
let poolTablePrefs = defaultPoolTablePrefs(), vhostListPrefs = defaultVhostListPrefs(), poolPreferenceSaveRunning = false, poolPreferenceSaveQueued = false; let poolTablePrefs = defaultPoolTablePrefs(), vhostListPrefs = defaultVhostListPrefs(), poolPreferenceSaveRunning = false, poolPreferenceSaveQueued = false;
function normalizePoolTablePrefs(preferences) { function normalizePoolTablePrefs(preferences) {
@@ -161,7 +163,7 @@ function normalizePoolTablePrefs(preferences) {
return { return {
sort: poolColumns.includes(preferences?.backend_table_sort) ? preferences.backend_table_sort : "vhost", sort: poolColumns.includes(preferences?.backend_table_sort) ? preferences.backend_table_sort : "vhost",
descending: preferences?.backend_table_descending === true, descending: preferences?.backend_table_descending === true,
columns: selected.length ? selected : [...poolColumns], columns: selected.length ? selected : defaultPoolColumns(),
}; };
} }
function normalizeVhostListPrefs(preferences) { function normalizeVhostListPrefs(preferences) {
@@ -256,7 +258,7 @@ function initPoolTableTools() {
})); }));
syncPoolColumns(); syncPoolSortDirection(); syncPoolColumns(); syncPoolSortDirection();
} }
const BO_SORT_LABELS = { vhost: "vhVhost", backend: "backend", status: "status", active: "vhActive", requests: "vhRequests", errors: "vhErrors", latency: "vhLatency", traffic: "vhTraffic", generation: "vhClientGeneration", generationRequests: "vhGenerationRequests", generationAge: "vhGenerationAge", rotations: "vhClientRotations", lastFailure: "vhLastFailure", lastRecovery: "vhLastRecovery" }; const BO_SORT_LABELS = { vhost: "vhVhost", backend: "backend", status: "status", active: "vhActive", requests: "vhRequests", errors: "vhErrors", latency: "vhLatency", traffic: "vhTraffic", rate: "backendColRate", statuses: "backendColStatuses", p95: "backendColP95", cache: "backendColCache", probe: "backendColProbe", causes: "backendColCauses", protocol: "backendColProtocol", generation: "vhClientGeneration", generationRequests: "vhGenerationRequests", generationAge: "vhGenerationAge", rotations: "vhClientRotations", lastFailure: "vhLastFailure", lastRecovery: "vhLastRecovery" };
const BO_DETAIL_COLUMNS = ["generation", "generationRequests", "generationAge", "rotations", "lastFailure", "lastRecovery"]; const BO_DETAIL_COLUMNS = ["generation", "generationRequests", "generationAge", "rotations", "lastFailure", "lastRecovery"];
const BO_SAMPLES = 30; const BO_SAMPLES = 30;
const boOpen = new Set(), boSeries = new Map(); const boOpen = new Set(), boSeries = new Map();
@@ -270,6 +272,54 @@ function buildBackendOverviewTools() {
input.type = "checkbox"; input.dataset.poolColumnToggle = key; text.dataset.i18n = BO_SORT_LABELS[key]; label.append(input, text); options.append(label); input.type = "checkbox"; input.dataset.poolColumnToggle = key; text.dataset.i18n = BO_SORT_LABELS[key]; label.append(input, text); options.append(label);
} }
} }
const BO_CAUSES = [["http_5xx_total", "5xx"], ["timeouts_total", "timeout"], ["connect_errors_total", "connect"], ["tls_errors_total", "TLS"], ["dns_errors_total", "DNS"], ["io_errors_total", "I/O"], ["queue_timeouts_total", "queue"], ["capacity_rejections_total", "limit"], ["retry_attempts_total", "retry"]];
function boMs(value) {
if (!(value > 0)) return "-";
if (value >= 10000) return ">5 s";
return value >= 1000 ? `${Number((value / 1000).toFixed(1))} s` : `${Math.round(value)} ms`;
}
function boPercent(part, total) {
if (!(total > 0) || !(part > 0)) return "0%";
const pct = part / total * 100;
return pct < 0.1 ? "<0.1%" : `${pct.toFixed(pct < 10 ? 1 : 0)}%`;
}
function backendRequestRate(key, requests, snapshot, now, seen) {
seen.add(key);
const previous = backendRequestHistory.get(key);
if (previous?.snapshot === snapshot) return previous.rate;
const continuous = rustProxyRateSampling.continuous(previous, now, rustProxyRateSampling.cadence("overview_seconds"));
const rate = continuous && requests >= previous.requests && now > previous.at ? (requests - previous.requests) / ((now - previous.at) / 1000) : previous?.rate || 0;
backendRequestHistory.set(key, { ...rustProxyRateSampling.stamp(now), requests, snapshot, rate: continuous ? rate : 0 });
return continuous ? rate : 0;
}
function backendExtraMetrics(pool, member, status, snapshot, now, seen) {
const vhost = status.vhost_metrics?.[pool.id] || {}, cacheTotals = status.vhost_cache_totals?.[pool.id] || null;
const classes = Array.isArray(vhost.status_classes) ? vhost.status_classes.map(value => Math.max(0, Number(value) || 0)) : [0, 0, 0, 0, 0];
const classTotal = classes.reduce((sum, value) => sum + value, 0);
const hits = Number(cacheTotals?.hits) || 0, misses = Number(cacheTotals?.misses) || 0, bypasses = Number(cacheTotals?.bypasses) || 0, lookups = hits + misses + bypasses;
const cacheBytes = Math.max(0, Number(cacheTotals?.saved_body_bytes) || 0), outBytes = Math.max(cacheBytes, Number(vhost.bytes_out) || 0), originBytes = Math.max(0, outBytes - cacheBytes);
const p95 = Number(vhost.p95_latency_ms) || 0, p99 = Number(vhost.p99_latency_ms) || 0;
const probeOff = member.health_mode === "off", probeLatency = Number(member.latency_ms) || 0, probeAt = Number(member.last_probe_ms) || 0;
const causes = BO_CAUSES.map(([field, label]) => ({ label, value: Math.max(0, Number(member[field]) || 0) })).filter(cause => cause.value > 0).sort((a, b) => b.value - a.value);
const causeTotal = causes.filter(cause => cause.label !== "retry").reduce((sum, cause) => sum + cause.value, 0);
const h1 = Math.max(0, Number(member.upstream_protocol_http1_total) || 0), h2 = Math.max(0, Number(member.upstream_protocol_http2_total) || 0);
const protocol = member.upstream_protocol_observed === "h1" || member.upstream_protocol_observed === "h2" ? member.upstream_protocol_observed.toUpperCase() : "";
const rate = backendRequestRate(`${pool.id}\u0000${member.id}`, Math.max(0, Number(member.requests) || 0), snapshot, now, seen);
const hitRatio = lookups ? hits / lookups : -1, errorShare = classTotal ? (classes[3] + classes[4]) / classTotal : -1;
return {
rate, classes, classTotal, hits, misses, bypasses, lookups, cacheBytes, originBytes, outBytes, p95, p99, probeOff, probeLatency, probeAt, lastProbeResult: member.last_result || "", causes, causeTotal, h1, h2, protocol,
sort: { rate, statuses: errorShare, p95, cache: hitRatio, probe: probeOff ? -1 : probeLatency, causes: causeTotal, protocol: h1 + h2 ? h2 / (h1 + h2) : -1 },
text: {
rate: `${rate.toFixed(rate < 10 ? 1 : 0)}/s`,
statuses: classTotal ? ["2xx", "3xx", "4xx", "5xx"].map((label, index) => `${label} ${boPercent(classes[index + 1], classTotal)}`).join(" · ") : "-",
p95: p95 ? `${boMs(p95)} / ${boMs(p99)}` : "-",
cache: lookups || cacheBytes ? `HIT ${boPercent(hits, lookups)} · ${bytes(cacheBytes)} / ${bytes(originBytes)}` : "-",
probe: probeOff || !(probeLatency > 0) ? "-" : `${probeLatency.toFixed(1)} ms`,
causes: causes.length ? causes.map(cause => `${cause.label} ${cause.value}`).join(" · ") : "-",
protocol: protocol ? `${protocol}${h1 + h2 ? ` · H2 ${boPercent(h2, h1 + h2)}` : ""}` : "-",
},
};
}
function boSeriesOf(item) { return item.series || { latency: [], inRate: [], outRate: [], requests: [] }; } function boSeriesOf(item) { return item.series || { latency: [], inRate: [], outRate: [], requests: [] }; }
function boAgo(ms) { function boAgo(ms) {
const seconds = Math.max(0, Math.floor((Date.now() - Number(ms)) / 1000)); const seconds = Math.max(0, Math.floor((Date.now() - Number(ms)) / 1000));
@@ -341,6 +391,67 @@ function renderBackendOverview(items, hasProxy, snapshot = null) {
$("backend-overview-card")?.classList.toggle("is-no-proxy", !boHasProxy); $("backend-overview-card")?.classList.toggle("is-no-proxy", !boHasProxy);
applyBackendOverviewView(); applyBackendOverviewView();
} }
function boStack(parts) {
const track = el("div", undefined, "bo-stack"), total = parts.reduce((sum, [value]) => sum + value, 0);
if (total > 0) for (const [value, kind] of parts) if (value > 0) { const part = el("i", undefined, `is-${kind}`); part.style.flexGrow = String(value); part.style.minWidth = "2px"; track.append(part); }
return track;
}
function boRing(ratio) {
const svg = document.createElementNS(BO_SVG, "svg"), radius = 8, length = 2 * Math.PI * radius;
svg.setAttribute("viewBox", "0 0 20 20"); svg.setAttribute("class", "bo-ring"); svg.setAttribute("aria-hidden", "true");
for (const kind of ["track", "value"]) {
const circle = document.createElementNS(BO_SVG, "circle");
circle.setAttribute("cx", "10"); circle.setAttribute("cy", "10"); circle.setAttribute("r", String(radius)); circle.setAttribute("class", `bo-ring-${kind}`);
if (kind === "value") { circle.setAttribute("stroke-dasharray", `${(Math.max(0, Math.min(1, ratio)) * length).toFixed(2)} ${length.toFixed(2)}`); circle.setAttribute("transform", "rotate(-90 10 10)"); }
svg.append(circle);
}
return svg;
}
const BO_EXTRA_CELLS = {
rate(cell, extra) { cell.className = "is-num"; cell.textContent = extra.text.rate; if (!(extra.rate > 0)) cell.classList.add("is-zero"); },
statuses(cell, extra) {
cell.className = "bo-statuses"; cell.title = `${t("backendVhostScope")}\n${["1xx", "2xx", "3xx", "4xx", "5xx"].map((label, index) => `${label}: ${extra.classes[index]}`).join("\n")}`;
if (!extra.classTotal) { cell.append(el("span", "-", "is-zero")); return; }
const top = el("div", undefined, "bo-meter-top"), issues = el("small");
top.append(el("span", `2xx ${boPercent(extra.classes[1], extra.classTotal)}`));
for (const [index, kind] of [[3, "warn"], [4, "bad"]]) if (extra.classes[index] > 0) issues.append(el("b", `${index + 1}xx ${boPercent(extra.classes[index], extra.classTotal)}`, `is-${kind}`));
if (!issues.children.length) issues.textContent = "4xx/5xx 0%";
top.append(issues); cell.append(top, boStack([[extra.classes[0] + extra.classes[1], "ok"], [extra.classes[2], "info"], [extra.classes[3], "warn"], [extra.classes[4], "bad"]]));
},
p95(cell, extra) {
cell.className = "bo-pair"; cell.title = `${t("backendVhostScope")} · ${t("backendBucketBound")}`;
if (!extra.p95) { cell.append(el("span", "-", "is-zero")); return; }
cell.append(el("span", boMs(extra.p95)), el("small", `p99 ${boMs(extra.p99)}`));
},
probe(cell, extra) {
cell.className = "bo-pair";
if (extra.probeOff || !(extra.probeLatency > 0)) { cell.append(el("span", "-", "is-zero")); return; }
cell.title = extra.lastProbeResult; cell.append(el("span", `${extra.probeLatency.toFixed(1)} ms`));
if (extra.probeAt > 0) cell.append(el("small", boAgo(extra.probeAt)));
},
cache(cell, extra) {
cell.className = "bo-cache";
cell.title = `${t("backendVhostScope")}\nHIT ${extra.hits} · MISS ${extra.misses} · BYPASS ${extra.bypasses}\nCACHE ${bytes(extra.cacheBytes)} (${boPercent(extra.cacheBytes, extra.outBytes)}) · ORIGIN ${bytes(extra.originBytes)}`;
if (!extra.lookups && !extra.cacheBytes) { cell.append(el("span", "-", "is-zero")); return; }
const wrap = el("div", undefined, "bo-chartcell"), hit = el("div", undefined, "bo-cache-hit"), text = el("div", undefined, "bo-traffic-text");
hit.append(boRing(extra.lookups ? extra.hits / extra.lookups : 0), el("span", extra.lookups ? boPercent(extra.hits, extra.lookups) : "-"));
const line = (kind, label, value) => { const row = el("div", undefined, `bo-${kind}`); row.append(el("span", label), el("b", bytes(value), value > 0 ? "" : "is-zero")); return row; };
text.append(line("hit", "CACHE", extra.cacheBytes), line("origin", "ORIGIN", extra.originBytes));
wrap.append(hit, text); cell.append(wrap);
},
causes(cell, extra) {
cell.className = "bo-causes"; cell.title = extra.text.causes;
if (!extra.causes.length) { cell.append(el("span", "-", "is-zero")); return; }
for (const cause of extra.causes.slice(0, 3)) { const chip = el("span", undefined, `bo-cause${cause.label === "retry" ? " is-soft" : ""}`); chip.append(cause.label, el("b", String(cause.value))); cell.append(chip); }
if (extra.causes.length > 3) cell.append(el("small", `+${extra.causes.length - 3}`));
},
protocol(cell, extra) {
cell.className = "bo-pair"; cell.title = `H1 ${extra.h1} · H2 ${extra.h2}`;
if (!extra.protocol) { cell.append(el("span", "-", "is-zero")); return; }
cell.append(el("span", extra.protocol));
if (extra.h1 + extra.h2) cell.append(el("small", `H2 ${boPercent(extra.h2, extra.h1 + extra.h2)}`));
},
};
function applyBackendOverviewView() { function applyBackendOverviewView() {
const body = $("bo-rows"); if (!body) return; const body = $("bo-rows"); if (!body) return;
const visibleColumns = new Set(poolTablePrefs.columns); const visibleColumns = new Set(poolTablePrefs.columns);
@@ -370,7 +481,9 @@ function applyBackendOverviewView() {
const columns = [ const columns = [
expandable && ["toggle", ""], showIdentity && ["identity", `${visibleColumns.has("vhost") ? t("vhVhost") : ""}${visibleColumns.has("vhost") && visibleColumns.has("backend") ? " / " : ""}${visibleColumns.has("backend") ? t("backend") : ""}`], expandable && ["toggle", ""], showIdentity && ["identity", `${visibleColumns.has("vhost") ? t("vhVhost") : ""}${visibleColumns.has("vhost") && visibleColumns.has("backend") ? " / " : ""}${visibleColumns.has("backend") ? t("backend") : ""}`],
visibleColumns.has("status") && ["status", t("status")], visibleColumns.has("active") && ["active", t("vhActive"), true], visibleColumns.has("requests") && ["requests", t("vhRequests"), true], visibleColumns.has("status") && ["status", t("status")], visibleColumns.has("active") && ["active", t("vhActive"), true], visibleColumns.has("requests") && ["requests", t("vhRequests"), true],
visibleColumns.has("errors") && ["errors", t("vhErrors")], visibleColumns.has("latency") && ["latency", t("vhLatency")], visibleColumns.has("traffic") && ["traffic", t("vhTraffic"), true], showEvent && ["event", t("backendLastEvent")], visibleColumns.has("rate") && ["rate", t("backendColRate"), true], visibleColumns.has("errors") && ["errors", t("vhErrors")], visibleColumns.has("statuses") && ["statuses", t("backendColStatuses")], visibleColumns.has("latency") && ["latency", t("vhLatency")],
visibleColumns.has("p95") && ["p95", t("backendColP95"), true], visibleColumns.has("probe") && ["probe", t("backendColProbe"), true], visibleColumns.has("cache") && ["cache", t("backendColCache")], visibleColumns.has("traffic") && ["traffic", t("vhTraffic"), true],
visibleColumns.has("causes") && ["causes", t("backendColCauses")], visibleColumns.has("protocol") && ["protocol", t("backendColProtocol")], showEvent && ["event", t("backendLastEvent")],
].filter(Boolean); ].filter(Boolean);
const head = $("bo-head"); if (head) head.replaceChildren(...columns.map(([id, label, numeric]) => { const th = el("th", label, numeric ? "is-num" : ""); th.dataset.boColumn = id; if (id === "toggle") th.setAttribute("aria-label", t("backendDetailsToggle")); return th; })); const head = $("bo-head"); if (head) head.replaceChildren(...columns.map(([id, label, numeric]) => { const th = el("th", label, numeric ? "is-num" : ""); th.dataset.boColumn = id; if (id === "toggle") th.setAttribute("aria-label", t("backendDetailsToggle")); return th; }));
const rows = []; const rows = [];
@@ -405,6 +518,7 @@ function applyBackendOverviewView() {
text.append(line("in", "IN", item.trafficIn || 0), line("out", "OUT", item.trafficOut || 0)); text.append(line("in", "IN", item.trafficIn || 0), line("out", "OUT", item.trafficOut || 0));
wrap.append(boTrafficChart(boSeriesOf(item).inRate, boSeriesOf(item).outRate), text); cell.append(wrap); wrap.append(boTrafficChart(boSeriesOf(item).inRate, boSeriesOf(item).outRate), text); cell.append(wrap);
} }
else if (id in BO_EXTRA_CELLS) BO_EXTRA_CELLS[id](cell, item.extra);
else if (id === "event") { else if (id === "event") {
cell.className = "bo-event"; cell.className = "bo-event";
if (item.lastFailureMs && item.lastFailureMs >= item.lastRecoveryMs) { const line = el("span", item.lastFailureClass || t("backendEventFailed"), "is-failed"); line.title = item.lastFailureDetail || ""; cell.append(line, el("small", boAgo(item.lastFailureMs))); } if (item.lastFailureMs && item.lastFailureMs >= item.lastRecoveryMs) { const line = el("span", item.lastFailureClass || t("backendEventFailed"), "is-failed"); line.title = item.lastFailureDetail || ""; cell.append(line, el("small", boAgo(item.lastFailureMs))); }
@@ -651,7 +765,7 @@ function renderStatus(status) {
refreshVhostBackendPopup(status); refreshVhostBackendPopup(status);
form.querySelectorAll(".vh-member").forEach(row => row.syncLocalRisk?.()); form.querySelectorAll(".vh-member").forEach(row => row.syncLocalRisk?.());
const rows = [], summaries = new Map(), aggregate = new Map(), overviewItems = []; const rows = [], summaries = new Map(), aggregate = new Map(), overviewItems = [];
const trafficSnapshot = status, trafficNow = performance.now(), seenTraffic = new Set(), seenVhostTraffic = new Set(); const trafficSnapshot = status, trafficNow = performance.now(), seenTraffic = new Set(), seenVhostTraffic = new Set(), seenRequests = new Set();
const statusKeys = { up: "vhUp", down: "vhDown", checking: "vhChecking", draining: "vhDraining", disabled: "vhDisabled" }; const statusKeys = { up: "vhUp", down: "vhDown", checking: "vhChecking", draining: "vhDraining", disabled: "vhDisabled" };
const configuredVhosts = new Map((envelope?.config.vhosts || []).map(vhost => [vhost.id, vhost])); const configuredVhosts = new Map((envelope?.config.vhosts || []).map(vhost => [vhost.id, vhost]));
for (const pool of status.backend_pools || []) { for (const pool of status.backend_pools || []) {
@@ -659,6 +773,7 @@ function renderStatus(status) {
let up = 0, active = 0, requests = 0, errors = 0, traffic = 0, weightedLatency = 0, latencyWeight = 0; let up = 0, active = 0, requests = 0, errors = 0, traffic = 0, weightedLatency = 0, latencyWeight = 0;
for (const member of members) { for (const member of members) {
const memberTraffic = backendTrafficRate(pool.id, member, trafficSnapshot, trafficNow, seenTraffic); const memberTraffic = backendTrafficRate(pool.id, member, trafficSnapshot, trafficNow, seenTraffic);
const extra = backendExtraMetrics(pool, member, status, trafficSnapshot, trafficNow, seenRequests);
const label = vhostDisabled ? t("vhDisabled") : (member.health_mode === "off" && member.status === "up" ? t("vhHealthDisabled") : t(statusKeys[member.status] || "vhHealthUnknown")); const label = vhostDisabled ? t("vhDisabled") : (member.health_mode === "off" && member.status === "up" ? t("vhHealthDisabled") : t(statusKeys[member.status] || "vhHealthUnknown"));
const activeLimit = Number(member.max_concurrent_requests) > 0 ? `${member.active ?? 0}/${member.max_concurrent_requests}` : `${member.active ?? 0}/∞`; const activeLimit = Number(member.max_concurrent_requests) > 0 ? `${member.active ?? 0}/${member.max_concurrent_requests}` : `${member.active ?? 0}/∞`;
const lastFailure = Number(member.last_failure_at_ms) > 0 ? `${member.last_failure_class || "-"} · ${formatTime(member.last_failure_at_ms)}` : "-"; const lastFailure = Number(member.last_failure_at_ms) > 0 ? `${member.last_failure_class || "-"} · ${formatTime(member.last_failure_at_ms)}` : "-";
@@ -668,17 +783,17 @@ function renderStatus(status) {
const generationAge = Math.max(0, Number(generationAgeRaw)); const generationAge = Math.max(0, Number(generationAgeRaw));
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 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, 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]; member.ewma_latency_ms > 0 ? `${member.ewma_latency_ms.toFixed(1)} ms` : "-", `${rateBytes(memberTraffic)}/s`, extra.text.rate, extra.text.statuses, extra.text.p95, extra.text.cache, extra.text.probe, extra.text.causes, extra.text.protocol, member.upstream_client_generation ?? "-", member.upstream_client_generation_requests ?? "-", generationAgeText, member.upstream_client_rotations_total ?? "-", lastFailure, lastRecovery];
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 }; 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, ...extra.sort, 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(); 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); cells.forEach((value, index) => { const cell = el("td", String(value ?? "-")); const column = poolColumns[index]; cell.dataset.poolColumn = column; cell.title = column === "status" ? (vhostDisabled ? t("vhDisabled") : `${label} · ${healthDetail}`) : column === "lastFailure" ? (member.last_failure_detail || lastFailure) : String(value ?? "-"); if (column === "status" && !vhostDisabled) cell.className = member.status === "down" ? "status-error" : member.status === "up" ? "status-good" : ""; if (column === "lastFailure" && member.last_failure_at_ms) cell.classList.add("backend-last-failure"); row.append(cell); }); rows.push(row);
const tone = vhostDisabled ? "muted" : member.health_mode === "off" && member.status === "up" ? "muted" : member.status === "up" ? "up" : member.status === "down" ? "down" : member.status === "checking" || member.status === "draining" ? "warn" : "muted"; const tone = vhostDisabled ? "muted" : member.health_mode === "off" && member.status === "up" ? "muted" : member.status === "up" ? "up" : member.status === "down" ? "down" : member.status === "checking" || member.status === "draining" ? "warn" : "muted";
overviewItems.push({ overviewItems.push({
key: `${pool.id}\u0000${member.id}`, vhost: poolLabel, backend: `${member.id}${member.backup ? " / " + t("vhBackup") : ""}`, disabled: vhostDisabled, tone, group: tone === "up" ? "up" : tone === "down" ? "down" : "other", key: `${pool.id}\u0000${member.id}`, vhost: poolLabel, backend: `${member.id}${member.backup ? " / " + t("vhBackup") : ""}`, disabled: vhostDisabled, tone, group: tone === "up" ? "up" : tone === "down" ? "down" : "other",
label, title: vhostDisabled ? t("vhDisabled") : `${label} · ${healthDetail}`, activeText: activeLimit, requests: Number(member.requests) || 0, errors: Number(member.errors) || 0, latency: Number(member.ewma_latency_ms) || 0, traffic: memberTraffic, ...(({ inRate, outRate }) => ({ trafficIn: inRate, trafficOut: outRate }))(backendTrafficSplit(pool.id, member)), label, title: vhostDisabled ? t("vhDisabled") : `${label} · ${healthDetail}`, activeText: activeLimit, requests: Number(member.requests) || 0, errors: Number(member.errors) || 0, latency: Number(member.ewma_latency_ms) || 0, traffic: memberTraffic, ...(({ inRate, outRate }) => ({ trafficIn: inRate, trafficOut: outRate }))(backendTrafficSplit(pool.id, member)),
generation: member.upstream_client_generation ?? "-", generationRequests: member.upstream_client_generation_requests ?? "-", generationAgeText, rotations: member.upstream_client_rotations_total ?? "-", generation: member.upstream_client_generation ?? "-", generationRequests: member.upstream_client_generation_requests ?? "-", generationAgeText, rotations: member.upstream_client_rotations_total ?? "-",
lastFailureMs: Number(member.last_failure_at_ms) || 0, lastFailureClass: member.last_failure_class || "", lastFailureDetail: member.last_failure_detail || "", lastFailureText: lastFailure, lastRecoveryMs: Number(member.last_recovered_ms) || 0, lastRecoveryText: lastRecovery, lastFailureMs: Number(member.last_failure_at_ms) || 0, lastFailureClass: member.last_failure_class || "", lastFailureDetail: member.last_failure_detail || "", lastFailureText: lastFailure, lastRecoveryMs: Number(member.last_recovered_ms) || 0, lastRecoveryText: lastRecovery,
sort: sortValues, search: row.poolSearchValue, extra, sort: sortValues, search: row.poolSearchValue,
}); });
if (!vhostDisabled && member.status === "up") up++; active += Number(member.active) || 0; requests += Number(member.requests) || 0; errors += Number(member.errors) || 0; traffic += memberTraffic; 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; } 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; }
@@ -698,6 +813,7 @@ function renderStatus(status) {
}); });
} }
for (const key of backendTrafficHistory.keys()) if (!seenTraffic.has(key)) backendTrafficHistory.delete(key); for (const key of backendTrafficHistory.keys()) if (!seenTraffic.has(key)) backendTrafficHistory.delete(key);
for (const key of backendRequestHistory.keys()) if (!seenRequests.has(key)) backendRequestHistory.delete(key);
for (const [id, vhost] of configuredVhosts) { for (const [id, vhost] of configuredVhosts) {
const existing = aggregate.get(id) || {}; const existing = aggregate.get(id) || {};
const metrics = status.vhost_metrics?.[id]; const metrics = status.vhost_metrics?.[id];
@@ -822,5 +938,5 @@ $("pool-open").addEventListener("click", () => setPanel("vhosts"));
initPoolTableTools(); initPoolTableTools();
$("location-help-close")?.addEventListener("click", () => $("location-help-dialog")?.close()); $("location-help-close")?.addEventListener("click", () => $("location-help-dialog")?.close());
window.rustProxyDrafts.register("vhost", { labelKey: "vhosts", isOpen: () => dialog.open, isDirty: () => dirty, suspend: suspendDraft, restore: restoreDraft, close: () => { dirty = false; if (dialog.open) dialog.close(); } }); window.rustProxyDrafts.register("vhost", { labelKey: "vhosts", isOpen: () => dialog.open, isDirty: () => dirty, suspend: suspendDraft, restore: restoreDraft, close: () => { dirty = false; if (dialog.open) dialog.close(); } });
window.rustProxyVhosts = { render, renderStatus, applyPreferences, reset() { closeVhostBackendPopup(); dialog.close(); draftEpoch++; stagedCertificates = []; uploading = false; editing = null; revision = null; dirty = false; initialized = false; backendHealthEditorControl = null; poolTablePrefs = defaultPoolTablePrefs(); vhostListPrefs = defaultVhostListPrefs(); 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; const vhostSort = $("vhost-sort"); if (vhostSort) vhostSort.value = vhostListPrefs.sort; syncVhostSortDirection(); applyPoolTableView(); } }; window.rustProxyVhosts = { render, renderStatus, applyPreferences, reset() { closeVhostBackendPopup(); dialog.close(); draftEpoch++; stagedCertificates = []; uploading = false; editing = null; revision = null; dirty = false; initialized = false; backendHealthEditorControl = null; poolTablePrefs = defaultPoolTablePrefs(); vhostListPrefs = defaultVhostListPrefs(); poolPreferenceSaveRunning = false; poolPreferenceSaveQueued = false; chartHistory.clear(); backendTrafficHistory.clear(); backendRequestHistory.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; const vhostSort = $("vhost-sort"); if (vhostSort) vhostSort.value = vhostListPrefs.sort; syncVhostSortDirection(); applyPoolTableView(); } };
if (envelope) render(); if (latestStatus) renderStatus(latestStatus); if (envelope) render(); if (latestStatus) renderStatus(latestStatus);
+3
View File
@@ -275,6 +275,9 @@ impl Cache {
} }
view view
} }
pub fn scope_totals(&self) -> BTreeMap<String, ScopeCacheTotals> {
self.domain_stats.scope_totals()
}
pub fn record(&self, scope: &str, domain: &str, outcome: &str, reason: &'static str) { pub fn record(&self, scope: &str, domain: &str, outcome: &str, reason: &'static str) {
self.domain_stats.record(scope, domain, outcome, reason); self.domain_stats.record(scope, domain, outcome, reason);
} }
+17
View File
@@ -574,6 +574,23 @@ mod dashboard_tests {
assert_eq!(result.domains[0].counters.saved_body_bytes, 1040); assert_eq!(result.domains[0].counters.saved_body_bytes, 1040);
} }
#[test]
fn scope_totals_sum_domains_per_vhost() {
let cache = Cache::new(7);
cache.record("alpha", "a.example", "HIT", "cache_hit");
cache.record("alpha", "b.example", "HIT", "cache_hit");
cache.record("alpha", "b.example", "MISS", "cache_miss");
cache.record("beta", "c.example", "BYPASS", "request_cookie");
cache.domain_stats.saved_body_hit("alpha", "a.example", 300);
cache.domain_stats.saved_body_hit("alpha", "b.example", 700);
let totals = cache.scope_totals();
assert_eq!(
totals["alpha"],
ScopeCacheTotals { hits: 2, misses: 1, bypasses: 0, saved_body_bytes: 1000 }
);
assert_eq!(totals["beta"].bypasses, 1);
}
#[test] #[test]
fn per_domain_counter_cardinality_is_bounded() { fn per_domain_counter_cardinality_is_bounded() {
let cache = Cache::new(7); let cache = Cache::new(7);
+7
View File
@@ -586,3 +586,10 @@ pub struct DomainCounters {
pub saved_body_bytes: u64, pub saved_body_bytes: u64,
pub reasons: BTreeMap<String, u64>, pub reasons: BTreeMap<String, u64>,
} }
#[derive(Debug, Clone, Copy, Default, Serialize, PartialEq, Eq)]
pub struct ScopeCacheTotals {
pub hits: u64,
pub misses: u64,
pub bypasses: u64,
pub saved_body_bytes: u64,
}
+21
View File
@@ -196,4 +196,25 @@ impl DomainStatsStore {
} }
counters counters
} }
fn scope_totals(&self) -> BTreeMap<String, ScopeCacheTotals> {
let published = self.domains.load();
let load = |value: &AtomicU64| value.load(Ordering::Relaxed);
published
.index
.iter()
.map(|(scope, domains)| {
let mut totals = ScopeCacheTotals::default();
for stats in domains.values() {
totals.hits = totals.hits.saturating_add(load(&stats.hits));
totals.misses = totals.misses.saturating_add(load(&stats.misses));
totals.bypasses = totals.bypasses.saturating_add(load(&stats.bypasses));
totals.saved_body_bytes = totals
.saved_body_bytes
.saturating_add(load(&stats.saved_body_bytes));
}
(scope.clone(), totals)
})
.collect()
}
} }
+27
View File
@@ -24,6 +24,9 @@ pub struct VhostMetricsView {
pub bytes_out: u64, pub bytes_out: u64,
pub average_latency_ms: f64, pub average_latency_ms: f64,
pub ewma_latency_ms: f64, pub ewma_latency_ms: f64,
pub status_classes: [u64; 5],
pub p95_latency_ms: Option<f64>,
pub p99_latency_ms: Option<f64>,
} }
impl VhostMetrics { impl VhostMetrics {
@@ -95,8 +98,32 @@ impl VhostMetrics {
duration_us as f64 / completed as f64 / 1000.0 duration_us as f64 / completed as f64 / 1000.0
}, },
ewma_latency_ms: n(&self.ewma_duration_us) as f64 / 1000.0, ewma_latency_ms: n(&self.ewma_duration_us) as f64 / 1000.0,
status_classes: std::array::from_fn(|index| n(&self.status[index])),
p95_latency_ms: self.latency_quantile_ms(95),
p99_latency_ms: self.latency_quantile_ms(99),
} }
} }
pub fn latency_quantile_ms(&self, percent: u64) -> Option<f64> {
let counts: [u64; 11] = std::array::from_fn(|index| n(&self.buckets[index]));
let total = counts.iter().fold(0u64, |sum, value| sum.saturating_add(*value));
if total == 0 {
return None;
}
let target = total.saturating_mul(percent).div_ceil(100).max(1);
let mut seen = 0u64;
for (index, count) in counts.iter().enumerate() {
seen = seen.saturating_add(*count);
if seen >= target {
let bound = DURATION_BUCKETS_US
.get(index)
.copied()
.unwrap_or(DURATION_BUCKETS_US[DURATION_BUCKETS_US.len() - 1] * 2);
return Some(bound as f64 / 1000.0);
}
}
None
}
} }
#[cfg(test)] #[cfg(test)]
include!("tests.rs"); include!("tests.rs");
+21
View File
@@ -69,4 +69,25 @@ mod compression_counter_tests {
assert_eq!(view.compression_zstd, 1); assert_eq!(view.compression_zstd, 1);
assert_eq!(view.compression_deflate, 1); assert_eq!(view.compression_deflate, 1);
} }
#[test]
fn view_reports_status_classes_and_bucket_quantiles() {
let m = VhostMetrics::default();
assert_eq!(m.view().p95_latency_ms, None);
m.active.store(100, Ordering::Relaxed);
for index in 0..100u64 {
let (status, millis) = match index {
0..=89 => (200, 3),
90..=95 => (404, 40),
96..=98 => (302, 200),
_ => (502, 9_000),
};
m.complete(status, false, std::time::Duration::from_millis(millis), 0, 0);
}
let view = m.view();
assert_eq!(view.status_classes, [0, 90, 3, 6, 1]);
assert_eq!(view.p95_latency_ms, Some(50.0));
assert_eq!(view.p99_latency_ms, Some(250.0));
assert_eq!(m.latency_quantile_ms(100), Some(10_000.0));
}
} }
+1 -1
View File
@@ -51,5 +51,5 @@ const events = openapi.paths["/api/events"].get;
const logsFlag = (events.parameters || []).find(parameter => parameter.name === "logs"); const logsFlag = (events.parameters || []).find(parameter => parameter.name === "logs");
assert.equal(logsFlag?.schema?.type, "boolean"); assert.equal(logsFlag?.schema?.type, "boolean");
assert.equal(logsFlag?.schema?.default, true); assert.equal(logsFlag?.schema?.default, true);
assert.equal(openapi.info.version, "0.36.10"); assert.equal(openapi.info.version, "0.36.11");
console.log("Panel refresh/API contract checks: OK"); console.log("Panel refresh/API contract checks: OK");