commit
93ec785f4f
@ -50,8 +50,9 @@ class General extends Component
|
||||
$this->getDbUrl();
|
||||
}
|
||||
public function getDbUrl() {
|
||||
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->destination->server->ip}:{$this->database->public_port}/{$this->database->postgres_db}";
|
||||
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->destination->server->getIp}:{$this->database->public_port}/{$this->database->postgres_db}";
|
||||
} else {
|
||||
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->uuid}:5432/{$this->database->postgres_db}";
|
||||
}
|
||||
|
@ -29,7 +29,10 @@ class Index extends Component
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->query = request()->query();
|
||||
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
|
||||
$this->refreshStack();
|
||||
$this->applications = $this->service->applications->sort();
|
||||
$this->databases = $this->service->databases->sort();
|
||||
ray($this->applications);
|
||||
ray($this->databases);
|
||||
}
|
||||
public function saveCompose($raw)
|
||||
{
|
||||
|
35
app/Http/Livewire/Project/Service/Storage.php
Normal file
35
app/Http/Livewire/Project/Service/Storage.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Service;
|
||||
|
||||
use App\Models\LocalPersistentVolume;
|
||||
use Livewire\Component;
|
||||
|
||||
class Storage extends Component
|
||||
{
|
||||
protected $listeners = ['addNewVolume'];
|
||||
public $resource;
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.storage');
|
||||
}
|
||||
public function addNewVolume($data)
|
||||
{
|
||||
try {
|
||||
LocalPersistentVolume::create([
|
||||
'name' => $data['name'],
|
||||
'mount_path' => $data['mount_path'],
|
||||
'host_path' => $data['host_path'],
|
||||
'resource_id' => $this->resource->id,
|
||||
'resource_type' => $this->resource->getMorphClass(),
|
||||
]);
|
||||
$this->resource->refresh();
|
||||
$this->emit('success', 'Storage added successfully');
|
||||
$this->emit('clearAddStorage');
|
||||
$this->emit('refreshStorages');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
@ -33,7 +33,7 @@ class Add extends Component
|
||||
{
|
||||
$this->validate();
|
||||
$name = $this->uuid . '-' . $this->name;
|
||||
$this->emitUp('submit', [
|
||||
$this->emit('addNewVolume', [
|
||||
'name' => $name,
|
||||
'mount_path' => $this->mount_path,
|
||||
'host_path' => $this->host_path,
|
||||
|
@ -7,30 +7,11 @@ use Livewire\Component;
|
||||
|
||||
class All extends Component
|
||||
{
|
||||
public bool $isHeaderVisible = true;
|
||||
public $resource;
|
||||
protected $listeners = ['refreshStorages', 'submit'];
|
||||
protected $listeners = ['refreshStorages'];
|
||||
|
||||
public function refreshStorages()
|
||||
{
|
||||
$this->resource->refresh();
|
||||
}
|
||||
|
||||
public function submit($data)
|
||||
{
|
||||
try {
|
||||
LocalPersistentVolume::create([
|
||||
'name' => $data['name'],
|
||||
'mount_path' => $data['mount_path'],
|
||||
'host_path' => $data['host_path'],
|
||||
'resource_id' => $this->resource->id,
|
||||
'resource_type' => $this->resource->getMorphClass(),
|
||||
]);
|
||||
$this->resource->refresh();
|
||||
$this->emit('success', 'Storage added successfully');
|
||||
$this->emit('clearAddStorage');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,10 @@ class Application extends BaseModel
|
||||
{
|
||||
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
||||
}
|
||||
public function fileStorages()
|
||||
{
|
||||
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||
}
|
||||
|
||||
public function type()
|
||||
{
|
||||
|
@ -31,23 +31,18 @@ class LocalFileVolume extends BaseModel
|
||||
}
|
||||
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
|
||||
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
|
||||
ray($isFile);
|
||||
if ($isFile == 'OK' && $fileVolume->is_directory) {
|
||||
throw new \Exception("File $path 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.");
|
||||
} else if ($isDir == 'OK' && !$fileVolume->is_directory) {
|
||||
throw new \Exception("File $path is a directory on the server, but you are trying to mark it as a file. Please delete the directory on the server or mark it as directory.");
|
||||
}
|
||||
if (($isFile == 'NOK' && !$fileVolume->is_directory) || $isFile == 'OK') {
|
||||
$rootDir = Str::of($path)->dirname();
|
||||
$commands->push("mkdir -p $rootDir > /dev/null 2>&1 || true");
|
||||
$commands->push("touch $path > /dev/null 2>&1 || true");
|
||||
if ($content) {
|
||||
$content = base64_encode($content);
|
||||
$commands->push("echo '$content' | base64 -d > $path");
|
||||
}
|
||||
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
|
||||
if (!$fileVolume->is_directory && $isDir == 'NOK') {
|
||||
$content = base64_encode($content);
|
||||
$commands->push("echo '$content' | base64 -d > $path");
|
||||
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
|
||||
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
|
||||
}
|
||||
ray($commands->toArray());
|
||||
return instant_remote_process($commands, $server);
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ namespace App\Models;
|
||||
use App\Enums\ProxyStatus;
|
||||
use App\Enums\ProxyTypes;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
||||
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
||||
|
||||
@ -120,7 +121,20 @@ class Server extends BaseModel
|
||||
{
|
||||
return $this->hasMany(Service::class);
|
||||
}
|
||||
|
||||
public function getIp(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
if (isDev()) {
|
||||
return '127.0.0.1';
|
||||
}
|
||||
if ($this->ip === 'host.docker.internal') {
|
||||
return base_ip();
|
||||
}
|
||||
return $this->ip;
|
||||
}
|
||||
);
|
||||
}
|
||||
public function previews()
|
||||
{
|
||||
return $this->destinations()->map(function ($standaloneDocker) {
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class ServiceDatabase extends BaseModel
|
||||
{
|
||||
|
@ -76,6 +76,11 @@ class StandalonePostgresql extends BaseModel
|
||||
return $this->belongsTo(Environment::class);
|
||||
}
|
||||
|
||||
public function fileStorages()
|
||||
{
|
||||
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||
}
|
||||
|
||||
public function destination()
|
||||
{
|
||||
return $this->morphTo();
|
||||
|
@ -85,28 +85,39 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneS
|
||||
} else {
|
||||
$fileLocation = $path;
|
||||
}
|
||||
ray($path,$fileLocation);
|
||||
// Exists and is a file
|
||||
$isFile = instant_remote_process(["test -f $fileLocation && echo OK || echo NOK"], $server);
|
||||
// Exists and is a directory
|
||||
$isDir = instant_remote_process(["test -d $fileLocation && echo OK || echo NOK"], $server);
|
||||
if ($isFile === 'NOK' &&!$fileVolume->is_directory && $isInit) {
|
||||
$fileVolume->saveStorageOnServer($oneService);
|
||||
continue;
|
||||
}
|
||||
if ($isFile == 'OK' && !$fileVolume->is_directory) {
|
||||
|
||||
if ($isFile == 'OK') {
|
||||
// If its a file & exists
|
||||
$filesystemContent = instant_remote_process(["cat $fileLocation"], $server);
|
||||
if (base64_encode($filesystemContent) != base64_encode($content)) {
|
||||
$fileVolume->content = $filesystemContent;
|
||||
$fileVolume->save();
|
||||
}
|
||||
} else {
|
||||
if ($isDir == 'OK') {
|
||||
$fileVolume->content = null;
|
||||
$fileVolume->is_directory = true;
|
||||
$fileVolume->save();
|
||||
} else {
|
||||
$fileVolume->content = null;
|
||||
$fileVolume->is_directory = false;
|
||||
$fileVolume->save();
|
||||
}
|
||||
$fileVolume->content = $filesystemContent;
|
||||
$fileVolume->is_directory = false;
|
||||
$fileVolume->save();
|
||||
} else if ($isDir == 'OK') {
|
||||
// If its a directory & exists
|
||||
$fileVolume->content = null;
|
||||
$fileVolume->is_directory = true;
|
||||
$fileVolume->save();
|
||||
} else if ($isFile == 'NOK' && $isDir == 'NOK' && !$fileVolume->is_directory && $isInit && $content) {
|
||||
// Does not exists (no dir or file), not flagged as directory, is init, has content
|
||||
$fileVolume->content = $content;
|
||||
$fileVolume->is_directory = false;
|
||||
$fileVolume->save();
|
||||
$content = base64_encode($content);
|
||||
$dir = Str::of($fileLocation)->dirname();
|
||||
instant_remote_process([
|
||||
"mkdir -p $dir",
|
||||
"echo '$content' | base64 -d > $fileLocation"
|
||||
], $server);
|
||||
} else if ($isFile == 'NOK' && $isDir == 'NOK' && $fileVolume->is_directory && $isInit) {
|
||||
$fileVolume->content = null;
|
||||
$fileVolume->is_directory = true;
|
||||
$fileVolume->save();
|
||||
instant_remote_process(["mkdir -p $fileLocation"], $server);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
|
@ -7,7 +7,7 @@ return [
|
||||
|
||||
// The release version of your application
|
||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||
'release' => '4.0.0-beta.62',
|
||||
'release' => '4.0.0-beta.63',
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
return '4.0.0-beta.62';
|
||||
return '4.0.0-beta.63';
|
||||
|
@ -34,14 +34,14 @@ services:
|
||||
POSTGRES_DB: "${DB_DATABASE:-coolify}"
|
||||
POSTGRES_HOST_AUTH_METHOD: "trust"
|
||||
volumes:
|
||||
- ./_data/coolify/_volumes/database/:/var/lib/postgresql/data
|
||||
- /data/coolify/_volumes/database/:/var/lib/postgresql/data
|
||||
redis:
|
||||
ports:
|
||||
- "${FORWARD_REDIS_PORT:-6379}:6379"
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- ./_data/coolify/_volumes/redis/:/data
|
||||
- /data/coolify/_volumes/redis/:/data
|
||||
vite:
|
||||
image: node:19
|
||||
working_dir: /var/www/html
|
||||
@ -56,7 +56,7 @@ services:
|
||||
volumes:
|
||||
- /:/host
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./_data/coolify/:/data/coolify
|
||||
- /data/coolify/:/data/coolify
|
||||
mailpit:
|
||||
image: "axllent/mailpit:latest"
|
||||
container_name: coolify-mail
|
||||
@ -76,6 +76,6 @@ services:
|
||||
MINIO_ACCESS_KEY: "${MINIO_ACCESS_KEY:-minioadmin}"
|
||||
MINIO_SECRET_KEY: "${MINIO_SECRET_KEY:-minioadmin}"
|
||||
volumes:
|
||||
- ./_data/coolify/_volumes/minio/:/data
|
||||
- /data/coolify/_volumes/minio/:/data
|
||||
networks:
|
||||
- coolify
|
||||
|
@ -26,27 +26,29 @@
|
||||
<x-forms.input label="Image" id="database.image" required
|
||||
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/postgres'>https://hub.docker.com/_/postgres</a>" />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
@if ($database->started_at)
|
||||
<x-forms.input label="Username" id="database.postgres_username" placeholder="If empty: postgres"
|
||||
readonly helper="You can only modify it before the initial start." />
|
||||
<x-forms.input label="Password" id="database.postgres_password" type="password" required readonly
|
||||
helper="You can only modify it before the initial start." />
|
||||
<x-forms.input label="Database" id="database.postgres_db"
|
||||
|
||||
@if ($database->started_at)
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input label="Initial Username" id="database.postgres_username" placeholder="If empty: postgres"
|
||||
readonly helper="You can only change this in the database." />
|
||||
<x-forms.input label="Initial Password" id="database.postgres_password" type="password" required readonly
|
||||
helper="You can only change this in the database." />
|
||||
<x-forms.input label="Initial Database" id="database.postgres_db"
|
||||
placeholder="If empty, it will be the same as Username." readonly
|
||||
helper="You can only modify it before the initial start." />
|
||||
@else
|
||||
<x-forms.input label="Username" id="database.postgres_user" placeholder="If empty: postgres"
|
||||
helper="You can only modify it before the initial start." />
|
||||
<x-forms.input label="Password" id="database.postgres_password" type="password" required
|
||||
helper="You can only modify it before the initial start." />
|
||||
helper="You can only change this in the database." />
|
||||
</div>
|
||||
@else
|
||||
<div class="pt-8 text-warning">Please verify these values. You can only modify them before the initial start. After that, you need to modify it in the database.
|
||||
</div>
|
||||
<div class="flex gap-2 pb-8">
|
||||
<x-forms.input label="Username" id="database.postgres_user" placeholder="If empty: postgres" />
|
||||
<x-forms.input label="Password" id="database.postgres_password" type="password" required />
|
||||
<x-forms.input label="Database" id="database.postgres_db"
|
||||
placeholder="If empty, it will be the same as Username."
|
||||
helper="You can only modify it before the initial start." />
|
||||
@endif
|
||||
</div>
|
||||
placeholder="If empty, it will be the same as Username." />
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input label="Initial Arguments" id="database.postgres_initdb_args"
|
||||
<x-forms.input label="Initial Database Arguments" id="database.postgres_initdb_args"
|
||||
placeholder="If empty, use default. See in docker docs." />
|
||||
<x-forms.input label="Host Auth Method" id="database.postgres_host_auth_method"
|
||||
placeholder="If empty, use default. See in docker docs." />
|
||||
@ -55,11 +57,12 @@
|
||||
<h3 class="py-2">Network</h3>
|
||||
<div class="flex items-end gap-2">
|
||||
<x-forms.input placeholder="3000:5432" id="database.ports_mappings" label="Ports Mappings"
|
||||
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold text-warning'>Example</span>3000:5432,3002:5433" />
|
||||
<x-forms.input placeholder="5432" disabled="{{$database->is_public}}" id="database.public_port" label="Public Port" />
|
||||
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
|
||||
</div>
|
||||
<x-forms.input label="Postgres URL" readonly wire:model="db_url" />
|
||||
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold text-warning'>Example</span>3000:5432,3002:5433" />
|
||||
<x-forms.input placeholder="5432" disabled="{{ $database->is_public }}" id="database.public_port"
|
||||
label="Public Port" />
|
||||
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
|
||||
</div>
|
||||
<x-forms.input label="Postgres URL" readonly wire:model="db_url" />
|
||||
</div>
|
||||
</form>
|
||||
<div class="pb-16">
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div x-data="{ raw: true, activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }" wire:poll.10000ms="checkStatus">
|
||||
<div x-data="{ raw: true, activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }" wire:poll.2000ms="checkStatus">
|
||||
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" />
|
||||
<livewire:project.service.compose-modal :raw="$service->docker_compose_raw" :actual="$service->docker_compose" />
|
||||
<div class="flex h-full pt-6">
|
||||
@ -102,42 +102,17 @@
|
||||
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'storages'">
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Storages</h2>
|
||||
</div>
|
||||
<div class="pb-4">Persistent storage to preserve data between deployments.</div>
|
||||
<span class="text-warning">Please modify storage layout in your Docker Compose file.</span>
|
||||
@foreach ($applications as $application)
|
||||
@if ($loop->first)
|
||||
<livewire:project.shared.storages.all :resource="$application" />
|
||||
@else
|
||||
<livewire:project.shared.storages.all :resource="$application" :isHeaderVisible="false" />
|
||||
@endif
|
||||
@if ($application->fileStorages()->get()->count() > 0)
|
||||
<h5 class="py-4">Mounted Files/Dirs (binds)</h5>
|
||||
<div class="flex flex-col gap-4">
|
||||
@foreach ($application->fileStorages()->get()->sort() as $fileStorage)
|
||||
<livewire:project.service.file-storage :fileStorage="$fileStorage"
|
||||
wire:key="{{ $loop->index }}" />
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
<livewire:project.service.storage wire:key="application-{{ $application->id }}"
|
||||
:resource="$application" />
|
||||
@endforeach
|
||||
@foreach ($databases as $database)
|
||||
@if ($loop->first)
|
||||
<h3 class="pt-4">{{ Str::headline($database->name) }}</h3>
|
||||
@if ($applications->count() > 0)
|
||||
<livewire:project.shared.storages.all :resource="$database" :isHeaderVisible="false" />
|
||||
@else
|
||||
<livewire:project.shared.storages.all :resource="$database" />
|
||||
@endif
|
||||
@if ($database->fileStorages()->get()->count() > 0)
|
||||
<h5 class="py-4">Mounted Files/Dirs (binds)</h5>
|
||||
<div class="flex flex-col gap-4">
|
||||
@foreach ($database->fileStorages()->get()->sort() as $fileStorage)
|
||||
<livewire:project.service.file-storage :fileStorage="$fileStorage"
|
||||
wire:key="{{ $loop->index }}" />
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<livewire:project.shared.storages.all :resource="$database" :isHeaderVisible="false" />
|
||||
@endif
|
||||
<livewire:project.service.storage wire:key="database-{{ $database->id }}" :resource="$database" />
|
||||
@endforeach
|
||||
|
||||
</div>
|
||||
|
@ -24,15 +24,13 @@
|
||||
<livewire:project.service.application :application="$serviceApplication" />
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'storages'">
|
||||
<livewire:project.shared.storages.all :resource="$serviceApplication" />
|
||||
@if ($serviceApplication->fileStorages()->get()->count() > 0)
|
||||
<h5 class="py-4">Mounted Files/Dirs (binds)</h5>
|
||||
<div class="flex flex-col gap-4">
|
||||
@foreach ($serviceApplication->fileStorages()->get()->sort() as $fileStorage)
|
||||
<livewire:project.service.file-storage :fileStorage="$fileStorage" wire:key="{{ $loop->index }}" />
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Storages</h2>
|
||||
</div>
|
||||
<div class="pb-4">Persistent storage to preserve data between deployments.</div>
|
||||
<span class="text-warning">Please modify storage layout in your Docker Compose file.</span>
|
||||
<livewire:project.service.storage wire:key="application-{{ $serviceApplication->id }}"
|
||||
:resource="$serviceApplication" />
|
||||
</div>
|
||||
@endisset
|
||||
@isset($serviceDatabase)
|
||||
@ -40,15 +38,13 @@
|
||||
<livewire:project.service.database :database="$serviceDatabase" />
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'storages'">
|
||||
<livewire:project.shared.storages.all :resource="$serviceDatabase" />
|
||||
@if ($serviceDatabase->fileStorages()->get()->count() > 0)
|
||||
<h5 class="py-4">Mounted Files/Dirs (binds)</h5>
|
||||
<div class="flex flex-col gap-4">
|
||||
@foreach ($serviceDatabase->fileStorages()->get()->sort() as $fileStorage)
|
||||
<livewire:project.service.file-storage :fileStorage="$fileStorage" wire:key="{{ $loop->index }}" />
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Storages</h2>
|
||||
</div>
|
||||
<div class="pb-4">Persistent storage to preserve data between deployments.</div>
|
||||
<span class="text-warning">Please modify storage layout in your Docker Compose file.</span>
|
||||
<livewire:project.service.storage wire:key="application-{{ $serviceDatabase->id }}"
|
||||
:resource="$serviceDatabase" />
|
||||
</div>
|
||||
@endisset
|
||||
</div>
|
||||
|
49
resources/views/livewire/project/service/storage.blade.php
Normal file
49
resources/views/livewire/project/service/storage.blade.php
Normal file
@ -0,0 +1,49 @@
|
||||
<div>
|
||||
@if (
|
||||
$resource->getMorphClass() == 'App\Models\Application' ||
|
||||
$resource->getMorphClass() == 'App\Models\StandalonePostgresql')
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Storages</h2>
|
||||
<x-helper
|
||||
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-forms.button class="btn" onclick="newStorage.showModal()">+ Add</x-forms.button>
|
||||
<livewire:project.shared.storages.add :uuid="$resource->uuid" />
|
||||
</div>
|
||||
<div class="pb-4">Persistent storage to preserve data between deployments.</div>
|
||||
@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
|
||||
@endif
|
||||
@else
|
||||
@if (
|
||||
$resource->persistentStorages()->get()->count() > 0 ||
|
||||
$resource->fileStorages()->get()->count() > 0)
|
||||
<h3 class="pt-4">{{ Str::headline($resource->name) }} </h3>
|
||||
@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-{{ $resource->uuid }}" />
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
@ -29,13 +29,8 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-1 pl-8">
|
||||
@if (serviceStatus($resource) === 'running')
|
||||
<livewire:project.shared.get-logs :server="$server" :container="$container" />
|
||||
@else
|
||||
Service is not running.
|
||||
@endif
|
||||
<livewire:project.shared.get-logs :server="$server" :container="$container" />
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</x-layout>
|
||||
|
@ -1,25 +1,4 @@
|
||||
<div>
|
||||
@if ($isHeaderVisible)
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Storages</h2>
|
||||
@if ($resource->type() !== 'service')
|
||||
<x-helper
|
||||
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-forms.button class="btn" onclick="newStorage.showModal()">+ Add</x-forms.button>
|
||||
<livewire:project.shared.storages.add :uuid="$resource->uuid" />
|
||||
@endif
|
||||
</div>
|
||||
<div class="pb-4">Persistent storage to preserve data between deployments.</div>
|
||||
@if ($resource->type() === 'service')
|
||||
<span class="text-warning">Please modify storage layout in your <a class="underline"
|
||||
href="{{ Str::of(url()->current())->beforeLast('/') }}">Docker Compose</a> file.</span>
|
||||
<h2 class="pt-4">{{ Str::headline($resource->name) }} </h2>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex flex-col gap-4">
|
||||
@foreach ($resource->persistentStorages as $storage)
|
||||
@if ($resource->type() === 'service')
|
||||
|
@ -14,8 +14,7 @@
|
||||
@click.prevent="activeTab = 'source'; window.location.hash = 'source'" href="#">Source</a>
|
||||
@endif
|
||||
<a :class="activeTab === 'server' && 'text-white'"
|
||||
@click.prevent="activeTab = 'server'; window.location.hash = 'server'"
|
||||
href="#">Server
|
||||
@click.prevent="activeTab = 'server'; window.location.hash = 'server'" href="#">Server
|
||||
</a>
|
||||
<a :class="activeTab === 'storages' && 'text-white'"
|
||||
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'" href="#">Storages
|
||||
@ -27,8 +26,8 @@
|
||||
</a>
|
||||
@endif
|
||||
<a :class="activeTab === 'health' && 'text-white'"
|
||||
@click.prevent="activeTab = 'health'; window.location.hash = 'health'" href="#">Health Checks
|
||||
</a>
|
||||
@click.prevent="activeTab = 'health'; window.location.hash = 'health'" href="#">Health Checks
|
||||
</a>
|
||||
<a :class="activeTab === 'rollback' && 'text-white'"
|
||||
@click.prevent="activeTab = 'rollback'; window.location.hash = 'rollback'" href="#">Rollback
|
||||
</a>
|
||||
@ -56,7 +55,7 @@
|
||||
<livewire:project.shared.destination :destination="$application->destination" />
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'storages'">
|
||||
<livewire:project.shared.storages.all :resource="$application" />
|
||||
<livewire:project.service.storage :resource="$application" />
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'previews'">
|
||||
<livewire:project.application.previews :application="$application" />
|
||||
|
@ -47,7 +47,7 @@
|
||||
<livewire:project.shared.destination :destination="$database->destination" />
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'storages'">
|
||||
<livewire:project.shared.storages.all :resource="$database" />
|
||||
<livewire:project.service.storage :resource="$database" />
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'resource-limits'">
|
||||
<livewire:project.shared.resource-limits :resource="$database" />
|
||||
|
57
scripts/sync_volume.sh
Normal file
57
scripts/sync_volume.sh
Normal file
@ -0,0 +1,57 @@
|
||||
#!/bin/bash
|
||||
# Sync docker volumes between two servers
|
||||
|
||||
VERSION="1.0.0"
|
||||
SOURCE=$1
|
||||
DESTINATION=$2
|
||||
set -e
|
||||
if [ -z "$SOURCE" ]; then
|
||||
echo "Source server is not specified."
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$DESTINATION" ]; then
|
||||
echo "Destination server is not specified."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SOURCE_USER=$(echo $SOURCE | cut -d@ -f1)
|
||||
SOURCE_SERVER=$(echo $SOURCE | cut -d: -f1 | cut -d@ -f2)
|
||||
SOURCE_PORT=$(echo $SOURCE | cut -d: -f2 | cut -d/ -f1)
|
||||
SOURCE_VOLUME_NAME=$(echo $SOURCE | cut -d/ -f2)
|
||||
|
||||
if ! [[ "$SOURCE_PORT" =~ ^[0-9]+$ ]]; then
|
||||
echo "Invalid source port: $SOURCE_PORT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DESTINATION_USER=$(echo $DESTINATION | cut -d@ -f1)
|
||||
DESTINATION_SERVER=$(echo $DESTINATION | cut -d: -f1 | cut -d@ -f2)
|
||||
DESTINATION_PORT=$(echo $DESTINATION | cut -d: -f2 | cut -d/ -f1)
|
||||
DESTINATION_VOLUME_NAME=$(echo $DESTINATION | cut -d/ -f2)
|
||||
|
||||
if ! [[ "$DESTINATION_PORT" =~ ^[0-9]+$ ]]; then
|
||||
echo "Invalid destination port: $DESTINATION_PORT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Generating backup file to ./$SOURCE_VOLUME_NAME.tgz"
|
||||
ssh -p $SOURCE_PORT $SOURCE_USER@$SOURCE_SERVER "docker run -v $SOURCE_VOLUME_NAME:/volume --rm --log-driver none loomchild/volume-backup backup -c pigz -v" >./$SOURCE_VOLUME_NAME.tgz
|
||||
echo ""
|
||||
if [ -f "./$SOURCE_VOLUME_NAME.tgz" ]; then
|
||||
echo "Uploading backup file to $DESTINATION_SERVER:~/$DESTINATION_VOLUME_NAME.tgz"
|
||||
scp -P $DESTINATION_PORT ./$SOURCE_VOLUME_NAME.tgz $DESTINATION_USER@$DESTINATION_SERVER:~/$DESTINATION_VOLUME_NAME.tgz
|
||||
echo ""
|
||||
echo "Restoring backup file on remote ($DESTINATION_SERVER:/~/$DESTINATION_VOLUME_NAME.tgz)"
|
||||
ssh -p $DESTINATION_PORT $DESTINATION_USER@$DESTINATION_SERVER "docker run -i -v $DESTINATION_VOLUME_NAME:/volume --log-driver none --rm loomchild/volume-backup restore -c pigz -vf < ~/$DESTINATION_VOLUME_NAME.tgz"
|
||||
echo ""
|
||||
echo "Deleting backup file on remote ($DESTINATION_SERVER:/~/$DESTINATION_VOLUME_NAME.tgz)"
|
||||
ssh -p $DESTINATION_PORT $DESTINATION_USER@$DESTINATION_SERVER "rm ~/$DESTINATION_VOLUME_NAME.tgz"
|
||||
|
||||
echo ""
|
||||
echo "Local file ./$SOURCE_VOLUME_NAME.tgz is not deleted."
|
||||
|
||||
echo ""
|
||||
echo "WARNING: If you are copying a database volume, you need to set the right users/passwords on the destination service's environment variables."
|
||||
echo "Why? Because we are copying the volume as-is, so the database credentials will bethe same as on the source volume."
|
||||
fi
|
||||
|
@ -2,6 +2,6 @@
|
||||
"plausible-analytics": {
|
||||
"documentation": "https://plausible.io/docs",
|
||||
"slogan": "A lighweight and open-source website analytics tool.",
|
||||
"compose": "dmVyc2lvbjogIjMuMyIKc2VydmljZXM6CiAgcGxhdXNpYmxlLWFuYWx5dGljczoKICAgIGltYWdlOiBwbGF1c2libGUvYW5hbHl0aWNzOnYyLjAKICAgIGNvbW1hbmQ6IHNoIC1jICJzbGVlcCAxMCAmJiAvZW50cnlwb2ludC5zaCBkYiBjcmVhdGVkYiAmJiAvZW50cnlwb2ludC5zaCBkYiBtaWdyYXRlICYmIC9lbnRyeXBvaW50LnNoIHJ1biIKICAgIGVudmlyb25tZW50OgogICAgICAtIERBVEFCQVNFX1VSTD1wb3N0Z3JlczovL3Bvc3RncmVzOiRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTQHBsYXVzaWJsZV9kYi9wbGF1c2libGUKICAgICAgLSBCQVNFX1VSTD0kU0VSVklDRV9GUUROX1BMQVVTSUJMRQogICAgICAtIFNFQ1JFVF9LRVlfQkFTRT0kU0VSVklDRV9CQVNFNjRfNjRfUExBVVNJQkxFCiAgICBkZXBlbmRzX29uOgogICAgICAtIHBsYXVzaWJsZV9kYgogICAgICAtIHBsYXVzaWJsZV9ldmVudHNfZGIKICAgICAgLSBtYWlsCgogIG1haWw6CiAgICBpbWFnZTogYnl0ZW1hcmsvc210cAoKICBwbGF1c2libGVfZGI6CiAgICBpbWFnZTogcG9zdGdyZXM6MTQtYWxwaW5lCiAgICB2b2x1bWVzOgogICAgICAtIGRiLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQT1NUR1JFU19EQj1wbGF1c2libGUKICAgICAgLSBQT1NUR1JFU19QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwoKICBwbGF1c2libGVfZXZlbnRzX2RiOgogICAgaW1hZ2U6IGNsaWNraG91c2UvY2xpY2tob3VzZS1zZXJ2ZXI6MjMuMy43LjUtYWxwaW5lCiAgICB2b2x1bWVzOgogICAgICAtIHR5cGU6IHZvbHVtZQogICAgICAgIHNvdXJjZTogZXZlbnQtZGF0YQogICAgICAgIHRhcmdldDogL3Zhci9saWIvY2xpY2tob3VzZQogICAgICAtIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vY2xpY2tob3VzZS9jbGlja2hvdXNlLWNvbmZpZy54bWwKICAgICAgICB0YXJnZXQ6IC9ldGMvY2xpY2tob3VzZS1zZXJ2ZXIvY29uZmlnLmQvbG9nZ2luZy54bWwKICAgICAgICByZWFkX29ubHk6IHRydWUKICAgICAgICBjb250ZW50OiA+LQogICAgICAgICAgPGNsaWNraG91c2U+PHByb2ZpbGVzPjxkZWZhdWx0Pjxsb2dfcXVlcmllcz4wPC9sb2dfcXVlcmllcz48bG9nX3F1ZXJ5X3RocmVhZHM+MDwvbG9nX3F1ZXJ5X3RocmVhZHM+PC9kZWZhdWx0PjwvcHJvZmlsZXM+PC9jbGlja2hvdXNlPgogICAgICAtIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vY2xpY2tob3VzZS9jbGlja2hvdXNlLXVzZXItY29uZmlnLnhtbAogICAgICAgIHRhcmdldDogL2V0Yy9jbGlja2hvdXNlLXNlcnZlci91c2Vycy5kL2xvZ2dpbmcueG1sCiAgICAgICAgcmVhZF9vbmx5OiB0cnVlCiAgICAgICAgY29udGVudDogPi0KICAgICAgICAgIDxjbGlja2hvdXNlPjxsb2dnZXI+PGxldmVsPndhcm5pbmc8L2xldmVsPjxjb25zb2xlPnRydWU8L2NvbnNvbGU+PC9sb2dnZXI+PHF1ZXJ5X3RocmVhZF9sb2cKICAgICAgICAgIHJlbW92ZT0icmVtb3ZlIi8+PHF1ZXJ5X2xvZyByZW1vdmU9InJlbW92ZSIvPjx0ZXh0X2xvZwogICAgICAgICAgcmVtb3ZlPSJyZW1vdmUiLz48dHJhY2VfbG9nIHJlbW92ZT0icmVtb3ZlIi8+PG1ldHJpY19sb2cKICAgICAgICAgIHJlbW92ZT0icmVtb3ZlIi8+PGFzeW5jaHJvbm91c19tZXRyaWNfbG9nCiAgICAgICAgICByZW1vdmU9InJlbW92ZSIvPjxzZXNzaW9uX2xvZyByZW1vdmU9InJlbW92ZSIvPjxwYXJ0X2xvZwogICAgICAgICAgcmVtb3ZlPSJyZW1vdmUiLz48L2NsaWNraG91c2U+CiAgICB1bGltaXRzOgogICAgICBub2ZpbGU6CiAgICAgICAgc29mdDogMjYyMTQ0CiAgICAgICAgaGFyZDogMjYyMTQ0Cg=="
|
||||
"compose": "dmVyc2lvbjogJzMuMycKc2VydmljZXM6CiAgcGxhdXNpYmxlX2RiOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNC1hbHBpbmUnCiAgICByZXN0YXJ0OiBhbHdheXMKICAgIHZvbHVtZXM6CiAgICAgIC0gJy9ldGMvZGF0YS9wbGF1c2libGUvZGItZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQT1NUR1JFU19QQVNTV09SRD0kUE9TVEdSRVNfUEFTU1dPUkQKICBwbGF1c2libGVfZXZlbnRzX2RiOgogICAgaW1hZ2U6ICdjbGlja2hvdXNlL2NsaWNraG91c2Utc2VydmVyOjIzLjMuNy41LWFscGluZScKICAgIHJlc3RhcnQ6IGFsd2F5cwogICAgdm9sdW1lczoKICAgICAgLSAnL2V0Yy9kYXRhL3BsYXVzaWJsZS9ldmVudC1kYXRhOi92YXIvbGliL2NsaWNraG91c2UnCiAgICAgIC0gdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogL2V0Yy9kYXRhL3BsYXVzaWJsZS9jbGlja2hvdXNlL2NsaWNraG91c2UtY29uZmlnLnhtbAogICAgICAgIHRhcmdldDogL2V0Yy9jbGlja2hvdXNlLXNlcnZlci9jb25maWcuZC9sb2dnaW5nLnhtbAogICAgICAgIHJlYWRfb25seTogdHJ1ZQogICAgICAgIGNvbnRlbnQ6ID4tCiAgICAgICAgICA8Y2xpY2tob3VzZT48cHJvZmlsZXM+PGRlZmF1bHQ+PGxvZ19xdWVyaWVzPjA8L2xvZ19xdWVyaWVzPjxsb2dfcXVlcnlfdGhyZWFkcz4wPC9sb2dfcXVlcnlfdGhyZWFkcz48L2RlZmF1bHQ+PC9wcm9maWxlcz48L2NsaWNraG91c2U+CiAgICAgIC0gdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogL2V0Yy9kYXRhL3BsYXVzaWJsZS9jbGlja2hvdXNlL2NsaWNraG91c2UtdXNlci1jb25maWcueG1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2NsaWNraG91c2Utc2VydmVyL3VzZXJzLmQvbG9nZ2luZy54bWwKICAgICAgICByZWFkX29ubHk6IHRydWUKICAgICAgICBjb250ZW50OiA+LQogICAgICAgICAgPGNsaWNraG91c2U+PGxvZ2dlcj48bGV2ZWw+d2FybmluZzwvbGV2ZWw+PGNvbnNvbGU+dHJ1ZTwvY29uc29sZT48L2xvZ2dlcj48cXVlcnlfdGhyZWFkX2xvZwogICAgICAgICAgcmVtb3ZlPSJyZW1vdmUiLz48cXVlcnlfbG9nIHJlbW92ZT0icmVtb3ZlIi8+PHRleHRfbG9nCiAgICAgICAgICByZW1vdmU9InJlbW92ZSIvPjx0cmFjZV9sb2cgcmVtb3ZlPSJyZW1vdmUiLz48bWV0cmljX2xvZwogICAgICAgICAgcmVtb3ZlPSJyZW1vdmUiLz48YXN5bmNocm9ub3VzX21ldHJpY19sb2cKICAgICAgICAgIHJlbW92ZT0icmVtb3ZlIi8+PHNlc3Npb25fbG9nIHJlbW92ZT0icmVtb3ZlIi8+PHBhcnRfbG9nCiAgICAgICAgICByZW1vdmU9InJlbW92ZSIvPjwvY2xpY2tob3VzZT4KICAgIHVsaW1pdHM6CiAgICAgICAgbm9maWxlOgogICAgICAgICAgc29mdDogMjYyMTQ0CiAgICAgICAgICBoYXJkOiAyNjIxNDQKICBwbGF1c2libGU6CiAgICBpbWFnZTogJ3BsYXVzaWJsZS9hbmFseXRpY3M6djIuMCcKICAgIHJlc3RhcnQ6IGFsd2F5cwogICAgY29tbWFuZDogJ3NoIC1jICJzbGVlcCAxMCAmJiAvZW50cnlwb2ludC5zaCBkYiBjcmVhdGVkYiAmJiAvZW50cnlwb2ludC5zaCBkYiBtaWdyYXRlICYmIC9lbnRyeXBvaW50LnNoIHJ1biInCiAgICBkZXBlbmRzX29uOgogICAgICAtIHBsYXVzaWJsZV9kYgogICAgICAtIHBsYXVzaWJsZV9ldmVudHNfZGIKICAgIHBvcnRzOgogICAgICAtICc4MDAwOjgwMDAnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRUNSRVRfS0VZX0JBU0U9JFNFQ1JFVF9LRVlfQkFTRQogICAgICAtIERBVEFCQVNFX1VSTD0kREFUQUJBU0VfVVJMCiAgICAgIC0gJ0NMSUNLSE9VU0VfREFUQUJBU0VfVVJMPWh0dHA6Ly9wbGF1c2libGVfZXZlbnRzX2RiOjgxMjMvcGxhdXNpYmxlX2V2ZW50c19kYicKICAgICAgLSBNQUlMRVJfQURBUFRFUj0kTUFJTEVSX0FEQVBURVIKICAgICAgLSBTRU5ER1JJRF9BUElfS0VZPSRTRU5ER1JJRF9BUElfS0VZCiAgICAgIC0gR09PR0xFX0NMSUVOVF9JRD0kR09PR0xFX0NMSUVOVF9JRAogICAgICAtIEdPT0dMRV9DTElFTlRfU0VDUkVUPSRHT09HTEVfQ0xJRU5UX1NFQ1JFVAogICAgICAtIERJU0FCTEVfUkVHSVNUUkFUSU9OPSRESVNBQkxFX1JFR0lTVFJBVElPTgogICAgICAtIEJBU0VfVVJMPSRCQVNFX1VSTAogICAgICAtIExPR19GQUlMRURfTE9HSU5fQVRURU1QVFM9JExPR19GQUlMRURfTE9HSU5fQVRURU1QVFMK"
|
||||
},
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
"version": "3.12.36"
|
||||
},
|
||||
"v4": {
|
||||
"version": "4.0.0-beta.62"
|
||||
"version": "4.0.0-beta.63"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user