fix(wg): traffic header shows live peer totals; QR-name limit made explicit (v0.9.46)

- The Traffic panel header ("Empfangen/Gesendet") drew from the sampled
  throughput, which is 0 until the minutely sampler accrues deltas — so a peer
  with live traffic (e.g. 244 B) showed "0 B" in the header. It now sums the
  live per-peer rx/tx counters, so the header always matches the peer rows. The
  chart below stays windowed throughput (fills from the sampler).
- import_hint reworded to state plainly that the WireGuard phone app ALWAYS
  prompts for a tunnel name on a QR scan (app behaviour, not changeable); the
  .conf download is the path to an automatic name (filename = tunnel name).

Tests: header sums live peer counters. 354 pass, Pint clean, /wireguard 200 with
no console errors, lang parity 89/89.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat/v1-foundation v0.9.46
boban 2026-06-21 22:59:26 +02:00
parent 60e1edc0d5
commit 645d7dd4a4
6 changed files with 39 additions and 6 deletions

View File

@ -13,7 +13,19 @@ getaggte Releases (Kanal `stable`, optional `beta`) — niemals Entwicklungs-Bui
_Keine offenen Änderungen — der nächste Stand wird hier gesammelt und als `vX.Y.Z` getaggt._ _Keine offenen Änderungen — der nächste Stand wird hier gesammelt und als `vX.Y.Z` getaggt._
## [0.9.45] - 2026-06-21 ## [0.9.46] - 2026-06-21
### Behoben
- **Traffic-Kopfzeile zeigte 0 B, obwohl der Peer Traffic hatte.** „Empfangen/Gesendet" oben speiste
sich aus dem Sample-Durchsatz (0, solange wenig Traffic floss), während die Peer-Zeile den
Live-Zähler zeigte — inkonsistent. Die Kopfzeile zeigt jetzt die **Live-Summe der Peer-Zähler**
(passt damit immer zur Peer-Zeile). Das Diagramm darunter bleibt der zeitfenster-bezogene Durchsatz
(füllt sich über den minütlichen Sampler, sobald spürbarer Traffic fließt).
### Geändert
- **Klarstellung QR-Import am Handy.** Der Hinweis sagt jetzt eindeutig: die WireGuard-App fragt beim
QR-Scan **immer** nach einem Tunnel-Namen (App-Vorgabe, nicht abschaltbar). Für einen automatischen
Namen die `.conf` **herunterladen** und importieren (Dateiname = Tunnel-Name).
### Hinzugefügt ### Hinzugefügt
- **DNS individuell einstellbar.** Der DNS-Server in den Client-Configs war fest auf `1.1.1.1`. Jetzt - **DNS individuell einstellbar.** Der DNS-Server in den Client-Configs war fest auf `1.1.1.1`. Jetzt

View File

