A focused Docker Compose management web application.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: delete project

+110 -67
+15 -1
packages/node/src/api/project.rs
··· 16 16 17 17 /// Returns the router for compose related endpoints. 18 18 pub fn router() -> Router { 19 - return Router::new().get(get_project_endpoint).patch(patch_compose); 19 + return Router::new() 20 + .get(get_project_endpoint) 21 + .patch(patch_compose) 22 + .delete(delete_project); 20 23 } 21 24 22 25 /// Retrieves the compose file for a given project. ··· 64 67 engine.patch_project(&project, &payload.0).await?; 65 68 return get_project(engine, &payload.to.take().unwrap_or(project.0)).await; 66 69 } 70 + 71 + /// Deletes a project and all of its resources. 72 + #[endpoint] 73 + pub async fn delete_project(project: PathParam<String>, depot: &mut Depot) -> LuminaryResponse<()> { 74 + let engine = obtain!(depot, LuminaryEngine); 75 + 76 + engine.wait_until_idle(&project, None).await?; 77 + engine.delete_project(&project).await?; 78 + 79 + return Ok(().into()); 80 + }
+19
packages/node/src/core/project.rs
··· 43 43 return Ok(()); 44 44 } 45 45 46 + #[wrap_err("Failed to delete project")] 47 + pub async fn delete_project(&self, project: &str) -> Result<()> { 48 + let (project_path, _) = self.get_path(project); 49 + 50 + self.stop(project, None).await?; 51 + 52 + if !project_path.exists() { 53 + eyre::bail!("Project '{}' does not exist", project); 54 + } 55 + 56 + fs::remove_dir_all(project_path) 57 + .await 58 + .wrap_err("Failed to delete project directory")?; 59 + 60 + self.refresh().await?; 61 + 62 + return Ok(()); 63 + } 64 + 46 65 /// Updates the given project by applying the provided patch 47 66 pub async fn patch_project(&self, project: &str, patch: &LuminaryProjectPatch) -> Result<()> { 48 67 // Validate request
+76 -66
packages/panel/src/routes/(authenticated)/projects/[project]/ProjectStatus.svelte
··· 1 1 <script lang="ts"> 2 + import { faArrowsRotate, faBan, faPlay, faRocket, faStop } from "@fortawesome/free-solid-svg-icons"; 2 3 import PromiseButton from "$lib/component/PromiseButton.svelte"; 3 - import { faArrowsRotate, faPlay, faRocket, faStop } from "@fortawesome/free-solid-svg-icons"; 4 + import StatusIcon from "$lib/component/StatusIcon.svelte"; 5 + import Tooltip from "$lib/component/Tooltip.svelte"; 6 + import { goto } from "$app/navigation"; 4 7 import Fa from "svelte-fa"; 5 8 import { api } from "$lib"; 6 - import StatusIcon from "$lib/component/StatusIcon.svelte"; 7 - import Tooltip from "$lib/component/Tooltip.svelte"; 8 9 9 10 let { project }: { project: api.LuminaryProject } = $props(); 10 11 ··· 15 16 if (services.some((service) => service.action !== action)) return "idle"; 16 17 return action; 17 18 }); 19 + 20 + async function deleteProject() { 21 + await api.client.DELETE("/api/project/{project}", { params: { path: { project: project.name } } }); 22 + await goto("/projects"); 23 + } 18 24 </script> 19 25 20 26 <h2>Actions</h2> ··· 22 28 <div>You must fix the <a href="#compose">compose file</a> to trigger actions.</div> 23 29 {:else} 24 30 <div class="flexr gap-5 wrap"> 25 - <div> 26 - <PromiseButton 27 - style="outline" 28 - disabled={project.busy} 29 - loading={allAction === "starting"} 30 - onclick={() => 31 - api.client.POST("/api/project/{project}/start", { params: { path: { project: project.name } } })} 32 - > 33 - {#snippet children(loading)} 34 - <div class="flexr center gap-10"> 35 - {#if !loading}<Fa icon={faPlay} />{/if} 36 - Start All 37 - </div> 38 - {/snippet} 39 - </PromiseButton> 40 - </div> 41 - <div> 42 - <PromiseButton 43 - style="outline" 44 - disabled={project.busy} 45 - loading={allAction === "restarting"} 46 - onclick={() => 47 - api.client.POST("/api/project/{project}/restart", { params: { path: { project: project.name } } })} 48 - > 49 - {#snippet children(loading)} 50 - <div class="flexr center gap-10"> 51 - {#if !loading}<Fa icon={faArrowsRotate} />{/if} 52 - Restart All 53 - </div> 54 - {/snippet} 55 - </PromiseButton> 56 - </div> 57 - <div> 58 - <PromiseButton 59 - style="outline" 60 - disabled={project.busy} 61 - loading={allAction === "stopping"} 62 - onclick={() => 63 - api.client.POST("/api/project/{project}/stop", { params: { path: { project: project.name } } })} 64 - > 65 - {#snippet children(loading)} 66 - <div class="flexr center gap-10"> 67 - {#if !loading}<Fa icon={faStop} />{/if} 68 - Stop All 69 - </div> 70 - {/snippet} 71 - </PromiseButton> 72 - </div> 73 - <div> 74 - <PromiseButton 75 - style="outline" 76 - disabled={project.busy} 77 - onclick={() => 78 - api.client.POST("/api/project/{project}/recreate", { params: { path: { project: project.name } } })} 79 - > 80 - {#snippet children(loading)} 81 - <div class="flexr center gap-10"> 82 - {#if !loading}<Fa icon={faRocket} />{/if} 83 - Recreate All 84 - </div> 85 - {/snippet} 86 - </PromiseButton> 87 - </div> 31 + <PromiseButton 32 + fit 33 + style="outline" 34 + disabled={project.busy} 35 + loading={allAction === "starting"} 36 + onclick={() => 37 + api.client.POST("/api/project/{project}/start", { params: { path: { project: project.name } } })} 38 + > 39 + {#snippet children(loading)} 40 + <div class="flexr center gap-10"> 41 + {#if !loading}<Fa icon={faPlay} />{/if} 42 + Start All 43 + </div> 44 + {/snippet} 45 + </PromiseButton> 46 + <PromiseButton 47 + fit 48 + style="outline" 49 + disabled={project.busy} 50 + loading={allAction === "restarting"} 51 + onclick={() => 52 + api.client.POST("/api/project/{project}/restart", { params: { path: { project: project.name } } })} 53 + > 54 + {#snippet children(loading)} 55 + <div class="flexr center gap-10"> 56 + {#if !loading}<Fa icon={faArrowsRotate} />{/if} 57 + Restart All 58 + </div> 59 + {/snippet} 60 + </PromiseButton> 61 + <PromiseButton 62 + fit 63 + style="outline" 64 + disabled={project.busy} 65 + loading={allAction === "stopping"} 66 + onclick={() => 67 + api.client.POST("/api/project/{project}/stop", { params: { path: { project: project.name } } })} 68 + > 69 + {#snippet children(loading)} 70 + <div class="flexr center gap-10"> 71 + {#if !loading}<Fa icon={faStop} />{/if} 72 + Stop All 73 + </div> 74 + {/snippet} 75 + </PromiseButton> 76 + <PromiseButton 77 + fit 78 + style="outline" 79 + disabled={project.busy} 80 + onclick={() => 81 + api.client.POST("/api/project/{project}/recreate", { params: { path: { project: project.name } } })} 82 + > 83 + {#snippet children(loading)} 84 + <div class="flexr center gap-10"> 85 + {#if !loading}<Fa icon={faRocket} />{/if} 86 + Recreate All 87 + </div> 88 + {/snippet} 89 + </PromiseButton> 90 + <PromiseButton fit style="outline" disabled={project.busy} onclick={deleteProject}> 91 + {#snippet children(loading)} 92 + <div class="flexr center gap-10"> 93 + {#if !loading}<Fa icon={faBan} />{/if} 94 + Delete Project 95 + </div> 96 + {/snippet} 97 + </PromiseButton> 88 98 </div> 89 99 {/if} 90 100