diff --git a/AGENTS.md b/AGENTS.md index 18599b7..30ec879 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ Read README.md, ROADMAP.md and docs/TEST_REPORT.md before making changes. The project name is RustProxy; the Cargo package/binary remains `rust-proxy` for -compatibility. Current source version: 0.33.4. +compatibility. Current source version: 0.33.5. ## Delivery diff --git a/CHANGELOG.md b/CHANGELOG.md index cbf15d0..43b627f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,22 @@ > Operator-facing release history. Historical engineering notes are kept in > [`docs/CHANGELOG_TECHNICAL_ARCHIVE.md`](docs/CHANGELOG_TECHNICAL_ARCHIVE.md). +## 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. +- 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 + Connection Activity. No change to collected values, scaling or APIs. +- Add simulated Chromium checks for chart headings, live peak placement, + dashboard densities and resized widget widths. + ## 0.33.4 - Unify per-Web-Service live metrics with Vhost cards: six matching live metric diff --git a/Cargo.lock b/Cargo.lock index 12059b1..d55918b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1881,7 +1881,7 @@ dependencies = [ [[package]] name = "rust-proxy" -version = "0.33.4" +version = "0.33.5" dependencies = [ "anyhow", "arc-swap", diff --git a/Cargo.toml b/Cargo.toml index 65774c0..d313d92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-proxy" -version = "0.33.4" +version = "0.33.5" edition = "2021" rust-version = "1.99" description = "RustProxy - independent reverse proxy with local administration" diff --git a/README.md b/README.md index 025c069..bd2d55c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,18 @@ -# RustProxy 0.33.4 +# RustProxy 0.33.5 + +## 0.33.5: adaptive CPU chart scaling 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. +- 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. + Remove the LIVE badge from **Connection Activity**, without changing its + counter, history, legend or chart rendering. ## 0.33.4: unified Web Server live metrics diff --git a/ROADMAP.md b/ROADMAP.md index 36b1586..896bb2b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,5 +1,19 @@ # RustProxy roadmap +## 0.33.5 - Adaptive Overview CPU chart scale + +- [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] 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. +- [x] Browser-test the two card headings, dynamic peak update and responsive + placement across viewport widths, dashboard densities and card spans. + ## 0.33.4 - Web Server/Vhost live-metric visual parity - [x] Show per-Web-Service live metrics without expanding a section. @@ -17,8 +31,6 @@ keep menu colors independent of the chart palette. - [x] Test navigation colors in Chromium across 4 themes and 8 chart palettes (invariance within each theme), 5 corner settings, hover, mobile and RTL. - - - [x] Keep the visual layout editor readable at 320-1920px with stable three/two/one-column previews, wrapping controls and no horizontal overflow; leave actual dashboard widths and free ordering unchanged. diff --git a/SHA256SUMS b/SHA256SUMS index 2c70bf0..98ee5f4 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -1,13 +1,13 @@ 61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore 4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example 9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore -90823b5d38f99f0031cd5fe81248506ed8fc5b0c6f72292bb023e95ed98d9f38 AGENTS.md -d331e6aab4fd331e73fe89bfcb96f57dd502f3b2089d6298139c63b6d12154bc CHANGELOG.md -629a3594e8cffea80603ec4650b91e5f4bea9d65266a1e1122b815ed0d6c5f45 Cargo.lock -e1d3659e9f745d37ff27a8ecdf0233b450127da4410ca51d4b3582a4d92c59bf Cargo.toml +ffae7ce20cc0fb1990394822515c933843ba39304aa44134403f0d25a6a66d9f AGENTS.md +95f433de4f779002f6358209dbb178933eab2036f8e69dabda72c5946ef32716 CHANGELOG.md +a35a5d557bb57df37d130a03752ffc092f005cb4849733d3d81485fb9450a931 Cargo.lock +8b77b2beffb31dd14ff62e225ff5384084d2b4e66faa100aaf38470883fca879 Cargo.toml 25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile -ca0c0f5658f91381c99a91fa3d2daddee82d185e310c8dce037c150c615dbf47 README.md -d02e695435b061b9d29408e72a1335390e549add0dc9ec901f852d982bfd66e2 ROADMAP.md +273fe8c9504f7565986b775850287fbc579a3de13aaffbfdd897a01ae64305fa README.md +de605453cec3511eae8b010af5791eb7d526fb09569f91c503b9dd53d0c2fe0e ROADMAP.md da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md @@ -130,7 +130,9 @@ e69813fb1d3a4cdcfb7243964cc323fc21a603087bf2e84188fa42b9d3cee4b5 scripts/runtim 174c8059625e3315a3d9fefe74c17f78ed850ff25ee15bfdabc148107dd344eb scripts/start_stream_backends.sh 046b4282550f5cb4e53906665c91c7442be70fc68fd0743466fd832582f7c979 scripts/test_stream_tcp.py 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 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 @@ -198,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 -94ca621117738e064276d426552cc4dfe87ba9b038ddcd73a8d4024c6659a474 src/admin/web/app/overview-wide-metrics.js +9c039138fa58d8382601a4b2de366ba7763beb4d35f1cff7899cf2ce36c55afb 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 @@ -232,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 -da102bf2c797c0614e964f535908e89064abd75172395bbca98da68b5e4edcc3 src/admin/web/index.html +840c6ef69b9db1d8c4b710ad0eadb4253ac729bd05a7f60e3bfaf41640dc57a3 src/admin/web/index.html 03ad9572bb14c34059636d612cf6b876d6361ce601817cff674c57b715dc65dd src/admin/web/integrations.js 7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json cbd41678eadd994dc5252628ee8ef9551fa9f7d8694ca5abd206240d9a96ad30 src/admin/web/login.html @@ -242,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 -c02d71ff3e1b63128045d3786f37bb2733bb70bf8d1037929e3cf65cbfc2df4e src/admin/web/style.css +7ffd017d38297a21160fd61926bb1d406b465e8fe610db9164234220dd8b111a src/admin/web/style.css d290c048450655b3eeae7c6ec54a82d645d1965513694ab29fdf7eef796a1abd src/admin/web/traffic-panel.js 69f412bd897e45f4889b71e9982cfff56df9ef20ba2da109b454713a5ecd7c74 src/admin/web/transform-panel.js d816f2f70eef398c18091479320c36d07f9afe07278a250b840ebcdd7bc715f2 src/admin/web/vhosts/core.js diff --git a/scripts/tests/dashboard_chart_headers_0335_browser_test.py b/scripts/tests/dashboard_chart_headers_0335_browser_test.py new file mode 100755 index 0000000..0a762e5 --- /dev/null +++ b/scripts/tests/dashboard_chart_headers_0335_browser_test.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +"""Overview traffic/connection heading cleanup against Chromium and the bundled JS. + +Runs a simulated authenticated DOM rather than a live RustProxy service. +""" +from pathlib import Path +import json +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') +ROUTER = (ROOT / 'src/admin/router.rs').read_text(encoding='utf-8') + + +def app_bundle(): + match = re.search(r'fn admin_app_javascript\([^)]*\)[^{]*\{', ROUTER) + assert match + body = ROUTER[match.end():] + body = body[:re.search(r'\]\s*\.join\("\\n"\)', body).start()] + result = [] + for line in body.splitlines(): + line = line.strip() + include = re.search(r'include_str!\("([^"]+)"\)', line) + if include: + result.append((ROOT / 'src/admin' / include.group(1)).read_text(encoding='utf-8')) + elif line == 'admin_vhosts_javascript(),': + result.append('(() => {\n' + '\n'.join( + (ROOT / 'src/admin/web/vhosts' / (name + '.js')).read_text(encoding='utf-8') + for name in ('core', 'protocol', 'health', 'rates', 'editor', 'render')) + '\n})();') + elif line.startswith('"') and line.endswith('",'): + result.append(json.loads(line[:-1])) + return '\n'.join(result) + + +with sync_playwright() as p: + browser = p.chromium.launch(headless=True, executable_path='/usr/bin/chromium', args=['--no-sandbox']) + bundle = app_bundle() + 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.add_script_tag(content=bundle) + page.evaluate('''() => { + document.documentElement.classList.remove('app-loading', 'app-startup-failed'); + document.getElementById('workspace').hidden = false; + document.getElementById('panel-overview').hidden = false; + currentPanel = 'overview'; + samples.splice(0); samples.push(1.5, 85.5, 12.3); + redrawOverviewHistoryCharts(); + }''') + for density in ('normal', 'compact', 'ultra'): + for span in (3, 6): + data = page.evaluate('''({density,span}) => { + document.documentElement.dataset.dashboardDensity = density; + const visible = ['traffic_chart', 'connection_chart']; + const hidden = ADMIN_DASHBOARD_WIDGETS.filter(id => !visible.includes(id)); + applyDashboardLayout([...visible,...hidden], hidden, + {traffic_chart:span,connection_chart:span}); + const traffic = document.querySelector('[data-dashboard-widget="traffic_chart"]'); + const connection = document.querySelector('[data-dashboard-widget="connection_chart"]'); + const heading = traffic.querySelector('.card-heading'); + const peak = traffic.querySelector('#chart-peak'); + const peakRect = peak.getBoundingClientRect(); + const cardRect = traffic.getBoundingClientRect(); + const headingRect = heading.getBoundingClientRect(); + return { + title: heading.querySelector('h2')?.textContent.trim(), + peak: peak.textContent, + peakInHeading: peak.parentElement === heading, + peakNotInFooter: !traffic.querySelector('.chart-footer #chart-peak'), + noTrafficEyebrow: !traffic.querySelector('.small-label'), + noTrafficLiveBadge: !traffic.querySelector('.pill.live'), + noConnectionLiveBadge: !connection.querySelector('.pill.live'), + connectionTitleIntact: !!connection.querySelector('.card-heading .small-label[data-i18n="connectionActivity"]'), + peakInsideCard: peakRect.left >= cardRect.left - 1 && peakRect.right <= cardRect.right + 1, + peakInsideHeading: peakRect.top >= headingRect.top - 1 && peakRect.bottom <= headingRect.bottom + 1, + browserOverflow: document.documentElement.scrollWidth - innerWidth, + }; + }''', {'density':density,'span':span}) + assert data['title'] == 'Requests per second', (width, density, span, data) + assert data['peak'] == 'max 85.5 req/s', (width, density, span, data) + for key in ('peakInHeading', 'peakNotInFooter', 'noTrafficEyebrow', + 'noTrafficLiveBadge', 'noConnectionLiveBadge', + 'connectionTitleIntact', 'peakInsideCard', 'peakInsideHeading'): + assert data[key], (width, density, span, key, data) + assert data['browserOverflow'] <= 2, (width, density, span, data) + assert not errors, (width, errors) + print(f'PASS traffic/connection chart headers at {width}px (all densities, spans 3 and 6)') + page.close() + browser.close() +print('PASS Overview chart heading cleanup') diff --git a/scripts/tests/dashboard_cpu_autoscale_0335_browser_test.py b/scripts/tests/dashboard_cpu_autoscale_0335_browser_test.py new file mode 100755 index 0000000..a6af4dc --- /dev/null +++ b/scripts/tests/dashboard_cpu_autoscale_0335_browser_test.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +"""0.33.5 CPU plot autoscaling 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') +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 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, + 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, + 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'].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=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'] - result['protocolHeight']) <= 1, ( + 'CPU must use standard chart height', 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['svgWidth'] > 100 and abs(result['svgWidth'] - result['drawnWidth']) < 2, result + assert result['legendInCard'] and result['overflow'] <= 2, (viewport, density, result) + + # Measured 0.5-0.8% must be visible, not flattened by a 100% ceiling. + 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']) == [77, 41], (viewport, second) + assert ys(second['process']['path']) == [113, 65], (viewport, second) + + # The peak follows observed history and also works above 100% (multi-core). + third = render_cpu(page, 1, 3.4, 2.2) + assert ys(third['system']['path'])[-1] == 17, (viewport, third) + assert ys(third['process']['path'])[-1] == 59, (viewport, third) + high = render_cpu(page, 1, 250, 123) + assert high['system']['label'] == '250.0%' + assert ys(high['system']['path'])[-1] == 17, (viewport, high) + assert 17 <= ys(high['process']['path'])[-1] < 137, (viewport, high) + + # Revision changes reset the history. Zero stays at the plot baseline. + idle = render_cpu(page, 2, 0, 0) + assert ys(idle['system']['path']) == [137] and ys(idle['process']['path']) == [137] + missing = render_cpu(page, 3, None, None) + assert missing['system']['path'] == '' and missing['process']['label'] == '-' + assert not errors, (viewport, errors) + print(f'PASS CPU auto Y-scale and standard plot height at {viewport}px (all densities)') + page.close() + browser.close() diff --git a/src/admin/web/app/overview-wide-metrics.js b/src/admin/web/app/overview-wide-metrics.js index 2ed5917..0214a71 100644 --- a/src/admin/web/app/overview-wide-metrics.js +++ b/src/admin/web/app/overview-wide-metrics.js @@ -85,14 +85,10 @@ window.rustProxyWideOverview = (() => { for (const [id, config] of Object.entries(configurations)) { const node = tile(id); if (!node) { baselines.delete(id); continue; } - // Keep collecting while hidden so restoring a widget does not restart rates. const values = config.counters ? counterRates(id, config.counters(status), now, record) : config.values(status).map(finite); const series = history.get(id) || config.names.map(() => []); if (record) { - // Counter rates have no meaningful value on the first sample after a - // paused tab. Do not add null to the path: it would split a live line. - // Still show "-" above the chart until a fresh interval is sampled. if (!config.counters || values.every(value => value !== null)) { values.forEach((value, i) => { series[i].push(value); @@ -102,8 +98,7 @@ window.rustProxyWideOverview = (() => { } last.set(id, values); } - const peak = Math.max(config.unit === "percent" ? 100 : 1, - ...series.flat().filter(n => n !== null)); + const peak = Math.max(1, ...series.flat().filter(n => n !== null)); config.names.forEach((key, i) => { const value = values[i]; const field = $(`overview-wide-${id}-${key}-value`); diff --git a/src/admin/web/index.html b/src/admin/web/index.html index a710821..ddd8fb6 100644 --- a/src/admin/web/index.html +++ b/src/admin/web/index.html @@ -1083,9 +1083,8 @@
-
LIVE TRAFFIC -

Requests per second

-
LIVE +

Requests per second

+ max 0.0 req/s
@@ -1112,7 +1111,7 @@ id="rate-transform">0.0/s + reload
@@ -1143,7 +1142,7 @@
CONNECTION ACTIVITY

0 Open connections

-
LIVE +