feat(frontend): start doing container page

This commit is contained in:
2025-01-14 17:11:28 -05:00
parent f4161b4f47
commit 9fa7f57a14
5 changed files with 294 additions and 44 deletions

View File

@ -6,6 +6,9 @@ import { createBrowserRouter, RouterProvider } from "react-router";
import Containers, {
Loader as ContainersLoader,
} from "./pages/containers/index.tsx";
import ContainerPage, {
Loader as ContainerPageLoader,
} from "./pages/containers/[name].tsx";
const router = createBrowserRouter([
{
@ -18,6 +21,11 @@ const router = createBrowserRouter([
element: <Containers />,
loader: ContainersLoader,
},
{
path: "/containers/:name",
element: <ContainerPage />,
loader: ContainerPageLoader,
},
],
},
]);

View File

@ -0,0 +1,88 @@
import { useLoaderData, useRevalidator } from "react-router";
import { Container } from "../../types";
import { Button, ButtonGroup, Paper, Typography } from "@mui/material";
import { useEffect, useState } from "react";
import { ContainerStats } from "dockerode";
interface Params {
name: string;
}
export async function Loader({ params }: { params: Params }) {
const container = await fetch(`/api/containers/${params.name}`);
const data = await container.json();
return data;
}
export default function ContainerPage() {
const container = useLoaderData() as Container & { statusCode: number };
if (container.statusCode === 404) {
return "Container not found";
}
const [stats, setStats] = useState<ContainerStats>();
useEffect(() => {
const statsSource = new EventSource(
`/api/containers/${container.name}/stats`,
);
statsSource.onmessage = ({ data }) => {
const parsed = JSON.parse(data);
setStats(parsed);
};
return () => {
statsSource.close();
};
}, []);
console.log("stats", stats);
const revalidator = useRevalidator();
const [isPowerStateLocked, setPowerStateLocked] = useState<boolean>();
return (
<Paper square sx={{ padding: 1 }}>
<Paper sx={{ display: "flex" }} variant="outlined">
<Typography variant="h6" sx={{ flexGrow: 1 }}>
Managing: {container.name}
</Typography>
<ButtonGroup>
<Button
loading={isPowerStateLocked}
onClick={async () => {
await switchPowerState(
container.status === "running" ? "stop" : "start",
);
}}
>
Power {container.status === "running" ? "off" : "on"}
</Button>
<Button
onClick={async () => await switchPowerState("restart")}
loading={isPowerStateLocked}
disabled={container.status === "exited"}
>
Restart
</Button>
</ButtonGroup>
</Paper>
CPU Usage: {stats?.cpu_stats.cpu_usage.total_usage}
</Paper>
);
async function switchPowerState(state: string) {
setPowerStateLocked(true);
const res = await fetch(`/api/containers/${container.name}/${state}`, {
method: "PUT",
});
if (res.ok) {
setPowerStateLocked(false);
revalidator.revalidate();
}
}
}

View File

@ -1,4 +1,4 @@
import { useLoaderData, useNavigate } from "react-router";
import { useLoaderData } from "react-router";
import { Container } from "../../types";
import {
Button,
@ -18,7 +18,6 @@ export async function Loader() {
export default function Containers() {
const containers = useLoaderData() as Container[];
const navigate = useNavigate();
return (
<>
@ -39,16 +38,7 @@ export default function Containers() {
Status: {container.status}
</CardContent>
<CardActions>
<Button
onClick={async () =>
await switchPower(
container.id,
container.status === "running" ? "stop" : "start",
)
}
>
Power {container.status === "running" ? "off" : "on"}
</Button>
<Button href={`/containers/${container.name}`}>Manage</Button>
<Button
href={`//${container.name}.${document.location.host}`}
target="_blank"
@ -62,9 +52,4 @@ export default function Containers() {
</Grid>
</>
);
async function switchPower(id: string, state: string) {
await fetch(`/api/containers/${id}/${state}`, { method: "PUT" });
navigate(".", { replace: true });
}
}