Fix realtime build logs scrolling in deployment history
- Reload page when new deployment starts but no card exists in DOM - Only update logs content when it changes to avoid unnecessary reflows - Use double requestAnimationFrame for reliable scroll-to-bottom timing
This commit is contained in:
parent
2cbcd3d72a
commit
2b63219f66
@ -22,9 +22,7 @@ document.addEventListener("alpine:init", () => {
|
|||||||
|
|
||||||
if (diffSec < 60) return "just now";
|
if (diffSec < 60) return "just now";
|
||||||
if (diffMin < 60)
|
if (diffMin < 60)
|
||||||
return (
|
return diffMin + (diffMin === 1 ? " minute ago" : " minutes ago");
|
||||||
diffMin + (diffMin === 1 ? " minute ago" : " minutes ago")
|
|
||||||
);
|
|
||||||
if (diffHour < 24)
|
if (diffHour < 24)
|
||||||
return diffHour + (diffHour === 1 ? " hour ago" : " hours ago");
|
return diffHour + (diffHour === 1 ? " hour ago" : " hours ago");
|
||||||
if (diffDay < 7)
|
if (diffDay < 7)
|
||||||
@ -36,8 +34,7 @@ document.addEventListener("alpine:init", () => {
|
|||||||
* Get the badge class for a given status
|
* Get the badge class for a given status
|
||||||
*/
|
*/
|
||||||
statusBadgeClass(status) {
|
statusBadgeClass(status) {
|
||||||
if (status === "running" || status === "success")
|
if (status === "running" || status === "success") return "badge-success";
|
||||||
return "badge-success";
|
|
||||||
if (status === "building" || status === "deploying")
|
if (status === "building" || status === "deploying")
|
||||||
return "badge-warning";
|
return "badge-warning";
|
||||||
if (status === "failed" || status === "error") return "badge-error";
|
if (status === "failed" || status === "error") return "badge-error";
|
||||||
@ -218,9 +215,7 @@ document.addEventListener("alpine:init", () => {
|
|||||||
this.containerLogs = data.logs || "No logs available";
|
this.containerLogs = data.logs || "No logs available";
|
||||||
this.containerStatus = data.status;
|
this.containerStatus = data.status;
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
Alpine.store("utils").scrollToBottom(
|
Alpine.store("utils").scrollToBottom(this.$refs.containerLogsWrapper);
|
||||||
this.$refs.containerLogsWrapper,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.containerLogs = "Failed to fetch logs";
|
this.containerLogs = "Failed to fetch logs";
|
||||||
@ -237,9 +232,7 @@ document.addEventListener("alpine:init", () => {
|
|||||||
this.buildLogs = data.logs || "No build logs available";
|
this.buildLogs = data.logs || "No build logs available";
|
||||||
this.buildStatus = data.status;
|
this.buildStatus = data.status;
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
Alpine.store("utils").scrollToBottom(
|
Alpine.store("utils").scrollToBottom(this.$refs.buildLogsWrapper);
|
||||||
this.$refs.buildLogsWrapper,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.buildLogs = "Failed to fetch logs";
|
this.buildLogs = "Failed to fetch logs";
|
||||||
@ -248,9 +241,7 @@ document.addEventListener("alpine:init", () => {
|
|||||||
|
|
||||||
async fetchRecentDeployments() {
|
async fetchRecentDeployments() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(
|
const res = await fetch(`/apps/${this.appId}/recent-deployments`);
|
||||||
`/apps/${this.appId}/recent-deployments`,
|
|
||||||
);
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
this.deployments = data.deployments || [];
|
this.deployments = data.deployments || [];
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -283,8 +274,7 @@ document.addEventListener("alpine:init", () => {
|
|||||||
|
|
||||||
get buildStatusBadgeClass() {
|
get buildStatusBadgeClass() {
|
||||||
return (
|
return (
|
||||||
Alpine.store("utils").statusBadgeClass(this.buildStatus) +
|
Alpine.store("utils").statusBadgeClass(this.buildStatus) + " text-xs"
|
||||||
" text-xs"
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -337,14 +327,25 @@ document.addEventListener("alpine:init", () => {
|
|||||||
try {
|
try {
|
||||||
const res = await fetch(`/apps/${this.appId}/status`);
|
const res = await fetch(`/apps/${this.appId}/status`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const deploying = Alpine.store("utils").isDeploying(
|
const deploying = Alpine.store("utils").isDeploying(data.status);
|
||||||
data.status,
|
|
||||||
);
|
|
||||||
|
|
||||||
|
// Detect new deployment
|
||||||
if (
|
if (
|
||||||
data.latestDeploymentID &&
|
data.latestDeploymentID &&
|
||||||
data.latestDeploymentID !== this.currentDeploymentId
|
data.latestDeploymentID !== this.currentDeploymentId
|
||||||
) {
|
) {
|
||||||
|
// Check if we have a card for this deployment
|
||||||
|
const hasCard = document.querySelector(
|
||||||
|
`[data-deployment-id="${data.latestDeploymentID}"]`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (deploying && !hasCard) {
|
||||||
|
// New deployment started but no card exists - reload to show it
|
||||||
|
window.location.reload();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.currentDeploymentId = data.latestDeploymentID;
|
this.currentDeploymentId = data.latestDeploymentID;
|
||||||
if (deploying) {
|
if (deploying) {
|
||||||
this.isDeploying = true;
|
this.isDeploying = true;
|
||||||
@ -376,19 +377,31 @@ 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 statusBadge =
|
const statusBadge = card.querySelector(".deployment-status");
|
||||||
card.querySelector(".deployment-status");
|
|
||||||
if (logsContent)
|
if (logsContent) {
|
||||||
logsContent.textContent = data.logs || "Loading...";
|
const newLogs = data.logs || "Loading...";
|
||||||
if (logsWrapper)
|
// Only update if content changed to avoid unnecessary reflows
|
||||||
Alpine.store("utils").scrollToBottom(logsWrapper);
|
if (logsContent.textContent !== newLogs) {
|
||||||
|
logsContent.textContent = newLogs;
|
||||||
|
// Scroll after content update - use double rAF to ensure DOM is ready
|
||||||
|
if (logsWrapper) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
logsWrapper.scrollTop = logsWrapper.scrollHeight;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (statusBadge) {
|
if (statusBadge) {
|
||||||
statusBadge.className =
|
statusBadge.className =
|
||||||
"deployment-status " +
|
"deployment-status " +
|
||||||
Alpine.store("utils").statusBadgeClass(data.status);
|
Alpine.store("utils").statusBadgeClass(data.status);
|
||||||
statusBadge.textContent = Alpine.store(
|
statusBadge.textContent = Alpine.store("utils").statusLabel(
|
||||||
"utils",
|
data.status,
|
||||||
).statusLabel(data.status);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -415,8 +428,7 @@ document.addEventListener("alpine:init", () => {
|
|||||||
this.$el.querySelectorAll("[data-time]").forEach((el) => {
|
this.$el.querySelectorAll("[data-time]").forEach((el) => {
|
||||||
const time = el.getAttribute("data-time");
|
const time = el.getAttribute("data-time");
|
||||||
if (time) {
|
if (time) {
|
||||||
el.textContent =
|
el.textContent = Alpine.store("utils").formatRelativeTime(time);
|
||||||
Alpine.store("utils").formatRelativeTime(time);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 60000);
|
}, 60000);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user