fix modal, add env variables, etc

This commit is contained in:
Andras Bacsai 2023-05-04 22:29:14 +02:00
parent d5b332fc59
commit c4a4801414
27 changed files with 379 additions and 102 deletions

View File

@ -1,48 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Application;
use App\Models\Application;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Route;
use Livewire\Component;
class EnvironmentVariable extends Component
{
public $parameters;
public $env;
public string|null $keyName = null;
public string|null $value = null;
public bool $isBuildOnly = false;
public bool $isNewEnv = false;
public function mount()
{
$this->parameters = Route::current()->parameters();
if (data_get($this->env, 'value') !== null) {
$this->value = $this->env['value'];
$this->isBuildOnly = $this->env['isBuildOnly'];
} else {
$this->isNewEnv = true;
}
}
public function updateEnv()
{
$application = Application::where('uuid', $this->parameters['application_uuid'])->first();
$application->environment_variables->set("{$this->keyName}.value", $this->value);
$application->environment_variables->set("{$this->keyName}.isBuildOnly", $this->isBuildOnly);
$application->save();
}
public function submit()
{
$this->updateEnv();
$this->emit('reloadWindow');
}
public function delete()
{
$application = Application::where('uuid', $this->parameters['application_uuid'])->first();
$application->environment_variables->forget($this->keyName);
$application->save();
$this->emit('reloadWindow');
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Http\Livewire\Project\Application\EnvironmentVariable;
use App\Models\Application;
use App\Models\EnvironmentVariable;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Route;
use Livewire\Component;
class Add extends Component
{
public $parameters;
public string $key;
public string $value;
public bool $is_build_time = false;
public function mount()
{
$this->parameters = Route::current()->parameters();
}
public function submit()
{
try {
$application_id = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail()->id;
EnvironmentVariable::create([
'key' => $this->key,
'value' => $this->value,
'is_build_time' => $this->is_build_time,
'application_id' => $application_id,
]);
$this->emit('reloadWindow');
} catch (mixed $e) {
dd('asdf');
if ($e instanceof QueryException) {
dd($e->errorInfo);
$this->emit('error', $e->errorInfo[2]);
} else {
$this->emit('error', $e);
}
}
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Http\Livewire\Project\Application\EnvironmentVariable;
use App\Models\Application;
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Route;
use Livewire\Component;
class Show extends Component
{
public $parameters;
public ModelsEnvironmentVariable $env;
protected $rules = [
'env.key' => 'required|string',
'env.value' => 'required|string',
'env.is_build_time' => 'required|boolean',
];
public function mount()
{
$this->parameters = Route::current()->parameters();
}
public function submit()
{
$this->validate();
$this->env->save();
}
public function delete()
{
$this->env->delete();
$this->emit('reloadWindow');
}
}

View File

@ -4,8 +4,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Spatie\Activitylog\Models\Activity;
use Illuminate\Database\Eloquent\Builder;
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Application extends BaseModel
{
@ -38,14 +37,6 @@ class Application extends BaseModel
'ports_exposes',
'publish_directory',
];
public $casts = [
'environment_variables' => SchemalessAttributes::class,
];
public function scopeWithEnvironmentVariables(): Builder
{
return $this->environment_variables->modelScope();
}
public function publishDirectory(): Attribute
{
return Attribute::make(
@ -83,6 +74,10 @@ class Application extends BaseModel
: explode(',', $this->ports_exposes)
);
}
public function environment_variables(): HasMany
{
return $this->hasMany(EnvironmentVariable::class);
}
public function environment()
{
return $this->belongsTo(Environment::class);

View File

@ -0,0 +1,46 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
class EnvironmentVariable extends Model
{
protected $fillable = ['key', 'value', 'is_build_time', 'application_id'];
protected $casts = [
'value' => 'encrypted',
'is_build_time' => 'boolean',
];
private function get_environment_variables(string $environment_variable): string|null
{
$team_id = session('currentTeam')->id;
if (str_contains(trim($environment_variable), '{{') && str_contains(trim($environment_variable), '}}')) {
$environment_variable = preg_replace('/\s+/', '', $environment_variable);
$environment_variable = str_replace('{{', '', $environment_variable);
$environment_variable = str_replace('}}', '', $environment_variable);
if (str_starts_with($environment_variable, 'global.')) {
$environment_variable = str_replace('global.', '', $environment_variable);
// $environment_variable = GlobalEnvironmentVariable::where('name', $environment_variable)->where('team_id', $team_id)->first()?->value;
return $environment_variable;
}
}
return decrypt($environment_variable);
}
private function set_environment_variables(string $environment_variable): string|null
{
if (!str_contains(trim($environment_variable), '{{') && !str_contains(trim($environment_variable), '}}')) {
return encrypt($environment_variable);
}
return $environment_variable;
}
protected function value(): Attribute
{
return Attribute::make(
get: fn (string $value) => $this->get_environment_variables($value),
set: fn (string $value) => $this->set_environment_variables($value),
);
}
}

View File

@ -39,8 +39,6 @@ return new class extends Migration
$table->string('base_directory')->default('/');
$table->string('publish_directory')->nullable();
$table->schemalessAttributes('environment_variables');
$table->string('health_check_path')->default('/');
$table->string('health_check_port')->nullable();
$table->string('health_check_host')->default('localhost');

View File

@ -0,0 +1,39 @@
<?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::create('environment_variables', function (Blueprint $table) {
$table->id();
$table->string('key');
$table->string('value')->nullable();
$table->boolean('is_build_time')->default(false);
$table->foreignId('application_id')->nullable();
$table->foreignId('service_id')->nullable();
$table->foreignId('database_id')->nullable();
$table->unique(['key', 'application_id', 'is_build_time']);
$table->unique(['key', 'service_id', 'is_build_time']);
$table->unique(['key', 'database_id', 'is_build_time']);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('environment_variables');
}
};

View File

@ -35,16 +35,6 @@ class ApplicationSeeder extends Seeder
'destination_type' => StandaloneDocker::class,
'source_id' => $github_public_source->id,
'source_type' => GithubApp::class,
'environment_variables' => [
'NODE_ENV' => [
'value' => 'production',
'isBuildOnly' => true,
],
'PORT' => [
'value' => 3000,
'isBuildOnly' => false,
]
]
]);
}
}

View File

@ -2,6 +2,7 @@
namespace Database\Seeders;
use App\Models\Environment;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
@ -27,6 +28,7 @@ class DatabaseSeeder extends Seeder
ApplicationSettingsSeeder::class,
DBSeeder::class,
ServiceSeeder::class,
EnvironmentVariableSeeder::class,
LocalPersistentVolumeSeeder::class,
]);
}

