A few fixes to containers and apps, also added remove app
This commit is contained in:
@@ -1,7 +1,11 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import testAuth from "../auth";
|
import testAuth from "../auth";
|
||||||
import { StatusCodes } from "http-status-codes";
|
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<NextResponse> {
|
export async function GET(req: NextRequest): Promise<NextResponse> {
|
||||||
const auth = await testAuth(req);
|
const auth = await testAuth(req);
|
||||||
@@ -47,3 +51,28 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
|||||||
...res,
|
...res,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function DELETE(req: NextRequest): Promise<NextResponse> {
|
||||||
|
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",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,9 +25,6 @@ export async function GET(req: NextRequest): Promise<NextResponse> {
|
|||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
message: "ok",
|
message: "ok",
|
||||||
stats: {
|
stats,
|
||||||
cpu_stats: stats?.cpu_stats,
|
|
||||||
memory_stats: stats?.memory_stats,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ import { DashboardLayout } from "@/components/dashboard/DashboardLayout";
|
|||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
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 { Progress } from "@/components/ui/progress";
|
||||||
import DeployApplication from "@/components/dialogs/DeployApplication";
|
import DeployApplication from "@/components/dialogs/DeployApplication";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { ImageApp } from "@/lib/types";
|
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() {
|
export default function ApplicationsPage() {
|
||||||
const [applications, setApplications] = useState<ImageApp[]>([]);
|
const [applications, setApplications] = useState<ImageApp[]>([]);
|
||||||
@@ -25,6 +26,11 @@ export default function ApplicationsPage() {
|
|||||||
return () => clearInterval(iid);
|
return () => clearInterval(iid);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const remove = async (appId: string) => {
|
||||||
|
await removeApp(appId);
|
||||||
|
setApplications((prev) => prev.filter((app) => app.id !== appId));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardLayout
|
<DashboardLayout
|
||||||
title="Applications"
|
title="Applications"
|
||||||
@@ -134,9 +140,9 @@ export default function ApplicationsPage() {
|
|||||||
<Play className="w-3 h-3" />
|
<Play className="w-3 h-3" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button variant="ghost" size="icon-sm">
|
<RemoveDialog
|
||||||
<RotateCcw className="w-3 h-3" />
|
remove={() => app.app?.id && remove(app.app?.id)}
|
||||||
</Button>
|
/>
|
||||||
<Button variant="ghost" size="icon-sm">
|
<Button variant="ghost" size="icon-sm">
|
||||||
<MoreVertical className="w-3 h-3" />
|
<MoreVertical className="w-3 h-3" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -15,17 +15,7 @@ import {
|
|||||||
startContainer,
|
startContainer,
|
||||||
stopContainer,
|
stopContainer,
|
||||||
} from "@/core/docker";
|
} from "@/core/docker";
|
||||||
import {
|
import RemoveDialog from "@/components/dialogs/Remove";
|
||||||
AlertDialog,
|
|
||||||
AlertDialogTrigger,
|
|
||||||
AlertDialogContent,
|
|
||||||
AlertDialogHeader,
|
|
||||||
AlertDialogTitle,
|
|
||||||
AlertDialogDescription,
|
|
||||||
AlertDialogFooter,
|
|
||||||
AlertDialogCancel,
|
|
||||||
AlertDialogAction,
|
|
||||||
} from "@/components/ui/alert-dialog";
|
|
||||||
|
|
||||||
type ContainerSummary = {
|
type ContainerSummary = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -68,39 +58,6 @@ async function summarizeContainer(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function RemoveDialog({ container }: { container: ContainerSummary }) {
|
|
||||||
return (
|
|
||||||
<AlertDialog>
|
|
||||||
<AlertDialogTrigger asChild>
|
|
||||||
<Button variant="ghost" size="icon-sm">
|
|
||||||
<Trash2 className="w-3 h-3 text-destructive" />
|
|
||||||
</Button>
|
|
||||||
</AlertDialogTrigger>
|
|
||||||
|
|
||||||
<AlertDialogContent>
|
|
||||||
<AlertDialogHeader>
|
|
||||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
|
||||||
<AlertDialogDescription>
|
|
||||||
This action cannot be undone. This will permanently remove the
|
|
||||||
container.
|
|
||||||
</AlertDialogDescription>
|
|
||||||
</AlertDialogHeader>
|
|
||||||
|
|
||||||
<AlertDialogFooter>
|
|
||||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
||||||
|
|
||||||
<AlertDialogAction
|
|
||||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
||||||
onClick={() => removeContainer(container.id)}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</AlertDialogAction>
|
|
||||||
</AlertDialogFooter>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ContainersPage() {
|
export default function ContainersPage() {
|
||||||
const [containers, setContainers] = useState<ContainerSummary[]>([]);
|
const [containers, setContainers] = useState<ContainerSummary[]>([]);
|
||||||
|
|
||||||
@@ -121,6 +78,9 @@ export default function ContainersPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fetchNodes();
|
fetchNodes();
|
||||||
|
|
||||||
|
const iid = setInterval(fetchNodes, 3000);
|
||||||
|
return () => clearInterval(iid);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -221,7 +181,9 @@ export default function ContainersPage() {
|
|||||||
<Play className="w-3 h-3" />
|
<Play className="w-3 h-3" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<RemoveDialog container={container} />
|
<RemoveDialog
|
||||||
|
remove={() => removeContainer(container.id)}
|
||||||
|
/>
|
||||||
<Button variant="ghost" size="icon-sm">
|
<Button variant="ghost" size="icon-sm">
|
||||||
<MoreVertical className="w-3 h-3" />
|
<MoreVertical className="w-3 h-3" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -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<T>({ remove }: { remove: () => T }) {
|
||||||
|
return (
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<Button variant="ghost" size="icon-sm">
|
||||||
|
<Trash2 className="w-3 h-3 text-destructive" />
|
||||||
|
</Button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
This action cannot be undone. This will permanently remove the item.
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
|
|
||||||
|
<AlertDialogAction
|
||||||
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||||
|
onClick={remove}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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}` },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ export async function getContainerStats(
|
|||||||
headers: { Authorization: `ApiKey ${n.key}` },
|
headers: { Authorization: `ApiKey ${n.key}` },
|
||||||
params: { containerId },
|
params: { containerId },
|
||||||
})
|
})
|
||||||
).data
|
).data?.stats
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
).find((s) => s);
|
).find((s) => s);
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,7 +15,15 @@ export async function getDocker() {
|
|||||||
export async function getContainerStats(
|
export async function getContainerStats(
|
||||||
containerId: string
|
containerId: string
|
||||||
): Promise<SimpleStats | undefined> {
|
): Promise<SimpleStats | undefined> {
|
||||||
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 {
|
return {
|
||||||
cpu: CPUusage(stats),
|
cpu: CPUusage(stats),
|
||||||
|
|||||||
Reference in New Issue
Block a user