From be2a6d6d1ea62bf287d57b047b2ee5b14140aa3e Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 10 Aug 2026 15:15:48 +0200 Subject: [PATCH] feat(domains): edit compose redirect direction in domain modal --- app/Livewire/Project/Application/Domains.php | 18 +++++- .../project/application/domains.blade.php | 62 +++++++++---------- .../application/partials/domain-row.blade.php | 1 + tests/Feature/ApplicationDomainsTest.php | 30 ++++++++- 4 files changed, 76 insertions(+), 35 deletions(-) diff --git a/app/Livewire/Project/Application/Domains.php b/app/Livewire/Project/Application/Domains.php index fa373f8e0..07e980808 100644 --- a/app/Livewire/Project/Application/Domains.php +++ b/app/Livewire/Project/Application/Domains.php @@ -16,6 +16,8 @@ class Domains extends Component use AuthorizesRequests; use InteractsWithCloudflareDomainConnect; + protected bool $notifyRedirectUpdate = true; + public Application $application; public string $redirect = 'both'; @@ -41,6 +43,8 @@ class Domains extends Component public string $editingIndexing = 'index'; + public string $editingDirection = 'both'; + public ?string $editingService = null; /** @var array */ @@ -100,6 +104,7 @@ class Domains extends Component 'newDomain' => ValidationPatterns::applicationDomainRules(), 'editingDomain' => ValidationPatterns::applicationDomainRules(), 'editingIndexing' => 'string|in:index,noindex', + 'editingDirection' => 'string|in:both,www,non-www', 'redirect' => 'string|required|in:both,www,non-www', 'serviceRedirects' => 'array', 'serviceRedirects.*' => 'string|in:both,www,non-www', @@ -907,6 +912,7 @@ class Domains extends Component $this->editingIndex = $index; $this->editingDomain = $this->domainRows[$index]['url']; $this->editingService = $this->domainRows[$index]['service']; + $this->editingDirection = $this->serviceRedirectFor($this->editingService); $this->editingIndexing = $this->application->isDomainNoindexed($this->editingDomain) ? 'noindex' : 'index'; $this->resetEditDomainDnsGate(); $this->resetErrorBag('editingDomain'); @@ -990,6 +996,7 @@ class Domains extends Component $this->editingIndex = null; $this->editingDomain = ''; $this->editingService = null; + $this->editingDirection = 'both'; $this->editingIndexing = 'index'; $this->resetEditDomainDnsGate(); $this->resetErrorBag('editingDomain'); @@ -1066,6 +1073,13 @@ class Domains extends Component $this->application->save(); $this->resetDefaultLabels(); + if ($this->isCompose && filled($service) && $this->editingDirection !== $this->savedRedirectForService($service)) { + $this->serviceRedirects[$this->serviceRedirectWireKey($service)] = $this->editingDirection; + $this->notifyRedirectUpdate = false; + $this->setServiceRedirect($service); + $this->notifyRedirectUpdate = true; + } + $this->forceSaveDomains = false; $this->pendingAction = null; $this->cancelEdit(); @@ -1303,7 +1317,9 @@ class Domains extends Component $this->pendingRedirectService = null; $this->forceSaveDomains = false; $this->resetDefaultLabels(); - $this->dispatch('success', "Redirect updated for {$serviceName}."); + if ($this->notifyRedirectUpdate) { + $this->dispatch('success', "Redirect updated for {$serviceName}."); + } $this->refreshDomains(); $this->checkUrlsDns($addedDomains, $serviceName); $this->pruneDomainDnsStatusesToCurrentDomains(); diff --git a/resources/views/livewire/project/application/domains.blade.php b/resources/views/livewire/project/application/domains.blade.php index 777566995..2e8a84760 100644 --- a/resources/views/livewire/project/application/domains.blade.php +++ b/resources/views/livewire/project/application/domains.blade.php @@ -18,12 +18,14 @@ localEditingIndex: @js($editingIndex), localEditingDomain: @js($editingDomain), localEditingService: @js($editingService), + localDirection: 'both', localIndexing: 'index', - openEditDomain(index, url, service, indexing) { + openEditDomain(index, url, service, indexing, direction) { this.localEditingIndex = index; this.localEditingDomain = url; this.localEditingService = service; this.editingServiceLabel = service || ''; + this.localDirection = direction || 'both'; this.localIndexing = indexing || 'index'; this.modalOpen = true; this.$nextTick(() => document.getElementById('editingDomainLocal')?.focus?.()); @@ -40,6 +42,7 @@ $wire.editingIndex = this.localEditingIndex; $wire.editingDomain = this.localEditingDomain; $wire.editingService = this.localEditingService; + $wire.editingDirection = this.localDirection; $wire.editingIndexing = this.localIndexing; $wire.showEditDomainModal = true; }, @@ -50,7 +53,7 @@ return values.some((value) => this.matchesDomainSearch(value)); }, }" - @open-edit-domain.window="openEditDomain($event.detail.index, $event.detail.url, $event.detail.service, $event.detail.indexing)" + @open-edit-domain.window="openEditDomain($event.detail.index, $event.detail.url, $event.detail.service, $event.detail.indexing, $event.detail.direction)" @edit-domain-saved.window="closeEditDomain()"> @can('update', $application) @@ -110,10 +113,6 @@ @endcan @endif - @elseif (! $labelsAreWritable && count($composeServices) > 0 && $composeDomainGroups->isNotEmpty()) -