View File

@ -0,0 +1,22 @@
<?php
namespace Database\Seeders;
use App\Models\EnvironmentVariable;
use Illuminate\Database\Seeder;
class EnvironmentVariableSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
EnvironmentVariable::create([
'key' => 'NODE_ENV',
'value' => 'production',
'is_build_time' => true,
'application_id' => 1,
]);
}
}

View File

@ -26,7 +26,8 @@
@if ($type === 'textarea')
<textarea {{ $attributes }} type={{ $type }} id={{ $id }} wire:model.defer={{ $id }}></textarea>
@else
<input {{ $attributes }} type={{ $type }} id={{ $id }}
<input wire:dirty.class="text-black bg-amber-300" {{ $attributes }} type={{ $type }}
id={{ $id }}
@if ($instantSave) wire:click='instantSave' wire:model.defer={{ $id }} @else wire:model.defer={{ $value ?? $id }} @endif />
@endif

View File

@ -76,6 +76,9 @@
Livewire.on('reloadWindow', () => {
window.location.reload();
})
Livewire.on('error', (message) => {
alert(message);
})
</script>
@endauth
</body>

View File

@ -0,0 +1,19 @@
@props([
'show' => null,
'message' => 'Are you sure you want to delete this?',
'action' => 'delete',
])
<div x-cloak x-show="{{ $show }}" x-transition.opacity class="fixed inset-0 bg-slate-900/75"></div>
<div x-cloak x-show="{{ $show }}" x-transition class="fixed inset-0 z-50 flex items-center justify-center">
<div @click.away="{{ $show }} = false" class="w-screen h-20 max-w-xl mx-auto bg-black rounded-lg">
<div class="flex flex-col items-center justify-center h-full">
<div class="pb-5 text-white">{{ $message }}</div>
<div>
<x-inputs.button isWarning wire:click='{{ $action }}'>
Yes
</x-inputs.button>
<x-inputs.button x-on:click="{{ $show }} = false">No</x-inputs.button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,5 @@
@extends('errors::minimal')
@section('title', __('Unauthorized'))
@section('code', '401')
@section('message', __('Unauthorized'))

View File

@ -0,0 +1,5 @@
@extends('errors::minimal')
@section('title', __('Payment Required'))
@section('code', '402')
@section('message', __('Payment Required'))

View File

