Improve upgrade process UX with better progress visibility
- Add step-by-step progress indicator (Preparing → Helper → Image → Restart) - Display elapsed time during upgrade (MM:SS format) - Show version transition in header (v4.0.0-beta.454 → v4.0.0-beta.456) - Add expandable changelog preview before upgrading - Reduce reload delay from 5s to 3s with countdown timer - Add "Reload Now" button to skip countdown - Improve status messages with step-specific descriptions - Add success state with clear indication when upgrade completes - Create new upgrade-progress component for visual step tracking 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
dafd10d8fa
commit
92326c09ea
3 changed files with 398 additions and 48 deletions
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
use App\Actions\Server\UpdateCoolify;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Services\ChangelogService;
|
||||
use Livewire\Component;
|
||||
|
||||
class Upgrade extends Component
|
||||
|
|
@ -14,21 +15,57 @@ class Upgrade extends Component
|
|||
|
||||
public string $latestVersion = '';
|
||||
|
||||
public string $currentVersion = '';
|
||||
|
||||
public array $changelogEntries = [];
|
||||
|
||||
protected $listeners = ['updateAvailable' => 'checkUpdate'];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->currentVersion = config('constants.coolify.version');
|
||||
}
|
||||
|
||||
public function checkUpdate()
|
||||
{
|
||||
try {
|
||||
$this->latestVersion = get_latest_version_of_coolify();
|
||||
$this->currentVersion = config('constants.coolify.version');
|
||||
$this->isUpgradeAvailable = data_get(InstanceSettings::get(), 'new_version_available', false);
|
||||
if (isDev()) {
|
||||
$this->isUpgradeAvailable = true;
|
||||
}
|
||||
$this->loadChangelog();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function loadChangelog()
|
||||
{
|
||||
try {
|
||||
$service = app(ChangelogService::class);
|
||||
$currentVersion = str_replace('v', '', $this->currentVersion);
|
||||
|
||||
$this->changelogEntries = $service->getEntries(1)
|
||||
->filter(function ($entry) use ($currentVersion) {
|
||||
$entryVersion = str_replace('v', '', $entry->tag_name);
|
||||
|
||||
return version_compare($entryVersion, $currentVersion, '>');
|
||||
})
|
||||
->take(3)
|
||||
->map(fn ($entry) => [
|
||||
'tag_name' => $entry->tag_name,
|
||||
'title' => $entry->title,
|
||||
'content_html' => $entry->content_html,
|
||||
])
|
||||
->values()
|
||||
->toArray();
|
||||
} catch (\Throwable $e) {
|
||||
$this->changelogEntries = [];
|
||||
}
|
||||
}
|
||||
|
||||
public function upgrade()
|
||||
{
|
||||
try {
|
||||
|
|
|
|||
143
resources/views/components/upgrade-progress.blade.php
Normal file
143
resources/views/components/upgrade-progress.blade.php
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
@props(['step' => 0])
|
||||
|
||||
<div class="w-full max-w-md mx-auto" x-data="{ activeStep: {{ $step }} }" x-effect="activeStep = $el.closest('[x-data]')?.__x?.$data?.currentStep ?? {{ $step }}">
|
||||
<div class="flex items-center justify-between">
|
||||
{{-- Step 1: Preparing --}}
|
||||
<div class="flex items-center flex-1">
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="flex items-center justify-center size-8 rounded-full border-2 transition-all duration-300"
|
||||
:class="{
|
||||
'bg-success border-success': currentStep > 1,
|
||||
'bg-warning/20 border-warning': currentStep === 1,
|
||||
'border-neutral-400 dark:border-coolgray-300': currentStep < 1
|
||||
}">
|
||||
<template x-if="currentStep > 1">
|
||||
<svg class="size-4 text-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</template>
|
||||
<template x-if="currentStep === 1">
|
||||
<svg class="size-4 text-warning animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</template>
|
||||
<template x-if="currentStep < 1">
|
||||
<span class="text-xs font-medium text-neutral-500 dark:text-neutral-400">1</span>
|
||||
</template>
|
||||
</div>
|
||||
<span class="mt-1.5 text-xs font-medium transition-colors duration-300"
|
||||
:class="{
|
||||
'text-success': currentStep > 1,
|
||||
'text-warning': currentStep === 1,
|
||||
'text-neutral-500 dark:text-neutral-400': currentStep < 1
|
||||
}">Preparing</span>
|
||||
</div>
|
||||
<div class="flex-1 h-0.5 mx-2 transition-all duration-300"
|
||||
:class="currentStep > 1 ? 'bg-success' : 'bg-neutral-300 dark:bg-coolgray-300'"></div>
|
||||
</div>
|
||||
|
||||
{{-- Step 2: Helper --}}
|
||||
<div class="flex items-center flex-1">
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="flex items-center justify-center size-8 rounded-full border-2 transition-all duration-300"
|
||||
:class="{
|
||||
'bg-success border-success': currentStep > 2,
|
||||
'bg-warning/20 border-warning': currentStep === 2,
|
||||
'border-neutral-400 dark:border-coolgray-300': currentStep < 2
|
||||
}">
|
||||
<template x-if="currentStep > 2">
|
||||
<svg class="size-4 text-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</template>
|
||||
<template x-if="currentStep === 2">
|
||||
<svg class="size-4 text-warning animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</template>
|
||||
<template x-if="currentStep < 2">
|
||||
<span class="text-xs font-medium text-neutral-500 dark:text-neutral-400">2</span>
|
||||
</template>
|
||||
</div>
|
||||
<span class="mt-1.5 text-xs font-medium transition-colors duration-300"
|
||||
:class="{
|
||||
'text-success': currentStep > 2,
|
||||
'text-warning': currentStep === 2,
|
||||
'text-neutral-500 dark:text-neutral-400': currentStep < 2
|
||||
}">Helper</span>
|
||||
</div>
|
||||
<div class="flex-1 h-0.5 mx-2 transition-all duration-300"
|
||||
:class="currentStep > 2 ? 'bg-success' : 'bg-neutral-300 dark:bg-coolgray-300'"></div>
|
||||
</div>
|
||||
|
||||
{{-- Step 3: Image --}}
|
||||
<div class="flex items-center flex-1">
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="flex items-center justify-center size-8 rounded-full border-2 transition-all duration-300"
|
||||
:class="{
|
||||
'bg-success border-success': currentStep > 3,
|
||||
'bg-warning/20 border-warning': currentStep === 3,
|
||||
'border-neutral-400 dark:border-coolgray-300': currentStep < 3
|
||||
}">
|
||||
<template x-if="currentStep > 3">
|
||||
<svg class="size-4 text-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</template>
|
||||
<template x-if="currentStep === 3">
|
||||
<svg class="size-4 text-warning animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</template>
|
||||
<template x-if="currentStep < 3">
|
||||
<span class="text-xs font-medium text-neutral-500 dark:text-neutral-400">3</span>
|
||||
</template>
|
||||
</div>
|
||||
<span class="mt-1.5 text-xs font-medium transition-colors duration-300"
|
||||
:class="{
|
||||
'text-success': currentStep > 3,
|
||||
'text-warning': currentStep === 3,
|
||||
'text-neutral-500 dark:text-neutral-400': currentStep < 3
|
||||
}">Image</span>
|
||||
</div>
|
||||
<div class="flex-1 h-0.5 mx-2 transition-all duration-300"
|
||||
:class="currentStep > 3 ? 'bg-success' : 'bg-neutral-300 dark:bg-coolgray-300'"></div>
|
||||
</div>
|
||||
|
||||
{{-- Step 4: Restart --}}
|
||||
<div class="flex items-center">
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="flex items-center justify-center size-8 rounded-full border-2 transition-all duration-300"
|
||||
:class="{
|
||||
'bg-success border-success': currentStep > 4,
|
||||
'bg-warning/20 border-warning': currentStep === 4,
|
||||
'border-neutral-400 dark:border-coolgray-300': currentStep < 4
|
||||
}">
|
||||
<template x-if="currentStep > 4">
|
||||
<svg class="size-4 text-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</template>
|
||||
<template x-if="currentStep === 4">
|
||||
<svg class="size-4 text-warning animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</template>
|
||||
<template x-if="currentStep < 4">
|
||||
<span class="text-xs font-medium text-neutral-500 dark:text-neutral-400">4</span>
|
||||
</template>
|
||||
</div>
|
||||
<span class="mt-1.5 text-xs font-medium transition-colors duration-300"
|
||||
:class="{
|
||||
'text-success': currentStep > 4,
|
||||
'text-warning': currentStep === 4,
|
||||
'text-neutral-500 dark:text-neutral-400': currentStep < 4
|
||||
}">Restart</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
<div @if ($isUpgradeAvailable) title="New version available" @else title="No upgrade available" @endif
|
||||
x-init="$wire.checkUpdate" x-data="upgradeModal">
|
||||
x-init="$wire.checkUpdate" x-data="upgradeModal({
|
||||
currentVersion: @js($currentVersion),
|
||||
latestVersion: @js($latestVersion)
|
||||
})">
|
||||
@if ($isUpgradeAvailable)
|
||||
<div :class="{ 'z-40': modalOpen }" class="relative w-auto h-auto">
|
||||
<button class="menu-item" @click="modalOpen=true" x-show="showProgress">
|
||||
|
|
@ -40,44 +43,137 @@ class="absolute inset-0 w-full h-full bg-black/20 backdrop-blur-xs"></div>
|
|||
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95"
|
||||
class="relative w-full py-6 border rounded-sm min-w-full lg:min-w-[36rem] max-w-fit bg-neutral-100 border-neutral-400 dark:bg-base px-7 dark:border-coolgray-300">
|
||||
|
||||
{{-- Header --}}
|
||||
<div class="flex items-center justify-between pb-3">
|
||||
<h3 class="text-lg font-semibold">Upgrade confirmation</h3>
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold" x-text="upgradeComplete ? 'Upgrade Complete!' : (showProgress ? 'Upgrading...' : 'Upgrade Available')"></h3>
|
||||
<div class="text-sm text-neutral-500 dark:text-neutral-400">
|
||||
{{ $currentVersion }} <span class="mx-1">→</span> {{ $latestVersion }}
|
||||
</div>
|
||||
</div>
|
||||
<button x-show="!showProgress" @click="modalOpen=false"
|
||||
class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 text-gray-600 rounded-full hover:text-gray-800 hover:bg-gray-50">
|
||||
class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 text-gray-600 rounded-full hover:text-gray-800 hover:bg-gray-50 dark:text-neutral-400 dark:hover:text-white dark:hover:bg-coolgray-300">
|
||||
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="relative w-auto pb-8">
|
||||
<p>Are you sure you would like to upgrade your instance to {{ $latestVersion }}?</p>
|
||||
<br />
|
||||
|
||||
<x-callout type="warning" title="Caution">
|
||||
<p>Any deployments running during the update process will
|
||||
fail. Please ensure no deployments are in progress on any server before continuing.
|
||||
</p>
|
||||
</x-callout>
|
||||
<br />
|
||||
<p>You can review the changelogs <a class="font-bold underline dark:text-white"
|
||||
href="https://github.com/coollabsio/coolify/releases" target="_blank">here</a>.</p>
|
||||
<br />
|
||||
<p>If something goes wrong and you cannot upgrade your instance, You can check the following
|
||||
<a class="font-bold underline dark:text-white" href="https://coolify.io/docs/upgrade"
|
||||
target="_blank">guide</a> on what to do.
|
||||
</p>
|
||||
<div class="flex flex-col pt-4" x-show="showProgress">
|
||||
<h2>Progress <x-loading /></h2>
|
||||
<div x-html="currentStatus"></div>
|
||||
</div>
|
||||
{{-- Content --}}
|
||||
<div class="relative w-auto pb-6">
|
||||
{{-- Progress View --}}
|
||||
<template x-if="showProgress">
|
||||
<div class="space-y-6">
|
||||
{{-- Step Progress Indicator --}}
|
||||
<div class="pt-2">
|
||||
<x-upgrade-progress />
|
||||
</div>
|
||||
|
||||
{{-- Elapsed Time --}}
|
||||
<div class="text-center">
|
||||
<span class="text-sm text-neutral-500 dark:text-neutral-400">Elapsed time:</span>
|
||||
<span class="ml-2 font-mono text-sm" x-text="formatElapsedTime()"></span>
|
||||
</div>
|
||||
|
||||
{{-- Current Status Message --}}
|
||||
<div class="p-4 rounded-lg bg-neutral-200 dark:bg-coolgray-200">
|
||||
<div class="flex items-center gap-3">
|
||||
<template x-if="!upgradeComplete">
|
||||
<svg class="w-5 h-5 text-warning animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</template>
|
||||
<template x-if="upgradeComplete">
|
||||
<svg class="w-5 h-5 text-success" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</template>
|
||||
<span x-text="currentStatus" class="text-sm"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Success State with Countdown --}}
|
||||
<template x-if="upgradeComplete">
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<p class="text-sm text-neutral-500 dark:text-neutral-400">
|
||||
Reloading in <span x-text="successCountdown" class="font-bold text-warning"></span> seconds...
|
||||
</p>
|
||||
<x-forms.button @click="reloadNow()" type="button">
|
||||
Reload Now
|
||||
</x-forms.button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
{{-- Confirmation View --}}
|
||||
<template x-if="!showProgress">
|
||||
<div class="space-y-4">
|
||||
{{-- Changelog Preview --}}
|
||||
@if (count($changelogEntries) > 0)
|
||||
<div x-data="{ showChangelog: false }">
|
||||
<button @click="showChangelog = !showChangelog"
|
||||
type="button"
|
||||
class="flex items-center gap-2 text-sm font-medium text-neutral-600 dark:text-neutral-300 hover:text-neutral-800 dark:hover:text-white">
|
||||
<svg class="w-4 h-4 transition-transform duration-200" :class="showChangelog ? 'rotate-90' : ''" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
What's new in {{ $latestVersion }}
|
||||
</button>
|
||||
|
||||
<div x-show="showChangelog" x-collapse class="mt-3">
|
||||
<div class="max-h-48 overflow-y-auto space-y-2 scrollbar">
|
||||
@foreach ($changelogEntries as $entry)
|
||||
<div class="p-3 text-sm rounded-lg bg-neutral-200 dark:bg-coolgray-200">
|
||||
<div class="font-semibold text-neutral-700 dark:text-neutral-200">{{ $entry['title'] }}</div>
|
||||
<div class="mt-1 text-xs text-neutral-500 dark:text-neutral-400 line-clamp-2">
|
||||
{!! Str::limit(strip_tags($entry['content_html']), 150) !!}
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
<a href="https://github.com/coollabsio/coolify/releases"
|
||||
target="_blank"
|
||||
class="inline-flex items-center gap-1 mt-2 text-xs text-blue-500 hover:text-blue-400 hover:underline">
|
||||
View full changelog
|
||||
<svg class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M4.25 5.5a.75.75 0 00-.75.75v8.5c0 .414.336.75.75.75h8.5a.75.75 0 00.75-.75v-4a.75.75 0 011.5 0v4A2.25 2.25 0 0112.75 17h-8.5A2.25 2.25 0 012 14.75v-8.5A2.25 2.25 0 014.25 4h5a.75.75 0 010 1.5h-5z" clip-rule="evenodd" />
|
||||
<path fill-rule="evenodd" d="M6.194 12.753a.75.75 0 001.06.053L16.5 4.44v2.81a.75.75 0 001.5 0v-4.5a.75.75 0 00-.75-.75h-4.5a.75.75 0 000 1.5h2.553l-9.056 8.194a.75.75 0 00-.053 1.06z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Warning --}}
|
||||
<x-callout type="warning" title="Caution">
|
||||
<p>Any deployments running during the update process will
|
||||
fail. Please ensure no deployments are in progress on any server before continuing.
|
||||
</p>
|
||||
</x-callout>
|
||||
|
||||
{{-- Help Links --}}
|
||||
<p class="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
If something goes wrong, check the
|
||||
<a class="font-medium underline dark:text-white hover:text-neutral-800 dark:hover:text-neutral-300"
|
||||
href="https://coolify.io/docs/upgrade"
|
||||
target="_blank">upgrade guide</a>.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
{{-- Footer Actions --}}
|
||||
<div class="flex gap-4" x-show="!showProgress">
|
||||
<x-forms.button @click="modalOpen=false"
|
||||
class="w-24 dark:bg-coolgray-200 dark:hover:bg-coolgray-300">Cancel
|
||||
</x-forms.button>
|
||||
<div class="flex-1"></div>
|
||||
<x-forms.button @click="confirmed" class="w-24" isHighlighted type="button">Continue
|
||||
<x-forms.button @click="confirmed" class="w-32" isHighlighted type="button">
|
||||
Upgrade Now
|
||||
</x-forms.button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -89,23 +185,57 @@ class="w-24 dark:bg-coolgray-200 dark:hover:bg-coolgray-300">Cancel
|
|||
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('upgradeModal', () => ({
|
||||
Alpine.data('upgradeModal', (config) => ({
|
||||
modalOpen: false,
|
||||
showProgress: false,
|
||||
currentStatus: '',
|
||||
checkHealthInterval: null,
|
||||
checkIfIamDeadInterval: null,
|
||||
elapsedInterval: null,
|
||||
healthCheckAttempts: 0,
|
||||
startTime: null,
|
||||
elapsedTime: 0,
|
||||
currentStep: 0,
|
||||
upgradeComplete: false,
|
||||
successCountdown: 3,
|
||||
currentVersion: config.currentVersion || '',
|
||||
latestVersion: config.latestVersion || '',
|
||||
|
||||
confirmed() {
|
||||
this.showProgress = true;
|
||||
this.$wire.$call('upgrade')
|
||||
this.currentStep = 1;
|
||||
this.startTimer();
|
||||
this.$wire.$call('upgrade');
|
||||
this.upgrade();
|
||||
window.addEventListener('beforeunload', (event) => {
|
||||
event.preventDefault();
|
||||
event.returnValue = '';
|
||||
});
|
||||
},
|
||||
|
||||
startTimer() {
|
||||
this.startTime = Date.now();
|
||||
this.elapsedInterval = setInterval(() => {
|
||||
this.elapsedTime = Math.floor((Date.now() - this.startTime) / 1000);
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
formatElapsedTime() {
|
||||
const minutes = Math.floor(this.elapsedTime / 60);
|
||||
const seconds = this.elapsedTime % 60;
|
||||
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
||||
},
|
||||
|
||||
getStepMessage(step) {
|
||||
const messages = {
|
||||
1: 'Preparing upgrade...',
|
||||
2: 'Pulling helper image...',
|
||||
3: 'Pulling Coolify image...',
|
||||
4: 'Restarting Coolify...'
|
||||
};
|
||||
return messages[step] || 'Processing...';
|
||||
},
|
||||
|
||||
getReviveStatusMessage(elapsedMinutes, attempts) {
|
||||
if (elapsedMinutes === 0) {
|
||||
return `Waiting for Coolify to come back online... (attempt ${attempts})`;
|
||||
|
|
@ -119,49 +249,88 @@ class="w-24 dark:bg-coolgray-200 dark:hover:bg-coolgray-300">Cancel
|
|||
return `Still updating. If this takes longer than 15 minutes, please check server logs... (${elapsedMinutes} minutes elapsed)`;
|
||||
}
|
||||
},
|
||||
|
||||
revive() {
|
||||
if (this.checkHealthInterval) return true;
|
||||
this.healthCheckAttempts = 0;
|
||||
this.startTime = Date.now();
|
||||
console.log('Checking server\'s health...')
|
||||
this.currentStep = 4;
|
||||
console.log('Checking server\'s health...');
|
||||
this.checkHealthInterval = setInterval(() => {
|
||||
this.healthCheckAttempts++;
|
||||
const elapsedMinutes = Math.floor((Date.now() - this.startTime) / 60000);
|
||||
fetch('/api/health')
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
this.currentStatus =
|
||||
'Coolify is back online. Reloading this page in 5 seconds...';
|
||||
if (this.checkHealthInterval) {
|
||||
clearInterval(this.checkHealthInterval);
|
||||
this.checkHealthInterval = null;
|
||||
}
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 5000)
|
||||
this.showSuccess();
|
||||
} else {
|
||||
this.currentStatus = this.getReviveStatusMessage(elapsedMinutes, this
|
||||
.healthCheckAttempts);
|
||||
this.currentStatus = this.getReviveStatusMessage(elapsedMinutes, this.healthCheckAttempts);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Health check failed:', error);
|
||||
this.currentStatus = this.getReviveStatusMessage(elapsedMinutes, this
|
||||
.healthCheckAttempts);
|
||||
this.currentStatus = this.getReviveStatusMessage(elapsedMinutes, this.healthCheckAttempts);
|
||||
});
|
||||
}, 2000);
|
||||
},
|
||||
|
||||
showSuccess() {
|
||||
if (this.checkHealthInterval) {
|
||||
clearInterval(this.checkHealthInterval);
|
||||
this.checkHealthInterval = null;
|
||||
}
|
||||
if (this.elapsedInterval) {
|
||||
clearInterval(this.elapsedInterval);
|
||||
this.elapsedInterval = null;
|
||||
}
|
||||
|
||||
this.upgradeComplete = true;
|
||||
this.currentStep = 5;
|
||||
this.currentStatus = `Successfully upgraded to ${this.latestVersion}`;
|
||||
this.successCountdown = 3;
|
||||
|
||||
const countdownInterval = setInterval(() => {
|
||||
this.successCountdown--;
|
||||
if (this.successCountdown <= 0) {
|
||||
clearInterval(countdownInterval);
|
||||
window.location.reload();
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
reloadNow() {
|
||||
window.location.reload();
|
||||
},
|
||||
|
||||
upgrade() {
|
||||
if (this.checkIfIamDeadInterval || this.showProgress) return true;
|
||||
this.currentStatus = 'Update in progress. Pulling new images and preparing to restart Coolify...';
|
||||
if (this.checkIfIamDeadInterval) return true;
|
||||
this.currentStep = 1;
|
||||
this.currentStatus = this.getStepMessage(1);
|
||||
|
||||
// Simulate step progression (since we can't get real-time feedback from Docker pulls)
|
||||
let stepTime = 0;
|
||||
const stepInterval = setInterval(() => {
|
||||
stepTime++;
|
||||
// Progress through steps based on elapsed time
|
||||
if (stepTime >= 3 && this.currentStep === 1) {
|
||||
this.currentStep = 2;
|
||||
this.currentStatus = this.getStepMessage(2);
|
||||
} else if (stepTime >= 8 && this.currentStep === 2) {
|
||||
this.currentStep = 3;
|
||||
this.currentStatus = this.getStepMessage(3);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
this.checkIfIamDeadInterval = setInterval(() => {
|
||||
fetch('/api/health')
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
this.currentStatus =
|
||||
"Update in progress. Pulling new images and preparing to restart Coolify..."
|
||||
// Still running, update status based on current step
|
||||
this.currentStatus = this.getStepMessage(this.currentStep);
|
||||
} else {
|
||||
this.currentStatus = "Coolify is restarting with the new version..."
|
||||
// Service is down, now waiting to revive
|
||||
clearInterval(stepInterval);
|
||||
this.currentStep = 4;
|
||||
this.currentStatus = 'Coolify is restarting with the new version...';
|
||||
if (this.checkIfIamDeadInterval) {
|
||||
clearInterval(this.checkIfIamDeadInterval);
|
||||
this.checkIfIamDeadInterval = null;
|
||||
|
|
@ -171,7 +340,9 @@ class="w-24 dark:bg-coolgray-200 dark:hover:bg-coolgray-300">Cancel
|
|||
})
|
||||
.catch(error => {
|
||||
console.error('Health check failed:', error);
|
||||
this.currentStatus = "Coolify is restarting with the new version..."
|
||||
clearInterval(stepInterval);
|
||||
this.currentStep = 4;
|
||||
this.currentStatus = 'Coolify is restarting with the new version...';
|
||||
if (this.checkIfIamDeadInterval) {
|
||||
clearInterval(this.checkIfIamDeadInterval);
|
||||
this.checkIfIamDeadInterval = null;
|
||||
|
|
@ -180,7 +351,6 @@ class="w-24 dark:bg-coolgray-200 dark:hover:bg-coolgray-300">Cancel
|
|||
});
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
}))
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in a new issue