Feat: more conformations and fixes

This commit is contained in:
ayntk-ai
2024-09-05 17:54:32 +02:00
parent 08df814408
commit fc3c69f687
12 changed files with 174 additions and 48 deletions

View File

@@ -17,7 +17,6 @@ class StopService
if (!$server->isFunctional()) { if (!$server->isFunctional()) {
return 'Server is not functional'; return 'Server is not functional';
} }
ray('Stopping service: ' . $service->name);
$containersToStop = $service->getContainersToStop(); $containersToStop = $service->getContainersToStop();
$service->stopContainers($containersToStop, $server); $service->stopContainers($containersToStop, $server);

View File

@@ -92,6 +92,9 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
throw $e; throw $e;
} finally { } finally {
$this->resource->forceDelete(); $this->resource->forceDelete();
if ($this->dockerCleanup) {
CleanupDocker::run($server, true);
}
Artisan::queue('cleanup:stucked-resources'); Artisan::queue('cleanup:stucked-resources');
} }
} }

View File

@@ -4,11 +4,25 @@ namespace App\Livewire;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Livewire\Component; use Livewire\Component;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
class NavbarDeleteTeam extends Component 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 = currentTeam();
$currentTeam->delete(); $currentTeam->delete();

View File

@@ -9,6 +9,8 @@ use Illuminate\Support\Collection;
use Livewire\Component; use Livewire\Component;
use Spatie\Url\Url; use Spatie\Url\Url;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
use Illuminate\Process\InvokedProcess;
use Illuminate\Support\Facades\Process;
class Previews extends Component class Previews extends Component
{ {
@@ -177,17 +179,20 @@ class Previews extends Component
public function stop(int $pull_request_id) public function stop(int $pull_request_id)
{ {
try { try {
$server = $this->application->destination->server;
$timeout = 300;
if ($this->application->destination->server->isSwarm()) { 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 { } else {
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id, $pull_request_id); $containers = getCurrentApplicationContainerStatus($server, $this->application->id, $pull_request_id)->toArray();
foreach ($containers as $container) { $this->stopContainers($containers, $server, $timeout);
$name = str_replace('/', '', $container['Names']);
instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false);
}
} }
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) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }
@@ -196,16 +201,21 @@ class Previews extends Component
public function delete(int $pull_request_id) public function delete(int $pull_request_id)
{ {
try { try {
$server = $this->application->destination->server;
$timeout = 300;
if ($this->application->destination->server->isSwarm()) { 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 { } else {
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id, $pull_request_id); $containers = getCurrentApplicationContainerStatus($server, $this->application->id, $pull_request_id)->toArray();
foreach ($containers as $container) { $this->stopContainers($containers, $server, $timeout);
$name = str_replace('/', '', $container['Names']);
instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false);
}
} }
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->application->refresh();
$this->dispatch('update_links'); $this->dispatch('update_links');
$this->dispatch('success', 'Preview deleted.'); $this->dispatch('success', 'Preview deleted.');
@@ -213,4 +223,49 @@ class Previews extends Component
return handleError($e, $this); 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);
}
}
} }

View File

@@ -15,6 +15,8 @@ use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql; use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis; use App\Models\StandaloneRedis;
use Livewire\Component; use Livewire\Component;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Auth;
class FileStorage extends Component 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 { try {
$message = 'File deleted.'; $message = 'File deleted.';
if ($this->fileStorage->is_directory) { if ($this->fileStorage->is_directory) {
@@ -121,8 +128,15 @@ class FileStorage extends Component
$this->submit(); $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.'],
]
]);
} }
} }

View File

@@ -4,6 +4,8 @@ namespace App\Livewire\Project\Service;
use App\Models\ServiceApplication; use App\Models\ServiceApplication;
use Livewire\Component; use Livewire\Component;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Auth;
class ServiceApplicationView extends Component class ServiceApplicationView extends Component
{ {
@@ -11,6 +13,10 @@ class ServiceApplicationView extends Component
public $parameters; public $parameters;
public $docker_cleanup = true;
public $delete_volumes = true;
protected $rules = [ protected $rules = [
'application.human_name' => 'nullable', 'application.human_name' => 'nullable',
'application.description' => 'nullable', 'application.description' => 'nullable',
@@ -23,12 +29,7 @@ class ServiceApplicationView extends Component
'application.is_stripprefix_enabled' => 'nullable|boolean', 'application.is_stripprefix_enabled' => 'nullable|boolean',
]; ];
public function render() public function updatedApplicationFqdn()
{
return view('livewire.project.service.service-application-view');
}
public function updatedApplicationFqdn()
{ {
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim(); $this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->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.'); $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 { try {
$this->application->delete(); $this->application->delete();
$this->dispatch('success', 'Application deleted.'); $this->dispatch('success', 'Application deleted.');
@@ -91,4 +97,17 @@ class ServiceApplicationView extends Component
$this->dispatch('generateDockerCompose'); $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.']
]
]);
}
} }

