diff --git a/app/Livewire/Wireguard/Index.php b/app/Livewire/Wireguard/Index.php
index 40315aa..d01f417 100644
--- a/app/Livewire/Wireguard/Index.php
+++ b/app/Livewire/Wireguard/Index.php
@@ -3,20 +3,36 @@
namespace App\Livewire\Wireguard;
use App\Services\WgStatus;
+use App\Services\WgTraffic;
use Livewire\Attributes\Layout;
use Livewire\Component;
/**
- * WireGuard dashboard — live status (read-only, Phase 1). Renders the host-collected
- * run/wg-status.json (via WgStatus) and wire:polls every 5s. No writes here (P3/P4).
+ * WireGuard dashboard — live status (P1) + traffic history (P2). Renders the host-collected
+ * status (WgStatus) and a bucketed throughput series (WgTraffic), wire:polls every 5s. Read-only.
*/
#[Layout('layouts.app')]
class Index extends Component
{
- public function render(WgStatus $wg)
+ /** Selected traffic window in seconds. One of WINDOWS. */
+ public int $window = 3600;
+
+ /** Allowed windows (seconds): 1h / 24h / 7d. */
+ public const WINDOWS = [3600, 86400, 604800];
+
+ public function setWindow(int $seconds): void
{
+ $this->window = in_array($seconds, self::WINDOWS, true) ? $seconds : 3600;
+ }
+
+ public function render(WgStatus $wg, WgTraffic $traffic)
+ {
+ $window = in_array($this->window, self::WINDOWS, true) ? $this->window : 3600;
+
return view('livewire.wireguard.index', [
'status' => $wg->read(),
+ 'traffic' => $traffic->series($window),
+ 'windows' => self::WINDOWS,
])->title(__('wireguard.title'));
}
}
diff --git a/lang/de/wireguard.php b/lang/de/wireguard.php
index 47e759c..1fdc86c 100644
--- a/lang/de/wireguard.php
+++ b/lang/de/wireguard.php
@@ -26,4 +26,11 @@ return [
'last_handshake' => 'letzter Handshake',
'never' => 'nie',
'down_up' => '↓ :rx · ↑ :tx',
+ 'traffic_title' => 'Traffic',
+ 'window_1h' => '1 Std',
+ 'window_24h' => '24 Std',
+ 'window_7d' => '7 Tage',
+ 'total_down' => 'Empfangen',
+ 'total_up' => 'Gesendet',
+ 'no_traffic' => 'Noch keine Verlaufsdaten — sie sammeln sich, sobald Peers verbunden sind.',
];
diff --git a/lang/en/wireguard.php b/lang/en/wireguard.php
index ed2a3a4..2ab4467 100644
--- a/lang/en/wireguard.php
+++ b/lang/en/wireguard.php
@@ -26,4 +26,11 @@ return [
'last_handshake' => 'last handshake',
'never' => 'never',
'down_up' => '↓ :rx · ↑ :tx',
+ 'traffic_title' => 'Traffic',
+ 'window_1h' => '1h',
+ 'window_24h' => '24h',
+ 'window_7d' => '7d',
+ 'total_down' => 'Received',
+ 'total_up' => 'Sent',
+ 'no_traffic' => 'No history yet — it accrues once peers are connected.',
];
diff --git a/resources/views/livewire/wireguard/index.blade.php b/resources/views/livewire/wireguard/index.blade.php
index e032627..19f818c 100644
--- a/resources/views/livewire/wireguard/index.blade.php
+++ b/resources/views/livewire/wireguard/index.blade.php
@@ -36,6 +36,59 @@
@endif
+ @php
+ $win = collect($windows)->mapWithKeys(fn ($s) => [$s => match ($s) {
+ 3600 => __('wireguard.window_1h'), 86400 => __('wireguard.window_24h'), default => __('wireguard.window_7d'),
+ }])->all();
+ $fmtB = function (int $n): string {
+ $u = ['B', 'KB', 'MB', 'GB', 'TB']; $i = 0; $v = (float) $n;
+ while ($v >= 1024 && $i < count($u) - 1) { $v /= 1024; $i++; }
+ return ($i === 0 ? (string) $n : number_format($v, 1)).' '.$u[$i];
+ };
+ $pts = $traffic['points'];
+ $n = max(1, count($pts) - 1);
+ $peak = max(1, $traffic['peak_down'], $traffic['peak_up']);
+ $line = function (string $key) use ($pts, $n, $peak) {
+ $out = [];
+ foreach ($pts as $i => $p) {
+ $x = round($i / $n * 600, 1);
+ $y = round(140 - ($p[$key] / $peak) * 132, 1);
+ $out[] = $x.','.$y;
+ }
+ return implode(' ', $out);
+ };
+ $hasTraffic = $traffic['total_down'] > 0 || $traffic['total_up'] > 0;
+ @endphp
+
+ {{ __('wireguard.no_traffic') }}