Make proxy restart run as background job to prevent localhost lockout

When restarting the proxy on localhost (where Coolify is running), the UI becomes inaccessible because the connection is lost. This change makes all proxy restarts run as background jobs with WebSocket notifications, allowing the operation to complete even after connection loss.

Changes:
- Enhanced ProxyStatusChangedUI event to carry activityId for log monitoring
- Updated RestartProxyJob to dispatch status events and track activity
- Simplified Navbar restart() to always dispatch job for all servers
- Enhanced showNotification() to handle activity monitoring and new statuses
- Added comprehensive unit and feature tests

Benefits:
- Prevents localhost lockout during proxy restarts
- Consistent behavior across all server types
- Non-blocking UI with real-time progress updates
- Automatic activity log monitoring
- Proper error handling and recovery

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai
2025-12-03 10:29:39 +01:00
parent b55aaf34d3
commit e4810a28d2
5 changed files with 376 additions and 8 deletions

View File

@@ -4,6 +4,8 @@ namespace App\Jobs;
use App\Actions\Proxy\StartProxy;
use App\Actions\Proxy\StopProxy;
use App\Enums\ProxyTypes;
use App\Events\ProxyStatusChangedUI;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
@@ -12,6 +14,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class RestartProxyJob implements ShouldBeEncrypted, ShouldQueue
{
@@ -21,6 +24,8 @@ class RestartProxyJob implements ShouldBeEncrypted, ShouldQueue
public $timeout = 60;
public ?int $activity_id = null;
public function middleware(): array
{
return [(new WithoutOverlapping('restart-proxy-'.$this->server->uuid))->expireAfter(60)->dontRelease()];
@@ -31,14 +36,45 @@ class RestartProxyJob implements ShouldBeEncrypted, ShouldQueue
public function handle()
{
try {
$teamId = $this->server->team_id;
// Stop proxy
StopProxy::run($this->server, restarting: true);
// Clear force_stop flag
$this->server->proxy->force_stop = false;
$this->server->save();
StartProxy::run($this->server, force: true, restarting: true);
// Start proxy asynchronously to get activity
$activity = StartProxy::run($this->server, force: true, restarting: true);
// Store activity ID and dispatch event with it
if ($activity && is_object($activity)) {
$this->activity_id = $activity->id;
ProxyStatusChangedUI::dispatch($teamId, $this->activity_id);
}
// Check Traefik version after restart (same as original behavior)
if ($this->server->proxyType() === ProxyTypes::TRAEFIK->value) {
$traefikVersions = get_traefik_versions();
if ($traefikVersions !== null) {
CheckTraefikVersionForServerJob::dispatch($this->server, $traefikVersions);
} else {
Log::warning('Traefik version check skipped: versions.json data unavailable', [
'server_id' => $this->server->id,
'server_name' => $this->server->name,
]);
}
}
} catch (\Throwable $e) {
// Set error status
$this->server->proxy->status = 'error';
$this->server->save();
// Notify UI of error
ProxyStatusChangedUI::dispatch($this->server->team_id);
return handleError($e);
}
}