Merge remote-tracking branch 'origin/next' into s3-restore

Resolve merge conflicts in:
- bootstrap/helpers/shared.php (kept both formatBytes, isSafeTmpPath, and formatContainerStatus functions)
- database/migrations/2025_10_10_120002_create_cloud_init_scripts_table.php (added Schema::hasTable check)
- database/migrations/2025_10_10_120002_create_webhook_notification_settings_table.php (added Schema::hasTable check)
- resources/views/livewire/project/application/general.blade.php (formatting/whitespace)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai
2025-11-25 09:35:37 +01:00
108 changed files with 7979 additions and 2734 deletions

View File

@@ -32,7 +32,20 @@
}
@utility input-sticky {
@apply block py-1.5 w-full text-sm text-black rounded-sm border-0 ring-1 ring-inset dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300 focus-visible:outline-none focus-visible:border-l-4 focus-visible:border-l-coollabs dark:focus-visible:border-l-warning;
@apply block py-1.5 w-full text-sm text-black rounded-sm border-0 dark:bg-coolgray-100 dark:text-white disabled:bg-neutral-200 disabled:text-neutral-500 dark:disabled:bg-coolgray-100/40 focus-visible:outline-none;
box-shadow: inset 4px 0 0 transparent, inset 0 0 0 1px #e5e5e5;
&:where(.dark, .dark *) {
box-shadow: inset 4px 0 0 transparent, inset 0 0 0 1px #242424;
}
&:focus-visible {
box-shadow: inset 4px 0 0 #6b16ed, inset 0 0 0 1px #e5e5e5;
}
&:where(.dark, .dark *):focus-visible {
box-shadow: inset 4px 0 0 #fcd452, inset 0 0 0 1px #242424;
}
}
@utility input-sticky-active {
@@ -46,20 +59,49 @@
/* input, select before */
@utility input-select {
@apply block py-1.5 w-full text-sm text-black rounded-sm border-0 ring-2 ring-inset dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300 disabled:bg-neutral-200 disabled:text-neutral-500 dark:disabled:bg-coolgray-100/40 dark:disabled:ring-transparent;
@apply block py-1.5 w-full text-sm text-black rounded-sm border-0 dark:bg-coolgray-100 dark:text-white disabled:bg-neutral-200 disabled:text-neutral-500 dark:disabled:bg-coolgray-100/40;
box-shadow: inset 4px 0 0 transparent, inset 0 0 0 2px #e5e5e5;
&:where(.dark, .dark *) {
box-shadow: inset 4px 0 0 transparent, inset 0 0 0 2px #242424;
}
&:disabled {
box-shadow: none;
}
&:where(.dark, .dark *):disabled {
box-shadow: none;
}
}
/* Readonly */
@utility input {
@apply dark:read-only:text-neutral-500 dark:read-only:ring-0 dark:read-only:bg-coolgray-100/40 placeholder:text-neutral-300 dark:placeholder:text-neutral-700 read-only:text-neutral-500 read-only:bg-neutral-200;
@apply dark:read-only:text-neutral-500 dark:read-only:bg-coolgray-100/40 placeholder:text-neutral-300 dark:placeholder:text-neutral-700 read-only:text-neutral-500 read-only:bg-neutral-200;
@apply input-select;
@apply focus-visible:outline-none focus-visible:border-l-4 focus-visible:border-l-coollabs dark:focus-visible:border-l-warning;
@apply focus-visible:outline-none;
&:focus-visible {
box-shadow: inset 4px 0 0 #6b16ed, inset 0 0 0 2px #e5e5e5;
}
&:where(.dark, .dark *):focus-visible {
box-shadow: inset 4px 0 0 #fcd452, inset 0 0 0 2px #242424;
}
&:read-only {
box-shadow: none;
}
&:where(.dark, .dark *):read-only {
box-shadow: none;
}
}
@utility select {
@apply w-full;
@apply input-select;
@apply focus-visible:outline-none focus-visible:border-l-4 focus-visible:border-l-coollabs dark:focus-visible:border-l-warning;
@apply focus-visible:outline-none;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='%23000000'%3e%3cpath stroke-linecap='round' stroke-linejoin='round' d='M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9'/%3e%3c/svg%3e");
background-position: right 0.5rem center;
background-repeat: no-repeat;
@@ -69,6 +111,14 @@
&:where(.dark, .dark *) {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='%23ffffff'%3e%3cpath stroke-linecap='round' stroke-linejoin='round' d='M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9'/%3e%3c/svg%3e");
}
&:focus-visible {
box-shadow: inset 4px 0 0 #6b16ed, inset 0 0 0 2px #e5e5e5;
}
&:where(.dark, .dark *):focus-visible {
box-shadow: inset 4px 0 0 #fcd452, inset 0 0 0 2px #242424;
}
}
@utility button {

View File

@@ -97,12 +97,14 @@
}" @click.outside="open = false" class="relative">
{{-- Unified Input Container with Tags Inside --}}
<div @click="$refs.searchInput.focus()"
class="flex flex-wrap gap-1.5 max-h-40 overflow-y-auto scrollbar py-1.5 px-2 w-full text-sm rounded-sm border-0 ring-2 ring-inset ring-neutral-200 dark:ring-coolgray-300 bg-white dark:bg-coolgray-100 cursor-text px-1 focus-within:border-l-4 focus-within:border-l-coollabs dark:focus-within:border-l-warning text-black dark:text-white"
<div @click="$refs.searchInput.focus()" x-data="{ focused: false }" @focusin="focused = true" @focusout="focused = false"
class="flex flex-wrap gap-1.5 max-h-40 overflow-y-auto scrollbar py-1.5 px-2 w-full text-sm rounded-sm border-0 bg-white dark:bg-coolgray-100 cursor-text px-1 text-black dark:text-white"
:style="focused ? 'box-shadow: inset 4px 0 0 #6b16ed, inset 0 0 0 2px #e5e5e5;' : 'box-shadow: inset 4px 0 0 transparent, inset 0 0 0 2px #e5e5e5;'"
x-init="$watch('focused', () => { if ($root.classList.contains('dark') || document.documentElement.classList.contains('dark')) { $el.style.boxShadow = focused ? 'inset 4px 0 0 #fcd452, inset 0 0 0 2px #242424' : 'inset 4px 0 0 transparent, inset 0 0 0 2px #242424'; } })"
:class="{
'opacity-50': {{ $disabled ? 'true' : 'false' }}
}" wire:loading.class="opacity-50"
wire:dirty.class="dark:border-l-warning border-l-coollabs border-l-4">
wire:dirty.class="[box-shadow:inset_4px_0_0_#6b16ed,inset_0_0_0_2px_#e5e5e5] dark:[box-shadow:inset_4px_0_0_#fcd452,inset_0_0_0_2px_#242424]">
{{-- Selected Tags Inside Input --}}
<template x-for="value in selected" :key="value">
@@ -221,11 +223,13 @@
<input type="hidden" :value="selected" @required($required) />
{{-- Input Container --}}
<div @click="openDropdown()"
class="flex items-center gap-2 py-1.5 w-full text-sm rounded-sm border-0 ring-2 ring-inset ring-neutral-200 dark:ring-coolgray-300 bg-white dark:bg-coolgray-100 cursor-text focus-within:border-l-4 focus-within:border-l-coollabs dark:focus-within:border-l-warning text-black dark:text-white"
<div @click="openDropdown()" x-data="{ focused: false }" @focusin="focused = true" @focusout="focused = false"
class="flex items-center gap-2 py-1.5 w-full text-sm rounded-sm border-0 bg-white dark:bg-coolgray-100 cursor-text text-black dark:text-white"
:style="focused ? 'box-shadow: inset 4px 0 0 #6b16ed, inset 0 0 0 2px #e5e5e5;' : 'box-shadow: inset 4px 0 0 transparent, inset 0 0 0 2px #e5e5e5;'"
x-init="$watch('focused', () => { if ($root.classList.contains('dark') || document.documentElement.classList.contains('dark')) { $el.style.boxShadow = focused ? 'inset 4px 0 0 #fcd452, inset 0 0 0 2px #242424' : 'inset 4px 0 0 transparent, inset 0 0 0 2px #242424'; } })"
:class="{
'opacity-50': {{ $disabled ? 'true' : 'false' }}
}" wire:loading.class="opacity-50" wire:dirty.class="dark:border-l-warning border-l-coollabs border-l-4">
}" wire:loading.class="opacity-50" wire:dirty.class="[box-shadow:inset_4px_0_0_#6b16ed,inset_0_0_0_2px_#e5e5e5] dark:[box-shadow:inset_4px_0_0_#fcd452,inset_0_0_0_2px_#242424]">
{{-- Display Selected Value or Search Input --}}
<div class="flex-1 flex items-center min-w-0 px-1">

View File

@@ -27,7 +27,7 @@
@endif
<input autocomplete="{{ $autocomplete }}" value="{{ $value }}"
{{ $attributes->merge(['class' => $defaultClass]) }} @required($required)
@if ($modelBinding !== 'null') wire:model={{ $modelBinding }} wire:dirty.class="dark:border-l-warning border-l-coollabs border-l-4" @endif
@if ($modelBinding !== 'null') wire:model={{ $modelBinding }} wire:dirty.class="[box-shadow:inset_4px_0_0_#6b16ed,inset_0_0_0_2px_#e5e5e5] dark:[box-shadow:inset_4px_0_0_#fcd452,inset_0_0_0_2px_#242424]" @endif
wire:loading.attr="disabled"
type="{{ $type }}" @readonly($readonly) @disabled($disabled) id="{{ $htmlId }}"
name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}"
@@ -38,7 +38,7 @@
@else
<input autocomplete="{{ $autocomplete }}" @if ($value) value="{{ $value }}" @endif
{{ $attributes->merge(['class' => $defaultClass]) }} @required($required) @readonly($readonly)
@if ($modelBinding !== 'null') wire:model={{ $modelBinding }} wire:dirty.class="dark:border-l-warning border-l-coollabs border-l-4" @endif
@if ($modelBinding !== 'null') wire:model={{ $modelBinding }} wire:dirty.class="[box-shadow:inset_4px_0_0_#6b16ed,inset_0_0_0_2px_#e5e5e5] dark:[box-shadow:inset_4px_0_0_#fcd452,inset_0_0_0_2px_#242424]" @endif
wire:loading.attr="disabled"
type="{{ $type }}" @disabled($disabled) min="{{ $attributes->get('min') }}"
max="{{ $attributes->get('max') }}" minlength="{{ $attributes->get('minlength') }}"

