feat: can edit file/dir volumes from ui in compose based apps

This commit is contained in:
Andras Bacsai 2024-04-15 19:47:17 +02:00
parent c99bb4cfd7
commit 85b33a60b3
13 changed files with 171 additions and 48 deletions

View File

@ -2,6 +2,7 @@
namespace App\Jobs;
use App\Models\Application;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Illuminate\Bus\Queueable;
@ -16,11 +17,11 @@ class ServerFilesFromServerJob implements ShouldQueue, ShouldBeEncrypted
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public ServiceApplication|ServiceDatabase $service)
public function __construct(public ServiceApplication|ServiceDatabase|Application $resource)
{
}
public function handle()
{
$this->service->getFilesFromServer(isInit: true);
$this->resource->getFilesFromServer(isInit: true);
}
}

View File

@ -3,6 +3,7 @@
namespace App\Livewire\Project\Application;
use App\Models\Application;
use App\Models\LocalFileVolume;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Livewire\Component;
@ -124,7 +125,7 @@ public function mount()
}
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
$this->ports_exposes = $this->application->ports_exposes;
$this->customLabels = $this->application->parseContainerLabels();
$this->customLabels = $this->application->parseContainerLabels();
if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
$this->application->custom_labels = base64_encode($this->customLabels);
@ -156,8 +157,36 @@ public function loadComposeFile($isInit = false)
return;
}
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation, 'initialDockerComposePrLocation' => $this->initialDockerComposePrLocation] = $this->application->loadComposeFile($isInit);
$compose = $this->application->parseCompose();
$services = data_get($compose, 'services');
if ($services) {
$volumes = collect($services)->map(function ($service) {
return data_get($service, 'volumes');
})->flatten()->filter(function ($volume) {
return str($volume)->startsWith('/data/coolify');
})->unique()->values();
foreach ($volumes as $volume) {
$source = Str::of($volume)->before(':');
$target = Str::of($volume)->after(':')->beforeLast(':');
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $this->application->id,
'resource_type' => get_class($this->application)
],
[
'fs_path' => $source,
'mount_path' => $target,
'resource_id' => $this->application->id,
'resource_type' => get_class($this->application)
]
);
}
}
$this->dispatch('success', 'Docker compose file loaded.');
$this->dispatch('compose_loaded');
$this->dispatch('refreshStorages');
} catch (\Throwable $e) {
$this->application->docker_compose_location = $this->initialDockerComposeLocation;
$this->application->docker_compose_pr_location = $this->initialDockerComposePrLocation;
@ -183,7 +212,8 @@ public function updatedApplicationBaseDirectory()
$this->loadComposeFile();
}
}
public function updatedApplicationFqdn() {
public function updatedApplicationFqdn()
{
$this->resetDefaultLabels();
}
public function updatedApplicationBuildPack()

View File

@ -2,6 +2,7 @@
namespace App\Livewire\Project\Service;
use App\Models\Application;
use App\Models\LocalFileVolume;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
@ -12,7 +13,7 @@
class FileStorage extends Component
{
public LocalFileVolume $fileStorage;
public ServiceApplication|ServiceDatabase|StandaloneClickhouse $resource;
public ServiceApplication|ServiceDatabase|StandaloneClickhouse|Application $resource;
public string $fs_path;
public ?string $workdir = null;
@ -33,6 +34,43 @@ public function mount()
$this->fs_path = $this->fileStorage->fs_path;
}
}
public function convertToDirectory() {
try {
$this->fileStorage->deleteStorageOnServer();
$this->fileStorage->is_directory = true;
$this->fileStorage->content = null;
$this->fileStorage->save();
$this->fileStorage->saveStorageOnServer();
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->dispatch('storagesChanged');
}
}
public function convertToFile() {
try {
$this->fileStorage->deleteStorageOnServer();
$this->fileStorage->is_directory = false;
$this->fileStorage->content = null;
$this->fileStorage->save();
$this->fileStorage->saveStorageOnServer();
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->dispatch('storagesChanged');
}
}
public function delete() {
try {
$this->fileStorage->deleteStorageOnServer();
$this->fileStorage->delete();
$this->dispatch('success', 'File deleted.');
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->dispatch('storagesChanged');
}
}
public function submit()
{
$original = $this->fileStorage->getOriginal();

View File

@ -7,12 +7,13 @@
class Storage extends Component
{
protected $listeners = ['addNewVolume'];
public $resource;
public function render()
public function getListeners()
{
return view('livewire.project.service.storage');
return [
'addNewVolume',
'storagesChanged'=> '$refresh'
];
}
public function addNewVolume($data)
{
@ -32,4 +33,8 @@ public function addNewVolume($data)
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.project.service.storage');
}
}

View File

@ -7,7 +7,7 @@
class All extends Component
{
public $resource;
protected $listeners = ['refreshStorages'];
protected $listeners = ['refreshStorages', 'storagesChanged' => '$refresh'];
public function refreshStorages()
{

View File

@ -954,4 +954,9 @@ public function isWatchPathsTriggered(Collection $modified_files): bool
});
return $matches->count() > 0;
}
public function getFilesFromServer(bool $isInit = false)
{
getFilesystemVolumesFromServer($this, $isInit);
}
}

