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 <noreply@anthropic.com>
This commit is contained in:
Andras BacsaiandClaude Opus 5.5 committed 2026-11-03 22:19:56 +01:00
1 parent 31c3fcf50d
commit dec776025e
5 files changed
+52 -1

No files matched your search

+2 -1
View File
@@ -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(),
);
}
+7
View File
@@ -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;
}
@@ -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);
});
@@ -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}`;
},
+29
View File
@@ -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']);
});