Refactor upgrade status to use Livewire instead of API endpoint
- Remove /api/upgrade-status endpoint and route - Add getUpgradeStatus() method to Upgrade Livewire component - Update frontend to call Livewire method via $wire.getUpgradeStatus() - Simpler approach: no separate API, auth handled by Livewire 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d111f448f5
commit
deb9a7dab0
4 changed files with 92 additions and 153 deletions
|
|
@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -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([
|
||||
|
|
|
|||
Loading…
Reference in a new issue