497 lines
17 KiB
PHP
497 lines
17 KiB
PHP
<?php
|
||
|
||
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');
|
||
//////// }
|
||
//////// }
|
||
//////// }
|
||
////////}
|