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->running = true; $this->progressText = 'Starte Backup...'; $this->progressPercent = '1'; } protected function load(bool $force = false): void { if (!is_file($this->statusFile)) { $this->running = false; return; } $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 = $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'); 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 formatBytes(int $b): string { 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'; } 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 = $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 Livewire\Component; // //class BackupStatusCard extends Component //{ // public ?string $lastAt = null; // finale Zeit // public ?string $lastSize = null; // menschenlesbar // public ?string $lastDuration = null; // menschenlesbar // public ?bool $ok = null; // // // Live-Status // public bool $running = false; // public ?string $step = null; // public int $percent = 0; // // 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 // { // // Script asynchron starten (sudoers muss gesetzt sein) // @shell_exec('nohup sudo -n /usr/local/sbin/mailwolt-backup >/dev/null 2>&1 &'); // // Sofort UI auf "läuft" stellen – Poll holt echten Status nach // $this->running = true; // $this->step = 'start'; // $this->percent = 1; // } // // protected function load(bool $force = false): void // { // $f = '/var/lib/mailwolt/backup.status'; // if (!is_file($f)) { // return; // } // // $data = []; // foreach (@file($f, FILE_IGNORE_NEW_LINES) ?: [] as $ln) { // if (strpos($ln, '=') !== false) { // [$k, $v] = explode('=', $ln, 2); // $data[$k] = $v; // } // } // // $state = $data['state'] ?? null; // $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; // } // } // // protected function fmtTs(string $iso): string // { // 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 ($b >= 1024 && $i < count($u) - 1) { // $b /= 1024; // $i++; // } // return sprintf('%.1f %s', $b, $u[$i]); // } // // protected 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 %= 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; // formatierte Zeit //// public ?string $lastSize = null; // human readable //// public ?string $lastDuration = null; // human readable //// 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'; //// //// 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 &'); //// // Sofort in "running" gehen – Poll übernimmt dann //// $this->state = 'running'; //// $this->step = 'mysqldump'; //// } //// //// public 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; //// } //// //// // 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'); ////// } ////// } ////// } //////}