improvement(core): simplify events for app/db/service status changes

This commit is contained in:
Andras Bacsai
2025-05-19 21:50:32 +02:00
parent 61e45fbf3d
commit 786bfa960f
32 changed files with 251 additions and 153 deletions

View File

@@ -3,6 +3,7 @@
namespace App\Actions\Application;
use App\Actions\Server\CleanupDocker;
use App\Events\ServiceStatusChanged;
use App\Models\Application;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -14,6 +15,7 @@ class StopApplication
public function handle(Application $application, bool $previewDeployments = false, bool $dockerCleanup = true)
{
ray('StopApplication');
try {
$server = $application->destination->server;
if (! $server->isFunctional()) {
@@ -38,6 +40,8 @@ class StopApplication
}
} catch (\Exception $e) {
return $e->getMessage();
} finally {
ServiceStatusChanged::dispatch($application->environment->project->team->id);
}
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Actions\Database;
use App\Actions\Server\CleanupDocker;
use App\Events\ServiceStatusChanged;
use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDragonfly;
use App\Models\StandaloneKeydb;
@@ -19,23 +20,30 @@ class StopDatabase
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database, bool $isDeleteOperation = false, bool $dockerCleanup = true)
{
$server = $database->destination->server;
if (! $server->isFunctional()) {
return 'Server is not functional';
}
$this->stopContainer($database, $database->uuid, 30);
if ($isDeleteOperation) {
if ($dockerCleanup) {
CleanupDocker::dispatch($server, true);
try {
$server = $database->destination->server;
if (! $server->isFunctional()) {
return 'Server is not functional';
}
$this->stopContainer($database, $database->uuid, 30);
if ($isDeleteOperation) {
if ($dockerCleanup) {
CleanupDocker::dispatch($server, true);
}
}
if ($database->is_public) {
StopDatabaseProxy::run($database);
}
return 'Database stopped successfully';
} catch (\Exception $e) {
return 'Database stop failed: '.$e->getMessage();
} finally {
ServiceStatusChanged::dispatch($database->environment->project->team->id);
}
if ($database->is_public) {
StopDatabaseProxy::run($database);
}
return 'Database stopped successfully';
}
private function stopContainer($database, string $containerName, int $timeout = 30): void

View File

@@ -4,6 +4,7 @@ namespace App\Actions\Docker;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Shared\ComplexStatusCheck;
use App\Events\ServiceChecked;
use App\Models\ApplicationPreview;
use App\Models\Server;
use App\Models\ServiceDatabase;
@@ -341,5 +342,6 @@ class GetContainersStatus
}
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
ServiceChecked::dispatch($this->server->team->id);
}
}

View File

@@ -41,6 +41,6 @@ class StartService
}
}
return remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
return remote_process($commands, $service->server, type_uuid: $service->uuid);
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Actions\Service;
use App\Actions\Server\CleanupDocker;
use App\Events\ServiceStatusChanged;
use App\Models\Service;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -31,6 +32,8 @@ class StopService
}
} catch (\Exception $e) {
return $e->getMessage();
} finally {
ServiceStatusChanged::dispatch($service->environment->project->team->id);
}
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ServiceChecked implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public ?int $teamId = null;
public function __construct($teamId = null)
{
if (is_null($teamId) && auth()->check() && auth()->user()->currentTeam()) {
$teamId = auth()->user()->currentTeam()->id;
}
$this->teamId = $teamId;
}
public function broadcastOn(): array
{
if (is_null($this->teamId)) {
return [];
}
return [
new PrivateChannel("team.{$this->teamId}"),
];
}
}

View File

