fix(logs): clean up deployment log scrolling on teardown

This commit is contained in:
Andras Bacsai
2026-08-18 14:02:48 +02:00
parent 861a904992
commit b2af1c11a5
2 changed files with 39 additions and 13 deletions
@@ -7,7 +7,9 @@
alwaysScroll: false,
followManuallyDisabled: false,
rafId: null,
scrollTimeout: null,
scrollDebounce: null,
destroyed: false,
colorLogs: localStorage.getItem('coolify-color-logs') === 'true',
logFilters: JSON.parse(localStorage.getItem('coolify-log-filters')) || {error: true, warning: true, debug: true, info: true},
searchQuery: '',
@@ -17,10 +19,7 @@
this.fullscreen = !this.fullscreen;
if (this.fullscreen === false) {
this.alwaysScroll = false;
if (this.rafId) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
this.cancelScrollLoop();
}
},
handleKeyDown(event) {
@@ -33,10 +32,21 @@
disableFollow() {
if (!this.alwaysScroll) return;
this.alwaysScroll = false;
this.cancelScrollLoop();
},
cancelScrollLoop() {
if (this.rafId) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
if (this.scrollTimeout) {
clearTimeout(this.scrollTimeout);
this.scrollTimeout = null;
}
if (this.scrollDebounce) {
clearTimeout(this.scrollDebounce);
this.scrollDebounce = null;
}
},
handleWheel(event) {
if (this.alwaysScroll && event.deltaY < 0) {
@@ -62,7 +72,8 @@
}
},
scrollToBottom() {
const logsContainer = document.getElementById('logsContainer');
if (this.destroyed) return;
const logsContainer = this.$root.querySelector('#logsContainer');
if (logsContainer) {
this.isScrolling = true;
logsContainer.scrollTop = logsContainer.scrollHeight;
@@ -70,11 +81,12 @@
}
},
scheduleScroll() {
if (!this.alwaysScroll) return;
if (!this.alwaysScroll || this.destroyed) return;
this.rafId = requestAnimationFrame(() => {
if (!this.alwaysScroll || this.destroyed) return;
this.scrollToBottom();
if (this.alwaysScroll) {
setTimeout(() => this.scheduleScroll(), 250);
if (this.alwaysScroll && !this.destroyed) {
this.scrollTimeout = setTimeout(() => this.scheduleScroll(), 250);
}
});
},
@@ -85,16 +97,14 @@
this.scheduleScroll();
} else {
this.followManuallyDisabled = true;
if (this.rafId) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
this.cancelScrollLoop();
}
},
handleScroll(event) {
if (this.isScrolling) return;
if (this.isScrolling || this.destroyed) return;
clearTimeout(this.scrollDebounce);
this.scrollDebounce = setTimeout(() => {
if (this.destroyed) return;
const el = event.target;
const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
if (!this.alwaysScroll && !this.followManuallyDisabled && distanceFromBottom <= 10) {
@@ -253,6 +263,11 @@
applyAfterUpdate();
}
});
},
destroy() {
this.destroyed = true;
this.alwaysScroll = false;
this.cancelScrollLoop();
}
}" @keydown.window="handleKeyDown($event)">
@if ($collapsible)
@@ -45,3 +45,14 @@ test('turning off follow logs stays off at the bottom of the log viewer', functi
->toContain('this.followManuallyDisabled = false');
}
});
test('runtime log follow loop is cleaned up when navigating away', function () {
$view = file_get_contents(resource_path('views/livewire/project/shared/get-logs.blade.php'));
expect($view)
->toContain('scrollTimeout: null')
->toContain("this.\$root.querySelector('#logsContainer')")
->toContain('destroy() {')
->toContain('this.destroyed = true')
->toContain('this.cancelScrollLoop()');
});