diff --git a/app/Livewire/Ui/System/BackupStatusCard.php b/app/Livewire/Ui/System/BackupStatusCard.php index 5a0bdeb..5032b42 100644 --- a/app/Livewire/Ui/System/BackupStatusCard.php +++ b/app/Livewire/Ui/System/BackupStatusCard.php @@ -2,96 +2,350 @@ namespace App\Livewire\Ui\System; -use Carbon\Carbon; use Livewire\Component; +use Illuminate\Support\Str; +use Carbon\Carbon; class BackupStatusCard extends Component { - public string $lastAt = '–'; - public string $lastSize = '–'; - public string $lastDuration = '–'; - public string $statusText = 'unbekannt'; - public string $statusColor = 'text-white/60 border-white/20 bg-white/5'; + // Anzeige-Felder (nur Ausgabe im Blade) + public ?string $lastAt = null; // "27.10.2025 18:27:35" + public ?string $lastSize = null; // "93.0 MB" + public ?string $lastDuration = null; // "11s" / "2m 03s" + public ?bool $ok = null; - public string $progressText = ''; - public string $progressPercent = '0'; - public string $progressVisibleClass = 'hidden'; // <- Sichtbarkeit + // Progress (nur wenn state=running) + public bool $running = false; + public int $percent = 0; + public ?string $step = null; // z.B. "compress" protected string $statusFile = '/var/lib/mailwolt/backup.status'; - 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 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 { + // asynchron starten (sudoers vorausgesetzt) @shell_exec('nohup sudo -n /usr/local/sbin/mailwolt-backup >/dev/null 2>&1 &'); - // UI sofort auf "läuft" setzen - $this->progressText = 'Vorbereitung läuft...'; - $this->progressPercent = '1'; - $this->progressVisibleClass = 'block'; + + // Sofort UI auf „läuft“ setzen – Poll holt Echtstatus + $this->running = true; + $this->percent = 1; + $this->step = 'start'; } - protected function load(bool $force = false): void + private function load(bool $force = false): void { - $kv = []; - if (is_file($this->statusFile)) { - 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 = $this->readStatus(); - $state = $kv['state'] ?? null; // running | done | failed - $step = $kv['step'] ?? null; - $percent = isset($kv['percent']) ? (int)$kv['percent'] : 0; - $ok = isset($kv['ok']) ? ((int)$kv['ok'] === 1) : null; + // Progress + $this->running = ($state['state'] ?? null) === 'running'; + $this->percent = (int)($state['percent'] ?? 0); + $this->step = $state['step'] ?? null; - // Anzeigeformatierungen wie gehabt … - // (deine bestehenden formatBytes/formatDuration/Timezone-Logik) + // Abschlusswerte + $fin = $state['finished_at'] ?? $state['start_at'] ?? null; + $this->lastAt = $fin ? $this->fmtDate($fin) : null; - $this->progressPercent = (string)max(0, min(100, $percent)); - $this->progressText = $this->mapStep($step); + $sizeB = isset($state['size']) ? (int)$state['size'] : null; + $this->lastSize = $sizeB !== null ? $this->fmtBytes($sizeB) : null; - // Sichtbarkeit steuern – KEINE Blade-Logik nötig - if ($state === 'running') { - $this->progressVisibleClass = 'block'; - } else { - // bei done/failed: Balken verstecken und auf 100% / finalen Text setzen - $this->progressVisibleClass = 'hidden'; - if ($percent >= 100 || $step === 'done') { - $this->progressPercent = '100'; - $this->progressText = 'Backup abgeschlossen.'; - } - } + $durS = isset($state['duration']) ? (int)$state['duration'] : null; + $this->lastDuration = $durS !== null ? $this->fmtDuration($durS) : null; - // Status-Badge (wie gehabt) - 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'; + $this->ok = isset($state['ok']) ? ((string)$state['ok'] === '1') : null; + + // Wenn fertig → Balken aus + if (!$this->running) { + $this->percent = 0; + $this->step = null; } } - private function mapStep(?string $step): string + private function readStatus(): array { - 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...', - }; + if (!is_readable($this->statusFile)) { + return []; + } + + $out = []; + foreach (@file($this->statusFile, FILE_IGNORE_NEW_LINES) ?: [] as $line) { + if (!str_contains($line, '=')) continue; + [$k, $v] = array_map('trim', explode('=', $line, 2)); + // nur erwartete Keys + if (in_array($k, ['state', 'pid', 'start_at', 'finished_at', 'step', 'percent', 'size', 'duration', 'ok'], true)) { + $out[$k] = $v; + } + } + return $out; + } + + private function fmtDate(string $iso): string + { + // in App-Zeitzone anzeigen + $tz = config('app.timezone', 'UTC'); + try { + return Carbon::parse($iso)->setTimezone($tz)->format('d.m.Y H:i:s'); + } catch (\Throwable) { + return $iso; + } + } + + private function fmtBytes(int $bytes): string + { + $u = ['B', 'KB', 'MB', 'GB', 'TB']; + $i = 0; + $n = (float)$bytes; + while ($n >= 1024 && $i < count($u) - 1) { + $n /= 1024; + $i++; + } + return number_format($n, ($i <= 1 ? 0 : 1), ',', '.') . ' ' . $u[$i]; + } + + private function fmtDuration(int $sec): string + { + if ($sec < 60) return $sec . 's'; + $m = intdiv($sec, 60); + $s = $sec % 60; + return sprintf('%dm %02ds', $m, $s); } } +//namespace App\Livewire\Ui\System; +// +//use Illuminate\Support\Str; +//use Livewire\Component; +//use Carbon\Carbon; +// +//class BackupStatusCard extends Component +//{ +// // Anzeige-Felder (fertig formatiert) +// public ?string $lastAt = null; +// public ?string $lastSize = null; +// public ?string $lastDuration = null; +// public ?bool $ok = null; +// +// // Laufstatus für Progress (nur zur Sichtbarkeit) +// public bool $running = false; +// public int $percent = 0; +// public string $progressText = ''; +// +// protected string $statusFile = '/var/lib/mailwolt/backup.status'; +// +// 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 +// { +// // asynchron starten – sudoers wie bereits gesetzt +// @shell_exec('nohup sudo -n /usr/local/sbin/mailwolt-backup >/dev/null 2>&1 &'); +// // UI direkt "laufend" schalten; echte Werte kommen über Poll +// $this->running = true; +// $this->percent = 1; +// $this->progressText = 'Backup gestartet …'; +// } +// +// protected function load(bool $force = false): void +// { +// $s = $this->readStatus(); +// +// // Progress +// $state = $s['state'] ?? null; +// $this->running = in_array($state, ['running'], true); +// $this->percent = (int)($s['percent'] ?? 0); +// $step = $s['step'] ?? ''; +// $this->progressText = $this->mapStepText($step, $state); +// +// // Abschlusswerte +// $finishedAt = $s['finished_at'] ?? ($state === 'done' ? ($s['start_at'] ?? null) : null); +// $sizeBytes = isset($s['size']) ? (int)$s['size'] : null; +// $durSec = isset($s['duration']) ? (int)$s['duration'] : null; +// $okStr = $s['ok'] ?? null; +// +// $this->lastAt = $finishedAt ? $this->fmtDate($finishedAt) : null; +// $this->lastSize = $sizeBytes !== null ? $this->fmtBytes($sizeBytes) : null; +// $this->lastDuration = $durSec !== null ? $this->fmtDuration($durSec) : null; +// $this->ok = $okStr !== null ? ($okStr === '1' || $okStr === 'true') : null; +// } +// +// protected function readStatus(): array +// { +// if (!is_readable($this->statusFile)) { +// return []; +// } +// $lines = @file($this->statusFile, FILE_IGNORE_NEW_LINES) ?: []; +// $out = []; +// foreach ($lines as $ln) { +// if (!str_contains($ln, '=')) continue; +// [$k, $v] = array_map('trim', explode('=', $ln, 2)); +// $out[$k] = $v; +// } +// return $out; +// } +// +// private function mapStepText(string $step, ?string $state): string +// { +// if ($state === 'done') return 'Backup abgeschlossen.'; +// if ($state === 'failed') return 'Backup fehlgeschlagen.'; +// return match ($step) { +// 'start' => 'Backup wird vorbereitet …', +// 'mysqldump' => 'Datenbank wird gesichert …', +// 'maildir' => 'Maildir wird gesichert …', +// 'app' => 'Applikation wird gesichert …', +// 'configs' => 'Konfigurationen werden gesichert …', +// 'compress' => 'Archiv wird komprimiert …', +// 'retention' => 'Alte Backups werden aufgeräumt …', +// default => $step ? Str::headline($step) . ' …' : 'Backup läuft …', +// }; +// } +// +// private function fmtDate(string $iso): string +// { +// return Carbon::parse($iso)->timezone(config('app.timezone', 'Europe/Berlin'))->format('d.m.Y H:i:s'); +// } +// +// private function fmtBytes(int $b): string +// { +// $units = ['B', 'KB', 'MB', 'GB', 'TB']; +// $i = 0; +// $val = $b; +// while ($val >= 1024 && $i < count($units) - 1) { +// $val /= 1024; +// $i++; +// } +// return number_format($val, $val >= 10 ? 0 : ($val >= 1 ? 1 : 0)) . ' ' . $units[$i]; +// } +// +// private function fmtDuration(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 = $m % 60; +// return sprintf('%dh %02dm', $h, $m); +// } +//} + +//namespace App\Livewire\Ui\System; +// +//use Carbon\Carbon; +//use Livewire\Component; +// +//class BackupStatusCard extends Component +//{ +// public string $lastAt = '–'; +// public string $lastSize = '–'; +// public string $lastDuration = '–'; +// public string $statusText = 'unbekannt'; +// public string $statusColor = 'text-white/60 border-white/20 bg-white/5'; +// +// public string $progressText = ''; +// public string $progressPercent = '0'; +// public string $progressVisibleClass = 'hidden'; // <- Sichtbarkeit +// +// protected string $statusFile = '/var/lib/mailwolt/backup.status'; +// +// 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 &'); +// // UI sofort auf "läuft" setzen +// $this->progressText = 'Vorbereitung läuft...'; +// $this->progressPercent = '1'; +// $this->progressVisibleClass = 'block'; +// } +// +// protected function load(bool $force = false): void +// { +// $kv = []; +// if (is_file($this->statusFile)) { +// 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 = $kv['state'] ?? null; // running | done | failed +// $step = $kv['step'] ?? null; +// $percent = isset($kv['percent']) ? (int)$kv['percent'] : 0; +// $ok = isset($kv['ok']) ? ((int)$kv['ok'] === 1) : null; +// +// // Anzeigeformatierungen wie gehabt … +// // (deine bestehenden formatBytes/formatDuration/Timezone-Logik) +// +// $this->progressPercent = (string)max(0, min(100, $percent)); +// $this->progressText = $this->mapStep($step); +// +// // Sichtbarkeit steuern – KEINE Blade-Logik nötig +// if ($state === 'running') { +// $this->progressVisibleClass = 'block'; +// } else { +// // bei done/failed: Balken verstecken und auf 100% / finalen Text setzen +// $this->progressVisibleClass = 'hidden'; +// if ($percent >= 100 || $step === 'done') { +// $this->progressPercent = '100'; +// $this->progressText = 'Backup abgeschlossen.'; +// } +// } +// +// // Status-Badge (wie gehabt) +// 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'; +// } +// } +// +// 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...', +// }; +// } +//} + // //class BackupStatusCard extends Component //{ 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 fb93922..e0534f3 100644 --- a/resources/views/livewire/ui/system/backup-status-card.blade.php +++ b/resources/views/livewire/ui/system/backup-status-card.blade.php @@ -1,5 +1,5 @@ -