mirror of
https://github.com/tiennm99/coolify.git
synced 2026-08-20 02:27:57 +00:00
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.
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -140,6 +140,7 @@ class StackForm extends Component
|
||||
{
|
||||
$this->dockerComposeRaw = $raw;
|
||||
$this->submit(notify: true);
|
||||
$this->dispatch('compose-save-finished');
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
@props([
|
||||
'title' => 'Advanced settings',
|
||||
'contentClass' => '',
|
||||
])
|
||||
|
||||
<div x-data="{ open: false }" {{ $attributes->class(['flex flex-col gap-4']) }}>
|
||||
<button type="button" x-on:click="open = !open"
|
||||
class="flex items-center gap-2 text-left text-sm font-medium hover:underline" :aria-expanded="open">
|
||||
<svg class="size-4 transition-transform" x-bind:class="open && 'rotate-90'" viewBox="0 0 20 20"
|
||||
fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M7.21 14.77a.75.75 0 0 1 .02-1.06L11.168 10 7.23 6.29a.75.75 0 1 1 1.04-1.08l4.5 4.25a.75.75 0 0 1 0 1.08l-4.5 4.25a.75.75 0 0 1-1.06-.02Z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
{{ $title }}
|
||||
</button>
|
||||
|
||||
<div x-show="open" x-cloak
|
||||
class="rounded-lg border border-neutral-200 p-4 dark:border-coolgray-400 {{ $contentClass }}">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -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)">
|
||||
<header class="flex-nowrap!">
|
||||
<h3 class="min-w-0 flex-1 truncate">{{ $title }}</h3>
|
||||
@isset($headerActions)
|
||||
<div class="flex shrink-0 items-center gap-2">
|
||||
{{ $headerActions }}
|
||||
</div>
|
||||
@endisset
|
||||
<button type="button" @click="modalOpen=false"
|
||||
class="flex size-7 shrink-0 cursor-pointer items-center justify-center rounded-md text-neutral-500 outline-0 transition-colors hover:bg-neutral-100 hover:text-black focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-accent dark:text-fg-faint dark:hover:bg-white/[0.06] dark:hover:text-fg">
|
||||
<x-reicon name="x" class="size-4" />
|
||||
|
||||
@@ -421,39 +421,21 @@
|
||||
<x-forms.input placeholder="Optional: Note what this server hosts" label="Description"
|
||||
id="remoteServerDescription" wire:model="remoteServerDescription" />
|
||||
|
||||
<div x-data="{ showAdvanced: false }" class="flex flex-col gap-4">
|
||||
<button @click="showAdvanced = !showAdvanced" type="button"
|
||||
class="flex items-center gap-2 text-left text-sm font-medium hover:underline">
|
||||
<svg x-show="!showAdvanced" class="size-4" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd"
|
||||
d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<svg x-show="showAdvanced" class="size-4" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd"
|
||||
d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
Advanced Connection Settings
|
||||
</button>
|
||||
<div x-show="showAdvanced" x-cloak
|
||||
class="grid grid-cols-1 lg:grid-cols-2 gap-4 p-4 rounded-lg border border-neutral-200 dark:border-coolgray-400">
|
||||
<x-forms.input placeholder="Default: 22" label="SSH Port" type="number"
|
||||
id="remoteServerPort" wire:model="remoteServerPort" />
|
||||
<div>
|
||||
<x-forms.input placeholder="Default: root" label="SSH User" id="remoteServerUser"
|
||||
wire:model="remoteServerUser" />
|
||||
<p class="mt-1 text-xs dark:text-white text-black">
|
||||
Non-root user support is experimental.
|
||||
<a class="font-bold underline hover:text-coollabs" target="_blank"
|
||||
href="https://coolify.io/docs/knowledge-base/server/non-root-user">Learn
|
||||
more</a>
|
||||
</p>
|
||||
</div>
|
||||
<x-forms.collapsible title="Advanced Connection Settings"
|
||||
content-class="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
<x-forms.input placeholder="Default: 22" label="SSH Port" type="number"
|
||||
id="remoteServerPort" wire:model="remoteServerPort" />
|
||||
<div>
|
||||
<x-forms.input placeholder="Default: root" label="SSH User" id="remoteServerUser"
|
||||
wire:model="remoteServerUser" />
|
||||
<p class="mt-1 text-xs text-black dark:text-white">
|
||||
Non-root user support is experimental.
|
||||
<a class="font-bold underline hover:text-coollabs" target="_blank"
|
||||
href="https://coolify.io/docs/knowledge-base/server/non-root-user">Learn
|
||||
more</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</x-forms.collapsible>
|
||||
<x-forms.button type="submit" class="w-full lg:w-auto">Validate Connection</x-forms.button>
|
||||
</form>
|
||||
</x-slot:actions>
|
||||
|
||||
@@ -157,35 +157,32 @@
|
||||
helper="Git repository (based on the base directory settings) will be copied to the deployment directory."
|
||||
x-bind:disabled="shouldDisable()" />
|
||||
</div>
|
||||
<div class="pt-4">The following commands are for advanced use cases.
|
||||
Only
|
||||
modify them if you
|
||||
know what are
|
||||
you doing.</div>
|
||||
<div class="grid gap-4 lg:grid-cols-2">
|
||||
<x-forms.input x-bind:disabled="shouldDisable()"
|
||||
placeholder="docker compose build" id="dockerComposeCustomBuildCommand"
|
||||
helper="The compose file path (<span class='dark:text-warning'>-f</span> flag) and environment variables (<span class='dark:text-warning'>--env-file</span> flag) are automatically injected based on your Base Directory and Docker Compose Location settings. You can override by providing your own <span class='dark:text-warning'>-f</span> or <span class='dark:text-warning'>--env-file</span> flags.<br><br>If you use this, you need to specify paths relatively and should use the same compose file in the custom command, otherwise the automatically configured labels / etc won't work.<br><br>Example usage: <span class='dark:text-warning'>docker compose build</span>"
|
||||
label="Custom build command" />
|
||||
<x-forms.input x-bind:disabled="shouldDisable()"
|
||||
placeholder="docker compose up -d" id="dockerComposeCustomStartCommand"
|
||||
helper="The compose file path (<span class='dark:text-warning'>-f</span> flag) and environment variables (<span class='dark:text-warning'>--env-file</span> flag) are automatically injected based on your Base Directory and Docker Compose Location settings. You can override by providing your own <span class='dark:text-warning'>-f</span> or <span class='dark:text-warning'>--env-file</span> flags.<br><br>If you use this, you need to specify paths relatively and should use the same compose file in the custom command, otherwise the automatically configured labels / etc won't work.<br><br>Example usage: <span class='dark:text-warning'>docker compose up -d</span>"
|
||||
label="Custom start command" />
|
||||
<div class="grid gap-4 pt-4">
|
||||
<div class="grid gap-4 lg:grid-cols-2">
|
||||
<x-forms.input x-bind:disabled="shouldDisable()"
|
||||
placeholder="docker compose build" id="dockerComposeCustomBuildCommand"
|
||||
helper="The compose file path (<span class='dark:text-warning'>-f</span> flag) and environment variables (<span class='dark:text-warning'>--env-file</span> flag) are automatically injected based on your Base Directory and Docker Compose Location settings. You can override by providing your own <span class='dark:text-warning'>-f</span> or <span class='dark:text-warning'>--env-file</span> flags.<br><br>If you use this, you need to specify paths relatively and should use the same compose file in the custom command, otherwise the automatically configured labels / etc won't work.<br><br>Example usage: <span class='dark:text-warning'>docker compose build</span>"
|
||||
label="Custom build command" />
|
||||
<x-forms.input x-bind:disabled="shouldDisable()"
|
||||
placeholder="docker compose up -d" id="dockerComposeCustomStartCommand"
|
||||
helper="The compose file path (<span class='dark:text-warning'>-f</span> flag) and environment variables (<span class='dark:text-warning'>--env-file</span> flag) are automatically injected based on your Base Directory and Docker Compose Location settings. You can override by providing your own <span class='dark:text-warning'>-f</span> or <span class='dark:text-warning'>--env-file</span> flags.<br><br>If you use this, you need to specify paths relatively and should use the same compose file in the custom command, otherwise the automatically configured labels / etc won't work.<br><br>Example usage: <span class='dark:text-warning'>docker compose up -d</span>"
|
||||
label="Custom start command" />
|
||||
</div>
|
||||
@if ($this->dockerComposeCustomBuildCommand)
|
||||
<div wire:key="docker-compose-build-preview">
|
||||
<x-forms.input readonly value="{{ $this->dockerComposeBuildCommandPreview }}"
|
||||
label="Final build command (preview)"
|
||||
helper="This shows the actual command that will be executed with auto-injected flags." />
|
||||
</div>
|
||||
@endif
|
||||
@if ($this->dockerComposeCustomStartCommand)
|
||||
<div wire:key="docker-compose-start-preview">
|
||||
<x-forms.input readonly value="{{ $this->dockerComposeStartCommandPreview }}"
|
||||
label="Final start command (preview)"
|
||||
helper="This shows the actual command that will be executed with auto-injected flags." />
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@if ($this->dockerComposeCustomBuildCommand)
|
||||
<div wire:key="docker-compose-build-preview">
|
||||
<x-forms.input readonly value="{{ $this->dockerComposeBuildCommandPreview }}"
|
||||
label="Final build command (preview)"
|
||||
helper="This shows the actual command that will be executed with auto-injected flags." />
|
||||
</div>
|
||||
@endif
|
||||
@if ($this->dockerComposeCustomStartCommand)
|
||||
<div wire:key="docker-compose-start-preview">
|
||||
<x-forms.input readonly value="{{ $this->dockerComposeStartCommandPreview }}"
|
||||
label="Final start command (preview)"
|
||||
helper="This shows the actual command that will be executed with auto-injected flags." />
|
||||
</div>
|
||||
@endif
|
||||
@if ($this->application->is_github_based() && !$this->application->is_public_repository())
|
||||
<div class="pt-4">
|
||||
<x-forms.textarea
|
||||
@@ -306,8 +303,8 @@
|
||||
</div>
|
||||
@endif
|
||||
@if ($buildPack === 'dockercompose')
|
||||
<div x-data="{ showRaw: true }">
|
||||
<div class="flex items-center gap-4">
|
||||
<div x-data="{ showRaw: true }" class="mt-5">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<h3>Docker Compose</h3>
|
||||
<x-forms.button x-show="{{ $application->settings->is_raw_compose_deployment_enabled ? 'false' : 'true' }}"
|
||||
@click.prevent="showRaw = !showRaw"
|
||||
|
||||
@@ -1,66 +1,34 @@
|
||||
<div x-data="{
|
||||
raw: true,
|
||||
showNormalTextarea: false,
|
||||
editorHeight: 400,
|
||||
calculateEditorHeight() {
|
||||
// Get viewport height
|
||||
const viewportHeight = window.innerHeight;
|
||||
// Modal max height is calc(100vh - 2rem) = viewport - 32px
|
||||
const modalMaxHeight = viewportHeight - 32;
|
||||
// Account for: modal header (~80px) + info text (~60px) + checkboxes (~80px) + buttons (~80px) + padding (~48px)
|
||||
const fixedElementsHeight = 348;
|
||||
// Calculate available height for editor
|
||||
const availableHeight = modalMaxHeight - fixedElementsHeight;
|
||||
// Set minimum height of 300px and maximum of available space
|
||||
this.editorHeight = Math.max(300, Math.min(availableHeight, viewportHeight - 200));
|
||||
}
|
||||
}" x-init="calculateEditorHeight(); window.addEventListener('resize', () => calculateEditorHeight())">
|
||||
<div class="pb-4">Volume names are updated upon save. The service UUID will be added as a prefix to all volumes, to
|
||||
prevent
|
||||
name collision. <br>To see the actual volume names, check the Deployable Compose file, or go to Storage
|
||||
menu.</div>
|
||||
<div x-data="{ raw: true, showNormalTextarea: false }"
|
||||
@compose-preview-toggle.window="raw = !raw"
|
||||
@compose-validate.window="$wire.validateCompose()"
|
||||
@compose-save.window="$wire.saveEditedCompose()"
|
||||
class="flex min-h-0 flex-col gap-3">
|
||||
<x-callout type="info" title="Volume names">
|
||||
Volume names are prefixed with the service UUID when you save to prevent collisions.
|
||||
</x-callout>
|
||||
|
||||
<div class="compose-editor-container" x-bind:style="`--editor-height: ${editorHeight}px`">
|
||||
<div class="compose-editor-container min-h-[24rem] overflow-hidden rounded-lg border border-neutral-200 bg-white dark:border-white/[0.10] dark:bg-[#0b0b0c]"
|
||||
style="--editor-height: clamp(24rem, calc(100dvh - 25rem), 48rem)">
|
||||
<div x-cloak x-show="raw" class="font-mono">
|
||||
<div x-cloak x-show="showNormalTextarea">
|
||||
<x-forms.textarea x-bind:style="`height: ${editorHeight}px`" id="dockerComposeRaw">
|
||||
</x-forms.textarea>
|
||||
<x-forms.textarea class="min-h-[24rem] font-mono" style="height: var(--editor-height)"
|
||||
id="dockerComposeRaw" />
|
||||
</div>
|
||||
<div x-cloak x-show="!showNormalTextarea">
|
||||
<x-forms.textarea allowTab useMonacoEditor monacoEditorLanguage="yaml" id="dockerComposeRaw">
|
||||
</x-forms.textarea>
|
||||
<x-forms.textarea allowTab useMonacoEditor monacoEditorLanguage="yaml" id="dockerComposeRaw" />
|
||||
</div>
|
||||
</div>
|
||||
<div x-cloak x-show="raw === false" class="font-mono">
|
||||
<x-forms.textarea x-bind:style="`height: ${editorHeight}px`" readonly id="dockerCompose">
|
||||
</x-forms.textarea>
|
||||
<x-forms.textarea class="min-h-[24rem] font-mono" style="height: var(--editor-height)" readonly
|
||||
id="dockerCompose" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="pt-2 flex gap-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<x-forms.checkbox label="Escape special characters in labels?"
|
||||
helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off."
|
||||
id="isContainerLabelEscapeEnabled" instantSave></x-forms.checkbox>
|
||||
<x-forms.checkbox label="Show Normal Textarea" x-model="showNormalTextarea"></x-forms.checkbox>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-wrap items-center gap-2 rounded-lg border border-neutral-200 bg-neutral-50 p-1 dark:border-white/[0.08] dark:bg-white/[0.025]">
|
||||
<x-forms.checkbox label="Escape special characters in labels"
|
||||
helper="By default, $ (and other characters) is escaped. A $ in a label is saved as $$. Turn this off to use environment variables inside labels."
|
||||
id="isContainerLabelEscapeEnabled" instantSave />
|
||||
<x-forms.checkbox label="Use plain-text editor" id="showNormalTextarea" x-model="showNormalTextarea" />
|
||||
</div>
|
||||
<div class="flex w-full gap-2 pt-4">
|
||||
<div x-cloak x-show="raw">
|
||||
<x-forms.button class="w-64" @click.prevent="raw = !raw">Show Deployable Compose</x-forms.button>
|
||||
</div>
|
||||
<div x-cloak x-show="raw === false">
|
||||
<x-forms.button class="w-64" @click.prevent="raw = !raw">Show Source
|
||||
Compose</x-forms.button>
|
||||
</div>
|
||||
<div class="flex-1"></div>
|
||||
@if (blank($service->service_type))
|
||||
<x-forms.button class="w-28" wire:click.prevent='validateCompose'>
|
||||
Validate
|
||||
</x-forms.button>
|
||||
@endif
|
||||
<x-forms.button class="w-28" wire:click.prevent='saveEditedCompose'>
|
||||
Save
|
||||
</x-forms.button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,26 @@
|
||||
<x-status-badge label="Parser {{ $service->compose_parsing_version }}" type="neutral" />
|
||||
@endif
|
||||
@can('update', $service)
|
||||
<x-modal-input buttonTitle="Edit Compose file" title="Edit Docker Compose" :closeOutside="false">
|
||||
<x-modal-input buttonTitle="Edit Compose file" title="Docker Compose" :closeOutside="false"
|
||||
:isLarge="true">
|
||||
<x-slot:headerActions>
|
||||
<div x-data="{ preview: false, saving: false }"
|
||||
@compose-save-finished.window="saving = false" class="flex items-center gap-2">
|
||||
<x-forms.button
|
||||
@click="preview = !preview; $dispatch('compose-preview-toggle')">
|
||||
<x-reicon name="eye" class="size-3.5" />
|
||||
<span x-text="preview ? 'Back to source Compose' : 'Preview generated Compose'"></span>
|
||||
</x-forms.button>
|
||||
@if (blank($service->service_type))
|
||||
<x-forms.button @click="$dispatch('compose-validate')">Validate</x-forms.button>
|
||||
@endif
|
||||
<x-forms.button @click="saving = true; $dispatch('compose-save')"
|
||||
x-bind:disabled="saving" isHighlighted>
|
||||
<x-loading-on-button x-show="saving" x-cloak />
|
||||
Save changes
|
||||
</x-forms.button>
|
||||
</div>
|
||||
</x-slot:headerActions>
|
||||
<livewire:project.service.edit-compose serviceId="{{ $service->id }}" />
|
||||
</x-modal-input>
|
||||
@endcan
|
||||
|
||||
@@ -76,27 +76,17 @@
|
||||
<x-forms.input id="description" label="Description" />
|
||||
</div>
|
||||
|
||||
<div x-data="{ advancedOpen: false }"
|
||||
class="mt-5 border-t border-neutral-200 pt-4 dark:border-white/[0.08]">
|
||||
<button type="button" @click="advancedOpen = !advancedOpen"
|
||||
class="flex w-full cursor-pointer items-center justify-between gap-3 text-left text-[13px] font-medium text-neutral-600 dark:text-fg-dim"
|
||||
:aria-expanded="advancedOpen">
|
||||
Advanced settings
|
||||
<x-reicon name="chevron-down" class="size-3.5 transition-transform"
|
||||
x-bind:class="advancedOpen && 'rotate-180'" />
|
||||
</button>
|
||||
|
||||
<div x-show="advancedOpen" x-cloak class="mt-4 flex flex-col gap-4">
|
||||
<div class="grid gap-4 lg:grid-cols-2">
|
||||
<x-forms.input id="user" label="User" required
|
||||
helper="Non-root SSH users are experimental." />
|
||||
<x-forms.input type="number" id="port" label="Port" required />
|
||||
</div>
|
||||
<x-forms.checkbox id="is_build_server"
|
||||
helper="Build servers compile applications but do not host deployments. Enabling this makes the server build-only."
|
||||
label="Use as a dedicated build server" />
|
||||
<x-forms.collapsible class="mt-5 border-t border-neutral-200 pt-4 dark:border-white/[0.08]"
|
||||
content-class="flex flex-col gap-4">
|
||||
<div class="grid gap-4 lg:grid-cols-2">
|
||||
<x-forms.input id="user" label="User" required
|
||||
helper="Non-root SSH users are experimental." />
|
||||
<x-forms.input type="number" id="port" label="Port" required />
|
||||
</div>
|
||||
</div>
|
||||
<x-forms.checkbox id="is_build_server"
|
||||
helper="Build servers compile applications but do not host deployments. Enabling this makes the server build-only."
|
||||
label="Use as a dedicated build server" />
|
||||
</x-forms.collapsible>
|
||||
</x-application.settings-section>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
@@ -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('<div class="flex items-center gap-4">');
|
||||
expect($view)
|
||||
->toContain('<div x-data="{ showRaw: true }" class="mt-5">')
|
||||
->toContain('<div class="flex items-center justify-between gap-4">');
|
||||
});
|
||||
|
||||
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('<x-forms.collapsible class="pt-4" content-class="grid gap-4">')
|
||||
->not->toContain('The following commands are for advanced use cases.')
|
||||
->and($onboarding)
|
||||
->toContain('<x-forms.collapsible title="Advanced Connection Settings"');
|
||||
});
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
|
||||
it('uses the large modal treatment for the compose editor', function () {
|
||||
$view = file_get_contents(resource_path('views/livewire/project/service/stack-form.blade.php'));
|
||||
$modal = file_get_contents(resource_path('views/components/modal-input.blade.php'));
|
||||
|
||||
expect($view)
|
||||
->toContain('title="Docker Compose"')
|
||||
->toContain(':isLarge="true"')
|
||||
->toContain('<x-slot:headerActions>')
|
||||
->toContain("\$dispatch('compose-preview-toggle')")
|
||||
->toContain("\$dispatch('compose-save')")
|
||||
->toContain('@compose-save-finished.window="saving = false"')
|
||||
->toContain('<x-loading-on-button x-show="saving" x-cloak />')
|
||||
->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('<x-callout type="info" title="Volume names">')
|
||||
->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('<x-forms.button x-bind:disabled="saving">Save changes</x-forms.button>');
|
||||
|
||||
expect($html)->toContain('x-bind:disabled="saving"');
|
||||
});
|
||||
@@ -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('<x-forms.collapsible class="mt-5 border-t border-neutral-200 pt-4 dark:border-white/[0.08]"')
|
||||
->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."');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user