mirror of
https://github.com/moghtech/komodo.git
synced 2026-04-29 12:43:26 -05:00
improve layout standardisation, dashboard
This commit is contained in:
@@ -73,7 +73,7 @@ export const ResourceUpdates = ({ type, id }: ResourceTarget) => {
|
||||
</Link>
|
||||
}
|
||||
>
|
||||
<div className="grid md:grid-cols-3 mt-2 gap-4">
|
||||
<div className="grid md:grid-cols-3 gap-4">
|
||||
{isLoading && <UpdatePlaceHolder />}
|
||||
{data?.updates.slice(0, 3).map((update) => (
|
||||
<UpdateCard update={update} key={update.id} />
|
||||
|
||||
@@ -4,19 +4,19 @@ interface PageProps {
|
||||
title: ReactNode;
|
||||
subtitle: ReactNode;
|
||||
actions: ReactNode;
|
||||
content: ReactNode;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const Page = ({ title, subtitle, actions, content }: PageProps) => (
|
||||
export const Page = ({ title, subtitle, actions, children }: PageProps) => (
|
||||
<div className="flex flex-col gap-12">
|
||||
<div className="flex flex-col gap-6 lg:flex-row lg:gap-0 justify-between">
|
||||
<div className="flex flex-col">
|
||||
{title}
|
||||
<h1 className="text-4xl">{title}</h1>
|
||||
{subtitle}
|
||||
</div>
|
||||
{actions}
|
||||
</div>
|
||||
{content}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -28,7 +28,7 @@ interface SectionProps {
|
||||
}
|
||||
|
||||
export const Section = ({ title, icon, actions, children }: SectionProps) => (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex justify-between">
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
{icon}
|
||||
|
||||
@@ -13,6 +13,7 @@ export const Resource = ({ title, info, actions, children }: ResourceProps) => (
|
||||
title={<h1 className="text-4xl">{title}</h1>}
|
||||
subtitle={<h2 className="text-md">{info}</h2>}
|
||||
actions={actions}
|
||||
content={children}
|
||||
/>
|
||||
>
|
||||
{children}
|
||||
</Page>
|
||||
);
|
||||
|
||||
@@ -47,11 +47,10 @@ export const Resources = ({
|
||||
<NewDeployment open={open} set={setOpen} />
|
||||
</div>
|
||||
}
|
||||
content={
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{components(search)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{components(search)}
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
68
frontend/src/pages/dashboard/components/builds-chart.tsx
Normal file
68
frontend/src/pages/dashboard/components/builds-chart.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import {
|
||||
ColorType,
|
||||
IChartApi,
|
||||
ISeriesApi,
|
||||
Time,
|
||||
createChart,
|
||||
} from "lightweight-charts";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useRead } from "@hooks";
|
||||
|
||||
export const BuildChart = () => {
|
||||
const container_ref = useRef<HTMLDivElement>(null);
|
||||
const line_ref = useRef<IChartApi>();
|
||||
const series_ref = useRef<ISeriesApi<"Histogram">>();
|
||||
const { data } = useRead("GetBuildMonthlyStats", {});
|
||||
|
||||
const handleResize = () =>
|
||||
line_ref.current?.applyOptions({
|
||||
width: container_ref.current?.clientWidth,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) return;
|
||||
if (line_ref.current) line_ref.current.remove();
|
||||
const init = () => {
|
||||
if (!container_ref.current) return;
|
||||
line_ref.current = createChart(container_ref.current, {
|
||||
width: container_ref.current.clientWidth,
|
||||
height: container_ref.current.clientHeight,
|
||||
layout: {
|
||||
background: { type: ColorType.Solid, color: "transparent" },
|
||||
textColor: "grey",
|
||||
fontSize: 12,
|
||||
},
|
||||
grid: {
|
||||
horzLines: { color: "transparent" },
|
||||
vertLines: { color: "transparent" },
|
||||
},
|
||||
handleScale: false,
|
||||
handleScroll: false,
|
||||
});
|
||||
line_ref.current.timeScale().fitContent();
|
||||
series_ref.current = line_ref.current.addHistogramSeries({
|
||||
priceLineVisible: false,
|
||||
});
|
||||
const max = data.days.reduce((m, c) => Math.max(m, c.time), 0);
|
||||
series_ref.current.setData(
|
||||
data.days.map((d) => ({
|
||||
time: (d.ts / 1000) as Time,
|
||||
value: d.count,
|
||||
color:
|
||||
d.time > max * 0.7
|
||||
? "darkred"
|
||||
: d.time > max * 0.35
|
||||
? "darkorange"
|
||||
: "darkgreen",
|
||||
})) ?? []
|
||||
);
|
||||
};
|
||||
init();
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}, [data]);
|
||||
|
||||
return <div className="w-full max-w-full h-full" ref={container_ref} />;
|
||||
};
|
||||
@@ -14,11 +14,11 @@ export const DeploymentsChart = () => {
|
||||
|
||||
return (
|
||||
<Link to="/deployments" className="w-full">
|
||||
<Card className="pb-4" hoverable>
|
||||
<Card hoverable>
|
||||
<CardHeader className="flex-row justify-between items-center">
|
||||
<CardTitle>Deployments</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex gap-4 items-center w-full">
|
||||
<CardContent className="flex gap-4 items-center w-full h-[200px]">
|
||||
<div className="flex flex-col gap-2 text-muted-foreground w-full">
|
||||
<CardDescription>
|
||||
<span className="text-green-500 font-bold">
|
||||
@@ -47,7 +47,9 @@ export const DeploymentsChart = () => {
|
||||
</div>
|
||||
<div className="flex justify-end items-center w-full">
|
||||
<PieChart
|
||||
className="w-20 h-20"
|
||||
className="w-32"
|
||||
segmentsShift={0.5}
|
||||
lineWidth={35}
|
||||
data={[
|
||||
{
|
||||
color: "#22C55E",
|
||||
|
||||
@@ -20,6 +20,7 @@ import { Types } from "@monitor/client";
|
||||
import { NewBuilder } from "@resources/builder/new";
|
||||
import { ResourceTarget } from "@monitor/client/dist/types";
|
||||
import { BuilderCard } from "@resources/builder";
|
||||
import { Section } from "@layouts/page";
|
||||
|
||||
const NewResource = () => {
|
||||
const [open, set] = useState<Types.ResourceTarget["type"] | false>(false);
|
||||
@@ -68,6 +69,23 @@ export const RecentlyViewed = () => {
|
||||
const user = useUser().data;
|
||||
const recents = useRead("GetUser", {}).data?.recently_viewed;
|
||||
|
||||
return (
|
||||
<Section
|
||||
title="Recently Viewed"
|
||||
icon={<History className="w-4 h-4" />}
|
||||
actions=""
|
||||
>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{recents?.map(({ type, id }) => {
|
||||
if (type === "Deployment") return <DeploymentCard key={id} id={id} />;
|
||||
if (type === "Build") return <BuildCard key={id} id={id} />;
|
||||
if (type === "Server") return <ServerCard key={id} id={id} />;
|
||||
if (type === "Builder") return <BuilderCard key={id} id={id} />;
|
||||
})}
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-12">
|
||||
<div className="flex justify-between">
|
||||
@@ -82,14 +100,6 @@ export const RecentlyViewed = () => {
|
||||
</div>
|
||||
<NewResource />
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{recents?.map(({ type, id }) => {
|
||||
if (type === "Deployment") return <DeploymentCard key={id} id={id} />;
|
||||
if (type === "Build") return <BuildCard key={id} id={id} />;
|
||||
if (type === "Server") return <ServerCard key={id} id={id} />;
|
||||
if (type === "Builder") return <BuilderCard key={id} id={id} />;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,14 +5,13 @@ import {
|
||||
CardDescription,
|
||||
CardContent,
|
||||
} from "@ui/card";
|
||||
import { WithLoading } from "@components/util";
|
||||
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("ListServers", {});
|
||||
const { data } = useRead("ListServers", {});
|
||||
|
||||
const running = data?.filter((d) => d.status === ServerStatus.Ok).length;
|
||||
const stopped = data?.filter((d) => d.status === ServerStatus.NotOk).length;
|
||||
@@ -22,52 +21,52 @@ export const ServersChart = () => {
|
||||
|
||||
return (
|
||||
<Link to="/servers" className="w-full">
|
||||
<Card className="pb-4" hoverable>
|
||||
<Card hoverable>
|
||||
<CardHeader className="flex-row justify-between items-center">
|
||||
<CardTitle>Servers</CardTitle>
|
||||
</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 className="flex gap-4 items-center w-full h-[200px]">
|
||||
<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-32"
|
||||
segmentsShift={0.15}
|
||||
lineWidth={30}
|
||||
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>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
@@ -1,42 +1,71 @@
|
||||
import { Card, CardHeader, CardTitle } from "@ui/card";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@ui/card";
|
||||
import { ServersChart } from "./components/servers-chart";
|
||||
import { DeploymentsChart } from "./components/deployments-chart";
|
||||
import { Link } from "react-router-dom";
|
||||
import { RecentlyViewed } from "./components/recently-viewed";
|
||||
import { Box } from "lucide-react";
|
||||
import { BuildChart } from "./components/builds-chart";
|
||||
import { Page, Section } from "@layouts/page";
|
||||
import { useUser } from "@hooks";
|
||||
|
||||
export const Dashboard = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-24">
|
||||
<RecentlyViewed />
|
||||
<div className="flex flex-col gap-6 w-full">
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<Box className="w-4 h-4" />
|
||||
<h2 className="text-xl">My Resources</h2>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row gap-4">
|
||||
<div className="flex flex-col md:flex-row gap-4 w-full h-fit">
|
||||
<DeploymentsChart />
|
||||
<ServersChart />
|
||||
</div>
|
||||
<div className="flex gap-4 w-full h-fit">
|
||||
<Link to="/builds" className="w-full max-w-[50%] h-full">
|
||||
<Card hoverable>
|
||||
<CardHeader>
|
||||
<CardTitle>Builds</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</Link>
|
||||
<Link to="/builders" className="w-full max-w-[50%] h-full">
|
||||
<Card hoverable>
|
||||
<CardHeader>
|
||||
<CardTitle>Builders</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</Link>
|
||||
</div>
|
||||
const DashboardTitle = () => {
|
||||
const user = useUser().data;
|
||||
return <>Hello, {user?.username}.</>;
|
||||
};
|
||||
|
||||
const Builds = () => (
|
||||
<Link to="/builds" className="w-full">
|
||||
<Card hoverable>
|
||||
<CardHeader>
|
||||
<CardTitle>Builds</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="h-[200px]">
|
||||
<BuildChart />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
);
|
||||
|
||||
const MyResources = () => (
|
||||
<Section title="My Resources" icon={<Box className="w-4 h-4" />} actions="">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col lg:flex-row gap-4">
|
||||
<div className="flex flex-col md:flex-row gap-4 w-full">
|
||||
<DeploymentsChart />
|
||||
<ServersChart />
|
||||
</div>
|
||||
<Builds />
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<Link to="/builders" className="w-full h-full">
|
||||
<Card hoverable>
|
||||
<CardHeader>
|
||||
<CardTitle>Builders</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</Link>
|
||||
<Link to="/alerters" className="w-full h-full">
|
||||
<Card hoverable>
|
||||
<CardHeader>
|
||||
<CardTitle>alerters</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</Link>
|
||||
<Link to="/repos" className="w-full h-full">
|
||||
<Card hoverable>
|
||||
<CardHeader>
|
||||
<CardTitle>repos</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</Section>
|
||||
);
|
||||
|
||||
export const Dashboard = () => (
|
||||
<Page title={<DashboardTitle />} subtitle="" actions="">
|
||||
<RecentlyViewed />
|
||||
<MyResources />
|
||||
</Page>
|
||||
);
|
||||
|
||||
@@ -41,7 +41,7 @@ const TabsContent = React.forwardRef<
|
||||
<TabsPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
"ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
Reference in New Issue
Block a user