Fix text selection issue in runtime logs view

Apply the same selection preservation fix to the runtime logs component
(get-logs.blade.php) that was applied to deployment logs:
- Add hasActiveLogSelection() helper to detect active text selection
- Skip re-render when user has text selected (preserves copy ability)
- Add renderTrigger mechanism to ensure filtering works after refresh
- Use x-effect for hidden state to properly react to Livewire updates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai 2025-12-15 10:52:00 +01:00
parent d40c2caca2
commit 5cc822c996

View file

@ -9,6 +9,7 @@
scrollDebounce: null, scrollDebounce: null,
colorLogs: localStorage.getItem('coolify-color-logs') === 'true', colorLogs: localStorage.getItem('coolify-color-logs') === 'true',
searchQuery: '', searchQuery: '',
renderTrigger: 0,
containerName: '{{ $container ?? "logs" }}', containerName: '{{ $container ?? "logs" }}',
makeFullscreen() { makeFullscreen() {
this.fullscreen = !this.fullscreen; this.fullscreen = !this.fullscreen;
@ -80,6 +81,18 @@
if (!this.searchQuery.trim()) return true; if (!this.searchQuery.trim()) return true;
return line.toLowerCase().includes(this.searchQuery.toLowerCase()); 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) { decodeHtml(text) {
// Decode HTML entities, handling double-encoding with max iteration limit to prevent DoS // Decode HTML entities, handling double-encoding with max iteration limit to prevent DoS
let decoded = text; let decoded = text;
@ -96,6 +109,12 @@
return decoded; return decoded;
}, },
renderHighlightedLog(el, text) { 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); const decoded = this.decodeHtml(text);
el.textContent = ''; el.textContent = '';
@ -167,6 +186,12 @@
this.$wire.getLogs(true); this.$wire.getLogs(true);
this.logsLoaded = true; this.logsLoaded = true;
} }
// Re-render logs after Livewire updates
Livewire.hook('commit', ({ succeed }) => {
succeed(() => {
this.$nextTick(() => { this.renderTrigger++; });
});
});
} }
}"> }">
@if ($collapsible) @if ($collapsible)
@ -350,8 +375,8 @@ class="text-gray-500 dark:text-gray-400 py-2">
@endphp @endphp
<div data-log-line data-log-content="{{ $line }}" <div data-log-line data-log-content="{{ $line }}"
x-effect="renderTrigger; searchQuery; $el.classList.toggle('hidden', !matchesSearch($el.dataset.logContent))"
x-bind:class="{ x-bind:class="{
'hidden': !matchesSearch($el.dataset.logContent),
'bg-red-500/10 dark:bg-red-500/15': colorLogs && getLogLevel($el.dataset.logContent) === 'error', 'bg-red-500/10 dark:bg-red-500/15': colorLogs && getLogLevel($el.dataset.logContent) === 'error',
'bg-yellow-500/10 dark:bg-yellow-500/15': colorLogs && getLogLevel($el.dataset.logContent) === 'warning', 'bg-yellow-500/10 dark:bg-yellow-500/15': colorLogs && getLogLevel($el.dataset.logContent) === 'warning',
'bg-purple-500/10 dark:bg-purple-500/15': colorLogs && getLogLevel($el.dataset.logContent) === 'debug', 'bg-purple-500/10 dark:bg-purple-500/15': colorLogs && getLogLevel($el.dataset.logContent) === 'debug',
@ -362,7 +387,7 @@ class="flex gap-2">
<span class="shrink-0 text-gray-500">{{ $timestamp }}</span> <span class="shrink-0 text-gray-500">{{ $timestamp }}</span>
@endif @endif
<span data-line-text="{{ $logContent }}" <span data-line-text="{{ $logContent }}"
x-effect="searchQuery; renderHighlightedLog($el, $el.dataset.lineText)" x-effect="renderTrigger; searchQuery; renderHighlightedLog($el, $el.dataset.lineText)"
class="whitespace-pre-wrap break-all"></span> class="whitespace-pre-wrap break-all"></span>
</div> </div>
@endforeach @endforeach