diff --git a/AGENTS.md b/AGENTS.md index fe122bc..829251b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index ed5f023..7a09897 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 `` 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 diff --git a/Cargo.lock b/Cargo.lock index 270b36c..3e5f122 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1881,7 +1881,7 @@ dependencies = [ [[package]] name = "rust-proxy" -version = "0.33.1" +version = "0.33.2" dependencies = [ "anyhow", "arc-swap", diff --git a/Cargo.toml b/Cargo.toml index 4710322..4077c5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 80bc861..adb5d1d 100644 --- a/README.md +++ b/README.md @@ -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 `` 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 `` 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 diff --git a/ROADMAP.md b/ROADMAP.md index aad1260..6311146 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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 diff --git a/SHA256SUMS b/SHA256SUMS index 1eedf95..70927c6 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -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 diff --git a/docs/DASHBOARD_PRESET.md b/docs/DASHBOARD_PRESET.md index 43dc0ed..36cfbaf 100644 --- a/docs/DASHBOARD_PRESET.md +++ b/docs/DASHBOARD_PRESET.md @@ -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. diff --git a/grafana/rustproxy-overview.json b/grafana/rustproxy-overview.json index 0c8de8f..0a578ca 100644 --- a/grafana/rustproxy-overview.json +++ b/grafana/rustproxy-overview.json @@ -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." -} +} \ No newline at end of file diff --git a/scripts/tests/progressbar_0332_browser_test.py b/scripts/tests/progressbar_0332_browser_test.py new file mode 100755 index 0000000..4bf2f87 --- /dev/null +++ b/scripts/tests/progressbar_0332_browser_test.py @@ -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'
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+''' + + +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).') diff --git a/scripts/tests/system_ram_meter_0332_browser_test.py b/scripts/tests/system_ram_meter_0332_browser_test.py new file mode 100755 index 0000000..cc5dbcc --- /dev/null +++ b/scripts/tests/system_ram_meter_0332_browser_test.py @@ -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.') diff --git a/src/admin/openapi.json b/src/admin/openapi.json index b803fe1..69936e6 100644 --- a/src/admin/openapi.json +++ b/src/admin/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "RustProxy Administration API", - "version": "0.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": [ diff --git a/src/admin/web/app/backend-dashboard.js b/src/admin/web/app/backend-dashboard.js index e808438..453a94a 100644 --- a/src/admin/web/app/backend-dashboard.js +++ b/src/admin/web/app/backend-dashboard.js @@ -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]; } diff --git a/src/admin/web/app/overview-extra-metrics.js b/src/admin/web/app/overview-extra-metrics.js index 866e672..c3e4d64 100644 --- a/src/admin/web/app/overview-extra-metrics.js +++ b/src/admin/web/app/overview-extra-metrics.js @@ -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) { diff --git a/src/admin/web/app/overview-wide-metrics.js b/src/admin/web/app/overview-wide-metrics.js index 1419773..f771678 100644 --- a/src/admin/web/app/overview-wide-metrics.js +++ b/src/admin/web/app/overview-wide-metrics.js @@ -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(() => []); diff --git a/src/admin/web/app/runtime.js b/src/admin/web/app/runtime.js index 36c5e2f..ec8d4ee 100644 --- a/src/admin/web/app/runtime.js +++ b/src/admin/web/app/runtime.js @@ -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; diff --git a/src/admin/web/app/stream-dashboard.js b/src/admin/web/app/stream-dashboard.js index b3ebd63..a86067e 100644 --- a/src/admin/web/app/stream-dashboard.js +++ b/src/admin/web/app/stream-dashboard.js @@ -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); diff --git a/src/admin/web/dashboard-defaults.json b/src/admin/web/dashboard-defaults.json index 868513e..df9e0c1 100644 --- a/src/admin/web/dashboard-defaults.json +++ b/src/admin/web/dashboard-defaults.json @@ -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 -} \ No newline at end of file +} diff --git a/src/admin/web/index.html b/src/admin/web/index.html index c150c5c..a0687de 100644 --- a/src/admin/web/index.html +++ b/src/admin/web/index.html @@ -1101,8 +1101,12 @@
System RAM-
+ id="system-memory">-
+
+ +
System swap-