diff --git a/app/Http/Controllers/Api/OtherController.php b/app/Http/Controllers/Api/OtherController.php index 35695a003..8f2ba25c8 100644 --- a/app/Http/Controllers/Api/OtherController.php +++ b/app/Http/Controllers/Api/OtherController.php @@ -3,7 +3,6 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; -use App\Models\Server; use Illuminate\Http\Request; use Illuminate\Support\Facades\Http; use OpenApi\Attributes as OA; @@ -187,114 +186,4 @@ public function healthcheck(Request $request) { return 'OK'; } - - #[OA\Get( - summary: 'Upgrade Status', - description: 'Get the current upgrade status. Returns the step and message from the upgrade process. Only available to root team members.', - path: '/upgrade-status', - operationId: 'upgrade-status', - security: [ - ['bearerAuth' => []], - ], - responses: [ - new OA\Response( - response: 200, - description: 'Returns upgrade status.', - content: new OA\JsonContent( - type: 'object', - properties: [ - new OA\Property(property: 'status', type: 'string', example: 'in_progress'), - new OA\Property(property: 'step', type: 'integer', example: 3), - new OA\Property(property: 'message', type: 'string', example: 'Pulling Docker images'), - ] - )), - new OA\Response( - response: 401, - ref: '#/components/responses/401', - ), - new OA\Response( - response: 403, - description: 'You are not allowed to view upgrade status.', - content: new OA\JsonContent( - type: 'object', - properties: [ - new OA\Property(property: 'message', type: 'string', example: 'You are not allowed to view upgrade status.'), - ] - )), - new OA\Response( - response: 400, - ref: '#/components/responses/400', - ), - ] - )] - public function upgradeStatus(Request $request) - { - // Only root team members can view upgrade status - $user = auth()->user(); - if (! $user || $user->currentTeam()->id !== 0) { - return response()->json(['message' => 'You are not allowed to view upgrade status.'], 403); - } - - $server = Server::find(0); - if (! $server) { - return response()->json(['status' => 'none', 'debug' => 'no_server']); - } - - $statusFile = '/data/coolify/source/.upgrade-status'; - - // Read status file from localhost via SSH - try { - $content = instant_remote_process( - ["cat {$statusFile} 2>/dev/null || echo ''"], - $server, - false - ); - $content = trim($content ?? ''); - } catch (\Exception $e) { - return response()->json(['status' => 'none', 'debug' => 'ssh_exception', 'error' => $e->getMessage()]); - } - - if (empty($content)) { - return response()->json(['status' => 'none', 'debug' => 'empty_content']); - } - - $parts = explode('|', $content); - if (count($parts) < 3) { - return response()->json(['status' => 'none', 'debug' => 'invalid_parts', 'content' => $content]); - } - - [$step, $message, $timestamp] = $parts; - - // Check if status is stale (older than 10 minutes) - upgrades shouldn't take longer - try { - $statusTime = new \DateTime($timestamp); - $now = new \DateTime; - $diffMinutes = ($now->getTimestamp() - $statusTime->getTimestamp()) / 60; - - if ($diffMinutes > 10) { - return response()->json(['status' => 'none', 'debug' => 'stale', 'minutes' => $diffMinutes]); - } - } catch (\Exception $e) { - // If timestamp parsing fails, treat as stale for security - return response()->json(['status' => 'none', 'debug' => 'timestamp_error', 'timestamp' => $timestamp]); - } - - // Determine status based on step - if ($step === 'error') { - return response()->json([ - 'status' => 'error', - 'step' => 0, - 'message' => $message, - ]); - } - - $stepInt = (int) $step; - $status = $stepInt >= 6 ? 'complete' : 'in_progress'; - - return response()->json([ - 'status' => $status, - 'step' => $stepInt, - 'message' => $message, - ]); - } } diff --git a/app/Livewire/Upgrade.php b/app/Livewire/Upgrade.php index 1b145b244..fd3701cd3 100644 --- a/app/Livewire/Upgrade.php +++ b/app/Livewire/Upgrade.php @@ -4,6 +4,7 @@ use App\Actions\Server\UpdateCoolify; use App\Models\InstanceSettings; +use App\Models\Server; use App\Services\ChangelogService; use Livewire\Component; @@ -78,4 +79,71 @@ public function upgrade() return handleError($e, $this); } } + + public function getUpgradeStatus(): array + { + // Only root team members can view upgrade status + if (auth()->user()?->currentTeam()->id !== 0) { + return ['status' => 'none']; + } + + $server = Server::find(0); + if (! $server) { + return ['status' => 'none']; + } + + $statusFile = '/data/coolify/source/.upgrade-status'; + + try { + $content = instant_remote_process( + ["cat {$statusFile} 2>/dev/null || echo ''"], + $server, + false + ); + $content = trim($content ?? ''); + } catch (\Throwable $e) { + return ['status' => 'none']; + } + + if (empty($content)) { + return ['status' => 'none']; + } + + $parts = explode('|', $content); + if (count($parts) < 3) { + return ['status' => 'none']; + } + + [$step, $message, $timestamp] = $parts; + + // Check if status is stale (older than 10 minutes) + try { + $statusTime = new \DateTime($timestamp); + $now = new \DateTime; + $diffMinutes = ($now->getTimestamp() - $statusTime->getTimestamp()) / 60; + + if ($diffMinutes > 10) { + return ['status' => 'none']; + } + } catch (\Throwable $e) { + return ['status' => 'none']; + } + + if ($step === 'error') { + return [ + 'status' => 'error', + 'step' => 0, + 'message' => $message, + ]; + } + + $stepInt = (int) $step; + $status = $stepInt >= 6 ? 'complete' : 'in_progress'; + + return [ + 'status' => $status, + 'step' => $stepInt, + 'message' => $message, + ]; + } } diff --git a/resources/views/livewire/upgrade.blade.php b/resources/views/livewire/upgrade.blade.php index 6d4e0a529..d9d717f44 100644 --- a/resources/views/livewire/upgrade.blade.php +++ b/resources/views/livewire/upgrade.blade.php @@ -284,45 +284,32 @@ class="w-24 dark:bg-coolgray-200 dark:hover:bg-coolgray-300">Cancel this.currentStatus = 'Starting upgrade...'; this.serviceDown = false; - // Poll upgrade status API for real progress - this.checkUpgradeStatusInterval = setInterval(() => { - fetch('/api/upgrade-status', { - credentials: 'same-origin', - headers: { - 'Accept': 'application/json', + // 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.currentStatus = `Error: ${data.message}`; } - }) - .then(response => { - if (response.ok) { - return response.json(); + } 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; } - // Auth errors (401/403) or service down - switch to health check - throw new Error('Service unavailable'); - }) - .then(data => { - 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.currentStatus = `Error: ${data.message}`; - } - }) - .catch(error => { - // Service is down - switch to health check mode - console.log('Upgrade status API 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(); - } - }); + this.revive(); + } + } }, 2000); } })) diff --git a/routes/api.php b/routes/api.php index 610e27b4d..aaf7d794b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -25,11 +25,6 @@ Route::get('/health', [OtherController::class, 'healthcheck']); }); -Route::middleware(['web', 'auth'])->group(function () { - Route::get('/upgrade-status', [OtherController::class, 'upgradeStatus']); - Route::get('/v1/upgrade-status', [OtherController::class, 'upgradeStatus']); -}); - Route::post('/feedback', [OtherController::class, 'feedback']); Route::group([