feat(ui): show S3 backup status and move domain action

Eager-load S3 on volume backup lists and add an S3 status column with
Configured/Unavailable/Not set badges. Surface Local vs S3 on the
persistent storages backup column. Move service resource domain links
to a globe action in the card footer, and update related tests and grid CSS.
This commit is contained in:
Andras Bacsai committed 2026-08-07 12:49:25 +02:00
1 parent d68b198d53
commit 4539d1442e
12 files changed
+82 -24

No files matched your search

+6 -6
View File
@@ -2149,7 +2149,7 @@ input[type="search"]::-webkit-search-results-decoration {
}
.backup-table-grid {
grid-template-columns: minmax(10rem, 1.7fr) 6rem minmax(7rem, 0.8fr) 7.5rem minmax(8rem, 1fr) 5rem;
grid-template-columns: minmax(10rem, 1.7fr) 6rem minmax(7rem, 0.8fr) 7.5rem 6.5rem minmax(8rem, 1fr) 5rem;
}
/* Persistent storage volumes: Name | Source | Destination | [PR suffix] | Backup | [Actions] */
@@ -2383,11 +2383,12 @@ input[type="search"]::-webkit-search-results-decoration {
@media (max-width: 900px) {
.backup-table-grid {
grid-template-columns: minmax(10rem, 1.5fr) 6rem 7.5rem minmax(8rem, 1fr);
grid-template-columns: minmax(10rem, 1.5fr) 6rem 7.5rem 6.5rem;
}
.backup-table-grid > :nth-child(3),
.backup-table-grid > :nth-child(6) {
.backup-table-grid > :nth-child(6),
.backup-table-grid > :nth-child(7) {
display: none;
}
@@ -2443,11 +2444,10 @@ input[type="search"]::-webkit-search-results-decoration {
}
.backup-table-grid {
grid-template-columns: minmax(0, 1fr) 7.5rem;
grid-template-columns: minmax(0, 1fr) 7.5rem 6.5rem;
}
.backup-table-grid > :nth-child(2),
.backup-table-grid > :nth-child(5) {
.backup-table-grid > :nth-child(2) {
display: none;
}
@@ -178,6 +178,7 @@
<span>Type</span>
<span>Schedule</span>
<span>Status</span>
<span>S3</span>
<span>Last run</span>
<span class="text-right">Executions</span>
</div>
@@ -212,6 +213,10 @@
<span>{{ $backup->targetType() }}</span>
<span>{{ $backup->frequency }}</span>
<span><x-status-badge :status="$statusLabel" :type="$statusType" /></span>
<span title="{{ $backup->save_s3 ? ($backup->s3?->name ?? 'S3 storage unavailable') : 'S3 storage is not configured' }}">
<x-status-badge :status="$backup->save_s3 ? ($backup->s3 ? 'Configured' : 'Unavailable') : 'Not set'"
:type="$backup->save_s3 ? ($backup->s3 ? 'success' : 'error') : 'neutral'" />
</span>
<span>
{{ $latestExecution?->finished_at?->diffForHumans() ?? ($status === 'running' ? 'Running now' : 'Never') }}
</span>
@@ -39,17 +39,10 @@
@endif
@if ($isApplication && $resource->fqdn)
<div class="mt-2 flex min-w-0 items-center gap-1.5">
<div class="mt-2 min-w-0">
<span class="min-w-0 truncate text-xs text-neutral-500 dark:text-fg-dim">
{{ $resource->fqdn }}
</span>
@can('update', $service)
<a class="icon-button shrink-0" title="Edit domains" aria-label="Edit domains"
{{ wireNavigate() }}
href="{{ route('project.service.domains', $parameters) }}">
<x-reicon name="settings" class="size-3.5" />
</a>
@endcan
</div>
@endif
</div>
@@ -63,6 +56,14 @@
<x-reicon name="database" class="size-4" />
</a>
@endif
@if ($isApplication && $resource->fqdn)
@can('update', $service)
<a class="icon-button" title="Manage domains" aria-label="Manage domains" {{ wireNavigate() }}
href="{{ route('project.service.domains', $parameters) }}">
<x-reicon name="globe" class="size-4" />
</a>
@endcan
@endif
<a class="icon-button" title="Resource settings" aria-label="Resource settings" {{ wireNavigate() }}
href="{{ route('project.service.index', [...$parameters, 'stack_service_uuid' => $resource->uuid]) }}">
<x-reicon name="settings" class="size-4" />
@@ -109,6 +110,14 @@
<x-reicon name="database" class="size-4" />
</a>
@endif
@if ($isApplication && $resource->fqdn)
@can('update', $service)
<a class="icon-button" title="Manage domains" aria-label="Manage domains" {{ wireNavigate() }}
href="{{ route('project.service.domains', $parameters) }}">
<x-reicon name="globe" class="size-4" />
</a>
@endcan
@endif
<a class="icon-button" title="Resource settings" aria-label="Resource settings" {{ wireNavigate() }}
href="{{ route('project.service.index', [...$parameters, 'stack_service_uuid' => $resource->uuid]) }}">
<x-reicon name="settings" class="size-4" />
@@ -179,6 +179,7 @@
<span>Type</span>
<span>Schedule</span>
<span>Status</span>
<span>S3</span>
<span>Last run</span>
<span class="text-right">Executions</span>
</div>
@@ -213,6 +214,10 @@
<span>{{ $backup->targetType() }}</span>
<span>{{ $backup->frequency }}</span>
<span><x-status-badge :status="$statusLabel" :type="$statusType" /></span>
<span title="{{ $backup->save_s3 ? ($backup->s3?->name ?? 'S3 storage unavailable') : 'S3 storage is not configured' }}">
<x-status-badge :status="$backup->save_s3 ? ($backup->s3 ? 'Configured' : 'Unavailable') : 'Not set'"
:type="$backup->save_s3 ? ($backup->s3 ? 'success' : 'error') : 'neutral'" />
</span>
<span>
{{ $latestExecution?->finished_at?->diffForHumans() ?? ($status === 'running' ? 'Running now' : 'Never') }}
</span>
@@ -39,8 +39,9 @@
if (! $form) {
continue;
}
$backupMeta = $volumeBackupMeta[$id] ?? ['enabled' => false, 'url' => null];
$backupMeta = $volumeBackupMeta[$id] ?? ['enabled' => false, 's3' => false, 'url' => null];
$hasEnabledBackup = $backupMeta['enabled'];
$hasS3Backup = $backupMeta['s3'];
$backupUrl = $backupMeta['url'];
$inputsReadonly = $form['isReadOnly'];
$displayHostPath = filled($form['hostPath']) ? $form['hostPath'] : '—';
@@ -78,7 +79,7 @@
</div>
@endif
<div class="volumes-col-backup data-table-cell-check">
<div class="volumes-col-backup flex items-center justify-center gap-1.5">
<span class="volumes-mobile-label volumes-field-label">Backup</span>
@if ($hasEnabledBackup)
<a @if ($backupUrl) href="{{ $backupUrl }}" @endif title="Volume backup is enabled">
@@ -87,6 +88,10 @@
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
</svg>
</a>
<span @class(['table-badge', 'table-badge-success' => $hasS3Backup])
title="{{ $hasS3Backup ? 'Backups are saved to S3' : 'Backups are stored locally only' }}">
{{ $hasS3Backup ? 'S3' : 'Local' }}
</span>
@else
<span class="data-table-cell-dash">-</span>
@endif
@@ -153,7 +158,7 @@
</div>
@endif
<div class="volumes-col-backup data-table-cell-check">
<div class="volumes-col-backup flex items-center justify-center gap-1.5">
<span class="volumes-mobile-label volumes-field-label">Backup</span>
@if ($hasEnabledBackup)
<a @if ($backupUrl) href="{{ $backupUrl }}" @endif title="Volume backup is enabled">
@@ -162,6 +167,10 @@
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
</svg>
</a>
<span @class(['table-badge', 'table-badge-success' => $hasS3Backup])
title="{{ $hasS3Backup ? 'Backups are saved to S3' : 'Backups are stored locally only' }}">
{{ $hasS3Backup ? 'S3' : 'Local' }}
</span>
@else
<span class="data-table-cell-dash">-</span>
@endif