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