diff --git a/app/Actions/Service/StopService.php b/app/Actions/Service/StopService.php index 6c0990331..d69898a58 100644 --- a/app/Actions/Service/StopService.php +++ b/app/Actions/Service/StopService.php @@ -17,7 +17,6 @@ class StopService if (!$server->isFunctional()) { return 'Server is not functional'; } - ray('Stopping service: ' . $service->name); $containersToStop = $service->getContainersToStop(); $service->stopContainers($containersToStop, $server); diff --git a/app/Jobs/DeleteResourceJob.php b/app/Jobs/DeleteResourceJob.php index 1ebbf4681..8f7dd806a 100644 --- a/app/Jobs/DeleteResourceJob.php +++ b/app/Jobs/DeleteResourceJob.php @@ -92,6 +92,9 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue throw $e; } finally { $this->resource->forceDelete(); + if ($this->dockerCleanup) { + CleanupDocker::run($server, true); + } Artisan::queue('cleanup:stucked-resources'); } } diff --git a/app/Livewire/NavbarDeleteTeam.php b/app/Livewire/NavbarDeleteTeam.php index ec196c154..6b3ea3fd4 100644 --- a/app/Livewire/NavbarDeleteTeam.php +++ b/app/Livewire/NavbarDeleteTeam.php @@ -4,11 +4,25 @@ namespace App\Livewire; use Illuminate\Support\Facades\DB; use Livewire\Component; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Hash; class NavbarDeleteTeam extends Component { - public function delete() + public $team; + + public function mount() { + $this->team = currentTeam()->name; + } + + public function delete($password) + { + if (!Hash::check($password, Auth::user()->password)) { + $this->addError('password', 'The provided password is incorrect.'); + return; + } + $currentTeam = currentTeam(); $currentTeam->delete(); diff --git a/app/Livewire/Project/Application/Previews.php b/app/Livewire/Project/Application/Previews.php index 30bc0a9d1..78fab7e44 100644 --- a/app/Livewire/Project/Application/Previews.php +++ b/app/Livewire/Project/Application/Previews.php @@ -9,6 +9,8 @@ use Illuminate\Support\Collection; use Livewire\Component; use Spatie\Url\Url; use Visus\Cuid2\Cuid2; +use Illuminate\Process\InvokedProcess; +use Illuminate\Support\Facades\Process; class Previews extends Component { @@ -177,17 +179,20 @@ class Previews extends Component public function stop(int $pull_request_id) { try { + $server = $this->application->destination->server; + $timeout = 300; + if ($this->application->destination->server->isSwarm()) { - instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $this->application->destination->server); + instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $server); } else { - $containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id, $pull_request_id); - foreach ($containers as $container) { - $name = str_replace('/', '', $container['Names']); - instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false); - } + $containers = getCurrentApplicationContainerStatus($server, $this->application->id, $pull_request_id)->toArray(); + $this->stopContainers($containers, $server, $timeout); } - GetContainersStatus::dispatchSync($this->application->destination->server)->onQueue('high'); - $this->dispatch('reloadWindow'); + + GetContainersStatus::run($server); + $this->application->refresh(); + $this->dispatch('containerStatusUpdated'); + $this->dispatch('success', 'Preview Deployment stopped.'); } catch (\Throwable $e) { return handleError($e, $this); } @@ -196,16 +201,21 @@ class Previews extends Component public function delete(int $pull_request_id) { try { + $server = $this->application->destination->server; + $timeout = 300; + if ($this->application->destination->server->isSwarm()) { - instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $this->application->destination->server); + instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $server); } else { - $containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id, $pull_request_id); - foreach ($containers as $container) { - $name = str_replace('/', '', $container['Names']); - instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false); - } + $containers = getCurrentApplicationContainerStatus($server, $this->application->id, $pull_request_id)->toArray(); + $this->stopContainers($containers, $server, $timeout); } - ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first()->delete(); + + ApplicationPreview::where('application_id', $this->application->id) + ->where('pull_request_id', $pull_request_id) + ->first() + ->delete(); + $this->application->refresh(); $this->dispatch('update_links'); $this->dispatch('success', 'Preview deleted.'); @@ -213,4 +223,49 @@ class Previews extends Component return handleError($e, $this); } } + + private function stopContainers(array $containers, $server, int $timeout) + { + $processes = []; + foreach ($containers as $container) { + $containerName = str_replace('/', '', $container['Names']); + $processes[$containerName] = $this->stopContainer($containerName, $timeout); + } + + $startTime = time(); + while (count($processes) > 0) { + $finishedProcesses = array_filter($processes, function ($process) { + return !$process->running(); + }); + foreach (array_keys($finishedProcesses) as $containerName) { + unset($processes[$containerName]); + $this->removeContainer($containerName, $server); + } + + if (time() - $startTime >= $timeout) { + $this->forceStopRemainingContainers(array_keys($processes), $server); + break; + } + + usleep(100000); + } + } + + private function stopContainer(string $containerName, int $timeout): InvokedProcess + { + return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName"); + } + + private function removeContainer(string $containerName, $server) + { + instant_remote_process(["docker rm -f $containerName"], $server, throwError: false); + } + + private function forceStopRemainingContainers(array $containerNames, $server) + { + foreach ($containerNames as $containerName) { + instant_remote_process(["docker kill $containerName"], $server, throwError: false); + $this->removeContainer($containerName, $server); + } + } } diff --git a/app/Livewire/Project/Service/FileStorage.php b/app/Livewire/Project/Service/FileStorage.php index 2d9c95daa..9f0d94aad 100644 --- a/app/Livewire/Project/Service/FileStorage.php +++ b/app/Livewire/Project/Service/FileStorage.php @@ -15,6 +15,8 @@ use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; use Livewire\Component; +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\Auth; class FileStorage extends Component { @@ -77,8 +79,13 @@ class FileStorage extends Component } } - public function delete() + public function delete($password) { + if (!Hash::check($password, Auth::user()->password)) { + $this->addError('password', 'The provided password is incorrect.'); + return; + } + try { $message = 'File deleted.'; if ($this->fileStorage->is_directory) { @@ -121,8 +128,15 @@ class FileStorage extends Component $this->submit(); } - public function render() + public function render() { - return view('livewire.project.service.file-storage'); + return view('livewire.project.service.file-storage', [ + 'directoryDeletionCheckboxes' => [ + ['id' => 'permanently_delete', 'label' => 'The selected directory and all its contents will be permantely deleted form the server.'], + ], + 'fileDeletionCheckboxes' => [ + ['id' => 'permanently_delete', 'label' => 'The selected file will be permanently deleted form the server.'], + ] + ]); } } diff --git a/app/Livewire/Project/Service/ServiceApplicationView.php b/app/Livewire/Project/Service/ServiceApplicationView.php index e7d00c3dd..feb6e7982 100644 --- a/app/Livewire/Project/Service/ServiceApplicationView.php +++ b/app/Livewire/Project/Service/ServiceApplicationView.php @@ -4,6 +4,8 @@ namespace App\Livewire\Project\Service; use App\Models\ServiceApplication; use Livewire\Component; +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\Auth; class ServiceApplicationView extends Component { @@ -11,6 +13,10 @@ class ServiceApplicationView extends Component public $parameters; + public $docker_cleanup = true; + public $delete_volumes = true; + + protected $rules = [ 'application.human_name' => 'nullable', 'application.description' => 'nullable', @@ -23,12 +29,7 @@ class ServiceApplicationView extends Component 'application.is_stripprefix_enabled' => 'nullable|boolean', ]; - public function render() - { - return view('livewire.project.service.service-application-view'); - } - - public function updatedApplicationFqdn() + public function updatedApplicationFqdn() { $this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim(); $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim(); @@ -56,8 +57,13 @@ class ServiceApplicationView extends Component $this->dispatch('success', 'You need to restart the service for the changes to take effect.'); } - public function delete() + public function delete($password) { + if (!Hash::check($password, Auth::user()->password)) { + $this->addError('password', 'The provided password is incorrect.'); + return; + } + try { $this->application->delete(); $this->dispatch('success', 'Application deleted.'); @@ -91,4 +97,17 @@ class ServiceApplicationView extends Component $this->dispatch('generateDockerCompose'); } } + + public function render() + { + return view('livewire.project.service.service-application-view', [ + 'checkboxes' => [ + ['id' => 'delete_volumes', 'label' => 'All associated volumes with this resource will be permanently deleted'], + ['id' => 'docker_cleanup', 'label' => 'Docker cleanup will be run on the server which removes builder cache and unused images'], + // ['id' => 'delete_associated_backups_locally', 'label' => 'All backups associated with this Ressource will be permanently deleted from local storage.'], + // ['id' => 'delete_associated_backups_s3', 'label' => 'All backups associated with this Ressource will be permanently deleted from the selected S3 Storage.'], + // ['id' => 'delete_associated_backups_sftp', 'label' => 'All backups associated with this Ressource will be permanently deleted from the selected SFTP Storage.'] + ] + ]); + } } diff --git a/app/Livewire/Project/Shared/Destination.php b/app/Livewire/Project/Shared/Destination.php index a2c018beb..1be724568 100644 --- a/app/Livewire/Project/Shared/Destination.php +++ b/app/Livewire/Project/Shared/Destination.php @@ -10,6 +10,8 @@ use App\Models\Server; use App\Models\StandaloneDocker; use Livewire\Component; use Visus\Cuid2\Cuid2; +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\Auth; class Destination extends Component { @@ -115,8 +117,13 @@ class Destination extends Component ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id')); } - public function removeServer(int $network_id, int $server_id) + public function removeServer(int $network_id, int $server_id, $password) { + if (!Hash::check($password, Auth::user()->password)) { + $this->addError('password', 'The provided password is incorrect.'); + return; + } + if ($this->resource->destination->server->id == $server_id && $this->resource->destination->id == $network_id) { $this->dispatch('error', 'You cannot remove this destination server.', 'You are trying to remove the main server.'); diff --git a/app/Livewire/Project/Shared/Storages/Show.php b/app/Livewire/Project/Shared/Storages/Show.php index d350df212..a0cb54aa0 100644 --- a/app/Livewire/Project/Shared/Storages/Show.php +++ b/app/Livewire/Project/Shared/Storages/Show.php @@ -45,7 +45,6 @@ class Show extends Component return; } - // Test deletion in more detail $this->storage->delete(); $this->dispatch('refreshStorages'); } diff --git a/resources/views/livewire/navbar-delete-team.blade.php b/resources/views/livewire/navbar-delete-team.blade.php index b660a0dd4..39f23abea 100644 --- a/resources/views/livewire/navbar-delete-team.blade.php +++ b/resources/views/livewire/navbar-delete-team.blade.php @@ -1,5 +1,13 @@