clupilot/app/resources/views/livewire/pages/backups/index.blade.php

197 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
use App\Models\Backup;
use App\Models\Site;
use Livewire\Attributes\Layout;
use Livewire\Volt\Component;
use Livewire\WithPagination;
new #[Layout('layouts.app')] class extends Component {
use WithPagination;
public bool $dailyEnabled = true;
public bool $snapshotsEnabled = true;
public bool $replicationEnabled = false;
public function with(): array
{
$last = Backup::orderByDesc('completed_at')->first();
$totalSizeBytes = (int) Backup::sum('size_bytes');
$failedCount = Backup::where('status', 'failed')->count();
$totalCount = Backup::count();
return [
'backups' => Backup::query()->with('site')->orderByDesc('completed_at')->paginate(10),
'lastBackupAt' => $last?->completed_at,
'totalSize' => $totalSizeBytes,
'totalSizeGb' => round($totalSizeBytes / 1024 / 1024 / 1024, 1),
'recoveryPoints' => $totalCount,
'failedCount' => $failedCount,
'storageQuotaGb' => 500,
'storageUsedPct' => min(100, (int) round(($totalSizeBytes / 1024 / 1024 / 1024) / 500 * 100)),
'totalSites' => Site::count(),
];
}
}; ?>
<div>
<div class="clu-topbar">
<div>
<h1 class="clu-page-title">Backups</h1>
<div class="clu-page-sub">{{ $recoveryPoints }} wiederherstellungspunkte · {{ $totalSites }} sites</div>
</div>
<span class="clu-status-chip"><span class="clu-pulse-dot"></span> aktiv</span>
<div class="spacer"></div>
<button class="clu-btn-primary" style="height:36px;padding:0 14px;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true">
<path d="M21 12c0 5-9 9-9 9s-9-4-9-9V5l9-3 9 3v7z"/>
</svg>
Backup jetzt
</button>
</div>
{{-- Metrics --}}
<div class="grid gap-[14px] mb-[18px]" style="grid-template-columns:repeat(auto-fit,minmax(220px,1fr));">
<div class="clu-metric success">
<div class="clu-metric-row">
<span class="clu-metric-label">Letztes Backup</span>
<span class="clu-metric-ico"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 8v4l3 2"/></svg></span>
</div>
<div class="clu-metric-value">{{ $lastBackupAt?->diffForHumans() ?? '—' }}</div>
<div class="clu-metric-delta">letzter erfolgreicher Lauf</div>
</div>
<div class="clu-metric">
<div class="clu-metric-row">
<span class="clu-metric-label">Storage</span>
<span class="clu-metric-ico"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v6c0 1.7 4 3 9 3s9-1.3 9-3V5M3 11v6c0 1.7 4 3 9 3s9-1.3 9-3v-6"/></svg></span>
</div>
<div class="clu-metric-value">{{ $totalSizeGb }}<span class="unit">GB</span></div>
<div class="clu-metric-delta">von {{ $storageQuotaGb }} GB ({{ $storageUsedPct }}%)</div>
</div>
<div class="clu-metric">
<div class="clu-metric-row">
<span class="clu-metric-label">Recovery-Points</span>
<span class="clu-metric-ico"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12a9 9 0 1 0 9-9"/><path d="M3 4v5h5M12 7v5l4 2"/></svg></span>
</div>
<div class="clu-metric-value">{{ $recoveryPoints }}</div>
<div class="clu-metric-delta">30 Tage Retention</div>
</div>
<div class="clu-metric @if($failedCount > 0) danger @endif">
<div class="clu-metric-row">
<span class="clu-metric-label">Fehlgeschlagen</span>
<span class="clu-metric-ico"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M15 9l-6 6M9 9l6 6"/></svg></span>
</div>
<div class="clu-metric-value">{{ $failedCount }}</div>
<div class="clu-metric-delta @if($failedCount > 0) bad @endif">@if($failedCount > 0)Aufmerksamkeit nötig @else alle OK @endif</div>
</div>
</div>
{{-- 2-col: Recent backups + Sticky summary --}}
<div class="grid gap-[14px]" style="grid-template-columns:minmax(0,2fr) minmax(280px,1fr);">
<div class="clu-card">
<div class="clu-card-head">
<h3>Neueste Backups</h3>
<span class="meta">letzte 7 Tage</span>
</div>
<div class="clu-card-body tight" style="overflow-x:auto;">
<table class="clu-table">
<thead>
<tr>
<th>Site</th>
<th>Typ</th>
<th>Größe</th>
<th>Wann</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
@forelse ($backups as $b)
<tr>
<td>
<div class="clu-site-cell">
<div class="clu-favicon">{{ mb_substr($b->site->domain ?? '?', 0, 1) }}</div>
<div class="clu-site-meta">
<span class="clu-site-domain">{{ $b->site->domain ?? '—' }}</span>
<span class="clu-site-server">{{ $b->storage_location ?? 's3://clupilot-backups' }}</span>
</div>
</div>
</td>
<td><span class="clu-pill muted">{{ ucfirst($b->type) }}</span></td>
<td class="font-mono">{{ $b->size_mb }} MB</td>
<td class="font-mono">{{ $b->completed_at?->diffForHumans() ?? '—' }}</td>
<td>
@if ($b->status === 'ok')
<span class="clu-health">ok</span>
@elseif ($b->status === 'failed')
<span class="clu-health bad">failed</span>
@else
<span class="clu-health warn">{{ $b->status }}</span>
@endif
</td>
<td>
<div class="clu-row-actions">
<button title="Restore" type="button">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12a9 9 0 1 0 9-9"/><path d="M3 4v5h5"/></svg>
</button>
<button title="Download" type="button">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v14M5 12l7 7 7-7M5 21h14"/></svg>
</button>
<button title="Mehr" type="button">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="5" cy="12" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="19" cy="12" r="1.5"/></svg>
</button>
</div>
</td>
</tr>
@empty
<tr><td colspan="6" style="text-align:center;padding:40px;color:var(--color-muted);">Noch keine Backups.</td></tr>
@endforelse
</tbody>
</table>
</div>
<div class="clu-pager">
<span class="clu-pager-meta">{{ $backups->firstItem() ?? 0 }}{{ $backups->lastItem() ?? 0 }} von {{ $backups->total() }}</span>
<div class="flex gap-[6px]">
<button class="clu-ghost-btn" wire:click="previousPage" @if($backups->onFirstPage()) disabled @endif>← Zurück</button>
<button class="clu-ghost-btn" wire:click="nextPage" @if(! $backups->hasMorePages()) disabled @endif>Weiter →</button>
</div>
</div>
</div>
{{-- Sticky summary --}}
<div class="clu-card" style="height:fit-content;position:sticky;top:90px;">
<div class="clu-card-head"><h3>Konfiguration</h3></div>
<div class="clu-card-body" style="display:flex;flex-direction:column;gap:14px;">
<label class="clu-checkbox justify-between" style="width:100%;justify-content:space-between;">
<span>Daily Backups</span>
<input type="checkbox" wire:model.live="dailyEnabled" />
<span class="box"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5"><path d="M5 12l5 5L20 7"/></svg></span>
</label>
<label class="clu-checkbox" style="width:100%;justify-content:space-between;">
<span>Hourly Snapshots</span>
<input type="checkbox" wire:model.live="snapshotsEnabled" />
<span class="box"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5"><path d="M5 12l5 5L20 7"/></svg></span>
</label>
<label class="clu-checkbox" style="width:100%;justify-content:space-between;">
<span>Multi-Region Replication</span>
<input type="checkbox" wire:model.live="replicationEnabled" />
<span class="box"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5"><path d="M5 12l5 5L20 7"/></svg></span>
</label>
<div style="border-top:1px solid var(--color-hairline);padding-top:14px;">
<div class="clu-stat-label">Storage</div>
<div class="clu-plan-meter mt-2"><span style="width:{{ $storageUsedPct }}%;"></span></div>
<div class="clu-plan-stats">
<span>{{ $totalSizeGb }} GB used</span>
<span>{{ $storageUsedPct }}%</span>
</div>
</div>
<div class="clu-feed-meta" style="line-height:1.5;">
AES-256 verschlüsselt · S3 Frankfurt · 30 Tage Retention
</div>
</div>
</div>
</div>
</div>