From ada3ea354a8d06f25c9aed092b411e34649cc7fa Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 7 Aug 2026 16:22:11 +0200 Subject: [PATCH] feat(ui): add collapsible advanced settings and enlarge compose modal Extract a shared forms collapsible for advanced SSH/build settings across boarding, server creation, and application general. Support larger modals with header actions, close the compose editor after save via compose-save-finished, and cover the layout in tests. --- app/Livewire/Project/Service/EditCompose.php | 1 - app/Livewire/Project/Service/StackForm.php | 1 + .../components/forms/collapsible.blade.php | 22 ++++++ .../views/components/modal-input.blade.php | 12 ++- .../views/livewire/boarding/index.blade.php | 46 ++++------- .../project/application/general.blade.php | 57 +++++++------- .../project/service/edit-compose.blade.php | 76 ++++++------------- .../project/service/stack-form.blade.php | 21 ++++- .../views/livewire/server/new/by-ip.blade.php | 30 +++----- .../Feature/ApplicationGeneralLayoutTest.php | 16 +++- tests/Feature/ComposeEditorLayoutTest.php | 64 ++++++++++++++++ .../ServerCreationBuildRoleLayoutTest.php | 5 +- 12 files changed, 207 insertions(+), 144 deletions(-) create mode 100644 resources/views/components/forms/collapsible.blade.php create mode 100644 tests/Feature/ComposeEditorLayoutTest.php diff --git a/app/Livewire/Project/Service/EditCompose.php b/app/Livewire/Project/Service/EditCompose.php index 0f5c739b1..46a8ecdc8 100644 --- a/app/Livewire/Project/Service/EditCompose.php +++ b/app/Livewire/Project/Service/EditCompose.php @@ -77,7 +77,6 @@ class EditCompose extends Component { try { $this->authorize('update', $this->service); - $this->dispatch('info', 'Saving new docker compose...'); $this->dispatch('saveCompose', $this->dockerComposeRaw); $this->dispatch('refreshStorages'); } catch (\Throwable $e) { diff --git a/app/Livewire/Project/Service/StackForm.php b/app/Livewire/Project/Service/StackForm.php index 86d5a57c1..92829edd6 100644 --- a/app/Livewire/Project/Service/StackForm.php +++ b/app/Livewire/Project/Service/StackForm.php @@ -140,6 +140,7 @@ class StackForm extends Component { $this->dockerComposeRaw = $raw; $this->submit(notify: true); + $this->dispatch('compose-save-finished'); } public function instantSave() diff --git a/resources/views/components/forms/collapsible.blade.php b/resources/views/components/forms/collapsible.blade.php new file mode 100644 index 000000000..3c4c7a134 --- /dev/null +++ b/resources/views/components/forms/collapsible.blade.php @@ -0,0 +1,22 @@ +@props([ + 'title' => 'Advanced settings', + 'contentClass' => '', +]) + +
class(['flex flex-col gap-4']) }}> + + +
+ {{ $slot }} +
+
diff --git a/resources/views/components/modal-input.blade.php b/resources/views/components/modal-input.blade.php index e3c836595..b80e3d649 100644 --- a/resources/views/components/modal-input.blade.php +++ b/resources/views/components/modal-input.blade.php @@ -12,6 +12,7 @@ // Optional Livewire bool property to entangle open state (survives Livewire re-renders). 'wireOpen' => null, 'contentClicks' => true, + 'isLarge' => false, ]) @php @@ -54,10 +55,19 @@ x-transition:leave="ease-in duration-100" x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100" x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95" - class="application-settings-form application-settings-section relative max-h-[calc(100dvh-2rem)] w-full lg:w-auto lg:min-w-2xl lg:max-w-4xl" + @class([ + 'application-settings-form application-settings-section relative flex max-h-[calc(100dvh-2rem)] w-full flex-col overflow-hidden', + 'lg:w-[95vw]! lg:max-w-7xl!' => $isLarge, + 'lg:w-auto lg:min-w-2xl lg:max-w-4xl' => ! $isLarge, + ]) style="box-shadow: 0 0 0 1px var(--coollabs-hairline), var(--shadow-modal)">

{{ $title }}

+ @isset($headerActions) +
+ {{ $headerActions }} +
+ @endisset -
- -
- -

- Non-root user support is experimental. - Learn - more -

-
+ + +
+ +

+ Non-root user support is experimental. + Learn + more +

-
+ Validate Connection diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 213800c11..6445a155c 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -157,35 +157,32 @@ helper="Git repository (based on the base directory settings) will be copied to the deployment directory." x-bind:disabled="shouldDisable()" /> -
The following commands are for advanced use cases. - Only - modify them if you - know what are - you doing.
-
- - +
+
+ + +
+ @if ($this->dockerComposeCustomBuildCommand) +
+ +
+ @endif + @if ($this->dockerComposeCustomStartCommand) +
+ +
+ @endif
- @if ($this->dockerComposeCustomBuildCommand) -
- -
- @endif - @if ($this->dockerComposeCustomStartCommand) -
- -
- @endif @if ($this->application->is_github_based() && !$this->application->is_public_repository())
@endif @if ($buildPack === 'dockercompose') -
-
+
+

Docker Compose

