This commit is contained in:
Mateusz Gruszczyński committed 2026-11-08 06:20:03 +01:00
1 parent 5cb63f542c
commit 17e314e159
27 files changed
+1012 -286

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
# RustProxy source and documentation maintenance # RustProxy source and documentation maintenance
Current source version: **0.36.2**. The Cargo package and binary are named `rust-proxy`. Current source version: **0.36.3**. The Cargo package and binary are named `rust-proxy`.
Before editing, read [README.md](README.md), the relevant [documentation guide](docs/README.md) and the actual code and tests. Historical release experiments are not current specifications. Use the bundled `src/admin/openapi.json` for API routes and payloads; the source implementation is authoritative for runtime behavior. Before editing, read [README.md](README.md), the relevant [documentation guide](docs/README.md) and the actual code and tests. Historical release experiments are not current specifications. Use the bundled `src/admin/openapi.json` for API routes and payloads; the source implementation is authoritative for runtime behavior.
+17
View File
@@ -2,11 +2,28 @@
Release history focused on **features, settings, default behavior, compatibility and fixes**. Changes to internal code structure and preliminary performance experiments are intentionally omitted. Historical descriptions reflect their respective releases; consult the current documentation for the settings available in the latest release. Release history focused on **features, settings, default behavior, compatibility and fixes**. Changes to internal code structure and preliminary performance experiments are intentionally omitted. Historical descriptions reflect their respective releases; consult the current documentation for the settings available in the latest release.
## 0.36.3 — settings, cache and dashboard layout polish
- **More chart styles:** Settings → Appearance → Chart style now offers eight additional presets: **Sunset**, **Aurora**, **Ocean**, **Forest**, **Pastel**, **Terminal** (green phosphor, square lines), **Blueprint** (thin technical lines on a blue tint) and **Colorblind safe** (Okabe–Ito palette with dashed grid). The `chart_style` preference accepts the new values (`sunset`, `aurora`, `ocean`, `forest`, `pastel`, `terminal`, `blueprint`, `colorblind`); existing values and the default are unchanged.
- **Cache → Domains and effectiveness:** numeric columns are right-aligned with tabular digits, HIT / MISS / BYPASS counts use the same colours as the Dashboard cache bar, the hit ratio has a small meter, domain names are emphasised and decision-counter chips are quieter.
- **Docker vhosts:** collapsed service rows are compact on desktop (publish switch, name with image/ports, and health with actions in one row instead of a stacked column).
- **Cache and Static response transformation pages:** the Cache → General tab is split into *Behavior* and *Memory and limits* groups (the fallback TTL and bypass options moved up next to the other behavior switches), switch descriptions sit directly under their checkbox, the memory/transform preset cards have a compact header, and numeric fields use a responsive grid (up to four per row) instead of two fixed columns.
- **Settings layout:** Settings → Appearance now uses one-setting-per-row layout (label and help on the left, control on the right, checkboxes on the right edge) instead of an uneven two-column grid, section headings no longer duplicate their first setting (`Charts`, `Tables and logs`) and the small eyebrow labels were dropped. Sub-tabs (Rules & headers, Cache, HTTP connections, Logging, …) are a horizontal tab bar on desktop instead of a vertical stack of full-width buttons, and the main settings menu has thin separators between its groups.
- **Compact stat cards:** Dashboard cards that consist of a title, a value and one secondary line (Requests, Active exchanges, Payload transferred, Errors, Process memory, HTTP/2 requests, backend and stream cards, …) now show that line to the right of the value, which makes them about 20 px lower. WebSocket tunnels shows a plain `H1 · H2 · H3` split (full names in the tooltip) HTTP/2 shows `H2 connections` and HTTP/3 now shows `H3 connections` (active HTTP/3 connections); the labels were shortened in `langs/en_US.json` and `langs/pl_pl.json`.
- **WebSocket tunnels card:** the *Established* count moved from its own text line to the right of the main value (it wraps below only when the card is too narrow), so the card is one line shorter.
- **Cache outcomes bar:** the HIT / MISS / BYPASS / OTHER segments of the Dashboard cache bar now show their percentage inside the bar (bar height 12 → 20 px with tighter margins, so the card keeps its height). A label that does not fit a narrow segment is hidden automatically and re-evaluated on resize; the legend below keeps the exact count and percentage.
- **CSS cleanup:** removed orphaned panel styles: the legacy `body[data-density=…]` rules (46 selectors across 7 modules; density is controlled only by `html[data-table-density]` and `html[data-dashboard-density]`), the unused `.badge` branch of the corner-style selector list, references to undefined custom properties (`--radius`, `--surface`, `--line-strong`; their fallbacks are now inlined) and duplicated `.path-permissions-*` blocks. A static audit found no other unused classes, ids, keyframes or custom properties. Computed styles of all panels at 402 px and 1280 px (several corner/density/icon settings) are identical before and after; the bundle is about 3 KB smaller.
- **Upgrade notes:** interface-only release; no configuration or database migration is required. New interface labels were added to `langs/en_US.json` and `langs/pl_pl.json` (`appearanceChartsTitle`, `appearanceTablesTitle`, `cacheSectionBehavior`, `cacheSectionMemory`, `h3ActiveConnections`, `chartStyleSunset` … `chartStyleColorblind`) and the WebSocket / HTTP/2 labels were shortened.
## 0.36.2 — icon semantics review and optional logo animation ## 0.36.2 — icon semantics review and optional logo animation
- **SVG icon review:** every panel control was re-checked against its function. Fixes include **Unlock layout** (was the sign-out icon, now an open padlock), **Save and lock** (padlock), **Dashboard presets** (layers instead of edit), **Docker agents settings** (was a pin because "docker" matched "dock"), cache/transform memory presets (were the undo icon because "preset" matched "reset"), **Previous/Next** in Cache and Stored logs (chevrons instead of database/list), **Close** in the health and history dialogs (close instead of activity/history), revision history **Reload/Restore/Compare** (refresh/undo/compare), **Sign in** (sign-in instead of check), **Sign out all sessions** (sign-out instead of trash), **Run operation** (play), **Purge / Ban** and **Active BANs** (ban), **Clean cold cache** and **Clear** (eraser), **Download now** (download), token generation/rotation (key), **Repeated values / Count repeats** (hash), **Open Logs** (list), **Configure fields** (sliders), **About** (info), automatic renewal enable/disable (toggles) and Live logs **Pause/Resume** (pause/play). Expand/collapse toggles now follow their current state. - **SVG icon review:** every panel control was re-checked against its function. Fixes include **Unlock layout** (was the sign-out icon, now an open padlock), **Save and lock** (padlock), **Dashboard presets** (layers instead of edit), **Docker agents settings** (was a pin because "docker" matched "dock"), cache/transform memory presets (were the undo icon because "preset" matched "reset"), **Previous/Next** in Cache and Stored logs (chevrons instead of database/list), **Close** in the health and history dialogs (close instead of activity/history), revision history **Reload/Restore/Compare** (refresh/undo/compare), **Sign in** (sign-in instead of check), **Sign out all sessions** (sign-out instead of trash), **Run operation** (play), **Purge / Ban** and **Active BANs** (ban), **Clean cold cache** and **Clear** (eraser), **Download now** (download), token generation/rotation (key), **Repeated values / Count repeats** (hash), **Open Logs** (list), **Configure fields** (sliders), **About** (info), automatic renewal enable/disable (toggles) and Live logs **Pause/Resume** (pause/play). Expand/collapse toggles now follow their current state.
- **Distinct section icons:** Streams (transfer arrows), Listeners (plug), GeoIP updates (map pin) and Administration (users) no longer share icons with Vhosts, HTTP connections and Settings. The settings cog, pin, undo and sign-in/sign-out glyphs were redrawn to be more recognizable. - **Distinct section icons:** Streams (transfer arrows), Listeners (plug), GeoIP updates (map pin) and Administration (users) no longer share icons with Vhosts, HTTP connections and Settings. The settings cog, pin, undo and sign-in/sign-out glyphs were redrawn to be more recognizable.
- **Icon fixes:** English labels whose translation equals the key (for example **Edit**) no longer fall back to the generic placeholder icon; buttons labelled with a single symbol (`×`, `−`, `↑`, `←` …) keep the native glyph instead of showing a duplicate SVG; protocol badges and history calendar days no longer receive icons. - **Icon fixes:** English labels whose translation equals the key (for example **Edit**) no longer fall back to the generic placeholder icon; buttons labelled with a single symbol (`×`, `−`, `↑`, `←` …) keep the native glyph instead of showing a duplicate SVG; protocol badges and history calendar days no longer receive icons.
- **Mobile/Safari chart fixes:** with SVG icons enabled, the **Hide** / **Show** buttons of the Traffic-by-interface charts made the Overview jump and flicker on every refresh. Root causes: the chart lookup (`row.querySelector("svg")`) could return the button's icon instead of the chart (axis labels were then drawn inside the button); the periodic label update rebuilt the button and its icon each time; and descendant chart rules (`.network-interface-row svg`, `.stat-chart svg`, `.system-card svg` …) stretched icons to full chart size. Charts are now addressed with `svg:not(.ui-icon)`, the axis renderer ignores icons, refreshes update only the text node, chart rules exclude `.ui-icon`, and small buttons use an 11 px icon so their height never changes. A measured 28-refresh run kept the container height constant (previously a 13 px swing) with zero icon rebuilds.
- **Badge consistency:** the upstream protocol badges in the Vhost policy row (for example `origin · H2 compatible → H2`) now use the same geometry as the other policy pills (9 px mono, 1 px letter-spacing, 4×8 padding, transparent background, square corners in the original corner style, 20 px height); only the colour differs (muted = not observed, green = observed). The **Transformed** marker in the Cache objects table now uses pill typography (1 px letter-spacing, square corners). ACL picker tags (selected countries/IPs) now use the same chip look as host chips (square corners, 10 px mono, same padding and remove button) instead of a rounded pill with a different font. A style comparison of every pill/badge/chip family and of controls sharing a row found no further deviations besides intentional ones (ACL rule action labels that mimic form fields, quick enable/disable toggles, the sort-direction arrow).
- **Spacing review:** a rendered scan of every panel, settings page and dialog (402 px and 1280 px wide) found descriptive text and blocks that touched the next element. Added spacing for TLS policy (description → TLS 1.2/1.3 switches, PQC help → documentation link, which is now styled as a link), Rules & headers, HTTP connections and Logging sub-tabs, Cache and Transform live-activity cards, empty-state notes under tables, Logs footnote, Dashboard presets footer, GeoIP and cache-cleanup field groups, htaccess/PHP help dialogs and several settings help paragraphs. The rules add space only where the layout had none.
- **Mobile layout:** the mobile **Menu** button no longer shows two hamburger icons, and **System uptime** wraps inside the System load card instead of overflowing its right edge.
- **Logo animation:** new **Settings → Appearance → Interface → Animate logo** option (account-scoped `logo_animation`, **off by default**) plays short, subtle random effects on the sidebar logo; it pauses in hidden tabs and respects reduced-motion preferences. - **Logo animation:** new **Settings → Appearance → Interface → Animate logo** option (account-scoped `logo_animation`, **off by default**) plays short, subtle random effects on the sidebar logo; it pauses in hidden tabs and respects reduced-motion preferences.
- **Upgrade notes:** no configuration or database migration is required; `logo_animation` is an additive preference field that defaults to `false` for existing accounts. - **Upgrade notes:** no configuration or database migration is required; `logo_animation` is an additive preference field that defaults to `false` for existing accounts.
Generated
+1 -1
View File
@@ -1943,7 +1943,7 @@ dependencies = [
[[package]] [[package]]
name = "rust-proxy" name = "rust-proxy"
version = "0.36.2" version = "0.36.3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arc-swap", "arc-swap",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "rust-proxy" name = "rust-proxy"
version = "0.36.2" version = "0.36.3"
edition = "2021" edition = "2021"
rust-version = "1.99.1" rust-version = "1.99.1"
description = "RustProxy - independent reverse proxy with local administration" description = "RustProxy - independent reverse proxy with local administration"
+26 -26
View File
@@ -1,10 +1,10 @@
61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore 61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example 4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
e76e688874828e3743d03693b1d2872b9633b4db09909673786caf30f7cfe489 .gitignore e76e688874828e3743d03693b1d2872b9633b4db09909673786caf30f7cfe489 .gitignore
eb2e5fcc5cd2ab599f6bd898c99d5ce21d417b5e67b60755809224313b8287c9 AGENTS.md a68d2e8a774443f5ba359ba1362144162573e895829297f55e924dd6debd98a7 AGENTS.md
9c209f4fa88b9d2f61e85fdefadcdfa156099c3b23f767f17b07e6ff4a89e556 CHANGELOG.md 10eb102a2a819deb15f46dca91fb2c1cc5c6354ad757da34ebcfca8a6f325b10 CHANGELOG.md
1b57c672088350d2ce0ce81980313b8f10615cb1a235e341c2c64ae258c23a46 Cargo.lock 5d74dab9713870cfdf2d0220736a47e07f322529cf7321b06599953cbc7930c3 Cargo.lock
2a37b391ded04b9a675e8befc6e82c06db2a05bf1ab6f4cb2e8defec193e5d81 Cargo.toml ca01b36019447f539eb4f6fa2abd4125db2593c03989f46efacf5f3bc2f05928 Cargo.toml
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile 25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0 LICENSE 0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0 LICENSE
82d1dee2fc68158f0ea73259151461e64c8b18d807cd1fecfb1ed99dc30cf5f4 README.md 82d1dee2fc68158f0ea73259151461e64c8b18d807cd1fecfb1ed99dc30cf5f4 README.md
@@ -88,12 +88,12 @@ de6550ab8f48d556d752c252212c38ba65491fd224f972c10fbe552f91ed7520 integrations/o
45be151cd85fc2ef2f1fdc3003ace63381af262e73af8a685ab48cb697cc0de1 integrations/routeros-oci-build/.env.routeros.example 45be151cd85fc2ef2f1fdc3003ace63381af262e73af8a685ab48cb697cc0de1 integrations/routeros-oci-build/.env.routeros.example
11aa102de1089ecc375f20c4bd5420fcfd3b2ef23fe0c8bdf04953fcfaf40ef0 integrations/routeros-oci-build/Dockerfile.routeros 11aa102de1089ecc375f20c4bd5420fcfd3b2ef23fe0c8bdf04953fcfaf40ef0 integrations/routeros-oci-build/Dockerfile.routeros
7c7a3e51f43b43e1c962a4b0bb591f99c1f06977bf77f23719f7f867dfe7eb8e integrations/routeros-oci-build/Dockerfile.routeros.dockerignore 7c7a3e51f43b43e1c962a4b0bb591f99c1f06977bf77f23719f7f867dfe7eb8e integrations/routeros-oci-build/Dockerfile.routeros.dockerignore
1effe1bc8b04c5469401b8a2e8174e0aa0ba73f0c2db10f6a21e5ef7d5a8bc65 integrations/routeros-oci-build/README.md 06a2f64af5372b12eb9421ab2b42fe7d7ce1ea39dc076f85d5af24fe38e7cc74 integrations/routeros-oci-build/README.md
3f092a9ab80221eddd67be4d1a776acbc6e04c1eb6011b588280c28e83b2582f integrations/routeros-oci-build/build-routeros-oci.sh 3f092a9ab80221eddd67be4d1a776acbc6e04c1eb6011b588280c28e83b2582f integrations/routeros-oci-build/build-routeros-oci.sh
3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a integrations/routeros-oci-build/docker-entrypoint.sh 3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a integrations/routeros-oci-build/docker-entrypoint.sh
13189fa546c3488dddd8efc80c47f6da5241e5fc28b79cbfe795414ea05c7a4f langs/README.md 13189fa546c3488dddd8efc80c47f6da5241e5fc28b79cbfe795414ea05c7a4f langs/README.md
f28ec357bafa539be82be86340651458b1002f8865411fd95a83c186cbc80a8a langs/en_US.json 052717c1d67c2b33084dae9d0e6f24c3a3ec4f16f0b7871f2a58773529961415 langs/en_US.json
03d219fbbb4cc7902b4f3979ea91a46bc8ee3c8b1e68b59b94303cbba087dc25 langs/pl_pl.json 7bb35391fb52cdcce6f03c0610f67e08cfa1798fee345ee7b6edd64b041d3f9f langs/pl_pl.json
9664543e4d03422788a65e42f968c1f026c2235cb5be6fc3e0ad318997077538 make_zip.py 9664543e4d03422788a65e42f968c1f026c2235cb5be6fc3e0ad318997077538 make_zip.py
e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql
20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql 20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql
@@ -165,11 +165,11 @@ d58a2ed83319176de4e66790ed292447c9497ae031464ec55cfe27174acb639a src/admin/file
77b8c05645969834cc8a58e249a7b60a57d6c47ad1c5cd92ffa5e4a9b5a8e73c src/admin/file_logs_query.rs 77b8c05645969834cc8a58e249a7b60a57d6c47ad1c5cd92ffa5e4a9b5a8e73c src/admin/file_logs_query.rs
743bb7436f77e04015328a0e7e7d425e43ef8aa8d61467bde157e462c1dcdc2e src/admin/logging_api.rs 743bb7436f77e04015328a0e7e7d425e43ef8aa8d61467bde157e462c1dcdc2e src/admin/logging_api.rs
243f4a9c3fa99781e19c3952354d2632c0ce65e1dafac729567cbe3c177f94cb src/admin/mod.rs 243f4a9c3fa99781e19c3952354d2632c0ce65e1dafac729567cbe3c177f94cb src/admin/mod.rs
cf588dd17399a752b23c3e87ba3cb6cdce02b059a6479528da7402ff517ddcdc src/admin/openapi.json 4ffdef6e47ce31633b08e090477c289e719cd829eb4991d598824dcb909dc389 src/admin/openapi.json
f7a356109786635dd58cba590c71c796dbf36b47f25845190c75ee5c6a9fcb86 src/admin/operations.rs f7a356109786635dd58cba590c71c796dbf36b47f25845190c75ee5c6a9fcb86 src/admin/operations.rs
257d93a29d0bda96462bcf695aba8d0ec0f9412c379e066ec46ae8ee034e3a53 src/admin/panel_assets.rs 257d93a29d0bda96462bcf695aba8d0ec0f9412c379e066ec46ae8ee034e3a53 src/admin/panel_assets.rs
f5127855c2994d052678865df36d703b74f4a0afc633daf7718b10da18d26e37 src/admin/panel_content.rs f5127855c2994d052678865df36d703b74f4a0afc633daf7718b10da18d26e37 src/admin/panel_content.rs
35dd127a37db8f18a0ddfcacc342a3028ef4ddc0cb619fd9b13b12b2ff047e86 src/admin/preferences.rs 229efd2691e51755f402ee04bcd9b806e3d3176c39526b9f7c2381ce779a7ba5 src/admin/preferences.rs
35a7f99c7448c90b8637f4b3a7becca138f1f378fe5dc3f87122151525bb20f9 src/admin/preferences_columns.rs 35a7f99c7448c90b8637f4b3a7becca138f1f378fe5dc3f87122151525bb20f9 src/admin/preferences_columns.rs
a238feee9afce314b8f6f7257e5488a53c4d680f33450a4e3e460fccbe54de88 src/admin/preferences_tests.rs a238feee9afce314b8f6f7257e5488a53c4d680f33450a4e3e460fccbe54de88 src/admin/preferences_tests.rs
62ecbae5dee00aeeca3eb806e2605372471dfab971cde8f66f11e711df8fcea1 src/admin/public/mod.rs 62ecbae5dee00aeeca3eb806e2605372471dfab971cde8f66f11e711df8fcea1 src/admin/public/mod.rs
@@ -187,37 +187,37 @@ c18c369124368f58524652565102817ed69fd25b222b0c5e737ef7632b45dcfb src/admin/web/
86abf50f39862d821cd2e43ca425671aa85289272915132c5a9a5dd1fc4182e2 src/admin/web/api-tokens.js 86abf50f39862d821cd2e43ca425671aa85289272915132c5a9a5dd1fc4182e2 src/admin/web/api-tokens.js
faa9735c2a02746b2954bc4955eb609f94afebb685cd5521d2629640cea2777a src/admin/web/app/backend-dashboard.js faa9735c2a02746b2954bc4955eb609f94afebb685cd5521d2629640cea2777a src/admin/web/app/backend-dashboard.js
aed3aea4eef0011c6b51d39736782e7d1ec52662e422f40e0c7b9ddfc19d0378 src/admin/web/app/certificate-inspection.js aed3aea4eef0011c6b51d39736782e7d1ec52662e422f40e0c7b9ddfc19d0378 src/admin/web/app/certificate-inspection.js
1a4d40a86067493e75b47f2d735d50fa4d161344ce145d4e502b0225144ed45a src/admin/web/app/chart-scales.js f6b0533ffd6d0e6471519120c440619a7fb4b255854ba3dd23d81cb35cd21afb src/admin/web/app/chart-scales.js
e5b39b5592ba81292571033f26934ac0cd42bcdca7395e91094973cecb3eeaf8 src/admin/web/app/config-history.js e5b39b5592ba81292571033f26934ac0cd42bcdca7395e91094973cecb3eeaf8 src/admin/web/app/config-history.js
fcce3b0204ca2c679f5d8049d020619664a3c0a45889975681258d82ae329988 src/admin/web/app/core.js fcce3b0204ca2c679f5d8049d020619664a3c0a45889975681258d82ae329988 src/admin/web/app/core.js
d695ec90fb5c98c27d7ba1d2bebf54f9840985fc530efcec3fcaf48d74b36c6b src/admin/web/app/dashboard-inline-layout.js d695ec90fb5c98c27d7ba1d2bebf54f9840985fc530efcec3fcaf48d74b36c6b src/admin/web/app/dashboard-inline-layout.js
e4238322a985d3e8b7f2de94b7af31e1c718775590b2c7b3428b878cd43337b7 src/admin/web/app/dashboard-layout-editor.js e4238322a985d3e8b7f2de94b7af31e1c718775590b2c7b3428b878cd43337b7 src/admin/web/app/dashboard-layout-editor.js
a184c50e1ca6bb5a14954e31b15f09ff9978fb939064ca724851bbb4e661dc56 src/admin/web/app/dashboard-presets.js a184c50e1ca6bb5a14954e31b15f09ff9978fb939064ca724851bbb4e661dc56 src/admin/web/app/dashboard-presets.js
bd120d5ff23f9893ab666e0ef07f08dfb3348053351bc4f7d950647871b2f7f4 src/admin/web/app/icons.js 0be1024d9ff657044e04ed71f275cff3b432406d5fdcdeac726daaee54444abd src/admin/web/app/icons.js
e5a92fa1cb7e049082f0764b245560c41a9a34bcd8a9536fd872d9169fa5465d src/admin/web/app/logo-animation.js e5a92fa1cb7e049082f0764b245560c41a9a34bcd8a9536fd872d9169fa5465d src/admin/web/app/logo-animation.js
32aa11f041eafdf16e66adef6f5847a67413785224da2c19ecbf825996622aff src/admin/web/app/overview-extra-metrics.js 32aa11f041eafdf16e66adef6f5847a67413785224da2c19ecbf825996622aff src/admin/web/app/overview-extra-metrics.js
5e62cccf6a9507fe208e9f41bbd08453c3d8c6a6b83c56d93a2bb22550ea6e9a src/admin/web/app/overview-masonry.js 5e62cccf6a9507fe208e9f41bbd08453c3d8c6a6b83c56d93a2bb22550ea6e9a src/admin/web/app/overview-masonry.js
4d1d4760cdff44771c9d880a7c0da81d7233ef6e3a4868111a522703831a7a54 src/admin/web/app/overview-samples.js 4d1d4760cdff44771c9d880a7c0da81d7233ef6e3a4868111a522703831a7a54 src/admin/web/app/overview-samples.js
db9012546cae8c58fc2bcbee71c9a95956a13a6db9e6d126c6a3770d45a4e4d2 src/admin/web/app/overview-wide-metrics.js db9012546cae8c58fc2bcbee71c9a95956a13a6db9e6d126c6a3770d45a4e4d2 src/admin/web/app/overview-wide-metrics.js
bfd9fafb3112197e8f875dbf87a9a66c81aef0fe83687601a84a66f5e13f69bc src/admin/web/app/path-permissions.js bfd9fafb3112197e8f875dbf87a9a66c81aef0fe83687601a84a66f5e13f69bc src/admin/web/app/path-permissions.js
0c23e28d7799582d3a3a5835ed6c8e5868a3cac678b5be7e902652d0901404f3 src/admin/web/app/preferences.js e540119edc555cefbe5147d97e06527adf49c984ba7a4dd51dc67603d3a46041 src/admin/web/app/preferences.js
9e2b12d34ab1fb29716645720577ad49b16c03b46c482a03695e8f645d170e79 src/admin/web/app/rate-sampling.js 9e2b12d34ab1fb29716645720577ad49b16c03b46c482a03695e8f645d170e79 src/admin/web/app/rate-sampling.js
3b8e566d81a5b94ef21c73e7805ae128454f085554de9358040de266bcbdb0e0 src/admin/web/app/refresh.js 3b8e566d81a5b94ef21c73e7805ae128454f085554de9358040de266bcbdb0e0 src/admin/web/app/refresh.js
c6ae53c0e95e67b9165822813efc622001be5354c19bb221a52514221d1df23e src/admin/web/app/runtime.js 5d1e805787530644b8853bd7f48d0efb1dc3f4829ca8b11eaa80c57434b8841d src/admin/web/app/runtime.js
31201586925e6f4792071d7d03fb05dcc4a69a03c02a4e85eb0746f45af68074 src/admin/web/app/session-resume.js 31201586925e6f4792071d7d03fb05dcc4a69a03c02a4e85eb0746f45af68074 src/admin/web/app/session-resume.js
c7a18238ac426de4d1cf84a395ee4dc66410d60a57a3822695dc10adc668d956 src/admin/web/app/settings.js c7a18238ac426de4d1cf84a395ee4dc66410d60a57a3822695dc10adc668d956 src/admin/web/app/settings.js
b7ead49224a4ec67181d3f24f9d9634bd6d76756f826a4d5d993300637d1d8f3 src/admin/web/app/stream-dashboard.js b7ead49224a4ec67181d3f24f9d9634bd6d76756f826a4d5d993300637d1d8f3 src/admin/web/app/stream-dashboard.js
7de070cc8cdde45d7e0932259299e341e25b32c9ffbd87e4fcce934d0f090de4 src/admin/web/app/web-overview.js 7de070cc8cdde45d7e0932259299e341e25b32c9ffbd87e4fcce934d0f090de4 src/admin/web/app/web-overview.js
f5855db3327f51170a28114e07d207745b4d6c8822d88d24c1a81eb92f345b14 src/admin/web/cache-panel.js d330e3351a55561ceb36573b0df91a663ca4765c3165ea340b6ba95c28f23a9b src/admin/web/cache-panel.js
9f323507649910561518b63cf6632e81b3ecc4b09501ae53b635705f34ea6587 src/admin/web/certificate-api.js 9f323507649910561518b63cf6632e81b3ecc4b09501ae53b635705f34ea6587 src/admin/web/certificate-api.js
a09f060b47771c56e9b9e57f1b896a32a2f87f1fb33401ac76932a91d247c67e src/admin/web/css/00-tokens.css 4810137aa4217a86770617d442cf5dc6bec0d9f19e20bb2155e932a9a111cedf src/admin/web/css/00-tokens.css
422a8c04d6499f806516be1694f316a4881d7cc191072218ea851445e2493506 src/admin/web/css/10-base.css ec3ff1b7824a7f03ee6d6b7ae9b6e0cbdb300e4891d44f982b6c04b3870f5c2b src/admin/web/css/10-base.css
b8cf033180211969912cbdf1190f52129b3bf40cda4522f740d7630c8c081224 src/admin/web/css/20-shell.css 8a1996d37f138790c12a704deedde9de74f733e7dcbb6be816fd68c281e8e0a9 src/admin/web/css/20-shell.css
feca0078ca889900a75d08a4a241e1a8ec787c8f4de25933d4883aac955e8023 src/admin/web/css/30-overview.css 9495c1b9f6a2cc72c1d99b4ea8c3cf3706835cb4670b53d5d7d71ef41ad62006 src/admin/web/css/30-overview.css
dd82b5e3ead91c20bb08cf8d71bbba31ea8e3d108c417913c63409f3e34da7e1 src/admin/web/css/40-editors.css 2d703473f7b751b587c32af6954c838a3160e9efa448236b6abe291832486b72 src/admin/web/css/40-editors.css
4a3d733d76d5f707d58c301f4dd5416c2401df8b8013f8c8836d07de99697f61 src/admin/web/css/50-settings.css 87df062d7478176641364c07de396ca988a37b1a8bcb892b53cc578fe8471850 src/admin/web/css/50-settings.css
f8b515c63080c71ee647cfd8c36628271e8357b31cd84f00cf591e9b752b44fe src/admin/web/css/60-monitoring.css 606d460ec901a095c9537dca6a0e56ece76142fdb25c59490703e62691d60356 src/admin/web/css/60-monitoring.css
fb7cc4d733cb9a3e320a8cb95269ca047394dded8846120ffb50aaeaaab4d749 src/admin/web/css/70-responsive.css 8ea9eb91ca422699e8b4a0cba8b3dbf71be57fc99b10cedc48fb5ec04d6dd609 src/admin/web/css/70-responsive.css
8009afdb04d5170687816730cc930a22fead1fe0248835ca7e8de81d6907395d src/admin/web/css/README.md 8009afdb04d5170687816730cc930a22fead1fe0248835ca7e8de81d6907395d src/admin/web/css/README.md
8241bd9489dfed082863948829a44a49bf0a8e9cd63c55809655109e393e0918 src/admin/web/css/manifest.json 8241bd9489dfed082863948829a44a49bf0a8e9cd63c55809655109e393e0918 src/admin/web/css/manifest.json
ee51ab0912e0ade6a3e1c2ea6f533bb2f65a6d3e6ec9087e25b9f0df009c19b2 src/admin/web/dashboard-defaults.json ee51ab0912e0ade6a3e1c2ea6f533bb2f65a6d3e6ec9087e25b9f0df009c19b2 src/admin/web/dashboard-defaults.json
@@ -244,14 +244,14 @@ e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg 0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
7cbdef9192a9dee1f8eac42b85f52b3190f7f6efae50004aa7ad116a910e4036 src/admin/web/file-logs.js 7cbdef9192a9dee1f8eac42b85f52b3190f7f6efae50004aa7ad116a910e4036 src/admin/web/file-logs.js
3f0525ca1d4e088ccd4988b8a117b26a8cf8c12213b24c37081d41b68d2b0ff2 src/admin/web/host404.js 3f0525ca1d4e088ccd4988b8a117b26a8cf8c12213b24c37081d41b68d2b0ff2 src/admin/web/host404.js
dbb063615c93ae0fd247b4e9dfbc92c772bb7bdf182fa1080c0054c8f22c6b97 src/admin/web/index.html feca752133d6a1300c5161af91124d5841e803a77f62231159b4b82b22db9dd9 src/admin/web/index.html
7979c490b7fcfcd8f5e52e99fcf7757d78509337a625341c98f62c6da50da374 src/admin/web/integrations.js 7979c490b7fcfcd8f5e52e99fcf7757d78509337a625341c98f62c6da50da374 src/admin/web/integrations.js
7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json 7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json
ac9f667273918e18406df06cb2613c923f50a88897693a03a18f500e22dc6ec2 src/admin/web/login.html ac9f667273918e18406df06cb2613c923f50a88897693a03a18f500e22dc6ec2 src/admin/web/login.html
c2e5f554810b8e34fb47361fb90a01073a2e332ee26d82e030956fad3ec1f83e src/admin/web/login.js c2e5f554810b8e34fb47361fb90a01073a2e332ee26d82e030956fad3ec1f83e src/admin/web/login.js
16d848a25f744e55d18757ce82cfd80e85bcc5b1777342c41e7bd732e13d8913 src/admin/web/management.js 16d848a25f744e55d18757ce82cfd80e85bcc5b1777342c41e7bd732e13d8913 src/admin/web/management.js
1f3d10e389c9c20e6eb019509fa6e82b50c6b43bbf988c1210a4d42bb73985d0 src/admin/web/partials/overview-charts.html 1f3d10e389c9c20e6eb019509fa6e82b50c6b43bbf988c1210a4d42bb73985d0 src/admin/web/partials/overview-charts.html
95ba5fa72c04628f211ca216931a895ddf2059a791fa9d17c74812acd6c874d0 src/admin/web/partials/overview-metrics.html 32b3f9b3ce9d1de928d2f23265d09b65a135fd045d1d24b98e830922be21432f src/admin/web/partials/overview-metrics.html
3928afce2ed6d5f2d3e29475dd29e4c3bd3c6df180ad5119beaff0c7a58e3b3b src/admin/web/prometheus-token.js 3928afce2ed6d5f2d3e29475dd29e4c3bd3c6df180ad5119beaff0c7a58e3b3b src/admin/web/prometheus-token.js
2d491fe9a547fe109dc5db6a74806d50b08c3daca15a6622c79c9fea33b10c63 src/admin/web/redirects.js 2d491fe9a547fe109dc5db6a74806d50b08c3daca15a6622c79c9fea33b10c63 src/admin/web/redirects.js
76a01f590d49649dcb1f554c221290919b2a8438e6001737f3c096fa6daec74d src/admin/web/settings.js 76a01f590d49649dcb1f554c221290919b2a8438e6001737f3c096fa6daec74d src/admin/web/settings.js
@@ -559,9 +559,9 @@ b791762b4ab7a43c3be19df84e523faf3dfb4df055f66d02178f93edb60d2204 src/web_server
714418c86ab241020a561fcf662a8fe324a6a70cc2fd3a313c56d7939c14b9e8 src/web_server/tests.rs 714418c86ab241020a561fcf662a8fe324a6a70cc2fd3a313c56d7939c14b9e8 src/web_server/tests.rs
f00743af2cf4a5952caef7a1dcf68e7817b5599a252a9973e1c22045517c0b0e tests/fixtures/argon2id.json f00743af2cf4a5952caef7a1dcf68e7817b5599a252a9973e1c22045517c0b0e tests/fixtures/argon2id.json
102b83171a6429b6375d8001d6c5294702385cd58ee55727cb657b1f3566e877 tests/panel_icons_browser.py 102b83171a6429b6375d8001d6c5294702385cd58ee55727cb657b1f3566e877 tests/panel_icons_browser.py
c968beeda4a1bf626991deeb9134e43d36d44fc922cb1c1c27221cf2d5a5c2c3 tests/panel_icons_contract.js 52e61c389a60d147184ee76925cac76e454f8f2e3cb91039bb06e17787ab900f tests/panel_icons_contract.js
01af3008be27fabb486c141c67099af104f2725cc56f4c656aea9741a3c150cd tests/panel_interface_contract.py 01af3008be27fabb486c141c67099af104f2725cc56f4c656aea9741a3c150cd tests/panel_interface_contract.py
40d7fa6e0b8111c0c9815cff43f235984c49b12ffd3947eb2542400fc807b621 tests/panel_refresh_contract.js d6731b46a5a22605cd5bcf996d9f5dbb921aaa455395ed0098bc808c6428c514 tests/panel_refresh_contract.js
e24a0f84f1bfc46964df865ac387d651f7b384d781cbc8f3a9a5b0b60f3d69ca tests/vhost_auth_selection.js e24a0f84f1bfc46964df865ac387d651f7b384d781cbc8f3a9a5b0b60f3d69ca tests/vhost_auth_selection.js
eca68a07c2b1e45fdb8f512294f5a432db3ae5a6aec8fb200b47f155cd9a31d6 tools/README.md eca68a07c2b1e45fdb8f512294f5a432db3ae5a6aec8fb200b47f155cd9a31d6 tools/README.md
7c30c280d0b0a8bf855eeffab583ca34dc161255f7ef6124148aa979c17c26d3 tools/dev/memory_load.py 7c30c280d0b0a8bf855eeffab583ca34dc161255f7ef6124148aa979c17c26d3 tools/dev/memory_load.py
+2 -2
View File
@@ -77,10 +77,10 @@ ENV_FILE=integrations/routeros-oci-build/.env \
bash integrations/routeros-oci-build/build-routeros-oci.sh push bash integrations/routeros-oci-build/build-routeros-oci.sh push
``` ```
For example, with RustProxy version `0.36.2` and the defaults this publishes: For example, with RustProxy version `0.36.3` and the defaults this publishes:
```text ```text
zot.example.net/network/rustproxy:0.36.2 zot.example.net/network/rustproxy:0.36.3
zot.example.net/network/rustproxy:latest zot.example.net/network/rustproxy:latest
zot.example.net/network/rustproxy:routeros zot.example.net/network/rustproxy:routeros
``` ```
+17 -4
View File
@@ -253,7 +253,8 @@
"certificateSafety": "Certificates secure TLS connections, not routing. Assign a certificate to a vhost. Changes affect new handshakes.", "certificateSafety": "Certificates secure TLS connections, not routing. Assign a certificate to a vhost. Changes affect new handshakes.",
"wsActive": "WebSocket tunnels", "wsActive": "WebSocket tunnels",
"h2Requests": "HTTP/2 requests", "h2Requests": "HTTP/2 requests",
"h2ActiveConnections": "Active H2 connections", "h2ActiveConnections": "H2 connections",
"h3ActiveConnections": "H3 connections",
"tlsFailures": "TLS handshake errors", "tlsFailures": "TLS handshake errors",
"tlsListener": "TLS listener", "tlsListener": "TLS listener",
"notEnabled": "Disabled", "notEnabled": "Disabled",
@@ -1877,9 +1878,9 @@
"tunnelKillReasonDefault": "admin panel", "tunnelKillReasonDefault": "admin panel",
"listenerH2c": "h2c cleartext HTTP/2 (prior knowledge + HTTP/1.1 Upgrade)", "listenerH2c": "h2c cleartext HTTP/2 (prior knowledge + HTTP/1.1 Upgrade)",
"vhWebsocketRfc8441": "RFC 8441 Extended CONNECT over HTTP/2", "vhWebsocketRfc8441": "RFC 8441 Extended CONNECT over HTTP/2",
"wsH1": "H1 Upgrade", "wsH1": "H1",
"wsH2Rfc8441": "H2 RFC 8441", "wsH2Rfc8441": "H2",
"wsH3Rfc9220": "H3 RFC 9220", "wsH3Rfc9220": "H3",
"wsEstablished": "Established", "wsEstablished": "Established",
"httpProtocolMix": "HTTP protocol mix", "httpProtocolMix": "HTTP protocol mix",
"wsProtocolMix": "WebSocket protocol mix", "wsProtocolMix": "WebSocket protocol mix",
@@ -2360,6 +2361,14 @@
"chartStyleMonochrome": "Monochrome", "chartStyleMonochrome": "Monochrome",
"chartStyleWarm": "Warm", "chartStyleWarm": "Warm",
"chartStyleCool": "Cool", "chartStyleCool": "Cool",
"chartStyleSunset": "Sunset",
"chartStyleAurora": "Aurora",
"chartStyleOcean": "Ocean",
"chartStyleForest": "Forest",
"chartStylePastel": "Pastel",
"chartStyleTerminal": "Terminal",
"chartStyleBlueprint": "Blueprint",
"chartStyleColorblind": "Colorblind safe",
"settingsSearch": "Find settings", "settingsSearch": "Find settings",
"settingsSearchPlaceholder": "Search settings, sections and options...", "settingsSearchPlaceholder": "Search settings, sections and options...",
"settingsSearchResults": "Matching sections", "settingsSearchResults": "Matching sections",
@@ -2368,6 +2377,10 @@
"appearanceInterfaceSection": "INTERFACE", "appearanceInterfaceSection": "INTERFACE",
"appearanceInterfaceTitle": "Interface", "appearanceInterfaceTitle": "Interface",
"appearanceChartsSection": "CHARTS", "appearanceChartsSection": "CHARTS",
"cacheSectionBehavior": "Behavior",
"cacheSectionMemory": "Memory and limits",
"appearanceChartsTitle": "Charts",
"appearanceTablesTitle": "Tables and logs",
"appearanceDataSection": "TABLES / LOGS", "appearanceDataSection": "TABLES / LOGS",
"appearanceDashboardSection": "DASHBOARD", "appearanceDashboardSection": "DASHBOARD",
"resetSection": "Reset section", "resetSection": "Reset section",
+18 -5
View File
@@ -253,7 +253,8 @@
"certificateSafety": "Certyfikaty samopodpisane służą do testów. Certyfikat nie tworzy trasy. Zmiana dotyczy nowych połączeń TLS.", "certificateSafety": "Certyfikaty samopodpisane służą do testów. Certyfikat nie tworzy trasy. Zmiana dotyczy nowych połączeń TLS.",
"wsActive": "Tunele WebSocket", "wsActive": "Tunele WebSocket",
"h2Requests": "Żądania HTTP/2", "h2Requests": "Żądania HTTP/2",
"h2ActiveConnections": "Aktywne połączenia H2", "h2ActiveConnections": "Połączenia H2",
"h3ActiveConnections": "Połączenia H3",
"tlsFailures": "Błędy handshake TLS", "tlsFailures": "Błędy handshake TLS",
"tlsListener": "Listener TLS", "tlsListener": "Listener TLS",
"notEnabled": "Wyłączony", "notEnabled": "Wyłączony",
@@ -1877,9 +1878,9 @@
"tunnelKillReasonDefault": "panel administracyjny", "tunnelKillReasonDefault": "panel administracyjny",
"listenerH2c": "h2c HTTP/2 bez TLS (prior knowledge + Upgrade z HTTP/1.1)", "listenerH2c": "h2c HTTP/2 bez TLS (prior knowledge + Upgrade z HTTP/1.1)",
"vhWebsocketRfc8441": "RFC 8441 Extended CONNECT przez HTTP/2", "vhWebsocketRfc8441": "RFC 8441 Extended CONNECT przez HTTP/2",
"wsH1": "H1 Upgrade", "wsH1": "H1",
"wsH2Rfc8441": "H2 RFC 8441", "wsH2Rfc8441": "H2",
"wsH3Rfc9220": "H3 RFC 9220", "wsH3Rfc9220": "H3",
"wsEstablished": "Utworzone", "wsEstablished": "Utworzone",
"httpProtocolMix": "Miks protokołów HTTP", "httpProtocolMix": "Miks protokołów HTTP",
"wsProtocolMix": "Miks protokołów WebSocket", "wsProtocolMix": "Miks protokołów WebSocket",
@@ -2360,6 +2361,14 @@
"chartStyleMonochrome": "Monochromatyczny", "chartStyleMonochrome": "Monochromatyczny",
"chartStyleWarm": "Ciepły", "chartStyleWarm": "Ciepły",
"chartStyleCool": "Chłodny", "chartStyleCool": "Chłodny",
"chartStyleSunset": "Zachód słońca",
"chartStyleAurora": "Zorza",
"chartStyleOcean": "Ocean",
"chartStyleForest": "Las",
"chartStylePastel": "Pastelowy",
"chartStyleTerminal": "Terminal",
"chartStyleBlueprint": "Rysunek techniczny",
"chartStyleColorblind": "Przyjazny daltonistom",
"settingsSearch": "Znajdź ustawienia", "settingsSearch": "Znajdź ustawienia",
"settingsSearchPlaceholder": "Szukaj ustawień, sekcji i opcji...", "settingsSearchPlaceholder": "Szukaj ustawień, sekcji i opcji...",
"settingsSearchResults": "Pasujące sekcje", "settingsSearchResults": "Pasujące sekcje",
@@ -2368,6 +2377,10 @@
"appearanceInterfaceSection": "INTERFEJS", "appearanceInterfaceSection": "INTERFEJS",
"appearanceInterfaceTitle": "Interfejs", "appearanceInterfaceTitle": "Interfejs",
"appearanceChartsSection": "WYKRESY", "appearanceChartsSection": "WYKRESY",
"cacheSectionBehavior": "Działanie",
"cacheSectionMemory": "Pamięć i limity",
"appearanceChartsTitle": "Wykresy",
"appearanceTablesTitle": "Tabele i logi",
"appearanceDataSection": "TABELE / LOGI", "appearanceDataSection": "TABELE / LOGI",
"appearanceDashboardSection": "DASHBOARD", "appearanceDashboardSection": "DASHBOARD",
"resetSection": "Resetuj sekcję", "resetSection": "Resetuj sekcję",
@@ -2645,7 +2658,7 @@
"backendWidgetInFlight": "W toku", "backendWidgetInFlight": "W toku",
"backendWidgetErrorsRate": "Błędy/s", "backendWidgetErrorsRate": "Błędy/s",
"backendWidgetErrorPercent": "Błędne żądania w ostatnim okresie", "backendWidgetErrorPercent": "Błędne żądania w ostatnim okresie",
"backendWidgetProbeCount": "Zdrowe źródła", "backendWidgetProbeCount": "Zdrowe backendy",
"backendWidgetH2c": "Backendy h2c", "backendWidgetH2c": "Backendy h2c",
"backendWidgetModeUnknown": "Nieznane", "backendWidgetModeUnknown": "Nieznane",
"vhProtocolCompatibilityWarning": "Tryb zgodności: zapisane wcześniej backendy HTTPS HTTP/2 zachowują fallback TLS H2/H1. Wybierz „Tylko H2”, jeżeli HTTP/1.1 ma być odrzucony.", "vhProtocolCompatibilityWarning": "Tryb zgodności: zapisane wcześniej backendy HTTPS HTTP/2 zachowują fallback TLS H2/H1. Wybierz „Tylko H2”, jeżeli HTTP/1.1 ma być odrzucony.",
+10 -2
View File
@@ -2,7 +2,7 @@
"openapi": "3.0.3", "openapi": "3.0.3",
"info": { "info": {
"title": "RustProxy Administration API", "title": "RustProxy Administration API",
"version": "0.36.2", "version": "0.36.3",
"description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust. The API also permits explicitly scoped GeneralApiBearer tokens on a narrow route allowlist, with RO/RW and source CIDR checks; API-token management and Prometheus-token management are browser-session-only." "description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust. The API also permits explicitly scoped GeneralApiBearer tokens on a narrow route allowlist, with RO/RW and source CIDR checks; API-token management and Prometheus-token management are browser-session-only."
}, },
"servers": [ "servers": [
@@ -3812,7 +3812,15 @@
"neon", "neon",
"monochrome", "monochrome",
"warm", "warm",
"cool" "cool",
"sunset",
"aurora",
"ocean",
"forest",
"pastel",
"terminal",
"blueprint",
"colorblind"
], ],
"default": "default", "default": "default",
"description": "Account-scoped visual presentation preset applied consistently to administration charts." "description": "Account-scoped visual presentation preset applied consistently to administration charts."
+8
View File
@@ -303,6 +303,14 @@ fn validate_admin_preferences(preferences: &AdminPreferences) -> Result<(), ApiE
| "monochrome" | "monochrome"
| "warm" | "warm"
| "cool" | "cool"
| "sunset"
| "aurora"
| "ocean"
| "forest"
| "pastel"
| "terminal"
| "blueprint"
| "colorblind"
) { ) {
return Err(ApiError( return Err(ApiError(
StatusCode::UNPROCESSABLE_ENTITY, StatusCode::UNPROCESSABLE_ENTITY,
+2 -2
View File
@@ -115,7 +115,7 @@ window.rustProxyChartScale = (() => {
{ top = 0.08, bottom = 0.92, clipped = false, seriesScales = null } = {}) { { top = 0.08, bottom = 0.92, clipped = false, seriesScales = null } = {}) {
const node = typeof target === "string" ? document.getElementById(target) : target; const node = typeof target === "string" ? document.getElementById(target) : target;
const svg = node?.tagName?.toLowerCase() === "svg" ? node : node?.ownerSVGElement; const svg = node?.tagName?.toLowerCase() === "svg" ? node : node?.ownerSVGElement;
if (!svg) return; if (!svg || svg.classList.contains("ui-icon")) return;
svg.rustProxyAxisData = { svg.rustProxyAxisData = {
peak: Number.isFinite(peakValue) && peakValue > 0 ? peakValue : 1, peak: Number.isFinite(peakValue) && peakValue > 0 ? peakValue : 1,
unit, count: Number.isInteger(count) && count > 0 ? count : 0, unit, count: Number.isInteger(count) && count > 0 ? count : 0,
@@ -127,7 +127,7 @@ window.rustProxyChartScale = (() => {
} }
function refreshAxes() { function refreshAxes() {
const show = document.documentElement.dataset.chartAxes === "true"; const show = document.documentElement.dataset.chartAxes === "true";
for (const svg of document.querySelectorAll("svg")) { for (const svg of document.querySelectorAll("svg:not(.ui-icon)")) {
if (show && svg.rustProxyAxisData) renderAxes(svg); if (show && svg.rustProxyAxisData) renderAxes(svg);
else if (!show && svg.parentElement?.classList.contains("rp-chart-axis-host")) { else if (!show && svg.parentElement?.classList.contains("rp-chart-axis-host")) {
const host = svg.parentElement; const host = svg.parentElement;
+1 -1
View File
@@ -168,7 +168,7 @@ function uiIconForTarget(target) {
if (manual && UI_ICON_SHAPES[manual]) return manual; if (manual && UI_ICON_SHAPES[manual]) return manual;
if (target.matches(".chart-style-choice")) return null; if (target.matches(".chart-style-choice")) return null;
if (target.matches(".overview-inline-grip, .overview-inline-narrow, .overview-inline-widen, .overview-inline-up, .overview-inline-down, .overview-inline-hide, .overview-layout-narrow, .overview-layout-widen, .overview-layout-up, .overview-layout-down, .overview-layout-hide")) return null; if (target.matches(".overview-inline-grip, .overview-inline-narrow, .overview-inline-widen, .overview-inline-up, .overview-inline-down, .overview-inline-hide, .overview-layout-narrow, .overview-layout-widen, .overview-layout-up, .overview-layout-down, .overview-layout-hide")) return null;
if (target.matches("#tunnels-open, .vhost-backend-protocol-badge, .config-history-day, .draft-dock-toggle")) return null; if (target.matches("#tunnels-open, .vhost-backend-protocol-badge, .config-history-day, .draft-dock-toggle, .mobile-menu-toggle")) return null;
if (UI_GLYPH_LABEL.test(String(target.textContent || "").trim())) return null; if (UI_GLYPH_LABEL.test(String(target.textContent || "").trim())) return null;
if (UI_ID_ICONS[target.id]) return UI_ID_ICONS[target.id]; if (UI_ID_ICONS[target.id]) return UI_ID_ICONS[target.id];
if (target.matches("#web-service-preset-apply, [data-cache-memory-preset], [data-cache-cold-preset], [data-transform-preset]")) return "wand"; if (target.matches("#web-service-preset-apply, [data-cache-memory-preset], [data-cache-cold-preset], [data-transform-preset]")) return "wand";
+2 -2
View File
@@ -1,6 +1,6 @@
const ADMIN_THEMES = new Set(["dark", "ultra_dark", "light", "cream"]); const ADMIN_THEMES = new Set(["dark", "ultra_dark", "light", "cream"]);
const ADMIN_TIME_FORMATS = new Set(["24h", "12h"]); const ADMIN_TIME_FORMATS = new Set(["24h", "12h"]);
const ADMIN_CHART_STYLES = new Set(["default", "soft", "technical", "high_contrast", "neon", "monochrome", "warm", "cool"]); const ADMIN_CHART_STYLES = new Set(["default", "soft", "technical", "high_contrast", "neon", "monochrome", "warm", "cool", "sunset", "aurora", "ocean", "forest", "pastel", "terminal", "blueprint", "colorblind"]);
const ADMIN_DENSITIES = new Set(["normal", "compact", "ultra"]); const ADMIN_DENSITIES = new Set(["normal", "compact", "ultra"]);
const ADMIN_BUTTON_STYLES = new Set(["normal", "compact"]); const ADMIN_BUTTON_STYLES = new Set(["normal", "compact"]);
const ADMIN_CORNER_STYLES = new Set(["default", "square", "subtle", "rounded", "extra"]); const ADMIN_CORNER_STYLES = new Set(["default", "square", "subtle", "rounded", "extra"]);
@@ -16,7 +16,7 @@ const ADMIN_APPEARANCE_DEFAULTS = Object.freeze({
chart_axes: false, interface_icons: false, logo_animation: false, chart_axes: false, interface_icons: false, logo_animation: false,
button_style: "normal", corner_style: "default", table_density: "normal", dashboard_density: ADMIN_DASHBOARD_DEFAULTS.dashboard_density, dashboard_widgets: [...ADMIN_DASHBOARD_WIDGETS], dashboard_hidden_widgets: [...ADMIN_DEFAULT_HIDDEN_WIDGETS], dashboard_widget_sizes: null, button_style: "normal", corner_style: "default", table_density: "normal", dashboard_density: ADMIN_DASHBOARD_DEFAULTS.dashboard_density, dashboard_widgets: [...ADMIN_DASHBOARD_WIDGETS], dashboard_hidden_widgets: [...ADMIN_DEFAULT_HIDDEN_WIDGETS], dashboard_widget_sizes: null,
}); });
const CHART_STYLE_KEYS = Object.freeze({ default: "chartStyleDefault", soft: "chartStyleSoft", technical: "chartStyleTechnical", high_contrast: "chartStyleHighContrast", neon: "chartStyleNeon", monochrome: "chartStyleMonochrome", warm: "chartStyleWarm", cool: "chartStyleCool" }); const CHART_STYLE_KEYS = Object.freeze({ default: "chartStyleDefault", soft: "chartStyleSoft", technical: "chartStyleTechnical", high_contrast: "chartStyleHighContrast", neon: "chartStyleNeon", monochrome: "chartStyleMonochrome", warm: "chartStyleWarm", cool: "chartStyleCool", sunset: "chartStyleSunset", aurora: "chartStyleAurora", ocean: "chartStyleOcean", forest: "chartStyleForest", pastel: "chartStylePastel", terminal: "chartStyleTerminal", blueprint: "chartStyleBlueprint", colorblind: "chartStyleColorblind" });
let adminPreferences = null, adminPreferencesLoading = null, adminPreferencesWrite = Promise.resolve(), adminPreferencesUnavailable = false; let adminPreferences = null, adminPreferencesLoading = null, adminPreferencesWrite = Promise.resolve(), adminPreferencesUnavailable = false;
function normalizeAdminTheme(value) { return ADMIN_THEMES.has(value) ? value : "dark"; } function normalizeAdminTheme(value) { return ADMIN_THEMES.has(value) ? value : "dark"; }
+33 -3
View File
@@ -282,6 +282,15 @@ function svgNode(tag, attrs = {}) {
function setChartText(node, value) { function setChartText(node, value) {
const text = String(value); const text = String(value);
// Controls decorated by the optional SVG icons hold [svg.ui-icon, text]; keep the icon
// and update only the text node so periodic refreshes do not rebuild the button.
const icon = node.querySelector(":scope > svg.ui-icon");
if (icon) {
const last = node.lastChild;
if (last?.nodeType === 3) { if (last.data !== text) last.data = text; }
else node.append(text);
return;
}
if (node.firstChild?.nodeType === 3 && node.firstChild === node.lastChild) { if (node.firstChild?.nodeType === 3 && node.firstChild === node.lastChild) {
if (node.firstChild.data !== text) node.firstChild.data = text; if (node.firstChild.data !== text) node.firstChild.data = text;
} else if (node.textContent !== text) { } else if (node.textContent !== text) {
@@ -346,9 +355,12 @@ function renderNetworkInterfaceCharts(interfaces, appendSample) {
setChartText(row.querySelector(".network-interface-hide"), t("hideMetric")); setChartText(row.querySelector(".network-interface-hide"), t("hideMetric"));
row.querySelector(".network-rx-line").setAttribute("points", sparklinePoints(history.rx, 300, 60, 5, peak) || "0,55 300,55"); row.querySelector(".network-rx-line").setAttribute("points", sparklinePoints(history.rx, 300, 60, 5, peak) || "0,55 300,55");
row.querySelector(".network-tx-line").setAttribute("points", sparklinePoints(history.tx, 300, 60, 5, peak) || "0,55 300,55"); row.querySelector(".network-tx-line").setAttribute("points", sparklinePoints(history.tx, 300, 60, 5, peak) || "0,55 300,55");
window.rustProxyChartScale.axes(row.querySelector("svg"), peak, "bytes/s", history.rx.length, // The Hide button precedes the chart and may carry an injected svg.ui-icon:
// address the chart explicitly so axes/labels never attach to the icon.
const chart = row.querySelector("svg:not(.ui-icon)");
window.rustProxyChartScale.axes(chart, peak, "bytes/s", history.rx.length,
{ top: 5 / 60, bottom: 55 / 60 }); { top: 5 / 60, bottom: 55 / 60 });
row.querySelector("svg").setAttribute("aria-label", `${name} ${t("networkInterfacesHistory")}`); chart.setAttribute("aria-label", `${name} ${t("networkInterfacesHistory")}`);
children.push(row); children.push(row);
} }
const hidden = current.filter(item => item?.name && hiddenNetworkInterfaces.has(item.name)); const hidden = current.filter(item => item?.name && hiddenNetworkInterfaces.has(item.name));
@@ -395,15 +407,32 @@ function cacheOutcomeMix(totals) {
const counts = [hits, misses, bypasses, requests - cacheTotal]; const counts = [hits, misses, bypasses, requests - cacheTotal];
return { counts, percentages: counts.map(value => value / requests * 100), requests }; return { counts, percentages: counts.map(value => value / requests * 100), requests };
} }
// Percent labels live inside the bar segments; a label that does not fit is hidden
// (the legend below always carries the exact value).
function fitCacheMixLabels() {
const bar = $("cache-mix-bar");
if (!bar) return;
bar.querySelectorAll(":scope > span").forEach(segment => {
segment.classList.remove("is-tight");
segment.classList.toggle("is-tight", segment.textContent !== "" && segment.scrollWidth > segment.clientWidth);
});
if (!bar.dataset.fitObserved && typeof ResizeObserver === "function") {
bar.dataset.fitObserved = "1";
new ResizeObserver(() => fitCacheMixLabels()).observe(bar);
}
}
function renderCacheMix(mix, totalRequests) { function renderCacheMix(mix, totalRequests) {
const names = ["hit", "miss", "bypass", "other"]; const names = ["hit", "miss", "bypass", "other"];
const unavailable = t(totalRequests === 0 ? "cacheMixWaiting" : "cacheMixInvalid"); const unavailable = t(totalRequests === 0 ? "cacheMixWaiting" : "cacheMixInvalid");
names.forEach((name, i) => { names.forEach((name, i) => {
const pct = mix ? mix.percentages[i] : 0; const pct = mix ? mix.percentages[i] : 0;
$(`cache-mix-${name}`).style.width = `${pct}%`; const segment = $(`cache-mix-${name}`);
segment.style.width = `${pct}%`;
segment.textContent = mix && pct > 0 ? `${fmt(pct, pct < 10 ? 1 : 0)}%` : "";
$(`cache-mix-${name}-count`).textContent = mix ? fmt(mix.counts[i]) : "-"; $(`cache-mix-${name}-count`).textContent = mix ? fmt(mix.counts[i]) : "-";
$(`cache-mix-${name}-percent`).textContent = mix ? `${fmt(pct, 1)}%` : "-"; $(`cache-mix-${name}-percent`).textContent = mix ? `${fmt(pct, 1)}%` : "-";
}); });
fitCacheMixLabels();
$("cache-mix-headline").textContent = mix ? `${fmt(mix.percentages[0], 1)}% HIT` : "-"; $("cache-mix-headline").textContent = mix ? `${fmt(mix.percentages[0], 1)}% HIT` : "-";
const caption = $("cache-mix-caption"); const caption = $("cache-mix-caption");
const validTotal = Number.isSafeInteger(totalRequests) && totalRequests >= 0; const validTotal = Number.isSafeInteger(totalRequests) && totalRequests >= 0;
@@ -424,6 +453,7 @@ function renderCacheMix(mix, totalRequests) {
function renderDiagnostics(status, appendSample = true, systemSample = appendSample) { function renderDiagnostics(status, appendSample = true, systemSample = appendSample) {
const d = status.system || {}, live = status.live_system || d, c = status.cache || {}, m = status.metrics || {}; const d = status.system || {}, live = status.live_system || d, c = status.cache || {}, m = status.metrics || {};
$("metric-h3").textContent = fmt(m.http3_requests); $("metric-h3").textContent = fmt(m.http3_requests);
$("metric-h3-active").textContent = fmt(m.http3_active_connections || 0);
const cacheHits = c.hits == null ? null : Number(c.hits), cacheMisses = c.misses == null ? null : Number(c.misses); const cacheHits = c.hits == null ? null : Number(c.hits), cacheMisses = c.misses == null ? null : Number(c.misses);
const cacheEvents = cacheHits !== null && cacheMisses !== null ? cacheHits + cacheMisses : 0; const cacheEvents = cacheHits !== null && cacheMisses !== null ? cacheHits + cacheMisses : 0;
$("metric-cache-ratio").textContent = cacheEvents > 0 ? `${fmt(cacheHits / cacheEvents * 100, 1)}%` : "-"; $("metric-cache-ratio").textContent = cacheEvents > 0 ? `${fmt(cacheHits / cacheEvents * 100, 1)}%` : "-";
+6 -1
View File
@@ -162,7 +162,12 @@
$("cp-expired").textContent = `${number(m.evictions)} / ${number(m.expired)}`; $("cp-expired").textContent = `${number(m.evictions)} / ${number(m.expired)}`;
$("cp-rejected").textContent = `${t("cacheRejected")}: ${number(m.rejected_fills)} · ${t("cacheMemoryPressureShort")}: ${number(m.memory_pressure_rejections)} · ${t("cacheNukeLimitShort")}: ${number(m.hot_eviction_limited)}`; $("cp-rejected").textContent = `${t("cacheRejected")}: ${number(m.rejected_fills)} · ${t("cacheMemoryPressureShort")}: ${number(m.memory_pressure_rejections)} · ${t("cacheNukeLimitShort")}: ${number(m.hot_eviction_limited)}`;
$("cache-domain-rows").replaceChildren(...shownGroups.map(row => { $("cache-domain-rows").replaceChildren(...shownGroups.map(row => {
const tr = el("tr"); for (const value of [row.vhost || t("cacheLegacy"), row.domain, number(row.hot_entries), number(row.cold_entries), fmtBytes(row.resident_bytes), fmtBytes(row.cold_bytes), `${number(row.hits)} / ${number(row.misses)} / ${number(row.bypasses)}`, `${fmt((row.hit_ratio || 0) * 100, 1)}%`]) tr.append(el("td", value)); const tr = el("tr"); for (const value of [row.vhost || t("cacheLegacy"), row.domain, number(row.hot_entries), number(row.cold_entries), fmtBytes(row.resident_bytes), fmtBytes(row.cold_bytes)]) tr.append(el("td", value));
// HIT / MISS / BYPASS: coloured counts that match the dashboard cache bar, plus a hit-ratio meter.
const outcomes = el("td", undefined, "cache-outcomes-cell");
[[row.hits, "cache-outcome-hit"], [row.misses, "cache-outcome-miss"], [row.bypasses, "cache-outcome-bypass"]].forEach(([count, cls], index) => { if (index) outcomes.append(el("span", "/", "cache-outcome-sep")); outcomes.append(el("span", number(count), cls)); });
const ratioPercent = Math.max(0, Math.min(100, (row.hit_ratio || 0) * 100)), ratio = el("td", undefined, "cache-ratio-cell"), meter = el("span", undefined, "cache-ratio-meter"), fill = el("i"); fill.style.width = `${ratioPercent}%`; meter.append(fill); ratio.append(el("span", `${fmt(ratioPercent, 1)}%`, "cache-ratio-value"), meter);
tr.append(outcomes, ratio);
const td = el("td", undefined, "cache-reasons"); for (const [key, count] of Object.entries(row.reasons || {}).sort((a, b) => b[1] - a[1])) { const tag = el("span", `${reason(key)}: ${number(count)}`, "cache-reason"); tag.title = key; td.append(tag); } tr.append(td); return tr; const td = el("td", undefined, "cache-reasons"); for (const [key, count] of Object.entries(row.reasons || {}).sort((a, b) => b[1] - a[1])) { const tag = el("span", `${reason(key)}: ${number(count)}`, "cache-reason"); tag.title = key; td.append(tag); } tr.append(td); return tr;
})); }));
const domainEmpty = $("cache-domains-empty"); const domainEmpty = $("cache-domains-empty");
+208 -14
View File
@@ -257,20 +257,6 @@ html[data-theme=cream] {
--scrollbar-thumb-hover: #978a76; --scrollbar-thumb-hover: #978a76;
} }
body[data-density=dense] {
--ui-gap: 4px;
--ui-card: 7px;
--ui-font: 11.5px;
--ui-control: 25px;
}
body[data-density=comfortable] {
--ui-gap: 13px;
--ui-card: 17px;
--ui-font: 14px;
--ui-control: 36px;
}
html[data-button-style=compact] body { html[data-button-style=compact] body {
--button-padding: 3px 7px; --button-padding: 3px 7px;
--button-font-size: 10px; --button-font-size: 10px;
@@ -333,6 +319,62 @@ html[data-button-style=compact] body {
--sw4: #70d7ff; --sw4: #70d7ff;
} }
.chart-style-choice[data-chart-preset=sunset] {
--sw1: #ff7a59;
--sw2: #ffb347;
--sw3: #e8509b;
--sw4: #9b6bff;
}
.chart-style-choice[data-chart-preset=aurora] {
--sw1: #4ff0b0;
--sw2: #6ec8ff;
--sw3: #b78cff;
--sw4: #ff7ad9;
}
.chart-style-choice[data-chart-preset=ocean] {
--sw1: #3fa7ff;
--sw2: #2fd0c0;
--sw3: #6f8cff;
--sw4: #9be7ff;
}
.chart-style-choice[data-chart-preset=forest] {
--sw1: #7bc96f;
--sw2: #d6c45a;
--sw3: #4fae9a;
--sw4: #c98f5c;
}
.chart-style-choice[data-chart-preset=pastel] {
--sw1: #f4b6c2;
--sw2: #b5e0c2;
--sw3: #b4c8f5;
--sw4: #e3c8f0;
}
.chart-style-choice[data-chart-preset=terminal] {
--sw1: #6bff7a;
--sw2: #e5ff6b;
--sw3: #4fd0a0;
--sw4: #ffb347;
}
.chart-style-choice[data-chart-preset=blueprint] {
--sw1: #cfe6ff;
--sw2: #8fc4ff;
--sw3: #ffffff;
--sw4: #7aa8e0;
}
.chart-style-choice[data-chart-preset=colorblind] {
--sw1: #e69f00;
--sw2: #56b4e9;
--sw3: #009e73;
--sw4: #cc79a7;
}
html[data-chart-style=default] { html[data-chart-style=default] {
--chart-accent: var(--accent); --chart-accent: var(--accent);
--chart-secondary: var(--green); --chart-secondary: var(--green);
@@ -485,6 +527,158 @@ html[data-chart-style=warm] {
--chart-svg-filter: saturate(1.1) brightness(1.04); --chart-svg-filter: saturate(1.1) brightness(1.04);
} }
html[data-chart-style=sunset] {
--chart-linecap: round;
--chart-linejoin: round;
--chart-accent: #ff7a59;
--chart-secondary: #ffb347;
--chart-tertiary: #e8509b;
--chart-quaternary: #9b6bff;
--chart-quinary: #ffd479;
--chart-senary: #ff9fb2;
--chart-septenary: #c08ad8;
--chart-octonary: #8f6aa8;
--chart-surface: color-mix(in srgb, var(--panel2) 90%, #3a1428 10%);
--chart-surface-alt: color-mix(in srgb, var(--panel) 92%, #2e1022 8%);
--chart-line-width: 2.5;
--chart-grid-opacity: .36;
--chart-grid-dash: 1 0;
--chart-svg-filter: saturate(1.12) brightness(1.04);
}
html[data-chart-style=aurora] {
--chart-linecap: round;
--chart-linejoin: round;
--chart-accent: #4ff0b0;
--chart-secondary: #6ec8ff;
--chart-tertiary: #b78cff;
--chart-quaternary: #ff7ad9;
--chart-quinary: #c8ff7a;
--chart-senary: #7affea;
--chart-septenary: #8fa0ff;
--chart-octonary: #d0a8ff;
--chart-surface: color-mix(in srgb, var(--panel2) 91%, #0a2630 9%);
--chart-surface-alt: color-mix(in srgb, var(--panel) 92%, #082028 8%);
--chart-line-width: 2.4;
--chart-grid-opacity: .4;
--chart-grid-dash: 2 5;
--chart-svg-filter: saturate(1.2) brightness(1.08);
}
html[data-chart-style=ocean] {
--chart-linecap: round;
--chart-linejoin: round;
--chart-accent: #3fa7ff;
--chart-secondary: #2fd0c0;
--chart-tertiary: #6f8cff;
--chart-quaternary: #9be7ff;
--chart-quinary: #52e0a8;
--chart-senary: #78b8ff;
--chart-septenary: #4a7fb5;
--chart-octonary: #5f9ea8;
--chart-surface: color-mix(in srgb, var(--panel2) 90%, #061c33 10%);
--chart-surface-alt: color-mix(in srgb, var(--panel) 92%, #05182b 8%);
--chart-line-width: 2.2;
--chart-grid-opacity: .4;
--chart-grid-dash: 2 5;
--chart-svg-filter: saturate(1.05);
}
html[data-chart-style=forest] {
--chart-linecap: round;
--chart-linejoin: round;
--chart-accent: #7bc96f;
--chart-secondary: #d6c45a;
--chart-tertiary: #4fae9a;
--chart-quaternary: #c98f5c;
--chart-quinary: #a4d86a;
--chart-senary: #e0a84f;
--chart-septenary: #7aa86a;
--chart-octonary: #8c7a5a;
--chart-surface: color-mix(in srgb, var(--panel2) 91%, #11230f 9%);
--chart-surface-alt: color-mix(in srgb, var(--panel) 92%, #0e1d0d 8%);
--chart-line-width: 2.3;
--chart-grid-opacity: .34;
--chart-grid-dash: 1 0;
--chart-svg-filter: saturate(.96);
}
html[data-chart-style=pastel] {
--chart-linecap: round;
--chart-linejoin: round;
--chart-accent: #f4b6c2;
--chart-secondary: #b5e0c2;
--chart-tertiary: #b4c8f5;
--chart-quaternary: #e3c8f0;
--chart-quinary: #f6dba4;
--chart-senary: #a8dcdc;
--chart-septenary: #d2b8e8;
--chart-octonary: #b9c2cc;
--chart-surface: color-mix(in srgb, var(--panel2) 90%, #fff 10%);
--chart-surface-alt: color-mix(in srgb, var(--panel) 93%, #fff 7%);
--chart-line-width: 3;
--chart-grid-opacity: .44;
--chart-grid-dash: 1 0;
--chart-svg-filter: saturate(.82) brightness(1.08);
}
html[data-chart-style=terminal] {
--chart-linecap: square;
--chart-linejoin: miter;
--chart-accent: #6bff7a;
--chart-secondary: #e5ff6b;
--chart-tertiary: #4fd0a0;
--chart-quaternary: #ffb347;
--chart-quinary: #a6ff9a;
--chart-senary: #d4ff9a;
--chart-septenary: #58b868;
--chart-octonary: #7a8f58;
--chart-surface: color-mix(in srgb, var(--panel2) 88%, #031a07 12%);
--chart-surface-alt: color-mix(in srgb, var(--panel) 90%, #021405 10%);
--chart-line-width: 1.6;
--chart-grid-opacity: .7;
--chart-grid-dash: 1 3;
--chart-svg-filter: saturate(1.1) contrast(1.1);
}
html[data-chart-style=blueprint] {
--chart-linecap: square;
--chart-linejoin: miter;
--chart-accent: #cfe6ff;
--chart-secondary: #8fc4ff;
--chart-tertiary: #ffffff;
--chart-quaternary: #7aa8e0;
--chart-quinary: #a8d0ff;
--chart-senary: #e8f2ff;
--chart-septenary: #6a90c0;
--chart-octonary: #506c94;
--chart-surface: color-mix(in srgb, var(--panel2) 84%, #0a2a5c 16%);
--chart-surface-alt: color-mix(in srgb, var(--panel) 88%, #08224a 12%);
--chart-line-width: 1.3;
--chart-grid-opacity: .78;
--chart-grid-dash: 4 4;
--chart-svg-filter: saturate(.9) contrast(1.06);
}
html[data-chart-style=colorblind] {
--chart-linecap: round;
--chart-linejoin: round;
--chart-accent: #e69f00;
--chart-secondary: #56b4e9;
--chart-tertiary: #009e73;
--chart-quaternary: #cc79a7;
--chart-quinary: #f0e442;
--chart-senary: #0072b2;
--chart-septenary: #d55e00;
--chart-octonary: #bdbdbd;
--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: 2.6;
--chart-grid-opacity: .7;
--chart-grid-dash: 3 4;
--chart-svg-filter: contrast(1.1);
}
html[data-chart-style] { html[data-chart-style] {
--traffic-h1: var(--chart-accent); --traffic-h1: var(--chart-accent);
--traffic-h2: var(--chart-secondary); --traffic-h2: var(--chart-secondary);
+34 -42
View File
@@ -1067,43 +1067,10 @@ dialog .vh-form,
align-items: start; align-items: start;
} }
body[data-density=compact] {
--ui-gap: 2px;
--ui-card: 5px;
--ui-font: 10.75px;
--ui-control: 22px;
line-height: 1.32;
}
body[data-density=compact] main {
padding: 0 10px;
}
body[data-density=compact] .card-heading {
margin-bottom: 5px;
}
body[data-density=compact] .card-heading h2,
html[data-dashboard-density=compact] #panel-overview .dashboard-overview-grid>.card .card-heading h2 { html[data-dashboard-density=compact] #panel-overview .dashboard-overview-grid>.card .card-heading h2 {
font-size: 14px; font-size: 14px;
} }
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] .page-footer {
padding: 7px 0;
margin-top: 4px;
}
.network-interface-row-head { .network-interface-row-head {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@@ -1151,7 +1118,7 @@ body[data-density=compact] .page-footer {
font: 10px var(--mono); font: 10px var(--mono);
} }
.network-interface-row svg { .network-interface-row svg:not(.ui-icon) {
display: block; display: block;
width: 100%; width: 100%;
height: 60px; height: 60px;
@@ -1251,6 +1218,29 @@ body[data-density=compact] .page-footer {
min-height: 24px; min-height: 24px;
padding: 3px 7px; padding: 3px 7px;
font: 9px var(--mono); font: 9px var(--mono);
display: inline-flex;
align-items: center;
white-space: nowrap;
}
/* Small controls: a smaller icon must not change the button height when the
optional SVG icons are toggled or re-applied during a refresh (Safari jumped). */
.network-interface-hide>.ui-icon,
.network-interface-hidden .quiet>.ui-icon {
width: 11px;
height: 11px;
margin-inline-end: 4px;
vertical-align: 0;
}
html[data-interface-icons="true"] .network-interface-hide>svg.ui-icon,
html[data-interface-icons="true"] .network-interface-hidden .quiet>svg.ui-icon {
width: 11px;
height: 11px;
}
.network-interface-charts {
overflow-anchor: none;
} }
.network-interface-row+.network-interface-row, .network-interface-row+.network-interface-row,
@@ -1570,14 +1560,6 @@ html[data-table-density=ultra] :is(table th, table td) {
min-width: 0; min-width: 0;
} }
.path-permissions-commands {
max-height: 180px;
}
.path-permissions-actions {
margin-top: 0;
}
.path-permissions-options>summary { .path-permissions-options>summary {
cursor: pointer; cursor: pointer;
} }
@@ -1599,6 +1581,7 @@ html[data-table-density=ultra] :is(table th, table td) {
} }
.path-permissions-commands { .path-permissions-commands {
max-height: 180px;
margin-bottom: 0; margin-bottom: 0;
} }
@@ -1609,3 +1592,12 @@ html[data-table-density=ultra] :is(table th, table td) {
.path-permissions-actions { .path-permissions-actions {
margin-top: 10px; margin-top: 10px;
} }
/* Safety net: whatever chart/card rule is added later, injected UI icons stay inline,
fixed-size and out of layout flow (they used to stretch inside chart cards on mobile Safari). */
html[data-interface-icons="true"] :is(button, a, summary)>svg.ui-icon {
display: inline-block;
position: static;
flex: 0 0 auto;
max-width: none;
overflow: visible;
}
-31
View File
@@ -604,37 +604,6 @@ main {
padding-top: 4px; padding-top: 4px;
} }
body[data-density=compact] .shell {
grid-template-columns: 164px minmax(0, 1fr);
}
body[data-density=compact] .sidebar {
padding: 14px 9px;
}
body[data-density=compact] .nav,
body[data-density=compact] .stat {
padding: 6px 7px;
}
body[data-density=compact] .topbar {
height: 42px;
}
body[data-density=compact] .page-intro {
padding: 7px 0 6px;
gap: 7px;
}
body[data-density=compact] .page-intro h1 {
font-size: 20px;
}
body[data-density=compact] .page-intro .lead {
font-size: 10px;
margin-top: 2px;
}
.top-log-menu>summary, .top-log-menu>summary,
.backend-column-picker>summary, .backend-column-picker>summary,
.file-log-search-help>summary { .file-log-search-help>summary {
+83 -45
View File
@@ -280,7 +280,7 @@ html[data-theme=cream] :is(.card, .stat, dialog) {
text-align: right; text-align: right;
} }
.mini-live-chart svg { .mini-live-chart svg:not(.ui-icon) {
display: block; display: block;
width: 100%; width: 100%;
height: 60px; height: 60px;
@@ -361,11 +361,11 @@ html[data-theme=cream] :is(.card, .stat, dialog) {
font-size: 10px; font-size: 10px;
} }
.activity-card svg { .activity-card svg:not(.ui-icon) {
height: 105px; height: 105px;
} }
.system-card svg { .system-card svg:not(.ui-icon) {
height: 80px; height: 80px;
} }
@@ -383,11 +383,6 @@ html[data-theme=cream] :is(.card, .stat, dialog) {
max-height: 205px; max-height: 205px;
} }
body[data-density=dense] .stat strong {
font-size: 22px;
margin: 2px 0;
}
.traffic-chart-grid, .traffic-chart-grid,
.logging-grid { .logging-grid {
display: grid; display: grid;
@@ -468,7 +463,7 @@ body[data-density=dense] .stat strong {
padding: 7px 10px 0; padding: 7px 10px 0;
} }
.vhost-chart svg { .vhost-chart svg:not(.ui-icon) {
display: block; display: block;
width: 100%; width: 100%;
height: 54px; height: 54px;
@@ -550,37 +545,12 @@ body[data-density=dense] .stat strong {
font-weight: 600; font-weight: 600;
} }
body[data-density=compact] .stat strong {
font-size: 18px;
margin: 1px 0;
}
body[data-density=compact] .stat p,
body[data-density=compact] .stat small,
html[data-dashboard-density=normal] #panel-overview .stat small, html[data-dashboard-density=normal] #panel-overview .stat small,
html[data-dashboard-density=ultra] #panel-overview .stat-cache-outcomes .cache-mix-header>span, html[data-dashboard-density=ultra] #panel-overview .stat-cache-outcomes .cache-mix-header>span,
html[data-dashboard-density=ultra] #panel-overview .stat-cache-outcomes .cache-mix-caption { html[data-dashboard-density=ultra] #panel-overview .stat-cache-outcomes .cache-mix-caption {
font-size: 9px; font-size: 9px;
} }
body[data-density=compact] .activity-card svg {
height: 84px;
}
body[data-density=compact] .system-card svg {
height: 66px;
}
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 {
max-height: 170px;
}
.network-interface-charts { .network-interface-charts {
display: grid; display: grid;
max-height: 260px; max-height: 260px;
@@ -664,7 +634,7 @@ body[data-density=compact] .pool-card .table-scroll {
margin-bottom: 3px; margin-bottom: 3px;
} }
.stat-chart svg { .stat-chart svg:not(.ui-icon) {
display: block; display: block;
width: 100%; width: 100%;
height: 26px; height: 26px;
@@ -769,7 +739,7 @@ body[data-density=compact] .pool-card .table-scroll {
padding: 14px 15px 12px; padding: 14px 15px 12px;
} }
.function-live-chart svg { .function-live-chart svg:not(.ui-icon) {
display: block; display: block;
width: 100%; width: 100%;
height: 180px; height: 180px;
@@ -851,7 +821,7 @@ body[data-density=compact] .pool-card .table-scroll {
font-weight: 400; font-weight: 400;
} }
.connection-card svg { .connection-card svg:not(.ui-icon) {
display: block; display: block;
width: 100%; width: 100%;
height: 100px; height: 100px;
@@ -1052,7 +1022,7 @@ body[data-density=compact] .pool-card .table-scroll {
border-radius: 0; border-radius: 0;
} }
:is(.stat-chart, .activity-card, .system-card, .function-live-chart) svg, :is(.stat-chart, .activity-card, .system-card, .function-live-chart) svg:not(.ui-icon),
:is(.traffic-donut, .traffic-bar-chart span) { :is(.traffic-donut, .traffic-bar-chart span) {
filter: var(--chart-svg-filter); filter: var(--chart-svg-filter);
} }
@@ -1127,15 +1097,15 @@ html[data-dashboard-density=ultra] #panel-overview .stat small {
line-height: 1.12; line-height: 1.12;
} }
html[data-dashboard-density=ultra] #panel-overview .stat-chart svg { html[data-dashboard-density=ultra] #panel-overview .stat-chart svg:not(.ui-icon) {
height: 24px; height: 24px;
} }
html[data-dashboard-density=normal] #panel-overview .stat-chart svg { html[data-dashboard-density=normal] #panel-overview .stat-chart svg:not(.ui-icon) {
height: 34px; height: 34px;
} }
html[data-dashboard-density=compact] #panel-overview .stat-chart svg { html[data-dashboard-density=compact] #panel-overview .stat-chart svg:not(.ui-icon) {
height: 28px; height: 28px;
} }
@@ -1737,7 +1707,7 @@ html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-sty
border-radius: var(--rp-radius-surface) !important; 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):not(.vhost-backend-trigger), 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-restore), 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):not(.vhost-backend-trigger), input:not([type=checkbox]):not([type=radio]):not([type=range]), select, textarea, .pill, .vhost-backend-protocol-badge, .overview-widget-type, .quiet, .outline, .primary, .code, .command, .dashboard-widget-toggle, .settings-tabs button, .overview-inline-restore),
html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-style=rounded], [data-corner-style=extra]) :is(.nav, .file-log-tab, .vhost-backend-trigger, .backend-column-options, .docker-service-quick-actions a, .docker-service-quick-actions button, .docker-view-toggle, .provider-picker, .renewal-meta-item) { html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-style=rounded], [data-corner-style=extra]) :is(.nav, .file-log-tab, .vhost-backend-trigger, .backend-column-options, .docker-service-quick-actions a, .docker-service-quick-actions button, .docker-view-toggle, .provider-picker, .renewal-meta-item) {
border-radius: var(--rp-radius-control) !important; border-radius: var(--rp-radius-control) !important;
} }
@@ -2074,7 +2044,7 @@ html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-sty
gap: 8px; gap: 8px;
padding: 9px; padding: 9px;
border: 1px solid var(--line); border: 1px solid var(--line);
border-radius: var(--rp-radius-surface, var(--radius, 8px)); border-radius: var(--rp-radius-surface, 8px);
background: var(--panel); background: var(--panel);
} }
@@ -2213,8 +2183,8 @@ html[data-dashboard-density] #panel-overview .stat :is(.stat-chart-legend, .over
#panel-overview .stat-cache-outcomes .cache-mix-bar { #panel-overview .stat-cache-outcomes .cache-mix-bar {
display: flex; display: flex;
width: 100%; width: 100%;
height: 12px; height: 20px;
margin: 7px 0 9px; margin: 3px 0 4px;
border-radius: var(--rp-progress-radius); border-radius: var(--rp-progress-radius);
background: var(--rp-progress-track); background: var(--rp-progress-track);
overflow: hidden; overflow: hidden;
@@ -2224,6 +2194,17 @@ html[data-dashboard-density] #panel-overview .stat :is(.stat-chart-legend, .over
display: block; display: block;
height: 100%; height: 100%;
min-width: 0; min-width: 0;
padding: 0 2px;
overflow: hidden;
color: #0c0e11;
font: 600 10px/20px var(--mono, ui-monospace, monospace);
text-align: center;
white-space: nowrap;
user-select: none;
}
#panel-overview .cache-mix-bar>span.is-tight {
color: transparent;
} }
#panel-overview .cache-mix-other { #panel-overview .cache-mix-other {
@@ -2323,3 +2304,60 @@ html[data-dashboard-density] #panel-overview .stat :is(.stat-chart-legend, .over
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
/* WebSocket tunnels: "Established" sits to the right of the main value instead of a third text line. */
#panel-overview .stat .stat-value-row {
display: flex;
align-items: baseline;
flex-wrap: wrap;
justify-content: space-between;
column-gap: 8px;
min-width: 0;
}
#panel-overview .stat .stat-value-row>strong {
flex: 0 0 auto;
}
#panel-overview .stat .stat-value-row>.stat-side-value {
display: block;
margin: 0 0 0 auto;
color: var(--muted);
font-size: 10px;
line-height: 1.3;
text-align: right;
white-space: nowrap;
}
#panel-overview .stat .stat-value-row>.stat-side-value b {
color: var(--text);
font-family: var(--mono);
font-size: 13px;
font-weight: 450;
}
/* Compact stat cards: a card made only of title, value and one secondary line shows that line
beside the value (right-aligned) instead of on a line of its own. */
#panel-overview .stats-grid>.stat:is(:not(.stat-donut, .stat-meter, .stat-with-action, .stat-chart):not(:has(>:not(span, strong, small))):has(>small)) {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
column-gap: 10px;
align-items: baseline;
align-content: start;
}
#panel-overview .stats-grid>.stat:is(:not(.stat-donut, .stat-meter, .stat-with-action, .stat-chart):not(:has(>:not(span, strong, small))):has(>small))>span:first-child {
grid-column: 1 / -1;
}
#panel-overview .stats-grid>.stat:is(:not(.stat-donut, .stat-meter, .stat-with-action, .stat-chart):not(:has(>:not(span, strong, small))):has(>small))>strong {
grid-column: 1;
min-width: 0;
}
#panel-overview .stats-grid>.stat:is(:not(.stat-donut, .stat-meter, .stat-with-action, .stat-chart):not(:has(>:not(span, strong, small))):has(>small))>small {
grid-column: 2;
max-width: 14em;
margin: 0;
text-align: right;
overflow-wrap: anywhere;
}
+96 -17
View File
@@ -29,7 +29,7 @@
.traffic-chart-card, .traffic-chart-card,
#panel-overview .backend-mode-bar span, #panel-overview .backend-mode-bar span,
#panel-overview .stats-grid>.stat, #panel-overview .stats-grid>.stat,
#panel-overview .dashboard-overview-grid>.activity-card svg, #panel-overview .dashboard-overview-grid>.activity-card svg:not(.ui-icon),
#panel-overview .dashboard-overview-grid>[data-dashboard-widget], #panel-overview .dashboard-overview-grid>[data-dashboard-widget],
.dashboard-density-setting>.fine, .dashboard-density-setting>.fine,
.dashboard-density-setting>select, .dashboard-density-setting>select,
@@ -1140,7 +1140,7 @@ html[data-dashboard-density=ultra] #panel-overview .dashboard-overview-grid>.car
display: grid; display: grid;
grid-template-columns: minmax(0, 1fr); grid-template-columns: minmax(0, 1fr);
gap: 11px; gap: 11px;
background: var(--surface, transparent); background: transparent;
} }
.vh-backend-health-editor h3 { .vh-backend-health-editor h3 {
@@ -1483,18 +1483,22 @@ html[data-dashboard-density=ultra] #panel-overview .dashboard-overview-grid>.car
grid-row: 1; grid-row: 1;
} }
/* Same geometry and typography as .pill so protocol indicators sit evenly in the
Vhost policy row; only the colour (muted = not observed, green = observed) differs. */
.vhost-backend-protocol-badge { .vhost-backend-protocol-badge {
display: inline-flex; display: inline-flex;
gap: 4px; gap: 4px;
align-items: baseline; align-items: center;
max-width: 100%; max-width: 100%;
min-width: 0; min-width: 0;
border: 1px solid var(--pill-line); border: 1px solid var(--pill-line);
border-radius: var(--rp-radius-control, 4px); border-radius: var(--rp-radius-control, 0);
background: var(--panel2); background: transparent;
color: var(--muted); color: var(--muted);
padding: 3px 7px; min-height: 0;
font: 10px var(--mono); padding: 4px 8px;
font: 9px/10px var(--mono);
letter-spacing: 1px;
overflow-wrap: anywhere; overflow-wrap: anywhere;
text-align: left; text-align: left;
} }
@@ -1504,6 +1508,10 @@ html.app-loading,
background: var(--bg); background: var(--bg);
} }
.vhost-policy-pills>.vhost-backend-protocol-badge {
align-self: center;
}
.vhost-backend-protocol-badge[data-observed=true] { .vhost-backend-protocol-badge[data-observed=true] {
color: var(--green); color: var(--green);
border-color: var(--pill-live-line); border-color: var(--pill-live-line);
@@ -1788,12 +1796,12 @@ html.app-loading,
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
max-width: 100%; max-width: 100%;
gap: 7px; gap: 5px;
padding: 2px 4px 2px 9px; padding: 4px 5px 4px 8px;
border-radius: var(--rp-radius-control, 15px); border-radius: var(--rp-radius-control, 0);
border: 1px solid var(--pill-live-line); border: 1px solid var(--pill-live-line);
background: var(--nav-selected-bg); background: var(--panel);
font-size: 11px; font: 10px var(--mono);
} }
.acl-picker-tag>span { .acl-picker-tag>span {
@@ -1803,13 +1811,12 @@ html.app-loading,
.acl-picker-tag .acl-picker-remove { .acl-picker-tag .acl-picker-remove {
border: none; border: none;
border-radius: var(--rp-radius-control, 50%); border-radius: var(--rp-radius-control, 0);
background: transparent; background: transparent;
color: var(--muted); color: var(--muted);
min-width: 20px; font-size: 15px;
height: 20px; line-height: 1;
font-size: 16px; padding: 0 3px;
padding: 0;
cursor: pointer; cursor: pointer;
} }
@@ -1941,3 +1948,75 @@ html.app-loading,
margin: 0 !important; margin: 0 !important;
min-width: 0; min-width: 0;
} }
/* Spacing review (0.36.2): descriptive text and blocks that used to touch the next
element. Rules only add space where the layout had none; grid/flex gaps are untouched. */
.fine+:is(.switch-grid, .settings-tabs, .table-scroll, .soft-card, .appearance-settings-grid, .runtime-information-grid, details),
.form-actions+.fine,
.table-scroll+:is(.empty, .fine),
.empty+.file-logs-pager {
margin-top: 10px;
}
:is(.card-heading, .window-heading)+.fine,
h2+.fine {
margin-top: 6px;
}
.location-help-content :is(p, .fine)+:is(p, .fine) {
margin-top: 8px;
}
.location-help-content :is(p, .fine, ul, ol, table, pre)+:is(h3, h4) {
margin-top: 16px;
}
.cache-toolbar+.function-live-chart,
.function-live-chart+.stats-grid,
.soft-card+.soft-card,
.field-grid+.field-grid {
margin-top: 12px;
}
.function-live-chart-summary+.function-live-chart-body {
margin-top: 8px;
}
.tls-pqc-control>:is(.fine, a) {
margin-block: 0;
}
.tls-pqc-control {
display: grid;
gap: 6px;
margin-top: 6px;
}
.tls-pqc-control>a {
justify-self: start;
color: var(--accent);
text-decoration: underline;
text-underline-offset: 3px;
font-size: 12px;
}
#settings-page-tls-policy .switch-grid {
margin-bottom: 12px;
}
#panel-settings .fine+.settings-tabs.sub-tabs,
.fine+.field-grid,
.vh-grid+.fine,
.logs-card+.log-footnote,
.overview-presets-content+.overview-layout-footer {
margin-top: 10px;
}
#settings-page-policies>.edit-form+div {
margin-top: 12px;
}
@media (min-width: 761px) {
#panel-settings .settings-tabs.sub-tabs+:is(div, form) {
margin-top: 12px;
}
}
+220 -35
View File
@@ -130,7 +130,6 @@
align-items: center; align-items: center;
} }
body[data-density=compact] .sidebar nav,
.certificate-coverage { .certificate-coverage {
margin-top: 14px; margin-top: 14px;
} }
@@ -688,40 +687,6 @@ body[data-density=compact] .sidebar nav,
margin-bottom: 12px; margin-bottom: 12px;
} }
body[data-density=compact] .docker-service-row {
padding: 6px 8px;
gap: 4px;
}
body[data-density=compact] .docker-service-head,
body[data-density=compact] .security-stack {
gap: 7px;
}
body[data-density=compact] .docker-agent-overview {
gap: 4px 12px;
margin: 4px 0 6px;
font-size: 10px;
}
body[data-density=compact] .docker-agent-details {
margin-bottom: 6px;
padding-top: 5px;
}
body[data-density=compact] .docker-service-editor {
padding-top: 5px;
}
body[data-density=compact] .docker-service-editor[open]>summary {
margin-bottom: 7px;
}
body[data-density=compact] .security-editor {
margin-top: 8px;
padding-top: 6px;
}
.docker-agent-grid>label { .docker-agent-grid>label {
display: grid; display: grid;
align-content: start; align-content: start;
@@ -1295,3 +1260,223 @@ html[data-dashboard-density=normal] #panel-overview .stat>span,
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
} }
/* Settings rows (0.36.3): label and help on the left, control on the right, one setting per row. */
#settings-page-appearance .appearance-section-heading .small-label {
display: none;
}
#settings-page-appearance .appearance-section-heading h3 {
margin: 0;
font-size: 14px;
}
#settings-page-appearance .appearance-settings-grid {
grid-template-columns: minmax(0, 1fr);
gap: 0;
}
#settings-page-appearance .appearance-settings-grid>label {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(170px, 260px);
grid-template-areas: "name control" "help control";
align-content: center;
align-items: center;
column-gap: 24px;
row-gap: 3px;
margin: 0;
padding: 11px 0;
border-top: 1px solid var(--line);
}
#settings-page-appearance .appearance-settings-grid>label:first-child {
border-top: 0;
padding-top: 0;
}
#settings-page-appearance .appearance-settings-grid>label>span:first-child {
grid-area: name;
align-self: end;
font-size: 12px;
font-weight: 600;
color: var(--text);
}
#settings-page-appearance .appearance-settings-grid>label>:is(select, input[type=range]) {
grid-area: control;
width: 100%;
min-width: 0;
}
#settings-page-appearance .appearance-settings-grid>label>small {
grid-area: help;
align-self: start;
margin: 0;
color: var(--muted);
font-size: 11px;
line-height: 1.45;
}
#settings-page-appearance .appearance-settings-grid>label:not(:has(>small), .check) {
grid-template-areas: "name control";
}
#settings-page-appearance .appearance-settings-grid>label:not(:has(>small), .check)>span:first-child {
align-self: center;
}
#settings-page-appearance .refresh-settings-grid>label.check>span,
#settings-page-appearance .dashboard-widget-settings-intro>span {
color: var(--text);
font-size: 12px;
font-weight: 600;
}
#settings-page-appearance .appearance-scale-control>span:first-child {
justify-content: flex-start;
}
/* Checkbox settings: text on the left, the checkbox on the right edge of the row
(.check is display:flex !important, so the row is reversed instead of using grid areas). */
#settings-page-appearance .appearance-settings-grid>label.check {
flex-direction: row-reverse;
justify-content: space-between;
align-items: center;
gap: 24px;
align-self: auto;
}
#settings-page-appearance .appearance-settings-grid>label.check>input {
flex: 0 0 auto;
margin: 0;
}
#settings-page-appearance .appearance-settings-grid>label.check>span {
grid-area: text;
display: grid;
gap: 3px;
flex: 1 1 auto;
min-width: 0;
align-self: center;
font-size: 12px;
font-weight: 400;
}
#settings-page-appearance .appearance-settings-grid>label.check>span b {
font-weight: 600;
color: var(--text);
}
#settings-page-appearance .appearance-settings-grid>label.check>span small {
color: var(--muted);
font-size: 11px;
line-height: 1.45;
}
#settings-page-appearance .appearance-chart-preview {
margin-top: 12px;
}
#settings-page-appearance .dashboard-appearance-grid>.dashboard-widget-settings {
margin-top: 4px;
padding-top: 12px;
border-top: 1px solid var(--line);
}
#settings-page-appearance .refresh-settings-grid>label.check {
grid-column: auto;
}
@media (max-width: 760px) {
#settings-page-appearance .appearance-settings-grid>label {
grid-template-columns: minmax(0, 1fr);
grid-template-areas: "name" "help" "control";
row-gap: 5px;
}
#settings-page-appearance .appearance-settings-grid>label>span:first-child {
align-self: start;
}
}
/* Settings navigation (0.36.3) */
@media (min-width: 761px) {
/* Sub-tabs are a horizontal tab bar; the vertical rail layout is for the main settings menu only. */
#panel-settings .settings-tabs.sub-tabs {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
gap: 4px;
padding: 5px;
margin-bottom: 14px;
}
#panel-settings .settings-tabs.sub-tabs button {
width: auto;
min-width: 0;
padding: 7px 14px;
text-align: center;
white-space: nowrap;
}
/* Visual groups in the main menu: appearance / traffic / rules / observability / system / security. */
#panel-settings #settings-tabs button:is([data-settings-tab=listeners], [data-settings-tab=policies], [data-settings-tab=logging], [data-settings-tab=administration], [data-settings-tab=certificate-api]) {
margin-top: 7px;
position: relative;
}
#panel-settings #settings-tabs button:is([data-settings-tab=listeners], [data-settings-tab=policies], [data-settings-tab=logging], [data-settings-tab=administration], [data-settings-tab=certificate-api])::before {
content: "";
position: absolute;
top: -5px;
right: 8px;
left: 8px;
border-top: 1px solid var(--line);
}
}
/* Cache and static transformation pages (0.36.3): grouped sections, help attached to its switch. */
#cache-form .cache-section-title {
margin-top: 20px;
padding-top: 14px;
border-top: 1px solid var(--line);
font-size: 13px;
}
#cache-form [data-cache-settings-page=general]>.cache-section-title:first-child {
margin-top: 2px;
padding-top: 0;
border-top: 0;
}
:is(#cache-form, #transform-form)>.check:first-of-type,
#cache-form [data-cache-settings-page=general]>.check:first-of-type {
font-weight: 600;
}
:is(#cache-form, #transform-form) .check+.fine {
margin: 3px 0 12px 24px;
}
:is(#cache-form, #transform-form) .cache-memory-sizing .card-heading {
justify-content: flex-start;
gap: 8px;
}
:is(#cache-form, #transform-form) .cache-memory-sizing .card-heading>h3 {
margin-right: auto;
}
:is(#cache-form, #transform-form) .field-grid {
grid-template-columns: repeat(auto-fill, minmax(min(100%, 210px), 1fr));
column-gap: 14px;
}
#transform-form .switch-grid {
grid-template-columns: repeat(auto-fill, minmax(min(100%, 200px), 1fr));
}
#cache-form #cache-default-ttl {
display: block;
max-width: 320px;
}
+81 -8
View File
@@ -430,13 +430,12 @@ html[data-dashboard-density] #panel-overview .dashboard-overview-grid>.card.pool
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
margin-left: 6px; margin-left: 6px;
padding: 1px 5px; padding: 2px 6px;
border: 1px solid var(--pill-line); border: 1px solid var(--pill-line);
border-radius: var(--rp-radius-control, 3px); border-radius: var(--rp-radius-control, 0);
color: var(--accent); color: var(--accent);
font: 9px var(--mono); font: 9px var(--mono);
text-transform: uppercase; letter-spacing: 1px;
letter-spacing: .35px;
} }
.cache-toolbar-message { .cache-toolbar-message {
@@ -574,8 +573,8 @@ html[data-dashboard-density] #panel-overview .dashboard-overview-grid>.card.pool
button.config-history-day.has-revisions { button.config-history-day.has-revisions {
cursor: pointer; cursor: pointer;
color: var(--text); color: var(--text);
border-color: var(--line-strong, var(--line)); border-color: var(--line);
background: var(--surface, var(--bg)); background: var(--bg);
} }
button.config-history-day.has-revisions:hover, button.config-history-day.has-revisions:hover,
@@ -834,7 +833,6 @@ html[data-table-density=ultra] .file-log-inline-detail {
padding: 5px 7px; padding: 5px 7px;
} }
body[data-density=compact] .mini-metrics div,
.acl-policy-mode-card input, .acl-policy-mode-card input,
.docker-service-certificate-bar>.check>input, .docker-service-certificate-bar>.check>input,
html[data-table-density=ultra] .file-log-field-grid { html[data-table-density=ultra] .file-log-field-grid {
@@ -1006,7 +1004,7 @@ html[data-table-density=ultra] .file-log-inline-detail pre {
.file-log-advanced-parser { .file-log-advanced-parser {
margin: 0 0 1rem; margin: 0 0 1rem;
padding: .75rem 1rem; padding: .75rem 1rem;
border-radius: var(--rp-radius-surface, var(--radius, 12px)); border-radius: var(--rp-radius-surface, 12px);
} }
.file-log-aggregate-form { .file-log-aggregate-form {
@@ -1074,3 +1072,78 @@ html[data-table-density=ultra] .file-log-inline-detail pre {
max-height: 68dvh; max-height: 68dvh;
font: 11px/1.5 var(--mono); font: 11px/1.5 var(--mono);
} }
/* Cache → Domains and effectiveness (0.36.3): numeric columns aligned, outcomes coloured like the dashboard bar. */
#cache-domain-scroll .cache-table :is(th, td):nth-child(n+3):nth-child(-n+8) {
text-align: right;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
#cache-domain-scroll .cache-table td:first-child {
color: var(--muted);
}
#cache-domain-scroll .cache-table td:nth-child(2) {
color: var(--text);
font-weight: 600;
}
#cache-domain-scroll .cache-table tbody tr:hover {
background: var(--panel2);
}
#cache-domain-scroll .cache-table td {
vertical-align: middle;
}
.cache-outcomes-cell {
letter-spacing: 0;
}
.cache-outcome-hit {
color: var(--chart-accent);
}
.cache-outcome-miss {
color: var(--chart-secondary);
}
.cache-outcome-bypass {
color: var(--chart-tertiary);
}
.cache-outcome-sep {
margin: 0 5px;
color: var(--muted);
opacity: .6;
}
.cache-ratio-cell {
min-width: 96px;
}
.cache-ratio-value {
display: block;
font-weight: 600;
}
.cache-ratio-meter {
display: block;
height: 3px;
margin-top: 4px;
overflow: hidden;
background: var(--line);
}
.cache-ratio-meter>i {
display: block;
height: 100%;
background: var(--chart-accent);
}
#cache-domain-scroll .cache-reason {
border-color: transparent;
background: var(--panel2);
color: var(--muted);
}
+50 -22
View File
@@ -302,18 +302,6 @@
max-width: calc(100vw - 12px); max-width: calc(100vw - 12px);
} }
body[data-density=compact] .sidebar nav {
margin-top: 0;
}
body[data-density=compact] .sidebar {
padding: 8px 9px;
}
body[data-density=compact] .topbar {
height: 42px;
}
.sidebar .brand { .sidebar .brand {
min-width: 0; min-width: 0;
} }
@@ -1293,16 +1281,6 @@
align-self: flex-start; align-self: flex-start;
} }
body[data-density=compact] .docker-service-row {
padding: 8px;
gap: 5px;
}
body[data-density=compact] .docker-service-head-actions .docker-runtime .pill {
top: 8px;
right: 8px;
}
.backend-column-options { .backend-column-options {
width: min(330px, calc(100vw - 32px)); width: min(330px, calc(100vw - 32px));
min-width: 0; min-width: 0;
@@ -1703,3 +1681,53 @@
--overview-widget-placement: 1/-1; --overview-widget-placement: 1/-1;
} }
} }
@media (max-width: 760px) {
#panel-overview .system-card .card-heading {
flex-wrap: wrap;
gap: 4px 12px;
}
#panel-overview .system-card .system-uptime-meta {
white-space: normal;
max-width: 100%;
margin-left: auto;
text-align: right;
}
}
/* Docker vhosts (0.36.3): compact service rows - switch | name and details | health and actions. */
@media (min-width:900px) {
.docker-service-head {
grid-template-columns: auto minmax(220px, 1fr) auto;
align-items: center;
gap: 6px 18px;
}
.docker-service-head-actions {
grid-column: auto;
justify-content: flex-end;
gap: 6px 10px;
}
.docker-service-head-actions .docker-runtime {
flex: 1 0 100%;
justify-content: flex-end;
}
.docker-service-meta {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 1px 14px;
}
.docker-service-meta>b {
flex: 0 0 100%;
font-size: 13px;
}
.docker-service-meta>span {
font-size: 11px;
}
}
+23 -11
View File
@@ -1821,7 +1821,7 @@
<section class="appearance-section"> <section class="appearance-section">
<div class="appearance-section-heading"> <div class="appearance-section-heading">
<div><span class="small-label" data-i18n="appearanceChartsSection">CHARTS</span> <div><span class="small-label" data-i18n="appearanceChartsSection">CHARTS</span>
<h3 data-i18n="chartStyle">Chart style</h3> <h3 data-i18n="appearanceChartsTitle">Charts</h3>
</div><button class="quiet appearance-section-reset" </div><button class="quiet appearance-section-reset"
data-appearance-reset="charts" data-i18n="resetSection" type="button">Reset data-appearance-reset="charts" data-i18n="resetSection" type="button">Reset
section</button> section</button>
@@ -1840,6 +1840,14 @@
</option> </option>
<option data-i18n="chartStyleWarm" value="warm">Warm</option> <option data-i18n="chartStyleWarm" value="warm">Warm</option>
<option data-i18n="chartStyleCool" value="cool">Cool</option> <option data-i18n="chartStyleCool" value="cool">Cool</option>
<option data-i18n="chartStyleSunset" value="sunset">Sunset</option>
<option data-i18n="chartStyleAurora" value="aurora">Aurora</option>
<option data-i18n="chartStyleOcean" value="ocean">Ocean</option>
<option data-i18n="chartStyleForest" value="forest">Forest</option>
<option data-i18n="chartStylePastel" value="pastel">Pastel</option>
<option data-i18n="chartStyleTerminal" value="terminal">Terminal</option>
<option data-i18n="chartStyleBlueprint" value="blueprint">Blueprint</option>
<option data-i18n="chartStyleColorblind" value="colorblind">Colorblind safe</option>
</select></label> </select></label>
<label class="check appearance-chart-axes-setting"><input id="ui-chart-axes" <label class="check appearance-chart-axes-setting"><input id="ui-chart-axes"
type="checkbox" /><span><b data-i18n="chartAxes">Show X/Y scales on type="checkbox" /><span><b data-i18n="chartAxes">Show X/Y scales on
@@ -1864,7 +1872,7 @@
<div class="appearance-section-heading"> <div class="appearance-section-heading">
<div><span class="small-label" data-i18n="appearanceDataSection">TABLES / <div><span class="small-label" data-i18n="appearanceDataSection">TABLES /
LOGS</span> LOGS</span>
<h3 data-i18n="tableDensity">Tables and logs density</h3> <h3 data-i18n="appearanceTablesTitle">Tables and logs</h3>
</div><button class="quiet appearance-section-reset" </div><button class="quiet appearance-section-reset"
data-appearance-reset="tables" data-i18n="resetSection" type="button">Reset data-appearance-reset="tables" data-i18n="resetSection" type="button">Reset
section</button> section</button>
@@ -2384,6 +2392,7 @@
data-i18n="cacheTabDiagnostics" type="button">Static & diagnostics</button> data-i18n="cacheTabDiagnostics" type="button">Static & diagnostics</button>
</nav> </nav>
<div data-cache-settings-page="general"> <div data-cache-settings-page="general">
<h3 class="subheading cache-section-title" data-i18n="cacheSectionBehavior">Behavior</h3>
<label class="check"><input id="cache-enabled" type="checkbox" /><span <label class="check"><input id="cache-enabled" type="checkbox" /><span
data-i18n="enableCache">Enable shared cache</span></label> data-i18n="enableCache">Enable shared cache</span></label>
<p class="fine" data-i18n="cacheSafety">Only complete public-safe GET responses. <p class="fine" data-i18n="cacheSafety">Only complete public-safe GET responses.
@@ -2429,7 +2438,17 @@
<input id="cache-varnish-grace" type="number" min="0" max="604800" step="1" <input id="cache-varnish-grace" type="number" min="0" max="604800" step="1"
required /> required />
</div> </div>
<div class="card soft-card cache-memory-sizing"> <label data-i18n="cacheDefaultTtl" for="cache-default-ttl">Fallback TTL for public
responses (0 = off)</label><input id="cache-default-ttl" max="604800" min="0"
required="" type="number" />
<label class="check"><input id="cache-query" type="checkbox" /><span
data-i18n="bypassQuery">Bypass URLs with a query string</span></label>
<label data-i18n="cachePrefixes" for="cache-prefixes">Bypass path prefixes, one per
line</label><textarea id="cache-prefixes" placeholder="/api/private" rows="3"
spellcheck="false"></textarea>
<h3 class="subheading cache-section-title" data-i18n="cacheSectionMemory">Memory and limits</h3>
<div class="card soft-card cache-memory-sizing">
<div class="card-heading"> <div class="card-heading">
<h3 data-i18n="cacheMemorySizingTitle">Memory sizing</h3> <h3 data-i18n="cacheMemorySizingTitle">Memory sizing</h3>
<span class="pill" id="cache-memory-preset-selected">-</span> <span class="pill" id="cache-memory-preset-selected">-</span>
@@ -2483,14 +2502,7 @@
<p class="fine" data-i18n="cacheAutoTrimHelp">When HOT memory is above the target, <p class="fine" data-i18n="cacheAutoTrimHelp">When HOT memory is above the target,
only entries idle for the configured age are removed in bounded passes. Active only entries idle for the configured age are removed in bounded passes. Active
objects are not purged.</p> objects are not purged.</p>
<label data-i18n="cacheDefaultTtl" for="cache-default-ttl">Fallback TTL for public
responses (0 = off)</label><input id="cache-default-ttl" max="604800" min="0"
required="" type="number" />
<label class="check"><input id="cache-query" type="checkbox" /><span
data-i18n="bypassQuery">Bypass URLs with a query string</span></label>
<label data-i18n="cachePrefixes" for="cache-prefixes">Bypass path prefixes, one per
line</label><textarea id="cache-prefixes" placeholder="/api/private" rows="3"
spellcheck="false"></textarea>
</div> </div>
<div data-cache-settings-page="resilience" hidden> <div data-cache-settings-page="resilience" hidden>
<h3 class="subheading" data-i18n="cacheCollapseTitle">Request collapsing</h3> <h3 class="subheading" data-i18n="cacheCollapseTitle">Request collapsing</h3>
+10 -9
View File
@@ -62,18 +62,19 @@
</article> </article>
<article class="stat stat-with-action" data-dashboard-widget="websocket"><span data-i18n="wsActive">WebSocket <article class="stat stat-with-action" data-dashboard-widget="websocket"><span data-i18n="wsActive">WebSocket
tunnels</span><button class="pill stat-corner-action stat-corner-badge" data-i18n="tunnelsOpen" tunnels</span><button class="pill stat-corner-action stat-corner-badge" data-i18n="tunnelsOpen"
data-ui-icon-name="none" id="tunnels-open" type="button">Active tunnels</button><strong data-ui-icon-name="none" id="tunnels-open" type="button">Active tunnels</button><div class="stat-value-row"><strong
id="metric-ws">0</strong><small aria-label="Active WebSocket tunnels by frontend protocol"><span id="metric-ws">0</strong><small class="stat-side-value" title="Established"><span
data-i18n="wsH1">H1 data-i18n="wsEstablished">Established</span> <b id="metric-ws-total">0</b></small></div><small
Upgrade</span> <span id="metric-ws-h1">0</span> · <span data-i18n="wsH2Rfc8441">H2 aria-label="Active WebSocket tunnels by frontend protocol"><span data-i18n="wsH1" title="H1 Upgrade">H1</span>
RFC 8441</span> <span id="metric-ws-h2">0</span> · <span data-i18n="wsH3Rfc9220">H3 <span id="metric-ws-h1">0</span> · <span data-i18n="wsH2Rfc8441" title="H2 RFC 8441">H2</span>
RFC 9220</span> <span id="metric-ws-h3">0</span><br><span data-i18n="wsEstablished">Established</span>: <span id="metric-ws-h2">0</span> · <span data-i18n="wsH3Rfc9220" title="H3 RFC 9220">H3</span>
<span id="metric-ws-total">0</span></small></article> <span id="metric-ws-h3">0</span></small></article>
<article class="stat" data-dashboard-widget="http2"><span data-i18n="h2Requests">HTTP/2 <article class="stat" data-dashboard-widget="http2"><span data-i18n="h2Requests">HTTP/2
requests</span><strong id="metric-h2">0</strong><small><span data-i18n="h2ActiveConnections">Active H2 requests</span><strong id="metric-h2">0</strong><small><span data-i18n="h2ActiveConnections">H2
connections</span>: <span id="metric-h2-active">0</span></small></article> connections</span>: <span id="metric-h2-active">0</span></small></article>
<article class="stat" data-dashboard-widget="http3"><span data-i18n="h3Requests">HTTP/3 <article class="stat" data-dashboard-widget="http3"><span data-i18n="h3Requests">HTTP/3
requests</span><strong id="metric-h3">0</strong></article> requests</span><strong id="metric-h3">0</strong><small><span data-i18n="h3ActiveConnections">H3
connections</span>: <span id="metric-h3-active">0</span></small></article>
<article class="stat" data-dashboard-widget="tls"><span data-i18n="tlsFailures">TLS handshake <article class="stat" data-dashboard-widget="tls"><span data-i18n="tlsFailures">TLS handshake
errors</span><strong id="metric-tls-errors">0</strong></article> errors</span><strong id="metric-tls-errors">0</strong></article>
<article class="stat stat-chart" data-dashboard-widget="http_protocol"><span data-i18n="httpProtocolMix">HTTP protocol <article class="stat stat-chart" data-dashboard-widget="http_protocol"><span data-i18n="httpProtocolMix">HTTP protocol
+61
View File
@@ -71,4 +71,65 @@ assert.match(shellCss, /prefers-reduced-motion: reduce[\s\S]*data-logo-effect/);
assert.equal(openapi.components.schemas.AdminPreferences.properties.logo_animation.default, false); assert.equal(openapi.components.schemas.AdminPreferences.properties.logo_animation.default, false);
assert.ok(pl.translations.logoAnimation && pl.translations.logoAnimationHelp); assert.ok(pl.translations.logoAnimation && pl.translations.logoAnimationHelp);
// Mobile/Safari regression: injected icons must never be mistaken for chart SVGs or
// stretched by descendant chart rules, and refreshes must not rebuild decorated buttons.
const runtimeJs = read("src/admin/web/app/runtime.js");
const scalesJs = read("src/admin/web/app/chart-scales.js");
const editorsCss = read("src/admin/web/css/40-editors.css");
assert.match(runtimeJs, /row\.querySelector\("svg:not\(\.ui-icon\)"\)/);
assert.doesNotMatch(runtimeJs, /row\.querySelector\("svg"\)/);
assert.match(runtimeJs, /querySelector\(":scope > svg\.ui-icon"\)/);
assert.match(scalesJs, /svg\.classList\.contains\("ui-icon"\)/);
assert.match(scalesJs, /querySelectorAll\("svg:not\(\.ui-icon\)"\)/);
for (const source of [css, overviewCss, editorsCss]) assert.doesNotMatch(source, /(^|\n)[^\n{}]*(\.network-interface-row|\.stat-chart|\.activity-card|\.system-card|\.function-live-chart|\.mini-live-chart|\.vhost-chart|\.connection-card) svg[ ,{]/, "descendant chart svg rules must exclude .ui-icon");
assert.match(css, /html\[data-interface-icons="true"\] :is\(button, a, summary\)>svg\.ui-icon/);
assert.match(icons, /\.mobile-menu-toggle"\) return null|draft-dock-toggle, \.mobile-menu-toggle/);
assert.match(editorsCss, /Spacing review \(0\.36\.2\)/);
assert.match(editorsCss, /\.vhost-backend-protocol-badge \{[^}]*letter-spacing: 1px;[^}]*\}/);
assert.match(editorsCss, /\.tls-pqc-control>a/);
console.log("Panel SVG icon preference contract: OK"); console.log("Panel SVG icon preference contract: OK");
// CSS cleanup (0.36.3): the legacy body[data-density] mechanism must not come back.
{
const cssDir = path.join(__dirname, "..", "src", "admin", "web", "css");
for (const name of fs.readdirSync(cssDir).filter((file) => file.endsWith(".css"))) {
assert.doesNotMatch(fs.readFileSync(path.join(cssDir, name), "utf8"), /body\[data-density/, `${name} still has legacy body[data-density] rules`);
}
}
// Cache outcomes bar shows percentages inside its segments and hides labels that do not fit.
{
const rt = fs.readFileSync(path.join(__dirname, "..", "src", "admin", "web", "app", "runtime.js"), "utf8");
assert.match(rt, /function fitCacheMixLabels/);
assert.match(rt, /segment\.textContent = mix && pct > 0/);
}
// Settings layout (0.36.3): rows pattern for Appearance and horizontal sub-tabs.
{
const css = fs.readFileSync(path.join(__dirname, "..", "src", "admin", "web", "css", "50-settings.css"), "utf8");
assert.match(css, /#settings-page-appearance \.appearance-settings-grid>label \{/);
assert.match(css, /#panel-settings \.settings-tabs\.sub-tabs \{\s*display: flex;\s*flex-direction: row;/);
}
// Chart styles (0.36.3): every style id must be wired through Rust validation, OpenAPI, JS, CSS tokens, the selector and the language pack.
{
const root = path.join(__dirname, "..");
const read = (file) => fs.readFileSync(path.join(root, file), "utf8");
const styles = ["default", "soft", "technical", "high_contrast", "neon", "monochrome", "warm", "cool", "sunset", "aurora", "ocean", "forest", "pastel", "terminal", "blueprint", "colorblind"];
const rust = read("src/admin/preferences.rs"), js = read("src/admin/web/app/preferences.js"), tokens = read("src/admin/web/css/00-tokens.css"), html = read("src/admin/web/index.html");
const openapi = JSON.parse(read("src/admin/openapi.json")).components.schemas;
const enumValues = JSON.stringify(openapi).match(/"chart_style":\{"type":"string","enum":(\[[^\]]*\])/);
assert.ok(enumValues, "chart_style enum must be present in OpenAPI");
const documented = JSON.parse(enumValues[1]);
for (const style of styles) {
assert.ok(documented.includes(style), `OpenAPI chart_style missing ${style}`);
assert.ok(rust.includes(`"${style}"`), `Rust validation missing ${style}`);
assert.ok(js.includes(`"${style}"`), `preferences.js missing ${style}`);
assert.ok(tokens.includes(`html[data-chart-style=${style}]`), `tokens missing ${style}`);
assert.ok(tokens.includes(`.chart-style-choice[data-chart-preset=${style}]`), `swatches missing ${style}`);
assert.ok(html.includes(`value="${style}"`), `selector missing ${style}`);
}
assert.equal(documented.length, styles.length, "OpenAPI chart_style enum must list exactly the supported styles");
}
+1 -1
View File
@@ -51,5 +51,5 @@ const events = openapi.paths["/api/events"].get;
const logsFlag = (events.parameters || []).find(parameter => parameter.name === "logs"); const logsFlag = (events.parameters || []).find(parameter => parameter.name === "logs");
assert.equal(logsFlag?.schema?.type, "boolean"); assert.equal(logsFlag?.schema?.type, "boolean");
assert.equal(logsFlag?.schema?.default, true); assert.equal(logsFlag?.schema?.default, true);
assert.equal(openapi.info.version, "0.36.2"); assert.equal(openapi.info.version, "0.36.3");
console.log("Panel refresh/API contract checks: OK"); console.log("Panel refresh/API contract checks: OK");