feat: can edit file/dir volumes from ui in compose based apps
This commit is contained in:
parent
c99bb4cfd7
commit
85b33a60b3
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
use App\Models\ServiceApplication;
|
use App\Models\ServiceApplication;
|
||||||
use App\Models\ServiceDatabase;
|
use App\Models\ServiceDatabase;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
@ -16,11 +17,11 @@ class ServerFilesFromServerJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
|
||||||
public function __construct(public ServiceApplication|ServiceDatabase $service)
|
public function __construct(public ServiceApplication|ServiceDatabase|Application $resource)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$this->service->getFilesFromServer(isInit: true);
|
$this->resource->getFilesFromServer(isInit: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Livewire\Project\Application;
|
namespace App\Livewire\Project\Application;
|
||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
|
use App\Models\LocalFileVolume;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Livewire\Component;
|
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->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
|
||||||
$this->ports_exposes = $this->application->ports_exposes;
|
$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') {
|
if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
|
||||||
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
|
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
|
||||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||||
@ -156,8 +157,36 @@ public function loadComposeFile($isInit = false)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation, 'initialDockerComposePrLocation' => $this->initialDockerComposePrLocation] = $this->application->loadComposeFile($isInit);
|
['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('success', 'Docker compose file loaded.');
|
||||||
$this->dispatch('compose_loaded');
|
$this->dispatch('compose_loaded');
|
||||||
|
$this->dispatch('refreshStorages');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->application->docker_compose_location = $this->initialDockerComposeLocation;
|
$this->application->docker_compose_location = $this->initialDockerComposeLocation;
|
||||||
$this->application->docker_compose_pr_location = $this->initialDockerComposePrLocation;
|
$this->application->docker_compose_pr_location = $this->initialDockerComposePrLocation;
|
||||||
@ -183,7 +212,8 @@ public function updatedApplicationBaseDirectory()
|
|||||||
$this->loadComposeFile();
|
$this->loadComposeFile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function updatedApplicationFqdn() {
|
public function updatedApplicationFqdn()
|
||||||
|
{
|
||||||
$this->resetDefaultLabels();
|
$this->resetDefaultLabels();
|
||||||
}
|
}
|
||||||
public function updatedApplicationBuildPack()
|
public function updatedApplicationBuildPack()
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Project\Service;
|
namespace App\Livewire\Project\Service;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
use App\Models\LocalFileVolume;
|
use App\Models\LocalFileVolume;
|
||||||
use App\Models\ServiceApplication;
|
use App\Models\ServiceApplication;
|
||||||
use App\Models\ServiceDatabase;
|
use App\Models\ServiceDatabase;
|
||||||
@ -12,7 +13,7 @@
|
|||||||
class FileStorage extends Component
|
class FileStorage extends Component
|
||||||
{
|
{
|
||||||
public LocalFileVolume $fileStorage;
|
public LocalFileVolume $fileStorage;
|
||||||
public ServiceApplication|ServiceDatabase|StandaloneClickhouse $resource;
|
public ServiceApplication|ServiceDatabase|StandaloneClickhouse|Application $resource;
|
||||||
public string $fs_path;
|
public string $fs_path;
|
||||||
public ?string $workdir = null;
|
public ?string $workdir = null;
|
||||||
|
|
||||||
@ -33,6 +34,43 @@ public function mount()
|
|||||||
$this->fs_path = $this->fileStorage->fs_path;
|
$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()
|
public function submit()
|
||||||
{
|
{
|
||||||
$original = $this->fileStorage->getOriginal();
|
$original = $this->fileStorage->getOriginal();
|
||||||
|
@ -7,12 +7,13 @@
|
|||||||
|
|
||||||
class Storage extends Component
|
class Storage extends Component
|
||||||
{
|
{
|
||||||
protected $listeners = ['addNewVolume'];
|
|
||||||
public $resource;
|
public $resource;
|
||||||
|
public function getListeners()
|
||||||
public function render()
|
|
||||||
{
|
{
|
||||||
return view('livewire.project.service.storage');
|
return [
|
||||||
|
'addNewVolume',
|
||||||
|
'storagesChanged'=> '$refresh'
|
||||||
|
];
|
||||||
}
|
}
|
||||||
public function addNewVolume($data)
|
public function addNewVolume($data)
|
||||||
{
|
{
|
||||||
@ -32,4 +33,8 @@ public function addNewVolume($data)
|
|||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.service.storage');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
class All extends Component
|
class All extends Component
|
||||||
{
|
{
|
||||||
public $resource;
|
public $resource;
|
||||||
protected $listeners = ['refreshStorages'];
|
protected $listeners = ['refreshStorages', 'storagesChanged' => '$refresh'];
|
||||||
|
|
||||||
public function refreshStorages()
|
public function refreshStorages()
|
||||||
{
|
{
|
||||||
|
@ -954,4 +954,9 @@ public function isWatchPathsTriggered(Collection $modified_files): bool
|
|||||||
});
|
});
|
||||||
return $matches->count() > 0;
|
return $matches->count() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getFilesFromServer(bool $isInit = false)
|
||||||
|
{
|
||||||
|
getFilesystemVolumesFromServer($this, $isInit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,26 @@ public function service()
|
|||||||
{
|
{
|
||||||
return $this->morphTo('resource');
|
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()
|
public function saveStorageOnServer()
|
||||||
{
|
{
|
||||||
$isService = data_get($this->resource, 'service');
|
$isService = data_get($this->resource, 'service');
|
||||||
@ -71,7 +91,6 @@ public function saveStorageOnServer()
|
|||||||
if ($chmod) {
|
if ($chmod) {
|
||||||
$commands->push("chmod $chmod $path");
|
$commands->push("chmod $chmod $path");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
|
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
|
||||||
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
|
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
|
||||||
|
@ -20,9 +20,9 @@ class EventServiceProvider extends ServiceProvider
|
|||||||
MaintenanceModeDisabledNotification::class,
|
MaintenanceModeDisabledNotification::class,
|
||||||
],
|
],
|
||||||
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
|
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
|
||||||
\SocialiteProviders\Azure\AzureExtendSocialite::class.'@handle',
|
\SocialiteProviders\Azure\AzureExtendSocialite::class . '@handle',
|
||||||
],
|
],
|
||||||
ProxyStarted::class => [
|
ProxyStarted::class => [
|
||||||
ProxyStartedNotification::class,
|
ProxyStartedNotification::class,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
use App\Models\EnvironmentVariable;
|
use App\Models\EnvironmentVariable;
|
||||||
use App\Models\Service;
|
|
||||||
use App\Models\ServiceApplication;
|
use App\Models\ServiceApplication;
|
||||||
use App\Models\ServiceDatabase;
|
use App\Models\ServiceDatabase;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
@ -21,12 +21,16 @@ function replaceVariables($variable)
|
|||||||
return $variable->replaceFirst('$', '')->replaceFirst('{', '')->replaceLast('}', '');
|
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 {
|
try {
|
||||||
$workdir = $oneService->service->workdir();
|
if ($oneService->getMorphClass() === 'App\Models\Application') {
|
||||||
$server = $oneService->service->server;
|
$workdir = $oneService->workdir();
|
||||||
|
$server = $oneService->destination->server;
|
||||||
|
} else{
|
||||||
|
$workdir = $oneService->service->workdir();
|
||||||
|
$server = $oneService->service->server;
|
||||||
|
}
|
||||||
$fileVolumes = $oneService->fileStorages()->get();
|
$fileVolumes = $oneService->fileStorages()->get();
|
||||||
$commands = collect([
|
$commands = collect([
|
||||||
"mkdir -p $workdir > /dev/null 2>&1 || true",
|
"mkdir -p $workdir > /dev/null 2>&1 || true",
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
href="#">Environment
|
href="#">Environment
|
||||||
Variables</a>
|
Variables</a>
|
||||||
@endif
|
@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'"
|
<a class="menu-item" :class="activeTab === 'storages' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'" href="#">Storages
|
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'" href="#">Storages
|
||||||
</a>
|
</a>
|
||||||
|
@ -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">
|
<div class="flex flex-col justify-center pb-4 text-sm select-text">
|
||||||
<h2>{{ data_get($resource, 'name', 'unknown') }}</h2>
|
<h2>{{ data_get($resource, 'name', 'unknown') }}</h2>
|
||||||
<div>{{ $workdir }}{{ $fs_path }} -> {{ $fileStorage->mount_path }}</div>
|
<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>
|
||||||
<div>
|
<form wire:submit='submit' class="flex flex-col gap-2">
|
||||||
<form wire:submit='submit' class="flex flex-col gap-2">
|
<div class="flex gap-2">
|
||||||
<div class="w-64">
|
@if ($fileStorage->is_directory)
|
||||||
<x-forms.checkbox instantSave label="Is directory?" id="fileStorage.is_directory"></x-forms.checkbox>
|
<x-modal-confirmation action="convertToFile" buttonTitle="Convert to file">
|
||||||
</div>
|
This will delete all files in this directory. It is not reversible. <br>Please think again.
|
||||||
@if (!$fileStorage->is_directory)
|
</x-modal-confirmation>
|
||||||
<x-forms.textarea label="Content" rows="20" id="fileStorage.content"></x-forms.textarea>
|
@else
|
||||||
<x-forms.button type="submit">Save</x-forms.button>
|
<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
|
@endif
|
||||||
</form>
|
<x-modal-confirmation isErrorButton buttonTitle="Delete">
|
||||||
</div>
|
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>
|
</div>
|
||||||
|
@ -14,25 +14,30 @@
|
|||||||
helper="For Preview Deployments, storage has a <span class='text-helper'>-pr-#PRNumber</span> in their
|
helper="For Preview Deployments, storage has a <span class='text-helper'>-pr-#PRNumber</span> in their
|
||||||
volume
|
volume
|
||||||
name, example: <span class='text-helper'>-pr-1</span>" />
|
name, example: <span class='text-helper'>-pr-1</span>" />
|
||||||
<x-modal-input buttonTitle="+ Add" title="New Persistent Storage">
|
@if ($resource?->build_pack !== 'dockercompose')
|
||||||
<livewire:project.shared.storages.add :uuid="$resource->uuid" />
|
<x-modal-input buttonTitle="+ Add" title="New Persistent Storage">
|
||||||
</x-modal-input>
|
<livewire:project.shared.storages.add :uuid="$resource->uuid" />
|
||||||
|
</x-modal-input>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="pb-4">Persistent storage to preserve data between deployments.</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)
|
@if ($resource->persistentStorages()->get()->count() === 0 && $resource->fileStorages()->get()->count() == 0)
|
||||||
<div>No storage found.</div>
|
<div class="pt-4">No storage found.</div>
|
||||||
@else
|
@endif
|
||||||
@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 ($resource->fileStorages()->get()->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 ($resource->fileStorages()->get()->sort() as $fileStorage)
|
||||||
<livewire:project.service.file-storage :fileStorage="$fileStorage"
|
<livewire:project.service.file-storage :fileStorage="$fileStorage"
|
||||||
wire:key="resource-{{ $resource->uuid }}" />
|
wire:key="resource-{{ $fileStorage->uuid }}" />
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@endif
|
|
||||||
@endif
|
@endif
|
||||||
@else
|
@else
|
||||||
@if ($resource->persistentStorages()->get()->count() > 0)
|
@if ($resource->persistentStorages()->get()->count() > 0)
|
||||||
@ -44,7 +49,8 @@
|
|||||||
@if ($resource->fileStorages()->get()->count() > 0)
|
@if ($resource->fileStorages()->get()->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 ($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
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>Environment variables (secrets) for this resource.</div>
|
<div>Environment variables (secrets) for this resource.</div>
|
||||||
@if ($resource->type() === 'service' || $resource?->build_pack === 'dockercompose')
|
@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
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@if ($view === 'normal')
|
@if ($view === 'normal')
|
||||||
|
Loading…
Reference in New Issue
Block a user