@@ -7,30 +7,29 @@ use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Auth;
class ServiceStatusChanged implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public int|string|null $userId = null;
public ?int $teamId = null;
public function __construct($userId = null)
public function __construct($teamId = null)
{
if (is_null($userId)) {
$userId = Auth::id() ?? null;
if (is_null($teamId) && auth()->check() && auth()->user()->currentTeam()) {
$teamId = auth()->user()->currentTeam()->id;
}
$this->userId = $userId;
$this->teamId = $teamId;
}
public function broadcastOn(): ?array
public function broadcastOn(): array
{
if (is_null($this->userId)) {
if (is_null($this->teamId)) {
return [];
}
return [
new PrivateChannel("user.{$this->userId}"),
new PrivateChannel("team.{$this->teamId}"),
];
}
}

View File

@@ -5,7 +5,7 @@ namespace App\Jobs;
use App\Actions\Docker\GetContainersStatus;
use App\Enums\ApplicationDeploymentStatus;
use App\Enums\ProcessStatus;
use App\Events\ApplicationStatusChanged;
use App\Events\ServiceStatusChanged;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\ApplicationPreview;
@@ -331,7 +331,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->application_deployment_queue->addLogEntry("Gracefully shutting down build container: {$this->deployment_uuid}");
$this->graceful_shutdown_container($this->deployment_uuid);
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
ServiceStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
}
}

View File

@@ -17,7 +17,15 @@ class Configuration extends Component
public $servers;
protected $listeners = ['buildPackUpdated' => '$refresh'];
public function getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},ServiceChecked" => '$refresh',
'buildPackUpdated' => '$refresh',
];
}
public function mount()
{

View File

@@ -28,6 +28,15 @@ class Index extends Component
protected $queryString = ['pull_request_id'];
public function getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},ServiceChecked" => '$refresh',
];
}
public function mount()
{
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();

View File

@@ -18,7 +18,15 @@ class Show extends Component
public $isKeepAliveOn = true;
protected $listeners = ['refreshQueue'];
public function getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},ServiceChecked" => '$refresh',
'refreshQueue',
];
}
public function mount()
{

View File

@@ -4,7 +4,6 @@ namespace App\Livewire\Project\Application;
use App\Actions\Application\StopApplication;
use App\Actions\Docker\GetContainersStatus;
use App\Events\ApplicationStatusChanged;
use App\Models\Application;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@@ -28,7 +27,8 @@ class Heading extends Component
$teamId = auth()->user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},ApplicationStatusChanged" => 'check_status',
"echo-private:team.{$teamId},ServiceStatusChanged" => 'checkStatus',
"echo-private:team.{$teamId},ServiceChecked" => '$refresh',
'compose_loaded' => '$refresh',
'update_links' => '$refresh',
];
@@ -46,14 +46,11 @@ class Heading extends Component
$this->lastDeploymentLink = $this->application->gitCommitLink(data_get($lastDeployment, 'commit'));
}
public function check_status($showNotification = false)
public function checkStatus()
{
if ($this->application->destination->server->isFunctional()) {
GetContainersStatus::dispatch($this->application->destination->server);
}
if ($showNotification) {
$this->dispatch('success', 'Success', 'Application status updated.');
}
}
public function force_deploy_without_cache()
@@ -111,16 +108,7 @@ class Heading extends Component
public function stop()
{
StopApplication::run($this->application, false, $this->docker_cleanup);
$this->application->status = 'exited';
$this->application->save();
if ($this->application->additional_servers->count() > 0) {
$this->application->additional_servers->each(function ($server) {
$server->pivot->status = 'exited:unhealthy';
$server->pivot->save();
});
}
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
StopApplication::dispatch($this->application, false, $this->docker_cleanup);
}
public function restart()

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Project\Database;
use Auth;
use Livewire\Component;
class Configuration extends Component
@@ -14,6 +15,15 @@ class Configuration extends Component
public $environment;
public function getListeners()
{
$teamId = Auth::user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},ServiceChecked" => '$refresh',
];
}
public function mount()
{
$this->currentRoute = request()->route()->getName();

View File

@@ -6,8 +6,7 @@ use App\Actions\Database\RestartDatabase;
use App\Actions\Database\StartDatabase;
use App\Actions\Database\StopDatabase;
use App\Actions\Docker\GetContainersStatus;
use App\Events\DatabaseStatusChanged;
use Illuminate\Support\Facades\Auth;
use App\Events\ServiceStatusChanged;
use Livewire\Component;
class Heading extends Component
@@ -20,36 +19,41 @@ class Heading extends Component
public function getListeners()
{
$userId = Auth::id();
$teamId = auth()->user()->currentTeam()->id;
return [
"echo-private:user.{$userId},DatabaseStatusChanged" => 'activityFinished',
"echo-private:team.{$teamId},ServiceStatusChanged" => 'checkStatus',
"echo-private:team.{$teamId},ServiceChecked" => 'activityFinished',
'refresh' => '$refresh',
'compose_loaded' => '$refresh',
'update_links' => '$refresh',
];
}
public function activityFinished()
{
$this->database->update([
'started_at' => now(),
]);
$this->check_status();
try {
$this->database->update([
'started_at' => now(),
]);
if (is_null($this->database->config_hash) || $this->database->isConfigurationChanged()) {
$this->database->isConfigurationChanged(true);
$this->dispatch('configurationChanged');
} else {
$this->dispatch('configurationChanged');
if (is_null($this->database->config_hash) || $this->database->isConfigurationChanged()) {
$this->database->isConfigurationChanged(true);
$this->dispatch('configurationChanged');
} else {
$this->dispatch('configurationChanged');
}
} catch (\Exception $e) {
return handleError($e, $this);
} finally {
$this->dispatch('refresh');
}
}
public function check_status($showNotification = false)
public function checkStatus()
{
if ($this->database->destination->server->isFunctional()) {
GetContainersStatus::run($this->database->destination->server);
}
if ($showNotification) {
$this->dispatch('success', 'Database status updated.');
GetContainersStatus::dispatch($this->database->destination->server);
}
}
@@ -60,23 +64,24 @@ class Heading extends Component
public function stop()
{
StopDatabase::run($this->database, false, $this->docker_cleanup);
$this->database->status = 'exited';
$this->database->save();
$this->check_status();
$this->dispatch('refresh');
try {
$this->dispatch('info', 'Stopping database.');
StopDatabase::dispatch($this->database, false, $this->docker_cleanup);
} catch (\Exception $e) {
$this->dispatch('error', $e->getMessage());
}
}
public function restart()
{
$activity = RestartDatabase::run($this->database);
$this->dispatch('activityMonitor', $activity->id, DatabaseStatusChanged::class);
$this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class);
}
public function start()
{
$activity = StartDatabase::run($this->database);
$this->dispatch('activityMonitor', $activity->id, DatabaseStatusChanged::class);
$this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class);
}
public function render()

