From d2c6cf59a9d8e22d8907a556d39d0776ded7d4e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Wed, 14 Oct 2026 13:04:06 +0200 Subject: [PATCH] v0.32.6_official --- CHANGELOG.md | 34 +- README.md | 12 +- SHA256SUMS | 28 +- dev.sh | 1 + docs/SETTINGS.md | 11 + docs/TEST_REPORT.md | 32 + langs/en_US.json | 7 + langs/pl_pl.json | 7 + src/admin/web/app/preferences.js | 54 +- src/admin/web/app/runtime.js | 27 +- src/admin/web/index.html | 46 +- src/admin/web/style.css | 2424 ++++++++++------- tools/checks/appearance_ux_0323_browser.py | 12 + ...ashboard_appearance_ribbon_0326_browser.py | 91 + ...shboard_appearance_ribbon_0326_ui_test.mjs | 36 + .../stream_0320_visibility_regression.mjs | 6 +- 16 files changed, 1801 insertions(+), 1027 deletions(-) create mode 100755 tools/checks/dashboard_appearance_ribbon_0326_browser.py create mode 100644 tools/checks/dashboard_appearance_ribbon_0326_ui_test.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fb2a6a..dbb01e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index f45aebb..fff1c0e 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/SHA256SUMS b/SHA256SUMS index b2b7dc5..7fb0f6c 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -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 diff --git a/dev.sh b/dev.sh index 0a559a1..d7e589a 100755 --- a/dev.sh +++ b/dev.sh @@ -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 diff --git a/docs/SETTINGS.md b/docs/SETTINGS.md index 362dd7e..9ea3c68 100644 --- a/docs/SETTINGS.md +++ b/docs/SETTINGS.md @@ -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: diff --git a/docs/TEST_REPORT.md b/docs/TEST_REPORT.md index 4a5dc5d..0e42dc2 100644 --- a/docs/TEST_REPORT.md +++ b/docs/TEST_REPORT.md @@ -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 diff --git a/langs/en_US.json b/langs/en_US.json index 8f9b9d2..660d702 100644 --- a/langs/en_US.json +++ b/langs/en_US.json @@ -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", diff --git a/langs/pl_pl.json b/langs/pl_pl.json index c5ddecf..16d1f9d 100644 --- a/langs/pl_pl.json +++ b/langs/pl_pl.json @@ -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", diff --git a/src/admin/web/app/preferences.js b/src/admin/web/app/preferences.js index 27cef8b..94ee171 100644 --- a/src/admin/web/app/preferences.js +++ b/src/admin/web/app/preferences.js @@ -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; diff --git a/src/admin/web/app/runtime.js b/src/admin/web/app/runtime.js index 4df059f..2ba1ce3 100644 --- a/src/admin/web/app/runtime.js +++ b/src/admin/web/app/runtime.js @@ -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) { diff --git a/src/admin/web/index.html b/src/admin/web/index.html index 346799f..b14caf2 100644 --- a/src/admin/web/index.html +++ b/src/admin/web/index.html @@ -2477,8 +2477,9 @@ data-appearance-reset="dashboard" data-i18n="resetSection" type="button">Reset section -
- -
Overview cardsShow, hide and reorder the telemetry - cards at the top of Overview. - -
+
+
+
+ Overview cards + Show, hide and + reorder all Overview blocks, including charts, diagnostics and + the backend table. +
+
+ + + + +
+
+
+ Scroll horizontally to browse + cards, or expand the list. Stream cards are opt-in and only change Dashboard presentation, not Stream routing or monitoring. diff --git a/src/admin/web/style.css b/src/admin/web/style.css index 05766a2..a28aeba 100644 --- a/src/admin/web/style.css +++ b/src/admin/web/style.css @@ -45,11 +45,11 @@ --warn-line: #786647; --blue: #8ebcff; --transform-chart: #d7adff; - --backdrop: rgb(0 0 0 / .68); + --backdrop: rgba(0, 0, 0, .68); --backdrop-soft: #070d12b8; --shadow-mid: #0006; --shadow-strong: #0009; - --neutral-soft: rgba(127, 127, 127, .05); + --neutral-soft: hsla(0, 0%, 49.8%, .05); --scrollbar-track: #151a16; --scrollbar-thumb: #4f5d50; --scrollbar-thumb-hover: #6b7b69; @@ -83,10 +83,10 @@ --sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif } -html[data-theme="light"] { +html[data-theme=light] { color-scheme: light; --bg: #f4f6f2; - --panel: #ffffff; + --panel: #fff; --panel2: #edf1eb; --line: #cbd3c7; --muted: #5e685f; @@ -101,7 +101,7 @@ html[data-theme="light"] { --text-subtle: #687369; --separator: #9ba69b; --border-hover: #839083; - --field-bg: #ffffff; + --field-bg: #fff; --field-line: #b7c1b5; --placeholder: #7a867b; --row-line: #e0e5dd; @@ -111,9 +111,9 @@ html[data-theme="light"] { --code-alt-bg: #eef3f6; --code-alt-text: #2d4049; --nav-selected-bg: #e1e9db; - --accent-ink: #ffffff; + --accent-ink: #fff; --accent-hover: #a84f30; - --selected-ink: #ffffff; + --selected-ink: #fff; --notice-text: #685b45; --notice-line: #d4c2a5; --notice-bg: #fff9ef; @@ -124,8 +124,8 @@ html[data-theme="light"] { --warn-line: #c6b27e; --blue: #4f77a9; --transform-chart: #7d4fa3; - --shadow-mid: rgb(31 39 33 / .16); - --shadow-strong: rgb(31 39 33 / .26); + --shadow-mid: rgba(31, 39, 33, .16); + --shadow-strong: rgba(31, 39, 33, .26); --neutral-soft: rgba(70, 78, 72, .05); --scrollbar-track: #e7ece4; --scrollbar-thumb: #a9b5a6; @@ -135,7 +135,7 @@ html[data-theme="light"] { /* 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"] { +html[data-theme=ultra_dark] { color-scheme: dark; --bg: #030404; --panel: #0b0d0e; @@ -148,7 +148,7 @@ html[data-theme="ultra_dark"] { --logo-secondary: #cfe0ac; --green: #b1dcb0; --red: #ff9899; - --sidebar-bg: #000000; + --sidebar-bg: #000; --brand-secondary: #a9cfb5; --nav-active-bg: #172422; --nav-hover-bg: #121819; @@ -184,11 +184,11 @@ html[data-theme="ultra_dark"] { --warn-line: #89754c; --blue: #8bbdfb; --transform-chart: #cfaeff; - --backdrop: rgb(0 0 0 / .86); - --backdrop-soft: rgb(0 0 0 / .77); - --shadow-mid: rgb(0 0 0 / .64); - --shadow-strong: rgb(0 0 0 / .84); - --neutral-soft: rgb(132 171 158 / .045); + --backdrop: rgba(0, 0, 0, .86); + --backdrop-soft: rgba(0, 0, 0, .77); + --shadow-mid: rgba(0, 0, 0, .64); + --shadow-strong: rgba(0, 0, 0, .84); + --neutral-soft: rgba(132, 171, 158, .045); --scrollbar-track: #060808; --scrollbar-thumb: #364243; --scrollbar-thumb-hover: #617373; @@ -196,7 +196,7 @@ html[data-theme="ultra_dark"] { /* 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"] { +html[data-theme=cream] { color-scheme: light; --bg: #f2ede3; --panel: #fcf9f2; @@ -224,9 +224,9 @@ html[data-theme="cream"] { --code-alt-bg: #e7ebe9; --code-alt-text: #364f4e; --nav-selected-bg: #d9cbb7; - --accent-ink: #ffffff; + --accent-ink: #fff; --accent-hover: #774632; - --selected-ink: #ffffff; + --selected-ink: #fff; --notice-text: #765e40; --notice-line: #d9c3a1; --notice-bg: #f8efdf; @@ -243,22 +243,22 @@ html[data-theme="cream"] { --warn-line: #b49c6b; --blue: #4d7292; --transform-chart: #83659a; - --backdrop: rgb(37 27 16 / .44); - --backdrop-soft: rgb(47 35 21 / .48); - --shadow-mid: rgb(64 49 34 / .13); - --shadow-strong: rgb(64 49 34 / .24); - --neutral-soft: rgb(104 91 69 / .045); + --backdrop: rgba(37, 27, 16, .44); + --backdrop-soft: rgba(47, 35, 21, .48); + --shadow-mid: rgba(64, 49, 34, .13); + --shadow-strong: rgba(64, 49, 34, .24); + --neutral-soft: rgba(104, 91, 69, .045); --scrollbar-track: #ece4d7; --scrollbar-thumb: #b5a997; --scrollbar-thumb-hover: #978a76; } -/* Theme palettes change color only; corners are controlled independently. */ -html[data-theme="ultra_dark"] :is(.card, .stat, dialog) { - box-shadow: 0 6px 24px rgb(0 0 0 / .18); +html[data-theme=ultra_dark] :is(.card, .stat, dialog) { + box-shadow: 0 6px 24px rgba(0, 0, 0, .18); } -html[data-theme="cream"] :is(.card, .stat, dialog) { - box-shadow: 0 6px 20px rgb(78 58 38 / .055); + +html[data-theme=cream] :is(.card, .stat, dialog) { + box-shadow: 0 6px 20px rgba(78, 58, 38, .055); } * { @@ -267,26 +267,26 @@ html[data-theme="cream"] :is(.card, .stat, dialog) { scrollbar-width: thin } -*::-webkit-scrollbar { +::-webkit-scrollbar { width: 10px; height: 10px } -*::-webkit-scrollbar-track { +::-webkit-scrollbar-track { background: var(--scrollbar-track) } -*::-webkit-scrollbar-thumb { +::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border: 2px solid var(--scrollbar-track); border-radius: 999px } -*::-webkit-scrollbar-thumb:hover { +::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover) } -*::-webkit-scrollbar-corner { +::-webkit-scrollbar-corner { background: var(--scrollbar-track) } @@ -365,8 +365,8 @@ html:not(.app-loading) .app-startup { display: inline-flex } -html.app-startup-failed .app-startup-spinner, -html.app-startup-failed .app-startup-message { +html.app-startup-failed .app-startup-message, +html.app-startup-failed .app-startup-spinner { display: none } @@ -381,7 +381,7 @@ html.app-startup-failed .app-startup-recovery { @keyframes app-startup-spin { to { - transform: rotate(360deg) + transform: rotate(1turn) } } @@ -391,10 +391,14 @@ html.app-startup-failed .app-startup-recovery { } } -@media (prefers-reduced-motion: reduce) { +@media(prefers-reduced-motion:reduce) { .app-startup-spinner { animation: none } + + * { + scroll-behavior: auto !important + } } body { @@ -413,21 +417,22 @@ textarea { font: inherit } -button, a, +button, input, -textarea, -select { +select, +textarea { -webkit-tap-highlight-color: transparent } -button, -a { +a, +button { touch-action: manipulation } button { - cursor: pointer + cursor: pointer; + border: 0 } button:disabled { @@ -451,16 +456,12 @@ p { margin: 0 } -button:focus-visible, -a:focus-visible { +a:focus-visible, +button:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px } -button { - border: 0 -} - h2 { letter-spacing: -.5px } @@ -493,7 +494,9 @@ h2 { border: 1px solid var(--logo-accent, var(--accent)); color: var(--logo-accent, var(--accent)); place-content: center; - position: relative + position: relative; + box-sizing: border-box; + padding-right: 2px } .brand-mark span { @@ -515,13 +518,18 @@ h2 { margin-top: 5px } +.transport-grid>div, nav { - display: flex; flex-direction: column; } +.local-tag, +.nav, +nav { + display: flex +} + .nav { - display: flex; align-items: center; gap: 13px; text-align: left; @@ -569,7 +577,6 @@ nav { .local-tag { font: 9px var(--mono); color: var(--green); - display: flex; gap: 8px; align-items: center } @@ -618,13 +625,16 @@ main { } .version { - font: 10px var(--mono); + font: 10px var(--mono) +} + +.quiet, +.version { color: var(--muted) } .quiet { background: transparent; - color: var(--muted); border: 1px solid var(--line); border-radius: 3px } @@ -674,7 +684,7 @@ main { } h1 { - font-size: clamp(32px, 3.6vw, 52px); + font-size: calc(clamp(32px, 3.6vw, 52px)); line-height: 1.1; font-weight: 520; letter-spacing: -2px @@ -723,6 +733,7 @@ h1 { border-top: 2px solid var(--accent) } +.stat small, .stat>span { color: var(--muted); } @@ -734,10 +745,6 @@ h1 { letter-spacing: -1px; } -.stat small { - color: var(--muted); -} - .stat:first-child small { color: var(--green) } @@ -775,7 +782,8 @@ h1 { color: var(--green) } -#traffic-chart { +#traffic-chart, +table { width: 100%; } @@ -807,7 +815,6 @@ h1 { } table { - width: 100%; border-collapse: collapse; text-align: left; font-size: 11px; @@ -841,8 +848,8 @@ td:first-child { color: var(--red) } -.meta, -.fine { +.fine, +.meta { font-size: 11px; line-height: 1.7; color: var(--muted) @@ -920,7 +927,7 @@ td:first-child { margin: 16px 0 7px } -input:not([type="checkbox"]):not([type="radio"]), +input:not([type=checkbox]):not([type=radio]), select, textarea { display: block; @@ -935,7 +942,7 @@ textarea { min-width: 0 } -input:not([type="checkbox"]):not([type="radio"]):focus, +input:not([type=checkbox]):not([type=radio]):focus, select:focus, textarea:focus { outline: 1px solid var(--green); @@ -946,8 +953,8 @@ input::placeholder { color: var(--placeholder) } -input[type="checkbox"], -input[type="radio"] { +input[type=checkbox], +input[type=radio] { display: inline-block; width: var(--ui-check-size); height: var(--ui-check-size); @@ -963,16 +970,16 @@ input.field-invalid, select.field-invalid, textarea.field-invalid { border-color: var(--red) !important; - outline: 1px solid rgb(255 170 162 / .72); + outline: 1px solid rgba(255, 170, 162, .72); } -.listener-choices.field-invalid, -.host-chip-editor.field-invalid { +.host-chip-editor.field-invalid, +.listener-choices.field-invalid { border-color: var(--red) !important; - box-shadow: 0 0 0 1px rgb(255 170 162 / .38); + box-shadow: 0 0 0 1px rgba(255, 170, 162, .38); } -.settings-tabs button.has-unsaved::after { +.settings-tabs button.has-unsaved:after { content: ""; display: inline-block; width: 6px; @@ -1046,7 +1053,7 @@ button.is-saving { } .login-section h1 { - font-size: clamp(42px, 5vw, 68px); + font-size: calc(clamp(42px, 5vw, 68px)); letter-spacing: -3px; max-width: 640px } @@ -1105,7 +1112,7 @@ button.is-saving { position: fixed; bottom: 26px; right: 26px; - max-width: min(580px, calc(100vw - 40px)); + max-width: calc(min(580px, 100vw - 40px)); background: var(--toast-bg); color: var(--toast-text); border: 1px solid var(--toast-line); @@ -1219,14 +1226,11 @@ button.is-saving { grid-template-columns: repeat(2, minmax(0, 1fr)) } + .config-columns, .overview-grid { grid-template-columns: 1fr 1fr } - .config-columns { - grid-template-columns: 1fr 1fr - } - .login-section { padding-top: 42px } @@ -1242,7 +1246,8 @@ button.is-saving { } - .nav span { + .nav span, + .version { display: none } @@ -1275,10 +1280,6 @@ button.is-saving { gap: 6px } - .version { - display: none - } - .quiet { font-size: 11px; padding: 7px 9px @@ -1319,8 +1320,8 @@ button.is-saving { font-size: 30px } - .overview-grid, - .config-columns { + .config-columns, + .overview-grid { grid-template-columns: 1fr; gap: 16px } @@ -1347,12 +1348,13 @@ button.is-saving { font-size: 20px } - .chart-footer { + .chart-footer, + .login-footer, + .page-footer { font-size: 8px } .page-footer { - font-size: 8px; flex-wrap: wrap } @@ -1364,10 +1366,6 @@ button.is-saving { padding: 20px } - .login-footer { - font-size: 8px - } - .login-footer span { margin: 0 5px } @@ -1382,12 +1380,6 @@ button.is-saving { } } -@media(prefers-reduced-motion:reduce) { - * { - scroll-behavior: auto !important - } -} - .brand-mark { display: flex; align-items: center; @@ -1454,9 +1446,6 @@ button.is-saving { } @media(max-width:600px) { - .sidebar { - flex-wrap: wrap - } .brand { font-size: 16px @@ -1476,6 +1465,7 @@ button.is-saving { } .sidebar { + flex-wrap: wrap; padding: 12px 16px } @@ -1515,7 +1505,6 @@ button.is-saving { .transport-grid>div { display: flex; - flex-direction: column; border: 1px solid var(--line, #d8dfdb); min-width: 0 } @@ -1524,13 +1513,16 @@ button.is-saving { opacity: .7 } +.command, +.fine, +.meta, +.mini-metrics dd, .transport-grid b { overflow-wrap: anywhere } .command { - white-space: normal; - overflow-wrap: anywhere + white-space: normal } #panel-tls .section-heading { @@ -1753,8 +1745,7 @@ progress::-moz-progress-bar { .mini-metrics dd { font: 10px var(--mono); margin: 0; - text-align: end; - overflow-wrap: anywhere + text-align: end } .mini-live-chart-stack { @@ -1815,14 +1806,11 @@ progress::-moz-progress-bar { vector-effect: non-scaling-stroke } +.cache-hit-line, .network-tx-line { stroke: var(--chart-secondary) } -.cache-hit-line { - stroke: var(--chart-secondary) -} - .cache-miss-line { stroke: var(--chart-accent) } @@ -1844,7 +1832,7 @@ progress::-moz-progress-bar { color: var(--muted) } -.mini-chart-legend span::before { +.mini-chart-legend span:before { content: ""; display: inline-block; width: 12px; @@ -1896,7 +1884,7 @@ progress::-moz-progress-bar { z-index: 80; inset-inline-end: 0; top: calc(100% + 4px); - width: min(190px, calc(100vw - 24px)); + width: calc(min(190px, 100vw - 24px)); overflow: visible; padding: 4px; display: grid; @@ -1920,7 +1908,8 @@ progress::-moz-progress-bar { white-space: nowrap } -.log-column-option:hover { +.log-column-option:hover, +.logs-card th { background: var(--panel) } @@ -1928,7 +1917,7 @@ progress::-moz-progress-bar { .log-column-options { top: auto; bottom: calc(100% + 4px); - width: min(360px, calc(100vw - 24px)); + width: calc(min(360px, 100vw - 24px)); grid-template-columns: repeat(2, minmax(0, 1fr)) } } @@ -1939,8 +1928,7 @@ progress::-moz-progress-bar { .logs-card th { position: sticky; - top: 0; - background: var(--panel) + top: 0 } th { @@ -1965,7 +1953,7 @@ td { margin: 10px 0 5px } -.edit-form input:not([type="checkbox"]):not([type="radio"]), +.edit-form input:not([type=checkbox]):not([type=radio]), .edit-form select, .edit-form textarea { padding: 8px 10px @@ -2078,34 +2066,29 @@ td { line-height: 1.5 } -.fine, -.meta { - overflow-wrap: anywhere -} - -.mini-metrics dd, -.chart-footer>span { +.chart-footer>span, +.mini-metrics dd { min-width: 0 } -[dir="rtl"] .nav { +[dir=rtl] .nav { text-align: right; border-left: 0; border-right: 2px solid transparent } -[dir="rtl"] .nav.active { +[dir=rtl] .nav.active { border-right-color: var(--green) } -[dir="rtl"] table { +[dir=rtl] table { text-align: right } -[dir="rtl"] .entity-details, -[dir="rtl"] input[type="url"], -[dir="rtl"] #config-editor { +[dir=rtl] #config-editor, +[dir=rtl] .entity-details, +[dir=rtl] input[type=url] { direction: ltr; text-align: left } @@ -2158,8 +2141,8 @@ td { padding-top: 18px } - .settings-grid, - .config-columns { + .config-columns, + .settings-grid { gap: 12px } @@ -2188,7 +2171,9 @@ td { } - .nav span { + .breadcrumb b, + .nav span, + .topbar .version { display: none } @@ -2202,10 +2187,6 @@ td { margin: 0 5px } - .topbar .version { - display: none - } - .language-select { max-width: 110px; font-size: 10px @@ -2259,7 +2240,7 @@ td { width: 100% } - .log-tools input:not([type="checkbox"]):not([type="radio"]) { + .log-tools input:not([type=checkbox]):not([type=radio]) { flex: 1; min-width: 95px; width: 95px @@ -2304,8 +2285,8 @@ td { gap: 8px } - .primary, - .outline { + .outline, + .primary { white-space: normal } @@ -2332,10 +2313,6 @@ td { .topbar { gap: 7px } - - .breadcrumb b { - display: none - } } .vhost-layout { @@ -2344,15 +2321,16 @@ td { align-items: start } +.vh-field, .vh-form, +.vh-group, .vhost-layout>.card { min-width: 0 } .vh-group { border: 1px solid var(--line); - border-radius: 6px; - min-width: 0 + border-radius: 6px } .vh-group legend, @@ -2376,7 +2354,6 @@ td { display: flex; flex-direction: column; gap: 4px; - min-width: 0; margin: 0; font-size: 11px; color: var(--muted) @@ -2515,7 +2492,7 @@ td { } #pool-search { - width: min(250px, 30vw); + width: calc(min(250px, 30vw)); min-width: 170px } @@ -2579,11 +2556,6 @@ td { min-width: 640px } -.pool-table th, -.pool-table td { - white-space: nowrap -} - .pool-table td:nth-child(n+4) { font-variant-numeric: tabular-nums; text-align: right @@ -2600,6 +2572,12 @@ td { text-overflow: ellipsis } +#toast[popover], +.app-confirm-message, +.cache-table td, +.location-summary, +.vh-health-summary, +.vh-live-summary, .vhost-layout .entity-details { overflow-wrap: anywhere } @@ -2671,14 +2649,14 @@ body { font-size: var(--ui-font) } -body[data-density="dense"] { +body[data-density=dense] { --ui-gap: 4px; --ui-card: 7px; --ui-font: 11.5px; --ui-control: 25px } -body[data-density="comfortable"] { +body[data-density=comfortable] { --ui-gap: 13px; --ui-card: 17px; --ui-font: 14px; @@ -2750,16 +2728,12 @@ main { margin-bottom: 3px } -.overview-grid, +.config-columns, .diagnostics-grid, +.overview-grid, .settings-grid, -.config-columns { - gap: var(--ui-gap); - margin-bottom: var(--ui-gap) -} - -.stats-grid, -.stats { +.stats, +.stats-grid { gap: var(--ui-gap); margin-bottom: var(--ui-gap) } @@ -2770,7 +2744,8 @@ main { .stat p, -.stat small { +.stat small, +.transport-grid span { font-size: 10px } @@ -2785,10 +2760,6 @@ main { border-radius: 4px } -.transport-grid span { - font-size: 10px -} - .transport-grid b { font-size: 14px } @@ -2810,8 +2781,9 @@ main { margin-bottom: var(--ui-gap) } -.pool-table th, -.pool-table td { +.pool-table td, +.pool-table th { + white-space: nowrap; padding: 5px 7px } @@ -2832,10 +2804,10 @@ main { margin-top: 9px } -.edit-form input:not([type="checkbox"]):not([type="radio"]), +.edit-form input:not([type=checkbox]):not([type=radio]), .edit-form select, .edit-form textarea, -input:not([type="checkbox"]):not([type="radio"]), +input:not([type=checkbox]):not([type=radio]), select, textarea { padding: var(--ui-field-pad-y) var(--ui-field-pad-x); @@ -2844,16 +2816,16 @@ textarea { } -button, -.primary, .outline, -.quiet { +.primary, +.quiet, +button { padding: var(--button-padding); font-size: var(--button-font-size); min-height: var(--button-min-height) } -html[data-button-style="compact"] body { +html[data-button-style=compact] body { --button-padding: 3px 7px; --button-font-size: 10px; --button-min-height: 22px; @@ -2884,14 +2856,13 @@ html[data-button-style="compact"] body { .vh-health-summary { grid-column: 1/-1; color: var(--muted); - font: 10px var(--mono); - overflow-wrap: anywhere + font: 10px var(--mono) } -.vhost-dialog, +.audit-dialog, .logs-dialog, -.audit-dialog { +.vhost-dialog { color: var(--text); background: var(--bg); border: 1px solid var(--line); @@ -2907,9 +2878,9 @@ html[data-button-style="compact"] body { overflow: auto } -.vhost-dialog::backdrop, +.audit-dialog::backdrop, .logs-dialog::backdrop, -.audit-dialog::backdrop { +.vhost-dialog::backdrop { background: var(--shadow-strong); backdrop-filter: blur(2px) } @@ -2935,7 +2906,6 @@ html[data-button-style="compact"] body { .vh-live-summary { font: 10px var(--mono); line-height: 1.7; - overflow-wrap: anywhere; color: var(--muted); margin: 5px 0 } @@ -3107,15 +3077,15 @@ html[data-button-style="compact"] body { width: 100% } -.log-tools select, -.log-tools input:not([type="checkbox"]):not([type="radio"]) { +.log-tools input:not([type=checkbox]):not([type=radio]), +.log-tools select { width: auto; max-width: 180px; min-height: 30px; font-size: 11px } -.log-tools input:not([type="checkbox"]):not([type="radio"]) { +.log-tools input:not([type=checkbox]):not([type=radio]) { flex: 1; min-width: 110px } @@ -3126,7 +3096,7 @@ html[data-button-style="compact"] body { } .logs-dialog .table-scroll { - max-height: min(60dvh, 580px); + max-height: calc(min(60dvh, 580px)); overflow: auto; border-top: 1px solid var(--line) } @@ -3135,8 +3105,8 @@ html[data-button-style="compact"] body { font: 11px var(--mono) } -.logs-dialog th, -.logs-dialog td { +.logs-dialog td, +.logs-dialog th { padding: 6px 7px; max-width: 360px; overflow: hidden; @@ -3158,7 +3128,7 @@ html[data-button-style="compact"] body { position: fixed; inset: auto 12px 10px auto; margin: 0; - width: min(1180px, calc(100vw - 24px)); + width: calc(min(1180px, 100vw - 24px)); max-height: 46dvh; z-index: 20 } @@ -3257,10 +3227,10 @@ html[data-button-style="compact"] body { font-size: 23px } - .overview-grid, + .config-columns, .diagnostics-grid, - .settings-grid, - .config-columns { + .overview-grid, + .settings-grid { grid-template-columns: minmax(0, 1fr) } @@ -3287,6 +3257,8 @@ html[data-button-style="compact"] body { grid-template-columns: 85px 1fr } + .page-label, + .page-source, .vh-header-row>:nth-child(3) { grid-column: 1/-1 } @@ -3341,14 +3313,6 @@ html[data-button-style="compact"] body { .page-editor { grid-template-columns: 1fr 1fr } - - .page-label { - grid-column: 1/-1 - } - - .page-source { - grid-column: 1/-1 - } } @media(max-width:380px) { @@ -3380,11 +3344,11 @@ main { } .stat strong { - font-size: clamp(20px, 1.8vw, 26px); + font-size: calc(clamp(20px, 1.8vw, 26px)); margin: 4px 0 } -body[data-density="dense"] .stat strong { +body[data-density=dense] .stat strong { font-size: 22px; margin: 2px 0 } @@ -3394,13 +3358,13 @@ body[data-density="dense"] .stat strong { inset: auto 18px 18px auto; margin: 0; width: max-content; - max-width: min(520px, calc(100vw - 28px)); + max-width: calc(min(520px, 100vw - 28px)); padding: 12px 16px; color: var(--text); background: var(--panel); border: 1px solid var(--green); box-shadow: 0 10px 40px var(--shadow-strong); - overflow-wrap: anywhere + pointer-events: none } #toast[popover].error { @@ -3444,14 +3408,12 @@ body[data-density="dense"] .stat strong { cursor: pointer } +.h3-editor .fine, .h3-editor .vh-grid { margin-top: 7px } -.h3-editor .fine { - margin-top: 7px -} - +.cache-toolbar, .listener-choices { flex-wrap: wrap; } @@ -3498,7 +3460,6 @@ body[data-density="dense"] .stat strong { display: flex; align-items: center; gap: 10px; - flex-wrap: wrap; padding: 12px } @@ -3551,7 +3512,6 @@ body[data-density="dense"] .stat strong { .cache-table td { max-width: 260px; - overflow-wrap: anywhere; vertical-align: top } @@ -3593,6 +3553,7 @@ body[data-density="dense"] .stat strong { grid-template-columns: minmax(0, 1fr) } + .cache-toolbar select, .cache-toolbar>* { max-width: 100% } @@ -3601,10 +3562,6 @@ body[data-density="dense"] .stat strong { flex-wrap: wrap } - .cache-toolbar select { - max-width: 100% - } - .cache-stats strong { font-size: 19px } @@ -3617,7 +3574,7 @@ body[data-density="dense"] .stat strong { } #cache-object-scroll { - max-height: min(49vh, 520px); + max-height: calc(min(49vh, 520px)); overflow: auto; scrollbar-gutter: stable } @@ -3655,18 +3612,21 @@ body[data-density="dense"] .stat strong { border-radius: 8px } -.settings-tabs button[aria-selected="true"] { +.settings-tabs button[aria-selected=true] { background: var(--accent); color: var(--selected-ink); border-color: var(--accent); font-weight: 700 } +.cache-operation-dialog .cache-dialog-heading>div:first-child, +.certificate-dialog .vh-grid label, .settings-pages { min-width: 0 } -.settings-page { +.settings-page, +.settings-page>.edit-form { width: 100%; max-width: none } @@ -3675,11 +3635,6 @@ body[data-density="dense"] .stat strong { display: none !important } -.settings-page>.edit-form { - width: 100%; - max-width: none -} - .settings-page>.edit-form+.edit-form { margin-top: var(--ui-gap) } @@ -3690,7 +3645,7 @@ body[data-density="dense"] .stat strong { .certificate-dialog { - width: min(820px, calc(100vw - 28px)); + width: calc(min(820px, 100vw - 28px)); max-height: 90dvh; padding: 0; border: 1px solid var(--line); @@ -3742,10 +3697,6 @@ body[data-density="dense"] .stat strong { gap: 10px 14px } -.certificate-dialog .vh-grid label { - min-width: 0 -} - .metrics-example, .metrics-preview { font: 11px/1.55 ui-monospace, SFMono-Regular, monospace; @@ -3790,12 +3741,12 @@ body[data-density="dense"] .stat strong { } -dialog.vhost-dialog, -dialog.logs-dialog, dialog.audit-dialog, dialog.certificate-dialog, dialog.config-history-dialog, -dialog.config-history-json-dialog { +dialog.config-history-json-dialog, +dialog.logs-dialog, +dialog.vhost-dialog { border-radius: 0; background: var(--bg); color: var(--text); @@ -3809,18 +3760,16 @@ dialog::backdrop { backdrop-filter: blur(2px) !important } +#toast[popover], +.settings-page>.card, +.settings-tabs, +.settings-tabs button, dialog .card, dialog .vh-form, dialog .window-heading { border-radius: 0 } -.settings-tabs, -.settings-tabs button, -.settings-page>.card { - border-radius: 0 -} - .certificate-dialog>.window-heading { padding: 10px 12px } @@ -3848,10 +3797,6 @@ dialog .window-heading { padding: 9px 0 } -.cache-operation-dialog .cache-dialog-heading>div:first-child { - min-width: 0 -} - .cache-operation-dialog .cache-dialog-heading button { margin-inline-start: auto; flex: 0 0 auto @@ -3868,9 +3813,9 @@ dialog .window-heading { gap: 9px } -.location-toolbar, +.location-actions, .location-header-toolbar, -.location-actions { +.location-toolbar { display: flex; align-items: center; flex-wrap: wrap; @@ -3891,7 +3836,6 @@ dialog .window-heading { cursor: pointer; padding: 10px 12px; font: 600 11px/1.45 var(--mono); - overflow-wrap: anywhere; color: var(--text) } @@ -3936,8 +3880,8 @@ dialog .window-heading { } #location-help-dialog { - width: min(760px, calc(100vw - 28px)); - max-height: min(82vh, 760px) + width: calc(min(760px, 100vw - 28px)); + max-height: calc(min(82vh, 760px)) } #location-help-body { @@ -3991,8 +3935,8 @@ dialog .window-heading { @media(max-width:640px) { - .basic-user, - .acl-rule { + .acl-rule, + .basic-user { grid-template-columns: 1fr 1fr } @@ -4005,13 +3949,8 @@ dialog .window-heading { } } -#toast[popover] { - border-radius: 0; - pointer-events: none -} - .app-confirm-dialog { - width: min(520px, calc(100vw - 28px)); + width: calc(min(520px, 100vw - 28px)); max-width: none; padding: 0; border: 1px solid var(--line); @@ -4034,8 +3973,7 @@ dialog .window-heading { .app-confirm-message { margin-top: 14px; color: var(--text); - white-space: pre-line; - overflow-wrap: anywhere + white-space: pre-line } .app-confirm-actions { @@ -4050,7 +3988,7 @@ dialog .window-heading { .app-confirm-danger:hover { border-color: var(--red); - background: rgb(255 170 162 / .08) + background: rgba(255, 170, 162, .08) } #panel-tls>.config-columns { @@ -4077,7 +4015,8 @@ dialog .window-heading { gap: 10px } -.certificate-card-identity { +.certificate-card-identity, +.location-header-editor { min-width: 0 } @@ -4142,8 +4081,8 @@ dialog .window-heading { margin-top: 10px } -.auth-page.auth-loading .auth-top, -.auth-page.auth-loading .auth-main { +.auth-page.auth-loading .auth-main, +.auth-page.auth-loading .auth-top { visibility: hidden } @@ -4196,15 +4135,11 @@ dialog .window-heading { } .login-section #auth-message.error { - border-color: rgb(255 170 162 / .58); - background: rgb(255 170 162 / .07); + border-color: rgba(255, 170, 162, .58); + background: rgba(255, 170, 162, .07); color: var(--red) } -.location-header-editor { - min-width: 0 -} - .location-header-editor h3 { font-size: 12px; margin: 8px 0 @@ -4219,13 +4154,13 @@ dialog .window-heading { grid-template-columns: 1fr 1fr } - .location-toolbar, - .location-actions { + .location-actions, + .location-toolbar { align-items: stretch } - .location-toolbar>button, - .location-actions>button { + .location-actions>button, + .location-toolbar>button { flex: 1 1 150px } @@ -4298,12 +4233,12 @@ dialog .window-heading { .vh-header-rule-fields { display: grid; - grid-template-columns: minmax(100px, .55fr) minmax(140px, 1fr) minmax(180px, 1.35fr); align-items: end; gap: 8px } -.vh-header-row.is-remove .vh-header-rule-fields { +.vh-header-row.is-remove .vh-header-rule-fields, +.vh-header-rule-fields { grid-template-columns: minmax(100px, .55fr) minmax(140px, 1fr) minmax(180px, 1.35fr) } @@ -4388,9 +4323,9 @@ dialog .window-heading { margin-top: 5px } - .vh-header-rule-fields, + .location-preview-grid, .vh-header-row.is-remove .vh-header-rule-fields, - .location-preview-grid { + .vh-header-rule-fields { grid-template-columns: 1fr } } @@ -4409,20 +4344,14 @@ dialog .window-heading { text-align: center } -.settings-pages, .settings-page, -.settings-page>.edit-form { +.settings-page>.edit-form, +.settings-pages { width: 100%; max-width: none } -.logging-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 12px; - margin: 14px 0 -} - +.logging-grid, .traffic-chart-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); @@ -4450,7 +4379,7 @@ dialog .window-heading { box-shadow: inset 0 0 0 1px var(--line) } -.traffic-donut::before { +.traffic-donut:before { content: ""; position: absolute; inset: 23%; @@ -4566,12 +4495,6 @@ dialog .window-heading { font: 700 10px var(--mono) } -@media(max-width:1100px) { - .traffic-wide-chart { - grid-column: span 1 - } -} - @media(max-width:650px) { .traffic-bar-row { grid-template-columns: minmax(88px, 1.2fr) minmax(90px, 2fr) minmax(60px, auto); @@ -4594,6 +4517,10 @@ dialog .window-heading { } @media(max-width:1100px) { + .traffic-wide-chart { + grid-column: span 1 + } + .traffic-chart-grid { grid-template-columns: repeat(2, minmax(0, 1fr)) } @@ -4601,8 +4528,8 @@ dialog .window-heading { @media(max-width:720px) { - .traffic-chart-grid, - .logging-grid { + .logging-grid, + .traffic-chart-grid { grid-template-columns: 1fr } @@ -4621,10 +4548,10 @@ dialog .window-heading { gap: 10px } -#panel-vhosts .card-heading, -#panel-web-server .card-heading, +#panel-notfound .card-heading, #panel-redirects .card-heading, -#panel-notfound .card-heading { +#panel-vhosts .card-heading, +#panel-web-server .card-heading { flex-wrap: wrap } @@ -4636,11 +4563,11 @@ dialog .window-heading { margin-left: auto } -#vhost-search, -#web-service-search, +#notfound-search, #redirect-search, -#notfound-search { - width: min(260px, 32vw); +#vhost-search, +#web-service-search { + width: calc(min(260px, 32vw)); min-width: 180px } @@ -4662,8 +4589,8 @@ dialog .window-heading { min-width: 0 } -.vhost-title-line, -.vhost-policy-pills { +.vhost-policy-pills, +.vhost-title-line { display: flex; align-items: center; flex-wrap: wrap; @@ -4691,8 +4618,8 @@ dialog .window-heading { background: var(--bg) } -.vhost-detail span, -.vhost-detail b { +.vhost-detail b, +.vhost-detail span { display: block } @@ -4769,9 +4696,10 @@ dialog .window-heading { flex-direction: column } - #panel-vhosts .card-heading, + #panel-notfound .card-heading, #panel-redirects .card-heading, - #panel-notfound .card-heading { + #panel-vhosts .card-heading, + .backend-card-heading { align-items: stretch; flex-direction: column } @@ -4781,19 +4709,14 @@ dialog .window-heading { margin-left: 0 } - #vhost-search, + #notfound-search, #redirect-search, - #notfound-search { + #vhost-search { flex: 1 1 auto; width: auto; min-width: 0 } - .backend-card-heading { - align-items: stretch; - flex-direction: column - } - .backend-table-tools { width: 100%; justify-content: flex-start; @@ -4810,7 +4733,7 @@ dialog .window-heading { right: auto; left: 0; grid-template-columns: 1fr; - min-width: min(290px, 82vw) + min-width: calc(min(290px, 82vw)) } .vhost-card-head .entity-actions { @@ -4890,7 +4813,6 @@ dialog .window-heading { color: var(--red) } -/* Vhost names and canonical WWW routing share one visual decision surface. */ .vh-www-panel { padding: 12px; border: 1px solid var(--line); @@ -4932,8 +4854,8 @@ dialog .window-heading { font-size: 11px } -.vh-www-inactive, -.vh-www-help { +.vh-www-help, +.vh-www-inactive { margin: 9px 0 0; font-size: 11px } @@ -4976,7 +4898,7 @@ dialog .window-heading { gap: 8px } -@media (max-width: 650px) { +@media(max-width:650px) { .vh-www-pair-directions { grid-template-columns: minmax(0, 1fr) } @@ -4993,8 +4915,8 @@ dialog .window-heading { cursor: pointer } -.vh-www-choice:hover, -.vh-www-choice:focus-within { +.vh-www-choice:focus-within, +.vh-www-choice:hover { border-color: var(--green) } @@ -5046,13 +4968,16 @@ dialog .window-heading { .vh-www-alias { font: 11px var(--mono); padding: 4px 6px; - border: 1px solid var(--line); background: var(--panel) } +.vh-www-alias, +.vhost-chart { + border: 1px solid var(--line) +} + .vhost-chart { margin-top: 9px; - border: 1px solid var(--line); background: var(--chart-surface-alt) } @@ -5135,7 +5060,7 @@ dialog .window-heading { } } -.acl-profile-rule-rows:empty::before { +.acl-profile-rule-rows:empty:before { content: "—"; display: block; color: var(--muted); @@ -5175,12 +5100,6 @@ dialog .window-heading { color: var(--muted) } -@media(max-width:640px) { - .acl-profile-rule { - grid-template-columns: 1fr - } -} - #stream-list { display: grid; gap: 10px @@ -5191,7 +5110,7 @@ dialog .window-heading { } #stream-search { - width: min(260px, 32vw); + width: calc(min(260px, 32vw)); min-width: 180px } @@ -5307,10 +5226,8 @@ dialog .window-heading { } @media(max-width:760px) { - .redirect-card .vhost-detail-strip { - grid-template-columns: 1fr - } + .redirect-card .vhost-detail-strip, .redirect-rule .vh-grid { grid-template-columns: 1fr } @@ -5388,6 +5305,7 @@ dialog .window-heading { font-size: 13px } +.docker-agent-security .fine, .vh-backend-endpoint { overflow-wrap: anywhere } @@ -5419,8 +5337,8 @@ dialog .window-heading { border-top: 1px solid var(--line) } -.vh-backend-advanced>summary, -.auth-advanced>summary { +.auth-advanced>summary, +.vh-backend-advanced>summary { cursor: pointer; color: var(--muted); font-size: 11px; @@ -5451,6 +5369,7 @@ dialog .window-heading { max-width: 620px } +#redirect-listeners>.listener-choices, .vh-backend-health-editor select { width: 100% } @@ -5513,10 +5432,6 @@ dialog .window-heading { display: none } -#redirect-listeners>.listener-choices { - width: 100% -} - @media(max-width:760px) { .protocol-toggle-row { display: grid; @@ -5595,10 +5510,6 @@ dialog .window-heading { min-width: 0; } -.docker-agent-security .fine { - overflow-wrap: anywhere; -} - .docker-agent-error { display: grid; gap: 5px; @@ -5664,23 +5575,23 @@ dialog .window-heading { gap: 10px; } -.docker-route-grid>label:not(.check), -.docker-health-grid>label { +.docker-health-grid>label, +.docker-route-grid>label:not(.check) { display: grid; gap: 5px; min-width: 0; } -.docker-route-grid>label:not(.check)>span, -.docker-health-grid>label>span { +.docker-health-grid>label>span, +.docker-route-grid>label:not(.check)>span { color: var(--muted); font-size: 11px; } -.docker-route-grid input:not([type="checkbox"]), -.docker-route-grid select, -.docker-health-grid input:not([type="checkbox"]), -.docker-health-grid select { +.docker-health-grid input:not([type=checkbox]), +.docker-health-grid select, +.docker-route-grid input:not([type=checkbox]), +.docker-route-grid select { min-width: 0; width: 100%; } @@ -5725,7 +5636,7 @@ dialog .window-heading { max-width: 100%; } -.docker-passive-health-editor .docker-checkbox-field input[type="checkbox"] { +.docker-passive-health-editor .docker-checkbox-field input[type=checkbox] { width: auto; } @@ -5776,8 +5687,8 @@ dialog .window-heading { color: var(--red); } -.docker-toolbar input[type="search"] { - width: min(280px, 38vw); +.docker-toolbar input[type=search] { + width: calc(min(280px, 38vw)); } .cache-toolbar-status { @@ -5788,7 +5699,7 @@ dialog .window-heading { white-space: nowrap; } -@media (max-width: 1320px) { +@media(max-width:1320px) { .docker-runtime { grid-column: 2; @@ -5811,7 +5722,7 @@ dialog .window-heading { } } -@media (max-width: 760px) { +@media(max-width:760px) { .docker-service-head { align-items: stretch; } @@ -5822,12 +5733,9 @@ dialog .window-heading { text-align: left; } - .docker-route-grid, - .docker-health-grid { - grid-template-columns: 1fr; - } - - .docker-passive-health-editor .docker-health-grid { + .docker-health-grid, + .docker-passive-health-editor .docker-health-grid, + .docker-route-grid { grid-template-columns: 1fr; } @@ -5836,17 +5744,13 @@ dialog .window-heading { grid-column: auto; } + .docker-agent-security, .docker-toolbar { align-items: flex-start; flex-direction: column; } - .docker-agent-security { - align-items: flex-start; - flex-direction: column; - } - - .docker-toolbar input[type="search"] { + .docker-toolbar input[type=search] { width: 100%; } @@ -5865,12 +5769,12 @@ dialog .window-heading { } #panel-settings .settings-shortcuts { - grid-column: 1 / -1; + grid-column: 1/-1; width: 100% } #panel-settings .settings-filter-row { - grid-column: 1 / -1; + grid-column: 1/-1; display: flex; align-items: end; justify-content: space-between; @@ -5910,7 +5814,7 @@ dialog .window-heading { min-width: 0 } -@media (max-width: 900px) { +@media(max-width:900px) { #panel-settings:not([hidden]) { display: block; } @@ -5997,7 +5901,7 @@ dialog .window-heading { font-family: var(--mono); } -@media (max-width: 1000px) { +@media(max-width:1000px) { .docker-acl-grid, .docker-timeout-grid { @@ -6005,7 +5909,7 @@ dialog .window-heading { } } -@media (max-width: 760px) { +@media(max-width:760px) { .docker-acl-grid, .docker-timeout-grid { @@ -6084,7 +5988,7 @@ dialog .window-heading { } .audit-dialog { - width: min(1040px, calc(100vw - 24px)); + width: calc(min(1040px, 100vw - 24px)); max-height: calc(100dvh - 28px); overflow: hidden; } @@ -6096,7 +6000,7 @@ dialog .window-heading { } .audit-dialog .table-scroll { - max-height: min(68dvh, 680px); + max-height: calc(min(68dvh, 680px)); overflow: auto; border-top: 1px solid var(--line); } @@ -6105,8 +6009,8 @@ dialog .window-heading { font: 11px var(--mono); } -.audit-dialog th, -.audit-dialog td { +.audit-dialog td, +.audit-dialog th { padding: 7px 8px; max-width: 440px; overflow: hidden; @@ -6204,7 +6108,7 @@ dialog .window-heading { border-top: 1px solid var(--line); } -@media (max-width: 1320px) { +@media(max-width:1320px) { .docker-service-head { grid-template-columns: auto 1fr; } @@ -6216,7 +6120,7 @@ dialog .window-heading { } } -@media (max-width: 760px) { +@media(max-width:760px) { .security-page-heading { flex-direction: column; } @@ -6225,8 +6129,9 @@ dialog .window-heading { width: 100%; } - .security-editor>summary, - .docker-service-editor>summary { + .docker-service-editor>summary, + .docker-service-head-actions, + .security-editor>summary { align-items: flex-start; flex-direction: column; } @@ -6237,8 +6142,6 @@ dialog .window-heading { .docker-service-head-actions { grid-column: auto; - align-items: flex-start; - flex-direction: column; } .docker-service-editor .docker-config-hint { @@ -6321,7 +6224,7 @@ dialog .window-heading { font-weight: 600; } -body[data-density="compact"] { +body[data-density=compact] { --ui-gap: 2px; --ui-card: 5px; --ui-font: 10.75px; @@ -6329,150 +6232,147 @@ body[data-density="compact"] { line-height: 1.32; } -body[data-density="compact"] .shell { +body[data-density=compact] .shell { grid-template-columns: 164px minmax(0, 1fr); } -body[data-density="compact"] main { +body[data-density=compact] main { padding: 0 10px; } -body[data-density="compact"] .sidebar { +body[data-density=compact] .sidebar { padding: 14px 9px; } -body[data-density="compact"] .sidebar nav { +body[data-density=compact] .sidebar nav { margin-top: 14px; } -body[data-density="compact"] .nav { +body[data-density=compact] .nav { padding: 6px 7px; } -body[data-density="compact"] .topbar { +body[data-density=compact] .topbar { height: 42px; } -body[data-density="compact"] .page-intro { +body[data-density=compact] .page-intro { padding: 7px 0 6px; gap: 7px; } -body[data-density="compact"] .page-intro h1 { +body[data-density=compact] .page-intro h1 { font-size: 20px; } -body[data-density="compact"] .page-intro .lead { +body[data-density=compact] .page-intro .lead { font-size: 10px; margin-top: 2px; } -body[data-density="compact"] .card-heading { +body[data-density=compact] .card-heading { margin-bottom: 5px; } -body[data-density="compact"] .card-heading h2 { +body[data-density=compact] .card-heading h2 { font-size: 14px; } -body[data-density="compact"] .stat { +body[data-density=compact] .stat { padding: 6px 7px; } -body[data-density="compact"] .stat strong { +body[data-density=compact] .stat strong { font-size: 18px; margin: 1px 0; } -body[data-density="compact"] .stat p, -body[data-density="compact"] .stat small { +body[data-density=compact] .stat p, +body[data-density=compact] .stat small, +body[data-density=compact] .transport-grid span { font-size: 9px; } -body[data-density="compact"] .transport-grid>div { +body[data-density=compact] .transport-grid>div { padding: 4px 7px !important; } -body[data-density="compact"] .transport-grid span { - font-size: 9px; -} - -body[data-density="compact"] .transport-grid b { +body[data-density=compact] .transport-grid b { font-size: 12px; } -body[data-density="compact"] .activity-card svg { +body[data-density=compact] .activity-card svg { height: 84px; } -body[data-density="compact"] .system-card svg { +body[data-density=compact] .system-card svg { height: 66px; } -body[data-density="compact"] .mini-metrics div { +body[data-density=compact] .mini-metrics div { margin-top: 2px; } -body[data-density="compact"] .pool-table th, -body[data-density="compact"] .pool-table td { +body[data-density=compact] .pool-table td, +body[data-density=compact] .pool-table th { padding: 3px 5px; font-size: 10px; } -body[data-density="compact"] .pool-card .table-scroll { +body[data-density=compact] .pool-card .table-scroll { max-height: 170px; } -body[data-density="compact"] input:not([type="checkbox"]):not([type="radio"]), -body[data-density="compact"] select, -body[data-density="compact"] textarea, -body[data-density="compact"] button, -body[data-density="compact"] .primary, -body[data-density="compact"] .outline, -body[data-density="compact"] .quiet { +body[data-density=compact] .outline, +body[data-density=compact] .primary, +body[data-density=compact] .quiet, +body[data-density=compact] button, +body[data-density=compact] input:not([type=checkbox]):not([type=radio]), +body[data-density=compact] select, +body[data-density=compact] textarea { padding: 3px 7px; font-size: 10px; } -body[data-density="compact"] .docker-service-row { +body[data-density=compact] .docker-service-row { padding: 6px 8px; gap: 4px; } -body[data-density="compact"] .docker-service-head { +body[data-density=compact] .docker-service-head { gap: 7px; } -body[data-density="compact"] .docker-agent-overview { +body[data-density=compact] .docker-agent-overview { gap: 4px 12px; margin: 4px 0 6px; font-size: 10px; } -body[data-density="compact"] .docker-agent-details { +body[data-density=compact] .docker-agent-details { margin-bottom: 6px; padding-top: 5px; } -body[data-density="compact"] .docker-service-editor { +body[data-density=compact] .docker-service-editor { padding-top: 5px; } -body[data-density="compact"] .docker-service-editor[open]>summary { +body[data-density=compact] .docker-service-editor[open]>summary { margin-bottom: 7px; } -body[data-density="compact"] .security-stack { +body[data-density=compact] .security-stack { gap: 7px; } -body[data-density="compact"] .security-editor { +body[data-density=compact] .security-editor { margin-top: 8px; padding-top: 6px; } -body[data-density="compact"] .page-footer { +body[data-density=compact] .page-footer { padding: 7px 0; margin-top: 4px; } @@ -6499,16 +6399,16 @@ body[data-density="compact"] .page-footer { white-space: nowrap; } -@media (max-width: 850px) { - body[data-density="compact"] .sidebar nav { +@media(max-width:850px) { + body[data-density=compact] .sidebar nav { margin-top: 0; } - body[data-density="compact"] .sidebar { + body[data-density=compact] .sidebar { padding: 8px 9px; } - body[data-density="compact"] .topbar { + body[data-density=compact] .topbar { height: 42px; } } @@ -6546,13 +6446,13 @@ body[data-density="compact"] .page-footer { margin: 0 !important } -.docker-agent-grid>label>span:first-child, -.docker-agent-field-label { +.docker-agent-field-label, +.docker-agent-grid>label>span:first-child { min-height: 14px; line-height: 14px } -.docker-agent-grid input:not([type="checkbox"]), +.docker-agent-grid input:not([type=checkbox]), .docker-agent-grid select { width: 100%; min-width: 0; @@ -6601,7 +6501,7 @@ body[data-density="compact"] .page-footer { border-top: 1px solid var(--line) } -@media (max-width: 760px) { +@media(max-width:760px) { .docker-agent-heading-actions { @@ -6674,10 +6574,10 @@ body[data-density="compact"] .page-footer { font-size: 11px } -.docker-route-grid .docker-field input:not([type="checkbox"]), -.docker-route-grid .docker-field select, -.docker-acl-grid .docker-field input:not([type="checkbox"]), -.docker-acl-grid .docker-field select { +.docker-acl-grid .docker-field input:not([type=checkbox]), +.docker-acl-grid .docker-field select, +.docker-route-grid .docker-field input:not([type=checkbox]), +.docker-route-grid .docker-field select { width: 100%; min-width: 0; min-height: 38px; @@ -6699,7 +6599,7 @@ body[data-density="compact"] .page-footer { line-height: 1.25 } -.check input[type="checkbox"] { +.check input[type=checkbox] { flex: 0 0 auto; align-self: center; transform: translateY(-1px) @@ -6769,7 +6669,7 @@ body[data-density="compact"] .page-footer { box-sizing: border-box } -@media (max-width: 760px) { +@media(max-width:760px) { .network-interface-row-head { align-items: flex-start; flex-direction: column; @@ -6801,7 +6701,7 @@ body[data-density="compact"] .page-footer { box-sizing: border-box } -.docker-route-grid .docker-alias-editor input:not([type="checkbox"]) { +.docker-route-grid .docker-alias-editor input:not([type=checkbox]) { flex: 1 1 180px; width: auto; min-width: 150px; @@ -6816,29 +6716,27 @@ body[data-density="compact"] .page-footer { line-height: 1.35 } -@media (max-width: 1320px) { +@media(max-width:1320px) { .docker-route-grid { grid-template-columns: 1fr 1fr } .docker-alias-field, .docker-switch-row { - grid-column: 1 / -1 + grid-column: 1/-1 } } -@media (max-width: 760px) { - .docker-route-grid { +@media(max-width:760px) { + + .docker-route-grid, + .transform-diagnostics { grid-template-columns: 1fr } .docker-alias-field { grid-column: auto } - - .transform-diagnostics { - grid-template-columns: 1fr; - } } #panel-tls .certificate-workspace-heading { @@ -6846,14 +6744,14 @@ body[data-density="compact"] .page-footer { margin-bottom: 12px } -#panel-tls .certificate-workspace-heading>div, -#panel-tls .certificate-subheading>div { +#panel-tls .certificate-subheading>div, +#panel-tls .certificate-workspace-heading>div { min-width: 0 } -#panel-tls .certificate-workspace-heading p, +#panel-tls .certificate-section-intro p, #panel-tls .certificate-subheading p, -#panel-tls .certificate-section-intro p { +#panel-tls .certificate-workspace-heading p { margin: 5px 0 0 } @@ -6902,17 +6800,17 @@ body[data-density="compact"] .page-footer { border-left: 3px solid var(--line) } -#panel-tls .certificate-operation-card[data-state="running"] { +#panel-tls .certificate-operation-card[data-state=running] { border-left-color: var(--accent) } -#panel-tls .certificate-operation-card[data-state="ready"], -#panel-tls .certificate-operation-card[data-state="complete"] { +#panel-tls .certificate-operation-card[data-state=complete], +#panel-tls .certificate-operation-card[data-state=ready] { border-left-color: var(--green) } -#panel-tls .certificate-operation-card[data-state="failed"], -#panel-tls .certificate-operation-card[data-state="unavailable"] { +#panel-tls .certificate-operation-card[data-state=failed], +#panel-tls .certificate-operation-card[data-state=unavailable] { border-left-color: var(--red) } @@ -6933,15 +6831,15 @@ body[data-density="compact"] .page-footer { overflow-wrap: anywhere } -#panel-tls .certificate-operation-card[data-state="failed"] .certificate-operation-heading strong, -#panel-tls .certificate-operation-card[data-state="failed"] .pill, -#panel-tls .certificate-operation-card[data-state="unavailable"] .certificate-operation-heading strong, -#panel-tls .certificate-operation-card[data-state="unavailable"] .pill { +#panel-tls .certificate-operation-card[data-state=failed] .certificate-operation-heading strong, +#panel-tls .certificate-operation-card[data-state=failed] .pill, +#panel-tls .certificate-operation-card[data-state=unavailable] .certificate-operation-heading strong, +#panel-tls .certificate-operation-card[data-state=unavailable] .pill { color: var(--red) } -#panel-tls .certificate-operation-card[data-state="ready"] .pill, -#panel-tls .certificate-operation-card[data-state="complete"] .pill { +#panel-tls .certificate-operation-card[data-state=complete] .pill, +#panel-tls .certificate-operation-card[data-state=ready] .pill { color: var(--green) } @@ -6973,7 +6871,7 @@ body[data-density="compact"] .page-footer { margin-bottom: 0 } -#panel-tls .renewal-entry[data-state="failed"] { +#panel-tls .renewal-entry[data-state=failed] { border-left: 3px solid var(--red) } @@ -7066,23 +6964,20 @@ body[data-density="compact"] .page-footer { @media(max-width:700px) { - #panel-tls .certificate-workspace-heading, - #panel-tls .certificate-subheading, #panel-tls .certificate-operation-heading, + #panel-tls .certificate-subheading, + #panel-tls .certificate-workspace-heading, #panel-tls .renewal-entry-head { align-items: stretch; flex-direction: column } - #panel-tls .certificate-workspace-heading>button, - #panel-tls .certificate-subheading>button { + #panel-tls .certificate-subheading>button, + #panel-tls .certificate-workspace-heading>button { width: 100% } - #panel-tls .certificate-tabs { - grid-template-columns: 1fr - } - + #panel-tls .certificate-tabs, #panel-tls .renewal-meta { grid-template-columns: 1fr } @@ -7112,8 +7007,8 @@ body[data-density="compact"] .page-footer { } .operation-message.error { - border-color: rgb(255 170 162 / .58); - background: rgb(255 170 162 / .07); + border-color: rgba(255, 170, 162, .58); + background: rgba(255, 170, 162, .07); color: var(--red); } @@ -7123,7 +7018,7 @@ body[data-density="compact"] .page-footer { } .cache-operation-dialog { - width: min(920px, calc(100vw - 32px)); + width: calc(min(920px, 100vw - 32px)); } .cache-operation-dialog .card { @@ -7174,7 +7069,6 @@ body[data-density="compact"] .page-footer { background: currentColor; } -/* The legend keys must use the same theme/preset palette as the SVG strokes. */ .stat-chart .stat-chart-legend .legend-h1 i { background: var(--traffic-h1); } @@ -7194,6 +7088,11 @@ body[data-density="compact"] .page-footer { .proto-h1-line { stroke: var(--traffic-h1); +} + +.proto-h1-line, +.proto-h2-line, +.proto-h3-line { fill: none; stroke-width: var(--chart-line-width); stroke-linecap: var(--chart-linecap); @@ -7201,16 +7100,10 @@ body[data-density="compact"] .page-footer { .proto-h2-line { stroke: var(--traffic-h2); - fill: none; - stroke-width: var(--chart-line-width); - stroke-linecap: var(--chart-linecap); } .proto-h3-line { stroke: var(--traffic-h3); - fill: none; - stroke-width: var(--chart-line-width); - stroke-linecap: var(--chart-linecap); } .stat-chart .chart-grid { @@ -7238,14 +7131,14 @@ body[data-density="compact"] .page-footer { display: none } -.function-live-chart-summary::before { +.function-live-chart-summary:before { content: "▸"; flex: 0 0 auto; color: var(--muted); font: 12px var(--mono) } -.function-live-chart[open]>.function-live-chart-summary::before { +.function-live-chart[open]>.function-live-chart-summary:before { content: "▾" } @@ -7282,7 +7175,7 @@ body[data-density="compact"] .page-footer { .function-live-chart svg { display: block; width: 100%; - height: clamp(180px, 22vw, 270px); + height: calc(clamp(180px, 22vw, 270px)); overflow: visible } @@ -7292,26 +7185,26 @@ body[data-density="compact"] .page-footer { vector-effect: non-scaling-stroke } -.transform-applied-line, -.transform-applied-key { +.transform-applied-key, +.transform-applied-line { stroke: var(--chart-quaternary); color: var(--chart-quaternary) } -.transform-cache-line, -.transform-cache-key { +.transform-cache-key, +.transform-cache-line { stroke: var(--chart-secondary); color: var(--chart-secondary) } -.transform-skipped-line, -.transform-skipped-key { +.transform-skipped-key, +.transform-skipped-line { stroke: var(--chart-septenary); color: var(--chart-septenary) } -.transform-error-line, -.transform-error-key { +.transform-error-key, +.transform-error-line { stroke: var(--chart-quinary); color: var(--chart-quinary) } @@ -7366,7 +7259,7 @@ body[data-density="compact"] .page-footer { font-weight: 650 } -@media (max-width: 760px) { +@media(max-width:760px) { .function-live-chart-summary { align-items: flex-start } @@ -7430,9 +7323,9 @@ body[data-density="compact"] .page-footer { white-space: nowrap; } -.payload-split b, +.connection-card .chart-footer b, .connection-legend b, -.connection-card .chart-footer b { +.payload-split b { color: var(--text); font-weight: 650; } @@ -7456,8 +7349,8 @@ body[data-density="compact"] .page-footer { overflow: visible; } -.connection-open-line, -.connection-exchange-line { +.connection-exchange-line, +.connection-open-line { fill: none; vector-effect: non-scaling-stroke; stroke-width: 2; @@ -7546,9 +7439,9 @@ body[data-density="compact"] .page-footer { margin-top: 10px; } -#certificate-api-token-field[hidden], +#certificate-api-cancel-edit[hidden], #certificate-api-edit-note[hidden], -#certificate-api-cancel-edit[hidden] { +#certificate-api-token-field[hidden] { display: none; } @@ -7599,7 +7492,7 @@ body[data-density="compact"] .page-footer { overflow-wrap: anywhere; } -@media (max-width: 760px) { +@media(max-width:760px) { .certificate-api-credential-row, .network-interface-row-head { @@ -7615,7 +7508,7 @@ body[data-density="compact"] .page-footer { margin: 12px 0; padding: 12px; border: 1px solid var(--line); - border-radius: 0px; + border-radius: 0; background: var(--panel2); } @@ -7663,8 +7556,8 @@ body[data-density="compact"] .page-footer { display: none; } -.metric-info>summary:hover, .metric-info>summary:focus-visible, +.metric-info>summary:hover, .metric-info[open]>summary { color: var(--text); border-color: var(--border-hover); @@ -7676,7 +7569,7 @@ body[data-density="compact"] .page-footer { z-index: 40; top: calc(100% + 7px); right: 0; - width: min(360px, calc(100vw - 44px)); + width: calc(min(360px, 100vw - 44px)); padding: 10px 11px; border: 1px solid var(--line); border-radius: 6px; @@ -7698,8 +7591,8 @@ body[data-density="compact"] .page-footer { stroke: var(--chart-secondary); } -.cache-queue-line, -.cache-cold-line { +.cache-cold-line, +.cache-queue-line { stroke: var(--chart-tertiary); } @@ -7743,7 +7636,7 @@ body[data-density="compact"] .page-footer { } .config-history-dialog { - width: min(1180px, calc(100vw - 24px)); + width: calc(min(1180px, 100vw - 24px)); max-width: calc(100vw - 24px); max-height: calc(100dvh - 28px); overflow: hidden; @@ -7752,7 +7645,7 @@ body[data-density="compact"] .page-footer { .config-history-dialog[open] { display: grid; grid-template-rows: auto minmax(0, 1fr); - height: min(820px, calc(100dvh - 28px)); + height: calc(min(820px, 100dvh - 28px)); } .config-history-layout { @@ -7796,8 +7689,8 @@ body[data-density="compact"] .page-footer { font-size: 12px; } -.config-history-weekdays, -.config-history-days { +.config-history-days, +.config-history-weekdays { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; @@ -7921,14 +7814,14 @@ button.config-history-day.is-selected { font: 10px var(--mono); } -@media (max-width: 860px) { +@media(max-width:860px) { .config-history-layout { display: block; overflow-y: auto; } .config-history-browser { - max-height: min(300px, 38dvh); + max-height: calc(min(300px, 38dvh)); overflow-y: auto; margin-bottom: 10px; } @@ -7994,7 +7887,7 @@ button.config-history-day.is-selected { } .mobile-menu-toggle:hover, - .mobile-menu-toggle[aria-expanded="true"] { + .mobile-menu-toggle[aria-expanded=true] { color: var(--text); border-color: var(--border-hover); background: var(--nav-active-bg); @@ -8010,7 +7903,7 @@ button.config-history-day.is-selected { } .sidebar .main-navigation { - grid-column: 1 / -1; + grid-column: 1/-1; display: none; width: 100%; margin: 0; @@ -8031,7 +7924,7 @@ button.config-history-day.is-selected { gap: 8px; padding: 9px 10px; border: 1px solid transparent; - border-left: 2px solid transparent; + border-left-width: 2px; text-align: left; } @@ -8049,7 +7942,7 @@ button.config-history-day.is-selected { } .about-dialog { - width: min(560px, calc(100vw - 24px)); + width: calc(min(560px, 100vw - 24px)); overflow: hidden; } @@ -8166,11 +8059,6 @@ button.config-history-day.is-selected { min-width: 0; } - -.file-log-column-picker { - align-self: end; -} - .file-logs-table { max-height: 62vh; } @@ -8197,12 +8085,9 @@ button.config-history-day.is-selected { text-align: left; } -.file-logs-table th .file-log-sort-button:hover { - color: var(--accent); -} - -.file-logs-table th[aria-sort="ascending"] .file-log-sort-button, -.file-logs-table th[aria-sort="descending"] .file-log-sort-button { +.file-logs-table th .file-log-sort-button:hover, +.file-logs-table th[aria-sort=ascending] .file-log-sort-button, +.file-logs-table th[aria-sort=descending] .file-log-sort-button { color: var(--accent); } @@ -8259,13 +8144,13 @@ button.config-history-day.is-selected { gap: 16px; } -@media (max-width: 1100px) { +@media(max-width:1100px) { .file-logs-filter { grid-template-columns: repeat(3, minmax(140px, 1fr)); } } -@media (max-width: 700px) { +@media(max-width:700px) { .file-logs-filter .file-log-search { grid-column: auto; @@ -8281,20 +8166,20 @@ button.config-history-day.is-selected { position: relative; } -.top-log-menu>summary, -.file-log-search-help>summary { +.file-log-search-help>summary, +.top-log-menu>summary { list-style: none; cursor: pointer; user-select: none; } -.top-log-menu>summary::-webkit-details-marker, -.file-log-search-help>summary::-webkit-details-marker { +.file-log-search-help>summary::-webkit-details-marker, +.top-log-menu>summary::-webkit-details-marker { display: none; } -.top-log-menu[open]>summary, -.file-log-search-help[open]>summary { +.file-log-search-help[open]>summary, +.top-log-menu[open]>summary { color: var(--text); border-color: var(--border-hover); } @@ -8338,14 +8223,15 @@ button.config-history-day.is-selected { inset-inline-end: 0; display: grid; gap: 7px; - width: min(420px, calc(100vw - 36px)); + width: calc(min(420px, 100vw - 36px)); padding: 12px; border: 1px solid var(--line); background: var(--panel2); box-shadow: 0 12px 28px var(--shadow-strong); } -.file-log-search-help-popover p { +.file-log-search-help-popover p, +.file-logs-table-toolbar .fine { margin: 0; } @@ -8375,21 +8261,19 @@ button.config-history-day.is-selected { min-height: 32px; } -.file-logs-table-toolbar .fine { - margin: 0; -} - .file-log-column-picker { + align-self: end; margin-inline-start: auto; } .file-log-column-picker .log-column-options { - width: min(360px, calc(100vw - 24px)); + width: calc(min(360px, 100vw - 24px)); max-height: 420px; overflow: auto; grid-template-columns: repeat(2, minmax(0, 1fr)); } +.dashboard-widget-setting-label, .file-log-column-picker .log-column-option span { overflow: hidden; text-overflow: ellipsis; @@ -8429,7 +8313,7 @@ button.config-history-day.is-selected { line-height: 1; } -@media (max-width: 700px) { +@media(max-width:700px) { .file-logs-heading-actions, .file-logs-table-toolbar { @@ -8437,6 +8321,8 @@ button.config-history-day.is-selected { flex-wrap: wrap; } + .file-log-column-picker .log-column-options, + .file-log-field, .file-logs-filter { grid-template-columns: 1fr; } @@ -8446,13 +8332,8 @@ button.config-history-day.is-selected { } .file-log-field { - grid-template-columns: 1fr; gap: 3px; } - - .file-log-column-picker .log-column-options { - grid-template-columns: 1fr; - } } .appearance-settings-card { @@ -8510,7 +8391,7 @@ button.config-history-day.is-selected { font-variant-numeric: tabular-nums; } -.appearance-scale-control input[type="range"] { +.appearance-scale-control input[type=range] { width: 100%; } @@ -8591,7 +8472,7 @@ button.config-history-day.is-selected { border: 1px solid var(--line) } -.chart-style-swatches i:nth-child(1) { +.chart-style-swatches i:first-child { background: var(--sw1); } @@ -8607,56 +8488,56 @@ button.config-history-day.is-selected { background: var(--sw4); } -.chart-style-choice[data-chart-preset="default"] { +.chart-style-choice[data-chart-preset=default] { --sw1: var(--accent); --sw2: var(--green); --sw3: var(--blue); --sw4: var(--transform-chart); } -.chart-style-choice[data-chart-preset="soft"] { +.chart-style-choice[data-chart-preset=soft] { --sw1: #e8a998; --sw2: #95c293; --sw3: #8daef6; --sw4: #b49de6; } -.chart-style-choice[data-chart-preset="technical"] { +.chart-style-choice[data-chart-preset=technical] { --sw1: #79c9ff; --sw2: #9adf90; --sw3: #ffd56e; --sw4: #c39cff; } -.chart-style-choice[data-chart-preset="high_contrast"] { +.chart-style-choice[data-chart-preset=high_contrast] { --sw1: #ffd54f; --sw2: #8dff80; --sw3: #6fd9ff; --sw4: #ff78d7; } -.chart-style-choice[data-chart-preset="neon"] { +.chart-style-choice[data-chart-preset=neon] { --sw1: #2cf6ff; --sw2: #7eff85; --sw3: #ff5cd0; --sw4: #8b7cff; } -.chart-style-choice[data-chart-preset="monochrome"] { +.chart-style-choice[data-chart-preset=monochrome] { --sw1: #f2f2f2; --sw2: #b8b8b8; --sw3: #858585; --sw4: #595959; } -.chart-style-choice[data-chart-preset="warm"] { +.chart-style-choice[data-chart-preset=warm] { --sw1: #ff9c5c; --sw2: #f4c46b; --sw3: #ff7b72; --sw4: #f4a261; } -.chart-style-choice[data-chart-preset="cool"] { +.chart-style-choice[data-chart-preset=cool] { --sw1: #5fb9ff; --sw2: #45d3c3; --sw3: #7d8fff; @@ -8680,7 +8561,6 @@ button.config-history-day.is-selected { .dashboard-widget-setting { display: grid; grid-template-columns: minmax(0, 1fr) auto; - align-items: center; gap: 4px; min-width: 0; min-height: 34px; @@ -8725,8 +8605,6 @@ button.config-history-day.is-selected { } .dashboard-widget-setting-label { - overflow: hidden; - text-overflow: ellipsis; white-space: nowrap; font-size: 11px } @@ -8751,7 +8629,7 @@ button.config-history-day.is-selected { border-top: 1px solid var(--line) } -html[data-chart-style="default"] { +html[data-chart-style=default] { --chart-accent: var(--accent); --chart-secondary: var(--green); --chart-tertiary: var(--blue); @@ -8770,7 +8648,7 @@ html[data-chart-style="default"] { --chart-svg-filter: none; } -html[data-chart-style="soft"] { +html[data-chart-style=soft] { --chart-accent: #e8a998; --chart-secondary: #95c293; --chart-tertiary: #8daef6; @@ -8779,8 +8657,8 @@ html[data-chart-style="soft"] { --chart-senary: #d0b78e; --chart-septenary: #99a790; --chart-octonary: #728070; - --chart-surface: color-mix(in srgb, var(--panel2) 88%, white 12%); - --chart-surface-alt: color-mix(in srgb, var(--panel) 92%, white 8%); + --chart-surface: color-mix(in srgb, var(--panel2) 88%, #fff 12%); + --chart-surface-alt: color-mix(in srgb, var(--panel) 92%, #fff 8%); --chart-line-width: 2.8; --chart-linecap: round; --chart-linejoin: round; @@ -8789,7 +8667,7 @@ html[data-chart-style="soft"] { --chart-svg-filter: saturate(.88) brightness(1.04); } -html[data-chart-style="technical"] { +html[data-chart-style=technical] { --chart-accent: #79c9ff; --chart-secondary: #9adf90; --chart-tertiary: #ffd56e; @@ -8808,7 +8686,7 @@ html[data-chart-style="technical"] { --chart-svg-filter: saturate(.76) contrast(1.08); } -html[data-chart-style="high_contrast"] { +html[data-chart-style=high_contrast] { --chart-accent: #ffd54f; --chart-secondary: #8dff80; --chart-tertiary: #6fd9ff; @@ -8817,17 +8695,24 @@ html[data-chart-style="high_contrast"] { --chart-senary: #f2e48f; --chart-septenary: #d7f0d9; --chart-octonary: #cfcfcf; - --chart-surface: color-mix(in srgb, var(--panel2) 94%, black 6%); - --chart-surface-alt: color-mix(in srgb, var(--panel) 94%, black 6%); + --chart-surface: color-mix(in srgb, var(--panel2) 94%, #000 6%); + --chart-surface-alt: color-mix(in srgb, var(--panel) 94%, #000 6%); --chart-line-width: 3; - --chart-linecap: round; - --chart-linejoin: round; --chart-grid-opacity: .95; --chart-grid-dash: 1 0; --chart-svg-filter: saturate(1.6) contrast(1.22) brightness(1.08); } -html[data-chart-style="neon"] { +html[data-chart-style=cool], +html[data-chart-style=high_contrast], +html[data-chart-style=monochrome], +html[data-chart-style=neon], +html[data-chart-style=warm] { + --chart-linecap: round; + --chart-linejoin: round; +} + +html[data-chart-style=neon] { --chart-accent: #2cf6ff; --chart-secondary: #7eff85; --chart-tertiary: #ff5cd0; @@ -8839,33 +8724,29 @@ html[data-chart-style="neon"] { --chart-surface: #0b1117; --chart-surface-alt: #101720; --chart-line-width: 2.45; - --chart-linecap: round; - --chart-linejoin: round; --chart-grid-opacity: .62; --chart-grid-dash: 2 4; --chart-svg-filter: saturate(1.25) brightness(1.12); } -html[data-chart-style="monochrome"] { - --chart-accent: color-mix(in srgb, var(--text) 92%, white 8%); +html[data-chart-style=monochrome] { + --chart-accent: color-mix(in srgb, var(--text) 92%, #fff 8%); --chart-secondary: color-mix(in srgb, var(--muted) 80%, var(--text) 20%); --chart-tertiary: color-mix(in srgb, var(--separator) 72%, var(--text) 28%); --chart-quaternary: color-mix(in srgb, var(--line) 45%, var(--text) 55%); - --chart-quinary: color-mix(in srgb, var(--muted) 55%, white 45%); + --chart-quinary: color-mix(in srgb, var(--muted) 55%, #fff 45%); --chart-senary: color-mix(in srgb, var(--outline-line) 58%, var(--text) 42%); --chart-septenary: color-mix(in srgb, var(--field-line) 70%, var(--text) 30%); --chart-octonary: color-mix(in srgb, var(--scrollbar-thumb) 75%, var(--text) 25%); --chart-surface: color-mix(in srgb, var(--panel2) 96%, var(--text) 4%); --chart-surface-alt: color-mix(in srgb, var(--panel) 96%, var(--text) 4%); --chart-line-width: 2.1; - --chart-linecap: round; - --chart-linejoin: round; --chart-grid-opacity: .4; --chart-grid-dash: 2 3; --chart-svg-filter: saturate(0) contrast(1.05); } -html[data-chart-style="warm"] { +html[data-chart-style=warm] { --chart-accent: #ff9c5c; --chart-secondary: #f4c46b; --chart-tertiary: #ff7b72; @@ -8877,14 +8758,12 @@ html[data-chart-style="warm"] { --chart-surface: color-mix(in srgb, var(--panel2) 88%, #3d1f10 12%); --chart-surface-alt: color-mix(in srgb, var(--panel) 90%, #32180d 10%); --chart-line-width: 2.4; - --chart-linecap: round; - --chart-linejoin: round; --chart-grid-opacity: .36; --chart-grid-dash: 1 0; --chart-svg-filter: saturate(1.1) brightness(1.04); } -html[data-chart-style="cool"] { +html[data-chart-style=cool] { --chart-accent: #5fb9ff; --chart-secondary: #45d3c3; --chart-tertiary: #7d8fff; @@ -8896,8 +8775,6 @@ html[data-chart-style="cool"] { --chart-surface: color-mix(in srgb, var(--panel2) 88%, #0d2740 12%); --chart-surface-alt: color-mix(in srgb, var(--panel) 90%, #0d2033 10%); --chart-line-width: 2.2; - --chart-linecap: round; - --chart-linejoin: round; --chart-grid-opacity: .46; --chart-grid-dash: 2 4; --chart-svg-filter: saturate(1.08) brightness(1.03); @@ -8914,131 +8791,122 @@ html[data-chart-style] { filter: var(--chart-svg-filter); } -/* Overview inter-tile spacing follows the same UI density variable as the - traffic, diagnostics and other Dashboard blocks, also at 90% UI zoom. */ -html[data-dashboard-density="normal"] #panel-overview .stats-grid { +html[data-dashboard-density=compact] #panel-overview .stats-grid, +html[data-dashboard-density=normal] #panel-overview .stats-grid, +html[data-dashboard-density=ultra] #panel-overview .stats-grid { gap: var(--ui-gap); } -html[data-dashboard-density="normal"] #panel-overview .stat { +html[data-dashboard-density=normal] #panel-overview .stat { padding: 12px 13px; } -html[data-dashboard-density="normal"] #panel-overview .stat>span { +html[data-dashboard-density=normal] #panel-overview .stat>span { font-size: 11px; } -html[data-dashboard-density="normal"] #panel-overview .stat strong { +html[data-dashboard-density=normal] #panel-overview .stat strong { font-size: 25px; margin: 5px 0 3px; line-height: 1.3; } -html[data-dashboard-density="normal"] #panel-overview .stat small { +html[data-dashboard-density=normal] #panel-overview .stat small { font-size: 9px; } -html[data-dashboard-density="compact"] #panel-overview .stats-grid { - gap: var(--ui-gap); -} - -html[data-dashboard-density="compact"] #panel-overview .stat { +html[data-dashboard-density=compact] #panel-overview .stat { padding: 10px 11px; } -html[data-dashboard-density="compact"] #panel-overview .stat>span { +html[data-dashboard-density=compact] #panel-overview .stat>span { font-size: 11px; line-height: 1.35; } -html[data-dashboard-density="compact"] #panel-overview .stat strong { +html[data-dashboard-density=compact] #panel-overview .stat strong { font-size: 25px; margin: 4px 0 2px; line-height: 1.2; } -html[data-dashboard-density="compact"] #panel-overview .stat small { +html[data-dashboard-density=compact] #panel-overview .stat small { font-size: 10px; line-height: 1.35; } -/* Override the older fixed 9px mono caption in the Payload card as well. */ -html[data-dashboard-density="compact"] #panel-overview .payload-split { +html[data-dashboard-density=compact] #panel-overview .payload-split { font-size: 10px !important; } -html[data-dashboard-density="ultra"] #panel-overview .stats-grid { - gap: var(--ui-gap); -} - -html[data-dashboard-density="ultra"] #panel-overview .stat { +html[data-dashboard-density=ultra] #panel-overview .stat { padding: 7px 8px; } -html[data-dashboard-density="ultra"] #panel-overview .stat>span { +html[data-dashboard-density=ultra] #panel-overview .stat>span { font-size: 9px; line-height: 1.08; } -html[data-dashboard-density="ultra"] #panel-overview .stat strong { +html[data-dashboard-density=ultra] #panel-overview .stat strong { font-size: 18px; margin: 2px 0 1px; line-height: 1.08; } -html[data-dashboard-density="ultra"] #panel-overview .stat small { +html[data-dashboard-density=ultra] #panel-overview .stat small { font-size: 7.5px; line-height: 1.12; } -html[data-dashboard-density="ultra"] #panel-overview .stat-chart svg { +html[data-dashboard-density=ultra] #panel-overview .stat-chart svg { height: 38px; } -html[data-table-density="compact"] :is(table th, table td) { +html[data-table-density=compact] :is(table th, table td) { padding-top: 6px; padding-bottom: 6px; font-size: 11px; line-height: 1.28; } -html[data-table-density="compact"] .file-log-field { +html[data-table-density=compact] .file-log-field { padding: 2px 6px; line-height: 1.18; } -html[data-table-density="compact"] .file-log-inline-detail pre { +html[data-table-density=compact] .file-log-inline-detail pre { line-height: 1.18; } -html[data-table-density="ultra"] :is(table th, table td) { +html[data-table-density=ultra] :is(table th, table td) { padding-top: 3px; padding-bottom: 3px; font-size: 10px; line-height: 1.15; } -html[data-table-density="ultra"] .file-log-inline-detail { +html[data-table-density=ultra] .file-log-inline-detail { padding: 5px 7px; } -html[data-table-density="ultra"] .file-log-field-grid { +html[data-table-density=ultra] .file-log-field-grid { margin-top: 2px; } -html[data-table-density="ultra"] .file-log-field { +html[data-table-density=ultra] .file-log-field { gap: 6px; padding: 1px 5px; font-size: 10px; line-height: 1.08; } -html[data-table-density="ultra"] .file-log-raw-json { +html[data-table-density=ultra] .file-log-raw-json { margin-top: 3px; } -html[data-table-density="ultra"] .file-log-inline-detail pre { +html[data-table-density=ultra] .file-log-inline-detail pre { margin-top: 3px; line-height: 1.08; } @@ -9068,7 +8936,7 @@ html[data-table-density="ultra"] .file-log-inline-detail pre { line-height: 1.28; } -@media (max-width: 760px) { +@media(max-width:760px) { .appearance-settings-grid { grid-template-columns: 1fr; } @@ -9083,7 +8951,7 @@ html[data-table-density="ultra"] .file-log-inline-detail pre { right: 14px; bottom: 14px; z-index: 900; - width: min(330px, calc(100vw - 28px)); + width: calc(min(330px, 100vw - 28px)); padding: 0; border: 1px solid var(--border-hover); border-radius: 0; @@ -9147,7 +9015,7 @@ html[data-table-density="ultra"] .file-log-inline-detail pre { .draft-dock-list { display: grid; gap: 0; - max-height: min(42vh, 360px); + max-height: calc(min(42vh, 360px)); overflow: auto; } @@ -9239,24 +9107,24 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { stroke-linejoin: var(--chart-linejoin); } +#panel-settings .settings-filter, .backend-table-tools, -.vhost-list-tools, .docker-toolbar, -.log-tools, .file-logs-filter, -#panel-settings .settings-filter { +.log-tools, +.vhost-list-tools { --toolbar-control-height: min(var(--button-min-height), var(--ui-field-min-height)); } +#panel-settings .settings-filter input, .backend-table-tools input, .backend-table-tools select, -.vhost-list-tools input[type="search"], -.docker-toolbar input[type="search"], -.log-tools input:not([type="checkbox"]):not([type="radio"]), -.log-tools select, +.docker-toolbar input[type=search], .file-logs-filter input, .file-logs-filter select, -#panel-settings .settings-filter input { +.log-tools input:not([type=checkbox]):not([type=radio]), +.log-tools select, +.vhost-list-tools input[type=search] { box-sizing: border-box; height: var(--toolbar-control-height); min-height: var(--toolbar-control-height); @@ -9275,21 +9143,22 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { background: var(--bg) } +.acl-policy-preview-head, .acl-policy-step-head, -.acl-policy-step-head>div, -.acl-policy-preview-head { +.acl-policy-step-head>div { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px } -.acl-policy-step-head>div { +.acl-policy-step-head>div, +.php-switch-row { justify-content: flex-start } -.acl-policy-step-head strong, -.acl-policy-preview-head>span { +.acl-policy-preview-head>span, +.acl-policy-step-head strong { display: block; font-size: 12px; color: var(--text) @@ -9355,7 +9224,7 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { } .acl-policy-mode-custom { - grid-column: 1 / -1 + grid-column: 1/-1 } .acl-policy-custom-controls { @@ -9394,13 +9263,13 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { font-weight: 700 } -.acl-profile-rule-action-badge.is-allow, -.acl-policy-preview-head .is-allow { +.acl-policy-preview-head .is-allow, +.acl-profile-rule-action-badge.is-allow { color: var(--green) } -.acl-profile-rule-action-badge.is-deny, -.acl-policy-preview-head .is-deny { +.acl-policy-preview-head .is-deny, +.acl-profile-rule-action-badge.is-deny { color: var(--red) } @@ -9444,7 +9313,8 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { color: var(--muted) } -.acl-policy-condition span { +.acl-policy-condition span, +.listener-active-address { overflow-wrap: anywhere } @@ -9471,9 +9341,9 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { @media(max-width:900px) { - .acl-profile-layout, + .acl-policy-custom-controls, .acl-policy-modes, - .acl-policy-custom-controls { + .acl-profile-layout { grid-template-columns: 1fr } @@ -9484,14 +9354,14 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { @media(max-width:640px) { - .acl-policy-step-head, - .acl-policy-preview-head { + .acl-policy-preview-head, + .acl-policy-step-head { align-items: stretch; flex-direction: column } - .acl-policy-switch, - .acl-policy-step-head>button { + .acl-policy-step-head>button, + .acl-policy-switch { width: 100% } } @@ -9533,10 +9403,6 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { color: var(--accent) } -.listener-active-address { - overflow-wrap: anywhere -} - .listener-active-feature { padding: 2px 5px; border: 1px solid var(--line); @@ -9566,7 +9432,6 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { .php-switch-row { display: flex; align-items: center; - justify-content: flex-start; gap: 10px; min-width: 0; min-height: 30px; @@ -9583,7 +9448,7 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { line-height: 1.5; } -.php-switch-row .php-switch-label input[type="checkbox"] { +.php-switch-row .php-switch-label input[type=checkbox] { position: static; flex: 0 0 var(--ui-check-size); margin: 0; @@ -9621,7 +9486,7 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { } .php-origin-errors { - grid-column: 1 / -1; + grid-column: 1/-1; display: grid; gap: 8px; padding: 10px 0; @@ -9663,15 +9528,47 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { white-space: nowrap } -@media (max-width: 480px) { +.page-intro-title-row>:is(.quiet, .outline) { + min-height: 35px; + display: inline-flex; + align-items: center; + justify-content: center; +} + +@media(max-width:700px) { + .page-intro-title-row { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + align-items: stretch; + gap: 8px; + } + + .page-intro-title-row .overview-inline-actions, .page-intro-title-row h1 { - font-size: clamp(18px, 5vw, 24px); - letter-spacing: -.6px + grid-column: 1/-1; } .page-intro-title-row .overview-refresh { + margin-left: 0; + } + + .page-intro-title-row>:is(.quiet, .outline) { + width: 100%; + min-width: 0; padding: 7px 9px; - font-size: 11px + white-space: normal; + text-align: center; + } +} + +@media(max-width:480px) { + .page-intro-title-row h1 { + font-size: calc(clamp(18px, 5vw, 24px)); + letter-spacing: -.6px + } + + .page-intro-title-row>:is(.quiet, .outline) { + font-size: 11px; } } @@ -9704,9 +9601,9 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { } .cache-maintenance-info .metric-info-popover { - max-height: min(60vh, 420px); + max-height: calc(min(60vh, 420px)); overflow-y: auto; - width: min(440px, calc(100vw - 56px)); + width: calc(min(440px, 100vw - 56px)); } .cache-maintenance-info .metric-info-popover code { @@ -9734,7 +9631,7 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { min-height: 0; color: var(--muted); font-size: .8rem; - font-weight: normal; + font-weight: 400; white-space: nowrap; cursor: pointer; } @@ -9794,7 +9691,7 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { font-size: 1rem; } -@media (max-width: 700px) { +@media(max-width:700px) { .file-logs-heading-actions .file-log-consolidate { margin-inline-end: auto; } @@ -9804,9 +9701,8 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { } } -/* 0.31.5 traffic/log analysis actions */ -.traffic-copy-value, -.file-log-aggregate-copy { +.file-log-aggregate-copy, +.traffic-copy-value { appearance: none; border: 0; background: transparent; @@ -9818,10 +9714,10 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { cursor: copy; } -.traffic-copy-value:hover, -.traffic-copy-value:focus-visible, +.file-log-aggregate-copy:focus-visible, .file-log-aggregate-copy:hover, -.file-log-aggregate-copy:focus-visible { +.traffic-copy-value:focus-visible, +.traffic-copy-value:hover { text-decoration: underline; } @@ -9844,31 +9740,28 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { margin-top: .75rem; } -.file-log-aggregate-table th:last-child, -.file-log-aggregate-table td:last-child { +.file-log-aggregate-table td:last-child, +.file-log-aggregate-table th:last-child { width: 8rem; text-align: right; } -@media (max-width: 800px) { +@media(max-width:800px) { .file-log-aggregate-form { grid-template-columns: 1fr; } } -/* Stored Logs on narrow screens: bounded container and a compact record list. - The complete parsed JSON is available by opening any row, while the desktop - table and the persisted column picker keep their original behavior. */ -@media (max-width: 700px) { +@media(max-width:700px) { - #panel-filelogs, - #panel-filelogs .file-logs-workspace, - #file-log-view-entries, #file-log-view-counts, + #file-log-view-entries, + #panel-filelogs, + #panel-filelogs .file-log-field-grid, #panel-filelogs .file-logs-table, #panel-filelogs .file-logs-table-toolbar, - #panel-filelogs .file-log-field-grid { + #panel-filelogs .file-logs-workspace { box-sizing: border-box; min-width: 0; max-width: 100%; @@ -9899,11 +9792,11 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { } #panel-filelogs .file-log-search-help-popover { - width: min(320px, calc(100vw - 54px)); + width: calc(min(320px, 100vw - 54px)); } - #panel-filelogs .file-logs-filter, - #panel-filelogs .file-log-aggregate-form { + #panel-filelogs .file-log-aggregate-form, + #panel-filelogs .file-logs-filter { grid-template-columns: minmax(0, 1fr); } @@ -9911,12 +9804,12 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { max-width: none; } - #panel-filelogs .file-logs-filter label, - #panel-filelogs .file-logs-filter input, - #panel-filelogs .file-logs-filter select, - #panel-filelogs .file-log-aggregate-form label, #panel-filelogs .file-log-aggregate-form input, - #panel-filelogs .file-log-aggregate-form select { + #panel-filelogs .file-log-aggregate-form label, + #panel-filelogs .file-log-aggregate-form select, + #panel-filelogs .file-logs-filter input, + #panel-filelogs .file-logs-filter label, + #panel-filelogs .file-logs-filter select { min-width: 0; max-width: 100%; } @@ -9989,34 +9882,32 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { overflow-wrap: anywhere; } - /* Every user-selected column is available even when the administrator - replaces all default columns with custom parsed fields. */ - #panel-filelogs .file-logs-table td[data-mobile-column="record"] { - grid-column: 1 / -1; + #panel-filelogs .file-logs-table td[data-mobile-column=record] { + grid-column: 1/-1; font-weight: 600; line-height: 1.45; padding-inline-end: 16px; } - #panel-filelogs .file-logs-table td[data-mobile-column="time"], - #panel-filelogs .file-logs-table td[data-mobile-column="event"], - #panel-filelogs .file-logs-table td[data-mobile-column="status"], - #panel-filelogs .file-logs-table td[data-mobile-column="vhost"] { + #panel-filelogs .file-logs-table td[data-mobile-column=event], + #panel-filelogs .file-logs-table td[data-mobile-column=status], + #panel-filelogs .file-logs-table td[data-mobile-column=time], + #panel-filelogs .file-logs-table td[data-mobile-column=vhost] { color: var(--muted); font-size: 11px; } - #panel-filelogs .file-logs-table tbody tr:not(.file-log-detail-row)>td::before { + #panel-filelogs .file-logs-table tbody tr:not(.file-log-detail-row)>td:before { content: attr(data-mobile-label) ": "; color: var(--text-subtle); font-weight: 400; } - #panel-filelogs .file-logs-table tbody tr:not(.file-log-detail-row)>td[data-mobile-column="record"]::before { + #panel-filelogs .file-logs-table tbody tr:not(.file-log-detail-row)>td[data-mobile-column=record]:before { content: ""; } - #panel-filelogs .file-logs-table tbody tr:not(.file-log-detail-row)::after { + #panel-filelogs .file-logs-table tbody tr:not(.file-log-detail-row):after { content: "+"; position: absolute; inset-block-end: 4px; @@ -10026,7 +9917,7 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { pointer-events: none; } - #panel-filelogs .file-logs-table tbody tr.selected::after { + #panel-filelogs .file-logs-table tbody tr.selected:after { content: "-"; } @@ -10082,11 +9973,10 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { } } -/* Tablet/narrow-desktop log tables scroll locally instead of expanding
. */ -#panel-filelogs, -#panel-filelogs .file-logs-workspace, +#file-log-view-counts, #file-log-view-entries, -#file-log-view-counts { +#panel-filelogs, +#panel-filelogs .file-logs-workspace { min-width: 0; max-width: 100%; } @@ -10095,36 +9985,35 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { grid-template-columns: minmax(0, 1fr); } -#panel-filelogs .file-logs-table, -#panel-filelogs .file-log-aggregate-table { +#panel-filelogs .file-log-aggregate-table, +#panel-filelogs .file-logs-table { box-sizing: border-box; min-width: 0; max-width: 100%; overflow-x: auto; } -@media (max-width: 700px) { +@media(max-width:700px) { #panel-filelogs .file-logs-table { overflow: visible; } - #panel-filelogs .file-logs-table td[data-mobile-column="time"], - #panel-filelogs .file-logs-table td[data-mobile-column="vhost"] { - grid-column: 1 / -1; + #panel-filelogs .file-logs-table td[data-mobile-column=time], + #panel-filelogs .file-logs-table td[data-mobile-column=vhost] { + grid-column: 1/-1; } - #panel-filelogs .file-logs-table td[data-mobile-column="record"] { + #panel-filelogs .file-logs-table td[data-mobile-column=record] { max-height: 4.4em; overflow: hidden; } - #panel-filelogs .file-logs-table tr.selected td[data-mobile-column="record"] { + #panel-filelogs .file-logs-table tr.selected td[data-mobile-column=record] { max-height: none; overflow-wrap: anywhere; } } -/* 0.31.7: separate protocol tuning actions; keep them comfortably tappable. */ .listener-protocol-settings-actions { display: flex; gap: 14px; @@ -10141,7 +10030,6 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { display: none; } -/* 0.31.7 revision preview shows only changed leaf fields. */ .config-history-change-values { display: flex; flex-wrap: wrap; @@ -10164,7 +10052,7 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { } .config-history-json-dialog { - width: min(1200px, calc(100vw - 24px)); + width: calc(min(1200px, 100vw - 24px)); max-width: calc(100vw - 24px); max-height: calc(100dvh - 28px); overflow: auto; @@ -10191,7 +10079,7 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { font: 11px/1.5 var(--mono); } -@media (max-width: 760px) { +@media(max-width:760px) { .config-history-json-columns { grid-template-columns: 1fr; } @@ -10211,11 +10099,12 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { font-size: 12px; } -.acl-profile-attached p { +#panel-overview .stats-grid .stat>small, +.acl-profile-attached p, +.stream-backend-status-row>span:first-child { overflow-wrap: anywhere; } -/* v0.32 Stream extensions reuse the responsive Vhost editor and stat cards. */ .stream-backend-pool { display: grid; gap: .8rem; @@ -10258,11 +10147,7 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { font-size: .85em; } -.stream-backend-status-row>span:first-child { - overflow-wrap: anywhere; -} - -[data-dashboard-widget^="stream_"][hidden] { +[data-dashboard-widget^=stream_][hidden] { display: none !important; } @@ -10275,18 +10160,15 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { grid-template-rows: auto; } -.vh-enable-control.vh-enable-no-hint> :is(input, .vh-enable-state) { +.vh-enable-control.vh-enable-no-hint>:is(input, .vh-enable-state) { grid-row: 1; } +#panel-overview .backend-mode-bar span, #panel-overview .stats-grid>.stat { min-width: 0; } -#panel-overview .stats-grid .stat>small { - overflow-wrap: anywhere; -} - #panel-overview .stream-traffic-legend .legend-rx i { background: var(--chart-accent); } @@ -10300,12 +10182,12 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { white-space: normal; } -@media (min-width: 1201px) { +@media(min-width:1201px) { #panel-overview .stats-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); } } -/* Optional HTTP backend mode distribution (configured modes, not negotiated ALPN). */ + #panel-overview .backend-mode-bar { display: flex; height: 7px; @@ -10316,96 +10198,304 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { background: var(--line); margin: 7px 0 5px; } -#panel-overview .backend-mode-bar span { min-width: 0; } -#panel-overview .backend-mode-h1 { background: var(--traffic-h1); } -#panel-overview .backend-mode-auto { background: var(--traffic-h2); } -#panel-overview .backend-mode-compat { background: var(--traffic-h2); opacity: .5; } -#panel-overview .backend-mode-h2 { background: var(--traffic-h3); } -#panel-overview .backend-mode-bar[aria-label] { background-clip: padding-box; } -.vh-origin-protocol-info { color: var(--muted); } -.vh-origin-protocol-warning { color: var(--muted); } +#panel-overview .backend-mode-h1 { + background: var(--traffic-h1); +} + +#panel-overview .backend-mode-auto { + background: var(--traffic-h2); +} + +#panel-overview .backend-mode-compat { + background: var(--traffic-h2); + opacity: .5; +} + +#panel-overview .backend-mode-h2 { + background: var(--traffic-h3); +} + +#panel-overview .backend-mode-bar[aria-label] { + background-clip: padding-box; +} + +.vh-origin-protocol-info, +.vh-origin-protocol-warning { + color: var(--muted); +} -/* Overview arrangement dialog: visual clones never participate in metric updates. */ .overview-layout-dialog { - width: min(1180px, calc(100vw - 24px)); + width: calc(min(1180px, 100vw - 24px)); max-height: calc(100dvh - 24px); overflow: auto; } -.overview-layout-dialog > .window-heading { + +.overview-layout-dialog>.window-heading { position: sticky; top: 0; z-index: 2; background: var(--bg); border-bottom: 1px solid var(--line); } -.overview-layout-body { display: grid; gap: 16px; padding: 14px; } -.overview-layout-body h3 { font-size: 14px; margin: 0 0 10px; } -.overview-layout-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--ui-gap, 10px); } -.overview-layout-tile { min-width: 0; border: 1px solid var(--line); background: var(--panel); cursor: grab; } -.overview-layout-tile:active { cursor: grabbing; } -.overview-layout-tile.is-dragging { opacity: .4; } -.overview-layout-tile.is-drop-target { outline: 2px solid var(--accent); outline-offset: -2px; } -.overview-layout-tile-toolbar { display: flex; align-items: center; gap: 3px; padding: 4px; border-bottom: 1px solid var(--line); } -.overview-layout-tile-toolbar button { min-height: 28px; min-width: 28px; padding: 3px 7px; } -.overview-layout-grip { padding: 0 5px; color: var(--muted); user-select: none; } -.overview-layout-hide { margin-inline-start: auto; font-size: 11px; } -.overview-layout-grid .stat { border: 0; padding: 12px; min-height: 98px; pointer-events: none; } -.overview-layout-grid .stat > span { font-size: 11px; } -.overview-layout-grid .stat strong { font-size: 24px; margin-top: 7px; overflow-wrap: anywhere; } -.overview-layout-grid .stat small { display: block; font-size: 10px; line-height: 1.4; overflow-wrap: anywhere; } -.overview-layout-grid .stat svg { width: 100%; height: 53px; } -.overview-layout-grid .stat-chart-legend { display: flex; flex-wrap: wrap; gap: 8px; } -.overview-layout-section-heading { display: flex; align-items: center; justify-content: space-between; gap: 10px; } -.overview-layout-section-heading input { max-width: 230px; } -.overview-layout-hidden-list { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; } -.overview-layout-hidden-item { display: flex; align-items: center; justify-content: space-between; gap: 8px; border: 1px solid var(--line); padding: 8px; background: var(--panel); min-width: 0; } -.overview-layout-hidden-item span { font-size: 12px; min-width: 0; overflow-wrap: anywhere; } -.overview-layout-hidden-item button { flex-shrink: 0; } -.overview-layout-footer { position: sticky; bottom: 0; display: flex; align-items: center; justify-content: flex-end; gap: 8px; padding: 12px; border-top: 1px solid var(--line); background: var(--bg); z-index: 2; } -.overview-layout-footer p { margin: 0 auto 0 0; font-size: 11px; overflow-wrap: anywhere; } -.dashboard-layout-open-settings { margin: 5px 0; justify-self: start; } -@media (max-width: 850px) { - .overview-layout-grid, .overview-layout-hidden-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } -} -@media (max-width: 550px) { - .overview-layout-grid, .overview-layout-hidden-list { grid-template-columns: minmax(0, 1fr); } - .overview-layout-section-heading { align-items: stretch; flex-direction: column; } - .overview-layout-section-heading input { max-width: none; width: 100%; } - .overview-layout-footer { flex-wrap: wrap; } - .overview-layout-footer p { width: 100%; } + +.overview-layout-body { + display: grid; + gap: 16px; + padding: 14px; +} + +.overview-layout-body h3 { + font-size: 14px; + margin: 0 0 10px; +} + +.overview-layout-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: var(--ui-gap, 10px); +} + +.overview-layout-tile { + min-width: 0; + border: 1px solid var(--line); + background: var(--panel); + cursor: grab; +} + +.overview-layout-tile:active { + cursor: grabbing; +} + +.overview-layout-tile.is-dragging { + opacity: .4; +} + +.overview-layout-tile.is-drop-target { + outline: 2px solid var(--accent); + outline-offset: -2px; +} + +.overview-layout-tile-toolbar { + display: flex; + align-items: center; + gap: 3px; + padding: 4px; + border-bottom: 1px solid var(--line); +} + +.overview-layout-tile-toolbar button { + min-height: 28px; + min-width: 28px; + padding: 3px 7px; +} + +.overview-layout-grip { + padding: 0 5px; + color: var(--muted); + user-select: none; +} + +.overview-layout-hide { + margin-inline-start: auto; + font-size: 11px; +} + +.overview-layout-grid .stat { + border: 0; + padding: 12px; + min-height: 98px; + pointer-events: none; +} + +.overview-layout-grid .stat>span { + font-size: 11px; +} + +.overview-layout-grid .stat strong { + font-size: 24px; + margin-top: 7px; + overflow-wrap: anywhere; +} + +.overview-layout-grid .stat small { + display: block; + font-size: 10px; + line-height: 1.4; + overflow-wrap: anywhere; +} + +.overview-layout-grid .stat svg { + width: 100%; + height: 53px; +} + +.overview-layout-grid .stat-chart-legend { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.overview-layout-section-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} + +.overview-layout-section-heading input { + max-width: 230px; +} + +.overview-layout-hidden-list { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 6px; +} + +.overview-layout-hidden-item { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + border: 1px solid var(--line); + padding: 8px; + background: var(--panel); + min-width: 0; +} + +.overview-layout-hidden-item span { + font-size: 12px; + min-width: 0; + overflow-wrap: anywhere; +} + +.overview-layout-hidden-item button { + flex-shrink: 0; +} + +.overview-layout-footer { + position: sticky; + bottom: 0; + display: flex; + align-items: center; + justify-content: flex-end; + gap: 8px; + padding: 12px; + border-top: 1px solid var(--line); + background: var(--bg); + z-index: 2; +} + +.overview-layout-footer p { + margin: 0 auto 0 0; + font-size: 11px; + overflow-wrap: anywhere; +} + +.dashboard-layout-open-settings { + margin: 5px 0; + justify-self: start; +} + +@media(max-width:850px) { + + .overview-layout-grid, + .overview-layout-hidden-list { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media(max-width:550px) { + + .overview-layout-grid, + .overview-layout-hidden-list { + grid-template-columns: minmax(0, 1fr); + } + + .overview-layout-section-heading { + align-items: stretch; + flex-direction: column; + } + + .overview-layout-section-heading input { + max-width: none; + width: 100%; + } + + .overview-layout-footer { + flex-wrap: wrap; + } + + .overview-layout-footer p { + width: 100%; + } } -/* One rearrangeable Overview canvas. Existing large charts, diagnostics and the - backend table keep their original nodes/IDs and their data update handlers. */ #panel-overview .dashboard-overview-grid { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: var(--ui-gap); align-items: stretch; } -#panel-overview .dashboard-overview-grid > [data-dashboard-widget] { min-width: 0; } -#panel-overview .dashboard-overview-grid > [hidden] { display: none !important; } -#panel-overview .dashboard-overview-grid > .card:not(.pool-card) { grid-column: span 2; } -#panel-overview .dashboard-overview-grid > .pool-card { grid-column: 1 / -1; margin-bottom: 0; } -#panel-overview .dashboard-overview-grid > .activity-card svg { min-width: 0; } -@media (max-width: 1200px) { - #panel-overview .dashboard-overview-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } - #panel-overview .dashboard-overview-grid > .stat { grid-column: span 2; } - #panel-overview .dashboard-overview-grid > .card:not(.pool-card) { grid-column: span 2; } + +#panel-overview .dashboard-overview-grid>.activity-card svg, +#panel-overview .dashboard-overview-grid>[data-dashboard-widget] { + min-width: 0; } -@media (max-width: 850px) { - #panel-overview .dashboard-overview-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } - #panel-overview .dashboard-overview-grid > .stat { grid-column: span 1; } - #panel-overview .dashboard-overview-grid > .card:not(.pool-card) { grid-column: 1 / -1; } + +#panel-overview .dashboard-overview-grid>[hidden] { + display: none !important; } -/* Even before the aligned layout script starts, phone-width Overview tiles - stay in two columns. A late 1-to-2-column switch can shift scroll position. */ -@media (max-width: 550px) { - #panel-overview .dashboard-overview-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } - #panel-overview .dashboard-overview-grid > .stat { grid-column: auto / span 1; } + +#panel-overview .dashboard-overview-grid>.card:not(.pool-card) { + grid-column: span 2; } -.overview-layout-tile.is-panel { grid-column: span 2; } + +#panel-overview .dashboard-overview-grid>.pool-card { + grid-column: 1/-1; + margin-bottom: 0; +} + +@media(max-width:1200px) { + #panel-overview .dashboard-overview-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + #panel-overview .dashboard-overview-grid>.card:not(.pool-card), + #panel-overview .dashboard-overview-grid>.stat { + grid-column: span 2; + } +} + +@media(max-width:850px) { + #panel-overview .dashboard-overview-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + #panel-overview .dashboard-overview-grid>.stat { + grid-column: span 1; + } + + #panel-overview .dashboard-overview-grid>.card:not(.pool-card) { + grid-column: 1/-1; + } +} + +@media(max-width:550px) { + #panel-overview .dashboard-overview-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + #panel-overview .dashboard-overview-grid>.stat { + grid-column: auto/span 1; + } +} + +.overview-layout-tile.is-panel { + grid-column: span 2; +} + .overview-layout-grid .overview-layout-panel-preview { display: grid; gap: 7px; @@ -10414,17 +10504,45 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { background: var(--panel); pointer-events: none; } -.overview-layout-panel-preview > strong { font-size: 13px; } -.overview-layout-panel-preview svg { width: 100%; height: 80px; } -.overview-layout-panel-preview small { color: var(--muted); } -.overview-layout-footer #overview-layout-default { margin-right: auto; } -.overview-reset-confirm { width: min(460px, calc(100vw - 24px)); padding: 16px; } -.overview-reset-confirm > p { line-height: 1.5; } -@media (max-width: 850px) { .overview-layout-tile.is-panel { grid-column: span 2; } } -@media (max-width: 550px) { .overview-layout-tile.is-panel { grid-column: span 1; } } -/* Overview block kinds come from the same server-validated registry as the - saved order. Labels distinguish numbers, plots, tables and diagnostic panels. */ +.overview-layout-panel-preview>strong { + font-size: 13px; +} + +.overview-layout-panel-preview svg { + width: 100%; + height: 80px; +} + +.overview-layout-panel-preview small { + color: var(--muted); +} + +.overview-layout-footer #overview-layout-default { + margin-right: auto; +} + +.overview-reset-confirm { + width: calc(min(460px, 100vw - 24px)); + padding: 16px; +} + +.overview-reset-confirm>p { + line-height: 1.5; +} + +@media(max-width:850px) { + .overview-layout-tile.is-panel { + grid-column: span 2; + } +} + +@media(max-width:550px) { + .overview-layout-tile.is-panel { + grid-column: span 1; + } +} + .overview-widget-type { display: inline-block; width: fit-content; @@ -10438,31 +10556,70 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { line-height: 1.25; overflow-wrap: anywhere; } -.dashboard-widget-setting .dashboard-widget-toggle { flex-wrap: wrap; } -.dashboard-widget-setting .overview-widget-type { margin-left: 15px; } -.overview-layout-tile-toolbar .overview-widget-type { margin-left: 3px; } -.overview-layout-hidden-item .overview-widget-type { flex: 0 0 auto; } -.overview-layout-filters { display: flex; flex-wrap: wrap; gap: 8px; } -.overview-layout-filters select, .overview-layout-filters input { width: min(220px, 100%); max-width: 100%; } -/* Inline editing of the actual Overview blocks; live charts and original DOM - IDs remain in place. Dragging is enabled only on the explicit grip button. */ -.overview-inline-actions { display: inline-flex; gap: 7px; align-items: center; flex-wrap: wrap; } -.overview-inline-actions[hidden], .overview-inline-hint[hidden] { display: none !important; } -.overview-inline-hint { margin: 0 0 10px; } -#panel-overview .dashboard-overview-grid.is-unlocked > [data-dashboard-widget] { +.dashboard-widget-setting .dashboard-widget-toggle { + flex-wrap: wrap; +} + +.dashboard-widget-setting .overview-widget-type { + margin-left: 15px; +} + +.overview-layout-tile-toolbar .overview-widget-type { + margin-left: 3px; +} + +.overview-layout-hidden-item .overview-widget-type { + flex: 0 0 auto; +} + +.overview-layout-filters { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.overview-layout-filters input, +.overview-layout-filters select { + width: min(220px, 100%); + max-width: 100%; +} + +.overview-inline-actions { + display: inline-flex; + gap: 7px; + align-items: center; + flex-wrap: wrap; +} + +.overview-inline-actions[hidden], +.overview-inline-hint[hidden] { + display: none !important; +} + +.overview-inline-hint { + margin: 0 0 10px; +} + +#panel-overview .dashboard-overview-grid.is-unlocked>[data-dashboard-widget] { position: relative; outline: 1px dashed var(--accent); outline-offset: -2px; padding-top: 66px; } -#panel-overview .dashboard-overview-grid.is-unlocked > .is-overview-dragging { opacity: .45; } -#panel-overview .dashboard-overview-grid.is-unlocked > .is-overview-drop-before { + +#panel-overview .dashboard-overview-grid.is-unlocked>.is-overview-dragging { + opacity: .45; +} + +#panel-overview .dashboard-overview-grid.is-unlocked>.is-overview-drop-before { box-shadow: inset 4px 0 var(--accent); } -#panel-overview .dashboard-overview-grid.is-unlocked > .is-overview-drop-after { + +#panel-overview .dashboard-overview-grid.is-unlocked>.is-overview-drop-after { box-shadow: inset -4px 0 var(--accent); } + .overview-inline-toolbar { position: absolute; top: 3px; @@ -10475,37 +10632,57 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { gap: 3px; pointer-events: auto; } + #panel-overview .overview-inline-toolbar button { min-width: 28px; min-height: 27px; padding: 2px 7px; font-size: 12px; } -#panel-overview .overview-inline-toolbar .overview-inline-grip { cursor: grab; user-select: none; } -#panel-overview .overview-inline-toolbar .overview-inline-grip:active { cursor: grabbing; } -#panel-overview .overview-inline-toolbar .overview-widget-type { margin-left: auto; } -@media (max-width: 600px) { - .overview-layout-hidden-item { flex-wrap: wrap; } - .overview-layout-filters select, .overview-layout-filters input { width: 100%; } - #panel-overview .dashboard-overview-grid.is-unlocked > [data-dashboard-widget] { padding-top: 68px; } + +#panel-overview .overview-inline-toolbar .overview-inline-grip { + cursor: grab; + user-select: none; +} + +#panel-overview .overview-inline-toolbar .overview-inline-grip:active { + cursor: grabbing; +} + +#panel-overview .overview-inline-toolbar .overview-widget-type { + margin-left: auto; +} + +@media(max-width:600px) { + .overview-layout-hidden-item { + flex-wrap: wrap; + } + + .overview-layout-filters input, + .overview-layout-filters select { + width: 100%; + } + + #panel-overview .dashboard-overview-grid.is-unlocked>[data-dashboard-widget] { + padding-top: 68px; + } } -/* Direct Overview editing: hide is reversible until Save and remains - recoverable later from the block selector or the compact hidden shelf. */ #panel-overview .overview-inline-toolbar .overview-inline-hide { color: var(--red); border: 1px solid var(--danger-line); background: var(--panel); - /* Use the same .quiet radius/corner preset as the arrow buttons. */ font-size: 18px; line-height: 1; } -#panel-overview .overview-inline-toolbar .overview-inline-hide:hover, -#panel-overview .overview-inline-toolbar .overview-inline-hide:focus-visible { + +#panel-overview .overview-inline-toolbar .overview-inline-hide:focus-visible, +#panel-overview .overview-inline-toolbar .overview-inline-hide:hover { border-color: var(--danger-line-strong); background: var(--notice-bg); } + .overview-inline-hidden { margin: 0 0 10px; border: 1px solid var(--line); @@ -10513,22 +10690,29 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { background: var(--panel); color: var(--text); } -.overview-inline-hidden > summary { + +.overview-inline-hidden>summary { padding: 9px 12px; cursor: pointer; color: var(--muted); font-size: 12px; } -.overview-inline-hidden > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } + +.overview-inline-hidden>summary:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; +} + .overview-inline-hidden-list { display: flex; flex-wrap: wrap; gap: 7px; - max-height: min(30vh, 240px); + max-height: calc(min(30vh, 240px)); overflow-y: auto; padding: 10px 12px; border-top: 1px solid var(--line); } + .overview-inline-hidden-item { display: inline-flex; align-items: center; @@ -10541,8 +10725,12 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { background: var(--panel2); font-size: 12px; } -.overview-inline-hidden-item > span:not(.overview-widget-type) { overflow-wrap: anywhere; } -.overview-inline-hidden-item > button { + +.overview-inline-hidden-item>span:not(.overview-widget-type) { + overflow-wrap: anywhere; +} + +.overview-inline-hidden-item>button { flex: 0 0 auto; min-width: 27px; min-height: 27px; @@ -10552,23 +10740,28 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { color: var(--green); background: var(--panel); } -.overview-inline-hidden-item > button:hover { border-color: var(--accent); } -@media (max-width: 600px) { - #panel-overview .dashboard-overview-grid.is-unlocked > [data-dashboard-widget] { padding-top: 82px; } - .overview-inline-hidden-item { max-width: 100%; } + +.overview-inline-hidden-item>button:hover { + border-color: var(--accent); +} + +@media(max-width:600px) { + #panel-overview .dashboard-overview-grid.is-unlocked>[data-dashboard-widget] { + padding-top: 82px; + } + + .overview-inline-hidden-item { + max-width: 100%; + } +} + +#panel-overview { + container: overview-canvas / inline-size; } -/* The original fixed Overview grid, also used for personalized layouts. - Metrics, wide charts, diagnostic panels and tables are separate rows; - every tile in a row stretches to the SAME height. This intentionally - favors symmetry over masonry or floating short cards beside tall panels. - Existing density controls define the identical horizontal/vertical gap. */ -#panel-overview { container: overview-canvas / inline-size; } #panel-overview .dashboard-overview-grid.is-aligned { display: grid; - /* A compact gutter throughout: tiles, large panels, rows and columns. - Keep the density setting, but avoid the former larger tile-to-tile gap. */ - --overview-gap: max(3px, calc(var(--ui-gap) - 1px)); + --overview-gap: var(--ui-gap); margin: 0; row-gap: var(--overview-gap); column-gap: var(--overview-gap); @@ -10578,18 +10771,15 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { align-items: stretch; align-content: start; gap: var(--overview-gap); + overflow-anchor: none; } -/* The earlier html[data-dashboard-density] .stats-grid rules have greater - specificity than .is-aligned and otherwise restore the old, larger gutter. - Keep the compact value in the combined Overview grid at every density. */ + #panel-overview .dashboard-overview-grid.is-aligned.stats-grid { gap: var(--overview-gap); } -/* Live metric text can change while the user scrolls; do not allow the - browser to re-anchor an entire dashboard against these changing values. */ -#panel-overview .dashboard-overview-grid.is-aligned { overflow-anchor: none; } -#panel-overview .dashboard-overview-grid.is-aligned > [data-dashboard-widget] { - grid-column: auto / span 2; + +#panel-overview .dashboard-overview-grid.is-aligned>[data-dashboard-widget] { + grid-column: auto/span 2; grid-row: auto; align-self: stretch; width: 100%; @@ -10599,29 +10789,58 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { margin-block: 0; position: relative; } -#panel-overview .dashboard-overview-grid.is-aligned > .card:not(.pool-card) { grid-column: auto / span 4; } -#panel-overview .dashboard-overview-grid.is-aligned > .card.overview-wide-chart { grid-column: auto / span 6; } -#panel-overview .dashboard-overview-grid.is-aligned > .pool-card { grid-column: 1 / -1; } -/* Each size category starts at the first track, even after drag/drop. */ -#panel-overview .dashboard-overview-grid.is-aligned > .overview-row-start, -#panel-overview .dashboard-overview-grid.is-aligned > .card.overview-row-start { grid-column-start: 1; } -@container overview-canvas (max-width: 1080px) { - #panel-overview .dashboard-overview-grid.is-aligned { grid-template-columns: repeat(6, minmax(0, 1fr)); } - #panel-overview .dashboard-overview-grid.is-aligned > [data-dashboard-widget].stat { grid-column: auto / span 2; } - #panel-overview .dashboard-overview-grid.is-aligned > .card:not(.pool-card) { grid-column: auto / span 3; } - #panel-overview .dashboard-overview-grid.is-aligned > .card.overview-wide-chart, - #panel-overview .dashboard-overview-grid.is-aligned > .pool-card { grid-column: 1 / -1; } + +#panel-overview .dashboard-overview-grid.is-aligned>.card:not(.pool-card) { + grid-column: auto/span 4; } + +#panel-overview .dashboard-overview-grid.is-aligned>.card.overview-wide-chart { + grid-column: auto/span 6; +} + +#panel-overview .dashboard-overview-grid.is-aligned>.pool-card { + grid-column: 1/-1; +} + +#panel-overview .dashboard-overview-grid.is-aligned>.card.overview-row-start, +#panel-overview .dashboard-overview-grid.is-aligned>.overview-row-start { + grid-column-start: 1; +} + +@container overview-canvas (max-width: 1080px) { + #panel-overview .dashboard-overview-grid.is-aligned { + grid-template-columns: repeat(6, minmax(0, 1fr)); + } + + #panel-overview .dashboard-overview-grid.is-aligned>[data-dashboard-widget].stat { + grid-column: auto/span 2; + } + + #panel-overview .dashboard-overview-grid.is-aligned>.card:not(.pool-card) { + grid-column: auto/span 3; + } + + #panel-overview .dashboard-overview-grid.is-aligned>.card.overview-wide-chart, + #panel-overview .dashboard-overview-grid.is-aligned>.pool-card { + grid-column: 1/-1; + } +} + @container overview-canvas (max-width: 700px) { - #panel-overview .dashboard-overview-grid.is-aligned { grid-template-columns: repeat(2, minmax(0, 1fr)); } - #panel-overview .dashboard-overview-grid.is-aligned > [data-dashboard-widget].stat { - grid-column: auto / span 1; + #panel-overview .dashboard-overview-grid.is-aligned { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + #panel-overview .dashboard-overview-grid.is-aligned>[data-dashboard-widget].stat { + grid-column: auto/span 1; touch-action: pan-y pinch-zoom; overflow-wrap: anywhere; } - /* The WebSocket tunnel action is absolutely positioned on desktop. In a - half-width phone tile it overlaps the label and value unless it flows. */ - #panel-overview .dashboard-overview-grid.is-aligned > .stat.stat-with-action { padding-right: 11px; } + + #panel-overview .dashboard-overview-grid.is-aligned>.stat.stat-with-action { + padding-right: 11px; + } + #panel-overview .dashboard-overview-grid.is-aligned .stat-with-action .stat-corner-action { position: static; display: block; @@ -10630,110 +10849,390 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline { margin: 6px 0 0; white-space: normal; } - html[data-dashboard-density="normal"] #panel-overview .dashboard-overview-grid.is-aligned > .stat strong, - html[data-dashboard-density="compact"] #panel-overview .dashboard-overview-grid.is-aligned > .stat strong { - font-size: clamp(18px, 5.5vw, 25px); + + html[data-dashboard-density=compact] #panel-overview .dashboard-overview-grid.is-aligned>.stat strong, + html[data-dashboard-density=normal] #panel-overview .dashboard-overview-grid.is-aligned>.stat strong { + font-size: calc(clamp(18px, 5.5vw, 25px)); } - #panel-overview .dashboard-overview-grid.is-aligned > .card:not(.pool-card) { grid-column: 1 / -1; } -} -/* Keep category boundaries intact at every responsive breakpoint. */ -#panel-overview .dashboard-overview-grid.is-aligned > [data-dashboard-widget].overview-row-start { grid-column-start: 1; } -/* Full-size optional charts: static SVG geometry and bounded historical data. */ -#panel-overview .overview-wide-chart { padding: 15px; overflow: hidden; } -#panel-overview .overview-wide-chart .card-heading { min-width: 0; align-items: baseline; gap: 10px; } -#panel-overview .overview-wide-chart h2 { min-width: 0; overflow-wrap: anywhere; } -#panel-overview .overview-wide-summary { color: var(--accent); font: 500 12px var(--mono); white-space: nowrap; } -#panel-overview .overview-wide-svg { display: block; width: 100%; height: clamp(125px, 13vw, 190px); margin-block: 13px 9px; } -#panel-overview .overview-wide-svg .chart-grid { stroke: var(--line); fill: none; stroke-width: 1; opacity: .6; } -#panel-overview .overview-wide-series { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; } -#panel-overview .overview-wide-series-0 { stroke: var(--chart-accent); } -#panel-overview .overview-wide-series-1 { stroke: var(--chart-secondary); } -#panel-overview .overview-wide-series-2 { stroke: var(--chart-tertiary); } -#panel-overview .overview-wide-legend { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; color: var(--muted); font-size: 11px; } -#panel-overview .overview-wide-legend > span { display: inline-flex; align-items: center; gap: 6px; min-width: 0; } -#panel-overview .overview-wide-legend i { width: 8px; height: 8px; border-radius: 100%; background: var(--chart-accent); } -#panel-overview .overview-wide-legend .overview-wide-key-1 { background: var(--chart-secondary); } -#panel-overview .overview-wide-legend .overview-wide-key-2 { background: var(--chart-tertiary); } -#panel-overview .overview-wide-legend b { font: 500 11px var(--mono); color: var(--text); } -/* Block metadata stays to the left of every action group. */ -.dashboard-widget-setting .dashboard-widget-toggle { flex-wrap: nowrap; } -.dashboard-widget-setting .dashboard-widget-toggle .overview-widget-type { margin: 0 0 0 auto; flex: 0 0 auto; } -.dashboard-widget-setting .dashboard-widget-setting-actions { justify-self: end; flex-shrink: 0; } -.overview-layout-tile-toolbar { flex-wrap: wrap; } -.overview-layout-tile-toolbar .overview-widget-type { margin: 0; } -.overview-layout-tile-toolbar .overview-layout-controls { display: inline-flex; align-items: center; gap: 3px; margin-left: auto; } -.overview-layout-hidden-item > button { margin-left: auto; } -.overview-layout-hidden-item > .overview-widget-type { margin: 0; } -#panel-overview .overview-inline-toolbar .overview-widget-type { margin: 0 auto 0 0; } -#panel-overview .overview-inline-toolbar .overview-inline-hide { margin-left: 0; } -.overview-inline-hidden-item > .overview-widget-type { margin-right: 0; } -.overview-inline-hidden-item > .overview-inline-restore { order: 3; margin-left: auto; } + #panel-overview .dashboard-overview-grid.is-aligned>.card:not(.pool-card) { + grid-column: 1/-1; + } +} -/* Appearance selector: always show the block-kind label, independently from - the visibility button, with actions pinned to the right edge. */ -.dashboard-widget-list { - grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); +#panel-overview .dashboard-overview-grid.is-aligned>[data-dashboard-widget].overview-row-start { + grid-column-start: 1; } -.dashboard-widget-setting { - grid-template-columns: minmax(0, 1fr) auto; - gap: 6px; + +#panel-overview .overview-wide-chart { + padding: 15px; + overflow: hidden; } -.dashboard-widget-setting .dashboard-widget-toggle { + +#panel-overview .overview-wide-chart .card-heading { + min-width: 0; + align-items: baseline; + gap: 10px; +} + +#panel-overview .overview-wide-chart h2 { + min-width: 0; + overflow-wrap: anywhere; +} + +#panel-overview .overview-wide-summary { + color: var(--accent); + font: 500 12px var(--mono); + white-space: nowrap; +} + +#panel-overview .overview-wide-svg { + display: block; width: 100%; + height: calc(clamp(125px, 13vw, 190px)); + margin-block: 13px 9px; +} + +#panel-overview .overview-wide-svg .chart-grid { + stroke: var(--line); + fill: none; + stroke-width: 1; + opacity: .6; +} + +#panel-overview .overview-wide-series { + fill: none; + stroke-width: 2; + stroke-linejoin: round; + stroke-linecap: round; +} + +#panel-overview .overview-wide-series-0 { + stroke: var(--chart-accent); +} + +#panel-overview .overview-wide-series-1 { + stroke: var(--chart-secondary); +} + +#panel-overview .overview-wide-series-2 { + stroke: var(--chart-tertiary); +} + +#panel-overview .overview-wide-legend { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px 16px; + color: var(--muted); + font-size: 11px; +} + +#panel-overview .overview-wide-legend>span { + display: inline-flex; + align-items: center; + gap: 6px; + min-width: 0; +} + +#panel-overview .overview-wide-legend i { + width: 8px; + height: 8px; + border-radius: 100%; + background: var(--chart-accent); +} + +#panel-overview .overview-wide-legend .overview-wide-key-1 { + background: var(--chart-secondary); +} + +#panel-overview .overview-wide-legend .overview-wide-key-2 { + background: var(--chart-tertiary); +} + +#panel-overview .overview-wide-legend b { + font: 500 11px var(--mono); + color: var(--text); +} + +.dashboard-widget-setting .dashboard-widget-toggle .overview-widget-type { + margin: 0 0 0 auto; + flex: 0 0 auto; +} + +.dashboard-widget-setting .dashboard-widget-setting-actions { + justify-self: end; + flex-shrink: 0; +} + +.overview-layout-tile-toolbar { + flex-wrap: wrap; +} + +.overview-layout-tile-toolbar .overview-widget-type { + margin: 0; +} + +.overview-layout-tile-toolbar .overview-layout-controls { + display: inline-flex; + align-items: center; + gap: 3px; + margin-left: auto; +} + +.overview-layout-hidden-item>button { + margin-left: auto; +} + +.overview-layout-hidden-item>.overview-widget-type { + margin: 0; +} + +#panel-overview .overview-inline-toolbar .overview-widget-type { + margin: 0 auto 0 0; +} + +#panel-overview .overview-inline-toolbar .overview-inline-hide { + margin-left: 0; +} + +.overview-inline-hidden-item>.overview-widget-type { + margin-right: 0; +} + +.overview-inline-hidden-item>.overview-inline-restore { + order: 3; + margin-left: auto; +} + +.dashboard-widget-list { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.dashboard-widget-setting { + display: inline-flex; + align-items: center; + gap: 8px; + flex: 1 1 280px; + min-width: min(280px, 100%); + max-width: 100%; + padding: 4px 6px; +} + +.dashboard-widget-setting .dashboard-widget-toggle { + width: auto; + flex: 1 1 auto; overflow: hidden; min-width: 0; flex-wrap: nowrap; } + .dashboard-widget-setting .dashboard-widget-setting-label { flex: 1 1 auto; min-width: 0; } -.dashboard-widget-setting > .overview-widget-type { + +.dashboard-widget-setting>.overview-widget-type { flex: none; margin: 0; white-space: nowrap; overflow-wrap: normal; } -.dashboard-widget-setting > .dashboard-widget-setting-actions { + +.dashboard-widget-setting>.dashboard-widget-setting-actions { justify-self: end; white-space: nowrap; } -/* Global shape preference. "default" applies NO overrides so existing dark, - light, cream and Ultra Dark keep the same established corner geometry. - Circular status dots, spinners, switches and chart markers retain their - semantic shape; underline tabs and navigation accent strips stay intact. */ -html[data-corner-style="square"] { --rp-radius-surface: 0px; --rp-radius-control: 0px; } -html[data-corner-style="subtle"] { --rp-radius-surface: 5px; --rp-radius-control: 4px; } -html[data-corner-style="rounded"] { --rp-radius-surface: 12px; --rp-radius-control: 8px; } -html[data-corner-style="extra"] { --rp-radius-surface: 18px; --rp-radius-control: 12px; } -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 -) { border-radius: var(--rp-radius-surface) !important; } -html:is([data-corner-style="square"], [data-corner-style="subtle"], [data-corner-style="rounded"], [data-corner-style="extra"]) :is( - button:not(.nav):not(.file-log-tab), input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), - select, textarea, .pill, .vhost-backend-protocol-badge, .overview-widget-type, .badge, .quiet, .outline, .primary, - .code, .command, .dashboard-widget-toggle, .settings-tabs button, - .overview-inline-hidden-toggle, .overview-inline-restore -) { border-radius: var(--rp-radius-control) !important; } +@media(max-width:700px) { + .dashboard-widget-list { + display: grid; + grid-template-columns: minmax(0, 1fr); + } -/* Appearance stays badge-free; badges remain useful in the visual editor. */ -#settings-page-appearance .dashboard-widget-setting .overview-widget-type { display: none; } + .dashboard-widget-setting { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + min-width: 0; + } +} -/* ACL DDNS input: keep the rule type separate from its hostname. */ -.acl-profile-rule { grid-template-columns: minmax(100px, .6fr) minmax(105px, .6fr) minmax(175px, 1.5fr) auto; } -@media (max-width: 640px) { .acl-profile-rule { grid-template-columns: 1fr; } } +.appearance-settings-grid.dashboard-appearance-grid { + grid-template-columns: minmax(0, 1fr); + gap: 12px; +} + +.appearance-settings-grid.dashboard-appearance-grid>.dashboard-density-setting { + display: grid; + grid-template-columns: minmax(145px, 200px) minmax(180px, 290px) minmax(0, 1fr); + align-items: center; + gap: 12px; +} + +.dashboard-density-setting>.fine, +.dashboard-density-setting>select { + min-width: 0; +} + +.dashboard-appearance-grid>.dashboard-widget-settings { + grid-column: 1/-1; + gap: 6px; +} + +.dashboard-widget-settings-head { + display: flex; + align-items: flex-start; + flex-wrap: wrap; + gap: 8px 14px; +} + +.dashboard-widget-settings-intro { + display: grid; + gap: 3px; + min-width: 0; + flex: 1 1 290px; +} + +.dashboard-widget-settings-controls { + display: flex; + flex: 0 0 auto; + align-items: center; + flex-wrap: wrap; + justify-content: flex-end; + gap: 6px; + margin-left: auto; +} + +.dashboard-widget-settings-controls>button { + min-height: 30px; + padding: 5px 10px; +} + +.dashboard-widget-settings-controls>.dashboard-widget-scroll-button { + min-width: 30px; + padding-inline: 6px; +} + +.dashboard-widget-settings-controls .dashboard-layout-open-settings { + margin: 0; + justify-self: auto; +} + +.dashboard-appearance-grid .dashboard-widget-list { + display: flex; + flex-wrap: nowrap; + align-items: stretch; + gap: 6px; + max-width: 100%; + min-width: 0; + margin: 0; + padding: 2px 1px 9px; + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: thin; + overscroll-behavior-x: contain; + scroll-snap-type: x proximity; + -webkit-overflow-scrolling: touch; +} + +.dashboard-appearance-grid .dashboard-widget-list>.dashboard-widget-setting { + flex: 0 0 226px; + width: 226px; + min-width: 226px; + max-width: 226px; + min-height: 33px; + box-sizing: border-box; + scroll-snap-align: start; +} + +.dashboard-appearance-grid .dashboard-widget-list.is-expanded { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(min(225px, 100%), 1fr)); + gap: 7px; + overflow: visible; + padding-bottom: 0; + scroll-snap-type: none; +} + +.dashboard-appearance-grid .dashboard-widget-list.is-expanded>.dashboard-widget-setting { + width: 100%; + max-width: none; + min-width: 0; +} + +#settings-page-appearance .dashboard-widget-setting .overview-widget-type, +.dashboard-appearance-grid .dashboard-widget-list.is-expanded~.dashboard-widget-scroll-hint { + display: none; +} + +@media(max-width:760px) { + .appearance-settings-grid.dashboard-appearance-grid>.dashboard-density-setting { + grid-template-columns: minmax(0, 1fr); + gap: 7px; + } + + .dashboard-widget-settings-head { + align-items: stretch; + } + + .dashboard-widget-settings-controls { + margin-left: 0; + justify-content: flex-start; + } + + .dashboard-appearance-grid .dashboard-widget-list>.dashboard-widget-setting { + flex-basis: 210px; + width: 210px; + min-width: 210px; + max-width: 210px; + } + + .dashboard-appearance-grid .dashboard-widget-list.is-expanded>.dashboard-widget-setting { + min-width: 0; + max-width: none; + } +} + +html[data-corner-style=square] { + --rp-radius-surface: 0px; + --rp-radius-control: 0px; +} + +html[data-corner-style=subtle] { + --rp-radius-surface: 5px; + --rp-radius-control: 4px; +} + +html[data-corner-style=rounded] { + --rp-radius-surface: 12px; + --rp-radius-control: 8px; +} + +html[data-corner-style=extra] { + --rp-radius-surface: 18px; + --rp-radius-control: 12px; +} + +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) { + border-radius: var(--rp-radius-surface) !important; +} + +html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-style=rounded], [data-corner-style=extra]) :is(button:not(.nav):not(.file-log-tab), input:not([type=checkbox]):not([type=radio]):not([type=range]), select, textarea, .pill, .vhost-backend-protocol-badge, .overview-widget-type, .badge, .quiet, .outline, .primary, .code, .command, .dashboard-widget-toggle, .settings-tabs button, .overview-inline-hidden-toggle, .overview-inline-restore) { + border-radius: var(--rp-radius-control) !important; +} + +.acl-profile-rule { + grid-template-columns: minmax(100px, .6fr) minmax(105px, .6fr) minmax(175px, 1.5fr) auto; +} + +@media(max-width:640px) { + .acl-profile-rule { + grid-template-columns: 1fr; + } +} -/* Latest HTTP version observed on a real request to each configured origin. - Protocol indicators share the Vhost policy-pills group but retain their - original colors and observed/unobserved states. */ .vhost-backend-protocol-badge { display: inline-flex; gap: 4px; @@ -10741,24 +11240,23 @@ html:is([data-corner-style="square"], [data-corner-style="subtle"], [data-corner max-width: 100%; min-width: 0; border: 1px solid var(--pill-line); + border-radius: 4px; background: var(--panel2); color: var(--muted); padding: 3px 7px; font: 10px var(--mono); overflow-wrap: anywhere; } -.vhost-backend-protocol-badge[data-observed="true"] { + +.vhost-backend-protocol-badge[data-observed=true] { color: var(--green); border-color: var(--pill-live-line); } -/* Phone Overview has one vertical scroll surface. Nested overflow on the - interface chart stack can steal a WebKit touch/momentum gesture and appear - to jump back to Process / Network. Desktop retains its bounded list. */ @container overview-canvas (max-width: 700px) { #panel-overview .network-interface-charts { max-height: none; overflow: visible; overscroll-behavior: auto; } -} +} \ No newline at end of file diff --git a/tools/checks/appearance_ux_0323_browser.py b/tools/checks/appearance_ux_0323_browser.py index 6db8347..539bb87 100755 --- a/tools/checks/appearance_ux_0323_browser.py +++ b/tools/checks/appearance_ux_0323_browser.py @@ -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') diff --git a/tools/checks/dashboard_appearance_ribbon_0326_browser.py b/tools/checks/dashboard_appearance_ribbon_0326_browser.py new file mode 100755 index 0000000..e82890a --- /dev/null +++ b/tools/checks/dashboard_appearance_ribbon_0326_browser.py @@ -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('
',0,heading) +section_end=html.index('
',heading)+len('') +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('
'+source+'
',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() diff --git a/tools/checks/dashboard_appearance_ribbon_0326_ui_test.mjs b/tools/checks/dashboard_appearance_ribbon_0326_ui_test.mjs new file mode 100644 index 0000000..ad23d52 --- /dev/null +++ b/tools/checks/dashboard_appearance_ribbon_0326_ui_test.mjs @@ -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'); diff --git a/tools/checks/stream_0320_visibility_regression.mjs b/tools/checks/stream_0320_visibility_regression.mjs index 8138bc1..b9448c9 100644 --- a/tools/checks/stream_0320_visibility_regression.mjs +++ b/tools/checks/stream_0320_visibility_regression.mjs @@ -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; } };