View File

@ -20,6 +20,26 @@ public function service()
{
return $this->morphTo('resource');
}
public function deleteStorageOnServer()
{
$isService = data_get($this->resource, 'service');
if ($isService) {
$workdir = $this->resource->service->workdir();
$server = $this->resource->service->server;
} else {
$workdir = $this->resource->workdir();
$server = $this->resource->destination->server;
}
$commands = collect([
"cd $workdir"
]);
$fs_path = data_get($this, 'fs_path');
if ($fs_path && $fs_path != '/' && $fs_path != '.' && $fs_path != '..') {
$commands->push("rm -rf $fs_path");
}
ray($commands);
return instant_remote_process($commands, $server);
}
public function saveStorageOnServer()
{
$isService = data_get($this->resource, 'service');
@ -71,7 +91,6 @@ public function saveStorageOnServer()
if ($chmod) {
$commands->push("chmod $chmod $path");
}
}
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");

View File

@ -20,9 +20,9 @@ class EventServiceProvider extends ServiceProvider
MaintenanceModeDisabledNotification::class,
],
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
\SocialiteProviders\Azure\AzureExtendSocialite::class.'@handle',
\SocialiteProviders\Azure\AzureExtendSocialite::class . '@handle',
],
ProxyStarted::class => [
ProxyStarted::class => [
ProxyStartedNotification::class,
],
];

View File

