From ddd96eb9f2575da134631645ee2de274f6ebefd9 Mon Sep 17 00:00:00 2001 From: boban Date: Mon, 27 Oct 2025 19:37:30 +0100 Subject: [PATCH] =?UTF-8?q?Fix:=20Mailbox=20Stats=20=C3=BCber=20Dovecot=20?= =?UTF-8?q?mit=20config/mailpool.php?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Livewire/Ui/System/BackupStatusCard.php | 435 ++++++++++++------ .../ui/system/backup-status-card.blade.php | 110 +++-- 2 files changed, 359 insertions(+), 186 deletions(-) diff --git a/app/Livewire/Ui/System/BackupStatusCard.php b/app/Livewire/Ui/System/BackupStatusCard.php index a23d0db..8550b42 100644 --- a/app/Livewire/Ui/System/BackupStatusCard.php +++ b/app/Livewire/Ui/System/BackupStatusCard.php @@ -1,22 +1,21 @@ /dev/null 2>&1 &'); - // Sofort UI auf "läuft" stellen – Poll holt echten Status nach $this->running = true; - $this->step = 'start'; - $this->percent = 1; + $this->progressText = 'Starte Backup...'; + $this->progressPercent = '1'; } protected function load(bool $force = false): void { - $f = '/var/lib/mailwolt/backup.status'; - if (!is_file($f)) { + if (!is_file($this->statusFile)) { + $this->running = false; return; } - $data = []; - foreach (@file($f, FILE_IGNORE_NEW_LINES) ?: [] as $ln) { - if (strpos($ln, '=') !== false) { - [$k, $v] = explode('=', $ln, 2); - $data[$k] = $v; + $kv = []; + foreach (@file($this->statusFile, FILE_IGNORE_NEW_LINES) ?: [] as $ln) { + $p = strpos($ln, '='); + if ($p !== false) { + $kv[substr($ln, 0, $p)] = substr($ln, $p + 1); } } - $state = $data['state'] ?? null; + $state = $kv['state'] ?? null; + $step = $kv['step'] ?? null; + $percent = isset($kv['percent']) ? (int)$kv['percent'] : 0; + $ok = isset($kv['ok']) ? ((int)$kv['ok'] === 1) : null; + + // Formatierung + $tz = config('app.timezone', 'Europe/Berlin'); + $finished = $kv['finished_at'] ?? $kv['start_at'] ?? null; + $this->lastAt = $finished + ? Carbon::parse($finished)->setTimezone($tz)->format('d.m.Y H:i:s') + : '–'; + + $this->lastSize = isset($kv['size']) + ? $this->formatBytes((int)$kv['size']) + : '–'; + + $this->lastDuration = isset($kv['duration']) + ? $this->formatDuration((int)$kv['duration']) + : '–'; + + // Fortschritt + $this->progressPercent = (string)$percent; + $this->progressText = $this->mapStep($step); + + // Status $this->running = ($state === 'running'); - // Progress - $this->step = $data['step'] ?? null; - $this->percent = (int)($data['percent'] ?? 0); - - // Finale Werte - if ($state === 'done' || $state === 'failed') { - $this->ok = ($data['ok'] ?? '') === '1'; - $ts = $data['finished_at'] ?? $data['start_at'] ?? null; - $this->lastAt = $ts ? $this->fmtTs($ts) : null; - - $bytes = (int)($data['size'] ?? 0); - $this->lastSize = $bytes ? $this->fmtBytes($bytes) : null; - - $dur = (int)($data['duration'] ?? 0); - $this->lastDuration = $dur ? $this->fmtDuration($dur) : null; + if ($ok === true) { + $this->statusText = 'erfolgreich'; + $this->statusColor = 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10'; + } elseif ($ok === false) { + $this->statusText = 'fehlgeschlagen'; + $this->statusColor = 'text-rose-300 border-rose-400/30 bg-rose-500/10'; + } else { + $this->statusText = 'unbekannt'; + $this->statusColor = 'text-white/60 border-white/20 bg-white/5'; } } - protected function fmtTs(string $iso): string + private function formatBytes(int $b): string { - return CarbonImmutable::parse($iso)->tz(config('app.timezone')) - ->format('d.m.Y H:i:s'); + if ($b >= 1024 * 1024 * 1024) return number_format($b / (1024 * 1024 * 1024), 1) . ' GB'; + if ($b >= 1024 * 1024) return number_format($b / (1024 * 1024), 1) . ' MB'; + if ($b >= 1024) return number_format($b / 1024, 0) . ' KB'; + return $b . ' B'; } - protected function fmtBytes(int $b): string - { - $u = ['B', 'KB', 'MB', 'GB', 'TB']; - $i = 0; - while ($b >= 1024 && $i < count($u) - 1) { - $b /= 1024; - $i++; - } - return sprintf('%.1f %s', $b, $u[$i]); - } - - protected function fmtDuration(int $s): string + private function formatDuration(int $s): string { if ($s < 60) return $s . 's'; $m = intdiv($s, 60); $r = $s % 60; if ($m < 60) return sprintf('%dm %02ds', $m, $r); $h = intdiv($m, 60); - $m %= 60; - return sprintf('%dh %02dm', $h, $m); + $m = $m % 60; + return sprintf('%dh %02dm %02ds', $h, $m, $r); + } + + private function mapStep(?string $step): string + { + return match ($step) { + 'mysqldump' => 'Datenbank wird gesichert...', + 'maildir' => 'Mail-Verzeichnis wird archiviert...', + 'app' => 'Anwendungsdaten werden gesichert...', + 'configs' => 'Konfigurationen werden gesichert...', + 'compress' => 'Backup wird komprimiert...', + 'retention' => 'Alte Backups werden gelöscht...', + 'done' => 'Backup abgeschlossen.', + default => 'Vorbereitung läuft...', + }; } } +// // //namespace App\Livewire\Ui\System; // //use Carbon\CarbonImmutable; -//use Illuminate\Support\Str; //use Livewire\Component; // //class BackupStatusCard extends Component //{ -// public ?string $lastAt = null; // formatierte Zeit -// public ?string $lastSize = null; // human readable -// public ?string $lastDuration = null; // human readable +// public ?string $lastAt = null; // finale Zeit +// public ?string $lastSize = null; // menschenlesbar +// public ?string $lastDuration = null; // menschenlesbar // public ?bool $ok = null; // -// // Laufzeit/Progress -// public string $state = 'idle'; // idle|running|done|error -// public string $step = ''; // aktueller Schritt -// public array $steps = [ -// 'mysqldump' => 'Datenbank sichern', -// 'maildir' => 'Maildir kopieren', -// 'app' => 'App sichern', -// 'configs' => 'Configs sichern', -// 'archive' => 'Archiv erstellen', -// 'compress' => 'Komprimieren', -// 'retention' => 'Aufräumen', -// 'finish' => 'Abschluss', -// ]; -// -// protected string $statusFile = '/var/lib/mailwolt/backup.status'; +// // Live-Status +// public bool $running = false; +// public ?string $step = null; +// public int $percent = 0; // // public function mount(): void // { -// $this->load(true); +// $this->load(); // } // // public function render() @@ -155,125 +164,245 @@ class BackupStatusCard extends Component // // public function runNow(): void // { +// // Script asynchron starten (sudoers muss gesetzt sein) // @shell_exec('nohup sudo -n /usr/local/sbin/mailwolt-backup >/dev/null 2>&1 &'); -// // Sofort in "running" gehen – Poll übernimmt dann -// $this->state = 'running'; -// $this->step = 'mysqldump'; +// // Sofort UI auf "läuft" stellen – Poll holt echten Status nach +// $this->running = true; +// $this->step = 'start'; +// $this->percent = 1; // } // -// public function load(bool $force = false): void +// protected function load(bool $force = false): void // { -// $raw = $this->readStatus(); -// $this->state = $raw['state'] ?? 'idle'; -// $this->step = $raw['step'] ?? ''; -// -// // Datum/Zeit hübsch -// if (!empty($raw['time'])) { -// $this->lastAt = $this->fmtTime($raw['time']); -// } else { -// $this->lastAt = null; +// $f = '/var/lib/mailwolt/backup.status'; +// if (!is_file($f)) { +// return; // } // -// // Größe/Dauer hübsch -// $bytes = isset($raw['size_bytes']) ? (int)$raw['size_bytes'] : null; -// $secs = isset($raw['dur_seconds']) ? (int)$raw['dur_seconds'] : null; -// -// $this->lastSize = $bytes !== null ? $this->humanBytes($bytes) : null; -// $this->lastDuration = $secs !== null ? $this->humanDuration($secs) : null; -// $this->ok = isset($raw['ok']) ? ((string)$raw['ok'] === '1') : null; -// } -// -// protected function readStatus(): array -// { -// if (!is_file($this->statusFile)) return []; -// $out = []; -// foreach (@file($this->statusFile, FILE_IGNORE_NEW_LINES) ?: [] as $ln) { -// if (!str_contains($ln, '=')) continue; -// [$k, $v] = array_map('trim', explode('=', $ln, 2)); -// $out[$k] = $v; +// $data = []; +// foreach (@file($f, FILE_IGNORE_NEW_LINES) ?: [] as $ln) { +// if (strpos($ln, '=') !== false) { +// [$k, $v] = explode('=', $ln, 2); +// $data[$k] = $v; +// } // } // -// // Backward compatibility (alte Keys) -// if (isset($out['size']) && !isset($out['size_bytes'])) { -// $out['size_bytes'] = (int)$out['size']; -// } -// if (isset($out['dur']) && !isset($out['dur_seconds'])) { -// $out['dur_seconds'] = (int)$out['dur']; -// } +// $state = $data['state'] ?? null; +// $this->running = ($state === 'running'); // -// return $out; -// } +// // Progress +// $this->step = $data['step'] ?? null; +// $this->percent = (int)($data['percent'] ?? 0); // -// protected function fmtTime(string $iso): string -// { -// try { -// $tz = config('app.timezone', 'UTC'); -// // ISO aus Script ist idealerweise UTC (Z) -// $dt = CarbonImmutable::parse($iso)->timezone($tz); -// // z.B. 27.10.2025, 16:48:02 (CET) -// return $dt->isoFormat('L, LTS') . ' ' . $dt->format('T'); -// } catch (\Throwable) { -// return $iso; +// // Finale Werte +// if ($state === 'done' || $state === 'failed') { +// $this->ok = ($data['ok'] ?? '') === '1'; +// $ts = $data['finished_at'] ?? $data['start_at'] ?? null; +// $this->lastAt = $ts ? $this->fmtTs($ts) : null; +// +// $bytes = (int)($data['size'] ?? 0); +// $this->lastSize = $bytes ? $this->fmtBytes($bytes) : null; +// +// $dur = (int)($data['duration'] ?? 0); +// $this->lastDuration = $dur ? $this->fmtDuration($dur) : null; // } // } // -// protected function humanBytes(int $bytes): string +// protected function fmtTs(string $iso): string // { -// $units = ['B', 'KB', 'MB', 'GB', 'TB']; +// return CarbonImmutable::parse($iso)->tz(config('app.timezone')) +// ->format('d.m.Y H:i:s'); +// } +// +// protected function fmtBytes(int $b): string +// { +// $u = ['B', 'KB', 'MB', 'GB', 'TB']; // $i = 0; -// while ($bytes >= 1024 && $i < count($units) - 1) { -// $bytes /= 1024; +// while ($b >= 1024 && $i < count($u) - 1) { +// $b /= 1024; // $i++; // } -// return number_format($bytes, $i === 0 ? 0 : 1, ',', '.') . ' ' . $units[$i]; +// return sprintf('%.1f %s', $b, $u[$i]); // } // -// protected function humanDuration(int $secs): string +// protected function fmtDuration(int $s): string // { -// if ($secs < 60) return $secs . ' s'; -// $m = intdiv($secs, 60); -// $s = $secs % 60; -// if ($m < 60) return sprintf('%d min %02d s', $m, $s); +// if ($s < 60) return $s . 's'; +// $m = intdiv($s, 60); +// $r = $s % 60; +// if ($m < 60) return sprintf('%dm %02ds', $m, $r); // $h = intdiv($m, 60); -// $m = $m % 60; -// return sprintf('%d h %02d min', $h, $m); +// $m %= 60; +// return sprintf('%dh %02dm', $h, $m); // } //} // //// ////namespace App\Livewire\Ui\System; //// +////use Carbon\CarbonImmutable; +////use Illuminate\Support\Str; ////use Livewire\Component; //// ////class BackupStatusCard extends Component ////{ -//// public ?string $lastAt = null; -//// public ?string $lastSize = null; -//// public ?string $lastDuration = null; +//// public ?string $lastAt = null; // formatierte Zeit +//// public ?string $lastSize = null; // human readable +//// public ?string $lastDuration = null; // human readable //// public ?bool $ok = null; //// -//// public function mount(): void { $this->load(); } -//// public function render() { return view('livewire.ui.system.backup-status-card'); } -//// public function refresh(): void { $this->load(true); } +//// // Laufzeit/Progress +//// public string $state = 'idle'; // idle|running|done|error +//// public string $step = ''; // aktueller Schritt +//// public array $steps = [ +//// 'mysqldump' => 'Datenbank sichern', +//// 'maildir' => 'Maildir kopieren', +//// 'app' => 'App sichern', +//// 'configs' => 'Configs sichern', +//// 'archive' => 'Archiv erstellen', +//// 'compress' => 'Komprimieren', +//// 'retention' => 'Aufräumen', +//// 'finish' => 'Abschluss', +//// ]; +//// +//// protected string $statusFile = '/var/lib/mailwolt/backup.status'; +//// +//// public function mount(): void +//// { +//// $this->load(true); +//// } +//// +//// public function render() +//// { +//// return view('livewire.ui.system.backup-status-card'); +//// } +//// +//// public function refresh(): void +//// { +//// $this->load(true); +//// } //// //// public function runNow(): void //// { //// @shell_exec('nohup sudo -n /usr/local/sbin/mailwolt-backup >/dev/null 2>&1 &'); -////// $this->dispatch('toast', type:'info', title:'Backup gestartet'); +//// // Sofort in "running" gehen – Poll übernimmt dann +//// $this->state = 'running'; +//// $this->step = 'mysqldump'; //// } //// -//// protected function load(bool $force=false): void +//// public function load(bool $force = false): void //// { -//// // Example: parse a tiny status file your backup script writes. -//// $f = '/var/lib/mailwolt/backup.status'; -//// if (is_file($f)) { -//// $lines = @file($f, FILE_IGNORE_NEW_LINES) ?: []; -//// foreach ($lines as $ln) { -//// if (str_starts_with($ln,'time=')) $this->lastAt = substr($ln,5); -//// if (str_starts_with($ln,'size=')) $this->lastSize = substr($ln,5); -//// if (str_starts_with($ln,'dur=')) $this->lastDuration = substr($ln,4); -//// if (str_starts_with($ln,'ok=')) $this->ok = (substr($ln,3) === '1'); -//// } +//// $raw = $this->readStatus(); +//// $this->state = $raw['state'] ?? 'idle'; +//// $this->step = $raw['step'] ?? ''; +//// +//// // Datum/Zeit hübsch +//// if (!empty($raw['time'])) { +//// $this->lastAt = $this->fmtTime($raw['time']); +//// } else { +//// $this->lastAt = null; +//// } +//// +//// // Größe/Dauer hübsch +//// $bytes = isset($raw['size_bytes']) ? (int)$raw['size_bytes'] : null; +//// $secs = isset($raw['dur_seconds']) ? (int)$raw['dur_seconds'] : null; +//// +//// $this->lastSize = $bytes !== null ? $this->humanBytes($bytes) : null; +//// $this->lastDuration = $secs !== null ? $this->humanDuration($secs) : null; +//// $this->ok = isset($raw['ok']) ? ((string)$raw['ok'] === '1') : null; +//// } +//// +//// protected function readStatus(): array +//// { +//// if (!is_file($this->statusFile)) return []; +//// $out = []; +//// foreach (@file($this->statusFile, FILE_IGNORE_NEW_LINES) ?: [] as $ln) { +//// if (!str_contains($ln, '=')) continue; +//// [$k, $v] = array_map('trim', explode('=', $ln, 2)); +//// $out[$k] = $v; +//// } +//// +//// // Backward compatibility (alte Keys) +//// if (isset($out['size']) && !isset($out['size_bytes'])) { +//// $out['size_bytes'] = (int)$out['size']; +//// } +//// if (isset($out['dur']) && !isset($out['dur_seconds'])) { +//// $out['dur_seconds'] = (int)$out['dur']; +//// } +//// +//// return $out; +//// } +//// +//// protected function fmtTime(string $iso): string +//// { +//// try { +//// $tz = config('app.timezone', 'UTC'); +//// // ISO aus Script ist idealerweise UTC (Z) +//// $dt = CarbonImmutable::parse($iso)->timezone($tz); +//// // z.B. 27.10.2025, 16:48:02 (CET) +//// return $dt->isoFormat('L, LTS') . ' ' . $dt->format('T'); +//// } catch (\Throwable) { +//// return $iso; //// } //// } +//// +//// protected function humanBytes(int $bytes): string +//// { +//// $units = ['B', 'KB', 'MB', 'GB', 'TB']; +//// $i = 0; +//// while ($bytes >= 1024 && $i < count($units) - 1) { +//// $bytes /= 1024; +//// $i++; +//// } +//// return number_format($bytes, $i === 0 ? 0 : 1, ',', '.') . ' ' . $units[$i]; +//// } +//// +//// protected function humanDuration(int $secs): string +//// { +//// if ($secs < 60) return $secs . ' s'; +//// $m = intdiv($secs, 60); +//// $s = $secs % 60; +//// if ($m < 60) return sprintf('%d min %02d s', $m, $s); +//// $h = intdiv($m, 60); +//// $m = $m % 60; +//// return sprintf('%d h %02d min', $h, $m); +//// } ////} +//// +////// +//////namespace App\Livewire\Ui\System; +////// +//////use Livewire\Component; +////// +//////class BackupStatusCard extends Component +//////{ +////// public ?string $lastAt = null; +////// public ?string $lastSize = null; +////// public ?string $lastDuration = null; +////// public ?bool $ok = null; +////// +////// public function mount(): void { $this->load(); } +////// public function render() { return view('livewire.ui.system.backup-status-card'); } +////// public function refresh(): void { $this->load(true); } +////// +////// public function runNow(): void +////// { +////// @shell_exec('nohup sudo -n /usr/local/sbin/mailwolt-backup >/dev/null 2>&1 &'); +//////// $this->dispatch('toast', type:'info', title:'Backup gestartet'); +////// } +////// +////// protected function load(bool $force=false): void +////// { +////// // Example: parse a tiny status file your backup script writes. +////// $f = '/var/lib/mailwolt/backup.status'; +////// if (is_file($f)) { +////// $lines = @file($f, FILE_IGNORE_NEW_LINES) ?: []; +////// foreach ($lines as $ln) { +////// if (str_starts_with($ln,'time=')) $this->lastAt = substr($ln,5); +////// if (str_starts_with($ln,'size=')) $this->lastSize = substr($ln,5); +////// if (str_starts_with($ln,'dur=')) $this->lastDuration = substr($ln,4); +////// if (str_starts_with($ln,'ok=')) $this->ok = (substr($ln,3) === '1'); +////// } +////// } +////// } +//////} diff --git a/resources/views/livewire/ui/system/backup-status-card.blade.php b/resources/views/livewire/ui/system/backup-status-card.blade.php index 848e30d..211a5f8 100644 --- a/resources/views/livewire/ui/system/backup-status-card.blade.php +++ b/resources/views/livewire/ui/system/backup-status-card.blade.php @@ -1,6 +1,7 @@
@@ -8,45 +9,88 @@ Backups
-
- @if($running) -
-
- Schritt: {{ $step ?? 'start' }} - {{ $percent }}% -
-
-
-
+ {{-- Fortschritt --}} +
+
+ {{ $progressText }} + {{ $progressPercent }}%
- @else -
-
Letztes Backup: - {{ $lastAt ?? '–' }} -
-
Größe: - {{ $lastSize ?? '–' }} -
-
Dauer: - {{ $lastDuration ?? '–' }} -
-
- - {{ $ok === null ? 'unbekannt' : ($ok ? 'erfolgreich' : 'fehlgeschlagen') }} - -
+
+
- @endif +
+ + {{-- Backup Infos --}} +
+
Letztes Backup: {{ $lastAt }}
+
Größe: {{ $lastSize }}
+
Dauer: {{ $lastDuration }}
+
+ + {{ $statusText }} + +
+
+{{----}} +{{--
--}} +{{--
--}} +{{-- --}} +{{-- Backups--}} +{{--
--}} + +{{-- --}} +{{--
--}} + +{{-- @if($running)--}} +{{--
--}} +{{--
--}} +{{-- Schritt: {{ $step ?? 'start' }}--}} +{{-- {{ $percent }}%--}} +{{--
--}} +{{--
--}} +{{--
--}} +{{--
--}} +{{--
--}} +{{-- @else--}} +{{--
--}} +{{--
Letztes Backup:--}} +{{-- {{ $lastAt ?? '–' }}--}} +{{--
--}} +{{--
Größe:--}} +{{-- {{ $lastSize ?? '–' }}--}} +{{--
--}} +{{--
Dauer:--}} +{{-- {{ $lastDuration ?? '–' }}--}} +{{--
--}} +{{--
--}} +{{-- --}} +{{-- {{ $ok === null ? 'unbekannt' : ($ok ? 'erfolgreich' : 'fehlgeschlagen') }}--}} +{{-- --}} +{{--
--}} +{{--
--}} +{{-- @endif--}} +{{--
--}} + {{--