v0.36.8
This commit is contained in:
1 parent
1ac94213e0
commit
0119b11005
28 files changed
+698
-153
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
# RustProxy source and documentation maintenance
|
# RustProxy source and documentation maintenance
|
||||||
|
|
||||||
Current source version: **0.36.7**. The Cargo package and binary are named `rust-proxy`.
|
Current source version: **0.36.8**. 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.
|
||||||
|
|
||||||
|
|||||||
+10
-1
@@ -2,11 +2,20 @@
|
|||||||
|
|
||||||
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.8 — editor forms, toasts and CSP fixes
|
||||||
|
|
||||||
|
- **Editor forms:** the `pattern` attribute of generated ID fields (vhost, location, member, listener, stream ID, error fallback target, affinity cookie name) escaped the hyphen incorrectly for browsers that compile patterns with the `v` flag, which logged "Pattern attribute value ... is not a valid regular expression". The hyphen is now escaped.
|
||||||
|
- **Toasts:** the toast element could be removed together with a rebuilt dialog, after which every save/validation message threw `Cannot set properties of null (setting 'textContent')` and the form could not be saved. The toast is now recreated on demand.
|
||||||
|
- **Cancel new entries:** vhost, redirect, 404 host, stream and web service editors have a **Cancel** button next to "Discard changes". It closes the editor and drops the draft (with a confirmation when something was typed) instead of only minimizing it to the draft dock.
|
||||||
|
- **CSP:** the cache outcomes bar no longer uses inline `style="width:0%"` attributes (blocked by `style-src 'self'`); the initial width comes from the stylesheet.
|
||||||
|
|
||||||
## 0.36.7 — Docker vhosts and cache panel polish
|
## 0.36.7 — Docker vhosts and cache panel polish
|
||||||
|
|
||||||
|
- **Backends table redesigned:** a richer replacement for the 14-column table. It takes over the existing `backend_table` slot (saved layouts keep its position, size and visibility); the previous compact table remains available as the optional **Tiny backends** (`backend_tiny`, hidden by default). Status badges with a coloured row edge, vhost and backend in one cell, error count with a share bar, latency with a short trend line, traffic per second and a "last event" column; filter chips (All / Up / Down / Other) with counts, search, sorting, column selection and "Manage vhosts" work as before and share the same saved sort, direction and column preferences. Client generation, generation requests/age, rotations and the last failure/recovery open under a chevron and stay open across refreshes. It follows the table density (normal, compact, ultra) and corner-style settings. The original table is still available as **Tiny backends**; the new card is visible in the factory layout (Tiny backends is hidden there), while existing saved layouts keep their current cards and can enable the new one in the layout editor.
|
||||||
|
- **Process card redesigned:** chips for platform, threads, start and uptime, Load rows with usage meters (CPU, RSS, file descriptors, cgroup), a Memory section with an anonymous/file-backed split bar, and a 2×2 grid of history charts (CPU, RSS, file descriptors, OS threads); cache memory is no longer shown there (see RAM cache). It takes over the existing `process_diagnostics` slot, so saved layouts keep its position, size and visibility. The previous card is still available as the optional **Process simple** (`process_simple`, hidden by default).
|
||||||
- **Docker vhosts:** the collapsible sections (Configure vhost, Protocols, Access control, timeouts, health checks) now align arrow, title and hint to the left with a common title column. Traffic statistics are expanded by default for every enabled app (a section you collapse stays collapsed). A new **Hide disabled** button hides all unpublished vhosts from the view at once; **Show hidden** brings them back.
|
- **Docker vhosts:** the collapsible sections (Configure vhost, Protocols, Access control, timeouts, health checks) now align arrow, title and hint to the left with a common title column. Traffic statistics are expanded by default for every enabled app (a section you collapse stays collapsed). A new **Hide disabled** button hides all unpublished vhosts from the view at once; **Show hidden** brings them back.
|
||||||
- **404 hosts:** cards use the same host chips → response layout as redirects, with listeners and certificate on a quiet meta line; redirect and 404 cards are separated by the same gap as vhost cards.
|
- **404 hosts:** cards use the same host chips → response layout as redirects, with listeners and certificate on a quiet meta line; redirect and 404 cards are separated by the same gap as vhost cards.
|
||||||
- **Cache panel:** the "transformed" badge stays visible next to long object URLs (the URL is shortened instead). Decision counters show a thin share bar under each counter (relative to the largest) and a compact "+N" toggle; the pager no longer shows doubled arrows; the folded Decision counters ("+N") keep their open/closed state across automatic refreshes.
|
- **Cache panel:** the pager follows the corner-style setting (outer corners only) and stays on one row (the heading wraps instead) with the arrow after "Next"; the "transformed" badge stays visible next to long object URLs (the URL is shortened instead). Decision counters show a thin share bar under each counter (relative to the largest) and a compact "+N" toggle; the pager no longer shows doubled arrows; the folded Decision counters ("+N") keep their open/closed state across automatic refreshes.
|
||||||
- **Dashboard cache outcomes:** the legend under the bar shows only the request counts; percentages are shown once, inside the bar and in the headline (tooltips give both).
|
- **Dashboard cache outcomes:** the legend under the bar shows only the request counts; percentages are shown once, inside the bar and in the headline (tooltips give both).
|
||||||
- **Build:** `SHA256SUMS` uses repository-relative paths again.
|
- **Build:** `SHA256SUMS` uses repository-relative paths again.
|
||||||
|
|
||||||
|
|||||||
Generated
+1
-1
@@ -1943,7 +1943,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-proxy"
|
name = "rust-proxy"
|
||||||
version = "0.36.7"
|
version = "0.36.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rust-proxy"
|
name = "rust-proxy"
|
||||||
version = "0.36.7"
|
version = "0.36.8"
|
||||||
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"
|
||||||
|
|||||||
+27
-27
@@ -1,10 +1,10 @@
|
|||||||
61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore
|
61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore
|
||||||
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
|
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
|
||||||
e76e688874828e3743d03693b1d2872b9633b4db09909673786caf30f7cfe489 .gitignore
|
e76e688874828e3743d03693b1d2872b9633b4db09909673786caf30f7cfe489 .gitignore
|
||||||
357a4c6e4ac6aca6baf93f43013fa8a1701e68289b7d0d47d1728d03734846eb AGENTS.md
|
f5c2aa12963c54c15b38b14af7ba2dbd22ba3e32ebe33519aac3dc6cd60a84ce AGENTS.md
|
||||||
c43200ec7df77449be7074265cce582e9af7c10af70dca843e9a13285fa0b317 CHANGELOG.md
|
eeaafad7d1f30f6669b39426a3466b99fa5662e8e60d204c4b8364de1d836bfd CHANGELOG.md
|
||||||
881fafd324cb38c3c016cd853e65b9870819d2ac6ce932a6dd6b6755701444d4 Cargo.lock
|
c43749d6c4f56ce7e5bee5820c7973c3550480a208ce44d68e73485e0b47bf9d Cargo.lock
|
||||||
90664b6534166fb8bfb8d2b61885e3397d5df78f584e5a334ff81c522833c555 Cargo.toml
|
f4e5fca45bc7035dd60901d6a2a67a81a924b10c83178d110bddf6250a38978d 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
|
||||||
5025cf1086254f3e664cd50be2737b437286cbf311c79d87578c3d1d6c4a4f64 integrations/routeros-oci-build/README.md
|
cc8ad6a18950d5537495511ffc714a231bc88652310d1f8f780b9d791ef12d07 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
|
||||||
4d4c80c3ac03a1f1adda548378cad5e4237921c7f8a5946ae101c6389d8377ac langs/en_US.json
|
4c87bf2e5d0ae93a87b5990015e2539f9da804b8fc4b4c6e366015b13e248002 langs/en_US.json
|
||||||
b0ebd18ce391a7fe6d34b20b5af9be896517fa2bf51be175a95943e23bb32846 langs/pl_pl.json
|
89403111c26ea45c2a9a7d32e0f0d2fec9f9dde7d2e032e88f634a8dd8083695 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,13 +165,13 @@ 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
|
||||||
96101a80516c9cd668feb90a2cdd766363b4335b37888a926bb322e0cf3a160a src/admin/openapi.json
|
7e585e45acfb3be01c44e85db1ba091d62558ae64cd16ea2946586d68903dac5 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
|
||||||
229efd2691e51755f402ee04bcd9b806e3d3176c39526b9f7c2381ce779a7ba5 src/admin/preferences.rs
|
229efd2691e51755f402ee04bcd9b806e3d3176c39526b9f7c2381ce779a7ba5 src/admin/preferences.rs
|
||||||
35a7f99c7448c90b8637f4b3a7becca138f1f378fe5dc3f87122151525bb20f9 src/admin/preferences_columns.rs
|
35a7f99c7448c90b8637f4b3a7becca138f1f378fe5dc3f87122151525bb20f9 src/admin/preferences_columns.rs
|
||||||
a238feee9afce314b8f6f7257e5488a53c4d680f33450a4e3e460fccbe54de88 src/admin/preferences_tests.rs
|
41b23e7114170afc2fb8e818b41321b5044debd8a6ab12e6af128cfa0cd4d9f0 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
|
||||||
@@ -189,9 +189,9 @@ faa9735c2a02746b2954bc4955eb609f94afebb685cd5521d2629640cea2777a src/admin/web/
|
|||||||
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
|
e5b39b5592ba81292571033f26934ac0cd42bcdca7395e91094973cecb3eeaf8 src/admin/web/app/config-history.js
|
||||||
d5453830975947c453542f4aa175e575a39604902213300fa2db0f77e02df1af src/admin/web/app/core.js
|
7409fe1ee03b1d35d0ffc55827010befb4323bb14c594914aea49ebdcb88a5c1 src/admin/web/app/core.js
|
||||||
d695ec90fb5c98c27d7ba1d2bebf54f9840985fc530efcec3fcaf48d74b36c6b src/admin/web/app/dashboard-inline-layout.js
|
d695ec90fb5c98c27d7ba1d2bebf54f9840985fc530efcec3fcaf48d74b36c6b src/admin/web/app/dashboard-inline-layout.js
|
||||||
e4238322a985d3e8b7f2de94b7af31e1c718775590b2c7b3428b878cd43337b7 src/admin/web/app/dashboard-layout-editor.js
|
8eed7cf2f5004bf89693eff57af7c37a7781afe5cff25e199ffab9855d06b7fd src/admin/web/app/dashboard-layout-editor.js
|
||||||
a184c50e1ca6bb5a14954e31b15f09ff9978fb939064ca724851bbb4e661dc56 src/admin/web/app/dashboard-presets.js
|
a184c50e1ca6bb5a14954e31b15f09ff9978fb939064ca724851bbb4e661dc56 src/admin/web/app/dashboard-presets.js
|
||||||
0be1024d9ff657044e04ed71f275cff3b432406d5fdcdeac726daaee54444abd src/admin/web/app/icons.js
|
0be1024d9ff657044e04ed71f275cff3b432406d5fdcdeac726daaee54444abd src/admin/web/app/icons.js
|
||||||
e5a92fa1cb7e049082f0764b245560c41a9a34bcd8a9536fd872d9169fa5465d src/admin/web/app/logo-animation.js
|
e5a92fa1cb7e049082f0764b245560c41a9a34bcd8a9536fd872d9169fa5465d src/admin/web/app/logo-animation.js
|
||||||
@@ -203,7 +203,7 @@ bfd9fafb3112197e8f875dbf87a9a66c81aef0fe83687601a84a66f5e13f69bc src/admin/web/
|
|||||||
e540119edc555cefbe5147d97e06527adf49c984ba7a4dd51dc67603d3a46041 src/admin/web/app/preferences.js
|
e540119edc555cefbe5147d97e06527adf49c984ba7a4dd51dc67603d3a46041 src/admin/web/app/preferences.js
|
||||||
9e2b12d34ab1fb29716645720577ad49b16c03b46c482a03695e8f645d170e79 src/admin/web/app/rate-sampling.js
|
9e2b12d34ab1fb29716645720577ad49b16c03b46c482a03695e8f645d170e79 src/admin/web/app/rate-sampling.js
|
||||||
3b8e566d81a5b94ef21c73e7805ae128454f085554de9358040de266bcbdb0e0 src/admin/web/app/refresh.js
|
3b8e566d81a5b94ef21c73e7805ae128454f085554de9358040de266bcbdb0e0 src/admin/web/app/refresh.js
|
||||||
e5f8a0124d57df71638df86b2262d5c210fe7947736712dda7f97e6ca36048f6 src/admin/web/app/runtime.js
|
222a735d8835dd1715867cd519a7f954a43028c257f284667fd931596c12e16e 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
|
||||||
b7ead49224a4ec67181d3f24f9d9634bd6d76756f826a4d5d993300637d1d8f3 src/admin/web/app/stream-dashboard.js
|
b7ead49224a4ec67181d3f24f9d9634bd6d76756f826a4d5d993300637d1d8f3 src/admin/web/app/stream-dashboard.js
|
||||||
@@ -211,23 +211,23 @@ b7ead49224a4ec67181d3f24f9d9634bd6d76756f826a4d5d993300637d1d8f3 src/admin/web/
|
|||||||
f87af546830f01411dcf93749b4f75ca0f0bb3745a3390eff73ee14fc768cef0 src/admin/web/cache-panel.js
|
f87af546830f01411dcf93749b4f75ca0f0bb3745a3390eff73ee14fc768cef0 src/admin/web/cache-panel.js
|
||||||
9f323507649910561518b63cf6632e81b3ecc4b09501ae53b635705f34ea6587 src/admin/web/certificate-api.js
|
9f323507649910561518b63cf6632e81b3ecc4b09501ae53b635705f34ea6587 src/admin/web/certificate-api.js
|
||||||
4810137aa4217a86770617d442cf5dc6bec0d9f19e20bb2155e932a9a111cedf src/admin/web/css/00-tokens.css
|
4810137aa4217a86770617d442cf5dc6bec0d9f19e20bb2155e932a9a111cedf src/admin/web/css/00-tokens.css
|
||||||
466a24e234e1ec15f17e7d1994e838ca1ee0f52f03a4ca8fb6d9903bf2afde55 src/admin/web/css/10-base.css
|
84584a08df60190f39c3d3d39c4c30fcd53eba6980ea1f237e28f546585bf588 src/admin/web/css/10-base.css
|
||||||
8a1996d37f138790c12a704deedde9de74f733e7dcbb6be816fd68c281e8e0a9 src/admin/web/css/20-shell.css
|
8a1996d37f138790c12a704deedde9de74f733e7dcbb6be816fd68c281e8e0a9 src/admin/web/css/20-shell.css
|
||||||
d4b20ada2d91ddbc1613c642194df19e601948b34c4483fd04930c3e6db02394 src/admin/web/css/30-overview.css
|
774df6f98eebab206a2ea821fbed46aae619dab0d66b6c828ae87dc6fb51612b src/admin/web/css/30-overview.css
|
||||||
ca93e41d1c6001d82f4891cacdd712e896a0355d6db9f76cf62dd2f2225a8674 src/admin/web/css/40-editors.css
|
ca93e41d1c6001d82f4891cacdd712e896a0355d6db9f76cf62dd2f2225a8674 src/admin/web/css/40-editors.css
|
||||||
87df062d7478176641364c07de396ca988a37b1a8bcb892b53cc578fe8471850 src/admin/web/css/50-settings.css
|
87df062d7478176641364c07de396ca988a37b1a8bcb892b53cc578fe8471850 src/admin/web/css/50-settings.css
|
||||||
9775d86312c03b9c722ce713f2dc4a99985157d2f6b98277f9c0dabdcf53de2c src/admin/web/css/60-monitoring.css
|
74419f367c4128824ec2f0901a99f54f68439d5d949d41d9cf3d6bd57568343e src/admin/web/css/60-monitoring.css
|
||||||
483664859787a2c54b51e73891cb760cfa15ff5e2e4132c96989eaa8109cf3a4 src/admin/web/css/70-responsive.css
|
aaeed9d44f578bb6ec931f9244621db1d1767b99aea85792eb8f818f439d32c4 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
|
||||||
ee51ab0912e0ade6a3e1c2ea6f533bb2f65a6d3e6ec9087e25b9f0df009c19b2 src/admin/web/dashboard-defaults.json
|
f6720722581aa70ab1fa69b298b4c8a4a1596bfe56e4783f9284d69a3586aeab src/admin/web/dashboard-defaults.json
|
||||||
20d65b22900d0de2f71dbafee2fe9a237e0a76515b386022b94c75d50bdfd926 src/admin/web/dashboard-widgets.json
|
cd57b89a363b6a4acd72731dfd534524ce98b9e2938f6d20cee660063b5d06f5 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
|
fec062a5959450b1711948adf3189ceb37edca6fbead2e5bfc03b7ce397c70b1 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
|
||||||
c52c430b9a6e584012e6014709e1e16ae7e775b2a5aedff8eb767fa867c1cbdd src/admin/web/drafts.js
|
8fbc2a837d6f20a7f96c856a91fcba967b40a33494fe27ec876ee1b3b685178f src/admin/web/drafts.js
|
||||||
007c24189ad7b497d4da024fc52e6c51393f91ee135f525a356e1f14882b7632 src/admin/web/error_400.html
|
007c24189ad7b497d4da024fc52e6c51393f91ee135f525a356e1f14882b7632 src/admin/web/error_400.html
|
||||||
232b766f278ae4424705af351910677fcd7ac9a20bb8320d9a12b763c91219a2 src/admin/web/error_401.html
|
232b766f278ae4424705af351910677fcd7ac9a20bb8320d9a12b763c91219a2 src/admin/web/error_401.html
|
||||||
16293363c7848471d866092fec5e83023df087aa824989e87450cee7b7ca1263 src/admin/web/error_403.html
|
16293363c7848471d866092fec5e83023df087aa824989e87450cee7b7ca1263 src/admin/web/error_403.html
|
||||||
@@ -244,27 +244,27 @@ e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/
|
|||||||
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
|
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
|
||||||
7cbdef9192a9dee1f8eac42b85f52b3190f7f6efae50004aa7ad116a910e4036 src/admin/web/file-logs.js
|
7cbdef9192a9dee1f8eac42b85f52b3190f7f6efae50004aa7ad116a910e4036 src/admin/web/file-logs.js
|
||||||
882e99059bb2ff110c452eadc0b5779a16af1e34cb1b2536c80cf2056f8c3e8a src/admin/web/host404.js
|
882e99059bb2ff110c452eadc0b5779a16af1e34cb1b2536c80cf2056f8c3e8a src/admin/web/host404.js
|
||||||
463cdd85c53dfed869335fe4d8559554b7e555a0b44f648df94a97b1e17f6e56 src/admin/web/index.html
|
6a1c94a2b35a10c7aa406e73b52a5e04a15ff229e0c6d358cef8d607e6789c3e src/admin/web/index.html
|
||||||
7979c490b7fcfcd8f5e52e99fcf7757d78509337a625341c98f62c6da50da374 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
|
||||||
c2e5f554810b8e34fb47361fb90a01073a2e332ee26d82e030956fad3ec1f83e src/admin/web/login.js
|
c2e5f554810b8e34fb47361fb90a01073a2e332ee26d82e030956fad3ec1f83e src/admin/web/login.js
|
||||||
44b44790341067304e15974afcbbc7cc99686e88c2e43f62979d285eca6f726e src/admin/web/management.js
|
44b44790341067304e15974afcbbc7cc99686e88c2e43f62979d285eca6f726e src/admin/web/management.js
|
||||||
1f3d10e389c9c20e6eb019509fa6e82b50c6b43bbf988c1210a4d42bb73985d0 src/admin/web/partials/overview-charts.html
|
1f3d10e389c9c20e6eb019509fa6e82b50c6b43bbf988c1210a4d42bb73985d0 src/admin/web/partials/overview-charts.html
|
||||||
c8d6ab08b1fde881b1ca40222b282be039e2c4fba1b79f59454221cf893a9bde src/admin/web/partials/overview-metrics.html
|
f619250d8da61517eb2151f17b640b06057e091640280091e08afd483d67e5ff src/admin/web/partials/overview-metrics.html
|
||||||
3928afce2ed6d5f2d3e29475dd29e4c3bd3c6df180ad5119beaff0c7a58e3b3b src/admin/web/prometheus-token.js
|
3928afce2ed6d5f2d3e29475dd29e4c3bd3c6df180ad5119beaff0c7a58e3b3b src/admin/web/prometheus-token.js
|
||||||
6f33915d33d3a8bbfaeec424651a63eda3d23b0f296ae028a59ea46463ba9fdf src/admin/web/redirects.js
|
6f33915d33d3a8bbfaeec424651a63eda3d23b0f296ae028a59ea46463ba9fdf src/admin/web/redirects.js
|
||||||
76a01f590d49649dcb1f554c221290919b2a8438e6001737f3c096fa6daec74d src/admin/web/settings.js
|
76a01f590d49649dcb1f554c221290919b2a8438e6001737f3c096fa6daec74d src/admin/web/settings.js
|
||||||
8eadbada619b15087783dcfa0e889a81f88e78034d084c5ab616f7912b835940 src/admin/web/status_200.html
|
8eadbada619b15087783dcfa0e889a81f88e78034d084c5ab616f7912b835940 src/admin/web/status_200.html
|
||||||
dc3f01d1bba6fe29223983d869c509ae99bd6a40e362c6ec4605078876a05a2f 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
|
||||||
87917b0388090f2b27ec6460be56fea96dc09b42735319e0a91330748848e288 src/admin/web/vhosts/core.js
|
1657db2c683292411c53e4bc0f67784529b7bc367552901d013e2c88793d613f src/admin/web/vhosts/core.js
|
||||||
141565fbd8d2dceaa9b6f488046b96c897c94d475450db22ce854ce534336318 src/admin/web/vhosts/editor.js
|
3f5fc9649801d091708e495634192fecd76482254eb671551427d3ee580eac65 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
|
||||||
b3b9c94d779f249f09e74894659437649d40f31879d4f3077d5df941d5443454 src/admin/web/vhosts/render.js
|
d00aba52c290e8d5738a2541d2fc004d41b3bdbe256db85b2f08302e5b3b783c 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
|
||||||
@@ -561,7 +561,7 @@ f00743af2cf4a5952caef7a1dcf68e7817b5599a252a9973e1c22045517c0b0e tests/fixtures
|
|||||||
102b83171a6429b6375d8001d6c5294702385cd58ee55727cb657b1f3566e877 tests/panel_icons_browser.py
|
102b83171a6429b6375d8001d6c5294702385cd58ee55727cb657b1f3566e877 tests/panel_icons_browser.py
|
||||||
52e61c389a60d147184ee76925cac76e454f8f2e3cb91039bb06e17787ab900f tests/panel_icons_contract.js
|
52e61c389a60d147184ee76925cac76e454f8f2e3cb91039bb06e17787ab900f tests/panel_icons_contract.js
|
||||||
01af3008be27fabb486c141c67099af104f2725cc56f4c656aea9741a3c150cd tests/panel_interface_contract.py
|
01af3008be27fabb486c141c67099af104f2725cc56f4c656aea9741a3c150cd tests/panel_interface_contract.py
|
||||||
f9ac7a0117056cce2bcf1a20404db9977a9b1194c3d7df0eaef48b7e7f82b9f0 tests/panel_refresh_contract.js
|
1dac7bd3a1396fa7ddbaf58fb002e249b3280fd5c956da82004066036e1dd366 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
|
||||||
|
|||||||
@@ -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.7` and the defaults this publishes:
|
For example, with RustProxy version `0.36.8` and the defaults this publishes:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
zot.example.net/network/rustproxy:0.36.7
|
zot.example.net/network/rustproxy:0.36.8
|
||||||
zot.example.net/network/rustproxy:latest
|
zot.example.net/network/rustproxy:latest
|
||||||
zot.example.net/network/rustproxy:routeros
|
zot.example.net/network/rustproxy:routeros
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -396,6 +396,7 @@
|
|||||||
"vhImport": "Load into editor",
|
"vhImport": "Load into editor",
|
||||||
"vhMigrationHint": "Migration is saved atomically. Routes sharing one host and one backend are grouped; ambiguous host ownership is rejected.",
|
"vhMigrationHint": "Migration is saved atomically. Routes sharing one host and one backend are grouped; ambiguous host ownership is rejected.",
|
||||||
"vhEditor": "Vhost configuration",
|
"vhEditor": "Vhost configuration",
|
||||||
|
"vhCancel": "Cancel",
|
||||||
"vhReset": "Discard changes",
|
"vhReset": "Discard changes",
|
||||||
"vhSafetyHint": "Private IP binding is not authentication. Keep firewall/ACL rules. HSTS preload is opt-in; affinity cookies are not login sessions.",
|
"vhSafetyHint": "Private IP binding is not authentication. Keep firewall/ACL rules. HSTS preload is opt-in; affinity cookies are not login sessions.",
|
||||||
"vhId": "Vhost ID",
|
"vhId": "Vhost ID",
|
||||||
@@ -2291,6 +2292,27 @@
|
|||||||
"vhPoolRecycleSeconds": "Maximum upstream pool age (seconds, 0 = off)",
|
"vhPoolRecycleSeconds": "Maximum upstream pool age (seconds, 0 = off)",
|
||||||
"vhHttp1MaxBuffer": "HTTP/1 upstream max read buffer (bytes)",
|
"vhHttp1MaxBuffer": "HTTP/1 upstream max read buffer (bytes)",
|
||||||
"vhPoolRecycleHelp": "Recycling replaces only the Hyper client generation for each backend; active exchanges continue normally. Defaults bound long-lived pool/TLS buffer retention without reloading the vhost or cache. Request recycling accepts 0 or 1000..10000000; age accepts 0 or 60..604800.",
|
"vhPoolRecycleHelp": "Recycling replaces only the Hyper client generation for each backend; active exchanges continue normally. Defaults bound long-lived pool/TLS buffer retention without reloading the vhost or cache. Request recycling accepts 0 or 1000..10000000; age accepts 0 or 60..604800.",
|
||||||
|
"backendTinyTitle": "Tiny backends",
|
||||||
|
"backendTinyDescription": "Compact backend diagnostics table with search and sorting (the original 14-column view).",
|
||||||
|
"backendOverviewTitle": "Backends",
|
||||||
|
"backendOverviewDescription": "Backend health with status badges, error share, latency trend and expandable client-generation details; search, sorting, filters and column selection.",
|
||||||
|
"backendFilterAll": "All",
|
||||||
|
"backendFilterOther": "Other",
|
||||||
|
"backendFilterLabel": "Filter backends by status",
|
||||||
|
"backendSummary": "{up} of {total} up",
|
||||||
|
"backendLastEvent": "Last event",
|
||||||
|
"backendEventFailed": "Failed",
|
||||||
|
"backendEventRecovered": "Recovered",
|
||||||
|
"backendEventNone": "No failures",
|
||||||
|
"backendAgo": "{value} ago",
|
||||||
|
"backendDetailsToggle": "Show client generation details",
|
||||||
|
"processOverviewTitle": "Process",
|
||||||
|
"processOverviewDescription": "Process CPU, file descriptors, memory split and CPU/RSS history (cache memory is on the RAM cache card).",
|
||||||
|
"processClassicTitle": "Process simple",
|
||||||
|
"processLoadTitle": "Load",
|
||||||
|
"processMemoryTitle": "Memory",
|
||||||
|
"processFdHistory": "File descriptors history",
|
||||||
|
"processThreadHistory": "OS threads history",
|
||||||
"backendSearch": "Search backends",
|
"backendSearch": "Search backends",
|
||||||
"backendSearchPlaceholder": "Search vhost, backend or state",
|
"backendSearchPlaceholder": "Search vhost, backend or state",
|
||||||
"backendSortBy": "Sort by",
|
"backendSortBy": "Sort by",
|
||||||
|
|||||||
@@ -396,6 +396,7 @@
|
|||||||
"vhImport": "Wczytaj do edytora",
|
"vhImport": "Wczytaj do edytora",
|
||||||
"vhMigrationHint": "Migracja jest atomowa. Trasy tych samych hostów i backendu są grupowane; niejednoznaczne przypisanie hostów jest odrzucane.",
|
"vhMigrationHint": "Migracja jest atomowa. Trasy tych samych hostów i backendu są grupowane; niejednoznaczne przypisanie hostów jest odrzucane.",
|
||||||
"vhEditor": "Konfiguracja vhosta",
|
"vhEditor": "Konfiguracja vhosta",
|
||||||
|
"vhCancel": "Anuluj",
|
||||||
"vhReset": "Odrzuć zmiany",
|
"vhReset": "Odrzuć zmiany",
|
||||||
"vhSafetyHint": "Prywatny IP nie zastępuje uwierzytelniania. Zachowaj firewall i ACL. Preload HSTS jest opcjonalny; cookie affinity nie jest sesją logowania.",
|
"vhSafetyHint": "Prywatny IP nie zastępuje uwierzytelniania. Zachowaj firewall i ACL. Preload HSTS jest opcjonalny; cookie affinity nie jest sesją logowania.",
|
||||||
"vhId": "Identyfikator vhosta",
|
"vhId": "Identyfikator vhosta",
|
||||||
@@ -2291,6 +2292,27 @@
|
|||||||
"vhPoolRecycleSeconds": "Maks. wiek puli upstream (sekundy, 0 = wył.)",
|
"vhPoolRecycleSeconds": "Maks. wiek puli upstream (sekundy, 0 = wył.)",
|
||||||
"vhHttp1MaxBuffer": "Maks. bufor odczytu HTTP/1 upstream (bajty)",
|
"vhHttp1MaxBuffer": "Maks. bufor odczytu HTTP/1 upstream (bajty)",
|
||||||
"vhPoolRecycleHelp": "Odświeżanie podmienia tylko generację klienta Hyper danego backendu; aktywne wymiany kończą się normalnie. Domyślne limity ograniczają retencję pamięci puli/TLS bez przeładowania vhosta ani cache. Limit żądań: 0 lub 1000..10000000; limit czasu: 0 lub 60..604800.",
|
"vhPoolRecycleHelp": "Odświeżanie podmienia tylko generację klienta Hyper danego backendu; aktywne wymiany kończą się normalnie. Domyślne limity ograniczają retencję pamięci puli/TLS bez przeładowania vhosta ani cache. Limit żądań: 0 lub 1000..10000000; limit czasu: 0 lub 60..604800.",
|
||||||
|
"backendTinyTitle": "Backendy (kompaktowe)",
|
||||||
|
"backendTinyDescription": "Kompaktowa tabela diagnostyki backendów z wyszukiwaniem i sortowaniem (dotychczasowy widok 14 kolumn).",
|
||||||
|
"backendOverviewTitle": "Backendy",
|
||||||
|
"backendOverviewDescription": "Stan backendów ze znacznikami statusu, udziałem błędów, trendem opóźnień i rozwijanymi szczegółami generacji klienta; wyszukiwanie, sortowanie, filtry i wybór kolumn.",
|
||||||
|
"backendFilterAll": "Wszystkie",
|
||||||
|
"backendFilterOther": "Inne",
|
||||||
|
"backendFilterLabel": "Filtruj backendy według statusu",
|
||||||
|
"backendSummary": "{up} z {total} działa",
|
||||||
|
"backendLastEvent": "Ostatnie zdarzenie",
|
||||||
|
"backendEventFailed": "Awaria",
|
||||||
|
"backendEventRecovered": "Przywrócony",
|
||||||
|
"backendEventNone": "Brak awarii",
|
||||||
|
"backendAgo": "{value} temu",
|
||||||
|
"backendDetailsToggle": "Pokaż szczegóły generacji klienta",
|
||||||
|
"processOverviewTitle": "Proces",
|
||||||
|
"processOverviewDescription": "Obciążenie procesu, deskryptory plików, podział pamięci oraz historia CPU i RSS (pamięć cache jest w karcie RAM cache).",
|
||||||
|
"processClassicTitle": "Proces prosty",
|
||||||
|
"processLoadTitle": "Obciążenie",
|
||||||
|
"processMemoryTitle": "Pamięć",
|
||||||
|
"processFdHistory": "Historia deskryptorów",
|
||||||
|
"processThreadHistory": "Historia wątków systemowych",
|
||||||
"backendSearch": "Szukaj backendów",
|
"backendSearch": "Szukaj backendów",
|
||||||
"backendSearchPlaceholder": "Szukaj vhosta, backendu lub stanu",
|
"backendSearchPlaceholder": "Szukaj vhosta, backendu lub stanu",
|
||||||
"backendSortBy": "Sortuj według",
|
"backendSortBy": "Sortuj według",
|
||||||
|
|||||||
@@ -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.7",
|
"version": "0.36.8",
|
||||||
"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": [
|
||||||
@@ -3953,8 +3953,10 @@
|
|||||||
"connection_chart",
|
"connection_chart",
|
||||||
"cache_diagnostics",
|
"cache_diagnostics",
|
||||||
"process_diagnostics",
|
"process_diagnostics",
|
||||||
|
"process_simple",
|
||||||
"network_diagnostics",
|
"network_diagnostics",
|
||||||
"backend_table",
|
"backend_table",
|
||||||
|
"backend_tiny",
|
||||||
"extra_cold_queue_memory",
|
"extra_cold_queue_memory",
|
||||||
"extra_cold_storage",
|
"extra_cold_storage",
|
||||||
"wide_transform_activity",
|
"wide_transform_activity",
|
||||||
@@ -4038,8 +4040,10 @@
|
|||||||
"connection_chart",
|
"connection_chart",
|
||||||
"cache_diagnostics",
|
"cache_diagnostics",
|
||||||
"process_diagnostics",
|
"process_diagnostics",
|
||||||
|
"process_simple",
|
||||||
"network_diagnostics",
|
"network_diagnostics",
|
||||||
"backend_table"
|
"backend_table",
|
||||||
|
"backend_tiny"
|
||||||
],
|
],
|
||||||
"description": "Account-scoped order for configurable Overview telemetry cards."
|
"description": "Account-scoped order for configurable Overview telemetry cards."
|
||||||
},
|
},
|
||||||
@@ -4130,8 +4134,10 @@
|
|||||||
"connection_chart",
|
"connection_chart",
|
||||||
"cache_diagnostics",
|
"cache_diagnostics",
|
||||||
"process_diagnostics",
|
"process_diagnostics",
|
||||||
|
"process_simple",
|
||||||
"network_diagnostics",
|
"network_diagnostics",
|
||||||
"backend_table",
|
"backend_table",
|
||||||
|
"backend_tiny",
|
||||||
"extra_cold_queue_memory",
|
"extra_cold_queue_memory",
|
||||||
"extra_cold_storage",
|
"extra_cold_storage",
|
||||||
"wide_transform_activity",
|
"wide_transform_activity",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ fn overview_factory_preset_matches_registry_and_operator_visibility() {
|
|||||||
"wide_compression_activity",
|
"wide_compression_activity",
|
||||||
"cache",
|
"cache",
|
||||||
"backend_table",
|
"backend_table",
|
||||||
|
"process_diagnostics",
|
||||||
] {
|
] {
|
||||||
assert!(order.contains(&id.to_owned()), "not registered: {id}");
|
assert!(order.contains(&id.to_owned()), "not registered: {id}");
|
||||||
assert!(
|
assert!(
|
||||||
@@ -28,6 +29,8 @@ fn overview_factory_preset_matches_registry_and_operator_visibility() {
|
|||||||
for id in [
|
for id in [
|
||||||
"bandwidth",
|
"bandwidth",
|
||||||
"backend_health",
|
"backend_health",
|
||||||
|
"backend_tiny",
|
||||||
|
"process_simple",
|
||||||
"extra_process_cpu",
|
"extra_process_cpu",
|
||||||
"stream_traffic",
|
"stream_traffic",
|
||||||
"wide_cache_activity",
|
"wide_cache_activity",
|
||||||
|
|||||||
@@ -61,15 +61,23 @@ function translate() {
|
|||||||
syncSettingsDirtyIndicators();
|
syncSettingsDirtyIndicators();
|
||||||
window.rustProxyIcons?.refresh?.();
|
window.rustProxyIcons?.refresh?.();
|
||||||
}
|
}
|
||||||
function hideToast() { const node = $("toast"); if (typeof node.hidePopover === "function" && node.matches(":popover-open")) node.hidePopover(); node.hidden = true; }
|
function toastNode() {
|
||||||
|
let node = document.getElementById("toast");
|
||||||
|
if (!node) { // the node lived inside a dialog that was rebuilt; recreate it instead of failing every later toast
|
||||||
|
node = document.createElement("div"); node.id = "toast"; node.className = "toast"; node.hidden = true;
|
||||||
|
node.setAttribute("role", "status"); node.setAttribute("aria-live", "polite"); node.setAttribute("popover", "manual"); document.body.append(node);
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
function hideToast() { const node = toastNode(); if (typeof node.hidePopover === "function" && node.matches(":popover-open")) node.hidePopover(); node.hidden = true; }
|
||||||
function raiseToast() {
|
function raiseToast() {
|
||||||
const node = $("toast"); if (node.hidden) return;
|
const node = toastNode(); if (node.hidden) return;
|
||||||
const modal = [...document.querySelectorAll("dialog[open]")].at(-1), parent = modal || document.body;
|
const modal = [...document.querySelectorAll("dialog[open]")].at(-1), parent = modal || document.body;
|
||||||
if (typeof node.hidePopover === "function" && node.matches(":popover-open")) node.hidePopover();
|
if (typeof node.hidePopover === "function" && node.matches(":popover-open")) node.hidePopover();
|
||||||
if (node.parentElement !== parent) parent.append(node);
|
if (node.parentElement !== parent) parent.append(node);
|
||||||
if (typeof node.showPopover === "function") node.showPopover();
|
if (typeof node.showPopover === "function") node.showPopover();
|
||||||
}
|
}
|
||||||
function toast(message, error = false) { clearTimeout(toastTimer); const node = $("toast"); node.textContent = message; node.classList.toggle("error", error); node.hidden = false; raiseToast(); toastTimer = setTimeout(hideToast, error ? 10000 : 4000); }
|
function toast(message, error = false) { clearTimeout(toastTimer); const node = toastNode(); node.textContent = message; node.classList.toggle("error", error); node.hidden = false; raiseToast(); toastTimer = setTimeout(hideToast, error ? 10000 : 4000); }
|
||||||
function authMessage(message = "") { const node = $("auth-message"); if (!node) return; node.textContent = message; node.classList.toggle("error", Boolean(message)); }
|
function authMessage(message = "") { const node = $("auth-message"); if (!node) return; node.textContent = message; node.classList.toggle("error", Boolean(message)); }
|
||||||
function fieldError(target, message) { const error = new Error(message); error.fieldTarget = target; return error; }
|
function fieldError(target, message) { const error = new Error(message); error.fieldTarget = target; return error; }
|
||||||
function clearFieldError(target) {
|
function clearFieldError(target) {
|
||||||
@@ -134,7 +142,7 @@ function apiErrorMessage(path, response, value) {
|
|||||||
const area = apiArea(path);
|
const area = apiArea(path);
|
||||||
return area ? t("errorContext").replace("{area}", t(area)).replace("{message}", detail) : detail;
|
return area ? t("errorContext").replace("{area}", t(area)).replace("{message}", detail) : detail;
|
||||||
}
|
}
|
||||||
new MutationObserver(records => { if (!records.some(r => r.target.tagName === "DIALOG")) return; const node = $("toast"); if (!node.hidden) raiseToast(); }).observe(document.body, { subtree: true, attributes: true, attributeFilter: ["open"] });
|
new MutationObserver(records => { if (!records.some(r => r.target.tagName === "DIALOG")) return; const node = toastNode(); if (!node.hidden) raiseToast(); }).observe(document.body, { subtree: true, attributes: true, attributeFilter: ["open"] });
|
||||||
async function api(path, options = {}) {
|
async function api(path, options = {}) {
|
||||||
const started = performance.now(), trackLatency = path.startsWith("/api/");
|
const started = performance.now(), trackLatency = path.startsWith("/api/");
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ window.rustProxyDashboardLayout = (() => {
|
|||||||
if (ADMIN_DASHBOARD_BY_ID.get(id)?.category === "panels") {
|
if (ADMIN_DASHBOARD_BY_ID.get(id)?.category === "panels") {
|
||||||
const card = el("article", undefined, "overview-layout-panel-preview");
|
const card = el("article", undefined, "overview-layout-panel-preview");
|
||||||
card.append(el("strong", label(id)));
|
card.append(el("strong", label(id)));
|
||||||
const chart = id === "backend_table" ? null : original.querySelector?.("svg");
|
const chart = id === "backend_tiny" || id === "backend_table" ? null : original.querySelector?.("svg");
|
||||||
if (chart) {
|
if (chart) {
|
||||||
const graphic = chart.cloneNode(true);
|
const graphic = chart.cloneNode(true);
|
||||||
for (const node of [graphic, ...graphic.querySelectorAll("[id]")]) node.removeAttribute("id");
|
for (const node of [graphic, ...graphic.querySelectorAll("[id]")]) node.removeAttribute("id");
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ $("reset-certificate").addEventListener("click", () => { $("certificate-form").r
|
|||||||
syncCertificateFileSource();
|
syncCertificateFileSource();
|
||||||
|
|
||||||
let systemSamples = [], connectionOpenSamples = [], connectionExchangeSamples = [], cacheMemorySamples = [], cacheTransientSamples = [], cacheFillSamples = [], cacheColdQueueSamples = [], cacheColdIoSamples = [], cacheColdStorageSamples = [], cacheHitSamples = [], cacheMissSamples = [], cacheBypassSamples = [], lastCacheCounters = null, lastCacheOutcomeRates = null,
|
let systemSamples = [], connectionOpenSamples = [], connectionExchangeSamples = [], cacheMemorySamples = [], cacheTransientSamples = [], cacheFillSamples = [], cacheColdQueueSamples = [], cacheColdIoSamples = [], cacheColdStorageSamples = [], cacheHitSamples = [], cacheMissSamples = [], cacheBypassSamples = [], lastCacheCounters = null, lastCacheOutcomeRates = null,
|
||||||
processCpuSamples = [], processRssSamples = [], networkRxSamples = [], networkTxSamples = [], responseLatencySamples = [], networkInterfaceSamples = new Map(), settingsDirty = new Set();
|
processCpuSamples = [], processRssSamples = [], processFdSamples = [], processThreadSamples = [], networkRxSamples = [], networkTxSamples = [], responseLatencySamples = [], networkInterfaceSamples = new Map(), settingsDirty = new Set();
|
||||||
const hiddenNetworkInterfaces = (() => {
|
const hiddenNetworkInterfaces = (() => {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(localStorage.getItem("rp-hidden-network-interfaces") || "[]");
|
const parsed = JSON.parse(localStorage.getItem("rp-hidden-network-interfaces") || "[]");
|
||||||
@@ -257,6 +257,68 @@ function setOptionalMetric(id, supported, text) {
|
|||||||
const row = node.closest("div"); if (row) row.hidden = !supported;
|
const row = node.closest("div"); if (row) row.hidden = !supported;
|
||||||
if (supported) node.textContent = text;
|
if (supported) node.textContent = text;
|
||||||
}
|
}
|
||||||
|
function setProcessMeter(id, used, limit) {
|
||||||
|
const meter = $(id)?.closest("div")?.querySelector(".pr-meter"); if (!meter) return;
|
||||||
|
const usedNumber = Number(used), limitNumber = Number(limit);
|
||||||
|
const ok = used != null && limit != null && Number.isFinite(usedNumber) && Number.isFinite(limitNumber) && limitNumber > 0;
|
||||||
|
meter.hidden = !ok; if (!ok) return;
|
||||||
|
const percent = Math.max(0, Math.min(100, usedNumber / limitNumber * 100));
|
||||||
|
const bar = meter.firstElementChild; bar.style.width = `${percent}%`; bar.classList.toggle("is-high", percent >= 85);
|
||||||
|
}
|
||||||
|
function renderProcessOverview(d, status, live, systemSample) {
|
||||||
|
if (!$("po-cpu")) return;
|
||||||
|
if (systemSample) {
|
||||||
|
const memory = status.memory_accounting || {};
|
||||||
|
const platform = [d.platform, d.environment].filter(Boolean).join(" / ");
|
||||||
|
setOptionalMetric("po-platform", !!d.platform && d.platform !== "Unsupported", platform);
|
||||||
|
setOptionalMetric("po-threads", d.process_threads != null, fmt(d.process_threads));
|
||||||
|
setOptionalMetric("po-start", d.process_start_time_seconds != null, d.process_start_time_seconds == null ? "" : formatDateTime(Number(d.process_start_time_seconds) * 1000));
|
||||||
|
setOptionalMetric("po-uptime", status.uptime_seconds != null, status.uptime_seconds == null ? "" : uptime(status.uptime_seconds));
|
||||||
|
setOptionalMetric("po-cpu", d.process_cpu_percent != null, `${fmt(d.process_cpu_percent, 1)}%`);
|
||||||
|
setProcessMeter("po-cpu", d.process_cpu_percent, 100);
|
||||||
|
setOptionalMetric("po-rss-total", d.process_rss_bytes != null, fmtBytes(d.process_rss_bytes));
|
||||||
|
setProcessMeter("po-rss-total", d.process_rss_bytes, d.cgroup_memory_max_bytes);
|
||||||
|
const fds = knownPair(d.open_fds, d.max_fds, value => fmt(value), t("unlimited"));
|
||||||
|
setOptionalMetric("po-fds", fds != null, fds);
|
||||||
|
setProcessMeter("po-fds", d.open_fds, d.max_fds);
|
||||||
|
const cgroupMemory = knownPair(d.cgroup_memory_current_bytes, d.cgroup_memory_max_bytes, value => fmtBytes(value), t("unlimited"));
|
||||||
|
setOptionalMetric("po-cgroup", cgroupMemory != null, cgroupMemory);
|
||||||
|
setProcessMeter("po-cgroup", d.cgroup_memory_current_bytes, d.cgroup_memory_max_bytes);
|
||||||
|
setOptionalMetric("po-rss-anon", d.process_rss_anon_bytes != null, fmtBytes(d.process_rss_anon_bytes));
|
||||||
|
const rssFile = knownPair(d.process_rss_file_bytes, d.process_rss_shmem_bytes, value => fmtBytes(value));
|
||||||
|
setOptionalMetric("po-rss-file", rssFile != null, rssFile);
|
||||||
|
setOptionalMetric("po-non-cache", memory.non_cache_rss_bytes != null, fmtBytes(memory.non_cache_rss_bytes));
|
||||||
|
const virtualMemory = knownPair(d.process_virtual_memory_bytes, d.process_virtual_memory_max_bytes, value => fmtBytes(value), t("unlimited"));
|
||||||
|
setOptionalMetric("po-virtual", virtualMemory != null, virtualMemory);
|
||||||
|
const dataSwap = knownPair(d.process_data_bytes, d.process_swap_bytes, value => fmtBytes(value));
|
||||||
|
setOptionalMetric("po-data-swap", dataSwap != null, dataSwap);
|
||||||
|
const split = $("po-rss-split"), anon = Number(d.process_rss_anon_bytes), file = Number(d.process_rss_file_bytes);
|
||||||
|
const known = d.process_rss_anon_bytes != null && d.process_rss_file_bytes != null && anon + file > 0;
|
||||||
|
split.hidden = !known;
|
||||||
|
if (known) { split.firstElementChild.style.width = `${anon / (anon + file) * 100}%`; split.lastElementChild.style.width = `${file / (anon + file) * 100}%`; }
|
||||||
|
}
|
||||||
|
$("po-cpu-sparkline").setAttribute("points", sparklinePoints(processCpuSamples));
|
||||||
|
window.rustProxyChartScale.axes("po-cpu-sparkline", Math.max(1, ...processCpuSamples), "percent",
|
||||||
|
processCpuSamples.length, { top: 5 / 60, bottom: 55 / 60 });
|
||||||
|
setChartText($("po-cpu-chart-value"), live.process_cpu_percent == null ? "" : `${fmt(live.process_cpu_percent, 1)}%`);
|
||||||
|
$("po-rss-sparkline").setAttribute("points", sparklinePoints(processRssSamples));
|
||||||
|
window.rustProxyChartScale.axes("po-rss-sparkline", Math.max(1, ...processRssSamples), "bytes",
|
||||||
|
processRssSamples.length, { top: 5 / 60, bottom: 55 / 60 });
|
||||||
|
setChartText($("po-rss-chart-value"), live.process_rss_bytes == null ? "" : fmtBytes(live.process_rss_bytes));
|
||||||
|
$("po-cpu-sparkline").closest(".mini-live-chart").hidden = live.process_cpu_percent == null;
|
||||||
|
$("po-rss-sparkline").closest(".mini-live-chart").hidden = live.process_rss_bytes == null;
|
||||||
|
const openFdsNow = live.open_fds ?? d.open_fds, threadsNow = live.process_threads ?? d.process_threads;
|
||||||
|
$("po-fd-sparkline").setAttribute("points", sparklinePoints(processFdSamples));
|
||||||
|
window.rustProxyChartScale.axes("po-fd-sparkline", Math.max(1, ...processFdSamples), "count",
|
||||||
|
processFdSamples.length, { top: 5 / 60, bottom: 55 / 60 });
|
||||||
|
setChartText($("po-fd-chart-value"), openFdsNow == null ? "" : fmt(openFdsNow));
|
||||||
|
$("po-fd-sparkline").closest(".mini-live-chart").hidden = openFdsNow == null;
|
||||||
|
$("po-thread-sparkline").setAttribute("points", sparklinePoints(processThreadSamples));
|
||||||
|
window.rustProxyChartScale.axes("po-thread-sparkline", Math.max(1, ...processThreadSamples), "count",
|
||||||
|
processThreadSamples.length, { top: 5 / 60, bottom: 55 / 60 });
|
||||||
|
setChartText($("po-thread-chart-value"), threadsNow == null ? "" : fmt(threadsNow));
|
||||||
|
$("po-thread-sparkline").closest(".mini-live-chart").hidden = threadsNow == null;
|
||||||
|
}
|
||||||
function knownPair(left, right, format, zeroRight = null) {
|
function knownPair(left, right, format, zeroRight = null) {
|
||||||
if (left == null && right == null) return null;
|
if (left == null && right == null) return null;
|
||||||
if (left == null) return format(right);
|
if (left == null) return format(right);
|
||||||
@@ -528,6 +590,9 @@ function renderDiagnostics(status, appendSample = true, systemSample = appendSam
|
|||||||
if (appendSample) {
|
if (appendSample) {
|
||||||
if (live.process_cpu_percent != null) { processCpuSamples.push(Number(live.process_cpu_percent)); processCpuSamples = processCpuSamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
|
if (live.process_cpu_percent != null) { processCpuSamples.push(Number(live.process_cpu_percent)); processCpuSamples = processCpuSamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
|
||||||
if (live.process_rss_bytes != null) { processRssSamples.push(Number(live.process_rss_bytes)); processRssSamples = processRssSamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
|
if (live.process_rss_bytes != null) { processRssSamples.push(Number(live.process_rss_bytes)); processRssSamples = processRssSamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
|
||||||
|
const openFds = live.open_fds ?? d.open_fds, threadCount = live.process_threads ?? d.process_threads;
|
||||||
|
if (openFds != null) { processFdSamples.push(Number(openFds)); processFdSamples = processFdSamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
|
||||||
|
if (threadCount != null) { processThreadSamples.push(Number(threadCount)); processThreadSamples = processThreadSamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
|
||||||
}
|
}
|
||||||
const cacheOutcomePeak = Math.max(1, ...cacheHitSamples, ...cacheMissSamples, ...cacheBypassSamples);
|
const cacheOutcomePeak = Math.max(1, ...cacheHitSamples, ...cacheMissSamples, ...cacheBypassSamples);
|
||||||
$("cache-hit-sparkline").setAttribute("points", sparklinePoints(cacheHitSamples, 300, 60, 5, cacheOutcomePeak));
|
$("cache-hit-sparkline").setAttribute("points", sparklinePoints(cacheHitSamples, 300, 60, 5, cacheOutcomePeak));
|
||||||
@@ -599,6 +664,7 @@ function renderDiagnostics(status, appendSample = true, systemSample = appendSam
|
|||||||
setChartText($("process-rss-chart-value"), live.process_rss_bytes == null ? "" : fmtBytes(live.process_rss_bytes));
|
setChartText($("process-rss-chart-value"), live.process_rss_bytes == null ? "" : fmtBytes(live.process_rss_bytes));
|
||||||
$("process-cpu-sparkline").closest(".mini-live-chart").hidden = live.process_cpu_percent == null;
|
$("process-cpu-sparkline").closest(".mini-live-chart").hidden = live.process_cpu_percent == null;
|
||||||
$("process-rss-sparkline").closest(".mini-live-chart").hidden = live.process_rss_bytes == null;
|
$("process-rss-sparkline").closest(".mini-live-chart").hidden = live.process_rss_bytes == null;
|
||||||
|
renderProcessOverview(d, status, live, systemSample);
|
||||||
updateCacheMemorySizing();
|
updateCacheMemorySizing();
|
||||||
const networkRateSupported = live.network_rx_bytes_per_second != null && live.network_tx_bytes_per_second != null;
|
const networkRateSupported = live.network_rx_bytes_per_second != null && live.network_tx_bytes_per_second != null;
|
||||||
if (systemSample) setOptionalMetric("system-network", d.network_rx_bytes_per_second != null && d.network_tx_bytes_per_second != null, `${fmtBytes(d.network_rx_bytes_per_second)} / ${fmtBytes(d.network_tx_bytes_per_second)}`);
|
if (systemSample) setOptionalMetric("system-network", d.network_rx_bytes_per_second != null && d.network_tx_bytes_per_second != null, `${fmtBytes(d.network_rx_bytes_per_second)} / ${fmtBytes(d.network_tx_bytes_per_second)}`);
|
||||||
@@ -628,6 +694,7 @@ function renderDiagnostics(status, appendSample = true, systemSample = appendSam
|
|||||||
$("system-sample-age").hidden = age == null;
|
$("system-sample-age").hidden = age == null;
|
||||||
if (age != null) $("system-sample-age").textContent = `${fmt(age)} s`;
|
if (age != null) $("system-sample-age").textContent = `${fmt(age)} s`;
|
||||||
$("system-sample-age").classList.toggle("status-error", age != null && age > 10);
|
$("system-sample-age").classList.toggle("status-error", age != null && age > 10);
|
||||||
|
if ($("po-sample-age")) { $("po-sample-age").hidden = age == null; if (age != null) $("po-sample-age").textContent = `${fmt(age)} s`; $("po-sample-age").classList.toggle("status-error", age != null && age > 10); }
|
||||||
const l = status.listeners || {};
|
const l = status.listeners || {};
|
||||||
$("listener-pending").hidden = !l.restart_required;
|
$("listener-pending").hidden = !l.restart_required;
|
||||||
$("listener-pending").textContent = t("restartPending");
|
$("listener-pending").textContent = t("restartPending");
|
||||||
|
|||||||
@@ -758,7 +758,8 @@ progress::-moz-progress-bar {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pool-search {
|
#pool-search,
|
||||||
|
#bo-search {
|
||||||
width: calc(min(250px, 30vw));
|
width: calc(min(250px, 30vw));
|
||||||
min-width: 170px;
|
min-width: 170px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2349,3 +2349,108 @@ html[data-dashboard-density] #panel-overview .stat :is(.stat-chart-legend, .over
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Backends overview card (0.36.7): follows table density (global table rules), corner style tokens and interface colours */
|
||||||
|
#panel-overview .backend-overview-card.is-no-proxy { display: none !important; }
|
||||||
|
.bo-heading { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px; min-width: 0; }
|
||||||
|
.bo-summary { color: var(--muted); font: 11px var(--mono); }
|
||||||
|
.bo-filters { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 10px; }
|
||||||
|
.bo-chip { display: inline-flex; align-items: center; gap: 8px; padding: 6px 10px; border: 1px solid var(--line); border-radius: var(--rp-radius-control, 0); background: transparent; color: var(--muted); font: 500 12px/1 var(--sans); cursor: pointer; }
|
||||||
|
.bo-chip:hover { color: var(--text); border-color: var(--border-hover); }
|
||||||
|
.bo-chip[aria-pressed=true] { color: var(--text); background: var(--nav-active-bg); border-color: var(--pill-line); }
|
||||||
|
.bo-chip b { color: var(--text-subtle); font: 600 11px var(--mono); }
|
||||||
|
.bo-chip[aria-pressed=true] b { color: var(--accent); }
|
||||||
|
.bo-chip.is-down b:not(.is-zero) { color: var(--red); }
|
||||||
|
.backend-overview-card .table-scroll { max-height: 520px; overflow: auto; border-top: 1px solid var(--line); }
|
||||||
|
.bo-table { width: 100%; min-width: 960px; border-collapse: collapse; font-size: 12px; }
|
||||||
|
.bo-table th { position: sticky; top: 0; z-index: 1; padding: 8px 10px; background: var(--panel); color: var(--muted); font: 600 10px/1.2 var(--sans); letter-spacing: .05em; text-align: left; text-transform: uppercase; white-space: nowrap; }
|
||||||
|
.bo-table td { padding: 9px 10px; border-top: 1px solid var(--row-line); vertical-align: middle; white-space: nowrap; }
|
||||||
|
.bo-table .is-num { font-family: var(--mono); font-variant-numeric: tabular-nums; text-align: right; }
|
||||||
|
.bo-table th.is-num { font-family: var(--sans); }
|
||||||
|
.bo-table .is-zero { color: var(--text-subtle); }
|
||||||
|
.bo-row:hover>td { background: var(--panel2); }
|
||||||
|
.bo-row.is-disabled>td:not(:first-child) { opacity: .6; }
|
||||||
|
.bo-row>td:first-child { box-shadow: inset 3px 0 0 var(--line); }
|
||||||
|
.bo-row.is-up>td:first-child { box-shadow: inset 3px 0 0 var(--pill-live-line); }
|
||||||
|
.bo-row.is-down>td:first-child { box-shadow: inset 3px 0 0 var(--red); }
|
||||||
|
.bo-row.is-warn>td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
|
||||||
|
.bo-expand { width: 24px; height: 24px; min-width: 0; padding: 0; border: 1px solid transparent; background: transparent; color: var(--muted); cursor: pointer; line-height: 1; }
|
||||||
|
.bo-expand::before { content: "›"; display: inline-block; font-size: 15px; transition: transform .15s ease; }
|
||||||
|
.bo-expand[aria-expanded=true]::before { transform: rotate(90deg); }
|
||||||
|
.bo-expand:hover { color: var(--text); border-color: var(--field-line); }
|
||||||
|
.bo-identity { max-width: 360px; }
|
||||||
|
.bo-name { overflow: hidden; color: var(--text); font-weight: 600; text-overflow: ellipsis; }
|
||||||
|
.bo-sub { margin-top: 2px; overflow: hidden; color: var(--muted); font: .88em var(--mono); text-overflow: ellipsis; }
|
||||||
|
.bo-badge { display: inline-flex; align-items: center; gap: 7px; padding: 2px 8px; border: 1px solid var(--field-line); border-radius: var(--rp-radius-control, 0); color: var(--muted); font: .82em var(--mono); letter-spacing: 1px; text-transform: uppercase; }
|
||||||
|
.bo-badge>i { width: 6px; height: 6px; border-radius: 50%; background: var(--text-subtle); }
|
||||||
|
.bo-badge.is-up { border-color: var(--pill-live-line); color: var(--green); }
|
||||||
|
.bo-badge.is-up>i { background: var(--green); }
|
||||||
|
.bo-badge.is-down { border-color: var(--danger-line-strong); color: var(--red); }
|
||||||
|
.bo-badge.is-down>i { background: var(--red); }
|
||||||
|
.bo-badge.is-warn { border-color: var(--warn-line); color: var(--accent); }
|
||||||
|
.bo-badge.is-warn>i { background: var(--accent); }
|
||||||
|
.bo-errors { min-width: 130px; }
|
||||||
|
.bo-meter-top { display: flex; justify-content: space-between; gap: 8px; font-family: var(--mono); font-variant-numeric: tabular-nums; }
|
||||||
|
.bo-meter-top small { color: var(--text-subtle); font-size: .9em; }
|
||||||
|
.bo-meter { height: 2px; margin-top: 5px; overflow: hidden; background: var(--line); border-radius: var(--rp-progress-radius, 0); }
|
||||||
|
.bo-meter>i { display: block; height: 100%; background: var(--accent); border-radius: var(--rp-progress-radius, 0); }
|
||||||
|
.bo-meter>i.is-high { background: var(--red); }
|
||||||
|
.bo-latency { min-width: 130px; font-family: var(--mono); font-variant-numeric: tabular-nums; }
|
||||||
|
.bo-latency>span { display: inline-block; min-width: 62px; }
|
||||||
|
.bo-spark { width: 56px; height: 18px; margin-left: 6px; vertical-align: middle; }
|
||||||
|
.bo-spark polyline { fill: none; stroke: var(--green); stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
|
||||||
|
.bo-row.is-down .bo-spark polyline { stroke: var(--red); }
|
||||||
|
.bo-row.is-warn .bo-spark polyline { stroke: var(--accent); }
|
||||||
|
.bo-event small { display: block; margin-top: 1px; color: var(--text-subtle); font-size: .9em; }
|
||||||
|
.bo-event .is-failed, .bo-detail-item .is-failed { color: var(--red); }
|
||||||
|
.bo-event .is-recovered, .bo-detail-item .is-recovered { color: var(--green); }
|
||||||
|
.bo-detail-row>td { padding: 0 10px 10px 40px; border-top: 0; white-space: normal; }
|
||||||
|
.bo-detail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; padding: 10px 12px; border: 1px solid var(--row-line); border-radius: var(--rp-radius-surface, 0); background: var(--bg); }
|
||||||
|
.bo-detail-item span { display: block; margin-bottom: 3px; color: var(--text-subtle); font-size: 11px; }
|
||||||
|
.bo-detail-item b { font: 500 12px var(--mono); overflow-wrap: anywhere; }
|
||||||
|
html[data-table-density=normal] .bo-table td { padding-top: 10px; padding-bottom: 10px; }
|
||||||
|
html[data-table-density=ultra] .bo-table th { padding: 4px 8px; }
|
||||||
|
html[data-table-density=ultra] .bo-table td { padding-left: 8px; padding-right: 8px; }
|
||||||
|
html[data-table-density=ultra] .bo-sub { display: none; }
|
||||||
|
html[data-table-density=ultra] .bo-spark { width: 40px; height: 12px; }
|
||||||
|
html[data-table-density=ultra] .bo-meter { margin-top: 2px; }
|
||||||
|
html[data-table-density=ultra] .bo-event small { display: inline; margin-left: 6px; }
|
||||||
|
html[data-table-density=ultra] .bo-badge { padding: 0 6px; }
|
||||||
|
html[data-table-density=ultra] .bo-chip { padding: 4px 8px; font-size: 11px; }
|
||||||
|
html[data-table-density=compact] .bo-spark { width: 48px; height: 15px; }
|
||||||
|
html[data-table-density=compact] .bo-chip { padding: 5px 9px; }
|
||||||
|
html[data-table-density=compact] .bo-meter { margin-top: 3px; }
|
||||||
|
|
||||||
|
/* Process overview card (process_diagnostics) */
|
||||||
|
[data-dashboard-widget="process_diagnostics"] { display: flex; flex-direction: column; min-height: 560px; }
|
||||||
|
.pr-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 0; }
|
||||||
|
.pr-chip { display: inline-flex; align-items: baseline; gap: 7px; margin: 0; padding: 3px 9px; border: 1px solid var(--line); border-radius: var(--rp-radius-control, 0); font: 11px var(--mono); }
|
||||||
|
.pr-chip dt { color: var(--text-subtle); font-size: 10px; letter-spacing: .05em; text-transform: uppercase; }
|
||||||
|
.pr-chip dd { margin: 0; font-weight: 500; }
|
||||||
|
.pr-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 0 22px; margin-top: 6px; }
|
||||||
|
.pr-title { margin: 10px 0 4px; color: var(--muted); font: 600 10px var(--sans); letter-spacing: .06em; text-transform: uppercase; }
|
||||||
|
.pr-list { margin: 0; }
|
||||||
|
.pr-row { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 2px 10px; padding: 7px 2px; border-top: 1px solid var(--row-line); }
|
||||||
|
.pr-row dt { color: var(--muted); font-size: 11px; }
|
||||||
|
.pr-row dd { margin: 0 0 0 auto; font: 12px var(--mono); font-variant-numeric: tabular-nums; text-align: end; overflow-wrap: anywhere; }
|
||||||
|
.pr-meter { flex: 0 0 100%; height: 2px; margin-top: 3px; overflow: hidden; background: var(--line); border-radius: var(--rp-progress-radius, 0); }
|
||||||
|
.pr-meter b { display: block; width: 0; height: 100%; background: var(--accent); border-radius: var(--rp-progress-radius, 0); }
|
||||||
|
.pr-meter b.is-high { background: var(--red); }
|
||||||
|
.pr-split { display: flex; height: 6px; margin: 2px 0 4px; overflow: hidden; background: var(--line); border-radius: var(--rp-progress-radius, 0); }
|
||||||
|
.pr-split .a { display: block; background: var(--accent); }
|
||||||
|
.pr-split .f { display: block; background: var(--blue); }
|
||||||
|
.pr-charts { flex: 1 1 auto; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 10px; }
|
||||||
|
.pr-charts .mini-live-chart { display: flex; flex-direction: column; margin-top: 0; }
|
||||||
|
.pr-charts .mini-live-chart svg:not(.ui-icon) { flex: 1 1 0; height: 0; min-height: 56px; }
|
||||||
|
.diagnostic-line.pr-line-blue { stroke: var(--blue); }
|
||||||
|
.diagnostic-line.pr-line-muted { stroke: var(--muted); }
|
||||||
|
html[data-table-density=compact] .pr-row { padding-top: 6px; padding-bottom: 6px; }
|
||||||
|
html[data-table-density=ultra] .pr-row { padding-top: 3px; padding-bottom: 3px; }
|
||||||
|
html[data-table-density=ultra] .pr-chip { padding: 1px 7px; }
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
[data-dashboard-widget="process_diagnostics"] { min-height: 0; }
|
||||||
|
.pr-cols, .pr-charts { grid-template-columns: 1fr; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* initial width of the cache outcome segments (set from JS via CSSOM; inline style attributes are blocked by the CSP) */
|
||||||
|
.cache-mix-hit, .cache-mix-miss, .cache-mix-bypass, .cache-mix-other { width: 0; }
|
||||||
@@ -1202,3 +1202,21 @@ html[data-table-density=ultra] .file-log-inline-detail pre {
|
|||||||
.cache-target-text { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
.cache-target-text { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
.cache-target-wrap .cache-transform-badge { flex: 0 0 auto; margin-left: 0; }
|
.cache-target-wrap .cache-transform-badge { flex: 0 0 auto; margin-left: 0; }
|
||||||
|
|
||||||
|
|
||||||
|
/* Cache pager (0.36.7): never wraps inside its frame; the heading wraps instead */
|
||||||
|
#panel-cache .card-heading:has(.cache-pager) { flex-wrap: wrap; row-gap: 8px; }
|
||||||
|
.cache-pager { flex-wrap: nowrap; flex: 0 0 auto; max-width: 100%; margin-left: auto; }
|
||||||
|
.cache-pager button { display: inline-flex; align-items: center; justify-content: center; gap: 6px; white-space: nowrap; flex: 0 0 auto; }
|
||||||
|
.cache-pager #cache-page { flex: 1 1 auto; white-space: nowrap; }
|
||||||
|
|
||||||
|
/* Cache pager follows the interface rounding setting: frame and the two outer buttons share the control radius */
|
||||||
|
.cache-pager { overflow: visible; border-radius: var(--rp-radius-control, 0px); }
|
||||||
|
.cache-pager button { border-radius: 0; }
|
||||||
|
.cache-pager>button:first-child { border-radius: max(0px, calc(var(--rp-radius-control, 0px) - 1px)) 0 0 max(0px, calc(var(--rp-radius-control, 0px) - 1px)); }
|
||||||
|
.cache-pager>button:last-child { border-radius: 0 max(0px, calc(var(--rp-radius-control, 0px) - 1px)) max(0px, calc(var(--rp-radius-control, 0px) - 1px)) 0; }
|
||||||
|
.cache-pager button:focus-visible { outline-offset: -2px; }
|
||||||
|
|
||||||
|
/* The global corner-style rule forces the full control radius on every button with !important; the pager is a segmented control, so only its outer corners are rounded */
|
||||||
|
#panel-cache .cache-pager>button { border-radius: 0 !important; }
|
||||||
|
#panel-cache .cache-pager>button:first-child { border-radius: max(0px, calc(var(--rp-radius-control, 0px) - 1px)) 0 0 max(0px, calc(var(--rp-radius-control, 0px) - 1px)) !important; }
|
||||||
|
#panel-cache .cache-pager>button:last-child { border-radius: 0 max(0px, calc(var(--rp-radius-control, 0px) - 1px)) max(0px, calc(var(--rp-radius-control, 0px) - 1px)) 0 !important; }
|
||||||
@@ -819,7 +819,8 @@
|
|||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pool-search {
|
#pool-search,
|
||||||
|
#bo-search {
|
||||||
flex: 1 1 100%;
|
flex: 1 1 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|||||||
@@ -65,8 +65,10 @@
|
|||||||
"cache_diagnostics",
|
"cache_diagnostics",
|
||||||
"extra_cold_queue_memory",
|
"extra_cold_queue_memory",
|
||||||
"process_diagnostics",
|
"process_diagnostics",
|
||||||
|
"process_simple",
|
||||||
"network_diagnostics",
|
"network_diagnostics",
|
||||||
"backend_table",
|
"backend_table",
|
||||||
|
"backend_tiny",
|
||||||
"www_status",
|
"www_status",
|
||||||
"www_kind",
|
"www_kind",
|
||||||
"www_capacity",
|
"www_capacity",
|
||||||
@@ -140,7 +142,9 @@
|
|||||||
"extra_backends_healthy",
|
"extra_backends_healthy",
|
||||||
"extra_error_share",
|
"extra_error_share",
|
||||||
"extra_rejected_rate",
|
"extra_rejected_rate",
|
||||||
"extra_cert_expiry"
|
"extra_cert_expiry",
|
||||||
|
"backend_tiny",
|
||||||
|
"process_simple"
|
||||||
],
|
],
|
||||||
"dashboard_density": "compact",
|
"dashboard_density": "compact",
|
||||||
"overview_samples": 600
|
"overview_samples": 600
|
||||||
|
|||||||
@@ -705,8 +705,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "process_diagnostics",
|
"id": "process_diagnostics",
|
||||||
"label_key": "processDiagnostics",
|
"label_key": "processOverviewTitle",
|
||||||
"description_key": "overviewPanelProcessDescription",
|
"description_key": "processOverviewDescription",
|
||||||
"element_selector": "[data-dashboard-widget=\"process_diagnostics\"]",
|
"element_selector": "[data-dashboard-widget=\"process_diagnostics\"]",
|
||||||
"data_source": "system_status",
|
"data_source": "system_status",
|
||||||
"no_data": "neutral_dash",
|
"no_data": "neutral_dash",
|
||||||
@@ -714,6 +714,17 @@
|
|||||||
"default_hidden": false,
|
"default_hidden": false,
|
||||||
"display_type": "panel"
|
"display_type": "panel"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "process_simple",
|
||||||
|
"label_key": "processClassicTitle",
|
||||||
|
"description_key": "overviewPanelProcessDescription",
|
||||||
|
"element_selector": "[data-dashboard-widget=\"process_simple\"]",
|
||||||
|
"data_source": "system_status",
|
||||||
|
"no_data": "neutral_dash",
|
||||||
|
"category": "panels",
|
||||||
|
"default_hidden": true,
|
||||||
|
"display_type": "panel"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "network_diagnostics",
|
"id": "network_diagnostics",
|
||||||
"label_key": "networkDiagnostics",
|
"label_key": "networkDiagnostics",
|
||||||
@@ -727,8 +738,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "backend_table",
|
"id": "backend_table",
|
||||||
"label_key": "vhHealthTitle",
|
"label_key": "backendOverviewTitle",
|
||||||
"description_key": "overviewPanelBackendsDescription",
|
"description_key": "backendOverviewDescription",
|
||||||
"element_selector": "[data-dashboard-widget=\"backend_table\"]",
|
"element_selector": "[data-dashboard-widget=\"backend_table\"]",
|
||||||
"data_source": "backend_status",
|
"data_source": "backend_status",
|
||||||
"no_data": "neutral_dash",
|
"no_data": "neutral_dash",
|
||||||
@@ -736,6 +747,17 @@
|
|||||||
"default_hidden": false,
|
"default_hidden": false,
|
||||||
"display_type": "table"
|
"display_type": "table"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "backend_tiny",
|
||||||
|
"label_key": "backendTinyTitle",
|
||||||
|
"description_key": "backendTinyDescription",
|
||||||
|
"element_selector": "[data-dashboard-widget=\"backend_tiny\"]",
|
||||||
|
"data_source": "backend_status",
|
||||||
|
"no_data": "neutral_dash",
|
||||||
|
"category": "panels",
|
||||||
|
"default_hidden": true,
|
||||||
|
"display_type": "table"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "www_status",
|
"id": "www_status",
|
||||||
"label_key": "webMetricStatus",
|
"label_key": "webMetricStatus",
|
||||||
|
|||||||
@@ -98,5 +98,11 @@
|
|||||||
run: async () => render()
|
run: async () => render()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.addEventListener("click", async event => {
|
||||||
|
const button = event.target.closest?.("[data-editor-cancel]"); if (!button) return;
|
||||||
|
const api = editors.get(button.dataset.editorCancel); if (!api?.isOpen?.()) return;
|
||||||
|
if (api.isDirty?.() && !(await appConfirm(t("draftDiscardConfirm"), { danger: true, confirmLabel: t("discardAction") }))) return;
|
||||||
|
api.close?.();
|
||||||
|
});
|
||||||
window.rustProxyDrafts = { register, dock: dockEditor, prepare, reset, refresh: render, captureControls, restoreControls, takeChildren, restoreChildren };
|
window.rustProxyDrafts = { register, dock: dockEditor, prepare, reset, refresh: render, captureControls, restoreControls, takeChildren, restoreChildren };
|
||||||
})();
|
})();
|
||||||
+194
-81
@@ -261,9 +261,87 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card" data-dashboard-widget="process_diagnostics">
|
<article class="card pr-card" data-dashboard-widget="process_diagnostics">
|
||||||
<div class="mini-heading">
|
<div class="mini-heading">
|
||||||
<h3 data-i18n="processDiagnostics">Process</h3>
|
<h3 data-i18n="processOverviewTitle">Process</h3>
|
||||||
|
<div class="mini-heading-actions">
|
||||||
|
<details class="metric-info">
|
||||||
|
<summary data-i18n-aria="memoryAccountingInfo"
|
||||||
|
aria-label="Memory accounting information">i</summary>
|
||||||
|
<div class="metric-info-popover">
|
||||||
|
<p class="fine" data-i18n="memoryAccountingHint">RSS is the real process
|
||||||
|
footprint. HOT/transient cache values are RustProxy-accounted
|
||||||
|
allocations; transform memory is a conservative active reservation.
|
||||||
|
Non-cache RSS is RSS minus accounted cache and includes transforms,
|
||||||
|
connections, TLS, logging, Tokio/runtime state, allocator overhead and
|
||||||
|
other buffers, so these rows are not all additive.</p>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
<span class="meta" id="po-sample-age">-</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<dl class="pr-chips">
|
||||||
|
<div class="pr-chip"><dt data-i18n="platform">Platform</dt><dd id="po-platform">-</dd></div>
|
||||||
|
<div class="pr-chip"><dt data-i18n="processThreads">OS threads</dt><dd id="po-threads">-</dd></div>
|
||||||
|
<div class="pr-chip"><dt data-i18n="processStartTime">Process start</dt><dd id="po-start">-</dd></div>
|
||||||
|
<div class="pr-chip"><dt data-i18n="uptime">Uptime</dt><dd id="po-uptime">-</dd></div>
|
||||||
|
</dl>
|
||||||
|
<div class="pr-cols">
|
||||||
|
<section>
|
||||||
|
<h4 class="pr-title" data-i18n="processLoadTitle">Load</h4>
|
||||||
|
<dl class="pr-list">
|
||||||
|
<div class="pr-row"><dt data-i18n="processCpu">CPU (100% = 1 core)</dt><dd id="po-cpu">-</dd><i class="pr-meter"><b></b></i></div>
|
||||||
|
<div class="pr-row"><dt data-i18n="processRssTotal">RSS total</dt><dd id="po-rss-total">-</dd><i class="pr-meter"><b></b></i></div>
|
||||||
|
<div class="pr-row"><dt data-i18n="fileDescriptors">File descriptors / limit</dt><dd id="po-fds">-</dd><i class="pr-meter"><b></b></i></div>
|
||||||
|
<div class="pr-row"><dt data-i18n="containerMemory">cgroup RAM / limit</dt><dd id="po-cgroup">-</dd><i class="pr-meter"><b></b></i></div>
|
||||||
|
</dl>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h4 class="pr-title" data-i18n="processMemoryTitle">Memory</h4>
|
||||||
|
<div class="pr-split" id="po-rss-split" aria-hidden="true"><i class="a"></i><i class="f"></i></div>
|
||||||
|
<dl class="pr-list pr-plain">
|
||||||
|
<div class="pr-row"><dt data-i18n="processRssAnon">Anonymous RSS</dt><dd id="po-rss-anon">-</dd></div>
|
||||||
|
<div class="pr-row"><dt data-i18n="processRssFile">File-backed / shared RSS</dt><dd id="po-rss-file">-</dd></div>
|
||||||
|
<div class="pr-row"><dt data-i18n="memoryNonCacheRss">Non-cache process RSS</dt><dd id="po-non-cache">-</dd></div>
|
||||||
|
<div class="pr-row"><dt data-i18n="processVirtualMemory">Virtual memory / limit</dt><dd id="po-virtual">-</dd></div>
|
||||||
|
<div class="pr-row"><dt data-i18n="processDataSwap">Data address space / swap</dt><dd id="po-data-swap">-</dd></div>
|
||||||
|
</dl>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="mini-live-chart-stack pr-charts">
|
||||||
|
<div class="mini-live-chart">
|
||||||
|
<div class="mini-live-chart-head"><span data-i18n="processCpuHistory">Process CPU history</span><b id="po-cpu-chart-value">-</b></div>
|
||||||
|
<svg preserveAspectRatio="none" aria-label="Process CPU history" data-i18n-aria="processCpuHistory" role="img" viewbox="0 0 300 60">
|
||||||
|
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||||||
|
<polyline class="diagnostic-line" id="po-cpu-sparkline" points="0,55 300,55"></polyline>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="mini-live-chart">
|
||||||
|
<div class="mini-live-chart-head"><span data-i18n="processRssHistory">Process RSS history</span><b id="po-rss-chart-value">-</b></div>
|
||||||
|
<svg preserveAspectRatio="none" aria-label="Process RSS history" data-i18n-aria="processRssHistory" role="img" viewbox="0 0 300 60">
|
||||||
|
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||||||
|
<polyline class="diagnostic-line process-rss-line" id="po-rss-sparkline" points="0,55 300,55"></polyline>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="mini-live-chart">
|
||||||
|
<div class="mini-live-chart-head"><span data-i18n="processFdHistory">File descriptors history</span><b id="po-fd-chart-value">-</b></div>
|
||||||
|
<svg preserveAspectRatio="none" aria-label="File descriptors history" data-i18n-aria="processFdHistory" role="img" viewbox="0 0 300 60">
|
||||||
|
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||||||
|
<polyline class="diagnostic-line pr-line-blue" id="po-fd-sparkline" points="0,55 300,55"></polyline>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="mini-live-chart">
|
||||||
|
<div class="mini-live-chart-head"><span data-i18n="processThreadHistory">OS threads history</span><b id="po-thread-chart-value">-</b></div>
|
||||||
|
<svg preserveAspectRatio="none" aria-label="OS threads history" data-i18n-aria="processThreadHistory" role="img" viewbox="0 0 300 60">
|
||||||
|
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||||||
|
<polyline class="diagnostic-line pr-line-muted" id="po-thread-sparkline" points="0,55 300,55"></polyline>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<article class="card" data-dashboard-widget="process_simple">
|
||||||
|
<div class="mini-heading">
|
||||||
|
<h3 data-i18n="processClassicTitle">Process simple</h3>
|
||||||
<div class="mini-heading-actions">
|
<div class="mini-heading-actions">
|
||||||
<details class="metric-info">
|
<details class="metric-info">
|
||||||
<summary data-i18n-aria="memoryAccountingInfo"
|
<summary data-i18n-aria="memoryAccountingInfo"
|
||||||
@@ -279,78 +357,77 @@
|
|||||||
</details>
|
</details>
|
||||||
<span class="meta" id="system-sample-age">-</span>
|
<span class="meta" id="system-sample-age">-</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</d <dl class="mini-metrics">
|
||||||
<dl class="mini-metrics">
|
<div>
|
||||||
<div>
|
<dt data-i18n="platform">Platform</dt>
|
||||||
<dt data-i18n="platform">Platform</dt>
|
<dd id="process-platform">-</dd>
|
||||||
<dd id="process-platform">-</dd>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
<dt data-i18n="processCpu">CPU (100% = 1 core)</dt>
|
||||||
<dt data-i18n="processCpu">CPU (100% = 1 core)</dt>
|
<dd id="process-cpu">-</dd>
|
||||||
<dd id="process-cpu">-</dd>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
<dt data-i18n="fileDescriptors">File descriptors / limit</dt>
|
||||||
<dt data-i18n="fileDescriptors">File descriptors / limit</dt>
|
<dd id="process-fds">-</dd>
|
||||||
<dd id="process-fds">-</dd>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
<dt data-i18n="processThreads">OS threads</dt>
|
||||||
<dt data-i18n="processThreads">OS threads</dt>
|
<dd id="process-threads">-</dd>
|
||||||
<dd id="process-threads">-</dd>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
<dt data-i18n="processVirtualMemory">Virtual memory / limit</dt>
|
||||||
<dt data-i18n="processVirtualMemory">Virtual memory / limit</dt>
|
<dd id="process-virtual-memory">-</dd>
|
||||||
<dd id="process-virtual-memory">-</dd>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
<dt data-i18n="processStartTime">Process start</dt>
|
||||||
<dt data-i18n="processStartTime">Process start</dt>
|
<dd id="process-start-time">-</dd>
|
||||||
<dd id="process-start-time">-</dd>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
<dt data-i18n="uptime">Uptime</dt>
|
||||||
<dt data-i18n="uptime">Uptime</dt>
|
<dd id="process-uptime">-</dd>
|
||||||
<dd id="process-uptime">-</dd>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
<dt data-i18n="processRssTotal">RSS total</dt>
|
||||||
<dt data-i18n="processRssTotal">RSS total</dt>
|
<dd id="process-rss-total">-</dd>
|
||||||
<dd id="process-rss-total">-</dd>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
<dt data-i18n="memoryCacheHot">HOT cache RAM / limit</dt>
|
||||||
<dt data-i18n="memoryCacheHot">HOT cache RAM / limit</dt>
|
<dd id="memory-cache-hot">-</dd>
|
||||||
<dd id="memory-cache-hot">-</dd>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
<dt data-i18n="memoryCacheTransient">Cache transient RAM / limit</dt>
|
||||||
<dt data-i18n="memoryCacheTransient">Cache transient RAM / limit</dt>
|
<dd id="memory-cache-transient">-</dd>
|
||||||
<dd id="memory-cache-transient">-</dd>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
<dt data-i18n="memoryTransform">Transform reserved RAM / budget</dt>
|
||||||
<dt data-i18n="memoryTransform">Transform reserved RAM / budget</dt>
|
<dd id="memory-transform">-</dd>
|
||||||
<dd id="memory-transform">-</dd>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
<dt data-i18n="memoryTransformInflight">Active transforms / limit</dt>
|
||||||
<dt data-i18n="memoryTransformInflight">Active transforms / limit</dt>
|
<dd id="memory-transform-inflight">-</dd>
|
||||||
<dd id="memory-transform-inflight">-</dd>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
<dt data-i18n="memoryNonCacheRss">Non-cache process RSS</dt>
|
||||||
<dt data-i18n="memoryNonCacheRss">Non-cache process RSS</dt>
|
<dd id="memory-non-cache-rss">-</dd>
|
||||||
<dd id="memory-non-cache-rss">-</dd>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
<dt data-i18n="processRssAnon">Anonymous RSS</dt>
|
||||||
<dt data-i18n="processRssAnon">Anonymous RSS</dt>
|
<dd id="process-rss-anon">-</dd>
|
||||||
<dd id="process-rss-anon">-</dd>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
<dt data-i18n="processRssFile">File-backed / shared RSS</dt>
|
||||||
<dt data-i18n="processRssFile">File-backed / shared RSS</dt>
|
<dd id="process-rss-file">-</dd>
|
||||||
<dd id="process-rss-file">-</dd>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
<dt data-i18n="processDataSwap">Data address space / swap</dt>
|
||||||
<dt data-i18n="processDataSwap">Data address space / swap</dt>
|
<dd id="process-data-swap">-</dd>
|
||||||
<dd id="process-data-swap">-</dd>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
<dt data-i18n="containerMemory">cgroup RAM / limit</dt>
|
||||||
<dt data-i18n="containerMemory">cgroup RAM / limit</dt>
|
<dd id="cgroup-memory">-</dd>
|
||||||
<dd id="cgroup-memory">-</dd>
|
</div>
|
||||||
</div>
|
</dl>
|
||||||
</dl>
|
<div class="mini-live-chart-stack">
|
||||||
<div class="mini-live-chart-stack">
|
|
||||||
<div class="mini-live-chart">
|
<div class="mini-live-chart">
|
||||||
<div class="mini-live-chart-head"><span data-i18n="processCpuHistory">Process CPU
|
<div class="mini-live-chart-head"><span data-i18n="processCpuHistory">Process CPU
|
||||||
history</span><b id="process-cpu-chart-value">-</b></div>
|
history</span><b id="process-cpu-chart-value">-</b></div>
|
||||||
@@ -433,10 +510,46 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card pool-card" id="backend-diagnostics-card"
|
<article class="card pool-card backend-overview-card" id="backend-overview-card"
|
||||||
data-dashboard-widget="backend_table">
|
data-dashboard-widget="backend_table">
|
||||||
<div class="card-heading backend-card-heading">
|
<div class="card-heading backend-card-heading">
|
||||||
<h2 data-i18n="vhHealthTitle">Backends</h2>
|
<div class="bo-heading">
|
||||||
|
<h2 data-i18n="backendOverviewTitle">Backends</h2>
|
||||||
|
<span class="bo-summary" id="bo-summary" aria-live="polite"></span>
|
||||||
|
</div>
|
||||||
|
<div class="backend-table-tools">
|
||||||
|
<label class="sr-only" data-i18n="backendSearch" for="bo-search">Search
|
||||||
|
backends</label>
|
||||||
|
<input autocomplete="off" data-i18n-aria="backendSearch"
|
||||||
|
data-i18n-placeholder="backendSearchPlaceholder" id="bo-search"
|
||||||
|
type="search" />
|
||||||
|
<label class="backend-sort-control"><span data-i18n="backendSortBy">Sort
|
||||||
|
by</span><select data-i18n-aria="backendSortBy" id="bo-sort"></select></label>
|
||||||
|
<button class="quiet backend-sort-direction" data-i18n-aria="backendSortAscending"
|
||||||
|
id="bo-sort-direction" type="button">↑</button>
|
||||||
|
<details class="backend-column-picker">
|
||||||
|
<summary class="quiet" data-i18n="backendColumns">Columns</summary>
|
||||||
|
<div class="backend-column-options" id="bo-column-options"></div>
|
||||||
|
</details>
|
||||||
|
<button class="quiet" data-i18n="vhManage" id="bo-open" type="button">Manage
|
||||||
|
vhosts</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bo-filters" id="bo-filters" role="group"></div>
|
||||||
|
<div class="table-scroll">
|
||||||
|
<table class="bo-table">
|
||||||
|
<thead>
|
||||||
|
<tr id="bo-head"></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="bo-rows"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<p class="fine" data-i18n="vhHealthEmpty" id="bo-empty">No backend diagnostics yet.</p>
|
||||||
|
</article>
|
||||||
|
<article class="card pool-card" id="backend-diagnostics-card"
|
||||||
|
data-dashboard-widget="backend_tiny">
|
||||||
|
<div class="card-heading backend-card-heading">
|
||||||
|
<h2 data-i18n="backendTinyTitle">Tiny backends</h2>
|
||||||
<div class="backend-table-tools">
|
<div class="backend-table-tools">
|
||||||
<label class="sr-only" data-i18n="backendSearch" for="pool-search">Search
|
<label class="sr-only" data-i18n="backendSearch" for="pool-search">Search
|
||||||
backends</label>
|
backends</label>
|
||||||
@@ -4120,7 +4233,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button class="primary" data-i18n="apply" type="submit">Validate, save and apply</button>
|
<button class="primary" data-i18n="apply" type="submit">Validate, save and apply</button>
|
||||||
<button class="quiet" data-i18n="vhReset" id="web-service-reset" type="button">Discard changes</button>
|
<button class="quiet" data-i18n="vhReset" id="web-service-reset" type="button">Discard changes</button><button class="quiet" data-editor-cancel="web-server" data-i18n="vhCancel" type="button">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
@@ -4267,7 +4380,7 @@ log_errors = On</code></pre>
|
|||||||
<div id="vhost-fields"></div>
|
<div id="vhost-fields"></div>
|
||||||
<div class="form-actions"><button class="primary" data-i18n="apply" id="vhost-save" type="submit">Validate,
|
<div class="form-actions"><button class="primary" data-i18n="apply" id="vhost-save" type="submit">Validate,
|
||||||
save and apply</button><button class="quiet" data-i18n="vhReset" id="vhost-reset"
|
save and apply</button><button class="quiet" data-i18n="vhReset" id="vhost-reset"
|
||||||
type="button">Discard changes</button></div>
|
type="button">Discard changes</button><button class="quiet" data-editor-cancel="vhost" data-i18n="vhCancel" type="button">Cancel</button></div>
|
||||||
<p class="fine" data-i18n="vhSafetyHint">Private IP binding is not authentication. Keep firewall/ACL rules.
|
<p class="fine" data-i18n="vhSafetyHint">Private IP binding is not authentication. Keep firewall/ACL rules.
|
||||||
HSTS preload is opt-in; affinity cookies are not login sessions.</p>
|
HSTS preload is opt-in; affinity cookies are not login sessions.</p>
|
||||||
</form>
|
</form>
|
||||||
@@ -4332,7 +4445,7 @@ log_errors = On</code></pre>
|
|||||||
</section>
|
</section>
|
||||||
<div class="form-actions"><button class="primary" data-i18n="apply" type="submit">Validate, save and
|
<div class="form-actions"><button class="primary" data-i18n="apply" type="submit">Validate, save and
|
||||||
apply</button><button class="quiet" data-i18n="vhReset" id="redirect-reset" type="button">Discard
|
apply</button><button class="quiet" data-i18n="vhReset" id="redirect-reset" type="button">Discard
|
||||||
changes</button></div>
|
changes</button><button class="quiet" data-editor-cancel="redirect" data-i18n="vhCancel" type="button">Cancel</button></div>
|
||||||
<p class="fine" data-i18n="redirectSafetyHint">Redirect hosts do not contact a backend. Global host ACLs
|
<p class="fine" data-i18n="redirectSafetyHint">Redirect hosts do not contact a backend. Global host ACLs
|
||||||
still apply. Avoid self-referential rules that can create browser redirect loops.</p>
|
still apply. Avoid self-referential rules that can create browser redirect loops.</p>
|
||||||
</form>
|
</form>
|
||||||
@@ -4403,7 +4516,7 @@ log_errors = On</code></pre>
|
|||||||
</section>
|
</section>
|
||||||
<div class="form-actions"><button class="primary" data-i18n="apply" type="submit">Validate, save and
|
<div class="form-actions"><button class="primary" data-i18n="apply" type="submit">Validate, save and
|
||||||
apply</button><button class="quiet" data-i18n="vhReset" id="notfound-reset" type="button">Discard
|
apply</button><button class="quiet" data-i18n="vhReset" id="notfound-reset" type="button">Discard
|
||||||
changes</button></div>
|
changes</button><button class="quiet" data-editor-cancel="notfound" data-i18n="vhCancel" type="button">Cancel</button></div>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
@@ -4419,7 +4532,7 @@ log_errors = On</code></pre>
|
|||||||
<div id="stream-fields"></div>
|
<div id="stream-fields"></div>
|
||||||
<div class="form-actions"><button class="primary" data-i18n="apply" id="stream-save" type="submit">Validate,
|
<div class="form-actions"><button class="primary" data-i18n="apply" id="stream-save" type="submit">Validate,
|
||||||
save and apply</button><button class="quiet" data-i18n="vhReset" id="stream-reset"
|
save and apply</button><button class="quiet" data-i18n="vhReset" id="stream-reset"
|
||||||
type="button">Discard changes</button></div>
|
type="button">Discard changes</button><button class="quiet" data-editor-cancel="stream" data-i18n="vhCancel" type="button">Cancel</button></div>
|
||||||
<p class="fine" data-i18n="streamRestartHelp">Changing a stream listener requires a RustProxy restart.</p>
|
<p class="fine" data-i18n="streamRestartHelp">Changing a stream listener requires a RustProxy restart.</p>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|||||||
@@ -117,7 +117,7 @@
|
|||||||
function addRow(item = { id: `listener-${$("named-listener-rows").children.length + 1}`, name: "", scope: "internal", enabled: true, binding: { address: "127.0.0.1:8181", tls: false, http2: false, http2_settings: null, h2c: false, http3: null } }) {
|
function addRow(item = { id: `listener-${$("named-listener-rows").children.length + 1}`, name: "", scope: "internal", enabled: true, binding: { address: "127.0.0.1:8181", tls: false, http2: false, http2_settings: null, h2c: false, http3: null } }) {
|
||||||
if ($("named-listener-rows").children.length >= 64) { toast(t("listenerLimit"), true); return; }
|
if ($("named-listener-rows").children.length >= 64) { toast(t("listenerLimit"), true); return; }
|
||||||
const row = el("fieldset", undefined, "named-listener-row"); row.append(label("legend", "listenerEndpoint")); const grid = el("div", undefined, "vh-grid"); row.append(grid); const b = item.binding || {};
|
const row = el("fieldset", undefined, "named-listener-row"); row.append(label("legend", "listenerEndpoint")); const grid = el("div", undefined, "vh-grid"); row.append(grid); const b = item.binding || {};
|
||||||
const id = field(grid, "listener-id", "listenerId", item.id, "text", { required: true, maxLength: 64, pattern: "[A-Za-z0-9_-]{1,64}" });
|
const id = field(grid, "listener-id", "listenerId", item.id, "text", { required: true, maxLength: 64, pattern: "[A-Za-z0-9_\\-]{1,64}" });
|
||||||
const name = field(grid, "listener-name", "listenerName", item.name, "text", { maxLength: 96 });
|
const name = field(grid, "listener-name", "listenerName", item.name, "text", { maxLength: 96 });
|
||||||
const scope = field(grid, "listener-scope", "listenerScope", item.scope || "internal", "select", { values: [["internal", "listenerInternal"], ["public", "listenerPublic"]] });
|
const scope = field(grid, "listener-scope", "listenerScope", item.scope || "internal", "select", { values: [["internal", "listenerInternal"], ["public", "listenerPublic"]] });
|
||||||
const currentAddresses = [b.address, ...(item.additional_addresses || [])].filter(Boolean);
|
const currentAddresses = [b.address, ...(item.additional_addresses || [])].filter(Boolean);
|
||||||
|
|||||||
@@ -40,10 +40,10 @@
|
|||||||
<strong id="cache-mix-headline">-</strong>
|
<strong id="cache-mix-headline">-</strong>
|
||||||
<div class="cache-mix-bar" id="cache-mix-bar" role="img"
|
<div class="cache-mix-bar" id="cache-mix-bar" role="img"
|
||||||
aria-label="Cumulative HTTP cache outcomes since service start">
|
aria-label="Cumulative HTTP cache outcomes since service start">
|
||||||
<span class="cache-mix-hit" id="cache-mix-hit" style="width:0%"></span>
|
<span class="cache-mix-hit" id="cache-mix-hit"></span>
|
||||||
<span class="cache-mix-miss" id="cache-mix-miss" style="width:0%"></span>
|
<span class="cache-mix-miss" id="cache-mix-miss"></span>
|
||||||
<span class="cache-mix-bypass" id="cache-mix-bypass" style="width:0%"></span>
|
<span class="cache-mix-bypass" id="cache-mix-bypass"></span>
|
||||||
<span class="cache-mix-other" id="cache-mix-other" style="width:0%"></span>
|
<span class="cache-mix-other" id="cache-mix-other"></span>
|
||||||
</div>
|
</div>
|
||||||
<small class="cache-mix-legend">
|
<small class="cache-mix-legend">
|
||||||
<span class="cache-mix-hit-key" id="cache-mix-hit-key"><i></i>HIT <b id="cache-mix-hit-count">-</b></span>
|
<span class="cache-mix-hit-key" id="cache-mix-hit-key"><i></i>HIT <b id="cache-mix-hit-count">-</b></span>
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
return grid;
|
return grid;
|
||||||
};
|
};
|
||||||
const connectionFields = backendSection("streamBackendConnectionSection");
|
const connectionFields = backendSection("streamBackendConnectionSection");
|
||||||
field(connectionFields, "backend-id", "streamBackendId", backend.id, "text", { required: true, maxLength: 64, pattern: "[A-Za-z0-9_-]{1,64}" });
|
field(connectionFields, "backend-id", "streamBackendId", backend.id, "text", { required: true, maxLength: 64, pattern: "[A-Za-z0-9_\\-]{1,64}" });
|
||||||
field(connectionFields, "backend-name", "streamBackendName", backend.name || "", "text", { maxLength: 96 });
|
field(connectionFields, "backend-name", "streamBackendName", backend.name || "", "text", { maxLength: 96 });
|
||||||
field(connectionFields, "backend-address", "streamBackendAddress", backend.address, "text", { required: true, maxLength: 128 });
|
field(connectionFields, "backend-address", "streamBackendAddress", backend.address, "text", { required: true, maxLength: 128 });
|
||||||
field(connectionFields, "backend-weight", "streamBackendWeight", backend.weight ?? 1, "number", { min: 1, max: 16, required: true });
|
field(connectionFields, "backend-weight", "streamBackendWeight", backend.weight ?? 1, "number", { min: 1, max: 16, required: true });
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
$("stream-editing").textContent = t(editing ? "streamEditLabel" : "streamNewLabel");
|
$("stream-editing").textContent = t(editing ? "streamEditLabel" : "streamNewLabel");
|
||||||
let g = group("streamEditor", "details");
|
let g = group("streamEditor", "details");
|
||||||
const streamName = field(g, "name", "streamName", value.name, "text", { maxLength: 96 });
|
const streamName = field(g, "name", "streamName", value.name, "text", { maxLength: 96 });
|
||||||
const streamIdInput = field(g, "id", "streamId", value.id, "text", { required: true, maxLength: 64, pattern: "[A-Za-z0-9_-]{1,64}", readOnly: Boolean(editing) });
|
const streamIdInput = field(g, "id", "streamId", value.id, "text", { required: true, maxLength: 64, pattern: "[A-Za-z0-9_\\-]{1,64}", readOnly: Boolean(editing) });
|
||||||
window.rustProxyAutoId.attach(streamIdInput, { elements: [streamName], taken: () => (envelope?.config.streams || []).map(item => item.id) });
|
window.rustProxyAutoId.attach(streamIdInput, { elements: [streamName], taken: () => (envelope?.config.streams || []).map(item => item.id) });
|
||||||
const protocol = field(g, "protocol", "streamProtocol", value.protocol || "tcp", "select", { values: [["tcp", "streamTcp"], ["udp", "streamUdp"]] });
|
const protocol = field(g, "protocol", "streamProtocol", value.protocol || "tcp", "select", { values: [["tcp", "streamTcp"], ["udp", "streamUdp"]] });
|
||||||
const enabledControl = field(g, "enabled", "vhEnabled", value.enabled !== false, "checkbox");
|
const enabledControl = field(g, "enabled", "vhEnabled", value.enabled !== false, "checkbox");
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ function memberRow(parent, value = { id: `origin-${parent.children.length + 1}`,
|
|||||||
identity.append(title, endpoint); head.append(identity); const remove = button("remove", () => { row.remove(); backendHealthEditorControl?.refresh(); changed(); }); head.append(remove); row.append(head);
|
identity.append(title, endpoint); head.append(identity); const remove = button("remove", () => { row.remove(); backendHealthEditorControl?.refresh(); changed(); }); head.append(remove); row.append(head);
|
||||||
|
|
||||||
const main = el("div", undefined, "vh-grid vh-backend-main"); row.append(main);
|
const main = el("div", undefined, "vh-grid vh-backend-main"); row.append(main);
|
||||||
const id = field(main, "member-id", "vhMemberId", value.id, "text", { pattern: "[A-Za-z0-9_-]{1,64}", maxLength: 64 });
|
const id = field(main, "member-id", "vhMemberId", value.id, "text", { pattern: "[A-Za-z0-9_\\-]{1,64}", maxLength: 64 });
|
||||||
const url = field(main, "member-url", "vhUrl", value.url, "url", { maxLength: 512 });
|
const url = field(main, "member-url", "vhUrl", value.url, "url", { maxLength: 512 });
|
||||||
field(main, "member-weight", "vhWeight", value.weight ?? 1, "number", { required: true, min: 1, max: 256 });
|
field(main, "member-weight", "vhWeight", value.weight ?? 1, "number", { required: true, min: 1, max: 256 });
|
||||||
const protocol = field(main, "member-protocol", "vhProtocol", value.protocol || "http1", "select", { values: [["http1", "vhProtocolHttp1"], ["auto", "vhProtocolAuto"], ["http2", "vhProtocolHttp2"], ["h2", "vhProtocolH2Strict"]] });
|
const protocol = field(main, "member-protocol", "vhProtocol", value.protocol || "http1", "select", { values: [["http1", "vhProtocolHttp1"], ["auto", "vhProtocolAuto"], ["http2", "vhProtocolHttp2"], ["h2", "vhProtocolH2Strict"]] });
|
||||||
@@ -379,7 +379,7 @@ function locationsEditor(parent, values) {
|
|||||||
const row = el("details", undefined, "vh-location"); row.open = true;
|
const row = el("details", undefined, "vh-location"); row.open = true;
|
||||||
const summary = el("summary", undefined, "location-summary"), body = el("div", undefined, "vh-grid location-body"); row.append(summary, body);
|
const summary = el("summary", undefined, "location-summary"), body = el("div", undefined, "vh-grid location-body"); row.append(summary, body);
|
||||||
const used = new Set([...rows.children].map(r => str("location-id", r))); let suffix = 1; while (used.has(`location-${suffix}`)) suffix++;
|
const used = new Set([...rows.children].map(r => str("location-id", r))); let suffix = 1; while (used.has(`location-${suffix}`)) suffix++;
|
||||||
field(body, "location-id", "locationId", value.id || `location-${suffix}`, "text", { required: true, pattern: "[A-Za-z0-9_-]{1,64}", maxLength: 64 });
|
field(body, "location-id", "locationId", value.id || `location-${suffix}`, "text", { required: true, pattern: "[A-Za-z0-9_\\-]{1,64}", maxLength: 64 });
|
||||||
const match = field(body, "location-match", "locationMatchType", value.match_type || "prefix", "select", { values: [["prefix", "locationMatchPrefix"], ["prefix_priority", "locationMatchPriority"], ["exact", "locationMatchExact"], ["regex", "locationMatchRegex"], ["regex_insensitive", "locationMatchRegexI"]] });
|
const match = field(body, "location-match", "locationMatchType", value.match_type || "prefix", "select", { values: [["prefix", "locationMatchPrefix"], ["prefix_priority", "locationMatchPriority"], ["exact", "locationMatchExact"], ["regex", "locationMatchRegex"], ["regex_insensitive", "locationMatchRegexI"]] });
|
||||||
const path = field(body, "location-path", "locationPathPattern", value.path_prefix || "/", "text", { required: true, maxLength: 1024 }); path.placeholder = "/api or ^/api/v[0-9]+/";
|
const path = field(body, "location-path", "locationPathPattern", value.path_prefix || "/", "text", { required: true, maxLength: 1024 }); path.placeholder = "/api or ^/api/v[0-9]+/";
|
||||||
const internal = field(body, "location-internal", "locationInternal", value.internal === true, "checkbox");
|
const internal = field(body, "location-internal", "locationInternal", value.internal === true, "checkbox");
|
||||||
@@ -391,7 +391,7 @@ function locationsEditor(parent, values) {
|
|||||||
const cache = field(body, "location-cache", "cacheEnabled", value.cache === true ? "on" : value.cache === false ? "off" : "", "select", { values: [["", "inherit"], ["on", "enabled"], ["off", "notEnabled"]] });
|
const cache = field(body, "location-cache", "cacheEnabled", value.cache === true ? "on" : value.cache === false ? "off" : "", "select", { values: [["", "inherit"], ["on", "enabled"], ["off", "notEnabled"]] });
|
||||||
|
|
||||||
const handling = el("details", undefined, "vh-wide location-section"), handlingBody = el("div", undefined, "vh-grid"); handling.append(text("summary", "locationHandling"), handlingBody); body.append(handling);
|
const handling = el("details", undefined, "vh-wide location-section"), handlingBody = el("div", undefined, "vh-grid"); handling.append(text("summary", "locationHandling"), handlingBody); body.append(handling);
|
||||||
field(handlingBody, "location-error-target", "locationErrorFallbackTarget", value.error_fallback?.target || "", "text", { maxLength: 64, pattern: "[A-Za-z0-9_-]{0,64}" });
|
field(handlingBody, "location-error-target", "locationErrorFallbackTarget", value.error_fallback?.target || "", "text", { maxLength: 64, pattern: "[A-Za-z0-9_\\-]{0,64}" });
|
||||||
field(handlingBody, "location-error-statuses", "locationErrorFallbackStatuses", (value.error_fallback?.statuses || [404]).join(", "), "text", { maxLength: 64 });
|
field(handlingBody, "location-error-statuses", "locationErrorFallbackStatuses", (value.error_fallback?.statuses || [404]).join(", "), "text", { maxLength: 64 });
|
||||||
note(handlingBody, "locationErrorFallbackHelp");
|
note(handlingBody, "locationErrorFallbackHelp");
|
||||||
const rewriteMode = field(handlingBody, "location-rewrite-mode", "locationRewriteMode", value.rewrite?.mode || "preserve", "select", { values: [["preserve", "locationRewritePreserve"], ["strip_prefix", "locationRewriteStrip"], ["replace_prefix", "locationRewriteReplace"], ["regex", "locationRewriteRegex"]] });
|
const rewriteMode = field(handlingBody, "location-rewrite-mode", "locationRewriteMode", value.rewrite?.mode || "preserve", "select", { values: [["preserve", "locationRewritePreserve"], ["strip_prefix", "locationRewriteStrip"], ["replace_prefix", "locationRewriteReplace"], ["regex", "locationRewriteRegex"]] });
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ function load(value = defaults(), migrationIds = [], show = true) {
|
|||||||
editing = value.id || null; revision = envelope?.revision ?? null; dirty = false; initialized = true;
|
editing = value.id || null; revision = envelope?.revision ?? null; dirty = false; initialized = true;
|
||||||
container.replaceChildren(); $("vhost-editing").textContent = t(editing ? "vhEditLabel" : "vhNewLabel");
|
container.replaceChildren(); $("vhost-editing").textContent = t(editing ? "vhEditLabel" : "vhNewLabel");
|
||||||
let g = group("vhEditor"); const basicFields = subsection(g, "vhGeneralDetailsSection");
|
let g = group("vhEditor"); const basicFields = subsection(g, "vhGeneralDetailsSection");
|
||||||
const vhostIdInput = field(basicFields, "id", "vhId", value.id, "text", { required: true, maxLength: 64, pattern: "[A-Za-z0-9_-]{1,64}" }); if (editing) note(basicFields, "vhIdRenameHelp");
|
const vhostIdInput = field(basicFields, "id", "vhId", value.id, "text", { required: true, maxLength: 64, pattern: "[A-Za-z0-9_\\-]{1,64}" }); if (editing) note(basicFields, "vhIdRenameHelp");
|
||||||
let refreshCanonical = () => { }, vhostAutoId = null, vhostAutoCertificate = null;
|
let refreshCanonical = () => { }, vhostAutoId = null, vhostAutoCertificate = null;
|
||||||
vhostAutoId = window.rustProxyAutoId.attach(vhostIdInput, { text: () => lines(read("hosts")?.value || "")[0] || "", taken: () => (envelope?.config.vhosts || []).map(item => item.id), resettable: !editing });
|
vhostAutoId = window.rustProxyAutoId.attach(vhostIdInput, { text: () => lines(read("hosts")?.value || "")[0] || "", taken: () => (envelope?.config.vhosts || []).map(item => item.id), resettable: !editing });
|
||||||
const enabledControl = field(basicFields, "enabled", "vhEnabled", value.enabled !== false, "checkbox");
|
const enabledControl = field(basicFields, "enabled", "vhEnabled", value.enabled !== false, "checkbox");
|
||||||
@@ -271,7 +271,7 @@ function load(value = defaults(), migrationIds = [], show = true) {
|
|||||||
passiveSlow.addEventListener("change", syncPassive);
|
passiveSlow.addEventListener("change", syncPassive);
|
||||||
syncPassive();
|
syncPassive();
|
||||||
g = group("vhSticky"); field(g, "sticky-mode", "vhStickyMode", sticky.mode || "off", "select", { values: [["off", "vhOff"], ["cookie", "vhCookie"], ["ip_hash", "vhIpHash"]] });
|
g = group("vhSticky"); field(g, "sticky-mode", "vhStickyMode", sticky.mode || "off", "select", { values: [["off", "vhOff"], ["cookie", "vhCookie"], ["ip_hash", "vhIpHash"]] });
|
||||||
field(g, "cookie-name", "vhCookieName", sticky.cookie_name || "", "text", { maxLength: 96, pattern: "[A-Za-z0-9_-]*" });
|
field(g, "cookie-name", "vhCookieName", sticky.cookie_name || "", "text", { maxLength: 96, pattern: "[A-Za-z0-9_\\-]*" });
|
||||||
field(g, "cookie-ttl", "vhCookieTtl", sticky.ttl_seconds ?? 3600, "number", { required: true, min: 60, max: 604800 }); note(g, "vhStickyNote");
|
field(g, "cookie-ttl", "vhCookieTtl", sticky.ttl_seconds ?? 3600, "number", { required: true, min: 60, max: 604800 }); note(g, "vhStickyNote");
|
||||||
const rate = value.rate_limit || {}, access = rate.country_access || {}, methodPolicy = value.method_policy || {};
|
const rate = value.rate_limit || {}, access = rate.country_access || {}, methodPolicy = value.method_policy || {};
|
||||||
g = group("vhTrafficPolicy");
|
g = group("vhTrafficPolicy");
|
||||||
@@ -342,7 +342,7 @@ function load(value = defaults(), migrationIds = [], show = true) {
|
|||||||
field(g, "acl", "vhAcl", JSON.stringify(value.acl ?? null, null, 2), "textarea", { rows: 5, maxLength: 32768 });
|
field(g, "acl", "vhAcl", JSON.stringify(value.acl ?? null, null, 2), "textarea", { rows: 5, maxLength: 32768 });
|
||||||
field(g, "headers", "vhHeaders", JSON.stringify(value.headers || {}, null, 2), "textarea", { rows: 5, maxLength: 65536 });
|
field(g, "headers", "vhHeaders", JSON.stringify(value.headers || {}, null, 2), "textarea", { rows: 5, maxLength: 65536 });
|
||||||
g = group("vhLocations");
|
g = group("vhLocations");
|
||||||
field(g, "main-error-target", "vhErrorFallbackTarget", value.error_fallback?.target || "", "text", { maxLength: 64, pattern: "[A-Za-z0-9_-]{0,64}" });
|
field(g, "main-error-target", "vhErrorFallbackTarget", value.error_fallback?.target || "", "text", { maxLength: 64, pattern: "[A-Za-z0-9_\\-]{0,64}" });
|
||||||
field(g, "main-error-statuses", "vhErrorFallbackStatuses", (value.error_fallback?.statuses || [404]).join(", "), "text", { maxLength: 64 });
|
field(g, "main-error-statuses", "vhErrorFallbackStatuses", (value.error_fallback?.statuses || [404]).join(", "), "text", { maxLength: 64 });
|
||||||
note(g, "vhErrorFallbackHelp");
|
note(g, "vhErrorFallbackHelp");
|
||||||
locationsReader = locationsEditor(g, value.routes || []);
|
locationsReader = locationsEditor(g, value.routes || []);
|
||||||
|
|||||||
+148
-10
@@ -202,7 +202,7 @@ async function savePoolTablePrefs() {
|
|||||||
if (!authenticated) return;
|
if (!authenticated) return;
|
||||||
if (!poolPreferenceSaveQueued) poolTablePrefs = normalizePoolTablePrefs(saved);
|
if (!poolPreferenceSaveQueued) poolTablePrefs = normalizePoolTablePrefs(saved);
|
||||||
}
|
}
|
||||||
syncPoolColumns(); syncPoolSortDirection();
|
syncPoolColumns(); syncPoolSortDirection(); applyBackendOverviewView();
|
||||||
} catch (error) { if (error.status !== 401) toast(error.message, true); }
|
} catch (error) { if (error.status !== 401) toast(error.message, true); }
|
||||||
finally { poolPreferenceSaveRunning = false; }
|
finally { poolPreferenceSaveRunning = false; }
|
||||||
}
|
}
|
||||||
@@ -212,11 +212,15 @@ function syncPoolColumns() {
|
|||||||
document.querySelectorAll("[data-pool-column-toggle]").forEach(input => { input.checked = visible.has(input.dataset.poolColumnToggle); });
|
document.querySelectorAll("[data-pool-column-toggle]").forEach(input => { input.checked = visible.has(input.dataset.poolColumnToggle); });
|
||||||
}
|
}
|
||||||
function syncPoolSortDirection() {
|
function syncPoolSortDirection() {
|
||||||
const control = $("pool-sort-direction"); if (!control) return;
|
for (const id of ["pool-sort-direction", "bo-sort-direction"]) {
|
||||||
control.textContent = poolTablePrefs.descending ? "↓" : "↑";
|
const control = $(id); if (!control) continue;
|
||||||
control.setAttribute("aria-label", t(poolTablePrefs.descending ? "backendSortDescending" : "backendSortAscending"));
|
control.textContent = poolTablePrefs.descending ? "↓" : "↑";
|
||||||
|
control.setAttribute("aria-label", t(poolTablePrefs.descending ? "backendSortDescending" : "backendSortAscending"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function applyPoolTableView() {
|
function applyPoolTableView() {
|
||||||
|
for (const id of ["pool-sort", "bo-sort"]) { const control = $(id); if (control && control.value !== poolTablePrefs.sort) control.value = poolTablePrefs.sort; }
|
||||||
|
applyBackendOverviewView();
|
||||||
const body = $("pool-rows"); if (!body) return;
|
const body = $("pool-rows"); if (!body) return;
|
||||||
const query = ($("pool-search")?.value || "").trim().toLowerCase();
|
const query = ($("pool-search")?.value || "").trim().toLowerCase();
|
||||||
const key = $("pool-sort")?.value || poolTablePrefs.sort;
|
const key = $("pool-sort")?.value || poolTablePrefs.sort;
|
||||||
@@ -234,18 +238,142 @@ function applyPoolTableView() {
|
|||||||
if (empty) { empty.textContent = query && rows.length && !visible ? t("backendSearchEmpty") : t("vhHealthEmpty"); empty.hidden = visible > 0 || (!rows.length && !document.querySelector("#backend-diagnostics-card:not([hidden])")); }
|
if (empty) { empty.textContent = query && rows.length && !visible ? t("backendSearchEmpty") : t("vhHealthEmpty"); empty.hidden = visible > 0 || (!rows.length && !document.querySelector("#backend-diagnostics-card:not([hidden])")); }
|
||||||
}
|
}
|
||||||
function initPoolTableTools() {
|
function initPoolTableTools() {
|
||||||
const sort = $("pool-sort"), direction = $("pool-sort-direction"), search = $("pool-search");
|
buildBackendOverviewTools();
|
||||||
if (sort) { sort.value = poolTablePrefs.sort; sort.addEventListener("change", () => { poolTablePrefs.sort = sort.value; void savePoolTablePrefs(); applyPoolTableView(); }); }
|
for (const id of ["pool-sort", "bo-sort"]) {
|
||||||
direction?.addEventListener("click", () => { poolTablePrefs.descending = !poolTablePrefs.descending; void savePoolTablePrefs(); applyPoolTableView(); });
|
const sort = $(id); if (!sort) continue;
|
||||||
search?.addEventListener("input", applyPoolTableView);
|
sort.value = poolTablePrefs.sort;
|
||||||
|
sort.addEventListener("change", () => { poolTablePrefs.sort = sort.value; void savePoolTablePrefs(); applyPoolTableView(); });
|
||||||
|
}
|
||||||
|
for (const id of ["pool-sort-direction", "bo-sort-direction"]) $(id)?.addEventListener("click", () => { poolTablePrefs.descending = !poolTablePrefs.descending; void savePoolTablePrefs(); applyPoolTableView(); });
|
||||||
|
$("pool-search")?.addEventListener("input", applyPoolTableView);
|
||||||
|
$("bo-search")?.addEventListener("input", applyBackendOverviewView);
|
||||||
|
$("bo-open")?.addEventListener("click", () => setPanel("vhosts"));
|
||||||
document.querySelectorAll("[data-pool-column-toggle]").forEach(input => input.addEventListener("change", () => {
|
document.querySelectorAll("[data-pool-column-toggle]").forEach(input => input.addEventListener("change", () => {
|
||||||
const visible = new Set(poolTablePrefs.columns);
|
const visible = new Set(poolTablePrefs.columns);
|
||||||
if (input.checked) visible.add(input.dataset.poolColumnToggle); else visible.delete(input.dataset.poolColumnToggle);
|
if (input.checked) visible.add(input.dataset.poolColumnToggle); else visible.delete(input.dataset.poolColumnToggle);
|
||||||
if (!visible.size) { input.checked = true; toast(t("backendColumnsRequired"), true); return; }
|
if (!visible.size) { input.checked = true; toast(t("backendColumnsRequired"), true); return; }
|
||||||
poolTablePrefs.columns = poolColumns.filter(key => visible.has(key)); void savePoolTablePrefs(); syncPoolColumns();
|
poolTablePrefs.columns = poolColumns.filter(key => visible.has(key)); void savePoolTablePrefs(); syncPoolColumns(); applyBackendOverviewView();
|
||||||
}));
|
}));
|
||||||
syncPoolColumns(); syncPoolSortDirection();
|
syncPoolColumns(); syncPoolSortDirection();
|
||||||
}
|
}
|
||||||
|
// Backends overview (dashboard card "backend_table"): a richer view of the same backend data. It shares the sort,
|
||||||
|
// direction and column preferences with the compact "Tiny backends" table and follows table density and corner style.
|
||||||
|
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_DETAIL_COLUMNS = ["generation", "generationRequests", "generationAge", "rotations", "lastFailure", "lastRecovery"];
|
||||||
|
const BO_LATENCY_SAMPLES = 24;
|
||||||
|
const boOpen = new Set(), boLatency = new Map();
|
||||||
|
let boItems = [], boFilter = "all", boHasProxy = true;
|
||||||
|
function buildBackendOverviewTools() {
|
||||||
|
const sort = $("bo-sort"), options = $("bo-column-options");
|
||||||
|
if (sort && !sort.options.length) for (const key of poolColumns) { const option = el("option", t(BO_SORT_LABELS[key])); option.value = key; option.dataset.i18n = BO_SORT_LABELS[key]; sort.append(option); }
|
||||||
|
if (options && !options.children.length) for (const key of poolColumns) {
|
||||||
|
const label = el("label", undefined, "check"), input = el("input"), text = el("span", t(BO_SORT_LABELS[key]));
|
||||||
|
input.type = "checkbox"; input.dataset.poolColumnToggle = key; text.dataset.i18n = BO_SORT_LABELS[key]; label.append(input, text); options.append(label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function boAgo(ms) {
|
||||||
|
const seconds = Math.max(0, Math.floor((Date.now() - Number(ms)) / 1000));
|
||||||
|
const value = seconds < 60 ? `${seconds} s` : seconds < 3600 ? `${Math.floor(seconds / 60)} min` : seconds < 86400 ? `${Math.floor(seconds / 3600)} h` : `${Math.floor(seconds / 86400)} d`;
|
||||||
|
return t("backendAgo").replace("{value}", value);
|
||||||
|
}
|
||||||
|
function boSparkline(history, current) {
|
||||||
|
const ns = "http://www.w3.org/2000/svg", svg = document.createElementNS(ns, "svg"), samples = history.filter(value => value > 0);
|
||||||
|
svg.setAttribute("viewBox", "0 0 64 20"); svg.setAttribute("class", "bo-spark"); svg.setAttribute("aria-hidden", "true");
|
||||||
|
if (!(current > 0) || samples.length < 3) return svg;
|
||||||
|
const min = Math.min(...samples), max = Math.max(...samples), flat = max - min < max * 0.05, step = 64 / (samples.length - 1), line = document.createElementNS(ns, "polyline");
|
||||||
|
line.setAttribute("points", samples.map((value, index) => `${(index * step).toFixed(1)},${(flat ? 10 : 17 - ((value - min) / (max - min)) * 14).toFixed(1)}`).join(" "));
|
||||||
|
svg.append(line); return svg;
|
||||||
|
}
|
||||||
|
function renderBackendOverview(items, hasProxy) {
|
||||||
|
boItems = items; boHasProxy = hasProxy !== false;
|
||||||
|
const seen = new Set();
|
||||||
|
for (const item of items) {
|
||||||
|
seen.add(item.key);
|
||||||
|
const history = boLatency.get(item.key) || []; history.push(item.latency); if (history.length > BO_LATENCY_SAMPLES) history.shift(); boLatency.set(item.key, history);
|
||||||
|
}
|
||||||
|
for (const key of boLatency.keys()) if (!seen.has(key)) boLatency.delete(key);
|
||||||
|
for (const key of boOpen) if (!seen.has(key)) boOpen.delete(key);
|
||||||
|
$("backend-overview-card")?.classList.toggle("is-no-proxy", !boHasProxy);
|
||||||
|
applyBackendOverviewView();
|
||||||
|
}
|
||||||
|
function applyBackendOverviewView() {
|
||||||
|
const body = $("bo-rows"); if (!body) return;
|
||||||
|
const visibleColumns = new Set(poolTablePrefs.columns);
|
||||||
|
const query = ($("bo-search")?.value || "").trim().toLowerCase();
|
||||||
|
const focusKey = document.activeElement?.dataset?.boKey, hadFocus = Boolean(focusKey) && body.contains(document.activeElement);
|
||||||
|
const counts = { all: boItems.length, up: 0, down: 0, other: 0 };
|
||||||
|
for (const item of boItems) counts[item.group]++;
|
||||||
|
const summary = $("bo-summary"); if (summary) summary.textContent = boItems.length ? t("backendSummary").replace("{up}", counts.up).replace("{total}", boItems.length) : "";
|
||||||
|
const filters = $("bo-filters");
|
||||||
|
if (filters) {
|
||||||
|
filters.setAttribute("aria-label", t("backendFilterLabel"));
|
||||||
|
filters.replaceChildren(...[["all", "backendFilterAll"], ["up", "vhUp"], ["down", "vhDown"], ["other", "backendFilterOther"]].map(([key, label]) => {
|
||||||
|
const chip = el("button", undefined, `bo-chip is-${key}`), count = el("b", String(counts[key]), counts[key] ? "" : "is-zero");
|
||||||
|
chip.type = "button"; chip.dataset.uiIconName = "none"; chip.setAttribute("aria-pressed", String(boFilter === key));
|
||||||
|
chip.append(el("span", t(label)), count); chip.addEventListener("click", () => { boFilter = key; applyBackendOverviewView(); }); return chip;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
const key = poolTablePrefs.sort;
|
||||||
|
const list = boItems.filter(item => (boFilter === "all" || item.group === boFilter) && (!query || item.search.includes(query)));
|
||||||
|
list.sort((left, right) => {
|
||||||
|
const a = left.sort[key] ?? "", b = right.sort[key] ?? "";
|
||||||
|
const result = typeof a === "number" && typeof b === "number" ? a - b : String(a).localeCompare(String(b), locale(), { numeric: true, sensitivity: "base" });
|
||||||
|
return poolTablePrefs.descending ? -result : result;
|
||||||
|
});
|
||||||
|
const showIdentity = visibleColumns.has("vhost") || visibleColumns.has("backend"), showEvent = visibleColumns.has("lastFailure") || visibleColumns.has("lastRecovery");
|
||||||
|
const detailColumns = BO_DETAIL_COLUMNS.filter(column => visibleColumns.has(column)), expandable = detailColumns.length > 0;
|
||||||
|
const columns = [
|
||||||
|
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("errors") && ["errors", t("vhErrors")], visibleColumns.has("latency") && ["latency", t("vhLatency")], visibleColumns.has("traffic") && ["traffic", t("vhTraffic"), true], showEvent && ["event", t("backendLastEvent")],
|
||||||
|
].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 rows = [];
|
||||||
|
for (const item of list) {
|
||||||
|
const row = el("tr", undefined, `bo-row is-${item.tone}${item.disabled ? " is-disabled" : ""}`), open = expandable && boOpen.has(item.key);
|
||||||
|
for (const [id] of columns) {
|
||||||
|
const cell = el("td"); cell.dataset.boColumn = id;
|
||||||
|
if (id === "toggle") {
|
||||||
|
const button = el("button", undefined, "bo-expand"); button.type = "button"; button.dataset.uiIconName = "none"; button.dataset.boKey = item.key;
|
||||||
|
button.setAttribute("aria-expanded", String(open)); button.setAttribute("aria-label", t("backendDetailsToggle")); button.title = t("backendDetailsToggle");
|
||||||
|
button.addEventListener("click", () => { if (boOpen.has(item.key)) boOpen.delete(item.key); else boOpen.add(item.key); applyBackendOverviewView(); });
|
||||||
|
cell.append(button);
|
||||||
|
} else if (id === "identity") {
|
||||||
|
cell.className = "bo-identity"; const name = el("div", visibleColumns.has("vhost") ? item.vhost : item.backend, "bo-name"); name.title = item.vhost; cell.append(name);
|
||||||
|
if (visibleColumns.has("vhost") && visibleColumns.has("backend")) { const sub = el("div", item.backend, "bo-sub"); sub.title = item.backend; cell.append(sub); }
|
||||||
|
} else if (id === "status") {
|
||||||
|
const badge = el("span", item.label, `bo-badge is-${item.tone}`); badge.title = item.title; badge.prepend(el("i")); cell.append(badge);
|
||||||
|
} else if (id === "active") { cell.className = "is-num"; cell.textContent = item.activeText; }
|
||||||
|
else if (id === "requests") { cell.className = "is-num"; cell.textContent = String(item.requests); }
|
||||||
|
else if (id === "errors") {
|
||||||
|
const pct = item.requests ? item.errors / item.requests * 100 : 0, top = el("div", undefined, "bo-meter-top"), track = el("div", undefined, "bo-meter"), fill = el("i");
|
||||||
|
top.append(el("span", String(item.errors), item.errors ? "" : "is-zero"), el("small", item.errors ? (pct < 0.1 ? "<0.1%" : `${pct.toFixed(pct < 10 ? 1 : 0)}%`) : "0%"));
|
||||||
|
fill.style.width = `${item.errors ? Math.min(100, Math.max(4, pct)) : 0}%`; if (pct >= 5) fill.className = "is-high"; track.append(fill); cell.className = "bo-errors"; cell.append(top, track);
|
||||||
|
} else if (id === "latency") {
|
||||||
|
cell.className = "bo-latency"; cell.append(el("span", item.latency > 0 ? `${item.latency.toFixed(1)} ms` : "-", item.latency > 0 ? "" : "is-zero"), boSparkline(boLatency.get(item.key) || [], item.latency));
|
||||||
|
} else if (id === "traffic") { cell.className = "is-num"; cell.textContent = `${rateBytes(item.traffic)}/s`; }
|
||||||
|
else if (id === "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))); }
|
||||||
|
else if (item.lastRecoveryMs) cell.append(el("span", t("backendEventRecovered"), "is-recovered"), el("small", boAgo(item.lastRecoveryMs)));
|
||||||
|
else cell.append(el("span", t("backendEventNone"), "is-zero"));
|
||||||
|
}
|
||||||
|
row.append(cell);
|
||||||
|
}
|
||||||
|
rows.push(row);
|
||||||
|
if (open) {
|
||||||
|
const detail = el("tr", undefined, "bo-detail-row"), cell = el("td"), grid = el("div", undefined, "bo-detail-grid"); cell.colSpan = Math.max(1, columns.length);
|
||||||
|
const values = { generation: item.generation, generationRequests: item.generationRequests, generationAge: item.generationAgeText, rotations: item.rotations, lastFailure: item.lastFailureText, lastRecovery: item.lastRecoveryText };
|
||||||
|
for (const column of detailColumns) { const entry = el("div", undefined, "bo-detail-item"), value = el("b", String(values[column] ?? "-")); if (column === "lastFailure" && item.lastFailureMs) { value.className = "is-failed"; value.title = item.lastFailureDetail || ""; } else if (column === "lastRecovery" && item.lastRecoveryMs) value.className = "is-recovered"; entry.append(el("span", t(BO_SORT_LABELS[column])), value); grid.append(entry); }
|
||||||
|
cell.append(grid); detail.append(cell); rows.push(detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body.replaceChildren(...rows);
|
||||||
|
if (hadFocus) [...body.querySelectorAll(".bo-expand")].find(button => button.dataset.boKey === focusKey)?.focus();
|
||||||
|
const empty = $("bo-empty");
|
||||||
|
if (empty) { empty.textContent = boItems.length ? t("backendSearchEmpty") : t("vhHealthEmpty"); empty.hidden = list.length > 0 || !boHasProxy; }
|
||||||
|
const toggleAll = document.querySelectorAll("#bo-column-options [data-pool-column-toggle]"); toggleAll.forEach(input => { input.checked = visibleColumns.has(input.dataset.poolColumnToggle); });
|
||||||
|
}
|
||||||
async function toggleVhostEnabled(vhost) {
|
async function toggleVhostEnabled(vhost) {
|
||||||
if (!envelope || busy) return;
|
if (!envelope || busy) return;
|
||||||
const next = structuredClone(envelope.config), target = (next.vhosts || []).find(item => item.id === vhost.id);
|
const next = structuredClone(envelope.config), target = (next.vhosts || []).find(item => item.id === vhost.id);
|
||||||
@@ -287,6 +415,7 @@ function render() {
|
|||||||
);
|
);
|
||||||
const diagnosticsCard = $("backend-diagnostics-card");
|
const diagnosticsCard = $("backend-diagnostics-card");
|
||||||
if (diagnosticsCard) diagnosticsCard.hidden = !configuredHasProxySource;
|
if (diagnosticsCard) diagnosticsCard.hidden = !configuredHasProxySource;
|
||||||
|
$("backend-overview-card")?.classList.toggle("is-no-proxy", !configuredHasProxySource);
|
||||||
const query = ($("vhost-search")?.value || "").trim().toLowerCase();
|
const query = ($("vhost-search")?.value || "").trim().toLowerCase();
|
||||||
const visibleVhosts = (config.vhosts || []).filter(v => {
|
const visibleVhosts = (config.vhosts || []).filter(v => {
|
||||||
if (!query) return true;
|
if (!query) return true;
|
||||||
@@ -459,7 +588,7 @@ function renderStatus(status) {
|
|||||||
renderUpstreamProtocolBadges(status);
|
renderUpstreamProtocolBadges(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();
|
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();
|
||||||
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]));
|
||||||
@@ -481,6 +610,14 @@ function renderStatus(status) {
|
|||||||
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, 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 ?? "-")); 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);
|
||||||
|
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({
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
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; }
|
||||||
}
|
}
|
||||||
@@ -524,6 +661,7 @@ function renderStatus(status) {
|
|||||||
vhost.source?.type !== "web_server" || (vhost.routes || []).some(route => !route.response && (route.source?.type || "reverse_proxy") === "reverse_proxy")
|
vhost.source?.type !== "web_server" || (vhost.routes || []).some(route => !route.response && (route.source?.type || "reverse_proxy") === "reverse_proxy")
|
||||||
);
|
);
|
||||||
$("pool-rows").replaceChildren(...rows);
|
$("pool-rows").replaceChildren(...rows);
|
||||||
|
renderBackendOverview(overviewItems, hasConfiguredProxySource);
|
||||||
const backendDiagnosticsCard = $("backend-diagnostics-card");
|
const backendDiagnosticsCard = $("backend-diagnostics-card");
|
||||||
if (backendDiagnosticsCard) backendDiagnosticsCard.hidden = !hasConfiguredProxySource;
|
if (backendDiagnosticsCard) backendDiagnosticsCard.hidden = !hasConfiguredProxySource;
|
||||||
applyPoolTableView();
|
applyPoolTableView();
|
||||||
|
|||||||
@@ -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.7");
|
assert.equal(openapi.info.version, "0.36.8");
|
||||||
console.log("Panel refresh/API contract checks: OK");
|
console.log("Panel refresh/API contract checks: OK");
|
||||||
Reference in new issue
Block a user