v0.33.6
This commit is contained in:
1 parent
5dd802ae83
commit
a286355b0e
22 files changed
+447
-70
No files matched your search
@@ -2,7 +2,7 @@
|
||||
|
||||
Read README.md, ROADMAP.md and docs/TEST_REPORT.md before making changes.
|
||||
The project name is RustProxy; the Cargo package/binary remains `rust-proxy` for
|
||||
compatibility. Current source version: 0.33.5.
|
||||
compatibility. Current source version: 0.33.6.
|
||||
|
||||
## Delivery
|
||||
|
||||
|
||||
+23
-7
@@ -3,15 +3,31 @@
|
||||
> Operator-facing release history. Historical engineering notes are kept in
|
||||
> [`docs/CHANGELOG_TECHNICAL_ARCHIVE.md`](docs/CHANGELOG_TECHNICAL_ARCHIVE.md).
|
||||
|
||||
## 0.33.6
|
||||
|
||||
- Normalize the height of all 14 large Overview/WWW plots to 125px, avoiding
|
||||
width-dependent vertical stretching; fix quarter-grid alignment.
|
||||
- Keep CPU at 0-100% and correct compact percentage sparklines to the same
|
||||
absolute bounds. Never hide actual CPU readings in labels.
|
||||
- Give transformation and compression activity, plus other unbounded rate,
|
||||
count, latency and memory series, minimum zero-based ranges instead of
|
||||
scaling near-zero readings to the plot ceiling. Extend only as necessary
|
||||
using rounded ticks, and hold the ceiling until chart reset.
|
||||
- Fix the full Cache/Transform rate plots at 180px across screen sizes.
|
||||
Preserve actual /s values, updates and history resets.
|
||||
- Add Chromium and helper-scale regressions for chart geometry and ranges.
|
||||
No changes to status APIs, polling, database schema or account settings.
|
||||
|
||||
## 0.33.5
|
||||
|
||||
- Autoscale the Overview **System and process CPU** time series using the
|
||||
measured CPU history instead of the fixed 0–100% vertical range. Actual
|
||||
CPU percentages in the summary and legend are unaffected.
|
||||
- Retain the same full, responsive plot height as other wide Overview charts.
|
||||
No other charts or dashboard sizing, data sources or settings are changed.
|
||||
- Add simulated Chromium checks for adaptive plot geometry, legend visibility
|
||||
and responsive chart heights at desktop and mobile viewport sizes.
|
||||
- Fix the Overview **System and process CPU** axis at 0–100% so low
|
||||
readings no longer expand to fill the chart. Clip plotted values above
|
||||
100% without changing the actual percentages shown in the legend.
|
||||
- Fix the CPU plot height at 125px across viewport widths and densities,
|
||||
independently of CPU readings. Other charts, their dimensions, data
|
||||
sources, dashboard layout and settings are unchanged.
|
||||
- Check fixed percent mapping, clipping, legend visibility and plot geometry
|
||||
with simulated Chromium at desktop and mobile viewport sizes.
|
||||
- Overview heading cleanup: move the existing request-rate maximum (`max XX.X
|
||||
req/s`) from the chart footer to the upper-right Requests per second header;
|
||||
remove its LIVE TRAFFIC eyebrow and LIVE badge. Remove the LIVE badge from
|
||||
|
||||
Generated
+1
-1
@@ -1881,7 +1881,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-proxy"
|
||||
version = "0.33.5"
|
||||
version = "0.33.6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rust-proxy"
|
||||
version = "0.33.5"
|
||||
version = "0.33.6"
|
||||
edition = "2021"
|
||||
rust-version = "1.99"
|
||||
description = "RustProxy - independent reverse proxy with local administration"
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# RustProxy 0.33.5
|
||||
# RustProxy 0.33.6
|
||||
|
||||
## 0.33.5: adaptive CPU chart scaling on Overview
|
||||
## 0.33.5: fixed CPU chart scale on Overview
|
||||
|
||||
- **System and process CPU** now uses the same adaptive vertical scale as other
|
||||
Overview time-series charts, instead of forcing a 0–100% axis. Low CPU
|
||||
readings are visible while the legend continues to show their actual values.
|
||||
- Keep the CPU chart at the same responsive height as other wide Overview
|
||||
charts (including HTTP protocol mix); do not shrink it or change any other
|
||||
charts, grid layout, sampling, APIs, databases or saved preferences.
|
||||
- **System and process CPU** uses a fixed 0–100% vertical scale, without
|
||||
autoscaling when measured CPU usage is low. Both lines and the legend retain
|
||||
real CPU percentages; values above 100% are clipped at the top of the plot.
|
||||
- Keep the CPU plot 125px high across viewport sizes and dashboard densities
|
||||
so it does not stretch neighboring widgets. Other chart scales and heights,
|
||||
grid layout, sampling, APIs, databases and saved preferences are unchanged.
|
||||
- Overview heading follow-up: **Requests per second** shows the existing
|
||||
dynamic `max XX.X req/s` value in the upper-right instead of the LIVE badge;
|
||||
remove the redundant LIVE TRAFFIC eyebrow and its former peak footer label.
|
||||
|
||||
+20
-7
@@ -1,13 +1,26 @@
|
||||
# RustProxy roadmap
|
||||
|
||||
## 0.33.5 - Adaptive Overview CPU chart scale
|
||||
## 0.33.6 - Stable dashboard chart ranges and plot heights
|
||||
|
||||
- [x] Autoscale only the System and process CPU timeline using its measured
|
||||
history; retain actual percentage labels and both plotted series.
|
||||
- [x] Give the CPU plot the standard wide-chart height; leave other chart
|
||||
scales, Overview cards and dashboard sizing behavior unchanged.
|
||||
- [x] Add simulated real-browser checks for plot geometry and low/high CPU
|
||||
samples across viewport widths and Dashboard densities.
|
||||
- [x] Fix all 14 Overview/WWW full-width SVG heights to 125px and align their
|
||||
grid lines with the actual drawing range.
|
||||
- [x] Apply zero-based, unit-specific scale floors to transform/compression,
|
||||
rate, connection, latency, memory and WWW series; retain meaningful high
|
||||
readings with stepped-up ceilings and no scale collapse on low samples.
|
||||
- [x] Keep CPU/other percentage sparklines on 0-100% without changing legends.
|
||||
- [x] Fix full Cache/Transform live charts at 180px and stabilize their rate
|
||||
axes, including scope-change resets.
|
||||
- [x] Test Chromium layout/scaling across 320-1920px and run available UI,
|
||||
schema and source tests. Rust runtime acceptance remains pending.
|
||||
|
||||
## 0.33.5 - Fixed Overview CPU chart scale
|
||||
|
||||
- [x] Set System and process CPU to an absolute 0–100% axis, independent of
|
||||
history, while keeping real percentage labels and both plotted series.
|
||||
- [x] Fix the CPU plot height at 125px regardless of viewport width or
|
||||
measured values; leave other chart scales and sizes unchanged.
|
||||
- [x] Check stable CPU chart geometry, percent mapping, clipping and labels
|
||||
in a simulated browser across viewport widths and Dashboard densities.
|
||||
- [x] Replace the Overview Requests per second LIVE badge with its live maximum
|
||||
rate, remove the LIVE TRAFFIC eyebrow and the duplicate peak footer entry.
|
||||
- [x] Remove Connection Activity's LIVE badge, preserving its metrics.
|
||||
|
||||
+8
-8
@@ -2,12 +2,12 @@
|
||||
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
|
||||
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
|
||||
ffae7ce20cc0fb1990394822515c933843ba39304aa44134403f0d25a6a66d9f AGENTS.md
|
||||
95f433de4f779002f6358209dbb178933eab2036f8e69dabda72c5946ef32716 CHANGELOG.md
|
||||
8b72ed7b17505c129b60faf97ee6a838b855e4303dc390ea7196b0d97ee79134 CHANGELOG.md
|
||||
a35a5d557bb57df37d130a03752ffc092f005cb4849733d3d81485fb9450a931 Cargo.lock
|
||||
8b77b2beffb31dd14ff62e225ff5384084d2b4e66faa100aaf38470883fca879 Cargo.toml
|
||||
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
|
||||
273fe8c9504f7565986b775850287fbc579a3de13aaffbfdd897a01ae64305fa README.md
|
||||
de605453cec3511eae8b010af5791eb7d526fb09569f91c503b9dd53d0c2fe0e ROADMAP.md
|
||||
dfe12a6a0d360e0febec982f30098123a8974007c7c3fba39509be5c45d3ebbd README.md
|
||||
65340bb1f93da256043cb6bd75b6c9de8539e31f0417f24c6f05093a27f8319f ROADMAP.md
|
||||
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
|
||||
f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs
|
||||
ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md
|
||||
@@ -64,7 +64,7 @@ ec7e32fd19e6ba6ed1732f1f3dd92ac0860609e8c0bbd3c1998840cdfb24bf54 docs/SECURITY.
|
||||
007537ee971463072b2482dfa4fdcf1955f837ced0b711d4eef3b6267d16d4f6 docs/SECURITY_AUDIT_0.31.0.md
|
||||
4dcf3243a13d9ae1827b5cefdbb9e27bd6310321c50d4d5934527d44437df6ef docs/SETTINGS.md
|
||||
8fc112cb45a2f35ef5b0dd4371aa6f43aca4df85db62c6ae02e56d74a3b702ae docs/STREAMS.md
|
||||
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 docs/TEST_REPORT.md
|
||||
72baf37ed0989de65123eba73f83d8c31cba7415382dd00e639d8ca412f0ef0d docs/TEST_REPORT.md
|
||||
cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md
|
||||
751c9b13b47d0959a8575d6e82bdecba988b950a374ba0b084fbba08b4b65ebe docs/TRANSFORM.md
|
||||
fe3ec6ca16ff933239c12dacf39dd7b45717c0551a8b9f6ce601bf78136c9e37 docs/TRANSPORT.md
|
||||
@@ -132,7 +132,7 @@ e69813fb1d3a4cdcfb7243964cc323fc21a603087bf2e84188fa42b9d3cee4b5 scripts/runtim
|
||||
f0b48ec0cc4411755cc0b4128eebce710375afa99344cb76198cf31074f8eb63 scripts/test_stream_udp.py
|
||||
cc7026ec9c5b69850a815816df093de0ffdf4aeadabb3af702783483122ede55 scripts/tests/dashboard_chart_headers_0335_browser_test.py
|
||||
aa3ca7419d6d7d506c5aaf4d624d20cc7e8ee66bec3cb0cf9b5e7d164bcbab57 scripts/tests/dashboard_chart_width_0333_browser_test.py
|
||||
3171c4e61c72e9ab12f41be7564fe68204aaa8bee7737b32ed6a2ca4e7a7ba94 scripts/tests/dashboard_cpu_autoscale_0335_browser_test.py
|
||||
fdcea6dd1e3bc160abe1139bf94b45c9a28f6aaf7afbe7e38fb452ee4892b8f1 scripts/tests/dashboard_cpu_fixed_scale_0335_browser_test.py
|
||||
52eacb467df123dc4a35f14933e006b090c9238df1b14dcff856a16b37c8063f scripts/tests/dashboard_layout_0331_browser_test.py
|
||||
276e732fc2dfb0d39badb159e4fdcba0d7d73509c60ad5c9df49621c03046f1b scripts/tests/dashboard_metric_visuals_ui_test.mjs
|
||||
61a0fb2c4de86f25debec9662aedd923bce8c2ccd51e75593d485b2e0c9b8551 scripts/tests/dashboard_modal_0333_browser_test.py
|
||||
@@ -200,7 +200,7 @@ f3b566d79ba47848021a74653dbd7fec72f0fb819c6b2a07259e047bc8c427fd src/admin/web/
|
||||
2f1669cbeb9f49aef43a548e2096825aa14c64c7097ffad520822bbbaefb2b4c src/admin/web/app/overview-extra-metrics.js
|
||||
5e62cccf6a9507fe208e9f41bbd08453c3d8c6a6b83c56d93a2bb22550ea6e9a src/admin/web/app/overview-masonry.js
|
||||
b52456b41451f156edf5f58492119c22726f2cf40cee938527748997e8ee7412 src/admin/web/app/overview-samples.js
|
||||
9c039138fa58d8382601a4b2de366ba7763beb4d35f1cff7899cf2ce36c55afb src/admin/web/app/overview-wide-metrics.js
|
||||
41d9d872ba2f8e1871564fa3f72e88bcb8f118c0ddd5255f256ac7230de945b6 src/admin/web/app/overview-wide-metrics.js
|
||||
5bde5f0881320464eb133cd44de9eff5211e6816e79f7f7e9574b16784fab126 src/admin/web/app/preferences.js
|
||||
8e75b0915edfbadefc4388701431541f4d35fd5d717363297b63740b3afd34d2 src/admin/web/app/rate-sampling.js
|
||||
43f4dddd1c3ea7fa42eeb9d7b655a8db48b5ddcc6c68213e08dfc2fcfbe8b17d src/admin/web/app/refresh.js
|
||||
@@ -234,7 +234,7 @@ e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/
|
||||
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
|
||||
7cbdef9192a9dee1f8eac42b85f52b3190f7f6efae50004aa7ad116a910e4036 src/admin/web/file-logs.js
|
||||
b14e7b3578322f309627f6fe965cc16abcf85da3437c9070be6caecae1908743 src/admin/web/host404.js
|
||||
840c6ef69b9db1d8c4b710ad0eadb4253ac729bd05a7f60e3bfaf41640dc57a3 src/admin/web/index.html
|
||||
0ebfde7081385aa7158f3b0f8f2b9355836e087c3d91a7c98d5187afbd47abd0 src/admin/web/index.html
|
||||
03ad9572bb14c34059636d612cf6b876d6361ce601817cff674c57b715dc65dd src/admin/web/integrations.js
|
||||
7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json
|
||||
cbd41678eadd994dc5252628ee8ef9551fa9f7d8694ca5abd206240d9a96ad30 src/admin/web/login.html
|
||||
@@ -244,7 +244,7 @@ c2e5f554810b8e34fb47361fb90a01073a2e332ee26d82e030956fad3ec1f83e src/admin/web/
|
||||
c28f4254ddc1f720d62646ee0f0b590b60a89c0bf447e8ec74ff3dde0c7635ba src/admin/web/settings.js
|
||||
8eadbada619b15087783dcfa0e889a81f88e78034d084c5ab616f7912b835940 src/admin/web/status_200.html
|
||||
c13cffe18a616fe5faa5ee945ab0d94d8d392ae8f32474c62fe41010e063ddb1 src/admin/web/streams.js
|
||||
7ffd017d38297a21160fd61926bb1d406b465e8fe610db9164234220dd8b111a src/admin/web/style.css
|
||||
cc0fdbed4a344487fef7593375177043ee4524b98f58842f37a97e2d2a414183 src/admin/web/style.css
|
||||
d290c048450655b3eeae7c6ec54a82d645d1965513694ab29fdf7eef796a1abd src/admin/web/traffic-panel.js
|
||||
69f412bd897e45f4889b71e9982cfff56df9ef20ba2da109b454713a5ecd7c74 src/admin/web/transform-panel.js
|
||||
d816f2f70eef398c18091479320c36d07f9afe07278a250b840ebcdd7bc715f2 src/admin/web/vhosts/core.js
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# RustProxy 0.33.6 - verification report
|
||||
|
||||
## Scope (2026-10-15)
|
||||
|
||||
Additional 0.33.6 chart/UI changes:
|
||||
- All 14 full-width Overview and Web Server plots retain a fixed 125px SVG
|
||||
height, regardless of viewport width and dashboard density.
|
||||
- Grid lines are mapped to the actual plotting range rather than approximate
|
||||
horizontal positions.
|
||||
- Transform and Compression activity (plus other unbounded unit series) use
|
||||
sane zero-based minimum ranges and monotonic, rounded range expansion, so
|
||||
low values do not look like maximum utilization and spikes remain visible.
|
||||
- Full Cache and Transform live charts keep an 180px plot height and the same
|
||||
stable per-second range policy. Compact percent sparklines stay 0-100%.
|
||||
|
||||
Previous 0.33.5 regression scope:
|
||||
|
||||
Correct the Overview **System and process CPU** widget:
|
||||
- Use a fixed 0-100% vertical range, not the measured-history peak.
|
||||
- Map 25%, 50% and 75% to the horizontal grid lines.
|
||||
- Clip plotted CPU values above 100% at the top without modifying their measured values in the legend or summary.
|
||||
- Fix only this plot's CSS height at 125px, independent of viewport width, dashboard density and CPU samples.
|
||||
- Leave other chart scales, plot dimensions, status sampling and dashboard preferences unchanged.
|
||||
|
||||
## Checks completed
|
||||
|
||||
- **PASS:** `python scripts/tests/dashboard_stable_scales_0336_browser_test.py`
|
||||
with real bundled HTML/CSS/JS in simulated Chromium at 320, 375, 768,
|
||||
1440 and 1920px and all three density modes. Covered 14 large plots,
|
||||
125/180px plot heights, quarter-grid alignment, 0-100% CPU mapping,
|
||||
steady low/high traffic plotting, minimum count/rate/latency/memory scales,
|
||||
sticky expanded scales, reset and non-clipped live operation-rate labels.
|
||||
|
||||
- **PASS:** `python scripts/tests/dashboard_cpu_fixed_scale_0335_browser_test.py` against the actual bundled HTML/CSS/JS in simulated Chromium, at 320, 375, 768, 1440 and 1920px, in normal/compact/ultra densities. Checked low and high CPU histories, missing samples, new status revisions, clipping above 100%, actual percentage labels, consistent grid positions, fixed plot height and no sample-dependent changes to the CPU/adjacent Connection Activity cards' heights.
|
||||
- **PASS:** All 11 prior `scripts/tests/*browser_test.py` simulated-browser regressions (including the 0.33.5 CPU test) and the new 0.33.6 browser regression: chart headers, resized widths, dashboard grid/modal resizing, sidebar, progress bars, System RAM, Web observability and Web Server metrics. No live RustProxy server was used.
|
||||
- **PASS:** `node scripts/tests/overview_samples_0330_ui_test.mjs`, `node scripts/tests/dashboard_resume_rate_charts_ui_test.mjs` and `node scripts/tests/dashboard_metric_visuals_ui_test.mjs`.
|
||||
- **PASS:** `./dev.sh doctor` and `python scripts/preflight.py` (source layout).
|
||||
- **PASS:** All eight `migrations/*.sql` applied successfully in two successive cycles to a disposable, in-memory SQLite database. This verifies SQL script execution/idempotency in this context, not an upgrade of production data.
|
||||
- **PASS:** Parsed `scripts/deploy/upgrade.py --help` and `scripts/migration/migrate_npm.py --help`; did not run a live upgrade or migration against an external database or service.
|
||||
|
||||
## Not verified in this environment
|
||||
|
||||
- `./dev.sh check` and `./dev.sh test` both exit 127: Cargo/rustc are unavailable. Therefore no Rust compilation, unit/integration suite, actual proxy runtime, real SSE feed, production database upgrade or live load test has been verified.
|
||||
- The real-runtime acceptance gate remains open until Rust builds and runs against representative test traffic.
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
"""0.33.5 fixed 0-100% CPU plot and height regression with production HTML/CSS/JS.
|
||||
|
||||
Uses Chromium with simulated authenticated DOM and status samples, not a live
|
||||
RustProxy server, SSE stream or real CPU telemetry.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import re
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
HTML = (ROOT / 'src/admin/web/index.html').read_text(encoding='utf-8')
|
||||
CSS = (ROOT / 'src/admin/web/style.css').read_text(encoding='utf-8')
|
||||
SCALE_JS = (ROOT / 'src/admin/web/app/chart-scales.js').read_text(encoding='utf-8')
|
||||
WIDE_JS = (ROOT / 'src/admin/web/app/overview-wide-metrics.js').read_text(encoding='utf-8')
|
||||
|
||||
|
||||
def inspect(page):
|
||||
return page.evaluate('''() => {
|
||||
const get = id => document.querySelector(`#dashboard-overview-grid [data-dashboard-widget="${id}"]`);
|
||||
const cpu = get('wide_cpu'), cpuSvg = cpu.querySelector('svg');
|
||||
const protocolSvg = get('wide_protocol_rates').querySelector('svg');
|
||||
const connection = get('connection_chart');
|
||||
const backendSvg = get('backend_traffic').querySelector('svg');
|
||||
const legend = cpu.querySelector('.overview-wide-legend').getBoundingClientRect();
|
||||
const bounds = cpu.getBoundingClientRect();
|
||||
const matrix = cpuSvg.getScreenCTM();
|
||||
return {
|
||||
cpuHeight: cpuSvg.getBoundingClientRect().height,
|
||||
cpuCardHeight: bounds.height,
|
||||
connectionCardHeight: connection.getBoundingClientRect().height,
|
||||
protocolHeight: protocolSvg.getBoundingClientRect().height,
|
||||
backendHeight: backendSvg.getBoundingClientRect().height,
|
||||
svgWidth: cpuSvg.getBoundingClientRect().width,
|
||||
drawnWidth: Math.hypot(matrix.a, matrix.b) * cpuSvg.viewBox.baseVal.width,
|
||||
ratio: cpuSvg.getAttribute('preserveAspectRatio'),
|
||||
cpuSeries: cpuSvg.querySelectorAll('.overview-wide-series').length,
|
||||
lineEffect: getComputedStyle(cpuSvg.querySelector('.overview-wide-series')).vectorEffect,
|
||||
gridEffect: getComputedStyle(cpuSvg.querySelector('.chart-grid')).vectorEffect,
|
||||
gridPath: cpuSvg.querySelector('.chart-grid').getAttribute('d'),
|
||||
legendInCard: legend.top >= bounds.top && legend.bottom <= bounds.bottom + 1,
|
||||
overflow: document.documentElement.scrollWidth - innerWidth,
|
||||
};
|
||||
}''')
|
||||
|
||||
|
||||
def render_cpu(page, revision, system, process):
|
||||
return page.evaluate('''({revision,system,process}) => {
|
||||
window.rustProxyWideOverview.render({revision,live_system:{
|
||||
cpu_percent:system, process_cpu_percent:process
|
||||
}});
|
||||
const read = name => {
|
||||
const prefix = `overview-wide-wide_cpu-${name}`;
|
||||
return {
|
||||
path: document.getElementById(`${prefix}-line`).getAttribute('d'),
|
||||
label: document.getElementById(`${prefix}-value`).textContent,
|
||||
};
|
||||
};
|
||||
return {system:read('system'),process:read('process')};
|
||||
}''', {'revision': revision, 'system': system, 'process': process})
|
||||
|
||||
|
||||
def ys(path):
|
||||
return [int(v) for v in re.findall(r'[ML]\d+\s+(\d+)', path)]
|
||||
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True, executable_path='/usr/bin/chromium', args=['--no-sandbox'])
|
||||
for viewport in (320, 375, 768, 1440, 1920):
|
||||
page = browser.new_page(viewport={'width': viewport, 'height': 950})
|
||||
errors = []
|
||||
page.on('pageerror', lambda error: errors.append(str(error)))
|
||||
page.set_content(HTML.replace('/assets/app___RUSTPROXY_ASSET_ID__.js', '')
|
||||
.replace('/assets/style___RUSTPROXY_ASSET_ID__.css', ''))
|
||||
page.add_style_tag(content=CSS)
|
||||
page.evaluate('''() => {
|
||||
document.documentElement.classList.remove('app-loading', 'app-startup-failed');
|
||||
document.getElementById('workspace').hidden = false;
|
||||
document.getElementById('panel-overview').hidden = false;
|
||||
const grid = document.getElementById('dashboard-overview-grid');
|
||||
grid.classList.add('is-aligned');
|
||||
for (const card of grid.querySelectorAll(':scope > [data-dashboard-widget]')) {
|
||||
card.hidden = !['wide_cpu', 'wide_protocol_rates', 'backend_traffic', 'connection_chart'].includes(card.dataset.dashboardWidget);
|
||||
}
|
||||
window.currentPanel = 'overview';
|
||||
window.$ = id => document.getElementById(id);
|
||||
window.fmt = (value, digits = 0) => Number(value).toFixed(digits);
|
||||
window.fmtBytes = value => `${value} B`;
|
||||
window.rustProxyOverviewSamples = {getLimit: () => 60, onChange: () => {}};
|
||||
}''')
|
||||
page.add_script_tag(content=SCALE_JS)
|
||||
page.add_script_tag(content=WIDE_JS)
|
||||
|
||||
for density in ('compact', 'normal', 'ultra'):
|
||||
page.evaluate('(density) => document.documentElement.dataset.dashboardDensity = density', density)
|
||||
result = inspect(page)
|
||||
assert result['protocolHeight'] >= 124, (viewport, density, result)
|
||||
assert abs(result['cpuHeight'] - 125) <= 1, (
|
||||
'CPU height must remain fixed at 125px', viewport, density, result)
|
||||
assert result['backendHeight'] > 0, (viewport, density, result)
|
||||
assert result['ratio'] == 'none' and result['cpuSeries'] == 2, result
|
||||
assert result['lineEffect'] == result['gridEffect'] == 'non-scaling-stroke', result
|
||||
assert result['gridPath'] == 'M0 47H600 M0 77H600 M0 107H600', result
|
||||
assert result['svgWidth'] > 100 and abs(result['svgWidth'] - result['drawnWidth']) < 2, result
|
||||
assert result['legendInCard'] and result['overflow'] <= 2, (viewport, density, result)
|
||||
|
||||
# Low measured CPU values stay near zero instead of filling the plot.
|
||||
first = render_cpu(page, 1, 0.5, 0.2)
|
||||
second = render_cpu(page, 1, 0.8, 0.6)
|
||||
assert first['system']['label'] == '0.5%' and second['process']['label'] == '0.6%'
|
||||
assert ys(second['system']['path']) == [136, 136], (viewport, second)
|
||||
assert ys(second['process']['path']) == [137, 136], (viewport, second)
|
||||
|
||||
# The Y axis is always 0-100% regardless of recorded history.
|
||||
third = render_cpu(page, 1, 3.4, 2.2)
|
||||
assert ys(third['system']['path'])[-1] == 133, (viewport, third)
|
||||
assert ys(third['process']['path'])[-1] == 134, (viewport, third)
|
||||
high = render_cpu(page, 1, 250, 123)
|
||||
assert high['system']['label'] == '250.0%' and high['process']['label'] == '123.0%'
|
||||
assert ys(high['system']['path'])[-1] == 17, (viewport, high)
|
||||
assert ys(high['process']['path'])[-1] == 17, (viewport, high)
|
||||
mid = render_cpu(page, 1, 50, 25)
|
||||
assert ys(mid['system']['path'])[-1] == 77, (viewport, mid)
|
||||
assert ys(mid['process']['path'])[-1] == 107, (viewport, mid)
|
||||
|
||||
# Revision changes reset history; missing readings do not plot a zero.
|
||||
idle = render_cpu(page, 2, 0, 0)
|
||||
assert ys(idle['system']['path']) == [137] and ys(idle['process']['path']) == [137]
|
||||
full = render_cpu(page, 2, 100, 100)
|
||||
assert ys(full['system']['path'])[-1] == 17 and ys(full['process']['path'])[-1] == 17
|
||||
missing = render_cpu(page, 3, None, None)
|
||||
assert missing['system']['path'] == '' and missing['process']['label'] == '-'
|
||||
|
||||
# Samples may change paths, but not the plot/card or neighboring card sizes.
|
||||
for density in ('compact', 'normal', 'ultra'):
|
||||
page.evaluate('(density) => document.documentElement.dataset.dashboardDensity = density', density)
|
||||
unchanged = inspect(page)
|
||||
assert abs(unchanged['cpuHeight'] - 125) <= 1, (viewport, density, unchanged)
|
||||
for value in (0.1, 99.9, 250, 0.0):
|
||||
before = inspect(page)
|
||||
render_cpu(page, 4, value, value)
|
||||
after = inspect(page)
|
||||
for key in ('cpuHeight', 'cpuCardHeight', 'connectionCardHeight'):
|
||||
assert abs(before[key] - after[key]) < 1, (viewport, density, value, key, before, after)
|
||||
assert not errors, (viewport, errors)
|
||||
print(f'PASS CPU fixed 0-100% Y-scale and 125px plot height at {viewport}px (all densities)')
|
||||
page.close()
|
||||
browser.close()
|
||||
@@ -39,7 +39,7 @@ const context = vm.createContext({
|
||||
t: key => lang[key] || key,
|
||||
console,
|
||||
});
|
||||
for (const script of ['app/rate-sampling.js', 'app/overview-extra-metrics.js', 'app/overview-wide-metrics.js']) {
|
||||
for (const script of ['app/rate-sampling.js', 'app/chart-scales.js', 'app/overview-extra-metrics.js', 'app/overview-wide-metrics.js']) {
|
||||
vm.runInContext(code(script), context, { filename: script });
|
||||
}
|
||||
const extra = window.rustProxyExtraOverview, wide = window.rustProxyWideOverview;
|
||||
|
||||
@@ -42,7 +42,7 @@ const context = vm.createContext({
|
||||
$: id => ids.get(id) || null, fmt: (value, n = 0) => Number(value).toFixed(n),
|
||||
fmtBytes: value => `${value} B`, t: value => value,
|
||||
});
|
||||
for (const file of ['app/rate-sampling.js', 'app/overview-wide-metrics.js', 'app/web-overview.js'])
|
||||
for (const file of ['app/rate-sampling.js', 'app/chart-scales.js', 'app/overview-wide-metrics.js', 'app/web-overview.js'])
|
||||
vm.runInContext(code(file), context, { filename: file });
|
||||
|
||||
const web = {
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3
|
||||
"""0.33.6 plot size/scale checks in simulated Chromium, not a running RustProxy."""
|
||||
from pathlib import Path
|
||||
import re
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
HTML = (ROOT / 'src/admin/web/index.html').read_text(encoding='utf-8')
|
||||
CSS = (ROOT / 'src/admin/web/style.css').read_text(encoding='utf-8')
|
||||
SCALES = (ROOT / 'src/admin/web/app/chart-scales.js').read_text(encoding='utf-8')
|
||||
RATES = (ROOT / 'src/admin/web/app/rate-sampling.js').read_text(encoding='utf-8')
|
||||
WIDE = (ROOT / 'src/admin/web/app/overview-wide-metrics.js').read_text(encoding='utf-8')
|
||||
|
||||
WIDE_IDS = ('wide_protocol_rates', 'wide_cpu', 'wide_memory', 'wide_cache_activity',
|
||||
'wide_connections', 'wide_backend_health', 'wide_latency',
|
||||
'wide_transform_activity', 'wide_compression_activity',
|
||||
'www_requests', 'www_latency', 'www_failures', 'www_traffic', 'www_status_trend')
|
||||
|
||||
with sync_playwright() as api:
|
||||
browser = api.chromium.launch(headless=True, executable_path='/usr/bin/chromium', args=['--no-sandbox'])
|
||||
for width in (320, 375, 768, 1440, 1920):
|
||||
page = browser.new_page(viewport={'width': width, 'height': 920})
|
||||
errors = []
|
||||
page.on('pageerror', lambda error: errors.append(str(error)))
|
||||
page.set_content(HTML.replace('/assets/app___RUSTPROXY_ASSET_ID__.js', '')
|
||||
.replace('/assets/style___RUSTPROXY_ASSET_ID__.css', ''))
|
||||
page.add_style_tag(content=CSS)
|
||||
page.evaluate('''() => {
|
||||
document.documentElement.classList.remove('app-loading','app-startup-failed');
|
||||
document.getElementById('workspace').hidden = false;
|
||||
document.getElementById('panel-overview').hidden = false;
|
||||
document.getElementById('dashboard-overview-grid').classList.add('is-aligned');
|
||||
for (const card of document.querySelectorAll('#dashboard-overview-grid>[data-dashboard-widget]'))
|
||||
card.hidden = !['wide_protocol_rates','wide_cpu','wide_memory','wide_cache_activity',
|
||||
'wide_connections','wide_backend_health','wide_latency',
|
||||
'wide_transform_activity','wide_compression_activity',
|
||||
'www_requests','www_latency','www_failures','www_traffic','www_status_trend'].includes(card.dataset.dashboardWidget);
|
||||
window.currentPanel='overview';
|
||||
window.$ = id => document.getElementById(id);
|
||||
window.fmt = (n, digits=0) => Number(n).toFixed(digits);
|
||||
window.fmtBytes = n => `${n} B`;
|
||||
window.rustProxyOverviewSamples = {getLimit:()=>60,onChange:()=>{}};
|
||||
window.rustProxyRefresh = {current:()=>({overview_seconds:3})};
|
||||
window.__clock = 1000;
|
||||
Object.defineProperty(performance,'now',{configurable:true,value:()=>window.__clock});
|
||||
}''')
|
||||
for script in (RATES, SCALES, WIDE):
|
||||
page.add_script_tag(content=script)
|
||||
for density in ('normal','compact','ultra'):
|
||||
measured = page.evaluate('''density => {
|
||||
document.documentElement.dataset.dashboardDensity=density;
|
||||
const results={};
|
||||
for (const card of document.querySelectorAll('#dashboard-overview-grid > .overview-wide-chart')) {
|
||||
const svg=card.querySelector('svg.overview-wide-svg');
|
||||
if (!svg) continue;
|
||||
const r=svg.getBoundingClientRect();
|
||||
results[card.dataset.dashboardWidget]={height:r.height, width:r.width,
|
||||
grid:svg.querySelector('.chart-grid').getAttribute('d'),
|
||||
contentWidth:Math.hypot(svg.getScreenCTM().a,svg.getScreenCTM().b)*600};
|
||||
}
|
||||
document.getElementById('panel-transform').hidden=false;
|
||||
document.getElementById('panel-cache').hidden=false;
|
||||
for(const id of ['transform-live-chart','cache-live-chart'])document.getElementById(id).open=true;
|
||||
results.transformDetails=document.getElementById('transform-live-activity-chart').getBoundingClientRect().height;
|
||||
results.cacheDetails=document.getElementById('cache-live-activity-chart').getBoundingClientRect().height;
|
||||
return results;
|
||||
}''', density)
|
||||
assert abs(measured['transformDetails'] - 180) < 1, (width,density,measured)
|
||||
assert abs(measured['cacheDetails'] - 180) < 1, (width,density,measured)
|
||||
for name in WIDE_IDS:
|
||||
data=measured[name]
|
||||
assert abs(data['height']-125)<1, (width,density,name,data)
|
||||
assert data['width']>50 and abs(data['width']-data['contentWidth'])<2, (width,density,name,data)
|
||||
assert data['grid']=='M0 47H600 M0 77H600 M0 107H600', (width,density,name,data)
|
||||
result = page.evaluate('''() => {
|
||||
const c=window.rustProxyChartScale;
|
||||
const first=c.peak('probe:rate','rate',[[.2,.7]]);
|
||||
const count=c.peak('probe:count','count',[[0,1]]);
|
||||
const latency=c.peak('probe:ms','ms',[[.1,1.5]]);
|
||||
const memory=c.peak('probe:bytes','bytes',[[1000]]);
|
||||
const high=c.peak('probe:rate','rate',[[101]]);
|
||||
const held=c.peak('probe:rate','rate',[[.3]]);
|
||||
c.reset('probe:');
|
||||
const reset=c.peak('probe:rate','rate',[[.3]]);
|
||||
const percent=c.peak('probe:cpu','percent',[[.1,500]]);
|
||||
const blank=c.peak('probe:blank','rate',[[null,NaN,Infinity]]);
|
||||
const before={revision:10,live_system:{cpu_percent:0.7,process_cpu_percent:0.2},
|
||||
metrics:{compression_gzip:50,compression_br:100,compression_zstd:10,compression_deflate:5},
|
||||
transform_metrics:{applied:2,cache_hits:10,skipped:1,errors:0}};
|
||||
window.rustProxyWideOverview.render(before);
|
||||
window.__clock=4000;
|
||||
before.metrics.compression_gzip+=3;before.metrics.compression_br+=6;
|
||||
before.metrics.compression_zstd+=0;before.metrics.compression_deflate+=0;
|
||||
before.transform_metrics.applied+=6;before.transform_metrics.cache_hits+=3;
|
||||
window.rustProxyWideOverview.render(before);
|
||||
const get = (id,key) => document.getElementById(`overview-wide-${id}-${key}-line`).getAttribute('d');
|
||||
const compressionLow=get('wide_compression_activity','gzip');
|
||||
const transformLow=get('wide_transform_activity','applied');
|
||||
const cpu=get('wide_cpu','system');
|
||||
const lowLabel=document.getElementById('overview-wide-wide_compression_activity-gzip-value').textContent;
|
||||
window.__clock=7000;
|
||||
before.metrics.compression_gzip+=603; // now 201/s => ceiling expands to 500/s
|
||||
before.transform_metrics.applied+=603;
|
||||
window.rustProxyWideOverview.render(before);
|
||||
const highLabel=document.getElementById('overview-wide-wide_compression_activity-gzip-value').textContent;
|
||||
const compressionHigh=get('wide_compression_activity','gzip');
|
||||
window.__clock=10000;
|
||||
before.metrics.compression_gzip+=3;
|
||||
before.transform_metrics.applied+=3;
|
||||
window.rustProxyWideOverview.render(before);
|
||||
const afterSpike=get('wide_compression_activity','gzip');
|
||||
return {first,count,latency,memory,high,held,reset,percent,blank,
|
||||
lowLabel,highLabel,compressionLow,transformLow,cpu,compressionHigh,afterSpike};
|
||||
}''')
|
||||
assert result['first']==100 and result['count']==10 and result['latency']==100, (width,result)
|
||||
assert result['memory']>=64*1024*1024, (width,result)
|
||||
assert (result['high'],result['held'],result['reset'],result['percent'],result['blank']) == (200,200,100,100,100), (width,result)
|
||||
assert result['lowLabel']=='1.0/s' and result['highLabel']=='201.0/s', (width,result)
|
||||
def yvalues(line): return [int(n) for n in re.findall(r'[ML]\d+ (\d+)',line)]
|
||||
assert yvalues(result['compressionLow'])[-1]>=135, (width,result)
|
||||
assert yvalues(result['transformLow'])[-1]>=130, (width,result)
|
||||
assert yvalues(result['cpu'])==[136,136], (width,result)
|
||||
assert yvalues(result['compressionHigh'])[-1]<100, (width,result)
|
||||
assert yvalues(result['afterSpike'])[-1]>=135, (width,result)
|
||||
assert not errors, (width,errors)
|
||||
print('PASS all 14 overview-wide plot sizes/grids + full Cache/Transform charts + stable scales:', width)
|
||||
page.close()
|
||||
browser.close()
|
||||
@@ -93,6 +93,7 @@ assert.equal(again.window.rustProxyOverviewSamples.getLimit(), 300);
|
||||
// Wide Overview charts must respect the same limit and redraw immediately when
|
||||
// the limit shrinks, without recording an extra point or hitting the network.
|
||||
vm.runInContext(read('src/admin/web/app/rate-sampling.js'), context);
|
||||
vm.runInContext(read('src/admin/web/app/chart-scales.js'), context);
|
||||
vm.runInContext(read('src/admin/web/app/overview-wide-metrics.js'), context);
|
||||
select.value = '600'; select.fire('change');
|
||||
const status = {
|
||||
|
||||
@@ -64,6 +64,7 @@ fn admin_app_javascript() -> &'static str {
|
||||
include_str!("web/app/session-resume.js"),
|
||||
include_str!("web/app/refresh.js"),
|
||||
include_str!("web/app/rate-sampling.js"),
|
||||
include_str!("web/app/chart-scales.js"),
|
||||
"const ADMIN_DASHBOARD_WIDGET_REGISTRY = Object.freeze(",
|
||||
include_str!("web/dashboard-widgets.json"),
|
||||
");",
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Stable plot ranges: percentages are absolute 0-100; unbounded metrics have
|
||||
// unit-appropriate zero-based floors, expanding only for real higher readings.
|
||||
window.rustProxyChartScale = (() => {
|
||||
const floors = { rate: 100, count: 10, ms: 100, bytes: 64 * 1024 * 1024, "bytes/s": 64 * 1024 };
|
||||
const ceilings = new Map();
|
||||
function peak(key, unit, series) {
|
||||
if (unit === "percent") return 100;
|
||||
const floor = floors[unit] ?? 1;
|
||||
let maximum = floor;
|
||||
for (const values of series) for (const value of values) {
|
||||
if (typeof value === "number" && Number.isFinite(value) && value >= 0)
|
||||
maximum = Math.max(maximum, value);
|
||||
}
|
||||
const magnitude = 10 ** Math.floor(Math.log10(maximum));
|
||||
const tick = [1, 2, 5, 10].find(n => n * magnitude >= maximum) ?? 10;
|
||||
const result = Math.max(ceilings.get(key) ?? 0, tick * magnitude);
|
||||
ceilings.set(key, result);
|
||||
return result;
|
||||
}
|
||||
function reset(prefix) {
|
||||
for (const key of ceilings.keys()) if (key.startsWith(prefix)) ceilings.delete(key);
|
||||
}
|
||||
return { peak, reset };
|
||||
})();
|
||||
@@ -134,10 +134,10 @@ window.rustProxyExtraOverview = (() => {
|
||||
if (unit === "rate") return `${fmt(value, 1)}/s`;
|
||||
return fmt(value);
|
||||
}
|
||||
function points(history, unit) {
|
||||
function points(id, history, unit) {
|
||||
if (!history.length) return "0,55 300,55";
|
||||
const peak = unit === "percent" ? Math.max(100, ...history) : Math.max(1, ...history);
|
||||
return history.map((v, i) => `${i * 300 / Math.max(1, history.length - 1)},${55 - v / peak * 50}`).join(" ");
|
||||
const peak = window.rustProxyChartScale.peak(`extra:${id}`, unit, [history]);
|
||||
return history.map((v, i) => `${i * 300 / Math.max(1, history.length - 1)},${55 - Math.min(v, peak) / peak * 50}`).join(" ");
|
||||
}
|
||||
function render(status, record = true) {
|
||||
if (!status || currentPanel !== "overview") return;
|
||||
@@ -150,6 +150,7 @@ window.rustProxyExtraOverview = (() => {
|
||||
const value = metric.value;
|
||||
if (lastUnits.get(id) !== metric.unit) {
|
||||
histories.delete(id);
|
||||
window.rustProxyChartScale.reset(`extra:${id}`);
|
||||
lastUnits.set(id, metric.unit);
|
||||
}
|
||||
if (!bounded.has(id) && id !== "extra_cache_hit_rate") {
|
||||
@@ -159,7 +160,7 @@ window.rustProxyExtraOverview = (() => {
|
||||
if (history.length > limit()) history.shift();
|
||||
histories.set(id, history);
|
||||
}
|
||||
$(`overview-extra-${id}-line`)?.setAttribute("points", points(history, metric.unit));
|
||||
$(`overview-extra-${id}-line`)?.setAttribute("points", points(id, history, metric.unit));
|
||||
}
|
||||
if (record) {
|
||||
lastValue.set(id, value);
|
||||
@@ -177,7 +178,7 @@ window.rustProxyExtraOverview = (() => {
|
||||
if (detail && id !== "extra_cache_hit_rate") detail.textContent = metric.detail || t("overviewExtraRecentSamples");
|
||||
}
|
||||
}
|
||||
function reset() { histories.clear(); lastCounter.clear(); lastValue.clear(); lastUnits.clear(); lastSegments.clear(); }
|
||||
function reset() { histories.clear(); lastCounter.clear(); lastValue.clear(); lastUnits.clear(); lastSegments.clear(); window.rustProxyChartScale.reset("extra:"); }
|
||||
window.rustProxyOverviewSamples?.onChange?.(() => {
|
||||
for (const history of histories.values()) window.rustProxyOverviewSamples.trim(history);
|
||||
if (typeof latestStatus !== "undefined" && latestStatus && currentPanel === "overview") render(latestStatus, false);
|
||||
|
||||
@@ -6,7 +6,7 @@ window.rustProxyWideOverview = (() => {
|
||||
[s.metrics?.http1_requests, s.metrics?.http2_requests, s.metrics?.http3_requests]
|
||||
},
|
||||
wide_cpu: {
|
||||
unit: "percent", names: ["system", "process"], values: s =>
|
||||
unit: "percent", names: ["system", "process"], max: 100, values: s =>
|
||||
[(s.live_system || s.system)?.cpu_percent, (s.live_system || s.system)?.process_cpu_percent]
|
||||
},
|
||||
wide_memory: {
|
||||
@@ -72,7 +72,8 @@ window.rustProxyWideOverview = (() => {
|
||||
const value = values[i];
|
||||
if (value === null) { drawing = false; continue; }
|
||||
const x = Math.round(600 * i / Math.max(1, count - 1));
|
||||
const y = Math.round(137 - 120 * value / peak);
|
||||
// Fixed-range charts stop at the upper bound; the legend keeps the real reading.
|
||||
const y = Math.round(137 - 120 * Math.min(value, peak) / peak);
|
||||
result += `${drawing ? " L" : " M"}${x} ${y}`;
|
||||
drawing = true;
|
||||
}
|
||||
@@ -98,7 +99,7 @@ window.rustProxyWideOverview = (() => {
|
||||
}
|
||||
last.set(id, values);
|
||||
}
|
||||
const peak = Math.max(1, ...series.flat().filter(n => n !== null));
|
||||
const peak = config.max ?? window.rustProxyChartScale.peak(`wide:${id}`, config.unit, series);
|
||||
config.names.forEach((key, i) => {
|
||||
const value = values[i];
|
||||
const field = $(`overview-wide-${id}-${key}-value`);
|
||||
@@ -111,7 +112,7 @@ window.rustProxyWideOverview = (() => {
|
||||
: format(values[0], config.unit);
|
||||
}
|
||||
}
|
||||
function reset() { history.clear(); baselines.clear(); last.clear(); revision = null; }
|
||||
function reset() { history.clear(); baselines.clear(); last.clear(); revision = null; window.rustProxyChartScale.reset("wide:"); }
|
||||
window.rustProxyOverviewSamples?.onChange?.(() => {
|
||||
for (const series of history.values()) for (const values of series) window.rustProxyOverviewSamples.trim(values);
|
||||
if (typeof latestStatus !== "undefined" && latestStatus && currentPanel === "overview") render(latestStatus, false);
|
||||
|
||||
@@ -87,7 +87,7 @@ window.rustProxyWebOverview = (() => {
|
||||
return output;
|
||||
}
|
||||
function smallPath(values) {
|
||||
const peak = Math.max(1, ...values.filter(v => v != null));
|
||||
const peak = window.rustProxyChartScale.peak("web:php-small", "rate", [values]);
|
||||
return values.filter(value => value !== null).map((value, i, present) => `${i * 300 / Math.max(1, present.length - 1)},${55 - value / peak * 50}`).join(" ");
|
||||
}
|
||||
function ring(id, amounts) {
|
||||
@@ -173,7 +173,8 @@ window.rustProxyWebOverview = (() => {
|
||||
names.forEach((_, i) => { series[i].push(set[id]?.[i] ?? null); window.rustProxyOverviewSamples.trim(series[i]); });
|
||||
histories.set(id, series);
|
||||
}
|
||||
const peak = Math.max(1, ...series.flat().filter(v => v !== null));
|
||||
const unit = id === "www_latency" ? "ms" : id === "www_traffic" ? "bytes/s" : "rate";
|
||||
const peak = window.rustProxyChartScale.peak(`web:${id}`, unit, series);
|
||||
names.forEach((name, i) => {
|
||||
const value = set[id]?.[i];
|
||||
const label = id === "www_latency" ? (value == null ? "-" : `${fmt(value, 1)} ms`)
|
||||
@@ -216,7 +217,7 @@ window.rustProxyWebOverview = (() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
function reset() { histories.clear(); serviceBaselines.clear(); baseline = null; last = null; revision = null; }
|
||||
function reset() { histories.clear(); serviceBaselines.clear(); baseline = null; last = null; revision = null; window.rustProxyChartScale.reset("web:"); }
|
||||
window.rustProxyOverviewSamples.onChange(() => {
|
||||
for (const history of histories.values()) {
|
||||
if (Array.isArray(history[0])) history.forEach(window.rustProxyOverviewSamples.trim);
|
||||
|
||||
@@ -70,12 +70,13 @@
|
||||
function resetLiveChart(selected = scope()) {
|
||||
liveChartScope = selected; liveChartPrevious = null; liveChartPreviousAt = null; liveChartGeneration = null;
|
||||
for (const values of Object.values(liveChartSamples)) values.length = 0;
|
||||
window.rustProxyChartScale.reset("cache-live");
|
||||
for (const id of ["cache-live-hit-line", "cache-live-miss-line", "cache-live-bypass-line"]) $(id)?.setAttribute("points", "0,170 900,170");
|
||||
for (const id of ["cache-live-hit-rate", "cache-live-miss-rate", "cache-live-bypass-rate"]) if ($(id)) $(id).textContent = "0.0/s";
|
||||
if ($("cache-live-chart-value")) $("cache-live-chart-value").textContent = "-";
|
||||
}
|
||||
function redrawLiveChart() {
|
||||
const peak = Math.max(1, ...liveChartSamples.hits, ...liveChartSamples.misses, ...liveChartSamples.bypasses);
|
||||
const peak = window.rustProxyChartScale.peak("cache-live", "rate", Object.values(liveChartSamples));
|
||||
$("cache-live-hit-line")?.setAttribute("points", sparklinePoints(liveChartSamples.hits, 900, 180, 10, peak));
|
||||
$("cache-live-miss-line")?.setAttribute("points", sparklinePoints(liveChartSamples.misses, 900, 180, 10, peak));
|
||||
$("cache-live-bypass-line")?.setAttribute("points", sparklinePoints(liveChartSamples.bypasses, 900, 180, 10, peak));
|
||||
|
||||
+16
-16
@@ -651,7 +651,7 @@
|
||||
</div>
|
||||
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
aria-label="HTTP/1.1 · HTTP/2 · HTTP/3 traffic">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0"
|
||||
id="overview-wide-wide_protocol_rates-h1-line" d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1"
|
||||
@@ -675,7 +675,7 @@
|
||||
</div>
|
||||
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
aria-label="System and process CPU">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0"
|
||||
id="overview-wide-wide_cpu-system-line" d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1"
|
||||
@@ -697,7 +697,7 @@
|
||||
</div>
|
||||
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
aria-label="Process RAM and cache memory">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0"
|
||||
id="overview-wide-wide_memory-process-line" d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1"
|
||||
@@ -720,7 +720,7 @@
|
||||
</div>
|
||||
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
aria-label="Cache hits / misses per second">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0"
|
||||
id="overview-wide-wide_cache_activity-hits-line" d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1"
|
||||
@@ -741,7 +741,7 @@
|
||||
</div>
|
||||
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
aria-label="Connections and WebSocket tunnels">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0"
|
||||
id="overview-wide-wide_connections-open-line" d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1"
|
||||
@@ -764,7 +764,7 @@
|
||||
</div>
|
||||
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
aria-label="Backend availability over time">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0"
|
||||
id="overview-wide-wide_backend_health-up-line" d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1"
|
||||
@@ -786,7 +786,7 @@
|
||||
</div>
|
||||
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
aria-label="Response and backend probe latency">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0"
|
||||
id="overview-wide-wide_latency-response-line" d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1"
|
||||
@@ -809,7 +809,7 @@
|
||||
</div>
|
||||
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
data-i18n-aria="overviewWideTransformActivity" aria-label="Transformation activity">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0"
|
||||
id="overview-wide-wide_transform_activity-applied-line" d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1"
|
||||
@@ -842,7 +842,7 @@
|
||||
</div>
|
||||
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
data-i18n-aria="overviewWideCompressionActivity" aria-label="Compression activity">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0"
|
||||
id="overview-wide-wide_compression_activity-gzip-line" d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1"
|
||||
@@ -954,7 +954,7 @@
|
||||
</div>
|
||||
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
aria-label="Web Server request rate" data-i18n-aria="webMetricRequests">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0" id="www_requests-all-line"
|
||||
d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1" id="www_requests-php-line"
|
||||
@@ -975,7 +975,7 @@
|
||||
</div>
|
||||
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
aria-label="Web Server header latency" data-i18n-aria="webMetricLatency">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0" id="www_latency-web-line"
|
||||
d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1" id="www_latency-php-line"
|
||||
@@ -996,7 +996,7 @@
|
||||
</div>
|
||||
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
aria-label="PHP FastCGI failures" data-i18n-aria="webMetricFailures">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0" id="www_failures-timeout-line"
|
||||
d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1"
|
||||
@@ -1027,7 +1027,7 @@
|
||||
</div>
|
||||
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
aria-label="Web Server body byte rate" data-i18n-aria="webMetricTraffic">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0" id="www_traffic-nonphp-line"
|
||||
d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1" id="www_traffic-php-line"
|
||||
@@ -1048,7 +1048,7 @@
|
||||
</div>
|
||||
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
aria-label="Web Server HTTP statuses" data-i18n-aria="webMetricStatusTrend">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0" id="www_status_trend-2xx-line"
|
||||
d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1" id="www_status_trend-3xx-line"
|
||||
@@ -2293,7 +2293,7 @@
|
||||
<svg aria-label="Live cache activity" data-i18n-aria="cacheLiveChartTitle"
|
||||
id="cache-live-activity-chart" role="img" viewBox="0 0 900 180"
|
||||
preserveAspectRatio="none">
|
||||
<path class="chart-grid" d="M0 45H900 M0 90H900 M0 135H900"></path>
|
||||
<path class="chart-grid" d="M0 50H900 M0 90H900 M0 130H900"></path>
|
||||
<polyline class="function-live-line cache-hit-line" id="cache-live-hit-line"
|
||||
points="0,170 900,170"></polyline>
|
||||
<polyline class="function-live-line cache-miss-line" id="cache-live-miss-line"
|
||||
@@ -2446,7 +2446,7 @@
|
||||
<svg aria-label="Live transformation activity" data-i18n-aria="transformLiveChartTitle"
|
||||
id="transform-live-activity-chart" role="img" viewBox="0 0 900 180"
|
||||
preserveAspectRatio="none">
|
||||
<path class="chart-grid" d="M0 45H900 M0 90H900 M0 135H900"></path>
|
||||
<path class="chart-grid" d="M0 50H900 M0 90H900 M0 130H900"></path>
|
||||
<polyline class="function-live-line transform-applied-line"
|
||||
id="transform-live-applied-line" points="0,170 900,170"></polyline>
|
||||
<polyline class="function-live-line transform-cache-line" id="transform-live-cache-line"
|
||||
|
||||
@@ -7177,7 +7177,7 @@ body[data-density=compact] .page-footer {
|
||||
.function-live-chart svg {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: calc(clamp(180px, 22vw, 270px));
|
||||
height: 180px;
|
||||
overflow: visible
|
||||
}
|
||||
|
||||
@@ -7270,9 +7270,6 @@ body[data-density=compact] .page-footer {
|
||||
white-space: normal
|
||||
}
|
||||
|
||||
.function-live-chart svg {
|
||||
height: 190px
|
||||
}
|
||||
|
||||
.function-live-chart-footer {
|
||||
align-items: flex-start;
|
||||
@@ -10889,7 +10886,7 @@ html[data-dashboard-density=ultra] #panel-overview .dashboard-overview-grid>.car
|
||||
#panel-overview .overview-wide-svg {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: calc(clamp(125px, 13vw, 190px));
|
||||
height: 125px;
|
||||
margin-block: 13px 9px;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,12 +22,13 @@
|
||||
function resetLiveChart(selected = scope()) {
|
||||
liveChartScope = selected; liveChartPrevious = null; liveChartPreviousAt = null; liveChartGeneration = null;
|
||||
for (const values of Object.values(liveChartSamples)) values.length = 0;
|
||||
window.rustProxyChartScale.reset("transform-live");
|
||||
for (const id of ["transform-live-applied-line", "transform-live-cache-line", "transform-live-skipped-line", "transform-live-error-line"]) $(id)?.setAttribute("points", "0,170 900,170");
|
||||
for (const id of ["transform-live-applied-rate", "transform-live-cache-rate", "transform-live-skipped-rate", "transform-live-error-rate"]) if ($(id)) $(id).textContent = "0.0/s";
|
||||
if ($("transform-live-chart-value")) $("transform-live-chart-value").textContent = "-";
|
||||
}
|
||||
function redrawLiveChart() {
|
||||
const peak = Math.max(1, ...liveChartSamples.applied, ...liveChartSamples.cache_hits, ...liveChartSamples.skipped, ...liveChartSamples.errors);
|
||||
const peak = window.rustProxyChartScale.peak("transform-live", "rate", Object.values(liveChartSamples));
|
||||
$("transform-live-applied-line")?.setAttribute("points", sparklinePoints(liveChartSamples.applied, 900, 180, 10, peak));
|
||||
$("transform-live-cache-line")?.setAttribute("points", sparklinePoints(liveChartSamples.cache_hits, 900, 180, 10, peak));
|
||||
$("transform-live-skipped-line")?.setAttribute("points", sparklinePoints(liveChartSamples.skipped, 900, 180, 10, peak));
|
||||
|
||||
Reference in new issue
Block a user