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']); +});