coolify/resources/views/livewire/upgrade.blade.php

420 lines
22 KiB
PHP
Raw Normal View History

2024-03-19 14:37:16 +00:00
<div @if ($isUpgradeAvailable) title="New version available" @else title="No upgrade available" @endif
x-init="$wire.checkUpdate" x-data="upgradeModal({
currentVersion: @js($currentVersion),
latestVersion: @js($latestVersion),
devMode: @js($devMode)
})">
2023-06-15 09:39:15 +00:00
@if ($isUpgradeAvailable)
<div :class="{ 'z-40': modalOpen }" class="relative w-auto h-auto">
<button class="menu-item" @click="modalOpen=true" x-show="showProgress">
<svg xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 text-pink-500 transition-colors hover:text-pink-300 lds-heart" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M19.5 13.572l-7.5 7.428l-7.5 -7.428m0 0a5 5 0 1 1 7.5 -6.566a5 5 0 1 1 7.5 6.572" />
</svg>
In progress
</button>
<button class="menu-item cursor-pointer" @click="modalOpen=true" x-show="!showProgress">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 text-pink-500 transition-colors hover:text-pink-300"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M9 12h-3.586a1 1 0 0 1 -.707 -1.707l6.586 -6.586a1 1 0 0 1 1.414 0l6.586 6.586a1 1 0 0 1 -.707 1.707h-3.586v3h-6v-3z" />
<path d="M9 21h6" />
<path d="M9 18h6" />
</svg>
Upgrade
</button>
<template x-teleport="body">
<div x-show="modalOpen"
v4.0.0-beta.420 (#6008) * chore(version): update coolify-realtime to version 1.0.9 in docker-compose and versions files * feat(migration): add is_sentinel_enabled column to server_settings with default true * fix(migration): update default value handling for is_sentinel_enabled column in server_settings * feat(seeder): dispatch StartProxy action for each server in ProductionSeeder * feat(seeder): add CheckAndStartSentinelJob dispatch for each server in ProductionSeeder * fix(seeder): conditionally dispatch CheckAndStartSentinelJob based on server's sentinel status * feat(seeder): conditionally dispatch StartProxy action based on proxy check result * refactor(ui): terminal * refactor(ui): remove terminal header from execute-container-command view * refactor(ui): remove unnecessary padding from deployment, backup, and logs sections * fix(service): disable healthcheck logging for Gotenberg (#6005) * fix(service): Joplin volume name (#5930) * chore(version): update coolify version to 4.0.0-beta.420 and nightly version to 4.0.0-beta.421 * fix(server): update sentinelUpdatedAt assignment to use server's sentinel_updated_at property * feat(service): update Changedetection template (#5937) * chore(service): changedetection remove unused code * fix(service): audiobookshelf healthcheck command (#5993) * refactor(service): update Hoarder to their new name karakeep (#5964) * fix(service): downgrade Evolution API phone version (#5977) * feat(service): add Miniflux service (#5843) * refactor(service): karakeep naming and formatting * refactor(service): improve miniflux - improve DB url - add depends_on - formatting, naming & order * feat(service): add Pingvin Share service (#5969) * fix(service): pingvinshare-with-clamav - add platform to make clamav work - formatting * feat(auth): Add Discord OAuth Provider (#5552) * feat(auth): Add Clerk OAuth Provider (#5553) * feat(auth): add Zitadel OAuth Provider (#5490) * Update composer.lock * fix(ssh): scp requires square brackets for ipv6 (#6001) * refactor(core): rename API rate limit ENV * refactor(ui): simplify container selection form in execute-container-command view * chore(service): Update Evolution API image to the official one (#6031) * chore(versions): bump coolify versions to v4.0.0-beta.420 and v4.0.0-beta.421 * fix(github): changing github app breaks the webhook. it does not anymore * feat(service): enhance service status handling and UI updates * fix(parser): improve FQDN generation and update environment variable handling * fix(ui): enhance status refresh buttons with loading indicators * fix(ui): update confirmation button text for stopping database and service * fix(routes): update middleware for deploy route to use 'api.ability:deploy' * fix(ui): refine API token creation form and update helper text for clarity * fix(ui): adjust layout of deployments section for improved alignment * chore(dependencies): update composer dependencies to latest versions including resend-laravel to ^0.19.0 and aws-sdk-php to 3.347.0 * refactor(email): streamline SMTP and resend settings logic for improved clarity * fix(ui): adjust project grid layout and refine server border styling for better visibility * fix(ui): update border styling for consistency across components and enhance loading indicators * feat(cleanup): add functionality to delete teams with no members or servers in CleanupStuckedResources command * refactor(invitation): rename methods for consistency and enhance invitation deletion logic * refactor(user): streamline user deletion process and enhance team management logic * fix(ui): add padding to section headers in settings views for improved spacing * fix(ui): reduce gap between input fields in email settings for better alignment * fix(docker): conditionally enable gzip compression in Traefik labels based on configuration * fix(parser): enable gzip compression conditionally for Pocketbase images and streamline service creation logic * fix(ui): update padding for trademarks policy and enhance spacing in advanced settings section * feat(ui): add heart icon and enhance popup messaging for sponsorship support * feat(settings): add sponsorship popup toggle and corresponding database migration * fix(ui): correct closing tag for sponsorship link in layout popups * fix(ui): refine wording in sponsorship donation prompt in layout popups * fix(ui): update navbar icon color and enhance popup layout for sponsorship support * Update resources/views/livewire/project/shared/health-checks.blade.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update app/Livewire/Subscription/Index.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix(ui): add target="_blank" to sponsorship links in layout popups for improved user experience * fix(models): refine comment wording in User model for clarity on user deletion criteria * Update app/Providers/RouteServiceProvider.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix(models): improve user deletion logic in User model to handle team member roles and prevent deletion if user is alone in root team * fix(ui): update wording in sponsorship prompt for clarity and engagement --------- Co-authored-by: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Co-authored-by: Khiet Tam Nguyen <86177399+nktnet1@users.noreply.github.com> Co-authored-by: Carsten <BanditsBacon@users.noreply.github.com> Co-authored-by: Alberto Rizzi <48057685+albertorizzi@users.noreply.github.com> Co-authored-by: Jonas Klesen <deklesen@gmail.com> Co-authored-by: Stew Night. <22344601+stewnight@users.noreply.github.com> Co-authored-by: Jeffer Marcelino <jeffersunde72@gmail.com> Co-authored-by: Lucas Eduardo <lucas59356@gmail.com> Co-authored-by: CrazyTim71 <118295691+CrazyTim71@users.noreply.github.com> Co-authored-by: Yassir Elmarissi <yassir.elmarissi@hm.edu> Co-authored-by: Hauke Schnau <hauke@schnau-lilienthal.de> Co-authored-by: Darren Sisson <74752850+djsisson@users.noreply.github.com> Co-authored-by: Alkesh Das <67038642+smad-bro@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-06-26 10:23:08 +00:00
class="fixed top-0 lg:pt-10 left-0 z-99 flex items-start justify-center w-screen h-screen" x-cloak>
<div x-show="modalOpen" x-transition:enter="ease-out duration-100" x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-100"
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
class="absolute inset-0 w-full h-full bg-black/20 backdrop-blur-xs"></div>
<div x-show="modalOpen" x-trap.inert.noscroll="modalOpen" x-transition:enter="ease-out duration-100"
x-transition:enter-start="opacity-0 -translate-y-2 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave="ease-in duration-100"
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-[48rem] max-w-[calc(100vw-2rem)] py-6 border rounded-sm 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">
<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">&rarr;</span> {{ $latestVersion }}
</div>
</div>
<button x-show="!showProgress || upgradeError" @click="upgradeError ? closeErrorModal() : 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 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>
{{-- 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 && !upgradeError">
<svg class="w-5 h-5 text-black dark: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>
<template x-if="upgradeError">
<svg class="w-5 h-5 text-error" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
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>
{{-- Error State with Close Button --}}
<template x-if="upgradeError">
<div class="flex flex-col items-center gap-4">
<p class="text-sm text-neutral-600 dark:text-neutral-400">
Check the logs on the server at /data/coolify/source/upgrade*.
</p>
<x-forms.button @click="closeErrorModal()" type="button">
Close
</x-forms.button>
</div>
</template>
</div>
</template>
{{-- Confirmation View --}}
<template x-if="!showProgress">
<div class="space-y-4">
{{-- Warning --}}
<x-callout type="warning" title="Caution">
<p>Any deployments running during the update process will
fail.
</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> or the
logs on the server at /data/coolify/source/upgrade*.
</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>
<template x-if="devMode">
<x-forms.button @click="simulateUpgrade" type="button"
class="dark:bg-coolgray-200 dark:hover:bg-coolgray-300">
Simulate
</x-forms.button>
</template>
<x-forms.button @click="confirmed" class="w-32" isHighlighted type="button">
Upgrade Now
</x-forms.button>
</div>
</div>
</div>
</template>
</div>
2023-06-15 09:39:15 +00:00
@endif
2024-03-19 14:37:16 +00:00
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('upgradeModal', (config) => ({
modalOpen: false,
showProgress: false,
currentStatus: '',
checkHealthInterval: null,
checkUpgradeStatusInterval: null,
elapsedInterval: null,
healthCheckAttempts: 0,
startTime: null,
elapsedTime: 0,
currentStep: 0,
upgradeComplete: false,
upgradeError: false,
successCountdown: 3,
currentVersion: config.currentVersion || '',
latestVersion: config.latestVersion || '',
serviceDown: false,
devMode: config.devMode || false,
simulationInterval: null,
simulateUpgrade() {
if (!this.devMode) return;
this.showProgress = true;
this.currentStep = 1;
this.currentStatus = '[DEV] Starting simulated upgrade...';
this.startTimer();
this.upgradeComplete = false;
this.upgradeError = false;
const steps = [
{ step: 1, status: '[DEV] Preparing upgrade environment...' },
{ step: 2, status: '[DEV] Pulling helper image...' },
{ step: 3, status: '[DEV] Pulling Coolify image...' },
{ step: 4, status: '[DEV] Restarting services...' },
];
let stepIndex = 0;
this.simulationInterval = setInterval(() => {
if (stepIndex < steps.length) {
this.currentStep = steps[stepIndex].step;
this.currentStatus = steps[stepIndex].status;
stepIndex++;
} else {
clearInterval(this.simulationInterval);
this.simulationInterval = null;
this.showSuccess();
}
}, 2000);
},
confirmed() {
this.showProgress = true;
this.currentStep = 1;
this.currentStatus = 'Starting upgrade...';
this.startTimer();
// Trigger server-side upgrade script via Livewire
this.$wire.$call('upgrade');
// Start client-side status polling
this.upgrade();
// Prevent accidental navigation during upgrade
this.beforeUnloadHandler = (event) => {
2024-06-25 10:48:56 +00:00
event.preventDefault();
event.returnValue = '';
};
window.addEventListener('beforeunload', this.beforeUnloadHandler);
},
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')}`;
},
mapStepToUI(apiStep) {
// Map backend steps (1-6) to UI steps (1-4)
// Backend: 1=config, 2=env, 3=pull, 4=stop, 5=start, 6=complete
// UI: 1=prepare, 2=pull images, 3=pull coolify, 4=restart
if (apiStep <= 2) return 1;
if (apiStep === 3) return 2;
if (apiStep <= 5) return 3;
return 4;
},
getReviveStatusMessage(elapsedMinutes, attempts) {
if (elapsedMinutes === 0) {
return `Waiting for Coolify to come back online... (attempt ${attempts})`;
} else if (elapsedMinutes < 2) {
return `Waiting for Coolify to come back online... (${elapsedMinutes} minute${elapsedMinutes !== 1 ? 's' : ''} elapsed)`;
} else if (elapsedMinutes < 5) {
return `Update in progress, this may take several minutes... (${elapsedMinutes} minutes elapsed)`;
} else if (elapsedMinutes < 10) {
return `Large updates can take 10+ minutes. Please be patient... (${elapsedMinutes} minutes elapsed)`;
} else {
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.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.showSuccess();
} else {
this.currentStatus = this.getReviveStatusMessage(elapsedMinutes, this.healthCheckAttempts);
}
})
.catch(error => {
console.error('Health check failed:', error);
this.currentStatus = this.getReviveStatusMessage(elapsedMinutes, this.healthCheckAttempts);
});
}, 2000);
},
showSuccess() {
if (this.checkHealthInterval) {
clearInterval(this.checkHealthInterval);
this.checkHealthInterval = null;
}
if (this.checkUpgradeStatusInterval) {
clearInterval(this.checkUpgradeStatusInterval);
this.checkUpgradeStatusInterval = null;
}
if (this.elapsedInterval) {
clearInterval(this.elapsedInterval);
this.elapsedInterval = null;
}
// Remove beforeunload handler now that upgrade is complete
if (this.beforeUnloadHandler) {
window.removeEventListener('beforeunload', this.beforeUnloadHandler);
this.beforeUnloadHandler = 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();
},
showError(message) {
// Stop all intervals
if (this.checkHealthInterval) {
clearInterval(this.checkHealthInterval);
this.checkHealthInterval = null;
}
if (this.checkUpgradeStatusInterval) {
clearInterval(this.checkUpgradeStatusInterval);
this.checkUpgradeStatusInterval = null;
}
if (this.elapsedInterval) {
clearInterval(this.elapsedInterval);
this.elapsedInterval = null;
}
// Remove beforeunload handler so user can close modal
if (this.beforeUnloadHandler) {
window.removeEventListener('beforeunload', this.beforeUnloadHandler);
this.beforeUnloadHandler = null;
}
this.upgradeError = true;
this.currentStatus = `Error: ${message}`;
},
closeErrorModal() {
this.modalOpen = false;
this.showProgress = false;
this.upgradeError = false;
this.currentStatus = '';
this.currentStep = 0;
},
upgrade() {
if (this.checkUpgradeStatusInterval) return true;
this.currentStep = 1;
this.currentStatus = 'Starting upgrade...';
this.serviceDown = false;
// Poll upgrade status via Livewire
this.checkUpgradeStatusInterval = setInterval(async () => {
try {
const data = await this.$wire.getUpgradeStatus();
if (data.status === 'in_progress') {
this.currentStep = this.mapStepToUI(data.step);
this.currentStatus = data.message;
} else if (data.status === 'complete') {
this.showSuccess();
} else if (data.status === 'error') {
this.showError(data.message);
}
} catch (error) {
// Service is down - switch to health check mode
console.log('Livewire unavailable, switching to health check mode');
if (!this.serviceDown) {
this.serviceDown = true;
this.currentStep = 4;
this.currentStatus = 'Coolify is restarting with the new version...';
if (this.checkUpgradeStatusInterval) {
clearInterval(this.checkUpgradeStatusInterval);
this.checkUpgradeStatusInterval = null;
}
this.revive();
}
}
}, 2000);
}
}))
})
</script>