feat(storage): consolidate storage management into a single component with enhanced UI
- Merged the storage management functionalities into the Storage component, replacing the previous Add component. - Introduced new methods for submitting persistent volumes, file mounts, and directory mounts, improving code organization and maintainability. - Enhanced the UI with modals for adding volumes, files, and directories, providing a more intuitive user experience. - Updated validation rules and error handling for improved robustness during storage submissions. - Removed deprecated Add component and associated views to streamline the codebase.
This commit is contained in:
parent
b38745536d
commit
ce5555ca9f
4 changed files with 358 additions and 246 deletions
|
|
@ -14,6 +14,22 @@ class Storage extends Component
|
||||||
|
|
||||||
public $fileStorage;
|
public $fileStorage;
|
||||||
|
|
||||||
|
public $isSwarm = false;
|
||||||
|
|
||||||
|
public string $name = '';
|
||||||
|
|
||||||
|
public string $mount_path = '';
|
||||||
|
|
||||||
|
public ?string $host_path = null;
|
||||||
|
|
||||||
|
public string $file_storage_path = '';
|
||||||
|
|
||||||
|
public ?string $file_storage_content = null;
|
||||||
|
|
||||||
|
public string $file_storage_directory_source = '';
|
||||||
|
|
||||||
|
public string $file_storage_directory_destination = '';
|
||||||
|
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
{
|
{
|
||||||
$teamId = auth()->user()->currentTeam()->id;
|
$teamId = auth()->user()->currentTeam()->id;
|
||||||
|
|
@ -27,6 +43,18 @@ public function getListeners()
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
if (str($this->resource->getMorphClass())->contains('Standalone')) {
|
||||||
|
$this->file_storage_directory_source = database_configuration_dir()."/{$this->resource->uuid}";
|
||||||
|
} else {
|
||||||
|
$this->file_storage_directory_source = application_configuration_dir()."/{$this->resource->uuid}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
|
||||||
|
if ($this->resource->destination->server->isSwarm()) {
|
||||||
|
$this->isSwarm = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->refreshStorages();
|
$this->refreshStorages();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,27 +95,123 @@ public function getDirectoryCountProperty()
|
||||||
return $this->directories->count();
|
return $this->directories->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addNewVolume($data)
|
public function submitPersistentVolume()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->authorize('update', $this->resource);
|
$this->authorize('update', $this->resource);
|
||||||
|
|
||||||
|
$this->validate([
|
||||||
|
'name' => 'required|string',
|
||||||
|
'mount_path' => 'required|string',
|
||||||
|
'host_path' => $this->isSwarm ? 'required|string' : 'string|nullable',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$name = $this->resource->uuid.'-'.$this->name;
|
||||||
|
|
||||||
LocalPersistentVolume::create([
|
LocalPersistentVolume::create([
|
||||||
'name' => $data['name'],
|
'name' => $name,
|
||||||
'mount_path' => $data['mount_path'],
|
'mount_path' => $this->mount_path,
|
||||||
'host_path' => $data['host_path'],
|
'host_path' => $this->host_path,
|
||||||
'resource_id' => $this->resource->id,
|
'resource_id' => $this->resource->id,
|
||||||
'resource_type' => $this->resource->getMorphClass(),
|
'resource_type' => $this->resource->getMorphClass(),
|
||||||
]);
|
]);
|
||||||
$this->resource->refresh();
|
$this->resource->refresh();
|
||||||
$this->dispatch('success', 'Storage added successfully');
|
$this->dispatch('success', 'Volume added successfully');
|
||||||
$this->dispatch('clearAddStorage');
|
$this->dispatch('closeStorageModal', 'volume');
|
||||||
$this->dispatch('refreshStorages');
|
$this->clearForm();
|
||||||
|
$this->refreshStorages();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function submitFileStorage()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->authorize('update', $this->resource);
|
||||||
|
|
||||||
|
$this->validate([
|
||||||
|
'file_storage_path' => 'required|string',
|
||||||
|
'file_storage_content' => 'nullable|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->file_storage_path = trim($this->file_storage_path);
|
||||||
|
$this->file_storage_path = str($this->file_storage_path)->start('/')->value();
|
||||||
|
|
||||||
|
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
|
||||||
|
$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!');
|
||||||
|
}
|
||||||
|
|
||||||
|
\App\Models\LocalFileVolume::create([
|
||||||
|
'fs_path' => $fs_path,
|
||||||
|
'mount_path' => $this->file_storage_path,
|
||||||
|
'content' => $this->file_storage_content,
|
||||||
|
'is_directory' => false,
|
||||||
|
'resource_id' => $this->resource->id,
|
||||||
|
'resource_type' => get_class($this->resource),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->dispatch('success', 'File mount added successfully');
|
||||||
|
$this->dispatch('closeStorageModal', 'file');
|
||||||
|
$this->clearForm();
|
||||||
|
$this->refreshStorages();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submitFileStorageDirectory()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->authorize('update', $this->resource);
|
||||||
|
|
||||||
|
$this->validate([
|
||||||
|
'file_storage_directory_source' => 'required|string',
|
||||||
|
'file_storage_directory_destination' => 'required|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$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_destination = trim($this->file_storage_directory_destination);
|
||||||
|
$this->file_storage_directory_destination = str($this->file_storage_directory_destination)->start('/')->value();
|
||||||
|
|
||||||
|
\App\Models\LocalFileVolume::create([
|
||||||
|
'fs_path' => $this->file_storage_directory_source,
|
||||||
|
'mount_path' => $this->file_storage_directory_destination,
|
||||||
|
'is_directory' => true,
|
||||||
|
'resource_id' => $this->resource->id,
|
||||||
|
'resource_type' => get_class($this->resource),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->dispatch('success', 'Directory mount added successfully');
|
||||||
|
$this->dispatch('closeStorageModal', 'directory');
|
||||||
|
$this->clearForm();
|
||||||
|
$this->refreshStorages();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clearForm()
|
||||||
|
{
|
||||||
|
$this->name = '';
|
||||||
|
$this->mount_path = '';
|
||||||
|
$this->host_path = null;
|
||||||
|
$this->file_storage_path = '';
|
||||||
|
$this->file_storage_content = null;
|
||||||
|
$this->file_storage_directory_destination = '';
|
||||||
|
|
||||||
|
if (str($this->resource->getMorphClass())->contains('Standalone')) {
|
||||||
|
$this->file_storage_directory_source = database_configuration_dir()."/{$this->resource->uuid}";
|
||||||
|
} else {
|
||||||
|
$this->file_storage_directory_source = application_configuration_dir()."/{$this->resource->uuid}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.service.storage');
|
return view('livewire.project.service.storage');
|
||||||
|
|
|
||||||
|
|
@ -1,174 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Livewire\Project\Shared\Storages;
|
|
||||||
|
|
||||||
use App\Models\Application;
|
|
||||||
use App\Models\LocalFileVolume;
|
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class Add extends Component
|
|
||||||
{
|
|
||||||
use AuthorizesRequests;
|
|
||||||
|
|
||||||
public $resource;
|
|
||||||
|
|
||||||
public $uuid;
|
|
||||||
|
|
||||||
public $parameters;
|
|
||||||
|
|
||||||
public $isSwarm = false;
|
|
||||||
|
|
||||||
public string $name;
|
|
||||||
|
|
||||||
public string $mount_path;
|
|
||||||
|
|
||||||
public ?string $host_path = null;
|
|
||||||
|
|
||||||
public string $file_storage_path;
|
|
||||||
|
|
||||||
public ?string $file_storage_content = null;
|
|
||||||
|
|
||||||
public string $file_storage_directory_source;
|
|
||||||
|
|
||||||
public string $file_storage_directory_destination;
|
|
||||||
|
|
||||||
public $rules = [
|
|
||||||
'name' => 'required|string',
|
|
||||||
'mount_path' => 'required|string',
|
|
||||||
'host_path' => 'string|nullable',
|
|
||||||
'file_storage_path' => 'string',
|
|
||||||
'file_storage_content' => 'nullable|string',
|
|
||||||
'file_storage_directory_source' => 'string',
|
|
||||||
'file_storage_directory_destination' => 'string',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $listeners = ['clearAddStorage' => 'clear'];
|
|
||||||
|
|
||||||
protected $validationAttributes = [
|
|
||||||
'name' => 'name',
|
|
||||||
'mount_path' => 'mount',
|
|
||||||
'host_path' => 'host',
|
|
||||||
'file_storage_path' => 'file storage path',
|
|
||||||
'file_storage_content' => 'file storage content',
|
|
||||||
'file_storage_directory_source' => 'file storage directory source',
|
|
||||||
'file_storage_directory_destination' => 'file storage directory destination',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
if (str($this->resource->getMorphClass())->contains('Standalone')) {
|
|
||||||
$this->file_storage_directory_source = database_configuration_dir()."/{$this->resource->uuid}";
|
|
||||||
} else {
|
|
||||||
$this->file_storage_directory_source = application_configuration_dir()."/{$this->resource->uuid}";
|
|
||||||
}
|
|
||||||
$this->uuid = $this->resource->uuid;
|
|
||||||
$this->parameters = get_route_parameters();
|
|
||||||
if (data_get($this->parameters, 'application_uuid')) {
|
|
||||||
$applicationUuid = $this->parameters['application_uuid'];
|
|
||||||
$application = Application::where('uuid', $applicationUuid)->first();
|
|
||||||
if (! $application) {
|
|
||||||
abort(404);
|
|
||||||
}
|
|
||||||
if ($application->destination->server->isSwarm()) {
|
|
||||||
$this->isSwarm = true;
|
|
||||||
$this->rules['host_path'] = 'required|string';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function submitFileStorage()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->authorize('update', $this->resource);
|
|
||||||
|
|
||||||
$this->validate([
|
|
||||||
'file_storage_path' => 'string',
|
|
||||||
'file_storage_content' => 'nullable|string',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->file_storage_path = trim($this->file_storage_path);
|
|
||||||
$this->file_storage_path = str($this->file_storage_path)->start('/')->value();
|
|
||||||
|
|
||||||
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
|
|
||||||
$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(
|
|
||||||
[
|
|
||||||
'fs_path' => $fs_path,
|
|
||||||
'mount_path' => $this->file_storage_path,
|
|
||||||
'content' => $this->file_storage_content,
|
|
||||||
'is_directory' => false,
|
|
||||||
'resource_id' => $this->resource->id,
|
|
||||||
'resource_type' => get_class($this->resource),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
$this->dispatch('refreshStorages');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function submitFileStorageDirectory()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->authorize('update', $this->resource);
|
|
||||||
|
|
||||||
$this->validate([
|
|
||||||
'file_storage_directory_source' => 'string',
|
|
||||||
'file_storage_directory_destination' => 'string',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$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_destination = trim($this->file_storage_directory_destination);
|
|
||||||
$this->file_storage_directory_destination = str($this->file_storage_directory_destination)->start('/')->value();
|
|
||||||
|
|
||||||
LocalFileVolume::create(
|
|
||||||
[
|
|
||||||
'fs_path' => $this->file_storage_directory_source,
|
|
||||||
'mount_path' => $this->file_storage_directory_destination,
|
|
||||||
'is_directory' => true,
|
|
||||||
'resource_id' => $this->resource->id,
|
|
||||||
'resource_type' => get_class($this->resource),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
$this->dispatch('refreshStorages');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function submitPersistentVolume()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->authorize('update', $this->resource);
|
|
||||||
|
|
||||||
$this->validate([
|
|
||||||
'name' => 'required|string',
|
|
||||||
'mount_path' => 'required|string',
|
|
||||||
'host_path' => 'string|nullable',
|
|
||||||
]);
|
|
||||||
$name = $this->uuid.'-'.$this->name;
|
|
||||||
$this->dispatch('addNewVolume', [
|
|
||||||
'name' => $name,
|
|
||||||
'mount_path' => $this->mount_path,
|
|
||||||
'host_path' => $this->host_path,
|
|
||||||
]);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function clear()
|
|
||||||
{
|
|
||||||
$this->name = '';
|
|
||||||
$this->mount_path = '';
|
|
||||||
$this->host_path = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -18,9 +18,230 @@
|
||||||
name, example: <span class='text-helper'>-pr-1</span>" />
|
name, example: <span class='text-helper'>-pr-1</span>" />
|
||||||
@if ($resource?->build_pack !== 'dockercompose')
|
@if ($resource?->build_pack !== 'dockercompose')
|
||||||
@can('update', $resource)
|
@can('update', $resource)
|
||||||
<x-modal-input :closeOutside="false" buttonTitle="+ Add" title="New Persistent Storage" minWidth="64rem">
|
<div x-data="{
|
||||||
<livewire:project.shared.storages.add :resource="$resource" />
|
dropdownOpen: false,
|
||||||
</x-modal-input>
|
volumeModalOpen: false,
|
||||||
|
fileModalOpen: false,
|
||||||
|
directoryModalOpen: false
|
||||||
|
}" @close-storage-modal.window="
|
||||||
|
if ($event.detail === 'volume') volumeModalOpen = false;
|
||||||
|
if ($event.detail === 'file') fileModalOpen = false;
|
||||||
|
if ($event.detail === 'directory') directoryModalOpen = false;
|
||||||
|
">
|
||||||
|
<div class="relative" @click.outside="dropdownOpen = false">
|
||||||
|
<x-forms.button @click="dropdownOpen = !dropdownOpen">
|
||||||
|
+ Add
|
||||||
|
<svg class="w-4 h-4 ml-2" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||||
|
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9" />
|
||||||
|
</svg>
|
||||||
|
</x-forms.button>
|
||||||
|
|
||||||
|
<div x-show="dropdownOpen" @click.away="dropdownOpen=false"
|
||||||
|
x-transition:enter="ease-out duration-200" x-transition:enter-start="-translate-y-2"
|
||||||
|
x-transition:enter-end="translate-y-0" class="absolute top-0 z-50 mt-10 min-w-max"
|
||||||
|
x-cloak>
|
||||||
|
<div
|
||||||
|
class="p-1 mt-1 bg-white border rounded-sm shadow-sm dark:bg-coolgray-200 dark:border-coolgray-300 border-neutral-300">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<a class="dropdown-item"
|
||||||
|
@click="volumeModalOpen = true; dropdownOpen = false">
|
||||||
|
<svg class="size-4" fill="none" stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M5 19a2 2 0 01-2-2V7a2 2 0 012-2h4l2 2h4a2 2 0 012 2v1M5 19h14a2 2 0 002-2v-5a2 2 0 00-2-2H9a2 2 0 00-2 2v5a2 2 0 01-2 2z" />
|
||||||
|
</svg>
|
||||||
|
Volume Mount
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item" @click="fileModalOpen = true; dropdownOpen = false">
|
||||||
|
<svg class="size-4" fill="none" stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
File Mount
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item"
|
||||||
|
@click="directoryModalOpen = true; dropdownOpen = false">
|
||||||
|
<svg class="size-4" fill="none" stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
|
||||||
|
</svg>
|
||||||
|
Directory Mount
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Volume Modal --}}
|
||||||
|
<template x-teleport="body">
|
||||||
|
<div x-show="volumeModalOpen" @keydown.window.escape="volumeModalOpen=false"
|
||||||
|
class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen">
|
||||||
|
<div x-show="volumeModalOpen" x-transition:enter="ease-out duration-100"
|
||||||
|
x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
|
||||||
|
x-transition:leave="ease-in duration-100" x-transition:leave-start="opacity-100"
|
||||||
|
x-transition:leave-end="opacity-0" @click="volumeModalOpen=false"
|
||||||
|
class="absolute inset-0 w-full h-full bg-black/20 backdrop-blur-xs"></div>
|
||||||
|
<div x-show="volumeModalOpen" x-trap.inert.noscroll="volumeModalOpen"
|
||||||
|
x-transition:enter="ease-out duration-100"
|
||||||
|
x-transition:enter-start="opacity-0 -translate-y-2 sm:scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave="ease-in duration-100"
|
||||||
|
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95"
|
||||||
|
class="relative w-full py-6 border rounded-sm drop-shadow-sm min-w-full lg:min-w-[36rem] max-w-fit bg-white border-neutral-200 dark:bg-base px-6 dark:border-coolgray-300">
|
||||||
|
<div class="flex items-center justify-between pb-3">
|
||||||
|
<h3 class="text-2xl font-bold">Add Volume Mount</h3>
|
||||||
|
<button @click="volumeModalOpen=false"
|
||||||
|
class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 rounded-full dark:text-white hover:bg-neutral-100 dark:hover:bg-coolgray-300 outline-0 focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning">
|
||||||
|
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||||
|
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="relative flex items-center justify-center w-auto"
|
||||||
|
x-init="$watch('volumeModalOpen', value => { if(value) { $nextTick(() => { const input = $el.querySelector('input'); input?.focus(); }) } })">
|
||||||
|
<form class="flex flex-col w-full gap-2 rounded-sm" wire:submit='submitPersistentVolume'>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div>Docker Volumes mounted to the container.</div>
|
||||||
|
</div>
|
||||||
|
@if ($isSwarm)
|
||||||
|
<div class="text-warning">Swarm Mode detected: You need to set a shared volume
|
||||||
|
(EFS/NFS/etc) on all the worker nodes if you would like to use a persistent
|
||||||
|
volumes.</div>
|
||||||
|
@endif
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<x-forms.input canGate="update" :canResource="$resource" placeholder="pv-name"
|
||||||
|
id="name" label="Name" required helper="Volume name." />
|
||||||
|
@if ($isSwarm)
|
||||||
|
<x-forms.input canGate="update" :canResource="$resource" placeholder="/root"
|
||||||
|
id="host_path" label="Source Path" required
|
||||||
|
helper="Directory on the host system." />
|
||||||
|
@else
|
||||||
|
<x-forms.input canGate="update" :canResource="$resource" placeholder="/root"
|
||||||
|
id="host_path" label="Source Path"
|
||||||
|
helper="Directory on the host system." />
|
||||||
|
@endif
|
||||||
|
<x-forms.input canGate="update" :canResource="$resource" placeholder="/tmp/root"
|
||||||
|
id="mount_path" label="Destination Path" required
|
||||||
|
helper="Directory inside the container." />
|
||||||
|
<x-forms.button canGate="update" :canResource="$resource" type="submit">
|
||||||
|
Add
|
||||||
|
</x-forms.button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
{{-- File Modal --}}
|
||||||
|
<template x-teleport="body">
|
||||||
|
<div x-show="fileModalOpen" @keydown.window.escape="fileModalOpen=false"
|
||||||
|
class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen">
|
||||||
|
<div x-show="fileModalOpen" x-transition:enter="ease-out duration-100"
|
||||||
|
x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
|
||||||
|
x-transition:leave="ease-in duration-100" x-transition:leave-start="opacity-100"
|
||||||
|
x-transition:leave-end="opacity-0" @click="fileModalOpen=false"
|
||||||
|
class="absolute inset-0 w-full h-full bg-black/20 backdrop-blur-xs"></div>
|
||||||
|
<div x-show="fileModalOpen" x-trap.inert.noscroll="fileModalOpen"
|
||||||
|
x-transition:enter="ease-out duration-100"
|
||||||
|
x-transition:enter-start="opacity-0 -translate-y-2 sm:scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave="ease-in duration-100"
|
||||||
|
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95"
|
||||||
|
class="relative w-full py-6 border rounded-sm drop-shadow-sm min-w-full lg:min-w-[36rem] max-w-fit bg-white border-neutral-200 dark:bg-base px-6 dark:border-coolgray-300">
|
||||||
|
<div class="flex items-center justify-between pb-3">
|
||||||
|
<h3 class="text-2xl font-bold">Add File Mount</h3>
|
||||||
|
<button @click="fileModalOpen=false"
|
||||||
|
class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 rounded-full dark:text-white hover:bg-neutral-100 dark:hover:bg-coolgray-300 outline-0 focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning">
|
||||||
|
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||||
|
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="relative flex items-center justify-center w-auto"
|
||||||
|
x-init="$watch('fileModalOpen', value => { if(value) { $nextTick(() => { const input = $el.querySelector('input'); input?.focus(); }) } })">
|
||||||
|
<form class="flex flex-col w-full gap-2 rounded-sm" wire:submit='submitFileStorage'>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div>Actual file mounted from the host system to the container.</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<x-forms.input canGate="update" :canResource="$resource"
|
||||||
|
placeholder="/etc/nginx/nginx.conf" id="file_storage_path"
|
||||||
|
label="Destination Path" required helper="File location inside the container" />
|
||||||
|
<x-forms.textarea canGate="update" :canResource="$resource" label="Content"
|
||||||
|
id="file_storage_content"></x-forms.textarea>
|
||||||
|
<x-forms.button canGate="update" :canResource="$resource" type="submit">
|
||||||
|
Add
|
||||||
|
</x-forms.button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
{{-- Directory Modal --}}
|
||||||
|
<template x-teleport="body">
|
||||||
|
<div x-show="directoryModalOpen" @keydown.window.escape="directoryModalOpen=false"
|
||||||
|
class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen">
|
||||||
|
<div x-show="directoryModalOpen" x-transition:enter="ease-out duration-100"
|
||||||
|
x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
|
||||||
|
x-transition:leave="ease-in duration-100" x-transition:leave-start="opacity-100"
|
||||||
|
x-transition:leave-end="opacity-0" @click="directoryModalOpen=false"
|
||||||
|
class="absolute inset-0 w-full h-full bg-black/20 backdrop-blur-xs"></div>
|
||||||
|
<div x-show="directoryModalOpen" x-trap.inert.noscroll="directoryModalOpen"
|
||||||
|
x-transition:enter="ease-out duration-100"
|
||||||
|
x-transition:enter-start="opacity-0 -translate-y-2 sm:scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave="ease-in duration-100"
|
||||||
|
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95"
|
||||||
|
class="relative w-full py-6 border rounded-sm drop-shadow-sm min-w-full lg:min-w-[36rem] max-w-fit bg-white border-neutral-200 dark:bg-base px-6 dark:border-coolgray-300">
|
||||||
|
<div class="flex items-center justify-between pb-3">
|
||||||
|
<h3 class="text-2xl font-bold">Add Directory Mount</h3>
|
||||||
|
<button @click="directoryModalOpen=false"
|
||||||
|
class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 rounded-full dark:text-white hover:bg-neutral-100 dark:hover:bg-coolgray-300 outline-0 focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning">
|
||||||
|
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||||
|
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="relative flex items-center justify-center w-auto"
|
||||||
|
x-init="$watch('directoryModalOpen', value => { if(value) { $nextTick(() => { const input = $el.querySelector('input'); input?.focus(); }) } })">
|
||||||
|
<form class="flex flex-col w-full gap-2 rounded-sm" wire:submit='submitFileStorageDirectory'>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div>Directory mounted from the host system to the container.</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<x-forms.input canGate="update" :canResource="$resource"
|
||||||
|
placeholder="{{ application_configuration_dir() }}/{{ $resource->uuid }}/etc/nginx"
|
||||||
|
id="file_storage_directory_source" label="Source Directory" required
|
||||||
|
helper="Directory on the host system." />
|
||||||
|
<x-forms.input canGate="update" :canResource="$resource" placeholder="/etc/nginx"
|
||||||
|
id="file_storage_directory_destination" label="Destination Directory" required
|
||||||
|
helper="Directory inside the container." />
|
||||||
|
<x-forms.button canGate="update" :canResource="$resource" type="submit">
|
||||||
|
Add
|
||||||
|
</x-forms.button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
@endcan
|
@endcan
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -51,19 +272,19 @@
|
||||||
<button @click="activeTab = 'volumes'"
|
<button @click="activeTab = 'volumes'"
|
||||||
:class="activeTab === 'volumes' ? 'border-b-2 dark:border-white border-black' : 'border-b-2 border-transparent'"
|
:class="activeTab === 'volumes' ? 'border-b-2 dark:border-white border-black' : 'border-b-2 border-transparent'"
|
||||||
@if (!$hasVolumes) disabled @endif
|
@if (!$hasVolumes) disabled @endif
|
||||||
class="px-4 py-2 -mb-px font-medium transition-colors {{ $hasVolumes ? 'dark:text-neutral-400 dark:hover:text-white text-neutral-600 hover:text-black cursor-pointer' : 'opacity-50 cursor-not-allowed' }}">
|
class="px-4 py-2 -mb-px font-medium transition-colors {{ $hasVolumes ? 'dark:text-neutral-400 dark:hover:text-white text-neutral-600 hover:text-black cursor-pointer' : 'opacity-50 cursor-not-allowed' }} focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning focus-visible:ring-offset-2 dark:focus-visible:ring-offset-coolgray-100">
|
||||||
Volumes ({{ $this->volumeCount }})
|
Volumes ({{ $this->volumeCount }})
|
||||||
</button>
|
</button>
|
||||||
<button @click="activeTab = 'files'"
|
<button @click="activeTab = 'files'"
|
||||||
:class="activeTab === 'files' ? 'border-b-2 dark:border-white border-black' : 'border-b-2 border-transparent'"
|
:class="activeTab === 'files' ? 'border-b-2 dark:border-white border-black' : 'border-b-2 border-transparent'"
|
||||||
@if (!$hasFiles) disabled @endif
|
@if (!$hasFiles) disabled @endif
|
||||||
class="px-4 py-2 -mb-px font-medium transition-colors {{ $hasFiles ? 'dark:text-neutral-400 dark:hover:text-white text-neutral-600 hover:text-black cursor-pointer' : 'opacity-50 cursor-not-allowed' }}">
|
class="px-4 py-2 -mb-px font-medium transition-colors {{ $hasFiles ? 'dark:text-neutral-400 dark:hover:text-white text-neutral-600 hover:text-black cursor-pointer' : 'opacity-50 cursor-not-allowed' }} focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning focus-visible:ring-offset-2 dark:focus-visible:ring-offset-coolgray-100">
|
||||||
Files ({{ $this->fileCount }})
|
Files ({{ $this->fileCount }})
|
||||||
</button>
|
</button>
|
||||||
<button @click="activeTab = 'directories'"
|
<button @click="activeTab = 'directories'"
|
||||||
:class="activeTab === 'directories' ? 'border-b-2 dark:border-white border-black' : 'border-b-2 border-transparent'"
|
:class="activeTab === 'directories' ? 'border-b-2 dark:border-white border-black' : 'border-b-2 border-transparent'"
|
||||||
@if (!$hasDirectories) disabled @endif
|
@if (!$hasDirectories) disabled @endif
|
||||||
class="px-4 py-2 -mb-px font-medium transition-colors {{ $hasDirectories ? 'dark:text-neutral-400 dark:hover:text-white text-neutral-600 hover:text-black cursor-pointer' : 'opacity-50 cursor-not-allowed' }}">
|
class="px-4 py-2 -mb-px font-medium transition-colors {{ $hasDirectories ? 'dark:text-neutral-400 dark:hover:text-white text-neutral-600 hover:text-black cursor-pointer' : 'opacity-50 cursor-not-allowed' }} focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning focus-visible:ring-offset-2 dark:focus-visible:ring-offset-coolgray-100">
|
||||||
Directories ({{ $this->directoryCount }})
|
Directories ({{ $this->directoryCount }})
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
<div class="flex flex-col w-full gap-2 max-h-[80vh] overflow-y-auto scrollbar">
|
|
||||||
<form class="flex flex-col w-full gap-2 rounded-sm " wire:submit='submitPersistentVolume'>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<h3>Volume Mount</h3>
|
|
||||||
<div>Docker Volumes mounted to the container.</div>
|
|
||||||
</div>
|
|
||||||
@if ($isSwarm)
|
|
||||||
<h5>Swarm Mode detected: You need to set a shared volume (EFS/NFS/etc) on all the worker nodes if you
|
|
||||||
would
|
|
||||||
like to use a persistent volumes.</h5>
|
|
||||||
@endif
|
|
||||||
<div class="flex flex-col gap-2 px-2">
|
|
||||||
<x-forms.input placeholder="pv-name" id="name" label="Name" required helper="Volume name." />
|
|
||||||
@if ($isSwarm)
|
|
||||||
<x-forms.input placeholder="/root" id="host_path" label="Source Path" required
|
|
||||||
helper="Directory on the host system." />
|
|
||||||
@else
|
|
||||||
<x-forms.input placeholder="/root" id="host_path" label="Source Path"
|
|
||||||
helper="Directory on the host system." />
|
|
||||||
@endif
|
|
||||||
<x-forms.input placeholder="/tmp/root" id="mount_path" label="Destination Path" required
|
|
||||||
helper="Directory inside the container." />
|
|
||||||
<x-forms.button type="submit" @click="modalOpen=false">
|
|
||||||
Add
|
|
||||||
</x-forms.button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
<form class="flex flex-col w-full gap-2 rounded-sm py-4" wire:submit='submitFileStorage'>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<h3>File Mount</h3>
|
|
||||||
<div>Actual file mounted from the host system to the container.</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2 px-2">
|
|
||||||
<x-forms.input placeholder="/etc/nginx/nginx.conf" id="file_storage_path" label="Destination Path" required
|
|
||||||
helper="File location inside the container" />
|
|
||||||
<x-forms.textarea label="Content" id="file_storage_content"></x-forms.textarea>
|
|
||||||
<x-forms.button type="submit" @click="modalOpen=false">
|
|
||||||
Add
|
|
||||||
</x-forms.button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<form class="flex flex-col w-full gap-2 rounded-sm" wire:submit='submitFileStorageDirectory'>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<h3>Directory Mount</h3>
|
|
||||||
<div>Directory mounted from the host system to the container.</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2 px-2">
|
|
||||||
<x-forms.input placeholder="{{ application_configuration_dir() }}/{{ $resource->uuid }}/etc/nginx"
|
|
||||||
id="file_storage_directory_source" label="Source Directory" required
|
|
||||||
helper="Directory on the host system." />
|
|
||||||
<x-forms.input placeholder="/etc/nginx" id="file_storage_directory_destination"
|
|
||||||
label="Destination Directory" required helper="Directory inside the container." />
|
|
||||||
<x-forms.button type="submit" @click="modalOpen=false">
|
|
||||||
Add
|
|
||||||
</x-forms.button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
Loading…
Reference in a new issue