View File

@@ -12,7 +12,7 @@
@endif
<select {{ $attributes->merge(['class' => $defaultClass]) }} @disabled($disabled) @required($required)
wire:loading.attr="disabled" name={{ $modelBinding }} id="{{ $htmlId }}"
@if ($attributes->whereStartsWith('wire:model')->first()) {{ $attributes->whereStartsWith('wire:model')->first() }} wire:dirty.class="dark:border-l-warning border-l-coollabs border-l-4" @else wire:model={{ $modelBinding }} wire:dirty.class="dark:border-l-warning border-l-coollabs border-l-4" @endif>
@if ($attributes->whereStartsWith('wire:model')->first()) {{ $attributes->whereStartsWith('wire:model')->first() }} wire:dirty.class="[box-shadow:inset_4px_0_0_#6b16ed,inset_0_0_0_2px_#e5e5e5] dark:[box-shadow:inset_4px_0_0_#fcd452,inset_0_0_0_2px_#242424]" @else wire:model={{ $modelBinding }} wire:dirty.class="[box-shadow:inset_4px_0_0_#6b16ed,inset_0_0_0_2px_#e5e5e5] dark:[box-shadow:inset_4px_0_0_#fcd452,inset_0_0_0_2px_#242424]" @endif>
{{ $slot }}
</select>
@error($modelBinding)

View File

@@ -45,16 +45,16 @@
@endif
<input x-cloak x-show="type === 'password'" value="{{ $value }}"
{{ $attributes->merge(['class' => $defaultClassInput]) }} @required($required)
@if ($modelBinding !== 'null') wire:model={{ $modelBinding }} wire:dirty.class="dark:border-l-warning border-l-coollabs border-l-4" @endif
@if ($modelBinding !== 'null') wire:model={{ $modelBinding }} wire:dirty.class="[box-shadow:inset_4px_0_0_#6b16ed,inset_0_0_0_2px_#e5e5e5] dark:[box-shadow:inset_4px_0_0_#fcd452,inset_0_0_0_2px_#242424]" @endif
wire:loading.attr="disabled"
type="{{ $type }}" @readonly($readonly) @disabled($disabled) id="{{ $htmlId }}"
name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}"
aria-placeholder="{{ $attributes->get('placeholder') }}">
<textarea minlength="{{ $minlength }}" maxlength="{{ $maxlength }}" x-cloak x-show="type !== 'password'"
placeholder="{{ $placeholder }}" {{ $attributes->merge(['class' => $defaultClass]) }}
@if ($realtimeValidation) wire:model.debounce.200ms="{{ $modelBinding }}" wire:dirty.class="dark:border-l-warning border-l-coollabs border-l-4"
@if ($realtimeValidation) wire:model.debounce.200ms="{{ $modelBinding }}" wire:dirty.class="[box-shadow:inset_4px_0_0_#6b16ed,inset_0_0_0_2px_#e5e5e5] dark:[box-shadow:inset_4px_0_0_#fcd452,inset_0_0_0_2px_#242424]"
@else
wire:model={{ $value ?? $modelBinding }} wire:dirty.class="dark:border-l-warning border-l-coollabs border-l-4" @endif
wire:model={{ $value ?? $modelBinding }} wire:dirty.class="[box-shadow:inset_4px_0_0_#6b16ed,inset_0_0_0_2px_#e5e5e5] dark:[box-shadow:inset_4px_0_0_#fcd452,inset_0_0_0_2px_#242424]" @endif
@disabled($disabled) @readonly($readonly) @required($required) id="{{ $htmlId }}"
name="{{ $name }}" name={{ $modelBinding }}
@if ($autofocus) x-ref="autofocusInput" @endif></textarea>
@@ -64,9 +64,9 @@
<textarea minlength="{{ $minlength }}" maxlength="{{ $maxlength }}"
{{ $allowTab ? '@keydown.tab=handleKeydown' : '' }} placeholder="{{ $placeholder }}"
{{ !$spellcheck ? 'spellcheck=false' : '' }} {{ $attributes->merge(['class' => $defaultClass]) }}
@if ($realtimeValidation) wire:model.debounce.200ms="{{ $modelBinding }}" wire:dirty.class="dark:border-l-warning border-l-coollabs border-l-4"
@if ($realtimeValidation) wire:model.debounce.200ms="{{ $modelBinding }}" wire:dirty.class="[box-shadow:inset_4px_0_0_#6b16ed,inset_0_0_0_2px_#e5e5e5] dark:[box-shadow:inset_4px_0_0_#fcd452,inset_0_0_0_2px_#242424]"
@else
wire:model={{ $value ?? $modelBinding }} wire:dirty.class="dark:border-l-warning border-l-coollabs border-l-4" @endif
wire:model={{ $value ?? $modelBinding }} wire:dirty.class="[box-shadow:inset_4px_0_0_#6b16ed,inset_0_0_0_2px_#e5e5e5] dark:[box-shadow:inset_4px_0_0_#fcd452,inset_0_0_0_2px_#242424]" @endif
@disabled($disabled) @readonly($readonly) @required($required) id="{{ $htmlId }}"
name="{{ $name }}" name={{ $modelBinding }}
@if ($autofocus) x-ref="autofocusInput" @endif></textarea>

View File

@@ -1,15 +1,33 @@
@props([
'status' => 'Degraded',
])
@php
// Handle both colon format (backend) and parentheses format (from services.blade.php)
// degraded:unhealthy → Degraded (unhealthy)
// degraded (unhealthy) → degraded (unhealthy) (already formatted, display as-is)
if (str($status)->contains('(')) {
// Already in parentheses format from services.blade.php - use as-is
$displayStatus = $status;
$healthStatus = str($status)->after('(')->before(')')->trim()->value();
} elseif (str($status)->contains(':') && !str($status)->startsWith('Proxy')) {
// Colon format from backend - transform it
$parts = explode(':', $status);
$displayStatus = str($parts[0])->headline();
$healthStatus = $parts[1] ?? null;
} else {
// Simple status without health
$displayStatus = str($status)->headline();
$healthStatus = null;
}
@endphp
<div class="flex items-center" >
<x-loading wire:loading.delay.longer />
<span wire:loading.remove.delay.longer class="flex items-center">
<div class="badge badge-warning"></div>
<div class="pl-2 pr-1 text-xs font-bold dark:text-warning">
{{ str($status)->before(':')->headline() }}
</div>
@if (!str($status)->startsWith('Proxy') && !str($status)->contains('('))
<div class="text-xs dark:text-warning">({{ str($status)->after(':') }})</div>
<div class="pl-2 pr-1 text-xs font-bold dark:text-warning">{{ $displayStatus }}</div>
@if ($healthStatus && !str($displayStatus)->contains('('))
<div class="text-xs dark:text-warning">({{ $healthStatus }})</div>
@endif
</span>
</div>

View File

@@ -5,9 +5,9 @@
])
@if (str($resource->status)->startsWith('running'))
<x-status.running :status="$resource->status" :title="$title" :lastDeploymentLink="$lastDeploymentLink" />
@elseif(str($resource->status)->startsWith('restarting') ||
str($resource->status)->startsWith('starting') ||
str($resource->status)->startsWith('degraded'))
@elseif(str($resource->status)->startsWith('degraded'))
<x-status.degraded :status="$resource->status" :title="$title" :lastDeploymentLink="$lastDeploymentLink" />
@elseif(str($resource->status)->startsWith('restarting') || str($resource->status)->startsWith('starting'))
<x-status.restarting :status="$resource->status" :title="$title" :lastDeploymentLink="$lastDeploymentLink" />
@else
<x-status.stopped :status="$resource->status" />

View File

@@ -4,6 +4,26 @@
'lastDeploymentLink' => null,
'noLoading' => false,
])
@php
// Handle both colon format (backend) and parentheses format (from services.blade.php)
// starting:unknown → Starting (unknown)
// starting (unknown) → starting (unknown) (already formatted, display as-is)
if (str($status)->contains('(')) {
// Already in parentheses format from services.blade.php - use as-is
$displayStatus = $status;
$healthStatus = str($status)->after('(')->before(')')->trim()->value();
} elseif (str($status)->contains(':') && !str($status)->startsWith('Proxy')) {
// Colon format from backend - transform it
$parts = explode(':', $status);
$displayStatus = str($parts[0])->headline();
$healthStatus = $parts[1] ?? null;
} else {
// Simple status without health
$displayStatus = str($status)->headline();
$healthStatus = null;
}
@endphp
<div class="flex items-center">
@if (!$noLoading)
<x-loading wire:loading.delay.longer />
@@ -13,14 +33,14 @@
<div class="pl-2 pr-1 text-xs font-bold dark:text-warning" @if($title) title="{{$title}}" @endif>
@if ($lastDeploymentLink)
<a href="{{ $lastDeploymentLink }}" target="_blank" class="underline cursor-pointer">
{{ str($status)->before(':')->headline() }}
{{ $displayStatus }}
</a>
@else
{{ str($status)->before(':')->headline() }}
{{ $displayStatus }}
@endif
</div>
@if (!str($status)->startsWith('Proxy') && !str($status)->contains('('))
<div class="text-xs dark:text-warning">({{ str($status)->after(':') }})</div>
@if ($healthStatus && !str($displayStatus)->contains('('))
<div class="text-xs dark:text-warning">({{ $healthStatus }})</div>
@endif
</span>
</div>

