feat(storage): group backups by database and filter by s3 status

Group backup schedules by their parent database (type + ID) for better
organization in the UI. Filter to only display backups with save_s3
enabled. Restructure the template to show database name as a header with
nested backups underneath, allowing clearer visualization of which
backups belong to each database. Add key binding to livewire component
to ensure proper re-rendering when resources change.
This commit is contained in:
Andras Bacsai 2026-03-19 12:04:16 +01:00
parent ca3ae289eb
commit 86c8ec9c20
3 changed files with 76 additions and 55 deletions

View file

@ -13,11 +13,13 @@ class Resources extends Component
public function render() public function render()
{ {
$backups = ScheduledDatabaseBackup::where('s3_storage_id', $this->storage->id) $backups = ScheduledDatabaseBackup::where('s3_storage_id', $this->storage->id)
->where('save_s3', true)
->with('database') ->with('database')
->get(); ->get()
->groupBy(fn ($backup) => $backup->database_type.'-'.$backup->database_id);
return view('livewire.storage.resources', [ return view('livewire.storage.resources', [
'backups' => $backups, 'groupedBackups' => $backups,
]); ]);
} }
} }

View file

@ -1,62 +1,81 @@
<div> <div>
<div class="grid gap-4 lg:grid-cols-2"> @forelse ($groupedBackups as $backups)
@forelse ($backups as $backup) @php
@php $firstBackup = $backups->first();
$database = $backup->database; $database = $firstBackup->database;
$databaseName = $database?->name ?? 'Deleted database'; $databaseName = $database?->name ?? 'Deleted database';
$link = null; $resourceLink = null;
if ($database && $database instanceof \App\Models\ServiceDatabase) { $backupParams = null;
$service = $database->service; if ($database && $database instanceof \App\Models\ServiceDatabase) {
if ($service) { $service = $database->service;
$environment = $service->environment; if ($service) {
$project = $environment?->project; $environment = $service->environment;
if ($project && $environment) {
$link = route('project.service.configuration', [
'project_uuid' => $project->uuid,
'environment_uuid' => $environment->uuid,
'service_uuid' => $service->uuid,
]);
}
}
} elseif ($database) {
$environment = $database->environment;
$project = $environment?->project; $project = $environment?->project;
if ($project && $environment) { if ($project && $environment) {
$link = route('project.database.backup.index', [ $resourceLink = route('project.service.configuration', [
'project_uuid' => $project->uuid, 'project_uuid' => $project->uuid,
'environment_uuid' => $environment->uuid, 'environment_uuid' => $environment->uuid,
'database_uuid' => $database->uuid, 'service_uuid' => $service->uuid,
]); ]);
} }
} }
@endphp } elseif ($database) {
@if ($link) $environment = $database->environment;
<a {{ wireNavigate() }} href="{{ $link }}" @class(['gap-2 border cursor-pointer coolbox group'])> $project = $environment?->project;
@else if ($project && $environment) {
<div @class(['gap-2 border coolbox'])> $resourceLink = route('project.database.backup.index', [
@endif 'project_uuid' => $project->uuid,
<div class="flex flex-col justify-center mx-6"> 'environment_uuid' => $environment->uuid,
<div class="box-title"> 'database_uuid' => $database->uuid,
{{ $databaseName }} ]);
</div> $backupParams = [
<div class="box-description"> 'project_uuid' => $project->uuid,
Frequency: {{ $backup->frequency }} 'environment_uuid' => $environment->uuid,
</div> 'database_uuid' => $database->uuid,
@if (!$backup->enabled) ];
<span class="px-2 py-1 text-xs font-semibold text-yellow-800 bg-yellow-100 rounded dark:text-yellow-100 dark:bg-yellow-800 w-fit"> }
Disabled }
</span> @endphp
@endif <div class="pb-6">
</div> <div class="flex items-center gap-2 pb-2">
@if ($link) @if ($resourceLink)
</a> <a {{ wireNavigate() }} href="{{ $resourceLink }}" class="text-lg font-bold dark:text-white hover:underline">{{ $databaseName }}</a>
@else @else
</div> <span class="text-lg font-bold dark:text-white">{{ $databaseName }}</span>
@endif @endif
@empty
<div>
<div>No backup schedules are using this storage.</div>
</div> </div>
@endforelse <div class="grid gap-4 lg:grid-cols-2">
</div> @foreach ($backups as $backup)
@php
$backupLink = null;
if ($backupParams) {
$backupLink = route('project.database.backup.execution', array_merge($backupParams, [
'backup_uuid' => $backup->uuid,
]));
}
@endphp
@if ($backupLink)
<a {{ wireNavigate() }} href="{{ $backupLink }}" @class(['gap-2 border cursor-pointer coolbox group'])>
@else
<div @class(['gap-2 border coolbox'])>
@endif
<div class="flex flex-col justify-center mx-6">
<div class="box-title">{{ $backup->frequency }}</div>
@if (!$backup->enabled)
<span class="px-2 py-1 text-xs font-semibold text-yellow-800 bg-yellow-100 rounded dark:text-yellow-100 dark:bg-yellow-800 w-fit">
Disabled
</span>
@endif
</div>
@if ($backupLink)
</a>
@else
</div>
@endif
@endforeach
</div>
</div>
@empty
<div>No backup schedules are using this storage.</div>
@endforelse
</div> </div>

View file

@ -46,7 +46,7 @@
@if ($currentRoute === 'storage.show') @if ($currentRoute === 'storage.show')
<livewire:storage.form :storage="$storage" /> <livewire:storage.form :storage="$storage" />
@elseif ($currentRoute === 'storage.resources') @elseif ($currentRoute === 'storage.resources')
<livewire:storage.resources :storage="$storage" /> <livewire:storage.resources :storage="$storage" :key="'resources-'.uniqid()" />
@endif @endif
</div> </div>
</div> </div>