diff --git a/app/Data/ApplicationPreview.php b/app/Data/ApplicationPreview.php deleted file mode 100644 index 8af3e3ec9..000000000 --- a/app/Data/ApplicationPreview.php +++ /dev/null @@ -1,15 +0,0 @@ -route('dashboard'); } - return view('project.application.configuration', ['application' => $application,]); + return view('project.application.configuration', ['application' => $application]); } public function deployments() { @@ -64,9 +65,18 @@ class ApplicationController extends Controller 'application_uuid' => $application->uuid, ]); } + $deployment = ApplicationDeploymentQueue::where('deployment_uuid', $deployment_uuid)->first(); + if (!$deployment) { + return redirect()->route('project.application.deployments', [ + 'project_uuid' => $project->uuid, + 'environment_name' => $environment->name, + 'application_uuid' => $application->uuid, + ]); + } return view('project.application.deployment', [ 'application' => $application, 'activity' => $activity, + 'deployment' => $deployment, 'deployment_uuid' => $deployment_uuid, ]); } diff --git a/app/Http/Livewire/Project/Application/Deploy.php b/app/Http/Livewire/Project/Application/Deploy.php index 2ccc9a9b3..b7c221a5d 100644 --- a/app/Http/Livewire/Project/Application/Deploy.php +++ b/app/Http/Livewire/Project/Application/Deploy.php @@ -37,12 +37,9 @@ class Deploy extends Component $this->set_deployment_uuid(); queue_application_deployment( - application: $this->application, - extra_attributes: [ - 'deployment_uuid' => $this->deployment_uuid, - 'application_uuid' => $this->application->uuid, - 'force_rebuild' => $force, - ] + application_id: $this->application->id, + deployment_uuid: $this->deployment_uuid, + force_rebuild: $force, ); return redirect()->route('project.application.deployments', [ 'project_uuid' => $this->parameters['project_uuid'], diff --git a/app/Http/Livewire/Project/Application/DeploymentCancel.php b/app/Http/Livewire/Project/Application/DeploymentCancel.php new file mode 100644 index 000000000..24e587023 --- /dev/null +++ b/app/Http/Livewire/Project/Application/DeploymentCancel.php @@ -0,0 +1,34 @@ +deployment_uuid . 'of application: ' . $this->application->uuid); + $deployment = ApplicationDeploymentQueue::where('deployment_uuid', $this->deployment_uuid)->firstOrFail(); + $deployment->status = 'cancelled'; + $deployment->save(); + $this->activity->properties = $this->activity->properties->merge([ + 'exitCode' => 1, + 'status' => ProcessStatus::CANCELLED->value, + ]); + $this->activity->save(); + + instant_remote_process(["docker rm -f {$this->deployment_uuid}"], $this->application->destination->server, throwError: false); + } catch (\Throwable $th) { + return general_error_handler($th, $this); + } + } +} diff --git a/app/Http/Livewire/Project/Application/Preview/Form.php b/app/Http/Livewire/Project/Application/Preview/Form.php new file mode 100644 index 000000000..a7a718592 --- /dev/null +++ b/app/Http/Livewire/Project/Application/Preview/Form.php @@ -0,0 +1,44 @@ + 'required', + ]; + public function resetToDefault() + { + $this->application->preview_url_template = '{{pr_id}}.{{domain}}'; + $this->preview_url_template = $this->application->preview_url_template; + $this->application->save(); + $this->generate_real_url(); + } + public function generate_real_url() + { + if (data_get($this->application, 'fqdn')) { + $url = Url::fromString($this->application->fqdn); + $host = $url->getHost(); + $this->preview_url_template = Str::of($this->application->preview_url_template)->replace('{{domain}}', $host); + } + } + public function mount() + { + $this->generate_real_url(); + } + public function submit() + { + $this->validate(); + $this->application->preview_url_template = str_replace(' ', '', $this->application->preview_url_template); + ray($this->application->preview_url_template); + $this->application->save(); + $this->generate_real_url(); + } +} diff --git a/app/Http/Livewire/Project/Application/Previews.php b/app/Http/Livewire/Project/Application/Previews.php index 4f21134cb..de61665cd 100644 --- a/app/Http/Livewire/Project/Application/Previews.php +++ b/app/Http/Livewire/Project/Application/Previews.php @@ -2,10 +2,77 @@ namespace App\Http\Livewire\Project\Application; +use App\Jobs\ContainerStatusJob; use App\Models\Application; +use App\Models\ApplicationPreview; +use Illuminate\Support\Collection; use Livewire\Component; +use Visus\Cuid2\Cuid2; class Previews extends Component { public Application $application; + public string $deployment_uuid; + public array $parameters; + public Collection $pull_requests; + public int $rate_limit_remaining; + + public function mount() + { + $this->pull_requests = collect(); + $this->parameters = get_parameters(); + } + public function loadStatus($pull_request_id) + { + dispatch(new ContainerStatusJob( + application: $this->application, + container_name: generate_container_name($this->application->uuid, $pull_request_id), + pull_request_id: $pull_request_id + )); + } + protected function set_deployment_uuid() + { + $this->deployment_uuid = new Cuid2(7); + $this->parameters['deployment_uuid'] = $this->deployment_uuid; + } + public function load_prs() + { + ['rate_limit_remaining' => $rate_limit_remaining, 'data' => $data] = get_from_git_api($this->application->source, "/repos/{$this->application->git_repository}/pulls"); + $this->rate_limit_remaining = $rate_limit_remaining; + $this->pull_requests = $data; + } + public function deploy(int $pull_request_id) + { + try { + $this->set_deployment_uuid(); + ApplicationPreview::create([ + 'application_id' => $this->application->id, + 'pull_request_id' => $pull_request_id, + ]); + queue_application_deployment( + application_id: $this->application->id, + deployment_uuid: $this->deployment_uuid, + force_rebuild: true, + pull_request_id: $pull_request_id, + ); + } catch (\Throwable $th) { + return general_error_handler($th, $this); + } + } + public function stop(int $pull_request_id) + { + try { + $container_name = generate_container_name($this->application->uuid, $pull_request_id); + ray('Stopping container: ' . $container_name); + + instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false); + dispatch(new ContainerStatusJob( + application: $this->application, + container_name: $container_name, + pull_request_id: $pull_request_id + )); + } catch (\Throwable $th) { + return general_error_handler($th, $this); + } + } } diff --git a/app/Http/Livewire/Project/Application/Rollback.php b/app/Http/Livewire/Project/Application/Rollback.php index 072784074..02a194936 100644 --- a/app/Http/Livewire/Project/Application/Rollback.php +++ b/app/Http/Livewire/Project/Application/Rollback.php @@ -18,18 +18,15 @@ class Rollback extends Component { $this->parameters = get_parameters(); } - public function rollbackImage($tag) + public function rollbackImage($commit) { $deployment_uuid = new Cuid2(7); queue_application_deployment( - application: $this->application, - extra_attributes: [ - 'deployment_uuid' => $deployment_uuid, - 'application_uuid' => $this->application->uuid, - 'force_rebuild' => false, - 'commit' => $tag, - ] + application_id: $this->application->id, + deployment_uuid: $deployment_uuid, + commit: $commit, + force_rebuild: false, ); return redirect()->route('project.application.deployments', [ diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index ecb2769b8..279282362 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -8,6 +8,7 @@ use App\Enums\ActivityTypes; use App\Enums\ProcessStatus; use App\Models\Application; use App\Models\ApplicationDeploymentQueue; +use App\Models\ApplicationPreview; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -19,6 +20,7 @@ use Spatie\Activitylog\Models\Activity; use Symfony\Component\Yaml\Yaml; use Illuminate\Support\Str; use Spatie\Url\Url; +use Visus\Cuid2\Cuid2; class ApplicationDeploymentJob implements ShouldQueue { @@ -35,6 +37,10 @@ class ApplicationDeploymentJob implements ShouldQueue private string $docker_compose; private $build_args; private $env_args; + private string $build_image_name; + private string $production_image_name; + private string $container_name; + private ApplicationPreview|null $preview; public static int $batch_counter = 0; public $timeout = 10200; @@ -42,20 +48,25 @@ class ApplicationDeploymentJob implements ShouldQueue public function __construct( public int $application_deployment_queue_id, public string $deployment_uuid, - public string $application_uuid, + public string $application_id, public bool $force_rebuild = false, - public string|null $commit = null, + public string|null $rollback_commit = null, + public string|null $pull_request_id = null, ) { $this->application_deployment_queue = ApplicationDeploymentQueue::find($this->application_deployment_queue_id); $this->application_deployment_queue->update([ 'status' => ProcessStatus::IN_PROGRESS->value, ]); - if ($this->commit) { - $this->git_commit = $this->commit; + if ($this->rollback_commit) { + $this->git_commit = $this->rollback_commit; } - $this->application = Application::query() - ->where('uuid', $this->application_uuid) - ->firstOrFail(); + + $this->application = Application::find($this->application_id); + + if ($this->pull_request_id) { + $this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id); + } + $this->destination = $this->application->destination->getMorphClass()::where('id', $this->application->destination->id)->first(); $server = $this->destination->server; @@ -78,7 +89,6 @@ class ApplicationDeploymentJob implements ShouldQueue ->event(ActivityTypes::DEPLOYMENT->value) ->log("[]"); } - public function handle(): void { try { @@ -87,115 +97,15 @@ class ApplicationDeploymentJob implements ShouldQueue } $this->workdir = "/artifacts/{$this->deployment_uuid}"; - - // Pull builder image - $this->execute_now([ - "echo 'Starting deployment of {$this->application->git_repository}:{$this->application->git_branch}...'", - "echo -n 'Pulling latest version of the builder image (ghcr.io/coollabsio/coolify-builder)... '", - ]); - - $this->execute_now([ - "docker run --pull=always -d --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/coollabsio/coolify-builder", - ], isDebuggable: true); - - $this->execute_now([ - "echo 'Done.'" - ]); - - if (is_null($this->git_commit)) { - // Import git repository - $this->execute_now([ - "echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} to {$this->workdir}... '" - ]); - - $this->execute_now([ - ...$this->gitImport(), - ], 'importing_git_repository'); - - $this->execute_now([ - "echo 'Done.'" - ]); - // Get git commit - $this->execute_now([$this->execute_in_builder("cd {$this->workdir} && git rev-parse HEAD")], 'commit_sha', hideFromOutput: true); - $this->git_commit = $this->activity->properties->get('commit_sha'); - } - - if (!$this->force_rebuild) { - $this->execute_now([ - "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->execute_now([ - "echo 'Docker Image found locally with the same Git Commit SHA. Build skipped...'" - ]); - // Generate docker-compose.yml - $this->generate_compose_file(); - - // Stop running container - $this->stop_running_container(); - - // Start application - $this->start_by_compose_file(); - $this->next(ProcessStatus::FINISHED->value); - return; - } - } - $this->execute_now([ - $this->execute_in_builder("rm -fr {$this->workdir}/.git") - ], hideFromOutput: true); - - $this->execute_now([ - "echo -n 'Generating nixpacks configuration... '", - ]); - $this->execute_now([ - $this->nixpacks_build_cmd(), - $this->execute_in_builder("cp {$this->workdir}/.nixpacks/Dockerfile {$this->workdir}/Dockerfile"), - $this->execute_in_builder("rm -f {$this->workdir}/.nixpacks/Dockerfile"), - ], isDebuggable: true); - - // Generate docker-compose.yml - $this->generate_compose_file(); - $this->execute_now([ - "echo 'Done.'", - "echo -n 'Building image... '", - ]); - - $this->generate_build_env_variables(); - $this->add_build_env_variables_to_dockerfile(); - - if ($this->application->settings->is_static) { - $this->execute_now([ - $this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->application->uuid}:{$this->git_commit}-build {$this->workdir}"), - ], isDebuggable: true); - - $dockerfile = "FROM {$this->application->static_image} -WORKDIR /usr/share/nginx/html/ -LABEL coolify.deploymentId={$this->deployment_uuid} -COPY --from={$this->application->uuid}:{$this->git_commit}-build /app/{$this->application->publish_directory} ."; - $docker_file = base64_encode($dockerfile); - - $this->execute_now([ - $this->execute_in_builder("echo '{$docker_file}' | base64 -d > {$this->workdir}/Dockerfile-prod"), - $this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t {$this->application->uuid}:{$this->git_commit} {$this->workdir}"), - ], hideFromOutput: true); + if ($this->pull_request_id) { + ray('Deploying pull/' . $this->pull_request_id . '/head for application: ' . $this->application->name); + $this->deploy_pull_request(); } else { - $this->execute_now([ - $this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->application->uuid}:{$this->git_commit} {$this->workdir}"), - ], isDebuggable: true); + $this->deploy(); } - - $this->execute_now([ - "echo 'Done.'", - ]); - // Stop running container - $this->stop_running_container(); - - // Start application - $this->start_by_compose_file(); - - $this->next(ProcessStatus::FINISHED->value); } catch (\Exception $e) { + ray('Oops something is not okay, are you okay? 😢'); + ray($e); $this->execute_now([ "echo '\nOops something is not okay, are you okay? 😢'", "echo '\n\n{$e->getMessage()}'", @@ -208,8 +118,154 @@ COPY --from={$this->application->uuid}:{$this->git_commit}-build /app/{$this->ap $this->execute_now(["docker rm -f {$this->deployment_uuid} >/dev/null 2>&1"], hideFromOutput: true); } } + + private function start_builder_image() + { + $this->execute_now([ + "echo -n 'Pulling latest version of the builder image (ghcr.io/coollabsio/coolify-builder)... '", + ]); + $this->execute_now([ + "docker run --pull=always -d --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/coollabsio/coolify-builder", + ], isDebuggable: true); + $this->execute_now([ + "echo 'Done.'" + ]); + $this->execute_now([ + $this->execute_in_builder("mkdir -p {$this->workdir}"), + ]); + } + + private function clone_repository() + { + $this->execute_now([ + "echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} to {$this->workdir}... '" + ]); + + $this->execute_now([ + ...$this->importing_git_repository(), + ], 'importing_git_repository'); + + $this->execute_now([ + "echo 'Done.'" + ]); + // Get git commit + $this->execute_now([$this->execute_in_builder("cd {$this->workdir} && git rev-parse HEAD")], 'commit_sha', hideFromOutput: true); + $this->git_commit = $this->activity->properties->get('commit_sha'); + } + + private function cleanup_git() + { + $this->execute_now([ + $this->execute_in_builder("rm -fr {$this->workdir}/.git") + ], hideFromOutput: true); + } + private function generate_buildpack() + { + $this->execute_now([ + "echo -n 'Generating nixpacks configuration... '", + ]); + $this->execute_now([ + $this->nixpacks_build_cmd(), + $this->execute_in_builder("cp {$this->workdir}/.nixpacks/Dockerfile {$this->workdir}/Dockerfile"), + $this->execute_in_builder("rm -f {$this->workdir}/.nixpacks/Dockerfile"), + ], isDebuggable: true); + } + private function build_image() + { + $this->execute_now([ + "echo -n 'Building image... '", + ]); + + if ($this->application->settings->is_static) { + $this->execute_now([ + $this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t { $this->build_image_name {$this->workdir}"), + ], isDebuggable: true); + + $dockerfile = "FROM {$this->application->static_image} +WORKDIR /usr/share/nginx/html/ +LABEL coolify.deploymentId={$this->deployment_uuid} +COPY --from=$this->build_image_name /app/{$this->application->publish_directory} ."; + $docker_file = base64_encode($dockerfile); + + $this->execute_now([ + $this->execute_in_builder("echo '{$docker_file}' | base64 -d > {$this->workdir}/Dockerfile-prod"), + $this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), + ], hideFromOutput: true); + } else { + $this->execute_now([ + $this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), + ], isDebuggable: true); + } + $this->execute_now([ + "echo 'Done.'", + ]); + } + private function deploy_pull_request() + { + $this->build_image_name = "{$this->application->uuid}:pr_{$this->pull_request_id}-build"; + $this->production_image_name = "{$this->application->uuid}:pr_{$this->pull_request_id}"; + $this->container_name = generate_container_name($this->application->uuid, $this->pull_request_id); + // Deploy pull request + $this->execute_now([ + "echo 'Starting deployment of {$this->application->git_repository}:{$this->application->git_branch} PR#{$this->pull_request_id}...'", + ]); + $this->start_builder_image(); + $this->clone_repository(); + $this->cleanup_git(); + $this->generate_buildpack(); + $this->generate_compose_file(); + // Needs separate preview variables + // $this->generate_build_env_variables(); + // $this->add_build_env_variables_to_dockerfile(); + $this->build_image(); + $this->stop_running_container(); + $this->start_by_compose_file(); + $this->next(ProcessStatus::FINISHED->value); + } + private function deploy() + { + $this->container_name = generate_container_name($this->application->uuid); + // Deploy normal commit + $this->execute_now([ + "echo 'Starting deployment of {$this->application->git_repository}:{$this->application->git_branch}...'", + ]); + $this->start_builder_image(); + if ($this->rollback_commit === 'HEAD') { + $this->clone_repository(); + } + $this->build_image_name = "{$this->application->uuid}:{$this->git_commit}-build"; + $this->production_image_name = "{$this->application->uuid}:{$this->git_commit}"; + ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name:' . $this->production_image_name); + if (!$this->force_rebuild) { + $this->execute_now([ + "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->execute_now([ + "echo 'Docker Image found locally with the same Git Commit SHA. Build skipped...'" + ]); + $this->generate_compose_file(); + $this->stop_running_container(); + $this->start_by_compose_file(); + $this->next(ProcessStatus::FINISHED->value); + return; + } + } + $this->cleanup_git(); + $this->generate_buildpack(); + $this->generate_compose_file(); + $this->generate_build_env_variables(); + $this->add_build_env_variables_to_dockerfile(); + $this->build_image(); + $this->stop_running_container(); + $this->start_by_compose_file(); + $this->next(ProcessStatus::FINISHED->value); + } + public function failed(): void { + ray('failed job'); $this->activity->properties = $this->activity->properties->merge([ 'exitCode' => 1, 'status' => ProcessStatus::ERROR->value, @@ -221,7 +277,11 @@ COPY --from={$this->application->uuid}:{$this->git_commit}-build /app/{$this->ap private function next(string $status) { - dispatch(new ContainerStatusJob($this->application->id)); + dispatch(new ContainerStatusJob( + application: $this->application, + container_name: $this->container_name, + pull_request_id: $this->pull_request_id + )); $this->application_deployment_queue->update([ 'status' => $status, ]); @@ -229,9 +289,9 @@ COPY --from={$this->application->uuid}:{$this->git_commit}-build /app/{$this->ap if ($next_found) { dispatch(new ApplicationDeploymentJob( application_deployment_queue_id: $next_found->id, - deployment_uuid: $next_found->extra_attributes['deployment_uuid'], - application_uuid: $next_found->extra_attributes['application_uuid'], - force_rebuild: $next_found->extra_attributes['force_rebuild'], + application_id: $next_found->application_id, + deployment_uuid: $next_found->deployment_uuid, + force_rebuild: $next_found->force_rebuild, )); } } @@ -286,15 +346,21 @@ COPY --from={$this->application->uuid}:{$this->git_commit}-build /app/{$this->ap private function generate_docker_compose() { $ports = $this->application->settings->is_static ? [80] : $this->application->ports_exposes_array; - $persistentStorages = $this->generate_local_persistent_volumes(); - $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); - $environment_variables = $this->generate_environment_variables($ports); + if ($this->pull_request_id) { + $persistent_storages = []; + $volume_names = []; + $environment_variables = []; + } else { + $persistent_storages = $this->generate_local_persistent_volumes(); + $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); + $environment_variables = $this->generate_environment_variables($ports); + } $docker_compose = [ 'version' => '3.8', 'services' => [ - $this->application->uuid => [ - 'image' => "{$this->application->uuid}:$this->git_commit", - 'container_name' => $this->application->uuid, + $this->container_name => [ + 'image' => $this->production_image_name, + 'container_name' => $this->container_name, 'restart' => 'always', 'environment' => $environment_variables, 'labels' => $this->set_labels_for_applications(), @@ -329,11 +395,11 @@ COPY --from={$this->application->uuid}:{$this->git_commit}-build /app/{$this->ap ] ] ]; - if (count($this->application->ports_mappings_array) > 0) { - $docker_compose['services'][$this->application->uuid]['ports'] = $this->application->ports_mappings_array; + if (count($this->application->ports_mappings_array) > 0 && !$this->pull_request_id) { + $docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array; } - if (count($persistentStorages) > 0) { - $docker_compose['services'][$this->application->uuid]['volumes'] = $persistentStorages; + if (count($persistent_storages) > 0) { + $docker_compose['services'][$this->container_name]['volumes'] = $persistent_storages; } if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; @@ -387,8 +453,27 @@ COPY --from={$this->application->uuid}:{$this->git_commit}-build /app/{$this->ap $labels[] = 'coolify.applicationId=' . $this->application->id; $labels[] = 'coolify.type=application'; $labels[] = 'coolify.name=' . $this->application->name; + if ($this->pull_request_id) { + $labels[] = 'coolify.pullRequestId=' . $this->pull_request_id; + } if ($this->application->fqdn) { - $domains = Str::of($this->application->fqdn)->explode(','); + if ($this->pull_request_id) { + $preview_fqdn = data_get($this->preview, 'fqdn'); + $template = $this->application->preview_url_template; + $url = Url::fromString($this->application->fqdn); + $host = $url->getHost(); + $schema = $url->getScheme(); + $random = new Cuid2(7); + $preview_fqdn = str_replace('{{random}}', $random, $template); + $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); + $preview_fqdn = str_replace('{{pr_id}}', $this->pull_request_id, $preview_fqdn); + $preview_fqdn = "$schema://$preview_fqdn"; + $this->preview->fqdn = $preview_fqdn; + $this->preview->save(); + $domains = Str::of($preview_fqdn)->explode(','); + } else { + $domains = Str::of($this->application->fqdn)->explode(','); + } $labels[] = 'traefik.enable=true'; foreach ($domains as $domain) { $url = Url::fromString($domain); @@ -475,7 +560,7 @@ COPY --from={$this->application->uuid}:{$this->git_commit}-build /app/{$this->ap throw new \RuntimeException($result->errorOutput()); } } - private function setGitImportSettings($git_clone_command) + private function set_git_import_settings($git_clone_command) { if ($this->application->git_commit_sha !== 'HEAD') { $git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git -c advice.detachedHead=false checkout {$this->application->git_commit_sha} >/dev/null 2>&1"; @@ -488,9 +573,12 @@ COPY --from={$this->application->uuid}:{$this->git_commit}-build /app/{$this->ap } return $git_clone_command; } - private function gitImport() + private function importing_git_repository() { $git_clone_command = "git clone -q -b {$this->application->git_branch}"; + if ($this->pull_request_id) { + $pr_branch_name = "pr_{$this->pull_request_id}_coolify"; + } if ($this->application->deploymentType() === 'source') { $source_html_url = data_get($this->application, 'source.html_url'); @@ -501,22 +589,30 @@ COPY --from={$this->application->uuid}:{$this->git_commit}-build /app/{$this->ap if ($this->source->getMorphClass() == 'App\Models\GithubApp') { if ($this->source->is_public) { $git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->application->git_repository} {$this->workdir}"; - $git_clone_command = $this->setGitImportSettings($git_clone_command); - return [ - $this->execute_in_builder($git_clone_command) - ]; + $git_clone_command = $this->set_git_import_settings($git_clone_command); + + $commands = [$this->execute_in_builder($git_clone_command)]; + + if ($this->pull_request_id) { + $commands[] = $this->execute_in_builder("cd {$this->workdir} && git fetch origin pull/{$this->pull_request_id}/head:$pr_branch_name && git checkout $pr_branch_name"); + } + return $commands; } else { $github_access_token = generate_github_installation_token($this->source); - return [ + $commands = [ $this->execute_in_builder("git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git {$this->workdir}") ]; + if ($this->pull_request_id) { + $commands[] = $this->execute_in_builder("cd {$this->workdir} && git fetch origin pull/{$this->pull_request_id}/head:$pr_branch_name && git checkout $pr_branch_name"); + } + return $commands; } } } if ($this->application->deploymentType() === 'deploy_key') { $private_key = base64_encode($this->application->private_key->private_key); $git_clone_command = "GIT_SSH_COMMAND=\"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_full_url} {$this->workdir}"; - $git_clone_command = $this->setGitImportSettings($git_clone_command); + $git_clone_command = $this->set_git_import_settings($git_clone_command); return [ $this->execute_in_builder("mkdir -p /root/.ssh"), $this->execute_in_builder("echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"), @@ -539,19 +635,22 @@ COPY --from={$this->application->uuid}:{$this->git_commit}-build /app/{$this->ap $nixpacks_command .= " --install-cmd \"{$this->application->install_command}\""; } $nixpacks_command .= " {$this->workdir}"; + ray('Build command: ' . $nixpacks_command); return $this->execute_in_builder($nixpacks_command); } private function stop_running_container() { $this->execute_now([ "echo -n 'Removing old instance... '", - $this->execute_in_builder("docker rm -f {$this->application->uuid} >/dev/null 2>&1"), + $this->execute_in_builder("docker rm -f $this->container_name >/dev/null 2>&1"), "echo 'Done.'", - "echo -n 'Starting your application... '", ]); } private function start_by_compose_file() { + $this->execute_now([ + "echo -n 'Starting your application... '", + ]); $this->execute_now([ $this->execute_in_builder("docker compose --project-directory {$this->workdir} up -d >/dev/null"), ], isDebuggable: true); @@ -564,7 +663,10 @@ COPY --from={$this->application->uuid}:{$this->git_commit}-build /app/{$this->ap $this->docker_compose = $this->generate_docker_compose(); $docker_compose_base64 = base64_encode($this->docker_compose); $this->execute_now([ - $this->execute_in_builder("mkdir -p {$this->workdir} && echo '{$docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml") + $this->execute_in_builder("echo '{$docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml") ], hideFromOutput: true); + $this->execute_now([ + "echo 'Done.'", + ]); } } diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index ce513b2bd..b53de6209 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -3,6 +3,7 @@ namespace App\Jobs; use App\Models\Application; +use App\Models\ApplicationPreview; use App\Models\Server; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeUnique; @@ -16,59 +17,38 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeUnique { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - private Application $application; - public function __construct( - public string|null $application_id = null, - ) { - if ($this->application_id) { - $this->application = Application::find($this->application_id); - } + public string $container_name; + public string|null $pull_request_id; + public Application $application; + + public function __construct($application, string $container_name, string|null $pull_request_id = null) + { + $this->application = $application; + $this->container_name = $container_name; + $this->pull_request_id = $pull_request_id; } public function uniqueId(): string { - return $this->application_id; + return $this->container_name; } public function handle(): void { try { - if ($this->application->uuid) { - $this->check_container_status(); + ray('running ContainerStatusJob', $this->container_name, $this->pull_request_id); + $status = get_container_status(server: $this->application->destination->server, container_id: $this->container_name, throwError: false); + ray('ContainerStatusJob', $status); + if ($this->pull_request_id) { + $preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id); + $preview->status = $status; + $preview->save(); } else { - $this->check_all_servers(); + $this->application->status = $status; + $this->application->save(); } } catch (\Exception $e) { Log::error($e->getMessage()); } } - protected function check_all_servers() - { - $servers = Server::all()->reject(fn (Server $server) => $server->settings->is_build_server); - $applications = Application::all(); - $not_found_applications = $applications; - $containers = collect(); - foreach ($servers as $server) { - $output = instant_remote_process(['docker ps -a -q --format \'{{json .}}\''], $server); - $containers = $containers->concat(format_docker_command_output_to_json($output)); - } - foreach ($containers as $container) { - $found_application = $applications->filter(function ($value, $key) use ($container) { - return $value->uuid == $container['Names']; - })->first(); - if ($found_application) { - $not_found_applications = $not_found_applications->filter(function ($value, $key) use ($found_application) { - return $value->uuid != $found_application->uuid; - }); - $found_application->status = $container['State']; - $found_application->save(); - Log::info('Found application: ' . $found_application->uuid . '. Set status to: ' . $found_application->status); - } - } - foreach ($not_found_applications as $not_found_application) { - $not_found_application->status = 'exited'; - $not_found_application->save(); - Log::info('Not found application: ' . $not_found_application->uuid . '. Set status to: ' . $not_found_application->status); - } - } protected function check_container_status() { if ($this->application->destination->server) { @@ -76,4 +56,34 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeUnique $this->application->save(); } } + // protected function check_all_servers() + // { + // $servers = Server::all()->reject(fn (Server $server) => $server->settings->is_build_server); + // $applications = Application::all(); + // $not_found_applications = $applications; + // $containers = collect(); + // foreach ($servers as $server) { + // $output = instant_remote_process(['docker ps -a -q --format \'{{json .}}\''], $server); + // $containers = $containers->concat(format_docker_command_output_to_json($output)); + // } + // foreach ($containers as $container) { + // $found_application = $applications->filter(function ($value, $key) use ($container) { + // return $value->uuid == $container['Names']; + // })->first(); + // if ($found_application) { + // $not_found_applications = $not_found_applications->filter(function ($value, $key) use ($found_application) { + // return $value->uuid != $found_application->uuid; + // }); + // $found_application->status = $container['State']; + // $found_application->save(); + // Log::info('Found application: ' . $found_application->uuid . '. Set status to: ' . $found_application->status); + // } + // } + // foreach ($not_found_applications as $not_found_application) { + // $not_found_application->status = 'exited'; + // $not_found_application->save(); + // Log::info('Not found application: ' . $not_found_application->uuid . '. Set status to: ' . $not_found_application->status); + // } + // } + } diff --git a/app/Models/Application.php b/app/Models/Application.php index 19ae1d9a6..4f19481c2 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -41,13 +41,6 @@ class Application extends BaseModel 'private_key_id' ]; - public $casts = [ - 'extra_attributes' => SchemalessAttributes::class, - ]; - public function scopeWithExtraAttributes(): Builder - { - return $this->extra_attributes->modelScope(); - } public function publishDirectory(): Attribute { return Attribute::make( @@ -130,6 +123,10 @@ class Application extends BaseModel { return $this->belongsTo(Environment::class); } + public function previews() + { + return $this->hasMany(ApplicationPreview::class); + } public function settings() { return $this->hasOne(ApplicationSetting::class); @@ -149,9 +146,7 @@ class Application extends BaseModel public function deployments() { - $asd = ApplicationDeploymentQueue::where('application_id', $this->id)->orderBy('created_at', 'desc')->get(); - return $asd; - // return Activity::where('subject_id', $this->id)->where('properties->type', '=', 'deployment')->orderBy('created_at', 'desc')->get(); + return ApplicationDeploymentQueue::where('application_id', $this->id)->orderBy('created_at', 'desc')->get(); } public function get_deployment(string $deployment_uuid) { diff --git a/app/Models/ApplicationDeploymentQueue.php b/app/Models/ApplicationDeploymentQueue.php index 1b492f42b..cee3293b0 100644 --- a/app/Models/ApplicationDeploymentQueue.php +++ b/app/Models/ApplicationDeploymentQueue.php @@ -10,15 +10,10 @@ class ApplicationDeploymentQueue extends Model { protected $fillable = [ 'application_id', + 'deployment_uuid', + 'pull_request_id', + 'force_rebuild', + 'commit', 'status', - 'extra_attributes', ]; - - public $casts = [ - 'extra_attributes' => SchemalessAttributes::class, - ]; - public function scopeWithExtraAttributes(): Builder - { - return $this->extra_attributes->modelScope(); - } } diff --git a/app/Models/ApplicationPreview.php b/app/Models/ApplicationPreview.php new file mode 100644 index 000000000..d7975ec9c --- /dev/null +++ b/app/Models/ApplicationPreview.php @@ -0,0 +1,22 @@ +belongsTo(Application::class); + } + static function findPreviewByApplicationAndPullId(int $application_id, int $pull_request_id) + { + return self::where('application_id', $application_id)->where('pull_request_id', $pull_request_id)->firstOrFail(); + } +} diff --git a/app/Models/Server.php b/app/Models/Server.php index d2afff3f5..9351becf7 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -28,6 +28,11 @@ class Server extends BaseModel 'extra_attributes' => SchemalessAttributes::class, ]; + public function scopeWithExtraAttributes(): Builder + { + return $this->extra_attributes->modelScope(); + } + public function standaloneDockers() { return $this->hasMany(StandaloneDocker::class); @@ -38,10 +43,7 @@ class Server extends BaseModel return $this->hasMany(SwarmDocker::class); } - public function scopeWithExtraAttributes(): Builder - { - return $this->extra_attributes->modelScope(); - } + public function privateKey() { diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index ff560e119..23061dde1 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,7 @@ namespace App\Providers; +use Illuminate\Support\Facades\Http; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider @@ -18,5 +19,10 @@ class AppServiceProvider extends ServiceProvider */ public function boot(): void { + Http::macro('github', function (string $api_url) { + return Http::withHeaders([ + 'Accept' => 'application/vnd.github.v3+json' + ])->baseUrl($api_url); + }); } } diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index 1a2043773..e72c7c980 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -1,17 +1,22 @@ $application->id, - 'extra_attributes' => $extra_attributes, + 'application_id' => $application_id, + 'deployment_uuid' => $deployment_uuid, + 'pull_request_id' => $pull_request_id, + 'force_rebuild' => $force_rebuild, + 'commit' => $commit, ]); - $queued_deployments = ApplicationDeploymentQueue::where('application_id', $application->id)->where('status', 'queued')->get()->sortByDesc('created_at'); - $running_deployments = ApplicationDeploymentQueue::where('application_id', $application->id)->where('status', 'in_progress')->get()->sortByDesc('created_at'); + $queued_deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('status', 'queued')->get()->sortByDesc('created_at'); + $running_deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('status', 'in_progress')->get()->sortByDesc('created_at'); + ray('Queued deployments: ' . $queued_deployments->count()); + ray('Running deployments: ' . $running_deployments->count()); if ($queued_deployments->count() > 1) { $queued_deployments = $queued_deployments->skip(1); $queued_deployments->each(function ($queued_deployment, $key) { @@ -24,9 +29,10 @@ function queue_application_deployment(Application $application, $extra_attribute } dispatch(new ApplicationDeploymentJob( application_deployment_queue_id: $deployment->id, - deployment_uuid: $extra_attributes['deployment_uuid'], - application_uuid: $extra_attributes['application_uuid'], - force_rebuild: $extra_attributes['force_rebuild'], - commit: $extra_attributes['commit'] ?? null, + application_id: $application_id, + deployment_uuid: $deployment_uuid, + force_rebuild: $force_rebuild, + rollback_commit: $commit, + pull_request_id: $pull_request_id, )); } diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index ce6057e5f..57fb04884 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -38,3 +38,12 @@ function get_container_status(Server $server, string $container_id, bool $throwE $container = format_docker_command_output_to_json($container); return $container[0]['Status']; } + +function generate_container_name(string $uuid, int|null $pull_request_id = null) +{ + if ($pull_request_id) { + return $uuid . '_pr_' . $pull_request_id; + } else { + return $uuid; + } +} diff --git a/bootstrap/helpers/github.php b/bootstrap/helpers/github.php index ee1810ae6..9ede08f22 100644 --- a/bootstrap/helpers/github.php +++ b/bootstrap/helpers/github.php @@ -1,6 +1,7 @@ toString(); return $issuedToken; } + +function get_from_git_api(GithubApp|GitlabApp $source, $endpoint) +{ + if ($source->getMorphClass() == 'App\Models\GithubApp') { + if ($source->is_public) { + $response = Http::github($source->api_url)->get($endpoint); + } + } + $json = $response->json(); + if ($response->status() !== 200) { + throw new \Exception("Failed to get data from {$source->name} with error: " . $json['message']); + } + return [ + 'rate_limit_remaining' => $response->header('X-RateLimit-Remaining'), + 'data' => collect($json) + ]; +} diff --git a/bootstrap/helpers/remoteProcess.php b/bootstrap/helpers/remoteProcess.php index d4197fe2a..9ead36cf6 100644 --- a/bootstrap/helpers/remoteProcess.php +++ b/bootstrap/helpers/remoteProcess.php @@ -85,7 +85,7 @@ function instant_remote_process(array $command, Server $server, $throwError = tr $output = trim($process->output()); $exitCode = $process->exitCode(); if ($exitCode !== 0) { - Log::info($process->errorOutput()); + ray($process->errorOutput()); if (!$throwError) { return null; } diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 57fa14c27..d3f017aa6 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -9,6 +9,7 @@ use Illuminate\Support\Str; function general_error_handler(\Throwable $e, $that = null, $isJson = false) { try { + ray('ERROR OCCURED: ' . $e->getMessage()); if ($e instanceof QueryException) { if ($e->errorInfo[0] === '23505') { throw new \Exception('Duplicate entry found.', '23505'); @@ -29,7 +30,7 @@ function general_error_handler(\Throwable $e, $that = null, $isJson = false) 'error' => $error->getMessage(), ]); } else { - // dump($error); + ray($error); } } } diff --git a/database/migrations/2023_03_27_081716_create_applications_table.php b/database/migrations/2023_03_27_081716_create_applications_table.php index bad0343d5..ec91f0737 100644 --- a/database/migrations/2023_03_27_081716_create_applications_table.php +++ b/database/migrations/2023_03_27_081716_create_applications_table.php @@ -41,8 +41,6 @@ return new class extends Migration $table->string('base_directory')->default('/'); $table->string('publish_directory')->nullable(); - $table->schemalessAttributes('extra_attributes'); - $table->string('health_check_path')->default('/'); $table->string('health_check_port')->nullable(); $table->string('health_check_host')->default('localhost'); @@ -65,6 +63,7 @@ return new class extends Migration $table->integer('limits_cpu_shares')->default(1024); $table->string('status')->default('exited'); + $table->string('preview_url_template')->default('{{pr_id}}.{{domain}}'); $table->nullableMorphs('destination'); $table->nullableMorphs('source'); diff --git a/database/migrations/2023_03_27_081718_create_application_previews_table.php b/database/migrations/2023_03_27_081718_create_application_previews_table.php new file mode 100644 index 000000000..73346268c --- /dev/null +++ b/database/migrations/2023_03_27_081718_create_application_previews_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('uuid')->unique(); + $table->integer('pull_request_id'); + + $table->string('fqdn')->unique()->nullable(); + $table->string('status')->default('exited'); + + $table->foreignId('application_id'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('application_previews'); + } +}; diff --git a/database/migrations/2023_05_24_083426_create_application_deployment_queues_table.php b/database/migrations/2023_05_24_083426_create_application_deployment_queues_table.php index f5d9d2cbc..bf8bfcc35 100644 --- a/database/migrations/2023_05_24_083426_create_application_deployment_queues_table.php +++ b/database/migrations/2023_05_24_083426_create_application_deployment_queues_table.php @@ -14,8 +14,10 @@ return new class extends Migration Schema::create('application_deployment_queues', function (Blueprint $table) { $table->id(); $table->string('application_id'); + $table->string('deployment_uuid')->unique(); $table->integer('pull_request_id')->default(0); - $table->schemalessAttributes('extra_attributes'); + $table->boolean('force_rebuild')->default(false); + $table->string('commit')->default('HEAD'); $table->string('status')->default('queued'); $table->timestamps(); }); diff --git a/database/seeders/ApplicationPreviewSeeder.php b/database/seeders/ApplicationPreviewSeeder.php new file mode 100644 index 000000000..512f1c990 --- /dev/null +++ b/database/seeders/ApplicationPreviewSeeder.php @@ -0,0 +1,25 @@ + $application_1->id, + // 'pull_request_id' => 1 + // ]); + } +} diff --git a/database/seeders/ApplicationSeeder.php b/database/seeders/ApplicationSeeder.php index 1bc48b76e..2859bed81 100644 --- a/database/seeders/ApplicationSeeder.php +++ b/database/seeders/ApplicationSeeder.php @@ -26,6 +26,7 @@ class ApplicationSeeder extends Seeder Application::create([ 'name' => 'coollabsio/coolify-examples:nodejs-fastify', + 'fqdn' => 'http://foo.com', 'repository_project_id' => 603035348, 'git_repository' => 'coollabsio/coolify-examples', 'git_branch' => 'nodejs-fastify', @@ -36,17 +37,7 @@ class ApplicationSeeder extends Seeder 'destination_id' => $standalone_docker_1->id, 'destination_type' => StandaloneDocker::class, 'source_id' => $github_public_source->id, - 'source_type' => GithubApp::class, - 'previews' => [ - ApplicationPreview::from([ - 'pullRequestId' => 1, - 'branch' => 'nodejs-fastify' - ]), - ApplicationPreview::from([ - 'pullRequestId' => 2, - 'branch' => 'nodejs-fastify' - ]) - ] + 'source_type' => GithubApp::class ]); } } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index a45d8462e..1f19fca02 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -26,6 +26,7 @@ class DatabaseSeeder extends Seeder GitlabAppSeeder::class, ApplicationSeeder::class, ApplicationSettingsSeeder::class, + ApplicationPreviewSeeder::class, DBSeeder::class, ServiceSeeder::class, EnvironmentVariableSeeder::class, diff --git a/resources/css/app.css b/resources/css/app.css index 4f964096c..f617da219 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -126,4 +126,7 @@ a { } .bg-coollabs-gradient { @apply text-transparent bg-clip-text bg-gradient-to-r from-purple-500 via-pink-500 to-red-500; +} +.bold-helper { + @apply inline-block font-bold text-warning } \ No newline at end of file diff --git a/resources/views/components/external-link.blade.php b/resources/views/components/external-link.blade.php index 8c42de94a..0ee609a91 100644 --- a/resources/views/components/external-link.blade.php +++ b/resources/views/components/external-link.blade.php @@ -1 +1 @@ - + diff --git a/resources/views/livewire/project/application/deployment-cancel.blade.php b/resources/views/livewire/project/application/deployment-cancel.blade.php new file mode 100644 index 000000000..c1220f8ea --- /dev/null +++ b/resources/views/livewire/project/application/deployment-cancel.blade.php @@ -0,0 +1,3 @@ +
+ Cancel +
diff --git a/resources/views/livewire/project/application/deployments.blade.php b/resources/views/livewire/project/application/deployments.blade.php index bf1131cdf..e8fc089ca 100644 --- a/resources/views/livewire/project/application/deployments.blade.php +++ b/resources/views/livewire/project/application/deployments.blade.php @@ -6,17 +6,30 @@ - $deployment->status === 'queued' || - $deployment->status === 'cancelled by system', + data_get($deployment, 'status') === 'queued' || + data_get($deployment, 'status') === 'cancelled by system', 'border-warning hover:bg-warning hover:text-black' => - $deployment->status === 'in_progress', - 'border-error hover:bg-error' => $deployment->status === 'error', - 'border-success hover:bg-success' => $deployment->status === 'finished', - ]) @if ($deployment->status !== 'cancelled by system' && $deployment->status !== 'queued') - href="{{ $current_url . '/' . $deployment->extra_attributes['deployment_uuid'] }}" + data_get($deployment, 'status') === 'in_progress', + 'border-error hover:bg-error' => + data_get($deployment, 'status') === 'error', + 'border-success hover:bg-success' => + data_get($deployment, 'status') === 'finished', + ]) @if (data_get($deployment, 'status') !== 'cancelled by system' && data_get($deployment, 'status') !== 'queued') + href="{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}" @endif class="hover:no-underline">
+ @if (data_get($deployment, 'pull_request_id')) +
Pull Request #{{ data_get($deployment, 'pull_request_id') }}
+ @else +
Commit: + @if (data_get($deployment, 'commit')) + {{ data_get($deployment, 'commit') }} + @else + HEAD + @endif +
+ @endif
{{ $deployment->status }}
diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 3b0b5bd87..091b44416 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -11,7 +11,7 @@
+ helper="You can specify one domain with path or more with comma.
Example- http://app.coolify.io, https://cloud.coolify.io/dashboard
- http://app.coolify.io/api/v3" />
@if ($wildcard_domain) diff --git a/resources/views/livewire/project/application/preview/form.blade.php b/resources/views/livewire/project/application/preview/form.blade.php new file mode 100644 index 000000000..23dea13ff --- /dev/null +++ b/resources/views/livewire/project/application/preview/form.blade.php @@ -0,0 +1,12 @@ +
+
+

Settings

+ Save + Reset to default +
+
+ +
Domain Preview: {{ $preview_url_template }}
+
+
diff --git a/resources/views/livewire/project/application/previews.blade.php b/resources/views/livewire/project/application/previews.blade.php index 8401cd554..83d993ce2 100644 --- a/resources/views/livewire/project/application/previews.blade.php +++ b/resources/views/livewire/project/application/previews.blade.php @@ -1,8 +1,55 @@

Previews

-
diff --git a/resources/views/livewire/project/new/public-git-repository.blade.php b/resources/views/livewire/project/new/public-git-repository.blade.php index aa6f70091..c291ee8b7 100644 --- a/resources/views/livewire/project/new/public-git-repository.blade.php +++ b/resources/views/livewire/project/new/public-git-repository.blade.php @@ -4,7 +4,7 @@
+ helper="Examplehttps://github.com/coollabsio/coolify-examples => main branch will be selected
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify => nodejs-fastify branch will be selected" /> @if ($is_static) diff --git a/resources/views/project/application/configuration.blade.php b/resources/views/project/application/configuration.blade.php index 51000c3d8..5695f3f95 100644 --- a/resources/views/project/application/configuration.blade.php +++ b/resources/views/project/application/configuration.blade.php @@ -29,6 +29,9 @@ Storages + Previews + Rollback @@ -59,6 +62,9 @@
+
+ +
diff --git a/resources/views/project/application/deployment.blade.php b/resources/views/project/application/deployment.blade.php index 208122b3c..139d33c49 100644 --- a/resources/views/project/application/deployment.blade.php +++ b/resources/views/project/application/deployment.blade.php @@ -13,5 +13,10 @@
{{ data_get($activity, 'properties.status') }}
+ @if (data_get($activity, 'properties.status') === 'in_progress' || + data_get($deployment, 'metadata.status') !== 'error' || + data_get($deployment, 'metadata.status') !== 'finished') + + @endif