lots of updates + refactoring

This commit is contained in:
Andras Bacsai 2023-08-07 22:14:21 +02:00
parent bfc20ef219
commit 971d7f703d
51 changed files with 532 additions and 324 deletions

View File

@ -5,19 +5,66 @@
use App\Models\Server; use App\Models\Server;
use App\Models\StandaloneDocker; use App\Models\StandaloneDocker;
use App\Models\Team; use App\Models\Team;
use App\Models\StandalonePostgres; use App\Models\StandalonePostgresql;
use Symfony\Component\Yaml\Yaml;
class StartPostgresql class StartPostgresql
{ {
public function __invoke(Server $server, StandalonePostgres $database) public function __invoke(Server $server, StandalonePostgresql $database)
{ {
$container_name = generate_container_name($database->uuid);
$destination = $database->destination;
$image = $database->image;
$docker_compose = [
'version' => '3.8',
'services' => [
$container_name => [
'image' => $image,
'container_name' => $container_name,
'environment'=> [
'POSTGRES_USER' => $database->postgres_user,
'POSTGRES_PASSWORD' => $database->postgres_password,
'POSTGRES_DB' => $database->postgres_db,
],
'restart' => 'always',
'networks' => [
$destination->network,
],
'healthcheck' => [
'test' => [
'CMD-SHELL',
'pg_isready',
'-d',
$database->postgres_db,
],
'interval' => '5s',
'timeout' => '5s',
'retries' => 10,
'start_period' => '5s'
],
'mem_limit' => $database->limits_memory,
'memswap_limit' => $database->limits_memory_swap,
'mem_swappiness' => $database->limits_memory_swappiness,
'mem_reservation' => $database->limits_memory_reservation,
'cpus' => $database->limits_cpus,
'cpuset' => $database->limits_cpuset,
'cpu_shares' => $database->limits_cpu_shares,
]
],
'networks' => [
$destination->network => [
'external' => false,
'name' => $destination->network,
'attachable' => true,
]
]
];
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$activity = remote_process([ $activity = remote_process([
"echo 'Creating required Docker networks...'", "mkdir -p /tmp/{$container_name}",
"echo 'Creating required Docker networks...'", "echo '{$docker_compose_base64}' | base64 -d > /tmp/{$container_name}/docker-compose.yml",
"echo 'Creating required Docker networks...'", "docker compose -f /tmp/{$container_name}/docker-compose.yml up -d",
"sleep 4",
"echo 'Creating required Docker networks...'",
"echo 'Creating required Docker networks...'",
], $server); ], $server);
return $activity; return $activity;

View File

@ -25,6 +25,7 @@ public function configuration()
if (!$application) { if (!$application) {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
ray($application->persistentStorages()->get());
return view('project.application.configuration', ['application' => $application]); return view('project.application.configuration', ['application' => $application]);
} }
public function deployments() public function deployments()

View File

@ -25,6 +25,7 @@ public function configuration()
if (!$database) { if (!$database) {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
ray($database->persistentStorages()->get());
return view('project.database.configuration', ['database' => $database]); return view('project.database.configuration', ['database' => $database]);
} }
} }

View File

@ -42,6 +42,7 @@ public function polling()
$this->setStatus(ProcessStatus::ERROR); $this->setStatus(ProcessStatus::ERROR);
} }
$this->isPollingActive = false; $this->isPollingActive = false;
$this->emit('activityFinished');
} }
} }
protected function setStatus($status) protected function setStatus($status)

View File

@ -1,45 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Application\EnvironmentVariable;
use App\Models\Application;
use App\Models\EnvironmentVariable;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class All extends Component
{
public Application $application;
public string|null $modalId = null;
protected $listeners = ['refreshEnvs', 'submit'];
public function mount()
{
$this->modalId = new Cuid2(7);
}
public function refreshEnvs()
{
$this->application->refresh();
}
public function submit($data)
{
try {
$found = $this->application->environment_variables()->where('key', $data['key'])->first();
if ($found) {
$this->emit('error', 'Environment variable already exists.');
return;
}
EnvironmentVariable::create([
'key' => $data['key'],
'value' => $data['value'],
'is_build_time' => $data['is_build_time'],
'is_preview' => $data['is_preview'],
'application_id' => $this->application->id,
]);
$this->application->refresh();
$this->emit('success', 'Environment variable added successfully.');
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
}
}
}

View File

