From c54902f7760c700ec3ed609eab8c1861dddbed91 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 12 Oct 2026 14:55:37 +0200 Subject: [PATCH] feat(logs): add text view, group log toolbar into menus, and use skeleton loaders - Runtime logs: add a compact text view, move display options and log level filters into a View menu, move copy and download into an Export menu, and remove the extra top margin. - Replace spinner loading cards with skeletons on the logs, dynamic configurations, proxy, TLS certificates, Docker images, environment variables, preview deployments, rollback, subscription, cloud provider, and new resource pages. --- resources/css/app.css | 56 ++++ resources/js/runtime-logs.js | 19 +- .../views/components/skeleton/cards.blade.php | 23 ++ .../views/components/skeleton/form.blade.php | 15 + .../views/components/skeleton/list.blade.php | 25 ++ .../views/components/skeleton/logs.blade.php | 30 ++ .../skeleton/settings-sections.blade.php | 20 ++ .../project/application/previews.blade.php | 4 +- .../project/application/rollback.blade.php | 5 +- .../livewire/project/new/select.blade.php | 4 +- .../shared/environment-variable/all.blade.php | 6 +- .../project/shared/get-logs.blade.php | 278 +++++++++--------- .../livewire/project/shared/logs.blade.php | 11 +- .../livewire/server/docker-images.blade.php | 5 +- .../server/new/by-digital-ocean.blade.php | 4 +- .../livewire/server/new/by-hetzner.blade.php | 4 +- .../livewire/server/new/by-vultr.blade.php | 4 +- .../views/livewire/server/proxy.blade.php | 8 +- .../server/proxy/certificates.blade.php | 6 +- .../proxy/dynamic-configurations.blade.php | 6 +- .../livewire/subscription/index.blade.php | 4 +- .../EnvironmentVariableAsyncLoadTest.php | 4 +- .../Server/NewServerProviderTokenUrlTest.php | 8 +- .../Browser/RuntimeLogsVirtualizationTest.php | 38 +++ 24 files changed, 397 insertions(+), 190 deletions(-) create mode 100644 resources/views/components/skeleton/cards.blade.php create mode 100644 resources/views/components/skeleton/form.blade.php create mode 100644 resources/views/components/skeleton/list.blade.php create mode 100644 resources/views/components/skeleton/logs.blade.php create mode 100644 resources/views/components/skeleton/settings-sections.blade.php diff --git a/resources/css/app.css b/resources/css/app.css index 9423c24430..c963d109fe 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -3956,6 +3956,25 @@ html[data-theme="custom"] .logs-viewer-timestamp { opacity: 1 !important; } +/* View and Export menus sit after the search field and the Lines control. */ +.runtime-log-menus { + display: flex; + order: 3; + align-items: center; + gap: 0.5rem; +} + +.runtime-log-menu-label { + padding: 0.5rem 0.5rem 0.25rem; + font-size: 0.6875rem; + font-weight: 500; + color: #737373; +} + +.dark .runtime-log-menu-label { + color: var(--color-fg-faint); +} + .runtime-log-viewport { min-height: 12rem; border-radius: 0 0 8px 8px; @@ -4139,6 +4158,43 @@ html[data-theme="custom"] .runtime-log-columns { grid-column: 2; } +/* Text view: one compact, wrapped text line per log entry, like the old viewer. */ +.runtime-log-text-view { + padding-block: 0.5rem; +} + +.runtime-log-viewport .runtime-log-text-view [data-log-line]:not(.hidden) { + display: flex; + gap: 0.75rem; + min-height: 0; + padding: 0 1rem; + border-bottom: 0; + cursor: text; + font-size: 0.75rem; + line-height: 1.5; +} + +.runtime-log-viewport .runtime-log-text-view [data-log-line]::before, +.runtime-log-viewport .runtime-log-text-view [data-log-line]::after { + display: none; +} + +.runtime-log-viewport .runtime-log-text-view .logs-viewer-timestamp { + font-size: inherit; + line-height: inherit; +} + +.runtime-log-viewport .runtime-log-text-view [data-line-text] { + flex: 1 1 auto; + overflow: visible; + overflow-wrap: anywhere; + white-space: pre-wrap; +} + +.runtime-log-viewport .runtime-log-text-view .log-error [data-line-text] { color: #e55e73; } +.runtime-log-viewport .runtime-log-text-view .log-warning [data-line-text] { color: #d79945; } +.runtime-log-viewport .runtime-log-text-view .log-debug [data-line-text] { color: #929099; } + @container runtime-log-explorer (max-width: 650px) { .runtime-log-columns, .runtime-log-viewport [data-log-line]:not(.hidden) { diff --git a/resources/js/runtime-logs.js b/resources/js/runtime-logs.js index c067d8ac36..0dad173ada 100644 --- a/resources/js/runtime-logs.js +++ b/resources/js/runtime-logs.js @@ -13,6 +13,7 @@ import { export const MAX_CLIENT_LOG_LINES = 50000; export const STREAM_INTERVAL_MS = 2000; const ESTIMATED_ROW_HEIGHT = 44; +const ESTIMATED_TEXT_ROW_HEIGHT = 20; const TIMESTAMP_PATTERN = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z) (.*)$/s; @@ -187,6 +188,7 @@ export function initializeRuntimeLogsComponent() { scrollDebounce: null, destroyed: false, colorLogs: localStorage.getItem('coolify-color-logs') === 'true', + textView: localStorage.getItem('coolify-log-text-view') === 'true', logFilters: readLogFilters(localStorage.getItem('coolify-log-filters')), searchQuery: '', appliedQuery: '', @@ -222,7 +224,7 @@ export function initializeRuntimeLogsComponent() { virtualizer = new Virtualizer({ count: 0, getScrollElement: () => this.$refs.viewport ?? null, - estimateSize: () => ESTIMATED_ROW_HEIGHT, + estimateSize: () => (this.textView ? ESTIMATED_TEXT_ROW_HEIGHT : ESTIMATED_ROW_HEIGHT), getItemKey: (index) => filtered[index]?.id ?? index, overscan: 12, // Keep the visible rows in place when old lines are trimmed from the top. @@ -370,6 +372,7 @@ export function initializeRuntimeLogsComponent() { }, toggleLogDetails(id, event) { + if (this.textView) return; if (window.getSelection()?.toString()) return; if (event.type === 'keydown' && !['Enter', ' '].includes(event.key)) return; if (event.type === 'keydown') event.preventDefault(); @@ -377,7 +380,7 @@ export function initializeRuntimeLogsComponent() { }, isLogExpanded(id) { - return this.expandedLogs[id] === true; + return !this.textView && this.expandedLogs[id] === true; }, toggleLogFilter(level) { @@ -391,6 +394,18 @@ export function initializeRuntimeLogsComponent() { localStorage.setItem('coolify-color-logs', this.colorLogs); }, + // Plain text lines without columns, row borders, or expandable details. + toggleTextView() { + this.textView = !this.textView; + localStorage.setItem('coolify-log-text-view', this.textView); + this.$nextTick(() => { + virtualizer?.measure(); + this.$refs.list?.querySelectorAll('.runtime-log-virtual-row').forEach((el) => this.measureRow(el)); + this.updateVirtualizer(); + if (this.alwaysScroll) this.scrollToBottom(); + }); + }, + downloadLogs() { const timestamp = new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-'); downloadText(this.visibleText() + '\n', `${this.containerName}-logs-${timestamp}.txt`); diff --git a/resources/views/components/skeleton/cards.blade.php b/resources/views/components/skeleton/cards.blade.php new file mode 100644 index 0000000000..c88406fcf3 --- /dev/null +++ b/resources/views/components/skeleton/cards.blade.php @@ -0,0 +1,23 @@ +@props([ + 'count' => 6, + 'label' => 'Loading', +]) + +{{-- Card grid skeleton: an icon tile, a title, and two description lines per card. --}} +
class(['grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3']) }}> + {{ $label }} + @for ($i = 0; $i < (int) $count; $i++) + + @endfor +
diff --git a/resources/views/components/skeleton/form.blade.php b/resources/views/components/skeleton/form.blade.php new file mode 100644 index 0000000000..56914de7a4 --- /dev/null +++ b/resources/views/components/skeleton/form.blade.php @@ -0,0 +1,15 @@ +@props([ + 'fields' => 4, + 'label' => 'Loading', +]) + +{{-- Form skeleton: a label and an input per field, two columns on wide screens. --}} +
class(['grid gap-4 md:grid-cols-2']) }}> + {{ $label }} + @for ($i = 0; $i < (int) $fields; $i++) + + @endfor +
diff --git a/resources/views/components/skeleton/list.blade.php b/resources/views/components/skeleton/list.blade.php new file mode 100644 index 0000000000..82695ef9d6 --- /dev/null +++ b/resources/views/components/skeleton/list.blade.php @@ -0,0 +1,25 @@ +@props([ + 'rows' => 4, + 'label' => 'Loading', +]) + +@php + // Vary the title widths so rows read as content, not a solid block. + $widths = ['w-2/5', 'w-1/3', 'w-1/2', 'w-1/4']; +@endphp + +{{-- List rows with a leading icon tile, a title, a meta line, and a trailing button. --}} +
class(['flex flex-col']) }}> + {{ $label }} + @for ($i = 0; $i < (int) $rows; $i++) + + @endfor +
diff --git a/resources/views/components/skeleton/logs.blade.php b/resources/views/components/skeleton/logs.blade.php new file mode 100644 index 0000000000..4e201dcbc5 --- /dev/null +++ b/resources/views/components/skeleton/logs.blade.php @@ -0,0 +1,30 @@ +@php + // Vary the line widths so rows read as log output, not a solid block. + $widths = ['w-3/5', 'w-2/5', 'w-4/5', 'w-1/2', 'w-3/4', 'w-1/3', 'w-2/3', 'w-1/2']; +@endphp + +{{-- Runtime log panel skeleton: header, toolbar, and log lines. --}} +
class(['runtime-log-shell overflow-hidden']) }}> + Loading containers +
+ + +
+
+ @for ($i = 0; $i < 4; $i++) + + @endfor +
+
+ @foreach ($widths as $width) +
+
+ @endforeach +
+
diff --git a/resources/views/components/skeleton/settings-sections.blade.php b/resources/views/components/skeleton/settings-sections.blade.php new file mode 100644 index 0000000000..2c9c195dc1 --- /dev/null +++ b/resources/views/components/skeleton/settings-sections.blade.php @@ -0,0 +1,20 @@ +@props([ + 'count' => 2, + 'label' => 'Loading', +]) + +{{-- Settings section skeleton: a title bar and a content block per section. --}} +
class(['flex flex-col gap-6']) }}> + {{ $label }} + @for ($i = 0; $i < (int) $count; $i++) + + @endfor +
diff --git a/resources/views/livewire/project/application/previews.blade.php b/resources/views/livewire/project/application/previews.blade.php index 23edbf3966..0792e96aa3 100644 --- a/resources/views/livewire/project/application/previews.blade.php +++ b/resources/views/livewire/project/application/previews.blade.php @@ -27,8 +27,8 @@ -
- +
+
diff --git a/resources/views/livewire/project/application/rollback.blade.php b/resources/views/livewire/project/application/rollback.blade.php index e28f8701c0..c159af392c 100644 --- a/resources/views/livewire/project/application/rollback.blade.php +++ b/resources/views/livewire/project/application/rollback.blade.php @@ -90,10 +90,7 @@
-
- - Loading available images… -
+
diff --git a/resources/views/livewire/project/new/select.blade.php b/resources/views/livewire/project/new/select.blade.php index 63386039e8..7722722582 100644 --- a/resources/views/livewire/project/new/select.blade.php +++ b/resources/views/livewire/project/new/select.blade.php @@ -92,8 +92,8 @@ -
- +
+
- + class="application-settings-section-body is-flush mt-1 w-full scroll-mt-28" role="status"> + Loading environment variables +
@else @php diff --git a/resources/views/livewire/project/shared/get-logs.blade.php b/resources/views/livewire/project/shared/get-logs.blade.php index 7b000c8a78..3f855abea3 100644 --- a/resources/views/livewire/project/shared/get-logs.blade.php +++ b/resources/views/livewire/project/shared/get-logs.blade.php @@ -42,13 +42,9 @@
- - -
- -
-
- - -
-
-
- - - - -
- - - - -
-
+
+ {{-- Display settings and level filters --}} + + +
+

Display

+ + + +

Log levels

+ + + + +
+
+ {{-- Copy and download --}} + + +
+ + + +
+
+
@@ -276,9 +272,9 @@ {{-- Alpine renders only the visible rows. Livewire must not morph them. --}}
-