@ -2,7 +2,7 @@
return [ return [
// First tagged release is v0.1.0 (semantic, not -dev). // First tagged release is v0.1.0 (semantic, not -dev).
'version' => '0.9.45', 'version' => '0.9.46',
// Deployed commit + branch. install.sh bakes these into .env from the host's .git (the prod // Deployed commit + branch. install.sh bakes these into .env from the host's .git (the prod
// image ships no .git); the Versions page prefers them and falls back to a live .git read in // image ships no .git); the Versions page prefers them and falls back to a live .git read in

View File

@ -48,7 +48,7 @@ return [
'new_peer_once' => 'Diese Konfiguration wird NUR EINMAL angezeigt und nicht gespeichert. Jetzt importieren (QR scannen oder Text kopieren).', 'new_peer_once' => 'Diese Konfiguration wird NUR EINMAL angezeigt und nicht gespeichert. Jetzt importieren (QR scannen oder Text kopieren).',
'close' => 'Schließen', 'close' => 'Schließen',
'download' => 'Herunterladen', 'download' => 'Herunterladen',
'import_hint' => 'QR mit der WireGuard-App scannen (Handy; die App fragt nach einem Tunnel-Namen) oder herunterladen und importieren (PC) — der Dateiname wird zum Tunnel-Namen.', 'import_hint' => 'Am Handy QR scannen — die WireGuard-App fragt dabei IMMER nach einem Tunnel-Namen (App-Vorgabe, nicht abschaltbar). Für einen automatischen Namen stattdessen Herunterladen und die .conf importieren (Dateiname = Tunnel-Name).',
'settings_title' => 'Einstellungen', 'settings_title' => 'Einstellungen',
'apply' => 'Anwenden', 'apply' => 'Anwenden',
'gate_toggle' => 'Gate', 'gate_toggle' => 'Gate',

View File

@ -48,7 +48,7 @@ return [
'new_peer_once' => 'This configuration is shown ONCE and never stored. Import it now (scan the QR or copy the text).', 'new_peer_once' => 'This configuration is shown ONCE and never stored. Import it now (scan the QR or copy the text).',
'close' => 'Close', 'close' => 'Close',
'download' => 'Download', 'download' => 'Download',
'import_hint' => 'Scan the QR with the WireGuard app (phone; it asks for a tunnel name) or download and import it (PC) — the filename becomes the tunnel name.', 'import_hint' => 'Scan the QR on a phone — the WireGuard app ALWAYS asks for a tunnel name (app behaviour, cannot be disabled). For an automatic name, download and import the .conf instead (filename = tunnel name).',
'settings_title' => 'Settings', 'settings_title' => 'Settings',
'apply' => 'Apply', 'apply' => 'Apply',
'gate_toggle' => 'Gate', 'gate_toggle' => 'Gate',

View File

@ -231,13 +231,17 @@
return implode(' ', $out); return implode(' ', $out);
}; };
$hasTraffic = $traffic['total_down'] > 0 || $traffic['total_up'] > 0; $hasTraffic = $traffic['total_down'] > 0 || $traffic['total_up'] > 0;
// Header = LIVE cumulative totals (sum of the peer counters), so it always matches the
// per-peer rows. The chart below is windowed THROUGHPUT history (needs the sampler).
$liveDown = array_sum(array_map(fn ($p) => $p['rx'], $status['peers']));
$liveUp = array_sum(array_map(fn ($p) => $p['tx'], $status['peers']));
@endphp @endphp
<x-panel :title="__('wireguard.traffic_title')" :padded="false"> <x-panel :title="__('wireguard.traffic_title')" :padded="false">
<div class="flex flex-wrap items-center justify-between gap-3 border-b border-line px-4 py-3 sm:px-5"> <div class="flex flex-wrap items-center justify-between gap-3 border-b border-line px-4 py-3 sm:px-5">
<div class="flex items-center gap-4 font-mono text-[11px]"> <div class="flex items-center gap-4 font-mono text-[11px]">
<span class="text-accent-text">{{ __('wireguard.total_down') }}: <span class="text-ink-2">{{ $fmtBytes($traffic['total_down']) }}</span></span> <span class="text-accent-text">{{ __('wireguard.total_down') }}: <span class="text-ink-2">{{ $fmtBytes($liveDown) }}</span></span>
<span class="text-cyan">{{ __('wireguard.total_up') }}: <span class="text-ink-2">{{ $fmtBytes($traffic['total_up']) }}</span></span> <span class="text-cyan">{{ __('wireguard.total_up') }}: <span class="text-ink-2">{{ $fmtBytes($liveUp) }}</span></span>
</div> </div>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
@foreach ($windows as $w) @foreach ($windows as $w)

View File

@ -72,6 +72,23 @@ class WireguardPageTest extends TestCase
->assertSee(__('wireguard.ssh_lock_turn_off')); ->assertSee(__('wireguard.ssh_lock_turn_off'));
} }
public function test_traffic_header_shows_live_cumulative_peer_totals(): void
{
// The header must match the per-peer rows (sum of live counters), not the sampled throughput.
file_put_contents($this->file, json_encode([
'at' => time(), 'configured' => true,
'gate' => ['marker' => false, 'chain' => false, 'ssh' => false], 'wg_quick' => 'active',
'server' => ['subnet' => '10.99.0.0/24', 'server_ip' => '10.99.0.1', 'port' => 51820, 'endpoint' => '1.2.3.4:51820', 'dns' => '1.1.1.1', 'pubkey' => 'srvpub'],
'peers' => [
['name' => 'a', 'pubkey' => 'p1', 'endpoint' => '', 'latest_handshake' => time(), 'rx' => 244, 'tx' => 92],
['name' => 'b', 'pubkey' => 'p2', 'endpoint' => '', 'latest_handshake' => 0, 'rx' => 1000, 'tx' => 0],
],
]));
Livewire::test(Index::class)
->assertSee('1.2 KB'); // received = 244 + 1000 = 1244 B (only the summed header shows this)
}
public function test_window_selector_switches_and_passes_traffic(): void public function test_window_selector_switches_and_passes_traffic(): void
{ {
WgTrafficSample::create(['peer_pubkey' => 'p1', 'peer_name' => 'a', 'rx' => 0, 'tx' => 0, 'sampled_at' => now()->subMinutes(10)]); WgTrafficSample::create(['peer_pubkey' => 'p1', 'peer_name' => 'a', 'rx' => 0, 'tx' => 0, 'sampled_at' => now()->subMinutes(10)]);