From ee4afbde807fe5818052d56b985298df16dcbdad Mon Sep 17 00:00:00 2001 From: sneak Date: Thu, 8 Jan 2026 10:21:16 -0800 Subject: [PATCH] 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. --- static/js/app.js | 117 +++++++++++++++++++++---------------- templates/deployments.html | 16 +++-- 2 files changed, 77 insertions(+), 56 deletions(-) diff --git a/static/js/app.js b/static/js/app.js index c9721f3..04becc9 100644 --- a/static/js/app.js +++ b/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; }, diff --git a/templates/deployments.html b/templates/deployments.html index 4b8295f..3f83911 100644 --- a/templates/deployments.html +++ b/templates/deployments.html @@ -27,7 +27,12 @@
{{if .Deployments}} {{range .Deployments}} -
+
@@ -40,7 +45,7 @@ {{end}}
- {{if eq .Status "success"}}Success{{else if eq .Status "failed"}}Failed{{else if eq .Status "building"}}Building{{else if eq .Status "deploying"}}Deploying{{else}}{{.Status}}{{end}} +
@@ -70,7 +75,7 @@ {{if or .Logs.Valid (eq .Status "building") (eq .Status "deploying")}}
- Build Logs + Build Logs {{if or (eq .Status "success") (eq .Status "failed")}} @@ -80,9 +85,10 @@ {{end}}
-
-
{{if .Logs.Valid}}{{.Logs.String}}{{else}}Loading...{{end}}
+
+

                 
+ {{if .Logs.Valid}}{{end}}
{{end}}