View File

@@ -4,6 +4,26 @@
'lastDeploymentLink' => null,
'noLoading' => false,
])
@php
// Handle both colon format (backend) and parentheses format (from services.blade.php)
// running:healthy → Running (healthy)
// running (healthy) → running (healthy) (already formatted, display as-is)
if (str($status)->contains('(')) {
// Already in parentheses format from services.blade.php - use as-is
$displayStatus = $status;
$healthStatus = str($status)->after('(')->before(')')->trim()->value();
} elseif (str($status)->contains(':') && !str($status)->startsWith('Proxy')) {
// Colon format from backend - transform it
$parts = explode(':', $status);
$displayStatus = str($parts[0])->headline();
$healthStatus = $parts[1] ?? null;
} else {
// Simple status without health
$displayStatus = str($status)->headline();
$healthStatus = null;
}
@endphp
<div class="flex items-center">
<div class="flex items-center">
<div wire:loading.delay.longer wire:target="checkProxy(true)" class="badge badge-warning"></div>
@@ -12,21 +32,27 @@
@if ($title) title="{{ $title }}" @endif>
@if ($lastDeploymentLink)
<a href="{{ $lastDeploymentLink }}" target="_blank" class="underline cursor-pointer">
{{ str($status)->before(':')->headline() }}
{{ $displayStatus }}
</a>
@else
{{ str($status)->before(':')->headline() }}
{{ $displayStatus }}
@endif
</div>
@if ($healthStatus && !str($displayStatus)->contains('('))
<div class="text-xs text-success">({{ $healthStatus }})</div>
@endif
@php
$showUnknownHelper =
!str($status)->startsWith('Proxy') &&
(str($status)->contains('unknown') || str($healthStatus)->contains('unknown'));
$showUnhealthyHelper =
!str($status)->startsWith('Proxy') &&
!str($status)->contains('(') &&
str($status)->contains('unhealthy');
(str($status)->contains('unhealthy') || str($healthStatus)->contains('unhealthy'));
@endphp
@if ($showUnhealthyHelper)
@if ($showUnknownHelper)
<div class="px-2">
<x-helper
helper="Unhealthy state. <span class='dark:text-warning text-coollabs'>This doesn't mean that the resource is malfunctioning.</span><br><br>- If the resource is accessible, it indicates that no health check is configured - it is not mandatory.<br>- If the resource is not accessible (returning 404 or 503), it may indicate that a health check is needed and has not passed. <span class='dark:text-warning text-coollabs'>Your action is required.</span><br><br>More details in the <a href='https://coolify.io/docs/knowledge-base/proxy/traefik/healthchecks' class='underline dark:text-warning text-coollabs' target='_blank'>documentation</a>.">
helper="No health check configured. <span class='dark:text-warning text-coollabs'>The resource may be functioning normally.</span><br><br>Traefik and Caddy will route traffic to this container even without a health check. However, configuring a health check is recommended to ensure the resource is ready before receiving traffic.<br><br>More details in the <a href='https://coolify.io/docs/knowledge-base/proxy/traefik/healthchecks' class='underline dark:text-warning text-coollabs' target='_blank'>documentation</a>.">
<x-slot:icon>
<svg class="hidden w-4 h-4 dark:text-warning lg:block" viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg">
@@ -36,6 +62,22 @@
</svg>
</x-slot:icon>
</x-helper>
</div>
@endif
@if ($showUnhealthyHelper)
<div class="px-2">
<x-helper
helper="Unhealthy state. <span class='dark:text-warning text-coollabs'>The health check is failing.</span><br><br>This resource will <span class='dark:text-warning text-coollabs'>NOT work with Traefik</span> as it expects a healthy state. Your action is required to fix the health check or the underlying issue causing it to fail.<br><br>More details in the <a href='https://coolify.io/docs/knowledge-base/proxy/traefik/healthchecks' class='underline dark:text-warning text-coollabs' target='_blank'>documentation</a>.">
<x-slot:icon>
<svg class="hidden w-4 h-4 dark:text-warning lg:block" viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16">
</path>
</svg>
</x-slot:icon>
</x-helper>
</div>
@endif
</div>

View File

@@ -1,13 +1,16 @@
@if (str($complexStatus)->contains('running'))
<x-status.running :status="$complexStatus" />
@elseif(str($complexStatus)->contains('starting'))
<x-status.restarting :status="$complexStatus" />
@elseif(str($complexStatus)->contains('restarting'))
<x-status.restarting :status="$complexStatus" />
@elseif(str($complexStatus)->contains('degraded'))
<x-status.degraded :status="$complexStatus" />
@php
$displayStatus = formatContainerStatus($complexStatus);
@endphp
@if (str($displayStatus)->lower()->contains('running'))
<x-status.running :status="$displayStatus" />
@elseif(str($displayStatus)->lower()->contains('starting'))
<x-status.restarting :status="$displayStatus" />
@elseif(str($displayStatus)->lower()->contains('restarting'))
<x-status.restarting :status="$displayStatus" />
@elseif(str($displayStatus)->lower()->contains('degraded'))
<x-status.degraded :status="$displayStatus" />
@else
<x-status.stopped :status="$complexStatus" />
<x-status.stopped :status="$displayStatus" />
@endif
@if (!str($complexStatus)->contains('exited') && $showRefreshButton)
<button wire:loading.remove.delay.shortest wire:target="manualCheckStatus" title="Refresh Status" wire:click='manualCheckStatus'

View File

@@ -2,12 +2,47 @@
'status' => 'Stopped',
'noLoading' => false,
])
@php
// Handle both colon format (backend) and parentheses format (from services.blade.php)
// For exited containers, health status is hidden (health checks don't run on stopped containers)
// exited:unhealthy → Exited
// exited (unhealthy) → Exited
if (str($status)->contains('(')) {
// Already in parentheses format from services.blade.php - use as-is
$displayStatus = $status;
$healthStatus = str($status)->after('(')->before(')')->trim()->value();
// Don't show health status for exited containers (health checks don't run on stopped containers)
if (str($displayStatus)->lower()->contains('exited')) {
$displayStatus = str($status)->before('(')->trim()->headline();
$healthStatus = null;
}
} elseif (str($status)->contains(':')) {
// Colon format from backend - transform it
$parts = explode(':', $status);
$displayStatus = str($parts[0])->headline();
$healthStatus = $parts[1] ?? null;
// Don't show health status for exited containers (health checks don't run on stopped containers)
if (str($displayStatus)->lower()->contains('exited')) {
$healthStatus = null;
}
} else {
// Simple status without health
$displayStatus = str($status)->headline();
$healthStatus = null;
}
@endphp
<div class="flex items-center">
@if (!$noLoading)
<x-loading wire:loading.delay.longer />
@endif
<span wire:loading.remove.delay.longer class="flex items-center">
<div class="badge badge-error "></div>
<div class="pl-2 pr-1 text-xs font-bold text-error">{{ str($status)->before(':')->headline() }}</div>
<div class="pl-2 pr-1 text-xs font-bold text-error">{{ $displayStatus }}</div>
@if ($healthStatus && !str($displayStatus)->contains('('))
<div class="text-xs text-error">({{ $healthStatus }})</div>
@endif
</span>
</div>

View File

@@ -5,7 +5,7 @@
])>
@if ($activity)
@if (isset($header))
<div class="flex gap-2 pb-2 flex-shrink-0">
<div class="flex gap-2 pb-2 flex-shrink-0" @if ($isPollingActive) wire:poll.1000ms @endif>
<h3>{{ $header }}</h3>
@if ($isPollingActive)
<x-loading />

View File

@@ -546,6 +546,13 @@
</div>
</div>
@if ($prerequisiteInstallAttempts > 0)
<div class="p-6 bg-neutral-50 dark:bg-coolgray-200 rounded-lg border border-neutral-200 dark:border-coolgray-400">
<h3 class="font-bold text-black dark:text-white mb-4">Installing Prerequisites</h3>
<livewire:activity-monitor header="Prerequisites Installation Logs" :showWaiting="false" />
</div>
@endif
<x-slide-over closeWithX fullScreen>
<x-slot:title>Server Validation</x-slot:title>
<x-slot:content>

View File

