diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 296076eac3..09a634c681 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -155,6 +155,7 @@ class General extends Component 'resetDefaultLabels', 'configurationChanged' => '$refresh', 'confirmDomainUsage', + 'composeFileLoaded' => 'syncLoadedComposeFile', ]; protected function rules(): array @@ -575,18 +576,7 @@ class General extends Component return; } - $this->parsedServices = redactLockedValuesInGeneratedCompose($this->parsedServices, $this->application); - - // Refresh parsedServiceDomains to reflect any changes in docker_compose_domains - $this->application->refresh(); - - // Sync the docker_compose_raw from the model to the component property - // This ensures the Monaco editor displays the loaded compose file - $this->syncData(); - - $this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : []; - // Convert service names with dots and dashes to use underscores for HTML form binding - $this->parsedServiceDomains = $this->sanitizeParsedServiceDomainsForForm($this->parsedServiceDomains); + $this->showLoadedComposeFile(); $showToast && $this->dispatch('success', 'Docker compose file loaded.'); $this->dispatch('compose_loaded'); @@ -605,6 +595,34 @@ class General extends Component } } + /** + * Shows a Compose file that the heading loaded while this form is open. + */ + public function syncLoadedComposeFile(): void + { + $this->authorize('update', $this->application); + + $this->application->refresh(); + $this->parsedServices = $this->application->parse(); + $this->showLoadedComposeFile(); + } + + private function showLoadedComposeFile(): void + { + $this->parsedServices = redactLockedValuesInGeneratedCompose($this->parsedServices, $this->application); + + // Refresh parsedServiceDomains to reflect any changes in docker_compose_domains + $this->application->refresh(); + + // Sync the docker_compose_raw from the model to the component property + // This ensures the Monaco editor displays the loaded compose file + $this->syncData(); + + $this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : []; + // Convert service names with dots and dashes to use underscores for HTML form binding + $this->parsedServiceDomains = $this->sanitizeParsedServiceDomainsForForm($this->parsedServiceDomains); + } + public function generateDomain(string $serviceName) { try { diff --git a/app/Livewire/Project/Application/Heading.php b/app/Livewire/Project/Application/Heading.php index 46711750c2..9c83db4219 100644 --- a/app/Livewire/Project/Application/Heading.php +++ b/app/Livewire/Project/Application/Heading.php @@ -4,10 +4,13 @@ namespace App\Livewire\Project\Application; use App\Actions\Application\StopApplication; use App\Actions\Docker\GetContainersStatus; +use App\Livewire\Project\Service\Storage; +use App\Livewire\Project\Shared\EnvironmentVariable\All; use App\Models\Application; use App\Models\ApplicationDeploymentQueue; use App\Traits\ListensToTeamChannel; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; +use Livewire\Attributes\Locked; use Livewire\Component; class Heading extends Component @@ -29,10 +32,18 @@ class Heading extends Component public bool $docker_cleanup = true; + /** + * Set once on page open, so that a later switch to the Compose build pack does not load the file + * before the user confirms its location. + */ + #[Locked] + public bool $loadComposeOnOpen = false; + public function getListeners() { return [ 'compose_loaded' => '$refresh', + 'configurationChanged' => '$refresh', 'update_links' => '$refresh', ...$this->teamChannelListeners([ 'ServiceStatusChanged' => 'checkStatus', @@ -53,6 +64,39 @@ class Heading extends Component $lastDeployment = $this->application->get_last_successful_deployment(); $this->lastDeploymentInfo = data_get_str($lastDeployment, 'commit')->limit(7).' '.data_get($lastDeployment, 'commit_message'); $this->lastDeploymentLink = $this->application->gitCommitLink(data_get($lastDeployment, 'commit')); + $this->loadComposeOnOpen = $this->needsComposeFile() && auth()->user()?->can('update', $this->application); + } + + public function needsComposeFile(): bool + { + return $this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw); + } + + public function loadComposeFile(bool $isInit = false) + { + try { + $this->authorize('update', $this->application); + + if ($isInit && ! $this->needsComposeFile()) { + return; + } + + $loaded = $this->application->loadComposeFile(); + if (is_null(data_get($loaded, 'parsedServices'))) { + $this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.'); + + return; + } + + $this->dispatch('success', 'Docker compose file loaded.'); + $this->dispatch('composeFileLoaded')->to(General::class); + $this->dispatch('storageCountsChanged')->to(Storage::class); + $this->dispatch('refreshEnvs')->to(All::class); + } catch (\Throwable $e) { + return handleError($e, $this); + } finally { + $this->application->refresh(); + } } /** @@ -126,7 +170,7 @@ class Heading extends Component try { $this->authorize('deploy', $this->application); - if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) { + if ($this->needsComposeFile()) { $this->dispatch('error', 'Failed to deploy', 'Please load a Compose file first.'); return; diff --git a/resources/views/components/forms/button.blade.php b/resources/views/components/forms/button.blade.php index 9da3a21525..bd22ecc887 100644 --- a/resources/views/components/forms/button.blade.php +++ b/resources/views/components/forms/button.blade.php @@ -81,7 +81,7 @@ @if ($authDisabled || filled($tooltip)) {{-- teleported so a dropdown or other stacking context cannot hide or move it --}}