From da30b80056496b4e5b79799a118c7021f9c8fdee Mon Sep 17 00:00:00 2001 From: boban Date: Mon, 27 Oct 2025 19:05:11 +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 | 307 ++++++++++++------ .../ui/system/backup-status-card.blade.php | 133 +++++--- 2 files changed, 299 insertions(+), 141 deletions(-) diff --git a/app/Livewire/Ui/System/BackupStatusCard.php b/app/Livewire/Ui/System/BackupStatusCard.php index 4a37ce9..a23d0db 100644 --- a/app/Livewire/Ui/System/BackupStatusCard.php +++ b/app/Livewire/Ui/System/BackupStatusCard.php @@ -1,37 +1,26 @@ '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() @@ -46,125 +35,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 1102bb8..848e30d 100644 --- a/resources/views/livewire/ui/system/backup-status-card.blade.php +++ b/resources/views/livewire/ui/system/backup-status-card.blade.php @@ -1,69 +1,118 @@
-
+
Backups
-
- {{-- Laufender Fortschritt --}} - @if($state === 'running') + @if($running)
-
Sicherung läuft…
-
    - @foreach($steps as $k => $label) - @php - $done = $k === 'finish' ? false : ($k === $step ? false : (array_key_first(array_flip($steps)) !== $k && $k !== $step && $step && array_search($k, array_keys($steps)) < array_search($step, array_keys($steps)))); - $isCurrent = $k === $step; - @endphp -
  • - - - {{ $label }} - -
  • - @endforeach -
+
+ Schritt: {{ $step ?? 'start' }} + {{ $percent }}% +
+
+
+
@else - {{-- Letztes Ergebnis --}}
Letztes Backup: - {{ $lastAt ?? '–' }} + {{ $lastAt ?? '–' }}
Größe: - {{ $lastSize ?? '–' }} + {{ $lastSize ?? '–' }}
Dauer: - {{ $lastDuration ?? '–' }} + {{ $lastDuration ?? '–' }}
-
- - {{ $ok === null ? 'unbekannt' : ($ok ? 'erfolgreich' : 'fehlgeschlagen') }} - +
+ + {{ $ok === null ? 'unbekannt' : ($ok ? 'erfolgreich' : 'fehlgeschlagen') }} +
@endif
+ +{{----}} +{{--
--}} +{{--
--}} +{{-- --}} +{{-- Backups--}} +{{--
--}} + +{{-- --}} +{{-- {{ $state === 'running' ? 'Läuft…' : 'Jetzt sichern' }}--}} +{{-- --}} +{{--
--}} + +{{-- --}}{{-- Laufender Fortschritt --}} +{{-- @if($state === 'running')--}} +{{--
--}} +{{--
Sicherung läuft…
--}} +{{--
    --}} +{{-- @foreach($steps as $k => $label)--}} +{{-- @php--}} +{{-- $done = $k === 'finish' ? false : ($k === $step ? false : (array_key_first(array_flip($steps)) !== $k && $k !== $step && $step && array_search($k, array_keys($steps)) < array_search($step, array_keys($steps))));--}} +{{-- $isCurrent = $k === $step;--}} +{{-- @endphp--}} +{{--
  • --}} +{{-- --}} +{{-- --}} +{{-- {{ $label }}--}} +{{-- --}} +{{--
  • --}} +{{-- @endforeach--}} +{{--
--}} +{{--
--}} +{{-- @else--}} +{{-- --}}{{-- Letztes Ergebnis --}} +{{--
--}} +{{--
Letztes Backup:--}} +{{-- {{ $lastAt ?? '–' }}--}} +{{--
--}} +{{--
Größe:--}} +{{-- {{ $lastSize ?? '–' }}--}} +{{--
--}} +{{--
Dauer:--}} +{{-- {{ $lastDuration ?? '–' }}--}} +{{--
--}} +{{--
--}} +{{-- --}} +{{-- {{ $ok === null ? 'unbekannt' : ($ok ? 'erfolgreich' : 'fehlgeschlagen') }}--}} +{{-- --}} +{{--
--}} +{{--
--}} +{{-- @endif--}} +{{--
--}} {{--
--}} {{--
--}} {{--
--}}