Merge branch 'next' into fix-ssh-keys
This commit is contained in:
@@ -3,50 +3,40 @@
|
||||
namespace App\Actions\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Actions\Server\CleanupDocker;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StopApplication
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(Application $application, bool $previewDeployments = false)
|
||||
public function handle(Application $application, bool $previewDeployments = false, bool $dockerCleanup = true)
|
||||
{
|
||||
if ($application->destination->server->isSwarm()) {
|
||||
instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$servers = collect([]);
|
||||
$servers->push($application->destination->server);
|
||||
$application->additional_servers->map(function ($server) use ($servers) {
|
||||
$servers->push($server);
|
||||
});
|
||||
foreach ($servers as $server) {
|
||||
if (! $server->isFunctional()) {
|
||||
try {
|
||||
$server = $application->destination->server;
|
||||
if (!$server->isFunctional()) {
|
||||
return 'Server is not functional';
|
||||
}
|
||||
if ($previewDeployments) {
|
||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, includePullrequests: true);
|
||||
} else {
|
||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
||||
}
|
||||
if ($containers->count() > 0) {
|
||||
foreach ($containers as $container) {
|
||||
$containerName = data_get($container, 'Names');
|
||||
if ($containerName) {
|
||||
instant_remote_process(command: ["docker stop --time=30 $containerName"], server: $server, throwError: false);
|
||||
instant_remote_process(command: ["docker rm $containerName"], server: $server, throwError: false);
|
||||
instant_remote_process(command: ["docker rm -f {$containerName}"], server: $server, throwError: false);
|
||||
}
|
||||
}
|
||||
ray('Stopping application: ' . $application->name);
|
||||
|
||||
if ($server->isSwarm()) {
|
||||
instant_remote_process(["docker stack rm {$application->uuid}"], $server);
|
||||
return;
|
||||
}
|
||||
|
||||
$containersToStop = $application->getContainersToStop($previewDeployments);
|
||||
$application->stopContainers($containersToStop, $server);
|
||||
|
||||
if ($application->build_pack === 'dockercompose') {
|
||||
// remove network
|
||||
$uuid = $application->uuid;
|
||||
instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
|
||||
instant_remote_process(["docker network rm {$uuid}"], $server, false);
|
||||
$application->delete_connected_networks($application->uuid);
|
||||
}
|
||||
|
||||
if ($dockerCleanup) {
|
||||
CleanupDocker::run($server, true);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
ray($e->getMessage());
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Actions\Server\CleanupDocker;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use App\Models\StandaloneKeydb;
|
||||
@@ -10,25 +11,65 @@ use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StopDatabase
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||
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';
|
||||
}
|
||||
|
||||
instant_remote_process(command: ["docker stop --time=30 $database->uuid"], server: $server, throwError: false);
|
||||
instant_remote_process(command: ["docker rm $database->uuid"], server: $server, throwError: false);
|
||||
instant_remote_process(command: ["docker rm -f $database->uuid"], server: $server, throwError: false);
|
||||
$this->stopContainer($database, $database->uuid, 300);
|
||||
if (! $isDeleteOperation) {
|
||||
if ($dockerCleanup) {
|
||||
CleanupDocker::run($server, true);
|
||||
}
|
||||
}
|
||||
|
||||
if ($database->is_public) {
|
||||
StopDatabaseProxy::run($database);
|
||||
}
|
||||
|
||||
return 'Database stopped successfully';
|
||||
}
|
||||
|
||||
private function stopContainer($database, string $containerName, int $timeout = 300): void
|
||||
{
|
||||
$server = $database->destination->server;
|
||||
|
||||
$process = Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
|
||||
|
||||
$startTime = time();
|
||||
while ($process->running()) {
|
||||
if (time() - $startTime >= $timeout) {
|
||||
$this->forceStopContainer($containerName, $server);
|
||||
break;
|
||||
}
|
||||
usleep(100000);
|
||||
}
|
||||
|
||||
$this->removeContainer($containerName, $server);
|
||||
}
|
||||
|
||||
private function forceStopContainer(string $containerName, $server): void
|
||||
{
|
||||
instant_remote_process(command: ["docker kill $containerName"], server: $server, throwError: false);
|
||||
}
|
||||
|
||||
private function removeContainer(string $containerName, $server): void
|
||||
{
|
||||
instant_remote_process(command: ["docker rm -f $containerName"], server: $server, throwError: false);
|
||||
}
|
||||
|
||||
private function deleteConnectedNetworks($uuid, $server)
|
||||
{
|
||||
instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
|
||||
instant_remote_process(["docker network rm {$uuid}"], $server, false);
|
||||
}
|
||||
}
|
||||
|
@@ -3,17 +3,18 @@
|
||||
namespace App\Actions\Service;
|
||||
|
||||
use App\Models\Service;
|
||||
use App\Actions\Server\CleanupDocker;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class DeleteService
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(Service $service)
|
||||
public function handle(Service $service, bool $deleteConfigurations, bool $deleteVolumes, bool $dockerCleanup, bool $deleteConnectedNetworks)
|
||||
{
|
||||
try {
|
||||
$server = data_get($service, 'server');
|
||||
if ($server->isFunctional()) {
|
||||
if ($deleteVolumes && $server->isFunctional()) {
|
||||
$storagesToDelete = collect([]);
|
||||
|
||||
$service->environment_variables()->delete();
|
||||
@@ -33,13 +34,29 @@ class DeleteService
|
||||
foreach ($storagesToDelete as $storage) {
|
||||
$commands[] = "docker volume rm -f $storage->name";
|
||||
}
|
||||
$commands[] = "docker rm -f $service->uuid";
|
||||
|
||||
instant_remote_process($commands, $server, false);
|
||||
// Execute volume deletion first, this must be done first otherwise volumes will not be deleted.
|
||||
if (!empty($commands)) {
|
||||
foreach ($commands as $command) {
|
||||
$result = instant_remote_process([$command], $server, false);
|
||||
if ($result !== 0) {
|
||||
ray("Failed to execute: $command");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($deleteConnectedNetworks) {
|
||||
$service->delete_connected_networks($service->uuid);
|
||||
}
|
||||
|
||||
instant_remote_process(["docker rm -f $service->uuid"], $server, throwError: false);
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception($e->getMessage());
|
||||
} finally {
|
||||
if ($deleteConfigurations) {
|
||||
$service->delete_configurations();
|
||||
}
|
||||
foreach ($service->applications()->get() as $application) {
|
||||
$application->forceDelete();
|
||||
}
|
||||
@@ -50,6 +67,11 @@ class DeleteService
|
||||
$task->delete();
|
||||
}
|
||||
$service->tags()->detach();
|
||||
$service->forceDelete();
|
||||
|
||||
if ($dockerCleanup) {
|
||||
CleanupDocker::run($server, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,46 +3,33 @@
|
||||
namespace App\Actions\Service;
|
||||
|
||||
use App\Models\Service;
|
||||
use App\Actions\Server\CleanupDocker;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StopService
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(Service $service)
|
||||
public function handle(Service $service, bool $isDeleteOperation = false, bool $dockerCleanup = true)
|
||||
{
|
||||
try {
|
||||
$server = $service->destination->server;
|
||||
if (! $server->isFunctional()) {
|
||||
if (!$server->isFunctional()) {
|
||||
return 'Server is not functional';
|
||||
}
|
||||
ray('Stopping service: '.$service->name);
|
||||
$applications = $service->applications()->get();
|
||||
foreach ($applications as $application) {
|
||||
if ($applications->count() < 6) {
|
||||
instant_remote_process(command: ["docker stop --time=10 {$application->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||
}
|
||||
instant_remote_process(command: ["docker rm {$application->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||
instant_remote_process(command: ["docker rm -f {$application->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||
$application->update(['status' => 'exited']);
|
||||
}
|
||||
$dbs = $service->databases()->get();
|
||||
foreach ($dbs as $db) {
|
||||
if ($dbs->count() < 6) {
|
||||
|
||||
instant_remote_process(command: ["docker stop --time=10 {$db->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||
$containersToStop = $service->getContainersToStop();
|
||||
$service->stopContainers($containersToStop, $server);
|
||||
|
||||
if (!$isDeleteOperation) {
|
||||
$service->delete_connected_networks($service->uuid);
|
||||
if ($dockerCleanup) {
|
||||
CleanupDocker::run($server, true);
|
||||
}
|
||||
instant_remote_process(command: ["docker rm {$db->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||
instant_remote_process(command: ["docker rm -f {$db->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||
$db->update(['status' => 'exited']);
|
||||
}
|
||||
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy"], $service->server);
|
||||
instant_remote_process(["docker network rm {$service->uuid}"], $service->server);
|
||||
} catch (\Exception $e) {
|
||||
ray($e->getMessage());
|
||||
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -29,6 +29,7 @@ use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Sleep;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Throwable;
|
||||
@@ -2200,20 +2201,40 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
$this->application_deployment_queue->addLogEntry('Building docker image completed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $timeout in seconds
|
||||
*/
|
||||
private function graceful_shutdown_container(string $containerName, int $timeout = 30)
|
||||
private function graceful_shutdown_container(string $containerName, int $timeout = 300)
|
||||
{
|
||||
try {
|
||||
$this->execute_remote_command(
|
||||
["docker stop --time=$timeout $containerName", 'hidden' => true, 'ignore_errors' => true],
|
||||
["docker rm $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||
);
|
||||
$process = Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
|
||||
|
||||
$startTime = time();
|
||||
while ($process->running()) {
|
||||
if (time() - $startTime >= $timeout) {
|
||||
$this->execute_remote_command(
|
||||
["docker kill $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||
);
|
||||
break;
|
||||
}
|
||||
usleep(100000);
|
||||
}
|
||||
|
||||
$isRunning = $this->execute_remote_command(
|
||||
["docker inspect -f '{{.State.Running}}' $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||
) === 'true';
|
||||
|
||||
if ($isRunning) {
|
||||
$this->execute_remote_command(
|
||||
["docker kill $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||
);
|
||||
}
|
||||
} catch (\Exception $error) {
|
||||
// report error if needed
|
||||
$this->application_deployment_queue->addLogEntry("Error stopping container $containerName: " . $error->getMessage(), 'stderr');
|
||||
}
|
||||
|
||||
$this->remove_container($containerName);
|
||||
}
|
||||
|
||||
private function remove_container(string $containerName)
|
||||
{
|
||||
$this->execute_remote_command(
|
||||
["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||
);
|
||||
@@ -2229,7 +2250,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
||||
if ($this->pull_request_id === 0) {
|
||||
$containers = $containers->filter(function ($container) {
|
||||
return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name.'-pr-'.$this->pull_request_id;
|
||||
return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name . '-pr-' . $this->pull_request_id;
|
||||
});
|
||||
}
|
||||
$containers->each(function ($container) {
|
||||
|
@@ -4,6 +4,7 @@ namespace App\Jobs;
|
||||
|
||||
use App\Actions\Application\StopApplication;
|
||||
use App\Actions\Database\StopDatabase;
|
||||
use App\Actions\Server\CleanupDocker;
|
||||
use App\Actions\Service\DeleteService;
|
||||
use App\Actions\Service\StopService;
|
||||
use App\Models\Application;
|
||||
@@ -30,8 +31,11 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
public function __construct(
|
||||
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource,
|
||||
public bool $deleteConfigurations = false,
|
||||
public bool $deleteVolumes = false) {}
|
||||
public bool $deleteConfigurations,
|
||||
public bool $deleteVolumes,
|
||||
public bool $dockerCleanup,
|
||||
public bool $deleteConnectedNetworks
|
||||
) {}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
@@ -51,11 +55,11 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
||||
case 'standalone-dragonfly':
|
||||
case 'standalone-clickhouse':
|
||||
$persistentStorages = $this->resource?->persistentStorages()?->get();
|
||||
StopDatabase::run($this->resource);
|
||||
StopDatabase::run($this->resource, true);
|
||||
break;
|
||||
case 'service':
|
||||
StopService::run($this->resource);
|
||||
DeleteService::run($this->resource);
|
||||
StopService::run($this->resource, true);
|
||||
DeleteService::run($this->resource, $this->deleteConfigurations, $this->deleteVolumes, $this->dockerCleanup, $this->deleteConnectedNetworks);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -65,12 +69,31 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
||||
if ($this->deleteConfigurations) {
|
||||
$this->resource?->delete_configurations();
|
||||
}
|
||||
|
||||
$isDatabase = $this->resource instanceof StandalonePostgresql
|
||||
|| $this->resource instanceof StandaloneRedis
|
||||
|| $this->resource instanceof StandaloneMongodb
|
||||
|| $this->resource instanceof StandaloneMysql
|
||||
|| $this->resource instanceof StandaloneMariadb
|
||||
|| $this->resource instanceof StandaloneKeydb
|
||||
|| $this->resource instanceof StandaloneDragonfly
|
||||
|| $this->resource instanceof StandaloneClickhouse;
|
||||
$server = data_get($this->resource, 'server') ?? data_get($this->resource, 'destination.server');
|
||||
if (($this->dockerCleanup || $isDatabase) && $server) {
|
||||
CleanupDocker::run($server, true);
|
||||
}
|
||||
|
||||
if ($this->deleteConnectedNetworks && ! $isDatabase) {
|
||||
$this->resource?->delete_connected_networks($this->resource->uuid);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
send_internal_notification('ContainerStoppingJob failed with: '.$e->getMessage());
|
||||
throw $e;
|
||||
} finally {
|
||||
$this->resource->forceDelete();
|
||||
if ($this->dockerCleanup) {
|
||||
CleanupDocker::run($server, true);
|
||||
}
|
||||
Artisan::queue('cleanup:stucked-resources');
|
||||
}
|
||||
}
|
||||
|
@@ -30,7 +30,6 @@ class Dashboard extends Component
|
||||
|
||||
public function cleanup_queue()
|
||||
{
|
||||
$this->dispatch('success', 'Cleanup started.');
|
||||
Artisan::queue('cleanup:application-deployment-queue', [
|
||||
'--team-id' => currentTeam()->id,
|
||||
]);
|
||||
|
@@ -1,7 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Destination;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class Form extends Component
|
||||
@@ -38,7 +37,7 @@ class Form extends Component
|
||||
}
|
||||
$this->destination->delete();
|
||||
|
||||
return redirect()->route('dashboard');
|
||||
return redirect()->route('destination.all');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -21,6 +21,8 @@ class Heading extends Component
|
||||
|
||||
protected string $deploymentUuid;
|
||||
|
||||
public bool $docker_cleanup = true;
|
||||
|
||||
public function getListeners()
|
||||
{
|
||||
$teamId = auth()->user()->currentTeam()->id;
|
||||
@@ -102,7 +104,7 @@ class Heading extends Component
|
||||
|
||||
public function stop()
|
||||
{
|
||||
StopApplication::run($this->application);
|
||||
StopApplication::run($this->application, false, $this->docker_cleanup);
|
||||
$this->application->status = 'exited';
|
||||
$this->application->save();
|
||||
if ($this->application->additional_servers->count() > 0) {
|
||||
@@ -135,4 +137,13 @@ class Heading extends Component
|
||||
'environment_name' => $this->parameters['environment_name'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.application.heading', [
|
||||
'checkboxes' => [
|
||||
['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
{
|
||||
@@ -184,17 +186,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);
|
||||
}
|
||||
@@ -203,16 +208,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.');
|
||||
@@ -220,4 +230,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,8 @@ namespace App\Livewire\Project\Database;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use Livewire\Component;
|
||||
use Spatie\Url\Url;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class BackupEdit extends Component
|
||||
{
|
||||
@@ -12,6 +14,10 @@ class BackupEdit extends Component
|
||||
|
||||
public $s3s;
|
||||
|
||||
public bool $delete_associated_backups_locally = false;
|
||||
public bool $delete_associated_backups_s3 = false;
|
||||
public bool $delete_associated_backups_sftp = false;
|
||||
|
||||
public ?string $status = null;
|
||||
|
||||
public array $parameters;
|
||||
@@ -46,16 +52,29 @@ class BackupEdit 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 {
|
||||
if ($this->delete_associated_backups_locally) {
|
||||
$this->deleteAssociatedBackupsLocally();
|
||||
}
|
||||
if ($this->delete_associated_backups_s3) {
|
||||
$this->deleteAssociatedBackupsS3();
|
||||
}
|
||||
|
||||
$this->backup->delete();
|
||||
|
||||
if ($this->backup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
||||
$previousUrl = url()->previous();
|
||||
$url = Url::fromString($previousUrl);
|
||||
$url = $url->withoutQueryParameter('selectedBackupId');
|
||||
$url = $url->withFragment('backups');
|
||||
$url = $url->getPath()."#{$url->getFragment()}";
|
||||
$url = $url->getPath() . "#{$url->getFragment()}";
|
||||
|
||||
return redirect($url);
|
||||
} else {
|
||||
@@ -104,4 +123,66 @@ class BackupEdit extends Component
|
||||
$this->dispatch('error', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteAssociatedBackupsLocally()
|
||||
{
|
||||
$executions = $this->backup->executions;
|
||||
$backupFolder = null;
|
||||
|
||||
foreach ($executions as $execution) {
|
||||
if ($this->backup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
||||
$server = $this->backup->database->service->destination->server;
|
||||
} else {
|
||||
$server = $this->backup->database->destination->server;
|
||||
}
|
||||
|
||||
if (!$backupFolder) {
|
||||
$backupFolder = dirname($execution->filename);
|
||||
}
|
||||
|
||||
delete_backup_locally($execution->filename, $server);
|
||||
$execution->delete();
|
||||
}
|
||||
|
||||
if ($backupFolder) {
|
||||
$this->deleteEmptyBackupFolder($backupFolder, $server);
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteAssociatedBackupsS3()
|
||||
{
|
||||
//Add function to delete backups from S3
|
||||
}
|
||||
|
||||
public function deleteAssociatedBackupsSftp()
|
||||
{
|
||||
//Add function to delete backups from SFTP
|
||||
}
|
||||
|
||||
private function deleteEmptyBackupFolder($folderPath, $server)
|
||||
{
|
||||
$checkEmpty = instant_remote_process(["[ -z \"$(ls -A '$folderPath')\" ] && echo 'empty' || echo 'not empty'"], $server);
|
||||
|
||||
if (trim($checkEmpty) === 'empty') {
|
||||
instant_remote_process(["rmdir '$folderPath'"], $server);
|
||||
|
||||
$parentFolder = dirname($folderPath);
|
||||
$checkParentEmpty = instant_remote_process(["[ -z \"$(ls -A '$parentFolder')\" ] && echo 'empty' || echo 'not empty'"], $server);
|
||||
|
||||
if (trim($checkParentEmpty) === 'empty') {
|
||||
instant_remote_process(["rmdir '$parentFolder'"], $server);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.database.backup-edit', [
|
||||
'checkboxes' => [
|
||||
['id' => 'delete_associated_backups_locally', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from local storage.'],
|
||||
// ['id' => 'delete_associated_backups_s3', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from the selected S3 Storage.']
|
||||
// ['id' => 'delete_associated_backups_sftp', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from the selected SFTP Storage.']
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,9 @@ namespace App\Livewire\Project\Database;
|
||||
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\Attributes\On;
|
||||
|
||||
class BackupExecutions extends Component
|
||||
{
|
||||
@@ -12,9 +15,12 @@ class BackupExecutions extends Component
|
||||
public $executions = [];
|
||||
public $setDeletableBackup;
|
||||
|
||||
public $delete_backup_s3 = true;
|
||||
public $delete_backup_sftp = true;
|
||||
|
||||
public function getListeners()
|
||||
{
|
||||
$userId = auth()->user()->id;
|
||||
$userId = Auth::id();
|
||||
|
||||
return [
|
||||
"echo-private:team.{$userId},BackupCreated" => 'refreshBackupExecutions',
|
||||
@@ -31,19 +37,34 @@ class BackupExecutions extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteBackup($exeuctionId)
|
||||
#[On('deleteBackup')]
|
||||
public function deleteBackup($executionId, $password)
|
||||
{
|
||||
$execution = $this->backup->executions()->where('id', $exeuctionId)->first();
|
||||
if (is_null($execution)) {
|
||||
$this->dispatch('error', 'Backup execution not found.');
|
||||
|
||||
if (!Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
return;
|
||||
}
|
||||
|
||||
$execution = $this->backup->executions()->where('id', $executionId)->first();
|
||||
if (is_null($execution)) {
|
||||
$this->dispatch('error', 'Backup execution not found.');
|
||||
return;
|
||||
}
|
||||
|
||||
if ($execution->scheduledDatabaseBackup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
||||
delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->service->destination->server);
|
||||
} else {
|
||||
delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->destination->server);
|
||||
}
|
||||
|
||||
if ($this->delete_backup_s3) {
|
||||
// Add logic to delete from S3
|
||||
}
|
||||
|
||||
if ($this->delete_backup_sftp) {
|
||||
// Add logic to delete from SFTP
|
||||
}
|
||||
|
||||
$execution->delete();
|
||||
$this->dispatch('success', 'Backup deleted.');
|
||||
$this->refreshBackupExecutions();
|
||||
@@ -106,4 +127,14 @@ class BackupExecutions extends Component
|
||||
}
|
||||
return $dateObj->format('Y-m-d H:i:s T');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.database.backup-executions', [
|
||||
'checkboxes' => [
|
||||
['id' => 'delete_backup_s3', 'label' => 'Delete the selected backup permanently form S3 Storage'],
|
||||
['id' => 'delete_backup_sftp', 'label' => 'Delete the selected backup permanently form SFTP Storage'],
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@@ -14,6 +14,8 @@ class Heading extends Component
|
||||
|
||||
public array $parameters;
|
||||
|
||||
public $docker_cleanup = true;
|
||||
|
||||
public function getListeners()
|
||||
{
|
||||
$userId = auth()->user()->id;
|
||||
@@ -54,7 +56,7 @@ class Heading extends Component
|
||||
|
||||
public function stop()
|
||||
{
|
||||
StopDatabase::run($this->database);
|
||||
StopDatabase::run($this->database, false, $this->docker_cleanup);
|
||||
$this->database->status = 'exited';
|
||||
$this->database->save();
|
||||
$this->check_status();
|
||||
@@ -71,4 +73,13 @@ class Heading extends Component
|
||||
$activity = StartDatabase::run($this->database);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.database.heading', [
|
||||
'checkboxes' => [
|
||||
['id' => 'docker_cleanup', 'label' => 'Cleanup docker build cache and unused images (next deployment could take longer).'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@@ -13,9 +13,12 @@ class DeleteEnvironment extends Component
|
||||
|
||||
public bool $disabled = false;
|
||||
|
||||
public string $environmentName = '';
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->environmentName = Environment::findOrFail($this->environment_id)->name;
|
||||
}
|
||||
|
||||
public function delete()
|
||||
|
@@ -13,9 +13,12 @@ class DeleteProject extends Component
|
||||
|
||||
public bool $disabled = false;
|
||||
|
||||
public string $projectName = '';
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->projectName = Project::findOrFail($this->project_id)->name;
|
||||
}
|
||||
|
||||
public function delete()
|
||||
|
@@ -52,7 +52,7 @@ class Configuration extends Component
|
||||
$application = $this->service->applications->find($id);
|
||||
if ($application) {
|
||||
$application->restart();
|
||||
$this->dispatch('success', 'Application restarted successfully.');
|
||||
$this->dispatch('success', 'Service application restarted successfully.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
@@ -65,7 +65,7 @@ class Configuration extends Component
|
||||
$database = $this->service->databases->find($id);
|
||||
if ($database) {
|
||||
$database->restart();
|
||||
$this->dispatch('success', 'Database restarted successfully.');
|
||||
$this->dispatch('success', 'Service database restarted successfully.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
|
@@ -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
|
||||
{
|
||||
@@ -83,8 +85,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) {
|
||||
@@ -127,8 +134,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.'],
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@@ -20,6 +20,8 @@ class Navbar extends Component
|
||||
|
||||
public $isDeploymentProgress = false;
|
||||
|
||||
public $docker_cleanup = true;
|
||||
|
||||
public $title = 'Configuration';
|
||||
|
||||
public function mount()
|
||||
@@ -42,7 +44,7 @@ class Navbar extends Component
|
||||
|
||||
public function serviceStarted()
|
||||
{
|
||||
$this->dispatch('success', 'Service status changed.');
|
||||
// $this->dispatch('success', 'Service status changed.');
|
||||
if (is_null($this->service->config_hash) || $this->service->isConfigurationChanged()) {
|
||||
$this->service->isConfigurationChanged(true);
|
||||
$this->dispatch('configurationChanged');
|
||||
@@ -62,11 +64,6 @@ class Navbar extends Component
|
||||
$this->dispatch('success', 'Service status updated.');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.navbar');
|
||||
}
|
||||
|
||||
public function checkDeployments()
|
||||
{
|
||||
try {
|
||||
@@ -97,14 +94,9 @@ class Navbar extends Component
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
}
|
||||
|
||||
public function stop(bool $forceCleanup = false)
|
||||
public function stop()
|
||||
{
|
||||
StopService::run($this->service);
|
||||
if ($forceCleanup) {
|
||||
$this->dispatch('success', 'Containers cleaned up.');
|
||||
} else {
|
||||
$this->dispatch('success', 'Service stopped.');
|
||||
}
|
||||
StopService::run($this->service, false, $this->docker_cleanup);
|
||||
ServiceStatusChanged::dispatch();
|
||||
}
|
||||
|
||||
@@ -123,4 +115,13 @@ class Navbar extends Component
|
||||
$activity = StartService::run($this->service);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.navbar', [
|
||||
'checkboxes' => [
|
||||
['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,8 @@
|
||||
namespace App\Livewire\Project\Service;
|
||||
|
||||
use App\Models\ServiceApplication;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Livewire\Component;
|
||||
|
||||
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,11 +29,6 @@ class ServiceApplicationView extends Component
|
||||
'application.is_stripprefix_enabled' => 'nullable|boolean',
|
||||
];
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.service-application-view');
|
||||
}
|
||||
|
||||
public function updatedApplicationFqdn()
|
||||
{
|
||||
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
|
||||
@@ -56,8 +57,14 @@ 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 +98,17 @@ class ServiceApplicationView extends Component
|
||||
$this->dispatch('generateDockerCompose');
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.service-application-view', [
|
||||
'checkboxes' => [
|
||||
['id' => 'delete_volumes', 'label' => __('resource.delete_volumes')],
|
||||
['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')],
|
||||
// ['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.']
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,11 @@
|
||||
namespace App\Livewire\Project\Shared;
|
||||
|
||||
use App\Jobs\DeleteResourceJob;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
@@ -10,6 +15,8 @@ class Danger extends Component
|
||||
{
|
||||
public $resource;
|
||||
|
||||
public $resourceName;
|
||||
|
||||
public $projectUuid;
|
||||
|
||||
public $environmentName;
|
||||
@@ -18,22 +25,93 @@ class Danger extends Component
|
||||
|
||||
public bool $delete_volumes = true;
|
||||
|
||||
public bool $docker_cleanup = true;
|
||||
|
||||
public bool $delete_connected_networks = true;
|
||||
|
||||
public ?string $modalId = null;
|
||||
|
||||
public string $resourceDomain = '';
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->modalId = new Cuid2;
|
||||
$parameters = get_route_parameters();
|
||||
$this->modalId = new Cuid2;
|
||||
$this->projectUuid = data_get($parameters, 'project_uuid');
|
||||
$this->environmentName = data_get($parameters, 'environment_name');
|
||||
|
||||
if ($this->resource === null) {
|
||||
if (isset($parameters['service_uuid'])) {
|
||||
$this->resource = Service::where('uuid', $parameters['service_uuid'])->first();
|
||||
} elseif (isset($parameters['stack_service_uuid'])) {
|
||||
$this->resource = ServiceApplication::where('uuid', $parameters['stack_service_uuid'])->first()
|
||||
?? ServiceDatabase::where('uuid', $parameters['stack_service_uuid'])->first();
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->resource === null) {
|
||||
$this->resourceName = 'Unknown Resource';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! method_exists($this->resource, 'type')) {
|
||||
$this->resourceName = 'Unknown Resource';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($this->resource->type()) {
|
||||
case 'application':
|
||||
$this->resourceName = $this->resource->name ?? 'Application';
|
||||
break;
|
||||
case 'standalone-postgresql':
|
||||
case 'standalone-redis':
|
||||
case 'standalone-mongodb':
|
||||
case 'standalone-mysql':
|
||||
case 'standalone-mariadb':
|
||||
case 'standalone-keydb':
|
||||
case 'standalone-dragonfly':
|
||||
case 'standalone-clickhouse':
|
||||
$this->resourceName = $this->resource->name ?? 'Database';
|
||||
break;
|
||||
case 'service':
|
||||
$this->resourceName = $this->resource->name ?? 'Service';
|
||||
break;
|
||||
case 'service-application':
|
||||
$this->resourceName = $this->resource->name ?? 'Service Application';
|
||||
break;
|
||||
case 'service-database':
|
||||
$this->resourceName = $this->resource->name ?? 'Service Database';
|
||||
break;
|
||||
default:
|
||||
$this->resourceName = 'Unknown Resource';
|
||||
}
|
||||
}
|
||||
|
||||
public function delete()
|
||||
public function delete($password)
|
||||
{
|
||||
if (! Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->resource) {
|
||||
$this->addError('resource', 'Resource not found.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// $this->authorize('delete', $this->resource);
|
||||
$this->resource->delete();
|
||||
DeleteResourceJob::dispatch($this->resource, $this->delete_configurations, $this->delete_volumes);
|
||||
DeleteResourceJob::dispatch(
|
||||
$this->resource,
|
||||
$this->delete_configurations,
|
||||
$this->delete_volumes,
|
||||
$this->docker_cleanup,
|
||||
$this->delete_connected_networks
|
||||
);
|
||||
|
||||
return redirect()->route('project.resource.index', [
|
||||
'project_uuid' => $this->projectUuid,
|
||||
@@ -43,4 +121,19 @@ class Danger extends Component
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.shared.danger', [
|
||||
'checkboxes' => [
|
||||
['id' => 'delete_volumes', 'label' => __('resource.delete_volumes')],
|
||||
['id' => 'delete_connected_networks', 'label' => __('resource.delete_connected_networks')],
|
||||
['id' => 'delete_configurations', 'label' => __('resource.delete_configurations')],
|
||||
['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')],
|
||||
// ['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.']
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@@ -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.');
|
||||
|
||||
|
@@ -20,6 +20,8 @@ class Show extends Component
|
||||
|
||||
public string $type;
|
||||
|
||||
public string $scheduledTaskName;
|
||||
|
||||
protected $rules = [
|
||||
'task.enabled' => 'required|boolean',
|
||||
'task.name' => 'required|string',
|
||||
@@ -49,6 +51,7 @@ class Show extends Component
|
||||
|
||||
$this->modalId = new Cuid2;
|
||||
$this->task = ModelsScheduledTask::where('uuid', request()->route('task_uuid'))->first();
|
||||
$this->scheduledTaskName = $this->task->name;
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
@@ -75,9 +78,9 @@ class Show extends Component
|
||||
$this->task->delete();
|
||||
|
||||
if ($this->type == 'application') {
|
||||
return redirect()->route('project.application.configuration', $this->parameters);
|
||||
return redirect()->route('project.application.configuration', $this->parameters, $this->scheduledTaskName);
|
||||
} else {
|
||||
return redirect()->route('project.service.configuration', $this->parameters);
|
||||
return redirect()->route('project.service.configuration', $this->parameters, $this->scheduledTaskName);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e);
|
||||
|
@@ -3,6 +3,8 @@
|
||||
namespace App\Livewire\Project\Shared\Storages;
|
||||
|
||||
use App\Models\LocalPersistentVolume;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\Component;
|
||||
|
||||
class Show extends Component
|
||||
@@ -36,8 +38,13 @@ class Show extends Component
|
||||
$this->dispatch('success', 'Storage updated successfully');
|
||||
}
|
||||
|
||||
public function delete()
|
||||
public function delete($password)
|
||||
{
|
||||
if (!Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->storage->delete();
|
||||
$this->dispatch('refreshStorages');
|
||||
}
|
||||
|
@@ -4,6 +4,8 @@ namespace App\Livewire\Server;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class Delete extends Component
|
||||
{
|
||||
@@ -11,8 +13,12 @@ class Delete extends Component
|
||||
|
||||
public $server;
|
||||
|
||||
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->authorize('delete', $this->server);
|
||||
if ($this->server->hasDefinedResources()) {
|
||||
|
@@ -7,6 +7,8 @@ use App\Actions\Proxy\StartProxy;
|
||||
use App\Events\ProxyStatusChanged;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Process\InvokedProcess;
|
||||
|
||||
class Deploy extends Component
|
||||
{
|
||||
@@ -94,21 +96,43 @@ class Deploy extends Component
|
||||
public function stop(bool $forceStop = true)
|
||||
{
|
||||
try {
|
||||
if ($this->server->isSwarm()) {
|
||||
instant_remote_process([
|
||||
'docker service rm coolify-proxy_traefik',
|
||||
], $this->server);
|
||||
} else {
|
||||
instant_remote_process([
|
||||
'docker rm -f coolify-proxy',
|
||||
], $this->server);
|
||||
$containerName = $this->server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy';
|
||||
$timeout = 30;
|
||||
|
||||
$process = $this->stopContainer($containerName, $timeout);
|
||||
|
||||
$startTime = time();
|
||||
while ($process->running()) {
|
||||
if (time() - $startTime >= $timeout) {
|
||||
$this->forceStopContainer($containerName);
|
||||
break;
|
||||
}
|
||||
usleep(100000);
|
||||
}
|
||||
$this->server->proxy->status = 'exited';
|
||||
$this->server->proxy->force_stop = $forceStop;
|
||||
$this->server->save();
|
||||
$this->dispatch('proxyStatusUpdated');
|
||||
|
||||
$this->removeContainer($containerName);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->server->proxy->force_stop = $forceStop;
|
||||
$this->server->proxy->status = 'exited';
|
||||
$this->server->save();
|
||||
$this->dispatch('proxyStatusUpdated');
|
||||
}
|
||||
}
|
||||
|
||||
private function stopContainer(string $containerName, int $timeout): InvokedProcess
|
||||
{
|
||||
return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
|
||||
}
|
||||
|
||||
private function forceStopContainer(string $containerName)
|
||||
{
|
||||
instant_remote_process(["docker kill $containerName"], $this->server, throwError: false);
|
||||
}
|
||||
|
||||
private function removeContainer(string $containerName)
|
||||
{
|
||||
instant_remote_process(["docker rm -f $containerName"], $this->server, throwError: false);
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,8 @@ namespace App\Livewire\Team;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class AdminView extends Component
|
||||
{
|
||||
@@ -73,8 +75,12 @@ class AdminView extends Component
|
||||
$team->delete();
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
public function delete($id, $password)
|
||||
{
|
||||
if (!Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
return;
|
||||
}
|
||||
if (! auth()->user()->isInstanceAdmin()) {
|
||||
return $this->dispatch('error', 'You are not authorized to delete users');
|
||||
}
|
||||
|
@@ -8,6 +8,8 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Process\InvokedProcess;
|
||||
use OpenApi\Attributes as OA;
|
||||
use RuntimeException;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
@@ -149,13 +151,65 @@ class Application extends BaseModel
|
||||
return Application::whereRelation('environment.project.team', 'id', $teamId)->orderBy('name');
|
||||
}
|
||||
|
||||
public function getContainersToStop(bool $previewDeployments = false): array
|
||||
{
|
||||
$containers = $previewDeployments
|
||||
? getCurrentApplicationContainerStatus($this->destination->server, $this->id, includePullrequests: true)
|
||||
: getCurrentApplicationContainerStatus($this->destination->server, $this->id, 0);
|
||||
|
||||
return $containers->pluck('Names')->toArray();
|
||||
}
|
||||
|
||||
public function stopContainers(array $containerNames, $server, int $timeout = 600)
|
||||
{
|
||||
$processes = [];
|
||||
foreach ($containerNames as $containerName) {
|
||||
$processes[$containerName] = $this->stopContainer($containerName, $server, $timeout);
|
||||
}
|
||||
|
||||
$startTime = time();
|
||||
while (count($processes) > 0) {
|
||||
$finishedProcesses = array_filter($processes, function ($process) {
|
||||
return !$process->running();
|
||||
});
|
||||
foreach ($finishedProcesses as $containerName => $process) {
|
||||
unset($processes[$containerName]);
|
||||
$this->removeContainer($containerName, $server);
|
||||
}
|
||||
|
||||
if (time() - $startTime >= $timeout) {
|
||||
$this->forceStopRemainingContainers(array_keys($processes), $server);
|
||||
break;
|
||||
}
|
||||
|
||||
usleep(100000);
|
||||
}
|
||||
}
|
||||
|
||||
public function stopContainer(string $containerName, $server, int $timeout): InvokedProcess
|
||||
{
|
||||
return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
|
||||
}
|
||||
|
||||
public function removeContainer(string $containerName, $server)
|
||||
{
|
||||
instant_remote_process(command: ["docker rm -f $containerName"], server: $server, throwError: false);
|
||||
}
|
||||
|
||||
public function forceStopRemainingContainers(array $containerNames, $server)
|
||||
{
|
||||
foreach ($containerNames as $containerName) {
|
||||
instant_remote_process(command: ["docker kill $containerName"], server: $server, throwError: false);
|
||||
$this->removeContainer($containerName, $server);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete_configurations()
|
||||
{
|
||||
$server = data_get($this, 'destination.server');
|
||||
$workdir = $this->workdir();
|
||||
if (str($workdir)->endsWith($this->uuid)) {
|
||||
ray('Deleting workdir');
|
||||
instant_remote_process(['rm -rf '.$this->workdir()], $server, false);
|
||||
instant_remote_process(['rm -rf ' . $this->workdir()], $server, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +230,14 @@ class Application extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
public function delete_connected_networks($uuid)
|
||||
{
|
||||
$server = data_get($this, 'destination.server');
|
||||
instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
|
||||
instant_remote_process(["docker network rm {$uuid}"], $server, false);
|
||||
}
|
||||
|
||||
|
||||
public function additional_servers()
|
||||
{
|
||||
return $this->belongsToMany(Server::class, 'additional_destinations')
|
||||
@@ -283,7 +345,7 @@ class Application extends BaseModel
|
||||
public function publishDirectory(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: fn ($value) => $value ? '/'.ltrim($value, '/') : null,
|
||||
set: fn ($value) => $value ? '/' . ltrim($value, '/') : null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -291,7 +353,7 @@ class Application extends BaseModel
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) {
|
||||
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
|
||||
if (str($this->git_repository)->contains('bitbucket')) {
|
||||
return "{$this->source->html_url}/{$this->git_repository}/src/{$this->git_branch}";
|
||||
}
|
||||
@@ -318,7 +380,7 @@ class Application extends BaseModel
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) {
|
||||
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
|
||||
return "{$this->source->html_url}/{$this->git_repository}/settings/hooks";
|
||||
}
|
||||
// Convert the SSH URL to HTTPS URL
|
||||
@@ -337,7 +399,7 @@ class Application extends BaseModel
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) {
|
||||
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
|
||||
return "{$this->source->html_url}/{$this->git_repository}/commits/{$this->git_branch}";
|
||||
}
|
||||
// Convert the SSH URL to HTTPS URL
|
||||
@@ -354,7 +416,7 @@ class Application extends BaseModel
|
||||
|
||||
public function gitCommitLink($link): string
|
||||
{
|
||||
if (! is_null(data_get($this, 'source.html_url')) && ! is_null(data_get($this, 'git_repository')) && ! is_null(data_get($this, 'git_branch'))) {
|
||||
if (!is_null(data_get($this, 'source.html_url')) && !is_null(data_get($this, 'git_repository')) && !is_null(data_get($this, 'git_branch'))) {
|
||||
if (str($this->source->html_url)->contains('bitbucket')) {
|
||||
return "{$this->source->html_url}/{$this->git_repository}/commits/{$link}";
|
||||
}
|
||||
@@ -365,7 +427,7 @@ class Application extends BaseModel
|
||||
$git_repository = str_replace('.git', '', $this->git_repository);
|
||||
$url = Url::fromString($git_repository);
|
||||
$url = $url->withUserInfo('');
|
||||
$url = $url->withPath($url->getPath().'/commits/'.$link);
|
||||
$url = $url->withPath($url->getPath() . '/commits/' . $link);
|
||||
|
||||
return $url->__toString();
|
||||
}
|
||||
@@ -418,7 +480,7 @@ class Application extends BaseModel
|
||||
public function baseDirectory(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: fn ($value) => '/'.ltrim($value, '/'),
|
||||
set: fn ($value) => '/' . ltrim($value, '/'),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -761,7 +823,7 @@ class Application extends BaseModel
|
||||
|
||||
public function workdir()
|
||||
{
|
||||
return application_configuration_dir()."/{$this->uuid}";
|
||||
return application_configuration_dir() . "/{$this->uuid}";
|
||||
}
|
||||
|
||||
public function isLogDrainEnabled()
|
||||
@@ -771,7 +833,7 @@ class Application extends BaseModel
|
||||
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = $this->fqdn.$this->git_repository.$this->git_branch.$this->git_commit_sha.$this->build_pack.$this->static_image.$this->install_command.$this->build_command.$this->start_command.$this->ports_exposes.$this->ports_mappings.$this->base_directory.$this->publish_directory.$this->dockerfile.$this->dockerfile_location.$this->custom_labels.$this->custom_docker_run_options.$this->dockerfile_target_build.$this->redirect;
|
||||
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->ports_exposes . $this->ports_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels . $this->custom_docker_run_options . $this->dockerfile_target_build . $this->redirect;
|
||||
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
|
||||
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
|
||||
} else {
|
||||
@@ -825,7 +887,7 @@ class Application extends BaseModel
|
||||
|
||||
public function dirOnServer()
|
||||
{
|
||||
return application_configuration_dir()."/{$this->uuid}";
|
||||
return application_configuration_dir() . "/{$this->uuid}";
|
||||
}
|
||||
|
||||
public function setGitImportSettings(string $deployment_uuid, string $git_clone_command, bool $public = false)
|
||||
@@ -871,7 +933,7 @@ class Application extends BaseModel
|
||||
if ($this->source->is_public) {
|
||||
$fullRepoUrl = "{$this->source->html_url}/{$customRepository}";
|
||||
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$customRepository} {$baseDir}";
|
||||
if (! $only_checkout) {
|
||||
if (!$only_checkout) {
|
||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true);
|
||||
}
|
||||
if ($exec_in_docker) {
|
||||
@@ -888,7 +950,7 @@ class Application extends BaseModel
|
||||
$git_clone_command = "{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository} {$baseDir}";
|
||||
$fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}";
|
||||
}
|
||||
if (! $only_checkout) {
|
||||
if (!$only_checkout) {
|
||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false);
|
||||
}
|
||||
if ($exec_in_docker) {
|
||||
@@ -949,7 +1011,7 @@ class Application extends BaseModel
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name);
|
||||
} elseif ($git_type === 'github' || $git_type === 'gitea') {
|
||||
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
||||
if ($exec_in_docker) {
|
||||
@@ -957,14 +1019,14 @@ class Application extends BaseModel
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name);
|
||||
} elseif ($git_type === 'bitbucket') {
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" ".$this->buildGitCheckoutCommand($commit);
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" " . $this->buildGitCheckoutCommand($commit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -993,7 +1055,7 @@ class Application extends BaseModel
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name);
|
||||
} elseif ($git_type === 'github' || $git_type === 'gitea') {
|
||||
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
||||
if ($exec_in_docker) {
|
||||
@@ -1001,14 +1063,14 @@ class Application extends BaseModel
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name);
|
||||
} elseif ($git_type === 'bitbucket') {
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" ".$this->buildGitCheckoutCommand($commit);
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" " . $this->buildGitCheckoutCommand($commit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1060,20 +1122,20 @@ class Application extends BaseModel
|
||||
}
|
||||
if ($source->startsWith('.')) {
|
||||
$source = $source->after('.');
|
||||
$source = $workdir.$source;
|
||||
$source = $workdir . $source;
|
||||
}
|
||||
$commands->push("mkdir -p $source > /dev/null 2>&1 || true");
|
||||
}
|
||||
}
|
||||
}
|
||||
$labels = collect(data_get($service, 'labels', []));
|
||||
if (! $labels->contains('coolify.managed')) {
|
||||
if (!$labels->contains('coolify.managed')) {
|
||||
$labels->push('coolify.managed=true');
|
||||
}
|
||||
if (! $labels->contains('coolify.applicationId')) {
|
||||
$labels->push('coolify.applicationId='.$this->id);
|
||||
if (!$labels->contains('coolify.applicationId')) {
|
||||
$labels->push('coolify.applicationId=' . $this->id);
|
||||
}
|
||||
if (! $labels->contains('coolify.type')) {
|
||||
if (!$labels->contains('coolify.type')) {
|
||||
$labels->push('coolify.type=application');
|
||||
}
|
||||
data_set($service, 'labels', $labels->toArray());
|
||||
@@ -1149,7 +1211,7 @@ class Application extends BaseModel
|
||||
$jsonNames = $json->keys()->toArray();
|
||||
$diff = array_diff($jsonNames, $names);
|
||||
$json = $json->filter(function ($value, $key) use ($diff) {
|
||||
return ! in_array($key, $diff);
|
||||
return !in_array($key, $diff);
|
||||
});
|
||||
if ($json) {
|
||||
$this->docker_compose_domains = json_encode($json);
|
||||
@@ -1166,13 +1228,12 @@ class Application extends BaseModel
|
||||
} else {
|
||||
throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile<br><br>Check if you used the right extension (.yaml or .yml) in the compose file name.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function parseContainerLabels(?ApplicationPreview $preview = null)
|
||||
{
|
||||
$customLabels = data_get($this, 'custom_labels');
|
||||
if (! $customLabels) {
|
||||
if (!$customLabels) {
|
||||
return;
|
||||
}
|
||||
if (base64_encode(base64_decode($customLabels, true)) !== $customLabels) {
|
||||
@@ -1255,10 +1316,10 @@ class Application extends BaseModel
|
||||
continue;
|
||||
}
|
||||
if (isset($healthcheckCommand) && str_contains($trimmedLine, '\\')) {
|
||||
$healthcheckCommand .= ' '.trim($trimmedLine, '\\ ');
|
||||
$healthcheckCommand .= ' ' . trim($trimmedLine, '\\ ');
|
||||
}
|
||||
if (isset($healthcheckCommand) && ! str_contains($trimmedLine, '\\') && ! empty($healthcheckCommand)) {
|
||||
$healthcheckCommand .= ' '.$trimmedLine;
|
||||
if (isset($healthcheckCommand) && !str_contains($trimmedLine, '\\') && !empty($healthcheckCommand)) {
|
||||
$healthcheckCommand .= ' ' . $trimmedLine;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,8 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Process\InvokedProcess;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Spatie\Url\Url;
|
||||
@@ -68,7 +70,7 @@ class Service extends BaseModel
|
||||
$databaseStorages = $this->databases()->get()->pluck('persistentStorages')->flatten()->sortBy('id');
|
||||
$storages = $applicationStorages->merge($databaseStorages)->implode('updated_at');
|
||||
|
||||
$newConfigHash = $images.$domains.$images.$storages;
|
||||
$newConfigHash = $images . $domains . $images . $storages;
|
||||
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
|
||||
$newConfigHash = md5($newConfigHash);
|
||||
$oldConfigHash = data_get($this, 'config_hash');
|
||||
@@ -131,15 +133,80 @@ class Service extends BaseModel
|
||||
return $this->morphToMany(Tag::class, 'taggable');
|
||||
}
|
||||
|
||||
public function getContainersToStop(): array
|
||||
{
|
||||
$containersToStop = [];
|
||||
$applications = $this->applications()->get();
|
||||
foreach ($applications as $application) {
|
||||
$containersToStop[] = "{$application->name}-{$this->uuid}";
|
||||
}
|
||||
$dbs = $this->databases()->get();
|
||||
foreach ($dbs as $db) {
|
||||
$containersToStop[] = "{$db->name}-{$this->uuid}";
|
||||
}
|
||||
return $containersToStop;
|
||||
}
|
||||
|
||||
public function stopContainers(array $containerNames, $server, int $timeout = 300)
|
||||
{
|
||||
$processes = [];
|
||||
foreach ($containerNames as $containerName) {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
public function stopContainer(string $containerName, int $timeout): InvokedProcess
|
||||
{
|
||||
return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
|
||||
}
|
||||
|
||||
public function removeContainer(string $containerName, $server)
|
||||
{
|
||||
instant_remote_process(command: ["docker rm -f $containerName"], server: $server, throwError: false);
|
||||
}
|
||||
|
||||
public function forceStopRemainingContainers(array $containerNames, $server)
|
||||
{
|
||||
foreach ($containerNames as $containerName) {
|
||||
instant_remote_process(command: ["docker kill $containerName"], server: $server, throwError: false);
|
||||
$this->removeContainer($containerName, $server);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete_configurations()
|
||||
{
|
||||
$server = data_get($this, 'server');
|
||||
$server = data_get($this, 'destination.server');
|
||||
$workdir = $this->workdir();
|
||||
if (str($workdir)->endsWith($this->uuid)) {
|
||||
instant_remote_process(['rm -rf '.$this->workdir()], $server, false);
|
||||
instant_remote_process(['rm -rf ' . $this->workdir()], $server, false);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete_connected_networks($uuid)
|
||||
{
|
||||
$server = data_get($this, 'destination.server');
|
||||
instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
|
||||
instant_remote_process(["docker network rm {$uuid}"], $server, false);
|
||||
}
|
||||
|
||||
public function status()
|
||||
{
|
||||
$applications = $this->applications;
|
||||
@@ -994,7 +1061,7 @@ class Service extends BaseModel
|
||||
|
||||
public function workdir()
|
||||
{
|
||||
return service_configuration_dir()."/{$this->uuid}";
|
||||
return service_configuration_dir() . "/{$this->uuid}";
|
||||
}
|
||||
|
||||
public function saveComposeConfigs()
|
||||
|
@@ -23,7 +23,7 @@ class ServiceApplication extends BaseModel
|
||||
|
||||
public function restart()
|
||||
{
|
||||
$container_id = $this->name.'-'.$this->service->uuid;
|
||||
$container_id = $this->name . '-' . $this->service->uuid;
|
||||
instant_remote_process(["docker restart {$container_id}"], $this->service->server);
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ class ServiceApplication extends BaseModel
|
||||
|
||||
public function workdir()
|
||||
{
|
||||
return service_configuration_dir()."/{$this->service->uuid}";
|
||||
return service_configuration_dir() . "/{$this->service->uuid}";
|
||||
}
|
||||
|
||||
public function serviceType()
|
||||
|
@@ -21,7 +21,7 @@ class ServiceDatabase extends BaseModel
|
||||
|
||||
public function restart()
|
||||
{
|
||||
$container_id = $this->name.'-'.$this->service->uuid;
|
||||
$container_id = $this->name . '-' . $this->service->uuid;
|
||||
remote_process(["docker restart {$container_id}"], $this->service->server);
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ class ServiceDatabase extends BaseModel
|
||||
|
||||
public function workdir()
|
||||
{
|
||||
return service_configuration_dir()."/{$this->service->uuid}";
|
||||
return service_configuration_dir() . "/{$this->service->uuid}";
|
||||
}
|
||||
|
||||
public function service()
|
||||
|
@@ -7,7 +7,7 @@ return [
|
||||
|
||||
// The release version of your application
|
||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||
'release' => '4.0.0-beta.341',
|
||||
'release' => '4.0.0-beta.342',
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
||||
|
@@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
return '4.0.0-beta.341';
|
||||
return '4.0.0-beta.342';
|
||||
|
@@ -26,5 +26,11 @@
|
||||
"input.code": "One-time code",
|
||||
"input.recovery_code": "Recovery code",
|
||||
"button.save": "Save",
|
||||
"repository.url": "<span class='text-helper'>Examples</span><br>For Public repositories, use <span class='text-helper'>https://...</span>.<br>For Private repositories, use <span class='text-helper'>git@...</span>.<br><br>https://github.com/coollabsio/coolify-examples <span class='text-helper'>main</span> branch will be selected<br>https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify <span class='text-helper'>nodejs-fastify</span> branch will be selected.<br>https://gitea.com/sedlav/expressjs.git <span class='text-helper'>main</span> branch will be selected.<br>https://gitlab.com/andrasbacsai/nodejs-example.git <span class='text-helper'>main</span> branch will be selected."
|
||||
"repository.url": "<span class='text-helper'>Examples</span><br>For Public repositories, use <span class='text-helper'>https://...</span>.<br>For Private repositories, use <span class='text-helper'>git@...</span>.<br><br>https://github.com/coollabsio/coolify-examples <span class='text-helper'>main</span> branch will be selected<br>https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify <span class='text-helper'>nodejs-fastify</span> branch will be selected.<br>https://gitea.com/sedlav/expressjs.git <span class='text-helper'>main</span> branch will be selected.<br>https://gitlab.com/andrasbacsai/nodejs-example.git <span class='text-helper'>main</span> branch will be selected.",
|
||||
"service.stop": "This service will be stopped.",
|
||||
"resource.docker_cleanup": "Run Docker Cleanup (remove unused images and builder cache).",
|
||||
"resource.non_persistent": "All non-persistent data will be deleted.",
|
||||
"resource.delete_volumes": "All volumes associated with this resource will be permanently deleted.",
|
||||
"resource.delete_connected_networks": "All non-predefined networks associated with this resource will be permanently deleted.",
|
||||
"resource.delete_configurations": "All configuration files will be permanently deleted from the server."
|
||||
}
|
||||
|
@@ -1,4 +1,15 @@
|
||||
@props([
|
||||
'id',
|
||||
'label' => null,
|
||||
'helper' => null,
|
||||
'disabled' => false,
|
||||
'instantSave' => false,
|
||||
'value' => null,
|
||||
'hideLabel' => false,
|
||||
])
|
||||
|
||||
<div class="flex flex-row items-center gap-4 px-2 py-1 form-control min-w-fit dark:hover:bg-coolgray-100">
|
||||
@if(!$hideLabel)
|
||||
<label class="flex gap-4 px-0 min-w-fit label">
|
||||
<span class="flex gap-2">
|
||||
@if ($label)
|
||||
@@ -11,6 +22,7 @@
|
||||
@endif
|
||||
</span>
|
||||
</label>
|
||||
@endif
|
||||
<span class="flex-grow"></span>
|
||||
<input @disabled($disabled) type="checkbox" {{ $attributes->merge(['class' => $defaultClass]) }}
|
||||
@if ($instantSave) wire:loading.attr="disabled" wire:click='{{ $instantSave === 'instantSave' || $instantSave == '1' ? 'instantSave' : $instantSave }}'
|
||||
|
@@ -1,14 +1,105 @@
|
||||
@props([
|
||||
'title' => 'Are you sure?',
|
||||
'isErrorButton' => false,
|
||||
'buttonTitle' => 'REWRITE THIS BUTTON TITLE PLEASSSSEEEE',
|
||||
'buttonTitle' => 'Confirm Action',
|
||||
'buttonFullWidth' => false,
|
||||
'customButton' => null,
|
||||
'disabled' => false,
|
||||
'action' => 'delete',
|
||||
'submitAction' => 'delete',
|
||||
'content' => null,
|
||||
'checkboxes' => [],
|
||||
'actions' => [],
|
||||
'confirmWithText' => true,
|
||||
'confirmationText' => 'Confirm Deletion',
|
||||
'confirmationLabel' => 'Please confirm the execution of the actions by entering the Name below',
|
||||
'shortConfirmationLabel' => 'Name',
|
||||
'confirmWithPassword' => true,
|
||||
'step1ButtonText' => 'Continue',
|
||||
'step2ButtonText' => 'Continue',
|
||||
'step3ButtonText' => 'Confirm',
|
||||
'dispatchEvent' => false,
|
||||
'dispatchEventType' => 'success',
|
||||
'dispatchEventMessage' => '',
|
||||
])
|
||||
<div x-data="{ modalOpen: false }" @keydown.escape.window="modalOpen = false" :class="{ 'z-40': modalOpen }"
|
||||
|
||||
<div x-data="{
|
||||
modalOpen: false,
|
||||
step: {{ empty($checkboxes) ? 2 : 1 }},
|
||||
initialStep: {{ empty($checkboxes) ? 2 : 1 }},
|
||||
finalStep: {{ $confirmWithPassword ? 3 : 2 }},
|
||||
deleteText: '',
|
||||
password: '',
|
||||
actions: @js($actions),
|
||||
confirmationText: @js($confirmationText),
|
||||
userConfirmationText: '',
|
||||
confirmWithText: @js($confirmWithText),
|
||||
confirmWithPassword: @js($confirmWithPassword),
|
||||
copied: false,
|
||||
submitAction: @js($submitAction),
|
||||
passwordError: '',
|
||||
selectedActions: @js(collect($checkboxes)->pluck('id')->filter(fn($id) => $this->$id)->values()->all()),
|
||||
dispatchEvent: @js($dispatchEvent),
|
||||
dispatchEventType: @js($dispatchEventType),
|
||||
dispatchEventMessage: @js($dispatchEventMessage),
|
||||
resetModal() {
|
||||
this.step = this.initialStep;
|
||||
this.deleteText = '';
|
||||
this.password = '';
|
||||
this.userConfirmationText = '';
|
||||
this.selectedActions = @js(collect($checkboxes)->pluck('id')->filter(fn($id) => $this->$id)->values()->all());
|
||||
$wire.$refresh();
|
||||
},
|
||||
step1ButtonText: @js($step1ButtonText),
|
||||
step2ButtonText: @js($step2ButtonText),
|
||||
step3ButtonText: @js($step3ButtonText),
|
||||
validatePassword() {
|
||||
if (this.confirmWithPassword && !this.password) {
|
||||
return 'Password is required.';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
submitForm() {
|
||||
if (this.confirmWithPassword) {
|
||||
this.passwordError = this.validatePassword();
|
||||
if (this.passwordError) {
|
||||
return Promise.resolve(this.passwordError);
|
||||
}
|
||||
}
|
||||
|
||||
const methodName = this.submitAction.split('(')[0];
|
||||
const paramsMatch = this.submitAction.match(/\((.*?)\)/);
|
||||
const params = paramsMatch ? paramsMatch[1].split(',').map(param => param.trim()) : [];
|
||||
|
||||
if (this.confirmWithPassword) {
|
||||
params.push(this.password);
|
||||
}
|
||||
params.push(this.selectedActions);
|
||||
|
||||
return $wire[methodName](...params)
|
||||
.then(result => {
|
||||
if (result === true) {
|
||||
return true;
|
||||
} else if (typeof result === 'string') {
|
||||
return result;
|
||||
}
|
||||
});
|
||||
},
|
||||
copyConfirmationText() {
|
||||
navigator.clipboard.writeText(this.confirmationText);
|
||||
this.copied = true;
|
||||
setTimeout(() => {
|
||||
this.copied = false;
|
||||
}, 2000);
|
||||
},
|
||||
toggleAction(id) {
|
||||
const index = this.selectedActions.indexOf(id);
|
||||
if (index > -1) {
|
||||
this.selectedActions.splice(index, 1);
|
||||
} else {
|
||||
this.selectedActions.push(id);
|
||||
}
|
||||
}
|
||||
}" @keydown.escape.window="modalOpen = false; resetModal()" :class="{ 'z-40': modalOpen }"
|
||||
class="relative w-auto h-auto">
|
||||
@if ($customButton)
|
||||
@if ($buttonFullWidth)
|
||||
@@ -60,11 +151,9 @@
|
||||
@endif
|
||||
@endif
|
||||
<template x-teleport="body">
|
||||
<div x-show="modalOpen"
|
||||
<div x-show="modalOpen" @click.away="modalOpen = false; resetModal()"
|
||||
class="fixed top-0 lg:pt-10 left-0 z-[99] flex items-start justify-center w-screen h-screen" x-cloak>
|
||||
<div x-show="modalOpen" x-transition:enter="ease-out duration-100" x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-100"
|
||||
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" @click="modalOpen=false"
|
||||
<div x-show="modalOpen" @click="modalOpen = false; resetModal()"
|
||||
class="absolute inset-0 w-full h-full bg-black bg-opacity-20 backdrop-blur-sm"></div>
|
||||
<div x-show="modalOpen" x-trap.inert.noscroll="modalOpen" x-transition:enter="ease-out duration-100"
|
||||
x-transition:enter-start="opacity-0 -translate-y-2 sm:scale-95"
|
||||
@@ -72,56 +161,186 @@
|
||||
x-transition:leave="ease-in duration-100"
|
||||
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95"
|
||||
class="relative w-full py-6 border rounded min-w-full lg:min-w-[36rem] max-w-fit bg-neutral-100 border-neutral-400 dark:bg-base px-7 dark:border-coolgray-300">
|
||||
class="relative w-full py-6 border rounded min-w-full lg:min-w-[36rem] max-w-[48rem] bg-neutral-100 border-neutral-400 dark:bg-base px-7 dark:border-coolgray-300">
|
||||
<div class="flex items-center justify-between pb-3">
|
||||
<h3 class="text-2xl font-bold">{{ $title }}</h3>
|
||||
{{-- <button @click="modalOpen=false"
|
||||
class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 rounded-full dark:text-white hover:bg-coolgray-300">
|
||||
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
<h3 class="text-2xl font-bold pr-8">{{ $title }}</h3>
|
||||
<button @click="modalOpen = false; resetModal()"
|
||||
class="absolute top-2 right-2 flex items-center justify-center w-8 h-8 rounded-full dark:text-white hover:bg-coolgray-300">
|
||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button> --}}
|
||||
</button>
|
||||
</div>
|
||||
<div class="relative w-auto pb-8">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
<div class="flex flex-row justify-end space-x-2">
|
||||
<x-forms.button @click="modalOpen=false"
|
||||
class="w-24 dark:bg-coolgray-200 dark:hover:bg-coolgray-300">Cancel
|
||||
</x-forms.button>
|
||||
<div class="flex-1"></div>
|
||||
@if ($attributes->whereStartsWith('wire:click')->first())
|
||||
@if ($isErrorButton)
|
||||
<x-forms.button @click="modalOpen=false" class="w-24" isError type="button"
|
||||
wire:click.prevent="{{ $attributes->get('wire:click') }}">Continue
|
||||
</x-forms.button>
|
||||
@else
|
||||
<x-forms.button @click="modalOpen=false" class="w-24" isHighlighted type="button"
|
||||
wire:click.prevent="{{ $attributes->get('wire:click') }}">Continue
|
||||
</x-forms.button>
|
||||
@endif
|
||||
@elseif ($attributes->whereStartsWith('@click')->first())
|
||||
@if ($isErrorButton)
|
||||
<x-forms.button class="w-24" isError type="button"
|
||||
@click="modalOpen=false;{{ $attributes->get('@click') }}">Continue
|
||||
</x-forms.button>
|
||||
@else
|
||||
<x-forms.button class="w-24" isHighlighted type="button"
|
||||
@click="modalOpen=false;{{ $attributes->get('@click') }}">Continue
|
||||
</x-forms.button>
|
||||
@endif
|
||||
@elseif ($action)
|
||||
@if ($isErrorButton)
|
||||
<x-forms.button @click="modalOpen=false" class="w-24" isError type="button"
|
||||
wire:click.prevent="{{ $action }}">Continue
|
||||
</x-forms.button>
|
||||
@else
|
||||
<x-forms.button @click="modalOpen=false" class="w-24" isHighlighted type="button"
|
||||
wire:click.prevent="{{ $action }}">Continue
|
||||
</x-forms.button>
|
||||
@endif
|
||||
@if (!empty($checkboxes))
|
||||
<!-- Step 1: Select actions -->
|
||||
<div x-show="step === 1">
|
||||
<div class="flex justify-between items-center">
|
||||
<h4>Actions</h4>
|
||||
</div>
|
||||
@foreach ($checkboxes as $index => $checkbox)
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<label for="{{ $checkbox['id'] }}"
|
||||
class="text-sm leading-5 text-gray-700 dark:text-gray-300 flex-grow pr-4">
|
||||
{{ $checkbox['label'] }}
|
||||
</label>
|
||||
<x-forms.checkbox :id="$checkbox['id']" :wire:model="$checkbox['id']"
|
||||
x-on:change="toggleAction('{{ $checkbox['id'] }}')" :checked="$this->{$checkbox['id']}"
|
||||
x-bind:checked="selectedActions.includes('{{ $checkbox['id'] }}')"
|
||||
class="flex-shrink-0" :hideLabel="true" />
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Step 2: Confirm deletion -->
|
||||
<div x-show="step === 2">
|
||||
<div class="bg-error border-l-4 border-red-500 text-white p-4 mb-4" role="alert">
|
||||
<p class="font-bold">Warning</p>
|
||||
<p>This operation is permanent and cannot be undone. Please think again before proceeding!
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-4">The following actions will be performed:</div>
|
||||
<ul class="mb-4 space-y-2">
|
||||
@foreach ($actions as $action)
|
||||
<li class="flex items-center text-red-500">
|
||||
<svg class="w-5 h-5 mr-2 flex-shrink-0" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
<span>{{ $action }}</span>
|
||||
</li>
|
||||
@endforeach
|
||||
@foreach ($checkboxes as $checkbox)
|
||||
<template x-if="selectedActions.includes('{{ $checkbox['id'] }}')">
|
||||
<li class="flex items-center text-red-500">
|
||||
<svg class="w-5 h-5 mr-2 flex-shrink-0" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
<span>{{ $checkbox['label'] }}</span>
|
||||
</li>
|
||||
</template>
|
||||
@endforeach
|
||||
</ul>
|
||||
@if ($confirmWithText)
|
||||
<div class="mb-4">
|
||||
<h4 class="text-lg font-semibold mb-2">Confirm Actions</h4>
|
||||
<p class="text-sm mb-2">{{ $confirmationLabel }}</p>
|
||||
<div class="relative mb-2">
|
||||
<input type="text" x-model="confirmationText"
|
||||
class="w-full p-2 pr-10 rounded text-black input cursor-text" readonly>
|
||||
<button @click="copyConfirmationText()"
|
||||
class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700"
|
||||
title="Copy confirmation text" x-ref="copyButton">
|
||||
<template x-if="!copied">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20"
|
||||
fill="currentColor">
|
||||
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
|
||||
<path
|
||||
d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
|
||||
</svg>
|
||||
</template>
|
||||
<template x-if="copied">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-green-500"
|
||||
viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</template>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<label for="userConfirmationText"
|
||||
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mt-4">
|
||||
{{ $shortConfirmationLabel }}
|
||||
</label>
|
||||
<input type="text" x-model="userConfirmationText"
|
||||
class="w-full p-2 rounded text-black input mt-1">
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Step 3: Password confirmation -->
|
||||
<div x-show="step === 3 && confirmWithPassword">
|
||||
<div class="bg-error border-l-4 border-red-500 text-white p-4 mb-4" role="alert">
|
||||
<p class="font-bold">Final Confirmation</p>
|
||||
<p>Please enter your password to confirm this destructive action.</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 mb-4">
|
||||
<label for="password-confirm"
|
||||
class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Your Password
|
||||
</label>
|
||||
<input type="password" id="password-confirm" x-model="password" class="input w-full"
|
||||
placeholder="Enter your password">
|
||||
<p x-show="passwordError" x-text="passwordError" class="text-red-500 text-sm mt-1"></p>
|
||||
@error('password')
|
||||
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Navigation buttons -->
|
||||
<div class="flex flex-wrap gap-2 justify-between mt-4">
|
||||
<template x-if="step > initialStep">
|
||||
<x-forms.button @click="step--" class="w-24 dark:bg-coolgray-200 dark:hover:bg-coolgray-300">
|
||||
Back
|
||||
</x-forms.button>
|
||||
</template>
|
||||
<template x-if="step === initialStep">
|
||||
<x-forms.button @click="modalOpen = false; resetModal()"
|
||||
class="w-24 dark:bg-coolgray-200 dark:hover:bg-coolgray-300">
|
||||
Cancel
|
||||
</x-forms.button>
|
||||
</template>
|
||||
|
||||
<template x-if="step === 1">
|
||||
<x-forms.button @click="step++" class="w-auto" isError>
|
||||
<span x-text="step1ButtonText"></span>
|
||||
</x-forms.button>
|
||||
</template>
|
||||
|
||||
<template x-if="step === 2">
|
||||
<x-forms.button x-bind:disabled="confirmWithText && userConfirmationText !== confirmationText"
|
||||
class="w-auto" isError
|
||||
@click="
|
||||
if (dispatchEvent) {
|
||||
$wire.dispatch(dispatchEventType, dispatchEventMessage);
|
||||
}
|
||||
if (confirmWithPassword) {
|
||||
step++;
|
||||
} else {
|
||||
modalOpen = false;
|
||||
resetModal();
|
||||
submitForm();
|
||||
}">
|
||||
<span x-text="step2ButtonText"></span>
|
||||
</x-forms.button>
|
||||
</template>
|
||||
|
||||
<template x-if="step === 3 && confirmWithPassword">
|
||||
<x-forms.button x-bind:disabled="!password" class="w-auto" isError
|
||||
@click="
|
||||
if (dispatchEvent) {
|
||||
$wire.dispatch(dispatchEventType, dispatchEventMessage);
|
||||
}
|
||||
submitForm().then((result) => {
|
||||
if (result === true) {
|
||||
modalOpen = false;
|
||||
resetModal();
|
||||
} else {
|
||||
passwordError = result;
|
||||
}
|
||||
});
|
||||
">
|
||||
<span x-text="step3ButtonText"></span>
|
||||
</x-forms.button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -122,9 +122,19 @@
|
||||
@if (count($deployments_per_server) > 0)
|
||||
<x-loading />
|
||||
@endif
|
||||
<x-modal-confirmation isErrorButton action="cleanup_queue" buttonTitle="Cleanup Queues">
|
||||
This will clean up the deployment queue. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Cleanup Queues?"
|
||||
buttonTitle="Cleanup Queues"
|
||||
isErrorButton
|
||||
submitAction="cleanup_queue"
|
||||
:actions="['All running Deployment Queues will be cleaned up.']"
|
||||
:confirmWithText="false"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Cleanup Deployment Queues"
|
||||
:dispatchEvent="true"
|
||||
dispatchEventType="success"
|
||||
dispatchEventMessage="Deployment Queues cleanup started."
|
||||
/>
|
||||
</div>
|
||||
<div wire:poll.3000ms="get_deployments" class="grid grid-cols-1">
|
||||
@forelse ($deployments_per_server as $server_name => $deployments)
|
||||
@@ -170,4 +180,6 @@
|
||||
}
|
||||
</script>
|
||||
{{-- <x-forms.button wire:click='getIptables'>Get IPTABLES</x-forms.button> --}}
|
||||
|
||||
|
||||
</div>
|
||||
|
@@ -6,9 +6,18 @@
|
||||
Save
|
||||
</x-forms.button>
|
||||
@if ($destination->network !== 'coolify')
|
||||
<x-modal-confirmation isErrorButton buttonTitle="Delete Destination">
|
||||
This destination will be deleted. It is not reversible. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Destination Deletion?"
|
||||
buttonTitle="Delete Destination"
|
||||
isErrorButton
|
||||
submitAction="delete"
|
||||
:actions="['This will delete the selected destination/network.']"
|
||||
confirmationText="{{ $destination->name }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Destination Name below"
|
||||
shortConfirmationLabel="Destination Name"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Delete Destination"
|
||||
/>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
|
@@ -1,5 +1,13 @@
|
||||
<div>
|
||||
<x-modal-confirmation buttonFullWidth isErrorButton buttonTitle="Delete Team">
|
||||
This team be deleted. It is not reversible. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Team Deletion?"
|
||||
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>
|
||||
|
@@ -68,11 +68,20 @@
|
||||
<option value="www">Redirect to www.</option>
|
||||
<option value="non-www">Redirect to non-www.</option>
|
||||
</x-forms.select>
|
||||
<x-modal-confirmation action="set_redirect">
|
||||
<x-modal-confirmation
|
||||
title="Confirm Redirection Setting?"
|
||||
buttonTitle="Set Direction"
|
||||
submitAction="set_redirect"
|
||||
:actions="['All traffic will be redirected to the selected direction.']"
|
||||
confirmationText="{{ $application->fqdn . '/' }}"
|
||||
confirmationLabel="Please confirm the execution of the action by entering the Application URL below"
|
||||
shortConfirmationLabel="Application URL"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Set Direction"
|
||||
>
|
||||
<x-slot:customButton>
|
||||
<div class="w-[7.2rem]">Set Direction</div>
|
||||
</x-slot:customButton>
|
||||
This will reset the container labels. Are you sure?
|
||||
</x-modal-confirmation>
|
||||
</div>
|
||||
@endif
|
||||
@@ -302,12 +311,18 @@
|
||||
helper="If you know what are you doing, you can enable this to edit the labels directly. Coolify won't update labels automatically. <br><br>Be careful, it could break the proxy configuration after you restart the container."
|
||||
id="application.settings.is_container_label_readonly_enabled" instantSave></x-forms.checkbox>
|
||||
</div>
|
||||
<x-modal-confirmation buttonFullWidth action="resetDefaultLabels"
|
||||
buttonTitle="Reset to Coolify Generated Labels">
|
||||
Are you sure you want to reset the labels to Coolify generated labels? <br>It could break the proxy
|
||||
configuration after you restart the container.
|
||||
</x-modal-confirmation>
|
||||
|
||||
<x-modal-confirmation
|
||||
title="Confirm Labels Reset to Coolify Defaults?"
|
||||
buttonTitle="Reset Labels to Coolify Defaults"
|
||||
buttonFullWidth
|
||||
submitAction="resetDefaultLabels"
|
||||
:actions="['All your custom proxy labels will be lost.', 'Proxy labels (traefik, caddy, etc) will be reset to the coolify defaults.']"
|
||||
confirmationText="{{ $application->fqdn . '/' }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Application URL below"
|
||||
shortConfirmationLabel="Application URL"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Reset Labels"
|
||||
/>
|
||||
@endif
|
||||
|
||||
<h3 class="pt-8">Pre/Post Deployment Commands</h3>
|
||||
|
@@ -72,7 +72,13 @@
|
||||
</x-forms.button>
|
||||
@endif
|
||||
@endif
|
||||
<x-modal-confirmation @click="$wire.dispatch('stopEvent')">
|
||||
<x-modal-confirmation title="Confirm Application Stopping?" buttonTitle="Stop"
|
||||
submitAction="stop" :checkboxes="$checkboxes" :actions="[
|
||||
'This application will be stopped.',
|
||||
'All non-persistent data of this application will be deleted.',
|
||||
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||
step1ButtonText="Continue" step2ButtonText="Confirm" :dispatchEvent="true"
|
||||
dispatchEventType="stopEvent">
|
||||
<x-slot:button-title>
|
||||
<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"
|
||||
@@ -87,7 +93,6 @@
|
||||
</svg>
|
||||
Stop
|
||||
</x-slot:button-title>
|
||||
This application will be stopped. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
@else
|
||||
<x-forms.button wire:click='deploy'>
|
||||
|
@@ -152,8 +152,15 @@
|
||||
@endif
|
||||
</x-forms.button>
|
||||
@if (data_get($preview, 'status') !== 'exited')
|
||||
<x-modal-confirmation isErrorButton
|
||||
action="stop({{ data_get($preview, 'pull_request_id') }})">
|
||||
<x-modal-confirmation
|
||||
title="Confirm Preview Deployment Stopping?"
|
||||
buttonTitle="Stop"
|
||||
submitAction="stop({{ data_get($preview, 'pull_request_id') }})"
|
||||
:actions="['This preview deployment will be stopped.', 'If the preview deployment is currently in use data could be lost.', 'All non-persistent data of this preview deployment (containers, networks, unused images) will be deleted (don\'t worry, no data is lost and you can start the preview deployment again).']"
|
||||
:confirmWithText="false"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Stop Preview Deployment"
|
||||
>
|
||||
<x-slot:customButton>
|
||||
<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"
|
||||
@@ -168,14 +175,19 @@
|
||||
</svg>
|
||||
Stop
|
||||
</x-slot:customButton>
|
||||
This will stop the preview deployment. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
@endif
|
||||
<x-modal-confirmation isErrorButton
|
||||
action="delete({{ data_get($preview, 'pull_request_id') }})" buttonTitle="Delete">
|
||||
This will delete the preview deployment. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
|
||||
<x-modal-confirmation
|
||||
title="Confirm Preview Deployment Deletion?"
|
||||
buttonTitle="Delete"
|
||||
isErrorButton
|
||||
submitAction="delete({{ data_get($preview, 'pull_request_id') }})"
|
||||
:actions="['All containers of this preview deployment will be stopped and permanently deleted.']"
|
||||
confirmationText="{{ data_get($preview, 'fqdn'). '/' }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Preview Deployment name below"
|
||||
shortConfirmationLabel="Preview Deployment Name"
|
||||
:confirmWithPassword="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
@@ -8,12 +8,17 @@
|
||||
<livewire:project.database.backup-now :backup="$backup" />
|
||||
@endif
|
||||
@if ($backup->database_id !== 0)
|
||||
<x-modal-confirmation isErrorButton>
|
||||
<x-slot:button-title>
|
||||
Delete
|
||||
</x-slot:button-title>
|
||||
This will stop the scheduled backup for this database.<br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Backup Schedule Deletion?"
|
||||
buttonTitle="Delete Backups and Schedule"
|
||||
isErrorButton
|
||||
submitAction="delete"
|
||||
:checkboxes="$checkboxes"
|
||||
:actions="['The selected backup schedule will be deleted.', 'Scheduled backups for this database will be stopped (if this is the only backup schedule for this database).']"
|
||||
confirmationText="{{ $backup->database->name }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Database Name of the scheduled backups below"
|
||||
shortConfirmationLabel="Database Name"
|
||||
/>
|
||||
@endif
|
||||
</div>
|
||||
<div class="w-48 pb-2">
|
||||
|
@@ -45,12 +45,20 @@
|
||||
<x-forms.button class="dark:hover:bg-coolgray-400"
|
||||
x-on:click="download_file('{{ data_get($execution, 'id') }}')">Download</x-forms.button>
|
||||
@endif
|
||||
<x-modal-confirmation isErrorButton action="deleteBackup({{ data_get($execution, 'id') }})">
|
||||
<x-slot:button-title>
|
||||
Delete
|
||||
</x-slot:button-title>
|
||||
This will delete this backup. It is not reversible.<br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Backup Deletion?"
|
||||
buttonTitle="Delete"
|
||||
isErrorButton
|
||||
submitAction="deleteBackup({{ data_get($execution, 'id') }})"
|
||||
{{-- :checkboxes="$checkboxes" --}}
|
||||
:actions="[
|
||||
'This backup will be permanently deleted from local storage.'
|
||||
]"
|
||||
confirmationText="{{ data_get($execution, 'filename') }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Backup Filename below"
|
||||
shortConfirmationLabel="Backup Filename"
|
||||
step3ButtonText="Permanently Delete Backup"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
|
@@ -23,73 +23,87 @@
|
||||
<button>Terminal</button>
|
||||
</a>
|
||||
@if (
|
||||
$database->getMorphClass() === 'App\Models\StandalonePostgresql' ||
|
||||
$database->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
||||
$database->getMorphClass() === 'App\Models\StandaloneMysql' ||
|
||||
$database->getMorphClass() === 'App\Models\StandaloneMariadb')
|
||||
<a class="{{ request()->routeIs('project.database.backup.index') ? 'dark:text-white' : '' }}"
|
||||
href="{{ route('project.database.backup.index', $parameters) }}">
|
||||
<button>Backups</button>
|
||||
</a>
|
||||
$database->getMorphClass() === 'App\Models\StandalonePostgresql' ||
|
||||
$database->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
||||
$database->getMorphClass() === 'App\Models\StandaloneMysql' ||
|
||||
$database->getMorphClass() === 'App\Models\StandaloneMariadb')
|
||||
<a class="{{ request()->routeIs('project.database.backup.index') ? 'dark:text-white' : '' }}" href="{{ route('project.database.backup.index', $parameters) }}">
|
||||
<button>Backups</button>
|
||||
</a>
|
||||
@endif
|
||||
</nav>
|
||||
<div class="flex flex-wrap gap-2 items-center">
|
||||
@if (!str($database->status)->startsWith('exited'))
|
||||
<x-modal-confirmation @click="$wire.dispatch('restartEvent')">
|
||||
<x-slot:button-title>
|
||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2">
|
||||
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
Restart
|
||||
</x-slot:button-title>
|
||||
This database will be restarted. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation @click="$wire.dispatch('stopEvent')">
|
||||
<x-slot:button-title>
|
||||
<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-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
<path d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
</svg>
|
||||
Stop
|
||||
</x-slot:button-title>
|
||||
This database will be stopped. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
@else
|
||||
<button @click="$wire.dispatch('startEvent')" class="gap-2 button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M7 4v16l13 -8z" />
|
||||
<x-modal-confirmation
|
||||
title="Confirm Database Restart?"
|
||||
buttonTitle="Restart"
|
||||
submitAction="restart"
|
||||
:actions="['This database will be unavailable during the restart.', 'If the database is currently in use data could be lost.']"
|
||||
:confirmWithText="false"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Restart Database"
|
||||
:dispatchEvent="true"
|
||||
dispatchEventType="restartEvent"
|
||||
>
|
||||
<x-slot:button-title>
|
||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
Start
|
||||
</button>
|
||||
Restart
|
||||
</x-slot:button-title>
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Database Stopping?"
|
||||
buttonTitle="Stop"
|
||||
submitAction="stop"
|
||||
:checkboxes="$checkboxes"
|
||||
:actions="['This database will be stopped.', '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).']"
|
||||
:confirmWithText="false"
|
||||
:confirmWithPassword="false"
|
||||
step1ButtonText="Continue Stopping Database"
|
||||
step2ButtonText="Stop Database"
|
||||
:dispatchEvent="true"
|
||||
dispatchEventType="stopEvent"
|
||||
>
|
||||
<x-slot:button-title>
|
||||
<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-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
<path d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
</svg>
|
||||
Stop
|
||||
</x-slot:button-title>
|
||||
</x-modal-confirmation>
|
||||
@else
|
||||
<button @click="$wire.dispatch('startEvent')" class="gap-2 button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M7 4v16l13 -8z" />
|
||||
</svg>
|
||||
Start
|
||||
</button>
|
||||
@endif
|
||||
@script
|
||||
<script>
|
||||
$wire.$on('startEvent', () => {
|
||||
window.dispatchEvent(new CustomEvent('startdatabase'));
|
||||
$wire.$call('start');
|
||||
});
|
||||
$wire.$on('stopEvent', () => {
|
||||
$wire.$dispatch('info', 'Stopping database.');
|
||||
$wire.$call('stop');
|
||||
});
|
||||
$wire.$on('restartEvent', () => {
|
||||
$wire.$dispatch('info', 'Restarting database.');
|
||||
$wire.$call('restart');
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
$wire.$on('startEvent', () => {
|
||||
window.dispatchEvent(new CustomEvent('startdatabase'));
|
||||
$wire.$call('start');
|
||||
});
|
||||
$wire.$on('stopEvent', () => {
|
||||
$wire.$dispatch('info', 'Stopping database.');
|
||||
$wire.$call('stop');
|
||||
});
|
||||
$wire.$on('restartEvent', () => {
|
||||
$wire.$dispatch('info', 'Restarting database.');
|
||||
$wire.$call('restart');
|
||||
});
|
||||
</script>
|
||||
@endscript
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
@@ -2,9 +2,21 @@
|
||||
<div class="flex items-end gap-2">
|
||||
<x-forms.input id="filename" label="Filename" />
|
||||
<x-forms.button type="submit">Save</x-forms.button>
|
||||
<x-modal-confirmation isErrorButton buttonTitle="Delete">
|
||||
This script will be deleted. It is not reversible. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm init-script deletion?"
|
||||
buttonTitle="Delete"
|
||||
isErrorButton
|
||||
submitAction="delete"
|
||||
:actions="[
|
||||
'The init-script of this database will be permanently deleted.',
|
||||
'If you are actively using this init-script, it could cause errors on redeployment.'
|
||||
]"
|
||||
confirmationText="{{ $filename }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the init-script name below"
|
||||
shortConfirmationLabel="Init-script Name"
|
||||
:confirmWithPassword=false
|
||||
step2ButtonText="Permanently Delete Init-script"
|
||||
/>
|
||||
</div>
|
||||
<x-forms.textarea id="content" label="Content" />
|
||||
</form>
|
||||
|
@@ -1,3 +1,12 @@
|
||||
<x-modal-confirmation isErrorButton buttonTitle="Delete Environment" disabled="{{ $disabled }}">
|
||||
This environment will be deleted. It is not reversible. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Environment Deletion?"
|
||||
buttonTitle="Delete Environment"
|
||||
isErrorButton
|
||||
submitAction="delete"
|
||||
:actions="['This will delete the selected environment.']"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Environment Name below"
|
||||
shortConfirmationLabel="Environment Name"
|
||||
confirmationText="{{ $environmentName }}"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Delete Environment"
|
||||
/>
|
||||
|
@@ -1,3 +1,12 @@
|
||||
<x-modal-confirmation isErrorButton buttonTitle="Delete Project" disabled="{{ $disabled }}">
|
||||
This project will be deleted. It is not reversible. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Project Deletion?"
|
||||
buttonTitle="Delete Project"
|
||||
isErrorButton
|
||||
submitAction="delete"
|
||||
:actions="['This will delete the selected project', 'All Environments inside the project will be deleted as well.']"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Project Name below"
|
||||
shortConfirmationLabel="Project Name"
|
||||
confirmationText="{{ $projectName }}"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Delete Project"
|
||||
/>
|
||||
|
@@ -107,11 +107,15 @@
|
||||
Settings
|
||||
</a>
|
||||
@if (str($application->status)->contains('running'))
|
||||
<x-modal-confirmation action="restartApplication({{ $application->id }})"
|
||||
isErrorButton buttonTitle="Restart">
|
||||
This application will be unavailable during the restart. <br>Please think
|
||||
again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Service Application Restart?"
|
||||
buttonTitle="Restart"
|
||||
submitAction="restartApplication({{ $application->id }})"
|
||||
:actions="['The selected service application will be unavailable during the restart.', 'If the service application is currently in use data could be lost.']"
|
||||
:confirmWithText="false"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Restart Service Container"
|
||||
/>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@@ -151,11 +155,15 @@
|
||||
Settings
|
||||
</a>
|
||||
@if (str($database->status)->contains('running'))
|
||||
<x-modal-confirmation action="restartDatabase({{ $database->id }})"
|
||||
isErrorButton buttonTitle="Restart">
|
||||
This database will be unavailable during the restart. <br>Please think
|
||||
again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Service Database Restart?"
|
||||
buttonTitle="Restart"
|
||||
submitAction="restartDatabase({{ $database->id }})"
|
||||
:actions="['This service database will be unavailable during the restart.', 'If the service database is currently in use data could be lost.']"
|
||||
:confirmWithText="false"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Restart Database"
|
||||
/>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -15,18 +15,56 @@
|
||||
<form wire:submit='submit' class="flex flex-col gap-2">
|
||||
<div class="flex gap-2">
|
||||
@if ($fileStorage->is_directory)
|
||||
<x-modal-confirmation action="convertToFile" buttonTitle="Convert to file">
|
||||
<div>This will delete all files in this directory. It is not reversible. <strong
|
||||
class="text-error">Please think
|
||||
again.</strong><br><br></div>
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Directory Conversion to File?"
|
||||
buttonTitle="Convert to file"
|
||||
submitAction="convertToFile"
|
||||
:actions="['All files in this directory will be permanently deleted and an empty file will be created in its place.']"
|
||||
confirmationText="{{ $fs_path }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Filepath below"
|
||||
shortConfirmationLabel="Filepath"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Convert to file"
|
||||
/>
|
||||
@else
|
||||
<x-modal-confirmation action="convertToDirectory" buttonTitle="Convert to directory">
|
||||
<div>This will delete the file and make a directory instead. It is not reversible.
|
||||
<strong class="text-error">Please think
|
||||
again.</strong><br><br>
|
||||
</div>
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm File Conversion to Directory?"
|
||||
buttonTitle="Convert to directory"
|
||||
submitAction="convertToDirectory"
|
||||
:actions="['The selected file will be permanently deleted and an empty directory will be created in its place.']"
|
||||
confirmationText="{{ $fs_path }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Filepath below"
|
||||
shortConfirmationLabel="Filepath"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Convert to directory"
|
||||
/>
|
||||
@endif
|
||||
@if ($fileStorage->is_directory)
|
||||
<x-modal-confirmation
|
||||
title="Confirm Directory Deletion?"
|
||||
buttonTitle="Delete Directory"
|
||||
isErrorButton
|
||||
submitAction="delete"
|
||||
:checkboxes="$directoryDeletionCheckboxes"
|
||||
:actions="['The selected directory and all its contents will be permanently deleted from the container.']"
|
||||
confirmationText="{{ $fs_path }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Filepath below"
|
||||
shortConfirmationLabel="Filepath"
|
||||
step3ButtonText="Permanently Delete Directory"
|
||||
/>
|
||||
@else
|
||||
<x-modal-confirmation
|
||||
title="Confirm File Deletion?"
|
||||
buttonTitle="Delete File"
|
||||
isErrorButton
|
||||
submitAction="delete"
|
||||
:checkboxes="$fileDeletionCheckboxes"
|
||||
:actions="['The selected file will be permanently deleted from the container.']"
|
||||
confirmationText="{{ $fs_path }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Filepath below"
|
||||
shortConfirmationLabel="Filepath"
|
||||
step3ButtonText="Permanently Delete File"
|
||||
/>
|
||||
@endif
|
||||
|
||||
@if (!$fileStorage->is_based_on_git)
|
||||
|
@@ -32,7 +32,9 @@
|
||||
</svg>
|
||||
Pull Latest Images & Restart
|
||||
</button>
|
||||
<x-modal-confirmation @click="$wire.dispatch('stopEvent')">
|
||||
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" submitAction="stop"
|
||||
:checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]" :confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue"
|
||||
step2ButtonText="Stop Service" :dispatchEvent="true" dispatchEventType="stopEvent">
|
||||
<x-slot:button-title>
|
||||
<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"
|
||||
@@ -45,7 +47,6 @@
|
||||
</svg>
|
||||
Stop
|
||||
</x-slot:button-title>
|
||||
This service will be stopped. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
@elseif (str($service->status())->contains('degraded'))
|
||||
<button @click="$wire.dispatch('startEvent')" class="gap-2 button">
|
||||
@@ -58,7 +59,10 @@
|
||||
</svg>
|
||||
Restart Degraded Services
|
||||
</button>
|
||||
<x-modal-confirmation @click="$wire.dispatch('stopEvent')">
|
||||
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" submitAction="stop"
|
||||
:checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]" :confirmWithText="false" :confirmWithPassword="false"
|
||||
step1ButtonText="Continue" step2ButtonText="Stop Service" :dispatchEvent="true"
|
||||
dispatchEventType="stopEvent">
|
||||
<x-slot:button-title>
|
||||
<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"
|
||||
@@ -71,7 +75,6 @@
|
||||
</svg>
|
||||
Stop
|
||||
</x-slot:button-title>
|
||||
This service will be stopped. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
@elseif (str($service->status())->contains('exited'))
|
||||
<button wire:click='stop(true)' class="gap-2 button">
|
||||
@@ -92,7 +95,10 @@
|
||||
Deploy
|
||||
</button>
|
||||
@else
|
||||
<x-modal-confirmation @click="$wire.dispatch('stopEvent')">
|
||||
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" submitAction="stop"
|
||||
:checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]" :confirmWithText="false" :confirmWithPassword="false"
|
||||
step1ButtonText="Continue" step2ButtonText="Stop Service" :dispatchEvent="true"
|
||||
dispatchEventType="stopEvent">
|
||||
<x-slot:button-title>
|
||||
<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"
|
||||
@@ -105,7 +111,6 @@
|
||||
</svg>
|
||||
Stop
|
||||
</x-slot:button-title>
|
||||
This service will be stopped. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<button @click="$wire.dispatch('startEvent')" class="gap-2 button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||
|
@@ -7,12 +7,20 @@
|
||||
<h2>{{ Str::headline($application->name) }}</h2>
|
||||
@endif
|
||||
<x-forms.button type="submit">Save</x-forms.button>
|
||||
<x-modal-confirmation isErrorButton>
|
||||
<x-slot:button-title>
|
||||
Delete
|
||||
</x-slot:button-title>
|
||||
This will delete this service application. It is not reversible.<br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Service Application Deletion?"
|
||||
buttonTitle="Delete"
|
||||
isErrorButton
|
||||
submitAction="delete"
|
||||
{{-- :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 class="flex flex-col gap-2">
|
||||
<div class="flex gap-2">
|
||||
|
@@ -2,15 +2,10 @@
|
||||
<h2>Danger Zone</h2>
|
||||
<div class="">Woah. I hope you know what are you doing.</div>
|
||||
<h4 class="pt-4">Delete Resource</h4>
|
||||
<div class="pb-4">This will stop your containers, delete all related data, etc. Beware! There is no coming
|
||||
back!
|
||||
<div class="pb-4">This will stop your containers, delete all related data, etc. Beware! There is no coming back!
|
||||
</div>
|
||||
<x-modal-confirmation isErrorButton buttonTitle="Delete" confirm={{ $confirm }}>
|
||||
<div class="px-2">This resource will be deleted. It is not reversible. <strong class="text-error">Please think
|
||||
again.</strong><br><br></div>
|
||||
<h4>Actions</h4>
|
||||
<x-forms.checkbox id="delete_configurations"
|
||||
label="Permanently delete configuration files from the server?"></x-forms.checkbox>
|
||||
<x-forms.checkbox id="delete_volumes" label="Permanently delete associated volumes?"></x-forms.checkbox>
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation title="Confirm Resource Deletion?" buttonTitle="Delete" isErrorButton submitAction="delete"
|
||||
buttonTitle="Delete" :checkboxes="$checkboxes" :actions="['All containers of this resource will be stopped and permanently deleted.']" confirmationText="{{ $resourceName }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the NAME of the resource below"
|
||||
shortConfirmationLabel="Resource Name" step3ButtonText="Delete Permanently" />
|
||||
</div>
|
||||
|
@@ -63,11 +63,16 @@
|
||||
wire:click="stop('{{ data_get($destination, 'server.id') }}')">Stop</x-forms.button>
|
||||
@endif
|
||||
<x-modal-confirmation
|
||||
action="removeServer({{ data_get($destination, 'id') }},{{ data_get($destination, 'server.id') }})"
|
||||
isErrorButton buttonTitle="Remove Server">
|
||||
This will stop the running application in this server and remove it as a deployment
|
||||
destination.<br><br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
title="Confirm server removal?"
|
||||
isErrorButton
|
||||
buttonTitle="Remove Server"
|
||||
submitAction="removeServer({{ data_get($destination, 'id') }},{{ data_get($destination, 'server.id') }})"
|
||||
: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>
|
||||
@endforeach
|
||||
|
@@ -11,10 +11,20 @@
|
||||
<path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0-2 0m-3-5V7a4 4 0 1 1 8 0v4" />
|
||||
</g>
|
||||
</svg>
|
||||
<x-modal-confirmation isErrorButton buttonTitle="Delete">
|
||||
You will delete environment variable <span
|
||||
class="font-bold dark:text-warning text-coollabs">{{ $env->key }}</span>.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Environment Variable Deletion?"
|
||||
isErrorButton
|
||||
buttonTitle="Delete"
|
||||
submitAction="delete"
|
||||
:actions="[
|
||||
'The selected environment variable will be permanently deleted.'
|
||||
]"
|
||||
confirmationText="{{ $env->key }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Environment Variable Name below"
|
||||
shortConfirmationLabel="Environment Variable Name"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Delete Environment Variable"
|
||||
/>
|
||||
</div>
|
||||
@else
|
||||
@if ($isDisabled)
|
||||
@@ -70,10 +80,20 @@
|
||||
<x-forms.button wire:click='lock'>
|
||||
Lock
|
||||
</x-forms.button>
|
||||
<x-modal-confirmation isErrorButton buttonTitle="Delete">
|
||||
You will delete environment variable <span
|
||||
class="font-bold dark:text-warning">{{ $env->key }}</span>.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Environment Variable Deletion?"
|
||||
isErrorButton
|
||||
buttonTitle="Delete"
|
||||
submitAction="delete"
|
||||
:actions="[
|
||||
'The selected environment variable will be permanently deleted.'
|
||||
]"
|
||||
confirmationText="{{ $env->key }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Environment Variable Name below"
|
||||
shortConfirmationLabel="Environment Variable Name"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Delete Environment Variable"
|
||||
/>
|
||||
@else
|
||||
<x-forms.button type="submit">
|
||||
Update
|
||||
@@ -81,10 +101,20 @@
|
||||
<x-forms.button wire:click='lock'>
|
||||
Lock
|
||||
</x-forms.button>
|
||||
<x-modal-confirmation buttonFullWidth isErrorButton buttonTitle="Delete">
|
||||
You will delete environment variable <span
|
||||
class="font-bold dark:text-warning">{{ $env->key }}</span>.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Environment Variable Deletion?"
|
||||
isErrorButton
|
||||
buttonTitle="Delete"
|
||||
submitAction="delete"
|
||||
:actions="[
|
||||
'The selected environment variable will be permanently deleted.'
|
||||
]"
|
||||
confirmationText="{{ $env->key }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Environment Variable Name below"
|
||||
shortConfirmationLabel="Environment Variable Name"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Delete Environment Variable"
|
||||
/>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
@@ -8,7 +8,20 @@
|
||||
@foreach ($servers->sortBy('id') as $server)
|
||||
<h5>Server: <span class="font-bold text-dark dark:text-white">{{ $server->name }}</span></h5>
|
||||
@foreach ($server->destinations() as $destination)
|
||||
<x-modal-confirmation action="cloneTo({{ data_get($destination, 'id') }})">
|
||||
<x-modal-confirmation
|
||||
title="Clone Resource?"
|
||||
buttonTitle="Clone Resource"
|
||||
submitAction="cloneTo({{ data_get($destination, 'id') }})"
|
||||
:actions="[
|
||||
'All containers of this resource will be duplicated and cloned to the selected destination.'
|
||||
]"
|
||||
:confirmWithText="false"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Clone Resource"
|
||||
dispatchEvent="true"
|
||||
dispatchEventType="success"
|
||||
dispatchEventMessage="Resource cloned to {{ $destination->name }} destination."
|
||||
>
|
||||
<x:slot name="content">
|
||||
<div class="box group">
|
||||
<div class="flex flex-col">
|
||||
@@ -17,7 +30,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</x:slot>
|
||||
<div>You are about to clone this resource.</div>
|
||||
</x-modal-confirmation>
|
||||
@endforeach
|
||||
@endforeach
|
||||
@@ -36,8 +48,21 @@
|
||||
<h5>Project: <span class="font-bold text-dark dark:text-white">{{ $project->name }}</span></h5>
|
||||
|
||||
@foreach ($project->environments as $environment)
|
||||
<x-modal-confirmation action="moveTo({{ data_get($environment, 'id') }})">
|
||||
<x:slot name="content">
|
||||
<x-modal-confirmation
|
||||
title="Move Resource?"
|
||||
buttonTitle="Move Resource"
|
||||
submitAction="moveTo({{ data_get($environment, 'id') }})"
|
||||
:actions="[
|
||||
'All containers of this resource will be moved to the selected environment.'
|
||||
]"
|
||||
:confirmWithText="false"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Move Resource"
|
||||
dispatchEvent="true"
|
||||
dispatchEventType="success"
|
||||
dispatchEventMessage="Resource moved to {{ $environment->name }} environment."
|
||||
>
|
||||
<x:slot:content>
|
||||
<div class="box group">
|
||||
<div class="flex flex-col">
|
||||
<div class="box-title">Environment</div>
|
||||
@@ -45,7 +70,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</x:slot>
|
||||
<div>You are about to move this resource.</div>
|
||||
</x-modal-confirmation>
|
||||
@endforeach
|
||||
@empty
|
||||
|
@@ -9,20 +9,26 @@
|
||||
<livewire:project.service.navbar :service="$resource" :parameters="$parameters" />
|
||||
@endif
|
||||
|
||||
<form wire:submit="submit" class="w-full">
|
||||
<div class="flex flex-col gap-2 pb-2">
|
||||
<div class="flex items-end gap-2 pt-4">
|
||||
<h2>Scheduled Task</h2>
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
</x-forms.button>
|
||||
<x-modal-confirmation isErrorButton buttonTitle="Delete Scheduled Task">
|
||||
You will delete scheduled task <span class="font-bold dark:text-warning">{{ $task->name }}</span>.
|
||||
</x-modal-confirmation>
|
||||
</div>
|
||||
<div class="w-48">
|
||||
<x-forms.checkbox instantSave id="task.enabled" label="Enabled" />
|
||||
</div>
|
||||
<form wire:submit="submit" class="w-full">
|
||||
<div class="flex flex-col gap-2 pb-2">
|
||||
<div class="flex items-end gap-2 pt-4">
|
||||
<h2>Scheduled Task</h2>
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
</x-forms.button>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Scheduled Task Deletion?"
|
||||
isErrorButton
|
||||
buttonTitle="Delete"
|
||||
submitAction="delete({{ $task->id }})"
|
||||
:actions="['The selected scheduled task will be permanently deleted.']"
|
||||
confirmationText="{{ $task->name }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Scheduled Task Name below"
|
||||
shortConfirmationLabel="Scheduled Task Name"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Delete Scheduled Task"
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<x-forms.input placeholder="Name" id="task.name" label="Name" required />
|
||||
|
@@ -46,13 +46,17 @@
|
||||
<x-forms.button type="submit">
|
||||
Update
|
||||
</x-forms.button>
|
||||
<x-modal-confirmation isErrorButton buttonTitle="Delete">
|
||||
This storage will be deleted <span class="font-bold dark:text-warning">{{ $storage->name }}</span>.
|
||||
It
|
||||
is
|
||||
not
|
||||
reversible. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm persistent storage deletion?"
|
||||
isErrorButton
|
||||
buttonTitle="Delete"
|
||||
submitAction="delete"
|
||||
:actions="['The selected persistent storage/volume will be permanently deleted.', 'If the persistent storage/volume is actvily used by a resource data will be lost.']"
|
||||
confirmationText="{{ $storage->name }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Storage Name below"
|
||||
shortConfirmationLabel="Storage Name"
|
||||
step3ButtonText="Permanently Delete Persistent Storage/Volume"
|
||||
/>
|
||||
</div>
|
||||
@endif
|
||||
</form>
|
||||
|
@@ -56,12 +56,18 @@
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<x-modal-confirmation isErrorButton action="revoke({{ data_get($token, 'id') }})">
|
||||
<x-slot:button-title>
|
||||
Revoke token
|
||||
</x-slot:button-title>
|
||||
This API Token will be deleted and anything using it will fail. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm API Token Revocation?"
|
||||
isErrorButton
|
||||
buttonTitle="Revoke token"
|
||||
submitAction="revoke({{ data_get($token, 'id') }})"
|
||||
:actions="['This API Token will be revoked and permanently deleted.', 'Any API call made with this token will fail.']"
|
||||
confirmationText="{{ $token->name }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the API Token Description below"
|
||||
shortConfirmationLabel="API Token Description"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Revoke API Token"
|
||||
/>
|
||||
</div>
|
||||
@empty
|
||||
<div>
|
||||
|
@@ -11,9 +11,18 @@
|
||||
Save
|
||||
</x-forms.button>
|
||||
@if (data_get($private_key, 'id') > 0)
|
||||
<x-modal-confirmation isErrorButton buttonTitle="Delete">
|
||||
This private key will be deleted. It is not reversible. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Private Key Deletion?"
|
||||
isErrorButton
|
||||
buttonTitle="Delete"
|
||||
submitAction="delete({{ $private_key->id }})"
|
||||
:actions="['This private key will be permanently deleted.', 'All servers connected to this private key will stop working.', 'Any git app using this private key will stop working.']"
|
||||
confirmationText="{{ $private_key->name }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Private Key Name below"
|
||||
shortConfirmationLabel="Private Key Name"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Delete Private Key"
|
||||
/>
|
||||
@endif
|
||||
</div>
|
||||
<x-forms.input id="private_key.name" label="Name" required />
|
||||
|
@@ -8,13 +8,29 @@
|
||||
</div>
|
||||
@if ($server->definedResources()->count() > 0)
|
||||
<div class="pb-2 text-red-500">You need to delete all resources before deleting this server.</div>
|
||||
<x-modal-confirmation disabled isErrorButton buttonTitle="Delete">
|
||||
This server will be deleted. It is not reversible. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Server Deletion?"
|
||||
isErrorButton
|
||||
buttonTitle="Delete"
|
||||
submitAction="delete"
|
||||
:actions="['This server will be permanently deleted.']"
|
||||
confirmationText="{{ $server->name }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Server Name below"
|
||||
shortConfirmationLabel="Server Name"
|
||||
step3ButtonText="Permanently Delete Server"
|
||||
/>
|
||||
@else
|
||||
<x-modal-confirmation isErrorButton buttonTitle="Delete">
|
||||
This server will be deleted. It is not reversible. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Server Deletion?"
|
||||
isErrorButton
|
||||
buttonTitle="Delete"
|
||||
submitAction="delete"
|
||||
:actions="['This server will be permanently deleted.']"
|
||||
confirmationText="{{ $server->name }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Server Name below"
|
||||
shortConfirmationLabel="Server Name"
|
||||
step2ButtonText="Permanently Delete Server"
|
||||
/>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
|
@@ -3,11 +3,15 @@
|
||||
<div class="flex gap-2">
|
||||
<h2>General</h2>
|
||||
@if ($server->id === 0)
|
||||
<x-modal-confirmation buttonTitle="Save" title="Change Localhost" action="submit">
|
||||
You could lose a lot of functionalities if you change the server details of the server where Coolify
|
||||
is
|
||||
running on.<br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Server Settings Change?"
|
||||
buttonTitle="Save"
|
||||
submitAction="submit"
|
||||
:actions="['You could lose a lot of functionalities if you change the server details of the server where Coolify is running on.']"
|
||||
:confirmWithText="false"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Save Server Settings"
|
||||
/>
|
||||
@else
|
||||
<x-forms.button type="submit">Save</x-forms.button>
|
||||
@if ($server->isFunctional())
|
||||
|
@@ -24,7 +24,17 @@
|
||||
</a>
|
||||
</button>
|
||||
@endif
|
||||
<x-modal-confirmation @click="$wire.dispatch('restartEvent')">
|
||||
<x-modal-confirmation
|
||||
title="Confirm Proxy Restart?"
|
||||
buttonTitle="Restart Proxy"
|
||||
submitAction="restart"
|
||||
:actions="['This proxy will be stopped and started again.', 'All resources hosted on coolify will be unavailable during the restart.']"
|
||||
:confirmWithText="false"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Restart Proxy"
|
||||
:dispatchEvent="true"
|
||||
dispatchEventType="restartEvent"
|
||||
>
|
||||
<x-slot:button-title>
|
||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
@@ -35,10 +45,18 @@
|
||||
</svg>
|
||||
Restart Proxy
|
||||
</x-slot:button-title>
|
||||
This proxy will be stopped and started. It is not reversible. <br>All resources will be unavailable
|
||||
during the restart. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation @click="$wire.dispatch('stopEvent')">
|
||||
<x-modal-confirmation
|
||||
title="Confirm Proxy Stopping?"
|
||||
buttonTitle="Stop Proxy"
|
||||
submitAction="stop(true)"
|
||||
:actions="['The coolify proxy will be stopped.', 'All resources hosted on coolify will be unavailable.']"
|
||||
:confirmWithText="false"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Stop Proxy"
|
||||
:dispatchEvent="true"
|
||||
dispatchEventType="stopEvent"
|
||||
>
|
||||
<x-slot:button-title>
|
||||
<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"
|
||||
@@ -51,8 +69,6 @@
|
||||
</svg>
|
||||
Stop Proxy
|
||||
</x-slot:button-title>
|
||||
This proxy will be stopped. It is not reversible. <br>All resources will be unavailable.
|
||||
<br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
</div>
|
||||
@else
|
||||
|
@@ -14,13 +14,31 @@
|
||||
</a>
|
||||
@endif
|
||||
@if ($applications->count() > 0)
|
||||
<x-modal-confirmation disabled isErrorButton buttonTitle="Delete">
|
||||
This source will be deleted. It is not reversible. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm GitHub App Deletion?"
|
||||
isErrorButton
|
||||
buttonTitle="Delete"
|
||||
submitAction="delete"
|
||||
:actions="['The selected GitHub App will be permanently deleted.']"
|
||||
confirmationText="{{ data_get($github_app, 'name') }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the GitHub App Name below"
|
||||
shortConfirmationLabel="GitHub App Name"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Delete GitHub App"
|
||||
/>
|
||||
@else
|
||||
<x-modal-confirmation isErrorButton buttonTitle="Delete">
|
||||
This source will be deleted. It is not reversible. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm GitHub App Deletion?"
|
||||
isErrorButton
|
||||
buttonTitle="Delete"
|
||||
submitAction="delete"
|
||||
:actions="['The selected GitHub App will be permanently deleted.']"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the GitHub App Name below"
|
||||
shortConfirmationLabel="GitHub App Name"
|
||||
confirmationText="{{ data_get($github_app, 'name') }}"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Delete GitHub App"
|
||||
/>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@@ -160,9 +178,18 @@
|
||||
<div class="flex items-center gap-2 pb-4">
|
||||
<h1>GitHub App</h1>
|
||||
<div class="flex gap-2">
|
||||
<x-modal-confirmation isErrorButton buttonTitle="Delete">
|
||||
This source will be deleted. It is not reversible. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm GitHub App Deletion?"
|
||||
isErrorButton
|
||||
buttonTitle="Delete"
|
||||
submitAction="delete"
|
||||
:actions="['The selected GitHub App will be permanently deleted.']"
|
||||
confirmationText="{{ data_get($github_app, 'name') }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the GitHub App Name below"
|
||||
shortConfirmationLabel="GitHub App Name"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Delete GitHub App"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-10 rounded alert-error">
|
||||
|
@@ -16,9 +16,18 @@
|
||||
<x-forms.button wire:click="test_s3_connection">
|
||||
Validate Connection
|
||||
</x-forms.button>
|
||||
<x-modal-confirmation isErrorButton buttonTitle="Delete">
|
||||
This storage will be deleted. It is not reversible. Your data won't be touched!<br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Storage Deletion?"
|
||||
isErrorButton
|
||||
buttonTitle="Delete"
|
||||
submitAction="delete({{ $storage->id }})"
|
||||
:actions="['The selected storage location will be permanently deleted from Coolify.', 'If the storage location is in use by any backup jobs those backup jobs will only store the backup locally on the server.']"
|
||||
confirmationText="{{ $storage->name }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Storage Name below"
|
||||
shortConfirmationLabel="Storage Name"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Delete Storage"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input label="Name" id="storage.name" />
|
||||
|
@@ -20,9 +20,18 @@
|
||||
<div class="w-[500px]">
|
||||
<x-forms.input readonly label="Deploy Webhook URL" id="webhook" />
|
||||
</div>
|
||||
<x-modal-confirmation isHighlighted buttonTitle="Redeploy All" action="redeploy_all">
|
||||
All resources will be redeployed.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Redeploy all resources with this tag?"
|
||||
isHighlighted
|
||||
buttonTitle="Redeploy All"
|
||||
submitAction="redeploy_all"
|
||||
:actions="['All resources with this tag will be redeployed.', 'During redeploy resources will be temporarily unavailable.']"
|
||||
confirmationText="{{ $oneTag->name }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Tag Name below"
|
||||
shortConfirmationLabel="Tag Name"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Redeploy All"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-2 pt-4 lg:grid-cols-2 xl:grid-cols-3">
|
||||
@foreach ($applications as $application)
|
||||
|
@@ -24,9 +24,18 @@
|
||||
<div class="w-[500px]">
|
||||
<x-forms.input readonly label="Deploy Webhook URL" id="webhook" />
|
||||
</div>
|
||||
<x-modal-confirmation isHighlighted buttonTitle="Redeploy All" action="redeploy_all">
|
||||
All resources will be redeployed.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Redeploy all resources with this tag?"
|
||||
isHighlighted
|
||||
buttonTitle="Redeploy All"
|
||||
submitAction="redeploy_all"
|
||||
:actions="['All resources with this tag will be redeployed.', 'During redeploy resources will be temporarily unavailable.']"
|
||||
confirmationText="{{ $oneTag->name }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Tag Name below"
|
||||
shortConfirmationLabel="Tag Name"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Redeploy All"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-2 pt-4 lg:grid-cols-2 xl:grid-cols-3">
|
||||
@foreach ($applications as $application)
|
||||
|
@@ -15,14 +15,17 @@
|
||||
<div>{{ $user->email }}</div>
|
||||
<div class="flex-1"></div>
|
||||
<div class="flex items-center justify-center gap-2 mx-4 text-xs font-bold ">
|
||||
<x-modal-confirmation isErrorButton action="delete({{ $user->id }})" buttonTitle="Delete">
|
||||
This will delete all resources (application, databases, services, configurations, servers,
|
||||
private keys, tags, etc.) from Coolify and <span
|
||||
class="font-bold text-red-500 dark:text-warning">from the server (if it's reachable)</span>.
|
||||
<br> <br>
|
||||
It is not reversible. <br><br>
|
||||
<div class="font-bold text-red-500 dark:text-white">Think twice!</div>
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm User Deletion?"
|
||||
buttonTitle="Delete"
|
||||
isErrorButton
|
||||
submitAction="delete({{ $user->id }})"
|
||||
:actions="['The selected user will be permanently deleted from Coolify and the database.', 'All resources (application, databases, services, configurations, servers, private keys, tags, etc.) related to this user will be deleted from Coolify and from the server (if the server is reachable).']"
|
||||
confirmationText="{{ $user->name }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the User Name below"
|
||||
shortConfirmationLabel="User Name"
|
||||
step3ButtonText="Permanently Delete User"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
|
@@ -31,9 +31,18 @@
|
||||
@else
|
||||
@if (currentTeam()->isEmpty())
|
||||
<div class="pb-4">This will delete your team. Beware! There is no coming back!</div>
|
||||
<x-modal-confirmation isErrorButton buttonTitle="Delete">
|
||||
This team be deleted. It is not reversible. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation
|
||||
title="Confirm Team Deletion?"
|
||||
buttonTitle="Delete"
|
||||
isErrorButton
|
||||
submitAction="delete({{ currentTeam()->id }})"
|
||||
:actions="['The current team will be permanently deleted from Coolify and the database.']"
|
||||
confirmationText="{{ currentTeam()->name }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Team Name below"
|
||||
shortConfirmationLabel="Team Name"
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Delete Team"
|
||||
/>
|
||||
@else
|
||||
<div>
|
||||
<div class="pb-4">You need to delete the following resources to be able to delete the team:</div>
|
||||
|
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"coolify": {
|
||||
"v4": {
|
||||
"version": "4.0.0-beta.341"
|
||||
"version": "4.0.0-beta.342"
|
||||
},
|
||||
"nightly": {
|
||||
"version": "4.0.0-beta.342"
|
||||
"version": "4.0.0-beta.343"
|
||||
},
|
||||
"helper": {
|
||||
"version": "1.0.1"
|
||||
@@ -13,4 +13,4 @@
|
||||
"version": "1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user