do not use hash routing for tabs
add empty project creation if local image is found, we only refresh the configuration
This commit is contained in:
parent
9f32730714
commit
2c68eed072
@ -40,7 +40,7 @@ class Kernel extends HttpKernel
|
||||
|
||||
'api' => [
|
||||
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
||||
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
|
||||
\Illuminate\Routing\Middleware\ThrottleRequests::class . ':api',
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
];
|
||||
|
@ -64,7 +64,7 @@ class Deploy extends Component
|
||||
|
||||
public function delete()
|
||||
{
|
||||
$this->kill();
|
||||
$this->stop();
|
||||
Application::find($this->applicationId)->delete();
|
||||
return redirect()->route('project.resources', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
@ -72,12 +72,6 @@ class Deploy extends Component
|
||||
]);
|
||||
}
|
||||
public function stop()
|
||||
{
|
||||
runRemoteCommandSync($this->destination->server, ["docker stop -t 0 {$this->application->uuid} >/dev/null 2>&1"]);
|
||||
$this->application->status = 'stopped';
|
||||
$this->application->save();
|
||||
}
|
||||
public function kill()
|
||||
{
|
||||
runRemoteCommandSync($this->destination->server, ["docker rm -f {$this->application->uuid}"]);
|
||||
if ($this->application->status != 'exited') {
|
||||
|
@ -22,7 +22,6 @@ class General extends Component
|
||||
public bool $is_git_lfs_allowed;
|
||||
public bool $is_debug;
|
||||
public bool $is_previews;
|
||||
public bool $is_bot;
|
||||
public bool $is_custom_ssl;
|
||||
public bool $is_http2;
|
||||
public bool $is_auto_deploy;
|
||||
@ -49,7 +48,6 @@ class General extends Component
|
||||
$this->application->settings->is_git_lfs_allowed = $this->is_git_lfs_allowed;
|
||||
$this->application->settings->is_debug = $this->is_debug;
|
||||
$this->application->settings->is_previews = $this->is_previews;
|
||||
$this->application->settings->is_bot = $this->is_bot;
|
||||
$this->application->settings->is_custom_ssl = $this->is_custom_ssl;
|
||||
$this->application->settings->is_http2 = $this->is_http2;
|
||||
$this->application->settings->is_auto_deploy = $this->is_auto_deploy;
|
||||
@ -65,7 +63,6 @@ class General extends Component
|
||||
$this->is_git_lfs_allowed = $this->application->settings->is_git_lfs_allowed;
|
||||
$this->is_debug = $this->application->settings->is_debug;
|
||||
$this->is_previews = $this->application->settings->is_previews;
|
||||
$this->is_bot = $this->application->settings->is_bot;
|
||||
$this->is_custom_ssl = $this->application->settings->is_custom_ssl;
|
||||
$this->is_http2 = $this->application->settings->is_http2;
|
||||
$this->is_auto_deploy = $this->application->settings->is_auto_deploy;
|
||||
|
19
app/Http/Livewire/Project/New/EmptyProject.php
Normal file
19
app/Http/Livewire/Project/New/EmptyProject.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\New;
|
||||
|
||||
use App\Models\Project;
|
||||
use Livewire\Component;
|
||||
|
||||
class EmptyProject extends Component
|
||||
{
|
||||
public function createEmptyProject()
|
||||
{
|
||||
$project = Project::create([
|
||||
'name' => fake()->company(),
|
||||
'description' => fake()->sentence(),
|
||||
'team_id' => session('currentTeam')->id,
|
||||
]);
|
||||
return redirect()->route('project.environments', ['project_uuid' => $project->uuid, 'environment_name' => 'production']);
|
||||
}
|
||||
}
|
@ -35,6 +35,7 @@ class DeployApplicationJob implements ShouldQueue
|
||||
protected Activity $activity;
|
||||
protected string $git_commit;
|
||||
protected string $workdir;
|
||||
protected string $docker_compose;
|
||||
public static int $batch_counter = 0;
|
||||
|
||||
/**
|
||||
@ -70,7 +71,33 @@ class DeployApplicationJob implements ShouldQueue
|
||||
->event(ActivityTypes::DEPLOYMENT->value)
|
||||
->log("[]");
|
||||
}
|
||||
protected function stopRunningContainer()
|
||||
{
|
||||
$this->executeNow([
|
||||
|
||||
"echo -n 'Removing old instance... '",
|
||||
$this->execute_in_builder("docker rm -f {$this->application->uuid} >/dev/null 2>&1"),
|
||||
"echo 'Done.'",
|
||||
"echo -n 'Starting your application... '",
|
||||
]);
|
||||
}
|
||||
protected function startByComposeFile()
|
||||
{
|
||||
$this->executeNow([
|
||||
$this->execute_in_builder("docker compose --project-directory {$this->workdir} up -d >/dev/null"),
|
||||
], isDebuggable: true);
|
||||
$this->executeNow([
|
||||
"echo 'Done. 🎉'",
|
||||
], isFinished: true);
|
||||
}
|
||||
protected function generateComposeFile()
|
||||
{
|
||||
$this->docker_compose = $this->generate_docker_compose();
|
||||
$docker_compose_base64 = base64_encode($this->docker_compose);
|
||||
$this->executeNow([
|
||||
$this->execute_in_builder("echo '{$docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml")
|
||||
], hideFromOutput: true);
|
||||
}
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
@ -86,7 +113,7 @@ class DeployApplicationJob implements ShouldQueue
|
||||
$wildcard_domain = $project_wildcard_domain ?? $global_wildcard_domain ?? null;
|
||||
|
||||
// Set wildcard domain
|
||||
if (!$this->application->settings->is_bot && !$this->application->fqdn && $wildcard_domain) {
|
||||
if (!$this->application->fqdn && $wildcard_domain) {
|
||||
$this->application->fqdn = 'http://' . $this->application->uuid . '.' . $wildcard_domain;
|
||||
$this->application->save();
|
||||
}
|
||||
@ -119,35 +146,30 @@ class DeployApplicationJob implements ShouldQueue
|
||||
|
||||
if (!$this->force_rebuild) {
|
||||
$this->executeNow([
|
||||
"docker inspect {$this->application->uuid} --format '{{json .Config.Image}}' 2>&1",
|
||||
], 'stopped_container_image', hideFromOutput: true, ignoreErrors: true);
|
||||
$image = $this->activity->properties->get('stopped_container_image');
|
||||
if (isset($image)) {
|
||||
$image = explode(':', str_replace('"', '', $image))[1];
|
||||
if ($image == $this->git_commit) {
|
||||
$this->executeNow([
|
||||
"echo -n 'Application found locally with the same Git Commit SHA. Starting it... '"
|
||||
]);
|
||||
$this->executeNow([
|
||||
"docker start {$this->application->uuid}"
|
||||
], hideFromOutput: true);
|
||||
"docker images -q {$this->application->uuid}:{$this->git_commit} 2>/dev/null",
|
||||
], 'local_image_found', hideFromOutput: true, ignoreErrors: true);
|
||||
$image_found = Str::of($this->activity->properties->get('local_image_found'))->trim()->isNotEmpty();
|
||||
if ($image_found) {
|
||||
$this->executeNow([
|
||||
"echo 'Docker Image found locally with the same Git Commit SHA. Build skipped...'"
|
||||
]);
|
||||
// Generate docker-compose.yml
|
||||
$this->generateComposeFile();
|
||||
|
||||
$this->executeNow([
|
||||
"echo 'Done. 🎉'",
|
||||
], isFinished: true);
|
||||
return;
|
||||
}
|
||||
// Stop running container
|
||||
$this->stopRunningContainer();
|
||||
|
||||
// Start application
|
||||
$this->startByComposeFile();
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->executeNow([
|
||||
$this->execute_in_builder("rm -fr {$this->workdir}/.git")
|
||||
], hideFromOutput: true);
|
||||
|
||||
$docker_compose = $this->generate_docker_compose();
|
||||
$docker_compose_base64 = base64_encode($docker_compose);
|
||||
$this->executeNow([
|
||||
$this->execute_in_builder("echo '{$docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml")
|
||||
], hideFromOutput: true);
|
||||
// Generate docker-compose.yml
|
||||
$this->generateComposeFile();
|
||||
|
||||
$this->executeNow([
|
||||
"echo -n 'Generating nixpacks configuration... '",
|
||||
@ -183,24 +205,18 @@ COPY --from={$this->application->uuid}:{$this->git_commit}-build /app/{$this->ap
|
||||
$this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile --build-arg SOURCE_COMMIT={$this->git_commit} --progress plain -t {$this->application->uuid}:{$this->git_commit} {$this->workdir}"),
|
||||
], isDebuggable: true);
|
||||
}
|
||||
|
||||
$this->executeNow([
|
||||
"echo 'Done.'",
|
||||
"echo -n 'Removing old instance... '",
|
||||
$this->execute_in_builder("docker rm -f {$this->application->uuid} >/dev/null 2>&1"),
|
||||
"echo 'Done.'",
|
||||
"echo -n 'Starting your application... '",
|
||||
]);
|
||||
$this->executeNow([
|
||||
$this->execute_in_builder("docker compose --project-directory {$this->workdir} up -d >/dev/null"),
|
||||
], isDebuggable: true);
|
||||
// Stop running container
|
||||
$this->stopRunningContainer();
|
||||
|
||||
// Start application
|
||||
$this->startByComposeFile();
|
||||
|
||||
$this->executeNow([
|
||||
"echo 'Done. 🎉'",
|
||||
], isFinished: true);
|
||||
// Saving docker-compose.yml
|
||||
Storage::disk('deployments')->put(Str::kebab($this->application->name) . '/docker-compose.yml', $docker_compose);
|
||||
// }
|
||||
|
||||
Storage::disk('deployments')->put(Str::kebab($this->application->name) . '/docker-compose.yml', $this->docker_compose);
|
||||
} catch (\Exception $e) {
|
||||
$this->executeNow([
|
||||
"echo 'Oops something is not okay, are you okay? 😢'",
|
||||
|
@ -36,6 +36,43 @@ class Application extends BaseModel
|
||||
'publish_directory',
|
||||
];
|
||||
|
||||
public function publishDirectory(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: fn ($value) => $value ? '/' . ltrim($value, '/') : null,
|
||||
);
|
||||
}
|
||||
public function baseDirectory(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: fn ($value) => '/' . ltrim($value, '/'),
|
||||
);
|
||||
}
|
||||
public function portsMappings(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: fn ($value) => $value === "" ? null : $value,
|
||||
);
|
||||
}
|
||||
public function portsMappingsArray(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () =>
|
||||
is_null($this->ports_mappings)
|
||||
? []
|
||||
: explode(',', $this->ports_mappings),
|
||||
|
||||
);
|
||||
}
|
||||
public function portsExposesArray(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () =>
|
||||
is_null($this->ports_exposes)
|
||||
? []
|
||||
: explode(',', $this->ports_exposes)
|
||||
);
|
||||
}
|
||||
public function environment()
|
||||
{
|
||||
return $this->belongsTo(Environment::class);
|
||||
@ -57,38 +94,7 @@ class Application extends BaseModel
|
||||
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
||||
}
|
||||
|
||||
public function publishDirectory(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: fn ($value) => $value ? '/' . ltrim($value, '/') : null,
|
||||
);
|
||||
}
|
||||
public function baseDirectory(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: fn ($value) => '/' . ltrim($value, '/'),
|
||||
);
|
||||
}
|
||||
public function portsMappingsArray(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () =>
|
||||
is_null($this->ports_mappings)
|
||||
? []
|
||||
: explode(',', $this->ports_mappings)
|
||||
|
||||
);
|
||||
}
|
||||
public function portsExposesArray(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () =>
|
||||
is_null($this->ports_exposes)
|
||||
? []
|
||||
: explode(',', $this->ports_exposes)
|
||||
|
||||
);
|
||||
}
|
||||
public function deployments()
|
||||
{
|
||||
return Activity::where('subject_id', $this->id)->where('properties->deployment_uuid', '!=', null)->orderBy('created_at', 'desc')->get();
|
||||
|
@ -20,7 +20,6 @@ return new class extends Migration
|
||||
$table->boolean('is_dual_cert')->default(false);
|
||||
$table->boolean('is_debug')->default(false);
|
||||
$table->boolean('is_previews')->default(false);
|
||||
$table->boolean('is_bot')->default(false);
|
||||
$table->boolean('is_custom_ssl')->default(false);
|
||||
$table->boolean('is_http2')->default(false);
|
||||
$table->foreignId('application_id');
|
||||
|
@ -15,7 +15,7 @@
|
||||
@livewireStyles
|
||||
</head>
|
||||
|
||||
<body x-data="confirmModal" x-on:keydown.escape="toggleConfirmModal">
|
||||
<body x-data="confirmModal">
|
||||
<x-navbar />
|
||||
<main>
|
||||
{{ $slot }}
|
||||
|
@ -5,15 +5,14 @@
|
||||
@if ($application->status === 'running')
|
||||
<button wire:click='start'>Restart</button>
|
||||
<button wire:click='forceRebuild'>Force Rebuild</button>
|
||||
<button wire:click='stop'>Stop</button>
|
||||
@else
|
||||
<button wire:click='start'>Start</button>
|
||||
<button wire:click='forceRebuild'>Start (no cache)</button>
|
||||
@endif
|
||||
<button wire:click='kill'>Kill</button>
|
||||
<button wire:click='stop'>Stop</button>
|
||||
<span wire:poll.5000ms='pollingStatus'>
|
||||
@if ($application->status === 'running')
|
||||
@if (!data_get($application, 'settings.is_bot') && data_get($application, 'fqdn'))
|
||||
@if (data_get($application, 'fqdn'))
|
||||
<a target="_blank" href="{{ data_get($application, 'fqdn') }}">Open URL</a>
|
||||
@endif
|
||||
|
||||
|
@ -28,7 +28,7 @@
|
||||
@if ($application->settings->is_static)
|
||||
<x-form-input id="application.ports_exposes" label="Ports Exposes" disabled />
|
||||
@else
|
||||
<x-form-input id="application.ports_exposes" label="Ports Exposes" />
|
||||
<x-form-input id="application.ports_exposes" label="Ports Exposes" required />
|
||||
@endif
|
||||
|
||||
<x-form-input id="application.ports_mappings" label="Ports Mappings" />
|
||||
@ -43,7 +43,6 @@
|
||||
<x-form-input instantSave type="checkbox" id="is_auto_deploy" label="Auto Deploy?" />
|
||||
<x-form-input instantSave type="checkbox" id="is_dual_cert" label="Dual Certs?" />
|
||||
<x-form-input instantSave type="checkbox" id="is_previews" label="Previews?" />
|
||||
<x-form-input instantSave type="checkbox" id="is_bot" label="Is Bot?" />
|
||||
<x-form-input instantSave type="checkbox" id="is_custom_ssl" label="Is Custom SSL?" />
|
||||
<x-form-input instantSave type="checkbox" id="is_http2" label="Is Http2?" />
|
||||
<x-form-input instantSave type="checkbox" id="is_git_submodules_allowed" label="Git Submodules Allowed?" />
|
||||
|
@ -0,0 +1 @@
|
||||
<button wire:click='createEmptyProject'>Empty Project</button>
|
@ -1,29 +1,34 @@
|
||||
<x-layout>
|
||||
<h1>Configuration</h1>
|
||||
<x-applications.navbar :applicationId="$application->id" />
|
||||
<div x-data="{ tab: window.location.hash ? window.location.hash.substring(1) : 'general' }">
|
||||
<div x-data="{ activeTab: 'general' }">
|
||||
<div class="flex gap-4">
|
||||
<a @click.prevent="tab = 'general'; window.location.hash = 'general'" href="#">General</a>
|
||||
<a @click.prevent="tab = 'envs'; window.location.hash = 'envs'" href="#">Environment Variables</a>
|
||||
<a @click.prevent="tab = 'source'; window.location.hash = 'source'" href="#">Source</a>
|
||||
<a @click.prevent="tab = 'destination'; window.location.hash = 'destination'" href="#">Destination
|
||||
<a :class="activeTab === 'general' && 'text-green-500'" @click.prevent="activeTab = 'general'"
|
||||
href="#">General</a>
|
||||
<a :class="activeTab === 'envs' && 'text-green-500'" @click.prevent="activeTab = 'envs'"
|
||||
href="#">Environment Variables</a>
|
||||
<a :class="activeTab === 'source' && 'text-green-500'" @click.prevent="activeTab = 'source'"
|
||||
href="#">Source</a>
|
||||
<a :class="activeTab === 'destination' && 'text-green-500'" @click.prevent="activeTab = 'destination'"
|
||||
href="#">Destination
|
||||
</a>
|
||||
<a @click.prevent="tab = 'storages'; window.location.hash = 'storages'" href="#">Storage
|
||||
<a :class="activeTab === 'storages' && 'text-green-500'" @click.prevent="activeTab = 'storages'"
|
||||
href="#">Storage
|
||||
</a>
|
||||
</div>
|
||||
<div x-cloak x-show="tab === 'general'">
|
||||
<div x-cloak x-show="activeTab === 'general'">
|
||||
<livewire:project.application.general :applicationId="$application->id" />
|
||||
</div>
|
||||
<div x-cloak x-show="tab === 'envs'">
|
||||
<div x-cloak x-show="activeTab === 'envs'">
|
||||
<livewire:project.application.environment-variables />
|
||||
</div>
|
||||
<div x-cloak x-show="tab === 'source'">
|
||||
<div x-cloak x-show="activeTab === 'source'">
|
||||
<livewire:project.application.source :applicationId="$application->id" />
|
||||
</div>
|
||||
<div x-cloak x-show="tab === 'destination'">
|
||||
<div x-cloak x-show="activeTab === 'destination'">
|
||||
<livewire:project.application.destination :destination="$application->destination" />
|
||||
</div>
|
||||
<div x-cloak x-show="tab === 'storages'">
|
||||
<div x-cloak x-show="activeTab === 'storages'">
|
||||
<livewire:project.application.storages :storages="$application->persistentStorages" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -4,28 +4,22 @@
|
||||
@elseif ($type === 'resource')
|
||||
<h1>New Resource</h1>
|
||||
@endif
|
||||
<div x-data="{ tab: window.location.hash ? window.location.hash.substring(1) : 'choose' }">
|
||||
<div x-data="{ activeTab: 'choose' }">
|
||||
<div class="flex flex-col w-64 gap-2 mb-10">
|
||||
<button @click.prevent="tab = 'public-repo'; window.location.hash = 'public-repo'">Public Repository
|
||||
</button>
|
||||
<button @click.prevent="tab = 'github-private-repo'; window.location.hash = 'github-private-repo'">Private
|
||||
Repository (GitHub App)</button>
|
||||
<button @click.prevent="activeTab = 'public-repo'">Public Repository</button>
|
||||
<button @click.prevent="activeTab = 'github-private-repo'">Private Repository (GitHub App)</button>
|
||||
@if ($type === 'project')
|
||||
<button @click.prevent="tab = 'empty-project'; window.location.hash = 'empty-project'">Empty
|
||||
Project</button>
|
||||
<livewire:project.new.empty-project />
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div x-cloak x-show="tab === 'public-repo'">
|
||||
<div x-cloak x-show="activeTab === 'public-repo'">
|
||||
<livewire:project.new.public-git-repository :type="$type" />
|
||||
</div>
|
||||
<div x-cloak x-show="tab === 'github-private-repo'">
|
||||
<div x-cloak x-show="activeTab === 'github-private-repo'">
|
||||
github-private-repo
|
||||
</div>
|
||||
<div x-cloak x-show="tab === 'empty-project'">
|
||||
empty-project
|
||||
</div>
|
||||
<div x-cloak x-show="tab === 'choose'">
|
||||
<div x-cloak x-show="activeTab === 'choose'">
|
||||
Choose any option
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user