From 0a567cb8e7dc0bab09f769d71dd160b44626ad10 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 15 Oct 2026 14:10:14 +0200 Subject: [PATCH] feat(backups): refresh backup views live on team channel events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Refresh application, database and volume backup lists on BackupCreated - Refresh backup execution, edit and service resource views on ServiceStatusChanged as well as ServiceChecked - Hide the deployments counter when only the current page's application is deploying, since its heading already shows "Deploying…" - Show storage section shortcuts in the service configuration sidebar - Drive server sidebar Sentinel/Security active state from activeMenu and activeSubMenu instead of route matching - Start the app sidebar expanded for new users - Drop a column from the backup executions table grid and adjust the responsive breakpoints - Update the related feature tests --- app/Livewire/DeploymentsIndicator.php | 22 ++++ .../Project/Application/Backup/Index.php | 10 +- .../Project/Database/Backup/Execution.php | 10 +- app/Livewire/Project/Database/BackupEdit.php | 5 +- .../Project/Database/ScheduledBackups.php | 10 +- app/Livewire/Project/Service/ResourceCard.php | 1 + .../Project/Service/VolumeBackup/Index.php | 1 + .../Project/Shared/Storages/VolumeBackups.php | 11 ++ resources/css/app.css | 10 +- .../views/components/server/sidebar.blade.php | 12 +-- .../service/configuration-sidebar.blade.php | 15 ++- resources/views/layouts/app.blade.php | 4 +- .../livewire/deployments-indicator.blade.php | 2 +- .../database/backup-executions.blade.php | 46 ++++---- .../service/backup-executions.blade.php | 3 + .../project/service/configuration.blade.php | 102 +----------------- .../shared/storages/volume-backups.blade.php | 3 + .../server/security/patches.blade.php | 2 +- .../server/security/terminal-access.blade.php | 7 +- .../livewire/server/sentinel/logs.blade.php | 2 +- .../livewire/server/sentinel/show.blade.php | 2 +- .../ServiceConfigAuthorizationTest.php | 10 ++ tests/Feature/BackupNowAvailabilityTest.php | 21 ++-- .../Feature/BackupS3DestinationsFormTest.php | 2 +- tests/Feature/DeploymentsIndicatorTest.php | 39 +++++++ tests/Feature/ServerSettingsAuditTest.php | 6 ++ tests/Feature/ServiceHeadingStatusTest.php | 15 +++ 27 files changed, 209 insertions(+), 164 deletions(-) diff --git a/app/Livewire/DeploymentsIndicator.php b/app/Livewire/DeploymentsIndicator.php index 55dc5b9a85..190da1a097 100644 --- a/app/Livewire/DeploymentsIndicator.php +++ b/app/Livewire/DeploymentsIndicator.php @@ -5,6 +5,7 @@ namespace App\Livewire; use App\Models\ApplicationDeploymentQueue; use App\Models\Server; use Livewire\Attributes\Computed; +use Livewire\Attributes\Locked; use Livewire\Component; class DeploymentsIndicator extends Component @@ -14,6 +15,17 @@ class DeploymentsIndicator extends Component */ public string $variant = 'topbar'; + /** + * The application on the current page. Its heading has its own "Deploying…" indicator. + */ + #[Locked] + public ?string $currentApplicationUuid = null; + + public function mount(): void + { + $this->currentApplicationUuid = request()->route('application_uuid'); + } + #[Computed] public function deployments() { @@ -41,6 +53,16 @@ class DeploymentsIndicator extends Component return $this->deployments->count(); } + /** + * Hide the counter when it would only repeat the current page's "Deploying…" indicator. + */ + #[Computed] + public function isOnlyCurrentApplicationDeploying(): bool + { + return $this->currentApplicationUuid !== null + && $this->deployments->every(fn ($deployment) => $deployment->application?->uuid === $this->currentApplicationUuid); + } + public function render() { return view('livewire.deployments-indicator'); diff --git a/app/Livewire/Project/Application/Backup/Index.php b/app/Livewire/Project/Application/Backup/Index.php index 7ca097db96..e53a3cdf88 100644 --- a/app/Livewire/Project/Application/Backup/Index.php +++ b/app/Livewire/Project/Application/Backup/Index.php @@ -4,6 +4,7 @@ namespace App\Livewire\Project\Application\Backup; use App\Models\Application; use App\Models\ScheduledVolumeBackup; +use App\Traits\ListensToTeamChannel; use Illuminate\Contracts\View\View; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Livewire\Component; @@ -11,6 +12,7 @@ use Livewire\Component; class Index extends Component { use AuthorizesRequests; + use ListensToTeamChannel; public Application $application; @@ -18,7 +20,13 @@ class Index extends Component public string $search = ''; - protected $listeners = ['refreshVolumeBackups' => '$refresh']; + public function getListeners(): array + { + return [ + 'refreshVolumeBackups' => '$refresh', + ...$this->teamChannelListeners(['BackupCreated' => '$refresh']), + ]; + } public function mount(): void { diff --git a/app/Livewire/Project/Database/Backup/Execution.php b/app/Livewire/Project/Database/Backup/Execution.php index 02e8e8c0fe..c96c7e5217 100644 --- a/app/Livewire/Project/Database/Backup/Execution.php +++ b/app/Livewire/Project/Database/Backup/Execution.php @@ -6,6 +6,7 @@ use App\Livewire\Concerns\ManagesDatabaseBackup; use App\Livewire\Project\Database\BackupEdit; use App\Models\S3Storage; use App\Models\ScheduledDatabaseBackup; +use App\Traits\ListensToTeamChannel; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Auth; @@ -15,6 +16,7 @@ use Livewire\Features\SupportRedirects\Redirector; class Execution extends Component { use AuthorizesRequests; + use ListensToTeamChannel; use ManagesDatabaseBackup; public $database; @@ -42,7 +44,13 @@ class Execution extends Component $listeners["echo-private:user.{$user->id},DatabaseStatusChanged"] = '$refresh'; } - return $listeners; + return [ + ...$listeners, + ...$this->teamChannelListeners([ + 'ServiceChecked' => '$refresh', + 'ServiceStatusChanged' => '$refresh', + ]), + ]; } public function mount() diff --git a/app/Livewire/Project/Database/BackupEdit.php b/app/Livewire/Project/Database/BackupEdit.php index 81be813f64..97da01715e 100644 --- a/app/Livewire/Project/Database/BackupEdit.php +++ b/app/Livewire/Project/Database/BackupEdit.php @@ -115,7 +115,10 @@ class BackupEdit extends Component return [ ...$listeners, - ...$this->teamChannelListeners(['ServiceChecked' => 'refreshStatus']), + ...$this->teamChannelListeners([ + 'ServiceChecked' => 'refreshStatus', + 'ServiceStatusChanged' => 'refreshStatus', + ]), ]; } diff --git a/app/Livewire/Project/Database/ScheduledBackups.php b/app/Livewire/Project/Database/ScheduledBackups.php index 2071337b4b..b9da614ea3 100644 --- a/app/Livewire/Project/Database/ScheduledBackups.php +++ b/app/Livewire/Project/Database/ScheduledBackups.php @@ -3,6 +3,7 @@ namespace App\Livewire\Project\Database; use App\Models\ServiceDatabase; +use App\Traits\ListensToTeamChannel; use Illuminate\Contracts\View\View; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Livewire\Component; @@ -10,6 +11,7 @@ use Livewire\Component; class ScheduledBackups extends Component { use AuthorizesRequests; + use ListensToTeamChannel; public $database; @@ -19,7 +21,13 @@ class ScheduledBackups extends Component public string $custom_type = 'mysql'; - protected $listeners = ['refreshScheduledBackups']; + public function getListeners(): array + { + return [ + 'refreshScheduledBackups', + ...$this->teamChannelListeners(['BackupCreated' => '$refresh']), + ]; + } public function mount(): void { diff --git a/app/Livewire/Project/Service/ResourceCard.php b/app/Livewire/Project/Service/ResourceCard.php index 22ed772a80..5b67baab01 100644 --- a/app/Livewire/Project/Service/ResourceCard.php +++ b/app/Livewire/Project/Service/ResourceCard.php @@ -24,6 +24,7 @@ class ResourceCard extends Component public function getListeners(): array { return $this->teamChannelListeners([ + 'ServiceStatusChanged' => 'refreshResource', 'ServiceChecked' => 'refreshResource', ]); } diff --git a/app/Livewire/Project/Service/VolumeBackup/Index.php b/app/Livewire/Project/Service/VolumeBackup/Index.php index 64dbbb8df2..e73bc12216 100644 --- a/app/Livewire/Project/Service/VolumeBackup/Index.php +++ b/app/Livewire/Project/Service/VolumeBackup/Index.php @@ -45,6 +45,7 @@ class Index extends Component 'modalClosed' => 'closeScheduleModal', ...$this->teamChannelListeners([ 'ServiceChecked' => '$refresh', + 'ServiceStatusChanged' => '$refresh', 'BackupCreated' => '$refresh', ]), ]; diff --git a/app/Livewire/Project/Shared/Storages/VolumeBackups.php b/app/Livewire/Project/Shared/Storages/VolumeBackups.php index 44cd519850..bfa122d0e2 100644 --- a/app/Livewire/Project/Shared/Storages/VolumeBackups.php +++ b/app/Livewire/Project/Shared/Storages/VolumeBackups.php @@ -11,6 +11,7 @@ use App\Models\LocalPersistentVolume; use App\Models\S3Storage; use App\Models\ScheduledVolumeBackup; use App\Models\Service; +use App\Traits\ListensToTeamChannel; use App\Traits\PreparesBackupDownloads; use Illuminate\Database\Eloquent\Builder; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; @@ -25,6 +26,7 @@ use Throwable; class VolumeBackups extends Component { use AuthorizesRequests; + use ListensToTeamChannel; use PreparesBackupDownloads; use WithPagination; @@ -72,6 +74,15 @@ class VolumeBackups extends Component public int $perPage = 10; + public function getListeners(): array + { + if ($this->section !== 'executions') { + return []; + } + + return $this->teamChannelListeners(['BackupCreated' => '$refresh']); + } + public function updatedPerPage(): void { $this->perPage = max(1, min(100, $this->perPage)); diff --git a/resources/css/app.css b/resources/css/app.css index 8e95f2f1d5..0efb327b20 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -3279,7 +3279,7 @@ input[type="search"]::-webkit-search-results-decoration { } .backup-executions-table-grid { - grid-template-columns: 6.5rem minmax(7rem, 0.8fr) minmax(14rem, 1.5fr) 7rem 5rem 4rem minmax(8rem, 1fr) 5rem; + grid-template-columns: 6.5rem minmax(7rem, 0.8fr) minmax(14rem, 1.5fr) 8.5rem 5rem minmax(8rem, 1fr) 5rem; gap: 0.75rem; min-width: 66rem; } @@ -3345,11 +3345,10 @@ input[type="search"]::-webkit-search-results-decoration { } .backup-executions-table-grid { - grid-template-columns: 7.5rem minmax(9rem, 0.8fr) minmax(14rem, 1.5fr) 8rem 6rem minmax(9rem, auto); + grid-template-columns: 7.5rem minmax(9rem, 0.8fr) minmax(14rem, 1.5fr) 8.5rem minmax(8rem, 1fr) 5rem; } - .backup-executions-table-grid > :nth-child(6), - .backup-executions-table-grid > :nth-child(7) { + .backup-executions-table-grid > :nth-child(5) { display: none; } } @@ -3440,8 +3439,7 @@ input[type="search"]::-webkit-search-results-decoration { .backup-executions-table-grid > :nth-child(4), .backup-executions-table-grid > :nth-child(5), - .backup-executions-table-grid > :nth-child(6), - .backup-executions-table-grid > :nth-child(7) { + .backup-executions-table-grid > :nth-child(6) { display: none; } diff --git a/resources/views/components/server/sidebar.blade.php b/resources/views/components/server/sidebar.blade.php index 3ff08eec17..d5eb8f6c3a 100644 --- a/resources/views/components/server/sidebar.blade.php +++ b/resources/views/components/server/sidebar.blade.php @@ -77,7 +77,7 @@ [ 'label' => 'Sentinel', 'route' => 'server.sentinel', - 'active' => request()->routeIs('server.sentinel', 'server.sentinel.*'), + 'active' => $activeMenu === 'sentinel', 'icon' => 'shield-star', 'group' => 'Platform', 'visible' => ! $server->isSwarm() && $server->canHostResources() && auth()->user()?->can('viewSentinel', $server), @@ -85,8 +85,8 @@ 'warning' => $server->isSentinelEnabled() && $sentinelStatus === 'out_of_sync', 'tracks_sentinel_status' => true, 'children' => [ - ['label' => 'Configuration', 'route' => 'server.sentinel', 'active' => request()->routeIs('server.sentinel'), 'icon' => 'settings'], - ['label' => 'Logs', 'route' => 'server.sentinel.logs', 'active' => request()->routeIs('server.sentinel.logs'), 'icon' => 'file-content'], + ['label' => 'Configuration', 'route' => 'server.sentinel', 'active' => $activeSubMenu === 'configuration', 'icon' => 'settings'], + ['label' => 'Logs', 'route' => 'server.sentinel.logs', 'active' => $activeSubMenu === 'logs', 'icon' => 'file-content'], ], ], [ @@ -182,13 +182,13 @@ [ 'label' => 'Security', 'route' => 'server.security.patches', - 'active' => request()->routeIs('server.security.*'), + 'active' => $activeMenu === 'security', 'icon' => 'shield-alert', 'group' => 'Security', 'visible' => auth()->user()?->can('update', $server), 'children' => [ - ['label' => 'Server Patching', 'route' => 'server.security.patches', 'active' => request()->routeIs('server.security.patches'), 'icon' => 'bandage'], - ['label' => 'Terminal Access', 'route' => 'server.security.terminal-access', 'active' => request()->routeIs('server.security.terminal-access'), 'icon' => 'browser-terminal', 'navigate' => false], + ['label' => 'Server Patching', 'route' => 'server.security.patches', 'active' => $activeSubMenu === 'patches', 'icon' => 'bandage'], + ['label' => 'Terminal Access', 'route' => 'server.security.terminal-access', 'active' => $activeSubMenu === 'terminal-access', 'icon' => 'browser-terminal', 'navigate' => false], ], ], [ diff --git a/resources/views/components/service/configuration-sidebar.blade.php b/resources/views/components/service/configuration-sidebar.blade.php index 461d43c395..f0469c1eec 100644 --- a/resources/views/components/service/configuration-sidebar.blade.php +++ b/resources/views/components/service/configuration-sidebar.blade.php @@ -1,4 +1,4 @@ -@props(['service', 'currentRoute', 'navOnly' => false]) +@props(['service', 'currentRoute', 'navOnly' => false, 'storageSections' => collect()]) @php $serviceRouteParameters = [ @@ -89,6 +89,19 @@ @endif + @if ($menuItem['active'] && $menuItem['route'] === 'project.service.storages' && $storageSections->isNotEmpty()) +
+ @endif @endforeach @endforeach diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index 4ec6c9937b..3e664f5683 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -9,8 +9,8 @@ @authReview generated archives, storage availability, and backup output.
{{ data_get($execution, 'filename', 'N/A') }}
-