v0.32.6_official
This commit is contained in:
1 parent
fc8711d4d8
commit
d2c6cf59a9
16 files changed
+1800
-1026
No files matched your search
+22
-12
@@ -5,7 +5,26 @@
|
||||
|
||||
## 0.32.6
|
||||
|
||||
- **Development-branch diagnostics charts (no version bump):** publish cached
|
||||
- **Development-branch Appearance layout:** replace the long
|
||||
half-width Overview cards settings list with a full-width, compact horizontal
|
||||
ribbon. Keep every visibility switch and reorder control operational, add
|
||||
left/right browsing, and provide an optional expanded grid. Preserve stored
|
||||
layout preferences, theme shapes, and the visual layout editor.
|
||||
|
||||
- **Development-branch dashboard polish:** keep the Cache
|
||||
hits/misses/bypasses per-second chart visually continuous across browser-tab
|
||||
minimize/restore by reusing the last valid rate sample until a fresh interval
|
||||
is available again, rather than leaving a visible gap. Preserve the 3 s
|
||||
Overview cadence and do not change server polling.
|
||||
- **Development-branch mobile/layout polish:** make the
|
||||
Overview page-intro action buttons equal-height and symmetric on mobile,
|
||||
tighten the Appearance → Dashboard layout → Overview cards editor into a more
|
||||
horizontal wrapped control strip, increase the gap between the `p` and the right border of the
|
||||
RustProxy logo mark, and normalize the Overview canvas gutter so small-small,
|
||||
small-large and large-large dashboard block transitions use the same spacing.
|
||||
|
||||
|
||||
- **Development-branch diagnostics charts:** publish cached
|
||||
CPU, process CPU/RSS and per-interface RX/TX samples in compact live status;
|
||||
charts follow the Overview update cadence (default 3 s, accepting saved 2 s)
|
||||
while all Process details still follow the selected System-info refresh. The
|
||||
@@ -14,7 +33,7 @@
|
||||
for reported WebKit jumps near Process/Network; physical iOS 27 validation is
|
||||
still required. Match the Origin protocol badge's radius to neighboring
|
||||
policy pills for both default and configured corner styles, preserving colors.
|
||||
- **Development-branch rate continuity (no version bump):** preserve cache
|
||||
- **Development-branch rate continuity:** preserve cache
|
||||
HIT/MISS and other counter-derived chart histories when a browser tab is
|
||||
suspended, minimized or restored from BFCache. Treat resumed/overdue
|
||||
observations as new counter baselines, not artificial 0/s samples. Show
|
||||
@@ -22,7 +41,7 @@
|
||||
values. Apply one shared rule to Cache, Transform, Overview, backend,
|
||||
vhost and Stream charts without adding polling or changing System-info
|
||||
scheduling. Add isolated JavaScript pause/resume and reset tests.
|
||||
- **Development-branch follow-up (no version bump):** update existing network-interface
|
||||
- **Development-branch follow-up:** update existing network-interface
|
||||
diagnostic chart nodes in place during live refresh, preserving DOM identity
|
||||
and avoiding scroll-anchor loss around the Process and Network panels on touch
|
||||
browsers. Keep network-interface history advancing on every live Overview update
|
||||
@@ -46,8 +65,6 @@
|
||||
|
||||
## 0.32.5
|
||||
|
||||
- Fix the administration panel startup `ReferenceError: Cannot access 'i18n' before initialization` in the new DNS/DDNS ACL editor. Define its localization helper before creating the DNS hint; preserve all ACL settings and translations. Add a JavaScript startup regression test to the release checks.
|
||||
- Fix Rust `E0505` during dynamic ACL DNS resolution: give `lookup_host` an independently owned hostname, while returning the original normalized cache key. The resolver timeout, allowlist expiration and fail-closed behavior are unchanged.
|
||||
- Add bounded DNS/DDNS hostnames as **allow-only** client ACL rules in reusable
|
||||
profiles, host policies, vhosts, locations and Docker ACL assignments.
|
||||
- Refresh A/AAAA answers asynchronously every 60 seconds and after config
|
||||
@@ -60,13 +77,6 @@
|
||||
|
||||
## 0.32.4
|
||||
|
||||
- Fix Rust `E0382` in retry construction: clone the upstream URI before
|
||||
passing it into the request builder so authority normalization can borrow
|
||||
the original URI afterward. Keep backend destination, Host and retry rules.
|
||||
- Reduce Overview gutters uniformly (normal 4px, dense 3px, comfortable
|
||||
12px). Override the older dashboard-density spacing selectors for the
|
||||
aligned grid so tile-to-tile and tile-to-panel transitions match in both
|
||||
directions without changing row symmetry.
|
||||
- Allow `preserve_host` for HTTP/1.1, HTTPS Auto H2/H1 ALPN, legacy compatible
|
||||
HTTP/2 and strict H2/H2C. Forward the public Host consistently as HTTP/2
|
||||
`:authority` while keeping DNS/TCP pinned to the configured origin URL and
|
||||
|
||||
@@ -9,7 +9,13 @@ streaming HTTP/TLS/WebSocket forwarding, raw TCP/UDP stream hosts, IP-bound vhos
|
||||
|
||||
## What changed in 0.32.6
|
||||
|
||||
- **Rate-chart pause recovery (0.32.6 development branch):** share one
|
||||
- **Appearance Dashboard layout ** Overview cards now use a
|
||||
full-width horizontal strip instead of a long list in the right half of
|
||||
Settings. Navigate sideways with the arrow buttons, toggle card visibility
|
||||
or reorder in place, or select **Show all cards** to expand to a full grid.
|
||||
The existing visual editor and saved per-account preferences are unchanged.
|
||||
|
||||
- **Rate-chart pause recovery:** share one
|
||||
counter continuity policy across Cache HIT/MISS/BYPASS, Transform, HTTP
|
||||
Overview/protocols, optional wide/small charts, backend and Stream/Vhost
|
||||
traffic charts. After tab hide/pagehide (including BFCache) or a delayed
|
||||
@@ -18,7 +24,7 @@ streaming HTTP/TLS/WebSocket forwarding, raw TCP/UDP stream hosts, IP-bound vhos
|
||||
zero still appears as 0/s. Cache and Transform intervals and the separate
|
||||
System information interval remain configurable; no additional polling.
|
||||
|
||||
- **Development fixes (same 0.32.6 version):** keep Network diagnostics
|
||||
- **Development fixes :** keep Network diagnostics
|
||||
chart DOM stable across live refreshes, preventing repeated row/SVG replacement
|
||||
during scrolling. Network-interface chart history advances at the Overview
|
||||
live-status cadence using the latest sampled rates; fetching system data still
|
||||
@@ -147,7 +153,7 @@ streaming HTTP/TLS/WebSocket forwarding, raw TCP/UDP stream hosts, IP-bound vhos
|
||||
|
||||
- **Overview readability at 90% UI scale:** compare the supplied 0.31.9 source with 0.32.0, remove the latter's forced desktop card heights and restore content-sized rows. The six-column desktop layout and visible chart legends remain intact. Increase compact Dashboard headings (10 → 11px), values (22 → 25px), secondary text (8.5 → 10px) and the Payload breakdown (9 → 10px).
|
||||
- **Consistent tile spacing:** Overview now uses `--ui-gap`, the same density-aware spacing variable as other Dashboard sections, instead of its own fixed 10/8/6px spacing in normal/compact/ultra Dashboard modes. UI scale (including 90%) affects all gaps equally.
|
||||
- **Appearance hints without horizontal scrolling:** Dashboard layout Stream card descriptions now use native, localized browser tooltips and accessible button descriptions instead of absolute-positioned custom bubbles. The settings grid fits narrower containers and obsolete tooltip CSS has been removed.
|
||||
- **Appearance hints without overflow-causing tooltips:** Dashboard layout Stream card descriptions now use native, localized browser tooltips and accessible button descriptions instead of absolute-positioned custom bubbles. The settings grid fits narrower containers and obsolete tooltip CSS has been removed.
|
||||
- Preserve Dashboard visibility/order preferences, density choices, responsive column breakpoints and opt-in Stream cards. No runtime, API, SQLite, proxy routing or language-pack changes. The Rust-independent `./dev.sh test` checks passed, then stopped with code 127 because Cargo is unavailable; **Cargo/build and authenticated-browser validation remain open** until executed on a supported runtime.
|
||||
|
||||
## Previously in 0.32.0
|
||||
|
||||
+15
-13
@@ -2,17 +2,17 @@
|
||||
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
|
||||
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
|
||||
a402ee07826dfab1b1d63b98c4a9f75809f056b908c92abacf0bba816d7ad133 AGENTS.md
|
||||
54383ec0610f11c61ef64d3cf0afa63f5667b212c03db653f37a9277a637df8c CHANGELOG.md
|
||||
e4a565f39f4ecca622f0eb27822c7399e45b4c4d1a2fd1f669185d53196a969b CHANGELOG.md
|
||||
934ec0e7f8a307b00319606a9c5d7e91dac6449a23dd1f3abcefe4304eb4de06 Cargo.lock
|
||||
8a7606650ce03f1faf36c893a17ccf1c9e8da4259e0248ac2c9bf030a64872d1 Cargo.toml
|
||||
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
|
||||
dc35bafda41c2e0db266523f553d0803740c5e842edfbdd7af202041c53aa359 README.md
|
||||
0f8cedf595f57f96644e8d4dc608b43e6ebd9a73b31157a210d624492a6c5206 README.md
|
||||
da519345b2bce492e747c268df2730971543d1bdb198c3e854671a7d107e5609 ROADMAP.md
|
||||
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
|
||||
f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs
|
||||
ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md
|
||||
4834c278430ddf3bf842995735195368f1ac2c3b5c1d749df4f9238caf1e2b25 deploy/rustproxy.service
|
||||
c51ac385fa992cdcfade11aba0151e20f1d2d01362deac7c04e585dfbc287f19 dev.sh
|
||||
26a607151fd343a59c6f147e70897c4b1bfeab376348f3270eb8a373503d7be5 dev.sh
|
||||
5f51efe621f45512a466fa7f2656867c6502a135ca040f4bfeec06a6919b39b6 docker-agent/README.md
|
||||
7fee7d51a7deb67e73e53a0bdff3aa4630aea9bab9b23a0f9b8001b2169892b1 docker-agent/certificate.go
|
||||
5e4f565c3c9de4dba897af2e801ea646a83395e72eae4717a6d502dff2fcec10 docker-agent/docker-agent.env.example
|
||||
@@ -61,9 +61,9 @@ d85c5567fc6a7714b96ad7cfe4ee9ff2085f84c4f31534a6484340d03a73e0f7 docs/RENEWAL.m
|
||||
ec7e32fd19e6ba6ed1732f1f3dd92ac0860609e8c0bbd3c1998840cdfb24bf54 docs/SECURITY.md
|
||||
620b69e566d27af04f0604f4ca3f88cf95cdfea7fbc6ae9dd92446d2b2b5d9be docs/SECURITY_AUDIT.md
|
||||
007537ee971463072b2482dfa4fdcf1955f837ced0b711d4eef3b6267d16d4f6 docs/SECURITY_AUDIT_0.31.0.md
|
||||
46d3cb5a2b5909902c85de193bc5129c751c5ebd19dc752db282588d54b5e25c docs/SETTINGS.md
|
||||
a41cef4a4da5e098fe3e9dfa0fffb3625f2e09792eacdf5dbf8d8fd532146d26 docs/SETTINGS.md
|
||||
8fc112cb45a2f35ef5b0dd4371aa6f43aca4df85db62c6ae02e56d74a3b702ae docs/STREAMS.md
|
||||
81997f880b89ea68b2820f47a83db030425e91ef5da90d1bd459754a1eb63f6c docs/TEST_REPORT.md
|
||||
130befc0f8216214567dd4b2df2574ea751b56b22c8f2cc3ba48e8d0aaccc3b5 docs/TEST_REPORT.md
|
||||
cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md
|
||||
751c9b13b47d0959a8575d6e82bdecba988b950a374ba0b084fbba08b4b65ebe docs/TRANSFORM.md
|
||||
fe3ec6ca16ff933239c12dacf39dd7b45717c0551a8b9f6ce601bf78136c9e37 docs/TRANSPORT.md
|
||||
@@ -95,8 +95,8 @@ cb01fdef286f38b442dd9252c23c4f8e44eb72e9ecdd99adb926e8e9f6318bb1 integrations/r
|
||||
96563a83e87cfb33e238c2a970ce36b1a1b51e91318d09b123c3755002ba2646 integrations/routeros-oci-build/build-routeros-oci.sh
|
||||
3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a integrations/routeros-oci-build/docker-entrypoint.sh
|
||||
71cde48daa538431dfac51e438b1ed23b061abfdb4e144ba704237d75ac6270c langs/README.md
|
||||
fc6695db8c2a9896dc97642ef70d56de86657521d0f4e1f2f57b99ac97c4f2dc langs/en_US.json
|
||||
a65d977f8380113d3ec7db5d51bffe50b87f5d93c5b139371f2351093b547216 langs/pl_pl.json
|
||||
a1f873bfe87d090bac46962598aeb90ddfa62ae257b3b76ec1af011c74197bac langs/en_US.json
|
||||
ca95ffe6919c07699155d1c49fa711916f91f0fbe928e5a36be23f05519ffbbb langs/pl_pl.json
|
||||
a99ad040be10fa028351e290b9363236b3513a1cadbdc9fb9e0260d517b9b0ae make_zip.py
|
||||
e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql
|
||||
20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql
|
||||
@@ -182,10 +182,10 @@ dc097b9c6ec8ebd4fa53ed8b0a641c9446fe44184c74246b521cfe49cf5a32b3 src/admin/web/
|
||||
e5928664c259feab3e9cfd3be69d05571f3aa60445ca373560f50857ecc8bfc0 src/admin/web/app/overview-extra-metrics.js
|
||||
fb0ba435beaa91e518537606f5fe55b0eea63c70efd290798c8d9754fa6771b2 src/admin/web/app/overview-masonry.js
|
||||
c4d59e129837db854f2851ac69130325f6d2f25fd1b67a009354b83da1dfbf02 src/admin/web/app/overview-wide-metrics.js
|
||||
a73d4efc9cf2f6bf7117f6f6dcaaea106e03c328451abbe4c10281179750899d src/admin/web/app/preferences.js
|
||||
7e0a4dc7274a1d1e4cc928e360a6b77c48a71d586b585a9e28c3bb2eeb17d46a src/admin/web/app/preferences.js
|
||||
8e75b0915edfbadefc4388701431541f4d35fd5d717363297b63740b3afd34d2 src/admin/web/app/rate-sampling.js
|
||||
43f4dddd1c3ea7fa42eeb9d7b655a8db48b5ddcc6c68213e08dfc2fcfbe8b17d src/admin/web/app/refresh.js
|
||||
96b19ad3fe98541013f278d63ccbe5c3f5a7f9b04a02167780176e6b15532057 src/admin/web/app/runtime.js
|
||||
7eee9fd6f930288ee3f87dd7a2c64bddb24ae5326cf69600accc376fb015fc87 src/admin/web/app/runtime.js
|
||||
31201586925e6f4792071d7d03fb05dcc4a69a03c02a4e85eb0746f45af68074 src/admin/web/app/session-resume.js
|
||||
86ac42bd4a6a84bb137d5a3c3dc3d1f4e404a543447845e6ea6e6e8210e268ce src/admin/web/app/settings.js
|
||||
fbd2c402828088695f5f5f4b210d1ea8da24ae6425f61248c82c5b07b14afd56 src/admin/web/app/stream-dashboard.js
|
||||
@@ -213,7 +213,7 @@ e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/
|
||||
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
|
||||
7cbdef9192a9dee1f8eac42b85f52b3190f7f6efae50004aa7ad116a910e4036 src/admin/web/file-logs.js
|
||||
b14e7b3578322f309627f6fe965cc16abcf85da3437c9070be6caecae1908743 src/admin/web/host404.js
|
||||
2574793d504ec835e477c599187b98d50014686da215cc8adc7548c1e2bdbe5e src/admin/web/index.html
|
||||
1335cf75c5da63bdb2b9978aa79ec3ad37fb66e6c3cb38647375227c37c2442f src/admin/web/index.html
|
||||
03ad9572bb14c34059636d612cf6b876d6361ce601817cff674c57b715dc65dd src/admin/web/integrations.js
|
||||
7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json
|
||||
cbd41678eadd994dc5252628ee8ef9551fa9f7d8694ca5abd206240d9a96ad30 src/admin/web/login.html
|
||||
@@ -223,7 +223,7 @@ c2e5f554810b8e34fb47361fb90a01073a2e332ee26d82e030956fad3ec1f83e src/admin/web/
|
||||
c28f4254ddc1f720d62646ee0f0b590b60a89c0bf447e8ec74ff3dde0c7635ba src/admin/web/settings.js
|
||||
8eadbada619b15087783dcfa0e889a81f88e78034d084c5ab616f7912b835940 src/admin/web/status_200.html
|
||||
c13cffe18a616fe5faa5ee945ab0d94d8d392ae8f32474c62fe41010e063ddb1 src/admin/web/streams.js
|
||||
aed93c1eb1d18735d9058755f3e648f0f74f2797855446f1d10d330dbf306edd src/admin/web/style.css
|
||||
cd89785fc8f27353983846c799f9256a1a893d775cb4853fb62b76c29e42f8c7 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
|
||||
@@ -498,7 +498,7 @@ e5c58fc9b7685ec911bfa86f6ca4f0cc97c19a90c0cff5b60ea9887f1afe9ced tools/checks/a
|
||||
0060f8217640403c6af2d5091fdc9087d0fb2fc30cd545a3c58277a396d8121c tools/checks/admin_public_origin_0314_sim.mjs
|
||||
fd082812411cff7afb71b2289a6f1a469b8d3c8a7cfdcdd9e567a2a4baf81be4 tools/checks/admin_surface_audit_0310.py
|
||||
c9d0bef4b8ed7a2f49b0fad3e116cace62d3e6e1ca31e02a3073d1fdde5157d8 tools/checks/admin_themes_0323_ui_test.mjs
|
||||
02b02ad7831524f963da3b80b1b42bb6efef9c4a63ce89649b18f70a0110d32d tools/checks/appearance_ux_0323_browser.py
|
||||
5b7d146a4558b2435fb4e8813db9e20e446988003fff884b3e132b28d926fa3c tools/checks/appearance_ux_0323_browser.py
|
||||
8b6e1a355cae58901118b399fa2d165f7e3fd8465c89cac82776906b8b23f0a6 tools/checks/appearance_ux_0323_ui_test.mjs
|
||||
f3dba206c60cd2e6fcc5f4a2c8d652ccf47362694448312cc1f8dc4112d93e7e tools/checks/backend_cache_0297_ui_test.mjs
|
||||
e82765ae946461952e68369f0b16f0a99f01e625bd61d2551513504f7b2f4d46 tools/checks/backend_dashboard_0323_ui_test.mjs
|
||||
@@ -507,6 +507,8 @@ a135c9456143975279f18233caab44b8c64f5025ef4ceeb1f2ad22090b588019 tools/checks/b
|
||||
a1fb118e3a32819689cb607d574b0060ca29ff1f6a68c505231a4e88ea93b5de tools/checks/certificate_api_0319.py
|
||||
526d16db9de7be414a64483a1e9276d4a4402e3c7efd2479f79d01cc5b388f46 tools/checks/certificate_api_0319_ui_test.mjs
|
||||
71cf48ba645ae2b7a95c73faa362d12942553e37d79680b796edf29c1484d9eb tools/checks/check_source.py
|
||||
a1ac2ab24958c6e8527c7797f224f94e765abd7173d4e5085a64362b193112c1 tools/checks/dashboard_appearance_ribbon_0326_browser.py
|
||||
0ab28370c06b2258321e20d27d63098c339fd9dc3b6f62aef6f5266c81ff28b5 tools/checks/dashboard_appearance_ribbon_0326_ui_test.mjs
|
||||
dc4df51fc15d4b6345c6625890ddbe014103851ebfa16abc583fbb0d809f4015 tools/checks/dashboard_boot_0311_ui_test.mjs
|
||||
7508f232a9e48049002dd4c9f34f9737bc92d304c51219f50430578dbc721855 tools/checks/dashboard_inline_layout_0323_ui_test.mjs
|
||||
dba768a42247cdd01863a032cf7ba223a52240285e70b0f7a0abd4a2e68c5fa1 tools/checks/dashboard_layout_editor_0323_ui_test.mjs
|
||||
@@ -553,7 +555,7 @@ db8881e46622ca2dbab4c29df003020006c611cd7fe087878c3e9c930cdd9853 tools/checks/s
|
||||
011bbe65dae072999a3951aba3d74a520926abf836b784cdb2395023d2942867 tools/checks/source_module_contracts.py
|
||||
37eebe90c0a60f7d7f4fbdd09986171b7dbd50c4d3760d94c31595dd93a2e680 tools/checks/source_pruning_0317.py
|
||||
be78df1e438d76be83e15398b310c9843ae5965b0ec6b41279b3a9650c313d8a tools/checks/stream_0320_ui_test.mjs
|
||||
e04f41b09c95e7bf67944c85690aafd825822877e9e0058cc872fc9e29bac78e tools/checks/stream_0320_visibility_regression.mjs
|
||||
6c7d20fa2abb55de997c19abd07ec1afa8d5121c91e07f231976bcdfbfbba778 tools/checks/stream_0320_visibility_regression.mjs
|
||||
8542f5c668155aa9f70de11e6fdaf64c5a80629a15ecc5f1d8c40f3d655f9b55 tools/checks/system_info_0301_ui_test.mjs
|
||||
543a488951ea6ec8fe06e04f168ccc4d4f2af3cfc281c9a728ab7bd87377fa98 tools/checks/system_info_no_subprocess_contracts.py
|
||||
e650225258168eeb9623694c6684f9a5ace92ca94debdff2558b5c77f7c998bc tools/checks/upstream_protocols_0323.py
|
||||
|
||||
@@ -128,6 +128,7 @@ if [[ "$mode" == "test" ]]; then
|
||||
node tools/checks/dashboard_inline_layout_0323_ui_test.mjs
|
||||
node tools/checks/admin_themes_0323_ui_test.mjs
|
||||
node tools/checks/appearance_ux_0323_ui_test.mjs
|
||||
node tools/checks/dashboard_appearance_ribbon_0326_ui_test.mjs
|
||||
node tools/checks/backend_protocol_0326_ui_test.mjs
|
||||
node tools/checks/overview_extra_metrics_0323_ui_test.mjs
|
||||
node tools/checks/rate_pause_0326_ui_test.mjs
|
||||
|
||||
@@ -37,6 +37,17 @@ Open **Settings → Certificate API** (`/settings?tab=certificate-api`) to manag
|
||||
|
||||
The existing **Generate / rotate token** form still creates new Key IDs (and retains its previous ability to rotate a Key ID by supplying it). Use **Edit settings** to modify settings without rotating, and **Regenerate token** to rotate without changing any settings. See [API](API.md) and [access control](API_ACCESS.md).
|
||||
|
||||
## Compact Overview card controls (0.32.6 development branch)
|
||||
|
||||
**Settings → Appearance → Dashboard layout** now puts Dashboard density on a
|
||||
compact row and gives the Overview card controls the full width of the section.
|
||||
The card selector is a single horizontally scrollable strip by default, with
|
||||
left/right browsing buttons and accessible keyboard focus. **Show all cards**
|
||||
expands the same 62 visibility/reorder controls into a wrapped grid. Use the
|
||||
existing visual layout editor for drag-and-drop arrangement. Reordering and
|
||||
visibility still save through `/api/preferences` per account; this UI-only
|
||||
change does not alter server polling, proxy routing or backend metrics.
|
||||
|
||||
## Refresh preferences (0.31.0)
|
||||
|
||||
**Settings -> Appearance -> Data refresh** saves an additional `refresh` object in the existing `admin_preferences` document for the signed-in account. It does not modify proxy configuration or create any migration. Older documents, including partially populated `refresh` objects, use defaults. Browser and server enforce exact allowed values:
|
||||
|
||||
@@ -1,5 +1,37 @@
|
||||
# Verification report - RustProxy 0.32.6
|
||||
|
||||
## 0.32.6 development follow-up — compact Appearance Dashboard layout
|
||||
|
||||
- The previous right-half, wrapping Overview card list left most of the screen
|
||||
empty and grew excessively tall with the 62-card registry. Moved Dashboard
|
||||
density to a compact full-width row and made the selector a full-width,
|
||||
single-line horizontal ribbon. Added previous/next browsing, a **Show all**
|
||||
expanded grid, horizontal earlier/later order buttons and Polish/English
|
||||
strings. Every existing toggle, order save, visual editor and account-scoped
|
||||
`/api/preferences` path remains intact.
|
||||
- Chromium fixture with real CSS, real 62-card renderer and mocked preferences:
|
||||
at 1500px, the default ribbon is 47px high and the Appearance dashboard
|
||||
section about 274px high; at 393px, the ribbon is 47px, the section about
|
||||
419px, and there is no page-level horizontal overflow. Programmatic browse,
|
||||
visibility-toggle, retained scroll position and expand/collapse all passed.
|
||||
`tools/checks/dashboard_appearance_ribbon_0326_browser.py` reproduces this
|
||||
test. This is a simulated interface test, not a compiled Rust or iOS Safari test.
|
||||
- Source/Node checks additionally verify translations, all card IDs and the
|
||||
unchanged preference persistence calls. Full Rust build and physical iOS
|
||||
acceptance remain pending.
|
||||
|
||||
|
||||
## 0.32.6 development follow-up — logo right-edge spacing
|
||||
|
||||
- Corrected the previous logo-spacing adjustment: the desired clearance is
|
||||
between the final `p` glyph and the **right border of the logo frame**,
|
||||
not between `/` and `p`. Removed extra slash margins and added a 2px
|
||||
right-side flex-box inset without changing the frame size or color.
|
||||
- Browser regression checks the text glyph's bounding rectangle against the
|
||||
frame at desktop and mobile widths; Chromium measures approximately 5px
|
||||
clearance compared with approximately 2px before correction. This does not
|
||||
require Rust compilation or imply a physical iOS Safari verification.
|
||||
|
||||
## 0.32.6 development follow-up — live diagnostic charts / mobile scroll
|
||||
|
||||
- The existing server diagnostics collector samples OS and process metrics in
|
||||
|
||||
@@ -2210,6 +2210,13 @@
|
||||
"dashboardDensityHelp": "Controls Overview telemetry card size without changing the rest of the interface.",
|
||||
"dashboardWidgets": "Overview cards",
|
||||
"dashboardWidgetsHelp": "Show, hide and reorder all Overview blocks, including charts, diagnostics and the backend table.",
|
||||
"dashboardWidgetsScrollHelp": "Scroll horizontally to browse cards, or expand the list.",
|
||||
"dashboardWidgetsMoveEarlier": "Move card earlier",
|
||||
"dashboardWidgetsMoveLater": "Move card later",
|
||||
"dashboardWidgetsScrollNext": "Scroll cards right",
|
||||
"dashboardWidgetsScrollBack": "Scroll cards left",
|
||||
"dashboardWidgetsCompact": "Compact view",
|
||||
"dashboardWidgetsShowAll": "Show all cards",
|
||||
"dashboardWidgetVisible": "Visible",
|
||||
"moveUp": "Move up",
|
||||
"moveDown": "Move down",
|
||||
|
||||
@@ -2210,6 +2210,13 @@
|
||||
"dashboardDensityHelp": "Steruje rozmiarem kafelków telemetrii w Overview bez zmiany reszty interfejsu.",
|
||||
"dashboardWidgets": "Kafelki Overview",
|
||||
"dashboardWidgetsHelp": "Pokazuj, ukrywaj i układaj wszystkie bloki Overview, także wykresy, diagnostykę i tabelę backendów.",
|
||||
"dashboardWidgetsScrollHelp": "Przewijaj kafelki poziomo lub rozwiń całą listę.",
|
||||
"dashboardWidgetsMoveEarlier": "Przesuń kafelek wcześniej",
|
||||
"dashboardWidgetsMoveLater": "Przesuń kafelek później",
|
||||
"dashboardWidgetsScrollNext": "Przewiń kafelki w prawo",
|
||||
"dashboardWidgetsScrollBack": "Przewiń kafelki w lewo",
|
||||
"dashboardWidgetsCompact": "Widok kompaktowy",
|
||||
"dashboardWidgetsShowAll": "Pokaż wszystkie",
|
||||
"dashboardWidgetVisible": "Widoczny",
|
||||
"moveUp": "Przenieś wyżej",
|
||||
"moveDown": "Przenieś niżej",
|
||||
|
||||
@@ -96,8 +96,6 @@ function applyDashboardLayout(order, hidden) {
|
||||
const normalizedOrder = normalizeDashboardWidgets(order), hiddenSet = new Set(normalizeHiddenDashboardWidgets(hidden));
|
||||
const grid = document.querySelector("#panel-overview .stats-grid"); if (!grid) return { order: normalizedOrder, hidden: [...hiddenSet] };
|
||||
const nodes = new Map([...grid.querySelectorAll("[data-dashboard-widget]")].map(node => [node.dataset.dashboardWidget, node]));
|
||||
// Re-appending an already ordered live grid can break touch-scroll
|
||||
// anchoring in mobile browsers. Move the DOM only on a real order change.
|
||||
const orderedNodes = normalizedOrder.map(id => nodes.get(id)).filter(Boolean);
|
||||
for (const node of orderedNodes) node.hidden = hiddenSet.has(node.dataset.dashboardWidget);
|
||||
const currentNodes = [...grid.querySelectorAll("[data-dashboard-widget]")];
|
||||
@@ -129,23 +127,25 @@ function dashboardWidgetTypeBadge(id) {
|
||||
}
|
||||
function renderDashboardWidgetSettings() {
|
||||
const list = $("dashboard-widget-list"); if (!list) return;
|
||||
const previousScroll = list.scrollLeft;
|
||||
const focusedRow = document.activeElement?.closest?.("[data-dashboard-widget-setting]");
|
||||
const focusedId = focusedRow && list.contains?.(focusedRow) ? focusedRow.dataset.dashboardWidgetSetting : null;
|
||||
const focusedAction = focusedId ? document.activeElement?.dataset.dashboardWidgetAction : null;
|
||||
const current = adminPreferences || ADMIN_APPEARANCE_DEFAULTS, order = normalizeDashboardWidgets(current.dashboard_widgets), hidden = new Set(normalizeHiddenDashboardWidgets(current.dashboard_hidden_widgets));
|
||||
const rows = order.map((id, index) => {
|
||||
const row = el("div", undefined, "dashboard-widget-setting"); row.dataset.dashboardWidgetSetting = id;
|
||||
const visible = !hidden.has(id);
|
||||
const toggle = el("button", undefined, "dashboard-widget-toggle"); toggle.type = "button"; toggle.classList.toggle("is-hidden", !visible); toggle.setAttribute("aria-pressed", String(visible)); toggle.setAttribute("aria-label", `${t("dashboardWidgetVisible")}: ${dashboardWidgetLabel(id)}`);
|
||||
const toggle = el("button", undefined, "dashboard-widget-toggle"); toggle.type = "button"; toggle.dataset.dashboardWidgetAction = "toggle"; toggle.classList.toggle("is-hidden", !visible); toggle.setAttribute("aria-pressed", String(visible)); toggle.setAttribute("aria-label", `${t("dashboardWidgetVisible")}: ${dashboardWidgetLabel(id)}`);
|
||||
const descriptionKey = ADMIN_DASHBOARD_BY_ID.get(id)?.description_key;
|
||||
if (descriptionKey) {
|
||||
const description = t(descriptionKey);
|
||||
row.title = description;
|
||||
toggle.setAttribute("aria-description", description);
|
||||
}
|
||||
// Type badges belong in the visual layout editor, not the long
|
||||
// Appearance settings list where they crowd the visibility controls.
|
||||
toggle.append(el("span", undefined, "dashboard-widget-visibility"), el("span", dashboardWidgetLabel(id), "dashboard-widget-setting-label"));
|
||||
const actions = el("span", undefined, "dashboard-widget-setting-actions");
|
||||
const up = el("button", "↑", "quiet"); up.type = "button"; up.disabled = index === 0; up.setAttribute("aria-label", t("moveUp"));
|
||||
const down = el("button", "↓", "quiet"); down.type = "button"; down.disabled = index === order.length - 1; down.setAttribute("aria-label", t("moveDown"));
|
||||
const up = el("button", "←", "quiet"); up.type = "button"; up.dataset.dashboardWidgetAction = "up"; up.disabled = index === 0; up.setAttribute("aria-label", t("dashboardWidgetsMoveEarlier"));
|
||||
const down = el("button", "→", "quiet"); down.type = "button"; down.dataset.dashboardWidgetAction = "down"; down.disabled = index === order.length - 1; down.setAttribute("aria-label", t("dashboardWidgetsMoveLater"));
|
||||
toggle.addEventListener("click", () => {
|
||||
const previous = [...hidden], next = new Set(hidden); if (visible) next.add(id); else next.delete(id);
|
||||
if (adminPreferences) adminPreferences.dashboard_hidden_widgets = [...next]; applyDashboardLayout(order, [...next]); renderDashboardWidgetSettings();
|
||||
@@ -160,6 +160,46 @@ function renderDashboardWidgetSettings() {
|
||||
up.addEventListener("click", () => move(-1)); down.addEventListener("click", () => move(1)); actions.append(up, down); row.append(toggle, actions); return row;
|
||||
});
|
||||
list.replaceChildren(...rows);
|
||||
list.scrollLeft = previousScroll;
|
||||
if (focusedId && focusedAction) {
|
||||
const row = [...list.children].find(item => item.dataset.dashboardWidgetSetting === focusedId);
|
||||
row?.querySelector(`[data-dashboard-widget-action="${focusedAction}"]`)?.focus({ preventScroll: true });
|
||||
}
|
||||
syncDashboardWidgetListView();
|
||||
if (typeof requestAnimationFrame === "function") requestAnimationFrame(syncDashboardWidgetScrollButtons);
|
||||
}
|
||||
let dashboardWidgetListExpanded = false;
|
||||
function syncDashboardWidgetListView() {
|
||||
const list = $("dashboard-widget-list"), expand = $("dashboard-widget-expand");
|
||||
if (!list || !expand) return;
|
||||
list.classList.toggle("is-expanded", dashboardWidgetListExpanded);
|
||||
expand.setAttribute("aria-expanded", String(dashboardWidgetListExpanded));
|
||||
const key = dashboardWidgetListExpanded ? "dashboardWidgetsCompact" : "dashboardWidgetsShowAll";
|
||||
expand.dataset.i18n = key;
|
||||
expand.textContent = t(key);
|
||||
for (const id of ["dashboard-widget-scroll-prev", "dashboard-widget-scroll-next"]) {
|
||||
const button = $(id); if (button) button.hidden = dashboardWidgetListExpanded;
|
||||
}
|
||||
syncDashboardWidgetScrollButtons();
|
||||
}
|
||||
function syncDashboardWidgetScrollButtons() {
|
||||
const list = $("dashboard-widget-list"); if (!list) return;
|
||||
const previous = $("dashboard-widget-scroll-prev"), next = $("dashboard-widget-scroll-next");
|
||||
if (previous) previous.disabled = dashboardWidgetListExpanded || list.scrollLeft <= 1;
|
||||
if (next) next.disabled = dashboardWidgetListExpanded || list.scrollLeft + list.clientWidth >= list.scrollWidth - 1;
|
||||
}
|
||||
$("dashboard-widget-expand")?.addEventListener("click", () => {
|
||||
dashboardWidgetListExpanded = !dashboardWidgetListExpanded;
|
||||
syncDashboardWidgetListView();
|
||||
});
|
||||
$("dashboard-widget-list")?.addEventListener?.("scroll", syncDashboardWidgetScrollButtons, { passive: true });
|
||||
window.addEventListener?.("resize", syncDashboardWidgetScrollButtons);
|
||||
for (const [id, direction] of [["dashboard-widget-scroll-prev", -1], ["dashboard-widget-scroll-next", 1]]) {
|
||||
$(id)?.addEventListener("click", () => {
|
||||
const list = $("dashboard-widget-list");
|
||||
if (!list) return;
|
||||
list.scrollBy({ left: direction * Math.max(160, list.clientWidth * .8), behavior: "smooth" });
|
||||
});
|
||||
}
|
||||
function refreshChartStylePalette(active = adminPreferences?.chart_style || "default") {
|
||||
const target = $("chart-style-palette"); if (!target || !languagePacks.size) return;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
function verifyAdminModules() {
|
||||
const required = [
|
||||
["rustProxyIntegrations", "bindings"],
|
||||
@@ -12,7 +11,6 @@ function verifyAdminModules() {
|
||||
const missing = required.find(([module, method]) => typeof window[module]?.[method] !== "function");
|
||||
if (missing) throw new Error(`Panel resources incomplete (${missing[0]}). Reload to retry.`);
|
||||
}
|
||||
|
||||
async function startAdminPanel() {
|
||||
try {
|
||||
verifyAdminModules();
|
||||
@@ -33,7 +31,6 @@ async function startAdminPanel() {
|
||||
}
|
||||
if (document.readyState === "complete") startAdminPanel();
|
||||
else document.addEventListener("DOMContentLoaded", startAdminPanel, { once: true });
|
||||
|
||||
function certificateUsesExternalFiles(cert) {
|
||||
return String(cert?.cert_file || "").startsWith("/") && String(cert?.key_file || "").startsWith("/");
|
||||
}
|
||||
@@ -53,7 +50,6 @@ function syncCertificateFileSource() {
|
||||
$("certificate-external-acl-example").hidden = !external;
|
||||
}
|
||||
$("certificate-file-source").addEventListener("change", syncCertificateFileSource);
|
||||
|
||||
function renderCertificates() {
|
||||
if (!envelope) return;
|
||||
const settings = envelope.config.tls || {};
|
||||
@@ -69,7 +65,6 @@ function renderCertificates() {
|
||||
if (settings.default_certificate === cert.id) used.push(t("defaultLabel"));
|
||||
const managed = !!cert.issuer, inUse = used.length > 0;
|
||||
const box = el("div", undefined, "entity certificate-card");
|
||||
|
||||
const head = el("div", undefined, "certificate-card-head");
|
||||
const identity = el("div", undefined, "certificate-card-identity");
|
||||
identity.append(el("span", cert.id, "entity-title"));
|
||||
@@ -152,7 +147,7 @@ $("reload-certificates").addEventListener("click", async () => {
|
||||
$("reset-certificate").addEventListener("click", () => { $("certificate-form").reset(); $("certificate-file-source").value = "managed"; syncCertificateFileSource(); });
|
||||
syncCertificateFileSource();
|
||||
|
||||
let systemSamples = [], connectionOpenSamples = [], connectionExchangeSamples = [], cacheMemorySamples = [], cacheTransientSamples = [], cacheFillSamples = [], cacheColdQueueSamples = [], cacheColdIoSamples = [], cacheColdStorageSamples = [], cacheHitSamples = [], cacheMissSamples = [], cacheBypassSamples = [], lastCacheCounters = 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();
|
||||
const hiddenNetworkInterfaces = (() => {
|
||||
try {
|
||||
@@ -206,8 +201,7 @@ function svgNode(tag, attrs = {}) {
|
||||
for (const [name, value] of Object.entries(attrs)) node.setAttribute(name, value);
|
||||
return node;
|
||||
}
|
||||
// Diagnostic charts keep their SVG and label nodes across samples. Updating the
|
||||
// existing Text node avoids remove/insert mutations while the user is scrolling.
|
||||
|
||||
function setChartText(node, value) {
|
||||
const text = String(value);
|
||||
if (node.firstChild?.nodeType === 3 && node.firstChild === node.lastChild) {
|
||||
@@ -216,8 +210,7 @@ function setChartText(node, value) {
|
||||
node.textContent = text;
|
||||
}
|
||||
}
|
||||
// Preserve stable Overview DOM while live samples arrive: replacing network SVGs
|
||||
// during an iOS WebKit momentum scroll can invalidate the current scroll anchor.
|
||||
|
||||
let latestNetworkInterfaceSnapshot = [];
|
||||
function renderNetworkInterfaceCharts(interfaces, appendSample) {
|
||||
const current = Array.isArray(interfaces) ? interfaces.slice().sort((a, b) => String(a.name || "").localeCompare(String(b.name || ""))) : [];
|
||||
@@ -305,8 +298,7 @@ function renderNetworkInterfaceCharts(interfaces, appendSample) {
|
||||
children.push(empty);
|
||||
}
|
||||
}
|
||||
// The common live-sample path updates only text and SVG path attributes.
|
||||
// Reparent rows solely when interfaces or visibility have actually changed.
|
||||
|
||||
if (children.length !== target.children.length || children.some((node, index) => node !== target.children[index])) {
|
||||
target.replaceChildren(...children);
|
||||
}
|
||||
@@ -316,7 +308,6 @@ function renderDiagnostics(status, appendSample = true, systemSample = appendSam
|
||||
$("metric-h3").textContent = fmt(m.http3_requests);
|
||||
$("metric-cache-ratio").textContent = c.hit_ratio == null ? "-" : `${fmt(c.hit_ratio * 100, 1)}%`;
|
||||
$("cache-summary").textContent = `${fmt(c.hits)} HIT / ${fmt(c.misses)} MISS`;
|
||||
// Slow details vs cached live chart telemetry.
|
||||
if (systemSample) {
|
||||
if (d.process_rss_bytes != null) $("metric-rss").textContent = fmtBytes(d.process_rss_bytes);
|
||||
$("process-summary").hidden = d.process_threads == null;
|
||||
@@ -358,9 +349,13 @@ function renderDiagnostics(status, appendSample = true, systemSample = appendSam
|
||||
const currentCounters = [c.hits, c.misses, c.bypasses].map(Number);
|
||||
if (currentCounters.every(Number.isFinite)) {
|
||||
const delta = rustProxyRateSampling.difference(lastCacheCounters, currentCounters, performance.now(), rustProxyRateSampling.cadence("overview_seconds"));
|
||||
if (delta.deltas || !lastCacheCounters) { const values = delta.deltas || [0, 0, 0];
|
||||
cacheHitSamples.push(values[0]); cacheMissSamples.push(values[1]); cacheBypassSamples.push(values[2]); cacheHitSamples = cacheHitSamples.slice(-60); cacheMissSamples = cacheMissSamples.slice(-60); cacheBypassSamples = cacheBypassSamples.slice(-60);
|
||||
} lastCacheCounters = delta.next;
|
||||
const values = delta.deltas || lastCacheOutcomeRates || (!lastCacheCounters ? [0, 0, 0] : null);
|
||||
if (values) {
|
||||
cacheHitSamples.push(values[0]); cacheMissSamples.push(values[1]); cacheBypassSamples.push(values[2]);
|
||||
cacheHitSamples = cacheHitSamples.slice(-60); cacheMissSamples = cacheMissSamples.slice(-60); cacheBypassSamples = cacheBypassSamples.slice(-60);
|
||||
lastCacheOutcomeRates = [...values];
|
||||
}
|
||||
lastCacheCounters = delta.next;
|
||||
}
|
||||
}
|
||||
if (appendSample) {
|
||||
|
||||
+35
-11
@@ -2477,8 +2477,9 @@
|
||||
data-appearance-reset="dashboard" data-i18n="resetSection"
|
||||
type="button">Reset section</button>
|
||||
</div>
|
||||
<div class="appearance-settings-grid">
|
||||
<label><span data-i18n="dashboardDensity">Dashboard density</span><select
|
||||
<div class="appearance-settings-grid dashboard-appearance-grid">
|
||||
<label class="dashboard-density-setting"><span
|
||||
data-i18n="dashboardDensity">Dashboard density</span><select
|
||||
id="ui-dashboard-density">
|
||||
<option data-i18n="densityNormal" value="normal">Normal</option>
|
||||
<option data-i18n="densityCompact" value="compact">Compact</option>
|
||||
@@ -2486,15 +2487,38 @@
|
||||
</select><small class="fine" data-i18n="dashboardDensityHelp">Controls
|
||||
Overview telemetry card size without changing the rest of the
|
||||
interface.</small></label>
|
||||
<div class="dashboard-widget-settings"><span
|
||||
data-i18n="dashboardWidgets">Overview cards</span><small class="fine"
|
||||
data-i18n="dashboardWidgetsHelp">Show, hide and reorder the telemetry
|
||||
cards at the top of Overview.</small>
|
||||
<button class="outline dashboard-layout-open-settings" type="button"
|
||||
id="overview-layout-open-settings"
|
||||
data-i18n="overviewLayoutEditorOpenSettings">Visual layout
|
||||
editor</button>
|
||||
<div class="dashboard-widget-list" id="dashboard-widget-list"></div>
|
||||
<div class="dashboard-widget-settings">
|
||||
<div class="dashboard-widget-settings-head">
|
||||
<div class="dashboard-widget-settings-intro">
|
||||
<span data-i18n="dashboardWidgets">Overview cards</span>
|
||||
<small class="fine" data-i18n="dashboardWidgetsHelp">Show, hide and
|
||||
reorder all Overview blocks, including charts, diagnostics and
|
||||
the backend table.</small>
|
||||
</div>
|
||||
<div class="dashboard-widget-settings-controls">
|
||||
<button class="outline dashboard-layout-open-settings" type="button"
|
||||
id="overview-layout-open-settings"
|
||||
data-i18n="overviewLayoutEditorOpenSettings">Visual layout
|
||||
editor</button>
|
||||
<button class="quiet" id="dashboard-widget-expand" type="button"
|
||||
aria-expanded="false" aria-controls="dashboard-widget-list"
|
||||
data-i18n="dashboardWidgetsShowAll">Show all</button>
|
||||
<button class="quiet dashboard-widget-scroll-button"
|
||||
id="dashboard-widget-scroll-prev" type="button"
|
||||
data-i18n-aria="dashboardWidgetsScrollBack"
|
||||
aria-label="Scroll cards left">←</button>
|
||||
<button class="quiet dashboard-widget-scroll-button"
|
||||
id="dashboard-widget-scroll-next" type="button"
|
||||
data-i18n-aria="dashboardWidgetsScrollNext"
|
||||
aria-label="Scroll cards right">→</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-widget-list" id="dashboard-widget-list" role="group"
|
||||
data-i18n-aria="dashboardWidgets" aria-label="Overview cards"
|
||||
tabindex="0"></div>
|
||||
<small class="fine dashboard-widget-scroll-hint"
|
||||
data-i18n="dashboardWidgetsScrollHelp">Scroll horizontally to browse
|
||||
cards, or expand the list.</small>
|
||||
<small class="fine" data-i18n="streamWidgetSettingsHelp">Stream cards are
|
||||
opt-in and only change Dashboard presentation, not Stream routing or
|
||||
monitoring.</small>
|
||||
|
||||
+1460
-962
File diff suppressed because it is too large.
Load diff
@@ -65,6 +65,18 @@ with sync_playwright() as playwright:
|
||||
assert page.locator('#test-action').evaluate('(n) => getComputedStyle(n).borderTopLeftRadius') == expected_control, (theme, style, 'button')
|
||||
assert page.locator('#test-input').evaluate('(n) => getComputedStyle(n).borderTopLeftRadius') == expected_control, (theme, style, 'input')
|
||||
page.evaluate('document.documentElement.dataset.theme = "dark"; document.documentElement.dataset.cornerStyle = "default"')
|
||||
# The last glyph must have room before the outlined logo's right edge,
|
||||
# including the 34px mobile variant; the slash-to-p spacing is unchanged.
|
||||
for width in (1320, 790, 450, 345):
|
||||
page.set_viewport_size({'width': width, 'height': 950})
|
||||
inset = page.locator('#test-logo').evaluate('''mark => {
|
||||
const p = [...mark.childNodes].find(node => node.nodeType === 3 && node.textContent.trim() === 'p');
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(p);
|
||||
return mark.getBoundingClientRect().right - range.getBoundingClientRect().right;
|
||||
}''')
|
||||
assert inset >= 4, (width, inset)
|
||||
page.set_viewport_size({'width': 1320, 'height': 950})
|
||||
original_logo = page.locator('#test-logo').evaluate('(n) => getComputedStyle(n).borderTopColor')
|
||||
page.evaluate('document.documentElement.dataset.theme = "ultra_dark"')
|
||||
black_logo = page.locator('#test-logo').evaluate('(n) => getComputedStyle(n).borderTopColor')
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Optional real-CSS/JS Chromium regression for the horizontal Overview card ribbon.
|
||||
|
||||
Mocks authenticated preferences; no server or physical iOS device involved.
|
||||
Run with: python tools/checks/dashboard_appearance_ribbon_0326_browser.py
|
||||
"""
|
||||
from pathlib import Path
|
||||
from playwright.sync_api import sync_playwright
|
||||
import shutil
|
||||
import json
|
||||
root=Path(__file__).resolve().parents[2]
|
||||
html=(root/'src/admin/web/index.html').read_text()
|
||||
css=(root/'src/admin/web/style.css').read_text()
|
||||
prefs=(root/'src/admin/web/app/preferences.js').read_text()
|
||||
registry=json.loads((root/'src/admin/web/dashboard-widgets.json').read_text())
|
||||
translations=json.loads((root/'langs/pl_pl.json').read_text())['translations']
|
||||
heading=html.index('data-i18n="dashboardLayout"')
|
||||
section_start=html.rfind('<section class="appearance-section">',0,heading)
|
||||
section_end=html.index('</section>',heading)+len('</section>')
|
||||
assert section_start >= 0
|
||||
source=html[section_start:section_end]
|
||||
start=prefs.index('function renderDashboardWidgetSettings() {')
|
||||
end=prefs.index('function refreshChartStylePalette(',start)
|
||||
code=prefs[start:end]
|
||||
stub='''
|
||||
const $ = id => document.getElementById(id);
|
||||
const el = (tag, txt, classes) => { let node=document.createElement(tag); if (txt!==undefined) node.textContent=String(txt); if (classes) node.className=classes; return node; };
|
||||
const labels = __TRANSLATIONS__;
|
||||
const registry = __REGISTRY__;
|
||||
const ADMIN_DASHBOARD_BY_ID = new Map(registry.map(def=>[def.id,def]));
|
||||
const order = registry.map(def=>def.id);
|
||||
let adminPreferences = {dashboard_widgets:order, dashboard_hidden_widgets:registry.filter(def=>def.default_hidden).map(def=>def.id)};
|
||||
const ADMIN_APPEARANCE_DEFAULTS = adminPreferences;
|
||||
function t(key){ return labels[key] || key; }
|
||||
function dashboardWidgetLabel(id) { return t(ADMIN_DASHBOARD_BY_ID.get(id)?.label_key || id); }
|
||||
function normalizeDashboardWidgets(value){return Array.isArray(value)?value:order;}
|
||||
function normalizeHiddenDashboardWidgets(value){return Array.isArray(value)?value:[];}
|
||||
function applyDashboardLayout(order,hidden){ window.__state={order,hidden}; }
|
||||
async function saveAppearancePreference(next){ Object.assign(adminPreferences,next);return {}; }
|
||||
'''.replace('__TRANSLATIONS__',json.dumps(translations,ensure_ascii=False)).replace('__REGISTRY__',json.dumps(registry))
|
||||
with sync_playwright() as p:
|
||||
browser=p.chromium.launch(executable_path=shutil.which('chromium') or shutil.which('chromium-browser'),args=['--no-sandbox'],headless=True)
|
||||
for width,height in [(1500,950),(393,850)]:
|
||||
page=browser.new_page(viewport={'width':width,'height':height},device_scale_factor=1)
|
||||
page.set_content('<!doctype html><html data-theme="dark" lang="pl"><head><style>'+css+'</style></head><body><main style="margin:0; padding:28px 16px; width:100%; max-width:100%; box-sizing:border-box">'+source+'</main></body></html>',wait_until='domcontentloaded')
|
||||
page.add_script_tag(content=stub+code+'\nrenderDashboardWidgetSettings();')
|
||||
page.wait_for_timeout(90)
|
||||
metrics=page.evaluate('''() => {
|
||||
const list=document.getElementById('dashboard-widget-list'), section=document.querySelector('.appearance-section');
|
||||
const parent=list.closest('.dashboard-widget-settings');
|
||||
return { total: list.children.length, listHeight:list.getBoundingClientRect().height,
|
||||
listWidth:list.getBoundingClientRect().width, contentWidth:list.scrollWidth,
|
||||
sectionHeight:section.getBoundingClientRect().height,
|
||||
overflowX:getComputedStyle(list).overflowX,
|
||||
firstWidth:list.firstElementChild.getBoundingClientRect().width,
|
||||
density: document.querySelector('.dashboard-density-setting').getBoundingClientRect().width,
|
||||
widget:parent.getBoundingClientRect().width,
|
||||
viewportOverflow:document.documentElement.scrollWidth>window.innerWidth,
|
||||
nextDisabled:document.getElementById('dashboard-widget-scroll-next').disabled};
|
||||
}''')
|
||||
print(width,'COMPACT',metrics)
|
||||
assert metrics['total']==len(registry)
|
||||
assert metrics['listHeight']<66,(width,metrics)
|
||||
assert metrics['contentWidth']>metrics['listWidth'],(width,metrics)
|
||||
assert metrics['sectionHeight'] < (320 if width>700 else 450), (width,metrics)
|
||||
assert not metrics['viewportOverflow']
|
||||
page.locator('#dashboard-widget-scroll-next').click()
|
||||
page.wait_for_timeout(450)
|
||||
pos=page.evaluate('document.getElementById("dashboard-widget-list").scrollLeft')
|
||||
print(width,'scroll after next:',pos)
|
||||
assert pos>10
|
||||
page.locator('.dashboard-widget-setting').nth(7).locator('button.dashboard-widget-toggle').click(force=True)
|
||||
page.wait_for_timeout(60)
|
||||
state=page.evaluate('window.__state')
|
||||
assert state and len(state['order'])==len(registry)
|
||||
retained=page.evaluate('document.getElementById("dashboard-widget-list").scrollLeft')
|
||||
assert retained>=pos-15,(pos,retained)
|
||||
page.locator('#dashboard-widget-expand').click()
|
||||
page.wait_for_timeout(60)
|
||||
expanded=page.evaluate('''() => { const list = document.getElementById("dashboard-widget-list"); return { height:list.getBoundingClientRect().height, mode:getComputedStyle(list).display, count:list.children.length, expanded:document.getElementById('dashboard-widget-expand').getAttribute('aria-expanded'), prevHidden:document.getElementById('dashboard-widget-scroll-prev').hidden } }''')
|
||||
print(width,'EXPANDED',expanded)
|
||||
assert expanded['mode']=='grid'
|
||||
assert expanded['expanded']=='true' and expanded['prevHidden']
|
||||
assert expanded['height']>metrics['listHeight']*3
|
||||
page.locator('#dashboard-widget-expand').click()
|
||||
assert page.locator('#dashboard-widget-expand').get_attribute('aria-expanded')=='false'
|
||||
outfile=str(root.parent / f'dashboard_appearance_0326_preview_{width}.png')
|
||||
page.locator('.appearance-section').screenshot(path=outfile)
|
||||
print('screenshot',outfile)
|
||||
page.close()
|
||||
browser.close()
|
||||
@@ -0,0 +1,36 @@
|
||||
// Appearance ribbon: compact by default, with full-width expansion and the same
|
||||
// persisted visibility/reorder controls. No browser or Rust service required.
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
|
||||
const html = fs.readFileSync('src/admin/web/index.html', 'utf8');
|
||||
const css = fs.readFileSync('src/admin/web/style.css', 'utf8');
|
||||
const js = fs.readFileSync('src/admin/web/app/preferences.js', 'utf8');
|
||||
const registry = JSON.parse(fs.readFileSync('src/admin/web/dashboard-widgets.json', 'utf8'));
|
||||
const locales = ['en_US', 'pl_pl'].map(id => JSON.parse(fs.readFileSync(`langs/${id}.json`, 'utf8')).translations);
|
||||
for (const id of ['dashboard-widget-list', 'dashboard-widget-expand', 'dashboard-widget-scroll-prev',
|
||||
'dashboard-widget-scroll-next', 'overview-layout-open-settings', 'ui-dashboard-density']) {
|
||||
assert(html.includes(`id="${id}"`), `missing ${id}`);
|
||||
}
|
||||
assert.match(html, /class="appearance-settings-grid dashboard-appearance-grid"/);
|
||||
assert.match(css, /\.appearance-settings-grid\.dashboard-appearance-grid\s*\{[^}]*grid-template-columns:\s*minmax\(0, 1fr\)/s,
|
||||
'dashboard setting layout must use the full available width');
|
||||
assert.match(css, /\.dashboard-appearance-grid \.dashboard-widget-list\s*\{[^}]*flex-wrap:\s*nowrap;[^}]*overflow-x:\s*auto;/s,
|
||||
'the default card selector must be a horizontal ribbon, not a tall two-column list');
|
||||
assert.match(css, /\.dashboard-appearance-grid \.dashboard-widget-list\.is-expanded\s*\{[^}]*display:\s*grid;/s);
|
||||
assert.match(css, /\.dashboard-widget-settings-controls > \.dashboard-widget-scroll-button/);
|
||||
assert(js.includes('list.scrollLeft = previousScroll'), 'rebuilding settings must preserve horizontal scroll position');
|
||||
assert(js.includes('focusedId') && js.includes('preventScroll: true'), 'keyboard focus must survive updates');
|
||||
assert(js.includes('saveAppearancePreference({ dashboard_hidden_widgets:') &&
|
||||
js.includes('saveAppearancePreference({ dashboard_widgets:'), 'both APIs must retain persistence');
|
||||
assert(js.includes('const up = el("button", "←"') && js.includes('const down = el("button", "→"'),
|
||||
'horizontal order controls must align with the ribbon');
|
||||
assert.equal(registry.length, 62, 'all configured Overview cards must remain available');
|
||||
for (const translation of locales) {
|
||||
for (const key of ['dashboardWidgetsShowAll', 'dashboardWidgetsCompact', 'dashboardWidgetsScrollBack',
|
||||
'dashboardWidgetsScrollNext', 'dashboardWidgetsScrollHelp',
|
||||
'dashboardWidgetsMoveEarlier', 'dashboardWidgetsMoveLater']) {
|
||||
assert(translation[key], `missing ${key}`);
|
||||
}
|
||||
}
|
||||
console.log('PASS: full-width compact Appearance ribbon with expandable 62-card layout, keyboard focus and preference persistence');
|
||||
@@ -100,8 +100,10 @@ assert(ids.every(id => cards.get(id).hidden), 'hiding cards must still work');
|
||||
assert(!cards.get('requests').hidden, 'legacy widgets must retain their visibility');
|
||||
// Native title tooltips do not participate in layout or create horizontal scroll.
|
||||
assert.doesNotMatch(css, /dashboard-widget-(?:description|setting-label-block)/, 'obsolete custom tooltip styles must be removed');
|
||||
assert.match(css, /grid-template-columns: repeat\(auto-fit, minmax\(min\(260px, 100%\), 1fr\)\)/,
|
||||
'effective Appearance card columns must preserve a readable name and right-aligned arrows');
|
||||
assert.match(css, /\.dashboard-appearance-grid \.dashboard-widget-list\s*\{[^}]*overflow-x: auto;/s,
|
||||
'Appearance must use a compact horizontal ribbon instead of long half-width columns');
|
||||
assert.match(css, /\.dashboard-appearance-grid \.dashboard-widget-list > \.dashboard-widget-setting\s*\{[^}]*min-width: 226px;/s,
|
||||
'Appearance ribbon must retain readable labels and right-aligned reorder buttons');
|
||||
assert.doesNotMatch(prefsSource, /role", "tooltip"|aria-describedby|dashboard-widget-description/,
|
||||
'old popover DOM and associations must be removed');
|
||||
const widgetList = { children: [], replaceChildren(...children) { this.children = children; } };
|
||||
|
||||
Reference in new issue
Block a user