fix: clean up utility classes and improve readability in Blade templates

This commit is contained in:
Andras Bacsai 2025-11-05 08:59:05 +01:00
parent 5b79844a3a
commit 54964d54d4
4 changed files with 259 additions and 274 deletions

View file

@ -124,7 +124,7 @@ @utility menu {
} }
@utility menu-item { @utility menu-item {
@apply flex gap-3 items-center px-2 py-1 w-full text-sm sm:pr-0 dark:hover:bg-coolgray-100 dark:hover:text-white hover:bg-neutral-300 min-w-fit sm:min-w-64 text-black dark:text-neutral-400; @apply flex gap-3 items-center px-2 py-1 w-full text-sm sm:pr-0 dark:hover:bg-coolgray-100 dark:hover:text-white hover:bg-neutral-300 min-w-fit sm:min-w-64;
} }
@utility menu-item-active { @utility menu-item-active {
@ -152,7 +152,7 @@ @utility custom-modal {
} }
@utility navbar-main { @utility navbar-main {
@apply flex flex-col gap-4 justify-items-start pb-2 border-b-2 border-solid h-fit md:flex-row sm:justify-between dark:border-coolgray-200 border-neutral-200 md:items-center; @apply flex flex-col gap-4 justify-items-start pb-2 border-b-2 border-solid h-fit md:flex-row sm:justify-between dark:border-coolgray-200 border-neutral-200 md:items-center text-neutral-700 dark:text-neutral-400;
} }
@utility loading { @utility loading {
@ -220,7 +220,7 @@ @utility title {
} }
@utility subtitle { @utility subtitle {
@apply pt-2 pb-9 text-neutral-500 dark:text-neutral-400; @apply pt-2 pb-9;
} }
@utility fullscreen { @utility fullscreen {

View file

@ -9,16 +9,15 @@
@auth @auth
<livewire:deployments-indicator /> <livewire:deployments-indicator />
<div x-data="{ <div x-data="{
open: false, open: false,
init() { init() {
this.pageWidth = localStorage.getItem('pageWidth'); this.pageWidth = localStorage.getItem('pageWidth');
if (!this.pageWidth) { if (!this.pageWidth) {
this.pageWidth = 'full'; this.pageWidth = 'full';
localStorage.setItem('pageWidth', 'full'); localStorage.setItem('pageWidth', 'full');
} }
} }
}" x-cloak class="mx-auto text-neutral-800 dark:text-white" }" x-cloak class="mx-auto" :class="pageWidth === 'full' ? '' : 'max-w-7xl'">
:class="pageWidth === 'full' ? '' : 'max-w-7xl'">
<div class="relative z-50 lg:hidden" :class="open ? 'block' : 'hidden'" role="dialog" aria-modal="true"> <div class="relative z-50 lg:hidden" :class="open ? 'block' : 'hidden'" role="dialog" aria-modal="true">
<div class="fixed inset-0 bg-black/80" x-on:click="open = false"></div> <div class="fixed inset-0 bg-black/80" x-on:click="open = false"></div>
<div class="fixed inset-y-0 right-0 h-full flex"> <div class="fixed inset-y-0 right-0 h-full flex">
@ -56,8 +55,8 @@ class="text-xl font-bold tracking-wide dark:text-white hover:opacity-80 transiti
<button type="button" class="-m-2.5 p-2.5 dark:text-warning" x-on:click="open = !open"> <button type="button" class="-m-2.5 p-2.5 dark:text-warning" x-on:click="open = !open">
<span class="sr-only">Open sidebar</span> <span class="sr-only">Open sidebar</span>
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 24 24"> <svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
d="M4 6h16M4 12h16M4 18h16" /> stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg> </svg>
</button> </button>
</div> </div>

View file

@ -2,7 +2,7 @@
<html data-theme="dark" lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <html data-theme="dark" lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<script> <script>
// Immediate theme application - runs before any rendering // Immediate theme application - runs before any rendering
(function () { (function() {
const t = localStorage.theme || 'dark'; const t = localStorage.theme || 'dark';
const d = t === 'dark' || (t === 'system' && matchMedia('(prefers-color-scheme: dark)').matches); const d = t === 'dark' || (t === 'system' && matchMedia('(prefers-color-scheme: dark)').matches);
document.documentElement.classList[d ? 'add' : 'remove']('dark'); document.documentElement.classList[d ? 'add' : 'remove']('dark');
@ -75,102 +75,102 @@
</head> </head>
@section('body') @section('body')
<body> <body>
<x-toast /> <x-toast />
<script data-navigate-once> <script data-navigate-once>
// Global HTML sanitization function using DOMPurify // Global HTML sanitization function using DOMPurify
window.sanitizeHTML = function (html) { window.sanitizeHTML = function(html) {
if (!html) return ''; if (!html) return '';
const URL_RE = /^(https?:|mailto:)/i; const URL_RE = /^(https?:|mailto:)/i;
const config = { const config = {
ALLOWED_TAGS: ['a', 'b', 'br', 'code', 'del', 'div', 'em', 'i', 'p', 'pre', 's', 'span', 'strong', ALLOWED_TAGS: ['a', 'b', 'br', 'code', 'del', 'div', 'em', 'i', 'p', 'pre', 's', 'span', 'strong',
'u' 'u'
], ],
ALLOWED_ATTR: ['class', 'href', 'target', 'title', 'rel'], ALLOWED_ATTR: ['class', 'href', 'target', 'title', 'rel'],
ALLOW_DATA_ATTR: false, ALLOW_DATA_ATTR: false,
FORBID_TAGS: ['script', 'object', 'embed', 'applet', 'iframe', 'form', 'input', 'button', 'select', FORBID_TAGS: ['script', 'object', 'embed', 'applet', 'iframe', 'form', 'input', 'button', 'select',
'textarea', 'details', 'summary', 'dialog', 'style' 'textarea', 'details', 'summary', 'dialog', 'style'
], ],
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onfocus', 'onblur', 'onchange', FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onfocus', 'onblur', 'onchange',
'onsubmit', 'ontoggle', 'style' 'onsubmit', 'ontoggle', 'style'
], ],
KEEP_CONTENT: true, KEEP_CONTENT: true,
RETURN_DOM: false, RETURN_DOM: false,
RETURN_DOM_FRAGMENT: false, RETURN_DOM_FRAGMENT: false,
SANITIZE_DOM: true, SANITIZE_DOM: true,
SANITIZE_NAMED_PROPS: true, SANITIZE_NAMED_PROPS: true,
SAFE_FOR_TEMPLATES: true, SAFE_FOR_TEMPLATES: true,
ALLOWED_URI_REGEXP: URL_RE ALLOWED_URI_REGEXP: URL_RE
};
// One-time hook registration (idempotent pattern)
if (!window.__dpLinkHook) {
DOMPurify.addHook('afterSanitizeAttributes', node => {
// Remove Alpine.js directives to prevent XSS
if (node.hasAttributes && node.hasAttributes()) {
const attrs = Array.from(node.attributes);
attrs.forEach(attr => {
// Remove x-* attributes (Alpine directives)
if (attr.name.startsWith('x-')) {
node.removeAttribute(attr.name);
}
// Remove @* attributes (Alpine event shorthand)
if (attr.name.startsWith('@')) {
node.removeAttribute(attr.name);
}
// Remove :* attributes (Alpine binding shorthand)
if (attr.name.startsWith(':')) {
node.removeAttribute(attr.name);
}
});
}
// Existing link sanitization
if (node.nodeName === 'A' && node.hasAttribute('href')) {
const href = node.getAttribute('href') || '';
if (!URL_RE.test(href)) node.removeAttribute('href');
if (node.getAttribute('target') === '_blank') {
node.setAttribute('rel', 'noopener noreferrer');
}
}
});
window.__dpLinkHook = true;
}
return DOMPurify.sanitize(html, config);
}; };
// One-time hook registration (idempotent pattern) // Initialize theme if not set
if (!window.__dpLinkHook) { if (!('theme' in localStorage)) {
DOMPurify.addHook('afterSanitizeAttributes', node => { localStorage.theme = 'dark';
// Remove Alpine.js directives to prevent XSS
if (node.hasAttributes && node.hasAttributes()) {
const attrs = Array.from(node.attributes);
attrs.forEach(attr => {
// Remove x-* attributes (Alpine directives)
if (attr.name.startsWith('x-')) {
node.removeAttribute(attr.name);
}
// Remove @* attributes (Alpine event shorthand)
if (attr.name.startsWith('@')) {
node.removeAttribute(attr.name);
}
// Remove :* attributes (Alpine binding shorthand)
if (attr.name.startsWith(':')) {
node.removeAttribute(attr.name);
}
});
}
// Existing link sanitization
if (node.nodeName === 'A' && node.hasAttribute('href')) {
const href = node.getAttribute('href') || '';
if (!URL_RE.test(href)) node.removeAttribute('href');
if (node.getAttribute('target') === '_blank') {
node.setAttribute('rel', 'noopener noreferrer');
}
}
});
window.__dpLinkHook = true;
} }
return DOMPurify.sanitize(html, config);
};
// Initialize theme if not set let theme = localStorage.theme
if (!('theme' in localStorage)) { let cpuColor = '#1e90ff'
localStorage.theme = 'dark'; let ramColor = '#00ced1'
} let textColor = '#ffffff'
let editorBackground = '#181818'
let editorTheme = 'blackboard'
let theme = localStorage.theme function checkTheme() {
let cpuColor = '#1e90ff' theme = localStorage.theme
let ramColor = '#00ced1' if (theme == 'system') {
let textColor = '#ffffff' theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
let editorBackground = '#181818' }
let editorTheme = 'blackboard' if (theme == 'dark') {
cpuColor = '#1e90ff'
function checkTheme() { ramColor = '#00ced1'
theme = localStorage.theme textColor = '#ffffff'
if (theme == 'system') { editorBackground = '#181818'
theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' editorTheme = 'blackboard'
} else {
cpuColor = '#1e90ff'
ramColor = '#00ced1'
textColor = '#000000'
editorBackground = '#ffffff'
editorTheme = null
}
} }
if (theme == 'dark') { @auth
cpuColor = '#1e90ff'
ramColor = '#00ced1'
textColor = '#ffffff'
editorBackground = '#181818'
editorTheme = 'blackboard'
} else {
cpuColor = '#1e90ff'
ramColor = '#00ced1'
textColor = '#000000'
editorBackground = '#ffffff'
editorTheme = null
}
}
@auth
window.Pusher = Pusher; window.Pusher = Pusher;
window.Echo = new Echo({ window.Echo = new Echo({
broadcaster: 'pusher', broadcaster: 'pusher',
@ -199,131 +199,131 @@ function checkTheme() {
// Maximum number of reconnection attempts // Maximum number of reconnection attempts
maxAttempts: 15 maxAttempts: 15
}); });
@endauth @endauth
let checkHealthInterval = null; let checkHealthInterval = null;
let checkIfIamDeadInterval = null; let checkIfIamDeadInterval = null;
function changePasswordFieldType(event) { function changePasswordFieldType(event) {
let element = event.target let element = event.target
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
if (element.className === "relative") { if (element.className === "relative") {
break; break;
}
element = element.parentElement;
} }
element = element.parentElement; element = element.children[1];
} if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
element = element.children[1]; if (element.type === 'password') {
if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') { element.type = 'text';
if (element.type === 'password') { if (element.disabled) return;
element.type = 'text'; element.classList.add('truncate');
if (element.disabled) return; this.type = 'text';
element.classList.add('truncate'); } else {
this.type = 'text'; element.type = 'password';
} else { if (element.disabled) return;
element.type = 'password'; element.classList.remove('truncate');
if (element.disabled) return; this.type = 'password';
element.classList.remove('truncate'); }
this.type = 'password';
} }
} }
}
function copyToClipboard(text) { function copyToClipboard(text) {
navigator?.clipboard?.writeText(text) && window.Livewire.dispatch('success', 'Copied to clipboard.'); navigator?.clipboard?.writeText(text) && window.Livewire.dispatch('success', 'Copied to clipboard.');
} }
document.addEventListener('livewire:init', () => { document.addEventListener('livewire:init', () => {
window.Livewire.on('reloadWindow', (timeout) => { window.Livewire.on('reloadWindow', (timeout) => {
if (timeout) { if (timeout) {
setTimeout(() => { setTimeout(() => {
window.location.reload();
}, timeout);
return;
} else {
window.location.reload(); window.location.reload();
}, timeout); }
return; })
} else { window.Livewire.on('info', (message) => {
window.location.reload(); if (typeof message === 'string') {
} window.toast('Info', {
}) type: 'info',
window.Livewire.on('info', (message) => { description: message,
if (typeof message === 'string') { })
window.toast('Info', { return;
type: 'info', }
description: message, if (message.length == 1) {
}) window.toast('Info', {
return; type: 'info',
} description: message[0],
if (message.length == 1) { })
window.toast('Info', { } else if (message.length == 2) {
type: 'info', window.toast(message[0], {
description: message[0], type: 'info',
}) description: message[1],
} else if (message.length == 2) { })
window.toast(message[0], { }
type: 'info', })
description: message[1], window.Livewire.on('error', (message) => {
}) if (typeof message === 'string') {
} window.toast('Error', {
}) type: 'danger',
window.Livewire.on('error', (message) => { description: message,
if (typeof message === 'string') { })
window.toast('Error', { return;
type: 'danger', }
description: message, if (message.length == 1) {
}) window.toast('Error', {
return; type: 'danger',
} description: message[0],
if (message.length == 1) { })
window.toast('Error', { } else if (message.length == 2) {
type: 'danger', window.toast(message[0], {
description: message[0], type: 'danger',
}) description: message[1],
} else if (message.length == 2) { })
window.toast(message[0], { }
type: 'danger', })
description: message[1], window.Livewire.on('warning', (message) => {
}) if (typeof message === 'string') {
} window.toast('Warning', {
}) type: 'warning',
window.Livewire.on('warning', (message) => { description: message,
if (typeof message === 'string') { })
window.toast('Warning', { return;
type: 'warning', }
description: message, if (message.length == 1) {
}) window.toast('Warning', {
return; type: 'warning',
} description: message[0],
if (message.length == 1) { })
window.toast('Warning', { } else if (message.length == 2) {
type: 'warning', window.toast(message[0], {
description: message[0], type: 'warning',
}) description: message[1],
} else if (message.length == 2) { })
window.toast(message[0], { }
type: 'warning', })
description: message[1], window.Livewire.on('success', (message) => {
}) if (typeof message === 'string') {
} window.toast('Success', {
}) type: 'success',
window.Livewire.on('success', (message) => { description: message,
if (typeof message === 'string') { })
window.toast('Success', { return;
type: 'success', }
description: message, if (message.length == 1) {
}) window.toast('Success', {
return; type: 'success',
} description: message[0],
if (message.length == 1) { })
window.toast('Success', { } else if (message.length == 2) {
type: 'success', window.toast(message[0], {
description: message[0], type: 'success',
}) description: message[1],
} else if (message.length == 2) { })
window.toast(message[0], { }
type: 'success', })
description: message[1], });
}) </script>
} </body>
})
});
</script>
</body>
@show @show
</html> </html>

View file

@ -23,16 +23,15 @@
@if (!$application->dockerfile && $application->build_pack !== 'dockerimage') @if (!$application->dockerfile && $application->build_pack !== 'dockerimage')
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.select x-bind:disabled="shouldDisable()" wire:model.live="buildPack" <x-forms.select x-bind:disabled="shouldDisable()" wire:model.live="buildPack" label="Build Pack"
label="Build Pack" required> required>
<option value="nixpacks">Nixpacks</option> <option value="nixpacks">Nixpacks</option>
<option value="static">Static</option> <option value="static">Static</option>
<option value="dockerfile">Dockerfile</option> <option value="dockerfile">Dockerfile</option>
<option value="dockercompose">Docker Compose</option> <option value="dockercompose">Docker Compose</option>
</x-forms.select> </x-forms.select>
@if ($application->settings->is_static || $application->build_pack === 'static') @if ($application->settings->is_static || $application->build_pack === 'static')
<x-forms.select x-bind:disabled="!canUpdate" id="staticImage" <x-forms.select x-bind:disabled="!canUpdate" id="staticImage" label="Static Image" required>
label="Static Image" required>
<option value="nginx:alpine">nginx:alpine</option> <option value="nginx:alpine">nginx:alpine</option>
<option disabled value="apache:alpine">apache:alpine</option> <option disabled value="apache:alpine">apache:alpine</option>
</x-forms.select> </x-forms.select>
@ -75,7 +74,9 @@
submitAction="generateNginxConfiguration('{{ $application->settings->is_spa ? 'spa' : 'static' }}')" submitAction="generateNginxConfiguration('{{ $application->settings->is_spa ? 'spa' : 'static' }}')"
:actions="[ :actions="[
'This will overwrite your current custom Nginx configuration.', 'This will overwrite your current custom Nginx configuration.',
'The default configuration will be generated based on your application type (' . ($application->settings->is_spa ? 'SPA' : 'static') . ').', 'The default configuration will be generated based on your application type (' .
($application->settings->is_spa ? 'SPA' : 'static') .
').',
]" /> ]" />
@endcan @endcan
@endif @endif
@ -94,13 +95,11 @@
@if ($application->build_pack !== 'dockercompose') @if ($application->build_pack !== 'dockercompose')
<div class="flex items-end gap-2"> <div class="flex items-end gap-2">
@if ($application->settings->is_container_label_readonly_enabled == false) @if ($application->settings->is_container_label_readonly_enabled == false)
<x-forms.input placeholder="https://coolify.io" wire:model="fqdn" <x-forms.input placeholder="https://coolify.io" wire:model="fqdn" label="Domains" readonly
label="Domains" readonly
helper="Readonly labels are disabled. You can set the domains in the labels section." helper="Readonly labels are disabled. You can set the domains in the labels section."
x-bind:disabled="!canUpdate" /> x-bind:disabled="!canUpdate" />
@else @else
<x-forms.input placeholder="https://coolify.io" wire:model="fqdn" <x-forms.input placeholder="https://coolify.io" wire:model="fqdn" label="Domains"
label="Domains"
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. " helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "
x-bind:disabled="!canUpdate" /> x-bind:disabled="!canUpdate" />
@can('update', $application) @can('update', $application)
@ -210,21 +209,17 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
<x-forms.input <x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>" helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k --hostname=myapp" placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k --hostname=myapp"
id="customDockerRunOptions" label="Custom Docker Options" id="customDockerRunOptions" label="Custom Docker Options" x-bind:disabled="!canUpdate" />
x-bind:disabled="!canUpdate" />
@else @else
@if ($application->could_set_build_commands()) @if ($application->could_set_build_commands())
@if ($application->build_pack === 'nixpacks') @if ($application->build_pack === 'nixpacks')
<div class="flex flex-col gap-2 xl:flex-row"> <div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml" <x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml"
id="installCommand" label="Install Command" id="installCommand" label="Install Command" x-bind:disabled="!canUpdate" />
x-bind:disabled="!canUpdate" />
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml" <x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml"
id="buildCommand" label="Build Command" id="buildCommand" label="Build Command" x-bind:disabled="!canUpdate" />
x-bind:disabled="!canUpdate" />
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml" <x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml"
id="startCommand" label="Start Command" id="startCommand" label="Start Command" x-bind:disabled="!canUpdate" />
x-bind:disabled="!canUpdate" />
</div> </div>
<div class="pt-1 text-xs">Nixpacks will detect the required configuration <div class="pt-1 text-xs">Nixpacks will detect the required configuration
automatically. automatically.
@ -246,13 +241,12 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
id="baseDirectory" label="Base Directory" id="baseDirectory" label="Base Directory"
helper="Directory to use as root. Useful for monorepos." /> helper="Directory to use as root. Useful for monorepos." />
<x-forms.input x-bind:disabled="shouldDisable()" <x-forms.input x-bind:disabled="shouldDisable()"
placeholder="/docker-compose.yaml" placeholder="/docker-compose.yaml" id="dockerComposeLocation"
id="dockerComposeLocation" label="Docker Compose Location" label="Docker Compose Location"
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }}</span>" /> helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }}</span>" />
</div> </div>
<div class="w-96"> <div class="w-96">
<x-forms.checkbox instantSave <x-forms.checkbox instantSave id="isPreserveRepositoryEnabled"
id="isPreserveRepositoryEnabled"
label="Preserve Repository During Deployment" label="Preserve Repository During Deployment"
helper="Git repository (based on the base directory settings) will be copied to the deployment directory." helper="Git repository (based on the base directory settings) will be copied to the deployment directory."
x-bind:disabled="shouldDisable()" /> x-bind:disabled="shouldDisable()" />
@ -264,13 +258,11 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
you doing.</div> you doing.</div>
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.input x-bind:disabled="shouldDisable()" <x-forms.input x-bind:disabled="shouldDisable()"
placeholder="docker compose build" placeholder="docker compose build" id="dockerComposeCustomBuildCommand"
id="dockerComposeCustomBuildCommand"
helper="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>So in your case, use: <span class='dark:text-warning'>docker compose -f .{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }} build</span>" helper="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>So in your case, use: <span class='dark:text-warning'>docker compose -f .{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }} build</span>"
label="Custom Build Command" /> label="Custom Build Command" />
<x-forms.input x-bind:disabled="shouldDisable()" <x-forms.input x-bind:disabled="shouldDisable()"
placeholder="docker compose up -d" placeholder="docker compose up -d" id="dockerComposeCustomStartCommand"
id="dockerComposeCustomStartCommand"
helper="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>So in your case, use: <span class='dark:text-warning'>docker compose -f .{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }} up -d</span>" helper="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>So in your case, use: <span class='dark:text-warning'>docker compose -f .{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }} up -d</span>"
label="Custom Start Command" /> label="Custom Start Command" />
</div> </div>
@ -278,15 +270,14 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
<div class="pt-4"> <div class="pt-4">
<x-forms.textarea <x-forms.textarea
helper="Order-based pattern matching to filter Git webhook deployments. Supports wildcards (*, **, ?) and negation (!). Last matching pattern wins." helper="Order-based pattern matching to filter Git webhook deployments. Supports wildcards (*, **, ?) and negation (!). Last matching pattern wins."
placeholder="services/api/**" id="watchPaths" placeholder="services/api/**" id="watchPaths" label="Watch Paths"
label="Watch Paths" x-bind:disabled="shouldDisable()" /> x-bind:disabled="shouldDisable()" />
</div> </div>
@endif @endif
</div> </div>
@else @else
<div class="flex flex-col gap-2 xl:flex-row"> <div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input placeholder="/" id="baseDirectory" <x-forms.input placeholder="/" id="baseDirectory" label="Base Directory"
label="Base Directory"
helper="Directory to use as root. Useful for monorepos." helper="Directory to use as root. Useful for monorepos."
x-bind:disabled="!canUpdate" /> x-bind:disabled="!canUpdate" />
@if ($application->build_pack === 'dockerfile' && !$application->dockerfile) @if ($application->build_pack === 'dockerfile' && !$application->dockerfile)
@ -297,8 +288,7 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
@endif @endif
@if ($application->build_pack === 'dockerfile') @if ($application->build_pack === 'dockerfile')
<x-forms.input id="dockerfileTargetBuild" <x-forms.input id="dockerfileTargetBuild" label="Docker Build Stage Target"
label="Docker Build Stage Target"
helper="Useful if you have multi-staged dockerfile." helper="Useful if you have multi-staged dockerfile."
x-bind:disabled="!canUpdate" /> x-bind:disabled="!canUpdate" />
@endif @endif
@ -317,8 +307,8 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
<div class="pb-4"> <div class="pb-4">
<x-forms.textarea <x-forms.textarea
helper="Order-based pattern matching to filter Git webhook deployments. Supports wildcards (*, **, ?) and negation (!). Last matching pattern wins." helper="Order-based pattern matching to filter Git webhook deployments. Supports wildcards (*, **, ?) and negation (!). Last matching pattern wins."
placeholder="src/pages/**" id="watchPaths" placeholder="src/pages/**" id="watchPaths" label="Watch Paths"
label="Watch Paths" x-bind:disabled="!canUpdate" /> x-bind:disabled="!canUpdate" />
</div> </div>
@endif @endif
<x-forms.input <x-forms.input
@ -331,8 +321,8 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
<div class="pt-2 w-96"> <div class="pt-2 w-96">
<x-forms.checkbox <x-forms.checkbox
helper="Use a build server to build your application. You can configure your build server in the Server settings. For more info, check the <a href='https://coolify.io/docs/knowledge-base/server/build-server' class='underline' target='_blank'>documentation</a>." helper="Use a build server to build your application. You can configure your build server in the Server settings. For more info, check the <a href='https://coolify.io/docs/knowledge-base/server/build-server' class='underline' target='_blank'>documentation</a>."
instantSave id="isBuildServerEnabled" instantSave id="isBuildServerEnabled" label="Use a Build Server?"
label="Use a Build Server?" x-bind:disabled="!canUpdate" /> x-bind:disabled="!canUpdate" />
</div> </div>
@endif @endif
@endif @endif
@ -359,8 +349,7 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
helper="You need to modify the docker compose file in the git repository." helper="You need to modify the docker compose file in the git repository."
monacoEditorLanguage="yaml" useMonacoEditor /> monacoEditorLanguage="yaml" useMonacoEditor />
@endif @endif
<x-forms.textarea rows="10" readonly id="dockerCompose" <x-forms.textarea rows="10" readonly id="dockerCompose" label="Docker Compose Content"
label="Docker Compose Content"
helper="You need to modify the docker compose file in the git repository." helper="You need to modify the docker compose file in the git repository."
monacoEditorLanguage="yaml" useMonacoEditor /> monacoEditorLanguage="yaml" useMonacoEditor />
@endif @endif
@ -386,13 +375,11 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
x-bind:disabled="!canUpdate" /> x-bind:disabled="!canUpdate" />
@else @else
@if ($application->settings->is_container_label_readonly_enabled === false) @if ($application->settings->is_container_label_readonly_enabled === false)
<x-forms.input placeholder="3000,3001" id="portsExposes" <x-forms.input placeholder="3000,3001" id="portsExposes" label="Ports Exposes" readonly
label="Ports Exposes" readonly
helper="Readonly labels are disabled. You can set the ports manually in the labels section." helper="Readonly labels are disabled. You can set the ports manually in the labels section."
x-bind:disabled="!canUpdate" /> x-bind:disabled="!canUpdate" />
@else @else
<x-forms.input placeholder="3000,3001" id="portsExposes" <x-forms.input placeholder="3000,3001" id="portsExposes" label="Ports Exposes" required
label="Ports Exposes" required
helper="A comma separated list of ports your application uses. The first port will be used as default healthcheck port if nothing defined in the Healthcheck menu. Be sure to set this correctly." helper="A comma separated list of ports your application uses. The first port will be used as default healthcheck port if nothing defined in the Healthcheck menu. Be sure to set this correctly."
x-bind:disabled="!canUpdate" /> x-bind:disabled="!canUpdate" />
@endif @endif
@ -413,15 +400,14 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
<div> <div>
<div class="w-96"> <div class="w-96">
<x-forms.checkbox helper="This will add the proper proxy labels to the container." instantSave <x-forms.checkbox helper="This will add the proper proxy labels to the container." instantSave
label="Enable" id="isHttpBasicAuthEnabled" label="Enable" id="isHttpBasicAuthEnabled" x-bind:disabled="!canUpdate" />
x-bind:disabled="!canUpdate" />
</div> </div>
@if ($application->is_http_basic_auth_enabled) @if ($application->is_http_basic_auth_enabled)
<div class="flex gap-2 py-2"> <div class="flex gap-2 py-2">
<x-forms.input id="httpBasicAuthUsername" label="Username" required <x-forms.input id="httpBasicAuthUsername" label="Username" required
x-bind:disabled="!canUpdate" /> x-bind:disabled="!canUpdate" />
<x-forms.input id="httpBasicAuthPassword" type="password" label="Password" <x-forms.input id="httpBasicAuthPassword" type="password" label="Password" required
required x-bind:disabled="!canUpdate" /> x-bind:disabled="!canUpdate" />
</div> </div>
@endif @endif
</div> </div>
@ -472,8 +458,8 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
id="postDeploymentCommand" label="Post-deployment " id="postDeploymentCommand" label="Post-deployment "
helper="An optional script or command to execute in the newly built container after the deployment completes.<br>It is always executed with 'sh -c', so you do not need add it manually." /> helper="An optional script or command to execute in the newly built container after the deployment completes.<br>It is always executed with 'sh -c', so you do not need add it manually." />
@if ($application->build_pack === 'dockercompose') @if ($application->build_pack === 'dockercompose')
<x-forms.input x-bind:disabled="shouldDisable()" <x-forms.input x-bind:disabled="shouldDisable()" id="postDeploymentCommandContainer"
id="postDeploymentCommandContainer" label="Container Name" label="Container Name"
helper="The name of the container to execute within. You can leave it blank if your application only has one container." /> helper="The name of the container to execute within. You can leave it blank if your application only has one container." />
@endif @endif
</div> </div>