From c9d65300c9be1898a7f8468c6c927a05ff5a12a1 Mon Sep 17 00:00:00 2001 From: mbecker20 Date: Fri, 17 May 2024 01:35:34 -0700 Subject: [PATCH] work on dashboard 2 --- frontend/src/components/config/util.tsx | 3 + .../components/resources/alerter/index.tsx | 3 +- .../src/components/resources/build/chart.tsx | 105 +++++++++++ .../src/components/resources/build/config.tsx | 1 + .../components/resources/build/dashboard.tsx | 170 ++++++++++-------- .../src/components/resources/build/index.tsx | 7 +- .../components/resources/builder/index.tsx | 8 +- frontend/src/components/resources/common.tsx | 38 +++- .../resources/deployment/config/index.tsx | 1 + .../resources/deployment/dashboard.tsx | 16 +- .../components/resources/deployment/index.tsx | 3 +- .../resources/procedure/dashboard.tsx | 117 ++++++++++++ .../components/resources/procedure/index.tsx | 12 +- .../components/resources/repo/dashboard.tsx | 117 ++++++++++++ .../src/components/resources/repo/index.tsx | 3 +- .../resources/server-template/index.tsx | 3 +- .../src/components/resources/server/index.tsx | 3 +- frontend/src/lib/utils.ts | 2 +- frontend/src/pages/home/dashboard2.tsx | 26 ++- frontend/src/pages/home/index.tsx | 2 +- frontend/src/types.d.ts | 3 +- 21 files changed, 529 insertions(+), 114 deletions(-) create mode 100644 frontend/src/components/resources/build/chart.tsx create mode 100644 frontend/src/components/resources/procedure/dashboard.tsx create mode 100644 frontend/src/components/resources/repo/dashboard.tsx diff --git a/frontend/src/components/config/util.tsx b/frontend/src/components/config/util.tsx index b746a00ed..b8ee80566 100644 --- a/frontend/src/components/config/util.tsx +++ b/frontend/src/components/config/util.tsx @@ -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 = ({ diff --git a/frontend/src/components/resources/alerter/index.tsx b/frontend/src/components/resources/alerter/index.tsx index e9b716945..a48b7caae 100644 --- a/frontend/src/components/resources/alerter/index.tsx +++ b/frontend/src/components/resources/alerter/index.tsx @@ -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: () => , BigIcon: () => , diff --git a/frontend/src/components/resources/build/chart.tsx b/frontend/src/components/resources/build/chart.tsx new file mode 100644 index 000000000..9a45ad483 --- /dev/null +++ b/frontend/src/components/resources/build/chart.tsx @@ -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(null); + const line_ref = useRef(); + const series_ref = useRef>(); + 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 ( + + + +
+
+ Builds + +
{summary?.total} Total
|{" "} +
{build_stats?.total_time.toFixed(2)} Hours
+
+
+ +
+
+ +
+ + + + ); +}; diff --git a/frontend/src/components/resources/build/config.tsx b/frontend/src/components/resources/build/config.tsx index dfd513f4e..1bc969807 100644 --- a/frontend/src/components/resources/build/config.tsx +++ b/frontend/src/components/resources/build/config.tsx @@ -166,6 +166,7 @@ export const BuildConfig = ({ id, titleOther }: { id: string; titleOther: ReactN ], }) } + disabled={disabled} /> ), components: { diff --git a/frontend/src/components/resources/build/dashboard.tsx b/frontend/src/components/resources/build/dashboard.tsx index 9a45ad483..0f56952e9 100644 --- a/frontend/src/components/resources/build/dashboard.tsx +++ b/frontend/src/components/resources/build/dashboard.tsx @@ -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(null); - const line_ref = useRef(); - const series_ref = useRef>(); - 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 ( - - + +
Builds - -
{summary?.total} Total
|{" "} -
{build_stats?.total_time.toFixed(2)} Hours
-
+ {summary?.total} Total
- -
+ +
+ + + {summary?.ok}{" "} + + Ok + + + + {summary?.building}{" "} + + Building + + + + {summary?.failed}{" "} + + Failed + + + + {summary?.unknown}{" "} + + Unknown + +
+
+ +
diff --git a/frontend/src/components/resources/build/index.tsx b/frontend/src/components/resources/build/index.tsx index 26eefcf46..7b85c551d 100644 --- a/frontend/src/components/resources/build/index.tsx +++ b/frontend/src/components/resources/build/index.tsx @@ -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: () => , Table: BuildTable, Name: ({ id }) => <>{useBuild(id)?.name}, - name: (id) => useBuild(id)?.name, Icon: ({ id }) => , BigIcon: ({ id }) => , diff --git a/frontend/src/components/resources/builder/index.tsx b/frontend/src/components/resources/builder/index.tsx index d9fa55393..530c56c03 100644 --- a/frontend/src/components/resources/builder/index.tsx +++ b/frontend/src/components/resources/builder/index.tsx @@ -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 = { { - 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: () => , BigIcon: () => , diff --git a/frontend/src/components/resources/common.tsx b/frontend/src/components/resources/common.tsx index 7e283bd3c..ef7dbc0ec 100644 --- a/frontend/src/components/resources/common.tsx +++ b/frontend/src/components/resources/common.tsx @@ -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 = ({
); -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) => void; disabled: boolean; - align?: "start" | "center" | "end" + align?: "start" | "center" | "end"; }) => ( -); \ No newline at end of file +); + +export const RecentCard = ({ + type, + id, +}: { + type: UsableResource; + id: string; +}) => { + const Components = ResourceComponents[type]; + const tags = Components.list_item(id)?.tags; + return ( + + +
+ + +
+
+ +
+
+ + ); +}; diff --git a/frontend/src/components/resources/deployment/config/index.tsx b/frontend/src/components/resources/deployment/config/index.tsx index bb58a3b86..63f65375a 100644 --- a/frontend/src/components/resources/deployment/config/index.tsx +++ b/frontend/src/components/resources/deployment/config/index.tsx @@ -202,6 +202,7 @@ export const DeploymentConfig = ({ ], }) } + disabled={disabled} /> ), components: { diff --git a/frontend/src/components/resources/deployment/dashboard.tsx b/frontend/src/components/resources/deployment/dashboard.tsx index 3a9306ead..93fc3cc80 100644 --- a/frontend/src/components/resources/deployment/dashboard.tsx +++ b/frontend/src/components/resources/deployment/dashboard.tsx @@ -19,8 +19,8 @@ export const DeploymentsChart = () => { const summary = useRead("GetDeploymentsSummary", {}).data; return ( - - + +
@@ -30,9 +30,9 @@ export const DeploymentsChart = () => {
- -
- + +
+ { Running - + { Stopped - + { Not Deployed - + { }; export const DeploymentComponents: RequiredResourceComponents = { + list_item: (id) => useDeployment(id), + Dashboard: DeploymentsChart, New: () => , @@ -109,7 +111,6 @@ export const DeploymentComponents: RequiredResourceComponents = { }, Name: ({ id }) => <>{useDeployment(id)?.name}, - name: (id) => useDeployment(id)?.name, Icon: ({ id }) => , BigIcon: ({ id }) => , diff --git a/frontend/src/components/resources/procedure/dashboard.tsx b/frontend/src/components/resources/procedure/dashboard.tsx new file mode 100644 index 000000000..4d57e82ed --- /dev/null +++ b/frontend/src/components/resources/procedure/dashboard.tsx @@ -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 ( + + + +
+
+ Procedures + {summary?.total} Total +
+ +
+
+ +
+ + + {summary?.ok}{" "} + + Ok + + + + {summary?.running}{" "} + + Running + + + + {summary?.failed}{" "} + + Failed + + + + {summary?.unknown}{" "} + + Unknown + +
+
+ +
+
+
+ + ); +}; diff --git a/frontend/src/components/resources/procedure/index.tsx b/frontend/src/components/resources/procedure/index.tsx index 05ddaf003..a982a6874 100644 --- a/frontend/src/components/resources/procedure/index.tsx +++ b/frontend/src/components/resources/procedure/index.tsx @@ -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: () => , BigIcon: () => , @@ -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 ( {state} diff --git a/frontend/src/components/resources/repo/dashboard.tsx b/frontend/src/components/resources/repo/dashboard.tsx new file mode 100644 index 000000000..0d763c5ec --- /dev/null +++ b/frontend/src/components/resources/repo/dashboard.tsx @@ -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 ( + + + +
+
+ Repos + {summary?.total} Total +
+ +
+
+ +
+ + + {summary?.ok}{" "} + + Ok + + + + {(summary?.cloning ?? 0) + (summary?.pulling ?? 0)}{" "} + + Pulling + + + + {summary?.failed}{" "} + + Failed + + + + {summary?.unknown}{" "} + + Unknown + +
+
+ +
+
+
+ + ); +}; diff --git a/frontend/src/components/resources/repo/index.tsx b/frontend/src/components/resources/repo/index.tsx index 23a001702..816847890 100644 --- a/frontend/src/components/resources/repo/index.tsx +++ b/frontend/src/components/resources/repo/index.tsx @@ -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 }) => , BigIcon: ({ id }) => , diff --git a/frontend/src/components/resources/server-template/index.tsx b/frontend/src/components/resources/server-template/index.tsx index 2a75bb472..79ca9ef83 100644 --- a/frontend/src/components/resources/server-template/index.tsx +++ b/frontend/src/components/resources/server-template/index.tsx @@ -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: () => , BigIcon: () => , diff --git a/frontend/src/components/resources/server/index.tsx b/frontend/src/components/resources/server/index.tsx index 159666d16..b51345c78 100644 --- a/frontend/src/components/resources/server/index.tsx +++ b/frontend/src/components/resources/server/index.tsx @@ -104,6 +104,8 @@ const ConfigOrDeployments = ({ id }: { id: string }) => { }; export const ServerComponents: RequiredResourceComponents = { + list_item: (id) => useServer(id), + Dashboard: ServersChart, New: () => , @@ -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} />, diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index 4988ac4c7..bf8d93cde 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -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) => { diff --git a/frontend/src/pages/home/dashboard2.tsx b/frontend/src/pages/home/dashboard2.tsx index e68e8fa25..591b0caaf 100644 --- a/frontend/src/pages/home/dashboard2.tsx +++ b/frontend/src/pages/home/dashboard2.tsx @@ -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 ( + +
+ +
+ +
+
+ {user?.recent_deployments?.slice(0, 6).map((id) => ( + + ))} +
+
); -} \ No newline at end of file +}; + +// const ResourceRow = ({ type }: { type: UsableResource }) => { +// const Components = +// } diff --git a/frontend/src/pages/home/index.tsx b/frontend/src/pages/home/index.tsx index f36de2eda..1a4f56cb4 100644 --- a/frontend/src/pages/home/index.tsx +++ b/frontend/src/pages/home/index.tsx @@ -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"; diff --git a/frontend/src/types.d.ts b/frontend/src/types.d.ts index a7957fa89..a99b4d540 100644 --- a/frontend/src/types.d.ts +++ b/frontend/src/types.d.ts @@ -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 | 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;