View File

@@ -10,6 +10,8 @@ use App\Models\Server;
use App\Models\StandaloneDocker; use App\Models\StandaloneDocker;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Auth;
class Destination extends Component class Destination extends Component
{ {
@@ -115,8 +117,13 @@ class Destination extends Component
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id')); 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) { 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.'); $this->dispatch('error', 'You cannot remove this destination server.', 'You are trying to remove the main server.');

View File

@@ -45,7 +45,6 @@ class Show extends Component
return; return;
} }
// Test deletion in more detail
$this->storage->delete(); $this->storage->delete();
$this->dispatch('refreshStorages'); $this->dispatch('refreshStorages');
} }

View File

@@ -1,5 +1,13 @@
<div> <div>
<x-modal-confirmation buttonFullWidth isErrorButton buttonTitle="Delete Team"> <x-modal-confirmation
This team be deleted. It is not reversible. <br>Please think again. title="Confirm Team Deletion?"
</x-modal-confirmation> buttonTitle="Delete Team"
isErrorButton
submitAction="delete"
:actions="['The current Team will be permanently deleted.']"
confirmationText="{{ $team }}"
confirmationLabel="Please confirm the execution of the actions by entering the Team Name below"
shortConfirmationLabel="Team Name"
step3ButtonText="Permanently Delete Team"
/>
</div> </div>

View File

@@ -160,8 +160,6 @@
:confirmWithText="false" :confirmWithText="false"
:confirmWithPassword="false" :confirmWithPassword="false"
step2ButtonText="Stop Preview Deployment" step2ButtonText="Stop Preview Deployment"
:dispatchEvent="true"
dispatchEventType="stopEvent"
> >
<x-slot:customButton> <x-slot:customButton>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error"
@@ -189,9 +187,6 @@
confirmationLabel="Please confirm the execution of the actions by entering the Preview Deployment name below" confirmationLabel="Please confirm the execution of the actions by entering the Preview Deployment name below"
shortConfirmationLabel="Preview Deployment Name" shortConfirmationLabel="Preview Deployment Name"
:confirmWithPassword="false" :confirmWithPassword="false"
:dispatchEvent="true"
dispatchEventType="deleteEvent"
dispatchEventMessage="Preview Deployment Deleted"
/> />
</div> </div>
</div> </div>

View File

@@ -7,12 +7,20 @@
<h2>{{ Str::headline($application->name) }}</h2> <h2>{{ Str::headline($application->name) }}</h2>
@endif @endif
<x-forms.button type="submit">Save</x-forms.button> <x-forms.button type="submit">Save</x-forms.button>
<x-modal-confirmation isErrorButton> <x-modal-confirmation
<x-slot:button-title> title="Confirm Service Application Deletion?"
Delete buttonTitle="Delete"
</x-slot:button-title> isErrorButton
This will delete this service application. It is not reversible.<br>Please think again. submitAction="delete"
</x-modal-confirmation> {{-- :checkboxes="$checkboxes" --}}
:actions="[
'The selected service application container will be stopped and permanently deleted.'
]"
confirmationText="{{ Str::headline($application->name) }}"
confirmationLabel="Please confirm the execution of the actions by entering the Service Application Name below"
shortConfirmationLabel="Service Application Name"
step3ButtonText="Permanently Delete Service Application"
/>
</div> </div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div class="flex gap-2"> <div class="flex gap-2">

View File

@@ -66,11 +66,16 @@
wire:click="stop('{{ data_get($destination, 'server.id') }}')">Stop</x-forms.button> wire:click="stop('{{ data_get($destination, 'server.id') }}')">Stop</x-forms.button>
@endif @endif
<x-modal-confirmation <x-modal-confirmation
action="removeServer({{ data_get($destination, 'id') }},{{ data_get($destination, 'server.id') }})" title="Confirm server removal?"
isErrorButton buttonTitle="Remove Server"> isErrorButton
This will stop the running application in this server and remove it as a deployment buttonTitle="Remove Server"
destination.<br><br>Please think again. submitAction="removeServer({{ data_get($destination, 'id') }},{{ data_get($destination, 'server.id') }})"
</x-modal-confirmation> :actions="['This will stop the all running applications on this server and remove it as a deployment destination.']"
confirmationText="{{ data_get($destination, 'server.name') }}"
confirmationLabel="Please confirm the execution of the actions by entering the Server Name below"
shortConfirmationLabel="Server Name"
step3ButtonText="Permanently Remove Server"
/>
</div> </div>
</div> </div>
@endforeach @endforeach