View File

@@ -2,7 +2,6 @@
namespace App\Livewire\Project\Service;
use App\Actions\Docker\GetContainersStatus;
use App\Models\Service;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
@@ -27,13 +26,10 @@ class Configuration extends Component
public function getListeners()
{
$userId = Auth::id();
$teamId = Auth::user()->currentTeam()->id;
return [
"echo-private:user.{$userId},ServiceStatusChanged" => 'check_status',
'refreshStatus' => '$refresh',
'check_status',
'refreshServices',
"echo-private:team.{$teamId},ServiceChecked" => 'serviceChecked',
];
}
@@ -97,19 +93,15 @@ class Configuration extends Component
}
}
public function check_status()
public function serviceChecked()
{
try {
if ($this->service->server->isFunctional()) {
GetContainersStatus::dispatch($this->service->server);
}
$this->service->applications->each(function ($application) {
$application->refresh();
});
$this->service->databases->each(function ($database) {
$database->refresh();
});
$this->dispatch('refreshStatus');
} catch (\Exception $e) {
return handleError($e, $this);
}

View File

@@ -47,7 +47,6 @@ class EditDomain extends Component
$this->application->service->parse();
$this->dispatch('refresh');
$this->dispatch('configurationChanged');
$this->dispatch('refreshStatus');
} catch (\Throwable $e) {
$originalFqdn = $this->application->getOriginal('fqdn');
if ($originalFqdn !== $this->application->fqdn) {

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Project\Service;
use App\Actions\Docker\GetContainersStatus;
use App\Actions\Service\StartService;
use App\Actions\Service\StopService;
use App\Enums\ProcessStatus;
@@ -11,7 +12,7 @@ use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use Spatie\Activitylog\Models\Activity;
class Navbar extends Component
class Heading extends Component
{
public Service $service;
@@ -35,35 +36,44 @@ class Navbar extends Component
public function getListeners()
{
$userId = Auth::id();
$teamId = Auth::user()->currentTeam()->id;
return [
"echo-private:user.{$userId},ServiceStatusChanged" => 'serviceStarted',
"echo-private:team.{$teamId},ServiceStatusChanged" => 'checkStatus',
"echo-private:team.{$teamId},ServiceChecked" => 'serviceChecked',
'refresh' => '$refresh',
'envsUpdated' => '$refresh',
'refreshStatus' => '$refresh',
];
}
public function serviceStarted()
public function checkStatus()
{
// $this->dispatch('success', 'Service status changed.');
if (is_null($this->service->config_hash) || $this->service->isConfigurationChanged()) {
$this->service->isConfigurationChanged(true);
$this->dispatch('configurationChanged');
} else {
$this->dispatch('configurationChanged');
if ($this->service->server->isFunctional()) {
GetContainersStatus::dispatch($this->service->server);
}
}
public function check_status_without_notification()
public function serviceChecked()
{
$this->dispatch('check_status');
}
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()) {
$this->service->isConfigurationChanged(true);
$this->dispatch('configurationChanged');
} else {
$this->dispatch('configurationChanged');
}
} catch (\Exception $e) {
return handleError($e, $this);
} finally {
$this->dispatch('refresh')->self();
}
public function check_status()
{
$this->dispatch('check_status');
$this->dispatch('success', 'Service status updated.');
}
public function checkDeployments()
@@ -86,7 +96,7 @@ class Navbar extends Component
public function start()
{
$activity = StartService::run($this->service, pullLatestImages: true);
$this->dispatch('activityMonitor', $activity->id);
$this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class);
}
public function forceDeploy()
@@ -98,22 +108,16 @@ class Navbar extends Component
$activity->save();
}
$activity = StartService::run($this->service, pullLatestImages: true, stopBeforeStart: true);
$this->dispatch('activityMonitor', $activity->id);
$this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class);
} catch (\Exception $e) {
$this->dispatch('error', $e->getMessage());
}
}
public function stop($cleanupContainers = false)
public function stop()
{
try {
StopService::run($this->service, false, $this->docker_cleanup);
ServiceStatusChanged::dispatch();
if ($cleanupContainers) {
$this->dispatch('success', 'Containers cleaned up.');
} else {
$this->dispatch('success', 'Service stopped.');
}
StopService::dispatch($this->service, false, $this->docker_cleanup);
} catch (\Exception $e) {
$this->dispatch('error', $e->getMessage());
}
@@ -128,7 +132,7 @@ class Navbar extends Component
return;
}
$activity = StartService::run($this->service, stopBeforeStart: true);
$this->dispatch('activityMonitor', $activity->id);
$this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class);
}
public function pullAndRestartEvent()
@@ -140,12 +144,12 @@ class Navbar extends Component
return;
}
$activity = StartService::run($this->service, pullLatestImages: true, stopBeforeStart: true);
$this->dispatch('activityMonitor', $activity->id);
$this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class);
}
public function render()
{
return view('livewire.project.service.navbar', [
return view('livewire.project.service.heading', [
'checkboxes' => [
['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')],
],

View File

@@ -119,17 +119,14 @@ class Destination extends Component
public function refreshServers()
{
GetContainersStatus::run($this->resource->destination->server);
// ContainerStatusJob::dispatchSync($this->resource->destination->server);
$this->loadData();
$this->dispatch('refresh');
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
}
public function addServer(int $network_id, int $server_id)
{
$this->resource->additional_networks()->attach($network_id, ['server_id' => $server_id]);
$this->loadData();
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
}
public function removeServer(int $network_id, int $server_id, $password)

View File

@@ -35,6 +35,15 @@ class ExecuteContainerCommand extends Component
'command' => 'required',
];
public function getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},ServiceChecked" => '$refresh',
];
}
public function mount()
{
if (! auth()->user()->isAdmin()) {

View File

@@ -37,6 +37,15 @@ class Logs extends Component
public $cpu;
public function getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},ServiceChecked" => '$refresh',
];
}
public function loadContainers($server_id)
{
try {

View File

@@ -46,6 +46,15 @@ class Show extends Component
#[Locked]
public string $task_uuid;
public function getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},ServiceChecked" => '$refresh',
];
}
public function mount(string $task_uuid, string $project_uuid, string $environment_uuid, ?string $application_uuid = null, ?string $service_uuid = null)
{
try {