mailwolt/resources/views/livewire/ui/system/backup-job-list.blade.php

162 lines
10 KiB
PHP

<x-slot:breadcrumbParent>System</x-slot:breadcrumbParent>
<x-slot:breadcrumb>Backups</x-slot:breadcrumb>
<div wire:poll.4s>
<div class="mbx-page-header">
<div class="mbx-page-title">
<svg width="16" height="16" viewBox="0 0 14 14" fill="none"><path d="M2.5 9H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/><rect x="3" y="8.5" width="8" height="5" rx="1.5" stroke="currentColor" stroke-width="1.2"/></svg>
Backups
</div>
<div class="mbx-page-actions">
<button wire:click="runNow"
wire:loading.attr="disabled"
wire:target="runNow"
class="mbx-btn-primary"
{{ $hasRunning ? 'disabled' : '' }}
style="{{ $hasRunning ? 'opacity:.5;cursor:default' : '' }}">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2 2l8 4-8 4V2Z" fill="currentColor"/></svg>
<span wire:loading.remove wire:target="runNow">Jetzt sichern</span>
<span wire:loading wire:target="runNow">Startet…</span>
</button>
<a href="{{ route('ui.system.settings') }}#backup" class="mbx-btn-mute">Zeitplan</a>
</div>
</div>
{{-- Policy Info --}}
@if($policy)
<div style="display:flex;gap:10px;align-items:center;padding:10px 14px;background:var(--mw-bg4);border:1px solid var(--mw-b2);border-radius:8px;margin-bottom:16px;font-size:12px;color:var(--mw-t3)">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><circle cx="6" cy="6" r="5" stroke="var(--mw-t4)" stroke-width="1.2"/><path d="M6 3.5v3l1.5 1.5" stroke="var(--mw-t4)" stroke-width="1.2" stroke-linecap="round"/></svg>
<span>
Zeitplan:
<code style="font-family:monospace;font-size:11px;color:var(--mw-v2)">{{ $policy->schedule_cron }}</code>
&nbsp;·&nbsp;
@if($policy->enabled)
<span style="color:#22c55e">Aktiv</span>
@else
<span style="color:var(--mw-t4)">Deaktiviert</span>
@endif
&nbsp;·&nbsp; Aufbewahrung: {{ $policy->retention_count }} Backups
</span>
</div>
@else
<div style="padding:12px 16px;background:var(--mw-bg4);border:1px solid var(--mw-b2);border-radius:8px;margin-bottom:16px;font-size:12px;color:var(--mw-t4)">
Kein Backup-Zeitplan konfiguriert. <a href="{{ route('ui.system.settings') }}#backup" style="color:var(--mw-v2)">Jetzt einrichten </a>
</div>
@endif
{{-- Jobs Table --}}
<div class="mbx-section">
<div class="mbx-domain-head">
<div class="mbx-domain-info">
<span class="mbx-badge-mute">Backup-Verlauf</span>
</div>
<span style="font-size:11px;color:var(--mw-t4)">{{ $jobs->total() }} Einträge</span>
</div>
@if($jobs->isEmpty())
<div style="padding:40px 20px;text-align:center;color:var(--mw-t4);font-size:13px">
Noch keine Backups vorhanden.
<div style="margin-top:6px;font-size:12px">Klicke auf „Jetzt sichern" um das erste Backup zu starten.</div>
</div>
@else
<div style="overflow-x:auto">
<table style="width:100%;border-collapse:collapse;font-size:12.5px">
<thead>
<tr style="border-bottom:1px solid var(--mw-b2)">
<th style="padding:10px 16px;text-align:left;font-weight:500;color:var(--mw-t4);font-size:11px">Status</th>
<th style="padding:10px 16px;text-align:left;font-weight:500;color:var(--mw-t4);font-size:11px">Gestartet</th>
<th style="padding:10px 16px;text-align:left;font-weight:500;color:var(--mw-t4);font-size:11px">Dauer</th>
<th style="padding:10px 16px;text-align:left;font-weight:500;color:var(--mw-t4);font-size:11px">Größe</th>
<th style="padding:10px 16px;text-align:left;font-weight:500;color:var(--mw-t4);font-size:11px">Datei</th>
<th style="padding:10px 16px;text-align:right;font-weight:500;color:var(--mw-t4);font-size:11px">Aktionen</th>
</tr>
</thead>
<tbody>
@foreach($jobs as $job)
@php
$badge = match($job->status) {
'ok' => ['class' => 'mbx-badge-ok', 'label' => 'OK'],
'failed' => ['class' => 'mbx-badge-err', 'label' => 'Fehler'],
'running' => ['class' => 'mbx-badge-info-sm', 'label' => 'Läuft'],
'queued' => ['class' => 'mbx-badge-mute', 'label' => 'Warteschlange'],
'canceled'=> ['class' => 'mbx-badge-mute', 'label' => 'Abgebrochen'],
default => ['class' => 'mbx-badge-mute', 'label' => ucfirst($job->status)],
};
$duration = ($job->started_at && $job->finished_at)
? $job->started_at->diffInSeconds($job->finished_at) . 's'
: ($job->started_at ? '…' : '—');
$size = $job->size_bytes > 0
? (function($b) {
$u = ['B','KB','MB','GB']; $i = 0; $v = (float)$b;
while ($v >= 1024 && $i < 3) { $v /= 1024; $i++; }
return number_format($v, $i <= 1 ? 0 : 1) . ' ' . $u[$i];
})($job->size_bytes)
: '—';
@endphp
<tr style="border-bottom:1px solid var(--mw-b2);transition:background .1s" onmouseenter="this.style.background='var(--mw-bg3)'" onmouseleave="this.style.background=''">
<td style="padding:10px 16px">
<span class="{{ $badge['class'] }}" style="font-size:10.5px">{{ $badge['label'] }}</span>
@if($job->status === 'running')
<span style="display:inline-block;width:6px;height:6px;border-radius:50%;background:#3b82f6;margin-left:5px;animation:pulse 1.5s infinite"></span>
@endif
</td>
<td style="padding:10px 16px;color:var(--mw-t2)" title="{{ $job->started_at?->format('d.m.Y H:i:s') }}">
{{ $job->started_at?->diffForHumans() ?? '—' }}
</td>
<td style="padding:10px 16px;color:var(--mw-t3);font-family:monospace">{{ $duration }}</td>
<td style="padding:10px 16px;color:var(--mw-t3)">{{ $size }}</td>
<td style="padding:10px 16px;color:var(--mw-t4);font-family:monospace;font-size:11px;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="{{ $job->artifact_path }}">
{{ $job->artifact_path ? basename($job->artifact_path) : '—' }}
</td>
<td style="padding:10px 16px;text-align:right">
<div style="display:flex;gap:6px;justify-content:flex-end">
@if(in_array($job->status, ['queued','running']))
<button wire:click="openProgress({{ $job->id }})"
class="mbx-btn-mute" style="font-size:11px;padding:3px 10px">
<svg width="11" height="11" viewBox="0 0 12 12" fill="none"><circle cx="6" cy="6" r="5" stroke="currentColor" stroke-width="1.2"/><path d="M4 6l1.5 1.5L8 4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
Fortschritt
</button>
@endif
@if($job->status === 'ok' && $job->artifact_path)
<button wire:click="openRestoreConfirm({{ $job->id }})"
class="mbx-btn-mute" style="font-size:11px;padding:3px 10px">
<svg width="11" height="11" viewBox="0 0 12 12" fill="none"><path d="M2 6a4 4 0 1 1 .5 2" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/><path d="M2 4v2H4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
Wiederherstellen
</button>
@endif
@if($job->error || $job->log_excerpt)
<button wire:click="openProgress({{ $job->id }})"
class="mbx-act-btn" title="Details anzeigen">
<svg width="11" height="11" viewBox="0 0 12 12" fill="none"><circle cx="6" cy="6" r="5" stroke="currentColor" stroke-width="1.2"/><path d="M6 4v2.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><circle cx="6" cy="9" r=".6" fill="currentColor"/></svg>
</button>
@endif
<button wire:click="openDeleteConfirm({{ $job->id }})"
class="mw-btn-del" style="font-size:11px;padding:3px 8px">
<svg width="10" height="10" viewBox="0 0 12 12" fill="none"><path d="M2 2l8 8M10 2l-8 8" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</button>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@if($jobs->hasPages())
<div style="padding:12px 16px;border-top:1px solid var(--mw-b2)">
{{ $jobs->links() }}
</div>
@endif
@endif
</div>
<style>
@keyframes pulse { 0%,100% { opacity:1 } 50% { opacity:.3 } }
.mbx-badge-info-sm { display:inline-flex;align-items:center;padding:2px 7px;border-radius:5px;font-size:10.5px;font-weight:500;background:rgba(59,130,246,.13);color:#60a5fa;border:1px solid rgba(59,130,246,.25) }
.mbx-badge-err { display:inline-flex;align-items:center;padding:2px 7px;border-radius:5px;font-size:10.5px;font-weight:500;background:rgba(239,68,68,.1);color:#f87171;border:1px solid rgba(239,68,68,.2) }
</style>
</div>