mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-14 08:42:38 -04:00
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>
282 lines
8.7 KiB
PHP
282 lines
8.7 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Project\Service;
|
|
|
|
use App\Actions\Docker\GetContainersStatus;
|
|
use App\Actions\Service\StartService;
|
|
use App\Actions\Service\StopService;
|
|
use App\Actions\Service\StopServiceApplication;
|
|
use App\Enums\ProcessStatus;
|
|
use App\Models\Service;
|
|
use App\Models\ServiceApplication;
|
|
use App\Models\ServiceDatabase;
|
|
use App\Support\ResourceStartActivity;
|
|
use App\Traits\ListensToTeamChannel;
|
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
|
use Livewire\Component;
|
|
use Spatie\Activitylog\Models\Activity;
|
|
|
|
class Heading extends Component
|
|
{
|
|
use AuthorizesRequests;
|
|
use ListensToTeamChannel;
|
|
|
|
public Service $service;
|
|
|
|
public array $parameters;
|
|
|
|
public array $query;
|
|
|
|
public $isDeploymentProgress = false;
|
|
|
|
public $runningActivityId = null;
|
|
|
|
public $docker_cleanup = true;
|
|
|
|
public $title = 'Configuration';
|
|
|
|
public function mount()
|
|
{
|
|
$this->authorizeService('view');
|
|
|
|
$this->checkDeployments();
|
|
|
|
if (str($this->service->status)->contains('running') && is_null($this->service->config_hash)) {
|
|
$this->service->isConfigurationChanged(true);
|
|
$this->dispatch('configurationChanged');
|
|
}
|
|
}
|
|
|
|
public function getListeners()
|
|
{
|
|
return [
|
|
'refresh' => '$refresh',
|
|
'envsUpdated' => '$refresh',
|
|
...$this->teamChannelListeners([
|
|
'ServiceStatusChanged' => 'checkStatus',
|
|
'ServiceChecked' => 'serviceChecked',
|
|
]),
|
|
];
|
|
}
|
|
|
|
public function checkStatus()
|
|
{
|
|
$this->authorizeService('view');
|
|
|
|
$this->checkDeployments();
|
|
|
|
if ($this->service->server->isFunctional()) {
|
|
GetContainersStatus::dispatch($this->service->server);
|
|
} else {
|
|
$this->dispatch('error', 'Server is not functional.');
|
|
}
|
|
}
|
|
|
|
public function manualCheckStatus()
|
|
{
|
|
$this->checkStatus();
|
|
}
|
|
|
|
public function serviceChecked()
|
|
{
|
|
$this->authorizeService('view');
|
|
|
|
try {
|
|
$this->service->applications->each(function ($application) {
|
|
$application->refresh();
|
|
});
|
|
$this->service->databases->each(function ($database) {
|
|
$database->refresh();
|
|
});
|
|
if (is_null($this->service->config_hash)) {
|
|
$this->service->isConfigurationChanged(true);
|
|
}
|
|
$this->dispatch('configurationChanged');
|
|
} catch (\Exception $e) {
|
|
return handleError($e, $this);
|
|
} finally {
|
|
$this->dispatch('refresh')->self();
|
|
}
|
|
|
|
}
|
|
|
|
public function checkDeployments()
|
|
{
|
|
$this->authorizeService('view');
|
|
|
|
try {
|
|
$activity = ResourceStartActivity::latestRunning($this->service->uuid);
|
|
$this->isDeploymentProgress = $activity !== null;
|
|
$this->runningActivityId = $activity?->id;
|
|
} catch (\Throwable) {
|
|
$this->isDeploymentProgress = false;
|
|
$this->runningActivityId = null;
|
|
}
|
|
|
|
return $this->isDeploymentProgress;
|
|
}
|
|
|
|
/**
|
|
* Re-attach the live log dialog to a deployment that is already running.
|
|
* Used by the "Deploying…" indicator and when Deploy/Restart is clicked
|
|
* while a deployment is in progress, so the running log reappears instead
|
|
* of a dead-end error.
|
|
*/
|
|
public function reopenDeployment()
|
|
{
|
|
$this->authorizeService('view');
|
|
|
|
$this->checkDeployments();
|
|
|
|
if ($this->isDeploymentProgress && $this->runningActivityId) {
|
|
$this->dispatch('activityMonitor', $this->runningActivityId);
|
|
$this->js("window.dispatchEvent(new CustomEvent('startservice'))");
|
|
} else {
|
|
$this->dispatch('info', 'No deployment is currently running.');
|
|
}
|
|
}
|
|
|
|
public function start()
|
|
{
|
|
try {
|
|
$this->authorizeService('deploy');
|
|
$activity = StartService::run($this->service, pullLatestImages: true);
|
|
$this->auditServiceAction('ui.service.started');
|
|
$this->markDeploymentRunning($activity->id);
|
|
$this->js("window.dispatchEvent(new CustomEvent('startservice'))");
|
|
$this->dispatch('activityMonitor', $activity->id);
|
|
} catch (\Throwable $e) {
|
|
return handleError($e, $this);
|
|
}
|
|
}
|
|
|
|
public function forceDeploy()
|
|
{
|
|
try {
|
|
$this->authorizeService('deploy');
|
|
$activities = Activity::where('properties->type_uuid', $this->service->uuid)
|
|
->where(function ($q) {
|
|
$q->where('properties->status', ProcessStatus::IN_PROGRESS->value)
|
|
->orWhere('properties->status', ProcessStatus::QUEUED->value);
|
|
})->get();
|
|
foreach ($activities as $activity) {
|
|
$activity->properties->status = ProcessStatus::ERROR->value;
|
|
$activity->save();
|
|
}
|
|
$activity = StartService::run($this->service, pullLatestImages: true, stopBeforeStart: true);
|
|
$this->markDeploymentRunning($activity->id);
|
|
$this->js("window.dispatchEvent(new CustomEvent('startservice'))");
|
|
$this->dispatch('activityMonitor', $activity->id);
|
|
} catch (\Throwable $e) {
|
|
return handleError($e, $this);
|
|
}
|
|
}
|
|
|
|
private function markDeploymentRunning($activityId): void
|
|
{
|
|
$this->isDeploymentProgress = true;
|
|
$this->runningActivityId = $activityId;
|
|
}
|
|
|
|
public function stop()
|
|
{
|
|
try {
|
|
$this->authorizeService('stop');
|
|
StopService::dispatch($this->service, false, $this->docker_cleanup);
|
|
$this->auditServiceAction('ui.service.stopped');
|
|
} catch (\Throwable $e) {
|
|
return handleError($e, $this);
|
|
}
|
|
}
|
|
|
|
public function restart()
|
|
{
|
|
try {
|
|
$this->authorizeService('deploy');
|
|
$this->checkDeployments();
|
|
if ($this->isDeploymentProgress) {
|
|
$this->dispatch('error', 'There is a deployment in progress.');
|
|
|
|
return;
|
|
}
|
|
$activity = StartService::run($this->service, stopBeforeStart: true);
|
|
$this->auditServiceAction('ui.service.restarted');
|
|
$this->markDeploymentRunning($activity->id);
|
|
$this->js("window.dispatchEvent(new CustomEvent('startservice'))");
|
|
$this->dispatch('activityMonitor', $activity->id);
|
|
} catch (\Throwable $e) {
|
|
return handleError($e, $this);
|
|
}
|
|
}
|
|
|
|
public function removeSelectedResourceContainer(): void
|
|
{
|
|
$resource = $this->selectedResource();
|
|
if (! $resource) {
|
|
return;
|
|
}
|
|
|
|
$this->authorize('update', $resource);
|
|
StopServiceApplication::run($resource, true, true);
|
|
$this->dispatch('success', 'Container removed.');
|
|
}
|
|
|
|
private function selectedResource(): ServiceApplication|ServiceDatabase|null
|
|
{
|
|
$uuid = data_get($this->parameters, 'stack_service_uuid');
|
|
if (! $uuid) {
|
|
return null;
|
|
}
|
|
|
|
return $this->service->applications()->whereUuid($uuid)->first()
|
|
?? $this->service->databases()->whereUuid($uuid)->first();
|
|
}
|
|
|
|
public function pullAndRestartEvent()
|
|
{
|
|
try {
|
|
$this->authorizeService('deploy');
|
|
$this->checkDeployments();
|
|
if ($this->isDeploymentProgress) {
|
|
$this->dispatch('error', 'There is a deployment in progress.');
|
|
|
|
return;
|
|
}
|
|
$activity = StartService::run($this->service, pullLatestImages: true, stopBeforeStart: true);
|
|
$this->auditServiceAction('ui.service.restarted');
|
|
$this->markDeploymentRunning($activity->id);
|
|
$this->js("window.dispatchEvent(new CustomEvent('startservice'))");
|
|
$this->dispatch('activityMonitor', $activity->id);
|
|
} catch (\Throwable $e) {
|
|
return handleError($e, $this);
|
|
}
|
|
}
|
|
|
|
private function authorizeService(string $ability): void
|
|
{
|
|
$this->service = Service::ownedByCurrentTeam()
|
|
->whereKey($this->service->getKey())
|
|
->firstOrFail();
|
|
|
|
$this->authorize($ability, $this->service);
|
|
}
|
|
|
|
private function auditServiceAction(string $event): void
|
|
{
|
|
auditLog($event, [
|
|
'team_id' => $this->service->team()?->id,
|
|
'service_uuid' => $this->service->uuid,
|
|
'service_name' => $this->service->name,
|
|
]);
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.project.service.heading', [
|
|
'checkboxes' => [
|
|
['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')],
|
|
],
|
|
]);
|
|
}
|
|
}
|