fix(ui): respect sidebar toggle and show application domains

Honor an explicit sidebarCollapsed preference instead of always auto-collapsing with a second nav. Show the primary domain on the application access card, use a persisted GitLab name in the page title after delete, and shrink the compose view switcher.
This commit is contained in:
Andras Bacsai 2026-09-10 08:36:15 +02:00
parent 424dbd36ff
commit fbb569a4b3
8 changed files with 75 additions and 24 deletions

View file

@ -9,6 +9,7 @@
@auth
<div x-data="{
open: false,
hasSidebarPreference: localStorage.getItem('sidebarCollapsed') !== null,
userCollapsed: localStorage.getItem('sidebarCollapsed') === 'true',
autoCollapse: localStorage.getItem('sidebarAutoCollapse') !== 'false',
hasSecondBar: false,
@ -25,7 +26,7 @@
},
targetCollapsed() {
this.hasSecondBar = !!document.querySelector('.application-settings-navigation');
return this.userCollapsed || (this.autoCollapse && this.hasSecondBar);
return this.hasSidebarPreference ? this.userCollapsed : (this.autoCollapse && this.hasSecondBar);
},
applyCollapsed(animate) {
const target = this.targetCollapsed();
@ -41,6 +42,7 @@
},
toggleSidebar() {
this.collapsed = !this.collapsed;
this.hasSidebarPreference = true;
this.userCollapsed = this.collapsed;
localStorage.setItem('sidebarCollapsed', this.userCollapsed);
},

View file

@ -32,6 +32,7 @@
<h3 class="mb-3 text-sm font-semibold text-black dark:text-fg">Public access</h3>
@php
$domainCount = 0;
$primaryDomain = null;
if ($buildPack === 'dockercompose') {
$composeDomains = $application->docker_compose_domains
? json_decode($application->docker_compose_domains, true)
@ -41,35 +42,57 @@
$domainString = data_get($serviceDomain, 'domain');
if (filled($domainString)) {
$domainCount += countDomains($domainString);
$primaryDomain ??= collect(explode(',', $domainString))
->map(fn ($domain) => trim($domain))
->first(fn ($domain) => filled($domain));
}
}
}
} elseif (filled($fqdn)) {
$domainCount = countDomains($fqdn);
$primaryDomain = collect(explode(',', $fqdn))
->map(fn ($domain) => trim($domain))
->first(fn ($domain) => filled($domain));
}
$additionalDomainCount = max(0, $domainCount - 1);
@endphp
<div class="flex items-center gap-3 rounded-lg border border-neutral-200 bg-neutral-50/60 px-4 py-3 dark:border-white/[0.07] dark:bg-white/[0.05]">
<div class="flex min-w-0 flex-1 items-center gap-3">
@php
$applicationDomainsUrl = route('project.application.domains', [
'project_uuid' => $application->environment->project->uuid,
'environment_uuid' => $application->environment->uuid,
'application_uuid' => $application->uuid,
]);
@endphp
<div class="group relative flex items-center gap-3 rounded-lg border border-neutral-200 bg-neutral-50/60 px-4 py-3 transition-colors hover:bg-neutral-100 focus-within:ring-2 focus-within:ring-coollabs/40 dark:border-white/[0.07] dark:bg-white/[0.05] dark:hover:bg-white/[0.08] dark:focus-within:ring-warning/40">
<a class="flex min-w-0 flex-1 items-center gap-3 after:absolute after:inset-0 after:content-[''] focus-visible:outline-none"
aria-label="{{ $domainCount > 0 ? 'Manage application domains' : 'Add an application domain' }}"
href="{{ $applicationDomainsUrl }}" {{ wireNavigate() }}>
<div class="flex size-9 shrink-0 items-center justify-center rounded-md bg-neutral-200/70 text-neutral-600 dark:bg-white/[0.07] dark:text-fg-dim">
<x-reicon name="globe" class="size-4" />
</div>
<div class="min-w-0">
<p class="text-sm font-medium text-black dark:text-fg">
{{ $domainCount }} configured {{ Str::plural('domain', $domainCount) }}
@if ($primaryDomain)
<span class="block truncate">{{ $primaryDomain }}</span>
@else
No public domain configured
@endif
</p>
<p class="text-xs text-neutral-500 dark:text-fg-dim">
Domains, DNS checks, and redirect settings
@if ($additionalDomainCount > 0)
+{{ $additionalDomainCount }} more {{ Str::plural('domain', $additionalDomainCount) }}
@elseif ($domainCount === 0)
Make this application available from a URL
@else
Manage DNS checks and redirect settings
@endif
</p>
</div>
</div>
<a class="icon-button ml-auto shrink-0" title="Manage domains"
aria-label="Manage domains"
href="{{ route('project.application.domains', [
'project_uuid' => $application->environment->project->uuid,
'environment_uuid' => $application->environment->uuid,
'application_uuid' => $application->uuid,
]) }}" {{ wireNavigate() }}>
<x-reicon name="settings" class="size-4" />
</a>
<a class="button relative z-10 ml-auto shrink-0" aria-label="{{ $domainCount > 0 ? 'Manage application domains' : 'Add an application domain' }}"
href="{{ $applicationDomainsUrl }}" {{ wireNavigate() }}>
{{ $domainCount > 0 ? 'Manage domains' : 'Add domain' }}
<x-reicon name="arrow-right" class="size-4" />
</a>
</div>
</section>

