From be67932b47695ceba5106279cdece9dae1ab3fc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Thu, 3 Sep 2026 15:11:15 +0200 Subject: [PATCH] v0.12.0-red_lines --- FILE_MANIFEST.sha256 | 4 ++-- web/js/navigation.js | 48 ++++++++++++++++++++++++++++++++++++-------- web/styles.css | 31 ++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 10 deletions(-) diff --git a/FILE_MANIFEST.sha256 b/FILE_MANIFEST.sha256 index 986e6fe..9b99e85 100644 --- a/FILE_MANIFEST.sha256 +++ b/FILE_MANIFEST.sha256 @@ -197,13 +197,13 @@ d4aea1d6ec595000eaddca3b6f0e3d4003901365ece7788147a6ed207cffcc78 ./web/js/flows f06557c6d338259059b990f8f79c91d68ac8ec1f245c302468f08929b5506901 ./web/js/forms.js dbf36223863ba882c03eccea4516ba8db7acf0c2e72c6c64490cb994f1e20f96 ./web/js/history.js 7eaa04992ea828dc89f5eaebf674cbbfd160f53eb8d0d6ef0d4e47bd8c28c435 ./web/js/main.js -a0959f9caf84f1873e3549261de8b1a069f4df8326b17bfe1f8c3de120299875 ./web/js/navigation.js +733fcc595ed9c03c9639acc6caba46f0588f29e0b95bf72891e34443e7aa9db8 ./web/js/navigation.js 9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js/README.md 2bc9cfa306cdeafff39a7bfc8c5744568e0130d88432351cba8b062178aefce3 ./web/js/realtime.js c8c82e88c3715b1bfabf155e36266a4c94f5e2fc03eb39dcdd9d08a1985a997c ./web/js/router.js 3c4a96bbc73cdfaf2f76798201f88ef43776875dce42f2b75d6a1808cef496d9 ./web/js/settings.js bdeb55546a1dff5e5bdfde4b114d6330f025df1b62e528bf18a93ad6dd4c3e83 ./web/js/settings-ui.js 6b653e4fe2d4db4ffae6ff8f39b8ce57a10a990dc0000ee4aae51d949afadd52 ./web/manifest.webmanifest -c5d515b987d96ebfbc7593e9bac9aff64c8d54420f23cc8df5e2c84b14a69852 ./web/styles.css +a4f8a8ba9cfb29cb49db1d465d81e4d98a05165de17709ea0523c49fc45cfddf ./web/styles.css ae1b03f30b494f474a781a5d32072f1e73eba2b7c768109fc1cc8028cb6662a4 ./web/sw.js d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js diff --git a/web/js/navigation.js b/web/js/navigation.js index 544c5e5..19d22b7 100644 --- a/web/js/navigation.js +++ b/web/js/navigation.js @@ -54,22 +54,54 @@ function addPingSample(id, value, error = '') { function pingSparkline(samples) { const width = 360, height = 92, padX = 8, padY = 9; + if (!samples.length) return `
${esc(tr('devices.pingNoSamples'))}
`; + const valid = samples.map((sample, index) => ({ index, value: sample.value == null ? NaN : Number(sample.value) })).filter(item => Number.isFinite(item.value)); - if (!valid.length) return `
${esc(tr('devices.pingNoSamples'))}
`; const values = valid.map(item => item.value); - const min = Math.min(...values); - const max = Math.max(...values); + const min = values.length ? Math.min(...values) : 0; + const max = values.length ? Math.max(...values) : 10; const ceiling = Math.max(max, 10); const floor = Math.min(min, 0); const range = Math.max(1, ceiling - floor); const lastIndex = Math.max(1, samples.length - 1); - const points = valid.map(item => { - const x = padX + (item.index / lastIndex) * (width - padX * 2); - const y = height - padY - ((item.value - floor) / range) * (height - padY * 2); + const pointFor = (index, value) => { + const x = padX + (index / lastIndex) * (width - padX * 2); + const y = height - padY - ((value - floor) / range) * (height - padY * 2); return `${x.toFixed(1)},${y.toFixed(1)}`; - }).join(' '); + }; + + // Keep successful ping runs separate so a packet loss never gets hidden by a line + // connecting the samples before and after the failed request. + const runs = []; + let currentRun = []; + samples.forEach((sample, index) => { + const value = sample.value == null ? NaN : Number(sample.value); + if (Number.isFinite(value)) { + currentRun.push(pointFor(index, value)); + } else if (currentRun.length) { + runs.push(currentRun); + currentRun = []; + } + }); + if (currentRun.length) runs.push(currentRun); + + const lineRuns = runs.map(points => points.length === 1 + ? `` + : `` + ).join(''); + + // Failed requests are packet-loss / unavailability samples. Draw a red band and + // an X at each failed position so even a single loss is immediately visible. + const losses = samples.map((sample, index) => ({ sample, index })).filter(({ sample }) => sample.value == null).map(({ index }) => { + const x = padX + (index / lastIndex) * (width - padX * 2); + const bandWidth = Math.max(4, Math.min(10, (width - padX * 2) / Math.max(samples.length, 12))); + const left = Math.max(0, x - bandWidth / 2); + const markerY = height - padY - 5; + return ``; + }).join(''); + const guide = [0.25, 0.5, 0.75].map(ratio => ``).join(''); - return `${guide}`; + return `${guide}${lineRuns}${losses}`; } function pingStats(samples) { diff --git a/web/styles.css b/web/styles.css index 346d069..fab6e97 100644 --- a/web/styles.css +++ b/web/styles.css @@ -7141,3 +7141,34 @@ body.chart-fullscreen-open::before { content:""; position:fixed; inset:0; z-inde @media (max-width: 640px) { .technical-config-actions button { flex: 1 1 100%; } } + +/* Ping diagnostics: make packet loss and device unavailability explicit on the chart. */ +.ping-sparkline .ping-success-line { + fill: none; + stroke: var(--accent); + stroke-width: 2; + stroke-linejoin: round; + stroke-linecap: round; + vector-effect: non-scaling-stroke; +} + +.ping-sparkline .ping-success-point { + fill: var(--accent); + stroke: var(--accent); + vector-effect: non-scaling-stroke; +} + +.ping-sparkline .ping-loss-marker rect { + fill: var(--danger-soft); + stroke: var(--danger); + stroke-width: 1; + vector-effect: non-scaling-stroke; +} + +.ping-sparkline .ping-loss-marker path { + fill: none; + stroke: var(--danger); + stroke-width: 2; + stroke-linecap: round; + vector-effect: non-scaling-stroke; +}