Fix: log viewer wipes itself due to auto-reload on deploy done

Two bugs causing 'can't see why deploy failed':
- showLog() called window.location.reload() on the SSE 'done' event,
  wiping the log panel before the user could read it.
- For already-finished deploys, SSE would immediately fire 'done' and
  reload, showing logs for < 1 second.

Fix:
- showLog() now fetches the deploy via REST first. If done, it renders
  the stored log directly (no SSE). If still running, it streams via
  SSE and closes without reloading when done.
- Added onerror fallback: re-fetches the log via REST if SSE drops.
- Status badge (green/red) updates inline instead of triggering reload.
- Page now auto-opens the latest deploy log on load so the failure
  reason is visible immediately without any clicking.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
This commit is contained in:
Claude 2026-03-19 08:57:01 +00:00
parent 0180f37c31
commit d322cc3ce1
No known key found for this signature in database

View file

@ -213,6 +213,8 @@ pub async fn app_detail(
.await
.unwrap_or_default();
let latest_deploy_id = deploys.first().map(|d| d.id.as_str()).unwrap_or("");
let mut deploy_rows = String::new();
for d in &deploys {
let sha_short = d.sha.as_deref()
@ -268,7 +270,7 @@ pub async fn app_detail(
<tbody>{deploy_rows}</tbody>
</table>
<div id="log-panel" style="display:none;margin-top:16px">
<h2 style="margin-bottom:8px">Build Log</h2>
<h2 id="log-title" style="margin-bottom:8px">Build Log</h2>
<pre id="log-out"></pre>
</div>
</div>
@ -299,20 +301,64 @@ pub async fn app_detail(
<script>
const APP_ID = '{app_id}';
// Auto-open the latest deploy log on page load.
window.addEventListener('DOMContentLoaded', () => {{
const latest = '{latest_deploy_id}';
if (latest) showLog(latest);
}});
async function deploy() {{
const r = await fetch('/api/apps/' + APP_ID + '/deploy', {{method:'POST'}});
if (r.ok) {{ const d = await r.json(); showLog(d.id); }}
else alert('Deploy failed: ' + await r.text());
}}
function showLog(deployId) {{
async function showLog(deployId) {{
const panel = document.getElementById('log-panel');
const out = document.getElementById('log-out');
const title = document.getElementById('log-title');
panel.style.display = 'block';
out.textContent = '';
out.textContent = 'Loading';
panel.scrollIntoView({{behavior:'smooth'}});
// Fetch current deploy state first.
const r = await fetch('/api/deploys/' + deployId);
if (!r.ok) {{ out.textContent = 'Could not load deploy ' + deployId; return; }}
const deploy = await r.json();
title.textContent = 'Build Log ' + deploy.status;
title.style.color = deploy.status === 'success' ? '#4ade80'
: deploy.status === 'failed' ? '#f87171' : '#fb923c';
// Already finished — just render the stored log, no SSE needed.
if (deploy.status === 'success' || deploy.status === 'failed') {{
out.textContent = deploy.log || '(no output captured)';
out.scrollTop = out.scrollHeight;
return;
}}
// Still running — stream updates via SSE.
out.textContent = '';
const es = new EventSource('/api/deploys/' + deployId + '/logs');
es.onmessage = e => {{ out.textContent += e.data; out.scrollTop = out.scrollHeight; }};
es.addEventListener('done', () => {{ es.close(); window.location.reload(); }});
es.onmessage = e => {{
out.textContent += e.data;
out.scrollTop = out.scrollHeight;
}};
es.addEventListener('done', e => {{
es.close();
title.textContent = 'Build Log ' + e.data;
title.style.color = e.data === 'success' ? '#4ade80' : '#f87171';
}});
es.onerror = () => {{
es.close();
// Fallback: re-fetch the finished log.
fetch('/api/deploys/' + deployId)
.then(r => r.json())
.then(d => {{
out.textContent = d.log || out.textContent;
title.textContent = 'Build Log ' + d.status;
}});
}};
}}
async function setEnv() {{
const key = document.getElementById('ev-key').value.trim();
@ -331,15 +377,16 @@ pub async fn app_detail(
window.location.reload();
}}
</script>"#,
name = app.name,
repo = app.repo_url,
branch = app.branch,
port = app.port,
host = host,
app_id = app.id,
secret = app.webhook_secret,
deploy_rows = deploy_rows,
env_rows = env_rows,
name = app.name,
repo = app.repo_url,
branch = app.branch,
port = app.port,
host = host,
app_id = app.id,
secret = app.webhook_secret,
deploy_rows = deploy_rows,
env_rows = env_rows,
latest_deploy_id = latest_deploy_id,
);
Ok(Html(page(&app.name, &body)))