Fix usability issues: redirect on missing app and back-to-dashboard after deploy

- app_detail now redirects to / instead of 404 when app is not found
  (handles case where app was removed while user was on the detail page)
- Add a "← Dashboard" button in the log panel that appears once a
  deployment finishes (both success and failed), giving the user a clear
  path back to the main screen

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
This commit is contained in:
Claude 2026-03-19 12:10:12 +00:00
parent 944feb39ec
commit b83de1e743
No known key found for this signature in database

View file

@ -1,7 +1,7 @@
use axum::{
extract::{Path, State},
http::StatusCode,
response::Html,
response::{Html, IntoResponse, Redirect, Response},
};
use crate::{
@ -189,13 +189,16 @@ pub async fn index(State(s): State<AppState>) -> Result<Html<String>, StatusCode
pub async fn app_detail(
State(s): State<AppState>,
Path(app_id): Path<String>,
) -> Result<Html<String>, StatusCode> {
let app = sqlx::query_as::<_, App>("SELECT * FROM apps WHERE id = ?")
) -> Response {
let app = match sqlx::query_as::<_, App>("SELECT * FROM apps WHERE id = ?")
.bind(&app_id)
.fetch_optional(&s.db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.ok_or(StatusCode::NOT_FOUND)?;
{
Err(_) => return StatusCode::INTERNAL_SERVER_ERROR.into_response(),
Ok(None) => return Redirect::to("/").into_response(),
Ok(Some(a)) => a,
};
let deploys = sqlx::query_as::<_, Deploy>(
"SELECT * FROM deploys WHERE app_id = ? ORDER BY created_at DESC LIMIT 15",
@ -270,7 +273,12 @@ pub async fn app_detail(
<tbody>{deploy_rows}</tbody>
</table>
<div id="log-panel" style="display:none;margin-top:16px">
<h2 id="log-title" style="margin-bottom:8px">Build Log</h2>
<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px">
<h2 id="log-title">Build Log</h2>
<a id="back-btn" href="/" style="display:none">
<button> Dashboard</button>
</a>
</div>
<pre id="log-out"></pre>
</div>
</div>
@ -334,6 +342,7 @@ pub async fn app_detail(
if (deploy.status === 'success' || deploy.status === 'failed') {{
out.textContent = deploy.log || '(no output captured)';
out.scrollTop = out.scrollHeight;
document.getElementById('back-btn').style.display = 'inline-block';
return;
}}
@ -348,6 +357,7 @@ pub async fn app_detail(
es.close();
title.textContent = 'Build Log ' + e.data;
title.style.color = e.data === 'success' ? '#4ade80' : '#f87171';
document.getElementById('back-btn').style.display = 'inline-block';
}});
es.onerror = () => {{
es.close();
@ -389,5 +399,5 @@ pub async fn app_detail(
latest_deploy_id = latest_deploy_id,
);
Ok(Html(page(&app.name, &body)))
Html(page(&app.name, &body)).into_response()
}