Compare commits
1 Commits
v0.1.73-ma
...
v0.1.74-ma
| Author | SHA1 | Date | |
|---|---|---|---|
| 652e451afe |
@@ -259,16 +259,18 @@ pub async fn stop_project_container(
|
||||
.get(&project_id)
|
||||
.ok_or_else(|| format!("Project {} not found", project_id))?;
|
||||
|
||||
if let Some(ref container_id) = project.container_id {
|
||||
state.projects_store.update_status(&project_id, ProjectStatus::Stopping)?;
|
||||
state.projects_store.update_status(&project_id, ProjectStatus::Stopping)?;
|
||||
|
||||
if let Some(ref container_id) = project.container_id {
|
||||
// Close exec sessions for this project
|
||||
state.exec_manager.close_sessions_for_container(container_id).await;
|
||||
|
||||
docker::stop_container(container_id).await?;
|
||||
state.projects_store.update_status(&project_id, ProjectStatus::Stopped)?;
|
||||
if let Err(e) = docker::stop_container(container_id).await {
|
||||
log::warn!("Docker stop failed for container {} (project {}): {} — resetting to Stopped anyway", container_id, project_id, e);
|
||||
}
|
||||
}
|
||||
|
||||
state.projects_store.update_status(&project_id, ProjectStatus::Stopped)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -70,17 +70,38 @@ impl ProjectsStore {
|
||||
(Vec::new(), false)
|
||||
};
|
||||
|
||||
// Reconcile stale transient statuses: on a cold app start no Docker
|
||||
// operations can be in flight, so Starting/Stopping are always stale.
|
||||
let mut projects = projects;
|
||||
let mut needs_save = needs_save;
|
||||
for p in projects.iter_mut() {
|
||||
match p.status {
|
||||
crate::models::ProjectStatus::Starting | crate::models::ProjectStatus::Stopping => {
|
||||
log::warn!(
|
||||
"Reconciling stale '{}' status for project '{}' ({}) → Stopped",
|
||||
serde_json::to_string(&p.status).unwrap_or_default().trim_matches('"'),
|
||||
p.name,
|
||||
p.id
|
||||
);
|
||||
p.status = crate::models::ProjectStatus::Stopped;
|
||||
p.updated_at = chrono::Utc::now().to_rfc3339();
|
||||
needs_save = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let store = Self {
|
||||
projects: Mutex::new(projects),
|
||||
file_path,
|
||||
};
|
||||
|
||||
// Persist migrated format back to disk
|
||||
// Persist migrated/reconciled format back to disk
|
||||
if needs_save {
|
||||
log::info!("Migrated projects.json from single-path to multi-path format");
|
||||
log::info!("Saving reconciled/migrated projects.json to disk");
|
||||
let projects = store.lock();
|
||||
if let Err(e) = store.save(&projects) {
|
||||
log::error!("Failed to save migrated projects: {}", e);
|
||||
log::error!("Failed to save projects: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -315,9 +315,12 @@ export default function ProjectCard({ project }: Props) {
|
||||
<ActionButton onClick={handleOpenTerminal} disabled={loading} label="Terminal" accent />
|
||||
</>
|
||||
) : (
|
||||
<span className="text-xs text-[var(--text-secondary)]">
|
||||
{project.status}...
|
||||
</span>
|
||||
<>
|
||||
<span className="text-xs text-[var(--text-secondary)]">
|
||||
{project.status}...
|
||||
</span>
|
||||
<ActionButton onClick={handleStop} disabled={loading} label="Force Stop" danger />
|
||||
</>
|
||||
)}
|
||||
<ActionButton
|
||||
onClick={(e) => { e?.stopPropagation?.(); setShowConfig(!showConfig); }}
|
||||
|
||||
Reference in New Issue
Block a user