-
Volume names are updated upon save. The service UUID will be added as a prefix to all volumes, to - prevent - name collision.
To see the actual volume names, check the Deployable Compose file, or go to Storage - menu.
+
+ + Volume names are prefixed with the service UUID when you save to prevent collisions. + -
+
- - +
- - +
- - +
-
-
- - -
+
+ +
-
-
- Show Deployable Compose -
-
- Show Source - Compose -
-
- @if (blank($service->service_type)) - - Validate - - @endif - - Save - -
-
\ No newline at end of file +
diff --git a/resources/views/livewire/project/service/stack-form.blade.php b/resources/views/livewire/project/service/stack-form.blade.php index 0dc8638e7..65eb0e3fb 100644 --- a/resources/views/livewire/project/service/stack-form.blade.php +++ b/resources/views/livewire/project/service/stack-form.blade.php @@ -9,7 +9,26 @@ @endif @can('update', $service) - + + +
+ + + + + @if (blank($service->service_type)) + Validate + @endif + + + Save changes + +
+
@endcan diff --git a/resources/views/livewire/server/new/by-ip.blade.php b/resources/views/livewire/server/new/by-ip.blade.php index 0539ab05a..51b93e99c 100644 --- a/resources/views/livewire/server/new/by-ip.blade.php +++ b/resources/views/livewire/server/new/by-ip.blade.php @@ -76,27 +76,17 @@
-
- - -
-
- - -
- + +
+ +
-
+ + @endif diff --git a/tests/Feature/ApplicationGeneralLayoutTest.php b/tests/Feature/ApplicationGeneralLayoutTest.php index dae9fbc00..a28d91439 100644 --- a/tests/Feature/ApplicationGeneralLayoutTest.php +++ b/tests/Feature/ApplicationGeneralLayoutTest.php @@ -16,5 +16,19 @@ test('compose actions are grouped with the application details header', function test('docker compose heading separates its title and action', function () { $view = file_get_contents(resource_path('views/livewire/project/application/general.blade.php')); - expect($view)->toContain('
'); + expect($view) + ->toContain('
') + ->toContain('
'); +}); + +test('onboarding uses the reusable advanced settings component', function () { + $view = file_get_contents(resource_path('views/livewire/project/application/general.blade.php')); + $onboarding = file_get_contents(resource_path('views/livewire/boarding/index.blade.php')); + + expect($view) + ->toContain('id="dockerComposeCustomBuildCommand"') + ->not->toContain('') + ->not->toContain('The following commands are for advanced use cases.') + ->and($onboarding) + ->toContain('toContain('title="Docker Compose"') + ->toContain(':isLarge="true"') + ->toContain('') + ->toContain("\$dispatch('compose-preview-toggle')") + ->toContain("\$dispatch('compose-save')") + ->toContain('@compose-save-finished.window="saving = false"') + ->toContain('') + ->toContain('x-bind:disabled="saving"') + ->not->toContain('name="refresh"') + ->not->toContain("saving ? 'Saving...' : 'Save changes'") + ->not->toContain(' :disabled="saving"') + ->toContain('Preview generated Compose') + ->toContain('Back to source Compose') + ->toContain('Save changes') + ->toContain('isHighlighted'); + + expect($modal)->toContain('lg:w-[95vw]! lg:max-w-7xl!'); +}); + +it('renders the compose editor with clear guidance settings and actions', function () { + $view = file_get_contents(resource_path('views/livewire/project/service/edit-compose.blade.php')); + + expect($view) + ->toContain('') + ->not->toContain('View the final names') + ->toContain('Use plain-text editor') + ->toContain('min-h-[24rem]') + ->toContain('@compose-preview-toggle.window') + ->toContain('@compose-save.window') + ->not->toContain("finally(() => \$dispatch('compose-save-finished'))") + ->not->toContain('sticky bottom-0') + ->not->toContain('Cancel') + ->not->toContain('Show Normal Textarea') + ->not->toContain('Show Deployable Compose'); +}); + +it('keeps the save button loading until the parent compose save finishes', function () { + $component = file_get_contents(app_path('Livewire/Project/Service/StackForm.php')); + + expect($component) + ->toContain('public function saveCompose($raw)') + ->toContain("\$this->dispatch('compose-save-finished')"); +}); + +it('does not show a saving notification for compose changes', function () { + $component = file_get_contents(app_path('Livewire/Project/Service/EditCompose.php')); + + expect($component)->not->toContain("\$this->dispatch('info', 'Saving new docker compose...')"); +}); + +it('keeps the saving state as an Alpine button binding', function () { + $html = Blade::render('Save changes'); + + expect($html)->toContain('x-bind:disabled="saving"'); +}); diff --git a/tests/Feature/ServerCreationBuildRoleLayoutTest.php b/tests/Feature/ServerCreationBuildRoleLayoutTest.php index d459ff0bb..af3271ae4 100644 --- a/tests/Feature/ServerCreationBuildRoleLayoutTest.php +++ b/tests/Feature/ServerCreationBuildRoleLayoutTest.php @@ -5,10 +5,7 @@ test('server creation keeps private key actions together and advanced options co expect($view) ->toContain('class="flex items-end gap-3"') - ->toContain('x-data="{ advancedOpen: false }"') - ->toContain('x-show="advancedOpen" x-cloak') - ->toContain('Advanced settings') - ->toContain('class="flex w-full cursor-pointer items-center justify-between gap-3') + ->toContain('toContain('label="Use as a dedicated build server"') ->toContain('helper="Build servers compile applications but do not host deployments. Enabling this makes the server build-only."'); });