From dec776025e4af36c543bfe2e410869629ea67073 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 3 Nov 2026 22:19:56 +0100 Subject: [PATCH] fix(upgrade): finish the upgrade modal when the new version answers Read the running version from the container that answers the status request, not from the old page state, so the modal shows success as soon as the new version is up. Hide all Livewire error pages while an upgrade runs. Closes #8024 Co-Authored-By: Claude Opus 5.5 --- app/Livewire/Upgrade.php | 3 +- resources/js/livewire-request-failure.js | 7 +++++ resources/js/livewire-request-failure.test.js | 11 +++++++ resources/views/livewire/upgrade.blade.php | 3 ++ tests/Feature/UpgradeComponentTest.php | 29 +++++++++++++++++++ 5 files changed, 52 insertions(+), 1 deletion(-) diff --git a/app/Livewire/Upgrade.php b/app/Livewire/Upgrade.php index 45a4a28cca..cfcab71f56 100644 --- a/app/Livewire/Upgrade.php +++ b/app/Livewire/Upgrade.php @@ -125,7 +125,8 @@ class Upgrade extends Component return CoolifyUpgradeStatus::fromFile( content: $content, - runningVersion: $this->currentVersion !== '' ? $this->currentVersion : (string) config('constants.coolify.version'), + // The version of the container that answers, not $currentVersion: after the restart, that is still the old version of the page. + runningVersion: (string) config('constants.coolify.version'), targetVersion: $this->latestVersion !== '' ? $this->latestVersion : get_latest_version_of_coolify(), ); } diff --git a/resources/js/livewire-request-failure.js b/resources/js/livewire-request-failure.js index e1bd82583c..8a54e17849 100644 --- a/resources/js/livewire-request-failure.js +++ b/resources/js/livewire-request-failure.js @@ -15,6 +15,13 @@ export function createLivewireRequestFailureHandler({ now = Date.now } = {}) { let lastToastGestureAt = Number.NEGATIVE_INFINITY; return ({ status, content, preventDefault, gestureAt = Number.NEGATIVE_INFINITY }) => { + // While Coolify restarts for an upgrade, any request can fail (proxy errors, then the new + // version answering the old page). The upgrade modal shows the progress, so show no error page. + if (window.coolifyUpgradeInProgress) { + preventDefault(); + return; + } + if (!INFRASTRUCTURE_FAILURE_STATUSES.has(status)) { return; } diff --git a/resources/js/livewire-request-failure.test.js b/resources/js/livewire-request-failure.test.js index 64a5cebbce..4e10cc1894 100644 --- a/resources/js/livewire-request-failure.test.js +++ b/resources/js/livewire-request-failure.test.js @@ -144,3 +144,14 @@ test('other failures keep Livewire default handling', () => { } assert.equal(harness.toasts(), 0); }); + +test('during a Coolify upgrade every failure is suppressed without a toast', () => { + const harness = createHarness(); + global.window.coolifyUpgradeInProgress = true; + + harness.gesture(); + for (const status of [404, 419, 500, 502]) { + assert.equal(harness.fail(status), true, `expected ${status} to be suppressed`); + } + assert.equal(harness.toasts(), 0); +}); diff --git a/resources/views/livewire/upgrade.blade.php b/resources/views/livewire/upgrade.blade.php index 868f5d60b7..f8fd7f513c 100644 --- a/resources/views/livewire/upgrade.blade.php +++ b/resources/views/livewire/upgrade.blade.php @@ -239,6 +239,8 @@ this.currentStep = 1; this.currentStatus = 'Starting upgrade...'; this.startTimer(); + // Coolify restarts during the upgrade, so do not show failed Livewire requests as error pages. + window.coolifyUpgradeInProgress = true; // Trigger server-side upgrade script via Livewire this.$wire.$call('upgrade'); // Start client-side status polling @@ -454,6 +456,7 @@ this.beforeUnloadHandler = null; } + window.coolifyUpgradeInProgress = false; this.upgradeError = true; this.currentStatus = `Error: ${message}`; }, diff --git a/tests/Feature/UpgradeComponentTest.php b/tests/Feature/UpgradeComponentTest.php index 3c04306c1d..5c7f595feb 100644 --- a/tests/Feature/UpgradeComponentTest.php +++ b/tests/Feature/UpgradeComponentTest.php @@ -2,8 +2,13 @@ use App\Livewire\Upgrade; use App\Models\InstanceSettings; +use App\Models\PrivateKey; +use App\Models\Server; +use App\Models\Team; +use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Process; use Livewire\Livewire; uses(RefreshDatabase::class); @@ -92,3 +97,27 @@ it('clears stale upgrade availability when current version is newer than cached expect((bool) InstanceSettings::findOrFail(0)->new_version_available)->toBeFalse(); }); + +it('reports the upgrade as complete when the new version answers the status request of the old page', function () { + $rootTeam = Team::find(0) ?? Team::factory()->create(['id' => 0]); + $privateKey = PrivateKey::factory()->create(['team_id' => $rootTeam->id]); + Server::factory()->create(['id' => 0, 'team_id' => $rootTeam->id, 'private_key_id' => $privateKey->id]); + InstanceSettings::forceCreate(['id' => 0, 'new_version_available' => true]); + $user = User::factory()->create(); + $rootTeam->members()->attach($user->id, ['role' => 'admin']); + $this->actingAs($user); + session(['currentTeam' => ['id' => $rootTeam->id]]); + Cache::put('coolify:versions', ['coolify' => ['version' => '4.6.0']], 3600); + + // The page was rendered by the old version. + config(['constants.coolify.version' => '4.5.0']); + $component = Livewire::test(Upgrade::class)->assertSet('currentVersion', '4.5.0'); + + // The restarted container runs the new version and reads the finished status file. + config(['constants.coolify.version' => '4.6.0']); + $statusLine = '6|Upgrade complete|'.now()->toIso8601String(); + Process::fake(['*' => Process::result(output: $statusLine)]); + + expect($component->instance()->getUpgradeStatus()) + ->toMatchArray(['status' => 'complete', 'running_version' => '4.6.0']); +});