@ -0,0 +1,5 @@
@extends('errors::minimal')
@section('title', __('Forbidden'))
@section('code', '403')
@section('message', __($exception->getMessage() ?: 'Forbidden'))

View File

@ -0,0 +1,5 @@
@extends('errors::minimal')
@section('title', __('Not Found'))
@section('code', '404')
@section('message', __('Not Found'))

View File

@ -0,0 +1,5 @@
@extends('errors::minimal')
@section('title', __('Page Expired'))
@section('code', '419')
@section('message', __('Page Expired'))

View File

@ -0,0 +1,5 @@
@extends('errors::minimal')
@section('title', __('Too Many Requests'))
@section('code', '429')
@section('message', __('Too Many Requests'))

View File

@ -0,0 +1,5 @@
@extends('errors::minimal')
@section('title', __('Server Error'))
@section('code', '500')
@section('message', __('Server Error'))

View File

@ -0,0 +1,5 @@
@extends('errors::minimal')
@section('title', __('Service Unavailable'))
@section('code', '503')
@section('message', __('Service Unavailable'))

View File

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>@yield('title')</title>
<!-- Styles -->
<style>
html, body {
background-color: #fff;
color: #636b6f;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-weight: 100;
height: 100vh;
margin: 0;
}
.full-height {
height: 100vh;
}
.flex-center {
align-items: center;
display: flex;
justify-content: center;
}
.position-ref {
position: relative;
}
.content {
text-align: center;
}
.title {
font-size: 36px;
padding: 20px;
}
</style>
</head>
<body>
<div class="flex-center position-ref full-height">
<div class="content">
<div class="title">
@yield('message')
</div>
</div>
</div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -1,28 +0,0 @@
<div>
@if ($isNewEnv === true)
<form wire:submit.prevent='submit' class="flex gap-2 p-4">
@else
<form wire:submit.prevent='updateEnv' class="flex gap-2 p-4">
@endif
<input type="text" wire:model.defer="keyName" />
<input type="text" wire:model.defer="value" />
<div class="flex flex-col">
<div class="flex items-center gap-2">
<input type="checkbox" wire:model.defer="isBuildOnly" />
<label>Used during build?</label>
</div>
</div>
<x-inputs.button type="submit">
@if ($isNewEnv)
Add
@else
Update
@endif
</x-inputs.button>
@if ($isNewEnv === false)
<x-inputs.button isWarning wire:click.prevent='delete'>
Delete
</x-inputs.button>
@endif
</form>
</div>

View File

@ -0,0 +1,13 @@
<form wire:submit.prevent='submit' class="flex gap-2 p-4">
<input type="text" wire:model.defer="key" wire:dirty.class="text-black bg-amber-300" />
<input type="text" wire:model.defer="value" wire:dirty.class="text-black bg-amber-300" />
<div class="flex flex-col">
<div class="flex items-center gap-2">
<input type="checkbox" wire:model.defer="is_build_time" />
<label>Used during build?</label>
</div>
</div>
<x-inputs.button type="submit">
Add
</x-inputs.button>
</form>

View File

@ -0,0 +1,19 @@
<div x-data="{ deleteEnvironment: false }">
<form wire:submit.prevent='submit' class="flex gap-2 p-4">
<input type="text" wire:model.defer="env.key" wire:dirty.class="text-black bg-amber-300" />
<input type="text" wire:model.defer="env.value" wire:dirty.class="text-black bg-amber-300" />
<div class="flex flex-col">
<div class="flex items-center gap-2">
<input type="checkbox" wire:model.defer="env.is_build_time" />
<label>Used during build?</label>
</div>
</div>
<x-inputs.button type="submit">
Update
</x-inputs.button>
<x-inputs.button x-on:click="deleteEnvironment = true" isWarning>
Delete
</x-inputs.button>
</form>
<x-naked-modal show="deleteEnvironment" message="Are you sure you want to delete {{ $env->key }}?" />
</div>

View File

@ -25,12 +25,13 @@
</div>
<div x-cloak x-show="activeTab === 'environment-variables'" class="flex flex-col gap-2">
<h3>Environment Variables</h3>
@forelse ($application->environment_variables->all() as $keyName => $env)
<livewire:project.application.environment-variable :keyName="$keyName" :env="$env" />
@forelse ($application->environment_variables as $env)
<livewire:project.application.environment-variable.show :env="$env" />
@empty
<p>There are no environment variables for this application.</p>
@endforelse
<livewire:project.application.environment-variable />
<h4>Add new environment variable</h4>
<livewire:project.application.environment-variable.add />
</div>
<div x-cloak x-show="activeTab === 'source'">
<h3>Source</h3>