diff --git a/app/Livewire/Ui/System/BackupStatusCard.php b/app/Livewire/Ui/System/BackupStatusCard.php index ccf5809..4a37ce9 100644 --- a/app/Livewire/Ui/System/BackupStatusCard.php +++ b/app/Livewire/Ui/System/BackupStatusCard.php @@ -2,37 +2,169 @@ 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 /usr/local/sbin/mailwolt-backup >/dev/null 2>&1 &'); - $this->dispatch('toast', type:'info', title:'Backup gestartet'); + @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'; } - 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/app/Support/BuildMeta.php b/app/Support/BuildMeta.php index b65d0a4..087e7b5 100644 --- a/app/Support/BuildMeta.php +++ b/app/Support/BuildMeta.php @@ -32,9 +32,9 @@ final class BuildMeta } // 2) Fallback: .env APP_VERSION - if ($m->version === 'dev' && ($envVer = env('APP_VERSION'))) { - $m->version = trim($envVer); - } +// if ($m->version === 'dev' && ($envVer = env('APP_VERSION'))) { +// $m->version = trim($envVer); +// } // 3) Fallback: Git (ohne "-dirty") if ($m->rev === '' || $m->version === 'dev') {