Fix build logs overflow by using Alpine.js reactive bindings
Replaced manual DOM manipulation with a deploymentCard Alpine component that uses x-text bindings and x-ref for proper scrolling, matching the working container logs implementation.
This commit is contained in:
117
static/js/app.js
117
static/js/app.js
@@ -295,6 +295,71 @@ document.addEventListener("alpine:init", () => {
|
||||
},
|
||||
}));
|
||||
|
||||
// ============================================
|
||||
// Deployment Card Component (for individual deployment cards)
|
||||
// ============================================
|
||||
Alpine.data("deploymentCard", (config) => ({
|
||||
appId: config.appId,
|
||||
deploymentId: config.deploymentId,
|
||||
logs: "",
|
||||
status: config.status || "",
|
||||
pollInterval: null,
|
||||
|
||||
init() {
|
||||
// Read initial logs from script tag (avoids escaping issues)
|
||||
const initialLogsEl = this.$el.querySelector(".initial-logs");
|
||||
this.logs = initialLogsEl?.textContent || "Loading...";
|
||||
|
||||
// Only poll if deployment is in progress
|
||||
if (Alpine.store("utils").isDeploying(this.status)) {
|
||||
this.fetchLogs();
|
||||
this.pollInterval = setInterval(() => this.fetchLogs(), 1000);
|
||||
}
|
||||
},
|
||||
|
||||
destroy() {
|
||||
if (this.pollInterval) {
|
||||
clearInterval(this.pollInterval);
|
||||
}
|
||||
},
|
||||
|
||||
async fetchLogs() {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/apps/${this.appId}/deployments/${this.deploymentId}/logs`,
|
||||
);
|
||||
const data = await res.json();
|
||||
this.logs = data.logs || "No logs available";
|
||||
this.status = data.status;
|
||||
|
||||
// Scroll to bottom after update
|
||||
this.$nextTick(() => {
|
||||
Alpine.store("utils").scrollToBottom(this.$refs.logsWrapper);
|
||||
});
|
||||
|
||||
// Stop polling if deployment is done
|
||||
if (!Alpine.store("utils").isDeploying(data.status)) {
|
||||
if (this.pollInterval) {
|
||||
clearInterval(this.pollInterval);
|
||||
this.pollInterval = null;
|
||||
}
|
||||
// Reload page to show final state with duration etc
|
||||
window.location.reload();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Logs fetch error:", err);
|
||||
}
|
||||
},
|
||||
|
||||
get statusBadgeClass() {
|
||||
return Alpine.store("utils").statusBadgeClass(this.status);
|
||||
},
|
||||
|
||||
get statusLabel() {
|
||||
return Alpine.store("utils").statusLabel(this.status);
|
||||
},
|
||||
}));
|
||||
|
||||
// ============================================
|
||||
// Deployments History Page Component
|
||||
// ============================================
|
||||
@@ -317,10 +382,7 @@ document.addEventListener("alpine:init", () => {
|
||||
}
|
||||
|
||||
this.fetchAppStatus();
|
||||
setInterval(() => {
|
||||
this.fetchAppStatus();
|
||||
this.fetchDeploymentLogs();
|
||||
}, 1000);
|
||||
setInterval(() => this.fetchAppStatus(), 1000);
|
||||
},
|
||||
|
||||
async fetchAppStatus() {
|
||||
@@ -365,53 +427,6 @@ document.addEventListener("alpine:init", () => {
|
||||
}
|
||||
},
|
||||
|
||||
async fetchDeploymentLogs() {
|
||||
if (!this.currentDeploymentId || !this.isDeploying) return;
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/apps/${this.appId}/deployments/${this.currentDeploymentId}/logs`,
|
||||
);
|
||||
const data = await res.json();
|
||||
|
||||
// Update the deployment card
|
||||
const card = document.querySelector(
|
||||
`[data-deployment-id="${this.currentDeploymentId}"]`,
|
||||
);
|
||||
if (card) {
|
||||
const logsContent = card.querySelector(".logs-content");
|
||||
const logsWrapper = card.querySelector(".logs-wrapper");
|
||||
const statusBadge = card.querySelector(".deployment-status");
|
||||
|
||||
if (logsContent) {
|
||||
const newLogs = data.logs || "Loading...";
|
||||
// Only update if content changed to avoid unnecessary reflows
|
||||
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) {
|
||||
statusBadge.className =
|
||||
"deployment-status " +
|
||||
Alpine.store("utils").statusBadgeClass(data.status);
|
||||
statusBadge.textContent = Alpine.store("utils").statusLabel(
|
||||
data.status,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Logs fetch error:", err);
|
||||
}
|
||||
},
|
||||
|
||||
submitDeploy() {
|
||||
this.isDeploying = true;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user