feat(ui): Shadow UI redesign, domain management, and DNS autoconfigure (#11119)

Co-authored-by: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com>
This commit is contained in:
Andras BacsaiandShadowArcanist authored and GitHub committed 2026-08-03 23:11:54 +02:00
1 parent 0b843bb07c
commit 8ae56587d5
497 files changed
+44045 -22630

No files matched your search

@@ -1,208 +1,172 @@
<div wire:init='refreshBackupExecutions'>
<div wire:init="refreshBackupExecutions">
@isset($backup)
<div class="flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center">
<h2 class="py-0">Executions</h2>
@if ($executions_count > 0)
<div class="flex items-center gap-2">
<x-forms.button disabled="{{ !$showPrev }}" wire:click="previousPage('{{ $defaultTake }}')">
<svg class="w-4 h-4" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="m14 6l-6 6l6 6z" />
</svg>
</x-forms.button>
<span class="text-sm text-gray-600 dark:text-gray-400 px-2">
Page {{ $currentPage }} of {{ ceil($executions_count / $defaultTake) }}
</span>
<x-forms.button disabled="{{ !$showNext }}" wire:click="nextPage('{{ $defaultTake }}')">
<svg class="w-4 h-4" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="m10 18l6-6l-6-6z" />
</svg>
</x-forms.button>
<section class="application-settings-section overflow-hidden">
<div class="application-settings-section-header">
<div>
<h2>Executions</h2>
<p>Review generated archives, storage availability, and backup output.</p>
</div>
<div class="flex flex-wrap items-center gap-2">
<x-forms.button wire:click="cleanupFailed">Clean failed backups</x-forms.button>
<x-modal-confirmation title="Cleanup Deleted Backup Entries?" isErrorButton
submitAction="cleanupDeleted()" :actions="[
'Permanently delete execution records already removed from local storage.',
'Actual backup files are not changed.',
]"
confirmationText="cleanup deleted backups"
confirmationLabel="Type cleanup deleted backups to confirm."
shortConfirmationLabel="Confirmation">
<x-slot:trigger>
<x-forms.button isError>Clean deleted entries</x-forms.button>
</x-slot:trigger>
</x-modal-confirmation>
</div>
@endif
<div class="flex flex-col gap-2 sm:flex-row sm:flex-wrap">
<x-forms.button wire:click='cleanupFailed' class="w-full sm:w-auto">Cleanup Failed Backups</x-forms.button>
<x-modal-confirmation title="Cleanup Deleted Backup Entries?" isErrorButton
submitAction="cleanupDeleted()"
:actions="['This will permanently delete all backup execution entries that are marked as deleted from local storage.', 'This only removes database entries, not actual backup files.']"
confirmationText="cleanup deleted backups"
confirmationLabel="Please confirm by typing 'cleanup deleted backups' below"
shortConfirmationLabel="Confirmation">
<x-slot:trigger>
<x-forms.button isError class="w-full sm:w-auto">Cleanup Deleted</x-forms.button>
</x-slot:trigger>
</x-modal-confirmation>
</div>
</div>
<div @if (!$skip) wire:poll.5000ms="refreshBackupExecutions" @endif
class="flex flex-col gap-4 pt-2">
@forelse($executions as $execution)
<div wire:key="{{ data_get($execution, 'id') }}" @class([
'flex flex-col border-l-2 transition-colors p-4 bg-white dark:bg-coolgray-100 text-black dark:text-white',
'border-blue-500/50 border-dashed' =>
data_get($execution, 'status') === 'running',
'border-error' => data_get($execution, 'status') === 'failed',
'border-success' => data_get($execution, 'status') === 'success',
])>
@if (data_get($execution, 'status') === 'running')
<div class="absolute top-2 right-2">
<x-loading />
</div>
@endif
<div class="flex items-center gap-2 mb-2">
<span @class([
'px-3 py-1 rounded-md text-xs font-medium tracking-wide shadow-xs',
'bg-blue-100/80 text-blue-700 dark:bg-blue-500/20 dark:text-blue-300 dark:shadow-blue-900/5' =>
data_get($execution, 'status') === 'running',
'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-200 dark:shadow-red-900/5' =>
data_get($execution, 'status') === 'failed',
'bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-200 dark:shadow-amber-900/5' =>
data_get($execution, 'status') === 'success' && data_get($execution, 's3_uploaded') === false,
'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-200 dark:shadow-green-900/5' =>
data_get($execution, 'status') === 'success' && data_get($execution, 's3_uploaded') !== false,
])>
@php
$statusText = match (data_get($execution, 'status')) {
'success' => data_get($execution, 's3_uploaded') === false ? 'Success (S3 Warning)' : 'Success',
'running' => 'In Progress',
'failed' => 'Failed',
default => ucfirst(data_get($execution, 'status')),
};
@endphp
{{ $statusText }}
</span>
<div @if (! $skip) wire:poll.5000ms="refreshBackupExecutions" @endif
class="application-settings-section-body p-0!">
<div class="data-table">
<div class="data-table-header backup-executions-table-grid">
<span>Status</span>
<span>Database</span>
<span>Finished</span>
<span>Duration</span>
<span>Size</span>
<span>Availability</span>
<span class="text-right">Actions</span>
</div>
<div class="text-gray-600 dark:text-gray-400 text-sm">
@if (data_get($execution, 'status') === 'running')
<span title="Started: {{ formatDateInServerTimezone(data_get($execution, 'created_at'), $this->server()) }}">
Running for {{ calculateDuration(data_get($execution, 'created_at'), now()) }}
</span>
@else
<span title="Started: {{ formatDateInServerTimezone(data_get($execution, 'created_at'), $this->server()) }}&#10;Ended: {{ formatDateInServerTimezone(data_get($execution, 'finished_at'), $this->server()) }}">
{{ \Carbon\Carbon::parse(data_get($execution, 'finished_at'))->diffForHumans() }}
({{ calculateDuration(data_get($execution, 'created_at'), data_get($execution, 'finished_at')) }})
• {{ \Carbon\Carbon::parse(data_get($execution, 'finished_at'))->format('M j, H:i') }}
</span>
@endif
• Database: {{ data_get($execution, 'database_name', 'N/A') }}
@if(data_get($execution, 'size'))
• Size: {{ formatBytes(data_get($execution, 'size')) }}
@endif
</div>
<div class="text-gray-600 dark:text-gray-400 text-sm">
Location: {{ data_get($execution, 'filename', 'N/A') }}
</div>
<div class="flex flex-col gap-2 mt-2 sm:flex-row sm:flex-wrap sm:items-center sm:gap-3">
<div class="text-gray-600 dark:text-gray-400 text-sm">
Backup Availability:
</div>
<span @class([
'px-2 py-1 rounded-sm text-xs font-medium',
'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-200' => !data_get(
$execution,
'local_storage_deleted',
false),
'bg-gray-100 text-gray-600 dark:bg-gray-800/50 dark:text-gray-400' => data_get(
$execution,
'local_storage_deleted',
false),
])>
<span class="flex items-center gap-1">
@if (!data_get($execution, 'local_storage_deleted', false))
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"></path>
</svg>
@else
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
clip-rule="evenodd"></path>
</svg>
@endif
Local Storage
</span>
</span>
@if (data_get($execution, 's3_uploaded') !== null)
<span @class([
'px-2 py-1 rounded-sm text-xs font-medium',
'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-200' => data_get($execution, 's3_uploaded') === false && !data_get($execution, 's3_storage_deleted', false),
'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-200' => data_get($execution, 's3_uploaded') === true && !data_get($execution, 's3_storage_deleted', false),
'bg-gray-100 text-gray-600 dark:bg-gray-800/50 dark:text-gray-400' => data_get($execution, 's3_storage_deleted', false),
])>
<span class="flex items-center gap-1">
@if (data_get($execution, 's3_uploaded') === true && !data_get($execution, 's3_storage_deleted', false))
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"></path>
</svg>
@else
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
clip-rule="evenodd"></path>
</svg>
@endif
S3 Storage
</span>
</span>
@endif
</div>
@if (data_get($execution, 'message'))
<div class="mt-2 p-2 bg-gray-100 dark:bg-coolgray-200 rounded-sm">
<pre class="whitespace-pre-wrap text-sm">{{ data_get($execution, 'message') }}</pre>
</div>
@endif
<div class="grid grid-cols-2 gap-2 mt-4 sm:flex sm:flex-wrap">
@if (data_get($execution, 'status') === 'success')
<x-forms.button class="w-full dark:hover:bg-coolgray-400 sm:w-auto"
x-on:click="download_file('{{ data_get($execution, 'id') }}')">Download</x-forms.button>
@endif
@forelse ($executions as $execution)
@php
$executionStatus = data_get($execution, 'status');
[$executionStatusLabel, $executionStatusType] = match ($executionStatus) {
'success' => data_get($execution, 's3_uploaded') === false
? ['S3 warning', 'warning']
: ['Success', 'success'],
'running' => ['In progress', 'warning'],
'failed' => ['Failed', 'error'],
default => [str($executionStatus)->headline(), 'neutral'],
};
$executionCheckboxes = [];
$deleteActions = [];
if (!data_get($execution, 'local_storage_deleted', false)) {
if (! data_get($execution, 'local_storage_deleted', false)) {
$deleteActions[] = 'This backup will be permanently deleted from local storage.';
}
if (data_get($execution, 's3_uploaded') === true && !data_get($execution, 's3_storage_deleted', false)) {
$executionCheckboxes[] = ['id' => 'delete_backup_s3', 'label' => 'Delete the selected backup permanently from S3 Storage'];
if (data_get($execution, 's3_uploaded') === true
&& ! data_get($execution, 's3_storage_deleted', false)) {
$executionCheckboxes[] = [
'id' => 'delete_backup_s3',
'label' => 'Delete the selected backup permanently from S3 Storage',
];
}
if (empty($deleteActions)) {
$deleteActions[] = 'This backup execution record will be deleted.';
}
@endphp
<x-modal-confirmation title="Confirm Backup Deletion?" isErrorButton
submitAction="deleteBackup({{ data_get($execution, 'id') }})" :checkboxes="$executionCheckboxes"
:actions="$deleteActions" confirmationText="{{ data_get($execution, 'filename') }}"
confirmationLabel="Please confirm the execution of the actions by entering the Backup Filename below"
shortConfirmationLabel="Backup Filename">
<x-slot:trigger>
<x-forms.button isError class="w-full sm:w-auto">Delete</x-forms.button>
</x-slot:trigger>
</x-modal-confirmation>
</div>
<div wire:key="{{ data_get($execution, 'id') }}"
class="border-b border-neutral-200 last:border-b-0 dark:border-white/[0.06]">
<div class="data-table-row backup-executions-table-grid">
<div class="flex items-center gap-2">
<x-status-badge :status="$executionStatusLabel"
:type="$executionStatusType" />
@if ($executionStatus === 'running')
<x-loading />
@endif
</div>
<div class="truncate text-[12px] font-medium text-black dark:text-fg">
{{ data_get($execution, 'database_name', 'N/A') }}
</div>
<div class="text-[11px] text-neutral-600 dark:text-fg-dim">
@if ($executionStatus === 'running')
Running now
@else
{{ \Carbon\Carbon::parse(data_get($execution, 'finished_at'))->diffForHumans() }}
@endif
</div>
<div class="text-[11px] text-neutral-600 dark:text-fg-dim">
{{ calculateDuration(
data_get($execution, 'created_at'),
$executionStatus === 'running' ? now() : data_get($execution, 'finished_at'),
) }}
</div>
<div class="text-[11px] text-neutral-600 dark:text-fg-dim">
{{ data_get($execution, 'size') ? formatBytes(data_get($execution, 'size')) : '-' }}
</div>
<div class="flex flex-wrap items-center gap-1.5">
<x-status-badge label="Local"
:status="data_get($execution, 'local_storage_deleted', false) ? 'Deleted' : 'Available'"
:type="data_get($execution, 'local_storage_deleted', false) ? 'neutral' : 'success'" />
@if (data_get($execution, 's3_uploaded') !== null)
<x-status-badge label="S3"
:status="data_get($execution, 's3_storage_deleted', false)
? 'Deleted'
: (data_get($execution, 's3_uploaded') ? 'Available' : 'Failed')"
:type="data_get($execution, 's3_storage_deleted', false)
? 'neutral'
: (data_get($execution, 's3_uploaded') ? 'success' : 'error')" />
@endif
</div>
<div class="flex items-center justify-end gap-1">
@if ($executionStatus === 'success')
<button type="button" class="button"
x-on:click="download_file('{{ data_get($execution, 'id') }}')">
Download
</button>
@endif
<x-modal-confirmation title="Confirm Backup Deletion?" isErrorButton
submitAction="deleteBackup({{ data_get($execution, 'id') }})"
:checkboxes="$executionCheckboxes" :actions="$deleteActions"
confirmationText="{{ data_get($execution, 'filename') }}"
confirmationLabel="Enter the backup filename to confirm."
shortConfirmationLabel="Backup Filename">
<x-slot:trigger>
<x-forms.button isError>Delete</x-forms.button>
</x-slot:trigger>
</x-modal-confirmation>
</div>
</div>
@if (data_get($execution, 'message'))
<div class="border-t border-neutral-200 bg-neutral-50 px-4 py-3 dark:border-white/[0.06] dark:bg-white/[0.02]">
<pre
class="max-h-48 overflow-auto whitespace-pre-wrap rounded-lg bg-neutral-100 p-3 font-mono text-xs leading-5 text-neutral-700 dark:bg-black/20 dark:text-fg-dim">{{ data_get($execution, 'message') }}</pre>
</div>
@endif
</div>
@empty
<x-empty size="sm" title="No backup executions"
description="Execution history appears here after the schedule runs."
icon-name="browser-terminal" />
@endforelse
</div>
@empty
<div class="p-4 bg-gray-100 dark:bg-coolgray-100 rounded-sm">No executions found.</div>
@endforelse
</div>
@if ($executions_count > 0)
<div
class="flex items-center justify-between border-t border-neutral-200 px-4 py-3 text-sm text-neutral-500 dark:border-white/[0.06] dark:text-fg-dim">
<span>{{ $executions_count }} execution{{ $executions_count === 1 ? '' : 's' }}</span>
<div class="flex items-center gap-2">
<span>Page {{ $currentPage }} of {{ ceil($executions_count / $defaultTake) }}</span>
<button type="button" class="icon-button" @disabled(! $showPrev)
wire:click="previousPage('{{ $defaultTake }}')" aria-label="Previous page">
<x-reicon name="arrow-right" class="size-3.5 rotate-180" />
</button>
<button type="button" class="icon-button" @disabled(! $showNext)
wire:click="nextPage('{{ $defaultTake }}')" aria-label="Next page">
<x-reicon name="arrow-right" class="size-3.5" />
</button>
</div>
</div>
@endif
</div>
</section>
@endisset
</div>
@script
<script>
window.download_file = function(executionId) {
window.open('/download/backup/' + executionId, '_blank');
}
</script>
<script>
window.download_file = function(executionId) {
window.open('/download/backup/' + executionId, '_blank');
}
</script>
@endscript