Merge remote-tracking branch 'origin/next' into remove-local-volume-source

This commit is contained in:
Andras Bacsai
2026-08-19 13:13:09 +02:00
81 changed files with 3098 additions and 335 deletions
+30
View File
@@ -2121,6 +2121,36 @@ input[type="search"]::-webkit-search-results-decoration {
width: 100%;
}
.validation-installation-logs {
border: 1px solid var(--coollabs-fill);
}
.checkpoint-scroll-fade::after {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
z-index: 1;
width: 2rem;
background: linear-gradient(to left, var(--coollabs-base), transparent);
pointer-events: none;
}
@media (max-width: 639px) {
.process-dialog-mobile-fullscreen {
height: 100dvh !important;
min-height: 100dvh;
max-height: 100dvh;
border-radius: 0;
box-shadow: inset 0 0 0 1px var(--coollabs-hairline) !important;
}
.process-dialog-mobile-fullscreen .process-dialog-body {
border-radius: 0;
}
}
/* Data table (layer-card body, full-bleed) */
.data-table-header {
display: grid;
@@ -38,11 +38,11 @@
this.visible = true;
const rect = target.getBoundingClientRect();
this.below = rect.top < 48;
this.x = rect.left;
this.x = rect.left + rect.width / 2;
this.y = this.below ? rect.bottom + 8 : rect.top - 8;
this.$nextTick(() => {
const width = this.$refs.tooltip?.offsetWidth || 0;
this.x = Math.max(8, Math.min(window.innerWidth - width - 8, this.x));
this.x = Math.max(8, Math.min(window.innerWidth - width - 8, this.x - width / 2));
this.$nextTick(() => this.positioned = true);
});
},
@@ -1,5 +1,6 @@
@props([
'closeWithX' => false,
'mobileFullscreen' => false,
'open' => false,
'size' => 'lg',
])
@@ -40,7 +41,11 @@
<div class="fixed inset-0 overflow-y-auto">
<div @if (! $closeWithX) @click.self="processDialogOpen = false" @endif
class="flex min-h-full items-center justify-center p-4 sm:p-6">
@class([
'flex min-h-full items-center justify-center',
'p-4 sm:p-6' => ! $mobileFullscreen,
'p-0 sm:p-6' => $mobileFullscreen,
])>
<div x-show="processDialogOpen"
x-trap.inert.noscroll="processDialogOpen"
x-transition:enter="ease-out duration-150"
@@ -54,6 +59,7 @@
aria-labelledby="process-dialog-title"
@class([
'application-settings-section application-settings-form process-dialog relative flex flex-col overflow-hidden',
'process-dialog-mobile-fullscreen' => $mobileFullscreen,
$panelWidth,
// Fixed shell size so empty “waiting for process” state does not collapse.
'min-h-[min(70dvh,28rem)] h-[min(85dvh,52rem)] max-h-[calc(100dvh-2rem)]',
@@ -70,6 +70,7 @@
'icon' => 'shield-star',
'group' => 'Platform',
'visible' => $server->isFunctional() && ! $server->isSwarm() && ! $server->settings->is_build_server && auth()->user()?->can('viewSentinel', $server),
'warning' => $server->isSentinelEnabled() && ! $server->isSentinelLive(),
'children' => [
['label' => 'Configuration', 'route' => 'server.sentinel', 'active' => request()->routeIs('server.sentinel'), 'icon' => 'settings'],
['label' => 'Logs', 'route' => 'server.sentinel.logs', 'active' => request()->routeIs('server.sentinel.logs'), 'icon' => 'file-content'],
@@ -6,7 +6,10 @@
@php
$serverReady = $server->isFunctional();
$proxyNeedsAttention = $server->proxySet() && ! in_array($proxyStatus, ['running'], true);
$proxyUpdateAvailable = $server->proxySet()
&& ($server->hasCurrentTraefikOutdatedInfo() || $server->hasPendingProxyConfiguration());
$proxyNeedsAttention = $server->proxySet()
&& (! in_array($proxyStatus, ['running'], true) || $proxyUpdateAvailable);
$sentinelNeedsAttention = $showSentinelStatus && ! $server->isSentinelLive();
[$summaryLabel, $summaryType] = match (true) {
@@ -58,9 +61,9 @@
class="listbox-option gap-2.5!" @click="open = false" role="menuitem">
<span @class([
'size-1.5 shrink-0 rounded-full',
'bg-success' => $proxyStatus === 'running',
'bg-warning' => in_array($proxyStatus, ['starting', 'restarting', 'stopping'], true),
'bg-error' => ! in_array($proxyStatus, ['running', 'starting', 'restarting', 'stopping'], true),
'bg-success' => $proxyStatus === 'running' && ! $proxyUpdateAvailable,
'bg-warning' => $proxyNeedsAttention && ($proxyUpdateAvailable || in_array($proxyStatus, ['starting', 'restarting', 'stopping'], true)),
'bg-error' => $proxyNeedsAttention && ! $proxyUpdateAvailable && ! in_array($proxyStatus, ['starting', 'restarting', 'stopping'], true),
])></span>
<span class="flex-1">Proxy</span>
<span>{{ str($proxyStatus ?: 'unknown')->headline() }}</span>
@@ -69,7 +72,11 @@
@if ($showSentinelStatus)
<a href="{{ route('server.sentinel', ['server_uuid' => $server->uuid]) }}" {{ wireNavigate() }}
class="listbox-option gap-2.5!" @click="open = false" role="menuitem">
<span class="size-1.5 shrink-0 rounded-full {{ $server->isSentinelLive() ? 'bg-success' : 'bg-error' }}"></span>
<span @class([
'size-1.5 shrink-0 rounded-full',
'bg-success' => ! $sentinelNeedsAttention,
'bg-warning' => $sentinelNeedsAttention,
])></span>
<span class="flex-1">Sentinel</span>
<span>{{ $server->isSentinelLive() ? 'In sync' : 'Out of sync' }}</span>
</a>
@@ -16,13 +16,19 @@
themeColor: localStorage.getItem('themeColor') || '#6b16ed',
themeColorFrame: null,
avatarUrl: @js($user?->avatar_path ? route('profile.avatar', ['v' => $user->updated_at->timestamp]) : null),
openPanel() {
this.appearanceOpen = false;
this.open = true;
},
closePanel() {
this.open = false;
},
setTheme(type, closeMenu = true) {
this.theme = type;
localStorage.setItem('theme', type);
if (closeMenu) {
this.appearanceOpen = false;
this.open = false;
this.closePanel();
}
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
@@ -62,9 +68,9 @@
localStorage.setItem('themeColor', color);
localStorage.setItem('theme', 'custom');
},
}" @avatar-updated.window="avatarUrl = $event.detail.url" @keydown.escape.window="open = false; appearanceOpen = false"
@click.outside="open = false; appearanceOpen = false">
<button type="button" @click="open = !open"
}" @avatar-updated.window="avatarUrl = $event.detail.url" @keydown.escape.window="closePanel()"
@click.outside="closePanel()">
<button type="button" @click="open ? closePanel() : openPanel()"
title="{{ $userName }}" aria-label="Account menu for {{ $userName }}"
@if ($sidebar) :class="collapsed && 'w-8 justify-center px-0'" @endif
@class([
@@ -188,7 +194,7 @@
</a>
<x-modal-input title="How can we help?">
<x-slot:content>
<div class="listbox-option cursor-pointer" @click="open = false">
<div class="listbox-option cursor-pointer" @click="closePanel()">
<span class="flex items-center gap-2">
<x-reicon name="feedback" class="size-4 opacity-80" />
Feedback
+3
View File
@@ -124,6 +124,9 @@
<div id="server-timing-hud-slot-mobile" data-server-timing-hud-slot
class="hidden shrink-0 items-center"></div>
<div id="configuration-warning-hud-slot-mobile" class="relative shrink-0"></div>
@if (isInstanceAdmin() && !isCloud())
<livewire:upgrade key="mobile-upgrade" />
@endif
<x-top-user-menu />
<button type="button" class="-m-1 p-2 text-neutral-500 dark:text-fg-dim" x-on:click="open = !open">
<span class="sr-only">Open sidebar</span>
+1 -1
View File
@@ -175,7 +175,7 @@
<div class="grid min-w-0 grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4">
@foreach ($dashboardServers as $server)
@php
$proxyNeedsAttention = $server->proxySet() && $server->proxy->status !== 'running';
$proxyNeedsAttention = $server->proxySet() && ($server->proxy->status !== 'running' || $server->hasCurrentTraefikOutdatedInfo());
$sentinelNeedsAttention = $server->isSentinelEnabled() && ! $server->isSentinelLive();
[$serverStatus, $serverStatusType] = match (true) {
@@ -113,6 +113,10 @@
@endcan
<x-forms.input canGate="update" :canResource="$settings" id="smtpTimeout" type="number"
helper="Timeout value for sending emails." label="Timeout" />
<x-forms.input canGate="update" :canResource="$settings" id="smtpEhloDomain"
placeholder="coolify.example.com"
helper="Fully qualified domain sent in the SMTP EHLO command. Uses the system default when empty."
label="EHLO domain" />
</div>
</x-application.settings-section>
</div>
@@ -33,7 +33,8 @@
&& $server->settings->is_usable
&& ! $server->settings->force_disabled
&& ! $isTransferredAway;
$proxyNeedsAttention = $isReady && $server->proxySet() && $server->proxy->status !== 'running';
$proxyNeedsAttention = $isReady && $server->proxySet()
&& ($server->proxy->status !== 'running' || $server->hasCurrentTraefikOutdatedInfo());
$sentinelNeedsAttention = $isReady && $server->isSentinelEnabled() && ! $server->isSentinelLive();
$status = match (true) {
@@ -184,7 +184,7 @@
</div>
@endif
<x-process-dialog closeWithX size="xl" :open="$isValidating">
<x-process-dialog closeWithX mobileFullscreen size="xl" :open="$isValidating">
<x-slot:title>Validate and configure</x-slot:title>
<x-slot:content>
<livewire:server.validate-and-install :server="$server"
@@ -84,15 +84,26 @@
</x-forms.button>
@else
<div data-validation-checkpoints
class="overflow-hidden rounded-[10px] border border-neutral-200 dark:border-white/[0.08]">
class="shrink-0 overflow-hidden rounded-[10px] border border-neutral-200 dark:border-white/[0.08]">
<div class="border-b border-neutral-200 px-4 py-2.5 dark:border-white/[0.08]">
<h3 class="text-[13px] font-medium text-neutral-600 dark:text-fg-dim">Validation checkpoints</h3>
</div>
<div class="divide-y divide-neutral-200 dark:divide-white/[0.07]">
@foreach ($checkpoints as $checkpoint)
<x-checkpoint-item :title="$checkpoint['title']" :description="$checkpoint['description']"
:status="$checkpoint['status']" />
@endforeach
<div class="checkpoint-scroll-fade relative min-w-0" x-data="{
observer: null,
scrollToRunning() {
this.$refs.track.querySelector('[data-checkpoint-status=running]')?.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' })
}
}"
x-init="$nextTick(() => scrollToRunning()); observer = new MutationObserver(() => $nextTick(() => scrollToRunning())); observer.observe($refs.track, { subtree: true, childList: true, attributes: true, attributeFilter: ['data-checkpoint-status'] })"
x-destroy="observer?.disconnect()">
<div class="flex min-w-0 snap-x snap-mandatory overflow-x-auto overscroll-x-contain scroll-smooth scrollbar divide-x divide-neutral-200 dark:divide-white/[0.07]"
x-ref="track">
@foreach ($checkpoints as $checkpoint)
<x-checkpoint-item :title="$checkpoint['title']" :description="$checkpoint['description']"
:status="$checkpoint['status']" data-checkpoint-status="{{ $checkpoint['status'] }}"
class="basis-[88%] shrink-0 snap-start sm:basis-72 lg:basis-80" />
@endforeach
</div>
</div>
</div>
@@ -107,7 +118,7 @@
</x-forms.button>
</div>
@elseif ($isInstalling)
<section class="application-settings-section">
<section class="application-settings-section validation-installation-logs">
<div class="application-settings-section-body">
<livewire:activity-monitor :header="$installationStep.' installation logs'" :showWaiting="false" />
</div>
@@ -8,7 +8,7 @@
{{-- One bar for the whole page. Three stacked bars made Save run
submitResend(), which required an API key even when Resend was off. --}}
<x-unsaved-bar action="submit"
targets="smtpFromName,smtpFromAddress,smtpHost,smtpPort,smtpEncryption,smtpUsername,smtpPassword,smtpTimeout,resendApiKey" />
targets="smtpFromName,smtpFromAddress,smtpHost,smtpPort,smtpEncryption,smtpUsername,smtpPassword,smtpTimeout,smtpEhloDomain,resendApiKey" />
<form wire:submit="submit">
<x-application.settings-section title="Sender">
@@ -48,6 +48,9 @@
autocomplete="new-password" />
<x-forms.input id="smtpTimeout" type="number"
helper="Maximum delivery time in seconds." label="Timeout" />
<x-forms.input id="smtpEhloDomain" placeholder="coolify.example.com"
helper="Fully qualified domain sent in the SMTP EHLO command. Uses the system default when empty."
label="EHLO domain" />
</div>
</x-application.settings-section>
</form>
@@ -8,6 +8,11 @@
{{-- Exclude is_auto_update_enabled (instantSave) so the bar does not flash. --}}
<x-unsaved-bar action="submit" targets="update_check_frequency,auto_update_frequency" />
<x-application.settings-section title="Update Coolify"
helper="Install the latest Coolify version manually when an update is available.">
<livewire:upgrade :full-button="true" key="settings-upgrade" />
</x-application.settings-section>
<x-application.settings-section title="Update checks">
<x-slot:actions>
<x-forms.button type="button" wire:click="checkManually">
@@ -22,7 +22,7 @@
Teams
</div>
@foreach (auth()->user()->teams as $team)
<button type="button" wire:click="switch_to({{ $team->id }})" @click="open = false"
<button type="button" wire:click="switch_to({{ $team->id }}, window.location.href)" @click="open = false"
class="listbox-option {{ $team->id === $currentTeam->id ? 'bg-neutral-100 font-medium dark:bg-white/[0.06]' : '' }}">
<span class="min-w-0 flex-1 truncate">{{ $team->name }}</span>
</button>
@@ -66,7 +66,7 @@
Teams
</div>
@foreach (auth()->user()->teams as $team)
<button type="button" wire:click="switch_to({{ $team->id }})" @click="teamOpen = false"
<button type="button" wire:click="switch_to({{ $team->id }}, window.location.href)" @click="teamOpen = false"
class="listbox-option {{ $team->id === $currentTeam->id ? 'bg-neutral-100 font-medium dark:bg-white/[0.06]' : '' }}">
<span class="min-w-0 flex-1 truncate">{{ $team->name }}</span>
</button>
@@ -6,6 +6,14 @@
})">
@if ($isUpgradeAvailable)
<div :class="{ 'z-40': modalOpen }" class="relative w-auto h-auto">
@if ($fullButton)
<x-forms.button type="button" @click="modalOpen=true" x-show="!showProgress" x-cloak isHighlighted>
Upgrade now
</x-forms.button>
<x-forms.button type="button" @click="modalOpen=true" x-show="showProgress" x-cloak isHighlighted>
Updating…
</x-forms.button>
@else
<button type="button" title="Upgrade in progress" aria-label="Upgrade in progress"
@click="modalOpen=true" x-show="showProgress" x-cloak
class="inline-flex h-[18px] cursor-pointer items-center rounded-full bg-coollabs/10 px-1.5 text-[9.5px] font-semibold leading-none text-coollabs ring-1 ring-inset ring-coollabs/25 transition-colors hover:bg-coollabs/15 dark:bg-warning/15 dark:text-warning dark:ring-warning/25 dark:hover:bg-warning/20">
@@ -16,6 +24,7 @@
class="inline-flex h-[18px] cursor-pointer items-center rounded-full bg-coollabs/10 px-1.5 text-[9.5px] font-semibold leading-none text-coollabs ring-1 ring-inset ring-coollabs/25 transition-colors hover:bg-coollabs/15 dark:bg-warning/15 dark:text-warning dark:ring-warning/25 dark:hover:bg-warning/20">
Update available
</button>
@endif
<template x-teleport="body">
<div x-show="modalOpen"
class="fixed inset-0 z-99 flex min-h-full items-center justify-center overflow-y-auto p-4"
@@ -162,6 +171,8 @@
</div>
</template>
</div>
@elseif ($fullButton)
<p class="text-sm text-neutral-600 dark:text-fg-dim">Coolify is up to date.</p>
@endif
</div>