@@ -254,7 +254,8 @@
class="fixed top-0 left-0 z-99 flex items-start justify-center w-screen h-screen pt-[10vh]">
<div @click="closeModal()" class="absolute inset-0 w-full h-full bg-black/50 backdrop-blur-sm">
</div>
<div x-show="modalOpen" x-trap.inert="modalOpen" x-init="$watch('modalOpen', value => { document.body.style.overflow = value ? 'hidden' : '' })"
<div x-show="modalOpen" x-trap.inert="modalOpen"
x-init="$watch('modalOpen', value => { document.body.style.overflow = value ? 'hidden' : '' })"
x-transition:enter="ease-out duration-200" x-transition:enter-start="opacity-0 -translate-y-4 scale-95"
x-transition:enter-end="opacity-100 translate-y-0 scale-100" x-transition:leave="ease-in duration-150"
x-transition:leave-start="opacity-100 translate-y-0 scale-100"
@@ -271,8 +272,7 @@
</svg>
<svg x-show="isLoadingInitialData" x-cloak class="animate-spin h-5 w-5 text-warning"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"
stroke-width="4">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4">
</circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
@@ -311,8 +311,8 @@
class="text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M15 19l-7-7 7-7" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 19l-7-7 7-7" />
</svg>
</button>
<div>
@@ -327,13 +327,11 @@
</div>
</div>
@if ($loadingServers)
<div
class="flex items-center gap-3 p-3 bg-neutral-50 dark:bg-coolgray-200 rounded-lg">
<svg class="animate-spin h-5 w-5 text-yellow-500"
xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10"
stroke="currentColor" stroke-width="4"></circle>
<div class="flex items-center gap-3 p-3 bg-neutral-50 dark:bg-coolgray-200 rounded-lg">
<svg class="animate-spin h-5 w-5 text-yellow-500" xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"
stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
</path>
@@ -343,8 +341,7 @@
</div>
@elseif (count($availableServers) > 0)
@foreach ($availableServers as $index => $server)
<button type="button"
wire:click="selectServer({{ $server['id'] }}, true)"
<button type="button" wire:click="selectServer({{ $server['id'] }}, true)"
class="search-result-item w-full text-left block px-4 py-3 min-h-[4rem] hover:bg-yellow-50 dark:hover:bg-yellow-900/20 transition-colors focus:outline-none focus:bg-yellow-100 dark:focus:bg-yellow-900/30 border-b border-neutral-100 dark:border-coolgray-300 last:border-0">
<div class="flex items-center justify-between gap-3 min-h-[2.5rem]">
<div class="flex-1 min-w-0">
@@ -352,8 +349,7 @@
{{ $server['name'] }}
</div>
@if (!empty($server['description']))
<div
class="text-xs text-neutral-500 dark:text-neutral-400">
<div class="text-xs text-neutral-500 dark:text-neutral-400">
{{ $server['description'] }}
</div>
@else
@@ -363,10 +359,10 @@
@endif
</div>
<svg xmlns="http://www.w3.org/2000/svg"
class="shrink-0 h-5 w-5 text-yellow-500 dark:text-yellow-400"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M9 5l7 7-7 7" />
class="shrink-0 h-5 w-5 text-yellow-500 dark:text-yellow-400" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 5l7 7-7 7" />
</svg>
</div>
</button>
@@ -388,10 +384,10 @@
<button type="button"
@click="$wire.set('searchQuery', ''); setTimeout(() => $refs.searchInput.focus(), 100)"
class="text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M15 19l-7-7 7-7" />
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 19l-7-7 7-7" />
</svg>
</button>
<div>
@@ -406,13 +402,11 @@
</div>
</div>
@if ($loadingDestinations)
<div
class="flex items-center gap-3 p-3 bg-neutral-50 dark:bg-coolgray-200 rounded-lg">
<svg class="animate-spin h-5 w-5 text-yellow-500"
xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10"
stroke="currentColor" stroke-width="4"></circle>
<div class="flex items-center gap-3 p-3 bg-neutral-50 dark:bg-coolgray-200 rounded-lg">
<svg class="animate-spin h-5 w-5 text-yellow-500" xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"
stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
</path>
@@ -422,25 +416,22 @@
</div>
@elseif (count($availableDestinations) > 0)
@foreach ($availableDestinations as $index => $destination)
<button type="button"
wire:click="selectDestination('{{ $destination['uuid'] }}', true)"
<button type="button" wire:click="selectDestination('{{ $destination['uuid'] }}', true)"
class="search-result-item w-full text-left block px-4 py-3 min-h-[4rem] hover:bg-yellow-50 dark:hover:bg-yellow-900/20 transition-colors focus:outline-none focus:bg-yellow-100 dark:focus:bg-yellow-900/30 border-b border-neutral-100 dark:border-coolgray-300 last:border-0">
<div
class="flex items-center justify-between gap-3 min-h-[2.5rem]">
<div class="flex items-center justify-between gap-3 min-h-[2.5rem]">
<div class="flex-1 min-w-0">
<div class="font-medium text-neutral-900 dark:text-white">
{{ $destination['name'] }}
</div>
<div
class="text-xs text-neutral-500 dark:text-neutral-400">
<div class="text-xs text-neutral-500 dark:text-neutral-400">
Network: {{ $destination['network'] }}
</div>
</div>
<svg xmlns="http://www.w3.org/2000/svg"
class="shrink-0 h-5 w-5 text-yellow-500 dark:text-yellow-400"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M9 5l7 7-7 7" />
class="shrink-0 h-5 w-5 text-yellow-500 dark:text-yellow-400" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 5l7 7-7 7" />
</svg>
</div>
</button>
@@ -462,10 +453,10 @@
<button type="button"
@click="$wire.set('searchQuery', ''); setTimeout(() => $refs.searchInput.focus(), 100)"
class="text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M15 19l-7-7 7-7" />
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 19l-7-7 7-7" />
</svg>
</button>
<div>
@@ -480,13 +471,11 @@
</div>
</div>
@if ($loadingProjects)
<div
class="flex items-center gap-3 p-3 bg-neutral-50 dark:bg-coolgray-200 rounded-lg">
<svg class="animate-spin h-5 w-5 text-yellow-500"
xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10"
stroke="currentColor" stroke-width="4"></circle>
<div class="flex items-center gap-3 p-3 bg-neutral-50 dark:bg-coolgray-200 rounded-lg">
<svg class="animate-spin h-5 w-5 text-yellow-500" xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"
stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
</path>
@@ -496,18 +485,15 @@
</div>
@elseif (count($availableProjects) > 0)
@foreach ($availableProjects as $index => $project)
<button type="button"
wire:click="selectProject('{{ $project['uuid'] }}', true)"
<button type="button" wire:click="selectProject('{{ $project['uuid'] }}', true)"
class="search-result-item w-full text-left block px-4 py-3 min-h-[4rem] hover:bg-yellow-50 dark:hover:bg-yellow-900/20 transition-colors focus:outline-none focus:bg-yellow-100 dark:focus:bg-yellow-900/30 border-b border-neutral-100 dark:border-coolgray-300 last:border-0">
<div
class="flex items-center justify-between gap-3 min-h-[2.5rem]">
<div class="flex items-center justify-between gap-3 min-h-[2.5rem]">
<div class="flex-1 min-w-0">
<div class="font-medium text-neutral-900 dark:text-white">
{{ $project['name'] }}
</div>
@if (!empty($project['description']))
<div
class="text-xs text-neutral-500 dark:text-neutral-400">
<div class="text-xs text-neutral-500 dark:text-neutral-400">
{{ $project['description'] }}
</div>
@else
@@ -517,10 +503,10 @@
@endif
</div>
<svg xmlns="http://www.w3.org/2000/svg"
class="shrink-0 h-5 w-5 text-yellow-500 dark:text-yellow-400"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M9 5l7 7-7 7" />
class="shrink-0 h-5 w-5 text-yellow-500 dark:text-yellow-400" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 5l7 7-7 7" />
</svg>
</div>
</button>
@@ -542,10 +528,10 @@
<button type="button"
@click="$wire.set('searchQuery', ''); setTimeout(() => $refs.searchInput.focus(), 100)"
class="text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M15 19l-7-7 7-7" />
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 19l-7-7 7-7" />
</svg>
</button>
<div>
@@ -560,13 +546,11 @@
</div>
</div>
@if ($loadingEnvironments)
<div
class="flex items-center gap-3 p-3 bg-neutral-50 dark:bg-coolgray-200 rounded-lg">
<svg class="animate-spin h-5 w-5 text-yellow-500"
xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10"
stroke="currentColor" stroke-width="4"></circle>
<div class="flex items-center gap-3 p-3 bg-neutral-50 dark:bg-coolgray-200 rounded-lg">
<svg class="animate-spin h-5 w-5 text-yellow-500" xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"
stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
</path>
@@ -576,18 +560,15 @@
</div>
@elseif (count($availableEnvironments) > 0)
@foreach ($availableEnvironments as $index => $environment)
<button type="button"
wire:click="selectEnvironment('{{ $environment['uuid'] }}', true)"
<button type="button" wire:click="selectEnvironment('{{ $environment['uuid'] }}', true)"
class="search-result-item w-full text-left block px-4 py-3 min-h-[4rem] hover:bg-yellow-50 dark:hover:bg-yellow-900/20 transition-colors focus:outline-none focus:bg-yellow-100 dark:focus:bg-yellow-900/30 border-b border-neutral-100 dark:border-coolgray-300 last:border-0">
<div
class="flex items-center justify-between gap-3 min-h-[2.5rem]">
<div class="flex items-center justify-between gap-3 min-h-[2.5rem]">
<div class="flex-1 min-w-0">
<div class="font-medium text-neutral-900 dark:text-white">
{{ $environment['name'] }}
</div>
@if (!empty($environment['description']))
<div
class="text-xs text-neutral-500 dark:text-neutral-400">
<div class="text-xs text-neutral-500 dark:text-neutral-400">
{{ $environment['description'] }}
</div>
@else
@@ -597,10 +578,10 @@
@endif
</div>
<svg xmlns="http://www.w3.org/2000/svg"
class="shrink-0 h-5 w-5 text-yellow-500 dark:text-yellow-400"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M9 5l7 7-7 7" />
class="shrink-0 h-5 w-5 text-yellow-500 dark:text-yellow-400" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 5l7 7-7 7" />
</svg>
</div>
</button>
@@ -639,8 +620,7 @@
<div class="flex items-center justify-between gap-3">
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2 mb-1">
<span
class="font-medium text-neutral-900 dark:text-white truncate">
<span class="font-medium text-neutral-900 dark:text-white truncate">
{{ $result['name'] }}
</span>
<span
@@ -661,15 +641,13 @@
</span>
</div>
@if (!empty($result['project']) && !empty($result['environment']))
<div
class="text-xs text-neutral-500 dark:text-neutral-400 mb-1">
<div class="text-xs text-neutral-500 dark:text-neutral-400 mb-1">
{{ $result['project'] }} /
{{ $result['environment'] }}
</div>
@endif
@if (!empty($result['description']))
<div
class="text-sm text-neutral-600 dark:text-neutral-400">
<div class="text-sm text-neutral-600 dark:text-neutral-400">
{{ Str::limit($result['description'], 80) }}
</div>
@endif
@@ -677,8 +655,8 @@
<svg xmlns="http://www.w3.org/2000/svg"
class="shrink-0 h-5 w-5 text-neutral-300 dark:text-neutral-600 self-center"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M9 5l7 7-7 7" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 5l7 7-7 7" />
</svg>
</div>
</a>
@@ -708,16 +686,15 @@
<div
class="flex-shrink-0 w-10 h-10 rounded-lg bg-yellow-100 dark:bg-yellow-900/40 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-yellow-600 dark:text-yellow-400"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M12 4v16m8-8H4" />
class="h-5 w-5 text-yellow-600 dark:text-yellow-400" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 4v16m8-8H4" />
</svg>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2 mb-1">
<div
class="font-medium text-neutral-900 dark:text-white truncate">
<div class="font-medium text-neutral-900 dark:text-white truncate">
{{ $item['name'] }}
</div>
@if (isset($item['quickcommand']))
@@ -725,8 +702,7 @@
class="text-xs text-neutral-500 dark:text-neutral-400 shrink-0">{{ $item['quickcommand'] }}</span>
@endif
</div>
<div
class="text-sm text-neutral-600 dark:text-neutral-400 truncate">
<div class="text-sm text-neutral-600 dark:text-neutral-400 truncate">
{{ $item['description'] }}
</div>
</div>
@@ -734,8 +710,8 @@
<svg xmlns="http://www.w3.org/2000/svg"
class="shrink-0 h-5 w-5 text-yellow-500 dark:text-yellow-400 self-center"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M9 5l7 7-7 7" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 5l7 7-7 7" />
</svg>
</div>
</button>
@@ -820,8 +796,7 @@
class="flex-shrink-0 w-10 h-10 rounded-lg bg-yellow-100 dark:bg-yellow-900/40 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-yellow-600 dark:text-yellow-400"
fill="none" viewBox="0 0 24 24"
stroke="currentColor">
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
@@ -869,14 +844,6 @@
<p class="mt-2 text-xs text-neutral-400 dark:text-neutral-500">
💡 Tip: Search for service names like "wordpress", "postgres", or "redis"
</p>
<div class="mt-4">
<a href="{{ route('onboarding') }}" class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-coollabs dark:bg-warning hover:bg-coollabs-100 dark:hover:bg-warning/90 rounded-lg transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
View Onboarding Guide
</a>
</div>
</div>
</div>
</template>
@@ -897,12 +864,10 @@
if (firstInput) firstInput.focus();
}, 200);
}
})"
class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen">
<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" @click="modalOpen=false"
})" class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen">
<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" @click="modalOpen=false"
class="absolute inset-0 w-full h-full bg-black/20 backdrop-blur-xs"></div>
<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"
@@ -915,8 +880,8 @@
<h3 class="text-2xl font-bold">New Project</h3>
<button @click="modalOpen=false"
class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 rounded-full dark:text-white hover:bg-neutral-100 dark:hover:bg-coolgray-300 outline-0 focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning">
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
@@ -939,12 +904,10 @@
if (firstInput) firstInput.focus();
}, 200);
}
})"
class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen">
<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" @click="modalOpen=false"
})" class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen">
<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" @click="modalOpen=false"
class="absolute inset-0 w-full h-full bg-black/20 backdrop-blur-xs"></div>
<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"
@@ -957,8 +920,8 @@
<h3 class="text-2xl font-bold">New Server</h3>
<button @click="modalOpen=false"
class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 rounded-full dark:text-white hover:bg-neutral-100 dark:hover:bg-coolgray-300 outline-0 focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning">
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
@@ -981,12 +944,10 @@
if (firstInput) firstInput.focus();
}, 200);
}
})"
class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen">
<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" @click="modalOpen=false"
})" class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen">
<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" @click="modalOpen=false"
class="absolute inset-0 w-full h-full bg-black/20 backdrop-blur-xs"></div>
<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"
@@ -999,8 +960,8 @@
<h3 class="text-2xl font-bold">New Team</h3>
<button @click="modalOpen=false"
class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 rounded-full dark:text-white hover:bg-neutral-100 dark:hover:bg-coolgray-300 outline-0 focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning">
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
@@ -1023,12 +984,10 @@
if (firstInput) firstInput.focus();
}, 200);
}
})"
class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen">
<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" @click="modalOpen=false"
})" class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen">
<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" @click="modalOpen=false"
class="absolute inset-0 w-full h-full bg-black/20 backdrop-blur-xs"></div>
<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"
@@ -1041,8 +1000,8 @@
<h3 class="text-2xl font-bold">New S3 Storage</h3>
<button @click="modalOpen=false"
class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 rounded-full dark:text-white hover:bg-neutral-100 dark:hover:bg-coolgray-300 outline-0 focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning">
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
@@ -1065,12 +1024,10 @@
if (firstInput) firstInput.focus();
}, 200);
}
})"
class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen">
<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" @click="modalOpen=false"
})" class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen">
<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" @click="modalOpen=false"
class="absolute inset-0 w-full h-full bg-black/20 backdrop-blur-xs"></div>
<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"
@@ -1083,8 +1040,8 @@
<h3 class="text-2xl font-bold">New Private Key</h3>
<button @click="modalOpen=false"
class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 rounded-full dark:text-white hover:bg-neutral-100 dark:hover:bg-coolgray-300 outline-0 focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning">
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
@@ -1107,12 +1064,10 @@
if (firstInput) firstInput.focus();
}, 200);
}
})"
class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen">
<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" @click="modalOpen=false"
})" class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen">
<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" @click="modalOpen=false"
class="absolute inset-0 w-full h-full bg-black/20 backdrop-blur-xs"></div>
<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"
@@ -1125,8 +1080,8 @@
<h3 class="text-2xl font-bold">New GitHub App</h3>
<button @click="modalOpen=false"
class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 rounded-full dark:text-white hover:bg-neutral-100 dark:hover:bg-coolgray-300 outline-0 focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning">
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
@@ -1139,4 +1094,4 @@
</template>
</div>
</div>
</div>

