162 lines
10 KiB
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>
|
|
·
|
|
@if($policy->enabled)
|
|
<span style="color:#22c55e">Aktiv</span>
|
|
@else
|
|
<span style="color:var(--mw-t4)">Deaktiviert</span>
|
|
@endif
|
|
· 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>
|