diff --git a/src/app/api/applications/route.ts b/src/app/api/applications/route.ts index c7e0e6a..e014d16 100644 --- a/src/app/api/applications/route.ts +++ b/src/app/api/applications/route.ts @@ -1,7 +1,11 @@ import { NextRequest, NextResponse } from "next/server"; import testAuth from "../auth"; import { StatusCodes } from "http-status-codes"; -import { deployNewApp, getApplications } from "@/core/server/application"; +import { + deployNewApp, + getApplications, + removeApp, +} from "@/core/server/application"; export async function GET(req: NextRequest): Promise { const auth = await testAuth(req); @@ -47,3 +51,28 @@ export async function POST(req: NextRequest): Promise { ...res, }); } + +export async function DELETE(req: NextRequest): Promise { + const auth = await testAuth(req); + const appId = req.nextUrl.searchParams.get("appId"); + + if (auth) { + return NextResponse.json( + { message: auth }, + { status: StatusCodes.UNAUTHORIZED } + ); + } + + if (!appId) { + return NextResponse.json( + { message: "Missing param appId" }, + { status: StatusCodes.BAD_REQUEST } + ); + } + + await removeApp(appId); + + return NextResponse.json({ + message: "ok", + }); +} diff --git a/src/app/api/container/stats/route.ts b/src/app/api/container/stats/route.ts index e30ce89..f775bf8 100644 --- a/src/app/api/container/stats/route.ts +++ b/src/app/api/container/stats/route.ts @@ -25,9 +25,6 @@ export async function GET(req: NextRequest): Promise { return NextResponse.json({ message: "ok", - stats: { - cpu_stats: stats?.cpu_stats, - memory_stats: stats?.memory_stats, - }, + stats, }); } diff --git a/src/app/applications/page.tsx b/src/app/applications/page.tsx index 83489e1..cfa56f6 100644 --- a/src/app/applications/page.tsx +++ b/src/app/applications/page.tsx @@ -4,12 +4,13 @@ import { DashboardLayout } from "@/components/dashboard/DashboardLayout"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import { Play, Square, RotateCcw, MoreVertical, Plus } from "lucide-react"; +import { Play, Square, MoreVertical, Plus } from "lucide-react"; import { Progress } from "@/components/ui/progress"; import DeployApplication from "@/components/dialogs/DeployApplication"; import { useEffect, useState } from "react"; import { ImageApp } from "@/lib/types"; -import { getApplications } from "@/core/application"; +import { getApplications, removeApp } from "@/core/application"; +import RemoveDialog from "@/components/dialogs/Remove"; export default function ApplicationsPage() { const [applications, setApplications] = useState([]); @@ -25,6 +26,11 @@ export default function ApplicationsPage() { return () => clearInterval(iid); }, []); + const remove = async (appId: string) => { + await removeApp(appId); + setApplications((prev) => prev.filter((app) => app.id !== appId)); + }; + return ( )} - + app.app?.id && remove(app.app?.id)} + /> diff --git a/src/app/containers/page.tsx b/src/app/containers/page.tsx index a548500..62590e4 100644 --- a/src/app/containers/page.tsx +++ b/src/app/containers/page.tsx @@ -15,17 +15,7 @@ import { startContainer, stopContainer, } from "@/core/docker"; -import { - AlertDialog, - AlertDialogTrigger, - AlertDialogContent, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogCancel, - AlertDialogAction, -} from "@/components/ui/alert-dialog"; +import RemoveDialog from "@/components/dialogs/Remove"; type ContainerSummary = { id: string; @@ -68,39 +58,6 @@ async function summarizeContainer( }; } -function RemoveDialog({ container }: { container: ContainerSummary }) { - return ( - - - - - - - - Are you absolutely sure? - - This action cannot be undone. This will permanently remove the - container. - - - - - Cancel - - removeContainer(container.id)} - > - Delete - - - - - ); -} - export default function ContainersPage() { const [containers, setContainers] = useState([]); @@ -121,6 +78,9 @@ export default function ContainersPage() { }; fetchNodes(); + + const iid = setInterval(fetchNodes, 3000); + return () => clearInterval(iid); }, []); return ( @@ -221,7 +181,9 @@ export default function ContainersPage() { )} - + removeContainer(container.id)} + /> diff --git a/src/components/dialogs/Remove.tsx b/src/components/dialogs/Remove.tsx new file mode 100644 index 0000000..6a66ac9 --- /dev/null +++ b/src/components/dialogs/Remove.tsx @@ -0,0 +1,45 @@ +import { + AlertDialog, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogCancel, + AlertDialogAction, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { Trash2 } from "lucide-react"; + +export default function RemoveDialog({ remove }: { remove: () => T }) { + return ( + + + + + + + + Are you absolutely sure? + + This action cannot be undone. This will permanently remove the item. + + + + + Cancel + + + Delete + + + + + ); +} diff --git a/src/core/application.ts b/src/core/application.ts index 4355640..42e9a0f 100644 --- a/src/core/application.ts +++ b/src/core/application.ts @@ -72,3 +72,14 @@ export async function setEnv(appId: string, variables: EnvVariable[]) { ); }); } + +export async function removeApp(appId: string) { + const nodes = await getNodes(); + + nodes.forEach(async (n) => { + await axios.delete(`http://${n.ip}:3000/api/applications`, { + params: { appId }, + headers: { Authorization: `ApiKey ${n.key}` }, + }); + }); +} diff --git a/src/core/docker.ts b/src/core/docker.ts index 4bdd03e..00b8d67 100644 --- a/src/core/docker.ts +++ b/src/core/docker.ts @@ -35,7 +35,7 @@ export async function getContainerStats( headers: { Authorization: `ApiKey ${n.key}` }, params: { containerId }, }) - ).data + ).data?.stats ) ) ).find((s) => s); diff --git a/src/core/server/application.ts b/src/core/server/application.ts index 4954897..14a5e0d 100644 --- a/src/core/server/application.ts +++ b/src/core/server/application.ts @@ -164,3 +164,11 @@ export async function setEnv(appId: string, variables: EnvVariable[]) { } }); } + +export async function removeApp(appId: string) { + const imageTag = `dockship/${appId}:latest`; + const image = docker.getImage(imageTag); + await image.remove(); + const appPath = path.join(DATA_DIR, appId); + fs.rmSync(appPath, { recursive: true }); +} diff --git a/src/core/server/docker.ts b/src/core/server/docker.ts index f6342ec..e8e203a 100644 --- a/src/core/server/docker.ts +++ b/src/core/server/docker.ts @@ -15,7 +15,15 @@ export async function getDocker() { export async function getContainerStats( containerId: string ): Promise { - const stats = await docker.getContainer(containerId).stats({ stream: false }); + const container = docker.getContainer(containerId); + + const data = await container.inspect(); + + if (!data.State.Running) { + return; + } + + const stats = await container.stats({ stream: false }); return { cpu: CPUusage(stats),