2026-08-03 21:11:54 +00:00
@ php
$configuredCount = collect ( $domainRows ) -> where ( 'is_suggested' , false ) -> count ();
$suggestedCount = collect ( $domainRows ) -> where ( 'is_suggested' , true ) -> count ();
$hasRows = count ( $domainRows ) > 0 ;
$helperText = $isCompose
? 'Manage domains for every service in this Docker Compose application.'
: 'Manage domains for this application.' ;
@ endphp
2026-08-05 11:50:11 +00:00
< div class = " flex flex-col gap-4 "
x - data = " {
modalOpen : @ js ( $showEditDomainModal || $editDomainDnsFailed ),
editingServiceLabel : @ js ( $editingService ? ? '' ),
// Local-only until Save — never touch $wire on open/close (avoids Livewire toJSON proxy bugs).
localEditingIndex : @ js ( $editingIndex ),
localEditingDomain : @ js ( $editingDomain ),
localEditingService : @ js ( $editingService ),
openEditDomain ( index , url , service ) {
this . localEditingIndex = index ;
this . localEditingDomain = url ;
this . localEditingService = service ;
this . editingServiceLabel = service || '' ;
this . modalOpen = true ;
this . $nextTick (() => document . getElementById ( 'editingDomainLocal' ) ? . focus ? . ());
},
closeEditDomain () {
this . modalOpen = false ;
this . editingServiceLabel = '' ;
this . localEditingIndex = null ;
this . localEditingDomain = '' ;
this . localEditingService = null ;
},
prepareEditSubmit () {
// Sync Alpine → Livewire only when the user actually saves (one request).
$wire . editingIndex = this . localEditingIndex ;
$wire . editingDomain = this . localEditingDomain ;
$wire . editingService = this . localEditingService ;
$wire . showEditDomainModal = true ;
},
} "
@ open - edit - domain . window = " openEditDomain( $event .detail.index, $event .detail.url, $event .detail.service) " >
2026-08-03 21:11:54 +00:00
< x - application . settings - section id = " domains-section " title = " Domains " : helper = " $helperText " >
@ can ( 'update' , $application )
< x - slot : actions >
< x - forms . button wire : click = " checkAllDns " wire : loading . attr = " disabled " wire : target = " checkAllDns,checkDomainDns " >
< x - reicon name = " refresh " class = " size-3.5 " />
Recheck DNS
</ x - forms . button >
</ x - slot : actions >
@ endcan
@ if ( $labelsAreWritable )
2026-08-05 11:50:11 +00:00
< x - callout type = " warning " title = " Domains managed via labels " class = " mb-4 " >
2026-08-03 21:11:54 +00:00
Container label readonly mode is disabled . Domains must be set in the Labels section on the General page .
</ x - callout >
@ endif
@ if ( $isCompose && count ( $composeServices ) === 0 )
< x - callout type = " info " title = " No services " >
No non - database services found in the Docker Compose file . Domains can only be assigned to application
services .
</ x - callout >
@ endif
@ cannot ( 'update' , $application )
< x - callout type = " danger " title = " Insufficient permissions " >
You don ' t have permission to manage domains . Contact your team administrator for access .
</ x - callout >
@ endcannot
@ if ( ! $isCompose )
@ if ( $labelsAreWritable )
@ if ( $application -> redirect === 'both' )
< x - forms . input label = " Direction " value = " Allow www & non-www " readonly
helper = " Readonly labels are disabled. You can set the direction in the labels section. " />
@ elseif ( $application -> redirect === 'www' )
< x - forms . input label = " Direction " value = " Redirect to www " readonly
helper = " Readonly labels are disabled. You can set the direction in the labels section. " />
@ elseif ( $application -> redirect === 'non-www' )
< x - forms . input label = " Direction " value = " Redirect to non-www " readonly
helper = " Readonly labels are disabled. You can set the direction in the labels section. " />
@ endif
@ else
< div class = " flex w-full flex-col gap-3 sm:flex-row sm:items-end " >
< div class = " min-w-0 flex-1 " >
< x - forms . listbox id = " redirect " label = " Direction " required : options = " [
[ 'value' => 'both' , 'label' => 'Allow www & non-www' ],
[ 'value' => 'www' , 'label' => 'Redirect to www' ],
[ 'value' => 'non-www' , 'label' => 'Redirect to non-www' ],
] " helper= " Add < strong > both </ strong > www and non - www in Coolify . Both hostnames must resolve to this server so the proxy can serve or redirect them . Do not use a DNS - provider URL redirect record for the non - canonical host ; Coolify handles the HTTP redirect . Changes apply when you click Set direction . "
: disabled = " ! auth()->user()?->can('update', $application ) " />
</ div >
@ can ( 'update' , $application )
< div class = " w-full shrink-0 sm:w-auto " >
< x - modal - confirmation title = " Confirm redirection setting? " buttonTitle = " Set direction "
submitAction = " setRedirect " : actions = " ['All traffic will be redirected to the selected direction.'] "
confirmationText = " { { ( $application->fqdn ?: 'domains') . '/' }} "
confirmationLabel = " Please confirm the execution of the action by entering the Application URL below "
shortConfirmationLabel = " Application URL " : confirmWithPassword = " false "
step2ButtonText = " Set direction " canGate = " update " : canResource = " $application " />
</ div >
@ endcan
</ div >
@ endif
@ elseif ( ! $labelsAreWritable && count ( $composeServices ) > 0 )
< p class = " text-sm text-neutral-500 dark:text-fg-dim " >
Per - service www / non - www redirects are available next to each service group in the table below .
</ p >
@ endif
</ x - application . settings - section >
{{ -- Toolbar -- }}
< div class = " mt-2 flex flex-wrap items-center gap-2 " >
< div class = " min-w-0 flex-1 " >
< p class = " text-[13px] text-neutral-500 dark:text-fg-dim " >
{{ $configuredCount }} domain {{ $configuredCount === 1 ? '' : 's' }}
@ if ( $suggestedCount > 0 )
2026-08-05 11:50:11 +00:00
· {{ $suggestedCount }} not added
2026-08-03 21:11:54 +00:00
@ endif
</ p >
</ div >
< div class = " ml-auto flex flex-wrap items-center gap-2 " >
@ can ( 'update' , $application )
2026-08-05 13:15:50 +00:00
@ include ( 'livewire.project.shared.cloudflare-autoconfigure' )
2026-08-03 21:11:54 +00:00
@ unless ( $labelsAreWritable )
@ if ( ! $isCompose || count ( $composeServices ) > 0 )
< x - modal - input title = " Add domain " : closeOutside = " false " : wireIgnore = " false "
canGate = " update " : canResource = " $application " >
< x - slot : content >
< button type = " button "
class = " button bg-coollabs/10! text-coollabs! ring-1 ring-coollabs/25 hover:bg-coollabs/15! dark:bg-warning/15! dark:text-warning! dark:ring-warning/25 dark:hover:bg-warning/20! " >
< x - reicon name = " plus " class = " size-3.5 " />
Add
</ button >
</ x - slot : content >
< form wire : submit = " addDomain " class = " application-settings-form flex flex-col gap-4 " >
@ if ( $isCompose && count ( $composeServices ) > 0 )
< x - forms . select label = " Service " id = " newDomainService " required >
@ foreach ( $composeServices as $serviceName )
< option value = " { { $serviceName }} " > {{ $serviceName }} </ option >
@ endforeach
</ x - forms . select >
@ endif
< x - forms . input id = " newDomain " label = " Domain URL " placeholder = " https://app.example.com "
helper = " Full URL including scheme. Optional path and container port are supported.<br><br><span class='text-helper'>Examples</span><br>- https://app.coolify.io<br>- https://app.coolify.io/api/v3<br>- https://app.coolify.io:3000<br>- https://app.coolify.io:8080/api "
required />
@ if ( $addDomainDnsFailed )
2026-08-05 11:50:11 +00:00
< x - callout type = " danger " title = " DNS is not pointing to the right IP " >
This domain does not currently resolve to this server .
Traffic may not reach Coolify until you update DNS .
Are you sure you want to add it anyway ?
@ if ( filled ( $addDomainDnsMessage ))
< div class = " pt-2 " > {{ $addDomainDnsMessage }} </ div >
2026-08-03 21:11:54 +00:00
@ endif
</ x - callout >
@ endif
< div class = " flex flex-wrap items-center justify-between gap-2 pt-2 " >
< x - forms . button type = " button " wire : click = " generateDomain " >
Generate domain
</ x - forms . button >
< div class = " flex flex-wrap gap-2 " >
@ if ( $addDomainDnsFailed )
< x - forms . button type = " button " wire : click = " confirmAddDomainDespiteDns " isError >
Continue
</ x - forms . button >
@ else
< x - forms . button type = " submit " isHighlighted >
Save
</ x - forms . button >
@ endif
</ div >
</ div >
</ form >
</ x - modal - input >
@ endif
@ endunless
@ endcan
</ div >
</ div >
{{ -- Table / empty -- }}
< div id = " domains-table-section "
class = " application-settings-section-body mt-1 scroll-mt-28 { { $hasRows ? 'is-flush' : '' }} w-full " >
@ if ( $isCompose && count ( $composeServices ) === 0 && ! $hasRows )
< x - empty size = " sm " title = " No services available "
description = " No non-database services found in the Docker Compose file. "
icon - name = " globe " />
@ elseif ( ! $hasRows )
< x - empty size = " sm " title = " No domains configured "
description = " Add your first domain with the + Add button above, or generate one with the server wildcard domain. "
icon - name = " globe " />
@ elseif ( $isCompose )
@ php
$grouped = collect ( $domainRows ) -> groupBy ( fn ( $row ) => $row [ 'service' ] ? ? '__unknown' );
$serviceOrder = $composeServices ;
foreach ( $grouped -> keys () as $name ) {
if ( $name !== '__unknown' && ! in_array ( $name , $serviceOrder , true )) {
$serviceOrder [] = $name ;
}
}
@ endphp
< div class = " flex flex-col gap-6 " >
@ foreach ( $serviceOrder as $serviceName )
@ php
$rows = $grouped -> get ( $serviceName , collect ());
$serviceConfigured = $rows -> where ( 'is_suggested' , false ) -> count ();
$serviceSuggested = $rows -> where ( 'is_suggested' , true ) -> count ();
$redirectWireKey = $this -> serviceRedirectWireKey ( $serviceName );
@ endphp
< div class = " flex flex-col gap-3 " >
< div class = " flex flex-wrap items-end justify-between gap-3 " >
< div >
< h3 class = " text-[14px] font-semibold text-black dark:text-fg " > {{ $serviceName }} </ h3 >
< p class = " mt-0.5 text-[12px] text-neutral-500 dark:text-fg-dim " >
{{ $serviceConfigured }} domain {{ $serviceConfigured === 1 ? '' : 's' }}
@ if ( $serviceSuggested > 0 )
2026-08-05 11:50:11 +00:00
· {{ $serviceSuggested }} not added
2026-08-03 21:11:54 +00:00
@ endif
</ p >
</ div >
@ unless ( $labelsAreWritable )
< div class = " flex w-full max-w-xl flex-col gap-2 sm:flex-row sm:items-end " >
< div class = " min-w-0 flex-1 " >
< x - forms . select label = " Direction " id = " serviceRedirects. { { $redirectWireKey }} "
wire : model = " serviceRedirects. { { $redirectWireKey }} " required
helper = " Per-service www/non-www redirect. Add both hosts when using a redirect. "
canGate = " update " : canResource = " $application " >
< option value = " both " > Allow www & non - www </ option >
< option value = " www " > Redirect to www </ option >
< option value = " non-www " > Redirect to non - www </ option >
</ x - forms . select >
</ div >
@ can ( 'update' , $application )
< x - modal - confirmation title = " Confirm redirection setting? " buttonTitle = " Set direction "
submitAction = 'setServiceRedirect({{ \Illuminate\Support\Js::from($serviceName) }})'
: actions = " ['Traffic for this service will be redirected to the selected direction. Missing www/non-www counterparts will be added automatically when possible.'] "
confirmationText = " { { $serviceName }}/ "
confirmationLabel = " Please confirm by entering the service name below "
shortConfirmationLabel = " Service name " : confirmWithPassword = " false "
step2ButtonText = " Set direction " canGate = " update " : canResource = " $application " />
@ endcan
</ div >
@ endunless
</ div >
@ if ( $rows -> isEmpty ())
< x - empty size = " sm " title = " No domains for this service "
description = " Use + Add and select { { $serviceName }}. "
icon - name = " globe " />
@ else
< div class = " data-table w-full " >
< div class = " data-table-header domains-table-grid-compose " >
< span > Domain </ span >
< span > Service </ span >
2026-08-05 11:50:11 +00:00
< span > DNS Check </ span >
2026-08-03 21:11:54 +00:00
< span > Last checked </ span >
< span ></ span >
</ div >
@ foreach ( $rows as $row )
@ php
$index = collect ( $domainRows ) -> search (
fn ( $item ) => $item [ 'url' ] === $row [ 'url' ]
&& ( $item [ 'service' ] ? ? null ) === ( $row [ 'service' ] ? ? null )
&& ( bool ) ( $item [ 'is_suggested' ] ? ? false ) === ( bool ) ( $row [ 'is_suggested' ] ? ? false ),
);
@ endphp
@ include ( 'livewire.project.application.partials.domain-row' , [
'index' => $index ,
'row' => $row ,
'application' => $application ,
'labelsAreWritable' => $labelsAreWritable ,
'isCompose' => true ,
])
@ endforeach
</ div >
@ endif
</ div >
@ endforeach
</ div >
@ else
< div class = " data-table w-full " >
< div class = " data-table-header domains-table-grid " >
< span > Domain </ span >
2026-08-05 11:50:11 +00:00
< span > DNS Check </ span >
2026-08-03 21:11:54 +00:00
< span > Last checked </ span >
< span ></ span >
</ div >
@ foreach ( $domainRows as $index => $row )
@ include ( 'livewire.project.application.partials.domain-row' , [
'index' => $index ,
'row' => $row ,
'application' => $application ,
'labelsAreWritable' => $labelsAreWritable ,
'isCompose' => false ,
])
@ endforeach
</ div >
@ endif
</ div >
2026-08-05 11:50:11 +00:00
{{ -- Edit domain modal : open / close is Alpine - only ; server runs only on Save / Continue . -- }}
< div class = " relative h-auto w-auto " : class = " { 'z-40': modalOpen } "
@ keydown . window . escape = " if (modalOpen) { closeEditDomain() } " >
< template x - teleport = " body " >
< div x - show = " modalOpen " class = " fixed inset-0 z-99 overflow-y-auto " x - cloak >
< div x - show = " modalOpen " x - transition : enter = " ease-out duration-100 "
x - transition : enter - start = " opacity-0 " x - transition : enter - end = " opacity-100 "
x - transition : leave = " ease-in duration-100 " x - transition : leave - start = " opacity-100 "
x - transition : leave - end = " opacity-0 "
class = " absolute inset-0 h-full w-full bg-black/50 backdrop-blur-[2px] "
@ click = " closeEditDomain() " ></ div >
< div class = " relative flex min-h-full items-start justify-center p-4 sm:items-center " >
< div x - show = " modalOpen " x - trap . inert . noscroll = " modalOpen "
x - transition : enter = " ease-out duration-100 "
x - transition : enter - start = " opacity-0 -translate-y-2 sm:scale-95 "
x - transition : enter - end = " opacity-100 translate-y-0 sm:scale-100 "
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 flex max-h-[calc(100dvh-2rem)] w-full flex-col overflow-hidden lg:w-auto lg:min-w-2xl lg:max-w-4xl "
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 " > Edit domain </ h3 >
< button type = " button " @ click = " closeEditDomain() "
class = " icon-button shrink-0 " aria - label = " Close " >
< x - reicon name = " x " class = " size-4 " />
</ button >
</ header >
< div class = " application-settings-section-body relative min-h-0 flex-1 overflow-y-auto "
style = " -webkit-overflow-scrolling: touch; " >
< form @ submit . prevent = " prepareEditSubmit(); $wire .updateDomain() " class = " flex flex-col gap-4 " >
< div x - show = " editingServiceLabel " x - cloak class = " w-full " >
< div class = " mb-1.5 flex h-4 w-full items-center gap-1.5 " >
< label class = " mb-0! flex items-center gap-1 text-sm font-medium leading-4 " > Service </ label >
</ div >
< input type = " text " class = " input " readonly x - bind : value = " editingServiceLabel " />
</ div >
2026-08-03 21:11:54 +00:00
2026-08-05 11:50:11 +00:00
< div class = " w-full " >
< div class = " mb-1.5 flex h-4 w-full items-center gap-1.5 " >
< label class = " mb-0! flex items-center gap-1 text-sm font-medium leading-4 " for = " editingDomainLocal " >
Domain URL < x - highlighted text = " * " />
</ label >
</ div >
< input id = " editingDomainLocal " type = " url " class = " input " required
2026-08-03 21:11:54 +00:00
placeholder = " https://app.example.com "
2026-08-05 11:50:11 +00:00
x - model = " localEditingDomain " />
< p class = " mt-1 text-[12px] leading-5 text-neutral-500 dark:text-fg-dim " >
Full URL including scheme . Optional path and container port are supported .
</ p >
@ error ( 'editingDomain' )
< p class = " mt-1 text-[12px] text-red-500 " > {{ $message }} </ p >
@ enderror
</ div >
2026-08-03 21:11:54 +00:00
2026-08-05 11:50:11 +00:00
@ if ( $editDomainDnsFailed )
< x - callout type = " danger " title = " DNS is not pointing to the right IP " >
This domain does not currently resolve to this server .
Traffic may not reach Coolify until you update DNS .
Are you sure you want to save it anyway ?
@ if ( filled ( $editDomainDnsMessage ))
< div class = " pt-2 " > {{ $editDomainDnsMessage }} </ div >
@ endif
</ x - callout >
@ endif
2026-08-03 21:11:54 +00:00
2026-08-05 11:50:11 +00:00
< div class = " flex flex-wrap items-center justify-end gap-2 pt-2 " >
< x - forms . button type = " button " @ click = " closeEditDomain() " >
Cancel
</ x - forms . button >
@ if ( $editDomainDnsFailed )
< x - forms . button type = " button " isError
@ click = " prepareEditSubmit(); $wire .forceSaveEditDns = true; $wire .confirmUpdateDomainDespiteDns() " >
Continue
2026-08-03 21:11:54 +00:00
</ x - forms . button >
2026-08-05 11:50:11 +00:00
@ else
< x - forms . button type = " submit " isHighlighted >
Save
</ x - forms . button >
@ endif
</ div >
</ form >
2026-08-03 21:11:54 +00:00
</ div >
</ div >
</ div >
2026-08-05 11:50:11 +00:00
</ div >
</ template >
</ div >
2026-08-03 21:11:54 +00:00
< x - domain - conflict - modal : conflicts = " $domainConflicts " : showModal = " $showDomainConflictModal "
confirmAction = " confirmDomainUsage " />
</ div >