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 &'); // Sofort UI auf „läuft“ setzen – Poll holt Echtstatus $this->running = true; $this->percent = 1; $this->step = 'start'; } private function load(bool $force = false): void { $state = $this->readStatus(); // Progress $this->running = ($state['state'] ?? null) === 'running'; $this->percent = (int)($state['percent'] ?? 0); $this->step = $state['step'] ?? null; // Abschlusswerte $fin = $state['finished_at'] ?? $state['start_at'] ?? null; $this->lastAt = $fin ? $this->fmtDate($fin) : null; $sizeB = isset($state['size']) ? (int)$state['size'] : null; $this->lastSize = $sizeB !== null ? $this->fmtBytes($sizeB) : null; $durS = isset($state['duration']) ? (int)$state['duration'] : null; $this->lastDuration = $durS !== null ? $this->fmtDuration($durS) : null; $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 readStatus(): array { 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 //{ // 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 bool $running = false; // 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 &'); // $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'); //////// } //////// } //////// } ////////}