'string|nullable', ]; public function mount() { $this->pull_requests = collect(); $this->parameters = get_route_parameters(); } public function load_prs() { try { $this->authorize('update', $this->application); ['rate_limit_remaining' => $rate_limit_remaining, 'data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/pulls"); $this->rate_limit_remaining = $rate_limit_remaining; $this->pull_requests = $data->sortBy('number')->values(); } catch (\Throwable $e) { $this->rate_limit_remaining = 0; return handleError($e, $this); } } public function confirmDomainUsage() { $this->forceSaveDomains = true; $this->showDomainConflictModal = false; if ($this->pendingPreviewId) { $this->save_preview($this->pendingPreviewId); $this->pendingPreviewId = null; } } public function save_preview($preview_id) { try { $this->authorize('update', $this->application); $success = true; $preview = $this->application->previews->find($preview_id); if (data_get_str($preview, 'fqdn')->isNotEmpty()) { $preview->fqdn = str($preview->fqdn)->replaceEnd(',', '')->trim(); $preview->fqdn = str($preview->fqdn)->replaceStart(',', '')->trim(); $preview->fqdn = str($preview->fqdn)->trim()->lower(); if (! validateDNSEntry($preview->fqdn, $this->application->destination->server)) { $this->dispatch('error', 'Validating DNS failed.', "Make sure you have added the DNS records correctly.

$preview->fqdn->{$this->application->destination->server->ip}

Check this documentation for further help."); $success = false; } // Check for domain conflicts if not forcing save if (! $this->forceSaveDomains) { $result = checkDomainUsage(resource: $this->application, domain: $preview->fqdn); if ($result['hasConflicts']) { $this->domainConflicts = $result['conflicts']; $this->showDomainConflictModal = true; $this->pendingPreviewId = $preview_id; return; } } else { // Reset the force flag after using it $this->forceSaveDomains = false; } } if (! $preview) { throw new \Exception('Preview not found'); } $success && $preview->save(); $success && $this->dispatch('success', 'Preview saved.

Do not forget to redeploy the preview to apply the changes.'); } catch (\Throwable $e) { return handleError($e, $this); } } public function generate_preview($preview_id) { try { $this->authorize('update', $this->application); $preview = $this->application->previews->find($preview_id); if (! $preview) { $this->dispatch('error', 'Preview not found.'); return; } if ($this->application->build_pack === 'dockercompose') { $preview->generate_preview_fqdn_compose(); $this->application->refresh(); $this->dispatch('success', 'Domain generated.'); return; } $preview->generate_preview_fqdn(); $this->application->refresh(); $this->dispatch('update_links'); $this->dispatch('success', 'Domain generated.'); } catch (\Throwable $e) { return handleError($e, $this); } } public function add(int $pull_request_id, ?string $pull_request_html_url = null) { try { $this->authorize('update', $this->application); if ($this->application->build_pack === 'dockercompose') { $this->setDeploymentUuid(); $found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first(); if (! $found && ! is_null($pull_request_html_url)) { $found = ApplicationPreview::create([ 'application_id' => $this->application->id, 'pull_request_id' => $pull_request_id, 'pull_request_html_url' => $pull_request_html_url, 'docker_compose_domains' => $this->application->docker_compose_domains, ]); } $found->generate_preview_fqdn_compose(); $this->application->refresh(); } else { $this->setDeploymentUuid(); $found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first(); if (! $found && ! is_null($pull_request_html_url)) { $found = ApplicationPreview::create([ 'application_id' => $this->application->id, 'pull_request_id' => $pull_request_id, 'pull_request_html_url' => $pull_request_html_url, ]); } $found->generate_preview_fqdn(); $this->application->refresh(); $this->dispatch('update_links'); $this->dispatch('success', 'Preview added.'); } } catch (\Throwable $e) { return handleError($e, $this); } } public function force_deploy_without_cache(int $pull_request_id, ?string $pull_request_html_url = null) { $this->authorize('deploy', $this->application); $this->deploy($pull_request_id, $pull_request_html_url, force_rebuild: true); } public function add_and_deploy(int $pull_request_id, ?string $pull_request_html_url = null) { $this->authorize('deploy', $this->application); $this->add($pull_request_id, $pull_request_html_url); $this->deploy($pull_request_id, $pull_request_html_url); } public function deploy(int $pull_request_id, ?string $pull_request_html_url = null, bool $force_rebuild = false) { $this->authorize('deploy', $this->application); try { $this->setDeploymentUuid(); $found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first(); if (! $found && ! is_null($pull_request_html_url)) { ApplicationPreview::create([ 'application_id' => $this->application->id, 'pull_request_id' => $pull_request_id, 'pull_request_html_url' => $pull_request_html_url, ]); } $result = queue_application_deployment( application: $this->application, deployment_uuid: $this->deployment_uuid, force_rebuild: $force_rebuild, pull_request_id: $pull_request_id, git_type: $found->git_type ?? null, ); if ($result['status'] === 'skipped') { $this->dispatch('success', 'Deployment skipped', $result['message']); return; } return redirect()->route('project.application.deployment.show', [ 'project_uuid' => $this->parameters['project_uuid'], 'application_uuid' => $this->parameters['application_uuid'], 'deployment_uuid' => $this->deployment_uuid, 'environment_uuid' => $this->parameters['environment_uuid'], ]); } catch (\Throwable $e) { return handleError($e, $this); } } protected function setDeploymentUuid() { $this->deployment_uuid = new Cuid2; $this->parameters['deployment_uuid'] = $this->deployment_uuid; } private function stopContainers(array $containers, $server) { $containersToStop = collect($containers)->pluck('Names')->toArray(); foreach ($containersToStop as $containerName) { instant_remote_process(command: [ "docker stop --time=30 $containerName", "docker rm -f $containerName", ], server: $server, throwError: false); } } public function stop(int $pull_request_id) { $this->authorize('deploy', $this->application); try { $server = $this->application->destination->server; if ($this->application->destination->server->isSwarm()) { instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $server); } else { $containers = getCurrentApplicationContainerStatus($server, $this->application->id, $pull_request_id)->toArray(); $this->stopContainers($containers, $server); } GetContainersStatus::run($server); $this->application->refresh(); $this->dispatch('containerStatusUpdated'); $this->dispatch('success', 'Preview Deployment stopped.'); } catch (\Throwable $e) { return handleError($e, $this); } } public function delete(int $pull_request_id) { try { $this->authorize('delete', $this->application); $preview = ApplicationPreview::where('application_id', $this->application->id) ->where('pull_request_id', $pull_request_id) ->first(); if (! $preview) { $this->dispatch('error', 'Preview not found.'); return; } // Soft delete immediately for instant UI feedback $preview->delete(); // Dispatch the job for async cleanup (container stopping + force delete) DeleteResourceJob::dispatch($preview); // Refresh the application and its previews relationship to reflect the soft delete $this->application->load('previews'); $this->dispatch('update_links'); $this->dispatch('success', 'Preview deletion started. It may take a few moments to complete.'); } catch (\Throwable $e) { return handleError($e, $this); } } }