Files
coolify/app/Livewire/Project/Service/Heading.php
T

308 lines
9.6 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';
/**
* The container statuses of the stack resources at the last check.
* The heading polls them, so it tells the resource cards when they change.
*/
public ?string $resourceStatuses = null;
public function mount()
{
$this->authorizeService('view');
$this->checkDeployments();
$this->resourceStatuses = $this->currentResourceStatuses();
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();
$this->dispatchIfResourceStatusesChanged();
if ($this->service->server->isFunctional()) {
GetContainersStatus::dispatch($this->service->server);
} else {
$this->dispatch('error', 'Server is not functional.');
}
}
private function dispatchIfResourceStatusesChanged(): void
{
$statuses = $this->currentResourceStatuses();
if ($statuses === $this->resourceStatuses) {
return;
}
$this->resourceStatuses = $statuses;
$this->dispatch('serviceResourceStatusesChanged');
}
private function currentResourceStatuses(): string
{
return $this->service->applications->pluck('status', 'uuid')
->merge($this->service->databases->pluck('status', 'uuid'))
->toJson();
}
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')],
],
]);
}
}