mailwolt/app/Livewire/Ui/System/BackupJobList.php

131 lines
4.1 KiB
PHP

<?php
namespace App\Livewire\Ui\System;
use App\Models\BackupJob;
use App\Models\BackupPolicy;
use Livewire\Attributes\Layout;
use Livewire\Attributes\On;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.dvx')]
#[Title('Backups · Mailwolt')]
class BackupJobList extends Component
{
public function runNow(): void
{
$policy = BackupPolicy::first();
if (!$policy) {
$this->dispatch('toast', type: 'warn', badge: 'Backup',
title: 'Kein Zeitplan', text: 'Bitte zuerst einen Backup-Zeitplan konfigurieren.', duration: 4000);
return;
}
// Reset stale jobs (stuck > 30 min with no running process)
BackupJob::whereIn('status', ['queued', 'running'])
->where('started_at', '<', now()->subMinutes(30))
->update(['status' => 'failed', 'finished_at' => now(), 'error' => 'Timeout — Prozess nicht mehr aktiv.']);
$running = BackupJob::whereIn('status', ['queued', 'running'])->exists();
if ($running) {
$this->dispatch('toast', type: 'warn', badge: 'Backup',
title: 'Läuft bereits', text: 'Ein Backup-Job ist bereits aktiv.', duration: 3000);
return;
}
$job = BackupJob::create([
'policy_id' => $policy->id,
'status' => 'queued',
'started_at' => now(),
]);
$artisan = base_path('artisan');
exec("nohup php {$artisan} backup:run {$job->id} > /dev/null 2>&1 &");
$this->dispatch('openModal',
component: 'ui.system.modal.backup-progress-modal',
arguments: ['jobId' => $job->id]
);
}
public function openProgress(int $id): void
{
$this->dispatch('openModal',
component: 'ui.system.modal.backup-progress-modal',
arguments: ['jobId' => $id]
);
}
public function openDeleteConfirm(int $id): void
{
$this->dispatch('openModal',
component: 'ui.system.modal.backup-delete-modal',
arguments: ['jobId' => $id]
);
}
public function openRestoreConfirm(int $id): void
{
$this->dispatch('openModal',
component: 'ui.system.modal.backup-restore-confirm-modal',
arguments: ['jobId' => $id]
);
}
#[On('backup-list-refresh')]
public function refresh(): void {}
#[On('backup:do-restore')]
public function onRestoreConfirmed(int $jobId): void
{
$this->restore($jobId);
}
public function restore(int $id): void
{
$sourceJob = BackupJob::findOrFail($id);
if (!$sourceJob->artifact_path || !file_exists($sourceJob->artifact_path)) {
$this->dispatch('toast', type: 'warn', badge: 'Backup',
title: 'Datei nicht gefunden', text: 'Das Backup-Archiv wurde nicht gefunden.', duration: 4000);
return;
}
$token = 'mailwolt_restore_' . uniqid();
$artisan = base_path('artisan');
exec("nohup php {$artisan} restore:run {$sourceJob->id} {$token} > /dev/null 2>&1 &");
$this->dispatch('openModal',
component: 'ui.system.modal.backup-progress-modal',
arguments: ['jobId' => $sourceJob->id, 'restoreToken' => $token]
);
}
public function delete(int $id): void
{
$job = BackupJob::findOrFail($id);
if ($job->artifact_path && file_exists($job->artifact_path)) {
@unlink($job->artifact_path);
}
$job->delete();
$this->dispatch('toast', type: 'done', badge: 'Backup',
title: 'Gelöscht', text: 'Backup-Eintrag wurde entfernt.', duration: 3000);
}
public function render()
{
$jobs = BackupJob::where('checksum', '!=', 'restore')->orWhereNull('checksum')->latest('started_at')->paginate(20);
$policy = BackupPolicy::first();
$hasRunning = BackupJob::whereIn('status', ['queued', 'running'])
->where('started_at', '>=', now()->subMinutes(30))
->exists();
return view('livewire.ui.system.backup-job-list', compact('jobs', 'policy', 'hasRunning'));
}
}