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 @@ @auth
$variant === 'topbar'])> - @if ($this->deploymentCount > 0) + @if ($this->deploymentCount > 0 && ! $this->isOnlyCurrentApplicationDeploying) @php $deploymentLabel = $this->deploymentCount.' '.Str::plural('deployment', $this->deploymentCount); @endphp diff --git a/resources/views/livewire/project/database/backup-executions.blade.php b/resources/views/livewire/project/database/backup-executions.blade.php index ff8083636e..62bcde4df5 100644 --- a/resources/views/livewire/project/database/backup-executions.blade.php +++ b/resources/views/livewire/project/database/backup-executions.blade.php @@ -7,7 +7,6 @@

Executions

-

Review generated archives, storage availability, and backup output.

Clean failed backups @@ -40,9 +39,8 @@ class="data-table-header backup-executions-table-grid h-auto rounded-none px-4 py-2.5 text-[11px]"> Status Database - Backup path - Finished - Duration + Archive + Time Size Availability Actions @@ -58,6 +56,7 @@ 'failed' => ['Failed', 'error'], default => [str($executionStatus)->headline(), 'neutral'], }; + $finishedAt = \Carbon\Carbon::parse(data_get($execution, 'finished_at') ?? data_get($execution, 'updated_at')); $s3Replicas = $execution->s3Replicas; $hasLiveS3Copies = $s3Replicas->isNotEmpty() ? $s3Replicas->contains(fn ($replica) => $replica->s3_uploaded === true && ! $replica->s3_storage_deleted) @@ -90,40 +89,33 @@
{{ data_get($execution, 'database_name', 'N/A') }}
-
- {{ data_get($execution, 'filename', 'N/A') }} - +
+
-
+
@if ($executionStatus === 'running') - Running now + Running for {{ calculateDuration(data_get($execution, 'created_at'), now()) }} @else - {{ \Carbon\Carbon::parse(data_get($execution, 'finished_at'))->diffForHumans() }} + {{ $finishedAt->diffForHumans() }}
+ {{ calculateDuration(data_get($execution, 'created_at'), $finishedAt) }} @endif
-
- {{ calculateDuration( - data_get($execution, 'created_at'), - $executionStatus === 'running' ? now() : data_get($execution, 'finished_at'), - ) }} -
-
+
{{ data_get($execution, 'size') ? formatBytes(data_get($execution, 'size')) : '-' }}
- @foreach ($s3Replicas as $replica) message" /> @endforeach @if ($s3Replicas->isEmpty() && data_get($execution, 's3_uploaded') !== null) - diff --git a/resources/views/livewire/project/service/backup-executions.blade.php b/resources/views/livewire/project/service/backup-executions.blade.php index 246e362ee8..e25d1976ee 100644 --- a/resources/views/livewire/project/service/backup-executions.blade.php +++ b/resources/views/livewire/project/service/backup-executions.blade.php @@ -2,6 +2,9 @@ @if (count($pendingDownloads) > 0) @endif + @if ($executions->onFirstPage()) + + @endif @if ($selectedExecution) diff --git a/resources/views/livewire/project/service/configuration.blade.php b/resources/views/livewire/project/service/configuration.blade.php index c2223fede6..a0dbc2bfb3 100644 --- a/resources/views/livewire/project/service/configuration.blade.php +++ b/resources/views/livewire/project/service/configuration.blade.php @@ -6,53 +6,6 @@ @php - $serviceRouteParameters = [ - 'project_uuid' => $project->uuid, - 'environment_uuid' => $environment->uuid, - 'service_uuid' => $service->uuid, - ]; - - $configurationItems = collect([ - ['label' => 'General', 'route' => 'project.service.configuration', 'icon' => 'settings'], - ['label' => 'Domains', 'route' => 'project.service.domains', 'icon' => 'globe'], - ['label' => 'Environment Variables', 'route' => 'project.service.environment-variables', 'icon' => 'variables', 'hasWarning' => ! $service->isDeployable], - ['label' => 'Persistent Storage', 'route' => 'project.service.storages', 'icon' => 'storages'], - ['label' => 'Backups', 'route' => 'project.service.volume-backups.index', 'icon' => 'database'], - ['label' => 'Import Backup', 'route' => 'project.service.import-backup', 'icon' => 'upload', 'navigate' => false], - ['label' => 'Runtime Logs', 'route' => 'project.service.logs', 'icon' => 'unordered-list', 'navigate' => false], - ['label' => 'Terminal', 'route' => 'project.service.command', 'icon' => 'browser-terminal', 'navigate' => false, 'visible' => auth()->user()?->can('canAccessTerminal')], - ['label' => 'Analytics', 'route' => 'project.service.analytics', 'icon' => 'analytics'], - ['label' => 'Scheduled Tasks', 'route' => 'project.service.scheduled-tasks.show', 'icon' => 'calendar'], - ['label' => 'Webhooks', 'route' => 'project.service.webhooks', 'icon' => 'notifications'], - ['label' => 'Resource Operations', 'route' => 'project.service.resource-operations', 'icon' => 'server-update'], - ['label' => 'Tags', 'route' => 'project.service.tags', 'icon' => 'tags'], - ['label' => 'Danger Zone', 'route' => 'project.service.danger', 'icon' => 'danger'], - ])->filter(fn (array $item): bool => $item['visible'] ?? true)->map(fn (array $item): array => [ - ...$item, - 'active' => $currentRoute === $item['route'] - || ($item['route'] === 'project.service.scheduled-tasks.show' - && str($currentRoute)->startsWith('project.service.scheduled-tasks')), - ]); - - $menuGroups = [ - 'Settings' => ['General', 'Domains', 'Environment Variables', 'Persistent Storage'], - 'Observe & troubleshoot' => ['Runtime Logs', 'Terminal', 'Analytics'], - 'Automation' => ['Scheduled Tasks', 'Webhooks', 'Backups', 'Import Backup'], - 'Operations' => ['Resource Operations', 'Tags'], - // Always last, after a divider and without a group header. - 'Danger Zone' => ['Danger Zone'], - ]; - - $groupedItems = collect($menuGroups) - ->map(fn (array $labels) => collect($labels) - ->map(fn (string $label) => $configurationItems->firstWhere('label', $label)) - ->filter() - ->values()) - ->filter(fn ($items) => $items->isNotEmpty()); - - // Group that holds the current page — always kept open, even if collapsed before. - $activeGroup = (string) $groupedItems->search(fn ($items) => $items->contains(fn ($item) => $item['active'] ?? false)); - $storageSections = $applications ->concat($databases) ->map(fn ($resource): array => [ @@ -63,59 +16,8 @@
- - - +
@if ($currentRoute === 'project.service.configuration') diff --git a/resources/views/livewire/project/shared/storages/volume-backups.blade.php b/resources/views/livewire/project/shared/storages/volume-backups.blade.php index 052aa48f5e..0a395fa08e 100644 --- a/resources/views/livewire/project/shared/storages/volume-backups.blade.php +++ b/resources/views/livewire/project/shared/storages/volume-backups.blade.php @@ -17,6 +17,9 @@ @endcan @endif @if ($section === 'executions') + @if ($backup && $executions->onFirstPage()) + + @endif @include('livewire.project.shared.storages.volume-backups.executions') @elseif ($section === 'danger') @include('livewire.project.shared.storages.volume-backups.danger') diff --git a/resources/views/livewire/server/security/patches.blade.php b/resources/views/livewire/server/security/patches.blade.php index f1e4fc3f7a..bcb23f8c87 100644 --- a/resources/views/livewire/server/security/patches.blade.php +++ b/resources/views/livewire/server/security/patches.blade.php @@ -16,7 +16,7 @@
- +
- +
- - - -
- +
- + @if ($server->isFunctional())
diff --git a/tests/Feature/Authorization/ServiceConfigAuthorizationTest.php b/tests/Feature/Authorization/ServiceConfigAuthorizationTest.php index 2d37fd00d9..16ff1e9947 100644 --- a/tests/Feature/Authorization/ServiceConfigAuthorizationTest.php +++ b/tests/Feature/Authorization/ServiceConfigAuthorizationTest.php @@ -222,6 +222,16 @@ test('admin sees terminal link for service', function () { ->assertSee('Terminal'); }); +test('service configuration page sidebar links to the file browser', function () { + $this->actingAs($this->admin); + session(['currentTeam' => $this->team]); + Team::query()->update(['show_boarding' => false]); + + $this->get(route('project.service.configuration', $this->serviceParams)) + ->assertOk() + ->assertSee(route('project.service.files', $this->serviceParams), false); +}); + test('admin sees deploy button for service', function () { $this->actingAs($this->admin); session(['currentTeam' => $this->team]); diff --git a/tests/Feature/BackupNowAvailabilityTest.php b/tests/Feature/BackupNowAvailabilityTest.php index 3d03994201..c1a35d979b 100644 --- a/tests/Feature/BackupNowAvailabilityTest.php +++ b/tests/Feature/BackupNowAvailabilityTest.php @@ -114,16 +114,23 @@ it('checks current database status before queuing from a stale page', function ( Queue::assertNotPushed(DatabaseBackupJob::class); })->with('database backup controls'); -it('refreshes service backup buttons when the service status check completes', function () { +it('refreshes service backup buttons when the service status changes', function (string $componentClass, string $event) { $database = $this->databases['service']; $database->update(['status' => 'exited:unhealthy']); - $component = Livewire::test(Index::class, ['service' => $this->service]); + $parameters = $componentClass === Index::class + ? ['service' => $this->service] + : ['backup' => $this->backups['service']->fresh(), 'availableS3Storages' => collect()]; + $component = Livewire::test($componentClass, $parameters); + $disabledButton = '/]*wire:click(\.stop)?="backupNow/s'; $database->update(['status' => 'running:healthy']); - $component->dispatch('echo-private:team.'.currentTeam()->id.',ServiceChecked'); - expect($component->html())->not->toMatch('/]*wire:click.stop="backupNow/s'); + $component->dispatch('echo-private:team.'.currentTeam()->id.','.$event); + expect($component->html())->not->toMatch($disabledButton); $database->update(['status' => 'exited:unhealthy']); - $component->dispatch('echo-private:team.'.currentTeam()->id.',ServiceChecked'); - expect($component->html())->toMatch('/]*wire:click.stop="backupNow/s'); -}); + $component->dispatch('echo-private:team.'.currentTeam()->id.','.$event); + expect($component->html())->toMatch($disabledButton); +})->with([ + 'service list' => Index::class, + 'service settings' => BackupEdit::class, +])->with(['ServiceChecked', 'ServiceStatusChanged']); diff --git a/tests/Feature/BackupS3DestinationsFormTest.php b/tests/Feature/BackupS3DestinationsFormTest.php index 2e025fda21..61098268f7 100644 --- a/tests/Feature/BackupS3DestinationsFormTest.php +++ b/tests/Feature/BackupS3DestinationsFormTest.php @@ -282,7 +282,7 @@ it('shows the copy status of each destination in the execution lists', function } Livewire::test(BackupExecutions::class, ['backup' => $databaseBackup]) - ->assertSeeInOrder(['First S3 Available', 'Second S3 Failed']) + ->assertSeeInOrder(['First S3', 'Second S3 failed']) ->assertSee('Access denied') ->assertSee('Delete the selected backup permanently from every S3 storage'); diff --git a/tests/Feature/DeploymentsIndicatorTest.php b/tests/Feature/DeploymentsIndicatorTest.php index faa6262ad2..38758c6b51 100644 --- a/tests/Feature/DeploymentsIndicatorTest.php +++ b/tests/Feature/DeploymentsIndicatorTest.php @@ -74,3 +74,42 @@ it('does not show deployments of another team', function () { ->assertDontSee('Indicator App') ->assertDontSee('Active deployments'); }); + +it('hides the indicator on the page of the only application that is deploying', function () { + $this->get(route('project.application.configuration', [ + 'project_uuid' => $this->project->uuid, + 'environment_uuid' => $this->environment->uuid, + 'application_uuid' => $this->application->uuid, + ])) + ->assertSuccessful() + ->assertDontSee('aria-label="Active deployments"', false); +}); + +it('keeps the indicator on an application page when another application is deploying', function () { + $otherApplication = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + 'name' => 'Other App', + ]); + + ApplicationDeploymentQueue::create([ + 'application_id' => $otherApplication->id, + 'application_name' => $otherApplication->name, + 'deployment_uuid' => 'deploy-indicator-'.fake()->uuid(), + 'deployment_url' => '/deployments/other', + 'server_id' => $this->server->id, + 'server_name' => $this->server->name, + 'status' => ApplicationDeploymentStatus::QUEUED->value, + 'pull_request_id' => 0, + ]); + + $this->get(route('project.application.configuration', [ + 'project_uuid' => $this->project->uuid, + 'environment_uuid' => $this->environment->uuid, + 'application_uuid' => $this->application->uuid, + ])) + ->assertSuccessful() + ->assertSee('2 deployments', false) + ->assertSee('aria-label="Active deployments"', false); +}); diff --git a/tests/Feature/ServerSettingsAuditTest.php b/tests/Feature/ServerSettingsAuditTest.php index c9976c6657..979caccd1f 100644 --- a/tests/Feature/ServerSettingsAuditTest.php +++ b/tests/Feature/ServerSettingsAuditTest.php @@ -126,6 +126,12 @@ test('toggling terminal access records enabled and disabled events', function () ->and(serverSettingsAuditRows('ui.server.terminal_access.disabled'))->toHaveCount(1); }); +test('toggling terminal access keeps the security sidebar group open', function () { + Livewire::test(TerminalAccess::class, ['server_uuid' => $this->server->uuid]) + ->call('toggleTerminal', 'password') + ->assertSeeHtml("activeGroup: 'Security'"); +}); + test('server settings saves record changed setting names without the sentinel token', function () { $newToken = 'audit-secret-sentinel-token-123'; diff --git a/tests/Feature/ServiceHeadingStatusTest.php b/tests/Feature/ServiceHeadingStatusTest.php index 9cd0488e2d..fb4678ad2f 100644 --- a/tests/Feature/ServiceHeadingStatusTest.php +++ b/tests/Feature/ServiceHeadingStatusTest.php @@ -1,6 +1,7 @@ assertSeeInOrder(['Service status', 'Containers', 'Healthcheck']) ->assertSee('Degraded'); }); + +test('a compose resource row shows the new status when the service status changes', function () { + $component = Livewire::test(ResourceCard::class, [ + 'service' => $this->service->fresh(), + 'resource' => $this->exitedApp->fresh(), + 'parameters' => $this->parameters, + ])->assertSee('Exited'); + + $this->exitedApp->forceFill(['status' => 'running:healthy'])->save(); + + $component->dispatch("echo-private:team.{$this->team->id},ServiceStatusChanged") + ->assertSee('Running') + ->assertDontSee('Exited'); +});