diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 0e1a4c47e..34283cd47 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -55,10 +55,6 @@ class General extends Component public ?string $customNetworkAliases = null; - public ?string $currentInternalHostname = null; - - public bool $currentInternalHostnameLoaded = false; - public ?string $dockerfile = null; public ?string $dockerfileLocation = null; @@ -452,32 +448,6 @@ public function syncData(bool $toModel = false): void } } - public function loadCurrentInternalHostname(): void - { - if ($this->application->build_pack === 'dockercompose') { - $this->currentInternalHostnameLoaded = true; - - return; - } - - try { - $containers = getCurrentApplicationContainerStatus( - $this->application->destination->server, - $this->application->id, - 0 - ); - $currentContainer = $containers->first( - fn ($container) => data_get($container, 'State') === 'running' - ) ?? $containers->first(); - - $this->currentInternalHostname = data_get($currentContainer, 'Names'); - } catch (\Throwable) { - $this->currentInternalHostname = null; - } finally { - $this->currentInternalHostnameLoaded = true; - } - } - public function setSiteType(): void { $this->isStatic = $this->siteType !== 'dynamic'; diff --git a/app/Livewire/Project/Application/InternalAccess.php b/app/Livewire/Project/Application/InternalAccess.php new file mode 100644 index 000000000..827cafd25 --- /dev/null +++ b/app/Livewire/Project/Application/InternalAccess.php @@ -0,0 +1,41 @@ +application->destination->server, + $this->application->id, + 0 + ); + $currentContainer = $containers->first( + fn ($container) => data_get($container, 'State') === 'running' + ) ?? $containers->first(); + + $this->currentInternalHostname = data_get($currentContainer, 'Names'); + } catch (\Throwable) { + $this->currentInternalHostname = null; + } finally { + $this->currentInternalHostnameLoaded = true; + } + } + + public function render(): View + { + return view('livewire.project.application.internal-access'); + } +} diff --git a/app/Livewire/Project/Application/Status.php b/app/Livewire/Project/Application/Status.php new file mode 100644 index 000000000..ec17b8679 --- /dev/null +++ b/app/Livewire/Project/Application/Status.php @@ -0,0 +1,31 @@ +user()->currentTeam()->id; + + return [ + "echo-private:team.{$teamId},ServiceStatusChanged" => 'refreshStatus', + ]; + } + + public function refreshStatus(): void + { + $this->application->refresh(); + } + + public function render(): View + { + return view('livewire.project.application.status'); + } +} diff --git a/resources/views/components/application/configuration-sidebar.blade.php b/resources/views/components/application/configuration-sidebar.blade.php index b480a69ea..fe70c8cdb 100644 --- a/resources/views/components/application/configuration-sidebar.blade.php +++ b/resources/views/components/application/configuration-sidebar.blade.php @@ -175,8 +175,7 @@ $pageSections = [ 'project.application.configuration' => array_values(array_filter([ ['id' => 'application-details-section', 'label' => 'Application details'], - ['id' => 'public-access-section', 'label' => 'Public access'], - $isComposeApp ? null : ['id' => 'internal-access-section', 'label' => 'Internal access'], + ['id' => 'access-section', 'label' => 'Access'], ['id' => 'build-pipeline-section', 'label' => 'Build pipeline'], $isComposeApp ? null : ['id' => 'container-image-section', 'label' => 'Container image'], $isComposeApp ? null : ['id' => 'networking-section', 'label' => 'Networking'], diff --git a/resources/views/components/popup-small.blade.php b/resources/views/components/popup-small.blade.php index 78e6e468e..3dc031eab 100644 --- a/resources/views/components/popup-small.blade.php +++ b/resources/views/components/popup-small.blade.php @@ -6,7 +6,6 @@ 'compactStoragePrefix' => null, ]) - diff --git a/resources/views/components/top-breadcrumb.blade.php b/resources/views/components/top-breadcrumb.blade.php index 3c22d2cbf..c521d3b94 100644 --- a/resources/views/components/top-breadcrumb.blade.php +++ b/resources/views/components/top-breadcrumb.blade.php @@ -244,24 +244,11 @@ class="listbox-option {{ $env->uuid === $currentEnvironment->uuid ? 'bg-neutral- @endif @if ($currentApplication) - @php - $applicationStatus = str($currentApplication->status ?? 'exited'); - [$statusDotClass, $statusLabel] = match (true) { - $applicationStatus->startsWith('running') => ['bg-[#3fb950]', 'Running'], - $applicationStatus->startsWith('degraded') => ['bg-orange-400', 'Degraded'], - $applicationStatus->startsWith('restarting'), - $applicationStatus->startsWith('starting') => ['bg-warning', 'Restarting'], - default => ['bg-neutral-400 dark:bg-fg-faint', 'Stopped'], - }; - @endphp / {{ $currentApplication->name }} - - - {{ $statusLabel }} - + @endif diff --git a/resources/views/components/unsaved-bar.blade.php b/resources/views/components/unsaved-bar.blade.php index 993acd996..dc7f90949 100644 --- a/resources/views/components/unsaved-bar.blade.php +++ b/resources/views/components/unsaved-bar.blade.php @@ -21,6 +21,19 @@ Desktop: compact single-row centered pill. --}}
{{ $label }} @@ -30,8 +43,10 @@ class="h-8 rounded-lg bg-white/[0.07] px-3.5 text-[13px] font-medium text-fg tra Reset
diff --git a/resources/views/livewire/global-search.blade.php b/resources/views/livewire/global-search.blade.php index 3cf3ead6a..1ec7d5491 100644 --- a/resources/views/livewire/global-search.blade.php +++ b/resources/views/livewire/global-search.blade.php @@ -283,9 +283,8 @@ }"> - + {{-- Create resource modals: always mounted with stable Livewire keys so Alpine teleport can open them. Do not loop @livewire() — Livewire diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 904e75694..127595927 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -26,7 +26,11 @@ - + +
$buildPack !== 'dockercompose', + ])> +

