mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-14 22:17:19 +00:00
fix(ui): stabilize domain controls and unify resource navigation
Preserve optimistic listbox selections during Livewire saves, refresh service domains after updates, and align application, service, and server navigation layouts.
This commit is contained in:
1 parent
3f47d41880
commit
41b3ed95aa
21 files changed
+464
-954
No files matched your search
+41
-24
@@ -2109,11 +2109,15 @@ input[type="search"]::-webkit-search-results-decoration {
|
||||
}
|
||||
|
||||
.domains-table-grid {
|
||||
grid-template-columns: minmax(0, 1.8fr) 8.5rem minmax(7rem, 0.9fr) 10rem 11rem 6.5rem;
|
||||
grid-template-columns: minmax(0, 1.8fr) 8.5rem 10rem 11rem 6.5rem;
|
||||
}
|
||||
|
||||
.domains-table-grid-service {
|
||||
grid-template-columns: minmax(0, 1.8fr) 8.5rem 10rem 6.5rem;
|
||||
}
|
||||
|
||||
.domains-table-grid-compose {
|
||||
grid-template-columns: minmax(0, 1.6fr) minmax(6rem, 0.8fr) 8.5rem minmax(7rem, 0.9fr) 10rem 11rem 6.5rem;
|
||||
grid-template-columns: minmax(0, 1.6fr) minmax(6rem, 0.8fr) 8.5rem 10rem 11rem 6.5rem;
|
||||
}
|
||||
|
||||
.domains-mobile-label {
|
||||
@@ -2127,9 +2131,9 @@ input[type="search"]::-webkit-search-results-decoration {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
/* Hide "Last checked" (3rd of 4) */
|
||||
.domains-table-grid > :nth-child(3) {
|
||||
display: none;
|
||||
.domains-table-grid-service {
|
||||
grid-template-columns: minmax(0, 1fr) 8.25rem 9rem 5.5rem;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.domains-table-grid-compose {
|
||||
@@ -2137,20 +2141,21 @@ input[type="search"]::-webkit-search-results-decoration {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
/* Hide "Service" (2) and "Last checked" (4) of 5 */
|
||||
.domains-table-grid-compose > :nth-child(2),
|
||||
.domains-table-grid-compose > :nth-child(4) {
|
||||
/* Hide "Service" */
|
||||
.domains-table-grid-compose > :nth-child(2) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.data-table-header.domains-table-grid,
|
||||
.data-table-header.domains-table-grid-service,
|
||||
.data-table-header.domains-table-grid-compose {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.data-table-row.domains-table-grid,
|
||||
.data-table-row.domains-table-grid-service,
|
||||
.data-table-row.domains-table-grid-compose {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
@@ -2172,26 +2177,28 @@ input[type="search"]::-webkit-search-results-decoration {
|
||||
'indexing indexing';
|
||||
}
|
||||
|
||||
.data-table-row.domains-table-grid.domains-row-without-direction > :nth-child(5),
|
||||
.data-table-row.domains-table-grid-compose.domains-row-without-direction > :nth-child(6) {
|
||||
.data-table-row.domains-table-grid.domains-row-without-direction > :nth-child(4),
|
||||
.data-table-row.domains-table-grid-compose.domains-row-without-direction > :nth-child(5) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Domain cell */
|
||||
.data-table-row.domains-table-grid > :nth-child(1),
|
||||
.data-table-row.domains-table-grid-service > :nth-child(1),
|
||||
.data-table-row.domains-table-grid-compose > :nth-child(1) {
|
||||
grid-area: domain;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.data-table-row.domains-table-grid > :nth-child(1) a,
|
||||
.data-table-row.domains-table-grid-service > :nth-child(1) a,
|
||||
.data-table-row.domains-table-grid-compose > :nth-child(1) a {
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* Non-compose: 1 Domain, 2 DNS, 3 Last checked, 4 Indexing, 5 Direction, 6 Actions */
|
||||
/* Non-compose: 1 Domain, 2 DNS, 3 Indexing, 4 Direction, 5 Actions */
|
||||
.data-table-row.domains-table-grid > :nth-child(2) {
|
||||
grid-area: meta;
|
||||
display: flex !important;
|
||||
@@ -2201,23 +2208,37 @@ input[type="search"]::-webkit-search-results-decoration {
|
||||
}
|
||||
|
||||
.data-table-row.domains-table-grid > :nth-child(3) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.data-table-row.domains-table-grid > :nth-child(4) {
|
||||
grid-area: indexing;
|
||||
}
|
||||
|
||||
.data-table-row.domains-table-grid > :nth-child(5) {
|
||||
.data-table-row.domains-table-grid > :nth-child(4) {
|
||||
grid-area: direction;
|
||||
}
|
||||
|
||||
.data-table-row.domains-table-grid > :nth-child(6) {
|
||||
.data-table-row.domains-table-grid > :nth-child(5) {
|
||||
grid-area: actions;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
/* Compose: 1 Domain, 2 Service, 3 DNS, 4 Last checked, 5 Indexing, 6 Direction, 7 Actions */
|
||||
/* Compose service group: 1 Domain, 2 DNS, 3 Indexing, 4 Actions */
|
||||
.data-table-row.domains-table-grid-service > :nth-child(2) {
|
||||
grid-area: meta;
|
||||
display: flex !important;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.data-table-row.domains-table-grid-service > :nth-child(3) {
|
||||
grid-area: indexing;
|
||||
}
|
||||
|
||||
.data-table-row.domains-table-grid-service > :nth-child(4) {
|
||||
grid-area: actions;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
/* Compose: 1 Domain, 2 Service, 3 DNS, 4 Indexing, 5 Direction, 6 Actions */
|
||||
.data-table-row.domains-table-grid-compose > :nth-child(2) {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -2231,18 +2252,14 @@ input[type="search"]::-webkit-search-results-decoration {
|
||||
}
|
||||
|
||||
.data-table-row.domains-table-grid-compose > :nth-child(4) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.data-table-row.domains-table-grid-compose > :nth-child(5) {
|
||||
grid-area: indexing;
|
||||
}
|
||||
|
||||
.data-table-row.domains-table-grid-compose > :nth-child(6) {
|
||||
.data-table-row.domains-table-grid-compose > :nth-child(5) {
|
||||
grid-area: direction;
|
||||
}
|
||||
|
||||
.data-table-row.domains-table-grid-compose > :nth-child(7) {
|
||||
.data-table-row.domains-table-grid-compose > :nth-child(6) {
|
||||
grid-area: actions;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<div class="relative" x-data="{ open: false }" @click.outside="open = false"
|
||||
@keydown.escape.window="open = false">
|
||||
<button type="button" class="button" @click="open = !open" :aria-expanded="open"
|
||||
aria-haspopup="menu">
|
||||
<x-reicon name="grid" class="size-3.5 opacity-70" />
|
||||
Advanced
|
||||
<x-reicon name="chevron-down" class="size-3 opacity-55" />
|
||||
</button>
|
||||
|
||||
<div x-cloak x-show="open" x-transition.origin.top.right
|
||||
class="listbox-panel top-full! right-0! left-auto! mt-1! w-60! min-w-0!" role="menu">
|
||||
@can('deploy', $application)
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
wire:click="{{ $application->status === 'running' ? 'force_deploy_without_cache' : 'deploy(true)' }}"
|
||||
@click="open = false" role="menuitem">
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
Force deploy without cache
|
||||
</button>
|
||||
@else
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!" disabled>
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
Force deploy without cache
|
||||
</button>
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
@@ -15,6 +15,7 @@
|
||||
'disabled' => false,
|
||||
'tooltip' => true,
|
||||
'portal' => false,
|
||||
'preserveValue' => false,
|
||||
])
|
||||
|
||||
@php
|
||||
@@ -45,22 +46,33 @@
|
||||
<div class="relative min-w-0" x-data="{
|
||||
open: false,
|
||||
positioned: false,
|
||||
saving: false,
|
||||
options: @js(array_values($options)),
|
||||
value: @if (!$wire) @js($value) @elseif ($live) @entangle($id).live @else @entangle($id) @endif,
|
||||
value: @if (!$wire) @js($value) @elseif ($live && ! $onChange) @entangle($id).live @else @entangle($id) @endif,
|
||||
get current() {
|
||||
const found = this.options.find((option) => String(option.value) === String(this.value));
|
||||
return found ? found.label : @js($placeholder);
|
||||
},
|
||||
choose(option) {
|
||||
if (option.disabled) return;
|
||||
async choose(option) {
|
||||
if (this.saving || option.disabled) return;
|
||||
this.open = false;
|
||||
if (String(option.value) === String(this.value)) return;
|
||||
this.value = option.value;
|
||||
this.$dispatch('listbox-change', { value: option.value });
|
||||
@if ($onChange && is_array($onChangeArgs))
|
||||
this.$nextTick(() => this.$wire.{{ $onChange }}(...@js($onChangeArgs), option.value));
|
||||
this.saving = true;
|
||||
try {
|
||||
await this.$wire.{{ $onChange }}(...@js($onChangeArgs), option.value);
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
@elseif ($onChange)
|
||||
this.$nextTick(() => this.$wire.{{ $onChange }}());
|
||||
this.saving = true;
|
||||
try {
|
||||
await this.$wire.{{ $onChange }}();
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
@endif
|
||||
},
|
||||
toggle() {
|
||||
@@ -94,8 +106,10 @@
|
||||
panel.style.minWidth = `${triggerRect.width}px`;
|
||||
this.positioned = true;
|
||||
}
|
||||
}" x-modelable="value" {{ $attributes->whereStartsWith('x-model') }}
|
||||
}" x-modelable="value" :class="{ 'pointer-events-none opacity-70': saving }"
|
||||
{{ $attributes->whereStartsWith('x-model') }}
|
||||
{{ $attributes->whereStartsWith('x-effect') }}
|
||||
@if ($preserveValue) wire:ignore @endif
|
||||
@click.outside="open = false" @keydown.escape="open = false" @resize.window="open && positionPanel()">
|
||||
<button x-ref="trigger" id="{{ $triggerId }}" type="button" class="listbox-trigger" @click="toggle()"
|
||||
@disabled($disabled) {{ $attributes->whereStartsWith('x-bind:disabled') }} aria-haspopup="listbox"
|
||||
|
||||
@@ -37,8 +37,9 @@
|
||||
<div class="relative min-w-0" x-data="{
|
||||
open: false,
|
||||
query: '',
|
||||
saving: false,
|
||||
options: @js(array_values($options)),
|
||||
value: @if (!$wire) @js($value) @elseif ($live) @entangle($id).live @else @entangle($id) @endif,
|
||||
value: @if (!$wire) @js($value) @elseif ($live && ! $onChange) @entangle($id).live @else @entangle($id) @endif,
|
||||
get current() {
|
||||
const found = this.options.find((option) => String(option.value) === String(this.value));
|
||||
return found ? found.label : @js($placeholder);
|
||||
@@ -72,8 +73,8 @@
|
||||
this.open = false;
|
||||
this.query = '';
|
||||
},
|
||||
choose(option) {
|
||||
if (option.disabled) {
|
||||
async choose(option) {
|
||||
if (this.saving || option.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -83,9 +84,17 @@
|
||||
}
|
||||
|
||||
this.value = option.value;
|
||||
@if ($onChange) this.$nextTick(() => this.$wire.{{ $onChange }}()); @endif
|
||||
@if ($onChange)
|
||||
this.saving = true;
|
||||
try {
|
||||
await this.$wire.{{ $onChange }}();
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
@endif
|
||||
}
|
||||
}" x-modelable="value" {{ $attributes->whereStartsWith('x-model') }}
|
||||
}" x-modelable="value" :class="{ 'pointer-events-none opacity-70': saving }"
|
||||
{{ $attributes->whereStartsWith('x-model') }}
|
||||
{{ $attributes->whereStartsWith('x-effect') }}
|
||||
@click.outside="close()" @keydown.escape.window="open && close()">
|
||||
<button id="{{ $id }}-trigger" type="button" class="listbox-trigger" @click="toggle()"
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
'refresh3' => '<path d="M14.55 21.67C18.84 20.54 22 16.64 22 12C22 6.48 17.56 2 12 2C5.33 2 2 7.56 2 7.56M2 7.56V3M2 7.56H4.01H6.44" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 12C2 17.52 6.48 22 12 22" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="3 3"/>',
|
||||
'restart' => '<path fill-rule="evenodd" clip-rule="evenodd" d="M18.364 3.05762C18.7782 3.05762 19.114 3.3934 19.114 3.80762V8.05026C19.114 8.46447 18.7782 8.80026 18.364 8.80026H14.1213C13.7071 8.80026 13.3713 8.46447 13.3713 8.05026C13.3713 7.63604 13.7071 7.30026 14.1213 7.30026H16.4817C13.6363 5.05718 9.4987 5.24825 6.87348 7.87348C4.04217 10.7048 4.04217 15.2952 6.87348 18.1265C9.70478 20.9578 14.2952 20.9578 17.1265 18.1265C19.0234 16.2297 19.6504 13.5428 19.0039 11.1219C18.897 10.7217 19.1348 10.3106 19.535 10.2038C19.9352 10.0969 20.3462 10.3347 20.4531 10.7349C21.2321 13.6518 20.478 16.8964 18.1872 19.1872C14.7701 22.6043 9.2299 22.6043 5.81282 19.1872C2.39573 15.7701 2.39573 10.2299 5.81282 6.81282C9.04483 3.5808 14.1762 3.40576 17.614 6.28768V3.80762C17.614 3.3934 17.9497 3.05762 18.364 3.05762Z" fill="currentColor"/>',
|
||||
'stop' => '<path fill-rule="evenodd" clip-rule="evenodd" d="M11.9426 1.25H12.0574C14.3658 1.24999 16.1748 1.24998 17.5863 1.43975C19.031 1.63399 20.1711 2.03933 21.0659 2.93414C21.9607 3.82895 22.366 4.96897 22.5603 6.41371C22.75 7.82519 22.75 9.63423 22.75 11.9426V12.0574C22.75 14.3658 22.75 16.1748 22.5603 17.5863C22.366 19.031 21.9607 20.1711 21.0659 21.0659C20.1711 21.9607 19.031 22.366 17.5863 22.5603C16.1748 22.75 14.3658 22.75 12.0574 22.75H11.9426C9.63423 22.75 7.82519 22.75 6.41371 22.5603C4.96897 22.366 3.82895 21.9607 2.93414 21.0659C2.03933 20.1711 1.63399 19.031 1.43975 17.5863C1.24998 16.1748 1.24999 14.3658 1.25 12.0574V11.9426C1.24999 9.63423 1.24998 7.82519 1.43975 6.41371C1.63399 4.96897 2.03933 3.82895 2.93414 2.93414C3.82895 2.03933 4.96897 1.63399 6.41371 1.43975C7.82519 1.24998 9.63423 1.24999 11.9426 1.25ZM6.61358 2.92637C5.33517 3.09825 4.56445 3.42514 3.9948 3.9948C3.42514 4.56445 3.09825 5.33517 2.92637 6.61358C2.75159 7.91356 2.75 9.62177 2.75 12C2.75 14.3782 2.75159 16.0864 2.92637 17.3864C3.09825 18.6648 3.42514 19.4355 3.9948 20.0052C4.56445 20.5749 5.33517 20.9018 6.61358 21.0736C7.91356 21.2484 9.62177 21.25 12 21.25C14.3782 21.25 16.0864 21.2484 17.3864 21.0736C18.6648 20.9018 19.4355 20.5749 20.0052 20.0052C20.5749 19.4355 20.9018 18.6648 21.0736 17.3864C21.2484 16.0864 21.25 14.3782 21.25 12C21.25 9.62177 21.2484 7.91356 21.0736 6.61358C20.9018 5.33517 20.5749 4.56445 20.0052 3.9948C19.4355 3.42514 18.6648 3.09825 17.3864 2.92637C16.0864 2.75159 14.3782 2.75 12 2.75C9.62177 2.75 7.91356 2.75159 6.61358 2.92637Z" fill="currentColor"/>',
|
||||
'stop-circle' => '<circle cx="12" cy="12" r="10.25" stroke="currentColor" stroke-width="1.5"/><rect x="8.25" y="8.25" width="7.5" height="7.5" rx="1" stroke="currentColor" stroke-width="1.5"/>',
|
||||
'play-circle' => '<path fill-rule="evenodd" clip-rule="evenodd" d="M11.0748 7.50835C9.74622 6.72395 8.25 7.79065 8.25 9.21316V14.7868C8.25 16.2093 9.74622 17.276 11.0748 16.4916L15.795 13.7048C17.0683 12.953 17.0683 11.047 15.795 10.2952L11.0748 7.50835ZM9.75 9.21316C9.75 9.01468 9.84615 8.87585 9.95947 8.80498C10.0691 8.73641 10.1919 8.72898 10.3122 8.80003L15.0324 11.5869C15.165 11.6652 15.25 11.8148 15.25 12C15.25 12.1852 15.165 12.3348 15.0324 12.4131L10.3122 15.2C10.1919 15.271 10.0691 15.2636 9.95947 15.195C9.84615 15.1242 9.75 14.9853 9.75 14.7868V9.21316Z" fill="currentColor"/><path fill-rule="evenodd" clip-rule="evenodd" d="M12 1.25C6.06294 1.25 1.25 6.06294 1.25 12C1.25 17.9371 6.06294 22.75 12 22.75C17.9371 22.75 22.75 17.9371 22.75 12C22.75 6.06294 17.9371 1.25 12 1.25ZM2.75 12C2.75 6.89137 6.89137 2.75 12 2.75C17.1086 2.75 21.25 6.89137 21.25 12C21.25 17.1086 17.1086 21.25 12 21.25C6.89137 21.25 2.75 17.1086 2.75 12Z" fill="currentColor"/>',
|
||||
'browser-code' => '<path d="M10.1303 11.5697C10.4232 11.8626 10.4232 12.3375 10.1303 12.6304L8.26067 14.5L10.1303 16.3697C10.4232 16.6626 10.4232 17.1375 10.1303 17.4304C9.83745 17.7233 9.36258 17.7233 9.06968 17.4304L6.66968 15.0304C6.37679 14.7375 6.37679 14.2626 6.66968 13.9697L9.06968 11.5697C9.36258 11.2768 9.83745 11.2768 10.1303 11.5697Z" fill="currentColor"/><path d="M14.9304 11.5697C14.6375 11.2768 14.1626 11.2768 13.8697 11.5697C13.5768 11.8626 13.5768 12.3375 13.8697 12.6304L15.7394 14.5L13.8697 16.3697C13.5768 16.6626 13.5768 17.1375 13.8697 17.4304C14.1626 17.7233 14.6375 17.7233 14.9304 17.4304L17.3304 15.0304C17.6232 14.7375 17.6232 14.2626 17.3304 13.9697L14.9304 11.5697Z" fill="currentColor"/><path fill-rule="evenodd" clip-rule="evenodd" d="M18.1318 2.25006H5.86821C5.12513 2.25005 4.52196 2.25005 4.03273 2.29002C3.52778 2.33128 3.07851 2.41882 2.66103 2.63154C2.00247 2.9671 1.46703 3.50253 1.13148 4.1611C0.918762 4.57857 0.831214 5.02784 0.789958 5.53279C0.749986 6.02202 0.749993 6.62518 0.75 7.36825V16.6319C0.749993 17.3749 0.749986 17.9781 0.789958 18.4673C0.831214 18.9723 0.918762 19.4215 1.13148 19.839C1.46703 20.4976 2.00247 21.033 2.66103 21.3686C3.07851 21.5813 3.52778 21.6688 4.03273 21.7101C4.52195 21.7501 5.12507 21.7501 5.86811 21.7501H18.1318C18.8748 21.7501 19.4781 21.7501 19.9673 21.7101C20.4722 21.6688 20.9215 21.5813 21.339 21.3686C21.9975 21.033 22.533 20.4976 22.8685 19.839C23.0812 19.4215 23.1688 18.9723 23.21 18.4673C23.25 17.9781 23.25 17.375 23.25 16.632V7.36825C23.25 6.62521 23.25 6.02201 23.21 5.53279C23.1688 5.02784 23.0812 4.57857 22.8685 4.1611C22.533 3.50253 21.9975 2.9671 21.339 2.63154C20.9215 2.41882 20.4722 2.33128 19.9673 2.29002C19.478 2.25005 18.8749 2.25005 18.1318 2.25006ZM21.75 7.25006C21.7498 6.54749 21.7472 6.04844 21.715 5.65494C21.6813 5.24275 21.619 5.01286 21.532 4.84208C21.3403 4.46576 21.0343 4.1598 20.658 3.96805C20.4872 3.88103 20.2573 3.81871 19.8451 3.78504C19.4242 3.75065 18.8824 3.75006 18.1 3.75006H5.9C5.11755 3.75006 4.57582 3.75065 4.15488 3.78504C3.74269 3.81871 3.5128 3.88103 3.34202 3.96805C2.9657 4.1598 2.65973 4.46576 2.46799 4.84208C2.38097 5.01286 2.31865 5.24275 2.28498 5.65494C2.25283 6.04844 2.25022 6.54749 2.25002 7.25006H21.75ZM2.25 8.75006H21.75V16.6001C21.75 17.3825 21.7494 17.9242 21.715 18.3452C21.6813 18.7574 21.619 18.9873 21.532 19.158C21.3403 19.5344 21.0343 19.8403 20.658 20.0321C20.4872 20.1191 20.2573 20.1814 19.8451 20.2151C19.4242 20.2495 18.8824 20.2501 18.1 20.2501H5.9C5.11755 20.2501 4.57582 20.2495 4.15488 20.2151C3.74269 20.1814 3.5128 20.1191 3.34202 20.0321C2.9657 19.8403 2.65973 19.5344 2.46799 19.158C2.38097 18.9873 2.31865 18.7574 2.28498 18.3452C2.25058 17.9242 2.25 17.3825 2.25 16.6001V8.75006Z" fill="currentColor"/>',
|
||||
'database' => '<path fill-rule="evenodd" clip-rule="evenodd" d="M3.25 6C3.25 4.45831 4.48029 3.26447 6.00774 2.50075C7.58004 1.7146 9.69967 1.25 12 1.25C14.3003 1.25 16.42 1.7146 17.9923 2.50075C19.5197 3.26447 20.75 4.45831 20.75 6V18C20.75 19.5417 19.5197 20.7355 17.9923 21.4992C16.42 22.2854 14.3003 22.75 12 22.75C9.69967 22.75 7.58004 22.2854 6.00774 21.4992C4.48029 20.7355 3.25 19.5417 3.25 18V6ZM4.75 6C4.75 5.33255 5.31057 4.52639 6.67856 3.84239C8.00168 3.18083 9.88205 2.75 12 2.75C14.118 2.75 15.9983 3.18083 17.3214 3.84239C18.6894 4.52639 19.25 5.33255 19.25 6C19.25 6.66745 18.6894 7.47361 17.3214 8.15761C15.9983 8.81917 14.118 9.25 12 9.25C9.88205 9.25 8.00168 8.81917 6.67856 8.15761C5.31057 7.47361 4.75 6.66745 4.75 6ZM4.75 18C4.75 18.6674 5.31057 19.4736 6.67856 20.1576C8.00168 20.8192 9.88205 21.25 12 21.25C14.118 21.25 15.9983 20.8192 17.3214 20.1576C18.6894 19.4736 19.25 18.6674 19.25 18V14.7072C18.8733 15.0077 18.4459 15.2724 17.9923 15.4992C16.42 16.2854 14.3003 16.75 12 16.75C9.69967 16.75 7.58004 16.2854 6.00774 15.4992C5.55414 15.2724 5.12675 15.0077 4.75 14.7072V18ZM19.25 8.70722V12C19.25 12.6674 18.6894 13.4736 17.3214 14.1576C15.9983 14.8192 14.118 15.25 12 15.25C9.88205 15.25 8.00168 14.8192 6.67856 14.1576C5.31057 13.4736 4.75 12.6674 4.75 12V8.70722C5.12675 9.00772 5.55414 9.27245 6.00774 9.49925C7.58004 10.2854 9.69967 10.75 12 10.75C14.3003 10.75 16.42 10.2854 17.9923 9.49925C18.4459 9.27245 18.8733 9.00772 19.25 8.70722Z" fill="currentColor"/>',
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
@php
|
||||
$status = str($service->status ?? '');
|
||||
$canDeploy = auth()->user()->can('deploy', $service);
|
||||
$canStop = auth()->user()->can('stop', $service);
|
||||
@endphp
|
||||
|
||||
<div class="relative" x-data="{ open: false }" @click.outside="open = false"
|
||||
@keydown.escape.window="open = false">
|
||||
<button type="button" class="button" @click="open = !open" :aria-expanded="open"
|
||||
aria-haspopup="menu">
|
||||
<x-reicon name="grid" class="size-3.5 opacity-70" />
|
||||
Advanced
|
||||
<x-reicon name="chevron-down" class="size-3 opacity-55" />
|
||||
</button>
|
||||
|
||||
<div x-cloak x-show="open" x-transition.origin.top.right
|
||||
class="listbox-panel top-full! right-0! left-auto! mt-1! w-64! min-w-0!" role="menu">
|
||||
@if ($status->contains('running'))
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(! $canDeploy)
|
||||
@click="$wire.dispatch('pullAndRestartEvent'); open = false"
|
||||
role="menuitem">
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
Pull Latest Images & Restart
|
||||
</button>
|
||||
@elseif ($status->contains('degraded'))
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(! $canDeploy)
|
||||
@click="$wire.dispatch('forceDeployEvent'); open = false"
|
||||
role="menuitem">
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
Force Restart
|
||||
</button>
|
||||
@else
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(! $canDeploy)
|
||||
@click="$wire.dispatch('forceDeployEvent'); open = false"
|
||||
role="menuitem">
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
Force Deploy
|
||||
</button>
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(! $canStop)
|
||||
@click="$wire.dispatch('cleanupEvent'); open = false"
|
||||
role="menuitem">
|
||||
<x-reicon name="trash" class="size-3.5 opacity-70" />
|
||||
Force Cleanup Containers
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@@ -202,20 +202,38 @@
|
||||
wire:key="application-compose-domain-group-{{ $redirectWireKey }}"
|
||||
x-show="matchesDomainSearch(@js($serviceName.' '.$rows->pluck('url')->implode(' ')))"
|
||||
class="border-b border-neutral-200 last:border-b-0 dark:border-white/10">
|
||||
<div class="flex w-full items-center gap-3 border-b border-neutral-200 bg-neutral-50 px-4 py-3 dark:border-white/10 dark:bg-white/[0.04]">
|
||||
<div class="flex w-full items-center justify-between gap-3 border-b border-neutral-200 bg-neutral-50 px-4 py-3 dark:border-white/10 dark:bg-white/[0.04]">
|
||||
<span class="min-w-0 flex-1 truncate text-sm font-medium text-black dark:text-white">
|
||||
{{ $serviceName }}
|
||||
</span>
|
||||
<div class="flex shrink-0 items-center gap-2">
|
||||
<span class="hidden text-xs text-neutral-500 sm:inline dark:text-fg-dim">Direction</span>
|
||||
@if (auth()->user()?->can('update', $application) && ! $labelsAreWritable)
|
||||
<x-forms.listbox id="domain-direction-service-{{ $redirectWireKey }}" :wire="false"
|
||||
:value="$serviceRedirects[$redirectWireKey] ?? 'both'" preserveValue
|
||||
onChange="updateServiceRedirect" :onChangeArgs="[$serviceName]" portal :options="[
|
||||
['value' => 'both', 'label' => 'Allow www & non-www'],
|
||||
['value' => 'www', 'label' => 'Redirect to www'],
|
||||
['value' => 'non-www', 'label' => 'Redirect to non-www'],
|
||||
]" />
|
||||
@else
|
||||
<span class="text-[13px] text-neutral-500 dark:text-fg-dim">
|
||||
{{ match ($serviceRedirects[$redirectWireKey] ?? 'both') {
|
||||
'www' => 'Redirect to www',
|
||||
'non-www' => 'Redirect to non-www',
|
||||
default => 'Allow both',
|
||||
} }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div wire:key="application-compose-domain-rows-{{ $redirectWireKey }}-{{ md5(serialize($rows->all())) }}"
|
||||
class="data-table w-full">
|
||||
<div class="data-table-header domains-table-grid">
|
||||
<div class="data-table-header domains-table-grid-service">
|
||||
<span>Domain</span>
|
||||
<span>DNS</span>
|
||||
<span>Last checked</span>
|
||||
<span>DNS Check</span>
|
||||
<span>Search engine indexing</span>
|
||||
<span>Direction</span>
|
||||
<span></span>
|
||||
</div>
|
||||
@foreach ($rows as $row)
|
||||
@@ -232,7 +250,8 @@
|
||||
'application' => $application,
|
||||
'labelsAreWritable' => $labelsAreWritable,
|
||||
'isCompose' => false,
|
||||
'domainDirection' => $serviceRedirects[$redirectWireKey] ?? 'both',
|
||||
'showDirectionControl' => false,
|
||||
'domainGridClass' => 'domains-table-grid-service',
|
||||
])
|
||||
@endforeach
|
||||
</div>
|
||||
@@ -250,7 +269,6 @@
|
||||
<div class="data-table-header domains-table-grid">
|
||||
<span>Domain</span>
|
||||
<span>DNS Check</span>
|
||||
<span>Last checked</span>
|
||||
<span>Search engine indexing</span>
|
||||
<span>Direction</span>
|
||||
<span></span>
|
||||
|
||||
@@ -104,13 +104,13 @@
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@click="open = false; document.getElementById('application-mobile-stop-trigger')?.click()"
|
||||
role="menuitem">
|
||||
<x-reicon name="stop" class="size-3.5 text-error" />
|
||||
<x-reicon name="stop-circle" class="size-3.5 text-error" />
|
||||
Stop
|
||||
</button>
|
||||
@else
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!" disabled
|
||||
role="menuitem">
|
||||
<x-reicon name="stop" class="size-3.5 opacity-70" />
|
||||
<x-reicon name="stop-circle" class="size-3.5 opacity-70" />
|
||||
Stop
|
||||
</button>
|
||||
@endcan
|
||||
@@ -183,30 +183,28 @@
|
||||
@if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw))
|
||||
<span class="px-2 text-[13px] text-neutral-500 dark:text-fg-dim">Load a Compose file to deploy.</span>
|
||||
@else
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<x-applications.advanced :application="$application" />
|
||||
@endif
|
||||
<div class="resource-heading-menus shrink-0">
|
||||
<x-applications.links :application="$application" />
|
||||
</div>
|
||||
@if (str($application->status)->startsWith('exited'))
|
||||
<x-forms.button id="application-desktop-deploy" canGate="deploy" :canResource="$application"
|
||||
wire:click="deploy">
|
||||
<x-reicon name="play-circle" class="size-4 opacity-70" />
|
||||
Deploy
|
||||
</x-forms.button>
|
||||
@else
|
||||
<div id="application-desktop-actions" class="relative" x-data="{ open: false }"
|
||||
<div id="application-desktop-actions" class="relative" x-data="{ open: false }"
|
||||
@click.outside="open = false" @keydown.escape.window="open = false">
|
||||
<button type="button" class="button" @click="open = !open" :aria-expanded="open"
|
||||
aria-haspopup="menu">
|
||||
<x-reicon name="play-circle" class="size-3.5 text-warning" />
|
||||
<x-reicon name="play-circle" class="size-3.5 opacity-70" />
|
||||
Actions
|
||||
<x-reicon name="chevron-down" class="size-3 opacity-55" />
|
||||
</button>
|
||||
|
||||
<div x-cloak x-show="open" x-transition.origin.top.right
|
||||
class="listbox-panel top-full! right-0! left-auto! mt-1! w-60! min-w-0!" role="menu">
|
||||
@if (str($application->status)->startsWith('exited'))
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('deploy', $application))
|
||||
wire:click="deploy" @click="open = false" role="menuitem">
|
||||
<x-reicon name="play-circle" class="size-3.5 opacity-70" />
|
||||
Deploy
|
||||
</button>
|
||||
@else
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
@can('deploy', $application)
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@@ -255,18 +253,28 @@
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@click="open = false; document.getElementById('application-mobile-stop-trigger')?.click()"
|
||||
role="menuitem">
|
||||
<x-reicon name="stop" class="size-3.5 text-error" />
|
||||
<x-reicon name="stop-circle" class="size-3.5 text-error" />
|
||||
Stop
|
||||
</button>
|
||||
@else
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!" disabled>
|
||||
<x-reicon name="stop" class="size-3.5 opacity-70" />
|
||||
<x-reicon name="stop-circle" class="size-3.5 opacity-70" />
|
||||
Stop
|
||||
</button>
|
||||
@endcan
|
||||
</div>
|
||||
@endif
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<div class="my-1 border-t border-coolgray-200 dark:border-coolgray-300" role="separator"></div>
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('deploy', $application))
|
||||
wire:click="{{ $application->status === 'running' ? 'force_deploy_without_cache' : 'deploy(true)' }}"
|
||||
@click="open = false" role="menuitem">
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
Force deploy without cache
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,10 +13,7 @@
|
||||
'pending' => 'DNS pending',
|
||||
default => 'DNS unknown',
|
||||
};
|
||||
$checkedAt = ! empty($row['checked_at'])
|
||||
? \Illuminate\Support\Carbon::parse($row['checked_at'])->diffForHumans()
|
||||
: null;
|
||||
$gridClass = ($isCompose ?? false) ? 'domains-table-grid-compose' : 'domains-table-grid';
|
||||
$gridClass = $domainGridClass ?? (($isCompose ?? false) ? 'domains-table-grid-compose' : 'domains-table-grid');
|
||||
$domainParts = $isSuggested ? null : parse_url($row['url']);
|
||||
$faviconUrl = is_array($domainParts) && isset($domainParts['scheme'], $domainParts['host'])
|
||||
? $domainParts['scheme'].'://'.$domainParts['host'].(isset($domainParts['port']) ? ':'.$domainParts['port'] : '').'/favicon.ico'
|
||||
@@ -38,7 +35,7 @@
|
||||
->filter(fn ($item) => $redirectPairKey($item['url']) === $pairKey)
|
||||
->keys()
|
||||
->first();
|
||||
$showDirection = ! $isSuggested && $firstPairRowIndex === $index;
|
||||
$showDirection = ($showDirectionControl ?? true) && ! $isSuggested && $firstPairRowIndex === $index;
|
||||
@endphp
|
||||
|
||||
<div wire:key="domain-row-{{ $index }}-{{ md5(($isSuggested ? 's:' : '') . $row['url'] . '|' . ($row['service'] ?? '')) }}"
|
||||
@@ -59,12 +56,19 @@
|
||||
</span>
|
||||
@else
|
||||
@if ($faviconUrl)
|
||||
<img src="{{ $faviconUrl }}" alt="" loading="lazy" decoding="async"
|
||||
referrerpolicy="no-referrer" x-on:error="$el.remove()"
|
||||
class="size-4 shrink-0 rounded-sm" />
|
||||
<span class="relative size-4 shrink-0" aria-hidden="true">
|
||||
<x-reicon name="globe"
|
||||
class="domain-favicon-fallback size-4 text-neutral-400 dark:text-fg-faint" />
|
||||
<img src="{{ $faviconUrl }}" alt="" loading="lazy" decoding="async"
|
||||
referrerpolicy="no-referrer"
|
||||
x-init="if ($el.complete && $el.naturalWidth > 0) { $el.previousElementSibling.classList.add('hidden'); $el.classList.remove('invisible') }"
|
||||
x-on:load="$el.previousElementSibling.classList.add('hidden'); $el.classList.remove('invisible')"
|
||||
x-on:error="$el.remove()"
|
||||
class="invisible absolute inset-0 size-4 rounded-sm" />
|
||||
</span>
|
||||
@endif
|
||||
<a href="{{ getFqdnWithoutPort($row['url']) }}" target="_blank"
|
||||
class="min-w-0 text-[13px] text-black underline decoration-neutral-300 underline-offset-2 hover:decoration-coollabs sm:truncate dark:text-fg dark:decoration-white/20 dark:hover:decoration-warning"
|
||||
class="min-w-0 flex-1 text-[13px] text-black underline decoration-neutral-300 underline-offset-2 hover:decoration-coollabs sm:truncate dark:text-fg dark:decoration-white/20 dark:hover:decoration-warning"
|
||||
title="{{ $row['url'] }}">
|
||||
{{ $row['url'] }}
|
||||
</a>
|
||||
@@ -101,11 +105,6 @@
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 truncate text-[13px] text-neutral-500 dark:text-fg-dim"
|
||||
title="{{ $checkedAt ?? '' }}">
|
||||
{{ $checkedAt ?: '-' }}
|
||||
</div>
|
||||
|
||||
<div class="min-w-0" title="Search engine indexing">
|
||||
@unless ($isSuggested)
|
||||
<span class="domains-mobile-label">Search engine indexing</span>
|
||||
@@ -114,6 +113,7 @@
|
||||
<span class="text-[13px] text-neutral-500 dark:text-fg-dim">-</span>
|
||||
@elseif (auth()->user()?->can('update', $application) && ! $labelsAreWritable)
|
||||
<x-forms.listbox id="domain-indexing-{{ $index }}" :wire="false"
|
||||
preserveValue
|
||||
:value="$application->isDomainNoindexed($row['url']) ? 'noindex' : 'index'"
|
||||
onChange="toggleNoindexDomain" :onChangeArgs="[$row['url']]" portal :options="[
|
||||
['value' => 'index', 'label' => 'Indexable'],
|
||||
@@ -126,6 +126,7 @@
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if ($showDirectionControl ?? true)
|
||||
<div class="min-w-0" title="Direction">
|
||||
@php
|
||||
$rowDirection = $domainDirection ?? $redirect;
|
||||
@@ -140,6 +141,7 @@
|
||||
@endif
|
||||
@if ($showDirection && auth()->user()?->can('update', $application) && ! $labelsAreWritable)
|
||||
<x-forms.listbox id="domain-direction-{{ $index }}" :wire="false" :value="$rowDirection"
|
||||
preserveValue
|
||||
:onChange="$isCompose ? 'updateServiceRedirect' : 'updateRedirect'"
|
||||
:onChangeArgs="$isCompose ? [$row['service']] : []" portal :options="[
|
||||
['value' => 'both', 'label' => 'Allow www & non-www'],
|
||||
@@ -150,6 +152,7 @@
|
||||
<span class="text-[13px] text-neutral-500 dark:text-fg-dim">{{ $directionLabel }}</span>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
@can('update', $application)
|
||||
|
||||
@@ -107,13 +107,13 @@
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@click="open = false; document.getElementById('service-stop-trigger')?.click()"
|
||||
role="menuitem">
|
||||
<x-reicon name="stop" class="size-3.5 text-error" />
|
||||
<x-reicon name="stop-circle" class="size-3.5 text-error" />
|
||||
Stop
|
||||
</button>
|
||||
@else
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!" disabled
|
||||
role="menuitem">
|
||||
<x-reicon name="stop" class="size-3.5 opacity-70" />
|
||||
<x-reicon name="stop-circle" class="size-3.5 opacity-70" />
|
||||
Stop
|
||||
</button>
|
||||
@endcan
|
||||
@@ -181,43 +181,70 @@
|
||||
class="resource-heading-navbar application-heading-actions flex w-auto min-w-0 items-center justify-end gap-1 overflow-visible">
|
||||
<div class="resource-heading-actions flex shrink-0 items-center gap-0.5">
|
||||
@if ($service->isDeployable)
|
||||
<x-services.advanced :service="$service" />
|
||||
<div class="resource-heading-menus shrink-0">
|
||||
<x-services.links :service="$service" />
|
||||
</div>
|
||||
@if ($serviceStatus->contains('running') || $serviceStatus->contains('degraded'))
|
||||
<div id="service-desktop-actions" class="relative" x-data="{ open: false }"
|
||||
<div id="service-desktop-actions" class="relative" x-data="{ open: false }"
|
||||
@click.outside="open = false" @keydown.escape.window="open = false">
|
||||
<button type="button" class="button" @click="open = !open" :aria-expanded="open">
|
||||
<x-reicon name="play-circle" class="size-3.5 text-warning" />
|
||||
<x-reicon name="play-circle" class="size-3.5 opacity-70" />
|
||||
Actions
|
||||
<x-reicon name="chevron-down" class="size-3 opacity-55" />
|
||||
</button>
|
||||
<div x-cloak x-show="open" x-transition.origin.top.right
|
||||
class="listbox-panel top-full! right-0! left-auto! mt-1! w-52! min-w-0!" role="menu">
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('deploy', $service))
|
||||
@click="open = false; document.getElementById('service-restart-trigger')?.click()">
|
||||
<x-reicon name="restart" class="size-3.5 opacity-70" />
|
||||
Restart
|
||||
</button>
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('stop', $service))
|
||||
@click="open = false; document.getElementById('service-stop-trigger')?.click()">
|
||||
<x-reicon name="stop" class="size-3.5 text-error" />
|
||||
Stop
|
||||
</button>
|
||||
class="listbox-panel top-full! right-0! left-auto! mt-1! w-64! min-w-0!" role="menu">
|
||||
@if ($serviceStatus->contains('running') || $serviceStatus->contains('degraded'))
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('deploy', $service))
|
||||
@click="open = false; document.getElementById('service-restart-trigger')?.click()">
|
||||
<x-reicon name="restart" class="size-3.5 opacity-70" />
|
||||
Restart
|
||||
</button>
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('stop', $service))
|
||||
@click="open = false; document.getElementById('service-stop-trigger')?.click()">
|
||||
<x-reicon name="stop-circle" class="size-3.5 text-error" />
|
||||
Stop
|
||||
</button>
|
||||
@else
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('deploy', $service))
|
||||
@click="deploying = true; $wire.dispatch('startEvent'); open = false">
|
||||
<x-reicon name="play-circle" class="size-3.5 opacity-70" />
|
||||
Deploy
|
||||
</button>
|
||||
@endif
|
||||
<div class="my-1 border-t border-coolgray-200 dark:border-coolgray-300" role="separator"></div>
|
||||
@if ($serviceStatus->contains('running'))
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('deploy', $service))
|
||||
@click="$wire.dispatch('pullAndRestartEvent'); open = false">
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
Pull Latest Images & Restart
|
||||
</button>
|
||||
@elseif ($serviceStatus->contains('degraded'))
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('deploy', $service))
|
||||
@click="$wire.dispatch('forceDeployEvent'); open = false">
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
Force Restart
|
||||
</button>
|
||||
@else
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('deploy', $service))
|
||||
@click="$wire.dispatch('forceDeployEvent'); open = false">
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
Force Deploy
|
||||
</button>
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('stop', $service))
|
||||
@click="$wire.dispatch('cleanupEvent'); open = false">
|
||||
<x-reicon name="trash" class="size-3.5 opacity-70" />
|
||||
Force Cleanup Containers
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<x-forms.button canGate="deploy" :canResource="$service"
|
||||
x-bind:disabled="deploying"
|
||||
@click="deploying = true; $wire.dispatch('startEvent')">
|
||||
<x-loading-on-button x-show="deploying" x-cloak />
|
||||
<x-reicon name="play-circle" class="size-4 opacity-70" x-show="!deploying" />
|
||||
<span x-text="deploying ? 'Deploying…' : 'Deploy'">Deploy</span>
|
||||
</x-forms.button>
|
||||
@endif
|
||||
</div>
|
||||
@else
|
||||
<a href="{{ $environmentVariablesUrl }}" {{ wireNavigate() }}
|
||||
aria-label="Open required environment variables">
|
||||
@@ -264,7 +291,7 @@
|
||||
|
||||
if (isDeploymentProgress) {
|
||||
$wire.$dispatch('error',
|
||||
'There is a deployment in progress.<br><br>You can force deploy in the Advanced section.');
|
||||
'There is a deployment in progress.<br><br>You can force deploy from the Actions menu.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -278,7 +305,7 @@
|
||||
|
||||
if (isDeploymentProgress) {
|
||||
$wire.$dispatch('error',
|
||||
'There is a deployment in progress.<br><br>You can force deploy in the Advanced section.');
|
||||
'There is a deployment in progress.<br><br>You can force deploy from the Actions menu.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
@if ($showServiceColumn)
|
||||
<span>Service</span>
|
||||
@endif
|
||||
<span>DNS</span>
|
||||
<span>Last checked</span>
|
||||
<span>DNS Check</span>
|
||||
<span>Search engine indexing</span>
|
||||
<span>Direction</span>
|
||||
<span></span>
|
||||
@@ -39,9 +38,6 @@
|
||||
'pending' => 'DNS pending',
|
||||
default => 'DNS unknown',
|
||||
};
|
||||
$checkedAt = ! empty($row['checked_at'])
|
||||
? \Illuminate\Support\Carbon::parse($row['checked_at'])->diffForHumans()
|
||||
: null;
|
||||
$serviceLabel = filled($row['service_name'] ?? null)
|
||||
? \Illuminate\Support\Str::headline($row['service_name'])
|
||||
: '-';
|
||||
@@ -84,12 +80,19 @@
|
||||
</span>
|
||||
@else
|
||||
@if ($faviconUrl)
|
||||
<img src="{{ $faviconUrl }}" alt="" loading="lazy" decoding="async"
|
||||
referrerpolicy="no-referrer" x-on:error="$el.remove()"
|
||||
class="size-4 shrink-0 rounded-sm" />
|
||||
<span class="relative size-4 shrink-0" aria-hidden="true">
|
||||
<x-reicon name="globe"
|
||||
class="domain-favicon-fallback size-4 text-neutral-400 dark:text-fg-faint" />
|
||||
<img src="{{ $faviconUrl }}" alt="" loading="lazy" decoding="async"
|
||||
referrerpolicy="no-referrer"
|
||||
x-init="if ($el.complete && $el.naturalWidth > 0) { $el.previousElementSibling.classList.add('hidden'); $el.classList.remove('invisible') }"
|
||||
x-on:load="$el.previousElementSibling.classList.add('hidden'); $el.classList.remove('invisible')"
|
||||
x-on:error="$el.remove()"
|
||||
class="invisible absolute inset-0 size-4 rounded-sm" />
|
||||
</span>
|
||||
@endif
|
||||
<a href="{{ getFqdnWithoutPort($row['url']) }}" target="_blank"
|
||||
class="min-w-0 text-[13px] text-black underline decoration-neutral-300 underline-offset-2 hover:decoration-coollabs sm:truncate dark:text-fg dark:decoration-white/20 dark:hover:decoration-warning"
|
||||
class="min-w-0 flex-1 text-[13px] text-black underline decoration-neutral-300 underline-offset-2 hover:decoration-coollabs sm:truncate dark:text-fg dark:decoration-white/20 dark:hover:decoration-warning"
|
||||
title="{{ $row['url'] }}">
|
||||
{{ $row['url'] }}
|
||||
</a>
|
||||
@@ -123,10 +126,6 @@
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 truncate text-[13px] text-neutral-500 dark:text-fg-dim">
|
||||
{{ $checkedAt ?: '-' }}
|
||||
</div>
|
||||
|
||||
<div class="min-w-0">
|
||||
@unless ($isSuggested)
|
||||
<span class="domains-mobile-label">Search engine indexing</span>
|
||||
@@ -136,6 +135,7 @@
|
||||
@elseif (auth()->user()?->can('update', $service))
|
||||
<x-forms.listbox id="service-domain-indexing-{{ $row['service_application_id'] }}-{{ $index }}"
|
||||
:wire="false"
|
||||
preserveValue
|
||||
:value="$service->applications->firstWhere('id', $row['service_application_id'])?->isDomainNoindexed($row['url']) ? 'noindex' : 'index'"
|
||||
onChange="toggleNoindexDomain"
|
||||
:onChangeArgs="[(int) $row['service_application_id'], $row['url']]" portal :options="[
|
||||
@@ -163,7 +163,7 @@
|
||||
@endif
|
||||
@if ($showDirection && auth()->user()?->can('update', $service))
|
||||
<x-forms.listbox id="service-domain-direction-{{ $row['service_application_id'] }}-{{ $index }}"
|
||||
:wire="false" :value="$rowDirection" onChange="updateServiceRedirect"
|
||||
:wire="false" :value="$rowDirection" preserveValue onChange="updateServiceRedirect"
|
||||
:onChangeArgs="[(int) $row['service_application_id']]" portal :options="[
|
||||
['value' => 'both', 'label' => 'Allow www & non-www'],
|
||||
['value' => 'www', 'label' => 'Redirect to www'],
|
||||
|
||||
@@ -171,33 +171,43 @@
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@click="open = false; document.getElementById('server-mobile-restart-proxy-trigger')?.click()"
|
||||
role="menuitem">
|
||||
<x-reicon name="restart" class="size-3.5 text-orange-500 dark:text-warning" />
|
||||
<span class="flex size-4 shrink-0 items-center justify-center">
|
||||
<x-reicon name="restart" class="size-3.5 text-orange-500 dark:text-warning" />
|
||||
</span>
|
||||
Restart Proxy
|
||||
</button>
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@click="open = false; document.getElementById('server-mobile-stop-proxy-trigger')?.click()"
|
||||
role="menuitem">
|
||||
<x-reicon name="stop" class="size-3.5 text-error" />
|
||||
<span class="flex size-4 shrink-0 items-center justify-center">
|
||||
<x-reicon name="stop-circle" class="size-3.5 text-error" />
|
||||
</span>
|
||||
Stop Proxy
|
||||
</button>
|
||||
@if ($traefikDashboardAvailable)
|
||||
<a class="listbox-option justify-start! gap-2.5!" target="_blank"
|
||||
href="http://{{ $serverIp }}:8080" @click="open = false" role="menuitem">
|
||||
<x-reicon name="external-link" class="size-3.5 opacity-70" />
|
||||
<span class="flex size-4 shrink-0 items-center justify-center">
|
||||
<x-reicon name="external-link" class="size-3! opacity-70" />
|
||||
</span>
|
||||
Traefik Dashboard
|
||||
</a>
|
||||
@endif
|
||||
@else
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@click="open = false; $wire.dispatch('checkProxyEvent')" role="menuitem">
|
||||
<x-reicon name="play-circle" class="size-3.5 text-warning" />
|
||||
<span class="flex size-4 shrink-0 items-center justify-center">
|
||||
<x-reicon name="play-circle" class="size-3.5 text-warning" />
|
||||
</span>
|
||||
Start Proxy
|
||||
</button>
|
||||
@endif
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
wire:click="checkProxyStatus" wire:loading.attr="disabled"
|
||||
@click="open = false" role="menuitem">
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
<span class="flex size-4 shrink-0 items-center justify-center">
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
</span>
|
||||
Refresh Proxy Status
|
||||
</button>
|
||||
</div>
|
||||
@@ -276,55 +286,65 @@
|
||||
|
||||
@if ($server->proxySet())
|
||||
@can('manageProxy', $server)
|
||||
<div
|
||||
class="resource-heading-actions flex shrink-0 items-center gap-0.5">
|
||||
@if ($proxyCanBeStopped)
|
||||
<div class="mt-1" wire:loading wire:target="loadProxyConfiguration">
|
||||
<x-loading text="Checking Traefik dashboard" />
|
||||
</div>
|
||||
@if ($traefikDashboardAvailable)
|
||||
<a class="button" target="_blank" href="http://{{ $serverIp }}:8080">
|
||||
Traefik Dashboard
|
||||
<x-external-link />
|
||||
</a>
|
||||
<div id="server-desktop-actions" class="resource-heading-actions relative shrink-0"
|
||||
x-data="{ open: false }" @click.outside="open = false"
|
||||
@keydown.escape.window="open = false">
|
||||
<button type="button" class="button" @click="open = !open" :aria-expanded="open"
|
||||
aria-haspopup="menu" wire:loading.attr="disabled" wire:loading.class="is-loading"
|
||||
wire:target="checkProxy,startProxy">
|
||||
<x-reicon name="play-circle" class="size-3.5 opacity-70" />
|
||||
Actions
|
||||
<x-reicon name="chevron-down" class="size-3 opacity-55" />
|
||||
</button>
|
||||
|
||||
<div x-cloak x-show="open" x-transition.origin.top.right
|
||||
class="listbox-panel top-full! right-0! left-auto! mt-1! w-60! min-w-0!" role="menu">
|
||||
@if ($proxyCanBeStopped)
|
||||
@if ($traefikDashboardAvailable)
|
||||
<a class="listbox-option justify-start! gap-2.5!" target="_blank"
|
||||
href="http://{{ $serverIp }}:8080" @click="open = false" role="menuitem">
|
||||
<span class="flex size-4 shrink-0 items-center justify-center">
|
||||
<x-reicon name="external-link" class="size-3! opacity-70" />
|
||||
</span>
|
||||
Traefik Dashboard
|
||||
</a>
|
||||
@endif
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@click="open = false; document.getElementById('server-mobile-restart-proxy-trigger')?.click()"
|
||||
role="menuitem">
|
||||
<span class="flex size-4 shrink-0 items-center justify-center">
|
||||
<x-reicon name="restart" class="size-3.5 opacity-70" />
|
||||
</span>
|
||||
Restart Proxy
|
||||
</button>
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@click="open = false; document.getElementById('server-mobile-stop-proxy-trigger')?.click()"
|
||||
role="menuitem">
|
||||
<span class="flex size-4 shrink-0 items-center justify-center">
|
||||
<x-reicon name="stop-circle" class="size-3.5 text-error" />
|
||||
</span>
|
||||
Stop Proxy
|
||||
</button>
|
||||
@else
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@click="open = false; $wire.dispatch('checkProxyEvent')" role="menuitem">
|
||||
<span class="flex size-4 shrink-0 items-center justify-center">
|
||||
<x-reicon name="play-circle" class="size-3.5 opacity-70" />
|
||||
</span>
|
||||
Start Proxy
|
||||
</button>
|
||||
@endif
|
||||
<x-modal-confirmation title="Confirm Proxy Restart?" buttonTitle="Restart Proxy"
|
||||
submitAction="restart" :actions="[
|
||||
'This proxy will be stopped and started again.',
|
||||
'All resources hosted on Coolify will be unavailable during the restart.',
|
||||
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||
step2ButtonText="Restart Proxy" :dispatchEvent="true"
|
||||
dispatchEventType="restartEvent">
|
||||
<x-slot:content>
|
||||
<x-forms.button title="Restart proxy">
|
||||
<x-reicon name="restart"
|
||||
class="size-4 text-orange-500 dark:text-warning" />
|
||||
Restart Proxy
|
||||
</x-forms.button>
|
||||
</x-slot:content>
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation title="Confirm Proxy Stopping?" buttonTitle="Stop Proxy"
|
||||
submitAction="stop(true)" :actions="[
|
||||
'The Coolify proxy will be stopped.',
|
||||
'All resources hosted on Coolify will be unavailable.',
|
||||
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||
step2ButtonText="Stop Proxy" :dispatchEvent="true"
|
||||
dispatchEventType="stopEvent">
|
||||
<x-slot:content>
|
||||
<x-forms.button isError title="Stop proxy">
|
||||
<x-reicon name="stop" class="size-4 text-error" />
|
||||
Stop Proxy
|
||||
</x-forms.button>
|
||||
</x-slot:content>
|
||||
</x-modal-confirmation>
|
||||
@else
|
||||
<x-forms.button @click="$wire.dispatch('checkProxyEvent')"
|
||||
wire:target="checkProxy,startProxy">
|
||||
<x-reicon name="play-circle"
|
||||
class="size-4 text-coollabs dark:text-warning" />
|
||||
Start Proxy
|
||||
</x-forms.button>
|
||||
@endif
|
||||
<div class="my-1 border-t border-coolgray-200 dark:border-coolgray-300"
|
||||
role="separator"></div>
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
wire:click="checkProxyStatus" wire:loading.attr="disabled"
|
||||
@click="open = false" role="menuitem">
|
||||
<span class="flex size-4 shrink-0 items-center justify-center">
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
</span>
|
||||
Refresh Proxy Status
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@endcan
|
||||
@endif
|
||||
|
||||
Reference in new issue
Block a user