Always scroll logs to bottom on every update

This commit is contained in:
Jeffrey Paul 2025-12-31 14:56:56 -08:00
parent 83986626a4
commit 58687e77f5

View File

@ -59,14 +59,6 @@ document.addEventListener("alpine:init", () => {
return status === "building" || status === "deploying"; return status === "building" || status === "deploying";
}, },
/**
* Check if element is scrolled near the bottom (within threshold)
*/
isNearBottom(el, threshold = 100) {
if (!el) return true;
return el.scrollHeight - el.scrollTop - el.clientHeight < threshold;
},
/** /**
* Scroll an element to the bottom * Scroll an element to the bottom
*/ */
@ -221,18 +213,15 @@ document.addEventListener("alpine:init", () => {
async fetchContainerLogs() { async fetchContainerLogs() {
try { try {
const wrapper = this.$refs.containerLogsWrapper;
const wasNearBottom =
Alpine.store("utils").isNearBottom(wrapper);
const res = await fetch(`/apps/${this.appId}/container-logs`); const res = await fetch(`/apps/${this.appId}/container-logs`);
const data = await res.json(); const data = await res.json();
this.containerLogs = data.logs || "No logs available"; this.containerLogs = data.logs || "No logs available";
this.containerStatus = data.status; this.containerStatus = data.status;
if (wasNearBottom) { this.$nextTick(() => {
this.$nextTick(() => { Alpine.store("utils").scrollToBottom(
Alpine.store("utils").scrollToBottom(wrapper); this.$refs.containerLogsWrapper,
}); );
} });
} catch (err) { } catch (err) {
this.containerLogs = "Failed to fetch logs"; this.containerLogs = "Failed to fetch logs";
} }
@ -241,20 +230,17 @@ document.addEventListener("alpine:init", () => {
async fetchBuildLogs() { async fetchBuildLogs() {
if (!this.currentDeploymentId) return; if (!this.currentDeploymentId) return;
try { try {
const wrapper = this.$refs.buildLogsWrapper;
const wasNearBottom =
Alpine.store("utils").isNearBottom(wrapper);
const res = await fetch( const res = await fetch(
`/apps/${this.appId}/deployments/${this.currentDeploymentId}/logs`, `/apps/${this.appId}/deployments/${this.currentDeploymentId}/logs`,
); );
const data = await res.json(); const data = await res.json();
this.buildLogs = data.logs || "No build logs available"; this.buildLogs = data.logs || "No build logs available";
this.buildStatus = data.status; this.buildStatus = data.status;
if (wasNearBottom) { this.$nextTick(() => {
this.$nextTick(() => { Alpine.store("utils").scrollToBottom(
Alpine.store("utils").scrollToBottom(wrapper); this.$refs.buildLogsWrapper,
}); );
} });
} catch (err) { } catch (err) {
this.buildLogs = "Failed to fetch logs"; this.buildLogs = "Failed to fetch logs";
} }
@ -383,10 +369,6 @@ document.addEventListener("alpine:init", () => {
async fetchLiveLogs() { async fetchLiveLogs() {
if (!this.currentDeploymentId || !this.isDeploying) return; if (!this.currentDeploymentId || !this.isDeploying) return;
try { try {
const wrapper = this.$refs.liveLogsWrapper;
const wasNearBottom =
Alpine.store("utils").isNearBottom(wrapper);
const res = await fetch( const res = await fetch(
`/apps/${this.appId}/deployments/${this.currentDeploymentId}/logs`, `/apps/${this.appId}/deployments/${this.currentDeploymentId}/logs`,
); );
@ -394,11 +376,11 @@ document.addEventListener("alpine:init", () => {
this.liveLogs = data.logs || "Waiting for logs..."; this.liveLogs = data.logs || "Waiting for logs...";
this.liveStatus = data.status; this.liveStatus = data.status;
if (wasNearBottom) { this.$nextTick(() => {
this.$nextTick(() => { Alpine.store("utils").scrollToBottom(
Alpine.store("utils").scrollToBottom(wrapper); this.$refs.liveLogsWrapper,
}); );
} });
// Update matching deployment card if present // Update matching deployment card if present
const card = document.querySelector( const card = document.querySelector(
@ -407,13 +389,11 @@ document.addEventListener("alpine:init", () => {
if (card) { if (card) {
const logsContent = card.querySelector(".logs-content"); const logsContent = card.querySelector(".logs-content");
const logsWrapper = card.querySelector(".logs-wrapper"); const logsWrapper = card.querySelector(".logs-wrapper");
const cardWasNearBottom =
Alpine.store("utils").isNearBottom(logsWrapper);
const statusBadge = const statusBadge =
card.querySelector(".deployment-status"); card.querySelector(".deployment-status");
if (logsContent) if (logsContent)
logsContent.textContent = data.logs || "Loading..."; logsContent.textContent = data.logs || "Loading...";
if (logsWrapper && cardWasNearBottom) if (logsWrapper)
Alpine.store("utils").scrollToBottom(logsWrapper); Alpine.store("utils").scrollToBottom(logsWrapper);
if (statusBadge) { if (statusBadge) {
statusBadge.className = statusBadge.className =