forked from github-starred/komodo
add some basic dashboard components, add server page
This commit is contained in:
@@ -8,7 +8,7 @@ export const Layout = () => {
|
||||
const { data, isError } = useUser();
|
||||
const navigate = useNavigate();
|
||||
const path = useLocation().pathname;
|
||||
// if (isError && !path.includes("login")) navigate("/login");
|
||||
// if (isError && (!path.includes("login") || !path.includes("signup"))) navigate("/login");
|
||||
|
||||
console.log(data);
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { Button } from "@ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
} from "@ui/card";
|
||||
import { WithLoading } from "@components/util";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import { PieChart } from "react-minimal-pie-chart";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useRead } from "@hooks";
|
||||
|
||||
export const DeploymentsChart = () => {
|
||||
const { data, isLoading, isError } = useRead({
|
||||
type: "ListDeployments",
|
||||
params: {},
|
||||
});
|
||||
|
||||
const running = data?.filter((d) => d.state === "running").length;
|
||||
const stopped = data?.filter((d) => d.state === "exited").length;
|
||||
const not_deployed = data?.filter((d) => d.state === "not_deployed").length;
|
||||
|
||||
return (
|
||||
<Card className="pb-4 w-full">
|
||||
<CardHeader className="flex-row justify-between items-center">
|
||||
<CardTitle>Deployments</CardTitle>
|
||||
<Link to="/deployments">
|
||||
<Button variant="outline" size="sm">
|
||||
<CardDescription>{data?.length} Total</CardDescription>
|
||||
<ChevronRight className="w-3 h-3" />
|
||||
</Button>
|
||||
</Link>
|
||||
</CardHeader>
|
||||
<CardContent className="flex gap-4 items-center w-full">
|
||||
<WithLoading {...{ isLoading, isError }}>
|
||||
<div className="flex flex-col gap-2 text-muted-foreground w-full">
|
||||
<CardDescription>
|
||||
<span className="text-green-500 font-bold">{running} </span>
|
||||
Running
|
||||
</CardDescription>
|
||||
<CardDescription>
|
||||
<span className="text-red-500 font-bold">{stopped} </span>
|
||||
Stopped
|
||||
</CardDescription>
|
||||
<CardDescription>
|
||||
<span className="text-blue-500 font-bold">{not_deployed} </span>
|
||||
Not Deployed
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className="flex justify-end items-center w-full">
|
||||
<PieChart
|
||||
className="w-20 h-20"
|
||||
data={[
|
||||
{
|
||||
color: "#22C55E",
|
||||
value: running ?? 0,
|
||||
title: "deployed",
|
||||
key: "deployed",
|
||||
},
|
||||
{
|
||||
color: "#EF0044",
|
||||
value: stopped ?? 0,
|
||||
title: "stopped",
|
||||
key: "stopped",
|
||||
},
|
||||
{
|
||||
color: "#3B82F6",
|
||||
value: not_deployed ?? 0,
|
||||
title: "not-deployed",
|
||||
key: "not-deployed",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</WithLoading>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
84
frontend/src/pages/dashboard/components/servers-chart.tsx
Normal file
84
frontend/src/pages/dashboard/components/servers-chart.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Button } from "@ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
} from "@ui/card";
|
||||
import { WithLoading } from "@components/util";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import { PieChart } from "react-minimal-pie-chart";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useRead } from "@hooks";
|
||||
import { ServerStatus } from "@monitor/client/dist/types";
|
||||
|
||||
export const ServersChart = () => {
|
||||
const { data, isLoading, isError } = useRead({
|
||||
type: "ListServers",
|
||||
params: {},
|
||||
});
|
||||
|
||||
const running = data?.filter((d) => d.status === ServerStatus.Ok).length;
|
||||
const stopped = data?.filter((d) => d.status === ServerStatus.NotOk).length;
|
||||
const not_deployed = data?.filter(
|
||||
(d) => d.status === ServerStatus.Disabled
|
||||
).length;
|
||||
|
||||
return (
|
||||
<Card className="w-full pb-4">
|
||||
<CardHeader className="flex-row justify-between items-center">
|
||||
<CardTitle>Servers</CardTitle>
|
||||
<Link to="/servers">
|
||||
<Button variant="outline" size="sm">
|
||||
<CardDescription>{data?.length} Total</CardDescription>
|
||||
<ChevronRight className="w-3 h-3" />
|
||||
</Button>
|
||||
</Link>
|
||||
</CardHeader>
|
||||
<CardContent className="flex gap-4 items-center w-full">
|
||||
<WithLoading {...{ isLoading, isError }}>
|
||||
<div className="flex flex-col gap-2 text-muted-foreground w-full">
|
||||
<CardDescription>
|
||||
<span className="text-green-500 font-bold">{running} </span>
|
||||
Healthy
|
||||
</CardDescription>
|
||||
<CardDescription>
|
||||
<span className="text-red-500 font-bold">{stopped} </span>
|
||||
Unhealthy
|
||||
</CardDescription>
|
||||
<CardDescription>
|
||||
<span className="text-blue-500 font-bold">{not_deployed} </span>
|
||||
Disabled
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className="flex justify-end items-center w-full">
|
||||
<PieChart
|
||||
className="w-20 h-20"
|
||||
data={[
|
||||
{
|
||||
color: "#22C55E",
|
||||
value: running ?? 0,
|
||||
title: "deployed",
|
||||
key: "deployed",
|
||||
},
|
||||
{
|
||||
color: "#EF0044",
|
||||
value: stopped ?? 0,
|
||||
title: "stopped",
|
||||
key: "stopped",
|
||||
},
|
||||
{
|
||||
color: "#3B82F6",
|
||||
value: not_deployed ?? 0,
|
||||
title: "not-deployed",
|
||||
key: "not-deployed",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</WithLoading>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
95
frontend/src/pages/dashboard/index.tsx
Normal file
95
frontend/src/pages/dashboard/index.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import { useRead, useUser } from "@hooks";
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
|
||||
import { version_to_string } from "@util/helpers";
|
||||
import { ServersChart } from "./components/servers-chart";
|
||||
import { DeploymentsChart } from "./components/deployments-chart";
|
||||
import { Input } from "@ui/input";
|
||||
import { Button } from "@ui/button";
|
||||
import { PlusCircle } from "lucide-react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
const DeploymentsList = () => {
|
||||
const deployments = useRead({ type: "ListDeployments", params: {} }).data;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 w-full border-r pr-4">
|
||||
<h2 className="text-lg">Deployments</h2>
|
||||
{deployments?.map((deployment) => (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{deployment.name}</CardTitle>
|
||||
<CardDescription>{deployment.version}</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ServersList = () => {
|
||||
const servers = useRead({ type: "ListServers", params: {} }).data;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 w-full border-r pr-4">
|
||||
<h2 className="text-lg">Servers</h2>
|
||||
{servers?.map((server) => (
|
||||
<Link to={`/servers/${server.id}`} key={server.id}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{server.name}</CardTitle>
|
||||
<CardDescription>{server.status}</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const BuildsList = () => {
|
||||
const builds = useRead({ type: "ListBuilds", params: {} }).data;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<h2 className="text-lg">Builds</h2>
|
||||
{builds?.map((build) => (
|
||||
<Card>
|
||||
<CardHeader key={build.id}>
|
||||
<CardTitle>{build.name}</CardTitle>
|
||||
<CardDescription>
|
||||
{version_to_string(build.version)}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Dashboard = () => {
|
||||
const user = useUser().data;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-xl"> Hello, {user?.username}.</h1>
|
||||
<div className="flex gap-4">
|
||||
<Input className="w-[300px]" placeholder="Search" />
|
||||
<Button className="w-[120px]" variant="outline" intent="success">
|
||||
<PlusCircle className="w-4 h-4 mr-2 text-green-500" />
|
||||
Add New
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<DeploymentsChart />
|
||||
<ServersChart />
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<DeploymentsList />
|
||||
<ServersList />
|
||||
<BuildsList />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
113
frontend/src/pages/server/index.tsx
Normal file
113
frontend/src/pages/server/index.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import { useRead } from "@hooks";
|
||||
import { Resource } from "@layouts/resource";
|
||||
import { ServerStatus } from "@monitor/client/dist/types";
|
||||
import { CardDescription } from "@ui/card";
|
||||
import { cn } from "@util/helpers";
|
||||
import { Circle, Cpu, Database, MemoryStick } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
export const ServerName = ({ serverId }: { serverId: string | undefined }) => {
|
||||
const servers = useRead({ type: "ListServers", params: {} }).data;
|
||||
const server = servers?.find((s) => s.id === serverId);
|
||||
return <>{server?.name ?? "..."}</>;
|
||||
};
|
||||
|
||||
export const ServerInfo = ({ serverId }: { serverId: string | undefined }) => {
|
||||
const servers = useRead({ type: "ListServers", params: {} }).data;
|
||||
const server = servers?.find((s) => s.id === serverId);
|
||||
return (
|
||||
<div className="flex items-center gap-4">
|
||||
{serverId && <ServerStats serverId={serverId} />}
|
||||
<CardDescription>|</CardDescription>
|
||||
<div className="flex items-center gap-2">
|
||||
<CardDescription> Status: {server?.status}</CardDescription>
|
||||
<ServerStatusIcon serverId={serverId} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ServerStats = ({ serverId }: { serverId: string }) => {
|
||||
const { data, refetch } = useRead({
|
||||
type: "GetBasicSystemStats",
|
||||
params: { server_id: serverId },
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const handle = setInterval(() => refetch(), 30000);
|
||||
return () => {
|
||||
clearInterval(handle);
|
||||
};
|
||||
}, [refetch]);
|
||||
|
||||
return (
|
||||
<div className="flex gap-4">
|
||||
<div className="flex gap-2 items-center">
|
||||
<Cpu className="w-4 h-4" />
|
||||
<CardDescription>{data?.cpu_perc.toFixed(2)}%</CardDescription>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<MemoryStick className="w-4 h-4" />
|
||||
<CardDescription>{data?.mem_total_gb.toFixed(2)} GB</CardDescription>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Database className="w-4 h-4" />
|
||||
<CardDescription>{data?.disk_total_gb.toFixed(2)} GB</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ServerStatusIcon = ({
|
||||
serverId,
|
||||
sm,
|
||||
}: {
|
||||
serverId: string | undefined;
|
||||
sm?: boolean;
|
||||
}) => {
|
||||
const servers = useRead({ type: "ListServers", params: {} }).data;
|
||||
const server = servers?.find((s) => s.id === serverId);
|
||||
return (
|
||||
<Circle
|
||||
className={cn(
|
||||
"w-4 h-4 stroke-none",
|
||||
server?.status === ServerStatus.Ok && "fill-green-500",
|
||||
server?.status === ServerStatus.NotOk && "fill-red-500",
|
||||
server?.status === ServerStatus.Disabled && "fill-blue-500",
|
||||
sm && "w-3 h-3"
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Server = () => {
|
||||
const { serverId } = useParams();
|
||||
// const { data } = useRead({ type: "GetServer", params: { id: serverId! } });
|
||||
|
||||
return (
|
||||
<Resource
|
||||
title={<ServerName serverId={serverId} />}
|
||||
info={<ServerInfo serverId={serverId} />}
|
||||
actions=""
|
||||
tabs={[
|
||||
{
|
||||
title: "Config",
|
||||
component: <>config</>,
|
||||
},
|
||||
{
|
||||
title: "Deployments",
|
||||
component: <>server deployments</>,
|
||||
},
|
||||
{
|
||||
title: "Stats",
|
||||
component: "server stats",
|
||||
},
|
||||
{
|
||||
title: "Updates",
|
||||
component: <>updates</>,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -6,13 +6,15 @@ import { RouterProvider, createBrowserRouter } from "react-router-dom";
|
||||
import { Layout } from "@layouts/layout";
|
||||
import { Login } from "@pages/auth/login";
|
||||
import { Signup } from "@pages/auth/signup";
|
||||
import { Dashboard } from "@pages/dashboard";
|
||||
import { Server } from "@pages/server";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <Layout />,
|
||||
children: [
|
||||
{ path: "", element: <>hello</> },
|
||||
{ path: "", element: <Dashboard /> },
|
||||
{ path: "login", element: <Login /> },
|
||||
{ path: "signup", element: <Signup /> },
|
||||
|
||||
@@ -30,13 +32,13 @@ const router = createBrowserRouter([
|
||||
// { path: ":buildId", element: <Build /> },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// path: "servers",
|
||||
// children: [
|
||||
// { path: "", element: <Servers /> },
|
||||
// { path: ":serverId", element: <Server /> },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
path: "servers",
|
||||
children: [
|
||||
{ path: "", element: "servers" },
|
||||
{ path: ":serverId", element: <Server /> },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user