- Per-service www/non-www redirects are available next to each service group below. -

@endif
@@ -231,44 +230,30 @@ ->map(fn ($serviceName) => $serviceName.' '.$grouped->get($serviceName, collect())->pluck('url')->implode(' ')) ->values(); @endphp -
+
@foreach ($serviceOrder as $serviceName) @php $rows = $grouped->get($serviceName, collect()); $redirectWireKey = $this->serviceRedirectWireKey($serviceName); - $redirectLabel = match ($serviceRedirects[$redirectWireKey] ?? 'both') { - 'www' => 'Redirect to www', - 'non-www' => 'Redirect to non-www', - default => 'Allow both', - }; @endphp
-
+
{{ $serviceName }} - @unless ($labelsAreWritable) - @can('update', $application) -
- -
- @else - {{ $redirectLabel }} - @endcan - @endunless
+
+ Domain + DNS + Last checked + +
@foreach ($rows as $row) @php $index = collect($domainRows)->search( @@ -283,6 +268,7 @@ 'application' => $application, 'labelsAreWritable' => $labelsAreWritable, 'isCompose' => false, + 'domainDirection' => $serviceRedirects[$redirectWireKey] ?? 'both', ]) @endforeach
@@ -372,11 +358,21 @@
@unless ($labelsAreWritable) - +
+ @if ($isCompose) + + @endif + +
@endunless @if ($editDomainDnsFailed) diff --git a/resources/views/livewire/project/application/partials/domain-row.blade.php b/resources/views/livewire/project/application/partials/domain-row.blade.php index 785a27a9e..e217d6bfc 100644 --- a/resources/views/livewire/project/application/partials/domain-row.blade.php +++ b/resources/views/livewire/project/application/partials/domain-row.blade.php @@ -108,6 +108,7 @@ url: @js($row['url']), service: @js($row['service'] ?? null), indexing: @js($application->isDomainNoindexed($row['url']) ? 'noindex' : 'index'), + direction: @js($domainDirection ?? $redirect), })" class="icon-button shrink-0" title="Edit domain" aria-label="Edit domain"> diff --git a/tests/Feature/ApplicationDomainsTest.php b/tests/Feature/ApplicationDomainsTest.php index c4fe9a873..96de79b52 100644 --- a/tests/Feature/ApplicationDomainsTest.php +++ b/tests/Feature/ApplicationDomainsTest.php @@ -1219,11 +1219,39 @@ it('uses the compact service domains layout for compose applications', function expect($view) ->toContain('application-compose-domain-group-{{ $redirectWireKey }}') ->toContain('class="application-settings-section-body mt-1 scroll-mt-28') - ->toContain('htmlId="application-compose-domain-redirect-{{ $redirectWireKey }}"') + ->toContain('bg-neutral-50 px-4 py-3 dark:border-white/10 dark:bg-white/[0.04]') + ->toContain('class="data-table-header domains-table-grid"') + ->toContain('id="edit-domain-direction"') + ->not->toContain('htmlId="application-compose-domain-redirect-{{ $redirectWireKey }}"') ->not->toContain('aria-label="Redirect direction for {{ $serviceName }}"') ->not->toContain('title="No domains for this service"'); }); +it('updates a compose service redirect from the edit domain modal', function () { + $this->application->update([ + 'build_pack' => 'dockercompose', + 'fqdn' => null, + 'docker_compose_raw' => "services:\n web:\n image: nginx:alpine\n", + 'docker_compose_domains' => json_encode([ + 'web' => ['domain' => 'https://web.example.com', 'redirect' => 'both'], + ]), + ]); + + Livewire::test(Domains::class, ['application' => $this->application->fresh()]) + ->set('isCompose', true) + ->set('composeServices', ['web']) + ->call('startEdit', 0) + ->assertSet('editingDirection', 'both') + ->set('editingDirection', 'www') + ->call('updateDomain') + ->assertDispatched('success'); + + $domains = json_decode($this->application->fresh()->docker_compose_domains, true); + + expect(data_get($domains, 'web.redirect'))->toBe('www') + ->and(data_get($domains, 'web.domain'))->toContain('https://www.web.example.com'); +}); + it('provides client-side search for compose service domains', function () { $view = file_get_contents(resource_path('views/livewire/project/application/domains.blade.php'));