View File

@@ -30,15 +30,15 @@
@endif
<a class="menu-item flex items-center gap-2" wire:current.exact="menu-item-active"
href="{{ route('project.application.servers', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Servers
@if (str($application->status)->contains('degraded'))
<span title="Some servers are unavailable">
@if ($application->server_status == false)
<span title="One or more servers are unreachable or misconfigured.">
<svg class="w-4 h-4 text-error" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16" />
</svg>
</span>
@elseif ($application->server_status == false)
<span title="The underlying server(s) has problems.">
@elseif ($application->additional_servers()->exists() && str($application->status)->contains('degraded'))
<span title="Application is in degraded state across multiple servers.">
<svg class="w-4 h-4 text-error" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16" />

View File

@@ -12,6 +12,12 @@
<div>{{ $application->compose_parsing_version }}</div>
@endif
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
@if ($application->build_pack === 'dockercompose')
<x-forms.button canGate="update" :canResource="$application" wire:target='initLoadingCompose'
x-on:click="$wire.dispatch('loadCompose', false)">
{{ $application->docker_compose_raw ? 'Reload Compose File' : 'Load Compose File' }}
</x-forms.button>
@endif
</div>
<div>General configuration for your application.</div>
<div class="flex flex-col gap-2 py-4">
@@ -40,9 +46,10 @@
@if ($application->build_pack === 'dockercompose')
@if (
!is_null($parsedServices) &&
!is_null($parsedServices) &&
count($parsedServices) > 0 &&
!$application->settings->is_raw_compose_deployment_enabled)
!$application->settings->is_raw_compose_deployment_enabled
)
<h3 class="pt-6">Domains</h3>
@foreach (data_get($parsedServices, 'services') as $serviceName => $service)
@if (!isDatabaseImage(data_get($service, 'image')))
@@ -73,11 +80,11 @@
buttonTitle="Generate Default Nginx Configuration" buttonFullWidth
submitAction="generateNginxConfiguration('{{ $application->settings->is_spa ? 'spa' : 'static' }}')"
:actions="[
'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') .
').',
]" />
'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') .
').',
]" />
@endcan
@endif
<div class="w-96 pb-6">
@@ -159,9 +166,8 @@
</div>
@if ($application->destination->server->isSwarm())
@if ($application->build_pack !== 'dockerimage')
<div>Docker Swarm requires the image to be available in a registry. More info <a
class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
target="_blank">here</a>.</div>
<div>Docker Swarm requires the image to be available in a registry. More info <a class="underline"
href="https://coolify.io/docs/knowledge-base/docker/registry" target="_blank">here</a>.</div>
@endif
@endif
<div class="flex flex-col gap-2 xl:flex-row">
@@ -173,19 +179,19 @@
helper="Enter a tag (e.g., 'latest', 'v1.2.3') or SHA256 hash (e.g., 'sha256-59e02939b1bf39f16c93138a28727aec520bb916da021180ae502c61626b3cf0')"
x-bind:disabled="!canUpdate" />
@else
<x-forms.input id="dockerRegistryImageName" label="Docker Image"
x-bind:disabled="!canUpdate" />
<x-forms.input id="dockerRegistryImageName" label="Docker Image" x-bind:disabled="!canUpdate" />
<x-forms.input id="dockerRegistryImageTag" label="Docker Image Tag or Hash"
helper="Enter a tag (e.g., 'latest', 'v1.2.3') or SHA256 hash (e.g., 'sha256-59e02939b1bf39f16c93138a28727aec520bb916da021180ae502c61626b3cf0')"
x-bind:disabled="!canUpdate" />
@endif
@else
@if (
$application->destination->server->isSwarm() ||
$application->destination->server->isSwarm() ||
$application->additional_servers->count() > 0 ||
$application->settings->is_build_server_enabled)
<x-forms.input id="dockerRegistryImageName" required label="Docker Image"
placeholder="Required!" x-bind:disabled="!canUpdate" />
$application->settings->is_build_server_enabled
)
<x-forms.input id="dockerRegistryImageName" required label="Docker Image" placeholder="Required!"
x-bind:disabled="!canUpdate" />
<x-forms.input id="dockerRegistryImageTag"
helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag."
placeholder="Empty means latest will be used." label="Docker Image Tag"
@@ -193,10 +199,9 @@
@else
<x-forms.input id="dockerRegistryImageName"
helper="Empty means it won't push the image to a docker registry. Pre-tag the image with your registry url if you want to push it to a private registry (default: Dockerhub). <br><br>Example: ghcr.io/myimage"
placeholder="Empty means it won't push the image to a docker registry."
label="Docker Image" x-bind:disabled="!canUpdate" />
<x-forms.input id="dockerRegistryImageTag"
placeholder="Empty means only push commit sha tag."
placeholder="Empty means it won't push the image to a docker registry." label="Docker Image"
x-bind:disabled="!canUpdate" />
<x-forms.input id="dockerRegistryImageTag" placeholder="Empty means only push commit sha tag."
helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag."
label="Docker Image Tag" x-bind:disabled="!canUpdate" />
@endif
@@ -233,16 +238,14 @@
@if ($application->build_pack === 'dockercompose')
@can('update', $application)
<div class="flex flex-col gap-2" x-init="$wire.dispatch('loadCompose', true)">
@else
@else
<div class="flex flex-col gap-2">
@endcan
@endcan
<div class="flex gap-2">
<x-forms.input x-bind:disabled="shouldDisable()" placeholder="/"
id="baseDirectory" label="Base Directory"
helper="Directory to use as root. Useful for monorepos." />
<x-forms.input x-bind:disabled="shouldDisable()"
placeholder="/docker-compose.yaml" id="dockerComposeLocation"
label="Docker Compose Location"
<x-forms.input x-bind:disabled="shouldDisable()" placeholder="/" id="baseDirectory"
label="Base Directory" helper="Directory to use as root. Useful for monorepos." />
<x-forms.input x-bind:disabled="shouldDisable()" placeholder="/docker-compose.yaml"
id="dockerComposeLocation" 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>" />
</div>
<div class="w-96">
@@ -257,29 +260,25 @@
know what are
you doing.</div>
<div class="flex gap-2">
<x-forms.input x-bind:disabled="shouldDisable()"
placeholder="docker compose build" id="dockerComposeCustomBuildCommand"
<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"
<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 }}"
<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 }}"
<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>
@@ -293,30 +292,27 @@
</div>
@endif
</div>
@else
@else
<div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input placeholder="/" id="baseDirectory" label="Base Directory"
helper="Directory to use as root. Useful for monorepos."
x-bind:disabled="!canUpdate" />
helper="Directory to use as root. Useful for monorepos." x-bind:disabled="!canUpdate" />
@if ($application->build_pack === 'dockerfile' && !$application->dockerfile)
<x-forms.input placeholder="/Dockerfile" id="dockerfileLocation"
label="Dockerfile Location"
<x-forms.input placeholder="/Dockerfile" id="dockerfileLocation" label="Dockerfile Location"
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($application->base_directory . $application->dockerfile_location, '/') }}</span>"
x-bind:disabled="!canUpdate" />
@endif
@if ($application->build_pack === 'dockerfile')
<x-forms.input id="dockerfileTargetBuild" label="Docker Build Stage Target"
helper="Useful if you have multi-staged dockerfile."
x-bind:disabled="!canUpdate" />
helper="Useful if you have multi-staged dockerfile." x-bind:disabled="!canUpdate" />
@endif
@if ($application->could_set_build_commands())
@if ($application->settings->is_static)
<x-forms.input placeholder="/dist" id="publishDirectory"
label="Publish Directory" required x-bind:disabled="!canUpdate" />
<x-forms.input placeholder="/dist" id="publishDirectory" label="Publish Directory" required
x-bind:disabled="!canUpdate" />
@else
<x-forms.input placeholder="/" id="publishDirectory"
label="Publish Directory" x-bind:disabled="!canUpdate" />
<x-forms.input placeholder="/" id="publishDirectory" label="Publish Directory"
x-bind:disabled="!canUpdate" />
@endif
@endif
@@ -332,8 +328,7 @@
<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>"
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"
x-bind:disabled="!canUpdate" />
id="customDockerRunOptions" label="Custom Docker Options" x-bind:disabled="!canUpdate" />
@if ($application->build_pack !== 'dockercompose')
<div class="pt-2 w-96">
@@ -343,189 +338,204 @@
x-bind:disabled="!canUpdate" />
</div>
@endif
@endif
</div>
@endif
</div>
@endif
</div>
@if ($application->build_pack === 'dockercompose')
<div class="flex items-center gap-2 pb-4">
<h3>Docker Compose</h3>
@can('update', $application)
<x-forms.button wire:target='initLoadingCompose'
x-on:click="$wire.dispatch('loadCompose', false)">Reload Compose File</x-forms.button>
@endcan
</div>
@if ($application->settings->is_raw_compose_deployment_enabled)
<x-forms.textarea rows="10" readonly id="dockerComposeRaw"
label="Docker Compose Content (applicationId: {{ $application->id }})"
helper="You need to modify the docker compose file in the git repository."
monacoEditorLanguage="yaml" useMonacoEditor />
@else
@if ((int) $application->compose_parsing_version >= 3)
@if ($application->build_pack === 'dockercompose')
<div x-data="{ showRaw: true }">
<div class="flex items-center gap-2">
<h3>Docker Compose</h3>
<x-forms.button x-show="!($application->settings->is_raw_compose_deployment_enabled)" @click.prevent="showRaw = !showRaw" x-text="showRaw ? 'Show Deployable Compose' : 'Show Raw Compose'"></x-forms.button>
</div>
@if ($application->settings->is_raw_compose_deployment_enabled)
<x-forms.textarea rows="10" readonly id="dockerComposeRaw"
label="Docker Compose Content (raw)"
label="Docker Compose Content (applicationId: {{ $application->id }})"
helper="You need to modify the docker compose file in the git repository."
monacoEditorLanguage="yaml" useMonacoEditor />
@endif
<x-forms.textarea rows="10" readonly id="dockerCompose" label="Docker Compose Content"
helper="You need to modify the docker compose file in the git repository."
monacoEditorLanguage="yaml" useMonacoEditor />
@endif
<div class="w-96">
<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-bind:disabled="!canUpdate"></x-forms.checkbox>
{{-- <x-forms.checkbox label="Readonly labels"
helper="Labels are readonly by default. Readonly means that edits you do to the labels could be lost and Coolify will autogenerate the labels for you. If you want to edit the labels directly, disable this option. <br><br>Be careful, it could break the proxy configuration after you restart the container as Coolify will now NOT autogenerate the labels for you (ofc you can always reset the labels to the coolify defaults manually)."
id="isContainerLabelReadonlyEnabled" instantSave></x-forms.checkbox> --}}
</div>
@endif
@if ($application->dockerfile)
<x-forms.textarea label="Dockerfile" id="dockerfile" monacoEditorLanguage="dockerfile"
useMonacoEditor rows="6" x-bind:disabled="!canUpdate"> </x-forms.textarea>
@endif
@if ($application->build_pack !== 'dockercompose')
<h3 class="pt-8">Network</h3>
@if ($this->detectedPortInfo)
@if ($this->detectedPortInfo['isEmpty'])
<div class="flex items-start gap-2 p-4 mb-4 text-sm rounded-lg bg-yellow-50 dark:bg-yellow-900/20 text-yellow-800 dark:text-yellow-300 border border-yellow-200 dark:border-yellow-800">
<svg class="w-5 h-5 shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"/>
</svg>
<div>
<span class="font-semibold">PORT environment variable detected ({{ $this->detectedPortInfo['port'] }})</span>
<p class="mt-1">Your Ports Exposes field is empty. Consider setting it to <strong>{{ $this->detectedPortInfo['port'] }}</strong> to ensure the proxy routes traffic correctly.</p>
</div>
</div>
@elseif (!$this->detectedPortInfo['matches'])
<div class="flex items-start gap-2 p-4 mb-4 text-sm rounded-lg bg-yellow-50 dark:bg-yellow-900/20 text-yellow-800 dark:text-yellow-300 border border-yellow-200 dark:border-yellow-800">
<svg class="w-5 h-5 shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"/>
</svg>
<div>
<span class="font-semibold">PORT mismatch detected</span>
<p class="mt-1">Your PORT environment variable is set to <strong>{{ $this->detectedPortInfo['port'] }}</strong>, but it's not in your Ports Exposes configuration. Ensure they match for proper proxy routing.</p>
</div>
</div>
@else
<div class="flex items-start gap-2 p-4 mb-4 text-sm rounded-lg bg-blue-50 dark:bg-blue-900/20 text-blue-800 dark:text-blue-300 border border-blue-200 dark:border-blue-800">
<svg class="w-5 h-5 shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd"/>
</svg>
<div>
<span class="font-semibold">PORT environment variable configured</span>
<p class="mt-1">Your PORT environment variable ({{ $this->detectedPortInfo['port'] }}) matches your Ports Exposes configuration.</p>
@if ((int) $application->compose_parsing_version >= 3)
<div x-show="showRaw">
<x-forms.textarea rows="10" readonly id="dockerComposeRaw" label="Docker Compose Content (raw)"
helper="You need to modify the docker compose file in the git repository."
monacoEditorLanguage="yaml" useMonacoEditor />
</div>
@endif
<div x-show="showRaw === false">
<x-forms.textarea rows="10" readonly id="dockerCompose" label="Docker Compose Content"
helper="You need to modify the docker compose file in the git repository."
monacoEditorLanguage="yaml" useMonacoEditor />
</div>
@endif
<div class="w-96">
<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-bind:disabled="!canUpdate"></x-forms.checkbox>
{{-- <x-forms.checkbox label="Readonly labels"
helper="Labels are readonly by default. Readonly means that edits you do to the labels could be lost and Coolify will autogenerate the labels for you. If you want to edit the labels directly, disable this option. <br><br>Be careful, it could break the proxy configuration after you restart the container as Coolify will now NOT autogenerate the labels for you (ofc you can always reset the labels to the coolify defaults manually)."
id="isContainerLabelReadonlyEnabled" instantSave></x-forms.checkbox> --}}
</div>
</div>
@endif
<div class="flex flex-col gap-2 xl:flex-row">
@if ($application->settings->is_static || $application->build_pack === 'static')
<x-forms.input id="portsExposes" label="Ports Exposes" readonly
x-bind:disabled="!canUpdate" />
@else
@if ($application->settings->is_container_label_readonly_enabled === false)
<x-forms.input placeholder="3000,3001" id="portsExposes" label="Ports Exposes" readonly
helper="Readonly labels are disabled. You can set the ports manually in the labels section."
x-bind:disabled="!canUpdate" />
@if ($application->dockerfile)
<x-forms.textarea label="Dockerfile" id="dockerfile" monacoEditorLanguage="dockerfile" useMonacoEditor
rows="6" x-bind:disabled="!canUpdate"> </x-forms.textarea>
@endif
@if ($application->build_pack !== 'dockercompose')
<h3 class="pt-8">Network</h3>
@if ($this->detectedPortInfo)
@if ($this->detectedPortInfo['isEmpty'])
<div
class="flex items-start gap-2 p-4 mb-4 text-sm rounded-lg bg-yellow-50 dark:bg-yellow-900/20 text-yellow-800 dark:text-yellow-300 border border-yellow-200 dark:border-yellow-800">
<svg class="w-5 h-5 shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z"
clip-rule="evenodd" />
</svg>
<div>
<span class="font-semibold">PORT environment variable detected
({{ $this->detectedPortInfo['port'] }})</span>
<p class="mt-1">Your Ports Exposes field is empty. Consider setting it to
<strong>{{ $this->detectedPortInfo['port'] }}</strong> to ensure the proxy routes traffic
correctly.</p>
</div>
</div>
@elseif (!$this->detectedPortInfo['matches'])
<div
class="flex items-start gap-2 p-4 mb-4 text-sm rounded-lg bg-yellow-50 dark:bg-yellow-900/20 text-yellow-800 dark:text-yellow-300 border border-yellow-200 dark:border-yellow-800">
<svg class="w-5 h-5 shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z"
clip-rule="evenodd" />
</svg>
<div>
<span class="font-semibold">PORT mismatch detected</span>
<p class="mt-1">Your PORT environment variable is set to
<strong>{{ $this->detectedPortInfo['port'] }}</strong>, but it's not in your Ports Exposes
configuration. Ensure they match for proper proxy routing.</p>
</div>
</div>
@else
<x-forms.input placeholder="3000,3001" id="portsExposes" 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."
x-bind:disabled="!canUpdate" />
<div
class="flex items-start gap-2 p-4 mb-4 text-sm rounded-lg bg-blue-50 dark:bg-blue-900/20 text-blue-800 dark:text-blue-300 border border-blue-200 dark:border-blue-800">
<svg class="w-5 h-5 shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z"
clip-rule="evenodd" />
</svg>
<div>
<span class="font-semibold">PORT environment variable configured</span>
<p class="mt-1">Your PORT environment variable ({{ $this->detectedPortInfo['port'] }}) matches
your Ports Exposes configuration.</p>
</div>
</div>
@endif
@endif
@if (!$application->destination->server->isSwarm())
<x-forms.input placeholder="3000:3000" id="portsMappings" label="Ports Mappings"
helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><br><span class='inline-block font-bold dark:text-warning'>Example:</span><br>3000:3000,3002:3002<br><br>Rolling update is not supported if you have a port mapped to the host."
x-bind:disabled="!canUpdate" />
@endif
@if (!$application->destination->server->isSwarm())
<x-forms.input id="customNetworkAliases" label="Network Aliases"
helper="A comma separated list of custom network aliases you would like to add for container in Docker network.<br><br><span class='inline-block font-bold dark:text-warning'>Example:</span><br>api.internal,api.local"
wire:model="customNetworkAliases" x-bind:disabled="!canUpdate" />
@endif
</div>
<h3 class="pt-8">HTTP Basic Authentication</h3>
<div>
<div class="w-96">
<x-forms.checkbox helper="This will add the proper proxy labels to the container." instantSave
label="Enable" id="isHttpBasicAuthEnabled" x-bind:disabled="!canUpdate" />
<div class="flex flex-col gap-2 xl:flex-row">
@if ($application->settings->is_static || $application->build_pack === 'static')
<x-forms.input id="portsExposes" label="Ports Exposes" readonly x-bind:disabled="!canUpdate" />
@else
@if ($application->settings->is_container_label_readonly_enabled === false)
<x-forms.input placeholder="3000,3001" id="portsExposes" label="Ports Exposes" readonly
helper="Readonly labels are disabled. You can set the ports manually in the labels section."
x-bind:disabled="!canUpdate" />
@else
<x-forms.input placeholder="3000,3001" id="portsExposes" 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."
x-bind:disabled="!canUpdate" />
@endif
@endif
@if (!$application->destination->server->isSwarm())
<x-forms.input placeholder="3000:3000" id="portsMappings" label="Ports Mappings"
helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><br><span class='inline-block font-bold dark:text-warning'>Example:</span><br>3000:3000,3002:3002<br><br>Rolling update is not supported if you have a port mapped to the host."
x-bind:disabled="!canUpdate" />
@endif
@if (!$application->destination->server->isSwarm())
<x-forms.input id="customNetworkAliases" label="Network Aliases"
helper="A comma separated list of custom network aliases you would like to add for container in Docker network.<br><br><span class='inline-block font-bold dark:text-warning'>Example:</span><br>api.internal,api.local"
wire:model="customNetworkAliases" x-bind:disabled="!canUpdate" />
@endif
</div>
@if ($application->is_http_basic_auth_enabled)
<div class="flex gap-2 py-2">
<x-forms.input id="httpBasicAuthUsername" label="Username" required
x-bind:disabled="!canUpdate" />
<x-forms.input id="httpBasicAuthPassword" type="password" label="Password" required
x-bind:disabled="!canUpdate" />
</div>
@endif
</div>
@if ($application->settings->is_container_label_readonly_enabled)
<x-forms.textarea readonly disabled label="Container Labels" rows="15" id="customLabels"
monacoEditorLanguage="ini" useMonacoEditor x-bind:disabled="!canUpdate"></x-forms.textarea>
@else
<x-forms.textarea label="Container Labels" rows="15" id="customLabels"
monacoEditorLanguage="ini" useMonacoEditor x-bind:disabled="!canUpdate"></x-forms.textarea>
@endif
<div class="w-96">
<x-forms.checkbox label="Readonly labels"
helper="Labels are readonly by default. Readonly means that edits you do to the labels could be lost and Coolify will autogenerate the labels for you. If you want to edit the labels directly, disable this option. <br><br>Be careful, it could break the proxy configuration after you restart the container as Coolify will now NOT autogenerate the labels for you (ofc you can always reset the labels to the coolify defaults manually)."
id="isContainerLabelReadonlyEnabled" instantSave
x-bind:disabled="!canUpdate"></x-forms.checkbox>
<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-bind:disabled="!canUpdate"></x-forms.checkbox>
</div>
@can('update', $application)
<x-modal-confirmation title="Confirm Labels Reset to Coolify Defaults?"
buttonTitle="Reset Labels to Defaults" buttonFullWidth submitAction="resetDefaultLabels(true)"
:actions="[
<h3 class="pt-8">HTTP Basic Authentication</h3>
<div>
<div class="w-96">
<x-forms.checkbox helper="This will add the proper proxy labels to the container." instantSave
label="Enable" id="isHttpBasicAuthEnabled" x-bind:disabled="!canUpdate" />
</div>
@if ($application->is_http_basic_auth_enabled)
<div class="flex gap-2 py-2">
<x-forms.input id="httpBasicAuthUsername" label="Username" required
x-bind:disabled="!canUpdate" />
<x-forms.input id="httpBasicAuthPassword" type="password" label="Password" required
x-bind:disabled="!canUpdate" />
</div>
@endif
</div>
@if ($application->settings->is_container_label_readonly_enabled)
<x-forms.textarea readonly disabled label="Container Labels" rows="15" id="customLabels"
monacoEditorLanguage="ini" useMonacoEditor x-bind:disabled="!canUpdate"></x-forms.textarea>
@else
<x-forms.textarea label="Container Labels" rows="15" id="customLabels" monacoEditorLanguage="ini"
useMonacoEditor x-bind:disabled="!canUpdate"></x-forms.textarea>
@endif
<div class="w-96">
<x-forms.checkbox label="Readonly labels"
helper="Labels are readonly by default. Readonly means that edits you do to the labels could be lost and Coolify will autogenerate the labels for you. If you want to edit the labels directly, disable this option. <br><br>Be careful, it could break the proxy configuration after you restart the container as Coolify will now NOT autogenerate the labels for you (ofc you can always reset the labels to the coolify defaults manually)."
id="isContainerLabelReadonlyEnabled" instantSave
x-bind:disabled="!canUpdate"></x-forms.checkbox>
<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-bind:disabled="!canUpdate"></x-forms.checkbox>
</div>
@can('update', $application)
<x-modal-confirmation title="Confirm Labels Reset to Coolify Defaults?"
buttonTitle="Reset Labels to Defaults" buttonFullWidth submitAction="resetDefaultLabels(true)"
:actions="[
'All your custom proxy labels will be lost.',
'Proxy labels (traefik, caddy, etc) will be reset to the coolify defaults.',
]" confirmationText="{{ $application->fqdn . '/' }}"
confirmationLabel="Please confirm the execution of the actions by entering the Application URL below"
shortConfirmationLabel="Application URL" :confirmWithPassword="false"
step2ButtonText="Permanently Reset Labels" />
@endcan
@endif
confirmationLabel="Please confirm the execution of the actions by entering the Application URL below"
shortConfirmationLabel="Application URL" :confirmWithPassword="false"
step2ButtonText="Permanently Reset Labels" />
@endcan
@endif
<h3 class="pt-8">Pre/Post Deployment Commands</h3>
<div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input x-bind:disabled="shouldDisable()" placeholder="php artisan migrate"
id="preDeploymentCommand" label="Pre-deployment "
helper="An optional script or command to execute in the existing container before the deployment begins.<br>It is always executed with 'sh -c', so you do not need add it manually." />
@if ($application->build_pack === 'dockercompose')
<x-forms.input x-bind:disabled="shouldDisable()" id="preDeploymentCommandContainer"
label="Container Name"
helper="The name of the container to execute within. You can leave it blank if your application only has one container." />
@endif
<h3 class="pt-8">Pre/Post Deployment Commands</h3>
<div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input x-bind:disabled="shouldDisable()" placeholder="php artisan migrate"
id="preDeploymentCommand" label="Pre-deployment "
helper="An optional script or command to execute in the existing container before the deployment begins.<br>It is always executed with 'sh -c', so you do not need add it manually." />
@if ($application->build_pack === 'dockercompose')
<x-forms.input x-bind:disabled="shouldDisable()" id="preDeploymentCommandContainer"
label="Container Name"
helper="The name of the container to execute within. You can leave it blank if your application only has one container." />
@endif
</div>
<div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input x-bind:disabled="shouldDisable()" placeholder="php artisan migrate"
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." />
@if ($application->build_pack === 'dockercompose')
<x-forms.input x-bind:disabled="shouldDisable()" id="postDeploymentCommandContainer"
label="Container Name"
helper="The name of the container to execute within. You can leave it blank if your application only has one container." />
@endif
</div>
</div>
<div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input x-bind:disabled="shouldDisable()" placeholder="php artisan migrate"
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." />
@if ($application->build_pack === 'dockercompose')
<x-forms.input x-bind:disabled="shouldDisable()" id="postDeploymentCommandContainer"
label="Container Name"
helper="The name of the container to execute within. You can leave it blank if your application only has one container." />
@endif
</div>
</div>
</form>
<x-domain-conflict-modal :conflicts="$domainConflicts" :showModal="$showDomainConflictModal" confirmAction="confirmDomainUsage" />
<x-domain-conflict-modal :conflicts="$domainConflicts" :showModal="$showDomainConflictModal"
confirmAction="confirmDomainUsage" />
@script
<script>
$wire.$on('loadCompose', (isInit = true) => {
// Only load compose file if user has permission (this event should only be dispatched when authorized)
$wire.initLoadingCompose = true;
$wire.loadComposeFile(isInit);
});
</script>
<script>
$wire.$on('loadCompose', (isInit = true) => {
// Only load compose file if user has permission (this event should only be dispatched when authorized)
$wire.initLoadingCompose = true;
$wire.loadComposeFile(isInit);
});
</script>
@endscript
</div>
</div>

