Merge remote-tracking branch 'origin/next' into fix/ux-close-icon-buttons

This commit is contained in:
Andras Bacsai committed 2026-06-03 11:40:56 +02:00
commit 41fd6f5608
263 files changed
+9358 -3475

No files matched your search

@@ -0,0 +1,94 @@
@props([
'database',
'label',
'dbUrl' => null,
'dbUrlPublic' => null,
'supportsSsl' => true,
'enableSsl' => false,
'sslMode' => null,
'sslModeOptions' => null,
'sslModeHelper' => null,
'certificateValidUntil' => null,
'isExited' => false,
'showPublicUrlPlaceholder' => false,
])
@php
$urlHelper = 'If you change the user/password/port, this could be different. This is with the default values.';
@endphp
<div class="flex flex-col gap-2">
<x-forms.input :label="$label . ' URL (internal)'" :helper="$urlHelper" type="password" readonly
wire:model="dbUrl" canGate="update" :canResource="$database" />
@if ($dbUrlPublic)
<x-forms.input :label="$label . ' URL (public)'" :helper="$urlHelper" type="password" readonly
wire:model="dbUrlPublic" canGate="update" :canResource="$database" />
@elseif ($showPublicUrlPlaceholder)
<x-forms.input :label="$label . ' URL (public)'" :helper="$urlHelper" readonly
value="Starting the database will generate this." canGate="update" :canResource="$database" />
@endif
@if ($supportsSsl)
<div class="flex flex-col gap-2 pt-4">
<div class="flex items-center justify-between py-2">
<div class="flex items-center justify-between w-full">
<h3>SSL Configuration</h3>
@if ($enableSsl && $certificateValidUntil)
<x-modal-confirmation title="Regenerate SSL Certificates"
buttonTitle="Regenerate SSL Certificates" :actions="[
'The SSL certificate of this database will be regenerated.',
'You must restart the database after regenerating the certificate to start using the new certificate.',
]"
submitAction="regenerateSslCertificate" :confirmWithText="false" :confirmWithPassword="false" />
@endif
</div>
</div>
@if ($enableSsl && $certificateValidUntil)
<span class="text-sm">Valid until:
@if (now()->gt($certificateValidUntil))
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expired</span>
@elseif(now()->addDays(30)->gt($certificateValidUntil))
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expiring
soon</span>
@else
<span>{{ $certificateValidUntil->format('d.m.Y H:i:s') }}</span>
@endif
</span>
@endif
<div class="flex flex-col gap-2">
<div class="w-64" wire:key="enable_ssl">
@if ($isExited)
<x-forms.checkbox id="enableSsl" label="Enable SSL" wire:model.live="enableSsl"
instantSave="instantSaveSSL" canGate="update" :canResource="$database" />
@else
<x-forms.checkbox id="enableSsl" label="Enable SSL" wire:model.live="enableSsl"
instantSave="instantSaveSSL" disabled
helper="Database should be stopped to change this setting." canGate="update"
:canResource="$database" />
@endif
</div>
@if ($sslModeOptions && $enableSsl)
<div class="mx-2">
@if ($isExited)
<x-forms.select id="sslMode" label="SSL Mode" wire:model.live="sslMode"
instantSave="instantSaveSSL" :helper="$sslModeHelper" canGate="update"
:canResource="$database">
@foreach ($sslModeOptions as $value => $option)
<option value="{{ $value }}" title="{{ $option['title'] ?? '' }}">{{ $option['label'] }}</option>
@endforeach
</x-forms.select>
@else
<x-forms.select id="sslMode" label="SSL Mode" instantSave="instantSaveSSL" disabled
helper="Database should be stopped to change this setting." canGate="update"
:canResource="$database">
@foreach ($sslModeOptions as $value => $option)
<option value="{{ $value }}" title="{{ $option['title'] ?? '' }}">{{ $option['label'] }}</option>
@endforeach
</x-forms.select>
@endif
</div>
@endif
</div>
</div>
@endif
</div>
@@ -4,7 +4,7 @@
])
@php
$changes = collect(data_get($diff, 'changes', []))->filter(fn ($change) => data_get($change, 'key') !== 'domains.custom_labels')->values()->all();
$changes = collect(data_get($diff, 'changes', []))->values()->all();
$count = count($changes);
$requiresBuild = collect($changes)->contains(fn ($change) => data_get($change, 'impact') === 'build');
@endphp
@@ -41,16 +41,63 @@
</div>
<div class="divide-y divide-neutral-300 dark:divide-coolgray-200">
@foreach ($sectionChanges as $change)
@php
$changeKey = (string) data_get($change, 'key');
$expandable = data_get($change, 'expandable', false);
$oldDisplay = (string) data_get($change, 'old_display_value');
$newDisplay = (string) data_get($change, 'new_display_value');
$oldFull = data_get($change, 'old_full_value') ?? $oldDisplay;
$newFull = data_get($change, 'new_full_value') ?? $newDisplay;
$label = (string) data_get($change, 'label');
$labelTruncated = mb_strlen($label) > 20;
$rowExpandable = $expandable || $labelTruncated;
@endphp
<div class="grid grid-cols-[12rem_1fr_1.5rem_1fr] items-start gap-2 px-3 py-1.5 text-neutral-700 dark:text-neutral-300">
<div class="shrink-0 font-medium text-black dark:text-white">
{{ data_get($change, 'label') }}
<div class="min-w-0 shrink-0 font-medium text-black dark:text-white">
@if ($rowExpandable)
<div class="break-words"
:class="expandedRows['{{ $changeKey }}'] ? '' : 'truncate'"
x-text="expandedRows['{{ $changeKey }}'] ? @js($label) : @js((string) str($label)->limit(20))"></div>
@else
{{ $label }}
@endif
</div>
<div class="truncate text-red-700 dark:text-red-400/80" title="{{ data_get($change, 'old_display_value') }}">
{{ data_get($change, 'old_display_value') }}
<div class="min-w-0 text-red-700 dark:text-red-400/80">
@if ($expandable)
<div class="break-words"
:class="expandedRows['{{ $changeKey }}'] ? 'whitespace-pre-wrap' : 'truncate'"
x-text="expandedRows['{{ $changeKey }}'] ? @js($oldFull) : @js($oldDisplay)"></div>
@else
<div class="truncate">{{ $oldDisplay }}</div>
@endif
</div>
<div class="text-center text-neutral-500 dark:text-neutral-400">→</div>
<div class="truncate text-green-700 dark:text-green-500" title="{{ data_get($change, 'new_display_value') }}">
{{ data_get($change, 'new_display_value') }}
<div class="flex min-w-0 items-start gap-1 text-green-700 dark:text-green-500">
<div class="min-w-0 flex-1">
@if ($expandable)
<div class="break-words"
:class="expandedRows['{{ $changeKey }}'] ? 'whitespace-pre-wrap' : 'truncate'"
x-text="expandedRows['{{ $changeKey }}'] ? @js($newFull) : @js($newDisplay)"></div>
@else
<div class="truncate">{{ $newDisplay }}</div>
@endif
</div>
@if ($rowExpandable)
<button type="button"
x-on:click="expandedRows['{{ $changeKey }}'] = ! expandedRows['{{ $changeKey }}']"
:aria-expanded="!! expandedRows['{{ $changeKey }}']"
title="Toggle full value"
class="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center text-neutral-400 transition hover:text-black dark:hover:text-white">
<svg x-show="! expandedRows['{{ $changeKey }}']" class="h-3.5 w-3.5"
viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M13.28 7.78l3.22-3.22v2.69a.75.75 0 0 0 1.5 0v-4.5a.75.75 0 0 0-.75-.75h-4.5a.75.75 0 0 0 0 1.5h2.69l-3.22 3.22a.75.75 0 0 0 1.06 1.06ZM2 17.25v-4.5a.75.75 0 0 1 1.5 0v2.69l3.22-3.22a.75.75 0 0 1 1.06 1.06L4.56 16.5h2.69a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z" clip-rule="evenodd" />
</svg>
<svg x-show="expandedRows['{{ $changeKey }}']" x-cloak class="h-3.5 w-3.5"
viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M3.28 2.22a.75.75 0 0 0-1.06 1.06L5.44 6.5H2.75a.75.75 0 0 0 0 1.5h4.5A.75.75 0 0 0 8 7.25v-4.5a.75.75 0 0 0-1.5 0v2.69L3.28 2.22ZM13.5 2.75a.75.75 0 0 0-1.5 0v4.5c0 .414.336.75.75.75h4.5a.75.75 0 0 0 0-1.5h-2.69l3.22-3.22a.75.75 0 0 0-1.06-1.06L13.5 5.44V2.75ZM3.28 17.78l3.22-3.22v2.69a.75.75 0 0 0 1.5 0v-4.5a.75.75 0 0 0-.75-.75h-4.5a.75.75 0 0 0 0 1.5h2.69l-3.22 3.22a.75.75 0 1 0 1.06 1.06ZM12 12.75c0-.414.336-.75.75-.75h4.5a.75.75 0 0 1 0 1.5h-2.69l3.22 3.22a.75.75 0 1 1-1.06 1.06l-3.22-3.22v2.69a.75.75 0 0 1-1.5 0v-4.5Z" clip-rule="evenodd" />
</svg>
</button>
@endif
</div>
</div>
@endforeach
@@ -1,7 +1,13 @@
@props(['text'])
@props(['text', 'label' => null])
<div class="relative" x-data="{ copied: false, isSecure: window.isSecureContext }">
<input type="text" value="{{ $text }}" readonly class="input">
<div class="w-full" x-data="{ copied: false, isSecure: window.isSecureContext }">
@if ($label)
<label class="flex gap-1 items-center mb-1 text-sm font-medium">{{ $label }}</label>
@endif
<div class="relative">
<input type="text" value="{{ $text }}" class="input pr-10"
@keydown.prevent @paste.prevent @cut.prevent @drop.prevent
@focus="$event.target.select()">
<button
x-show="isSecure"
@click.prevent="copied = true; navigator.clipboard.writeText({{ Js::from($text) }}); setTimeout(() => copied = false, 1000)"
@@ -15,4 +21,5 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
</button>
</div>
</div>
@@ -196,26 +196,6 @@
}"
@click.outside="showDropdown = false">
@if ($type === 'password' && $allowToPeak)
<button type="button" x-on:click="type = type === 'password' ? 'text' : 'password'"
class="flex absolute inset-y-0 right-0 z-10 items-center pr-2 cursor-pointer dark:hover:text-white"
aria-label="Toggle password visibility">
<svg x-show="type === 'password'" xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" />
</svg>
<svg x-cloak x-show="type === 'text'" xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10.585 10.587a2 2 0 0 0 2.829 2.828" />
<path d="M16.681 16.673a8.717 8.717 0 0 1 -4.681 1.327c-3.6 0 -6.6 -2 -9 -6c1.272 -2.12 2.712 -3.678 4.32 -4.674m2.86 -1.146a9.055 9.055 0 0 1 1.82 -.18c3.6 0 6.6 2 9 6c-.666 1.11 -1.379 2.067 -2.138 2.87" />
<path d="M3 3l18 18" />
</svg>
</button>
@endif
<input
x-ref="input"
@input="handleInput()"
@@ -241,6 +221,26 @@
placeholder="{{ $attributes->get('placeholder') }}"
@if ($autofocus) autofocus @endif>
@if ($type === 'password' && $allowToPeak)
<button type="button" x-on:click="type = type === 'password' ? 'text' : 'password'"
class="flex absolute inset-y-0 right-0 z-10 items-center pr-2 cursor-pointer dark:hover:text-white"
aria-label="Toggle password visibility">
<svg x-show="type === 'password'" xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" />
</svg>
<svg x-cloak x-show="type === 'text'" xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10.585 10.587a2 2 0 0 0 2.829 2.828" />
<path d="M16.681 16.673a8.717 8.717 0 0 1 -4.681 1.327c-3.6 0 -6.6 -2 -9 -6c1.272 -2.12 2.712 -3.678 4.32 -4.674m2.86 -1.146a9.055 9.055 0 0 1 1.82 -.18c3.6 0 6.6 2 9 6c-.666 1.11 -1.379 2.067 -2.138 2.87" />
<path d="M3 3l18 18" />
</svg>
</button>
@endif
{{-- Dropdown for suggestions --}}
<div x-show="showDropdown"
x-transition
@@ -14,6 +14,16 @@
@endif
@if ($type === 'password')
<div class="relative" x-data="{ type: 'password' }" @success.window="type = 'password'">
<input autocomplete="{{ $autocomplete }}" value="{{ $value }}"
x-bind:type="type"
x-bind:class="{ 'truncate': type === 'text' && ! $el.disabled }"
{{ $attributes->merge(['class' => $defaultClass]) }} @required($required)
@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"
@readonly($readonly) @disabled($disabled) id="{{ $htmlId }}"
name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}"
aria-placeholder="{{ $attributes->get('placeholder') }}"
@if ($autofocus) x-ref="autofocusInput" @endif>
@if ($allowToPeak)
<button type="button" x-on:click="type = type === 'password' ? 'text' : 'password'"
class="flex absolute inset-y-0 right-0 items-center pr-2 cursor-pointer dark:hover:text-white"
@@ -35,16 +45,6 @@
</svg>
</button>
@endif
<input autocomplete="{{ $autocomplete }}" value="{{ $value }}"
x-bind:type="type"
x-bind:class="{ 'truncate': type === 'text' && ! $el.disabled }"
{{ $attributes->merge(['class' => $defaultClass]) }} @required($required)
@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"
@readonly($readonly) @disabled($disabled) id="{{ $htmlId }}"
name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}"
aria-placeholder="{{ $attributes->get('placeholder') }}"
@if ($autofocus) x-ref="autofocusInput" @endif>
</div>
@else
@@ -31,6 +31,21 @@
@else
@if ($type === 'password')
<div class="relative" x-data="{ type: 'password' }" @success.window="type = 'password'">
<input x-cloak x-show="type === 'password'" value="{{ $value }}"
{{ $attributes->merge(['class' => $defaultClassInput]) }} @required($required)
@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="[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="[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>
@if ($allowToPeak)
<button type="button" x-on:click="type = type === 'password' ? 'text' : 'password'"
class="absolute inset-y-0 right-0 flex items-center h-6 pt-2 pr-2 cursor-pointer dark:hover:text-white"
@@ -51,21 +66,6 @@
</svg>
</button>
@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="[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="[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="[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>
</div>
@else
+79 -9
View File
@@ -49,23 +49,32 @@
localStorage.setItem('theme', type);
this.queryTheme();
},
cycleTheme() {
const themes = ['light', 'system', 'dark'];
const currentIndex = themes.indexOf(this.theme || localStorage.getItem('theme') || 'dark');
this.setTheme(themes[(currentIndex + 1) % themes.length]);
},
queryTheme() {
const darkModePreference = window.matchMedia('(prefers-color-scheme: dark)').matches;
const userSettings = localStorage.getItem('theme') || 'dark';
localStorage.setItem('theme', userSettings);
let isDark = false;
if (userSettings === 'dark') {
document.documentElement.classList.add('dark');
this.theme = 'dark';
isDark = true;
} else if (userSettings === 'light') {
document.documentElement.classList.remove('dark');
this.theme = 'light';
} else if (darkModePreference) {
this.theme = 'system';
document.documentElement.classList.add('dark');
isDark = true;
} else if (!darkModePreference) {
this.theme = 'system';
document.documentElement.classList.remove('dark');
}
document.querySelector('meta[name=theme-color]')?.setAttribute('content', isDark ? '#101010' : '#ffffff');
},
checkZoom() {
if (this.zoom === null) {
@@ -92,9 +101,9 @@
}
}
}">
<div class="flex pt-4 pb-4 pl-2 items-start gap-2 motion-safe:transition-all motion-safe:duration-200 motion-safe:ease-out motion-reduce:transition-none"
:class="collapsed ? 'lg:flex-col lg:items-center lg:pl-0 lg:gap-3 lg:pt-7' : 'lg:pt-6'">
<div class="flex flex-col w-full" :class="collapsed && 'lg:hidden'">
<div class="flex pt-4 pb-4 pl-2 pr-3 items-start gap-3"
:class="collapsed ? 'lg:flex-col lg:items-center lg:pl-0 lg:pr-0 lg:gap-3 lg:pt-7' : 'lg:pt-6'">
<div class="flex min-w-0 flex-1 flex-col" :class="collapsed && 'lg:hidden'">
<a href="/" {{ wireNavigate() }} class="text-2xl font-bold tracking-tight dark:text-white hover:opacity-80 transition-opacity">Coolify</a>
<x-version />
</div>
@@ -107,10 +116,10 @@
</a>
<x-version class="text-[10px]" />
</div>
<div :class="collapsed && 'lg:hidden'">
<div class="min-w-0 flex-1" :class="collapsed && 'lg:hidden'">
<!-- Search button that triggers global search modal -->
<button @click="$dispatch('open-global-search')" type="button" title="Search (Press / or ⌘K)"
class="flex items-center gap-1.5 px-2.5 py-1.5 bg-neutral-100 dark:bg-coolgray-100 border border-neutral-300 dark:border-coolgray-200 rounded-md hover:bg-neutral-200 dark:hover:bg-coolgray-200 transition-colors">
class="flex h-8 w-full items-center justify-between gap-1.5 px-2.5 py-1.5 bg-neutral-100 dark:bg-coolgray-100 border border-neutral-300 dark:border-coolgray-200 rounded-md hover:bg-neutral-200 dark:hover:bg-coolgray-200 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-neutral-500 dark:text-neutral-400"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
@@ -120,11 +129,8 @@
class="px-1 py-0.5 text-xs font-semibold text-neutral-500 dark:text-neutral-400 bg-neutral-200 dark:bg-coolgray-200 rounded">/</kbd>
</button>
</div>
<div :class="collapsed && 'lg:hidden'">
<livewire:settings-dropdown />
</div>
</div>
<div class="px-2 pt-2 pb-7 overflow-hidden motion-safe:transition-all motion-safe:duration-200 motion-safe:ease-out motion-reduce:transition-none" :class="collapsed && 'lg:px-0 lg:pt-0 lg:pb-4 lg:min-h-8 lg:flex lg:justify-center'">
<div class="px-2 pt-2 pb-7 overflow-hidden" :class="collapsed && 'lg:px-0 lg:pt-0 lg:pb-4 lg:min-h-8 lg:flex lg:justify-center'">
<livewire:switch-team />
</div>
<ul role="list" class="flex flex-col flex-1 gap-y-7">
@@ -366,6 +372,70 @@
@endif
@endif
<div class="flex-1"></div>
<li>
<livewire:settings-dropdown trigger="changelog-sidebar" />
</li>
<li>
<div class="menu-item" title="Theme" aria-label="Theme switcher" :class="collapsed && 'lg:hidden'">
<svg x-show="theme === 'dark'" class="menu-item-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
</svg>
<svg x-show="theme === 'light'" class="menu-item-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
<svg x-show="theme === 'system'" class="menu-item-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
<span class="menu-item-label">Theme</span>
<div class="ml-auto flex items-center gap-0.5 rounded-sm bg-neutral-100 p-0.5 dark:bg-coolgray-200">
<button type="button" @click.stop="setTheme('light')" title="Light" aria-label="Use light theme"
class="grid size-6 place-items-center rounded-sm text-xs hover:bg-white hover:text-coollabs dark:hover:bg-base dark:hover:text-warning"
:class="theme === 'light' && 'bg-white text-coollabs shadow-sm dark:bg-base dark:text-warning'">
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
</button>
<button type="button" @click.stop="setTheme('system')" title="System default" aria-label="Use system theme"
class="grid size-6 place-items-center rounded-sm text-xs hover:bg-white hover:text-coollabs dark:hover:bg-base dark:hover:text-warning"
:class="theme === 'system' && 'bg-white text-coollabs shadow-sm dark:bg-base dark:text-warning'">
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
</button>
<button type="button" @click.stop="setTheme('dark')" title="Dark" aria-label="Use dark theme"
class="grid size-6 place-items-center rounded-sm text-xs hover:bg-white hover:text-coollabs dark:hover:bg-base dark:hover:text-warning"
:class="theme === 'dark' && 'bg-white text-coollabs shadow-sm dark:bg-base dark:text-warning'">
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
</svg>
</button>
</div>
</div>
<button type="button" @click.stop="cycleTheme()"
:title="`Theme: ${theme === 'system' ? 'System default' : theme}. Click to change.`"
:aria-label="`Theme: ${theme === 'system' ? 'System default' : theme}. Click to change theme.`"
class="menu-item hidden"
:class="collapsed && 'lg:flex'">
<svg x-show="theme === 'dark'" class="menu-item-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
</svg>
<svg x-show="theme === 'light'" class="menu-item-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
<svg x-show="theme === 'system'" class="menu-item-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
</button>
</li>
@if (isInstanceAdmin() && !isCloud())
@persist('upgrade')
<li>
@@ -0,0 +1,17 @@
<div class="pb-6">
<h1>Profile</h1>
<div class="subtitle">Your user profile settings.</div>
<div class="navbar-main">
<nav class="flex items-center gap-6 min-h-10">
<a class="{{ request()->routeIs('profile') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
href="{{ route('profile') }}">
General
</a>
<a class="{{ request()->routeIs('profile.appearance') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
href="{{ route('profile.appearance') }}">
Appearance
</a>
<div class="flex-1"></div>
</nav>
</div>
</div>
@@ -0,0 +1,10 @@
<div class="sub-menu-wrapper">
<a class="{{ request()->routeIs('server.sentinel') ? 'sub-menu-item menu-item-active' : 'sub-menu-item' }}" {{ wireNavigate() }}
href="{{ route('server.sentinel', $parameters) }}">
<span class="menu-item-label">Configuration</span>
</a>
<a class="{{ request()->routeIs('server.sentinel.logs') ? 'sub-menu-item menu-item-active' : 'sub-menu-item' }}" {{ wireNavigate() }}
href="{{ route('server.sentinel.logs', $parameters) }}">
<span class="menu-item-label">Logs</span>
</a>
</div>
@@ -6,11 +6,6 @@
href="{{ route('server.advanced', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Advanced</span>
</a>
@endif
@if ($server->isFunctional() && !$server->isSwarm() && !$server->isBuildServer())
<a class="sub-menu-item {{ $activeMenu === 'sentinel' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.sentinel', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Sentinel</span>
</a>
@endif
<a class="sub-menu-item {{ $activeMenu === 'private-key' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.private-key', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Private Key</span>
</a>
@@ -37,7 +32,7 @@
<a class="sub-menu-item {{ $activeMenu === 'log-drains' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.log-drains', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Log Drains</span></a>
<a class="sub-menu-item {{ $activeMenu === 'metrics' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.charts', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Metrics</span></a>
href="{{ route('server.metrics', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Metrics</span></a>
@endif
@if (!$server->isBuildServer() && !$server->settings->is_cloudflare_tunnel)
<a class="sub-menu-item {{ $activeMenu === 'swarm' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
@@ -2,7 +2,11 @@
'title' => null,
'lastDeploymentLink' => null,
'resource' => null,
'showRefreshButton' => true,
])
@php
$stoppedAfterRestartLimit = $resource && method_exists($resource, 'stoppedAfterRestartLimit') && $resource->stoppedAfterRestartLimit();
@endphp
<div class="flex flex-wrap items-center gap-1">
@if (str($resource->status)->startsWith('running'))
<x-status.running :status="$resource->status" :title="$title" :lastDeploymentLink="$lastDeploymentLink" />
@@ -13,13 +17,20 @@
@else
<x-status.stopped :status="$resource->status" />
@endif
@if (isset($resource->restart_count) && $resource->restart_count > 0 && !str($resource->status)->startsWith('exited'))
@if (isset($resource->restart_count) && $resource->restart_count > 0 && (!str($resource->status)->startsWith('exited') || $stoppedAfterRestartLimit))
<div class="flex items-center">
<span class="text-xs dark:text-warning" title="Container has restarted {{ $resource->restart_count }} time{{ $resource->restart_count > 1 ? 's' : '' }}. Last restart: {{ $resource->last_restart_at?->diffForHumans() }}">
({{ $resource->restart_count }}x restarts)
</span>
</div>
@endif
@if ($stoppedAfterRestartLimit)
<div class="flex items-center">
<span class="text-xs dark:text-warning" title="Container has crashed and Coolify stopped it after {{ $resource->restart_count }} restart attempts.">
Stopped after reaching restart limit ({{ $resource->restart_count }}/{{ $resource->max_restart_count }}).
</span>
</div>
@endif
@if (!str($resource->status)->contains('exited') && $showRefreshButton)
<button wire:loading.remove.delay.shortest wire:target="manualCheckStatus" title="Refresh Status" wire:click='manualCheckStatus'
class="dark:hover:fill-white fill-black dark:fill-warning">