forked from github-starred/komodo
work on dashboard 2
This commit is contained in:
@@ -363,9 +363,11 @@ export const SystemCommand = ({
|
||||
export const AddExtraArgMenu = ({
|
||||
onSelect,
|
||||
type,
|
||||
disabled,
|
||||
}: {
|
||||
onSelect: (suggestion: string) => void;
|
||||
type: "Deployment" | "Build";
|
||||
disabled?: boolean;
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
@@ -376,6 +378,7 @@ export const AddExtraArgMenu = ({
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="flex items-center gap-2 w-[200px]"
|
||||
disabled={disabled}
|
||||
>
|
||||
<PlusCircle className="w-4 h-4" /> Add Extra Arg
|
||||
</Button>
|
||||
|
||||
@@ -23,6 +23,8 @@ const useAlerter = (id?: string) =>
|
||||
useRead("ListAlerters", {}).data?.find((d) => d.id === id);
|
||||
|
||||
export const AlerterComponents: RequiredResourceComponents = {
|
||||
list_item: (id) => useAlerter(id),
|
||||
|
||||
Dashboard: () => {
|
||||
const alerters_count = useRead("ListAlerters", {}).data?.length;
|
||||
return (
|
||||
@@ -91,7 +93,6 @@ export const AlerterComponents: RequiredResourceComponents = {
|
||||
Table: AlerterTable,
|
||||
|
||||
Name: ({ id }: { id: string }) => <>{useAlerter(id)?.name}</>,
|
||||
name: (id) => useAlerter(id)?.name,
|
||||
|
||||
Icon: () => <AlarmClock className="w-4 h-4" />,
|
||||
BigIcon: () => <AlarmClock className="w-8 h-8" />,
|
||||
|
||||
105
frontend/src/components/resources/build/chart.tsx
Normal file
105
frontend/src/components/resources/build/chart.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import {
|
||||
ColorType,
|
||||
IChartApi,
|
||||
ISeriesApi,
|
||||
Time,
|
||||
createChart,
|
||||
} from "lightweight-charts";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useRead } from "@lib/hooks";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@ui/card";
|
||||
import { Hammer } from "lucide-react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { convertTsMsToLocalUnixTsInSecs } from "@lib/utils";
|
||||
|
||||
export const BuildChart = () => {
|
||||
const container_ref = useRef<HTMLDivElement>(null);
|
||||
const line_ref = useRef<IChartApi>();
|
||||
const series_ref = useRef<ISeriesApi<"Histogram">>();
|
||||
const build_stats = useRead("GetBuildMonthlyStats", {}).data;
|
||||
const summary = useRead("GetBuildsSummary", {}).data;
|
||||
|
||||
const handleResize = () =>
|
||||
line_ref.current?.applyOptions({
|
||||
width: container_ref.current?.clientWidth,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!build_stats) return;
|
||||
if (line_ref.current) line_ref.current.remove();
|
||||
const init = () => {
|
||||
if (!container_ref.current) return;
|
||||
|
||||
// INIT LINE
|
||||
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();
|
||||
|
||||
// INIT SERIES
|
||||
series_ref.current = line_ref.current.addHistogramSeries({
|
||||
priceLineVisible: false,
|
||||
});
|
||||
const max = build_stats.days.reduce((m, c) => Math.max(m, c.time), 0);
|
||||
series_ref.current.setData(
|
||||
build_stats.days.map((d) => ({
|
||||
time: convertTsMsToLocalUnixTsInSecs(d.ts) as Time,
|
||||
value: d.count,
|
||||
color:
|
||||
d.time > max * 0.7
|
||||
? "darkred"
|
||||
: d.time > max * 0.35
|
||||
? "darkorange"
|
||||
: "darkgreen",
|
||||
})) ?? []
|
||||
);
|
||||
};
|
||||
|
||||
// Run the effect
|
||||
init();
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}, [build_stats]);
|
||||
|
||||
return (
|
||||
<Link to="/builds" className="w-full">
|
||||
<Card className="hover:bg-accent/50 transition-colors cursor-pointer">
|
||||
<CardHeader>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<CardTitle>Builds</CardTitle>
|
||||
<CardDescription className="flex gap-2">
|
||||
<div>{summary?.total} Total</div> |{" "}
|
||||
<div>{build_stats?.total_time.toFixed(2)} Hours</div>
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Hammer className="w-4 h-4" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="hidden xl:flex h-[200px]">
|
||||
<div className="w-full max-w-full h-full" ref={container_ref} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
@@ -166,6 +166,7 @@ export const BuildConfig = ({ id, titleOther }: { id: string; titleOther: ReactN
|
||||
],
|
||||
})
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
),
|
||||
components: {
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
import {
|
||||
ColorType,
|
||||
IChartApi,
|
||||
ISeriesApi,
|
||||
Time,
|
||||
createChart,
|
||||
} from "lightweight-charts";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useRead } from "@lib/hooks";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -14,90 +5,111 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@ui/card";
|
||||
import { PieChart } from "react-minimal-pie-chart";
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { Hammer } from "lucide-react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { convertTsMsToLocalUnixTsInSecs } from "@lib/utils";
|
||||
import { cn } from "@lib/utils";
|
||||
import {
|
||||
hex_color_by_intention,
|
||||
text_color_class_by_intention,
|
||||
} from "@lib/color";
|
||||
|
||||
export const BuildChart = () => {
|
||||
const container_ref = useRef<HTMLDivElement>(null);
|
||||
const line_ref = useRef<IChartApi>();
|
||||
const series_ref = useRef<ISeriesApi<"Histogram">>();
|
||||
const build_stats = useRead("GetBuildMonthlyStats", {}).data;
|
||||
export const BuildDashboard = () => {
|
||||
const summary = useRead("GetBuildsSummary", {}).data;
|
||||
|
||||
const handleResize = () =>
|
||||
line_ref.current?.applyOptions({
|
||||
width: container_ref.current?.clientWidth,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!build_stats) return;
|
||||
if (line_ref.current) line_ref.current.remove();
|
||||
const init = () => {
|
||||
if (!container_ref.current) return;
|
||||
|
||||
// INIT LINE
|
||||
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();
|
||||
|
||||
// INIT SERIES
|
||||
series_ref.current = line_ref.current.addHistogramSeries({
|
||||
priceLineVisible: false,
|
||||
});
|
||||
const max = build_stats.days.reduce((m, c) => Math.max(m, c.time), 0);
|
||||
series_ref.current.setData(
|
||||
build_stats.days.map((d) => ({
|
||||
time: convertTsMsToLocalUnixTsInSecs(d.ts) as Time,
|
||||
value: d.count,
|
||||
color:
|
||||
d.time > max * 0.7
|
||||
? "darkred"
|
||||
: d.time > max * 0.35
|
||||
? "darkorange"
|
||||
: "darkgreen",
|
||||
})) ?? []
|
||||
);
|
||||
};
|
||||
|
||||
// Run the effect
|
||||
init();
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}, [build_stats]);
|
||||
|
||||
return (
|
||||
<Link to="/builds" className="w-full">
|
||||
<Card className="hover:bg-accent/50 transition-colors cursor-pointer">
|
||||
<Link to="/builds">
|
||||
<Card className="hover:bg-accent/50 transition-colors cursor-pointer w-fit">
|
||||
<CardHeader>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<CardTitle>Builds</CardTitle>
|
||||
<CardDescription className="flex gap-2">
|
||||
<div>{summary?.total} Total</div> |{" "}
|
||||
<div>{build_stats?.total_time.toFixed(2)} Hours</div>
|
||||
</CardDescription>
|
||||
<CardDescription>{summary?.total} Total</CardDescription>
|
||||
</div>
|
||||
<Hammer className="w-4 h-4" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="hidden xl:flex h-[200px]">
|
||||
<div className="w-full max-w-full h-full" ref={container_ref} />
|
||||
<CardContent className="hidden xl:flex h-[200px] items-center justify-between gap-4">
|
||||
<div className="flex flex-col gap-2 text-muted-foreground w-full text-nowrap">
|
||||
<CardDescription className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Good"),
|
||||
"font-bold"
|
||||
)}
|
||||
>
|
||||
{summary?.ok}{" "}
|
||||
</span>
|
||||
Ok
|
||||
</CardDescription>
|
||||
<CardDescription className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Warning"),
|
||||
"font-bold"
|
||||
)}
|
||||
>
|
||||
{summary?.building}{" "}
|
||||
</span>
|
||||
Building
|
||||
</CardDescription>
|
||||
<CardDescription className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Critical"),
|
||||
"font-bold"
|
||||
)}
|
||||
>
|
||||
{summary?.failed}{" "}
|
||||
</span>
|
||||
Failed
|
||||
</CardDescription>
|
||||
<CardDescription className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Unknown"),
|
||||
"font-bold"
|
||||
)}
|
||||
>
|
||||
{summary?.unknown}{" "}
|
||||
</span>
|
||||
Unknown
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className="flex justify-end items-center w-full">
|
||||
<PieChart
|
||||
className="w-32 h-32"
|
||||
radius={42}
|
||||
lineWidth={30}
|
||||
data={[
|
||||
{
|
||||
color: hex_color_by_intention("Good"),
|
||||
value: summary?.ok ?? 0,
|
||||
title: "ok",
|
||||
key: "ok",
|
||||
},
|
||||
{
|
||||
color: hex_color_by_intention("Warning"),
|
||||
value: summary?.building ?? 0,
|
||||
title: "building",
|
||||
key: "building",
|
||||
},
|
||||
{
|
||||
color: hex_color_by_intention("Critical"),
|
||||
value: summary?.failed ?? 0,
|
||||
title: "failed",
|
||||
key: "failed",
|
||||
},
|
||||
{
|
||||
color: hex_color_by_intention("Unknown"),
|
||||
value: summary?.unknown ?? 0,
|
||||
title: "unknown",
|
||||
key: "unknown",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useRead } from "@lib/hooks";
|
||||
import { RequiredResourceComponents } from "@types";
|
||||
import { FolderGit, Hammer } from "lucide-react";
|
||||
import { BuildConfig } from "./config";
|
||||
import { BuildChart } from "./dashboard";
|
||||
import { BuildDashboard } from "./dashboard";
|
||||
import { BuildTable } from "./table";
|
||||
import { DeleteResource, NewResource } from "../common";
|
||||
import { DeploymentTable } from "../deployment/table";
|
||||
@@ -83,14 +83,15 @@ const ConfigOrDeployments = ({ id }: { id: string }) => {
|
||||
};
|
||||
|
||||
export const BuildComponents: RequiredResourceComponents = {
|
||||
Dashboard: BuildChart,
|
||||
list_item: (id) => useBuild(id),
|
||||
|
||||
Dashboard: BuildDashboard,
|
||||
|
||||
New: () => <NewResource type="Build" />,
|
||||
|
||||
Table: BuildTable,
|
||||
|
||||
Name: ({ id }) => <>{useBuild(id)?.name}</>,
|
||||
name: (id) => useBuild(id)?.name,
|
||||
|
||||
Icon: ({ id }) => <BuildIcon id={id} size={4} />,
|
||||
BigIcon: ({ id }) => <BuildIcon id={id} size={8} />,
|
||||
|
||||
@@ -36,6 +36,8 @@ export const BuilderInstanceType = ({ id }: { id: string }) => {
|
||||
};
|
||||
|
||||
export const BuilderComponents: RequiredResourceComponents = {
|
||||
list_item: (id) => useBuilder(id),
|
||||
|
||||
Dashboard: () => {
|
||||
const builders_count = useRead("ListBuilders", {}).data?.length;
|
||||
return (
|
||||
@@ -65,8 +67,9 @@ export const BuilderComponents: RequiredResourceComponents = {
|
||||
<NewLayout
|
||||
entityType="Builder"
|
||||
onSuccess={async () => {
|
||||
if (!type) return
|
||||
const id = (await mutateAsync({ name, config: { type, params: {} } }))._id?.$oid!;
|
||||
if (!type) return;
|
||||
const id = (await mutateAsync({ name, config: { type, params: {} } }))
|
||||
._id?.$oid!;
|
||||
nav(`/builders/${id}`);
|
||||
}}
|
||||
enabled={!!name && !!type}
|
||||
@@ -103,7 +106,6 @@ export const BuilderComponents: RequiredResourceComponents = {
|
||||
Table: BuilderTable,
|
||||
|
||||
Name: ({ id }: { id: string }) => <>{useBuilder(id)?.name}</>,
|
||||
name: (id) => useBuilder(id)?.name,
|
||||
|
||||
Icon: () => <Factory className="w-4 h-4" />,
|
||||
BigIcon: () => <Factory className="w-8 h-8" />,
|
||||
|
||||
@@ -35,6 +35,8 @@ import { NewLayout } from "@components/layouts";
|
||||
import { Types } from "@monitor/client";
|
||||
import { ConfigItem, DoubleInput } from "@components/config/util";
|
||||
import { usableResourcePath } from "@lib/utils";
|
||||
import { Card } from "@ui/card";
|
||||
import { TagsWithBadge } from "@components/tags";
|
||||
|
||||
export const ResourceDescription = ({
|
||||
type,
|
||||
@@ -87,7 +89,7 @@ export const ResourceSelector = ({
|
||||
selected: string | undefined;
|
||||
onSelect?: (id: string) => void;
|
||||
disabled?: boolean;
|
||||
align?: "start" | "center" | "end"
|
||||
align?: "start" | "center" | "end";
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [input, setInput] = useState("");
|
||||
@@ -311,11 +313,7 @@ export const LabelsConfig = ({
|
||||
</div>
|
||||
);
|
||||
|
||||
export const CopyGithubWebhook = ({
|
||||
path,
|
||||
}: {
|
||||
path: string;
|
||||
}) => {
|
||||
export const CopyGithubWebhook = ({ path }: { path: string }) => {
|
||||
const base_url = useRead("GetCoreInfo", {}).data?.github_webhook_base_url;
|
||||
const url = base_url + "/listener/github" + path;
|
||||
return (
|
||||
@@ -335,7 +333,7 @@ export const ServerSelector = ({
|
||||
selected: string | undefined;
|
||||
set: (input: Partial<Types.DeploymentConfig>) => void;
|
||||
disabled: boolean;
|
||||
align?: "start" | "center" | "end"
|
||||
align?: "start" | "center" | "end";
|
||||
}) => (
|
||||
<ConfigItem label="Server">
|
||||
<ResourceSelector
|
||||
@@ -347,3 +345,27 @@ export const ServerSelector = ({
|
||||
/>
|
||||
</ConfigItem>
|
||||
);
|
||||
|
||||
export const RecentCard = ({
|
||||
type,
|
||||
id,
|
||||
}: {
|
||||
type: UsableResource;
|
||||
id: string;
|
||||
}) => {
|
||||
const Components = ResourceComponents[type];
|
||||
const tags = Components.list_item(id)?.tags;
|
||||
return (
|
||||
<Link to={`${usableResourcePath(type)}/${id}`} className="h-full">
|
||||
<Card className="h-full px-6 py-4 flex flex-col justify-between hover:bg-accent/50 transition-colors cursor-pointer">
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<Components.Name id={id} />
|
||||
<Components.Icon id={id} />
|
||||
</div>
|
||||
<div className="flex items-end justify-end gap-2 w-full">
|
||||
<TagsWithBadge tag_ids={tags} />
|
||||
</div>
|
||||
</Card>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -202,6 +202,7 @@ export const DeploymentConfig = ({
|
||||
],
|
||||
})
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
),
|
||||
components: {
|
||||
|
||||
@@ -19,8 +19,8 @@ export const DeploymentsChart = () => {
|
||||
const summary = useRead("GetDeploymentsSummary", {}).data;
|
||||
|
||||
return (
|
||||
<Link to="/deployments" className="w-full">
|
||||
<Card className="hover:bg-accent/50 transition-colors cursor-pointer">
|
||||
<Link to="/deployments">
|
||||
<Card className="hover:bg-accent/50 transition-colors cursor-pointer w-fit">
|
||||
<CardHeader>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
@@ -30,9 +30,9 @@ export const DeploymentsChart = () => {
|
||||
<Rocket className="w-4 h-4" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="hidden xl:flex h-[200px] items-center justify-between">
|
||||
<div className="flex flex-col gap-2 text-muted-foreground w-full">
|
||||
<CardDescription>
|
||||
<CardContent className="hidden xl:flex h-[200px] items-center justify-between gap-4">
|
||||
<div className="flex flex-col gap-2 text-muted-foreground w-full text-nowrap">
|
||||
<CardDescription className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Good"),
|
||||
@@ -43,7 +43,7 @@ export const DeploymentsChart = () => {
|
||||
</span>
|
||||
Running
|
||||
</CardDescription>
|
||||
<CardDescription>
|
||||
<CardDescription className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Critical"),
|
||||
@@ -54,7 +54,7 @@ export const DeploymentsChart = () => {
|
||||
</span>
|
||||
Stopped
|
||||
</CardDescription>
|
||||
<CardDescription>
|
||||
<CardDescription className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Neutral"),
|
||||
@@ -65,7 +65,7 @@ export const DeploymentsChart = () => {
|
||||
</span>
|
||||
Not Deployed
|
||||
</CardDescription>
|
||||
<CardDescription>
|
||||
<CardDescription className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Unknown"),
|
||||
|
||||
@@ -99,6 +99,8 @@ const DeploymentIcon = ({ id, size }: { id?: string; size: number }) => {
|
||||
};
|
||||
|
||||
export const DeploymentComponents: RequiredResourceComponents = {
|
||||
list_item: (id) => useDeployment(id),
|
||||
|
||||
Dashboard: DeploymentsChart,
|
||||
|
||||
New: () => <NewResource type="Deployment" />,
|
||||
@@ -109,7 +111,6 @@ export const DeploymentComponents: RequiredResourceComponents = {
|
||||
},
|
||||
|
||||
Name: ({ id }) => <>{useDeployment(id)?.name}</>,
|
||||
name: (id) => useDeployment(id)?.name,
|
||||
|
||||
Icon: ({ id }) => <DeploymentIcon id={id} size={4} />,
|
||||
BigIcon: ({ id }) => <DeploymentIcon id={id} size={8} />,
|
||||
|
||||
117
frontend/src/components/resources/procedure/dashboard.tsx
Normal file
117
frontend/src/components/resources/procedure/dashboard.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@ui/card";
|
||||
import { PieChart } from "react-minimal-pie-chart";
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { Route } from "lucide-react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { cn } from "@lib/utils";
|
||||
import {
|
||||
hex_color_by_intention,
|
||||
text_color_class_by_intention,
|
||||
} from "@lib/color";
|
||||
|
||||
export const ProcedureDashboard = () => {
|
||||
const summary = useRead("GetProceduresSummary", {}).data;
|
||||
|
||||
return (
|
||||
<Link to="/procedures">
|
||||
<Card className="hover:bg-accent/50 transition-colors cursor-pointer w-fit">
|
||||
<CardHeader>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<CardTitle>Procedures</CardTitle>
|
||||
<CardDescription>{summary?.total} Total</CardDescription>
|
||||
</div>
|
||||
<Route className="w-4 h-4" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="hidden xl:flex h-[200px] items-center justify-between gap-4">
|
||||
<div className="flex flex-col gap-2 text-muted-foreground w-full text-nowrap">
|
||||
<CardDescription className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Good"),
|
||||
"font-bold"
|
||||
)}
|
||||
>
|
||||
{summary?.ok}{" "}
|
||||
</span>
|
||||
Ok
|
||||
</CardDescription>
|
||||
<CardDescription className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Warning"),
|
||||
"font-bold"
|
||||
)}
|
||||
>
|
||||
{summary?.running}{" "}
|
||||
</span>
|
||||
Running
|
||||
</CardDescription>
|
||||
<CardDescription className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Critical"),
|
||||
"font-bold"
|
||||
)}
|
||||
>
|
||||
{summary?.failed}{" "}
|
||||
</span>
|
||||
Failed
|
||||
</CardDescription>
|
||||
<CardDescription className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Unknown"),
|
||||
"font-bold"
|
||||
)}
|
||||
>
|
||||
{summary?.unknown}{" "}
|
||||
</span>
|
||||
Unknown
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className="flex justify-end items-center w-full">
|
||||
<PieChart
|
||||
className="w-32 h-32"
|
||||
radius={42}
|
||||
lineWidth={30}
|
||||
data={[
|
||||
{
|
||||
color: hex_color_by_intention("Good"),
|
||||
value: summary?.ok ?? 0,
|
||||
title: "ok",
|
||||
key: "ok",
|
||||
},
|
||||
{
|
||||
color: hex_color_by_intention("Warning"),
|
||||
value: summary?.running ?? 0,
|
||||
title: "running",
|
||||
key: "running",
|
||||
},
|
||||
{
|
||||
color: hex_color_by_intention("Critical"),
|
||||
value: summary?.failed ?? 0,
|
||||
title: "failed",
|
||||
key: "failed",
|
||||
},
|
||||
{
|
||||
color: hex_color_by_intention("Unknown"),
|
||||
value: summary?.unknown ?? 0,
|
||||
title: "unknown",
|
||||
key: "unknown",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
@@ -7,13 +7,18 @@ import { Link } from "react-router-dom";
|
||||
import { ProcedureConfig } from "./config";
|
||||
import { ProcedureTable } from "./table";
|
||||
import { DeleteResource, NewResource } from "../common";
|
||||
import { bg_color_class_by_intention, procedure_state_intention } from "@lib/color";
|
||||
import {
|
||||
bg_color_class_by_intention,
|
||||
procedure_state_intention,
|
||||
} from "@lib/color";
|
||||
import { cn } from "@lib/utils";
|
||||
|
||||
const useProcedure = (id?: string) =>
|
||||
useRead("ListProcedures", {}).data?.find((d) => d.id === id);
|
||||
|
||||
export const ProcedureComponents: RequiredResourceComponents = {
|
||||
list_item: (id) => useProcedure(id),
|
||||
|
||||
Dashboard: () => {
|
||||
const procedure_count = useRead("ListProcedures", {}).data?.length;
|
||||
return (
|
||||
@@ -38,7 +43,6 @@ export const ProcedureComponents: RequiredResourceComponents = {
|
||||
Table: ProcedureTable,
|
||||
|
||||
Name: ({ id }) => <>{useProcedure(id)?.name}</>,
|
||||
name: (id) => useProcedure(id)?.name,
|
||||
|
||||
Icon: () => <Route className="w-4" />,
|
||||
BigIcon: () => <Route className="w-8" />,
|
||||
@@ -46,7 +50,9 @@ export const ProcedureComponents: RequiredResourceComponents = {
|
||||
Status: {
|
||||
State: ({ id }) => {
|
||||
let state = useProcedure(id)?.info.state;
|
||||
const color = bg_color_class_by_intention(procedure_state_intention(state));
|
||||
const color = bg_color_class_by_intention(
|
||||
procedure_state_intention(state)
|
||||
);
|
||||
return (
|
||||
<Card className={cn("w-fit", color)}>
|
||||
<CardHeader className="py-0 px-2">{state}</CardHeader>
|
||||
|
||||
117
frontend/src/components/resources/repo/dashboard.tsx
Normal file
117
frontend/src/components/resources/repo/dashboard.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@ui/card";
|
||||
import { PieChart } from "react-minimal-pie-chart";
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { GitBranch } from "lucide-react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { cn } from "@lib/utils";
|
||||
import {
|
||||
hex_color_by_intention,
|
||||
text_color_class_by_intention,
|
||||
} from "@lib/color";
|
||||
|
||||
export const RepoDashboard = () => {
|
||||
const summary = useRead("GetReposSummary", {}).data;
|
||||
|
||||
return (
|
||||
<Link to="/repos">
|
||||
<Card className="hover:bg-accent/50 transition-colors cursor-pointer w-fit">
|
||||
<CardHeader>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<CardTitle>Repos</CardTitle>
|
||||
<CardDescription>{summary?.total} Total</CardDescription>
|
||||
</div>
|
||||
<GitBranch className="w-4 h-4" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="hidden xl:flex h-[200px] items-center justify-between gap-4">
|
||||
<div className="flex flex-col gap-2 text-muted-foreground w-full text-nowrap">
|
||||
<CardDescription className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Good"),
|
||||
"font-bold"
|
||||
)}
|
||||
>
|
||||
{summary?.ok}{" "}
|
||||
</span>
|
||||
Ok
|
||||
</CardDescription>
|
||||
<CardDescription className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Warning"),
|
||||
"font-bold"
|
||||
)}
|
||||
>
|
||||
{(summary?.cloning ?? 0) + (summary?.pulling ?? 0)}{" "}
|
||||
</span>
|
||||
Pulling
|
||||
</CardDescription>
|
||||
<CardDescription className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Critical"),
|
||||
"font-bold"
|
||||
)}
|
||||
>
|
||||
{summary?.failed}{" "}
|
||||
</span>
|
||||
Failed
|
||||
</CardDescription>
|
||||
<CardDescription className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Unknown"),
|
||||
"font-bold"
|
||||
)}
|
||||
>
|
||||
{summary?.unknown}{" "}
|
||||
</span>
|
||||
Unknown
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className="flex justify-end items-center w-full">
|
||||
<PieChart
|
||||
className="w-32 h-32"
|
||||
radius={42}
|
||||
lineWidth={30}
|
||||
data={[
|
||||
{
|
||||
color: hex_color_by_intention("Good"),
|
||||
value: summary?.ok ?? 0,
|
||||
title: "ok",
|
||||
key: "ok",
|
||||
},
|
||||
{
|
||||
color: hex_color_by_intention("Warning"),
|
||||
value: (summary?.cloning ?? 0) + (summary?.pulling ?? 0),
|
||||
title: "pulling",
|
||||
key: "pulling",
|
||||
},
|
||||
{
|
||||
color: hex_color_by_intention("Critical"),
|
||||
value: summary?.failed ?? 0,
|
||||
title: "failed",
|
||||
key: "failed",
|
||||
},
|
||||
{
|
||||
color: hex_color_by_intention("Unknown"),
|
||||
value: summary?.unknown ?? 0,
|
||||
title: "unknown",
|
||||
key: "unknown",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
@@ -27,6 +27,8 @@ const RepoIcon = ({ id, size }: { id?: string; size: number }) => {
|
||||
};
|
||||
|
||||
export const RepoComponents: RequiredResourceComponents = {
|
||||
list_item: (id) => useRepo(id),
|
||||
|
||||
Dashboard: () => {
|
||||
const repo_count = useRead("ListRepos", {}).data?.length;
|
||||
return (
|
||||
@@ -51,7 +53,6 @@ export const RepoComponents: RequiredResourceComponents = {
|
||||
Table: RepoTable,
|
||||
|
||||
Name: ({ id }) => <>{useRepo(id)?.name}</>,
|
||||
name: (id) => useRepo(id)?.name,
|
||||
|
||||
Icon: ({ id }) => <RepoIcon id={id} size={4} />,
|
||||
BigIcon: ({ id }) => <RepoIcon id={id} size={8} />,
|
||||
|
||||
@@ -24,6 +24,8 @@ export const useServerTemplate = (id?: string) =>
|
||||
useRead("ListServerTemplates", {}).data?.find((d) => d.id === id);
|
||||
|
||||
export const ServerTemplateComponents: RequiredResourceComponents = {
|
||||
list_item: (id) => useServerTemplate(id),
|
||||
|
||||
Dashboard: () => {
|
||||
const count = useRead("ListServerTemplates", {}).data?.length;
|
||||
return (
|
||||
@@ -91,7 +93,6 @@ export const ServerTemplateComponents: RequiredResourceComponents = {
|
||||
Table: ServerTemplateTable,
|
||||
|
||||
Name: ({ id }) => <>{useServerTemplate(id)?.name}</>,
|
||||
name: (id) => useServerTemplate(id)?.name,
|
||||
|
||||
Icon: () => <ServerCog className="w-4 h-4" />,
|
||||
BigIcon: () => <ServerCog className="w-8 h-8" />,
|
||||
|
||||
@@ -104,6 +104,8 @@ const ConfigOrDeployments = ({ id }: { id: string }) => {
|
||||
};
|
||||
|
||||
export const ServerComponents: RequiredResourceComponents = {
|
||||
list_item: (id) => useServer(id),
|
||||
|
||||
Dashboard: ServersChart,
|
||||
|
||||
New: () => <NewResource type="Server" />,
|
||||
@@ -111,7 +113,6 @@ export const ServerComponents: RequiredResourceComponents = {
|
||||
Table: ServerTable,
|
||||
|
||||
Name: ({ id }: { id: string }) => <>{useServer(id)?.name}</>,
|
||||
name: (id) => useServer(id)?.name,
|
||||
|
||||
Icon: ({ id }) => <_ServerIcon id={id} size={4} />,
|
||||
BigIcon: ({ id }) => <_ServerIcon id={id} size={8} />,
|
||||
|
||||
@@ -63,7 +63,7 @@ export function version_is_none({ major, minor, patch }: Types.Version) {
|
||||
|
||||
export function resource_name(type: UsableResource, id: string) {
|
||||
const Components = ResourceComponents[type];
|
||||
return Components.name(id);
|
||||
return Components.list_item(id)?.name;
|
||||
}
|
||||
|
||||
export const level_to_number = (level: Types.PermissionLevel | undefined) => {
|
||||
|
||||
@@ -1,12 +1,34 @@
|
||||
import { OpenAlerts } from "@components/alert";
|
||||
import { Page } from "@components/layouts";
|
||||
import { ResourceComponents } from "@components/resources";
|
||||
import { RecentCard } from "@components/resources/common";
|
||||
import { AllUpdates } from "@components/updates/resource";
|
||||
import { useUser } from "@lib/hooks";
|
||||
import { UsableResource } from "@types";
|
||||
import { Separator } from "@ui/separator";
|
||||
|
||||
export const Dashboard = () => {
|
||||
return (
|
||||
const user = useUser().data;
|
||||
return (
|
||||
<Page title="">
|
||||
<OpenAlerts />
|
||||
<AllUpdates />
|
||||
|
||||
<div className="flex gap-4">
|
||||
<ResourceComponents.Deployment.Dashboard />
|
||||
<div className="py-2">
|
||||
<Separator orientation="vertical" />
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-4 w-full">
|
||||
{user?.recent_deployments?.slice(0, 6).map((id) => (
|
||||
<RecentCard type="Deployment" id={id} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// const ResourceRow = ({ type }: { type: UsableResource }) => {
|
||||
// const Components =
|
||||
// }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { homeViewAtom } from "@main";
|
||||
import { useAtom } from "jotai";
|
||||
import { Dashboard } from "./dashboard";
|
||||
import { Dashboard } from "./dashboard2";
|
||||
import { AllResources } from "./all_resources";
|
||||
import { Tree } from "./tree";
|
||||
import { useSetTitle } from "@lib/hooks";
|
||||
|
||||
3
frontend/src/types.d.ts
vendored
3
frontend/src/types.d.ts
vendored
@@ -6,6 +6,8 @@ type IdComponent = React.FC<{ id: string }>;
|
||||
type OptionalIdComponent = React.FC<{ id?: string }>;
|
||||
|
||||
export interface RequiredResourceComponents {
|
||||
list_item: (id: string) => Types.ResourceListItem<unknown> | undefined;
|
||||
|
||||
/** Summary card for use in dashboard */
|
||||
Dashboard: React.FC;
|
||||
|
||||
@@ -17,7 +19,6 @@ export interface RequiredResourceComponents {
|
||||
|
||||
/** Name of the resource */
|
||||
Name: IdComponent;
|
||||
name: (id: string) => string | undefined;
|
||||
|
||||
/** Icon for the component */
|
||||
Icon: OptionalIdComponent;
|
||||
|
||||
Reference in New Issue
Block a user