View File

@@ -118,7 +118,7 @@
<div class="max-w-full px-4 truncate box-description" x-text="item.description"></div>
<div class="max-w-full px-4 truncate box-description" x-text="item.fqdn"></div>
<template x-if="item.server_status == false">
<div class="px-4 text-xs font-bold text-error">The underlying server has problems
<div class="px-4 text-xs font-bold text-error">Server is unreachable or misconfigured
</div>
</template>
</div>
@@ -167,7 +167,7 @@
<div class="max-w-full px-4 truncate box-description" x-text="item.description"></div>
<div class="max-w-full px-4 truncate box-description" x-text="item.fqdn"></div>
<template x-if="item.server_status == false">
<div class="px-4 text-xs font-bold text-error">The underlying server has problems
<div class="px-4 text-xs font-bold text-error">Server is unreachable or misconfigured
</div>
</template>
</div>
@@ -216,7 +216,7 @@
<div class="max-w-full px-4 truncate box-description" x-text="item.description"></div>
<div class="max-w-full px-4 truncate box-description" x-text="item.fqdn"></div>
<template x-if="item.server_status == false">
<div class="px-4 text-xs font-bold text-error">The underlying server has problems
<div class="px-4 text-xs font-bold text-error">Server is unreachable or misconfigured
</div>
</template>
</div>