Public access

@php $domainCount = 0; if ($buildPack === 'dockercompose') { @@ -54,15 +58,6 @@ @else {{ $domainCount }} domains set. @endif - Manage domains, DNS checks, and www redirects - - on the Domains page - .

-
- @if ($currentInternalHostname) - - @else -
- - -
- @endif - - - -
-
-

- Internal hostnames are only reachable by resources connected to this Docker network. -

- -
- + @endif + @if (!$application->dockerfile && $application->build_pack !== 'dockerimage') diff --git a/resources/views/livewire/project/application/internal-access.blade.php b/resources/views/livewire/project/application/internal-access.blade.php new file mode 100644 index 000000000..8ab1442ba --- /dev/null +++ b/resources/views/livewire/project/application/internal-access.blade.php @@ -0,0 +1,41 @@ +@php + $networkAliases = str($application->custom_network_aliases ?? '') + ->explode(',') + ->map(fn ($alias) => trim($alias)) + ->filter() + ->values(); + $exposedPorts = str($application->ports_exposes ?? '') + ->explode(',') + ->map(fn ($port) => trim($port)) + ->filter() + ->implode(', '); +@endphp + +
+

Internal access

+
+ @if ($currentInternalHostname) + + @else +
+ + +
+ @endif + + + +
+
+

+ Internal hostnames are only reachable by resources connected to this Docker network. +

+ +
+
diff --git a/resources/views/livewire/project/application/status.blade.php b/resources/views/livewire/project/application/status.blade.php new file mode 100644 index 000000000..64e7cdda4 --- /dev/null +++ b/resources/views/livewire/project/application/status.blade.php @@ -0,0 +1,17 @@ +@php + $applicationStatus = str($application->status ?? 'exited'); + [$statusDotClass, $statusLabel] = match (true) { + $applicationStatus->startsWith('running') => ['bg-[#3fb950]', 'Running'], + $applicationStatus->startsWith('degraded') => ['bg-orange-400', 'Degraded'], + $applicationStatus->startsWith('restarting'), + $applicationStatus->startsWith('starting') => ['bg-warning', 'Restarting'], + default => ['bg-neutral-400 dark:bg-fg-faint', 'Stopped'], + }; +@endphp + + + + {{ $statusLabel }} + diff --git a/tests/Feature/ApplicationHeadingActiveTabTest.php b/tests/Feature/ApplicationHeadingActiveTabTest.php index 3fd8eb33b..8394c60ac 100644 --- a/tests/Feature/ApplicationHeadingActiveTabTest.php +++ b/tests/Feature/ApplicationHeadingActiveTabTest.php @@ -1,6 +1,7 @@ assertSet('activeRouteName', 'project.application.webhooks'); }); +it('refreshes the breadcrumb application status after it changes', function () { + $this->actingAs($this->admin); + session(['currentTeam' => $this->team]); + + $component = Livewire::test(ApplicationStatus::class, ['application' => $this->application]) + ->assertSee('Running'); + + $this->application->update(['status' => 'exited']); + + $component + ->call('refreshStatus') + ->assertSee('Stopped') + ->assertDontSee('Running'); +}); + it('uses app-tab-active utility for resource heading active styles', function () { $utilities = file_get_contents(resource_path('css/utilities.css')); diff --git a/tests/Feature/ApplicationInternalAccessSectionTest.php b/tests/Feature/ApplicationInternalAccessSectionTest.php index c7e4b8b25..f0517d8c1 100644 --- a/tests/Feature/ApplicationInternalAccessSectionTest.php +++ b/tests/Feature/ApplicationInternalAccessSectionTest.php @@ -3,11 +3,19 @@ it('shows internal Docker access details in application general settings', function () { $generalSettings = file_get_contents(resource_path('views/livewire/project/application/general.blade.php')); $generalComponent = file_get_contents(app_path('Livewire/Project/Application/General.php')); + $internalAccessSettings = file_get_contents(resource_path('views/livewire/project/application/internal-access.blade.php')); + $internalAccessComponent = file_get_contents(app_path('Livewire/Project/Application/InternalAccess.php')); $configurationSidebar = file_get_contents(resource_path('views/components/application/configuration-sidebar.blade.php')); expect($generalSettings) + ->toContain('id="access-section" title="Access"') + ->toContain('

Public access

') + ->toContain('not->toContain('wire:init="loadCurrentInternalHostname"') + ->and($internalAccessSettings) ->toContain('id="internal-access-section"') - ->toContain('title="Internal access"') + ->toContain('

Internal access

') + ->not->toContain('toContain('Internal hostname') ->toContain('Docker network') ->toContain('Exposed ports') @@ -18,12 +26,14 @@ ->not->toContain('Changes with each deployment') ->toContain("window.scrollToSettingsSection?.('networking-section')") ->and($generalComponent) + ->not->toContain('public function loadCurrentInternalHostname(): void') + ->and($internalAccessComponent) ->toContain('public ?string $currentInternalHostname = null;') ->toContain('public function loadCurrentInternalHostname(): void') ->toContain('getCurrentApplicationContainerStatus(') ->toContain("data_get(\$currentContainer, 'Names')") ->and($configurationSidebar) - ->toContain("['id' => 'internal-access-section', 'label' => 'Internal access']"); + ->toContain("['id' => 'access-section', 'label' => 'Access']"); }); it('does not show the internal access section for Docker Compose applications', function () { @@ -33,5 +43,7 @@ expect($generalSettings) ->toContain("@if (\$buildPack !== 'dockercompose')") ->and($configurationSidebar) - ->toContain("\$isComposeApp ? null : ['id' => 'internal-access-section', 'label' => 'Internal access']"); + ->toContain("['id' => 'access-section', 'label' => 'Access']") + ->not->toContain("['id' => 'public-access-section', 'label' => 'Public access']") + ->not->toContain("['id' => 'internal-access-section', 'label' => 'Internal access']"); }); diff --git a/tests/Feature/GlobalSearchLoadingStateTest.php b/tests/Feature/GlobalSearchLoadingStateTest.php index 7430ef7ea..82a3237b6 100644 --- a/tests/Feature/GlobalSearchLoadingStateTest.php +++ b/tests/Feature/GlobalSearchLoadingStateTest.php @@ -12,3 +12,16 @@ ->toContain('runPaletteTransition') ->toContain('Loading…'); }); + +it('uses a single Alpine result renderer for every command palette result type', function () { + $view = file_get_contents(resource_path('views/livewire/global-search.blade.php')); + + expect($view) + ->not->toContain('Create mode (server-rendered path)') + ->not->toContain('!$wire.isCreateMode') + ->toContain("\n
not->toContain("\n