diff --git a/app/Livewire/Project/Application/Deployment/Show.php b/app/Livewire/Project/Application/Deployment/Show.php index 44ab419c2..8c0ee1a3f 100644 --- a/app/Livewire/Project/Application/Deployment/Show.php +++ b/app/Livewire/Project/Application/Deployment/Show.php @@ -20,6 +20,8 @@ class Show extends Component public bool $is_debug_enabled = false; + public bool $fullscreen = false; + private bool $deploymentFinishedDispatched = false; public function getListeners() diff --git a/resources/views/livewire/project/application/deployment/show.blade.php b/resources/views/livewire/project/application/deployment/show.blade.php index f2cde05cf..5f37786f5 100644 --- a/resources/views/livewire/project/application/deployment/show.blade.php +++ b/resources/views/livewire/project/application/deployment/show.blade.php @@ -6,7 +6,7 @@
- @if (data_get($application_deployment_queue, 'status') === 'in_progress') -
Deployment is -
- {{ Str::headline(data_get($this->application_deployment_queue, 'status')) }}. -
- -
- {{--
Logs will be updated automatically.
--}} - @else -
Deployment is {{ Str::headline(data_get($application_deployment_queue, 'status')) }}. -
- @endif -
+
+ :class="fullscreen ? 'h-full' : 'border border-dotted rounded-sm'">
- - +
+ @if (data_get($application_deployment_queue, 'status') === 'in_progress') +
+ Deployment is + In Progress + +
+ @else +
+ Deployment is + {{ Str::headline(data_get($application_deployment_queue, 'status')) }} +
+ @endif + +
$searchableContent = $line['timestamp'] . ' ' . $lineContent; @endphp
isset($line['command']) && $line['command'], 'flex gap-2', ])> diff --git a/resources/views/livewire/project/shared/get-logs.blade.php b/resources/views/livewire/project/shared/get-logs.blade.php index 8504a160f..c4b610873 100644 --- a/resources/views/livewire/project/shared/get-logs.blade.php +++ b/resources/views/livewire/project/shared/get-logs.blade.php @@ -9,6 +9,7 @@ scrollDebounce: null, colorLogs: localStorage.getItem('coolify-color-logs') === 'true', searchQuery: '', + renderTrigger: 0, containerName: '{{ $container ?? "logs" }}', makeFullscreen() { this.fullscreen = !this.fullscreen; @@ -80,6 +81,18 @@ if (!this.searchQuery.trim()) return true; return line.toLowerCase().includes(this.searchQuery.toLowerCase()); }, + hasActiveLogSelection() { + const selection = window.getSelection(); + if (!selection || selection.isCollapsed || !selection.toString().trim()) { + return false; + } + const logsContainer = document.getElementById('logs'); + if (!logsContainer) return false; + + // Check if selection is within the logs container + const range = selection.getRangeAt(0); + return logsContainer.contains(range.commonAncestorContainer); + }, decodeHtml(text) { // Decode HTML entities, handling double-encoding with max iteration limit to prevent DoS let decoded = text; @@ -96,6 +109,12 @@ return decoded; }, renderHighlightedLog(el, text) { + // Skip re-render if user has text selected in logs (preserves copy ability) + // But always render if the element is empty (initial render) + if (el.textContent && this.hasActiveLogSelection()) { + return; + } + const decoded = this.decodeHtml(text); el.textContent = ''; @@ -167,6 +186,18 @@ this.$wire.getLogs(true); this.logsLoaded = true; } + // Prevent Livewire from morphing logs container when text is selected + Livewire.hook('morph.updating', ({ el, component, toEl, skip }) => { + if (el.id === 'logs' && this.hasActiveLogSelection()) { + skip(); + } + }); + // Re-render logs after Livewire updates + Livewire.hook('commit', ({ succeed }) => { + succeed(() => { + this.$nextTick(() => { this.renderTrigger++; }); + }); + }); } }"> @if ($collapsible) @@ -235,6 +266,23 @@ class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text- d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" /> + -