Files
coolify/app/Livewire/Project/Shared/Destination.php
T
Andras BacsaiandClaude Opus 5.5 3452faec87 fix(livewire): bind team broadcast listeners to the mounted team
Livewire rebuilds listener names from the session team on every event
call. After a team switch in another tab, open pages still listen on the
old team channel, so events failed with EventHandlerDoesNotExist (500),
or ran handlers that load resources scoped to the new team (404).

Store the team at mount and build team channel listeners from it. Events
for a team that is no longer the session team are ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 10:31:32 +02:00

210 lines
8.1 KiB
PHP

<?php
namespace App\Livewire\Project\Shared;
use App\Actions\Application\StopApplicationOneServer;
use App\Actions\Docker\GetContainersStatus;
use App\Events\ApplicationStatusChanged;
use App\Models\Application;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Services\ScheduleNextRunRecalculator;
use App\Traits\ListensToTeamChannel;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Collection;
use Livewire\Component;
class Destination extends Component
{
use AuthorizesRequests;
use ListensToTeamChannel;
public $resource;
public Collection $networks;
public function getListeners()
{
return [
'refresh' => 'mount',
...$this->teamChannelListeners([
'ApplicationStatusChanged' => 'loadData',
'ServiceStatusChanged' => 'mount',
]),
];
}
public function mount()
{
$this->networks = collect([]);
$this->loadData();
}
public function loadData()
{
$all_networks = collect([]);
$all_networks = $all_networks->push($this->resource->destination);
$all_networks = $all_networks->merge($this->resource->additional_networks);
$this->networks = Server::isUsable()->get()->map(function ($server) {
return $server->standaloneDockers;
})->flatten();
$this->networks = $this->networks->reject(function ($network) use ($all_networks) {
return $all_networks->pluck('id')->contains($network->id);
});
$this->networks = $this->networks->reject(function ($network) {
return $this->resource->destination->server->id == $network->server->id;
});
if ($this->resource?->additional_servers?->count() > 0) {
$this->networks = $this->networks->reject(function ($network) {
return $this->resource->additional_servers->pluck('id')->contains($network->server->id);
});
}
}
public function stop($serverId)
{
try {
$this->authorize('deploy', $this->resource);
$server = Server::ownedByCurrentTeam()->findOrFail($serverId);
StopApplicationOneServer::run($this->resource, $server);
auditLog('ui.application.destination_stopped', [
'team_id' => $this->resource->team()?->id,
'application_uuid' => $this->resource->uuid,
'application_name' => $this->resource->name,
'server_uuid' => $server->uuid,
'server_name' => $server->name,
]);
$this->refreshServers();
} catch (\Exception $e) {
return handleError($e, $this);
}
}
public function redeploy(int $network_id, int $server_id)
{
try {
$this->authorize('deploy', $this->resource);
if ($this->resource->additional_servers->count() > 0 && str($this->resource->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/multiple-servers">documentation</a>');
return;
}
$deployment_uuid = new_public_id();
$server = Server::ownedByCurrentTeam()->findOrFail($server_id);
$destination = $server->standaloneDockers->where('id', $network_id)->firstOrFail();
$result = queue_application_deployment(
deployment_uuid: $deployment_uuid,
application: $this->resource,
server: $server,
destination: $destination,
only_this_server: true,
no_questions_asked: true,
);
if ($result['status'] === 'queue_full') {
$this->dispatch('error', 'Deployment queue full', $result['message']);
return;
}
if ($result['status'] === 'skipped') {
$this->dispatch('success', 'Deployment skipped', $result['message']);
return;
}
return redirectRoute($this, 'project.application.deployment.show', [
'project_uuid' => data_get($this->resource, 'environment.project.uuid'),
'application_uuid' => data_get($this->resource, 'uuid'),
'deployment_uuid' => $deployment_uuid,
'environment_uuid' => data_get($this->resource, 'environment.uuid'),
]);
} catch (\Exception $e) {
return handleError($e, $this);
}
}
public function promote(int $network_id, int $server_id)
{
try {
$server = Server::ownedByCurrentTeam()->findOrFail($server_id);
$network = StandaloneDocker::ownedByCurrentTeam()->where('server_id', $server->id)->findOrFail($network_id);
$this->authorize('update', $this->resource);
$this->resource->getConnection()->transaction(function () use ($network, $server) {
$mainDestination = $this->resource->destination;
$this->resource->update([
'destination_id' => $network->id,
'destination_type' => StandaloneDocker::class,
]);
$this->resource->additional_networks()
->wherePivot('server_id', $server->id)
->detach($network->id);
$this->resource->additional_networks()->attach($mainDestination->id, ['server_id' => $mainDestination->server->id]);
app(ScheduleNextRunRecalculator::class)->forResource($this->resource);
});
$this->resource->refresh();
$this->refreshServers();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function refreshServers()
{
GetContainersStatus::run($this->resource->destination->server);
$this->loadData();
$this->dispatch('refresh');
}
public function addServer(int $network_id, int $server_id)
{
try {
$server = Server::ownedByCurrentTeam()->findOrFail($server_id);
$network = StandaloneDocker::ownedByCurrentTeam()->where('server_id', $server->id)->findOrFail($network_id);
$this->authorize('update', $this->resource);
$reason = $this->resource instanceof Application
? $this->resource->additionalServersUnavailableReason()
: 'Only applications can use multiple servers.';
if ($reason) {
$this->dispatch('error', 'Failed to add server.', $reason);
return;
}
$this->resource->additional_networks()->syncWithoutDetaching([
$network->id => ['server_id' => $server->id],
]);
$this->dispatch('refresh');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function removeServer(int $network_id, int $server_id, $password, $selectedActions = [])
{
try {
$this->authorize('update', $this->resource);
if (! verifyPasswordConfirmation($password, $this)) {
return 'The provided password is incorrect.';
}
if ($this->resource->destination->server->id == $server_id && $this->resource->destination->id == $network_id) {
$this->dispatch('error', 'You are trying to remove the main server.');
return;
}
$server = Server::ownedByCurrentTeam()->findOrFail($server_id);
StopApplicationOneServer::run($this->resource, $server);
$this->resource->additional_networks()
->wherePivot('server_id', $server_id)
->detach($network_id);
$this->loadData();
$this->dispatch('refresh');
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
return true;
} catch (\Exception $e) {
return handleError($e, $this);
}
}
}