Merge pull request #4777 from peaklabs-dev/fix-cloning
fix/feat: Improves Cloning
This commit is contained in:
@@ -863,7 +863,7 @@ class ApplicationsController extends Controller
|
|||||||
$application->settings->save();
|
$application->settings->save();
|
||||||
}
|
}
|
||||||
$application->refresh();
|
$application->refresh();
|
||||||
if (! $application->settings->is_container_label_readonly_enabled) {
|
if ($application->settings->is_container_label_readonly_enabled) {
|
||||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||||
$application->save();
|
$application->save();
|
||||||
}
|
}
|
||||||
@@ -964,7 +964,7 @@ class ApplicationsController extends Controller
|
|||||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||||
$application->settings->save();
|
$application->settings->save();
|
||||||
}
|
}
|
||||||
if (! $application->settings->is_container_label_readonly_enabled) {
|
if ($application->settings->is_container_label_readonly_enabled) {
|
||||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||||
$application->save();
|
$application->save();
|
||||||
}
|
}
|
||||||
@@ -1061,7 +1061,7 @@ class ApplicationsController extends Controller
|
|||||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||||
$application->settings->save();
|
$application->settings->save();
|
||||||
}
|
}
|
||||||
if (! $application->settings->is_container_label_readonly_enabled) {
|
if ($application->settings->is_container_label_readonly_enabled) {
|
||||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||||
$application->save();
|
$application->save();
|
||||||
}
|
}
|
||||||
@@ -1150,7 +1150,7 @@ class ApplicationsController extends Controller
|
|||||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||||
$application->settings->save();
|
$application->settings->save();
|
||||||
}
|
}
|
||||||
if (! $application->settings->is_container_label_readonly_enabled) {
|
if ($application->settings->is_container_label_readonly_enabled) {
|
||||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||||
$application->save();
|
$application->save();
|
||||||
}
|
}
|
||||||
@@ -1214,7 +1214,7 @@ class ApplicationsController extends Controller
|
|||||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||||
$application->settings->save();
|
$application->settings->save();
|
||||||
}
|
}
|
||||||
if (! $application->settings->is_container_label_readonly_enabled) {
|
if ($application->settings->is_container_label_readonly_enabled) {
|
||||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||||
$application->save();
|
$application->save();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1682,7 +1682,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->application->custom_labels = base64_encode($labels->implode("\n"));
|
$this->application->custom_labels = base64_encode($labels->implode("\n"));
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
} else {
|
} else {
|
||||||
if (! $this->application->settings->is_container_label_readonly_enabled) {
|
if ($this->application->settings->is_container_label_readonly_enabled) {
|
||||||
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
104
app/Jobs/VolumeCloneJob.php
Normal file
104
app/Jobs/VolumeCloneJob.php
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\LocalPersistentVolume;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class VolumeCloneJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected string $cloneDir = '/data/coolify/clone';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected string $sourceVolume,
|
||||||
|
protected string $targetVolume,
|
||||||
|
protected Server $sourceServer,
|
||||||
|
protected ?Server $targetServer,
|
||||||
|
protected LocalPersistentVolume $persistentVolume
|
||||||
|
) {
|
||||||
|
$this->onQueue('high');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (! $this->targetServer || $this->targetServer->id === $this->sourceServer->id) {
|
||||||
|
$this->cloneLocalVolume();
|
||||||
|
} else {
|
||||||
|
$this->cloneRemoteVolume();
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error("Failed to copy volume data for {$this->sourceVolume}: ".$e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function cloneLocalVolume()
|
||||||
|
{
|
||||||
|
instant_remote_process([
|
||||||
|
"docker volume create $this->targetVolume",
|
||||||
|
"docker run --rm -v $this->sourceVolume:/source -v $this->targetVolume:/target alpine sh -c 'cp -a /source/. /target/ && chown -R 1000:1000 /target'",
|
||||||
|
], $this->sourceServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function cloneRemoteVolume()
|
||||||
|
{
|
||||||
|
$sourceCloneDir = "{$this->cloneDir}/{$this->sourceVolume}";
|
||||||
|
$targetCloneDir = "{$this->cloneDir}/{$this->targetVolume}";
|
||||||
|
|
||||||
|
try {
|
||||||
|
instant_remote_process([
|
||||||
|
"mkdir -p $sourceCloneDir",
|
||||||
|
"chmod 777 $sourceCloneDir",
|
||||||
|
"docker run --rm -v $this->sourceVolume:/source -v $sourceCloneDir:/clone alpine sh -c 'cd /source && tar czf /clone/volume-data.tar.gz .'",
|
||||||
|
], $this->sourceServer);
|
||||||
|
|
||||||
|
instant_remote_process([
|
||||||
|
"mkdir -p $targetCloneDir",
|
||||||
|
"chmod 777 $targetCloneDir",
|
||||||
|
], $this->targetServer);
|
||||||
|
|
||||||
|
instant_scp(
|
||||||
|
"$sourceCloneDir/volume-data.tar.gz",
|
||||||
|
"$targetCloneDir/volume-data.tar.gz",
|
||||||
|
$this->sourceServer,
|
||||||
|
$this->targetServer
|
||||||
|
);
|
||||||
|
|
||||||
|
instant_remote_process([
|
||||||
|
"docker volume create $this->targetVolume",
|
||||||
|
"docker run --rm -v $this->targetVolume:/target -v $targetCloneDir:/clone alpine sh -c 'cd /target && tar xzf /clone/volume-data.tar.gz && chown -R 1000:1000 /target'",
|
||||||
|
], $this->targetServer);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error("Failed to clone volume {$this->sourceVolume} to {$this->targetVolume}: ".$e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
instant_remote_process([
|
||||||
|
"rm -rf $sourceCloneDir",
|
||||||
|
], $this->sourceServer, false);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::warning('Failed to clean up source server clone directory: '.$e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($this->targetServer) {
|
||||||
|
instant_remote_process([
|
||||||
|
"rm -rf $targetCloneDir",
|
||||||
|
], $this->targetServer, false);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::warning('Failed to clean up target server clone directory: '.$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -153,7 +153,7 @@ class General extends Component
|
|||||||
$this->is_preserve_repository_enabled = $this->application->settings->is_preserve_repository_enabled;
|
$this->is_preserve_repository_enabled = $this->application->settings->is_preserve_repository_enabled;
|
||||||
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
|
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
|
||||||
$this->customLabels = $this->application->parseContainerLabels();
|
$this->customLabels = $this->application->parseContainerLabels();
|
||||||
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) {
|
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && $this->application->settings->is_container_label_readonly_enabled === true) {
|
||||||
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
||||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Project;
|
namespace App\Livewire\Project;
|
||||||
|
|
||||||
|
use App\Actions\Application\StopApplication;
|
||||||
|
use App\Actions\Database\StartDatabase;
|
||||||
|
use App\Actions\Database\StopDatabase;
|
||||||
|
use App\Actions\Service\StartService;
|
||||||
|
use App\Actions\Service\StopService;
|
||||||
|
use App\Jobs\VolumeCloneJob;
|
||||||
use App\Models\Environment;
|
use App\Models\Environment;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
@@ -34,6 +40,8 @@ class CloneMe extends Component
|
|||||||
|
|
||||||
public string $newName = '';
|
public string $newName = '';
|
||||||
|
|
||||||
|
public bool $cloneVolumeData = false;
|
||||||
|
|
||||||
protected $messages = [
|
protected $messages = [
|
||||||
'selectedServer' => 'Please select a server.',
|
'selectedServer' => 'Please select a server.',
|
||||||
'selectedDestination' => 'Please select a server & destination.',
|
'selectedDestination' => 'Please select a server & destination.',
|
||||||
@@ -50,6 +58,11 @@ class CloneMe extends Component
|
|||||||
$this->newName = str($this->project->name.'-clone-'.(string) new Cuid2)->slug();
|
$this->newName = str($this->project->name.'-clone-'.(string) new Cuid2)->slug();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toggleVolumeCloning(bool $value)
|
||||||
|
{
|
||||||
|
$this->cloneVolumeData = $value;
|
||||||
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.clone-me');
|
return view('livewire.project.clone-me');
|
||||||
@@ -108,35 +121,153 @@ class CloneMe extends Component
|
|||||||
$databases = $this->environment->databases();
|
$databases = $this->environment->databases();
|
||||||
$services = $this->environment->services;
|
$services = $this->environment->services;
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
|
$applicationSettings = $application->settings;
|
||||||
|
|
||||||
$uuid = (string) new Cuid2;
|
$uuid = (string) new Cuid2;
|
||||||
$newApplication = $application->replicate()->fill([
|
$url = $application->fqdn;
|
||||||
|
if ($this->server->proxyType() !== 'NONE' && $applicationSettings->is_container_label_readonly_enabled === true) {
|
||||||
|
$url = generateFqdn($this->server, $uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
$newApplication = $application->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
'additional_servers_count',
|
||||||
|
'additional_networks_count',
|
||||||
|
])->fill([
|
||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'fqdn' => generateFqdn($this->server, $uuid),
|
'fqdn' => $url,
|
||||||
'status' => 'exited',
|
'status' => 'exited',
|
||||||
'environment_id' => $environment->id,
|
'environment_id' => $environment->id,
|
||||||
// This is not correct, but we need to set it to something
|
|
||||||
'destination_id' => $this->selectedDestination,
|
'destination_id' => $this->selectedDestination,
|
||||||
]);
|
]);
|
||||||
$newApplication->save();
|
$newApplication->save();
|
||||||
|
|
||||||
|
if ($newApplication->destination->server->proxyType() !== 'NONE' && $applicationSettings->is_container_label_readonly_enabled === true) {
|
||||||
|
$customLabels = str(implode('|coolify|', generateLabelsApplication($newApplication)))->replace('|coolify|', "\n");
|
||||||
|
$newApplication->custom_labels = base64_encode($customLabels);
|
||||||
|
$newApplication->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$newApplication->settings()->delete();
|
||||||
|
if ($applicationSettings) {
|
||||||
|
$newApplicationSettings = $applicationSettings->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'application_id' => $newApplication->id,
|
||||||
|
]);
|
||||||
|
$newApplicationSettings->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$tags = $application->tags;
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$newApplication->tags()->attach($tag->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scheduledTasks = $application->scheduled_tasks()->get();
|
||||||
|
foreach ($scheduledTasks as $task) {
|
||||||
|
$newTask = $task->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'uuid' => (string) new Cuid2,
|
||||||
|
'application_id' => $newApplication->id,
|
||||||
|
'team_id' => currentTeam()->id,
|
||||||
|
]);
|
||||||
|
$newTask->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$applicationPreviews = $application->previews()->get();
|
||||||
|
foreach ($applicationPreviews as $preview) {
|
||||||
|
$newPreview = $preview->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'application_id' => $newApplication->id,
|
||||||
|
'status' => 'exited',
|
||||||
|
]);
|
||||||
|
$newPreview->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$persistentVolumes = $application->persistentStorages()->get();
|
||||||
|
foreach ($persistentVolumes as $volume) {
|
||||||
|
$newName = '';
|
||||||
|
if (str_starts_with($volume->name, $application->uuid)) {
|
||||||
|
$newName = str($volume->name)->replace($application->uuid, $newApplication->uuid);
|
||||||
|
} else {
|
||||||
|
$newName = $newApplication->uuid.'-'.$volume->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
$newPersistentVolume = $volume->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'name' => $newName,
|
||||||
|
'resource_id' => $newApplication->id,
|
||||||
|
]);
|
||||||
|
$newPersistentVolume->save();
|
||||||
|
|
||||||
|
if ($this->cloneVolumeData) {
|
||||||
|
try {
|
||||||
|
StopApplication::dispatch($application, false, false);
|
||||||
|
$sourceVolume = $volume->name;
|
||||||
|
$targetVolume = $newPersistentVolume->name;
|
||||||
|
$sourceServer = $application->destination->server;
|
||||||
|
$targetServer = $newApplication->destination->server;
|
||||||
|
|
||||||
|
VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume);
|
||||||
|
|
||||||
|
queue_application_deployment(
|
||||||
|
deployment_uuid: (string) new Cuid2,
|
||||||
|
application: $application,
|
||||||
|
server: $sourceServer,
|
||||||
|
destination: $application->destination,
|
||||||
|
no_questions_asked: true
|
||||||
|
);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileStorages = $application->fileStorages()->get();
|
||||||
|
foreach ($fileStorages as $storage) {
|
||||||
|
$newStorage = $storage->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'resource_id' => $newApplication->id,
|
||||||
|
]);
|
||||||
|
$newStorage->save();
|
||||||
|
}
|
||||||
|
|
||||||
$environmentVaribles = $application->environment_variables()->get();
|
$environmentVaribles = $application->environment_variables()->get();
|
||||||
foreach ($environmentVaribles as $environmentVarible) {
|
foreach ($environmentVaribles as $environmentVarible) {
|
||||||
$newEnvironmentVariable = $environmentVarible->replicate()->fill([
|
$newEnvironmentVariable = $environmentVarible->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
'resourceable_id' => $newApplication->id,
|
'resourceable_id' => $newApplication->id,
|
||||||
]);
|
]);
|
||||||
$newEnvironmentVariable->save();
|
$newEnvironmentVariable->save();
|
||||||
}
|
}
|
||||||
$persistentVolumes = $application->persistentStorages()->get();
|
|
||||||
foreach ($persistentVolumes as $volume) {
|
|
||||||
$newPersistentVolume = $volume->replicate()->fill([
|
|
||||||
'name' => $newApplication->uuid.'-'.str($volume->name)->afterLast('-'),
|
|
||||||
'resource_id' => $newApplication->id,
|
|
||||||
]);
|
|
||||||
$newPersistentVolume->save();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($databases as $database) {
|
foreach ($databases as $database) {
|
||||||
$uuid = (string) new Cuid2;
|
$uuid = (string) new Cuid2;
|
||||||
$newDatabase = $database->replicate()->fill([
|
$newDatabase = $database->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'status' => 'exited',
|
'status' => 'exited',
|
||||||
'started_at' => null,
|
'started_at' => null,
|
||||||
@@ -144,42 +275,294 @@ class CloneMe extends Component
|
|||||||
'destination_id' => $this->selectedDestination,
|
'destination_id' => $this->selectedDestination,
|
||||||
]);
|
]);
|
||||||
$newDatabase->save();
|
$newDatabase->save();
|
||||||
|
|
||||||
|
$tags = $database->tags;
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$newDatabase->tags()->attach($tag->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$newDatabase->persistentStorages()->delete();
|
||||||
|
$persistentVolumes = $database->persistentStorages()->get();
|
||||||
|
foreach ($persistentVolumes as $volume) {
|
||||||
|
$originalName = $volume->name;
|
||||||
|
$newName = '';
|
||||||
|
|
||||||
|
if (str_starts_with($originalName, 'postgres-data-')) {
|
||||||
|
$newName = 'postgres-data-'.$newDatabase->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'mysql-data-')) {
|
||||||
|
$newName = 'mysql-data-'.$newDatabase->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'redis-data-')) {
|
||||||
|
$newName = 'redis-data-'.$newDatabase->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'clickhouse-data-')) {
|
||||||
|
$newName = 'clickhouse-data-'.$newDatabase->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'mariadb-data-')) {
|
||||||
|
$newName = 'mariadb-data-'.$newDatabase->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'mongodb-data-')) {
|
||||||
|
$newName = 'mongodb-data-'.$newDatabase->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'keydb-data-')) {
|
||||||
|
$newName = 'keydb-data-'.$newDatabase->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'dragonfly-data-')) {
|
||||||
|
$newName = 'dragonfly-data-'.$newDatabase->uuid;
|
||||||
|
} else {
|
||||||
|
if (str_starts_with($volume->name, $database->uuid)) {
|
||||||
|
$newName = str($volume->name)->replace($database->uuid, $newDatabase->uuid);
|
||||||
|
} else {
|
||||||
|
$newName = $newDatabase->uuid.'-'.$volume->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$newPersistentVolume = $volume->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'name' => $newName,
|
||||||
|
'resource_id' => $newDatabase->id,
|
||||||
|
]);
|
||||||
|
$newPersistentVolume->save();
|
||||||
|
|
||||||
|
if ($this->cloneVolumeData) {
|
||||||
|
try {
|
||||||
|
StopDatabase::dispatch($database);
|
||||||
|
$sourceVolume = $volume->name;
|
||||||
|
$targetVolume = $newPersistentVolume->name;
|
||||||
|
$sourceServer = $database->destination->server;
|
||||||
|
$targetServer = $newDatabase->destination->server;
|
||||||
|
|
||||||
|
VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume);
|
||||||
|
|
||||||
|
StartDatabase::dispatch($database);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileStorages = $database->fileStorages()->get();
|
||||||
|
foreach ($fileStorages as $storage) {
|
||||||
|
$newStorage = $storage->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'resource_id' => $newDatabase->id,
|
||||||
|
]);
|
||||||
|
$newStorage->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scheduledBackups = $database->scheduledBackups()->get();
|
||||||
|
foreach ($scheduledBackups as $backup) {
|
||||||
|
$uuid = (string) new Cuid2;
|
||||||
|
$newBackup = $backup->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'uuid' => $uuid,
|
||||||
|
'database_id' => $newDatabase->id,
|
||||||
|
'database_type' => $newDatabase->getMorphClass(),
|
||||||
|
'team_id' => currentTeam()->id,
|
||||||
|
]);
|
||||||
|
$newBackup->save();
|
||||||
|
}
|
||||||
|
|
||||||
$environmentVaribles = $database->environment_variables()->get();
|
$environmentVaribles = $database->environment_variables()->get();
|
||||||
foreach ($environmentVaribles as $environmentVarible) {
|
foreach ($environmentVaribles as $environmentVarible) {
|
||||||
$payload = [];
|
$payload = [];
|
||||||
$payload['resourceable_id'] = $newDatabase->id;
|
$payload['resourceable_id'] = $newDatabase->id;
|
||||||
$payload['resourceable_type'] = $newDatabase->getMorphClass();
|
$payload['resourceable_type'] = $newDatabase->getMorphClass();
|
||||||
$newEnvironmentVariable = $environmentVarible->replicate()->fill($payload);
|
$newEnvironmentVariable = $environmentVarible->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill($payload);
|
||||||
$newEnvironmentVariable->save();
|
$newEnvironmentVariable->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($services as $service) {
|
foreach ($services as $service) {
|
||||||
$uuid = (string) new Cuid2;
|
$uuid = (string) new Cuid2;
|
||||||
$newService = $service->replicate()->fill([
|
$newService = $service->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'environment_id' => $environment->id,
|
'environment_id' => $environment->id,
|
||||||
'destination_id' => $this->selectedDestination,
|
'destination_id' => $this->selectedDestination,
|
||||||
]);
|
]);
|
||||||
$newService->save();
|
$newService->save();
|
||||||
|
|
||||||
|
$tags = $service->tags;
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$newService->tags()->attach($tag->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scheduledTasks = $service->scheduled_tasks()->get();
|
||||||
|
foreach ($scheduledTasks as $task) {
|
||||||
|
$newTask = $task->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'uuid' => (string) new Cuid2,
|
||||||
|
'service_id' => $newService->id,
|
||||||
|
'team_id' => currentTeam()->id,
|
||||||
|
]);
|
||||||
|
$newTask->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$environmentVariables = $service->environment_variables()->get();
|
||||||
|
foreach ($environmentVariables as $environmentVariable) {
|
||||||
|
$newEnvironmentVariable = $environmentVariable->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'resourceable_id' => $newService->id,
|
||||||
|
'resourceable_type' => $newService->getMorphClass(),
|
||||||
|
]);
|
||||||
|
$newEnvironmentVariable->save();
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($newService->applications() as $application) {
|
foreach ($newService->applications() as $application) {
|
||||||
$application->update([
|
$application->update([
|
||||||
'status' => 'exited',
|
'status' => 'exited',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$persistentVolumes = $application->persistentStorages()->get();
|
||||||
|
foreach ($persistentVolumes as $volume) {
|
||||||
|
$newName = '';
|
||||||
|
if (str_starts_with($volume->name, $application->uuid)) {
|
||||||
|
$newName = str($volume->name)->replace($application->uuid, $application->uuid);
|
||||||
|
} else {
|
||||||
|
$newName = $application->uuid.'-'.$volume->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
$newPersistentVolume = $volume->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'name' => $newName,
|
||||||
|
'resource_id' => $application->id,
|
||||||
|
]);
|
||||||
|
$newPersistentVolume->save();
|
||||||
|
|
||||||
|
if ($this->cloneVolumeData) {
|
||||||
|
try {
|
||||||
|
StopService::dispatch($application, false, false);
|
||||||
|
$sourceVolume = $volume->name;
|
||||||
|
$targetVolume = $newPersistentVolume->name;
|
||||||
|
$sourceServer = $application->service->destination->server;
|
||||||
|
$targetServer = $newService->destination->server;
|
||||||
|
|
||||||
|
VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume);
|
||||||
|
|
||||||
|
StartService::dispatch($application);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileStorages = $application->fileStorages()->get();
|
||||||
|
foreach ($fileStorages as $storage) {
|
||||||
|
$newStorage = $storage->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'resource_id' => $application->id,
|
||||||
|
]);
|
||||||
|
$newStorage->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($newService->databases() as $database) {
|
foreach ($newService->databases() as $database) {
|
||||||
$database->update([
|
$database->update([
|
||||||
'status' => 'exited',
|
'status' => 'exited',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$persistentVolumes = $database->persistentStorages()->get();
|
||||||
|
foreach ($persistentVolumes as $volume) {
|
||||||
|
$newName = '';
|
||||||
|
if (str_starts_with($volume->name, $database->uuid)) {
|
||||||
|
$newName = str($volume->name)->replace($database->uuid, $database->uuid);
|
||||||
|
} else {
|
||||||
|
$newName = $database->uuid.'-'.$volume->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
$newPersistentVolume = $volume->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'name' => $newName,
|
||||||
|
'resource_id' => $database->id,
|
||||||
|
]);
|
||||||
|
$newPersistentVolume->save();
|
||||||
|
|
||||||
|
if ($this->cloneVolumeData) {
|
||||||
|
try {
|
||||||
|
StopService::dispatch($database->service, false, false);
|
||||||
|
$sourceVolume = $volume->name;
|
||||||
|
$targetVolume = $newPersistentVolume->name;
|
||||||
|
$sourceServer = $database->service->destination->server;
|
||||||
|
$targetServer = $newService->destination->server;
|
||||||
|
|
||||||
|
VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume);
|
||||||
|
|
||||||
|
StartService::dispatch($database->service);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileStorages = $database->fileStorages()->get();
|
||||||
|
foreach ($fileStorages as $storage) {
|
||||||
|
$newStorage = $storage->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'resource_id' => $database->id,
|
||||||
|
]);
|
||||||
|
$newStorage->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scheduledBackups = $database->scheduledBackups()->get();
|
||||||
|
foreach ($scheduledBackups as $backup) {
|
||||||
|
$uuid = (string) new Cuid2;
|
||||||
|
$newBackup = $backup->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'uuid' => $uuid,
|
||||||
|
'database_id' => $database->id,
|
||||||
|
'database_type' => $database->getMorphClass(),
|
||||||
|
'team_id' => currentTeam()->id,
|
||||||
|
]);
|
||||||
|
$newBackup->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$newService->parse();
|
$newService->parse();
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->route('project.resource.index', [
|
|
||||||
'project_uuid' => $project->uuid,
|
|
||||||
'environment_uuid' => $environment->uuid,
|
|
||||||
]);
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return handleError($e, $this);
|
handleError($e, $this);
|
||||||
|
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
if (! isset($e)) {
|
||||||
|
return redirect()->route('project.resource.index', [
|
||||||
|
'project_uuid' => $project->uuid,
|
||||||
|
'environment_uuid' => $environment->uuid,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Project\Shared;
|
namespace App\Livewire\Project\Shared;
|
||||||
|
|
||||||
|
use App\Actions\Application\StopApplication;
|
||||||
|
use App\Actions\Database\StartDatabase;
|
||||||
|
use App\Actions\Database\StopDatabase;
|
||||||
|
use App\Actions\Service\StartService;
|
||||||
|
use App\Actions\Service\StopService;
|
||||||
|
use App\Jobs\VolumeCloneJob;
|
||||||
use App\Models\Environment;
|
use App\Models\Environment;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\StandaloneDocker;
|
use App\Models\StandaloneDocker;
|
||||||
@@ -21,6 +27,8 @@ class ResourceOperations extends Component
|
|||||||
|
|
||||||
public $servers;
|
public $servers;
|
||||||
|
|
||||||
|
public bool $cloneVolumeData = false;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$parameters = get_route_parameters();
|
$parameters = get_route_parameters();
|
||||||
@@ -30,6 +38,11 @@ class ResourceOperations extends Component
|
|||||||
$this->servers = currentTeam()->servers;
|
$this->servers = currentTeam()->servers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toggleVolumeCloning(bool $value)
|
||||||
|
{
|
||||||
|
$this->cloneVolumeData = $value;
|
||||||
|
}
|
||||||
|
|
||||||
public function cloneTo($destination_id)
|
public function cloneTo($destination_id)
|
||||||
{
|
{
|
||||||
$new_destination = StandaloneDocker::find($destination_id);
|
$new_destination = StandaloneDocker::find($destination_id);
|
||||||
@@ -41,42 +54,148 @@ class ResourceOperations extends Component
|
|||||||
}
|
}
|
||||||
$uuid = (string) new Cuid2;
|
$uuid = (string) new Cuid2;
|
||||||
$server = $new_destination->server;
|
$server = $new_destination->server;
|
||||||
|
|
||||||
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
|
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
|
||||||
$name = 'clone-of-'.str($this->resource->name)->limit(20).'-'.$uuid;
|
$name = 'clone-of-'.str($this->resource->name)->limit(20).'-'.$uuid;
|
||||||
|
$applicationSettings = $this->resource->settings;
|
||||||
|
$url = $this->resource->fqdn;
|
||||||
|
|
||||||
$new_resource = $this->resource->replicate()->fill([
|
if ($server->proxyType() !== 'NONE' && $applicationSettings->is_container_label_readonly_enabled === true) {
|
||||||
|
$url = generateFqdn($server, $uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_resource = $this->resource->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
'additional_servers_count',
|
||||||
|
'additional_networks_count',
|
||||||
|
])->fill([
|
||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'fqdn' => generateFqdn($server, $uuid),
|
'fqdn' => $url,
|
||||||
'status' => 'exited',
|
'status' => 'exited',
|
||||||
'destination_id' => $new_destination->id,
|
'destination_id' => $new_destination->id,
|
||||||
]);
|
]);
|
||||||
$new_resource->save();
|
$new_resource->save();
|
||||||
if ($new_resource->destination->server->proxyType() !== 'NONE') {
|
|
||||||
|
if ($new_resource->destination->server->proxyType() !== 'NONE' && $applicationSettings->is_container_label_readonly_enabled === true) {
|
||||||
$customLabels = str(implode('|coolify|', generateLabelsApplication($new_resource)))->replace('|coolify|', "\n");
|
$customLabels = str(implode('|coolify|', generateLabelsApplication($new_resource)))->replace('|coolify|', "\n");
|
||||||
$new_resource->custom_labels = base64_encode($customLabels);
|
$new_resource->custom_labels = base64_encode($customLabels);
|
||||||
$new_resource->save();
|
$new_resource->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$new_resource->settings()->delete();
|
||||||
|
if ($applicationSettings) {
|
||||||
|
$newApplicationSettings = $applicationSettings->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'application_id' => $new_resource->id,
|
||||||
|
]);
|
||||||
|
$newApplicationSettings->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$tags = $this->resource->tags;
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$new_resource->tags()->attach($tag->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scheduledTasks = $this->resource->scheduled_tasks()->get();
|
||||||
|
foreach ($scheduledTasks as $task) {
|
||||||
|
$newTask = $task->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'uuid' => (string) new Cuid2,
|
||||||
|
'application_id' => $new_resource->id,
|
||||||
|
'team_id' => currentTeam()->id,
|
||||||
|
]);
|
||||||
|
$newTask->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$applicationPreviews = $this->resource->previews()->get();
|
||||||
|
foreach ($applicationPreviews as $preview) {
|
||||||
|
$newPreview = $preview->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'application_id' => $new_resource->id,
|
||||||
|
'status' => 'exited',
|
||||||
|
]);
|
||||||
|
$newPreview->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$persistentVolumes = $this->resource->persistentStorages()->get();
|
||||||
|
foreach ($persistentVolumes as $volume) {
|
||||||
|
$newName = '';
|
||||||
|
if (str_starts_with($volume->name, $this->resource->uuid)) {
|
||||||
|
$newName = str($volume->name)->replace($this->resource->uuid, $new_resource->uuid);
|
||||||
|
} else {
|
||||||
|
$newName = $new_resource->uuid.'-'.str($volume->name)->afterLast('-');
|
||||||
|
}
|
||||||
|
|
||||||
|
$newPersistentVolume = $volume->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'name' => $newName,
|
||||||
|
'resource_id' => $new_resource->id,
|
||||||
|
]);
|
||||||
|
$newPersistentVolume->save();
|
||||||
|
|
||||||
|
if ($this->cloneVolumeData) {
|
||||||
|
try {
|
||||||
|
StopApplication::dispatch($this->resource, false, false);
|
||||||
|
$sourceVolume = $volume->name;
|
||||||
|
$targetVolume = $newPersistentVolume->name;
|
||||||
|
$sourceServer = $this->resource->destination->server;
|
||||||
|
$targetServer = $new_resource->destination->server;
|
||||||
|
|
||||||
|
VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume);
|
||||||
|
|
||||||
|
queue_application_deployment(
|
||||||
|
deployment_uuid: (string) new Cuid2,
|
||||||
|
application: $this->resource,
|
||||||
|
server: $sourceServer,
|
||||||
|
destination: $this->resource->destination,
|
||||||
|
no_questions_asked: true
|
||||||
|
);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileStorages = $this->resource->fileStorages()->get();
|
||||||
|
foreach ($fileStorages as $storage) {
|
||||||
|
$newStorage = $storage->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'resource_id' => $new_resource->id,
|
||||||
|
]);
|
||||||
|
$newStorage->save();
|
||||||
|
}
|
||||||
|
|
||||||
$environmentVaribles = $this->resource->environment_variables()->get();
|
$environmentVaribles = $this->resource->environment_variables()->get();
|
||||||
foreach ($environmentVaribles as $environmentVarible) {
|
foreach ($environmentVaribles as $environmentVarible) {
|
||||||
$newEnvironmentVariable = $environmentVarible->replicate()->fill([
|
$newEnvironmentVariable = $environmentVarible->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
'resourceable_id' => $new_resource->id,
|
'resourceable_id' => $new_resource->id,
|
||||||
'resourceable_type' => $new_resource->getMorphClass(),
|
'resourceable_type' => $new_resource->getMorphClass(),
|
||||||
]);
|
]);
|
||||||
$newEnvironmentVariable->save();
|
$newEnvironmentVariable->save();
|
||||||
}
|
}
|
||||||
$persistentVolumes = $this->resource->persistentStorages()->get();
|
|
||||||
foreach ($persistentVolumes as $volume) {
|
|
||||||
$volumeName = str($volume->name)->replace($this->resource->uuid, $new_resource->uuid)->value();
|
|
||||||
if ($volumeName === $volume->name) {
|
|
||||||
$volumeName = $new_resource->uuid.'-'.str($volume->name)->afterLast('-');
|
|
||||||
}
|
|
||||||
$newPersistentVolume = $volume->replicate()->fill([
|
|
||||||
'name' => $volumeName,
|
|
||||||
'resource_id' => $new_resource->id,
|
|
||||||
]);
|
|
||||||
$newPersistentVolume->save();
|
|
||||||
}
|
|
||||||
$route = route('project.application.configuration', [
|
$route = route('project.application.configuration', [
|
||||||
'project_uuid' => $this->projectUuid,
|
'project_uuid' => $this->projectUuid,
|
||||||
'environment_uuid' => $this->environmentUuid,
|
'environment_uuid' => $this->environmentUuid,
|
||||||
@@ -95,7 +214,11 @@ class ResourceOperations extends Component
|
|||||||
$this->resource->getMorphClass() === \App\Models\StandaloneClickhouse::class
|
$this->resource->getMorphClass() === \App\Models\StandaloneClickhouse::class
|
||||||
) {
|
) {
|
||||||
$uuid = (string) new Cuid2;
|
$uuid = (string) new Cuid2;
|
||||||
$new_resource = $this->resource->replicate()->fill([
|
$new_resource = $this->resource->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'name' => $this->resource->name.'-clone-'.$uuid,
|
'name' => $this->resource->name.'-clone-'.$uuid,
|
||||||
'status' => 'exited',
|
'status' => 'exited',
|
||||||
@@ -103,23 +226,111 @@ class ResourceOperations extends Component
|
|||||||
'destination_id' => $new_destination->id,
|
'destination_id' => $new_destination->id,
|
||||||
]);
|
]);
|
||||||
$new_resource->save();
|
$new_resource->save();
|
||||||
|
|
||||||
|
$tags = $this->resource->tags;
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$new_resource->tags()->attach($tag->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_resource->persistentStorages()->delete();
|
||||||
|
$persistentVolumes = $this->resource->persistentStorages()->get();
|
||||||
|
foreach ($persistentVolumes as $volume) {
|
||||||
|
$originalName = $volume->name;
|
||||||
|
$newName = '';
|
||||||
|
|
||||||
|
if (str_starts_with($originalName, 'postgres-data-')) {
|
||||||
|
$newName = 'postgres-data-'.$new_resource->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'mysql-data-')) {
|
||||||
|
$newName = 'mysql-data-'.$new_resource->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'redis-data-')) {
|
||||||
|
$newName = 'redis-data-'.$new_resource->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'clickhouse-data-')) {
|
||||||
|
$newName = 'clickhouse-data-'.$new_resource->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'mariadb-data-')) {
|
||||||
|
$newName = 'mariadb-data-'.$new_resource->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'mongodb-data-')) {
|
||||||
|
$newName = 'mongodb-data-'.$new_resource->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'keydb-data-')) {
|
||||||
|
$newName = 'keydb-data-'.$new_resource->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'dragonfly-data-')) {
|
||||||
|
$newName = 'dragonfly-data-'.$new_resource->uuid;
|
||||||
|
} else {
|
||||||
|
if (str_starts_with($volume->name, $this->resource->uuid)) {
|
||||||
|
$newName = str($volume->name)->replace($this->resource->uuid, $new_resource->uuid);
|
||||||
|
} else {
|
||||||
|
$newName = $new_resource->uuid.'-'.$volume->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$newPersistentVolume = $volume->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'name' => $newName,
|
||||||
|
'resource_id' => $new_resource->id,
|
||||||
|
]);
|
||||||
|
$newPersistentVolume->save();
|
||||||
|
|
||||||
|
if ($this->cloneVolumeData) {
|
||||||
|
try {
|
||||||
|
StopDatabase::dispatch($this->resource);
|
||||||
|
$sourceVolume = $volume->name;
|
||||||
|
$targetVolume = $newPersistentVolume->name;
|
||||||
|
$sourceServer = $this->resource->destination->server;
|
||||||
|
$targetServer = $new_resource->destination->server;
|
||||||
|
|
||||||
|
VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume);
|
||||||
|
|
||||||
|
StartDatabase::dispatch($this->resource);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileStorages = $this->resource->fileStorages()->get();
|
||||||
|
foreach ($fileStorages as $storage) {
|
||||||
|
$newStorage = $storage->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'resource_id' => $new_resource->id,
|
||||||
|
]);
|
||||||
|
$newStorage->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scheduledBackups = $this->resource->scheduledBackups()->get();
|
||||||
|
foreach ($scheduledBackups as $backup) {
|
||||||
|
$uuid = (string) new Cuid2;
|
||||||
|
$newBackup = $backup->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'uuid' => $uuid,
|
||||||
|
'database_id' => $new_resource->id,
|
||||||
|
'database_type' => $new_resource->getMorphClass(),
|
||||||
|
'team_id' => currentTeam()->id,
|
||||||
|
]);
|
||||||
|
$newBackup->save();
|
||||||
|
}
|
||||||
|
|
||||||
$environmentVaribles = $this->resource->environment_variables()->get();
|
$environmentVaribles = $this->resource->environment_variables()->get();
|
||||||
foreach ($environmentVaribles as $environmentVarible) {
|
foreach ($environmentVaribles as $environmentVarible) {
|
||||||
$payload = [];
|
$payload = [
|
||||||
if ($this->resource->type() === 'standalone-postgresql') {
|
'resourceable_id' => $new_resource->id,
|
||||||
$payload['standalone_postgresql_id'] = $new_resource->id;
|
'resourceable_type' => $new_resource->getMorphClass(),
|
||||||
} elseif ($this->resource->type() === 'standalone-redis') {
|
];
|
||||||
$payload['standalone_redis_id'] = $new_resource->id;
|
$newEnvironmentVariable = $environmentVarible->replicate([
|
||||||
} elseif ($this->resource->type() === 'standalone-mongodb') {
|
'id',
|
||||||
$payload['standalone_mongodb_id'] = $new_resource->id;
|
'created_at',
|
||||||
} elseif ($this->resource->type() === 'standalone-mysql') {
|
'updated_at',
|
||||||
$payload['standalone_mysql_id'] = $new_resource->id;
|
])->fill($payload);
|
||||||
} elseif ($this->resource->type() === 'standalone-mariadb') {
|
|
||||||
$payload['standalone_mariadb_id'] = $new_resource->id;
|
|
||||||
}
|
|
||||||
$newEnvironmentVariable = $environmentVarible->replicate()->fill($payload);
|
|
||||||
$newEnvironmentVariable->save();
|
$newEnvironmentVariable->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
$route = route('project.database.configuration', [
|
$route = route('project.database.configuration', [
|
||||||
'project_uuid' => $this->projectUuid,
|
'project_uuid' => $this->projectUuid,
|
||||||
'environment_uuid' => $this->environmentUuid,
|
'environment_uuid' => $this->environmentUuid,
|
||||||
@@ -129,23 +340,138 @@ class ResourceOperations extends Component
|
|||||||
return redirect()->to($route);
|
return redirect()->to($route);
|
||||||
} elseif ($this->resource->type() === 'service') {
|
} elseif ($this->resource->type() === 'service') {
|
||||||
$uuid = (string) new Cuid2;
|
$uuid = (string) new Cuid2;
|
||||||
$new_resource = $this->resource->replicate()->fill([
|
$new_resource = $this->resource->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'name' => $this->resource->name.'-clone-'.$uuid,
|
'name' => $this->resource->name.'-clone-'.$uuid,
|
||||||
'destination_id' => $new_destination->id,
|
'destination_id' => $new_destination->id,
|
||||||
|
'destination_type' => $new_destination->getMorphClass(),
|
||||||
|
'server_id' => $new_destination->server_id, // server_id is probably not needed anymore because of the new polymorphic relationships (here it is needed for clone to a different server to work - but maybe we can drop the column)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$new_resource->save();
|
$new_resource->save();
|
||||||
|
|
||||||
|
$tags = $this->resource->tags;
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$new_resource->tags()->attach($tag->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scheduledTasks = $this->resource->scheduled_tasks()->get();
|
||||||
|
foreach ($scheduledTasks as $task) {
|
||||||
|
$newTask = $task->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'uuid' => (string) new Cuid2,
|
||||||
|
'service_id' => $new_resource->id,
|
||||||
|
'team_id' => currentTeam()->id,
|
||||||
|
]);
|
||||||
|
$newTask->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$environmentVariables = $this->resource->environment_variables()->get();
|
||||||
|
foreach ($environmentVariables as $environmentVariable) {
|
||||||
|
$newEnvironmentVariable = $environmentVariable->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'resourceable_id' => $new_resource->id,
|
||||||
|
'resourceable_type' => $new_resource->getMorphClass(),
|
||||||
|
]);
|
||||||
|
$newEnvironmentVariable->save();
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($new_resource->applications() as $application) {
|
foreach ($new_resource->applications() as $application) {
|
||||||
$application->update([
|
$application->update([
|
||||||
'status' => 'exited',
|
'status' => 'exited',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$persistentVolumes = $application->persistentStorages()->get();
|
||||||
|
foreach ($persistentVolumes as $volume) {
|
||||||
|
$newName = '';
|
||||||
|
if (str_starts_with($volume->name, $volume->resource->uuid)) {
|
||||||
|
$newName = str($volume->name)->replace($volume->resource->uuid, $application->uuid);
|
||||||
|
} else {
|
||||||
|
$newName = $application->uuid.'-'.str($volume->name)->afterLast('-');
|
||||||
|
}
|
||||||
|
|
||||||
|
$newPersistentVolume = $volume->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'name' => $newName,
|
||||||
|
'resource_id' => $application->id,
|
||||||
|
]);
|
||||||
|
$newPersistentVolume->save();
|
||||||
|
|
||||||
|
if ($this->cloneVolumeData) {
|
||||||
|
try {
|
||||||
|
StopService::dispatch($application, false, false);
|
||||||
|
$sourceVolume = $volume->name;
|
||||||
|
$targetVolume = $newPersistentVolume->name;
|
||||||
|
$sourceServer = $application->service->destination->server;
|
||||||
|
$targetServer = $new_resource->destination->server;
|
||||||
|
|
||||||
|
VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume);
|
||||||
|
|
||||||
|
StartService::dispatch($application);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($new_resource->databases() as $database) {
|
foreach ($new_resource->databases() as $database) {
|
||||||
$database->update([
|
$database->update([
|
||||||
'status' => 'exited',
|
'status' => 'exited',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$persistentVolumes = $database->persistentStorages()->get();
|
||||||
|
foreach ($persistentVolumes as $volume) {
|
||||||
|
$newName = '';
|
||||||
|
if (str_starts_with($volume->name, $volume->resource->uuid)) {
|
||||||
|
$newName = str($volume->name)->replace($volume->resource->uuid, $database->uuid);
|
||||||
|
} else {
|
||||||
|
$newName = $database->uuid.'-'.str($volume->name)->afterLast('-');
|
||||||
|
}
|
||||||
|
|
||||||
|
$newPersistentVolume = $volume->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'name' => $newName,
|
||||||
|
'resource_id' => $database->id,
|
||||||
|
]);
|
||||||
|
$newPersistentVolume->save();
|
||||||
|
|
||||||
|
if ($this->cloneVolumeData) {
|
||||||
|
try {
|
||||||
|
StopService::dispatch($database->service, false, false);
|
||||||
|
$sourceVolume = $volume->name;
|
||||||
|
$targetVolume = $newPersistentVolume->name;
|
||||||
|
$sourceServer = $database->service->destination->server;
|
||||||
|
$targetServer = $new_resource->destination->server;
|
||||||
|
|
||||||
|
VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume);
|
||||||
|
|
||||||
|
StartService::dispatch($database->service);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$new_resource->parse();
|
$new_resource->parse();
|
||||||
|
|
||||||
$route = route('project.service.configuration', [
|
$route = route('project.service.configuration', [
|
||||||
'project_uuid' => $this->projectUuid,
|
'project_uuid' => $this->projectUuid,
|
||||||
'environment_uuid' => $this->environmentUuid,
|
'environment_uuid' => $this->environmentUuid,
|
||||||
|
|||||||
@@ -81,11 +81,18 @@ class Add extends Component
|
|||||||
'file_storage_path' => 'string',
|
'file_storage_path' => 'string',
|
||||||
'file_storage_content' => 'nullable|string',
|
'file_storage_content' => 'nullable|string',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->file_storage_path = trim($this->file_storage_path);
|
$this->file_storage_path = trim($this->file_storage_path);
|
||||||
$this->file_storage_path = str($this->file_storage_path)->start('/')->value();
|
$this->file_storage_path = str($this->file_storage_path)->start('/')->value();
|
||||||
|
|
||||||
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
|
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
|
||||||
$fs_path = application_configuration_dir().'/'.$this->resource->uuid.$this->file_storage_path;
|
$fs_path = application_configuration_dir().'/'.$this->resource->uuid.$this->file_storage_path;
|
||||||
|
} elseif (str($this->resource->getMorphClass())->contains('Standalone')) {
|
||||||
|
$fs_path = database_configuration_dir().'/'.$this->resource->uuid.$this->file_storage_path;
|
||||||
|
} else {
|
||||||
|
throw new \Exception('No valid resource type for file mount storage type!');
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalFileVolume::create(
|
LocalFileVolume::create(
|
||||||
[
|
[
|
||||||
'fs_path' => $fs_path,
|
'fs_path' => $fs_path,
|
||||||
@@ -109,10 +116,12 @@ class Add extends Component
|
|||||||
'file_storage_directory_source' => 'string',
|
'file_storage_directory_source' => 'string',
|
||||||
'file_storage_directory_destination' => 'string',
|
'file_storage_directory_destination' => 'string',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->file_storage_directory_source = trim($this->file_storage_directory_source);
|
$this->file_storage_directory_source = trim($this->file_storage_directory_source);
|
||||||
$this->file_storage_directory_source = str($this->file_storage_directory_source)->start('/')->value();
|
$this->file_storage_directory_source = str($this->file_storage_directory_source)->start('/')->value();
|
||||||
$this->file_storage_directory_destination = trim($this->file_storage_directory_destination);
|
$this->file_storage_directory_destination = trim($this->file_storage_directory_destination);
|
||||||
$this->file_storage_directory_destination = str($this->file_storage_directory_destination)->start('/')->value();
|
$this->file_storage_directory_destination = str($this->file_storage_directory_destination)->start('/')->value();
|
||||||
|
|
||||||
LocalFileVolume::create(
|
LocalFileVolume::create(
|
||||||
[
|
[
|
||||||
'fs_path' => $this->file_storage_directory_source,
|
'fs_path' => $this->file_storage_directory_source,
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
DB::table('application_settings')
|
||||||
|
->update([
|
||||||
|
'is_container_label_readonly_enabled' => DB::raw('NOT is_container_label_readonly_enabled'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Schema::table('application_settings', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_container_label_readonly_enabled')->default(true)->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
DB::table('application_settings')
|
||||||
|
->update([
|
||||||
|
'is_container_label_readonly_enabled' => DB::raw('NOT is_container_label_readonly_enabled'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Schema::table('application_settings', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_container_label_readonly_enabled')->default(false)->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -54,7 +54,10 @@
|
|||||||
fontSize: monacoFontSize,
|
fontSize: monacoFontSize,
|
||||||
lineNumbersMinChars: 3,
|
lineNumbersMinChars: 3,
|
||||||
automaticLayout: true,
|
automaticLayout: true,
|
||||||
language: '{{ $language }}'
|
language: '{{ $language }}',
|
||||||
|
domReadOnly: '{{ $readonly ?? false }}',
|
||||||
|
contextmenu: '!{{ $readonly ?? false }}',
|
||||||
|
renderLineHighlight: '{{ $readonly ?? false }} ? none : all'
|
||||||
});
|
});
|
||||||
|
|
||||||
const observer = new MutationObserver((mutations) => {
|
const observer = new MutationObserver((mutations) => {
|
||||||
@@ -95,7 +98,7 @@
|
|||||||
}, 5);" :id="monacoId">
|
}, 5);" :id="monacoId">
|
||||||
</div>
|
</div>
|
||||||
<div class="relative z-10 w-full h-full">
|
<div class="relative z-10 w-full h-full">
|
||||||
<div x-ref="monacoEditorElement" class="w-full h-96 text-md"></div>
|
<div x-ref="monacoEditorElement" class="w-full h-96 text-md {{ $readonly ? 'opacity-65' : '' }}"></div>
|
||||||
<div x-ref="monacoPlaceholderElement" x-show="monacoPlaceholder" @click="monacoEditorFocus()"
|
<div x-ref="monacoPlaceholderElement" x-show="monacoPlaceholder" @click="monacoEditorFocus()"
|
||||||
:style="'font-size: ' + monacoFontSize"
|
:style="'font-size: ' + monacoFontSize"
|
||||||
class="w-full text-sm font-mono absolute z-50 text-gray-500 ml-14 -translate-x-0.5 mt-0.5 left-0 top-0"
|
class="w-full text-sm font-mono absolute z-50 text-gray-500 ml-14 -translate-x-0.5 mt-0.5 left-0 top-0"
|
||||||
|
|||||||
@@ -283,9 +283,9 @@
|
|||||||
<x-forms.checkbox label="Escape special characters in labels?"
|
<x-forms.checkbox label="Escape special characters in labels?"
|
||||||
helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off."
|
helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off."
|
||||||
id="application.settings.is_container_label_escape_enabled" instantSave></x-forms.checkbox>
|
id="application.settings.is_container_label_escape_enabled" instantSave></x-forms.checkbox>
|
||||||
<x-forms.checkbox label="Readonly labels"
|
{{-- <x-forms.checkbox label="Readonly labels"
|
||||||
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."
|
helper="Labels are readonly by default. Readonly means that edits you do to the labels could be lost and Coolify will autogenrate the labels for you. If you want to edit the labels directly, disable this option. <br><br>Be careful, it could break the proxy configuration after you restart the container as Coolify will now NOT autogenrate the labels for you (ofc you can alway reset the labels to the coolify defaults manually)."
|
||||||
id="application.settings.is_container_label_readonly_enabled" instantSave></x-forms.checkbox>
|
id="application.settings.is_container_label_readonly_enabled" instantSave></x-forms.checkbox> --}}
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@if ($application->dockerfile)
|
@if ($application->dockerfile)
|
||||||
@@ -308,15 +308,20 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<x-forms.textarea label="Container Labels" rows="15" id="customLabels"
|
@if ($application->settings->is_container_label_readonly_enabled)
|
||||||
monacoEditorLanguage="ini" useMonacoEditor></x-forms.textarea>
|
<x-forms.textarea readonly disabled label="Container Labels" rows="15" id="customLabels"
|
||||||
|
monacoEditorLanguage="ini" useMonacoEditor></x-forms.textarea>
|
||||||
|
@else
|
||||||
|
<x-forms.textarea label="Container Labels" rows="15" id="customLabels"
|
||||||
|
monacoEditorLanguage="ini" useMonacoEditor></x-forms.textarea>
|
||||||
|
@endif
|
||||||
<div class="w-96">
|
<div class="w-96">
|
||||||
|
<x-forms.checkbox label="Readonly labels"
|
||||||
|
helper="Labels are readonly by default. Readonly means that edits you do to the labels could be lost and Coolify will autogenrate the labels for you. If you want to edit the labels directly, disable this option. <br><br>Be careful, it could break the proxy configuration after you restart the container as Coolify will now NOT autogenrate the labels for you (ofc you can alway reset the labels to the coolify defaults manually)."
|
||||||
|
id="application.settings.is_container_label_readonly_enabled" instantSave></x-forms.checkbox>
|
||||||
<x-forms.checkbox label="Escape special characters in labels?"
|
<x-forms.checkbox label="Escape special characters in labels?"
|
||||||
helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off."
|
helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off."
|
||||||
id="application.settings.is_container_label_escape_enabled" instantSave></x-forms.checkbox>
|
id="application.settings.is_container_label_escape_enabled" instantSave></x-forms.checkbox>
|
||||||
<x-forms.checkbox label="Readonly labels"
|
|
||||||
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>
|
</div>
|
||||||
<x-modal-confirmation title="Confirm Labels Reset to Coolify Defaults?"
|
<x-modal-confirmation title="Confirm Labels Reset to Coolify Defaults?"
|
||||||
buttonTitle="Reset Labels to Defaults" buttonFullWidth submitAction="resetDefaultLabels(true)"
|
buttonTitle="Reset Labels to Defaults" buttonFullWidth submitAction="resetDefaultLabels(true)"
|
||||||
|
|||||||
@@ -9,7 +9,39 @@
|
|||||||
<x-forms.input required id="newName" label="New Name" />
|
<x-forms.input required id="newName" label="New Name" />
|
||||||
<x-forms.button isHighlighted wire:click="clone('project')" class="mt-4">Clone to a new Project</x-forms.button>
|
<x-forms.button isHighlighted wire:click="clone('project')" class="mt-4">Clone to a new Project</x-forms.button>
|
||||||
<x-forms.button isHighlighted wire:click="clone('environment')" class="mt-4">Clone to a new Environment</x-forms.button>
|
<x-forms.button isHighlighted wire:click="clone('environment')" class="mt-4">Clone to a new Environment</x-forms.button>
|
||||||
<h3 class="pt-4 pb-2">Servers</h3>
|
{{--
|
||||||
|
<div class="mt-8">
|
||||||
|
<h3 class="text-lg font-bold mb-2">Clone Volume Data</h3>
|
||||||
|
<div class="text-sm text-gray-600 dark:text-gray-300 mb-4">
|
||||||
|
Clone your volume data to the new resources volumes. This process requires a brief container downtime to ensure data consistency.
|
||||||
|
</div>
|
||||||
|
<div wire:poll>
|
||||||
|
@if(!$cloneVolumeData)
|
||||||
|
<div wire:key="volume-disabled">
|
||||||
|
<x-modal-confirmation
|
||||||
|
title="Enable Volume Data Cloning?"
|
||||||
|
buttonTitle="Enable Volume Cloning"
|
||||||
|
submitAction="toggleVolumeCloning(true)"
|
||||||
|
:actions="['This will temporarily stop all the containers to copy the volume data to the new resources to ensure data consistency.', 'The process runs in the background and may take a few minutes.']"
|
||||||
|
:confirmWithPassword="false"
|
||||||
|
:confirmWithText="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div wire:key="volume-enabled" class="max-w-md">
|
||||||
|
<x-forms.checkbox
|
||||||
|
label="Clone Volume Data"
|
||||||
|
id="cloneVolumeData"
|
||||||
|
wire:model="cloneVolumeData"
|
||||||
|
wire:change="toggleVolumeCloning(false)"
|
||||||
|
:checked="$cloneVolumeData"
|
||||||
|
helper="Volume Data will be cloned to the new resources. Containers will be temporarily stopped during the cloning process." />
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div> --}}
|
||||||
|
|
||||||
|
<h3 class="pt-8 pb-2">Servers</h3>
|
||||||
<div>Choose the server and network to clone the resources to.</div>
|
<div>Choose the server and network to clone the resources to.</div>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
@foreach ($servers->sortBy('id') as $server)
|
@foreach ($servers->sortBy('id') as $server)
|
||||||
@@ -29,7 +61,7 @@
|
|||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="pt-4 pb-2">Resources</h3>
|
<h3 class="pt-8 pb-2">Resources</h3>
|
||||||
<div>These will be cloned to the new project</div>
|
<div>These will be cloned to the new project</div>
|
||||||
<div class="grid grid-cols-1 gap-2 pt-4 opacity-95 lg:grid-cols-2 xl:grid-cols-3">
|
<div class="grid grid-cols-1 gap-2 pt-4 opacity-95 lg:grid-cols-2 xl:grid-cols-3">
|
||||||
@foreach ($environment->applications->sortBy('name') as $application)
|
@foreach ($environment->applications->sortBy('name') as $application)
|
||||||
|
|||||||
@@ -1,8 +1,40 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2>Resource Operations</h2>
|
<h2>Resource Operations</h2>
|
||||||
<div class="pb-4">You can easily make different kind of operations on this resource.</div>
|
<div>You can easily make different kind of operations on this resource.</div>
|
||||||
|
{{--
|
||||||
|
<div class="mt-8">
|
||||||
|
<h3 class="text-lg font-bold mb-2">Clone Volume Data</h3>
|
||||||
|
<div class="text-sm text-gray-600 dark:text-gray-300 mb-4">
|
||||||
|
Clone your volume data to the new resources volumes. This process requires a brief container downtime to ensure data consistency.
|
||||||
|
</div>
|
||||||
|
<div class="pb-4">
|
||||||
|
@if(!$cloneVolumeData)
|
||||||
|
<div wire:key="volume-disabled">
|
||||||
|
<x-modal-confirmation
|
||||||
|
title="Enable Volume Data Cloning?"
|
||||||
|
buttonTitle="Enable Volume Cloning"
|
||||||
|
submitAction="toggleVolumeCloning(true)"
|
||||||
|
:actions="['This will temporarily stop all the containers to copy the volume data to the new resources to ensure data consistency.', 'The process runs in the background and may take a few minutes.']"
|
||||||
|
:confirmWithPassword="false"
|
||||||
|
:confirmWithText="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div wire:key="volume-enabled" class="max-w-md">
|
||||||
|
<x-forms.checkbox
|
||||||
|
label="Clone Volume Data"
|
||||||
|
id="cloneVolumeData"
|
||||||
|
wire:model="cloneVolumeData"
|
||||||
|
wire:change="toggleVolumeCloning(false)"
|
||||||
|
:checked="$cloneVolumeData"
|
||||||
|
helper="Volume Data will be cloned to the new resources. Containers will be temporarily stopped during the cloning process." />
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div> --}}
|
||||||
|
|
||||||
<h3>Clone</h3>
|
<h3>Clone</h3>
|
||||||
<div class="pb-4">To another project / environment on a different server.</div>
|
<div class="pb-4">To another project / environment on a different / same server.</div>
|
||||||
<div class="pb-4">
|
<div class="pb-4">
|
||||||
<div class="flex flex-col flex-wrap gap-2">
|
<div class="flex flex-col flex-wrap gap-2">
|
||||||
@foreach ($servers->sortBy('id') as $server)
|
@foreach ($servers->sortBy('id') as $server)
|
||||||
|
|||||||
Reference in New Issue
Block a user