View file

@ -117,7 +117,7 @@ class="grid grid-cols-2 gap-0.5 border-y border-neutral-200 py-3 sm:grid-cols-3
</div>
<div class="flex w-full items-center justify-between gap-2 sm:w-auto sm:justify-start">
<div
class="flex h-9 items-center rounded-lg border border-neutral-200 bg-white p-0.5 dark:border-white/[0.08] dark:bg-white/[0.06]">
class="flex h-8 items-center rounded-lg border border-neutral-200 bg-white dark:border-white/[0.08] dark:bg-white/[0.06]">
<button type="button" x-on:click="setViewMode('table')"
class="flex size-7.5 items-center justify-center rounded-md transition-colors"
:class="viewMode === 'table'

View file

@ -1,6 +1,6 @@
<div>
<x-slot:title>
{{ $gitlab_app->name ?: 'GitLab App' }} | Sources | Coolify
{{ $name ?: 'GitLab App' }} | Sources | Coolify
</x-slot>
@if ($isConnected)

View file

@ -10,13 +10,18 @@
expect($generalSettings)
->toContain('id="access-section" title="Access"')
->toContain('<h3 class="mb-3 text-sm font-semibold text-black dark:text-fg">Public access</h3>')
->toContain("Str::plural('domain', \$domainCount)")
->toContain('Domains, DNS checks, and redirect settings')
->toContain('class="flex items-center gap-3 rounded-lg')
->toContain('class="icon-button ml-auto shrink-0"')
->toContain('aria-label="Manage domains"')
->toContain('<x-reicon name="settings" class="size-4" />')
->not->toContain('<x-reicon name="arrow-right" class="size-3.5" />')
->toContain('$primaryDomain = null;')
->toContain('$additionalDomainCount = max(0, $domainCount - 1);')
->toContain("aria-label=\"{{ \$domainCount > 0 ? 'Manage application domains' : 'Add an application domain' }}\"")
->toContain('{{ $primaryDomain }}')
->toContain("+{{ \$additionalDomainCount }} more {{ Str::plural('domain', \$additionalDomainCount) }}")
->toContain('No public domain configured')
->toContain('Make this application available from a URL')
->toContain("{{ \$domainCount > 0 ? 'Manage domains' : 'Add domain' }}")
->toContain('class="button relative z-10 ml-auto shrink-0"')
->toContain('aria-label="{{ $domainCount > 0 ? \'Manage application domains\' : \'Add an application domain\' }}"')
->toContain('<x-reicon name="arrow-right" class="size-4" />')
->not->toContain('<x-reicon name="settings" class="size-4" />')
->toContain('<livewire:project.application.internal-access')
->not->toContain('wire:init="loadCurrentInternalHostname"')
->and($internalAccessSettings)

View file

@ -57,6 +57,23 @@
->assertSet('apiUrl', 'https://gitlab.example.com/api/v4');
});
test('redirects without rendering an error after the gitlab app is deleted', function () {
Livewire::withQueryParams(['gitlab_app_uuid' => $this->gitlabApp->uuid])
->test(Change::class)
->call('delete')
->assertRedirect(route('source.all'));
$this->assertModelMissing($this->gitlabApp);
});
test('uses the persisted name for the title during a post-delete update', function () {
$view = file_get_contents(resource_path('views/livewire/source/gitlab/change.blade.php'));
expect($view)
->toContain("{{ \$name ?: 'GitLab App' }} | Sources | Coolify")
->not->toContain("{{ \$gitlab_app->name ?: 'GitLab App' }} | Sources | Coolify");
});
test('saves and reloads the application secret after refresh', function () {
Livewire::withQueryParams(['gitlab_app_uuid' => $this->gitlabApp->uuid])
->test(Change::class)

View file

@ -12,6 +12,8 @@
->toContain('aria-label="Grid view"')
->toContain('mb-3 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between')
->toContain('flex w-full items-center justify-between gap-2 sm:w-auto sm:justify-start')
->toContain('flex h-8 items-center rounded-lg border border-neutral-200 bg-white')
->not->toContain('flex h-9 items-center rounded-lg border border-neutral-200 bg-white')
->toContain("localStorage.setItem('service-compose-resources-view', mode)")
->not->toContain('>Sort</button>')
->and($resourceCard)

View file

@ -27,8 +27,10 @@
$layout = file_get_contents(resource_path('views/layouts/app.blade.php'));
expect($layout)
->toContain("hasSidebarPreference: localStorage.getItem('sidebarCollapsed') !== null")
->toContain("userCollapsed: localStorage.getItem('sidebarCollapsed') === 'true'")
->toContain('return this.userCollapsed || (this.autoCollapse && this.hasSecondBar);')
->toContain('return this.hasSidebarPreference ? this.userCollapsed : (this.autoCollapse && this.hasSecondBar);')
->toContain('this.hasSidebarPreference = true;')
->toContain('this.userCollapsed = this.collapsed;')
->toContain("localStorage.setItem('sidebarCollapsed', this.userCollapsed);")
->toContain('sidebarReady: false')