View File

@@ -90,7 +90,7 @@
@endcan
</span>
@endif
<div class="pt-2 text-xs">{{ $application->status }}</div>
<div class="pt-2 text-xs">{{ formatContainerStatus($application->status) }}</div>
</div>
<div class="flex items-center px-4">
<a class="mx-4 text-xs font-bold hover:underline"
@@ -139,7 +139,7 @@
@if ($database->description)
<span class="text-xs">{{ Str::limit($database->description, 60) }}</span>
@endif
<div class="text-xs">{{ $database->status }}</div>
<div class="text-xs">{{ formatContainerStatus($database->status) }}</div>
</div>
<div class="flex items-center px-4">
@if ($database->isBackupSolutionAvailable() || $database->is_migrated)

View File

@@ -34,7 +34,7 @@
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2">
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
<path d="M19.933 13.041 a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
<path d="M20 4v5h-5" />
</g>
</svg>

View File

@@ -52,6 +52,30 @@
@endif
@endif
@if ($uptime && $supported_os_type)
@if ($prerequisites_installed)
<div class="flex w-64 gap-2">Prerequisites are installed: <svg class="w-5 h-5 text-success"
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<g fill="currentColor">
<path
d="m237.66 85.26l-128.4 128.4a8 8 0 0 1-11.32 0l-71.6-72a8 8 0 0 1 0-11.31l24-24a8 8 0 0 1 11.32 0l36.68 35.32a8 8 0 0 0 11.32 0l92.68-91.32a8 8 0 0 1 11.32 0l24 23.6a8 8 0 0 1 0 11.31"
opacity=".2" />
<path
d="m243.28 68.24l-24-23.56a16 16 0 0 0-22.58 0L104 136l-.11-.11l-36.64-35.27a16 16 0 0 0-22.57.06l-24 24a16 16 0 0 0 0 22.61l71.62 72a16 16 0 0 0 22.63 0l128.4-128.38a16 16 0 0 0-.05-22.67M103.62 208L32 136l24-24l.11.11l36.64 35.27a16 16 0 0 0 22.52 0L208.06 56L232 79.6Z" />
</g>
</svg></div>
@else
@if ($error)
<div class="flex w-64 gap-2">Prerequisites are installed: <svg class="w-5 h-5 text-error"
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M208.49 191.51a12 12 0 0 1-17 17L128 145l-63.51 63.49a12 12 0 0 1-17-17L111 128L47.51 64.49a12 12 0 0 1 17-17L128 111l63.51-63.52a12 12 0 0 1 17 17L145 128Z" />
</svg></div>
@else
<div class="w-64"><x-loading text="Prerequisites are installed:" /></div>
@endif
@endif
@endif
@if ($uptime && $supported_os_type && $prerequisites_installed)
@if ($docker_installed)
<div class="flex w-64 gap-2">Docker is installed: <svg class="w-5 h-5 text-success"
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
@@ -120,7 +144,7 @@
@endif
@endif
<livewire:activity-monitor header="Docker Installation Logs" :showWaiting="false" />
<livewire:activity-monitor header="{{ $installationStep }} Installation Logs" :showWaiting="false" />
@isset($error)
<pre class="font-bold whitespace-pre-line text-error">{!! $error !!}</pre>
<x-forms.button canGate="update" :canResource="$server" wire:click="retry" class="mt-4">