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,21 +5,68 @@
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\Team;
use App\Models\StandalonePostgres;
use App\Models\StandalonePostgresql;
use Symfony\Component\Yaml\Yaml;
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([
"echo 'Creating required Docker networks...'",
"echo 'Creating required Docker networks...'",
"echo 'Creating required Docker networks...'",
"sleep 4",
"echo 'Creating required Docker networks...'",
"echo 'Creating required Docker networks...'",
"mkdir -p /tmp/{$container_name}",
"echo '{$docker_compose_base64}' | base64 -d > /tmp/{$container_name}/docker-compose.yml",
"docker compose -f /tmp/{$container_name}/docker-compose.yml up -d",
], $server);
return $activity;
}
}
}

View File

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

View File

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

View File

@ -42,6 +42,7 @@ public function polling()
$this->setStatus(ProcessStatus::ERROR);
}
$this->isPollingActive = false;
$this->emit('activityFinished');
}
}
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;
use App\Jobs\ApplicationContainerStatusJob;
use App\Jobs\ContainerStatusJob;
use App\Models\Application;
use App\Notifications\Application\StatusChanged;
use Livewire\Component;
@ -22,8 +22,8 @@ public function mount()
public function check_status()
{
dispatch_sync(new ApplicationContainerStatusJob(
application: $this->application,
dispatch_sync(new ContainerStatusJob(
resource: $this->application,
container_name: generate_container_name($this->application->uuid),
));
$this->application->refresh();
@ -62,4 +62,4 @@ protected function setDeploymentUuid()
$this->deploymentUuid = new Cuid2(7);
$this->parameters['deployment_uuid'] = $this->deploymentUuid;
}
}
}

View File

