@@ -132,7 +251,7 @@ pub async fn index(State(s): State
) -> Result, StatusCode
Apps
- | Name | Repo | Branch | Status | Actions |
+ | Name | Repo | Branch | Container | Last Deploy | Actions |
{rows}
@@ -165,20 +284,40 @@ pub async fn index(State(s): State) -> Result, StatusCode
await fetch('/api/apps/' + id, {{method: 'DELETE'}});
window.location.reload();
}}
- // Auto-refresh status every 5 s.
+
+ function deployBadgeHtml(s) {{
+ const cls = {{success:'badge-success',failed:'badge-failed',building:'badge-building',queued:'badge-building'}}[s] || 'badge-unknown';
+ return `${{s}}`;
+ }}
+ function containerBadgeHtml(s) {{
+ const cls = {{running:'badge-success',exited:'badge-failed',restarting:'badge-building'}}[s] || 'badge-unknown';
+ return `${{s}}`;
+ }}
+
+ // Auto-refresh container + deploy statuses every 5 s.
setInterval(async () => {{
- const r = await fetch('/api/apps');
+ const r = await fetch('/api/status');
if (!r.ok) return;
- const apps = await r.json();
- // Only update the status badges to avoid disrupting interactions.
- apps.forEach(app => {{
- const row = document.querySelector(`tr[data-id="${{app.id}}"]`);
- if (row) row.querySelector('.badge').textContent = app.status ?? '–';
+ const statuses = await r.json();
+ Object.entries(statuses).forEach(([id, s]) => {{
+ const row = document.querySelector(`tr[data-id="${{id}}"]`);
+ if (!row) return;
+ const cb = row.querySelector('[data-container-badge]');
+ const db = row.querySelector('[data-deploy-badge]');
+ if (cb) cb.innerHTML = containerBadgeHtml(s.container);
+ if (db) db.innerHTML = deployBadgeHtml(s.deploy);
}});
}}, 5000);
"#,
- n = apps.len(),
- rows = rows,
+ n = apps.len(),
+ rows = rows,
+ load = stats.load_1m,
+ ram_used = stats.ram_used_mb,
+ ram_total = stats.ram_total_mb,
+ ram_pct = ram_pct,
+ disk_used = stats.disk_used_gb,
+ disk_total = stats.disk_total_gb,
+ disk_pct = stats.disk_pct,
);
Ok(Html(page("Dashboard", &body)))
@@ -401,3 +540,41 @@ pub async fn app_detail(
Html(page(&app.name, &body)).into_response()
}
+
+// ── Status API (container + deploy) ───────────────────────────────────────────
+
+pub async fn status_json(
+ State(s): State,
+) -> Result, StatusCode> {
+ let apps = sqlx::query_as::<_, App>("SELECT * FROM apps ORDER BY created_at DESC")
+ .fetch_all(&s.db)
+ .await
+ .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
+
+ let futs: Vec<_> = apps.iter().map(|a| {
+ let db = s.db.clone();
+ let id = a.id.clone();
+ async move {
+ let deploy = sqlx::query_scalar::<_, String>(
+ "SELECT status FROM deploys WHERE app_id = ? ORDER BY created_at DESC LIMIT 1",
+ )
+ .bind(&id)
+ .fetch_optional(&db)
+ .await
+ .unwrap_or(None)
+ .unwrap_or_else(|| "–".to_string());
+
+ let container = get_container_status(&id).await;
+ (id, deploy, container)
+ }
+ }).collect();
+
+ let results = join_all(futs).await;
+ let map: serde_json::Map = results.into_iter()
+ .map(|(id, deploy, container)| {
+ (id, serde_json::json!({ "deploy": deploy, "container": container }))
+ })
+ .collect();
+
+ Ok(Json(serde_json::Value::Object(map)))
+}