@ -1,7 +1,7 @@
<?php
use App\Models\Application;
use App\Models\EnvironmentVariable;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Illuminate\Support\Str;
@ -21,12 +21,16 @@ function replaceVariables($variable)
return $variable->replaceFirst('$', '')->replaceFirst('{', '')->replaceLast('}', '');
}
function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneService, bool $isInit = false)
function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Application $oneService, bool $isInit = false)
{
// TODO: make this async
try {
$workdir = $oneService->service->workdir();
$server = $oneService->service->server;
if ($oneService->getMorphClass() === 'App\Models\Application') {
$workdir = $oneService->workdir();
$server = $oneService->destination->server;
} else{
$workdir = $oneService->service->workdir();
$server = $oneService->service->server;
}
$fileVolumes = $oneService->fileStorages()->get();
$commands = collect([
"mkdir -p $workdir > /dev/null 2>&1 || true",

View File

@ -19,7 +19,7 @@
href="#">Environment
Variables</a>
@endif
@if ($application->build_pack !== 'static' && $application->build_pack !== 'dockercompose')
@if ($application->build_pack !== 'static')
<a class="menu-item" :class="activeTab === 'storages' && 'menu-item-active'"
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'" href="#">Storages
</a>

View File

@ -1,17 +1,32 @@
<div class="p-4 transition border rounded cursor-pointer border-coolgray-200">
<div class="p-4 transition border rounded dark:border-coolgray-200">
<div class="flex flex-col justify-center pb-4 text-sm select-text">
<h2>{{ data_get($resource, 'name', 'unknown') }}</h2>
<div>{{ $workdir }}{{ $fs_path }} -> {{ $fileStorage->mount_path }}</div>
@if ($fileStorage->is_directory)
<div class="text-xs">Directory</div>
@else
<div class="text-xs">File</div>
@endif
</div>
<div>
<form wire:submit='submit' class="flex flex-col gap-2">
<div class="w-64">
<x-forms.checkbox instantSave label="Is directory?" id="fileStorage.is_directory"></x-forms.checkbox>
</div>
@if (!$fileStorage->is_directory)
<x-forms.textarea label="Content" rows="20" id="fileStorage.content"></x-forms.textarea>
<x-forms.button type="submit">Save</x-forms.button>
<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">
This will delete all files in this directory. It is not reversible. <br>Please think again.
</x-modal-confirmation>
@else
<x-modal-confirmation action="convertToDirectory" buttonTitle="Convert to directory">
This will convert this to a directory. If it was a file, it will be deleted. It is not reversible. <br>Please think again.
</x-modal-confirmation>
@endif
</form>
</div>
<x-modal-confirmation isErrorButton buttonTitle="Delete">
This file / directory will be deleted. It is not reversible. <br>Please think again.
</x-modal-confirmation>
</div>
@if (!$fileStorage->is_directory)
<x-forms.textarea label="Content" rows="20" id="fileStorage.content"></x-forms.textarea>
<x-forms.button class="w-full" type="submit">Save</x-forms.button>
@endif
</form>
</div>

View File

@ -14,25 +14,30 @@
helper="For Preview Deployments, storage has a <span class='text-helper'>-pr-#PRNumber</span> in their
volume
name, example: <span class='text-helper'>-pr-1</span>" />
<x-modal-input buttonTitle="+ Add" title="New Persistent Storage">
<livewire:project.shared.storages.add :uuid="$resource->uuid" />
</x-modal-input>
@if ($resource?->build_pack !== 'dockercompose')
<x-modal-input buttonTitle="+ Add" title="New Persistent Storage">
<livewire:project.shared.storages.add :uuid="$resource->uuid" />
</x-modal-input>
@endif
</div>
<div class="pb-4">Persistent storage to preserve data between deployments.</div>
@if ($resource?->build_pack === 'dockercompose')
<span class="dark:text-warning text-coollabs">Please modify storage layout in your Docker Compose
file or reload the compose file to reread the storage layout.</span>
@endif
@if ($resource->persistentStorages()->get()->count() === 0 && $resource->fileStorages()->get()->count() == 0)
<div>No storage found.</div>
@else
@if ($resource->persistentStorages()->get()->count() > 0)
<livewire:project.shared.storages.all :resource="$resource" />
@endif
@if ($resource->fileStorages()->get()->count() > 0)
<div class="flex flex-col gap-4 pt-4">
@foreach ($resource->fileStorages()->get()->sort() as $fileStorage)
<livewire:project.service.file-storage :fileStorage="$fileStorage"
wire:key="resource-{{ $resource->uuid }}" />
@endforeach
</div>
@endif
<div class="pt-4">No storage found.</div>
@endif
@if ($resource->persistentStorages()->get()->count() > 0)
<livewire:project.shared.storages.all :resource="$resource" />
@endif
@if ($resource->fileStorages()->get()->count() > 0)
<div class="flex flex-col gap-4 pt-4">
@foreach ($resource->fileStorages()->get()->sort() as $fileStorage)
<livewire:project.service.file-storage :fileStorage="$fileStorage"
wire:key="resource-{{ $fileStorage->uuid }}" />
@endforeach
</div>
@endif
@else
@if ($resource->persistentStorages()->get()->count() > 0)
@ -44,7 +49,8 @@
@if ($resource->fileStorages()->get()->count() > 0)
<div class="flex flex-col gap-4 pt-4">
@foreach ($resource->fileStorages()->get()->sort() as $fileStorage)
<livewire:project.service.file-storage :fileStorage="$fileStorage" wire:key="resource-{{ $resource->uuid }}" />
<livewire:project.service.file-storage :fileStorage="$fileStorage"
wire:key="resource-{{ $fileStorage->uuid }}" />
@endforeach
</div>
@endif

View File

@ -12,7 +12,7 @@
</div>
<div>Environment variables (secrets) for this resource.</div>
@if ($resource->type() === 'service' || $resource?->build_pack === 'dockercompose')
<div>Hardcoded variables are not shown here.</div>
<div class="pt-4 dark:text-warning text-coollabs">Hardcoded variables are not shown here.</div>
@endif
</div>
@if ($view === 'normal')