From 17a9fa624e37f8bef1c5736b5d0c3a663a10ede2 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 6 Nov 2026 15:20:41 +0100 Subject: [PATCH] feat(ui): move maintenance mode to its own page with a confirmed toggle Co-Authored-By: Claude Opus 5.5 --- app/Livewire/Project/Shared/Maintenance.php | 8 +++-- .../configuration-sidebar.blade.php | 8 ++++- .../service/configuration-sidebar.blade.php | 3 +- .../application/configuration.blade.php | 1 + .../project/service/configuration.blade.php | 1 + .../project/shared/maintenance.blade.php | 33 +++++++++++++++---- routes/web.php | 2 ++ tests/Feature/Proxy/MaintenanceModeTest.php | 6 ++-- 8 files changed, 47 insertions(+), 15 deletions(-) diff --git a/app/Livewire/Project/Shared/Maintenance.php b/app/Livewire/Project/Shared/Maintenance.php index 29c2263e5a..b79f9d1ff3 100644 --- a/app/Livewire/Project/Shared/Maintenance.php +++ b/app/Livewire/Project/Shared/Maintenance.php @@ -38,13 +38,17 @@ class Maintenance extends Component && in_array($server->proxyType(), [ProxyTypes::TRAEFIK->value, ProxyTypes::CADDY->value], true)); } - public function instantSaveMaintenance(): void + /** + * Turn maintenance mode on or off. The header button calls this after the user confirms. + */ + public function toggleMaintenance(): void { try { $this->authorize('update', $this->resource); - $this->resource->is_maintenance_enabled = $this->isMaintenanceEnabled; + $this->resource->is_maintenance_enabled = ! $this->resource->is_maintenance_enabled; $this->resource->save(); $this->resource->syncMaintenancePage(); + $this->isMaintenanceEnabled = (bool) $this->resource->is_maintenance_enabled; $this->dispatch('success', $this->isMaintenanceEnabled ? 'Maintenance mode enabled.' : 'Maintenance mode disabled.'); } catch (\Throwable $e) { $this->isMaintenanceEnabled = (bool) $this->resource->fresh()?->is_maintenance_enabled; diff --git a/resources/views/components/application/configuration-sidebar.blade.php b/resources/views/components/application/configuration-sidebar.blade.php index f94f010b3d..c049effcc4 100644 --- a/resources/views/components/application/configuration-sidebar.blade.php +++ b/resources/views/components/application/configuration-sidebar.blade.php @@ -119,6 +119,11 @@ 'route' => 'project.application.resource-operations', 'active' => $currentRoute === 'project.application.resource-operations', ], + [ + 'label' => 'Maintenance Mode', + 'route' => 'project.application.maintenance', + 'active' => $currentRoute === 'project.application.maintenance', + ], [ 'label' => 'Metrics', 'route' => 'project.application.metrics', @@ -175,6 +180,7 @@ 'Rollback' => 'time-back', 'Resource Limits' => 'cpu', 'Resource Operations' => 'server-update', + 'Maintenance Mode' => 'bandage', 'Metrics' => 'graph', 'Analytics' => 'analytics', 'Security' => 'shield-alert', @@ -188,7 +194,7 @@ 'Observe & troubleshoot' => ['Runtime Logs', 'Deployment Logs', 'Terminal', 'Files', 'Metrics', 'Analytics', 'Security'], 'Deploy' => ['Git Source', 'Servers', 'Preview Deployments'], 'Automation' => ['Scheduled Tasks', 'Webhooks', 'Backups'], - 'Operations' => ['Resource Operations', 'Resource Limits', 'Rollback', 'Tags'], + 'Operations' => ['Resource Operations', 'Maintenance Mode', 'Resource Limits', 'Rollback', 'Tags'], 'Danger Zone' => ['Danger Zone'], ]; $groupedMenuItems = collect($menuGroups) diff --git a/resources/views/components/service/configuration-sidebar.blade.php b/resources/views/components/service/configuration-sidebar.blade.php index 258b48abd1..56961a6a8c 100644 --- a/resources/views/components/service/configuration-sidebar.blade.php +++ b/resources/views/components/service/configuration-sidebar.blade.php @@ -22,6 +22,7 @@ ['label' => 'Scheduled Tasks', 'route' => 'project.service.scheduled-tasks.show', 'icon' => 'calendar'], ['label' => 'Webhooks', 'route' => 'project.service.webhooks', 'icon' => 'webhook'], ['label' => 'Resource Operations', 'route' => 'project.service.resource-operations', 'icon' => 'server-update'], + ['label' => 'Maintenance Mode', 'route' => 'project.service.maintenance', 'icon' => 'bandage'], ['label' => 'Security', 'route' => 'project.service.vulnerabilities', 'icon' => 'shield-alert', 'visible' => $imageScanningAllowed, 'security' => $imageScanningAllowed ? \App\Services\ImageScan\SecurityStatus::stored($service)->status->value : null], ['label' => 'Tags', 'route' => 'project.service.tags', 'icon' => 'tags'], @@ -40,7 +41,7 @@ 'Settings' => ['General', 'Domains', 'Environment Variables', 'Persistent Storage'], 'Observe & troubleshoot' => ['Runtime Logs', 'Terminal', 'Files', 'Analytics', 'Security'], 'Automation' => ['Scheduled Tasks', 'Webhooks', 'Backups'], - 'Operations' => ['Resource Operations', 'Tags'], + 'Operations' => ['Resource Operations', 'Maintenance Mode', 'Tags'], 'Danger Zone' => ['Danger Zone'], ]; diff --git a/resources/views/livewire/project/application/configuration.blade.php b/resources/views/livewire/project/application/configuration.blade.php index 0d5c61e8bf..26445d410d 100644 --- a/resources/views/livewire/project/application/configuration.blade.php +++ b/resources/views/livewire/project/application/configuration.blade.php @@ -14,6 +14,7 @@ @elseif ($currentRoute === 'project.application.domains') + @elseif ($currentRoute === 'project.application.maintenance') @elseif ($currentRoute === 'project.application.swarm' && $application->destination->server->isSwarm()) diff --git a/resources/views/livewire/project/service/configuration.blade.php b/resources/views/livewire/project/service/configuration.blade.php index c0cb85f513..b5ad51065e 100644 --- a/resources/views/livewire/project/service/configuration.blade.php +++ b/resources/views/livewire/project/service/configuration.blade.php @@ -81,6 +81,7 @@ :lazy="(bool) ($service->server ?? $service->destination?->server)?->isTrafficAnalyticsEnabled()" /> @elseif ($currentRoute === 'project.service.domains') + @elseif ($currentRoute === 'project.service.maintenance') @elseif ($currentRoute === 'project.service.environment-variables') diff --git a/resources/views/livewire/project/shared/maintenance.blade.php b/resources/views/livewire/project/shared/maintenance.blade.php index 822d672f47..378c4dab4f 100644 --- a/resources/views/livewire/project/shared/maintenance.blade.php +++ b/resources/views/livewire/project/shared/maintenance.blade.php @@ -3,14 +3,33 @@ @if ($this->isSupported) + + {{-- The inline wrapper puts the badge text on the same line as the title text. --}} + + @if ($isMaintenanceEnabled) + + @else + + @endif + + + @can('update', $resource) + + + + @endcan
-
- -
group(function () { Route::prefix('project/{project_uuid}/environment/{environment_uuid}/application/{application_uuid}')->group(function () { Route::get('/', ApplicationConfiguration::class)->name('project.application.configuration'); Route::get('/domains', ApplicationConfiguration::class)->name('project.application.domains'); + Route::get('/maintenance', ApplicationConfiguration::class)->name('project.application.maintenance'); Route::get('/swarm', ApplicationConfiguration::class)->name('project.application.swarm'); Route::get('/advanced', ApplicationConfiguration::class)->name('project.application.advanced'); Route::get('/environment-variables', ApplicationConfiguration::class)->name('project.application.environment-variables'); @@ -490,6 +491,7 @@ Route::middleware(['auth', 'verified'])->group(function () { Route::prefix('project/{project_uuid}/environment/{environment_uuid}/service/{service_uuid}')->group(function () { Route::get('/', ServiceConfiguration::class)->name('project.service.configuration'); Route::get('/domains', ServiceConfiguration::class)->name('project.service.domains'); + Route::get('/maintenance', ServiceConfiguration::class)->name('project.service.maintenance'); Route::get('/logs', Logs::class)->name('project.service.logs'); Route::get('/analytics', ServiceConfiguration::class)->name('project.service.analytics'); Route::get('/vulnerabilities', ServiceConfiguration::class)->name('project.service.vulnerabilities'); diff --git a/tests/Feature/Proxy/MaintenanceModeTest.php b/tests/Feature/Proxy/MaintenanceModeTest.php index e0791cbc63..fb2eb30faa 100644 --- a/tests/Feature/Proxy/MaintenanceModeTest.php +++ b/tests/Feature/Proxy/MaintenanceModeTest.php @@ -262,8 +262,7 @@ it('lets an admin enable maintenance and save a custom page', function () { ->set('maintenancePage', '

Back soon

') ->call('submit') ->assertDispatched('success') - ->set('isMaintenanceEnabled', true) - ->call('instantSaveMaintenance') + ->call('toggleMaintenance') ->assertDispatched('success'); $application->refresh(); @@ -292,8 +291,7 @@ it('does not let a member or another team change maintenance mode', function (st session(['currentTeam' => $team]); Livewire::test(Maintenance::class, ['resource' => $service]) - ->set('isMaintenanceEnabled', true) - ->call('instantSaveMaintenance') + ->call('toggleMaintenance') ->assertDispatched('error') ->set('maintenancePage', '

Hacked

') ->call('submit')