This commit is contained in:
Mateusz Gruszczyński committed 2026-10-15 11:10:49 +02:00
1 parent b757b34a31
commit 5a36bb0c4b
20 files changed
+356 -186

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@
Read README.md, ROADMAP.md and docs/TEST_REPORT.md before making changes.
The project name is RustProxy; the Cargo package/binary remains `rust-proxy` for
compatibility. Current source version: 0.33.1.
compatibility. Current source version: 0.33.2.
## Delivery
+17
View File
@@ -3,6 +3,23 @@
> Operator-facing release history. Historical engineering notes are kept in
> [`docs/CHANGELOG_TECHNICAL_ARCHIVE.md`](docs/CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.33.2
- Synchronize Cache occupancy progressbar colors with the selected Appearance
chart palette, matching the existing Overview fill color. Native WebKit and
Firefox progressbar tracks and fills honor the theme automatically.
- Synchronize track and fill corner rounding across Cache, Overview and WWW
with the Appearance corner setting (square, subtle, rounded and extra).
Also align Web Server ranking bars and traffic ranking bars.
- No changes to telemetry, polling, persisted preferences or cache behavior.
- Add a Chromium browser regression for corner styles, all chart palettes,
theme switching and visible native Cache progress fill colors.
- Align the System Load RAM utilization bar with all other Overview meters:
replace the browser-rendered native `<progress>` element with the shared
chart-palette/corner-radius track and fill. Keep 5px height and status-based
percentage updates, including appropriate absent-data handling and ARIA
values; add a bundled-JavaScript browser regression for these states.
## 0.33.1
- Fix intermittent inline Overview hide/show reversions: backend and Stream
Generated
+1 -1
View File
@@ -1881,7 +1881,7 @@ dependencies = [
[[package]]
name = "rust-proxy"
version = "0.33.1"
version = "0.33.2"
dependencies = [
"anyhow",
"arc-swap",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "rust-proxy"
version = "0.33.1"
version = "0.33.2"
edition = "2021"
rust-version = "1.99"
description = "RustProxy - independent reverse proxy with local administration"
+17 -1
View File
@@ -1,4 +1,20 @@
# RustProxy 0.33.1
# RustProxy 0.33.2
## 0.33.2: unified progressbar appearance
- Cache HOT/COLD object, RAM and disk occupancy bars now use the same selected
chart palette (`--chart-accent`) as Overview meters, instead of fixed green.
Changing Appearance → Chart style updates every bar immediately, including
native `<progress>` controls. The neutral track retains theme contrast.
- Progressbar rounding follows Appearance → Corner rounding (default, square,
subtle, rounded and extra) across Cache, Overview, WWW and traffic ranking
bars. Both the track and its fill follow the same radius. No metrics, APIs,
polling behavior, dashboards or saved preferences are changed.
- The **System Load → RAM used / total** meter uses the same custom track/fill
as other Overview cards (including the chart palette and corner radius),
instead of browser-specific native `<progress>` rendering. Its original
5px height and status-based used/total percentage are unchanged. Missing
total RAM does not produce an invented percentage.
## 0.33.1: factory Overview preset and live layout preview
+11
View File
@@ -1,5 +1,16 @@
# RustProxy roadmap
## 0.33.2 - Shared progressbar visual settings
- [x] Apply Appearance corner rounding to native Cache progressbars and
custom Overview, WWW and traffic bars with matching track/fill shapes.
- [x] Apply the chosen chart color palette to Cache progressbars and other
progress fills; retain theme-aware tracks, without new API/status work.
- [x] Unify the System Load RAM meter with other Overview progressbars,
retaining the 5px height and accessible percentage updates.
- [x] Verify live CSS changes with Chromium across corners, chart palettes,
light/dark themes and mobile/desktop viewport widths.
## 0.33.1 - Factory dashboard preset and inline live preview
- [x] Ship the operator-supplied default ordering, hidden cards and compact density
+23 -21
View File
@@ -1,13 +1,13 @@
61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
72d08ff6d852ab20c16d9a4396053dfb49c4acfd817af683838783ac5285ceea AGENTS.md
1769a5bfa31abc9158b9f39e7f86279a54f196d2934f93fdfb4e7d248a86fada CHANGELOG.md
8f4e05b7681384329a9a126d48b9eb9a0a8d5bc0597997d67d2f7551a47dac2e Cargo.lock
ac9205ad566ed4c2fe8e862b2241eb4d34bcfaaff1242e7a0ec5f49731d7059a Cargo.toml
78027147942d2a355ad3bb92492faf70917908df879b10330bb9e42edfbd5d11 AGENTS.md
9a5f4c3478b4fba206fc8a3d6616f86b2cf2d640ee51f935aa5b0baeb9c0d7b7 CHANGELOG.md
53338adb66b8c6c345d742f1a8fca48b7965e70ee072b2f4069020d3d73f1be7 Cargo.lock
5ab876b82db0e76ae52ac1723555ebeef9182e1feedabd0a72f936d0940e4373 Cargo.toml
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
13ad656e45773ad5581c1fdb54635dc78a48e5f99c93ec589469882e85d2177e README.md
92a11de221459b5678659b1d79118e5318d404f91eda9ac7fadd69764c8ced45 ROADMAP.md
f73f11aade119401aff40f7929f08b9f8f889fbf89d0cd8be7a1efd69487fffd README.md
55a71d11fb998cf31874d1e7669175b51251f38f48349538b94b03e27283726c ROADMAP.md
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs
ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md
@@ -35,7 +35,7 @@ fb99fac0a490af262a0ecdad593ffe5164004c2166fe7ddd607476bcc56a1623 docs/CACHE.md
3822d72e12f82c37a9e70ab40c7e89e89145ad37f29914e4702db4bf024a5501 docs/CHANGELOG_TECHNICAL_ARCHIVE.md
c8e69e492179ce39958cc63615cf46c4ac08cfdb7f3483aaab66868a3abcd687 docs/CONTAINER.md
833ddb12f23dcac796223521388b19c6ec3ea04c5550cf1c5f4ea4eb7c58f3a0 docs/CUSTOM_LOCATIONS.md
3f2376d8a54229cad88706d4edda5a07db33f5ae81c5aa70d3a9f2b38389906c docs/DASHBOARD_PRESET.md
15153ba0eb325b374aa3a278f677900516665c2b131b55b6074ec5105924f41d docs/DASHBOARD_PRESET.md
4d70797bda8d227787fcff897951e702e2458fc6c6f5352aa0b5577595bd834b docs/DOCKER_DISCOVERY.md
46811a09a03d83f4af686c33e30e3f94d0508afe2c75147a26885c7247c49cbd docs/ERROR_LOGGING.md
f749f7bd2eb4ae9c484f77a3cd8cd5ff21445319a3938e0384a3414134fe713a docs/FILE_LOGS.md
@@ -64,7 +64,7 @@ ec7e32fd19e6ba6ed1732f1f3dd92ac0860609e8c0bbd3c1998840cdfb24bf54 docs/SECURITY.
007537ee971463072b2482dfa4fdcf1955f837ced0b711d4eef3b6267d16d4f6 docs/SECURITY_AUDIT_0.31.0.md
4dcf3243a13d9ae1827b5cefdbb9e27bd6310321c50d4d5934527d44437df6ef docs/SETTINGS.md
8fc112cb45a2f35ef5b0dd4371aa6f43aca4df85db62c6ae02e56d74a3b702ae docs/STREAMS.md
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 docs/TEST_REPORT.md
87279fa139a62cca82a4c97c1edae0b97e70ed9ff09af55773faecf581be763f docs/TEST_REPORT.md
cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md
751c9b13b47d0959a8575d6e82bdecba988b950a374ba0b084fbba08b4b65ebe docs/TRANSFORM.md
fe3ec6ca16ff933239c12dacf39dd7b45717c0551a8b9f6ce601bf78136c9e37 docs/TRANSPORT.md
@@ -83,7 +83,7 @@ b26833cb4251f9390a44192ecc827ff7f052fd19016a5ccf7498c94503ab4ce3 examples/mock-
d242de9614fbb52933b4accd32f4715ca94107abe4a3b1d7e088362b07a6ba60 examples/vhosts-balanced.json
af2bdb1343a8141069e94ccd2c7f92574ad848437e2f9fdb5c7576a9a18ef4a5 examples/vhosts-private.json
1b8974af79a5026e0648ceab58a32f233e1760e21e7465a0d46e9c41fb09964f grafana/README.md
abd03fbc4c24317871978b88930e175cc2ab1c20c69ce754a20ac8f8a6e9088a grafana/rustproxy-overview.json
cb4fe36423eca2cbc7961df04f28c6870e4303e4c16c7fe2d3f7b7bbb04d8dad grafana/rustproxy-overview.json
a05cbbf101b288bf9cf83fedfc5017925a8b360a78f313d31c1a70f15dc1dd22 install-debian13.sh
909e9ab94617ace74a425f15084bf85f3086cc52ba5b28f2b441ff800abc6ef9 install.sh
42f9f1d18c021fc7734b5bd020844bf678725a0e539e2fcbe05ec676f6239eae integrations/README.md
@@ -133,6 +133,8 @@ f0b48ec0cc4411755cc0b4128eebce710375afa99344cb76198cf31074f8eb63 scripts/test_s
52eacb467df123dc4a35f14933e006b090c9238df1b14dcff856a16b37c8063f scripts/tests/dashboard_layout_0331_browser_test.py
276e732fc2dfb0d39badb159e4fdcba0d7d73509c60ad5c9df49621c03046f1b scripts/tests/dashboard_metric_visuals_ui_test.mjs
aa12d00acdd226602b3efc90221fbc936f69f73bcabbaa6c01dfb0e05d08c505 scripts/tests/overview_samples_0330_ui_test.mjs
0ec69b7811db3dcd51cba60387900ca8df50eb836b22cc2a3ecfcc22b40a2edd scripts/tests/progressbar_0332_browser_test.py
d5f675b97a85220043a9475347412972db904e559d863ce5058aa18f649f9e16 scripts/tests/system_ram_meter_0332_browser_test.py
046b4282550f5cb4e53906665c91c7442be70fc68fd0743466fd832582f7c979 scripts/tests/test_stream_tcp.py
f0b48ec0cc4411755cc0b4128eebce710375afa99344cb76198cf31074f8eb63 scripts/tests/test_stream_udp.py
91bf803f05b97b4654a6e607de40fccdd0268882039d6d1c5d677edce9459006 scripts/tests/web_observability_0330_browser_test.py
@@ -164,13 +166,13 @@ c6b552cd5acd9e743c28b1a5564420c214c619fe891ab0561b7d4499aae8d6c0 src/admin/file
c720a7cad6a15c57b1f2ae7b3e5fbf30a97c64afb9137915f37b23f4e576bdc5 src/admin/file_logs_query.rs
74636c6560618ee95757335452baf8db03394e1ad18acd54252f54b15746b8d5 src/admin/logging_api.rs
33be50109497d0adb23c36a10894acb5508dd38f3c01136bd0438a4ce0ffe74b src/admin/mod.rs
38351a070d610e96105bc10441e1f50608542a7e51fb18eeecef7819f48f9ed5 src/admin/openapi.json
fc07433afa4c152e6ad5cc95676a0727fdb8a48e04a22c2b761e4a4f125a9e93 src/admin/openapi.json
d9120f7dc61d27621c5ac27ea49815dc7945c44d5b99d208da503818c198f649 src/admin/operations.rs
30895e05c5d5d0a26be29f667e0f6d72f16a96a5c8a98e09cd11d8ef2d7b4763 src/admin/panel_assets.rs
75bbedd54fdabb2b489642a642ee7c6533f69cdacb85e251763a56ba11174d5c src/admin/panel_content.rs
421c8284e0f45e9bb938e41feda4d97b7a17028d51ff3f27ce51ce063058257d src/admin/preferences.rs
fd19a594502c678f0f72efa29dc678ee477b956d47b6784c857b95f6265accf1 src/admin/preferences_columns.rs
1abda2a65cbee89248c8c252bc75fb6c4ffae1ac753b6b147866a51581f54f9f src/admin/preferences_tests.rs
b0f0c1799439f596fbd3c68126fb1ea229c75dfa3ebb48abff1cd5da355e993f src/admin/preferences.rs
a02e17edd5eeb3a842658235af0a73b66e70e870e725ff9b673b76de840e42fc src/admin/preferences_columns.rs
908ef7bb71496a3cb2a565147a427d61026bb4d01d804d76bcb10f071b9122a3 src/admin/preferences_tests.rs
055642f1239346616788f3b1da52f814de5ba0649316352459b6d84b0710eeea src/admin/public.rs
7a00e4ab943f74d2b1bdcbfab7f9cd3f584e9f59536b4aca6c4c31f5f5508d12 src/admin/public/tests.rs
9926827131254677a848c178c80850b3ca1993c05b4bf913687dc0c7992ad048 src/admin/refresh_preferences.rs
@@ -182,26 +184,26 @@ c97881b17b7a42511868b0e48e2f25aab6fb5b613d538f5e1f1967508454e486 src/admin/stat
cadd7dca29b9aad8c77c7bc23dd249ea37a15adfa4d636a8ef00273a91ce35dc src/admin/tail.rs
18b778c2c4e723e2fe5cbaaef20effa6ea50fcc35d507ebde2361c9b6eda62b2 src/admin/tests.rs
48fcd818c522fe80f2c7b2e218f87de6a7772ffd0a24e93502780b03ec78e335 src/admin/web/acme.js
fc89214fef20a4f5ad7958a7c98df62bd43e8d8e3f895a96ed632c9a3c204d48 src/admin/web/app/backend-dashboard.js
c0d08c5b8fc13dec9bf53fd842d224b9df3874fc3bdf31413e3ab182a8cec2aa src/admin/web/app/backend-dashboard.js
d6c5448db12de34a7bb4924411b44d09c7144bc3e659bd3c8a7c0ebf1938da1c src/admin/web/app/config-history.js
8aa205bc88530e4c4678f84ea324c6ecbc823b4ed681624102ff177f3867bc98 src/admin/web/app/core.js
e71f77d459d7894f6a0920e9adbe6393c85ed3113e40582fde0c814561da5fd3 src/admin/web/app/dashboard-inline-layout.js
dc097b9c6ec8ebd4fa53ed8b0a641c9446fe44184c74246b521cfe49cf5a32b3 src/admin/web/app/dashboard-layout-editor.js
1a3782c81e1848df738eab0ff0e38f7b35b839fe553612f3387c517ca2461bbb src/admin/web/app/overview-extra-metrics.js
2f1669cbeb9f49aef43a548e2096825aa14c64c7097ffad520822bbbaefb2b4c src/admin/web/app/overview-extra-metrics.js
fb0ba435beaa91e518537606f5fe55b0eea63c70efd290798c8d9754fa6771b2 src/admin/web/app/overview-masonry.js
b52456b41451f156edf5f58492119c22726f2cf40cee938527748997e8ee7412 src/admin/web/app/overview-samples.js
646d939eb9275d2aee369053d8da4b2e6502da61d072919e05dc530465b4f330 src/admin/web/app/overview-wide-metrics.js
107b010a64ecc4135d610eecf4f8d4fb12329806003d286b4328e31e9e3d859f src/admin/web/app/overview-wide-metrics.js
fb6c48b2367f9e1bfeced2cbb4362d1d85fe1ca0c1627c4660b4d4475ea70977 src/admin/web/app/preferences.js
8e75b0915edfbadefc4388701431541f4d35fd5d717363297b63740b3afd34d2 src/admin/web/app/rate-sampling.js
43f4dddd1c3ea7fa42eeb9d7b655a8db48b5ddcc6c68213e08dfc2fcfbe8b17d src/admin/web/app/refresh.js
b97095ae0c94184693bcd3929724c456edf3d66119b32db4fcdad5ae5b7a84bc src/admin/web/app/runtime.js
48896f36d282a2ec34b50052023146d712799b721033c895e43501992659966b src/admin/web/app/runtime.js
31201586925e6f4792071d7d03fb05dcc4a69a03c02a4e85eb0746f45af68074 src/admin/web/app/session-resume.js
86ac42bd4a6a84bb137d5a3c3dc3d1f4e404a543447845e6ea6e6e8210e268ce src/admin/web/app/settings.js
972a499ef7cd7956308c114a9a37fa567347ac7c554e979b3e648ec59bf34d76 src/admin/web/app/stream-dashboard.js
8386710827e5ccc9432fb46413368f94afc1016425404a6092b460903636837e src/admin/web/app/stream-dashboard.js
20ff05cfddc7f48931b0e9348b63188f82e8eaec38a7fe3e1829fd2e87301568 src/admin/web/app/web-overview.js
0603dc8793a731a0e582cbbec53aa6d1cc6e007faafcec5d8843730b2cdc39e1 src/admin/web/cache-panel.js
02a9f5a61379e68e1723e64ef4c589500e9a993adcb02fe352f1cf9456c90c52 src/admin/web/certificate-api.js
62c84ba46b231c9c62d00e910d182ad42ddb832bb04d3cbd158c296fdc78effd src/admin/web/dashboard-defaults.json
aec5755244242424e5091ff57c0258d2618588618fe1d52d36f081d03879f686 src/admin/web/dashboard-defaults.json
797e2935a34b974cb0e07a88056c31e6948cc11385bf2a076d902a332882d0a0 src/admin/web/dashboard-widgets.json
50d1ebcbaf4d0b2968da23bb943fc3f67131c5d32df3f61d23ff37aad9da5b81 src/admin/web/demo.html
d01a2e48f344fe074c1e63ee0a9bbf7151dfe26140690418535197e13a2a16d7 src/admin/web/docker.js
@@ -224,7 +226,7 @@ e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
7cbdef9192a9dee1f8eac42b85f52b3190f7f6efae50004aa7ad116a910e4036 src/admin/web/file-logs.js
b14e7b3578322f309627f6fe965cc16abcf85da3437c9070be6caecae1908743 src/admin/web/host404.js
7fc17c09a1c24f7753a84202f64cd7db370e923418e59bd35720d432a06abad9 src/admin/web/index.html
cd9505a1d30df205540a905235f3f78bd176ab7bba8f83f760394bba63d86082 src/admin/web/index.html
03ad9572bb14c34059636d612cf6b876d6361ce601817cff674c57b715dc65dd src/admin/web/integrations.js
7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json
cbd41678eadd994dc5252628ee8ef9551fa9f7d8694ca5abd206240d9a96ad30 src/admin/web/login.html
@@ -234,7 +236,7 @@ c2e5f554810b8e34fb47361fb90a01073a2e332ee26d82e030956fad3ec1f83e src/admin/web/
c28f4254ddc1f720d62646ee0f0b590b60a89c0bf447e8ec74ff3dde0c7635ba src/admin/web/settings.js
8eadbada619b15087783dcfa0e889a81f88e78034d084c5ab616f7912b835940 src/admin/web/status_200.html
c13cffe18a616fe5faa5ee945ab0d94d8d392ae8f32474c62fe41010e063ddb1 src/admin/web/streams.js
918c18132365e6522250c210e765df815f6adef06e2d411c08199e3c49cd34b0 src/admin/web/style.css
b6335c26d05f41aa2da33dc045e117e128db84b1c2036e1c0d3b2308df9b8ab0 src/admin/web/style.css
d290c048450655b3eeae7c6ec54a82d645d1965513694ab29fdf7eef796a1abd src/admin/web/traffic-panel.js
69f412bd897e45f4889b71e9982cfff56df9ef20ba2da109b454713a5ecd7c74 src/admin/web/transform-panel.js
d816f2f70eef398c18091479320c36d07f9afe07278a250b840ebcdd7bc715f2 src/admin/web/vhosts/core.js
+1 -1
View File
@@ -22,7 +22,7 @@ Web Server cards remain hidden by default.
## Deploying an edited preset
1. Edit `src/admin/web/dashboard-defaults.json` in the source tree.
2. Validate the JSON and identifiers
2. Validate the JSON and identifiers
3. Rebuild RustProxy (`./dev.sh check` / the normal release build) and deploy
the newly built binary. The preset is **embedded**; editing the installed
source JSON alone does not update a running binary.
+2 -2
View File
@@ -5640,9 +5640,9 @@
},
"timepicker": {},
"timezone": "browser",
"title": "RustProxy 0.33.1 - Operations",
"title": "RustProxy 0.33.2 - Operations",
"uid": "rustproxy-0170",
"version": 6,
"weekStart": "",
"description": "RustProxy 0.29.2 operations dashboard including backend client lifecycle/retry diagnostics, bounded Traffic analytics, country and User-Agent-family visibility, and built-in Web Server traffic."
}
}
+106
View File
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""Progressbar Appearance regression using the production CSS in Chromium.
Browser-only visual/CSS verification. Does not execute RustProxy or authenticate.
"""
from __future__ import annotations
from io import BytesIO
from pathlib import Path
import re
from PIL import Image
from playwright.sync_api import sync_playwright
ROOT = Path(__file__).resolve().parents[2]
CSS = (ROOT / 'src/admin/web/style.css').read_text(encoding='utf-8')
APP_HTML = (ROOT / 'src/admin/web/index.html').read_text(encoding='utf-8')
assert re.search(r'<div class="overview-meter system-memory-meter" id="memory-meter"', APP_HTML)
assert 'id="memory-meter-fill"' in APP_HTML
CORNERS = {'default': '3px', 'square': '0px', 'subtle': '2px', 'rounded': '4px', 'extra': '999px'}
STYLES = ('default', 'soft', 'technical', 'high_contrast', 'neon', 'monochrome', 'warm', 'cool')
HTML = '''<!doctype html><html><body>
<section id="panel-cache"><div class="stats-grid cache-stats">
<article class="stat"><progress id="cp-hot-entries-meter" value="55" max="100"></progress></article>
<article class="stat"><progress id="cp-memory" value="55" max="100"></progress></article>
<article class="stat"><progress id="cp-cold-entries-meter" value="55" max="100"></progress></article>
<article class="stat"><progress id="cp-cold-disk" value="55" max="100"></progress></article>
</div></section>
<main id="panel-overview">
<div class="stat stat-meter"><div class="overview-meter" id="overview-progress"><span class="overview-meter-fill" id="overview-fill" style="width:55%"></span></div></div>
<article class="card system-card"><div class="overview-meter system-memory-meter" id="memory-meter" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="55"><span class="overview-meter-fill" id="memory-meter-fill" style="width:55%"></span></div></article>
<div class="www-segmented" id="www-segmented"><span style="width:55%;background:var(--chart-accent)"></span></div>
<div class="www-top-meter" id="www-top"><span id="www-top-fill" style="width:55%"></span></div>
</main>
<div class="traffic-bar-track" id="traffic-track"><span class="traffic-bar-fill" id="traffic-fill" style="width:55%"></span></div>
<div id="chart-swatch" style="background:var(--chart-accent);width:4px;height:4px"></div>
</body></html>'''
def color_at_progress(page, progress_id: str) -> tuple[int, int, int]:
rect = page.locator(f'#{progress_id}').bounding_box()
assert rect and rect['width'] > 100
screenshot = Image.open(BytesIO(page.screenshot())).convert('RGB')
x = round(rect['x'] + rect['width'] * .22)
y = round(rect['y'] + rect['height'] / 2)
return screenshot.getpixel((x, y))
with sync_playwright() as playwright:
browser = playwright.chromium.launch(headless=True, executable_path='/usr/bin/chromium', args=['--no-sandbox'])
for width in (375, 1440):
page = browser.new_page(viewport={'width': width, 'height': 900})
errors = []
page.on('pageerror', lambda error: errors.append(str(error)))
page.set_content(HTML)
page.add_style_tag(content=CSS)
for corner, radius in CORNERS.items():
page.evaluate('corner => document.documentElement.dataset.cornerStyle = corner', corner)
actual = page.evaluate('''() => {
const ids = ['cp-hot-entries-meter','cp-memory','cp-cold-entries-meter','cp-cold-disk',
'overview-progress','overview-fill','memory-meter','memory-meter-fill','www-segmented','www-top','www-top-fill',
'traffic-track','traffic-fill'];
return Object.fromEntries(ids.map(id => [id, getComputedStyle(document.getElementById(id)).borderRadius]));
}''')
assert all(value == radius for value in actual.values()), (corner, radius, actual)
actual_pseudo = page.evaluate('''() => {
const p = document.getElementById('cp-memory');
return ['::-webkit-progress-bar', '::-webkit-progress-value'].map(pseudo => getComputedStyle(p, pseudo).borderRadius);
}''')
assert actual_pseudo == [radius, radius], (corner, actual_pseudo)
for theme in ('dark', 'light'):
page.evaluate('theme => document.documentElement.dataset.theme = theme', theme)
for chart_style in STYLES:
page.evaluate('style => document.documentElement.dataset.chartStyle = style', chart_style)
observed = page.evaluate('''() => {
const color = id => getComputedStyle(document.getElementById(id)).backgroundColor;
const progress = document.getElementById('cp-memory');
return {
chart:color('chart-swatch'), native:getComputedStyle(progress).accentColor,
overview:color('overview-fill'), systemRam:color('memory-meter-fill'), www:color('www-top-fill'), traffic:color('traffic-fill'),
track:getComputedStyle(progress).backgroundColor,
overviewTrack:color('overview-progress'), systemTrack:color('memory-meter'), wwwTrack:color('www-top'),
};
}''')
assert len({observed[key] for key in ('chart', 'native', 'overview', 'systemRam', 'www', 'traffic')}) == 1, (theme, chart_style, observed)
assert len({observed[key] for key in ('track', 'overviewTrack', 'systemTrack', 'wwwTrack')}) == 1, (theme, chart_style, observed)
# Native progress on Chromium is painted by the WebKit pseudo-element;
# a pixel check confirms the color, not just the accent-color property.
if chart_style in ('default', 'neon', 'monochrome'):
color = color_at_progress(page, 'cp-memory')
chart_rgb = tuple(page.evaluate("""() => {
const canvas = document.createElement('canvas');
canvas.width = canvas.height = 1;
const ctx = canvas.getContext('2d');
ctx.fillStyle = getComputedStyle(document.getElementById('chart-swatch')).backgroundColor;
ctx.fillRect(0, 0, 1, 1);
return [...ctx.getImageData(0, 0, 1, 1).data].slice(0, 3);
}"""))
assert color == chart_rgb, (theme, chart_style, color, chart_rgb)
assert not errors, errors
page.close()
browser.close()
print('PASS: native Cache, System RAM, Overview, WWW and Traffic bars respond to 5 corners and 8 palettes (dark/light, 375/1440px).')
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env python3
"""System Load RAM meter updates using the actual combined panel JavaScript.
This is a simulated authenticated DOM/status test, not a Rust runtime integration test.
"""
from pathlib import Path
import json
import re
from playwright.sync_api import sync_playwright
ROOT = Path(__file__).resolve().parents[2]
router = (ROOT / 'src/admin/router.rs').read_text()
def bundle_for(section):
match = re.search(r'fn ' + section + r'\([^)]*\)[^{]*\{', router)
assert match, section
text = router[match.end():]
text = text[:re.search(r'\]\s*\.join\("\\n"\)', text).start()]
result = []
for line in text.splitlines():
line = line.strip()
if 'include_str!("' in line:
path = re.search(r'include_str!\("([^"]+)"\)', line).group(1)
result.append((ROOT / 'src/admin' / path).read_text())
elif line == 'admin_vhosts_javascript(),':
result.append('(() => {\n' + ''.join((ROOT / 'src/admin/web/vhosts' / f'{part}.js').read_text() + '\n'
for part in ['core', 'protocol', 'health', 'rates', 'editor', 'render']) + '})();')
elif line.startswith('"') and line.endswith('",'):
result.append(json.loads(line[:-1]))
return '\n'.join(result)
html = (ROOT / 'src/admin/web/index.html').read_text()
css = (ROOT / 'src/admin/web/style.css').read_text()
js = bundle_for('admin_app_javascript')
with sync_playwright() as playwright:
browser = playwright.chromium.launch(headless=True, executable_path='/usr/bin/chromium', args=['--no-sandbox'])
for width in (375, 1440):
page = browser.new_page(viewport={'width': width, 'height': 900})
errors = []
page.on('pageerror', lambda error: errors.append(str(error)))
page.set_content(html.replace('/assets/app___RUSTPROXY_ASSET_ID__.js', '').replace('/assets/style___RUSTPROXY_ASSET_ID__.css', ''))
page.add_style_tag(content=css)
page.add_script_tag(content=js)
page.wait_for_timeout(100)
report = page.evaluate('''() => {
currentPanel = "overview";
document.getElementById('workspace').hidden = false;
document.getElementById('panel-overview').hidden = false;
const meter = document.getElementById('memory-meter');
const fill = document.getElementById('memory-meter-fill');
const snapshot = (used, total) => ({system: {source:'measured',cpu_percent:2.1,
memory_used_bytes:used, memory_total_bytes:total},
live_system:{cpu_percent:2.1}, cache:{}, metrics:{}, listeners:{}});
const values = [];
for (const [used, total] of [[250,1000],[800,1000],[0,1000],[null,null]]) {
renderDiagnostics(snapshot(used,total), false, true);
values.push({fill:fill.style.width, now:meter.getAttribute('aria-valuenow'),
hidden:meter.hidden, text:meter.getAttribute('aria-valuetext')});
}
return { values, role:meter.getAttribute('role'), cssHeight:getComputedStyle(meter).height,
scrollbar:document.documentElement.scrollWidth>window.innerWidth+2 };
}''')
assert report['role'] == 'progressbar' and report['cssHeight'] == '5px', report
assert report['values'][0]['fill'] == '25%' and report['values'][0]['now'] == '25', report
assert report['values'][1]['fill'] == '80%' and report['values'][1]['now'] == '80', report
assert report['values'][2]['fill'] == '0%' and report['values'][2]['now'] == '0', report
assert report['values'][3]['fill'] == '0%' and report['values'][3]['now'] is None and report['values'][3]['hidden'], report
assert not report['scrollbar'] and not errors, (width, errors, report)
page.close()
browser.close()
print('PASS: System RAM meter live percentage and unknown/zero handling on 375/1440px in bundled panel.')
+1 -1
View File
@@ -2,7 +2,7 @@
"openapi": "3.0.3",
"info": {
"title": "RustProxy Administration API",
"version": "0.33.1",
"version": "0.33.2",
"description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust."
},
"servers": [
+4 -1
View File
@@ -18,6 +18,7 @@ window.rustProxyBackendDashboard = (() => {
}
function syncVisibility(hiddenOverride = null) {
const prefs = window.rustProxyPreferences?.current?.();
// While unlocked, currentDraft is authoritative until Save or Cancel.
const hidden = new Set(hiddenOverride ?? window.rustProxyInlineLayout?.currentDraft?.()?.hidden
?? prefs?.dashboard_hidden_widgets ?? ADMIN_APPEARANCE_DEFAULTS.dashboard_hidden_widgets);
for (const id of ids) if (card(id)) card(id).hidden = hidden.has(id);
@@ -49,7 +50,8 @@ window.rustProxyBackendDashboard = (() => {
window.rustProxyExtraOverview?.meter?.("backend-dashboard-capacity", null);
reset();
}
// Counter deltas are matched to stable (pool, member) IDs, not aggregate
// totals: removed/rebuilt pools must never become negative or spike rates.
function memberCounters(members) {
const values = new Map();
for (const { pool, member } of members) {
@@ -71,6 +73,7 @@ window.rustProxyBackendDashboard = (() => {
for (const [id, next] of current) {
const old = earlier.get(id);
if (!old) continue;
// A generation reset on a member invalidates its entire interval.
if (tracked.some(field => next[field] < old[field])) continue;
for (const field of tracked) total[field] += next[field] - old[field];
}
+3 -1
View File
@@ -1,4 +1,5 @@
// Opt-in compact metrics and bounded visualizations. All readings reuse the
// existing Overview live-status payload (no additional requests or timers).
window.rustProxyExtraOverview = (() => {
const limit = () => window.rustProxyOverviewSamples?.getLimit?.() ?? 60;
const names = [
@@ -150,6 +151,7 @@ window.rustProxyExtraOverview = (() => {
for (const id of names) {
const tile = node(id);
if (!tile) { lastCounter.delete(id); continue; }
// Visibility is only a presentation concern; retain live history.
const metric = read(id, status, now, record);
const value = metric.value;
if (lastUnits.get(id) !== metric.unit) {
+1 -1
View File
@@ -85,7 +85,7 @@ window.rustProxyWideOverview = (() => {
for (const [id, config] of Object.entries(configurations)) {
const node = tile(id);
if (!node) { baselines.delete(id); continue; }
// Keep collecting while hidden so restoring a widget does not restart rates.
const values = config.counters ? counterRates(id, config.counters(status), now, record)
: config.values(status).map(finite);
const series = history.get(id) || config.names.map(() => []);
+14 -2
View File
@@ -319,8 +319,20 @@ function renderDiagnostics(status, appendSample = true, systemSample = appendSam
$("system-source").hidden = !sourceSupported; if (sourceSupported) $("system-source").textContent = t("realSample");
const systemMemory = knownPair(d.memory_used_bytes, d.memory_total_bytes, value => fmtBytes(value));
setOptionalMetric("system-memory", systemMemory != null, systemMemory);
$("memory-meter").hidden = systemMemory == null;
if (d.memory_used_bytes != null && d.memory_total_bytes > 0) $("memory-meter").value = d.memory_used_bytes / d.memory_total_bytes * 100; else $("memory-meter").removeAttribute("value");
const ramMeter = $("memory-meter"), ramFill = $("memory-meter-fill");
ramMeter.hidden = systemMemory == null;
const ramUsed = Number(d.memory_used_bytes), ramTotal = Number(d.memory_total_bytes);
const ramPercent = d.memory_used_bytes != null && d.memory_total_bytes != null &&
Number.isFinite(ramUsed) && Number.isFinite(ramTotal) && ramTotal > 0
? Math.max(0, Math.min(100, ramUsed / ramTotal * 100)) : null;
ramFill.style.width = `${ramPercent ?? 0}%`;
if (ramPercent == null) {
ramMeter.removeAttribute("aria-valuenow");
ramMeter.setAttribute("aria-valuetext", "-");
} else {
ramMeter.setAttribute("aria-valuenow", String(ramPercent));
ramMeter.setAttribute("aria-valuetext", `${fmt(ramPercent, 1)}%`);
}
const systemSwap = knownPair(d.system_swap_used_bytes, d.system_swap_total_bytes, value => fmtBytes(value));
setOptionalMetric("system-swap", systemSwap != null, systemSwap);
const loadSupported = Array.isArray(d.load_average) && d.load_average.length > 0;
+1
View File
@@ -16,6 +16,7 @@ window.rustProxyStreamDashboard = (() => {
}
function syncVisibility(hiddenOverride = null) {
const prefs = window.rustProxyPreferences?.current?.();
// While unlocked, currentDraft is authoritative until Save or Cancel.
const hidden = new Set(hiddenOverride ?? window.rustProxyInlineLayout?.currentDraft?.()?.hidden
?? prefs?.dashboard_hidden_widgets ?? ADMIN_APPEARANCE_DEFAULTS.dashboard_hidden_widgets);
for (const id of ids) if (card(id)) card(id).hidden = hidden.has(id);
+36 -130
View File
@@ -1,137 +1,43 @@
{
"dashboard_widgets": [
"requests",
"active",
"bandwidth",
"errors",
"memory",
"websocket",
"backend_traffic",
"extra_response_latency",
"http_protocol",
"tls",
"websocket_protocol",
"backend_latency",
"stream_overview",
"stream_traffic",
"stream_sessions",
"stream_backend_health",
"backend_health",
"backend_activity",
"backend_errors",
"backend_retries",
"http2",
"backend_protocols",
"backend_requests",
"backend_capacity",
"backend_http5xx",
"backend_timeouts",
"backend_network",
"http3",
"backend_queue",
"extra_open_connections",
"extra_process_cpu",
"extra_process_rss",
"extra_process_fds",
"backend_rotations",
"extra_websocket_active",
"extra_tls_error_rate",
"extra_request_error_rate",
"extra_cache_hit_rate",
"extra_backend_active",
"extra_h2_share",
"extra_cache_memory",
"extra_backend_unhealthy",
"extra_total_rps",
"extra_active_exchanges",
"extra_h1_share",
"extra_h3_share",
"extra_cache_miss_rate",
"extra_cold_storage",
"wide_protocol_rates",
"wide_cpu",
"wide_memory",
"wide_cache_activity",
"wide_connections",
"wide_backend_health",
"wide_latency",
"cache",
"traffic_chart",
"system_chart",
"connection_chart",
"wide_transform_activity",
"wide_compression_activity",
"cache_diagnostics",
"extra_cold_queue_memory",
"process_diagnostics",
"network_diagnostics",
"backend_table",
"www_status",
"www_kind",
"www_capacity",
"www_methods",
"www_error_rate",
"www_php_requests",
"www_requests",
"www_latency",
"www_failures",
"www_traffic",
"www_top_services",
"www_status_trend"
"requests", "active", "bandwidth", "errors", "memory", "websocket",
"backend_traffic", "extra_response_latency", "http_protocol", "tls",
"websocket_protocol", "backend_latency", "stream_overview", "stream_traffic",
"stream_sessions", "stream_backend_health", "backend_health", "backend_activity",
"backend_errors", "backend_retries", "http2", "backend_protocols",
"backend_requests", "backend_capacity", "backend_http5xx", "backend_timeouts",
"backend_network", "http3", "backend_queue", "extra_open_connections",
"extra_process_cpu", "extra_process_rss", "extra_process_fds", "backend_rotations",
"extra_websocket_active", "extra_tls_error_rate", "extra_request_error_rate",
"extra_cache_hit_rate", "extra_backend_active", "extra_h2_share",
"extra_cache_memory", "extra_backend_unhealthy", "extra_total_rps",
"extra_active_exchanges", "extra_h1_share", "extra_h3_share",
"extra_cache_miss_rate", "extra_cold_storage", "wide_protocol_rates",
"wide_cpu", "wide_memory", "wide_cache_activity", "wide_connections",
"wide_backend_health", "wide_latency", "cache", "traffic_chart",
"system_chart", "connection_chart", "wide_transform_activity",
"wide_compression_activity", "cache_diagnostics", "extra_cold_queue_memory",
"process_diagnostics", "network_diagnostics", "backend_table",
"www_status", "www_kind", "www_capacity", "www_methods", "www_error_rate",
"www_php_requests", "www_requests", "www_latency", "www_failures",
"www_traffic", "www_top_services", "www_status_trend"
],
"dashboard_hidden_widgets": [
"stream_overview",
"stream_backend_health",
"tls",
"stream_sessions",
"backend_health",
"backend_activity",
"backend_errors",
"backend_protocols",
"backend_requests",
"backend_timeouts",
"backend_network",
"extra_process_cpu",
"extra_process_rss",
"extra_process_fds",
"extra_websocket_active",
"extra_tls_error_rate",
"extra_request_error_rate",
"extra_h2_share",
"extra_backend_unhealthy",
"extra_total_rps",
"extra_h1_share",
"extra_h3_share",
"wide_memory",
"wide_connections",
"wide_backend_health",
"wide_protocol_rates",
"extra_active_exchanges",
"extra_cache_miss_rate",
"extra_cache_hit_rate",
"backend_capacity",
"stream_traffic",
"backend_http5xx",
"bandwidth",
"backend_queue",
"wide_cpu",
"wide_latency",
"www_status",
"www_kind",
"www_capacity",
"www_methods",
"www_error_rate",
"www_php_requests",
"www_requests",
"www_latency",
"www_failures",
"www_traffic",
"www_top_services",
"www_status_trend",
"extra_cold_queue_memory",
"wide_cache_activity",
"backend_rotations"
"stream_overview", "stream_backend_health", "tls", "stream_sessions",
"backend_health", "backend_activity", "backend_errors", "backend_protocols",
"backend_requests", "backend_timeouts", "backend_network", "extra_process_cpu",
"extra_process_rss", "extra_process_fds", "extra_websocket_active",
"extra_tls_error_rate", "extra_request_error_rate", "extra_h2_share",
"extra_backend_unhealthy", "extra_total_rps", "extra_h1_share",
"extra_h3_share", "wide_memory", "wide_connections", "wide_backend_health",
"wide_protocol_rates", "extra_active_exchanges", "extra_cache_miss_rate",
"extra_cache_hit_rate", "backend_capacity", "stream_traffic", "backend_http5xx",
"bandwidth", "backend_queue", "wide_cpu", "wide_latency", "www_status",
"www_kind", "www_capacity", "www_methods", "www_error_rate",
"www_php_requests", "www_requests", "www_latency", "www_failures",
"www_traffic", "www_top_services", "www_status_trend",
"extra_cold_queue_memory", "wide_cache_activity", "backend_rotations"
],
"dashboard_density": "compact",
"overview_samples": 600
}
}
+6 -2
View File
@@ -1101,8 +1101,12 @@
<polyline id="system-sparkline" points="0,95 300,95"></polyline>
</svg>
<div class="memory-line"><span data-i18n="systemMemory">System RAM</span><b
id="system-memory">-</b></div><progress aria-label="System RAM"
data-i18n-aria="systemMemory" id="memory-meter" max="100" value="0"></progress>
id="system-memory">-</b></div>
<div class="overview-meter system-memory-meter" id="memory-meter" role="progressbar"
aria-label="System RAM" data-i18n-aria="systemMemory" aria-valuemin="0"
aria-valuemax="100" aria-valuetext="-">
<span class="overview-meter-fill" id="memory-meter-fill"></span>
</div>
<div class="memory-line"><span data-i18n="systemSwap">System swap</span><b
id="system-swap">-</b></div>
<div class="chart-footer"><span data-i18n="loadAverage">Load 1 / 5 / 15 min</span><span
+39 -20
View File
@@ -69,6 +69,9 @@
--chart-grid-opacity: .28;
--chart-grid-dash: none;
--chart-svg-filter: none;
--rp-progress-fill: var(--chart-accent);
--rp-progress-track: var(--line);
--rp-progress-radius: 3px;
--traffic-h1: var(--chart-accent);
--traffic-h2: var(--chart-secondary);
--traffic-h3: var(--chart-tertiary);
@@ -132,9 +135,6 @@ html[data-theme=light] {
--scrollbar-thumb-hover: #879684
}
/* Optional AMOLED palette: near-black surfaces, readable borders and restrained
mint/coral emphasis. Does not change the established dark appearance. */
html[data-theme=ultra_dark] {
color-scheme: dark;
--bg: #030404;
@@ -194,8 +194,6 @@ html[data-theme=ultra_dark] {
--scrollbar-thumb-hover: #617373;
}
/* Warm, muted ivory. No pure-white canvas; numeric values and charts retain
clear contrast against their slightly lighter, neutral card surfaces. */
html[data-theme=cream] {
color-scheme: light;
--bg: #f2ede3;
@@ -1686,21 +1684,25 @@ progress {
border: 0;
appearance: none;
-webkit-appearance: none;
background: var(--line);
accent-color: var(--green);
background: var(--rp-progress-track);
accent-color: var(--rp-progress-fill);
border-radius: var(--rp-progress-radius);
overflow: hidden
}
progress::-webkit-progress-bar {
background: var(--line)
background: var(--rp-progress-track);
border-radius: var(--rp-progress-radius)
}
progress::-webkit-progress-value {
background: var(--green)
background: var(--rp-progress-fill);
border-radius: var(--rp-progress-radius)
}
progress::-moz-progress-bar {
background: var(--green)
background: var(--rp-progress-fill);
border-radius: var(--rp-progress-radius)
}
.diagnostics-grid {
@@ -4465,7 +4467,7 @@ dialog .window-heading {
.traffic-bar-track {
height: 10px;
border-radius: 999px;
border-radius: var(--rp-progress-radius);
background: var(--chart-surface);
box-shadow: inset 0 0 0 1px var(--line);
overflow: hidden
@@ -4476,7 +4478,7 @@ dialog .window-heading {
height: 100%;
min-width: 2px;
border-radius: inherit;
background: var(--chart-accent)
background: var(--rp-progress-fill)
}
.traffic-bar-value {
@@ -11174,21 +11176,25 @@ html[data-dashboard-density=ultra] #panel-overview .dashboard-overview-grid>.car
html[data-corner-style=square] {
--rp-radius-surface: 0px;
--rp-radius-control: 0px;
--rp-progress-radius: 0px;
}
html[data-corner-style=subtle] {
--rp-radius-surface: 5px;
--rp-radius-control: 4px;
--rp-progress-radius: 2px;
}
html[data-corner-style=rounded] {
--rp-radius-surface: 12px;
--rp-radius-control: 8px;
--rp-progress-radius: 4px;
}
html[data-corner-style=extra] {
--rp-radius-surface: 18px;
--rp-radius-control: 12px;
--rp-progress-radius: 999px;
}
html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-style=rounded], [data-corner-style=extra]) :is(.card, .stat, dialog, .login-card, .appearance-section, .settings-tabs, .dashboard-widget-setting, .overview-layout-tile, .overview-layout-hidden-item, .overview-inline-hidden-item, .overview-inline-hidden, .notice, .toast, .chart-style-choice, .stream-dashboard-card, .pool-card, .vhost-card, .vh-backend-card, .entity, .settings-filter-row, .stream-backend-row, .file-log-advanced-parser, .table-container, .metric-info-popover) {
@@ -11244,8 +11250,8 @@ html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-sty
height: 10px;
margin: 9px 0 8px;
overflow: hidden;
border-radius: var(--rp-radius-control, 5px);
background: var(--line);
border-radius: var(--rp-progress-radius);
background: var(--rp-progress-track);
}
#panel-overview .overview-meter-fill {
@@ -11253,10 +11259,22 @@ html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-sty
width: 0;
height: 100%;
border-radius: inherit;
background: var(--chart-accent);
background: var(--rp-progress-fill);
transition: width 180ms ease;
}
/* System Load uses the same track/fill component as other Overview meters,
while retaining its compact 5px height and existing vertical spacing. */
#panel-overview .system-card .system-memory-meter {
display: block;
width: 100%;
height: 5px;
margin: 6px 0 9px;
overflow: hidden;
background: var(--rp-progress-track);
border-radius: var(--rp-progress-radius);
}
#panel-overview .stat-meter>small {
display: block;
overflow-wrap: anywhere;
@@ -11488,8 +11506,8 @@ html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-sty
width: 100%;
height: 10px;
margin: 9px 0 8px;
background: var(--line);
border-radius: var(--rp-radius-control, 5px);
background: var(--rp-progress-track);
border-radius: var(--rp-progress-radius);
overflow: hidden;
}
@@ -11546,14 +11564,15 @@ html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-sty
#panel-overview .www-top-meter {
height: 7px;
background: var(--line);
border-radius: 4px;
background: var(--rp-progress-track);
border-radius: var(--rp-progress-radius);
overflow: hidden;
}
#panel-overview .www-top-meter span {
display: block;
background: var(--chart-accent);
background: var(--rp-progress-fill);
border-radius: inherit;
height: 100%;
}