@ -2,7 +2,7 @@
namespace App\Http\Livewire\Project\Application;
use App\Jobs\ApplicationContainerStatusJob;
use App\Jobs\ContainerStatusJob;
use App\Models\Application;
use App\Models\ApplicationPreview;
use Illuminate\Support\Collection;
@ -24,8 +24,8 @@ public function mount()
}
public function loadStatus($pull_request_id)
{
dispatch(new ApplicationContainerStatusJob(
application: $this->application,
dispatch(new ContainerStatusJob(
resource: $this->application,
container_name: generate_container_name($this->application->uuid, $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,20 +4,47 @@
use Livewire\Component;
use App\Actions\Database\StartPostgresql;
use App\Jobs\ContainerStatusJob;
use App\Notifications\Application\StatusChanged;
class Heading extends Component
{
public $database;
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()
{
$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() {
if ($this->database->type() === 'postgresql') {
if ($this->database->type() === 'standalone-postgresql') {
$activity = resolve(StartPostgresql::class)($this->database->destination->server, $this->database);
$this->emit('newMonitorActivity', $activity->id);
}
}
}
}

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
<?php
namespace App\Http\Livewire\Project\Application\EnvironmentVariable;
namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
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
namespace App\Http\Livewire\Project\Application\EnvironmentVariable;
namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
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
namespace App\Http\Livewire\Project\Application\Storages;
namespace App\Http\Livewire\Project\Shared\Storages;
use Livewire\Component;

View File

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

View File

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

View File

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

View File

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

View File

@ -43,7 +43,9 @@ protected static function booted()
'publish_directory',
'private_key_id'
];
public function type() {
return 'application';
}
public function publishDirectory(): Attribute
{
return Attribute::make(
@ -206,4 +208,4 @@ public function deploymentType()
}
throw new \Exception('No deployment type found');
}
}
}

View File

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

View File

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

View File

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

View File

@ -48,6 +48,6 @@ public function applications()
}
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()
{
return $this->morphMany(StandalonePostgres::class, 'destination');
return $this->morphMany(StandalonePostgresql::class, 'destination');
}
public function server()
{
@ -25,4 +25,4 @@ public function attachedTo()
{
return $this->applications->count() > 0 || $this->databases->count() > 0;
}
}
}

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
{
use Queueable;
public Application $application;
public $application;
public string $application_name;
public string|null $application_url = null;
public string $project_uuid;
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_name = data_get($application, 'name');
$this->project_uuid = data_get($application, 'environment.project.uuid');
$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) {
$this->fqdn = Str::of($this->fqdn)->explode(',')->first();
}

View File

@ -11,7 +11,7 @@
*/
public function up(): void
{
Schema::create('standalone_postgres', function (Blueprint $table) {
Schema::create('standalone_postgresqls', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->string('name');
@ -24,9 +24,21 @@ public function up(): void
$table->string('postgres_host_auth_method')->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->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->morphs('destination');
@ -40,6 +52,6 @@ public function up(): 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,
LocalPersistentVolumeSeeder::class,
S3StorageSeeder::class,
StandalonePostgresSeeder::class,
StandalonePostgresqlSeeder::class,
]);
}
}
}

View File

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

View File

@ -8,17 +8,6 @@
{{-- <x-applications.advanced :application="$application" /> --}}
@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">
<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">

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-databases.navbar :database="$database" :parameters="$parameters" />
</nav>

View File

@ -1,17 +1,33 @@
<div>
<form wire:submit.prevent="submit">
<form wire:submit.prevent="submit" class="flex flex-col gap-2">
<div class="flex items-center gap-2">
<h2>General</h2>
<x-forms.button type="submit">
Save
</x-forms.button>
</div>
<x-forms.input label="Name" id="database.name" />
<x-forms.input label="Description" id="database.description" />
<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="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." />
<div class="flex gap-2">
<x-forms.input label="Name" id="database.name" />
<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="Password" id="database.postgres_password" type="password" required />
<x-forms.input label="Database" id="database.postgres_db" placeholder="If empty, use $USERNAME." />
@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"
placeholder="If empty, use default." />
</form>

View File

@ -1,12 +1,12 @@
<div>
<x-modal yesOrNo modalId="{{ $modalId }}" modalTitle="Delete Application">
<x-modal yesOrNo modalId="{{ $modalId }}" modalTitle="Delete Resource">
<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-modal>
<h3>Danger Zone</h3>
<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
back!
</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>
<x-forms.input placeholder="NODE_ENV" id="key" label="Name" required />
<x-forms.input placeholder="production" id="value" label="Value" required />
<x-forms.checkbox id="is_build_time" label="Build Variable?" />
@if (data_get($parameters, 'application_uuid'))
<x-forms.checkbox id="is_build_time" label="Build Variable?" />
@endif
<x-forms.button onclick="newVariable.close()" type="submit">
Save
</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">
<x-forms.input id="env.key" />
<x-forms.input type="password" id="env.value" />
<x-forms.checkbox disabled id="env.is_build_time" label="Build Variable?" />
@if (data_get($parameters, 'application_uuid'))
<x-forms.checkbox disabled id="env.is_build_time" label="Build Variable?" />
@endif
<div class="flex gap-2">
<x-forms.button type="submit">
Update

View File

@ -7,20 +7,20 @@
<div class="">Limit your container resources by CPU & memory.</div>
<h3 class="pt-4">Limit CPUs</h3>
<div class="flex gap-2">
<x-forms.input placeholder="1.5" label="Number of CPUs" id="application.limits_cpus" />
<x-forms.input placeholder="0-2" label="CPU sets to use" id="application.limits_cpuset" />
<x-forms.input placeholder="1024" label="CPU Weight" id="application.limits_cpu_shares" />
<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="resource.limits_cpuset" />
<x-forms.input placeholder="1024" label="CPU Weight" id="resource.limits_cpu_shares" />
</div>
<h3 class="pt-4">Limit Memory</h3>
<div class="flex gap-2">
<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"
id="application.limits_memory" />
id="resource.limits_memory" />
<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"
id="application.limits_memory_swappiness" />
id="resource.limits_memory_swappiness" />
</div>
</form>
</div>

View File

@ -1,19 +1,19 @@
<div>
<div>
<div class="flex items-center gap-2">
<h2>Storages </h2>
<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.application.storages.add />
<livewire:project.shared.storages.add />
</div>
<div class="">Persistent storage to preserve data between deployments.</div>
</div>
<div class="flex flex-col gap-2 py-4">
@forelse ($application->persistentStorages as $storage)
<livewire:project.application.storages.show wire:key="storage-{{ $storage->id }}" :storage="$storage" />
@forelse ($resource->persistentStorages as $storage)
<livewire:project.shared.storages.show wire:key="storage-{{ $storage->id }}" :storage="$storage" />
@empty
<div class="text-neutral-500">No storages found.</div>
@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" />
</div>
<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 x-cloak x-show="activeTab === 'source'">
<livewire:project.application.source :application="$application" />
</div>
<div x-cloak x-show="activeTab === 'destination'">
<livewire:project.application.destination :destination="$application->destination" />
<livewire:project.shared.destination :destination="$application->destination" />
</div>
<div x-cloak x-show="activeTab === 'storages'">
<livewire:project.application.storages.all :application="$application" />
<livewire:project.shared.storages.all :resource="$application" />
</div>
<div x-cloak x-show="activeTab === 'previews'">
<livewire:project.application.previews :application="$application" />
@ -56,10 +56,10 @@
<livewire:project.application.rollback :application="$application" />
</div>
<div x-cloak x-show="activeTab === 'resource-limits'">
<livewire:project.application.resource-limits :application="$application" />
<livewire:project.shared.resource-limits :resource="$application" />
</div>
<div x-cloak x-show="activeTab === 'danger'">
<livewire:project.application.danger :application="$application" />
<livewire:project.shared.danger :resource="$application" />
</div>
</div>
</div>

View File

@ -19,8 +19,6 @@
@click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'"
href="#">Environment
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'"
@click.prevent="activeTab = 'destination'; window.location.hash = 'destination'"
href="#">Destination
@ -38,27 +36,24 @@
</div>
<div class="w-full pl-8">
<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" />
@endif
</div>
<div x-cloak x-show="activeTab === 'environment-variables'">
{{-- <livewire:project.application.environment-variable.all :application="$application" /> --}}
</div>
<div x-cloak x-show="activeTab === 'source'">
{{-- <livewire:project.application.source :application="$application" /> --}}
<livewire:project.shared.environment-variable.all :resource="$database" />
</div>
<div x-cloak x-show="activeTab === 'destination'">
{{-- <livewire:project.application.destination :destination="$application->destination" /> --}}
<livewire:project.shared.destination :destination="$database->destination" />
</div>
<div x-cloak x-show="activeTab === 'storages'">
{{-- <livewire:project.application.storages.all :application="$application" /> --}}
<livewire:project.shared.storages.all :resource="$database" />
</div>
<div x-cloak x-show="activeTab === 'resource-limits'">
{{-- <livewire:project.application.resource-limits :application="$application" /> --}}
<livewire:project.shared.resource-limits :resource="$database" />
</div>
<div x-cloak x-show="activeTab === 'danger'">
{{-- <livewire:project.application.danger :application="$application" /> --}}
<livewire:project.shared.danger :resource="$database" />
</div>
</div>
</div>