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

View File

@@ -3,6 +3,7 @@
namespace App\Actions\Database; namespace App\Actions\Database;
use App\Actions\Server\CleanupDocker; use App\Actions\Server\CleanupDocker;
use App\Events\ServiceStatusChanged;
use App\Models\StandaloneClickhouse; use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDragonfly; use App\Models\StandaloneDragonfly;
use App\Models\StandaloneKeydb; 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) public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database, bool $isDeleteOperation = false, bool $dockerCleanup = true)
{ {
$server = $database->destination->server; try {
if (! $server->isFunctional()) { $server = $database->destination->server;
return 'Server is not functional'; if (! $server->isFunctional()) {
} return 'Server is not functional';
$this->stopContainer($database, $database->uuid, 30);
if ($isDeleteOperation) {
if ($dockerCleanup) {
CleanupDocker::dispatch($server, true);
} }
$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 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\Database\StartDatabaseProxy;
use App\Actions\Shared\ComplexStatusCheck; use App\Actions\Shared\ComplexStatusCheck;
use App\Events\ServiceChecked;
use App\Models\ApplicationPreview; use App\Models\ApplicationPreview;
use App\Models\Server; use App\Models\Server;
use App\Models\ServiceDatabase; use App\Models\ServiceDatabase;
@@ -341,5 +342,6 @@ class GetContainersStatus
} }
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); // $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; namespace App\Actions\Service;
use App\Actions\Server\CleanupDocker; use App\Actions\Server\CleanupDocker;
use App\Events\ServiceStatusChanged;
use App\Models\Service; use App\Models\Service;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
@@ -31,6 +32,8 @@ class StopService
} }
} catch (\Exception $e) { } catch (\Exception $e) {
return $e->getMessage(); 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\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Auth;
class ServiceStatusChanged implements ShouldBroadcast class ServiceStatusChanged implements ShouldBroadcast
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; 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)) { if (is_null($teamId) && auth()->check() && auth()->user()->currentTeam()) {
$userId = Auth::id() ?? null; $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 [];
} }
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\Actions\Docker\GetContainersStatus;
use App\Enums\ApplicationDeploymentStatus; use App\Enums\ApplicationDeploymentStatus;
use App\Enums\ProcessStatus; use App\Enums\ProcessStatus;
use App\Events\ApplicationStatusChanged; use App\Events\ServiceStatusChanged;
use App\Models\Application; use App\Models\Application;
use App\Models\ApplicationDeploymentQueue; use App\Models\ApplicationDeploymentQueue;
use App\Models\ApplicationPreview; 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->application_deployment_queue->addLogEntry("Gracefully shutting down build container: {$this->deployment_uuid}");
$this->graceful_shutdown_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; 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() public function mount()
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -46,6 +46,15 @@ class Show extends Component
#[Locked] #[Locked]
public string $task_uuid; 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) public function mount(string $task_uuid, string $project_uuid, string $environment_uuid, ?string $application_uuid = null, ?string $service_uuid = null)
{ {
try { try {

View File

@@ -87,7 +87,6 @@
params.push(this.password); params.push(this.password);
} }
params.push(this.selectedActions); params.push(this.selectedActions);
return $wire[methodName](...params) return $wire[methodName](...params)
.then(result => { .then(result => {
if (result === true) { if (result === true) {

View File

@@ -13,7 +13,8 @@
<x-status.stopped :status="$resource->status" /> <x-status.stopped :status="$resource->status" />
@endif @endif
@if (!str($resource->status)->contains('exited') && $showRefreshButton) @if (!str($resource->status)->contains('exited') && $showRefreshButton)
<button title="Refresh Status" wire:click='check_status(true)' class="mx-1 dark:hover:fill-white fill-black dark:fill-warning"> <button title="Refresh Status" wire:click='checkStatus'
class="mx-1 dark:hover:fill-white fill-black dark:fill-warning">
<svg class="w-4 h-4" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg class="w-4 h-4" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path <path
d="M12 2a10.016 10.016 0 0 0-7 2.877V3a1 1 0 1 0-2 0v4.5a1 1 0 0 0 1 1h4.5a1 1 0 0 0 0-2H6.218A7.98 7.98 0 0 1 20 12a1 1 0 0 0 2 0A10.012 10.012 0 0 0 12 2zm7.989 13.5h-4.5a1 1 0 0 0 0 2h2.293A7.98 7.98 0 0 1 4 12a1 1 0 0 0-2 0a9.986 9.986 0 0 0 16.989 7.133V21a1 1 0 0 0 2 0v-4.5a1 1 0 0 0-1-1z" /> d="M12 2a10.016 10.016 0 0 0-7 2.877V3a1 1 0 1 0-2 0v4.5a1 1 0 0 0 1 1h4.5a1 1 0 0 0 0-2H6.218A7.98 7.98 0 0 1 20 12a1 1 0 0 0 2 0A10.012 10.012 0 0 0 12 2zm7.989 13.5h-4.5a1 1 0 0 0 0 2h2.293A7.98 7.98 0 0 1 4 12a1 1 0 0 0-2 0a9.986 9.986 0 0 0 16.989 7.133V21a1 1 0 0 0 2 0v-4.5a1 1 0 0 0-1-1z" />

View File

@@ -8,7 +8,8 @@
<x-status.stopped :status="$complexStatus" /> <x-status.stopped :status="$complexStatus" />
@endif @endif
@if (!str($complexStatus)->contains('exited') && $showRefreshButton) @if (!str($complexStatus)->contains('exited') && $showRefreshButton)
<button title="Refresh Status" wire:click='check_status(true)' class="mx-1 dark:hover:fill-white fill-black dark:fill-warning"> <button title="Refresh Status" wire:click='checkStatus'
class="mx-1 dark:hover:fill-white fill-black dark:fill-warning">
<svg class="w-4 h-4" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg class="w-4 h-4" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path <path
d="M12 2a10.016 10.016 0 0 0-7 2.877V3a1 1 0 1 0-2 0v4.5a1 1 0 0 0 1 1h4.5a1 1 0 0 0 0-2H6.218A7.98 7.98 0 0 1 20 12a1 1 0 0 0 2 0A10.012 10.012 0 0 0 12 2zm7.989 13.5h-4.5a1 1 0 0 0 0 2h2.293A7.98 7.98 0 0 1 4 12a1 1 0 0 0-2 0a9.986 9.986 0 0 0 16.989 7.133V21a1 1 0 0 0 2 0v-4.5a1 1 0 0 0-1-1z" /> d="M12 2a10.016 10.016 0 0 0-7 2.877V3a1 1 0 1 0-2 0v4.5a1 1 0 0 0 1 1h4.5a1 1 0 0 0 0-2H6.218A7.98 7.98 0 0 1 20 12a1 1 0 0 0 2 0A10.012 10.012 0 0 0 12 2zm7.989 13.5h-4.5a1 1 0 0 0 0 2h2.293A7.98 7.98 0 0 1 4 12a1 1 0 0 0-2 0a9.986 9.986 0 0 0 16.989 7.133V21a1 1 0 0 0 2 0v-4.5a1 1 0 0 0-1-1z" />

View File

@@ -1,4 +1,4 @@
<nav wire:poll.10000ms="check_status"> <nav wire:poll.10000ms="checkStatus">
<x-resources.breadcrumbs :resource="$application" :parameters="$parameters" :title="$lastDeploymentInfo" :lastDeploymentLink="$lastDeploymentLink" /> <x-resources.breadcrumbs :resource="$application" :parameters="$parameters" :title="$lastDeploymentInfo" :lastDeploymentLink="$lastDeploymentLink" />
<div class="navbar-main"> <div class="navbar-main">
<nav class="flex shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10"> <nav class="flex shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10">
@@ -81,8 +81,7 @@
'This application will be stopped.', 'This application will be stopped.',
'All non-persistent data of this application will be deleted.', 'All non-persistent data of this application will be deleted.',
]" :confirmWithText="false" :confirmWithPassword="false" ]" :confirmWithText="false" :confirmWithPassword="false"
step1ButtonText="Continue" step2ButtonText="Confirm" :dispatchEvent="true" step1ButtonText="Continue" step2ButtonText="Confirm">
dispatchEventType="stopEvent">
<x-slot:button-title> <x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"

View File

@@ -1,4 +1,4 @@
<nav wire:poll.10000ms="check_status"> <nav wire:poll.10000ms="checkStatus">
<x-resources.breadcrumbs :resource="$database" :parameters="$parameters" /> <x-resources.breadcrumbs :resource="$database" :parameters="$parameters" />
<x-slide-over @startdatabase.window="slideOverOpen = true" closeWithX fullScreen> <x-slide-over @startdatabase.window="slideOverOpen = true" closeWithX fullScreen>
<x-slot:title>Database Startup</x-slot:title> <x-slot:title>Database Startup</x-slot:title>
@@ -60,8 +60,7 @@
'If the database is currently in use data could be lost.', 'If the database is currently in use data could be lost.',
'All non-persistent data of this database (containers, networks, unused images) will be deleted (don\'t worry, no data is lost and you can start the database again).', 'All non-persistent data of this database (containers, networks, unused images) will be deleted (don\'t worry, no data is lost and you can start the database again).',
]" :confirmWithText="false" :confirmWithPassword="false" ]" :confirmWithText="false" :confirmWithPassword="false"
step1ButtonText="Continue" step2ButtonText="Stop Database" :dispatchEvent="true" step1ButtonText="Continue" step2ButtonText="Stop Database">
dispatchEventType="stopEvent">
<x-slot:button-title> <x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
@@ -99,6 +98,7 @@
}); });
$wire.$on('restartEvent', () => { $wire.$on('restartEvent', () => {
$wire.$dispatch('info', 'Restarting database.'); $wire.$dispatch('info', 'Restarting database.');
window.dispatchEvent(new CustomEvent('startdatabase'));
$wire.$call('restart'); $wire.$call('restart');
}); });
</script> </script>

View File

@@ -67,16 +67,16 @@
<div class="pb-2 truncate box-title" x-text="item.name"></div> <div class="pb-2 truncate box-title" x-text="item.name"></div>
<div class="flex-1"></div> <div class="flex-1"></div>
<template x-if="item.status.startsWith('running')"> <template x-if="item.status.startsWith('running')">
<div title="running" class="bg-success badge badge-absolute"></div> <div title="running" class="bg-success badge-dashboard"></div>
</template> </template>
<template x-if="item.status.startsWith('exited')"> <template x-if="item.status.startsWith('exited')">
<div title="exited" class="bg-error badge badge-absolute"></div> <div title="exited" class="bg-error badge-dashboard"></div>
</template> </template>
<template x-if="item.status.startsWith('restarting')"> <template x-if="item.status.startsWith('restarting')">
<div title="restarting" class="bg-warning badge badge-absolute"></div> <div title="restarting" class="bg-warning badge-dashboard"></div>
</template> </template>
<template x-if="item.status.startsWith('degraded')"> <template x-if="item.status.startsWith('degraded')">
<div title="degraded" class="bg-warning badge badge-absolute"></div> <div title="degraded" class="bg-warning badge-dashboard"></div>
</template> </template>
</div> </div>
<div class="max-w-full px-4 truncate box-description" x-text="item.description"></div> <div class="max-w-full px-4 truncate box-description" x-text="item.description"></div>
@@ -113,16 +113,16 @@
<div class="pb-2 truncate box-title" x-text="item.name"></div> <div class="pb-2 truncate box-title" x-text="item.name"></div>
<div class="flex-1"></div> <div class="flex-1"></div>
<template x-if="item.status.startsWith('running')"> <template x-if="item.status.startsWith('running')">
<div title="running" class="bg-success badge badge-absolute"></div> <div title="running" class="bg-success badge-dashboard"></div>
</template> </template>
<template x-if="item.status.startsWith('exited')"> <template x-if="item.status.startsWith('exited')">
<div title="exited" class="bg-error badge badge-absolute"></div> <div title="exited" class="bg-error badge-dashboard"></div>
</template> </template>
<template x-if="item.status.startsWith('restarting')"> <template x-if="item.status.startsWith('restarting')">
<div title="restarting" class="bg-warning badge badge-absolute"></div> <div title="restarting" class="bg-warning badge-dashboard"></div>
</template> </template>
<template x-if="item.status.startsWith('degraded')"> <template x-if="item.status.startsWith('degraded')">
<div title="degraded" class="bg-warning badge badge-absolute"></div> <div title="degraded" class="bg-warning badge-dashboard"></div>
</template> </template>
</div> </div>
<div class="max-w-full px-4 truncate box-description" x-text="item.description"></div> <div class="max-w-full px-4 truncate box-description" x-text="item.description"></div>
@@ -159,16 +159,16 @@
<div class="pb-2 truncate box-title" x-text="item.name"></div> <div class="pb-2 truncate box-title" x-text="item.name"></div>
<div class="flex-1"></div> <div class="flex-1"></div>
<template x-if="item.status.startsWith('running')"> <template x-if="item.status.startsWith('running')">
<div title="running" class="bg-success badge badge-absolute"></div> <div title="running" class="bg-success badge-dashboard"></div>
</template> </template>
<template x-if="item.status.startsWith('exited')"> <template x-if="item.status.startsWith('exited')">
<div title="exited" class="bg-error badge badge-absolute"></div> <div title="exited" class="bg-error badge-dashboard"></div>
</template> </template>
<template x-if="item.status.startsWith('restarting')"> <template x-if="item.status.startsWith('restarting')">
<div title="restarting" class="bg-warning badge badge-absolute"></div> <div title="restarting" class="bg-warning badge-dashboard"></div>
</template> </template>
<template x-if="item.status.startsWith('degraded')"> <template x-if="item.status.startsWith('degraded')">
<div title="degraded" class="bg-warning badge badge-absolute"></div> <div title="degraded" class="bg-warning badge-dashboard"></div>
</template> </template>
</div> </div>
<div class="max-w-full px-4 truncate box-description" x-text="item.description"></div> <div class="max-w-full px-4 truncate box-description" x-text="item.description"></div>

View File

@@ -2,7 +2,7 @@
<x-slot:title> <x-slot:title>
{{ data_get_str($service, 'name')->limit(10) }} > Configuration | Coolify {{ data_get_str($service, 'name')->limit(10) }} > Configuration | Coolify
</x-slot> </x-slot>
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" /> <livewire:project.service.heading :service="$service" :parameters="$parameters" :query="$query" />
<div class="flex flex-col h-full gap-8 pt-6 sm:flex-row"> <div class="flex flex-col h-full gap-8 pt-6 sm:flex-row">
<div class="flex flex-col items-start gap-2 min-w-fit"> <div class="flex flex-col items-start gap-2 min-w-fit">
@@ -36,7 +36,7 @@
@if ($currentRoute === 'project.service.configuration') @if ($currentRoute === 'project.service.configuration')
<livewire:project.service.stack-form :service="$service" /> <livewire:project.service.stack-form :service="$service" />
<h3>Services</h3> <h3>Services</h3>
<div class="grid grid-cols-1 gap-2 pt-4 xl:grid-cols-1" wire:poll.10000ms="check_status"> <div class="grid grid-cols-1 gap-2 pt-4 xl:grid-cols-1">
@foreach ($applications as $application) @foreach ($applications as $application)
<div @class([ <div @class([
'border-l border-dashed border-red-500' => str( 'border-l border-dashed border-red-500' => str(

View File

@@ -1,4 +1,4 @@
<div> <div wire:poll.10000ms="checkStatus">
<livewire:project.shared.configuration-checker :resource="$service" /> <livewire:project.shared.configuration-checker :resource="$service" />
<x-slide-over @startservice.window="slideOverOpen = true" closeWithX fullScreen> <x-slide-over @startservice.window="slideOverOpen = true" closeWithX fullScreen>
<x-slot:title>Service Startup</x-slot:title> <x-slot:title>Service Startup</x-slot:title>
@@ -40,8 +40,7 @@
</x-forms.button> </x-forms.button>
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" submitAction="stop" <x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" submitAction="stop"
:checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]" :confirmWithText="false" :confirmWithPassword="false" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]" :confirmWithText="false" :confirmWithPassword="false"
step1ButtonText="Continue" step2ButtonText="Stop Service" :dispatchEvent="true" step1ButtonText="Continue" step2ButtonText="Stop Service">
dispatchEventType="stopEvent">
<x-slot:button-title> <x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
@@ -69,8 +68,7 @@
</x-forms.button> </x-forms.button>
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" submitAction="stop" <x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" submitAction="stop"
:checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]" :confirmWithText="false" :confirmWithPassword="false" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]" :confirmWithText="false" :confirmWithPassword="false"
step1ButtonText="Continue" step2ButtonText="Stop Service" :dispatchEvent="true" step1ButtonText="Continue" step2ButtonText="Stop Service">
dispatchEventType="stopEvent">
<x-slot:button-title> <x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
@@ -98,8 +96,7 @@
@else @else
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" submitAction="stop" <x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" submitAction="stop"
:checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]" :confirmWithText="false" :confirmWithPassword="false" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]" :confirmWithText="false" :confirmWithPassword="false"
step1ButtonText="Continue" step2ButtonText="Stop Service" :dispatchEvent="true" step1ButtonText="Continue" step2ButtonText="Stop Service">
dispatchEventType="stopEvent">
<x-slot:button-title> <x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"

View File

@@ -1,5 +1,5 @@
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }"> <div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }">
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" /> <livewire:project.service.heading :service="$service" :parameters="$parameters" :query="$query" />
<div class="flex flex-col h-full gap-8 pt-6 sm:flex-row"> <div class="flex flex-col h-full gap-8 pt-6 sm:flex-row">
<div class="flex flex-col items-start gap-2 min-w-fit"> <div class="flex flex-col items-start gap-2 min-w-fit">
<a class="menu-item" <a class="menu-item"

View File

@@ -12,28 +12,31 @@
<livewire:project.database.heading :database="$resource" /> <livewire:project.database.heading :database="$resource" />
@elseif ($type === 'service') @elseif ($type === 'service')
<livewire:project.shared.configuration-checker :resource="$resource" /> <livewire:project.shared.configuration-checker :resource="$resource" />
<livewire:project.service.navbar :service="$resource" :parameters="$parameters" title="Terminal" /> <livewire:project.service.heading :service="$resource" :parameters="$parameters" title="Terminal" />
@elseif ($type === 'server') @elseif ($type === 'server')
<x-server.navbar :server="$server" :parameters="$parameters" /> <x-server.navbar :server="$server" :parameters="$parameters" />
@endif @endif
@if(!$hasShell) @if (!$hasShell)
<div class="flex items-center justify-center w-full py-4 mx-auto"> <div class="flex items-center justify-center w-full py-4 mx-auto">
<div class="p-4 w-full rounded-sm border dark:bg-coolgray-100 dark:border-coolgray-300"> <div class="p-4 w-full rounded-sm border dark:bg-coolgray-100 dark:border-coolgray-300">
<div class="flex flex-col items-center justify-center space-y-4"> <div class="flex flex-col items-center justify-center space-y-4">
<svg class="w-12 h-12 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-12 h-12 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg> </svg>
<div class="text-center"> <div class="text-center">
<h3 class="text-lg font-medium">Terminal Not Available</h3> <h3 class="text-lg font-medium">Terminal Not Available</h3>
<p class="mt-2 text-sm text-gray-500">No shell (bash/sh) is available in this container. Please ensure either bash or sh is installed to use the terminal.</p> <p class="mt-2 text-sm text-gray-500">No shell (bash/sh) is available in this container. Please
ensure either bash or sh is installed to use the terminal.</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@else @else
@if ($type === 'server') @if ($type === 'server')
<form class="w-full" wire:submit="$dispatchSelf('connectToServer')" wire:init="$dispatchSelf('connectToServer')"> <form class="w-full" wire:submit="$dispatchSelf('connectToServer')"
wire:init="$dispatchSelf('connectToServer')">
<x-forms.button class="w-full" type="submit">Reconnect</x-forms.button> <x-forms.button class="w-full" type="submit">Reconnect</x-forms.button>
</form> </form>
<div class="mx-auto w-full"> <div class="mx-auto w-full">

View File

@@ -6,7 +6,7 @@
<h1>Scheduled Task</h1> <h1>Scheduled Task</h1>
<livewire:project.application.heading :application="$resource" /> <livewire:project.application.heading :application="$resource" />
@elseif ($type === 'service') @elseif ($type === 'service')
<livewire:project.service.navbar :service="$resource" :parameters="$parameters" /> <livewire:project.service.heading :service="$resource" :parameters="$parameters" />
@endif @endif
<form wire:submit="submit" class="w-full"> <form wire:submit="submit" class="w-full">