fix: file storages (dir/file mount) handled properly
This commit is contained in:
32
app/Events/FileStorageChanged.php
Normal file
32
app/Events/FileStorageChanged.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class FileStorageChanged implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
public $teamId;
|
||||||
|
|
||||||
|
public function __construct($teamId = null)
|
||||||
|
{
|
||||||
|
ray($teamId);
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
throw new \Exception('Team id is null');
|
||||||
|
}
|
||||||
|
$this->teamId = $teamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel("team.{$this->teamId}"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -214,7 +214,7 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
$this->dispatch('success', 'Docker compose file loaded.');
|
$this->dispatch('success', 'Docker compose file loaded.');
|
||||||
$this->dispatch('compose_loaded');
|
$this->dispatch('compose_loaded');
|
||||||
$this->dispatch('refresh_storages');
|
$this->dispatch('refreshStorages');
|
||||||
$this->dispatch('refreshEnvs');
|
$this->dispatch('refreshEnvs');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->application->docker_compose_location = $this->initialDockerComposeLocation;
|
$this->application->docker_compose_location = $this->initialDockerComposeLocation;
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ class FileStorage extends Component
|
|||||||
|
|
||||||
public ?string $workdir = null;
|
public ?string $workdir = null;
|
||||||
|
|
||||||
|
public bool $permanently_delete = true;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'fileStorage.is_directory' => 'required',
|
'fileStorage.is_directory' => 'required',
|
||||||
'fileStorage.fs_path' => 'required',
|
'fileStorage.fs_path' => 'required',
|
||||||
@@ -56,7 +58,7 @@ class FileStorage extends Component
|
|||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
} finally {
|
} finally {
|
||||||
$this->dispatch('refresh_storages');
|
$this->dispatch('refreshStorages');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,20 +73,27 @@ class FileStorage extends Component
|
|||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
} finally {
|
} finally {
|
||||||
$this->dispatch('refresh_storages');
|
$this->dispatch('refreshStorages');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete()
|
public function delete()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->fileStorage->deleteStorageOnServer();
|
$message = 'File deleted.';
|
||||||
|
if ($this->fileStorage->is_directory) {
|
||||||
|
$message = 'Directory deleted.';
|
||||||
|
}
|
||||||
|
if ($this->permanently_delete) {
|
||||||
|
$message = 'Directory deleted from the server.';
|
||||||
|
$this->fileStorage->deleteStorageOnServer();
|
||||||
|
}
|
||||||
$this->fileStorage->delete();
|
$this->fileStorage->delete();
|
||||||
$this->dispatch('success', 'File deleted.');
|
$this->dispatch('success', $message);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
} finally {
|
} finally {
|
||||||
$this->dispatch('refresh_storages');
|
$this->dispatch('refreshStorages');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,35 @@ class Storage extends Component
|
|||||||
{
|
{
|
||||||
public $resource;
|
public $resource;
|
||||||
|
|
||||||
|
public $fileStorage;
|
||||||
|
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
{
|
{
|
||||||
|
$teamId = auth()->user()->currentTeam()->id;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
"echo-private:team.{$teamId},FileStorageChanged" => 'refreshStoragesFromEvent',
|
||||||
|
'refreshStorages' => '$refresh',
|
||||||
'addNewVolume',
|
'addNewVolume',
|
||||||
'refresh_storages' => '$refresh',
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->refreshStorages();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refreshStoragesFromEvent()
|
||||||
|
{
|
||||||
|
$this->refreshStorages();
|
||||||
|
$this->dispatch('warning', 'File storage changed. Usually it means that the file / directory is already defined on the server, so Coolify set it up for you properly on the UI.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refreshStorages()
|
||||||
|
{
|
||||||
|
$this->fileStorage = $this->resource->fileStorages()->get();
|
||||||
|
}
|
||||||
|
|
||||||
public function addNewVolume($data)
|
public function addNewVolume($data)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -30,7 +51,7 @@ class Storage extends Component
|
|||||||
$this->resource->refresh();
|
$this->resource->refresh();
|
||||||
$this->dispatch('success', 'Storage added successfully');
|
$this->dispatch('success', 'Storage added successfully');
|
||||||
$this->dispatch('clearAddStorage');
|
$this->dispatch('clearAddStorage');
|
||||||
$this->dispatch('refresh_storages');
|
$this->dispatch('refreshStorages');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class Add extends Component
|
|||||||
'resource_type' => get_class($this->resource),
|
'resource_type' => get_class($this->resource),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
$this->dispatch('refresh_storages');
|
$this->dispatch('refreshStorages');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
@@ -123,7 +123,7 @@ class Add extends Component
|
|||||||
'resource_type' => get_class($this->resource),
|
'resource_type' => get_class($this->resource),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
$this->dispatch('refresh_storages');
|
$this->dispatch('refreshStorages');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,5 +8,5 @@ class All extends Component
|
|||||||
{
|
{
|
||||||
public $resource;
|
public $resource;
|
||||||
|
|
||||||
protected $listeners = ['refresh_storages' => '$refresh'];
|
protected $listeners = ['refreshStorages' => '$refresh'];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,6 @@ class Show extends Component
|
|||||||
public function delete()
|
public function delete()
|
||||||
{
|
{
|
||||||
$this->storage->delete();
|
$this->storage->delete();
|
||||||
$this->dispatch('refresh_storages');
|
$this->dispatch('refreshStorages');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Events\FileStorageChanged;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
|
||||||
class LocalFileVolume extends BaseModel
|
class LocalFileVolume extends BaseModel
|
||||||
@@ -33,16 +34,23 @@ class LocalFileVolume extends BaseModel
|
|||||||
$workdir = $this->resource->workdir();
|
$workdir = $this->resource->workdir();
|
||||||
$server = $this->resource->destination->server;
|
$server = $this->resource->destination->server;
|
||||||
}
|
}
|
||||||
$commands = collect([
|
$commands = collect([]);
|
||||||
"cd $workdir",
|
|
||||||
]);
|
|
||||||
$fs_path = data_get($this, 'fs_path');
|
$fs_path = data_get($this, 'fs_path');
|
||||||
|
$isFile = instant_remote_process(["test -f $fs_path && echo OK || echo NOK"], $server);
|
||||||
|
$isDir = instant_remote_process(["test -d $fs_path && echo OK || echo NOK"], $server);
|
||||||
if ($fs_path && $fs_path != '/' && $fs_path != '.' && $fs_path != '..') {
|
if ($fs_path && $fs_path != '/' && $fs_path != '.' && $fs_path != '..') {
|
||||||
$commands->push("rm -rf $fs_path");
|
ray($isFile, $isDir);
|
||||||
}
|
if ($isFile === 'OK') {
|
||||||
ray($commands);
|
$commands->push("rm -rf $fs_path > /dev/null 2>&1 || true");
|
||||||
|
|
||||||
return instant_remote_process($commands, $server);
|
} elseif ($isDir === 'OK') {
|
||||||
|
$commands->push("rm -rf $fs_path > /dev/null 2>&1 || true");
|
||||||
|
$commands->push("rmdir $fs_path > /dev/null 2>&1 || true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($commands->count() > 0) {
|
||||||
|
return instant_remote_process($commands, $server);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveStorageOnServer()
|
public function saveStorageOnServer()
|
||||||
@@ -55,13 +63,10 @@ class LocalFileVolume extends BaseModel
|
|||||||
$workdir = $this->resource->workdir();
|
$workdir = $this->resource->workdir();
|
||||||
$server = $this->resource->destination->server;
|
$server = $this->resource->destination->server;
|
||||||
}
|
}
|
||||||
$commands = collect([
|
$commands = collect([]);
|
||||||
"mkdir -p $workdir > /dev/null 2>&1 || true",
|
if ($this->is_directory) {
|
||||||
"cd $workdir",
|
|
||||||
]);
|
|
||||||
$is_directory = $this->is_directory;
|
|
||||||
if ($is_directory) {
|
|
||||||
$commands->push("mkdir -p $this->fs_path > /dev/null 2>&1 || true");
|
$commands->push("mkdir -p $this->fs_path > /dev/null 2>&1 || true");
|
||||||
|
$commands->push("cd $workdir");
|
||||||
}
|
}
|
||||||
if (str($this->fs_path)->startsWith('.') || str($this->fs_path)->startsWith('/') || str($this->fs_path)->startsWith('~')) {
|
if (str($this->fs_path)->startsWith('.') || str($this->fs_path)->startsWith('/') || str($this->fs_path)->startsWith('~')) {
|
||||||
$parent_dir = str($this->fs_path)->beforeLast('/');
|
$parent_dir = str($this->fs_path)->beforeLast('/');
|
||||||
@@ -79,8 +84,11 @@ class LocalFileVolume extends BaseModel
|
|||||||
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
|
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
|
||||||
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
|
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
|
||||||
if ($isFile == 'OK' && $fileVolume->is_directory) {
|
if ($isFile == 'OK' && $fileVolume->is_directory) {
|
||||||
|
$content = instant_remote_process(["cat $path"], $server, false);
|
||||||
$fileVolume->is_directory = false;
|
$fileVolume->is_directory = false;
|
||||||
|
$fileVolume->content = $content;
|
||||||
$fileVolume->save();
|
$fileVolume->save();
|
||||||
|
FileStorageChanged::dispatch(data_get($server, 'team_id'));
|
||||||
throw new \Exception('The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.');
|
throw new \Exception('The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.');
|
||||||
} elseif ($isDir == 'OK' && ! $fileVolume->is_directory) {
|
} elseif ($isDir == 'OK' && ! $fileVolume->is_directory) {
|
||||||
$fileVolume->is_directory = true;
|
$fileVolume->is_directory = true;
|
||||||
|
|||||||
@@ -14,16 +14,30 @@
|
|||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
@if ($fileStorage->is_directory)
|
@if ($fileStorage->is_directory)
|
||||||
<x-modal-confirmation action="convertToFile" buttonTitle="Convert to file">
|
<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.
|
<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>
|
||||||
@else
|
@else
|
||||||
<x-modal-confirmation action="convertToDirectory" buttonTitle="Convert to directory">
|
<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.
|
<div>This will delete the file and make a directory instead. It is not reversible.
|
||||||
<br>Please think again.
|
<strong class="text-error">Please think
|
||||||
|
again.</strong><br><br>
|
||||||
|
</div>
|
||||||
</x-modal-confirmation>
|
</x-modal-confirmation>
|
||||||
@endif
|
@endif
|
||||||
<x-modal-confirmation isErrorButton buttonTitle="Delete">
|
<x-modal-confirmation isErrorButton buttonTitle="Delete">
|
||||||
This file / directory will be deleted. It is not reversible. <br>Please think again.
|
<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>
|
||||||
|
@if ($fileStorage->is_directory)
|
||||||
|
<x-forms.checkbox id="permanently_delete"
|
||||||
|
label="Permanently delete directory from the server?"></x-forms.checkbox>
|
||||||
|
@else
|
||||||
|
<x-forms.checkbox id="permanently_delete"
|
||||||
|
label="Permanently delete file from the server?"></x-forms.checkbox>
|
||||||
|
@endif
|
||||||
</x-modal-confirmation>
|
</x-modal-confirmation>
|
||||||
</div>
|
</div>
|
||||||
@if (!$fileStorage->is_directory)
|
@if (!$fileStorage->is_directory)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<span class="dark:text-warning text-coollabs">Please modify storage layout in your Docker Compose
|
<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>
|
file or reload the compose file to reread the storage layout.</span>
|
||||||
@else
|
@else
|
||||||
@if ($resource->persistentStorages()->get()->count() === 0 && $resource->fileStorages()->get()->count() == 0)
|
@if ($resource->persistentStorages()->get()->count() === 0 && $fileStorage->count() == 0)
|
||||||
<div class="pt-4">No storage found.</div>
|
<div class="pt-4">No storage found.</div>
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
@@ -33,9 +33,9 @@
|
|||||||
@if ($resource->persistentStorages()->get()->count() > 0)
|
@if ($resource->persistentStorages()->get()->count() > 0)
|
||||||
<livewire:project.shared.storages.all :resource="$resource" />
|
<livewire:project.shared.storages.all :resource="$resource" />
|
||||||
@endif
|
@endif
|
||||||
@if ($resource->fileStorages()->get()->count() > 0)
|
@if ($fileStorage->count() > 0)
|
||||||
<div class="flex flex-col gap-4 pt-4">
|
<div class="flex flex-col gap-4 pt-4">
|
||||||
@foreach ($resource->fileStorages()->get()->sort() as $fileStorage)
|
@foreach ($fileStorage->sort() as $fileStorage)
|
||||||
<livewire:project.service.file-storage :fileStorage="$fileStorage"
|
<livewire:project.service.file-storage :fileStorage="$fileStorage"
|
||||||
wire:key="resource-{{ $fileStorage->uuid }}" />
|
wire:key="resource-{{ $fileStorage->uuid }}" />
|
||||||
@endforeach
|
@endforeach
|
||||||
@@ -48,9 +48,9 @@
|
|||||||
@if ($resource->persistentStorages()->get()->count() > 0)
|
@if ($resource->persistentStorages()->get()->count() > 0)
|
||||||
<livewire:project.shared.storages.all :resource="$resource" />
|
<livewire:project.shared.storages.all :resource="$resource" />
|
||||||
@endif
|
@endif
|
||||||
@if ($resource->fileStorages()->get()->count() > 0)
|
@if ($fileStorage->count() > 0)
|
||||||
<div class="flex flex-col gap-4 pt-4">
|
<div class="flex flex-col gap-4 pt-4">
|
||||||
@foreach ($resource->fileStorages()->get()->sort() as $fileStorage)
|
@foreach ($fileStorage->sort() as $fileStorage)
|
||||||
<livewire:project.service.file-storage :fileStorage="$fileStorage"
|
<livewire:project.service.file-storage :fileStorage="$fileStorage"
|
||||||
wire:key="resource-{{ $fileStorage->uuid }}" />
|
wire:key="resource-{{ $fileStorage->uuid }}" />
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|||||||
Reference in New Issue
Block a user