feat(ui): polish domains UX and dark-mode loading accents

Group service domain tables with inline redirect controls, live-save
redirects without full refreshes, and only auto-pair www hosts when a
www redirect is active. Align helper tooltips, server-timing HUD, and
loading spinners with dark-theme accents; default theme-color to dark.
This commit is contained in:
Andras Bacsai
2026-08-06 13:18:46 +02:00
parent 232b999cc9
commit bb83c1e3be
19 changed files with 404 additions and 176 deletions
+30 -1
View File
@@ -352,6 +352,26 @@ tr td:first-child {
animation: lds-heart 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
}
/* Loading feedback uses the yellow brand accent consistently in dark mode. */
.dark .animate-spin {
color: var(--color-warning) !important;
}
.dark #nprogress .bar {
background: var(--color-warning) !important;
}
.dark #nprogress .peg {
box-shadow:
0 0 10px var(--color-warning),
0 0 5px var(--color-warning) !important;
}
.dark #nprogress .spinner-icon {
border-top-color: var(--color-warning) !important;
border-left-color: var(--color-warning) !important;
}
.log-highlight {
background-color: rgba(234, 179, 8, 0.4);
border-radius: 2px;
@@ -409,7 +429,7 @@ tr td:first-child {
.application-console-shell {
box-shadow:
inset 0 0 0 1px rgb(255 255 255 / 0.08),
inset 0 0 0 1px var(--console-theme-border, rgb(255 255 255 / 0.08)),
0 18px 50px rgb(0 0 0 / 0.18);
}
@@ -436,12 +456,20 @@ tr td:first-child {
.application-console-shell[data-console-theme="system"],
.terminal-fullscreen-shell[data-console-theme="system"] {
--console-theme-background: #fff;
--console-theme-border: #d4d4d8;
--console-theme-opacity: 1;
}
html:not(.dark) .application-console-shell[data-console-theme="system"] .application-console-header {
background: transparent;
color: #52525b;
border-color: rgb(0 0 0 / 0.1);
backdrop-filter: none;
}
html:not(.dark) .application-console-shell[data-console-theme="system"] .application-console-block::before {
background: transparent;
backdrop-filter: none;
}
html:not(.dark) .application-console-shell[data-console-theme="system"] .application-console-header [class*="text-white"] {
@@ -451,6 +479,7 @@ html:not(.dark) .application-console-shell[data-console-theme="system"] .applica
html.dark .application-console-shell[data-console-theme="system"],
html.dark .terminal-fullscreen-shell[data-console-theme="system"] {
--console-theme-background: #121214;
--console-theme-border: rgb(255 255 255 / 0.08);
}
.console-theme-selector {
+1 -1
View File
@@ -139,7 +139,7 @@
}
@utility loading-indicator {
@apply text-coollabs dark:text-coollabs-100;
@apply text-coollabs dark:text-warning;
}
/* Compact icon-only control (gear, chevrons, etc.) */
+17 -24
View File
@@ -5,21 +5,17 @@
<div x-data="{
open: false,
pinned: false,
hideTimer: null,
style: '',
show(pinned = false) {
show() {
this.cancelHide();
this.pinned = pinned;
this.open = true;
this.$nextTick(() => this.position());
},
hide() {
this.cancelHide();
this.hideTimer = setTimeout(() => {
if (!this.pinned) {
this.open = false;
}
this.open = false;
}, 150);
},
cancelHide() {
@@ -30,7 +26,6 @@
},
close() {
this.cancelHide();
this.pinned = false;
this.open = false;
},
closeWhenFocusLeaves() {
@@ -81,8 +76,8 @@
'size-3.5' => ! isset($trigger),
])
aria-label="{{ $label }}" :aria-describedby="open ? $id('helper-popup') : null"
@mouseenter="show(false)" @mouseleave="hide" @focus="show(false)" @blur="closeWhenFocusLeaves()"
@click.prevent.stop="open && pinned ? close() : show(true)">
@mouseenter="show()" @mouseleave="hide" @focus="show()" @blur="closeWhenFocusLeaves()"
@click.prevent.stop="show()">
@isset($trigger)
{{ $trigger }}
@elseif (isset($icon))
@@ -93,20 +88,18 @@
aria-hidden="true" />
@endisset
</button>
<template x-teleport="body">
<div x-ref="popup" x-show="open" x-cloak :id="$id('helper-popup')" role="tooltip"
x-transition:enter="transition ease-out duration-100"
x-transition:enter-start="opacity-0 translate-y-0.5"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
:style="style"
class="info-helper-popup fixed z-[9999] w-max max-w-[min(20rem,calc(100vw-2rem))]"
@mouseenter="cancelHide()" @mouseleave="hide()" @focusout="closeWhenFocusLeaves()" @click.stop>
<div class="px-3 py-2.5 text-[13px] leading-5">
{!! $helper !!}
</div>
<div x-ref="popup" x-show="open" x-cloak :id="$id('helper-popup')" role="tooltip"
x-transition:enter="transition ease-out duration-100"
x-transition:enter-start="opacity-0 translate-y-0.5"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
:style="style"
class="info-helper-popup fixed z-[9999] w-max max-w-[min(20rem,calc(100vw-2rem))]"
@mouseenter="cancelHide()" @mouseleave="hide()" @focusout="closeWhenFocusLeaves()" @click.stop>
<div class="px-3 py-2.5 text-[13px] leading-5">
{!! $helper !!}
</div>
</template>
</div>
</div>
@@ -1,29 +1,81 @@
{{-- Dev-only Server-Timing HUD. Injected by AddServerTimingHeaders on full HTML responses.
JS docks into the top bar: #server-timing-hud-slot (lg+) or #server-timing-hud-slot-mobile (<lg);
floats bottom-left only if no navbar slot is available. --}}
<style data-server-timing-hud-styles>
#server-timing-hud {
--sth-background: rgba(255, 255, 255, .96);
--sth-text: #18181b;
--sth-strong: #09090b;
--sth-secondary: #52525b;
--sth-muted: #71717a;
--sth-border: rgba(0, 0, 0, .14);
--sth-surface: rgba(0, 0, 0, .035);
--sth-shadow: 0 12px 32px rgba(0, 0, 0, .18);
--sth-scrollbar-track: rgba(0, 0, 0, .06);
--sth-scrollbar-thumb: #a1a1aa;
--sth-livewire: #0284c7;
--sth-xhr: #9333ea;
--sth-document: #4d7c0f;
}
html.dark #server-timing-hud {
--sth-background: rgba(16, 16, 16, .96);
--sth-text: #e5e5e5;
--sth-strong: #fafafa;
--sth-secondary: #a3a3a3;
--sth-muted: #737373;
--sth-border: rgba(255, 255, 255, .12);
--sth-surface: rgba(255, 255, 255, .04);
--sth-shadow: 0 12px 32px rgba(0, 0, 0, .4);
--sth-scrollbar-track: rgba(255, 255, 255, .06);
--sth-scrollbar-thumb: #71717a;
--sth-livewire: #38bdf8;
--sth-xhr: #c084fc;
--sth-document: #a3e635;
}
#server-timing-hud [data-sth-log] {
scrollbar-color: var(--sth-scrollbar-thumb) var(--sth-scrollbar-track);
scrollbar-width: thin;
}
#server-timing-hud [data-sth-log]::-webkit-scrollbar {
width: 8px;
}
#server-timing-hud [data-sth-log]::-webkit-scrollbar-thumb {
border-radius: 9999px;
background: var(--sth-scrollbar-thumb);
}
#server-timing-hud [data-sth-log]::-webkit-scrollbar-track {
border-radius: 9999px;
background: var(--sth-scrollbar-track);
}
</style>
<div id="server-timing-hud" data-server-timing-hud data-metrics='@json($metrics)' data-path="{{ $path }}"
data-sth-mode="float"
style="position:fixed;bottom:12px;left:12px;z-index:2147483000;font:12px/1.4 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;color:#e5e5e5;pointer-events:auto">
style="position:fixed;bottom:12px;left:12px;z-index:2147483000;font:12px/1.4 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;color:var(--sth-text);pointer-events:auto">
<div data-sth-shell style="display:flex;flex-direction:column;align-items:flex-end;gap:6px;position:relative">
<button type="button" data-sth-toggle aria-expanded="false" aria-controls="server-timing-hud-panel"
style="border:1px solid rgba(255,255,255,.12);background:rgba(16,16,16,.92);color:#fafafa;border-radius:999px;padding:5px 9px;cursor:pointer;box-shadow:0 4px 16px rgba(0,0,0,.28);backdrop-filter:blur(8px);user-select:none;white-space:nowrap;line-height:1.2"
style="border:1px solid var(--sth-border);background:var(--sth-background);color:var(--sth-strong);border-radius:999px;padding:5px 9px;cursor:pointer;box-shadow:var(--sth-shadow);backdrop-filter:blur(8px);user-select:none;white-space:nowrap;line-height:1.2"
title="Show/hide Server-Timing history">
<span data-sth-summary>ST …</span>
</button>
<div id="server-timing-hud-panel" data-sth-panel hidden
style="width:min(420px,calc(100vw - 24px));border:1px solid rgba(255,255,255,.12);background:rgba(16,16,16,.96);border-radius:12px;padding:10px 12px;box-shadow:0 12px 32px rgba(0,0,0,.4);backdrop-filter:blur(10px);position:absolute;right:0;bottom:calc(100% + 6px);z-index:2147483001">
style="width:min(420px,calc(100vw - 24px));border:1px solid var(--sth-border);background:var(--sth-background);color:var(--sth-text);border-radius:12px;padding:10px 12px;box-shadow:var(--sth-shadow);backdrop-filter:blur(10px);position:absolute;right:0;bottom:calc(100% + 6px);z-index:2147483001">
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px">
<strong style="font-size:11px;letter-spacing:.04em;text-transform:uppercase;color:#a3a3a3">Server Timing</strong>
<strong style="font-size:11px;letter-spacing:.04em;text-transform:uppercase;color:var(--sth-secondary)">Server Timing</strong>
<div style="display:flex;align-items:center;gap:8px">
<span data-sth-count style="font-size:10px;color:#737373"></span>
<span data-sth-count style="font-size:10px;color:var(--sth-muted)"></span>
<button type="button" data-sth-clear
style="border:0;background:transparent;color:#a3a3a3;cursor:pointer;font:inherit;font-size:10px;padding:0;text-decoration:underline"
style="border:0;background:transparent;color:var(--sth-secondary);cursor:pointer;font:inherit;font-size:10px;padding:0;text-decoration:underline"
title="Clear request log">Clear</button>
</div>
</div>
<div data-sth-log
style="max-height:min(50vh,420px);overflow:auto;display:flex;flex-direction:column;gap:6px;margin:0;padding:0"></div>
<p style="margin:8px 0 0;font-size:10px;color:#737373">Local only · click row to copy AI-ready dump · pill toggles</p>
<p style="margin:8px 0 0;font-size:10px;color:var(--sth-muted)">Local only · click row to copy AI-ready dump · pill toggles</p>
</div>
</div>
</div>
@@ -84,7 +136,7 @@
'opacity:1',
isMobileSlot ? 'margin-right:4px' : 'margin-right:8px',
'font:11px/1.3 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace',
'color:#e5e5e5',
'color:var(--sth-text)',
'pointer-events:auto',
].join(';');
@@ -136,7 +188,7 @@
'visibility:visible',
'opacity:1',
'font:12px/1.4 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace',
'color:#e5e5e5',
'color:var(--sth-text)',
'pointer-events:auto',
].join(';');
@@ -354,12 +406,12 @@
function kindColor(kind) {
if (kind === 'livewire') {
return '#38bdf8';
return 'var(--sth-livewire)';
}
if (kind === 'xhr') {
return '#c084fc';
return 'var(--sth-xhr)';
}
return '#a3e635';
return 'var(--sth-document)';
}
function formatTime(ts) {
@@ -509,7 +561,7 @@
card.style.outline = prev || '';
if (hint) {
hint.textContent = 'Click to copy';
hint.style.color = '#737373';
hint.style.color = 'var(--sth-muted)';
}
}, 1200);
}
@@ -585,7 +637,7 @@
if (countEl) {
countEl.textContent = '0 requests';
}
log.innerHTML = '<div style="color:#737373;font-size:11px;padding:8px 0">No requests yet</div>';
log.innerHTML = '<div style="color:var(--sth-muted);font-size:11px;padding:8px 0">No requests yet</div>';
return;
}
@@ -617,10 +669,10 @@
card.style.width = '100%';
card.style.textAlign = 'left';
card.style.cursor = 'pointer';
card.style.border = '1px solid rgba(255,255,255,.08)';
card.style.border = '1px solid var(--sth-border)';
card.style.borderRadius = '8px';
card.style.padding = '8px';
card.style.background = index === 0 ? 'rgba(255,255,255,.04)' : 'transparent';
card.style.background = index === 0 ? 'var(--sth-surface)' : 'transparent';
card.style.color = 'inherit';
card.style.font = 'inherit';
@@ -640,18 +692,18 @@
const time = document.createElement('span');
time.textContent = formatTime(entry.at);
time.style.color = '#737373';
time.style.color = 'var(--sth-muted)';
time.style.fontSize = '10px';
const method = document.createElement('span');
method.textContent = entry.method;
method.style.color = '#a3a3a3';
method.style.color = 'var(--sth-secondary)';
method.style.fontSize = '10px';
const path = document.createElement('span');
path.textContent = entry.path;
path.title = entry.path;
path.style.color = '#e5e5e5';
path.style.color = 'var(--sth-text)';
path.style.fontSize = '10px';
path.style.overflow = 'hidden';
path.style.textOverflow = 'ellipsis';
@@ -663,7 +715,7 @@
copyHint.setAttribute('data-sth-copy-hint', '');
copyHint.textContent = 'Click to copy';
copyHint.style.fontSize = '10px';
copyHint.style.color = '#737373';
copyHint.style.color = 'var(--sth-muted)';
copyHint.style.marginLeft = 'auto';
head.appendChild(kind);
@@ -699,10 +751,10 @@
row.style.gap = '8px';
const k = document.createElement('span');
k.textContent = labels[name] || name;
k.style.color = '#737373';
k.style.color = 'var(--sth-muted)';
const v = document.createElement('span');
v.textContent = formatValue(name, entry.metrics[name]);
v.style.color = '#fafafa';
v.style.color = 'var(--sth-strong)';
if (name === 'app' && Number(entry.metrics[name]) >= 500) {
v.style.color = '#fbbf24';
}
+1 -1
View File
@@ -50,7 +50,7 @@
@endif
</div>
{{-- Collapse toggle + team switcher --}}
<div class="flex items-center gap-1.5 min-w-0 flex-1 pl-3 pr-4">
<div class="flex items-center gap-0.5 min-w-0 flex-1 pl-3 pr-4">
<x-top-breadcrumb />
<div id="server-topbar-context" class="min-w-0"></div>
<div class="flex-1"></div>
+1 -1
View File
@@ -14,7 +14,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex">
<meta name="theme-color" content="#ffffff" id="theme-color-meta" />
<meta name="theme-color" content="#101010" id="theme-color-meta" />
<meta name="color-scheme" content="dark light" />
<meta name="Description" content="Coolify: An open-source & self-hostable Heroku / Netlify / Vercel alternative" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
@@ -3,9 +3,9 @@
$suggestedCount = collect($domainRows)->where('is_suggested', true)->count();
$hasRows = count($domainRows) > 0;
$serviceAppCount = count($serviceApps);
$isSingleService = $serviceAppCount === 1;
$singleApp = $isSingleService ? collect($serviceApps)->first() : null;
$singleAppId = $singleApp['id'] ?? null;
$domainGroups = collect($domainRows)
->groupBy('service_application_id')
->filter(fn ($rows) => $rows->contains(fn ($row) => ! ($row['is_suggested'] ?? false)));
@endphp
<div class="flex flex-col gap-4"
@@ -38,8 +38,7 @@
},
}"
@open-edit-domain.window="openEditDomain($event.detail.index, $event.detail.url, $event.detail.serviceApplicationId, $event.detail.serviceLabel)">
<x-application.settings-section id="service-domains-section" title="Domains"
helper="Manage domains and www/non-www redirects for applications in this stack.">
<x-application.settings-section id="service-domains-section" title="Domains">
@can('update', $service)
<x-slot:actions>
<x-forms.button wire:click="checkAllDns" wire:loading.attr="disabled"
@@ -56,35 +55,10 @@
</x-callout>
@endcannot
@if ($isSingleService && $singleAppId)
<div class="flex w-full flex-col gap-3 sm:flex-row sm:items-end">
<div class="min-w-0 flex-1">
<x-forms.select canGate="update" :canResource="$service" label="Direction"
id="serviceRedirects.{{ $singleAppId }}" wire:model="serviceRedirects.{{ $singleAppId }}"
required
helper="Add both www and non-www hosts when using a redirect. Both hostnames must resolve to this server.">
<option value="both">Allow www & non-www</option>
<option value="www">Redirect to www</option>
<option value="non-www">Redirect to non-www</option>
</x-forms.select>
</div>
@can('update', $service)
<div class="w-full shrink-0 sm:w-auto">
<x-modal-confirmation title="Confirm redirection setting?" buttonTitle="Set direction"
submitAction="setServiceRedirect({{ (int) $singleAppId }})"
:actions="['Traffic for this service will be redirected to the selected direction.']"
confirmationText="{{ ($singleApp['name'] ?? 'service') . '/' }}"
confirmationLabel="Please confirm by entering the service name below"
shortConfirmationLabel="Service name" :confirmWithPassword="false"
step2ButtonText="Set direction" canGate="update" :canResource="$service" />
</div>
@endcan
</div>
@elseif ($serviceAppCount > 1)
<p class="text-sm text-neutral-500 dark:text-fg-dim">
Domains are listed with their service name. Set each service redirect from the group controls under the table when needed.
</p>
@endif
<p class="text-sm text-neutral-500 dark:text-fg-dim">
Manage domains and www/non-www redirects for applications in this stack.
</p>
</x-application.settings-section>
{{-- Toolbar --}}
@@ -175,60 +149,61 @@
icon-name="globe" />
</div>
@else
{{-- Flat table with Service column so assignment is always visible --}}
<div wire:key="service-domains-list-{{ md5(serialize($domainRows)) }}"
class="application-settings-section-body is-flush mt-1 w-full scroll-mt-28">
@include('livewire.project.service.partials.domain-table', [
'rows' => collect($domainRows),
'domainRows' => $domainRows,
'service' => $service,
'showServiceColumn' => true,
])
</div>
{{-- Multi-service redirect controls under the table --}}
@if ($serviceAppCount > 1)
<div class="mt-2 flex flex-col gap-4">
@foreach ($serviceApps as $app)
@php
$appId = $app['id'];
$heading = $app['name'] ?? 'Service';
$appDomainCount = collect($domainRows)
->where('service_application_id', $appId)
->where('is_suggested', false)
->count();
@endphp
<x-application.settings-section
:id="'service-domain-redirect-'.$appId"
:title="\Illuminate\Support\Str::headline($heading)"
:helper="$appDomainCount.' domain'.($appDomainCount === 1 ? '' : 's')">
<div class="flex w-full flex-col gap-3 sm:flex-row sm:items-end">
<div class="min-w-0 flex-1">
<x-forms.select canGate="update" :canResource="$service" label="Direction"
id="serviceRedirects.{{ $appId }}" wire:model="serviceRedirects.{{ $appId }}"
required
helper="Per-service www/non-www redirect. Add both hosts when using a redirect.">
<div wire:key="service-domains-list"
class="application-settings-section-body is-flush mt-1 w-full scroll-mt-28 overflow-hidden">
@foreach ($domainGroups as $appId => $rows)
@php
$app = collect($serviceApps)->firstWhere('id', (int) $appId);
$heading = \Illuminate\Support\Str::headline($app['name'] ?? $rows->first()['service_name'] ?? 'Service');
$appDomainCount = $rows->where('is_suggested', false)->count();
$redirect = $serviceRedirects[$appId] ?? 'both';
$redirectLabel = match ($redirect) {
'www' => 'Redirect to www',
'non-www' => 'Redirect to non-www',
default => 'Allow both',
};
@endphp
<section id="service-domain-group-{{ $appId }}" wire:key="service-domain-group-{{ $appId }}"
class="border-b border-neutral-200 last:border-b-0 dark:border-white/10">
<div class="flex w-full items-center gap-3 px-4 py-3">
<span class="min-w-0 flex-1 truncate text-sm font-medium text-black dark:text-white">{{ $heading }}</span>
<span class="hidden shrink-0 text-xs text-neutral-500 sm:inline dark:text-fg-dim">
{{ $appDomainCount }} domain{{ $appDomainCount === 1 ? '' : 's' }}
</span>
@can('update', $service)
<div class="relative flex shrink-0 items-center gap-2 px-1 py-1 text-sm text-neutral-600 dark:text-fg-dim"
wire:loading.class="opacity-50" wire:target="serviceRedirects.{{ $appId }}">
<span>{{ $redirectLabel }}</span>
<x-reicon name="chevron-down" class="size-4 shrink-0"
wire:loading.remove wire:target="serviceRedirects.{{ $appId }}" />
<x-loading-on-button wire:loading.delay wire:target="serviceRedirects.{{ $appId }}" />
<select id="service-domain-redirect-{{ $appId }}"
wire:model.change="serviceRedirects.{{ $appId }}"
wire:loading.attr="disabled" wire:target="serviceRedirects.{{ $appId }}"
class="absolute inset-0 size-full cursor-pointer opacity-0 disabled:cursor-wait"
aria-label="Redirect direction for {{ $heading }}">
<option value="both">Allow www & non-www</option>
<option value="www">Redirect to www</option>
<option value="non-www">Redirect to non-www</option>
</x-forms.select>
</select>
</div>
@can('update', $service)
<div class="w-full shrink-0 sm:w-auto">
<x-modal-confirmation title="Confirm redirection setting?" buttonTitle="Set direction"
submitAction="setServiceRedirect({{ (int) $appId }})"
:actions="['Traffic for this service will be redirected to the selected direction.']"
confirmationText="{{ ($heading ?: 'service') . '/' }}"
confirmationLabel="Please confirm by entering the service name below"
shortConfirmationLabel="Service name" :confirmWithPassword="false"
step2ButtonText="Set direction" canGate="update" :canResource="$service" />
</div>
@endcan
</div>
</x-application.settings-section>
@endforeach
</div>
@endif
@else
<span class="shrink-0 text-sm text-neutral-600 dark:text-fg-dim">{{ $redirectLabel }}</span>
@endcan
</div>
<div wire:key="service-domain-rows-{{ $appId }}-{{ md5(serialize($rows->all())) }}">
@include('livewire.project.service.partials.domain-table', [
'rows' => $rows,
'domainRows' => $domainRows,
'service' => $service,
'showServiceColumn' => false,
'showHeader' => false,
])
</div>
</section>
@endforeach
</div>
@endif
{{-- Edit domain modal: open/close is Alpine-only; server runs only on Save / Continue. --}}
@@ -1,18 +1,21 @@
@php
$showServiceColumn = $showServiceColumn ?? false;
$showHeader = $showHeader ?? true;
$gridClass = $showServiceColumn ? 'domains-table-grid-compose' : 'domains-table-grid';
@endphp
<div class="data-table w-full">
<div class="data-table-header {{ $gridClass }}">
<span>Domain</span>
@if ($showServiceColumn)
<span>Service</span>
@endif
<span>DNS</span>
<span>Last checked</span>
<span></span>
</div>
@if ($showHeader)
<div class="data-table-header {{ $gridClass }}">
<span>Domain</span>
@if ($showServiceColumn)
<span>Service</span>
@endif
<span>DNS</span>
<span>Last checked</span>
<span></span>
</div>
@endif
@foreach ($rows as $row)
@php
$index = collect($domainRows)->search(
@@ -12,6 +12,8 @@
@forelse($executions as $execution)
<div class="border-b border-neutral-200 last:border-b-0 dark:border-white/[0.08]">
<button type="button" wire:click="selectExecution({{ data_get($execution, 'id') }})"
wire:loading.attr="disabled"
wire:target="selectExecution({{ data_get($execution, 'id') }})"
class="flex w-full items-center gap-4 px-4 py-3 text-left transition-colors hover:bg-neutral-50 dark:hover:bg-white/[0.03]">
<x-status-badge
:status="match (data_get($execution, 'status')) {
@@ -40,7 +42,10 @@
</p>
</div>
<x-chevron-down
wire:loading.remove wire:target="selectExecution({{ data_get($execution, 'id') }})"
class="size-4 shrink-0 text-neutral-400 transition-transform {{ data_get($execution, 'id') == $selectedKey ? 'rotate-180' : '' }}" />
<x-loading wire:loading wire:target="selectExecution({{ data_get($execution, 'id') }})"
class="size-4 shrink-0" />
</button>
@if (data_get($execution, 'id') == $selectedKey)
@@ -57,7 +62,7 @@
</div>
@if ($this->logLines->isNotEmpty())
<pre class="max-h-80 overflow-auto rounded-lg bg-neutral-950 p-4 font-mono text-xs leading-5 text-neutral-300">@foreach ($this->logLines as $line)
<pre class="max-h-80 overflow-auto rounded-lg bg-white text-neutral-700 p-4 font-mono text-xs leading-5 ring-1 ring-neutral-200 dark:bg-neutral-950 dark:text-neutral-300 dark:ring-white/[0.08]">@foreach ($this->logLines as $line)
{{ $line }}
@endforeach</pre>
@if ($this->hasMoreLogs())