@ -2,7 +2,7 @@
namespace App\Http\Livewire\Project\Application; namespace App\Http\Livewire\Project\Application;
use App\Jobs\ApplicationContainerStatusJob; use App\Jobs\ContainerStatusJob;
use App\Models\Application; use App\Models\Application;
use App\Notifications\Application\StatusChanged; use App\Notifications\Application\StatusChanged;
use Livewire\Component; use Livewire\Component;
@ -22,8 +22,8 @@ public function mount()
public function check_status() public function check_status()
{ {
dispatch_sync(new ApplicationContainerStatusJob( dispatch_sync(new ContainerStatusJob(
application: $this->application, resource: $this->application,
container_name: generate_container_name($this->application->uuid), container_name: generate_container_name($this->application->uuid),
)); ));
$this->application->refresh(); $this->application->refresh();

View File

@ -2,7 +2,7 @@
namespace App\Http\Livewire\Project\Application; namespace App\Http\Livewire\Project\Application;
use App\Jobs\ApplicationContainerStatusJob; use App\Jobs\ContainerStatusJob;
use App\Models\Application; use App\Models\Application;
use App\Models\ApplicationPreview; use App\Models\ApplicationPreview;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@ -24,8 +24,8 @@ public function mount()
} }
public function loadStatus($pull_request_id) public function loadStatus($pull_request_id)
{ {
dispatch(new ApplicationContainerStatusJob( dispatch(new ContainerStatusJob(
application: $this->application, resource: $this->application,
container_name: generate_container_name($this->application->uuid, $pull_request_id), container_name: generate_container_name($this->application->uuid, $pull_request_id),
pull_request_id: $pull_request_id pull_request_id: $pull_request_id
)); ));

View File

@ -1,60 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Application;
use App\Models\Application;
use Livewire\Component;
class ResourceLimits extends Component
{
public Application $application;
protected $rules = [
'application.limits_memory' => 'required|string',
'application.limits_memory_swap' => 'required|string',
'application.limits_memory_swappiness' => 'required|integer|min:0|max:100',
'application.limits_memory_reservation' => 'required|string',
'application.limits_cpus' => 'nullable',
'application.limits_cpuset' => 'nullable',
'application.limits_cpu_shares' => 'nullable',
];
protected $validationAttributes = [
'application.limits_memory' => 'memory',
'application.limits_memory_swap' => 'swap',
'application.limits_memory_swappiness' => 'swappiness',
'application.limits_memory_reservation' => 'reservation',
'application.limits_cpus' => 'cpus',
'application.limits_cpuset' => 'cpuset',
'application.limits_cpu_shares' => 'cpu shares',
];
public function submit()
{
try {
if (!$this->application->limits_memory) {
$this->application->limits_memory = "0";
}
if (!$this->application->limits_memory_swap) {
$this->application->limits_memory_swap = "0";
}
if (!$this->application->limits_memory_swappiness) {
$this->application->limits_memory_swappiness = "60";
}
if (!$this->application->limits_memory_reservation) {
$this->application->limits_memory_reservation = "0";
}
if (!$this->application->limits_cpus) {
$this->application->limits_cpus = "0";
}
if (!$this->application->limits_cpuset) {
$this->application->limits_cpuset = "0";
}
if (!$this->application->limits_cpu_shares) {
$this->application->limits_cpu_shares = 1024;
}
$this->validate();
$this->application->save();
$this->emit('success', 'Resource limits updated successfully.');
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
}
}
}

View File

@ -4,18 +4,45 @@
use Livewire\Component; use Livewire\Component;
use App\Actions\Database\StartPostgresql; use App\Actions\Database\StartPostgresql;
use App\Jobs\ContainerStatusJob;
use App\Notifications\Application\StatusChanged;
class Heading extends Component class Heading extends Component
{ {
public $database; public $database;
public array $parameters; public array $parameters;
protected $listeners = ['activityFinished'];
public function activityFinished() {
$this->database->update([
'started_at' => now(),
]);
$this->emit('refresh');
$this->check_status();
}
public function check_status()
{
dispatch_sync(new ContainerStatusJob(
resource: $this->database,
container_name: generate_container_name($this->database->uuid),
));
$this->database->refresh();
}
public function mount() public function mount()
{ {
$this->parameters = getRouteParameters(); $this->parameters = getRouteParameters();
} }
public function stop() {
remote_process(
["docker rm -f {$this->database->uuid}"],
$this->database->destination->server
);
$this->database->status = 'stopped';
$this->database->save();
$this->database->environment->project->team->notify(new StatusChanged($this->database));
}
public function start() { public function start() {
if ($this->database->type() === 'postgresql') { if ($this->database->type() === 'standalone-postgresql') {
$activity = resolve(StartPostgresql::class)($this->database->destination->server, $this->database); $activity = resolve(StartPostgresql::class)($this->database->destination->server, $this->database);
$this->emit('newMonitorActivity', $activity->id); $this->emit('newMonitorActivity', $activity->id);
} }

View File

@ -7,6 +7,8 @@
class General extends Component class General extends Component
{ {
public $database; public $database;
protected $listeners = ['refresh'];
protected $rules = [ protected $rules = [
'database.name' => 'required', 'database.name' => 'required',
'database.description' => 'nullable', 'database.description' => 'nullable',
@ -16,6 +18,7 @@ class General extends Component
'database.postgres_initdb_args' => 'nullable', 'database.postgres_initdb_args' => 'nullable',
'database.postgres_host_auth_method' => 'nullable', 'database.postgres_host_auth_method' => 'nullable',
'database.init_scripts' => 'nullable', 'database.init_scripts' => 'nullable',
'database.image' => 'required',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'database.name' => 'Name', 'database.name' => 'Name',
@ -26,7 +29,11 @@ class General extends Component
'database.postgres_initdb_args' => 'Postgres Initdb Args', 'database.postgres_initdb_args' => 'Postgres Initdb Args',
'database.postgres_host_auth_method' => 'Postgres Host Auth Method', 'database.postgres_host_auth_method' => 'Postgres Host Auth Method',
'database.init_scripts' => 'Init Scripts', 'database.init_scripts' => 'Init Scripts',
'database.image' => 'Image',
]; ];
public function refresh() {
$this->database->refresh();
}
public function submit() { public function submit() {
try { try {
$this->validate(); $this->validate();

View File

@ -1,14 +1,13 @@
<?php <?php
namespace App\Http\Livewire\Project\Application; namespace App\Http\Livewire\Project\Shared;
use App\Models\Application;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
class Danger extends Component class Danger extends Component
{ {
public Application $application; public $resource;
public array $parameters; public array $parameters;
public string|null $modalId = null; public string|null $modalId = null;
@ -19,10 +18,10 @@ public function mount()
} }
public function delete() public function delete()
{ {
$destination = $this->application->destination->getMorphClass()::where('id', $this->application->destination->id)->first(); $destination = $this->resource->destination->getMorphClass()::where('id', $this->resource->destination->id)->first();
instant_remote_process(["docker rm -f {$this->application->uuid}"], $destination->server); instant_remote_process(["docker rm -f {$this->resource->uuid}"], $destination->server);
$this->application->delete(); $this->resource->delete();
return redirect()->route('project.resources', [ return redirect()->route('project.resources', [
'project_uuid' => $this->parameters['project_uuid'], 'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->parameters['environment_name'] 'environment_name' => $this->parameters['environment_name']

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Application; namespace App\Http\Livewire\Project\Shared;
use Livewire\Component; use Livewire\Component;

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Application\EnvironmentVariable; namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
use Livewire\Component; use Livewire\Component;

View File

@ -0,0 +1,49 @@
<?php
namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
use App\Models\EnvironmentVariable;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class All extends Component
{
public $resource;
public string|null $modalId = null;
protected $listeners = ['refreshEnvs', 'submit'];
public function mount()
{
$this->modalId = new Cuid2(7);
}
public function refreshEnvs()
{
$this->resource->refresh();
}
public function submit($data)
{
try {
$found = $this->resource->environment_variables()->where('key', $data['key'])->first();
if ($found) {
$this->emit('error', 'Environment variable already exists.');
return;
}
$environment = new EnvironmentVariable();
$environment->key = $data['key'];
$environment->value = $data['value'];
$environment->is_build_time = $data['is_build_time'];
$environment->is_preview = $data['is_preview'];
if($this->resource->type() === 'application') {
$environment->application_id = $this->resource->id;
}
if($this->resource->type() === 'standalone-postgresql') {
$environment->standalone_postgresql_id = $this->resource->id;
}
$environment->save();
$this->resource->refresh();
$this->emit('success', 'Environment variable added successfully.');
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
}
}
}

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Application\EnvironmentVariable; namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable; use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use Livewire\Component; use Livewire\Component;

View File

@ -0,0 +1,59 @@
<?php
namespace App\Http\Livewire\Project\Shared;
use Livewire\Component;
class ResourceLimits extends Component
{
public $resource;
protected $rules = [
'resource.limits_memory' => 'required|string',
'resource.limits_memory_swap' => 'required|string',
'resource.limits_memory_swappiness' => 'required|integer|min:0|max:100',
'resource.limits_memory_reservation' => 'required|string',
'resource.limits_cpus' => 'nullable',
'resource.limits_cpuset' => 'nullable',
'resource.limits_cpu_shares' => 'nullable',
];
protected $validationAttributes = [
'resource.limits_memory' => 'memory',
'resource.limits_memory_swap' => 'swap',
'resource.limits_memory_swappiness' => 'swappiness',
'resource.limits_memory_reservation' => 'reservation',
'resource.limits_cpus' => 'cpus',
'resource.limits_cpuset' => 'cpuset',
'resource.limits_cpu_shares' => 'cpu shares',
];
public function submit()
{
try {
if (!$this->resource->limits_memory) {
$this->resource->limits_memory = "0";
}
if (!$this->resource->limits_memory_swap) {
$this->resource->limits_memory_swap = "0";
}
if (!$this->resource->limits_memory_swappiness) {
$this->resource->limits_memory_swappiness = "60";
}
if (!$this->resource->limits_memory_reservation) {
$this->resource->limits_memory_reservation = "0";
}
if (!$this->resource->limits_cpus) {
$this->resource->limits_cpus = "0";
}
if (!$this->resource->limits_cpuset) {
$this->resource->limits_cpuset = "0";
}
if (!$this->resource->limits_cpu_shares) {
$this->resource->limits_cpu_shares = 1024;
}
$this->validate();
$this->resource->save();
$this->emit('success', 'Resource limits updated successfully.');
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
}
}
}

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Application\Storages; namespace App\Http\Livewire\Project\Shared\Storages;
use Livewire\Component; use Livewire\Component;

View File

@ -1,18 +1,17 @@
<?php <?php
namespace App\Http\Livewire\Project\Application\Storages; namespace App\Http\Livewire\Project\Shared\Storages;
use App\Models\Application;
use App\Models\LocalPersistentVolume; use App\Models\LocalPersistentVolume;
use Livewire\Component; use Livewire\Component;
class All extends Component class All extends Component
{ {
public Application $application; public $resource;
protected $listeners = ['refreshStorages', 'submit']; protected $listeners = ['refreshStorages', 'submit'];
public function refreshStorages() public function refreshStorages()
{ {
$this->application->refresh(); $this->resource->refresh();
} }
public function submit($data) public function submit($data)
{ {
@ -21,10 +20,10 @@ public function submit($data)
'name' => $data['name'], 'name' => $data['name'],
'mount_path' => $data['mount_path'], 'mount_path' => $data['mount_path'],
'host_path' => $data['host_path'], 'host_path' => $data['host_path'],
'resource_id' => $this->application->id, 'resource_id' => $this->resource->id,
'resource_type' => Application::class, 'resource_type' => $this->resource->getMorphClass(),
]); ]);
$this->application->refresh(); $this->resource->refresh();
$this->emit('success', 'Storage added successfully'); $this->emit('success', 'Storage added successfully');
$this->emit('clearAddStorage'); $this->emit('clearAddStorage');
} catch (\Exception $e) { } catch (\Exception $e) {

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Application\Storages; namespace App\Http\Livewire\Project\Shared\Storages;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;

View File

@ -13,17 +13,17 @@
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
class ApplicationContainerStatusJob implements ShouldQueue, ShouldBeUnique class ContainerStatusJob implements ShouldQueue, ShouldBeUnique
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public string $container_name; public string $container_name;
public string|null $pull_request_id; public string|null $pull_request_id;
public Application $application; public $resource;
public function __construct($application, string $container_name, string|null $pull_request_id = null) public function __construct($resource, string $container_name, string|null $pull_request_id = null)
{ {
$this->application = $application; $this->resource = $resource;
$this->container_name = $container_name; $this->container_name = $container_name;
$this->pull_request_id = $pull_request_id; $this->pull_request_id = $pull_request_id;
} }
@ -34,18 +34,18 @@ public function uniqueId(): string
public function handle(): void public function handle(): void
{ {
try { try {
$status = get_container_status(server: $this->application->destination->server, container_id: $this->container_name, throwError: false); $status = get_container_status(server: $this->resource->destination->server, container_id: $this->container_name, throwError: false);
if ($this->application->status === 'running' && $status === 'stopped') { if ($this->resource->status === 'running' && $status === 'stopped') {
$this->application->environment->project->team->notify(new StatusChanged($this->application)); $this->resource->environment->project->team->notify(new StatusChanged($this->resource));
} }
if ($this->pull_request_id) { if ($this->pull_request_id) {
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id); $preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->resource->id, $this->pull_request_id);
$preview->status = $status; $preview->status = $status;
$preview->save(); $preview->save();
} else { } else {
$this->application->status = $status; $this->resource->status = $status;
$this->application->save(); $this->resource->save();
} }
} catch (\Exception $e) { } catch (\Exception $e) {
ray($e->getMessage()); ray($e->getMessage());

View File

@ -23,8 +23,8 @@ public function handle(): void
{ {
try { try {
foreach ($this->applications as $application) { foreach ($this->applications as $application) {
dispatch(new ApplicationContainerStatusJob( dispatch(new ContainerStatusJob(
application: $application, resource: $application,
container_name: generate_container_name($application->uuid), container_name: generate_container_name($application->uuid),
)); ));
} }

View File

@ -43,7 +43,9 @@ protected static function booted()
'publish_directory', 'publish_directory',
'private_key_id' 'private_key_id'
]; ];
public function type() {
return 'application';
}
public function publishDirectory(): Attribute public function publishDirectory(): Attribute
{ {
return Attribute::make( return Attribute::make(

View File

@ -33,7 +33,7 @@ public function applications()
} }
public function postgresqls() public function postgresqls()
{ {
return $this->hasMany(StandalonePostgres::class); return $this->hasMany(StandalonePostgresql::class);
} }
public function services() public function services()
{ {

View File

@ -9,10 +9,16 @@
class EnvironmentVariable extends Model class EnvironmentVariable extends Model
{ {
protected $guarded = [];
protected $casts = [
"key" => 'string',
'value' => 'encrypted',
'is_build_time' => 'boolean',
];
protected static function booted() protected static function booted()
{ {
static::created(function ($environment_variable) { static::created(function ($environment_variable) {
if (!$environment_variable->is_preview) { if ($environment_variable->application_id && !$environment_variable->is_preview) {
ModelsEnvironmentVariable::create([ ModelsEnvironmentVariable::create([
'key' => $environment_variable->key, 'key' => $environment_variable->key,
'value' => $environment_variable->value, 'value' => $environment_variable->value,
@ -23,12 +29,7 @@ protected static function booted()
} }
}); });
} }
protected $fillable = ['key', 'value', 'is_build_time', 'application_id', 'is_preview'];
protected $casts = [
"key" => 'string',
'value' => 'encrypted',
'is_build_time' => 'boolean',
];
private function get_environment_variables(string $environment_variable): string|null private function get_environment_variables(string $environment_variable): string|null
{ {
// $team_id = session('currentTeam')->id; // $team_id = session('currentTeam')->id;

View File

@ -8,18 +8,15 @@
class LocalPersistentVolume extends Model class LocalPersistentVolume extends Model
{ {
protected $fillable = [ protected $guarded = [];
'name',
'mount_path',
'host_path',
'container_id',
'resource_id',
'resource_type',
];
public function application() public function application()
{ {
return $this->morphTo(); return $this->morphTo();
} }
public function standalone_postgresql()
{
return $this->morphTo();
}
protected function name(): Attribute protected function name(): Attribute
{ {
return Attribute::make( return Attribute::make(

View File

@ -48,6 +48,6 @@ public function applications()
} }
public function postgresqls() public function postgresqls()
{ {
return $this->hasManyThrough(StandalonePostgres::class, Environment::class); return $this->hasManyThrough(StandalonePostgresql::class, Environment::class);
} }
} }

View File

@ -15,7 +15,7 @@ public function applications()
} }
public function postgresqls() public function postgresqls()
{ {
return $this->morphMany(StandalonePostgres::class, 'destination'); return $this->morphMany(StandalonePostgresql::class, 'destination');
} }
public function server() public function server()
{ {

View File

@ -1,25 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class StandalonePostgres extends BaseModel
{
use HasFactory;
protected $guarded = [];
protected $casts = [
'postgres_password' => 'encrypted',
];
public function type() {
return 'postgresql';
}
public function environment()
{
return $this->belongsTo(Environment::class);
}
public function destination()
{
return $this->morphTo();
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use App\Models\EnvironmentVariable;
use App\Models\LocalPersistentVolume;
class StandalonePostgresql extends BaseModel
{
use HasFactory;
protected static function booted()
{
static::created(function ($database) {
LocalPersistentVolume::create([
'name' => 'postgres-data-' . $database->uuid,
'mount_path' => '/var/lib/postgresql/data',
'host_path' => null,
'resource_id' => $database->id,
'resource_type' => $database->getMorphClass(),
'is_readonly' => true
]);
});
}
protected $guarded = [];
protected $casts = [
'postgres_password' => 'encrypted',
];
public function type() {
return 'standalone-postgresql';
}
public function environment()
{
return $this->belongsTo(Environment::class);
}
public function destination()
{
return $this->morphTo();
}
public function environment_variables(): HasMany
{
return $this->hasMany(EnvironmentVariable::class);
}
public function persistentStorages()
{
return $this->morphMany(LocalPersistentVolume::class, 'resource');
}
}

View File

@ -15,21 +15,21 @@
class StatusChanged extends Notification implements ShouldQueue class StatusChanged extends Notification implements ShouldQueue
{ {
use Queueable; use Queueable;
public Application $application; public $application;
public string $application_name; public string $application_name;
public string|null $application_url = null; public string|null $application_url = null;
public string $project_uuid; public string $project_uuid;
public string $environment_name; public string $environment_name;
public string $fqdn; public string|null $fqdn;
public function __construct(Application $application) public function __construct($application)
{ {
$this->application = $application; $this->application = $application;
$this->application_name = data_get($application, 'name'); $this->application_name = data_get($application, 'name');
$this->project_uuid = data_get($application, 'environment.project.uuid'); $this->project_uuid = data_get($application, 'environment.project.uuid');
$this->environment_name = data_get($application, 'environment.name'); $this->environment_name = data_get($application, 'environment.name');
$this->fqdn = data_get($application, 'fqdn'); $this->fqdn = data_get($application, 'fqdn', null);
if (Str::of($this->fqdn)->explode(',')->count() > 1) { if (Str::of($this->fqdn)->explode(',')->count() > 1) {
$this->fqdn = Str::of($this->fqdn)->explode(',')->first(); $this->fqdn = Str::of($this->fqdn)->explode(',')->first();
} }

View File

@ -11,7 +11,7 @@
*/ */
public function up(): void public function up(): void
{ {
Schema::create('standalone_postgres', function (Blueprint $table) { Schema::create('standalone_postgresqls', function (Blueprint $table) {
$table->id(); $table->id();
$table->string('uuid')->unique(); $table->string('uuid')->unique();
$table->string('name'); $table->string('name');
@ -24,9 +24,21 @@ public function up(): void
$table->string('postgres_host_auth_method')->nullable(); $table->string('postgres_host_auth_method')->nullable();
$table->json('init_scripts')->nullable(); $table->json('init_scripts')->nullable();
$table->string('status')->default('exited');
$table->string('image')->default('postgres:15-alpine');
$table->boolean('is_public')->default(false); $table->boolean('is_public')->default(false);
$table->integer('public_port')->nullable(); $table->integer('public_port')->nullable();
$table->string('limits_memory')->default("0");
$table->string('limits_memory_swap')->default("0");
$table->integer('limits_memory_swappiness')->default(60);
$table->string('limits_memory_reservation')->default("0");
$table->string('limits_cpus')->default("0");
$table->string('limits_cpuset')->nullable()->default("0");
$table->integer('limits_cpu_shares')->default(1024);
$table->timestamp('started_at')->nullable(); $table->timestamp('started_at')->nullable();
$table->morphs('destination'); $table->morphs('destination');
@ -40,6 +52,6 @@ public function up(): void
*/ */
public function down(): void public function down(): void
{ {
Schema::dropIfExists('standalone_postgres'); Schema::dropIfExists('standalone_postgresqls');
} }
}; };

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('environment_variables', function (Blueprint $table) {
$table->dropColumn('service_id');
$table->dropColumn('database_id');
$table->foreignId('standalone_postgresql_id')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('environment_variables', function (Blueprint $table) {
$table->foreignId('service_id')->nullable();
$table->foreignId('database_id')->nullable();
$table->dropColumn('standalone_postgresql_id');
});
}
};

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('local_persistent_volumes', function (Blueprint $table) {
$table->boolean('is_readonly')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('local_persistent_volumes', function (Blueprint $table) {
$table->dropColumn('is_readonly');
});
}
};

View File

@ -32,7 +32,7 @@ public function run(): void
EnvironmentVariableSeeder::class, EnvironmentVariableSeeder::class,
LocalPersistentVolumeSeeder::class, LocalPersistentVolumeSeeder::class,
S3StorageSeeder::class, S3StorageSeeder::class,
StandalonePostgresSeeder::class, StandalonePostgresqlSeeder::class,
]); ]);
} }
} }

View File

@ -4,17 +4,17 @@
use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use App\Models\StandalonePostgres; use App\Models\StandalonePostgresql;
use App\Models\StandaloneDocker; use App\Models\StandaloneDocker;
class StandalonePostgresSeeder extends Seeder class StandalonePostgresqlSeeder extends Seeder
{ {
/** /**
* Run the database seeds. * Run the database seeds.
*/ */
public function run(): void public function run(): void
{ {
StandalonePostgres::create([ StandalonePostgresql::create([
'name' => 'Local PostgreSQL', 'name' => 'Local PostgreSQL',
'description' => 'Local PostgreSQL for testing', 'description' => 'Local PostgreSQL for testing',
'postgres_password' => 'postgres', 'postgres_password' => 'postgres',

View File

@ -8,17 +8,6 @@
{{-- <x-applications.advanced :application="$application" /> --}} {{-- <x-applications.advanced :application="$application" /> --}}
@if ($database->status === 'running') @if ($database->status === 'running')
<button wire:click='start' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-warning" viewBox="0 0 24 24" stroke-width="2"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M10.09 4.01l.496 -.495a2 2 0 0 1 2.828 0l7.071 7.07a2 2 0 0 1 0 2.83l-7.07 7.07a2 2 0 0 1 -2.83 0l-7.07 -7.07a2 2 0 0 1 0 -2.83l3.535 -3.535h-3.988">
</path>
<path d="M7.05 11.038v-3.988"></path>
</svg>
Restart
</button>
<button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400"> <button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" stroke-width="2" <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" stroke-width="2"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">

View File

@ -1,26 +0,0 @@
<div class="flex flex-col gap-2">
<div>
<div class="flex items-center gap-2">
<h2>Environment Variables</h2>
<x-forms.button class="btn" onclick="newVariable.showModal()">+ Add</x-forms.button>
<livewire:project.application.environment-variable.add />
</div>
<div class="">Environment (secrets) variables for normal deployments.</div>
</div>
@forelse ($application->environment_variables as $env)
<livewire:project.application.environment-variable.show wire:key="environment-{{ $env->id }}"
:env="$env" />
@empty
<div class="text-neutral-500">No environment variables found.</div>
@endforelse
@if ($application->environment_variables_preview->count() > 0)
<div>
<h3>Preview Deployments</h3>
<div class="">Environment (secrets) variables for Preview Deployments.</div>
</div>
@foreach ($application->environment_variables_preview as $env)
<livewire:project.application.environment-variable.show wire:key="environment-{{ $env->id }}"
:env="$env" />
@endforeach
@endif
</div>

View File

@ -1,21 +0,0 @@
<div>
<x-modal yesOrNo modalId="{{ $modalId }}" modalTitle="Delete Storage">
<x-slot:modalBody>
<p>This storage will be deleted <span class="font-bold text-warning">({{ $storage->name }})</span>. It is not
reversible. <br>Please think again.</p>
</x-slot:modalBody>
</x-modal>
<form wire:submit.prevent='submit' class="flex flex-col gap-2 xl:items-end xl:flex-row">
<x-forms.input id="storage.name" label="Name" required />
<x-forms.input id="storage.host_path" label="Source Path" />
<x-forms.input id="storage.mount_path" label="Destination Path" required />
<div class="flex gap-2">
<x-forms.button type="submit">
Update
</x-forms.button>
<x-forms.button isError isModal modalId="{{ $modalId }}">
Delete
</x-forms.button>
</div>
</form>
</div>

View File

@ -1,4 +1,4 @@
<nav> <nav x-init="$wire.check_status" wire:poll.10000ms="check_status">
<x-resources.breadcrumbs :resource="$database" :parameters="$parameters" /> <x-resources.breadcrumbs :resource="$database" :parameters="$parameters" />
<x-databases.navbar :database="$database" :parameters="$parameters" /> <x-databases.navbar :database="$database" :parameters="$parameters" />
</nav> </nav>

View File

@ -1,17 +1,33 @@
<div> <div>
<form wire:submit.prevent="submit"> <form wire:submit.prevent="submit" class="flex flex-col gap-2">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h2>General</h2> <h2>General</h2>
<x-forms.button type="submit"> <x-forms.button type="submit">
Save Save
</x-forms.button> </x-forms.button>
</div> </div>
<div class="flex gap-2">
<x-forms.input label="Name" id="database.name" /> <x-forms.input label="Name" id="database.name" />
<x-forms.input label="Description" id="database.description" /> <x-forms.input label="Description" id="database.description" />
<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, use postgres."
readonly />
<x-forms.input label="Password" id="database.postgres_password" type="password" required readonly />
<x-forms.input label="Database" id="database.postgres_db" placeholder="If empty, use $USERNAME."
readonly />
@else
<x-forms.input label="Username" id="database.postgres_username" placeholder="If empty, use postgres." /> <x-forms.input label="Username" id="database.postgres_username" placeholder="If empty, use postgres." />
<x-forms.input label="Password" id="database.postgres_password" type="password" /> <x-forms.input label="Password" id="database.postgres_password" type="password" required />
<x-forms.input label="Database" id="database.postgres_db" placeholder="If empty, use $USERNAME." /> <x-forms.input label="Database" id="database.postgres_db" placeholder="If empty, use $USERNAME." />
<x-forms.input label="Init Args" id="database.postgres_initdb_args" placeholder="If empty, use default." /> @endif
</div>
<x-forms.input label="Initial Arguments" id="database.postgres_initdb_args"
placeholder="If empty, use default." />
<x-forms.input label="Host Auth Method" id="database.postgres_host_auth_method" <x-forms.input label="Host Auth Method" id="database.postgres_host_auth_method"
placeholder="If empty, use default." /> placeholder="If empty, use default." />
</form> </form>

View File

@ -1,12 +1,12 @@
<div> <div>
<x-modal yesOrNo modalId="{{ $modalId }}" modalTitle="Delete Application"> <x-modal yesOrNo modalId="{{ $modalId }}" modalTitle="Delete Resource">
<x-slot:modalBody> <x-slot:modalBody>
<p>This application will be deleted. It is not reversible. <br>Please think again.</p> <p>This resource will be deleted. It is not reversible. <br>Please think again.</p>
</x-slot:modalBody> </x-slot:modalBody>
</x-modal> </x-modal>
<h3>Danger Zone</h3> <h3>Danger Zone</h3>
<div class="">Woah. I hope you know what are you doing.</div> <div class="">Woah. I hope you know what are you doing.</div>
<h4 class="pt-4">Delete Application</h4> <h4 class="pt-4">Delete Resource</h4>
<div class="pb-4">This will stop your containers, delete all related data, etc. Beware! There is no coming <div class="pb-4">This will stop your containers, delete all related data, etc. Beware! There is no coming
back! back!
</div> </div>

View File

@ -0,0 +1,8 @@
<div>
<h2>Destination</h2>
<div class="">The destination server / network where your application will be deployed to.</div>
<div class="py-4 ">
<p>Server: {{ data_get($destination, 'server.name') }}</p>
<p>Destination Network: {{ $destination->network }}</p>
</div>
</div>

View File

@ -3,7 +3,9 @@
<h3 class="text-lg font-bold">Add Environment Variable</h3> <h3 class="text-lg font-bold">Add Environment Variable</h3>
<x-forms.input placeholder="NODE_ENV" id="key" label="Name" required /> <x-forms.input placeholder="NODE_ENV" id="key" label="Name" required />
<x-forms.input placeholder="production" id="value" label="Value" required /> <x-forms.input placeholder="production" id="value" label="Value" required />
@if (data_get($parameters, 'application_uuid'))
<x-forms.checkbox id="is_build_time" label="Build Variable?" /> <x-forms.checkbox id="is_build_time" label="Build Variable?" />
@endif
<x-forms.button onclick="newVariable.close()" type="submit"> <x-forms.button onclick="newVariable.close()" type="submit">
Save Save
</x-forms.button> </x-forms.button>

View File

@ -0,0 +1,26 @@
<div class="flex flex-col gap-2">
<div>
<div class="flex items-center gap-2">
<h2>Environment Variables</h2>
<x-forms.button class="btn" onclick="newVariable.showModal()">+ Add</x-forms.button>
<livewire:project.shared.environment-variable.add />
</div>
<div>Environment (secrets) variables for this resource.</div>
</div>
@forelse ($resource->environment_variables as $env)
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
:env="$env" />
@empty
<div class="text-neutral-500">No environment variables found.</div>
@endforelse
@if ($resource->type() === 'application' && $resource->environment_variables_preview->count() > 0)
<div>
<h3>Preview Deployments</h3>
<div>Environment (secrets) variables for Preview Deployments.</div>
</div>
@foreach ($resource->environment_variables_preview as $env)
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
:env="$env" />
@endforeach
@endif
</div>

View File

@ -8,7 +8,9 @@ class="font-bold text-warning">({{ $env->key }})</span>?</p>
<form wire:submit.prevent='submit' class="flex flex-col items-center gap-2 xl:flex-row"> <form wire:submit.prevent='submit' class="flex flex-col items-center gap-2 xl:flex-row">
<x-forms.input id="env.key" /> <x-forms.input id="env.key" />
<x-forms.input type="password" id="env.value" /> <x-forms.input type="password" id="env.value" />
@if (data_get($parameters, 'application_uuid'))
<x-forms.checkbox disabled id="env.is_build_time" label="Build Variable?" /> <x-forms.checkbox disabled id="env.is_build_time" label="Build Variable?" />
@endif
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.button type="submit"> <x-forms.button type="submit">
Update Update

View File

@ -7,20 +7,20 @@
<div class="">Limit your container resources by CPU & memory.</div> <div class="">Limit your container resources by CPU & memory.</div>
<h3 class="pt-4">Limit CPUs</h3> <h3 class="pt-4">Limit CPUs</h3>
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.input placeholder="1.5" label="Number of CPUs" id="application.limits_cpus" /> <x-forms.input placeholder="1.5" label="Number of CPUs" id="resource.limits_cpus" />
<x-forms.input placeholder="0-2" label="CPU sets to use" id="application.limits_cpuset" /> <x-forms.input placeholder="0-2" label="CPU sets to use" id="resource.limits_cpuset" />
<x-forms.input placeholder="1024" label="CPU Weight" id="application.limits_cpu_shares" /> <x-forms.input placeholder="1024" label="CPU Weight" id="resource.limits_cpu_shares" />
</div> </div>
<h3 class="pt-4">Limit Memory</h3> <h3 class="pt-4">Limit Memory</h3>
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.input placeholder="69b or 420k or 1337m or 1g" label="Soft Memory Limit" <x-forms.input placeholder="69b or 420k or 1337m or 1g" label="Soft Memory Limit"
id="application.limits_memory_reservation" /> id="resource.limits_memory_reservation" />
<x-forms.input placeholder="69b or 420k or 1337m or 1g" label="Maximum Memory Limit" <x-forms.input placeholder="69b or 420k or 1337m or 1g" label="Maximum Memory Limit"
id="application.limits_memory" /> id="resource.limits_memory" />
<x-forms.input placeholder="69b or 420k or 1337m or 1g" label="Maximum Swap Limit" <x-forms.input placeholder="69b or 420k or 1337m or 1g" label="Maximum Swap Limit"
id="application.limits_memory_swap" /> id="resource.limits_memory_swap" />
<x-forms.input placeholder="0-100" type="number" min="0" max="100" label="Swappiness" <x-forms.input placeholder="0-100" type="number" min="0" max="100" label="Swappiness"
id="application.limits_memory_swappiness" /> id="resource.limits_memory_swappiness" />
</div> </div>
</form> </form>
</div> </div>

View File

@ -7,13 +7,13 @@
volume volume
name, example: <span class='text-helper'>-pr-1</span>" /> name, example: <span class='text-helper'>-pr-1</span>" />
<x-forms.button class="btn" onclick="newStorage.showModal()">+ Add</x-forms.button> <x-forms.button class="btn" onclick="newStorage.showModal()">+ Add</x-forms.button>
<livewire:project.application.storages.add /> <livewire:project.shared.storages.add />
</div> </div>
<div class="">Persistent storage to preserve data between deployments.</div> <div class="">Persistent storage to preserve data between deployments.</div>
</div> </div>
<div class="flex flex-col gap-2 py-4"> <div class="flex flex-col gap-2 py-4">
@forelse ($application->persistentStorages as $storage) @forelse ($resource->persistentStorages as $storage)
<livewire:project.application.storages.show wire:key="storage-{{ $storage->id }}" :storage="$storage" /> <livewire:project.shared.storages.show wire:key="storage-{{ $storage->id }}" :storage="$storage" />
@empty @empty
<div class="text-neutral-500">No storages found.</div> <div class="text-neutral-500">No storages found.</div>
@endforelse @endforelse

View File

@ -0,0 +1,35 @@
<div>
<x-modal yesOrNo modalId="{{ $modalId }}" modalTitle="Delete Storage">
<x-slot:modalBody>
<p>This storage will be deleted <span class="font-bold text-warning">({{ $storage->name }})</span>. It is not
reversible. <br>Please think again.</p>
</x-slot:modalBody>
</x-modal>
<form wire:submit.prevent='submit' class="flex flex-col gap-2 xl:items-end xl:flex-row">
@if ($storage->is_readonly)
<x-forms.input id="storage.name" label="Name" required readonly />
<x-forms.input id="storage.host_path" label="Source Path" readonly />
<x-forms.input id="storage.mount_path" label="Destination Path" required readonly />
<div class="flex gap-2">
<x-forms.button type="submit" disabled>
Update
</x-forms.button>
<x-forms.button isError isModal modalId="{{ $modalId }}" disabled>
Delete
</x-forms.button>
</div>
@else
<x-forms.input id="storage.name" label="Name" required />
<x-forms.input id="storage.host_path" label="Source Path" />
<x-forms.input id="storage.mount_path" label="Destination Path" required />
<div class="flex gap-2">
<x-forms.button type="submit">
Update
</x-forms.button>
<x-forms.button isError isModal modalId="{{ $modalId }}">
Delete
</x-forms.button>
</div>
@endif
</form>
</div>

View File

@ -38,16 +38,16 @@
<livewire:project.application.general :application="$application" /> <livewire:project.application.general :application="$application" />
</div> </div>
<div x-cloak x-show="activeTab === 'environment-variables'"> <div x-cloak x-show="activeTab === 'environment-variables'">
<livewire:project.application.environment-variable.all :application="$application" /> <livewire:project.shared.environment-variable.all :resource="$application" />
</div> </div>
<div x-cloak x-show="activeTab === 'source'"> <div x-cloak x-show="activeTab === 'source'">
<livewire:project.application.source :application="$application" /> <livewire:project.application.source :application="$application" />
</div> </div>
<div x-cloak x-show="activeTab === 'destination'"> <div x-cloak x-show="activeTab === 'destination'">
<livewire:project.application.destination :destination="$application->destination" /> <livewire:project.shared.destination :destination="$application->destination" />
</div> </div>
<div x-cloak x-show="activeTab === 'storages'"> <div x-cloak x-show="activeTab === 'storages'">
<livewire:project.application.storages.all :application="$application" /> <livewire:project.shared.storages.all :resource="$application" />
</div> </div>
<div x-cloak x-show="activeTab === 'previews'"> <div x-cloak x-show="activeTab === 'previews'">
<livewire:project.application.previews :application="$application" /> <livewire:project.application.previews :application="$application" />
@ -56,10 +56,10 @@
<livewire:project.application.rollback :application="$application" /> <livewire:project.application.rollback :application="$application" />
</div> </div>
<div x-cloak x-show="activeTab === 'resource-limits'"> <div x-cloak x-show="activeTab === 'resource-limits'">
<livewire:project.application.resource-limits :application="$application" /> <livewire:project.shared.resource-limits :resource="$application" />
</div> </div>
<div x-cloak x-show="activeTab === 'danger'"> <div x-cloak x-show="activeTab === 'danger'">
<livewire:project.application.danger :application="$application" /> <livewire:project.shared.danger :resource="$application" />
</div> </div>
</div> </div>
</div> </div>

View File

@ -19,8 +19,6 @@
@click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'" @click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'"
href="#">Environment href="#">Environment
Variables</a> Variables</a>
<a :class="activeTab === 'source' && 'text-white'"
@click.prevent="activeTab = 'source'; window.location.hash = 'source'" href="#">Source</a>
<a :class="activeTab === 'destination' && 'text-white'" <a :class="activeTab === 'destination' && 'text-white'"
@click.prevent="activeTab = 'destination'; window.location.hash = 'destination'" @click.prevent="activeTab = 'destination'; window.location.hash = 'destination'"
href="#">Destination href="#">Destination
@ -38,27 +36,24 @@
</div> </div>
<div class="w-full pl-8"> <div class="w-full pl-8">
<div x-cloak x-show="activeTab === 'general'" class="h-full"> <div x-cloak x-show="activeTab === 'general'" class="h-full">
@if ($database->type() === 'postgresql') @if ($database->type() === 'standalone-postgresql')
<livewire:project.database.postgresql.general :database="$database" /> <livewire:project.database.postgresql.general :database="$database" />
@endif @endif
</div> </div>
<div x-cloak x-show="activeTab === 'environment-variables'"> <div x-cloak x-show="activeTab === 'environment-variables'">
{{-- <livewire:project.application.environment-variable.all :application="$application" /> --}} <livewire:project.shared.environment-variable.all :resource="$database" />
</div>
<div x-cloak x-show="activeTab === 'source'">
{{-- <livewire:project.application.source :application="$application" /> --}}
</div> </div>
<div x-cloak x-show="activeTab === 'destination'"> <div x-cloak x-show="activeTab === 'destination'">
{{-- <livewire:project.application.destination :destination="$application->destination" /> --}} <livewire:project.shared.destination :destination="$database->destination" />
</div> </div>
<div x-cloak x-show="activeTab === 'storages'"> <div x-cloak x-show="activeTab === 'storages'">
{{-- <livewire:project.application.storages.all :application="$application" /> --}} <livewire:project.shared.storages.all :resource="$database" />
</div> </div>
<div x-cloak x-show="activeTab === 'resource-limits'"> <div x-cloak x-show="activeTab === 'resource-limits'">
{{-- <livewire:project.application.resource-limits :application="$application" /> --}} <livewire:project.shared.resource-limits :resource="$database" />
</div> </div>
<div x-cloak x-show="activeTab === 'danger'"> <div x-cloak x-show="activeTab === 'danger'">
{{-- <livewire:project.application.danger :application="$application" /> --}} <livewire:project.shared.danger :resource="$database" />
</div> </div>
</div> </div>
</div> </div>